4

Is it possible to create values for a multiselect attribute via CSV upload?

For example we want to populate a multiselect attribute for Car Year. We added a few sample entries and the additional_attributes data we exported was:

make=Audi,model=A3,year=1990|1991|1992

We then tried to test import a whole load more dates but these weren't created in the system, we're looking for a process where we could potentially add hundreds of entries.

Are we even close thinking this is possible...?

Thanks,

1 Answer 1

5

It is not currently natively supported by Magento.

I found a workaround to manage it. I add multi-select new options dynamically during CSV validation. It works for manual import and Schedule EE import. It is not the best because you need to reload attribute option cache for each line. If you found some optimization, please share it.

Solution:

Create a new ImportExport module.

Add these dependencies in etc/di.xml :

<preference for="Magento\CatalogImportExport\Model\Import\Product\Validator" type="Hublot\ImportExport\Model\Import\Product\Validator" />
<preference for="Magento\ConfigurableImportExport\Model\Import\Product\Type\Configurable" type="Hublot\ImportExport\Model\Import\Product\Type\Configurable" />
<preference for="Magento\CatalogImportExport\Model\Import\Product\Type\Simple" type="Hublot\ImportExport\Model\Import\Product\Type\Simple" />

Extend the class app/code/Vendor/ImportExport/Model/Import/Product/Validator.php :

<?php
/**
 * Copyright © 2016 Magento. All rights reserved.
 * See COPYING.txt for license details.
 */
namespace Vendor\ImportExport\Model\Import\Product;

use Braintree\Exception;
use \Magento\CatalogImportExport\Model\Import\Product as Product;
use \Magento\CatalogImportExport\Model\Import\Product\RowValidatorInterface as RowValidatorInterface;

class Validator extends \Magento\CatalogImportExport\Model\Import\Product\Validator
{
    protected $dynamicallyOptionAdded = array();

    /**
     * @param \Magento\Framework\Stdlib\StringUtils $string
     * @param RowValidatorInterface[] $validators
     */
    public function __construct(
        \Magento\Framework\Stdlib\StringUtils $string,
        $validators = [],
        \Magento\Catalog\Model\Product\Attribute\OptionManagement $optionManagement,
        \Magento\Eav\Api\Data\AttributeOptionInterfaceFactory $optionDataFactory,
        \Magento\Framework\Api\DataObjectHelper $dataObjectHelper,
        \Psr\Log\LoggerInterface $logger
    ) {
        $this->optionManagement = $optionManagement;
        $this->optionDataFactory = $optionDataFactory;
        $this->dataObjectHelper = $dataObjectHelper;
        $this->_logger = $logger;

        parent::__construct($string, $validators);
    }

    /**
     * @param string $attrCode
     * @param array $attrParams
     * @param array $rowData
     * @return bool
     * @SuppressWarnings(PHPMD.CyclomaticComplexity)
     * @SuppressWarnings(PHPMD.NPathComplexity)
     */
    public function isAttributeValid($attrCode, array $attrParams, array $rowData)
    {
        $this->_rowData = $rowData;
        if (isset($rowData['product_type']) && !empty($attrParams['apply_to'])
            && !in_array($rowData['product_type'], $attrParams['apply_to'])
        ) {
            return true;
        }

        if (!$this->isRequiredAttributeValid($attrCode, $attrParams, $rowData)) {
            $valid = false;
            $this->_addMessages(
                [
                    sprintf(
                        $this->context->retrieveMessageTemplate(
                            RowValidatorInterface::ERROR_VALUE_IS_REQUIRED
                        ),
                        $attrCode
                    )
                ]
            );
            return $valid;
        }

        if (!strlen(trim($rowData[$attrCode]))) {
            return true;
        }
        switch ($attrParams['type']) {
            case 'varchar':
            case 'text':
                $valid = $this->textValidation($attrCode, $attrParams['type']);
                break;
            case 'decimal':
            case 'int':
                $valid = $this->numericValidation($attrCode, $attrParams['type']);
                break;
            case 'select':
            case 'boolean':
            case 'multiselect':
                $values = explode(Product::PSEUDO_MULTI_LINE_SEPARATOR, $rowData[$attrCode]);
                $valid = true;

                // Start custom
                foreach ($values as $value) {
                    // If option not exist and not already dynamically added
                    if (!empty($value) && !isset($attrParams['options'][strtolower($value)]) && !isset($this->dynamicallyOptionAdded[$attrCode][strtolower($value)])) {
                        // Create option value
                        $optionDataObject = $this->optionDataFactory->create();
                        $this->dataObjectHelper->populateWithArray(
                            $optionDataObject,
                            array(
                                'label' => $value,
                                'sort_order' => 100,
                                'is_default' => true
                            ),
                            '\Magento\Eav\Api\Data\AttributeOptionInterface'
                        );

                        // Add option dynamically
                        if ($this->optionManagement->add($attrCode, $optionDataObject)) {
                            // Add new option value dynamically created to the different entityTypeModel cache
                            $entityTypeModel                = $this->context->retrieveProductTypeByName($rowData['product_type']);
                            $configurableEntityTypeModel    = $this->context->retrieveProductTypeByName('configurable');

                            // Refresh attributes cache for entityTypeModel cache
                            if ($entityTypeModel) {
                                $entityTypeModel->refreshCacheAttributes();
                            }

                            if ($configurableEntityTypeModel) {
                                $configurableEntityTypeModel->refreshCacheAttributes();
                            }

                            $this->dynamicallyOptionAdded[$attrCode][strtolower($value)] = true;
                            $attrParams['options'][strtolower($value)] = true;
                        }
                    }
                }

                if (isset($this->dynamicallyOptionAdded[$attrCode])) {
                    foreach ($this->dynamicallyOptionAdded[$attrCode] as $key => $value) {
                        $attrParams['options'][$key] = $value;
                    }
                }
                // end custom

                foreach ($values as $value) {
                    $valid = $valid && isset($attrParams['options'][strtolower($value)]);
                }
                if (!$valid) {
                    $this->_addMessages(
                        [
                            sprintf(
                                $this->context->retrieveMessageTemplate(
                                    RowValidatorInterface::ERROR_INVALID_ATTRIBUTE_OPTION
                                ),
                                $attrCode
                            )
                        ]
                    );
                }

                break;
            case 'datetime':
                $val = trim($rowData[$attrCode]);
                $valid = strtotime($val) !== false;
                if (!$valid) {
                    $this->_addMessages([RowValidatorInterface::ERROR_INVALID_ATTRIBUTE_TYPE]);
                }
                break;
            default:
                $valid = true;
                break;
        }

        if ($valid && !empty($attrParams['is_unique'])) {
            if (isset($this->_uniqueAttributes[$attrCode][$rowData[$attrCode]])
                && ($this->_uniqueAttributes[$attrCode][$rowData[$attrCode]] != $rowData[Product::COL_SKU])) {
                $this->_addMessages([RowValidatorInterface::ERROR_DUPLICATE_UNIQUE_ATTRIBUTE]);
                return false;
            }
            $this->_uniqueAttributes[$attrCode][$rowData[$attrCode]] = $rowData[Product::COL_SKU];
        }

        if (!$valid) {
            $this->setInvalidAttribute($attrCode);
        }

        return (bool)$valid;

    }
}

Extend the class app/code/Vendor/ImportExport/Model/Import/Product/Type/Configurable.php :

<?php

// @codingStandardsIgnoreFile

namespace Vendor\ImportExport\Model\Import\Product\Type;

use Magento\Catalog\Api\Data\ProductInterface;
use Magento\CatalogImportExport\Model\Import\Product as ImportProduct;

/**
 * Importing configurable products
 * @package Magento\ConfigurableImportExport\Model\Import\Product\Type
 * @SuppressWarnings(PHPMD.TooManyFields)
 * @SuppressWarnings(PHPMD.ExcessiveClassComplexity)
 * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
 */
class Configurable extends \Magento\ConfigurableImportExport\Model\Import\Product\Type\Configurable
{
    /**
     * In case we've dynamically added new attribute option during import we need to add it to our cache
     * in order to keep it up to date.
     *
     * @todo Try an optimal solution in order to update only the need part of the cache (Check AddAttributeOption)
     *
     */
    public function refreshCacheAttributes()
    {
        // Need to force reload attribute cache
        self::$commonAttributesCache = [];
        $this->_initAttributes();
    }
}

Extend the class app/code/Vendor/ImportExport/Model/Import/Product/Type/Simple.php :

<?php
/**
 * Copyright © 2016 Magento. All rights reserved.
 * See COPYING.txt for license details.
 */
namespace Vendor\ImportExport\Model\Import\Product\Type;
/**
 * Import entity simple product type
 *
 * @author      Magento Core Team <[email protected]>
 */
class Simple extends \Magento\CatalogImportExport\Model\Import\Product\Type\Simple
{
    /**
     * In case we've dynamically added new attribute option during import we need to add it to our cache
     * in order to keep it up to date.
     *
     * @todo Try an optimal solution in order to update only the need part of the cache (Check AddAttributeOption)
     *
     */
    public function refreshCacheAttributes()
    {
        // Need to force reload attribute cache
        self::$commonAttributesCache = [];
        $this->_initAttributes();
    }
}
6
  • Thanks so much for your process Franck, let me try this on our development server!
    – bennychaos
    Nov 24, 2016 at 17:32
  • No problem, share your experiments and update vote if it works for you. Nov 25, 2016 at 7:54
  • It's creating only one option value. Aug 4, 2017 at 9:47
  • It is the goal, your attribute should be created before you import your product thanks to setup script. Aug 5, 2017 at 13:50
  • is this not fixed yet in magento 2.3.1
    – Jaisa
    Jul 22, 2020 at 19:57

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.