childrenCategoriesProvider = $childrenCategoriesProvider; $this->categoryUrlRewriteGenerator = $categoryUrlRewriteGenerator; $this->productUrlRewriteGenerator = $productUrlRewriteGenerator; $this->urlPersist = $urlPersist; $this->productCollectionFactory = $productCollectionFactory; $this->categoryBasedProductRewriteGenerator = $categoryBasedProductRewriteGenerator; $objectManager = ObjectManager::getInstance(); $mergeDataProviderFactory = $mergeDataProviderFactory ?: $objectManager->get(MergeDataProviderFactory::class); $this->mergeDataProviderPrototype = $mergeDataProviderFactory->create(); $this->serializer = $serializer ?: $objectManager->get(Json::class); $this->productScopeRewriteGenerator = $productScopeRewriteGenerator ?: $objectManager->get(ProductScopeRewriteGenerator::class); } /** * Generates URL rewrites for products assigned to category. * * @param Category $category * @return array */ public function generateProductUrlRewrites(Category $category): array { $mergeDataProvider = clone $this->mergeDataProviderPrototype; $this->isSkippedProduct[$category->getEntityId()] = []; $saveRewriteHistory = (bool)$category->getData('save_rewrites_history'); $storeId = (int)$category->getStoreId(); if ($category->getChangedProductIds()) { $this->generateChangedProductUrls($mergeDataProvider, $category, $storeId, $saveRewriteHistory); } else { $mergeDataProvider->merge( $this->getCategoryProductsUrlRewrites( $category, $storeId, $saveRewriteHistory, $category->getEntityId() ) ); } foreach ($this->childrenCategoriesProvider->getChildren($category, true) as $childCategory) { $mergeDataProvider->merge( $this->getCategoryProductsUrlRewrites( $childCategory, $storeId, $saveRewriteHistory, $category->getEntityId() ) ); } return $mergeDataProvider->getData(); } /** * Update product url rewrites for changed product. * * @param Category $category * @return array */ public function updateProductUrlRewritesForChangedProduct(Category $category): array { $mergeDataProvider = clone $this->mergeDataProviderPrototype; $this->isSkippedProduct[$category->getEntityId()] = []; $saveRewriteHistory = (bool)$category->getData('save_rewrites_history'); $storeIds = $this->getCategoryStoreIds($category); if ($category->getChangedProductIds()) { foreach ($storeIds as $storeId) { $this->generateChangedProductUrls($mergeDataProvider, $category, (int)$storeId, $saveRewriteHistory); } } return $mergeDataProvider->getData(); } /** * Delete category rewrites for children. * * @param Category $category * @return void */ public function deleteCategoryRewritesForChildren(Category $category) { $categoryIds = $this->childrenCategoriesProvider->getChildrenIds($category, true); $categoryIds[] = $category->getId(); foreach ($categoryIds as $categoryId) { $this->urlPersist->deleteByData( [ UrlRewrite::ENTITY_ID => $categoryId, UrlRewrite::ENTITY_TYPE => CategoryUrlRewriteGenerator::ENTITY_TYPE, ] ); $this->urlPersist->deleteByData( [ UrlRewrite::METADATA => $this->serializer->serialize(['category_id' => $categoryId]), UrlRewrite::ENTITY_TYPE => ProductUrlRewriteGenerator::ENTITY_TYPE, ] ); } } /** * Get category products url rewrites. * * @param Category $category * @param int $storeId * @param bool $saveRewriteHistory * @param int|null $rootCategoryId * @return array */ private function getCategoryProductsUrlRewrites( Category $category, $storeId, $saveRewriteHistory, $rootCategoryId = null ) { $mergeDataProvider = clone $this->mergeDataProviderPrototype; /** @var Collection $productCollection */ $productCollection = $this->productCollectionFactory->create(); $productCollection->addCategoriesFilter(['eq' => [$category->getEntityId()]]) ->setStoreId($storeId) ->addAttributeToSelect('name') ->addAttributeToSelect('visibility') ->addAttributeToSelect('url_key') ->addAttributeToSelect('url_path'); foreach ($productCollection as $product) { if (isset($this->isSkippedProduct[$category->getEntityId()]) && in_array($product->getId(), $this->isSkippedProduct[$category->getEntityId()]) ) { continue; } $this->isSkippedProduct[$category->getEntityId()][] = $product->getId(); $product->setStoreId($storeId); $product->setData('save_rewrites_history', $saveRewriteHistory); $mergeDataProvider->merge( $this->categoryBasedProductRewriteGenerator->generate($product, $rootCategoryId) ); } return $mergeDataProvider->getData(); } /** * Generates product URL rewrites. * * @param MergeDataProvider $mergeDataProvider * @param Category $category * @param int $storeId * @param bool $saveRewriteHistory * @return void */ private function generateChangedProductUrls( MergeDataProvider $mergeDataProvider, Category $category, int $storeId, bool $saveRewriteHistory ) { $this->isSkippedProduct[$category->getEntityId()] = $category->getAffectedProductIds(); $categoryStoreIds = [$storeId]; // If category is changed in the Global scope when need to regenerate product URL rewrites for all other scopes. if ($this->productScopeRewriteGenerator->isGlobalScope($storeId)) { $categoryStoreIds = $this->getCategoryStoreIds($category); } foreach ($categoryStoreIds as $categoryStoreId) { /* @var Collection $collection */ $collection = $this->productCollectionFactory->create() ->setStoreId($categoryStoreId) ->addIdFilter($category->getAffectedProductIds()) ->addAttributeToSelect('visibility') ->addAttributeToSelect('name') ->addAttributeToSelect('url_key') ->addAttributeToSelect('url_path'); $collection->setPageSize(1000); $pageCount = $collection->getLastPageNumber(); $currentPage = 1; while ($currentPage <= $pageCount) { $collection->setCurPage($currentPage); foreach ($collection as $product) { $product->setData('save_rewrites_history', $saveRewriteHistory); $product->setStoreId($categoryStoreId); $mergeDataProvider->merge( $this->productUrlRewriteGenerator->generate($product, $category->getEntityId()) ); } $collection->clear(); $currentPage++; } } } /** * Gets category store IDs without Global Store. * * @param Category $category * @return array */ private function getCategoryStoreIds(Category $category): array { $ids = $category->getStoreIds(); return array_filter($ids); } }