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 00cd5a89a..5f124e339 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()) { @@ -30,6 +33,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($this->getStoreId()) . $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 +107,7 @@ public function getConfiguration() $level = ''; $categoryId = ''; $parentCategoryName = ''; + $childCategories = []; $addToCartParams = $this->getAddToCartParams(); @@ -88,14 +126,17 @@ public function getConfiguration() if ($category && $category->getDisplayMode() !== 'PAGE') { $category->getUrlInstance()->setStore($this->getStoreId()); + if (self::IS_CATEGORY_NAVIGATION_ENABLED) { + $childCategories = $this->getChildCategoryUrls($category); + } $categoryId = $category->getId(); $level = -1; foreach ($category->getPathIds() as $treeCategoryId) { if ($path !== '') { - $path .= ' /// '; - }else{ + $path .= $config->getCategorySeparator(); + } else { $parentCategoryName = $categoryHelper->getCategoryName($treeCategoryId, $this->getStoreId()); } @@ -144,7 +185,6 @@ public function getConfiguration() } $attributesToFilter = $config->getAttributesToFilter($customerGroupId); - $algoliaJsConfig = [ 'instant' => [ 'enabled' => $config->isInstantEnabled(), @@ -154,6 +194,10 @@ public function getConfiguration() 'infiniteScrollEnabled' => $config->isInfiniteScrollEnabled(), 'urlTrackedParameters' => $this->getUrlTrackedParameters(), 'isSearchBoxEnabled' => $config->isInstantSearchBoxEnabled(), + 'isVisualMerchEnabled' => $config->isVisualMerchEnabled(), + 'categorySeparator' => $config->getCategorySeparator(), + 'categoryPageIdAttribute' => $config->getCategoryPageIdAttributeName(), + 'isCategoryNavigationEnabled' => self::IS_CATEGORY_NAVIGATION_ENABLED, 'hidePagination' => $config->hidePaginationInInstantSearchPage() ], 'autocomplete' => [ @@ -196,10 +240,7 @@ public function getConfiguration() 'indexName' => $coreHelper->getBaseIndexName(), 'apiKey' => $algoliaHelper->generateSearchSecuredApiKey( $config->getSearchOnlyAPIKey(), - array_merge( - $config->getAttributesToRetrieve($customerGroupId), - $attributesToFilter - ) + $attributesToFilter ), 'attributeFilter' => $attributesToFilter, 'facets' => $facets, @@ -234,6 +275,8 @@ public function getConfiguration() 'path' => $path, 'level' => $level, 'parentCategory' => $parentCategoryName, + 'childCategories' => $childCategories, + 'url' => $this->getUrl('*/*/*', ['_use_rewrite' => true, '_forced_secure' => true]) ], 'showCatsNotIncludedInNavigation' => $config->showCatsNotIncludedInNavigation(), 'showSuggestionsOnNoResultsPage' => $config->showSuggestionsOnNoResultsPage(), diff --git a/CHANGELOG.md b/CHANGELOG.md index 4bec36d74..82866a101 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,14 @@ # CHANGE LOG +## 3.11.0-beta + +### Bug Fixes +- Support for Merch Studio and the Visual Merchandiser. +- Upgraded the Algolia PHP client to version 3.3.2 +- Upgraded the Algolia insight 2.6.0 +- Preserve facet selections after adding an item to the cart from the PLP +- Fixes related to Neural Search compatibility + ## 3.10.5 ### Bug Fixes 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/AlgoliaHelper.php b/Helper/AlgoliaHelper.php index c4351fac6..63c764005 100755 --- a/Helper/AlgoliaHelper.php +++ b/Helper/AlgoliaHelper.php @@ -193,7 +193,13 @@ public function generateSearchSecuredApiKey($key, $params = []) public function getSettings($indexName) { - return $this->getIndex($indexName)->getSettings(); + try { + return $this->getIndex($indexName)->getSettings(); + }catch (\Exception $e) { + if ($e->getCode() !== 404) { + throw $e; + } + } } public function mergeSettings($indexName, $settings, $mergeSettingsFrom = '') diff --git a/Helper/ConfigHelper.php b/Helper/ConfigHelper.php index d37ab3baa..50bebedb1 100755 --- a/Helper/ConfigHelper.php +++ b/Helper/ConfigHelper.php @@ -49,11 +49,14 @@ class ConfigHelper public const PRODUCT_ATTRIBUTES = 'algoliasearch_products/products/product_additional_attributes'; 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 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'; 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'; @@ -885,6 +888,24 @@ 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 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 @@ -1454,6 +1475,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 2478840d3..52405a4cd 100755 --- a/Helper/Entity/ProductHelper.php +++ b/Helper/Entity/ProductHelper.php @@ -469,11 +469,11 @@ public function setSettings($indexName, $indexNameTmp, $storeId, $saveToTmpIndic // Merge current replicas with sorting replicas to not delete A/B testing replica indices try { $currentSettings = $this->algoliaHelper->getSettings($indexName); - if (array_key_exists('replicas', $currentSettings)) { + if (is_array($currentSettings) && array_key_exists('replicas', $currentSettings)) { $replicas = array_values(array_unique(array_merge($replicas, $currentSettings['replicas']))); } } catch (AlgoliaException $e) { - if ($e->getMessage() !== 'Index does not exist') { + if ($e->getCode() !== 404) { throw $e; } } @@ -527,9 +527,7 @@ public function setSettings($indexName, $indexNameTmp, $storeId, $saveToTmpIndic Copying query rules to "' . $indexNameTmp . '" to not to erase them with the index move. '); } catch (AlgoliaException $e) { - // Fail silently if query rules are disabled on the app - // If QRs are disabled, nothing will happen and the extension will work as expected - if ($e->getMessage() !== 'Query Rules are not enabled on this application') { + if ($e->getCode() !== 404) { throw $e; } } @@ -734,6 +732,53 @@ 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; + } + } + } + + /** + * 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); + + } + + /** + * 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)) + ); + } + /** * @param $customData * @param Product $product @@ -758,22 +803,28 @@ protected function addBundleProductDefaultOptions($customData, Product $product } /** - * @param $customData + * 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($customData, 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(); - if (is_array($_categoryIds) && count($_categoryIds) > 0) { + if (is_array($_categoryIds) && count($_categoryIds)) { $categoryCollection = $this->getAllCategories($_categoryIds, $storeId); /** @var Store $store */ @@ -781,84 +832,123 @@ 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; } + $categoryData['categoryNames'][] = $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; + $categoryData['categoryIds'][] = $treeCategoryId; + $paths[] = $name; } } - $categoriesWithPath[] = $path; + $categoryData['categoriesWithPath'][] = $paths; } } - foreach ($categoriesWithPath as $result) { - for ($i = count($result) - 1; $i > 0; $i--) { - $categoriesWithPath[] = array_slice($result, 0, $i); + // 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']); + + return $categoryData; + } + + /** + * Flatten non hierarchical paths for merchandising + * + * @param array $paths + * @return array + */ + protected function flattenCategoryPaths(array $paths, int $storeId): array + { + return array_map( + function ($path) use ($storeId) { return implode($this->configHelper->getCategorySeparator($storeId), $path); }, + $paths + ); + } + + /** + * 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); + } - $categoriesWithPath = array_intersect_key( - $categoriesWithPath, - array_unique(array_map('serialize', $categoriesWithPath)) - ); + /** + * @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 + { + $storeId = $product->getStoreId(); - $hierarchicalCategories = $this->getHierarchicalCategories($categoriesWithPath); + $categoryData = $this->buildCategoryData($product); + $hierarchicalCategories = $this->getHierarchicalCategories($categoryData['categoriesWithPath'], $storeId); + $algoliaData['categories'] = $hierarchicalCategories; + $algoliaData['categories_without_path'] = $categoryData['categoryNames']; + $algoliaData['categoryIds'] = array_values(array_unique($categoryData['categoryIds'])); - $customData['categories'] = $hierarchicalCategories; - $customData['categories_without_path'] = $categories; - $customData['categoryIds'] = array_values(array_unique($categoryIds)); + if ($this->configHelper->isVisualMerchEnabled($storeId)) { + $autoAnchorPaths = $this->autoAnchorParentCategories($categoryData['categoriesWithPath']); + $algoliaData[$this->configHelper->getCategoryPageIdAttributeName($storeId)] = $this->flattenCategoryPaths($autoAnchorPaths, $storeId); + } - return $customData; + return $algoliaData; } /** - * @param $categoriesWithPath + * @param array $categoriesWithPath + * @param int $storeId * @return array */ - protected function getHierarchicalCategories($categoriesWithPath) + protected function getHierarchicalCategories(array $categoriesWithPath, int $storeId): array { - $hierachivalCategories = []; + $hierarchicalCategories = []; $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($hierarchicalCategories[$levelName . $i]) === false) { + $hierarchicalCategories[$levelName . $i] = []; } if ($category[$i] === null) { continue; } - $hierachivalCategories[$levelName . $i][] = implode(' /// ', array_slice($category, 0, $i + 1)); + $hierarchicalCategories[$levelName . $i][] = implode($this->configHelper->getCategorySeparator($storeId), array_slice($category, 0, $i + 1)); } } - foreach ($hierachivalCategories as &$level) { + // dedupe in case of multi category assignment + foreach ($hierarchicalCategories as &$level) { $level = array_values(array_unique($level)); } - return $hierachivalCategories; + return $hierarchicalCategories; } /** @@ -1254,6 +1344,10 @@ protected function getAttributesForFaceting($storeId) // Used for merchandising $attributesForFaceting[] = 'categoryIds'; + if ($this->configHelper->isVisualMerchEnabled($storeId)) { + $attributesForFaceting[] = 'searchable(' . $this->configHelper->getCategoryPageIdAttributeName($storeId) . ')'; + } + return $attributesForFaceting; } @@ -1366,9 +1460,7 @@ protected function clearFacetsQueryRules(SearchIndex $index) $page++; } while (($page * $hitsPerPage) < $fetchedQueryRules['nbHits']); } catch (AlgoliaException $e) { - // Fail silently if query rules are disabled on the app - // If QRs are disabled, nothing will happen and the extension will work as expected - if ($e->getMessage() !== 'Query Rules are not enabled on this application') { + if ($e->getCode() !== 404) { throw $e; } } diff --git a/Helper/Logger.php b/Helper/Logger.php index 8707b5564..a50b9a739 100755 --- a/Helper/Logger.php +++ b/Helper/Logger.php @@ -74,8 +74,15 @@ public function log($message) } } + public function error($message) { + if ($this->enabled) { + $this->logger->error($message); + } + } + private function formatTime($begin, $end) { return ($end - $begin) . 'sec'; } + } diff --git a/Helper/MerchandisingHelper.php b/Helper/MerchandisingHelper.php index 90e237e7a..092e61523 100755 --- a/Helper/MerchandisingHelper.php +++ b/Helper/MerchandisingHelper.php @@ -160,9 +160,7 @@ public function copyQueryRules($storeId, $entityIdFrom, $entityIdTo, $entityType ]); } } catch (AlgoliaException $e) { - // Fail silently if query rules are disabled on the app - // If QRs are disabled, nothing will happen and the extension will work as expected - if ($e->getMessage() !== 'Query Rules are not enabled on this application') { + if ($e->getCode() !== 404) { throw $e; } } diff --git a/Model/Observer/SaveSettings.php b/Model/Observer/SaveSettings.php index 898e0e9b9..1b1fa3bc4 100755 --- a/Model/Observer/SaveSettings.php +++ b/Model/Observer/SaveSettings.php @@ -25,7 +25,7 @@ class SaveSettings implements ObserverInterface protected $algoliaHelper; /** - * @var AlgoliaHelper + * @var Data */ protected $helper; @@ -67,7 +67,7 @@ public function execute(Observer $observer) foreach ($storeIds as $storeId) { $indexName = $this->helper->getIndexName($this->productHelper->getIndexNameSuffix(), $storeId); $currentSettings = $this->algoliaHelper->getSettings($indexName); - if (array_key_exists('replicas', $currentSettings)) { + if (is_array($currentSettings) && array_key_exists('replicas', $currentSettings)) { $this->algoliaHelper->setSettings($indexName, ['replicas' => []]); $setReplicasTaskId = $this->algoliaHelper->getLastTaskId(); $this->algoliaHelper->waitLastTask($indexName, $setReplicasTaskId); @@ -79,7 +79,7 @@ public function execute(Observer $observer) } } } catch (\Exception $e) { - if ($e->getMessage() !== 'Index does not exist') { + if ($e->getCode() !== 404) { throw $e; } } diff --git a/README.md b/README.md index 854772b90..a81674e20 100755 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ Algolia Search for Magento 2 ============================ -![Latest version](https://img.shields.io/badge/latest-3.10.5-green) +![Latest version](https://img.shields.io/badge/latest-3.11.0%20beta-green) ![Magento 2](https://img.shields.io/badge/Magento-2.4.x-orange) ![PHP](https://img.shields.io/badge/PHP-8.2%2C8.1%2C7.4-blue) @@ -81,6 +81,7 @@ Knowing the version of the library will help you understand what is available in | v3.x | [0.38.0](https://github.com/algolia/autocomplete.js/tree/v0.38.0) | [4.15.0](https://github.com/algolia/instantsearch.js/tree/v4.15.0) | [1.7.1](https://github.com/algolia/search-insights.js/tree/v1.7.1) | NA | | v3.9.1 | [1.6.3](https://github.com/algolia/autocomplete.js/tree/v1.6.3) | [4.41.0](https://github.com/algolia/instantsearch.js/tree/v4.41.0) | [1.7.1](https://github.com/algolia/search-insights.js/tree/v1.7.1) | [1.5.0](https://github.com/algolia/recommend/tree/v1.5.0) | | v3.10.x | [1.6.3](https://github.com/algolia/autocomplete.js/tree/v1.6.3) | [4.41.0](https://github.com/algolia/instantsearch.js/tree/v4.41.0) | [1.7.1](https://github.com/algolia/search-insights.js/tree/v1.7.1) | [1.8.0](https://github.com/algolia/recommend/tree/v1.8.0) | +| v3.11.0-beta | [1.6.3](https://github.com/algolia/autocomplete.js/tree/v1.6.3) | [4.41.0](https://github.com/algolia/instantsearch.js/tree/v4.41.0) | [2.6.0](https://github.com/algolia/search-insights.js/tree/v2.6.0) | [1.8.0](https://github.com/algolia/recommend/tree/v1.8.0) | The autocomplete and instantsearch libraries are accessible in the `algoliaBundle` global. This bundle is a prepackage javascript file that contains it's dependencies. What is included in this bundle can be seen here: diff --git a/composer.json b/composer.json index 2aaf0d519..7a837ab00 100644 --- a/composer.json +++ b/composer.json @@ -3,10 +3,10 @@ "description": "Algolia Search integration for Magento 2", "type": "magento2-module", "license": ["MIT"], - "version": "3.10.5", + "version": "3.11.0-beta", "require": { "magento/framework": "~102.0|~103.0", - "algolia/algoliasearch-client-php": "3.2", + "algolia/algoliasearch-client-php": "3.3.2", "guzzlehttp/guzzle": "^6.3.3|^7.3.0", "ext-json": "*", "ext-PDO": "*", diff --git a/etc/adminhtml/system.xml b/etc/adminhtml/system.xml index 5efe7aec0..9dba73d2e 100755 --- a/etc/adminhtml/system.xml +++ b/etc/adminhtml/system.xml @@ -459,6 +459,31 @@ ]]> + + + Magento\Config\Model\Config\Source\Yesno + +
+ 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 + +
@@ -514,6 +539,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 ed3e64a09..819711738 100644 --- a/etc/config.xml +++ b/etc/config.xml @@ -27,12 +27,15 @@ + 0 + categoryPageId + /// diff --git a/etc/module.xml b/etc/module.xml index ccd322ec6..7e8028918 100755 --- a/etc/module.xml +++ b/etc/module.xml @@ -1,6 +1,6 @@ - + diff --git a/view/adminhtml/templates/landingpage/search-configuration.phtml b/view/adminhtml/templates/landingpage/search-configuration.phtml index a40d38e24..274b74119 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,8 +484,7 @@ $isConfig = [ var hierarchicalMenuParams = { container: facet.wrapper.appendChild(createISWidgetContainer(facet.attribute)), attributes: hierarchical_levels, - separator: ' /// ', - alwaysGetRootLevel: true, + separator: config.categorySeparator, limit: config.maxValuesPerFacet, templates: templates, sortBy: ['name:asc'], 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}} - +