6
6
7
7
namespace Magento \Elasticsearch \Model \ResourceModel \Fulltext \Collection ;
8
8
9
+ use Magento \Catalog \Api \Data \ProductInterface ;
10
+ use Magento \CatalogInventory \Model \StockStatusApplierInterface ;
11
+ use Magento \CatalogInventory \Model \ResourceModel \StockStatusFilterInterface ;
9
12
use Magento \CatalogSearch \Model \ResourceModel \Fulltext \Collection \SearchResultApplierInterface ;
10
13
use Magento \Framework \Api \Search \SearchResultInterface ;
14
+ use Magento \Framework \App \Config \ScopeConfigInterface ;
15
+ use Magento \Framework \App \ObjectManager ;
11
16
use Magento \Framework \Data \Collection ;
17
+ use Magento \Framework \EntityManager \MetadataPool ;
12
18
13
19
/**
14
20
* Resolve specific attributes for search criteria.
21
+ *
22
+ * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
15
23
*/
16
24
class SearchResultApplier implements SearchResultApplierInterface
17
25
{
@@ -35,22 +43,56 @@ class SearchResultApplier implements SearchResultApplierInterface
35
43
*/
36
44
private $ currentPage ;
37
45
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
+
38
66
/**
39
67
* @param Collection $collection
40
68
* @param SearchResultInterface $searchResult
41
69
* @param int $size
42
70
* @param int $currentPage
71
+ * @param ScopeConfigInterface|null $scopeConfig
72
+ * @param MetadataPool|null $metadataPool
73
+ * @param StockStatusFilterInterface|null $stockStatusFilter
74
+ * @param StockStatusApplierInterface|null $stockStatusApplier
43
75
*/
44
76
public function __construct (
45
77
Collection $ collection ,
46
78
SearchResultInterface $ searchResult ,
47
79
int $ size ,
48
- int $ currentPage
80
+ int $ currentPage ,
81
+ ?ScopeConfigInterface $ scopeConfig = null ,
82
+ ?MetadataPool $ metadataPool = null ,
83
+ ?StockStatusFilterInterface $ stockStatusFilter = null ,
84
+ ?StockStatusApplierInterface $ stockStatusApplier = null
49
85
) {
50
86
$ this ->collection = $ collection ;
51
87
$ this ->searchResult = $ searchResult ;
52
88
$ this ->size = $ size ;
53
89
$ 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);
54
96
}
55
97
56
98
/**
@@ -60,16 +102,18 @@ public function apply()
60
102
{
61
103
if (empty ($ this ->searchResult ->getItems ())) {
62
104
$ this ->collection ->getSelect ()->where ('NULL ' );
63
-
64
105
return ;
65
106
}
66
107
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
+ }
71
115
}
72
- $ orderList = join (', ' , $ ids );
116
+ $ orderList = implode (', ' , $ ids );
73
117
$ this ->collection ->getSelect ()
74
118
->where ('e.entity_id IN (?) ' , $ ids )
75
119
->reset (\Magento \Framework \DB \Select::ORDER )
@@ -116,4 +160,127 @@ private function getOffset(int $pageNumber, int $pageSize): int
116
160
{
117
161
return ($ pageNumber - 1 ) * $ pageSize ;
118
162
}
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
+ }
119
286
}
0 commit comments