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