Skip to content

Commit 392dbf6

Browse files
Merge branch '2.4-develop-mainline' into 21853
2 parents 873b814 + 5977749 commit 392dbf6

File tree

49 files changed

+1518
-213
lines changed

Some content is hidden

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

49 files changed

+1518
-213
lines changed

.github/stale.yml

+68
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
# Configuration for probot-stale - https://github.com/probot/stale
2+
3+
# Number of days of inactivity before an Issue or Pull Request becomes stale
4+
daysUntilStale: 76
5+
6+
# Number of days of inactivity before an Issue or Pull Request with the stale label is closed.
7+
# Set to false to disable. If disabled, issues still need to be closed manually, but will remain marked as stale.
8+
daysUntilClose: 14
9+
10+
# Only issues or pull requests with all of these labels are check if stale. Defaults to `[]` (disabled)
11+
onlyLabels: []
12+
13+
# Issues or Pull Requests with these labels will never be considered stale. Set to `[]` to disable
14+
exemptLabels:
15+
- "Priority: P0"
16+
- "Priority: P1"
17+
- "Priority: P2"
18+
- "Progress: dev in progress"
19+
- "Progress: PR in progress"
20+
- "Progress: done"
21+
- "B2B: GraphQL"
22+
- "Progress: PR Created"
23+
- "PAP"
24+
- "Project: Login as Customer"
25+
- "Project: GraphQL"
26+
27+
# Set to true to ignore issues in a project (defaults to false)
28+
exemptProjects: false
29+
30+
# Set to true to ignore issues in a milestone (defaults to false)
31+
exemptMilestones: false
32+
33+
# Set to true to ignore issues with an assignee (defaults to false)
34+
exemptAssignees: false
35+
36+
# Label to use when marking as stale
37+
staleLabel: "stale issue"
38+
39+
# Comment to post when marking as stale. Set to `false` to disable
40+
markComment: >
41+
This issue has been automatically marked as stale because it has not had
42+
recent activity. It will be closed after 14 days if no further activity occurs. Thank you
43+
for your contributions.
44+
# Comment to post when removing the stale label.
45+
# unmarkComment: >
46+
# Your comment here.
47+
48+
# Comment to post when closing a stale Issue or Pull Request.
49+
# closeComment: >
50+
# Your comment here.
51+
52+
# Limit the number of actions per hour, from 1-30. Default is 30
53+
limitPerRun: 30
54+
55+
# Limit to only `issues` or `pulls`
56+
only: issues
57+
58+
# Optionally, specify configuration settings that are specific to just 'issues' or 'pulls':
59+
# pulls:
60+
# daysUntilStale: 30
61+
# markComment: >
62+
# This pull request has been automatically marked as stale because it has not had
63+
# recent activity. It will be closed if no further activity occurs. Thank you
64+
# for your contributions.
65+
66+
# issues:
67+
# exemptLabels:
68+
# - confirmed

app/code/Magento/AsynchronousOperations/etc/db_schema.xml

+1-1
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434
<table name="magento_operation" resource="default" engine="innodb" comment="Operation entity">
3535
<column xsi:type="int" name="id" padding="10" unsigned="true" nullable="false" identity="true"
3636
comment="Operation ID"/>
37-
<column xsi:type="int" name="operation_key" padding="10" unsigned="true" nullable="false"
37+
<column xsi:type="int" name="operation_key" padding="10" unsigned="true" nullable="true"
3838
comment="Operation Key"/>
3939
<column xsi:type="varbinary" name="bulk_uuid" nullable="true" length="39" comment="Related Bulk UUID"/>
4040
<column xsi:type="varchar" name="topic_name" nullable="true" length="255"

app/code/Magento/Catalog/Controller/Adminhtml/Product/Attribute/Validate.php

+8-2
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
class Validate extends AttributeAction implements HttpGetActionInterface, HttpPostActionInterface
3434
{
3535
const DEFAULT_MESSAGE_KEY = 'message';
36+
private const RESERVED_ATTRIBUTE_CODES = ['product_type', 'type_id'];
3637

3738
/**
3839
* @var JsonFactory
@@ -145,11 +146,16 @@ public function execute()
145146
);
146147
}
147148

148-
if ($attribute->getId() && !$attributeId || $attributeCode === 'product_type' || $attributeCode === 'type_id') {
149+
if (in_array($attributeCode, self::RESERVED_ATTRIBUTE_CODES, true)) {
150+
$message = __('Code (%1) is a reserved key and cannot be used as attribute code.', $attributeCode);
151+
$this->setMessageToResponse($response, [$message]);
152+
$response->setError(true);
153+
}
154+
155+
if ($attribute->getId() && !$attributeId) {
149156
$message = strlen($this->getRequest()->getParam('attribute_code'))
150157
? __('An attribute with this code already exists.')
151158
: __('An attribute with the same code (%1) already exists.', $attributeCode);
152-
153159
$this->setMessageToResponse($response, [$message]);
154160

155161
$response->setError(true);

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

+52-14
Original file line numberDiff line numberDiff line change
@@ -9,19 +9,27 @@
99
use Magento\Catalog\Model\Product;
1010
use Magento\Customer\Api\GroupManagementInterface;
1111
use Magento\Customer\Model\Session;
12+
use Magento\Framework\App\Config\ScopeConfigInterface;
13+
use Magento\Framework\App\ObjectManager;
1214
use Magento\Framework\Pricing\Adjustment\CalculatorInterface;
1315
use Magento\Framework\Pricing\Amount\AmountInterface;
1416
use Magento\Framework\Pricing\Price\AbstractPrice;
1517
use Magento\Framework\Pricing\Price\BasePriceProviderInterface;
18+
use Magento\Framework\Pricing\PriceCurrencyInterface;
1619
use Magento\Framework\Pricing\PriceInfoInterface;
1720
use Magento\Customer\Model\Group\RetrieverInterface as CustomerGroupRetrieverInterface;
21+
use Magento\Tax\Model\Config;
1822

1923
/**
2024
* @api
2125
* @since 100.0.2
26+
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
27+
* @SuppressWarnings(PHPMD.CookieAndSessionMisuse)
2228
*/
2329
class TierPrice extends AbstractPrice implements TierPriceInterface, BasePriceProviderInterface
2430
{
31+
private const XML_PATH_TAX_DISPLAY_TYPE = 'tax/display/type';
32+
2533
/**
2634
* Price type tier
2735
*/
@@ -62,35 +70,43 @@ class TierPrice extends AbstractPrice implements TierPriceInterface, BasePricePr
6270
*/
6371
private $customerGroupRetriever;
6472

73+
/**
74+
* @var ScopeConfigInterface
75+
*/
76+
private $scopeConfig;
77+
6578
/**
6679
* @param Product $saleableItem
6780
* @param float $quantity
6881
* @param CalculatorInterface $calculator
69-
* @param \Magento\Framework\Pricing\PriceCurrencyInterface $priceCurrency
82+
* @param PriceCurrencyInterface $priceCurrency
7083
* @param Session $customerSession
7184
* @param GroupManagementInterface $groupManagement
7285
* @param CustomerGroupRetrieverInterface|null $customerGroupRetriever
86+
* @param ScopeConfigInterface|null $scopeConfig
7387
*/
7488
public function __construct(
7589
Product $saleableItem,
7690
$quantity,
7791
CalculatorInterface $calculator,
78-
\Magento\Framework\Pricing\PriceCurrencyInterface $priceCurrency,
92+
PriceCurrencyInterface $priceCurrency,
7993
Session $customerSession,
8094
GroupManagementInterface $groupManagement,
81-
CustomerGroupRetrieverInterface $customerGroupRetriever = null
95+
CustomerGroupRetrieverInterface $customerGroupRetriever = null,
96+
?ScopeConfigInterface $scopeConfig = null
8297
) {
8398
$quantity = (float)$quantity ? $quantity : 1;
8499
parent::__construct($saleableItem, $quantity, $calculator, $priceCurrency);
85100
$this->customerSession = $customerSession;
86101
$this->groupManagement = $groupManagement;
87102
$this->customerGroupRetriever = $customerGroupRetriever
88-
?? \Magento\Framework\App\ObjectManager::getInstance()->get(CustomerGroupRetrieverInterface::class);
103+
?? ObjectManager::getInstance()->get(CustomerGroupRetrieverInterface::class);
89104
if ($saleableItem->hasCustomerGroupId()) {
90105
$this->customerGroup = (int) $saleableItem->getCustomerGroupId();
91106
} else {
92107
$this->customerGroup = (int) $this->customerGroupRetriever->getCustomerGroupId();
93108
}
109+
$this->scopeConfig = $scopeConfig ?: ObjectManager::getInstance()->get(ScopeConfigInterface::class);
94110
}
95111

96112
/**
@@ -136,6 +152,8 @@ protected function isFirstPriceBetter($firstPrice, $secondPrice)
136152
}
137153

138154
/**
155+
* Returns tier price count
156+
*
139157
* @return int
140158
*/
141159
public function getTierPriceCount()
@@ -144,6 +162,8 @@ public function getTierPriceCount()
144162
}
145163

146164
/**
165+
* Returns tier price list
166+
*
147167
* @return array
148168
*/
149169
public function getTierPriceList()
@@ -155,15 +175,32 @@ public function getTierPriceList()
155175
$this->priceList,
156176
function (&$priceData) {
157177
/* convert string value to float */
158-
$priceData['price_qty'] = $priceData['price_qty'] * 1;
178+
$priceData['price_qty'] *= 1;
179+
if ($this->getConfigTaxDisplayType() === Config::DISPLAY_TYPE_BOTH) {
180+
$exclTaxPrice = $this->calculator->getAmount($priceData['price'], $this->product, true);
181+
$priceData['excl_tax_price'] = $exclTaxPrice;
182+
}
159183
$priceData['price'] = $this->applyAdjustment($priceData['price']);
160184
}
161185
);
162186
}
187+
163188
return $this->priceList;
164189
}
165190

166191
/**
192+
* Returns config tax display type
193+
*
194+
* @return int
195+
*/
196+
private function getConfigTaxDisplayType(): int
197+
{
198+
return (int) $this->scopeConfig->getValue(self::XML_PATH_TAX_DISPLAY_TYPE);
199+
}
200+
201+
/**
202+
* Filters tier prices
203+
*
167204
* @param array $priceList
168205
* @return array
169206
*/
@@ -204,6 +241,8 @@ protected function filterTierPrices(array $priceList)
204241
}
205242

206243
/**
244+
* Returns base price
245+
*
207246
* @return float
208247
*/
209248
protected function getBasePrice()
@@ -213,25 +252,22 @@ protected function getBasePrice()
213252
}
214253

215254
/**
216-
* Calculates savings percentage according to the given tier price amount
217-
* and related product price amount.
255+
* Calculates savings percentage according to the given tier price amount and related product price amount.
218256
*
219257
* @param AmountInterface $amount
220-
*
221258
* @return float
222259
*/
223260
public function getSavePercent(AmountInterface $amount)
224261
{
225-
$productPriceAmount = $this->priceInfo->getPrice(
226-
FinalPrice::PRICE_CODE
227-
)->getAmount();
262+
$productPriceAmount = $this->priceInfo->getPrice(FinalPrice::PRICE_CODE)
263+
->getAmount();
228264

229-
return round(
230-
100 - ((100 / $productPriceAmount->getValue()) * $amount->getValue())
231-
);
265+
return round(100 - ((100 / $productPriceAmount->getValue()) * $amount->getValue()));
232266
}
233267

234268
/**
269+
* Apply adjustment to price
270+
*
235271
* @param float|string $price
236272
* @return \Magento\Framework\Pricing\Amount\AmountInterface
237273
*/
@@ -314,6 +350,8 @@ protected function getStoredTierPrices()
314350
}
315351

316352
/**
353+
* Return is percentage discount
354+
*
317355
* @return bool
318356
*/
319357
public function isPercentageDiscount()

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

+14
Original file line numberDiff line numberDiff line change
@@ -385,6 +385,20 @@
385385
<data key="default_store_label" unique="suffix">Attribute Store label &lt;span&gt; </data>
386386
<data key="frontend_input">text</data>
387387
</entity>
388+
<entity name="ProductTypeIdAttribute" type="ProductAttribute">
389+
<data key="frontend_label">Type id</data>
390+
<data key="attribute_code">type_id</data>
391+
<data key="frontend_input">text</data>
392+
<data key="is_required">No</data>
393+
<data key="is_required_admin">No</data>
394+
</entity>
395+
<entity name="ProductProductTypeAttribute" type="ProductAttribute">
396+
<data key="frontend_label">Product type</data>
397+
<data key="attribute_code">product_type</data>
398+
<data key="frontend_input">text</data>
399+
<data key="is_required">No</data>
400+
<data key="is_required_admin">No</data>
401+
</entity>
388402
<!-- Product attribute from file "export_import_configurable_product.csv" -->
389403
<entity name="ProductAttributeWithTwoOptionsForExportImport" extends="productAttributeDropdownTwoOptions" type="ProductAttribute">
390404
<data key="attribute_code">attribute</data>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!--
3+
/**
4+
* Copyright © Magento, Inc. All rights reserved.
5+
* See COPYING.txt for license details.
6+
*/
7+
-->
8+
<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
9+
xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd">
10+
<test name="CreateProductAttributeEntityWithReservedKeysTest">
11+
<annotations>
12+
<features value="Catalog"/>
13+
<stories value="Create Product Attributes"/>
14+
<title value="Attributess with reserved codes should not be created"/>
15+
<description value="Admin should not be able to create product attribute with reserved codes"/>
16+
<severity value="MINOR"/>
17+
<testCaseId value="MC-37806"/>
18+
<group value="catalog"/>
19+
</annotations>
20+
21+
<before>
22+
<actionGroup ref="AdminLoginActionGroup" stepKey="loginAsAdmin"/>
23+
</before>
24+
<after>
25+
<actionGroup ref="AdminLogoutActionGroup" stepKey="logout"/>
26+
</after>
27+
28+
<!--Navigate to Stores > Attributes > Product.-->
29+
<amOnPage url="{{AdminProductAttributeGridPage.url}}" stepKey="goToProductAttributesGrid"/>
30+
31+
<!--Create new Product Attribute as TextField, with type_id code.-->
32+
<actionGroup ref="CreateProductAttributeActionGroup" stepKey="createAttribute">
33+
<argument name="attribute" value="ProductTypeIdAttribute"/>
34+
</actionGroup>
35+
<see stepKey="seeErrorMessage" selector="{{AdminMessagesSection.errorMessage}}" userInput="Code (type_id) is a reserved key and cannot be used as attribute code."/>
36+
37+
<!--Navigate to Stores > Attributes > Product.-->
38+
<amOnPage url="{{AdminProductAttributeGridPage.url}}" stepKey="backToProductAttributesGrid"/>
39+
40+
<!--Create new Product Attribute as TextField, with product_type code.-->
41+
<actionGroup ref="CreateProductAttributeActionGroup" stepKey="createAttribute2">
42+
<argument name="attribute" value="ProductProductTypeAttribute"/>
43+
</actionGroup>
44+
45+
<see stepKey="seeErrorMessage2" selector="{{AdminMessagesSection.errorMessage}}" userInput="Code (product_type) is a reserved key and cannot be used as attribute code."/>
46+
</test>
47+
</tests>

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

+1
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,7 @@ Catalog,Catalog
209209
"You saved the product attribute.","You saved the product attribute."
210210
"An attribute with this code already exists.","An attribute with this code already exists."
211211
"An attribute with the same code (%1) already exists.","An attribute with the same code (%1) already exists."
212+
"Code (%1) is a reserved key and cannot be used as attribute code.","Code (%1) is a reserved key and cannot be used as attribute code."
212213
"The value of Admin must be unique.","The value of Admin must be unique."
213214
"The value of Admin scope can't be empty.","The value of Admin scope can't be empty."
214215
"You duplicated the product.","You duplicated the product."

app/code/Magento/CatalogUrlRewrite/Model/CategoryUrlRewriteGenerator.php

+7-1
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,11 @@ class CategoryUrlRewriteGenerator
5454
*/
5555
private $mergeDataProviderPrototype;
5656

57+
/**
58+
* @var CategoryRepositoryInterface
59+
*/
60+
private $categoryRepository;
61+
5762
/**
5863
* @var bool
5964
*/
@@ -124,10 +129,11 @@ protected function generateForGlobalScope(
124129
$mergeDataProvider = clone $this->mergeDataProviderPrototype;
125130
$categoryId = $category->getId();
126131
foreach ($category->getStoreIds() as $storeId) {
127-
$category->setStoreId($storeId);
128132
if (!$this->isGlobalScope($storeId)
129133
&& $this->isOverrideUrlsForStore($storeId, $categoryId, $overrideStoreUrls)
130134
) {
135+
$category = clone $category; // prevent undesired side effects on original object
136+
$category->setStoreId($storeId);
131137
$this->updateCategoryUrlForStore($storeId, $category);
132138
$mergeDataProvider->merge($this->generateForSpecificStoreView($storeId, $category, $rootCategoryId));
133139
}

app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/CategoryUrlRewriteGeneratorTest.php

+1
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,7 @@ public function testGenerationForGlobalScope()
158158
],
159159
$this->categoryUrlRewriteGenerator->generate($this->category, false, $categoryId)
160160
);
161+
$this->assertEquals(0, $this->category->getStoreId(), 'Store ID should not have been modified');
161162
}
162163

163164
/**

0 commit comments

Comments
 (0)