![]() 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/old/app/code/Cnc/Catalog/Console/ |
<?php /** * Copyright (c) 2020 Kaliop Digital Commerce (https://digitalcommerce.kaliop.com) All Rights Reserved. * https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0) * Cnc * Radosław Stępień <[email protected]> <[email protected]> */ namespace Cnc\Catalog\Console; use Cnc\Catalog\Model\Attribute\MsiAttributes; use Cnc\Catalog\Model\Config; use Cnc\Catalog\Model\Product\ImportFactory; use Cnc\Catalog\Model\ResourceModel\DataFactory as SourceDataFactory; use Cnc\Catalog\Setup\Patch\Data\UpdateCustomTablesAttributesData; use Exception; use Magento\Catalog\Api\AttributeSetRepositoryInterface; use Magento\Catalog\Api\ProductRepositoryInterface; use Magento\Catalog\Model\ProductFactory; use Magento\Catalog\Model\ResourceModel\Category\CollectionFactory as CategoryCollectionFactory; use Magento\Catalog\Model\ResourceModel\Product\CollectionFactory; use Magento\Eav\Api\Data\AttributeSetInterface; use Magento\Framework\Api\FilterBuilder; use Magento\Framework\Api\SearchCriteriaBuilder; use Magento\Framework\App\Area; use Magento\Framework\App\ResourceConnection; use Magento\Framework\App\State; use Magento\Framework\Exception\FileSystemException; use Magento\Framework\Exception\LocalizedException; use Magento\Framework\Filesystem\Driver\File; use Magento\Framework\Registry; use Magento\Framework\Setup\SampleData\FixtureManager; use Magento\Framework\Url\Validator as UrlValidator; use Psr\Log\LoggerInterface; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; class CreateProducts extends Command { const FILE_PART = 'part'; const CLEAR_CATALOG = 'clear_catalog'; const MULTIPLE_VALUES_SEPARATOR = ';;'; const NOT_VISIBLE_INDIVIDUALLY = 'not visible individually'; const CATALOG_SEARCH = 'catalog, search'; const FR_LANGUAGE_IMPORT_ID = 4; const EN_LANGUAGE_IMPORT_ID = 1; const FIXTURE_PRODUCTS_FR_FILE_PATH = 'Cnc_Catalog::fixtures/products.csv'; const FIXTURE_PRODUCTS_DESCRIPTION_FR_FILE_PATH = 'Cnc_Catalog::fixtures/products_description.csv'; const FIXTURE_ATTRIBUTE_SETS_FR_FILE_PATH = 'Cnc_Catalog::fixtures/type_products.csv'; const FIXTURE_ATTRIBUTE_SETS_RELATION_FR_FILE_PATH = 'Cnc_Catalog::fixtures/products_to_type_products.csv'; const FIXTURE_CATEGORIES_DESCRIPTION_FR_FILE_PATH = 'Cnc_Catalog::fixtures/categories_description.csv'; const FIXTURE_PRODUCTS_TO_CATEGORIES_FR_FILE_PATH = 'Cnc_Catalog::fixtures/products_to_categories.csv'; const FIXTURE_CROSSSELLING_FILE_PATH = 'Cnc_Catalog::fixtures/cross_selling.csv'; const PRODUCT_DESCRIPTION_FIELDS_MAPPING = [ 'name' => 'products_name', 'description' => 'products_description', 'grouped_url_key' => 'products_url', 'short_description' => 'products_small_desc', 'meta_title' => 'products_head_title_tag', 'meta_description' => 'products_head_desc_tag', 'meta_keywords' => 'products_head_keywords_tag' ]; /** * @var LoggerInterface */ private $logger; /** * @var File */ private $file; /** * @var FixtureManager */ private $fixtureManager; /** * @var Registry */ private $registry; /** * @var State */ protected $state; /** * @var CollectionFactory */ private $productCollectionFactory; /** * @var ProductRepositoryInterface */ private $productRepository; /** * @var ProductFactory */ private $productFactory; /** * @var AttributeSetRepositoryInterface */ private $attributeSetRepository; /** * @var FilterBuilder */ private $filterBuilder; /** * @var SearchCriteriaBuilder */ private $searchCriteriaBuilder; /** * @var SourceDataFactory */ private $sourceDataFactory; /** * @var ImportFactory */ private $importFactory; /** * @var ResourceConnection */ private $resource; /** * @var UrlValidator */ private $urlValidator; /** * @var CategoryCollectionFactory */ private $categoryCollectionFactory; /** * @var MsiAttributes */ private $msiAttributes; private $productModel; private $productData; private $preparedData = []; private $productDescriptionData; private $productToAttributeSetData; private $productsToCategoriesData; private $productsCategoriesPaths; private $crossSellsData; private $importPart; private $msiAttributesData = []; protected $errors = []; /** * CreateProducts constructor. * @param LoggerInterface $logger * @param File $file * @param FixtureManager $fixtureManager * @param Registry $registry * @param State $state * @param CollectionFactory $productCollectionFactory * @param ProductRepositoryInterface $productRepository * @param ProductFactory $productFactory * @param AttributeSetRepositoryInterface $attributeSetRepository * @param FilterBuilder $filterBuilder * @param SearchCriteriaBuilder $searchCriteriaBuilder * @param SourceDataFactory $sourceDataFactory * @param ImportFactory $importFactory * @param ResourceConnection $resource * @param UrlValidator $urlValidator * @param CategoryCollectionFactory $categoryCollectionFactory * @param MsiAttributes $msiAttributes * @param string|null $name */ public function __construct( LoggerInterface $logger, File $file, FixtureManager $fixtureManager, Registry $registry, State $state, CollectionFactory $productCollectionFactory, ProductRepositoryInterface $productRepository, ProductFactory $productFactory, AttributeSetRepositoryInterface $attributeSetRepository, FilterBuilder $filterBuilder, SearchCriteriaBuilder $searchCriteriaBuilder, SourceDataFactory $sourceDataFactory, ImportFactory $importFactory, ResourceConnection $resource, UrlValidator $urlValidator, CategoryCollectionFactory $categoryCollectionFactory, MsiAttributes $msiAttributes, string $name = null ) { parent::__construct($name); $this->logger = $logger; $this->file = $file; $this->fixtureManager = $fixtureManager; $this->registry = $registry; $this->state = $state; $this->productCollectionFactory = $productCollectionFactory; $this->productRepository = $productRepository; $this->productFactory = $productFactory; $this->attributeSetRepository = $attributeSetRepository; $this->filterBuilder = $filterBuilder; $this->searchCriteriaBuilder = $searchCriteriaBuilder; $this->sourceDataFactory = $sourceDataFactory; $this->importFactory = $importFactory; $this->resource = $resource; $this->urlValidator = $urlValidator; $this->categoryCollectionFactory = $categoryCollectionFactory; $this->msiAttributes = $msiAttributes; $this->productModel = $productFactory->create(); } /** * @inheritDoc */ protected function configure() { $this->setName('catalog:products:create'); $this->setDescription('Products migration'); $this->addOption( self::FILE_PART, null, InputOption::VALUE_OPTIONAL, 'Choose one of two parts of file to import' ); $this->addOption( self::CLEAR_CATALOG, null, InputOption::VALUE_OPTIONAL, 'Clear products entities' ); parent::configure(); } /** * @param InputInterface $input * @param OutputInterface $output * @return int|void|null */ protected function execute(InputInterface $input, OutputInterface $output) { $this->prepareMsiMapping(); try { $this->productToAttributeSetData = []; $this->importPart = $input->getOption(self::FILE_PART); $this->productData = $this->getDataFromFile(self::FIXTURE_PRODUCTS_FR_FILE_PATH); $this->productToAttributeSetData = $this->getDataFromFile(self::FIXTURE_ATTRIBUTE_SETS_RELATION_FR_FILE_PATH); $this->productsToCategoriesData = $this->getDataFromFile(self::FIXTURE_PRODUCTS_TO_CATEGORIES_FR_FILE_PATH); $this->getProductDescriptionFields(self::FIXTURE_PRODUCTS_DESCRIPTION_FR_FILE_PATH); $this->getCrossSellingData(self::FIXTURE_CROSSSELLING_FILE_PATH); $this->state->setAreaCode(Area::AREA_ADMINHTML); $output->writeln("Removing all products before import new ones..."); if ($input->getOption(self::CLEAR_CATALOG)) { //Clear products entities only when true parameter was passed $this->removeAllProducts(); } $output->writeln("Clean done!"); $output->writeln( "Preparing categories paths with names, to assign products to the right categories..." ); $this->prepareCategoriesPaths(); $output->writeln("Categories mapping done!"); $output->writeln("Preparing rest of products data mapping..."); $this->prepareData(); $output->writeln("Products data mapping done!"); $output->writeln("Begin products import..."); $this->toMagentoProducts(); $output->writeln("End Importing products!"); } catch (Exception $e) { $output->writeln($e->getMessage()); $this->logger->critical($e->getMessage()); } } /** * Clear table before importing new products */ public function removeAllProducts() { $connection = $this->resource->getConnection(); //Clear products table before importing new ones $connection->query("DELETE FROM catalog_product_entity;"); //Clear url rewrites table to avoid error with 'already defined url for current store' $connection->query("DELETE FROM url_rewrite WHERE entity_type = 'product';"); } /** * Data mapping to the structure required by magento importer * @throws FileSystemException * @throws LocalizedException */ public function prepareData() { $configuredGroupedSkus = []; $changedGroupedSkus = []; $groupedProducts = []; $importedUrlKeys = []; $connection = $this->resource->getConnection(); foreach ($this->productData as $key => $item) { if ($simpleSku = $this->getImportField('products_model', $item)) { $this->preparedData[$key]['id'] = $this->getImportField('products_id', $item) ?: 0; if ($this->preparedData[$key]['id'] == 0) { // Cannot import product without OSCommerce ID, it's needed to get related data from other files var_export("Product ID not found in import files (it's needed to find related data from other files), skipping!\r\n"); unset($this->preparedData[$key]); continue; } if ($this->importPart == 1) { if ($this->preparedData[$key]['id'] > 5000) { unset($this->preparedData[$key]); continue; } } elseif ($this->importPart == 2) { if ($this->preparedData[$key]['id'] <= 5000) { unset($this->preparedData[$key]); continue; } } //Prepare description fields for EN store view foreach (self::PRODUCT_DESCRIPTION_FIELDS_MAPPING as $magentoField => $importField) { $description = $this->getDescriptionData( $this->preparedData[$key]['id'], self::EN_LANGUAGE_IMPORT_ID, $magentoField ); $this->preparedData[$key][$magentoField] = $description; } if ($this->preparedData[$key]['name'] == false) { // Cannot import product without Name var_export("Name not found in import files for product ID: " . $this->preparedData[$key]['id'] . ", skipping!\r\n"); unset($this->preparedData[$key]); continue; } $this->preparedData[$key]['_product_websites'] = 'cnc_eu' . self::MULTIPLE_VALUES_SEPARATOR . 'cnc_gb' . self::MULTIPLE_VALUES_SEPARATOR . 'cnc_us'; //Commented, to check if nothing broken, becasue we do not use default stock, but SOURCES with MSI //$this->preparedData[$key]['qty'] = $this->getImportField('products_quantity', $item) ?: 0; $this->preparedData[$key]['sku'] = $simpleSku; $qty = $this->getImportField('products_quantity', $item) ?: 0; $connection->insert( $connection->getTableName('cnc_stock_import'), ['source_code' => 'cnc_fr', 'sku' => $this->preparedData[$key]['sku'], 'qty' => $qty], ); if ($this->preparedData[$key]['sku'] == false) { // Cannot import product without SKU var_export("SKU not found in import files for product ID: " . $this->preparedData[$key]['id'] . ", skipping!\r\n"); unset($this->preparedData[$key]); continue; } //Prepare unique sku for url key generation, because of problem with duplicated // when sku contains '-' at the end (magento truncate this char) if (substr($this->preparedData[$key]['sku'], -1) == '-') { $skuForUrlKey = $this->preparedData[$key]['sku'] . uniqid(); } else { $skuForUrlKey = $this->preparedData[$key]['sku']; } $this->preparedData[$key]['price'] = $this->getImportField('products_price', $item) ?: 0; $this->preparedData[$key]['product_type'] = 'simple'; $this->preparedData[$key]['image'] = $this->getImportField('products_image', $item) ?: 'empty.png'; $this->preparedData[$key]['created_at'] = $this->getImportField('products_date_added', $item); $this->preparedData[$key]['updated_at'] = $this->getImportField('products_last_modified', $item); $this->preparedData[$key]['status'] = $this->getImportField('products_status', $item); $this->preparedData[$key]['weight'] = $this->getImportField('products_weight', $item) ?: 0; $this->preparedData[$key]['visibility'] = self::CATALOG_SEARCH; $this->preparedData[$key]['tax_class_name'] = 'Biens et services soumis à la TVA à 20%'; $this->preparedData[$key]['merchant_center_category'] = $this->getImportField('products_google_merchant', $item); $this->preparedData[$key]['_attribute_set'] = $this->getAttributeSetForProduct( $this->preparedData[$key]['id'] )->getAttributeSetName(); $this->preparedData[$key]['url_key'] = $this->productModel->formatUrlKey( $this->preparedData[$key]['name'] . '-' . $skuForUrlKey . '-p-' . $this->preparedData[$key]['id'] ); //Get categories for import data for EN store if (array_key_exists($this->preparedData[$key]['id'], $this->productsCategoriesPaths)) { $this->preparedData[$key]['categories'] = $this->productsCategoriesPaths[$this->preparedData[$key]['id']]; } //Get categories for import data for FR store and rest of data for FR store if categories are set if (array_key_exists($this->preparedData[$key]['id'], $this->productsCategoriesPaths)) { //Prepare description fields for EN store view foreach (self::PRODUCT_DESCRIPTION_FIELDS_MAPPING as $magentoField => $importField) { $this->preparedData[$key . 'cnc_fr_eu'][$magentoField] = $this->getDescriptionData( $this->preparedData[$key]['id'], self::FR_LANGUAGE_IMPORT_ID, $magentoField ); } // Exit from FR store product creating when Name is not configured for it if ($this->preparedData[$key . 'cnc_fr_eu']['name'] != false) { $this->preparedData[$key . 'cnc_fr_eu']['sku'] = $this->preparedData[$key]['sku']; $this->preparedData[$key . 'cnc_fr_eu']['url_key'] = $this->productModel->formatUrlKey( $this->preparedData[$key . 'cnc_fr_eu']['name'] . '-' . $skuForUrlKey . '-p-' . $this->preparedData[$key]['id'] ); $this->preparedData[$key . 'cnc_fr_eu']['_product_websites'] = 'cnc_eu'; /** * Commented, to check if nothing broken, * becasue we do not use default stock, * but SOURCES with MSI */ // $this->preparedData[$key . 'cnc_fr_eu']['qty'] = $this->preparedData[$key]['qty']; $this->preparedData[$key . 'cnc_fr_eu']['price'] = $this->preparedData[$key]['price']; $this->preparedData[$key . 'cnc_fr_eu']['cnc_net_weight'] = 0; $this->preparedData[$key . 'cnc_fr_eu']['product_type'] = $this->preparedData[$key]['product_type']; $this->preparedData[$key . 'cnc_fr_eu']['_attribute_set'] = $this->preparedData[$key]['_attribute_set']; $this->preparedData[$key . 'cnc_fr_eu']['store_view_code'] = 'cnc_fr_eu'; $this->preparedData[$key . 'cnc_fr_eu']['_store'] = 'cnc_fr_eu'; $this->preparedData[$key . 'cnc_fr_eu']['categories'] = $this->productsCategoriesPaths[$this->preparedData[$key]['id']]; } else { // Cannot import product without Name unset($this->preparedData[$key . 'cnc_fr_eu']); } } if (isset($item['products_group'])) { // take already changed sku of grouped to avoid problems with different grouped products assignment if (array_key_exists($item['products_group'], $changedGroupedSkus)) { $item['products_group'] = $changedGroupedSkus[$item['products_group']]; } if ($item['products_model'] == $item['products_group']) { //Change sku of grouped product when it's the same as simple associated, to avoid errors $groupedProductSku = $item['products_group'] . '-g'; $changedGroupedSkus[$item['products_group']] = $groupedProductSku; $item['products_group'] = $groupedProductSku; } } $youtube = 0; //TODO: Currently native Magento Importer is not supporting Media video import //Populate cnc data on Simple Product $this->preparedData[$key]['cnc_manufacturer'] = $this->getCncAttribute('manufacturers_id', $item); $this->preparedData[$key]['cnc_number_ordered'] = $this->getImportField('products_ordered', $item); $this->preparedData[$key]['cnc_state_of_wear'] = $this->getCncAttribute('etats_id', $item); $this->preparedData[$key]['cnc_guarantee_duration'] = $this->getCncAttribute('garanties_id', $item); $this->preparedData[$key]['cnc_guarantee_type'] = $this->getCncAttribute('types_id', $item); $availabilityValueId = $this->getCncAttribute('disponibilites_id', $item); $connection->insertOnDuplicate( $connection->getTableName('msi_custom_attribute_value'), [ 'source_code' => 'cnc_fr', 'sku' => $this->preparedData[$key]['sku'], 'attribute_id' => $this->msiAttributes->getAttributeId('cnc_availability'), 'value' => (int) $availabilityValueId ], ); $this->preparedData[$key]['cnc_oscom_id'] = $this->getImportField('products_id', $item); $this->preparedData[$key]['cnc_net_weight'] = 0; $this->preparedData[$key]['cnc_salable_out_of_stock'] = $this->getImportField('products_achat_hors_stock_ok', $item); $this->preparedData[$key]['cnc_customs_code'] = $this->getImportField('products_code_douanier', $item) ?: 0; if ($manufacturerPriceDate = $this->getImportField('products_price_constate_date', $item)) { if ($manufacturerPriceDate != null && $manufacturerPriceDate !== 'NULL' && strtotime($manufacturerPriceDate) !== false) { $this->preparedData[$key]['cnc_manufacturer_price_date'] = $manufacturerPriceDate; } } if ($manufacturerPrice = $this->getImportField('products_price_constate', $item)) { if ($manufacturerPrice != null && $manufacturerPrice !== 'NULL') { $this->preparedData[$key]['cnc_manufacturer_price'] = $manufacturerPrice; } } //Assign crossSell products from OsCommerce as Upsell products in Magento2 if (isset($this->crossSellsData[$simpleSku])) { $this->preparedData[$key]['_upsell_sku'] = $this->crossSellsData[$simpleSku]; } //Populate data on Grouped Product if ($groupedSku = $this->getImportField('products_group', $item)) { $groupedProducts[$groupedSku]['product_type'] = 'grouped'; if (!isset($groupedProducts[$groupedSku]['sku'])) { $groupedProducts[$groupedSku]['sku'] = $groupedSku; } if ($this->getImportField('products_order', $item) == 1 || !in_array($groupedSku, $configuredGroupedSkus)) { $groupedProducts[$groupedSku]['_product_websites'] = 'cnc_eu' . self::MULTIPLE_VALUES_SEPARATOR . 'cnc_gb' . self::MULTIPLE_VALUES_SEPARATOR . 'cnc_us'; $groupedProducts[$groupedSku]['visibility'] = self::CATALOG_SEARCH; if (isset($this->preparedData[$key]['cnc_manufacturer_price_date'])) { $groupedProducts[$groupedSku]['cnc_manufacturer_price_date'] = $this->preparedData[$key]['cnc_manufacturer_price_date']; } if (isset($this->preparedData[$key]['cnc_manufacturer_price'])) { $groupedProducts[$groupedSku]['cnc_manufacturer_price'] = $this->preparedData[$key]['cnc_manufacturer_price']; } $groupedProducts[$groupedSku]['_attribute_set'] = $this->preparedData[$key]['_attribute_set']; //Add custom string to the name, because of url keys generation error during import, //some of simple products have the same names and change the grouped name helps here. $groupedProducts[$groupedSku]['name'] = $this->preparedData[$key]['name']; //Take url keys exported from M1 if exists, in other way create unique url key, because of // problem with duplicated skus and names which gives errors for duplicated urls if (isset($this->preparedData[$key]['grouped_url_key']) && !in_array($this->preparedData[$key]['grouped_url_key'], $importedUrlKeys)) { $groupedProducts[$groupedSku]['url_key'] = $this->preparedData[$key]['grouped_url_key'] . '-p-' . $this->preparedData[$key]['id']; $importedUrlKeys[] = $groupedProducts[$groupedSku]['url_key']; } else { $groupedProducts[$groupedSku]['url_key'] = $this->productModel->formatUrlKey( $groupedProducts[$groupedSku]['name'] . '-' . uniqid() . '-p-' . $this->preparedData[$key]['id'] ); } $groupedProducts[$groupedSku]['display_product_options_in'] = 'Block after Info Column'; $groupedProducts[$groupedSku]['description'] = $this->preparedData[$key]['description']; $groupedProducts[$groupedSku]['cnc_manufacturer'] = $this->preparedData[$key]['cnc_manufacturer']; $groupedProducts[$groupedSku]['cnc_net_weight'] = 0; $groupedProducts[$groupedSku]['cnc_oscom_id'] = $this->preparedData[$key]['cnc_oscom_id']; //Prepare grouped products FR store view data if (isset($this->preparedData[$key . 'cnc_fr_eu']['name']) ) { $groupedProducts[$groupedSku . 'cnc_fr_eu']['product_type'] = 'grouped'; $groupedProducts[$groupedSku . 'cnc_fr_eu']['name'] = $this->preparedData[$key . 'cnc_fr_eu']['name']; $groupedProducts[$groupedSku . 'cnc_fr_eu']['description'] = $this->preparedData[$key . 'cnc_fr_eu']['description']; $groupedProducts[$groupedSku . 'cnc_fr_eu']['sku'] = $groupedSku; $groupedProducts[$groupedSku . 'cnc_fr_eu']['url_key'] = $this->productModel->formatUrlKey( $this->preparedData[$key . 'cnc_fr_eu']['name'] . '-' . uniqid() . '-p-' . $this->preparedData[$key]['id'] ); $groupedProducts[$groupedSku . 'cnc_fr_eu']['_product_websites'] = $this->preparedData[$key . 'cnc_fr_eu']['_product_websites']; $groupedProducts[$groupedSku . 'cnc_fr_eu']['cnc_net_weight'] = 0; $groupedProducts[$groupedSku . 'cnc_fr_eu']['_attribute_set'] = $this->preparedData[$key . 'cnc_fr_eu']['_attribute_set']; $groupedProducts[$groupedSku . 'cnc_fr_eu']['store_view_code'] = $this->preparedData[$key . 'cnc_fr_eu']['store_view_code']; $groupedProducts[$groupedSku . 'cnc_fr_eu']['_store'] = $this->preparedData[$key . 'cnc_fr_eu']['_store']; $groupedProducts[$groupedSku . 'cnc_fr_eu']['categories'] = $this->preparedData[$key . 'cnc_fr_eu']['categories']; } //We are not saving some attributes data on Simple Product if Grouped Product exist unset($this->preparedData[$key]['description']); unset($this->preparedData[$key]['cnc_manufacturer']); if (isset($this->preparedData[$key . 'cnc_fr_eu']['description'])) { unset($this->preparedData[$key . 'cnc_fr_eu']['description']); } $groupedProducts[$groupedSku]['image'] = $this->preparedData[$key]['image']; if (isset($this->preparedData[$key]['categories'])) { $groupedProducts[$groupedSku]['categories'] = $this->preparedData[$key]['categories']; } //Assign crossSell products from OsCommerce as Upsell products in Magento2 if (isset($this->crossSellsData[$groupedSku])) { $groupedProducts[$groupedSku]['_upsell_sku'] = $this->crossSellsData[$groupedSku]; } $configuredGroupedSkus[] = $groupedProducts[$groupedSku]['sku']; } if (isset($groupedProducts[$groupedSku]['associated_skus'])) { //prepare custom format, because only this format is acceptable in Magento importer $multipleSkusFormat = trim($groupedProducts[$groupedSku]['associated_skus'], '"') . ',' . $simpleSku . '=' . 1; $groupedProducts[$groupedSku]['associated_skus'] = $multipleSkusFormat; } else { $groupedProducts[$groupedSku]['associated_skus'] = $simpleSku . '=' . 1; } //Disable simple product visibility when it's assigned to grouped $this->preparedData[$key]['visibility'] = self::NOT_VISIBLE_INDIVIDUALLY; } } elseif (array_key_exists('products_id', $item)) { var_export("Missing products_model field (SKU) for product ID " . $item['products_id'] . "\r\n"); } else { var_export("Missing products_model field (SKU) and ID for one of products\r\n"); } } $this->preparedData = array_merge($this->preparedData, $groupedProducts); } /** * Mapping for products assignment to categories used in import field 'categories' * @throws LocalizedException */ public function prepareCategoriesPaths() { $productsToCategory = []; foreach ($this->productsToCategoriesData as $productCategoryRelation) { if (array_key_exists('products_id', $productCategoryRelation) && array_key_exists('categories_id', $productCategoryRelation)) { $productsToCategory[$productCategoryRelation['products_id']][] = $productCategoryRelation['categories_id']; } } $categoriesPaths = $this->getCategoriesPaths(); foreach ($productsToCategory as $productId => $categoriesIds) { foreach ($categoriesIds as $categoryId) { if (isset($categoriesPaths[$categoryId]) && $categoriesPaths[$categoryId] != false) { if (isset($this->productsCategoriesPaths[$productId])) { $this->productsCategoriesPaths[$productId] = $this->productsCategoriesPaths[$productId] . self::MULTIPLE_VALUES_SEPARATOR . $categoriesPaths[$categoryId]; } else { $this->productsCategoriesPaths[$productId] = $categoriesPaths[$categoryId]; } } } } } /** * Build categories paths using names instead of IDs, needed in magento product importer to assign categories * @param $categoryName * @return bool|string|string[] * @throws LocalizedException */ public function getCategoryNamesPath($categoryName) { $storeId = 0; $categoriesNamesPath = false; $categoryCollection = $this->categoryCollectionFactory->create(); $categoryCollection->setStoreId($storeId) ->addAttributeToFilter('name', $categoryName) ->setPageSize(1); $result = $categoryCollection->getFirstItem(); if ($result->getId()) { $pathIds = $result->getPathIds(); $pathCategoriesCollection = $this->categoryCollectionFactory->create(); $pathCategoriesCollection->setStoreId($storeId) ->addAttributeToSelect('name') ->addAttributeToFilter('entity_id', ['in' => $pathIds]); $categoriesNamesPath = ''; foreach ($pathCategoriesCollection as $pathCategory) { $categoryName = $pathCategory->getName(); if (strpos($categoryName, '/')) { //Fix for categories with slash in name. Magento divides categories by slash, // but with \ before this slash it will be treated like one category $categoryName = str_replace('/', '\/', $categoryName); } $categoriesNamesPath .= $categoryName . '/'; } $categoriesNamesPath = substr($categoriesNamesPath, 0, -1); $categoriesNamesPath = str_replace( 'Root Catalog/', '', $categoriesNamesPath ); } return $categoriesNamesPath; } /** * Import! */ protected function toMagentoProducts() { $sourceModel = $this->sourceDataFactory->create(['data' => [$this->preparedData]]); $product = $this->importFactory->create(['importData' => $sourceModel]); //Set double semi column as Multiple value separator. $parameters = $product->getParameters(); $parameters[\Magento\ImportExport\Model\Import::FIELD_FIELD_MULTIPLE_VALUE_SEPARATOR] = self::MULTIPLE_VALUES_SEPARATOR; $product->setParameters($parameters); //Launch import of bunch. try { $product->importData(); } catch (Exception $exception) { var_export($exception->getMessage()); } //Check errors $errors = $product->getErrorAggregator()->getAllErrors(); if (count($errors)) { array_map(function ($error) { $this->logger->info( sprintf( "Error on line %s (%s) : %s", $error->getRowNumber(), $error->getErrorCode(), $error->getErrorMessage() ) ); $this->errors[] = $error->getRowNumber(); }, $errors); } unset($this->preparedData); unset($this->errors); } /** * Get attribute sets data from attribute sets import file to make attribute set field importable * @param $productId * @return AttributeSetInterface * @throws FileSystemException * @throws LocalizedException */ public function getAttributeSetForProduct($productId) { $attributeSetData = $this->getDataFromFile(self::FIXTURE_ATTRIBUTE_SETS_FR_FILE_PATH); // Default attribute set for products $attributeSetName = 'Other'; foreach ($this->productToAttributeSetData as $relation) { if (isset($relation['products_id']) && $relation['products_id'] == $productId) { $setId = $relation['type_products_id']; foreach ($attributeSetData as $attributeSet) { if ($attributeSet['type_products_id'] == $setId && $attributeSet['language_id'] == self::EN_LANGUAGE_IMPORT_ID) { $attributeSetName = $attributeSet['type_products_name']; } } } } $filter = $this->filterBuilder ->setField('attribute_set_name') ->setValue($attributeSetName) ->setConditionType('eq') ->create(); $searchCriteria = $this->searchCriteriaBuilder ->addFilters([$filter]) ->create(); $attributeSet = $this->attributeSetRepository->getList($searchCriteria); $items = $attributeSet->getItems(); return reset($items); } /** * Get description data of product from mapped array * * @param $productId * @param $languageId * @param $field * @return bool */ public function getDescriptionData($productId, $languageId, $field) { $result = false; if (array_key_exists($productId, $this->productDescriptionData) && array_key_exists($languageId, $this->productDescriptionData[$productId]) && array_key_exists($field, $this->productDescriptionData[$productId][$languageId])) { $result = $this->productDescriptionData[$productId][$languageId][$field]; } return $result; } /** * Get CNC attributes data from corresponding import files for each of these attributes, * because it's the same as in magento (we import it before products import from the same files) * @param $field * @param $item * @return bool * @throws FileSystemException * @throws LocalizedException */ public function getCncAttribute($field, $item) { $attributeValue = false; if ($value = $this->getImportField($field, $item)) { $fileName = false; switch ($field) { case 'manufacturers_id': $fileName = UpdateCustomTablesAttributesData::FIXTURE_MANUFACTURER_DATA_FILE_PATH; break; case 'etats_id': $fileName = UpdateCustomTablesAttributesData::FIXTURE_STATE_OF_WEAR_DATA_FILE_PATH; break; case 'garanties_id': $fileName = UpdateCustomTablesAttributesData::FIXTURE_GUARANTEE_DURATION_DATA_FILE_PATH; break; case 'types_id': $fileName = UpdateCustomTablesAttributesData::FIXTURE_GUARANTEE_TYPE_DATA_FILE_PATH; break; case 'disponibilites_id': $mapping = [ 1 => Config::PRODUCT_AVAILABILITY_ICON_SHIPMENT_WITHIN_3_DAYS_VALUE, 2 => Config::PRODUCT_AVAILABILITY_ICON_IMMEDIATE_SHIPMENT_VALUE, 3 => Config::PRODUCT_AVAILABILITY_ICON_AVAILABLE_IN_5_TO_8_DAYS_VALUE, 4 => Config::PRODUCT_AVAILABILITY_ICON_CONFIRMATION_OF_SHIPPING_TIME_ON_REQUEST_VALUE, 6 => Config::PRODUCT_AVAILABILITY_ICON_OUT_OF_STOCK_VALUE ]; return $this->msiAttributesData['cnc_availability'][$mapping[$value]]; } if ($fileName) { $attributeFileData = $this->getDataFromFile($fileName); foreach ($attributeFileData as $row) { //ID is always at first element and the name in second, in the attributes files. $dataRow = array_values($row); if ($value == $dataRow[0]) { $attributeValue = $dataRow[1]; } } } } return $attributeValue; } /** * Get single import field * * @param $key * @param $item * @return string|bool * @throws FileSystemException */ public function getImportField($key, $item) { $returnValue = false; if (array_key_exists($key, $item)) { if ($key == 'products_image') { if ($this->file->isExists($this->file->getRealPath('pub/media/import/' . $item[$key])) && $this->urlValidator->isValid($item[$key])) { $returnValue = $item[$key]; } else { var_export("Image file not exists or has invalid name in pub/media/import dir: {$item[$key]}\r\n"); } } else { $returnValue = $item[$key]; } } return $returnValue; } /** * Set product description to smaller array than comes from whole file * * @param $fileName * @throws FileSystemException * @throws LocalizedException */ public function getProductDescriptionFields($fileName) { $data = $this->getDataFromFile($fileName); foreach ($data as $row) { if (array_key_exists('products_id', $row) && array_key_exists('language_id', $row)) { foreach (self::PRODUCT_DESCRIPTION_FIELDS_MAPPING as $magentoField => $importField) { $this->productDescriptionData[$row['products_id']][$row['language_id']][$magentoField] = array_key_exists($importField, $row) ? $row[$importField] : null; } } } } /** * @param $fileName * @throws FileSystemException * @throws LocalizedException */ public function getCrossSellingData($fileName) { $IdToSkuMapping = []; $data = $this->getDataFromFile($fileName); //Prepare specific mapping used only in crossSells/upsells association in this import foreach ($this->productData as $productRow) { if (isset($productRow['products_id']) && isset($productRow['products_model'])) { //get grouped product sku when current simple is linked to one if (isset($productRow['products_group'])) { $IdToSkuMapping[$productRow['products_id']] = [ 'simpleSku' => $productRow['products_model'], 'groupedSku' => $productRow['products_group'] ]; } else { $IdToSkuMapping[$productRow['products_id']] = $productRow['products_model']; } } } foreach ($data as $row) { if (isset($row['products_id']) && isset($row['list_id']) && isset($IdToSkuMapping[$row['products_id']])) { //prepare mapping from id's to skus of crossSell products foreach (explode(',', $row['list_id']) as $crossSellId) { if (isset($IdToSkuMapping[$crossSellId]) && isset($IdToSkuMapping[$row['products_id']])) { //Get sku of new upsell product (osCommerce crossells will be imported as Magento upsells) if (isset($IdToSkuMapping[$crossSellId]['groupedSku'])) { $crossSellSku = $IdToSkuMapping[$crossSellId]['groupedSku']; } else { $crossSellSku = $IdToSkuMapping[$crossSellId]; } //Get the sku of product to which we assign if (isset($IdToSkuMapping[$row['products_id']]['groupedSku'])) { $keySku = $IdToSkuMapping[$row['products_id']]['groupedSku']; } else { $keySku = $IdToSkuMapping[$row['products_id']]; } //Set data to be used in import main logic if (isset($this->crossSellsData[$keySku])) { $crossSellsSkusFormat = $this->crossSellsData[$keySku] . self::MULTIPLE_VALUES_SEPARATOR . $crossSellSku; $this->crossSellsData[$keySku] = $crossSellsSkusFormat; } else { $this->crossSellsData[$keySku] = $crossSellSku; } } } } } } /** * Get simplier mapping of category path with parents names, needed in magento importer * to assign product to the right category. * @return array * @throws LocalizedException */ public function getCategoriesPaths() { $categoriesData = $this->getDataFromFile(self::FIXTURE_CATEGORIES_DESCRIPTION_FR_FILE_PATH); $categoriesPaths = []; foreach ($categoriesData as $categoryData) { if (array_key_exists('categories_id', $categoryData) && array_key_exists('categories_name', $categoryData)) { if (!isset($categoriesPaths[$categoryData['categories_id']])) { $categoriesPaths[$categoryData['categories_id']] = $this->getCategoryNamesPath($categoryData['categories_name']); } elseif ($categoriesPaths[$categoryData['categories_id']] == false) { $categoriesPaths[$categoryData['categories_id']] = $this->getCategoryNamesPath($categoryData['categories_name']); } } } return $categoriesPaths; } /** * Read the data from provided import/relation file * @param $name * @return array * @throws FileSystemException * @throws LocalizedException */ public function getDataFromFile($name) { $data = []; $fileName = $this->fixtureManager->getFixture($name); $file = $this->file->fileOpen($fileName, "r"); $header = $this->file->fileGetCsv($file, 0, ","); while ($row = $this->file->fileGetCsv($file, 0, ",")) { $data[] = array_filter(array_combine($header, $row)); } return $data; } /** * Prepare mapping for msi custom attributes. */ private function prepareMsiMapping() { $attributesCode = ['cnc_availability', 'cnc_localization']; foreach ($attributesCode as $attributeCode) { $attribute = $this->msiAttributes->getAttribute($attributeCode); $this->msiAttributesData[$attributeCode] = $this->msiAttributes->getDropdownOptions($attribute->getId()); } } }