diff --git a/app/code/Magento/Catalog/Model/ProductRepository.php b/app/code/Magento/Catalog/Model/ProductRepository.php
index e814dc03cf37f..2b9ee8f862907 100644
--- a/app/code/Magento/Catalog/Model/ProductRepository.php
+++ b/app/code/Magento/Catalog/Model/ProductRepository.php
@@ -333,8 +333,13 @@ protected function initializeProductData(array $productData, $createNew)
$product->setWebsiteIds([$this->storeManager->getStore(true)->getWebsiteId()]);
}
} else {
- unset($this->instances[$productData['sku']]);
- $product = $this->get($productData['sku']);
+ if (!empty($productData['id'])) {
+ unset($this->instancesById[$productData['id']]);
+ $product = $this->getById($productData['id']);
+ } else {
+ unset($this->instances[$productData['sku']]);
+ $product = $this->get($productData['sku']);
+ }
}
foreach ($productData as $key => $value) {
@@ -562,7 +567,7 @@ public function save(\Magento\Catalog\Api\Data\ProductInterface $product, $saveO
$tierPrices = $product->getData('tier_price');
try {
- $existingProduct = $this->get($product->getSku());
+ $existingProduct = $product->getId() ? $this->getById($product->getId()) : $this->get($product->getSku());
$product->setData(
$this->resourceModel->getLinkField(),
diff --git a/app/code/Magento/Catalog/Test/Unit/Model/ProductRepositoryTest.php b/app/code/Magento/Catalog/Test/Unit/Model/ProductRepositoryTest.php
index c98705b4eda63..2a9a867fa20b5 100644
--- a/app/code/Magento/Catalog/Test/Unit/Model/ProductRepositoryTest.php
+++ b/app/code/Magento/Catalog/Test/Unit/Model/ProductRepositoryTest.php
@@ -610,7 +610,7 @@ public function testSaveException()
->willReturn(true);
$this->resourceModelMock->expects($this->once())->method('save')->with($this->productMock)
->willThrowException(new \Magento\Eav\Model\Entity\Attribute\Exception(__('123')));
- $this->productMock->expects($this->once())->method('getId')->willReturn(null);
+ $this->productMock->expects($this->exactly(2))->method('getId')->willReturn(null);
$this->extensibleDataObjectConverterMock
->expects($this->once())
->method('toNestedArray')
@@ -634,7 +634,7 @@ public function testSaveInvalidProductException()
$this->initializationHelperMock->expects($this->never())->method('initialize');
$this->resourceModelMock->expects($this->once())->method('validate')->with($this->productMock)
->willReturn(['error1', 'error2']);
- $this->productMock->expects($this->never())->method('getId');
+ $this->productMock->expects($this->once())->method('getId')->willReturn(null);
$this->extensibleDataObjectConverterMock
->expects($this->once())
->method('toNestedArray')
diff --git a/app/code/Magento/Rule/Model/Condition/Product/AbstractProduct.php b/app/code/Magento/Rule/Model/Condition/Product/AbstractProduct.php
index 5ab1379b96cf6..9a6f1b48620dc 100644
--- a/app/code/Magento/Rule/Model/Condition/Product/AbstractProduct.php
+++ b/app/code/Magento/Rule/Model/Condition/Product/AbstractProduct.php
@@ -241,7 +241,9 @@ protected function _prepareValueOptions()
} else {
$addEmptyOption = true;
}
- $selectOptions = $attributeObject->getSource()->getAllOptions($addEmptyOption);
+ $selectOptions = $this->removeTagsFromLabel(
+ $attributeObject->getSource()->getAllOptions($addEmptyOption)
+ );
}
}
@@ -734,4 +736,21 @@ protected function getEavAttributeTableAlias()
return 'at_' . $attribute->getAttributeCode();
}
+
+ /**
+ * Remove html tags from attribute labels.
+ *
+ * @param array $selectOptions
+ * @return array
+ */
+ private function removeTagsFromLabel(array $selectOptions)
+ {
+ foreach ($selectOptions as &$option) {
+ if (isset($option['label'])) {
+ $option['label'] = strip_tags($option['label']);
+ }
+ }
+
+ return $selectOptions;
+ }
}
diff --git a/app/code/Magento/Tax/i18n/en_US.csv b/app/code/Magento/Tax/i18n/en_US.csv
index 2314f27b92928..e6d89deb7696c 100644
--- a/app/code/Magento/Tax/i18n/en_US.csv
+++ b/app/code/Magento/Tax/i18n/en_US.csv
@@ -176,4 +176,5 @@ Rate,Rate
"Order Total Incl. Tax","Order Total Incl. Tax"
"Order Total","Order Total"
"Your credit card will be charged for","Your credit card will be charged for"
-"An error occurred while loading tax rates.","An error occurred while loading tax rates."
\ No newline at end of file
+"An error occurred while loading tax rates.","An error occurred while loading tax rates."
+"You will be charged for","You will be charged for"
diff --git a/app/code/Magento/Tax/view/frontend/layout/checkout_index_index.xml b/app/code/Magento/Tax/view/frontend/layout/checkout_index_index.xml
index 6d867fcb71f2e..6969580b78b8f 100644
--- a/app/code/Magento/Tax/view/frontend/layout/checkout_index_index.xml
+++ b/app/code/Magento/Tax/view/frontend/layout/checkout_index_index.xml
@@ -70,7 +70,7 @@
-
- Order Total Excl. Tax
- Order Total Incl. Tax
- - Your credit card will be charged for
+ - You will be charged for
- Order Total
diff --git a/dev/tests/integration/testsuite/Magento/Catalog/Model/ProductRepositoryTest.php b/dev/tests/integration/testsuite/Magento/Catalog/Model/ProductRepositoryTest.php
new file mode 100644
index 0000000000000..9518e9c0cdf4f
--- /dev/null
+++ b/dev/tests/integration/testsuite/Magento/Catalog/Model/ProductRepositoryTest.php
@@ -0,0 +1,52 @@
+productRepository = Bootstrap::getObjectManager()->get(ProductRepositoryInterface::class);
+ }
+
+ /**
+ * Test Product Repository can change(update) "sku" for given product.
+ *
+ * @magentoDataFixture Magento/Catalog/_files/product_simple.php
+ * @magentoAppArea adminhtml
+ */
+ public function testUpdateProductSku()
+ {
+ $newSku = 'simple-edited';
+ $productId = Bootstrap::getObjectManager()->get(ProductResource::class)->getIdBySku('simple');
+ $initialProduct = Bootstrap::getObjectManager()->create(Product::class)->load($productId);
+
+ $initialProduct->setSku($newSku);
+ $this->productRepository->save($initialProduct);
+
+ $updatedProduct = Bootstrap::getObjectManager()->create(Product::class);
+ $updatedProduct->load($productId);
+ self::assertSame($newSku, $updatedProduct->getSku());
+ }
+}
diff --git a/dev/tests/integration/testsuite/Magento/Rule/Model/Condition/Product/AbstractProductTest.php b/dev/tests/integration/testsuite/Magento/Rule/Model/Condition/Product/AbstractProductTest.php
new file mode 100644
index 0000000000000..3c706b453b4d1
--- /dev/null
+++ b/dev/tests/integration/testsuite/Magento/Rule/Model/Condition/Product/AbstractProductTest.php
@@ -0,0 +1,83 @@
+get(Context::class);
+ $helperData = $objectManager->get(Data::class);
+ $config = $objectManager->get(Config::class);
+ $productFactory = $objectManager->get(ProductFactory::class);
+ $productRepository = $objectManager->get(ProductRepositoryInterface::class);
+ $productResource = $objectManager->get(Product::class);
+ $attributeSetCollection = $objectManager->get(Collection::class);
+ $localeFormat = $objectManager->get(FormatInterface::class);
+ $data = [];
+ $productCategoryList = $objectManager->get(ProductCategoryList::class);
+ $this->model = $this->getMockBuilder(AbstractProduct::class)
+ ->setMethods(['getOperator', 'getFormName', 'setFormName'])
+ ->setConstructorArgs([
+ $context,
+ $helperData,
+ $config,
+ $productFactory,
+ $productRepository,
+ $productResource,
+ $attributeSetCollection,
+ $localeFormat,
+ $data,
+ $productCategoryList
+ ])
+ ->getMockForAbstractClass();
+ }
+
+ /**
+ * Test Abstract Rule product condition data model shows attribute labels in more readable view
+ * (without html tags, if one presented).
+ *
+ * @magentoDataFixture Magento/Rule/_files/dropdown_attribute_with_html.php
+ */
+ public function testGetValueSelectOptions()
+ {
+ $expectedLabels = [' ', 'Option 1', 'Option 2', 'Option 3'];
+ $this->model->setAttribute('dropdown_attribute_with_html');
+ $options = $this->model->getValueSelectOptions();
+ $labels = [];
+ foreach ($options as $option) {
+ $labels[] = $option['label'];
+ }
+ self::assertSame($expectedLabels, $labels);
+ }
+}
diff --git a/dev/tests/integration/testsuite/Magento/Rule/_files/dropdown_attribute_with_html.php b/dev/tests/integration/testsuite/Magento/Rule/_files/dropdown_attribute_with_html.php
new file mode 100644
index 0000000000000..d4c6036a340cd
--- /dev/null
+++ b/dev/tests/integration/testsuite/Magento/Rule/_files/dropdown_attribute_with_html.php
@@ -0,0 +1,59 @@
+create(
+ \Magento\Catalog\Model\ResourceModel\Eav\Attribute::class
+);
+
+if (!$attribute->loadByCode(4, 'dropdown_attribute_with_html')->getId()) {
+ /** @var $installer \Magento\Catalog\Setup\CategorySetup */
+ $installer = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create(
+ \Magento\Catalog\Setup\CategorySetup::class
+ );
+
+ $attribute->setData(
+ [
+ 'attribute_code' => 'dropdown_attribute_with_html',
+ 'entity_type_id' => $installer->getEntityTypeId('catalog_product'),
+ 'is_global' => 0,
+ 'is_user_defined' => 1,
+ 'frontend_input' => 'select',
+ 'is_unique' => 0,
+ 'is_required' => 0,
+ 'is_searchable' => 0,
+ 'is_visible_in_advanced_search' => 0,
+ 'is_comparable' => 0,
+ 'is_filterable' => 0,
+ 'is_filterable_in_search' => 0,
+ 'is_used_for_promo_rules' => 0,
+ 'is_html_allowed_on_front' => 1,
+ 'is_visible_on_front' => 0,
+ 'used_in_product_listing' => 0,
+ 'used_for_sort_by' => 0,
+ 'frontend_label' => ['Drop-Down Attribute'],
+ 'backend_type' => 'varchar',
+ 'backend_model' => \Magento\Eav\Model\Entity\Attribute\Backend\ArrayBackend::class,
+ 'option' => [
+ 'value' => [
+ 'option_1' => ['Option 1'],
+ 'option_2' => ['Option 2'],
+ 'option_3' => ['Option 3'],
+ ],
+ 'order' => [
+ 'option_1' => 1,
+ 'option_2' => 2,
+ 'option_3' => 3,
+ ],
+ ],
+ ]
+ );
+ $attribute->save();
+
+ /* Assign attribute to attribute set */
+ $installer->addAttributeToGroup('catalog_product', 'Default', 'Attributes', $attribute->getId());
+}
diff --git a/dev/tests/integration/testsuite/Magento/Rule/_files/dropdown_attribute_with_html_rollback.php b/dev/tests/integration/testsuite/Magento/Rule/_files/dropdown_attribute_with_html_rollback.php
new file mode 100644
index 0000000000000..130cfea7442e0
--- /dev/null
+++ b/dev/tests/integration/testsuite/Magento/Rule/_files/dropdown_attribute_with_html_rollback.php
@@ -0,0 +1,22 @@
+get(\Magento\Framework\Registry::class);
+$registry->unregister('isSecureArea');
+$registry->register('isSecureArea', true);
+/** @var $attribute Attribute */
+$attribute = Bootstrap::getObjectManager()->create(
+ Attribute::class
+);
+$attribute->load('dropdown_attribute_with_html', 'attribute_code');
+$attribute->delete();
+
+$registry->unregister('isSecureArea');
+$registry->register('isSecureArea', false);
diff --git a/dev/tests/static/testsuite/Magento/Test/Php/XssPhtmlTemplateTest.php b/dev/tests/static/testsuite/Magento/Test/Php/XssPhtmlTemplateTest.php
index 34531b6b7c658..fac14af5ecab8 100644
--- a/dev/tests/static/testsuite/Magento/Test/Php/XssPhtmlTemplateTest.php
+++ b/dev/tests/static/testsuite/Magento/Test/Php/XssPhtmlTemplateTest.php
@@ -27,16 +27,14 @@ public function testXssSensitiveOutput()
* Static test will cover the following cases:
*
* 1. /\* @noEscape \*\/ before output. Output doesn't require escaping. Test is green.
- * 2. /\* @escapeNotVerified \*\/ before output. Output escaping is not checked and
- * should be verified. Test is green.
- * 3. Methods which contains "html" in their names (e.g. echo $object->{suffix}Html{postfix}() ).
+ * 2. Methods which contains "html" in their names (e.g. echo $object->{suffix}Html{postfix}() ).
* Data is ready for the HTML output. Test is green.
- * 4. AbstractBlock methods escapeHtml, escapeUrl, escapeQuote, escapeXssInUrl are allowed. Test is green.
- * 5. Type casting and php function count() are allowed
+ * 3. AbstractBlock methods escapeHtml, escapeUrl, escapeQuote, escapeXssInUrl are allowed. Test is green.
+ * 4. Type casting and php function count() are allowed
* (e.g. echo (int)$var, echo (float)$var, echo (bool)$var, echo count($var)). Test is green.
- * 6. Output in single quotes (e.g. echo 'some text'). Test is green.
- * 7. Output in double quotes without variables (e.g. echo "some text"). Test is green.
- * 8. Other of p.1-7. Output is not escaped. Test is red.
+ * 5. Output in single quotes (e.g. echo 'some text'). Test is green.
+ * 6. Output in double quotes without variables (e.g. echo "some text"). Test is green.
+ * 7. Other of p.1-6. Output is not escaped. Test is red.
*
* @param string $file
*/
diff --git a/lib/internal/Magento/Framework/Crontab/CrontabManager.php b/lib/internal/Magento/Framework/Crontab/CrontabManager.php
index 1f079b766c22e..94e2027abd135 100644
--- a/lib/internal/Magento/Framework/Crontab/CrontabManager.php
+++ b/lib/internal/Magento/Framework/Crontab/CrontabManager.php
@@ -138,6 +138,11 @@ public function removeTasks()
private function generateSection($content, $tasks = [])
{
if ($tasks) {
+ // Add EOL symbol to previous line if not exist.
+ if (substr($content, -strlen(PHP_EOL)) !== PHP_EOL) {
+ $content .= PHP_EOL;
+ }
+
$content .= $this->getTasksBlockStart() . PHP_EOL;
foreach ($tasks as $task) {
$content .= $task['expression'] . ' ' . PHP_BINARY . ' ' . $task['command'] . PHP_EOL;
diff --git a/lib/internal/Magento/Framework/Crontab/Test/Unit/CrontabManagerTest.php b/lib/internal/Magento/Framework/Crontab/Test/Unit/CrontabManagerTest.php
index 779bc7621be7c..3c52b5d33a5ef 100644
--- a/lib/internal/Magento/Framework/Crontab/Test/Unit/CrontabManagerTest.php
+++ b/lib/internal/Magento/Framework/Crontab/Test/Unit/CrontabManagerTest.php
@@ -337,6 +337,17 @@ public function saveTasksDataProvider()
. ' %% cron:run | grep -v \"Ran \'jobs\' by schedule\"' . PHP_EOL
. CrontabManagerInterface::TASKS_BLOCK_END . ' ' . md5(BP) . PHP_EOL,
],
+ [
+ 'tasks' => [
+ ['command' => '{magentoRoot}run.php % cron:run | grep -v "Ran \'jobs\' by schedule"']
+ ],
+ 'content' => '* * * * * /bin/php /var/www/cron.php',
+ 'contentToSave' => '* * * * * /bin/php /var/www/cron.php' . PHP_EOL
+ . CrontabManagerInterface::TASKS_BLOCK_START . ' ' . md5(BP) . PHP_EOL
+ . '* * * * * ' . PHP_BINARY . ' /var/www/magento2/run.php'
+ . ' %% cron:run | grep -v \"Ran \'jobs\' by schedule\"' . PHP_EOL
+ . CrontabManagerInterface::TASKS_BLOCK_END . ' ' . md5(BP) . PHP_EOL,
+ ],
];
}
}
diff --git a/lib/internal/Magento/Framework/Webapi/ServiceInputProcessor.php b/lib/internal/Magento/Framework/Webapi/ServiceInputProcessor.php
index faca0c1530a53..6558890698082 100644
--- a/lib/internal/Magento/Framework/Webapi/ServiceInputProcessor.php
+++ b/lib/internal/Magento/Framework/Webapi/ServiceInputProcessor.php
@@ -315,6 +315,10 @@ protected function _createDataObjectForTypeAndArrayValue($type, $customAttribute
*/
public function convertValue($data, $type)
{
+ if ($data === '') {
+ return $data;
+ }
+
$isArrayType = $this->typeProcessor->isArrayType($type);
if ($isArrayType && isset($data['item'])) {
$data = $this->_removeSoapItemNode($data);
@@ -325,13 +329,7 @@ public function convertValue($data, $type)
/** Complex type or array of complex types */
if ($isArrayType) {
// Initializing the result for array type else it will return null for empty array
- $result = is_array($data) ? [] : null;
- $itemType = $this->typeProcessor->getArrayItemType($type);
- if (is_array($data)) {
- foreach ($data as $key => $item) {
- $result[$key] = $this->_createFromArray($itemType, $item);
- }
- }
+ $result = $this->getResultForArrayType($data, $type);
} else {
$result = $this->_createFromArray($type, $data);
}
@@ -385,4 +383,23 @@ protected function processInputError($inputError)
}
}
}
+
+ /**
+ * @param mixed $data
+ * @param string $type
+ *
+ * @return array|null
+ */
+ private function getResultForArrayType($data, $type)
+ {
+ $result = is_array($data) ? [] : null;
+ $itemType = $this->typeProcessor->getArrayItemType($type);
+ if (is_array($data)) {
+ foreach ($data as $key => $item) {
+ $result[$key] = $this->_createFromArray($itemType, $item);
+ }
+ }
+
+ return $result;
+ }
}
diff --git a/lib/internal/Magento/Framework/Webapi/Test/Unit/ServiceInputProcessorTest.php b/lib/internal/Magento/Framework/Webapi/Test/Unit/ServiceInputProcessorTest.php
index 6f5a18916e04d..fe85e59221847 100644
--- a/lib/internal/Magento/Framework/Webapi/Test/Unit/ServiceInputProcessorTest.php
+++ b/lib/internal/Magento/Framework/Webapi/Test/Unit/ServiceInputProcessorTest.php
@@ -569,4 +569,42 @@ public function invalidCustomAttributesDataProvider()
]
];
}
+
+ /**
+ * Test if $data == '', then we have to get the same ''.
+ *
+ * @param string $data
+ * @param string $type
+ *
+ * @dataProvider convertValueWithEmptyValueDataProvider
+ */
+ public function testConvertValueWithEmptyValue($data, $type)
+ {
+ $actualData = $this->serviceInputProcessor->convertValue($data, $type);
+
+ $this->assertEquals($data, $actualData);
+ }
+
+ /**
+ * DataProvider for testConvertValueWithEmptyValue.
+ *
+ * @return array
+ */
+ public function convertValueWithEmptyValueDataProvider()
+ {
+ return [
+ [
+ '',
+ 'string'
+ ],
+ [
+ '',
+ 'int'
+ ],
+ [
+ '',
+ 'float'
+ ],
+ ];
+ }
}