Skip to content

Commit fc47ae2

Browse files
author
Viktor Kopin
committed
MC-37665: Updating a category through the REST API will uncheck "Use Default Value" on a bunch of attributes
1 parent 3c3c8ed commit fc47ae2

File tree

3 files changed

+252
-25
lines changed

3 files changed

+252
-25
lines changed

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

Lines changed: 36 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,16 @@
77

88
namespace Magento\Catalog\Model;
99

10+
use Magento\Catalog\Model\CategoryRepository\PopulateWithValues;
11+
use Magento\Catalog\Model\ResourceModel\Category as CategoryResource;
12+
use Magento\Framework\Api\ExtensibleDataObjectConverter;
13+
use Magento\Framework\App\ObjectManager;
14+
use Magento\Framework\EntityManager\MetadataPool;
1015
use Magento\Framework\Exception\CouldNotSaveException;
1116
use Magento\Framework\Exception\NoSuchEntityException;
1217
use Magento\Framework\Exception\StateException;
1318
use Magento\Catalog\Api\Data\CategoryInterface;
19+
use Magento\Store\Model\StoreManagerInterface;
1420

1521
/**
1622
* Repository for categories.
@@ -25,27 +31,27 @@ class CategoryRepository implements \Magento\Catalog\Api\CategoryRepositoryInter
2531
protected $instances = [];
2632

2733
/**
28-
* @var \Magento\Store\Model\StoreManagerInterface
34+
* @var StoreManagerInterface
2935
*/
3036
protected $storeManager;
3137

3238
/**
33-
* @var \Magento\Catalog\Model\CategoryFactory
39+
* @var CategoryFactory
3440
*/
3541
protected $categoryFactory;
3642

3743
/**
38-
* @var \Magento\Catalog\Model\ResourceModel\Category
44+
* @var CategoryResource
3945
*/
4046
protected $categoryResource;
4147

4248
/**
43-
* @var \Magento\Framework\EntityManager\MetadataPool
49+
* @var MetadataPool
4450
*/
4551
protected $metadataPool;
4652

4753
/**
48-
* @var \Magento\Framework\Api\ExtensibleDataObjectConverter
54+
* @var ExtensibleDataObjectConverter
4955
*/
5056
private $extensibleDataObjectConverter;
5157

@@ -57,28 +63,37 @@ class CategoryRepository implements \Magento\Catalog\Api\CategoryRepositoryInter
5763
protected $useConfigFields = ['available_sort_by', 'default_sort_by', 'filter_price_range'];
5864

5965
/**
60-
* @param \Magento\Catalog\Model\CategoryFactory $categoryFactory
61-
* @param \Magento\Catalog\Model\ResourceModel\Category $categoryResource
62-
* @param \Magento\Store\Model\StoreManagerInterface $storeManager
66+
* @var PopulateWithValues
67+
*/
68+
private $populateWithValues;
69+
70+
/**
71+
* @param CategoryFactory $categoryFactory
72+
* @param CategoryResource $categoryResource
73+
* @param StoreManagerInterface $storeManager
74+
* @param PopulateWithValues $populateWithValues
6375
*/
6476
public function __construct(
65-
\Magento\Catalog\Model\CategoryFactory $categoryFactory,
66-
\Magento\Catalog\Model\ResourceModel\Category $categoryResource,
67-
\Magento\Store\Model\StoreManagerInterface $storeManager
77+
CategoryFactory $categoryFactory,
78+
CategoryResource $categoryResource,
79+
StoreManagerInterface $storeManager,
80+
PopulateWithValues $populateWithValues
6881
) {
6982
$this->categoryFactory = $categoryFactory;
7083
$this->categoryResource = $categoryResource;
7184
$this->storeManager = $storeManager;
85+
$objectManager = ObjectManager::getInstance();
86+
$this->populateWithValues = $populateWithValues ?? $objectManager->get(PopulateWithValues::class);
7287
}
7388

7489
/**
7590
* @inheritdoc
7691
*/
77-
public function save(\Magento\Catalog\Api\Data\CategoryInterface $category)
92+
public function save(CategoryInterface $category)
7893
{
7994
$storeId = (int)$this->storeManager->getStore()->getId();
8095
$existingData = $this->getExtensibleDataObjectConverter()
81-
->toNestedArray($category, [], \Magento\Catalog\Api\Data\CategoryInterface::class);
96+
->toNestedArray($category, [], CategoryInterface::class);
8297
$existingData = array_diff_key($existingData, array_flip(['path', 'level', 'parent_id']));
8398
$existingData['store_id'] = $storeId;
8499

@@ -110,7 +125,7 @@ public function save(\Magento\Catalog\Api\Data\CategoryInterface $category)
110125
$existingData['parent_id'] = $parentId;
111126
$existingData['level'] = null;
112127
}
113-
$category->addData($existingData);
128+
$this->populateWithValues->execute($category, $existingData);
114129
try {
115130
$this->validateCategory($category);
116131
$this->categoryResource->save($category);
@@ -151,7 +166,7 @@ public function get($categoryId, $storeId = null)
151166
/**
152167
* @inheritdoc
153168
*/
154-
public function delete(\Magento\Catalog\Api\Data\CategoryInterface $category)
169+
public function delete(CategoryInterface $category)
155170
{
156171
try {
157172
$categoryId = $category->getId();
@@ -213,29 +228,29 @@ protected function validateCategory(Category $category)
213228
/**
214229
* Lazy loader for the converter.
215230
*
216-
* @return \Magento\Framework\Api\ExtensibleDataObjectConverter
231+
* @return ExtensibleDataObjectConverter
217232
*
218233
* @deprecated 101.0.0
219234
*/
220235
private function getExtensibleDataObjectConverter()
221236
{
222237
if ($this->extensibleDataObjectConverter === null) {
223-
$this->extensibleDataObjectConverter = \Magento\Framework\App\ObjectManager::getInstance()
224-
->get(\Magento\Framework\Api\ExtensibleDataObjectConverter::class);
238+
$this->extensibleDataObjectConverter = ObjectManager::getInstance()
239+
->get(ExtensibleDataObjectConverter::class);
225240
}
226241
return $this->extensibleDataObjectConverter;
227242
}
228243

229244
/**
230245
* Lazy loader for the metadata pool.
231246
*
232-
* @return \Magento\Framework\EntityManager\MetadataPool
247+
* @return MetadataPool
233248
*/
234249
private function getMetadataPool()
235250
{
236251
if (null === $this->metadataPool) {
237-
$this->metadataPool = \Magento\Framework\App\ObjectManager::getInstance()
238-
->get(\Magento\Framework\EntityManager\MetadataPool::class);
252+
$this->metadataPool = ObjectManager::getInstance()
253+
->get(MetadataPool::class);
239254
}
240255
return $this->metadataPool;
241256
}
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
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\Model\CategoryRepository;
9+
10+
use Magento\Catalog\Api\Data\CategoryInterface;
11+
use Magento\Catalog\Model\Attribute\ScopeOverriddenValue;
12+
use Magento\Catalog\Model\Category;
13+
use Magento\Eav\Api\AttributeRepositoryInterface as AttributeRepository;
14+
use Magento\Eav\Api\Data\AttributeInterface;
15+
use Magento\Eav\Model\Entity\Attribute\ScopedAttributeInterface;
16+
use Magento\Framework\Api\FilterBuilder;
17+
use Magento\Framework\Api\SearchCriteriaBuilder;
18+
19+
/**
20+
* Add data to category entity and populate with default values
21+
*/
22+
class PopulateWithValues
23+
{
24+
/**
25+
* @var ScopeOverriddenValue
26+
*/
27+
private $scopeOverriddenValue;
28+
29+
/**
30+
* @var AttributeRepository
31+
*/
32+
private $attributeRepository;
33+
34+
/**
35+
* @var SearchCriteriaBuilder
36+
*/
37+
private $searchCriteriaBuilder;
38+
39+
/**
40+
* @var FilterBuilder
41+
*/
42+
private $filterBuilder;
43+
44+
/**
45+
* @param ScopeOverriddenValue $scopeOverriddenValue
46+
* @param AttributeRepository $attributeRepository
47+
* @param SearchCriteriaBuilder $searchCriteriaBuilder
48+
* @param FilterBuilder $filterBuilder
49+
*/
50+
public function __construct(
51+
ScopeOverriddenValue $scopeOverriddenValue,
52+
AttributeRepository $attributeRepository,
53+
SearchCriteriaBuilder $searchCriteriaBuilder,
54+
FilterBuilder $filterBuilder
55+
) {
56+
$this->scopeOverriddenValue = $scopeOverriddenValue;
57+
$this->attributeRepository = $attributeRepository;
58+
$this->searchCriteriaBuilder = $searchCriteriaBuilder;
59+
$this->filterBuilder = $filterBuilder;
60+
}
61+
62+
/**
63+
* Set null to entity default values
64+
*
65+
* @param CategoryInterface $category
66+
* @param array $existingData
67+
* @return void
68+
*/
69+
public function execute(CategoryInterface $category, array $existingData): void
70+
{
71+
$storeId = $existingData['store_id'];
72+
$overriddenValues = array_filter($category->getData(), function ($key) use ($category, $storeId) {
73+
/** @var Category $category */
74+
return $this->scopeOverriddenValue->containsValue(
75+
CategoryInterface::class,
76+
$category,
77+
$key,
78+
$storeId
79+
);
80+
}, ARRAY_FILTER_USE_KEY);
81+
$defaultValues = array_diff_key($category->getData(), $overriddenValues);
82+
array_walk($defaultValues, function (&$value, $key) {
83+
$attributes = $this->getAttributes();
84+
if (isset($attributes[$key]) && !$attributes[$key]->isStatic()) {
85+
$value = null;
86+
}
87+
});
88+
$category->addData($defaultValues);
89+
$category->addData($existingData);
90+
$useDefaultAttributes = array_filter($category->getData(), function ($attributeValue) {
91+
return null === $attributeValue;
92+
});
93+
$category->setData('use_default', array_map(function () {
94+
return true;
95+
}, $useDefaultAttributes));
96+
}
97+
98+
/**
99+
* Returns entity attributes.
100+
*
101+
* @return AttributeInterface[]
102+
*/
103+
private function getAttributes()
104+
{
105+
$searchResult = $this->attributeRepository->getList(
106+
$this->searchCriteriaBuilder->addFilters(
107+
[
108+
$this->filterBuilder
109+
->setField('is_global')
110+
->setConditionType('in')
111+
->setValue([ScopedAttributeInterface::SCOPE_STORE, ScopedAttributeInterface::SCOPE_WEBSITE])
112+
->create()
113+
]
114+
)->create()
115+
);
116+
$result = [];
117+
foreach ($searchResult->getItems() as $attribute) {
118+
$result[$attribute->getAttributeCode()] = $attribute;
119+
}
120+
121+
return $result;
122+
}
123+
}

0 commit comments

Comments
 (0)