Skip to content

Issue 36096: Layered Navigation does not filter products properly #36102

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 4 commits into
base: 2.4-develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 66 additions & 7 deletions app/code/Magento/AdvancedSearch/Model/ResourceModel/Index.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,22 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
declare(strict_types=1);

namespace Magento\AdvancedSearch\Model\ResourceModel;

use Magento\Framework\Model\ResourceModel\Db\AbstractDb;
use Magento\Framework\Search\Request\IndexScopeResolverInterface;
use Magento\Store\Model\StoreManagerInterface;
use Magento\Framework\Model\ResourceModel\Db\Context;
use Magento\Framework\EntityManager\MetadataPool;
use Magento\Catalog\Api\Data\CategoryInterface;
use Magento\Catalog\Model\Indexer\Category\Product\AbstractAction;
use Magento\Catalog\Model\Indexer\Product\Price\DimensionCollectionFactory;
use Magento\Framework\App\ObjectManager;
use Magento\Framework\EntityManager\MetadataPool;
use Magento\Framework\Model\ResourceModel\Db\AbstractDb;
use Magento\Framework\Model\ResourceModel\Db\Context;
use Magento\Framework\Search\Request\Dimension;
use Magento\Catalog\Model\Indexer\Category\Product\AbstractAction;
use Magento\Framework\Search\Request\IndexScopeResolverInterface;
use Magento\Framework\Search\Request\IndexScopeResolverInterface as TableResolver;
use Magento\Catalog\Model\Indexer\Product\Price\DimensionCollectionFactory;
use Magento\Store\Model\Indexer\WebsiteDimensionProvider;
use Magento\Store\Model\StoreManagerInterface;

/**
* @api
Expand Down Expand Up @@ -150,6 +152,63 @@ public function getPriceIndexData($productIds, $storeId)
return $priceProductsIndexData[$websiteId];
}

/**
* Return array of inventory data products
*
* @param null|array $productIds
* @return array
* @since 100.1.0
*/
private function getCatalogProductInventoryData($productIds = null)
{
$connection = $this->getConnection();
$catalogProductIndexInventorySelect = [];

foreach ($this->dimensionCollectionFactory->create() as $dimensions) {
if (!isset($dimensions[WebsiteDimensionProvider::DIMENSION_NAME]) ||
$this->websiteId === null ||
$dimensions[WebsiteDimensionProvider::DIMENSION_NAME]->getValue() === $this->websiteId) {
$select = $connection->select()->from(
$this->tableResolver->resolve('cataloginventory_stock_status', $dimensions),
['product_id', 'website_id', 'stock_status']
);
if ($productIds) {
$select->where('product_id IN (?)', $productIds);
}
$catalogProductIndexInventorySelect[] = $select;
}
}

$catalogProductIndexInventoryUnionSelect = $connection->select()->union($catalogProductIndexInventorySelect);

$result = [];
foreach ($connection->fetchAll($catalogProductIndexInventoryUnionSelect) as $row) {
$result[$row['website_id']][$row['product_id']] = (int) $row['stock_status'];
}

return $result;
}

/**
* Retrieve inventory data for product
*
* @param null|array $productIds
* @param int $websiteId
* @return array
*/
public function getInventoryIndexData($productIds, $websiteId = null)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

introduce the public function make the Semantic test failed. @sidolov Can you give us some advice about that ?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Backward capability don't allow introduce new public methods to class marked as @api and should be replaced by new class

{
$this->websiteId = $websiteId;
$inventoryProductsIndexData = $this->getCatalogProductInventoryData($productIds);
$this->websiteId = null;

if (!isset($inventoryProductsIndexData[(int) $websiteId])) {
return [];
}

return $inventoryProductsIndexData[(int) $websiteId];
}

/**
* Prepare system index data for products.
*
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
<?php
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
declare(strict_types=1);

namespace Magento\Elasticsearch\Model\Adapter\BatchDataMapper;

use Magento\AdvancedSearch\Model\Adapter\DataMapper\AdditionalFieldsProviderInterface;
use Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\AttributeProvider;
use Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\FieldProvider\FieldName\ResolverInterface;
use Magento\Elasticsearch\Model\ResourceModel\Index;
use Magento\Framework\App\Config\ScopeConfigInterface;
use Magento\Store\Model\StoreManagerInterface;

/**
* Provide data mapping for inventory fields
*/
class InventoryFieldsProvider implements AdditionalFieldsProviderInterface
{
public const IS_SALABLE = 'is_salable';

/**
* @var Index
*/
private $resourceIndex;

/**
* @var StoreManagerInterface
*/
private $storeManager;

/**
* @var AttributeProvider
*/
private $attributeAdapterProvider;

/**
* @var ResolverInterface
*/
private $fieldNameResolver;

/**
* @var ScopeConfigInterface
*/
private $scopeConfig;

/**
* @param Index $resourceIndex
* @param StoreManagerInterface $storeManager
* @param AttributeProvider $attributeAdapterProvider
* @param ResolverInterface $fieldNameResolver
* @param ScopeConfigInterface $scopeConfig
*/
public function __construct(
Index $resourceIndex,
StoreManagerInterface $storeManager,
AttributeProvider $attributeAdapterProvider,
ResolverInterface $fieldNameResolver,
ScopeConfigInterface $scopeConfig
) {
$this->resourceIndex = $resourceIndex;
$this->storeManager = $storeManager;
$this->attributeAdapterProvider = $attributeAdapterProvider;
$this->fieldNameResolver = $fieldNameResolver;
$this->scopeConfig = $scopeConfig;
}

/**
* @inheritdoc
*/
public function getFields(array $productIds, $storeId)
{
$fields = [];

if ($this->hasShowOutOfStockStatus()) {
$inventoryData = $this->resourceIndex->getInventoryIndexData($productIds);
foreach ($productIds as $productId) {
$fields[$productId] = $this->getProductInventoryData($productId, $inventoryData);
}
}
return $fields;
}

/**
* Prepare inventory index for product.
*
* @param int $productId
* @param array $inventoryData
* @return array
*/
private function getProductInventoryData($productId, array $inventoryData)
{
$result = [];
if (array_key_exists($productId, $inventoryData)) {
$inStockAttribute = $this->attributeAdapterProvider->getByAttributeCode(self::IS_SALABLE);
$fieldName = $this->fieldNameResolver->getFieldName($inStockAttribute);
$result[$fieldName] = $inventoryData[$productId];
}

return $result;
}

/**
* Returns if display out of stock status set or not in catalog inventory
*
* @return bool
*/
private function hasShowOutOfStockStatus(): bool
{
return (bool) $this->scopeConfig->getValue(
\Magento\CatalogInventory\Model\Configuration::XML_PATH_SHOW_OUT_OF_STOCK,
\Magento\Store\Model\ScopeInterface::SCOPE_STORE
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?php
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
declare(strict_types=1);

namespace Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\FieldProvider\FieldName\Resolver;

use Magento\Elasticsearch\Model\Adapter\BatchDataMapper\InventoryFieldsProvider;
use Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\AttributeAdapter;
use Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\FieldProvider\FieldName\ResolverInterface;

/**
* Resolver field name for is_salable attribute.
* @SuppressWarnings(PHPMD.CookieAndSessionMisuse)
*/
class IsSalable implements ResolverInterface
{
/**
* @inheritdoc
*/
public function getFieldName(AttributeAdapter $attribute, $context = []): ?string
{
if ($attribute->getAttributeCode() === InventoryFieldsProvider::IS_SALABLE) {
return $attribute->getAttributeCode();
}

return null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,13 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
declare(strict_types=1);

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

use Magento\Catalog\Api\Data\ProductInterface;
use Magento\CatalogInventory\Model\StockStatusApplierInterface;
use Magento\CatalogInventory\Model\ResourceModel\StockStatusFilterInterface;
use Magento\CatalogInventory\Model\StockStatusApplierInterface;
use Magento\CatalogSearch\Model\ResourceModel\Fulltext\Collection\SearchResultApplierInterface;
use Magento\Framework\Api\Search\SearchResultInterface;
use Magento\Framework\App\Config\ScopeConfigInterface;
Expand Down Expand Up @@ -105,14 +106,12 @@ public function apply()
return;
}

$ids = $this->getProductIdsBySaleability();

if (count($ids) == 0) {
$items = $this->sliceItems($this->searchResult->getItems(), $this->size, $this->currentPage);
foreach ($items as $item) {
$ids[] = (int)$item->getId();
}
$ids = [];
$items = $this->sliceItems($this->searchResult->getItems(), $this->size, $this->currentPage);
foreach ($items as $item) {
$ids[] = (int)$item->getId();
}

$orderList = implode(',', $ids);
$this->collection->getSelect()
->where('e.entity_id IN (?)', $ids)
Expand All @@ -139,7 +138,7 @@ private function sliceItems(array $items, int $size, int $currentPage): array
$currentPage = 1;
}
if ($currentPage > $maxAllowedPageNumber) {
$currentPage = $maxAllowedPageNumber;
$currentPage = (int) $maxAllowedPageNumber;
}

$offset = $this->getOffset($currentPage, $size);
Expand All @@ -164,6 +163,9 @@ private function getOffset(int $pageNumber, int $pageSize): int
* Fetch filtered product ids sorted by the saleability and other applied sort orders
*
* @return array
* @deprecated
* @see Not use anymore
* @SuppressWarnings(PHPMD.UnusedPrivateMethod)
*/
private function getProductIdsBySaleability(): array
{
Expand Down Expand Up @@ -204,6 +206,8 @@ private function getProductIdsBySaleability(): array
* @return array
* @throws \Magento\Framework\Exception\LocalizedException
* @throws \Exception
* @deprecated
* @see Not use anymore
*/
private function categoryProductByCustomSortOrder(int $categoryId): array
{
Expand Down Expand Up @@ -275,6 +279,8 @@ private function categoryProductByCustomSortOrder(int $categoryId): array
* Returns if display out of stock status set or not in catalog inventory
*
* @return bool
* @deprecated
* @see Not use anymore
*/
private function hasShowOutOfStockStatus(): bool
{
Expand Down
Loading