Skip to content

Commit 1cda6ea

Browse files
committed
MAGETWO-60154: [Backport] Saving category deletes UrlRewrites for subcategories and all products in them - for 2.1.x
1 parent a36c4de commit 1cda6ea

11 files changed

+476
-86
lines changed
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
<?php
2+
/**
3+
* Copyright © 2013-2017 Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
namespace Magento\CatalogUrlRewrite\Model;
7+
8+
use Magento\Catalog\Model\Product;
9+
use Magento\Catalog\Model\Product\Visibility;
10+
11+
/**
12+
* Class ProductUrlRewriteGenerator.
13+
* @package Magento\CatalogUrlRewrite\Model
14+
*/
15+
class CategoryProductUrlPathGenerator
16+
{
17+
/**
18+
* Generates url rewrites for different scopes.
19+
*
20+
* @var ProductScopeRewriteGenerator
21+
*/
22+
private $productScopeRewriteGenerator;
23+
24+
/**
25+
* @param ProductScopeRewriteGenerator $productScopeRewriteGenerator
26+
*/
27+
public function __construct(
28+
ProductScopeRewriteGenerator $productScopeRewriteGenerator
29+
) {
30+
$this->productScopeRewriteGenerator = $productScopeRewriteGenerator;
31+
}
32+
33+
/**
34+
* Generate product url rewrites based on all product categories.
35+
*
36+
* @param Product $product
37+
* @param int|null $rootCategoryId
38+
* @return \Magento\UrlRewrite\Service\V1\Data\UrlRewrite[]
39+
*/
40+
public function generate(Product $product, $rootCategoryId = null)
41+
{
42+
if ($product->getVisibility() == Visibility::VISIBILITY_NOT_VISIBLE) {
43+
return [];
44+
}
45+
46+
$storeId = $product->getStoreId();
47+
48+
$productCategories = $product->getCategoryCollection()
49+
->addAttributeToSelect('url_key')
50+
->addAttributeToSelect('url_path');
51+
52+
$urls = $this->productScopeRewriteGenerator->isGlobalScope($storeId)
53+
? $this->productScopeRewriteGenerator->generateForGlobalScope(
54+
$productCategories,
55+
$product,
56+
$rootCategoryId
57+
)
58+
: $this->productScopeRewriteGenerator->generateForSpecificStoreView(
59+
$storeId,
60+
$productCategories,
61+
$product,
62+
$rootCategoryId
63+
);
64+
65+
return $urls;
66+
}
67+
}

app/code/Magento/CatalogUrlRewrite/Model/Product/AnchorUrlRewriteGenerator.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ public function __construct(
4141
}
4242

4343
/**
44-
* Generate list based on categories
44+
* Generate product rewrites for anchor categories.
4545
*
4646
* @param int $storeId
4747
* @param Product $product

app/code/Magento/CatalogUrlRewrite/Model/Product/CanonicalUrlRewriteGenerator.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ public function __construct(ProductUrlPathGenerator $productUrlPathGenerator, Ur
3030
}
3131

3232
/**
33-
* Generate list based on store view
33+
* Generate product rewrites without categories.
3434
*
3535
* @param int $storeId
3636
* @param Product $product

app/code/Magento/CatalogUrlRewrite/Model/Product/CategoriesUrlRewriteGenerator.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ public function __construct(ProductUrlPathGenerator $productUrlPathGenerator, Ur
3131
}
3232

3333
/**
34-
* Generate list based on categories
34+
* Generate product rewrites with categories.
3535
*
3636
* @param int $storeId
3737
* @param Product $product

app/code/Magento/CatalogUrlRewrite/Model/Product/CurrentUrlRewritesRegenerator.php

Lines changed: 57 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
namespace Magento\CatalogUrlRewrite\Model\Product;
77

88
use Magento\Catalog\Model\Category;
9+
use Magento\Catalog\Model\CategoryRepository;
910
use Magento\Catalog\Model\Product;
1011
use Magento\UrlRewrite\Service\V1\Data\UrlRewrite;
1112
use Magento\UrlRewrite\Model\OptionProvider;
@@ -67,19 +68,26 @@ class CurrentUrlRewritesRegenerator
6768
*/
6869
private $mergeDataProviderPrototype;
6970

71+
/**
72+
* @var CategoryRepository
73+
*/
74+
private $categoryRepository;
75+
7076
/**
7177
* @param UrlFinderInterface $urlFinder
7278
* @param ProductUrlPathGenerator $productUrlPathGenerator
7379
* @param UrlRewriteFactory $urlRewriteFactory
7480
* @param UrlRewriteFinder|null $urlRewriteFinder
7581
* @param \Magento\UrlRewrite\Model\MergeDataProviderFactory|null $mergeDataProviderFactory
82+
* @param CategoryRepository|null $categoryRepository
7683
*/
7784
public function __construct(
7885
UrlFinderInterface $urlFinder,
7986
ProductUrlPathGenerator $productUrlPathGenerator,
8087
UrlRewriteFactory $urlRewriteFactory,
8188
UrlRewriteFinder $urlRewriteFinder = null,
82-
MergeDataProviderFactory $mergeDataProviderFactory = null
89+
MergeDataProviderFactory $mergeDataProviderFactory = null,
90+
CategoryRepository $categoryRepository = null
8391
) {
8492
$this->urlFinder = $urlFinder;
8593
$this->productUrlPathGenerator = $productUrlPathGenerator;
@@ -89,11 +97,12 @@ public function __construct(
8997
if (!isset($mergeDataProviderFactory)) {
9098
$mergeDataProviderFactory = ObjectManager::getInstance()->get(MergeDataProviderFactory::class);
9199
}
100+
$this->categoryRepository = $categoryRepository ?: ObjectManager::getInstance()->get(CategoryRepository::class);
92101
$this->mergeDataProviderPrototype = $mergeDataProviderFactory->create();
93102
}
94103

95104
/**
96-
* Generate list based on current rewrites
105+
* Generate product rewrites based on current rewrites without anchor categories.
97106
*
98107
* @param int $storeId
99108
* @param Product $product
@@ -126,6 +135,52 @@ public function generate($storeId, Product $product, ObjectRegistry $productCate
126135
return $mergeDataProvider->getData();
127136
}
128137

138+
/**
139+
* Generate product rewrites for anchor categories based on current rewrites.
140+
*
141+
* @param int $storeId
142+
* @param Product $product
143+
* @param ObjectRegistry $productCategories
144+
* @param int|null $rootCategoryId
145+
* @return UrlRewrite[]
146+
*/
147+
public function generateAnchor(
148+
$storeId,
149+
Product $product,
150+
ObjectRegistry $productCategories,
151+
$rootCategoryId = null
152+
) {
153+
$anchorCategoryIds = [];
154+
$mergeDataProvider = clone $this->mergeDataProviderPrototype;
155+
156+
$currentUrlRewrites = $this->urlRewriteFinder->findAllByData(
157+
$product->getEntityId(),
158+
$storeId,
159+
ProductUrlRewriteGenerator::ENTITY_TYPE,
160+
$rootCategoryId
161+
);
162+
163+
foreach ($productCategories->getList() as $productCategory) {
164+
$anchorCategoryIds = array_merge($productCategory->getAnchorsAbove(), $anchorCategoryIds);
165+
}
166+
167+
foreach ($currentUrlRewrites as $currentUrlRewrite) {
168+
$metadata = $currentUrlRewrite->getMetadata();
169+
if (isset($metadata['category_id']) && $metadata['category_id'] > 0) {
170+
$category = $this->categoryRepository->get($metadata['category_id'], $storeId);
171+
if (in_array($category->getId(), $anchorCategoryIds)) {
172+
$mergeDataProvider->merge(
173+
$currentUrlRewrite->getIsAutogenerated()
174+
? $this->generateForAutogenerated($currentUrlRewrite, $storeId, $category, $product)
175+
: $this->generateForCustom($currentUrlRewrite, $storeId, $category, $product)
176+
);
177+
}
178+
}
179+
}
180+
181+
return $mergeDataProvider->getData();
182+
}
183+
129184
/**
130185
* @param UrlRewrite $url
131186
* @param int $storeId

app/code/Magento/CatalogUrlRewrite/Model/ProductScopeRewriteGenerator.php

Lines changed: 46 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
*/
66
namespace Magento\CatalogUrlRewrite\Model;
77

8+
use Magento\Catalog\Api\CategoryRepositoryInterface;
89
use Magento\Catalog\Model\Category;
910
use Magento\Catalog\Model\Product;
1011
use Magento\CatalogUrlRewrite\Model\Product\CanonicalUrlRewriteGenerator;
@@ -80,6 +81,11 @@ class ProductScopeRewriteGenerator
8081
*/
8182
private $mergeDataProviderPrototype;
8283

84+
/**
85+
* @var CategoryRepositoryInterface
86+
*/
87+
private $categoryRepository;
88+
8389
/**
8490
* @param StoreViewService $storeViewService
8591
* @param StoreManagerInterface $storeManager
@@ -89,6 +95,7 @@ class ProductScopeRewriteGenerator
8995
* @param CurrentUrlRewritesRegenerator $currentUrlRewritesRegenerator
9096
* @param AnchorUrlRewriteGenerator $anchorUrlRewriteGenerator
9197
* @param \Magento\UrlRewrite\Model\MergeDataProviderFactory|null $mergeDataProviderFactory
98+
* @param CategoryRepositoryInterface|null $categoryRepository
9299
*/
93100
public function __construct(
94101
StoreViewService $storeViewService,
@@ -98,7 +105,8 @@ public function __construct(
98105
CategoriesUrlRewriteGenerator $categoriesUrlRewriteGenerator,
99106
CurrentUrlRewritesRegenerator $currentUrlRewritesRegenerator,
100107
AnchorUrlRewriteGenerator $anchorUrlRewriteGenerator,
101-
MergeDataProviderFactory $mergeDataProviderFactory = null
108+
MergeDataProviderFactory $mergeDataProviderFactory = null,
109+
CategoryRepositoryInterface $categoryRepository = null
102110
) {
103111
$this->storeViewService = $storeViewService;
104112
$this->storeManager = $storeManager;
@@ -111,6 +119,8 @@ public function __construct(
111119
$mergeDataProviderFactory = ObjectManager::getInstance()->get(MergeDataProviderFactory::class);
112120
}
113121
$this->mergeDataProviderPrototype = $mergeDataProviderFactory->create();
122+
$this->categoryRepository = $categoryRepository ?:
123+
ObjectManager::getInstance()->get(CategoryRepositoryInterface::class);
114124
}
115125

116126
/**
@@ -167,9 +177,12 @@ public function generateForSpecificStoreView($storeId, $productCategories, Produ
167177
$mergeDataProvider = clone $this->mergeDataProviderPrototype;
168178
$categories = [];
169179
foreach ($productCategories as $category) {
170-
if ($this->isCategoryProperForGenerating($category, $storeId)) {
171-
$categories[] = $category;
180+
if (!$this->isCategoryProperForGenerating($category, $storeId)) {
181+
continue;
172182
}
183+
184+
// category should be loaded per appropriate store if category's URL key has been changed
185+
$categories[] = $this->getCategoryWithOverriddenUrlKey($storeId, $category);
173186
}
174187
$productCategories = $this->objectRegistryFactory->create(['entities' => $categories]);
175188

@@ -190,6 +203,14 @@ public function generateForSpecificStoreView($storeId, $productCategories, Produ
190203
$mergeDataProvider->merge(
191204
$this->anchorUrlRewriteGenerator->generate($storeId, $product, $productCategories)
192205
);
206+
$mergeDataProvider->merge(
207+
$this->currentUrlRewritesRegenerator->generateAnchor(
208+
$storeId,
209+
$product,
210+
$productCategories,
211+
$rootCategoryId
212+
)
213+
);
193214

194215
return $mergeDataProvider->getData();
195216
}
@@ -210,4 +231,26 @@ public function isCategoryProperForGenerating(Category $category, $storeId)
210231

211232
return false;
212233
}
234+
235+
/**
236+
* Checks if URL key has been changed for provided category and returns reloaded category,
237+
* in other case - returns provided category.
238+
*
239+
* @param $storeId
240+
* @param Category $category
241+
* @return Category
242+
*/
243+
private function getCategoryWithOverriddenUrlKey($storeId, Category $category)
244+
{
245+
$isUrlKeyOverridden = $this->storeViewService->doesEntityHaveOverriddenUrlKeyForStore(
246+
$storeId,
247+
$category->getEntityId(),
248+
Category::ENTITY
249+
);
250+
251+
if (!$isUrlKeyOverridden) {
252+
return $category;
253+
}
254+
return $this->categoryRepository->get($category->getEntityId(), $storeId);
255+
}
213256
}

0 commit comments

Comments
 (0)