ver. 0.294: Remove all 12 legacy autoload/shop/ classes (~2363 lines)
Complete Domain-Driven Architecture migration: - Phase 1-4: Transport, ProductSet, Coupon, Shop, Search, Basket, ProductCustomField, Category, ProductAttribute, Promotion - Phase 5: Order (~562 lines) + Product (~952 lines) - ~20 Product methods migrated to ProductRepository - Apilo sync migrated to OrderAdminService - Production hotfixes: stale Redis cache (prices 0.00), unqualified Product:: refs in LayoutEngine, object->array template conversion - AttributeRepository::getAttributeValueById() Redis cache added Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1218,7 +1218,7 @@ class ProductRepository
|
||||
$vat = $this->db->get( 'pp_shop_products', 'vat', [ 'id' => $productId ] );
|
||||
$attributeRepository = new \Domain\Attribute\AttributeRepository( $this->db );
|
||||
|
||||
$permutations = \shop\Product::array_cartesian( $attributes );
|
||||
$permutations = self::arrayCartesian( $attributes );
|
||||
if ( !\Shared\Helpers\Helpers::is_array_fix( $permutations ) ) {
|
||||
return true;
|
||||
}
|
||||
@@ -1544,11 +1544,12 @@ class ProductRepository
|
||||
|
||||
if ( \Shared\Helpers\Helpers::is_array_fix( $rows ) ) {
|
||||
foreach ( $rows as $productId ) {
|
||||
$product = \shop\Product::getFromCache( $productId, $lang_id );
|
||||
$product = $this->findCached( $productId, $lang_id );
|
||||
if ( !$product ) continue;
|
||||
|
||||
if ( is_array( $product->product_combinations ) && count( $product->product_combinations ) ) {
|
||||
foreach ( $product->product_combinations as $productCombination ) {
|
||||
if ( $productCombination->quantity !== null || $productCombination->stock_0_buy ) {
|
||||
if ( is_array( $product['product_combinations'] ) && count( $product['product_combinations'] ) ) {
|
||||
foreach ( $product['product_combinations'] as $productCombination ) {
|
||||
if ( $productCombination['quantity'] !== null || $productCombination['stock_0_buy'] ) {
|
||||
$this->appendCombinationToXml( $doc, $channelNode, $product, $productCombination, $domainPrefix, $url );
|
||||
}
|
||||
}
|
||||
@@ -1567,29 +1568,29 @@ class ProductRepository
|
||||
private function appendCombinationToXml(\DOMDocument $doc, \DOMElement $channelNode, $product, $combination, string $domainPrefix, string $url): void
|
||||
{
|
||||
$itemNode = $channelNode->appendChild( $doc->createElement( 'item' ) );
|
||||
$itemNode->appendChild( $doc->createElement( 'g:id', $combination->id ) );
|
||||
$itemNode->appendChild( $doc->createElement( 'g:item_group_id', $product->id ) );
|
||||
$itemNode->appendChild( $doc->createElement( 'g:id', $combination['id'] ) );
|
||||
$itemNode->appendChild( $doc->createElement( 'g:item_group_id', $product['id'] ) );
|
||||
|
||||
for ( $l = 0; $l <= 4; $l++ ) {
|
||||
$label = 'custom_label_' . $l;
|
||||
if ( $product->$label ) {
|
||||
$itemNode->appendChild( $doc->createElement( 'g:' . $label, $product->$label ) );
|
||||
if ( isset( $product[$label] ) && $product[$label] ) {
|
||||
$itemNode->appendChild( $doc->createElement( 'g:' . $label, $product[$label] ) );
|
||||
}
|
||||
}
|
||||
|
||||
$title = $product->language['xml_name'] ?: $product->language['name'];
|
||||
$itemNode->appendChild( $doc->createElement( 'title', str_replace( '&', '&', $title ) . ' - ' . $product->generateSubtitleFromAttributes( $combination->permutation_hash ) ) );
|
||||
$title = $product['language']['xml_name'] ?: $product['language']['name'];
|
||||
$itemNode->appendChild( $doc->createElement( 'title', str_replace( '&', '&', $title ) . ' - ' . $this->generateSubtitleFromAttributes( $combination['permutation_hash'] ) ) );
|
||||
|
||||
$gtin = $product->ean ?: $this->generateEAN( $product->id );
|
||||
$gtin = $product['ean'] ?: $this->generateEAN( $product['id'] );
|
||||
$itemNode->appendChild( $doc->createElement( 'g:gtin', $gtin ) );
|
||||
|
||||
$desc = $product->language['short_description'] ?: $product->language['name'];
|
||||
$desc = $product['language']['short_description'] ?: $product['language']['name'];
|
||||
$itemNode->appendChild( $doc->createElement( 'g:description', html_entity_decode( strip_tags( $desc ) ) ) );
|
||||
|
||||
if ( $product->language['seo_link'] ) {
|
||||
$link = $domainPrefix . '://' . $url . '/' . \Shared\Helpers\Helpers::seo( $product->language['seo_link'] ) . '/' . str_replace( '|', '/', $combination->permutation_hash );
|
||||
if ( $product['language']['seo_link'] ) {
|
||||
$link = $domainPrefix . '://' . $url . '/' . \Shared\Helpers\Helpers::seo( $product['language']['seo_link'] ) . '/' . str_replace( '|', '/', $combination['permutation_hash'] );
|
||||
} else {
|
||||
$link = $domainPrefix . '://' . $url . '/p-' . $product->id . '-' . \Shared\Helpers\Helpers::seo( $product->language['name'] ) . '/' . str_replace( '|', '/', $combination->permutation_hash );
|
||||
$link = $domainPrefix . '://' . $url . '/p-' . $product['id'] . '-' . \Shared\Helpers\Helpers::seo( $product['language']['name'] ) . '/' . str_replace( '|', '/', $combination['permutation_hash'] );
|
||||
}
|
||||
$itemNode->appendChild( $doc->createElement( 'link', $link ) );
|
||||
|
||||
@@ -1597,32 +1598,32 @@ class ProductRepository
|
||||
|
||||
$itemNode->appendChild( $doc->createElement( 'g:condition', 'new' ) );
|
||||
|
||||
if ( $combination->quantity !== null ) {
|
||||
if ( $combination->quantity > 0 ) {
|
||||
if ( $combination['quantity'] !== null ) {
|
||||
if ( $combination['quantity'] > 0 ) {
|
||||
$itemNode->appendChild( $doc->createElement( 'g:availability', 'in stock' ) );
|
||||
$itemNode->appendChild( $doc->createElement( 'g:quantity', $combination->quantity ) );
|
||||
$itemNode->appendChild( $doc->createElement( 'g:quantity', $combination['quantity'] ) );
|
||||
} else {
|
||||
$itemNode->appendChild( $doc->createElement( 'g:availability', $combination->stock_0_buy ? 'in stock' : 'out of stock' ) );
|
||||
$itemNode->appendChild( $doc->createElement( 'g:availability', $combination['stock_0_buy'] ? 'in stock' : 'out of stock' ) );
|
||||
}
|
||||
} else {
|
||||
if ( $product->quantity > 0 ) {
|
||||
if ( $product['quantity'] > 0 ) {
|
||||
$itemNode->appendChild( $doc->createElement( 'g:availability', 'in stock' ) );
|
||||
$itemNode->appendChild( $doc->createElement( 'g:quantity', $product->quantity ) );
|
||||
$itemNode->appendChild( $doc->createElement( 'g:quantity', $product['quantity'] ) );
|
||||
} else {
|
||||
$itemNode->appendChild( $doc->createElement( 'g:availability', $product->stock_0_buy ? 'in stock' : 'out of stock' ) );
|
||||
$itemNode->appendChild( $doc->createElement( 'g:quantity', $product->stock_0_buy ? 999 : 0 ) );
|
||||
$itemNode->appendChild( $doc->createElement( 'g:availability', $product['stock_0_buy'] ? 'in stock' : 'out of stock' ) );
|
||||
$itemNode->appendChild( $doc->createElement( 'g:quantity', $product['stock_0_buy'] ? 999 : 0 ) );
|
||||
}
|
||||
}
|
||||
|
||||
if ( $combination->price_brutto ) {
|
||||
$itemNode->appendChild( $doc->createElement( 'g:price', $combination->price_brutto . ' PLN' ) );
|
||||
if ( $combination->price_brutto_promo ) {
|
||||
$itemNode->appendChild( $doc->createElement( 'g:sale_price', $combination->price_brutto_promo . ' PLN' ) );
|
||||
if ( $combination['price_brutto'] ) {
|
||||
$itemNode->appendChild( $doc->createElement( 'g:price', $combination['price_brutto'] . ' PLN' ) );
|
||||
if ( $combination['price_brutto_promo'] ) {
|
||||
$itemNode->appendChild( $doc->createElement( 'g:sale_price', $combination['price_brutto_promo'] . ' PLN' ) );
|
||||
}
|
||||
} else {
|
||||
$itemNode->appendChild( $doc->createElement( 'g:price', $product->price_brutto . ' PLN' ) );
|
||||
if ( $product->price_brutto_promo ) {
|
||||
$itemNode->appendChild( $doc->createElement( 'g:sale_price', $product->price_brutto_promo . ' PLN' ) );
|
||||
$itemNode->appendChild( $doc->createElement( 'g:price', $product['price_brutto'] . ' PLN' ) );
|
||||
if ( $product['price_brutto_promo'] ) {
|
||||
$itemNode->appendChild( $doc->createElement( 'g:sale_price', $product['price_brutto_promo'] . ' PLN' ) );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1635,33 +1636,33 @@ class ProductRepository
|
||||
private function appendProductToXml(\DOMDocument $doc, \DOMElement $channelNode, $product, string $domainPrefix, string $url): void
|
||||
{
|
||||
$itemNode = $channelNode->appendChild( $doc->createElement( 'item' ) );
|
||||
$itemNode->appendChild( $doc->createElement( 'g:id', $product->id ) );
|
||||
$itemNode->appendChild( $doc->createElement( 'g:item_group_id', $product->id ) );
|
||||
$itemNode->appendChild( $doc->createElement( 'g:id', $product['id'] ) );
|
||||
$itemNode->appendChild( $doc->createElement( 'g:item_group_id', $product['id'] ) );
|
||||
|
||||
if ( $product->google_xml_label ) {
|
||||
$itemNode->appendChild( $doc->createElement( 'g:custom_label_0', $product->google_xml_label ) );
|
||||
if ( isset( $product['google_xml_label'] ) && $product['google_xml_label'] ) {
|
||||
$itemNode->appendChild( $doc->createElement( 'g:custom_label_0', $product['google_xml_label'] ) );
|
||||
}
|
||||
|
||||
$title = $product->language['xml_name'] ?: $product->language['name'];
|
||||
$title = $product['language']['xml_name'] ?: $product['language']['name'];
|
||||
$itemNode->appendChild( $doc->createElement( 'title', str_replace( '&', '&', $title ) ) );
|
||||
|
||||
$gtin = $product->ean ?: $this->generateEAN( $product->id );
|
||||
$gtin = $product['ean'] ?: $this->generateEAN( $product['id'] );
|
||||
$itemNode->appendChild( $doc->createElement( 'g:gtin', $gtin ) );
|
||||
|
||||
$desc = $product->language['short_description'] ?: $product->language['name'];
|
||||
$desc = $product['language']['short_description'] ?: $product['language']['name'];
|
||||
$itemNode->appendChild( $doc->createElement( 'g:description', html_entity_decode( strip_tags( $desc ) ) ) );
|
||||
|
||||
if ( $product->language['seo_link'] ) {
|
||||
$link = $domainPrefix . '://' . $url . '/' . \Shared\Helpers\Helpers::seo( $product->language['seo_link'] );
|
||||
if ( $product['language']['seo_link'] ) {
|
||||
$link = $domainPrefix . '://' . $url . '/' . \Shared\Helpers\Helpers::seo( $product['language']['seo_link'] );
|
||||
} else {
|
||||
$link = $domainPrefix . '://' . $url . '/p-' . $product->id . '-' . \Shared\Helpers\Helpers::seo( $product->language['name'] );
|
||||
$link = $domainPrefix . '://' . $url . '/p-' . $product['id'] . '-' . \Shared\Helpers\Helpers::seo( $product['language']['name'] );
|
||||
}
|
||||
$itemNode->appendChild( $doc->createElement( 'link', $link ) );
|
||||
|
||||
for ( $l = 0; $l <= 4; $l++ ) {
|
||||
$label = 'custom_label_' . $l;
|
||||
if ( $product->$label ) {
|
||||
$itemNode->appendChild( $doc->createElement( 'g:' . $label, $product->$label ) );
|
||||
if ( isset( $product[$label] ) && $product[$label] ) {
|
||||
$itemNode->appendChild( $doc->createElement( 'g:' . $label, $product[$label] ) );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1669,11 +1670,11 @@ class ProductRepository
|
||||
|
||||
$itemNode->appendChild( $doc->createElement( 'g:condition', 'new' ) );
|
||||
|
||||
if ( $product->quantity ) {
|
||||
if ( $product['quantity'] ) {
|
||||
$itemNode->appendChild( $doc->createElement( 'g:availability', 'in stock' ) );
|
||||
$itemNode->appendChild( $doc->createElement( 'g:quantity', $product->quantity ) );
|
||||
$itemNode->appendChild( $doc->createElement( 'g:quantity', $product['quantity'] ) );
|
||||
} else {
|
||||
if ( $product->stock_0_buy ) {
|
||||
if ( $product['stock_0_buy'] ) {
|
||||
$itemNode->appendChild( $doc->createElement( 'g:availability', 'in stock' ) );
|
||||
$itemNode->appendChild( $doc->createElement( 'g:quantity', 999 ) );
|
||||
} else {
|
||||
@@ -1682,9 +1683,9 @@ class ProductRepository
|
||||
}
|
||||
}
|
||||
|
||||
$itemNode->appendChild( $doc->createElement( 'g:price', $product->price_brutto . ' PLN' ) );
|
||||
if ( $product->price_brutto_promo ) {
|
||||
$itemNode->appendChild( $doc->createElement( 'g:sale_price', $product->price_brutto_promo . ' PLN' ) );
|
||||
$itemNode->appendChild( $doc->createElement( 'g:price', $product['price_brutto'] . ' PLN' ) );
|
||||
if ( $product['price_brutto_promo'] ) {
|
||||
$itemNode->appendChild( $doc->createElement( 'g:sale_price', $product['price_brutto_promo'] . ' PLN' ) );
|
||||
}
|
||||
|
||||
$this->appendShippingToXml( $doc, $itemNode, $product );
|
||||
@@ -1695,12 +1696,12 @@ class ProductRepository
|
||||
*/
|
||||
private function appendImagesToXml(\DOMDocument $doc, \DOMElement $itemNode, $product, string $domainPrefix, string $url): void
|
||||
{
|
||||
if ( isset( $product->images[0] ) ) {
|
||||
$itemNode->appendChild( $doc->createElement( 'g:image_link', $domainPrefix . '://' . $url . $product->images[0]['src'] ) );
|
||||
if ( isset( $product['images'][0] ) ) {
|
||||
$itemNode->appendChild( $doc->createElement( 'g:image_link', $domainPrefix . '://' . $url . $product['images'][0]['src'] ) );
|
||||
}
|
||||
if ( count( $product->images ) > 1 ) {
|
||||
for ( $i = 1; $i < count( $product->images ); ++$i ) {
|
||||
$itemNode->appendChild( $doc->createElement( 'g:additional_image_link', $domainPrefix . '://' . $url . $product->images[$i]['src'] ) );
|
||||
if ( is_array( $product['images'] ) && count( $product['images'] ) > 1 ) {
|
||||
for ( $i = 1; $i < count( $product['images'] ); ++$i ) {
|
||||
$itemNode->appendChild( $doc->createElement( 'g:additional_image_link', $domainPrefix . '://' . $url . $product['images'][$i]['src'] ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1714,7 +1715,7 @@ class ProductRepository
|
||||
$shippingNode->appendChild( $doc->createElement( 'g:country', 'PL' ) );
|
||||
$shippingNode->appendChild( $doc->createElement( 'g:service', '1 dzień roboczy' ) );
|
||||
$shippingNode->appendChild( $doc->createElement( 'g:price',
|
||||
( new \Domain\Transport\TransportRepository( $this->db ) )->lowestTransportPrice( (int) $product->wp ) . ' PLN'
|
||||
( new \Domain\Transport\TransportRepository( $this->db ) )->lowestTransportPrice( (int) $product['wp'] ) . ' PLN'
|
||||
) );
|
||||
}
|
||||
|
||||
@@ -2243,4 +2244,603 @@ class ProductRepository
|
||||
'AND' => ['product_id' => $productId, 'lang_id' => $langId],
|
||||
]);
|
||||
}
|
||||
|
||||
public function findCustomFieldCached(int $customFieldId)
|
||||
{
|
||||
if ($customFieldId <= 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$cacheHandler = new \Shared\Cache\CacheHandler();
|
||||
$cacheKey = 'custom_field:' . $customFieldId;
|
||||
$cached = $cacheHandler->get($cacheKey);
|
||||
|
||||
if ($cached) {
|
||||
return unserialize($cached);
|
||||
}
|
||||
|
||||
$result = $this->db->get('pp_shop_products_custom_fields', '*', ['id_additional_field' => $customFieldId]);
|
||||
if (!is_array($result)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$cacheHandler->set($cacheKey, $result, 60 * 60 * 24);
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// Migrated from \shop\Product
|
||||
// =========================================================================
|
||||
|
||||
/**
|
||||
* Replacement for $this->findCached() — returns array instead of Product object.
|
||||
* Uses same Redis cache key pattern: shop\product:{id}:{lang}:{hash}
|
||||
*/
|
||||
public function findCached(int $productId, string $langId = null, string $permutationHash = null)
|
||||
{
|
||||
if ($productId <= 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!$langId) {
|
||||
$langId = (new \Domain\Languages\LanguagesRepository($this->db))->defaultLanguage();
|
||||
}
|
||||
|
||||
$cacheKey = "shop\\product:$productId:$langId:$permutationHash";
|
||||
|
||||
$cacheHandler = new \Shared\Cache\CacheHandler();
|
||||
$cached = $cacheHandler->get($cacheKey);
|
||||
|
||||
if ($cached) {
|
||||
$data = unserialize($cached);
|
||||
// Legacy cache may contain old \shop\Product objects — invalidate and re-fetch
|
||||
if (is_object($data)) {
|
||||
$cacheHandler->delete($cacheKey);
|
||||
// Fall through to re-fetch from DB
|
||||
} elseif (is_array($data)) {
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
|
||||
$product = $this->db->get('pp_shop_products', '*', ['id' => $productId]);
|
||||
if (!is_array($product)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$effectiveId = $productId;
|
||||
|
||||
// Combination — load data from parent
|
||||
if ($product['parent_id']) {
|
||||
$effectiveId = (int) $product['parent_id'];
|
||||
|
||||
if (!$product['price_netto'] || !$product['price_brutto']) {
|
||||
$parentPrices = $this->db->get('pp_shop_products', ['price_netto', 'price_brutto', 'price_netto_promo', 'price_brutto_promo', 'vat', 'wp'], ['id' => $effectiveId]);
|
||||
if (is_array($parentPrices)) {
|
||||
foreach ($parentPrices as $k => $v) {
|
||||
$product[$k] = $v;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Language
|
||||
$langRows = $this->db->select('pp_shop_products_langs', '*', ['AND' => ['product_id' => $effectiveId, 'lang_id' => $langId]]);
|
||||
if (\Shared\Helpers\Helpers::is_array_fix($langRows)) {
|
||||
foreach ($langRows as $row) {
|
||||
if ($row['copy_from']) {
|
||||
$copyRows = $this->db->select('pp_shop_products_langs', '*', ['AND' => ['product_id' => $effectiveId, 'lang_id' => $row['copy_from']]]);
|
||||
if (is_array($copyRows)) {
|
||||
foreach ($copyRows as $row2) {
|
||||
$product['language'] = $row2;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$product['language'] = $row;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Images, files, categories, related, sets
|
||||
$product['images'] = $this->db->select('pp_shop_products_images', '*', ['product_id' => $effectiveId, 'ORDER' => ['o' => 'ASC', 'id' => 'ASC']]);
|
||||
$product['files'] = $this->db->select('pp_shop_products_files', '*', ['product_id' => $effectiveId]);
|
||||
$product['categories'] = $this->db->select('pp_shop_products_categories', 'category_id', ['product_id' => $effectiveId]);
|
||||
$product['products_related'] = $this->db->select('pp_shop_products_related', 'product_related_id', ['product_id' => $effectiveId]);
|
||||
$product['products_sets'] = $this->db->select('pp_shop_product_sets_products', 'product_id', ['AND' => ['set_id' => (int) ($product['set_id'] ?? 0), 'product_id[!]' => $effectiveId]]);
|
||||
|
||||
// Product combinations (only for main products)
|
||||
if (!$product['parent_id']) {
|
||||
$combIds = $this->db->select('pp_shop_products', 'id', ['parent_id' => $productId]);
|
||||
if (\Shared\Helpers\Helpers::is_array_fix($combIds)) {
|
||||
$combos = [];
|
||||
foreach ($combIds as $combId) {
|
||||
$combos[] = $this->findCached((int) $combId, $langId);
|
||||
}
|
||||
$product['product_combinations'] = $combos;
|
||||
}
|
||||
}
|
||||
|
||||
// Producer
|
||||
$producer = $this->db->get('pp_shop_producer', '*', ['id' => (int) ($product['producer_id'] ?? 0)]);
|
||||
$producerLang = $this->db->get('pp_shop_producer_lang', '*', ['AND' => ['producer_id' => (int) ($product['producer_id'] ?? 0), 'lang_id' => $langId]]);
|
||||
if (is_array($producer)) {
|
||||
$producer['description'] = is_array($producerLang) ? ($producerLang['description'] ?? null) : null;
|
||||
$producer['data'] = is_array($producerLang) ? ($producerLang['data'] ?? null) : null;
|
||||
$producer['meta_title'] = is_array($producerLang) ? ($producerLang['meta_title'] ?? null) : null;
|
||||
}
|
||||
$product['producer'] = $producer;
|
||||
|
||||
// Permutation price overrides
|
||||
if ($permutationHash) {
|
||||
$permPrices = $this->db->get('pp_shop_products', ['price_netto', 'price_brutto', 'price_netto_promo', 'price_brutto_promo'], ['AND' => ['permutation_hash' => $permutationHash, 'parent_id' => $productId]]);
|
||||
if (is_array($permPrices)) {
|
||||
if ($permPrices['price_netto'] !== null) $product['price_netto'] = $permPrices['price_netto'];
|
||||
if ($permPrices['price_brutto'] !== null) $product['price_brutto'] = $permPrices['price_brutto'];
|
||||
if ($permPrices['price_netto_promo'] !== null) $product['price_netto_promo'] = $permPrices['price_netto_promo'];
|
||||
if ($permPrices['price_brutto_promo'] !== null) $product['price_brutto_promo'] = $permPrices['price_brutto_promo'];
|
||||
}
|
||||
}
|
||||
|
||||
// Custom fields
|
||||
$product['custom_fields'] = $this->db->select('pp_shop_products_custom_fields', '*', ['id_product' => $productId]);
|
||||
|
||||
$cacheHandler->set($cacheKey, $product);
|
||||
|
||||
return $product;
|
||||
}
|
||||
|
||||
public function isProductOnPromotion(int $productId): bool
|
||||
{
|
||||
return $this->db->get('pp_shop_products', 'price_netto_promo', ['id' => $productId]) !== null;
|
||||
}
|
||||
|
||||
public function productSetsWhenAddToBasket(int $productId): string
|
||||
{
|
||||
$cacheHandler = new \Shared\Cache\CacheHandler();
|
||||
$cacheKey = "ProductRepository::productSetsWhenAddToBasket:$productId";
|
||||
|
||||
$objectData = $cacheHandler->get($cacheKey);
|
||||
|
||||
if (!$objectData) {
|
||||
$parentId = $this->db->get('pp_shop_products', 'parent_id', ['id' => $productId]);
|
||||
if ($parentId) {
|
||||
$setId = $this->db->get('pp_shop_products', 'set_id', ['id' => $parentId]);
|
||||
} else {
|
||||
$setId = $this->db->get('pp_shop_products', 'set_id', ['id' => $productId]);
|
||||
}
|
||||
|
||||
$products = $this->db->select('pp_shop_product_sets_products', 'product_id', ['set_id' => $setId]);
|
||||
if (!$products) {
|
||||
$products_intersection = $this->db->select('pp_shop_orders_products_intersection', ['product_1_id', 'product_2_id'], ['OR' => ['product_1_id' => $productId, 'product_2_id' => $productId], 'ORDER' => ['count' => 'DESC'], 'LIMIT' => 5]);
|
||||
if (!count($products_intersection)) {
|
||||
$parentId2 = $this->db->get('pp_shop_products', 'parent_id', ['id' => $productId]);
|
||||
$products_intersection = $this->db->select('pp_shop_orders_products_intersection', ['product_1_id', 'product_2_id'], ['OR' => ['product_1_id' => $parentId2, 'product_2_id' => $parentId2], 'ORDER' => ['count' => 'DESC'], 'LIMIT' => 5]);
|
||||
}
|
||||
|
||||
$products = [];
|
||||
foreach ($products_intersection as $pi) {
|
||||
if ($pi['product_1_id'] != $productId) {
|
||||
$products[] = $pi['product_1_id'];
|
||||
} else {
|
||||
$products[] = $pi['product_2_id'];
|
||||
}
|
||||
}
|
||||
$products = array_unique($products);
|
||||
}
|
||||
|
||||
$cacheHandler->set($cacheKey, $products);
|
||||
} else {
|
||||
$products = unserialize($objectData);
|
||||
}
|
||||
|
||||
if (is_array($products)) {
|
||||
foreach ($products as $k => $pid) {
|
||||
if (!$this->isProductActiveCached((int) $pid)) {
|
||||
unset($products[$k]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return \Shared\Tpl\Tpl::view('shop-basket/alert-product-sets', [
|
||||
'products' => $products,
|
||||
]);
|
||||
}
|
||||
|
||||
public function addVisit(int $productId): void
|
||||
{
|
||||
$this->db->update('pp_shop_products', ['visits[+]' => 1], ['id' => $productId]);
|
||||
}
|
||||
|
||||
public function getProductImg(int $productId)
|
||||
{
|
||||
$rows = $this->db->select('pp_shop_products_images', 'src', ['product_id' => $productId, 'ORDER' => ['o' => 'ASC']]);
|
||||
if (\Shared\Helpers\Helpers::is_array_fix($rows)) {
|
||||
foreach ($rows as $row) {
|
||||
return $row;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public function getProductUrl(int $productId): string
|
||||
{
|
||||
$langId = (new \Domain\Languages\LanguagesRepository($this->db))->defaultLanguage();
|
||||
|
||||
$langRows = $this->db->select('pp_shop_products_langs', '*', ['AND' => ['product_id' => $productId, 'lang_id' => $langId]]);
|
||||
$language = null;
|
||||
if (\Shared\Helpers\Helpers::is_array_fix($langRows)) {
|
||||
foreach ($langRows as $row) {
|
||||
if ($row['copy_from']) {
|
||||
$copyRows = $this->db->select('pp_shop_products_langs', '*', ['AND' => ['product_id' => $productId, 'lang_id' => $row['copy_from']]]);
|
||||
if (is_array($copyRows)) {
|
||||
foreach ($copyRows as $row2) {
|
||||
$language = $row2;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$language = $row;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($language && $language['seo_link']) {
|
||||
return '/' . $language['seo_link'];
|
||||
}
|
||||
return '/p-' . $productId . '-' . \Shared\Helpers\Helpers::seo($language ? $language['name'] : '');
|
||||
}
|
||||
|
||||
public function searchProductsByNameCount(string $query, string $langId): int
|
||||
{
|
||||
$results = $this->db->query('SELECT COUNT(0) AS c FROM ( '
|
||||
. 'SELECT psp.id, '
|
||||
. '( CASE '
|
||||
. 'WHEN copy_from IS NULL THEN name '
|
||||
. 'WHEN copy_from IS NOT NULL THEN ( '
|
||||
. 'SELECT name FROM pp_shop_products_langs WHERE lang_id = pspl.copy_from AND product_id = psp.id '
|
||||
. ') '
|
||||
. 'END ) AS name '
|
||||
. 'FROM pp_shop_products AS psp '
|
||||
. 'INNER JOIN pp_shop_products_langs AS pspl ON pspl.product_id = psp.id '
|
||||
. 'WHERE status = 1 AND name LIKE :query AND lang_id = :lang_id '
|
||||
. ') AS q1', [
|
||||
':query' => '%' . $query . '%',
|
||||
':lang_id' => $langId,
|
||||
])->fetchAll(\PDO::FETCH_ASSOC);
|
||||
|
||||
return (int) ($results[0]['c'] ?? 0);
|
||||
}
|
||||
|
||||
public function getProductsIdByName(string $query, string $langId, int $limit, int $from): array
|
||||
{
|
||||
$results = $this->db->query('SELECT psp.id, '
|
||||
. '( CASE '
|
||||
. 'WHEN copy_from IS NULL THEN name '
|
||||
. 'WHEN copy_from IS NOT NULL THEN ( '
|
||||
. 'SELECT name FROM pp_shop_products_langs WHERE lang_id = pspl.copy_from AND product_id = psp.id '
|
||||
. ') '
|
||||
. 'END ) AS name '
|
||||
. 'FROM pp_shop_products AS psp '
|
||||
. 'INNER JOIN pp_shop_products_langs AS pspl ON pspl.product_id = psp.id '
|
||||
. 'WHERE status = 1 AND name LIKE :query AND lang_id = :lang_id '
|
||||
. 'ORDER BY name ASC '
|
||||
. 'LIMIT ' . (int) $from . ',' . (int) $limit, [
|
||||
':query' => '%' . $query . '%',
|
||||
':lang_id' => $langId,
|
||||
])->fetchAll(\PDO::FETCH_ASSOC);
|
||||
|
||||
$output = [];
|
||||
if (is_array($results)) {
|
||||
foreach ($results as $row) {
|
||||
$output[] = $row['id'];
|
||||
}
|
||||
}
|
||||
return $output;
|
||||
}
|
||||
|
||||
public function searchProductsByName(string $query, string $langId, int $page = 0): array
|
||||
{
|
||||
$count = $this->searchProductsByNameCount($query, $langId);
|
||||
$ls = ceil($count / 12);
|
||||
|
||||
if ($page < 1) {
|
||||
$page = 1;
|
||||
} elseif ($page > $ls) {
|
||||
$page = (int) $ls;
|
||||
}
|
||||
|
||||
$from = 12 * ($page - 1);
|
||||
if ($from < 0) {
|
||||
$from = 0;
|
||||
}
|
||||
|
||||
return [
|
||||
'products' => $this->getProductsIdByName($query, $langId, 12, $from),
|
||||
'count' => $count,
|
||||
'ls' => $ls,
|
||||
];
|
||||
}
|
||||
|
||||
public function searchProductByNameAjax(string $query, string $langId): array
|
||||
{
|
||||
$results = $this->db->query(
|
||||
'SELECT product_id FROM pp_shop_products_langs AS pspl '
|
||||
. 'INNER JOIN pp_shop_products AS psp ON psp.id = pspl.product_id '
|
||||
. 'WHERE status = 1 AND lang_id = :lang_id AND LOWER(name) LIKE :query '
|
||||
. 'ORDER BY visits DESC LIMIT 12',
|
||||
[':query' => '%' . $query . '%', ':lang_id' => $langId]
|
||||
)->fetchAll(\PDO::FETCH_ASSOC);
|
||||
|
||||
return is_array($results) ? $results : [];
|
||||
}
|
||||
|
||||
public function isStock0Buy(int $productId)
|
||||
{
|
||||
$parentId = $this->db->get('pp_shop_products', 'parent_id', ['id' => $productId]);
|
||||
if ($parentId) {
|
||||
return $this->db->get('pp_shop_products', 'stock_0_buy', ['id' => $parentId]);
|
||||
}
|
||||
return $this->db->get('pp_shop_products', 'stock_0_buy', ['id' => $productId]);
|
||||
}
|
||||
|
||||
public function getProductPermutationQuantityOptions(int $productId, $permutation)
|
||||
{
|
||||
global $settings;
|
||||
|
||||
$cacheHandler = new \Shared\Cache\CacheHandler();
|
||||
$cacheKey = "ProductRepository::getProductPermutationQuantityOptions:v2:$productId:$permutation";
|
||||
|
||||
$objectData = $cacheHandler->get($cacheKey);
|
||||
|
||||
if ($objectData) {
|
||||
return unserialize($objectData);
|
||||
}
|
||||
|
||||
$result = [];
|
||||
|
||||
if ($this->db->count('pp_shop_products', ['AND' => ['parent_id' => $productId, 'permutation_hash' => $permutation]])) {
|
||||
$result['quantity'] = $this->db->get('pp_shop_products', 'quantity', ['AND' => ['parent_id' => $productId, 'permutation_hash' => $permutation]]);
|
||||
$result['stock_0_buy'] = $this->db->get('pp_shop_products', 'stock_0_buy', ['AND' => ['parent_id' => $productId, 'permutation_hash' => $permutation]]);
|
||||
|
||||
if ($result['quantity'] === null) {
|
||||
$result['quantity'] = $this->db->get('pp_shop_products', 'quantity', ['id' => $productId]);
|
||||
if ($result['stock_0_buy'] === null) {
|
||||
$result['stock_0_buy'] = $this->db->get('pp_shop_products', 'stock_0_buy', ['id' => $productId]);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$result['quantity'] = $this->db->get('pp_shop_products', 'quantity', ['id' => $productId]);
|
||||
$result['stock_0_buy'] = $this->db->get('pp_shop_products', 'stock_0_buy', ['id' => $productId]);
|
||||
}
|
||||
|
||||
$result['messages'] = $this->db->get('pp_shop_products_langs', ['warehouse_message_zero', 'warehouse_message_nonzero'], ['AND' => ['product_id' => $productId, 'lang_id' => 'pl']]);
|
||||
if (!isset($result['messages']['warehouse_message_zero']) || !$result['messages']['warehouse_message_zero']) {
|
||||
$result['messages']['warehouse_message_zero'] = isset($settings['warehouse_message_zero_pl']) ? $settings['warehouse_message_zero_pl'] : '';
|
||||
}
|
||||
if (!isset($result['messages']['warehouse_message_nonzero']) || !$result['messages']['warehouse_message_nonzero']) {
|
||||
$result['messages']['warehouse_message_nonzero'] = isset($settings['warehouse_message_nonzero_pl']) ? $settings['warehouse_message_nonzero_pl'] : '';
|
||||
}
|
||||
|
||||
$cacheHandler->set($cacheKey, $result);
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function getProductIdByAttributes(int $parentId, array $attributes)
|
||||
{
|
||||
return $this->db->get('pp_shop_products', 'id', ['AND' => ['parent_id' => $parentId, 'permutation_hash' => implode('|', $attributes)]]);
|
||||
}
|
||||
|
||||
public function getProductPermutationHash(int $productId)
|
||||
{
|
||||
return $this->db->get('pp_shop_products', 'permutation_hash', ['id' => $productId]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build attribute list from product combinations (array of arrays with permutation_hash).
|
||||
*/
|
||||
public function getProductAttributes($products)
|
||||
{
|
||||
if (!is_array($products) || !count($products)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$attrRepo = new \Domain\Attribute\AttributeRepository($this->db);
|
||||
$attributes = [];
|
||||
|
||||
foreach ($products as $product) {
|
||||
$hash = is_array($product) ? ($product['permutation_hash'] ?? '') : (is_object($product) ? $product->permutation_hash : '');
|
||||
$permutations = explode('|', $hash);
|
||||
foreach ($permutations as $permutation) {
|
||||
$parts = explode('-', $permutation);
|
||||
if (count($parts) < 2) continue;
|
||||
|
||||
$value = [];
|
||||
$value['id'] = $parts[1];
|
||||
$value['is_default'] = $attrRepo->isValueDefault((int) $parts[1]);
|
||||
|
||||
if (array_search($parts[1], array_column($attributes, 'id')) === false) {
|
||||
$attributes[$parts[0]][] = $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$attributes = \Shared\Helpers\Helpers::removeDuplicates($attributes, 'id');
|
||||
|
||||
$sorted = [];
|
||||
foreach ($attributes as $key => $val) {
|
||||
$row = [];
|
||||
$row['id'] = $key;
|
||||
$row['values'] = $val;
|
||||
$sorted[$attrRepo->getAttributeOrder((int) $key)] = $row;
|
||||
}
|
||||
|
||||
return $sorted;
|
||||
}
|
||||
|
||||
public function generateSkuCode(): string
|
||||
{
|
||||
$skus = $this->db->select('pp_shop_products', 'sku', ['sku[~]' => 'PP-']);
|
||||
$codes = [];
|
||||
if (is_array($skus)) {
|
||||
foreach ($skus as $sku) {
|
||||
$codes[] = (int) substr($sku, 3);
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($codes)) {
|
||||
return 'PP-' . str_pad(max($codes) + 1, 6, '0', STR_PAD_LEFT);
|
||||
}
|
||||
return 'PP-000001';
|
||||
}
|
||||
|
||||
public function productMeta(int $productId): array
|
||||
{
|
||||
$result = $this->db->select(
|
||||
'pp_shop_products_categories (ppc)',
|
||||
['[>]pp_shop_categories_langs (pcl)' => ['ppc.category_id' => 'category_id']],
|
||||
['pcl.title', 'pcl.seo_link'],
|
||||
['ppc.product_id' => $productId]
|
||||
);
|
||||
return is_array($result) ? $result : [];
|
||||
}
|
||||
|
||||
public function generateSubtitleFromAttributes(string $permutationHash, string $langId = null): string
|
||||
{
|
||||
if (!$langId) {
|
||||
global $lang_id;
|
||||
$langId = $lang_id;
|
||||
}
|
||||
|
||||
$subtitle = '';
|
||||
$attrRepo = new \Domain\Attribute\AttributeRepository($this->db);
|
||||
$parts = explode('|', $permutationHash);
|
||||
|
||||
foreach ($parts as $part) {
|
||||
$attr = explode('-', $part);
|
||||
if (count($attr) < 2) continue;
|
||||
|
||||
if ($subtitle) {
|
||||
$subtitle .= ', ';
|
||||
}
|
||||
$subtitle .= $attrRepo->getAttributeNameById((int) $attr[0], $langId) . ': ' . $attrRepo->getAttributeValueById((int) $attr[1], $langId);
|
||||
}
|
||||
|
||||
return $subtitle;
|
||||
}
|
||||
|
||||
public function getDefaultCombinationPrices(array $product): array
|
||||
{
|
||||
$prices = [];
|
||||
if (!isset($product['product_combinations']) || !is_array($product['product_combinations'])) {
|
||||
return $prices;
|
||||
}
|
||||
|
||||
$permutationHash = '';
|
||||
$attributes = $this->getProductAttributes($product['product_combinations']);
|
||||
if (is_array($attributes)) {
|
||||
foreach ($attributes as $attribute) {
|
||||
foreach ($attribute['values'] as $value) {
|
||||
if ($value['is_default']) {
|
||||
if ($permutationHash) {
|
||||
$permutationHash .= '|';
|
||||
}
|
||||
$permutationHash .= $attribute['id'] . '-' . $value['id'];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($product['product_combinations'] as $combo) {
|
||||
$comboHash = is_array($combo) ? ($combo['permutation_hash'] ?? '') : (is_object($combo) ? $combo->permutation_hash : '');
|
||||
if ($comboHash == $permutationHash) {
|
||||
$prices['price_netto'] = is_array($combo) ? ($combo['price_netto'] ?? null) : $combo->price_netto;
|
||||
$prices['price_brutto'] = is_array($combo) ? ($combo['price_brutto'] ?? null) : $combo->price_brutto;
|
||||
$prices['price_netto_promo'] = is_array($combo) ? ($combo['price_netto_promo'] ?? null) : $combo->price_netto_promo;
|
||||
$prices['price_brutto_promo'] = is_array($combo) ? ($combo['price_brutto_promo'] ?? null) : $combo->price_brutto_promo;
|
||||
}
|
||||
}
|
||||
|
||||
return $prices;
|
||||
}
|
||||
|
||||
public function getProductDataBySelectedAttributes(array $product, string $selectedAttribute): array
|
||||
{
|
||||
global $settings;
|
||||
|
||||
$result = [];
|
||||
|
||||
if (isset($product['product_combinations']) && is_array($product['product_combinations'])) {
|
||||
foreach ($product['product_combinations'] as $combo) {
|
||||
$comboHash = is_array($combo) ? ($combo['permutation_hash'] ?? '') : (is_object($combo) ? $combo->permutation_hash : '');
|
||||
if ($comboHash == $selectedAttribute) {
|
||||
$comboQty = is_array($combo) ? ($combo['quantity'] ?? null) : $combo->quantity;
|
||||
$comboS0B = is_array($combo) ? ($combo['stock_0_buy'] ?? null) : $combo->stock_0_buy;
|
||||
|
||||
if ($comboQty !== null || $comboS0B) {
|
||||
$result['quantity'] = $comboQty;
|
||||
$result['stock_0_buy'] = $comboS0B;
|
||||
$result['price_netto'] = \Shared\Helpers\Helpers::shortPrice(is_array($combo) ? $combo['price_netto'] : $combo->price_netto);
|
||||
$result['price_brutto'] = \Shared\Helpers\Helpers::shortPrice(is_array($combo) ? $combo['price_brutto'] : $combo->price_brutto);
|
||||
$pnp = is_array($combo) ? ($combo['price_netto_promo'] ?? null) : $combo->price_netto_promo;
|
||||
$pbp = is_array($combo) ? ($combo['price_brutto_promo'] ?? null) : $combo->price_brutto_promo;
|
||||
$result['price_netto_promo'] = $pnp ? \Shared\Helpers\Helpers::shortPrice($pnp) : null;
|
||||
$result['price_brutto_promo'] = $pbp ? \Shared\Helpers\Helpers::shortPrice($pbp) : null;
|
||||
} else {
|
||||
$result['quantity'] = $product['quantity'] ?? null;
|
||||
$result['stock_0_buy'] = $product['stock_0_buy'] ?? null;
|
||||
$result['price_netto'] = \Shared\Helpers\Helpers::shortPrice($product['price_netto'] ?? 0);
|
||||
$result['price_brutto'] = \Shared\Helpers\Helpers::shortPrice($product['price_brutto'] ?? 0);
|
||||
$result['price_netto_promo'] = ($product['price_netto_promo'] ?? null) ? \Shared\Helpers\Helpers::shortPrice($product['price_netto_promo']) : null;
|
||||
$result['price_brutto_promo'] = ($product['price_brutto_promo'] ?? null) ? \Shared\Helpers\Helpers::shortPrice($product['price_brutto_promo']) : null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$lang = isset($product['language']) ? $product['language'] : [];
|
||||
$result['messages']['warehouse_message_zero'] = !empty($lang['warehouse_message_zero']) ? $lang['warehouse_message_zero'] : (isset($settings['warehouse_message_zero_pl']) ? $settings['warehouse_message_zero_pl'] : '');
|
||||
$result['messages']['warehouse_message_nonzero'] = !empty($lang['warehouse_message_nonzero']) ? $lang['warehouse_message_nonzero'] : (isset($settings['warehouse_message_nonzero_pl']) ? $settings['warehouse_message_nonzero_pl'] : '');
|
||||
$result['permutation_hash'] = $selectedAttribute;
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function productCategories(int $productId): array
|
||||
{
|
||||
$result = $this->db->select('pp_shop_products_categories', 'category_id', ['product_id' => $productId]);
|
||||
return is_array($result) ? $result : [];
|
||||
}
|
||||
|
||||
public static function arrayCartesian(array $input): array
|
||||
{
|
||||
$result = [];
|
||||
|
||||
foreach ($input as $key => $values) {
|
||||
if (empty($values)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (empty($result)) {
|
||||
foreach ($values as $value) {
|
||||
$result[] = [$key => $value];
|
||||
}
|
||||
} else {
|
||||
$append = [];
|
||||
foreach ($result as &$product) {
|
||||
$product[$key] = array_shift($values);
|
||||
$copy = $product;
|
||||
|
||||
foreach ($values as $item) {
|
||||
$copy[$key] = $item;
|
||||
$append[] = $copy;
|
||||
}
|
||||
|
||||
array_unshift($values, $product[$key]);
|
||||
}
|
||||
$result = array_merge($result, $append);
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user