From aa0953cd9b3b69999d55bed41f8b858803f922e3 Mon Sep 17 00:00:00 2001 From: Denis Protassoff Date: Wed, 30 Nov 2022 17:10:46 +0300 Subject: [PATCH 1/2] 5325 Modified criteria builder to include visibility filter --- .../Product/SearchCriteriaBuilder.php | 250 ++++++++++++++++++ src/etc/di.xml | 3 + 2 files changed, 253 insertions(+) create mode 100644 src/DataProvider/Product/SearchCriteriaBuilder.php diff --git a/src/DataProvider/Product/SearchCriteriaBuilder.php b/src/DataProvider/Product/SearchCriteriaBuilder.php new file mode 100644 index 0000000..fe1f348 --- /dev/null +++ b/src/DataProvider/Product/SearchCriteriaBuilder.php @@ -0,0 +1,250 @@ + + * @copyright Copyright (c) 2022 Scandiweb, Ltd (https://scandiweb.com) + */ + +declare(strict_types=1); + +namespace ScandiPWA\CatalogGraphQl\DataProvider\Product; + +use Magento\Catalog\Api\Data\EavAttributeInterface; +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\App\Config\ScopeConfigInterface; +use Magento\Framework\GraphQl\Query\Resolver\Argument\SearchCriteria\Builder; +use Magento\Catalog\Model\Product\Visibility; +use Magento\Framework\Api\SortOrderBuilder; +use Magento\CatalogGraphQl\DataProvider\Product\SearchCriteriaBuilder as MagentoSearchCriteriaBuilder; + +/** + * Class SearchCriteriaBuilder + * + * @package ScandiPWA\CatalogGraphQl\DataProvider\Product + */ +class SearchCriteriaBuilder extends MagentoSearchCriteriaBuilder +{ + /** + * @var ScopeConfigInterface + */ + private $scopeConfig; + + /** + * @var FilterBuilder + */ + private $filterBuilder; + + /** + * @var FilterGroupBuilder + */ + private $filterGroupBuilder; + + /** + * @var Builder + */ + private $builder; + /** + * @var Visibility + */ + private $visibility; + + /** + * @var SortOrderBuilder + */ + private $sortOrderBuilder; + + /** + * @param Builder $builder + * @param ScopeConfigInterface $scopeConfig + * @param FilterBuilder $filterBuilder + * @param FilterGroupBuilder $filterGroupBuilder + * @param Visibility $visibility + * @param SortOrderBuilder $sortOrderBuilder + */ + public function __construct( + Builder $builder, + ScopeConfigInterface $scopeConfig, + FilterBuilder $filterBuilder, + FilterGroupBuilder $filterGroupBuilder, + Visibility $visibility, + SortOrderBuilder $sortOrderBuilder + ) { + $this->scopeConfig = $scopeConfig; + $this->filterBuilder = $filterBuilder; + $this->filterGroupBuilder = $filterGroupBuilder; + $this->builder = $builder; + $this->visibility = $visibility; + $this->sortOrderBuilder = $sortOrderBuilder; + } + + /** + * Build search criteria + * + * @param array $args + * @param bool $includeAggregation + * @return SearchCriteriaInterface + */ + public function build(array $args, bool $includeAggregation): SearchCriteriaInterface + { + $searchCriteria = $this->builder->build('products', $args); + $isSearch = !empty($args['search']); + $this->updateRangeFilters($searchCriteria); + + if ($includeAggregation) { + $this->preparePriceAggregation($searchCriteria); + $requestName = 'graphql_product_search_with_aggregation'; + } else { + $requestName = 'graphql_product_search'; + } + $searchCriteria->setRequestName($requestName); + + if ($isSearch) { + $this->addFilter($searchCriteria, 'search_term', $args['search']); + } + + if (!$searchCriteria->getSortOrders()) { + $this->addDefaultSortOrder($searchCriteria, $args, $isSearch); + } + + $this->addEntityIdSort($searchCriteria, $args); + $this->addVisibilityFilter($searchCriteria, $isSearch); + + $searchCriteria->setCurrentPage($args['currentPage']); + $searchCriteria->setPageSize($args['pageSize']); + + return $searchCriteria; + } + + /** + * Changed to always add visibility filter + * Add filter by visibility + * + * @param SearchCriteriaInterface $searchCriteria + * @param bool $isSearch + * @param bool $isFilter + */ + private function addVisibilityFilter(SearchCriteriaInterface $searchCriteria, bool $isSearch): void + { + $visibilityIds = $isSearch + ? $this->visibility->getVisibleInSearchIds() + : $this->visibility->getVisibleInCatalogIds(); + + $this->addFilter($searchCriteria, 'visibility', $visibilityIds, 'in'); + } + + /** + * Add sort by Entity ID + * + * @param SearchCriteriaInterface $searchCriteria + * @param array $args + */ + private function addEntityIdSort(SearchCriteriaInterface $searchCriteria, array $args): void + { + $sortOrder = !empty($args['sort']) ? reset($args['sort']) : SortOrder::SORT_DESC; + $sortOrderArray = $searchCriteria->getSortOrders(); + $sortOrderArray[] = $this->sortOrderBuilder + ->setField('_id') + ->setDirection($sortOrder) + ->create(); + $searchCriteria->setSortOrders($sortOrderArray); + } + + /** + * Prepare price aggregation algorithm + * + * @param SearchCriteriaInterface $searchCriteria + * @return void + */ + private function preparePriceAggregation(SearchCriteriaInterface $searchCriteria): void + { + $priceRangeCalculation = $this->scopeConfig->getValue( + \Magento\Catalog\Model\Layer\Filter\Dynamic\AlgorithmFactory::XML_PATH_RANGE_CALCULATION, + \Magento\Store\Model\ScopeInterface::SCOPE_STORE + ); + if ($priceRangeCalculation) { + $this->addFilter($searchCriteria, 'price_dynamic_algorithm', $priceRangeCalculation); + } + } + + /** + * Add filter to search criteria + * + * @param SearchCriteriaInterface $searchCriteria + * @param string $field + * @param mixed $value + * @param string|null $condition + */ + private function addFilter( + SearchCriteriaInterface $searchCriteria, + string $field, + $value, + ?string $condition = null + ): void { + $filter = $this->filterBuilder + ->setField($field) + ->setValue($value) + ->setConditionType($condition) + ->create(); + + $this->filterGroupBuilder->addFilter($filter); + $filterGroups = $searchCriteria->getFilterGroups(); + $filterGroups[] = $this->filterGroupBuilder->create(); + $searchCriteria->setFilterGroups($filterGroups); + } + + /** + * Sort by relevance DESC by default + * + * @param SearchCriteriaInterface $searchCriteria + * @param array $args + * @param bool $isSearch + */ + private function addDefaultSortOrder(SearchCriteriaInterface $searchCriteria, array $args, $isSearch = false): void + { + $defaultSortOrder = []; + if ($isSearch) { + $defaultSortOrder[] = $this->sortOrderBuilder + ->setField('relevance') + ->setDirection(SortOrder::SORT_DESC) + ->create(); + } else { + $categoryIdFilter = isset($args['filter']['category_id']) ? $args['filter']['category_id'] : false; + if ($categoryIdFilter) { + if (!is_array($categoryIdFilter[array_key_first($categoryIdFilter)]) + || count($categoryIdFilter[array_key_first($categoryIdFilter)]) <= 1 + ) { + $defaultSortOrder[] = $this->sortOrderBuilder + ->setField(EavAttributeInterface::POSITION) + ->setDirection(SortOrder::SORT_ASC) + ->create(); + } + } + } + + $searchCriteria->setSortOrders($defaultSortOrder); + } + + /** + * Format range filters so replacement works + * + * Range filter fields in search request must replace value like '%field.from%' or '%field.to%' + * + * @param SearchCriteriaInterface $searchCriteria + */ + private function updateRangeFilters(SearchCriteriaInterface $searchCriteria): void + { + $filterGroups = $searchCriteria->getFilterGroups(); + foreach ($filterGroups as $filterGroup) { + $filters = $filterGroup->getFilters(); + foreach ($filters as $filter) { + if (in_array($filter->getConditionType(), ['from', 'to'])) { + $filter->setField($filter->getField() . '.' . $filter->getConditionType()); + } + } + } + } +} diff --git a/src/etc/di.xml b/src/etc/di.xml index ab69c44..7cf33bd 100755 --- a/src/etc/di.xml +++ b/src/etc/di.xml @@ -19,6 +19,9 @@ + + From c90394d1c2d80c9daa660a3b286b99174eb27c3c Mon Sep 17 00:00:00 2001 From: Denis Protassoff Date: Thu, 1 Dec 2022 14:24:28 +0300 Subject: [PATCH 2/2] 5325 Improved code quality --- .../Product/SearchCriteriaBuilder.php | 32 ++++++++++++------- 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/src/DataProvider/Product/SearchCriteriaBuilder.php b/src/DataProvider/Product/SearchCriteriaBuilder.php index fe1f348..68e3059 100644 --- a/src/DataProvider/Product/SearchCriteriaBuilder.php +++ b/src/DataProvider/Product/SearchCriteriaBuilder.php @@ -31,31 +31,31 @@ class SearchCriteriaBuilder extends MagentoSearchCriteriaBuilder /** * @var ScopeConfigInterface */ - private $scopeConfig; + protected $scopeConfig; /** * @var FilterBuilder */ - private $filterBuilder; + protected $filterBuilder; /** * @var FilterGroupBuilder */ - private $filterGroupBuilder; + protected $filterGroupBuilder; /** * @var Builder */ - private $builder; + protected $builder; /** * @var Visibility */ - private $visibility; + protected $visibility; /** * @var SortOrderBuilder */ - private $sortOrderBuilder; + protected $sortOrderBuilder; /** * @param Builder $builder @@ -100,6 +100,7 @@ public function build(array $args, bool $includeAggregation): SearchCriteriaInte } else { $requestName = 'graphql_product_search'; } + $searchCriteria->setRequestName($requestName); if ($isSearch) { @@ -111,6 +112,7 @@ public function build(array $args, bool $includeAggregation): SearchCriteriaInte } $this->addEntityIdSort($searchCriteria, $args); + // Removed $isFilter parameter $this->addVisibilityFilter($searchCriteria, $isSearch); $searchCriteria->setCurrentPage($args['currentPage']); @@ -127,8 +129,9 @@ public function build(array $args, bool $includeAggregation): SearchCriteriaInte * @param bool $isSearch * @param bool $isFilter */ - private function addVisibilityFilter(SearchCriteriaInterface $searchCriteria, bool $isSearch): void + protected function addVisibilityFilter(SearchCriteriaInterface $searchCriteria, bool $isSearch): void { + // Removed $isFilter parameter and related check $visibilityIds = $isSearch ? $this->visibility->getVisibleInSearchIds() : $this->visibility->getVisibleInCatalogIds(); @@ -142,7 +145,7 @@ private function addVisibilityFilter(SearchCriteriaInterface $searchCriteria, bo * @param SearchCriteriaInterface $searchCriteria * @param array $args */ - private function addEntityIdSort(SearchCriteriaInterface $searchCriteria, array $args): void + protected function addEntityIdSort(SearchCriteriaInterface $searchCriteria, array $args): void { $sortOrder = !empty($args['sort']) ? reset($args['sort']) : SortOrder::SORT_DESC; $sortOrderArray = $searchCriteria->getSortOrders(); @@ -159,12 +162,13 @@ private function addEntityIdSort(SearchCriteriaInterface $searchCriteria, array * @param SearchCriteriaInterface $searchCriteria * @return void */ - private function preparePriceAggregation(SearchCriteriaInterface $searchCriteria): void + protected function preparePriceAggregation(SearchCriteriaInterface $searchCriteria): void { $priceRangeCalculation = $this->scopeConfig->getValue( \Magento\Catalog\Model\Layer\Filter\Dynamic\AlgorithmFactory::XML_PATH_RANGE_CALCULATION, \Magento\Store\Model\ScopeInterface::SCOPE_STORE ); + if ($priceRangeCalculation) { $this->addFilter($searchCriteria, 'price_dynamic_algorithm', $priceRangeCalculation); } @@ -178,7 +182,7 @@ private function preparePriceAggregation(SearchCriteriaInterface $searchCriteria * @param mixed $value * @param string|null $condition */ - private function addFilter( + protected function addFilter( SearchCriteriaInterface $searchCriteria, string $field, $value, @@ -203,9 +207,10 @@ private function addFilter( * @param array $args * @param bool $isSearch */ - private function addDefaultSortOrder(SearchCriteriaInterface $searchCriteria, array $args, $isSearch = false): void + protected function addDefaultSortOrder(SearchCriteriaInterface $searchCriteria, array $args, $isSearch = false): void { $defaultSortOrder = []; + if ($isSearch) { $defaultSortOrder[] = $this->sortOrderBuilder ->setField('relevance') @@ -213,6 +218,7 @@ private function addDefaultSortOrder(SearchCriteriaInterface $searchCriteria, ar ->create(); } else { $categoryIdFilter = isset($args['filter']['category_id']) ? $args['filter']['category_id'] : false; + if ($categoryIdFilter) { if (!is_array($categoryIdFilter[array_key_first($categoryIdFilter)]) || count($categoryIdFilter[array_key_first($categoryIdFilter)]) <= 1 @@ -235,11 +241,13 @@ private function addDefaultSortOrder(SearchCriteriaInterface $searchCriteria, ar * * @param SearchCriteriaInterface $searchCriteria */ - private function updateRangeFilters(SearchCriteriaInterface $searchCriteria): void + protected function updateRangeFilters(SearchCriteriaInterface $searchCriteria): void { $filterGroups = $searchCriteria->getFilterGroups(); + foreach ($filterGroups as $filterGroup) { $filters = $filterGroup->getFilters(); + foreach ($filters as $filter) { if (in_array($filter->getConditionType(), ['from', 'to'])) { $filter->setField($filter->getField() . '.' . $filter->getConditionType());