diff --git a/app/code/Magento/Analytics/Model/ExportDataHandler.php b/app/code/Magento/Analytics/Model/ExportDataHandler.php index dabe6b05ffd71..f704a2b8dc71e 100644 --- a/app/code/Magento/Analytics/Model/ExportDataHandler.php +++ b/app/code/Magento/Analytics/Model/ExportDataHandler.php @@ -90,7 +90,7 @@ public function __construct( public function prepareExportData() { try { - $tmpDirectory = $this->filesystem->getDirectoryWrite(DirectoryList::TMP); + $tmpDirectory = $this->filesystem->getDirectoryWrite(DirectoryList::SYS_TMP); $this->prepareDirectory($tmpDirectory, $this->getTmpFilesDirRelativePath()); $this->reportWriter->write($tmpDirectory, $this->getTmpFilesDirRelativePath()); @@ -122,7 +122,17 @@ public function prepareExportData() */ private function getTmpFilesDirRelativePath() { - return $this->subdirectoryPath . 'tmp/'; + return $this->subdirectoryPath . 'tmp/' . $this->getInstanceIdentifier() . '/'; + } + + /** + * Return unique identifier for an instance. + * + * @return string + */ + private function getInstanceIdentifier() + { + return hash('sha256', BP); } /** diff --git a/app/code/Magento/Analytics/Test/Unit/Model/ExportDataHandlerTest.php b/app/code/Magento/Analytics/Test/Unit/Model/ExportDataHandlerTest.php index 1a8bbaffb65b7..755c7217c6b32 100644 --- a/app/code/Magento/Analytics/Test/Unit/Model/ExportDataHandlerTest.php +++ b/app/code/Magento/Analytics/Test/Unit/Model/ExportDataHandlerTest.php @@ -112,13 +112,23 @@ protected function setUp(): void ); } + /** + * Return unique identifier for an instance. + * + * @return string + */ + private function getInstanceIdentifier() + { + return hash('sha256', BP); + } + /** * @param bool $isArchiveSourceDirectory * @dataProvider prepareExportDataDataProvider */ public function testPrepareExportData($isArchiveSourceDirectory) { - $tmpFilesDirectoryPath = $this->subdirectoryPath . 'tmp/'; + $tmpFilesDirectoryPath = $this->subdirectoryPath . 'tmp/' . $this->getInstanceIdentifier() . '/'; $archiveRelativePath = $this->subdirectoryPath . $this->archiveName; $archiveSource = $isArchiveSourceDirectory ? (__DIR__) : '/tmp/' . $tmpFilesDirectoryPath; @@ -127,7 +137,7 @@ public function testPrepareExportData($isArchiveSourceDirectory) $this->filesystemMock ->expects($this->once()) ->method('getDirectoryWrite') - ->with(DirectoryList::TMP) + ->with(DirectoryList::SYS_TMP) ->willReturn($this->directoryMock); $this->directoryMock ->expects($this->exactly(4)) @@ -210,13 +220,13 @@ public static function prepareExportDataDataProvider() public function testPrepareExportDataWithLocalizedException() { $this->expectException('Magento\Framework\Exception\LocalizedException'); - $tmpFilesDirectoryPath = $this->subdirectoryPath . 'tmp/'; + $tmpFilesDirectoryPath = $this->subdirectoryPath . 'tmp/' . $this->getInstanceIdentifier() . '/'; $archivePath = $this->subdirectoryPath . $this->archiveName; $this->filesystemMock ->expects($this->once()) ->method('getDirectoryWrite') - ->with(DirectoryList::TMP) + ->with(DirectoryList::SYS_TMP) ->willReturn($this->directoryMock); $this->reportWriterMock ->expects($this->once()) diff --git a/app/code/Magento/CatalogGraphQl/DataProvider/Product/SearchCriteriaBuilder.php b/app/code/Magento/CatalogGraphQl/DataProvider/Product/SearchCriteriaBuilder.php index e05a5ec1c8141..757e64cb34288 100644 --- a/app/code/Magento/CatalogGraphQl/DataProvider/Product/SearchCriteriaBuilder.php +++ b/app/code/Magento/CatalogGraphQl/DataProvider/Product/SearchCriteriaBuilder.php @@ -8,103 +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\Exception\LocalizedException; -use Magento\Framework\GraphQl\Query\Resolver\Argument\SearchCriteria\Builder; +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; - - /** - * @var SearchConfig - */ - private SearchConfig $searchConfig; - - /** - * @var RequestDataBuilder|mixed - */ - private RequestDataBuilder $localData; - - /** - * @param Builder $builder * @param ScopeConfigInterface $scopeConfig * @param FilterBuilder $filterBuilder * @param FilterGroupBuilder $filterGroupBuilder * @param Visibility $visibility - * @param SortOrderBuilder|null $sortOrderBuilder - * @param Config|null $eavConfig - * @param SearchConfig|null $searchConfig - * @param RequestDataBuilder|null $localData + * @param SortOrderBuilder $sortOrderBuilder + * @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, - SearchConfig $searchConfig = null, - RequestDataBuilder $localData = 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); - $this->searchConfig = $searchConfig ?? ObjectManager::getInstance()->get(SearchConfig::class); - $this->localData = $localData ?? ObjectManager::getInstance()->get(RequestDataBuilder::class); } /** @@ -117,45 +64,42 @@ public function __construct( */ public function build(array $args, bool $includeAggregation): SearchCriteriaInterface { - $partialMatchFilters = []; + $isSearch = isset($args['search']); + $requestName = $includeAggregation ? 'graphql_product_search_with_aggregation' : 'graphql_product_search'; + if (isset($args['filter'])) { $partialMatchFilters = $this->getPartialMatchFilters($args); + if (count($partialMatchFilters)) { + $this->updateMatchTypeRequestConfig($requestName, $partialMatchFilters); + } $args = $this->removeMatchTypeFromArguments($args); } - $searchCriteria = $this->builder->build('products', $args); - $isSearch = isset($args['search']); - $this->updateRangeFilters($searchCriteria); - if ($includeAggregation) { - $attributeData = $this->eavConfig->getAttribute(Product::ENTITY, 'price'); - $priceOptions = $attributeData->getData(); - if ($priceOptions['is_filterable'] != 0) { - $this->preparePriceAggregation($searchCriteria); + $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); } - $requestName = 'graphql_product_search_with_aggregation'; - } else { - $requestName = 'graphql_product_search'; - } - $searchCriteria->setRequestName($requestName); - - if (count($partialMatchFilters)) { - $this->updateMatchTypeRequestConfig($requestName, $partialMatchFilters); } - + $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']['category_id'])); - $searchCriteria->setCurrentPage($args['currentPage']); - $searchCriteria->setPageSize($args['pageSize']); - return $searchCriteria; } @@ -164,7 +108,6 @@ public function build(array $args, bool $includeAggregation): SearchCriteriaInte * * @param string $requestName * @param array $partialMatchFilters - * * @return void */ private function updateMatchTypeRequestConfig(string $requestName, array $partialMatchFilters): void @@ -184,7 +127,6 @@ private function updateMatchTypeRequestConfig(string $requestName, array $partia * Check if and what type of match_type value was requested * * @param array $args - * * @return array */ private function getPartialMatchFilters(array $args): array @@ -202,7 +144,6 @@ private function getPartialMatchFilters(array $args): array * Remove the match_type to avoid search criteria containing it * * @param array $args - * * @return array */ private function removeMatchTypeFromArguments(array $args): array @@ -254,7 +195,7 @@ private function addEntityIdSort(SearchCriteriaInterface $searchCriteria): void } $sortOrderArray[] = $this->sortOrderBuilder - ->setField('_id') + ->setField('entity_id') ->setDirection($sortDir) ->create(); $searchCriteria->setSortOrders($sortOrderArray); @@ -264,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 @@ -319,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/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/DataProvider/ProductSearch.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/DataProvider/ProductSearch.php index f1d30ab942aab..5c170f428bc94 100644 --- a/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/DataProvider/ProductSearch.php +++ b/app/code/Magento/CatalogGraphQl/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/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/DataProvider/ProductSearch/ProductCollectionSearchCriteriaBuilder.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/DataProvider/ProductSearch/ProductCollectionSearchCriteriaBuilder.php deleted file mode 100644 index 03e8358b1ee7a..0000000000000 --- a/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/DataProvider/ProductSearch/ProductCollectionSearchCriteriaBuilder.php +++ /dev/null @@ -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/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/Query/Search.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/Query/Search.php index c4d189cd7cb0c..f77f19bb684b0 100644 --- a/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/Query/Search.php +++ b/app/code/Magento/CatalogGraphQl/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/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/SearchCriteria/CollectionProcessor/FilterProcessor/CategoryFilter.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/SearchCriteria/CollectionProcessor/FilterProcessor/CategoryFilter.php index d15c072b9fe4b..e8825b95a9c7c 100644 --- a/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/SearchCriteria/CollectionProcessor/FilterProcessor/CategoryFilter.php +++ b/app/code/Magento/CatalogGraphQl/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/app/code/Magento/CatalogGraphQl/Test/Unit/DataProvider/Product/SearchCriteriaBuilderTest.php b/app/code/Magento/CatalogGraphQl/Test/Unit/DataProvider/Product/SearchCriteriaBuilderTest.php index 562b90156689e..97c4fcde30ce2 100644 --- a/app/code/Magento/CatalogGraphQl/Test/Unit/DataProvider/Product/SearchCriteriaBuilderTest.php +++ b/app/code/Magento/CatalogGraphQl/Test/Unit/DataProvider/Product/SearchCriteriaBuilderTest.php @@ -7,21 +7,22 @@ namespace Magento\CatalogGraphQl\Test\Unit\DataProvider\Product; -use Magento\Catalog\Model\Product; +use Magento\Catalog\Api\ProductAttributeRepositoryInterface; use Magento\Catalog\Model\Product\Visibility; use Magento\Catalog\Model\ResourceModel\Eav\Attribute; use Magento\CatalogGraphQl\DataProvider\Product\RequestDataBuilder; use Magento\CatalogGraphQl\DataProvider\Product\SearchCriteriaBuilder; -use Magento\Eav\Model\Config; +use Magento\CatalogSearch\Model\ResourceModel\Fulltext\Collection\SearchCriteriaResolverFactory; +use Magento\CatalogSearch\Model\ResourceModel\Fulltext\Collection\SearchCriteriaResolverInterface; use Magento\Framework\Api\Filter; use Magento\Framework\Api\FilterBuilder; use Magento\Framework\Api\Search\FilterGroupBuilder; -use Magento\Framework\Api\Search\SearchCriteriaInterface; +use Magento\Framework\Api\Search\SearchCriteria; use Magento\Framework\Api\SortOrderBuilder; use Magento\Framework\App\Config\ScopeConfigInterface; -use Magento\Framework\GraphQl\Query\Resolver\Argument\SearchCriteria\Builder; +use Magento\Framework\GraphQl\Query\Resolver\Argument\SearchCriteria\ArgumentApplierPool; use Magento\Framework\Search\Request\Config as SearchConfig; -use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; /** @@ -32,32 +33,27 @@ class SearchCriteriaBuilderTest extends TestCase { /** - * @var ScopeConfigInterface + * @var ScopeConfigInterface|MockObject */ private ScopeConfigInterface $scopeConfig; /** - * @var FilterBuilder + * @var FilterBuilder|MockObject */ private FilterBuilder $filterBuilder; /** - * @var FilterGroupBuilder + * @var FilterGroupBuilder|MockObject */ private FilterGroupBuilder $filterGroupBuilder; /** - * @var Builder - */ - private Builder $builder; - - /** - * @var Visibility + * @var Visibility|MockObject */ private Visibility $visibility; /** - * @var SortOrderBuilder + * @var SortOrderBuilder|MockObject */ private SortOrderBuilder $sortOrderBuilder; @@ -67,9 +63,29 @@ class SearchCriteriaBuilderTest extends TestCase private SearchCriteriaBuilder $model; /** - * @var Config + * @var ProductAttributeRepositoryInterface|MockObject + */ + private ProductAttributeRepositoryInterface $productAttributeRepository; + + /** + * @var SearchConfig|MockObject */ - private Config $eavConfig; + private SearchConfig $searchConfig; + + /** + * @var RequestDataBuilder|MockObject + */ + private RequestDataBuilder $localData; + + /** + * @var SearchCriteriaResolverFactory|MockObject + */ + private SearchCriteriaResolverFactory $criteriaResolverFactory; + + /** + * @var ArgumentApplierPool|MockObject + */ + private ArgumentApplierPool $argumentApplierPool; /** * @inheritdoc @@ -77,33 +93,28 @@ class SearchCriteriaBuilderTest extends TestCase protected function setUp(): void { parent::setUp(); - $objectManagerHelper = new ObjectManagerHelper($this); - $objects = [ - [ - SearchConfig::class, - $this->createMock(SearchConfig::class) - ], - [ - RequestDataBuilder::class, - $this->createMock(RequestDataBuilder::class) - ] - ]; - $objectManagerHelper->prepareObjectManager($objects); - $this->builder = $this->createMock(Builder::class); + $this->scopeConfig = $this->createMock(ScopeConfigInterface::class); $this->filterBuilder = $this->createMock(FilterBuilder::class); $this->filterGroupBuilder = $this->createMock(FilterGroupBuilder::class); $this->sortOrderBuilder = $this->createMock(SortOrderBuilder::class); $this->visibility = $this->createMock(Visibility::class); - $this->eavConfig = $this->createMock(Config::class); + $this->productAttributeRepository = $this->createMock(ProductAttributeRepositoryInterface::class); + $this->searchConfig = $this->createMock(SearchConfig::class); + $this->localData = $this->createMock(RequestDataBuilder::class); + $this->criteriaResolverFactory = $this->createMock(SearchCriteriaResolverFactory::class); + $this->argumentApplierPool = $this->createMock(ArgumentApplierPool::class); $this->model = new SearchCriteriaBuilder( - $this->builder, $this->scopeConfig, $this->filterBuilder, $this->filterGroupBuilder, $this->visibility, $this->sortOrderBuilder, - $this->eavConfig + $this->productAttributeRepository, + $this->searchConfig, + $this->localData, + $this->criteriaResolverFactory, + $this->argumentApplierPool, ); } @@ -112,26 +123,26 @@ public function testBuild(): void $args = ['search' => '', 'pageSize' => 20, 'currentPage' => 1]; $filter = $this->createMock(Filter::class); - - $searchCriteria = $this->getMockBuilder(SearchCriteriaInterface::class) - ->disableOriginalConstructor() - ->getMockForAbstractClass(); + $searchCriteria = $this->createMock(SearchCriteria::class); $attributeInterface = $this->getMockBuilder(Attribute::class) ->disableOriginalConstructor() ->getMockForAbstractClass(); $attributeInterface->setData(['is_filterable' => 0]); - $this->builder->expects($this->any()) - ->method('build') - ->with('products', $args) + $searchCriteriaResolver = $this->createMock(SearchCriteriaResolverInterface::class); + $this->criteriaResolverFactory->expects(self::once()) + ->method('create') + ->willReturn($searchCriteriaResolver); + $searchCriteriaResolver->expects(self::once()) + ->method('resolve') ->willReturn($searchCriteria); $searchCriteria->expects($this->any())->method('getFilterGroups')->willReturn([]); - $this->eavConfig->expects($this->any()) - ->method('getAttribute') - ->with(Product::ENTITY, 'price') + $this->productAttributeRepository->expects(self::once()) + ->method('get') + ->with('price') ->willReturn($attributeInterface); - $sortOrderList = ['relevance', '_id']; + $sortOrderList = ['relevance', 'entity_id']; $this->sortOrderBuilder->expects($this->exactly(2)) ->method('setField') @@ -149,8 +160,6 @@ public function testBuild(): void ->method('create') ->willReturn([]); - $filterOrderList = ['search_term', 'visibility']; - $this->filterBuilder->expects($this->exactly(2)) ->method('setField') ->willReturnCallback(function ($filterOrderList) { diff --git a/app/code/Magento/CatalogGraphQl/Test/Unit/Model/Resolver/Products/Query/SearchTest.php b/app/code/Magento/CatalogGraphQl/Test/Unit/Model/Resolver/Products/Query/SearchTest.php index 8efd1914869d3..9dabacf0911f2 100644 --- a/app/code/Magento/CatalogGraphQl/Test/Unit/Model/Resolver/Products/Query/SearchTest.php +++ b/app/code/Magento/CatalogGraphQl/Test/Unit/Model/Resolver/Products/Query/SearchTest.php @@ -18,10 +18,8 @@ use Magento\Framework\Api\Search\SearchResultInterface; use Magento\Framework\GraphQl\Query\Resolver\ArgumentsProcessorInterface; use Magento\Framework\GraphQl\Schema\Type\ResolveInfo; -use Magento\GraphQl\Model\Query\ContextExtensionInterface; use Magento\GraphQl\Model\Query\ContextInterface; use Magento\Search\Api\SearchInterface; -use Magento\Search\Model\Search\PageSizeProvider; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; @@ -42,11 +40,6 @@ class SearchTest extends TestCase */ private $searchResultFactory; - /** - * @var PageSizeProvider|MockObject - */ - private $pageSizeProvider; - /** * @var FieldSelection|MockObject */ @@ -94,9 +87,6 @@ protected function setUp(): void $this->searchResultFactory = $this->getMockBuilder(SearchResultFactory::class) ->disableOriginalConstructor() ->getMock(); - $this->pageSizeProvider = $this->getMockBuilder(PageSizeProvider::class) - ->disableOriginalConstructor() - ->getMock(); $this->fieldSelection = $this->getMockBuilder(FieldSelection::class) ->disableOriginalConstructor() ->getMock(); @@ -118,7 +108,6 @@ protected function setUp(): void $this->model = new Search( $this->search, $this->searchResultFactory, - $this->pageSizeProvider, $this->fieldSelection, $this->productsProvider, $this->searchCriteriaBuilder, @@ -130,7 +119,7 @@ protected function setUp(): void public function testPopulateSearchQueryStats(): void { - $args = ['search' => 'test']; + $args = ['search' => 'test', 'pageSize' => 10, 'currentPage' => 1]; $context = $this->getMockBuilder(ContextInterface::class) ->disableOriginalConstructor() ->getMockForAbstractClass(); diff --git a/app/code/Magento/CatalogGraphQl/Test/Unit/Model/Resolver/Products/SearchCriteria/CollectionProcessor/FilterProcessor/CategoryFilterTest.php b/app/code/Magento/CatalogGraphQl/Test/Unit/Model/Resolver/Products/SearchCriteria/CollectionProcessor/FilterProcessor/CategoryFilterTest.php index b827bf822ee3f..8c92027138823 100644 --- a/app/code/Magento/CatalogGraphQl/Test/Unit/Model/Resolver/Products/SearchCriteria/CollectionProcessor/FilterProcessor/CategoryFilterTest.php +++ b/app/code/Magento/CatalogGraphQl/Test/Unit/Model/Resolver/Products/SearchCriteria/CollectionProcessor/FilterProcessor/CategoryFilterTest.php @@ -57,60 +57,6 @@ protected function setUp(): void ); } - /** - * Test that category filter works correctly with condition type "eq" - */ - public function testApplyWithConditionTypeEq(): void - { - $filter = new Filter(); - $category = $this->createMock(\Magento\Catalog\Model\Category::class); - $collection = $this->createMock(Collection::class); - $filter->setConditionType('eq'); - $categoryId = 1; - $filter->setValue($categoryId); - $this->categoryFactory->expects($this->once()) - ->method('create') - ->willReturn($category); - $this->categoryResourceModel->expects($this->once()) - ->method('load') - ->with($category, $categoryId); - $collection->expects($this->once()) - ->method('addCategoryFilter') - ->with($category); - $collection->expects($this->once()) - ->method('getFlag') - ->with('search_resut_applied') - ->willReturn(true); - $this->model->apply($filter, $collection); - } - - /** - * Test that category filter works correctly with condition type "in" and single category - */ - public function testApplyWithConditionTypeInAndSingleCategory(): void - { - $filter = new Filter(); - $category = $this->createMock(\Magento\Catalog\Model\Category::class); - $collection = $this->createMock(Collection::class); - $filter->setConditionType('in'); - $categoryId = 1; - $filter->setValue($categoryId); - $this->categoryFactory->expects($this->once()) - ->method('create') - ->willReturn($category); - $this->categoryResourceModel->expects($this->once()) - ->method('load') - ->with($category, $categoryId); - $collection->expects($this->once()) - ->method('addCategoryFilter') - ->with($category); - $collection->expects($this->once()) - ->method('getFlag') - ->with('search_resut_applied') - ->willReturn(true); - $this->model->apply($filter, $collection); - } - /** * Test that category filter works correctly with condition type "in" and multiple categories */ @@ -151,10 +97,6 @@ public function testApplyWithConditionTypeInAndMultipleCategories(): void $collection->expects($this->once()) ->method('addCategoriesFilter') ->with(['in' => [1, 2, 3]]); - $collection->expects($this->once()) - ->method('getFlag') - ->with('search_resut_applied') - ->willReturn(false); $category1->expects($this->once()) ->method('getIsAnchor') ->willReturn(true); @@ -198,10 +140,6 @@ public function testApplyWithOtherSupportedConditionTypes(string $condition): vo $collection->expects($this->once()) ->method('addCategoriesFilter') ->with([$condition => [1, 2]]); - $collection->expects($this->once()) - ->method('getFlag') - ->with('search_resut_applied') - ->willReturn(false); $category->expects($this->once()) ->method('getIsAnchor') ->willReturn(true); diff --git a/app/code/Magento/CatalogSearch/Model/ResourceModel/Fulltext/Collection.php b/app/code/Magento/CatalogSearch/Model/ResourceModel/Fulltext/Collection.php index d8d4f158ecaa2..237b5a1bb6841 100644 --- a/app/code/Magento/CatalogSearch/Model/ResourceModel/Fulltext/Collection.php +++ b/app/code/Magento/CatalogSearch/Model/ResourceModel/Fulltext/Collection.php @@ -581,7 +581,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/app/code/Magento/CatalogSearch/Model/ResourceModel/Fulltext/Collection/SearchResultApplier.php b/app/code/Magento/CatalogSearch/Model/ResourceModel/Fulltext/Collection/SearchResultApplier.php index 36e0a85fa4307..49676b7c3b208 100644 --- a/app/code/Magento/CatalogSearch/Model/ResourceModel/Fulltext/Collection/SearchResultApplier.php +++ b/app/code/Magento/CatalogSearch/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/app/code/Magento/Elasticsearch/Model/Adapter/BatchDataMapper/ProductDataMapper.php b/app/code/Magento/Elasticsearch/Model/Adapter/BatchDataMapper/ProductDataMapper.php index c929df0ee52b9..bfaab6e98e99f 100644 --- a/app/code/Magento/Elasticsearch/Model/Adapter/BatchDataMapper/ProductDataMapper.php +++ b/app/code/Magento/Elasticsearch/Model/Adapter/BatchDataMapper/ProductDataMapper.php @@ -315,7 +315,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/app/code/Magento/Elasticsearch/Model/ResourceModel/Fulltext/Collection/SearchCriteriaResolver.php b/app/code/Magento/Elasticsearch/Model/ResourceModel/Fulltext/Collection/SearchCriteriaResolver.php index 258d9d35c16f9..a85a9fb16f36f 100644 --- a/app/code/Magento/Elasticsearch/Model/ResourceModel/Fulltext/Collection/SearchCriteriaResolver.php +++ b/app/code/Magento/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; @@ -23,11 +21,6 @@ class SearchCriteriaResolver implements SearchCriteriaResolverInterface */ private $builder; - /** - * @var Collection - */ - private $collection; - /** * @var string */ @@ -39,7 +32,7 @@ class SearchCriteriaResolver implements SearchCriteriaResolverInterface private $size; /** - * @var array + * @var array|null */ private $orders; @@ -49,24 +42,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/app/code/Magento/Elasticsearch/SearchAdapter/Query/Builder/Sort.php b/app/code/Magento/Elasticsearch/SearchAdapter/Query/Builder/Sort.php index a6fe48fa312c6..23709dd6e9285 100644 --- a/app/code/Magento/Elasticsearch/SearchAdapter/Query/Builder/Sort.php +++ b/app/code/Magento/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; /** @@ -19,56 +17,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; } /** @@ -76,9 +44,6 @@ public function __construct( * * @param RequestInterface $request * @return array - * - * @SuppressWarnings(PHPMD.CyclomaticComplexity) - * @SuppressWarnings(PHPMD.NPathComplexity) */ public function getSort(RequestInterface $request) { @@ -90,38 +55,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/app/code/Magento/Elasticsearch/SearchAdapter/Query/Builder/Sort/DefaultExpression.php b/app/code/Magento/Elasticsearch/SearchAdapter/Query/Builder/Sort/DefaultExpression.php new file mode 100644 index 0000000000000..809a8dfb35902 --- /dev/null +++ b/app/code/Magento/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/app/code/Magento/Elasticsearch/SearchAdapter/Query/Builder/Sort/EntityId.php b/app/code/Magento/Elasticsearch/SearchAdapter/Query/Builder/Sort/EntityId.php new file mode 100644 index 0000000000000..5362cbacc536a --- /dev/null +++ b/app/code/Magento/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/app/code/Magento/Elasticsearch/SearchAdapter/Query/Builder/Sort/ExpressionBuilder.php b/app/code/Magento/Elasticsearch/SearchAdapter/Query/Builder/Sort/ExpressionBuilder.php new file mode 100644 index 0000000000000..f94bd1eb40987 --- /dev/null +++ b/app/code/Magento/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/app/code/Magento/Elasticsearch/SearchAdapter/Query/Builder/Sort/ExpressionBuilderInterface.php b/app/code/Magento/Elasticsearch/SearchAdapter/Query/Builder/Sort/ExpressionBuilderInterface.php new file mode 100644 index 0000000000000..54bb2d11afacc --- /dev/null +++ b/app/code/Magento/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' => <<