From 80f6a36fafd0da486fb009c29c986ee07b7afd0c Mon Sep 17 00:00:00 2001 From: Vladyslav Podorozhnyi Date: Mon, 22 Oct 2018 09:18:46 +0300 Subject: [PATCH 1/6] magento/magento2#16069: Configurable product price is not displayed if all children are out of stock and even if Display Out of Stock Products is set to "yes" - allow "out of stock" items to be used for price determination of configurable product ONLY in case all child products are "out of stock". --- .../Catalog/Pricing/Render/FinalPriceBox.php | 12 +- .../Product/BaseSelectProcessorInterface.php | 27 +++ .../Product/LinkedProductSelectBuilder.php | 3 +- .../StockStatusBaseSelectProcessor.php | 32 +++- .../Product/Type/Configurable/StockStatus.php | 96 +++++++++++ .../Configurable/StockStatusInterface.php | 20 +++ .../Pricing/Render/FinalPriceBox.php | 19 ++- .../StockStatusBaseSelectProcessorTest.php | 35 +++- .../Type/Configurable/StockStatusTest.php | 155 ++++++++++++++++++ .../Magento/ConfigurableProduct/etc/di.xml | 1 + 10 files changed, 385 insertions(+), 15 deletions(-) create mode 100644 app/code/Magento/ConfigurableProduct/Model/ResourceModel/Product/BaseSelectProcessorInterface.php create mode 100644 app/code/Magento/ConfigurableProduct/Model/ResourceModel/Product/Type/Configurable/StockStatus.php create mode 100644 app/code/Magento/ConfigurableProduct/Model/ResourceModel/Product/Type/Configurable/StockStatusInterface.php create mode 100644 app/code/Magento/ConfigurableProduct/Test/Unit/Model/ResourceModel/Product/Type/Configurable/StockStatusTest.php diff --git a/app/code/Magento/Catalog/Pricing/Render/FinalPriceBox.php b/app/code/Magento/Catalog/Pricing/Render/FinalPriceBox.php index e0a92ea0e0bea..50a362ef2576f 100644 --- a/app/code/Magento/Catalog/Pricing/Render/FinalPriceBox.php +++ b/app/code/Magento/Catalog/Pricing/Render/FinalPriceBox.php @@ -64,7 +64,8 @@ public function __construct( */ protected function _toHtml() { - if (!$this->salableResolver->isSalable($this->getSaleableItem())) { + if (!$this->salableResolver->isSalable($this->getSaleableItem()) && + $this->isApplySalableCheck($this->getSaleableItem())) { return ''; } @@ -86,6 +87,15 @@ protected function _toHtml() return $this->wrapResult($result); } + /** + * @param SaleableInterface $salableItem + * @return bool + */ + protected function isApplySalableCheck(SaleableInterface $salableItem) + { + return true; + } + /** * Check is MSRP applicable for the current product. * diff --git a/app/code/Magento/ConfigurableProduct/Model/ResourceModel/Product/BaseSelectProcessorInterface.php b/app/code/Magento/ConfigurableProduct/Model/ResourceModel/Product/BaseSelectProcessorInterface.php new file mode 100644 index 0000000000000..6fa6d8eb9117a --- /dev/null +++ b/app/code/Magento/ConfigurableProduct/Model/ResourceModel/Product/BaseSelectProcessorInterface.php @@ -0,0 +1,27 @@ +linkedProductSelectBuilder->build($productId); foreach ($selects as $select) { - $this->baseSelectProcessor->process($select); + $this->baseSelectProcessor->process($select, $productId); } return $selects; diff --git a/app/code/Magento/ConfigurableProduct/Model/ResourceModel/Product/StockStatusBaseSelectProcessor.php b/app/code/Magento/ConfigurableProduct/Model/ResourceModel/Product/StockStatusBaseSelectProcessor.php index bc118043adc8e..e20a36795678f 100644 --- a/app/code/Magento/ConfigurableProduct/Model/ResourceModel/Product/StockStatusBaseSelectProcessor.php +++ b/app/code/Magento/ConfigurableProduct/Model/ResourceModel/Product/StockStatusBaseSelectProcessor.php @@ -3,13 +3,14 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + namespace Magento\ConfigurableProduct\Model\ResourceModel\Product; use Magento\Framework\DB\Select; -use Magento\Catalog\Model\ResourceModel\Product\BaseSelectProcessorInterface; use Magento\CatalogInventory\Api\StockConfigurationInterface; use Magento\CatalogInventory\Model\Stock\Status as StockStatus; use Magento\CatalogInventory\Model\ResourceModel\Stock\Status as StockStatusResource; +use Magento\ConfigurableProduct\Model\ResourceModel\Product\Type\Configurable\StockStatusInterface as StockStatusConfigurableInterface; /** * A Select object processor. @@ -28,24 +29,35 @@ class StockStatusBaseSelectProcessor implements BaseSelectProcessorInterface */ private $stockStatusResource; + /** + * @var StockStatusConfigurableInterface + */ + private $stockStatusConfigurableResource; + /** * @param StockConfigurationInterface $stockConfig * @param StockStatusResource $stockStatusResource + * @param StockStatusConfigurableInterface $stockStatusConfigurableResource */ public function __construct( StockConfigurationInterface $stockConfig, - StockStatusResource $stockStatusResource - ) { + StockStatusResource $stockStatusResource, + StockStatusConfigurableInterface $stockStatusConfigurableResource + ) + { $this->stockConfig = $stockConfig; $this->stockStatusResource = $stockStatusResource; + $this->stockStatusConfigurableResource = $stockStatusConfigurableResource; } /** * {@inheritdoc} */ - public function process(Select $select) + public function process(Select $select, $productId) { - if ($this->stockConfig->isShowOutOfStock()) { + if ($this->stockConfig->isShowOutOfStock() && + !$this->isAllChildOutOfStock($productId) + ) { $select->joinInner( ['stock' => $this->stockStatusResource->getMainTable()], sprintf( @@ -61,4 +73,14 @@ public function process(Select $select) return $select; } + + /** + * @param int $productId + * @return bool + * @throws \Exception + */ + protected function isAllChildOutOfStock($productId) + { + return $this->stockStatusConfigurableResource->isAllChildOutOfStock($productId); + } } diff --git a/app/code/Magento/ConfigurableProduct/Model/ResourceModel/Product/Type/Configurable/StockStatus.php b/app/code/Magento/ConfigurableProduct/Model/ResourceModel/Product/Type/Configurable/StockStatus.php new file mode 100644 index 0000000000000..4ebd4ce143b57 --- /dev/null +++ b/app/code/Magento/ConfigurableProduct/Model/ResourceModel/Product/Type/Configurable/StockStatus.php @@ -0,0 +1,96 @@ +resource = $resource; + $this->metadataPool = $metadataPool; + } + + /** + * @var array + */ + private $allChildOutOfStockInfo = []; + + /** + * @inheritdoc + */ + public function isAllChildOutOfStock($productId) + { + if (isset($this->allChildOutOfStockInfo[$productId])) { + return $this->allChildOutOfStockInfo[$productId]; + } + + $statuses = $this->getAllChildStockInfo($productId); + $isAllChildOutOfStock = true; + foreach ($statuses as $status) { + if ($status == CatalogInventoryStockStatusInterface::STATUS_IN_STOCK) { + $isAllChildOutOfStock = false; + break; + } + } + + $this->allChildOutOfStockInfo[$productId] = $isAllChildOutOfStock; + return $this->allChildOutOfStockInfo[$productId]; + } + + /** + * @return \Magento\Framework\DB\Adapter\AdapterInterface + */ + protected function getConnection() + { + return $this->resource->getConnection(ResourceConnection::DEFAULT_CONNECTION); + } + + /** + * @param int $productId + * @return array + * @throws \Exception + */ + protected function getAllChildStockInfo($productId) + { + $linkField = $this->metadataPool->getMetadata(ProductInterface::class)->getLinkField(); + $productTable = $this->resource->getTableName('catalog_product_entity'); + $productRelationTable = $this->resource->getTableName('catalog_product_relation'); + + $select = $this->getConnection()->select() + ->from(['parent' => $productTable], '', []) + ->joinInner( + ['link' => $productRelationTable], + "link.parent_id = parent.$linkField", + ['id' => 'child_id'] + )->joinInner( + ['stock' => $this->resource->getTableName('cataloginventory_stock_status')], + 'stock.product_id = link.child_id', + ['stock_status'] + )->where(sprintf('parent.%s = ?', $linkField), $productId); + + return $this->getConnection()->fetchPairs($select); + } +} diff --git a/app/code/Magento/ConfigurableProduct/Model/ResourceModel/Product/Type/Configurable/StockStatusInterface.php b/app/code/Magento/ConfigurableProduct/Model/ResourceModel/Product/Type/Configurable/StockStatusInterface.php new file mode 100644 index 0000000000000..0843b2b47bdcf --- /dev/null +++ b/app/code/Magento/ConfigurableProduct/Model/ResourceModel/Product/Type/Configurable/StockStatusInterface.php @@ -0,0 +1,20 @@ +lowestPriceOptionsProvider = $lowestPriceOptionsProvider ?: ObjectManager::getInstance()->get(LowestPriceOptionsProviderInterface::class); + $this->stockStatus = $stockStatus ?: ObjectManager::getInstance()->get(StockStatus::class); } /** @@ -77,4 +86,12 @@ public function hasSpecialPrice() } return false; } + + /** + * @inheritdoc + */ + protected function isApplySalableCheck(SaleableInterface $salableItem) + { + return !$this->stockStatus->isAllChildOutOfStock($salableItem->getId()); + } } diff --git a/app/code/Magento/ConfigurableProduct/Test/Unit/Model/ResourceModel/Product/StockStatusBaseSelectProcessorTest.php b/app/code/Magento/ConfigurableProduct/Test/Unit/Model/ResourceModel/Product/StockStatusBaseSelectProcessorTest.php index dac3cef35ffa5..776b91f7e4fd3 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Unit/Model/ResourceModel/Product/StockStatusBaseSelectProcessorTest.php +++ b/app/code/Magento/ConfigurableProduct/Test/Unit/Model/ResourceModel/Product/StockStatusBaseSelectProcessorTest.php @@ -12,6 +12,7 @@ use Magento\CatalogInventory\Model\Stock\Status as StockStatus; use Magento\CatalogInventory\Model\ResourceModel\Stock\Status as StockStatusResource; use Magento\ConfigurableProduct\Model\ResourceModel\Product\StockStatusBaseSelectProcessor; +use Magento\ConfigurableProduct\Model\ResourceModel\Product\Type\Configurable\StockStatusInterface as StockStatusConfigurableInterface; class StockStatusBaseSelectProcessorTest extends \PHPUnit\Framework\TestCase { @@ -25,11 +26,21 @@ class StockStatusBaseSelectProcessorTest extends \PHPUnit\Framework\TestCase */ private $stockConfigMock; + /** + * @var StockStatusConfigurableInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $stockStatusConfigurableResourceMock; + /** * @var string */ private $stockStatusTable = 'cataloginventory_stock_status'; + /** + * @var int + */ + private $productId = 1; + /** * @var StockStatusResource|\PHPUnit_Framework_MockObject_MockObject */ @@ -48,32 +59,42 @@ protected function setUp() ->method('getMainTable') ->willReturn($this->stockStatusTable); + $this->stockStatusConfigurableResourceMock = $this->getMockBuilder(StockStatusConfigurableInterface::class) + ->disableOriginalConstructor() + ->getMock(); + $this->subject = (new ObjectManager($this))->getObject( StockStatusBaseSelectProcessor::class, [ 'stockConfig' => $this->stockConfigMock, - 'stockStatusResource' => $this->stockStatusResourceMock + 'stockStatusResource' => $this->stockStatusResourceMock, + 'stockStatusConfigurableResource' => $this->stockStatusConfigurableResourceMock ] ); } /** * @param bool $isShowOutOfStock + * @param bool $isAllChildOutOfStock * * @dataProvider processDataProvider */ - public function testProcess($isShowOutOfStock) + public function testProcess($isShowOutOfStock, $isAllChildOutOfStock) { $this->stockConfigMock->expects($this->any()) ->method('isShowOutOfStock') ->willReturn($isShowOutOfStock); + $this->stockStatusConfigurableResourceMock->expects($this->any()) + ->method('isAllChildOutOfStock') + ->willReturn($isAllChildOutOfStock); + /** @var Select|\PHPUnit_Framework_MockObject_MockObject $selectMock */ $selectMock = $this->getMockBuilder(Select::class) ->disableOriginalConstructor() ->getMock(); - if ($isShowOutOfStock) { + if ($isShowOutOfStock && !$isAllChildOutOfStock) { $selectMock->expects($this->once()) ->method('joinInner') ->with( @@ -97,7 +118,7 @@ public function testProcess($isShowOutOfStock) ->method($this->anything()); } - $this->assertEquals($selectMock, $this->subject->process($selectMock)); + $this->assertEquals($selectMock, $this->subject->process($selectMock, $this->productId)); } /** @@ -106,8 +127,10 @@ public function testProcess($isShowOutOfStock) public function processDataProvider() { return [ - 'Out of stock products are being displayed' => [true], - 'Out of stock products are NOT being displayed' => [false] + 'Out of stock filter was NOT applied [true, true]' => [true, true], + 'Out of stock filter was applied [true, false]' => [true, false], + 'Out of stock filter was NOT applied [false, true]' => [false, true], + 'Out of stock filter was NOT applied [false, false]' => [false, false] ]; } } diff --git a/app/code/Magento/ConfigurableProduct/Test/Unit/Model/ResourceModel/Product/Type/Configurable/StockStatusTest.php b/app/code/Magento/ConfigurableProduct/Test/Unit/Model/ResourceModel/Product/Type/Configurable/StockStatusTest.php new file mode 100644 index 0000000000000..d12e40d63f12b --- /dev/null +++ b/app/code/Magento/ConfigurableProduct/Test/Unit/Model/ResourceModel/Product/Type/Configurable/StockStatusTest.php @@ -0,0 +1,155 @@ +connection = $this->getMockBuilder(AdapterInterface::class)->getMock(); + + $this->resource = $this->createMock(ResourceConnection::class); + $this->resource->expects($this->any())->method('getConnection')->will($this->returnValue($this->connection)); + $this->resource->expects($this->any())->method('getTableName')->willReturnArgument(0); + + $this->metadataPool = $this->createMock(MetadataPool::class); + $entityMetadataMock = $this->createMock(EntityMetadata::class); + $entityMetadataMock->method('getLinkField')->willReturn(\Magento\Eav\Model\Entity::DEFAULT_ENTITY_ID_FIELD); + $this->metadataPool->method('getMetadata')->willReturn($entityMetadataMock); + + $this->subject = (new ObjectManager($this))->getObject( + StockStatus::class, + [ + 'resource' => $this->resource, + 'metadataPool' => $this->metadataPool + ] + ); + } + + /** + * @param array $childStockInfo + * @param bool $result + * + * @throws \Exception + * + * @dataProvider processDataProvider + */ + public function testIsAllChildOutOfStock($childStockInfo, $result) + { + /** @var Select|\PHPUnit_Framework_MockObject_MockObject $selectMock */ + $selectMock = $this->getMockBuilder(Select::class) + ->disableOriginalConstructor() + ->getMock(); + + $selectMock->expects($this->once()) + ->method('from') + ->with(['parent' => $this->productTable], '', []) + ->willReturnSelf(); + + $selectMock->expects($this->exactly(2)) + ->method('joinInner') + ->withConsecutive( + [ + ['link' => $this->productRelationTable], + "link.parent_id = parent." . \Magento\Eav\Model\Entity::DEFAULT_ENTITY_ID_FIELD, + ['id' => 'child_id'] + ], + [ + ['stock' => $this->catalogInventoryTable], + 'stock.product_id = link.child_id', + ['stock_status'] + ] + )->willReturnSelf(); + + $selectMock->expects($this->once()) + ->method('where') + ->with( + sprintf('parent.%s = ?', \Magento\Eav\Model\Entity::DEFAULT_ENTITY_ID_FIELD), + $this->productId + ) + ->willReturnSelf(); + + $this->connection->method('select')->willReturn($selectMock); + $this->connection->method('fetchPairs')->willReturn($childStockInfo); + + $this->assertEquals($result, $this->subject->isAllChildOutOfStock($this->productId)); + } + + /** + * @return array + */ + public function processDataProvider() + { + return [ + 'All child out of stock' => [ + [ + '1' => StockStatusInterface::STATUS_OUT_OF_STOCK, + '2' => StockStatusInterface::STATUS_OUT_OF_STOCK, + '3' => StockStatusInterface::STATUS_OUT_OF_STOCK + ], + true + ], + 'NOT all child out of stock' => [ + [ + '1' => StockStatusInterface::STATUS_OUT_OF_STOCK, + '2' => StockStatusInterface::STATUS_IN_STOCK, + '3' => StockStatusInterface::STATUS_IN_STOCK + ], + false + ] + ]; + } +} diff --git a/app/code/Magento/ConfigurableProduct/etc/di.xml b/app/code/Magento/ConfigurableProduct/etc/di.xml index dfbad0dd5a764..ade1dbe42fae8 100644 --- a/app/code/Magento/ConfigurableProduct/etc/di.xml +++ b/app/code/Magento/ConfigurableProduct/etc/di.xml @@ -16,6 +16,7 @@ + From b7868716855c8d3a57b332e4b27a4db04359af3e Mon Sep 17 00:00:00 2001 From: Vladyslav Podorozhnyi Date: Mon, 22 Oct 2018 14:44:55 +0300 Subject: [PATCH 2/6] magento/magento2#16069: Configurable product price is not displayed if all children are out of stock and even if Display Out of Stock Products is set to "yes" - add strict types. --- .../Catalog/Pricing/Render/FinalPriceBox.php | 5 +++-- .../Product/BaseSelectProcessorInterface.php | 4 +++- .../Product/LinkedProductSelectBuilder.php | 6 ++++-- .../StockStatusBaseSelectProcessor.php | 19 +++++++++++-------- .../Product/Type/Configurable/StockStatus.php | 15 +++++++++------ .../Configurable/StockStatusInterface.php | 4 +++- .../Pricing/Render/FinalPriceBox.php | 6 ++++-- .../StockStatusBaseSelectProcessorTest.php | 3 ++- 8 files changed, 39 insertions(+), 23 deletions(-) diff --git a/app/code/Magento/Catalog/Pricing/Render/FinalPriceBox.php b/app/code/Magento/Catalog/Pricing/Render/FinalPriceBox.php index 50a362ef2576f..eba0f878f4c43 100644 --- a/app/code/Magento/Catalog/Pricing/Render/FinalPriceBox.php +++ b/app/code/Magento/Catalog/Pricing/Render/FinalPriceBox.php @@ -3,6 +3,7 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); namespace Magento\Catalog\Pricing\Render; @@ -91,9 +92,9 @@ protected function _toHtml() * @param SaleableInterface $salableItem * @return bool */ - protected function isApplySalableCheck(SaleableInterface $salableItem) + protected function isApplySalableCheck(SaleableInterface $salableItem): bool { - return true; + return (bool)$salableItem; } /** diff --git a/app/code/Magento/ConfigurableProduct/Model/ResourceModel/Product/BaseSelectProcessorInterface.php b/app/code/Magento/ConfigurableProduct/Model/ResourceModel/Product/BaseSelectProcessorInterface.php index 6fa6d8eb9117a..bc61a596ca352 100644 --- a/app/code/Magento/ConfigurableProduct/Model/ResourceModel/Product/BaseSelectProcessorInterface.php +++ b/app/code/Magento/ConfigurableProduct/Model/ResourceModel/Product/BaseSelectProcessorInterface.php @@ -3,6 +3,8 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\ConfigurableProduct\Model\ResourceModel\Product; use Magento\Framework\DB\Select; @@ -23,5 +25,5 @@ interface BaseSelectProcessorInterface * @param int $productId * @return Select */ - public function process(Select $select, $productId); + public function process(Select $select, int $productId): Select; } diff --git a/app/code/Magento/ConfigurableProduct/Model/ResourceModel/Product/LinkedProductSelectBuilder.php b/app/code/Magento/ConfigurableProduct/Model/ResourceModel/Product/LinkedProductSelectBuilder.php index 2881d4eb36b6e..a85bff57cd553 100644 --- a/app/code/Magento/ConfigurableProduct/Model/ResourceModel/Product/LinkedProductSelectBuilder.php +++ b/app/code/Magento/ConfigurableProduct/Model/ResourceModel/Product/LinkedProductSelectBuilder.php @@ -3,6 +3,8 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\ConfigurableProduct\Model\ResourceModel\Product; use Magento\Catalog\Model\ResourceModel\Product\LinkedProductSelectBuilderInterface; @@ -38,9 +40,9 @@ public function __construct( } /** - * {@inheritdoc} + * @inheritdoc */ - public function build($productId) + public function build($productId): array { $selects = $this->linkedProductSelectBuilder->build($productId); diff --git a/app/code/Magento/ConfigurableProduct/Model/ResourceModel/Product/StockStatusBaseSelectProcessor.php b/app/code/Magento/ConfigurableProduct/Model/ResourceModel/Product/StockStatusBaseSelectProcessor.php index e20a36795678f..c7fe5e0be362b 100644 --- a/app/code/Magento/ConfigurableProduct/Model/ResourceModel/Product/StockStatusBaseSelectProcessor.php +++ b/app/code/Magento/ConfigurableProduct/Model/ResourceModel/Product/StockStatusBaseSelectProcessor.php @@ -3,14 +3,17 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); namespace Magento\ConfigurableProduct\Model\ResourceModel\Product; use Magento\Framework\DB\Select; +use Magento\Framework\App\ObjectManager; use Magento\CatalogInventory\Api\StockConfigurationInterface; use Magento\CatalogInventory\Model\Stock\Status as StockStatus; use Magento\CatalogInventory\Model\ResourceModel\Stock\Status as StockStatusResource; -use Magento\ConfigurableProduct\Model\ResourceModel\Product\Type\Configurable\StockStatusInterface as StockStatusConfigurableInterface; +use Magento\ConfigurableProduct\Model\ResourceModel\Product\Type\Configurable\StockStatusInterface + as StockStatusConfigurableInterface; /** * A Select object processor. @@ -42,18 +45,18 @@ class StockStatusBaseSelectProcessor implements BaseSelectProcessorInterface public function __construct( StockConfigurationInterface $stockConfig, StockStatusResource $stockStatusResource, - StockStatusConfigurableInterface $stockStatusConfigurableResource - ) - { + StockStatusConfigurableInterface $stockStatusConfigurableResource = null + ) { $this->stockConfig = $stockConfig; $this->stockStatusResource = $stockStatusResource; - $this->stockStatusConfigurableResource = $stockStatusConfigurableResource; + $this->stockStatusConfigurableResource = $stockStatusConfigurableResource ?: + ObjectManager::getInstance()->get(StockStatusConfigurableInterface::class); } /** - * {@inheritdoc} + * @inheritdoc */ - public function process(Select $select, $productId) + public function process(Select $select, int $productId): Select { if ($this->stockConfig->isShowOutOfStock() && !$this->isAllChildOutOfStock($productId) @@ -79,7 +82,7 @@ public function process(Select $select, $productId) * @return bool * @throws \Exception */ - protected function isAllChildOutOfStock($productId) + private function isAllChildOutOfStock(int $productId): bool { return $this->stockStatusConfigurableResource->isAllChildOutOfStock($productId); } diff --git a/app/code/Magento/ConfigurableProduct/Model/ResourceModel/Product/Type/Configurable/StockStatus.php b/app/code/Magento/ConfigurableProduct/Model/ResourceModel/Product/Type/Configurable/StockStatus.php index 4ebd4ce143b57..2fadac8dcf94c 100644 --- a/app/code/Magento/ConfigurableProduct/Model/ResourceModel/Product/Type/Configurable/StockStatus.php +++ b/app/code/Magento/ConfigurableProduct/Model/ResourceModel/Product/Type/Configurable/StockStatus.php @@ -3,12 +3,15 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\ConfigurableProduct\Model\ResourceModel\Product\Type\Configurable; use Magento\Framework\EntityManager\MetadataPool; use Magento\Framework\App\ResourceConnection; use Magento\Catalog\Api\Data\ProductInterface; use Magento\CatalogInventory\Api\Data\StockStatusInterface as CatalogInventoryStockStatusInterface; +use Magento\Framework\DB\Adapter\AdapterInterface; class StockStatus implements StockStatusInterface { @@ -41,7 +44,7 @@ public function __construct(ResourceConnection $resource, MetadataPool $metadata /** * @inheritdoc */ - public function isAllChildOutOfStock($productId) + public function isAllChildOutOfStock(int $productId): bool { if (isset($this->allChildOutOfStockInfo[$productId])) { return $this->allChildOutOfStockInfo[$productId]; @@ -50,7 +53,7 @@ public function isAllChildOutOfStock($productId) $statuses = $this->getAllChildStockInfo($productId); $isAllChildOutOfStock = true; foreach ($statuses as $status) { - if ($status == CatalogInventoryStockStatusInterface::STATUS_IN_STOCK) { + if ($status === CatalogInventoryStockStatusInterface::STATUS_IN_STOCK) { $isAllChildOutOfStock = false; break; } @@ -61,11 +64,11 @@ public function isAllChildOutOfStock($productId) } /** - * @return \Magento\Framework\DB\Adapter\AdapterInterface + * @return AdapterInterface */ - protected function getConnection() + protected function getConnection(): AdapterInterface { - return $this->resource->getConnection(ResourceConnection::DEFAULT_CONNECTION); + return $this->resource->getConnection(); } /** @@ -73,7 +76,7 @@ protected function getConnection() * @return array * @throws \Exception */ - protected function getAllChildStockInfo($productId) + private function getAllChildStockInfo(int $productId): array { $linkField = $this->metadataPool->getMetadata(ProductInterface::class)->getLinkField(); $productTable = $this->resource->getTableName('catalog_product_entity'); diff --git a/app/code/Magento/ConfigurableProduct/Model/ResourceModel/Product/Type/Configurable/StockStatusInterface.php b/app/code/Magento/ConfigurableProduct/Model/ResourceModel/Product/Type/Configurable/StockStatusInterface.php index 0843b2b47bdcf..5c9073375f442 100644 --- a/app/code/Magento/ConfigurableProduct/Model/ResourceModel/Product/Type/Configurable/StockStatusInterface.php +++ b/app/code/Magento/ConfigurableProduct/Model/ResourceModel/Product/Type/Configurable/StockStatusInterface.php @@ -3,6 +3,8 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\ConfigurableProduct\Model\ResourceModel\Product\Type\Configurable; /** @@ -16,5 +18,5 @@ interface StockStatusInterface * @return bool * @throws \Exception */ - public function isAllChildOutOfStock($productId); + public function isAllChildOutOfStock(int $productId): bool; } diff --git a/app/code/Magento/ConfigurableProduct/Pricing/Render/FinalPriceBox.php b/app/code/Magento/ConfigurableProduct/Pricing/Render/FinalPriceBox.php index 6fd692b68cd46..7b0d5bace8c55 100644 --- a/app/code/Magento/ConfigurableProduct/Pricing/Render/FinalPriceBox.php +++ b/app/code/Magento/ConfigurableProduct/Pricing/Render/FinalPriceBox.php @@ -3,6 +3,8 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\ConfigurableProduct\Pricing\Render; use Magento\Catalog\Model\Product\Pricing\Renderer\SalableResolverInterface; @@ -74,7 +76,7 @@ public function __construct( * * @return bool */ - public function hasSpecialPrice() + public function hasSpecialPrice(): bool { $product = $this->getSaleableItem(); foreach ($this->lowestPriceOptionsProvider->getProducts($product) as $subProduct) { @@ -90,7 +92,7 @@ public function hasSpecialPrice() /** * @inheritdoc */ - protected function isApplySalableCheck(SaleableInterface $salableItem) + protected function isApplySalableCheck(SaleableInterface $salableItem): bool { return !$this->stockStatus->isAllChildOutOfStock($salableItem->getId()); } diff --git a/app/code/Magento/ConfigurableProduct/Test/Unit/Model/ResourceModel/Product/StockStatusBaseSelectProcessorTest.php b/app/code/Magento/ConfigurableProduct/Test/Unit/Model/ResourceModel/Product/StockStatusBaseSelectProcessorTest.php index 776b91f7e4fd3..40a12e09e285d 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Unit/Model/ResourceModel/Product/StockStatusBaseSelectProcessorTest.php +++ b/app/code/Magento/ConfigurableProduct/Test/Unit/Model/ResourceModel/Product/StockStatusBaseSelectProcessorTest.php @@ -12,7 +12,8 @@ use Magento\CatalogInventory\Model\Stock\Status as StockStatus; use Magento\CatalogInventory\Model\ResourceModel\Stock\Status as StockStatusResource; use Magento\ConfigurableProduct\Model\ResourceModel\Product\StockStatusBaseSelectProcessor; -use Magento\ConfigurableProduct\Model\ResourceModel\Product\Type\Configurable\StockStatusInterface as StockStatusConfigurableInterface; +use Magento\ConfigurableProduct\Model\ResourceModel\Product\Type\Configurable\StockStatusInterface + as StockStatusConfigurableInterface; class StockStatusBaseSelectProcessorTest extends \PHPUnit\Framework\TestCase { From d0092968ac482ad62121eba4bb171f13bbc07260 Mon Sep 17 00:00:00 2001 From: Vladyslav Podorozhnyi Date: Mon, 22 Oct 2018 16:19:57 +0300 Subject: [PATCH 3/6] magento/magento2#16069: Configurable product price is not displayed if all children are out of stock and even if Display Out of Stock Products is set to "yes" - refactor variable names. --- .../Product/StockStatusBaseSelectProcessor.php | 10 +++++----- .../Pricing/Render/FinalPriceBox.php | 6 +++--- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/app/code/Magento/ConfigurableProduct/Model/ResourceModel/Product/StockStatusBaseSelectProcessor.php b/app/code/Magento/ConfigurableProduct/Model/ResourceModel/Product/StockStatusBaseSelectProcessor.php index c7fe5e0be362b..5574bd3434db3 100644 --- a/app/code/Magento/ConfigurableProduct/Model/ResourceModel/Product/StockStatusBaseSelectProcessor.php +++ b/app/code/Magento/ConfigurableProduct/Model/ResourceModel/Product/StockStatusBaseSelectProcessor.php @@ -35,21 +35,21 @@ class StockStatusBaseSelectProcessor implements BaseSelectProcessorInterface /** * @var StockStatusConfigurableInterface */ - private $stockStatusConfigurableResource; + private $stockConfigurable; /** * @param StockConfigurationInterface $stockConfig * @param StockStatusResource $stockStatusResource - * @param StockStatusConfigurableInterface $stockStatusConfigurableResource + * @param StockStatusConfigurableInterface $stockConfigurable */ public function __construct( StockConfigurationInterface $stockConfig, StockStatusResource $stockStatusResource, - StockStatusConfigurableInterface $stockStatusConfigurableResource = null + StockStatusConfigurableInterface $stockConfigurable = null ) { $this->stockConfig = $stockConfig; $this->stockStatusResource = $stockStatusResource; - $this->stockStatusConfigurableResource = $stockStatusConfigurableResource ?: + $this->stockConfigurable = $stockConfigurable ?: ObjectManager::getInstance()->get(StockStatusConfigurableInterface::class); } @@ -84,6 +84,6 @@ public function process(Select $select, int $productId): Select */ private function isAllChildOutOfStock(int $productId): bool { - return $this->stockStatusConfigurableResource->isAllChildOutOfStock($productId); + return $this->stockConfigurable->isAllChildOutOfStock($productId); } } diff --git a/app/code/Magento/ConfigurableProduct/Pricing/Render/FinalPriceBox.php b/app/code/Magento/ConfigurableProduct/Pricing/Render/FinalPriceBox.php index 7b0d5bace8c55..ebac55f653154 100644 --- a/app/code/Magento/ConfigurableProduct/Pricing/Render/FinalPriceBox.php +++ b/app/code/Magento/ConfigurableProduct/Pricing/Render/FinalPriceBox.php @@ -41,7 +41,7 @@ class FinalPriceBox extends \Magento\Catalog\Pricing\Render\FinalPriceBox * @param array $data * @param LowestPriceOptionsProviderInterface $lowestPriceOptionsProvider * @param SalableResolverInterface|null $salableResolver - * @param MinimalPriceCalculatorInterface|null $minimalPriceCalculator + * @param MinimalPriceCalculatorInterface|null $minPriceCalculator * @param StockStatus $stockStatus * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ @@ -54,7 +54,7 @@ public function __construct( array $data = [], LowestPriceOptionsProviderInterface $lowestPriceOptionsProvider = null, SalableResolverInterface $salableResolver = null, - MinimalPriceCalculatorInterface $minimalPriceCalculator = null, + MinimalPriceCalculatorInterface $minPriceCalculator = null, StockStatus $stockStatus = null ) { parent::__construct( @@ -64,7 +64,7 @@ public function __construct( $rendererPool, $data, $salableResolver, - $minimalPriceCalculator + $minPriceCalculator ); $this->lowestPriceOptionsProvider = $lowestPriceOptionsProvider ?: ObjectManager::getInstance()->get(LowestPriceOptionsProviderInterface::class); From 0b19e04703a39e0f61d6db702648e006b0b1835b Mon Sep 17 00:00:00 2001 From: Vladyslav Podorozhnyi Date: Mon, 22 Oct 2018 16:21:54 +0300 Subject: [PATCH 4/6] magento/magento2#16069: Configurable product price is not displayed if all children are out of stock and even if Display Out of Stock Products is set to "yes" - add blank space. --- .../ResourceModel/Product/Type/Configurable/StockStatus.php | 1 + 1 file changed, 1 insertion(+) diff --git a/app/code/Magento/ConfigurableProduct/Model/ResourceModel/Product/Type/Configurable/StockStatus.php b/app/code/Magento/ConfigurableProduct/Model/ResourceModel/Product/Type/Configurable/StockStatus.php index 2fadac8dcf94c..5f06759f2dfea 100644 --- a/app/code/Magento/ConfigurableProduct/Model/ResourceModel/Product/Type/Configurable/StockStatus.php +++ b/app/code/Magento/ConfigurableProduct/Model/ResourceModel/Product/Type/Configurable/StockStatus.php @@ -60,6 +60,7 @@ public function isAllChildOutOfStock(int $productId): bool } $this->allChildOutOfStockInfo[$productId] = $isAllChildOutOfStock; + return $this->allChildOutOfStockInfo[$productId]; } From 207e2e14306ff1ebea3294d82ed47b8804778d73 Mon Sep 17 00:00:00 2001 From: Vladyslav Podorozhnyi Date: Mon, 22 Oct 2018 20:46:00 +0300 Subject: [PATCH 5/6] magento#16069: Configurable product price is not displayed if all children are out of stock and even if Display Out of Stock Products is set to "yes" - remove unneeded usage of the variable. --- app/code/Magento/Catalog/Pricing/Render/FinalPriceBox.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/code/Magento/Catalog/Pricing/Render/FinalPriceBox.php b/app/code/Magento/Catalog/Pricing/Render/FinalPriceBox.php index eba0f878f4c43..0ba54c8745c92 100644 --- a/app/code/Magento/Catalog/Pricing/Render/FinalPriceBox.php +++ b/app/code/Magento/Catalog/Pricing/Render/FinalPriceBox.php @@ -91,10 +91,11 @@ protected function _toHtml() /** * @param SaleableInterface $salableItem * @return bool + * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ protected function isApplySalableCheck(SaleableInterface $salableItem): bool { - return (bool)$salableItem; + return true; } /** From f4ec67e5aada173a27586131598d5b24957472d6 Mon Sep 17 00:00:00 2001 From: Vladyslav Podorozhnyi Date: Tue, 23 Oct 2018 19:25:35 +0300 Subject: [PATCH 6/6] magento/magento2#16069: Configurable product price is not displayed if all children are out of stock and even if Display Out of Stock Products is set to "yes" --- .../Catalog/Pricing/Render/FinalPriceBox.php | 5 +++-- .../Product/LinkedProductSelectBuilder.php | 2 +- .../Pricing/Render/FinalPriceBox.php | 20 ++++++++++++------- .../LinkedProductSelectBuilderTest.php | 4 +++- .../StockStatusBaseSelectProcessorTest.php | 12 ++++++----- .../Type/Configurable/StockStatusTest.php | 1 + 6 files changed, 28 insertions(+), 16 deletions(-) diff --git a/app/code/Magento/Catalog/Pricing/Render/FinalPriceBox.php b/app/code/Magento/Catalog/Pricing/Render/FinalPriceBox.php index 0ba54c8745c92..9add6074558fd 100644 --- a/app/code/Magento/Catalog/Pricing/Render/FinalPriceBox.php +++ b/app/code/Magento/Catalog/Pricing/Render/FinalPriceBox.php @@ -65,8 +65,9 @@ public function __construct( */ protected function _toHtml() { - if (!$this->salableResolver->isSalable($this->getSaleableItem()) && - $this->isApplySalableCheck($this->getSaleableItem())) { + if ($this->isApplySalableCheck($this->getSaleableItem()) && + !$this->salableResolver->isSalable($this->getSaleableItem()) + ) { return ''; } diff --git a/app/code/Magento/ConfigurableProduct/Model/ResourceModel/Product/LinkedProductSelectBuilder.php b/app/code/Magento/ConfigurableProduct/Model/ResourceModel/Product/LinkedProductSelectBuilder.php index a85bff57cd553..bd93f183f09a0 100644 --- a/app/code/Magento/ConfigurableProduct/Model/ResourceModel/Product/LinkedProductSelectBuilder.php +++ b/app/code/Magento/ConfigurableProduct/Model/ResourceModel/Product/LinkedProductSelectBuilder.php @@ -47,7 +47,7 @@ public function build($productId): array $selects = $this->linkedProductSelectBuilder->build($productId); foreach ($selects as $select) { - $this->baseSelectProcessor->process($select, $productId); + $this->baseSelectProcessor->process($select, (int)$productId); } return $selects; diff --git a/app/code/Magento/ConfigurableProduct/Pricing/Render/FinalPriceBox.php b/app/code/Magento/ConfigurableProduct/Pricing/Render/FinalPriceBox.php index ebac55f653154..edc6be8fe5b2e 100644 --- a/app/code/Magento/ConfigurableProduct/Pricing/Render/FinalPriceBox.php +++ b/app/code/Magento/ConfigurableProduct/Pricing/Render/FinalPriceBox.php @@ -18,8 +18,12 @@ use Magento\Framework\Pricing\Render\RendererPool; use Magento\Framework\Pricing\SaleableInterface; use Magento\Framework\View\Element\Template\Context; -use Magento\ConfigurableProduct\Model\ResourceModel\Product\Type\Configurable\StockStatus; +use Magento\CatalogInventory\Api\StockConfigurationInterface; +/** + * Class FinalPriceBox + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ class FinalPriceBox extends \Magento\Catalog\Pricing\Render\FinalPriceBox { /** @@ -28,9 +32,9 @@ class FinalPriceBox extends \Magento\Catalog\Pricing\Render\FinalPriceBox private $lowestPriceOptionsProvider; /** - * @var StockStatus + * @var StockConfigurationInterface */ - private $stockStatus; + private $stockConfig; /** * @param Context $context @@ -42,8 +46,9 @@ class FinalPriceBox extends \Magento\Catalog\Pricing\Render\FinalPriceBox * @param LowestPriceOptionsProviderInterface $lowestPriceOptionsProvider * @param SalableResolverInterface|null $salableResolver * @param MinimalPriceCalculatorInterface|null $minPriceCalculator - * @param StockStatus $stockStatus + * @param StockConfigurationInterface|null $stockConfig * @SuppressWarnings(PHPMD.UnusedFormalParameter) + * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ public function __construct( Context $context, @@ -55,7 +60,7 @@ public function __construct( LowestPriceOptionsProviderInterface $lowestPriceOptionsProvider = null, SalableResolverInterface $salableResolver = null, MinimalPriceCalculatorInterface $minPriceCalculator = null, - StockStatus $stockStatus = null + StockConfigurationInterface $stockConfig = null ) { parent::__construct( $context, @@ -68,7 +73,8 @@ public function __construct( ); $this->lowestPriceOptionsProvider = $lowestPriceOptionsProvider ?: ObjectManager::getInstance()->get(LowestPriceOptionsProviderInterface::class); - $this->stockStatus = $stockStatus ?: ObjectManager::getInstance()->get(StockStatus::class); + $this->stockConfig = $stockConfig ?: + ObjectManager::getInstance()->get(StockConfigurationInterface::class); } /** @@ -94,6 +100,6 @@ public function hasSpecialPrice(): bool */ protected function isApplySalableCheck(SaleableInterface $salableItem): bool { - return !$this->stockStatus->isAllChildOutOfStock($salableItem->getId()); + return !$this->stockConfig->isShowOutOfStock(); } } diff --git a/app/code/Magento/ConfigurableProduct/Test/Unit/Model/ResourceModel/Product/LinkedProductSelectBuilderTest.php b/app/code/Magento/ConfigurableProduct/Test/Unit/Model/ResourceModel/Product/LinkedProductSelectBuilderTest.php index 46dc9edb48dcc..9b934f6cf246b 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Unit/Model/ResourceModel/Product/LinkedProductSelectBuilderTest.php +++ b/app/code/Magento/ConfigurableProduct/Test/Unit/Model/ResourceModel/Product/LinkedProductSelectBuilderTest.php @@ -3,11 +3,13 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\ConfigurableProduct\Test\Unit\Model\ResourceModel\Product; use Magento\Framework\DB\Select; use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; -use Magento\Catalog\Model\ResourceModel\Product\BaseSelectProcessorInterface; +use Magento\ConfigurableProduct\Model\ResourceModel\Product\BaseSelectProcessorInterface; use Magento\Catalog\Model\ResourceModel\Product\LinkedProductSelectBuilderInterface; use Magento\ConfigurableProduct\Model\ResourceModel\Product\LinkedProductSelectBuilder; diff --git a/app/code/Magento/ConfigurableProduct/Test/Unit/Model/ResourceModel/Product/StockStatusBaseSelectProcessorTest.php b/app/code/Magento/ConfigurableProduct/Test/Unit/Model/ResourceModel/Product/StockStatusBaseSelectProcessorTest.php index 40a12e09e285d..6220e4d106488 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Unit/Model/ResourceModel/Product/StockStatusBaseSelectProcessorTest.php +++ b/app/code/Magento/ConfigurableProduct/Test/Unit/Model/ResourceModel/Product/StockStatusBaseSelectProcessorTest.php @@ -3,11 +3,13 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\ConfigurableProduct\Test\Unit\Model\ResourceModel\Product; use Magento\Framework\DB\Select; use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; -use Magento\Catalog\Model\ResourceModel\Product\BaseSelectProcessorInterface; +use Magento\ConfigurableProduct\Model\ResourceModel\Product\BaseSelectProcessorInterface; use Magento\CatalogInventory\Api\StockConfigurationInterface; use Magento\CatalogInventory\Model\Stock\Status as StockStatus; use Magento\CatalogInventory\Model\ResourceModel\Stock\Status as StockStatusResource; @@ -30,7 +32,7 @@ class StockStatusBaseSelectProcessorTest extends \PHPUnit\Framework\TestCase /** * @var StockStatusConfigurableInterface|\PHPUnit_Framework_MockObject_MockObject */ - private $stockStatusConfigurableResourceMock; + private $stockConfigurable; /** * @var string @@ -60,7 +62,7 @@ protected function setUp() ->method('getMainTable') ->willReturn($this->stockStatusTable); - $this->stockStatusConfigurableResourceMock = $this->getMockBuilder(StockStatusConfigurableInterface::class) + $this->stockConfigurable = $this->getMockBuilder(StockStatusConfigurableInterface::class) ->disableOriginalConstructor() ->getMock(); @@ -69,7 +71,7 @@ protected function setUp() [ 'stockConfig' => $this->stockConfigMock, 'stockStatusResource' => $this->stockStatusResourceMock, - 'stockStatusConfigurableResource' => $this->stockStatusConfigurableResourceMock + 'stockConfigurable' => $this->stockConfigurable ] ); } @@ -86,7 +88,7 @@ public function testProcess($isShowOutOfStock, $isAllChildOutOfStock) ->method('isShowOutOfStock') ->willReturn($isShowOutOfStock); - $this->stockStatusConfigurableResourceMock->expects($this->any()) + $this->stockConfigurable->expects($this->any()) ->method('isAllChildOutOfStock') ->willReturn($isAllChildOutOfStock); diff --git a/app/code/Magento/ConfigurableProduct/Test/Unit/Model/ResourceModel/Product/Type/Configurable/StockStatusTest.php b/app/code/Magento/ConfigurableProduct/Test/Unit/Model/ResourceModel/Product/Type/Configurable/StockStatusTest.php index d12e40d63f12b..18a90e13b6796 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Unit/Model/ResourceModel/Product/Type/Configurable/StockStatusTest.php +++ b/app/code/Magento/ConfigurableProduct/Test/Unit/Model/ResourceModel/Product/Type/Configurable/StockStatusTest.php @@ -3,6 +3,7 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); namespace Magento\ConfigurableProduct\Test\Unit\Model\ResourceModel\Product\Type\Configurable;