Magento – Magento 2.1.2 – Importing multiselect attribute values from CSV

attributescsvimportmagento2multiselect-attribute

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,

Best Answer

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 <core@magentocommerce.com>
 */
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();
    }
}
Related Topic