Skip to content

Commit 1f3c80b

Browse files
author
vnayda
committed
Merge remote-tracking branch 'mainline/2.1-develop' into MAGETWO-59953
2 parents 8e6c315 + de3b4df commit 1f3c80b

File tree

58 files changed

+4373
-562
lines changed

Some content is hidden

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

58 files changed

+4373
-562
lines changed

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

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -916,7 +916,7 @@ protected function collectRawData()
916916
if (is_scalar($attrValue)) {
917917
if (!in_array($fieldName, $this->_getExportMainAttrCodes())) {
918918
$additionalAttributes[$fieldName] = $fieldName .
919-
ImportProduct::PAIR_NAME_VALUE_SEPARATOR . $attrValue;
919+
ImportProduct::PAIR_NAME_VALUE_SEPARATOR . $this->wrapValue($attrValue);
920920
}
921921
$data[$itemId][$storeId][$fieldName] = htmlspecialchars_decode($attrValue);
922922
}
@@ -926,7 +926,7 @@ protected function collectRawData()
926926
$additionalAttributes[$code] = $fieldName .
927927
ImportProduct::PAIR_NAME_VALUE_SEPARATOR . implode(
928928
ImportProduct::PSEUDO_MULTI_LINE_SEPARATOR,
929-
$this->collectedMultiselectsData[$storeId][$productLinkId][$code]
929+
$this->wrapValue($this->collectedMultiselectsData[$storeId][$productLinkId][$code])
930930
);
931931
}
932932
}
@@ -957,6 +957,25 @@ protected function collectRawData()
957957
return $data;
958958
}
959959

960+
/**
961+
* Wrap values with double quotes if "Fields Enclosure" option is enabled
962+
*
963+
* @param string|array $value
964+
* @return string|array
965+
*/
966+
private function wrapValue($value)
967+
{
968+
if (!empty($this->_parameters[\Magento\ImportExport\Model\Export::FIELDS_ENCLOSURE])) {
969+
$wrap = function ($value) {
970+
return sprintf('"%s"', str_replace('"', '""', $value));
971+
};
972+
973+
$value = is_array($value) ? array_map($wrap, $value) : $wrap($value);
974+
}
975+
976+
return $value;
977+
}
978+
960979
/**
961980
* @return array
962981
*/

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

Lines changed: 128 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -626,6 +626,13 @@ class Product extends \Magento\ImportExport\Model\Import\Entity\AbstractEntity
626626
*/
627627
private $productEntityIdentifierField;
628628

629+
/**
630+
* Escaped separator value for regular expression.
631+
* The value is based on PSEUDO_MULTI_LINE_SEPARATOR constant.
632+
* @var string
633+
*/
634+
private $multiLineSeparatorForRegexp;
635+
629636
/**
630637
* @param \Magento\Framework\Json\Helper\Data $jsonHelper
631638
* @param \Magento\ImportExport\Helper\Data $importExportData
@@ -2392,6 +2399,127 @@ private function _parseAdditionalAttributes($rowData)
23922399
return $rowData;
23932400
}
23942401

2402+
/**
2403+
* Retrieves additional attributes in format:
2404+
* [
2405+
* code1 => value1,
2406+
* code2 => value2,
2407+
* ...
2408+
* codeN => valueN
2409+
* ]
2410+
*
2411+
* @param string $additionalAttributes Attributes data that will be parsed
2412+
* @return array
2413+
*/
2414+
private function parseAdditionalAttributes($additionalAttributes)
2415+
{
2416+
return empty($this->_parameters[Import::FIELDS_ENCLOSURE])
2417+
? $this->parseAttributesWithoutWrappedValues($additionalAttributes)
2418+
: $this->parseAttributesWithWrappedValues($additionalAttributes);
2419+
}
2420+
2421+
/**
2422+
* Parses data and returns attributes in format:
2423+
* [
2424+
* code1 => value1,
2425+
* code2 => value2,
2426+
* ...
2427+
* codeN => valueN
2428+
* ]
2429+
*
2430+
* @param string $attributesData Attributes data that will be parsed. It keeps data in format:
2431+
* code=value,code2=value2...,codeN=valueN
2432+
* @return array
2433+
*/
2434+
private function parseAttributesWithoutWrappedValues($attributesData)
2435+
{
2436+
$attributeNameValuePairs = explode($this->getMultipleValueSeparator(), $attributesData);
2437+
$preparedAttributes = [];
2438+
$code = '';
2439+
foreach ($attributeNameValuePairs as $attributeData) {
2440+
//process case when attribute has ImportModel::DEFAULT_GLOBAL_MULTI_VALUE_SEPARATOR inside its value
2441+
if (strpos($attributeData, self::PAIR_NAME_VALUE_SEPARATOR) === false) {
2442+
if (!$code) {
2443+
continue;
2444+
}
2445+
$preparedAttributes[$code] .= $this->getMultipleValueSeparator() . $attributeData;
2446+
continue;
2447+
}
2448+
list($code, $value) = explode(self::PAIR_NAME_VALUE_SEPARATOR, $attributeData, 2);
2449+
$preparedAttributes[$code] = $value;
2450+
}
2451+
return $preparedAttributes;
2452+
}
2453+
2454+
/**
2455+
* Parses data and returns attributes in format:
2456+
* [
2457+
* code1 => value1,
2458+
* code2 => value2,
2459+
* ...
2460+
* codeN => valueN
2461+
* ]
2462+
* All values have unescaped data except mupliselect attributes,
2463+
* they should be parsed in additional method - parseMultiselectValues()
2464+
*
2465+
* @param string $attributesData Attributes data that will be parsed. It keeps data in format:
2466+
* code="value",code2="value2"...,codeN="valueN"
2467+
* where every value is wrapped in double quotes. Double quotes as part of value should be duplicated.
2468+
* E.g. attribute with code 'attr_code' has value 'my"value'. This data should be stored as attr_code="my""value"
2469+
*
2470+
* @return array
2471+
*/
2472+
private function parseAttributesWithWrappedValues($attributesData)
2473+
{
2474+
$attributes = [];
2475+
preg_match_all('~((?:[a-z0-9_])+)="((?:[^"]|""|"' . $this->getMultiLineSeparatorForRegexp() . '")+)"+~',
2476+
$attributesData,
2477+
$matches
2478+
);
2479+
foreach ($matches[1] as $i => $attributeCode) {
2480+
$attribute = $this->retrieveAttributeByCode($attributeCode);
2481+
$value = 'multiselect' != $attribute->getFrontendInput()
2482+
? str_replace('""', '"', $matches[2][$i])
2483+
: '"' . $matches[2][$i] . '"';
2484+
$attributes[$attributeCode] = $value;
2485+
}
2486+
return $attributes;
2487+
}
2488+
2489+
/**
2490+
* Parse values of multiselect attributes depends on "Fields Enclosure" parameter
2491+
*
2492+
* @param string $values
2493+
* @return array
2494+
*/
2495+
public function parseMultiselectValues($values)
2496+
{
2497+
if (empty($this->_parameters[Import::FIELDS_ENCLOSURE])) {
2498+
return explode(self::PSEUDO_MULTI_LINE_SEPARATOR, $values);
2499+
}
2500+
if (preg_match_all('~"((?:[^"]|"")*)"~', $values, $matches)) {
2501+
return $values = array_map(function ($value) {
2502+
return str_replace('""', '"', $value);
2503+
}, $matches[1]);
2504+
}
2505+
return [$values];
2506+
}
2507+
2508+
/**
2509+
* Retrieves escaped PSEUDO_MULTI_LINE_SEPARATOR if it is metacharacter for regular expression
2510+
*
2511+
* @return string
2512+
*/
2513+
private function getMultiLineSeparatorForRegexp()
2514+
{
2515+
if (!$this->multiLineSeparatorForRegexp) {
2516+
$this->multiLineSeparatorForRegexp = in_array(self::PSEUDO_MULTI_LINE_SEPARATOR, str_split('[\^$.|?*+(){}'))
2517+
? '\\' . self::PSEUDO_MULTI_LINE_SEPARATOR
2518+
: self::PSEUDO_MULTI_LINE_SEPARATOR;
2519+
}
2520+
return $this->multiLineSeparatorForRegexp;
2521+
}
2522+
23952523
/**
23962524
* Set values in use_config_ fields.
23972525
*
@@ -2575,29 +2703,4 @@ private function getProductIdentifierField()
25752703
}
25762704
return $this->productEntityIdentifierField;
25772705
}
2578-
2579-
/**
2580-
* Retrieves additional attributes as array code=>value
2581-
*
2582-
* @param string $additionalAttributes
2583-
* @return array
2584-
*/
2585-
private function parseAdditionalAttributes($additionalAttributes)
2586-
{
2587-
$attributeNameValuePairs = explode($this->getMultipleValueSeparator(), $additionalAttributes);
2588-
$preparedAttributes = [];
2589-
$code = '';
2590-
foreach ($attributeNameValuePairs as $attributeData) {
2591-
if (strpos($attributeData, self::PAIR_NAME_VALUE_SEPARATOR) === false) {
2592-
if (!$code) {
2593-
continue;
2594-
}
2595-
$preparedAttributes[$code] .= $this->getMultipleValueSeparator() . $attributeData;
2596-
continue;
2597-
}
2598-
list($code, $value) = explode(self::PAIR_NAME_VALUE_SEPARATOR, $attributeData, 2);
2599-
$preparedAttributes[$code] = $value;
2600-
}
2601-
return $preparedAttributes;
2602-
}
26032706
}

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':

0 commit comments

Comments
 (0)