Skip to content

Commit 9ab950d

Browse files
committed
Merge branch 'ACP2E-465' of https://github.com/magento-l3/magento2ce into PR-2022-02-15-CE2
2 parents df7c9e9 + 74c220a commit 9ab950d

File tree

4 files changed

+324
-21
lines changed

4 files changed

+324
-21
lines changed

app/code/Magento/Catalog/Model/Attribute/ScopeOverriddenValue.php

Lines changed: 68 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66

77
namespace Magento\Catalog\Model\Attribute;
88

9+
use Magento\Catalog\Model\AbstractModel;
10+
use Magento\Framework\DataObject;
911
use Magento\Framework\EntityManager\MetadataPool;
1012
use Magento\Eav\Api\AttributeRepositoryInterface as AttributeRepository;
1113
use Magento\Framework\Api\SearchCriteriaBuilder;
@@ -15,7 +17,6 @@
1517
use Magento\Framework\App\ResourceConnection;
1618

1719
/**
18-
* Class ScopeOverriddenValue
1920
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
2021
*/
2122
class ScopeOverriddenValue
@@ -76,7 +77,7 @@ public function __construct(
7677
* Whether attribute value is overridden in specific store
7778
*
7879
* @param string $entityType
79-
* @param \Magento\Catalog\Model\AbstractModel $entity
80+
* @param AbstractModel $entity
8081
* @param string $attributeCode
8182
* @param int|string $storeId
8283
* @return bool
@@ -86,39 +87,41 @@ public function containsValue($entityType, $entity, $attributeCode, $storeId)
8687
if ((int)$storeId === Store::DEFAULT_STORE_ID) {
8788
return false;
8889
}
89-
if (!isset($this->attributesValues[$storeId])) {
90+
$values = $this->getAttributesValues($entityType, $entity);
91+
92+
if (!isset($values[$storeId])) {
9093
$this->initAttributeValues($entityType, $entity, (int)$storeId);
94+
$values = $this->getAttributesValues($entityType, $entity);
9195
}
9296

93-
return isset($this->attributesValues[$storeId])
94-
&& array_key_exists($attributeCode, $this->attributesValues[$storeId]);
97+
return isset($values[$storeId]) && array_key_exists($attributeCode, $values[$storeId]);
9598
}
9699

97100
/**
98101
* Get attribute default values
99102
*
100103
* @param string $entityType
101-
* @param \Magento\Catalog\Model\AbstractModel $entity
104+
* @param AbstractModel $entity
102105
* @return array
103106
*
104107
* @deprecated 101.0.0
105108
*/
106109
public function getDefaultValues($entityType, $entity)
107110
{
108-
if ($this->attributesValues === null) {
111+
$values = $this->getAttributesValues($entityType, $entity);
112+
if (!isset($values[Store::DEFAULT_STORE_ID])) {
109113
$this->initAttributeValues($entityType, $entity, (int)$entity->getStoreId());
114+
$values = $this->getAttributesValues($entityType, $entity);
110115
}
111116

112-
return isset($this->attributesValues[Store::DEFAULT_STORE_ID])
113-
? $this->attributesValues[Store::DEFAULT_STORE_ID]
114-
: [];
117+
return $values[Store::DEFAULT_STORE_ID] ?? [];
115118
}
116119

117120
/**
118121
* Init attribute values.
119122
*
120123
* @param string $entityType
121-
* @param \Magento\Catalog\Model\AbstractModel $entity
124+
* @param AbstractModel $entity
122125
* @param int $storeId
123126
* @throws \Magento\Framework\Exception\LocalizedException
124127
* @return void
@@ -129,6 +132,7 @@ private function initAttributeValues($entityType, $entity, $storeId)
129132
/** @var \Magento\Eav\Model\Entity\Attribute\AbstractAttribute $attribute */
130133
$attributeTables = [];
131134
if ($metadata->getEavEntityType()) {
135+
$entityId = $entity->getData($metadata->getLinkField());
132136
foreach ($this->getAttributes($entityType) as $attribute) {
133137
if (!$attribute->isStatic()) {
134138
$attributeTables[$attribute->getBackend()->getTable()][] = $attribute->getAttributeId();
@@ -147,7 +151,7 @@ private function initAttributeValues($entityType, $entity, $storeId)
147151
'a.attribute_id = t.attribute_id',
148152
['attribute_code' => 'a.attribute_code']
149153
)
150-
->where($metadata->getLinkField() . ' = ?', $entity->getData($metadata->getLinkField()))
154+
->where($metadata->getLinkField() . ' = ?', $entityId)
151155
->where('t.attribute_id IN (?)', $attributeCodes)
152156
->where('t.store_id IN (?)', $storeIds);
153157
$selects[] = $select;
@@ -158,9 +162,12 @@ private function initAttributeValues($entityType, $entity, $storeId)
158162
\Magento\Framework\DB\Select::SQL_UNION_ALL
159163
);
160164
$attributes = $metadata->getEntityConnection()->fetchAll((string)$unionSelect);
165+
$values = array_fill_keys($storeIds, []);
161166
foreach ($attributes as $attribute) {
162-
$this->attributesValues[$attribute['store_id']][$attribute['attribute_code']] = $attribute['value'];
167+
$values[$attribute['store_id']][$attribute['attribute_code']] = $attribute['value'];
163168
}
169+
$values += $this->getAttributesValues($entityType, $entity);
170+
$this->setAttributesValues($entityType, $entity, $values);
164171
}
165172
}
166173

@@ -187,4 +194,52 @@ private function getAttributes($entityType)
187194
);
188195
return $searchResult->getItems();
189196
}
197+
198+
/**
199+
* Clear entity attributes values cache
200+
*
201+
* @param string $entityType
202+
* @param DataObject $entity
203+
* @return void
204+
* @throws \Exception
205+
*/
206+
public function clearAttributesValues(string $entityType, DataObject $entity): void
207+
{
208+
if (isset($this->attributesValues[$entityType])) {
209+
$metadata = $this->metadataPool->getMetadata($entityType);
210+
$entityId = $entity->getData($metadata->getLinkField());
211+
unset($this->attributesValues[$entityType][$entityId]);
212+
}
213+
}
214+
215+
/**
216+
* Get entity attributes values from cache
217+
*
218+
* @param string $entityType
219+
* @param DataObject $entity
220+
* @return array
221+
* @throws \Exception
222+
*/
223+
private function getAttributesValues(string $entityType, DataObject $entity): array
224+
{
225+
$metadata = $this->metadataPool->getMetadata($entityType);
226+
$entityId = $entity->getData($metadata->getLinkField());
227+
return $this->attributesValues[$entityType][$entityId] ?? [];
228+
}
229+
230+
/**
231+
* Set entity attributes values into cache
232+
*
233+
* @param string $entityType
234+
* @param DataObject $entity
235+
* @param array $values
236+
* @return void
237+
* @throws \Exception
238+
*/
239+
private function setAttributesValues(string $entityType, DataObject $entity, array $values): void
240+
{
241+
$metadata = $this->metadataPool->getMetadata($entityType);
242+
$entityId = $entity->getData($metadata->getLinkField());
243+
$this->attributesValues[$entityType][$entityId] = $values;
244+
}
190245
}

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

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
*/
66
namespace Magento\Catalog\Model\ResourceModel;
77

8+
use Magento\Catalog\Api\Data\ProductInterface;
9+
use Magento\Catalog\Model\Attribute\ScopeOverriddenValue;
810
use Magento\Catalog\Model\ResourceModel\Product\Website\Link as ProductWebsiteLink;
911
use Magento\Eav\Api\AttributeManagementInterface;
1012
use Magento\Framework\App\ObjectManager;
@@ -40,15 +42,11 @@ class Product extends AbstractResource
4042
protected $_productCategoryTable;
4143

4244
/**
43-
* Catalog category
44-
*
4545
* @var Category
4646
*/
4747
protected $_catalogCategory;
4848

4949
/**
50-
* Category collection factory
51-
*
5250
* @var Category\CollectionFactory
5351
*/
5452
protected $_categoryCollectionFactory;
@@ -105,6 +103,11 @@ class Product extends AbstractResource
105103
*/
106104
private $mediaImageDeleteProcessor;
107105

106+
/**
107+
* @var ScopeOverriddenValue
108+
*/
109+
private $scopeOverriddenValue;
110+
108111
/**
109112
* @param \Magento\Eav\Model\Entity\Context $context
110113
* @param \Magento\Store\Model\StoreManagerInterface $storeManager
@@ -120,6 +123,7 @@ class Product extends AbstractResource
120123
* @param UniqueValidationInterface|null $uniqueValidator
121124
* @param AttributeManagementInterface|null $eavAttributeManagement
122125
* @param MediaImageDeleteProcessor|null $mediaImageDeleteProcessor
126+
* @param ScopeOverriddenValue|null $scopeOverriddenValue
123127
* @SuppressWarnings(PHPMD.ExcessiveParameterList)
124128
*/
125129
public function __construct(
@@ -136,7 +140,8 @@ public function __construct(
136140
TableMaintainer $tableMaintainer = null,
137141
UniqueValidationInterface $uniqueValidator = null,
138142
AttributeManagementInterface $eavAttributeManagement = null,
139-
?MediaImageDeleteProcessor $mediaImageDeleteProcessor = null
143+
?MediaImageDeleteProcessor $mediaImageDeleteProcessor = null,
144+
?ScopeOverriddenValue $scopeOverriddenValue = null
140145
) {
141146
$this->_categoryCollectionFactory = $categoryCollectionFactory;
142147
$this->_catalogCategory = $catalogCategory;
@@ -157,6 +162,8 @@ public function __construct(
157162
?? ObjectManager::getInstance()->get(AttributeManagementInterface::class);
158163
$this->mediaImageDeleteProcessor = $mediaImageDeleteProcessor
159164
?? ObjectManager::getInstance()->get(MediaImageDeleteProcessor::class);
165+
$this->scopeOverriddenValue = $scopeOverriddenValue
166+
?? ObjectManager::getInstance()->get(ScopeOverriddenValue::class);
160167
}
161168

162169
/**
@@ -316,6 +323,7 @@ protected function _afterSave(DataObject $product)
316323
{
317324
$this->removeNotInSetAttributeValues($product);
318325
$this->_saveWebsiteIds($product)->_saveCategories($product);
326+
$this->scopeOverriddenValue->clearAttributesValues(ProductInterface::class, $product);
319327
return parent::_afterSave($product);
320328
}
321329

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

Lines changed: 92 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
use Magento\Catalog\Api\Data\ProductInterface;
1111
use Magento\Eav\Model\Entity\Attribute\ScopedAttributeInterface;
1212
use Magento\Store\Model\Store;
13+
use Magento\TestFramework\Fixture\DataFixtureStorage;
14+
use Magento\TestFramework\Fixture\DataFixtureStorageManager;
1315
use Magento\TestFramework\Helper\Bootstrap;
1416
use Magento\TestFramework\TestCase\WebapiAbstract;
1517
use Magento\Catalog\Model\Attribute\ScopeOverriddenValue;
@@ -26,9 +28,9 @@
2628
*/
2729
class ProductRepositoryMultiWebsiteTest extends WebapiAbstract
2830
{
29-
const SERVICE_NAME = 'catalogProductRepositoryV1';
30-
const SERVICE_VERSION = 'V1';
31-
const RESOURCE_PATH = '/V1/products';
31+
private const SERVICE_NAME = 'catalogProductRepositoryV1';
32+
private const SERVICE_VERSION = 'V1';
33+
private const RESOURCE_PATH = '/V1/products';
3234

3335
/**
3436
* @var array
@@ -40,13 +42,19 @@ class ProductRepositoryMultiWebsiteTest extends WebapiAbstract
4042
*/
4143
private $objectManager;
4244

45+
/**
46+
* @var DataFixtureStorage
47+
*/
48+
private $fixtures;
49+
4350
/**
4451
* @inheritDoc
4552
*/
4653
protected function setUp(): void
4754
{
4855
parent::setUp();
4956
$this->objectManager = Bootstrap::getObjectManager();
57+
$this->fixtures = $this->objectManager->get(DataFixtureStorageManager::class)->getStorage();
5058
}
5159

5260
/**
@@ -329,6 +337,52 @@ public function testPartialUpdate(): void
329337
);
330338
}
331339

340+
/**
341+
* @magentoApiDataFixture Magento\Store\Test\Fixture\Website as:website2
342+
* @magentoApiDataFixture Magento\Store\Test\Fixture\Group with:{"website_id":"$website2.id$"} as:store_group2
343+
* @magentoApiDataFixture Magento\Store\Test\Fixture\Store with:{"store_group_id":"$store_group2.id$"} as:store2
344+
* @magentoApiDataFixture Magento\Store\Test\Fixture\Store with:{"store_group_id":"$store_group2.id$"} as:store3
345+
* @magentoApiDataFixture Magento\Catalog\Test\Fixture\Product with:{"website_ids":[1,"$website2.id$"]} as:product
346+
* @magentoConfigFixture catalog/price/scope 1
347+
*/
348+
public function testUpdatePrice(): void
349+
{
350+
$store = $this->objectManager->get(Store::class);
351+
$defaultWebsiteStore1 = $store->load('default', 'code')->getCode();
352+
$secondWebsiteStore1 = $this->fixtures->get('store2')->getCode();
353+
$secondWebsiteStore2 = $this->fixtures->get('store3')->getCode();
354+
$sku = $this->fixtures->get('product')->getSku();
355+
356+
// change any attribute value in second store
357+
$request = [
358+
ProductInterface::SKU => $sku,
359+
'name' => 'updated product name for storeview'
360+
];
361+
$this->saveProduct($request, $secondWebsiteStore1);
362+
363+
// now update prices in second website
364+
$request = [
365+
ProductInterface::SKU => $sku,
366+
'price' => 9,
367+
ProductInterface::CUSTOM_ATTRIBUTES => [
368+
[
369+
'attribute_code' => 'special_price',
370+
'value' => 8,
371+
]
372+
],
373+
];
374+
$this->saveProduct($request, $secondWebsiteStore1);
375+
$defaultWebsiteStore1Response = $this->flattenCustomAttributes($this->getProduct($sku, $defaultWebsiteStore1));
376+
$this->assertEquals(10, $defaultWebsiteStore1Response['price']);
377+
$this->assertArrayNotHasKey('special_price', $defaultWebsiteStore1Response);
378+
$secondWebsiteStore1Response = $this->flattenCustomAttributes($this->getProduct($sku, $secondWebsiteStore1));
379+
$this->assertEquals(9, $secondWebsiteStore1Response['price']);
380+
$this->assertEquals(8, $secondWebsiteStore1Response['special_price']);
381+
$secondWebsiteStore2Response = $this->flattenCustomAttributes($this->getProduct($sku, $secondWebsiteStore2));
382+
$this->assertEquals(9, $secondWebsiteStore2Response['price']);
383+
$this->assertEquals(8, $secondWebsiteStore2Response['special_price']);
384+
}
385+
332386
/**
333387
* @param array $expected
334388
* @param array $actual
@@ -381,4 +435,39 @@ private function updateAttribute(string $code, array $data): void
381435
$attribute->addData($data);
382436
$attributeRepository->save($attribute);
383437
}
438+
439+
/**
440+
* @param array $data
441+
* @return array
442+
*/
443+
private function flattenCustomAttributes(array $data): array
444+
{
445+
$customAttributes = $data[ProductInterface::CUSTOM_ATTRIBUTES] ?? [];
446+
unset($data[ProductInterface::CUSTOM_ATTRIBUTES]);
447+
return array_merge(array_column($customAttributes, 'value', 'attribute_code'), $data);
448+
}
449+
450+
/**
451+
* Get product
452+
*
453+
* @param string $sku
454+
* @param string|null $storeCode
455+
* @return array|bool|float|int|string
456+
*/
457+
private function getProduct($sku, $storeCode = null)
458+
{
459+
$serviceInfo = [
460+
'rest' => [
461+
'resourcePath' => self::RESOURCE_PATH . '/' . $sku,
462+
'httpMethod' => Request::HTTP_METHOD_GET,
463+
],
464+
'soap' => [
465+
'service' => self::SERVICE_NAME,
466+
'serviceVersion' => self::SERVICE_VERSION,
467+
'operation' => self::SERVICE_NAME . 'Get',
468+
],
469+
];
470+
471+
return $this->_webApiCall($serviceInfo, ['sku' => $sku], null, $storeCode);
472+
}
384473
}

0 commit comments

Comments
 (0)