Skip to content

Commit f0bc113

Browse files
committed
MC-42506: [CLOUD] duplicated product does not retain some store view specific data
- fixed - removed redundant unit test - added integration test
1 parent 21d65a6 commit f0bc113

File tree

5 files changed

+188
-419
lines changed

5 files changed

+188
-419
lines changed

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

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,13 @@
66
namespace Magento\Catalog\Model\Product;
77

88
use Magento\Catalog\Api\Data\ProductInterface;
9+
use Magento\Catalog\Api\ProductRepositoryInterface;
910
use Magento\Catalog\Model\Attribute\ScopeOverriddenValue;
1011
use Magento\Catalog\Model\Product;
1112
use Magento\Catalog\Model\Product\Attribute\Source\Status;
1213
use Magento\Catalog\Model\Product\Option\Repository as OptionRepository;
1314
use Magento\Catalog\Model\ProductFactory;
14-
use Magento\Framework\App\ObjectManager;
15+
use Magento\Catalog\Model\ResourceModel\DuplicatedProductAttributesCopier;
1516
use Magento\Framework\EntityManager\MetadataPool;
1617
use Magento\Store\Model\Store;
1718
use Magento\UrlRewrite\Model\Exception\UrlAlreadyExistsException;
@@ -50,25 +51,41 @@ class Copier
5051
*/
5152
private $scopeOverriddenValue;
5253

54+
/**
55+
* @var ProductRepositoryInterface
56+
*/
57+
private $productRepository;
58+
59+
/**
60+
* @var DuplicatedProductAttributesCopier
61+
*/
62+
private $attributeCopier;
63+
5364
/**
5465
* @param CopyConstructorInterface $copyConstructor
5566
* @param ProductFactory $productFactory
5667
* @param ScopeOverriddenValue $scopeOverriddenValue
5768
* @param OptionRepository|null $optionRepository
5869
* @param MetadataPool|null $metadataPool
70+
* @param ProductRepositoryInterface $productRepository
71+
* @param DuplicatedProductAttributesCopier $attributeCopier
5972
*/
6073
public function __construct(
6174
CopyConstructorInterface $copyConstructor,
6275
ProductFactory $productFactory,
6376
ScopeOverriddenValue $scopeOverriddenValue,
6477
OptionRepository $optionRepository,
65-
MetadataPool $metadataPool
78+
MetadataPool $metadataPool,
79+
ProductRepositoryInterface $productRepository,
80+
DuplicatedProductAttributesCopier $attributeCopier
6681
) {
6782
$this->productFactory = $productFactory;
6883
$this->copyConstructor = $copyConstructor;
6984
$this->scopeOverriddenValue = $scopeOverriddenValue;
7085
$this->optionRepository = $optionRepository;
7186
$this->metadataPool = $metadataPool;
87+
$this->productRepository = $productRepository;
88+
$this->attributeCopier = $attributeCopier;
7289
}
7390

7491
/**
@@ -79,11 +96,13 @@ public function __construct(
7996
*/
8097
public function copy(Product $product): Product
8198
{
82-
$product->getWebsiteIds();
83-
$product->getCategoryIds();
84-
8599
$metadata = $this->metadataPool->getMetadata(ProductInterface::class);
86100

101+
/* Regardless in what scope the product was provided,
102+
for duplicating we want to clone product in Global scope first */
103+
if ((int)$product->getStoreId() !== Store::DEFAULT_STORE_ID) {
104+
$product = $this->productRepository->getById($product->getId(), true, Store::DEFAULT_STORE_ID);
105+
}
87106
/** @var Product $duplicate */
88107
$duplicate = $this->productFactory->create();
89108
$productData = $product->getData();
@@ -102,6 +121,7 @@ public function copy(Product $product): Product
102121
$duplicate->setStoreId(Store::DEFAULT_STORE_ID);
103122
$this->copyConstructor->build($product, $duplicate);
104123
$this->setDefaultUrl($product, $duplicate);
124+
$this->attributeCopier->copyProductAttributes($product, $duplicate);
105125
$this->setStoresUrl($product, $duplicate);
106126
$this->optionRepository->duplicate($product, $duplicate);
107127

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
namespace Magento\Catalog\Model\ResourceModel;
7+
8+
/**
9+
* DuplicatedProductAttributesCopier
10+
*
11+
* Is used to copy product attributes related to non-global scope
12+
* from source to target product during product duplication
13+
*/
14+
15+
use Magento\Catalog\Api\Data\ProductInterface;
16+
use Magento\Catalog\Model\Product;
17+
use Magento\Catalog\Model\ResourceModel\Product\Attribute\CollectionFactory;
18+
use Magento\Framework\App\ResourceConnection;
19+
use Magento\Framework\EntityManager\MetadataPool;
20+
use Magento\Store\Model\Store;
21+
22+
/**
23+
*
24+
*/
25+
class DuplicatedProductAttributesCopier
26+
{
27+
/**
28+
* @var MetadataPool
29+
*/
30+
private $metadataPool;
31+
32+
/**
33+
* @var CollectionFactory
34+
*/
35+
private $collectionFactory;
36+
37+
/**
38+
* @var ResourceConnection
39+
*/
40+
private $resource;
41+
42+
/**
43+
* @param MetadataPool $metadataPool
44+
* @param CollectionFactory $collectionFactory
45+
* @param ResourceConnection $resource
46+
*/
47+
public function __construct(
48+
MetadataPool $metadataPool,
49+
CollectionFactory $collectionFactory,
50+
ResourceConnection $resource
51+
) {
52+
$this->metadataPool = $metadataPool;
53+
$this->collectionFactory = $collectionFactory;
54+
$this->resource = $resource;
55+
}
56+
57+
/**
58+
* Copy non-global Product Attributes form source to target
59+
*
60+
* @param $source Product
61+
* @param $target Product
62+
* @return void
63+
*/
64+
public function copyProductAttributes(Product $source, Product $target): void
65+
{
66+
$metadata = $this->metadataPool->getMetadata(ProductInterface::class);
67+
$linkField = $metadata->getLinkField();
68+
$attributeCollection = $this->collectionFactory->create()
69+
->setAttributeSetFilter($source->getAttributeSetId())
70+
->addFieldToFilter('backend_type', ['neq' => 'static'])
71+
->addFieldToFilter('is_global', 0);
72+
73+
$eavTableNames = [];
74+
foreach ($attributeCollection->getItems() as $item) {
75+
/** @var \Magento\Catalog\Model\ResourceModel\Eav\Attribute $item */
76+
$eavTableNames[] = $item->getBackendTable();
77+
}
78+
79+
$connection = $this->resource->getConnection();
80+
foreach (array_unique($eavTableNames) as $eavTable) {
81+
$select = $connection->select()
82+
->from(
83+
['main_table' => $this->resource->getTableName($eavTable)],
84+
['attribute_id', 'store_id', 'value']
85+
)->where($linkField . ' = ?', $source->getData($linkField))
86+
->where('store_id <> ?', Store::DEFAULT_STORE_ID);
87+
$records = $connection->fetchAll($select);
88+
89+
if (!count($records)) {
90+
continue;
91+
}
92+
93+
foreach ($records as $index => $bind) {
94+
$bind[$linkField] = $target->getData($linkField);
95+
$records[$index] = $bind;
96+
}
97+
98+
$connection->insertMultiple($this->resource->getTableName($eavTable), $records);
99+
}
100+
}
101+
}

0 commit comments

Comments
 (0)