Skip to content

Commit 7cb18be

Browse files
Merge branch '2.4-develop' into ACPT-961
2 parents a930687 + bd8634b commit 7cb18be

File tree

17 files changed

+439
-68
lines changed

17 files changed

+439
-68
lines changed

app/code/Magento/Catalog/Model/Product/Attribute/Repository.php

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
namespace Magento\Catalog\Model\Product\Attribute;
88

99
use Laminas\Validator\Regex;
10-
use Magento\Eav\Api\Data\AttributeInterface;
10+
use Magento\Catalog\Api\Data\EavAttributeInterface;
1111
use Magento\Eav\Model\Entity\Attribute;
1212
use Magento\Framework\Exception\InputException;
1313
use Magento\Framework\Exception\NoSuchEntityException;
@@ -19,6 +19,8 @@
1919
*/
2020
class Repository implements \Magento\Catalog\Api\ProductAttributeRepositoryInterface
2121
{
22+
private const FILTERABLE_ALLOWED_INPUT_TYPES = ['date', 'datetime', 'text', 'textarea', 'texteditor'];
23+
2224
/**
2325
* @var \Magento\Catalog\Model\ResourceModel\Attribute
2426
*/
@@ -110,6 +112,22 @@ public function getList(\Magento\Framework\Api\SearchCriteriaInterface $searchCr
110112
*/
111113
public function save(\Magento\Catalog\Api\Data\ProductAttributeInterface $attribute)
112114
{
115+
if (in_array($attribute->getFrontendInput(), self::FILTERABLE_ALLOWED_INPUT_TYPES)) {
116+
if ($attribute->getIsFilterable()) {
117+
throw InputException::invalidFieldValue(
118+
EavAttributeInterface::IS_FILTERABLE,
119+
$attribute->getIsFilterable()
120+
);
121+
}
122+
123+
if ($attribute->getIsFilterableInSearch()) {
124+
throw InputException::invalidFieldValue(
125+
EavAttributeInterface::IS_FILTERABLE_IN_SEARCH,
126+
$attribute->getIsFilterableInSearch()
127+
);
128+
}
129+
}
130+
113131
$attribute->setEntityTypeId(
114132
$this->eavConfig
115133
->getEntityType(\Magento\Catalog\Api\Data\ProductAttributeInterface::ENTITY_TYPE_CODE)
@@ -156,7 +174,7 @@ public function save(\Magento\Catalog\Api\Data\ProductAttributeInterface $attrib
156174
);
157175
$attribute->setIsUserDefined(1);
158176
}
159-
if (!empty($attribute->getData(AttributeInterface::OPTIONS))) {
177+
if (!empty($attribute->getData(EavAttributeInterface::OPTIONS))) {
160178
$options = [];
161179
$sortOrder = 0;
162180
$default = [];

app/code/Magento/Catalog/Test/Mftf/Data/ProductAttributeData.xml

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,8 @@
2020
<data key="is_wysiwyg_enabled">true</data>
2121
<data key="is_visible_in_advanced_search">true</data>
2222
<data key="is_visible_on_front">true</data>
23-
<data key="is_filterable">true</data>
24-
<data key="is_filterable_in_search">true</data>
23+
<data key="is_filterable">false</data>
24+
<data key="is_filterable_in_search">false</data>
2525
<data key="used_in_product_listing">true</data>
2626
<data key="is_used_for_promo_rules">true</data>
2727
<data key="is_comparable">true</data>
@@ -227,8 +227,8 @@
227227
<data key="is_visible">true</data>
228228
<data key="is_visible_in_advanced_search">true</data>
229229
<data key="is_visible_on_front">true</data>
230-
<data key="is_filterable">true</data>
231-
<data key="is_filterable_in_search">true</data>
230+
<data key="is_filterable">false</data>
231+
<data key="is_filterable_in_search">false</data>
232232
<data key="used_in_product_listing">true</data>
233233
<data key="is_used_for_promo_rules">true</data>
234234
<data key="is_comparable">true</data>
@@ -356,8 +356,8 @@
356356
<data key="is_wysiwyg_enabled">true</data>
357357
<data key="is_visible_in_advanced_search">true</data>
358358
<data key="is_visible_on_front">true</data>
359-
<data key="is_filterable">true</data>
360-
<data key="is_filterable_in_search">true</data>
359+
<data key="is_filterable">false</data>
360+
<data key="is_filterable_in_search">false</data>
361361
<data key="used_in_product_listing">true</data>
362362
<data key="is_used_for_promo_rules">true</data>
363363
<data key="is_comparable">true</data>

app/code/Magento/Catalog/Test/Unit/Model/Product/Attribute/RepositoryTest.php

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,42 @@ public function testSaveInputExceptionRequiredField()
241241
$this->model->save($attributeMock);
242242
}
243243

244+
/**
245+
* @param string $field
246+
* @param string $method
247+
* @param bool $filterable
248+
*
249+
* @return void
250+
* @dataProvider filterableDataProvider
251+
*/
252+
public function testSaveInputExceptionInvalidIsFilterableFieldValue(
253+
string $field,
254+
string $method,
255+
bool $filterable
256+
) : void {
257+
$this->expectException('Magento\Framework\Exception\InputException');
258+
$this->expectExceptionMessage('Invalid value of "'.$filterable.'" provided for the '.$field.' field.');
259+
$attributeMock = $this->createPartialMock(
260+
Attribute::class,
261+
['getFrontendInput', $method]
262+
);
263+
$attributeMock->expects($this->atLeastOnce())->method('getFrontendInput')->willReturn('text');
264+
$attributeMock->expects($this->atLeastOnce())->method($method)->willReturn($filterable);
265+
266+
$this->model->save($attributeMock);
267+
}
268+
269+
/**
270+
* @return array
271+
*/
272+
public function filterableDataProvider(): array
273+
{
274+
return [
275+
[ProductAttributeInterface::IS_FILTERABLE, 'getIsFilterable', true],
276+
[ProductAttributeInterface::IS_FILTERABLE_IN_SEARCH, 'getIsFilterableInSearch', true]
277+
];
278+
}
279+
244280
public function testSaveInputExceptionInvalidFieldValue()
245281
{
246282
$this->expectException('Magento\Framework\Exception\InputException');

app/code/Magento/CatalogGraphQl/Model/Resolver/Aggregations.php

Lines changed: 37 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,6 @@
2121
*/
2222
class Aggregations implements ResolverInterface
2323
{
24-
/**
25-
* @var Layer\DataProvider\Filters
26-
*/
27-
private $filtersDataProvider;
28-
2924
/**
3025
* @var LayerBuilder
3126
*/
@@ -42,18 +37,15 @@ class Aggregations implements ResolverInterface
4237
private $includeDirectChildrenOnly;
4338

4439
/**
45-
* @param \Magento\CatalogGraphQl\Model\Resolver\Layer\DataProvider\Filters $filtersDataProvider
4640
* @param LayerBuilder $layerBuilder
4741
* @param PriceCurrency $priceCurrency
4842
* @param Category\IncludeDirectChildrenOnly $includeDirectChildrenOnly
4943
*/
5044
public function __construct(
51-
\Magento\CatalogGraphQl\Model\Resolver\Layer\DataProvider\Filters $filtersDataProvider,
5245
LayerBuilder $layerBuilder,
5346
PriceCurrency $priceCurrency = null,
5447
Category\IncludeDirectChildrenOnly $includeDirectChildrenOnly = null
5548
) {
56-
$this->filtersDataProvider = $filtersDataProvider;
5749
$this->layerBuilder = $layerBuilder;
5850
$this->priceCurrency = $priceCurrency ?: ObjectManager::getInstance()->get(PriceCurrency::class);
5951
$this->includeDirectChildrenOnly = $includeDirectChildrenOnly
@@ -75,30 +67,45 @@ public function resolve(
7567
}
7668

7769
$aggregations = $value['search_result']->getSearchAggregation();
70+
if (!$aggregations || (int)$value['total_count'] == 0) {
71+
return [];
72+
}
7873

79-
if ($aggregations) {
80-
$categoryFilter = $value['categories'] ?? [];
81-
$includeDirectChildrenOnly = $args['filter']['category']['includeDirectChildrenOnly'] ?? false;
82-
if ($includeDirectChildrenOnly && !empty($categoryFilter)) {
83-
$this->includeDirectChildrenOnly->setFilter(['category' => $categoryFilter]);
84-
}
85-
/** @var StoreInterface $store */
86-
$store = $context->getExtensionAttributes()->getStore();
87-
$storeId = (int)$store->getId();
88-
$results = $this->layerBuilder->build($aggregations, $storeId);
89-
if (isset($results['price_bucket'])) {
90-
foreach ($results['price_bucket']['options'] as &$value) {
91-
list($from, $to) = explode('-', $value['label']);
92-
$newLabel = $this->priceCurrency->convertAndRound($from)
93-
. '-'
94-
. $this->priceCurrency->convertAndRound($to);
95-
$value['label'] = $newLabel;
96-
$value['value'] = str_replace('-', '_', $newLabel);
97-
}
98-
}
74+
$categoryFilter = $value['categories'] ?? [];
75+
$includeDirectChildrenOnly = $args['filter']['category']['includeDirectChildrenOnly'] ?? false;
76+
if ($includeDirectChildrenOnly && !empty($categoryFilter)) {
77+
$this->includeDirectChildrenOnly->setFilter(['category' => $categoryFilter]);
78+
}
79+
80+
$results = $this->layerBuilder->build(
81+
$aggregations,
82+
(int)$context->getExtensionAttributes()->getStore()->getId()
83+
);
84+
if (!isset($results['price_bucket']['options'])) {
9985
return $results;
100-
} else {
101-
return [];
10286
}
87+
88+
$priceBucketOptions = [];
89+
foreach ($results['price_bucket']['options'] as $optionValue) {
90+
$priceBucketOptions[] = $this->getConvertedAndRoundedOptionValue($optionValue);
91+
}
92+
$results['price_bucket']['options'] = $priceBucketOptions;
93+
94+
return $results;
95+
}
96+
97+
/**
98+
* Converts and rounds option value
99+
*
100+
* @param String[] $optionValue
101+
* @return String[]
102+
*/
103+
private function getConvertedAndRoundedOptionValue(array $optionValue): array
104+
{
105+
list($from, $to) = explode('-', $optionValue['label']);
106+
$newLabel = $this->priceCurrency->convertAndRound($from) . '-' . $this->priceCurrency->convertAndRound($to);
107+
$optionValue['label'] = $newLabel;
108+
$optionValue['value'] = str_replace('-', '_', $newLabel);
109+
return $optionValue;
103110
}
104111
}

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

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1086,7 +1086,7 @@ protected function collectRawData()
10861086

10871087
if ($storeId != Store::DEFAULT_STORE_ID
10881088
&& isset($data[$itemId][Store::DEFAULT_STORE_ID][$fieldName])
1089-
&& $data[$itemId][Store::DEFAULT_STORE_ID][$fieldName] == htmlspecialchars_decode($attrValue)
1089+
&& $data[$itemId][Store::DEFAULT_STORE_ID][$fieldName] == $attrValue
10901090
) {
10911091
continue;
10921092
}
@@ -1097,7 +1097,7 @@ protected function collectRawData()
10971097
$additionalAttributes[$fieldName] = $fieldName .
10981098
ImportProduct::PAIR_NAME_VALUE_SEPARATOR . $this->wrapValue($attrValue);
10991099
}
1100-
$data[$itemId][$storeId][$fieldName] = htmlspecialchars_decode($attrValue);
1100+
$data[$itemId][$storeId][$fieldName] = $attrValue;
11011101
}
11021102
} else {
11031103
$this->collectMultiselectValues($item, $code, $storeId);
@@ -1112,7 +1112,6 @@ protected function collectRawData()
11121112
}
11131113

11141114
if (!empty($additionalAttributes)) {
1115-
$additionalAttributes = array_map('htmlspecialchars_decode', $additionalAttributes);
11161115
$data[$itemId][$storeId][self::COL_ADDITIONAL_ATTRIBUTES] =
11171116
implode(Import::DEFAULT_GLOBAL_MULTI_VALUE_SEPARATOR, $additionalAttributes);
11181117
} else {
@@ -1123,7 +1122,7 @@ protected function collectRawData()
11231122
$data[$itemId][$storeId][self::COL_STORE] = $storeCode;
11241123
$data[$itemId][$storeId][self::COL_ATTR_SET] = $this->_attrSetIdToName[$attrSetId];
11251124
$data[$itemId][$storeId][self::COL_TYPE] = $item->getTypeId();
1126-
$data[$itemId][$storeId][self::COL_SKU] = htmlspecialchars_decode($item->getSku());
1125+
$data[$itemId][$storeId][self::COL_SKU] = $item->getSku();
11271126
$data[$itemId][$storeId]['store_id'] = $storeId;
11281127
$data[$itemId][$storeId]['product_id'] = $itemId;
11291128
$data[$itemId][$storeId]['product_link_id'] = $productLinkId;

app/code/Magento/Customer/Controller/Section/Load.php

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ class Load extends \Magento\Framework\App\Action\Action implements HttpGetAction
2424
/**
2525
* @var Identifier
2626
* @deprecated 101.0.0
27+
* @see Used only for backward compatibility for do not break current class implementation with its dependencies
2728
*/
2829
protected $sectionIdentifier;
2930

@@ -69,7 +70,9 @@ public function execute()
6970
$resultJson->setHeader('Pragma', 'no-cache', true);
7071
try {
7172
$sectionNames = $this->getRequest()->getParam('sections');
72-
$sectionNames = $sectionNames ? array_unique(\explode(',', $sectionNames)) : null;
73+
$sectionNames = $sectionNames
74+
? array_unique(is_array($sectionNames) ? $sectionNames : explode(',', $sectionNames))
75+
: null;
7376

7477
$forceNewSectionTimestamp = $this->getRequest()->getParam('force_new_section_timestamp');
7578
if ('false' === $forceNewSectionTimestamp) {

app/code/Magento/Customer/Test/Unit/Controller/Section/LoadTest.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,12 @@ public function executeDataProvider()
146146
'sectionNamesAsArray' => null,
147147
'forceNewTimestamp' => false
148148
],
149+
[
150+
'sectionNames' => ['sectionName1', 'sectionName2', 'sectionName3'],
151+
'forceNewSectionTimestamp' => 'forceNewSectionTimestamp',
152+
'sectionNamesAsArray' => ['sectionName1', 'sectionName2', 'sectionName3'],
153+
'forceNewTimestamp' => true
154+
],
149155
];
150156
}
151157

app/code/Magento/CustomerGraphQl/Model/Resolver/ChangePassword.php

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,11 @@
88
namespace Magento\CustomerGraphQl\Model\Resolver;
99

1010
use Magento\Customer\Api\AccountManagementInterface;
11+
use Magento\Customer\Model\EmailNotificationInterface;
1112
use Magento\CustomerGraphQl\Model\Customer\CheckCustomerPassword;
1213
use Magento\CustomerGraphQl\Model\Customer\ExtractCustomerData;
1314
use Magento\CustomerGraphQl\Model\Customer\GetCustomer;
15+
use Magento\Framework\App\ObjectManager;
1416
use Magento\Framework\Exception\LocalizedException;
1517
use Magento\Framework\GraphQl\Config\Element\Field;
1618
use Magento\Framework\GraphQl\Exception\GraphQlAuthorizationException;
@@ -44,22 +46,31 @@ class ChangePassword implements ResolverInterface
4446
*/
4547
private $extractCustomerData;
4648

49+
/**
50+
* @var EmailNotificationInterface
51+
*/
52+
private $emailNotification;
53+
4754
/**
4855
* @param GetCustomer $getCustomer
4956
* @param CheckCustomerPassword $checkCustomerPassword
5057
* @param AccountManagementInterface $accountManagement
5158
* @param ExtractCustomerData $extractCustomerData
59+
* @param EmailNotificationInterface|null $emailNotification
5260
*/
5361
public function __construct(
5462
GetCustomer $getCustomer,
5563
CheckCustomerPassword $checkCustomerPassword,
5664
AccountManagementInterface $accountManagement,
57-
ExtractCustomerData $extractCustomerData
65+
ExtractCustomerData $extractCustomerData,
66+
?EmailNotificationInterface $emailNotification = null
5867
) {
5968
$this->getCustomer = $getCustomer;
6069
$this->checkCustomerPassword = $checkCustomerPassword;
6170
$this->accountManagement = $accountManagement;
6271
$this->extractCustomerData = $extractCustomerData;
72+
$this->emailNotification = $emailNotification
73+
?? ObjectManager::getInstance()->get(EmailNotificationInterface::class);
6374
}
6475

6576
/**
@@ -89,12 +100,25 @@ public function resolve(
89100
$this->checkCustomerPassword->execute($args['currentPassword'], $customerId);
90101

91102
try {
92-
$this->accountManagement->changePasswordById($customerId, $args['currentPassword'], $args['newPassword']);
103+
$isPasswordChanged = $this->accountManagement->changePasswordById(
104+
$customerId,
105+
$args['currentPassword'],
106+
$args['newPassword']
107+
);
93108
} catch (LocalizedException $e) {
94109
throw new GraphQlInputException(__($e->getMessage()), $e);
95110
}
96111

97112
$customer = $this->getCustomer->execute($context);
113+
114+
if ($isPasswordChanged) {
115+
$this->emailNotification->credentialsChanged(
116+
$customer,
117+
$customer->getEmail(),
118+
$isPasswordChanged
119+
);
120+
}
121+
98122
return $this->extractCustomerData->execute($customer);
99123
}
100124
}

app/code/Magento/Quote/Model/Quote/Item/Compare.php

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,10 @@
55
*/
66
namespace Magento\Quote\Model\Quote\Item;
77

8-
use Magento\Quote\Model\Quote\Item;
9-
use Magento\Framework\Serialize\Serializer\Json;
108
use Magento\Framework\App\ObjectManager;
119
use Magento\Framework\Serialize\JsonValidator;
10+
use Magento\Framework\Serialize\Serializer\Json;
11+
use Magento\Quote\Model\Quote\Item;
1212

1313
/**
1414
* Compare quote items
@@ -68,6 +68,10 @@ protected function getOptionValues($value)
6868
*/
6969
public function compare(Item $target, Item $compared)
7070
{
71+
if ($target->getSku() !== null && $target->getSku() === $compared->getSku()) {
72+
return true;
73+
}
74+
7175
if ($target->getProductId() != $compared->getProductId()) {
7276
return false;
7377
}

0 commit comments

Comments
 (0)