Skip to content

Commit 31dce79

Browse files
authored
Merge pull request #5466 from magento-honey-badgers/MC-32120
[honey] MC-32120: Catalog price rules are not included in CartItemPrices
2 parents b423851 + e3e9f70 commit 31dce79

34 files changed

+3125
-281
lines changed

app/code/Magento/Catalog/Model/ResourceModel/Product/Collection.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1717,7 +1717,7 @@ public function addAttributeToSort($attribute, $dir = self::SORT_ORDER_ASC)
17171717
// optimize if using cat index
17181718
$filters = $this->_productLimitationFilters;
17191719
if (isset($filters['category_id']) || isset($filters['visibility'])) {
1720-
$this->getSelect()->order('cat_index.position ' . $dir);
1720+
$this->getSelect()->order(['cat_index.position ' . $dir, 'e.entity_id ' . $dir]);
17211721
} else {
17221722
$this->getSelect()->order('e.entity_id ' . $dir);
17231723
}

app/code/Magento/CatalogGraphQl/Model/AttributesJoiner.php

Lines changed: 31 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ public function __construct(array $fieldToAttributeMap = [])
4545
* @param AbstractCollection $collection
4646
* @return void
4747
*/
48-
public function join(FieldNode $fieldNode, AbstractCollection $collection) : void
48+
public function join(FieldNode $fieldNode, AbstractCollection $collection): void
4949
{
5050
foreach ($this->getQueryFields($fieldNode) as $field) {
5151
$this->addFieldToCollection($collection, $field);
@@ -60,19 +60,20 @@ public function join(FieldNode $fieldNode, AbstractCollection $collection) : voi
6060
*/
6161
public function getQueryFields(FieldNode $fieldNode): array
6262
{
63-
if (!isset($this->queryFields[$fieldNode->name->value])) {
64-
$this->queryFields[$fieldNode->name->value] = [];
63+
if (null === $this->getFieldNodeSelections($fieldNode)) {
6564
$query = $fieldNode->selectionSet->selections;
65+
$selectedFields = [];
6666
/** @var FieldNode $field */
6767
foreach ($query as $field) {
6868
if ($field->kind === 'InlineFragment') {
6969
continue;
7070
}
71-
$this->queryFields[$fieldNode->name->value][] = $field->name->value;
71+
$selectedFields[] = $field->name->value;
7272
}
73+
$this->setSelectionsForFieldNode($fieldNode, $selectedFields);
7374
}
7475

75-
return $this->queryFields[$fieldNode->name->value];
76+
return $this->getFieldNodeSelections($fieldNode);
7677
}
7778

7879
/**
@@ -83,7 +84,7 @@ public function getQueryFields(FieldNode $fieldNode): array
8384
* @param AbstractCollection $collection
8485
* @param string $field
8586
*/
86-
private function addFieldToCollection(AbstractCollection $collection, string $field)
87+
private function addFieldToCollection(AbstractCollection $collection, string $field): void
8788
{
8889
$attribute = isset($this->fieldToAttributeMap[$field]) ? $this->fieldToAttributeMap[$field] : $field;
8990

@@ -99,4 +100,28 @@ private function addFieldToCollection(AbstractCollection $collection, string $fi
99100
}
100101
}
101102
}
103+
104+
/**
105+
* Get the fields selections for a query node
106+
*
107+
* @param FieldNode $fieldNode
108+
* @return array|null
109+
*/
110+
private function getFieldNodeSelections(FieldNode $fieldNode): ?array
111+
{
112+
return $this->queryFields[$fieldNode->name->value][$fieldNode->name->loc->start] ?? null;
113+
}
114+
115+
/**
116+
* Set the field selections for a query node
117+
*
118+
* Index nodes by name and position so nodes with same name don't collide
119+
*
120+
* @param FieldNode $fieldNode
121+
* @param array $selectedFields
122+
*/
123+
private function setSelectionsForFieldNode(FieldNode $fieldNode, array $selectedFields): void
124+
{
125+
$this->queryFields[$fieldNode->name->value][$fieldNode->name->loc->start] = $selectedFields;
126+
}
102127
}

app/code/Magento/CatalogGraphQl/Model/Category/CategoryFilter.php

Lines changed: 77 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,20 @@
77

88
namespace Magento\CatalogGraphQl\Model\Category;
99

10+
use Magento\Catalog\Api\CategoryListInterface;
1011
use Magento\Catalog\Api\Data\CategoryInterface;
11-
use Magento\Catalog\Model\ResourceModel\Category\Collection;
1212
use Magento\Framework\App\Config\ScopeConfigInterface;
1313
use Magento\Framework\Exception\InputException;
14+
use Magento\Framework\GraphQl\Exception\GraphQlInputException;
15+
use Magento\Framework\GraphQl\Query\Resolver\Argument\SearchCriteria\ArgumentApplier\Filter;
16+
use Magento\Framework\Search\Adapter\Mysql\Query\Builder\Match;
17+
use Magento\Search\Model\Query;
1418
use Magento\Store\Api\Data\StoreInterface;
1519
use Magento\Store\Model\ScopeInterface;
16-
use Magento\Search\Model\Query;
20+
use Magento\Framework\GraphQl\Query\Resolver\Argument\SearchCriteria\Builder;
1721

1822
/**
19-
* Category filter allows to filter collection using 'id, url_key, name' from search criteria.
23+
* Category filter allows filtering category results by attributes.
2024
*/
2125
class CategoryFilter
2226
{
@@ -25,78 +29,106 @@ class CategoryFilter
2529
*/
2630
private $scopeConfig;
2731

32+
/**
33+
* @var CategoryListInterface
34+
*/
35+
private $categoryList;
36+
37+
/**
38+
* @var Builder
39+
*/
40+
private $searchCriteriaBuilder;
41+
2842
/**
2943
* @param ScopeConfigInterface $scopeConfig
44+
* @param CategoryListInterface $categoryList
45+
* @param Builder $searchCriteriaBuilder
3046
*/
3147
public function __construct(
32-
ScopeConfigInterface $scopeConfig
48+
ScopeConfigInterface $scopeConfig,
49+
CategoryListInterface $categoryList,
50+
Builder $searchCriteriaBuilder
3351
) {
3452
$this->scopeConfig = $scopeConfig;
53+
$this->categoryList = $categoryList;
54+
$this->searchCriteriaBuilder = $searchCriteriaBuilder;
3555
}
3656

3757
/**
38-
* Filter for filtering the requested categories id's based on url_key, ids, name in the result.
58+
* Search for categories
3959
*
40-
* @param array $args
41-
* @param Collection $categoryCollection
60+
* @param array $criteria
4261
* @param StoreInterface $store
62+
* @return int[]
4363
* @throws InputException
4464
*/
45-
public function applyFilters(array $args, Collection $categoryCollection, StoreInterface $store)
65+
public function getResult(array $criteria, StoreInterface $store)
4666
{
47-
$categoryCollection->addAttributeToFilter(CategoryInterface::KEY_IS_ACTIVE, ['eq' => 1]);
48-
foreach ($args['filters'] as $field => $cond) {
49-
foreach ($cond as $condType => $value) {
50-
if ($field === 'ids') {
51-
$categoryCollection->addIdFilter($value);
52-
} else {
53-
$this->addAttributeFilter($categoryCollection, $field, $condType, $value, $store);
54-
}
55-
}
67+
$categoryIds = [];
68+
$criteria[Filter::ARGUMENT_NAME] = $this->formatMatchFilters($criteria['filters'], $store);
69+
$criteria[Filter::ARGUMENT_NAME][CategoryInterface::KEY_IS_ACTIVE] = ['eq' => 1];
70+
$searchCriteria = $this->searchCriteriaBuilder->build('categoryList', $criteria);
71+
$pageSize = $criteria['pageSize'] ?? 20;
72+
$currentPage = $criteria['currentPage'] ?? 1;
73+
$searchCriteria->setPageSize($pageSize)->setCurrentPage($currentPage);
74+
75+
$categories = $this->categoryList->getList($searchCriteria);
76+
foreach ($categories->getItems() as $category) {
77+
$categoryIds[] = (int)$category->getId();
5678
}
57-
}
5879

59-
/**
60-
* Add filter to category collection
61-
*
62-
* @param Collection $categoryCollection
63-
* @param string $field
64-
* @param string $condType
65-
* @param string|array $value
66-
* @param StoreInterface $store
67-
* @throws InputException
68-
*/
69-
private function addAttributeFilter($categoryCollection, $field, $condType, $value, $store)
70-
{
71-
if ($condType === 'match') {
72-
$this->addMatchFilter($categoryCollection, $field, $value, $store);
73-
return;
80+
$totalPages = 0;
81+
if ($categories->getTotalCount() > 0 && $searchCriteria->getPageSize() > 0) {
82+
$totalPages = ceil($categories->getTotalCount() / $searchCriteria->getPageSize());
7483
}
75-
$categoryCollection->addAttributeToFilter($field, [$condType => $value]);
84+
if ($searchCriteria->getCurrentPage() > $totalPages && $categories->getTotalCount() > 0) {
85+
throw new GraphQlInputException(
86+
__(
87+
'currentPage value %1 specified is greater than the %2 page(s) available.',
88+
[$searchCriteria->getCurrentPage(), $totalPages]
89+
)
90+
);
91+
}
92+
93+
return [
94+
'category_ids' => $categoryIds,
95+
'total_count' => $categories->getTotalCount(),
96+
'page_info' => [
97+
'total_pages' => $totalPages,
98+
'page_size' => $searchCriteria->getPageSize(),
99+
'current_page' => $searchCriteria->getCurrentPage(),
100+
]
101+
];
76102
}
77103

78104
/**
79-
* Add match filter to collection
105+
* Format match filters to behave like fuzzy match
80106
*
81-
* @param Collection $categoryCollection
82-
* @param string $field
83-
* @param string $value
107+
* @param array $filters
84108
* @param StoreInterface $store
109+
* @return array
85110
* @throws InputException
86111
*/
87-
private function addMatchFilter($categoryCollection, $field, $value, $store)
112+
private function formatMatchFilters(array $filters, StoreInterface $store): array
88113
{
89114
$minQueryLength = $this->scopeConfig->getValue(
90115
Query::XML_PATH_MIN_QUERY_LENGTH,
91116
ScopeInterface::SCOPE_STORE,
92117
$store
93118
);
94-
$searchValue = str_replace('%', '', $value);
95-
$matchLength = strlen($searchValue);
96-
if ($matchLength < $minQueryLength) {
97-
throw new InputException(__('Invalid match filter'));
98-
}
99119

100-
$categoryCollection->addAttributeToFilter($field, ['like' => "%{$searchValue}%"]);
120+
foreach ($filters as $filter => $condition) {
121+
$conditionType = current(array_keys($condition));
122+
if ($conditionType === 'match') {
123+
$searchValue = trim(str_replace(Match::SPECIAL_CHARACTERS, '', $condition[$conditionType]));
124+
$matchLength = strlen($searchValue);
125+
if ($matchLength < $minQueryLength) {
126+
throw new InputException(__('Invalid match filter. Minimum length is %1.', $minQueryLength));
127+
}
128+
unset($filters[$filter]['match']);
129+
$filters[$filter]['like'] = '%' . $searchValue . '%';
130+
}
131+
}
132+
return $filters;
101133
}
102134
}
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
declare(strict_types=1);
7+
8+
namespace Magento\CatalogGraphQl\Model\Category;
9+
10+
use Magento\Framework\GraphQl\ConfigInterface;
11+
use Magento\Framework\GraphQl\Query\Resolver\Argument\FieldEntityAttributesInterface;
12+
13+
/**
14+
* Retrieve filterable attributes for Category queries
15+
*/
16+
class CategoryFilterAttributesForAst implements FieldEntityAttributesInterface
17+
{
18+
/**
19+
* Map schema fields to entity attributes
20+
*
21+
* @var array
22+
*/
23+
private $fieldMapping = [
24+
'ids' => 'entity_id'
25+
];
26+
27+
/**
28+
* @var array
29+
*/
30+
private $additionalFields = [
31+
'is_active'
32+
];
33+
34+
/**
35+
* @var ConfigInterface
36+
*/
37+
private $config;
38+
39+
/**
40+
* @param ConfigInterface $config
41+
* @param array $additionalFields
42+
* @param array $attributeFieldMapping
43+
*/
44+
public function __construct(
45+
ConfigInterface $config,
46+
array $additionalFields = [],
47+
array $attributeFieldMapping = []
48+
) {
49+
$this->config = $config;
50+
$this->additionalFields = array_merge($this->additionalFields, $additionalFields);
51+
$this->fieldMapping = array_merge($this->fieldMapping, $attributeFieldMapping);
52+
}
53+
54+
/**
55+
* @inheritdoc
56+
*
57+
* Gather attributes for Category filtering
58+
* Example format ['attributeNameInGraphQl' => ['type' => 'String'. 'fieldName' => 'attributeNameInSearchCriteria']]
59+
*
60+
* @return array
61+
*/
62+
public function getEntityAttributes() : array
63+
{
64+
$categoryFilterType = $this->config->getConfigElement('CategoryFilterInput');
65+
66+
if (!$categoryFilterType) {
67+
throw new \LogicException(__("CategoryFilterInput type not defined in schema."));
68+
}
69+
70+
$fields = [];
71+
foreach ($categoryFilterType->getFields() as $field) {
72+
$fields[$field->getName()] = [
73+
'type' => 'String',
74+
'fieldName' => $this->fieldMapping[$field->getName()] ?? $field->getName(),
75+
];
76+
}
77+
78+
foreach ($this->additionalFields as $additionalField) {
79+
$fields[$additionalField] = [
80+
'type' => 'String',
81+
'fieldName' => $additionalField,
82+
];
83+
}
84+
85+
return $fields;
86+
}
87+
}

0 commit comments

Comments
 (0)