Skip to content

Commit a479af5

Browse files
MC-21755: Changing Attributes sets doesn't remove attribute from layered navigation and search
1 parent 5b54f06 commit a479af5

File tree

3 files changed

+118
-14
lines changed

3 files changed

+118
-14
lines changed

app/code/Magento/Catalog/Model/ResourceModel/Product.php

Lines changed: 76 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,12 @@
66
namespace Magento\Catalog\Model\ResourceModel;
77

88
use Magento\Catalog\Model\ResourceModel\Product\Website\Link as ProductWebsiteLink;
9+
use Magento\Eav\Api\AttributeManagementInterface;
910
use Magento\Framework\App\ObjectManager;
1011
use Magento\Catalog\Model\Indexer\Category\Product\TableMaintainer;
1112
use Magento\Catalog\Model\Product as ProductEntity;
1213
use Magento\Eav\Model\Entity\Attribute\UniqueValidationInterface;
14+
use Magento\Framework\DataObject;
1315
use Magento\Framework\EntityManager\EntityManager;
1416
use Magento\Framework\Model\AbstractModel;
1517

@@ -93,6 +95,11 @@ class Product extends AbstractResource
9395
*/
9496
private $tableMaintainer;
9597

98+
/**
99+
* @var AttributeManagementInterface
100+
*/
101+
private $eavAttributeManagement;
102+
96103
/**
97104
* @param \Magento\Eav\Model\Entity\Context $context
98105
* @param \Magento\Store\Model\StoreManagerInterface $storeManager
@@ -106,7 +113,7 @@ class Product extends AbstractResource
106113
* @param array $data
107114
* @param TableMaintainer|null $tableMaintainer
108115
* @param UniqueValidationInterface|null $uniqueValidator
109-
*
116+
* @param AttributeManagementInterface|null $eavAttributeManagement
110117
* @SuppressWarnings(PHPMD.ExcessiveParameterList)
111118
*/
112119
public function __construct(
@@ -121,7 +128,8 @@ public function __construct(
121128
\Magento\Catalog\Model\Product\Attribute\DefaultAttributes $defaultAttributes,
122129
$data = [],
123130
TableMaintainer $tableMaintainer = null,
124-
UniqueValidationInterface $uniqueValidator = null
131+
UniqueValidationInterface $uniqueValidator = null,
132+
AttributeManagementInterface $eavAttributeManagement = null
125133
) {
126134
$this->_categoryCollectionFactory = $categoryCollectionFactory;
127135
$this->_catalogCategory = $catalogCategory;
@@ -138,6 +146,8 @@ public function __construct(
138146
);
139147
$this->connectionName = 'catalog';
140148
$this->tableMaintainer = $tableMaintainer ?: ObjectManager::getInstance()->get(TableMaintainer::class);
149+
$this->eavAttributeManagement = $eavAttributeManagement
150+
?? ObjectManager::getInstance()->get(AttributeManagementInterface::class);
141151
}
142152

143153
/**
@@ -268,10 +278,10 @@ public function getIdBySku($sku)
268278
/**
269279
* Process product data before save
270280
*
271-
* @param \Magento\Framework\DataObject $object
281+
* @param DataObject $object
272282
* @return $this
273283
*/
274-
protected function _beforeSave(\Magento\Framework\DataObject $object)
284+
protected function _beforeSave(DataObject $object)
275285
{
276286
$self = parent::_beforeSave($object);
277287
/**
@@ -286,15 +296,73 @@ protected function _beforeSave(\Magento\Framework\DataObject $object)
286296
/**
287297
* Save data related with product
288298
*
289-
* @param \Magento\Framework\DataObject $product
299+
* @param DataObject $product
290300
* @return $this
291301
*/
292-
protected function _afterSave(\Magento\Framework\DataObject $product)
302+
protected function _afterSave(DataObject $product)
293303
{
304+
$this->removeNotInSetAttributeValues($product);
294305
$this->_saveWebsiteIds($product)->_saveCategories($product);
295306
return parent::_afterSave($product);
296307
}
297308

309+
/**
310+
* Remove attribute values that absent in product attribute set
311+
*
312+
* @param DataObject $product
313+
* @return DataObject
314+
*/
315+
private function removeNotInSetAttributeValues(DataObject $product): DataObject
316+
{
317+
$oldAttributeSetId = $product->getOrigData(ProductEntity::ATTRIBUTE_SET_ID);
318+
if ($oldAttributeSetId && $product->dataHasChangedFor(ProductEntity::ATTRIBUTE_SET_ID)) {
319+
$newAttributes = $product->getAttributes();
320+
$newAttributesCodes = array_keys($newAttributes);
321+
$oldAttributes = $this->eavAttributeManagement->getAttributes(
322+
ProductEntity::ENTITY,
323+
$oldAttributeSetId
324+
);
325+
$oldAttributesCodes = [];
326+
foreach ($oldAttributes as $oldAttribute) {
327+
$oldAttributesCodes[] = $oldAttribute->getAttributecode();
328+
}
329+
$notInSetAttributeCodes = array_diff($oldAttributesCodes, $newAttributesCodes);
330+
if (!empty($notInSetAttributeCodes)) {
331+
$this->deleteSelectedEntityAttributeRows($product, $notInSetAttributeCodes);
332+
}
333+
}
334+
335+
return $product;
336+
}
337+
338+
/**
339+
* Clear selected entity attribute rows
340+
*
341+
* @param DataObject $product
342+
* @param array $attributeCodes
343+
* @return void
344+
*/
345+
private function deleteSelectedEntityAttributeRows(DataObject $product, array $attributeCodes): void
346+
{
347+
$backendTables = [];
348+
foreach ($attributeCodes as $attributeCode) {
349+
$attribute = $this->getAttribute($attributeCode);
350+
$backendTable = $attribute->getBackendTable();
351+
if (!$attribute->isStatic() && $backendTable) {
352+
$backendTables[$backendTable][] = $attribute->getId();
353+
}
354+
}
355+
356+
$entityIdField = $this->getLinkField();
357+
$entityId = $product->getData($entityIdField);
358+
foreach ($backendTables as $backendTable => $attributes) {
359+
$connection = $this->getConnection();
360+
$where = $connection->quoteInto('attribute_id IN (?)', $attributes);
361+
$where .= $connection->quoteInto(" AND {$entityIdField} = ?", $entityId);
362+
$connection->delete($backendTable, $where);
363+
}
364+
}
365+
298366
/**
299367
* @inheritdoc
300368
*/
@@ -337,12 +405,12 @@ protected function _saveWebsiteIds($product)
337405
/**
338406
* Save product category relations
339407
*
340-
* @param \Magento\Framework\DataObject $object
408+
* @param DataObject $object
341409
* @return $this
342410
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
343411
* @deprecated 101.1.0
344412
*/
345-
protected function _saveCategories(\Magento\Framework\DataObject $object)
413+
protected function _saveCategories(DataObject $object)
346414
{
347415
return $this;
348416
}

dev/tests/integration/testsuite/Magento/Catalog/Model/ResourceModel/ProductTest.php

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,12 @@
66
namespace Magento\Catalog\Model\ResourceModel;
77

88
use Magento\Catalog\Api\ProductRepositoryInterface;
9+
use Magento\Catalog\Model\ResourceModel\Product\Action;
10+
use Magento\Eav\Api\Data\AttributeSetInterface;
11+
use Magento\Eav\Model\AttributeSetRepository;
12+
use Magento\Framework\Api\SearchCriteriaBuilder;
913
use Magento\Framework\ObjectManagerInterface;
14+
use Magento\TestFramework\Eav\Model\GetAttributeSetByName;
1015
use Magento\TestFramework\Helper\Bootstrap;
1116
use PHPUnit\Framework\TestCase;
1217
use Magento\Framework\Exception\NoSuchEntityException;
@@ -164,4 +169,33 @@ public function testUpdateStoreSpecificSpecialPrice()
164169
$product = $this->productRepository->get('simple', false, 0, true);
165170
$this->assertEquals(5.99, $product->getSpecialPrice());
166171
}
172+
173+
/**
174+
* Checks that product has no attribute values for attributes not assigned to the product's attribute set.
175+
*
176+
* @magentoDataFixture Magento/Catalog/_files/product_simple.php
177+
* @magentoDataFixture Magento/Catalog/_files/attribute_set_with_image_attribute.php
178+
*/
179+
public function testChangeAttributeSet()
180+
{
181+
$attributeCode = 'funny_image';
182+
/** @var GetAttributeSetByName $attributeSetModel */
183+
$attributeSetModel = $this->objectManager->get(GetAttributeSetByName::class);
184+
$attributeSet = $attributeSetModel->execute('attribute_set_with_media_attribute');
185+
186+
$product = $this->productRepository->get('simple', true, 1, true);
187+
$product->setAttributeSetId($attributeSet->getAttributeSetId());
188+
$this->productRepository->save($product);
189+
$product->setData($attributeCode, 'test');
190+
$this->model->saveAttribute($product, $attributeCode);
191+
192+
$product = $this->productRepository->get('simple', true, 1, true);
193+
$this->assertEquals('test', $product->getData($attributeCode));
194+
195+
$product->setAttributeSetId($product->getDefaultAttributeSetId());
196+
$this->productRepository->save($product);
197+
198+
$attribute = $this->model->getAttributeRawValue($product->getId(), $attributeCode, 1);
199+
$this->assertEmpty($attribute);
200+
}
167201
}

dev/tests/integration/testsuite/Magento/Catalog/_files/attribute_set_with_image_attribute_rollback.php

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,10 @@
1717
$attributeCollection->setCodeFilter('funny_image');
1818
$attributeCollection->setEntityTypeFilter($entityType->getId());
1919
$attributeCollection->setPageSize(1);
20-
$attributeCollection->load();
21-
$attribute = $attributeCollection->fetchItem();
22-
$attribute->delete();
20+
$attribute = $attributeCollection->getFirstItem();
21+
if ($attribute->getId()) {
22+
$attribute->delete();
23+
}
2324

2425
// remove attribute set
2526

@@ -31,8 +32,9 @@
3132
$attributeSetCollection->addFilter('entity_type_id', $entityType->getId());
3233
$attributeSetCollection->setOrder('attribute_set_id'); // descending is default value
3334
$attributeSetCollection->setPageSize(1);
34-
$attributeSetCollection->load();
3535

3636
/** @var \Magento\Eav\Model\Entity\Attribute\Set $attributeSet */
37-
$attributeSet = $attributeSetCollection->fetchItem();
38-
$attributeSet->delete();
37+
$attributeSet = $attributeSetCollection->getFirstItem();
38+
if ($attributeSet->getId()) {
39+
$attributeSet->delete();
40+
}

0 commit comments

Comments
 (0)