Skip to content

Commit 66da081

Browse files
committed
Merge remote-tracking branch 'l3/MC-41422' into PR-L3-20210405
2 parents be82efb + 306f515 commit 66da081

File tree

2 files changed

+205
-1
lines changed

2 files changed

+205
-1
lines changed

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

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
use Magento\Catalog\Api\CategoryLinkManagementInterface;
1010
use Magento\Catalog\Api\Data\ProductExtension;
1111
use Magento\Catalog\Api\Data\ProductInterface;
12+
use Magento\Catalog\Model\Attribute\ScopeOverriddenValue;
1213
use Magento\Catalog\Model\Product\Gallery\MimeTypeExtensionMap;
1314
use Magento\Catalog\Model\ProductRepository\MediaGalleryProcessor;
1415
use Magento\Catalog\Model\ResourceModel\Product\Collection;
@@ -181,6 +182,11 @@ class ProductRepository implements \Magento\Catalog\Api\ProductRepositoryInterfa
181182
*/
182183
private $linkManagement;
183184

185+
/**
186+
* @var ScopeOverriddenValue
187+
*/
188+
private $scopeOverriddenValue;
189+
184190
/**
185191
* ProductRepository constructor.
186192
* @param ProductFactory $productFactory
@@ -208,6 +214,7 @@ class ProductRepository implements \Magento\Catalog\Api\ProductRepositoryInterfa
208214
* @param int $cacheLimit [optional]
209215
* @param ReadExtensions $readExtensions
210216
* @param CategoryLinkManagementInterface $linkManagement
217+
* @param ScopeOverriddenValue|null $scopeOverriddenValue
211218
* @SuppressWarnings(PHPMD.ExcessiveParameterList)
212219
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
213220
*/
@@ -236,7 +243,8 @@ public function __construct(
236243
\Magento\Framework\Serialize\Serializer\Json $serializer = null,
237244
$cacheLimit = 1000,
238245
ReadExtensions $readExtensions = null,
239-
CategoryLinkManagementInterface $linkManagement = null
246+
CategoryLinkManagementInterface $linkManagement = null,
247+
?ScopeOverriddenValue $scopeOverriddenValue = null
240248
) {
241249
$this->productFactory = $productFactory;
242250
$this->collectionFactory = $collectionFactory;
@@ -263,6 +271,8 @@ public function __construct(
263271
->get(ReadExtensions::class);
264272
$this->linkManagement = $linkManagement ?: \Magento\Framework\App\ObjectManager::getInstance()
265273
->get(CategoryLinkManagementInterface::class);
274+
$this->scopeOverriddenValue = $scopeOverriddenValue ?: \Magento\Framework\App\ObjectManager::getInstance()
275+
->get(ScopeOverriddenValue::class);
266276
}
267277

268278
/**
@@ -599,6 +609,12 @@ public function save(ProductInterface $product, $saveOptions = false)
599609
&& !$attribute->isStatic()
600610
&& !array_key_exists($attributeCode, $productDataToChange)
601611
&& $value !== null
612+
&& !$this->scopeOverriddenValue->containsValue(
613+
ProductInterface::class,
614+
$product,
615+
$attributeCode,
616+
$product->getStoreId()
617+
)
602618
) {
603619
$product->setData($attributeCode);
604620
$hasDataChanged = true;

dev/tests/api-functional/testsuite/Magento/Catalog/Api/ProductRepositoryMultiWebsiteTest.php

Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
namespace Magento\Catalog\Api;
99

1010
use Magento\Catalog\Api\Data\ProductInterface;
11+
use Magento\Eav\Model\Entity\Attribute\ScopedAttributeInterface;
1112
use Magento\Store\Model\Store;
1213
use Magento\TestFramework\Helper\Bootstrap;
1314
use Magento\TestFramework\TestCase\WebapiAbstract;
@@ -193,4 +194,191 @@ public function testProductDefaultValuesWithTwoWebsites(): void
193194
$storeId
194195
));
195196
}
197+
198+
/**
199+
* @magentoApiDataFixture Magento/Store/_files/second_website_with_two_stores.php
200+
* @magentoApiDataFixture Magento/Catalog/_files/product_text_attribute.php
201+
* @magentoApiDataFixture Magento/Catalog/_files/product_varchar_attribute.php
202+
* @SuppressWarnings(PHPMD.ExcessiveMethodLength)
203+
*/
204+
public function testPartialUpdate(): void
205+
{
206+
$this->_markTestAsRestOnly(
207+
'Test skipped due to known issue with SOAP. NULL value is cast to corresponding attribute type.'
208+
);
209+
$sku = 'api_test_update_product';
210+
$store = $this->objectManager->get(Store::class);
211+
$storeId = (int) $store->load('fixture_third_store', 'code')->getId();
212+
$this->updateAttribute('varchar_attribute', ['is_global' => ScopedAttributeInterface::SCOPE_STORE]);
213+
$this->updateAttribute('text_attribute', ['is_global' => ScopedAttributeInterface::SCOPE_STORE]);
214+
$request1 = [
215+
ProductInterface::SKU => $sku,
216+
ProductInterface::NAME => 'Test 1',
217+
ProductInterface::ATTRIBUTE_SET_ID => 4,
218+
ProductInterface::PRICE => 10,
219+
ProductInterface::STATUS => 1,
220+
ProductInterface::VISIBILITY => 4,
221+
ProductInterface::TYPE_ID => 'simple',
222+
ProductInterface::WEIGHT => 100,
223+
ProductInterface::CUSTOM_ATTRIBUTES => [
224+
[
225+
'attribute_code' => 'varchar_attribute',
226+
'value' => 'api_test_value_varchar',
227+
],
228+
[
229+
'attribute_code' => 'text_attribute',
230+
'value' => 'api_test_value_text',
231+
]
232+
],
233+
];
234+
$response = $this->saveProduct($request1, 'all');
235+
$this->assertResponse($request1, $response);
236+
$this->fixtureProducts[] = $sku;
237+
/** @var ProductRepositoryInterface $productRepository */
238+
$productRepository = $this->objectManager->get(ProductRepositoryInterface::class);
239+
$product = $productRepository->get($sku);
240+
$request2 = [
241+
ProductInterface::SKU => $sku,
242+
ProductInterface::CUSTOM_ATTRIBUTES => [
243+
[
244+
'attribute_code' => 'varchar_attribute',
245+
'value' => 'api_test_value_varchar_changed',
246+
]
247+
],
248+
];
249+
$response2 = $this->saveProduct($request2, 'fixture_third_store');
250+
$expected = [
251+
'varchar_attribute' => 'api_test_value_varchar_changed',
252+
'text_attribute' => 'api_test_value_text'
253+
];
254+
$this->assertResponse(
255+
array_merge($request1, $expected),
256+
$response2
257+
);
258+
$this->assertOverriddenValues(
259+
[
260+
'visibility' => false,
261+
'tax_class_id' => false,
262+
'status' => false,
263+
'name' => false,
264+
'varchar_attribute' => true,
265+
'text_attribute' => false,
266+
],
267+
$product,
268+
$storeId
269+
);
270+
$request3 = [
271+
ProductInterface::SKU => $sku,
272+
ProductInterface::CUSTOM_ATTRIBUTES => [
273+
[
274+
'attribute_code' => 'text_attribute',
275+
'value' => 'api_test_value_text_changed',
276+
]
277+
],
278+
];
279+
$response3 = $this->saveProduct($request3, 'fixture_third_store');
280+
$expected = [
281+
'varchar_attribute' => 'api_test_value_varchar_changed',
282+
'text_attribute' => 'api_test_value_text_changed'
283+
];
284+
$this->assertResponse(
285+
array_merge($request1, $expected),
286+
$response3
287+
);
288+
$this->assertOverriddenValues(
289+
[
290+
'visibility' => false,
291+
'tax_class_id' => false,
292+
'status' => false,
293+
'name' => false,
294+
'varchar_attribute' => true,
295+
'text_attribute' => true,
296+
],
297+
$product,
298+
$storeId
299+
);
300+
$request4 = [
301+
ProductInterface::SKU => $sku,
302+
ProductInterface::CUSTOM_ATTRIBUTES => [
303+
[
304+
'attribute_code' => 'text_attribute',
305+
'value' => null,
306+
]
307+
],
308+
];
309+
$response4 = $this->saveProduct($request4, 'fixture_third_store');
310+
$expected = [
311+
'varchar_attribute' => 'api_test_value_varchar_changed',
312+
'text_attribute' => 'api_test_value_text'
313+
];
314+
$this->assertResponse(
315+
array_merge($request1, $expected),
316+
$response4
317+
);
318+
$this->assertOverriddenValues(
319+
[
320+
'visibility' => false,
321+
'tax_class_id' => false,
322+
'status' => false,
323+
'name' => false,
324+
'varchar_attribute' => true,
325+
'text_attribute' => false,
326+
],
327+
$product,
328+
$storeId
329+
);
330+
}
331+
332+
/**
333+
* @param array $expected
334+
* @param array $actual
335+
*/
336+
private function assertResponse(array $expected, array $actual): void
337+
{
338+
$customAttributes = $expected[ProductInterface::CUSTOM_ATTRIBUTES] ?? [];
339+
unset($expected[ProductInterface::CUSTOM_ATTRIBUTES]);
340+
$expected = array_merge(array_column($customAttributes, 'value', 'attribute_code'), $expected);
341+
342+
$customAttributes = $actual[ProductInterface::CUSTOM_ATTRIBUTES] ?? [];
343+
unset($actual[ProductInterface::CUSTOM_ATTRIBUTES]);
344+
$actual = array_merge(array_column($customAttributes, 'value', 'attribute_code'), $actual);
345+
346+
$this->assertEquals($expected, array_intersect_key($actual, $expected));
347+
}
348+
349+
/**
350+
* @param Product $product
351+
* @param array $expected
352+
* @param int $storeId
353+
*/
354+
private function assertOverriddenValues(array $expected, Product $product, int $storeId): void
355+
{
356+
/** @var ScopeOverriddenValue $scopeOverriddenValue */
357+
$scopeOverriddenValue = $this->objectManager->create(ScopeOverriddenValue::class);
358+
$actual = [];
359+
foreach (array_keys($expected) as $attribute) {
360+
$actual[$attribute] = $scopeOverriddenValue->containsValue(
361+
ProductInterface::class,
362+
$product,
363+
$attribute,
364+
$storeId
365+
);
366+
}
367+
$this->assertEquals($expected, $actual);
368+
}
369+
370+
/**
371+
* Update attribute
372+
*
373+
* @param string $code
374+
* @param array $data
375+
* @return void
376+
*/
377+
private function updateAttribute(string $code, array $data): void
378+
{
379+
$attributeRepository = $this->objectManager->create(ProductAttributeRepositoryInterface::class);
380+
$attribute = $attributeRepository->get($code);
381+
$attribute->addData($data);
382+
$attributeRepository->save($attribute);
383+
}
196384
}

0 commit comments

Comments
 (0)