Skip to content

Commit dcd6409

Browse files
authored
Merge pull request #9886 from magento-gl/comprs_june
[Bluetooth] Community Pull Requests delivery - June
2 parents 536edc2 + a23f013 commit dcd6409

File tree

26 files changed

+570
-295
lines changed

26 files changed

+570
-295
lines changed

app/bootstrap.php

Lines changed: 2 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,8 @@
1313
}
1414
#ini_set('display_errors', 1);
1515

16-
/* PHP version validation */
17-
if (!defined('PHP_VERSION_ID') || PHP_VERSION_ID < 80100) {
18-
if (PHP_SAPI == 'cli') {
16+
if (PHP_VERSION_ID < 80100) {
17+
if (PHP_SAPI === 'cli') {
1918
echo 'Magento supports PHP 8.1.0 or later. ' .
2019
'Please read https://experienceleague.adobe.com/docs/commerce-operations/installation-guide/system-requirements.html';
2120
} else {
@@ -31,16 +30,6 @@
3130
exit(1);
3231
}
3332

34-
// PHP 8 compatibility. Define constants that are not present in PHP < 8.0
35-
if (!defined('PHP_VERSION_ID') || PHP_VERSION_ID < 80000) {
36-
if (!defined('T_NAME_QUALIFIED')) {
37-
define('T_NAME_QUALIFIED', 24001);
38-
}
39-
if (!defined('T_NAME_FULLY_QUALIFIED')) {
40-
define('T_NAME_FULLY_QUALIFIED', 24002);
41-
}
42-
}
43-
4433
require_once __DIR__ . '/autoload.php';
4534
// Sets default autoload mappings, may be overridden in Bootstrap::create
4635
\Magento\Framework\App\Bootstrap::populateAutoloader(BP, []);

app/code/Magento/Bundle/Model/Product/Price.php

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
use Magento\Framework\Pricing\PriceCurrencyInterface;
1010
use Magento\Framework\App\ObjectManager;
1111
use Magento\Catalog\Api\Data\ProductTierPriceExtensionFactory;
12+
use Magento\Catalog\Model\Pricing\SpecialPriceService;
1213

1314
/**
1415
* Bundle product type price model
@@ -66,6 +67,7 @@ class Price extends \Magento\Catalog\Model\Product\Type\Price
6667
* @param \Magento\Catalog\Helper\Data $catalogData
6768
* @param \Magento\Framework\Serialize\Serializer\Json|null $serializer
6869
* @param ProductTierPriceExtensionFactory|null $tierPriceExtensionFactory
70+
* @param SpecialPriceService|null $specialPriceService
6971
* @SuppressWarnings(PHPMD.ExcessiveParameterList)
7072
*/
7173
public function __construct(
@@ -80,7 +82,8 @@ public function __construct(
8082
\Magento\Framework\App\Config\ScopeConfigInterface $config,
8183
\Magento\Catalog\Helper\Data $catalogData,
8284
?\Magento\Framework\Serialize\Serializer\Json $serializer = null,
83-
?ProductTierPriceExtensionFactory $tierPriceExtensionFactory = null
85+
?ProductTierPriceExtensionFactory $tierPriceExtensionFactory = null,
86+
?SpecialPriceService $specialPriceService = null
8487
) {
8588
$this->_catalogData = $catalogData;
8689
$this->serializer = $serializer ?: ObjectManager::getInstance()
@@ -95,7 +98,8 @@ public function __construct(
9598
$groupManagement,
9699
$tierPriceFactory,
97100
$config,
98-
$tierPriceExtensionFactory
101+
$tierPriceExtensionFactory,
102+
$specialPriceService
99103
);
100104
}
101105

@@ -629,6 +633,9 @@ public function calculateSpecialPrice(
629633
$store = null
630634
) {
631635
if ($specialPrice !== null && $specialPrice != false) {
636+
637+
$specialPriceTo = $this->getSpecialPriceService()->execute($specialPriceTo);
638+
632639
if ($this->_localeDate->isScopeDateInInterval($store, $specialPriceFrom, $specialPriceTo)) {
633640
$specialPrice = $finalPrice * ($specialPrice / 100);
634641
$finalPrice = min($finalPrice, $specialPrice);

app/code/Magento/Bundle/Test/Unit/Model/Product/PriceTest.php

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
use Magento\Catalog\Api\Data\ProductTierPriceInterfaceFactory;
1515
use Magento\Catalog\Helper\Data;
1616
use Magento\Catalog\Model\Product;
17+
use Magento\Catalog\Model\Pricing\SpecialPriceService;
1718
use Magento\CatalogRule\Model\ResourceModel\RuleFactory;
1819
use Magento\Customer\Api\GroupManagementInterface;
1920
use Magento\Customer\Model\Session;
@@ -135,6 +136,17 @@ function ($value) {
135136
->onlyMethods(['create'])
136137
->disableOriginalConstructor()
137138
->getMock();
139+
140+
$specialPriceService = $this->getMockBuilder(SpecialPriceService::class)
141+
->disableOriginalConstructor()
142+
->getMock();
143+
144+
$specialPriceService->expects($this->any())
145+
->method('execute')
146+
->willReturnCallback(function ($value) {
147+
return $value;
148+
});
149+
138150
$objectManagerHelper = new ObjectManagerHelper($this);
139151
$this->model = $objectManagerHelper->getObject(
140152
Price::class,
@@ -150,7 +162,8 @@ function ($value) {
150162
'config' => $scopeConfig,
151163
'catalogData' => $this->catalogHelperMock,
152164
'serializer' => $this->serializer,
153-
'tierPriceExtensionFactory' => $tierPriceExtensionFactoryMock
165+
'tierPriceExtensionFactory' => $tierPriceExtensionFactoryMock,
166+
'specialPriceService' => $specialPriceService
154167
]
155168
);
156169
}

app/code/Magento/Bundle/Test/Unit/Pricing/Price/SpecialPriceTest.php

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
use Magento\Bundle\Pricing\Price\SpecialPrice;
1111
use Magento\Catalog\Model\Product;
1212
use Magento\Catalog\Pricing\Price\RegularPrice;
13+
use Magento\Catalog\Model\Pricing\SpecialPriceService;
1314
use Magento\Framework\Pricing\Price\PriceInterface;
1415
use Magento\Framework\Pricing\PriceCurrencyInterface;
1516
use Magento\Framework\Pricing\PriceInfo\Base;
@@ -47,6 +48,11 @@ class SpecialPriceTest extends TestCase
4748
*/
4849
protected $priceCurrencyMock;
4950

51+
/**
52+
* @var SpecialPriceService|MockObject
53+
*/
54+
private $specialPriceService;
55+
5056
protected function setUp(): void
5157
{
5258
$this->saleable = $this->getMockBuilder(Product::class)
@@ -62,13 +68,18 @@ protected function setUp(): void
6268

6369
$this->priceCurrencyMock = $this->getMockForAbstractClass(PriceCurrencyInterface::class);
6470

71+
$this->specialPriceService = $this->getMockBuilder(SpecialPriceService::class)
72+
->disableOriginalConstructor()
73+
->getMock();
74+
6575
$objectHelper = new ObjectManager($this);
6676
$this->model = $objectHelper->getObject(
6777
SpecialPrice::class,
6878
[
6979
'saleableItem' => $this->saleable,
7080
'localeDate' => $this->localeDate,
71-
'priceCurrency' => $this->priceCurrencyMock
81+
'priceCurrency' => $this->priceCurrencyMock,
82+
'specialPriceService' => $this->specialPriceService
7283
]
7384
);
7485
}
@@ -102,6 +113,11 @@ public function testGetValue($regularPrice, $specialPrice, $isScopeDateInInterva
102113
->with(WebsiteInterface::ADMIN_CODE, $specialFromDate, $specialToDate)
103114
->willReturn($isScopeDateInInterval);
104115

116+
$this->specialPriceService->expects($this->once())
117+
->method('execute')
118+
->with($specialToDate)
119+
->willReturn($specialToDate);
120+
105121
$this->priceCurrencyMock->expects($this->never())
106122
->method('convertAndRound');
107123

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
<?php
2+
/**
3+
* Copyright 2025 Adobe
4+
* All Rights Reserved.
5+
*/
6+
declare(strict_types=1);
7+
8+
namespace Magento\Catalog\Model\Pricing;
9+
10+
/**
11+
* This class provides functionality to normalize the end date/time of special prices
12+
*/
13+
class SpecialPriceService
14+
{
15+
/**
16+
* This class subtracts one day from $dateTo if it contains a specific time (hours, minutes, seconds)
17+
* because \Magento\Framework\Stdlib\DateTime\Timezone::isScopeDateInInterval adds one day.
18+
* This ensures that the special price expires exactly at the specified time
19+
*
20+
* For example,
21+
* - If $dateTo is "2025-05-12 17:00:00", it will be converted to "2025-05-11 17:00:00"
22+
* - If $dateTo is "2024-05-12 00:00:00", it will remain unchanged
23+
*
24+
* @param mixed $dateTo
25+
* @return mixed|string
26+
*/
27+
public function execute(mixed $dateTo): mixed
28+
{
29+
if ($dateTo
30+
&& strtotime($dateTo) !== false
31+
&& date('H:i:s', strtotime($dateTo)) !== '00:00:00') {
32+
$dateToTimestamp = strtotime($dateTo);
33+
$dateTo = date('Y-m-d H:i:s', $dateToTimestamp - 86400);
34+
}
35+
36+
return $dateTo;
37+
}
38+
}

app/code/Magento/Catalog/Model/Product/Type/Price.php

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
use Magento\Catalog\Api\Data\ProductTierPriceExtensionFactory;
1616
use Magento\Framework\App\ObjectManager;
1717
use Magento\Store\Api\Data\WebsiteInterface;
18+
use Magento\Catalog\Model\Pricing\SpecialPriceService;
1819

1920
/**
2021
* Product type price model
@@ -88,6 +89,11 @@ class Price implements ResetAfterRequestInterface
8889
*/
8990
private $tierPriceExtensionFactory;
9091

92+
/**
93+
* @var SpecialPriceService|null
94+
*/
95+
private ?SpecialPriceService $specialPriceService;
96+
9197
/**
9298
* Constructor
9399
*
@@ -101,6 +107,7 @@ class Price implements ResetAfterRequestInterface
101107
* @param \Magento\Catalog\Api\Data\ProductTierPriceInterfaceFactory $tierPriceFactory
102108
* @param \Magento\Framework\App\Config\ScopeConfigInterface $config
103109
* @param ProductTierPriceExtensionFactory|null $tierPriceExtensionFactory
110+
* @param SpecialPriceService|null $specialPriceService
104111
* @SuppressWarnings(PHPMD.ExcessiveParameterList)
105112
*/
106113
public function __construct(
@@ -113,7 +120,8 @@ public function __construct(
113120
GroupManagementInterface $groupManagement,
114121
\Magento\Catalog\Api\Data\ProductTierPriceInterfaceFactory $tierPriceFactory,
115122
\Magento\Framework\App\Config\ScopeConfigInterface $config,
116-
?ProductTierPriceExtensionFactory $tierPriceExtensionFactory = null
123+
?ProductTierPriceExtensionFactory $tierPriceExtensionFactory = null,
124+
?SpecialPriceService $specialPriceService = null
117125
) {
118126
$this->_ruleFactory = $ruleFactory;
119127
$this->_storeManager = $storeManager;
@@ -126,6 +134,18 @@ public function __construct(
126134
$this->config = $config;
127135
$this->tierPriceExtensionFactory = $tierPriceExtensionFactory ?: ObjectManager::getInstance()
128136
->get(ProductTierPriceExtensionFactory::class);
137+
$this->specialPriceService = $specialPriceService ?: ObjectManager::getInstance()
138+
->get(SpecialPriceService::class);
139+
}
140+
141+
/**
142+
* Returns the SpecialPriceService instance
143+
*
144+
* @return SpecialPriceService|null
145+
*/
146+
protected function getSpecialPriceService(): ?SpecialPriceService
147+
{
148+
return $this->specialPriceService;
129149
}
130150

131151
/**
@@ -642,6 +662,9 @@ public function calculateSpecialPrice(
642662
$store = null
643663
) {
644664
if ($specialPrice !== null && $specialPrice != false) {
665+
666+
$specialPriceTo = $this->specialPriceService->execute($specialPriceTo);
667+
645668
if ($this->_localeDate->isScopeDateInInterval($store, $specialPriceFrom, $specialPriceTo)) {
646669
$finalPrice = min($finalPrice, (float) $specialPrice);
647670
}

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

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,13 @@
77
namespace Magento\Catalog\Pricing\Price;
88

99
use Magento\Catalog\Model\Product;
10+
use Magento\Framework\App\ObjectManager;
1011
use Magento\Framework\Pricing\Adjustment\CalculatorInterface;
1112
use Magento\Framework\Pricing\Price\AbstractPrice;
1213
use Magento\Framework\Pricing\Price\BasePriceProviderInterface;
1314
use Magento\Framework\Stdlib\DateTime\TimezoneInterface;
1415
use Magento\Store\Api\Data\WebsiteInterface;
16+
use Magento\Catalog\Model\Pricing\SpecialPriceService;
1517

1618
/**
1719
* Special price model
@@ -21,29 +23,38 @@ class SpecialPrice extends AbstractPrice implements SpecialPriceInterface, BaseP
2123
/**
2224
* Price type special
2325
*/
24-
const PRICE_CODE = 'special_price';
26+
public const PRICE_CODE = 'special_price';
2527

2628
/**
2729
* @var TimezoneInterface
2830
*/
2931
protected $localeDate;
3032

33+
/**
34+
* @var SpecialPriceService
35+
*/
36+
private SpecialPriceService $specialPriceService;
37+
3138
/**
3239
* @param Product $saleableItem
3340
* @param float $quantity
3441
* @param CalculatorInterface $calculator
3542
* @param \Magento\Framework\Pricing\PriceCurrencyInterface $priceCurrency
3643
* @param TimezoneInterface $localeDate
44+
* @param SpecialPriceService|null $specialPriceService
3745
*/
3846
public function __construct(
3947
Product $saleableItem,
4048
$quantity,
4149
CalculatorInterface $calculator,
4250
\Magento\Framework\Pricing\PriceCurrencyInterface $priceCurrency,
43-
TimezoneInterface $localeDate
51+
TimezoneInterface $localeDate,
52+
?SpecialPriceService $specialPriceService = null
4453
) {
4554
parent::__construct($saleableItem, $quantity, $calculator, $priceCurrency);
4655
$this->localeDate = $localeDate;
56+
$this->specialPriceService = $specialPriceService ?: ObjectManager::getInstance()
57+
->get(SpecialPriceService::class);
4758
}
4859

4960
/**
@@ -103,10 +114,12 @@ public function getSpecialToDate()
103114
*/
104115
public function isScopeDateInInterval()
105116
{
117+
$dateTo = $this->specialPriceService->execute($this->getSpecialToDate());
118+
106119
return $this->localeDate->isScopeDateInInterval(
107120
WebsiteInterface::ADMIN_CODE,
108121
$this->getSpecialFromDate(),
109-
$this->getSpecialToDate()
122+
$dateTo
110123
);
111124
}
112125

app/code/Magento/Catalog/Test/Mftf/Test/AdminAddInStockProductToTheCartTest.xml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@
4848
<argument name="value" value="Yes"/>
4949
</actionGroup>
5050
<actionGroup ref="AdminFillAdvancedInventoryQtyActionGroup" stepKey="fillProductQty">
51-
<argument name="qty" value="5"/>
51+
<argument name="qty" value="8"/>
5252
</actionGroup>
5353
<comment userInput="Comment is added to preserve the step key for backward compatibility" stepKey="uncheckMiniQtyCheckBox"/>
5454
<actionGroup ref="AdminSetMinAllowedQtyForProductActionGroup" stepKey="fillMiniAllowedQty">
@@ -111,17 +111,17 @@
111111
<argument name="productQty" value="4"/>
112112
</actionGroup>
113113
<actionGroup ref="StorefrontAddProductToCartWithQtyActionGroup" stepKey="addAdditionalProductToCart">
114-
<argument name="productQty" value="1"/>
114+
<argument name="productQty" value="4"/>
115115
</actionGroup>
116116
<comment userInput="Comment is added to preserve the step key for backward compatibility" stepKey="seeSuccessSaveMessage"/>
117117
<seeElement selector="{{StorefrontMinicartSection.quantity(6)}}" stepKey="seeAddedProductQuantityInCart"/>
118118
<actionGroup ref="StorefrontClickOnMiniCartActionGroup" stepKey="clickOnMiniCart"/>
119-
<executeJS function="return {{SimpleProduct.price}} * 5" stepKey="expectedCartSubtotal"/>
119+
<executeJS function="return {{SimpleProduct.price}} * 8" stepKey="expectedCartSubtotal"/>
120120
<actionGroup ref="AssertStorefrontMiniCartItemsActionGroup" stepKey="seeProductNameInMiniCart">
121121
<argument name="productName" value="{{SimpleProduct.name}}"/>
122122
<argument name="productPrice" value="{{SimpleProduct.price}}"/>
123123
<argument name="cartSubtotal" value="{$expectedCartSubtotal}" />
124-
<argument name="qty" value="5"/>
124+
<argument name="qty" value="8"/>
125125
</actionGroup>
126126
<comment userInput="Comment is added to preserve the step key for backward compatibility" stepKey="seeProductPriceInMiniCart"/>
127127
<comment userInput="Comment is added to preserve the step key for backward compatibility" stepKey="seeCheckOutButtonInMiniCart"/>

0 commit comments

Comments
 (0)