diff --git a/app/code/Magento/CatalogGraphQl/Model/ProductLinksTypeResolver.php b/app/code/Magento/CatalogGraphQl/Model/ProductLinksTypeResolver.php
index 5a230ceed0ca4..c6de07bdedd19 100644
--- a/app/code/Magento/CatalogGraphQl/Model/ProductLinksTypeResolver.php
+++ b/app/code/Magento/CatalogGraphQl/Model/ProductLinksTypeResolver.php
@@ -10,7 +10,7 @@
use Magento\Framework\GraphQl\Query\Resolver\TypeResolverInterface;
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
class ProductLinksTypeResolver implements TypeResolverInterface
{
@@ -20,9 +20,9 @@ class ProductLinksTypeResolver implements TypeResolverInterface
private $linkTypes = ['related', 'upsell', 'crosssell'];
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
- public function resolveType(array $data) : string
+ public function resolveType(array $data): string
{
if (isset($data['link_type'])) {
$linkType = $data['link_type'];
diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/Product/BatchProductLinks.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/Product/BatchProductLinks.php
index 14732ecf37c63..187fd05c1001e 100644
--- a/app/code/Magento/CatalogGraphQl/Model/Resolver/Product/BatchProductLinks.php
+++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/Product/BatchProductLinks.php
@@ -22,7 +22,15 @@ class BatchProductLinks implements BatchServiceContractResolverInterface
/**
* @var string[]
*/
- private static $linkTypes = ['related', 'upsell', 'crosssell'];
+ private $linkTypes;
+
+ /**
+ * @param array $linkTypes
+ */
+ public function __construct(array $linkTypes)
+ {
+ $this->linkTypes = $linkTypes;
+ }
/**
* @inheritDoc
@@ -44,7 +52,7 @@ public function convertToServiceArgument(ResolveRequestInterface $request)
/** @var \Magento\Catalog\Model\Product $product */
$product = $value['model'];
- return new ListCriteria((string)$product->getId(), self::$linkTypes, $product);
+ return new ListCriteria((string)$product->getId(), $this->linkTypes, $product);
}
/**
diff --git a/app/code/Magento/CatalogGraphQl/etc/di.xml b/app/code/Magento/CatalogGraphQl/etc/di.xml
index 5fec7bfd4fda7..03f9d7ad03f04 100644
--- a/app/code/Magento/CatalogGraphQl/etc/di.xml
+++ b/app/code/Magento/CatalogGraphQl/etc/di.xml
@@ -74,4 +74,14 @@
+
+
+
+
+ - related
+ - upsell
+ - crosssell
+
+
+
diff --git a/app/code/Magento/CatalogImportExport/Model/Import/Product.php b/app/code/Magento/CatalogImportExport/Model/Import/Product.php
index c5fcac99767bd..189bfa61f2c42 100644
--- a/app/code/Magento/CatalogImportExport/Model/Import/Product.php
+++ b/app/code/Magento/CatalogImportExport/Model/Import/Product.php
@@ -1595,6 +1595,7 @@ protected function _saveProducts()
}
$rowSku = $rowData[self::COL_SKU];
+ $rowSkuNormalized = mb_strtolower($rowSku);
if (null === $rowSku) {
$this->getErrorAggregator()->addRowToSkip($rowNum);
@@ -1604,9 +1605,9 @@ protected function _saveProducts()
$storeId = !empty($rowData[self::COL_STORE])
? $this->getStoreIdByCode($rowData[self::COL_STORE])
: Store::DEFAULT_STORE_ID;
- $rowExistingImages = $existingImages[$storeId][$rowSku] ?? [];
+ $rowExistingImages = $existingImages[$storeId][$rowSkuNormalized] ?? [];
$rowStoreMediaGalleryValues = $rowExistingImages;
- $rowExistingImages += $existingImages[Store::DEFAULT_STORE_ID][$rowSku] ?? [];
+ $rowExistingImages += $existingImages[Store::DEFAULT_STORE_ID][$rowSkuNormalized] ?? [];
if (self::SCOPE_STORE == $rowScope) {
// set necessary data from SCOPE_DEFAULT row
@@ -1762,10 +1763,11 @@ protected function _saveProducts()
continue;
}
- if (isset($rowExistingImages[$uploadedFile])) {
- $currentFileData = $rowExistingImages[$uploadedFile];
+ $uploadedFileNormalized = ltrim($uploadedFile, '/\\');
+ if (isset($rowExistingImages[$uploadedFileNormalized])) {
+ $currentFileData = $rowExistingImages[$uploadedFileNormalized];
$currentFileData['store_id'] = $storeId;
- $storeMediaGalleryValueExists = isset($rowStoreMediaGalleryValues[$uploadedFile]);
+ $storeMediaGalleryValueExists = isset($rowStoreMediaGalleryValues[$uploadedFileNormalized]);
if (array_key_exists($uploadedFile, $imageHiddenStates)
&& $currentFileData['disabled'] != $imageHiddenStates[$uploadedFile]
) {
diff --git a/app/code/Magento/CatalogImportExport/Model/Import/Product/MediaGalleryProcessor.php b/app/code/Magento/CatalogImportExport/Model/Import/Product/MediaGalleryProcessor.php
index a94a87a44b32a..d4694b72ba64f 100644
--- a/app/code/Magento/CatalogImportExport/Model/Import/Product/MediaGalleryProcessor.php
+++ b/app/code/Magento/CatalogImportExport/Model/Import/Product/MediaGalleryProcessor.php
@@ -384,7 +384,9 @@ public function getExistingImages(array $bunch)
foreach ($this->connection->fetchAll($select) as $image) {
$storeId = $image['store_id'];
unset($image['store_id']);
- $result[$storeId][$image['sku']][$image['value']] = $image;
+ $sku = mb_strtolower($image['sku']);
+ $value = ltrim($image['value'], '/\\');
+ $result[$storeId][$sku][$value] = $image;
}
return $result;
diff --git a/app/code/Magento/Elasticsearch/Elasticsearch5/Model/Adapter/FieldMapper/Product/FieldProvider/FieldType/Converter.php b/app/code/Magento/Elasticsearch/Elasticsearch5/Model/Adapter/FieldMapper/Product/FieldProvider/FieldType/Converter.php
index 1f6e05c9e02fc..8576d8df0cc95 100644
--- a/app/code/Magento/Elasticsearch/Elasticsearch5/Model/Adapter/FieldMapper/Product/FieldProvider/FieldType/Converter.php
+++ b/app/code/Magento/Elasticsearch/Elasticsearch5/Model/Adapter/FieldMapper/Product/FieldProvider/FieldType/Converter.php
@@ -19,7 +19,7 @@ class Converter implements ConverterInterface
*/
private const ES_DATA_TYPE_TEXT = 'text';
private const ES_DATA_TYPE_KEYWORD = 'keyword';
- private const ES_DATA_TYPE_FLOAT = 'float';
+ private const ES_DATA_TYPE_DOUBLE = 'double';
private const ES_DATA_TYPE_INT = 'integer';
private const ES_DATA_TYPE_DATE = 'date';
/**#@-*/
@@ -32,7 +32,7 @@ class Converter implements ConverterInterface
private $mapping = [
self::INTERNAL_DATA_TYPE_STRING => self::ES_DATA_TYPE_TEXT,
self::INTERNAL_DATA_TYPE_KEYWORD => self::ES_DATA_TYPE_KEYWORD,
- self::INTERNAL_DATA_TYPE_FLOAT => self::ES_DATA_TYPE_FLOAT,
+ self::INTERNAL_DATA_TYPE_FLOAT => self::ES_DATA_TYPE_DOUBLE,
self::INTERNAL_DATA_TYPE_INT => self::ES_DATA_TYPE_INT,
self::INTERNAL_DATA_TYPE_DATE => self::ES_DATA_TYPE_DATE,
];
diff --git a/app/code/Magento/Elasticsearch/Elasticsearch5/Model/Client/Elasticsearch.php b/app/code/Magento/Elasticsearch/Elasticsearch5/Model/Client/Elasticsearch.php
index bd9a380230652..8d8787a5eff72 100644
--- a/app/code/Magento/Elasticsearch/Elasticsearch5/Model/Client/Elasticsearch.php
+++ b/app/code/Magento/Elasticsearch/Elasticsearch5/Model/Client/Elasticsearch.php
@@ -276,7 +276,7 @@ public function addFieldsMapping(array $fields, $index, $entityType)
'match' => 'price_*',
'match_mapping_type' => 'string',
'mapping' => [
- 'type' => 'float',
+ 'type' => 'double',
'store' => true,
],
],
diff --git a/app/code/Magento/Elasticsearch/Model/Adapter/FieldMapper/Product/FieldProvider/FieldType/Converter.php b/app/code/Magento/Elasticsearch/Model/Adapter/FieldMapper/Product/FieldProvider/FieldType/Converter.php
index 88dab83698794..2067dcdc7fe9f 100644
--- a/app/code/Magento/Elasticsearch/Model/Adapter/FieldMapper/Product/FieldProvider/FieldType/Converter.php
+++ b/app/code/Magento/Elasticsearch/Model/Adapter/FieldMapper/Product/FieldProvider/FieldType/Converter.php
@@ -16,7 +16,7 @@ class Converter implements ConverterInterface
* Text flags for Elasticsearch field types
*/
private const ES_DATA_TYPE_STRING = 'string';
- private const ES_DATA_TYPE_FLOAT = 'float';
+ private const ES_DATA_TYPE_DOUBLE = 'double';
private const ES_DATA_TYPE_INT = 'integer';
private const ES_DATA_TYPE_DATE = 'date';
/**#@-*/
@@ -29,7 +29,7 @@ class Converter implements ConverterInterface
private $mapping = [
self::INTERNAL_DATA_TYPE_STRING => self::ES_DATA_TYPE_STRING,
self::INTERNAL_DATA_TYPE_KEYWORD => self::ES_DATA_TYPE_STRING,
- self::INTERNAL_DATA_TYPE_FLOAT => self::ES_DATA_TYPE_FLOAT,
+ self::INTERNAL_DATA_TYPE_FLOAT => self::ES_DATA_TYPE_DOUBLE,
self::INTERNAL_DATA_TYPE_INT => self::ES_DATA_TYPE_INT,
self::INTERNAL_DATA_TYPE_DATE => self::ES_DATA_TYPE_DATE,
];
diff --git a/app/code/Magento/Elasticsearch/Setup/Patch/Data/InvalidateIndex.php b/app/code/Magento/Elasticsearch/Setup/Patch/Data/InvalidateIndex.php
new file mode 100644
index 0000000000000..7cd72c322d647
--- /dev/null
+++ b/app/code/Magento/Elasticsearch/Setup/Patch/Data/InvalidateIndex.php
@@ -0,0 +1,66 @@
+moduleDataSetup = $moduleDataSetup;
+ $this->indexerRegistry = $indexerRegistry;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function apply(): PatchInterface
+ {
+ $this->indexerRegistry->get(FulltextIndexer::INDEXER_ID)->invalidate();
+ return $this;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public static function getDependencies(): array
+ {
+ return [];
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function getAliases(): array
+ {
+ return [];
+ }
+}
diff --git a/app/code/Magento/Elasticsearch/Test/Unit/Elasticsearch5/Model/Client/ElasticsearchTest.php b/app/code/Magento/Elasticsearch/Test/Unit/Elasticsearch5/Model/Client/ElasticsearchTest.php
index 49a894f1295c7..575a64dc43abd 100644
--- a/app/code/Magento/Elasticsearch/Test/Unit/Elasticsearch5/Model/Client/ElasticsearchTest.php
+++ b/app/code/Magento/Elasticsearch/Test/Unit/Elasticsearch5/Model/Client/ElasticsearchTest.php
@@ -329,7 +329,7 @@ public function testAddFieldsMapping()
'match' => 'price_*',
'match_mapping_type' => 'string',
'mapping' => [
- 'type' => 'float',
+ 'type' => 'double',
'store' => true,
],
],
@@ -400,7 +400,7 @@ public function testAddFieldsMappingFailure()
'match' => 'price_*',
'match_mapping_type' => 'string',
'mapping' => [
- 'type' => 'float',
+ 'type' => 'double',
'store' => true,
],
],
diff --git a/app/code/Magento/Elasticsearch/Test/Unit/Model/Adapter/FieldMapper/Product/FieldProvider/DynamicFieldTest.php b/app/code/Magento/Elasticsearch/Test/Unit/Model/Adapter/FieldMapper/Product/FieldProvider/DynamicFieldTest.php
index 87f072836544e..a9bcd1a20a1b2 100644
--- a/app/code/Magento/Elasticsearch/Test/Unit/Model/Adapter/FieldMapper/Product/FieldProvider/DynamicFieldTest.php
+++ b/app/code/Magento/Elasticsearch/Test/Unit/Model/Adapter/FieldMapper/Product/FieldProvider/DynamicFieldTest.php
@@ -246,7 +246,7 @@ function ($type) use ($complexType) {
if ($type === 'string') {
return 'string';
} elseif ($type === 'float') {
- return 'float';
+ return 'double';
} elseif ($type === 'integer') {
return 'integer';
} else {
@@ -281,7 +281,7 @@ public function attributeProvider()
'index' => 'no_index'
],
'price_1_1' => [
- 'type' => 'float',
+ 'type' => 'double',
'store' => true
]
]
@@ -300,7 +300,7 @@ public function attributeProvider()
'index' => 'no_index'
],
'price_1_1' => [
- 'type' => 'float',
+ 'type' => 'double',
'store' => true
]
],
@@ -319,7 +319,7 @@ public function attributeProvider()
'index' => 'no_index'
],
'price_1_1' => [
- 'type' => 'float',
+ 'type' => 'double',
'store' => true
]
]
diff --git a/app/code/Magento/Elasticsearch/Test/Unit/Model/Adapter/FieldMapper/Product/FieldProvider/FieldType/ConverterTest.php b/app/code/Magento/Elasticsearch/Test/Unit/Model/Adapter/FieldMapper/Product/FieldProvider/FieldType/ConverterTest.php
index 75b79bc43e805..718adf255254f 100644
--- a/app/code/Magento/Elasticsearch/Test/Unit/Model/Adapter/FieldMapper/Product/FieldProvider/FieldType/ConverterTest.php
+++ b/app/code/Magento/Elasticsearch/Test/Unit/Model/Adapter/FieldMapper/Product/FieldProvider/FieldType/ConverterTest.php
@@ -56,7 +56,7 @@ public function convertProvider()
{
return [
['string', 'string'],
- ['float', 'float'],
+ ['float', 'double'],
['integer', 'integer'],
];
}
diff --git a/app/code/Magento/Elasticsearch6/Model/Client/Elasticsearch.php b/app/code/Magento/Elasticsearch6/Model/Client/Elasticsearch.php
index 2c1c283c5b24d..0571b075aff28 100644
--- a/app/code/Magento/Elasticsearch6/Model/Client/Elasticsearch.php
+++ b/app/code/Magento/Elasticsearch6/Model/Client/Elasticsearch.php
@@ -281,7 +281,7 @@ public function addFieldsMapping(array $fields, $index, $entityType)
'match' => 'price_*',
'match_mapping_type' => 'string',
'mapping' => [
- 'type' => 'float',
+ 'type' => 'double',
'store' => true,
],
],
diff --git a/app/code/Magento/Elasticsearch6/Test/Unit/Model/Client/ElasticsearchTest.php b/app/code/Magento/Elasticsearch6/Test/Unit/Model/Client/ElasticsearchTest.php
index aa0b49400c517..2a7fa2ce8114a 100644
--- a/app/code/Magento/Elasticsearch6/Test/Unit/Model/Client/ElasticsearchTest.php
+++ b/app/code/Magento/Elasticsearch6/Test/Unit/Model/Client/ElasticsearchTest.php
@@ -439,7 +439,7 @@ public function testAddFieldsMapping()
'match' => 'price_*',
'match_mapping_type' => 'string',
'mapping' => [
- 'type' => 'float',
+ 'type' => 'double',
'store' => true,
],
],
@@ -509,7 +509,7 @@ public function testAddFieldsMappingFailure()
'match' => 'price_*',
'match_mapping_type' => 'string',
'mapping' => [
- 'type' => 'float',
+ 'type' => 'double',
'store' => true,
],
],
diff --git a/app/code/Magento/Elasticsearch7/Model/Client/Elasticsearch.php b/app/code/Magento/Elasticsearch7/Model/Client/Elasticsearch.php
index feacca8d62804..4b318f987abfe 100644
--- a/app/code/Magento/Elasticsearch7/Model/Client/Elasticsearch.php
+++ b/app/code/Magento/Elasticsearch7/Model/Client/Elasticsearch.php
@@ -289,7 +289,7 @@ public function addFieldsMapping(array $fields, string $index, string $entityTyp
'match' => 'price_*',
'match_mapping_type' => 'string',
'mapping' => [
- 'type' => 'float',
+ 'type' => 'double',
'store' => true,
],
],
diff --git a/app/code/Magento/Elasticsearch7/Test/Unit/Model/Client/ElasticsearchTest.php b/app/code/Magento/Elasticsearch7/Test/Unit/Model/Client/ElasticsearchTest.php
index 593bbd7792f46..091387f844d55 100644
--- a/app/code/Magento/Elasticsearch7/Test/Unit/Model/Client/ElasticsearchTest.php
+++ b/app/code/Magento/Elasticsearch7/Test/Unit/Model/Client/ElasticsearchTest.php
@@ -438,7 +438,7 @@ public function testAddFieldsMapping()
'match' => 'price_*',
'match_mapping_type' => 'string',
'mapping' => [
- 'type' => 'float',
+ 'type' => 'double',
'store' => true,
],
],
@@ -509,7 +509,7 @@ public function testAddFieldsMappingFailure()
'match' => 'price_*',
'match_mapping_type' => 'string',
'mapping' => [
- 'type' => 'float',
+ 'type' => 'double',
'store' => true,
],
],
diff --git a/app/code/Magento/GroupedProductGraphQl/Model/GroupedProductLinksTypeResolver.php b/app/code/Magento/GroupedProductGraphQl/Model/GroupedProductLinksTypeResolver.php
index 92cfb375fea41..29fa2bffabb3b 100644
--- a/app/code/Magento/GroupedProductGraphQl/Model/GroupedProductLinksTypeResolver.php
+++ b/app/code/Magento/GroupedProductGraphQl/Model/GroupedProductLinksTypeResolver.php
@@ -10,7 +10,7 @@
use Magento\Framework\GraphQl\Query\Resolver\TypeResolverInterface;
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
class GroupedProductLinksTypeResolver implements TypeResolverInterface
{
@@ -20,14 +20,14 @@ class GroupedProductLinksTypeResolver implements TypeResolverInterface
private $linkTypes = ['associated'];
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
- public function resolveType(array $data) : string
+ public function resolveType(array $data): string
{
if (isset($data['link_type'])) {
$linkType = $data['link_type'];
if (in_array($linkType, $this->linkTypes)) {
- return 'GroupedProductLinks';
+ return 'ProductLinks';
}
}
return '';
diff --git a/app/code/Magento/GroupedProductGraphQl/etc/di.xml b/app/code/Magento/GroupedProductGraphQl/etc/di.xml
index 35b63370baf2f..717bc14826f70 100644
--- a/app/code/Magento/GroupedProductGraphQl/etc/di.xml
+++ b/app/code/Magento/GroupedProductGraphQl/etc/di.xml
@@ -13,4 +13,11 @@
+
+
+
+ - associated
+
+
+
diff --git a/app/code/Magento/Payment/Block/Transparent/Redirect.php b/app/code/Magento/Payment/Block/Transparent/Redirect.php
index 1be6dec4cc1d8..97a09df38d120 100644
--- a/app/code/Magento/Payment/Block/Transparent/Redirect.php
+++ b/app/code/Magento/Payment/Block/Transparent/Redirect.php
@@ -53,10 +53,21 @@ public function getRedirectUrl(): string
/**
* Returns params to be redirected.
*
+ * Encodes invalid UTF-8 values to UTF-8 to prevent character escape error.
+ * Some payment methods like PayPal, send data in merchant defined language encoding
+ * which can be different from the system character encoding (UTF-8).
+ *
* @return array
*/
public function getPostParams(): array
{
- return (array)$this->_request->getPostValue();
+ $params = [];
+ foreach ($this->_request->getPostValue() as $name => $value) {
+ if (!empty($value) && mb_detect_encoding($value, 'UTF-8', true) === false) {
+ $value = utf8_encode($value);
+ }
+ $params[$name] = $value;
+ }
+ return $params;
}
}
diff --git a/app/code/Magento/Payment/Test/Unit/Block/Transparent/RedirectTest.php b/app/code/Magento/Payment/Test/Unit/Block/Transparent/RedirectTest.php
new file mode 100644
index 0000000000000..1cd1230a14634
--- /dev/null
+++ b/app/code/Magento/Payment/Test/Unit/Block/Transparent/RedirectTest.php
@@ -0,0 +1,102 @@
+context = $this->createMock(\Magento\Framework\View\Element\Template\Context::class);
+ $this->request = $this->createMock(\Magento\Framework\App\Request\Http::class);
+ $this->context->method('getRequest')
+ ->willReturn($this->request);
+ $this->url = $this->createMock(\Magento\Framework\UrlInterface::class);
+ $this->model = new Redirect(
+ $this->context,
+ $this->url
+ );
+ }
+
+ /**
+ * @param array $postData
+ * @param array $expected
+ * @dataProvider getPostParamsDataProvider
+ */
+ public function testGetPostParams(array $postData, array $expected): void
+ {
+ $this->request->method('getPostValue')
+ ->willReturn($postData);
+ $this->assertEquals($expected, $this->model->getPostParams());
+ }
+
+ /**
+ * @return array
+ */
+ public function getPostParamsDataProvider(): array
+ {
+ return [
+ [
+ [
+ 'BILLTOEMAIL' => 'john.doe@magento.lo',
+ 'BILLTOSTREET' => '3640 Holdrege Ave',
+ 'BILLTOZIP' => '90016',
+ 'BILLTOLASTNAME' => 'Ãtienne',
+ 'BILLTOFIRSTNAME' => 'Ãillin',
+ ],
+ [
+ 'BILLTOEMAIL' => 'john.doe@magento.lo',
+ 'BILLTOSTREET' => '3640 Holdrege Ave',
+ 'BILLTOZIP' => '90016',
+ 'BILLTOLASTNAME' => 'Ãtienne',
+ 'BILLTOFIRSTNAME' => 'Ãillin',
+ ]
+ ],
+ [
+ [
+ 'BILLTOEMAIL' => 'john.doe@magento.lo',
+ 'BILLTOSTREET' => '3640 Holdrege Ave',
+ 'BILLTOZIP' => '90016',
+ 'BILLTOLASTNAME' => mb_convert_encoding('Ãtienne', 'ISO-8859-1'),
+ 'BILLTOFIRSTNAME' => mb_convert_encoding('Ãillin', 'ISO-8859-1'),
+ ],
+ [
+ 'BILLTOEMAIL' => 'john.doe@magento.lo',
+ 'BILLTOSTREET' => '3640 Holdrege Ave',
+ 'BILLTOZIP' => '90016',
+ 'BILLTOLASTNAME' => 'Ãtienne',
+ 'BILLTOFIRSTNAME' => 'Ãillin',
+ ]
+ ]
+ ];
+ }
+}
diff --git a/app/code/Magento/Quote/Model/ResourceModel/Quote.php b/app/code/Magento/Quote/Model/ResourceModel/Quote.php
index 48945dacd1738..749e9944a6ad3 100644
--- a/app/code/Magento/Quote/Model/ResourceModel/Quote.php
+++ b/app/code/Magento/Quote/Model/ResourceModel/Quote.php
@@ -230,7 +230,8 @@ public function subtractProductFromQuotes($product)
'items_qty' => new \Zend_Db_Expr(
$connection->quoteIdentifier('q.items_qty') . ' - ' . $connection->quoteIdentifier('qi.qty')
),
- 'items_count' => new \Zend_Db_Expr($ifSql)
+ 'items_count' => new \Zend_Db_Expr($ifSql),
+ 'updated_at' => 'q.updated_at',
]
)->join(
['qi' => $this->getTable('quote_item')],
@@ -277,21 +278,27 @@ public function markQuotesRecollect($productIds)
{
$tableQuote = $this->getTable('quote');
$tableItem = $this->getTable('quote_item');
- $subSelect = $this->getConnection()->select()->from(
- $tableItem,
- ['entity_id' => 'quote_id']
- )->where(
- 'product_id IN ( ? )',
- $productIds
- )->group(
- 'quote_id'
- );
-
- $select = $this->getConnection()->select()->join(
- ['t2' => $subSelect],
- 't1.entity_id = t2.entity_id',
- ['trigger_recollect' => new \Zend_Db_Expr('1')]
- );
+ $subSelect = $this->getConnection()
+ ->select()
+ ->from(
+ $tableItem,
+ ['entity_id' => 'quote_id']
+ )->where(
+ 'product_id IN ( ? )',
+ $productIds
+ )->group(
+ 'quote_id'
+ );
+ $select = $this->getConnection()
+ ->select()
+ ->join(
+ ['t2' => $subSelect],
+ 't1.entity_id = t2.entity_id',
+ [
+ 'trigger_recollect' => new \Zend_Db_Expr('1'),
+ 'updated_at' => 't1.updated_at',
+ ]
+ );
$updateQuery = $select->crossUpdateFromSelect(['t1' => $tableQuote]);
$this->getConnection()->query($updateQuery);
diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/GroupedProduct/GroupedProductViewTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/GroupedProduct/GroupedProductViewTest.php
index e6db0b9e808ef..8cb0a6db972b4 100644
--- a/dev/tests/api-functional/testsuite/Magento/GraphQl/GroupedProduct/GroupedProductViewTest.php
+++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/GroupedProduct/GroupedProductViewTest.php
@@ -7,12 +7,29 @@
namespace Magento\GraphQl\GroupedProduct;
+use Magento\Catalog\Api\Data\ProductInterface;
use Magento\Catalog\Api\ProductRepositoryInterface;
use Magento\TestFramework\ObjectManager;
use Magento\TestFramework\TestCase\GraphQlAbstract;
+/**
+ * Class to test GraphQl response with grouped products
+ */
class GroupedProductViewTest extends GraphQlAbstract
{
+ /**
+ * @var ProductRepositoryInterface
+ */
+ private $productRepository;
+
+ /**
+ * @inheritdoc
+ */
+ protected function setUp(): void
+ {
+ parent::setUp();
+ $this->productRepository = ObjectManager::getInstance()->get(ProductRepositoryInterface::class);
+ }
/**
* @magentoApiDataFixture Magento/GroupedProduct/_files/product_grouped.php
@@ -20,17 +37,16 @@ class GroupedProductViewTest extends GraphQlAbstract
public function testAllFieldsGroupedProduct()
{
$productSku = 'grouped-product';
- $query
- = <<graphQlQuery($query);
- /** @var ProductRepositoryInterface $productRepository */
- $productRepository = ObjectManager::getInstance()->get(ProductRepositoryInterface::class);
- $groupedProduct = $productRepository->get($productSku, false, null, true);
+ $groupedProduct = $this->productRepository->get($productSku, false, null, true);
- $this->assertGroupedProductItems($groupedProduct, $response['products']['items'][0]);
+ $this->assertNotEmpty(
+ $response['products']['items'][0]['items'],
+ "Precondition failed: 'Grouped product items' must not be empty"
+ );
+ $this->assertGroupedProductItems($groupedProduct, $response['products']['items'][0]['items']);
+ $this->assertNotEmpty(
+ $response['products']['items'][0]['product_links'],
+ "Precondition failed: 'Linked product items' must not be empty"
+ );
+ $this->assertProductLinks($groupedProduct, $response['products']['items'][0]['product_links']);
}
- private function assertGroupedProductItems($product, $actualResponse)
+ /**
+ * @param ProductInterface $product
+ * @param array $items
+ */
+ private function assertGroupedProductItems(ProductInterface $product, array $items): void
{
- $this->assertNotEmpty(
- $actualResponse['items'],
- "Precondition failed: 'grouped product items' must not be empty"
- );
- $this->assertCount(2, $actualResponse['items']);
+ $this->assertCount(2, $items);
$groupedProductLinks = $product->getProductLinks();
- foreach ($actualResponse['items'] as $itemIndex => $bundleItems) {
- $this->assertNotEmpty($bundleItems);
+ foreach ($items as $itemIndex => $bundleItem) {
+ $this->assertNotEmpty($bundleItem);
$associatedProductSku = $groupedProductLinks[$itemIndex]->getLinkedProductSku();
-
- $productsRepository = ObjectManager::getInstance()->get(ProductRepositoryInterface::class);
- /** @var \Magento\Catalog\Model\Product $associatedProduct */
- $associatedProduct = $productsRepository->get($associatedProductSku);
+ $associatedProduct = $this->productRepository->get($associatedProductSku);
$this->assertEquals(
$groupedProductLinks[$itemIndex]->getExtensionAttributes()->getQty(),
- $actualResponse['items'][$itemIndex]['qty']
+ $bundleItem['qty']
);
$this->assertEquals(
$groupedProductLinks[$itemIndex]->getPosition(),
- $actualResponse['items'][$itemIndex]['position']
+ $bundleItem['position']
);
$this->assertResponseFields(
- $actualResponse['items'][$itemIndex]['product'],
+ $bundleItem['product'],
[
- 'sku' => $associatedProductSku,
- 'type_id' => $groupedProductLinks[$itemIndex]->getLinkedProductType(),
- 'url_key'=> $associatedProduct->getUrlKey(),
- 'name' => $associatedProduct->getName()
+ 'sku' => $associatedProductSku,
+ 'type_id' => $groupedProductLinks[$itemIndex]->getLinkedProductType(),
+ 'url_key'=> $associatedProduct->getUrlKey(),
+ 'name' => $associatedProduct->getName()
]
);
}
}
+
+ /**
+ * @param ProductInterface $product
+ * @param array $links
+ * @return void
+ */
+ private function assertProductLinks(ProductInterface $product, array $links): void
+ {
+ $this->assertCount(2, $links);
+ $productLinks = $product->getProductLinks();
+ foreach ($links as $itemIndex => $linkedItem) {
+ $this->assertNotEmpty($linkedItem);
+ $this->assertEquals(
+ $productLinks[$itemIndex]->getPosition(),
+ $linkedItem['position']
+ );
+ $this->assertEquals(
+ $productLinks[$itemIndex]->getLinkedProductSku(),
+ $linkedItem['linked_product_sku']
+ );
+ $this->assertEquals(
+ $productLinks[$itemIndex]->getLinkType(),
+ $linkedItem['link_type']
+ );
+ }
+ }
}
diff --git a/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/ProductTest.php b/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/ProductTest.php
index 9dee418f010a8..d3f012bb0852f 100644
--- a/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/ProductTest.php
+++ b/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/ProductTest.php
@@ -3196,4 +3196,29 @@ public function testImportProductsWithLinksInDifferentBunches()
}
$this->assertEquals($linksData, $importedProductLinks);
}
+
+ /**
+ * Tests that image name does not have to be prefixed by slash
+ *
+ * @magentoDataFixture mediaImportImageFixture
+ * @magentoDataFixture Magento/Store/_files/core_fixturestore.php
+ * @magentoDataFixture Magento/Catalog/_files/product_with_image.php
+ */
+ public function testUpdateImageByNameNotPrefixedWithSlash()
+ {
+ $expectedLabelForDefaultStoreView = 'image label updated';
+ $expectedImageFile = '/m/a/magento_image.jpg';
+ $secondStoreCode = 'fixturestore';
+ $productSku = 'simple';
+ $this->importDataForMediaTest('import_image_name_without_slash.csv');
+ $product = $this->getProductBySku($productSku);
+ $imageItems = $product->getMediaGalleryImages()->getItems();
+ $this->assertCount(1, $imageItems);
+ $imageItem = array_shift($imageItems);
+ $this->assertEquals($expectedImageFile, $imageItem->getFile());
+ $this->assertEquals($expectedLabelForDefaultStoreView, $imageItem->getLabel());
+ $product = $this->getProductBySku($productSku, $secondStoreCode);
+ $imageItems = $product->getMediaGalleryImages()->getItems();
+ $this->assertCount(0, $imageItems);
+ }
}
diff --git a/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/_files/import_image_name_without_slash.csv b/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/_files/import_image_name_without_slash.csv
new file mode 100644
index 0000000000000..415501daf89d8
--- /dev/null
+++ b/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/_files/import_image_name_without_slash.csv
@@ -0,0 +1,3 @@
+"sku","store_view_code","base_image","base_image_label","hide_from_product_page"
+"simple",,"m/a/magento_image.jpg","image label updated",
+"simple","fixturestore",,,"m/a/magento_image.jpg"
diff --git a/dev/tests/integration/testsuite/Magento/Quote/Model/Product/Plugin/UpdateQuoteItemsTest.php b/dev/tests/integration/testsuite/Magento/Quote/Model/Product/Plugin/UpdateQuoteItemsTest.php
new file mode 100644
index 0000000000000..3aadad7e9ebec
--- /dev/null
+++ b/dev/tests/integration/testsuite/Magento/Quote/Model/Product/Plugin/UpdateQuoteItemsTest.php
@@ -0,0 +1,78 @@
+getQuoteByReservedOrderId = $objectManager->get(GetQuoteByReservedOrderId::class);
+ $this->productRepository = $objectManager->get(ProductRepository::class);
+ }
+
+ /**
+ * Test to mark the quote as need to recollect and doesn't update the field "updated_at" after change product price
+ *
+ * @magentoDataFixture Magento/Checkout/_files/quote_with_simple_product_saved.php
+ * @return void
+ */
+ public function testMarkQuoteRecollectAfterChangeProductPrice(): void
+ {
+ $quote = $this->getQuoteByReservedOrderId->execute('test_order_with_simple_product_without_address');
+ $this->assertNotNull($quote);
+ $this->assertFalse((bool)$quote->getTriggerRecollect());
+ $this->assertNotEmpty($quote->getItems());
+ $quoteItem = current($quote->getItems());
+ $product = $quoteItem->getProduct();
+
+ $product->setPrice((float)$product->getPrice() + 10);
+ $this->productRepository->save($product);
+
+ /** @var AdapterInterface $connection */
+ $connection = $quote->getResource()->getConnection();
+ $select = $connection->select()
+ ->from(
+ $connection->getTableName('quote'),
+ ['updated_at', 'trigger_recollect']
+ )->where(
+ "reserved_order_id = 'test_order_with_simple_product_without_address'"
+ );
+
+ $quoteRow = $connection->fetchRow($select);
+ $this->assertNotEmpty($quoteRow);
+ $this->assertTrue((bool)$quoteRow['trigger_recollect']);
+ $this->assertEquals($quote->getUpdatedAt(), $quoteRow['updated_at']);
+ }
+}