Skip to content

Commit d146b1a

Browse files
authored
Merge branch '2.4-develop' into comps_78764_2205
2 parents 36896d1 + 7accebf commit d146b1a

File tree

160 files changed

+23093
-20997
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

160 files changed

+23093
-20997
lines changed

app/code/Magento/Backend/Test/Mftf/Data/BackenedData.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<?xml version="1.0" encoding="UTF-8"?>
22
<!--
33
/**
4-
* Copyright 2018 Adobe
4+
* Copyright 2025 Adobe
55
* All Rights Reserved.
66
*/
77
-->

app/code/Magento/Backend/view/adminhtml/web/js/validate-store.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,12 +47,19 @@ define([
4747
_saveHandler: function (form) {
4848
var formData = {},
4949
requestData = {},
50+
counts = {},
5051
options = $.data(form, 'validator').settings;
5152

5253
if ($(form).validation('isValid')) {
5354
$.each($(form).serializeArray(), function () {
55+
counts[this.name] = (counts[this.name] || 0) + 1;
56+
57+
if (formData[this.name]) {
58+
this.name = this.name.replace(new RegExp(/\[\]$/g), '[' + (counts[this.name] - 1) + ']');
59+
}
5460
formData[this.name] = this.value || '';
5561
});
62+
5663
requestData = {
5764
action: $(form).attr('action'),
5865
data: formData
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!--
3+
/**
4+
* Copyright 2021 Adobe
5+
* All Rights Reserved.
6+
*/
7+
-->
8+
9+
<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
10+
xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd">
11+
<actionGroup name="AssertCaptchaVisibleOnEmailToFriendFormActionGroup">
12+
<waitForElementVisible selector="{{StorefrontEmailToFriendSection.captchaField}}" stepKey="waitToSeeCaptchaField"/>
13+
<waitForElementVisible selector="{{StorefrontEmailToFriendSection.captchaImg}}" stepKey="waitToSeeCaptchaImage"/>
14+
<waitForElementVisible selector="{{StorefrontEmailToFriendSection.captchaReload}}" stepKey="waitToSeeCaptchaReloadButton"/>
15+
<reloadPage stepKey="refreshPage"/>
16+
<waitForPageLoad stepKey="waitForPageReloaded"/>
17+
<waitForElementVisible selector="{{StorefrontEmailToFriendSection.captchaField}}" stepKey="waitToSeeCaptchaFieldAfterPageReload"/>
18+
<waitForElementVisible selector="{{StorefrontEmailToFriendSection.captchaImg}}" stepKey="waitToSeeCaptchaImageAfterPageReload"/>
19+
<waitForElementVisible selector="{{StorefrontEmailToFriendSection.captchaReload}}" stepKey="waitToSeeCaptchaReloadButtonAfterPageReload"/>
20+
</actionGroup>
21+
</actionGroups>
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!--
3+
/**
4+
* Copyright 2021 Adobe
5+
* All Rights Reserved.
6+
*/
7+
-->
8+
9+
<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
10+
xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd">
11+
<actionGroup name="StorefrontFillCaptchaOnEmailToFriendFormActionGroup">
12+
<arguments>
13+
<argument name="captcha" type="string"/>
14+
</arguments>
15+
<fillField userInput="{{captcha}}" selector="{{StorefrontEmailToFriendSection.captchaField}}" stepKey="fillCaptchaField"/>
16+
</actionGroup>
17+
</actionGroups>

app/code/Magento/Captcha/Test/Mftf/Data/CaptchaConfigData.xml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,12 @@
163163
<data key="label">ABCDEFGHJKMnpqrstuvwxyz23456789</data>
164164
<data key="value">ABCDEFGHJKMnpqrstuvwxyz23456789</data>
165165
</entity>
166+
<entity name="StorefrontCaptchaOnEmailToFriendFormConfigData">
167+
<data key="path">customer/captcha/forms</data>
168+
<data key="scope_id">0</data>
169+
<data key="label">Send To Friend Form</data>
170+
<data key="value">product_sendtofriend_form</data>
171+
</entity>
166172
<entity name="AdminCaptchaForgotPasswordFormConfigData">
167173
<!-- Magento default value -->
168174
<data key="path">admin/captcha/forms</data>
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!--
3+
/**
4+
* Copyright 2021 Adobe
5+
* All Rights Reserved.
6+
*/
7+
-->
8+
9+
<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
10+
xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd">
11+
<test name="StorefrontCaptchaOnEmailToFriendFormTest">
12+
<annotations>
13+
<features value="Captcha"/>
14+
<stories value="Sending email about products in store with enabled captcha"/>
15+
<title value="Captcha on email to friend form"/>
16+
<description value="Customer should be able send email to friend about product with enabled captcha"/>
17+
<severity value="MINOR"/>
18+
<group value="captcha"/>
19+
</annotations>
20+
<before>
21+
<createData entity="SimpleSubCategory" stepKey="createCategory"/>
22+
<createData entity="SimpleProduct" stepKey="createProduct">
23+
<requiredEntity createDataKey="createCategory"/>
24+
</createData>
25+
<createData entity="Simple_US_Customer" stepKey="createCustomer"/>
26+
<magentoCLI command="config:set {{StorefrontCustomerCaptchaLength3ConfigData.path}} {{StorefrontCustomerCaptchaLength3ConfigData.value}}" stepKey="setCaptchaLength"/>
27+
<magentoCLI command="config:set {{StorefrontCustomerCaptchaSymbols1ConfigData.path}} {{StorefrontCustomerCaptchaSymbols1ConfigData.value}}" stepKey="setCaptchaSymbols"/>
28+
<magentoCLI command="config:set {{StorefrontCaptchaOnEmailToFriendFormConfigData.path}} {{StorefrontCaptchaOnEmailToFriendFormConfigData.value}}" stepKey="enableEmailToFriendCaptcha"/>
29+
<magentoCLI command="config:set {{StorefrontCustomerCaptchaModeAlwaysConfigData.path}} {{StorefrontCustomerCaptchaModeAlwaysConfigData.value}}" stepKey="setCaptchaAlwaysVisible"/>
30+
<magentoCLI command="config:set {{EnableCatalogEmailToFriendOptionConfigData.path}} {{EnableCatalogEmailToFriendOptionConfigData.value}}" stepKey="enableSendToFriend"/>
31+
<actionGroup ref="CliCacheCleanActionGroup" stepKey="cleanInvalidatedCaches">
32+
<argument name="tags" value="config full_page"/>
33+
</actionGroup>
34+
</before>
35+
<after>
36+
<deleteData createDataKey="createCategory" stepKey="deleteCategory"/>
37+
<deleteData createDataKey="createProduct" stepKey="deleteProduct"/>
38+
<actionGroup ref="StorefrontCustomerLogoutActionGroup" stepKey="logoutCustomer"/>
39+
<deleteData createDataKey="createCustomer" stepKey="deleteCustomer"/>
40+
<magentoCLI command="config:set {{StorefrontCustomerCaptchaDefaultLengthConfigData.path}} {{StorefrontCustomerCaptchaDefaultLengthConfigData.value}}" stepKey="setDefaultCaptchaLength"/>
41+
<magentoCLI command="config:set {{StorefrontCustomerCaptchaDefaultSymbolsConfigData.path}} {{StorefrontCustomerCaptchaDefaultSymbolsConfigData.value}}" stepKey="setDefaultCaptchaSymbols"/>
42+
<magentoCLI command="config:set {{StorefrontCustomerCaptchaModeAfterFailConfigData.path}} {{StorefrontCustomerCaptchaModeAfterFailConfigData.value}}" stepKey="setCaptchaDefaultVisibility"/>
43+
<magentoCLI command="config:set {{StorefrontCaptchaOnCustomerLoginConfigData.path}} {{StorefrontCaptchaOnCustomerLoginConfigData.value}},{{StorefrontCaptchaOnCustomerForgotPasswordConfigData.value}}" stepKey="enableCaptchaOnDefaultForms"/>
44+
<magentoCLI command="config:set {{DisableCatalogEmailToFriendOptionConfigData.path}} {{DisableCatalogEmailToFriendOptionConfigData.value}}" stepKey="disableSendToFriend"/>
45+
<actionGroup ref="CliCacheCleanActionGroup" stepKey="cleanInvalidatedCaches">
46+
<argument name="tags" value="config full_page"/>
47+
</actionGroup>
48+
</after>
49+
50+
<actionGroup ref="LoginToStorefrontActionGroup" stepKey="loginToStorefrontAccount">
51+
<argument name="Customer" value="$createCustomer$"/>
52+
</actionGroup>
53+
<actionGroup ref="OpenProductFromCategoryPageActionGroup" stepKey="openProductFromCategory">
54+
<argument name="category" value="$createCategory$"/>
55+
<argument name="product" value="$createProduct$"/>
56+
</actionGroup>
57+
<actionGroup ref="StorefrontClickEmailToFriendButtonOnProductPageActionGroup" stepKey="clickEmailButton"/>
58+
<actionGroup ref="AssertCaptchaVisibleOnEmailToFriendFormActionGroup" stepKey="seeCaptchaOnEmailToFriendForm"/>
59+
<actionGroup ref="StorefrontFillFieldsOnEmailToFriendFormActionGroup" stepKey="fillFieldsOnForm">
60+
<argument name="senderName" value="{{Simple_US_Customer.firstname}}"/>
61+
<argument name="senderEmail" value="{{Simple_US_Customer.email}}"/>
62+
<argument name="message" value="{{EmailToFriendValidValues.shareInfo_message}}"/>
63+
<argument name="recipientName" value="{{EmailToFriendValidValues.recipient_name}}"/>
64+
<argument name="recipientEmail" value="{{EmailToFriendValidValues.shareInfo_email}}"/>
65+
</actionGroup>
66+
<actionGroup ref="StorefrontFillCaptchaOnEmailToFriendFormActionGroup" stepKey="fillCaptchaFieldWithIncorrectValues">
67+
<argument name="captcha" value="{{WrongCaptcha.value}}"/>
68+
</actionGroup>
69+
<actionGroup ref="StorefrontClickSendEmailButtonOnEmailToFriendFormActionGroup" stepKey="submitEmailToFriendForm"/>
70+
<actionGroup ref="AssertMessageCustomerChangeAccountInfoActionGroup" stepKey="assertErrorMessage">
71+
<argument name="message" value="Incorrect CAPTCHA"/>
72+
<argument name="messageType" value="error"/>
73+
</actionGroup>
74+
<actionGroup ref="StorefrontFillFieldsOnEmailToFriendFormActionGroup" stepKey="fillFieldsOnEmailToFriendForm">
75+
<argument name="senderName" value="{{Simple_US_Customer.firstname}}"/>
76+
<argument name="senderEmail" value="{{Simple_US_Customer.email}}"/>
77+
<argument name="message" value="{{EmailToFriendValidValues.shareInfo_message}}"/>
78+
<argument name="recipientName" value="{{EmailToFriendValidValues.recipient_name}}"/>
79+
<argument name="recipientEmail" value="{{EmailToFriendValidValues.shareInfo_email}}"/>
80+
</actionGroup>
81+
<actionGroup ref="StorefrontFillCaptchaOnEmailToFriendFormActionGroup" stepKey="fillCaptchaFieldWithCorrectValues">
82+
<argument name="captcha" value="{{PreconfiguredCaptcha.value}}"/>
83+
</actionGroup>
84+
<actionGroup ref="StorefrontClickSendEmailButtonOnEmailToFriendFormActionGroup" stepKey="submitEmailToFriendFormWithValidValues"/>
85+
<actionGroup ref="AssertMessageCustomerChangeAccountInfoActionGroup" stepKey="assertSuccessMessage">
86+
<argument name="message" value="The link to a friend was sent."/>
87+
</actionGroup>
88+
</test>
89+
</tests>

app/code/Magento/Catalog/Helper/Category.php

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,10 @@
1010
use Magento\Catalog\Model\CategoryFactory;
1111
use Magento\Framework\App\Helper\AbstractHelper;
1212
use Magento\Framework\App\Helper\Context;
13+
use Magento\Framework\App\ObjectManager;
1314
use Magento\Framework\Data\CollectionFactory;
1415
use Magento\Framework\Data\Tree\Node\Collection;
16+
use Magento\Framework\Escaper;
1517
use Magento\Framework\Exception\NoSuchEntityException;
1618
use Magento\Framework\ObjectManager\ResetAfterRequestInterface;
1719
use Magento\Store\Model\ScopeInterface;
@@ -63,24 +65,33 @@ class Category extends AbstractHelper implements ResetAfterRequestInterface
6365
*/
6466
protected $categoryRepository;
6567

68+
/**
69+
* @var Escaper|null
70+
*/
71+
private ?Escaper $escaper;
72+
6673
/**
6774
* @param Context $context
6875
* @param CategoryFactory $categoryFactory
6976
* @param StoreManagerInterface $storeManager
7077
* @param CollectionFactory $dataCollectionFactory
7178
* @param CategoryRepositoryInterface $categoryRepository
79+
* @param Escaper|null $escaper
7280
*/
7381
public function __construct(
7482
Context $context,
7583
CategoryFactory $categoryFactory,
7684
StoreManagerInterface $storeManager,
7785
CollectionFactory $dataCollectionFactory,
78-
CategoryRepositoryInterface $categoryRepository
86+
CategoryRepositoryInterface $categoryRepository,
87+
?Escaper $escaper = null
7988
) {
8089
$this->_categoryFactory = $categoryFactory;
8190
$this->_storeManager = $storeManager;
8291
$this->_dataCollectionFactory = $dataCollectionFactory;
8392
$this->categoryRepository = $categoryRepository;
93+
$this->escaper = $escaper ?: ObjectManager::getInstance()->get(Escaper::class);
94+
8495
parent::__construct($context);
8596
}
8697

@@ -204,6 +215,7 @@ public function getCanonicalUrl(string $categoryUrl): string
204215
if ($params && isset($params['p'])) {
205216
$categoryUrl = $categoryUrl . '?p=' . $params['p'];
206217
}
207-
return $categoryUrl;
218+
219+
return $this->escaper->escapeUrl($categoryUrl);
208220
}
209221
}

app/code/Magento/Catalog/Model/Product/Gallery/CreateHandler.php

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -219,9 +219,18 @@ public function execute($product, $arguments = [])
219219
$clearImages[] = $image['file'];
220220
} elseif (empty($image['value_id']) || !empty($image['recreate'])) {
221221
$newFile = $this->moveImageFromTmp($image['file'] ?? '');
222-
$image['new_file'] = $newFile;
223-
$newImages[$image['file']] = $image;
224-
$image['file'] = $newFile;
222+
if (!empty($image['recreate']) && $newFile !== $image['file']) {
223+
//delete old image
224+
$this->mediaDirectory->renameFile(
225+
$this->mediaConfig->getMediaPath($newFile),
226+
$this->mediaConfig->getMediaPath($image['file'])
227+
);
228+
$existImages[$image['file']] = $image;
229+
} else {
230+
$image['new_file'] = $newFile;
231+
$newImages[$image['file']] = $image;
232+
$image['file'] = $newFile;
233+
}
225234
} else {
226235
$existImages[$image['file']] = $image;
227236
}

app/code/Magento/Catalog/Model/ResourceModel/Category/Collection.php

Lines changed: 63 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
use Magento\Framework\App\Config\ScopeConfigInterface;
1212
use Magento\Framework\DB\Select;
1313
use Magento\Store\Model\ScopeInterface;
14+
use Magento\Framework\DB\Adapter\AdapterInterface;
15+
use Magento\Framework\DB\Ddl\Table;
1416

1517
/**
1618
* Category resource collection
@@ -370,40 +372,80 @@ public function loadProductCount($items, $countRegular = true, $countAnchor = tr
370372
* @param array $categoryIds
371373
* @param int $websiteId
372374
* @return array
375+
* @throws \Zend_Db_Exception
373376
*/
374377
private function getCountFromCategoryTableBulk(
375378
array $categoryIds,
376379
int $websiteId
377380
) : array {
378-
$subSelect = clone $this->_conn->select();
379-
$subSelect->from(['ce2' => $this->getTable('catalog_category_entity')], 'ce2.entity_id')
380-
->where("ce2.path LIKE CONCAT(ce.path, '/%') OR ce2.path = ce.path");
381-
382-
$select = clone $this->_conn->select();
383-
$select->from(
384-
['ce' => $this->getTable('catalog_category_entity')],
385-
'ce.entity_id'
386-
);
387-
$joinCondition = new \Zend_Db_Expr("cp.category_id IN ({$subSelect})");
388-
$select->joinLeft(
389-
['cp' => $this->getProductTable()],
390-
$joinCondition,
391-
'COUNT(DISTINCT cp.product_id) AS product_count'
381+
$connection = $this->_conn;
382+
$tempTableName = 'temp_category_descendants_' . uniqid();
383+
$tempTable = $connection->newTable($tempTableName)
384+
->addColumn(
385+
'category_id',
386+
Table::TYPE_INTEGER,
387+
null,
388+
['unsigned' => true, 'nullable' => false],
389+
'Category ID'
390+
)
391+
->addColumn(
392+
'descendant_id',
393+
Table::TYPE_INTEGER,
394+
null,
395+
['unsigned' => true, 'nullable' => false],
396+
'Descendant ID'
397+
)
398+
->addIndex(
399+
$connection->getIndexName($tempTableName, ['category_id', 'descendant_id']),
400+
['category_id', 'descendant_id'],
401+
['type' => AdapterInterface::INDEX_TYPE_PRIMARY]
402+
);
403+
$connection->createTemporaryTable($tempTable);
404+
$selectDescendants = $connection->select()
405+
->from(
406+
['ce' => $this->getTable('catalog_category_entity')],
407+
['category_id' => 'ce.entity_id', 'descendant_id' => 'ce2.entity_id']
408+
)
409+
->joinInner(
410+
['ce2' => $this->getTable('catalog_category_entity')],
411+
'ce2.path LIKE CONCAT(ce.path, \'/%\') OR ce2.entity_id = ce.entity_id',
412+
[]
413+
)
414+
->where('ce.entity_id IN (?)', $categoryIds);
415+
416+
$connection->query(
417+
$connection->insertFromSelect(
418+
$selectDescendants,
419+
$tempTableName,
420+
['category_id', 'descendant_id']
421+
)
392422
);
423+
$select = $connection->select()
424+
->from(
425+
['t' => $tempTableName],
426+
['category_id' => 't.category_id']
427+
)
428+
->joinLeft(
429+
['cp' => $this->getTable('catalog_category_product')],
430+
'cp.category_id = t.descendant_id',
431+
['product_count' => 'COUNT(DISTINCT cp.product_id)']
432+
);
393433
if ($websiteId) {
394434
$select->join(
395435
['w' => $this->getProductWebsiteTable()],
396436
'cp.product_id = w.product_id',
397437
[]
398-
)->where(
399-
'w.website_id = ?',
400-
$websiteId
401-
);
438+
)->where('w.website_id = ?', $websiteId);
439+
}
440+
$select->group('t.category_id');
441+
$result = $connection->fetchPairs($select);
442+
$connection->dropTemporaryTable($tempTableName);
443+
$counts = array_fill_keys($categoryIds, 0);
444+
foreach ($result as $categoryId => $count) {
445+
$counts[$categoryId] = (int)$count;
402446
}
403-
$select->where('ce.entity_id IN(?)', $categoryIds);
404-
$select->group('ce.entity_id');
405447

406-
return $this->_conn->fetchPairs($select);
448+
return $counts;
407449
}
408450

409451
/**
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!--
3+
/**
4+
* Copyright 2021 Adobe
5+
* All Rights Reserved.
6+
*/
7+
-->
8+
9+
<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
10+
xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd">
11+
<actionGroup name="StorefrontClickEmailToFriendButtonOnProductPageActionGroup">
12+
<click selector="{{StorefrontProductPageSection.email}}" stepKey="clickEmailToFriendButton"/>
13+
</actionGroup>
14+
</actionGroups>

0 commit comments

Comments
 (0)