Skip to content

Commit c0f43b3

Browse files
author
Anna Bukatar
committed
Merge branch 'ACP2E-322' of https://github.com/magento-l3/magento2ce into PR-2022-02-22
2 parents 031b784 + 8b67891 commit c0f43b3

File tree

8 files changed

+459
-9
lines changed

8 files changed

+459
-9
lines changed

app/code/Magento/CatalogInventory/Model/ResourceModel/StockStatusFilter.php

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,18 @@
1010
use Magento\CatalogInventory\Api\Data\StockStatusInterface;
1111
use Magento\CatalogInventory\Api\StockConfigurationInterface;
1212
use Magento\CatalogInventory\Model\Stock;
13+
use Magento\CatalogInventory\Model\StockStatusApplierInterface;
1314
use Magento\Framework\App\ResourceConnection;
1415
use Magento\Framework\DB\Select;
16+
use Magento\Framework\App\ObjectManager;
1517

1618
/**
1719
* Generic in-stock status filter
1820
*/
1921
class StockStatusFilter implements StockStatusFilterInterface
2022
{
2123
private const TABLE_NAME = 'cataloginventory_stock_status';
24+
2225
/**
2326
* @var ResourceConnection
2427
*/
@@ -29,16 +32,25 @@ class StockStatusFilter implements StockStatusFilterInterface
2932
*/
3033
private $stockConfiguration;
3134

35+
/**
36+
* @var StockStatusApplierInterface
37+
*/
38+
private $stockStatusApplier;
39+
3240
/**
3341
* @param ResourceConnection $resource
3442
* @param StockConfigurationInterface $stockConfiguration
43+
* @param StockStatusApplierInterface|null $stockStatusApplier
3544
*/
3645
public function __construct(
3746
ResourceConnection $resource,
38-
StockConfigurationInterface $stockConfiguration
47+
StockConfigurationInterface $stockConfiguration,
48+
?StockStatusApplierInterface $stockStatusApplier = null
3949
) {
4050
$this->resource = $resource;
4151
$this->stockConfiguration = $stockConfiguration;
52+
$this->stockStatusApplier = $stockStatusApplier
53+
?? ObjectManager::getInstance()->get(StockStatusApplierInterface::class);
4254
}
4355

4456
/**
@@ -67,7 +79,12 @@ public function execute(
6779
implode(' AND ', $joinCondition),
6880
[]
6981
);
70-
$select->where("{$stockStatusTableAlias}.stock_status = ?", StockStatusInterface::STATUS_IN_STOCK);
82+
83+
if ($this->stockStatusApplier->hasSearchResultApplier()) {
84+
$select->columns(["{$stockStatusTableAlias}.stock_status AS is_salable"]);
85+
} else {
86+
$select->where("{$stockStatusTableAlias}.stock_status = ?", StockStatusInterface::STATUS_IN_STOCK);
87+
}
7188

7289
return $select;
7390
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
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\CatalogInventory\Model;
9+
10+
/**
11+
* Search Result Applier getters and setters
12+
*/
13+
class StockStatusApplier implements StockStatusApplierInterface
14+
{
15+
/**
16+
* Storefront search result applier flag
17+
*
18+
* @var bool
19+
*/
20+
private $searchResultApplier = false;
21+
22+
/**
23+
* Set flag, if the request is originated from SearchResultApplier
24+
*
25+
* @param bool $status
26+
*/
27+
public function setSearchResultApplier(bool $status): void
28+
{
29+
$this->searchResultApplier = $status;
30+
}
31+
32+
/**
33+
* Get flag, if the request is originated from SearchResultApplier
34+
*
35+
* @return bool
36+
*/
37+
public function hasSearchResultApplier() : bool
38+
{
39+
return $this->searchResultApplier;
40+
}
41+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
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\CatalogInventory\Model;
9+
10+
/**
11+
* Search Result Applier interface.
12+
*/
13+
interface StockStatusApplierInterface
14+
{
15+
16+
/**
17+
* Set flag, if the request is originated from SearchResultApplier
18+
*
19+
* @param bool $status
20+
*/
21+
public function setSearchResultApplier(bool $status): void;
22+
23+
/**
24+
* Get flag, if the request is originated from SearchResultApplier
25+
*
26+
* @return bool
27+
*/
28+
public function hasSearchResultApplier() : bool;
29+
}

app/code/Magento/CatalogInventory/etc/di.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333

3434
<preference for="Magento\CatalogInventory\Model\ResourceModel\QtyCounterInterface" type="Magento\CatalogInventory\Model\ResourceModel\Stock" />
3535
<preference for="Magento\CatalogInventory\Model\ResourceModel\StockStatusFilterInterface" type="Magento\CatalogInventory\Model\ResourceModel\StockStatusFilter" />
36+
<preference for="Magento\CatalogInventory\Model\StockStatusApplierInterface" type="Magento\CatalogInventory\Model\StockStatusApplier" />
3637
<type name="Magento\Catalog\Model\Product\Attribute\Repository">
3738
<plugin name="filterCustomAttribute" type="Magento\CatalogInventory\Model\Plugin\FilterCustomAttribute" />
3839
</type>

app/code/Magento/Elasticsearch/Model/ResourceModel/Fulltext/Collection/SearchResultApplier.php

Lines changed: 174 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,20 @@
66

77
namespace Magento\Elasticsearch\Model\ResourceModel\Fulltext\Collection;
88

9+
use Magento\Catalog\Api\Data\ProductInterface;
10+
use Magento\CatalogInventory\Model\StockStatusApplierInterface;
11+
use Magento\CatalogInventory\Model\ResourceModel\StockStatusFilterInterface;
912
use Magento\CatalogSearch\Model\ResourceModel\Fulltext\Collection\SearchResultApplierInterface;
1013
use Magento\Framework\Api\Search\SearchResultInterface;
14+
use Magento\Framework\App\Config\ScopeConfigInterface;
15+
use Magento\Framework\App\ObjectManager;
1116
use Magento\Framework\Data\Collection;
17+
use Magento\Framework\EntityManager\MetadataPool;
1218

1319
/**
1420
* Resolve specific attributes for search criteria.
21+
*
22+
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
1523
*/
1624
class SearchResultApplier implements SearchResultApplierInterface
1725
{
@@ -35,22 +43,56 @@ class SearchResultApplier implements SearchResultApplierInterface
3543
*/
3644
private $currentPage;
3745

46+
/**
47+
* @var ScopeConfigInterface
48+
*/
49+
private $scopeConfig;
50+
51+
/**
52+
* @var MetadataPool
53+
*/
54+
private $metadataPool;
55+
56+
/**
57+
* @var StockStatusFilterInterface
58+
*/
59+
private $stockStatusFilter;
60+
61+
/**
62+
* @var StockStatusApplierInterface
63+
*/
64+
private $stockStatusApplier;
65+
3866
/**
3967
* @param Collection $collection
4068
* @param SearchResultInterface $searchResult
4169
* @param int $size
4270
* @param int $currentPage
71+
* @param ScopeConfigInterface|null $scopeConfig
72+
* @param MetadataPool|null $metadataPool
73+
* @param StockStatusFilterInterface|null $stockStatusFilter
74+
* @param StockStatusApplierInterface|null $stockStatusApplier
4375
*/
4476
public function __construct(
4577
Collection $collection,
4678
SearchResultInterface $searchResult,
4779
int $size,
48-
int $currentPage
80+
int $currentPage,
81+
?ScopeConfigInterface $scopeConfig = null,
82+
?MetadataPool $metadataPool = null,
83+
?StockStatusFilterInterface $stockStatusFilter = null,
84+
?StockStatusApplierInterface $stockStatusApplier = null
4985
) {
5086
$this->collection = $collection;
5187
$this->searchResult = $searchResult;
5288
$this->size = $size;
5389
$this->currentPage = $currentPage;
90+
$this->scopeConfig = $scopeConfig ?? ObjectManager::getInstance()->get(ScopeConfigInterface::class);
91+
$this->metadataPool = $metadataPool ?? ObjectManager::getInstance()->get(MetadataPool::class);
92+
$this->stockStatusFilter = $stockStatusFilter
93+
?? ObjectManager::getInstance()->get(StockStatusFilterInterface::class);
94+
$this->stockStatusApplier = $stockStatusApplier
95+
?? ObjectManager::getInstance()->get(StockStatusApplierInterface::class);
5496
}
5597

5698
/**
@@ -60,16 +102,18 @@ public function apply()
60102
{
61103
if (empty($this->searchResult->getItems())) {
62104
$this->collection->getSelect()->where('NULL');
63-
64105
return;
65106
}
66107

67-
$items = $this->sliceItems($this->searchResult->getItems(), $this->size, $this->currentPage);
68-
$ids = [];
69-
foreach ($items as $item) {
70-
$ids[] = (int)$item->getId();
108+
$ids = $this->getProductIdsBySaleability();
109+
110+
if (count($ids) == 0) {
111+
$items = $this->sliceItems($this->searchResult->getItems(), $this->size, $this->currentPage);
112+
foreach ($items as $item) {
113+
$ids[] = (int)$item->getId();
114+
}
71115
}
72-
$orderList = join(',', $ids);
116+
$orderList = implode(',', $ids);
73117
$this->collection->getSelect()
74118
->where('e.entity_id IN (?)', $ids)
75119
->reset(\Magento\Framework\DB\Select::ORDER)
@@ -116,4 +160,127 @@ private function getOffset(int $pageNumber, int $pageSize): int
116160
{
117161
return ($pageNumber - 1) * $pageSize;
118162
}
163+
/**
164+
* Fetch filtered product ids sorted by the saleability and other applied sort orders
165+
*
166+
* @return array
167+
*/
168+
private function getProductIdsBySaleability(): array
169+
{
170+
$ids = [];
171+
172+
if (!$this->hasShowOutOfStockStatus()) {
173+
return $ids;
174+
}
175+
176+
if ($this->collection->getFlag('has_stock_status_filter')
177+
|| $this->collection->getFlag('has_category_filter')) {
178+
$categoryId = null;
179+
$searchCriteria = $this->searchResult->getSearchCriteria();
180+
foreach ($searchCriteria->getFilterGroups() as $filterGroup) {
181+
foreach ($filterGroup->getFilters() as $filter) {
182+
if ($filter->getField() === 'category_ids') {
183+
$categoryId = $filter->getValue();
184+
break 2;
185+
}
186+
}
187+
}
188+
189+
if ($categoryId) {
190+
$resultSet = $this->categoryProductByCustomSortOrder($categoryId);
191+
foreach ($resultSet as $item) {
192+
$ids[] = (int)$item['entity_id'];
193+
}
194+
}
195+
}
196+
197+
return $ids;
198+
}
199+
200+
/**
201+
* Fetch product resultset by custom sort orders
202+
*
203+
* @param int $categoryId
204+
* @return array
205+
* @throws \Magento\Framework\Exception\LocalizedException
206+
* @throws \Exception
207+
*/
208+
private function categoryProductByCustomSortOrder(int $categoryId): array
209+
{
210+
$storeId = $this->collection->getStoreId();
211+
$searchCriteria = $this->searchResult->getSearchCriteria();
212+
$sortOrders = $searchCriteria->getSortOrders() ?? [];
213+
$sortOrders = array_merge(['is_salable' => \Magento\Framework\DB\Select::SQL_DESC], $sortOrders);
214+
215+
$connection = $this->collection->getConnection();
216+
$query = clone $connection->select()
217+
->reset(\Magento\Framework\DB\Select::ORDER)
218+
->reset(\Magento\Framework\DB\Select::LIMIT_COUNT)
219+
->reset(\Magento\Framework\DB\Select::LIMIT_OFFSET)
220+
->reset(\Magento\Framework\DB\Select::COLUMNS);
221+
$query->from(
222+
['e' => $this->collection->getTable('catalog_product_entity')],
223+
['e.entity_id']
224+
);
225+
$this->stockStatusApplier->setSearchResultApplier(true);
226+
$query = $this->stockStatusFilter->execute($query, 'e', 'stockItem');
227+
$query->join(
228+
['cat_index' => $this->collection->getTable('catalog_category_product_index_store' . $storeId)],
229+
'cat_index.product_id = e.entity_id'
230+
. ' AND cat_index.category_id = ' . $categoryId
231+
. ' AND cat_index.store_id = ' . $storeId,
232+
['cat_index.position']
233+
);
234+
foreach ($sortOrders as $field => $dir) {
235+
if ($field === 'name') {
236+
$entityTypeId = $this->collection->getEntity()->getTypeId();
237+
$entityMetadata = $this->metadataPool->getMetadata(ProductInterface::class);
238+
$linkField = $entityMetadata->getLinkField();
239+
$query->joinLeft(
240+
['product_var' => $this->collection->getTable('catalog_product_entity_varchar')],
241+
"product_var.{$linkField} = e.{$linkField} AND product_var.attribute_id =
242+
(SELECT attribute_id FROM eav_attribute WHERE entity_type_id={$entityTypeId}
243+
AND attribute_code='name')",
244+
['product_var.value AS name']
245+
);
246+
} elseif ($field === 'price') {
247+
$query->joinLeft(
248+
['price_index' => $this->collection->getTable('catalog_product_index_price')],
249+
'price_index.entity_id = e.entity_id'
250+
. ' AND price_index.customer_group_id = 0'
251+
. ' AND price_index.website_id = (Select website_id FROM store WHERE store_id = '
252+
. $storeId . ')',
253+
['price_index.max_price AS price']
254+
);
255+
}
256+
$columnFilters = [];
257+
$columnsParts = $query->getPart('columns');
258+
foreach ($columnsParts as $columns) {
259+
$columnFilters[] = $columns[2] ?? $columns[1];
260+
}
261+
if (in_array($field, $columnFilters, true)) {
262+
$query->order(new \Zend_Db_Expr("{$field} {$dir}"));
263+
}
264+
}
265+
266+
$query->limit(
267+
$searchCriteria->getPageSize(),
268+
$searchCriteria->getCurrentPage() * $searchCriteria->getPageSize()
269+
);
270+
271+
return $connection->fetchAssoc($query) ?? [];
272+
}
273+
274+
/**
275+
* Returns if display out of stock status set or not in catalog inventory
276+
*
277+
* @return bool
278+
*/
279+
private function hasShowOutOfStockStatus(): bool
280+
{
281+
return (bool) $this->scopeConfig->getValue(
282+
\Magento\CatalogInventory\Model\Configuration::XML_PATH_SHOW_OUT_OF_STOCK,
283+
\Magento\Store\Model\ScopeInterface::SCOPE_STORE
284+
);
285+
}
119286
}

0 commit comments

Comments
 (0)