Skip to content

Commit e79e57b

Browse files
author
Maksym Novik
committed
Magento2 Attribute option does not validate for existing records before insert #16852
1 parent 211dd25 commit e79e57b

File tree

6 files changed

+710
-57
lines changed

6 files changed

+710
-57
lines changed
Lines changed: 210 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,210 @@
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\Setup;
9+
10+
use Magento\Eav\Api\Data\AttributeInterface;
11+
use Magento\Framework\Exception\LocalizedException;
12+
use Magento\Framework\Setup\ModuleDataSetupInterface;
13+
14+
/**
15+
* Add option to attribute
16+
*/
17+
class AddOptionToAttribute
18+
{
19+
/**
20+
* @var ModuleDataSetupInterface
21+
*/
22+
private $setup;
23+
24+
/**
25+
* @param ModuleDataSetupInterface $setup
26+
*/
27+
public function __construct(
28+
ModuleDataSetupInterface $setup
29+
) {
30+
$this->setup = $setup;
31+
}
32+
33+
/**
34+
* Add Attribute Option
35+
*
36+
* @param array $option
37+
*
38+
* @return void
39+
* @throws LocalizedException
40+
*/
41+
public function execute(array $option): void
42+
{
43+
$optionTable = $this->setup->getTable('eav_attribute_option');
44+
$optionValueTable = $this->setup->getTable('eav_attribute_option_value');
45+
46+
if (isset($option['value'])) {
47+
$this->addValue($option, $optionTable, $optionValueTable);
48+
} elseif (isset($option['values'])) {
49+
$this->addValues($option, $optionTable, $optionValueTable);
50+
}
51+
}
52+
53+
/**
54+
* Add option value
55+
*
56+
* @param array $option
57+
* @param string $optionTable
58+
* @param string $optionValueTable
59+
*
60+
* @return void
61+
* @throws LocalizedException
62+
*/
63+
private function addValue(array $option, string $optionTable, string $optionValueTable): void
64+
{
65+
$value = $option['value'];
66+
foreach ($value as $optionId => $values) {
67+
$intOptionId = (int)$optionId;
68+
if (!empty($option['delete'][$optionId])) {
69+
if ($intOptionId) {
70+
$condition = ['option_id =?' => $intOptionId];
71+
$this->setup->getConnection()->delete($optionTable, $condition);
72+
}
73+
continue;
74+
}
75+
76+
if (!$intOptionId) {
77+
$data = [
78+
'attribute_id' => $option['attribute_id'],
79+
'sort_order' => isset($option['order'][$optionId]) ? $option['order'][$optionId] : 0,
80+
];
81+
$this->setup->getConnection()->insert($optionTable, $data);
82+
$intOptionId = $this->setup->getConnection()->lastInsertId($optionTable);
83+
} else {
84+
$data = [
85+
'sort_order' => isset($option['order'][$optionId]) ? $option['order'][$optionId] : 0,
86+
];
87+
$this->setup->getConnection()->update(
88+
$optionTable,
89+
$data,
90+
['option_id=?' => $intOptionId]
91+
);
92+
}
93+
94+
// Default value
95+
if (!isset($values[0])) {
96+
throw new LocalizedException(
97+
__("The default option isn't defined. Set the option and try again.")
98+
);
99+
}
100+
$condition = ['option_id =?' => $intOptionId];
101+
$this->setup->getConnection()->delete($optionValueTable, $condition);
102+
foreach ($values as $storeId => $value) {
103+
$data = ['option_id' => $intOptionId, 'store_id' => $storeId, 'value' => $value];
104+
$this->setup->getConnection()->insert($optionValueTable, $data);
105+
}
106+
}
107+
}
108+
109+
/**
110+
* Add option values
111+
*
112+
* @param array $option
113+
* @param string $optionTable
114+
* @param string $optionValueTable
115+
*
116+
* @return void
117+
*/
118+
private function addValues(array $option, string $optionTable, string $optionValueTable): void
119+
{
120+
$values = $option['values'];
121+
$attributeId = (int)$option['attribute_id'];
122+
$existingOptions = $this->getExistingAttributeOptions($attributeId, $optionTable, $optionValueTable);
123+
foreach ($values as $sortOrder => $value) {
124+
// add option
125+
$data = ['attribute_id' => $attributeId, 'sort_order' => $sortOrder];
126+
if (!$this->isExistingOptionValue($value, $existingOptions)) {
127+
$this->setup->getConnection()->insert($optionTable, $data);
128+
129+
//add option value
130+
$intOptionId = $this->setup->getConnection()->lastInsertId($optionTable);
131+
$data = ['option_id' => $intOptionId, 'store_id' => 0, 'value' => $value];
132+
$this->setup->getConnection()->insert($optionValueTable, $data);
133+
} elseif ($optionId = $this->getExistingOptionIdWithDiffSortOrder(
134+
$sortOrder,
135+
$value,
136+
$existingOptions
137+
)
138+
) {
139+
$this->setup->getConnection()->update(
140+
$optionTable,
141+
['sort_order' => $sortOrder],
142+
['option_id = ?' => $optionId]
143+
);
144+
}
145+
}
146+
}
147+
148+
/**
149+
* Check if option value already exists
150+
*
151+
* @param string $value
152+
* @param array $existingOptions
153+
*
154+
* @return bool
155+
*/
156+
private function isExistingOptionValue(string $value, array $existingOptions): bool
157+
{
158+
foreach ($existingOptions as $option) {
159+
if ($option['value'] == $value) {
160+
return true;
161+
}
162+
}
163+
164+
return false;
165+
}
166+
167+
/**
168+
* Get existing attribute options
169+
*
170+
* @param int $attributeId
171+
* @param string $optionTable
172+
* @param string $optionValueTable
173+
*
174+
* @return array
175+
*/
176+
private function getExistingAttributeOptions(int $attributeId, string $optionTable, string $optionValueTable): array
177+
{
178+
$select = $this->setup
179+
->getConnection()
180+
->select()
181+
->from(['o' => $optionTable])
182+
->reset('columns')
183+
->columns(['option_id', 'sort_order'])
184+
->join(['ov' => $optionValueTable], 'o.option_id = ov.option_id', 'value')
185+
->where(AttributeInterface::ATTRIBUTE_ID . ' = ?', $attributeId)
186+
->where('store_id = 0');
187+
188+
return $this->setup->getConnection()->fetchAll($select);
189+
}
190+
191+
/**
192+
* Check if option already exists, but sort_order differs
193+
*
194+
* @param int $sortOrder
195+
* @param string $value
196+
* @param array $existingOptions
197+
*
198+
* @return int|null
199+
*/
200+
private function getExistingOptionIdWithDiffSortOrder(int $sortOrder, string $value, array $existingOptions): ?int
201+
{
202+
foreach ($existingOptions as $option) {
203+
if ($option['value'] == $value && $option['sort_order'] != $sortOrder) {
204+
return (int)$option['option_id'];
205+
}
206+
}
207+
208+
return null;
209+
}
210+
}

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

Lines changed: 12 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,11 @@ class EavSetup
8282
*/
8383
private $_defaultAttributeSetName = 'Default';
8484

85+
/**
86+
* @var AddOptionToAttribute
87+
*/
88+
private $addAttributeOption;
89+
8590
/**
8691
* @var Code
8792
*/
@@ -95,18 +100,23 @@ class EavSetup
95100
* @param CacheInterface $cache
96101
* @param CollectionFactory $attrGroupCollectionFactory
97102
* @param Code|null $attributeCodeValidator
103+
* @param AddOptionToAttribute|null $addAttributeOption
104+
* @SuppressWarnings(PHPMD.LongVariable)
98105
*/
99106
public function __construct(
100107
ModuleDataSetupInterface $setup,
101108
Context $context,
102109
CacheInterface $cache,
103110
CollectionFactory $attrGroupCollectionFactory,
104-
Code $attributeCodeValidator = null
111+
Code $attributeCodeValidator = null,
112+
AddOptionToAttribute $addAttributeOption = null
105113
) {
106114
$this->cache = $cache;
107115
$this->attrGroupCollectionFactory = $attrGroupCollectionFactory;
108116
$this->attributeMapper = $context->getAttributeMapper();
109117
$this->setup = $setup;
118+
$this->addAttributeOption = $addAttributeOption
119+
?? ObjectManager::getInstance()->get(AddOptionToAttribute::class);
110120
$this->attributeCodeValidator = $attributeCodeValidator ?: ObjectManager::getInstance()->get(
111121
Code::class
112122
);
@@ -868,62 +878,7 @@ public function addAttribute($entityTypeId, $code, array $attr)
868878
*/
869879
public function addAttributeOption($option)
870880
{
871-
$optionTable = $this->setup->getTable('eav_attribute_option');
872-
$optionValueTable = $this->setup->getTable('eav_attribute_option_value');
873-
874-
if (isset($option['value'])) {
875-
foreach ($option['value'] as $optionId => $values) {
876-
$intOptionId = (int)$optionId;
877-
if (!empty($option['delete'][$optionId])) {
878-
if ($intOptionId) {
879-
$condition = ['option_id =?' => $intOptionId];
880-
$this->setup->getConnection()->delete($optionTable, $condition);
881-
}
882-
continue;
883-
}
884-
885-
if (!$intOptionId) {
886-
$data = [
887-
'attribute_id' => $option['attribute_id'],
888-
'sort_order' => isset($option['order'][$optionId]) ? $option['order'][$optionId] : 0,
889-
];
890-
$this->setup->getConnection()->insert($optionTable, $data);
891-
$intOptionId = $this->setup->getConnection()->lastInsertId($optionTable);
892-
} else {
893-
$data = [
894-
'sort_order' => isset($option['order'][$optionId]) ? $option['order'][$optionId] : 0,
895-
];
896-
$this->setup->getConnection()->update(
897-
$optionTable,
898-
$data,
899-
['option_id=?' => $intOptionId]
900-
);
901-
}
902-
903-
// Default value
904-
if (!isset($values[0])) {
905-
throw new \Magento\Framework\Exception\LocalizedException(
906-
__("The default option isn't defined. Set the option and try again.")
907-
);
908-
}
909-
$condition = ['option_id =?' => $intOptionId];
910-
$this->setup->getConnection()->delete($optionValueTable, $condition);
911-
foreach ($values as $storeId => $value) {
912-
$data = ['option_id' => $intOptionId, 'store_id' => $storeId, 'value' => $value];
913-
$this->setup->getConnection()->insert($optionValueTable, $data);
914-
}
915-
}
916-
} elseif (isset($option['values'])) {
917-
foreach ($option['values'] as $sortOrder => $label) {
918-
// add option
919-
$data = ['attribute_id' => $option['attribute_id'], 'sort_order' => $sortOrder];
920-
$this->setup->getConnection()->insert($optionTable, $data);
921-
$intOptionId = $this->setup->getConnection()->lastInsertId($optionTable);
922-
923-
$data = ['option_id' => $intOptionId, 'store_id' => 0, 'value' => $label];
924-
$this->setup->getConnection()->insert($optionValueTable, $data);
925-
}
926-
}
881+
$this->addAttributeOption->execute($option);
927882
}
928883

929884
/**

0 commit comments

Comments
 (0)