From d162ddbb925131801c0496705f2012211905ba80 Mon Sep 17 00:00:00 2001 From: Eric Wright Date: Wed, 10 May 2023 09:37:02 -0400 Subject: [PATCH 01/32] MAGE-640 Fix typo in variable name --- Helper/Entity/ProductHelper.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Helper/Entity/ProductHelper.php b/Helper/Entity/ProductHelper.php index 09ad43c1c..96cc530d6 100755 --- a/Helper/Entity/ProductHelper.php +++ b/Helper/Entity/ProductHelper.php @@ -793,30 +793,30 @@ protected function addCategoryData($customData, Product $product) */ protected function getHierarchicalCategories($categoriesWithPath) { - $hierachivalCategories = []; + $hierachicalCategories = []; $levelName = 'level'; foreach ($categoriesWithPath as $category) { $categoryCount = count($category); for ($i = 0; $i < $categoryCount; $i++) { - if (isset($hierachivalCategories[$levelName . $i]) === false) { - $hierachivalCategories[$levelName . $i] = []; + if (isset($hierachicalCategories[$levelName . $i]) === false) { + $hierachicalCategories[$levelName . $i] = []; } if ($category[$i] === null) { continue; } - $hierachivalCategories[$levelName . $i][] = implode(' /// ', array_slice($category, 0, $i + 1)); + $hierachicalCategories[$levelName . $i][] = implode(' /// ', array_slice($category, 0, $i + 1)); } } - foreach ($hierachivalCategories as &$level) { + foreach ($hierachicalCategories as &$level) { $level = array_values(array_unique($level)); } - return $hierachivalCategories; + return $hierachicalCategories; } /** From 79e64f0de0511e5101ec2351fc69ad195704aac2 Mon Sep 17 00:00:00 2001 From: Eric Wright Date: Wed, 10 May 2023 10:23:37 -0400 Subject: [PATCH 02/32] MAGE-640 Refactor valid category logic and remove extraneous category processing --- Helper/Entity/ProductHelper.php | 75 +++++++++++++++++++++------------ 1 file changed, 48 insertions(+), 27 deletions(-) diff --git a/Helper/Entity/ProductHelper.php b/Helper/Entity/ProductHelper.php index 96cc530d6..9d06e0074 100755 --- a/Helper/Entity/ProductHelper.php +++ b/Helper/Entity/ProductHelper.php @@ -715,14 +715,47 @@ protected function addAttribute($attribute, $defaultData, $customData, $addition return $customData; } + protected function getCategoryPaths($product, $category) + { + $category->getUrlInstance()->setStore($product->getStoreId()); + $path = []; + + foreach ($category->getPathIds() as $treeCategoryId) { + $name = $this->categoryHelper->getCategoryName($treeCategoryId, $storeId); + if ($name) { + $categoryIds[] = $treeCategoryId; + $path[] = $name; + } + } + } + /** - * @param $customData + * A category should only be indexed if in the path of the current store and has a valid name. + * + * @param $category + * @param $rootCat + * @param $storeId + * @return string|null + */ + protected function getValidCategoryName($category, $rootCat, $storeId): ?string + { + $pathParts = explode('/', $category->getPath()); + if (isset($pathParts[1]) && $pathParts[1] !== $rootCat) { + return null; + } + + return $this->categoryHelper->getCategoryName($category->getId(), $storeId); + + } + + /** + * @param $algoliaObject object to be serialized to Algolia index * @param Product $product * @return mixed * @throws \Magento\Framework\Exception\LocalizedException * @throws \Magento\Framework\Exception\NoSuchEntityException */ - protected function addCategoryData($customData, Product $product) + protected function addCategoryData($algoliaObject, Product $product) { $storeId = $product->getStoreId(); $categories = []; @@ -731,7 +764,7 @@ protected function addCategoryData($customData, Product $product) $_categoryIds = $product->getCategoryIds(); - if (is_array($_categoryIds) && count($_categoryIds) > 0) { + if (is_array($_categoryIds) && count($_categoryIds)) { $categoryCollection = $this->getAllCategories($_categoryIds, $storeId); /** @var Store $store */ @@ -739,40 +772,28 @@ protected function addCategoryData($customData, Product $product) $rootCat = $store->getRootCategoryId(); foreach ($categoryCollection as $category) { - // Check and skip all categories that is not - // in the path of the current store. - $path = $category->getPath(); - $pathParts = explode('/', $path); - if (isset($pathParts[1]) && $pathParts[1] !== $rootCat) { + $categoryName = $this->getValidCategoryName($category, $rootCat, $storeId); + if (!$categoryName) { continue; } + $categories[] = $categoryName; - $categoryName = $this->categoryHelper->getCategoryName($category->getId(), $storeId); - if ($categoryName) { - $categories[] = $categoryName; - } - - $category->getUrlInstance()->setStore($product->getStoreId()); - $path = []; + $category->getUrlInstance()->setStore($storeId); + $paths = []; foreach ($category->getPathIds() as $treeCategoryId) { $name = $this->categoryHelper->getCategoryName($treeCategoryId, $storeId); if ($name) { $categoryIds[] = $treeCategoryId; - $path[] = $name; + $paths[] = $name; } } - $categoriesWithPath[] = $path; - } - } - - foreach ($categoriesWithPath as $result) { - for ($i = count($result) - 1; $i > 0; $i--) { - $categoriesWithPath[] = array_slice($result, 0, $i); + $categoriesWithPath[] = $paths; } } + // Filter out non unique category path entries - is this a likely scenario? $categoriesWithPath = array_intersect_key( $categoriesWithPath, array_unique(array_map('serialize', $categoriesWithPath)) @@ -780,11 +801,11 @@ protected function addCategoryData($customData, Product $product) $hierarchicalCategories = $this->getHierarchicalCategories($categoriesWithPath); - $customData['categories'] = $hierarchicalCategories; - $customData['categories_without_path'] = $categories; - $customData['categoryIds'] = array_values(array_unique($categoryIds)); + $algoliaObject['categories'] = $hierarchicalCategories; + $algoliaObject['categories_without_path'] = $categories; + $algoliaObject['categoryIds'] = array_values(array_unique($categoryIds)); - return $customData; + return $algoliaObject; } /** From b9fce766ccebd5394f79812b6bc328e043990cae Mon Sep 17 00:00:00 2001 From: Eric Wright Date: Wed, 10 May 2023 12:04:03 -0400 Subject: [PATCH 03/32] MAGE-640 Refactor to minimize variable pollution and for clarity via self documenting function names --- Helper/Entity/ProductHelper.php | 71 +++++++++++++++++++++++---------- 1 file changed, 51 insertions(+), 20 deletions(-) diff --git a/Helper/Entity/ProductHelper.php b/Helper/Entity/ProductHelper.php index 9d06e0074..295963d06 100755 --- a/Helper/Entity/ProductHelper.php +++ b/Helper/Entity/ProductHelper.php @@ -749,18 +749,38 @@ protected function getValidCategoryName($category, $rootCat, $storeId): ?string } /** - * @param $algoliaObject object to be serialized to Algolia index + * Filter out non unique category path entries. + * + * @param $paths + * @return array + */ + protected function dedupePaths($paths): array + { + return array_intersect_key( + $paths, + array_unique(array_map('serialize', $paths)) + ); + } + + /** + * For a given product extract category data including category names, parent paths and all category tree IDs + * * @param Product $product - * @return mixed + * @return array|array[] * @throws \Magento\Framework\Exception\LocalizedException * @throws \Magento\Framework\Exception\NoSuchEntityException */ - protected function addCategoryData($algoliaObject, Product $product) + protected function buildCategoryData(Product $product): array { + // Build within a single loop + // TODO: Profile for efficiency vs separate loops + $categoryData = [ + 'categoryNames' => [], + 'categoryIds' => [], + 'categoriesWithPath' => [], + ]; + $storeId = $product->getStoreId(); - $categories = []; - $categoriesWithPath = []; - $categoryIds = []; $_categoryIds = $product->getCategoryIds(); @@ -776,7 +796,7 @@ protected function addCategoryData($algoliaObject, Product $product) if (!$categoryName) { continue; } - $categories[] = $categoryName; + $categoryData['categoryNames'][] = $categoryName; $category->getUrlInstance()->setStore($storeId); $paths = []; @@ -784,28 +804,38 @@ protected function addCategoryData($algoliaObject, Product $product) foreach ($category->getPathIds() as $treeCategoryId) { $name = $this->categoryHelper->getCategoryName($treeCategoryId, $storeId); if ($name) { - $categoryIds[] = $treeCategoryId; + $categoryData['categoryIds'][] = $treeCategoryId; $paths[] = $name; } } - $categoriesWithPath[] = $paths; + $categoryData['categoriesWithPath'][] = $paths; } } - // Filter out non unique category path entries - is this a likely scenario? - $categoriesWithPath = array_intersect_key( - $categoriesWithPath, - array_unique(array_map('serialize', $categoriesWithPath)) - ); - - $hierarchicalCategories = $this->getHierarchicalCategories($categoriesWithPath); + // TODO: Evaluate use cases + // Based on old extraneous array manip logic (since removed) - is this still a likely scenario? + $categoryData['categoriesWithPath'] = $this->dedupePaths($categoryData['categoriesWithPath']); - $algoliaObject['categories'] = $hierarchicalCategories; - $algoliaObject['categories_without_path'] = $categories; - $algoliaObject['categoryIds'] = array_values(array_unique($categoryIds)); + return $categoryData; + } + + /** + * @param array $algoliaData Data for product object to be serialized to Algolia index + * @param Product $product + * @return mixed + * @throws \Magento\Framework\Exception\LocalizedException + * @throws \Magento\Framework\Exception\NoSuchEntityException + */ + protected function addCategoryData(array $algoliaData, Product $product): array + { + $categoryData = $this->buildCategoryData($product); + $hierarchicalCategories = $this->getHierarchicalCategories($categoryData['categoriesWithPath']); + $algoliaData['categories'] = $hierarchicalCategories; + $algoliaData['categories_without_path'] = $categoryData['categoryNames']; + $algoliaData['categoryIds'] = array_values(array_unique($categoryData['categoryIds'])); - return $algoliaObject; + return $algoliaData; } /** @@ -833,6 +863,7 @@ protected function getHierarchicalCategories($categoriesWithPath) } } + // dedupe in case of multi category assignment foreach ($hierachicalCategories as &$level) { $level = array_values(array_unique($level)); } From 2516d8d6749d1cd3d5a280343fd9035b218843c5 Mon Sep 17 00:00:00 2001 From: Eric Wright Date: Wed, 10 May 2023 12:07:18 -0400 Subject: [PATCH 04/32] MAGE-640 Typo fix take 2 XD --- Helper/Entity/ProductHelper.php | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Helper/Entity/ProductHelper.php b/Helper/Entity/ProductHelper.php index 295963d06..ef9547b0d 100755 --- a/Helper/Entity/ProductHelper.php +++ b/Helper/Entity/ProductHelper.php @@ -819,7 +819,7 @@ protected function buildCategoryData(Product $product): array return $categoryData; } - + /** * @param array $algoliaData Data for product object to be serialized to Algolia index * @param Product $product @@ -844,31 +844,31 @@ protected function addCategoryData(array $algoliaData, Product $product): array */ protected function getHierarchicalCategories($categoriesWithPath) { - $hierachicalCategories = []; + $hierarchicalCategories = []; $levelName = 'level'; foreach ($categoriesWithPath as $category) { $categoryCount = count($category); for ($i = 0; $i < $categoryCount; $i++) { - if (isset($hierachicalCategories[$levelName . $i]) === false) { - $hierachicalCategories[$levelName . $i] = []; + if (isset($hierarchicalCategories[$levelName . $i]) === false) { + $hierarchicalCategories[$levelName . $i] = []; } if ($category[$i] === null) { continue; } - $hierachicalCategories[$levelName . $i][] = implode(' /// ', array_slice($category, 0, $i + 1)); + $hierarchicalCategories[$levelName . $i][] = implode(' /// ', array_slice($category, 0, $i + 1)); } } // dedupe in case of multi category assignment - foreach ($hierachicalCategories as &$level) { + foreach ($hierarchicalCategories as &$level) { $level = array_values(array_unique($level)); } - return $hierachicalCategories; + return $hierarchicalCategories; } /** From 925f46a1e3319ad4671e6343682be956dd7a35c4 Mon Sep 17 00:00:00 2001 From: Eric Wright Date: Wed, 10 May 2023 12:30:37 -0400 Subject: [PATCH 05/32] MAGE-640 Add configuration for visual merch enablement --- Helper/ConfigHelper.php | 10 ++++++++++ etc/adminhtml/system.xml | 12 ++++++++++++ etc/config.xml | 1 + 3 files changed, 23 insertions(+) diff --git a/Helper/ConfigHelper.php b/Helper/ConfigHelper.php index 003ab9a2d..5ca6f7880 100755 --- a/Helper/ConfigHelper.php +++ b/Helper/ConfigHelper.php @@ -49,6 +49,7 @@ class ConfigHelper public const PRODUCT_CUSTOM_RANKING = 'algoliasearch_products/products/custom_ranking_product_attributes'; public const USE_ADAPTIVE_IMAGE = 'algoliasearch_products/products/use_adaptive_image'; public const INDEX_OUT_OF_STOCK_OPTIONS = 'algoliasearch_products/products/index_out_of_stock_options'; + public const ENABLE_VISUAL_MERCHANDISING = 'algoliasearch_products/products/enable_visual_merchandising'; public const CATEGORY_ATTRIBUTES = 'algoliasearch_categories/categories/category_additional_attributes'; public const CATEGORY_CUSTOM_RANKING = 'algoliasearch_categories/categories/custom_ranking_category_attributes'; @@ -875,6 +876,15 @@ public function useAdaptiveImage($storeId = null) return $this->configInterface->isSetFlag(self::USE_ADAPTIVE_IMAGE, ScopeInterface::SCOPE_STORE, $storeId); } + /** + * @param $storeId + * @return bool + */ + public function isVisualMerchEnabled($storeId = null): bool + { + return $this->configInterface->isSetFlag(self::ENABLE_VISUAL_MERCHANDISING, ScopeInterface::SCOPE_STORE, $storeId); + } + /** * @param $storeId * @return mixed diff --git a/etc/adminhtml/system.xml b/etc/adminhtml/system.xml index 6fbb41d99..5d6db5018 100755 --- a/etc/adminhtml/system.xml +++ b/etc/adminhtml/system.xml @@ -451,6 +451,18 @@ Magento\Config\Model\Config\Source\Yesno + + + Magento\Config\Model\Config\Source\Yesno + +
+ NOTE: Merchandising Studio is only available on Premium plans. + ]]> +
+
diff --git a/etc/config.xml b/etc/config.xml index 47a7adb62..c42bdf539 100644 --- a/etc/config.xml +++ b/etc/config.xml @@ -26,6 +26,7 @@ + 0 From 9a7eaa71beec5d8f00278816303b6eba06a4b1c8 Mon Sep 17 00:00:00 2001 From: Eric Wright Date: Wed, 10 May 2023 17:34:50 -0400 Subject: [PATCH 06/32] MAGE-640 Add flattened categories for merchandising --- Helper/Entity/ProductHelper.php | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/Helper/Entity/ProductHelper.php b/Helper/Entity/ProductHelper.php index ef9547b0d..641b7cc72 100755 --- a/Helper/Entity/ProductHelper.php +++ b/Helper/Entity/ProductHelper.php @@ -35,6 +35,8 @@ class ProductHelper { + public const CATEGORY_SEPARATOR = ' /// '; + /** * @var CollectionFactory */ @@ -820,6 +822,20 @@ protected function buildCategoryData(Product $product): array return $categoryData; } + /** + * Flatten non hierarchical paths for merchandising + * + * @param array $paths + * @return array + */ + protected function flattenCategoryPaths(array $paths): array + { + return array_map( + function ($path) { return implode(self::CATEGORY_SEPARATOR, $path); }, + $paths + ); + } + /** * @param array $algoliaData Data for product object to be serialized to Algolia index * @param Product $product @@ -835,6 +851,10 @@ protected function addCategoryData(array $algoliaData, Product $product): array $algoliaData['categories_without_path'] = $categoryData['categoryNames']; $algoliaData['categoryIds'] = array_values(array_unique($categoryData['categoryIds'])); + if ($this->configHelper->isVisualMerchEnabled()) { + $algoliaData['categories_with_path'] = $this->flattenCategoryPaths($categoryData['categoriesWithPath']); + } + return $algoliaData; } @@ -842,7 +862,7 @@ protected function addCategoryData(array $algoliaData, Product $product): array * @param $categoriesWithPath * @return array */ - protected function getHierarchicalCategories($categoriesWithPath) + protected function getHierarchicalCategories($categoriesWithPath): array { $hierarchicalCategories = []; @@ -859,7 +879,7 @@ protected function getHierarchicalCategories($categoriesWithPath) continue; } - $hierarchicalCategories[$levelName . $i][] = implode(' /// ', array_slice($category, 0, $i + 1)); + $hierarchicalCategories[$levelName . $i][] = implode(self::CATEGORY_SEPARATOR, array_slice($category, 0, $i + 1)); } } From 76a563d9ef8384e428f3b80d3d938318f37c6ebc Mon Sep 17 00:00:00 2001 From: Eric Wright Date: Thu, 11 May 2023 08:56:11 -0400 Subject: [PATCH 07/32] MAGE-640 Apply facet to index for merch enablement --- Helper/Entity/ProductHelper.php | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/Helper/Entity/ProductHelper.php b/Helper/Entity/ProductHelper.php index 641b7cc72..554857cc4 100755 --- a/Helper/Entity/ProductHelper.php +++ b/Helper/Entity/ProductHelper.php @@ -36,6 +36,7 @@ class ProductHelper { public const CATEGORY_SEPARATOR = ' /// '; + public const CATEGORY_PAGE_ID_ATTRIBUTE = 'categoryPageId'; /** * @var CollectionFactory @@ -824,7 +825,7 @@ protected function buildCategoryData(Product $product): array /** * Flatten non hierarchical paths for merchandising - * + * * @param array $paths * @return array */ @@ -852,7 +853,7 @@ protected function addCategoryData(array $algoliaData, Product $product): array $algoliaData['categoryIds'] = array_values(array_unique($categoryData['categoryIds'])); if ($this->configHelper->isVisualMerchEnabled()) { - $algoliaData['categories_with_path'] = $this->flattenCategoryPaths($categoryData['categoriesWithPath']); + $algoliaData[self::CATEGORY_PAGE_ID_ATTRIBUTE] = $this->flattenCategoryPaths($categoryData['categoriesWithPath']); } return $algoliaData; @@ -1284,6 +1285,10 @@ protected function getAttributesForFaceting($storeId) // Used for merchandising $attributesForFaceting[] = 'categoryIds'; + if ($this->configHelper->isVisualMerchEnabled($storeId)) { + $attributesForFaceting[] = 'searchable(' . self::CATEGORY_PAGE_ID_ATTRIBUTE . ')'; + } + return $attributesForFaceting; } From 018aeaa49755766bcf75994fbbed821909ffa8d2 Mon Sep 17 00:00:00 2001 From: Eric Wright Date: Thu, 11 May 2023 09:29:17 -0400 Subject: [PATCH 08/32] MAGE-640 Make category page ID attribute configurable --- Helper/ConfigHelper.php | 12 +++++++++++- Helper/Entity/ProductHelper.php | 7 +++---- etc/adminhtml/system.xml | 19 ++++++++++++++++--- etc/config.xml | 1 + 4 files changed, 31 insertions(+), 8 deletions(-) diff --git a/Helper/ConfigHelper.php b/Helper/ConfigHelper.php index 5ca6f7880..7d3894f17 100755 --- a/Helper/ConfigHelper.php +++ b/Helper/ConfigHelper.php @@ -50,6 +50,7 @@ class ConfigHelper public const USE_ADAPTIVE_IMAGE = 'algoliasearch_products/products/use_adaptive_image'; public const INDEX_OUT_OF_STOCK_OPTIONS = 'algoliasearch_products/products/index_out_of_stock_options'; public const ENABLE_VISUAL_MERCHANDISING = 'algoliasearch_products/products/enable_visual_merchandising'; + public const CATEGORY_PAGE_ID_ATTRIBUTE_NAME = 'algoliasearch_products/products/category_page_id_attribute_name'; public const CATEGORY_ATTRIBUTES = 'algoliasearch_categories/categories/category_additional_attributes'; public const CATEGORY_CUSTOM_RANKING = 'algoliasearch_categories/categories/custom_ranking_category_attributes'; @@ -885,6 +886,15 @@ public function isVisualMerchEnabled($storeId = null): bool return $this->configInterface->isSetFlag(self::ENABLE_VISUAL_MERCHANDISING, ScopeInterface::SCOPE_STORE, $storeId); } + /** + * @param $storeId + * @return string + */ + public function getCategoryPageIdAttributeName($storeId = null): string + { + return (string) $this->configInterface->getValue(self::CATEGORY_PAGE_ID_ATTRIBUTE_NAME, ScopeInterface::SCOPE_STORE, $storeId); + } + /** * @param $storeId * @return mixed @@ -1624,4 +1634,4 @@ public function isAutocompleteNavigatorEnabled($storeId = null) $storeId ); } -} \ No newline at end of file +} diff --git a/Helper/Entity/ProductHelper.php b/Helper/Entity/ProductHelper.php index 554857cc4..10d370e64 100755 --- a/Helper/Entity/ProductHelper.php +++ b/Helper/Entity/ProductHelper.php @@ -36,7 +36,6 @@ class ProductHelper { public const CATEGORY_SEPARATOR = ' /// '; - public const CATEGORY_PAGE_ID_ATTRIBUTE = 'categoryPageId'; /** * @var CollectionFactory @@ -852,8 +851,8 @@ protected function addCategoryData(array $algoliaData, Product $product): array $algoliaData['categories_without_path'] = $categoryData['categoryNames']; $algoliaData['categoryIds'] = array_values(array_unique($categoryData['categoryIds'])); - if ($this->configHelper->isVisualMerchEnabled()) { - $algoliaData[self::CATEGORY_PAGE_ID_ATTRIBUTE] = $this->flattenCategoryPaths($categoryData['categoriesWithPath']); + if ($this->configHelper->isVisualMerchEnabled($product->getStoreId())) { + $algoliaData[$this->configHelper->getCategoryPageIdAttributeName($product->getStoreId())] = $this->flattenCategoryPaths($categoryData['categoriesWithPath']); } return $algoliaData; @@ -1286,7 +1285,7 @@ protected function getAttributesForFaceting($storeId) $attributesForFaceting[] = 'categoryIds'; if ($this->configHelper->isVisualMerchEnabled($storeId)) { - $attributesForFaceting[] = 'searchable(' . self::CATEGORY_PAGE_ID_ATTRIBUTE . ')'; + $attributesForFaceting[] = 'searchable(' . $this->configHelper->getCategoryPageIdAttributeName($storeId) . ')'; } return $attributesForFaceting; diff --git a/etc/adminhtml/system.xml b/etc/adminhtml/system.xml index 5d6db5018..6832437e8 100755 --- a/etc/adminhtml/system.xml +++ b/etc/adminhtml/system.xml @@ -326,7 +326,7 @@ Magento\Config\Model\Config\Source\Yesno documentation + Virtual Replica is only available on Premium plans documentation ]]> @@ -459,10 +459,23 @@ If enabled, products will be indexed into Algolia with a compatible category data format that supports advanced merchandising features such as visual rules and the new Merchandising Studio.

- NOTE: Merchandising Studio is only available on Premium plans. + NOTE: After enabling, you must run a full product reindex to ensure that the attribute data is properly applied to your storefront catalog. + Merchandising Studio is only available on Premium plans. ]]> + + + + category page ID. Default is categoryPageId. + ]]> + + required-entry validate-data + + 1 + +
@@ -1210,4 +1223,4 @@
- \ No newline at end of file + diff --git a/etc/config.xml b/etc/config.xml index c42bdf539..3c91cdaf2 100644 --- a/etc/config.xml +++ b/etc/config.xml @@ -27,6 +27,7 @@ 0 + categoryPageId From 13e8c2d7851236b3842746490a9085dde64435da Mon Sep 17 00:00:00 2001 From: Eric Wright Date: Thu, 11 May 2023 16:52:00 -0400 Subject: [PATCH 09/32] MAGE-644 Add configurable category separator --- Block/Configuration.php | 7 ++++--- Helper/Adapter/FiltersHelper.php | 2 +- Helper/ConfigHelper.php | 10 +++++++++ Helper/Entity/ProductHelper.php | 21 ++++++++++--------- etc/adminhtml/system.xml | 13 ++++++++++++ etc/config.xml | 1 + .../landingpage/search-configuration.phtml | 3 ++- view/frontend/web/instantsearch.js | 6 +++--- view/frontend/web/internals/common.js | 2 +- 9 files changed, 46 insertions(+), 19 deletions(-) diff --git a/Block/Configuration.php b/Block/Configuration.php index 6c5c871a6..7bf875446 100755 --- a/Block/Configuration.php +++ b/Block/Configuration.php @@ -94,8 +94,8 @@ public function getConfiguration() $level = -1; foreach ($category->getPathIds() as $treeCategoryId) { if ($path !== '') { - $path .= ' /// '; - }else{ + $path .= $config->getCategorySeparator(); + } else { $parentCategoryName = $categoryHelper->getCategoryName($treeCategoryId, $this->getStoreId()); } @@ -154,6 +154,7 @@ public function getConfiguration() 'infiniteScrollEnabled' => $config->isInfiniteScrollEnabled(), 'urlTrackedParameters' => $this->getUrlTrackedParameters(), 'isSearchBoxEnabled' => $config->isInstantSearchBoxEnabled(), + 'categorySeparator' => $config->getCategorySeparator() ], 'autocomplete' => [ 'enabled' => $config->isAutoCompleteEnabled(), @@ -382,4 +383,4 @@ protected function getLandingPageConfiguration() { return $this->isLandingPage() ? $this->getCurrentLandingPage()->getConfiguration() : json_encode([]); } -} \ No newline at end of file +} diff --git a/Helper/Adapter/FiltersHelper.php b/Helper/Adapter/FiltersHelper.php index 00764c9ca..571d71989 100644 --- a/Helper/Adapter/FiltersHelper.php +++ b/Helper/Adapter/FiltersHelper.php @@ -166,7 +166,7 @@ public function getFacetFilters($storeId, $parameters = null) if ($facet['attribute'] == 'categories') { $level = '.level' . (count($facetValues) - 1); - $facetFilters[] = $facet['attribute'] . $level . ':' . implode(' /// ', $facetValues); + $facetFilters[] = $facet['attribute'] . $level . ':' . implode($this->config->getCategorySeparator($storeId), $facetValues); continue; } diff --git a/Helper/ConfigHelper.php b/Helper/ConfigHelper.php index 7d3894f17..6e76760cb 100755 --- a/Helper/ConfigHelper.php +++ b/Helper/ConfigHelper.php @@ -56,6 +56,7 @@ class ConfigHelper public const CATEGORY_CUSTOM_RANKING = 'algoliasearch_categories/categories/custom_ranking_category_attributes'; public const SHOW_CATS_NOT_INCLUDED_IN_NAV = 'algoliasearch_categories/categories/show_cats_not_included_in_navigation'; public const INDEX_EMPTY_CATEGORIES = 'algoliasearch_categories/categories/index_empty_categories'; + public const CATEGORY_SEPARATOR = 'algoliasearch_categories/categories/category_separator'; public const IS_ACTIVE = 'algoliasearch_queue/queue/active'; public const NUMBER_OF_JOB_TO_RUN = 'algoliasearch_queue/queue/number_of_job_to_run'; @@ -1463,6 +1464,15 @@ public function getCategoryAdditionalAttributes($storeId = null) return []; } + /** + * @param $storeId + * @return string + */ + public function getCategorySeparator($storeId = null): string + { + return (string) $this->configInterface->getValue(self::CATEGORY_SEPARATOR, ScopeInterface::SCOPE_STORE, $storeId); + } + /** * @param $groupId * @return array diff --git a/Helper/Entity/ProductHelper.php b/Helper/Entity/ProductHelper.php index 10d370e64..0252c66f4 100755 --- a/Helper/Entity/ProductHelper.php +++ b/Helper/Entity/ProductHelper.php @@ -35,8 +35,6 @@ class ProductHelper { - public const CATEGORY_SEPARATOR = ' /// '; - /** * @var CollectionFactory */ @@ -828,10 +826,10 @@ protected function buildCategoryData(Product $product): array * @param array $paths * @return array */ - protected function flattenCategoryPaths(array $paths): array + protected function flattenCategoryPaths(array $paths, int $storeId): array { return array_map( - function ($path) { return implode(self::CATEGORY_SEPARATOR, $path); }, + function ($path) use ($storeId) { return implode($this->configHelper->getCategorySeparator($storeId), $path); }, $paths ); } @@ -845,24 +843,27 @@ function ($path) { return implode(self::CATEGORY_SEPARATOR, $path); }, */ protected function addCategoryData(array $algoliaData, Product $product): array { + $storeId = $product->getStoreId(); + $categoryData = $this->buildCategoryData($product); - $hierarchicalCategories = $this->getHierarchicalCategories($categoryData['categoriesWithPath']); + $hierarchicalCategories = $this->getHierarchicalCategories($categoryData['categoriesWithPath'], $storeId); $algoliaData['categories'] = $hierarchicalCategories; $algoliaData['categories_without_path'] = $categoryData['categoryNames']; $algoliaData['categoryIds'] = array_values(array_unique($categoryData['categoryIds'])); - if ($this->configHelper->isVisualMerchEnabled($product->getStoreId())) { - $algoliaData[$this->configHelper->getCategoryPageIdAttributeName($product->getStoreId())] = $this->flattenCategoryPaths($categoryData['categoriesWithPath']); + if ($this->configHelper->isVisualMerchEnabled($storeId)) { + $algoliaData[$this->configHelper->getCategoryPageIdAttributeName($storeId)] = $this->flattenCategoryPaths($categoryData['categoriesWithPath'], $storeId); } return $algoliaData; } /** - * @param $categoriesWithPath + * @param array $categoriesWithPath + * @param int $storeId * @return array */ - protected function getHierarchicalCategories($categoriesWithPath): array + protected function getHierarchicalCategories(array $categoriesWithPath, int $storeId): array { $hierarchicalCategories = []; @@ -879,7 +880,7 @@ protected function getHierarchicalCategories($categoriesWithPath): array continue; } - $hierarchicalCategories[$levelName . $i][] = implode(self::CATEGORY_SEPARATOR, array_slice($category, 0, $i + 1)); + $hierarchicalCategories[$levelName . $i][] = implode($this->configHelper->getCategorySeparator($storeId), array_slice($category, 0, $i + 1)); } } diff --git a/etc/adminhtml/system.xml b/etc/adminhtml/system.xml index 6832437e8..a4c951b06 100755 --- a/etc/adminhtml/system.xml +++ b/etc/adminhtml/system.xml @@ -531,6 +531,19 @@ ]]> + + + required-entry + + Spaces matter! Default is " /// ". + +
Do not forget to reindex the Algolia Search Products indexer after you've modified this value. + + ]]> +
+
diff --git a/etc/config.xml b/etc/config.xml index 3c91cdaf2..d3065650e 100644 --- a/etc/config.xml +++ b/etc/config.xml @@ -34,6 +34,7 @@ + /// diff --git a/view/adminhtml/templates/landingpage/search-configuration.phtml b/view/adminhtml/templates/landingpage/search-configuration.phtml index a40d38e24..a874608ac 100644 --- a/view/adminhtml/templates/landingpage/search-configuration.phtml +++ b/view/adminhtml/templates/landingpage/search-configuration.phtml @@ -19,6 +19,7 @@ $isConfig = [ 'currencyCode' => $configHelper->getCurrencyCode(), 'maxValuesPerFacet' => (int) $configHelper->getMaxValuesPerFacet(), 'landingPageConfig' => json_decode($landingPage->getConfiguration()), + 'categorySeparator' => $configHelper->getCategorySeparator(), 'searchParameters' => [ 'query' => $landingPage->getQuery(), 'hitsPerPage' => 10, @@ -483,7 +484,7 @@ $isConfig = [ var hierarchicalMenuParams = { container: facet.wrapper.appendChild(createISWidgetContainer(facet.attribute)), attributes: hierarchical_levels, - separator: ' /// ', + separator: config.categorySeparator, alwaysGetRootLevel: true, limit: config.maxValuesPerFacet, templates: templates, diff --git a/view/frontend/web/instantsearch.js b/view/frontend/web/instantsearch.js index ac1f0ce2f..6839a6519 100644 --- a/view/frontend/web/instantsearch.js +++ b/view/frontend/web/instantsearch.js @@ -271,7 +271,7 @@ requirejs(['algoliaBundle', 'Magento_Catalog/js/price-utils'], function (algolia item.label = attribute.label; item.refinements.forEach(function (refinement) { if (refinement.type !== 'hierarchical') return refinement; - var levels = refinement.label.split('///'); + var levels = refinement.label.split(algoliaConfig.instant.categorySeparator); var lastLevel = levels[levels.length - 1]; refinement.label = lastLevel; }); @@ -430,7 +430,7 @@ requirejs(['algoliaBundle', 'Magento_Catalog/js/price-utils'], function (algolia var hierarchicalMenuParams = { container: facet.wrapper.appendChild(createISWidgetContainer(facet.attribute)), attributes: hierarchical_levels, - separator: ' /// ', + separator: algoliaConfig.instant.categorySeparator, templates: templates, alwaysGetRootLevel: false, showParentLevel:false, @@ -670,4 +670,4 @@ requirejs(['algoliaBundle', 'Magento_Catalog/js/price-utils'], function (algolia return options; } -}); \ No newline at end of file +}); diff --git a/view/frontend/web/internals/common.js b/view/frontend/web/internals/common.js index e0b1913f0..2915458a8 100755 --- a/view/frontend/web/internals/common.js +++ b/view/frontend/web/internals/common.js @@ -404,7 +404,7 @@ requirejs(['algoliaBundle'], function(algoliaBundle) { if (algoliaConfig.isLandingPage && typeof uiStateProductIndex['hierarchicalMenu']['categories.level0'] === 'undefined' && 'categories.level0' in landingPageConfig) { - uiStateProductIndex['hierarchicalMenu']['categories.level0'] = landingPageConfig['categories.level0'].split(' /// '); + uiStateProductIndex['hierarchicalMenu']['categories.level0'] = landingPageConfig['categories.level0'].split(algoliaConfig.instant.categorySeparator); } } if (currentFacet.attribute == 'categories' && algoliaConfig.isCategoryPage) { From 8136e264364f776d70a517c78d97f9cf7991486d Mon Sep 17 00:00:00 2001 From: Eric Wright Date: Tue, 16 May 2023 23:13:15 -0400 Subject: [PATCH 10/32] MAGE-640 Add auto anchoring logic to support categoryPageId filter with InstantSearch --- Helper/Entity/ProductHelper.php | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/Helper/Entity/ProductHelper.php b/Helper/Entity/ProductHelper.php index 0252c66f4..ac8268e2c 100755 --- a/Helper/Entity/ProductHelper.php +++ b/Helper/Entity/ProductHelper.php @@ -834,6 +834,24 @@ function ($path) use ($storeId) { return implode($this->configHelper->getCategor ); } + /** + * Take an array of paths where each element is an array of parent-child hierarchies and + * append to the top level array each possible parent iteration. + * This serves to emulate anchoring in Magento in order to use category page id filtering + * without explicit category assignment. + * + * @param array $paths + * @return array + */ + protected function autoAnchorParentCategories(array $paths): array { + foreach ($paths as $path) { + for ($i = count($path) - 1; $i > 0; $i--) { + $paths[] = array_slice($path,0, $i); + } + } + return $this->dedupePaths($paths); + } + /** * @param array $algoliaData Data for product object to be serialized to Algolia index * @param Product $product @@ -852,7 +870,8 @@ protected function addCategoryData(array $algoliaData, Product $product): array $algoliaData['categoryIds'] = array_values(array_unique($categoryData['categoryIds'])); if ($this->configHelper->isVisualMerchEnabled($storeId)) { - $algoliaData[$this->configHelper->getCategoryPageIdAttributeName($storeId)] = $this->flattenCategoryPaths($categoryData['categoriesWithPath'], $storeId); + $autoAnchorPaths = $this->autoAnchorParentCategories($categoryData['categoriesWithPath']); + $algoliaData[$this->configHelper->getCategoryPageIdAttributeName($storeId)] = $this->flattenCategoryPaths($autoAnchorPaths, $storeId); } return $algoliaData; From 622fa6a96dd569f882a9967075ce7569d628485f Mon Sep 17 00:00:00 2001 From: Eric Wright Date: Wed, 17 May 2023 09:09:42 -0400 Subject: [PATCH 11/32] MAGE-640 conditionally enforce category page ID filter for visual merch --- Block/Configuration.php | 4 +++- view/frontend/web/instantsearch.js | 4 ++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/Block/Configuration.php b/Block/Configuration.php index 7bf875446..c173059d6 100755 --- a/Block/Configuration.php +++ b/Block/Configuration.php @@ -154,7 +154,9 @@ public function getConfiguration() 'infiniteScrollEnabled' => $config->isInfiniteScrollEnabled(), 'urlTrackedParameters' => $this->getUrlTrackedParameters(), 'isSearchBoxEnabled' => $config->isInstantSearchBoxEnabled(), - 'categorySeparator' => $config->getCategorySeparator() + 'isVisualMerchEnabled' => $config->isVisualMerchEnabled(), + 'categorySeparator' => $config->getCategorySeparator(), + 'categoryPageIdAttribute' => $config->getCategoryPageIdAttributeName() ], 'autocomplete' => [ 'enabled' => $config->isAutoCompleteEnabled(), diff --git a/view/frontend/web/instantsearch.js b/view/frontend/web/instantsearch.js index 6839a6519..be2008487 100644 --- a/view/frontend/web/instantsearch.js +++ b/view/frontend/web/instantsearch.js @@ -102,6 +102,10 @@ requirejs(['algoliaBundle', 'Magento_Catalog/js/price-utils'], function (algolia searchParameters['facetsRefinements']['categories.level' + algoliaConfig.request.level] = [algoliaConfig.request.path]; } } + + if (algoliaConfig.instant.isVisualMerchEnabled && algoliaConfig.isCategoryPage ) { + searchParameters.filters = `${algoliaConfig.instant.categoryPageIdAttribute}:'${algoliaConfig.request.path}'`; + } instantsearchOptions = algolia.triggerHooks('beforeInstantsearchInit', instantsearchOptions, algoliaBundle); From 1de42462ba0d7698041e25b76b24e87eb92f3616 Mon Sep 17 00:00:00 2001 From: Mohit Choudhary Date: Thu, 25 May 2023 18:18:42 +0530 Subject: [PATCH 12/32] Updated Search Insights Code --- view/frontend/web/internals/search-insights.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/view/frontend/web/internals/search-insights.js b/view/frontend/web/internals/search-insights.js index b6b28400c..21b7df9b4 100644 --- a/view/frontend/web/internals/search-insights.js +++ b/view/frontend/web/internals/search-insights.js @@ -1 +1 @@ -!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((e=e||self).AlgoliaAnalytics={})}(this,function(e){"use strict";var t=function(){try{return Boolean(navigator.cookieEnabled)}catch(e){return!1}},r=function(){try{return Boolean(navigator.sendBeacon)}catch(e){return!1}},n=function(){try{return Boolean(XMLHttpRequest)}catch(e){return!1}},o=function(e){return void 0===e},i=function(e){return"string"==typeof e},s=function(e){return"number"==typeof e},a=function(e){return"function"==typeof e};var c="insights-js (1.7.1)";var u=["de","us"],p=2592e6;var h,f,l,d,w=function(){return"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,function(e){var t=16*Math.random()|0;return("x"===e?t:3&t|8).toString(16)})},b="_ALGOLIA",y=function(e,t,r){var n=new Date;n.setTime(n.getTime()+r);var o="expires="+n.toUTCString();document.cookie=e+"="+t+";"+o+";path=/"},v=function(e){for(var t=e+"=",r=document.cookie.split(";"),n=0;n0;)r[n]=arguments[n+1];e&&a(t[e])?t[e].apply(t,r):console.warn("The method `"+e+"` doesn't exist.")}),o=e[r];o.queue=o.queue||[];var i=o.queue;i.forEach(function(e){var t=[].slice.call(e),r=t[0],o=t.slice(1);n.apply(void 0,[r].concat(o))}),i.push=function(e){var t=[].slice.call(e),r=t[0],o=t.slice(1);n.apply(void 0,[r].concat(o))}}}function D(e){var t=new g({requestFn:e});return"undefined"!=typeof window&&k.call(t,window),t}var E=D(function(){if(r())return I;if(n())return m;throw new Error("Could not find a supported HTTP request client in this environment.")}());e.createInsightsClient=D,e.default=E,Object.defineProperty(e,"__esModule",{value:!0})}); \ No newline at end of file +!function(e,n){"object"==typeof exports&&"undefined"!=typeof module?n(exports):"function"==typeof define&&define.amd?define(["exports"],n):n((e=e||self).AlgoliaAnalytics={})}(this,function(e){"use strict";function n(){"function"!=typeof Object.assign&&(Object.assign=function(e,n){var t=arguments;if(null==e)throw new TypeError("Cannot convert undefined or null to object");for(var i=Object(e),o=1;o0;)t[i]=arguments[i+1];e&&c(n[e])?n[e].apply(n,t):console.warn("The method `"+e+"` doesn't exist.")}),o=e[t];o.queue=o.queue||[];var r=o.queue;r.forEach(function(e){var n=[].slice.call(e),t=n[0],o=n.slice(1);i.apply(void 0,[t].concat(o))}),r.push=function(e){var n=[].slice.call(e),t=n[0],o=n.slice(1);i.apply(void 0,[t].concat(o))}}}function C(e){var n=new j({requestFn:e});return"object"==typeof window&&P.call(n,window),n.version=h,n}var U=C(function(){if(r())return w;if(s())return A;throw new Error("Could not find a supported HTTP request client in this environment.")}());e.createInsightsClient=C,e.default=U,Object.defineProperty(e,"__esModule",{value:!0})}); From 04ca76496aac2d645fb51dc40b7ce91da95be4c6 Mon Sep 17 00:00:00 2001 From: Eric Wright Date: Tue, 30 May 2023 16:45:59 -0400 Subject: [PATCH 13/32] MAGE-569 Anchor category faceting based category context (only show subcategories) --- .../landingpage/search-configuration.phtml | 1 - view/frontend/web/instantsearch.js | 36 ++++++++++--------- 2 files changed, 20 insertions(+), 17 deletions(-) diff --git a/view/adminhtml/templates/landingpage/search-configuration.phtml b/view/adminhtml/templates/landingpage/search-configuration.phtml index a874608ac..274b74119 100644 --- a/view/adminhtml/templates/landingpage/search-configuration.phtml +++ b/view/adminhtml/templates/landingpage/search-configuration.phtml @@ -485,7 +485,6 @@ $isConfig = [ container: facet.wrapper.appendChild(createISWidgetContainer(facet.attribute)), attributes: hierarchical_levels, separator: config.categorySeparator, - alwaysGetRootLevel: true, limit: config.maxValuesPerFacet, templates: templates, sortBy: ['name:asc'], diff --git a/view/frontend/web/instantsearch.js b/view/frontend/web/instantsearch.js index be2008487..1d153a61e 100644 --- a/view/frontend/web/instantsearch.js +++ b/view/frontend/web/instantsearch.js @@ -102,7 +102,7 @@ requirejs(['algoliaBundle', 'Magento_Catalog/js/price-utils'], function (algolia searchParameters['facetsRefinements']['categories.level' + algoliaConfig.request.level] = [algoliaConfig.request.path]; } } - + if (algoliaConfig.instant.isVisualMerchEnabled && algoliaConfig.isCategoryPage ) { searchParameters.filters = `${algoliaConfig.instant.categoryPageIdAttribute}:'${algoliaConfig.request.path}'`; } @@ -431,29 +431,33 @@ requirejs(['algoliaBundle', 'Magento_Catalog/js/price-utils'], function (algolia hierarchical_levels.push('categories.level' + l.toString()); } + + //return array of items starting from root based on category + const findRoot = (items) => { + const root = items.find(element => algoliaConfig.request.path.startsWith(element.value)); + + if (!root) { + return items; + } + if (!root.data) { + return []; + } + + return findRoot(root.data); + + }; var hierarchicalMenuParams = { container: facet.wrapper.appendChild(createISWidgetContainer(facet.attribute)), attributes: hierarchical_levels, separator: algoliaConfig.instant.categorySeparator, templates: templates, - alwaysGetRootLevel: false, - showParentLevel:false, + showParentLevel: true, limit: algoliaConfig.maxValuesPerFacet, sortBy: ['name:asc'], transformItems(items) { - if(algoliaConfig.isCategoryPage) { - var filteredData = []; - items.forEach(element => { - if (element.label == algoliaConfig.request.parentCategory) { - filteredData.push(element); - }; - }); - items = filteredData; - } - return items.map(item => ({ - ...item, - label: item.label, - })); + return (algoliaConfig.isCategoryPage) + ? findRoot(items) + : items; }, }; From 7dc72243253fed4a80b96aa79fc540f124966574 Mon Sep 17 00:00:00 2001 From: Eric Wright Date: Tue, 30 May 2023 16:48:04 -0400 Subject: [PATCH 14/32] MAGE-569 Hide empty category facets --- view/frontend/web/instantsearch.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/view/frontend/web/instantsearch.js b/view/frontend/web/instantsearch.js index 1d153a61e..11e8126fa 100644 --- a/view/frontend/web/instantsearch.js +++ b/view/frontend/web/instantsearch.js @@ -468,6 +468,9 @@ requirejs(['algoliaBundle', 'Magento_Catalog/js/price-utils'], function (algolia hierarchicalMenuParams.panelOptions = { templates: { header: '
' + (facet.label ? facet.label : facet.attribute) + '
', + }, + hidden: function ({items}) { + return !items.length; } }; From 96effa63d7066121ede83a5f9bc8808445fc09f5 Mon Sep 17 00:00:00 2001 From: Eric Wright Date: Tue, 30 May 2023 18:10:44 -0400 Subject: [PATCH 15/32] MAGE-569 Add conditional currentRefinements for subcategory facets --- view/frontend/web/instantsearch.js | 45 ++++++++++++++++++------------ 1 file changed, 27 insertions(+), 18 deletions(-) diff --git a/view/frontend/web/instantsearch.js b/view/frontend/web/instantsearch.js index 11e8126fa..853de2eb2 100644 --- a/view/frontend/web/instantsearch.js +++ b/view/frontend/web/instantsearch.js @@ -261,26 +261,35 @@ requirejs(['algoliaBundle', 'Magento_Catalog/js/price-utils'], function (algolia templates: { item: $('#current-refinements-template').html() }, - includedAttributes: attributes.map(function (attribute) { - if (!(algoliaConfig.isCategoryPage && attribute.name.indexOf('categories') > -1)) { + includedAttributes: attributes.map(attribute => { + console.warn("Refinement:", attribute); + if (attribute.name.indexOf('categories') === -1 + || algoliaConfig.instant.isVisualMerchEnabled + || !algoliaConfig.isCategoryPage) return attribute.name; - } }), - transformItems: function (items) { - return items.map(function (item) { - var attribute = attributes.filter(function (_attribute) { - return item.attribute === _attribute.name - })[0]; - if (!attribute) return item; - item.label = attribute.label; - item.refinements.forEach(function (refinement) { - if (refinement.type !== 'hierarchical') return refinement; - var levels = refinement.label.split(algoliaConfig.instant.categorySeparator); - var lastLevel = levels[levels.length - 1]; - refinement.label = lastLevel; - }); - return item; - }) + transformItems: items => { + console.log("%cRefinement transform:%o", 'color:green;background:yellow', items); + return items + .filter(item => { + return !algoliaConfig.isCategoryPage + || item.refinements.filter(refinement => refinement.value !== algoliaConfig.request.path).length; // do not expose the category root + }) + .map(item => { + const attribute = attributes.filter(_attribute => { + return item.attribute === _attribute.name + })[0]; + if (!attribute) return item; + item.label = attribute.label; + item.refinements.forEach(function (refinement) { + if (refinement.type !== 'hierarchical') return refinement; + + const levels = refinement.label.split(algoliaConfig.instant.categorySeparator); + const lastLevel = levels[levels.length - 1]; + refinement.label = lastLevel; + }); + return item; + }) } }, From e3717cf9f6b035e6d089346caca071e9abc245f2 Mon Sep 17 00:00:00 2001 From: Eric Wright Date: Wed, 31 May 2023 09:07:26 -0400 Subject: [PATCH 16/32] MAGE-569 Disable category inclusion on currentRefinements until implementation of custom renderer to intercept delete operation on browse --- view/frontend/web/instantsearch.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/view/frontend/web/instantsearch.js b/view/frontend/web/instantsearch.js index 853de2eb2..d15056e66 100644 --- a/view/frontend/web/instantsearch.js +++ b/view/frontend/web/instantsearch.js @@ -262,15 +262,13 @@ requirejs(['algoliaBundle', 'Magento_Catalog/js/price-utils'], function (algolia item: $('#current-refinements-template').html() }, includedAttributes: attributes.map(attribute => { - console.warn("Refinement:", attribute); if (attribute.name.indexOf('categories') === -1 - || algoliaConfig.instant.isVisualMerchEnabled - || !algoliaConfig.isCategoryPage) + || !algoliaConfig.isCategoryPage) // For category browse, requires a custom renderer to prevent removal of the root node from hierarchicalMenu widget return attribute.name; }), transformItems: items => { - console.log("%cRefinement transform:%o", 'color:green;background:yellow', items); return items + // This filter is only applicable if categories facet is included as an attribute .filter(item => { return !algoliaConfig.isCategoryPage || item.refinements.filter(refinement => refinement.value !== algoliaConfig.request.path).length; // do not expose the category root From ffe173b4f5979085c731580fb1a349e941741aa7 Mon Sep 17 00:00:00 2001 From: Eric Wright Date: Wed, 31 May 2023 09:14:58 -0400 Subject: [PATCH 17/32] MAGE-569 Implement recursive backend retrieval of URLs for all subcats --- Block/Configuration.php | 37 ++++++++++++++++++++++++++++++ view/frontend/web/instantsearch.js | 12 ++++++++-- 2 files changed, 47 insertions(+), 2 deletions(-) diff --git a/Block/Configuration.php b/Block/Configuration.php index c173059d6..6feb245e3 100755 --- a/Block/Configuration.php +++ b/Block/Configuration.php @@ -30,6 +30,40 @@ public function isSearchPage() return false; } + /** + * @param \Magento\Catalog\Model\Category $cat + * @return string + */ + protected function initCategoryParentPath(\Magento\Catalog\Model\Category $cat): string { + $path = ''; + foreach ($cat->getPathIds() as $treeCategoryId) { + if ($path) { + $path .= $this->getConfigHelper()->getCategorySeparator($this->getStoreId()); + } + $path .= $this->getCategoryHelper()->getCategoryName($treeCategoryId, $this->getStoreId()); + } + return $path; + } + + /** + * @param \Magento\Catalog\Model\Category $cat + * @param string $parent + * @param array $arr + * @return array + */ + protected function getChildCategoryUrls(\Magento\Catalog\Model\Category $cat, string $parent = '', array $arr = array()): array { + if (!$parent) { + $parent = $this->initCategoryParentPath($cat); + } + + foreach ($cat->getChildrenCategories() as $child) { + $key = $parent ? $parent . $this->getConfigHelper()->getCategorySeparator() . $child->getName() : $child ->getName(); + $arr[$key]['url'] = $child->getUrl(); + $arr = array_merge($arr, $this->getChildCategoryUrls($child, $key, $arr)); + } + return $arr; + } + public function getConfiguration() { $config = $this->getConfigHelper(); @@ -70,6 +104,7 @@ public function getConfiguration() $level = ''; $categoryId = ''; $parentCategoryName = ''; + $childCategories = []; $addToCartParams = $this->getAddToCartParams(); @@ -88,6 +123,7 @@ public function getConfiguration() if ($category && $category->getDisplayMode() !== 'PAGE') { $category->getUrlInstance()->setStore($this->getStoreId()); + $childCategories = $this->getChildCategoryUrls($category); $categoryId = $category->getId(); @@ -236,6 +272,7 @@ public function getConfiguration() 'path' => $path, 'level' => $level, 'parentCategory' => $parentCategoryName, + 'childCategories' => $childCategories ], 'showCatsNotIncludedInNavigation' => $config->showCatsNotIncludedInNavigation(), 'showSuggestionsOnNoResultsPage' => $config->showSuggestionsOnNoResultsPage(), diff --git a/view/frontend/web/instantsearch.js b/view/frontend/web/instantsearch.js index d15056e66..27366cf53 100644 --- a/view/frontend/web/instantsearch.js +++ b/view/frontend/web/instantsearch.js @@ -463,13 +463,21 @@ requirejs(['algoliaBundle', 'Magento_Catalog/js/price-utils'], function (algolia sortBy: ['name:asc'], transformItems(items) { return (algoliaConfig.isCategoryPage) - ? findRoot(items) + ? findRoot(items).map( + item => { + if (true) { + item.categoryUrl = algoliaConfig.request.childCategories[item.value]['url']; + } + console.log('item:', item); + return item; + } + ) : items; }, }; hierarchicalMenuParams.templates.item = '' + - '{{label}}' + ' ' + + '{{label}}' + ' ' + '{{#helpers.formatNumber}}{{count}}{{/helpers.formatNumber}}' + ''; hierarchicalMenuParams.panelOptions = { From c8a7ff9758562cd87b0dbadb30eebbc813742da7 Mon Sep 17 00:00:00 2001 From: Eric Wright Date: Wed, 31 May 2023 09:43:51 -0400 Subject: [PATCH 18/32] MAGE-569 Feature flag category navigation for future enablement via custom renderer --- Block/Configuration.php | 13 +++++++++---- view/frontend/web/instantsearch.js | 11 ++++++----- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/Block/Configuration.php b/Block/Configuration.php index 6feb245e3..8c7d65254 100755 --- a/Block/Configuration.php +++ b/Block/Configuration.php @@ -9,6 +9,9 @@ class Configuration extends Algolia implements CollectionDataSourceInterface { + //Placeholder for future implementation (requires customer renderer for hierarchicalMenu widget) + private const IS_CATEGORY_NAVIGATION_ENABLED = false; + public function isSearchPage() { if ($this->getConfigHelper()->isInstantEnabled()) { @@ -57,7 +60,7 @@ protected function getChildCategoryUrls(\Magento\Catalog\Model\Category $cat, st } foreach ($cat->getChildrenCategories() as $child) { - $key = $parent ? $parent . $this->getConfigHelper()->getCategorySeparator() . $child->getName() : $child ->getName(); + $key = $parent ? $parent . $this->getConfigHelper()->getCategorySeparator($this->getStoreId()) . $child->getName() : $child ->getName(); $arr[$key]['url'] = $child->getUrl(); $arr = array_merge($arr, $this->getChildCategoryUrls($child, $key, $arr)); } @@ -123,7 +126,9 @@ public function getConfiguration() if ($category && $category->getDisplayMode() !== 'PAGE') { $category->getUrlInstance()->setStore($this->getStoreId()); - $childCategories = $this->getChildCategoryUrls($category); + if (self::IS_CATEGORY_NAVIGATION_ENABLED) { + $childCategories = $this->getChildCategoryUrls($category); + } $categoryId = $category->getId(); @@ -180,7 +185,6 @@ public function getConfiguration() } $attributesToFilter = $config->getAttributesToFilter($customerGroupId); - $algoliaJsConfig = [ 'instant' => [ 'enabled' => $config->isInstantEnabled(), @@ -192,7 +196,8 @@ public function getConfiguration() 'isSearchBoxEnabled' => $config->isInstantSearchBoxEnabled(), 'isVisualMerchEnabled' => $config->isVisualMerchEnabled(), 'categorySeparator' => $config->getCategorySeparator(), - 'categoryPageIdAttribute' => $config->getCategoryPageIdAttributeName() + 'categoryPageIdAttribute' => $config->getCategoryPageIdAttributeName(), + 'isCategoryNavigationEnabled' => self::IS_CATEGORY_NAVIGATION_ENABLED ], 'autocomplete' => [ 'enabled' => $config->isAutoCompleteEnabled(), diff --git a/view/frontend/web/instantsearch.js b/view/frontend/web/instantsearch.js index 27366cf53..19a161343 100644 --- a/view/frontend/web/instantsearch.js +++ b/view/frontend/web/instantsearch.js @@ -258,6 +258,7 @@ requirejs(['algoliaBundle', 'Magento_Catalog/js/price-utils'], function (algolia **/ currentRefinements: { container: '#current-refinements', + // TODO: Remove this - it does nothing templates: { item: $('#current-refinements-template').html() }, @@ -464,12 +465,12 @@ requirejs(['algoliaBundle', 'Magento_Catalog/js/price-utils'], function (algolia transformItems(items) { return (algoliaConfig.isCategoryPage) ? findRoot(items).map( + // TODO: Make recursive and implement custom renderer item => { - if (true) { - item.categoryUrl = algoliaConfig.request.childCategories[item.value]['url']; - } - console.log('item:', item); - return item; + return { + ...item, + categoryUrl: algoliaConfig.instant.isCategoryNavigationEnabled ? algoliaConfig.request.childCategories[item.value]['url'] : '' + }; } ) : items; From 8aa5fd4cf5e0516612c7defa58c1b867a7d7e711 Mon Sep 17 00:00:00 2001 From: Eric Wright Date: Thu, 1 Jun 2023 11:45:43 -0400 Subject: [PATCH 19/32] MAGE-569 Merge conflict resolution --- etc/adminhtml/system.xml | 8 +- view/frontend/web/instantsearch.js | 465 +++++++++++++++-------------- 2 files changed, 247 insertions(+), 226 deletions(-) diff --git a/etc/adminhtml/system.xml b/etc/adminhtml/system.xml index 9f76af712..5841eb705 100755 --- a/etc/adminhtml/system.xml +++ b/etc/adminhtml/system.xml @@ -783,13 +783,7 @@ Magento\Config\Model\Config\Source\Yesno - -
- To schedule the run you need to add this in your crontab:
- */5 * * * * php /absolute/path/to/magento/bin/magento indexer:reindex algolia_queue_runner -
Enabling this option is recommended in production or if your store has a lot of products. - ]]> + Algolia\AlgoliaSearch\Model\Config\Link
diff --git a/view/frontend/web/instantsearch.js b/view/frontend/web/instantsearch.js index 13c722f0c..2fa626912 100644 --- a/view/frontend/web/instantsearch.js +++ b/view/frontend/web/instantsearch.js @@ -111,7 +111,11 @@ define( } } - instantsearchOptions = algolia.triggerHooks('beforeInstantsearchInit', instantsearchOptions, algoliaBundle); + if (algoliaConfig.instant.isVisualMerchEnabled && algoliaConfig.isCategoryPage ) { + searchParameters.filters = `${algoliaConfig.instant.categoryPageIdAttribute}:'${algoliaConfig.request.path}'`; + } + + instantsearchOptions = algolia.triggerHooks('beforeInstantsearchInit', instantsearchOptions, algoliaBundle); var search = algoliaBundle.instantsearch(instantsearchOptions); @@ -143,192 +147,201 @@ define( }); var allWidgetConfiguration = { - infiniteHits: {}, - hits: {}, - configure: searchParameters, - custom: [ - /** - * Custom widget - this widget is used to refine results for search page or catalog page - * Docs: https://www.algolia.com/doc/guides/building-search-ui/widgets/create-your-own-widgets/js/ - **/ - { - getWidgetSearchParameters: function (searchParameters) { - if (algoliaConfig.request.query.length > 0 && location.hash.length < 1) { - return searchParameters.setQuery(algoliaConfig.request.query) - } - return searchParameters; - }, - init: function (data) { - var page = data.helper.state.page; - - if (algoliaConfig.request.refinementKey.length > 0) { - data.helper.toggleRefine(algoliaConfig.request.refinementKey, algoliaConfig.request.refinementValue); - } - - if (algoliaConfig.isCategoryPage) { - data.helper.addNumericRefinement('visibility_catalog', '=', 1); - } else { - data.helper.addNumericRefinement('visibility_search', '=', 1); - } - - data.helper.setPage(page); - }, - render: function (data) { - if (!algoliaConfig.isSearchPage) { - if (data.results.query.length === 0 && data.results.nbHits === 0) { - $('.algolia-instant-replaced-content').show(); - $('.algolia-instant-selector-results').hide(); - } else { - $('.algolia-instant-replaced-content').hide(); - $('.algolia-instant-selector-results').show(); - } - } - } - }, - /** - * Custom widget - Suggestions - * This widget renders suggestion queries which might be interesting for your customer - * Docs: https://www.algolia.com/doc/guides/building-search-ui/widgets/create-your-own-widgets/js/ - **/ - { - suggestions: [], - init: function () { - if (algoliaConfig.showSuggestionsOnNoResultsPage) { - var $this = this; - $.each(algoliaConfig.popularQueries.slice(0, Math.min(4, algoliaConfig.popularQueries.length)), function (i, query) { - query = $('
').html(query).text(); //xss - $this.suggestions.push('' + query + ''); - }); - } - }, - render: function (data) { - if (data.results.hits.length === 0) { - var content = '
'; - content += '
' + algoliaConfig.translations.noProducts + ' "' + $("
").text(data.results.query).html() + '"
'; - content += ''; - content += algoliaConfig.translations.or + ' ' + algoliaConfig.translations.seeAll + '' - - content += '
'; - - $('#instant-empty-results-container').html(content); - } else { - $('#instant-empty-results-container').html(''); - } - } - } - ], - /** - * stats - * Docs: https://www.algolia.com/doc/api-reference/widgets/stats/js/ - **/ - stats: { - container: '#algolia-stats', - templates: { - text: function (data) { - var hoganTemplate = algoliaBundle.Hogan.compile($('#instant-stats-template').html()); - - data.first = data.page * data.hitsPerPage + 1; - data.last = Math.min(data.page * data.hitsPerPage + data.hitsPerPage, data.nbHits); - data.seconds = data.processingTimeMS / 1000; - data.translations = window.algoliaConfig.translations; - - return hoganTemplate.render(data) - } - } - }, - /** - * sortBy - * Docs: https://www.algolia.com/doc/api-reference/widgets/sort-by/js/ - **/ - sortBy: { - container: '#algolia-sorts', - items: algoliaConfig.sortingIndices.map(function (sortingIndice) { - return { - label: sortingIndice.label, - value: sortingIndice.name, - } - }) - }, - /** - * currentRefinements - * Widget displays all filters and refinements applied on query. It also let your customer to clear them one by one - * Docs: https://www.algolia.com/doc/api-reference/widgets/current-refinements/js/ - **/ - currentRefinements: { - container: '#current-refinements', - templates: { - item: $('#current-refinements-template').html() - }, - includedAttributes: attributes.map(function (attribute) { - if (!(algoliaConfig.isCategoryPage && attribute.name.indexOf('categories') > -1)) { - return attribute.name; - } - }), - transformItems: function (items) { - return items.map(function (item) { - var attribute = attributes.filter(function (_attribute) { - return item.attribute === _attribute.name - })[0]; - if (!attribute) return item; - item.label = attribute.label; - item.refinements.forEach(function (refinement) { - if (refinement.type !== 'hierarchical') return refinement; - var levels = refinement.label.split('///'); - var lastLevel = levels[levels.length - 1]; - refinement.label = lastLevel; - }); - return item; - }) - } - }, - - /* - * clearRefinements - * Widget displays a button that lets the user clean every refinement applied to the search. You can control which attributes are impacted by the button with the options. - * Docs: https://www.algolia.com/doc/api-reference/widgets/clear-refinements/js/ - **/ - clearRefinements: { - container: '#clear-refinements', - templates: { - resetLabel: algoliaConfig.translations.clearAll, - }, - includedAttributes: attributes.map(function (attribute) { - if (!(algoliaConfig.isCategoryPage && attribute.name.indexOf('categories') > -1)) { - return attribute.name; - } - }), - cssClasses: { - button: ['action', 'primary'] - }, - transformItems: function (items) { - return items.map(function (item) { - var attribute = attributes.filter(function (_attribute) { - return item.attribute === _attribute.name - })[0]; - if (!attribute) return item; - item.label = attribute.label; - return item; - }) - } - }, - /* - * queryRuleCustomData - * The queryRuleCustomData widget displays custom data from Query Rules. - * Docs: https://www.algolia.com/doc/api-reference/widgets/query-rule-custom-data/js/ - **/ - queryRuleCustomData: { - container: '#algolia-banner', - templates: { - default: '{{#items}} {{#banner}} {{{banner}}} {{/banner}} {{/items}}', - } - } - }; + infiniteHits: {}, + hits : {}, + configure : searchParameters, + custom : [ + /** + * Custom widget - this widget is used to refine results for search page or catalog page + * Docs: https://www.algolia.com/doc/guides/building-search-ui/widgets/create-your-own-widgets/js/ + **/ + { + getWidgetSearchParameters: function (searchParameters) { + if (algoliaConfig.request.query.length > 0 && location.hash.length < 1) { + return searchParameters.setQuery(algoliaConfig.request.query) + } + return searchParameters; + }, + init : function (data) { + var page = data.helper.state.page; + + if (algoliaConfig.request.refinementKey.length > 0) { + data.helper.toggleRefine(algoliaConfig.request.refinementKey, algoliaConfig.request.refinementValue); + } + + if (algoliaConfig.isCategoryPage) { + data.helper.addNumericRefinement('visibility_catalog', '=', 1); + } else { + data.helper.addNumericRefinement('visibility_search', '=', 1); + } + + data.helper.setPage(page); + }, + render : function (data) { + if (!algoliaConfig.isSearchPage) { + if (data.results.query.length === 0 && data.results.nbHits === 0) { + $('.algolia-instant-replaced-content').show(); + $('.algolia-instant-selector-results').hide(); + } else { + $('.algolia-instant-replaced-content').hide(); + $('.algolia-instant-selector-results').show(); + } + } + } + }, + /** + * Custom widget - Suggestions + * This widget renders suggestion queries which might be interesting for your customer + * Docs: https://www.algolia.com/doc/guides/building-search-ui/widgets/create-your-own-widgets/js/ + **/ + { + suggestions: [], + init : function () { + if (algoliaConfig.showSuggestionsOnNoResultsPage) { + var $this = this; + $.each(algoliaConfig.popularQueries.slice(0, Math.min(4, algoliaConfig.popularQueries.length)), function (i, query) { + query = $('
').html(query).text(); //xss + $this.suggestions.push('' + query + ''); + }); + } + }, + render : function (data) { + if (data.results.hits.length === 0) { + var content = '
'; + content += '
' + algoliaConfig.translations.noProducts + ' "' + $("
").text(data.results.query).html() + '"
'; + content += ''; + content += algoliaConfig.translations.or + ' ' + algoliaConfig.translations.seeAll + '' + + content += '
'; + + $('#instant-empty-results-container').html(content); + } else { + $('#instant-empty-results-container').html(''); + } + } + } + ], + /** + * stats + * Docs: https://www.algolia.com/doc/api-reference/widgets/stats/js/ + **/ + stats: { + container: '#algolia-stats', + templates: { + text: function (data) { + var hoganTemplate = algoliaBundle.Hogan.compile($('#instant-stats-template').html()); + + data.first = data.page * data.hitsPerPage + 1; + data.last = Math.min(data.page * data.hitsPerPage + data.hitsPerPage, data.nbHits); + data.seconds = data.processingTimeMS / 1000; + data.translations = window.algoliaConfig.translations; + + return hoganTemplate.render(data) + } + } + }, + /** + * sortBy + * Docs: https://www.algolia.com/doc/api-reference/widgets/sort-by/js/ + **/ + sortBy: { + container: '#algolia-sorts', + items : algoliaConfig.sortingIndices.map(function (sortingIndice) { + return { + label: sortingIndice.label, + value: sortingIndice.name, + } + }) + }, + /** + * currentRefinements + * Widget displays all filters and refinements applied on query. It also let your customer to clear them one by one + * Docs: https://www.algolia.com/doc/api-reference/widgets/current-refinements/js/ + **/ + currentRefinements: { + container : '#current-refinements', + // TODO: Remove this - it does nothing + templates : { + item: $('#current-refinements-template').html() + }, + includedAttributes: attributes.map(attribute => { + if (attribute.name.indexOf('categories') === -1 + || !algoliaConfig.isCategoryPage) // For category browse, requires a custom renderer to prevent removal of the root node from hierarchicalMenu widget + return attribute.name; + }), + + transformItems: items => { + return items + // This filter is only applicable if categories facet is included as an attribute + .filter(item => { + return !algoliaConfig.isCategoryPage + || item.refinements.filter(refinement => refinement.value !== algoliaConfig.request.path).length; // do not expose the category root + }) + .map(item => { + const attribute = attributes.filter(_attribute => { + return item.attribute === _attribute.name + })[0]; + if (!attribute) return item; + item.label = attribute.label; + item.refinements.forEach(function (refinement) { + if (refinement.type !== 'hierarchical') return refinement; + + const levels = refinement.label.split(algoliaConfig.instant.categorySeparator); + const lastLevel = levels[levels.length - 1]; + refinement.label = lastLevel; + }); + return item; + }); + }, + + /* + * clearRefinements + * Widget displays a button that lets the user clean every refinement applied to the search. You can control which attributes are impacted by the button with the options. + * Docs: https://www.algolia.com/doc/api-reference/widgets/clear-refinements/js/ + **/ + clearRefinements: { + container : '#clear-refinements', + templates : { + resetLabel: algoliaConfig.translations.clearAll, + }, + includedAttributes: attributes.map(function (attribute) { + if (!(algoliaConfig.isCategoryPage && attribute.name.indexOf('categories') > -1)) { + return attribute.name; + } + }), + cssClasses : { + button: ['action', 'primary'] + }, + transformItems : function (items) { + return items.map(function (item) { + var attribute = attributes.filter(function (_attribute) { + return item.attribute === _attribute.name + })[0]; + if (!attribute) return item; + item.label = attribute.label; + return item; + }) + } + }, + /* + * queryRuleCustomData + * The queryRuleCustomData widget displays custom data from Query Rules. + * Docs: https://www.algolia.com/doc/api-reference/widgets/query-rule-custom-data/js/ + **/ + queryRuleCustomData: { + container: '#algolia-banner', + templates: { + default: '{{#items}} {{#banner}} {{{banner}}} {{/banner}} {{/items}}', + } + } + } + }; if (algoliaConfig.instant.isSearchBoxEnabled) { /** @@ -435,42 +448,56 @@ define( hierarchical_levels.push('categories.level' + l.toString()); } - var hierarchicalMenuParams = { - container: facet.wrapper.appendChild(createISWidgetContainer(facet.attribute)), - attributes: hierarchical_levels, - separator: ' /// ', - templates: templates, - alwaysGetRootLevel: false, - showParentLevel: false, - limit: algoliaConfig.maxValuesPerFacet, - sortBy: ['name:asc'], - transformItems(items) { - if (algoliaConfig.isCategoryPage) { - var filteredData = []; - items.forEach(element => { - if (element.label == algoliaConfig.request.parentCategory) { - filteredData.push(element); - } - }); - items = filteredData; - } - return items.map(item => ({ - ...item, - label: item.label, - })); - }, - }; + //return array of items starting from root based on category + const findRoot = (items) => { + const root = items.find(element => algoliaConfig.request.path.startsWith(element.value)); + + if (!root) { + return items; + } + if (!root.data) { + return []; + } + + return findRoot(root.data); + + }; + + var hierarchicalMenuParams = { + container : facet.wrapper.appendChild(createISWidgetContainer(facet.attribute)), + attributes : hierarchical_levels, + separator : algoliaConfig.instant.categorySeparator, + templates : templates, + showParentLevel : true, + limit : algoliaConfig.maxValuesPerFacet, + sortBy : ['name:asc'], + transformItems(items) { + return (algoliaConfig.isCategoryPage) + ? findRoot(items).map( + item => { + return { + ...item, + categoryUrl: algoliaConfig.instant.isCategoryNavigationEnabled ? algoliaConfig.request.childCategories[item.value]['url'] : '' + }; + } + ) + : items; + }, + }; hierarchicalMenuParams.templates.item = '' + - '{{label}}' + ' ' + - '{{#helpers.formatNumber}}{{count}}{{/helpers.formatNumber}}' + + '{{label}}' + ' ' + + '{{#helpers.formatNumber}}{{count}}{{/helpers.formatNumber}}' + ''; hierarchicalMenuParams.panelOptions = { - templates: { - header: '
' + (facet.label ? facet.label : facet.attribute) + '
', - } - }; + templates: { + header: '
' + (facet.label ? facet.label : facet.attribute) + '
', + }, + hidden : function ({items}) { + return !items.length; + } + }; return ['hierarchicalMenu', hierarchicalMenuParams]; } @@ -689,4 +716,4 @@ define( return options; } } -); \ No newline at end of file +); From de4872a049a2a04abf858e06f99f0691702cb5fc Mon Sep 17 00:00:00 2001 From: Eric Wright Date: Thu, 15 Jun 2023 15:24:22 -0400 Subject: [PATCH 20/32] MAGE-674 Add frontend capture of facet routing params for redirect after add to cart from PLP --- Block/Algolia.php | 10 ++++++---- Block/Configuration.php | 3 ++- view/frontend/templates/instant/hit.phtml | 2 +- view/frontend/web/instantsearch.js | 14 +++++++++++++- view/frontend/web/internals/common.js | 1 + 5 files changed, 23 insertions(+), 7 deletions(-) diff --git a/Block/Algolia.php b/Block/Algolia.php index 8ed8f8c88..c87e58eef 100755 --- a/Block/Algolia.php +++ b/Block/Algolia.php @@ -270,13 +270,12 @@ public function getLastOrder() return $this->checkoutSession->getLastRealOrder(); } - public function getAddToCartParams() + public function getAddToCartParams() : array { - $url = $this->getAddToCartUrl(); - return [ - 'action' => $url, + 'action' => $this->_urlBuilder->getUrl('checkout/cart/add', []), 'formKey' => $this->formKey->getFormKey(), + 'redirectUrlParam' => ActionInterface::PARAM_NAME_URL_ENCODED ]; } @@ -285,6 +284,9 @@ public function getTimestamp() return $this->date->gmtTimestamp('today midnight'); } + /** + * @deprecated This function is deprecated as redirect routes must be derived on the frontend not backend + */ protected function getAddToCartUrl($additional = []) { $continueUrl = $this->urlHelper->getEncodedUrl($this->_urlBuilder->getCurrentUrl()); diff --git a/Block/Configuration.php b/Block/Configuration.php index 762bb6721..a169982f7 100755 --- a/Block/Configuration.php +++ b/Block/Configuration.php @@ -233,6 +233,7 @@ public function getConfiguration() 'path' => $path, 'level' => $level, 'parentCategory' => $parentCategoryName, + 'url' => $this->getUrl('*/*/*', ['_use_rewrite' => true, '_forced_secure' => true]) ], 'showCatsNotIncludedInNavigation' => $config->showCatsNotIncludedInNavigation(), 'showSuggestionsOnNoResultsPage' => $config->showSuggestionsOnNoResultsPage(), @@ -381,4 +382,4 @@ protected function getLandingPageConfiguration() { return $this->isLandingPage() ? $this->getCurrentLandingPage()->getConfiguration() : json_encode([]); } -} \ No newline at end of file +} diff --git a/view/frontend/templates/instant/hit.phtml b/view/frontend/templates/instant/hit.phtml index 8e18c1d0b..fb65c7943 100644 --- a/view/frontend/templates/instant/hit.phtml +++ b/view/frontend/templates/instant/hit.phtml @@ -86,7 +86,7 @@ $origFormatedVar = $block->escapeHtml('price' . $priceKey . '_original_formated' {{#_highlightResult.default_bundle_options}}{{/_highlightResult.default_bundle_options}} - +