Skip to content

Commit cd8c5d3

Browse files
Merge branch '2.4-develop' into missing-csp-allowlists
2 parents b9c813e + 132b9e6 commit cd8c5d3

File tree

58 files changed

+1579
-396
lines changed

Some content is hidden

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

58 files changed

+1579
-396
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/AdminNotification/Controller/Adminhtml/Notification/Index.php

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
namespace Magento\AdminNotification\Controller\Adminhtml\Notification;
77

88
use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface;
9+
use Magento\Framework\View\Result\Page;
910

1011
class Index extends \Magento\AdminNotification\Controller\Adminhtml\Notification implements HttpGetActionInterface
1112
{
@@ -14,14 +15,14 @@ class Index extends \Magento\AdminNotification\Controller\Adminhtml\Notification
1415
*/
1516
public function execute()
1617
{
17-
$this->_view->loadLayout();
18-
$this->_setActiveMenu(
19-
'Magento_AdminNotification::system_adminnotification'
20-
)->_addBreadcrumb(
18+
/** @var Page $resultPage */
19+
$resultPage = $this->resultFactory->create(\Magento\Framework\Controller\ResultFactory::TYPE_PAGE);
20+
$resultPage->setActiveMenu('Magento_AdminNotification::system_adminnotification');
21+
$resultPage->addBreadcrumb(
2122
__('Messages Inbox'),
2223
__('Messages Inbox')
2324
);
24-
$this->_view->getPage()->getConfig()->getTitle()->prepend(__('Notifications'));
25-
return $this->_view->renderLayout();
25+
$resultPage->getConfig()->getTitle()->prepend(__('Notifications'));
26+
return $resultPage;
2627
}
2728
}

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/Mftf/Section/StorefrontCategoryProductSection.xml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@
1111
<section name="StorefrontCategoryProductSection">
1212
<element name="priceToByProductId" type="text" selector="div[data-product-id='{{id}}'] .price-to" parameterized="true"/>
1313
<element name="priceFromByProductId" type="text" selector="div[data-product-id='{{id}}'] .price-from" parameterized="true"/>
14+
<element name="priceAsLowAsLabelByProductId" type="text" selector="div[data-product-id='{{id}}'] p.minimal-price span.price-container > span.price-label" parameterized="true"/>
15+
<element name="priceAsLowAsFinalPriceByProductId" type="text" selector="div[data-product-id='{{id}}'] p.minimal-price span.price-container > span.price-wrapper > span.price" parameterized="true"/>
16+
<element name="oldPriceByProductId" type="text" selector="div[data-product-id='{{id}}'] .old-price" parameterized="true"/>
1417
<element name="priceToByProductSku" type="text" selector="//li[.//form[@data-product-sku='{{sku}}']]//p[contains(@class,'price-to')]" parameterized="true"/>
1518
</section>
1619
</sections>

app/code/Magento/Bundle/Test/Mftf/Section/StorefrontProductInfoMainSection.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
<element name="priceTo" type="text" selector=".product-info-price .price-to"/>
1414
<element name="minPrice" type="text" selector="span[data-price-type='minPrice']"/>
1515
<element name="maxPrice" type="text" selector="span[data-price-type='minPrice']"/>
16+
<element name="asLowAsFinalPriceLabel" type="text" selector="div.price-box.price-final_price p.minimal-price > span.price-final_price span.price-label"/>
1617
<element name="asLowAsFinalPrice" type="text" selector="div.price-box.price-final_price p.minimal-price > span.price-final_price span.price"/>
1718
<element name="fixedFinalPrice" type="text" selector="div.price-box.price-final_price span.price-final_price span.price-wrapper span.price"/>
1819
<element name="productBundleOptionsCheckbox" type="checkbox" selector="//*[@id='product-options-wrapper']//div[@class='fieldset']//label[contains(.,'{{childName}}')]/../input" parameterized="true" timeout="30"/>
Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!--
3+
/**
4+
* Copyright 2025 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="StorefrontCheckPriceForFixedBundleProductWithCatalogRuleTest">
12+
<annotations>
13+
<features value="Bundle"/>
14+
<stories value="View bundle product price"/>
15+
<title value="Check prices for bundle product with catalog rule"/>
16+
<description value="To Verify Catalog Rules Prices for Fixed Bundle Product on Storefront"/>
17+
<testCaseId value="AC-3973"/>
18+
<severity value="MAJOR"/>
19+
</annotations>
20+
<before>
21+
<!-- Precondition step1,2: create category, catalog rule price with data and apply to products -->
22+
<createData entity="_defaultCategory" stepKey="category1"/>
23+
<!-- login to backend -->
24+
<actionGroup ref="AdminLoginActionGroup" stepKey="loginAsAdmin"/>
25+
<actionGroup ref="AdminCatalogPriceRuleDeleteAllActionGroup" stepKey="clearExistingCatalogPriceRules"/>
26+
<actionGroup ref="AdminOpenNewCatalogPriceRuleFormPageActionGroup" stepKey="openNewCatalogPriceRulePage"/>
27+
<actionGroup ref="AdminCatalogPriceRuleFillMainInfoActionGroup" stepKey="fillMainInfoForCatalogPriceRule"/>
28+
<actionGroup ref="AdminFillCatalogRuleConditionActionGroup" stepKey="fillConditionsForCatalogPriceRule">
29+
<argument name="conditionValue" value="$category1.id$"/>
30+
</actionGroup>
31+
<actionGroup ref="AdminCatalogPriceRuleFillActionsActionGroup" stepKey="fillActionsForCatalogPriceRule">
32+
<argument name="apply" value="by_percent"/>
33+
<argument name="discountAmount" value="10"/>
34+
</actionGroup>
35+
<actionGroup ref="AdminCatalogPriceRuleSaveAndApplyActionGroup" stepKey="saveAndApplyFirstPriceRule"/>
36+
<!-- Precondition step3: create 6 simple products -->
37+
<!-- Precondition step3.1: create first product -->
38+
<createData entity="_defaultProduct" stepKey="firstProduct">
39+
<field key="price">10.25</field>
40+
<field key="quantity">100</field>
41+
<requiredEntity createDataKey="category1"/>
42+
</createData>
43+
<!-- Precondition step3.2: create second product -->
44+
<createData entity="_defaultProduct" stepKey="secondProduct">
45+
<field key="price">15.50</field>
46+
<field key="status">0</field>
47+
<field key="quantity">100</field>
48+
<requiredEntity createDataKey="category1"/>
49+
</createData>
50+
<!-- Precondition step3.3: create third product -->
51+
<createData entity="_defaultProduct" stepKey="thirdProduct">
52+
<field key="price">20.75</field>
53+
<field key="quantity">100</field>
54+
<requiredEntity createDataKey="category1"/>
55+
</createData>
56+
<!-- Precondition step3.4: create fourth product -->
57+
<createData entity="_defaultProduct" stepKey="fourthProduct">
58+
<field key="price">25.15</field>
59+
<field key="quantity">100</field>
60+
<requiredEntity createDataKey="category1"/>
61+
</createData>
62+
<!-- Precondition step3.5: create fifth product -->
63+
<createData entity="_defaultProduct" stepKey="fifthProduct">
64+
<field key="price">30.45</field>
65+
<field key="status">0</field>
66+
<field key="quantity">100</field>
67+
<requiredEntity createDataKey="category1"/>
68+
</createData>
69+
<!-- Precondition step3.6: create sixth product -->
70+
<createData entity="_defaultProduct" stepKey="sixthProduct">
71+
<field key="price">35.65</field>
72+
<field key="quantity">100</field>
73+
<requiredEntity createDataKey="category1"/>
74+
</createData>
75+
<!-- Precondition step4: Create Bundle product -->
76+
<createData entity="FixedBundleProduct" stepKey="createFixedBundleProduct">
77+
<requiredEntity createDataKey="category1"/>
78+
<field key="price">110.25</field>
79+
</createData>
80+
<!-- Precondition step4.6: Add options to the product -->
81+
<createData entity="MultipleSelectOption" stepKey="createBundleOption1_1">
82+
<requiredEntity createDataKey="createFixedBundleProduct"/>
83+
<field key="required">true</field>
84+
</createData>
85+
<createData entity="DropDownBundleOption" stepKey="createBundleOption1_2">
86+
<requiredEntity createDataKey="createFixedBundleProduct"/>
87+
<field key="required">false</field>
88+
</createData>
89+
<!-- Precondition step4.6.1: add products to multiselect option -->
90+
<createData entity="ApiBundleLinkFixed" stepKey="linkOptionToFirstProduct">
91+
<requiredEntity createDataKey="createFixedBundleProduct"/>
92+
<requiredEntity createDataKey="createBundleOption1_1"/>
93+
<requiredEntity createDataKey="firstProduct"/>
94+
<field key="qty">2</field>
95+
<field key="price">25.15</field>
96+
</createData>
97+
<createData entity="ApiBundleLinkFixed" stepKey="linkOptionToSecondProduct">
98+
<requiredEntity createDataKey="createFixedBundleProduct"/>
99+
<requiredEntity createDataKey="createBundleOption1_1"/>
100+
<requiredEntity createDataKey="secondProduct"/>
101+
<field key="qty">5</field>
102+
<field key="price">5</field>
103+
<field key="price_type">1</field>
104+
</createData>
105+
<createData entity="ApiBundleLinkFixed" stepKey="linkOptionToThirdProduct">
106+
<requiredEntity createDataKey="createFixedBundleProduct"/>
107+
<requiredEntity createDataKey="createBundleOption1_1"/>
108+
<requiredEntity createDataKey="thirdProduct"/>
109+
<field key="qty">2</field>
110+
<field key="price">30</field>
111+
<field key="price_type">1</field>
112+
</createData>
113+
<!-- Precondition step4.6.2: add products to single select option -->
114+
<createData entity="ApiBundleLinkFixed" stepKey="linkOptionToFourthProduct">
115+
<requiredEntity createDataKey="createFixedBundleProduct"/>
116+
<requiredEntity createDataKey="createBundleOption1_2"/>
117+
<requiredEntity createDataKey="fourthProduct"/>
118+
<field key="qty">2</field>
119+
<field key="price">10</field>
120+
<field key="price_type">1</field>
121+
</createData>
122+
<createData entity="ApiBundleLinkFixed" stepKey="linkOptionToFifthProduct">
123+
<requiredEntity createDataKey="createFixedBundleProduct"/>
124+
<requiredEntity createDataKey="createBundleOption1_2"/>
125+
<requiredEntity createDataKey="fifthProduct"/>
126+
<field key="qty">5</field>
127+
<field key="price">20.55</field>
128+
</createData>
129+
<createData entity="ApiBundleLinkFixed" stepKey="linkOptionToSixthProduct">
130+
<requiredEntity createDataKey="createFixedBundleProduct"/>
131+
<requiredEntity createDataKey="createBundleOption1_2"/>
132+
<requiredEntity createDataKey="sixthProduct"/>
133+
<field key="qty">1</field>
134+
<field key="price">45</field>
135+
<field key="price_type">1</field>
136+
</createData>
137+
</before>
138+
<after>
139+
<!-- delete created product data -->
140+
<deleteData createDataKey="firstProduct" stepKey="deleteFirstProduct"/>
141+
<deleteData createDataKey="secondProduct" stepKey="deleteSecondProduct"/>
142+
<deleteData createDataKey="thirdProduct" stepKey="deleteThirdProduct"/>
143+
<deleteData createDataKey="fourthProduct" stepKey="deleteFourthProduct"/>
144+
<deleteData createDataKey="fifthProduct" stepKey="deleteFifthProduct"/>
145+
<deleteData createDataKey="sixthProduct" stepKey="deleteSixthProduct"/>
146+
<!-- delete created bundle product and catalog rule -->
147+
<deleteData createDataKey="createFixedBundleProduct" stepKey="deleteBundleProduct"/>
148+
<actionGroup ref="AdminCatalogPriceRuleDeleteAllActionGroup" stepKey="deleteCatalogPriceRules"/>
149+
<!-- logout admin -->
150+
<actionGroup ref="AdminLogoutActionGroup" stepKey="logout"/>
151+
</after>
152+
<!-- check product prices on category and product page -->
153+
<!-- step1: Go to storefront category page -->
154+
<actionGroup ref="StorefrontNavigateCategoryPageActionGroup" stepKey="amOnCategoryPage">
155+
<argument name="category" value="$$category1$$"/>
156+
</actionGroup>
157+
<!-- step2: check price for fixed bundled on category page -->
158+
<grabTextFrom selector="{{StorefrontCategoryProductSection.priceAsLowAsFinalPriceByProductId($$createFixedBundleProduct.id$$)}}" stepKey="grabProductFinalPriceOnCategory"/>
159+
<assertEquals message="ExpectedFinalPriceOnCategory" stepKey="assertBundleProductFinalPriceOnCategory">
160+
<actualResult type="variable">grabProductFinalPriceOnCategory</actualResult>
161+
<expectedResult type="string">$149.53</expectedResult>
162+
</assertEquals>
163+
<grabTextFrom selector="{{StorefrontCategoryProductSection.oldPriceByProductId($$createFixedBundleProduct.id$$)}}" stepKey="grabProductOldPriceOnCategory"/>
164+
<assertEquals message="ExpectedOldPriceOnCategory" stepKey="assertBundleProductOldPriceOnCategory">
165+
<actualResult type="variable">grabProductOldPriceOnCategory</actualResult>
166+
<expectedResult type="string">Regular Price $160.55</expectedResult>
167+
</assertEquals>
168+
<grabTextFrom selector="{{StorefrontCategoryProductSection.priceAsLowAsLabelByProductId($$createFixedBundleProduct.id$$)}}" stepKey="grabProductFinalPriceLabelOnCategory"/>
169+
<assertEquals message="ExpectedFinalPriceLabelOnCategory" stepKey="assertBundleProductFinalPriceLabelOnCategory">
170+
<actualResult type="variable">grabProductFinalPriceLabelOnCategory</actualResult>
171+
<expectedResult type="string">As low as</expectedResult>
172+
</assertEquals>
173+
<!-- step3: Go to fixed bundled product page -->
174+
<actionGroup ref="StorefrontOpenProductPageActionGroup" stepKey="openBundleProductPage">
175+
<argument name="productUrl" value="$$createFixedBundleProduct.custom_attributes[url_key]$$"/>
176+
</actionGroup>
177+
<!-- step4: check price for fixed Bundle on product page -->
178+
<grabTextFrom selector="{{StorefrontProductInfoMainSection.oldPrice}}" stepKey="grabProductOldPrice"/>
179+
<assertEquals message="ExpectedOldPrice" stepKey="assertBundleProductOldPrice">
180+
<actualResult type="variable">grabProductOldPrice</actualResult>
181+
<expectedResult type="string">Regular Price $160.55</expectedResult>
182+
</assertEquals>
183+
<grabTextFrom selector="{{StorefrontProductInfoMainSection.asLowAsFinalPrice}}" stepKey="grabProductFinalPrice"/>
184+
<assertEquals message="ExpectedFinalPrice" stepKey="assertBundleProductPrice">
185+
<actualResult type="variable">grabProductFinalPrice</actualResult>
186+
<expectedResult type="string">$149.53</expectedResult>
187+
</assertEquals>
188+
<grabTextFrom selector="{{StorefrontProductInfoMainSection.asLowAsFinalPriceLabel}}" stepKey="grabProductPriceLabel"/>
189+
<assertEquals message="ExpectedPriceLabel" stepKey="assertProductPriceLabel">
190+
<actualResult type="variable">grabProductPriceLabel</actualResult>
191+
<expectedResult type="string">As low as</expectedResult>
192+
</assertEquals>
193+
</test>
194+
</tests>

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
}

0 commit comments

Comments
 (0)