Advertisement
Guest User

Untitled

a guest
Sep 15th, 2019
169
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 109.96 KB | None | 0 0
  1. <?php
  2. /**
  3. * Copyright © Magento, Inc. All rights reserved.
  4. * See COPYING.txt for license details.
  5. */
  6.  
  7. namespace Magento\CatalogImportExport\Model\Import;
  8.  
  9. use Magento\Catalog\Api\ProductRepositoryInterface;
  10. use Magento\Catalog\Model\Config as CatalogConfig;
  11. use Magento\Catalog\Model\Product\Visibility;
  12. use Magento\CatalogImportExport\Model\Import\Product\ImageTypeProcessor;
  13. use Magento\CatalogImportExport\Model\Import\Product\MediaGalleryProcessor;
  14. use Magento\CatalogImportExport\Model\Import\Product\RowValidatorInterface as ValidatorInterface;
  15. use Magento\CatalogImportExport\Model\StockItemImporterInterface;
  16. use Magento\CatalogInventory\Api\Data\StockItemInterface;
  17. use Magento\Framework\App\Filesystem\DirectoryList;
  18. use Magento\Framework\App\ObjectManager;
  19. use Magento\Framework\Exception\LocalizedException;
  20. use Magento\Framework\Exception\NoSuchEntityException;
  21. use Magento\Framework\Filesystem;
  22. use Magento\Framework\Intl\DateTimeFactory;
  23. use Magento\Framework\Model\ResourceModel\Db\ObjectRelationProcessor;
  24. use Magento\Framework\Model\ResourceModel\Db\TransactionManagerInterface;
  25. use Magento\Framework\Stdlib\DateTime;
  26. use Magento\ImportExport\Model\Import;
  27. use Magento\ImportExport\Model\Import\Entity\AbstractEntity;
  28. use Magento\ImportExport\Model\Import\ErrorProcessing\ProcessingError;
  29. use Magento\ImportExport\Model\Import\ErrorProcessing\ProcessingErrorAggregatorInterface;
  30. use Magento\Store\Model\Store;
  31.  
  32. /**
  33. * Import entity product model
  34. *
  35. * @api
  36. *
  37. * @SuppressWarnings(PHPMD.TooManyFields)
  38. * @SuppressWarnings(PHPMD.ExcessiveClassComplexity)
  39. * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
  40. * @since 100.0.2
  41. */
  42. class Product extends \Magento\ImportExport\Model\Import\Entity\AbstractEntity
  43. {
  44. const CONFIG_KEY_PRODUCT_TYPES = 'global/importexport/import_product_types';
  45.  
  46. /**
  47. * Size of bunch - part of products to save in one step.
  48. */
  49. const BUNCH_SIZE = 20;
  50.  
  51. /**
  52. * Size of bunch to delete attributes of products in one step.
  53. */
  54. const ATTRIBUTE_DELETE_BUNCH = 1000;
  55.  
  56. /**
  57. * Pseudo multi line separator in one cell.
  58. *
  59. * Can be used as custom option value delimiter or in configurable fields cells.
  60. */
  61. const PSEUDO_MULTI_LINE_SEPARATOR = '|';
  62.  
  63. /**
  64. * Symbol between Name and Value between Pairs.
  65. */
  66. const PAIR_NAME_VALUE_SEPARATOR = '=';
  67.  
  68. /**
  69. * Value that means all entities (e.g. websites, groups etc.)
  70. */
  71. const VALUE_ALL = 'all';
  72.  
  73. /**
  74. * Data row scopes.
  75. */
  76. const SCOPE_DEFAULT = 1;
  77.  
  78. const SCOPE_WEBSITE = 2;
  79.  
  80. const SCOPE_STORE = 0;
  81.  
  82. const SCOPE_NULL = -1;
  83.  
  84. /**
  85. * Permanent column names.
  86. *
  87. * Names that begins with underscore is not an attribute. This name convention is for
  88. * to avoid interference with same attribute name.
  89. */
  90.  
  91. /**
  92. * Column product store.
  93. */
  94. const COL_STORE = '_store';
  95.  
  96. /**
  97. * Column product store view code.
  98. */
  99. const COL_STORE_VIEW_CODE = 'store_view_code';
  100.  
  101. /**
  102. * Column website.
  103. */
  104. const COL_WEBSITE = 'website_code';
  105.  
  106. /**
  107. * Column product attribute set.
  108. */
  109. const COL_ATTR_SET = '_attribute_set';
  110.  
  111. /**
  112. * Column product type.
  113. */
  114. const COL_TYPE = 'product_type';
  115.  
  116. /**
  117. * Column product category.
  118. */
  119. const COL_CATEGORY = 'categories';
  120.  
  121. /**
  122. * Column product visibility.
  123. */
  124. const COL_VISIBILITY = 'visibility';
  125.  
  126. /**
  127. * Column product sku.
  128. */
  129. const COL_SKU = 'sku';
  130.  
  131. /**
  132. * Column product name.
  133. */
  134. const COL_NAME = 'name';
  135.  
  136. /**
  137. * Column product website.
  138. */
  139. const COL_PRODUCT_WEBSITES = '_product_websites';
  140.  
  141. /**
  142. * Media gallery attribute code.
  143. */
  144. const MEDIA_GALLERY_ATTRIBUTE_CODE = 'media_gallery';
  145.  
  146. /**
  147. * Column media image.
  148. */
  149. const COL_MEDIA_IMAGE = '_media_image';
  150.  
  151. /**
  152. * Inventory use config.
  153. */
  154. const INVENTORY_USE_CONFIG = 'Use Config';
  155.  
  156. /**
  157. * Inventory use config prefix.
  158. */
  159. const INVENTORY_USE_CONFIG_PREFIX = 'use_config_';
  160.  
  161. /**
  162. * Url key attribute code
  163. */
  164. const URL_KEY = 'url_key';
  165.  
  166. /**
  167. * Attribute cache
  168. *
  169. * @var array
  170. */
  171. protected $_attributeCache = [];
  172.  
  173. /**
  174. * Pairs of attribute set ID-to-name.
  175. *
  176. * @var array
  177. */
  178. protected $_attrSetIdToName = [];
  179.  
  180. /**
  181. * Pairs of attribute set name-to-ID.
  182. *
  183. * @var array
  184. */
  185. protected $_attrSetNameToId = [];
  186.  
  187. /**
  188. * @var string
  189. * @since 100.0.4
  190. */
  191. protected $mediaGalleryTableName;
  192.  
  193. /**
  194. * @var string
  195. * @since 100.0.4
  196. */
  197. protected $mediaGalleryValueTableName;
  198. /**
  199. * @var string
  200. * @since 100.0.4
  201. */
  202. protected $mediaGalleryEntityToValueTableName;
  203.  
  204. /**
  205. * @var string
  206. * @since 100.0.4
  207. */
  208. protected $productEntityTableName;
  209.  
  210. /**
  211. * Attributes with index (not label) value.
  212. *
  213. * @var string[]
  214. */
  215. protected $_indexValueAttributes = [
  216. 'status',
  217. 'tax_class_id',
  218. ];
  219.  
  220. /**
  221. * Links attribute name-to-link type ID.
  222. *
  223. * @var array
  224. */
  225. protected $_linkNameToId = [
  226. '_related_' => \Magento\Catalog\Model\Product\Link::LINK_TYPE_RELATED,
  227. '_crosssell_' => \Magento\Catalog\Model\Product\Link::LINK_TYPE_CROSSSELL,
  228. '_upsell_' => \Magento\Catalog\Model\Product\Link::LINK_TYPE_UPSELL,
  229. ];
  230.  
  231. /**
  232. * Attributes codes which shows as date
  233. *
  234. * @var array
  235. * @since 100.1.2
  236. */
  237. protected $dateAttrCodes = [
  238. 'special_from_date',
  239. 'special_to_date',
  240. 'news_from_date',
  241. 'news_to_date',
  242. 'custom_design_from',
  243. 'custom_design_to'
  244. ];
  245.  
  246. /**
  247. * Need to log in import history
  248. *
  249. * @var bool
  250. */
  251. protected $logInHistory = true;
  252.  
  253. /**
  254. * Attribute id for product images storage.
  255. *
  256. * @var array
  257. */
  258. protected $_mediaGalleryAttributeId = null;
  259.  
  260. /**
  261. * Validation failure message template definitions
  262. *
  263. * @var array
  264. * @codingStandardsIgnoreStart
  265. */
  266. protected $_messageTemplates = [
  267. ValidatorInterface::ERROR_INVALID_SCOPE => 'Invalid value in Scope column',
  268. ValidatorInterface::ERROR_INVALID_WEBSITE => 'Invalid value in Website column (website does not exist?)',
  269. ValidatorInterface::ERROR_INVALID_STORE => 'Invalid value in Store column (store doesn\'t exist?)',
  270. ValidatorInterface::ERROR_INVALID_ATTR_SET => 'Invalid value for Attribute Set column (set doesn\'t exist?)',
  271. ValidatorInterface::ERROR_INVALID_TYPE => 'Product Type is invalid or not supported',
  272. ValidatorInterface::ERROR_INVALID_CATEGORY => 'Category does not exist',
  273. ValidatorInterface::ERROR_VALUE_IS_REQUIRED => 'Please make sure attribute "%s" is not empty.',
  274. ValidatorInterface::ERROR_TYPE_CHANGED => 'Trying to change type of existing products',
  275. ValidatorInterface::ERROR_SKU_IS_EMPTY => 'SKU is empty',
  276. ValidatorInterface::ERROR_NO_DEFAULT_ROW => 'Default values row does not exist',
  277. ValidatorInterface::ERROR_CHANGE_TYPE => 'Product type change is not allowed',
  278. ValidatorInterface::ERROR_DUPLICATE_SCOPE => 'Duplicate scope',
  279. ValidatorInterface::ERROR_DUPLICATE_SKU => 'Duplicate SKU',
  280. ValidatorInterface::ERROR_CHANGE_ATTR_SET => 'Attribute set change is not allowed',
  281. ValidatorInterface::ERROR_TYPE_UNSUPPORTED => 'Product type is not supported',
  282. ValidatorInterface::ERROR_ROW_IS_ORPHAN => 'Orphan rows that will be skipped due default row errors',
  283. ValidatorInterface::ERROR_INVALID_TIER_PRICE_QTY => 'Tier Price data price or quantity value is invalid',
  284. ValidatorInterface::ERROR_INVALID_TIER_PRICE_SITE => 'Tier Price data website is invalid',
  285. ValidatorInterface::ERROR_INVALID_TIER_PRICE_GROUP => 'Tier Price customer group ID is invalid',
  286. ValidatorInterface::ERROR_TIER_DATA_INCOMPLETE => 'Tier Price data is incomplete',
  287. ValidatorInterface::ERROR_SKU_NOT_FOUND_FOR_DELETE => 'Product with specified SKU not found',
  288. ValidatorInterface::ERROR_SUPER_PRODUCTS_SKU_NOT_FOUND => 'Product with specified super products SKU not found',
  289. ValidatorInterface::ERROR_MEDIA_DATA_INCOMPLETE => 'Media data is incomplete',
  290. ValidatorInterface::ERROR_EXCEEDED_MAX_LENGTH => 'Attribute %s exceeded max length',
  291. ValidatorInterface::ERROR_INVALID_ATTRIBUTE_TYPE => 'Value for \'%s\' attribute contains incorrect value',
  292. ValidatorInterface::ERROR_ABSENT_REQUIRED_ATTRIBUTE => 'Attribute %s is required',
  293. ValidatorInterface::ERROR_INVALID_ATTRIBUTE_OPTION => 'Value for \'%s\' attribute contains incorrect value, see acceptable values on settings specified for Admin',
  294. ValidatorInterface::ERROR_DUPLICATE_UNIQUE_ATTRIBUTE => 'Duplicated unique attribute',
  295. ValidatorInterface::ERROR_INVALID_VARIATIONS_CUSTOM_OPTIONS => 'Value for \'%s\' sub attribute in \'%s\' attribute contains incorrect value, acceptable values are: \'dropdown\', \'checkbox\', \'radio\', \'text\'',
  296. ValidatorInterface::ERROR_INVALID_MEDIA_URL_OR_PATH => 'Wrong URL/path used for attribute %s',
  297. ValidatorInterface::ERROR_MEDIA_PATH_NOT_ACCESSIBLE => 'Imported resource (image) does not exist in the local media storage',
  298. ValidatorInterface::ERROR_MEDIA_URL_NOT_ACCESSIBLE => 'Imported resource (image) could not be downloaded from external resource due to timeout or access permissions',
  299. ValidatorInterface::ERROR_INVALID_WEIGHT => 'Product weight is invalid',
  300. ValidatorInterface::ERROR_DUPLICATE_URL_KEY => 'Url key: \'%s\' was already generated for an item with the SKU: \'%s\'. You need to specify the unique URL key manually',
  301. ValidatorInterface::ERROR_DUPLICATE_MULTISELECT_VALUES => 'Value for multiselect attribute %s contains duplicated values',
  302. 'invalidNewToDateValue' => 'Make sure new_to_date is later than or the same as new_from_date',
  303. ];
  304. //@codingStandardsIgnoreEnd
  305.  
  306. /**
  307. * Map between import file fields and system fields/attributes.
  308. *
  309. * @var array
  310. */
  311. protected $_fieldsMap = [
  312. 'image' => 'base_image',
  313. 'image_label' => "base_image_label",
  314. 'thumbnail' => 'thumbnail_image',
  315. 'thumbnail_label' => 'thumbnail_image_label',
  316. self::COL_MEDIA_IMAGE => 'additional_images',
  317. '_media_image_label' => 'additional_image_labels',
  318. '_media_is_disabled' => 'hide_from_product_page',
  319. Product::COL_STORE => 'store_view_code',
  320. Product::COL_ATTR_SET => 'attribute_set_code',
  321. Product::COL_TYPE => 'product_type',
  322. Product::COL_PRODUCT_WEBSITES => 'product_websites',
  323. 'status' => 'product_online',
  324. 'news_from_date' => 'new_from_date',
  325. 'news_to_date' => 'new_to_date',
  326. 'options_container' => 'display_product_options_in',
  327. 'minimal_price' => 'map_price',
  328. 'msrp' => 'msrp_price',
  329. 'msrp_enabled' => 'map_enabled',
  330. 'special_from_date' => 'special_price_from_date',
  331. 'special_to_date' => 'special_price_to_date',
  332. 'min_qty' => 'out_of_stock_qty',
  333. 'backorders' => 'allow_backorders',
  334. 'min_sale_qty' => 'min_cart_qty',
  335. 'max_sale_qty' => 'max_cart_qty',
  336. 'notify_stock_qty' => 'notify_on_stock_below',
  337. '_related_sku' => 'related_skus',
  338. '_related_position' => 'related_position',
  339. '_crosssell_sku' => 'crosssell_skus',
  340. '_crosssell_position' => 'crosssell_position',
  341. '_upsell_sku' => 'upsell_skus',
  342. '_upsell_position' => 'upsell_position',
  343. 'meta_keyword' => 'meta_keywords',
  344. ];
  345.  
  346. /**
  347. * Existing products SKU-related information in form of array:
  348. *
  349. * [SKU] => array(
  350. * 'type_id' => (string) product type
  351. * 'attr_set_id' => (int) product attribute set ID
  352. * 'entity_id' => (int) product ID
  353. * 'supported_type' => (boolean) is product type supported by current version of import module
  354. * )
  355. *
  356. * @var array
  357. */
  358. protected $_oldSku = [];
  359.  
  360. /**
  361. * Column names that holds values with particular meaning.
  362. *
  363. * @var string[]
  364. */
  365. protected $_specialAttributes = [
  366. self::COL_STORE,
  367. self::COL_ATTR_SET,
  368. self::COL_TYPE,
  369. self::COL_CATEGORY,
  370. '_product_websites',
  371. self::COL_PRODUCT_WEBSITES,
  372. '_tier_price_website',
  373. '_tier_price_customer_group',
  374. '_tier_price_qty',
  375. '_tier_price_price',
  376. '_related_sku',
  377. '_related_position',
  378. '_crosssell_sku',
  379. '_crosssell_position',
  380. '_upsell_sku',
  381. '_upsell_position',
  382. '_custom_option_store',
  383. '_custom_option_type',
  384. '_custom_option_title',
  385. '_custom_option_is_required',
  386. '_custom_option_price',
  387. '_custom_option_sku',
  388. '_custom_option_max_characters',
  389. '_custom_option_sort_order',
  390. '_custom_option_file_extension',
  391. '_custom_option_image_size_x',
  392. '_custom_option_image_size_y',
  393. '_custom_option_row_title',
  394. '_custom_option_row_price',
  395. '_custom_option_row_sku',
  396. '_custom_option_row_sort',
  397. '_media_attribute_id',
  398. self::COL_MEDIA_IMAGE,
  399. '_media_label',
  400. '_media_position',
  401. '_media_is_disabled',
  402. ];
  403.  
  404. /**
  405. * @var array
  406. */
  407. protected $defaultStockData = [
  408. 'manage_stock' => 1,
  409. 'use_config_manage_stock' => 1,
  410. 'qty' => 0,
  411. 'min_qty' => 0,
  412. 'use_config_min_qty' => 1,
  413. 'min_sale_qty' => 1,
  414. 'use_config_min_sale_qty' => 1,
  415. 'max_sale_qty' => 10000,
  416. 'use_config_max_sale_qty' => 1,
  417. 'is_qty_decimal' => 0,
  418. 'backorders' => 0,
  419. 'use_config_backorders' => 1,
  420. 'notify_stock_qty' => 1,
  421. 'use_config_notify_stock_qty' => 1,
  422. 'enable_qty_increments' => 0,
  423. 'use_config_enable_qty_inc' => 1,
  424. 'qty_increments' => 0,
  425. 'use_config_qty_increments' => 1,
  426. 'is_in_stock' => 1,
  427. 'low_stock_date' => null,
  428. 'stock_status_changed_auto' => 0,
  429. 'is_decimal_divided' => 0,
  430. ];
  431.  
  432. /**
  433. * Column names that holds images files names
  434. *
  435. * Note: the order of array items has a value in order to properly set 'position' value
  436. * of media gallery items.
  437. *
  438. * @var string[]
  439. */
  440. protected $_imagesArrayKeys = [];
  441.  
  442. /**
  443. * Permanent entity columns.
  444. *
  445. * @var string[]
  446. */
  447. protected $_permanentAttributes = [self::COL_SKU];
  448.  
  449. /**
  450. * Array of supported product types as keys with appropriate model object as value.
  451. *
  452. * @var \Magento\CatalogImportExport\Model\Import\Product\Type\AbstractType[]
  453. */
  454. protected $_productTypeModels = [];
  455.  
  456. /**
  457. * Media files uploader
  458. *
  459. * @var \Magento\CatalogImportExport\Model\Import\Uploader
  460. */
  461. protected $_fileUploader;
  462.  
  463. /**
  464. * Import entity which provide import of product custom options
  465. *
  466. * @var \Magento\CatalogImportExport\Model\Import\Product\Option
  467. */
  468. protected $_optionEntity;
  469.  
  470. /**
  471. * Catalog data
  472. *
  473. * @var \Magento\Catalog\Helper\Data
  474. */
  475. protected $_catalogData = null;
  476.  
  477. /**
  478. * @var \Magento\CatalogInventory\Api\StockRegistryInterface
  479. */
  480. protected $stockRegistry;
  481.  
  482. /**
  483. * @var \Magento\CatalogInventory\Api\StockConfigurationInterface
  484. */
  485. protected $stockConfiguration;
  486.  
  487. /**
  488. * @var \Magento\CatalogInventory\Model\Spi\StockStateProviderInterface
  489. */
  490. protected $stockStateProvider;
  491.  
  492. /**
  493. * Core event manager proxy
  494. *
  495. * @var \Magento\Framework\Event\ManagerInterface
  496. */
  497. protected $_eventManager = null;
  498.  
  499. /**
  500. * @var \Magento\ImportExport\Model\Import\Config
  501. */
  502. protected $_importConfig;
  503.  
  504. /**
  505. * @var \Magento\CatalogImportExport\Model\Import\Proxy\Product\ResourceModelFactory
  506. */
  507. protected $_resourceFactory;
  508.  
  509. /**
  510. * @var \Magento\CatalogImportExport\Model\Import\Proxy\Product\ResourceModel
  511. */
  512. protected $_resource;
  513.  
  514. /**
  515. * @var \Magento\Eav\Model\ResourceModel\Entity\Attribute\Set\CollectionFactory
  516. */
  517. protected $_setColFactory;
  518.  
  519. /**
  520. * @var \Magento\CatalogImportExport\Model\Import\Product\Type\Factory
  521. */
  522. protected $_productTypeFactory;
  523.  
  524. /**
  525. * @var \Magento\Catalog\Model\ResourceModel\Product\LinkFactory
  526. */
  527. protected $_linkFactory;
  528.  
  529. /**
  530. * @var \Magento\CatalogImportExport\Model\Import\Proxy\ProductFactory
  531. */
  532. protected $_proxyProdFactory;
  533.  
  534. /**
  535. * @var \Magento\CatalogImportExport\Model\Import\UploaderFactory
  536. */
  537. protected $_uploaderFactory;
  538.  
  539. /**
  540. * @var \Magento\Framework\Filesystem\Directory\WriteInterface
  541. */
  542. protected $_mediaDirectory;
  543.  
  544. /**
  545. * @var \Magento\CatalogInventory\Model\ResourceModel\Stock\ItemFactory
  546. * @deprecated this variable isn't used anymore.
  547. */
  548. protected $_stockResItemFac;
  549.  
  550. /**
  551. * @var \Magento\Framework\Stdlib\DateTime\TimezoneInterface
  552. */
  553. protected $_localeDate;
  554.  
  555. /**
  556. * @var DateTime
  557. */
  558. protected $dateTime;
  559.  
  560. /**
  561. * @var \Magento\Framework\Indexer\IndexerRegistry
  562. */
  563. protected $indexerRegistry;
  564.  
  565. /**
  566. * @var Product\StoreResolver
  567. */
  568. protected $storeResolver;
  569.  
  570. /**
  571. * @var Product\SkuProcessor
  572. */
  573. protected $skuProcessor;
  574.  
  575. /**
  576. * @var Product\CategoryProcessor
  577. */
  578. protected $categoryProcessor;
  579.  
  580. /**
  581. * @var \Magento\Framework\App\Config\ScopeConfigInterface
  582. * @since 100.0.3
  583. */
  584. protected $scopeConfig;
  585.  
  586. /**
  587. * @var \Magento\Catalog\Model\Product\Url
  588. * @since 100.0.3
  589. */
  590. protected $productUrl;
  591.  
  592. /**
  593. * @var array
  594. */
  595. protected $websitesCache = [];
  596.  
  597. /**
  598. * @var array
  599. */
  600. protected $categoriesCache = [];
  601.  
  602. /**
  603. * @var array
  604. * @since 100.0.3
  605. */
  606. protected $productUrlSuffix = [];
  607.  
  608. /**
  609. * @var array
  610. * @deprecated 100.1.5
  611. * @since 100.0.3
  612. */
  613. protected $productUrlKeys = [];
  614.  
  615. /**
  616. * Instance of product tax class processor.
  617. *
  618. * @var Product\TaxClassProcessor
  619. */
  620. protected $taxClassProcessor;
  621.  
  622. /**
  623. * @var Product\Validator
  624. */
  625. protected $validator;
  626.  
  627. /**
  628. * Array of validated rows.
  629. *
  630. * @var array
  631. */
  632. protected $validatedRows;
  633.  
  634. /**
  635. * @var \Psr\Log\LoggerInterface
  636. */
  637. private $_logger;
  638.  
  639. /**
  640. * {@inheritdoc}
  641. */
  642. protected $masterAttributeCode = 'sku';
  643.  
  644. /**
  645. * @var ObjectRelationProcessor
  646. */
  647. protected $objectRelationProcessor;
  648.  
  649. /**
  650. * @var TransactionManagerInterface
  651. */
  652. protected $transactionManager;
  653.  
  654. /**
  655. * Flag for replace operation.
  656. *
  657. * @var null
  658. */
  659. protected $_replaceFlag = null;
  660.  
  661. /**
  662. * Flag for replace operation.
  663. *
  664. * @var null
  665. */
  666. protected $cachedImages = null;
  667.  
  668. /**
  669. * @var array
  670. * @since 100.0.3
  671. */
  672. protected $urlKeys = [];
  673.  
  674. /**
  675. * @var array
  676. * @since 100.0.3
  677. */
  678. protected $rowNumbers = [];
  679.  
  680. /**
  681. * Product entity link field
  682. *
  683. * @var string
  684. */
  685. private $productEntityLinkField;
  686.  
  687. /**
  688. * Product entity identifier field
  689. *
  690. * @var string
  691. */
  692. private $productEntityIdentifierField;
  693.  
  694. /**
  695. * Escaped separator value for regular expression.
  696. * The value is based on PSEUDO_MULTI_LINE_SEPARATOR constant.
  697. * @var string
  698. */
  699. private $multiLineSeparatorForRegexp;
  700.  
  701. /**
  702. * Container for filesystem object.
  703. *
  704. * @var Filesystem
  705. */
  706. private $filesystem;
  707.  
  708. /**
  709. * Catalog config.
  710. *
  711. * @var CatalogConfig
  712. */
  713. private $catalogConfig;
  714.  
  715. /**
  716. * Stock Item Importer
  717. *
  718. * @var StockItemImporterInterface
  719. */
  720. private $stockItemImporter;
  721.  
  722. /**
  723. * @var ImageTypeProcessor
  724. */
  725. private $imageTypeProcessor;
  726.  
  727. /**
  728. * Provide ability to process and save images during import.
  729. *
  730. * @var MediaGalleryProcessor
  731. */
  732. private $mediaProcessor;
  733.  
  734. /**
  735. * @var DateTimeFactory
  736. */
  737. private $dateTimeFactory;
  738.  
  739. /**
  740. * @var ProductRepositoryInterface
  741. */
  742. private $productRepository;
  743.  
  744. /**
  745. * @param \Magento\Framework\Json\Helper\Data $jsonHelper
  746. * @param \Magento\ImportExport\Helper\Data $importExportData
  747. * @param \Magento\ImportExport\Model\ResourceModel\Import\Data $importData
  748. * @param \Magento\Eav\Model\Config $config
  749. * @param \Magento\Framework\App\ResourceConnection $resource
  750. * @param \Magento\ImportExport\Model\ResourceModel\Helper $resourceHelper
  751. * @param \Magento\Framework\Stdlib\StringUtils $string
  752. * @param ProcessingErrorAggregatorInterface $errorAggregator
  753. * @param \Magento\Framework\Event\ManagerInterface $eventManager
  754. * @param \Magento\CatalogInventory\Api\StockRegistryInterface $stockRegistry
  755. * @param \Magento\CatalogInventory\Api\StockConfigurationInterface $stockConfiguration
  756. * @param \Magento\CatalogInventory\Model\Spi\StockStateProviderInterface $stockStateProvider
  757. * @param \Magento\Catalog\Helper\Data $catalogData
  758. * @param \Magento\ImportExport\Model\Import\Config $importConfig
  759. * @param Proxy\Product\ResourceModelFactory $resourceFactory
  760. * @param Product\OptionFactory $optionFactory
  761. * @param \Magento\Eav\Model\ResourceModel\Entity\Attribute\Set\CollectionFactory $setColFactory
  762. * @param Product\Type\Factory $productTypeFactory
  763. * @param \Magento\Catalog\Model\ResourceModel\Product\LinkFactory $linkFactory
  764. * @param Proxy\ProductFactory $proxyProdFactory
  765. * @param UploaderFactory $uploaderFactory
  766. * @param \Magento\Framework\Filesystem $filesystem
  767. * @param \Magento\CatalogInventory\Model\ResourceModel\Stock\ItemFactory $stockResItemFac
  768. * @param DateTime\TimezoneInterface $localeDate
  769. * @param DateTime $dateTime
  770. * @param \Psr\Log\LoggerInterface $logger
  771. * @param \Magento\Framework\Indexer\IndexerRegistry $indexerRegistry
  772. * @param Product\StoreResolver $storeResolver
  773. * @param Product\SkuProcessor $skuProcessor
  774. * @param Product\CategoryProcessor $categoryProcessor
  775. * @param Product\Validator $validator
  776. * @param ObjectRelationProcessor $objectRelationProcessor
  777. * @param TransactionManagerInterface $transactionManager
  778. * @param Product\TaxClassProcessor $taxClassProcessor
  779. * @param \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig
  780. * @param \Magento\Catalog\Model\Product\Url $productUrl
  781. * @param array $data
  782. * @param array $dateAttrCodes
  783. * @param CatalogConfig $catalogConfig
  784. * @param ImageTypeProcessor $imageTypeProcessor
  785. * @param MediaGalleryProcessor $mediaProcessor
  786. * @param StockItemImporterInterface|null $stockItemImporter
  787. * @param DateTimeFactory $dateTimeFactory
  788. * @param ProductRepositoryInterface|null $productRepository
  789. * @throws LocalizedException
  790. * @throws \Magento\Framework\Exception\FileSystemException
  791. * @SuppressWarnings(PHPMD.ExcessiveParameterList)
  792. * @SuppressWarnings(PHPMD.ExcessiveMethodLength)
  793. */
  794. public function __construct(
  795. \Magento\Framework\Json\Helper\Data $jsonHelper,
  796. \Magento\ImportExport\Helper\Data $importExportData,
  797. \Magento\ImportExport\Model\ResourceModel\Import\Data $importData,
  798. \Magento\Eav\Model\Config $config,
  799. \Magento\Framework\App\ResourceConnection $resource,
  800. \Magento\ImportExport\Model\ResourceModel\Helper $resourceHelper,
  801. \Magento\Framework\Stdlib\StringUtils $string,
  802. ProcessingErrorAggregatorInterface $errorAggregator,
  803. \Magento\Framework\Event\ManagerInterface $eventManager,
  804. \Magento\CatalogInventory\Api\StockRegistryInterface $stockRegistry,
  805. \Magento\CatalogInventory\Api\StockConfigurationInterface $stockConfiguration,
  806. \Magento\CatalogInventory\Model\Spi\StockStateProviderInterface $stockStateProvider,
  807. \Magento\Catalog\Helper\Data $catalogData,
  808. \Magento\ImportExport\Model\Import\Config $importConfig,
  809. \Magento\CatalogImportExport\Model\Import\Proxy\Product\ResourceModelFactory $resourceFactory,
  810. \Magento\CatalogImportExport\Model\Import\Product\OptionFactory $optionFactory,
  811. \Magento\Eav\Model\ResourceModel\Entity\Attribute\Set\CollectionFactory $setColFactory,
  812. \Magento\CatalogImportExport\Model\Import\Product\Type\Factory $productTypeFactory,
  813. \Magento\Catalog\Model\ResourceModel\Product\LinkFactory $linkFactory,
  814. \Magento\CatalogImportExport\Model\Import\Proxy\ProductFactory $proxyProdFactory,
  815. \Magento\CatalogImportExport\Model\Import\UploaderFactory $uploaderFactory,
  816. \Magento\Framework\Filesystem $filesystem,
  817. \Magento\CatalogInventory\Model\ResourceModel\Stock\ItemFactory $stockResItemFac,
  818. \Magento\Framework\Stdlib\DateTime\TimezoneInterface $localeDate,
  819. DateTime $dateTime,
  820. \Psr\Log\LoggerInterface $logger,
  821. \Magento\Framework\Indexer\IndexerRegistry $indexerRegistry,
  822. Product\StoreResolver $storeResolver,
  823. Product\SkuProcessor $skuProcessor,
  824. Product\CategoryProcessor $categoryProcessor,
  825. Product\Validator $validator,
  826. ObjectRelationProcessor $objectRelationProcessor,
  827. TransactionManagerInterface $transactionManager,
  828. Product\TaxClassProcessor $taxClassProcessor,
  829. \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig,
  830. \Magento\Catalog\Model\Product\Url $productUrl,
  831. array $data = [],
  832. array $dateAttrCodes = [],
  833. CatalogConfig $catalogConfig = null,
  834. ImageTypeProcessor $imageTypeProcessor = null,
  835. MediaGalleryProcessor $mediaProcessor = null,
  836. StockItemImporterInterface $stockItemImporter = null,
  837. DateTimeFactory $dateTimeFactory = null,
  838. ProductRepositoryInterface $productRepository = null
  839. ) {
  840. $this->_eventManager = $eventManager;
  841. $this->stockRegistry = $stockRegistry;
  842. $this->stockConfiguration = $stockConfiguration;
  843. $this->stockStateProvider = $stockStateProvider;
  844. $this->_catalogData = $catalogData;
  845. $this->_importConfig = $importConfig;
  846. $this->_resourceFactory = $resourceFactory;
  847. $this->_setColFactory = $setColFactory;
  848. $this->_productTypeFactory = $productTypeFactory;
  849. $this->_linkFactory = $linkFactory;
  850. $this->_proxyProdFactory = $proxyProdFactory;
  851. $this->_uploaderFactory = $uploaderFactory;
  852. $this->filesystem = $filesystem;
  853. $this->_mediaDirectory = $filesystem->getDirectoryWrite(DirectoryList::ROOT);
  854. $this->_stockResItemFac = $stockResItemFac;
  855. $this->_localeDate = $localeDate;
  856. $this->dateTime = $dateTime;
  857. $this->indexerRegistry = $indexerRegistry;
  858. $this->_logger = $logger;
  859. $this->storeResolver = $storeResolver;
  860. $this->skuProcessor = $skuProcessor;
  861. $this->categoryProcessor = $categoryProcessor;
  862. $this->validator = $validator;
  863. $this->objectRelationProcessor = $objectRelationProcessor;
  864. $this->transactionManager = $transactionManager;
  865. $this->taxClassProcessor = $taxClassProcessor;
  866. $this->scopeConfig = $scopeConfig;
  867. $this->productUrl = $productUrl;
  868. $this->dateAttrCodes = array_merge($this->dateAttrCodes, $dateAttrCodes);
  869. $this->catalogConfig = $catalogConfig ?: ObjectManager::getInstance()->get(CatalogConfig::class);
  870. $this->imageTypeProcessor = $imageTypeProcessor ?: ObjectManager::getInstance()->get(ImageTypeProcessor::class);
  871. $this->mediaProcessor = $mediaProcessor ?: ObjectManager::getInstance()->get(MediaGalleryProcessor::class);
  872. $this->stockItemImporter = $stockItemImporter ?: ObjectManager::getInstance()
  873. ->get(StockItemImporterInterface::class);
  874. parent::__construct(
  875. $jsonHelper,
  876. $importExportData,
  877. $importData,
  878. $config,
  879. $resource,
  880. $resourceHelper,
  881. $string,
  882. $errorAggregator
  883. );
  884. $this->_optionEntity = $data['option_entity'] ??
  885. $optionFactory->create(['data' => ['product_entity' => $this]]);
  886. $this->_initAttributeSets()
  887. ->_initTypeModels()
  888. ->_initSkus()
  889. ->initImagesArrayKeys();
  890. $this->validator->init($this);
  891. $this->dateTimeFactory = $dateTimeFactory ?? ObjectManager::getInstance()->get(DateTimeFactory::class);
  892. $this->productRepository = $productRepository ?? ObjectManager::getInstance()
  893. ->get(ProductRepositoryInterface::class);
  894. }
  895.  
  896. /**
  897. * Check one attribute. Can be overridden in child.
  898. *
  899. * @param string $attrCode Attribute code
  900. * @param array $attrParams Attribute params
  901. * @param array $rowData Row data
  902. * @param int $rowNum
  903. * @return bool
  904. */
  905. public function isAttributeValid($attrCode, array $attrParams, array $rowData, $rowNum)
  906. {
  907. if (!$this->validator->isAttributeValid($attrCode, $attrParams, $rowData)) {
  908. foreach ($this->validator->getMessages() as $message) {
  909. $this->skipRow($rowNum, $message, ProcessingError::ERROR_LEVEL_NOT_CRITICAL, $attrCode);
  910. }
  911. return false;
  912. }
  913. return true;
  914. }
  915.  
  916. /**
  917. * Multiple value separator getter.
  918. *
  919. * @return string
  920. */
  921. public function getMultipleValueSeparator()
  922. {
  923. if (!empty($this->_parameters[Import::FIELD_FIELD_MULTIPLE_VALUE_SEPARATOR])) {
  924. return $this->_parameters[Import::FIELD_FIELD_MULTIPLE_VALUE_SEPARATOR];
  925. }
  926. return Import::DEFAULT_GLOBAL_MULTI_VALUE_SEPARATOR;
  927. }
  928.  
  929. /**
  930. * Return empty attribute value constant
  931. *
  932. * @return string
  933. */
  934. public function getEmptyAttributeValueConstant()
  935. {
  936. if (!empty($this->_parameters[Import::FIELD_EMPTY_ATTRIBUTE_VALUE_CONSTANT])) {
  937. return $this->_parameters[Import::FIELD_EMPTY_ATTRIBUTE_VALUE_CONSTANT];
  938. }
  939. return Import::DEFAULT_EMPTY_ATTRIBUTE_VALUE_CONSTANT;
  940. }
  941.  
  942. /**
  943. * Retrieve instance of product custom options import entity
  944. *
  945. * @return \Magento\CatalogImportExport\Model\Import\Product\Option
  946. */
  947. public function getOptionEntity()
  948. {
  949. return $this->_optionEntity;
  950. }
  951.  
  952. /**
  953. * Retrieve id of media gallery attribute.
  954. *
  955. * @return int
  956. */
  957. public function getMediaGalleryAttributeId()
  958. {
  959. if (!$this->_mediaGalleryAttributeId) {
  960. /** @var $resource \Magento\CatalogImportExport\Model\Import\Proxy\Product\ResourceModel */
  961. $resource = $this->_resourceFactory->create();
  962. $this->_mediaGalleryAttributeId = $resource->getAttribute(self::MEDIA_GALLERY_ATTRIBUTE_CODE)->getId();
  963. }
  964. return $this->_mediaGalleryAttributeId;
  965. }
  966.  
  967. /**
  968. * Retrieve product type by name.
  969. *
  970. * @param string $name
  971. * @return Product\Type\AbstractType
  972. */
  973. public function retrieveProductTypeByName($name)
  974. {
  975. if (isset($this->_productTypeModels[$name])) {
  976. return $this->_productTypeModels[$name];
  977. }
  978. return null;
  979. }
  980.  
  981. /**
  982. * Set import parameters
  983. *
  984. * @param array $params
  985. * @return $this
  986. */
  987. public function setParameters(array $params)
  988. {
  989. parent::setParameters($params);
  990. $this->getOptionEntity()->setParameters($params);
  991.  
  992. return $this;
  993. }
  994.  
  995. /**
  996. * Delete products for replacement.
  997. *
  998. * @return $this
  999. */
  1000. public function deleteProductsForReplacement()
  1001. {
  1002. $this->setParameters(array_merge(
  1003. $this->getParameters(),
  1004. ['behavior' => Import::BEHAVIOR_DELETE]
  1005. ));
  1006. $this->_deleteProducts();
  1007.  
  1008. return $this;
  1009. }
  1010.  
  1011. /**
  1012. * Delete products.
  1013. *
  1014. * @return $this
  1015. * @throws \Exception
  1016. */
  1017. protected function _deleteProducts()
  1018. {
  1019. $productEntityTable = $this->_resourceFactory->create()->getEntityTable();
  1020.  
  1021. while ($bunch = $this->_dataSourceModel->getNextBunch()) {
  1022. $idsToDelete = [];
  1023.  
  1024. foreach ($bunch as $rowNum => $rowData) {
  1025. if ($this->validateRow($rowData, $rowNum) && self::SCOPE_DEFAULT == $this->getRowScope($rowData)) {
  1026. $idsToDelete[] = $this->getExistingSku($rowData[self::COL_SKU])['entity_id'];
  1027. }
  1028. }
  1029. if ($idsToDelete) {
  1030. $this->countItemsDeleted += count($idsToDelete);
  1031. $this->transactionManager->start($this->_connection);
  1032. try {
  1033. $this->objectRelationProcessor->delete(
  1034. $this->transactionManager,
  1035. $this->_connection,
  1036. $productEntityTable,
  1037. $this->_connection->quoteInto('entity_id IN (?)', $idsToDelete),
  1038. ['entity_id' => $idsToDelete]
  1039. );
  1040. $this->_eventManager->dispatch(
  1041. 'catalog_product_import_bunch_delete_commit_before',
  1042. [
  1043. 'adapter' => $this,
  1044. 'bunch' => $bunch,
  1045. 'ids_to_delete' => $idsToDelete,
  1046. ]
  1047. );
  1048. $this->transactionManager->commit();
  1049. } catch (\Exception $e) {
  1050. $this->transactionManager->rollBack();
  1051. throw $e;
  1052. }
  1053. $this->_eventManager->dispatch(
  1054. 'catalog_product_import_bunch_delete_after',
  1055. ['adapter' => $this, 'bunch' => $bunch]
  1056. );
  1057. }
  1058. }
  1059. return $this;
  1060. }
  1061.  
  1062. /**
  1063. * Create Product entity from raw data.
  1064. *
  1065. * @throws \Exception
  1066. * @return bool Result of operation.
  1067. * @SuppressWarnings(PHPMD.CyclomaticComplexity)
  1068. */
  1069. protected function _importData()
  1070. {
  1071. $this->_validatedRows = null;
  1072. if (Import::BEHAVIOR_DELETE == $this->getBehavior()) {
  1073. $this->_deleteProducts();
  1074. } elseif (Import::BEHAVIOR_REPLACE == $this->getBehavior()) {
  1075. $this->_replaceFlag = true;
  1076. $this->_replaceProducts();
  1077. } else {
  1078. $this->_saveProductsData();
  1079. }
  1080. $this->_eventManager->dispatch('catalog_product_import_finish_before', ['adapter' => $this]);
  1081. return true;
  1082. }
  1083.  
  1084. /**
  1085. * Replace imported products.
  1086. *
  1087. * @return $this
  1088. */
  1089. protected function _replaceProducts()
  1090. {
  1091. $this->deleteProductsForReplacement();
  1092. $this->_oldSku = $this->skuProcessor->reloadOldSkus()->getOldSkus();
  1093. $this->_validatedRows = null;
  1094. $this->setParameters(array_merge(
  1095. $this->getParameters(),
  1096. ['behavior' => Import::BEHAVIOR_APPEND]
  1097. ));
  1098. $this->_saveProductsData();
  1099.  
  1100. return $this;
  1101. }
  1102.  
  1103. /**
  1104. * Save products data.
  1105. *
  1106. * @return $this
  1107. */
  1108. protected function _saveProductsData()
  1109. {
  1110. $this->_saveProducts();
  1111. foreach ($this->_productTypeModels as $productTypeModel) {
  1112. $productTypeModel->saveData();
  1113. }
  1114. $this->_saveLinks();
  1115. $this->_saveStockItem();
  1116. if ($this->_replaceFlag) {
  1117. $this->getOptionEntity()->clearProductsSkuToId();
  1118. }
  1119. $this->getOptionEntity()->importData();
  1120.  
  1121. return $this;
  1122. }
  1123.  
  1124. /**
  1125. * Initialize attribute sets code-to-id pairs.
  1126. *
  1127. * @return $this
  1128. */
  1129. protected function _initAttributeSets()
  1130. {
  1131. foreach ($this->_setColFactory->create()->setEntityTypeFilter($this->_entityTypeId) as $attributeSet) {
  1132. $this->_attrSetNameToId[$attributeSet->getAttributeSetName()] = $attributeSet->getId();
  1133. $this->_attrSetIdToName[$attributeSet->getId()] = $attributeSet->getAttributeSetName();
  1134. }
  1135. return $this;
  1136. }
  1137.  
  1138. /**
  1139. * Initialize existent product SKUs.
  1140. *
  1141. * @return $this
  1142. */
  1143. protected function _initSkus()
  1144. {
  1145. $this->skuProcessor->setTypeModels($this->_productTypeModels);
  1146. $this->_oldSku = $this->skuProcessor->reloadOldSkus()->getOldSkus();
  1147. return $this;
  1148. }
  1149.  
  1150. /**
  1151. * Initialize image array keys.
  1152. *
  1153. * @return $this
  1154. */
  1155. private function initImagesArrayKeys()
  1156. {
  1157. $this->_imagesArrayKeys = $this->imageTypeProcessor->getImageTypes();
  1158. return $this;
  1159. }
  1160.  
  1161. /**
  1162. * Initialize product type models.
  1163. *
  1164. * @return $this
  1165. * @throws \Magento\Framework\Exception\LocalizedException
  1166. */
  1167. protected function _initTypeModels()
  1168. {
  1169. $productTypes = $this->_importConfig->getEntityTypes($this->getEntityTypeCode());
  1170. foreach ($productTypes as $productTypeName => $productTypeConfig) {
  1171. $params = [$this, $productTypeName];
  1172. if (!($model = $this->_productTypeFactory->create($productTypeConfig['model'], ['params' => $params]))
  1173. ) {
  1174. throw new LocalizedException(
  1175. __('Entity type model \'%1\' is not found', $productTypeConfig['model'])
  1176. );
  1177. }
  1178. if (!$model instanceof \Magento\CatalogImportExport\Model\Import\Product\Type\AbstractType) {
  1179. throw new LocalizedException(
  1180. __(
  1181. 'Entity type model must be an instance of '
  1182. . \Magento\CatalogImportExport\Model\Import\Product\Type\AbstractType::class
  1183. )
  1184. );
  1185. }
  1186. if ($model->isSuitable()) {
  1187. $this->_productTypeModels[$productTypeName] = $model;
  1188. }
  1189. $this->_fieldsMap = array_merge($this->_fieldsMap, $model->getCustomFieldsMapping());
  1190. $this->_specialAttributes = array_merge($this->_specialAttributes, $model->getParticularAttributes());
  1191. }
  1192. $this->_initErrorTemplates();
  1193. // remove doubles
  1194. $this->_specialAttributes = array_unique($this->_specialAttributes);
  1195.  
  1196. return $this;
  1197. }
  1198.  
  1199. /**
  1200. * Initialize Product error templates
  1201. */
  1202. protected function _initErrorTemplates()
  1203. {
  1204. foreach ($this->_messageTemplates as $errorCode => $template) {
  1205. $this->addMessageTemplate($errorCode, $template);
  1206. }
  1207. }
  1208.  
  1209. /**
  1210. * Set valid attribute set and product type to rows.
  1211. *
  1212. * Set valid attribute set and product type to rows with all
  1213. * scopes to ensure that existing products doesn't changed.
  1214. *
  1215. * @param array $rowData
  1216. * @return array
  1217. */
  1218. protected function _prepareRowForDb(array $rowData)
  1219. {
  1220. $rowData = $this->_customFieldsMapping($rowData);
  1221.  
  1222. $rowData = parent::_prepareRowForDb($rowData);
  1223.  
  1224. static $lastSku = null;
  1225.  
  1226. if (Import::BEHAVIOR_DELETE == $this->getBehavior()) {
  1227. return $rowData;
  1228. }
  1229.  
  1230. $lastSku = $rowData[self::COL_SKU];
  1231.  
  1232. if ($this->isSkuExist($lastSku)) {
  1233. $newSku = $this->skuProcessor->getNewSku($lastSku);
  1234. $rowData[self::COL_ATTR_SET] = $newSku['attr_set_code'];
  1235. $rowData[self::COL_TYPE] = $newSku['type_id'];
  1236. }
  1237.  
  1238. return $rowData;
  1239. }
  1240.  
  1241. /**
  1242. * Gather and save information about product links.
  1243. *
  1244. * Must be called after ALL products saving done.
  1245. *
  1246. * @return $this
  1247. * @SuppressWarnings(PHPMD.CyclomaticComplexity)
  1248. * @SuppressWarnings(PHPMD.NPathComplexity)
  1249. * @SuppressWarnings(PHPMD.ExcessiveMethodLength)
  1250. */
  1251. protected function _saveLinks()
  1252. {
  1253. $resource = $this->_linkFactory->create();
  1254. $mainTable = $resource->getMainTable();
  1255. $positionAttrId = [];
  1256. $nextLinkId = $this->_resourceHelper->getNextAutoincrement($mainTable);
  1257.  
  1258. // pre-load 'position' attributes ID for each link type once
  1259. foreach ($this->_linkNameToId as $linkName => $linkId) {
  1260. $select = $this->_connection->select()->from(
  1261. $resource->getTable('catalog_product_link_attribute'),
  1262. ['id' => 'product_link_attribute_id']
  1263. )->where(
  1264. 'link_type_id = :link_id AND product_link_attribute_code = :position'
  1265. );
  1266. $bind = [':link_id' => $linkId, ':position' => 'position'];
  1267. $positionAttrId[$linkId] = $this->_connection->fetchOne($select, $bind);
  1268. }
  1269. while ($bunch = $this->_dataSourceModel->getNextBunch()) {
  1270. $productIds = [];
  1271. $linkRows = [];
  1272. $positionRows = [];
  1273.  
  1274. foreach ($bunch as $rowNum => $rowData) {
  1275. if (!$this->isRowAllowedToImport($rowData, $rowNum)) {
  1276. continue;
  1277. }
  1278.  
  1279. $sku = $rowData[self::COL_SKU];
  1280.  
  1281. $productId = $this->skuProcessor->getNewSku($sku)[$this->getProductEntityLinkField()];
  1282. $productLinkKeys = [];
  1283. $select = $this->_connection->select()->from(
  1284. $resource->getTable('catalog_product_link'),
  1285. ['id' => 'link_id', 'linked_id' => 'linked_product_id', 'link_type_id' => 'link_type_id']
  1286. )->where(
  1287. 'product_id = :product_id'
  1288. );
  1289. $bind = [':product_id' => $productId];
  1290. foreach ($this->_connection->fetchAll($select, $bind) as $linkData) {
  1291. $linkKey = "{$productId}-{$linkData['linked_id']}-{$linkData['link_type_id']}";
  1292. $productLinkKeys[$linkKey] = $linkData['id'];
  1293. }
  1294. foreach ($this->_linkNameToId as $linkName => $linkId) {
  1295. $productIds[] = $productId;
  1296. if (isset($rowData[$linkName . 'sku'])) {
  1297. $linkSkus = explode($this->getMultipleValueSeparator(), $rowData[$linkName . 'sku']);
  1298. $linkPositions = !empty($rowData[$linkName . 'position'])
  1299. ? explode($this->getMultipleValueSeparator(), $rowData[$linkName . 'position'])
  1300. : [];
  1301. foreach ($linkSkus as $linkedKey => $linkedSku) {
  1302. $linkedSku = trim($linkedSku);
  1303. if (($this->skuProcessor->getNewSku($linkedSku) !== null || $this->isSkuExist($linkedSku))
  1304. && strcasecmp($linkedSku, $sku) !== 0
  1305. ) {
  1306. $newSku = $this->skuProcessor->getNewSku($linkedSku);
  1307. if (!empty($newSku)) {
  1308. $linkedId = $newSku['entity_id'];
  1309. } else {
  1310. $linkedId = $this->getExistingSku($linkedSku)['entity_id'];
  1311. }
  1312.  
  1313. if ($linkedId == null) {
  1314. // Import file links to a SKU which is skipped for some reason,
  1315. // which leads to a "NULL"
  1316. // link causing fatal errors.
  1317. $this->_logger->critical(
  1318. new \Exception(
  1319. sprintf(
  1320. 'WARNING: Orphaned link skipped: From SKU %s (ID %d) to SKU %s, ' .
  1321. 'Link type id: %d',
  1322. $sku,
  1323. $productId,
  1324. $linkedSku,
  1325. $linkId
  1326. )
  1327. )
  1328. );
  1329. continue;
  1330. }
  1331.  
  1332. $linkKey = "{$productId}-{$linkedId}-{$linkId}";
  1333. if (empty($productLinkKeys[$linkKey])) {
  1334. $productLinkKeys[$linkKey] = $nextLinkId;
  1335. }
  1336. if (!isset($linkRows[$linkKey])) {
  1337. $linkRows[$linkKey] = [
  1338. 'link_id' => $productLinkKeys[$linkKey],
  1339. 'product_id' => $productId,
  1340. 'linked_product_id' => $linkedId,
  1341. 'link_type_id' => $linkId,
  1342. ];
  1343. }
  1344. if (!empty($linkPositions[$linkedKey])) {
  1345. $positionRows[] = [
  1346. 'link_id' => $productLinkKeys[$linkKey],
  1347. 'product_link_attribute_id' => $positionAttrId[$linkId],
  1348. 'value' => $linkPositions[$linkedKey],
  1349. ];
  1350. }
  1351. $nextLinkId++;
  1352. }
  1353. }
  1354. }
  1355. }
  1356. }
  1357. if (Import::BEHAVIOR_APPEND != $this->getBehavior() && $productIds) {
  1358. $this->_connection->delete(
  1359. $mainTable,
  1360. $this->_connection->quoteInto('product_id IN (?)', array_unique($productIds))
  1361. );
  1362. }
  1363. if ($linkRows) {
  1364. $this->_connection->insertOnDuplicate($mainTable, $linkRows, ['link_id']);
  1365. }
  1366. if ($positionRows) {
  1367. // process linked product positions
  1368. $this->_connection->insertOnDuplicate(
  1369. $resource->getAttributeTypeTable('int'),
  1370. $positionRows,
  1371. ['value']
  1372. );
  1373. }
  1374. }
  1375. return $this;
  1376. }
  1377.  
  1378. /**
  1379. * Save product attributes.
  1380. *
  1381. * @param array $attributesData
  1382. * @return $this
  1383. */
  1384. protected function _saveProductAttributes(array $attributesData)
  1385. {
  1386. $linkField = $this->getProductEntityLinkField();
  1387. foreach ($attributesData as $tableName => $skuData) {
  1388. $tableData = [];
  1389. foreach ($skuData as $sku => $attributes) {
  1390. $linkId = $this->_oldSku[strtolower($sku)][$linkField];
  1391. foreach ($attributes as $attributeId => $storeValues) {
  1392. foreach ($storeValues as $storeId => $storeValue) {
  1393. $tableData[] = [
  1394. $linkField => $linkId,
  1395. 'attribute_id' => $attributeId,
  1396. 'store_id' => $storeId,
  1397. 'value' => $storeValue,
  1398. ];
  1399. }
  1400. }
  1401. }
  1402. $this->_connection->insertOnDuplicate($tableName, $tableData, ['value']);
  1403. }
  1404.  
  1405. return $this;
  1406. }
  1407.  
  1408. /**
  1409. * Save product categories.
  1410. *
  1411. * @param array $categoriesData
  1412. * @return $this
  1413. */
  1414. protected function _saveProductCategories(array $categoriesData)
  1415. {
  1416. static $tableName = null;
  1417.  
  1418. if (!$tableName) {
  1419. $tableName = $this->_resourceFactory->create()->getProductCategoryTable();
  1420. }
  1421. if ($categoriesData) {
  1422. $categoriesIn = [];
  1423. $delProductId = [];
  1424.  
  1425. foreach ($categoriesData as $delSku => $categories) {
  1426. $productId = $this->skuProcessor->getNewSku($delSku)['entity_id'];
  1427. $delProductId[] = $productId;
  1428.  
  1429. foreach (array_keys($categories) as $categoryId) {
  1430. $categoriesIn[] = ['product_id' => $productId, 'category_id' => $categoryId, 'position' => 0];
  1431. }
  1432. }
  1433. if (Import::BEHAVIOR_APPEND != $this->getBehavior()) {
  1434. $this->_connection->delete(
  1435. $tableName,
  1436. $this->_connection->quoteInto('product_id IN (?)', $delProductId)
  1437. );
  1438. }
  1439. if ($categoriesIn) {
  1440. $this->_connection->insertOnDuplicate($tableName, $categoriesIn, ['product_id', 'category_id']);
  1441. }
  1442. }
  1443. return $this;
  1444. }
  1445.  
  1446. /**
  1447. * Update and insert data in entity table.
  1448. *
  1449. * @param array $entityRowsIn Row for insert
  1450. * @param array $entityRowsUp Row for update
  1451. * @return $this
  1452. * @since 100.1.0
  1453. */
  1454. public function saveProductEntity(array $entityRowsIn, array $entityRowsUp)
  1455. {
  1456. static $entityTable = null;
  1457. $this->countItemsCreated += count($entityRowsIn);
  1458. $this->countItemsUpdated += count($entityRowsUp);
  1459.  
  1460. if (!$entityTable) {
  1461. $entityTable = $this->_resourceFactory->create()->getEntityTable();
  1462. }
  1463. if ($entityRowsUp) {
  1464. $this->_connection->insertOnDuplicate($entityTable, $entityRowsUp, ['updated_at', 'attribute_set_id']);
  1465. }
  1466. if ($entityRowsIn) {
  1467. $this->_connection->insertMultiple($entityTable, $entityRowsIn);
  1468.  
  1469. $select = $this->_connection->select()->from(
  1470. $entityTable,
  1471. array_merge($this->getNewSkuFieldsForSelect(), $this->getOldSkuFieldsForSelect())
  1472. )->where(
  1473. $this->_connection->quoteInto('sku IN (?)', array_keys($entityRowsIn))
  1474. );
  1475. $newProducts = $this->_connection->fetchAll($select);
  1476. foreach ($newProducts as $data) {
  1477. $sku = $data['sku'];
  1478. unset($data['sku']);
  1479. foreach ($data as $key => $value) {
  1480. $this->skuProcessor->setNewSkuData($sku, $key, $value);
  1481. }
  1482. }
  1483.  
  1484. $this->updateOldSku($newProducts);
  1485. }
  1486.  
  1487. return $this;
  1488. }
  1489.  
  1490. /**
  1491. * Return additional data, needed to select.
  1492. *
  1493. * @return array
  1494. */
  1495. private function getOldSkuFieldsForSelect()
  1496. {
  1497. return ['type_id', 'attribute_set_id'];
  1498. }
  1499.  
  1500. /**
  1501. * Adds newly created products to _oldSku
  1502. *
  1503. * @param array $newProducts
  1504. * @return void
  1505. */
  1506. private function updateOldSku(array $newProducts)
  1507. {
  1508. $oldSkus = [];
  1509. foreach ($newProducts as $info) {
  1510. $typeId = $info['type_id'];
  1511. $sku = strtolower($info['sku']);
  1512. $oldSkus[$sku] = [
  1513. 'type_id' => $typeId,
  1514. 'attr_set_id' => $info['attribute_set_id'],
  1515. $this->getProductIdentifierField() => $info[$this->getProductIdentifierField()],
  1516. 'supported_type' => isset($this->_productTypeModels[$typeId]),
  1517. $this->getProductEntityLinkField() => $info[$this->getProductEntityLinkField()],
  1518. ];
  1519. }
  1520.  
  1521. $this->_oldSku = array_replace($this->_oldSku, $oldSkus);
  1522. }
  1523.  
  1524. /**
  1525. * Get new SKU fields for select
  1526. *
  1527. * @return array
  1528. */
  1529. private function getNewSkuFieldsForSelect()
  1530. {
  1531. $fields = ['sku', $this->getProductEntityLinkField()];
  1532. if ($this->getProductEntityLinkField() != $this->getProductIdentifierField()) {
  1533. $fields[] = $this->getProductIdentifierField();
  1534. }
  1535. return $fields;
  1536. }
  1537.  
  1538. /**
  1539. * Init media gallery resources
  1540. *
  1541. * @return void
  1542. * @since 100.0.4
  1543. * @deprecated
  1544. */
  1545. protected function initMediaGalleryResources()
  1546. {
  1547. if (null == $this->mediaGalleryTableName) {
  1548. $this->productEntityTableName = $this->getResource()->getTable('catalog_product_entity');
  1549. $this->mediaGalleryTableName = $this->getResource()->getTable('catalog_product_entity_media_gallery');
  1550. $this->mediaGalleryValueTableName = $this->getResource()->getTable(
  1551. 'catalog_product_entity_media_gallery_value'
  1552. );
  1553. $this->mediaGalleryEntityToValueTableName = $this->getResource()->getTable(
  1554. 'catalog_product_entity_media_gallery_value_to_entity'
  1555. );
  1556. }
  1557. }
  1558.  
  1559. /**
  1560. * Get existing images for current bunch
  1561. *
  1562. * @param array $bunch
  1563. * @return array
  1564. */
  1565. protected function getExistingImages($bunch)
  1566. {
  1567. return $this->mediaProcessor->getExistingImages($bunch);
  1568. }
  1569.  
  1570. /**
  1571. * Retrieve image from row.
  1572. *
  1573. * @param array $rowData
  1574. * @return array
  1575. */
  1576. public function getImagesFromRow(array $rowData)
  1577. {
  1578. $images = [];
  1579. $labels = [];
  1580. foreach ($this->_imagesArrayKeys as $column) {
  1581. if (!empty($rowData[$column])) {
  1582. $images[$column] = array_unique(
  1583. array_map(
  1584. 'trim',
  1585. explode($this->getMultipleValueSeparator(), $rowData[$column])
  1586. )
  1587. );
  1588.  
  1589. if (!empty($rowData[$column . '_label'])) {
  1590. $labels[$column] = $this->parseMultipleValues($rowData[$column . '_label']);
  1591.  
  1592. if (count($labels[$column]) > count($images[$column])) {
  1593. $labels[$column] = array_slice($labels[$column], 0, count($images[$column]));
  1594. }
  1595. }
  1596. }
  1597. }
  1598.  
  1599. return [$images, $labels];
  1600. }
  1601.  
  1602. /**
  1603. * Gather and save information about product entities.
  1604. *
  1605. * @return $this
  1606. * @SuppressWarnings(PHPMD.CyclomaticComplexity)
  1607. * @SuppressWarnings(PHPMD.NPathComplexity)
  1608. * @SuppressWarnings(PHPMD.ExcessiveMethodLength)
  1609. * @SuppressWarnings(PHPMD.UnusedLocalVariable)
  1610. * @throws LocalizedException
  1611. */
  1612. protected function _saveProducts()
  1613. {
  1614. $priceIsGlobal = $this->_catalogData->isPriceGlobal();
  1615. $productLimit = null;
  1616. $productsQty = null;
  1617. $entityLinkField = $this->getProductEntityLinkField();
  1618.  
  1619. while ($bunch = $this->_dataSourceModel->getNextBunch()) {
  1620. $entityRowsIn = [];
  1621. $entityRowsUp = [];
  1622. $attributes = [];
  1623. $this->websitesCache = [];
  1624. $this->categoriesCache = [];
  1625. $tierPrices = [];
  1626. $mediaGallery = [];
  1627. $labelsForUpdate = [];
  1628. $imagesForChangeVisibility = [];
  1629. $uploadedImages = [];
  1630. $previousType = null;
  1631. $prevAttributeSet = null;
  1632. $importDir = $this->_mediaDirectory->getAbsolutePath($this->getImportDir());
  1633.  
  1634. $existingImages = $this->getExistingImages($bunch);
  1635.  
  1636. $this->addImageHashes($existingImages);
  1637.  
  1638. foreach ($bunch as $rowNum => $rowData) {
  1639. // reset category processor's failed categories array
  1640. $this->categoryProcessor->clearFailedCategories();
  1641.  
  1642. if (!$this->validateRow($rowData, $rowNum)) {
  1643. continue;
  1644. }
  1645. if ($this->getErrorAggregator()->hasToBeTerminated()) {
  1646. $this->getErrorAggregator()->addRowToSkip($rowNum);
  1647. continue;
  1648. }
  1649. $rowScope = $this->getRowScope($rowData);
  1650.  
  1651. $urlKey = $this->getUrlKey($rowData);
  1652. if (!empty($rowData[self::URL_KEY])) {
  1653. // If url_key column and its value were in the CSV file
  1654. $rowData[self::URL_KEY] = $urlKey;
  1655. } elseif ($this->isNeedToChangeUrlKey($rowData)) {
  1656. // If url_key column was empty or even not declared in the CSV file but by the rules it is need to
  1657. // be setteed. In case when url_key is generating from name column we have to ensure that the bunch
  1658. // of products will pass for the event with url_key column.
  1659. $bunch[$rowNum][self::URL_KEY] = $rowData[self::URL_KEY] = $urlKey;
  1660. }
  1661.  
  1662. $rowSku = $rowData[self::COL_SKU];
  1663.  
  1664. if (null === $rowSku) {
  1665. $this->getErrorAggregator()->addRowToSkip($rowNum);
  1666. continue;
  1667. }
  1668.  
  1669. if (self::SCOPE_STORE == $rowScope) {
  1670. // set necessary data from SCOPE_DEFAULT row
  1671. $rowData[self::COL_TYPE] = $this->skuProcessor->getNewSku($rowSku)['type_id'];
  1672. $rowData['attribute_set_id'] = $this->skuProcessor->getNewSku($rowSku)['attr_set_id'];
  1673. $rowData[self::COL_ATTR_SET] = $this->skuProcessor->getNewSku($rowSku)['attr_set_code'];
  1674. }
  1675.  
  1676. // 1. Entity phase
  1677. if ($this->isSkuExist($rowSku)) {
  1678. // existing row
  1679. if (isset($rowData['attribute_set_code'])) {
  1680. $attributeSetId = $this->catalogConfig->getAttributeSetId(
  1681. $this->getEntityTypeId(),
  1682. $rowData['attribute_set_code']
  1683. );
  1684.  
  1685. // wrong attribute_set_code was received
  1686. if (!$attributeSetId) {
  1687. throw new LocalizedException(
  1688. __(
  1689. 'Wrong attribute set code "%1", please correct it and try again.',
  1690. $rowData['attribute_set_code']
  1691. )
  1692. );
  1693. }
  1694. } else {
  1695. $attributeSetId = $this->skuProcessor->getNewSku($rowSku)['attr_set_id'];
  1696. }
  1697.  
  1698. $entityRowsUp[] = [
  1699. 'updated_at' => (new \DateTime())->format(DateTime::DATETIME_PHP_FORMAT),
  1700. 'attribute_set_id' => $attributeSetId,
  1701. $entityLinkField => $this->getExistingSku($rowSku)[$entityLinkField]
  1702. ];
  1703. } else {
  1704. if (!$productLimit || $productsQty < $productLimit) {
  1705. $entityRowsIn[strtolower($rowSku)] = [
  1706. 'attribute_set_id' => $this->skuProcessor->getNewSku($rowSku)['attr_set_id'],
  1707. 'type_id' => $this->skuProcessor->getNewSku($rowSku)['type_id'],
  1708. 'sku' => $rowSku,
  1709. 'has_options' => isset($rowData['has_options']) ? $rowData['has_options'] : 0,
  1710. 'created_at' => (new \DateTime())->format(DateTime::DATETIME_PHP_FORMAT),
  1711. 'updated_at' => (new \DateTime())->format(DateTime::DATETIME_PHP_FORMAT),
  1712. ];
  1713. $productsQty++;
  1714. } else {
  1715. $rowSku = null;
  1716. // sign for child rows to be skipped
  1717. $this->getErrorAggregator()->addRowToSkip($rowNum);
  1718. continue;
  1719. }
  1720. }
  1721.  
  1722. if (!array_key_exists($rowSku, $this->websitesCache)) {
  1723. $this->websitesCache[$rowSku] = [];
  1724. }
  1725. // 2. Product-to-Website phase
  1726. if (!empty($rowData[self::COL_PRODUCT_WEBSITES])) {
  1727. $websiteCodes = explode($this->getMultipleValueSeparator(), $rowData[self::COL_PRODUCT_WEBSITES]);
  1728. foreach ($websiteCodes as $websiteCode) {
  1729. $websiteId = $this->storeResolver->getWebsiteCodeToId($websiteCode);
  1730. $this->websitesCache[$rowSku][$websiteId] = true;
  1731. }
  1732. } else {
  1733. $product = $this->retrieveProductBySku($rowSku);
  1734. if ($product) {
  1735. $websiteIds = $product->getWebsiteIds();
  1736. foreach ($websiteIds as $websiteId) {
  1737. $this->websitesCache[$rowSku][$websiteId] = true;
  1738. }
  1739. }
  1740. }
  1741.  
  1742. // 3. Categories phase
  1743. if (!array_key_exists($rowSku, $this->categoriesCache)) {
  1744. $this->categoriesCache[$rowSku] = [];
  1745. }
  1746. $rowData['rowNum'] = $rowNum;
  1747. $categoryIds = $this->processRowCategories($rowData);
  1748. foreach ($categoryIds as $id) {
  1749. $this->categoriesCache[$rowSku][$id] = true;
  1750. }
  1751. unset($rowData['rowNum']);
  1752.  
  1753. // 4.1. Tier prices phase
  1754. if (!empty($rowData['_tier_price_website'])) {
  1755. $tierPrices[$rowSku][] = [
  1756. 'all_groups' => $rowData['_tier_price_customer_group'] == self::VALUE_ALL,
  1757. 'customer_group_id' => $rowData['_tier_price_customer_group'] ==
  1758. self::VALUE_ALL ? 0 : $rowData['_tier_price_customer_group'],
  1759. 'qty' => $rowData['_tier_price_qty'],
  1760. 'value' => $rowData['_tier_price_price'],
  1761. 'website_id' => self::VALUE_ALL == $rowData['_tier_price_website'] ||
  1762. $priceIsGlobal ? 0 : $this->storeResolver->getWebsiteCodeToId($rowData['_tier_price_website']),
  1763. ];
  1764. }
  1765.  
  1766. if (!$this->validateRow($rowData, $rowNum)) {
  1767. continue;
  1768. }
  1769.  
  1770. // 5. Media gallery phase
  1771. list($rowImages, $rowLabels) = $this->getImagesFromRow($rowData);
  1772. $storeId = !empty($rowData[self::COL_STORE])
  1773. ? $this->getStoreIdByCode($rowData[self::COL_STORE])
  1774. : Store::DEFAULT_STORE_ID;
  1775. $imageHiddenStates = $this->getImagesHiddenStates($rowData);
  1776. foreach (array_keys($imageHiddenStates) as $image) {
  1777. if (array_key_exists($rowSku, $existingImages)
  1778. && array_key_exists($image, $existingImages[$rowSku])
  1779. ) {
  1780. $rowImages[self::COL_MEDIA_IMAGE][] = $image;
  1781. $uploadedImages[$image] = $image;
  1782. }
  1783.  
  1784. if (empty($rowImages)) {
  1785. $rowImages[self::COL_MEDIA_IMAGE][] = $image;
  1786. }
  1787. }
  1788.  
  1789. $rowData[self::COL_MEDIA_IMAGE] = [];
  1790.  
  1791. /*
  1792. * Note: to avoid problems with undefined sorting, the value of media gallery items positions
  1793. * must be unique in scope of one product.
  1794. */
  1795. $position = 0;
  1796. foreach ($rowImages as $column => $columnImages) {
  1797. foreach ($columnImages as $columnImageKey => $columnImage) {
  1798. $filename = $importDir . DIRECTORY_SEPARATOR . $columnImage;
  1799. $hash = '';
  1800. if ($this->_mediaDirectory->isReadable($filename)) {
  1801. $hash = md5_file($filename);
  1802. }
  1803.  
  1804. if (!isset($existingImages[$rowSku])) {
  1805. $imageAlreadyExists = false;
  1806. } else {
  1807. $imageAlreadyExists = array_reduce($existingImages[$rowSku], function ($exists, $file) use ($hash) {
  1808. if ($exists) {
  1809. return $exists;
  1810. }
  1811. if ($file['hash'] === $hash) {
  1812. return $file['value'];
  1813. }
  1814. return $exists;
  1815. }, '');
  1816. }
  1817.  
  1818. if ($imageAlreadyExists) {
  1819. $uploadedFile = $imageAlreadyExists;
  1820. } else {
  1821. if (!isset($uploadedImages[$columnImage])) {
  1822. $uploadedFile = $this->uploadMediaFiles($columnImage);
  1823. $uploadedFile = $uploadedFile ?: $this->getSystemFile($columnImage);
  1824. if ($uploadedFile) {
  1825. $uploadedImages[$columnImage] = $uploadedFile;
  1826. } else {
  1827. // unset($rowData[$column]);
  1828. // $this->skipRow($rowNum, ValidatorInterface::ERROR_MEDIA_URL_NOT_ACCESSIBLE);
  1829. }
  1830. } else {
  1831. $uploadedFile = $uploadedImages[$columnImage];
  1832. }
  1833. }
  1834.  
  1835. if ($uploadedFile && $column !== self::COL_MEDIA_IMAGE) {
  1836. $rowData[$column] = $uploadedFile;
  1837. }
  1838.  
  1839. if ($uploadedFile && !isset($mediaGallery[$storeId][$rowSku][$uploadedFile])) {
  1840. if (isset($existingImages[$rowSku][$uploadedFile])) {
  1841. $currentFileData = $existingImages[$rowSku][$uploadedFile];
  1842. if (isset($rowLabels[$column][$columnImageKey])
  1843. && $rowLabels[$column][$columnImageKey] !=
  1844. $currentFileData['label']
  1845. ) {
  1846. $labelsForUpdate[] = [
  1847. 'label' => $rowLabels[$column][$columnImageKey],
  1848. 'imageData' => $currentFileData
  1849. ];
  1850. }
  1851.  
  1852. if (array_key_exists($uploadedFile, $imageHiddenStates)
  1853. && $currentFileData['disabled'] != $imageHiddenStates[$uploadedFile]
  1854. ) {
  1855. $imagesForChangeVisibility[] = [
  1856. 'disabled' => $imageHiddenStates[$uploadedFile],
  1857. 'imageData' => $currentFileData
  1858. ];
  1859. }
  1860. } else {
  1861. if ($column == self::COL_MEDIA_IMAGE) {
  1862. $rowData[$column][] = $uploadedFile;
  1863. }
  1864. $mediaGallery[$storeId][$rowSku][$uploadedFile] = [
  1865. 'attribute_id' => $this->getMediaGalleryAttributeId(),
  1866. 'label' => isset($rowLabels[$column][$columnImageKey])
  1867. ? $rowLabels[$column][$columnImageKey]
  1868. : '',
  1869. 'position' => ++$position,
  1870. 'disabled' => isset($imageHiddenStates[$columnImage])
  1871. ? $imageHiddenStates[$columnImage] : '0',
  1872. 'value' => $uploadedFile,
  1873. ];
  1874. }
  1875. }
  1876. }
  1877. }
  1878.  
  1879. // 6. Attributes phase
  1880. $rowStore = (self::SCOPE_STORE == $rowScope)
  1881. ? $this->storeResolver->getStoreCodeToId($rowData[self::COL_STORE])
  1882. : 0;
  1883. $productType = isset($rowData[self::COL_TYPE]) ? $rowData[self::COL_TYPE] : null;
  1884. if ($productType !== null) {
  1885. $previousType = $productType;
  1886. }
  1887. if (isset($rowData[self::COL_ATTR_SET])) {
  1888. $prevAttributeSet = $rowData[self::COL_ATTR_SET];
  1889. }
  1890. if (self::SCOPE_NULL == $rowScope) {
  1891. // for multiselect attributes only
  1892. if ($prevAttributeSet !== null) {
  1893. $rowData[self::COL_ATTR_SET] = $prevAttributeSet;
  1894. }
  1895. if ($productType === null && $previousType !== null) {
  1896. $productType = $previousType;
  1897. }
  1898. if ($productType === null) {
  1899. continue;
  1900. }
  1901. }
  1902.  
  1903. $productTypeModel = $this->_productTypeModels[$productType];
  1904. if (!empty($rowData['tax_class_name'])) {
  1905. $rowData['tax_class_id'] =
  1906. $this->taxClassProcessor->upsertTaxClass($rowData['tax_class_name'], $productTypeModel);
  1907. }
  1908.  
  1909. if ($this->getBehavior() == Import::BEHAVIOR_APPEND ||
  1910. empty($rowData[self::COL_SKU])
  1911. ) {
  1912. $rowData = $productTypeModel->clearEmptyData($rowData);
  1913. }
  1914.  
  1915. $rowData = $productTypeModel->prepareAttributesWithDefaultValueForSave(
  1916. $rowData,
  1917. !$this->isSkuExist($rowSku)
  1918. );
  1919. $product = $this->_proxyProdFactory->create(['data' => $rowData]);
  1920.  
  1921. foreach ($rowData as $attrCode => $attrValue) {
  1922. $attribute = $this->retrieveAttributeByCode($attrCode);
  1923.  
  1924. if ('multiselect' != $attribute->getFrontendInput() && self::SCOPE_NULL == $rowScope) {
  1925. // skip attribute processing for SCOPE_NULL rows
  1926. continue;
  1927. }
  1928. $attrId = $attribute->getId();
  1929. $backModel = $attribute->getBackendModel();
  1930. $attrTable = $attribute->getBackend()->getTable();
  1931. $storeIds = [0];
  1932.  
  1933. if ('datetime' == $attribute->getBackendType()
  1934. && (
  1935. in_array($attribute->getAttributeCode(), $this->dateAttrCodes)
  1936. || $attribute->getIsUserDefined()
  1937. )
  1938. ) {
  1939. $attrValue = $this->dateTime->formatDate($attrValue, false);
  1940. } elseif ('datetime' == $attribute->getBackendType() && strtotime($attrValue)) {
  1941. $attrValue = gmdate(
  1942. 'Y-m-d H:i:s',
  1943. $this->_localeDate->date($attrValue)->getTimestamp()
  1944. );
  1945. } elseif ($backModel) {
  1946. $attribute->getBackend()->beforeSave($product);
  1947. $attrValue = $product->getData($attribute->getAttributeCode());
  1948. }
  1949. if (self::SCOPE_STORE == $rowScope) {
  1950. if (self::SCOPE_WEBSITE == $attribute->getIsGlobal()) {
  1951. // check website defaults already set
  1952. if (!isset($attributes[$attrTable][$rowSku][$attrId][$rowStore])) {
  1953. $storeIds = $this->storeResolver->getStoreIdToWebsiteStoreIds($rowStore);
  1954. }
  1955. } elseif (self::SCOPE_STORE == $attribute->getIsGlobal()) {
  1956. $storeIds = [$rowStore];
  1957. }
  1958. if (!$this->isSkuExist($rowSku)) {
  1959. $storeIds[] = 0;
  1960. }
  1961. }
  1962. foreach ($storeIds as $storeId) {
  1963. if (!isset($attributes[$attrTable][$rowSku][$attrId][$storeId])) {
  1964. $attributes[$attrTable][$rowSku][$attrId][$storeId] = $attrValue;
  1965. }
  1966. }
  1967. // restore 'backend_model' to avoid 'default' setting
  1968. $attribute->setBackendModel($backModel);
  1969. }
  1970. }
  1971.  
  1972. foreach ($bunch as $rowNum => $rowData) {
  1973. if ($this->getErrorAggregator()->isRowInvalid($rowNum)) {
  1974. unset($bunch[$rowNum]);
  1975. }
  1976. }
  1977.  
  1978. $this->saveProductEntity(
  1979. $entityRowsIn,
  1980. $entityRowsUp
  1981. )->_saveProductWebsites(
  1982. $this->websitesCache
  1983. )->_saveProductCategories(
  1984. $this->categoriesCache
  1985. )->_saveProductTierPrices(
  1986. $tierPrices
  1987. )->_saveMediaGallery(
  1988. $mediaGallery
  1989. )->_saveProductAttributes(
  1990. $attributes
  1991. )->updateMediaGalleryVisibility(
  1992. $imagesForChangeVisibility
  1993. )->updateMediaGalleryLabels(
  1994. $labelsForUpdate
  1995. );
  1996.  
  1997. $this->_eventManager->dispatch(
  1998. 'catalog_product_import_bunch_save_after',
  1999. ['adapter' => $this, 'bunch' => $bunch]
  2000. );
  2001. }
  2002.  
  2003. return $this;
  2004. }
  2005.  
  2006. /**
  2007. * Generate md5 hashes for existing images for comparison with newly uploaded images.
  2008. *
  2009. * @param array $images
  2010. */
  2011. public function addImageHashes(&$images) {
  2012. $dirConfig = DirectoryList::getDefaultConfig();
  2013. $dirAddon = $dirConfig[DirectoryList::MEDIA][DirectoryList::PATH];
  2014. $productPath = $this->_mediaDirectory->getAbsolutePath($dirAddon . '/catalog/product');
  2015.  
  2016. foreach ($images as $sku => $files) {
  2017. foreach ($files as $path => $file) {
  2018. $images[$sku][$path]['hash'] = md5_file($productPath . $file['value']);
  2019. }
  2020. }
  2021. }
  2022.  
  2023. /**
  2024. * Prepare array with image states (visible or hidden from product page)
  2025. *
  2026. * @param array $rowData
  2027. * @return array
  2028. */
  2029. private function getImagesHiddenStates($rowData)
  2030. {
  2031. $statesArray = [];
  2032. $mappingArray = [
  2033. '_media_is_disabled' => '1'
  2034. ];
  2035.  
  2036. foreach ($mappingArray as $key => $value) {
  2037. if (isset($rowData[$key]) && strlen(trim($rowData[$key]))) {
  2038. $items = explode($this->getMultipleValueSeparator(), $rowData[$key]);
  2039.  
  2040. foreach ($items as $item) {
  2041. $statesArray[$item] = $value;
  2042. }
  2043. }
  2044. }
  2045.  
  2046. return $statesArray;
  2047. }
  2048.  
  2049. /**
  2050. * Resolve valid category ids from provided row data.
  2051. *
  2052. * @param array $rowData
  2053. * @return array
  2054. */
  2055. protected function processRowCategories($rowData)
  2056. {
  2057. $categoriesString = empty($rowData[self::COL_CATEGORY]) ? '' : $rowData[self::COL_CATEGORY];
  2058. $categoryIds = [];
  2059. if (!empty($categoriesString)) {
  2060. $categoryIds = $this->categoryProcessor->upsertCategories(
  2061. $categoriesString,
  2062. $this->getMultipleValueSeparator()
  2063. );
  2064. foreach ($this->categoryProcessor->getFailedCategories() as $error) {
  2065. $this->errorAggregator->addError(
  2066. AbstractEntity::ERROR_CODE_CATEGORY_NOT_VALID,
  2067. ProcessingError::ERROR_LEVEL_NOT_CRITICAL,
  2068. $rowData['rowNum'],
  2069. self::COL_CATEGORY,
  2070. __('Category "%1" has not been created.', $error['category'])
  2071. . ' ' . $error['exception']->getMessage()
  2072. );
  2073. }
  2074. } else {
  2075. $product = $this->retrieveProductBySku($rowData['sku']);
  2076. if ($product) {
  2077. $categoryIds = $product->getCategoryIds();
  2078. }
  2079. }
  2080. return $categoryIds;
  2081. }
  2082.  
  2083. /**
  2084. * Get product websites.
  2085. *
  2086. * @param string $productSku
  2087. * @return array
  2088. */
  2089. public function getProductWebsites($productSku)
  2090. {
  2091. return array_keys($this->websitesCache[$productSku]);
  2092. }
  2093.  
  2094. /**
  2095. * Retrieve product categories.
  2096. *
  2097. * @param string $productSku
  2098. * @return array
  2099. */
  2100. public function getProductCategories($productSku)
  2101. {
  2102. return array_keys($this->categoriesCache[$productSku]);
  2103. }
  2104.  
  2105. /**
  2106. * Get store id by code.
  2107. *
  2108. * @param string $storeCode
  2109. * @return array|int|null|string
  2110. */
  2111. public function getStoreIdByCode($storeCode)
  2112. {
  2113. if (empty($storeCode)) {
  2114. return self::SCOPE_DEFAULT;
  2115. }
  2116. return $this->storeResolver->getStoreCodeToId($storeCode);
  2117. }
  2118.  
  2119. /**
  2120. * Save product tier prices.
  2121. *
  2122. * @param array $tierPriceData
  2123. * @return $this
  2124. */
  2125. protected function _saveProductTierPrices(array $tierPriceData)
  2126. {
  2127. static $tableName = null;
  2128.  
  2129. if (!$tableName) {
  2130. $tableName = $this->_resourceFactory->create()->getTable('catalog_product_entity_tier_price');
  2131. }
  2132. if ($tierPriceData) {
  2133. $tierPriceIn = [];
  2134. $delProductId = [];
  2135.  
  2136. foreach ($tierPriceData as $delSku => $tierPriceRows) {
  2137. $productId = $this->skuProcessor->getNewSku($delSku)[$this->getProductEntityLinkField()];
  2138. $delProductId[] = $productId;
  2139.  
  2140. foreach ($tierPriceRows as $row) {
  2141. $row[$this->getProductEntityLinkField()] = $productId;
  2142. $tierPriceIn[] = $row;
  2143. }
  2144. }
  2145. if (Import::BEHAVIOR_APPEND != $this->getBehavior()) {
  2146. $this->_connection->delete(
  2147. $tableName,
  2148. $this->_connection->quoteInto("{$this->getProductEntityLinkField()} IN (?)", $delProductId)
  2149. );
  2150. }
  2151. if ($tierPriceIn) {
  2152. $this->_connection->insertOnDuplicate($tableName, $tierPriceIn, ['value']);
  2153. }
  2154. }
  2155. return $this;
  2156. }
  2157.  
  2158. /**
  2159. * Returns the import directory if specified or a default import directory (media/import).
  2160. *
  2161. * @return string
  2162. */
  2163. protected function getImportDir()
  2164. {
  2165. $dirConfig = DirectoryList::getDefaultConfig();
  2166. $dirAddon = $dirConfig[DirectoryList::MEDIA][DirectoryList::PATH];
  2167.  
  2168. if (!empty($this->_parameters[Import::FIELD_NAME_IMG_FILE_DIR])) {
  2169. $tmpPath = $this->_parameters[Import::FIELD_NAME_IMG_FILE_DIR];
  2170. } else {
  2171. $tmpPath = $dirAddon . '/' . $this->_mediaDirectory->getRelativePath('import');
  2172. }
  2173. return $tmpPath;
  2174. }
  2175.  
  2176. /**
  2177. * Returns an object for upload a media files
  2178. *
  2179. * @return \Magento\CatalogImportExport\Model\Import\Uploader
  2180. * @throws \Magento\Framework\Exception\LocalizedException
  2181. */
  2182. protected function _getUploader()
  2183. {
  2184. if ($this->_fileUploader === null) {
  2185. $this->_fileUploader = $this->_uploaderFactory->create();
  2186.  
  2187. $this->_fileUploader->init();
  2188.  
  2189. $dirConfig = DirectoryList::getDefaultConfig();
  2190. $dirAddon = $dirConfig[DirectoryList::MEDIA][DirectoryList::PATH];
  2191.  
  2192. $tmpPath = $this->getImportDir();
  2193.  
  2194. if (!$this->_fileUploader->setTmpDir($tmpPath)) {
  2195. throw new LocalizedException(
  2196. __('File directory \'%1\' is not readable.', $tmpPath)
  2197. );
  2198. }
  2199. $destinationDir = "catalog/product";
  2200. $destinationPath = $dirAddon . '/' . $this->_mediaDirectory->getRelativePath($destinationDir);
  2201.  
  2202. $this->_mediaDirectory->create($destinationPath);
  2203. if (!$this->_fileUploader->setDestDir($destinationPath)) {
  2204. throw new LocalizedException(
  2205. __('File directory \'%1\' is not writable.', $destinationPath)
  2206. );
  2207. }
  2208. }
  2209. return $this->_fileUploader;
  2210. }
  2211.  
  2212. /**
  2213. * Retrieve uploader.
  2214. *
  2215. * @return Uploader
  2216. * @throws \Magento\Framework\Exception\LocalizedException
  2217. */
  2218. public function getUploader()
  2219. {
  2220. return $this->_getUploader();
  2221. }
  2222.  
  2223. /**
  2224. * Uploading files into the "catalog/product" media folder.
  2225. *
  2226. * Return a new file name if the same file is already exists.
  2227. *
  2228. * @param string $fileName
  2229. * @param bool $renameFileOff [optional] boolean to pass.
  2230. * Default is false which will set not to rename the file after import.
  2231. * @return string
  2232. */
  2233. protected function uploadMediaFiles($fileName, $renameFileOff = false)
  2234. {
  2235. try {
  2236. $res = $this->_getUploader()->move($fileName, $renameFileOff);
  2237. return $res['file'];
  2238. } catch (\Exception $e) {
  2239. $this->_logger->critical($e);
  2240. return '';
  2241. }
  2242. }
  2243.  
  2244. /**
  2245. * Try to find file by it's path.
  2246. *
  2247. * @param string $fileName
  2248. * @return string
  2249. */
  2250. private function getSystemFile($fileName)
  2251. {
  2252. $filePath = 'catalog' . DIRECTORY_SEPARATOR . 'product' . DIRECTORY_SEPARATOR . $fileName;
  2253. /** @var \Magento\Framework\Filesystem\Directory\ReadInterface $read */
  2254. $read = $this->filesystem->getDirectoryRead(DirectoryList::MEDIA);
  2255.  
  2256. return $read->isExist($filePath) && $read->isReadable($filePath) ? $fileName : '';
  2257. }
  2258.  
  2259. /**
  2260. * Save product media gallery.
  2261. *
  2262. * @param array $mediaGalleryData
  2263. * @return $this
  2264. */
  2265. protected function _saveMediaGallery(array $mediaGalleryData)
  2266. {
  2267. if (empty($mediaGalleryData)) {
  2268. return $this;
  2269. }
  2270. $this->mediaProcessor->saveMediaGallery($mediaGalleryData);
  2271.  
  2272. return $this;
  2273. }
  2274.  
  2275. /**
  2276. * Save product websites.
  2277. *
  2278. * @param array $websiteData
  2279. * @return $this
  2280. */
  2281. protected function _saveProductWebsites(array $websiteData)
  2282. {
  2283. static $tableName = null;
  2284.  
  2285. if (!$tableName) {
  2286. $tableName = $this->_resourceFactory->create()->getProductWebsiteTable();
  2287. }
  2288. if ($websiteData) {
  2289. $websitesData = [];
  2290. $delProductId = [];
  2291.  
  2292. foreach ($websiteData as $delSku => $websites) {
  2293. $productId = $this->skuProcessor->getNewSku($delSku)['entity_id'];
  2294. $delProductId[] = $productId;
  2295.  
  2296. foreach (array_keys($websites) as $websiteId) {
  2297. $websitesData[] = ['product_id' => $productId, 'website_id' => $websiteId];
  2298. }
  2299. }
  2300. if (Import::BEHAVIOR_APPEND != $this->getBehavior()) {
  2301. $this->_connection->delete(
  2302. $tableName,
  2303. $this->_connection->quoteInto('product_id IN (?)', $delProductId)
  2304. );
  2305. }
  2306. if ($websitesData) {
  2307. $this->_connection->insertOnDuplicate($tableName, $websitesData);
  2308. }
  2309. }
  2310. return $this;
  2311. }
  2312.  
  2313. /**
  2314. * Stock item saving.
  2315. *
  2316. * @return $this
  2317. */
  2318. protected function _saveStockItem()
  2319. {
  2320. while ($bunch = $this->_dataSourceModel->getNextBunch()) {
  2321. $stockData = [];
  2322. $productIdsToReindex = [];
  2323. // Format bunch to stock data rows
  2324. foreach ($bunch as $rowNum => $rowData) {
  2325. if (!$this->isRowAllowedToImport($rowData, $rowNum)) {
  2326. continue;
  2327. }
  2328.  
  2329. $row = [];
  2330. $sku = $rowData[self::COL_SKU];
  2331. if ($this->skuProcessor->getNewSku($sku) !== null) {
  2332. $row = $this->formatStockDataForRow($rowData);
  2333. $productIdsToReindex[] = $row['product_id'];
  2334. }
  2335.  
  2336. if (!isset($stockData[$sku])) {
  2337. $stockData[$sku] = $row;
  2338. }
  2339. }
  2340.  
  2341. // Insert rows
  2342. if (!empty($stockData)) {
  2343. $this->stockItemImporter->import($stockData);
  2344. }
  2345.  
  2346. $this->reindexProducts($productIdsToReindex);
  2347. }
  2348. return $this;
  2349. }
  2350.  
  2351. /**
  2352. * Initiate product reindex by product ids
  2353. *
  2354. * @param array $productIdsToReindex
  2355. * @return void
  2356. */
  2357. private function reindexProducts($productIdsToReindex = [])
  2358. {
  2359. $indexer = $this->indexerRegistry->get('catalog_product_category');
  2360. if (is_array($productIdsToReindex) && count($productIdsToReindex) > 0 && !$indexer->isScheduled()) {
  2361. $indexer->reindexList($productIdsToReindex);
  2362. }
  2363. }
  2364.  
  2365. /**
  2366. * Retrieve attribute by code
  2367. *
  2368. * @param string $attrCode
  2369. * @return mixed
  2370. */
  2371. public function retrieveAttributeByCode($attrCode)
  2372. {
  2373. /** @var string $attrCode */
  2374. $attrCode = mb_strtolower($attrCode);
  2375.  
  2376. if (!isset($this->_attributeCache[$attrCode])) {
  2377. $this->_attributeCache[$attrCode] = $this->getResource()->getAttribute($attrCode);
  2378. }
  2379.  
  2380. return $this->_attributeCache[$attrCode];
  2381. }
  2382.  
  2383. /**
  2384. * Attribute set ID-to-name pairs getter.
  2385. *
  2386. * @return array
  2387. */
  2388. public function getAttrSetIdToName()
  2389. {
  2390. return $this->_attrSetIdToName;
  2391. }
  2392.  
  2393. /**
  2394. * DB connection getter.
  2395. *
  2396. * @return \Magento\Framework\DB\Adapter\AdapterInterface
  2397. */
  2398. public function getConnection()
  2399. {
  2400. return $this->_connection;
  2401. }
  2402.  
  2403. /**
  2404. * EAV entity type code getter.
  2405. *
  2406. * @abstract
  2407. * @return string
  2408. */
  2409. public function getEntityTypeCode()
  2410. {
  2411. return 'catalog_product';
  2412. }
  2413.  
  2414. /**
  2415. * New products SKU data.
  2416. *
  2417. * Returns array of new products data with SKU as key. All SKU keys are in lowercase for avoiding creation of
  2418. * new products with the same SKU in different letter cases.
  2419. *
  2420. * @param string $sku
  2421. * @return array
  2422. */
  2423. public function getNewSku($sku = null)
  2424. {
  2425. return $this->skuProcessor->getNewSku($sku);
  2426. }
  2427.  
  2428. /**
  2429. * Get next bunch of validated rows.
  2430. *
  2431. * @return array|null
  2432. */
  2433. public function getNextBunch()
  2434. {
  2435. return $this->_dataSourceModel->getNextBunch();
  2436. }
  2437.  
  2438. /**
  2439. * Existing products SKU getter.
  2440. *
  2441. * Returns array of existing products data with SKU as key. All SKU keys are in lowercase for avoiding creation of
  2442. * new products with the same SKU in different letter cases.
  2443. *
  2444. * @return array
  2445. */
  2446. public function getOldSku()
  2447. {
  2448. return $this->_oldSku;
  2449. }
  2450.  
  2451. /**
  2452. * Retrieve Category Processor
  2453. *
  2454. * @return \Magento\CatalogImportExport\Model\Import\Product\CategoryProcessor
  2455. */
  2456. public function getCategoryProcessor()
  2457. {
  2458. return $this->categoryProcessor;
  2459. }
  2460.  
  2461. /**
  2462. * Obtain scope of the row from row data.
  2463. *
  2464. * @param array $rowData
  2465. * @return int
  2466. */
  2467. public function getRowScope(array $rowData)
  2468. {
  2469. if (empty($rowData[self::COL_STORE])) {
  2470. return self::SCOPE_DEFAULT;
  2471. }
  2472. return self::SCOPE_STORE;
  2473. }
  2474.  
  2475. /**
  2476. * Validate data row.
  2477. *
  2478. * @param array $rowData
  2479. * @param int $rowNum
  2480. * @return boolean
  2481. * @SuppressWarnings(PHPMD.CyclomaticComplexity)
  2482. * @SuppressWarnings(PHPMD.NPathComplexity)
  2483. * @SuppressWarnings(PHPMD.ExcessiveMethodLength)
  2484. * @throws \Zend_Validate_Exception
  2485. */
  2486. public function validateRow(array $rowData, $rowNum)
  2487. {
  2488. if (isset($this->_validatedRows[$rowNum])) {
  2489. // check that row is already validated
  2490. return !$this->getErrorAggregator()->isRowInvalid($rowNum);
  2491. }
  2492. $this->_validatedRows[$rowNum] = true;
  2493.  
  2494. $rowScope = $this->getRowScope($rowData);
  2495. $sku = $rowData[self::COL_SKU];
  2496.  
  2497. // BEHAVIOR_DELETE and BEHAVIOR_REPLACE use specific validation logic
  2498. if (Import::BEHAVIOR_REPLACE == $this->getBehavior()) {
  2499. if (self::SCOPE_DEFAULT == $rowScope && !$this->isSkuExist($sku)) {
  2500. $this->skipRow($rowNum, ValidatorInterface::ERROR_SKU_NOT_FOUND_FOR_DELETE);
  2501. return false;
  2502. }
  2503. }
  2504. if (Import::BEHAVIOR_DELETE == $this->getBehavior()) {
  2505. if (self::SCOPE_DEFAULT == $rowScope && !$this->isSkuExist($sku)) {
  2506. $this->skipRow($rowNum, ValidatorInterface::ERROR_SKU_NOT_FOUND_FOR_DELETE);
  2507. return false;
  2508. }
  2509. return true;
  2510. }
  2511.  
  2512. // if product doesn't exist, need to throw critical error else all errors should be not critical.
  2513. $errorLevel = $this->getValidationErrorLevel($sku);
  2514.  
  2515. if (!$this->validator->isValid($rowData)) {
  2516. foreach ($this->validator->getMessages() as $message) {
  2517. $this->skipRow($rowNum, $message, $errorLevel, $this->validator->getInvalidAttribute());
  2518. }
  2519. }
  2520.  
  2521. if (null === $sku) {
  2522. $this->skipRow($rowNum, ValidatorInterface::ERROR_SKU_IS_EMPTY, $errorLevel);
  2523. } elseif (false === $sku) {
  2524. $this->skipRow($rowNum, ValidatorInterface::ERROR_ROW_IS_ORPHAN, $errorLevel);
  2525. } elseif (self::SCOPE_STORE == $rowScope
  2526. && !$this->storeResolver->getStoreCodeToId($rowData[self::COL_STORE])
  2527. ) {
  2528. $this->skipRow($rowNum, ValidatorInterface::ERROR_INVALID_STORE, $errorLevel);
  2529. }
  2530.  
  2531. // SKU is specified, row is SCOPE_DEFAULT, new product block begins
  2532. $this->_processedEntitiesCount++;
  2533.  
  2534. if ($this->isSkuExist($sku) && Import::BEHAVIOR_REPLACE !== $this->getBehavior()) {
  2535. // can we get all necessary data from existent DB product?
  2536. // check for supported type of existing product
  2537. if (isset($this->_productTypeModels[$this->getExistingSku($sku)['type_id']])) {
  2538. $this->skuProcessor->addNewSku(
  2539. $sku,
  2540. $this->prepareNewSkuData($sku)
  2541. );
  2542. } else {
  2543. $this->skipRow($rowNum, ValidatorInterface::ERROR_TYPE_UNSUPPORTED, $errorLevel);
  2544. }
  2545. } else {
  2546. // validate new product type and attribute set
  2547. if (!isset($rowData[self::COL_TYPE], $this->_productTypeModels[$rowData[self::COL_TYPE]])) {
  2548. $this->skipRow($rowNum, ValidatorInterface::ERROR_INVALID_TYPE, $errorLevel);
  2549. } elseif (!isset($rowData[self::COL_ATTR_SET], $this->_attrSetNameToId[$rowData[self::COL_ATTR_SET]])
  2550. ) {
  2551. $this->skipRow($rowNum, ValidatorInterface::ERROR_INVALID_ATTR_SET, $errorLevel);
  2552. } elseif ($this->skuProcessor->getNewSku($sku) === null) {
  2553. $this->skuProcessor->addNewSku(
  2554. $sku,
  2555. [
  2556. 'row_id' => null,
  2557. 'entity_id' => null,
  2558. 'type_id' => $rowData[self::COL_TYPE],
  2559. 'attr_set_id' => $this->_attrSetNameToId[$rowData[self::COL_ATTR_SET]],
  2560. 'attr_set_code' => $rowData[self::COL_ATTR_SET],
  2561. ]
  2562. );
  2563. }
  2564. }
  2565.  
  2566. if (!$this->getErrorAggregator()->isRowInvalid($rowNum)) {
  2567. $newSku = $this->skuProcessor->getNewSku($sku);
  2568. // set attribute set code into row data for followed attribute validation in type model
  2569. $rowData[self::COL_ATTR_SET] = $newSku['attr_set_code'];
  2570.  
  2571. /** @var \Magento\CatalogImportExport\Model\Import\Product\Type\AbstractType $productTypeValidator */
  2572. // isRowValid can add error to general errors pull if row is invalid
  2573. $productTypeValidator = $this->_productTypeModels[$newSku['type_id']];
  2574. $productTypeValidator->isRowValid(
  2575. $rowData,
  2576. $rowNum,
  2577. !($this->isSkuExist($sku) && Import::BEHAVIOR_REPLACE !== $this->getBehavior())
  2578. );
  2579. }
  2580. // validate custom options
  2581. $this->getOptionEntity()->validateRow($rowData, $rowNum);
  2582.  
  2583. if ($this->isNeedToValidateUrlKey($rowData)) {
  2584. $urlKey = strtolower($this->getUrlKey($rowData));
  2585. $storeCodes = empty($rowData[self::COL_STORE_VIEW_CODE])
  2586. ? array_flip($this->storeResolver->getStoreCodeToId())
  2587. : explode($this->getMultipleValueSeparator(), $rowData[self::COL_STORE_VIEW_CODE]);
  2588. foreach ($storeCodes as $storeCode) {
  2589. $storeId = $this->storeResolver->getStoreCodeToId($storeCode);
  2590. $productUrlSuffix = $this->getProductUrlSuffix($storeId);
  2591. $urlPath = $urlKey . $productUrlSuffix;
  2592. if (empty($this->urlKeys[$storeId][$urlPath])
  2593. || ($this->urlKeys[$storeId][$urlPath] == $sku)
  2594. ) {
  2595. $this->urlKeys[$storeId][$urlPath] = $sku;
  2596. $this->rowNumbers[$storeId][$urlPath] = $rowNum;
  2597. } else {
  2598. $message = sprintf(
  2599. $this->retrieveMessageTemplate(ValidatorInterface::ERROR_DUPLICATE_URL_KEY),
  2600. $urlKey,
  2601. $this->urlKeys[$storeId][$urlPath]
  2602. );
  2603. $this->addRowError(
  2604. ValidatorInterface::ERROR_DUPLICATE_URL_KEY,
  2605. $rowNum,
  2606. $rowData[self::COL_NAME],
  2607. $message,
  2608. ProcessingError::ERROR_LEVEL_NOT_CRITICAL
  2609. )
  2610. ->getErrorAggregator()
  2611. ->addRowToSkip($rowNum);
  2612. }
  2613. }
  2614. }
  2615.  
  2616. if (!empty($rowData['new_from_date']) && !empty($rowData['new_to_date'])
  2617. ) {
  2618. $newFromTimestamp = strtotime($this->dateTime->formatDate($rowData['new_from_date'], false));
  2619. $newToTimestamp = strtotime($this->dateTime->formatDate($rowData['new_to_date'], false));
  2620. if ($newFromTimestamp > $newToTimestamp) {
  2621. $this->skipRow(
  2622. $rowNum,
  2623. 'invalidNewToDateValue',
  2624. $errorLevel,
  2625. $rowData['new_to_date']
  2626. );
  2627. }
  2628. }
  2629.  
  2630. return !$this->getErrorAggregator()->isRowInvalid($rowNum);
  2631. }
  2632.  
  2633. /**
  2634. * Check if need to validate url key.
  2635. *
  2636. * @param array $rowData
  2637. * @return bool
  2638. */
  2639. private function isNeedToValidateUrlKey($rowData)
  2640. {
  2641. return (!empty($rowData[self::URL_KEY]) || !empty($rowData[self::COL_NAME]))
  2642. && (empty($rowData[self::COL_VISIBILITY])
  2643. || $rowData[self::COL_VISIBILITY]
  2644. !== (string)Visibility::getOptionArray()[Visibility::VISIBILITY_NOT_VISIBLE]);
  2645. }
  2646.  
  2647. /**
  2648. * Prepare new SKU data
  2649. *
  2650. * @param string $sku
  2651. * @return array
  2652. */
  2653. private function prepareNewSkuData($sku)
  2654. {
  2655. $data = [];
  2656. foreach ($this->getExistingSku($sku) as $key => $value) {
  2657. $data[$key] = $value;
  2658. }
  2659.  
  2660. $data['attr_set_code'] = $this->_attrSetIdToName[$this->getExistingSku($sku)['attr_set_id']];
  2661.  
  2662. return $data;
  2663. }
  2664.  
  2665. /**
  2666. * Parse attributes names and values string to array.
  2667. *
  2668. * @param array $rowData
  2669. *
  2670. * @return array
  2671. */
  2672. private function _parseAdditionalAttributes($rowData)
  2673. {
  2674. if (empty($rowData['additional_attributes'])) {
  2675. return $rowData;
  2676. }
  2677. $rowData = array_merge($rowData, $this->getAdditionalAttributes($rowData['additional_attributes']));
  2678. return $rowData;
  2679. }
  2680.  
  2681. /**
  2682. * Retrieves additional attributes in format:
  2683. * [
  2684. * code1 => value1,
  2685. * code2 => value2,
  2686. * ...
  2687. * codeN => valueN
  2688. * ]
  2689. *
  2690. * @param string $additionalAttributes Attributes data that will be parsed
  2691. * @return array
  2692. */
  2693. private function getAdditionalAttributes($additionalAttributes)
  2694. {
  2695. return empty($this->_parameters[Import::FIELDS_ENCLOSURE])
  2696. ? $this->parseAttributesWithoutWrappedValues($additionalAttributes)
  2697. : $this->parseAttributesWithWrappedValues($additionalAttributes);
  2698. }
  2699.  
  2700. /**
  2701. * Parses data and returns attributes in format:
  2702. * [
  2703. * code1 => value1,
  2704. * code2 => value2,
  2705. * ...
  2706. * codeN => valueN
  2707. * ]
  2708. *
  2709. * @param string $attributesData Attributes data that will be parsed. It keeps data in format:
  2710. * code=value,code2=value2...,codeN=valueN
  2711. * @return array
  2712. */
  2713. private function parseAttributesWithoutWrappedValues($attributesData)
  2714. {
  2715. $attributeNameValuePairs = explode($this->getMultipleValueSeparator(), $attributesData);
  2716. $preparedAttributes = [];
  2717. $code = '';
  2718. foreach ($attributeNameValuePairs as $attributeData) {
  2719. //process case when attribute has ImportModel::DEFAULT_GLOBAL_MULTI_VALUE_SEPARATOR inside its value
  2720. if (strpos($attributeData, self::PAIR_NAME_VALUE_SEPARATOR) === false) {
  2721. if (!$code) {
  2722. continue;
  2723. }
  2724. $preparedAttributes[$code] .= $this->getMultipleValueSeparator() . $attributeData;
  2725. continue;
  2726. }
  2727. list($code, $value) = explode(self::PAIR_NAME_VALUE_SEPARATOR, $attributeData, 2);
  2728. $code = mb_strtolower($code);
  2729. $preparedAttributes[$code] = $value;
  2730. }
  2731. return $preparedAttributes;
  2732. }
  2733.  
  2734. /**
  2735. * Parses data and returns attributes in format:
  2736. * [
  2737. * code1 => value1,
  2738. * code2 => value2,
  2739. * ...
  2740. * codeN => valueN
  2741. * ]
  2742. * All values have unescaped data except mupliselect attributes,
  2743. * they should be parsed in additional method - parseMultiselectValues()
  2744. *
  2745. * @param string $attributesData Attributes data that will be parsed. It keeps data in format:
  2746. * code="value",code2="value2"...,codeN="valueN"
  2747. * where every value is wrapped in double quotes. Double quotes as part of value should be duplicated.
  2748. * E.g. attribute with code 'attr_code' has value 'my"value'. This data should be stored as attr_code="my""value"
  2749. *
  2750. * @return array
  2751. */
  2752. private function parseAttributesWithWrappedValues($attributesData)
  2753. {
  2754. $attributes = [];
  2755. preg_match_all(
  2756. '~((?:[a-zA-Z0-9_])+)="((?:[^"]|""|"' . $this->getMultiLineSeparatorForRegexp() . '")+)"+~',
  2757. $attributesData,
  2758. $matches
  2759. );
  2760. foreach ($matches[1] as $i => $attributeCode) {
  2761. $attribute = $this->retrieveAttributeByCode($attributeCode);
  2762. $value = 'multiselect' != $attribute->getFrontendInput()
  2763. ? str_replace('""', '"', $matches[2][$i])
  2764. : '"' . $matches[2][$i] . '"';
  2765. $attributes[mb_strtolower($attributeCode)] = $value;
  2766. }
  2767. return $attributes;
  2768. }
  2769.  
  2770. /**
  2771. * Parse values of multiselect attributes depends on "Fields Enclosure" parameter
  2772. *
  2773. * @param string $values
  2774. * @param string $delimiter
  2775. * @return array
  2776. * @since 100.1.2
  2777. */
  2778. public function parseMultiselectValues($values, $delimiter = self::PSEUDO_MULTI_LINE_SEPARATOR)
  2779. {
  2780. if (empty($this->_parameters[Import::FIELDS_ENCLOSURE])) {
  2781. return explode($delimiter, $values);
  2782. }
  2783. if (preg_match_all('~"((?:[^"]|"")*)"~', $values, $matches)) {
  2784. return $values = array_map(function ($value) {
  2785. return str_replace('""', '"', $value);
  2786. }, $matches[1]);
  2787. }
  2788. return [$values];
  2789. }
  2790.  
  2791. /**
  2792. * Retrieves escaped PSEUDO_MULTI_LINE_SEPARATOR if it is metacharacter for regular expression
  2793. *
  2794. * @return string
  2795. */
  2796. private function getMultiLineSeparatorForRegexp()
  2797. {
  2798. if (!$this->multiLineSeparatorForRegexp) {
  2799. $this->multiLineSeparatorForRegexp = in_array(self::PSEUDO_MULTI_LINE_SEPARATOR, str_split('[\^$.|?*+(){}'))
  2800. ? '\\' . self::PSEUDO_MULTI_LINE_SEPARATOR
  2801. : self::PSEUDO_MULTI_LINE_SEPARATOR;
  2802. }
  2803. return $this->multiLineSeparatorForRegexp;
  2804. }
  2805.  
  2806. /**
  2807. * Set values in use_config_ fields.
  2808. *
  2809. * @param array $rowData
  2810. *
  2811. * @return array
  2812. */
  2813. private function _setStockUseConfigFieldsValues($rowData)
  2814. {
  2815. $useConfigFields = [];
  2816. foreach ($rowData as $key => $value) {
  2817. $useConfigName = $key === StockItemInterface::ENABLE_QTY_INCREMENTS
  2818. ? StockItemInterface::USE_CONFIG_ENABLE_QTY_INC
  2819. : self::INVENTORY_USE_CONFIG_PREFIX . $key;
  2820.  
  2821. if (isset($this->defaultStockData[$key])
  2822. && isset($this->defaultStockData[$useConfigName])
  2823. && !empty($value)
  2824. && empty($rowData[$useConfigName])
  2825. ) {
  2826. $useConfigFields[$useConfigName] = ($value == self::INVENTORY_USE_CONFIG) ? 1 : 0;
  2827. }
  2828. }
  2829. $rowData = array_merge($rowData, $useConfigFields);
  2830. return $rowData;
  2831. }
  2832.  
  2833. /**
  2834. * Custom fields mapping for changed purposes of fields and field names.
  2835. *
  2836. * @param array $rowData
  2837. *
  2838. * @return array
  2839. */
  2840. private function _customFieldsMapping($rowData)
  2841. {
  2842. foreach ($this->_fieldsMap as $systemFieldName => $fileFieldName) {
  2843. if (array_key_exists($fileFieldName, $rowData)) {
  2844. $rowData[$systemFieldName] = $rowData[$fileFieldName];
  2845. }
  2846. }
  2847.  
  2848. $rowData = $this->_parseAdditionalAttributes($rowData);
  2849.  
  2850. $rowData = $this->_setStockUseConfigFieldsValues($rowData);
  2851. if (array_key_exists('status', $rowData)
  2852. && $rowData['status'] != \Magento\Catalog\Model\Product\Attribute\Source\Status::STATUS_ENABLED
  2853. ) {
  2854. if ($rowData['status'] == 'yes') {
  2855. $rowData['status'] = \Magento\Catalog\Model\Product\Attribute\Source\Status::STATUS_ENABLED;
  2856. } elseif (!empty($rowData['status']) || $this->getRowScope($rowData) == self::SCOPE_DEFAULT) {
  2857. $rowData['status'] = \Magento\Catalog\Model\Product\Attribute\Source\Status::STATUS_DISABLED;
  2858. }
  2859. }
  2860. return $rowData;
  2861. }
  2862.  
  2863. /**
  2864. * Validate data rows and save bunches to DB
  2865. *
  2866. * @return $this|AbstractEntity
  2867. */
  2868. protected function _saveValidatedBunches()
  2869. {
  2870. $source = $this->_getSource();
  2871. $source->rewind();
  2872.  
  2873. while ($source->valid()) {
  2874. try {
  2875. $rowData = $source->current();
  2876. } catch (\InvalidArgumentException $e) {
  2877. $this->addRowError($e->getMessage(), $this->_processedRowsCount);
  2878. $this->_processedRowsCount++;
  2879. $source->next();
  2880. continue;
  2881. }
  2882.  
  2883. $rowData = $this->_customFieldsMapping($rowData);
  2884.  
  2885. $this->validateRow($rowData, $source->key());
  2886.  
  2887. $source->next();
  2888. }
  2889. $this->checkUrlKeyDuplicates();
  2890. $this->getOptionEntity()->validateAmbiguousData();
  2891. return parent::_saveValidatedBunches();
  2892. }
  2893.  
  2894. /**
  2895. * Check that url_keys are not assigned to other products in DB
  2896. *
  2897. * @return void
  2898. * @since 100.0.3
  2899. */
  2900. protected function checkUrlKeyDuplicates()
  2901. {
  2902. $resource = $this->getResource();
  2903. foreach ($this->urlKeys as $storeId => $urlKeys) {
  2904. $urlKeyDuplicates = $this->_connection->fetchAssoc(
  2905. $this->_connection->select()->from(
  2906. ['url_rewrite' => $resource->getTable('url_rewrite')],
  2907. ['request_path', 'store_id']
  2908. )->joinLeft(
  2909. ['cpe' => $resource->getTable('catalog_product_entity')],
  2910. "cpe.entity_id = url_rewrite.entity_id"
  2911. )->where('request_path IN (?)', array_keys($urlKeys))
  2912. ->where('store_id IN (?)', $storeId)
  2913. ->where('cpe.sku not in (?)', array_values($urlKeys))
  2914. );
  2915. foreach ($urlKeyDuplicates as $entityData) {
  2916. $rowNum = $this->rowNumbers[$entityData['store_id']][$entityData['request_path']];
  2917. $message = sprintf(
  2918. $this->retrieveMessageTemplate(ValidatorInterface::ERROR_DUPLICATE_URL_KEY),
  2919. $entityData['request_path'],
  2920. $entityData['sku']
  2921. );
  2922. $this->addRowError(ValidatorInterface::ERROR_DUPLICATE_URL_KEY, $rowNum, 'url_key', $message);
  2923. }
  2924. }
  2925. }
  2926.  
  2927. /**
  2928. * Retrieve product rewrite suffix for store
  2929. *
  2930. * @param int $storeId
  2931. * @return string
  2932. * @since 100.0.3
  2933. */
  2934. protected function getProductUrlSuffix($storeId = null)
  2935. {
  2936. if (!isset($this->productUrlSuffix[$storeId])) {
  2937. $this->productUrlSuffix[$storeId] = $this->scopeConfig->getValue(
  2938. \Magento\CatalogUrlRewrite\Model\ProductUrlPathGenerator::XML_PATH_PRODUCT_URL_SUFFIX,
  2939. \Magento\Store\Model\ScopeInterface::SCOPE_STORE,
  2940. $storeId
  2941. );
  2942. }
  2943. return $this->productUrlSuffix[$storeId];
  2944. }
  2945.  
  2946. /**
  2947. * Retrieve url key from provided row data.
  2948. *
  2949. * @param array $rowData
  2950. * @return string
  2951. *
  2952. * @since 100.0.3
  2953. */
  2954. protected function getUrlKey($rowData)
  2955. {
  2956. if (!empty($rowData[self::URL_KEY])) {
  2957. return $this->productUrl->formatUrlKey($rowData[self::URL_KEY]);
  2958. }
  2959.  
  2960. if (!empty($rowData[self::COL_NAME])) {
  2961. return $this->productUrl->formatUrlKey($rowData[self::COL_NAME]);
  2962. }
  2963.  
  2964. return '';
  2965. }
  2966.  
  2967. /**
  2968. * Retrieve resource.
  2969. *
  2970. * @return Proxy\Product\ResourceModel
  2971. *
  2972. * @since 100.0.3
  2973. */
  2974. protected function getResource()
  2975. {
  2976. if (!$this->_resource) {
  2977. $this->_resource = $this->_resourceFactory->create();
  2978. }
  2979. return $this->_resource;
  2980. }
  2981.  
  2982. /**
  2983. * Whether a url key is needed to be change.
  2984. *
  2985. * @param array $rowData
  2986. * @return bool
  2987. */
  2988. private function isNeedToChangeUrlKey(array $rowData): bool
  2989. {
  2990. $urlKey = $this->getUrlKey($rowData);
  2991. $productExists = $this->isSkuExist($rowData[self::COL_SKU]);
  2992. $markedToEraseUrlKey = isset($rowData[self::URL_KEY]);
  2993. // The product isn't new and the url key index wasn't marked for change.
  2994. if (!$urlKey && $productExists && !$markedToEraseUrlKey) {
  2995. // Seems there is no need to change the url key
  2996. return false;
  2997. }
  2998.  
  2999. return true;
  3000. }
  3001.  
  3002. /**
  3003. * Get product entity link field
  3004. *
  3005. * @return string
  3006. */
  3007. private function getProductEntityLinkField()
  3008. {
  3009. if (!$this->productEntityLinkField) {
  3010. $this->productEntityLinkField = $this->getMetadataPool()
  3011. ->getMetadata(\Magento\Catalog\Api\Data\ProductInterface::class)
  3012. ->getLinkField();
  3013. }
  3014. return $this->productEntityLinkField;
  3015. }
  3016.  
  3017. /**
  3018. * Get product entity identifier field
  3019. *
  3020. * @return string
  3021. */
  3022. private function getProductIdentifierField()
  3023. {
  3024. if (!$this->productEntityIdentifierField) {
  3025. $this->productEntityIdentifierField = $this->getMetadataPool()
  3026. ->getMetadata(\Magento\Catalog\Api\Data\ProductInterface::class)
  3027. ->getIdentifierField();
  3028. }
  3029. return $this->productEntityIdentifierField;
  3030. }
  3031.  
  3032. /**
  3033. * Update media gallery labels
  3034. *
  3035. * @param array $labels
  3036. * @return void
  3037. */
  3038. private function updateMediaGalleryLabels(array $labels)
  3039. {
  3040. if (!empty($labels)) {
  3041. $this->mediaProcessor->updateMediaGalleryLabels($labels);
  3042. }
  3043. }
  3044.  
  3045. /**
  3046. * Update 'disabled' field for media gallery entity
  3047. *
  3048. * @param array $images
  3049. * @return $this
  3050. */
  3051. private function updateMediaGalleryVisibility(array $images)
  3052. {
  3053. if (!empty($images)) {
  3054. $this->mediaProcessor->updateMediaGalleryVisibility($images);
  3055. }
  3056.  
  3057. return $this;
  3058. }
  3059.  
  3060. /**
  3061. * Parse values from multiple attributes fields
  3062. *
  3063. * @param string $labelRow
  3064. * @return array
  3065. */
  3066. private function parseMultipleValues($labelRow)
  3067. {
  3068. return $this->parseMultiselectValues(
  3069. $labelRow,
  3070. $this->getMultipleValueSeparator()
  3071. );
  3072. }
  3073.  
  3074. /**
  3075. * Check if product exists for specified SKU
  3076. *
  3077. * @param string $sku
  3078. * @return bool
  3079. */
  3080. private function isSkuExist($sku)
  3081. {
  3082. $sku = strtolower($sku);
  3083. return isset($this->_oldSku[$sku]);
  3084. }
  3085.  
  3086. /**
  3087. * Get existing product data for specified SKU
  3088. *
  3089. * @param string $sku
  3090. * @return array
  3091. */
  3092. private function getExistingSku($sku)
  3093. {
  3094. return $this->_oldSku[strtolower($sku)];
  3095. }
  3096.  
  3097. /**
  3098. * Format row data to DB compatible values.
  3099. *
  3100. * @param array $rowData
  3101. * @return array
  3102. */
  3103. private function formatStockDataForRow(array $rowData): array
  3104. {
  3105. $sku = $rowData[self::COL_SKU];
  3106. $row['product_id'] = $this->skuProcessor->getNewSku($sku)['entity_id'];
  3107. $row['website_id'] = $this->stockConfiguration->getDefaultScopeId();
  3108. $row['stock_id'] = $this->stockRegistry->getStock($row['website_id'])->getStockId();
  3109.  
  3110. $stockItemDo = $this->stockRegistry->getStockItem($row['product_id'], $row['website_id']);
  3111. $existStockData = $stockItemDo->getData();
  3112.  
  3113. $row = array_merge(
  3114. $this->defaultStockData,
  3115. array_intersect_key($existStockData, $this->defaultStockData),
  3116. array_intersect_key($rowData, $this->defaultStockData),
  3117. $row
  3118. );
  3119.  
  3120. if ($this->stockConfiguration->isQty($this->skuProcessor->getNewSku($sku)['type_id'])) {
  3121. $stockItemDo->setData($row);
  3122. $row['is_in_stock'] = $row['is_in_stock'] ?? $this->stockStateProvider->verifyStock($stockItemDo);
  3123. if ($this->stockStateProvider->verifyNotification($stockItemDo)) {
  3124. $date = $this->dateTimeFactory->create('now', new \DateTimeZone('UTC'));
  3125. $row['low_stock_date'] = $date->format(DateTime::DATETIME_PHP_FORMAT);
  3126. }
  3127. $row['stock_status_changed_auto'] = (int)!$this->stockStateProvider->verifyStock($stockItemDo);
  3128. } else {
  3129. $row['qty'] = 0;
  3130. }
  3131.  
  3132. return $row;
  3133. }
  3134.  
  3135. /**
  3136. * Retrieve product by sku.
  3137. *
  3138. * @param string $sku
  3139. * @return \Magento\Catalog\Api\Data\ProductInterface|null
  3140. */
  3141. private function retrieveProductBySku($sku)
  3142. {
  3143. try {
  3144. $product = $this->productRepository->get($sku);
  3145. } catch (NoSuchEntityException $e) {
  3146. return null;
  3147. }
  3148. return $product;
  3149. }
  3150.  
  3151. /**
  3152. * Add row as skipped
  3153. *
  3154. * @param int $rowNum
  3155. * @param string $errorCode Error code or simply column name
  3156. * @param string $errorLevel error level
  3157. * @param string|null $colName optional column name
  3158. * @return $this
  3159. */
  3160. private function skipRow(
  3161. $rowNum,
  3162. string $errorCode,
  3163. string $errorLevel = ProcessingError::ERROR_LEVEL_NOT_CRITICAL,
  3164. $colName = null
  3165. ): self {
  3166. $this->addRowError($errorCode, $rowNum, $colName, null, $errorLevel);
  3167. $this->getErrorAggregator()
  3168. ->addRowToSkip($rowNum);
  3169. return $this;
  3170. }
  3171.  
  3172. /**
  3173. * Returns errorLevel for validation
  3174. *
  3175. * @param string $sku
  3176. * @return string
  3177. */
  3178. private function getValidationErrorLevel($sku): string
  3179. {
  3180. return (!$this->isSkuExist($sku) && Import::BEHAVIOR_REPLACE !== $this->getBehavior())
  3181. ? ProcessingError::ERROR_LEVEL_CRITICAL
  3182. : ProcessingError::ERROR_LEVEL_NOT_CRITICAL;
  3183. }
  3184. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement