diff --git a/app/code/Magento/CatalogGraphQl/Model/Product/Attributes/Collection.php b/app/code/Magento/CatalogGraphQl/Model/Product/Attributes/Collection.php new file mode 100644 index 0000000000000..6c82eb2e058b5 --- /dev/null +++ b/app/code/Magento/CatalogGraphQl/Model/Product/Attributes/Collection.php @@ -0,0 +1,137 @@ +attributesCollection = $attributesCollection; + $this->productDataProvider = $productDataProvider; + $this->searchCriteriaBuilder = $searchCriteriaBuilder; + $this->frontend = $frontend; + } + + /** + * Add product id to attribute collection filter. + * + * @param int $productId + */ + public function addProductId(int $productId): void + { + if (!in_array($productId, $this->productIds)) { + $this->productIds[] = $productId; + } + } + + /** + * Retrieve attributes values for given product id or empty array + * + * @param int $productId + * @return array + */ + public function getAttributesValueByProductId(int $productId): array + { + $attributes = $this->fetch(); + if (!isset($attributes[$productId])) { + return []; + } + + return $attributes[$productId]; + } + + /** + * Fetch attribute data + * + * @return array + */ + private function fetch(): array + { + if (empty($this->productIds) || !empty($this->attributeValueMap)) { + return $this->attributeValueMap; + } + + $attributes = $this->attributesCollection->getAttributes(); + + $this->searchCriteriaBuilder->addFilter('entity_id', $this->productIds, 'in'); + $products = $this->productDataProvider->getList( + $this->searchCriteriaBuilder->create(), + ['*'], + false, + true + )->getItems(); + + foreach ($products as $productId => $product) { + $data = []; + foreach ($attributes as $attribute) { + $value = $this->frontend->setAttribute($attribute)->getValue($product); + if ($value instanceof Phrase) { + $value = (string) $value; + } + $value = $value ? $value : null; + $data[] = [ + 'value' => $value, + 'code' => $attribute->getAttributeCode(), + ]; + } + $this->attributeValueMap[$productId] = $data; + } + return $this->attributeValueMap; + } +} diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/Product/CustomAttributes.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/Product/CustomAttributes.php new file mode 100644 index 0000000000000..45b6556e96293 --- /dev/null +++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/Product/CustomAttributes.php @@ -0,0 +1,63 @@ +attributesCollection = $attributesCollection; + $this->valueFactory = $valueFactory; + } + + /** + * @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')); + } + $product = $value['model']; + + $this->attributesCollection->addProductId((int)$product->getId()); + $productId = $product->getId(); + $result = function () use ($productId) { + return $this->attributesCollection->getAttributesValueByProductId((int)$productId); + }; + return $this->valueFactory->create($result); + } +} diff --git a/app/code/Magento/CatalogGraphQl/etc/schema.graphqls b/app/code/Magento/CatalogGraphQl/etc/schema.graphqls index 0817987a383da..d1aeb2f4b6703 100644 --- a/app/code/Magento/CatalogGraphQl/etc/schema.graphqls +++ b/app/code/Magento/CatalogGraphQl/etc/schema.graphqls @@ -123,6 +123,12 @@ interface ProductInterface @typeResolver(class: "Magento\\CatalogGraphQl\\Model\ categories: [CategoryInterface] @doc(description: "The categories assigned to a product.") @resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\Categories") @cache(cacheIdentity: "Magento\\CatalogGraphQl\\Model\\Resolver\\Category\\CategoriesIdentity") canonical_url: String @doc(description: "The relative canonical URL. This value is returned only if the system setting 'Use Canonical Link Meta Tag For Products' is enabled.") @resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\Product\\CanonicalUrl") media_gallery: [MediaGalleryInterface] @doc(description: "An array of media gallery objects.") @resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\Product\\MediaGallery") + custom_attributes: [CustomAttribute] @doc(description: "An array of custom product attributes.") @resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\Product\\CustomAttributes") +} + +type CustomAttribute { + code: String! + value: String } interface PhysicalProductInterface @typeResolver(class: "Magento\\CatalogGraphQl\\Model\\ProductInterfaceTypeResolverComposite") @doc(description: "Contains attributes specific to tangible products.") { diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductsWithCustomAttributesTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductsWithCustomAttributesTest.php new file mode 100644 index 0000000000000..af809f425c8c2 --- /dev/null +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductsWithCustomAttributesTest.php @@ -0,0 +1,106 @@ +cleanCache()) { + $this->fail('Cache could not be cleaned properly.'); + } + + $multiSelectAttribute = "multiselect_attribute"; + $value = 'Option 2, Option 3, Option 4 "!@#$%^&*'; + $query = <<graphQlQuery($query); + $this->assertArrayHasKey('products', $response); + $this->assertArrayHasKey('items', $response['products']); + $this->assertCount(1, $response['products']['items']); + $this->assertArrayHasKey(0, $response['products']['items']); + $this->assertArrayHasKey('custom_attributes', $response['products']['items'][0]); + $customAttributes = $response['products']['items'][0]['custom_attributes']; + $this->assertCustomAttribute($customAttributes, $multiSelectAttribute, $value); + } + + /** + * @magentoApiDataFixture Magento/Catalog/_files/product_simple_with_custom_attribute.php + */ + public function testQueryProductWithTextCustomAttribute() + { + if (!$this->cleanCache()) { + $this->fail('Cache could not be cleaned properly.'); + } + $prductSku = 'simple'; + $textAttribute = "attribute_code_custom"; + $value = 'customAttributeValue'; + $query = <<graphQlQuery($query); + $this->assertArrayHasKey('products', $response); + $this->assertArrayHasKey('items', $response['products']); + $this->assertCount(1, $response['products']['items']); + $this->assertArrayHasKey(0, $response['products']['items']); + $this->assertArrayHasKey('custom_attributes', $response['products']['items'][0]); + $customAttributes = $response['products']['items'][0]['custom_attributes']; + $this->assertCustomAttribute($customAttributes, $textAttribute, $value); + } + + /** + * @param array $customAttributes + * @param string $searchAttribute + * @param string $actualValue + */ + private function assertCustomAttribute($customAttributes, $searchAttribute, $actualValue) + { + $flag = false; + $customAttributeValue = ""; + foreach ($customAttributes as $attributes) { + if ($attributes['code'] == $searchAttribute) { + $flag = true; + $customAttributeValue = $attributes['value']; + break; + } + } + $this->assertStringContainsString($actualValue, $customAttributeValue); + $this->assertEquals(true, $flag); + } +}