@@ -132,7 +139,7 @@
diff --git a/app/code/Magento/Checkout/view/frontend/templates/cart/form.phtml b/app/code/Magento/Checkout/view/frontend/templates/cart/form.phtml
index 1005c11e44d95..84ab9b13d8f3a 100644
--- a/app/code/Magento/Checkout/view/frontend/templates/cart/form.phtml
+++ b/app/code/Magento/Checkout/view/frontend/templates/cart/form.phtml
@@ -14,7 +14,8 @@
method="post"
id="form-validate"
data-mage-init='{"Magento_Checkout/js/action/update-shopping-cart":
- {"validationURL" : "/checkout/cart/updateItemQty"}
+ {"validationURL" : "/checkout/cart/updateItemQty",
+ "updateCartActionContainer": "#update_cart_action_container"}
}'
class="form form-cart">
= $block->getBlockHtml('formkey') ?>
diff --git a/app/code/Magento/Checkout/view/frontend/web/js/action/update-shopping-cart.js b/app/code/Magento/Checkout/view/frontend/web/js/action/update-shopping-cart.js
index ce1527b3d72d6..1920bc4d7ac41 100644
--- a/app/code/Magento/Checkout/view/frontend/web/js/action/update-shopping-cart.js
+++ b/app/code/Magento/Checkout/view/frontend/web/js/action/update-shopping-cart.js
@@ -14,7 +14,8 @@ define([
$.widget('mage.updateShoppingCart', {
options: {
validationURL: '',
- eventName: 'updateCartItemQty'
+ eventName: 'updateCartItemQty',
+ updateCartActionContainer: ''
},
/** @inheritdoc */
@@ -31,7 +32,9 @@ define([
* @return {Boolean}
*/
onSubmit: function (event) {
- if (!this.options.validationURL) {
+ var action = this.element.find(this.options.updateCartActionContainer).val();
+
+ if (!this.options.validationURL || action === 'empty_cart') {
return true;
}
diff --git a/app/code/Magento/Cms/Model/Wysiwyg/Images/Storage.php b/app/code/Magento/Cms/Model/Wysiwyg/Images/Storage.php
index 2cd1647a1bf22..90dcf3dc8df78 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 906a7d4fbc605..20c0b2075f5c3 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
@@ -414,6 +414,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/Ui/DataProvider/Product/Form/Modifier/ConfigurablePanel.php b/app/code/Magento/ConfigurableProduct/Ui/DataProvider/Product/Form/Modifier/ConfigurablePanel.php
index 9fd225e8acaab..dcdffddbeeec0 100644
--- a/app/code/Magento/ConfigurableProduct/Ui/DataProvider/Product/Form/Modifier/ConfigurablePanel.php
+++ b/app/code/Magento/ConfigurableProduct/Ui/DataProvider/Product/Form/Modifier/ConfigurablePanel.php
@@ -5,14 +5,14 @@
*/
namespace Magento\ConfigurableProduct\Ui\DataProvider\Product\Form\Modifier;
+use Magento\Catalog\Model\Locator\LocatorInterface;
use Magento\Catalog\Model\Product\Attribute\Backend\Sku;
use Magento\Catalog\Ui\DataProvider\Product\Form\Modifier\AbstractModifier;
+use Magento\Framework\UrlInterface;
use Magento\Ui\Component\Container;
-use Magento\Ui\Component\Form;
use Magento\Ui\Component\DynamicRows;
+use Magento\Ui\Component\Form;
use Magento\Ui\Component\Modal;
-use Magento\Framework\UrlInterface;
-use Magento\Catalog\Model\Locator\LocatorInterface;
/**
* Data provider for Configurable panel
@@ -90,7 +90,7 @@ public function __construct(
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function modifyData(array $data)
{
@@ -98,7 +98,7 @@ public function modifyData(array $data)
}
/**
- * {@inheritdoc}
+ * @inheritdoc
* @SuppressWarnings(PHPMD.ExcessiveMethodLength)
*/
public function modifyMeta(array $meta)
@@ -197,7 +197,7 @@ public function modifyMeta(array $meta)
'autoRender' => false,
'componentType' => 'insertListing',
'component' => 'Magento_ConfigurableProduct/js'
- .'/components/associated-product-insert-listing',
+ . '/components/associated-product-insert-listing',
'dataScope' => $this->associatedListingPrefix
. static::ASSOCIATED_PRODUCT_LISTING,
'externalProvider' => $this->associatedListingPrefix
@@ -328,14 +328,12 @@ protected function getButtonSet()
'component' => 'Magento_Ui/js/form/components/button',
'actions' => [
[
- 'targetName' =>
- $this->dataScopeName . '.configurableModal',
+ 'targetName' => $this->dataScopeName . '.configurableModal',
'actionName' => 'trigger',
'params' => ['active', true],
],
[
- 'targetName' =>
- $this->dataScopeName . '.configurableModal',
+ 'targetName' => $this->dataScopeName . '.configurableModal',
'actionName' => 'openModal',
],
],
@@ -574,6 +572,7 @@ protected function getColumn(
'dataType' => Form\Element\DataType\Text::NAME,
'dataScope' => $name,
'visibleIfCanEdit' => false,
+ 'labelVisible' => false,
'imports' => [
'visible' => '!${$.provider}:${$.parentScope}.canEdit'
],
@@ -592,6 +591,7 @@ protected function getColumn(
'component' => 'Magento_Ui/js/form/components/group',
'label' => $label,
'dataScope' => '',
+ 'showLabel' => false
];
$container['children'] = [
$name . '_edit' => $fieldEdit,
diff --git a/app/code/Magento/Customer/Block/Adminhtml/Edit/Tab/Orders.php b/app/code/Magento/Customer/Block/Adminhtml/Edit/Tab/Orders.php
index bb190260e4776..c1266febff99d 100644
--- a/app/code/Magento/Customer/Block/Adminhtml/Edit/Tab/Orders.php
+++ b/app/code/Magento/Customer/Block/Adminhtml/Edit/Tab/Orders.php
@@ -63,7 +63,8 @@ protected function _construct()
{
parent::_construct();
$this->setId('customer_orders_grid');
- $this->setDefaultSort('created_at', 'desc');
+ $this->setDefaultSort('created_at');
+ $this->setDefaultDir('desc');
$this->setUseAjax(true);
}
diff --git a/app/code/Magento/Customer/Block/Adminhtml/Edit/Tab/View/Cart.php b/app/code/Magento/Customer/Block/Adminhtml/Edit/Tab/View/Cart.php
index d0973d3baf383..988a157805b36 100644
--- a/app/code/Magento/Customer/Block/Adminhtml/Edit/Tab/View/Cart.php
+++ b/app/code/Magento/Customer/Block/Adminhtml/Edit/Tab/View/Cart.php
@@ -77,7 +77,8 @@ protected function _construct()
{
parent::_construct();
$this->setId('customer_view_cart_grid');
- $this->setDefaultSort('added_at', 'desc');
+ $this->setDefaultSort('added_at');
+ $this->setDefaultDir('desc');
$this->setSortable(false);
$this->setPagerVisibility(false);
$this->setFilterVisibility(false);
diff --git a/app/code/Magento/Eav/Model/Entity/AbstractEntity.php b/app/code/Magento/Eav/Model/Entity/AbstractEntity.php
index 7f01cf268d06b..7f6734b8b6b60 100644
--- a/app/code/Magento/Eav/Model/Entity/AbstractEntity.php
+++ b/app/code/Magento/Eav/Model/Entity/AbstractEntity.php
@@ -1695,14 +1695,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/Reports/Model/ResourceModel/Order/Collection.php b/app/code/Magento/Reports/Model/ResourceModel/Order/Collection.php
index fd9adbe734101..da7ab97a1b211 100644
--- a/app/code/Magento/Reports/Model/ResourceModel/Order/Collection.php
+++ b/app/code/Magento/Reports/Model/ResourceModel/Order/Collection.php
@@ -445,7 +445,7 @@ public function getDateRange($range, $customStart, $customEnd, $returnObjects =
break;
case 'custom':
- $dateStart = $customStart ? $customStart : $dateEnd;
+ $dateStart = $customStart ? $customStart : $dateStart;
$dateEnd = $customEnd ? $customEnd : $dateEnd;
break;
diff --git a/app/code/Magento/Sales/Setup/UpgradeData.php b/app/code/Magento/Sales/Setup/UpgradeData.php
index 77b96791e8cea..2e5a454e62fdd 100644
--- a/app/code/Magento/Sales/Setup/UpgradeData.php
+++ b/app/code/Magento/Sales/Setup/UpgradeData.php
@@ -15,6 +15,7 @@
use Magento\Framework\Setup\ModuleDataSetupInterface;
use Magento\Framework\Setup\UpgradeDataInterface;
use Magento\Quote\Model\QuoteFactory;
+use Magento\Sales\Model\Order\Address;
use Magento\Sales\Model\OrderFactory;
use Magento\Sales\Model\ResourceModel\Order\Address\CollectionFactory as AddressCollectionFactory;
@@ -42,27 +43,14 @@ class UpgradeData implements UpgradeDataInterface
*/
private $aggregatedFieldConverter;
- /**
- * @var AddressCollectionFactory
- */
- private $addressCollectionFactory;
-
- /**
- * @var OrderFactory
- */
- private $orderFactory;
-
- /**
- * @var QuoteFactory
- */
- private $quoteFactory;
-
/**
* @var State
*/
private $state;
/**
+ * @SuppressWarnings(PHPMD.UnusedFormalParameter)
+ *
* @param SalesSetupFactory $salesSetupFactory
* @param Config $eavConfig
* @param AggregatedFieldDataConverter $aggregatedFieldConverter
@@ -83,9 +71,6 @@ public function __construct(
$this->salesSetupFactory = $salesSetupFactory;
$this->eavConfig = $eavConfig;
$this->aggregatedFieldConverter = $aggregatedFieldConverter;
- $this->addressCollectionFactory = $addressCollFactory;
- $this->orderFactory = $orderFactory;
- $this->quoteFactory = $quoteFactory;
$this->state = $state;
}
@@ -125,6 +110,7 @@ public function upgrade(ModuleDataSetupInterface $setup, ModuleContextInterface
* @param string $setupVersion
* @param SalesSetup $salesSetup
* @return void
+ * @throws \Magento\Framework\DB\FieldDataConversionException
*/
private function convertSerializedDataToJson($setupVersion, SalesSetup $salesSetup)
{
@@ -173,32 +159,95 @@ private function convertSerializedDataToJson($setupVersion, SalesSetup $salesSet
/**
* Fill quote_address_id in table sales_order_address if it is empty.
- *
* @param ModuleDataSetupInterface $setup
*/
public function fillQuoteAddressIdInSalesOrderAddress(ModuleDataSetupInterface $setup)
{
- $addressTable = $setup->getTable('sales_order_address');
- $updateOrderAddress = $setup->getConnection()
+ $this->fillQuoteAddressIdInSalesOrderAddressByType($setup, Address::TYPE_SHIPPING);
+ $this->fillQuoteAddressIdInSalesOrderAddressByType($setup, Address::TYPE_BILLING);
+ }
+
+ /**
+ * @param ModuleDataSetupInterface $setup
+ * @param string $addressType
+ */
+ private function fillQuoteAddressIdInSalesOrderAddressByType(ModuleDataSetupInterface $setup, $addressType)
+ {
+ $salesConnection = $setup->getConnection('sales');
+
+ $orderTable = $setup->getTable('sales_order', 'sales');
+ $orderAddressTable = $setup->getTable('sales_order_address', 'sales');
+
+ $query = $salesConnection
->select()
+ ->from(
+ ['sales_order_address' => $orderAddressTable],
+ ['entity_id', 'address_type']
+ )
->joinInner(
- ['sales_order' => $setup->getTable('sales_order')],
- $addressTable . '.parent_id = sales_order.entity_id',
- ['quote_address_id' => 'quote_address.address_id']
+ ['sales_order' => $orderTable],
+ 'sales_order_address.parent_id = sales_order.entity_id',
+ ['quote_id' => 'sales_order.quote_id']
+ )
+ ->where('sales_order_address.quote_address_id IS NULL')
+ ->where('sales_order_address.address_type = ?', $addressType)
+ ->order('sales_order_address.entity_id');
+
+ $batchSize = 5000;
+ $result = $salesConnection->query($query);
+ $count = $result->rowCount();
+ $batches = ceil($count / $batchSize);
+
+ for ($batch = $batches; $batch > 0; $batch--) {
+ $query->limitPage($batch, $batchSize);
+ $result = $salesConnection->fetchAssoc($query);
+
+ $this->fillQuoteAddressIdInSalesOrderAddressProcessBatch($setup, $result, $addressType);
+ }
+ }
+
+ /**
+ * @param ModuleDataSetupInterface $setup
+ * @param array $orderAddresses
+ * @param string $addressType
+ */
+ private function fillQuoteAddressIdInSalesOrderAddressProcessBatch(
+ ModuleDataSetupInterface $setup,
+ array $orderAddresses,
+ $addressType
+ ) {
+ $salesConnection = $setup->getConnection('sales');
+ $quoteConnection = $setup->getConnection('checkout');
+
+ $quoteAddressTable = $setup->getTable('quote_address', 'checkout');
+ $quoteTable = $setup->getTable('quote', 'checkout');
+ $salesOrderAddressTable = $setup->getTable('sales_order_address', 'sales');
+
+ $query = $quoteConnection
+ ->select()
+ ->from(
+ ['quote_address' => $quoteAddressTable],
+ ['quote_id', 'address_id']
)
->joinInner(
- ['quote_address' => $setup->getTable('quote_address')],
- 'sales_order.quote_id = quote_address.quote_id
- AND ' . $addressTable . '.address_type = quote_address.address_type',
+ ['quote' => $quoteTable],
+ 'quote_address.quote_id = quote.entity_id',
[]
)
- ->where(
- $addressTable . '.quote_address_id IS NULL'
- );
- $updateOrderAddress = $setup->getConnection()->updateFromSelect(
- $updateOrderAddress,
- $addressTable
- );
- $setup->getConnection()->query($updateOrderAddress);
+ ->where('quote.entity_id in (?)', array_column($orderAddresses, 'quote_id'))
+ ->where('address_type = ?', $addressType);
+
+ $quoteAddresses = $quoteConnection->fetchAssoc($query);
+
+ foreach ($orderAddresses as $orderAddress) {
+ $bind = [
+ 'quote_address_id' => $quoteAddresses[$orderAddress['quote_id']]['address_id'] ?? null,
+ ];
+ $where = [
+ 'entity_id = ?' => $orderAddress['entity_id']
+ ];
+
+ $salesConnection->update($salesOrderAddressTable, $bind, $where);
+ }
}
}
diff --git a/app/code/Magento/SalesRule/Model/ResourceModel/Rule/Collection.php b/app/code/Magento/SalesRule/Model/ResourceModel/Rule/Collection.php
index 5e6f3847c8e31..423cd1543117b 100644
--- a/app/code/Magento/SalesRule/Model/ResourceModel/Rule/Collection.php
+++ b/app/code/Magento/SalesRule/Model/ResourceModel/Rule/Collection.php
@@ -9,6 +9,9 @@
use Magento\Framework\DB\Select;
use Magento\Framework\Serialize\Serializer\Json;
use Magento\Quote\Model\Quote\Address;
+use Magento\SalesRule\Api\Data\CouponInterface;
+use Magento\SalesRule\Model\Coupon;
+use Magento\SalesRule\Model\Rule;
/**
* Sales Rules resource collection model.
@@ -107,12 +110,15 @@ protected function mapAssociatedEntities($entityType, $objectField)
$associatedEntities = $this->getConnection()->fetchAll($select);
- array_map(function ($associatedEntity) use ($entityInfo, $ruleIdField, $objectField) {
- $item = $this->getItemByColumnValue($ruleIdField, $associatedEntity[$ruleIdField]);
- $itemAssociatedValue = $item->getData($objectField) === null ? [] : $item->getData($objectField);
- $itemAssociatedValue[] = $associatedEntity[$entityInfo['entity_id_field']];
- $item->setData($objectField, $itemAssociatedValue);
- }, $associatedEntities);
+ array_map(
+ function ($associatedEntity) use ($entityInfo, $ruleIdField, $objectField) {
+ $item = $this->getItemByColumnValue($ruleIdField, $associatedEntity[$ruleIdField]);
+ $itemAssociatedValue = $item->getData($objectField) === null ? [] : $item->getData($objectField);
+ $itemAssociatedValue[] = $associatedEntity[$entityInfo['entity_id_field']];
+ $item->setData($objectField, $itemAssociatedValue);
+ },
+ $associatedEntities
+ );
}
/**
@@ -141,6 +147,7 @@ protected function _afterLoad()
* @param string $couponCode
* @param string|null $now
* @param Address $address allow extensions to further filter out rules based on quote address
+ * @throws \Zend_Db_Select_Exception
* @use $this->addWebsiteGroupDateFilter()
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
* @return $this
@@ -153,32 +160,24 @@ public function setValidationFilter(
Address $address = null
) {
if (!$this->getFlag('validation_filter')) {
- /* We need to overwrite joinLeft if coupon is applied */
- $this->getSelect()->reset();
- parent::_initSelect();
+ $this->prepareSelect($websiteId, $customerGroupId, $now);
- $this->addWebsiteGroupDateFilter($websiteId, $customerGroupId, $now);
- $select = $this->getSelect();
+ $noCouponRules = $this->getNoCouponCodeSelect();
- $connection = $this->getConnection();
- if (strlen($couponCode)) {
- $noCouponWhereCondition = $connection->quoteInto(
- 'main_table.coupon_type = ?',
- \Magento\SalesRule\Model\Rule::COUPON_TYPE_NO_COUPON
- );
- $relatedRulesIds = $this->getCouponRelatedRuleIds($couponCode);
-
- $select->where(
- $noCouponWhereCondition . ' OR main_table.rule_id IN (?)',
- $relatedRulesIds,
- Select::TYPE_CONDITION
- );
+ if ($couponCode) {
+ $couponRules = $this->getCouponCodeSelect($couponCode);
+
+ $allAllowedRules = $this->getConnection()->select();
+ $allAllowedRules->union([$noCouponRules, $couponRules], Select::SQL_UNION_ALL);
+
+ $wrapper = $this->getConnection()->select();
+ $wrapper->from($allAllowedRules);
+
+ $this->_select = $wrapper;
} else {
- $this->addFieldToFilter(
- 'main_table.coupon_type',
- \Magento\SalesRule\Model\Rule::COUPON_TYPE_NO_COUPON
- );
+ $this->_select = $noCouponRules;
}
+
$this->setOrder('sort_order', self::SORT_ORDER_ASC);
$this->setFlag('validation_filter', true);
}
@@ -187,72 +186,98 @@ public function setValidationFilter(
}
/**
- * Get rules ids related to coupon code
+ * Recreate the default select object for specific needs of salesrule evaluation with coupon codes.
*
- * @param string $couponCode
- * @return array
+ * @param int $websiteId
+ * @param int $customerGroupId
+ * @param string $now
*/
- private function getCouponRelatedRuleIds(string $couponCode): array
+ private function prepareSelect($websiteId, $customerGroupId, $now)
{
- $connection = $this->getConnection();
- $select = $connection->select()->from(
- ['main_table' => $this->getTable('salesrule')],
- 'rule_id'
+ $this->getSelect()->reset();
+ parent::_initSelect();
+
+ $this->addWebsiteGroupDateFilter($websiteId, $customerGroupId, $now);
+ }
+
+ /**
+ * Return select object to determine all active rules not needing a coupon code.
+ *
+ * @return Select
+ */
+ private function getNoCouponCodeSelect()
+ {
+ $noCouponSelect = clone $this->getSelect();
+
+ $noCouponSelect->where(
+ 'main_table.coupon_type = ?',
+ Rule::COUPON_TYPE_NO_COUPON
);
- $select->joinLeft(
- ['rule_coupons' => $this->getTable('salesrule_coupon')],
- $connection->quoteInto(
- 'main_table.rule_id = rule_coupons.rule_id AND main_table.coupon_type != ?',
- \Magento\SalesRule\Model\Rule::COUPON_TYPE_NO_COUPON,
- null
- )
+
+ $noCouponSelect->columns([Coupon::KEY_CODE => new \Zend_Db_Expr('NULL')]);
+
+ return $noCouponSelect;
+ }
+
+ /**
+ * Determine all active rules that are valid for the given coupon code.
+ *
+ * @param string $couponCode
+ * @return Select
+ */
+ private function getCouponCodeSelect($couponCode)
+ {
+ $couponSelect = clone $this->getSelect();
+
+ $this->joinCouponTable($couponCode, $couponSelect);
+
+ $notExpired = $this->getConnection()->quoteInto(
+ '(rule_coupons.expiration_date IS NULL OR rule_coupons.expiration_date >= ?)',
+ $this->_date->date()->format('Y-m-d')
);
- $autoGeneratedCouponCondition = [
- $connection->quoteInto(
- "main_table.coupon_type = ?",
- \Magento\SalesRule\Model\Rule::COUPON_TYPE_AUTO
- ),
- $connection->quoteInto(
- "rule_coupons.type = ?",
- \Magento\SalesRule\Api\Data\CouponInterface::TYPE_GENERATED
- ),
- ];
-
- $orWhereConditions = [
- "(" . implode($autoGeneratedCouponCondition, " AND ") . ")",
- $connection->quoteInto(
- '(main_table.coupon_type = ? AND main_table.use_auto_generation = 1 AND rule_coupons.type = 1)',
- \Magento\SalesRule\Model\Rule::COUPON_TYPE_SPECIFIC
- ),
- $connection->quoteInto(
- '(main_table.coupon_type = ? AND main_table.use_auto_generation = 0 AND rule_coupons.type = 0)',
- \Magento\SalesRule\Model\Rule::COUPON_TYPE_SPECIFIC
- ),
- ];
-
- $andWhereConditions = [
- $connection->quoteInto(
- 'rule_coupons.code = ?',
- $couponCode
- ),
- $connection->quoteInto(
- '(rule_coupons.expiration_date IS NULL OR rule_coupons.expiration_date >= ?)',
- $this->_date->date()->format('Y-m-d')
- ),
- ];
-
- $orWhereCondition = implode(' OR ', $orWhereConditions);
- $andWhereCondition = implode(' AND ', $andWhereConditions);
-
- $select->where(
- '(' . $orWhereCondition . ') AND ' . $andWhereCondition,
+ $isAutogenerated =
+ $this->getConnection()->quoteInto('main_table.coupon_type = ?', Rule::COUPON_TYPE_AUTO)
+ . ' AND ' .
+ $this->getConnection()->quoteInto('rule_coupons.type = ?', CouponInterface::TYPE_GENERATED);
+
+ $isValidSpecific =
+ $this->getConnection()->quoteInto('(main_table.coupon_type = ?)', Rule::COUPON_TYPE_SPECIFIC)
+ . ' AND (' .
+ '(main_table.use_auto_generation = 1 AND rule_coupons.type = 1)'
+ . ' OR ' .
+ '(main_table.use_auto_generation = 0 AND rule_coupons.type = 0)'
+ . ')';
+
+ $couponSelect->where(
+ "$notExpired AND ($isAutogenerated OR $isValidSpecific)",
null,
Select::TYPE_CONDITION
);
- $select->group('main_table.rule_id');
- return $connection->fetchCol($select);
+ return $couponSelect;
+ }
+
+ /**
+ * Join coupon table to select.
+ *
+ * @param string $couponCode
+ * @param Select $couponSelect
+ */
+ private function joinCouponTable($couponCode, Select $couponSelect)
+ {
+ $couponJoinCondition =
+ 'main_table.rule_id = rule_coupons.rule_id'
+ . ' AND ' .
+ $this->getConnection()->quoteInto('main_table.coupon_type <> ?', Rule::COUPON_TYPE_NO_COUPON)
+ . ' AND ' .
+ $this->getConnection()->quoteInto('rule_coupons.code = ?', $couponCode);
+
+ $couponSelect->joinInner(
+ ['rule_coupons' => $this->getTable('salesrule_coupon')],
+ $couponJoinCondition,
+ [Coupon::KEY_CODE]
+ );
}
/**
diff --git a/app/code/Magento/Swatches/view/frontend/web/js/swatch-renderer.js b/app/code/Magento/Swatches/view/frontend/web/js/swatch-renderer.js
index 3e28982ad44d3..2cb8bd1c25d46 100644
--- a/app/code/Magento/Swatches/view/frontend/web/js/swatch-renderer.js
+++ b/app/code/Magento/Swatches/view/frontend/web/js/swatch-renderer.js
@@ -918,7 +918,8 @@ define([
$productPrice = $product.find(this.options.selectorProductPrice),
options = _.object(_.keys($widget.optionsMap), {}),
result,
- tierPriceHtml;
+ tierPriceHtml,
+ isShow;
$widget.element.find('.' + $widget.options.classes.attributeClass + '[option-selected]').each(function () {
var attributeId = $(this).attr('attribute-id');
@@ -935,11 +936,9 @@ define([
}
);
- if (typeof result != 'undefined' && result.oldPrice.amount !== result.finalPrice.amount) {
- $(this.options.slyOldPriceSelector).show();
- } else {
- $(this.options.slyOldPriceSelector).hide();
- }
+ isShow = typeof result != 'undefined' && result.oldPrice.amount !== result.finalPrice.amount;
+
+ $product.find(this.options.slyOldPriceSelector)[isShow ? 'show' : 'hide']();
if (typeof result != 'undefined' && result.tierPrices.length) {
if (this.options.tierPriceTemplate) {
diff --git a/app/code/Magento/UrlRewrite/Model/Storage/DbStorage.php b/app/code/Magento/UrlRewrite/Model/Storage/DbStorage.php
index 60b845f95e5cc..f98801c266109 100644
--- a/app/code/Magento/UrlRewrite/Model/Storage/DbStorage.php
+++ b/app/code/Magento/UrlRewrite/Model/Storage/DbStorage.php
@@ -14,6 +14,9 @@
use Psr\Log\LoggerInterface;
use Magento\UrlRewrite\Service\V1\Data\UrlRewrite as UrlRewriteData;
+/**
+ * DB storage implementation for url rewrites
+ */
class DbStorage extends AbstractStorage
{
/**
@@ -37,7 +40,7 @@ class DbStorage extends AbstractStorage
protected $resource;
/**
- * @var \Psr\Log\LoggerInterface
+ * @var LoggerInterface
*/
private $logger;
@@ -45,7 +48,7 @@ class DbStorage extends AbstractStorage
* @param \Magento\UrlRewrite\Service\V1\Data\UrlRewriteFactory $urlRewriteFactory
* @param DataObjectHelper $dataObjectHelper
* @param \Magento\Framework\App\ResourceConnection $resource
- * @param \Psr\Log\LoggerInterface|null $logger
+ * @param LoggerInterface|null $logger
*/
public function __construct(
UrlRewriteFactory $urlRewriteFactory,
@@ -56,7 +59,7 @@ public function __construct(
$this->connection = $resource->getConnection();
$this->resource = $resource;
$this->logger = $logger ?: \Magento\Framework\App\ObjectManager::getInstance()
- ->get(\Psr\Log\LoggerInterface::class);
+ ->get(LoggerInterface::class);
parent::__construct($urlRewriteFactory, $dataObjectHelper);
}
@@ -97,42 +100,18 @@ protected function doFindOneByData(array $data)
$result = null;
$requestPath = $data[UrlRewrite::REQUEST_PATH];
-
- $data[UrlRewrite::REQUEST_PATH] = [
+ $decodedRequestPath = urldecode($requestPath);
+ $data[UrlRewrite::REQUEST_PATH] = array_unique([
rtrim($requestPath, '/'),
rtrim($requestPath, '/') . '/',
- ];
+ rtrim($decodedRequestPath, '/'),
+ rtrim($decodedRequestPath, '/') . '/',
+ ]);
$resultsFromDb = $this->connection->fetchAll($this->prepareSelect($data));
-
- if (count($resultsFromDb) === 1) {
- $resultFromDb = current($resultsFromDb);
- $redirectTypes = [OptionProvider::TEMPORARY, OptionProvider::PERMANENT];
-
- // If request path matches the DB value or it's redirect - we can return result from DB
- $canReturnResultFromDb = ($resultFromDb[UrlRewrite::REQUEST_PATH] === $requestPath
- || in_array((int)$resultFromDb[UrlRewrite::REDIRECT_TYPE], $redirectTypes, true));
-
- // Otherwise return 301 redirect to request path from DB results
- $result = $canReturnResultFromDb ? $resultFromDb : [
- UrlRewrite::ENTITY_TYPE => 'custom',
- UrlRewrite::ENTITY_ID => '0',
- UrlRewrite::REQUEST_PATH => $requestPath,
- UrlRewrite::TARGET_PATH => $resultFromDb[UrlRewrite::REQUEST_PATH],
- UrlRewrite::REDIRECT_TYPE => OptionProvider::PERMANENT,
- UrlRewrite::STORE_ID => $resultFromDb[UrlRewrite::STORE_ID],
- UrlRewrite::DESCRIPTION => null,
- UrlRewrite::IS_AUTOGENERATED => '0',
- UrlRewrite::METADATA => null,
- ];
- } else {
- // If we have 2 results - return the row that matches request path
- foreach ($resultsFromDb as $resultFromDb) {
- if ($resultFromDb[UrlRewrite::REQUEST_PATH] === $requestPath) {
- $result = $resultFromDb;
- break;
- }
- }
+ if ($resultsFromDb) {
+ $urlRewrite = $this->extractMostRelevantUrlRewrite($requestPath, $resultsFromDb);
+ $result = $this->prepareUrlRewrite($requestPath, $urlRewrite);
}
return $result;
@@ -142,8 +121,78 @@ protected function doFindOneByData(array $data)
}
/**
- * @param UrlRewrite[] $urls
+ * Extract most relevant url rewrite from url rewrites list
+ *
+ * @param string $requestPath
+ * @param array $urlRewrites
+ * @return array|null
+ */
+ private function extractMostRelevantUrlRewrite(string $requestPath, array $urlRewrites)
+ {
+ $prioritizedUrlRewrites = [];
+ foreach ($urlRewrites as $urlRewrite) {
+ switch (true) {
+ case $urlRewrite[UrlRewrite::REQUEST_PATH] === $requestPath:
+ $priority = 1;
+ break;
+ case $urlRewrite[UrlRewrite::REQUEST_PATH] === urldecode($requestPath):
+ $priority = 2;
+ break;
+ case rtrim($urlRewrite[UrlRewrite::REQUEST_PATH], '/') === rtrim($requestPath, '/'):
+ $priority = 3;
+ break;
+ case rtrim($urlRewrite[UrlRewrite::REQUEST_PATH], '/') === rtrim(urldecode($requestPath), '/'):
+ $priority = 4;
+ break;
+ default:
+ $priority = 5;
+ break;
+ }
+ $prioritizedUrlRewrites[$priority] = $urlRewrite;
+ }
+ ksort($prioritizedUrlRewrites);
+
+ return array_shift($prioritizedUrlRewrites);
+ }
+
+ /**
+ * Prepare url rewrite
*
+ * If request path matches the DB value or it's redirect - we can return result from DB
+ * Otherwise return 301 redirect to request path from DB results
+ *
+ * @param string $requestPath
+ * @param array $urlRewrite
+ * @return array
+ */
+ private function prepareUrlRewrite(string $requestPath, array $urlRewrite): array
+ {
+ $redirectTypes = [OptionProvider::TEMPORARY, OptionProvider::PERMANENT];
+ $canReturnResultFromDb = (
+ in_array($urlRewrite[UrlRewrite::REQUEST_PATH], [$requestPath, urldecode($requestPath)], true)
+ || in_array((int) $urlRewrite[UrlRewrite::REDIRECT_TYPE], $redirectTypes, true)
+ );
+ if (!$canReturnResultFromDb) {
+ $urlRewrite = [
+ UrlRewrite::ENTITY_TYPE => 'custom',
+ UrlRewrite::ENTITY_ID => '0',
+ UrlRewrite::REQUEST_PATH => $requestPath,
+ UrlRewrite::TARGET_PATH => $urlRewrite[UrlRewrite::REQUEST_PATH],
+ UrlRewrite::REDIRECT_TYPE => OptionProvider::PERMANENT,
+ UrlRewrite::STORE_ID => $urlRewrite[UrlRewrite::STORE_ID],
+ UrlRewrite::DESCRIPTION => null,
+ UrlRewrite::IS_AUTOGENERATED => '0',
+ UrlRewrite::METADATA => null,
+ ];
+ }
+
+ return $urlRewrite;
+ }
+
+ /**
+ * Delete old url rewrites
+ *
+ * @param UrlRewrite[] $urls
* @return void
*/
private function deleteOldUrls(array $urls)
diff --git a/dev/tests/integration/testsuite/Magento/Catalog/Model/ResourceModel/ProductTest.php b/dev/tests/integration/testsuite/Magento/Catalog/Model/ResourceModel/ProductTest.php
index 7954e2c36227f..476f01eb277df 100644
--- a/dev/tests/integration/testsuite/Magento/Catalog/Model/ResourceModel/ProductTest.php
+++ b/dev/tests/integration/testsuite/Magento/Catalog/Model/ResourceModel/ProductTest.php
@@ -12,6 +12,11 @@
class ProductTest extends TestCase
{
+ /**
+ * @var ProductRepositoryInterface
+ */
+ private $productRepository;
+
/**
* @var Product
*/
@@ -29,7 +34,8 @@ protected function setUp()
{
$this->objectManager = Bootstrap::getObjectManager();
- $this->model = $this->objectManager->get(Product::class);
+ $this->productRepository = $this->objectManager->create(ProductRepositoryInterface::class);
+ $this->model = $this->objectManager->create(Product::class);
}
/**
@@ -42,11 +48,29 @@ public function testGetAttributeRawValue()
$sku = 'simple';
$attribute = 'name';
- /** @var ProductRepositoryInterface $productRepository */
- $productRepository = $this->objectManager->get(ProductRepositoryInterface::class);
- $product = $productRepository->get($sku);
-
+ $product = $this->productRepository->get($sku);
$actual = $this->model->getAttributeRawValue($product->getId(), $attribute, null);
self::assertEquals($product->getName(), $actual);
}
+
+ /**
+ * @magentoAppArea adminhtml
+ * @magentoDataFixture Magento/Catalog/_files/product_special_price.php
+ * @magentoAppIsolation enabled
+ * @magentoConfigFixture default_store catalog/price/scope 1
+ */
+ public function testUpdateStoreSpecificSpecialPrice()
+ {
+ /** @var \Magento\Catalog\Model\Product $product */
+ $product = $this->productRepository->get('simple', true, 1);
+ $this->assertEquals(5.99, $product->getSpecialPrice());
+
+ $product->setSpecialPrice('');
+ $this->model->save($product);
+ $product = $this->productRepository->get('simple', false, 1, true);
+ $this->assertEmpty($product->getSpecialPrice());
+
+ $product = $this->productRepository->get('simple', false, 0, true);
+ $this->assertEquals(5.99, $product->getSpecialPrice());
+ }
}
diff --git a/dev/tests/integration/testsuite/Magento/UrlRewrite/Model/UrlFinderInterfaceTest.php b/dev/tests/integration/testsuite/Magento/UrlRewrite/Model/UrlFinderInterfaceTest.php
new file mode 100644
index 0000000000000..b6055f14e79d2
--- /dev/null
+++ b/dev/tests/integration/testsuite/Magento/UrlRewrite/Model/UrlFinderInterfaceTest.php
@@ -0,0 +1,71 @@
+urlFinder = Bootstrap::getObjectManager()->create(UrlFinderInterface::class);
+ }
+
+ /**
+ * @dataProvider findOneDataProvider
+ * @param string $requestPath
+ * @param string $targetPath
+ * @param int $redirectType
+ */
+ public function testFindOneByData(string $requestPath, string $targetPath, int $redirectType)
+ {
+ $data = [
+ UrlRewrite::REQUEST_PATH => $requestPath,
+ ];
+ $urlRewrite = $this->urlFinder->findOneByData($data);
+ $this->assertEquals($targetPath, $urlRewrite->getTargetPath());
+ $this->assertEquals($redirectType, $urlRewrite->getRedirectType());
+ }
+
+ /**
+ * @return array
+ */
+ public function findOneDataProvider(): array
+ {
+ return [
+ ['string', 'test_page1', 0],
+ ['string/', 'string', 301],
+ ['string_permanent', 'test_page1', 301],
+ ['string_permanent/', 'test_page1', 301],
+ ['string_temporary', 'test_page1', 302],
+ ['string_temporary/', 'test_page1', 302],
+ ['строка', 'test_page1', 0],
+ ['строка/', 'строка', 301],
+ [urlencode('строка'), 'test_page2', 0],
+ [urlencode('строка') . '/', urlencode('строка'), 301],
+ ['другая_строка', 'test_page1', 302],
+ ['другая_строка/', 'test_page1', 302],
+ [urlencode('другая_строка'), 'test_page1', 302],
+ [urlencode('другая_строка') . '/', 'test_page1', 302],
+ ['السلسلة', 'test_page1', 0],
+ [urlencode('السلسلة'), 'test_page1', 0],
+ ];
+ }
+}
diff --git a/dev/tests/integration/testsuite/Magento/UrlRewrite/_files/url_rewrites.php b/dev/tests/integration/testsuite/Magento/UrlRewrite/_files/url_rewrites.php
new file mode 100644
index 0000000000000..9edc6507308ee
--- /dev/null
+++ b/dev/tests/integration/testsuite/Magento/UrlRewrite/_files/url_rewrites.php
@@ -0,0 +1,42 @@
+create(\Magento\UrlRewrite\Model\ResourceModel\UrlRewrite::class);
+foreach ($rewritesData as $rewriteData) {
+ list ($requestPath, $targetPath, $redirectType) = $rewriteData;
+ $rewrite = $objectManager->create(\Magento\UrlRewrite\Model\UrlRewrite::class);
+ $rewrite->setEntityType('custom')
+ ->setRequestPath($requestPath)
+ ->setTargetPath($targetPath)
+ ->setRedirectType($redirectType);
+ $rewriteResource->save($rewrite);
+}
diff --git a/dev/tests/integration/testsuite/Magento/UrlRewrite/_files/url_rewrites_rollback.php b/dev/tests/integration/testsuite/Magento/UrlRewrite/_files/url_rewrites_rollback.php
new file mode 100644
index 0000000000000..a98f947d614e0
--- /dev/null
+++ b/dev/tests/integration/testsuite/Magento/UrlRewrite/_files/url_rewrites_rollback.php
@@ -0,0 +1,20 @@
+get(\Magento\Framework\Registry::class);
+$registry->unregister('isSecureArea');
+$registry->register('isSecureArea', true);
+
+$urlRewriteCollection = $objectManager->create(\Magento\UrlRewrite\Model\ResourceModel\UrlRewriteCollection::class);
+$collection = $urlRewriteCollection
+ ->addFieldToFilter('target_path', ['test_page1', 'test_page2'])
+ ->load()
+ ->walk('delete');
+
+$registry->unregister('isSecureArea');
+$registry->register('isSecureArea', false);
diff --git a/dev/tests/static/testsuite/Magento/Test/Integrity/ComposerTest.php b/dev/tests/static/testsuite/Magento/Test/Integrity/ComposerTest.php
index 6fc84486c626b..2f1ab7a75bc83 100644
--- a/dev/tests/static/testsuite/Magento/Test/Integrity/ComposerTest.php
+++ b/dev/tests/static/testsuite/Magento/Test/Integrity/ComposerTest.php
@@ -452,7 +452,7 @@ private function convertModuleToPackageName($moduleName)
{
list($vendor, $name) = explode('_', $moduleName, 2);
$package = 'module';
- foreach (preg_split('/([A-Z][a-z\d]+)/', $name, -1, PREG_SPLIT_DELIM_CAPTURE) as $chunk) {
+ foreach (preg_split('/([A-Z\d][a-z]*)/', $name, -1, PREG_SPLIT_DELIM_CAPTURE) as $chunk) {
$package .= $chunk ? "-{$chunk}" : '';
}
return strtolower("{$vendor}/{$package}");