diff --git a/Model/Indexer/Category/CategoryIndexer.php b/Model/Indexer/Category/CategoryIndexer.php new file mode 100644 index 0000000..1634db7 --- /dev/null +++ b/Model/Indexer/Category/CategoryIndexer.php @@ -0,0 +1,153 @@ +dimensionProvider = $dimensionProvider; + $this->getUrlsForCategories = $getUrlsForCategories; + $this->prerenderClient = $prerenderClient; + $this->deploymentConfig = $deploymentConfig; + $this->batchSize = $batchSize; + $this->prerenderConfigHelper = $prerenderConfigHelper; + } + + /** + * Execute full indexation + * + * @return void + */ + public function executeFull(): void + { + $this->executeList([]); + } + + /** + * Execute partial indexation by ID list + * + * @param int[] $ids + * @return void + */ + public function executeList(array $ids): void + { + foreach ($this->dimensionProvider->getIterator() as $dimension) { + try { + $this->executeByDimensions($dimension, new \ArrayIterator($ids)); + } catch (FileSystemException|RuntimeException $e) { + continue; + } + } + } + + /** + * Execute partial indexation by ID + * + * @param int $id + * @return void + * @throws LocalizedException + */ + public function executeRow($id): void + { + if (!$id) { + throw new LocalizedException( + __('Cannot recache url for an undefined product.') + ); + } + $this->executeList([$id]); + } + + /** + * Execute materialization on ids entities + * + * @param int[] $ids + * @return void + */ + public function execute($ids): void + { + $this->executeList($ids); + } + + /** + * Execute indexing per dimension (store) + * + * @param arry $dimensions + * @param \Traversable $entityIds + * @throws FileSystemException + * @throws RuntimeException + */ + public function executeByDimensions(array $dimensions, \Traversable $entityIds): void + { + if (count($dimensions) > 1 || !isset($dimensions[StoreDimensionProvider::DIMENSION_NAME])) { + throw new \InvalidArgumentException('Indexer "' . self::INDEXER_ID . '" supports only Store dimension'); + } + $storeId = (int)$dimensions[StoreDimensionProvider::DIMENSION_NAME]->getValue(); + + if (!$this->prerenderConfigHelper->isRecacheEnabled($storeId)) { + return; + } + + $entityIds = iterator_to_array($entityIds); + // get urls for the products + $urls = $this->getUrlsForCategories->execute($entityIds, $storeId); + + $this->batchSize = $this->deploymentConfig->get( + self::DEPLOYMENT_CONFIG_INDEXER_BATCHES . self::INDEXER_ID . '/partial_reindex' + ) ?? $this->batchSize; + + $urlBatches = array_chunk($urls, $this->batchSize); + foreach ($urlBatches as $batchUrls) { + $this->prerenderClient->recacheUrls($batchUrls, $storeId); + } + } +} diff --git a/Model/Indexer/Category/ProductIndexer.php b/Model/Indexer/Category/ProductIndexer.php new file mode 100644 index 0000000..696dd99 --- /dev/null +++ b/Model/Indexer/Category/ProductIndexer.php @@ -0,0 +1,162 @@ +dimensionProvider = $dimensionProvider; + $this->productCategoriesDataProvider = $productCategoriesDataProvider; + $this->getUrlsForCategories = $getUrlsForCategories; + $this->prerenderClient = $prerenderClient; + $this->deploymentConfig = $deploymentConfig; + $this->batchSize = $batchSize; + $this->prerenderConfigHelper = $prerenderConfigHelper; + } + + /** + * Execute full indexation + * + * @return void + */ + public function executeFull(): void + { + $this->executeList([]); + } + + /** + * Execute partial indexation by ID list + * + * @param int[] $ids + * @return void + */ + public function executeList(array $ids): void + { + foreach ($this->dimensionProvider->getIterator() as $dimension) { + try { + $this->executeByDimensions($dimension, new \ArrayIterator($ids)); + } catch (FileSystemException|RuntimeException $e) { + continue; + } + } + } + + /** + * Execute partial indexation by ID + * + * @param int $id + * @return void + * @throws LocalizedException + */ + public function executeRow($id): void + { + if (!$id) { + throw new LocalizedException( + __('Cannot recache url for an undefined product.') + ); + } + $this->executeList([$id]); + } + + /** + * Execute materialization on ids entities + * + * @param int[] $ids + * @return void + */ + public function execute($ids): void + { + $this->executeList($ids); + } + + /** + * Execute indexing per dimension (store) + * + * @param arry $dimensions + * @param \Traversable $entityIds + * @throws FileSystemException + * @throws RuntimeException + */ + public function executeByDimensions(array $dimensions, \Traversable $entityIds): void + { + if (count($dimensions) > 1 || !isset($dimensions[StoreDimensionProvider::DIMENSION_NAME])) { + throw new \InvalidArgumentException('Indexer "' . self::INDEXER_ID . '" supports only Store dimension'); + } + $storeId = (int)$dimensions[StoreDimensionProvider::DIMENSION_NAME]->getValue(); + + if (!$this->prerenderConfigHelper->isRecacheEnabled($storeId)) { + return; + } + + $entityIds = iterator_to_array($entityIds); + // get list of category ids for the products + $categoryIds = $this->productCategoriesDataProvider->getCategoryIdsForProducts($entityIds, $storeId); + + // get urls for the products + $urls = $this->getUrlsForCategories->execute($categoryIds, $storeId); + + $this->batchSize = $this->deploymentConfig->get( + self::DEPLOYMENT_CONFIG_INDEXER_BATCHES . self::INDEXER_ID . '/partial_reindex' + ) ?? $this->batchSize; + + $urlBatches = array_chunk($urls, $this->batchSize); + foreach ($urlBatches as $batchUrls) { + $this->prerenderClient->recacheUrls($batchUrls, $storeId); + } + } +} diff --git a/Model/Indexer/DataProvider/ProductCategories.php b/Model/Indexer/DataProvider/ProductCategories.php new file mode 100644 index 0000000..8b3acb1 --- /dev/null +++ b/Model/Indexer/DataProvider/ProductCategories.php @@ -0,0 +1,62 @@ +productCollectionFactory = $productCollectionFactory; + $this->categoryCollectionFactory = $categoryCollectionFactory; + } + + /** + * Get complete list of categories containing one or more of the given products + * + * @param array $productIds + * @param int $storeId + * @return array + */ + public function getCategoryIdsForProducts(array $productIds, int $storeId): array + { + // if array of product ids is empty, just load all categories + if (empty($productIds)) { + $categoryCollection = $this->categoryCollectionFactory->create(); + $categoryCollection->setStoreId($storeId); + return $categoryCollection->getAllIds(); + } + + $productCollection = $this->productCollectionFactory->create(); + $productCollection->addIdFilter($productIds); + $productCollection->setStoreId($storeId); + // add category information + $productCollection->addCategoryIds(); + + $categoryIds = []; + /** @var Product $product */ + foreach ($productCollection->getItems() as $product) { + $categoryIds[] = $product->getCategoryIds(); + } + return array_unique(array_merge(...$categoryIds)); + } +} diff --git a/Model/Indexer/Product/ProductIndexer.php b/Model/Indexer/Product/ProductIndexer.php index 4134383..83f3cc7 100644 --- a/Model/Indexer/Product/ProductIndexer.php +++ b/Model/Indexer/Product/ProductIndexer.php @@ -9,7 +9,7 @@ use Aligent\PrerenderIo\Api\PrerenderClientInterface; use Aligent\PrerenderIo\Helper\Config; -use Aligent\PrerenderIo\Model\Product\GetUrlsForProducts; +use Aligent\PrerenderIo\Model\Url\GetUrlsForProducts; use Magento\Framework\App\DeploymentConfig; use Magento\Framework\Exception\FileSystemException; use Magento\Framework\Exception\LocalizedException; diff --git a/Model/Url/GetUrlsForCategories.php b/Model/Url/GetUrlsForCategories.php new file mode 100644 index 0000000..50d9bfb --- /dev/null +++ b/Model/Url/GetUrlsForCategories.php @@ -0,0 +1,83 @@ +categoryCollectionFactory = $categoryCollectionFactory; + $this->storeManager = $storeManager; + $this->emulation = $emulation; + } + + /** + * Generate category URLs based on URL_REWRITE entries + * + * @param array $categoryIds + * @param int $storeId + * @return array + */ + public function execute(array $categoryIds, int $storeId): array + { + $categoryCollection = $this->categoryCollectionFactory->create(); + // if array of category ids is empty, just load all categories + if (!empty($categoryIds)) { + $categoryCollection->addIdFilter($categoryIds); + } + $categoryCollection->setStoreId($storeId); + $categoryCollection->addUrlRewriteToResult(); + + try { + /** @var Store $store */ + $store = $this->storeManager->getStore($storeId); + } catch (NoSuchEntityException $e) { + return []; + } + + $this->emulation->startEnvironmentEmulation($storeId); + $urls = []; + /** @var Category $category */ + foreach ($categoryCollection as $category) { + $urlPath = $category->getData('request_path'); + if (empty($urlPath)) { + continue; + } + try { + // remove trailing slashes from urls + $urls[] = rtrim($store->getUrl($urlPath), '/'); + } catch (NoSuchEntityException $e) { + continue; + } + } + $this->emulation->stopEnvironmentEmulation(); + return $urls; + } +} diff --git a/Model/Product/GetUrlsForProducts.php b/Model/Url/GetUrlsForProducts.php similarity index 98% rename from Model/Product/GetUrlsForProducts.php rename to Model/Url/GetUrlsForProducts.php index f89d498..bfec571 100644 --- a/Model/Product/GetUrlsForProducts.php +++ b/Model/Url/GetUrlsForProducts.php @@ -4,7 +4,7 @@ */ declare(strict_types=1); -namespace Aligent\PrerenderIo\Model\Product; +namespace Aligent\PrerenderIo\Model\Url; use Magento\Catalog\Model\Product; use Magento\Catalog\Model\ResourceModel\Product\CollectionFactory; diff --git a/README.md b/README.md index 67b9b6a..bb31471 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,14 @@ # magento2-prerender-io -Provides integration between Magento 2 and [Prerender.io](https://prerender.io), giving the ability for product pages to be automatically recached when a product is updated. +Provides integration between Magento 2 and [Prerender.io](https://prerender.io), giving the ability for pages to be automatically recached when required. ## Overview -This module provides a new indexer, `prerender_io_product`, which will send URL recache requests to Prerender.io (in batches of up to 1000) when changes are made to products. -This will ensure that the cache product pages are kept up-to-date at all times. +This module provides new indexers: + +- `prerender_io_product`, which will send URL recache requests for products to Prerender.io (in batches of up to 1000) when changes are made to products. +- `prerender_io_category`, which will send URL recache requests for categories to Prerender.io (in batches of up to 1000) when changes are made to categories. +- `prerender_io_category_product`, which will send URL recache requests for categories to Prerender.io (in batches of up to 1000) when changes are made to products. + +These will ensure that the cached pages are kept up-to-date at all times. ## Installation To install via composer, simply run: @@ -12,12 +17,12 @@ To install via composer, simply run: composer require aligent/magento2-prerender-io ``` -Then, ensure the module is installed and the index is set to `Schedule`: +Then, ensure the module is installed and the indexers are set to `Schedule`: ```bash bin/magento module:enable Aligent_PrerenderIo bin/magento setup:upgrade -bin/magento indexer:set-mode schedule prerender_io_product +bin/magento indexer:set-mode schedule prerender_io_product prerender_io_category prerender_io_category_product ``` ## Configuration diff --git a/etc/di.xml b/etc/di.xml index d81f5fe..165a5a2 100644 --- a/etc/di.xml +++ b/etc/di.xml @@ -14,4 +14,18 @@ + + + + Magento\Store\Model\StoreDimensionProvider + + + + + + + Magento\Store\Model\StoreDimensionProvider + + + diff --git a/etc/indexer.xml b/etc/indexer.xml index d95a3de..5c6c11a 100644 --- a/etc/indexer.xml +++ b/etc/indexer.xml @@ -8,4 +8,12 @@ Prerender.io Product Recaching Recaches product urls when products are updated + + Prerender.io Category Recaching + Recaches category urls when categories are updated + + + Prerender.io Category Product Recaching + Recaches category urls when products are updated + diff --git a/etc/mview.xml b/etc/mview.xml index 4b36ad0..7e838c4 100644 --- a/etc/mview.xml +++ b/etc/mview.xml @@ -20,4 +20,31 @@ + + +
+
+
+
+
+
+ + + + +
+
+
+
+
+
+
+
+
+
+
+
+
+ +