Skip to content

Commit 1d4b161

Browse files
MAGETWO-94427: [2.3] Mini cart not getting updated if product disabled from backed
1 parent f710f9b commit 1d4b161

File tree

8 files changed

+350
-48
lines changed

8 files changed

+350
-48
lines changed
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
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+
9+
<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
10+
xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd">
11+
<section name="StorefrontMiniCartSection">
12+
<element name="quantity" type="button" selector="span.counter-number"/>
13+
<element name="show" type="button" selector="a.showcart"/>
14+
<element name="goToCheckout" type="button" selector="#top-cart-btn-checkout" timeout="30"/>
15+
</section>
16+
</sections>
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
declare(strict_types=1);
7+
8+
namespace Magento\Quote\Model\Product\Plugin;
9+
10+
use Magento\Catalog\Model\Product\Attribute\Source\Status;
11+
use Magento\Catalog\Model\Product\Action as ProductAction;
12+
use Magento\Quote\Model\ResourceModel\Quote as QuoteResource;
13+
14+
/**
15+
* Remove quote items after mass disabling products
16+
*/
17+
class MarkQuotesRecollectMassDisabled
18+
{
19+
/** @var QuoteResource$quoteResource */
20+
private $quoteResource;
21+
22+
/**
23+
* @param QuoteResource $quoteResource
24+
*/
25+
public function __construct(
26+
QuoteResource $quoteResource
27+
) {
28+
$this->quoteResource = $quoteResource;
29+
}
30+
31+
/**
32+
* Clean quote items after mass disabling product
33+
*
34+
* @param \Magento\Catalog\Model\Product\Action $subject
35+
* @param \Magento\Catalog\Model\Product\Action $result
36+
* @param int[] $productIds
37+
* @param int[] $attrData
38+
* @param int $storeId
39+
* @return \Magento\Catalog\Model\Product\Action
40+
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
41+
*/
42+
public function afterUpdateAttributes(
43+
ProductAction $subject,
44+
ProductAction $result,
45+
$productIds,
46+
$attrData,
47+
$storeId
48+
): ProductAction {
49+
if (isset($attrData['status']) && $attrData['status'] === Status::STATUS_DISABLED) {
50+
$this->quoteResource->markQuotesRecollect($productIds);
51+
}
52+
53+
return $result;
54+
}
55+
}

app/code/Magento/Quote/Model/Quote/TotalsCollector.php

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,8 @@ public function __construct(
103103
}
104104

105105
/**
106+
* Collect quote totals.
107+
*
106108
* @param \Magento\Quote\Model\Quote $quote
107109
* @return Address\Total
108110
*/
@@ -115,6 +117,8 @@ public function collectQuoteTotals(\Magento\Quote\Model\Quote $quote)
115117
}
116118

117119
/**
120+
* Collect quote.
121+
*
118122
* @param \Magento\Quote\Model\Quote $quote
119123
* @return \Magento\Quote\Model\Quote\Address\Total
120124
*/
@@ -172,6 +176,8 @@ public function collect(\Magento\Quote\Model\Quote $quote)
172176
}
173177

174178
/**
179+
* Validate coupon code.
180+
*
175181
* @param \Magento\Quote\Model\Quote $quote
176182
* @return $this
177183
*/
@@ -203,11 +209,12 @@ protected function _validateCouponCode(\Magento\Quote\Model\Quote $quote)
203209
*/
204210
protected function _collectItemsQtys(\Magento\Quote\Model\Quote $quote)
205211
{
212+
$quoteItems = $quote->getAllVisibleItems();
206213
$quote->setItemsCount(0);
207214
$quote->setItemsQty(0);
208215
$quote->setVirtualItemsQty(0);
209216

210-
foreach ($quote->getAllVisibleItems() as $item) {
217+
foreach ($quoteItems as $item) {
211218
if ($item->getParentItem()) {
212219
continue;
213220
}
@@ -231,6 +238,8 @@ protected function _collectItemsQtys(\Magento\Quote\Model\Quote $quote)
231238
}
232239

233240
/**
241+
* Collect address total.
242+
*
234243
* @param \Magento\Quote\Model\Quote $quote
235244
* @param Address $address
236245
* @return Address\Total

app/code/Magento/Quote/Model/ResourceModel/Quote/Item/Collection.php

Lines changed: 96 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,16 @@
33
* Copyright © Magento, Inc. All rights reserved.
44
* See COPYING.txt for license details.
55
*/
6+
declare(strict_types=1);
7+
68
namespace Magento\Quote\Model\ResourceModel\Quote\Item;
79

8-
use \Magento\Catalog\Model\ResourceModel\Product\Collection as ProductCollection;
10+
use Magento\Catalog\Api\Data\ProductInterface;
11+
use Magento\Catalog\Model\ResourceModel\Product\Collection as ProductCollection;
12+
use Magento\Catalog\Model\Product\Attribute\Source\Status as ProductStatus;
13+
use Magento\Quote\Model\Quote;
14+
use Magento\Quote\Model\Quote\Item as QuoteItem;
15+
use Magento\Quote\Model\ResourceModel\Quote\Item as ResourceQuoteItem;
916

1017
/**
1118
* Quote item resource collection
@@ -50,6 +57,11 @@ class Collection extends \Magento\Framework\Model\ResourceModel\Db\VersionContro
5057
*/
5158
private $storeManager;
5259

60+
/**
61+
* @var bool $recollectQuote
62+
*/
63+
private $recollectQuote = false;
64+
5365
/**
5466
* @param \Magento\Framework\Data\Collection\EntityFactory $entityFactory
5567
* @param \Psr\Log\LoggerInterface $logger
@@ -102,15 +114,15 @@ public function __construct(
102114
*/
103115
protected function _construct()
104116
{
105-
$this->_init(\Magento\Quote\Model\Quote\Item::class, \Magento\Quote\Model\ResourceModel\Quote\Item::class);
117+
$this->_init(QuoteItem::class, ResourceQuoteItem::class);
106118
}
107119

108120
/**
109121
* Retrieve store Id (From Quote)
110122
*
111123
* @return int
112124
*/
113-
public function getStoreId()
125+
public function getStoreId(): int
114126
{
115127
// Fallback to current storeId if no quote is provided
116128
// (see https://github.com/magento/magento2/commit/9d3be732a88884a66d667b443b3dc1655ddd0721)
@@ -119,12 +131,12 @@ public function getStoreId()
119131
}
120132

121133
/**
122-
* Set Quote object to Collection
134+
* Set Quote object to Collection.
123135
*
124-
* @param \Magento\Quote\Model\Quote $quote
136+
* @param Quote $quote
125137
* @return $this
126138
*/
127-
public function setQuote($quote)
139+
public function setQuote($quote): self
128140
{
129141
$this->_quote = $quote;
130142
$quoteId = $quote->getId();
@@ -138,13 +150,15 @@ public function setQuote($quote)
138150
}
139151

140152
/**
141-
* Reset the collection and join it to quotes table. Optionally can select items with specified product id only.
153+
* Reset the collection and inner join it to quotes table.
154+
*
155+
* Optionally can select items with specified product id only
142156
*
143157
* @param string $quotesTableName
144158
* @param int $productId
145159
* @return $this
146160
*/
147-
public function resetJoinQuotes($quotesTableName, $productId = null)
161+
public function resetJoinQuotes($quotesTableName, $productId = null): self
148162
{
149163
$this->getSelect()->reset()->from(
150164
['qi' => $this->getResource()->getMainTable()],
@@ -161,11 +175,11 @@ public function resetJoinQuotes($quotesTableName, $productId = null)
161175
}
162176

163177
/**
164-
* After load processing
178+
* After load processing.
165179
*
166180
* @return $this
167181
*/
168-
protected function _afterLoad()
182+
protected function _afterLoad(): self
169183
{
170184
parent::_afterLoad();
171185

@@ -194,11 +208,11 @@ protected function _afterLoad()
194208
}
195209

196210
/**
197-
* Add options to items
211+
* Add options to items.
198212
*
199213
* @return $this
200214
*/
201-
protected function _assignOptions()
215+
protected function _assignOptions(): self
202216
{
203217
$itemIds = array_keys($this->_items);
204218
$optionCollection = $this->_itemOptionCollectionFactory->create()->addItemFilter($itemIds);
@@ -212,12 +226,12 @@ protected function _assignOptions()
212226
}
213227

214228
/**
215-
* Add products to items and item options
229+
* Add products to items and item options.
216230
*
217231
* @return $this
218232
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
219233
*/
220-
protected function _assignProducts()
234+
protected function _assignProducts(): self
221235
{
222236
\Magento\Framework\Profiler::start('QUOTE:' . __METHOD__, ['group' => 'QUOTE', 'method' => __METHOD__]);
223237
$productCollection = $this->_productCollectionFactory->create()->setStoreId(
@@ -239,53 +253,88 @@ protected function _assignProducts()
239253
['collection' => $productCollection]
240254
);
241255

242-
$recollectQuote = false;
243256
foreach ($this as $item) {
257+
/** @var ProductInterface $product */
244258
$product = $productCollection->getItemById($item->getProductId());
245-
if ($product) {
259+
$isValidProduct = $this->isValidProduct($product);
260+
$qtyOptions = [];
261+
if ($isValidProduct) {
246262
$product->setCustomOptions([]);
247-
$qtyOptions = [];
248-
$optionProductIds = [];
249-
foreach ($item->getOptions() as $option) {
250-
/**
251-
* Call type-specific logic for product associated with quote item
252-
*/
253-
$product->getTypeInstance()->assignProductToOption(
254-
$productCollection->getItemById($option->getProductId()),
255-
$option,
256-
$product
257-
);
258-
259-
if (is_object($option->getProduct()) && $option->getProduct()->getId() != $product->getId()) {
260-
$optionProductIds[$option->getProduct()->getId()] = $option->getProduct()->getId();
261-
}
262-
}
263-
264-
if ($optionProductIds) {
265-
foreach ($optionProductIds as $optionProductId) {
266-
$qtyOption = $item->getOptionByCode('product_qty_' . $optionProductId);
267-
if ($qtyOption) {
268-
$qtyOptions[$optionProductId] = $qtyOption;
269-
}
263+
$optionProductIds = $this->getOptionProductIds($item, $product, $productCollection);
264+
foreach ($optionProductIds as $optionProductId) {
265+
$qtyOption = $item->getOptionByCode('product_qty_' . $optionProductId);
266+
if ($qtyOption) {
267+
$qtyOptions[$optionProductId] = $qtyOption;
270268
}
271269
}
272-
273-
$item->setQtyOptions($qtyOptions)->setProduct($product);
274270
} else {
275271
$item->isDeleted(true);
276-
$recollectQuote = true;
272+
$this->recollectQuote = true;
273+
}
274+
if (!$item->isDeleted()) {
275+
$item->setQtyOptions($qtyOptions)->setProduct($product);
276+
$item->checkData();
277277
}
278-
$item->checkData();
279278
}
280-
281-
if ($recollectQuote && $this->_quote) {
279+
if ($this->recollectQuote && $this->_quote) {
282280
$this->_quote->collectTotals();
283281
}
284282
\Magento\Framework\Profiler::stop('QUOTE:' . __METHOD__);
285283

286284
return $this;
287285
}
288286

287+
/**
288+
* Get product Ids from option.
289+
*
290+
* @param QuoteItem $item
291+
* @param ProductInterface $product
292+
* @param ProductCollection $productCollection
293+
* @return array
294+
*/
295+
private function getOptionProductIds(
296+
QuoteItem $item,
297+
ProductInterface $product,
298+
ProductCollection $productCollection
299+
): array {
300+
$optionProductIds = [];
301+
foreach ($item->getOptions() as $option) {
302+
/**
303+
* Call type-specific logic for product associated with quote item
304+
*/
305+
$product->getTypeInstance()->assignProductToOption(
306+
$productCollection->getItemById($option->getProductId()),
307+
$option,
308+
$product
309+
);
310+
311+
if (is_object($option->getProduct()) && $option->getProduct()->getId() != $product->getId()) {
312+
$isValidProduct = $this->isValidProduct($option->getProduct());
313+
if (!$isValidProduct && !$item->isDeleted()) {
314+
$item->isDeleted(true);
315+
$this->recollectQuote = true;
316+
continue;
317+
}
318+
$optionProductIds[$option->getProduct()->getId()] = $option->getProduct()->getId();
319+
}
320+
}
321+
322+
return $optionProductIds;
323+
}
324+
325+
/**
326+
* Check is valid product.
327+
*
328+
* @param ProductInterface $product
329+
* @return bool
330+
*/
331+
private function isValidProduct(ProductInterface $product): bool
332+
{
333+
$result = ($product && (int)$product->getStatus() !== ProductStatus::STATUS_DISABLED);
334+
335+
return $result;
336+
}
337+
289338
/**
290339
* Prevents adding stock status filter to the collection of products.
291340
*
@@ -294,7 +343,7 @@ protected function _assignProducts()
294343
*
295344
* @see \Magento\CatalogInventory\Helper\Stock::addIsInStockFilterToCollection
296345
*/
297-
private function skipStockStatusFilter(ProductCollection $productCollection)
346+
private function skipStockStatusFilter(ProductCollection $productCollection): void
298347
{
299348
$productCollection->setFlag('has_stock_status_filter', true);
300349
}
@@ -304,7 +353,7 @@ private function skipStockStatusFilter(ProductCollection $productCollection)
304353
*
305354
* @return void
306355
*/
307-
private function removeItemsWithAbsentProducts()
356+
private function removeItemsWithAbsentProducts(): void
308357
{
309358
if (count($this->_productIds) === 0) {
310359
return;

0 commit comments

Comments
 (0)