Skip to content

Commit d3a69d5

Browse files
author
vnayda
committed
Merge remote-tracking branch 'origin/develop' into MAGETWO-57963
2 parents c2a8940 + c533b3d commit d3a69d5

File tree

82 files changed

+639
-119
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

82 files changed

+639
-119
lines changed

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

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -953,7 +953,7 @@ protected function collectRawData()
953953
if (is_scalar($attrValue)) {
954954
if (!in_array($fieldName, $this->_getExportMainAttrCodes())) {
955955
$additionalAttributes[$fieldName] = $fieldName .
956-
ImportProduct::PAIR_NAME_VALUE_SEPARATOR . $attrValue;
956+
ImportProduct::PAIR_NAME_VALUE_SEPARATOR . $this->wrapValue($attrValue);
957957
}
958958
$data[$itemId][$storeId][$fieldName] = htmlspecialchars_decode($attrValue);
959959
}
@@ -963,7 +963,7 @@ protected function collectRawData()
963963
$additionalAttributes[$code] = $fieldName .
964964
ImportProduct::PAIR_NAME_VALUE_SEPARATOR . implode(
965965
ImportProduct::PSEUDO_MULTI_LINE_SEPARATOR,
966-
$this->collectedMultiselectsData[$storeId][$productLinkId][$code]
966+
$this->wrapValue($this->collectedMultiselectsData[$storeId][$productLinkId][$code])
967967
);
968968
}
969969
}
@@ -994,6 +994,25 @@ protected function collectRawData()
994994
return $data;
995995
}
996996

997+
/**
998+
* Wrap values with double quotes if "Fields Enclosure" option is enabled
999+
*
1000+
* @param string|array $value
1001+
* @return string|array
1002+
*/
1003+
private function wrapValue($value)
1004+
{
1005+
if (!empty($this->_parameters[\Magento\ImportExport\Model\Export::FIELDS_ENCLOSURE])) {
1006+
$wrap = function ($value) {
1007+
return sprintf('"%s"', str_replace('"', '""', $value));
1008+
};
1009+
1010+
$value = is_array($value) ? array_map($wrap, $value) : $wrap($value);
1011+
}
1012+
1013+
return $value;
1014+
}
1015+
9971016
/**
9981017
* @return array
9991018
*/

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

Lines changed: 105 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -644,6 +644,13 @@ class Product extends \Magento\ImportExport\Model\Import\Entity\AbstractEntity
644644
*/
645645
private $productEntityIdentifierField;
646646

647+
/**
648+
* Escaped separator value for regular expression.
649+
* The value is based on PSEUDO_MULTI_LINE_SEPARATOR constant.
650+
* @var string
651+
*/
652+
private $multiLineSeparatorForRegexp;
653+
647654
/**
648655
* @param \Magento\Framework\Json\Helper\Data $jsonHelper
649656
* @param \Magento\ImportExport\Helper\Data $importExportData
@@ -2438,14 +2445,40 @@ private function _parseAdditionalAttributes($rowData)
24382445
}
24392446

24402447
/**
2441-
* Retrieves additional attributes as array code=>value.
2448+
* Retrieves additional attributes in format:
2449+
* [
2450+
* code1 => value1,
2451+
* code2 => value2,
2452+
* ...
2453+
* codeN => valueN
2454+
* ]
24422455
*
2443-
* @param string $additionalAttributes
2456+
* @param string $additionalAttributes Attributes data that will be parsed
24442457
* @return array
24452458
*/
24462459
private function parseAdditionalAttributes($additionalAttributes)
24472460
{
2448-
$attributeNameValuePairs = explode($this->getMultipleValueSeparator(), $additionalAttributes);
2461+
return empty($this->_parameters[Import::FIELDS_ENCLOSURE])
2462+
? $this->parseAttributesWithoutWrappedValues($additionalAttributes)
2463+
: $this->parseAttributesWithWrappedValues($additionalAttributes);
2464+
}
2465+
2466+
/**
2467+
* Parses data and returns attributes in format:
2468+
* [
2469+
* code1 => value1,
2470+
* code2 => value2,
2471+
* ...
2472+
* codeN => valueN
2473+
* ]
2474+
*
2475+
* @param string $attributesData Attributes data that will be parsed. It keeps data in format:
2476+
* code=value,code2=value2...,codeN=valueN
2477+
* @return array
2478+
*/
2479+
private function parseAttributesWithoutWrappedValues($attributesData)
2480+
{
2481+
$attributeNameValuePairs = explode($this->getMultipleValueSeparator(), $attributesData);
24492482
$preparedAttributes = [];
24502483
$code = '';
24512484
foreach ($attributeNameValuePairs as $attributeData) {
@@ -2463,6 +2496,75 @@ private function parseAdditionalAttributes($additionalAttributes)
24632496
return $preparedAttributes;
24642497
}
24652498

2499+
/**
2500+
* Parses data and returns attributes in format:
2501+
* [
2502+
* code1 => value1,
2503+
* code2 => value2,
2504+
* ...
2505+
* codeN => valueN
2506+
* ]
2507+
* All values have unescaped data except mupliselect attributes,
2508+
* they should be parsed in additional method - parseMultiselectValues()
2509+
*
2510+
* @param string $attributesData Attributes data that will be parsed. It keeps data in format:
2511+
* code="value",code2="value2"...,codeN="valueN"
2512+
* where every value is wrapped in double quotes. Double quotes as part of value should be duplicated.
2513+
* E.g. attribute with code 'attr_code' has value 'my"value'. This data should be stored as attr_code="my""value"
2514+
*
2515+
* @return array
2516+
*/
2517+
private function parseAttributesWithWrappedValues($attributesData)
2518+
{
2519+
$attributes = [];
2520+
preg_match_all('~((?:[a-z0-9_])+)="((?:[^"]|""|"' . $this->getMultiLineSeparatorForRegexp() . '")+)"+~',
2521+
$attributesData,
2522+
$matches
2523+
);
2524+
foreach ($matches[1] as $i => $attributeCode) {
2525+
$attribute = $this->retrieveAttributeByCode($attributeCode);
2526+
$value = 'multiselect' != $attribute->getFrontendInput()
2527+
? str_replace('""', '"', $matches[2][$i])
2528+
: '"' . $matches[2][$i] . '"';
2529+
$attributes[$attributeCode] = $value;
2530+
}
2531+
return $attributes;
2532+
}
2533+
2534+
/**
2535+
* Parse values of multiselect attributes depends on "Fields Enclosure" parameter
2536+
*
2537+
* @param string $values
2538+
* @return array
2539+
*/
2540+
public function parseMultiselectValues($values)
2541+
{
2542+
if (empty($this->_parameters[Import::FIELDS_ENCLOSURE])) {
2543+
return explode(self::PSEUDO_MULTI_LINE_SEPARATOR, $values);
2544+
}
2545+
if (preg_match_all('~"((?:[^"]|"")*)"~', $values, $matches)) {
2546+
return $values = array_map(function ($value) {
2547+
return str_replace('""', '"', $value);
2548+
}, $matches[1]);
2549+
}
2550+
return [$values];
2551+
}
2552+
2553+
/**
2554+
* Retrieves escaped PSEUDO_MULTI_LINE_SEPARATOR if it is metacharacter for regular expression
2555+
*
2556+
* @return string
2557+
*/
2558+
private function getMultiLineSeparatorForRegexp()
2559+
{
2560+
if (!$this->multiLineSeparatorForRegexp) {
2561+
$this->multiLineSeparatorForRegexp = in_array(self::PSEUDO_MULTI_LINE_SEPARATOR, str_split('[\^$.|?*+(){}'))
2562+
? '\\' . self::PSEUDO_MULTI_LINE_SEPARATOR
2563+
: self::PSEUDO_MULTI_LINE_SEPARATOR;
2564+
}
2565+
return $this->multiLineSeparatorForRegexp;
2566+
}
2567+
24662568
/**
24672569
* Set values in use_config_ fields.
24682570
*

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

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -490,23 +490,25 @@ public function prepareAttributesWithDefaultValueForSave(array $rowData, $withDe
490490
$resultAttrs = [];
491491

492492
foreach ($this->_getProductAttributes($rowData) as $attrCode => $attrParams) {
493-
if (!$attrParams['is_static']) {
494-
if (isset($rowData[$attrCode]) && strlen($rowData[$attrCode])) {
495-
$resultAttrs[$attrCode] = in_array($attrParams['type'], ['select', 'boolean'])
496-
? $attrParams['options'][strtolower($rowData[$attrCode])]
497-
: $rowData[$attrCode];
498-
if ('multiselect' == $attrParams['type']) {
499-
$resultAttrs[$attrCode] = [];
500-
foreach (explode(Product::PSEUDO_MULTI_LINE_SEPARATOR, $rowData[$attrCode]) as $value) {
501-
$resultAttrs[$attrCode][] = $attrParams['options'][strtolower($value)];
502-
}
503-
$resultAttrs[$attrCode] = implode(',', $resultAttrs[$attrCode]);
493+
if ($attrParams['is_static']) {
494+
continue;
495+
}
496+
if (isset($rowData[$attrCode]) && strlen($rowData[$attrCode])) {
497+
if (in_array($attrParams['type'], ['select', 'boolean'])) {
498+
$resultAttrs[$attrCode] = $attrParams['options'][strtolower($rowData[$attrCode])];
499+
} elseif ('multiselect' == $attrParams['type']) {
500+
$resultAttrs[$attrCode] = [];
501+
foreach ($this->_entityModel->parseMultiselectValues($rowData[$attrCode]) as $value) {
502+
$resultAttrs[$attrCode][] = $attrParams['options'][strtolower($value)];
504503
}
505-
} elseif (array_key_exists($attrCode, $rowData)) {
504+
$resultAttrs[$attrCode] = implode(',', $resultAttrs[$attrCode]);
505+
} else {
506506
$resultAttrs[$attrCode] = $rowData[$attrCode];
507-
} elseif ($withDefaultValue && null !== $attrParams['default_value']) {
508-
$resultAttrs[$attrCode] = $attrParams['default_value'];
509507
}
508+
} elseif (array_key_exists($attrCode, $rowData)) {
509+
$resultAttrs[$attrCode] = $rowData[$attrCode];
510+
} elseif ($withDefaultValue && null !== $attrParams['default_value']) {
511+
$resultAttrs[$attrCode] = $attrParams['default_value'];
510512
}
511513
}
512514

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

Lines changed: 33 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,32 @@ protected function textValidation($attrCode, $type)
7171
return $valid;
7272
}
7373

74+
/**
75+
* Check if value is valid attribute option
76+
*
77+
* @param string $attrCode
78+
* @param array $possibleOptions
79+
* @param string $value
80+
* @return bool
81+
*/
82+
private function validateOption($attrCode, $possibleOptions, $value)
83+
{
84+
if (!isset($possibleOptions[strtolower($value)])) {
85+
$this->_addMessages(
86+
[
87+
sprintf(
88+
$this->context->retrieveMessageTemplate(
89+
RowValidatorInterface::ERROR_INVALID_ATTRIBUTE_OPTION
90+
),
91+
$attrCode
92+
)
93+
]
94+
);
95+
return false;
96+
}
97+
return true;
98+
}
99+
74100
/**
75101
* @param mixed $attrCode
76102
* @param string $type
@@ -166,23 +192,15 @@ public function isAttributeValid($attrCode, array $attrParams, array $rowData)
166192
break;
167193
case 'select':
168194
case 'boolean':
195+
$valid = $this->validateOption($attrCode, $attrParams['options'], $rowData[$attrCode]);
196+
break;
169197
case 'multiselect':
170-
$values = explode(Product::PSEUDO_MULTI_LINE_SEPARATOR, $rowData[$attrCode]);
171-
$valid = true;
198+
$values = $this->context->parseMultiselectValues($rowData[$attrCode]);
172199
foreach ($values as $value) {
173-
$valid = $valid && isset($attrParams['options'][strtolower($value)]);
174-
}
175-
if (!$valid) {
176-
$this->_addMessages(
177-
[
178-
sprintf(
179-
$this->context->retrieveMessageTemplate(
180-
RowValidatorInterface::ERROR_INVALID_ATTRIBUTE_OPTION
181-
),
182-
$attrCode
183-
)
184-
]
185-
);
200+
$valid = $this->validateOption($attrCode, $attrParams['options'], $value);
201+
if (!$valid) {
202+
break;
203+
}
186204
}
187205
break;
188206
case 'datetime':

app/code/Magento/Deploy/Console/Command/DeployStaticContentCommand.php

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
* Copyright © 2016 Magento. All rights reserved.
44
* See COPYING.txt for license details.
55
*/
6-
76
namespace Magento\Deploy\Console\Command;
87

98
use Magento\Framework\App\Utility\Files;
@@ -19,6 +18,8 @@
1918
use Magento\Framework\App\State;
2019
use Magento\Deploy\Console\Command\DeployStaticOptionsInterface as Options;
2120
use Magento\Deploy\Model\DeployManager;
21+
use Magento\Framework\App\Cache;
22+
use Magento\Framework\App\Cache\Type\Dummy as DummyCache;
2223

2324
/**
2425
* Deploy static content command
@@ -29,7 +30,7 @@ class DeployStaticContentCommand extends Command
2930
/**
3031
* Key for dry-run option
3132
* @deprecated
32-
* @see Magento\Deploy\Console\Command\DeployStaticOptionsInterface::DRY_RUN
33+
* @see \Magento\Deploy\Console\Command\DeployStaticOptionsInterface::DRY_RUN
3334
*/
3435
const DRY_RUN_OPTION = 'dry-run';
3536

@@ -87,6 +88,7 @@ class DeployStaticContentCommand extends Command
8788
* @param ObjectManagerFactory $objectManagerFactory
8889
* @param Locale $validator
8990
* @param ObjectManagerInterface $objectManager
91+
* @throws \LogicException When the command name is empty
9092
*/
9193
public function __construct(
9294
ObjectManagerFactory $objectManagerFactory,
@@ -96,6 +98,7 @@ public function __construct(
9698
$this->objectManagerFactory = $objectManagerFactory;
9799
$this->validator = $validator;
98100
$this->objectManager = $objectManager;
101+
99102
parent::__construct();
100103
}
101104

@@ -373,6 +376,7 @@ private function getDeployableEntities($entities, $includedEntities, $excludedEn
373376
/**
374377
* {@inheritdoc}
375378
* @throws \InvalidArgumentException
379+
* @throws LocalizedException
376380
*/
377381
protected function execute(InputInterface $input, OutputInterface $output)
378382
{
@@ -394,9 +398,9 @@ protected function execute(InputInterface $input, OutputInterface $output)
394398
list ($deployableLanguages, $deployableAreaThemeMap, $requestedThemes)
395399
= $this->prepareDeployableEntities($filesUtil);
396400

397-
$output->writeln("Requested languages: " . implode(', ', $deployableLanguages));
398-
$output->writeln("Requested areas: " . implode(', ', array_keys($deployableAreaThemeMap)));
399-
$output->writeln("Requested themes: " . implode(', ', $requestedThemes));
401+
$output->writeln('Requested languages: ' . implode(', ', $deployableLanguages));
402+
$output->writeln('Requested areas: ' . implode(', ', array_keys($deployableAreaThemeMap)));
403+
$output->writeln('Requested themes: ' . implode(', ', $requestedThemes));
400404

401405
/** @var $deployManager DeployManager */
402406
$deployManager = $this->objectManager->create(
@@ -415,11 +419,13 @@ protected function execute(InputInterface $input, OutputInterface $output)
415419
}
416420
}
417421

422+
$this->mockCache();
418423
return $deployManager->deploy();
419424
}
420425

421426
/**
422427
* @param Files $filesUtil
428+
* @throws \InvalidArgumentException
423429
* @return array
424430
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
425431
* @SuppressWarnings(PHPMD.NPathComplexity)
@@ -476,4 +482,18 @@ private function prepareDeployableEntities($filesUtil)
476482

477483
return [$deployableLanguages, $deployableAreaThemeMap, $requestedThemes];
478484
}
485+
486+
/**
487+
* Mock Cache class with dummy implementation
488+
*
489+
* @return void
490+
*/
491+
private function mockCache()
492+
{
493+
$this->objectManager->configure([
494+
'preferences' => [
495+
Cache::class => DummyCache::class
496+
]
497+
]);
498+
}
479499
}

app/code/Magento/ImportExport/Block/Adminhtml/Export/Edit/Form.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,16 @@ protected function _prepareForm()
8686
'values' => $this->_formatFactory->create()->toOptionArray()
8787
]
8888
);
89+
$fieldset->addField(
90+
\Magento\ImportExport\Model\Export::FIELDS_ENCLOSURE,
91+
'checkbox',
92+
[
93+
'name' => \Magento\ImportExport\Model\Export::FIELDS_ENCLOSURE,
94+
'label' => __('Fields Enclosure'),
95+
'title' => __('Fields Enclosure'),
96+
'value' => 1,
97+
]
98+
);
8999

90100
$form->setUseContainer(true);
91101
$this->setForm($form);

0 commit comments

Comments
 (0)