Skip to content

Commit ff4bb64

Browse files
AnujNehraAnujNehra
authored andcommitted
Merge branch '2.4-develop' into ACP2E-2212
2 parents 25c4e11 + 821bcac commit ff4bb64

File tree

629 files changed

+44879
-35162
lines changed

Some content is hidden

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

629 files changed

+44879
-35162
lines changed

app/code/Magento/Backend/Test/Unit/Model/Menu/Config/_files/invalidMenuXmlArray.php

Lines changed: 314 additions & 128 deletions
Large diffs are not rendered by default.

app/code/Magento/Backend/view/adminhtml/web/js/dashboard/chart.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ define([
1717
$.widget('mage.dashboardChart', {
1818
options: {
1919
updateUrl: '',
20+
responsive: true,
21+
maintainAspectRatio: false,
2022
periodSelect: null,
2123
periodUnits: [],
2224
precision: 0,

app/code/Magento/Bundle/Pricing/Adjustment/DefaultSelectionPriceListProvider.php

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,10 @@ public function getPriceList(Product $bundleProduct, $searchMin, $useRegularPric
8585
[(int)$option->getOptionId()],
8686
$bundleProduct
8787
);
88-
$selectionsCollection->setFlag('has_stock_status_filter', true);
88+
89+
if ((int)$bundleProduct->getPriceType() !== Price::PRICE_TYPE_FIXED) {
90+
$selectionsCollection->setFlag('has_stock_status_filter', true);
91+
}
8992
$selectionsCollection->removeAttributeToSelect();
9093

9194
if (!$useRegularPrice) {

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
<element name="asLowAsFinalPrice" type="text" selector="div.price-box.price-final_price p.minimal-price > span.price-final_price span.price"/>
1717
<element name="fixedFinalPrice" type="text" selector="div.price-box.price-final_price > span.price-final_price span.price"/>
1818
<element name="productBundleOptionsCheckbox" type="checkbox" selector="//*[@id='product-options-wrapper']//div[@class='fieldset']//label[contains(.,'{{childName}}')]/../input" parameterized="true" timeout="30"/>
19+
<element name="productBundleOneOptionInput" type="input" selector="//*[@id='product-options-wrapper']//div[@class='fieldset']//label[contains(.,'{{childName}}')]/..//input[contains(@class, 'option')]" parameterized="true" timeout="30"/>
20+
<element name="productBundleOptionQty" type="input" selector="//*[@id='product-options-wrapper']//div[@class='fieldset']//label[contains(.,'{{childName}}')]/..//input[contains(@class, 'qty')]" parameterized="true" timeout="30"/>
1921
<element name="includingTaxPrice" type="text" selector=".//*[@class='price-wrapper price-including-tax']/span"/>
2022
<element name="excludingTaxPrice" type="text" selector=".//*[@class='price-wrapper price-excluding-tax']/span"/>
2123
</section>

app/code/Magento/Bundle/Test/Unit/Pricing/Adjustment/DefaultSelectionPriceListProviderTest.php

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
namespace Magento\Bundle\Test\Unit\Pricing\Adjustment;
99

1010
use Magento\Bundle\Model\Option;
11+
use Magento\Bundle\Model\Product\Price;
1112
use Magento\Bundle\Model\Product\Type;
1213
use Magento\Bundle\Model\ResourceModel\Option\Collection;
1314
use Magento\Bundle\Model\ResourceModel\Selection\Collection as SelectionCollection;
@@ -109,6 +110,8 @@ protected function setUp(): void
109110

110111
$this->product = $this->getMockBuilder(Product::class)
111112
->disableOriginalConstructor()
113+
->addMethods(['getPriceType'])
114+
->onlyMethods(['getTypeInstance', 'isSalable'])
112115
->getMock();
113116
$this->optionsCollection = $this->getMockBuilder(Collection::class)
114117
->disableOriginalConstructor()
@@ -142,6 +145,54 @@ public function testGetPriceList(): void
142145
{
143146
$optionId = 1;
144147

148+
$this->typeInstance->expects($this->any())
149+
->method('getOptionsCollection')
150+
->with($this->product)
151+
->willReturn($this->optionsCollection);
152+
$this->product->expects($this->any())
153+
->method('getTypeInstance')
154+
->willReturn($this->typeInstance);
155+
$this->product->expects($this->once())
156+
->method('getPriceType')->willReturn(Price::PRICE_TYPE_FIXED);
157+
$this->optionsCollection->expects($this->once())
158+
->method('getIterator')
159+
->willReturn(new \ArrayIterator([$this->option]));
160+
$this->option->expects($this->once())
161+
->method('getOptionId')
162+
->willReturn($optionId);
163+
$this->typeInstance->expects($this->once())
164+
->method('getSelectionsCollection')
165+
->with([$optionId], $this->product)
166+
->willReturn($this->selectionCollection);
167+
$this->option->expects($this->once())
168+
->method('isMultiSelection')
169+
->willReturn(true);
170+
$this->storeManager->expects($this->once())
171+
->method('getStore')
172+
->willReturn($this->store);
173+
$this->store->expects($this->once())
174+
->method('getWebsiteId')
175+
->willReturn(0);
176+
$this->websiteRepository->expects($this->once())
177+
->method('getDefault')
178+
->willReturn($this->website);
179+
$this->website->expects($this->once())
180+
->method('getId')
181+
->willReturn(1);
182+
$this->selectionCollection->expects($this->once())
183+
->method('getIterator')
184+
->willReturn(new \ArrayIterator([]));
185+
$this->selectionCollection->expects($this->never())
186+
->method('setFlag')
187+
->with('has_stock_status_filter', true);
188+
189+
$this->model->getPriceList($this->product, false, false);
190+
}
191+
192+
public function testGetPriceListForFixedPriceType(): void
193+
{
194+
$optionId = 1;
195+
145196
$this->typeInstance->expects($this->any())
146197
->method('getOptionsCollection')
147198
->with($this->product)

app/code/Magento/Bundle/view/base/web/js/price-bundle.js

Lines changed: 72 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ define([
1111
'underscore',
1212
'mage/template',
1313
'priceUtils',
14+
'jquery/jquery.parsequery',
1415
'priceBox'
1516
], function ($, _, mageTemplate, utils) {
1617
'use strict';
@@ -40,9 +41,14 @@ define([
4041
*/
4142
_init: function initPriceBundle() {
4243
var form = this.element,
43-
options = $(this.options.productBundleSelector, form);
44+
options = $(this.options.productBundleSelector, form),
45+
qty = $(this.options.qtyFieldSelector, form);
46+
47+
// Override defaults with URL query parameters and/or inputs values
48+
this._overrideDefaults();
4449

4550
options.trigger('change');
51+
qty.trigger('change');
4652
},
4753

4854
/**
@@ -60,6 +66,71 @@ define([
6066
qty.on('change', this._onQtyFieldChanged.bind(this));
6167
},
6268

69+
/**
70+
* Override default options values settings with either URL query parameters or
71+
* initialized inputs values.
72+
* @private
73+
*/
74+
_overrideDefaults: function () {
75+
var hashIndex = window.location.href.indexOf('#');
76+
77+
if (hashIndex !== -1) {
78+
this._parseQueryParams(window.location.href.substr(hashIndex + 1));
79+
}
80+
},
81+
82+
/**
83+
* Parse query parameters from a query string and set options values based on the
84+
* key value pairs of the parameters.
85+
* @param {*} queryString - URL query string containing query parameters.
86+
* @private
87+
*/
88+
_parseQueryParams: function (queryString) {
89+
var queryParams = $.parseQuery({
90+
query: queryString
91+
}),
92+
selectedValues = [],
93+
form = this.element,
94+
options = $(this.options.productBundleSelector, form),
95+
qtys = $(this.options.qtyFieldSelector, form);
96+
97+
$.each(queryParams, $.proxy(function (key, value) {
98+
qtys.each(function (index, qty) {
99+
if (qty.name === key) {
100+
$(qty).val(value);
101+
}
102+
});
103+
options.each(function (index, option) {
104+
let optionType = $(option).prop('type');
105+
106+
if (option.name === key ||
107+
optionType === 'select-multiple'
108+
&& key.indexOf(option.name.substr(0, option.name.length - 2)) !== false
109+
) {
110+
111+
switch (optionType) {
112+
case 'radio':
113+
$(option).val() === value ? $(option).prop('checked', true) : '';
114+
break;
115+
case 'checkbox':
116+
$(option).prop('checked', true);
117+
break;
118+
case 'hidden':
119+
case 'select-one':
120+
$(option).val(value);
121+
break;
122+
case 'select-multiple':
123+
selectedValues.push(value);
124+
break;
125+
}
126+
if (optionType === 'select-multiple' && selectedValues.length) {
127+
$(option).val(selectedValues);
128+
}
129+
}
130+
});
131+
}, this));
132+
},
133+
63134
/**
64135
* Update price box config with bundle option prices
65136
* @private

app/code/Magento/BundleImportExport/Model/Import/Product/Type/Bundle.php

Lines changed: 19 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -181,22 +181,26 @@ protected function parseSelections($rowData, $entityId)
181181
return [];
182182
}
183183

184-
$rowData['bundle_values'] = str_replace(
185-
self::BEFORE_OPTION_VALUE_DELIMITER,
186-
$this->_entityModel->getMultipleValueSeparator(),
187-
$rowData['bundle_values']
188-
);
189-
$selections = explode(
190-
Product::PSEUDO_MULTI_LINE_SEPARATOR,
191-
$rowData['bundle_values']
192-
);
184+
if (is_string($rowData['bundle_values'])) {
185+
$rowData['bundle_values'] = str_replace(
186+
self::BEFORE_OPTION_VALUE_DELIMITER,
187+
$this->_entityModel->getMultipleValueSeparator(),
188+
$rowData['bundle_values']
189+
);
190+
$selections = explode(
191+
Product::PSEUDO_MULTI_LINE_SEPARATOR,
192+
$rowData['bundle_values']
193+
);
194+
} else {
195+
$selections = $rowData['bundle_values'];
196+
}
197+
193198
foreach ($selections as $selection) {
194-
$values = explode($this->_entityModel->getMultipleValueSeparator(), $selection);
195-
$option = $this->parseOption($values);
196-
if (isset($option['sku']) && isset($option['name'])) {
197-
if (!isset($this->_cachedOptions[$entityId])) {
198-
$this->_cachedOptions[$entityId] = [];
199-
}
199+
$option = is_string($selection)
200+
? $this->parseOption(explode($this->_entityModel->getMultipleValueSeparator(), $selection))
201+
: $selection;
202+
203+
if (isset($option['sku'], $option['name'])) {
200204
$this->_cachedSkus[] = $option['sku'];
201205
if (!isset($this->_cachedOptions[$entityId][$option['name']])) {
202206
$this->_cachedOptions[$entityId][$option['name']] = [];

app/code/Magento/Catalog/Block/Adminhtml/Product/Helper/Form/Gallery/Content.php

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
*/
1414
namespace Magento\Catalog\Block\Adminhtml\Product\Helper\Form\Gallery;
1515

16+
use Magento\Catalog\Helper\Image;
1617
use Magento\Framework\App\ObjectManager;
1718
use Magento\Backend\Block\Media\Uploader;
1819
use Magento\Framework\Json\Helper\Data as JsonHelper;
@@ -45,7 +46,7 @@ class Content extends \Magento\Backend\Block\Widget
4546
protected $_jsonEncoder;
4647

4748
/**
48-
* @var \Magento\Catalog\Helper\Image
49+
* @var Image
4950
*/
5051
private $imageHelper;
5152

@@ -67,6 +68,7 @@ class Content extends \Magento\Backend\Block\Widget
6768
* @param ImageUploadConfigDataProvider $imageUploadConfigDataProvider
6869
* @param Database $fileStorageDatabase
6970
* @param JsonHelper|null $jsonHelper
71+
* @param Image|null $imageHelper
7072
*/
7173
public function __construct(
7274
\Magento\Backend\Block\Template\Context $context,
@@ -75,7 +77,8 @@ public function __construct(
7577
array $data = [],
7678
ImageUploadConfigDataProvider $imageUploadConfigDataProvider = null,
7779
Database $fileStorageDatabase = null,
78-
?JsonHelper $jsonHelper = null
80+
?JsonHelper $jsonHelper = null,
81+
?Image $imageHelper = null
7982
) {
8083
$this->_jsonEncoder = $jsonEncoder;
8184
$this->_mediaConfig = $mediaConfig;
@@ -85,6 +88,7 @@ public function __construct(
8588
?: ObjectManager::getInstance()->get(ImageUploadConfigDataProvider::class);
8689
$this->fileStorageDatabase = $fileStorageDatabase
8790
?: ObjectManager::getInstance()->get(Database::class);
91+
$this->imageHelper = $imageHelper ?: ObjectManager::getInstance()->get(Image::class);
8892
}
8993

9094
/**
@@ -191,7 +195,7 @@ public function getImagesJson()
191195
$fileHandler = $mediaDir->stat($this->_mediaConfig->getMediaPath($image['file']));
192196
$image['size'] = $fileHandler['size'];
193197
} catch (FileSystemException $e) {
194-
$image['url'] = $this->getImageHelper()->getDefaultPlaceholderUrl('small_image');
198+
$image['url'] = $this->imageHelper->getDefaultPlaceholderUrl('small_image');
195199
$image['size'] = 0;
196200
$this->_logger->warning($e);
197201
}
@@ -304,17 +308,14 @@ public function getImageTypesJson()
304308
}
305309

306310
/**
307-
* Returns image helper object.
311+
* Flag if gallery content editing is enabled.
308312
*
309-
* @return \Magento\Catalog\Helper\Image
310-
* @deprecated 101.0.3
313+
* Is enabled by default, exposed to interceptors to add custom logic
314+
*
315+
* @return bool
311316
*/
312-
private function getImageHelper()
317+
public function isEditEnabled() : bool
313318
{
314-
if ($this->imageHelper === null) {
315-
$this->imageHelper = \Magento\Framework\App\ObjectManager::getInstance()
316-
->get(\Magento\Catalog\Helper\Image::class);
317-
}
318-
return $this->imageHelper;
319+
return true;
319320
}
320321
}

app/code/Magento/Catalog/Controller/Adminhtml/Product/Initialization/Helper/AttributeFilter.php

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@
88

99
namespace Magento\Catalog\Controller\Adminhtml\Product\Initialization\Helper;
1010

11-
use \Magento\Catalog\Model\Product;
11+
use Magento\Catalog\Model\Product;
12+
use Magento\Catalog\Model\ResourceModel\Eav\Attribute;
1213

1314
/**
1415
* Class provides functionality to check and filter data came with product form.
@@ -32,7 +33,8 @@ public function prepareProductAttributes(Product $product, array $productData, a
3233
{
3334
$attributeList = $product->getAttributes();
3435
foreach ($productData as $attributeCode => $attributeValue) {
35-
if ($this->isAttributeShouldNotBeUpdated($product, $useDefaults, $attributeCode, $attributeValue)) {
36+
if ($this->isAttributeShouldNotBeUpdated($product, $useDefaults, $attributeCode, $attributeValue) &&
37+
$this->isCustomAttrEmptyValueAllowed($attributeList, $attributeCode, $productData)) {
3638
unset($productData[$attributeCode]);
3739
}
3840

@@ -63,6 +65,34 @@ private function prepareConfigData(Product $product, string $attributeCode, arra
6365
return $productData;
6466
}
6567

68+
/**
69+
* Check if custom attribute with empty value allowed
70+
*
71+
* @param mixed $attributeList
72+
* @param string $attributeCode
73+
* @param array $productData
74+
* @return bool
75+
*/
76+
private function isCustomAttrEmptyValueAllowed(
77+
$attributeList,
78+
string $attributeCode,
79+
array $productData
80+
): bool {
81+
$isAllowed = true;
82+
if ($attributeList && isset($attributeList[$attributeCode])) {
83+
/** @var Attribute $attribute */
84+
$attribute = $attributeList[$attributeCode];
85+
$isAttributeUserDefined = (int) $attribute->getIsUserDefined();
86+
$isAttributeIsRequired = (int) $attribute->getIsRequired();
87+
88+
if ($isAttributeUserDefined && !$isAttributeIsRequired &&
89+
empty($productData[$attributeCode])) {
90+
$isAllowed = false;
91+
}
92+
}
93+
return $isAllowed;
94+
}
95+
6696
/**
6797
* Prepare default attribute data for product.
6898
*
@@ -74,13 +104,15 @@ private function prepareConfigData(Product $product, string $attributeCode, arra
74104
private function prepareDefaultData(array $attributeList, string $attributeCode, array $productData): array
75105
{
76106
if (isset($attributeList[$attributeCode])) {
77-
/** @var \Magento\Catalog\Model\ResourceModel\Eav\Attribute $attribute */
107+
/** @var Attribute $attribute */
78108
$attribute = $attributeList[$attributeCode];
79109
$attributeType = $attribute->getBackendType();
110+
$attributeIsUserDefined = (int) $attribute->getIsUserDefined();
80111
// For non-numeric types set the attributeValue to 'false' to trigger their removal from the db
81112
if ($attributeType === 'varchar' || $attributeType === 'text' || $attributeType === 'datetime') {
82113
$attribute->setIsRequired(false);
83-
$productData[$attributeCode] = $attribute->getDefaultValue() ?: false;
114+
$productData[$attributeCode] = $attributeIsUserDefined ? false :
115+
($attribute->getDefaultValue() ?: false);
84116
} else {
85117
$productData[$attributeCode] = null;
86118
}

app/code/Magento/Catalog/Model/ResourceModel/AbstractResource.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ protected function _isApplicableAttribute($object, $attribute)
8787
{
8888
$applyTo = $attribute->getApplyTo() ?: [];
8989
return (count($applyTo) == 0 || in_array($object->getTypeId(), $applyTo))
90-
&& $attribute->isInSet($object->getAttributeSetId());
90+
&& $attribute->isInSet($object->getAttributeSetId() ?? $this->getEntityType()->getDefaultAttributeSetId());
9191
}
9292

9393
/**

0 commit comments

Comments
 (0)