Skip to content

Commit 8e2959d

Browse files
author
Jeroen
committed
Prevent endless loop when duplicating product
1 parent f6405d5 commit 8e2959d

File tree

2 files changed

+185
-73
lines changed

2 files changed

+185
-73
lines changed

app/code/Magento/Catalog/Model/Product/Copier.php

Lines changed: 35 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,11 @@
88
use Magento\Catalog\Api\Data\ProductInterface;
99
use Magento\Catalog\Model\Attribute\ScopeOverriddenValue;
1010
use Magento\Catalog\Model\Product;
11+
use Magento\Catalog\Model\Product\Option\Repository as OptionRepository;
1112
use Magento\Catalog\Model\ProductFactory;
13+
use Magento\Framework\App\ObjectManager;
14+
use Magento\Framework\EntityManager\MetadataPool;
15+
use Magento\UrlRewrite\Model\Exception\UrlAlreadyExistsException;
1216

1317
/**
1418
* Catalog product copier.
@@ -35,9 +39,10 @@ class Copier
3539
protected $productFactory;
3640

3741
/**
38-
* @var \Magento\Framework\EntityManager\MetadataPool
42+
* @var MetadataPool
3943
*/
4044
protected $metadataPool;
45+
4146
/**
4247
* @var ScopeOverriddenValue
4348
*/
@@ -47,30 +52,38 @@ class Copier
4752
* @param CopyConstructorInterface $copyConstructor
4853
* @param ProductFactory $productFactory
4954
* @param ScopeOverriddenValue $scopeOverriddenValue
55+
* @param OptionRepository|null $optionRepository
56+
* @param MetadataPool|null $metadataPool
5057
*/
5158
public function __construct(
5259
CopyConstructorInterface $copyConstructor,
5360
ProductFactory $productFactory,
54-
ScopeOverriddenValue $scopeOverriddenValue
61+
ScopeOverriddenValue $scopeOverriddenValue,
62+
OptionRepository $optionRepository = null,
63+
MetadataPool $metadataPool = null
5564
) {
5665
$this->productFactory = $productFactory;
5766
$this->copyConstructor = $copyConstructor;
5867
$this->scopeOverriddenValue = $scopeOverriddenValue;
68+
$this->optionRepository = $optionRepository ?: ObjectManager::getInstance()->get(OptionRepository::class);
69+
$this->metadataPool = $metadataPool ?: ObjectManager::getInstance()->get(MetadataPool::class);
5970
}
6071

6172
/**
6273
* Create product duplicate
6374
*
6475
* @param \Magento\Catalog\Model\Product $product
76+
*
6577
* @return \Magento\Catalog\Model\Product
78+
*
79+
* @throws \Exception
6680
*/
6781
public function copy(Product $product)
6882
{
6983
$product->getWebsiteIds();
7084
$product->getCategoryIds();
7185

72-
/** @var \Magento\Framework\EntityManager\EntityMetadataInterface $metadata */
73-
$metadata = $this->getMetadataPool()->getMetadata(ProductInterface::class);
86+
$metadata = $this->metadataPool->getMetadata(ProductInterface::class);
7487

7588
/** @var \Magento\Catalog\Model\Product $duplicate */
7689
$duplicate = $this->productFactory->create();
@@ -88,7 +101,7 @@ public function copy(Product $product)
88101
$this->copyConstructor->build($product, $duplicate);
89102
$this->setDefaultUrl($product, $duplicate);
90103
$this->setStoresUrl($product, $duplicate);
91-
$this->getOptionRepository()->duplicate($product, $duplicate);
104+
$this->optionRepository->duplicate($product, $duplicate);
92105
$product->getResource()->duplicate(
93106
$product->getData($metadata->getLinkField()),
94107
$duplicate->getData($metadata->getLinkField())
@@ -123,13 +136,16 @@ private function setDefaultUrl(Product $product, Product $duplicate) : void
123136
*
124137
* @param Product $product
125138
* @param Product $duplicate
139+
*
126140
* @return void
141+
* @throws UrlAlreadyExistsException
127142
*/
128143
private function setStoresUrl(Product $product, Product $duplicate) : void
129144
{
130145
$storeIds = $duplicate->getStoreIds();
131146
$productId = $product->getId();
132147
$productResource = $product->getResource();
148+
$attribute = $productResource->getAttribute('url_key');
133149
$duplicate->setData('save_rewrites_history', false);
134150
foreach ($storeIds as $storeId) {
135151
$useDefault = !$this->scopeOverriddenValue->containsValue(
@@ -141,20 +157,23 @@ private function setStoresUrl(Product $product, Product $duplicate) : void
141157
if ($useDefault) {
142158
continue;
143159
}
144-
$isDuplicateSaved = false;
160+
145161
$duplicate->setStoreId($storeId);
146162
$urlKey = $productResource->getAttributeRawValue($productId, 'url_key', $storeId);
163+
$iteration = 0;
164+
147165
do {
166+
if ($iteration === 10) {
167+
throw new UrlAlreadyExistsException();
168+
}
169+
148170
$urlKey = $this->modifyUrl($urlKey);
149171
$duplicate->setUrlKey($urlKey);
150-
$duplicate->setData('url_path', null);
151-
try {
152-
$duplicate->save();
153-
$isDuplicateSaved = true;
154-
// phpcs:ignore Magento2.CodeAnalysis.EmptyBlock
155-
} catch (\Magento\Framework\Exception\AlreadyExistsException $e) {
156-
}
157-
} while (!$isDuplicateSaved);
172+
$iteration++;
173+
} while (!$attribute->getEntity()->checkAttributeUniqueValue($attribute, $duplicate));
174+
$duplicate->setData('url_path', null);
175+
$productResource->saveAttribute($duplicate, 'url_path');
176+
$productResource->saveAttribute($duplicate, 'url_key');
158177
}
159178
$duplicate->setStoreId(\Magento\Store\Model\Store::DEFAULT_STORE_ID);
160179
}
@@ -168,38 +187,8 @@ private function setStoresUrl(Product $product, Product $duplicate) : void
168187
private function modifyUrl(string $urlKey) : string
169188
{
170189
return preg_match('/(.*)-(\d+)$/', $urlKey, $matches)
171-
? $matches[1] . '-' . ($matches[2] + 1)
172-
: $urlKey . '-1';
173-
}
174-
175-
/**
176-
* Returns product option repository.
177-
*
178-
* @return Option\Repository
179-
* @deprecated 101.0.0
180-
*/
181-
private function getOptionRepository()
182-
{
183-
if (null === $this->optionRepository) {
184-
$this->optionRepository = \Magento\Framework\App\ObjectManager::getInstance()
185-
->get(\Magento\Catalog\Model\Product\Option\Repository::class);
186-
}
187-
return $this->optionRepository;
188-
}
189-
190-
/**
191-
* Returns metadata pool.
192-
*
193-
* @return \Magento\Framework\EntityManager\MetadataPool
194-
* @deprecated 101.0.0
195-
*/
196-
private function getMetadataPool()
197-
{
198-
if (null === $this->metadataPool) {
199-
$this->metadataPool = \Magento\Framework\App\ObjectManager::getInstance()
200-
->get(\Magento\Framework\EntityManager\MetadataPool::class);
201-
}
202-
return $this->metadataPool;
190+
? $matches[1] . '-' . ($matches[2] + 1)
191+
: $urlKey . '-1';
203192
}
204193

205194
/**

0 commit comments

Comments
 (0)