diff --git a/vendor/magento/module-catalog-graph-ql/DataProvider/Product/RequestDataBuilder.php b/vendor/magento/module-catalog-graph-ql/DataProvider/Product/RequestDataBuilder.php new file mode 100644 index 0000000000000..1e4a2ac32439a --- /dev/null +++ b/vendor/magento/module-catalog-graph-ql/DataProvider/Product/RequestDataBuilder.php @@ -0,0 +1,40 @@ +_resetState(); + } + + public function setData($data) + { + $this->data = $data; + } + + public function getData(string $key) + { + return $this->data[$key] ?? null; + + } + + /** + * @inheritDoc + */ + public function _resetState(): void + { + $this->data = []; + } +} diff --git a/vendor/magento/module-catalog-graph-ql/DataProvider/Product/SearchCriteriaBuilder.php b/vendor/magento/module-catalog-graph-ql/DataProvider/Product/SearchCriteriaBuilder.php index d67a50875b81d..757e64cb34288 100644 --- a/vendor/magento/module-catalog-graph-ql/DataProvider/Product/SearchCriteriaBuilder.php +++ b/vendor/magento/module-catalog-graph-ql/DataProvider/Product/SearchCriteriaBuilder.php @@ -8,84 +8,50 @@ namespace Magento\CatalogGraphQl\DataProvider\Product; use Magento\Catalog\Api\Data\EavAttributeInterface; -use Magento\Catalog\Model\Product; +use Magento\Catalog\Api\ProductAttributeRepositoryInterface; use Magento\Catalog\Model\Product\Visibility; -use Magento\Eav\Model\Config; +use Magento\CatalogSearch\Model\ResourceModel\Fulltext\Collection\SearchCriteriaResolverFactory; use Magento\Framework\Api\FilterBuilder; use Magento\Framework\Api\Search\FilterGroupBuilder; use Magento\Framework\Api\Search\SearchCriteriaInterface; use Magento\Framework\Api\SortOrder; use Magento\Framework\Api\SortOrderBuilder; use Magento\Framework\App\Config\ScopeConfigInterface; -use Magento\Framework\App\ObjectManager; -use Magento\Framework\GraphQl\Query\Resolver\Argument\SearchCriteria\Builder; +use Magento\Framework\Exception\LocalizedException; +use Magento\Framework\GraphQl\Query\Resolver\Argument\SearchCriteria\ArgumentApplierPool; +use Magento\Framework\Search\Request\Config as SearchConfig; /** * Build search criteria * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ - class SearchCriteriaBuilder { /** - * @var ScopeConfigInterface - */ - private $scopeConfig; - - /** - * @var FilterBuilder - */ - private $filterBuilder; - - /** - * @var FilterGroupBuilder - */ - private $filterGroupBuilder; - - /** - * @var Builder - */ - private $builder; - /** - * @var Visibility - */ - private $visibility; - - /** - * @var SortOrderBuilder - */ - private $sortOrderBuilder; - - /** - * @var Config - */ - private Config $eavConfig; - - /** - * @param Builder $builder * @param ScopeConfigInterface $scopeConfig * @param FilterBuilder $filterBuilder * @param FilterGroupBuilder $filterGroupBuilder * @param Visibility $visibility * @param SortOrderBuilder $sortOrderBuilder - * @param Config $eavConfig + * @param ProductAttributeRepositoryInterface $productAttributeRepository + * @param SearchConfig $searchConfig + * @param RequestDataBuilder $localData + * @param SearchCriteriaResolverFactory $criteriaResolverFactory + * @param ArgumentApplierPool $argumentApplierPool + * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ public function __construct( - Builder $builder, - ScopeConfigInterface $scopeConfig, - FilterBuilder $filterBuilder, - FilterGroupBuilder $filterGroupBuilder, - Visibility $visibility, - SortOrderBuilder $sortOrderBuilder = null, - Config $eavConfig = null + private readonly ScopeConfigInterface $scopeConfig, + private readonly FilterBuilder $filterBuilder, + private readonly FilterGroupBuilder $filterGroupBuilder, + private readonly Visibility $visibility, + private readonly SortOrderBuilder $sortOrderBuilder, + private readonly ProductAttributeRepositoryInterface $productAttributeRepository, + private readonly SearchConfig $searchConfig, + private readonly RequestDataBuilder $localData, + private readonly SearchCriteriaResolverFactory $criteriaResolverFactory, + private readonly ArgumentApplierPool $argumentApplierPool, ) { - $this->scopeConfig = $scopeConfig; - $this->filterBuilder = $filterBuilder; - $this->filterGroupBuilder = $filterGroupBuilder; - $this->builder = $builder; - $this->visibility = $visibility; - $this->sortOrderBuilder = $sortOrderBuilder ?? ObjectManager::getInstance()->get(SortOrderBuilder::class); - $this->eavConfig = $eavConfig ?? ObjectManager::getInstance()->get(Config::class); } /** @@ -94,42 +60,103 @@ public function __construct( * @param array $args * @param bool $includeAggregation * @return SearchCriteriaInterface + * @throws LocalizedException */ public function build(array $args, bool $includeAggregation): SearchCriteriaInterface { - $searchCriteria = $this->builder->build('products', $args); - $isSearch = !empty($args['search']); - $this->updateRangeFilters($searchCriteria); - if ($includeAggregation) { - $attributeData = $this->eavConfig->getAttribute(Product::ENTITY, 'price'); - $priceOptions = $attributeData->getData(); + $isSearch = isset($args['search']); + $requestName = $includeAggregation ? 'graphql_product_search_with_aggregation' : 'graphql_product_search'; - if ($priceOptions['is_filterable'] != 0) { - $this->preparePriceAggregation($searchCriteria); + if (isset($args['filter'])) { + $partialMatchFilters = $this->getPartialMatchFilters($args); + if (count($partialMatchFilters)) { + $this->updateMatchTypeRequestConfig($requestName, $partialMatchFilters); } - $requestName = 'graphql_product_search_with_aggregation'; - } else { - $requestName = 'graphql_product_search'; + $args = $this->removeMatchTypeFromArguments($args); } - $searchCriteria->setRequestName($requestName); + $searchCriteria = $this->criteriaResolverFactory->create( + [ + 'searchRequestName' => $requestName, + 'currentPage' => $args['currentPage'], + 'size' => $args['pageSize'], + 'orders' => null, + ] + )->resolve(); + foreach ($args as $argumentName => $argument) { + if ($this->argumentApplierPool->hasApplier($argumentName)) { + $argumentApplier = $this->argumentApplierPool->getApplier($argumentName); + $argumentApplier->applyArgument($searchCriteria, 'products', $argumentName, $argument); + } + } + $this->updateRangeFilters($searchCriteria); + $this->preparePriceAggregation($searchCriteria, $includeAggregation); if ($isSearch) { $this->addFilter($searchCriteria, 'search_term', $args['search']); } - if (!$searchCriteria->getSortOrders()) { $this->addDefaultSortOrder($searchCriteria, $args, $isSearch); } - $this->addEntityIdSort($searchCriteria); - $this->addVisibilityFilter($searchCriteria, $isSearch, !empty($args['filter'])); - - $searchCriteria->setCurrentPage($args['currentPage']); - $searchCriteria->setPageSize($args['pageSize']); + $this->addVisibilityFilter($searchCriteria, $isSearch, !empty($args['filter']['category_id'])); return $searchCriteria; } + /** + * Update dynamically the search match type based on requested params + * + * @param string $requestName + * @param array $partialMatchFilters + * @return void + */ + private function updateMatchTypeRequestConfig(string $requestName, array $partialMatchFilters): void + { + $data = $this->searchConfig->get($requestName); + foreach ($data['queries'] as $queryName => $query) { + foreach ($query['match'] ?? [] as $index => $matchItem) { + if (in_array($matchItem['field'] ?? null, $partialMatchFilters, true)) { + $data['queries'][$queryName]['match'][$index]['matchCondition'] = 'match_phrase_prefix'; + } + } + } + $this->localData->setData([$requestName => $data]); + } + + /** + * Check if and what type of match_type value was requested + * + * @param array $args + * @return array + */ + private function getPartialMatchFilters(array $args): array + { + $partialMatchFilters = []; + foreach ($args['filter'] as $fieldName => $conditions) { + if (isset($conditions['match_type']) && $conditions['match_type'] === 'PARTIAL') { + $partialMatchFilters[] = $fieldName; + } + } + return $partialMatchFilters; + } + + /** + * Remove the match_type to avoid search criteria containing it + * + * @param array $args + * @return array + */ + private function removeMatchTypeFromArguments(array $args): array + { + foreach ($args['filter'] as &$conditions) { + if (isset($conditions['match_type'])) { + unset($conditions['match_type']); + } + } + + return $args; + } + /** * Add filter by visibility * @@ -168,7 +195,7 @@ private function addEntityIdSort(SearchCriteriaInterface $searchCriteria): void } $sortOrderArray[] = $this->sortOrderBuilder - ->setField('_id') + ->setField('entity_id') ->setDirection($sortDir) ->create(); $searchCriteria->setSortOrders($sortOrderArray); @@ -178,10 +205,21 @@ private function addEntityIdSort(SearchCriteriaInterface $searchCriteria): void * Prepare price aggregation algorithm * * @param SearchCriteriaInterface $searchCriteria + * @param bool $includeAggregation * @return void */ - private function preparePriceAggregation(SearchCriteriaInterface $searchCriteria): void + private function preparePriceAggregation(SearchCriteriaInterface $searchCriteria, bool $includeAggregation): void { + if (!$includeAggregation) { + return; + } + + $attributeData = $this->productAttributeRepository->get('price'); + $priceOptions = $attributeData->getData(); + if ((int) $priceOptions['is_filterable'] === 0) { + return; + } + $priceRangeCalculation = $this->scopeConfig->getValue( \Magento\Catalog\Model\Layer\Filter\Dynamic\AlgorithmFactory::XML_PATH_RANGE_CALCULATION, \Magento\Store\Model\ScopeInterface::SCOPE_STORE @@ -233,7 +271,7 @@ private function addDefaultSortOrder(SearchCriteriaInterface $searchCriteria, ar ->setDirection(SortOrder::SORT_DESC) ->create(); } else { - $categoryIdFilter = isset($args['filter']['category_id']) ? $args['filter']['category_id'] : false; + $categoryIdFilter = $args['filter']['category_id'] ?? false; if ($categoryIdFilter) { if (!is_array($categoryIdFilter[array_key_first($categoryIdFilter)]) || count($categoryIdFilter[array_key_first($categoryIdFilter)]) <= 1 diff --git a/vendor/magento/module-catalog-graph-ql/Model/Resolver/Products/DataProvider/ProductSearch.php b/vendor/magento/module-catalog-graph-ql/Model/Resolver/Products/DataProvider/ProductSearch.php index f1d30ab942aab..5c170f428bc94 100644 --- a/vendor/magento/module-catalog-graph-ql/Model/Resolver/Products/DataProvider/ProductSearch.php +++ b/vendor/magento/module-catalog-graph-ql/Model/Resolver/Products/DataProvider/ProductSearch.php @@ -8,18 +8,15 @@ namespace Magento\CatalogGraphQl\Model\Resolver\Products\DataProvider; use Magento\Catalog\Api\Data\ProductSearchResultsInterfaceFactory; -use Magento\Catalog\Model\Product\Visibility; use Magento\Catalog\Model\ResourceModel\Product\Collection; use Magento\Catalog\Model\ResourceModel\Product\CollectionFactory; use Magento\CatalogGraphQl\Model\Resolver\Products\DataProvider\Product\CollectionProcessorInterface; use Magento\CatalogGraphQl\Model\Resolver\Products\DataProvider\Product\CollectionPostProcessorInterface; -use Magento\CatalogGraphQl\Model\Resolver\Products\DataProvider\ProductSearch\ProductCollectionSearchCriteriaBuilder; use Magento\CatalogSearch\Model\ResourceModel\Fulltext\Collection\SearchResultApplierFactory; -use Magento\CatalogSearch\Model\ResourceModel\Fulltext\Collection\SearchResultApplierInterface; +use Magento\Framework\Api\Search\SearchCriteriaInterfaceFactory as SearchCriteriaFactory; use Magento\Framework\Api\Search\SearchResultInterface; use Magento\Framework\Api\SearchCriteriaInterface; use Magento\Framework\Api\SearchResultsInterface; -use Magento\Framework\App\ObjectManager; use Magento\GraphQl\Model\Query\ContextInterface; /** @@ -53,14 +50,9 @@ class ProductSearch private $searchResultApplierFactory; /** - * @var ProductCollectionSearchCriteriaBuilder + * @var SearchCriteriaFactory */ - private $searchCriteriaBuilder; - - /** - * @var Visibility - */ - private $catalogProductVisibility; + private $searchCriteriaFactory; /** * @param CollectionFactory $collectionFactory @@ -68,8 +60,7 @@ class ProductSearch * @param CollectionProcessorInterface $collectionPreProcessor * @param CollectionPostProcessorInterface $collectionPostProcessor * @param SearchResultApplierFactory $searchResultsApplierFactory - * @param ProductCollectionSearchCriteriaBuilder $searchCriteriaBuilder - * @param Visibility $catalogProductVisibility + * @param SearchCriteriaFactory $searchCriteriaFactory */ public function __construct( CollectionFactory $collectionFactory, @@ -77,16 +68,14 @@ public function __construct( CollectionProcessorInterface $collectionPreProcessor, CollectionPostProcessorInterface $collectionPostProcessor, SearchResultApplierFactory $searchResultsApplierFactory, - ProductCollectionSearchCriteriaBuilder $searchCriteriaBuilder, - Visibility $catalogProductVisibility + SearchCriteriaFactory $searchCriteriaFactory ) { $this->collectionFactory = $collectionFactory; $this->searchResultsFactory = $searchResultsFactory; $this->collectionPreProcessor = $collectionPreProcessor; $this->collectionPostProcessor = $collectionPostProcessor; $this->searchResultApplierFactory = $searchResultsApplierFactory; - $this->searchCriteriaBuilder = $searchCriteriaBuilder; - $this->catalogProductVisibility = $catalogProductVisibility; + $this->searchCriteriaFactory = $searchCriteriaFactory; } /** @@ -107,75 +96,27 @@ public function getList( /** @var Collection $collection */ $collection = $this->collectionFactory->create(); - //Create a copy of search criteria without filters to preserve the results from search - $searchCriteriaForCollection = $this->searchCriteriaBuilder->build($searchCriteria); //Apply CatalogSearch results from search and join table - $this->getSearchResultsApplier( - $searchResult, - $collection, - $this->getSortOrderArray($searchCriteriaForCollection) - )->apply(); - - $collection->setFlag('search_resut_applied', true); - - $collection->setVisibility($this->catalogProductVisibility->getVisibleInSiteIds()); - $this->collectionPreProcessor->process($collection, $searchCriteriaForCollection, $attributes, $context); - $collection->load(); - $this->collectionPostProcessor->process($collection, $attributes, $context); - - $searchResults = $this->searchResultsFactory->create(); - $searchResults->setSearchCriteria($searchCriteriaForCollection); - $searchResults->setItems($collection->getItems()); - $searchResults->setTotalCount($collection->getSize()); - return $searchResults; - } - - /** - * Create searchResultApplier - * - * @param SearchResultInterface $searchResult - * @param Collection $collection - * @param array $orders - * @return SearchResultApplierInterface - */ - private function getSearchResultsApplier( - SearchResultInterface $searchResult, - Collection $collection, - array $orders - ): SearchResultApplierInterface { - return $this->searchResultApplierFactory->create( + $searchResultsApplier = $this->searchResultApplierFactory->create( [ 'collection' => $collection, 'searchResult' => $searchResult, - 'orders' => $orders ] ); - } + $searchResultsApplier->apply(); - /** - * Format sort orders into associative array - * - * E.g. ['field1' => 'DESC', 'field2' => 'ASC", ...] - * - * @param SearchCriteriaInterface $searchCriteria - * @return array - */ - private function getSortOrderArray(SearchCriteriaInterface $searchCriteria) - { - $ordersArray = []; - $sortOrders = $searchCriteria->getSortOrders(); - if (is_array($sortOrders)) { - foreach ($sortOrders as $sortOrder) { - // I am replacing _id with entity_id because in ElasticSearch _id is required for sorting by ID. - // Where as entity_id is required when using ID as the sort in $collection->load();. - // @see \Magento\CatalogGraphQl\Model\Resolver\Products\Query\Search::getResult - if ($sortOrder->getField() === '_id') { - $sortOrder->setField('entity_id'); - } - $ordersArray[$sortOrder->getField()] = $sortOrder->getDirection(); - } - } + //Empty search criteria for backward compatibility. + //Search criteria must be already applied to the search result. + $emptySearchCriteria = $this->searchCriteriaFactory->create(); + $this->collectionPreProcessor->process($collection, $emptySearchCriteria, $attributes, $context); + $collection->load(); + $this->collectionPostProcessor->process($collection, $attributes, $context); - return $ordersArray; + $searchResults = $this->searchResultsFactory->create(); + $searchResults->setSearchCriteria($searchCriteria); + $searchResults->setItems($collection->getItems()); + $searchResults->setTotalCount($searchResult->getTotalCount()); + + return $searchResults; } } diff --git a/vendor/magento/module-catalog-graph-ql/Model/Resolver/Products/DataProvider/ProductSearch/ProductCollectionSearchCriteriaBuilder.php b/vendor/magento/module-catalog-graph-ql/Model/Resolver/Products/DataProvider/ProductSearch/ProductCollectionSearchCriteriaBuilder.php index 03e8358b1ee7a..e69de29bb2d1d 100644 --- a/vendor/magento/module-catalog-graph-ql/Model/Resolver/Products/DataProvider/ProductSearch/ProductCollectionSearchCriteriaBuilder.php +++ b/vendor/magento/module-catalog-graph-ql/Model/Resolver/Products/DataProvider/ProductSearch/ProductCollectionSearchCriteriaBuilder.php @@ -1,77 +0,0 @@ -searchCriteriaFactory = $searchCriteriaFactory; - $this->filterBuilder = $filterBuilder; - $this->filterGroupBuilder = $filterGroupBuilder; - } - - /** - * Build searchCriteria from search for product collection - * - * @param SearchCriteriaInterface $searchCriteria - * @return SearchCriteriaInterface - */ - public function build(SearchCriteriaInterface $searchCriteria): SearchCriteriaInterface - { - //Create a copy of search criteria without filters to preserve the results from search - $searchCriteriaForCollection = $this->searchCriteriaFactory->create() - ->setSortOrders($searchCriteria->getSortOrders()) - ->setPageSize($searchCriteria->getPageSize()) - ->setCurrentPage($searchCriteria->getCurrentPage()); - - //Add category id to enable sorting by position - foreach ($searchCriteria->getFilterGroups() as $filterGroup) { - foreach ($filterGroup->getFilters() as $filter) { - if ($filter->getField() == CategoryProductLink::KEY_CATEGORY_ID) { - $categoryFilter = $this->filterBuilder - ->setField(CategoryProductLink::KEY_CATEGORY_ID) - ->setValue($filter->getValue()) - ->setConditionType($filter->getConditionType()) - ->create(); - - $this->filterGroupBuilder->addFilter($categoryFilter); - $categoryGroup = $this->filterGroupBuilder->create(); - $searchCriteriaForCollection->setFilterGroups([$categoryGroup]); - } - } - } - return $searchCriteriaForCollection; - } -} diff --git a/vendor/magento/module-catalog-graph-ql/Model/Resolver/Products/Query/Search.php b/vendor/magento/module-catalog-graph-ql/Model/Resolver/Products/Query/Search.php index c4d189cd7cb0c..f77f19bb684b0 100644 --- a/vendor/magento/module-catalog-graph-ql/Model/Resolver/Products/Query/Search.php +++ b/vendor/magento/module-catalog-graph-ql/Model/Resolver/Products/Query/Search.php @@ -13,13 +13,11 @@ use Magento\CatalogGraphQl\Model\Resolver\Products\SearchResult; use Magento\CatalogGraphQl\Model\Resolver\Products\SearchResultFactory; use Magento\Framework\Api\Search\SearchCriteriaInterface; -use Magento\Framework\App\ObjectManager; use Magento\Framework\GraphQl\Exception\GraphQlInputException; use Magento\Framework\GraphQl\Query\Resolver\ArgumentsProcessorInterface; use Magento\Framework\GraphQl\Schema\Type\ResolveInfo; use Magento\GraphQl\Model\Query\ContextInterface; use Magento\Search\Api\SearchInterface; -use Magento\Search\Model\Search\PageSizeProvider; /** * Full text search for catalog using given search criteria. @@ -38,11 +36,6 @@ class Search implements ProductQueryInterface */ private $searchResultFactory; - /** - * @var PageSizeProvider - */ - private $pageSizeProvider; - /** * @var FieldSelection */ @@ -76,36 +69,31 @@ class Search implements ProductQueryInterface /** * @param SearchInterface $search * @param SearchResultFactory $searchResultFactory - * @param PageSizeProvider $pageSize * @param FieldSelection $fieldSelection * @param ProductSearch $productsProvider * @param SearchCriteriaBuilder $searchCriteriaBuilder - * @param ArgumentsProcessorInterface|null $argsSelection - * @param Suggestions|null $suggestions - * @param QueryPopularity|null $queryPopularity + * @param ArgumentsProcessorInterface $argsSelection + * @param Suggestions $suggestions + * @param QueryPopularity $queryPopularity */ public function __construct( SearchInterface $search, SearchResultFactory $searchResultFactory, - PageSizeProvider $pageSize, FieldSelection $fieldSelection, ProductSearch $productsProvider, SearchCriteriaBuilder $searchCriteriaBuilder, - ArgumentsProcessorInterface $argsSelection = null, - Suggestions $suggestions = null, - QueryPopularity $queryPopularity = null + ArgumentsProcessorInterface $argsSelection, + Suggestions $suggestions, + QueryPopularity $queryPopularity ) { $this->search = $search; $this->searchResultFactory = $searchResultFactory; - $this->pageSizeProvider = $pageSize; $this->fieldSelection = $fieldSelection; $this->productsProvider = $productsProvider; $this->searchCriteriaBuilder = $searchCriteriaBuilder; - $this->argsSelection = $argsSelection ?: ObjectManager::getInstance() - ->get(ArgumentsProcessorInterface::class); - $this->suggestions = $suggestions ?: ObjectManager::getInstance() - ->get(Suggestions::class); - $this->queryPopularity = $queryPopularity ?: ObjectManager::getInstance()->get(QueryPopularity::class); + $this->argsSelection = $argsSelection; + $this->suggestions = $suggestions; + $this->queryPopularity = $queryPopularity; } /** @@ -123,18 +111,7 @@ public function getResult( ContextInterface $context ): SearchResult { $searchCriteria = $this->buildSearchCriteria($args, $info); - - $realPageSize = $searchCriteria->getPageSize(); - $realCurrentPage = $searchCriteria->getCurrentPage(); - //Because of limitations of sort and pagination on search API we will query all IDS - $pageSize = $this->pageSizeProvider->getMaxPageSize(); - $searchCriteria->setPageSize($pageSize); - $searchCriteria->setCurrentPage(0); $itemsResults = $this->search->search($searchCriteria); - - //Address limitations of sort and pagination on search API apply original pagination from GQL query - $searchCriteria->setPageSize($realPageSize); - $searchCriteria->setCurrentPage($realCurrentPage); $searchResults = $this->productsProvider->getList( $searchCriteria, $itemsResults, @@ -142,7 +119,9 @@ public function getResult( $context ); - $totalPages = $realPageSize ? ((int)ceil($searchResults->getTotalCount() / $realPageSize)) : 0; + $totalPages = $searchCriteria->getPageSize() + ? ((int)ceil($searchResults->getTotalCount() / $searchCriteria->getPageSize())) + : 0; // add query statistics data if (!empty($args['search'])) { @@ -167,8 +146,8 @@ public function getResult( 'totalCount' => $totalCount, 'productsSearchResult' => $productArray, 'searchAggregation' => $itemsResults->getAggregations(), - 'pageSize' => $realPageSize, - 'currentPage' => $realCurrentPage, + 'pageSize' => $args['pageSize'], + 'currentPage' => $args['currentPage'], 'totalPages' => $totalPages, 'suggestions' => $suggestions, ] diff --git a/vendor/magento/module-catalog-graph-ql/Model/Resolver/Products/SearchCriteria/CollectionProcessor/FilterProcessor/CategoryFilter.php b/vendor/magento/module-catalog-graph-ql/Model/Resolver/Products/SearchCriteria/CollectionProcessor/FilterProcessor/CategoryFilter.php index d15c072b9fe4b..e8825b95a9c7c 100644 --- a/vendor/magento/module-catalog-graph-ql/Model/Resolver/Products/SearchCriteria/CollectionProcessor/FilterProcessor/CategoryFilter.php +++ b/vendor/magento/module-catalog-graph-ql/Model/Resolver/Products/SearchCriteria/CollectionProcessor/FilterProcessor/CategoryFilter.php @@ -105,14 +105,11 @@ public function apply(Filter $filter, AbstractDb $collection) } elseif ($conditionType === self::CONDITION_TYPE_IN) { $this->joinMinimalPosition->execute($collection, $ids); } - /** Prevent filtering duplication as the filter should be already applied to the search result */ - if (!$collection->getFlag('search_resut_applied')) { - $collection->addCategoriesFilter( - [ - $conditionType => array_map('intval', $this->getCategoryIds($ids)) - ] - ); - } + $collection->addCategoriesFilter( + [ + $conditionType => array_map('intval', $this->getCategoryIds($ids)) + ] + ); } return true; diff --git a/vendor/magento/module-catalog-search/Model/ResourceModel/Fulltext/Collection.php b/vendor/magento/module-catalog-search/Model/ResourceModel/Fulltext/Collection.php index 9b66606d37a9e..22ed526d81611 100644 --- a/vendor/magento/module-catalog-search/Model/ResourceModel/Fulltext/Collection.php +++ b/vendor/magento/module-catalog-search/Model/ResourceModel/Fulltext/Collection.php @@ -567,7 +567,7 @@ protected function _beforeLoad() * for the same requests and products with the same relevance * NOTE: this does not replace existing orders but ADDs one more */ - $this->setOrder('entity_id'); + $this->setOrder('entity_id', Select::SQL_ASC); return parent::_beforeLoad(); } diff --git a/vendor/magento/module-catalog-search/Model/ResourceModel/Fulltext/Collection/SearchResultApplier.php b/vendor/magento/module-catalog-search/Model/ResourceModel/Fulltext/Collection/SearchResultApplier.php index 36e0a85fa4307..49676b7c3b208 100644 --- a/vendor/magento/module-catalog-search/Model/ResourceModel/Fulltext/Collection/SearchResultApplier.php +++ b/vendor/magento/module-catalog-search/Model/ResourceModel/Fulltext/Collection/SearchResultApplier.php @@ -27,24 +27,16 @@ class SearchResultApplier implements SearchResultApplierInterface */ private $searchResult; - /** - * @var array - */ - private $orders; - /** * @param Collection $collection * @param SearchResultInterface $searchResult - * @param array $orders */ public function __construct( Collection $collection, - SearchResultInterface $searchResult, - array $orders + SearchResultInterface $searchResult ) { $this->collection = $collection; $this->searchResult = $searchResult; - $this->orders = $orders; } /** @@ -56,18 +48,16 @@ public function apply() $this->collection->getSelect()->where('NULL'); return; } + $ids = []; foreach ($this->searchResult->getItems() as $item) { $ids[] = (int)$item->getId(); } $orderList = implode(',', $ids); - $this->collection->getSelect()->where('e.entity_id IN (?)', $ids); - - if (isset($this->orders['relevance'])) { - $this->collection->getSelect() - ->reset(\Magento\Framework\DB\Select::ORDER) - ->order(new \Magento\Framework\DB\Sql\Expression("FIELD(e.entity_id, $orderList)")); - } + $this->collection->getSelect() + ->where('e.entity_id IN (?)', $ids) + ->reset(\Magento\Framework\DB\Select::ORDER) + ->order(new \Magento\Framework\DB\Sql\Expression("FIELD(e.entity_id, $orderList)")); } } diff --git a/vendor/magento/module-elasticsearch/Model/Adapter/BatchDataMapper/ProductDataMapper.php b/vendor/magento/module-elasticsearch/Model/Adapter/BatchDataMapper/ProductDataMapper.php index a9fb67f209aa7..043f6ca3fd798 100644 --- a/vendor/magento/module-elasticsearch/Model/Adapter/BatchDataMapper/ProductDataMapper.php +++ b/vendor/magento/module-elasticsearch/Model/Adapter/BatchDataMapper/ProductDataMapper.php @@ -311,7 +311,7 @@ function (string $valueId) { && in_array($attribute->getAttributeCode(), $this->sortableAttributesValuesToImplode) && count($attributeValues) > 1 ) { - $attributeValues = [$productId => implode(' ', $attributeValues)]; + $attributeValues = [$productId => implode("\n", $attributeValues)]; } if (in_array($attribute->getAttributeCode(), $this->sortableCaseSensitiveAttributes)) { diff --git a/vendor/magento/module-elasticsearch/Model/ResourceModel/Fulltext/Collection/SearchCriteriaResolver.php b/vendor/magento/module-elasticsearch/Model/ResourceModel/Fulltext/Collection/SearchCriteriaResolver.php index 64ce76c729334..7863cd52fcd80 100644 --- a/vendor/magento/module-elasticsearch/Model/ResourceModel/Fulltext/Collection/SearchCriteriaResolver.php +++ b/vendor/magento/module-elasticsearch/Model/ResourceModel/Fulltext/Collection/SearchCriteriaResolver.php @@ -3,11 +3,9 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - namespace Magento\Elasticsearch\Model\ResourceModel\Fulltext\Collection; use Magento\CatalogSearch\Model\ResourceModel\Fulltext\Collection\SearchCriteriaResolverInterface; -use Magento\Framework\Data\Collection; use Magento\Framework\Api\Search\SearchCriteriaBuilder; use Magento\Framework\Api\Search\SearchCriteria; @@ -21,11 +19,6 @@ class SearchCriteriaResolver implements SearchCriteriaResolverInterface */ private $builder; - /** - * @var Collection - */ - private $collection; - /** * @var string */ @@ -37,7 +30,7 @@ class SearchCriteriaResolver implements SearchCriteriaResolverInterface private $size; /** - * @var array + * @var array|null */ private $orders; @@ -47,24 +40,20 @@ class SearchCriteriaResolver implements SearchCriteriaResolverInterface private $currentPage; /** - * SearchCriteriaResolver constructor. * @param SearchCriteriaBuilder $builder - * @param Collection $collection * @param string $searchRequestName * @param int $currentPage * @param int $size - * @param array $orders + * @param array|null $orders */ public function __construct( SearchCriteriaBuilder $builder, - Collection $collection, string $searchRequestName, int $currentPage, int $size, - ?array $orders + ?array $orders = null ) { $this->builder = $builder; - $this->collection = $collection; $this->searchRequestName = $searchRequestName; $this->currentPage = $currentPage; $this->size = $size; diff --git a/vendor/magento/module-elasticsearch/SearchAdapter/Query/Builder/Sort.php b/vendor/magento/module-elasticsearch/SearchAdapter/Query/Builder/Sort.php index 7d41d54fb22a5..9721c66522086 100644 --- a/vendor/magento/module-elasticsearch/SearchAdapter/Query/Builder/Sort.php +++ b/vendor/magento/module-elasticsearch/SearchAdapter/Query/Builder/Sort.php @@ -7,9 +7,7 @@ namespace Magento\Elasticsearch\SearchAdapter\Query\Builder; use Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\AttributeProvider; -use Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\FieldProvider\FieldName\ResolverInterface - as FieldNameResolver; -use Magento\Elasticsearch\Model\Adapter\FieldMapperInterface; +use Magento\Elasticsearch\SearchAdapter\Query\Builder\Sort\ExpressionBuilderInterface as SortExpressionBuilder; use Magento\Framework\Search\RequestInterface; /** @@ -17,56 +15,26 @@ */ class Sort { - /** - * List of fields that need to skipp by default. - */ - private const DEFAULT_SKIPPED_FIELDS = [ - 'entity_id', - ]; - - /** - * Default mapping for special fields. - */ - private const DEFAULT_MAP = [ - 'relevance' => '_score', - ]; - /** * @var AttributeProvider */ private $attributeAdapterProvider; /** - * @var FieldNameResolver - */ - private $fieldNameResolver; - - /** - * @var array + * @var SortExpressionBuilder */ - private $skippedFields; - - /** - * @var array - */ - private $map; + private $sortExpressionBuilder; /** * @param AttributeProvider $attributeAdapterProvider - * @param FieldNameResolver $fieldNameResolver - * @param array $skippedFields - * @param array $map + * @param SortExpressionBuilder $sortExpressionBuilder */ public function __construct( AttributeProvider $attributeAdapterProvider, - FieldNameResolver $fieldNameResolver, - array $skippedFields = [], - array $map = [] + SortExpressionBuilder $sortExpressionBuilder ) { $this->attributeAdapterProvider = $attributeAdapterProvider; - $this->fieldNameResolver = $fieldNameResolver; - $this->skippedFields = array_merge(self::DEFAULT_SKIPPED_FIELDS, $skippedFields); - $this->map = array_merge(self::DEFAULT_MAP, $map); + $this->sortExpressionBuilder = $sortExpressionBuilder; } /** @@ -74,9 +42,6 @@ public function __construct( * * @param RequestInterface $request * @return array - * - * @SuppressWarnings(PHPMD.CyclomaticComplexity) - * @SuppressWarnings(PHPMD.NPathComplexity) */ public function getSort(RequestInterface $request) { @@ -88,38 +53,11 @@ public function getSort(RequestInterface $request) if (!method_exists($request, 'getSort')) { return $sorts; } + foreach ($request->getSort() as $item) { - if (in_array($item['field'], $this->skippedFields)) { - continue; - } $attribute = $this->attributeAdapterProvider->getByAttributeCode((string)$item['field']); - $fieldName = $this->fieldNameResolver->getFieldName($attribute); - if (isset($this->map[$fieldName])) { - $fieldName = $this->map[$fieldName]; - } - if ($attribute->isSortable() && - !$attribute->isComplexType() && - !($attribute->isFloatType() || $attribute->isIntegerType()) - ) { - $suffix = $this->fieldNameResolver->getFieldName( - $attribute, - ['type' => FieldMapperInterface::TYPE_SORT] - ); - $fieldName .= '.' . $suffix; - } - if ($attribute->isComplexType() && $attribute->isSortable()) { - $fieldName .= '_value'; - $suffix = $this->fieldNameResolver->getFieldName( - $attribute, - ['type' => FieldMapperInterface::TYPE_SORT] - ); - $fieldName .= '.' . $suffix; - } - $sorts[] = [ - $fieldName => [ - 'order' => strtolower($item['direction'] ?? '') - ] - ]; + $direction = strtolower($item['direction'] ?? ''); + $sorts[] = $this->sortExpressionBuilder->build($attribute, $direction, $request); } return $sorts; diff --git a/vendor/magento/module-elasticsearch/SearchAdapter/Query/Builder/Sort/DefaultExpression.php b/vendor/magento/module-elasticsearch/SearchAdapter/Query/Builder/Sort/DefaultExpression.php new file mode 100644 index 0000000000000..809a8dfb35902 --- /dev/null +++ b/vendor/magento/module-elasticsearch/SearchAdapter/Query/Builder/Sort/DefaultExpression.php @@ -0,0 +1,60 @@ +fieldNameResolver = $fieldNameResolver; + } + + /** + * @inheritdoc + */ + public function build(AttributeAdapter $attribute, string $direction, RequestInterface $request): array + { + $fieldName = $this->fieldNameResolver->getFieldName($attribute); + if ($attribute->isSortable() && + !$attribute->isComplexType() && + !($attribute->isFloatType() || $attribute->isIntegerType()) + ) { + $suffix = $this->fieldNameResolver->getFieldName( + $attribute, + ['type' => FieldMapperInterface::TYPE_SORT] + ); + $fieldName .= '.' . $suffix; + } + if ($attribute->isComplexType() && $attribute->isSortable()) { + $fieldName .= '_value'; + $suffix = $this->fieldNameResolver->getFieldName( + $attribute, + ['type' => FieldMapperInterface::TYPE_SORT] + ); + $fieldName .= '.' . $suffix; + } + + return [ + $fieldName => ['order' => $direction], + ]; + } +} diff --git a/vendor/magento/module-elasticsearch/SearchAdapter/Query/Builder/Sort/EntityId.php b/vendor/magento/module-elasticsearch/SearchAdapter/Query/Builder/Sort/EntityId.php new file mode 100644 index 0000000000000..5362cbacc536a --- /dev/null +++ b/vendor/magento/module-elasticsearch/SearchAdapter/Query/Builder/Sort/EntityId.php @@ -0,0 +1,31 @@ + [ + 'type' => 'number', + 'script' => [ + 'lang' => 'painless', + 'source' => 'Long.parseLong(doc[\'_id\'].value)', + ], + 'order' => $direction, + ], + ]; + } +} diff --git a/vendor/magento/module-elasticsearch/SearchAdapter/Query/Builder/Sort/ExpressionBuilder.php b/vendor/magento/module-elasticsearch/SearchAdapter/Query/Builder/Sort/ExpressionBuilder.php new file mode 100644 index 0000000000000..f94bd1eb40987 --- /dev/null +++ b/vendor/magento/module-elasticsearch/SearchAdapter/Query/Builder/Sort/ExpressionBuilder.php @@ -0,0 +1,46 @@ +defaultExpressionBuilder = $defaultExpressionBuilder; + $this->customExpressionBuilders = $customExpressionBuilders; + } + + /** + * @inheritdoc + */ + public function build(AttributeAdapter $attribute, string $direction, RequestInterface $request): array + { + return isset($this->customExpressionBuilders[$attribute->getAttributeCode()]) + ? $this->customExpressionBuilders[$attribute->getAttributeCode()]->build($attribute, $direction, $request) + : $this->defaultExpressionBuilder->build($attribute, $direction, $request); + } +} diff --git a/vendor/magento/module-elasticsearch/SearchAdapter/Query/Builder/Sort/ExpressionBuilderInterface.php b/vendor/magento/module-elasticsearch/SearchAdapter/Query/Builder/Sort/ExpressionBuilderInterface.php new file mode 100644 index 0000000000000..54bb2d11afacc --- /dev/null +++ b/vendor/magento/module-elasticsearch/SearchAdapter/Query/Builder/Sort/ExpressionBuilderInterface.php @@ -0,0 +1,24 @@ +fieldNameResolver = $fieldNameResolver; + } + + /** + * @inheritdoc + */ + public function build(AttributeAdapter $attribute, string $direction, RequestInterface $request): array + { + $sortParams = ['order' => $direction]; + + $categoryIds = $this->getCategoryIdsFromQuery($request->getQuery()); + if (count($categoryIds) > 1) { + $fieldNames = []; + foreach ($categoryIds as $categoryId) { + $fieldNames[] = $this->fieldNameResolver->getFieldName($attribute, ['categoryId' => $categoryId]); + } + $fieldName = '_script'; + $sortParams += [ + 'type' => 'number', + 'script' => [ + 'lang' => 'painless', + 'source' => <<