diff --git a/.gitignore b/.gitignore
index 68d38d9ca7817..8ec1104f25535 100644
--- a/.gitignore
+++ b/.gitignore
@@ -48,6 +48,8 @@ atlassian*
/pub/media/import/*
!/pub/media/import/.htaccess
/pub/media/logo/*
+/pub/media/custom_options/*
+!/pub/media/custom_options/.htaccess
/pub/media/theme/*
/pub/media/theme_customization/*
!/pub/media/theme_customization/.htaccess
diff --git a/app/code/Magento/Backend/Block/Widget/Grid/Massaction/AbstractMassaction.php b/app/code/Magento/Backend/Block/Widget/Grid/Massaction/AbstractMassaction.php
index 9890a10a4ceb0..891b2a3ada724 100644
--- a/app/code/Magento/Backend/Block/Widget/Grid/Massaction/AbstractMassaction.php
+++ b/app/code/Magento/Backend/Block/Widget/Grid/Massaction/AbstractMassaction.php
@@ -282,25 +282,23 @@ public function getGridIdsJson()
if (!$this->getUseSelectAll()) {
return '';
}
- /** @var \Magento\Framework\Data\Collection $allIdsCollection */
- $allIdsCollection = clone $this->getParentBlock()->getCollection();
- if ($this->getMassactionIdField()) {
- $massActionIdField = $this->getMassactionIdField();
+ /** @var \Magento\Framework\Model\ResourceModel\Db\Collection\AbstractCollection $collection */
+ $collection = clone $this->getParentBlock()->getCollection();
+
+ if ($collection instanceof AbstractDb) {
+ $idsSelect = clone $collection->getSelect();
+ $idsSelect->reset(\Magento\Framework\DB\Select::ORDER);
+ $idsSelect->reset(\Magento\Framework\DB\Select::LIMIT_COUNT);
+ $idsSelect->reset(\Magento\Framework\DB\Select::LIMIT_OFFSET);
+ $idsSelect->reset(\Magento\Framework\DB\Select::COLUMNS);
+ $idsSelect->columns($this->getMassactionIdField(), 'main_table');
+ $idList = $collection->getConnection()->fetchCol($idsSelect);
} else {
- $massActionIdField = $this->getParentBlock()->getMassactionIdField();
+ $idList = $collection->setPageSize(0)->getColumnValues($this->getMassactionIdField());
}
- if ($allIdsCollection instanceof AbstractDb) {
- $allIdsCollection->getSelect()->limit();
- $allIdsCollection->clear();
- }
-
- $gridIds = $allIdsCollection->setPageSize(0)->getColumnValues($massActionIdField);
- if (!empty($gridIds)) {
- return join(",", $gridIds);
- }
- return '';
+ return implode(',', $idList);
}
/**
diff --git a/app/code/Magento/Backend/Test/Unit/Block/Widget/Grid/MassactionTest.php b/app/code/Magento/Backend/Test/Unit/Block/Widget/Grid/MassactionTest.php
index e8143b5f6b43a..e62b73f39241d 100644
--- a/app/code/Magento/Backend/Test/Unit/Block/Widget/Grid/MassactionTest.php
+++ b/app/code/Magento/Backend/Test/Unit/Block/Widget/Grid/MassactionTest.php
@@ -269,62 +269,6 @@ public function testGetGridIdsJsonWithoutUseSelectAll()
$this->assertEmpty($this->_block->getGridIdsJson());
}
- /**
- * @param array $items
- * @param string $result
- *
- * @dataProvider dataProviderGetGridIdsJsonWithUseSelectAll
- */
- public function testGetGridIdsJsonWithUseSelectAll(array $items, $result)
- {
- $this->_block->setUseSelectAll(true);
-
- if ($this->_block->getMassactionIdField()) {
- $massActionIdField = $this->_block->getMassactionIdField();
- } else {
- $massActionIdField = $this->_block->getParentBlock()->getMassactionIdField();
- }
-
- $collectionMock = $this->getMockBuilder(\Magento\Framework\Data\Collection::class)
- ->disableOriginalConstructor()
- ->getMock();
-
- $this->_gridMock->expects($this->once())
- ->method('getCollection')
- ->willReturn($collectionMock);
- $collectionMock->expects($this->once())
- ->method('setPageSize')
- ->with(0)
- ->willReturnSelf();
- $collectionMock->expects($this->once())
- ->method('getColumnValues')
- ->with($massActionIdField)
- ->willReturn($items);
-
- $this->assertEquals($result, $this->_block->getGridIdsJson());
- }
-
- /**
- * @return array
- */
- public function dataProviderGetGridIdsJsonWithUseSelectAll()
- {
- return [
- [
- [],
- '',
- ],
- [
- [1],
- '1',
- ],
- [
- [1, 2, 3],
- '1,2,3',
- ],
- ];
- }
-
/**
* @param string $itemId
* @param array|\Magento\Framework\DataObject $item
diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Save.php b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Save.php
index e84d9ff12906e..825d0ee032d6c 100644
--- a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Save.php
+++ b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Save.php
@@ -159,6 +159,7 @@ public function execute()
if ($redirectBack === 'duplicate') {
$product->unsetData('quantity_and_stock_status');
$newProduct = $this->productCopier->copy($product);
+ $this->checkUniqueAttributes($product);
$this->messageManager->addSuccessMessage(__('You duplicated the product.'));
}
} catch (\Magento\Framework\Exception\LocalizedException $e) {
@@ -343,4 +344,25 @@ private function persistMediaData(ProductInterface $product, array $data)
return $data;
}
+
+ /**
+ * Check unique attributes and add error to message manager
+ *
+ * @param \Magento\Catalog\Model\Product $product
+ */
+ private function checkUniqueAttributes(\Magento\Catalog\Model\Product $product)
+ {
+ $uniqueLabels = [];
+ foreach ($product->getAttributes() as $attribute) {
+ if ($attribute->getIsUnique() && $attribute->getIsUserDefined()
+ && $product->getData($attribute->getAttributeCode()) !== null
+ ) {
+ $uniqueLabels[] = $attribute->getDefaultFrontendLabel();
+ }
+ }
+ if ($uniqueLabels) {
+ $uniqueLabels = implode('", "', $uniqueLabels);
+ $this->messageManager->addErrorMessage(__('The value of attribute(s) "%1" must be unique', $uniqueLabels));
+ }
+ }
}
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateCustomProductAttributeWithDropdownFieldTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateCustomProductAttributeWithDropdownFieldTest.xml
index 09b49011938e8..a3f543e9cf32a 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateCustomProductAttributeWithDropdownFieldTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateCustomProductAttributeWithDropdownFieldTest.xml
@@ -16,6 +16,9 @@
+
+
+
diff --git a/app/code/Magento/Catalog/etc/adminhtml/system.xml b/app/code/Magento/Catalog/etc/adminhtml/system.xml
index 7a05601fcd666..1d563244f1432 100644
--- a/app/code/Magento/Catalog/etc/adminhtml/system.xml
+++ b/app/code/Magento/Catalog/etc/adminhtml/system.xml
@@ -59,7 +59,7 @@
Comma-separated.
- validate-per-page-value-list
+ validate-per-page-value-list required-entry
@@ -69,7 +69,7 @@
Comma-separated.
- validate-per-page-value-list
+ validate-per-page-value-list required-entry
diff --git a/app/code/Magento/CatalogUrlRewrite/Model/Products/AdaptUrlRewritesToVisibilityAttribute.php b/app/code/Magento/CatalogUrlRewrite/Model/Products/AdaptUrlRewritesToVisibilityAttribute.php
new file mode 100644
index 0000000000000..f5851cf1e11b1
--- /dev/null
+++ b/app/code/Magento/CatalogUrlRewrite/Model/Products/AdaptUrlRewritesToVisibilityAttribute.php
@@ -0,0 +1,127 @@
+productCollectionFactory = $collectionFactory;
+ $this->urlRewriteGenerator = $urlRewriteGenerator;
+ $this->urlPersist = $urlPersist;
+ $this->urlPathGenerator = $urlPathGenerator;
+ }
+
+ /**
+ * Process Url Rewrites according to the products visibility attribute
+ *
+ * @param array $productIds
+ * @param int $visibility
+ * @throws UrlAlreadyExistsException
+ */
+ public function execute(array $productIds, int $visibility): void
+ {
+ $products = $this->getProductsByIds($productIds);
+
+ /** @var Product $product */
+ foreach ($products as $product) {
+ if ($visibility == Visibility::VISIBILITY_NOT_VISIBLE) {
+ $this->urlPersist->deleteByData(
+ [
+ UrlRewrite::ENTITY_ID => $product->getId(),
+ UrlRewrite::ENTITY_TYPE => ProductUrlRewriteGenerator::ENTITY_TYPE,
+ ]
+ );
+ } elseif ($visibility !== Visibility::VISIBILITY_NOT_VISIBLE) {
+ $product->setVisibility($visibility);
+ $productUrlPath = $this->urlPathGenerator->getUrlPath($product);
+ $productUrlRewrite = $this->urlRewriteGenerator->generate($product);
+ $product->unsUrlPath();
+ $product->setUrlPath($productUrlPath);
+
+ try {
+ $this->urlPersist->replace($productUrlRewrite);
+ } catch (UrlAlreadyExistsException $e) {
+ throw new UrlAlreadyExistsException(
+ __(
+ 'Can not change the visibility of the product with SKU equals "%1". '
+ . 'URL key "%2" for specified store already exists.',
+ $product->getSku(),
+ $product->getUrlKey()
+ ),
+ $e,
+ $e->getCode(),
+ $e->getUrls()
+ );
+ }
+ }
+ }
+ }
+
+ /**
+ * Get Product Models by Id's
+ *
+ * @param array $productIds
+ * @return array
+ */
+ private function getProductsByIds(array $productIds): array
+ {
+ $productCollection = $this->productCollectionFactory->create();
+ $productCollection->addAttributeToSelect(ProductInterface::VISIBILITY);
+ $productCollection->addAttributeToSelect('url_key');
+ $productCollection->addFieldToFilter(
+ 'entity_id',
+ ['in' => array_unique($productIds)]
+ );
+
+ return $productCollection->getItems();
+ }
+}
diff --git a/app/code/Magento/CatalogUrlRewrite/Observer/ProcessUrlRewriteOnChangeProductVisibilityObserver.php b/app/code/Magento/CatalogUrlRewrite/Observer/ProcessUrlRewriteOnChangeProductVisibilityObserver.php
new file mode 100644
index 0000000000000..2337bb3646bd7
--- /dev/null
+++ b/app/code/Magento/CatalogUrlRewrite/Observer/ProcessUrlRewriteOnChangeProductVisibilityObserver.php
@@ -0,0 +1,54 @@
+adaptUrlRewritesToVisibility = $adaptUrlRewritesToVisibility;
+ }
+
+ /**
+ * Generate urls for UrlRewrites and save it in storage
+ *
+ * @param Observer $observer
+ * @return void
+ * @throws UrlAlreadyExistsException
+ */
+ public function execute(Observer $observer)
+ {
+ $event = $observer->getEvent();
+ $attrData = $event->getAttributesData();
+ $productIds = $event->getProductIds();
+ $visibility = $attrData[ProductInterface::VISIBILITY] ?? 0;
+
+ if (!$visibility || !$productIds) {
+ return;
+ }
+
+ $this->adaptUrlRewritesToVisibility->execute($productIds, (int)$visibility);
+ }
+}
diff --git a/app/code/Magento/CatalogUrlRewrite/etc/events.xml b/app/code/Magento/CatalogUrlRewrite/etc/events.xml
index cc558fe81f16d..728442acf7a44 100644
--- a/app/code/Magento/CatalogUrlRewrite/etc/events.xml
+++ b/app/code/Magento/CatalogUrlRewrite/etc/events.xml
@@ -27,6 +27,9 @@
+
+
+
diff --git a/app/code/Magento/CheckoutAgreements/view/frontend/layout/multishipping_checkout_overview.xml b/app/code/Magento/CheckoutAgreements/view/frontend/layout/multishipping_checkout_overview.xml
index 3f742de0177da..122160f1a10cd 100644
--- a/app/code/Magento/CheckoutAgreements/view/frontend/layout/multishipping_checkout_overview.xml
+++ b/app/code/Magento/CheckoutAgreements/view/frontend/layout/multishipping_checkout_overview.xml
@@ -8,7 +8,7 @@
-
+
diff --git a/app/code/Magento/CheckoutAgreements/view/frontend/templates/multishipping_agreements.phtml b/app/code/Magento/CheckoutAgreements/view/frontend/templates/multishipping_agreements.phtml
index 3400770f5cee8..33227f0cdce3c 100644
--- a/app/code/Magento/CheckoutAgreements/view/frontend/templates/multishipping_agreements.phtml
+++ b/app/code/Magento/CheckoutAgreements/view/frontend/templates/multishipping_agreements.phtml
@@ -4,6 +4,7 @@
* See COPYING.txt for license details.
*/
+// @deprecated
// @codingStandardsIgnoreFile
?>
diff --git a/app/code/Magento/Cms/Model/Wysiwyg/Images/Storage.php b/app/code/Magento/Cms/Model/Wysiwyg/Images/Storage.php
index b2ef78bab9909..ca563bd9d8f61 100644
--- a/app/code/Magento/Cms/Model/Wysiwyg/Images/Storage.php
+++ b/app/code/Magento/Cms/Model/Wysiwyg/Images/Storage.php
@@ -270,7 +270,8 @@ public function getDirsCollection($path)
$collection = $this->getCollection($path)
->setCollectDirs(true)
->setCollectFiles(false)
- ->setCollectRecursively(false);
+ ->setCollectRecursively(false)
+ ->setOrder('basename', \Magento\Framework\Data\Collection\Filesystem::SORT_ORDER_ASC);
$conditions = $this->getConditionsForExcludeDirs();
diff --git a/app/code/Magento/Cms/Test/Unit/Model/Wysiwyg/Images/StorageTest.php b/app/code/Magento/Cms/Test/Unit/Model/Wysiwyg/Images/StorageTest.php
index 309f08a54aab6..7bec1e3601461 100644
--- a/app/code/Magento/Cms/Test/Unit/Model/Wysiwyg/Images/StorageTest.php
+++ b/app/code/Magento/Cms/Test/Unit/Model/Wysiwyg/Images/StorageTest.php
@@ -417,6 +417,10 @@ protected function generalTestGetDirsCollection($path, $collectionArray = [], $e
->method('setCollectRecursively')
->with(false)
->willReturnSelf();
+ $storageCollectionMock->expects($this->once())
+ ->method('setOrder')
+ ->with('basename', \Magento\Framework\Data\Collection\Filesystem::SORT_ORDER_ASC)
+ ->willReturnSelf();
$storageCollectionMock->expects($this->once())
->method('getIterator')
->willReturn(new \ArrayIterator($collectionArray));
diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminCreateAndSwitchProductType.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminCreateAndSwitchProductType.xml
index e79f7f75cce3f..93df31a7d89e5 100755
--- a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminCreateAndSwitchProductType.xml
+++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminCreateAndSwitchProductType.xml
@@ -54,7 +54,7 @@
-
+
@@ -83,4 +83,120 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/ConfigurableProduct/etc/di.xml b/app/code/Magento/ConfigurableProduct/etc/di.xml
index 06134fcbf09d8..c3ffe988b00d7 100644
--- a/app/code/Magento/ConfigurableProduct/etc/di.xml
+++ b/app/code/Magento/ConfigurableProduct/etc/di.xml
@@ -248,4 +248,11 @@
+
+
+
+ - configurable
+
+
+
diff --git a/app/code/Magento/DirectoryGraphQl/etc/schema.graphqls b/app/code/Magento/DirectoryGraphQl/etc/schema.graphqls
index 40ef6975fad8b..8da1920f9a444 100644
--- a/app/code/Magento/DirectoryGraphQl/etc/schema.graphqls
+++ b/app/code/Magento/DirectoryGraphQl/etc/schema.graphqls
@@ -10,8 +10,10 @@ type Query {
type Currency {
base_currency_code: String
base_currency_symbol: String
- default_display_currecy_code: String
- default_display_currecy_symbol: String
+ default_display_currecy_code: String @deprecated(reason: "Symbol was missed. Use `default_display_currency_code`.")
+ default_display_currency_code: String
+ default_display_currecy_symbol: String @deprecated(reason: "Symbol was missed. Use `default_display_currency_symbol`.")
+ default_display_currency_symbol: String
available_currency_codes: [String]
exchange_rates: [ExchangeRate]
}
diff --git a/app/code/Magento/Eav/Model/Entity/AbstractEntity.php b/app/code/Magento/Eav/Model/Entity/AbstractEntity.php
index d0a5e8de53ae9..1fd71e446e6bb 100644
--- a/app/code/Magento/Eav/Model/Entity/AbstractEntity.php
+++ b/app/code/Magento/Eav/Model/Entity/AbstractEntity.php
@@ -1683,14 +1683,16 @@ public function saveAttribute(DataObject $object, $attributeCode)
$connection->beginTransaction();
try {
- $select = $connection->select()->from($table, 'value_id')->where($where);
- $origValueId = $connection->fetchOne($select);
+ $select = $connection->select()->from($table, ['value_id', 'value'])->where($where);
+ $origRow = $connection->fetchRow($select);
+ $origValueId = $origRow['value_id'] ?? false;
+ $origValue = $origRow['value'] ?? null;
if ($origValueId === false && $newValue !== null) {
$this->_insertAttribute($object, $attribute, $newValue);
} elseif ($origValueId !== false && $newValue !== null) {
$this->_updateAttribute($object, $attribute, $origValueId, $newValue);
- } elseif ($origValueId !== false && $newValue === null) {
+ } elseif ($origValueId !== false && $newValue === null && $origValue !== null) {
$connection->delete($table, $where);
}
$this->_processAttributeValues();
diff --git a/app/code/Magento/Eav/Model/Entity/Attribute/Group.php b/app/code/Magento/Eav/Model/Entity/Attribute/Group.php
index 0b6ac2b998de7..2e55964560588 100644
--- a/app/code/Magento/Eav/Model/Entity/Attribute/Group.php
+++ b/app/code/Magento/Eav/Model/Entity/Attribute/Group.php
@@ -3,11 +3,16 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
+
namespace Magento\Eav\Model\Entity\Attribute;
+use Magento\Eav\Api\Data\AttributeGroupExtensionInterface;
use Magento\Framework\Api\AttributeValueFactory;
+use Magento\Framework\Exception\LocalizedException;
/**
+ * Entity attribute group model
+ *
* @api
* @method int getSortOrder()
* @method \Magento\Eav\Model\Entity\Attribute\Group setSortOrder(int $value)
@@ -27,6 +32,11 @@ class Group extends \Magento\Framework\Model\AbstractExtensibleModel implements
*/
private $translitFilter;
+ /**
+ * @var array
+ */
+ private $reservedSystemNames = [];
+
/**
* @param \Magento\Framework\Model\Context $context
* @param \Magento\Framework\Registry $registry
@@ -35,7 +45,8 @@ class Group extends \Magento\Framework\Model\AbstractExtensibleModel implements
* @param \Magento\Framework\Filter\Translit $translitFilter
* @param \Magento\Framework\Model\ResourceModel\AbstractResource $resource
* @param \Magento\Framework\Data\Collection\AbstractDb $resourceCollection
- * @param array $data
+ * @param array $data (optional)
+ * @param array $reservedSystemNames (optional)
*/
public function __construct(
\Magento\Framework\Model\Context $context,
@@ -45,7 +56,8 @@ public function __construct(
\Magento\Framework\Filter\Translit $translitFilter,
\Magento\Framework\Model\ResourceModel\AbstractResource $resource = null,
\Magento\Framework\Data\Collection\AbstractDb $resourceCollection = null,
- array $data = []
+ array $data = [],
+ array $reservedSystemNames = []
) {
parent::__construct(
$context,
@@ -56,6 +68,7 @@ public function __construct(
$resourceCollection,
$data
);
+ $this->reservedSystemNames = $reservedSystemNames;
$this->translitFilter = $translitFilter;
}
@@ -74,6 +87,7 @@ protected function _construct()
* Checks if current attribute group exists
*
* @return bool
+ * @throws LocalizedException
* @codeCoverageIgnore
*/
public function itemExists()
@@ -85,6 +99,7 @@ public function itemExists()
* Delete groups
*
* @return $this
+ * @throws LocalizedException
* @codeCoverageIgnore
*/
public function deleteGroups()
@@ -110,9 +125,10 @@ public function beforeSave()
),
'-'
);
- if (empty($attributeGroupCode)) {
+ $isReservedSystemName = in_array(strtolower($attributeGroupCode), $this->reservedSystemNames);
+ if (empty($attributeGroupCode) || $isReservedSystemName) {
// in the following code md5 is not used for security purposes
- $attributeGroupCode = md5($groupName);
+ $attributeGroupCode = md5(strtolower($groupName));
}
$this->setAttributeGroupCode($attributeGroupCode);
}
@@ -121,7 +137,8 @@ public function beforeSave()
}
/**
- * {@inheritdoc}
+ * @inheritdoc
+ *
* @codeCoverageIgnoreStart
*/
public function getAttributeGroupId()
@@ -130,7 +147,7 @@ public function getAttributeGroupId()
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function getAttributeGroupName()
{
@@ -138,7 +155,7 @@ public function getAttributeGroupName()
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function getAttributeSetId()
{
@@ -146,7 +163,7 @@ public function getAttributeSetId()
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function setAttributeGroupId($attributeGroupId)
{
@@ -154,7 +171,7 @@ public function setAttributeGroupId($attributeGroupId)
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function setAttributeGroupName($attributeGroupName)
{
@@ -162,7 +179,7 @@ public function setAttributeGroupName($attributeGroupName)
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function setAttributeSetId($attributeSetId)
{
@@ -170,9 +187,9 @@ public function setAttributeSetId($attributeSetId)
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*
- * @return \Magento\Eav\Api\Data\AttributeGroupExtensionInterface|null
+ * @return AttributeGroupExtensionInterface|null
*/
public function getExtensionAttributes()
{
@@ -180,14 +197,13 @@ public function getExtensionAttributes()
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*
- * @param \Magento\Eav\Api\Data\AttributeGroupExtensionInterface $extensionAttributes
+ * @param AttributeGroupExtensionInterface $extensionAttributes
* @return $this
*/
- public function setExtensionAttributes(
- \Magento\Eav\Api\Data\AttributeGroupExtensionInterface $extensionAttributes
- ) {
+ public function setExtensionAttributes(AttributeGroupExtensionInterface $extensionAttributes)
+ {
return $this->_setExtensionAttributes($extensionAttributes);
}
diff --git a/app/code/Magento/Eav/Test/Unit/Model/Entity/Attribute/GroupTest.php b/app/code/Magento/Eav/Test/Unit/Model/Entity/Attribute/GroupTest.php
index d4c91e98d9608..1584b922abaa9 100644
--- a/app/code/Magento/Eav/Test/Unit/Model/Entity/Attribute/GroupTest.php
+++ b/app/code/Magento/Eav/Test/Unit/Model/Entity/Attribute/GroupTest.php
@@ -40,6 +40,7 @@ protected function setUp()
'resource' => $this->resourceMock,
'translitFilter' => $translitFilter,
'context' => $contextMock,
+ 'reservedSystemNames' => ['configurable'],
];
$objectManager = new ObjectManager($this);
$this->model = $objectManager->getObject(
@@ -67,6 +68,8 @@ public function attributeGroupCodeDataProvider()
{
return [
['General Group', 'general-group'],
+ ['configurable', md5('configurable')],
+ ['configurAble', md5('configurable')],
['///', md5('///')],
];
}
diff --git a/app/code/Magento/Eav/etc/di.xml b/app/code/Magento/Eav/etc/di.xml
index a4c89dcfab2af..db6f9b0a64f9f 100644
--- a/app/code/Magento/Eav/etc/di.xml
+++ b/app/code/Magento/Eav/etc/di.xml
@@ -210,4 +210,3 @@
-
diff --git a/app/code/Magento/Elasticsearch/SearchAdapter/Aggregation/Builder/Term.php b/app/code/Magento/Elasticsearch/SearchAdapter/Aggregation/Builder/Term.php
index 0c03a9df18dc8..eeb48f805bccf 100644
--- a/app/code/Magento/Elasticsearch/SearchAdapter/Aggregation/Builder/Term.php
+++ b/app/code/Magento/Elasticsearch/SearchAdapter/Aggregation/Builder/Term.php
@@ -22,13 +22,15 @@ public function build(
array $queryResult,
DataProviderInterface $dataProvider
) {
+ $buckets = $queryResult['aggregations'][$bucket->getName()]['buckets'] ?? [];
$values = [];
- foreach ($queryResult['aggregations'][$bucket->getName()]['buckets'] as $resultBucket) {
+ foreach ($buckets as $resultBucket) {
$values[$resultBucket['key']] = [
'value' => $resultBucket['key'],
'count' => $resultBucket['doc_count'],
];
}
+
return $values;
}
}
diff --git a/app/code/Magento/Elasticsearch/SearchAdapter/Query/Builder/Match.php b/app/code/Magento/Elasticsearch/SearchAdapter/Query/Builder/Match.php
index aaa9d8a88382f..afd383c13421f 100644
--- a/app/code/Magento/Elasticsearch/SearchAdapter/Query/Builder/Match.php
+++ b/app/code/Magento/Elasticsearch/SearchAdapter/Query/Builder/Match.php
@@ -5,6 +5,10 @@
*/
namespace Magento\Elasticsearch\SearchAdapter\Query\Builder;
+use Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\AttributeProvider;
+use Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\FieldProvider\FieldType\ResolverInterface as TypeResolver;
+use Magento\Elasticsearch\SearchAdapter\Query\ValueTransformerPool;
+use Magento\Framework\App\ObjectManager;
use Magento\Framework\Search\Request\Query\BoolExpression;
use Magento\Framework\Search\Request\QueryInterface as RequestQueryInterface;
use Magento\Elasticsearch\Model\Adapter\FieldMapperInterface;
@@ -26,20 +30,49 @@ class Match implements QueryInterface
private $fieldMapper;
/**
+ * @deprecated
+ * @see \Magento\Elasticsearch\SearchAdapter\Query\ValueTransformer\TextTransformer
* @var PreprocessorInterface[]
*/
protected $preprocessorContainer;
+ /**
+ * @var AttributeProvider
+ */
+ private $attributeProvider;
+
+ /**
+ * @var TypeResolver
+ */
+ private $fieldTypeResolver;
+
+ /**
+ * @var ValueTransformerPool
+ */
+ private $valueTransformerPool;
+
/**
* @param FieldMapperInterface $fieldMapper
* @param PreprocessorInterface[] $preprocessorContainer
+ * @param AttributeProvider|null $attributeProvider
+ * @param TypeResolver|null $fieldTypeResolver
+ * @param ValueTransformerPool|null $valueTransformerPool
*/
public function __construct(
FieldMapperInterface $fieldMapper,
- array $preprocessorContainer
+ array $preprocessorContainer,
+ AttributeProvider $attributeProvider = null,
+ TypeResolver $fieldTypeResolver = null,
+ ValueTransformerPool $valueTransformerPool = null
) {
$this->fieldMapper = $fieldMapper;
$this->preprocessorContainer = $preprocessorContainer;
+ $this->attributeProvider = $attributeProvider ?? ObjectManager::getInstance()
+ ->get(AttributeProvider::class);
+ $this->fieldTypeResolver = $fieldTypeResolver ?? ObjectManager::getInstance()
+ ->get(TypeResolver::class);
+ $this->valueTransformerPool = $valueTransformerPool ?? ObjectManager::getInstance()
+ ->get(ValueTransformerPool::class);
}
/**
@@ -72,10 +105,6 @@ public function build(array $selectQuery, RequestQueryInterface $requestQuery, $
*/
protected function prepareQuery($queryValue, $conditionType)
{
- $queryValue = $this->escape($queryValue);
- foreach ($this->preprocessorContainer as $preprocessor) {
- $queryValue = $preprocessor->process($queryValue);
- }
$condition = $conditionType === BoolExpression::QUERY_CONDITION_NOT ?
self::QUERY_CONDITION_MUST_NOT : $conditionType;
return [
@@ -104,10 +133,24 @@ protected function buildQueries(array $matches, array $queryValue)
// Checking for quoted phrase \"phrase test\", trim escaped surrounding quotes if found
$count = 0;
- $value = preg_replace('#^\\\\"(.*)\\\\"$#m', '$1', $queryValue['value'], -1, $count);
+ $value = preg_replace('#^"(.*)"$#m', '$1', $queryValue['value'], -1, $count);
$condition = ($count) ? 'match_phrase' : 'match';
+ $transformedTypes = [];
foreach ($matches as $match) {
+ $attributeAdapter = $this->attributeProvider->getByAttributeCode($match['field']);
+ $fieldType = $this->fieldTypeResolver->getFieldType($attributeAdapter);
+ $valueTransformer = $this->valueTransformerPool->get($fieldType ?? 'text');
+ $valueTransformerHash = \spl_object_hash($valueTransformer);
+ if (!isset($transformedTypes[$valueTransformerHash])) {
+ $transformedTypes[$valueTransformerHash] = $valueTransformer->transform($value);
+ }
+ $transformedValue = $transformedTypes[$valueTransformerHash];
+ if (null === $transformedValue) {
+ //Value is incompatible with this field type.
+ continue;
+ }
+
$resolvedField = $this->fieldMapper->getFieldName(
$match['field'],
['type' => FieldMapperInterface::TYPE_QUERY]
@@ -117,8 +160,8 @@ protected function buildQueries(array $matches, array $queryValue)
'body' => [
$condition => [
$resolvedField => [
- 'query' => $value,
- 'boost' => isset($match['boost']) ? $match['boost'] : 1,
+ 'query' => $transformedValue,
+ 'boost' => $match['boost'] ?? 1,
],
],
],
@@ -131,16 +174,13 @@ protected function buildQueries(array $matches, array $queryValue)
/**
* Escape a value for special query characters such as ':', '(', ')', '*', '?', etc.
*
- * Cut trailing plus or minus sign, and @ symbol, using of which causes InnoDB to report a syntax error.
- * https://dev.mysql.com/doc/refman/5.7/en/fulltext-boolean.html Fulltext-boolean search docs.
- *
+ * @deprecated
+ * @see \Magento\Elasticsearch\SearchAdapter\Query\ValueTransformer\TextTransformer
* @param string $value
* @return string
*/
protected function escape($value)
{
- $value = preg_replace('/@+|[@+-]+$/', '', $value);
-
$pattern = '/(\+|-|&&|\|\||!|\(|\)|\{|}|\[|]|\^|"|~|\*|\?|:|\\\)/';
$replace = '\\\$1';
diff --git a/app/code/Magento/Elasticsearch/SearchAdapter/Query/ValueTransformer/DateTransformer.php b/app/code/Magento/Elasticsearch/SearchAdapter/Query/ValueTransformer/DateTransformer.php
new file mode 100644
index 0000000000000..49eca6e9d82a6
--- /dev/null
+++ b/app/code/Magento/Elasticsearch/SearchAdapter/Query/ValueTransformer/DateTransformer.php
@@ -0,0 +1,44 @@
+dateFieldType = $dateFieldType;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function transform(string $value): ?string
+ {
+ try {
+ $formattedDate = $this->dateFieldType->formatDate(null, $value);
+ } catch (\Exception $e) {
+ $formattedDate = null;
+ }
+
+ return $formattedDate;
+ }
+}
diff --git a/app/code/Magento/Elasticsearch/SearchAdapter/Query/ValueTransformer/FloatTransformer.php b/app/code/Magento/Elasticsearch/SearchAdapter/Query/ValueTransformer/FloatTransformer.php
new file mode 100644
index 0000000000000..5e330076d3df7
--- /dev/null
+++ b/app/code/Magento/Elasticsearch/SearchAdapter/Query/ValueTransformer/FloatTransformer.php
@@ -0,0 +1,24 @@
+preprocessors = $preprocessors;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function transform(string $value): string
+ {
+ $value = $this->escape($value);
+ foreach ($this->preprocessors as $preprocessor) {
+ $value = $preprocessor->process($value);
+ }
+
+ return $value;
+ }
+
+ /**
+ * Escape a value for special query characters such as ':', '(', ')', '*', '?', etc.
+ *
+ * @param string $value
+ * @return string
+ */
+ private function escape(string $value): string
+ {
+ $pattern = '/(\+|-|&&|\|\||!|\(|\)|\{|}|\[|]|\^|"|~|\*|\?|:|\\\)/';
+ $replace = '\\\$1';
+
+ return preg_replace($pattern, $replace, $value);
+ }
+}
diff --git a/app/code/Magento/Elasticsearch/SearchAdapter/Query/ValueTransformerInterface.php b/app/code/Magento/Elasticsearch/SearchAdapter/Query/ValueTransformerInterface.php
new file mode 100644
index 0000000000000..c84ddc69cc7a8
--- /dev/null
+++ b/app/code/Magento/Elasticsearch/SearchAdapter/Query/ValueTransformerInterface.php
@@ -0,0 +1,22 @@
+transformers = $valueTransformers;
+ }
+
+ /**
+ * Get value transformer related to field type.
+ *
+ * @param string $fieldType
+ * @return ValueTransformerInterface
+ */
+ public function get(string $fieldType): ValueTransformerInterface
+ {
+ return $this->transformers[$fieldType] ?? $this->transformers['default'];
+ }
+}
diff --git a/app/code/Magento/Elasticsearch/Test/Unit/SearchAdapter/Query/Builder/MatchTest.php b/app/code/Magento/Elasticsearch/Test/Unit/SearchAdapter/Query/Builder/MatchTest.php
index 8114feb09d35d..d0ffc6debcd8a 100644
--- a/app/code/Magento/Elasticsearch/Test/Unit/SearchAdapter/Query/Builder/MatchTest.php
+++ b/app/code/Magento/Elasticsearch/Test/Unit/SearchAdapter/Query/Builder/MatchTest.php
@@ -5,14 +5,29 @@
*/
namespace Magento\Elasticsearch\Test\Unit\SearchAdapter\Query\Builder;
+use Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\AttributeAdapter;
+use Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\AttributeProvider;
+use Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\FieldProvider\FieldType\ResolverInterface as TypeResolver;
use Magento\Elasticsearch\Model\Adapter\FieldMapperInterface;
use Magento\Elasticsearch\SearchAdapter\Query\Builder\Match as MatchQueryBuilder;
+use Magento\Elasticsearch\SearchAdapter\Query\ValueTransformerInterface;
+use Magento\Elasticsearch\SearchAdapter\Query\ValueTransformerPool;
use Magento\Framework\Search\Request\Query\Match as MatchRequestQuery;
use Magento\Framework\TestFramework\Unit\Helper\ObjectManager;
-use PHPUnit_Framework_MockObject_MockObject as MockObject;
+use PHPUnit\Framework\MockObject\MockObject as MockObject;
class MatchTest extends \PHPUnit\Framework\TestCase
{
+ /**
+ * @var AttributeProvider|MockObject
+ */
+ private $attributeProvider;
+
+ /**
+ * @var TypeResolver|MockObject
+ */
+ private $fieldTypeResolver;
+
/**
* @var MatchQueryBuilder
*/
@@ -23,46 +38,63 @@ class MatchTest extends \PHPUnit\Framework\TestCase
*/
protected function setUp()
{
+ $this->attributeProvider = $this->createMock(AttributeProvider::class);
+ $this->fieldTypeResolver = $this->createMock(TypeResolver::class);
+
+ $valueTransformerPoolMock = $this->createMock(ValueTransformerPool::class);
+ $valueTransformerMock = $this->createMock(ValueTransformerInterface::class);
+ $valueTransformerPoolMock->method('get')
+ ->willReturn($valueTransformerMock);
+ $valueTransformerMock->method('transform')
+ ->willReturnArgument(0);
+
$this->matchQueryBuilder = (new ObjectManager($this))->getObject(
MatchQueryBuilder::class,
[
'fieldMapper' => $this->getFieldMapper(),
'preprocessorContainer' => [],
+ 'attributeProvider' => $this->attributeProvider,
+ 'fieldTypeResolver' => $this->fieldTypeResolver,
+ 'valueTransformerPool' => $valueTransformerPoolMock,
]
);
}
/**
* Tests that method constructs a correct select query.
- * @see MatchQueryBuilder::build
- *
- * @dataProvider queryValuesInvariantsProvider
*
- * @param string $rawQueryValue
- * @param string $errorMessage
+ * @see MatchQueryBuilder::build
*/
- public function testBuild($rawQueryValue, $errorMessage)
+ public function testBuild()
{
- $this->assertSelectQuery(
- $this->matchQueryBuilder->build([], $this->getMatchRequestQuery($rawQueryValue), 'not'),
- $errorMessage
- );
- }
+ $attributeAdapter = $this->createMock(AttributeAdapter::class);
+ $this->attributeProvider->expects($this->once())
+ ->method('getByAttributeCode')
+ ->with('some_field')
+ ->willReturn($attributeAdapter);
+ $this->fieldTypeResolver->expects($this->once())
+ ->method('getFieldType')
+ ->with($attributeAdapter)
+ ->willReturn('text');
+
+ $rawQueryValue = 'query_value';
+ $selectQuery = $this->matchQueryBuilder->build([], $this->getMatchRequestQuery($rawQueryValue), 'not');
- /**
- * @link https://dev.mysql.com/doc/refman/5.7/en/fulltext-boolean.html Fulltext-boolean search docs.
- *
- * @return array
- */
- public function queryValuesInvariantsProvider()
- {
- return [
- ['query_value', 'Select query field must match simple raw query value.'],
- ['query_value+', 'Specifying a trailing plus sign causes InnoDB to report a syntax error.'],
- ['query_value-', 'Specifying a trailing minus sign causes InnoDB to report a syntax error.'],
- ['query_@value', 'The @ symbol is reserved for use by the @distance proximity search operator.'],
- ['query_value+@', 'The @ symbol is reserved for use by the @distance proximity search operator.'],
+ $expectedSelectQuery = [
+ 'bool' => [
+ 'must_not' => [
+ [
+ 'match' => [
+ 'some_field' => [
+ 'query' => $rawQueryValue,
+ 'boost' => 43,
+ ],
+ ],
+ ],
+ ],
+ ],
];
+ $this->assertEquals($expectedSelectQuery, $selectQuery);
}
/**
@@ -76,6 +108,16 @@ public function queryValuesInvariantsProvider()
*/
public function testBuildMatchQuery($rawQueryValue, $queryValue, $match)
{
+ $attributeAdapter = $this->createMock(AttributeAdapter::class);
+ $this->attributeProvider->expects($this->once())
+ ->method('getByAttributeCode')
+ ->with('some_field')
+ ->willReturn($attributeAdapter);
+ $this->fieldTypeResolver->expects($this->once())
+ ->method('getFieldType')
+ ->with($attributeAdapter)
+ ->willReturn('text');
+
$query = $this->matchQueryBuilder->build([], $this->getMatchRequestQuery($rawQueryValue), 'should');
$expectedSelectQuery = [
@@ -111,30 +153,6 @@ public function matchProvider()
];
}
- /**
- * @param array $selectQuery
- * @param string $errorMessage
- */
- private function assertSelectQuery($selectQuery, $errorMessage)
- {
- $expectedSelectQuery = [
- 'bool' => [
- 'must_not' => [
- [
- 'match' => [
- 'some_field' => [
- 'query' => 'query_value',
- 'boost' => 43,
- ],
- ],
- ],
- ],
- ],
- ];
-
- $this->assertEquals($expectedSelectQuery, $selectQuery, $errorMessage);
- }
-
/**
* Gets fieldMapper mock object.
*
diff --git a/app/code/Magento/Elasticsearch/etc/di.xml b/app/code/Magento/Elasticsearch/etc/di.xml
index 4a354a2ea528f..9732ae8226431 100644
--- a/app/code/Magento/Elasticsearch/etc/di.xml
+++ b/app/code/Magento/Elasticsearch/etc/di.xml
@@ -540,4 +540,22 @@
+
+
+
+ - Magento\Elasticsearch\SearchAdapter\Query\ValueTransformer\TextTransformer
+ - Magento\Elasticsearch\SearchAdapter\Query\ValueTransformer\DateTransformer
+ - Magento\Elasticsearch\SearchAdapter\Query\ValueTransformer\FloatTransformer
+ - Magento\Elasticsearch\SearchAdapter\Query\ValueTransformer\IntegerTransformer
+
+
+
+
+
+
+ - Magento\Elasticsearch\SearchAdapter\Query\Preprocessor\Stopwords
+ - Magento\Search\Adapter\Query\Preprocessor\Synonyms
+
+
+
diff --git a/app/code/Magento/Multishipping/view/frontend/web/js/overview.js b/app/code/Magento/Multishipping/view/frontend/web/js/overview.js
index 9b867cd7217b1..3a6d73e304974 100644
--- a/app/code/Magento/Multishipping/view/frontend/web/js/overview.js
+++ b/app/code/Magento/Multishipping/view/frontend/web/js/overview.js
@@ -15,7 +15,7 @@ define([
opacity: 0.5, // CSS opacity for the 'Place Order' button when it's clicked and then disabled.
pleaseWaitLoader: 'span.please-wait', // 'Submitting order information...' Ajax loader.
placeOrderSubmit: 'button[type="submit"]', // The 'Place Order' button.
- agreements: '#checkout-agreements' // Container for all of the checkout agreements and terms/conditions
+ agreements: '.checkout-agreements' // Container for all of the checkout agreements and terms/conditions
},
/**
diff --git a/app/code/Magento/Quote/Model/QuoteManagement.php b/app/code/Magento/Quote/Model/QuoteManagement.php
index 8f216b64aa9b0..2fcfd2dfadabb 100644
--- a/app/code/Magento/Quote/Model/QuoteManagement.php
+++ b/app/code/Magento/Quote/Model/QuoteManagement.php
@@ -532,19 +532,7 @@ protected function submitQuote(QuoteEntity $quote, $orderData = [])
);
$this->quoteRepository->save($quote);
} catch (\Exception $e) {
- if (!empty($this->addressesToSync)) {
- foreach ($this->addressesToSync as $addressId) {
- $this->addressRepository->deleteById($addressId);
- }
- }
- $this->eventManager->dispatch(
- 'sales_model_service_quote_submit_failure',
- [
- 'order' => $order,
- 'quote' => $quote,
- 'exception' => $e
- ]
- );
+ $this->rollbackAddresses($quote, $order, $e);
throw $e;
}
return $order;
@@ -611,4 +599,41 @@ protected function _prepareCustomerQuote($quote)
$shipping->setIsDefaultBilling(true);
}
}
+
+ /**
+ * Remove related to order and quote addresses and submit exception to further processing.
+ *
+ * @param Quote $quote
+ * @param \Magento\Sales\Api\Data\OrderInterface $order
+ * @param \Exception $e
+ * @throws \Exception
+ */
+ private function rollbackAddresses(
+ QuoteEntity $quote,
+ \Magento\Sales\Api\Data\OrderInterface $order,
+ \Exception $e
+ ): void {
+ try {
+ if (!empty($this->addressesToSync)) {
+ foreach ($this->addressesToSync as $addressId) {
+ $this->addressRepository->deleteById($addressId);
+ }
+ }
+ $this->eventManager->dispatch(
+ 'sales_model_service_quote_submit_failure',
+ [
+ 'order' => $order,
+ 'quote' => $quote,
+ 'exception' => $e,
+ ]
+ );
+ } catch (\Exception $consecutiveException) {
+ $message = sprintf(
+ "An exception occurred on 'sales_model_service_quote_submit_failure' event: %s",
+ $consecutiveException->getMessage()
+ );
+
+ throw new \Exception($message, 0, $e);
+ }
+ }
}
diff --git a/app/code/Magento/QuoteGraphQl/Model/Cart/AddSimpleProductToCart.php b/app/code/Magento/QuoteGraphQl/Model/Cart/AddSimpleProductToCart.php
index 1b32866ed883c..6868ce3f7f1ff 100644
--- a/app/code/Magento/QuoteGraphQl/Model/Cart/AddSimpleProductToCart.php
+++ b/app/code/Magento/QuoteGraphQl/Model/Cart/AddSimpleProductToCart.php
@@ -67,6 +67,11 @@ public function execute(Quote $cart, array $cartItemData): void
{
$sku = $this->extractSku($cartItemData);
$qty = $this->extractQty($cartItemData);
+ if ($qty <= 0) {
+ throw new GraphQlInputException(
+ __('Please enter a number greater than 0 in this field.')
+ );
+ }
$customizableOptions = $this->extractCustomizableOptions($cartItemData);
try {
diff --git a/app/code/Magento/QuoteGraphQl/Model/Resolver/SetPaymentMethodOnCart.php b/app/code/Magento/QuoteGraphQl/Model/Resolver/SetPaymentMethodOnCart.php
index a93c8032c996a..d1dcb4a48a76b 100644
--- a/app/code/Magento/QuoteGraphQl/Model/Resolver/SetPaymentMethodOnCart.php
+++ b/app/code/Magento/QuoteGraphQl/Model/Resolver/SetPaymentMethodOnCart.php
@@ -60,12 +60,12 @@ public function __construct(
public function resolve(Field $field, $context, ResolveInfo $info, array $value = null, array $args = null)
{
if (!isset($args['input']['cart_id']) || empty($args['input']['cart_id'])) {
- throw new GraphQlInputException(__('Required parameter "cart_id" is missing'));
+ throw new GraphQlInputException(__('Required parameter "cart_id" is missing.'));
}
$maskedCartId = $args['input']['cart_id'];
if (!isset($args['input']['payment_method']['code']) || empty($args['input']['payment_method']['code'])) {
- throw new GraphQlInputException(__('Required parameter "payment_method" is missing'));
+ throw new GraphQlInputException(__('Required parameter "code" for "payment_method" is missing.'));
}
$paymentMethodCode = $args['input']['payment_method']['code'];
diff --git a/app/code/Magento/Reports/Model/ResourceModel/Order/Collection.php b/app/code/Magento/Reports/Model/ResourceModel/Order/Collection.php
index da7ab97a1b211..d89a118bff94b 100644
--- a/app/code/Magento/Reports/Model/ResourceModel/Order/Collection.php
+++ b/app/code/Magento/Reports/Model/ResourceModel/Order/Collection.php
@@ -769,11 +769,12 @@ public function addOrdersCount()
*/
public function addRevenueToSelect($convertCurrency = false)
{
- $expr = $this->getTotalsExpression(
+ $expr = $this->getTotalsExpressionWithDiscountRefunded(
!$convertCurrency,
$this->getConnection()->getIfNullSql('main_table.base_subtotal_refunded', 0),
$this->getConnection()->getIfNullSql('main_table.base_subtotal_canceled', 0),
- $this->getConnection()->getIfNullSql('main_table.base_discount_canceled', 0)
+ $this->getConnection()->getIfNullSql('ABS(main_table.base_discount_refunded)', 0),
+ $this->getConnection()->getIfNullSql('ABS(main_table.base_discount_canceled)', 0)
);
$this->getSelect()->columns(['revenue' => $expr]);
@@ -791,11 +792,12 @@ public function addSumAvgTotals($storeId = 0)
/**
* calculate average and total amount
*/
- $expr = $this->getTotalsExpression(
+ $expr = $this->getTotalsExpressionWithDiscountRefunded(
$storeId,
$this->getConnection()->getIfNullSql('main_table.base_subtotal_refunded', 0),
$this->getConnection()->getIfNullSql('main_table.base_subtotal_canceled', 0),
- $this->getConnection()->getIfNullSql('main_table.base_discount_canceled', 0)
+ $this->getConnection()->getIfNullSql('ABS(main_table.base_discount_refunded)', 0),
+ $this->getConnection()->getIfNullSql('ABS(main_table.base_discount_canceled)', 0)
);
$this->getSelect()->columns(
@@ -808,13 +810,15 @@ public function addSumAvgTotals($storeId = 0)
}
/**
- * Get SQL expression for totals
+ * Get SQL expression for totals.
*
* @param int $storeId
* @param string $baseSubtotalRefunded
* @param string $baseSubtotalCanceled
* @param string $baseDiscountCanceled
* @return string
+ * @deprecated
+ * @see getTotalsExpressionWithDiscountRefunded
*/
protected function getTotalsExpression(
$storeId,
@@ -825,10 +829,40 @@ protected function getTotalsExpression(
$template = ($storeId != 0)
? '(main_table.base_subtotal - %2$s - %1$s - ABS(main_table.base_discount_amount) - %3$s)'
: '((main_table.base_subtotal - %1$s - %2$s - ABS(main_table.base_discount_amount) + %3$s) '
- . ' * main_table.base_to_global_rate)';
+ . ' * main_table.base_to_global_rate)';
return sprintf($template, $baseSubtotalRefunded, $baseSubtotalCanceled, $baseDiscountCanceled);
}
+ /**
+ * Get SQL expression for totals with discount refunded.
+ *
+ * @param int $storeId
+ * @param string $baseSubtotalRefunded
+ * @param string $baseSubtotalCanceled
+ * @param string $baseDiscountRefunded
+ * @param string $baseDiscountCanceled
+ * @return string
+ */
+ private function getTotalsExpressionWithDiscountRefunded(
+ $storeId,
+ $baseSubtotalRefunded,
+ $baseSubtotalCanceled,
+ $baseDiscountRefunded,
+ $baseDiscountCanceled
+ ) {
+ $template = ($storeId != 0)
+ ? '(main_table.base_subtotal - %2$s - %1$s - (ABS(main_table.base_discount_amount) - %3$s - %4$s))'
+ : '((main_table.base_subtotal - %1$s - %2$s - (ABS(main_table.base_discount_amount) - %3$s - %4$s)) '
+ . ' * main_table.base_to_global_rate)';
+ return sprintf(
+ $template,
+ $baseSubtotalRefunded,
+ $baseSubtotalCanceled,
+ $baseDiscountRefunded,
+ $baseDiscountCanceled
+ );
+ }
+
/**
* Sort order by total amount
*
diff --git a/app/code/Magento/Review/etc/acl.xml b/app/code/Magento/Review/etc/acl.xml
index 09b80750da14d..46fdb20dee4a1 100644
--- a/app/code/Magento/Review/etc/acl.xml
+++ b/app/code/Magento/Review/etc/acl.xml
@@ -16,8 +16,8 @@
-
+
diff --git a/app/code/Magento/Review/etc/adminhtml/menu.xml b/app/code/Magento/Review/etc/adminhtml/menu.xml
index 0a2e49450e0cf..7376329471921 100644
--- a/app/code/Magento/Review/etc/adminhtml/menu.xml
+++ b/app/code/Magento/Review/etc/adminhtml/menu.xml
@@ -8,8 +8,8 @@