diff --git a/Api/CategoryVersionLoggerInterface.php b/Api/CategoryVersionLoggerInterface.php new file mode 100644 index 000000000..7fbf40dc9 --- /dev/null +++ b/Api/CategoryVersionLoggerInterface.php @@ -0,0 +1,31 @@ +config = $config; @@ -158,6 +165,7 @@ public function __construct( $this->personalizationHelper = $personalizationHelper; $this->checkoutSession = $checkoutSession; $this->date = $date; + $this->currentCategory = $currentCategory; parent::__construct($context, $data); } @@ -255,7 +263,7 @@ public function getStoreId() public function getCurrentCategory() { - return $this->registry->registry('current_category'); + return $this->currentCategory->get(); } /** @return Product */ diff --git a/Block/Configuration.php b/Block/Configuration.php index 1d770cfe3..d8b1af57a 100755 --- a/Block/Configuration.php +++ b/Block/Configuration.php @@ -122,6 +122,7 @@ public function getConfiguration() if ($config->isInstantEnabled() && $config->replaceCategories() && $request->getControllerName() === 'category') { + $category = $this->getCurrentCategory(); if ($category && $category->getDisplayMode() !== 'PAGE') { @@ -278,6 +279,8 @@ public function getConfiguration() 'level' => $level, 'parentCategory' => $parentCategoryName, 'childCategories' => $childCategories, + 'hasCategoryVersions' => $this->hasCategoryVersions(), + 'alternatePaths' => array_values($this->getAlternatePaths($path)), 'url' => $this->getUrl('*/*/*', ['_use_rewrite' => true, '_forced_secure' => true]) ], 'showCatsNotIncludedInNavigation' => $config->showCatsNotIncludedInNavigation(), @@ -427,4 +430,21 @@ protected function getLandingPageConfiguration() { return $this->isLandingPage() ? $this->getCurrentLandingPage()->getConfiguration() : json_encode([]); } + + protected function hasCategoryVersions(): bool + { + if (!$this->config->isCategoryVersionTrackingEnabled()) return false; + return (bool) $this->getCurrentCategory()->getExtensionAttributes()->getAlgoliaCategoryVersions()?->hasVersions(); + } + + protected function getAlternatePaths(string $mainPath): array + { + if (!$this->config->isCategoryVersionTrackingEnabled() || !$this->hasCategoryVersions()) return []; + return array_unique( + array_merge( + [$mainPath], + $this->getCurrentCategory()->getExtensionAttributes()->getAlgoliaCategoryVersions()->getSearchFilters() + ) + ); + } } diff --git a/Helper/ConfigHelper.php b/Helper/ConfigHelper.php index af2b3312a..d1c2f0023 100755 --- a/Helper/ConfigHelper.php +++ b/Helper/ConfigHelper.php @@ -55,6 +55,7 @@ class ConfigHelper 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 ENABLE_CATEGORY_VERSIONS = 'algoliasearch_products/products/enable_category_versions'; public const CATEGORY_ATTRIBUTES = 'algoliasearch_categories/categories/category_additional_attributes'; public const CATEGORY_CUSTOM_RANKING = 'algoliasearch_categories/categories/custom_ranking_category_attributes'; @@ -910,6 +911,15 @@ public function getCategoryPageIdAttributeName($storeId = null): string return (string) $this->configInterface->getValue(self::CATEGORY_PAGE_ID_ATTRIBUTE_NAME, ScopeInterface::SCOPE_STORE, $storeId); } + /** + * @param $storeId + * @return bool + */ + public function isCategoryVersionTrackingEnabled($storeId = null): bool + { + return $this->configInterface->isSetFlag(self::ENABLE_CATEGORY_VERSIONS, ScopeInterface::SCOPE_STORE, $storeId); + } + /** * @param $storeId * @return mixed diff --git a/Model/CategoryVersion.php b/Model/CategoryVersion.php new file mode 100644 index 000000000..320713c39 --- /dev/null +++ b/Model/CategoryVersion.php @@ -0,0 +1,77 @@ +_init(ResourceModel\CategoryVersion::class); + } + + /** + * @inheritDoc + */ + public function getIdentities(): array + { + return [self::CACHE_TAG . '_' . $this->getId()]; + } + + public function getCategoryId(): int + { + return $this->getData(CategoryVersionInterface::KEY_CATEGORY_ID); + } + + public function setCategoryId(int $categoryId): CategoryVersionInterface + { + return $this->setData(CategoryVersionInterface::KEY_CATEGORY_ID, $categoryId); + } + + public function getStoreId(): int + { + return $this->getData(CategoryVersionInterface::KEY_STORE_ID); + } + + public function setStoreId(int $storeId): CategoryVersionInterface + { + return $this->setData(CategoryVersionInterface::KEY_STORE_ID, $storeId); + } + + public function getOldValue(): string + { + return $this->getData(CategoryVersionInterface::KEY_OLD_VALUE); + } + + public function setOldValue(string $val): CategoryVersionInterface + { + return $this->setData(CategoryVersionInterface::KEY_OLD_VALUE, $val); + } + + public function getNewValue(): string + { + return $this->getData(CategoryVersionInterface::KEY_NEW_VALUE); + } + + public function setNewValue(string $val): CategoryVersionInterface + { + return $this->setData(CategoryVersionInterface::KEY_NEW_VALUE, $val); + } + + public function getUpdatedAt(): string + { + return $this->getData(CategoryVersionInterface::KEY_UPDATED_AT); + } + + public function setUpdatedAt(?string $val): CategoryVersionInterface + { + return $this->setData(CategoryVersionInterface::KEY_UPDATED_AT, $val); + } +} diff --git a/Model/CategoryVersion/CategoryVersionAttribute.php b/Model/CategoryVersion/CategoryVersionAttribute.php new file mode 100644 index 000000000..0b2f7f143 --- /dev/null +++ b/Model/CategoryVersion/CategoryVersionAttribute.php @@ -0,0 +1,92 @@ +categoryVersionRepository = $categoryVersionRepository; + $this->searchCriteriaBuilder = $searchCriteriaBuilder; + $this->versions = null; + } + + /** + * @inheritDoc + */ + public function load(int $categoryId, int $storeId = null) : CategoryVersionAttributeInterface + { + $this->categoryId = $categoryId; + $this->storeId = $storeId; + return $this; + } + + /** + * @inheritDoc + */ + public function hasVersions(int $storeId = null): bool + { + $storeId ??= $this->storeId; + if (!$storeId) return false; + + return (bool) count($this->getVersions($storeId)); + } + + /** + * @inheritDoc + */ + public function getVersions(int $storeId = null): array + { + $storeId ??= $this->storeId; + if (!$storeId) return []; + + if (!$this->versions) { + $searchCriteria = $this->searchCriteriaBuilder + ->addFilter(CategoryVersionInterface::KEY_CATEGORY_ID, $this->categoryId) + ->addFilter(CategoryVersionInterface::KEY_STORE_ID, $storeId); + /* @var CategoryVersionSearchResultsInterface */ + $this->versions = array_values($this->categoryVersionRepository->getList($searchCriteria->create())->getItems()); + } + + return $this->versions; + } + + /** + * @inheritDoc + */ + public function getSearchFilters(int $storeId = null): array + { + $storeId ??= $this->storeId; + if (!$storeId) return []; + + return array_map( + function(CategoryVersionInterface $version) { + return $version->getOldValue(); + }, + $this->getVersions($storeId) + ); + } +} diff --git a/Model/CategoryVersion/CategoryVersionLogger.php b/Model/CategoryVersion/CategoryVersionLogger.php new file mode 100644 index 000000000..9b3a801a4 --- /dev/null +++ b/Model/CategoryVersion/CategoryVersionLogger.php @@ -0,0 +1,235 @@ +config = $config; + $this->categoryRepository = $categoryRepository; + $this->categoryVersionRepository = $categoryVersionRepository; + $this->storeManager = $storeManager; + $this->searchCriteriaBuilder = $searchCriteriaBuilder; + } + + /** + * @inheritDoc + */ + public function logCategoryChange(Category $category, int $storedId = 0): void + { + if (!$this->config->isCategoryVersionTrackingEnabled($storedId)) return; + + foreach ($this->getStoreIds($category, $storedId) as $id) { + $newPath = $this->getNewCategoryPath($category, $id); + $oldPath = $this->getOldCategoryPath($category, $id); + /** @var CategoryVersionInterface $version */ + $version = $this->getCategoryVersion($category->getId(), $oldPath, $id); + $version->setCategoryId($category->getId()); + $version->setStoreId($id); + $version->setOldValue($oldPath); + $version->setNewValue($newPath); + $version->setUpdatedAt(null); + $this->categoryVersionRepository->save($version); + } + } + + /** + * @inheritDoc + */ + public function logCategoryMove(Category $category): void { + $defaultStoreId = self::DEFAULT_STORE; + if (!$this->config->isCategoryVersionTrackingEnabled($defaultStoreId)) return; + + foreach ($this->getStoreIds($category, $defaultStoreId, false) as $id) { + /** @var CategoryInterface */ + $scopedCategory = $this->getCachedCategory($category->getId(), $id); + $newPath = $this->getNewCategoryPath($scopedCategory, $id); + $oldPath = $this->getCategoryPath( + $scopedCategory->getName(), + $this->getPathIds($category->getOrigData(CategoryInterface::KEY_PATH)), + $id + ); + /** @var CategoryVersionInterface */ + $version = $this->getCategoryVersion($category->getId(), $oldPath, $id); + $version->setCategoryId($category->getId()); + $version->setStoreId($id); + $version->setOldValue($oldPath); + $version->setNewValue($newPath); + $version->setUpdatedAt(null); + $this->categoryVersionRepository->save($version); + } + } + + /** + * Get deduplicated record + * @param int $categoryId + * @param string $path + * @param int $storeId + * @return CategoryVersionInterface + */ + protected function getCategoryVersion(int $categoryId, string $path, int $storeId): CategoryVersionInterface { + $searchCriteria = $this->searchCriteriaBuilder + ->addFilter(CategoryVersionInterface::KEY_CATEGORY_ID, $categoryId) + ->addFilter(CategoryVersionInterface::KEY_OLD_VALUE, $path) + ->addFilter(CategoryVersionInterface::KEY_STORE_ID, $storeId); + /* @var CategoryVersionSearchResultsInterface */ + $results = $this->categoryVersionRepository->getList($searchCriteria->create()); + if ($results->getTotalCount()) { + return array_values($results->getItems())[0]; + } else { + return $this->categoryVersionRepository->getNew(); + } + } + + /** + * Get the new category path for the category being updated + * @param Category $category + * @param int $storeId + * @return string + * @throws NoSuchEntityException + */ + protected function getNewCategoryPath(Category $category, int $storeId): string + { + return $this->getCategoryPath($category->getName(), $category->getPathIds(), $storeId); + } + + /** + * Get the old category path for a standard category save operation + * @param Category $category + * @param int $storeId + * @return string + * @throws NoSuchEntityException + */ + protected function getOldCategoryPath(Category $category, int $storeId): string + { + return $this->getCategoryPath( + $category->getOrigData(CategoryInterface::KEY_NAME), + $this->getPathIds($category->getOrigData(CategoryInterface::KEY_PATH)), + $storeId + ); + } + + /** + * Get the category path name (used for category page IDs) + * @param string $categoryName + * @param array $pathIds + * @param int $storeId + * @return string + * @throws NoSuchEntityException + */ + protected function getCategoryPath(string $categoryName, array $pathIds, int $storeId): string + { + $path = $categoryName; + foreach (array_slice(array_reverse($pathIds), 1) as $treeCategoryId) { + $level = $this->getCachedCategory($treeCategoryId, $storeId); + if ((int) $level->getLevel() < self::MIN_CATEGORY_LEVEL) break; + $path = $level->getName() . $this->config->getCategorySeparator() . $path; + } + return $path; + } + + /** + * @param int $categoryId + * @param int $storeId + * @return CategoryInterface + * @throws NoSuchEntityException + */ + protected function getCachedCategory(int $categoryId, int $storeId): CategoryInterface + { + $key = "$categoryId-$storeId"; + if (!array_key_exists($key, $this->categoryCache)) { + $this->categoryCache[$key] = $this->categoryRepository->get($categoryId, $storeId); + } + return $this->categoryCache[$key]; + } + + /** + * For extracting path ids from orig path data + * @param string|null $path + * @return int[] + */ + protected function getPathIds(string|null $path): array + { + return $path !== null ? explode('/', $path) : []; + } + + /** + * Get applicable store ID's to log versions for - either the specified store, all stores or all non overridden stores + * @param CategoryInterface $category + * @param int $storeId - specific store or 0 (for default) + * @param bool $filterOverride - whether to include overridden stores + * @return array|int[] + * @throws NoSuchEntityException + */ + protected function getStoreIds(CategoryInterface $category, int $storeId, bool $filterOverride = true): array + { + //specified store + if ($storeId) return [$storeId]; + + $storeIds = []; + foreach (array_keys($this->storeManager->getStores()) as $id) { + if ($this->config->isEnabledBackend($id) + && $this->config->isCategoryVersionTrackingEnabled($id) + && (!$filterOverride || !$this->isCategoryOverridden($category, $id))) { + $storeIds[] = $id; + } + } + return $storeIds; + } + + /** + * @param CategoryInterface $defaultCategory + * @param int $storeId + * @return bool + * @throws NoSuchEntityException + */ + protected function isCategoryOverridden(CategoryInterface $defaultCategory, int $storeId): bool + { + $storeScopedCategory = $this->categoryRepository->get($defaultCategory->getId(), $storeId); + return $storeScopedCategory->getName() !== $defaultCategory->getName(); + } +} diff --git a/Model/CategoryVersion/CategoryVersionRepository.php b/Model/CategoryVersion/CategoryVersionRepository.php new file mode 100644 index 000000000..3f688bc2a --- /dev/null +++ b/Model/CategoryVersion/CategoryVersionRepository.php @@ -0,0 +1,116 @@ +versionFactory = $versionFactory; + $this->versionResource = $versionResource; + $this->searchResultsFactory = $searchResultsFactory; + $this->categoryVersionCollectionFactory = $categoryVersionCollectionFactory; + $this->collectionProcessor = $collectionProcessor; + } + + /** + * @inheritDoc + */ + public function getNew(): CategoryVersionInterface + { + return $this->versionFactory->create(); + } + + /** + * @inheritDoc + */ + public function save(CategoryVersionInterface $version): CategoryVersionInterface + { + $this->versionResource->save($version); + return $version; + } + + /** + * @inheritDoc + */ + public function deleteById($id): void + { + $version = $this->getById($id); + unset($this->versions[$id]); + $this->delete($version); + } + + /** + * @inheritDoc + */ + public function getById(int $id): CategoryVersionInterface + { + if (!isset($this->versions[$id])) { + $version = $this->versionFactory->create(); + $this->versionResource->load($version, $id); + if (!$version->getId()) { + throw new NoSuchEntityException(__("No category version with id $id exists.")); + } + $this->versions[$version->getId()] = $version; + } + return $this->versions[$id]; + } + + /** + * @inheritDoc + */ + public function delete(CategoryVersionInterface $version): void + { + $this->versionResource->delete($version); + } + + /** + * @inheritDoc + */ + public function getList(SearchCriteriaInterface $searchCriteria = null): CategoryVersionSearchResultsInterface + { + $collection = $this->categoryVersionCollectionFactory->create(); + $searchResults = $this->searchResultsFactory->create(); + if ($searchCriteria) { + $this->collectionProcessor->process($searchCriteria, $collection); + $searchResults->setSearchCriteria($searchCriteria); + } + $searchResults->setItems($collection->getItems()); + $searchResults->setTotalCount($collection->getSize()); + return $searchResults; + } +} diff --git a/Model/CategoryVersion/CategoryVersionSearchResults.php b/Model/CategoryVersion/CategoryVersionSearchResults.php new file mode 100644 index 000000000..603269686 --- /dev/null +++ b/Model/CategoryVersion/CategoryVersionSearchResults.php @@ -0,0 +1,13 @@ +indexerRegistry = $indexerRegistry; $this->indexer = $indexerRegistry->get('algolia_categories'); $this->configHelper = $configHelper; $this->resource = $resource; + $this->logger = $logger; + $this->storeManager = $storeManager; + $this->categoryVersionLogger = $categoryVersionLogger; } /** @@ -48,28 +62,41 @@ public function __construct( * @param CategoryModel $category * * @return CategoryResourceModel + * @throws NoSuchEntityException */ public function afterSave( CategoryResourceModel $categoryResource, CategoryResourceModel $result, - CategoryModel $category - ) { + CategoryModel $category + ) + { if (!$this->configHelper->getApplicationID() || !$this->configHelper->getAPIKey() || !$this->configHelper->getSearchOnlyAPIKey()) { return $result; } - $categoryResource->addCommitCallback(function () use ($category) { + + $storeId = $this->storeManager->getStore()->getId(); + + $categoryResource->addCommitCallback(function () use ($category, $storeId) { $collectionIds = []; // To reduce the indexing operation for products, only update if these values have changed - if ($category->getOrigData('name') !== $category->getData('name') - || $category->getOrigData('include_in_menu') !== $category->getData('include_in_menu') - || $category->getOrigData('is_active') !== $category->getData('is_active') - || $category->getOrigData('path') !== $category->getData('path')) { + if ($this->isDataChanged($category, [ + CategoryInterface::KEY_NAME, + CategoryInterface::KEY_PATH, + CategoryInterface::KEY_INCLUDE_IN_MENU, + CategoryInterface::KEY_IS_ACTIVE + ])) { /** @var ProductCollection $productCollection */ $productCollection = $category->getProductCollection(); $collectionIds = (array) $productCollection->getColumnValues('entity_id'); + if ($this->isDataChanged($category, [CategoryInterface::KEY_PATH])) { + $this->categoryVersionLogger->logCategoryMove($category); + } else { + $this->categoryVersionLogger->logCategoryChange($category, $storeId); + } } + $changedProductIds = ($category->getChangedProductIds() !== null ? (array) $category->getChangedProductIds() : []); if (!$this->indexer->isScheduled()) { @@ -87,29 +114,18 @@ public function afterSave( } /** - * @param CategoryResourceModel $categoryResource - * @param CategoryResourceModel $result - * @param CategoryModel $category - * - * @return CategoryResourceModel + * @param AbstractModel $model + * @param array $fields + * @return bool */ - public function afterDelete( - CategoryResourceModel $categoryResource, - CategoryResourceModel $result, - CategoryModel $category - ) { - $categoryResource->addCommitCallback(function () use ($category) { - // mview should be able to handle the changes for catalog_category_product relationship - if (!$this->indexer->isScheduled()) { - /* we are using products position because getProductCollection() doesn't use correct store */ - $productCollection = $category->getProductsPosition(); - CategoryIndexer::$affectedProductIds = array_keys($productCollection); - - $this->indexer->reindexRow($category->getId()); + protected function isDataChanged(AbstractModel $model, array $fields): bool + { + foreach ($fields as $field) { + if ($model->getOrigData($field) !== $model->getData($field)) { + return true; } - }); - - return $result; + } + return false; } /** @@ -134,4 +150,31 @@ private function updateCategoryProducts(array $productIds) } } } + + /** + * @param CategoryResourceModel $categoryResource + * @param CategoryResourceModel $result + * @param CategoryModel $category + * + * @return CategoryResourceModel + */ + public function afterDelete( + CategoryResourceModel $categoryResource, + CategoryResourceModel $result, + CategoryModel $category + ) + { + $categoryResource->addCommitCallback(function () use ($category) { + // mview should be able to handle the changes for catalog_category_product relationship + if (!$this->indexer->isScheduled()) { + /* we are using products position because getProductCollection() doesn't use correct store */ + $productCollection = $category->getProductsPosition(); + CategoryIndexer::$affectedProductIds = array_keys($productCollection); + + $this->indexer->reindexRow($category->getId()); + } + }); + + return $result; + } } diff --git a/Model/ResourceModel/CategoryVersion.php b/Model/ResourceModel/CategoryVersion.php new file mode 100644 index 000000000..546525593 --- /dev/null +++ b/Model/ResourceModel/CategoryVersion.php @@ -0,0 +1,14 @@ +_init(self::TABLE_NAME, self::ID); + } +} diff --git a/Model/ResourceModel/CategoryVersion/Collection.php b/Model/ResourceModel/CategoryVersion/Collection.php new file mode 100644 index 000000000..4481f32f2 --- /dev/null +++ b/Model/ResourceModel/CategoryVersion/Collection.php @@ -0,0 +1,18 @@ +_init(\Algolia\AlgoliaSearch\Model\CategoryVersion::class, \Algolia\AlgoliaSearch\Model\ResourceModel\CategoryVersion::class); + } + +} diff --git a/Observer/RegisterCurrentCategoryObserver.php b/Observer/RegisterCurrentCategoryObserver.php new file mode 100644 index 000000000..a08c90b8e --- /dev/null +++ b/Observer/RegisterCurrentCategoryObserver.php @@ -0,0 +1,28 @@ +currentCategory = $currentCategory; + } + + /** + * @inheritDoc + */ + public function execute(Observer $observer) + { + /** @var CategoryInterface */ + $category = $observer->getEvent()->getData('category'); + $this->currentCategory->set($category); + } +} diff --git a/Plugin/CategoryMovePlugin.php b/Plugin/CategoryMovePlugin.php new file mode 100644 index 000000000..9ffe936e3 --- /dev/null +++ b/Plugin/CategoryMovePlugin.php @@ -0,0 +1,42 @@ +storeManager = $storeManager; + $this->categoryVersionLogger = $categoryVersionLogger; + } + + /** + * @param Category $subject + * @param Category $result + * @param int $parentId + * @param int|null $afterCategoryId + * @return Category + * @throws AlreadyExistsException + * @throws NoSuchEntityException + */ + public function afterMove(Category $subject, Category $result, int $parentId, null|int $afterCategoryId) + { + $this->categoryVersionLogger->logCategoryMove($result); + return $result; + } +} diff --git a/Plugin/CategoryVersionPlugin.php b/Plugin/CategoryVersionPlugin.php new file mode 100644 index 000000000..eb8de38fb --- /dev/null +++ b/Plugin/CategoryVersionPlugin.php @@ -0,0 +1,62 @@ +versionRepository = $versionRepository; + $this->extensionFactory = $extensionFactory; + $this->versionAttributeFactory = $versionAttributeFactory; + $this->config = $config; + } + + /** + * @param CategoryRepositoryInterface $categoryRepository + * @param CategoryInterface $category + * @param int $categoryId + * @param int|null $storeId + * @return CategoryInterface + * @throws \Magento\Framework\Exception\NoSuchEntityException + */ + public function afterGet(CategoryRepositoryInterface $categoryRepository, CategoryInterface $category, int $categoryId, int $storeId = null): CategoryInterface { + if (!$this->config->isCategoryVersionTrackingEnabled()) return $category; + \Magento\Framework\App\ObjectManager::getInstance()->get(\Psr\Log\LoggerInterface::class)->debug("Category called for store id $storeId and category id $categoryId"); + + $extensionAttributes = $category->getExtensionAttributes() ?? $this->extensionFactory->create(); + $versionAttribute = $extensionAttributes->getAlgoliaCategoryVersions() ?? $this->versionAttributeFactory->create()->load($categoryId, $storeId); + $extensionAttributes->setAlgoliaCategoryVersions($versionAttribute); + $category->setExtensionAttributes($extensionAttributes); + return $category; + } + + // Unsupported in admin by core + public function afterSave(CategoryRepositoryInterface $categoryRepository, CategoryInterface $result, CategoryInterface $category): CategoryInterface { + \Magento\Framework\App\ObjectManager::getInstance()->get(\Psr\Log\LoggerInterface::class)->debug('Category save called'); + return $result; + } +} diff --git a/Registry/CurrentCategory.php b/Registry/CurrentCategory.php new file mode 100644 index 000000000..e7f19c77f --- /dev/null +++ b/Registry/CurrentCategory.php @@ -0,0 +1,31 @@ +categoryRepository = $categoryRepository; + $this->categoryFactory = $categoryFactory; + } + + public function set(CategoryInterface $category): void { + $this->category = $category; + } + + public function get(): CategoryInterface { + return $this->category ?? $this->categoryFactory->create(); + } +} diff --git a/etc/adminhtml/di.xml b/etc/adminhtml/di.xml index cabd4ec50..4171e5d4e 100755 --- a/etc/adminhtml/di.xml +++ b/etc/adminhtml/di.xml @@ -2,8 +2,10 @@ + + diff --git a/etc/adminhtml/system.xml b/etc/adminhtml/system.xml index 6d8720563..8f696c0b3 100755 --- a/etc/adminhtml/system.xml +++ b/etc/adminhtml/system.xml @@ -534,6 +534,22 @@ 1 + + + Magento\Config\Model\Config\Source\Yesno + +
+ Enable this setting to allow the Algolia extension to track the changes you make in Magento so that product records + can still be retrieved based on previous category identifiers. + ]]> +
+ + 1 + +
diff --git a/etc/config.xml b/etc/config.xml index bb3a1ffd3..a2d8446b6 100644 --- a/etc/config.xml +++ b/etc/config.xml @@ -37,6 +37,7 @@ 0 categoryPageId + 0 diff --git a/etc/db_schema.xml b/etc/db_schema.xml index b1a64de7d..f140d0503 100644 --- a/etc/db_schema.xml +++ b/etc/db_schema.xml @@ -92,6 +92,32 @@
+ + + + + + + + + + + + + + + + + + + + + +
+ diff --git a/etc/db_schema_whitelist.json b/etc/db_schema_whitelist.json index 56431ba85..4c89cac9f 100644 --- a/etc/db_schema_whitelist.json +++ b/etc/db_schema_whitelist.json @@ -98,5 +98,27 @@ "column": { "algoliasearch_query_param": true } + }, + "algoliasearch_category_version": { + "column": { + "version_id": true, + "entity_id": true, + "store_id": true, + "old_value": true, + "new_value": true, + "created_at": true, + "updated_at": true, + "indexed_at": true, + "resolved_at": true + }, + "index": { + "ALGOLIASEARCH_CATEGORY_VERSION_CREATED_AT": true, + "ALGOLIASEARCH_CATEGORY_VERSION_UPDATED_AT": true + }, + "constraint": { + "PRIMARY": true, + "ALGOLIASRCH_CTGR_VERSION_ENTT_ID_CAT_CTGR_ENTT_ENTT_ID": true, + "ALGOLIASEARCH_CATEGORY_VERSION_STORE_ID_STORE_STORE_ID": true + } } } \ No newline at end of file diff --git a/etc/di.xml b/etc/di.xml index 13ef1b1b3..67e134579 100755 --- a/etc/di.xml +++ b/etc/di.xml @@ -33,6 +33,12 @@ + + + + + + @@ -140,4 +146,4 @@ Algolia\AlgoliaSearch\Model\ResourceModel\QueueArchive\Collection - \ No newline at end of file + diff --git a/etc/extension_attributes.xml b/etc/extension_attributes.xml new file mode 100644 index 000000000..8296e7d95 --- /dev/null +++ b/etc/extension_attributes.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/etc/frontend/di.xml b/etc/frontend/di.xml index d14ad0439..26a3b65db 100644 --- a/etc/frontend/di.xml +++ b/etc/frontend/di.xml @@ -20,4 +20,8 @@ + + + + diff --git a/etc/frontend/events.xml b/etc/frontend/events.xml index df4c060f1..90147b29f 100755 --- a/etc/frontend/events.xml +++ b/etc/frontend/events.xml @@ -23,4 +23,9 @@ + + + + + diff --git a/etc/module.xml b/etc/module.xml index 7e8028918..4cf5b46e7 100755 --- a/etc/module.xml +++ b/etc/module.xml @@ -1,6 +1,6 @@ - + diff --git a/view/frontend/web/instantsearch.js b/view/frontend/web/instantsearch.js index 8eaeb72a2..3545d87f8 100644 --- a/view/frontend/web/instantsearch.js +++ b/view/frontend/web/instantsearch.js @@ -111,8 +111,13 @@ define( } } + + const getCategoryPageIdFilter = (param, paths) => { + return paths.map(path => `${param}:'${path}'`).join(' OR '); + }; + if (algoliaConfig.instant.isVisualMerchEnabled && algoliaConfig.isCategoryPage ) { - searchParameters.filters = `${algoliaConfig.instant.categoryPageIdAttribute}:'${algoliaConfig.request.path}'`; + searchParameters.filters = getCategoryPageIdFilter(algoliaConfig.instant.categoryPageIdAttribute, algoliaConfig.request.alternatePaths); } instantsearchOptions = algolia.triggerHooks('beforeInstantsearchInit', instantsearchOptions, algoliaBundle); @@ -446,40 +451,25 @@ define( * Custom widgets can be added to this object like [attribute]: function(facet, templates) * Function must return an array [: string, : object] **/ - var customAttributeFacet = { + const customAttributeFacet = { categories: function (facet, templates) { - var hierarchical_levels = []; - for (var l = 0; l < 10; l++) { + const hierarchical_levels = []; + for (let l = 0; l < 10; l++) { 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 = { + const hierarchicalMenuParams = { container : facet.wrapper.appendChild(createISWidgetContainer(facet.attribute)), attributes : hierarchical_levels, separator : algoliaConfig.instant.categorySeparator, templates : templates, showParentLevel : true, limit : algoliaConfig.maxValuesPerFacet, + rootPath : algoliaConfig.request.hasCategoryVersions ? null : algoliaConfig.request.path, sortBy : ['name:asc'], transformItems(items) { return (algoliaConfig.isCategoryPage) - ? findRoot(items).map( + ? items.map( item => { return { ...item, @@ -726,4 +716,4 @@ define( 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 93c0a13e6..3ad560a6e 100755 --- a/view/frontend/web/internals/common.js +++ b/view/frontend/web/internals/common.js @@ -405,10 +405,10 @@ define(['jquery', '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) { + if (currentFacet.attribute == 'categories' && algoliaConfig.isCategoryPage && !algoliaConfig.request.hasCategoryVersions) { uiStateProductIndex['hierarchicalMenu']['categories.level0'] = [algoliaConfig.request.path]; } // Handle sliders