Skip to content

Commit 5441188

Browse files
Merge remote-tracking branch 'origin/MAGETWO-69717-Import' into Okapis-PR
2 parents bde1749 + 2cd9353 commit 5441188

File tree

3 files changed

+179
-40
lines changed
  • app/code/Magento/CatalogImportExport
  • dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import

3 files changed

+179
-40
lines changed

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

Lines changed: 103 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
use Magento\Framework\App\ResourceConnection;
1313
use Magento\ImportExport\Model\Import\ErrorProcessing\ProcessingErrorAggregatorInterface;
1414
use Magento\Catalog\Api\Data\ProductInterface;
15+
use Magento\Catalog\Model\ResourceModel\Product\Option\Value\Collection as ProductOptionValueCollection;
16+
use Magento\Catalog\Model\ResourceModel\Product\Option\Value\CollectionFactory as ProductOptionValueCollectionFactory;
1517

1618
/**
1719
* Entity class which provide possibility to import product custom options
@@ -320,6 +322,16 @@ class Option extends \Magento\ImportExport\Model\Import\Entity\AbstractEntity
320322
*/
321323
private $productEntityIdentifierField;
322324

325+
/**
326+
* @var ProductOptionValueCollectionFactory
327+
*/
328+
private $productOptionValueCollectionFactory;
329+
330+
/**
331+
* @var array
332+
*/
333+
private $optionTypeTitles;
334+
323335
/**
324336
* @param \Magento\ImportExport\Model\ResourceModel\Import\Data $importData
325337
* @param ResourceConnection $resource
@@ -333,6 +345,7 @@ class Option extends \Magento\ImportExport\Model\Import\Entity\AbstractEntity
333345
* @param \Magento\Framework\Stdlib\DateTime\TimezoneInterface $dateTime
334346
* @param ProcessingErrorAggregatorInterface $errorAggregator
335347
* @param array $data
348+
* @param ProductOptionValueCollectionFactory $productOptionValueCollectionFactory
336349
* @throws \Magento\Framework\Exception\LocalizedException
337350
*
338351
* @SuppressWarnings(PHPMD.ExcessiveParameterList)
@@ -349,7 +362,8 @@ public function __construct(
349362
\Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig,
350363
\Magento\Framework\Stdlib\DateTime\TimezoneInterface $dateTime,
351364
ProcessingErrorAggregatorInterface $errorAggregator,
352-
array $data = []
365+
array $data = [],
366+
ProductOptionValueCollectionFactory $productOptionValueCollectionFactory = null
353367
) {
354368
$this->_resource = $resource;
355369
$this->_catalogData = $catalogData;
@@ -360,6 +374,8 @@ public function __construct(
360374
$this->_colIteratorFactory = $colIteratorFactory;
361375
$this->_scopeConfig = $scopeConfig;
362376
$this->dateTime = $dateTime;
377+
$this->productOptionValueCollectionFactory = $productOptionValueCollectionFactory
378+
?: \Magento\Framework\App\ObjectManager::getInstance()->get(ProductOptionValueCollectionFactory::class);
363379

364380
if (isset($data['connection'])) {
365381
$this->_connection = $data['connection'];
@@ -1121,7 +1137,7 @@ private function processOptionRow($name, $optionRow)
11211137
if (isset($optionRow['price'])) {
11221138
$percent_suffix = '';
11231139
if (isset($optionRow['price_type']) && $optionRow['price_type'] == 'percent') {
1124-
$percent_suffix = '%';
1140+
$percent_suffix = '%';
11251141
}
11261142
$result[self::COLUMN_ROW_PRICE] = $optionRow['price'] . $percent_suffix;
11271143
}
@@ -1230,6 +1246,7 @@ protected function _importData()
12301246
if ($this->_isReadyForSaving($options, $titles, $typeValues)) {
12311247
if ($this->getBehavior() == \Magento\ImportExport\Model\Import::BEHAVIOR_APPEND) {
12321248
$this->_compareOptionsWithExisting($options, $titles, $prices, $typeValues);
1249+
$this->restoreOriginalOptionTypeIds($typeValues, $typePrices, $typeTitles);
12331250
}
12341251

12351252
$this->_saveOptions(
@@ -1296,12 +1313,12 @@ protected function _collectOptionMainData(
12961313
$optionData = $this->_getOptionData($rowData, $this->_rowProductId, $nextOptionId, $this->_rowType);
12971314

12981315
if (!$this->_isRowHasSpecificType(
1299-
$this->_rowType
1300-
) && ($priceData = $this->_getPriceData(
1301-
$rowData,
1302-
$nextOptionId,
1303-
$this->_rowType
1304-
))
1316+
$this->_rowType
1317+
) && ($priceData = $this->_getPriceData(
1318+
$rowData,
1319+
$nextOptionId,
1320+
$this->_rowType
1321+
))
13051322
) {
13061323
$prices[$nextOptionId] = $priceData;
13071324
}
@@ -1434,6 +1451,68 @@ protected function _compareOptionsWithExisting(array &$options, array &$titles,
14341451
return $this;
14351452
}
14361453

1454+
/**
1455+
* Restore original IDs for existing option types.
1456+
*
1457+
* Warning: arguments are modified by reference
1458+
*
1459+
* @param array $typeValues
1460+
* @param array $typePrices
1461+
* @param array $typeTitles
1462+
* @return void
1463+
*/
1464+
private function restoreOriginalOptionTypeIds(array &$typeValues, array &$typePrices, array &$typeTitles)
1465+
{
1466+
foreach ($typeValues as $optionId => &$optionTypes) {
1467+
foreach ($optionTypes as &$optionType) {
1468+
$optionTypeId = $optionType['option_type_id'];
1469+
foreach ($typeTitles[$optionTypeId] as $storeId => $optionTypeTitle) {
1470+
$existingTypeId = $this->getExistingOptionTypeId($optionId, $storeId, $optionTypeTitle);
1471+
if ($existingTypeId) {
1472+
$optionType['option_type_id'] = $existingTypeId;
1473+
$typeTitles[$existingTypeId] = $typeTitles[$optionTypeId];
1474+
unset($typeTitles[$optionTypeId]);
1475+
$typePrices[$existingTypeId] = $typePrices[$optionTypeId];
1476+
unset($typePrices[$optionTypeId]);
1477+
// If option type titles match at least in one store, consider current option type as existing
1478+
break;
1479+
}
1480+
}
1481+
}
1482+
}
1483+
}
1484+
1485+
/**
1486+
* Identify ID of the provided option type by its title in the specified store.
1487+
*
1488+
* @param int $optionId
1489+
* @param int $storeId
1490+
* @param string $optionTypeTitle
1491+
* @return int|null
1492+
*/
1493+
private function getExistingOptionTypeId($optionId, $storeId, $optionTypeTitle)
1494+
{
1495+
if (!isset($this->optionTypeTitles[$storeId])) {
1496+
/** @var ProductOptionValueCollection $optionTypeCollection */
1497+
$optionTypeCollection = $this->productOptionValueCollectionFactory->create();
1498+
$optionTypeCollection->addTitleToResult($storeId);
1499+
/** @var \Magento\Catalog\Model\Product\Option\Value $type */
1500+
foreach ($optionTypeCollection as $type) {
1501+
$this->optionTypeTitles[$storeId][$type->getOptionId()][$type->getId()] = $type->getTitle();
1502+
}
1503+
}
1504+
if (isset($this->optionTypeTitles[$storeId][$optionId])
1505+
&& is_array($this->optionTypeTitles[$storeId][$optionId])
1506+
) {
1507+
foreach ($this->optionTypeTitles[$storeId][$optionId] as $optionTypeId => $currentTypeTitle) {
1508+
if ($optionTypeTitle === $currentTypeTitle) {
1509+
return $optionTypeId;
1510+
}
1511+
}
1512+
}
1513+
return null;
1514+
}
1515+
14371516
/**
14381517
* Parse required data from current row and store to class internal variables some data
14391518
* for underlying dependent rows
@@ -1565,13 +1644,13 @@ protected function _getOptionData(array $rowData, $productId, $optionId, $type)
15651644
protected function _getPriceData(array $rowData, $optionId, $type)
15661645
{
15671646
if (in_array(
1568-
'price',
1569-
$this->_specificTypes[$type]
1570-
) && isset(
1571-
$rowData[self::COLUMN_PREFIX . 'price']
1572-
) && strlen(
1573-
$rowData[self::COLUMN_PREFIX . 'price']
1574-
) > 0
1647+
'price',
1648+
$this->_specificTypes[$type]
1649+
) && isset(
1650+
$rowData[self::COLUMN_PREFIX . 'price']
1651+
) && strlen(
1652+
$rowData[self::COLUMN_PREFIX . 'price']
1653+
) > 0
15751654
) {
15761655
$priceData = [
15771656
'option_id' => $optionId,
@@ -1821,13 +1900,17 @@ protected function _updateProducts(array $data)
18211900
* @return array
18221901
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
18231902
*/
1824-
protected function _parseCustomOptions($rowData)
1903+
protected function _parseCustomOptions($rowData)
18251904
{
18261905
$beforeOptionValueSkuDelimiter = ';';
18271906
if (empty($rowData['custom_options'])) {
18281907
return $rowData;
18291908
}
1830-
$rowData['custom_options'] = str_replace($beforeOptionValueSkuDelimiter, $this->_productEntity->getMultipleValueSeparator(), $rowData['custom_options']);
1909+
$rowData['custom_options'] = str_replace(
1910+
$beforeOptionValueSkuDelimiter,
1911+
$this->_productEntity->getMultipleValueSeparator(),
1912+
$rowData['custom_options']
1913+
);
18311914
$options = [];
18321915
$optionValues = explode(Product::PSEUDO_MULTI_LINE_SEPARATOR, $rowData['custom_options']);
18331916
$k = 0;
@@ -1839,7 +1922,7 @@ protected function _parseCustomOptions($rowData)
18391922
if (!empty($nameAndValue)) {
18401923
$value = isset($nameAndValue[1]) ? $nameAndValue[1] : '';
18411924
$value = trim($value);
1842-
$fieldName = trim($nameAndValue[0]);
1925+
$fieldName = trim($nameAndValue[0]);
18431926
if ($value && ($fieldName == 'name')) {
18441927
if ($name != $value) {
18451928
$name = $value;
@@ -1878,7 +1961,7 @@ private function getProductEntityLinkField()
18781961
{
18791962
if (!$this->productEntityLinkField) {
18801963
$this->productEntityLinkField = $this->getMetadataPool()
1881-
->getMetadata(\Magento\Catalog\Api\Data\ProductInterface::class)
1964+
->getMetadata(ProductInterface::class)
18821965
->getLinkField();
18831966
}
18841967
return $this->productEntityLinkField;
@@ -1893,7 +1976,7 @@ private function getProductIdentifierField()
18931976
{
18941977
if (!$this->productEntityIdentifierField) {
18951978
$this->productEntityIdentifierField = $this->getMetadataPool()
1896-
->getMetadata(\Magento\Catalog\Api\Data\ProductInterface::class)
1979+
->getMetadata(ProductInterface::class)
18971980
->getIdentifierField();
18981981
}
18991982
return $this->productEntityIdentifierField;

app/code/Magento/CatalogImportExport/Test/Unit/Model/Import/Product/Type/OptionTest.php

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,8 @@ class OptionTest extends \Magento\ImportExport\Test\Unit\Model\Import\AbstractIm
215215

216216
/**
217217
* Init entity adapter model
218+
*
219+
* @SuppressWarnings(PHPMD.ExcessiveMethodLength)
218220
*/
219221
protected function setUp()
220222
{
@@ -249,6 +251,17 @@ protected function setUp()
249251
$entityMetadataMock->expects($this->any())
250252
->method('getLinkField')
251253
->willReturn('entity_id');
254+
$optionValueCollectionFactoryMock = $this->createMock(
255+
\Magento\Catalog\Model\ResourceModel\Product\Option\Value\CollectionFactory::class
256+
);
257+
$optionValueCollectionMock = $this->createPartialMock(
258+
\Magento\Catalog\Model\ResourceModel\Product\Option\Value\Collection::class,
259+
['getIterator', 'addTitleToResult']
260+
);
261+
$optionValueCollectionMock->expects($this->any())->method('getIterator')
262+
->willReturn($this->createMock(\Traversable::class));
263+
$optionValueCollectionFactoryMock->expects($this->any())
264+
->method('create')->willReturn($optionValueCollectionMock);
252265
$modelClassArgs = [
253266
$this->createMock(\Magento\ImportExport\Model\ResourceModel\Import\Data::class),
254267
$this->createMock(\Magento\Framework\App\ResourceConnection::class),
@@ -263,7 +276,8 @@ protected function setUp()
263276
$this->createMock(
264277
\Magento\ImportExport\Model\Import\ErrorProcessing\ProcessingErrorAggregatorInterface::class
265278
),
266-
$this->_getModelDependencies($addExpectations, $deleteBehavior, $doubleOptions)
279+
$this->_getModelDependencies($addExpectations, $deleteBehavior, $doubleOptions),
280+
$optionValueCollectionFactoryMock
267281
];
268282

269283
$modelClassName = \Magento\CatalogImportExport\Model\Import\Product\Option::class;

dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/ProductTest.php

Lines changed: 61 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
namespace Magento\CatalogImportExport\Model\Import;
1616

1717
use Magento\Catalog\Api\Data\ProductInterface;
18+
use Magento\Catalog\Api\ProductCustomOptionRepositoryInterface;
1819
use Magento\Catalog\Api\ProductRepositoryInterface;
1920
use Magento\Catalog\Model\Category;
2021
use Magento\CatalogImportExport\Model\Import\Product\RowValidatorInterface;
@@ -256,26 +257,13 @@ public function testStockState()
256257
public function testSaveCustomOptions($importFile, $sku)
257258
{
258259
$pathToFile = __DIR__ . '/_files/' . $importFile;
259-
$filesystem = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()
260-
->create(\Magento\Framework\Filesystem::class);
261-
$directory = $filesystem->getDirectoryWrite(DirectoryList::ROOT);
262-
263-
$source = $this->objectManager->create(
264-
\Magento\ImportExport\Model\Import\Source\Csv::class,
265-
[
266-
'file' => $pathToFile,
267-
'directory' => $directory
268-
]
269-
);
270-
$errors = $this->_model->setParameters(
271-
['behavior' => \Magento\ImportExport\Model\Import::BEHAVIOR_APPEND, 'entity' => 'catalog_product']
272-
)->setSource(
273-
$source
274-
)->validateData();
260+
$importModel = $this->createImportModel($pathToFile);
261+
$errors = $importModel->validateData();
275262

276263
$this->assertTrue($errors->getErrorsCount() == 0);
277-
$this->_model->importData();
264+
$importModel->importData();
278265

266+
/** @var \Magento\Catalog\Api\ProductRepositoryInterface $productRepository */
279267
$productRepository = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create(
280268
\Magento\Catalog\Api\ProductRepositoryInterface::class
281269
);
@@ -316,11 +304,14 @@ public function testSaveCustomOptions($importFile, $sku)
316304
}
317305
$this->assertTrue($elementExist, 'Element must exist.');
318306
}
307+
308+
// Make sure that after importing existing options again, option IDs and option value IDs are not changed
309+
$customOptionValues = $this->getCustomOptionValues($sku);
310+
$this->createImportModel($pathToFile)->importData();
311+
$this->assertEquals($customOptionValues, $this->getCustomOptionValues($sku));
319312
}
320313

321314
/**
322-
* Data provider for test 'testSaveCustomOptionsDuplicate'
323-
*
324315
* @return array
325316
*/
326317
public function getBehaviorDataProvider()
@@ -337,6 +328,57 @@ public function getBehaviorDataProvider()
337328
];
338329
}
339330

331+
/**
332+
* @param string $pathToFile
333+
* @param string $behavior
334+
* @return \Magento\CatalogImportExport\Model\Import\Product
335+
*/
336+
private function createImportModel($pathToFile, $behavior = \Magento\ImportExport\Model\Import::BEHAVIOR_APPEND)
337+
{
338+
$filesystem = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()
339+
->create(\Magento\Framework\Filesystem::class);
340+
$directory = $filesystem->getDirectoryWrite(DirectoryList::ROOT);
341+
342+
/** @var \Magento\ImportExport\Model\Import\Source\Csv $source */
343+
$source = $this->objectManager->create(
344+
\Magento\ImportExport\Model\Import\Source\Csv::class,
345+
[
346+
'file' => $pathToFile,
347+
'directory' => $directory
348+
]
349+
);
350+
351+
/** @var \Magento\CatalogImportExport\Model\Import\Product $importModel */
352+
$importModel = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create(
353+
\Magento\CatalogImportExport\Model\Import\Product::class
354+
);
355+
$importModel->setParameters(['behavior' => $behavior, 'entity' => 'catalog_product'])->setSource($source);
356+
357+
return $importModel;
358+
}
359+
360+
/**
361+
* @param string $productSku
362+
* @return array ['optionId' => ['optionValueId' => 'optionValueTitle', ...], ...]
363+
*/
364+
private function getCustomOptionValues($productSku)
365+
{
366+
/** @var ProductRepositoryInterface $productRepository */
367+
$productRepository = $this->objectManager->get(ProductRepositoryInterface::class);
368+
/** @var ProductCustomOptionRepositoryInterface $customOptionRepository */
369+
$customOptionRepository = $this->objectManager->get(ProductCustomOptionRepositoryInterface::class);
370+
$simpleProduct = $productRepository->get($productSku, false, null, true);
371+
$originalProductOptions = $customOptionRepository->getProductOptions($simpleProduct);
372+
$optionValues = [];
373+
foreach ($originalProductOptions as $productOption) {
374+
foreach ((array)$productOption->getValues() as $optionValue) {
375+
$optionValues[$productOption->getOptionId()][$optionValue->getOptionTypeId()]
376+
= $optionValue->getTitle();
377+
}
378+
}
379+
return $optionValues;
380+
}
381+
340382
/**
341383
* Test if datetime properly saved after import
342384
*

0 commit comments

Comments
 (0)