select()
->from(['e' => $entityTableName], $entityTableAttributes)
- ->where('e.entity_id IN (?)', $entityIds);
+ ->where('e.entity_id IN (?)', $entityIds, \Zend_Db::INT_TYPE);
return $select;
}
diff --git a/app/code/Magento/CatalogGraphQl/Model/Category/CategoryUidsArgsProcessor.php b/app/code/Magento/CatalogGraphQl/Model/Category/CategoryUidsArgsProcessor.php
new file mode 100644
index 0000000000000..e091be32698c7
--- /dev/null
+++ b/app/code/Magento/CatalogGraphQl/Model/Category/CategoryUidsArgsProcessor.php
@@ -0,0 +1,69 @@
+uidEncoder = $uidEncoder;
+ }
+
+ /**
+ * Composite processor that loops through available processors for arguments that come from graphql input
+ *
+ * @param string $fieldName,
+ * @param array $args
+ * @return array
+ * @throws GraphQlInputException
+ */
+ public function process(
+ string $fieldName,
+ array $args
+ ): array {
+ $filterKey = 'filters';
+ $idFilter = $args[$filterKey][self::ID] ?? [];
+ $uidFilter = $args[$filterKey][self::UID] ?? [];
+ if (!empty($idFilter)
+ && !empty($uidFilter)
+ && ($fieldName === 'categories' || $fieldName === 'categoryList')) {
+ throw new GraphQlInputException(
+ __('`%1` and `%2` can\'t be used at the same time.', [self::ID, self::UID])
+ );
+ } elseif (!empty($uidFilter)) {
+ if (isset($uidFilter['eq'])) {
+ $args[$filterKey][self::ID]['eq'] = $this->uidEncoder->decode(
+ $uidFilter['eq']
+ );
+ } elseif (!empty($uidFilter['in'])) {
+ foreach ($uidFilter['in'] as $uids) {
+ $args[$filterKey][self::ID]['in'][] = $this->uidEncoder->decode($uids);
+ }
+ }
+ unset($args[$filterKey][self::UID]);
+ }
+ return $args;
+ }
+}
diff --git a/app/code/Magento/CatalogGraphQl/Model/Category/Hydrator.php b/app/code/Magento/CatalogGraphQl/Model/Category/Hydrator.php
index d2c1fc8f7be9f..675118b953102 100644
--- a/app/code/Magento/CatalogGraphQl/Model/Category/Hydrator.php
+++ b/app/code/Magento/CatalogGraphQl/Model/Category/Hydrator.php
@@ -10,6 +10,8 @@
use Magento\Catalog\Api\Data\CategoryInterface;
use Magento\Catalog\Model\Category;
use Magento\CatalogGraphQl\Model\Resolver\Products\DataProvider\CustomAttributesFlattener;
+use Magento\Framework\App\ObjectManager;
+use Magento\Framework\GraphQl\Query\Uid;
use Magento\Framework\Reflection\DataObjectProcessor;
/**
@@ -27,16 +29,23 @@ class Hydrator
*/
private $dataObjectProcessor;
+ /** @var Uid */
+ private $uidEncoder;
+
/**
* @param CustomAttributesFlattener $flattener
* @param DataObjectProcessor $dataObjectProcessor
+ * @param Uid|null $uidEncoder
*/
public function __construct(
CustomAttributesFlattener $flattener,
- DataObjectProcessor $dataObjectProcessor
+ DataObjectProcessor $dataObjectProcessor,
+ Uid $uidEncoder = null
) {
$this->flattener = $flattener;
$this->dataObjectProcessor = $dataObjectProcessor;
+ $this->uidEncoder = $uidEncoder ?: ObjectManager::getInstance()
+ ->get(Uid::class);
}
/**
@@ -54,6 +63,7 @@ public function hydrateCategory(Category $category, $basicFieldsOnly = false) :
$categoryData = $this->dataObjectProcessor->buildOutputDataArray($category, CategoryInterface::class);
}
$categoryData['id'] = $category->getId();
+ $categoryData['uid'] = $this->uidEncoder->encode((string) $category->getId());
$categoryData['children'] = [];
$categoryData['available_sort_by'] = $category->getAvailableSortBy();
$categoryData['model'] = $category;
diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/CategoriesQuery.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/CategoriesQuery.php
index 4d7ce13fd23cc..0e653995ebcab 100644
--- a/app/code/Magento/CatalogGraphQl/Model/Resolver/CategoriesQuery.php
+++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/CategoriesQuery.php
@@ -7,14 +7,15 @@
namespace Magento\CatalogGraphQl\Model\Resolver;
+use Magento\CatalogGraphQl\Model\Category\CategoryFilter;
+use Magento\CatalogGraphQl\Model\Resolver\Products\DataProvider\CategoryTree;
use Magento\CatalogGraphQl\Model\Resolver\Products\DataProvider\ExtractDataFromCategoryTree;
use Magento\Framework\Exception\InputException;
use Magento\Framework\GraphQl\Config\Element\Field;
use Magento\Framework\GraphQl\Exception\GraphQlInputException;
+use Magento\Framework\GraphQl\Query\Resolver\ArgumentsProcessorInterface;
use Magento\Framework\GraphQl\Query\ResolverInterface;
use Magento\Framework\GraphQl\Schema\Type\ResolveInfo;
-use Magento\CatalogGraphQl\Model\Resolver\Products\DataProvider\CategoryTree;
-use Magento\CatalogGraphQl\Model\Category\CategoryFilter;
/**
* Categories resolver, used for GraphQL category data request processing.
@@ -36,19 +37,27 @@ class CategoriesQuery implements ResolverInterface
*/
private $extractDataFromCategoryTree;
+ /**
+ * @var ArgumentsProcessorInterface
+ */
+ private $argsSelection;
+
/**
* @param CategoryTree $categoryTree
* @param ExtractDataFromCategoryTree $extractDataFromCategoryTree
* @param CategoryFilter $categoryFilter
+ * @param ArgumentsProcessorInterface $argsSelection
*/
public function __construct(
CategoryTree $categoryTree,
ExtractDataFromCategoryTree $extractDataFromCategoryTree,
- CategoryFilter $categoryFilter
+ CategoryFilter $categoryFilter,
+ ArgumentsProcessorInterface $argsSelection
) {
$this->categoryTree = $categoryTree;
$this->extractDataFromCategoryTree = $extractDataFromCategoryTree;
$this->categoryFilter = $categoryFilter;
+ $this->argsSelection = $argsSelection;
}
/**
@@ -70,7 +79,8 @@ public function resolve(Field $field, $context, ResolveInfo $info, array $value
}
try {
- $filterResult = $this->categoryFilter->getResult($args, $store, [], $context);
+ $processedArgs = $this->argsSelection->process($info->fieldName, $args);
+ $filterResult = $this->categoryFilter->getResult($processedArgs, $store, [], $context);
} catch (InputException $e) {
throw new GraphQlInputException(__($e->getMessage()));
}
diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/Category/DataProvider/Breadcrumbs.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/Category/DataProvider/Breadcrumbs.php
index dcd6f816088dd..04c1754e69eb8 100644
--- a/app/code/Magento/CatalogGraphQl/Model/Resolver/Category/DataProvider/Breadcrumbs.php
+++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/Category/DataProvider/Breadcrumbs.php
@@ -9,6 +9,8 @@
use Magento\Catalog\Api\Data\CategoryInterface;
use Magento\Catalog\Model\ResourceModel\Category\CollectionFactory;
+use Magento\Framework\App\ObjectManager;
+use Magento\Framework\GraphQl\Query\Uid;
/**
* Breadcrumbs data provider
@@ -20,13 +22,20 @@ class Breadcrumbs
*/
private $collectionFactory;
+ /** @var Uid */
+ private $uidEncoder;
+
/**
* @param CollectionFactory $collectionFactory
+ * @param Uid|null $uidEncoder
*/
public function __construct(
- CollectionFactory $collectionFactory
+ CollectionFactory $collectionFactory,
+ Uid $uidEncoder = null
) {
$this->collectionFactory = $collectionFactory;
+ $this->uidEncoder = $uidEncoder ?: ObjectManager::getInstance()
+ ->get(Uid::class);
}
/**
@@ -52,6 +61,7 @@ public function getData(string $categoryPath): array
foreach ($collection as $category) {
$breadcrumbsData[] = [
'category_id' => $category->getId(),
+ 'category_uid' => $this->uidEncoder->encode((string) $category->getId()),
'category_name' => $category->getName(),
'category_level' => $category->getLevel(),
'category_url_key' => $category->getUrlKey(),
diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/CategoryList.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/CategoryList.php
index 13db03bb2766b..747e05806a821 100644
--- a/app/code/Magento/CatalogGraphQl/Model/Resolver/CategoryList.php
+++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/CategoryList.php
@@ -7,14 +7,15 @@
namespace Magento\CatalogGraphQl\Model\Resolver;
+use Magento\CatalogGraphQl\Model\Category\CategoryFilter;
+use Magento\CatalogGraphQl\Model\Resolver\Products\DataProvider\CategoryTree;
use Magento\CatalogGraphQl\Model\Resolver\Products\DataProvider\ExtractDataFromCategoryTree;
use Magento\Framework\Exception\InputException;
use Magento\Framework\GraphQl\Config\Element\Field;
use Magento\Framework\GraphQl\Exception\GraphQlInputException;
+use Magento\Framework\GraphQl\Query\Resolver\ArgumentsProcessorInterface;
use Magento\Framework\GraphQl\Query\ResolverInterface;
use Magento\Framework\GraphQl\Schema\Type\ResolveInfo;
-use Magento\CatalogGraphQl\Model\Resolver\Products\DataProvider\CategoryTree;
-use Magento\CatalogGraphQl\Model\Category\CategoryFilter;
/**
* Category List resolver, used for GraphQL category data request processing.
@@ -36,19 +37,27 @@ class CategoryList implements ResolverInterface
*/
private $extractDataFromCategoryTree;
+ /**
+ * @var ArgumentsProcessorInterface
+ */
+ private $argsSelection;
+
/**
* @param CategoryTree $categoryTree
* @param ExtractDataFromCategoryTree $extractDataFromCategoryTree
* @param CategoryFilter $categoryFilter
+ * @param ArgumentsProcessorInterface $argsSelection
*/
public function __construct(
CategoryTree $categoryTree,
ExtractDataFromCategoryTree $extractDataFromCategoryTree,
- CategoryFilter $categoryFilter
+ CategoryFilter $categoryFilter,
+ ArgumentsProcessorInterface $argsSelection
) {
$this->categoryTree = $categoryTree;
$this->extractDataFromCategoryTree = $extractDataFromCategoryTree;
$this->categoryFilter = $categoryFilter;
+ $this->argsSelection = $argsSelection;
}
/**
@@ -65,7 +74,9 @@ public function resolve(Field $field, $context, ResolveInfo $info, array $value
$args['filters']['ids'] = ['eq' => $store->getRootCategoryId()];
}
try {
- $filterResults = $this->categoryFilter->getResult($args, $store, [], $context);
+ $processedArgs = $this->argsSelection->process($info->fieldName, $args);
+ $filterResults = $this->categoryFilter->getResult($processedArgs, $store, [], $context);
+
$rootCategoryIds = $filterResults['category_ids'];
} catch (InputException $e) {
throw new GraphQlInputException(__($e->getMessage()));
diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/Product/EntityIdToId.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/Product/EntityIdToId.php
index 701ee70204486..2cfb78418bbae 100644
--- a/app/code/Magento/CatalogGraphQl/Model/Resolver/Product/EntityIdToId.php
+++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/Product/EntityIdToId.php
@@ -16,9 +16,10 @@
use Magento\Framework\GraphQl\Query\ResolverInterface;
/**
- * @inheritdoc
- *
* Fixed the id related data in the product data
+ *
+ * @deprecated Use UID
+ * @see \Magento\CatalogGraphQl\Model\Resolver\Product\EntityIdToUid
*/
class EntityIdToId implements ResolverInterface
{
diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/Product/EntityIdToUid.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/Product/EntityIdToUid.php
new file mode 100644
index 0000000000000..90d36e3ed8c82
--- /dev/null
+++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/Product/EntityIdToUid.php
@@ -0,0 +1,67 @@
+metadataPool = $metadataPool;
+ $this->uidEncoder = $uidEncoder;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function resolve(
+ Field $field,
+ $context,
+ ResolveInfo $info,
+ array $value = null,
+ array $args = null
+ ) {
+ if (!isset($value['model'])) {
+ throw new LocalizedException(__('"model" value should be specified'));
+ }
+
+ /** @var Product $product */
+ $product = $value['model'];
+
+ $productId = $product->getData(
+ $this->metadataPool->getMetadata(ProductInterface::class)->getIdentifierField()
+ );
+
+ return $this->uidEncoder->encode((string) $productId);
+ }
+}
diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/Product/Identity.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/Product/Identity.php
index 7aec66ccb699f..aff8fa8a6fc6d 100644
--- a/app/code/Magento/CatalogGraphQl/Model/Resolver/Product/Identity.php
+++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/Product/Identity.php
@@ -8,6 +8,8 @@
namespace Magento\CatalogGraphQl\Model\Resolver\Product;
use Magento\Framework\GraphQl\Query\Resolver\IdentityInterface;
+use Magento\Catalog\Model\Category;
+use Magento\Catalog\Model\Product;
/**
* Identity for resolved products
@@ -15,7 +17,8 @@
class Identity implements IdentityInterface
{
/** @var string */
- private $cacheTag = \Magento\Catalog\Model\Product::CACHE_TAG;
+ private $cacheTagProduct = Product::CACHE_TAG;
+ private $cacheTagCategory = Category::CACHE_TAG;
/**
* Get product ids for cache tag
@@ -26,12 +29,19 @@ class Identity implements IdentityInterface
public function getIdentities(array $resolvedData): array
{
$ids = [];
+ $categories = $resolvedData['categories'] ?? [];
$items = $resolvedData['items'] ?? [];
+ foreach ($categories as $category) {
+ $ids[] = sprintf('%s_%s', $this->cacheTagCategory, $category);
+ }
+ if (!empty($categories)) {
+ array_unshift($ids, $this->cacheTagCategory);
+ }
foreach ($items as $item) {
- $ids[] = sprintf('%s_%s', $this->cacheTag, $item['entity_id']);
+ $ids[] = sprintf('%s_%s', $this->cacheTagProduct, $item['entity_id']);
}
if (!empty($ids)) {
- array_unshift($ids, $this->cacheTag);
+ array_unshift($ids, $this->cacheTagProduct);
}
return $ids;
diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/Product/MediaGalleryEntries.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/Product/MediaGalleryEntries.php
index 8843ad02320c6..3bcc69f94cda0 100644
--- a/app/code/Magento/CatalogGraphQl/Model/Resolver/Product/MediaGalleryEntries.php
+++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/Product/MediaGalleryEntries.php
@@ -9,6 +9,7 @@
use Magento\Framework\Exception\LocalizedException;
use Magento\Framework\GraphQl\Query\Resolver\ContextInterface;
+use Magento\Framework\GraphQl\Query\Uid;
use Magento\Framework\GraphQl\Schema\Type\ResolveInfo;
use Magento\Catalog\Model\Product;
use Magento\Framework\GraphQl\Config\Element\Field;
@@ -22,6 +23,17 @@
*/
class MediaGalleryEntries implements ResolverInterface
{
+ /** @var Uid */
+ private $uidEncoder;
+
+ /**
+ * Uid $uidEncoder
+ */
+ public function __construct(Uid $uidEncoder)
+ {
+ $this->uidEncoder = $uidEncoder;
+ }
+
/**
* @inheritdoc
*
@@ -53,6 +65,7 @@ public function resolve(
if (!empty($product->getMediaGalleryEntries())) {
foreach ($product->getMediaGalleryEntries() as $key => $entry) {
$mediaGalleryEntries[$key] = $entry->getData();
+ $mediaGalleryEntries[$key]['uid'] = $this->uidEncoder->encode((string) $entry->getId());
if ($entry->getExtensionAttributes() && $entry->getExtensionAttributes()->getVideoContent()) {
$mediaGalleryEntries[$key]['video_content']
= $entry->getExtensionAttributes()->getVideoContent()->getData();
diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/Product/Options.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/Product/Options.php
index 76602288039c5..f735ab846689f 100644
--- a/app/code/Magento/CatalogGraphQl/Model/Resolver/Product/Options.php
+++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/Product/Options.php
@@ -7,8 +7,10 @@
namespace Magento\CatalogGraphQl\Model\Resolver\Product;
+use Magento\Framework\App\ObjectManager;
use Magento\Framework\Exception\LocalizedException;
use Magento\Framework\GraphQl\Query\Resolver\ContextInterface;
+use Magento\Framework\GraphQl\Query\Uid;
use Magento\Framework\GraphQl\Schema\Type\ResolveInfo;
use Magento\Catalog\Model\Product;
use Magento\Catalog\Model\Product\Option;
@@ -20,6 +22,23 @@
*/
class Options implements ResolverInterface
{
+ /**
+ * Option type name
+ */
+ private const OPTION_TYPE = 'custom-option';
+
+ /** @var Uid */
+ private $uidEncoder;
+
+ /**
+ * Uid|null $uidEncoder
+ */
+ public function __construct(Uid $uidEncoder = null)
+ {
+ $this->uidEncoder = $uidEncoder ?: ObjectManager::getInstance()
+ ->get(Uid::class);
+ }
+
/**
* @inheritdoc
*
@@ -55,7 +74,9 @@ public function resolve(
$options[$key] = $option->getData();
$options[$key]['required'] = $option->getIsRequire();
$options[$key]['product_sku'] = $option->getProductSku();
-
+ $options[$key]['uid'] = $this->uidEncoder->encode(
+ self::OPTION_TYPE . '/' . $option->getOptionId()
+ );
$values = $option->getValues() ?: [];
/** @var Option\Value $value */
foreach ($values as $valueKey => $value) {
diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/Product/PriceRange.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/Product/PriceRange.php
index 805571d58d634..ed5ae433dd5b7 100644
--- a/app/code/Magento/CatalogGraphQl/Model/Resolver/Product/PriceRange.php
+++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/Product/PriceRange.php
@@ -63,6 +63,13 @@ public function resolve(
$product = $value['model'];
$product->unsetData('minimal_price');
+ if ($context) {
+ $customerGroupId = $context->getExtensionAttributes()->getCustomerGroupId();
+ if ($customerGroupId !== null) {
+ $product->setCustomerGroupId($customerGroupId);
+ }
+ }
+
$requestedFields = $info->getFieldSelection(10);
$returnArray = [];
diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/Products.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products.php
index ba158fab0120c..eebcbfba55b1f 100644
--- a/app/code/Magento/CatalogGraphQl/Model/Resolver/Products.php
+++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products.php
@@ -79,6 +79,11 @@ public function resolve(
'layer_type' => isset($args['search']) ? Resolver::CATALOG_LAYER_SEARCH : Resolver::CATALOG_LAYER_CATEGORY,
];
+ if (isset($args['filter']['category_id'])) {
+ $data['categories'] = $args['filter']['category_id']['eq'] ?? $args['filter']['category_id']['in'];
+ $data['categories'] = is_array($data['categories']) ? $data['categories'] : [$data['categories']];
+ }
+
return $data;
}
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
index 4a124d69bd20f..03e8358b1ee7a 100644
--- a/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/DataProvider/ProductSearch/ProductCollectionSearchCriteriaBuilder.php
+++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/DataProvider/ProductSearch/ProductCollectionSearchCriteriaBuilder.php
@@ -61,7 +61,7 @@ public function build(SearchCriteriaInterface $searchCriteria): SearchCriteriaIn
foreach ($filterGroup->getFilters() as $filter) {
if ($filter->getField() == CategoryProductLink::KEY_CATEGORY_ID) {
$categoryFilter = $this->filterBuilder
- ->setField($filter->getField())
+ ->setField(CategoryProductLink::KEY_CATEGORY_ID)
->setValue($filter->getValue())
->setConditionType($filter->getConditionType())
->create();
diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/Query/CategoryUidArgsProcessor.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/Query/CategoryUidArgsProcessor.php
new file mode 100644
index 0000000000000..33845c6dcce6e
--- /dev/null
+++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/Query/CategoryUidArgsProcessor.php
@@ -0,0 +1,66 @@
+uidEncoder = $uidEncoder;
+ }
+
+ /**
+ * Composite processor that loops through available processors for arguments that come from graphql input
+ *
+ * @param string $fieldName,
+ * @param array $args
+ * @return array
+ * @throws GraphQlInputException
+ */
+ public function process(
+ string $fieldName,
+ array $args
+ ): array {
+ $idFilter = $args['filter'][self::ID] ?? [];
+ $uidFilter = $args['filter'][self::UID] ?? [];
+ if (!empty($idFilter)
+ && !empty($uidFilter)
+ && $fieldName === 'products') {
+ throw new GraphQlInputException(
+ __('`%1` and `%2` can\'t be used at the same time.', [self::ID, self::UID])
+ );
+ } elseif (!empty($uidFilter)) {
+ if (isset($uidFilter['eq'])) {
+ $args['filter'][self::ID]['eq'] = $this->uidEncoder->decode((string) $uidFilter['eq']);
+ } elseif (!empty($uidFilter['in'])) {
+ foreach ($uidFilter['in'] as $uid) {
+ $args['filter'][self::ID]['in'][] = $this->uidEncoder->decode((string) $uid);
+ }
+ }
+ unset($args['filter'][self::UID]);
+ }
+ return $args;
+ }
+}
diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/Query/Filter.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/Query/Filter.php
index d70a3aa7e63c3..0f482fd12e4e3 100644
--- a/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/Query/Filter.php
+++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/Query/Filter.php
@@ -10,6 +10,7 @@
use Magento\Catalog\Model\Product;
use Magento\Framework\Api\SearchCriteriaInterface;
use Magento\Framework\Exception\InputException;
+use Magento\Framework\GraphQl\Exception\GraphQlInputException;
use Magento\Framework\GraphQl\Query\Resolver\Argument\SearchCriteria\Builder as SearchCriteriaBuilder;
use Magento\Framework\GraphQl\Schema\Type\ResolveInfo;
use Magento\CatalogGraphQl\Model\Resolver\Products\DataProvider\Product as ProductProvider;
@@ -19,6 +20,8 @@
use Magento\Search\Model\Query;
use Magento\Framework\App\Config\ScopeConfigInterface;
use Magento\Store\Model\ScopeInterface;
+use Magento\Framework\App\ObjectManager;
+use Magento\Framework\GraphQl\Query\Resolver\ArgumentsProcessorInterface;
/**
* Retrieve filtered product data based off given search criteria in a format that GraphQL can interpret.
@@ -50,25 +53,34 @@ class Filter implements ProductQueryInterface
*/
private $scopeConfig;
+ /**
+ * @var ArgumentsProcessorInterface
+ */
+ private $argsSelection;
+
/**
* @param SearchResultFactory $searchResultFactory
* @param ProductProvider $productDataProvider
* @param FieldSelection $fieldSelection
* @param SearchCriteriaBuilder $searchCriteriaBuilder
* @param ScopeConfigInterface $scopeConfig
+ * @param ArgumentsProcessorInterface|null $argsSelection
*/
public function __construct(
SearchResultFactory $searchResultFactory,
ProductProvider $productDataProvider,
FieldSelection $fieldSelection,
SearchCriteriaBuilder $searchCriteriaBuilder,
- ScopeConfigInterface $scopeConfig
+ ScopeConfigInterface $scopeConfig,
+ ArgumentsProcessorInterface $argsSelection = null
) {
$this->searchResultFactory = $searchResultFactory;
$this->productDataProvider = $productDataProvider;
$this->fieldSelection = $fieldSelection;
$this->searchCriteriaBuilder = $searchCriteriaBuilder;
$this->scopeConfig = $scopeConfig;
+ $this->argsSelection = $argsSelection ? : ObjectManager::getInstance()
+ ->get(ArgumentsProcessorInterface::class);
}
/**
@@ -78,6 +90,7 @@ public function __construct(
* @param ResolveInfo $info
* @param ContextInterface $context
* @return SearchResult
+ * @throws GraphQlInputException
*/
public function getResult(
array $args,
@@ -86,10 +99,10 @@ public function getResult(
): SearchResult {
$fields = $this->fieldSelection->getProductsFieldSelection($info);
try {
- $searchCriteria = $this->buildSearchCriteria($args, $info);
+ $searchCriteria = $this->buildSearchCriteria($info->fieldName, $args);
$searchResults = $this->productDataProvider->getList($searchCriteria, $fields, false, false, $context);
} catch (InputException $e) {
- return $this->createEmptyResult($args);
+ return $this->createEmptyResult((int)$args['pageSize'], (int)$args['currentPage']);
}
$productArray = [];
@@ -120,19 +133,22 @@ public function getResult(
/**
* Build search criteria from query input args
*
+ * @param string $fieldName
* @param array $args
- * @param ResolveInfo $info
* @return SearchCriteriaInterface
+ * @throws GraphQlInputException
+ * @throws InputException
*/
- private function buildSearchCriteria(array $args, ResolveInfo $info): SearchCriteriaInterface
+ private function buildSearchCriteria(string $fieldName, array $args): SearchCriteriaInterface
{
- if (!empty($args['filter'])) {
- $args['filter'] = $this->formatFilters($args['filter']);
+ $processedArgs = $this->argsSelection->process($fieldName, $args);
+ if (!empty($processedArgs['filter'])) {
+ $processedArgs['filter'] = $this->formatFilters($processedArgs['filter']);
}
- $criteria = $this->searchCriteriaBuilder->build($info->fieldName, $args);
- $criteria->setCurrentPage($args['currentPage']);
- $criteria->setPageSize($args['pageSize']);
+ $criteria = $this->searchCriteriaBuilder->build($fieldName, $processedArgs);
+ $criteria->setCurrentPage($processedArgs['currentPage']);
+ $criteria->setPageSize($processedArgs['pageSize']);
return $criteria;
}
@@ -175,17 +191,18 @@ private function formatFilters(array $filters): array
*
* Used for handling exceptions gracefully
*
- * @param array $args
+ * @param int $pageSize
+ * @param int $currentPage
* @return SearchResult
*/
- private function createEmptyResult(array $args): SearchResult
+ private function createEmptyResult(int $pageSize, int $currentPage): SearchResult
{
return $this->searchResultFactory->create(
[
'totalCount' => 0,
'productsSearchResult' => [],
- 'pageSize' => $args['pageSize'],
- 'currentPage' => $args['currentPage'],
+ 'pageSize' => $pageSize,
+ 'currentPage' => $currentPage,
'totalPages' => 0,
]
);
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 4eb76fb5c2d5b..221a402cb2fff 100644
--- a/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/Query/Search.php
+++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/Query/Search.php
@@ -12,7 +12,9 @@
use Magento\CatalogGraphQl\Model\Resolver\Products\SearchResult;
use Magento\CatalogGraphQl\Model\Resolver\Products\SearchResultFactory;
use Magento\Framework\Api\Search\SearchCriteriaInterface;
-use Magento\Framework\Exception\InputException;
+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;
@@ -43,6 +45,11 @@ class Search implements ProductQueryInterface
*/
private $fieldSelection;
+ /**
+ * @var ArgumentsProcessorInterface
+ */
+ private $argsSelection;
+
/**
* @var ProductSearch
*/
@@ -60,6 +67,7 @@ class Search implements ProductQueryInterface
* @param FieldSelection $fieldSelection
* @param ProductSearch $productsProvider
* @param SearchCriteriaBuilder $searchCriteriaBuilder
+ * @param ArgumentsProcessorInterface|null $argsSelection
*/
public function __construct(
SearchInterface $search,
@@ -67,7 +75,8 @@ public function __construct(
PageSizeProvider $pageSize,
FieldSelection $fieldSelection,
ProductSearch $productsProvider,
- SearchCriteriaBuilder $searchCriteriaBuilder
+ SearchCriteriaBuilder $searchCriteriaBuilder,
+ ArgumentsProcessorInterface $argsSelection = null
) {
$this->search = $search;
$this->searchResultFactory = $searchResultFactory;
@@ -75,6 +84,8 @@ public function __construct(
$this->fieldSelection = $fieldSelection;
$this->productsProvider = $productsProvider;
$this->searchCriteriaBuilder = $searchCriteriaBuilder;
+ $this->argsSelection = $argsSelection ?: ObjectManager::getInstance()
+ ->get(ArgumentsProcessorInterface::class);
}
/**
@@ -84,14 +95,13 @@ public function __construct(
* @param ResolveInfo $info
* @param ContextInterface $context
* @return SearchResult
- * @throws InputException
+ * @throws GraphQlInputException
*/
public function getResult(
array $args,
ResolveInfo $info,
ContextInterface $context
): SearchResult {
- $queryFields = $this->fieldSelection->getProductsFieldSelection($info);
$searchCriteria = $this->buildSearchCriteria($args, $info);
$realPageSize = $searchCriteria->getPageSize();
@@ -108,7 +118,7 @@ public function getResult(
$searchResults = $this->productsProvider->getList(
$searchCriteria,
$itemsResults,
- $queryFields,
+ $this->fieldSelection->getProductsFieldSelection($info),
$context
);
@@ -144,7 +154,8 @@ private function buildSearchCriteria(array $args, ResolveInfo $info): SearchCrit
{
$productFields = (array)$info->getFieldSelection(1);
$includeAggregations = isset($productFields['filters']) || isset($productFields['aggregations']);
- $searchCriteria = $this->searchCriteriaBuilder->build($args, $includeAggregations);
+ $processedArgs = $this->argsSelection->process((string) $info->fieldName, $args);
+ $searchCriteria = $this->searchCriteriaBuilder->build($processedArgs, $includeAggregations);
return $searchCriteria;
}
diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/RootCategoryId.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/RootCategoryId.php
index 4b3e0a1a58dfd..4575c2013dc9c 100644
--- a/app/code/Magento/CatalogGraphQl/Model/Resolver/RootCategoryId.php
+++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/RootCategoryId.php
@@ -13,6 +13,9 @@
/**
* Root category tree field resolver, used for GraphQL request processing.
+ *
+ * @deprecated Use the UID instead of a numeric id
+ * @see \Magento\CatalogGraphQl\Model\Resolver\RootCategoryUid
*/
class RootCategoryId implements ResolverInterface
{
diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/RootCategoryUid.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/RootCategoryUid.php
new file mode 100644
index 0000000000000..9503e9f09b03c
--- /dev/null
+++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/RootCategoryUid.php
@@ -0,0 +1,38 @@
+uidEncoder = $uidEncoder;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function resolve(Field $field, $context, ResolveInfo $info, array $value = null, array $args = null)
+ {
+ return $this->uidEncoder->encode((string) $context->getExtensionAttributes()->getStore()->getRootCategoryId());
+ }
+}
diff --git a/app/code/Magento/CatalogGraphQl/Plugin/Search/Request/ConfigReader.php b/app/code/Magento/CatalogGraphQl/Plugin/Search/Request/ConfigReader.php
index 992ab50467c72..b61ecfff4e3f1 100644
--- a/app/code/Magento/CatalogGraphQl/Plugin/Search/Request/ConfigReader.php
+++ b/app/code/Magento/CatalogGraphQl/Plugin/Search/Request/ConfigReader.php
@@ -106,6 +106,9 @@ private function getSearchableAttributes(): array
$productAttributes->addFieldToFilter(
['is_searchable', 'is_visible_in_advanced_search', 'is_filterable', 'is_filterable_in_search'],
[1, 1, [1, 2], 1]
+ )->setOrder(
+ 'position',
+ 'ASC'
);
/** @var Attribute $attribute */
diff --git a/app/code/Magento/CatalogGraphQl/etc/di.xml b/app/code/Magento/CatalogGraphQl/etc/di.xml
index fd3a834bff160..8c6fac0fe621c 100644
--- a/app/code/Magento/CatalogGraphQl/etc/di.xml
+++ b/app/code/Magento/CatalogGraphQl/etc/di.xml
@@ -67,6 +67,15 @@
+
+
+
+ Magento\CatalogGraphQl\Model\Resolver\Products\Query\CategoryUidArgsProcessor
+ Magento\CatalogGraphQl\Model\Category\CategoryUidsArgsProcessor
+
+
+
+
diff --git a/app/code/Magento/CatalogGraphQl/etc/schema.graphqls b/app/code/Magento/CatalogGraphQl/etc/schema.graphqls
index 812965228682f..79281ff42cf26 100644
--- a/app/code/Magento/CatalogGraphQl/etc/schema.graphqls
+++ b/app/code/Magento/CatalogGraphQl/etc/schema.graphqls
@@ -83,27 +83,28 @@ interface ProductLinksInterface @typeResolver(class: "Magento\\CatalogGraphQl\\M
}
interface ProductInterface @typeResolver(class: "Magento\\CatalogGraphQl\\Model\\ProductInterfaceTypeResolverComposite") @doc(description: "The ProductInterface contains attributes that are common to all types of products. Note that descriptions may not be available for custom and EAV attributes.") {
- id: Int @doc(description: "The ID number assigned to the product.") @resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\Product\\EntityIdToId")
+ id: Int @deprecated(reason: "Use the `uid` field instead.") @doc(description: "The ID number assigned to the product.") @resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\Product\\EntityIdToId")
+ uid: ID! @doc(description: "The unique ID for a `ProductInterface` object.") @resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\Product\\EntityIdToUid")
name: String @doc(description: "The product name. Customers use this name to identify the product.")
sku: String @doc(description: "A number or code assigned to a product to identify the product, options, price, and manufacturer.")
description: ComplexTextValue @doc(description: "Detailed information about the product. The value can include simple HTML tags.") @resolver(class: "\\Magento\\CatalogGraphQl\\Model\\Resolver\\Product\\ProductComplexTextAttribute")
short_description: ComplexTextValue @doc(description: "A short description of the product. Its use depends on the theme.") @resolver(class: "\\Magento\\CatalogGraphQl\\Model\\Resolver\\Product\\ProductComplexTextAttribute")
special_price: Float @doc(description: "The discounted price of the product.") @resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\Product\\SpecialPrice")
- special_from_date: String @doc(description: "The beginning date that a product has a special price.")
+ special_from_date: String @deprecated(reason: "The field should not be used on the storefront.") @doc(description: "The beginning date that a product has a special price.")
special_to_date: String @doc(description: "The end date that a product has a special price.")
- attribute_set_id: Int @doc(description: "The attribute set assigned to the product.")
+ attribute_set_id: Int @deprecated(reason: "The field should not be used on the storefront.") @doc(description: "The attribute set assigned to the product.")
meta_title: String @doc(description: "A string that is displayed in the title bar and tab of the browser and in search results lists.")
meta_keyword: String @doc(description: "A comma-separated list of keywords that are visible only to search engines.")
meta_description: String @doc(description: "A brief overview of the product for search results listings, maximum 255 characters.")
image: ProductImage @doc(description: "The relative path to the main image on the product page.") @resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\Product\\ProductImage")
small_image: ProductImage @doc(description: "The relative path to the small image, which is used on catalog pages.") @resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\Product\\ProductImage")
thumbnail: ProductImage @doc(description: "The relative path to the product's thumbnail image.") @resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\Product\\ProductImage")
- new_from_date: String @doc(description: "The beginning date for new product listings, and determines if the product is featured as a new product.") @resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\Product\\NewFromTo")
- new_to_date: String @doc(description: "The end date for new product listings.") @resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\Product\\NewFromTo")
+ new_from_date: String @deprecated(reason: "The field should not be used on the storefront.") @doc(description: "The beginning date for new product listings, and determines if the product is featured as a new product.") @resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\Product\\NewFromTo")
+ new_to_date: String @deprecated(reason: "The field should not be used on the storefront.") @doc(description: "The end date for new product listings.") @resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\Product\\NewFromTo")
tier_price: Float @deprecated(reason: "Use price_tiers for product tier price information.") @doc(description: "The price when tier pricing is in effect and the items purchased threshold has been reached.")
options_container: String @doc(description: "If the product has multiple options, determines where they appear on the product page.")
- created_at: String @doc(description: "Timestamp indicating when the product was created.")
- updated_at: String @doc(description: "Timestamp indicating when the product was updated.")
+ created_at: String @deprecated(reason: "The field should not be used on the storefront.") @doc(description: "Timestamp indicating when the product was created.")
+ updated_at: String @deprecated(reason: "The field should not be used on the storefront.") @doc(description: "Timestamp indicating when the product was updated.")
country_of_manufacture: String @doc(description: "The product's country of origin.")
type_id: String @doc(description: "One of simple, virtual, bundle, downloadable, grouped, or configurable.") @deprecated(reason: "Use __typename instead.")
websites: [Website] @doc(description: "An array of websites in which the product is available.") @deprecated(reason: "The field should not be used on the storefront.") @resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\Product\\Websites")
@@ -132,7 +133,7 @@ type CustomizableAreaValue @doc(description: "CustomizableAreaValue defines the
price_type: PriceTypeEnum @doc(description: "FIXED, PERCENT, or DYNAMIC.")
sku: String @doc(description: "The Stock Keeping Unit for this option.")
max_characters: Int @doc(description: "The maximum number of characters that can be entered for this customizable option.")
- uid: ID! @doc(description: "A string that encodes option details.") @resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\Product\\CustomizableEnteredOptionValueUid") # A Base64 string that encodes option details.
+ uid: ID! @doc(description: "The unique ID for a `CustomizableAreaValue` object.") @resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\Product\\CustomizableEnteredOptionValueUid")
}
type CategoryTree implements CategoryInterface @doc(description: "Category Tree implementation.") {
@@ -154,7 +155,7 @@ type CustomizableDateValue @doc(description: "CustomizableDateValue defines the
price: Float @doc(description: "The price assigned to this option.")
price_type: PriceTypeEnum @doc(description: "FIXED, PERCENT, or DYNAMIC.")
sku: String @doc(description: "The Stock Keeping Unit for this option.")
- uid: ID! @doc(description: "A string that encodes option details.") @resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\Product\\CustomizableEnteredOptionValueUid") # A Base64 string that encodes option details.
+ uid: ID! @doc(description: "The unique ID for a `CustomizableDateValue` object.") @resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\Product\\CustomizableEnteredOptionValueUid")
}
type CustomizableDropDownOption implements CustomizableOptionInterface @doc(description: "CustomizableDropDownOption contains information about a drop down menu that is defined as part of a customizable option.") {
@@ -168,7 +169,7 @@ type CustomizableDropDownValue @doc(description: "CustomizableDropDownValue defi
sku: String @doc(description: "The Stock Keeping Unit for this option.")
title: String @doc(description: "The display name for this option.")
sort_order: Int @doc(description: "The order in which the option is displayed.")
- uid: ID! @doc(description: "A string that encodes option details.") @resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\Product\\CustomizableSelectedOptionValueUid") # A Base64 string that encodes option details.
+ uid: ID! @doc(description: "The unique ID for a `CustomizableDropDownValue` object.") @resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\Product\\CustomizableSelectedOptionValueUid")
}
type CustomizableMultipleOption implements CustomizableOptionInterface @doc(description: "CustomizableMultipleOption contains information about a multiselect that is defined as part of a customizable option.") {
@@ -182,7 +183,7 @@ type CustomizableMultipleValue @doc(description: "CustomizableMultipleValue defi
sku: String @doc(description: "The Stock Keeping Unit for this option.")
title: String @doc(description: "The display name for this option.")
sort_order: Int @doc(description: "The order in which the option is displayed.")
- uid: ID! @doc(description: "A string that encodes option details.") @resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\Product\\CustomizableSelectedOptionValueUid")
+ uid: ID! @doc(description: "The unique ID for a `CustomizableMultipleValue` object.") @resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\Product\\CustomizableSelectedOptionValueUid")
}
type CustomizableFieldOption implements CustomizableOptionInterface @doc(description: "CustomizableFieldOption contains information about a text field that is defined as part of a customizable option.") {
@@ -195,7 +196,7 @@ type CustomizableFieldValue @doc(description: "CustomizableFieldValue defines th
price_type: PriceTypeEnum @doc(description: "FIXED, PERCENT, or DYNAMIC.")
sku: String @doc(description: "The Stock Keeping Unit for this option.")
max_characters: Int @doc(description: "The maximum number of characters that can be entered for this customizable option.")
- uid: ID! @doc(description: "A string that encodes option details.") @resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\Product\\CustomizableEnteredOptionValueUid") # A Base64 string that encodes option details.
+ uid: ID! @doc(description: "The unique ID for a `CustomizableFieldValue` object.") @resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\Product\\CustomizableEnteredOptionValueUid")
}
type CustomizableFileOption implements CustomizableOptionInterface @doc(description: "CustomizableFileOption contains information about a file picker that is defined as part of a customizable option.") {
@@ -210,7 +211,7 @@ type CustomizableFileValue @doc(description: "CustomizableFileValue defines the
file_extension: String @doc(description: "The file extension to accept.")
image_size_x: Int @doc(description: "The maximum width of an image.")
image_size_y: Int @doc(description: "The maximum height of an image.")
- uid: ID! @doc(description: "A string that encodes option details.") @resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\Product\\CustomizableEnteredOptionValueUid") # A Base64 string that encodes option details.
+ uid: ID! @doc(description: "The unique ID for a `CustomizableFileValue` object.") @resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\Product\\CustomizableEnteredOptionValueUid")
}
interface MediaGalleryInterface @doc(description: "Contains basic information about a product image or video.") @typeResolver(class: "Magento\\CatalogGraphQl\\Model\\MediaGalleryTypeResolver") {
@@ -231,7 +232,8 @@ interface CustomizableOptionInterface @typeResolver(class: "Magento\\CatalogGrap
title: String @doc(description: "The display name for this option.")
required: Boolean @doc(description: "Indicates whether the option is required.")
sort_order: Int @doc(description: "The order in which the option is displayed.")
- option_id: Int @doc(description: "Option ID.")
+ option_id: Int @deprecated(reason: "Use `uid` instead") @doc(description: "Option ID.")
+ uid: ID! @doc(description: "The unique ID for a `CustomizableOptionInterface` object.")
}
interface CustomizableProductInterface @typeResolver(class: "Magento\\CatalogGraphQl\\Model\\ProductInterfaceTypeResolverComposite") @doc(description: "CustomizableProductInterface contains information about customizable product options.") {
@@ -239,7 +241,8 @@ interface CustomizableProductInterface @typeResolver(class: "Magento\\CatalogGra
}
interface CategoryInterface @typeResolver(class: "Magento\\CatalogGraphQl\\Model\\CategoryInterfaceTypeResolver") @doc(description: "CategoryInterface contains the full set of attributes that can be returned in a category search.") {
- id: Int @doc(description: "An ID that uniquely identifies the category.")
+ id: Int @deprecated(reason: "Use the `uid` argument instead.") @doc(description: "An ID that uniquely identifies the category.")
+ uid: ID! @doc(description: "The unique ID for a `CategoryInterface` object.")
description: String @doc(description: "An optional description of the category.") @resolver(class: "\\Magento\\CatalogGraphQl\\Model\\Resolver\\Category\\CategoryHtmlAttribute")
name: String @doc(description: "The display name of the category.")
path: String @doc(description: "Category Path.")
@@ -249,8 +252,8 @@ interface CategoryInterface @typeResolver(class: "Magento\\CatalogGraphQl\\Model
canonical_url: String @doc(description: "Relative canonical URL. This value is returned only if the system setting 'Use Canonical Link Meta Tag For Categories' is enabled") @resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\Category\\CanonicalUrl")
position: Int @doc(description: "The position of the category relative to other categories at the same level in tree.")
level: Int @doc(description: "Indicates the depth of the category within the tree.")
- created_at: String @doc(description: "Timestamp indicating when the category was created.")
- updated_at: String @doc(description: "Timestamp indicating when the category was updated.")
+ created_at: String @deprecated(reason: "The field should not be used on the storefront.") @doc(description: "Timestamp indicating when the category was created.")
+ updated_at: String @deprecated(reason: "The field should not be used on the storefront.") @doc(description: "Timestamp indicating when the category was updated.")
product_count: Int @doc(description: "The number of products in the category that are marked as visible. By default, in complex products, parent products are visible, but their child products are not.") @resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\Category\\ProductsCount")
default_sort_by: String @doc(description: "The attribute to use for sorting.")
products(
@@ -261,8 +264,9 @@ interface CategoryInterface @typeResolver(class: "Magento\\CatalogGraphQl\\Model
breadcrumbs: [Breadcrumb] @doc(description: "Breadcrumbs, parent categories info.") @resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\Category\\Breadcrumbs")
}
-type Breadcrumb @doc(description: "Breadcrumb item."){
- category_id: Int @doc(description: "Category ID.")
+type Breadcrumb @doc(description: "Breadcrumb item.") {
+ category_id: Int @deprecated(reason: "Use the `category_uid` argument instead.") @doc(description: "Category ID.")
+ category_uid: ID! @doc(description: "The unique ID for a `Breadcrumb` object.")
category_name: String @doc(description: "Category name.")
category_level: Int @doc(description: "Category level.")
category_url_key: String @doc(description: "Category URL key.")
@@ -280,7 +284,7 @@ type CustomizableRadioValue @doc(description: "CustomizableRadioValue defines th
sku: String @doc(description: "The Stock Keeping Unit for this option.")
title: String @doc(description: "The display name for this option.")
sort_order: Int @doc(description: "The order in which the radio button is displayed.")
- uid: ID! @doc(description: "A string that encodes option details.") @resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\Product\\CustomizableSelectedOptionValueUid") # A Base64 string that encodes option details.
+ uid: ID! @doc(description: "The unique ID for a `CustomizableRadioValue` object.") @resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\Product\\CustomizableSelectedOptionValueUid")
}
type CustomizableCheckboxOption implements CustomizableOptionInterface @doc(description: "CustomizableCheckbbixOption contains information about a set of checkbox values that are defined as part of a customizable option.") {
@@ -294,7 +298,7 @@ type CustomizableCheckboxValue @doc(description: "CustomizableCheckboxValue defi
sku: String @doc(description: "The Stock Keeping Unit for this option.")
title: String @doc(description: "The display name for this option.")
sort_order: Int @doc(description: "The order in which the checkbox value is displayed.")
- uid: ID! @doc(description: "A string that encodes option details.") @resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\Product\\CustomizableSelectedOptionValueUid") # A Base64 string that encodes option details.
+ uid: ID! @doc(description: "The unique ID for a `CustomizableCheckboxValue` object.") @resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\Product\\CustomizableSelectedOptionValueUid")
}
type VirtualProduct implements ProductInterface, CustomizableProductInterface @doc(description: "A virtual product is non-tangible product that does not require shipping and is not kept in inventory.") {
@@ -320,13 +324,15 @@ type CategoryProducts @doc(description: "The category products object returned i
}
input ProductAttributeFilterInput @doc(description: "ProductAttributeFilterInput 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.") {
- category_id: FilterEqualTypeInput @doc(description: "Filter product by category id")
+ category_id: FilterEqualTypeInput @deprecated(reason: "Use the `category_uid` argument instead.") @doc(description: "Deprecated: use `category_uid` to filter product by category id.")
+ category_uid: FilterEqualTypeInput @doc(description: "Filter product by the unique ID for a `CategoryInterface` object.")
}
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.")
{
- ids: FilterEqualTypeInput @doc(description: "Filter by category ID that uniquely identifies the category.")
- parent_id: FilterEqualTypeInput @doc(description: "Filter by parent category ID")
+ ids: FilterEqualTypeInput @deprecated(reason: "Use the `category_uid` argument instead.") @doc(description: "Deprecated: use 'category_uid' to filter uniquely identifiers of categories.")
+ category_uid: FilterEqualTypeInput @doc(description: "Filter by the unique category ID for a `CategoryInterface` object.")
+ parent_id: FilterEqualTypeInput @doc(description: "Filter by the unique parent category ID for a `CategoryInterface` object.")
url_key: FilterEqualTypeInput @doc(description: "Filter by the part of the URL that identifies the category.")
name: FilterMatchTypeInput @doc(description: "Filter by the display name of the category.")
url_path: FilterEqualTypeInput @doc(description: "Filter by the URL path for the category.")
@@ -426,7 +432,8 @@ input ProductAttributeSortInput @doc(description: "ProductAttributeSortInput spe
}
type MediaGalleryEntry @doc(description: "MediaGalleryEntry defines characteristics about images and videos associated with a specific product.") {
- id: Int @doc(description: "The identifier assigned to the object.")
+ id: Int @deprecated(reason: "Use `uid` instead.") @doc(description: "The identifier assigned to the object.")
+ uid: ID! @doc(description: "The unique ID for a `MediaGalleryEntry` object.")
media_type: String @doc(description: "image or video.")
label: String @doc(description: "The alt text displayed on the UI when the user points to the image.")
position: Int @doc(description: "The media item's position after it has been sorted.")
@@ -454,7 +461,7 @@ type LayerFilterItem implements LayerFilterItemInterface {
}
-type Aggregation @doc(description: "A bucket that contains information for each filterable option (such as price, category ID, and custom attributes).") {
+type Aggregation @doc(description: "A bucket that contains information for each filterable option (such as price, category `UID`, and custom attributes).") {
count: Int @doc(description: "The number of options in the aggregation group.")
label: String @doc(description: "The aggregation display name.")
attribute_code: String! @doc(description: "Attribute code of the aggregation group.")
@@ -491,7 +498,8 @@ type StoreConfig @doc(description: "The type contains information about a store
grid_per_page : Int @doc(description: "Products per Page on Grid Default Value.")
list_per_page : Int @doc(description: "Products per Page on List Default Value.")
catalog_default_sort_by : String @doc(description: "Default Sort By.")
- root_category_id: Int @doc(description: "The ID of the root category") @resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\RootCategoryId")
+ root_category_id: Int @deprecated(reason: "Use `root_category_uid` instead") @doc(description: "The ID of the root category") @resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\RootCategoryId")
+ root_category_uid: ID @doc(description: "The unique ID for a `CategoryInterface` object.") @resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\RootCategoryUid")
}
type SimpleWishlistItem implements WishlistItemInterface @doc(description: "A simple product wish list Item") {
diff --git a/app/code/Magento/CatalogImportExport/Model/Import/Product.php b/app/code/Magento/CatalogImportExport/Model/Import/Product.php
index 428961aa6ddf6..673dbcb3b3c99 100644
--- a/app/code/Magento/CatalogImportExport/Model/Import/Product.php
+++ b/app/code/Magento/CatalogImportExport/Model/Import/Product.php
@@ -1807,7 +1807,7 @@ protected function _saveProducts()
if ($column === self::COL_MEDIA_IMAGE) {
$rowData[$column][] = $uploadedFile;
}
- $mediaGallery[$storeId][$rowSku][$uploadedFile] = [
+ $mediaGalleryStoreData = [
'attribute_id' => $this->getMediaGalleryAttributeId(),
'label' => isset($rowLabels[$column][$columnImageKey])
? $rowLabels[$column][$columnImageKey]
@@ -1817,6 +1817,15 @@ protected function _saveProducts()
? $imageHiddenStates[$columnImage] : '0',
'value' => $uploadedFile,
];
+ $mediaGallery[$storeId][$rowSku][$uploadedFile] = $mediaGalleryStoreData;
+ // Add record for default scope if it does not exist
+ if (!($mediaGallery[Store::DEFAULT_STORE_ID][$rowSku][$uploadedFile] ?? [])) {
+ //Set label and disabled values to their default values
+ $mediaGalleryStoreData['label'] = null;
+ $mediaGalleryStoreData['disabled'] = 0;
+ $mediaGallery[Store::DEFAULT_STORE_ID][$rowSku][$uploadedFile] = $mediaGalleryStoreData;
+ }
+
}
}
}
@@ -1965,7 +1974,7 @@ private function getAlreadyExistedImage(array $imageRow, string $columnImage, st
if (filter_var($columnImage, FILTER_VALIDATE_URL)) {
$hash = $this->getFileHash($columnImage);
} else {
- $path = $importDir . DS . $columnImage;
+ $path = $importDir . DIRECTORY_SEPARATOR . $columnImage;
$hash = $this->isFileExists($path) ? $this->getFileHash($path) : '';
}
@@ -1991,7 +2000,7 @@ function ($exists, $file) use ($hash) {
private function addImageHashes(array &$images): void
{
$productMediaPath = $this->filesystem->getDirectoryRead(DirectoryList::MEDIA)
- ->getAbsolutePath(DS . 'catalog' . DS . 'product');
+ ->getAbsolutePath(DIRECTORY_SEPARATOR . 'catalog' . DIRECTORY_SEPARATOR . 'product');
foreach ($images as $storeId => $skus) {
foreach ($skus as $sku => $files) {
@@ -2188,7 +2197,7 @@ private function getImportDir(): string
$dirAddon = $dirConfig[DirectoryList::MEDIA][DirectoryList::PATH];
return empty($this->_parameters[Import::FIELD_NAME_IMG_FILE_DIR])
- ? $dirAddon . DS . $this->_mediaDirectory->getRelativePath('import')
+ ? $dirAddon . DIRECTORY_SEPARATOR . $this->_mediaDirectory->getRelativePath('import')
: $this->_parameters[Import::FIELD_NAME_IMG_FILE_DIR];
}
diff --git a/app/code/Magento/CatalogImportExport/Model/Import/Product/MediaGalleryProcessor.php b/app/code/Magento/CatalogImportExport/Model/Import/Product/MediaGalleryProcessor.php
index d4694b72ba64f..c838688c1c4f8 100644
--- a/app/code/Magento/CatalogImportExport/Model/Import/Product/MediaGalleryProcessor.php
+++ b/app/code/Magento/CatalogImportExport/Model/Import/Product/MediaGalleryProcessor.php
@@ -12,6 +12,7 @@
use Magento\Framework\App\ResourceConnection;
use Magento\Framework\EntityManager\MetadataPool;
use Magento\ImportExport\Model\Import\ErrorProcessing\ProcessingErrorAggregatorInterface;
+use Magento\Store\Model\Store;
/**
* Process and saves images during import.
@@ -259,7 +260,10 @@ private function prepareMediaGalleryValueData(
$position = $data['position'];
$storeId = $data['store_id'];
$mediaGalleryValueData[$index]['value_id'] = $productIdMediaValueIdMap[$productId][$value];
- $mediaGalleryValueData[$index]['position'] = $position + ($lastPositions[$storeId][$productId] ?? 0);
+ $lastPosition = $lastPositions[$storeId][$productId]
+ ?? $lastPositions[Store::DEFAULT_STORE_ID][$productId]
+ ?? 0;
+ $mediaGalleryValueData[$index]['position'] = $position + $lastPosition;
unset($mediaGalleryValueData[$index]['value']);
}
return $mediaGalleryValueData;
diff --git a/app/code/Magento/CatalogInventory/Model/Indexer/ProductPriceIndexFilter.php b/app/code/Magento/CatalogInventory/Model/Indexer/ProductPriceIndexFilter.php
index 2ad7ca9f14963..2b37c9099a0e6 100644
--- a/app/code/Magento/CatalogInventory/Model/Indexer/ProductPriceIndexFilter.php
+++ b/app/code/Magento/CatalogInventory/Model/Indexer/ProductPriceIndexFilter.php
@@ -105,7 +105,7 @@ public function modifyPrice(IndexTableStructure $priceTable, array $entityIds =
}
if (!empty($entityIds)) {
- $select->where('stock_item.product_id in (?)', $entityIds, \Zend_Db::INT_TYPE);
+ $select->where('stock_item.product_id IN (?)', $entityIds, \Zend_Db::INT_TYPE);
}
$select->group('stock_item.product_id');
@@ -121,7 +121,7 @@ public function modifyPrice(IndexTableStructure $priceTable, array $entityIds =
foreach ($batchSelectIterator as $select) {
$productIds = null;
foreach ($connection->query($select)->fetchAll() as $row) {
- $productIds[] = $row['product_id'];
+ $productIds[] = (int) $row['product_id'];
}
if ($productIds !== null) {
$where = [$priceTable->getEntityField() .' IN (?)' => $productIds];
diff --git a/app/code/Magento/CatalogInventory/Model/Indexer/Stock/AbstractAction.php b/app/code/Magento/CatalogInventory/Model/Indexer/Stock/AbstractAction.php
index 4ea6b6bcfde9a..0b5f248331bfd 100644
--- a/app/code/Magento/CatalogInventory/Model/Indexer/Stock/AbstractAction.php
+++ b/app/code/Magento/CatalogInventory/Model/Indexer/Stock/AbstractAction.php
@@ -167,7 +167,7 @@ public function getRelationsByChild($childIds)
)->join(
['relation' => $this->_getTable('catalog_product_relation')],
'relation.parent_id = cpe.' . $linkField
- )->where('child_id IN(?)', $childIds);
+ )->where('child_id IN(?)', $childIds, \Zend_Db::INT_TYPE);
return $connection->fetchCol($select);
}
@@ -262,7 +262,7 @@ private function doReindex($productIds = [])
// retrieve product types by processIds
$select = $connection->select()
->from($this->_getTable('catalog_product_entity'), ['entity_id', 'type_id'])
- ->where('entity_id IN(?)', $productIds);
+ ->where('entity_id IN(?)', $productIds, \Zend_Db::INT_TYPE);
$pairs = $connection->fetchPairs($select);
$byType = [];
diff --git a/app/code/Magento/CatalogInventory/Model/Indexer/Stock/Action/Full.php b/app/code/Magento/CatalogInventory/Model/Indexer/Stock/Action/Full.php
index 43a5aabee9779..f85f3f3576279 100644
--- a/app/code/Magento/CatalogInventory/Model/Indexer/Stock/Action/Full.php
+++ b/app/code/Magento/CatalogInventory/Model/Indexer/Stock/Action/Full.php
@@ -149,13 +149,21 @@ public function execute($ids = null): void
$select = $connection->select();
$select->distinct(true);
- $select->from(['e' => $entityMetadata->getEntityTable()], $entityMetadata->getIdentifierField());
+ $select->from(
+ [
+ 'e' => $entityMetadata->getEntityTable()
+ ],
+ $entityMetadata->getIdentifierField()
+ )->where(
+ 'type_id = ?',
+ $indexer->getTypeId()
+ );
$batchQueries = $this->batchQueryGenerator->generate(
$entityMetadata->getIdentifierField(),
$select,
$batchRowCount,
- BatchIteratorInterface::NON_UNIQUE_FIELD_ITERATOR
+ BatchIteratorInterface::UNIQUE_FIELD_ITERATOR
);
foreach ($batchQueries as $query) {
diff --git a/app/code/Magento/CatalogInventory/Model/ResourceModel/Indexer/Stock/DefaultStock.php b/app/code/Magento/CatalogInventory/Model/ResourceModel/Indexer/Stock/DefaultStock.php
index c151e5897abd5..dec18044b699e 100644
--- a/app/code/Magento/CatalogInventory/Model/ResourceModel/Indexer/Stock/DefaultStock.php
+++ b/app/code/Magento/CatalogInventory/Model/ResourceModel/Indexer/Stock/DefaultStock.php
@@ -261,7 +261,7 @@ protected function _getStockStatusSelect($entityIds = null, $usePrimaryTable = f
$select->columns(['status' => $this->getStatusExpression($connection, true)]);
if ($entityIds !== null) {
- $select->where('e.entity_id IN(?)', $entityIds);
+ $select->where('e.entity_id IN(?)', $entityIds, \Zend_Db::INT_TYPE);
}
return $select;
diff --git a/app/code/Magento/CatalogInventory/Model/ResourceModel/Stock/Status.php b/app/code/Magento/CatalogInventory/Model/ResourceModel/Stock/Status.php
index 02e443d09b228..afb7d51335df8 100644
--- a/app/code/Magento/CatalogInventory/Model/ResourceModel/Stock/Status.php
+++ b/app/code/Magento/CatalogInventory/Model/ResourceModel/Stock/Status.php
@@ -153,7 +153,7 @@ public function getProductsStockStatuses($productIds, $websiteId, $stockId = Sto
$select = $this->getConnection()->select()
->from($this->getMainTable(), ['product_id', 'stock_status'])
- ->where('product_id IN(?)', $productIds)
+ ->where('product_id IN(?)', $productIds, \Zend_Db::INT_TYPE)
->where('stock_id=?', (int) $stockId)
->where('website_id=?', (int) $websiteId);
return $this->getConnection()->fetchPairs($select);
@@ -190,7 +190,8 @@ public function getProductsType($productIds)
['entity_id', 'type_id']
)->where(
'entity_id IN(?)',
- $productIds
+ $productIds,
+ \Zend_Db::INT_TYPE
);
return $this->getConnection()->fetchPairs($select);
}
@@ -360,7 +361,8 @@ public function getProductStatus($productIds, $storeId = null)
$attribute->getAttributeId()
)->where(
"t1.{$linkField} IN(?)",
- $productIds
+ $productIds,
+ \Zend_Db::INT_TYPE
);
$rows = $connection->fetchPairs($select);
diff --git a/app/code/Magento/CatalogInventory/Plugin/MassUpdateProductAttribute.php b/app/code/Magento/CatalogInventory/Plugin/MassUpdateProductAttribute.php
index 2dd47eae16959..9351abf0ead3d 100644
--- a/app/code/Magento/CatalogInventory/Plugin/MassUpdateProductAttribute.php
+++ b/app/code/Magento/CatalogInventory/Plugin/MassUpdateProductAttribute.php
@@ -64,6 +64,14 @@ class MassUpdateProductAttribute
* @var ProductRepositoryInterface
*/
private $productRepository;
+
+ /**
+ * @var array
+ */
+ private $useConfigFieldMap = [
+ 'enable_qty_increments' => 'use_config_enable_qty_inc'
+ ];
+
/**
* @param \Magento\CatalogInventory\Model\Indexer\Stock\Processor $stockIndexerProcessor
* @param \Magento\Framework\Api\DataObjectHelper $dataObjectHelper
@@ -146,7 +154,9 @@ private function addConfigSettings($inventoryData)
{
$options = $this->stockConfiguration->getConfigItemOptions();
foreach ($options as $option) {
- $useConfig = 'use_config_' . $option;
+ $useConfig = isset($this->useConfigFieldMap[$option])
+ ? $this->useConfigFieldMap[$option]
+ : 'use_config_' . $option;
if (isset($inventoryData[$option]) && !isset($inventoryData[$useConfig])) {
$inventoryData[$useConfig] = 0;
}
diff --git a/app/code/Magento/CatalogInventory/Test/Unit/Model/Indexer/ProductPriceIndexFilterTest.php b/app/code/Magento/CatalogInventory/Test/Unit/Model/Indexer/ProductPriceIndexFilterTest.php
index 0f3d8be212e30..090a74afca050 100644
--- a/app/code/Magento/CatalogInventory/Test/Unit/Model/Indexer/ProductPriceIndexFilterTest.php
+++ b/app/code/Magento/CatalogInventory/Test/Unit/Model/Indexer/ProductPriceIndexFilterTest.php
@@ -82,7 +82,7 @@ public function testModifyPrice()
$connectionMock->expects($this->once())->method('select')->willReturn($selectMock);
$selectMock->expects($this->at(2))
->method('where')
- ->with('stock_item.product_id in (?)', $entityIds)
+ ->with('stock_item.product_id IN (?)', $entityIds)
->willReturn($selectMock);
$this->generator->expects($this->once())
->method('generate')
diff --git a/app/code/Magento/CatalogInventory/etc/mview.xml b/app/code/Magento/CatalogInventory/etc/mview.xml
index 338f1fe0610a1..9733fa32583f1 100644
--- a/app/code/Magento/CatalogInventory/etc/mview.xml
+++ b/app/code/Magento/CatalogInventory/etc/mview.xml
@@ -11,6 +11,7 @@