Skip to content

Commit ba9bfd5

Browse files
Merge remote-tracking branch 'remotes/github/2.3-develop' into EPAM-PR-85
2 parents 1f8c6cf + fc387f4 commit ba9bfd5

File tree

45 files changed

+2624
-257
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

45 files changed

+2624
-257
lines changed

app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminCategoryActionGroup.xml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,18 @@
191191
<click selector="{{AdminCategorySidebarTreeSection.expandAll}}" stepKey="expandToSeeAllCategories"/>
192192
<dontSee selector="{{AdminCategorySidebarTreeSection.categoryInTree(categoryEntity.name)}}" stepKey="dontSeeCategoryInTree"/>
193193
</actionGroup>
194+
<actionGroup name="AdminDeleteCategoryByName" extends="DeleteCategory">
195+
<arguments>
196+
<argument name="categoryName" type="string" defaultValue="category1"/>
197+
</arguments>
198+
<remove keyForRemoval="clickCategoryLink"/>
199+
<remove keyForRemoval="dontSeeCategoryInTree"/>
200+
<remove keyForRemoval="expandToSeeAllCategories"/>
201+
<conditionalClick selector="{{AdminCategorySidebarTreeSection.expandAll}}" dependentSelector="{{AdminCategorySidebarTreeSection.categoryByName(categoryName)}}" visible="false" stepKey="expandCategories" after="waitForCategoryPageLoad"/>
202+
<click selector="{{AdminCategorySidebarTreeSection.categoryByName(categoryName)}}" stepKey="clickCategory" after="expandCategories"/>
203+
<conditionalClick selector="{{AdminCategorySidebarTreeSection.expandAll}}" dependentSelector="{{AdminCategorySidebarTreeSection.categoryByName(categoryName)}}" visible="false" stepKey="expandCategoriesToSeeAll" after="seeDeleteSuccess"/>
204+
<dontSee selector="{{AdminCategorySidebarTreeSection.categoryByName(categoryName)}}" stepKey="dontSeeCategory" after="expandCategoriesToSeeAll"/>
205+
</actionGroup>
194206

195207
<!-- Actions to fill out a new category from the product page-->
196208
<!-- The action assumes that you are already on an admin product configuration page -->

app/code/Magento/Catalog/Test/Mftf/Section/AdminCategorySidebarTreeSection.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,5 +19,6 @@
1919
<element name="lastCreatedCategory" type="block" selector=".x-tree-root-ct li li:last-child" />
2020
<element name="treeContainer" type="block" selector=".tree-holder" />
2121
<element name="expandRootCategory" type="text" selector="img.x-tree-elbow-end-plus"/>
22+
<element name="categoryByName" type="text" selector="//div[contains(@class, 'categories-side-col')]//a/span[contains(text(), '{{categoryName}}')]" parameterized="true" timeout="30"/>
2223
</section>
2324
</sections>

app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFormSection.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
99
xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd">
1010
<section name="AdminProductFormSection">
11+
<element name="datepickerNewAttribute" type="input" selector="[data-index='{{attrName}}'] input" timeout="30" parameterized="true"/>
1112
<element name="attributeSet" type="select" selector="div[data-index='attribute_set_id'] .admin__field-control"/>
1213
<element name="attributeSetFilter" type="input" selector="div[data-index='attribute_set_id'] .admin__field-control input" timeout="30"/>
1314
<element name="attributeSetFilterResult" type="input" selector="div[data-index='attribute_set_id'] .action-menu-item._last" timeout="30"/>
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
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\Catalog\Ui\DataProvider\Product\Modifier;
9+
10+
use Magento\Framework\Escaper;
11+
use Magento\Ui\DataProvider\Modifier\ModifierInterface;
12+
13+
/**
14+
* Modify product listing attributes
15+
*/
16+
class Attributes implements ModifierInterface
17+
{
18+
/**
19+
* @var Escaper
20+
*/
21+
private $escaper;
22+
23+
/**
24+
* @var array
25+
*/
26+
private $escapeAttributes;
27+
28+
/**
29+
* @param Escaper $escaper
30+
* @param array $escapeAttributes
31+
*/
32+
public function __construct(
33+
Escaper $escaper,
34+
array $escapeAttributes = []
35+
) {
36+
$this->escaper = $escaper;
37+
$this->escapeAttributes = $escapeAttributes;
38+
}
39+
40+
/**
41+
* @inheritdoc
42+
*/
43+
public function modifyData(array $data)
44+
{
45+
if (!empty($data) && !empty($this->escapeAttributes)) {
46+
foreach ($data['items'] as &$item) {
47+
foreach ($this->escapeAttributes as $escapeAttribute) {
48+
if (isset($item[$escapeAttribute])) {
49+
$item[$escapeAttribute] = $this->escaper->escapeHtml($item[$escapeAttribute]);
50+
}
51+
}
52+
}
53+
}
54+
return $data;
55+
}
56+
57+
/**
58+
* @inheritdoc
59+
*/
60+
public function modifyMeta(array $meta)
61+
{
62+
return $meta;
63+
}
64+
}

app/code/Magento/Catalog/etc/adminhtml/di.xml

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,24 @@
166166
<argument name="pool" xsi:type="object">Magento\Catalog\Ui\DataProvider\Product\Form\Modifier\Pool</argument>
167167
</arguments>
168168
</type>
169-
<virtualType name="Magento\Catalog\Ui\DataProvider\Product\Listing\Modifier\Pool" type="Magento\Ui\DataProvider\Modifier\Pool"/>
169+
<virtualType name="Magento\Catalog\Ui\DataProvider\Product\Listing\Modifier\Pool" type="Magento\Ui\DataProvider\Modifier\Pool">
170+
<arguments>
171+
<argument name="modifiers" xsi:type="array">
172+
<item name="attributes" xsi:type="array">
173+
<item name="class" xsi:type="string">Magento\Catalog\Ui\DataProvider\Product\Modifier\Attributes</item>
174+
<item name="sortOrder" xsi:type="number">10</item>
175+
</item>
176+
</argument>
177+
</arguments>
178+
</virtualType>
179+
<type name="Magento\Catalog\Ui\DataProvider\Product\Modifier\Attributes">
180+
<arguments>
181+
<argument name="escapeAttributes" xsi:type="array">
182+
<item name="name" xsi:type="string">name</item>
183+
<item name="sku" xsi:type="string">sku</item>
184+
</argument>
185+
</arguments>
186+
</type>
170187
<type name="Magento\Catalog\Ui\DataProvider\Product\Form\Modifier\CustomOptions">
171188
<arguments>
172189
<argument name="scopeName" xsi:type="string">product_form.product_form</argument>
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
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\Catalog\Api\Data\CategoryInterface;
11+
use Magento\Catalog\Model\ResourceModel\Category\Collection;
12+
use Magento\Framework\App\Config\ScopeConfigInterface;
13+
use Magento\Framework\Exception\InputException;
14+
use Magento\Store\Api\Data\StoreInterface;
15+
use Magento\Store\Model\ScopeInterface;
16+
use Magento\Search\Model\Query;
17+
18+
/**
19+
* Category filter allows to filter collection using 'id, url_key, name' from search criteria.
20+
*/
21+
class CategoryFilter
22+
{
23+
/**
24+
* @var ScopeConfigInterface
25+
*/
26+
private $scopeConfig;
27+
28+
/**
29+
* @param ScopeConfigInterface $scopeConfig
30+
*/
31+
public function __construct(
32+
ScopeConfigInterface $scopeConfig
33+
) {
34+
$this->scopeConfig = $scopeConfig;
35+
}
36+
37+
/**
38+
* Filter for filtering the requested categories id's based on url_key, ids, name in the result.
39+
*
40+
* @param array $args
41+
* @param Collection $categoryCollection
42+
* @param StoreInterface $store
43+
* @throws InputException
44+
*/
45+
public function applyFilters(array $args, Collection $categoryCollection, StoreInterface $store)
46+
{
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+
}
56+
}
57+
}
58+
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;
74+
}
75+
$categoryCollection->addAttributeToFilter($field, [$condType => $value]);
76+
}
77+
78+
/**
79+
* Add match filter to collection
80+
*
81+
* @param Collection $categoryCollection
82+
* @param string $field
83+
* @param string $value
84+
* @param StoreInterface $store
85+
* @throws InputException
86+
*/
87+
private function addMatchFilter($categoryCollection, $field, $value, $store)
88+
{
89+
$minQueryLength = $this->scopeConfig->getValue(
90+
Query::XML_PATH_MIN_QUERY_LENGTH,
91+
ScopeInterface::SCOPE_STORE,
92+
$store
93+
);
94+
$searchValue = str_replace('%', '', $value);
95+
$matchLength = strlen($searchValue);
96+
if ($matchLength < $minQueryLength) {
97+
throw new InputException(__('Invalid match filter'));
98+
}
99+
100+
$categoryCollection->addAttributeToFilter($field, ['like' => "%{$searchValue}%"]);
101+
}
102+
}
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
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\Resolver;
9+
10+
use Magento\CatalogGraphQl\Model\Resolver\Products\DataProvider\ExtractDataFromCategoryTree;
11+
use Magento\Framework\Exception\InputException;
12+
use Magento\Framework\GraphQl\Config\Element\Field;
13+
use Magento\Framework\GraphQl\Exception\GraphQlNoSuchEntityException;
14+
use Magento\Framework\GraphQl\Query\ResolverInterface;
15+
use Magento\Framework\GraphQl\Schema\Type\ResolveInfo;
16+
use Magento\CatalogGraphQl\Model\Resolver\Products\DataProvider\CategoryTree;
17+
use Magento\CatalogGraphQl\Model\Category\CategoryFilter;
18+
use Magento\Catalog\Model\ResourceModel\Category\CollectionFactory;
19+
20+
/**
21+
* Category List resolver, used for GraphQL category data request processing.
22+
*/
23+
class CategoryList implements ResolverInterface
24+
{
25+
/**
26+
* @var CategoryTree
27+
*/
28+
private $categoryTree;
29+
30+
/**
31+
* @var CollectionFactory
32+
*/
33+
private $collectionFactory;
34+
35+
/**
36+
* @var CategoryFilter
37+
*/
38+
private $categoryFilter;
39+
40+
/**
41+
* @var ExtractDataFromCategoryTree
42+
*/
43+
private $extractDataFromCategoryTree;
44+
45+
/**
46+
* @param CategoryTree $categoryTree
47+
* @param ExtractDataFromCategoryTree $extractDataFromCategoryTree
48+
* @param CategoryFilter $categoryFilter
49+
* @param CollectionFactory $collectionFactory
50+
*/
51+
public function __construct(
52+
CategoryTree $categoryTree,
53+
ExtractDataFromCategoryTree $extractDataFromCategoryTree,
54+
CategoryFilter $categoryFilter,
55+
CollectionFactory $collectionFactory
56+
) {
57+
$this->categoryTree = $categoryTree;
58+
$this->extractDataFromCategoryTree = $extractDataFromCategoryTree;
59+
$this->categoryFilter = $categoryFilter;
60+
$this->collectionFactory = $collectionFactory;
61+
}
62+
63+
/**
64+
* @inheritdoc
65+
*/
66+
public function resolve(Field $field, $context, ResolveInfo $info, array $value = null, array $args = null)
67+
{
68+
if (isset($value[$field->getName()])) {
69+
return $value[$field->getName()];
70+
}
71+
$store = $context->getExtensionAttributes()->getStore();
72+
73+
$rootCategoryIds = [];
74+
if (!isset($args['filters'])) {
75+
$rootCategoryIds[] = (int)$store->getRootCategoryId();
76+
} else {
77+
$categoryCollection = $this->collectionFactory->create();
78+
try {
79+
$this->categoryFilter->applyFilters($args, $categoryCollection, $store);
80+
} catch (InputException $e) {
81+
return [];
82+
}
83+
84+
foreach ($categoryCollection as $category) {
85+
$rootCategoryIds[] = (int)$category->getId();
86+
}
87+
}
88+
89+
$result = $this->fetchCategories($rootCategoryIds, $info);
90+
return $result;
91+
}
92+
93+
/**
94+
* Fetch category tree data
95+
*
96+
* @param array $categoryIds
97+
* @param ResolveInfo $info
98+
* @return array
99+
* @throws GraphQlNoSuchEntityException
100+
*/
101+
private function fetchCategories(array $categoryIds, ResolveInfo $info)
102+
{
103+
$fetchedCategories = [];
104+
foreach ($categoryIds as $categoryId) {
105+
$categoryTree = $this->categoryTree->getTree($info, $categoryId);
106+
if (empty($categoryTree)) {
107+
continue;
108+
}
109+
$fetchedCategories[] = current($this->extractDataFromCategoryTree->execute($categoryTree));
110+
}
111+
112+
return $fetchedCategories;
113+
}
114+
}

app/code/Magento/CatalogGraphQl/Model/Resolver/Products/Query/FieldSelection.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -60,9 +60,9 @@ private function getProductFields(ResolveInfo $info): array
6060
$fieldNames[] = $this->collectProductFieldNames($selection, $fieldNames);
6161
}
6262
}
63-
64-
$fieldNames = array_merge(...$fieldNames);
65-
63+
if (!empty($fieldNames)) {
64+
$fieldNames = array_merge(...$fieldNames);
65+
}
6666
return $fieldNames;
6767
}
6868

app/code/Magento/CatalogGraphQl/etc/schema.graphqls

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,12 @@ type Query {
1111
): Products
1212
@resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\Products") @doc(description: "The products query searches for products that match the criteria specified in the search and filter attributes.") @cache(cacheIdentity: "Magento\\CatalogGraphQl\\Model\\Resolver\\Product\\Identity")
1313
category (
14-
id: Int @doc(description: "Id of the category.")
14+
id: Int @doc(description: "Id of the category.")
1515
): CategoryTree
16-
@resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\CategoryTree") @doc(description: "The category query searches for categories that match the criteria specified in the search and filter attributes.") @cache(cacheIdentity: "Magento\\CatalogGraphQl\\Model\\Resolver\\Category\\CategoryTreeIdentity")
16+
@resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\CategoryTree") @doc(description: "The category query searches for categories that match the criteria specified in the search and filter attributes.") @deprecated(reason: "Use 'categoryList' query instead of 'category' query") @cache(cacheIdentity: "Magento\\CatalogGraphQl\\Model\\Resolver\\Category\\CategoryTreeIdentity")
17+
categoryList(
18+
filters: CategoryFilterInput @doc(description: "Identifies which Category filter inputs to search for and return.")
19+
): [CategoryTree] @doc(description: "Returns an array of categories based on the specified filters.") @resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\CategoryList") @cache(cacheIdentity: "Magento\\CatalogGraphQl\\Model\\Resolver\\Category\\CategoriesIdentity")
1720
}
1821

1922
type Price @doc(description: "Price is deprecated, replaced by ProductPrice. The Price object defines the price of a product as well as any tax-related adjustments.") {
@@ -294,6 +297,13 @@ input ProductAttributeFilterInput @doc(description: "ProductAttributeFilterInput
294297
category_id: FilterEqualTypeInput @doc(description: "Filter product by category id")
295298
}
296299

300+
input CategoryFilterInput @doc(description: "CategoryFilterInput defines the filters to be used in the search. A filter contains at least one attribute, a comparison operator, and the value that is being searched for.")
301+
{
302+
ids: FilterEqualTypeInput @doc(description: "Filter by category ID that uniquely identifies the category.")
303+
url_key: FilterEqualTypeInput @doc(description: "Filter by the part of the URL that identifies the category")
304+
name: FilterMatchTypeInput @doc(description: "Filter by the display name of the category.")
305+
}
306+
297307
input ProductFilterInput @doc(description: "ProductFilterInput is deprecated, use @ProductAttributeFilterInput instead. ProductFilterInput defines the filters to be used in the search. A filter contains at least one attribute, a comparison operator, and the value that is being searched for.") {
298308
name: FilterTypeInput @doc(description: "The product name. Customers use this name to identify the product.")
299309
sku: FilterTypeInput @doc(description: "A number or code assigned to a product to identify the product, options, price, and manufacturer.")

0 commit comments

Comments
 (0)