33 * Copyright © Magento, Inc. All rights reserved.
44 * See COPYING.txt for license details.
55 */
6+
67namespace Magento \CatalogImportExport \Model \Import ;
78
89use Magento \Catalog \Model \Config as CatalogConfig ;
910use Magento \Catalog \Model \Product \Visibility ;
1011use Magento \CatalogImportExport \Model \Import \Product \MediaGalleryProcessor ;
1112use Magento \CatalogImportExport \Model \Import \Product \RowValidatorInterface as ValidatorInterface ;
13+ use Magento \CatalogImportExport \Model \StockItemImporterInterface ;
1214use Magento \CatalogInventory \Api \Data \StockItemInterface ;
1315use Magento \Framework \App \Filesystem \DirectoryList ;
1416use Magento \Framework \Exception \LocalizedException ;
@@ -638,6 +640,13 @@ class Product extends \Magento\ImportExport\Model\Import\Entity\AbstractEntity
638640 */
639641 private $ _logger ;
640642
643+ /**
644+ * "Duplicate multiselect values" error array key
645+ *
646+ * @var string
647+ */
648+ private static $ errorDuplicateMultiselectValues = 'duplicatedMultiselectValues ' ;
649+
641650 /**
642651 * {@inheritdoc}
643652 */
@@ -767,7 +776,6 @@ class Product extends \Magento\ImportExport\Model\Import\Entity\AbstractEntity
767776 * @param CatalogConfig $catalogConfig
768777 * @param MediaGalleryProcessor $mediaProcessor
769778 * @param ProductRepositoryInterface|null $productRepository
770- * @throws \Magento\Framework\Exception\LocalizedException
771779 *
772780 * @SuppressWarnings(PHPMD.ExcessiveParameterList)
773781 */
@@ -857,18 +865,18 @@ public function __construct(
857865 $ string ,
858866 $ errorAggregator
859867 );
860- $ this ->_optionEntity = isset (
861- $ data ['option_entity ' ]
862- ) ? $ data ['option_entity ' ] : $ optionFactory ->create (
863- ['data ' => ['product_entity ' => $ this ]]
864- );
868+ $ this ->_optionEntity = $ data ['option_entity ' ]
869+ ?? $ optionFactory ->create (['data ' => ['product_entity ' => $ this ]]);
865870
866871 $ this ->_initAttributeSets ()
867872 ->_initTypeModels ()
868873 ->_initSkus ();
869874 $ this ->validator ->init ($ this );
870875 $ this ->productRepository = $ productRepository ?? \Magento \Framework \App \ObjectManager::getInstance ()
871876 ->get (ProductRepositoryInterface::class);
877+
878+ $ errorMessageText = __ ('Value for multiselect attribute %s contains duplicated values ' );
879+ $ this ->_messageTemplates [self ::$ errorDuplicateMultiselectValues ] = $ errorMessageText ;
872880 }
873881
874882 /**
@@ -884,7 +892,7 @@ public function isAttributeValid($attrCode, array $attrParams, array $rowData, $
884892 {
885893 if (!$ this ->validator ->isAttributeValid ($ attrCode , $ attrParams , $ rowData )) {
886894 foreach ($ this ->validator ->getMessages () as $ message ) {
887- $ this ->addRowError ( $ message , $ rowNum , $ attrCode );
895+ $ this ->skipRow ( $ rowNum , $ message , ProcessingError:: ERROR_LEVEL_NOT_CRITICAL , $ attrCode );
888896 }
889897 return false ;
890898 }
@@ -1592,7 +1600,7 @@ protected function _saveProducts()
15921600 if (!empty ($ rowData [self ::URL_KEY ])) {
15931601 // If url_key column and its value were in the CSV file
15941602 $ rowData [self ::URL_KEY ] = $ urlKey ;
1595- } else if ($ this ->isNeedToChangeUrlKey ($ rowData )) {
1603+ } elseif ($ this ->isNeedToChangeUrlKey ($ rowData )) {
15961604 // If url_key column was empty or even not declared in the CSV file but by the rules it is need to
15971605 // be setteed. In case when url_key is generating from name column we have to ensure that the bunch
15981606 // of products will pass for the event with url_key column.
@@ -1604,7 +1612,9 @@ protected function _saveProducts()
16041612 if (null === $ rowSku ) {
16051613 $ this ->getErrorAggregator ()->addRowToSkip ($ rowNum );
16061614 continue ;
1607- } elseif (self ::SCOPE_STORE == $ rowScope ) {
1615+ }
1616+
1617+ if (self ::SCOPE_STORE == $ rowScope ) {
16081618 // set necessary data from SCOPE_DEFAULT row
16091619 $ rowData [self ::COL_TYPE ] = $ this ->skuProcessor ->getNewSku ($ rowSku )['type_id ' ];
16101620 $ rowData ['attribute_set_id ' ] = $ this ->skuProcessor ->getNewSku ($ rowSku )['attr_set_id ' ];
@@ -1740,13 +1750,7 @@ protected function _saveProducts()
17401750 $ uploadedImages [$ columnImage ] = $ uploadedFile ;
17411751 } else {
17421752 unset($ rowData [$ column ]);
1743- $ this ->addRowError (
1744- ValidatorInterface::ERROR_MEDIA_URL_NOT_ACCESSIBLE ,
1745- $ rowNum ,
1746- null ,
1747- null ,
1748- ProcessingError::ERROR_LEVEL_NOT_CRITICAL
1749- );
1753+ $ this ->skipRow ($ rowNum , ValidatorInterface::ERROR_MEDIA_URL_NOT_ACCESSIBLE );
17501754 }
17511755 } else {
17521756 $ uploadedFile = $ uploadedImages [$ columnImage ];
@@ -2380,32 +2384,35 @@ public function validateRow(array $rowData, $rowNum)
23802384 // BEHAVIOR_DELETE and BEHAVIOR_REPLACE use specific validation logic
23812385 if (Import::BEHAVIOR_REPLACE == $ this ->getBehavior ()) {
23822386 if (self ::SCOPE_DEFAULT == $ rowScope && !$ this ->isSkuExist ($ sku )) {
2383- $ this ->addRowError ( ValidatorInterface::ERROR_SKU_NOT_FOUND_FOR_DELETE , $ rowNum );
2387+ $ this ->skipRow ( $ rowNum , ValidatorInterface::ERROR_SKU_NOT_FOUND_FOR_DELETE );
23842388 return false ;
23852389 }
23862390 }
23872391 if (Import::BEHAVIOR_DELETE == $ this ->getBehavior ()) {
23882392 if (self ::SCOPE_DEFAULT == $ rowScope && !$ this ->isSkuExist ($ sku )) {
2389- $ this ->addRowError ( ValidatorInterface::ERROR_SKU_NOT_FOUND_FOR_DELETE , $ rowNum );
2393+ $ this ->skipRow ( $ rowNum , ValidatorInterface::ERROR_SKU_NOT_FOUND_FOR_DELETE );
23902394 return false ;
23912395 }
23922396 return true ;
23932397 }
23942398
2399+ // if product doesn't exist, need to throw critical error else all errors should be not critical.
2400+ $ errorLevel = $ this ->getValidationErrorLevel ($ sku );
2401+
23952402 if (!$ this ->validator ->isValid ($ rowData )) {
23962403 foreach ($ this ->validator ->getMessages () as $ message ) {
2397- $ this ->addRowError ( $ message , $ rowNum , $ this ->validator ->getInvalidAttribute ());
2404+ $ this ->skipRow ( $ rowNum , $ message , $ errorLevel , $ this ->validator ->getInvalidAttribute ());
23982405 }
23992406 }
24002407
24012408 if (null === $ sku ) {
2402- $ this ->addRowError ( ValidatorInterface::ERROR_SKU_IS_EMPTY , $ rowNum );
2409+ $ this ->skipRow ( $ rowNum , ValidatorInterface::ERROR_SKU_IS_EMPTY , $ errorLevel );
24032410 } elseif (false === $ sku ) {
2404- $ this ->addRowError ( ValidatorInterface::ERROR_ROW_IS_ORPHAN , $ rowNum );
2411+ $ this ->skipRow ( $ rowNum , ValidatorInterface::ERROR_ROW_IS_ORPHAN , $ errorLevel );
24052412 } elseif (self ::SCOPE_STORE == $ rowScope
24062413 && !$ this ->storeResolver ->getStoreCodeToId ($ rowData [self ::COL_STORE ])
24072414 ) {
2408- $ this ->addRowError ( ValidatorInterface::ERROR_INVALID_STORE , $ rowNum );
2415+ $ this ->skipRow ( $ rowNum , ValidatorInterface::ERROR_INVALID_STORE , $ errorLevel );
24092416 }
24102417
24112418 // SKU is specified, row is SCOPE_DEFAULT, new product block begins
@@ -2420,19 +2427,15 @@ public function validateRow(array $rowData, $rowNum)
24202427 $ this ->prepareNewSkuData ($ sku )
24212428 );
24222429 } else {
2423- $ this ->addRowError ( ValidatorInterface::ERROR_TYPE_UNSUPPORTED , $ rowNum );
2430+ $ this ->skipRow ( $ rowNum , ValidatorInterface::ERROR_TYPE_UNSUPPORTED , $ errorLevel );
24242431 }
24252432 } else {
24262433 // validate new product type and attribute set
2427- if (!isset ($ rowData [self ::COL_TYPE ]) || !isset ($ this ->_productTypeModels [$ rowData [self ::COL_TYPE ]])) {
2428- $ this ->addRowError (ValidatorInterface::ERROR_INVALID_TYPE , $ rowNum );
2429- } elseif (!isset (
2430- $ rowData [self ::COL_ATTR_SET ]
2431- ) || !isset (
2432- $ this ->_attrSetNameToId [$ rowData [self ::COL_ATTR_SET ]]
2433- )
2434+ if (!isset ($ rowData [self ::COL_TYPE ], $ this ->_productTypeModels [$ rowData [self ::COL_TYPE ]])) {
2435+ $ this ->skipRow ($ rowNum , ValidatorInterface::ERROR_INVALID_TYPE , $ errorLevel );
2436+ } elseif (!isset ($ rowData [self ::COL_ATTR_SET ], $ this ->_attrSetNameToId [$ rowData [self ::COL_ATTR_SET ]])
24342437 ) {
2435- $ this ->addRowError ( ValidatorInterface::ERROR_INVALID_ATTR_SET , $ rowNum );
2438+ $ this ->skipRow ( $ rowNum , ValidatorInterface::ERROR_INVALID_ATTR_SET , $ errorLevel );
24362439 } elseif (is_null ($ this ->skuProcessor ->getNewSku ($ sku ))) {
24372440 $ this ->skuProcessor ->addNewSku (
24382441 $ sku ,
@@ -2488,8 +2491,11 @@ public function validateRow(array $rowData, $rowNum)
24882491 ValidatorInterface::ERROR_DUPLICATE_URL_KEY ,
24892492 $ rowNum ,
24902493 $ rowData [self ::COL_NAME ],
2491- $ message
2492- );
2494+ $ message ,
2495+ $ errorLevel
2496+ )
2497+ ->getErrorAggregator ()
2498+ ->addRowToSkip ($ rowNum );
24932499 }
24942500 }
24952501 }
@@ -2499,9 +2505,10 @@ public function validateRow(array $rowData, $rowNum)
24992505 $ newFromTimestamp = strtotime ($ this ->dateTime ->formatDate ($ rowData [self ::COL_NEW_FROM_DATE ], false ));
25002506 $ newToTimestamp = strtotime ($ this ->dateTime ->formatDate ($ rowData [self ::COL_NEW_TO_DATE ], false ));
25012507 if ($ newFromTimestamp > $ newToTimestamp ) {
2502- $ this ->addRowError (
2503- ValidatorInterface::ERROR_NEW_TO_DATE ,
2508+ $ this ->skipRow (
25042509 $ rowNum ,
2510+ ValidatorInterface::ERROR_NEW_TO_DATE ,
2511+ $ errorLevel ,
25052512 $ rowData [self ::COL_NEW_TO_DATE ]
25062513 );
25072514 }
@@ -3029,4 +3036,39 @@ private function retrieveProductBySku(string $sku)
30293036
30303037 return $ product ;
30313038 }
3039+
3040+ /**
3041+ * Add row as skipped
3042+ *
3043+ * @param int $rowNum
3044+ * @param string $errorCode Error code or simply column name
3045+ * @param string $errorLevel error level
3046+ * @param string|null $colName optional column name
3047+ * @return $this
3048+ */
3049+ private function skipRow (
3050+ int $ rowNum ,
3051+ string $ errorCode ,
3052+ string $ errorLevel = ProcessingError::ERROR_LEVEL_NOT_CRITICAL ,
3053+ $ colName = null
3054+ ): self {
3055+ $ this ->addRowError ($ errorCode , $ rowNum , $ colName , null , $ errorLevel );
3056+ $ this ->getErrorAggregator ()
3057+ ->addRowToSkip ($ rowNum );
3058+
3059+ return $ this ;
3060+ }
3061+
3062+ /**
3063+ * Returns errorLevel for validation
3064+ *
3065+ * @param string|bool|null $sku
3066+ * @return string
3067+ */
3068+ private function getValidationErrorLevel ($ sku ): string
3069+ {
3070+ return (!$ this ->isSkuExist ($ sku ) && Import::BEHAVIOR_REPLACE !== $ this ->getBehavior ())
3071+ ? ProcessingError::ERROR_LEVEL_CRITICAL
3072+ : ProcessingError::ERROR_LEVEL_NOT_CRITICAL ;
3073+ }
30323074}
0 commit comments