Skip to content

Commit 83b03a9

Browse files
committed
Merge remote-tracking branch 'origin/MC-39481' into 2.4-develop-pr120
2 parents 43a568e + 6a27b0c commit 83b03a9

File tree

3 files changed

+242
-4
lines changed

3 files changed

+242
-4
lines changed

app/code/Magento/Catalog/Model/ProductRepository.php

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@
2929
use Magento\Framework\Exception\StateException;
3030
use Magento\Framework\Exception\TemporaryState\CouldNotSaveException as TemporaryCouldNotSaveException;
3131
use Magento\Framework\Exception\ValidatorException;
32+
use Magento\Store\Model\Store;
33+
use Magento\Catalog\Api\Data\EavAttributeInterface;
3234

3335
/**
3436
* @inheritdoc
@@ -515,9 +517,12 @@ public function save(ProductInterface $product, $saveOptions = false)
515517
{
516518
$assignToCategories = false;
517519
$tierPrices = $product->getData('tier_price');
520+
$productDataToChange = $product->getData();
518521

519522
try {
520-
$existingProduct = $product->getId() ? $this->getById($product->getId()) : $this->get($product->getSku());
523+
$existingProduct = $product->getId() ?
524+
$this->getById($product->getId()) :
525+
$this->get($product->getSku());
521526

522527
$product->setData(
523528
$this->resourceModel->getLinkField(),
@@ -549,7 +554,6 @@ public function save(ProductInterface $product, $saveOptions = false)
549554
$productDataArray['store_id'] = (int) $this->storeManager->getStore()->getId();
550555
}
551556
$product = $this->initializeProductData($productDataArray, empty($existingProduct));
552-
553557
$this->processLinks($product, $productLinks);
554558
if (isset($productDataArray['media_gallery'])) {
555559
$this->processMediaGallery($product, $productDataArray['media_gallery']['images']);
@@ -570,6 +574,42 @@ public function save(ProductInterface $product, $saveOptions = false)
570574
$product->setData('tier_price', $tierPrices);
571575
}
572576

577+
try {
578+
$stores = $product->getStoreIds();
579+
$websites = $product->getWebsiteIds();
580+
} catch (NoSuchEntityException $exception) {
581+
$stores = null;
582+
$websites = null;
583+
}
584+
585+
if (!empty($existingProduct) && is_array($stores) && is_array($websites)) {
586+
$hasDataChanged = false;
587+
$productAttributes = $product->getAttributes();
588+
if ($productAttributes !== null
589+
&& $product->getStoreId() !== Store::DEFAULT_STORE_ID
590+
&& (count($stores) > 1 || count($websites) === 1)
591+
) {
592+
foreach ($productAttributes as $attribute) {
593+
$attributeCode = $attribute->getAttributeCode();
594+
$value = $product->getData($attributeCode);
595+
if ($existingProduct->getData($attributeCode) === $value
596+
&& $attribute->getScope() !== EavAttributeInterface::SCOPE_GLOBAL_TEXT
597+
&& !is_array($value)
598+
&& $attribute->getData('frontend_input') !== 'media_image'
599+
&& !$attribute->isStatic()
600+
&& !array_key_exists($attributeCode, $productDataToChange)
601+
&& $value !== null
602+
) {
603+
$product->setData($attributeCode);
604+
$hasDataChanged = true;
605+
}
606+
}
607+
if ($hasDataChanged) {
608+
$product->setData('_edit_mode', true);
609+
}
610+
}
611+
}
612+
573613
$this->saveProduct($product);
574614
if ($assignToCategories === true && $product->getCategoryIds()) {
575615
$this->linkManagement->assignProductToCategories(

app/code/Magento/Catalog/Test/Unit/Model/ProductRepositoryTest.php

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -220,7 +220,8 @@ protected function setUp(): void
220220
'getStoreId',
221221
'getMediaGalleryEntries',
222222
'getExtensionAttributes',
223-
'getCategoryIds'
223+
'getCategoryIds',
224+
'getAttributes'
224225
]
225226
)
226227
->disableOriginalConstructor()
@@ -243,7 +244,8 @@ protected function setUp(): void
243244
'save',
244245
'getMediaGalleryEntries',
245246
'getExtensionAttributes',
246-
'getCategoryIds'
247+
'getCategoryIds',
248+
'getAttributes'
247249
]
248250
)
249251
->disableOriginalConstructor()
Lines changed: 196 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
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\Catalog\Api;
9+
10+
use Magento\Catalog\Api\Data\ProductInterface;
11+
use Magento\Store\Model\Store;
12+
use Magento\TestFramework\Helper\Bootstrap;
13+
use Magento\TestFramework\TestCase\WebapiAbstract;
14+
use Magento\Catalog\Model\Attribute\ScopeOverriddenValue;
15+
use Magento\Catalog\Model\Product;
16+
use Magento\Framework\Webapi\Rest\Request;
17+
use Magento\Framework\ObjectManagerInterface;
18+
19+
/**
20+
* Test for \Magento\Catalog\Api\ProductRepositoryInterface
21+
*
22+
* @magentoAppIsolation enabled
23+
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
24+
* @SuppressWarnings(PHPMD.ExcessiveClassComplexity)
25+
*/
26+
class ProductRepositoryMultiWebsiteTest extends WebapiAbstract
27+
{
28+
const SERVICE_NAME = 'catalogProductRepositoryV1';
29+
const SERVICE_VERSION = 'V1';
30+
const RESOURCE_PATH = '/V1/products';
31+
32+
/**
33+
* @var array
34+
*/
35+
private $fixtureProducts = [];
36+
37+
/**
38+
* @var ObjectManagerInterface
39+
*/
40+
private $objectManager;
41+
42+
/**
43+
* @inheritDoc
44+
*/
45+
protected function setUp(): void
46+
{
47+
parent::setUp();
48+
$this->objectManager = Bootstrap::getObjectManager();
49+
}
50+
51+
/**
52+
* @inheritDoc
53+
*/
54+
protected function tearDown(): void
55+
{
56+
parent::tearDown();
57+
$this->deleteFixtureProducts();
58+
}
59+
60+
/**
61+
* Save Product
62+
*
63+
* @param array $product
64+
* @param string|null $storeCode
65+
* @param string|null $token
66+
* @return mixed
67+
*/
68+
private function saveProduct(array $product, ?string $storeCode = null, ?string $token = null)
69+
{
70+
if (isset($product['custom_attributes'])) {
71+
foreach ($product['custom_attributes'] as &$attribute) {
72+
if ($attribute['attribute_code'] == 'category_ids'
73+
&& !is_array($attribute['value'])
74+
) {
75+
$attribute['value'] = [""];
76+
}
77+
}
78+
}
79+
$serviceInfo = [
80+
'rest' => [
81+
'resourcePath' => self::RESOURCE_PATH,
82+
'httpMethod' => Request::HTTP_METHOD_POST,
83+
],
84+
'soap' => [
85+
'service' => self::SERVICE_NAME,
86+
'serviceVersion' => self::SERVICE_VERSION,
87+
'operation' => self::SERVICE_NAME . 'Save',
88+
],
89+
];
90+
if ($token) {
91+
$serviceInfo['rest']['token'] = $serviceInfo['soap']['token'] = $token;
92+
}
93+
$requestData = ['product' => $product];
94+
95+
return $this->_webApiCall($serviceInfo, $requestData, null, $storeCode);
96+
}
97+
98+
/**
99+
* Delete Product
100+
*
101+
* @param string $sku
102+
* @return boolean
103+
*/
104+
private function deleteProduct(string $sku) : bool
105+
{
106+
$serviceInfo = [
107+
'rest' => [
108+
'resourcePath' => self::RESOURCE_PATH . '/' . $sku,
109+
'httpMethod' => Request::HTTP_METHOD_DELETE,
110+
],
111+
'soap' => [
112+
'service' => self::SERVICE_NAME,
113+
'serviceVersion' => self::SERVICE_VERSION,
114+
'operation' => self::SERVICE_NAME . 'DeleteById',
115+
],
116+
];
117+
118+
return (TESTS_WEB_API_ADAPTER == self::ADAPTER_SOAP) ?
119+
$this->_webApiCall($serviceInfo, ['sku' => $sku]) : $this->_webApiCall($serviceInfo);
120+
}
121+
122+
/**
123+
* @return void
124+
*/
125+
private function deleteFixtureProducts(): void
126+
{
127+
foreach ($this->fixtureProducts as $sku) {
128+
$this->deleteProduct($sku);
129+
}
130+
$this->fixtureProducts = [];
131+
}
132+
133+
/**
134+
* Test that updating some values for product for specified store won't uncheck 'use default values'
135+
* for attributes which weren't changed
136+
*
137+
* @magentoApiDataFixture Magento/Store/_files/second_website_with_two_stores.php
138+
*/
139+
public function testProductDefaultValuesWithTwoWebsites(): void
140+
{
141+
$productData = [
142+
ProductInterface::SKU => 'test-1',
143+
ProductInterface::NAME => 'Test 1',
144+
ProductInterface::ATTRIBUTE_SET_ID => 4,
145+
ProductInterface::PRICE => 10,
146+
ProductInterface::STATUS => 1,
147+
ProductInterface::VISIBILITY => 4,
148+
ProductInterface::TYPE_ID => 'simple',
149+
ProductInterface::WEIGHT => 100,
150+
];
151+
152+
$response = $this->saveProduct($productData, 'all');
153+
154+
$this->assertEquals($response[ProductInterface::SKU], $productData[ProductInterface::SKU]);
155+
156+
$this->fixtureProducts[] = $productData[ProductInterface::SKU];
157+
158+
$productEditData = [
159+
ProductInterface::SKU => 'test-1',
160+
ProductInterface::NAME => 'Test 1 changed',
161+
];
162+
163+
$responseAfterEdit = $this->saveProduct($productEditData, 'fixture_third_store');
164+
165+
$this->assertEquals($productEditData[ProductInterface::NAME], $responseAfterEdit[ProductInterface::NAME]);
166+
167+
$store = $this->objectManager->get(Store::class);
168+
/** @var Product $model */
169+
$model = $this->objectManager->get(Product::class);
170+
$product = $model->load($model->getIdBySku($responseAfterEdit[ProductInterface::SKU]));
171+
172+
/** @var ScopeOverriddenValue $scopeOverriddenValue */
173+
$scopeOverriddenValue = $this->objectManager->get(ScopeOverriddenValue::class);
174+
$storeId = $store->load('fixture_third_store', 'code')->getId();
175+
$this->assertFalse($scopeOverriddenValue->containsValue(
176+
ProductInterface::class,
177+
$product,
178+
'visibility',
179+
$storeId
180+
));
181+
182+
$this->assertFalse($scopeOverriddenValue->containsValue(
183+
ProductInterface::class,
184+
$product,
185+
'tax_class_id',
186+
$storeId
187+
));
188+
189+
$this->assertFalse($scopeOverriddenValue->containsValue(
190+
ProductInterface::class,
191+
$product,
192+
'status',
193+
$storeId
194+
));
195+
}
196+
}

0 commit comments

Comments
 (0)