imagecms/ImageCMS

View on GitHub
application/modules/exchange/classes/Products.php

Summary

Maintainability
F
1 wk
Test Coverage
<?php

namespace exchange\classes;

use CI;
use CMSFactory\ModuleSettings;
use core\models\Route;
use core\models\RouteQuery;
use Exception;
use MediaManager\Image;
use MY_Controller;
use Propel\Runtime\ActiveQuery\Criteria;
use SimpleXMLElement;

/**
 *
 * @author kolia
 */
class Products extends ExchangeBase
{

    /**
     *
     * @var array
     */
    private $additionalParentsCategories = [];

    /**
     *
     * @var array
     */
    protected $compare_exIds = [];

    /**
     *
     * @var array
     */
    protected $compare_properties = [];

    /**
     *
     * @var array
     */
    protected $compare_urls = [];

    /**
     *
     * @var array
     */
    private $existingProductsIds = [];

    /**
     *
     * @var array
     */
    protected $i18nExisting = [];

    /**
     *
     * @var boolean
     */
    private $ignoreExistingDescriptions = false;

    /**
     *
     * @var
     */
    private $ignoreExistingProducts = false;

    /**
     * If product have images, then all
     * his old images need to be deleted
     * @var array
     */
    protected $imagesToDelete = [];

    /**
     * @var array
     */
    protected $imagesToResize = [];

    /**
     * @var array
     */
    protected $imagesToResizeAdditional = [];

    /**
     *
     * @var DataCollector
     */
    protected $insertCollector;

    /**
     * Array of products main categories
     * @var array
     */
    private $parentCat = [];

    /**
     *
     * @var array
     */
    protected $productProCats = [];

    /**
     * In the xml there can be two different products with same names, this array
     * gathers just created product urls to prevent equal addresses
     * @var array
     */
    protected $productsNewUrls = [];

    /**
     *
     * @var array
     */
    protected $productss;

    /**
     *
     * @var boolean
     */
    protected $runResize = FALSE;

    /**
     * If products have 'ХарактеристикиТовара' run fix
     * @var bool
     */
    protected $runVariantsFix = false;

    /**
     * Path of folder where xml and images stored
     * @var string
     */
    protected $tempDir;

    /**
     *
     * @var DataCollector
     */
    protected $updateCollector;

    /**
     *
     * @var VariantCharacteristics
     */
    private $variantCharacteristics;

    private $categoryUrls = [];

    protected function addProductsToUpperCategories() {

        $products = $this->db->select('shop_products.id, shop_category.full_path_ids')
            ->join('shop_category', 'shop_category.id = shop_products.category_id')
            ->get('shop_products')
            ->result();

        $insertData = [];
        foreach ($products as $product) {
            $path = unserialize($product->full_path_ids);
            foreach ($path as $fpi) {
                $newData = [
                            'category_id' => $fpi,
                            'product_id'  => $product->id,
                           ];
                if ($this->isProductCategoriesRowNew($newData) == FALSE) {
                    continue;
                }
                $insertData[] = $newData;
            }
        }
        $this->insertBatch('shop_product_categories', $insertData);
    }

    protected function getUrls() {

        $productExIds = [];
        foreach ($this->products as $product) {
            if (!empty($product['external_id'])) {
                $productExIds[$product['external_id']] = $product['id'];
            }
        }
        return $productExIds;
    }

    /**
     *
     */
    protected function import_() {

        $ignoreExisting = ModuleSettings::ofModule('exchange')->get('ignore_existing');
        $this->ignoreExistingProducts = isset($ignoreExisting['products']);
        $this->ignoreExistingDescriptions = isset($ignoreExisting['description']);

        $this->insertCollector = new DataCollector();
        $this->updateCollector = new DataCollector();

        $storageFilePath = CI::$APP->config->item('characteristicsStorageFilePath');
        $this->variantCharacteristics = new VariantCharacteristics($storageFilePath);

        $this->rememberOriginAdditionalParentsCats();

        $this->getCompareData();

        $this->processProducts1();
        $this->insert1();
        $this->processProducts23_Insert23();

        if (!$this->ignoreExistingProducts) {
            $this->update();
        }
        $this->rememberOriginAdditionalParentsCats();

        $this->rebuildAdditionalParentsCats();
        $this->runResize();
        //        $this->rebuildProductProperties();
    }

    /**
     *
     */
    protected function rememberOriginAdditionalParentsCats() {

        $result = $this->db
            ->select(['shop_products.id', 'shop_category.full_path_ids'])
            ->join('shop_category', 'shop_category.id=shop_products.category_id')
            ->get('shop_products');

        if (!$result) {
            return;
        }

        $result = $result->result_array();
        foreach ($result as $row) {
            $this->additionalParentsCategories[$row['id']] = unserialize($row['full_path_ids']);
        }
    }

    /**
     * Getting from base data of product for fast compare (external_ids, urls...)
     * @param integer $type if empty = all,
     *  1 - only external_ids,
     *  2 - only urls,
     *  3 - only properties
     */
    protected function getCompareData($type = NULL) {

        foreach ($this->products as $product) {
            // external ids of products
            if (!empty($product['external_id']) & ($type == NULL || (int) $type == 1)) {
                $this->compare_exIds[$product['external_id']] = $product['id'];
            }
            if ($type == NULL || (int) $type == 2) {
                $this->compare_urls[$product['url']] = $product['external_id'];
            }
        }
        if ($type == NULL || (int) $type == 3) {
            foreach ($this->properties as $property) {
                if (!empty($property['external_id'])) {
                    $this->compare_properties[$property['external_id']] = $property;
                }
            }
        }
    }

    /**
     * Gather data for tables:
     *  - `shop_products` (data will be ready)
     *  - `shop_products_i18n` (after `shop_products`)
     *  - `shop_product_variants` (after `shop_products`)
     *  - `shop_product_variants_i18n` (after `shop_product_variants`)
     *  - `shop_products_categories` (after `shop_products`)
     *  - `shop_product_images` (after `shop_products`)
     *
     *  P.S. For updating all data will be ready immediately, inserting need depends on the order
     *
     */
    protected function processProducts1() {

        $propertiesDataUpdate = [];
        $productPropDTDel = [];

        // for variants
        $productsUniqueExIds = [];

        // passing each product
        foreach ($this->importData as $product) {

            if (FALSE !== strpos((string) $product->Ид, '#')) { // this is variant of product
                $exId = array_shift(explode('#', (string) $product->Ид)); // getting product id
                $variantExId = (string) $product->Ид;
            } else {
                $exId = (string) $product->Ид;
                $variantExId = NULL;
            }

            // GETTING ALL DATA
            list($products, $i18n) = $this->pass1Helper_getProductData($product, $exId);

            $isNewProduct = !isset($this->compare_exIds[$products['external_id']]);
            $isNewVariant = $variantExId == null ? false : !isset($this->variantsIds[$variantExId]);

            list($variant, $variantI18n) = $this->pass1Helper_getVariantData($product, $isNewProduct);

            try {
                $categoryId = $this->pass1Helper_getCategoryData($product);
                $products['category_id'] = $categoryId;
            } catch (Exception $e) {
                continue;
            }

            list($additionalImages, $mainImage) = $this->pass1Helper_getImagesData($product, $exId);

            $this->imagesToResize[] = $mainImage;
            foreach ($additionalImages as $addImg) {
                $this->imagesToResizeAdditional[] = $addImg['image_name'];
            }

            if ($mainImage != NULL) {
                $variant['mainImage'] = $mainImage;
            } else {
                $variant['mainImage'] = '';
            }

            list($shopProductPropertiesData, $brandId) = $this->pass1Helper_getPropertiesData($product, $categoryId);

            if ($brandId) {
                $products['brand_id'] = $brandId;
            }

            $url = translit_url((string) $product->Наименование);

            // splitting on new & existing products (by external_id)
            if ($isNewProduct) { // NEW
                // default variants values (without `product_id`)
                if (!$variantExId == NULL) { // this is variant of product
                    $this->insertCollector->addData('shop_product_variants', $variant, $variantExId);
                    $this->insertCollector->addData('shop_product_variants_i18n', $variantI18n, $variantExId);
                    if (!isset($productsUniqueExIds[$exId])) {
                        $productsUniqueExIds[$exId] = 0;
                    } else {
                        $this->insertCollector->newPass();
                        $this->updateCollector->newPass();
                        continue;
                    }
                } else {
                    $this->insertCollector->addData('shop_product_variants', $variant, $exId);
                    $this->insertCollector->addData('shop_product_variants_i18n', $variantI18n, $exId);
                }

                if (isset($this->compare_urls[$url])) {
                    if ($this->compare_urls[$url] != $exId) { // add some number to url
                        $i = 1;
                        $url_ = $url;
                        while (array_key_exists($url, $this->compare_urls)) {
                            $url = $url_ . ++$i;
                        }
                    }
                }

                $products['url'] = $url;

                $this->compare_urls[$url] = $exId;

                $this->insertCollector->addData('shop_products', $products, $exId);
                $this->insertCollector->addData('shop_products_i18n', $i18n, $exId);

                $this->insertCollector->addData('shop_product_categories', ['category_id' => $products['category_id']], $exId);
                foreach ($shopProductPropertiesData as $propertyData) {
                    $this->insertCollector->updateData('shop_product_properties_data', $propertyData, $exId);
                }
                if ($additionalImages != NULL) {
                    $this->insertCollector->addData('shop_product_images', $additionalImages, $exId);
                }
            } else { // EXISTING
                $productId = $this->compare_exIds[$exId];
                $this->existingProductsIds[] = $productId;
                $variant = array_merge($variant, ['product_id' => $productId]);

                if (isset($this->variantImages[$exId]) & empty($variant['mainImage'])) {
                    $variant['mainImage'] = $this->variantImages[$exId];
                }

                // to not drop prices on update
                unset($variant['price_in_main']);
                unset($variant['price']);

                if (!$variantExId == NULL) { // this is variant of product
                    if ($isNewVariant) {
                        $this->insertCollector->addData('shop_product_variants', $variant, $variantExId);
                        $this->insertCollector->addData('shop_product_variants_i18n', $variantI18n, $variantExId);
                    } else {
                        $this->updateCollector->addData('shop_product_variants', $variant, $variantExId);
                        $this->updateCollector->addData('shop_product_variants_i18n', $variantI18n, $variantExId);
                    }

                    if (!isset($productsUniqueExIds[$exId])) {
                        $productsUniqueExIds[$exId] = 0;
                    } else {
                        $this->insertCollector->newPass();
                        $this->updateCollector->newPass();
                        continue;
                    }
                } else {
                    $this->updateCollector->addData('shop_product_variants', array_merge($variant, ['product_id' => $productId]));
                    $this->updateCollector->addData('shop_product_variants_i18n', $variantI18n, $exId);
                }

                if (isset($products['category_id'])) { // will be updated by product_id
                    $this->updateCollector->addData(
                        'shop_product_categories',
                        [
                         'product_id'  => $productId,
                         'category_id' => $products['category_id'],
                        ]
                    );
                }
                $this->updateCollector->addData('shop_products', array_merge($products, ['id' => $productId, 'url' => $url]));
                $this->updateCollector->addData('shop_products_i18n', array_merge($i18n, ['id' => $productId]));

                foreach ($shopProductPropertiesData as $propertyData) {
                    $propertiesDataUpdate[] = array_merge($propertyData, ['product_id' => $productId]);
                }
                $productPropDTDel[] = $productId;
                if ($additionalImages != NULL) {
                    $countAddImg = count($additionalImages);
                    for ($i = 0; $i < $countAddImg; $i++) {
                        $additionalImages[$i]['product_id'] = $productId;
                    }
                    $this->updateCollector->addData('shop_product_images', $additionalImages);
                }
            }

            $this->insertCollector->newPass();
            $this->updateCollector->newPass();
        }

        if (!$this->ignoreExistingProducts and $productPropDTDel) {
            $this->db
                ->where_in('product_id', $productPropDTDel)
                ->delete('shop_product_properties_data');

            $this->insertPropertiesData('shop_product_properties_data', $propertiesDataUpdate);
        }

        $this->variantCharacteristics->saveCharacteristics();
    }

    /**
     * Method-helper for simplify processProducts1 method
     * @param SimpleXMLElement $product
     * @param string $exId
     * @return array
     */
    protected function pass1Helper_getProductData(SimpleXMLElement $product, $exId) {

        $products = [
                     'external_id' => $exId,
                     'active'      => (string) $product->Статус == 'Удален' ? 0 : 1,
                    ];
        $i18n = [
                 'locale' => $this->locale,
                 'name'   => (string) $product->Наименование,
                ];

        $hasDescription = false;

        if (isset($product->Описание)) {
            $hasDescription = true;
            $description = (string) $product->Описание;
        } elseif (isset($product->ЗначенияРеквизитов->ЗначениеРеквизита->Наименование)) {
            foreach ($product->ЗначенияРеквизитов->ЗначениеРеквизита as $recVal) {
                if ((string) $recVal->Наименование == 'Полное наименование') {
                    $hasDescription = true;
                    $description = (string) $recVal->Значение;
                }
            }
        }

        if ($hasDescription) {
            $locale = MY_Controller::defaultLocale();
            $res = $this->db
                ->select('shop_products_i18n.short_description, shop_products_i18n.full_description')
                ->from('shop_products')
                ->where('external_id', $exId)
                ->where('shop_products_i18n.locale', $locale)
                ->join('shop_products_i18n', 'shop_products_i18n.id = shop_products.id')
                ->get();

            if ($res->num_rows() > 0) {
                $desc = $res->row_array();
                $shortDescIsEmpty = ('' == $desc['short_description']);
                $FullDescIsEmpty = ('' == $desc['full_description']);
            }
            if ($this->ignoreExistingDescriptions) {
                $shortDescIsEmpty && $i18n['short_description'] = $description;
                $FullDescIsEmpty && $i18n['full_description'] = $description;
            } else {
                $i18n['short_description'] = $description;
                $i18n['full_description'] = $description;
            }
        }

        return [
                $products,
                $i18n,
               ];
    }

    /**
     * Method-helper for simplify processProducts1 method
     * @param SimpleXMLElement $product
     * @param bool $isNew
     * @return array
     */
    protected function pass1Helper_getVariantData(SimpleXMLElement $product, $isNew = true) {

        $variant = [
                    'external_id' => (string) $product->Ид,
                    'number'      => (string) $product->Артикул,
                    'currency'    => $this->mainCurrencyId,
                   ];

        //$name = (string) $product->Наименование;
        $name = '';
        if (isset($product->ХарактеристикиТовара)) {
            foreach ($product->ХарактеристикиТовара->ХарактеристикаТовара as $value) {
                $chName = (string) $value->Наименование;
                $chValue = (string) $value->Значение;

                $name .= ' ' . $chValue;
                $this->variantCharacteristics->addCharacteristic($chName, $chValue);
            }
        }

        $variantI18n = [
                        'locale' => $this->locale,
                        'name'   => trim($name),
                       ];

        if ($isNew) {
            $defaultVariantsValues = [
                                      'price'         => '0.00000',
                                      'stock'         => 0,
                                      'position'      => 0,
                                      'price_in_main' => '0.00000',
                                     ];
        } else {
            $defaultVariantsValues = [];
        }

        return [
                array_merge($variant, $defaultVariantsValues),
                $variantI18n,
               ];
    }

    /**
     * Method-helper for simplify processProducts1 method
     * @param SimpleXMLElement $product
     * @return int
     * @throws Exception
     */
    protected function pass1Helper_getCategoryData(SimpleXMLElement $product) {

        $categoryId = NULL;
        if (isset($product->Группы)) {
            $categoryExId = (string) $product->Группы->Ид;
            $categoryId = Categories::getInstance()->categoryExists2($categoryExId, TRUE);
            if (!$categoryId) {
                throw new Exception(sprintf('Error! Product category with id [%s] not found in file', $categoryExId));
            }
        } else {
            throw new Exception(sprintf('Error! Product "%s" category not found in file', $product->Наименование));
        }

        return $categoryId;
    }

    /**
     * Method-helper for simplify processProducts1 method
     * @param SimpleXMLElement $product
     * @param string $exId
     * @return array
     */
    protected function pass1Helper_getImagesData(SimpleXMLElement $product, $exId) {

        $additionalImages = NULL;
        $mainImage = NULL;

        if (count($product->Картинка) > 1) {
            $this->imagesToDelete[$exId] = NULL;
        }
        $i = 0;
        foreach ((array) $product->Картинка as $image) {
            $path = (string) $image;
            $fileName = pathinfo($path, PATHINFO_BASENAME);
            if ($i == 0) { // main image
                if (file_exists($this->tempDir . $path) and !file_exists('./uploads/shop/products/origin/' . $fileName)) {
                    $copied = copy($this->tempDir . $path, './uploads/shop/products/origin/' . $fileName);
                    if ($copied != FALSE) {
                        $mainImage = $fileName;
                    }
                } elseif (file_exists('./uploads/shop/products/origin/' . $fileName)) {
                    $mainImage = $fileName;
                }
            } else { // rest of images will be an additional
                if (file_exists($this->tempDir . $path) and !file_exists('./uploads/shop/products/origin/additional/' . $fileName)) {
                    $copied = copy($this->tempDir . $path, './uploads/shop/products/origin/additional/' . $fileName);
                    if ($copied != FALSE) {
                        $additionalImages[] = [
                                               'position'   => $i - 1,
                                               'image_name' => $fileName,
                                              ];
                    }
                } elseif (file_exists('./uploads/shop/products/origin/additional/' . $fileName)) {
                    $additionalImages[] = [
                                           'position'   => $i - 1,
                                           'image_name' => $fileName,
                                          ];
                }
            }
            $i++;
        }

        return [
                $additionalImages,
                $mainImage,
               ];
    }

    /**
     * Method-helper for simplify processProducts1 method
     * @param SimpleXMLElement $product
     * @param int $categoryId
     * @return array
     */
    protected function pass1Helper_getPropertiesData(SimpleXMLElement $product, $categoryId) {

        $brandIdentif = Properties::getInstance()->getBrandIdentif();
        $brandId = '';

        $shopProductPropertiesData = [];
        // processing properties of product
        if (isset($product->ЗначенияСвойств)) {
            foreach ($product->ЗначенияСвойств->ЗначенияСвойства as $property) {
                $propertyValue = (string) $property->Значение;
                if (empty($propertyValue)) {
                    continue;
                }
                // check for "brand"
                $propertyExId = (string) $property->Ид;
                if ($propertyExId == $brandIdentif) {
                    $brandId = Properties::getInstance()->getBrandIdByExId($propertyValue) ?: Properties::getInstance()->getBrandIdByName($propertyValue);
                    continue;
                }

                if (!isset($this->compare_properties[$propertyExId])) {
                    continue;
                }

                $propertyId = $this->compare_properties[$propertyExId]['id'];

                // if property is multiple, then correting value
                if (Properties::getInstance()->dictionaryProperties[$propertyExId]) {
                    $propertyValue = Properties::getInstance()->dictionaryProperties[$propertyExId][$propertyValue];
                }

                $shopProductPropertiesData[] = [
                                                'property_id' => $propertyId,
                                                'value'       => $propertyValue,
                                                'locale'      => $this->locale,
                                               ];

                if ($categoryId != NULL) {
                    $newRow = TRUE;
                    foreach ($this->productProCats as $row) {
                        if ($row['property_id'] == $propertyId & $row['category_id'] == $categoryId) {
                            $newRow = FALSE;
                            break;
                        }
                    }
                    if ($newRow == TRUE) {
                        $this->productProCats[] = [
                                                   'property_id' => $propertyId,
                                                   'category_id' => $categoryId,
                                                  ]; // TODO: то тоже тре бде потім розібрати
                    }
                }
            }
        }

        return [
                $shopProductPropertiesData,
                $brandId,
               ];
    }

    // ------------------------------ HELPERS ------------------------------

    /**
     * Inserting data into `shop_products`
     */
    protected function insert1() {

        $this->insertProducts($this->insertCollector->getData('shop_products'));
        $this->dataLoad->getNewData('products');

        // properties-categories relations
        if (count($this->productProCats) > 0) {
            $epc = $this->db->get('shop_product_properties_categories')->result_array();
            foreach ($this->productProCats as $key => $newRowData) {
                $newRowIsReallyNew = TRUE;
                foreach ($epc as $rowData) {
                    if ($newRowData['property_id'] == $rowData['property_id'] & $newRowData['category_id'] == $rowData['category_id']) {
                        unset($this->productProCats[$key]);
                        $newRowIsReallyNew = FALSE;
                        break;
                    }
                }
                if ($newRowIsReallyNew == FALSE) {
                    continue;
                }
            }
            $this->insertBatch('shop_product_properties_categories', $this->productProCats);
        }
    }

    private function insertProducts($data) {
        if (FALSE == (count($data) > 0)) {
            return;
        }

        foreach ($data as $value) {
            $routeUrl = $value['url'];
            unset($value['url']);
            $value['created'] = time();
            $value['updated'] = time();
            $this->db->set($value)->insert('shop_products');

            $id = $this->db->insert_id();

            $category_id = $value['category_id'];
            if (!array_key_exists($category_id, $this->categoryUrls)) {
                $parentRoute = RouteQuery::create()->filterByType(Route::TYPE_SHOP_CATEGORY)
                    ->findOneByEntityId($category_id);
                $this->categoryUrls[$category_id] = $parentRoute->getFullUrl();
            }

            $parentUrl = $this->categoryUrls[$category_id];

            $route = new Route();
            $route->setType(Route::TYPE_PRODUCT)
                ->setParentUrl($parentUrl)
                ->setUrl($routeUrl)
                ->setEntityId($id)
                ->save();

            $this->db->update('shop_products', ['route_id' => $route->getId()], ['id' => $id]);

        }

        $error = $this->db->_error_message();

        if (!empty($error)) {
            throw new Exception('Error on inserting into `shop_products`: ' . $error);
        }
        // gathering statistics
        ExchangeBase::$stats[] = [
                                  'query type'    => 'insert',
                                  'table name'    => 'shop_products',
                                  'affected rows' => count($data),
                                 ];
    }

    /**
     * Filling with product_id tables that need it, and inserting into other tables
     * @throws \Exception
     */
    protected function processProducts23_Insert23() {

        // process 2 (adding product_id to tables data)
        $products = &$this->insertCollector->getData('shop_products');
        $productsI18n = &$this->insertCollector->getData('shop_products_i18n');
        $variants = &$this->insertCollector->getData('shop_product_variants');
        $propertiesData = &$this->insertCollector->getData('shop_product_properties_data');
        $images = &$this->insertCollector->getData('shop_product_images');
        $productCategories = &$this->insertCollector->getData('shop_product_categories');
        $imagesToDelete = [];
        $propertiesData_ = [];
        $imagesForInsert = [];

        foreach ($this->productIds as $externalId => $productId) {
            if (FALSE == isset($products[$externalId])) {
                continue;
            }
            $productsI18n[$externalId]['id'] = $productId;
            if (isset($propertiesData[$externalId])) {
                foreach ($propertiesData[$externalId] as $oneProductPropData) {
                    $propertiesData_[] = array_merge($oneProductPropData, ['product_id' => $productId]);
                }
            }
            if (isset($images[$externalId])) {
                $countImgs = count($images[$externalId]);
                for ($i = 0; $i < $countImgs; $i++) {
                    $images[$externalId][$i]['product_id'] = $productId;
                    $imagesForInsert[] = $images[$externalId][$i];
                }
            }
            if (isset($this->imagesToDelete[$externalId])) {
                $imagesToDelete[] = $productId;
            }
            if (isset($productCategories[$externalId])) {
                $productCategories[$externalId]['product_id'] = $productId;
                if (FALSE == $this->isProductCategoriesRowNew($productCategories[$externalId])) {
                    unset($productCategories[$externalId]);
                }
            }
        }
        $this->insertCollector->unsetData('shop_product_properties_data');
        // insert 2 (inserting those which where needed product_id)
        $this->insertBatch('shop_products_i18n', $productsI18n);

        foreach ($variants as $variantExId => $variantData) {
            $productExId = array_shift(explode('#', $variantExId));
            $variants[$variantExId]['product_id'] = $this->productIds[$productExId];
        }

        $this->insertBatch('shop_product_variants', $variants);

        // adding shop_product_properties_data and shop_product_properties_data_i18n is not so ordinary...
        if (count($propertiesData_) > 0) {

            $data = $this->prepareProductPropertiesData($propertiesData_);
            $this->insertBatch('shop_product_properties_data', $data);
        }

        // inserting shop_product_categories
        $this->insertBatch('shop_product_categories', $productCategories);

        //process3 (variants)
        $variantsI18n = &$this->insertCollector->getData('shop_product_variants_i18n');

        $variantsIds = $this->dataLoad->getNewData('variantsIds');
        foreach ($variantsIds as $externalId => $variantId) {
            if (FALSE == isset($variantsI18n[$externalId])) {
                continue;
            }
            $variantsI18n[$externalId]['id'] = $variantId;
        }

        // insert 3
        $this->insertBatch('shop_product_variants_i18n', $variantsI18n);

        // deleting additional images, inserting new
        if ($imagesToDelete) {
            $this->db->where_in('product_id', $imagesToDelete)->delete('shop_product_images');
        }
        $this->insertBatch('shop_product_images', $imagesForInsert);
    }

    /**
     *
     * @param array $newRowData
     * @return boolean
     */
    protected function isProductCategoriesRowNew(array $newRowData) {

        if (!isset($this->existingRows)) {
            $this->existingRows = $this->db->get('shop_product_categories')->result_array();
        }

        foreach ($this->existingRows as $existingRowData) {
            if ($existingRowData['product_id'] == $newRowData['product_id'] & $existingRowData['category_id'] == $newRowData['category_id']) {
                return FALSE;
            }
        }
        return TRUE;
    }

    /**
     * Update all
     * @throws \Exception
     */
    protected function update() {

        // update has only two "passes" - as we already got product_id
        $this->updateFromCollector('shop_products', 'id');
        $this->updateFromCollector('shop_product_categories', 'product_id');
        $this->updateFromCollector('shop_products_i18n', 'id');
        //$this->updateFromCollector('shop_product_properties_data', 'product_id');
        //        $this->updateFromCollector('shop_product_images', 'product_id');
        $this->updateFromCollector('shop_product_variants', 'external_id');

        // get variants ids, and update variants_18n
        $variantsI18n = &$this->updateCollector->getData('shop_product_variants_i18n');

        $this->dataLoad->getNewData('variantsIds');
        foreach ($variantsI18n as $exId => $variantI18n) {
            $variantsI18n[$exId]['id'] = $this->variantsIds[$exId];
        }

        $this->updateBatch('shop_product_variants_i18n', $variantsI18n, 'id');

        $productsIdsWithImages = [];
        $additionalImages = [];
        $additionalImages_ = &$this->updateCollector->getData('shop_product_images');
        $countAddImgs = count($additionalImages_);
        for ($i = 0; $i < $countAddImgs; $i++) {
            foreach ($additionalImages_[$i] as $addImgData) {
                $productsIdsWithImages[] = $addImgData['product_id'];
            }
            $additionalImages = array_merge($additionalImages, $additionalImages_[$i]);
        }
        if ($productsIdsWithImages) {
            $this->db
                ->where_in('product_id', array_unique($productsIdsWithImages))
                ->delete('shop_product_images');
        }

        $this->insertBatch('shop_product_images', $additionalImages);
    }

    /**
     * Helper-method for updating
     * @param string $tableName
     * @param string $compareField
     * @throws \Exception
     */
    protected function updateFromCollector($tableName, $compareField) {

        $this->updateBatch($tableName, $this->updateCollector->getData($tableName), $compareField);
    }

    // ----------------------- end of HELPERS ------------------------------

    /**
     * Set shop categories for products
     * @throws Exception
     */
    public function rebuildAdditionalParentsCats() {

        $ids = [];

        foreach ($this->products as $products) {
            $ids[] = $products['id'];
        }

        $products = $this->db
            ->select('shop_products.category_id, shop_products.id, shop_category.full_path_ids, shop_category.parent_id, shop_category.id as cat_id')
            ->join('shop_category', 'shop_category.id = shop_products.category_id')
            ->where_in('shop_products.id', $ids)
            ->get('shop_products')
            ->result();

        foreach ($products as $p) {
            $this->parentCat[$p->id] = $p->category_id;
        }

        if ($this->ignoreExistingProducts) {
            // deleting only "path categories" data (parents)
            foreach ($this->additionalParentsCategories as $productId => $parentCategoriesIds) {

                array_push($parentCategoriesIds, $this->parentCat[$productId]);
                if ($parentCategoriesIds and in_array($productId, $ids)) {
                    $this->db
                        ->where('product_id', $productId)
                        ->where_in('category_id', $parentCategoriesIds)
                        ->delete('shop_product_categories');
                }
            }
        } else {
            $this->db->where_in('product_id', $ids)->delete('shop_product_categories');
        }

        foreach ($products as $product) {
            $pathIds = unserialize($product->full_path_ids);

            array_push($pathIds, $product->cat_id);
            foreach ($pathIds as $categoryId) {
                $this->db->insert(
                    'shop_product_categories',
                    [
                     'category_id' => $categoryId,
                     'product_id'  => $product->id,
                    ]
                );
            }
        }
    }

    private function runResize() {

        if ($this->runResize) {
            Image::create()
                ->resizeByName($this->imagesToResize)
                ->resizeByNameAdditional(array_unique($this->imagesToResizeAdditional));
        }
    }

    protected function rebuildProductProperties() {

        $productTableProperties = $this->db->select('shop_product_properties_data.property_id, shop_product_property_value_i18n.value, shop_product_property_value_i18n.locale')
            ->from('shop_product_properties_data')
            ->join('shop_product_property_value', 'shop_product_properties_data.value_id = shop_product_property_value.id')
            ->join('shop_product_property_value_i18n', 'shop_product_property_value.id = shop_product_property_value_i18n.id')
            ->get()->result_array();

        $propertiesTableValues = $this->db->select('id, data, locale')->get('shop_product_properties_i18n')->result_array();

        //  array:
        //      [property_id][array property_locales][array property_values]

        $productPropertiesOrdered = [];

        foreach ($productTableProperties as $one) {
            if (!isset($productPropertiesOrdered[$one['property_id']])) {
                $productPropertiesOrdered[$one['property_id']] = [];
            }
            if (!isset($productPropertiesOrdered[$one['property_id']][$one['locale']])) {
                $productPropertiesOrdered[$one['property_id']][$one['locale']] = [];
            }
            if (!in_array($one['value'], $productPropertiesOrdered[$one['property_id']][$one['locale']], true)) {
                array_push($productPropertiesOrdered[$one['property_id']][$one['locale']], $one['value']);
            }
        }

        foreach ($propertiesTableValues as $one) {
            $id = $one['id'];
            $locale = $one['locale'];
            $insertData = [];
            $res = true;
            $error = false;

            if (array_key_exists($id, $productPropertiesOrdered) && array_key_exists($locale, $productPropertiesOrdered[$id])) {
                $onePropertyValues = unserialize($one['data']) ?: [];
                $insertData['data'] = serialize(array_unique(array_merge($onePropertyValues, $productPropertiesOrdered[$id][$locale])));
                $res = $res && $this->db->where('id', $id)->update('shop_product_properties_i18n', $insertData);
                $error = $this->db->_error_message() ?: $error;
            }
        }
    }

    /**
     * @param bool $run_resize
     * @return $this
     */
    public function setResize($run_resize = FALSE) {

        $this->runResize = $run_resize;
        return $this;
    }

    /**
     * Setting temporary folder with import data. Mantadory!
     * @param string $tempDir
     * @return $this
     */
    public function setTempDir($tempDir) {

        $this->tempDir = $tempDir;
        return $this;
    }

    private function getPropertyValueIdOrCreate($propertyId, $value, $locale) {
        $valueModel = \SPropertyValueQuery::create()
            ->joinWithI18n($locale, Criteria::INNER_JOIN)
            ->useI18nQuery($locale)
            ->filterByValue($value)
            ->endUse()
            ->filterByPropertyId($propertyId)
            ->findOne();

        if (!$valueModel) {
            $valueModel = new \SPropertyValue();
            $valueModel->setPropertyId($propertyId)
                ->setValue($value)
                ->setLocale($locale)
                ->save();

        }

        return $valueModel->getId();
    }

    private function prepareProductPropertiesData($propertiesData) {

        $data = [];
        foreach ($propertiesData as $key => $item) {

            $dataItem = [
                         'property_id' => $item['property_id'],
                         'product_id'  => $item['product_id'],
                         'value_id'    => $this->getPropertyValueIdOrCreate($item['property_id'], $item['value'], $item['locale']),

                        ];
            $data[] = $dataItem;
        }

        return $data;
    }

}