Skip to content

Commit 41c0b4c

Browse files
Merge branch '2.4-develop' into ACQE-6392
2 parents 543774b + 7e0e558 commit 41c0b4c

File tree

20 files changed

+751
-87
lines changed

20 files changed

+751
-87
lines changed

app/code/Magento/Catalog/Test/Mftf/Test/EndToEndB2CAdminTest.xml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@
2222
<magentoCLI command="config:set checkout/options/enable_guest_checkout_login 1" stepKey="EnablingGuestCheckoutLogin"/>
2323
</before>
2424
<after>
25+
<actionGroup ref="AdminOpenProductIndexPageActionGroup" stepKey="goToProductList"/>
26+
<actionGroup ref="AdminClearGridFiltersActionGroup" stepKey="resetProductFilters"/>
2527
<actionGroup ref="AdminLogoutActionGroup" stepKey="logoutOfAdmin"/>
2628
<magentoCLI command="config:set checkout/options/enable_guest_checkout_login 0" stepKey="DisablingGuestCheckoutLogin"/>
2729
</after>
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
<?php
2+
/************************************************************************
3+
*
4+
* Copyright 2024 Adobe
5+
* All Rights Reserved.
6+
*
7+
* NOTICE: All information contained herein is, and remains
8+
* the property of Adobe and its suppliers, if any. The intellectual
9+
* and technical concepts contained herein are proprietary to Adobe
10+
* and its suppliers and are protected by all applicable intellectual
11+
* property laws, including trade secret and copyright laws.
12+
* Dissemination of this information or reproduction of this material
13+
* is strictly forbidden unless prior written permission is obtained
14+
* from Adobe.
15+
* ************************************************************************
16+
*/
17+
declare(strict_types=1);
18+
19+
namespace Magento\CatalogImportExport\Model\Import\Product;
20+
21+
use Magento\Catalog\Api\Data\ProductInterface;
22+
use Magento\CatalogImportExport\Model\Import\Product;
23+
use Magento\Eav\Model\Entity\Attribute\AbstractAttribute;
24+
use Magento\Framework\EntityManager\MetadataPool;
25+
use Magento\Framework\Exception\LocalizedException;
26+
27+
class UniqueAttributeValidator
28+
{
29+
/**
30+
* @var array
31+
*/
32+
private array $cache = [];
33+
34+
/**
35+
* @param MetadataPool $metadataPool
36+
* @param SkuStorage $skuStorage
37+
*/
38+
public function __construct(
39+
private readonly MetadataPool $metadataPool,
40+
private readonly SkuStorage $skuStorage
41+
) {
42+
}
43+
44+
/**
45+
* Check if provided value is unique for the attribute
46+
*
47+
* @param Product $context
48+
* @param string $attributeCode
49+
* @param string $sku
50+
* @param string $value
51+
* @return bool
52+
* @throws \Exception
53+
*/
54+
public function isValid(Product $context, string $attributeCode, string $sku, string $value): bool
55+
{
56+
$cacheKey = strtolower($attributeCode);
57+
if (!isset($this->cache[$cacheKey])) {
58+
$this->cache[$cacheKey] = $this->load($context, $attributeCode);
59+
}
60+
$entityData = $this->skuStorage->get($sku);
61+
$id = null;
62+
if ($entityData !== null) {
63+
$id = $entityData[$this->metadataPool->getMetadata(ProductInterface::class)->getLinkField()];
64+
}
65+
return !isset($this->cache[$cacheKey][$value]) || in_array($id, $this->cache[$cacheKey][$value]);
66+
}
67+
68+
/**
69+
* Load attribute values with corresponding entity ids
70+
*
71+
* @param Product $context
72+
* @param string $attributeCode
73+
* @return array
74+
* @throws LocalizedException
75+
*/
76+
private function load(Product $context, string $attributeCode): array
77+
{
78+
/** @var AbstractAttribute $attributeObject */
79+
$attributeObject = $context->retrieveAttributeByCode($attributeCode);
80+
if ($attributeObject->isStatic()) {
81+
return [];
82+
}
83+
$metadata = $this->metadataPool->getMetadata(ProductInterface::class);
84+
$connection = $context->getConnection();
85+
$idField = $metadata->getLinkField();
86+
$select = $connection->select()
87+
->from(
88+
$attributeObject->getBackend()->getTable(),
89+
['value', $idField]
90+
)
91+
->where(
92+
'attribute_id = :attribute_id'
93+
);
94+
$result = [];
95+
foreach ($connection->fetchAll($select, ['attribute_id' => $attributeObject->getId()]) as $row) {
96+
$result[$row['value']][] = $row[$idField];
97+
}
98+
return $result;
99+
}
100+
101+
/**
102+
* Clear cached attribute values
103+
*
104+
* @return void
105+
*/
106+
public function clearCache(): void
107+
{
108+
$this->cache = [];
109+
}
110+
}

app/code/Magento/CatalogImportExport/Model/Import/Product/Validator.php

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,11 @@ class Validator extends AbstractValidator implements RowValidatorInterface
5151
*/
5252
protected $invalidAttribute;
5353

54+
/**
55+
* @var UniqueAttributeValidator
56+
*/
57+
private $uniqueAttributeValidator;
58+
5459
/**
5560
* @var TimezoneInterface
5661
*/
@@ -60,16 +65,20 @@ class Validator extends AbstractValidator implements RowValidatorInterface
6065
* @param StringUtils $string
6166
* @param RowValidatorInterface[] $validators
6267
* @param TimezoneInterface|null $localeDate
68+
* @param UniqueAttributeValidator|null $uniqueAttributeValidator
6369
*/
6470
public function __construct(
6571
\Magento\Framework\Stdlib\StringUtils $string,
6672
$validators = [],
67-
?TimezoneInterface $localeDate = null
73+
?TimezoneInterface $localeDate = null,
74+
?UniqueAttributeValidator $uniqueAttributeValidator = null
6875
) {
6976
$this->string = $string;
7077
$this->validators = $validators;
7178
$this->localeDate = $localeDate ?: ObjectManager::getInstance()
7279
->get(TimezoneInterface::class);
80+
$this->uniqueAttributeValidator = $uniqueAttributeValidator
81+
?: ObjectManager::getInstance()->get(UniqueAttributeValidator::class);
7382
}
7483

7584
/**
@@ -242,7 +251,14 @@ public function isAttributeValid($attrCode, array $attrParams, array $rowData)
242251

243252
if ($valid && !empty($attrParams['is_unique'])) {
244253
if (isset($this->_uniqueAttributes[$attrCode][$rowData[$attrCode]])
245-
&& ($this->_uniqueAttributes[$attrCode][$rowData[$attrCode]] != $rowData[Product::COL_SKU])) {
254+
&& ($this->_uniqueAttributes[$attrCode][$rowData[$attrCode]] != $rowData[Product::COL_SKU])
255+
|| !$this->uniqueAttributeValidator->isValid(
256+
$this->context,
257+
(string) $attrCode,
258+
(string) $rowData[Product::COL_SKU],
259+
(string) $rowData[$attrCode]
260+
)
261+
) {
246262
$this->_addMessages([RowValidatorInterface::ERROR_DUPLICATE_UNIQUE_ATTRIBUTE]);
247263
return false;
248264
}
@@ -452,11 +468,23 @@ private function isCategoriesValid(string|array $value) : bool
452468
*/
453469
public function init($context)
454470
{
471+
$this->_uniqueAttributes = [];
472+
$this->uniqueAttributeValidator->clearCache();
455473
$this->context = $context;
456474
foreach ($this->validators as $validator) {
457475
$validator->init($context);
458476
}
459477

460478
return $this;
461479
}
480+
481+
/**
482+
* @inheritdoc
483+
*/
484+
public function _resetState(): void
485+
{
486+
$this->_uniqueAttributes = [];
487+
$this->uniqueAttributeValidator->clearCache();
488+
parent::_resetState();
489+
}
462490
}

app/code/Magento/CatalogImportExport/Test/Unit/Model/Import/Product/ValidatorTest.php

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
use Magento\CatalogImportExport\Model\Import\Product;
1111
use Magento\CatalogImportExport\Model\Import\Product\Type\Simple;
12+
use Magento\CatalogImportExport\Model\Import\Product\UniqueAttributeValidator;
1213
use Magento\CatalogImportExport\Model\Import\Product\Validator;
1314
use Magento\CatalogImportExport\Model\Import\Product\Validator\Media;
1415
use Magento\CatalogImportExport\Model\Import\Product\Validator\Website;
@@ -41,6 +42,11 @@ class ValidatorTest extends TestCase
4142
/** @var Validator\Website|MockObject */
4243
protected $validatorTwo;
4344

45+
/**
46+
* @var UniqueAttributeValidator|MockObject
47+
*/
48+
private $uniqueAttributeValidator;
49+
4450
protected function setUp(): void
4551
{
4652
$entityTypeModel = $this->createPartialMock(
@@ -63,6 +69,7 @@ protected function setUp(): void
6369
Website::class,
6470
['init', 'isValid', 'getMessages']
6571
);
72+
$this->uniqueAttributeValidator = $this->createMock(UniqueAttributeValidator::class);
6673

6774
$this->validators = [$this->validatorOne, $this->validatorTwo];
6875
$timezone = $this->createMock(\Magento\Framework\Stdlib\DateTime\TimezoneInterface::class);
@@ -77,7 +84,8 @@ function ($date = null) {
7784
$this->validator = new Validator(
7885
new StringUtils(),
7986
$this->validators,
80-
$timezone
87+
$timezone,
88+
$this->uniqueAttributeValidator
8189
);
8290
$this->validator->init($this->context);
8391
}
@@ -88,10 +96,18 @@ function ($date = null) {
8896
* @param array $rowData
8997
* @param bool $isValid
9098
* @param string $attrCode
99+
* @param bool $uniqueAttributeValidatorResult
91100
* @dataProvider attributeValidationProvider
92101
*/
93-
public function testAttributeValidation($behavior, $attrParams, $rowData, $isValid, $attrCode = 'attribute_code')
94-
{
102+
public function testAttributeValidation(
103+
string $behavior,
104+
array $attrParams,
105+
array $rowData,
106+
bool $isValid,
107+
string $attrCode = 'attribute_code',
108+
bool $uniqueAttributeValidatorResult = true
109+
) {
110+
$this->uniqueAttributeValidator->method('isValid')->willReturn($uniqueAttributeValidatorResult);
95111
$this->context->method('getMultipleValueSeparator')->willReturn(Product::PSEUDO_MULTI_LINE_SEPARATOR);
96112
$this->context->expects($this->any())->method('getBehavior')->willReturn($behavior);
97113
$result = $this->validator->isAttributeValid(
@@ -226,6 +242,14 @@ public function attributeValidationProvider()
226242
['product_type' => 'any', 'unique_attribute' => 'unique-value', Product::COL_SKU => 'sku-0'],
227243
true,
228244
'unique_attribute'
245+
],
246+
[
247+
Import::BEHAVIOR_APPEND,
248+
['is_required' => true, 'type' => 'varchar', 'is_unique' => true],
249+
['product_type' => 'any', 'unique_attribute' => 'unique-value', Product::COL_SKU => 'sku-0'],
250+
false,
251+
'unique_attribute',
252+
false
229253
]
230254
];
231255
}

app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminConfigurableProductDeleteTest/AdminConfigurableProductBulkDeleteTest.xml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,8 @@
125125
</before>
126126

127127
<after>
128+
<actionGroup ref="AdminOpenProductIndexPageActionGroup" stepKey="goToProductList"/>
129+
<actionGroup ref="AdminClearGridFiltersActionGroup" stepKey="resetProductFilters"/>
128130
<actionGroup ref="AdminLogoutActionGroup" stepKey="adminLogout"/>
129131
<deleteData createDataKey="createConfigChildProduct1" stepKey="deleteConfigChildProduct1"/>
130132
<deleteData createDataKey="createConfigChildProduct2" stepKey="deleteConfigChildProduct2"/>

app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminConfigurableProductDeleteTest/AdminConfigurableProductDeleteTest.xml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,8 @@
6666
</before>
6767

6868
<after>
69+
<actionGroup ref="AdminOpenProductIndexPageActionGroup" stepKey="goToProductList"/>
70+
<actionGroup ref="AdminClearGridFiltersActionGroup" stepKey="resetProductFilters"/>
6971
<actionGroup ref="AdminLogoutActionGroup" stepKey="adminLogout"/>
7072
<deleteData createDataKey="createConfigChildProduct1" stepKey="deleteConfigChildProduct1"/>
7173
<deleteData createDataKey="createConfigChildProduct2" stepKey="deleteConfigChildProduct2"/>

app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminConfigurableProductSearchTest/AdminConfigurableProductSearchTest.xml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,8 @@
6767
</before>
6868

6969
<after>
70+
<actionGroup ref="AdminOpenProductIndexPageActionGroup" stepKey="goToProductList"/>
71+
<actionGroup ref="AdminClearGridFiltersActionGroup" stepKey="resetProductFilters"/>
7072
<actionGroup ref="AdminLogoutActionGroup" stepKey="adminLogout"/>
7173
<deleteData createDataKey="createConfigProduct" stepKey="deleteConfigProduct"/>
7274
<deleteData createDataKey="createConfigChildProduct1" stepKey="deleteConfigChildProduct1"/>

app/code/Magento/Customer/Model/Customer.php

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -342,21 +342,25 @@ public function _construct()
342342
public function getDataModel()
343343
{
344344
$customerData = $this->getData();
345-
$addressesData = [];
345+
$regularAddresses = $defaultAddresses = [];
346346
/** @var \Magento\Customer\Model\Address $address */
347347
foreach ($this->getAddresses() as $address) {
348348
if (!isset($this->storedAddress[$address->getId()])) {
349349
$this->storedAddress[$address->getId()] = $address->getDataModel();
350350
}
351-
$addressesData[] = $this->storedAddress[$address->getId()];
351+
if ($this->storedAddress[$address->getId()]->isDefaultShipping()) {
352+
$defaultAddresses[] = $this->storedAddress[$address->getId()];
353+
} else {
354+
$regularAddresses[] = $this->storedAddress[$address->getId()];
355+
}
352356
}
353357
$customerDataObject = $this->customerDataFactory->create();
354358
$this->dataObjectHelper->populateWithArray(
355359
$customerDataObject,
356360
$customerData,
357361
\Magento\Customer\Api\Data\CustomerInterface::class
358362
);
359-
$customerDataObject->setAddresses($addressesData)
363+
$customerDataObject->setAddresses(array_merge($defaultAddresses, $regularAddresses))
360364
->setId($this->getId());
361365
return $customerDataObject;
362366
}

app/code/Magento/Customer/Test/Unit/Model/CustomerTest.php

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,18 @@
11
<?php declare(strict_types=1);
2-
/**
3-
* Unit test for customer service layer \Magento\Customer\Model\Customer
2+
/************************************************************************
3+
*
4+
* Copyright 2023 Adobe
5+
* All Rights Reserved.
46
*
5-
* Copyright © Magento, Inc. All rights reserved.
6-
* See COPYING.txt for license details.
7+
* NOTICE: All information contained herein is, and remains
8+
* the property of Adobe and its suppliers, if any. The intellectual
9+
* and technical concepts contained herein are proprietary to Adobe
10+
* and its suppliers and are protected by all applicable intellectual
11+
* property laws, including trade secret and copyright laws.
12+
* Dissemination of this information or reproduction of this material
13+
* is strictly forbidden unless prior written permission is obtained
14+
* from Adobe.
15+
* ************************************************************************
716
*/
817

918
/**
@@ -387,6 +396,7 @@ public function testGetDataModel()
387396
$this->_model->setEntityId($customerId);
388397
$this->_model->setId($customerId);
389398
$addressDataModel = $this->getMockForAbstractClass(AddressInterface::class);
399+
$addressDataModel->expects($this->exactly(4))->method('isDefaultShipping')->willReturn(true);
390400
$address = $this->getMockBuilder(AddressModel::class)
391401
->disableOriginalConstructor()
392402
->setMethods(['setCustomer', 'getDataModel'])

0 commit comments

Comments
 (0)