Skip to content

Commit eb468d0

Browse files
Merge branch '2.4-develop' into issue/27925
2 parents b97c6c3 + b9c13fd commit eb468d0

File tree

314 files changed

+12329
-1342
lines changed

Some content is hidden

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

314 files changed

+12329
-1342
lines changed

app/bootstrap.php

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,15 @@
1414
#ini_set('display_errors', 1);
1515

1616
/* PHP version validation */
17-
if (!defined('PHP_VERSION_ID') || PHP_VERSION_ID < 70103) {
17+
if (!defined('PHP_VERSION_ID') || PHP_VERSION_ID < 70300) {
1818
if (PHP_SAPI == 'cli') {
19-
echo 'Magento supports PHP 7.1.3 or later. ' .
20-
'Please read https://devdocs.magento.com/guides/v2.3/install-gde/system-requirements-tech.html';
19+
echo 'Magento supports PHP 7.3.0 or later. ' .
20+
'Please read https://devdocs.magento.com/guides/v2.4/install-gde/system-requirements-tech.html';
2121
} else {
2222
echo <<<HTML
2323
<div style="font:12px/1.35em arial, helvetica, sans-serif;">
24-
<p>Magento supports PHP 7.1.3 or later. Please read
25-
<a target="_blank" href="https://devdocs.magento.com/guides/v2.3/install-gde/system-requirements-tech.html">
24+
<p>Magento supports PHP 7.3.0 or later. Please read
25+
<a target="_blank" href="https://devdocs.magento.com/guides/v2.4/install-gde/system-requirements-tech.html">
2626
Magento System Requirements</a>.
2727
</div>
2828
HTML;

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

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ public function __construct(
3939
}
4040

4141
/**
42+
* Bundle Product Items Data
43+
*
4244
* @param \Magento\Catalog\Api\Data\ProductInterface $product
4345
* @param int $optionId
4446
* @return \Magento\Bundle\Api\Data\LinkInterface[]
@@ -50,8 +52,12 @@ public function getItems(\Magento\Catalog\Api\Data\ProductInterface $product, $o
5052
$productLinks = [];
5153
/** @var \Magento\Catalog\Model\Product $selection */
5254
foreach ($selectionCollection as $selection) {
55+
$bundledProductPrice = $selection->getSelectionPriceValue();
56+
if ($bundledProductPrice <= 0) {
57+
$bundledProductPrice = $selection->getPrice();
58+
}
5359
$selectionPriceType = $product->getPriceType() ? $selection->getSelectionPriceType() : null;
54-
$selectionPrice = $product->getPriceType() ? $selection->getSelectionPriceValue() : null;
60+
$selectionPrice = $bundledProductPrice ? $bundledProductPrice : null;
5561

5662
/** @var \Magento\Bundle\Api\Data\LinkInterface $productLink */
5763
$productLink = $this->linkFactory->create();
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
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" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd">
9+
<test name="StorefrontBundlePlaceOrderWithMultipleOptionsSuccessTest">
10+
<annotations>
11+
<features value="Bundle"/>
12+
<stories value="Bundle product details page"/>
13+
<title value="Customer should be able to see all the bundle items in invoice view"/>
14+
<description value="Customer should be able to see all the bundle items in invoice view"/>
15+
<severity value="MAJOR"/>
16+
<testCaseId value="MC-37515"/>
17+
<group value="Bundle"/>
18+
</annotations>
19+
<before>
20+
<createData entity="_defaultCategory" stepKey="createPreReqCategory"/>
21+
<createData entity="SimpleProduct2" stepKey="firstSimpleProduct"/>
22+
<createData entity="SimpleProduct2" stepKey="secondSimpleProduct"/>
23+
<createData entity="CustomerEntityOne" stepKey="createCustomer"/>
24+
<actionGroup stepKey="loginToAdminPanel" ref="AdminLoginActionGroup"/>
25+
</before>
26+
<after>
27+
<deleteData createDataKey="createPreReqCategory" stepKey="deletePreReqCategory"/>
28+
<deleteData createDataKey="firstSimpleProduct" stepKey="deleteFirstSimpleProduct"/>
29+
<deleteData createDataKey="secondSimpleProduct" stepKey="deleteSecondSimpleProduct"/>
30+
<deleteData createDataKey="createCustomer" stepKey="deleteCustomer"/>
31+
<actionGroup ref="AdminLogoutActionGroup" stepKey="logout"/>
32+
</after>
33+
<!-- Create new bundle product -->
34+
<actionGroup ref="GoToSpecifiedCreateProductPageActionGroup" stepKey="createBundleProduct">
35+
<argument name="productType" value="bundle"/>
36+
</actionGroup>
37+
38+
<!-- Fill all main fields -->
39+
<actionGroup ref="FillMainBundleProductFormActionGroup" stepKey="fillMainProductFields"/>
40+
41+
<!-- Add first bundle option to the product -->
42+
<actionGroup ref="AddBundleOptionWithTwoProductsActionGroup" stepKey="addFirstBundleOption">
43+
<argument name="x" value="0"/>
44+
<argument name="n" value="1"/>
45+
<argument name="prodOneSku" value="$firstSimpleProduct.sku$"/>
46+
<argument name="prodTwoSku" value="$secondSimpleProduct.sku$$"/>
47+
<argument name="optionTitle" value="{{CheckboxOption.title}}"/>
48+
<argument name="inputType" value="{{CheckboxOption.type}}"/>
49+
</actionGroup>
50+
51+
<!-- Save product form -->
52+
<actionGroup ref="SaveProductFormActionGroup" stepKey="saveWithThreeOptions"/>
53+
54+
<!--Login customer on storefront-->
55+
<actionGroup ref="LoginToStorefrontActionGroup" stepKey="loginCustomer">
56+
<argument name="Customer" value="$$createCustomer$$" />
57+
</actionGroup>
58+
59+
<!--Open Product Page-->
60+
<actionGroup ref="StorefrontOpenProductPageActionGroup" stepKey="openStorefrontProductPage">
61+
<argument name="productUrl" value="{{BundleProduct.name}}"/>
62+
</actionGroup>
63+
64+
<!-- Add bundle to cart -->
65+
<actionGroup ref="StorefrontSelectCustomizeAndAddToTheCartButtonActionGroup" stepKey="clickAddToCart">
66+
<argument name="productUrl" value="{{BundleProduct.name}}"/>
67+
</actionGroup>
68+
<checkOption selector="{{StorefrontBundledSection.checkboxOptionThreeProducts(CheckboxOption.title, '1')}}" stepKey="selectOption2Product1"/>
69+
<checkOption selector="{{StorefrontBundledSection.checkboxOptionThreeProducts(CheckboxOption.title, '2')}}" stepKey="selectOption2Product2"/>
70+
<actionGroup ref="StorefrontEnterProductQuantityAndAddToTheCartActionGroup" stepKey="enterProductQuantityAndAddToTheCart">
71+
<argument name="quantity" value="1"/>
72+
</actionGroup>
73+
74+
<!--Navigate to checkout-->
75+
<actionGroup ref="StorefrontOpenCheckoutPageActionGroup" stepKey="openCheckoutPage"/>
76+
<!-- Click next button to open payment section -->
77+
<actionGroup ref="StorefrontCheckoutClickNextButtonActionGroup" stepKey="clickNext"/>
78+
<!-- Click place order -->
79+
<actionGroup ref="ClickPlaceOrderActionGroup" stepKey="placeOrder"/>
80+
<grabTextFrom selector="{{CheckoutSuccessMainSection.orderNumber22}}" stepKey="grabOrderNumber"/>
81+
82+
<!-- Order review page has address that was created during checkout -->
83+
<actionGroup ref="OpenOrderByIdActionGroup" stepKey="filterOrdersGridById">
84+
<argument name="orderId" value="{$grabOrderNumber}"/>
85+
</actionGroup>
86+
87+
<!-- Open create invoice page -->
88+
<actionGroup ref="StartCreateInvoiceFromOrderPageActionGroup" stepKey="startInvoice"/>
89+
90+
<!-- Assert item options display -->
91+
<see selector="{{AdminInvoiceItemsSection.bundleItem}}" userInput="50 x $firstSimpleProduct.sku$" stepKey="seeFirstProductInList"/>
92+
<see selector="{{AdminInvoiceItemsSection.bundleItem}}" userInput="50 x $secondSimpleProduct.sku$" stepKey="seeSecondProductInList"/>
93+
</test>
94+
</tests>

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ public function testLinksList()
9191
->method('getSelectionsCollection')
9292
->with([$optionId], $this->productMock)
9393
->willReturn([$this->selectionMock]);
94-
$this->productMock->expects($this->exactly(2))->method('getPriceType')->willReturn('price_type');
94+
$this->productMock->expects($this->once())->method('getPriceType')->willReturn('price_type');
9595
$this->selectionMock->expects($this->once())
9696
->method('getSelectionPriceType')
9797
->willReturn('selection_price_type');

app/code/Magento/Bundle/view/adminhtml/templates/sales/invoice/create/items/renderer.phtml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
<?php $items = $block->getChildren($_item); ?>
1616
<?php $_count = count($items) ?>
1717
<?php $_index = 0 ?>
18+
<?php $canEditItemQty = true ?>
1819
<?php
1920
/** @var \Magento\Catalog\Helper\Data $catalogHelper */
2021
$catalogHelper = $block->getData('catalogHelper');
@@ -37,7 +38,7 @@ $catalogHelper = $block->getData('catalogHelper');
3738
<?php if ($_item->getOrderItem()->getParentItem()): ?>
3839
<?php
3940
if ($shipTogether) {
40-
continue;
41+
$canEditItemQty = false;
4142
}
4243
?>
4344
<?php $attributes = $block->getSelectionAttributes($_item) ?>
@@ -130,7 +131,7 @@ $catalogHelper = $block->getData('catalogHelper');
130131
</td>
131132
<td class="col-qty-invoice">
132133
<?php if ($block->canShowPriceInfo($_item) || $shipTogether): ?>
133-
<?php if ($block->canEditQty()): ?>
134+
<?php if ($block->canEditQty() && $canEditItemQty): ?>
134135
<input type="text"
135136
class="input-text admin__control-text qty-input"
136137
name="invoice[items][<?= $block->escapeHtmlAttr($_item->getOrderItemId()) ?>]"

app/code/Magento/Catalog/Block/Product/View.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,10 @@ public function getJsonConfig()
196196
'productId' => (int)$product->getId(),
197197
'priceFormat' => $this->_localeFormat->getPriceFormat(),
198198
'prices' => [
199+
'baseOldPrice' => [
200+
'amount' => $priceInfo->getPrice('regular_price')->getAmount()->getBaseAmount() * 1,
201+
'adjustments' => []
202+
],
199203
'oldPrice' => [
200204
'amount' => $priceInfo->getPrice('regular_price')->getAmount()->getValue() * 1,
201205
'adjustments' => []

app/code/Magento/Catalog/Helper/Image.php

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -384,7 +384,9 @@ public function backgroundColor($colorRGB)
384384
{
385385
// assume that 3 params were given instead of array
386386
if (!is_array($colorRGB)) {
387+
//phpcs:disable
387388
$colorRGB = func_get_args();
389+
//phpcs:enabled
388390
}
389391
$this->_getModel()->setBackgroundColor($colorRGB);
390392
return $this;
@@ -498,7 +500,11 @@ protected function initBaseFile()
498500
if ($this->getImageFile()) {
499501
$model->setBaseFile($this->getImageFile());
500502
} else {
501-
$model->setBaseFile($this->getProduct()->getData($model->getDestinationSubdir()));
503+
$model->setBaseFile(
504+
$this->getProduct()
505+
? $this->getProduct()->getData($model->getDestinationSubdir())
506+
: ''
507+
);
502508
}
503509
}
504510
return $this;

app/code/Magento/Catalog/Model/Product/Gallery/CreateHandler.php

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -596,10 +596,21 @@ private function canRemoveImage(ProductInterface $product, string $imageFile) :b
596596
$canRemoveImage = true;
597597
$gallery = $this->getImagesForAllStores($product);
598598
$storeId = $product->getStoreId();
599+
$storeIds = [];
600+
$storeIds[] = 0;
601+
$websiteIds = array_map('intval', $product->getWebsiteIds() ?? []);
602+
foreach ($this->storeManager->getStores() as $store) {
603+
if (in_array((int) $store->getWebsiteId(), $websiteIds, true)) {
604+
$storeIds[] = (int) $store->getId();
605+
}
606+
}
599607

600608
if (!empty($gallery)) {
601609
foreach ($gallery as $image) {
602-
if ($image['filepath'] === $imageFile && (int) $image['store_id'] !== $storeId) {
610+
if (in_array((int) $image['store_id'], $storeIds)
611+
&& $image['filepath'] === $imageFile
612+
&& (int) $image['store_id'] !== $storeId
613+
) {
603614
$canRemoveImage = false;
604615
}
605616
}

app/code/Magento/Catalog/Model/Product/Gallery/UpdateHandler.php

Lines changed: 89 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,69 @@
55
*/
66
namespace Magento\Catalog\Model\Product\Gallery;
77

8+
use Magento\Catalog\Api\Data\ProductInterface;
9+
use Magento\Catalog\Api\ProductAttributeRepositoryInterface;
10+
use Magento\Catalog\Model\Product;
11+
use Magento\Catalog\Model\Product\Media\Config;
812
use Magento\Catalog\Model\ResourceModel\Product\Gallery;
9-
use Magento\Framework\EntityManager\Operation\ExtensionInterface;
13+
use Magento\Eav\Model\ResourceModel\AttributeValue;
14+
use Magento\Framework\App\ObjectManager;
15+
use Magento\Framework\EntityManager\MetadataPool;
16+
use Magento\Framework\Filesystem;
17+
use Magento\Framework\Json\Helper\Data;
18+
use Magento\MediaStorage\Helper\File\Storage\Database;
19+
use Magento\Store\Model\Store;
20+
use Magento\Store\Model\StoreManagerInterface;
1021

1122
/**
1223
* Update handler for catalog product gallery.
1324
*
1425
* @api
1526
* @since 101.0.0
27+
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
1628
*/
17-
class UpdateHandler extends \Magento\Catalog\Model\Product\Gallery\CreateHandler
29+
class UpdateHandler extends CreateHandler
1830
{
31+
/**
32+
* @var AttributeValue
33+
*/
34+
private $attributeValue;
35+
36+
/**
37+
* @param MetadataPool $metadataPool
38+
* @param ProductAttributeRepositoryInterface $attributeRepository
39+
* @param Gallery $resourceModel
40+
* @param Data $jsonHelper
41+
* @param Config $mediaConfig
42+
* @param Filesystem $filesystem
43+
* @param Database $fileStorageDb
44+
* @param StoreManagerInterface|null $storeManager
45+
* @param AttributeValue|null $attributeValue
46+
*/
47+
public function __construct(
48+
MetadataPool $metadataPool,
49+
ProductAttributeRepositoryInterface $attributeRepository,
50+
Gallery $resourceModel,
51+
Data $jsonHelper,
52+
Config $mediaConfig,
53+
Filesystem $filesystem,
54+
Database $fileStorageDb,
55+
StoreManagerInterface $storeManager = null,
56+
?AttributeValue $attributeValue = null
57+
) {
58+
parent::__construct(
59+
$metadataPool,
60+
$attributeRepository,
61+
$resourceModel,
62+
$jsonHelper,
63+
$mediaConfig,
64+
$filesystem,
65+
$fileStorageDb,
66+
$storeManager
67+
);
68+
$this->attributeValue = $attributeValue ?: ObjectManager::getInstance()->get(AttributeValue::class);
69+
}
70+
1971
/**
2072
* @inheritdoc
2173
*
@@ -26,6 +78,7 @@ protected function processDeletedImages($product, array &$images)
2678
$filesToDelete = [];
2779
$recordsToDelete = [];
2880
$picturesInOtherStores = [];
81+
$imagesToDelete = [];
2982

3083
foreach ($this->resourceModel->getProductImages($product, $this->extractStoreIds($product)) as $image) {
3184
$picturesInOtherStores[$image['filepath']] = true;
@@ -38,6 +91,7 @@ protected function processDeletedImages($product, array &$images)
3891
continue;
3992
}
4093
$recordsToDelete[] = $image['value_id'];
94+
$imagesToDelete[] = $image['file'];
4195
$catalogPath = $this->mediaConfig->getBaseMediaPath();
4296
$isFile = $this->mediaDirectory->isFile($catalogPath . $image['file']);
4397
// only delete physical files if they are not used by any other products and if this file exist
@@ -48,8 +102,8 @@ protected function processDeletedImages($product, array &$images)
48102
}
49103
}
50104

105+
$this->deleteMediaAttributeValues($product, $imagesToDelete);
51106
$this->resourceModel->deleteGallery($recordsToDelete);
52-
53107
$this->removeDeletedImages($filesToDelete);
54108
}
55109

@@ -94,14 +148,14 @@ protected function processNewImage($product, array &$image)
94148
/**
95149
* Retrieve store ids from product.
96150
*
97-
* @param \Magento\Catalog\Model\Product $product
151+
* @param Product $product
98152
* @return array
99153
* @since 101.0.0
100154
*/
101155
protected function extractStoreIds($product)
102156
{
103157
$storeIds = $product->getStoreIds();
104-
$storeIds[] = \Magento\Store\Model\Store::DEFAULT_STORE_ID;
158+
$storeIds[] = Store::DEFAULT_STORE_ID;
105159

106160
// Removing current storeId.
107161
$storeIds = array_flip($storeIds);
@@ -125,5 +179,35 @@ protected function removeDeletedImages(array $files)
125179
foreach ($files as $filePath) {
126180
$this->mediaDirectory->delete($catalogPath . '/' . $filePath);
127181
}
182+
return null;
183+
}
184+
185+
/**
186+
* Delete media attributes values for given images
187+
*
188+
* @param Product $product
189+
* @param string[] $images
190+
*/
191+
private function deleteMediaAttributeValues(Product $product, array $images): void
192+
{
193+
if ($images) {
194+
$values = $this->attributeValue->getValues(
195+
ProductInterface::class,
196+
$product->getData($this->metadata->getLinkField()),
197+
$this->mediaConfig->getMediaAttributeCodes()
198+
);
199+
$valuesToDelete = [];
200+
foreach ($values as $value) {
201+
if (in_array($value['value'], $images, true)) {
202+
$valuesToDelete[] = $value;
203+
}
204+
}
205+
if ($valuesToDelete) {
206+
$this->attributeValue->deleteValues(
207+
ProductInterface::class,
208+
$valuesToDelete
209+
);
210+
}
211+
}
128212
}
129213
}

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

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,14 @@
2020
</annotations>
2121

2222
<before>
23-
<actionGroup ref="AdminLoginActionGroup" stepKey="loginAsAdmin"/>
2423
<createData entity="SimpleProduct2" stepKey="createSimpleProduct"/>
24+
<actionGroup ref="AdminLoginActionGroup" stepKey="loginAsAdmin"/>
25+
<actionGroup ref="AdminOpenProductIndexPageActionGroup" stepKey="navigateToProductIndex"/>
26+
<actionGroup ref="ClearFiltersAdminDataGridActionGroup" stepKey="clearGridFilter"/>
27+
<!-- Should wait a bit for filters really cleared because waitForPageLoad does not wait for javascripts to be finished -->
28+
<!-- Without this test will fail sometimes -->
29+
<wait time="5" stepKey="waitFilterReallyCleared"/>
30+
<reloadPage stepKey="reloadPage"/>
2531
</before>
2632

2733
<after>

0 commit comments

Comments
 (0)