Skip to content

Commit 24382e3

Browse files
author
vitaliyboyko
committed
ENGCOM-20434: Consider deleting/creating url rewrites on change the product attribute visibility via mass action
1 parent 2957731 commit 24382e3

File tree

8 files changed

+213
-25
lines changed

8 files changed

+213
-25
lines changed
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
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\Observer;
9+
10+
use Magento\Catalog\Api\Data\ProductInterface;
11+
use Magento\Catalog\Model\Product\Visibility;
12+
use Magento\CatalogUrlRewrite\Model\ProductUrlPathGenerator;
13+
use Magento\CatalogUrlRewrite\Model\ProductUrlRewriteGenerator;
14+
use Magento\UrlRewrite\Model\Exception\UrlAlreadyExistsException;
15+
use Magento\UrlRewrite\Model\UrlPersistInterface;
16+
use Magento\UrlRewrite\Service\V1\Data\UrlRewrite;
17+
use Magento\Catalog\Model\ResourceModel\Product\CollectionFactory;
18+
use Magento\Framework\Event\Observer;
19+
use Magento\Framework\Event\ObserverInterface;
20+
21+
/**
22+
* Class ProductProcessUrlRewriteSavingObserver
23+
*/
24+
class ProcessUrlRewriteOnChangeProductVisibilityObserver implements ObserverInterface
25+
{
26+
/**
27+
* @var CollectionFactory
28+
*/
29+
private $productCollectionFactory;
30+
/**
31+
* @var ProductUrlRewriteGenerator
32+
*/
33+
private $urlRewriteGenerator;
34+
/**
35+
* @var UrlPersistInterface
36+
*/
37+
private $urlPersist;
38+
/**
39+
* @var ProductUrlPathGenerator
40+
*/
41+
private $urlPathGenerator;
42+
/**
43+
* @param CollectionFactory $collectionFactory
44+
* @param ProductUrlRewriteGenerator $urlRewriteGenerator
45+
* @param UrlPersistInterface $urlPersist
46+
* @param ProductUrlPathGenerator|null $urlPathGenerator
47+
*/
48+
public function __construct(
49+
CollectionFactory $collectionFactory,
50+
ProductUrlRewriteGenerator $urlRewriteGenerator,
51+
UrlPersistInterface $urlPersist,
52+
ProductUrlPathGenerator $urlPathGenerator
53+
) {
54+
$this->productCollectionFactory = $collectionFactory;
55+
$this->urlRewriteGenerator = $urlRewriteGenerator;
56+
$this->urlPersist = $urlPersist;
57+
$this->urlPathGenerator = $urlPathGenerator;
58+
}
59+
/**
60+
* Generate urls for UrlRewrites and save it in storage
61+
*
62+
* @param Observer $observer
63+
* @return array
64+
* @throws UrlAlreadyExistsException
65+
*/
66+
public function execute(Observer $observer)
67+
{
68+
$event = $observer->getEvent();
69+
$attrData = $event->getAttributesData();
70+
$productIds = $event->getProductIds();
71+
$storeId = $event->getStoreId();
72+
$visibility = $attrData[ProductInterface::VISIBILITY] ?? null;
73+
74+
if (!$visibility) {
75+
return [$attrData, $productIds, $storeId];
76+
}
77+
78+
$productCollection = $this->productCollectionFactory->create();
79+
$productCollection->addAttributeToSelect(ProductInterface::VISIBILITY);
80+
$productCollection->addAttributeToSelect('url_key');
81+
$productCollection->addFieldToFilter(
82+
'entity_id',
83+
['in' => array_unique($productIds)]
84+
);
85+
86+
foreach ($productCollection as $product) {
87+
if ($visibility == Visibility::VISIBILITY_NOT_VISIBLE) {
88+
$this->urlPersist->deleteByData(
89+
[
90+
UrlRewrite::ENTITY_ID => $product->getId(),
91+
UrlRewrite::ENTITY_TYPE => ProductUrlRewriteGenerator::ENTITY_TYPE,
92+
]
93+
);
94+
} elseif ($visibility !== Visibility::VISIBILITY_NOT_VISIBLE) {
95+
$product->setVisibility($visibility);
96+
$productUrlPath = $this->urlPathGenerator->getUrlPath($product);
97+
$productUrlRewrite = $this->urlRewriteGenerator->generate($product);
98+
$product->unsUrlPath();
99+
$product->setUrlPath($productUrlPath);
100+
$this->urlPersist->replace($productUrlRewrite);
101+
}
102+
}
103+
104+
return [$attrData, $productIds, $storeId];
105+
}
106+
}

app/code/Magento/CatalogUrlRewrite/Observer/ProductProcessUrlRewriteSavingObserver.php

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,18 @@
33
* Copyright © Magento, Inc. All rights reserved.
44
* See COPYING.txt for license details.
55
*/
6+
declare(strict_types=1);
7+
68
namespace Magento\CatalogUrlRewrite\Observer;
79

810
use Magento\Catalog\Model\Product;
911
use Magento\CatalogUrlRewrite\Model\ProductUrlPathGenerator;
1012
use Magento\CatalogUrlRewrite\Model\ProductUrlRewriteGenerator;
1113
use Magento\Framework\App\ObjectManager;
14+
use Magento\UrlRewrite\Model\Exception\UrlAlreadyExistsException;
1215
use Magento\UrlRewrite\Model\UrlPersistInterface;
1316
use Magento\Framework\Event\ObserverInterface;
17+
use Magento\Catalog\Model\ResourceModel\Product\CollectionFactory;
1418

1519
/**
1620
* Class ProductProcessUrlRewriteSavingObserver
@@ -32,20 +36,29 @@ class ProductProcessUrlRewriteSavingObserver implements ObserverInterface
3236
*/
3337
private $productUrlPathGenerator;
3438

39+
/**
40+
* @var CollectionFactory
41+
*/
42+
private $collectionFactory;
43+
3544
/**
3645
* @param ProductUrlRewriteGenerator $productUrlRewriteGenerator
3746
* @param UrlPersistInterface $urlPersist
38-
* @param ProductUrlPathGenerator|null $productUrlPathGenerator
47+
* @param ProductUrlPathGenerator|null $urlPathGenerator
48+
* @param CollectionFactory|null $collectionFactory
3949
*/
4050
public function __construct(
4151
ProductUrlRewriteGenerator $productUrlRewriteGenerator,
4252
UrlPersistInterface $urlPersist,
43-
ProductUrlPathGenerator $productUrlPathGenerator = null
53+
ProductUrlPathGenerator $urlPathGenerator = null,
54+
CollectionFactory $collectionFactory = null
4455
) {
4556
$this->productUrlRewriteGenerator = $productUrlRewriteGenerator;
4657
$this->urlPersist = $urlPersist;
47-
$this->productUrlPathGenerator = $productUrlPathGenerator ?: ObjectManager::getInstance()
58+
$this->productUrlPathGenerator = $urlPathGenerator ?: ObjectManager::getInstance()
4859
->get(ProductUrlPathGenerator::class);
60+
$this->collectionFactory = $collectionFactory ?: ObjectManager::getInstance()
61+
->get(CollectionFactory::class);
4962
}
5063

5164
/**
@@ -69,7 +82,27 @@ public function execute(\Magento\Framework\Event\Observer $observer)
6982
$product->unsUrlPath();
7083
$product->setUrlPath($this->productUrlPathGenerator->getUrlPath($product));
7184
$this->urlPersist->replace($this->productUrlRewriteGenerator->generate($product));
85+
return;
7286
}
7387
}
88+
$this->validateUrlKey($product);
89+
}
90+
91+
/**
92+
* @param Product $product
93+
* @throws UrlAlreadyExistsException
94+
*/
95+
private function validateUrlKey(Product $product)
96+
{
97+
$productCollection = $this->collectionFactory->create();
98+
$productCollection->addFieldToFilter(
99+
'url_key',
100+
['in' => $product->getUrlKey()]
101+
);
102+
$productCollection->getSelect()->where('e.entity_id != ?', $product->getId());
103+
104+
if ($productCollection->getItems()) {
105+
throw new UrlAlreadyExistsException();
106+
}
74107
}
75108
}

app/code/Magento/CatalogUrlRewrite/Test/Unit/Observer/ProductProcessUrlRewriteSavingObserverTest.php

Lines changed: 40 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,7 @@
66

77
namespace Magento\CatalogUrlRewrite\Test\Unit\Observer;
88

9-
use Magento\CatalogUrlRewrite\Model\ProductUrlRewriteGenerator;
109
use Magento\Framework\TestFramework\Unit\Helper\ObjectManager;
11-
use Magento\UrlRewrite\Service\V1\Data\UrlRewrite;
1210

1311
/**
1412
* Class ProductProcessUrlRewriteSavingObserverTest
@@ -48,6 +46,21 @@ class ProductProcessUrlRewriteSavingObserverTest extends \PHPUnit\Framework\Test
4846
*/
4947
protected $objectManager;
5048

49+
/**
50+
* @var \Magento\Catalog\Model\ResourceModel\Product\CollectionFactory
51+
*/
52+
private $collectionFactory;
53+
54+
/**
55+
* @var \Magento\Catalog\Model\ResourceModel\Product\Collection
56+
*/
57+
private $productCollection;
58+
59+
/**
60+
* @var \Magento\Framework\DB\Select
61+
*/
62+
private $select;
63+
5164
/**
5265
* @var \Magento\CatalogUrlRewrite\Observer\ProductProcessUrlRewriteSavingObserver
5366
*/
@@ -60,13 +73,13 @@ protected function setUp()
6073
{
6174
$this->urlPersist = $this->createMock(\Magento\UrlRewrite\Model\UrlPersistInterface::class);
6275
$this->product = $this->createPartialMock(\Magento\Catalog\Model\Product::class, [
63-
'getId',
64-
'dataHasChangedFor',
65-
'isVisibleInSiteVisibility',
66-
'getIsChangedWebsites',
67-
'getIsChangedCategories',
68-
'getStoreId'
69-
]);
76+
'getId',
77+
'dataHasChangedFor',
78+
'isVisibleInSiteVisibility',
79+
'getIsChangedWebsites',
80+
'getIsChangedCategories',
81+
'getStoreId'
82+
]);
7083
$this->product->expects($this->any())->method('getId')->will($this->returnValue(3));
7184
$this->event = $this->createPartialMock(\Magento\Framework\Event::class, ['getProduct']);
7285
$this->event->expects($this->any())->method('getProduct')->willReturn($this->product);
@@ -79,12 +92,28 @@ protected function setUp()
7992
$this->productUrlRewriteGenerator->expects($this->any())
8093
->method('generate')
8194
->will($this->returnValue([3 => 'rewrite']));
95+
$this->collectionFactory = $this->createMock(
96+
\Magento\Catalog\Model\ResourceModel\Product\CollectionFactory::class
97+
);
98+
$this->productCollection = $this->createMock(
99+
\Magento\Catalog\Model\ResourceModel\Product\Collection::class
100+
);
101+
$this->select = $this->createMock(
102+
\Magento\Framework\DB\Select::class
103+
);
104+
$this->collectionFactory->expects($this->any())
105+
->method('create')
106+
->will($this->returnValue($this->productCollection));
107+
$this->productCollection->expects($this->any())
108+
->method('getSelect')
109+
->will($this->returnValue($this->select));
82110
$this->objectManager = new ObjectManager($this);
83111
$this->model = $this->objectManager->getObject(
84112
\Magento\CatalogUrlRewrite\Observer\ProductProcessUrlRewriteSavingObserver::class,
85113
[
86114
'productUrlRewriteGenerator' => $this->productUrlRewriteGenerator,
87-
'urlPersist' => $this->urlPersist
115+
'urlPersist' => $this->urlPersist,
116+
'collectionFactory' => $this->collectionFactory
88117
]
89118
);
90119
}
@@ -103,8 +132,7 @@ public function urlKeyDataProvider()
103132
'isChangedWebsites' => false,
104133
'isChangedCategories' => false,
105134
'visibilityResult' => true,
106-
'expectedReplaceCount' => 1,
107-
135+
'expectedReplaceCount' => 1
108136
],
109137
'no chnages' => [
110138
'isChangedUrlKey' => false,

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@
2727
<event name="catalog_product_save_after">
2828
<observer name="process_url_rewrite_saving" instance="Magento\CatalogUrlRewrite\Observer\ProductProcessUrlRewriteSavingObserver"/>
2929
</event>
30+
<event name="catalog_product_attribute_update_before">
31+
<observer name="process_url_rewrite_on_change_product_visibility" instance="Magento\CatalogUrlRewrite\Observer\ProcessUrlRewriteOnChangeProductVisibilityObserver"/>
32+
</event>
3033
<event name="catalog_category_save_before">
3134
<observer name="category_url_path_autogeneration" instance="Magento\CatalogUrlRewrite\Observer\CategoryUrlPathAutogeneratorObserver"/>
3235
</event>

dev/tests/integration/testsuite/Magento/CatalogSearch/_files/product_configurable_not_available.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@
4343
$productSku = array_shift($productsSku);
4444
$product->setTypeId(Type::TYPE_SIMPLE)
4545
->setAttributeSetId($attributeSetId)
46-
->setName('Configurable Option' . $option->getLabel())
46+
->setName('Configurable Option Not Available' . $option->getLabel())
4747
->setSku('simple_not_avalilable_' . $productSku)
4848
->setPrice(11)
4949
->setTestConfigurable($option->getValue())

dev/tests/integration/testsuite/Magento/CatalogUrlRewrite/Observer/ProductProcessUrlRewriteSavingObserverTest.php

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,11 @@
55
*/
66
namespace Magento\CatalogUrlRewrite\Observer;
77

8+
use Magento\Catalog\Api\ProductRepositoryInterface;
9+
use Magento\Catalog\Model\Product\Visibility;
810
use Magento\Store\Model\StoreManagerInterface;
11+
use Magento\UrlRewrite\Model\Exception\UrlAlreadyExistsException;
912
use Magento\UrlRewrite\Service\V1\Data\UrlRewrite;
10-
use Magento\CatalogUrlRewrite\Model\CategoryUrlRewriteGenerator;
1113

1214
/**
1315
* @magentoAppArea adminhtml
@@ -18,12 +20,16 @@ class ProductProcessUrlRewriteSavingObserverTest extends \PHPUnit\Framework\Test
1820
/** @var \Magento\Framework\ObjectManagerInterface */
1921
protected $objectManager;
2022

23+
/** @var ProductRepositoryInterface */
24+
private $productRepository;
25+
2126
/**
2227
* Set up
2328
*/
2429
protected function setUp()
2530
{
2631
$this->objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager();
32+
$this->productRepository = $this->objectManager->get(ProductRepositoryInterface::class);
2733
}
2834

2935
/**
@@ -136,10 +142,8 @@ public function testUrlKeyHasChangedInGlobalContext()
136142
*/
137143
public function testUrlKeyHasChangedInStoreviewContextWithPermanentRedirection()
138144
{
139-
/** @var \Magento\Catalog\Api\ProductRepositoryInterface $productRepository*/
140-
$productRepository = $this->objectManager->create(\Magento\Catalog\Api\ProductRepositoryInterface::class);
141145
/** @var \Magento\Catalog\Model\Product $product*/
142-
$product = $productRepository->get('product1');
146+
$product = $this->productRepository->get('product1');
143147

144148
/** @var StoreManagerInterface $storeManager */
145149
$storeManager = $this->objectManager->get(StoreManagerInterface::class);
@@ -234,4 +238,22 @@ public function testUrlKeyHasChangedInStoreviewContextWithoutPermanentRedirectio
234238
$this->assertContains($row, $actual);
235239
}
236240
}
241+
242+
/**
243+
* @magentoDataFixture Magento/CatalogUrlRewrite/_files/product_simple.php
244+
* @magentoDataFixture Magento/Catalog/_files/second_product_simple.php
245+
* @magentoAppIsolation enabled
246+
*/
247+
public function testDuplicatedUrlKeyInvisibleProduct()
248+
{
249+
$this->expectException(
250+
UrlAlreadyExistsException::class,
251+
'URL key for specified store already exists'
252+
);
253+
/** @var \Magento\Catalog\Model\Product $product*/
254+
$product = $this->productRepository->get('simple2');
255+
$product->setVisibility(Visibility::VISIBILITY_NOT_VISIBLE);
256+
$product->setUrlKey('simple_product');
257+
$product->save();
258+
}
237259
}

dev/tests/integration/testsuite/Magento/ConfigurableProduct/_files/configurable_products.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@
107107
->setId($productId)
108108
->setAttributeSetId($attributeSetId)
109109
->setWebsiteIds([1])
110-
->setName('Configurable Option' . $option->getLabel())
110+
->setName('Configurable Option 12345' . $option->getLabel())
111111
->setSku('simple_' . $productId)
112112
->setPrice($productId)
113113
->setTestConfigurable($option->getValue())

lib/internal/Magento/Framework/DB/Adapter/Pdo/Mysql.php

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -384,10 +384,6 @@ protected function _connect()
384384
throw new \Zend_Db_Adapter_Exception('No host configured to connect');
385385
}
386386

387-
if (isset($this->_config['port'])) {
388-
throw new \Zend_Db_Adapter_Exception('Port must be configured within host parameter (like localhost:3306');
389-
}
390-
391387
unset($this->_config['port']);
392388

393389
if (strpos($this->_config['host'], '/') !== false) {

0 commit comments

Comments
 (0)