Skip to content

Commit 19e1dce

Browse files
committed
ACP2E-156: Merchants can create custom attribute, but it causes an error when trying to save an entity account
- fix - add test
1 parent d3112e5 commit 19e1dce

File tree

5 files changed

+177
-21
lines changed

5 files changed

+177
-21
lines changed
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
declare(strict_types=1);
7+
8+
namespace Magento\Catalog\Model\Product;
9+
10+
use Magento\Eav\Model\Entity\Attribute\AbstractAttribute;
11+
use Magento\Eav\Model\ReservedAttributeCheckerInterface;
12+
13+
/**
14+
* Adapter for \Magento\Catalog\Model\Product\ReservedAttributeList
15+
*
16+
* Is created to implement proper interface and to use api class ReservedAttributeList
17+
* while keeping it backward compatible
18+
* @see \Magento\Catalog\Model\Product\ReservedAttributeList
19+
*/
20+
class ReservedAttributeCheckerAdapter implements ReservedAttributeCheckerInterface
21+
{
22+
/**
23+
* @var ReservedAttributeList
24+
*/
25+
protected $reservedAttributeList;
26+
27+
/**
28+
* @param ReservedAttributeList $reservedAttributeList
29+
*/
30+
public function __construct(
31+
ReservedAttributeList $reservedAttributeList
32+
) {
33+
$this->reservedAttributeList = $reservedAttributeList;
34+
}
35+
36+
/**
37+
* @inheritdoc
38+
*/
39+
public function isReservedAttribute(AbstractAttribute $attribute): bool
40+
{
41+
return $this->reservedAttributeList->isReservedAttribute($attribute);
42+
}
43+
}

app/code/Magento/Catalog/etc/di.xml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1333,4 +1333,13 @@
13331333
<argument name="imageResizeScheduler" xsi:type="object">Magento\MediaStorage\Service\ImageResizeScheduler\Proxy</argument>
13341334
</arguments>
13351335
</type>
1336+
<type name="Magento\Eav\Model\ReservedAttributeChecker">
1337+
<arguments>
1338+
<argument name="validators" xsi:type="array">
1339+
<item name="catalog_product" xsi:type="array">
1340+
<item name="product_reserved_attribute_codes" xsi:type="object">\Magento\Catalog\Model\Product\ReservedAttributeCheckerAdapter</item>
1341+
</item>
1342+
</argument>
1343+
</arguments>
1344+
</type>
13361345
</config>
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
declare(strict_types=1);
7+
8+
namespace Magento\Eav\Model;
9+
10+
use Magento\Eav\Model\Entity\Attribute\AbstractAttribute;
11+
use Magento\Framework\Exception\LocalizedException;
12+
13+
/**
14+
* Composite Reserved Attribute Checker
15+
*
16+
* Iterates through individual Reserved Attribute Checkers to check whether attribute is reserved by system
17+
*/
18+
class ReservedAttributeChecker implements ReservedAttributeCheckerInterface
19+
{
20+
/**
21+
* @var ReservedAttributeCheckerInterface[][]
22+
*/
23+
private $validators;
24+
25+
/**
26+
* @param array $validators
27+
*/
28+
public function __construct(
29+
$validators = []
30+
) {
31+
$this->validators = $validators;
32+
}
33+
34+
/**
35+
* @inheritdoc
36+
*/
37+
public function isReservedAttribute(AbstractAttribute $attribute): bool
38+
{
39+
$isReserved = false;
40+
try {
41+
$attribute->getEntityType();
42+
} catch (LocalizedException $exception) {
43+
$isReserved = false;
44+
}
45+
$validators = $this->validators[$attribute->getEntityType()->getEntityTypeCode()] ?? [];
46+
foreach ($validators as $validator) {
47+
$isReserved = $validator->isReservedAttribute($attribute);
48+
if ($isReserved === true) {
49+
break;
50+
}
51+
}
52+
53+
return $isReserved;
54+
}
55+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
declare(strict_types=1);
7+
8+
namespace Magento\Eav\Model;
9+
10+
use Magento\Eav\Model\Entity\Attribute\AbstractAttribute;
11+
12+
/**
13+
* Checks whether attribute is reserved by system
14+
*/
15+
interface ReservedAttributeCheckerInterface
16+
{
17+
/**
18+
* Check whether attribute is reserved by system.
19+
*
20+
* Check that given user defined EAV attribute doesn't contain the attribute code
21+
* that matches to a getter field related to related model (e.g. product, category, customer...)
22+
*
23+
* @param AbstractAttribute $attribute
24+
* @return bool
25+
*/
26+
public function isReservedAttribute(AbstractAttribute $attribute): bool;
27+
}

app/code/Magento/Eav/Setup/EavSetup.php

Lines changed: 43 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,12 @@
66

77
namespace Magento\Eav\Setup;
88

9+
use Magento\Eav\Model\AttributeFactory;
10+
use Magento\Eav\Model\Config;
11+
use Magento\Eav\Model\Entity\Attribute;
912
use Magento\Eav\Model\Entity\Setup\Context;
1013
use Magento\Eav\Model\Entity\Setup\PropertyMapperInterface;
14+
use Magento\Eav\Model\ReservedAttributeChecker;
1115
use Magento\Eav\Model\ResourceModel\Entity\Attribute\Group\CollectionFactory;
1216
use Magento\Eav\Model\Validator\Attribute\Code;
1317
use Magento\Framework\App\CacheInterface;
@@ -27,57 +31,41 @@
2731
class EavSetup
2832
{
2933
/**
30-
* Cache
31-
*
3234
* @var CacheInterface
3335
*/
3436
private $cache;
3537

3638
/**
37-
* Attribute group collection factory
38-
*
3939
* @var CollectionFactory
4040
*/
4141
private $attrGroupCollectionFactory;
4242

4343
/**
44-
* Attribute mapper
45-
*
4644
* @var PropertyMapperInterface
4745
*/
4846
private $attributeMapper;
4947

5048
/**
51-
* Setup model
52-
*
5349
* @var ModuleDataSetupInterface
5450
*/
5551
private $setup;
5652

5753
/**
58-
* General Attribute Group Name
59-
*
6054
* @var string
6155
*/
6256
private $_generalGroupName = 'General';
6357

6458
/**
65-
* Default attribute group name to id pairs
66-
*
6759
* @var array
6860
*/
6961
private $defaultGroupIdAssociations = ['general' => 1];
7062

7163
/**
72-
* Default attribute group name
73-
*
7464
* @var string
7565
*/
7666
private $_defaultGroupName = 'Default';
7767

7868
/**
79-
* Default attribute set name
80-
*
8169
* @var string
8270
*/
8371
private $_defaultAttributeSetName = 'Default';
@@ -92,6 +80,21 @@ class EavSetup
9280
*/
9381
private $attributeCodeValidator;
9482

83+
/**
84+
* @var ReservedAttributeChecker
85+
*/
86+
private $reservedAttributeChecker;
87+
88+
/**
89+
* @var AttributeFactory
90+
*/
91+
private $attributeFactory;
92+
93+
/**
94+
* @var Config|null
95+
*/
96+
private $eavConfig;
97+
9598
/**
9699
* Init
97100
*
@@ -101,6 +104,9 @@ class EavSetup
101104
* @param CollectionFactory $attrGroupCollectionFactory
102105
* @param Code|null $attributeCodeValidator
103106
* @param AddOptionToAttribute|null $addAttributeOption
107+
* @param ReservedAttributeChecker|null $reservedAttributeChecker
108+
* @param AttributeFactory|null $attributeFactory
109+
* @param Config|null $eavConfig
104110
* @SuppressWarnings(PHPMD.LongVariable)
105111
*/
106112
public function __construct(
@@ -109,17 +115,22 @@ public function __construct(
109115
CacheInterface $cache,
110116
CollectionFactory $attrGroupCollectionFactory,
111117
Code $attributeCodeValidator = null,
112-
AddOptionToAttribute $addAttributeOption = null
118+
AddOptionToAttribute $addAttributeOption = null,
119+
ReservedAttributeChecker $reservedAttributeChecker = null,
120+
AttributeFactory $attributeFactory = null,
121+
Config $eavConfig = null
113122
) {
114123
$this->cache = $cache;
115124
$this->attrGroupCollectionFactory = $attrGroupCollectionFactory;
116125
$this->attributeMapper = $context->getAttributeMapper();
117126
$this->setup = $setup;
118127
$this->addAttributeOption = $addAttributeOption
119128
?? ObjectManager::getInstance()->get(AddOptionToAttribute::class);
120-
$this->attributeCodeValidator = $attributeCodeValidator ?: ObjectManager::getInstance()->get(
121-
Code::class
122-
);
129+
$this->attributeCodeValidator = $attributeCodeValidator ?? ObjectManager::getInstance()->get(Code::class);
130+
$this->reservedAttributeChecker = $reservedAttributeChecker
131+
?? ObjectManager::getInstance()->get(ReservedAttributeChecker::class);
132+
$this->attributeFactory = $attributeFactory ?? ObjectManager::getInstance()->get(AttributeFactory::class);
133+
$this->eavConfig = $eavConfig ?? ObjectManager::getInstance()->get(Config::class);
123134
}
124135

125136
/**
@@ -1364,7 +1375,7 @@ public function installEntities($entities = null)
13641375

13651376
foreach ($entities as $entityName => $entity) {
13661377
$this->addEntityType($entityName, $entity);
1367-
1378+
$this->eavConfig->clear();
13681379
$frontendPrefix = isset($entity['frontend_prefix']) ? $entity['frontend_prefix'] : '';
13691380
$backendPrefix = isset($entity['backend_prefix']) ? $entity['backend_prefix'] : '';
13701381
$sourcePrefix = isset($entity['source_prefix']) ? $entity['source_prefix'] : '';
@@ -1503,5 +1514,16 @@ private function validateAttributeCode(array $data): void
15031514

15041515
throw new LocalizedException(__($errorMessage));
15051516
}
1517+
1518+
/* Actual attribute is created from data array for compatibility with reserved attribute validator logic */
1519+
$attribute = $this->attributeFactory->createAttribute(Attribute::class, $data);
1520+
if ($this->reservedAttributeChecker->isReservedAttribute($attribute)) {
1521+
throw new LocalizedException(
1522+
__(
1523+
'The attribute code \'%1\' is reserved by system. Please try another attribute code',
1524+
$attributeCode
1525+
)
1526+
);
1527+
}
15061528
}
15071529
}

0 commit comments

Comments
 (0)