diff --git a/app/code/Magento/CatalogGraphQl/DataProvider/Product/LayeredNavigation/AttributeOptionProvider.php b/app/code/Magento/CatalogGraphQl/DataProvider/Product/LayeredNavigation/AttributeOptionProvider.php index 320e0adc29b9f..140659abfbfe6 100644 --- a/app/code/Magento/CatalogGraphQl/DataProvider/Product/LayeredNavigation/AttributeOptionProvider.php +++ b/app/code/Magento/CatalogGraphQl/DataProvider/Product/LayeredNavigation/AttributeOptionProvider.php @@ -8,6 +8,7 @@ namespace Magento\CatalogGraphQl\DataProvider\Product\LayeredNavigation; use Magento\Framework\App\ResourceConnection; +use Magento\Store\Model\Store; /** * Fetch product attribute option data including attribute info @@ -41,16 +42,18 @@ public function __construct(ResourceConnection $resourceConnection) * Get option data. Return list of attributes with option data * * @param array $optionIds + * @param int|null $storeId * @param array $attributeCodes * @return array * @throws \Zend_Db_Statement_Exception */ - public function getOptions(array $optionIds, array $attributeCodes = []): array + public function getOptions(array $optionIds, ?int $storeId, array $attributeCodes = []): array { if (!$optionIds) { return []; } + $storeId = $storeId ?: Store::DEFAULT_STORE_ID; $connection = $this->resourceConnection->getConnection(); $select = $connection->select() ->from( @@ -70,9 +73,21 @@ public function getOptions(array $optionIds, array $attributeCodes = []): array ['option_value' => $this->resourceConnection->getTableName('eav_attribute_option_value')], 'options.option_id = option_value.option_id', [ - 'option_label' => 'option_value.value', 'option_id' => 'option_value.option_id', ] + )->joinLeft( + ['option_value_store' => $this->resourceConnection->getTableName('eav_attribute_option_value')], + "options.option_id = option_value_store.option_id AND option_value_store.store_id = {$storeId}", + [ + 'option_label' => $connection->getCheckSql( + 'option_value_store.value_id > 0', + 'option_value_store.value', + 'option_value.value' + ) + ] + )->where( + 'a.attribute_id = options.attribute_id AND option_value.store_id = ?', + Store::DEFAULT_STORE_ID ); $select->where('option_value.option_id IN (?)', $optionIds); diff --git a/app/code/Magento/CatalogGraphQl/DataProvider/Product/LayeredNavigation/Builder/Attribute.php b/app/code/Magento/CatalogGraphQl/DataProvider/Product/LayeredNavigation/Builder/Attribute.php index 0ec65c88024f2..105e91320de49 100644 --- a/app/code/Magento/CatalogGraphQl/DataProvider/Product/LayeredNavigation/Builder/Attribute.php +++ b/app/code/Magento/CatalogGraphQl/DataProvider/Product/LayeredNavigation/Builder/Attribute.php @@ -71,7 +71,7 @@ public function __construct( */ public function build(AggregationInterface $aggregation, ?int $storeId): array { - $attributeOptions = $this->getAttributeOptions($aggregation); + $attributeOptions = $this->getAttributeOptions($aggregation, $storeId); // build layer per attribute $result = []; @@ -133,10 +133,11 @@ private function isBucketEmpty(?BucketInterface $bucket): bool * Get list of attributes with options * * @param AggregationInterface $aggregation + * @param int|null $storeId * @return array * @throws \Zend_Db_Statement_Exception */ - private function getAttributeOptions(AggregationInterface $aggregation): array + private function getAttributeOptions(AggregationInterface $aggregation, ?int $storeId): array { $attributeOptionIds = []; $attributes = []; @@ -154,6 +155,6 @@ function (AggregationValueInterface $value) { return []; } - return $this->attributeOptionProvider->getOptions(\array_merge(...$attributeOptionIds), $attributes); + return $this->attributeOptionProvider->getOptions(\array_merge(...$attributeOptionIds), $storeId, $attributes); } } diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductAttributeStoreOptionsTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductAttributeStoreOptionsTest.php new file mode 100644 index 0000000000000..97c6c41ad6397 --- /dev/null +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductAttributeStoreOptionsTest.php @@ -0,0 +1,101 @@ +attributeLabelTest('Option Default Store'); + $this->attributeLabelTest('Option Test Store', ['Store' => 'test']); + } + + /** + * @param $expectedLabel + * @param array $headers + * @throws LocalizedException + * @throws Exception + */ + private function attributeLabelTest($expectedLabel, array $headers = []): void + { + /** @var Config $eavConfig */ + $eavConfig = Bootstrap::getObjectManager()->get(Config::class); + $attributeCode = 'test_configurable'; + $attribute = $eavConfig->getAttribute('catalog_product', $attributeCode); + + /** @var AttributeOptionInterface[] $options */ + $options = $attribute->getOptions(); + array_shift($options); + $optionValues = []; + + foreach ($options as $option) { + $optionValues[] = [ + 'value' => $option->getValue(), + ]; + } + + $expectedOptions = [ + [ + 'label' => $expectedLabel, + 'value' => $optionValues[0]['value'] + ] + ]; + + $query = <<graphQlQuery($query, [], '', $headers); + $this->assertNotEmpty($response['products']['aggregations']); + $actualAttributes = $response['products']['aggregations']; + $actualAttributeOptions = []; + + foreach ($actualAttributes as $actualAttribute) { + if ($actualAttribute['attribute_code'] === $attributeCode) { + $actualAttributeOptions = $actualAttribute['options']; + } + } + + $this->assertNotEmpty($actualAttributeOptions); + + foreach ($actualAttributeOptions as $key => $actualAttributeOption) { + if ($actualAttributeOption['value'] === $expectedOptions[$key]['value']) { + $this->assertEquals($actualAttributeOption['label'], $expectedOptions[$key]['label']); + } + } + } +} diff --git a/dev/tests/integration/testsuite/Magento/Catalog/_files/products_with_layered_navigation_attribute_store_options.php b/dev/tests/integration/testsuite/Magento/Catalog/_files/products_with_layered_navigation_attribute_store_options.php new file mode 100644 index 0000000000000..c2ebfa4389ab2 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Catalog/_files/products_with_layered_navigation_attribute_store_options.php @@ -0,0 +1,162 @@ +get(\Magento\Eav\Model\Config::class); +$attribute = $eavConfig->getAttribute('catalog_product', 'test_configurable'); + +$eavConfig->clear(); + +/** @var $installer \Magento\Catalog\Setup\CategorySetup */ +$installer = Bootstrap::getObjectManager()->create(\Magento\Catalog\Setup\CategorySetup::class); + +if (!$attribute->getId()) { + + /** @var $attribute \Magento\Catalog\Model\ResourceModel\Eav\Attribute */ + $attribute = Bootstrap::getObjectManager()->create( + \Magento\Catalog\Model\ResourceModel\Eav\Attribute::class + ); + + /** @var AttributeRepositoryInterface $attributeRepository */ + $attributeRepository = Bootstrap::getObjectManager()->create(AttributeRepositoryInterface::class); + + /** @var $store \Magento\Store\Model\Store */ + $store = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create(\Magento\Store\Model\Store::class); + $store = $store->load('test', 'code'); + + $attribute->setData( + [ + 'attribute_code' => 'test_configurable', + 'entity_type_id' => $installer->getEntityTypeId('catalog_product'), + 'is_global' => 1, + 'is_user_defined' => 1, + 'frontend_input' => 'select', + 'is_unique' => 0, + 'is_required' => 0, + 'is_searchable' => 1, + 'is_visible_in_advanced_search' => 1, + 'is_comparable' => 1, + 'is_filterable' => 1, + 'is_filterable_in_search' => 1, + 'is_used_for_promo_rules' => 0, + 'is_html_allowed_on_front' => 1, + 'is_visible_on_front' => 1, + 'used_in_product_listing' => 1, + 'used_for_sort_by' => 1, + 'frontend_label' => ['Test Configurable'], + 'backend_type' => 'int', + 'option' => [ + 'value' => ['option_0' => [ + Store::DEFAULT_STORE_ID => 'Option Admin Store', + Store::DISTRO_STORE_ID => 'Option Default Store', + $store->getId() => 'Option Test Store' + ], 'option_1' => ['Option 2']], + 'order' => ['option_0' => 1, 'option_1' => 2], + ], + 'default' => ['option_0'] + ] + ); + + $attributeRepository->save($attribute); + + /* Assign attribute to attribute set */ + $installer->addAttributeToGroup('catalog_product', 'Default', 'General', $attribute->getId()); +} + +$eavConfig->clear(); + +/** @var \Magento\Framework\ObjectManagerInterface $objectManager */ +$objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); + +/** @var $product \Magento\Catalog\Model\Product */ +$product = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create(\Magento\Catalog\Model\Product::class); +$product->isObjectNew(true); +$product->setTypeId(\Magento\Catalog\Model\Product\Type::TYPE_SIMPLE) + ->setId(10) + ->setAttributeSetId(4) + ->setName('Simple Product1') + ->setSku('simple1') + ->setTaxClassId('none') + ->setDescription('description') + ->setShortDescription('short description') + ->setOptionsContainer('container1') + ->setMsrpDisplayActualPriceType(\Magento\Msrp\Model\Product\Attribute\Source\Type::TYPE_IN_CART) + ->setPrice(10) + ->setWeight(1) + ->setMetaTitle('meta title') + ->setMetaKeyword('meta keyword') + ->setMetaDescription('meta description') + ->setVisibility(\Magento\Catalog\Model\Product\Visibility::VISIBILITY_BOTH) + ->setStatus(\Magento\Catalog\Model\Product\Attribute\Source\Status::STATUS_ENABLED) + ->setWebsiteIds([1]) + ->setCategoryIds([]) + ->setStockData(['use_config_manage_stock' => 1, 'qty' => 100, 'is_qty_decimal' => 0, 'is_in_stock' => 1]) + ->setSpecialPrice('5.99') + ->save(); + +$product = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create(\Magento\Catalog\Model\Product::class); +$product->isObjectNew(true); +$product->setTypeId(\Magento\Catalog\Model\Product\Type::TYPE_SIMPLE) + ->setId(11) + ->setAttributeSetId(4) + ->setName('Simple Product2') + ->setSku('simple2') + ->setTaxClassId('none') + ->setDescription('description') + ->setShortDescription('short description') + ->setOptionsContainer('container1') + ->setMsrpDisplayActualPriceType(\Magento\Msrp\Model\Product\Attribute\Source\Type::TYPE_ON_GESTURE) + ->setPrice(20) + ->setWeight(1) + ->setMetaTitle('meta title') + ->setMetaKeyword('meta keyword') + ->setMetaDescription('meta description') + ->setVisibility(\Magento\Catalog\Model\Product\Visibility::VISIBILITY_BOTH) + ->setStatus(\Magento\Catalog\Model\Product\Attribute\Source\Status::STATUS_ENABLED) + ->setWebsiteIds([1]) + ->setCategoryIds([]) + ->setStockData(['use_config_manage_stock' => 1, 'qty' => 50, 'is_qty_decimal' => 0, 'is_in_stock' => 1]) + ->setSpecialPrice('15.99') + ->save(); + +$category = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create(\Magento\Catalog\Model\Category::class); +$category->isObjectNew(true); +$category->setId( + 333 +)->setCreatedAt( + '2014-06-23 09:50:07' +)->setName( + 'Category 1' +)->setParentId( + 2 +)->setPath( + '1/2/333' +)->setLevel( + 2 +)->setAvailableSortBy( + ['position', 'name'] +)->setDefaultSortBy( + 'name' +)->setIsActive( + true +)->setPosition( + 1 +)->setPostedProducts( + [10 => 10, 11 => 11] +)->save(); + +/** @var \Magento\Indexer\Model\Indexer\Collection $indexerCollection */ +$indexerCollection = Bootstrap::getObjectManager()->get(\Magento\Indexer\Model\Indexer\Collection::class); +$indexerCollection->load(); + +/** @var \Magento\Indexer\Model\Indexer $indexer */ +foreach ($indexerCollection->getItems() as $indexer) { + $indexer->reindexAll(); +} diff --git a/dev/tests/integration/testsuite/Magento/Catalog/_files/products_with_layered_navigation_attribute_store_options_rollback.php b/dev/tests/integration/testsuite/Magento/Catalog/_files/products_with_layered_navigation_attribute_store_options_rollback.php new file mode 100644 index 0000000000000..6793051b5787b --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Catalog/_files/products_with_layered_navigation_attribute_store_options_rollback.php @@ -0,0 +1,50 @@ +get(\Magento\Framework\Registry::class); + +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', true); + +/** @var \Magento\Catalog\Api\ProductRepositoryInterface $productRepository */ +$productRepository = $objectManager->create(\Magento\Catalog\Api\ProductRepositoryInterface::class); + +foreach (['simple1', 'simple2'] as $sku) { + try { + $product = $productRepository->get($sku, false, null, true); + $productRepository->delete($product); + } catch (\Magento\Framework\Exception\NoSuchEntityException $exception) { + //Product already removed + } +} + +$productCollection = \Magento\TestFramework\Helper\Bootstrap::getObjectManager() + ->get(\Magento\Catalog\Model\ResourceModel\Product\Collection::class); +foreach ($productCollection as $product) { + $product->delete(); +} + +/** @var $category \Magento\Catalog\Model\Category */ +$category = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create(\Magento\Catalog\Model\Category::class); +$category->load(333); +if ($category->getId()) { + $category->delete(); +} + +$eavConfig = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->get(\Magento\Eav\Model\Config::class); +$attribute = $eavConfig->getAttribute('catalog_product', 'test_configurable'); +if ($attribute instanceof \Magento\Eav\Model\Entity\Attribute\AbstractAttribute + && $attribute->getId() +) { + $attribute->delete(); +} +$eavConfig->clear(); + +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', false);