Skip to content

Commit 9608ca2

Browse files
Merge pull request #9562 from adobe-commerce-tier-4/pr_january_doleksandr
[Support Tier-4 odubovyk] 01-30-2025 Regular delivery of bugfixes and improvements
2 parents f309978 + ac1e538 commit 9608ca2

File tree

40 files changed

+1370
-404
lines changed

40 files changed

+1370
-404
lines changed

app/code/Magento/Catalog/Controller/Adminhtml/Product/Attribute/Save.php

+10-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
<?php
22
/**
3-
*
43
* Copyright 2024 Adobe
54
* All Rights Reserved.
65
*/
@@ -272,6 +271,16 @@ public function execute()
272271
if (array_key_exists('reset_is-default_option', $data) && $data['reset_is-default_option']) {
273272
unset($data['reset_is-default_option']);
274273
$data['default_value'] = null;
274+
} elseif (isset($data['default'])) {
275+
$defaultOptions = [];
276+
foreach ($data['default'] as $defaultValue) {
277+
if ((int)$defaultValue > 0) {
278+
$defaultOptions[] = $defaultValue;
279+
}
280+
}
281+
if (!empty($defaultOptions)) {
282+
$data['default_value'] = implode(",", $defaultOptions);
283+
}
275284
}
276285

277286
$model->addData($data);

app/code/Magento/Catalog/Test/Mftf/Section/AdminCreateNewProductAttributeSection.xml

+5-5
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
<?xml version="1.0" encoding="UTF-8"?>
22
<!--
3-
/**
4-
* Copyright © Magento, Inc. All rights reserved.
5-
* See COPYING.txt for license details.
6-
*/
3+
/**
4+
* Copyright 2019 Adobe
5+
* All Rights Reserved.
6+
*/
77
-->
88

99
<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
@@ -19,7 +19,7 @@
1919
<element name="secondOptionStoreView" type="input" selector="//td[@class='col-option_1'][3]/input"/>
2020
<element name="customStoreViewField" type="input" selector="input[value='{{fieldValue}}']" parameterized="true"/>
2121
<element name="adminOption" type="input" selector="//input[contains(@name,'option[value][option_{{row}}][0]')]" parameterized="true"/>
22-
<element name="defaultRadioButton" type="radio" selector="//tr[{{row}}]//input[contains(@name,'default[]')]" parameterized="true"/>
22+
<element name="defaultRadioButton" type="radio" selector="//tr[{{row}}]//input[contains(@name,'default[')]" parameterized="true"/>
2323
<element name="isRequired" type="checkbox" selector="//input[contains(@name,'is_required')]/..//label"/>
2424
<element name="advancedAttributeProperties" type="text" selector="//div[contains(@data-index,'advanced_fieldset')]"/>
2525
<element name="attributeCode" type="input" selector="//*[@class='admin__fieldset-wrapper-content admin__collapsible-content _show']//input[@name='attribute_code']"/>

app/code/Magento/Catalog/Test/Mftf/Test/CreateProductAttributeEntityTest/CreateProductAttributeEntityMultiSelectTest.xml

+6-6
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
<?xml version="1.0" encoding="UTF-8"?>
22
<!--
3-
/**
4-
* Copyright © Magento, Inc. All rights reserved.
5-
* See COPYING.txt for license details.
6-
*/
3+
/**
4+
* Copyright 2024 Adobe
5+
* All Rights Reserved.
6+
*/
77
-->
88
<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
99
xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd">
@@ -53,7 +53,7 @@
5353
<argument name="frontName" value="{{multiselectProductAttribute.option1_frontend}}"/>
5454
<argument name="row" value="1"/>
5555
</actionGroup>
56-
<actionGroup ref="CreateAttributeDropdownNthOptionActionGroup" stepKey="createOption2">
56+
<actionGroup ref="CreateAttributeDropdownNthOptionAsDefaultActionGroup" stepKey="createOption2">
5757
<argument name="adminName" value="{{multiselectProductAttribute.option2_admin}}"/>
5858
<argument name="frontName" value="{{multiselectProductAttribute.option2_frontend}}"/>
5959
<argument name="row" value="2"/>
@@ -80,7 +80,7 @@
8080
<dontSeeCheckboxIsChecked stepKey="dontSeeOption1Default" selector="{{AttributePropertiesSection.dropdownNthOptionIsDefault('1')}}"/>
8181
<seeInField stepKey="seeOption2Admin" selector="{{AttributePropertiesSection.dropdownNthOptionAdmin('2')}}" userInput="{{multiselectProductAttribute.option2_admin}}"/>
8282
<seeInField stepKey="seeOption2StoreView" selector="{{AttributePropertiesSection.dropdownNthOptionDefaultStoreView('2')}}" userInput="{{multiselectProductAttribute.option2_frontend}}"/>
83-
<dontSeeCheckboxIsChecked stepKey="dontSeeOption2Default" selector="{{AttributePropertiesSection.dropdownNthOptionIsDefault('2')}}"/>
83+
<seeCheckboxIsChecked stepKey="dontSeeOption2Default" selector="{{AttributePropertiesSection.dropdownNthOptionIsDefault('2')}}"/>
8484
<seeInField stepKey="seeOption3Admin" selector="{{AttributePropertiesSection.dropdownNthOptionAdmin('3')}}" userInput="{{multiselectProductAttribute.option3_admin}}"/>
8585
<seeInField stepKey="seeOption3StoreView" selector="{{AttributePropertiesSection.dropdownNthOptionDefaultStoreView('3')}}" userInput="{{multiselectProductAttribute.option3_frontend}}"/>
8686
<seeCheckboxIsChecked stepKey="seeOption3Default" selector="{{AttributePropertiesSection.dropdownNthOptionIsDefault('3')}}"/>

app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/attribute/js.phtml

+5-4
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
<?php
22
/**
3-
* Copyright © Magento, Inc. All rights reserved.
4-
* See COPYING.txt for license details.
3+
* Copyright 2024 Adobe
4+
* All Rights Reserved.
55
*/
66
use Magento\Catalog\Helper\Data;
77

88
/** @var \Magento\Backend\Block\Template $block */
99
/** @var \Magento\Framework\View\Helper\SecureHtmlRenderer $secureRenderer */
10+
/** @var $escaper \Magento\Framework\Escaper */
1011
?>
1112

1213
<?php
@@ -234,7 +235,7 @@ script;
234235
elseif ($one == '_scope'):
235236
$scriptString .= 'scopeVisibility = false;';
236237
else:
237-
$scriptString .= "setRowVisibility('" . $block->escapeJs($one) . "', false);";
238+
$scriptString .= "setRowVisibility('" . $escaper->escapeJs($one) . "', false);";
238239
endif;
239240
endforeach;
240241
$scriptString .= <<<script
@@ -259,7 +260,7 @@ script;
259260
setRowVisibility('default_value_yesno', defaultValueYesnoVisibility);
260261
setRowVisibility('is_global', scopeVisibility);
261262
262-
var elems = document.getElementsByName('default[]');
263+
var elems = document.querySelectorAll('input[name^="default["]');
263264
for (var i = 0; i < elems.length; i++) {
264265
elems[i].type = optionDefaultInputType;
265266
}

app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/attribute/options.phtml

+1-1
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ $stores = $block->getStoresSortedBySortOrder();
7878
<input data-role="order" type="hidden" name="option[order][<%- data.id %>]" value="<%- data.sort_order %>" <?php if ($block->getReadOnly() || $block->canManageOptionDefaultOnly()):?> disabled="disabled"<?php endif; ?>/>
7979
</td>
8080
<td class="col-default control-table-actions-cell">
81-
<input class="input-radio" type="<%- data.intype %>" name="default[]" value="<%- data.id %>" <%- data.checked %><?php if ($block->getReadOnly()):?>disabled="disabled"<?php endif;?>/>
81+
<input class="input-radio" type="<%- data.intype %>" name="default[<% if (data.intype === 'checkbox') { %><%- data.id %><% } %>]" value="<%- data.id %>" <%- data.checked %><?php if ($block->getReadOnly()):?>disabled="disabled"<?php endif;?>/>
8282
</td>
8383
<?php foreach ($stores as $_store):?>
8484
<td class="col-<%- data.id %>"><input name="option[value][<%- data.id %>][<?= (int) $_store->getId() ?>]" value="<%- data.store<?= /* @noEscape */ (int) $_store->getId() ?> %>" class="input-text<?php if ($_store->getId() == \Magento\Store\Model\Store::DEFAULT_STORE_ID):?> required-option required-unique<?php endif; ?>" type="text" <?php if ($block->getReadOnly() || $block->canManageOptionDefaultOnly()):?> disabled="disabled"<?php endif;?>/></td>

app/code/Magento/Catalog/view/adminhtml/web/js/options.js

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/**
2-
* Copyright © Magento, Inc. All rights reserved.
3-
* See COPYING.txt for license details.
2+
* Copyright 2024 Adobe
3+
* All Rights Reserved.
44
*/
55

66
/* eslint-disable no-undef */
@@ -95,7 +95,7 @@ define([
9595
}
9696
},
9797
reset: function () {
98-
jQuery('input[name="default[]"]').prop('checked', false);
98+
jQuery('input[name^="default["]').prop('checked', false);
9999
jQuery('input[name="reset_is-default_option"]').val(1);
100100
},
101101

app/code/Magento/ConfigurableProduct/Test/Mftf/Section/AdminNewAttributePanelSection.xml

+5-5
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
<?xml version="1.0" encoding="UTF-8"?>
22
<!--
3-
/**
4-
* Copyright © Magento, Inc. All rights reserved.
5-
* See COPYING.txt for license details.
6-
*/
3+
/**
4+
* Copyright 2018 Adobe
5+
* All Rights Reserved.
6+
*/
77
-->
88

99
<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
@@ -25,7 +25,7 @@
2525
<element name="inputType" type="select" selector="select[name='frontend_input']" timeout="30"/>
2626
<element name="valuesRequired" type="select" selector="select#is_required"/>
2727
<element name="addOption" type="button" selector="#add_new_option_button"/>
28-
<element name="isDefault" type="radio" selector="[data-role='options-container'] tr:nth-of-type({{row}}) input[name='default[]']" parameterized="true"/>
28+
<element name="isDefault" type="radio" selector="[data-role='options-container'] tr:nth-of-type({{row}}) input[name^='default[']" parameterized="true"/>
2929
<element name="optionAdminValue" type="input" selector="[data-role='options-container'] input[name='option[value][option_{{row}}][0]']" parameterized="true"/>
3030
<element name="optionDefaultStoreValue" type="input" selector="[data-role='options-container'] input[name='option[value][option_{{row}}][1]']" parameterized="true"/>
3131
<element name="deleteOption" type="button" selector="#delete_button_option_{{row}}" parameterized="true"/>

app/code/Magento/Eav/Model/ResourceModel/Entity/Attribute.php

+3-3
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 2024 Adobe
4+
* All Rights Reserved.
55
*/
66

77
namespace Magento\Eav\Model\ResourceModel\Entity;
@@ -400,7 +400,7 @@ protected function _saveOption(AbstractModel $object)
400400
}
401401

402402
if ($object->getDefaultValue()) {
403-
$defaultValue[] = $object->getDefaultValue();
403+
$defaultValue = array_unique(array_merge($defaultValue, explode(",", $object->getDefaultValue())));
404404
}
405405

406406
$this->_saveDefaultValue($object, $defaultValue);

app/code/Magento/GraphQl/Helper/Error/AggregateExceptionMessageFormatter.php

+7-6
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 2021 Adobe
4+
* All Rights Reserved.
55
*/
66
declare(strict_types=1);
77

@@ -23,7 +23,7 @@ class AggregateExceptionMessageFormatter
2323
/**
2424
* @var ExceptionMessageFormatterInterface[]
2525
*/
26-
private $messageFormatters;
26+
private array $messageFormatters;
2727

2828
/**
2929
* @param ExceptionMessageFormatterInterface[] $messageFormatters
@@ -54,11 +54,12 @@ public function getFormatted(
5454
ResolveInfo $info
5555
): ClientAware {
5656
foreach ($this->messageFormatters as $formatter) {
57-
$formatted = $formatter->getFormatted($e, $messagePrefix, $field, $context, $info);
58-
if ($formatted) {
57+
if ($formatted = $formatter->getFormatted($e, $messagePrefix, $field, $context, $info)) {
5958
return $formatted;
6059
}
6160
}
62-
return new GraphQlInputException($defaultMessage, $e);
61+
62+
$message = $e->getCode() ? __($e->getMessage()) : $defaultMessage;
63+
return new GraphQlInputException($message, $e, $e->getCode());
6364
}
6465
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
<?php
2+
/**
3+
* Copyright 2024 Adobe
4+
* All Rights Reserved.
5+
*/
6+
declare(strict_types=1);
7+
8+
namespace Magento\GraphQl\Test\Unit\Helper\Error;
9+
10+
use GraphQL\Error\ClientAware;
11+
use Magento\Framework\Exception\LocalizedException;
12+
use Magento\Framework\GraphQl\Config\Element\Field;
13+
use Magento\Framework\GraphQl\Exception\GraphQlInputException;
14+
use Magento\Framework\GraphQl\Query\Resolver\ContextInterface;
15+
use Magento\Framework\GraphQl\Schema\Type\ResolveInfo;
16+
use Magento\Framework\Phrase;
17+
use Magento\GraphQl\Helper\Error\AggregateExceptionMessageFormatter;
18+
use Magento\GraphQl\Helper\Error\ExceptionMessageFormatterInterface;
19+
use PHPUnit\Framework\MockObject\Exception;
20+
use PHPUnit\Framework\MockObject\MockObject;
21+
use PHPUnit\Framework\TestCase;
22+
23+
class AggregateExceptionMessageFormatterTest extends TestCase
24+
{
25+
/**
26+
* @var ExceptionMessageFormatterInterface|MockObject
27+
*/
28+
private ExceptionMessageFormatterInterface $formatter;
29+
30+
/**
31+
* @var LocalizedException|LocalizedException&MockObject|MockObject
32+
*/
33+
private LocalizedException $exception;
34+
35+
/**
36+
* @var Phrase|Phrase&MockObject|MockObject
37+
*/
38+
private Phrase $phrase;
39+
40+
/**
41+
* @var Field|Field&MockObject|MockObject
42+
*/
43+
private Field $field;
44+
45+
/**
46+
* @var ContextInterface|ContextInterface&MockObject|MockObject
47+
*/
48+
private ContextInterface $context;
49+
50+
/**
51+
* @var ResolveInfo|ResolveInfo&MockObject|MockObject
52+
*/
53+
private ResolveInfo $info;
54+
55+
/**
56+
* @return void
57+
* @throws Exception
58+
*/
59+
protected function setUp(): void
60+
{
61+
$this->formatter = $this->createMock(ExceptionMessageFormatterInterface::class);
62+
$this->exception = $this->createMock(LocalizedException::class);
63+
$this->phrase = $this->createMock(Phrase::class);
64+
$this->field = $this->createMock(Field::class);
65+
$this->context = $this->createMock(ContextInterface::class);
66+
$this->info = $this->createMock(ResolveInfo::class);
67+
68+
parent::setUp();
69+
}
70+
71+
/**
72+
* @return void
73+
* @throws Exception
74+
*/
75+
public function testGetFormattedUsingFormatter(): void
76+
{
77+
$clientAware = $this->createMock(ClientAware::class);
78+
$this->formatter->expects($this->once())
79+
->method('getFormatted')
80+
->willReturn($clientAware);
81+
$messagePrefix = 'prefix';
82+
83+
$aggregateFormatter = new AggregateExceptionMessageFormatter([$this->formatter]);
84+
$this->assertSame(
85+
$clientAware,
86+
$aggregateFormatter->getFormatted(
87+
$this->exception,
88+
$this->phrase,
89+
$messagePrefix,
90+
$this->field,
91+
$this->context,
92+
$this->info
93+
)
94+
);
95+
}
96+
97+
/**
98+
* @return void
99+
*/
100+
public function testGetFormattedExceptionMessage(): void
101+
{
102+
$exceptionCode = 1;
103+
$exceptionMessage = 'exception message';
104+
$messagePrefix = 'prefix';
105+
$this->formatter->expects($this->once())
106+
->method('getFormatted')
107+
->willReturn(null);
108+
$paramException = new LocalizedException(__($exceptionMessage), null, $exceptionCode);
109+
110+
$aggregateFormatter = new AggregateExceptionMessageFormatter([$this->formatter]);
111+
$exception = $aggregateFormatter->getFormatted(
112+
$paramException,
113+
$this->phrase,
114+
$messagePrefix,
115+
$this->field,
116+
$this->context,
117+
$this->info
118+
);
119+
$this->assertInstanceOf(GraphQlInputException::class, $exception);
120+
$this->assertSame($exceptionCode, $exception->getCode());
121+
$this->assertSame($exceptionMessage, $exception->getMessage());
122+
}
123+
124+
/**
125+
* @return void
126+
*/
127+
public function testGetFormattedDefaultMessage(): void
128+
{
129+
$exceptionMessage = 'exception message';
130+
$messagePrefix = 'prefix';
131+
$this->formatter->expects($this->once())
132+
->method('getFormatted')
133+
->willReturn(null);
134+
$paramException = new LocalizedException(__($exceptionMessage));
135+
136+
$this->phrase->expects($this->once())
137+
->method('render')
138+
->willReturn('prefix default');
139+
$aggregateFormatter = new AggregateExceptionMessageFormatter([$this->formatter]);
140+
$exception = $aggregateFormatter->getFormatted(
141+
$paramException,
142+
$this->phrase,
143+
$messagePrefix,
144+
$this->field,
145+
$this->context,
146+
$this->info
147+
);
148+
$this->assertInstanceOf(GraphQlInputException::class, $exception);
149+
$this->assertSame('prefix default', $exception->getMessage());
150+
}
151+
}

0 commit comments

Comments
 (0)