Skip to content

Commit 9a68fbd

Browse files
committed
MAGETWO-90739: Out of stock options for configurable products still show up in search and layered navigation if Elasticsearch is enabled
1 parent 81f6b3e commit 9a68fbd

File tree

22 files changed

+645
-144
lines changed

22 files changed

+645
-144
lines changed

app/code/Magento/CatalogInventory/Api/Data/StockStatusInterface.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,14 @@
1414
*/
1515
interface StockStatusInterface extends ExtensibleDataInterface
1616
{
17+
/**#@+
18+
* Stock Status values.
19+
*/
20+
const STATUS_OUT_OF_STOCK = 0;
21+
22+
const STATUS_IN_STOCK = 1;
23+
/**#@-*/
24+
1725
/**#@+
1826
* Stock status object data keys
1927
*/

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

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,6 @@
1717
*/
1818
class Status extends AbstractExtensibleModel implements StockStatusInterface
1919
{
20-
/**#@+
21-
* Stock Status values
22-
*/
23-
const STATUS_OUT_OF_STOCK = 0;
24-
25-
const STATUS_IN_STOCK = 1;
26-
/**#@-*/
27-
2820
/**#@+
2921
* Field name
3022
*/

app/code/Magento/Elasticsearch/Model/Config.php

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
namespace Magento\Elasticsearch\Model;
77

88
use Magento\Framework\App\Config\ScopeConfigInterface;
9+
use Magento\Search\Model\EngineResolver;
910
use Magento\Store\Model\ScopeInterface;
1011
use Magento\AdvancedSearch\Model\Client\ClientOptionsInterface;
1112
use Magento\AdvancedSearch\Model\Client\ClientResolver;
@@ -58,17 +59,19 @@ class Config implements ClientOptionsInterface
5859
* Constructor
5960
*
6061
* @param ScopeConfigInterface $scopeConfig
61-
* @param ClientResolver $clientResolver
62-
* @param string $prefix
62+
* @param ClientResolver|null $clientResolver
63+
* @param EngineResolver|null $engineResolver
64+
* @param string|null $prefix
6365
*/
6466
public function __construct(
6567
ScopeConfigInterface $scopeConfig,
6668
ClientResolver $clientResolver = null,
69+
EngineResolver $engineResolver = null,
6770
$prefix = null
6871
) {
6972
$this->scopeConfig = $scopeConfig;
70-
$this->clientResolver = $clientResolver ?:
71-
ObjectManager::getInstance()->get(ClientResolver::class);
73+
$this->clientResolver = $clientResolver ?: ObjectManager::getInstance()->get(ClientResolver::class);
74+
$this->engineResolver = $engineResolver ?: ObjectManager::getInstance()->get(EngineResolver::class);
7275
$this->prefix = $prefix ?: $this->clientResolver->getCurrentEngine();
7376
}
7477

@@ -126,7 +129,7 @@ public function getSearchConfigData($field, $storeId = null)
126129
*/
127130
public function isElasticsearchEnabled()
128131
{
129-
return $this->getSearchConfigData('engine') == self::ENGINE_NAME;
132+
return $this->engineResolver->getCurrentSearchEngine() === self::ENGINE_NAME;
130133
}
131134

132135
/**
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
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\Elasticsearch\Model\Indexer\Plugin;
9+
10+
use Magento\Elasticsearch\Model\Config;
11+
use Magento\Framework\Indexer\Config\DependencyInfoProvider as Provider;
12+
use Magento\CatalogSearch\Model\Indexer\Fulltext as CatalogSearchFulltextIndexer;
13+
use Magento\CatalogInventory\Model\Indexer\Stock\Processor as CatalogInventoryStockIndexer;
14+
15+
/**
16+
* Plugin for maintenance catalog search index dependency on stock index.
17+
* If elasticsearch is used as search engine catalog search index becomes dependent on stock index. Elasticsearch
18+
* module declares this dependence. But in case when elasticsearch module is enabled and elasticsearch engine isn`t
19+
* used as search engine other search engines don`t need this dependency.
20+
* This plugin remove catalog search index dependency on stock index when elasticsearch isn`t used as search engine
21+
* except full reindexing. During full reindexing this dependency doesn`t make overhead.
22+
*/
23+
class DependencyUpdaterPlugin
24+
{
25+
/**
26+
* @var Config
27+
*/
28+
private $config;
29+
30+
/**
31+
* @param Config $config
32+
*/
33+
public function __construct(Config $config)
34+
{
35+
$this->config = $config;
36+
}
37+
38+
/**
39+
* Remove index dependency, if it needed, on run reindexing by specifics indexes.
40+
*
41+
* @param Provider $provider
42+
* @param array $dependencies
43+
* @param string $indexerId
44+
* @return array
45+
* @see \Magento\Indexer\Console\Command\IndexerReindexCommand::getIndexers()
46+
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
47+
*/
48+
public function afterGetIndexerIdsToRunBefore(Provider $provider, array $dependencies, string $indexerId): array
49+
{
50+
if ($this->isFilteringNeeded($indexerId, CatalogSearchFulltextIndexer::INDEXER_ID)) {
51+
$dependencies = array_diff($dependencies, [CatalogInventoryStockIndexer::INDEXER_ID]);
52+
}
53+
54+
return $dependencies;
55+
}
56+
57+
/**
58+
* Remove index dependency, if it needed, on reindex triggers.
59+
*
60+
* @param Provider $provider
61+
* @param array $dependencies
62+
* @param string $indexerId
63+
* @return array
64+
* @see \Magento\Indexer\Model\Indexer\DependencyDecorator
65+
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
66+
*/
67+
public function afterGetIndexerIdsToRunAfter(Provider $provider, array $dependencies, string $indexerId): array
68+
{
69+
if ($this->isFilteringNeeded($indexerId, CatalogInventoryStockIndexer::INDEXER_ID)) {
70+
$dependencies = array_diff($dependencies, [CatalogSearchFulltextIndexer::INDEXER_ID]);
71+
}
72+
73+
return $dependencies;
74+
}
75+
76+
/**
77+
* @param string $currentIndexerId
78+
* @param string $targetIndexerId
79+
* @return bool
80+
*/
81+
private function isFilteringNeeded(string $currentIndexerId, string $targetIndexerId): bool
82+
{
83+
return (!$this->config->isElasticsearchEnabled() && $targetIndexerId === $currentIndexerId);
84+
}
85+
}
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
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\Elasticsearch\Model\Indexer\Plugin;
9+
10+
use Magento\Elasticsearch\Model\Config;
11+
use Magento\CatalogInventory\Api\StockConfigurationInterface;
12+
use Magento\CatalogInventory\Api\StockStatusRepositoryInterface;
13+
use Magento\CatalogInventory\Api\StockStatusCriteriaInterfaceFactory;
14+
use Magento\CatalogInventory\Api\Data\StockStatusInterface;
15+
use Magento\CatalogSearch\Model\Indexer\Fulltext\Action\DataProvider;
16+
17+
/**
18+
* Plugin for filtering child products that are out of stock for preventing their saving to catalog search index.
19+
*/
20+
class StockedProductsFilterPlugin
21+
{
22+
/**
23+
* @var Config
24+
*/
25+
private $config;
26+
27+
/**
28+
* @var StockConfigurationInterface
29+
*/
30+
private $stockConfiguration;
31+
32+
/**
33+
* @var StockStatusRepositoryInterface
34+
*/
35+
private $stockStatusRepository;
36+
37+
/**
38+
* @var StockStatusCriteriaInterfaceFactory
39+
*/
40+
private $stockStatusCriteriaFactory;
41+
42+
/**
43+
* @param Config $config
44+
* @param StockConfigurationInterface $stockConfiguration
45+
* @param StockStatusRepositoryInterface $stockStatusRepository
46+
* @param StockStatusCriteriaInterfaceFactory $stockStatusCriteriaFactory
47+
*/
48+
public function __construct(
49+
Config $config,
50+
StockConfigurationInterface $stockConfiguration,
51+
StockStatusRepositoryInterface $stockStatusRepository,
52+
StockStatusCriteriaInterfaceFactory $stockStatusCriteriaFactory
53+
) {
54+
$this->config = $config;
55+
$this->stockConfiguration = $stockConfiguration;
56+
$this->stockStatusRepository = $stockStatusRepository;
57+
$this->stockStatusCriteriaFactory = $stockStatusCriteriaFactory;
58+
}
59+
60+
/**
61+
* Filter out of stock options for configurable product.
62+
*
63+
* @param DataProvider $dataProvider
64+
* @param array $indexData
65+
* @param array $productData
66+
* @param int $storeId
67+
* @return array
68+
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
69+
*/
70+
public function beforePrepareProductIndex(
71+
DataProvider $dataProvider,
72+
array $indexData,
73+
array $productData,
74+
int $storeId
75+
): array {
76+
if ($this->config->isElasticsearchEnabled() && !$this->stockConfiguration->isShowOutOfStock($storeId)) {
77+
$productIds = array_keys($indexData);
78+
$stockStatusCriteria = $this->stockStatusCriteriaFactory->create();
79+
$stockStatusCriteria->setProductsFilter($productIds);
80+
$stockStatusCollection = $this->stockStatusRepository->getList($stockStatusCriteria);
81+
$stockStatuses = $stockStatusCollection->getItems();
82+
$stockStatuses = array_filter($stockStatuses, function (StockStatusInterface $stockStatus) {
83+
return StockStatusInterface::STATUS_IN_STOCK == $stockStatus->getStockStatus();
84+
});
85+
$indexData = array_intersect_key($indexData, $stockStatuses);
86+
}
87+
88+
return [
89+
$indexData,
90+
$productData,
91+
$storeId,
92+
];
93+
}
94+
}
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
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\Elasticsearch\Test\Unit\Model\Indexer\Plugin;
9+
10+
use Magento\Elasticsearch\Model\Config;
11+
use Magento\Elasticsearch\Model\Indexer\Plugin\DependencyUpdaterPlugin;
12+
use Magento\Framework\Indexer\Config\DependencyInfoProvider;
13+
use Magento\CatalogSearch\Model\Indexer\Fulltext as CatalogSearchFulltextIndexer;
14+
use Magento\CatalogInventory\Model\Indexer\Stock\Processor as CatalogInventoryStockIndexer;
15+
16+
/**
17+
* Test for Magento\Elasticsearch\Model\Indexer\Plugin\DependencyUpdaterPlugin class.
18+
*/
19+
class DependencyUpdaterPluginTest extends \PHPUnit\Framework\TestCase
20+
{
21+
/**
22+
* @var Config|\PHPUnit_Framework_MockObject_MockObject
23+
*/
24+
private $configMock;
25+
26+
/**
27+
* @var DependencyUpdaterPlugin
28+
*/
29+
private $plugin;
30+
31+
/**
32+
* @var DependencyInfoProvider|\PHPUnit_Framework_MockObject_MockObject
33+
*/
34+
private $providerMock;
35+
36+
protected function setUp()
37+
{
38+
$this->configMock = $this->getMockBuilder(Config::class)
39+
->disableOriginalConstructor()
40+
->getMock();
41+
$this->configMock->expects($this->exactly(2))
42+
->method('isElasticsearchEnabled')
43+
->willReturnOnConsecutiveCalls(true, false);
44+
$this->providerMock = $this->getMockBuilder(DependencyInfoProvider::class)
45+
->disableOriginalConstructor()
46+
->getMock();
47+
48+
$this->plugin = new DependencyUpdaterPlugin($this->configMock);
49+
}
50+
51+
/**
52+
* @return void
53+
*/
54+
public function testAfterGetIndexerIdsToRunBefore(): void
55+
{
56+
$dependencies = [
57+
CatalogInventoryStockIndexer::INDEXER_ID,
58+
];
59+
$indexerId = CatalogSearchFulltextIndexer::INDEXER_ID;
60+
61+
$indexerIds = $this->plugin->afterGetIndexerIdsToRunBefore($this->providerMock, $dependencies, $indexerId);
62+
$this->assertContains(CatalogInventoryStockIndexer::INDEXER_ID, $indexerIds);
63+
64+
$indexerIds = $this->plugin->afterGetIndexerIdsToRunBefore($this->providerMock, $dependencies, $indexerId);
65+
$this->assertNotContains(CatalogInventoryStockIndexer::INDEXER_ID, $indexerIds);
66+
}
67+
68+
/**
69+
* @return void
70+
*/
71+
public function testAfterGetIndexerIdsToRunAfter(): void
72+
{
73+
$dependencies = [
74+
CatalogSearchFulltextIndexer::INDEXER_ID,
75+
];
76+
$indexerId = CatalogInventoryStockIndexer::INDEXER_ID;
77+
78+
$indexerIds = $this->plugin->afterGetIndexerIdsToRunAfter($this->providerMock, $dependencies, $indexerId);
79+
$this->assertContains(CatalogSearchFulltextIndexer::INDEXER_ID, $indexerIds);
80+
81+
$indexerIds = $this->plugin->afterGetIndexerIdsToRunAfter($this->providerMock, $dependencies, $indexerId);
82+
$this->assertNotContains(CatalogSearchFulltextIndexer::INDEXER_ID, $indexerIds);
83+
}
84+
}

0 commit comments

Comments
 (0)