diff --git a/Controller/Product/Card.php b/Controller/Product/Card.php index 00a4bd60..760f81fc 100644 --- a/Controller/Product/Card.php +++ b/Controller/Product/Card.php @@ -4,6 +4,8 @@ namespace Tweakwise\Magento2Tweakwise\Controller\Product; +use Magento\Catalog\Model\Product; +use Magento\PageCache\Model\Cache\Type as PageType; use Tweakwise\Magento2Tweakwise\Helper\Cache; use Magento\Framework\App\Action\HttpGetActionInterface; use Magento\Framework\App\RequestInterface; @@ -36,13 +38,24 @@ public function __construct( */ public function execute(): HttpInterface { - $itemId = (string) $this->request->getParam('item_id'); - $cardType = $this->request->getParam('card_type'); - $itemHtml = $cardType ? $this->cacheHelper->load($itemId, $cardType) : - $this->cacheHelper->load($itemId); + $itemId = (string)$this->request->getParam('item_id'); + $cacheKeyInfo = (string)$this->request->getParam('cache_key_info'); + $itemHtml = $this->cacheHelper->load($cacheKeyInfo); $response = $this->httpFactory->create(); $response->appendBody($itemHtml); + + $response->setHeader( + 'X-Magento-Tags', + implode( + ',', + [ + Product::CACHE_TAG, + sprintf('%s_%s', Product::CACHE_TAG, $itemId), + PageType::CACHE_TAG + ] + ) + ); $response->setPublicHeaders($this->config->getProductCardLifetime()); return $response; } diff --git a/Helper/Cache.php b/Helper/Cache.php index ec98b2d5..a33c7e6e 100644 --- a/Helper/Cache.php +++ b/Helper/Cache.php @@ -4,15 +4,16 @@ namespace Tweakwise\Magento2Tweakwise\Helper; -use Magento\Customer\Model\Session; +use Magento\Catalog\Model\Product; +use Magento\ConfigurableProduct\Model\Product\Type\Configurable; use Magento\Framework\App\CacheInterface; use Magento\Framework\App\Config\ScopeConfigInterface; use Magento\Framework\App\RequestInterface; use Magento\Framework\Exception\LocalizedException; use Magento\Framework\Exception\NoSuchEntityException; +use Magento\Framework\Serialize\Serializer\Json; use Magento\Framework\View\DesignInterface; use Magento\PageCache\Model\Config; -use Magento\Store\Model\StoreManagerInterface; use Tweakwise\Magento2Tweakwise\Model\Config as TweakwiseConfig; class Cache @@ -27,49 +28,51 @@ class Cache private ?bool $isHyvaTheme = null; /** - * @param StoreManagerInterface $storeManager * @param CacheInterface $cache - * @param Session $customerSession * @param RequestInterface $request * @param ScopeConfigInterface $scopeConfig * @param TweakwiseConfig $config * @param DesignInterface $viewDesign + * @param Json $jsonSerializer */ public function __construct( - private readonly StoreManagerInterface $storeManager, - private readonly CacheInterface $cache, - private readonly Session $customerSession, - private readonly RequestInterface $request, - private readonly ScopeConfigInterface $scopeConfig, - private readonly TweakwiseConfig $config, - private readonly DesignInterface $viewDesign + private readonly CacheInterface $cache, + private readonly RequestInterface $request, + private readonly ScopeConfigInterface $scopeConfig, + private readonly TweakwiseConfig $config, + private readonly DesignInterface $viewDesign, + private readonly Json $jsonSerializer ) { } /** - * @param string $itemId - * @param string $cardType + * @param string $hashedCacheKeyInfo * @return string * @throws LocalizedException * @throws NoSuchEntityException */ - public function load(string $itemId, string $cardType = 'default'): string + public function load(string $hashedCacheKeyInfo): string { - $result = $this->cache->load($this->getCacheKey($itemId, $cardType)); + $result = $this->cache->load($this->getCacheKey($hashedCacheKeyInfo)); return $result ? $result : ''; } /** * @param string $data - * @param string $itemId - * @param string $cardType + * @param string $hashedCacheKeyInfo + * @param array $tags * @return void * @throws LocalizedException * @throws NoSuchEntityException */ - public function save(string $data, string $itemId, string $cardType = 'default'): void + public function save(string $data, string $hashedCacheKeyInfo, array $tags = []): void { - $this->cache->save($data, $this->getCacheKey($itemId, $cardType)); + $this->cache->save( + $data, + $this->getCacheKey($hashedCacheKeyInfo), + $tags, + $this->config->getProductCardLifetime() + ); } /** @@ -88,6 +91,20 @@ public function isVarnishEnabled(): bool return $this->scopeConfig->getValue(Config::XML_PAGECACHE_TYPE) === (string) Config::VARNISH; } + /** + * @param Product $item + * @return string + * @throws LocalizedException + */ + public function getImage(Product $item): string + { + if (!$this->config->isGroupedProductsEnabled() || $item->getTypeId() !== Configurable::TYPE_CODE) { + return ''; + } + + return $item->getImage(); + } + /** * @return bool */ @@ -129,23 +146,32 @@ public function isHyvaTheme(): bool /** * @param string $itemId + * @param int $storeId + * @param int $customerGroupId + * @param string $image * @param string $cardType * @return string - * @throws LocalizedException - * @throws NoSuchEntityException */ - private function getCacheKey(string $itemId, string $cardType): string - { - $storeId = $this->storeManager->getStore()->getId(); - $customerGroupId = $this->customerSession->getCustomerGroupId(); + public function hashCacheKeyInfo( + string $itemId, + int $storeId, + int $customerGroupId, + string $image = '', + string $cardType = 'default', + ): string { + return sha1($this->jsonSerializer->serialize([$itemId, $storeId, $customerGroupId, $image, $cardType])); + } + /** + * @param string $hashedCacheKeyInfo + * @return string + */ + private function getCacheKey(string $hashedCacheKeyInfo): string + { return sprintf( - '%s_%s_%s_%s_%s', - $storeId, - $customerGroupId, - $itemId, - $cardType, - self::REDIS_CACHE_KEY + '%s_%s', + self::REDIS_CACHE_KEY, + $hashedCacheKeyInfo ); } } diff --git a/Model/Catalog/Product/Collection.php b/Model/Catalog/Product/Collection.php index 2222c776..dd5ec4f1 100644 --- a/Model/Catalog/Product/Collection.php +++ b/Model/Catalog/Product/Collection.php @@ -10,6 +10,7 @@ namespace Tweakwise\Magento2Tweakwise\Model\Catalog\Product; use Exception; +use Magento\ConfigurableProduct\Model\Product\Type\Configurable; use Tweakwise\Magento2Tweakwise\Model\Config; use Tweakwise\Magento2Tweakwise\Model\Enum\ItemType; use Tweakwise\Magento2Tweakwise\Api\Data\VisualInterface; @@ -167,11 +168,33 @@ protected function _afterLoad() parent::_afterLoad(); $this->applyCollectionSizeValues(); + $this->applyProductImages(); $this->addVisuals(); return $this; } + /** + * @return $this + */ + protected function applyProductImages(): AbstractCollection + { + foreach ($this->getProductImages() as $productId => $productImageUrl) { + if ( + !isset($this->_items[$productId]) || + $this->_items[$productId]->getTypeId() !== Configurable::TYPE_CODE + ) { + continue; + } + + $this->_items[$productId]->setData('image', $productImageUrl); + $this->_items[$productId]->setData('small_image', $productImageUrl); + $this->_items[$productId]->setData('thumbnail', $productImageUrl); + } + + return $this; + } + /** * @return void */ @@ -207,4 +230,18 @@ protected function getProductIds() $response = $this->navigationContext->getResponse(); return $response->getProductIds() ?? []; } + + /** + * @return array + */ + protected function getProductImages(): array + { + try { + $response = $this->navigationContext->getResponse(); + } catch (Exception $e) { + return []; + } + + return $response->getProductImages() ?? []; + } } diff --git a/Model/Client/Request.php b/Model/Client/Request.php index 91c3c940..c38d9ec8 100644 --- a/Model/Client/Request.php +++ b/Model/Client/Request.php @@ -29,6 +29,11 @@ class Request */ protected $path; + /** + * @var string + */ + protected $groupedPath; + /** * @var array */ @@ -76,6 +81,10 @@ public function getResponseType() */ public function getPath() { + if ($this->config->isGroupedProductsEnabled($this->storeManager->getStore()) && !empty($this->groupedPath)) { + return $this->groupedPath; + } + return $this->path; } diff --git a/Model/Client/Request/AutocompleteRequest.php b/Model/Client/Request/AutocompleteRequest.php index 678d5cbe..05a0471c 100644 --- a/Model/Client/Request/AutocompleteRequest.php +++ b/Model/Client/Request/AutocompleteRequest.php @@ -19,6 +19,11 @@ class AutocompleteRequest extends Request */ protected $path = 'autocomplete'; + /** + * @var string + */ + protected $groupedPath = 'autocomplete/grouped'; + /** * {@inheritDoc} */ diff --git a/Model/Client/Request/ProductNavigationRequest.php b/Model/Client/Request/ProductNavigationRequest.php index 57a4eb70..5dcaad22 100644 --- a/Model/Client/Request/ProductNavigationRequest.php +++ b/Model/Client/Request/ProductNavigationRequest.php @@ -9,11 +9,22 @@ namespace Tweakwise\Magento2Tweakwise\Model\Client\Request; +use Magento\Framework\Exception\LocalizedException; use Tweakwise\Magento2Tweakwise\Model\Client\Request; use Tweakwise\Magento2Tweakwise\Model\Client\Response\ProductNavigationResponse; class ProductNavigationRequest extends Request { + /** + * @var string + */ + protected $path = 'navigation'; + + /** + * @var string + */ + protected $groupedPath = 'navigation/grouped'; + /** * Maximum number of products returned for one request */ @@ -25,11 +36,6 @@ class ProductNavigationRequest extends Request private const SORT_ASC = 'ASC'; private const SORT_DESC = 'DESC'; - /** - * @var string - */ - protected $path = 'navigation'; - /** * @var array */ diff --git a/Model/Client/Request/ProductSearchRequest.php b/Model/Client/Request/ProductSearchRequest.php index c86d75c7..f7040f6f 100644 --- a/Model/Client/Request/ProductSearchRequest.php +++ b/Model/Client/Request/ProductSearchRequest.php @@ -21,4 +21,9 @@ class ProductSearchRequest extends ProductNavigationRequest implements SearchReq * @var string */ protected $path = 'navigation-search'; + + /** + * @var string + */ + protected $groupedPath = 'navigation-search/grouped'; } diff --git a/Model/Client/Request/Recommendations/ProductRequest.php b/Model/Client/Request/Recommendations/ProductRequest.php index 0ddc5c4b..0ab8763b 100644 --- a/Model/Client/Request/Recommendations/ProductRequest.php +++ b/Model/Client/Request/Recommendations/ProductRequest.php @@ -34,6 +34,11 @@ public function getProduct() public function setProduct(Product $product) { $this->product = $product; + + if ($this->config->isGroupedProductsEnabled() && $product->getTypeId() === 'configurable') { + $this->product = $this->getSimpleProduct($product); + } + return $this; } @@ -63,4 +68,18 @@ public function getPathSuffix() return '/' . $productTweakwiseId . parent::getPathSuffix(); } + + /** + * @param Product $product + * @return Product + */ + private function getSimpleProduct(Product $product): Product + { + $children = $product->getTypeInstance()->getUsedProducts($product); + foreach ($children as $child) { + if ($child->isSaleable() && $child->getTypeId() === Magento\Catalog\Model\Product\Type::TYPE_SIMPLE) { + return $child; + } + } + } } diff --git a/Model/Client/Request/Suggestions/ProductSuggestionsRequest.php b/Model/Client/Request/Suggestions/ProductSuggestionsRequest.php index 1fce9f5b..54ac9f32 100644 --- a/Model/Client/Request/Suggestions/ProductSuggestionsRequest.php +++ b/Model/Client/Request/Suggestions/ProductSuggestionsRequest.php @@ -19,6 +19,8 @@ class ProductSuggestionsRequest extends Request implements SearchRequestInterfac */ protected $path = 'suggestions/products'; + protected $groupedPath = 'suggestions/products/grouped'; + /** * @return string */ diff --git a/Model/Client/Request/Suggestions/SuggestionsRequest.php b/Model/Client/Request/Suggestions/SuggestionsRequest.php index dcdafdc5..9ded99a0 100644 --- a/Model/Client/Request/Suggestions/SuggestionsRequest.php +++ b/Model/Client/Request/Suggestions/SuggestionsRequest.php @@ -19,6 +19,11 @@ class SuggestionsRequest extends Request implements SearchRequestInterface */ protected $path = 'suggestions'; + /** + * @var string + */ + protected $groupedPath = 'suggestions/grouped'; + /** * @return string */ diff --git a/Model/Client/Response.php b/Model/Client/Response.php index 565740e0..fb85ae36 100644 --- a/Model/Client/Response.php +++ b/Model/Client/Response.php @@ -9,6 +9,7 @@ namespace Tweakwise\Magento2Tweakwise\Model\Client; +use Tweakwise\Magento2Tweakwise\Model\Client\Type\ItemType; use Tweakwise\Magento2Tweakwise\Model\Client\Type\Type; use Tweakwise\Magento2TweakwiseExport\Model\Helper; @@ -37,4 +38,85 @@ public function __construct(Helper $helper, Request $request, array $data = null $this->helper = $helper; parent::__construct($data); } + + /** + * Function to get items from groups and set the items + * @param array $groups + * @return $this + */ + public function setGroups(array $groups): self + { + if (!$groups) { + $this->setItems([]); + return $this; + } + + $items = []; + $groups = $this->normalizeArray($groups, 'group'); + foreach ($groups as $group) { + $simple = $this->getMostSuitableVariant($group); + $configurable = $this->getConfigurable($group); + + if (!$configurable) { + continue; + } + + if (!empty($simple['image'])) { + $configurable['image'] = $simple['image']; + } + + if (!empty($simple['type'])) { + $configurable['type'] = $simple['type']; + } + + $items[] = $configurable; + } + + $this->setItems($items); + return $this; + } + + /** + * Function to get most suitable variant. This is always the first item in the array. + * @param array $group + * @return array + */ + protected function getMostSuitableVariant(array $group): array + { + if (isset($group['items']['item'][0])) { + return reset($group['items']['item']); + } + + return $group['items']['item']; + } + + /** + * @param array $group + * @return array + */ + protected function getConfigurable(array $group): array + { + return ['itemno' => $group['code']]; + } + + /** + * @param ItemType[]|array[] $items + * @return $this + */ + public function setItems(array $items): self + { + $items = $this->normalizeArray($items, 'item'); + + $values = []; + foreach ($items as $value) { + if (!$value instanceof ItemType) { + $value = new ItemType($value); + } + + $values[] = $value; + } + + $this->data['items'] = $values; + return $this; + } } diff --git a/Model/Client/Response/AutocompleteResponse.php b/Model/Client/Response/AutocompleteResponse.php index bdb26af9..50c4e7f7 100644 --- a/Model/Client/Response/AutocompleteResponse.php +++ b/Model/Client/Response/AutocompleteResponse.php @@ -44,27 +44,6 @@ public function setSuggestions(array $suggestions) return $this; } - /** - * @param ItemType[]|array[] $items - * @return $this - */ - public function setItems(array $items) - { - $items = $this->normalizeArray($items, 'item'); - - $values = []; - foreach ($items as $value) { - if (!$value instanceof ItemType) { - $value = new ItemType($value); - } - - $values[] = $value; - } - - $this->data['items'] = $values; - return $this; - } - /** * @param InstantSearchType|array $instantSearch * @return $this diff --git a/Model/Client/Response/ProductNavigationResponse.php b/Model/Client/Response/ProductNavigationResponse.php index dda46284..6bd648e9 100644 --- a/Model/Client/Response/ProductNavigationResponse.php +++ b/Model/Client/Response/ProductNavigationResponse.php @@ -38,27 +38,6 @@ public function setFacets(array $facets) return $this; } - /** - * @param ItemType[]|array[] $items - * @return $this - */ - public function setItems(array $items) - { - $items = $this->normalizeArray($items, 'item'); - - $values = []; - foreach ($items as $value) { - if (!$value instanceof ItemType) { - $value = new ItemType($value); - } - - $values[] = $value; - } - - $this->data['items'] = $values; - return $this; - } - /** * @param PropertiesType|array $properties * @return $this @@ -106,4 +85,23 @@ public function getProductIds() return $ids; } + + /** + * @return array + */ + public function getProductImages(): array + { + $productImages = []; + foreach ($this->getItems() as $item) { + if (!$item->getImage()) { + continue; + } + + // Remove domain and media path when full url is used + $imageUrl = preg_replace("#^.*?/catalog/product/#", "", $item->getImage()); + $productImages[$this->helper->getStoreId($item->getId())] = $imageUrl; + } + + return $productImages; + } } diff --git a/Model/Client/Response/RecommendationsResponse.php b/Model/Client/Response/RecommendationsResponse.php index 7d2106fc..88284ea1 100644 --- a/Model/Client/Response/RecommendationsResponse.php +++ b/Model/Client/Response/RecommendationsResponse.php @@ -9,11 +9,40 @@ namespace Tweakwise\Magento2Tweakwise\Model\Client\Response; +use Tweakwise\Magento2Tweakwise\Model\Client\Request; use Tweakwise\Magento2Tweakwise\Model\Client\Response; use Tweakwise\Magento2Tweakwise\Model\Client\Type\ItemType; +use Tweakwise\Magento2Tweakwise\Model\Config; +use Tweakwise\Magento2TweakwiseExport\Model\Helper; class RecommendationsResponse extends Response { + /** + * @var bool + */ + private bool $proccessedGroupedProducts = false; + + /** + * RecommendationsResponse constructor. + * + * @param Helper $helper + * @param Request $request + * @param Config $config + * @param array|null $data + */ + public function __construct( + Helper $helper, + Request $request, + private readonly Config $config, + array $data = null + ) { + parent::__construct( + $helper, + $request, + $data + ); + } + /** * @param array $recommendation */ @@ -37,26 +66,26 @@ public function setRecommendation(array $recommendation) */ public function getItems(): array { - return $this->getDataValue('items') ?: []; - } - - /** - * @param ItemType[]|array[] $items - * @return $this - */ - public function setItems(array $items) - { - $items = $this->normalizeArray($items, 'item'); + if ($this->config->isGroupedProductsEnabled() && !$this->proccessedGroupedProducts) { + // Manually group items since recommendations doesn't have a grouped call yet. + $items = parent::getItems(); + $groups = []; + if (empty($items)) { + return $this->data['items']; + } - foreach ($items as $value) { - if (!$value instanceof ItemType) { - $value = new ItemType($value); + foreach ($items as $item) { + $groups['group'][] = [ + 'code' => $item->getGroupCodeFromAttributes(), + 'items' => ['item' => [$item->data]], + ]; } - $this->data['items'][] = $value; + $this->setGroups($groups); + $this->proccessedGroupedProducts = true; } - return $this; + return $this->data['items']; } public function replaceItems(array $items) diff --git a/Model/Client/Response/Suggestions/ProductSuggestionsResponse.php b/Model/Client/Response/Suggestions/ProductSuggestionsResponse.php index bb2ad5c2..c7ce56cb 100644 --- a/Model/Client/Response/Suggestions/ProductSuggestionsResponse.php +++ b/Model/Client/Response/Suggestions/ProductSuggestionsResponse.php @@ -8,27 +8,6 @@ class ProductSuggestionsResponse extends Response implements AutocompleteProductResponseInterface { - /** - * @param ItemType[]|array[] $items - * @return $this - */ - public function setItems(array $items) - { - $items = $this->normalizeArray($items, 'item'); - - $values = []; - foreach ($items as $value) { - if (!$value instanceof ItemType) { - $value = new ItemType($value); - } - - $values[] = $value; - } - - $this->data['items'] = $values; - return $this; - } - /** * @return int[] */ diff --git a/Model/Client/Type/ItemType.php b/Model/Client/Type/ItemType.php index 1e4051fc..8ed3bbee 100644 --- a/Model/Client/Type/ItemType.php +++ b/Model/Client/Type/ItemType.php @@ -117,4 +117,28 @@ public function getUrl() { return (string) $this->getDataValue('url'); } + + /** + * @return string + */ + public function getGroupCodeFromAttributes(): string + { + $attributes = $this->getDataValue('attributes'); + + foreach ($attributes as $attribute) { + if (!is_array($attribute)) { + $attribute = $attributes['attribute']; + } + + if ( + isset($attribute['name']) && + $attribute['name'] === 'groupcode' && + isset($attribute['values']['value']) + ) { + return (string)$attribute['values']['value']; + } + } + + return ''; + } } diff --git a/Model/Config.php b/Model/Config.php index 9f1cfe3a..10f1a892 100644 --- a/Model/Config.php +++ b/Model/Config.php @@ -24,6 +24,7 @@ /** * @SuppressWarnings(PHPMD.ExcessiveClassComplexity) + * @SuppressWarnings(PHPMD.ExcessivePublicCount) */ class Config { @@ -175,6 +176,16 @@ public function getGeneralAuthenticationKey(Store $store = null) return (string)$this->getStoreConfig('tweakwise/general/authentication_key', $store); } + /** + * @param Store|null $store + * @return bool + * @throws LocalizedException + */ + public function isGroupedProductsEnabled(Store $store = null): bool + { + return (bool)$this->getStoreConfig('tweakwise/general/grouped_products', $store); + } + /** * @deprecated * @see \Tweakwise\Magento2Tweakwise\Model\Client::REQUEST_TIMEOUT diff --git a/ViewModel/LinkedProductListItem.php b/ViewModel/LinkedProductListItem.php index 6072717d..6496ef4b 100644 --- a/ViewModel/LinkedProductListItem.php +++ b/ViewModel/LinkedProductListItem.php @@ -52,27 +52,36 @@ public function getItemHtml( ); } - $productId = (string) $product->getId(); + $productId = (string)$product->getId(); + $storeId = (int)$this->storeManager->getStore()->getId(); + $customerGroupId = (int)$this->customerSession->getCustomerGroupId(); $cardType = str_replace(' ', '_', $params['card_type']); - if (!$this->cacheHelper->load($productId, $cardType)) { + $hashedCacheKeyInfo = $this->cacheHelper->hashCacheKeyInfo( + $productId, + $storeId, + $customerGroupId, + $this->cacheHelper->getImage($product), + $cardType + ); + + if (!$this->cacheHelper->load($hashedCacheKeyInfo)) { $itemHtml = $this->getItemHtmlWithRenderer( $product, $parentBlock, $params ); - $this->cacheHelper->save($itemHtml, $productId, $cardType); + $this->cacheHelper->save( + $itemHtml, + $hashedCacheKeyInfo, + [Product::CACHE_TAG, sprintf('%s_%s', Product::CACHE_TAG, $productId)] + ); } - $storeId = $this->storeManager->getStore()->getId(); - $customerGroupId = $this->customerSession->getCustomerGroupId(); - return sprintf( - '', + '', Cache::PRODUCT_CARD_PATH, $productId, - $storeId, - $customerGroupId, - $cardType + $hashedCacheKeyInfo ); } diff --git a/ViewModel/ProductListItem.php b/ViewModel/ProductListItem.php index 0faa6df4..748f5141 100644 --- a/ViewModel/ProductListItem.php +++ b/ViewModel/ProductListItem.php @@ -67,10 +67,20 @@ public function getItemHtml( ); } - $itemId = (string) $item->getId(); - if (!$this->cacheHelper->load($itemId)) { + $itemId = (string)$item->getId(); + $storeId = (int)$this->storeManager->getStore()->getId(); + $customerGroupId = (int)$this->customerSession->getCustomerGroupId(); + $hashedCacheKeyInfo = $this->cacheHelper->hashCacheKeyInfo( + $itemId, + $storeId, + $customerGroupId, + $this->cacheHelper->getImage($item) + ); + + if (!$this->cacheHelper->load($hashedCacheKeyInfo)) { if ($isVisual) { $itemHtml = $this->getVisualHtml($item); + $this->cacheHelper->save($itemHtml, $hashedCacheKeyInfo); } else { $itemHtml = $this->getItemHtmlWithRenderer( $item, @@ -80,20 +90,19 @@ public function getItemHtml( $imageDisplayArea, $showDescription ); + $this->cacheHelper->save( + $itemHtml, + $hashedCacheKeyInfo, + [Product::CACHE_TAG, sprintf('%s_%s', Product::CACHE_TAG, $itemId)] + ); } - - $this->cacheHelper->save($itemHtml, $itemId); } - $storeId = $this->storeManager->getStore()->getId(); - $customerGroupId = $this->customerSession->getCustomerGroupId(); - return sprintf( - '', + '', Cache::PRODUCT_CARD_PATH, $itemId, - $storeId, - $customerGroupId + $hashedCacheKeyInfo ); } diff --git a/etc/adminhtml/system.xml b/etc/adminhtml/system.xml index 47eff37e..3e4481e9 100644 --- a/etc/adminhtml/system.xml +++ b/etc/adminhtml/system.xml @@ -18,6 +18,12 @@ Provided by Tweakwise (8 alphanumeric characters) required-entry validate-alphanum + + + Magento\Config\Model\Config\Source\Yesno + Display a collection of products with the same groupcode as one product tile. +