Skip to content

Commit 72f9180

Browse files
authored
Merge pull request #6263 from magento-tsg/2.4-develop-pr98
[Arrows] Fixes for 2.4 (pr98) (2.4-develop)
2 parents 9772213 + 9cd1d5b commit 72f9180

File tree

9 files changed

+383
-42
lines changed

9 files changed

+383
-42
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|null $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: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
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\Catalog\Api\CategoryAttributeRepositoryInterface 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+
use Magento\Store\Model\Store;
19+
20+
/**
21+
* Add data to category entity and populate with default values
22+
*/
23+
class PopulateWithValues
24+
{
25+
/**
26+
* @var ScopeOverriddenValue
27+
*/
28+
private $scopeOverriddenValue;
29+
30+
/**
31+
* @var AttributeRepository
32+
*/
33+
private $attributeRepository;
34+
35+
/**
36+
* @var SearchCriteriaBuilder
37+
*/
38+
private $searchCriteriaBuilder;
39+
40+
/**
41+
* @var FilterBuilder
42+
*/
43+
private $filterBuilder;
44+
45+
/**
46+
* @var AttributeInterface[]
47+
*/
48+
private $attributes;
49+
50+
/**
51+
* @param ScopeOverriddenValue $scopeOverriddenValue
52+
* @param AttributeRepository $attributeRepository
53+
* @param SearchCriteriaBuilder $searchCriteriaBuilder
54+
* @param FilterBuilder $filterBuilder
55+
*/
56+
public function __construct(
57+
ScopeOverriddenValue $scopeOverriddenValue,
58+
AttributeRepository $attributeRepository,
59+
SearchCriteriaBuilder $searchCriteriaBuilder,
60+
FilterBuilder $filterBuilder
61+
) {
62+
$this->scopeOverriddenValue = $scopeOverriddenValue;
63+
$this->attributeRepository = $attributeRepository;
64+
$this->searchCriteriaBuilder = $searchCriteriaBuilder;
65+
$this->filterBuilder = $filterBuilder;
66+
}
67+
68+
/**
69+
* Set null to entity default values
70+
*
71+
* @param CategoryInterface $category
72+
* @param array $existingData
73+
* @return void
74+
*/
75+
public function execute(CategoryInterface $category, array $existingData): void
76+
{
77+
$storeId = $existingData['store_id'] ?? Store::DEFAULT_STORE_ID;
78+
if ((int)$storeId !== Store::DEFAULT_STORE_ID) {
79+
$overriddenValues = array_filter(
80+
$category->getData(),
81+
function ($key) use ($category, $storeId) {
82+
/** @var Category $category */
83+
return $this->scopeOverriddenValue->containsValue(
84+
CategoryInterface::class,
85+
$category,
86+
$key,
87+
$storeId
88+
);
89+
},
90+
ARRAY_FILTER_USE_KEY
91+
);
92+
$defaultValues = array_diff_key($category->getData(), $overriddenValues);
93+
array_walk(
94+
$defaultValues,
95+
function (&$value, $key) {
96+
$attributes = $this->getAttributes();
97+
if (isset($attributes[$key]) && !$attributes[$key]->isStatic()) {
98+
$value = null;
99+
}
100+
}
101+
);
102+
$category->addData($defaultValues);
103+
}
104+
105+
$category->addData($existingData);
106+
$useDefaultAttributes = array_filter(
107+
$category->getData(),
108+
function ($attributeValue) {
109+
return null === $attributeValue;
110+
}
111+
);
112+
$category->setData(
113+
'use_default',
114+
array_map(
115+
function () {
116+
return true;
117+
},
118+
$useDefaultAttributes
119+
)
120+
);
121+
}
122+
123+
/**
124+
* Returns entity attributes.
125+
*
126+
* @return AttributeInterface[]
127+
*/
128+
private function getAttributes(): array
129+
{
130+
if ($this->attributes) {
131+
return $this->attributes;
132+
}
133+
134+
$searchResult = $this->attributeRepository->getList(
135+
$this->searchCriteriaBuilder->addFilters(
136+
[
137+
$this->filterBuilder
138+
->setField('is_global')
139+
->setConditionType('in')
140+
->setValue([ScopedAttributeInterface::SCOPE_STORE, ScopedAttributeInterface::SCOPE_WEBSITE])
141+
->create()
142+
]
143+
)->create()
144+
);
145+
146+
$this->attributes = [];
147+
foreach ($searchResult->getItems() as $attribute) {
148+
$this->attributes[$attribute->getAttributeCode()] = $attribute;
149+
}
150+
151+
return $this->attributes;
152+
}
153+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!--
3+
/**
4+
* Copyright © Magento, Inc. All rights reserved.
5+
* See COPYING.txt for license details.
6+
*/
7+
-->
8+
9+
<entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
10+
xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd">
11+
<entity name="NonexistentProduct" type="product">
12+
<data key="sku">NonexistentProductSku</data>
13+
<data key="qty">1</data>
14+
</entity>
15+
<entity name="SecondNonexistentProduct" type="product">
16+
<data key="sku">SecondNonexistentProductSku</data>
17+
<data key="qty">1</data>
18+
</entity>
19+
</entities>

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

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
use Magento\Catalog\Model\Category as CategoryModel;
1212
use Magento\Catalog\Model\CategoryFactory;
1313
use Magento\Catalog\Model\CategoryRepository;
14+
use Magento\Catalog\Model\CategoryRepository\PopulateWithValues;
1415
use Magento\Framework\Api\ExtensibleDataObjectConverter;
1516
use Magento\Framework\DataObject;
1617
use Magento\Framework\EntityManager\EntityMetadata;
@@ -63,6 +64,14 @@ class CategoryRepositoryTest extends TestCase
6364
*/
6465
protected $metadataPoolMock;
6566

67+
/**
68+
* @var PopulateWithValues|MockObject
69+
*/
70+
private $populateWithValuesMock;
71+
72+
/**
73+
* @inheridoc
74+
*/
6675
protected function setUp(): void
6776
{
6877
$this->categoryFactoryMock = $this->createPartialMock(
@@ -94,6 +103,12 @@ protected function setUp(): void
94103
->with(CategoryInterface::class)
95104
->willReturn($metadataMock);
96105

106+
$this->populateWithValuesMock = $this
107+
->getMockBuilder(PopulateWithValues::class)
108+
->onlyMethods(['execute'])
109+
->disableOriginalConstructor()
110+
->getMock();
111+
97112
$this->model = (new ObjectManager($this))->getObject(
98113
CategoryRepository::class,
99114
[
@@ -102,6 +117,7 @@ protected function setUp(): void
102117
'storeManager' => $this->storeManagerMock,
103118
'metadataPool' => $this->metadataPoolMock,
104119
'extensibleDataObjectConverter' => $this->extensibleDataObjectConverterMock,
120+
'populateWithValues' => $this->populateWithValuesMock,
105121
]
106122
);
107123
}
@@ -202,7 +218,7 @@ public function testFilterExtraFieldsOnUpdateCategory($categoryId, $categoryData
202218
->method('toNestedArray')
203219
->willReturn($categoryData);
204220
$categoryMock->expects($this->once())->method('validate')->willReturn(true);
205-
$categoryMock->expects($this->once())->method('addData')->with($dataForSave);
221+
$this->populateWithValuesMock->expects($this->once())->method('execute')->with($categoryMock, $dataForSave);
206222
$this->categoryResourceMock->expects($this->once())
207223
->method('save')
208224
->willReturn(DataObject::class);
@@ -230,11 +246,11 @@ public function testCreateNewCategory()
230246

231247
$categoryMock->expects($this->once())->method('getParentId')->willReturn($parentCategoryId);
232248
$parentCategoryMock->expects($this->once())->method('getPath')->willReturn('path');
233-
$categoryMock->expects($this->once())->method('addData')->with($dataForSave);
234249
$categoryMock->expects($this->once())->method('validate')->willReturn(true);
235250
$this->categoryResourceMock->expects($this->once())
236251
->method('save')
237252
->willReturn(DataObject::class);
253+
$this->populateWithValuesMock->expects($this->once())->method('execute')->with($categoryMock, $dataForSave);
238254
$this->assertEquals($categoryMock, $this->model->save($categoryMock));
239255
}
240256

app/code/Magento/Checkout/Test/Mftf/Section/StorefrontMinicartSection.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
<element name="emptyMiniCart" type="text" selector="//div[@class='minicart-wrapper']//span[@class='counter qty empty']/../.."/>
4040
<element name="minicartContent" type="block" selector="#minicart-content-wrapper"/>
4141
<element name="messageEmptyCart" type="text" selector="//*[@id='minicart-content-wrapper']//*[contains(@class,'subtitle empty')]"/>
42+
<element name="emptyCartMessageContent" type="text" selector="#minicart-content-wrapper .minicart.empty.text" timeout="30"/>
4243
<element name="visibleItemsCountText" type="text" selector="//div[@class='items-total']"/>
4344
<element name="productQuantity" type="input" selector="//*[@id='mini-cart']//a[contains(text(),'{{productName}}')]/../..//div[@class='details-qty qty']//input[@data-item-qty='{{qty}}']" parameterized="true"/>
4445
<element name="productImage" type="text" selector="//ol[@id='mini-cart']//img[@class='product-image-photo']"/>

0 commit comments

Comments
 (0)