Skip to content

Commit 5d8a453

Browse files
committed
ACP2E-4348: Manage Shopping Cart store scope issues
1 parent 0a3b703 commit 5d8a453

File tree

6 files changed

+414
-132
lines changed

6 files changed

+414
-132
lines changed

app/code/Magento/Catalog/Test/Fixture/Attribute.php

Lines changed: 118 additions & 126 deletions
Original file line numberDiff line numberDiff line change
@@ -7,51 +7,88 @@
77

88
namespace Magento\Catalog\Test\Fixture;
99

10+
use Magento\Catalog\Api\Data\ProductAttributeInterface;
11+
use Magento\Catalog\Api\Data\ProductAttributeInterfaceFactory;
1012
use Magento\Catalog\Api\ProductAttributeManagementInterface;
1113
use Magento\Catalog\Api\ProductAttributeRepositoryInterface;
1214
use Magento\Catalog\Model\Product;
13-
use Magento\Catalog\Model\ResourceModel\Attribute as ResourceModelAttribute;
14-
use Magento\Catalog\Model\ResourceModel\Eav\Attribute as EavAttribute;
15-
use Magento\Eav\Model\AttributeFactory;
1615
use Magento\Eav\Setup\EavSetup;
16+
use Magento\Framework\Api\DataObjectHelper;
1717
use Magento\Framework\DataObject;
1818
use Magento\TestFramework\Fixture\Api\DataMerger;
1919
use Magento\TestFramework\Fixture\Api\ServiceFactory;
2020
use Magento\TestFramework\Fixture\RevertibleDataFixtureInterface;
2121
use Magento\TestFramework\Fixture\Data\ProcessorInterface;
2222

23+
/**
24+
* Product attribute fixture
25+
*
26+
* 1. Create an attribute with default data
27+
* <pre>
28+
* #[
29+
* DataFixture(AttributeFixture::class, as: 'attribute')
30+
* ]
31+
* </pre>
32+
* 2. Create an attribute with custom data
33+
* <pre>
34+
* #[
35+
* DataFixture(AttributeFixture::class, ['is_filterable' => true], 'attribute')
36+
* ]
37+
* </pre>
38+
* 3. Update an existing attribute
39+
* <pre>
40+
* #[
41+
* DataFixture(
42+
* AttributeFixture::class,
43+
* [
44+
* 'attribute_code' => 'price',
45+
* 'scope' => 'website',
46+
* '_update' => true
47+
* ]
48+
* )
49+
* ]
50+
* </pre>
51+
*/
2352
class Attribute implements RevertibleDataFixtureInterface
2453
{
2554
private const DEFAULT_DATA = [
26-
'is_wysiwyg_enabled' => false,
27-
'is_html_allowed_on_front' => true,
28-
'used_for_sort_by' => false,
29-
'is_filterable' => false,
30-
'is_filterable_in_search' => false,
31-
'is_used_in_grid' => true,
32-
'is_visible_in_grid' => true,
33-
'is_filterable_in_grid' => true,
34-
'position' => 0,
35-
'apply_to' => [],
36-
'is_searchable' => false,
37-
'is_visible_in_advanced_search' => false,
38-
'is_comparable' => false,
39-
'is_used_for_promo_rules' => false,
40-
'is_visible_on_front' => false,
41-
'used_in_product_listing' => false,
42-
'is_visible' => true,
43-
'scope' => 'store',
44-
'attribute_code' => 'product_attribute%uniqid%',
45-
'frontend_input' => 'text',
46-
'entity_type_id' => '4',
47-
'is_required' => false,
48-
'options' => [],
49-
'is_user_defined' => true,
55+
ProductAttributeInterface::ATTRIBUTE_ID => null,
56+
ProductAttributeInterface::ATTRIBUTE_CODE => 'product_attribute%uniqid%',
57+
ProductAttributeInterface::ENTITY_TYPE_ID => '4',
58+
ProductAttributeInterface::SCOPE => ProductAttributeInterface::SCOPE_GLOBAL_TEXT,
59+
ProductAttributeInterface::IS_USER_DEFINED => true,
60+
ProductAttributeInterface::IS_SEARCHABLE => false,
61+
ProductAttributeInterface::IS_FILTERABLE => false,
62+
ProductAttributeInterface::IS_FILTERABLE_IN_SEARCH => false,
63+
ProductAttributeInterface::IS_FILTERABLE_IN_GRID => true,
64+
ProductAttributeInterface::IS_VISIBLE => true,
65+
ProductAttributeInterface::IS_VISIBLE_IN_GRID => true,
66+
ProductAttributeInterface::IS_VISIBLE_IN_ADVANCED_SEARCH => false,
67+
ProductAttributeInterface::IS_VISIBLE_ON_FRONT => false,
68+
ProductAttributeInterface::IS_USED_IN_GRID => true,
69+
ProductAttributeInterface::IS_COMPARABLE => false,
70+
ProductAttributeInterface::IS_USED_FOR_PROMO_RULES => false,
71+
ProductAttributeInterface::IS_REQUIRED => false,
72+
ProductAttributeInterface::IS_UNIQUE => false,
73+
ProductAttributeInterface::IS_WYSIWYG_ENABLED => false,
74+
ProductAttributeInterface::IS_HTML_ALLOWED_ON_FRONT => true,
75+
ProductAttributeInterface::USED_IN_PRODUCT_LISTING => false,
76+
ProductAttributeInterface::USED_FOR_SORT_BY => false,
77+
ProductAttributeInterface::POSITION => 0,
78+
ProductAttributeInterface::APPLY_TO => [],
79+
ProductAttributeInterface::OPTIONS => [],
80+
ProductAttributeInterface::NOTE => null,
81+
ProductAttributeInterface::BACKEND_TYPE => 'varchar',
82+
ProductAttributeInterface::BACKEND_MODEL => null,
83+
ProductAttributeInterface::FRONTEND_INPUT => 'text',
84+
ProductAttributeInterface::FRONTEND_CLASS => null,
85+
ProductAttributeInterface::SOURCE_MODEL => null,
86+
ProductAttributeInterface::EXTENSION_ATTRIBUTES_KEY => [],
87+
ProductAttributeInterface::CUSTOM_ATTRIBUTES => [],
88+
ProductAttributeInterface::FRONTEND_LABELS => [],
5089
'default_frontend_label' => 'Product Attribute%uniqid%',
51-
'frontend_labels' => [],
52-
'backend_type' => 'varchar',
53-
'is_unique' => '0',
54-
'validation_rules' => []
90+
'validation_rules' => [],
91+
"default_value" => null,
5592
];
5693

5794
private const DEFAULT_ATTRIBUTE_SET_DATA = [
@@ -60,103 +97,75 @@ class Attribute implements RevertibleDataFixtureInterface
6097
'_sort_order' => 0,
6198
];
6299

63-
/**
64-
* @var ServiceFactory
65-
*/
66-
private $serviceFactory;
67-
68-
/**
69-
* @var ProcessorInterface
70-
*/
71-
private $dataProcessor;
72-
73-
/**
74-
* @var EavSetup
75-
*/
76-
private $eavSetup;
77-
78-
/**
79-
* @var ProductAttributeManagementInterface
80-
*/
81-
private $productAttributeManagement;
82-
83-
/**
84-
* @var AttributeFactory
85-
*/
86-
private AttributeFactory $attributeFactory;
87-
88-
/**
89-
* @var DataMerger
90-
*/
91-
private DataMerger $dataMerger;
92-
93-
/**
94-
* @var ResourceModelAttribute
95-
*/
96-
private ResourceModelAttribute $resourceModelAttribute;
97-
98-
/**
99-
* @var ProductAttributeRepositoryInterface
100-
*/
101-
private ProductAttributeRepositoryInterface $productAttributeRepository;
102-
103100
/**
104101
* @param ServiceFactory $serviceFactory
105102
* @param ProcessorInterface $dataProcessor
106103
* @param EavSetup $eavSetup
107104
* @param ProductAttributeManagementInterface $productAttributeManagement
108-
* @param AttributeFactory $attributeFactory
109-
* @param DataMerger $dataMerger
110-
* @param ResourceModelAttribute $resourceModelAttribute
105+
* @param ProductAttributeInterfaceFactory $attributeFactory
111106
* @param ProductAttributeRepositoryInterface $productAttributeRepository
107+
* @param DataObjectHelper $dataObjectHelper
108+
* @param DataMerger $dataMerger
112109
*/
113110
public function __construct(
114-
ServiceFactory $serviceFactory,
115-
ProcessorInterface $dataProcessor,
116-
EavSetup $eavSetup,
117-
ProductAttributeManagementInterface $productAttributeManagement,
118-
AttributeFactory $attributeFactory,
119-
DataMerger $dataMerger,
120-
ResourceModelAttribute $resourceModelAttribute,
121-
ProductAttributeRepositoryInterface $productAttributeRepository
111+
private readonly ServiceFactory $serviceFactory,
112+
private readonly ProcessorInterface $dataProcessor,
113+
private readonly EavSetup $eavSetup,
114+
private readonly ProductAttributeManagementInterface $productAttributeManagement,
115+
private readonly ProductAttributeInterfaceFactory $attributeFactory,
116+
private readonly ProductAttributeRepositoryInterface $productAttributeRepository,
117+
private readonly DataObjectHelper $dataObjectHelper,
118+
private readonly DataMerger $dataMerger
122119
) {
123-
$this->serviceFactory = $serviceFactory;
124-
$this->dataProcessor = $dataProcessor;
125-
$this->eavSetup = $eavSetup;
126-
$this->productAttributeManagement = $productAttributeManagement;
127-
$this->attributeFactory = $attributeFactory;
128-
$this->dataMerger = $dataMerger;
129-
$this->resourceModelAttribute = $resourceModelAttribute;
130-
$this->productAttributeRepository = $productAttributeRepository;
131120
}
132121

133122
/**
134123
* {@inheritdoc}
135124
* @param array $data Parameters. Same format as Attribute::DEFAULT_DATA.
125+
*
126+
* Additional fields:
127+
* - `_update`: boolean - whether to update attribute instead of creating a new one
128+
* - `_set_id`: int - attribute set ID to assign the attribute to
129+
* - `_group_id`: int - attribute group ID to assign the attribute to
130+
* - `_sort_order`: int - sort order within the attribute group
131+
*
136132
* @return DataObject|null
137133
*/
138134
public function apply(array $data = []): ?DataObject
139135
{
140-
if (array_key_exists('additional_data', $data)) {
141-
return $this->applyAttributeWithAdditionalData($data);
142-
}
143-
144-
$attribute = $this->attributeFactory->createAttribute(
145-
EavAttribute::class,
146-
$this->prepareData(array_diff_key($data, self::DEFAULT_ATTRIBUTE_SET_DATA))
147-
);
148-
$attribute = $this->productAttributeRepository->save($attribute);
149-
136+
$attributeData = array_diff_key($data, self::DEFAULT_ATTRIBUTE_SET_DATA);
150137
$attributeSetData = $this->prepareAttributeSetData(
151138
array_intersect_key($data, self::DEFAULT_ATTRIBUTE_SET_DATA)
152139
);
140+
if (!empty($data['_update'])) {
141+
$attribute = $this->productAttributeRepository->get($data['attribute_code']);
142+
unset($attributeData['_update'], $attributeData['attribute_code']);
143+
} else {
144+
$attribute = $this->attributeFactory->create();
145+
$attributeData = $this->prepareData($attributeData);
146+
}
153147

154-
$this->productAttributeManagement->assign(
155-
$attributeSetData['_set_id'],
156-
$attributeSetData['_group_id'],
157-
$attribute->getAttributeCode(),
158-
$attributeSetData['_sort_order']
148+
$this->dataObjectHelper->populateWithArray(
149+
$attribute,
150+
$attributeData,
151+
ProductAttributeInterface::class
159152
);
153+
// Add data that are not part of the interface
154+
$attribute->addData(array_diff_key($attributeData, self::DEFAULT_DATA));
155+
if (isset($attributeData['scope'])) {
156+
$attribute->setScope($attributeData['scope']);
157+
}
158+
$attribute = $this->productAttributeRepository->save($attribute);
159+
160+
// Do not assign attribute if both set_id and group_id are not provided during update
161+
if (empty($data['_update']) || isset($data['_set_id'], $data['_group_id'])) {
162+
$this->productAttributeManagement->assign(
163+
$attributeSetData['_set_id'],
164+
$attributeSetData['_group_id'],
165+
$attribute->getAttributeCode(),
166+
$attributeSetData['_sort_order']
167+
);
168+
}
160169

161170
return $attribute;
162171
}
@@ -166,6 +175,9 @@ public function apply(array $data = []): ?DataObject
166175
*/
167176
public function revert(DataObject $data): void
168177
{
178+
if (!$data->getIsUserDefined()) {
179+
return;
180+
}
169181
$service = $this->serviceFactory->create(ProductAttributeRepositoryInterface::class, 'deleteById');
170182
$service->execute(
171183
[
@@ -174,26 +186,6 @@ public function revert(DataObject $data): void
174186
);
175187
}
176188

177-
/**
178-
* @param array $data Parameters. Same format as Attribute::DEFAULT_DATA.
179-
* @return DataObject|null
180-
*/
181-
private function applyAttributeWithAdditionalData(array $data = []): ?DataObject
182-
{
183-
$defaultData = array_merge(self::DEFAULT_DATA, ['additional_data' => null]);
184-
/** @var EavAttribute $attr */
185-
$attr = $this->attributeFactory->createAttribute(EavAttribute::class, $defaultData);
186-
$mergedData = $this->dataProcessor->process($this, $this->dataMerger->merge($defaultData, $data));
187-
188-
$attributeSetData = $this->prepareAttributeSetData(
189-
array_intersect_key($data, self::DEFAULT_ATTRIBUTE_SET_DATA)
190-
);
191-
192-
$attr->setData(array_merge($mergedData, $attributeSetData));
193-
$this->resourceModelAttribute->save($attr);
194-
return $attr;
195-
}
196-
197189
/**
198190
* Prepare attribute data
199191
*
@@ -202,7 +194,7 @@ private function applyAttributeWithAdditionalData(array $data = []): ?DataObject
202194
*/
203195
private function prepareData(array $data): array
204196
{
205-
$data = array_merge(self::DEFAULT_DATA, $data);
197+
$data = $this->dataMerger->merge(self::DEFAULT_DATA, $data, false);
206198
$data['frontend_label'] ??= $data['default_frontend_label'];
207199

208200
return $this->dataProcessor->process($this, $data);

0 commit comments

Comments
 (0)