Skip to content

Commit b349218

Browse files
committed
FIX: out-of-stock options for configurable product visible on frontend as sellable
1 parent 5e4bb2b commit b349218

File tree

3 files changed

+44
-35
lines changed

3 files changed

+44
-35
lines changed

app/code/Magento/CatalogInventory/Model/ResourceModel/Stock/Status.php

Lines changed: 22 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@
55
*/
66
namespace Magento\CatalogInventory\Model\ResourceModel\Stock;
77

8-
use Magento\CatalogInventory\Model\Stock;
98
use Magento\CatalogInventory\Api\StockConfigurationInterface;
9+
use Magento\CatalogInventory\Model\Stock;
1010
use Magento\Framework\App\ObjectManager;
1111

1212
/**
@@ -46,19 +46,23 @@ class Status extends \Magento\Framework\Model\ResourceModel\Db\AbstractDb
4646
* @param \Magento\Store\Model\WebsiteFactory $websiteFactory
4747
* @param \Magento\Eav\Model\Config $eavConfig
4848
* @param string $connectionName
49+
* @param \Magento\CatalogInventory\Api\StockConfigurationInterface $stockConfiguration
4950
*/
5051
public function __construct(
5152
\Magento\Framework\Model\ResourceModel\Db\Context $context,
5253
\Magento\Store\Model\StoreManagerInterface $storeManager,
5354
\Magento\Store\Model\WebsiteFactory $websiteFactory,
5455
\Magento\Eav\Model\Config $eavConfig,
55-
$connectionName = null
56+
$connectionName = null,
57+
$stockConfiguration = null
5658
) {
5759
parent::__construct($context, $connectionName);
5860

5961
$this->_storeManager = $storeManager;
6062
$this->_websiteFactory = $websiteFactory;
6163
$this->eavConfig = $eavConfig;
64+
$this->stockConfiguration = $stockConfiguration ?: ObjectManager::getInstance()
65+
->get(StockConfigurationInterface::class);
6266
}
6367

6468
/**
@@ -204,7 +208,7 @@ public function getProductCollection($lastEntityId = 0, $limit = 1000)
204208
*/
205209
public function addStockStatusToSelect(\Magento\Framework\DB\Select $select, \Magento\Store\Model\Website $website)
206210
{
207-
$websiteId = $this->getStockConfiguration()->getDefaultScopeId();
211+
$websiteId = $this->getWebsiteId($website->getId());
208212
$select->joinLeft(
209213
['stock_status' => $this->getMainTable()],
210214
'e.entity_id = stock_status.product_id AND stock_status.website_id=' . $websiteId,
@@ -221,7 +225,7 @@ public function addStockStatusToSelect(\Magento\Framework\DB\Select $select, \Ma
221225
*/
222226
public function addStockDataToCollection($collection, $isFilterInStock)
223227
{
224-
$websiteId = $this->getStockConfiguration()->getDefaultScopeId();
228+
$websiteId = $this->getWebsiteId();
225229
$joinCondition = $this->getConnection()->quoteInto(
226230
'e.entity_id = stock_status_index.product_id' . ' AND stock_status_index.website_id = ?',
227231
$websiteId
@@ -255,7 +259,7 @@ public function addStockDataToCollection($collection, $isFilterInStock)
255259
*/
256260
public function addIsInStockFilterToCollection($collection)
257261
{
258-
$websiteId = $this->getStockConfiguration()->getDefaultScopeId();
262+
$websiteId = $this->getWebsiteId();
259263
$joinCondition = $this->getConnection()->quoteInto(
260264
'e.entity_id = stock_status_index.product_id' . ' AND stock_status_index.website_id = ?',
261265
$websiteId
@@ -277,6 +281,19 @@ public function addIsInStockFilterToCollection($collection)
277281
return $this;
278282
}
279283

284+
/**
285+
* @param \Magento\Store\Model\Website $websiteId
286+
* @return int
287+
*/
288+
private function getWebsiteId($websiteId = null)
289+
{
290+
if (null === $websiteId) {
291+
$websiteId = $this->stockConfiguration->getDefaultScopeId();
292+
}
293+
294+
return $websiteId;
295+
}
296+
280297
/**
281298
* Retrieve Product(s) status for store
282299
* Return array where key is a product_id, value - status
@@ -335,18 +352,4 @@ public function getProductStatus($productIds, $storeId = null)
335352
}
336353
return $statuses;
337354
}
338-
339-
/**
340-
* @return StockConfigurationInterface
341-
*
342-
* @deprecated 100.1.0
343-
*/
344-
private function getStockConfiguration()
345-
{
346-
if ($this->stockConfiguration === null) {
347-
$this->stockConfiguration = \Magento\Framework\App\ObjectManager::getInstance()
348-
->get(\Magento\CatalogInventory\Api\StockConfigurationInterface::class);
349-
}
350-
return $this->stockConfiguration;
351-
}
352355
}

app/code/Magento/ConfigurableProduct/Model/Product/Type/Configurable.php

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,11 @@
1010
use Magento\Catalog\Api\Data\ProductInterface;
1111
use Magento\Catalog\Api\Data\ProductInterfaceFactory;
1212
use Magento\Catalog\Api\ProductRepositoryInterface;
13-
use Magento\ConfigurableProduct\Model\Product\Type\Collection\SalableProcessor;
1413
use Magento\Catalog\Model\Config;
14+
use Magento\Catalog\Model\Product\Gallery\ReadHandler as GalleryReadHandler;
15+
use Magento\ConfigurableProduct\Model\Product\Type\Collection\SalableProcessor;
1516
use Magento\Framework\App\ObjectManager;
1617
use Magento\Framework\EntityManager\MetadataPool;
17-
use Magento\Catalog\Model\Product\Gallery\ReadHandler as GalleryReadHandler;
1818

1919
/**
2020
* Configurable product type implementation
@@ -267,7 +267,6 @@ public function __construct(
267267
$productRepository,
268268
$serializer
269269
);
270-
271270
}
272271

273272
/**
@@ -1277,6 +1276,8 @@ public function getSalableUsedProducts(\Magento\Catalog\Model\Product $product,
12771276
* Load collection on sub-products for specified configurable product
12781277
*
12791278
* Load collection of sub-products, apply result to specified configurable product and store result to cache
1279+
* Please note $salableOnly parameter is used for backwards compatibility because of deprecated method
1280+
* getSalableUsedProducts
12801281
* Number of loaded sub-products depends on $salableOnly parameter
12811282
* $salableOnly = true - result array contains only salable sub-products
12821283
* $salableOnly = false - result array contains all sub-products
@@ -1293,7 +1294,7 @@ private function loadUsedProducts(\Magento\Catalog\Model\Product $product, $cach
12931294
if (!$product->hasData($dataFieldName)) {
12941295
$usedProducts = $this->readUsedProductsCacheData($cacheKey);
12951296
if ($usedProducts === null) {
1296-
$collection = $this->getConfiguredUsedProductCollection($product);
1297+
$collection = $this->getConfiguredUsedProductCollection($product, false);
12971298
if ($salableOnly) {
12981299
$collection = $this->salableProcessor->process($collection);
12991300
}
@@ -1387,13 +1388,18 @@ private function getUsedProductsCacheKey($keyParts)
13871388
* Retrieve related products collection with additional configuration
13881389
*
13891390
* @param \Magento\Catalog\Model\Product $product
1391+
* @param bool $skipStockFilter
13901392
* @return \Magento\ConfigurableProduct\Model\ResourceModel\Product\Type\Configurable\Product\Collection
13911393
*/
1392-
private function getConfiguredUsedProductCollection(\Magento\Catalog\Model\Product $product)
1393-
{
1394+
private function getConfiguredUsedProductCollection(
1395+
\Magento\Catalog\Model\Product $product,
1396+
$skipStockFilter = true
1397+
) {
13941398
$collection = $this->getUsedProductCollection($product);
1399+
if ($skipStockFilter) {
1400+
$collection->setFlag('has_stock_status_filter', true);
1401+
}
13951402
$collection
1396-
->setFlag('has_stock_status_filter', true)
13971403
->addAttributeToSelect($this->getCatalogConfig()->getProductAttributes())
13981404
->addFilterByRequiredOptions()
13991405
->setStoreId($product->getStoreId());

app/code/Magento/ConfigurableProduct/Test/Unit/Model/Product/Type/ConfigurableTest.php

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,20 +8,20 @@
88
use Magento\Catalog\Api\Data\ProductExtensionInterface;
99
use Magento\Catalog\Api\Data\ProductInterface;
1010
use Magento\Catalog\Model\Config;
11-
use Magento\ConfigurableProduct\Model\Product\Type\Configurable;
12-
use Magento\Framework\EntityManager\EntityMetadata;
13-
use Magento\Framework\EntityManager\MetadataPool;
14-
use Magento\Customer\Model\Session;
15-
use Magento\ConfigurableProduct\Model\ResourceModel\Product\Type\Configurable\Attribute\CollectionFactory;
16-
use Magento\ConfigurableProduct\Model\ResourceModel\Product\Type\Configurable\Attribute\Collection;
1711
use Magento\Catalog\Model\Product\Configuration\Item\Option\OptionInterface;
18-
use Magento\Framework\Api\ExtensionAttribute\JoinProcessorInterface;
12+
use Magento\ConfigurableProduct\Model\Product\Type\Collection\SalableProcessor;
13+
use Magento\ConfigurableProduct\Model\Product\Type\Configurable;
1914
use Magento\ConfigurableProduct\Model\Product\Type\Configurable\AttributeFactory;
15+
use Magento\ConfigurableProduct\Model\ResourceModel\Product\Type\Configurable\Attribute\Collection;
16+
use Magento\ConfigurableProduct\Model\ResourceModel\Product\Type\Configurable\Attribute\CollectionFactory;
17+
use Magento\ConfigurableProduct\Model\ResourceModel\Product\Type\Configurable\Product\Collection as ProductCollection;
2018
use Magento\ConfigurableProduct\Model\ResourceModel\Product\Type\ConfigurableFactory;
19+
use Magento\Customer\Model\Session;
2120
use Magento\Eav\Model\Entity\Attribute\Frontend\AbstractFrontend;
2221
use Magento\Eav\Model\Entity\Attribute\Source\AbstractSource;
23-
use Magento\ConfigurableProduct\Model\ResourceModel\Product\Type\Configurable\Product\Collection as ProductCollection;
24-
use Magento\ConfigurableProduct\Model\Product\Type\Collection\SalableProcessor;
22+
use Magento\Framework\Api\ExtensionAttribute\JoinProcessorInterface;
23+
use Magento\Framework\EntityManager\EntityMetadata;
24+
use Magento\Framework\EntityManager\MetadataPool;
2525

2626
/**
2727
* @SuppressWarnings(PHPMD.LongVariable)

0 commit comments

Comments
 (0)