Skip to content

Commit 6b5122c

Browse files
Merge branch '2.4-develop' into spartans_pr_16082025
2 parents 13b31fb + 47721be commit 6b5122c

File tree

39 files changed

+1221
-106
lines changed

39 files changed

+1221
-106
lines changed
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
<?php
2+
/**
3+
* Copyright 2025 Adobe
4+
* All Rights Reserved.
5+
*/
6+
declare(strict_types=1);
7+
8+
namespace Magento\Backend\Model\Config\Password;
9+
10+
use Magento\Framework\App\Config\Value;
11+
use Magento\Framework\Exception\LocalizedException;
12+
use Magento\User\Model\UserValidationRules;
13+
14+
/**
15+
* Backend model for admin minimum password length configuration
16+
*/
17+
class MinimumLength extends Value
18+
{
19+
/**
20+
* Validate the minimum password length value
21+
*
22+
* @return $this
23+
* @throws LocalizedException
24+
*/
25+
public function beforeSave()
26+
{
27+
$value = (int) $this->getValue();
28+
29+
if ($value < UserValidationRules::MIN_PASSWORD_LENGTH) {
30+
throw new LocalizedException(
31+
__(
32+
'The minimum admin password length must be at least %1 characters.',
33+
UserValidationRules::MIN_PASSWORD_LENGTH
34+
)
35+
);
36+
}
37+
38+
return parent::beforeSave();
39+
}
40+
}

app/code/Magento/Backend/etc/adminhtml/system.xml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -461,6 +461,12 @@
461461
<validate>required-entry integer validate-greater-than-zero</validate>
462462
<backend_model>Magento\Config\Model\Config\Backend\Admin\Password\Link\Expirationperiod</backend_model>
463463
</field>
464+
<field id="minimum_password_length" translate="label comment" type="text" sortOrder="8" showInDefault="1" canRestore="1">
465+
<label>Minimum Admin Password Length</label>
466+
<comment>Minimum number of characters required for admin user passwords. Values below 7 are not allowed.</comment>
467+
<validate>required-entry validate-digits validate-digits-range digits-range-7-65535</validate>
468+
<backend_model>Magento\Backend\Model\Config\Password\MinimumLength</backend_model>
469+
</field>
464470
<field id="use_form_key" translate="label" type="select" sortOrder="10" showInDefault="1" canRestore="1">
465471
<label>Add Secret Key to URLs</label>
466472
<source_model>Magento\Config\Model\Config\Source\Yesno</source_model>

app/code/Magento/Backend/etc/config.xml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@
1111
<dashboard>
1212
<enable_charts>0</enable_charts>
1313
</dashboard>
14+
<security>
15+
<minimum_password_length>7</minimum_password_length>
16+
</security>
1417
</admin>
1518
<dev>
1619
<template>

app/code/Magento/Backend/i18n/en_US.csv

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -467,3 +467,5 @@ Pagination,Pagination
467467
"Use URL parameter to enable template path hints for Storefront","Use URL parameter to enable template path hints for Storefront"
468468
"Add the following parameter to the URL to show template hints ?templatehints=[parameter_value]","Add the following parameter to the URL to show template hints ?templatehints=[parameter_value]"
469469
"URL key "%1" matches a reserved endpoint name (%2). Use another URL key.","URL key "%1" matches a reserved endpoint name (%2). Use another URL key."
470+
"Minimum Admin Password Length","Minimum Admin Password Length"
471+
"Minimum number of characters required for admin user passwords. Values below 7 are not allowed.","Minimum number of characters required for admin user passwords. Values below 7 are not allowed."

app/code/Magento/Catalog/Pricing/Price/SpecialPriceBulkResolver.php

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
use Magento\Framework\EntityManager\MetadataPool;
1414
use Magento\Framework\Session\SessionManagerInterface;
1515
use Magento\Framework\View\Element\Block\ArgumentInterface;
16+
use Magento\Store\Model\StoreManagerInterface;
1617

1718
class SpecialPriceBulkResolver implements SpecialPriceBulkResolverInterface, ArgumentInterface
1819
{
@@ -31,19 +32,27 @@ class SpecialPriceBulkResolver implements SpecialPriceBulkResolverInterface, Arg
3132
*/
3233
private SessionManagerInterface $customerSession;
3334

35+
/**
36+
* @var StoreManagerInterface
37+
*/
38+
private StoreManagerInterface $storeManager;
39+
3440
/**
3541
* @param ResourceConnection $resource
3642
* @param MetadataPool $metadataPool
3743
* @param SessionManagerInterface $customerSession
44+
* @param StoreManagerInterface $storeManager
3845
*/
3946
public function __construct(
4047
ResourceConnection $resource,
4148
MetadataPool $metadataPool,
42-
SessionManagerInterface $customerSession
49+
SessionManagerInterface $customerSession,
50+
StoreManagerInterface $storeManager
4351
) {
4452
$this->resource = $resource;
4553
$this->metadataPool = $metadataPool;
4654
$this->customerSession = $customerSession;
55+
$this->storeManager = $storeManager;
4756
}
4857

4958
/**
@@ -59,7 +68,7 @@ public function generateSpecialPriceMap(int $storeId, ?AbstractCollection $produ
5968
if (!$productCollection) {
6069
return [];
6170
}
62-
71+
$websiteId = $this->storeManager->getStore($storeId)->getWebsiteId();
6372
$metadata = $this->metadataPool->getMetadata(ProductInterface::class);
6473
$connection = $this->resource->getConnection();
6574
$select = $connection->select()
@@ -76,7 +85,7 @@ public function generateSpecialPriceMap(int $storeId, ?AbstractCollection $produ
7685
)
7786
->joinLeft(
7887
['price' => $this->resource->getTableName('catalog_product_index_price')],
79-
'price.entity_id = COALESCE(link.product_id, e.entity_id) AND price.website_id = ' . $storeId .
88+
'price.entity_id = COALESCE(link.product_id, e.entity_id) AND price.website_id = ' . $websiteId .
8089
' AND price.customer_group_id = ' . $this->customerSession->getCustomerGroupId()
8190
)
8291
->where('e.entity_id IN (' . implode(',', $productCollection->getAllIds()) . ')')

app/code/Magento/Catalog/Test/Unit/Pricing/Price/SpecialPriceBulkResolverTest.php

Lines changed: 31 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,14 @@
1616
use Magento\Framework\EntityManager\EntityMetadataInterface;
1717
use Magento\Framework\EntityManager\MetadataPool;
1818
use Magento\Framework\Session\SessionManagerInterface;
19+
use Magento\Store\Api\Data\StoreInterface;
20+
use Magento\Store\Model\StoreManagerInterface;
1921
use PHPUnit\Framework\MockObject\MockObject;
2022
use PHPUnit\Framework\TestCase;
2123

24+
/**
25+
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
26+
*/
2227
class SpecialPriceBulkResolverTest extends TestCase
2328
{
2429
/**
@@ -37,10 +42,15 @@ class SpecialPriceBulkResolverTest extends TestCase
3742
private SpecialPriceBulkResolver $specialPriceBulkResolver;
3843

3944
/**
40-
* @var SessionManagerInterface
45+
* @var SessionManagerInterface|MockObject
4146
*/
4247
private SessionManagerInterface $customerSession;
4348

49+
/**
50+
* @var StoreManagerInterface|MockObject
51+
*/
52+
private StoreManagerInterface $storeManager;
53+
4454
/**
4555
* @return void
4656
*/
@@ -52,11 +62,12 @@ protected function setUp(): void
5262
->disableOriginalConstructor()
5363
->addMethods(['getCustomerGroupId'])
5464
->getMockForAbstractClass();
55-
65+
$this->storeManager = $this->createMock(StoreManagerInterface::class);
5666
$this->specialPriceBulkResolver = new SpecialPriceBulkResolver(
5767
$this->resource,
5868
$this->metadataPool,
59-
$this->customerSession
69+
$this->customerSession,
70+
$this->storeManager
6071
);
6172
}
6273

@@ -75,9 +86,19 @@ public function testGenerateSpecialPriceMapNoCollection(): void
7586
*/
7687
public function testGenerateSpecialPriceMapCollection(): void
7788
{
89+
$storeId = 2;
90+
$websiteId = 1;
91+
$customerGroupId = 3;
7892
$product = $this->createMock(Product::class);
79-
80-
$this->customerSession->expects($this->once())->method('getCustomerGroupId')->willReturn(1);
93+
$store = $this->createMock(StoreInterface::class);
94+
$store->expects($this->once())->method('getWebsiteId')->willReturn($websiteId);
95+
$this->storeManager->expects($this->once())
96+
->method('getStore')
97+
->with($storeId)
98+
->willReturn($store);
99+
$this->customerSession->expects($this->once())
100+
->method('getCustomerGroupId')
101+
->willReturn($customerGroupId);
81102
$collection = $this->getMockBuilder(AbstractCollection::class)
82103
->disableOriginalConstructor()
83104
->onlyMethods(['getAllIds', 'getIterator'])
@@ -92,14 +113,13 @@ public function testGenerateSpecialPriceMapCollection(): void
92113
->method('getMetadata')
93114
->with(ProductInterface::class)
94115
->willReturn($metadata);
95-
96116
$connection = $this->getMockBuilder(AdapterInterface::class)
97117
->addMethods(['from', 'joinInner', 'where', 'columns', 'joinLeft'])
98118
->getMockForAbstractClass();
99119
$connection->expects($this->once())->method('select')->willReturnSelf();
100120
$connection->expects($this->once())
101121
->method('from')
102-
->with(['e' => 'catalog_product_super_link'])
122+
->with(['e' => 'catalog_product_entity'])
103123
->willReturnSelf();
104124
$connection->expects($this->exactly(3))
105125
->method('joinLeft')
@@ -130,12 +150,13 @@ public function testGenerateSpecialPriceMapCollection(): void
130150
$this->resource->expects($this->exactly(4))
131151
->method('getTableName')
132152
->willReturnOnConsecutiveCalls(
133-
'catalog_product_super_link',
134153
'catalog_product_entity',
154+
'catalog_product_super_link',
135155
'catalog_product_website',
136156
'catalog_product_index_price'
137157
);
138-
139-
$this->specialPriceBulkResolver->generateSpecialPriceMap(1, $collection);
158+
$result = $this->specialPriceBulkResolver->generateSpecialPriceMap($storeId, $collection);
159+
$expectedResult = [1 => true];
160+
$this->assertEquals($expectedResult, $result);
140161
}
141162
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
<?php
2+
/**
3+
* Copyright 2025 Adobe
4+
* All Rights Reserved.
5+
*/
6+
declare(strict_types=1);
7+
8+
namespace Magento\CatalogRule\Plugin\Model\Indexer;
9+
10+
use Magento\Catalog\Model\Indexer\Product\Price;
11+
use Magento\Quote\Model\ResourceModel\Quote as QuoteResourceModel;
12+
13+
/**
14+
* Recollect quote on product ids after rule change.
15+
*/
16+
class RecollectQuoteAfterRuleChange
17+
{
18+
/**
19+
* @param QuoteResourceModel $quoteResourceModel
20+
*/
21+
public function __construct(
22+
private readonly QuoteResourceModel $quoteResourceModel
23+
) {
24+
}
25+
26+
/**
27+
* Recollect quote on product ids after rule change.
28+
*
29+
* @param Price $subject
30+
* @param void $result
31+
* @param array $ids
32+
* @return void
33+
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
34+
*/
35+
public function afterExecute(Price $subject, $result, array $ids)
36+
{
37+
$this->quoteResourceModel->markQuotesRecollect($ids);
38+
}
39+
}

app/code/Magento/CatalogRule/composer.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@
1313
"magento/module-eav": "*",
1414
"magento/module-rule": "*",
1515
"magento/module-store": "*",
16-
"magento/module-ui": "*"
16+
"magento/module-ui": "*",
17+
"magento/module-quote": "*"
1718
},
1819
"suggest": {
1920
"magento/module-import-export": "*",

app/code/Magento/CatalogRule/etc/di.xml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,4 +164,7 @@
164164
<argument name="customConditionProvider" xsi:type="object">CatalogRuleCustomConditionProvider</argument>
165165
</arguments>
166166
</type>
167+
<type name="Magento\Catalog\Model\Indexer\Product\Price">
168+
<plugin name="recollect_quote_after_rule_change" type="Magento\CatalogRule\Plugin\Model\Indexer\RecollectQuoteAfterRuleChange"/>
169+
</type>
167170
</config>

app/code/Magento/Ui/i18n/en_US.csv

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ Keyword,Keyword
8585
"Please enter a valid email address (Ex: [email protected]).","Please enter a valid email address (Ex: [email protected])."
8686
"Please enter 6 or more characters. Leading and trailing spaces will be ignored.","Please enter 6 or more characters. Leading and trailing spaces will be ignored."
8787
"Please enter 7 or more characters, using both numeric and alphabetic.","Please enter 7 or more characters, using both numeric and alphabetic."
88+
"Please enter %1 or more characters, using both numeric and alphabetic.","Please enter %1 or more characters, using both numeric and alphabetic."
8889
"Minimum length of this field must be equal or greater than %1 symbols. Leading and trailing spaces will be ignored.","Minimum length of this field must be equal or greater than %1 symbols. Leading and trailing spaces will be ignored."
8990
"Minimum of different classes of characters in password is %1. Classes of characters: Lower Case, Upper Case, Digits, Special Characters.","Minimum of different classes of characters in password is %1. Classes of characters: Lower Case, Upper Case, Digits, Special Characters."
9091
"Please enter a valid URL. Protocol is required (http://, https:// or ftp://).","Please enter a valid URL. Protocol is required (http://, https:// or ftp://)."

0 commit comments

Comments
 (0)