Skip to content

Commit 687099e

Browse files
committed
Merge remote-tracking branch 'mainline/2.3-develop' into MC-19243
2 parents 44bf9d8 + 6f03dd3 commit 687099e

File tree

32 files changed

+607
-193
lines changed

32 files changed

+607
-193
lines changed

app/code/Magento/Bundle/Model/ResourceModel/Option/Collection.php

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
*/
66
namespace Magento\Bundle\Model\ResourceModel\Option;
77

8+
use Magento\Catalog\Model\Product\Attribute\Source\Status;
9+
810
/**
911
* Bundle Options Resource Collection
1012
* @api
@@ -138,12 +140,10 @@ public function setPositionOrder()
138140

139141
/**
140142
* Append selection to options
141-
* stripBefore - indicates to reload
142-
* appendAll - indicates do we need to filter by saleable and required custom options
143143
*
144144
* @param \Magento\Bundle\Model\ResourceModel\Selection\Collection $selectionsCollection
145-
* @param bool $stripBefore
146-
* @param bool $appendAll
145+
* @param bool $stripBefore indicates to reload
146+
* @param bool $appendAll indicates do we need to filter by saleable and required custom options
147147
* @return \Magento\Framework\DataObject[]
148148
*/
149149
public function appendSelections($selectionsCollection, $stripBefore = false, $appendAll = true)
@@ -156,7 +156,9 @@ public function appendSelections($selectionsCollection, $stripBefore = false, $a
156156
foreach ($selectionsCollection->getItems() as $key => $selection) {
157157
$option = $this->getItemById($selection->getOptionId());
158158
if ($option) {
159-
if ($appendAll || $selection->isSalable() && !$selection->getRequiredOptions()) {
159+
if ($appendAll ||
160+
((int) $selection->getStatus()) === Status::STATUS_ENABLED && !$selection->getRequiredOptions()
161+
) {
160162
$selection->setOption($option);
161163
$option->addSelection($selection);
162164
} else {

app/code/Magento/Catalog/Model/ProductAttributeGroupRepository.php

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,16 @@
44
* Copyright © Magento, Inc. All rights reserved.
55
* See COPYING.txt for license details.
66
*/
7+
declare(strict_types=1);
78

89
namespace Magento\Catalog\Model;
910

1011
use Magento\Framework\Exception\NoSuchEntityException;
1112
use Magento\Framework\Exception\StateException;
1213

14+
/**
15+
* Class \Magento\Catalog\Model\ProductAttributeGroupRepository
16+
*/
1317
class ProductAttributeGroupRepository implements \Magento\Catalog\Api\ProductAttributeGroupRepositoryInterface
1418
{
1519
/**
@@ -43,23 +47,29 @@ public function __construct(
4347
}
4448

4549
/**
46-
* {@inheritdoc}
50+
* @inheritdoc
4751
*/
4852
public function save(\Magento\Eav\Api\Data\AttributeGroupInterface $group)
4953
{
54+
/** @var \Magento\Catalog\Model\Product\Attribute\Group $group */
55+
$extensionAttributes = $group->getExtensionAttributes();
56+
if ($extensionAttributes) {
57+
$group->setSortOrder($extensionAttributes->getSortOrder());
58+
$group->setAttributeGroupCode($extensionAttributes->getAttributeGroupCode());
59+
}
5060
return $this->groupRepository->save($group);
5161
}
5262

5363
/**
54-
* {@inheritdoc}
64+
* @inheritdoc
5565
*/
5666
public function getList(\Magento\Framework\Api\SearchCriteriaInterface $searchCriteria)
5767
{
5868
return $this->groupRepository->getList($searchCriteria);
5969
}
6070

6171
/**
62-
* {@inheritdoc}
72+
* @inheritdoc
6373
*/
6474
public function get($groupId)
6575
{
@@ -75,7 +85,7 @@ public function get($groupId)
7585
}
7686

7787
/**
78-
* {@inheritdoc}
88+
* @inheritdoc
7989
*/
8090
public function deleteById($groupId)
8191
{
@@ -86,7 +96,7 @@ public function deleteById($groupId)
8696
}
8797

8898
/**
89-
* {@inheritdoc}
99+
* @inheritdoc
90100
*/
91101
public function delete(\Magento\Eav\Api\Data\AttributeGroupInterface $group)
92102
{

app/code/Magento/Catalog/view/frontend/templates/product/list/items.phtml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,7 @@ switch ($type = $block->getType()) {
170170
<?php if ($type == 'related' && $canItemsAddToCart) :?>
171171
<div class="block-actions">
172172
<?= $block->escapeHtml(__('Check items to add to the cart or')) ?>
173-
<button type="button" class="action select" role="button"><span><?= $block->escapeHtml(__('select all')) ?></span></button>
173+
<button type="button" class="action select" data-role="select-all"><span><?= $block->escapeHtml(__('select all')) ?></span></button>
174174
</div>
175175
<?php endif; ?>
176176
<div class="products wrapper grid products-grid products-<?= $block->escapeHtmlAttr($type) ?>">

app/code/Magento/Catalog/view/frontend/web/js/related-products.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ define([
1717
relatedProductsField: '#related-products-field', // Hidden input field that stores related products.
1818
selectAllMessage: $.mage.__('select all'),
1919
unselectAllMessage: $.mage.__('unselect all'),
20-
selectAllLink: '[role="button"]',
20+
selectAllLink: '[data-role="select-all"]',
2121
elementsSelector: '.item.product'
2222
},
2323

app/code/Magento/CatalogImportExport/Model/Import/Product/MediaGalleryProcessor.php

Lines changed: 47 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -284,6 +284,42 @@ private function initMediaGalleryResources()
284284
}
285285
}
286286

287+
/**
288+
* Get the last media position for each product from the given list
289+
*
290+
* @param int $storeId
291+
* @param array $productIds
292+
* @return array
293+
*/
294+
private function getLastMediaPositionPerProduct(int $storeId, array $productIds): array
295+
{
296+
$result = [];
297+
if ($productIds) {
298+
$productKeyName = $this->getProductEntityLinkField();
299+
// this result could be achieved by using GROUP BY. But there is no index on position column, therefore
300+
// it can be slower than the implementation below
301+
$positions = $this->connection->fetchAll(
302+
$this->connection
303+
->select()
304+
->from($this->mediaGalleryValueTableName, [$productKeyName, 'position'])
305+
->where("$productKeyName IN (?)", $productIds)
306+
->where('value_id is not null')
307+
->where('store_id = ?', $storeId)
308+
);
309+
// Make sure the result contains all product ids even if the product has no media files
310+
$result = array_fill_keys($productIds, 0);
311+
// Find the largest position for each product
312+
foreach ($positions as $record) {
313+
$productId = $record[$productKeyName];
314+
$result[$productId] = $result[$productId] < $record['position']
315+
? $record['position']
316+
: $result[$productId];
317+
}
318+
}
319+
320+
return $result;
321+
}
322+
287323
/**
288324
* Save media gallery data per store.
289325
*
@@ -301,24 +337,30 @@ private function processMediaPerStore(
301337
) {
302338
$multiInsertData = [];
303339
$dataForSkinnyTable = [];
340+
$lastMediaPositionPerProduct = $this->getLastMediaPositionPerProduct(
341+
$storeId,
342+
array_unique(array_merge(...array_values($valueToProductId)))
343+
);
344+
304345
foreach ($mediaGalleryData as $mediaGalleryRows) {
305346
foreach ($mediaGalleryRows as $insertValue) {
306-
foreach ($newMediaValues as $value_id => $values) {
347+
foreach ($newMediaValues as $valueId => $values) {
307348
if ($values['value'] == $insertValue['value']) {
308-
$insertValue['value_id'] = $value_id;
349+
$insertValue['value_id'] = $valueId;
309350
$insertValue[$this->getProductEntityLinkField()]
310351
= array_shift($valueToProductId[$values['value']]);
311-
unset($newMediaValues[$value_id]);
352+
unset($newMediaValues[$valueId]);
312353
break;
313354
}
314355
}
315356
if (isset($insertValue['value_id'])) {
357+
$productId = $insertValue[$this->getProductEntityLinkField()];
316358
$valueArr = [
317359
'value_id' => $insertValue['value_id'],
318360
'store_id' => $storeId,
319-
$this->getProductEntityLinkField() => $insertValue[$this->getProductEntityLinkField()],
361+
$this->getProductEntityLinkField() => $productId,
320362
'label' => $insertValue['label'],
321-
'position' => $insertValue['position'],
363+
'position' => $lastMediaPositionPerProduct[$productId] + $insertValue['position'],
322364
'disabled' => $insertValue['disabled'],
323365
];
324366
$multiInsertData[] = $valueArr;

app/code/Magento/Checkout/Model/Session.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -370,6 +370,11 @@ public function loadCustomerQuote()
370370
$this->quoteRepository->save(
371371
$customerQuote->merge($this->getQuote())->collectTotals()
372372
);
373+
$newQuote = $this->quoteRepository->get($customerQuote->getId());
374+
$this->quoteRepository->save(
375+
$newQuote->collectTotals()
376+
);
377+
$customerQuote = $newQuote;
373378
}
374379

375380
$this->setQuoteId($customerQuote->getId());

app/code/Magento/ConfigurableProduct/Block/Product/View/Type/Configurable.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
*/
88
namespace Magento\ConfigurableProduct\Block\Product\View\Type;
99

10+
use Magento\Catalog\Model\Product\Attribute\Source\Status;
1011
use Magento\ConfigurableProduct\Model\ConfigurableAttributeData;
1112
use Magento\Customer\Helper\Session\CurrentCustomer;
1213
use Magento\Customer\Model\Session;
@@ -183,8 +184,9 @@ public function getAllowProducts()
183184
$products = [];
184185
$skipSaleableCheck = $this->catalogProduct->getSkipSaleableCheck();
185186
$allProducts = $this->getProduct()->getTypeInstance()->getUsedProducts($this->getProduct(), null);
187+
/** @var $product \Magento\Catalog\Model\Product */
186188
foreach ($allProducts as $product) {
187-
if ($product->isSaleable() || $skipSaleableCheck) {
189+
if ($skipSaleableCheck || ((int) $product->getStatus()) === Status::STATUS_ENABLED) {
188190
$products[] = $product;
189191
}
190192
}

app/code/Magento/ConfigurableProduct/Helper/Data.php

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414

1515
/**
1616
* Class Data
17+
*
1718
* Helper class for getting options
1819
* @api
1920
* @since 100.0.2
@@ -87,8 +88,9 @@ public function getOptions($currentProduct, $allowedProducts)
8788
$productAttribute = $attribute->getProductAttribute();
8889
$productAttributeId = $productAttribute->getId();
8990
$attributeValue = $product->getData($productAttribute->getAttributeCode());
90-
91-
$options[$productAttributeId][$attributeValue][] = $productId;
91+
if ($product->isSalable()) {
92+
$options[$productAttributeId][$attributeValue][] = $productId;
93+
}
9294
$options['index'][$productId][$productAttributeId] = $attributeValue;
9395
}
9496
}

app/code/Magento/ConfigurableProduct/Plugin/Model/ResourceModel/Attribute/InStockOptionSelectBuilder.php

Lines changed: 22 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
*/
66
namespace Magento\ConfigurableProduct\Plugin\Model\ResourceModel\Attribute;
77

8+
use Magento\CatalogInventory\Api\StockConfigurationInterface;
89
use Magento\CatalogInventory\Model\ResourceModel\Stock\Status;
910
use Magento\ConfigurableProduct\Model\ResourceModel\Attribute\OptionSelectBuilderInterface;
1011
use Magento\Framework\DB\Select;
@@ -20,13 +21,21 @@ class InStockOptionSelectBuilder
2021
* @var Status
2122
*/
2223
private $stockStatusResource;
23-
24+
/**
25+
* @var StockConfigurationInterface
26+
*/
27+
private $stockConfig;
28+
2429
/**
2530
* @param Status $stockStatusResource
31+
* @param StockConfigurationInterface $stockConfig
2632
*/
27-
public function __construct(Status $stockStatusResource)
28-
{
33+
public function __construct(
34+
Status $stockStatusResource,
35+
StockConfigurationInterface $stockConfig
36+
) {
2937
$this->stockStatusResource = $stockStatusResource;
38+
$this->stockConfig = $stockConfig;
3039
}
3140

3241
/**
@@ -40,14 +49,16 @@ public function __construct(Status $stockStatusResource)
4049
*/
4150
public function afterGetSelect(OptionSelectBuilderInterface $subject, Select $select)
4251
{
43-
$select->joinInner(
44-
['stock' => $this->stockStatusResource->getMainTable()],
45-
'stock.product_id = entity.entity_id',
46-
[]
47-
)->where(
48-
'stock.stock_status = ?',
49-
\Magento\CatalogInventory\Model\Stock\Status::STATUS_IN_STOCK
50-
);
52+
if (!$this->stockConfig->isShowOutOfStock()) {
53+
$select->joinInner(
54+
['stock' => $this->stockStatusResource->getMainTable()],
55+
'stock.product_id = entity.entity_id',
56+
[]
57+
)->where(
58+
'stock.stock_status = ?',
59+
\Magento\CatalogInventory\Model\Stock\Status::STATUS_IN_STOCK
60+
);
61+
}
5162

5263
return $select;
5364
}

app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminCreateConfigurableProductWithThreeProductDisplayOutOfStockProductsTest.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,6 @@
135135
<!-- Assert out of stock option is absent on product page -->
136136
<amOnPage url="{{ApiConfigurableProduct.urlKey}}.html" stepKey="amOnProductPage"/>
137137
<waitForPageLoad stepKey="waitForProductPageLoad"/>
138-
<dontSee userInput="$$createConfigProductAttributeOptionThree.option[store_labels][1][label]$$" selector="{{StorefrontProductInfoMainSection.optionByAttributeId($$createConfigProductAttribute.attribute_id$$)}}" stepKey="assertOptionNotAvailable" />
138+
<see userInput="$$createConfigProductAttributeOptionThree.option[store_labels][1][label]$$" selector="{{StorefrontProductInfoMainSection.optionByAttributeId($$createConfigProductAttribute.attribute_id$$)}}" stepKey="assertOptionNotAvailable" />
139139
</test>
140140
</tests>

app/code/Magento/ConfigurableProduct/Test/Unit/Helper/DataTest.php

Lines changed: 22 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -87,11 +87,13 @@ public function testGetOptions(array $expected, array $data)
8787

8888
$this->_imageHelperMock->expects($this->any())
8989
->method('init')
90-
->willReturnMap([
91-
[$data['current_product_mock'], 'product_page_image_large', [], $imageHelper1],
92-
[$data['allowed_products'][0], 'product_page_image_large', [], $imageHelper1],
93-
[$data['allowed_products'][1], 'product_page_image_large', [], $imageHelper2],
94-
]);
90+
->willReturnMap(
91+
[
92+
[$data['current_product_mock'], 'product_page_image_large', [], $imageHelper1],
93+
[$data['allowed_products'][0], 'product_page_image_large', [], $imageHelper1],
94+
[$data['allowed_products'][1], 'product_page_image_large', [], $imageHelper2],
95+
]
96+
);
9597
}
9698

9799
$this->assertEquals(
@@ -148,14 +150,18 @@ public function getOptionsDataProvider()
148150
for ($i = 1; $i <= 2; $i++) {
149151
$productMock = $this->createPartialMock(
150152
\Magento\Catalog\Model\Product::class,
151-
['getData', 'getImage', 'getId', '__wakeup', 'getMediaGalleryImages']
153+
['getData', 'getImage', 'getId', '__wakeup', 'getMediaGalleryImages', 'isSalable']
152154
);
153155
$productMock->expects($this->any())
154156
->method('getData')
155157
->will($this->returnCallback([$this, 'getDataCallback']));
156158
$productMock->expects($this->any())
157159
->method('getId')
158160
->will($this->returnValue('product_id_' . $i));
161+
$productMock
162+
->expects($this->any())
163+
->method('isSalable')
164+
->will($this->returnValue(true));
159165
if ($i == 2) {
160166
$productMock->expects($this->any())
161167
->method('getImage')
@@ -230,11 +236,13 @@ public function testGetGalleryImages()
230236
self::identicalTo('product_page_image_large')
231237
]
232238
)
233-
->will(self::onConsecutiveCalls(
234-
'testSmallImageUrl',
235-
'testMediumImageUrl',
236-
'testLargeImageUrl'
237-
));
239+
->will(
240+
self::onConsecutiveCalls(
241+
'testSmallImageUrl',
242+
'testMediumImageUrl',
243+
'testLargeImageUrl'
244+
)
245+
);
238246
$this->_imageHelperMock->expects(self::never())
239247
->method('setImageFile')
240248
->with('test_file')
@@ -265,9 +273,9 @@ private function getImagesCollection()
265273
->getMock();
266274

267275
$items = [
268-
new \Magento\Framework\DataObject([
269-
'file' => 'test_file'
270-
]),
276+
new \Magento\Framework\DataObject(
277+
['file' => 'test_file']
278+
),
271279
];
272280

273281
$collectionMock->expects($this->any())

0 commit comments

Comments
 (0)