Skip to content

Magento2 attribute option does not validate for existing records before insert 16852 #21424

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
210 changes: 210 additions & 0 deletions app/code/Magento/Eav/Setup/AddOptionToAttribute.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,210 @@
<?php
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
declare(strict_types=1);

namespace Magento\Eav\Setup;

use Magento\Eav\Api\Data\AttributeInterface;
use Magento\Framework\Exception\LocalizedException;
use Magento\Framework\Setup\ModuleDataSetupInterface;

/**
* Add option to attribute
*/
class AddOptionToAttribute
{
/**
* @var ModuleDataSetupInterface
*/
private $setup;

/**
* @param ModuleDataSetupInterface $setup
*/
public function __construct(
ModuleDataSetupInterface $setup
) {
$this->setup = $setup;
}

/**
* Add Attribute Option
*
* @param array $option
*
* @return void
* @throws LocalizedException
*/
public function execute(array $option): void
{
$optionTable = $this->setup->getTable('eav_attribute_option');
$optionValueTable = $this->setup->getTable('eav_attribute_option_value');

if (isset($option['value'])) {
$this->addValue($option, $optionTable, $optionValueTable);
} elseif (isset($option['values'])) {
$this->addValues($option, $optionTable, $optionValueTable);
}
}

/**
* Add option value
*
* @param array $option
* @param string $optionTable
* @param string $optionValueTable
*
* @return void
* @throws LocalizedException
*/
private function addValue(array $option, string $optionTable, string $optionValueTable): void
{
$value = $option['value'];
foreach ($value as $optionId => $values) {
$intOptionId = (int)$optionId;
if (!empty($option['delete'][$optionId])) {
if ($intOptionId) {
$condition = ['option_id =?' => $intOptionId];
$this->setup->getConnection()->delete($optionTable, $condition);
}
continue;
}

if (!$intOptionId) {
$data = [
'attribute_id' => $option['attribute_id'],
'sort_order' => isset($option['order'][$optionId]) ? $option['order'][$optionId] : 0,
];
$this->setup->getConnection()->insert($optionTable, $data);
$intOptionId = $this->setup->getConnection()->lastInsertId($optionTable);
} else {
$data = [
'sort_order' => isset($option['order'][$optionId]) ? $option['order'][$optionId] : 0,
];
$this->setup->getConnection()->update(
$optionTable,
$data,
['option_id=?' => $intOptionId]
);
}

// Default value
if (!isset($values[0])) {
throw new LocalizedException(
__("The default option isn't defined. Set the option and try again.")
);
}
$condition = ['option_id =?' => $intOptionId];
$this->setup->getConnection()->delete($optionValueTable, $condition);
foreach ($values as $storeId => $value) {
$data = ['option_id' => $intOptionId, 'store_id' => $storeId, 'value' => $value];
$this->setup->getConnection()->insert($optionValueTable, $data);
}
}
}

/**
* Add option values
*
* @param array $option
* @param string $optionTable
* @param string $optionValueTable
*
* @return void
*/
private function addValues(array $option, string $optionTable, string $optionValueTable): void
{
$values = $option['values'];
$attributeId = (int)$option['attribute_id'];
$existingOptions = $this->getExistingAttributeOptions($attributeId, $optionTable, $optionValueTable);
foreach ($values as $sortOrder => $value) {
// add option
$data = ['attribute_id' => $attributeId, 'sort_order' => $sortOrder];
if (!$this->isExistingOptionValue($value, $existingOptions)) {
$this->setup->getConnection()->insert($optionTable, $data);

//add option value
$intOptionId = $this->setup->getConnection()->lastInsertId($optionTable);
$data = ['option_id' => $intOptionId, 'store_id' => 0, 'value' => $value];
$this->setup->getConnection()->insert($optionValueTable, $data);
} elseif ($optionId = $this->getExistingOptionIdWithDiffSortOrder(
$sortOrder,
$value,
$existingOptions
)
) {
$this->setup->getConnection()->update(
$optionTable,
['sort_order' => $sortOrder],
['option_id = ?' => $optionId]
);
}
}
}

/**
* Check if option value already exists
*
* @param string $value
* @param array $existingOptions
*
* @return bool
*/
private function isExistingOptionValue(string $value, array $existingOptions): bool
{
foreach ($existingOptions as $option) {
if ($option['value'] == $value) {
return true;
}
}

return false;
}

/**
* Get existing attribute options
*
* @param int $attributeId
* @param string $optionTable
* @param string $optionValueTable
*
* @return array
*/
private function getExistingAttributeOptions(int $attributeId, string $optionTable, string $optionValueTable): array
{
$select = $this->setup
->getConnection()
->select()
->from(['o' => $optionTable])
->reset('columns')
->columns(['option_id', 'sort_order'])
->join(['ov' => $optionValueTable], 'o.option_id = ov.option_id', 'value')
->where(AttributeInterface::ATTRIBUTE_ID . ' = ?', $attributeId)
->where('store_id = 0');

return $this->setup->getConnection()->fetchAll($select);
}

/**
* Check if option already exists, but sort_order differs
*
* @param int $sortOrder
* @param string $value
* @param array $existingOptions
*
* @return int|null
*/
private function getExistingOptionIdWithDiffSortOrder(int $sortOrder, string $value, array $existingOptions): ?int
{
foreach ($existingOptions as $option) {
if ($option['value'] == $value && $option['sort_order'] != $sortOrder) {
return (int)$option['option_id'];
}
}

return null;
}
}
70 changes: 13 additions & 57 deletions app/code/Magento/Eav/Setup/EavSetup.php
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,11 @@ class EavSetup
*/
private $_defaultAttributeSetName = 'Default';

/**
* @var AddOptionToAttribute
*/
private $addAttributeOption;

/**
* @var Code
*/
Expand All @@ -95,18 +100,23 @@ class EavSetup
* @param CacheInterface $cache
* @param CollectionFactory $attrGroupCollectionFactory
* @param Code|null $attributeCodeValidator
* @param AddOptionToAttribute|null $addAttributeOption
* @SuppressWarnings(PHPMD.LongVariable)
*/
public function __construct(
ModuleDataSetupInterface $setup,
Context $context,
CacheInterface $cache,
CollectionFactory $attrGroupCollectionFactory,
Code $attributeCodeValidator = null
Code $attributeCodeValidator = null,
AddOptionToAttribute $addAttributeOption = null
) {
$this->cache = $cache;
$this->attrGroupCollectionFactory = $attrGroupCollectionFactory;
$this->attributeMapper = $context->getAttributeMapper();
$this->setup = $setup;
$this->addAttributeOption = $addAttributeOption
?? ObjectManager::getInstance()->get(AddOptionToAttribute::class);
$this->attributeCodeValidator = $attributeCodeValidator ?: ObjectManager::getInstance()->get(
Code::class
);
Expand Down Expand Up @@ -567,6 +577,7 @@ public function addAttributeGroup($entityTypeId, $setId, $name, $sortOrder = nul
if (empty($data['attribute_group_code'])) {
if (empty($attributeGroupCode)) {
// in the following code md5 is not used for security purposes
// phpcs:disable Magento2.Security.InsecureFunction
$attributeGroupCode = md5($name);
}
$data['attribute_group_code'] = $attributeGroupCode;
Expand Down Expand Up @@ -868,62 +879,7 @@ public function addAttribute($entityTypeId, $code, array $attr)
*/
public function addAttributeOption($option)
{
$optionTable = $this->setup->getTable('eav_attribute_option');
$optionValueTable = $this->setup->getTable('eav_attribute_option_value');

if (isset($option['value'])) {
foreach ($option['value'] as $optionId => $values) {
$intOptionId = (int)$optionId;
if (!empty($option['delete'][$optionId])) {
if ($intOptionId) {
$condition = ['option_id =?' => $intOptionId];
$this->setup->getConnection()->delete($optionTable, $condition);
}
continue;
}

if (!$intOptionId) {
$data = [
'attribute_id' => $option['attribute_id'],
'sort_order' => isset($option['order'][$optionId]) ? $option['order'][$optionId] : 0,
];
$this->setup->getConnection()->insert($optionTable, $data);
$intOptionId = $this->setup->getConnection()->lastInsertId($optionTable);
} else {
$data = [
'sort_order' => isset($option['order'][$optionId]) ? $option['order'][$optionId] : 0,
];
$this->setup->getConnection()->update(
$optionTable,
$data,
['option_id=?' => $intOptionId]
);
}

// Default value
if (!isset($values[0])) {
throw new \Magento\Framework\Exception\LocalizedException(
__("The default option isn't defined. Set the option and try again.")
);
}
$condition = ['option_id =?' => $intOptionId];
$this->setup->getConnection()->delete($optionValueTable, $condition);
foreach ($values as $storeId => $value) {
$data = ['option_id' => $intOptionId, 'store_id' => $storeId, 'value' => $value];
$this->setup->getConnection()->insert($optionValueTable, $data);
}
}
} elseif (isset($option['values'])) {
foreach ($option['values'] as $sortOrder => $label) {
// add option
$data = ['attribute_id' => $option['attribute_id'], 'sort_order' => $sortOrder];
$this->setup->getConnection()->insert($optionTable, $data);
$intOptionId = $this->setup->getConnection()->lastInsertId($optionTable);

$data = ['option_id' => $intOptionId, 'store_id' => 0, 'value' => $label];
$this->setup->getConnection()->insert($optionValueTable, $data);
}
}
$this->addAttributeOption->execute($option);
}

/**
Expand Down
Loading