Skip to content

Commit 84405f1

Browse files
Merge pull request #1921 from magento-tsg/2.1-develop-pr37
Bug - MAGETWO-60154 [Backport] Saving category deletes UrlRewrites for subcategories and all products in them - for 2.1.x
2 parents 427d417 + 6045c0e commit 84405f1

11 files changed

+473
-90
lines changed
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
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+
* Generates product url rewrites based on all categories.
13+
*/
14+
class CategoryProductUrlPathGenerator
15+
{
16+
/**
17+
* Generates url rewrites for different scopes.
18+
*
19+
* @var ProductScopeRewriteGenerator
20+
*/
21+
private $productScopeRewriteGenerator;
22+
23+
/**
24+
* @param ProductScopeRewriteGenerator $productScopeRewriteGenerator
25+
*/
26+
public function __construct(
27+
ProductScopeRewriteGenerator $productScopeRewriteGenerator
28+
) {
29+
$this->productScopeRewriteGenerator = $productScopeRewriteGenerator;
30+
}
31+
32+
/**
33+
* Generate product url rewrites based on all product categories.
34+
*
35+
* @param Product $product
36+
* @param int|null $rootCategoryId
37+
* @return \Magento\UrlRewrite\Service\V1\Data\UrlRewrite[]
38+
*/
39+
public function generate(Product $product, $rootCategoryId = null)
40+
{
41+
if ($product->getVisibility() == Visibility::VISIBILITY_NOT_VISIBLE) {
42+
return [];
43+
}
44+
45+
$storeId = $product->getStoreId();
46+
47+
$productCategories = $product->getCategoryCollection()
48+
->addAttributeToSelect('url_key')
49+
->addAttributeToSelect('url_path');
50+
51+
$urls = $this->productScopeRewriteGenerator->isGlobalScope($storeId)
52+
? $this->productScopeRewriteGenerator->generateForGlobalScope(
53+
$productCategories,
54+
$product,
55+
$rootCategoryId
56+
)
57+
: $this->productScopeRewriteGenerator->generateForSpecificStoreView(
58+
$storeId,
59+
$productCategories,
60+
$product,
61+
$rootCategoryId
62+
);
63+
64+
return $urls;
65+
}
66+
}

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: 47 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,27 @@ 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 int $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+
255+
return $this->categoryRepository->get($category->getEntityId(), $storeId);
256+
}
213257
}

0 commit comments

Comments
 (0)