Skip to content

Commit c87537d

Browse files
Leonid PoluyanovAlex Bomko
authored andcommitted
MAGETWO-47001: Error on import products - validation not works
1 parent 794b23b commit c87537d

File tree

10 files changed

+317
-33
lines changed

10 files changed

+317
-33
lines changed

app/code/Magento/CatalogImportExport/Model/Import/Product.php

Lines changed: 125 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,11 @@ class Product extends \Magento\ImportExport\Model\Import\Entity\AbstractEntity
137137
*/
138138
const INVENTORY_USE_CONFIG_PREFIX = 'use_config_';
139139

140+
/**
141+
* Url key attribute code
142+
*/
143+
const URL_KEY = 'url_key';
144+
140145
/**
141146
* Attribute cache
142147
*
@@ -233,6 +238,7 @@ class Product extends \Magento\ImportExport\Model\Import\Entity\AbstractEntity
233238
ValidatorInterface::ERROR_MEDIA_PATH_NOT_ACCESSIBLE => 'Imported resource (image) does not exist in the local media storage',
234239
ValidatorInterface::ERROR_MEDIA_URL_NOT_ACCESSIBLE => 'Imported resource (image) could not be downloaded from external resource due to timeout or access permissions',
235240
ValidatorInterface::ERROR_INVALID_WEIGHT => 'Product weight is invalid',
241+
ValidatorInterface::ERROR_DUPLICATE_URL_KEY => 'Specified url key is already exist',
236242
];
237243

238244
/**
@@ -502,12 +508,24 @@ class Product extends \Magento\ImportExport\Model\Import\Entity\AbstractEntity
502508
*/
503509
protected $categoryProcessor;
504510

511+
/** @var \Magento\Framework\App\Config\ScopeConfigInterface */
512+
protected $scopeConfig;
513+
514+
/** @var \Magento\Catalog\Model\Product\Url */
515+
protected $productUrl;
516+
505517
/** @var array */
506518
protected $websitesCache = [];
507519

508520
/** @var array */
509521
protected $categoriesCache = [];
510522

523+
/** @var array */
524+
protected $productUrlSuffix = [];
525+
526+
/** @var array */
527+
protected $productUrlKeys = [];
528+
511529
/**
512530
* Instance of product tax class processor.
513531
*
@@ -561,6 +579,12 @@ class Product extends \Magento\ImportExport\Model\Import\Entity\AbstractEntity
561579
*/
562580
protected $cachedImages = null;
563581

582+
/** @var array */
583+
protected $urlKeys = [];
584+
585+
/** @var array */
586+
protected $rowNumbers = [];
587+
564588
/**
565589
* @param \Magento\Framework\Json\Helper\Data $jsonHelper
566590
* @param \Magento\ImportExport\Helper\Data $importExportData
@@ -596,6 +620,7 @@ class Product extends \Magento\ImportExport\Model\Import\Entity\AbstractEntity
596620
* @param ObjectRelationProcessor $objectRelationProcessor
597621
* @param TransactionManagerInterface $transactionManager
598622
* @param Product\TaxClassProcessor $taxClassProcessor
623+
* @param \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig
599624
* @param array $data
600625
* @throws \Magento\Framework\Exception\LocalizedException
601626
*
@@ -636,6 +661,8 @@ public function __construct(
636661
ObjectRelationProcessor $objectRelationProcessor,
637662
TransactionManagerInterface $transactionManager,
638663
Product\TaxClassProcessor $taxClassProcessor,
664+
\Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig,
665+
\Magento\Catalog\Model\Product\Url $productUrl,
639666
array $data = []
640667
) {
641668
$this->_eventManager = $eventManager;
@@ -663,6 +690,8 @@ public function __construct(
663690
$this->objectRelationProcessor = $objectRelationProcessor;
664691
$this->transactionManager = $transactionManager;
665692
$this->taxClassProcessor = $taxClassProcessor;
693+
$this->scopeConfig = $scopeConfig;
694+
$this->productUrl = $productUrl;
666695
parent::__construct(
667696
$jsonHelper,
668697
$importExportData,
@@ -1288,13 +1317,16 @@ protected function getExistingImages($images)
12881317
if (!$productMediaGalleryTableName) {
12891318
$productMediaGalleryTableName = $resource->getTable('catalog_product_entity_media_gallery');
12901319
}
1320+
$linkField = $this->metadataPool
1321+
->getMetadata(\Magento\Catalog\Api\Data\ProductInterface::class)
1322+
->getLinkField();
12911323
$select = $this->_connection->select()->from(
12921324
['mg' => $resource->getTable('catalog_product_entity_media_gallery')],
12931325
['value' => 'mg.value']
12941326
)->joinLeft(
12951327
['mgvte' => $resource->getTable('catalog_product_entity_media_gallery_value_to_entity')],
12961328
'(mg.value_id = mgvte.value_id)',
1297-
['entity_id' => 'mgvte.entity_id']
1329+
[$linkField => 'mgvte.' . $linkField]
12981330
)->where(
12991331
'mg.value IN(?)',
13001332
$images
@@ -1999,11 +2031,8 @@ protected function _saveStockItem()
19992031
*/
20002032
public function retrieveAttributeByCode($attrCode)
20012033
{
2002-
if (!$this->_resource) {
2003-
$this->_resource = $this->_resourceFactory->create();
2004-
}
20052034
if (!isset($this->_attributeCache[$attrCode])) {
2006-
$this->_attributeCache[$attrCode] = $this->_resource->getAttribute($attrCode);
2035+
$this->_attributeCache[$attrCode] = $this->getResource()->getAttribute($attrCode);
20072036
}
20082037
return $this->_attributeCache[$attrCode];
20092038
}
@@ -2214,7 +2243,25 @@ public function validateRow(array $rowData, $rowNum)
22142243
}
22152244
// validate custom options
22162245
$this->getOptionEntity()->validateRow($rowData, $rowNum);
2217-
2246+
if (!empty($rowData[self::URL_KEY]) || !empty($rowData[self::COL_NAME])) {
2247+
$urlKey = $this->getUrlKey($rowData);
2248+
$storeCodes = empty($rowData[self::COL_STORE_VIEW_CODE])
2249+
? array_flip($this->storeResolver->getStoreCodeToId())
2250+
: explode($this->getMultipleValueSeparator(), $rowData[self::COL_STORE_VIEW_CODE]);
2251+
foreach ($storeCodes as $storeCode) {
2252+
$storeId = $this->storeResolver->getStoreCodeToId($storeCode);
2253+
$productUrlSuffix = $this->getProductUrlSuffix($storeId);
2254+
$urlPath = $urlKey . $productUrlSuffix;
2255+
if (empty($this->urlKeys[$storeId][$urlPath])
2256+
|| ($this->urlKeys[$storeId][$urlPath] == $rowData[self::COL_SKU])
2257+
) {
2258+
$this->urlKeys[$storeId][$urlPath] = $rowData[self::COL_SKU];
2259+
$this->rowNumbers[$storeId][$urlPath] = $rowNum;
2260+
} else {
2261+
$this->addRowError(ValidatorInterface::ERROR_DUPLICATE_URL_KEY, $rowNum);
2262+
}
2263+
}
2264+
}
22182265
return !$this->getErrorAggregator()->isRowInvalid($rowNum);
22192266
}
22202267

@@ -2319,7 +2366,79 @@ protected function _saveValidatedBunches()
23192366
$this->validateRow($rowData, $source->key());
23202367
$source->next();
23212368
}
2369+
$this->checkUrlKeyDuplicates();
23222370
$this->getOptionEntity()->validateAmbiguousData();
23232371
return parent::_saveValidatedBunches();
23242372
}
2373+
2374+
/**
2375+
* Check that url_keys are not assigned to other products in DB
2376+
*
2377+
* @return void
2378+
*/
2379+
protected function checkUrlKeyDuplicates()
2380+
{
2381+
$resource = $this->getResource();
2382+
foreach ($this->urlKeys as $storeId => $urlKeys) {
2383+
$urlKeyDuplicates = $this->_connection->fetchAssoc(
2384+
$this->_connection->select()->from(
2385+
['url_rewrite' => $resource->getTable('url_rewrite')],
2386+
['request_path', 'store_id']
2387+
)->joinLeft(
2388+
['cpe' => $resource->getTable('catalog_product_entity')],
2389+
"cpe.entity_id = url_rewrite.entity_id"
2390+
)->where('request_path IN (?)', array_keys($urlKeys))
2391+
->where('store_id IN (?)', $storeId)
2392+
->where('cpe.sku not in (?)', array_values($urlKeys))
2393+
);
2394+
foreach ($urlKeyDuplicates as $urlKey => $entityData) {
2395+
$rowNum = $this->rowNumbers[$entityData['store_id']][$entityData['request_path']];
2396+
$this->addRowError(ValidatorInterface::ERROR_DUPLICATE_URL_KEY, $rowNum);
2397+
}
2398+
}
2399+
}
2400+
2401+
/**
2402+
* Retrieve product rewrite suffix for store
2403+
*
2404+
* @param int $storeId
2405+
* @return string
2406+
*/
2407+
protected function getProductUrlSuffix($storeId = null)
2408+
{
2409+
if (!isset($this->productUrlSuffix[$storeId])) {
2410+
$this->productUrlSuffix[$storeId] = $this->scopeConfig->getValue(
2411+
\Magento\CatalogUrlRewrite\Model\ProductUrlPathGenerator::XML_PATH_PRODUCT_URL_SUFFIX,
2412+
\Magento\Store\Model\ScopeInterface::SCOPE_STORE,
2413+
$storeId
2414+
);
2415+
}
2416+
return $this->productUrlSuffix[$storeId];
2417+
}
2418+
2419+
/**
2420+
* @param array $rowData
2421+
* @return string
2422+
*/
2423+
protected function getUrlKey($rowData)
2424+
{
2425+
if (!empty($rowData[self::URL_KEY])) {
2426+
$this->productUrlKeys[$rowData[self::COL_SKU]] = $rowData[self::URL_KEY];
2427+
}
2428+
$urlKey = !empty($this->productUrlKeys[$rowData[self::COL_SKU]])
2429+
? $this->productUrlKeys[$rowData[self::COL_SKU]]
2430+
: $this->productUrl->formatUrlKey($rowData[self::COL_NAME]);
2431+
return $urlKey;
2432+
}
2433+
2434+
/**
2435+
* @return Proxy\Product\ResourceModel
2436+
*/
2437+
protected function getResource()
2438+
{
2439+
if (!$this->_resource) {
2440+
$this->_resource = $this->_resourceFactory->create();
2441+
}
2442+
return $this->_resource;
2443+
}
23252444
}

app/code/Magento/CatalogImportExport/Model/Import/Product/RowValidatorInterface.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,8 @@ interface RowValidatorInterface extends \Magento\Framework\Validator\ValidatorIn
7575

7676
const ERROR_MEDIA_PATH_NOT_ACCESSIBLE = 'mediaPathNotAvailable';
7777

78+
const ERROR_DUPLICATE_URL_KEY = 'duplicatedUrlKey';
79+
7880
/**
7981
* Value that means all entities (e.g. websites, groups etc.)
8082
*/

app/code/Magento/CatalogImportExport/Test/Unit/Model/Import/ProductTest.php

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,12 @@ class ProductTest extends \Magento\ImportExport\Test\Unit\Model\Import\AbstractI
154154
*/
155155
protected $errorAggregator;
156156

157+
/** @var \Magento\Framework\App\Config\ScopeConfigInterface|\PHPUnit_Framework_MockObject_MockObject*/
158+
protected $scopeConfig;
159+
160+
/** @var \Magento\Catalog\Model\Product\Url|\PHPUnit_Framework_MockObject_MockObject*/
161+
protected $productUrl;
162+
157163
/**
158164
* @SuppressWarnings(PHPMD.ExcessiveMethodLength)
159165
*/
@@ -315,6 +321,14 @@ protected function setUp()
315321
->disableOriginalConstructor()
316322
->getMock();
317323

324+
$this->scopeConfig = $this->getMockBuilder('\Magento\Framework\App\Config\ScopeConfigInterface')
325+
->disableOriginalConstructor()
326+
->getMockForAbstractClass();
327+
328+
$this->productUrl = $this->getMockBuilder('\Magento\Catalog\Model\Product\Url')
329+
->disableOriginalConstructor()
330+
->getMock();
331+
318332
$this->errorAggregator = $this->getErrorAggregatorObject();
319333

320334
$this->data = [];
@@ -360,6 +374,8 @@ protected function setUp()
360374
$this->objectRelationProcessor,
361375
$this->transactionManager,
362376
$this->taxClassProcessor,
377+
$this->scopeConfig,
378+
$this->productUrl,
363379
$this->data
364380
);
365381
}

app/code/Magento/CatalogImportExport/composer.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
"require": {
55
"php": "~5.5.0|~5.6.0|~7.0.0",
66
"magento/module-catalog": "100.0.*",
7+
"magento/module-catalog-url-rewrite": "100.0.*",
78
"magento/module-eav": "100.0.*",
89
"magento/module-import-export": "100.0.*",
910
"magento/module-store": "100.0.*",

dev/tests/integration/testsuite/Magento/Catalog/Model/ResourceModel/_files/product_simple.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,5 +42,5 @@
4242
->setVisibility(\Magento\Catalog\Model\Product\Visibility::VISIBILITY_BOTH)
4343
->setStatus(\Magento\Catalog\Model\Product\Attribute\Source\Status::STATUS_ENABLED)
4444
->setUrlKey('url-key')
45-
->setUrlPath('url-key.html')
45+
->setUrlPath('url-key')
4646
->save();

0 commit comments

Comments
 (0)