diff --git a/app/code/Magento/CatalogGraphQl/Model/Category/CategoryFilter.php b/app/code/Magento/CatalogGraphQl/Model/Category/CategoryFilter.php index dc93005983776..4350b6dd85266 100644 --- a/app/code/Magento/CatalogGraphQl/Model/Category/CategoryFilter.php +++ b/app/code/Magento/CatalogGraphQl/Model/Category/CategoryFilter.php @@ -7,17 +7,17 @@ namespace Magento\CatalogGraphQl\Model\Category; -use Magento\Catalog\Api\CategoryListInterface; -use Magento\Catalog\Api\Data\CategoryInterface; -use Magento\Framework\App\Config\ScopeConfigInterface; +use Magento\Catalog\Api\CategoryRepositoryInterface; +use Magento\Catalog\Api\Data\CategorySearchResultsInterface; +use Magento\Catalog\Api\Data\CategorySearchResultsInterfaceFactory; +use Magento\Catalog\Model\ResourceModel\Category\CollectionFactory; +use Magento\CatalogGraphQl\Model\Resolver\Categories\DataProvider\Category\CollectionProcessorInterface; +use Magento\CatalogGraphQl\Model\Category\Filter\SearchCriteria; +use Magento\Framework\Api\ExtensionAttribute\JoinProcessorInterface; use Magento\Framework\Exception\InputException; use Magento\Framework\GraphQl\Exception\GraphQlInputException; -use Magento\Framework\GraphQl\Query\Resolver\Argument\SearchCriteria\ArgumentApplier\Filter; -use Magento\Framework\GraphQl\Query\Resolver\Argument\SearchCriteria\ArgumentApplier\Sort; -use Magento\Search\Model\Query; +use Magento\GraphQl\Model\Query\ContextInterface; use Magento\Store\Api\Data\StoreInterface; -use Magento\Store\Model\ScopeInterface; -use Magento\Framework\GraphQl\Query\Resolver\Argument\SearchCriteria\Builder; /** * Category filter allows filtering category results by attributes. @@ -25,38 +25,57 @@ class CategoryFilter { /** - * @var string + * @var CollectionFactory */ - private const SPECIAL_CHARACTERS = '-+~/\\<>\'":*$#@()!,.?`=%&^'; + private $categoryCollectionFactory; /** - * @var ScopeConfigInterface + * @var CollectionProcessorInterface */ - private $scopeConfig; + private $collectionProcessor; /** - * @var CategoryListInterface + * @var JoinProcessorInterface */ - private $categoryList; + private $extensionAttributesJoinProcessor; /** - * @var Builder + * @var CategorySearchResultsInterfaceFactory */ - private $searchCriteriaBuilder; + private $categorySearchResultsFactory; /** - * @param ScopeConfigInterface $scopeConfig - * @param CategoryListInterface $categoryList - * @param Builder $searchCriteriaBuilder + * @var CategoryRepositoryInterface + */ + private $categoryRepository; + + /** + * @var SearchCriteria + */ + private $searchCriteria; + + /** + * @param CollectionFactory $categoryCollectionFactory + * @param CollectionProcessorInterface $collectionProcessor + * @param JoinProcessorInterface $extensionAttributesJoinProcessor + * @param CategorySearchResultsInterfaceFactory $categorySearchResultsFactory + * @param CategoryRepositoryInterface $categoryRepository + * @param SearchCriteria $searchCriteria */ public function __construct( - ScopeConfigInterface $scopeConfig, - CategoryListInterface $categoryList, - Builder $searchCriteriaBuilder + CollectionFactory $categoryCollectionFactory, + CollectionProcessorInterface $collectionProcessor, + JoinProcessorInterface $extensionAttributesJoinProcessor, + CategorySearchResultsInterfaceFactory $categorySearchResultsFactory, + CategoryRepositoryInterface $categoryRepository, + SearchCriteria $searchCriteria ) { - $this->scopeConfig = $scopeConfig; - $this->categoryList = $categoryList; - $this->searchCriteriaBuilder = $searchCriteriaBuilder; + $this->categoryCollectionFactory = $categoryCollectionFactory; + $this->collectionProcessor = $collectionProcessor; + $this->extensionAttributesJoinProcessor = $extensionAttributesJoinProcessor; + $this->categorySearchResultsFactory = $categorySearchResultsFactory; + $this->categoryRepository = $categoryRepository; + $this->searchCriteria = $searchCriteria; } /** @@ -64,21 +83,25 @@ public function __construct( * * @param array $criteria * @param StoreInterface $store + * @param array $attributeNames + * @param ContextInterface $context * @return int[] * @throws InputException */ - public function getResult(array $criteria, StoreInterface $store) + public function getResult(array $criteria, StoreInterface $store, array $attributeNames, ContextInterface $context) { - $categoryIds = []; - $criteria[Filter::ARGUMENT_NAME] = $this->formatMatchFilters($criteria['filters'], $store); - $criteria[Filter::ARGUMENT_NAME][CategoryInterface::KEY_IS_ACTIVE] = ['eq' => 1]; - $criteria[Sort::ARGUMENT_NAME][CategoryInterface::KEY_POSITION] = ['ASC']; - $searchCriteria = $this->searchCriteriaBuilder->build('categoryList', $criteria); - $pageSize = $criteria['pageSize'] ?? 20; - $currentPage = $criteria['currentPage'] ?? 1; - $searchCriteria->setPageSize($pageSize)->setCurrentPage($currentPage); + $searchCriteria = $this->searchCriteria->buildCriteria($criteria, $store); + $collection = $this->categoryCollectionFactory->create(); + $this->extensionAttributesJoinProcessor->process($collection); + $this->collectionProcessor->process($collection, $searchCriteria, $attributeNames, $context); + + /** @var CategorySearchResultsInterface $searchResult */ + $categories = $this->categorySearchResultsFactory->create(); + $categories->setSearchCriteria($searchCriteria); + $categories->setItems($collection->getItems()); + $categories->setTotalCount($collection->getSize()); - $categories = $this->categoryList->getList($searchCriteria); + $categoryIds = []; foreach ($categories->getItems() as $category) { $categoryIds[] = (int)$category->getId(); } @@ -106,35 +129,4 @@ public function getResult(array $criteria, StoreInterface $store) ] ]; } - - /** - * Format match filters to behave like fuzzy match - * - * @param array $filters - * @param StoreInterface $store - * @return array - * @throws InputException - */ - private function formatMatchFilters(array $filters, StoreInterface $store): array - { - $minQueryLength = $this->scopeConfig->getValue( - Query::XML_PATH_MIN_QUERY_LENGTH, - ScopeInterface::SCOPE_STORE, - $store - ); - - foreach ($filters as $filter => $condition) { - $conditionType = current(array_keys($condition)); - if ($conditionType === 'match') { - $searchValue = trim(str_replace(self::SPECIAL_CHARACTERS, '', $condition[$conditionType])); - $matchLength = strlen($searchValue); - if ($matchLength < $minQueryLength) { - throw new InputException(__('Invalid match filter. Minimum length is %1.', $minQueryLength)); - } - unset($filters[$filter]['match']); - $filters[$filter]['like'] = '%' . $searchValue . '%'; - } - } - return $filters; - } } diff --git a/app/code/Magento/CatalogGraphQl/Model/Category/Filter/SearchCriteria.php b/app/code/Magento/CatalogGraphQl/Model/Category/Filter/SearchCriteria.php new file mode 100644 index 0000000000000..aea34f19fea16 --- /dev/null +++ b/app/code/Magento/CatalogGraphQl/Model/Category/Filter/SearchCriteria.php @@ -0,0 +1,105 @@ +\'":*$#@()!,.?`=%&^'; + + /** + * @var ScopeConfigInterface + */ + private $scopeConfig; + + /** + * @var Builder + */ + private $searchCriteriaBuilder; + + /** + * @param ScopeConfigInterface $scopeConfig + * @param Builder $searchCriteriaBuilder + */ + public function __construct( + ScopeConfigInterface $scopeConfig, + Builder $searchCriteriaBuilder + ) { + $this->scopeConfig = $scopeConfig; + $this->searchCriteriaBuilder = $searchCriteriaBuilder; + } + + /** + * Transform raw criteria data into SearchCriteriaInterface + * + * @param array $criteria + * @param StoreInterface $store + * @return SearchCriteriaInterface + * @throws InputException + */ + public function buildCriteria(array $criteria, StoreInterface $store): SearchCriteriaInterface + { + $criteria[Filter::ARGUMENT_NAME] = $this->formatMatchFilters($criteria['filters'], $store); + $criteria[Filter::ARGUMENT_NAME][CategoryInterface::KEY_IS_ACTIVE] = ['eq' => 1]; + $criteria[Sort::ARGUMENT_NAME][CategoryInterface::KEY_POSITION] = ['ASC']; + + $searchCriteria = $this->searchCriteriaBuilder->build('categoryList', $criteria); + $pageSize = $criteria['pageSize'] ?? 20; + $currentPage = $criteria['currentPage'] ?? 1; + $searchCriteria->setPageSize($pageSize)->setCurrentPage($currentPage); + + return $searchCriteria; + } + + /** + * Format match filters to behave like fuzzy match + * + * @param array $filters + * @param StoreInterface $store + * @return array + * @throws InputException + */ + private function formatMatchFilters(array $filters, StoreInterface $store): array + { + $minQueryLength = $this->scopeConfig->getValue( + Query::XML_PATH_MIN_QUERY_LENGTH, + ScopeInterface::SCOPE_STORE, + $store + ); + + foreach ($filters as $filter => $condition) { + $conditionType = current(array_keys($condition)); + if ($conditionType === 'match') { + $searchValue = trim(str_replace(self::SPECIAL_CHARACTERS, '', $condition[$conditionType])); + $matchLength = strlen($searchValue); + if ($matchLength < $minQueryLength) { + throw new InputException(__('Invalid match filter. Minimum length is %1.', $minQueryLength)); + } + unset($filters[$filter]['match']); + $filters[$filter]['like'] = '%' . $searchValue . '%'; + } + } + return $filters; + } +} diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/Categories/DataProvider/Category/CollectionProcessor/CatalogProcessor.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/Categories/DataProvider/Category/CollectionProcessor/CatalogProcessor.php new file mode 100644 index 0000000000000..c8f9ad5de008f --- /dev/null +++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/Categories/DataProvider/Category/CollectionProcessor/CatalogProcessor.php @@ -0,0 +1,55 @@ +collectionProcessor = $collectionProcessor; + } + + /** + * Process collection to add additional joins, attributes, and clauses to a category collection. + * + * @param Collection $collection + * @param SearchCriteriaInterface $searchCriteria + * @param array $attributeNames + * @param ContextInterface|null $context + * @return Collection + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function process( + Collection $collection, + SearchCriteriaInterface $searchCriteria, + array $attributeNames, + ContextInterface $context = null + ): Collection { + $this->collectionProcessor->process($searchCriteria, $collection); + + return $collection; + } +} diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/Categories/DataProvider/Category/CollectionProcessorInterface.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/Categories/DataProvider/Category/CollectionProcessorInterface.php new file mode 100644 index 0000000000000..5e79064e9acfa --- /dev/null +++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/Categories/DataProvider/Category/CollectionProcessorInterface.php @@ -0,0 +1,34 @@ +collectionProcessors = $collectionProcessors; + } + + /** + * Process collection to add additional joins, attributes, and clauses to a category collection. + * + * @param Collection $collection + * @param SearchCriteriaInterface $searchCriteria + * @param array $attributeNames + * @param ContextInterface|null $context + * @return Collection + */ + public function process( + Collection $collection, + SearchCriteriaInterface $searchCriteria, + array $attributeNames, + ContextInterface $context = null + ): Collection { + foreach ($this->collectionProcessors as $collectionProcessor) { + $collection = $collectionProcessor->process($collection, $searchCriteria, $attributeNames, $context); + } + + return $collection; + } +} diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/CategoriesQuery.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/CategoriesQuery.php index eb6708dc48f01..4d7ce13fd23cc 100644 --- a/app/code/Magento/CatalogGraphQl/Model/Resolver/CategoriesQuery.php +++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/CategoriesQuery.php @@ -70,7 +70,7 @@ public function resolve(Field $field, $context, ResolveInfo $info, array $value } try { - $filterResult = $this->categoryFilter->getResult($args, $store); + $filterResult = $this->categoryFilter->getResult($args, $store, [], $context); } catch (InputException $e) { throw new GraphQlInputException(__($e->getMessage())); } diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/CategoryList.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/CategoryList.php index f32c5a1f38425..13db03bb2766b 100644 --- a/app/code/Magento/CatalogGraphQl/Model/Resolver/CategoryList.php +++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/CategoryList.php @@ -65,7 +65,7 @@ public function resolve(Field $field, $context, ResolveInfo $info, array $value $args['filters']['ids'] = ['eq' => $store->getRootCategoryId()]; } try { - $filterResults = $this->categoryFilter->getResult($args, $store); + $filterResults = $this->categoryFilter->getResult($args, $store, [], $context); $rootCategoryIds = $filterResults['category_ids']; } catch (InputException $e) { throw new GraphQlInputException(__($e->getMessage())); diff --git a/app/code/Magento/CatalogGraphQl/etc/di.xml b/app/code/Magento/CatalogGraphQl/etc/di.xml index 03f9d7ad03f04..fd3a834bff160 100644 --- a/app/code/Magento/CatalogGraphQl/etc/di.xml +++ b/app/code/Magento/CatalogGraphQl/etc/di.xml @@ -7,6 +7,7 @@ --> + @@ -53,6 +54,13 @@ + + + + Magento\CatalogGraphQl\Model\Resolver\Categories\DataProvider\Category\CollectionProcessor\CatalogProcessor + + + Magento\Catalog\Model\Api\SearchCriteria\ProductCollectionProcessor @@ -84,4 +92,9 @@ + + + Magento\Eav\Model\Api\SearchCriteria\CollectionProcessor + +