Skip to content

Commit dccad97

Browse files
committed
Merge branch '2.0' of github.corp.magento.com:magento2/magento2ce into MAGETWO-47544
2 parents b6baf09 + 6711be5 commit dccad97

File tree

19 files changed

+464
-335
lines changed

19 files changed

+464
-335
lines changed

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

Lines changed: 121 additions & 5 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,
@@ -1999,11 +2028,8 @@ protected function _saveStockItem()
19992028
*/
20002029
public function retrieveAttributeByCode($attrCode)
20012030
{
2002-
if (!$this->_resource) {
2003-
$this->_resource = $this->_resourceFactory->create();
2004-
}
20052031
if (!isset($this->_attributeCache[$attrCode])) {
2006-
$this->_attributeCache[$attrCode] = $this->_resource->getAttribute($attrCode);
2032+
$this->_attributeCache[$attrCode] = $this->getResource()->getAttribute($attrCode);
20072033
}
20082034
return $this->_attributeCache[$attrCode];
20092035
}
@@ -2214,7 +2240,25 @@ public function validateRow(array $rowData, $rowNum)
22142240
}
22152241
// validate custom options
22162242
$this->getOptionEntity()->validateRow($rowData, $rowNum);
2217-
2243+
if (!empty($rowData[self::URL_KEY]) || !empty($rowData[self::COL_NAME])) {
2244+
$urlKey = $this->getUrlKey($rowData);
2245+
$storeCodes = empty($rowData[self::COL_STORE_VIEW_CODE])
2246+
? array_flip($this->storeResolver->getStoreCodeToId())
2247+
: explode($this->getMultipleValueSeparator(), $rowData[self::COL_STORE_VIEW_CODE]);
2248+
foreach ($storeCodes as $storeCode) {
2249+
$storeId = $this->storeResolver->getStoreCodeToId($storeCode);
2250+
$productUrlSuffix = $this->getProductUrlSuffix($storeId);
2251+
$urlPath = $urlKey . $productUrlSuffix;
2252+
if (empty($this->urlKeys[$storeId][$urlPath])
2253+
|| ($this->urlKeys[$storeId][$urlPath] == $rowData[self::COL_SKU])
2254+
) {
2255+
$this->urlKeys[$storeId][$urlPath] = $rowData[self::COL_SKU];
2256+
$this->rowNumbers[$storeId][$urlPath] = $rowNum;
2257+
} else {
2258+
$this->addRowError(ValidatorInterface::ERROR_DUPLICATE_URL_KEY, $rowNum);
2259+
}
2260+
}
2261+
}
22182262
return !$this->getErrorAggregator()->isRowInvalid($rowNum);
22192263
}
22202264

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

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.*",

app/code/Magento/CatalogUrlRewrite/Observer/AfterImportDataObserver.php

Lines changed: 4 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -66,9 +66,6 @@ class AfterImportDataObserver implements ObserverInterface
6666
/** @var \Magento\Catalog\Model\ProductFactory $catalogProductFactory */
6767
protected $catalogProductFactory;
6868

69-
/** @var int */
70-
protected $urlKeyAttribute;
71-
7269
/** @var array */
7370
protected $acceptableCategories;
7471

@@ -78,9 +75,6 @@ class AfterImportDataObserver implements ObserverInterface
7875
/** @var array */
7976
protected $websitesToStoreIds;
8077

81-
/** @var array */
82-
protected $entityStoresToCheckOverridden = [];
83-
8478
/** @var array */
8579
protected $storesCache = [];
8680

@@ -100,10 +94,8 @@ class AfterImportDataObserver implements ObserverInterface
10094

10195
/**
10296
* @param \Magento\Catalog\Model\ProductFactory $catalogProductFactory
103-
* @param \Magento\Eav\Model\Config $eavConfig
10497
* @param \Magento\CatalogUrlRewrite\Model\ObjectRegistryFactory $objectRegistryFactory
10598
* @param \Magento\CatalogUrlRewrite\Model\ProductUrlPathGenerator $productUrlPathGenerator
106-
* @param \Magento\Framework\App\ResourceConnection $resource
10799
* @param \Magento\CatalogUrlRewrite\Service\V1\StoreViewService $storeViewService
108100
* @param \Magento\Store\Model\StoreManagerInterface $storeManager
109101
* @param UrlPersistInterface $urlPersist
@@ -114,10 +106,8 @@ class AfterImportDataObserver implements ObserverInterface
114106
*/
115107
public function __construct(
116108
\Magento\Catalog\Model\ProductFactory $catalogProductFactory,
117-
\Magento\Eav\Model\Config $eavConfig,
118109
\Magento\CatalogUrlRewrite\Model\ObjectRegistryFactory $objectRegistryFactory,
119110
\Magento\CatalogUrlRewrite\Model\ProductUrlPathGenerator $productUrlPathGenerator,
120-
\Magento\Framework\App\ResourceConnection $resource,
121111
\Magento\CatalogUrlRewrite\Service\V1\StoreViewService $storeViewService,
122112
\Magento\Store\Model\StoreManagerInterface $storeManager,
123113
UrlPersistInterface $urlPersist,
@@ -131,16 +121,6 @@ public function __construct(
131121
$this->storeViewService = $storeViewService;
132122
$this->storeManager = $storeManager;
133123
$this->urlRewriteFactory = $urlRewriteFactory;
134-
$attribute = $eavConfig->getAttribute(Product::ENTITY, self::URL_KEY_ATTRIBUTE_CODE);
135-
if (!$attribute) {
136-
throw new \InvalidArgumentException(sprintf(
137-
'Cannot retrieve attribute for entity type "%s"',
138-
Product::ENTITY
139-
));
140-
}
141-
$this->connection = $resource->getConnection();
142-
$this->urlKeyAttributeId = $attribute->getId();
143-
$this->urlKeyAttributeBackendTable = $attribute->getBackendTable();
144124
$this->urlFinder = $urlFinder;
145125
}
146126

@@ -180,6 +160,10 @@ protected function _populateForUrlGeneration($rowData)
180160
if (empty($newSku) || !isset($newSku['entity_id'])) {
181161
return null;
182162
}
163+
if ($this->import->getRowScope($rowData) == ImportProduct::SCOPE_STORE
164+
&& empty($rowData[self::URL_KEY_ATTRIBUTE_CODE])) {
165+
return null;
166+
}
183167
$rowData['entity_id'] = $newSku['entity_id'];
184168

185169
$product = $this->catalogProductFactory->create();
@@ -254,8 +238,6 @@ protected function populateGlobalProduct($product)
254238
$this->storesCache[$storeId] = true;
255239
if (!$this->isGlobalScope($storeId)) {
256240
$this->addProductToImport($product, $storeId);
257-
$this->entityStoresToCheckOverridden[] = $this->connection->quoteInto('(store_id = ?', $storeId)
258-
. $this->connection->quoteInto(' AND entity_id = ?)', $product->getId());
259241
}
260242
}
261243
}
@@ -269,8 +251,6 @@ protected function populateGlobalProduct($product)
269251
*/
270252
protected function generateUrls()
271253
{
272-
$this->cleanOverriddenUrlKey();
273-
274254
/**
275255
* @var $urls \Magento\UrlRewrite\Service\V1\Data\UrlRewrite[]
276256
*/
@@ -291,25 +271,6 @@ protected function generateUrls()
291271
return $result;
292272
}
293273

294-
/**
295-
* @return $this
296-
*/
297-
protected function cleanOverriddenUrlKey()
298-
{
299-
if (empty($this->entityStoresToCheckOverridden)) {
300-
return $this;
301-
}
302-
$select = $this->connection->select()
303-
->from($this->urlKeyAttributeBackendTable, ['store_id', 'entity_id'])
304-
->where('attribute_id = ?', $this->urlKeyAttributeId)
305-
->where(implode(' OR ', $this->entityStoresToCheckOverridden));
306-
$entityStoresToClean = $this->connection->fetchAll($select);
307-
foreach ($entityStoresToClean as $entityStore) {
308-
unset($this->products[$entityStore['entity_id']][$entityStore['store_id']]);
309-
}
310-
return $this;
311-
}
312-
313274
/**
314275
* Check is global scope
315276
*

0 commit comments

Comments
 (0)