Skip to content

Commit 428601f

Browse files
committed
Merge remote-tracking branch 'origin/MAGETWO-90332' into 2.3-develop-pr13
2 parents e903e18 + b869450 commit 428601f

File tree

4 files changed

+334
-0
lines changed

4 files changed

+334
-0
lines changed
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
declare(strict_types=1);
7+
8+
namespace Magento\CatalogUrlRewrite\Model\Category\Plugin\Category;
9+
10+
use Magento\Framework\Model\AbstractModel;
11+
use Magento\Catalog\Model\Category;
12+
use Magento\CatalogUrlRewrite\Model\CategoryUrlPathGenerator;
13+
use Magento\CatalogUrlRewrite\Model\CategoryUrlRewriteGenerator;
14+
use Magento\CatalogUrlRewrite\Service\V1\StoreViewService;
15+
use Magento\UrlRewrite\Model\UrlPersistInterface;
16+
use Magento\Store\Model\Store;
17+
use Magento\Catalog\Model\ResourceModel\Category as CategoryResource;
18+
19+
/**
20+
* Generate and save url-rewrites for category if its parent have specified url-key for different store views.
21+
*/
22+
class UpdateUrlPath
23+
{
24+
/**
25+
* @var \Magento\CatalogUrlRewrite\Model\CategoryUrlPathGenerator
26+
*/
27+
private $categoryUrlPathGenerator;
28+
29+
/**
30+
* @var \Magento\CatalogUrlRewrite\Model\CategoryUrlRewriteGenerator
31+
*/
32+
private $categoryUrlRewriteGenerator;
33+
34+
/**
35+
* @var \Magento\CatalogUrlRewrite\Service\V1\StoreViewService
36+
*/
37+
private $storeViewService;
38+
39+
/**
40+
* @var \Magento\UrlRewrite\Model\UrlPersistInterface
41+
*/
42+
private $urlPersist;
43+
44+
/**
45+
* @param CategoryUrlPathGenerator $categoryUrlPathGenerator
46+
* @param CategoryUrlRewriteGenerator $categoryUrlRewriteGenerator
47+
* @param UrlPersistInterface $urlPersist
48+
* @param StoreViewService $storeViewService
49+
*/
50+
public function __construct(
51+
CategoryUrlPathGenerator $categoryUrlPathGenerator,
52+
CategoryUrlRewriteGenerator $categoryUrlRewriteGenerator,
53+
UrlPersistInterface $urlPersist,
54+
StoreViewService $storeViewService
55+
) {
56+
$this->categoryUrlPathGenerator = $categoryUrlPathGenerator;
57+
$this->categoryUrlRewriteGenerator = $categoryUrlRewriteGenerator;
58+
$this->urlPersist = $urlPersist;
59+
$this->storeViewService = $storeViewService;
60+
}
61+
62+
/**
63+
* Perform url updating for different stores.
64+
*
65+
* @param CategoryResource $subject
66+
* @param CategoryResource $result
67+
* @param AbstractModel $category
68+
* @return CategoryResource
69+
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
70+
*/
71+
public function afterSave(
72+
CategoryResource $subject,
73+
CategoryResource $result,
74+
AbstractModel $category
75+
): CategoryResource {
76+
$parentCategoryId = $category->getParentId();
77+
if ($category->isObjectNew()
78+
&& !$category->isInRootCategoryList()
79+
&& !empty($parentCategoryId)
80+
) {
81+
foreach ($category->getStoreIds() as $storeId) {
82+
if (!$this->isGlobalScope((int)$storeId)
83+
&& $this->storeViewService->doesEntityHaveOverriddenUrlPathForStore(
84+
$storeId,
85+
$parentCategoryId,
86+
Category::ENTITY
87+
)
88+
) {
89+
$category->setStoreId($storeId);
90+
$this->updateUrlPathForCategory($category, $subject);
91+
$this->urlPersist->replace($this->categoryUrlRewriteGenerator->generate($category));
92+
}
93+
}
94+
}
95+
96+
return $result;
97+
}
98+
99+
/**
100+
* Check that store id is in global scope.
101+
*
102+
* @param int $storeId
103+
* @return bool
104+
*/
105+
private function isGlobalScope(int $storeId): bool
106+
{
107+
return $storeId === Store::DEFAULT_STORE_ID;
108+
}
109+
110+
/**
111+
* Updates category url path.
112+
*
113+
* @param Category $category
114+
* @param CategoryResource $categoryResource
115+
* @return void
116+
*/
117+
private function updateUrlPathForCategory(Category $category, CategoryResource $categoryResource): void
118+
{
119+
$category->unsUrlPath();
120+
$category->setUrlPath($this->categoryUrlPathGenerator->getUrlPath($category));
121+
$categoryResource->saveAttribute($category, 'url_path');
122+
}
123+
}
Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
declare(strict_types=1);
7+
8+
namespace Magento\CatalogUrlRewrite\Test\Unit\Model\Category\Plugin\Category;
9+
10+
use Magento\Catalog\Model\Category;
11+
use Magento\CatalogUrlRewrite\Model\CategoryUrlPathGenerator;
12+
use Magento\CatalogUrlRewrite\Model\CategoryUrlRewriteGenerator;
13+
use Magento\CatalogUrlRewrite\Service\V1\StoreViewService;
14+
use Magento\Framework\TestFramework\Unit\Helper\ObjectManager;
15+
use Magento\UrlRewrite\Model\UrlPersistInterface;
16+
use PHPUnit_Framework_MockObject_MockObject as MockObject;
17+
use Magento\Catalog\Model\ResourceModel\Category as CategoryResource;
18+
19+
/**
20+
* Unit test for Magento\CatalogUrlRewrite\Model\Category\Plugin\Category\UpdateUrlPath class.
21+
*/
22+
class UpdateUrlPathTest extends \PHPUnit\Framework\TestCase
23+
{
24+
/**
25+
* @var ObjectManager
26+
*/
27+
private $objectManager;
28+
29+
/**
30+
* @var CategoryUrlPathGenerator|MockObject
31+
*/
32+
private $categoryUrlPathGenerator;
33+
34+
/**
35+
* @var CategoryUrlRewriteGenerator|MockObject
36+
*/
37+
private $categoryUrlRewriteGenerator;
38+
39+
/**
40+
* @var StoreViewService|MockObject
41+
*/
42+
private $storeViewService;
43+
44+
/**
45+
* @var UrlPersistInterface|MockObject
46+
*/
47+
private $urlPersist;
48+
49+
/**
50+
* @var CategoryResource|MockObject
51+
*/
52+
private $categoryResource;
53+
54+
/**
55+
* @var Category|MockObject
56+
*/
57+
private $category;
58+
59+
/**
60+
* @var \Magento\CatalogUrlRewrite\Model\Category\Plugin\Category\UpdateUrlPath
61+
*/
62+
private $updateUrlPathPlugin;
63+
64+
/**
65+
* @inheritdoc
66+
*/
67+
protected function setUp()
68+
{
69+
$this->objectManager = new ObjectManager($this);
70+
$this->categoryUrlPathGenerator = $this->getMockBuilder(CategoryUrlPathGenerator::class)
71+
->disableOriginalConstructor()
72+
->setMethods(['getUrlPath'])
73+
->getMock();
74+
$this->categoryUrlRewriteGenerator = $this->getMockBuilder(CategoryUrlRewriteGenerator::class)
75+
->disableOriginalConstructor()
76+
->setMethods(['generate'])
77+
->getMock();
78+
$this->categoryResource = $this->getMockBuilder(CategoryResource::class)
79+
->disableOriginalConstructor()
80+
->setMethods(['saveAttribute'])
81+
->getMock();
82+
$this->category = $this->getMockBuilder(Category::class)
83+
->disableOriginalConstructor()
84+
->setMethods(
85+
[
86+
'getStoreId',
87+
'getParentId',
88+
'isObjectNew',
89+
'isInRootCategoryList',
90+
'getStoreIds',
91+
'setStoreId',
92+
'unsUrlPath',
93+
'setUrlPath',
94+
]
95+
)
96+
->getMock();
97+
$this->storeViewService = $this->getMockBuilder(StoreViewService::class)
98+
->disableOriginalConstructor()
99+
->setMethods(['doesEntityHaveOverriddenUrlPathForStore'])
100+
->getMock();
101+
$this->urlPersist = $this->getMockBuilder(UrlPersistInterface::class)
102+
->disableOriginalConstructor()
103+
->setMethods(['replace'])
104+
->getMockForAbstractClass();
105+
106+
$this->updateUrlPathPlugin = $this->objectManager->getObject(
107+
\Magento\CatalogUrlRewrite\Model\Category\Plugin\Category\UpdateUrlPath::class,
108+
[
109+
'categoryUrlPathGenerator' => $this->categoryUrlPathGenerator,
110+
'categoryUrlRewriteGenerator' => $this->categoryUrlRewriteGenerator,
111+
'urlPersist' => $this->urlPersist,
112+
'storeViewService' => $this->storeViewService,
113+
]
114+
);
115+
}
116+
117+
public function testAroundSaveWithoutRootCategory()
118+
{
119+
$this->category->expects($this->atLeastOnce())->method('getParentId')->willReturn(0);
120+
$this->category->expects($this->atLeastOnce())->method('isObjectNew')->willReturn(true);
121+
$this->category->expects($this->atLeastOnce())->method('isInRootCategoryList')->willReturn(false);
122+
$this->category->expects($this->never())->method('getStoreIds');
123+
124+
$this->assertEquals(
125+
$this->categoryResource,
126+
$this->updateUrlPathPlugin->afterSave($this->categoryResource, $this->categoryResource, $this->category)
127+
);
128+
}
129+
130+
public function testAroundSaveWithRootCategory()
131+
{
132+
$parentId = 1;
133+
$categoryStoreIds = [0,1,2];
134+
$generatedUrlPath = 'parent_category/child_category';
135+
136+
$this->categoryUrlPathGenerator
137+
->expects($this->once())
138+
->method('getUrlPath')
139+
->with($this->category)
140+
->willReturn($generatedUrlPath);
141+
$this->category->expects($this->atLeastOnce())->method('getParentId')->willReturn($parentId);
142+
$this->category->expects($this->atLeastOnce())->method('isObjectNew')->willReturn(true);
143+
$this->category->expects($this->atLeastOnce())->method('isInRootCategoryList')->willReturn(false);
144+
$this->category->expects($this->atLeastOnce())->method('getStoreIds')->willReturn($categoryStoreIds);
145+
$this->category->expects($this->once())->method('setStoreId')->with($categoryStoreIds[2])->willReturnSelf();
146+
$this->category->expects($this->once())->method('unsUrlPath')->willReturnSelf();
147+
$this->category->expects($this->once())->method('setUrlPath')->with($generatedUrlPath)->willReturnSelf();
148+
$this->storeViewService->expects($this->exactly(2))->method('doesEntityHaveOverriddenUrlPathForStore')
149+
->willReturnMap(
150+
[
151+
[$categoryStoreIds[1], $parentId, 'catalog_category', false],
152+
[$categoryStoreIds[2], $parentId, 'catalog_category', true],
153+
]
154+
);
155+
$this->categoryResource
156+
->expects($this->once())
157+
->method('saveAttribute')
158+
->with($this->category, 'url_path')
159+
->willReturnSelf();
160+
$generatedUrlRewrite = $this->getMockBuilder(\Magento\UrlRewrite\Service\V1\Data\UrlRewrite::class)
161+
->disableOriginalConstructor()
162+
->getMock();
163+
$this->categoryUrlRewriteGenerator->expects($this->once())->method('generate')->with($this->category)
164+
->willReturn([$generatedUrlRewrite]);
165+
$this->urlPersist->expects($this->once())->method('replace')->with([$generatedUrlRewrite])->willReturnSelf();
166+
167+
$this->assertEquals(
168+
$this->categoryResource,
169+
$this->updateUrlPathPlugin->afterSave($this->categoryResource, $this->categoryResource, $this->category)
170+
);
171+
}
172+
}

app/code/Magento/CatalogUrlRewrite/etc/di.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
<type name="Magento\Catalog\Model\ResourceModel\Category">
2020
<plugin name="category_move_plugin" type="Magento\CatalogUrlRewrite\Model\Category\Plugin\Category\Move"/>
2121
<plugin name="category_delete_plugin" type="Magento\CatalogUrlRewrite\Model\Category\Plugin\Category\Remove"/>
22+
<plugin name="update_url_path_for_different_stores" type="Magento\CatalogUrlRewrite\Model\Category\Plugin\Category\UpdateUrlPath"/>
2223
</type>
2324
<type name="Magento\UrlRewrite\Model\StorageInterface">
2425
<plugin name="storage_plugin" type="Magento\CatalogUrlRewrite\Model\Category\Plugin\Storage"/>

dev/tests/integration/testsuite/Magento/Catalog/Model/CategoryTest.php

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -320,6 +320,44 @@ public function testDeleteChildren()
320320
$this->assertEquals($this->_model->getId(), null);
321321
}
322322

323+
/**
324+
* @magentoDataFixture Magento/Store/_files/second_store.php
325+
* @magentoDataFixture Magento/Catalog/_files/categories.php
326+
* @magentoDbIsolation enabled
327+
* @return void
328+
*/
329+
public function testCreateSubcategoryWithMultipleStores()
330+
{
331+
$parentCategoryId = 3;
332+
$storeManager = $this->objectManager->get(\Magento\Store\Model\StoreManagerInterface::class);
333+
$storeManager->setCurrentStore(\Magento\Store\Model\Store::ADMIN_CODE);
334+
/** @var \Magento\Store\Api\StoreRepositoryInterface $storeRepository */
335+
$storeRepository = $this->objectManager->get(\Magento\Store\Api\StoreRepositoryInterface::class);
336+
$storeId = $storeRepository->get('fixture_second_store')->getId();
337+
/** @var \Magento\Catalog\Api\CategoryRepositoryInterface $repository */
338+
$repository = $this->objectManager->get(\Magento\Catalog\Api\CategoryRepositoryInterface::class);
339+
$parentCategory = $repository->get($parentCategoryId, $storeId);
340+
$parentAllStoresPath = $parentCategory->getUrlPath();
341+
$parentSecondStoreKey = 'parent-category-url-key-second-store';
342+
$parentCategory->setUrlKey($parentSecondStoreKey);
343+
$repository->save($parentCategory);
344+
/** @var \Magento\Catalog\Model\Category $childCategory */
345+
$childCategory = $this->objectManager->create(\Magento\Catalog\Model\Category::class);
346+
$childCategory->setName('Test Category 100')
347+
->setParentId($parentCategoryId)
348+
->setLevel(2)
349+
->setAvailableSortBy(['position', 'name'])
350+
->setDefaultSortBy('name')
351+
->setIsActive(true)
352+
->setPosition(1)
353+
->isObjectNew(true);
354+
$repository->save($childCategory);
355+
$childCategorySecondStore = $repository->get($childCategory->getId(), $storeId);
356+
357+
$this->assertEquals($parentAllStoresPath . '/test-category-100', $childCategory->getUrlPath());
358+
$this->assertEquals($parentSecondStoreKey . '/test-category-100', $childCategorySecondStore->getUrlPath());
359+
}
360+
323361
protected function getCategoryByName($categoryName)
324362
{
325363
/* @var \Magento\Catalog\Model\ResourceModel\Category\Collection $collection */

0 commit comments

Comments
 (0)