Spamworldpro Mini Shell
Spamworldpro


Server : Apache
System : Linux server2.corals.io 4.18.0-348.2.1.el8_5.x86_64 #1 SMP Mon Nov 15 09:17:08 EST 2021 x86_64
User : corals ( 1002)
PHP Version : 7.4.33
Disable Function : exec,passthru,shell_exec,system
Directory :  /home/corals/cartforge.co/app/code/Xtento/StockImport/Model/Import/Entity/

Upload File :
current_dir [ Writeable ] document_root [ Writeable ]

 

Current File : /home/corals/cartforge.co/app/code/Xtento/StockImport/Model/Import/Entity/Stock.php
<?php

/**
 * Product:       Xtento_StockImport
 * ID:            u66QkJ5rBwmimhUzUElhIKqqWRvsbhC3WLqSMk5AjmQ=
 * Last Modified: 2022-10-11T19:19:33+00:00
 * File:          app/code/Xtento/StockImport/Model/Import/Entity/Stock.php
 * Copyright:     Copyright (c) XTENTO GmbH & Co. KG <[email protected]> / All rights reserved.
 */

namespace Xtento\StockImport\Model\Import\Entity;

use Magento\Catalog\Model\ProductFactory;
use Magento\CatalogInventory\Api\StockRegistryInterface;
use Magento\Customer\Model\Group;
use Magento\Eav\Model\Entity\Attribute;
use Magento\Framework\App\Config\ScopeConfigInterface;
use Magento\Framework\App\ResourceConnection;
use Magento\Framework\Event\ManagerInterface;
use Magento\Framework\Exception\LocalizedException;
use Magento\Framework\ObjectManagerInterface;
use Magento\Framework\Registry;
use Magento\Store\Model\StoreManagerInterface;
use Xtento\StockImport\Helper\Entity;
use Magento\Eav\Model\Config;
use Magento\Eav\Model\Entity\AttributeFactory;
use \Magento\Eav\Model\ResourceModel\Entity\Attribute\CollectionFactory as AttributeCollectionFactory;
use Xtento\StockImport\Helper\Module;
use Magento\Catalog\Model\Product\Action;
use Magento\Catalog\Model\Product\Attribute\Source\Status as ProductStatus;
use Magento\ConfigurableProduct\Model\Product\Type\Configurable;
use Magento\Framework\Indexer\IndexerRegistry;
use Magento\Framework\App\Cache\TypeListInterface;
use Magento\Framework\App\PageCache\Cache;
use Magento\CatalogInventory\Model\ResourceModel\Stock as ResourceStock;

class Stock extends AbstractEntity
{
    static $importStock = true;
    static $importPrices = true;
    static $importSpecialPrices = true;
    static $importCost = true;
    static $importProductStatus = true;
    static $importCustomAttributes = true;
    static $maxImportFilterCount = 3;

    /*
     * Attribute to identify stock items by
     */
    protected $attributeToLoadBy = 'sku';
    /*
     * Product identifiers (could be the SKU, an attribute, ... - attribute loaded by defined in function getProductIdsForProductIdentifiers)
     */
    protected $productIdentifiers = [];
    /*
     * Associative array holding productIdentifer => product_id
     */
    protected $productMap = [];
    protected $productTypeMap = [];
    protected $productIdToSku = []; // Required for MSI
    /*
     * Products not found in Magento
     */
    protected $productsNotFound = [];
    /*
     * Existing stock_items taken directly from the cataloginventory_stock_item table.
     */
    protected $stockItems = [];
    /*
     * Existing MSI stock items
     */
    protected $msiItems = [];
    /*
     * Existing stock_status items taken directly from the cataloginventory_stock_status table.
     */
    protected $stockStatusItems = [];
    /*
     * Which stock_items have been modified? Important for re-index
     */
    protected $modifiedStockItems = [];
    /*
     * Which MSI items have been modified?
     */
    protected $modifiedMsiItems = [];
    /*
     * Current prices for products
     */
    protected $prices = [];
    protected $specialPrices = [];
    protected $costValues = [];
    /*
     * Updated prices
     */
    protected $updatedPrices = [];
    protected $updatedSpecialPrices = [];
    protected $updatedCostValues = [];
    protected $updatedTierPrices = [];
    /*
     * Current product status
     */
    protected $productStatus = [];
    protected $updatedProductStatuses = [];
    /*
     * Custom product attribute values
     */
    protected $customProductAttributeValues = [];
    protected $updatedCustomProductAttributes = [];
    protected $customProductAttributes = [];

    /**
     * Entity ID field for catalog_product_entity_decimal update; changed in EE 2.1
     */
    protected $productEntityDecimalFieldName = 'entity_id';

    /**
     * Price scope, per website or global?
     */
    protected $priceScope = null; // 0 = global, 1 = website

    /**
     * @var ManagerInterface
     */
    protected $eventManager;

    /**
     * @var ObjectManagerInterface
     */
    protected $objectManager;

    /**
     * @var ProductFactory
     */
    protected $productFactory;

    /**
     * @var Entity
     */
    protected $entityHelper;

    /**
     * @var Config
     */
    protected $eavConfig;

    /**
     * @var AttributeFactory
     */
    protected $entityAttributeFactory;

    /**
     * @var AttributeCollectionFactory
     */
    protected $entityAttributeCollectionFactory;

    /**
     * @var Module
     */
    protected $moduleHelper;

    /**
     * @var Action
     */
    protected $productAction;

    /**
     * @var ScopeConfigInterface
     */
    protected $scopeConfig;

    /**
     * @var StoreManagerInterface
     */
    protected $storeManager;

    /**
     * @var Configurable
     */
    protected $configurableProduct;

    /**
     * @var StockRegistryInterface
     */
    protected $stockRegistry;

    /**
     * @var IndexerRegistry
     */
    protected $indexerRegistry;

    /**
     * @var TypeListInterface
     */
    protected $cacheTypeList;

    /**
     * @var Cache
     */
    protected $pageCache;

    /**
     * @var ResourceStock
     */
    protected $resourceStock;

    /**
     * @var \Magento\Framework\App\ProductMetadataInterface
     */
    protected $productMetadata;

    /**
     * @var \Xtento\XtCore\Helper\Utils
     */
    protected $utilsHelper;

    /**
     * @var \Magento\Framework\App\CacheInterface
     */
    protected $cacheManager;

    /**
     * @var \Magento\Catalog\Api\ProductRepositoryInterface
     */
    protected $productRepository;

    /**
     * Stock constructor.
     *
     * @param ResourceConnection $resourceConnection
     * @param Registry $frameworkRegistry
     * @param ObjectManagerInterface $objectManager
     * @param ManagerInterface $eventManagerInterface
     * @param ProductFactory $productFactory
     * @param Entity $entityHelper
     * @param Config $eavConfig
     * @param AttributeFactory $entityAttributeFactory
     * @param AttributeCollectionFactory $attributeCollectionFactory
     * @param Module $moduleHelper
     * @param Action $productAction
     * @param ScopeConfigInterface $scopeConfig
     * @param StoreManagerInterface $storeManager
     * @param Configurable $configurableProduct
     * @param StockRegistryInterface $stockRegistry
     * @param IndexerRegistry $indexerRegistry
     * @param TypeListInterface $typeList
     * @param Cache $pageCache
     * @param ResourceStock $resourceStock
     * @param \Magento\Framework\App\ProductMetadataInterface $productMetadata
     * @param \Xtento\XtCore\Helper\Utils $utilsHelper
     * @param \Magento\Framework\App\CacheInterface $cacheManager
     * @param \Magento\Catalog\Api\ProductRepositoryInterface $productRepository
     * @param array $data
     */
    public function __construct(
        ResourceConnection $resourceConnection,
        Registry $frameworkRegistry,
        ObjectManagerInterface $objectManager,
        ManagerInterface $eventManagerInterface,
        ProductFactory $productFactory,
        Entity $entityHelper,
        Config $eavConfig,
        AttributeFactory $entityAttributeFactory,
        AttributeCollectionFactory $attributeCollectionFactory,
        Module $moduleHelper,
        Action $productAction,
        ScopeConfigInterface $scopeConfig,
        StoreManagerInterface $storeManager,
        Configurable $configurableProduct,
        StockRegistryInterface $stockRegistry,
        IndexerRegistry $indexerRegistry,
        TypeListInterface $typeList,
        Cache $pageCache,
        ResourceStock $resourceStock,
        \Magento\Framework\App\ProductMetadataInterface $productMetadata,
        \Xtento\XtCore\Helper\Utils $utilsHelper,
        \Magento\Framework\App\CacheInterface $cacheManager,
        \Magento\Catalog\Api\ProductRepositoryInterface $productRepository,
        array $data = []
    ) {
        $this->eventManager = $eventManagerInterface;
        $this->objectManager = $objectManager;
        $this->productFactory = $productFactory;
        $this->entityHelper = $entityHelper;
        $this->eavConfig = $eavConfig;
        $this->entityAttributeFactory = $entityAttributeFactory;
        $this->entityAttributeCollectionFactory = $attributeCollectionFactory;
        $this->moduleHelper = $moduleHelper;
        $this->productAction = $productAction;
        $this->scopeConfig = $scopeConfig;
        $this->storeManager = $storeManager;
        $this->configurableProduct = $configurableProduct;
        $this->stockRegistry = $stockRegistry;
        $this->indexerRegistry = $indexerRegistry;
        $this->cacheTypeList = $typeList;
        $this->pageCache = $pageCache;
        $this->resourceStock = $resourceStock;
        $this->productMetadata = $productMetadata;
        $this->utilsHelper = $utilsHelper;
        $this->cacheManager = $cacheManager;
        $this->productRepository = $productRepository;
        parent::__construct($resourceConnection, $frameworkRegistry, $data);
    }

    /**
     * Prepare import by getting a mapping of the attribute used to identify the product and its product id
     *
     * @param $updatesInFilesToProcess
     *
     * @return bool
     */
    public function prepareImport($updatesInFilesToProcess)
    {
        // Check is Magento EE >=2.1, if so use different catalog_product_entity_decimal price field name (row_id in EE 2.1)
        if ($this->utilsHelper->isMagentoEnterprise() && $this->utilsHelper->mageVersionCompare($this->productMetadata->getVersion(), '2.1.0', '>=')) {
            $this->productEntityDecimalFieldName = 'row_id';
        }

        // Prepare import
        $this->eventManager->dispatch('xtento_stockimport_stockupdate_before', [
            'profile' => $this->getProfile(),
            'log' => $this->getLogEntry(),
            'updates' => &$updatesInFilesToProcess
        ]);

        if (!$this->getTestMode()) {
            // Reset stock, uncomment to enable
            /*$this->writeAdapter->update(
                $this->getTableName('cataloginventory_stock_item'),
                array('qty' => 0, 'is_in_stock' => 0)
            );
            $this->writeAdapter->update(
                $this->getTableName('cataloginventory_stock_status'),
                array('qty' => 0, 'stock_status' => 0)
            );*/

            // Reset stock for specific product IDs, uncomment to enable
            /** @var \Magento\Catalog\Model\ResourceModel\Product\Collection $productCollection */
            /*$productCollection = $this->productFactory->create()->getCollection();
            $productCollection->addAttributeToFilter('supplier', 'techdata');
            $productIds = $productCollection->getAllIds();
            if (is_array($productIds)) {
                $this->writeAdapter->update(
                    $this->getTableName('cataloginventory_stock_item'),
                    array('qty' => 0, 'is_in_stock' => 0),
                    "product_id IN (" . join(",", $productIds) . ")"
                );
                $this->writeAdapter->update(
                    $this->getTableName('cataloginventory_stock_status'),
                    array('qty' => 0, 'stock_status' => 0),
                    "product_id IN (" . join(",", $productIds) . ")"
                );
            }*/
        }

        // When importing duplicate SKUs from multiple files, the import may fail, as it thinks it needs to insert the stock item/status entry again. Refresh stock item/status tables after every file processed.
        // Prepare product identifiers
        $this->getProductIdentifiers($updatesInFilesToProcess);
        if (empty($this->productIdentifiers)) {
            $this->getLogEntry()->addDebugMessage(__('No products could be found in the import file.'));
            return false;
        }

        // Get product IDs for product identifiers
        $this->getProductIdsForProductIdentifiers();
        if (empty($this->productMap)) {
            $this->getLogEntry()->addDebugMessage(__('The products supplied in the import file could not be found in the Magento catalog.'));
            return false;
        }

        $this->applyFiltersToFoundProducts();
        if (empty($this->productMap)) {
            $this->getLogEntry()->addDebugMessage(__('The products supplied in the import file could not be found in the Magento catalog OR all were filtered by profile filters.'));
            return false;
        }

        // Find out which products couldn't be found in Magento
        foreach ($this->productIdentifiers as $productIdentifier) {
            if (!isset($this->productMap[$productIdentifier])) {
                array_push($this->productsNotFound, $productIdentifier);
            }
        }

        if (!$this->getTestMode()) {
            // Set all products not in import files to out of stock
            if ($this->getConfigFlag('reset_stock_of_products_not_in_file')) {
                // Get all products where stock is managed
                $select = $this->readAdapter->select()
                    ->from($this->getTableName('cataloginventory_stock_item'), ['product_id'])
                    ->where("manage_stock=1");
                $stockManagedProducts = $this->readAdapter->fetchCol($select);

                $productsToReset = [];
                foreach ($stockManagedProducts as $key => $productId) {
                    if (!in_array($productId, $this->productMap)) { // Product is not in import file, reset it
                        $productsToReset[] = $productId;
                    }
                }

                if (!empty($productsToReset)) {
                    $this->writeAdapter->update(
                        $this->getTableName('cataloginventory_stock_item'),
                        ['qty' => 0, 'is_in_stock' => 0],
                        "product_id IN (" . join(",", $productsToReset) . ")"
                    );
                    $this->writeAdapter->update(
                        $this->getTableName('cataloginventory_stock_status'),
                        ['qty' => 0, 'stock_status' => 0],
                        "product_id IN (" . join(",", $productsToReset) . ")"
                    );
                }

                if ($this->entityHelper->getMagentoMSISupport()) {
                    // Magento MSI version
                    // Not yet adjusted for "manage_stock"
                    $msiSource = false;
                    foreach ($updatesInFilesToProcess as $updateFile) {
                        foreach ($updateFile['ITEMS'] as $stockId => $updatesInFile) {
                            foreach ($updatesInFile as $productIdentifier => $updateData) {
                                if (isset($updateData['source_code'])) {
                                    $msiSource = $updateData['source_code'];
                                    break 3;
                                }
                            }
                        }
                    }
                    if ($msiSource !== false && !empty($this->productIdToSku)) {
                        $this->writeAdapter->update(
                            $this->getTableName('inventory_source_item'), [
                            'quantity' => 0,
                            'status' => 0
                        ], "sku not in ('" . join('\',\'', array_values($this->productIdToSku)) . "') and source_code=".$this->writeAdapter->quote($msiSource).""
                        );
                    }
                }
            }
        }

        // Which fields are in the file and should be handled for the import?
        $fieldsFound = [];
        foreach ($updatesInFilesToProcess as $updatesInFile) {
            if (isset($updatesInFile['FIELDS'])) {
                foreach ($updatesInFile['FIELDS'] as $field) {
                    $fieldsFound[] = $field;
                }
            }
        }
        if (!in_array('qty', $fieldsFound)
            && !in_array('is_in_stock', $fieldsFound)
            && !in_array('backorders', $fieldsFound)
            && !in_array('manage_stock', $fieldsFound)
            && !in_array('use_config_manage_stock', $fieldsFound)
            && !in_array('notify_stock_qty', $fieldsFound)
            && !in_array('min_qty', $fieldsFound)
            && !in_array('min_sale_qty', $fieldsFound)
            && !in_array('max_sale_qty', $fieldsFound)
        ) {
            self::$importStock = false;
        } else {
            self::$importStock = true;
        }
        if (!in_array('price', $fieldsFound)) {
            self::$importPrices = false;
        } else {
            self::$importPrices = true;
        }
        if (!in_array('special_price', $fieldsFound)) {
            self::$importSpecialPrices = false;
        } else {
            self::$importSpecialPrices = true;
        }
        if (!in_array('cost', $fieldsFound)) {
            self::$importCost = false;
        } else {
            self::$importCost = true;
        }
        if (!in_array('status', $fieldsFound)) {
            self::$importProductStatus = false;
        } else {
            self::$importProductStatus = true;
        }
        // Custom product attributes
        $customProductAttributeFound = false;
        foreach ($fieldsFound as $fieldFound) {
            if (stristr($fieldFound, 'cpa_') !== false) {
                $customProductAttributeFound = true;
            }
        }
        if ($customProductAttributeFound) {
            self::$importCustomAttributes = true;
        } else {
            self::$importCustomAttributes = false;
        }

        // Proceed with gathering information required for the import
        if (self::$importStock) {
            // Get current stock info - so what exists in the stock tables and what doesn't
            $this->getCurrentStockInfo();
            if ($this->entityHelper->getMagentoMSISupport()) {
                $this->getMsiItemInfo();
            }
        }

        if (self::$importPrices || self::$importSpecialPrices || self::$importCost) {
            // If price import is enabled.. get price info
            $this->priceScope = (int)$this->scopeConfig->getValue('catalog/price/scope', \Magento\Store\Model\ScopeInterface::SCOPE_WEBSITE) ?: 0;
            $this->getLogEntry()->addDebugMessage(__('Price scope for price updates: %1', $this->priceScope === 0 ? __('Global') : __('Per Website')));
            $this->getCurrentPriceInfo();
        }
        if (self::$importProductStatus) {
            $this->getCurrentProductInfo();
        }
        if (self::$importCustomAttributes) {
            $this->getCurrentProductAttributeValues($fieldsFound);
        }

        // Start transaction for all the updates.. performance is the key here!
        #$this->writeAdapter->query('LOCK TABLES '.$this->getTableName('cataloginventory_stock_status').' WRITE');
        #$this->writeAdapter->query('LOCK TABLES '.$this->getTableName('cataloginventory_stock_item').' WRITE');

        // Only start a transaction if no product data is updated:
        if (!self::$importPrices && !self::$importSpecialPrices && !self::$importCost && !self::$importProductStatus && !self::$importCustomAttributes) {
            $this->writeAdapter->beginTransaction();
        }

        $this->getLogEntry()->addDebugMessage(__('Transaction started. Starting import.'));
        return true;
    }

    /*
     * Get all the product identifiers we're supposed to identify stock items by. Could be the SKU or an attribute.
     */
    protected function getProductIdentifiers($updatesInFilesToProcess)
    {
        $this->productIdentifiers = [];
        foreach ($updatesInFilesToProcess as $updateFile) {
            foreach ($updateFile['ITEMS'] as $stockId => $updatesInFile) {
                foreach ($updatesInFile as $productIdentifier => $updateData) {
                    $productIdentifier = trim($productIdentifier);
                    array_push($this->productIdentifiers, strtolower($productIdentifier));
                }
            }
        }
        return $this->productIdentifiers;
    }

    /*
     * Get product ids for stock items based on the product identifiers supplied
     */
    protected function getProductIdsForProductIdentifiers()
    {
        // Which attribute is supposed be the identifier in the import file for the mapping to the actual products in Magento?
        if ($this->getConfig('product_identifier') == 'sku') {
            $this->attributeToLoadBy = 'sku';
        } else if ($this->getConfig('product_identifier') == 'attribute') {
            $this->attributeToLoadBy = $this->getConfig('product_identifier_attribute_code');
        } else if ($this->getConfig('product_identifier') == 'entity_id') {
            $this->attributeToLoadBy = 'entity_id';
        } else {
            throw new LocalizedException(__('Stock import: Attribute to use for identifying products not defined.'));
        }

        if ($this->attributeToLoadBy == 'sku') {
            $select = $this->readAdapter->select()
                ->from($this->getTableName('catalog_product_entity'), ['entity_id', 'type_id', 'sku'])
                ->where("LOWER(sku) in (" . $this->readAdapter->quote($this->productIdentifiers) . ")");
            $products = $this->readAdapter->fetchAll($select);

            foreach ($products as $product) {
                $this->productMap[trim(strtolower($product['sku']))] = $product['entity_id'];
                $this->productTypeMap[$product['entity_id']] = $product['type_id'];
                $this->productIdToSku[$product['entity_id']] = $product['sku'];
            }

            $productsNotFound = [];
            foreach ($this->productIdentifiers as $productIdentifier) {
                if (!isset($this->productMap[$productIdentifier])) {
                    array_push($productsNotFound, $productIdentifier);
                }
            }
            if (!empty($productsNotFound)) {
                $this->getLogEntry()->addDebugMessage(__('The following SKUs defined in the import file could not be found in the catalog: %1', join(", ", $productsNotFound)));
                #mail($this->moduleHelper->getDebugEmail(), 'Magento Stock Import Module @ ' . @$_SERVER['SERVER_NAME'], 'Stock Import products not found: ' . join(", ", $productsNotFound));
            }

            unset($products, $select);
        } else if ($this->getConfig('product_identifier') == 'attribute') {
            // Check if attribute exists
            $entityType = $this->eavConfig->getEntityType(\Magento\Catalog\Model\Product::ENTITY);
            $eavAttribute = $this->entityAttributeCollectionFactory->create()
                ->addFieldToFilter('entity_type_id', $entityType->getId())
                ->addFieldToFilter('attribute_code', $this->attributeToLoadBy)
                ->getFirstItem();
            if (!$eavAttribute || !$eavAttribute->getId()) {
                throw new LocalizedException(__('The supplied product attribute code used to identify products does not exist.'));
            }

            // Load product collection
            $productCollection = $this->productFactory->create()->getCollection()
                ->addAttributeToSelect('entity_id')
                ->addAttributeToSelect('sku')
                ->addAttributeToSelect($this->attributeToLoadBy)
                ->addAttributeToFilter($this->attributeToLoadBy, ['in' => str_replace("'", "", $this->productIdentifiers)]);

            foreach ($productCollection as $product) {
                $attrValue = $product->getData($this->attributeToLoadBy);
                $attrValue = trim(strtolower($attrValue));
                $this->productMap[$attrValue] = $product->getId();
                $this->productTypeMap[$product->getId()] = $product->getTypeId();
                $this->productIdToSku[$product->getId()] = $product->getSku();
            }
            unset($productCollection);
        } else if ($this->getConfig('product_identifier') == 'entity_id') {
            // We're supposed to use the entity_id to identify products.. that's great. Just use the IDs to load from tables etc. then!
            $select = $this->readAdapter->select()
                ->from($this->getTableName('catalog_product_entity'), ['entity_id', 'type_id', 'sku'])
                ->where("entity_id in (" . $this->readAdapter->quote($this->productIdentifiers) . ")");
            $products = $this->readAdapter->fetchAll($select);

            foreach ($products as $product) {
                if ($product['type_id'] == 'configurable' || $product['type_id'] == 'downloadable') {
                    continue;
                }
                $this->productMap[$product['entity_id']] = $product['entity_id'];
                $this->productTypeMap[$product['entity_id']] = $product['type_id'];
                $this->productIdToSku[$product['entity_id']] = $product['sku'];
            }

            $productsNotFound = [];
            foreach ($this->productIdentifiers as $productIdentifier) {
                if (!isset($this->productMap[$productIdentifier])) {
                    array_push($productsNotFound, $productIdentifier);
                }
            }
            if (!empty($productsNotFound)) {
                $this->getLogEntry()->addDebugMessage(__('The following product IDs defined in the import file could not be found in the catalog: %1', join(", ", $productsNotFound)));
                #mail($this->moduleHelper->getDebugEmail(), 'Stock Import Module @ ' . @$_SERVER['SERVER_NAME'], 'Stock Import - Products not found: ' . join(", ", $productsNotFound));
            }

            unset($products, $select);
        }
    }

    protected function applyFiltersToFoundProducts()
    {
        $profileConfig = $this->getProfile()->getConfiguration();
        for ($i = 1; $i <= self::$maxImportFilterCount; $i++) {
            if (!isset($profileConfig['import_filter_' . $i])) {
                continue;
            }
            $filter = $profileConfig['import_filter_' . $i];
            if (!array_key_exists('filter', $filter) ||
                !array_key_exists('attribute', $filter) ||
                !array_key_exists('condition', $filter) ||
                !array_key_exists('value', $filter)
            ) {
                $this->getLogEntry()->addDebugMessage(__('Warning: Filter %1 has not been configured properly. Filter skipped.', $i));
                continue;
            }
            if ($filter['filter'] == '' &&
                $filter['attribute'] == '' &&
                $filter['condition'] == '' &&
                $filter['value'] == ''
            ) {
                // Filter has not been set up - skip it.
                continue;
            }
            if ($filter['filter'] == '' ||
                $filter['attribute'] == '' ||
                $filter['condition'] == '' ||
                $filter['value'] == ''
            ) {
                $this->getLogEntry()->addDebugMessage(__('Warning: Filter %1 has not been configured properly. One more multiple filter fields are empty. Filter skipped.', $i));
                continue;
            }
            // Load products affected by filter, and "combine" products found + filter products so filter is applied.
            $productCollection = $this->productFactory->create()->getCollection()
                ->addAttributeToSelect('entity_id');
            // Determine product attribute to filter by, handle dropdown attributes
            $eavAttribute = $this->eavConfig->getAttribute('catalog_product', $filter['attribute']);
            if (!$eavAttribute || !$eavAttribute->getId()) {
                $this->getLogEntry()->addDebugMessage(__('Warning: Filter %1 uses a product attribute to filter which does not exist anymore. Filter skipped.', $i));
                continue;
            }
            if ($eavAttribute->getFrontendInput() == 'select') {
                $dropdownId = null;
                $attributeOptions = $eavAttribute->getSource()->getAllOptions();
                foreach ($attributeOptions as $option) {
                    if (strcasecmp($option['label'], $filter['value']) == 0 || $option['value'] == $filter['value']) {
                        $dropdownId = $option['value'];
                    }
                }
                if ($dropdownId === null) {
                    $this->getLogEntry()->addDebugMessage(__('Warning: Filter %1 tries to filter by a dropdown attribute value which does not exist. Please check the attribute "%2" and make sure the exact dropdown option ("%3") exists as an attribute option (Store View = Admin).', $i, $filter['attribute'], $filter['value']));
                    continue;
                } else {
                    $filter['value'] = $dropdownId;
                }
            } else {
                if ($filter['condition'] == 'like' || $filter['condition'] == 'nlike') {
                    $filter['value'] = '%' . $filter['value'] . '%';
                }
            }
            if ($filter['condition'] == 'neq') {
                $productCollection->addAttributeToFilter(
                    $filter['attribute'],
                    [
                        [$filter['condition'] => $filter['value']],
                        ['null' => true]
                    ],
                    'left'
                ); // Left join is required, so attribute values when joining attributes which don't have values which then are NULL can be checked
            } else {
                $productCollection->addAttributeToFilter(
                    $filter['attribute'],
                    [$filter['condition'] => $filter['value']]
                );
            }
            #echo (string)$productCollection->getSelect(); die();
            $foundProductIds = $productCollection->getAllIds();
            $removedProducts = 0;
            if ($filter['filter'] == 'include_only') {
                foreach ($this->productMap as $productIdentifier => $productId) {
                    if (!in_array($productId, $foundProductIds)) {
                        unset($this->productMap[$productIdentifier]);
                        $removedProducts++;
                    }
                }
            }
            if ($filter['filter'] == 'exclude') {
                foreach ($this->productMap as $productIdentifier => $productId) {
                    if (in_array($productId, $foundProductIds)) {
                        unset($this->productMap[$productIdentifier]);
                        $removedProducts++;
                    }
                }
            }
            $this->getLogEntry()->addDebugMessage(__('Filter %1 has removed/filtered %2 product(s) from the import files.', $i, $removedProducts));
        }
        #die();
    }

    /*
     * Get information about current stock settings, only for products we want to update though
     */
    protected function getCurrentStockInfo()
    {
        // Get stock_item information
        $select = $this->readAdapter->select()
            ->from($this->getTableName('cataloginventory_stock_item'),
                [
                    'product_id',
                    'qty',
                    'is_in_stock',
                    'stock_id',
                    'manage_stock',
                    'notify_stock_qty',
                    'use_config_manage_stock',
                    'min_qty',
                    'use_config_min_qty',
                    'min_sale_qty',
                    'use_config_min_sale_qty',
                    'max_sale_qty',
                    'use_config_max_sale_qty',
                    'backorders',
                    'use_config_backorders'
                ]
            )
            ->where("product_id in (" . join(",", array_values($this->productMap)) . ")");
        $stockItems = $this->readAdapter->fetchAll($select);

        foreach ($stockItems as $stockItem) {
            // Prepare qty field
            $stockItem['qty'] = sprintf('%.4f', $stockItem['qty']);
            $this->stockItems[$stockItem['stock_id']][$stockItem['product_id']] = $stockItem;
        }

        // Get stock_status information
        $select = $this->readAdapter->select()
            ->from(
                $this->getTableName('cataloginventory_stock_status'),
                ['product_id', 'qty', 'stock_status', 'stock_id']
            )
            ->where("product_id in (" . join(",", array_values($this->productMap)) . ")");
        $stockStatusItems = $this->readAdapter->fetchAll($select);

        foreach ($stockStatusItems as $stockStatusItem) {
            // Prepare qty field
            $stockStatusItem['qty'] = sprintf('%.4f', $stockStatusItem['qty']);
            $this->stockStatusItems[$stockStatusItem['stock_id']][$stockStatusItem['product_id']] = $stockStatusItem;
        }
    }

    /*
     * Get information about current stock items in MSI tables
     */
    protected function getMsiItemInfo()
    {
        // Get stock_item information
        $select = $this->readAdapter->select()
            ->from($this->getTableName('inventory_source_item'),
                [
                    'source_item_id',
                    'source_code',
                    'sku',
                    'quantity',
                    'status'
                ]
            )
            ->where("sku in (?)", array_values($this->productIdToSku));
        $stockItems = $this->readAdapter->fetchAll($select);

        foreach ($stockItems as $stockItem) {
            // Prepare qty field
            $stockItem['quantity'] = sprintf('%.4f', $stockItem['quantity']);
            $this->msiItems[$stockItem['source_code']][$stockItem['sku']] = $stockItem;
        }
    }

    /*
     * Get information about the current price levels for the products in the import file
     */
    protected function getCurrentPriceInfo()
    {
        if (self::$importPrices) {
            $priceAttributeId = $this->entityAttributeFactory->create()->getIdByCode('catalog_product', 'price');
            if ($priceAttributeId) {
                $select = $this->readAdapter->select()
                    ->from($this->getTableName('catalog_product_entity_decimal'))
                    ->where('attribute_id = ?', $priceAttributeId)
                    ->where($this->productEntityDecimalFieldName . " in (" . join(",", array_values($this->productMap)) . ")");
                if ($this->priceScope != 0) { // Per website
                    $configUpdateStoreId = $this->getConfig('price_update_store_id');
                    if (is_array($configUpdateStoreId)) {
                        $configUpdateStoreId = array_filter($configUpdateStoreId);
                    }
                    $storeIds = false;
                    if (!empty($configUpdateStoreId)) {
                        $storeIds = join(",", $configUpdateStoreId);
                        if (!empty($storeIds)) {
                            $select->where("store_id in (" . $storeIds . ")");
                        }
                    }
                    if ($storeIds === false) {
                        $select->where("store_id = ?", 0);
                    }
                } else {
                    $select->where("store_id = ?", 0);
                }
                $currentPrices = $this->readAdapter->fetchAll($select);

                foreach ($currentPrices as $currentPrice) {
                    $this->prices[$currentPrice['store_id']][$currentPrice[$this->productEntityDecimalFieldName]] = ['price' => sprintf('%.4f', $currentPrice['value'])];
                }
            } else {
                throw new LocalizedException(__('Error while trying to get current price info. The price attribute could not be found.'));
            }
        }
        if (self::$importSpecialPrices) {
            $priceAttributeId = $this->entityAttributeFactory->create()->getIdByCode('catalog_product', 'special_price');
            if ($priceAttributeId) {
                $select = $this->readAdapter->select()
                    ->from($this->getTableName('catalog_product_entity_decimal'))
                    ->where('attribute_id = ?', $priceAttributeId)
                    ->where($this->productEntityDecimalFieldName . " in (" . join(",", array_values($this->productMap)) . ")");
                if ($this->priceScope != 0) { // Per website
                    $configUpdateStoreId = $this->getConfig('price_update_store_id');
                    if (is_array($configUpdateStoreId)) {
                        $configUpdateStoreId = array_filter($configUpdateStoreId);
                    }
                    $storeIds = false;
                    if (!empty($configUpdateStoreId)) {
                        $storeIds = join(",", $configUpdateStoreId);
                        if (!empty($storeIds)) {
                            $select->where("store_id in (" . $storeIds . ")");
                        }
                    }
                    if ($storeIds === false) {
                        $select->where("store_id = ?", 0);
                    }
                } else {
                    $select->where("store_id = ?", 0);
                }
                $currentPrices = $this->readAdapter->fetchAll($select);

                foreach ($currentPrices as $currentPrice) {
                    if (!empty($currentPrice['value'])) {
                        $this->specialPrices[$currentPrice['store_id']][$currentPrice[$this->productEntityDecimalFieldName]] = ['special_price' => sprintf('%.4f', $currentPrice['value'])];
                    } else {
                        $this->specialPrices[$currentPrice['store_id']][$currentPrice[$this->productEntityDecimalFieldName]] = ['special_price' => ''];
                    }
                }
            } else {
                throw new LocalizedException(__('Error while trying to get current special price info. The special price attribute could not be found.'));
            }
        }
        if (self::$importCost) {
            $costAttributeId = $this->entityAttributeFactory->create()->getIdByCode('catalog_product', 'cost');
            if ($costAttributeId) {
                $select = $this->readAdapter->select()
                    ->from($this->getTableName('catalog_product_entity_decimal'))
                    ->where('attribute_id = ?', $costAttributeId)
                    ->where($this->productEntityDecimalFieldName . " in (" . join(",", array_values($this->productMap)) . ")");
                if ($this->priceScope != 0) { // Per website
                    $configUpdateStoreId = $this->getConfig('price_update_store_id');
                    if (is_array($configUpdateStoreId)) {
                        $configUpdateStoreId = array_filter($configUpdateStoreId);
                    }
                    $storeIds = false;
                    if (!empty($configUpdateStoreId)) {
                        $storeIds = join(",", $configUpdateStoreId);
                        if (!empty($storeIds)) {
                            $select->where("store_id in (" . $storeIds . ")");
                        }
                    }
                    if ($storeIds === false) {
                        $select->where("store_id = ?", 0);
                    }
                } else {
                    $select->where("store_id = ?", 0);
                }
                $currentPrices = $this->readAdapter->fetchAll($select);

                foreach ($currentPrices as $currentPrice) {
                    if (!empty($currentPrice['value'])) {
                        $this->costValues[$currentPrice['store_id']][$currentPrice[$this->productEntityDecimalFieldName]] = ['cost' => sprintf('%.4f', $currentPrice['value'])];
                    } else {
                        $this->costValues[$currentPrice['store_id']][$currentPrice[$this->productEntityDecimalFieldName]] = ['cost' => ''];
                    }
                }
            } else {
                self::$importCost = false;
                #throw new LocalizedException(__('Error while trying to get current "cost" info. The cost attribute could not be found.'));
            }
        }
        #var_dump($this->prices);
        #die();
    }

    /*
     * Get information about the current price levels for the products in the import file
     */
    protected function getCurrentProductInfo()
    {
        $attributeId = $this->entityAttributeFactory->create()->getIdByCode('catalog_product', 'status');
        if ($attributeId) {
            $select = $this->readAdapter->select()
                ->from($this->getTableName('catalog_product_entity_int'))
                ->where('attribute_id = ?', $attributeId)
                ->where($this->productEntityDecimalFieldName . " in (" . join(",", array_values($this->productMap)) . ")");
            $configUpdateStoreId = $this->getConfig('price_update_store_id');
            if (is_array($configUpdateStoreId)) {
                $configUpdateStoreId = array_filter($configUpdateStoreId);
            }
            $storeIds = false;
            if (!empty($configUpdateStoreId)) {
                $storeIds = join(",", $configUpdateStoreId);
                if (!empty($storeIds)) {
                    $select->where("store_id in (" . $storeIds . ")");
                }
            }
            if ($storeIds === false) {
                $select->where("store_id = ?", 0);
            }
            $products = $this->readAdapter->fetchAll($select);

            foreach ($products as $product) {
                $this->productStatus[$product['store_id']][$product[$this->productEntityDecimalFieldName]] = ['status' => $product['value']];
            }
        }
    }

    protected function getCurrentProductAttributeValues($fields)
    {
        foreach ($fields as $field) {
            $isCustomAttribute = false;
            $attributeCode = preg_replace('/^cpa_/', '', $field, -1, $isCustomAttribute);
            if ($isCustomAttribute) {
                $productAttribute = $this->entityAttributeFactory->create()->loadByCode('catalog_product', $attributeCode);
                if ($productAttribute && $productAttribute->getId()) {
                    $productAttribute->setStoreId(0);
                    $attributeUpdateStoreIds = [\Magento\Store\Model\Store::DEFAULT_STORE_ID];
                    if ((int)$productAttribute->getIsGlobal() !== \Magento\Eav\Model\Entity\Attribute\ScopedAttributeInterface::SCOPE_GLOBAL) {
                        $configUpdateStoreId = $this->getConfig('price_update_store_id');
                        if (is_array($configUpdateStoreId)) {
                            $configUpdateStoreId = array_filter($configUpdateStoreId);
                        }
                        if ($configUpdateStoreId !== '' && !empty($configUpdateStoreId)) {
                            $attributeUpdateStoreIds = $configUpdateStoreId;
                        }
                    }
                    // Set store (for attribute values / source/dropdown attributes)
                    // $productAttribute->setStoreId($updateStoreId);
                    // Push into array
                    $this->customProductAttributes[$attributeCode] = $productAttribute;
                    foreach ($attributeUpdateStoreIds as $updateStoreId) {
                        $this->customProductAttributeValues[$updateStoreId][$attributeCode] = [];
                    }
                    $select = $this->readAdapter->select()
                        ->from($productAttribute->getBackendTable())
                        ->where('attribute_id = ?', $productAttribute->getId())
                        ->where($this->productEntityDecimalFieldName . " in (" . join(",", array_values($this->productMap)) . ")")
                        ->where("store_id in (" . join(",", $attributeUpdateStoreIds) . ")");
                    $attributeValues = $this->readAdapter->fetchAll($select);

                    foreach ($attributeValues as $attributeValue) {
                        $this->customProductAttributeValues[$attributeValue['store_id']][$attributeCode][$attributeValue[$this->productEntityDecimalFieldName]] = $attributeValue['value'];
                    }

                    // Add empty values for attributes which don't have entries in the DB
                    foreach ($attributeUpdateStoreIds as $updateStoreId) {
                        foreach ($this->productMap as $productIdentifier => $productId) {
                            if (!array_key_exists($productId, $this->customProductAttributeValues[$updateStoreId][$attributeCode])) {
                                $this->customProductAttributeValues[$updateStoreId][$attributeCode][$productId] = '';
                            }
                        }
                    }
                } else {
                    throw new LocalizedException(__('Error while trying to load attribute %1, attribute not found. Remove it from the import profile.', $attributeCode));
                }
            }
        }
    }

    /*
     * Update stock level for product
     */
    public function processItem($productIdentifier, $updateData)
    {
        // Result (and debug information) returned to observer
        $importResult = ['error' => 'Nothing happened yet.'];

        if (isset($updateData['product_identifier'])) {
            unset($updateData['product_identifier']);
        }
        $productIdentifier = strtolower($productIdentifier);

        if (!isset($updateData['stock_id']) || empty($updateData['stock_id'])) {
            $updateData['stock_id'] = 1;
        } else {
            $updateData['stock_id'] = intval($updateData['stock_id']);
        }

        if ($this->entityHelper->getMagentoMSISupport() && (!isset($updateData['source_code']) || empty($updateData['source_code']))) {
            $updateData['source_code'] = 'default';
        }

        $msiNote = '';
        if (isset($updateData['source_code'])) {
            $msiNote = __(' [MSI Source: %1]', $updateData['source_code']);
        }

        // Update stock_item, stock_status and eventually the price
        if (isset($this->productMap[$productIdentifier])) {
            $productId = $this->productMap[$productIdentifier];
            // Current import result.. nothing has changed yet
            $importResult = ['changed' => false, 'debug' => __("Product '%1' was found in Magento, but no fields have changed. Identified product ID is %2.", $productIdentifier, $productId)];

            // Adjust stock level by pending/processing orders
            if ($this->getConfigFlag('adjust_stock_pending_orders')) {
                $orderStatuses = $this->getConfig('adjust_stock_pending_orders_statuses');
                if (empty($orderStatuses) || !is_array($orderStatuses)) {
                    $orderStatuses = ['pending', 'processing'];
                }
                $operationMode = 'decrease';
                if ($this->getConfig('adjust_stock_pending_orders_mode') == 2) {
                    $operationMode = 'increase';
                }
                if (isset($updateData['qty'])) {
                    $orderItemCollection = $this->objectManager->create('\Magento\Sales\Model\Order\ItemFactory')->create()->getCollection();
                    $orderItemCollection
                        ->getSelect()
                        ->joinInner(
                            ['order' => $this->getTableName('sales_order')],
                            'order.entity_id = main_table.order_id'
                        )
                        ->where('main_table.product_id=?', $productId)
                        ->where('order.status in (?)', $orderStatuses);
                    if ($orderItemCollection->count() > 0) {
                        $blockedQty = 0;
                        foreach ($orderItemCollection as $orderItem) {
                            $blockedQty += (int)$orderItem->getQtyOrdered() - (int)$orderItem->getQtyShipped();
                        }
                        if ($operationMode == 'decrease') {
                            $updateData['qty'] = $updateData['qty'] - $blockedQty;
                        } else {
                            $updateData['qty'] = $updateData['qty'] + $blockedQty;
                        }
                    }
                    #var_dump($orderItemCollection->count(), $productId, $updateData['qty'], $blockedQty); die();
                }
            }
            // End stock level adjustment by pending/processing orders

            // Fetch updated fields
            $updatedFields = $this->getUpdatedFields($updateData, $productId); // See if anything has changed..

            // Support for notifications sent by Mageants OutofStockNotification extension
            if (isset($updatedFields['is_in_stock']) && $updatedFields['is_in_stock']) {
                if ($this->utilsHelper->isExtensionInstalled('Mageants_OutofStockNotification')) {
                    $stockHelper = $this->objectManager->get('\Mageants\OutofStockNotification\Helper\Data');
                    if ($stockHelper->isEnable()) {
                        $stockHelper->sendNotifications($stockHelper->getStockNotifyCustomer(\Magento\Store\Model\Store::DEFAULT_STORE_ID), $this->productIdToSku[$productId]);
                    }
                }
            }

            if (self::$importStock) {
                // Check is supported product type
                $productType = $this->productTypeMap[$productId];
                if ($productType !== \Magento\Downloadable\Model\Product\Type::TYPE_DOWNLOADABLE) {
                    if (!isset($updateData['source_code']) || empty($updateData['source_code']) || $updateData['source_code'] == 'default') {
                        // Only update for source_code=default (MSI) or when no source_code is set
                        // stock_item routine:
                        if (isset($this->stockItems[$updateData['stock_id']][$productId])) {
                            // This is a known stock_item entry
                            if (!empty($updatedFields)) {
                                // Something has changed, update stock item
                                $importResult = $this->updateStockItem($productId, $productIdentifier, $updatedFields, $updateData);
                                // Push product ID to modified stock items.
                                array_push($this->modifiedStockItems, $productId);
                            } else {
                                // Nothing has changed
                                if ($this->getTestMode()) {
                                    #return $importResult;
                                }
                            }
                        } else {
                            // New stock item, insert stock item
                            $importResult = $this->insertStockItem($productId, $productIdentifier, $updateData);
                            // Push product ID to modified stock items.
                            array_push($this->modifiedStockItems, $productId);
                        }
                    }

                    if (!$this->getTestMode()) {
                        if (!isset($updateData['source_code']) || empty($updateData['source_code']) || $updateData['source_code'] == 'default') {
                            // Only update for source_code=default (MSI) or when no source_code is set
                            // stock_status routine, update only when not in test_mode
                            if (isset($this->stockStatusItems[$updateData['stock_id']][$productId])) {
                                // This is a known stock_status entry
                                if (!empty($updatedFields)) {
                                    // Something has changed, update stock_status
                                    $this->updateStockStatus($productId, $updatedFields, $updateData);
                                    // Push product ID to modified stock items.
                                    if (!isset($this->modifiedStockItems[$productId])) {
                                        array_push($this->modifiedStockItems, $productId);
                                    }
                                }
                            } else {
                                // New stock item, insert stock item
                                $this->insertStockStatus($productId, $updateData);
                                // Push product ID to modified stock items.
                                if (!isset($this->modifiedStockItems[$productId])) {
                                    array_push($this->modifiedStockItems, $productId);
                                }
                            }
                        }

                        if ($this->entityHelper->getMagentoMSISupport()) {
                            // MSI stock item routine, update MSI tables
                            $productSku = $this->productIdToSku[$productId];
                            $updatedMsiFields = $this->getUpdatedMsiFields($updateData, $productSku);
                            if (isset($this->msiItems[$updateData['source_code']][$productSku])) {
                                // This is a known MSI entry
                                if (!empty($updatedMsiFields)) {
                                    // Something has changed, update stock_status
                                    $this->updateMsiItem($productSku, $updatedMsiFields, $updateData);
                                    if ($importResult['changed'] === false) {
                                        $tempUpdatedFields = $updatedMsiFields;
                                        array_walk($tempUpdatedFields, function(&$i, $k) {
                                            $i = " \"$k\"=\"$i\"";
                                        });
                                        $importResult = ['changed' => true, 'debug' => __("Product '%1' (MSI Item) stock updated in Magento: %2.", $productSku, implode("", $tempUpdatedFields))];
                                    }
                                    // Push product ID to modified stock items.
                                    if (!isset($this->modifiedMsiItems[$productId])) {
                                        array_push($this->modifiedMsiItems, $productId);
                                    }
                                    if (!isset($this->modifiedStockItems[$productId])) {
                                        array_push($this->modifiedStockItems, $productId);
                                    }
                                }
                            } else {
                                // New MSI item, insert stock item
                                $this->insertMsiitem($productSku, $updateData);
                                // Push product ID to modified stock items.
                                if (!isset($this->modifiedMsiItems[$productId])) {
                                    array_push($this->modifiedMsiItems, $productId);
                                }
                                if (!isset($this->modifiedStockItems[$productId])) {
                                    array_push($this->modifiedStockItems, $productId);
                                }
                                if ($importResult['changed'] === false) {
                                    $tempUpdatedFields = $updatedMsiFields;
                                    array_walk($tempUpdatedFields, function(&$i, $k) {
                                        $i = " \"$k\"=\"$i\"";
                                    });
                                    $importResult = ['changed' => true, 'debug' => __("Product '%1' (MSI Item) stock created in Magento: %2.", $productSku, implode("", $tempUpdatedFields))];
                                }
                            }
                        }
                    }
                }
            }

            $productFieldsUpdated = [];
            
            // Which stores should be updated
            $priceUpdateStoreIds = [\Magento\Store\Model\Store::DEFAULT_STORE_ID];
            $configUpdateStoreId = $this->getConfig('price_update_store_id');
            if (is_array($configUpdateStoreId)) {
                foreach ($configUpdateStoreId as &$storeId) {
                    if ($storeId === '') {
                        $storeId = \Magento\Store\Model\Store::DEFAULT_STORE_ID;
                    }
                }
            }
            if ($configUpdateStoreId !== '' && !empty($configUpdateStoreId)) {
                $priceUpdateStoreIds = $configUpdateStoreId;
            }
            $attributeUpdateStoreIds = $priceUpdateStoreIds;
            if ($this->priceScope === 0) { // Global
                $priceUpdateStoreIds = [\Magento\Store\Model\Store::DEFAULT_STORE_ID];
            }

            // Tier price routine
            if (version_compare($this->utilsHelper->getMagentoVersion(), '2.2', '>=')) {
                $tierPriceUpdated = false;
                // Very first loop - get all tier prices to add/update
                $tierPrices = [];
                foreach ($updateData as $field => $newValue) {
                    $customerGroupId = preg_replace('/^tier_price\:/', '', $field, -1, $isTierPriceField);
                    if (!$isTierPriceField) {
                        continue;
                    }
                    if ($customerGroupId === 'all') {
                        $customerGroupId = Group::CUST_GROUP_ALL;
                    }
                    $tierPrices[$customerGroupId] = $newValue;
                }
                // Loop through fields and see if any tier prices are included
                if (!empty($tierPrices)) {
                    $tierPriceManagement = $this->objectManager->get('\Magento\Catalog\Api\ScopedProductTierPriceManagementInterface');
                    $tierPriceFactory = $this->objectManager->get('\Magento\Catalog\Api\Data\ProductTierPriceInterfaceFactory');
                    $tierPriceExtensionFactory = $this->objectManager->get('\Magento\Catalog\Api\Data\ProductTierPriceExtensionFactory');
                    $priceModifier = $this->objectManager->get('\Magento\Catalog\Model\Product\PriceModifier');
                    $productSku = $this->productIdToSku[$productId];
                    // Remove tier prices
                    //$product = $this->productRepository->getById($productId);
                    //$product->setData('tier_price', []);
                    //$this->productRepository->save($product); // Required to save tier price unfortunately
                    $product = $this->productRepository->get($productSku, ['edit_mode' => true], 0); // Store ID 0 required to get "all" tier prices
                    $hasChanged = false;
                    $tierPricesFromProduct = $product->getData('tier_price');
                    if (is_array($tierPricesFromProduct)) {
                        foreach ($tierPricesFromProduct as $price) {
                            if (isset($tierPrices[$price['cust_group']])) {
                                $priceModifier->removeTierPrice(
                                    $product,
                                    $price['cust_group'],
                                    $price['price_qty'],
                                    $price['website_id']
                                );
                                $hasChanged = true;
                            }
                        }
                    }
                    if ($hasChanged) {
                        $this->productRepository->save($product); // Required to save tier price unfortunately
                    }
                }
                foreach ($updateData as $field => $newValue) {
                    $customerGroupId = preg_replace('/^tier_price\:/', '', $field, -1, $isTierPriceField);
                    if ($customerGroupId === 'all') {
                        $customerGroupId = Group::CUST_GROUP_ALL;
                    }
                    if ($isTierPriceField) {
                        if (stristr($newValue, '__EMPTY__') === false) {
                            $websitesToUpdate = [];
                            foreach ($priceUpdateStoreIds as $updateStoreId) {
                                $websiteId = $this->storeManager->getStore($updateStoreId)->getWebsiteId();
                                $websitesToUpdate[$websiteId] = ['website_id' => $websiteId, 'store_id' => $updateStoreId];
                            }
                            foreach ($websitesToUpdate as $website) {
                                $parsedTierPrices = explode(';', $newValue);
                                foreach ($parsedTierPrices as $parsedTierPrice) {
                                    $tierPriceDetail = explode(':', $parsedTierPrice);
                                    if (!isset($tierPriceDetail[1])) {
                                        continue;
                                    }
                                    $tierQty = floatval($tierPriceDetail[0]);
                                    $tierValue = floatval($tierPriceDetail[1]);
                                    /** @var \Magento\Catalog\Api\Data\ProductTierPriceInterface $tierPrice */
                                    $tierPrice = $tierPriceFactory->create()->setExtensionAttributes($tierPriceExtensionFactory->create());
                                    $tierPrice->setCustomerGroupId($customerGroupId);
                                    $tierPrice->setQty($tierQty);
                                    $tierPrice->setValue($tierValue);
                                    if (strstr($tierPriceDetail[1], '%') !== false) {
                                        $tierPrice->getExtensionAttributes()->setPercentageValue($tierValue);
                                    }
                                    $tierPrice->getExtensionAttributes()->setWebsiteId($website['website_id']);
                                    $tierPriceManagement->add($productSku, $tierPrice);
                                }
                                $productFieldsUpdated[$website['store_id']][$field] = $newValue;
                            }
                        }
                        $tierPriceUpdated = true;
                    }
                }
                if ($tierPriceUpdated) {
                    array_push($this->updatedTierPrices, $productId);
                }
            }

            // price update routine
            if (self::$importPrices) {
                if (!empty($updateData) && isset($updateData['price'])) {
                    if ($updateData['price'] == "__EMPTY__") {
                        $newPrice = null;
                    } else {
                        $newPrice = sprintf('%.4f', $updateData['price']);
                    }
                    foreach ($priceUpdateStoreIds as $updateStoreId) {
                        if (isset($this->prices[$updateStoreId]) && isset($this->prices[$updateStoreId][$productId])) {
                            $currentPrice = $this->prices[$updateStoreId][$productId]['price'];
                            if ($currentPrice !== $newPrice || $newPrice == '') {
                                if (!$this->getTestMode()) {
                                    $this->productAction->updateAttributes([$productId], ['price' => $newPrice], $updateStoreId);
                                }
                                // Price has changed.
                                array_push($this->updatedPrices, $productId);
                                $productFieldsUpdated[$updateStoreId]['price'] = $currentPrice . ' => ' . $newPrice;
                            }
                        } else {
                            if (!$this->getTestMode()) {
                                $this->productAction->updateAttributes([$productId], ['price' => $newPrice], $updateStoreId);
                            }
                            // Price has changed.
                            array_push($this->updatedPrices, $productId);
                            $productFieldsUpdated[$updateStoreId]['price'] = 'null => ' . $newPrice;
                        }
                    }
                }
            }

            if (self::$importSpecialPrices && !$this->getTestMode()) {
                if (!empty($updateData) && isset($updateData['special_price'])) {
                    foreach ($priceUpdateStoreIds as $updateStoreId) {
                        if (isset($this->specialPrices[$updateStoreId]) && isset($this->specialPrices[$updateStoreId][$productId])) {
                            $currentPrice = $this->specialPrices[$updateStoreId][$productId]['special_price'];
                        } else {
                            $currentPrice = null;
                        }
                        if ($updateData['special_price'] != '') {
                            $newPrice = sprintf('%.4f', $updateData['special_price']);
                        } else {
                            $newPrice = '';
                        }
                        $fromDate = date('Y-m-d');
                        if ($newPrice === "0.0000") {
                            $newPrice = "";
                            $fromDate = "";
                        }
                        if ($currentPrice !== $newPrice || $newPrice == '') {
                            $this->productAction->updateAttributes([$productId], ['special_price' => $newPrice], $updateStoreId);
                            $this->productAction->updateAttributes([$productId], ['special_from_date' => $fromDate], $updateStoreId);
                            // Special price has changed.
                            array_push($this->updatedSpecialPrices, $productId);
                            $productFieldsUpdated[$updateStoreId]['special_price'] = $currentPrice . ' => ' . $newPrice;
                            $productFieldsUpdated[$updateStoreId]['special_from_date'] = $fromDate;
                        }
                    }
                } else {
                    foreach ($priceUpdateStoreIds as $updateStoreId) {
                        if (isset($this->specialPrices[$updateStoreId]) && isset($this->specialPrices[$updateStoreId][$productId])) {
                            if (!empty($this->specialPrices[$updateStoreId][$productId]['special_price'])) {
                                $this->productAction->updateAttributes([$productId], ['special_price' => ''], $updateStoreId);
                                $this->productAction->updateAttributes([$productId], ['special_from_date' => ''], $updateStoreId);
                                array_push($this->updatedSpecialPrices, $productId);
                                $productFieldsUpdated[$updateStoreId]['special_price'] = '';
                                $productFieldsUpdated[$updateStoreId]['special_from_date'] = '';
                            }
                        }
                    }
                }
            }

            if (self::$importCost && !$this->getTestMode()) {
                if (!empty($updateData) && isset($updateData['cost'])) {
                    foreach ($priceUpdateStoreIds as $updateStoreId) {
                        if (isset($this->costValues[$updateStoreId]) && isset($this->costValues[$updateStoreId][$productId])) {
                            $currentPrice = $this->costValues[$updateStoreId][$productId]['cost'];
                        } else {
                            $currentPrice = null;
                        }
                        if ($updateData['cost'] != '') {
                            $newPrice = sprintf('%.4f', $updateData['cost']);
                        } else {
                            $newPrice = '';
                        }
                        if (stristr($updateData['cost'], '__EMPTY__') !== false) {
                            $newPrice = null;
                        }
                        if ($currentPrice !== $newPrice || $newPrice == '') {
                            $this->productAction->updateAttributes([$productId], ['cost' => $newPrice], $updateStoreId);
                            // Cost has changed.
                            array_push($this->updatedCostValues, $productId);
                            $productFieldsUpdated[$updateStoreId]['cost'] = $currentPrice . ' => ' . $newPrice;
                        }
                    }
                } else {
                    foreach ($priceUpdateStoreIds as $updateStoreId) {
                        if (isset($this->costValues[$updateStoreId]) && isset($this->costValues[$updateStoreId][$productId])) {
                            if (!empty($this->costValues[$updateStoreId][$productId]['cost'])) {
                                $this->productAction->updateAttributes([$productId], ['cost' => ''], $updateStoreId);
                                $productFieldsUpdated[$updateStoreId]['cost'] = '';
                            }
                        }
                    }
                }
            }

            if (self::$importProductStatus && !$this->getTestMode()) {
                if (!empty($updateData) && isset($updateData['status'])) {
                    foreach ($attributeUpdateStoreIds as $updateStoreId) {
                        if (isset($this->productStatus[$updateStoreId]) && isset($this->productStatus[$updateStoreId][$productId])) {
                            $currentStatus = $this->productStatus[$updateStoreId][$productId]['status'];
                        } else {
                            $currentStatus = null;
                        }
                        if ($updateData['status'] != '') {
                            $updateStatus = strtolower($updateData['status']);
                            $newStatus = null;
                            if ($updateStatus == 'yes' || $updateStatus == '1' || $updateStatus == 'enabled' || $updateStatus == 'ja' || $updateStatus == 'true') {
                                $newStatus = 1;
                            }
                            if ($updateStatus == 'no' || $updateStatus == '0' || $updateStatus == 'disabled' || $updateStatus == 'nein' || $updateStatus == 'false') {
                                $newStatus = 2;
                            }
                            if ($newStatus === null && $updateStatus !== '') {
                                throw new LocalizedException(__('An invalid value was imported for the product status column. It should contain values like "Enabled" or "Disabled".'));
                            }
                            if ($currentStatus != $newStatus) {
                                if ($newStatus === 1) {
                                    $this->productAction->updateAttributes([$productId], ['status' => ProductStatus::STATUS_ENABLED], $updateStoreId);
                                } else {
                                    $this->productAction->updateAttributes([$productId], ['status' => ProductStatus::STATUS_DISABLED], $updateStoreId);
                                }
                                $productFieldsUpdated[$updateStoreId]['status'] = $currentStatus . ' => ' . $newStatus;
                                array_push($this->updatedProductStatuses, $productId);
                            }
                        }
                    }
                }
            }

            if (self::$importCustomAttributes && !$this->getTestMode() && !empty($updateData)) {
                $attributesToUpdate = [];
                foreach ($updateData as $field => $newValue) {
                    if ($newValue === '') {
                        continue;
                    }
                    if (stristr($newValue, '__EMPTY__') !== false) {
                        $newValue = '';
                    }
                    $isCustomAttribute = false;
                    $attributeCode = preg_replace('/^cpa_/', '', $field, -1, $isCustomAttribute);
                    if ($isCustomAttribute) {
                        /** @var Attribute $productAttribute */
                        $productAttribute = $this->customProductAttributes[$attributeCode];
                        $tempStoreIds = $attributeUpdateStoreIds;
                        if ((int)$productAttribute->getIsGlobal() === \Magento\Eav\Model\Entity\Attribute\ScopedAttributeInterface::SCOPE_GLOBAL) {
                            $tempStoreIds = [\Magento\Store\Model\Store::DEFAULT_STORE_ID]; // Global attribute
                        }
                        foreach ($tempStoreIds as $updateStoreId) {
                            if (isset($this->customProductAttributeValues[$updateStoreId]) && isset($this->customProductAttributeValues[$updateStoreId][$attributeCode]) && isset($this->customProductAttributeValues[$updateStoreId][$attributeCode][$productId])) {
                                $currentValue = $this->customProductAttributeValues[$updateStoreId][$attributeCode][$productId];
                            } else {
                                $currentValue = null;
                            }
                            $originalValue = $newValue;
                            if ($productAttribute->getFrontendInput() == 'multiselect') {
                                $selectValues = $productAttribute->getSource()->toOptionArray();
                                $splitValues = explode(',', $originalValue);
                                if ($newValue !== '') {
                                    $newValue = [];
                                    foreach ($splitValues as $splitValue) {
                                        foreach ($selectValues as $selectValue) {
                                            if ($selectValue['value'] == $splitValue || $selectValue['label'] == $splitValue) {
                                                $newValue[] = $selectValue['value'];
                                                break 1;
                                            }
                                        }
                                    }
                                    $newValue = implode(',', $newValue);
                                }
                            } else if ($productAttribute->usesSource() && $productAttribute->getAttributeCode() !== 'country_of_manufacture') {
                                $currentValue = intval($currentValue);
                                $tempVal = $productAttribute->getSource()->getOptionId($newValue);
                                $newValue = intval(($tempVal !== null) ? $tempVal : $originalValue);
                            }
                            if ($productAttribute->getBackendType() == 'decimal') {
                                if ($newValue === '') {
                                    $newValue = null;
                                } else {
                                    $newValue = sprintf('%.4f', $newValue);
                                }
                            }
                            //var_dump($productAttribute->getSource()->toOptionArray(), $productAttribute->usesSource(), $attributeCode, $currentValue, $newValue);
                            if ($currentValue !== $newValue) {
                                $attributesToUpdate[$updateStoreId][$attributeCode] = $newValue;
                                array_push($this->updatedCustomProductAttributes, $productId);
                                if ($originalValue !== $newValue && $productAttribute->usesSource()) {
                                    $productFieldsUpdated[$updateStoreId][$attributeCode] = $currentValue . ' => ' . sprintf('%s [ID: %s]', $originalValue, print_r($newValue, true));
                                } else {
                                    $productFieldsUpdated[$updateStoreId][$attributeCode] = $currentValue . ' => ' . $newValue;
                                }
                            }
                        }
                    }
                }
                //var_dump($attributesToUpdate); die();
                if (!empty($attributesToUpdate)) {
                    foreach ($attributesToUpdate as $updateStoreId => $storeAttributes) {
                        $this->productAction->updateAttributes([$productId], $storeAttributes, $updateStoreId);
                    }
                }
            }

            if (is_array($productFieldsUpdated) && !empty($productFieldsUpdated)) {
                $tempProductFields = $productFieldsUpdated;
                array_walk($tempProductFields, function(&$attributes, $storeId) {
                    $origAttributes = $attributes;
                    $attributes = __('Store ID %1:', $storeId);
                    foreach ($origAttributes as $key => $value) {
                        $attributes .= " \"$key: $value\"";
                    }
                });
                // Add debug messages to $importResult
                if (isset($importResult['error']) || (isset($importResult['changed']) && $importResult['changed'] === false)) {
                    if ($this->getTestMode()) {
                        $importResult = ['changed' => true, 'debug' => __("Product '%1' would have been updated. Identified product ID is %2. Updated fields: %3", $productIdentifier, $productId, implode(" | ", $tempProductFields))];
                    } else {
                        $importResult = ['changed' => true, 'debug' => __("Product '%1' has been updated. Identified product ID is %2. Updated fields: %3", $productIdentifier, $productId, implode(" | ", $tempProductFields))];
                    }
                } else if (isset($importResult['changed']) && $importResult['changed'] === true) {
                    $importResult['debug'] .= implode("", $tempProductFields);
                }
            }
        } else {
            // Product not found.
            #$importResult = array('error' => __("Product '%1' could not be found in Magento. We tried to identify the product by using the attribute '%2'.", $productIdentifier, $this->attributeToLoadBy));
            $importResult = ['changed' => false];
        }

        if (isset($importResult['debug'])) {
            $importResult['debug'] = $importResult['debug'] . $msiNote;
        }

        return $importResult;
    }

    /*
     * See which fields changed and if necessary modify other fields based on fields - example qty <= 0 -> is_in_stock = false
     */
    protected function getUpdatedFields($updateData, $productId)
    {
        $updatedFields = [];
        $productType = $this->productTypeMap[$productId];

        /*if (empty($this->stockItems)) {
            return $updatedFields;
        }*/

        /*
         * First run: See which values changed, adjust values based on that and return fields to update.
         */
        if (!isset($this->stockItems[$updateData['stock_id']]) || !isset($this->stockItems[$updateData['stock_id']][$productId])) {
            // New stock item/status
            foreach ($updateData as $field => $newValue) {
                if ($field == 'price' || $field == 'special_price' || $field == 'cost' || $field == 'stock_id' || $field == 'status' || preg_match('/^cpa_/', $field)) { // Do not process these here.
                    continue;
                }
                if ($productType === Configurable::TYPE_CODE) {
                    // Only certain fields can be updated for configurable products. Filter others.
                    $allowedFields = ['is_in_stock', 'manage_stock', 'use_config_manage_stock', 'notify_stock_qty', 'backorders', 'min_qty', 'min_sale_qty', 'max_sale_qty'];
                    if (!in_array($field, $allowedFields)) {
                        continue;
                    }
                }
                // Type casting - everything coming from the database is a string (apparently, at least in my tests)
                if ($field == 'manage_stock' || $field == 'use_config_manage_stock' || $field == 'is_in_stock' || $field == 'backorders') {
                    $newValue = (int)$newValue;
                }
                if ($field == 'notify_stock_qty' || $field == 'min_qty' || $field == 'min_sale_qty' || $field == 'max_sale_qty') {
                    $newValue = (float)$newValue;
                }
                $updatedFields[$field] = $newValue;
            }
        } else {
            // Already existing
            $stockItem = $this->stockItems[$updateData['stock_id']][$productId];
            foreach ($updateData as $field => $newValue) {
                foreach ($stockItem as $stockField => $stockValue) {
                    if ($stockField == 'price' || $stockField == 'special_price' || $stockField == 'cost' || $stockField == 'stock_id' || $stockField == 'status' || preg_match('/^cpa_/', $field)) { // Do not process these here.
                        continue;
                    }
                    if ($productType === Configurable::TYPE_CODE) {
                        // Only certain fields can be updated for configurable products. Filter others.
                        $allowedFields = ['is_in_stock', 'manage_stock', 'use_config_manage_stock', 'notify_stock_qty', 'backorders', 'min_qty', 'min_sale_qty', 'max_sale_qty'];
                        if (!in_array($field, $allowedFields)) {
                            continue;
                        }
                    }
                    if ($field == $stockField) {
                        // Type casting - everything coming from the database is a string (apparently, at least in my tests)
                        if ($stockField == 'manage_stock' || $stockField == 'use_config_manage_stock' || $stockField == 'is_in_stock' || $stockField == 'backorders') {
                            $stockValue = (int)$stockValue; // Should be an integer coming from the database.
                            $newValue = (int)$newValue;
                        }
                        if ($stockField == 'notify_stock_qty' || $stockField == 'min_qty' || $stockField == 'min_sale_qty' || $stockField == 'max_sale_qty') {
                            $stockValue = (float)$stockValue;
                            $newValue = (float)$newValue;
                        }
                        // Field types
                        /*
                         $result[StockItemInterface::MANAGE_STOCK] = (int)$stockItem->getManageStock();
                         $result[StockItemInterface::QTY] = (float)$stockItem->getQty();
                         $result[StockItemInterface::MIN_QTY] = (float)$stockItem->getMinQty();
                         $result[StockItemInterface::MIN_SALE_QTY] = (float)$stockItem->getMinSaleQty();
                         $result[StockItemInterface::MAX_SALE_QTY] = (float)$stockItem->getMaxSaleQty();
                         $result[StockItemInterface::IS_QTY_DECIMAL] = (int)$stockItem->getIsQtyDecimal();
                         $result[StockItemInterface::IS_DECIMAL_DIVIDED]= (int)$stockItem->getIsDecimalDivided();
                         $result[StockItemInterface::BACKORDERS] = (int)$stockItem->getBackorders();
                         $result[StockItemInterface::NOTIFY_STOCK_QTY] = (float)$stockItem->getNotifyStockQty();
                         $result[StockItemInterface::ENABLE_QTY_INCREMENTS] = (int)$stockItem->getEnableQtyIncrements();
                         $result[StockItemInterface::QTY_INCREMENTS] = (float)$stockItem->getQtyIncrements();
                         $result[StockItemInterface::IS_IN_STOCK] = (int)$stockItem->getIsInStock();
                        */
                        // Preparing field values
                        if ($stockField == 'qty') {
                            if ($this->getConfigFlag('import_relative_stock_level')) {
                                // Check for relative updating
                                $tempValue = (string)$newValue;
                                if ($tempValue[0] == '+') {
                                    $newValue = $stockValue + substr($newValue, 1);
                                }
                                if ($tempValue[0] == '-') {
                                    $newValue = $stockValue - substr($newValue, 1);
                                }
                            }
                        }
                        if ($stockField == 'backorders' && $newValue !== '') {
                            if (isset($stockItem['use_config_backorders']) && $stockItem['use_config_backorders'] == 1) {
                                $updatedFields['use_config_backorders'] = 0;
                            }
                        }
                        // Compare and see if the value changed at all
                        if ($newValue !== $stockValue) {
                            if (trim($newValue) !== '') {
                                $updatedFields[$field] = $newValue;
                            }
                        }
                        // Uncomment this to *increment* stock levels by the imported QTY instead of replacing the stock level with the imported QTY.
                        /*
                        if ($stockField == 'qty') {
                            if ($stockValue <= 0) $stockValue = 0;
                            $updatedFields[$field] = $stockValue + $newValue;
                        }
                        */
                        break 1;
                    }
                }
            }
        }

        /*
         * Second run: See if we have to adjust values based on other field values.
         */
        foreach ($updatedFields as $field => $value) {
            if ($field == 'qty' && !isset($updateData['is_in_stock'])) {
                // Update is_in_stock field, only if not set in import file and if config flag mark_out_of_stock is set to yes
                if (!isset($stockItem) || (int)$stockItem['use_config_min_qty'] === 1) {
                    $outOfStockValue = (int)$this->scopeConfig->getValue('cataloginventory/item_options/min_qty');
                } else {
                    $outOfStockValue = $stockItem['min_qty'];
                }
                // Get "backorders allowed"
                if (!isset($stockItem) || (int)$stockItem['use_config_backorders'] === 1) {
                    $allowBackorders = (int)$this->scopeConfig->getValue('cataloginventory/item_options/backorders');
                } else {
                    $allowBackorders = $stockItem['backorders'];
                }
                /* $value = Stock level */
                if (!$allowBackorders && $this->getConfigFlag('mark_out_of_stock') && $value <= $outOfStockValue) {
                    $updatedFields['is_in_stock'] = 0;
                } else if ($this->getConfigFlag('mark_out_of_stock') && $value > 0) {
                    $updatedFields['is_in_stock'] = (int)($value > $outOfStockValue);
                }
                if ($allowBackorders && $this->getConfigFlag('mark_out_of_stock') && $value <= $outOfStockValue) {
                    // Debug message: Not setting to out of stock as its a backorderable item
                }
                #var_dump((int)$stockItem['use_config_min_qty'], $outOfStockValue, $updatedFields['is_in_stock']); die();
            }
            if ($field == 'backorders' && isset($updateData['backorders']) && $updateData['backorders'] !== '') {
                $updatedFields['use_config_backorders'] = 0;
            }
            if ($field == 'min_qty' && isset($updateData['min_qty']) && $updateData['min_qty'] !== '') {
                $updatedFields['use_config_min_qty'] = 0;
            }
            if ($field == 'min_sale_qty' && isset($updateData['min_sale_qty']) && $updateData['min_sale_qty'] !== '') {
                $updatedFields['use_config_min_sale_qty'] = 0;
            }
            if ($field == 'max_sale_qty' && isset($updateData['max_sale_qty']) && $updateData['max_sale_qty'] !== '') {
                $updatedFields['use_config_max_sale_qty'] = 0;
            }
        }

        return $updatedFields;
    }


    /*
     * See which fields in the MSI tables have changed
     */
    protected function getUpdatedMsiFields($updateData, $productSku)
    {
        $updatedFields = [];
        $productId = array_search($productSku, $this->productIdToSku);
        $productType = $this->productTypeMap[$productId];

        /*
         * First run: See which values changed, adjust values based on that and return fields to update.
         */
        if (!isset($this->msiItems[$updateData['source_code']]) || !isset($this->msiItems[$updateData['source_code']][$productSku])) {
            // New stock item
            foreach ($updateData as $field => $newValue) {
                if ($field == 'is_in_stock') {
                    $field = 'status'; // Called status in MSI tables
                    $newValue = (int)$newValue;
                    $updatedFields[$field] = $newValue;
                }
                if ($field == 'qty' && $productType !== Configurable::TYPE_CODE) { // Cannot update qty for configurable
                    $field = 'quantity'; // Called quantity in MSI tables
                    $newValue = (float)$newValue;
                    $updatedFields[$field] = $newValue;
                }
            }
        } else {
            // Already existing
            $msiItem = $this->msiItems[$updateData['source_code']][$productSku];
            foreach ($updateData as $field => $newValue) {
                $processField = false;
                if ($field == 'is_in_stock') {
                    $field = 'status'; // Called status in MSI tables
                    $processField = true;
                }
                if ($field == 'qty' && $productType !== Configurable::TYPE_CODE) { // Cannot update qty for configurable
                    $field = 'quantity'; // Called quantity in MSI tables
                    $processField = true;
                }
                if (!$processField) {
                    continue;
                }
                foreach ($msiItem as $stockField => $stockValue) {
                    if ($field == $stockField) {
                        // Type casting - everything coming from the database is a string (apparently, at least in my tests)
                        if ($stockField == 'status') {
                            $stockValue = (int)$stockValue; // Should be an integer coming from the database.
                        }
                        // Preparing field values
                        if ($stockField == 'quantity') {
                            if ($productType === Configurable::TYPE_CODE) { // Cannot update qty for configurable
                                continue;
                            }
                            if ($this->getConfigFlag('import_relative_stock_level')) {
                                // Check for relative updating
                                $tempValue = (string)$newValue;
                                if ($tempValue[0] == '+') {
                                    $newValue = $stockValue + substr($newValue, 1);
                                }
                                if ($tempValue[0] == '-') {
                                    $newValue = $stockValue - substr($newValue, 1);
                                }
                            }
                        }
                        // Compare and see if the value changed at all
                        if ($newValue !== $stockValue) {
                            if (trim($newValue) !== '') {
                                $updatedFields[$field] = $newValue;
                            }
                        }
                        // Uncomment this to *increment* stock levels by the imported QTY instead of replacing the stock level with the imported QTY.
                        /*
                        if ($stockField == 'qty') {
                            if ($stockValue <= 0) $stockValue = 0;
                            $updatedFields[$field] = $stockValue + $newValue;
                        }
                        */
                        break 1;
                    }
                }
            }
        }

        // Get stock item for SKU
        if ($productId !== false && isset($this->stockItems[1]) && isset($this->stockItems[1][$productId])) {
            $stockItem = $this->stockItems[1][$productId];
        }

        /*
         * Second run: See if we have to adjust values based on other field values.
         */
        foreach ($updatedFields as $field => $value) {
            if ($field == 'quantity' && !isset($updateData['status'])) {
                // Update is_in_stock field, only if not set in import file and if config flag mark_out_of_stock is set to yes
                if (!isset($stockItem) || (int)$stockItem['use_config_min_qty'] === 1) {
                    $outOfStockValue = (int)$this->scopeConfig->getValue('cataloginventory/item_options/min_qty');
                } else {
                    $outOfStockValue = $stockItem['min_qty'];
                }
                // Get "backorders allowed"
                if (!isset($stockItem) || (int)$stockItem['use_config_backorders'] === 1) {
                    $allowBackorders = (int)$this->scopeConfig->getValue('cataloginventory/item_options/backorders');
                } else {
                    $allowBackorders = $stockItem['backorders'];
                }
                /* $value = Stock level */
                if (!$allowBackorders && $this->getConfigFlag('mark_out_of_stock') && $value <= $outOfStockValue) {
                    $updatedFields['status'] = 0;
                } else if ($this->getConfigFlag('mark_out_of_stock') && $value > 0) {
                    $updatedFields['status'] = (int)($value > $outOfStockValue);
                }
                if ($allowBackorders && $this->getConfigFlag('mark_out_of_stock') && $value <= $outOfStockValue) {
                    // Debug message: Not setting to out of stock as its a backorderable item
                }
                #var_dump((int)$stockItem['use_config_min_qty'], $outOfStockValue, $updatedFields['is_in_stock']); die();
            }
        }

        return $updatedFields;
    }

    protected function insertStockItem($productId, $productIdentifier, $updateData)
    {
        // Some debugging information
        $tempUpdateData = $updateData;
        array_walk($tempUpdateData, function(&$i, $k) {
            $i = " \"$k\"=\"$i\"";
        });
        if ($this->getTestMode()) {
            $importResult = ['changed' => true, 'debug' => __("Product '%1' (New stock item) would have been imported into Magento. Identified product ID is %2. New fields: %3", $productIdentifier, $productId, implode("", $tempUpdateData))];
            return $importResult;
        }

        // Prepare the stock_item and insert it
        if (isset($updateData['qty'])) {
            if (!isset($updateData['is_in_stock'])) {
                // Update is_in_stock field, only if not set in import file and if config flag mark_out_of_stock is set to yes
                $outOfStockValue = (int)$this->scopeConfig->getValue('cataloginventory/item_options/min_qty');
                if ($this->getConfigFlag('mark_out_of_stock') && $updateData['qty'] <= $outOfStockValue) {
                    $updateData['is_in_stock'] = 0;
                } else if ($this->getConfigFlag('mark_out_of_stock') && $updateData['qty'] > 0) {
                    $updateData['is_in_stock'] = (int)($updateData['qty'] > $outOfStockValue);
                }
            }
        }
        #$updateData['stock_id'] = 1;
        $updateData['product_id'] = $productId;

        $updatedFields = $updateData;
        foreach ($updatedFields as $field => $value) {
            if ($field != 'is_in_stock' && $field != 'qty' && $field != 'stock_id' && $field != 'product_id') { // Do not process these here.
                unset($updatedFields[$field]);
            }
        }
        $this->writeAdapter->insert($this->getTableName('cataloginventory_stock_item'), $updatedFields);

        // Import result
        $importResult = ['changed' => true, 'debug' => __("Product '%1' (New stock item) has been imported into Magento. Identified product ID is %2. New fields: %3", $productIdentifier, $productId, implode("", $tempUpdateData))];
        return $importResult;
    }

    protected function insertStockStatus($productId, $updatedFields)
    {
        // Entry in stock_status does not exist yet, insert it
        #$updateData['stock_id'] = 1;
        $updateData['product_id'] = $productId;
        if (isset($updatedFields['is_in_stock'])) {
            $updateData['stock_status'] = $updatedFields['is_in_stock'];
        }
        if (isset($updatedFields['qty'])) {
            if (!isset($updateData['stock_status'])) {
                // Update is_in_stock field, only if not set in import file and if config flag mark_out_of_stock is set to yes
                $outOfStockValue = (int)$this->scopeConfig->getValue('cataloginventory/item_options/min_qty');
                if ($this->getConfigFlag('mark_out_of_stock') && $updatedFields['qty'] <= $outOfStockValue) {
                    $updateData['stock_status'] = 0;
                } else if ($this->getConfigFlag('mark_out_of_stock') && $updatedFields['qty'] > 0) {
                    $updateData['stock_status'] = (int)($updatedFields['qty'] > $outOfStockValue);
                }
            }
            $updateData['qty'] = $updatedFields['qty'];
        }
        if (isset($updatedFields['stock_id'])) {
            $updateData['stock_id'] = $updatedFields['stock_id'];
        }

        //foreach ($this->storeManager->getWebsites() as $website) {
        $updateData['website_id'] = 0; // StockConfigurationInterface -> getDefaultScopeId()
        $this->writeAdapter->insert($this->getTableName('cataloginventory_stock_status'), $updateData);
        //}

        return $this;
    }

    protected function insertMsiItem($productSku, $updatedFields)
    {
        // Entry in MSI table does not exist yet, insert it
        $insertData = [
            'source_code' => $updatedFields['source_code'],
            'sku' => $productSku
        ];

        if (isset($updatedFields['is_in_stock'])) {
            $insertData['status'] = $updatedFields['is_in_stock'];
        }
        if (isset($updatedFields['qty'])) {
            if (!isset($insertData['status'])) {
                // Update is_in_stock field, only if not set in import file and if config flag mark_out_of_stock is set to yes
                $outOfStockValue = (int)$this->scopeConfig->getValue('cataloginventory/item_options/min_qty');
                if ($this->getConfigFlag('mark_out_of_stock') && $updatedFields['qty'] <= $outOfStockValue) {
                    $insertData['status'] = 0;
                } else if ($this->getConfigFlag('mark_out_of_stock') && $updatedFields['qty'] > 0) {
                    $insertData['status'] = (int)($updatedFields['qty'] > $outOfStockValue);
                }
            }
            $insertData['quantity'] = $updatedFields['qty'];
        }

        $this->writeAdapter->insert($this->getTableName('inventory_source_item'), $insertData);
        return $this;
    }

    protected function updateStockItem($productId, $productIdentifier, $updatedFields, $updateData)
    {
        // Some debugging information
        $tempUpdatedFields = $updatedFields;
        array_walk($tempUpdatedFields, function(&$i, $k) {
            $i = " \"$k\"=\"$i\"";
        });
        if ($this->getTestMode()) {
            // Don't touch the stock item. Just return some fancy debug information.
            $importResult = ['changed' => true, 'debug' => __("Product '%1' would have been imported into Magento. Identified product ID is %2. Updated fields: %3", $productIdentifier, $productId, implode("", $tempUpdatedFields))];
            return $importResult;
        }

        // Update stock_item
        $this->writeAdapter->update($this->getTableName('cataloginventory_stock_item'), $updatedFields, "product_id=$productId and stock_id=" . $updateData['stock_id']);

        // Import result
        $importResult = ['changed' => true, 'debug' => __("Product '%1' has been imported into Magento. Identified product ID is %2. Updated fields: %3", $productIdentifier, $productId, implode("", $tempUpdatedFields))];
        return $importResult;
    }

    protected function updateStockStatus($productId, $updatedFields, $updateData)
    {
        // Entry in stock_status already exists, update it
        $statusUpdate = [];
        if (isset($updatedFields['qty'])) {
            $statusUpdate['qty'] = $updatedFields['qty'];
        }
        if (isset($updatedFields['is_in_stock'])) {
            $statusUpdate['stock_status'] = $updatedFields['is_in_stock'];
        }

        // Update it only if something has changed which is interesting for the stock_status
        if (!empty($statusUpdate)) {
            $this->writeAdapter->update($this->getTableName('cataloginventory_stock_status'), $statusUpdate, "product_id=$productId and stock_id=" . $updateData['stock_id']); // . " and website_id=1");
        }

        return $this;
    }

    protected function updateMsiItem($productSku, $updatedFields, $updateData)
    {
        // Entry in MSI table already exists, update it
        $msiData = [];
        if (isset($updatedFields['quantity'])) {
            $msiData['quantity'] = $updatedFields['quantity'];
        }
        if (isset($updatedFields['status'])) {
            $msiData['status'] = $updatedFields['status'];
        }

        // Update MSI table
        $this->writeAdapter->update($this->getTableName('inventory_source_item'), $msiData, "sku=".$this->writeAdapter->quote($productSku)." and source_code=".$this->writeAdapter->quote($updateData['source_code'])."");

        return $this;
    }

    /*
     * After the import ran, currently the only thing done is committing the transaction and reindexing
     */
    public function afterRun()
    {
        // Commit the transaction, only if no product data is updated
        if (!self::$importPrices && !self::$importSpecialPrices && !self::$importCost && !self::$importProductStatus && !self::$importCustomAttributes) {
            $this->writeAdapter->commit();
        }
        #$this->writeAdapter->query('UNLOCK TABLES');

        // Reindex routine
        if ($this->getTestMode()) {
            $this->getLogEntry()->addDebugMessage(__('Test mode enabled. Not running any reindex action.'));
            return $this;
        }
        try {
            // MSI Reindex, if required
            if (!empty($this->modifiedMsiItems) && !$this->getConfigFlag('disable_msi_reindex')) {
                $this->getLogEntry()->addDebugMessage(__('Starting MSI reindex.'));
                $startTime = microtime(true);
                $indexer = $this->indexerRegistry->get('inventory');
                if (!$indexer->isWorking()) {
                    $indexer->reindexAll();
                }
                $this->getLogEntry()->addDebugMessage(__('MSI reindex completed in %1 seconds.', round(microtime(true) - $startTime)));
            }
            if (!empty($this->modifiedStockItems) || !empty($this->updatedCustomProductAttributes)) {
                if ($this->getConfigFlag('invalidate_fpc')) {
                    $flushCacheObserver = $this->objectManager->create('\Magento\PageCache\Observer\FlushCacheByTags');
                    //$flushVarnishObserver = $this->objectManager->create('\Magento\CacheInvalidate\Observer\InvalidateVarnishObserver');
                    // Full page cache invalidation - not possible without loading the collection unfortunately
                    $productIds = array_unique(array_merge($this->modifiedStockItems, $this->updatedCustomProductAttributes));
                    // Parent products
                    $select = $this->readAdapter->select()
                        ->from(['l' => $this->getTableName('catalog_product_super_link')], [])
                        ->join(
                            ['e' => $this->getTableName('catalog_product_entity')],
                            'e.' . $this->objectManager->get('Magento\ConfigurableProduct\Model\ResourceModel\Attribute\OptionProvider')->getProductEntityLinkField() . ' = l.parent_id',
                            ['e.entity_id']
                        )->where('l.product_id IN(?)', $productIds)->group('e.entity_id');
                    $parentProductIds = $this->readAdapter->fetchCol($select);
                    foreach ($parentProductIds as $parentProductId) {
                        //\Magento\Framework\App\ObjectManager::getInstance()->get('Magento\Framework\App\Cache\Type\Collection')->clean(\Zend_Cache::CLEANING_MODE_MATCHING_TAG, ['configurable_' . $parentProductId]);
                        $this->cacheManager->clean('catalog_product_' . $parentProductId);
                        $this->cacheManager->clean('cat_p_' . $parentProductId);
                    }
                    //$productIds = array_unique(array_merge($productIds, $parentProductIds)); // Also load parent product IDs - NOT required, flush cache observer getTags gets them automatically
                    $productCollection = $this->objectManager->create('\Magento\Catalog\Model\ResourceModel\Product\Collection');
                    $productCollection->addFieldToFilter('entity_id', ['in' => $productIds]);
                    foreach ($productCollection as $product) {
                        $this->cacheManager->clean('catalog_product_' . $product->getId());
                        $this->cacheManager->clean('cat_p_' . $product->getId());
                        $product->cleanCache();
                        // Trigger FPC observer
                        $observer = new \Magento\Framework\Event\Observer(
                            [
                                'event' => new \Magento\Framework\DataObject(
                                    [
                                        'object' => $product
                                    ]
                                )
                            ]
                        );
                        $flushCacheObserver->execute($observer);
                        //$flushVarnishObserver->execute($observer);
                    }
                }
                if ($this->getConfigFlag('update_parent_product_stock_after_import')) {
                    // Update all configurable products to in stock/out of stock based on the qty of their child products
                    // Magento MSI Variant:  Update all configurable products to in stock/out of stock based on the qty of their child products
                    if ($this->entityHelper->getMagentoMSISupport()) {
                        $configurableProducts = $this->productFactory->create()->getCollection()
                            ->addAttributeToFilter('type_id', Configurable::TYPE_CODE);
                        foreach ($configurableProducts as $configurableProduct) {
                            $isInStock = false;
                            $childProducts = $this->configurableProduct->getUsedProducts($configurableProduct);
                            foreach ($childProducts as $childProduct) {
                                $childStockItem = $this->stockRegistry->getStockItem($childProduct->getId());
                                if (!$childStockItem->getUseConfigMinQty()) {
                                    $minQty = $childStockItem->getMinQty();
                                } else {
                                    $minQty = (int)$this->scopeConfig->getValue('cataloginventory/item_options/min_qty');
                                }
                                try {
                                    $stockInfos = $this->objectManager->get('Magento\InventorySalesAdminUi\Model\GetSalableQuantityDataBySku')->execute($childProduct->getSku());
                                    foreach ($stockInfos as $stockInfo) {
                                        if ($stockInfo['qty'] > $minQty) {
                                            $isInStock = true;
                                            break 2;
                                        }
                                    }
                                } catch (\Exception $e) {
                                }
                            }
                            $this->writeAdapter->update($this->getTableName('cataloginventory_stock_item'), ['is_in_stock' => (int)$isInStock], "product_id=" . $configurableProduct->getId());
                        }
                    } else {
                        $configurableProducts = $this->productFactory->create()->getCollection()
                            ->addAttributeToFilter('type_id', Configurable::TYPE_CODE);
                        foreach ($configurableProducts as $configurableProduct) {
                            $isInStock = false;
                            $childProducts = $this->configurableProduct->getUsedProducts($configurableProduct);
                            foreach ($childProducts as $childProduct) {
                                $childStockItem = $this->stockRegistry->getStockItem($childProduct->getId());
                                if ($childStockItem->getIsInStock()) {
                                    $isInStock = true;
                                    break 1;
                                }
                            }
                            $this->writeAdapter->update($this->getTableName('cataloginventory_stock_item'), ['is_in_stock' => (int)$isInStock], "product_id=" . $configurableProduct->getId());
                        }
                    }
                }
                // Get "configurable products" and update all associated child products to qty of parent item
                /*if (!empty($this->productTypeMap)) {
                    foreach ($this->productTypeMap as $parentProductId => $productType) {
                        if ($productType == Configurable::TYPE_CODE) {
                            $parentProduct = $this->productFactory->create()->load($parentProductId);
                            $parentStockItem = $this->stockRegistry->getStockItem($parentProductId);
                            if ($parentStockItem->getId()) {
                                $childProducts = $this->configurableProduct->getUsedProducts($parentProduct);
                                foreach ($childProducts as $childProduct) {
                                    $childStockItem = $this->stockRegistry->getStockItem($childProduct->getId());
                                    if ($parentStockItem->getQty() !== $childStockItem->getQty()) {
                                        $childStockItem->setQty($parentStockItem->getQty())->save();
                                    }
                                }
                            }
                        }
                    }
                }*/
                // Check if M2ePro is installed, and if yes, update stock there:
                if ($this->utilsHelper->isExtensionInstalled('Ess_M2ePro')) {
                    $model = $this->objectManager->create('\Ess\M2ePro\PublicServices\Product\SqlChange');
                    if ($model !== false) {
                        // Stock level
                        if (!empty($this->modifiedStockItems)) {
                            foreach ($this->modifiedStockItems as $productId) {
                                $model->markQtyWasChanged($productId);
                            }
                        }
                        // Price
                        $priceUpdateProductIds = array_unique(array_merge($this->updatedPrices, $this->updatedTierPrices, $this->updatedSpecialPrices));
                        if (!empty($priceUpdateProductIds)) {
                            foreach ($priceUpdateProductIds as $productId) {
                                $model->markPriceWasChanged($productId);
                            }
                        }
                        // Product Status
                        if (!empty($this->updatedProductStatuses)) {
                            foreach ($this->updatedProductStatuses as $productId) {
                                $model->markStatusWasChanged($productId);
                            }
                        }
                        $model->applyChanges();
                        $this->getLogEntry()->addDebugMessage(__('Notified M2ePro about updated products/stock levels.'));
                    }
                }
                // Webkul eBay Connector support, do not call $product->save but instead just call their observer (but unfortunately we need to load each product...)
                if ($this->utilsHelper->isExtensionInstalled('Webkul_Ebaymagentoconnect')) {
                    foreach ($this->modifiedStockItems as $productId) {
                        $observer = $this->objectManager->create('\Webkul\Ebaymagentoconnect\Observer\CatalogProductSaveAfter');
                        $product = $this->objectManager->create('\Magento\Catalog\Model\Product')->load($productId);
                        $observer->execute(new \Magento\Framework\Event\Observer(['product' => $product]));
                        unset($product);
                    }
                }
                // Reindex, if required
                if ($this->getConfig('reindex_mode') == 'full') {
                    // Reindex - required for sure if using MSI
                    $this->getLogEntry()->addDebugMessage(__('Running reindex.'));
                    $startTime = microtime(true);
                    $indexer = $this->indexerRegistry->get(\Magento\CatalogInventory\Model\Indexer\Stock\Processor::INDEXER_ID);
                    if (!$indexer->isWorking()) {
                        $indexer->reindexAll();
                    }
                    $this->getLogEntry()->addDebugMessage(__('Reindex completed in %1 seconds.', round(microtime(true) - $startTime)));
                } else if ($this->getConfig('reindex_mode') == 'flag_index') {
                    $this->getLogEntry()->addDebugMessage(__('Flagging stock index as reindex required.'));
                    // Flag as reindex required
                    $indexer = $this->indexerRegistry->get(\Magento\CatalogInventory\Model\Indexer\Stock\Processor::INDEXER_ID);
                    if (!$indexer->isWorking()) {
                        $indexer->getState()->setStatus(\Magento\Framework\Indexer\StateInterface::STATUS_INVALID);
                    }
                } else if ($this->getConfig('reindex_mode') == 'no_reindex') {
                    $this->getLogEntry()->addDebugMessage(__('Reindexing disabled. Not touching index at all.'));
                }
            } else {
                $this->getLogEntry()->addDebugMessage(__('No stock items modified. No reindex actions required.'));
            }

            // Refresh Magento Enterprise Edition Full Page Cache
            if ($this->getConfig('enterprise_fpc_action') == 'invalidate') {
                $this->getLogEntry()->addDebugMessage(__('Invalidating Magento Enterprise Full Page Cache.'));
                $this->cacheTypeList->invalidate('full_page');
            } else if ($this->getConfig('enterprise_fpc_action') == 'clean') {
                $this->getLogEntry()->addDebugMessage(__('Cleaning Magento Enterprise Full Page Cache.'));
                $this->pageCache->clean();
            }

            if ($this->getConfigFlag('update_low_stock_date')) {
                // Refresh "Low stock date"
                $this->resourceStock->updateLowStockDate(true);
            }

            // Reindex for price updates
            if (self::$importPrices || self::$importSpecialPrices || self::$importCustomAttributes) {
                if (!empty($this->updatedPrices) || !empty($this->updatedTierPrices) || !empty($this->updatedSpecialPrices) || !empty($this->updatedProductStatuses) || !empty($this->updatedCustomProductAttributes)) {
                    $this->getLogEntry()->addDebugMessage(__('Price/attribute update: Running reindex for price, product_flat and category_product.'));
                    $startTime = microtime(true);
                    $indexers = [
                        \Magento\Catalog\Model\Indexer\Product\Price\Processor::INDEXER_ID,
                        \Magento\Catalog\Model\Indexer\Product\Flat\Processor::INDEXER_ID,
                        \Magento\Catalog\Model\Indexer\Category\Product::INDEXER_ID
                    ];
                    foreach ($indexers as $indexerId) {
                        try {
                            $indexer = $this->indexerRegistry->get($indexerId);
                        } catch (\InvalidArgumentException $e) {
                            continue; // Flat indexer doesn't exist if disabled
                        }
                        if (!$indexer->isWorking()) {
                            try {
                                $indexer->reindexAll();
                            } catch (\Exception $e) {
                                if ($e->getMessage() != 'No linked stock found') { // MSI Error
                                    $this->getLogEntry()->addDebugMessage(__('Error while reindexing %1. Exception: %2', $indexerId, $e->getMessage()));
                                }
                            }
                        }
                    }
                    $this->getLogEntry()->addDebugMessage(
                        __(
                            'Price/attribute update: Full reindex completed in %1 seconds.',
                            round(microtime(true) - $startTime)
                        )
                    );
                }
            }
        } catch (\Exception $e) {
            $this->getLogEntry()->addDebugMessage(__('General error while reindexing. Exception: %1', $e->getMessage()));
        }

        $this->eventManager->dispatch('xtento_stockimport_stockupdate_after', [
            'profile' => $this->getProfile(),
            'log' => $this->getLogEntry(),
            'modified_stock_items' => $this->modifiedStockItems // Array containing the product IDs updated
        ]);

        // End of reindexing routine
        $this->getLogEntry()->addDebugMessage(__('Done: afterRun() (Reindexer functions, ...)'));
    }
}

Spamworldpro Mini