diff --git a/app/code/Magento/Catalog/Model/ResourceModel/AbstractResource.php b/app/code/Magento/Catalog/Model/ResourceModel/AbstractResource.php index 3d7f863b7c0d3..3946be32184ec 100644 --- a/app/code/Magento/Catalog/Model/ResourceModel/AbstractResource.php +++ b/app/code/Magento/Catalog/Model/ResourceModel/AbstractResource.php @@ -15,6 +15,7 @@ /** * Catalog entity abstract model * + * phpcs:disable Magento2.Classes.AbstractApi * @api * * @SuppressWarnings(PHPMD.CouplingBetweenObjects) @@ -468,7 +469,7 @@ protected function _getOrigObject($object) * * @param AbstractAttribute $attribute * @param mixed $value New value of the attribute. - * @param array &$origData + * @param array $origData * @return bool */ protected function _canUpdateAttribute(AbstractAttribute $attribute, $value, array &$origData) @@ -560,15 +561,19 @@ public function getAttributeRawValue($entityId, $attribute, $store) $store = (int) $store; if ($typedAttributes) { foreach ($typedAttributes as $table => $_attributes) { + $defaultJoinCondition = [ + $connection->quoteInto('default_value.attribute_id IN (?)', array_keys($_attributes)), + "default_value.{$this->getLinkField()} = e.{$this->getLinkField()}", + 'default_value.store_id = 0', + ]; + $select = $connection->select() - ->from(['default_value' => $table], ['attribute_id']) - ->join( - ['e' => $this->getTable($this->getEntityTable())], - 'e.' . $this->getLinkField() . ' = ' . 'default_value.' . $this->getLinkField(), - '' - )->where('default_value.attribute_id IN (?)', array_keys($_attributes)) - ->where("e.entity_id = :entity_id") - ->where('default_value.store_id = ?', 0); + ->from(['e' => $this->getTable($this->getEntityTable())], []) + ->joinLeft( + ['default_value' => $table], + implode(' AND ', $defaultJoinCondition), + [] + )->where("e.entity_id = :entity_id"); $bind = ['entity_id' => $entityId]; @@ -578,6 +583,11 @@ public function getAttributeRawValue($entityId, $attribute, $store) 'default_value.value', 'store_value.value' ); + $attributeIdExpr = $connection->getCheckSql( + 'store_value.attribute_id IS NULL', + 'default_value.attribute_id', + 'store_value.attribute_id' + ); $joinCondition = [ $connection->quoteInto('store_value.attribute_id IN (?)', array_keys($_attributes)), "store_value.{$this->getLinkField()} = e.{$this->getLinkField()}", @@ -587,23 +597,28 @@ public function getAttributeRawValue($entityId, $attribute, $store) $select->joinLeft( ['store_value' => $table], implode(' AND ', $joinCondition), - ['attr_value' => $valueExpr] + ['attribute_id' => $attributeIdExpr, 'attr_value' => $valueExpr] ); $bind['store_id'] = $store; } else { - $select->columns(['attr_value' => 'value'], 'default_value'); + $select->columns( + ['attribute_id' => 'attribute_id', 'attr_value' => 'value'], + 'default_value' + ); } $result = $connection->fetchPairs($select, $bind); foreach ($result as $attrId => $value) { - $attrCode = $typedAttributes[$table][$attrId]; - $attributesData[$attrCode] = $value; + if ($attrId !== '') { + $attrCode = $typedAttributes[$table][$attrId]; + $attributesData[$attrCode] = $value; + } } } } - if (is_array($attributesData) && sizeof($attributesData) == 1) { + if (is_array($attributesData) && count($attributesData) == 1) { $attributesData = array_shift($attributesData); } diff --git a/dev/tests/integration/testsuite/Magento/Catalog/Model/ResourceModel/ProductTest.php b/dev/tests/integration/testsuite/Magento/Catalog/Model/ResourceModel/ProductTest.php old mode 100644 new mode 100755 index 476f01eb277df..e218c508b7d3e --- a/dev/tests/integration/testsuite/Magento/Catalog/Model/ResourceModel/ProductTest.php +++ b/dev/tests/integration/testsuite/Magento/Catalog/Model/ResourceModel/ProductTest.php @@ -9,7 +9,17 @@ use Magento\Framework\ObjectManagerInterface; use Magento\TestFramework\Helper\Bootstrap; use PHPUnit\Framework\TestCase; +use Magento\Framework\Exception\NoSuchEntityException; +use Magento\Framework\Exception\CouldNotSaveException; +use Magento\Framework\Exception\InputException; +use Magento\Framework\Exception\StateException; +/** + * Tests product resource model + * + * @see \Magento\Catalog\Model\ResourceModel\Product + * @see \Magento\Catalog\Model\ResourceModel\AbstractResource + */ class ProductTest extends TestCase { /** @@ -53,6 +63,87 @@ public function testGetAttributeRawValue() self::assertEquals($product->getName(), $actual); } + /** + * @magentoAppArea adminhtml + * @magentoDataFixture Magento/Catalog/_files/product_simple_with_custom_store_scope_attribute.php + * @throws NoSuchEntityException + * @throws CouldNotSaveException + * @throws InputException + * @throws StateException + */ + public function testGetAttributeRawValueGetDefault() + { + $product = $this->productRepository->get('simple_with_store_scoped_custom_attribute', true, 0, true); + $product->setCustomAttribute('store_scoped_attribute_code', 'default_value'); + $this->productRepository->save($product); + + $actual = $this->model->getAttributeRawValue($product->getId(), 'store_scoped_attribute_code', 1); + $this->assertEquals('default_value', $actual); + } + + /** + * @magentoAppArea adminhtml + * @magentoDataFixture Magento/Catalog/_files/product_simple_with_custom_store_scope_attribute.php + * @throws NoSuchEntityException + * @throws CouldNotSaveException + * @throws InputException + * @throws StateException + */ + public function testGetAttributeRawValueGetStoreSpecificValueNoDefault() + { + $product = $this->productRepository->get('simple_with_store_scoped_custom_attribute', true, 0, true); + $product->setCustomAttribute('store_scoped_attribute_code', null); + $this->productRepository->save($product); + + $product = $this->productRepository->get('simple_with_store_scoped_custom_attribute', true, 1, true); + $product->setCustomAttribute('store_scoped_attribute_code', 'store_value'); + $this->productRepository->save($product); + + $actual = $this->model->getAttributeRawValue($product->getId(), 'store_scoped_attribute_code', 1); + $this->assertEquals('store_value', $actual); + } + + /** + * @magentoAppArea adminhtml + * @magentoDataFixture Magento/Catalog/_files/product_simple_with_custom_store_scope_attribute.php + * @throws NoSuchEntityException + * @throws CouldNotSaveException + * @throws InputException + * @throws StateException + */ + public function testGetAttributeRawValueGetStoreSpecificValueWithDefault() + { + $product = $this->productRepository->get('simple_with_store_scoped_custom_attribute', true, 0, true); + $product->setCustomAttribute('store_scoped_attribute_code', 'default_value'); + $this->productRepository->save($product); + + $product = $this->productRepository->get('simple_with_store_scoped_custom_attribute', true, 1, true); + $product->setCustomAttribute('store_scoped_attribute_code', 'store_value'); + $this->productRepository->save($product); + + $actual = $this->model->getAttributeRawValue($product->getId(), 'store_scoped_attribute_code', 1); + $this->assertEquals('store_value', $actual); + } + + /** + * @magentoAppArea adminhtml + * @magentoDataFixture Magento/Catalog/_files/product_simple_with_custom_store_scope_attribute.php + * @throws NoSuchEntityException + * @throws CouldNotSaveException + * @throws InputException + * @throws StateException + * @throws NoSuchEntityException + */ + public function testGetAttributeRawValueGetStoreValueFallbackToDefault() + { + $product = $this->productRepository->get('simple_with_store_scoped_custom_attribute', true, 0, true); + $product->setCustomAttribute('store_scoped_attribute_code', 'default_value'); + $this->productRepository->save($product); + + $actual = $this->model->getAttributeRawValue($product->getId(), 'store_scoped_attribute_code', 1); + $this->assertEquals('default_value', $actual); + } + /** * @magentoAppArea adminhtml * @magentoDataFixture Magento/Catalog/_files/product_special_price.php diff --git a/dev/tests/integration/testsuite/Magento/Catalog/_files/product_simple_with_custom_store_scope_attribute.php b/dev/tests/integration/testsuite/Magento/Catalog/_files/product_simple_with_custom_store_scope_attribute.php new file mode 100755 index 0000000000000..183d531f947e8 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Catalog/_files/product_simple_with_custom_store_scope_attribute.php @@ -0,0 +1,72 @@ +get(ProductRepositoryInterface::class); +/** @var ProductFactory $productFactory */ +$productFactory = $objectManager->get(ProductFactory::class); +/** @var ProductAttributeRepositoryInterface $attributeRepository */ +$attributeRepository = $objectManager->get(ProductAttributeRepositoryInterface::class); + + +/** @var $installer CategorySetup */ +$installer = $objectManager->create(CategorySetup::class); +$entityModel = $objectManager->create(Entity::class); +$attributeSetId = $installer->getAttributeSetId(Product::ENTITY, 'Default'); +$entityTypeId = $entityModel->setType(Product::ENTITY) + ->getTypeId(); +$groupId = $installer->getDefaultAttributeGroupId($entityTypeId, $attributeSetId); + +/** @var ProductAttributeInterface $attribute */ +$attribute = $objectManager->create(ProductAttributeInterface::class); + +$attribute->setAttributeCode('store_scoped_attribute_code') + ->setEntityTypeId($entityTypeId) + ->setIsVisible(true) + ->setFrontendInput('text') + ->setIsFilterable(1) + ->setIsUserDefined(1) + ->setUsedInProductListing(1) + ->setBackendType('varchar') + ->setIsUsedInGrid(1) + ->setIsVisibleInGrid(1) + ->setIsFilterableInGrid(1) + ->setFrontendLabel('nobody cares') + ->setAttributeGroupId($groupId) + ->setAttributeSetId(4); + +$attributeRepository->save($attribute); + +$product = $productFactory->create() + ->setTypeId(Type::TYPE_SIMPLE) + ->setAttributeSetId(4) + ->setName('Simple With Store Scoped Custom Attribute') + ->setSku('simple_with_store_scoped_custom_attribute') + ->setPrice(100) + ->setVisibility(1) + ->setStockData( + [ + 'use_config_manage_stock' => 1, + 'qty' => 100, + 'is_in_stock' => 1, + ] + ) + ->setStatus(1); +$product->setCustomAttribute('store_scoped_attribute_code', 'default_value'); +$productRepository->save($product); diff --git a/dev/tests/integration/testsuite/Magento/Catalog/_files/product_simple_with_custom_store_scope_attribute_rollback.php b/dev/tests/integration/testsuite/Magento/Catalog/_files/product_simple_with_custom_store_scope_attribute_rollback.php new file mode 100755 index 0000000000000..54c832dd6a6ff --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Catalog/_files/product_simple_with_custom_store_scope_attribute_rollback.php @@ -0,0 +1,41 @@ +get(ProductRepositoryInterface::class); +/** @var ProductAttributeRepositoryInterface $attributeRepository */ +$attributeRepository = $objectManager->get(ProductAttributeRepositoryInterface::class); +/** @var Registry $registry */ +$registry = $objectManager->get(Registry::class); + +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', true); + +try { + /** @var \Magento\Catalog\Api\Data\ProductInterface $product */ + $product = $productRepository->get('simple_with_store_scoped_custom_attribute'); + $productRepository->delete($product); +} catch (NoSuchEntityException $e) { +} + +try { + /** @var \Magento\Catalog\Api\Data\ProductAttributeInterface $attribute */ + $attribute = $attributeRepository->get('store_scoped_attribute_code'); + $attributeRepository->delete($attribute); +} catch (NoSuchEntityException $e) { +} + +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', false);