Skip to content

Commit 52893d4

Browse files
committed
Merge branch 'ACP2E-3660' of https://github.com/adobe-commerce-tier-4/magento2ce into PR-03-17-2025
2 parents 22ce951 + c140ff4 commit 52893d4

File tree

15 files changed

+2365
-1900
lines changed

15 files changed

+2365
-1900
lines changed
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
<?php
2+
/**
3+
* Copyright 2025 Adobe
4+
* All Rights Reserved.
5+
*/
6+
declare(strict_types=1);
7+
8+
namespace Magento\Catalog\Test\Unit\Ui\Component\Listing\Columns;
9+
10+
use Magento\Catalog\Ui\Component\Listing\Columns\ProductActions;
11+
use Magento\Framework\UrlInterface;
12+
use Magento\Framework\View\Element\UiComponent\ContextInterface;
13+
use Magento\Framework\View\Element\UiComponentFactory;
14+
use PHPUnit\Framework\MockObject\MockObject;
15+
use PHPUnit\Framework\TestCase;
16+
17+
class ProductActionsTest extends TestCase
18+
{
19+
/**
20+
* @var ContextInterface|MockObject
21+
*/
22+
private ContextInterface $context;
23+
24+
/**
25+
* @var UiComponentFactory|MockObject
26+
*/
27+
private UiComponentFactory $uiComponentFactory;
28+
29+
/**
30+
* @var UrlInterface|MockObject
31+
*/
32+
private UrlInterface $urlBuilder;
33+
34+
/**
35+
* @inheritdoc
36+
*/
37+
protected function setUp(): void
38+
{
39+
parent::setUp();
40+
41+
$this->context = $this->createMock(ContextInterface::class);
42+
$this->uiComponentFactory = $this->createMock(UiComponentFactory::class);
43+
$this->urlBuilder = $this->createMock(UrlInterface::class);
44+
}
45+
46+
/**
47+
* @return void
48+
*/
49+
public function testPrepareDataSource(): void
50+
{
51+
$storeId = 1;
52+
$data = [
53+
'name' => 'test'
54+
];
55+
$dataSource = [
56+
'data' => [
57+
'items' => [
58+
[
59+
'entity_id' => 1,
60+
],
61+
],
62+
],
63+
];
64+
65+
$this->context->expects($this->once())
66+
->method('getFilterParam')
67+
->with('store_id')
68+
->willReturn($storeId);
69+
70+
$this->urlBuilder->expects($this->once())
71+
->method('getUrl')
72+
->with('catalog/product/edit', ['id' => 1, 'store' => 1])
73+
->willReturn('http://example.com/catalog/product/edit?id=1&store=1');
74+
75+
$productActions = new ProductActions(
76+
$this->context,
77+
$this->uiComponentFactory,
78+
$this->urlBuilder,
79+
[],
80+
$data
81+
);
82+
83+
$result = $productActions->prepareDataSource($dataSource);
84+
85+
$this->assertEquals(
86+
[
87+
'data' => [
88+
'items' => [
89+
[
90+
'entity_id' => 1,
91+
'test' => [
92+
'edit' => [
93+
'href' => 'http://example.com/catalog/product/edit?id=1&store=1',
94+
'ariaLabel' => 'Edit ',
95+
'label' => __('Edit'),
96+
'hidden' => false
97+
],
98+
],
99+
],
100+
],
101+
],
102+
],
103+
$result
104+
);
105+
}
106+
}
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
<?php
2+
/**
3+
* Copyright 2025 Adobe
4+
* All Rights Reserved.
5+
*/
6+
declare(strict_types=1);
7+
8+
namespace Magento\Catalog\Test\Unit\Ui\Component\Listing\Columns;
9+
10+
use Magento\Catalog\Helper\Image;
11+
use Magento\Catalog\Ui\Component\Listing\Columns\Thumbnail;
12+
use Magento\Framework\UrlInterface;
13+
use Magento\Framework\View\Element\UiComponent\ContextInterface;
14+
use Magento\Framework\View\Element\UiComponentFactory;
15+
use PHPUnit\Framework\MockObject\MockObject;
16+
use PHPUnit\Framework\TestCase;
17+
18+
class ThumbnailTest extends TestCase
19+
{
20+
/**
21+
* @var ContextInterface|MockObject
22+
*/
23+
private ContextInterface $context;
24+
25+
/**
26+
* @var UiComponentFactory|MockObject
27+
*/
28+
private UiComponentFactory $uiComponentFactory;
29+
30+
/**
31+
* @var Image|MockObject
32+
*/
33+
private Image $imageHelper;
34+
35+
/**
36+
* @var UrlInterface|MockObject
37+
*/
38+
private UrlInterface $urlBuilder;
39+
40+
/**
41+
* @inheritdoc
42+
*/
43+
protected function setUp(): void
44+
{
45+
parent::setUp();
46+
47+
$this->context = $this->createMock(ContextInterface::class);
48+
$this->uiComponentFactory = $this->createMock(UiComponentFactory::class);
49+
$this->imageHelper = $this->createMock(Image::class);
50+
$this->urlBuilder = $this->createMock(UrlInterface::class);
51+
}
52+
53+
/**
54+
* @return void
55+
*/
56+
public function testPrepareDataSource(): void
57+
{
58+
$dataSource = [
59+
'data' => [
60+
'items' => [
61+
[
62+
'entity_id' => 1,
63+
],
64+
],
65+
],
66+
];
67+
$data = [
68+
'name' => 'test'
69+
];
70+
$storeId = 1;
71+
$this->context->expects($this->once())
72+
->method('getRequestParam')
73+
->with('store')
74+
->willReturn($storeId);
75+
$this->imageHelper->expects($this->exactly(2))->method('init')->willReturnSelf();
76+
$this->imageHelper->expects($this->exactly(2))->method('getUrl')->willReturn('http://example.com/images');
77+
$this->imageHelper->expects($this->once())->method('getLabel')->willReturn('label');
78+
$this->urlBuilder->expects($this->once())
79+
->method('getUrl')
80+
->with('catalog/product/edit', ['id' => 1, 'store' => 1])
81+
->willReturn('http://example.com/catalog/product/edit?id=1&store=1');
82+
$thumbnail = new Thumbnail(
83+
$this->context,
84+
$this->uiComponentFactory,
85+
$this->imageHelper,
86+
$this->urlBuilder,
87+
[],
88+
$data
89+
);
90+
91+
$result = $thumbnail->prepareDataSource($dataSource);
92+
$this->assertEquals(
93+
[
94+
'data' => [
95+
'items' => [
96+
[
97+
'entity_id' => 1,
98+
'test_src' => 'http://example.com/images',
99+
'test_alt' => 'label',
100+
'test_link' => 'http://example.com/catalog/product/edit?id=1&store=1',
101+
'test_orig_src' => 'http://example.com/images'
102+
]
103+
]
104+
]
105+
],
106+
$result
107+
);
108+
}
109+
}

app/code/Magento/Catalog/Ui/Component/Listing/Columns/ProductActions.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<?php
22
/**
3-
* Copyright © Magento, Inc. All rights reserved.
4-
* See COPYING.txt for license details.
3+
* Copyright 2015 Adobe
4+
* All Rights Reserved.
55
*/
66
namespace Magento\Catalog\Ui\Component\Listing\Columns;
77

@@ -58,7 +58,7 @@ public function prepareDataSource(array $dataSource)
5858
'catalog/product/edit',
5959
['id' => $item['entity_id'], 'store' => $storeId]
6060
),
61-
'ariaLabel' => __('Edit ') . $item['name'],
61+
'ariaLabel' => __('Edit ') . ($item['name'] ?? ''),
6262
'label' => __('Edit'),
6363
'hidden' => false,
6464
];

app/code/Magento/Catalog/Ui/Component/Listing/Columns/Thumbnail.php

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<?php
22
/**
3-
* Copyright © Magento, Inc. All rights reserved.
4-
* See COPYING.txt for license details.
3+
* Copyright 2015 Adobe
4+
* All Rights Reserved.
55
*/
66
namespace Magento\Catalog\Ui\Component\Listing\Columns;
77

@@ -87,6 +87,9 @@ public function prepareDataSource(array $dataSource)
8787
protected function getAlt($row)
8888
{
8989
$altField = $this->getData('config/altField') ?: self::ALT_FIELD;
90+
if (!isset($row[$altField])) {
91+
return null;
92+
}
9093
// phpcs:disable Magento2.Functions.DiscouragedFunction
9194
return html_entity_decode($row[$altField], ENT_QUOTES, "UTF-8") ?? null;
9295
}

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2708,7 +2708,9 @@ public function validateRow(array $rowData, $rowNum)
27082708
// if product doesn't exist, need to throw critical error else all errors should be not critical.
27092709
$errorLevel = $this->getValidationErrorLevel($sku);
27102710

2711-
if (!$this->validator->isValid($rowData)) {
2711+
$hasValidatedImportParent = $sku && $this->getNewSku($sku);
2712+
$contextRowData = array_merge(['has_import_parent' => $hasValidatedImportParent], $rowData);
2713+
if (!$this->validator->isValid($contextRowData)) {
27122714
foreach ($this->validator->getMessages() as $message) {
27132715
$this->skipRow($rowNum, $message, $errorLevel, $this->validator->getInvalidAttribute());
27142716
}

app/code/Magento/CatalogImportExport/Model/Import/Product/Type/AbstractType.php

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<?php
22
/**
3-
* Copyright © Magento, Inc. All rights reserved.
4-
* See COPYING.txt for license details.
3+
* Copyright 2011 Adobe
4+
* All Rights Reserved.
55
*/
66
namespace Magento\CatalogImportExport\Model\Import\Product\Type;
77

@@ -27,6 +27,8 @@
2727
*/
2828
abstract class AbstractType
2929
{
30+
private const NON_REQUIRED_ATTRIBUTES_EXISTING_PRODUCTS = [Product::COL_NAME];
31+
3032
/**
3133
* @var array
3234
*/
@@ -581,7 +583,9 @@ public function isRowValid(array $rowData, $rowNum, $isNewProduct = true)
581583
// For the default scope - if this is a new product or
582584
// for an old product, if the imported doc has the column present for the attrCode
583585
if (Product::SCOPE_DEFAULT == $rowScope &&
584-
($isNewProduct || array_key_exists($attrCode, $rowData))) {
586+
($isNewProduct || !in_array($attrCode, self::NON_REQUIRED_ATTRIBUTES_EXISTING_PRODUCTS)) &&
587+
array_key_exists($attrCode, $rowData)
588+
) {
585589
$this->_entityModel->addRowError(
586590
RowValidatorInterface::ERROR_VALUE_IS_REQUIRED,
587591
$rowNum,

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

Lines changed: 56 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<?php
22
/**
3-
* Copyright © Magento, Inc. All rights reserved.
4-
* See COPYING.txt for license details.
3+
* Copyright 2015 Adobe
4+
* All Rights Reserved.
55
*/
66
namespace Magento\CatalogImportExport\Model\Import\Product;
77

@@ -24,7 +24,7 @@ class Validator extends AbstractValidator implements RowValidatorInterface
2424
* Filter chain const
2525
*/
2626
private const FILTER_CHAIN = "php://filter";
27-
27+
2828
/**
2929
* @var RowValidatorInterface[]|AbstractValidator[]
3030
*/
@@ -378,6 +378,40 @@ public function getInvalidAttribute()
378378
return $this->invalidAttribute;
379379
}
380380

381+
/**
382+
* Validate attributes against configured properties
383+
*
384+
* @return array
385+
*/
386+
private function validateAttributes(): array
387+
{
388+
$this->_clearMessages();
389+
$this->setInvalidAttribute(null);
390+
$attributeValidationResult['result'] = true;
391+
392+
if (!isset($this->_rowData['product_type'])) {
393+
$attributeValidationResult['result'] = false;
394+
return $attributeValidationResult;
395+
}
396+
$entityTypeModel = $this->context->retrieveProductTypeByName($this->_rowData['product_type']);
397+
if ($entityTypeModel) {
398+
$result = true;
399+
foreach ($this->_rowData as $attrCode => $attrValue) {
400+
$attrParams = $entityTypeModel->retrieveAttributeFromCache($attrCode);
401+
if ($attrCode === Product::COL_CATEGORY && $attrValue) {
402+
$result = $this->isCategoriesValid($attrValue);
403+
} elseif ($attrParams) {
404+
$result = $this->isAttributeValid($attrCode, $attrParams, $this->_rowData);
405+
}
406+
$attributeValidationResult['attributes'][$attrCode] = $result;
407+
}
408+
if ($this->getMessages()) {
409+
$attributeValidationResult['result'] = false;
410+
}
411+
}
412+
return $attributeValidationResult;
413+
}
414+
381415
/**
382416
* Is valid attributes
383417
*
@@ -415,14 +449,30 @@ public function isValid($value)
415449
{
416450
$this->_rowData = $value;
417451
$this->_clearMessages();
418-
$returnValue = $this->isValidAttributes();
452+
$validatedAttributes = $this->validateAttributes();
453+
/** @var Product\Validator\AbstractImportValidator $validator */
419454
foreach ($this->validators as $validator) {
420455
if (!$validator->isValid($value)) {
421-
$returnValue = false;
422456
$this->_addMessages($validator->getMessages());
457+
} else {
458+
//prioritize specialized validation
459+
if ($validator->getFieldName() &&
460+
isset($validatedAttributes['attributes']) &&
461+
isset($validatedAttributes['attributes'][$validator->getFieldName()]) &&
462+
$validatedAttributes['attributes'][$validator->getFieldName()] === false
463+
) {
464+
$validatedAttributes['attributes'][$validator->getFieldName()] = true;
465+
foreach ($this->_messages as $key => $message) {
466+
if (str_contains($message, $validator->getFieldName())) {
467+
unset($this->_messages[$key]);
468+
}
469+
}
470+
$this->_messages = array_values($this->_messages);
471+
}
423472
}
424473
}
425-
return $returnValue;
474+
475+
return count($this->_messages) == 0;
426476
}
427477

428478
/**

0 commit comments

Comments
 (0)