- |
+ |
= /* @escapeNotVerified */ $block->getActions($_item) ?>
diff --git a/app/code/Magento/Checkout/view/frontend/web/js/model/cart/estimate-service.js b/app/code/Magento/Checkout/view/frontend/web/js/model/cart/estimate-service.js
index 76e3d911e7d3f..54e496131972e 100644
--- a/app/code/Magento/Checkout/view/frontend/web/js/model/cart/estimate-service.js
+++ b/app/code/Magento/Checkout/view/frontend/web/js/model/cart/estimate-service.js
@@ -14,55 +14,71 @@ define([
'use strict';
var rateProcessors = [],
- totalsProcessors = [];
+ totalsProcessors = [],
- quote.shippingAddress.subscribe(function () {
- var type = quote.shippingAddress().getType();
+ /**
+ * Estimate totals for shipping address and update shipping rates.
+ */
+ estimateTotalsAndUpdateRates = function () {
+ var type = quote.shippingAddress().getType();
- if (
- quote.isVirtual() ||
- window.checkoutConfig.activeCarriers && window.checkoutConfig.activeCarriers.length === 0
- ) {
- // update totals block when estimated address was set
- totalsProcessors['default'] = totalsDefaultProvider;
- totalsProcessors[type] ?
- totalsProcessors[type].estimateTotals(quote.shippingAddress()) :
- totalsProcessors['default'].estimateTotals(quote.shippingAddress());
- } else {
- // check if user data not changed -> load rates from cache
- if (!cartCache.isChanged('address', quote.shippingAddress()) &&
- !cartCache.isChanged('cartVersion', customerData.get('cart')()['data_id']) &&
- cartCache.get('rates')
+ if (
+ quote.isVirtual() ||
+ window.checkoutConfig.activeCarriers && window.checkoutConfig.activeCarriers.length === 0
) {
- shippingService.setShippingRates(cartCache.get('rates'));
+ // update totals block when estimated address was set
+ totalsProcessors['default'] = totalsDefaultProvider;
+ totalsProcessors[type] ?
+ totalsProcessors[type].estimateTotals(quote.shippingAddress()) :
+ totalsProcessors['default'].estimateTotals(quote.shippingAddress());
+ } else {
+ // check if user data not changed -> load rates from cache
+ if (!cartCache.isChanged('address', quote.shippingAddress()) &&
+ !cartCache.isChanged('cartVersion', customerData.get('cart')()['data_id']) &&
+ cartCache.get('rates')
+ ) {
+ shippingService.setShippingRates(cartCache.get('rates'));
- return;
+ return;
+ }
+
+ // update rates list when estimated address was set
+ rateProcessors['default'] = defaultProcessor;
+ rateProcessors[type] ?
+ rateProcessors[type].getRates(quote.shippingAddress()) :
+ rateProcessors['default'].getRates(quote.shippingAddress());
+
+ // save rates to cache after load
+ shippingService.getShippingRates().subscribe(function (rates) {
+ cartCache.set('rates', rates);
+ });
}
+ },
- // update rates list when estimated address was set
- rateProcessors['default'] = defaultProcessor;
- rateProcessors[type] ?
- rateProcessors[type].getRates(quote.shippingAddress()) :
- rateProcessors['default'].getRates(quote.shippingAddress());
+ /**
+ * Estimate totals for shipping address.
+ */
+ estimateTotalsShipping = function () {
+ totalsDefaultProvider.estimateTotals(quote.shippingAddress());
+ },
- // save rates to cache after load
- shippingService.getShippingRates().subscribe(function (rates) {
- cartCache.set('rates', rates);
- });
- }
- });
- quote.shippingMethod.subscribe(function () {
- totalsDefaultProvider.estimateTotals(quote.shippingAddress());
- });
- quote.billingAddress.subscribe(function () {
- var type = quote.billingAddress().getType();
+ /**
+ * Estimate totals for billing address.
+ */
+ estimateTotalsBilling = function () {
+ var type = quote.billingAddress().getType();
+
+ if (quote.isVirtual()) {
+ // update totals block when estimated address was set
+ totalsProcessors['default'] = totalsDefaultProvider;
+ totalsProcessors[type] ?
+ totalsProcessors[type].estimateTotals(quote.billingAddress()) :
+ totalsProcessors['default'].estimateTotals(quote.billingAddress());
+ }
+ };
- if (quote.isVirtual()) {
- // update totals block when estimated address was set
- totalsProcessors['default'] = totalsDefaultProvider;
- totalsProcessors[type] ?
- totalsProcessors[type].estimateTotals(quote.billingAddress()) :
- totalsProcessors['default'].estimateTotals(quote.billingAddress());
- }
- });
+ quote.shippingAddress.subscribe(estimateTotalsAndUpdateRates);
+ quote.shippingMethod.subscribe(estimateTotalsShipping);
+ quote.billingAddress.subscribe(estimateTotalsBilling);
+ customerData.get('cart').subscribe(estimateTotalsShipping);
});
diff --git a/app/code/Magento/Checkout/view/frontend/web/js/model/checkout-data-resolver.js b/app/code/Magento/Checkout/view/frontend/web/js/model/checkout-data-resolver.js
index 73f4df567903c..28e04699f8daf 100644
--- a/app/code/Magento/Checkout/view/frontend/web/js/model/checkout-data-resolver.js
+++ b/app/code/Magento/Checkout/view/frontend/web/js/model/checkout-data-resolver.js
@@ -218,16 +218,31 @@ define([
* Apply resolved billing address to quote
*/
applyBillingAddress: function () {
- var shippingAddress;
+ var shippingAddress,
+ isBillingAddressInitialized;
if (quote.billingAddress()) {
selectBillingAddress(quote.billingAddress());
return;
}
+
+ if (quote.isVirtual()) {
+ isBillingAddressInitialized = addressList.some(function (addrs) {
+ if (addrs.isDefaultBilling()) {
+ selectBillingAddress(addrs);
+
+ return true;
+ }
+
+ return false;
+ });
+ }
+
shippingAddress = quote.shippingAddress();
- if (shippingAddress &&
+ if (!isBillingAddressInitialized &&
+ shippingAddress &&
shippingAddress.canUseForBilling() &&
(shippingAddress.isDefaultShipping() || !quote.isVirtual())
) {
diff --git a/app/code/Magento/Checkout/view/frontend/web/js/model/shipping-save-processor/default.js b/app/code/Magento/Checkout/view/frontend/web/js/model/shipping-save-processor/default.js
index 3a6574bff8948..447d626b339bd 100644
--- a/app/code/Magento/Checkout/view/frontend/web/js/model/shipping-save-processor/default.js
+++ b/app/code/Magento/Checkout/view/frontend/web/js/model/shipping-save-processor/default.js
@@ -35,9 +35,8 @@ define([
saveShippingInformation: function () {
var payload;
- if (!quote.billingAddress()) {
- selectBillingAddressAction(quote.shippingAddress());
- }
+ /* Assign selected address every time buyer selects address*/
+ selectBillingAddressAction(quote.shippingAddress());
payload = {
addressInformation: {
diff --git a/app/code/Magento/Checkout/view/frontend/web/template/billing-address/details.html b/app/code/Magento/Checkout/view/frontend/web/template/billing-address/details.html
index cc1d960bbe44b..ea521b3a8afd4 100644
--- a/app/code/Magento/Checkout/view/frontend/web/template/billing-address/details.html
+++ b/app/code/Magento/Checkout/view/frontend/web/template/billing-address/details.html
@@ -8,7 +8,7 @@
- ,
+ ,
diff --git a/app/code/Magento/Checkout/view/frontend/web/template/shipping-address/address-renderer/default.html b/app/code/Magento/Checkout/view/frontend/web/template/shipping-address/address-renderer/default.html
index 05ced7a978f82..2a5dc27328a43 100644
--- a/app/code/Magento/Checkout/view/frontend/web/template/shipping-address/address-renderer/default.html
+++ b/app/code/Magento/Checkout/view/frontend/web/template/shipping-address/address-renderer/default.html
@@ -8,7 +8,7 @@
- ,
+ ,
diff --git a/app/code/Magento/Checkout/view/frontend/web/template/shipping-information/address-renderer/default.html b/app/code/Magento/Checkout/view/frontend/web/template/shipping-information/address-renderer/default.html
index 97286a28552d2..541413955cb47 100644
--- a/app/code/Magento/Checkout/view/frontend/web/template/shipping-information/address-renderer/default.html
+++ b/app/code/Magento/Checkout/view/frontend/web/template/shipping-information/address-renderer/default.html
@@ -8,7 +8,7 @@
- ,
+ ,
diff --git a/app/code/Magento/Cms/view/adminhtml/layout/cms_wysiwyg_images_index.xml b/app/code/Magento/Cms/view/adminhtml/layout/cms_wysiwyg_images_index.xml
index 1bc8828ef6c8e..5bf66ef302f04 100644
--- a/app/code/Magento/Cms/view/adminhtml/layout/cms_wysiwyg_images_index.xml
+++ b/app/code/Magento/Cms/view/adminhtml/layout/cms_wysiwyg_images_index.xml
@@ -9,7 +9,11 @@
-
+
+
+ Magento\Backend\Block\DataProviders\UploadConfig
+
+
diff --git a/app/code/Magento/Cms/view/adminhtml/templates/browser/content/uploader.phtml b/app/code/Magento/Cms/view/adminhtml/templates/browser/content/uploader.phtml
index f87dc07dc04f4..27f14434b5fcf 100644
--- a/app/code/Magento/Cms/view/adminhtml/templates/browser/content/uploader.phtml
+++ b/app/code/Magento/Cms/view/adminhtml/templates/browser/content/uploader.phtml
@@ -7,6 +7,14 @@
// @codingStandardsIgnoreFile
/** @var $block \Magento\Cms\Block\Adminhtml\Wysiwyg\Images\Content\Uploader */
+
+$resizeConfig = $block->getImageUploadConfigData()->getIsResizeEnabled()
+ ? "{action: 'resize', maxWidth: "
+ . $block->getImageUploadMaxWidth()
+ . ", maxHeight: "
+ . $block->getImageUploadMaxHeight()
+ . "}"
+ : "{action: 'resize'}";
?>
@@ -99,11 +107,9 @@ require([
action: 'load',
fileTypes: /^image\/(gif|jpeg|png)$/,
maxFileSize: = (int) $block->getFileSizeService()->getMaxFileSize() ?> * 10
- }, {
- action: 'resize',
- maxWidth: = (int) $block->getImageUploadMaxWidth() ?> ,
- maxHeight: = (int) $block->getImageUploadMaxHeight() ?>
- }, {
+ },
+ = $block->escapeHtml($resizeConfig) ?>,
+ {
action: 'save'
}]
});
diff --git a/app/code/Magento/Config/Model/Config.php b/app/code/Magento/Config/Model/Config.php
index c6e2412f7e58f..0472c5daa276f 100644
--- a/app/code/Magento/Config/Model/Config.php
+++ b/app/code/Magento/Config/Model/Config.php
@@ -237,13 +237,14 @@ private function getField(string $sectionId, string $groupId, string $fieldId):
* Get field path
*
* @param Field $field
+ * @param string $fieldId Need for support of clone_field feature
* @param array &$oldConfig Need for compatibility with _processGroup()
* @param array &$extraOldGroups Need for compatibility with _processGroup()
* @return string
*/
- private function getFieldPath(Field $field, array &$oldConfig, array &$extraOldGroups): string
+ private function getFieldPath(Field $field, string $fieldId, array &$oldConfig, array &$extraOldGroups): string
{
- $path = $field->getGroupPath() . '/' . $field->getId();
+ $path = $field->getGroupPath() . '/' . $fieldId;
/**
* Look for custom defined field path
@@ -303,7 +304,7 @@ private function getChangedPaths(
if (isset($groupData['fields'])) {
foreach ($groupData['fields'] as $fieldId => $fieldData) {
$field = $this->getField($sectionId, $groupId, $fieldId);
- $path = $this->getFieldPath($field, $oldConfig, $extraOldGroups);
+ $path = $this->getFieldPath($field, $fieldId, $oldConfig, $extraOldGroups);
if ($this->isValueChanged($oldConfig, $path, $fieldData)) {
$changedPaths[] = $path;
}
@@ -398,7 +399,7 @@ protected function _processGroup(
$backendModel->addData($data);
$this->_checkSingleStoreMode($field, $backendModel);
- $path = $this->getFieldPath($field, $extraOldGroups, $oldConfig);
+ $path = $this->getFieldPath($field, $fieldId, $extraOldGroups, $oldConfig);
$backendModel->setPath($path)->setValue($fieldData['value']);
$inherit = !empty($fieldData['inherit']);
diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/ActionGroup/AdminCreateApiConfigurableProductActionGroup.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/ActionGroup/AdminCreateApiConfigurableProductActionGroup.xml
new file mode 100644
index 0000000000000..75686d23a11b9
--- /dev/null
+++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/ActionGroup/AdminCreateApiConfigurableProductActionGroup.xml
@@ -0,0 +1,65 @@
+
+
+
+
+
+
+
+
+
+
+
+ {{productName}}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Data/ConfigurableProductData.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Data/ConfigurableProductData.xml
index 83e0aa1deaedf..a2f824dd8864e 100644
--- a/app/code/Magento/ConfigurableProduct/Test/Mftf/Data/ConfigurableProductData.xml
+++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Data/ConfigurableProductData.xml
@@ -7,7 +7,7 @@
-->
+ xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd">
configurable
configurable
@@ -38,4 +38,15 @@
EavStockItem
CustomAttributeCategoryIds
+
+ api-configurable-product-with-out-category
+ configurable
+ 4
+ 4
+ API Configurable Product
+ api-configurable-product
+ 1
+ 100
+ EavStockItem
+
diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Data/ConfigurableProductOptionData.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Data/ConfigurableProductOptionData.xml
index 21dcf998a6399..e98175bc8d40b 100644
--- a/app/code/Magento/ConfigurableProduct/Test/Mftf/Data/ConfigurableProductOptionData.xml
+++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Data/ConfigurableProductOptionData.xml
@@ -7,11 +7,18 @@
-->
+ xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd">
option
ValueIndex1
ValueIndex2
+
+
+ option
+ ValueIndex1
+ ValueIndex2
+ ValueIndex3
+
diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Data/ValueIndexData.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Data/ValueIndexData.xml
index 54d489a446fd7..e1cd70790a6b2 100644
--- a/app/code/Magento/ConfigurableProduct/Test/Mftf/Data/ValueIndexData.xml
+++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Data/ValueIndexData.xml
@@ -7,11 +7,14 @@
-->
+ xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd">
+
+
+
diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/StorefrontSortingByPriceForConfigurableWithCatalogRuleAppliedTest.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/StorefrontSortingByPriceForConfigurableWithCatalogRuleAppliedTest.xml
new file mode 100644
index 0000000000000..c8ca2ced0d60f
--- /dev/null
+++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/StorefrontSortingByPriceForConfigurableWithCatalogRuleAppliedTest.xml
@@ -0,0 +1,156 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 5.00
+
+
+
+ 10.00
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 15.00
+
+
+
+
+ 20.00
+
+
+
+
+ 25.00
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/ConfigurableProduct/composer.json b/app/code/Magento/ConfigurableProduct/composer.json
index 33ee30a25b012..eae9dac046b23 100644
--- a/app/code/Magento/ConfigurableProduct/composer.json
+++ b/app/code/Magento/ConfigurableProduct/composer.json
@@ -19,6 +19,7 @@
"suggest": {
"magento/module-webapi": "100.2.*",
"magento/module-sales": "101.0.*",
+ "magento/module-sales-rule": "101.0.*",
"magento/module-product-video": "100.2.*",
"magento/module-configurable-sample-data": "Sample Data version:100.2.*",
"magento/module-product-links-sample-data": "Sample Data version:100.2.*"
diff --git a/app/code/Magento/ConfigurableProduct/etc/di.xml b/app/code/Magento/ConfigurableProduct/etc/di.xml
index dfbad0dd5a764..1dbb0969687d5 100644
--- a/app/code/Magento/ConfigurableProduct/etc/di.xml
+++ b/app/code/Magento/ConfigurableProduct/etc/di.xml
@@ -228,4 +228,11 @@
+
+
+
+ - false
+
+
+
diff --git a/app/code/Magento/ConfigurableProduct/view/frontend/web/js/options-updater.js b/app/code/Magento/ConfigurableProduct/view/frontend/web/js/options-updater.js
index 558a1fdf31085..6f18798303151 100644
--- a/app/code/Magento/ConfigurableProduct/view/frontend/web/js/options-updater.js
+++ b/app/code/Magento/ConfigurableProduct/view/frontend/web/js/options-updater.js
@@ -61,6 +61,7 @@ define([
this.setProductOptions(cartData());
this.updateOptions();
}.bind(this));
+ this.updateOptions();
},
/**
diff --git a/app/code/Magento/Customer/Model/GroupManagement.php b/app/code/Magento/Customer/Model/GroupManagement.php
index eb5d90f2fd7ea..48cb5d55061c5 100644
--- a/app/code/Magento/Customer/Model/GroupManagement.php
+++ b/app/code/Magento/Customer/Model/GroupManagement.php
@@ -15,7 +15,6 @@
use Magento\Framework\Api\SortOrderBuilder;
use Magento\Framework\App\Config\ScopeConfigInterface;
use Magento\Framework\App\ObjectManager;
-use Magento\Framework\Data\Collection;
use Magento\Framework\Exception\NoSuchEntityException;
use Magento\Store\Model\StoreManagerInterface;
@@ -170,7 +169,7 @@ public function getLoggedInGroups()
->create();
$groupNameSortOrder = $this->sortOrderBuilder
->setField('customer_group_code')
- ->setDirection(Collection::SORT_ORDER_ASC)
+ ->setAscendingDirection()
->create();
$searchCriteria = $this->searchCriteriaBuilder
->addFilters($notLoggedInFilter)
diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminConfigCustomerActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminConfigCustomerActionGroup.xml
new file mode 100644
index 0000000000000..3fc25ecf57faa
--- /dev/null
+++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminConfigCustomerActionGroup.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Customer/Test/Mftf/Data/AddressData.xml b/app/code/Magento/Customer/Test/Mftf/Data/AddressData.xml
index e4420f189900e..c8ba70c7f8b50 100644
--- a/app/code/Magento/Customer/Test/Mftf/Data/AddressData.xml
+++ b/app/code/Magento/Customer/Test/Mftf/Data/AddressData.xml
@@ -7,7 +7,7 @@
-->
+ xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd">
0
12
@@ -89,4 +89,17 @@
Yes
RegionNY
+
+ Jane
+ Doe
+ Magento
+
+ - 172, Westminster Bridge Rd
+
+ London
+
+ SE1 7RW
+ GB
+ 444-44-444-44
+
diff --git a/app/code/Magento/Customer/Test/Mftf/Data/CustomerGroupData.xml b/app/code/Magento/Customer/Test/Mftf/Data/CustomerGroupData.xml
new file mode 100644
index 0000000000000..d7fc22f085344
--- /dev/null
+++ b/app/code/Magento/Customer/Test/Mftf/Data/CustomerGroupData.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+ ApiCustomerGroup
+ 0
+ Retail Customer
+
+
diff --git a/app/code/Magento/Customer/Test/Mftf/Metadata/customer-group-meta.xml b/app/code/Magento/Customer/Test/Mftf/Metadata/customer-group-meta.xml
new file mode 100644
index 0000000000000..a45bf1645d088
--- /dev/null
+++ b/app/code/Magento/Customer/Test/Mftf/Metadata/customer-group-meta.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+ application/json
+
+
+
+ application/json
+
+
diff --git a/app/code/Magento/Customer/Test/Mftf/Page/AdminCustomerConfigPage.xml b/app/code/Magento/Customer/Test/Mftf/Page/AdminCustomerConfigPage.xml
new file mode 100644
index 0000000000000..3cf8490ec4af1
--- /dev/null
+++ b/app/code/Magento/Customer/Test/Mftf/Page/AdminCustomerConfigPage.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Customer/Test/Mftf/Page/StorefrontCustomerAddressPage.xml b/app/code/Magento/Customer/Test/Mftf/Page/StorefrontCustomerAddressPage.xml
new file mode 100644
index 0000000000000..bdbbcab67da67
--- /dev/null
+++ b/app/code/Magento/Customer/Test/Mftf/Page/StorefrontCustomerAddressPage.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerConfigSection.xml b/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerConfigSection.xml
new file mode 100644
index 0000000000000..33696fbd616e3
--- /dev/null
+++ b/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerConfigSection.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
diff --git a/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerAddressesSection.xml b/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerAddressesSection.xml
new file mode 100644
index 0000000000000..88b46d245105f
--- /dev/null
+++ b/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerAddressesSection.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerOrderViewSection.xml b/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerOrderViewSection.xml
index 01b758c9c3e3b..f6a6cb2d457e1 100644
--- a/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerOrderViewSection.xml
+++ b/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerOrderViewSection.xml
@@ -7,12 +7,14 @@
-->
+ xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd">
diff --git a/app/code/Magento/Customer/Test/Mftf/Test/AdminCreateCustomerTest.xml b/app/code/Magento/Customer/Test/Mftf/Test/AdminCreateCustomerTest.xml
index e5dc187e8ab7a..8e4d69e6a270d 100644
--- a/app/code/Magento/Customer/Test/Mftf/Test/AdminCreateCustomerTest.xml
+++ b/app/code/Magento/Customer/Test/Mftf/Test/AdminCreateCustomerTest.xml
@@ -7,7 +7,7 @@
-->
+ xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd">
@@ -19,6 +19,9 @@
+
+
+
diff --git a/app/code/Magento/CustomerImportExport/Model/Import/CustomerComposite.php b/app/code/Magento/CustomerImportExport/Model/Import/CustomerComposite.php
index 36c1e761d013c..1ff1adc3dac58 100644
--- a/app/code/Magento/CustomerImportExport/Model/Import/CustomerComposite.php
+++ b/app/code/Magento/CustomerImportExport/Model/Import/CustomerComposite.php
@@ -299,8 +299,8 @@ public function validateData()
$rows = [];
foreach ($source as $row) {
$rows[] = [
- Address::COLUMN_EMAIL => $row[Customer::COLUMN_EMAIL],
- Address::COLUMN_WEBSITE => $row[Customer::COLUMN_WEBSITE]
+ Address::COLUMN_EMAIL => $row[Customer::COLUMN_EMAIL] ?? null,
+ Address::COLUMN_WEBSITE => $row[Customer::COLUMN_WEBSITE] ?? null
];
}
$source->rewind();
diff --git a/app/code/Magento/Developer/Console/Command/SourceThemeDeployCommand.php b/app/code/Magento/Developer/Console/Command/SourceThemeDeployCommand.php
index 25519e5c83054..a2db0f43061ea 100644
--- a/app/code/Magento/Developer/Console/Command/SourceThemeDeployCommand.php
+++ b/app/code/Magento/Developer/Console/Command/SourceThemeDeployCommand.php
@@ -5,7 +5,6 @@
*/
namespace Magento\Developer\Console\Command;
-use Magento\Framework\App\State;
use Magento\Framework\Validator\Locale;
use Magento\Framework\View\Asset\Repository;
use Symfony\Component\Console\Command\Command;
diff --git a/app/code/Magento/Downloadable/Setup/UpgradeData.php b/app/code/Magento/Downloadable/Setup/UpgradeData.php
new file mode 100644
index 0000000000000..aec15ce7f9d90
--- /dev/null
+++ b/app/code/Magento/Downloadable/Setup/UpgradeData.php
@@ -0,0 +1,58 @@
+eavSetupFactory = $eavSetupFactory;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function upgrade(ModuleDataSetupInterface $setup, ModuleContextInterface $context)
+ {
+ $setup->startSetup();
+
+ if (version_compare($context->getVersion(), '2.0.3', '<')) {
+ /** @var EavSetup $eavSetup */
+ $eavSetup = $this->eavSetupFactory->create(['setup' => $setup]);
+ // remove default value
+ $eavSetup->updateAttribute(
+ \Magento\Catalog\Model\Product::ENTITY,
+ 'links_exist',
+ 'default_value',
+ null
+ );
+ }
+
+ $setup->endSetup();
+ }
+}
diff --git a/app/code/Magento/Downloadable/Test/Mftf/Data/LinkData.xml b/app/code/Magento/Downloadable/Test/Mftf/Data/LinkData.xml
new file mode 100644
index 0000000000000..4f48ca7309baa
--- /dev/null
+++ b/app/code/Magento/Downloadable/Test/Mftf/Data/LinkData.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+ Api Downloadable Link
+ 2.00
+ url
+ No
+ 1000
+ 0
+ https://static.magento.com/sites/all/themes/mag_redesign/images/magento-logo.svg
+
+
diff --git a/app/code/Magento/Downloadable/Test/Mftf/Data/ProductData.xml b/app/code/Magento/Downloadable/Test/Mftf/Data/ProductData.xml
new file mode 100644
index 0000000000000..28b4aa261b941
--- /dev/null
+++ b/app/code/Magento/Downloadable/Test/Mftf/Data/ProductData.xml
@@ -0,0 +1,26 @@
+
+
+
+
+
+ api-downloadable-product
+ downloadable
+ 4
+ 4
+ Api Downloadable Product
+ 123.00
+ api-downloadable-product
+ 1
+ 100
+ EavStockItem
+ ApiProductDescription
+ ApiProductShortDescription
+ apiDownloadableLink
+
+
diff --git a/app/code/Magento/Downloadable/Test/Mftf/Metadata/downloadable_link-meta.xml b/app/code/Magento/Downloadable/Test/Mftf/Metadata/downloadable_link-meta.xml
new file mode 100644
index 0000000000000..2511244d445c1
--- /dev/null
+++ b/app/code/Magento/Downloadable/Test/Mftf/Metadata/downloadable_link-meta.xml
@@ -0,0 +1,31 @@
+
+
+
+
+
+ application/json
+
+ boolean
+
+
diff --git a/app/code/Magento/Downloadable/Test/Mftf/Metadata/link_file_content-meta.xml b/app/code/Magento/Downloadable/Test/Mftf/Metadata/link_file_content-meta.xml
new file mode 100644
index 0000000000000..d5d6c16c71736
--- /dev/null
+++ b/app/code/Magento/Downloadable/Test/Mftf/Metadata/link_file_content-meta.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+ string
+ string
+
+
diff --git a/app/code/Magento/Downloadable/Test/Mftf/Metadata/sample_file_content-meta.xml b/app/code/Magento/Downloadable/Test/Mftf/Metadata/sample_file_content-meta.xml
new file mode 100644
index 0000000000000..3da91807ceb48
--- /dev/null
+++ b/app/code/Magento/Downloadable/Test/Mftf/Metadata/sample_file_content-meta.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+ string
+ string
+
+
diff --git a/app/code/Magento/Downloadable/etc/module.xml b/app/code/Magento/Downloadable/etc/module.xml
index 4c4e165feb014..fb13121335730 100644
--- a/app/code/Magento/Downloadable/etc/module.xml
+++ b/app/code/Magento/Downloadable/etc/module.xml
@@ -6,7 +6,7 @@
*/
-->
-
+
diff --git a/app/code/Magento/Eav/Model/Entity/Attribute/AbstractAttribute.php b/app/code/Magento/Eav/Model/Entity/Attribute/AbstractAttribute.php
index 65b60be0d96fd..1f13fe1405273 100644
--- a/app/code/Magento/Eav/Model/Entity/Attribute/AbstractAttribute.php
+++ b/app/code/Magento/Eav/Model/Entity/Attribute/AbstractAttribute.php
@@ -392,7 +392,7 @@ public function getIsVisibleOnFront()
}
/**
- * @return string|int|bool|float
+ * @return string|null
* @codeCoverageIgnore
*/
public function getDefaultValue()
diff --git a/app/code/Magento/Eav/Model/Entity/Attribute/Backend/AbstractBackend.php b/app/code/Magento/Eav/Model/Entity/Attribute/Backend/AbstractBackend.php
index 38e7b883f4ea5..e4bf1892d7222 100644
--- a/app/code/Magento/Eav/Model/Entity/Attribute/Backend/AbstractBackend.php
+++ b/app/code/Magento/Eav/Model/Entity/Attribute/Backend/AbstractBackend.php
@@ -203,12 +203,12 @@ public function getEntityValueId($entity)
/**
* Retrieve default value
*
- * @return mixed
+ * @return string
*/
public function getDefaultValue()
{
if ($this->_defaultValue === null) {
- if ($this->getAttribute()->getDefaultValue()) {
+ if ($this->getAttribute()->getDefaultValue() !== null) {
$this->_defaultValue = $this->getAttribute()->getDefaultValue();
} else {
$this->_defaultValue = "";
@@ -280,7 +280,7 @@ public function afterLoad($object)
public function beforeSave($object)
{
$attrCode = $this->getAttribute()->getAttributeCode();
- if (!$object->hasData($attrCode) && $this->getDefaultValue()) {
+ if (!$object->hasData($attrCode) && $this->getDefaultValue() !== '') {
$object->setData($attrCode, $this->getDefaultValue());
}
diff --git a/app/code/Magento/Eav/Model/Entity/Attribute/OptionManagement.php b/app/code/Magento/Eav/Model/Entity/Attribute/OptionManagement.php
index 01bdddc32af90..4a620ed5f35e5 100644
--- a/app/code/Magento/Eav/Model/Entity/Attribute/OptionManagement.php
+++ b/app/code/Magento/Eav/Model/Entity/Attribute/OptionManagement.php
@@ -132,7 +132,7 @@ public function getItems($entityType, $attributeCode)
*/
protected function validateOption($attribute, $optionId)
{
- if (!$attribute->getSource()->getOptionText($optionId)) {
+ if ($attribute->getSource()->getOptionText($optionId) === false) {
throw new NoSuchEntityException(
__('Attribute %1 does not contain option with Id %2', $attribute->getAttributeCode(), $optionId)
);
diff --git a/app/code/Magento/Eav/Model/Entity/Collection/AbstractCollection.php b/app/code/Magento/Eav/Model/Entity/Collection/AbstractCollection.php
index 77b4ce1c8eae8..2177686a4a6be 100644
--- a/app/code/Magento/Eav/Model/Entity/Collection/AbstractCollection.php
+++ b/app/code/Magento/Eav/Model/Entity/Collection/AbstractCollection.php
@@ -1233,7 +1233,7 @@ protected function _getLoadAttributesSelect($table, $attributeIds = [])
if ($entity->getEntityTable() == \Magento\Eav\Model\Entity::DEFAULT_ENTITY_TABLE && $entity->getTypeId()) {
$select->where(
- 'entity_type_id =?',
+ 't_d.entity_type_id =?',
$entity->getTypeId()
);
}
diff --git a/app/code/Magento/Email/Model/AbstractTemplate.php b/app/code/Magento/Email/Model/AbstractTemplate.php
index 81e993fad76df..a6ecdaf24ebbb 100644
--- a/app/code/Magento/Email/Model/AbstractTemplate.php
+++ b/app/code/Magento/Email/Model/AbstractTemplate.php
@@ -3,6 +3,7 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
+
namespace Magento\Email\Model;
use Magento\Framework\App\Filesystem\DirectoryList;
@@ -289,7 +290,7 @@ public function loadDefault($templateId)
/**
* trim copyright message
*/
- if (preg_match('/^/m', $templateText, $matches) && strpos($matches[0], 'Copyright') > 0) {
+ if (preg_match('/^/m', $templateText, $matches) && strpos($matches[0], 'Copyright') !== false) {
$templateText = str_replace($matches[0], '', $templateText);
}
@@ -530,13 +531,13 @@ protected function cancelDesignConfig()
*
* @param string $templateId
* @return $this
- * @throws \Magento\Framework\Exception\MailException
*/
public function setForcedArea($templateId)
{
- if (!isset($this->area)) {
+ if ($this->area === null) {
$this->area = $this->emailConfig->getTemplateArea($templateId);
}
+
return $this;
}
@@ -604,7 +605,9 @@ public function getDesignConfig()
public function setDesignConfig(array $config)
{
if (!isset($config['area']) || !isset($config['store'])) {
- throw new LocalizedException(__('Design config must have area and store.'));
+ throw new LocalizedException(
+ __('The design config needs an area and a store. Verify that both are set and try again.')
+ );
}
$this->getDesignConfig()->setData($config);
return $this;
diff --git a/app/code/Magento/Email/Model/Transport.php b/app/code/Magento/Email/Model/Transport.php
index 7e966a9a5a28d..dc7824a228e34 100644
--- a/app/code/Magento/Email/Model/Transport.php
+++ b/app/code/Magento/Email/Model/Transport.php
@@ -7,13 +7,16 @@
use Magento\Framework\App\Config\ScopeConfigInterface;
use Magento\Framework\Exception\MailException;
-use Magento\Framework\Mail\MessageInterface;
+use Magento\Framework\Mail\Message;
use Magento\Framework\Mail\TransportInterface;
+use Magento\Framework\Phrase;
use Magento\Store\Model\ScopeInterface;
+use Zend\Mail\Message as ZendMessage;
+use Zend\Mail\Transport\Sendmail;
/**
* Class that responsible for filling some message data before transporting it.
- * @see Zend_Mail_Transport_Sendmail is used for transport
+ * @see \Zend\Mail\Transport\Sendmail is used for transport
*/
class Transport implements TransportInterface
{
@@ -29,79 +32,81 @@ class Transport implements TransportInterface
const XML_PATH_SENDING_RETURN_PATH_EMAIL = 'system/smtp/return_path_email';
/**
- * Object for sending eMails
+ * Whether return path should be set or no.
*
- * @var \Zend_Mail_Transport_Sendmail
+ * Possible values are:
+ * 0 - no
+ * 1 - yes (set value as FROM address)
+ * 2 - use custom value
+ *
+ * @var int
*/
- private $transport;
+ private $isSetReturnPath;
/**
- * Email message object that should be instance of \Zend_Mail
- *
- * @var MessageInterface
+ * @var string|null
+ */
+ private $returnPathValue;
+
+ /**
+ * @var Sendmail
+ */
+ private $zendTransport;
+
+ /**
+ * @var Message
*/
private $message;
/**
- * Core store config
- *
- * @var ScopeConfigInterface
+ * @var ZendMessage
*/
- private $scopeConfig;
+ private $zendMessage;
/**
- * @param \Zend_Mail_Transport_Sendmail $transport
- * @param MessageInterface $message Email message object
+ * @param Message $message Email message object
* @param ScopeConfigInterface $scopeConfig Core store config
- * @param string|array|\Zend_Config|null $parameters Config options for sendmail parameters
- *
- * @throws \InvalidArgumentException when $message is not an instance of \Zend_Mail
+ * @param ZendMessage $zendMessage
+ * @param null|string|array|\Traversable $parameters Config options for sendmail parameters
*/
public function __construct(
- \Zend_Mail_Transport_Sendmail $transport,
- MessageInterface $message,
- ScopeConfigInterface $scopeConfig
+ Message $message,
+ ScopeConfigInterface $scopeConfig,
+ ZendMessage $zendMessage,
+ $parameters = null
) {
- if (!$message instanceof \Zend_Mail) {
- throw new \InvalidArgumentException('The message should be an instance of \Zend_Mail');
- }
- $this->transport = $transport;
+ $this->isSetReturnPath = (int) $scopeConfig->getValue(
+ self::XML_PATH_SENDING_SET_RETURN_PATH,
+ ScopeInterface::SCOPE_STORE
+ );
+ $this->returnPathValue = $scopeConfig->getValue(
+ self::XML_PATH_SENDING_RETURN_PATH_EMAIL,
+ ScopeInterface::SCOPE_STORE
+ );
+
+ $this->zendTransport = new Sendmail($parameters);
$this->message = $message;
- $this->scopeConfig = $scopeConfig;
+ $this->zendMessage = $zendMessage;
}
/**
- * Sets Return-Path to email if necessary, and sends email if it is allowed by System Configurations
- *
- * @return void
- * @throws MailException
+ * @inheritdoc
*/
public function sendMessage()
{
try {
- /* configuration of whether return path should be set or no. Possible values are:
- * 0 - no
- * 1 - yes (set value as FROM address)
- * 2 - use custom value
- * @see Magento\Config\Model\Config\Source\Yesnocustom
- */
- $isSetReturnPath = $this->scopeConfig->getValue(
- self::XML_PATH_SENDING_SET_RETURN_PATH,
- ScopeInterface::SCOPE_STORE
- );
- $returnPathValue = $this->scopeConfig->getValue(
- self::XML_PATH_SENDING_RETURN_PATH_EMAIL,
- ScopeInterface::SCOPE_STORE
- );
-
- if ($isSetReturnPath == '1') {
- $this->message->setReturnPath($this->message->getFrom());
- } elseif ($isSetReturnPath == '2' && $returnPathValue !== null) {
- $this->message->setReturnPath($returnPathValue);
+ $message = $this->zendMessage->fromString($this->message->getRawMessage());
+ if (2 === $this->isSetReturnPath && $this->returnPathValue) {
+ $message->setSender($this->returnPathValue);
+ } elseif (1 === $this->isSetReturnPath && $message->getFrom()->count()) {
+ $fromAddressList = $message->getFrom();
+ $fromAddressList->rewind();
+ $message->setSender($fromAddressList->current()->getEmail());
}
- $this->transport->send($this->message);
+
+ $this->zendTransport->send($message);
} catch (\Exception $e) {
- throw new MailException(__($e->getMessage()), $e);
+ throw new MailException(new Phrase($e->getMessage()), $e);
}
}
diff --git a/app/code/Magento/Email/Test/Unit/Model/Plugin/WindowsSmtpConfigTest.php b/app/code/Magento/Email/Test/Unit/Model/Plugin/WindowsSmtpConfigTest.php
new file mode 100644
index 0000000000000..b06dc3cb48cbc
--- /dev/null
+++ b/app/code/Magento/Email/Test/Unit/Model/Plugin/WindowsSmtpConfigTest.php
@@ -0,0 +1,100 @@
+osInfoMock = $this->createMock(OsInfo::class);
+ $this->configMock = $this->createMock(ReinitableConfigInterface::class);
+ $this->transportMock = $this->createMock(TransportInterface::class);
+
+ $this->windowsSmtpConfig = $objectManager->getObject(
+ WindowsSmtpConfig::class,
+ [
+ 'config' => $this->configMock,
+ 'osInfo' => $this->osInfoMock
+ ]
+ );
+ }
+
+ /**
+ * Test if SMTP settings if windows server
+ *
+ * @return void
+ */
+ public function testBeforeSendMessageOsWindows()
+ {
+ $this->osInfoMock->expects($this->once())
+ ->method('isWindows')
+ ->willReturn(true);
+
+ $this->configMock->expects($this->exactly(2))
+ ->method('getValue')
+ ->willReturnMap([
+ [WindowsSmtpConfig::XML_SMTP_HOST, '127.0.0.1'],
+ [WindowsSmtpConfig::XML_SMTP_PORT, '80']
+ ]);
+
+ $this->windowsSmtpConfig->beforeSendMessage($this->transportMock);
+ }
+
+ /**
+ * Test if SMTP settings if not windows server
+ *
+ * @return void
+ */
+ public function testBeforeSendMessageOsIsWindows()
+ {
+ $this->osInfoMock->expects($this->once())
+ ->method('isWindows')
+ ->willReturn(false);
+
+ $this->configMock->expects($this->never())
+ ->method('getValue');
+
+ $this->windowsSmtpConfig->beforeSendMessage($this->transportMock);
+ }
+}
diff --git a/app/code/Magento/Email/Test/Unit/Model/Template/SenderResolverTest.php b/app/code/Magento/Email/Test/Unit/Model/Template/SenderResolverTest.php
new file mode 100644
index 0000000000000..8d2931f3e38f5
--- /dev/null
+++ b/app/code/Magento/Email/Test/Unit/Model/Template/SenderResolverTest.php
@@ -0,0 +1,112 @@
+scopeConfig = $this->createMock(ScopeConfigInterface::class);
+
+ $this->senderResolver = $objectManager->getObject(
+ SenderResolver::class,
+ [
+ 'scopeConfig' => $this->scopeConfig
+ ]
+ );
+ }
+
+ /**
+ * Test returned information for given sender's name and email
+ *
+ * @return void
+ */
+ public function testResolve()
+ {
+ $sender = 'general';
+ $scopeId = null;
+
+ $this->scopeConfig->expects($this->exactly(2))
+ ->method('getValue')
+ ->willReturnMap([
+ [
+ 'trans_email/ident_' . $sender . '/name',
+ \Magento\Store\Model\ScopeInterface::SCOPE_STORE,
+ $scopeId,
+ 'Test Name'
+ ],
+ [
+ 'trans_email/ident_' . $sender . '/email',
+ \Magento\Store\Model\ScopeInterface::SCOPE_STORE,
+ $scopeId,
+ 'test@email.com'
+ ]
+ ]);
+
+ $result = $this->senderResolver->resolve($sender);
+
+ $this->assertTrue(isset($result['name']));
+ $this->assertEquals('Test Name', $result['name']);
+
+ $this->assertTrue(isset($result['email']));
+ $this->assertEquals('test@email.com', $result['email']);
+ }
+
+ /**
+ * Test if exception is thrown in case there is no name or email in result
+ *
+ * @dataProvider dataProvidedSenderArray
+ * @param array $sender
+ *
+ * @return void
+ */
+ public function testResolveThrowException(array $sender)
+ {
+ $this->expectExceptionMessage('Invalid sender data');
+ $this->expectException(MailException::class);
+ $this->senderResolver->resolve($sender);
+ }
+
+ /**
+ * @return array
+ */
+ public function dataProvidedSenderArray()
+ {
+ return [
+ [
+ ['name' => 'Name']
+ ],
+ [
+ ['email' => 'test@email.com']
+ ]
+ ];
+ }
+}
diff --git a/app/code/Magento/Email/Test/Unit/Model/TransportTest.php b/app/code/Magento/Email/Test/Unit/Model/TransportTest.php
deleted file mode 100644
index 3589e43996936..0000000000000
--- a/app/code/Magento/Email/Test/Unit/Model/TransportTest.php
+++ /dev/null
@@ -1,192 +0,0 @@
-transportMock = $this->createMock(\Zend_Mail_Transport_Sendmail::class);
-
- $this->messageMock = $this->createMock(Message::class);
-
- $this->scopeConfigMock = $this->getMockForAbstractClass(ScopeConfigInterface::class);
-
- $this->model = new Transport($this->transportMock, $this->messageMock, $this->scopeConfigMock);
- }
-
- /**
- * Tests that if any exception was caught, \Magento\Framework\Exception\MailException will thrown
- *
- * @expectedException \Magento\Framework\Exception\MailException
- */
- public function testSendMessageException()
- {
- $this->scopeConfigMock->expects($this->once())
- ->method('getValue')
- ->willThrowException(new \Exception('some exception'));
- $this->model->sendMessage();
- }
-
- /**
- * Tests that if sending Return-Path was disabled or email was not provided, - this header won't be set
- *
- * @param string|int|null $returnPathSet
- * @param string|null $returnPathEmail
- *
- * @dataProvider sendMessageWithoutReturnPathDataProvider
- */
- public function testSendMessageWithoutReturnPath($returnPathSet, $returnPathEmail = null)
- {
- $this->prepareSendingMessage($returnPathSet, $returnPathEmail);
-
- $this->messageMock->expects($this->never())
- ->method('setReturnPath');
- $this->transportMock->expects($this->once())
- ->method('send');
- $this->model->sendMessage();
- }
-
- /**
- * Tests that if sending Return-Path was disabled, this header won't be set
- *
- * @param string|int|null $returnPathSet
- * @param string|null $emailFrom
- *
- * @dataProvider sendMessageWithDefaultReturnPathDataProvider
- */
- public function testSendMessageWithDefaultReturnPath($returnPathSet, $emailFrom)
- {
- $this->prepareSendingMessage($returnPathSet, null);
-
- $this->messageMock->expects($this->once())
- ->method('setReturnPath')
- ->with($emailFrom);
- $this->messageMock->expects($this->once())
- ->method('getFrom')
- ->willReturn($emailFrom);
- $this->transportMock->expects($this->once())
- ->method('send');
- $this->model->sendMessage();
- }
-
- /**
- * Tests that if sending Return-Path was disabled, this header won't be set
- *
- * @param string|int|null $returnPathSet
- * @param string|null $emailFrom
- *
- * @dataProvider sendMessageWithCustomReturnPathDataProvider
- */
- public function testSendMessageWithCustomReturnPath($returnPathSet, $emailFrom)
- {
- $this->prepareSendingMessage($returnPathSet, $emailFrom);
-
- $this->messageMock->expects($this->once())
- ->method('setReturnPath')
- ->with($emailFrom);
- $this->messageMock->expects($this->never())
- ->method('getFrom')
- ->willReturn($emailFrom);
- $this->transportMock->expects($this->once())
- ->method('send');
- $this->model->sendMessage();
- }
-
- /**
- * Tests retrieving message object
- */
- public function testGetMessage()
- {
- $this->assertEquals($this->messageMock, $this->model->getMessage());
- }
-
- /**
- * Executes all main sets for sending message
- *
- * @param string|int|null $returnPathSet
- * @param string|null $returnPathEmail
- */
- private function prepareSendingMessage($returnPathSet, $returnPathEmail)
- {
- $map = [
- [Transport::XML_PATH_SENDING_SET_RETURN_PATH, ScopeInterface::SCOPE_STORE, null, $returnPathSet],
- [Transport::XML_PATH_SENDING_RETURN_PATH_EMAIL, ScopeInterface::SCOPE_STORE, null, $returnPathEmail]
- ];
- $this->scopeConfigMock->expects($this->exactly(2))
- ->method('getValue')
- ->willReturnMap($map);
- }
-
- /**
- * Data provider for testSendMessageWithoutReturnPath
- * @return array
- */
- public function sendMessageWithoutReturnPathDataProvider()
- {
- return [
- [0],
- ['0'],
- [3],
- ['2', null],
- [2, null],
- ];
- }
-
- /**
- * Data provider for testSendMessageWithDefaultReturnPath
- * @return array
- */
- public function sendMessageWithDefaultReturnPathDataProvider()
- {
- return [
- [1, 'test@exemple.com'],
- ['1', 'test@exemple.com'],
- ['1', '']
- ];
- }
-
- /**
- * Data provider for testSendMessageWithCustomReturnPath
- * @return array
- */
- public function sendMessageWithCustomReturnPathDataProvider()
- {
- return [
- [2, 'test@exemple.com'],
- ['2', 'test@exemple.com'],
- ['2', '']
- ];
- }
-}
diff --git a/app/code/Magento/GiftMessage/Test/Unit/Observer/SalesEventQuoteMergeTest.php b/app/code/Magento/GiftMessage/Test/Unit/Observer/SalesEventQuoteMergeTest.php
new file mode 100644
index 0000000000000..e7d9277593350
--- /dev/null
+++ b/app/code/Magento/GiftMessage/Test/Unit/Observer/SalesEventQuoteMergeTest.php
@@ -0,0 +1,81 @@
+salesEventQuoteMerge = $objectManger->getObject(SalesEventQuoteMerge::class);
+ }
+
+ /**
+ * @dataProvider dataProviderGiftMessageId
+ *
+ * @param null|int $giftMessageId
+ *
+ * @return void
+ */
+ public function testExecute($giftMessageId)
+ {
+ $sourceQuoteMock = $this->createPartialMock(Quote::class, ['getGiftMessageId']);
+ $sourceQuoteMock->expects($this->once())
+ ->method('getGiftMessageId')
+ ->willReturn($giftMessageId);
+
+ $targetQuoteMock = $this->createPartialMock(Quote::class, ['setGiftMessageId']);
+
+ if ($giftMessageId) {
+ $targetQuoteMock->expects($this->once())
+ ->method('setGiftMessageId');
+ } else {
+ $targetQuoteMock->expects($this->never())
+ ->method('setGiftMessageId');
+ }
+
+ $observer = $this->createMock(Observer::class);
+ $observer->expects($this->exactly(2))
+ ->method('getData')
+ ->willReturnMap([
+ ['quote', null, $targetQuoteMock],
+ ['source', null, $sourceQuoteMock]
+ ]);
+
+ $this->salesEventQuoteMerge->execute($observer);
+ }
+
+ /**
+ * @return array
+ */
+ public function dataProviderGiftMessageId(): array
+ {
+ return [
+ [null],
+ [1]
+ ];
+ }
+}
diff --git a/app/code/Magento/GroupedProduct/Test/Mftf/Data/GroupedProductData.xml b/app/code/Magento/GroupedProduct/Test/Mftf/Data/GroupedProductData.xml
new file mode 100644
index 0000000000000..e760b877fa33d
--- /dev/null
+++ b/app/code/Magento/GroupedProduct/Test/Mftf/Data/GroupedProductData.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+ api-grouped-product
+ grouped
+ 4
+ Api Grouped Product
+ 1
+ api-grouped-product
+ EavStockItem
+ ApiProductDescription
+ ApiProductShortDescription
+
+
diff --git a/app/code/Magento/GroupedProduct/Test/Mftf/Data/ProductLinkData.xml b/app/code/Magento/GroupedProduct/Test/Mftf/Data/ProductLinkData.xml
new file mode 100644
index 0000000000000..362e595593783
--- /dev/null
+++ b/app/code/Magento/GroupedProduct/Test/Mftf/Data/ProductLinkData.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+ associated
+ simple
+ 1
+ Qty1000
+
+
+ 2
+
+
diff --git a/app/code/Magento/GroupedProduct/Test/Mftf/Data/ProductLinkExtensionAttributeData.xml b/app/code/Magento/GroupedProduct/Test/Mftf/Data/ProductLinkExtensionAttributeData.xml
new file mode 100644
index 0000000000000..b580c876a6f30
--- /dev/null
+++ b/app/code/Magento/GroupedProduct/Test/Mftf/Data/ProductLinkExtensionAttributeData.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+ 1000
+
+
diff --git a/app/code/Magento/GroupedProduct/Test/Mftf/Data/ProductLinksData.xml b/app/code/Magento/GroupedProduct/Test/Mftf/Data/ProductLinksData.xml
new file mode 100644
index 0000000000000..68c95e856e2f8
--- /dev/null
+++ b/app/code/Magento/GroupedProduct/Test/Mftf/Data/ProductLinksData.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+ ProductLinkSimple1
+
+
+ ProductLinkSimple2
+
+
+
+ - ProductLinkSimple1
+ - ProductLinkSimple2
+
+
+
diff --git a/app/code/Magento/GroupedProduct/Test/Mftf/Metadata/product-meta.xml b/app/code/Magento/GroupedProduct/Test/Mftf/Metadata/product-meta.xml
new file mode 100644
index 0000000000000..87fa0a6379dd3
--- /dev/null
+++ b/app/code/Magento/GroupedProduct/Test/Mftf/Metadata/product-meta.xml
@@ -0,0 +1,44 @@
+
+
+
+
+
+
+ application/json
+
+
+
+
+ application/json
+
+
+ application/json
+
+
diff --git a/app/code/Magento/Integration/Model/CustomerTokenService.php b/app/code/Magento/Integration/Model/CustomerTokenService.php
index 947ca6f9f8821..adacf5ebacf71 100644
--- a/app/code/Magento/Integration/Model/CustomerTokenService.php
+++ b/app/code/Magento/Integration/Model/CustomerTokenService.php
@@ -14,6 +14,7 @@
use Magento\Integration\Model\ResourceModel\Oauth\Token\CollectionFactory as TokenCollectionFactory;
use Magento\Integration\Model\Oauth\Token\RequestThrottler;
use Magento\Framework\Exception\AuthenticationException;
+use Magento\Framework\Event\ManagerInterface;
class CustomerTokenService implements \Magento\Integration\Api\CustomerTokenServiceInterface
{
@@ -48,6 +49,11 @@ class CustomerTokenService implements \Magento\Integration\Api\CustomerTokenServ
*/
private $requestThrottler;
+ /**
+ * @var Magento\Framework\Event\ManagerInterface
+ */
+ private $eventManager;
+
/**
* Initialize service
*
@@ -55,17 +61,21 @@ class CustomerTokenService implements \Magento\Integration\Api\CustomerTokenServ
* @param AccountManagementInterface $accountManagement
* @param TokenCollectionFactory $tokenModelCollectionFactory
* @param \Magento\Integration\Model\CredentialsValidator $validatorHelper
+ * @param \Magento\Framework\Event\ManagerInterface $eventManager
*/
public function __construct(
TokenModelFactory $tokenModelFactory,
AccountManagementInterface $accountManagement,
TokenCollectionFactory $tokenModelCollectionFactory,
- CredentialsValidator $validatorHelper
+ CredentialsValidator $validatorHelper,
+ ManagerInterface $eventManager = null
) {
$this->tokenModelFactory = $tokenModelFactory;
$this->accountManagement = $accountManagement;
$this->tokenModelCollectionFactory = $tokenModelCollectionFactory;
$this->validatorHelper = $validatorHelper;
+ $this->eventManager = $eventManager ?: \Magento\Framework\App\ObjectManager::getInstance()
+ ->get(ManagerInterface::class);
}
/**
@@ -83,6 +93,7 @@ public function createCustomerAccessToken($username, $password)
__('You did not sign in correctly or your account is temporarily disabled.')
);
}
+ $this->eventManager->dispatch('customer_login', ['customer' => $customerDataObject]);
$this->getRequestThrottler()->resetAuthenticationFailuresCount($username, RequestThrottler::USER_TYPE_CUSTOMER);
return $this->tokenModelFactory->create()->createCustomerToken($customerDataObject->getId())->getToken();
}
diff --git a/app/code/Magento/Integration/Test/Unit/Model/CustomerTokenServiceTest.php b/app/code/Magento/Integration/Test/Unit/Model/CustomerTokenServiceTest.php
index ecd4788545c0a..170e7e42d919e 100644
--- a/app/code/Magento/Integration/Test/Unit/Model/CustomerTokenServiceTest.php
+++ b/app/code/Magento/Integration/Test/Unit/Model/CustomerTokenServiceTest.php
@@ -32,6 +32,9 @@ class CustomerTokenServiceTest extends \PHPUnit\Framework\TestCase
/** @var \Magento\Integration\Model\Oauth\Token|\PHPUnit_Framework_MockObject_MockObject */
private $_tokenMock;
+ /** @var \Magento\Framework\Event\ManagerInterface|\PHPUnit_Framework_MockObject_MockObject */
+ protected $manager;
+
protected function setUp()
{
$this->_tokenFactoryMock = $this->getMockBuilder(\Magento\Integration\Model\Oauth\TokenFactory::class)
@@ -67,11 +70,14 @@ protected function setUp()
\Magento\Integration\Model\CredentialsValidator::class
)->disableOriginalConstructor()->getMock();
+ $this->manager = $this->createMock(\Magento\Framework\Event\ManagerInterface::class);
+
$this->_tokenService = new \Magento\Integration\Model\CustomerTokenService(
$this->_tokenFactoryMock,
$this->_accountManagementMock,
$this->_tokenModelCollectionFactoryMock,
- $this->validatorHelperMock
+ $this->validatorHelperMock,
+ $this->manager
);
}
diff --git a/app/code/Magento/Integration/etc/webapi_rest/events.xml b/app/code/Magento/Integration/etc/webapi_rest/events.xml
new file mode 100644
index 0000000000000..e978698734277
--- /dev/null
+++ b/app/code/Magento/Integration/etc/webapi_rest/events.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Integration/etc/webapi_soap/events.xml b/app/code/Magento/Integration/etc/webapi_soap/events.xml
new file mode 100644
index 0000000000000..e978698734277
--- /dev/null
+++ b/app/code/Magento/Integration/etc/webapi_soap/events.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
diff --git a/app/code/Magento/NewRelicReporting/Model/Cron/ReportModulesInfo.php b/app/code/Magento/NewRelicReporting/Model/Cron/ReportModulesInfo.php
index 9cdc90bc46b2a..78c485c5bb6f5 100644
--- a/app/code/Magento/NewRelicReporting/Model/Cron/ReportModulesInfo.php
+++ b/app/code/Magento/NewRelicReporting/Model/Cron/ReportModulesInfo.php
@@ -64,6 +64,7 @@ public function report()
$moduleData = $this->collect->getModuleData();
if (count($moduleData['changes']) > 0) {
foreach ($moduleData['changes'] as $change) {
+ $modelData = [];
switch ($change['type']) {
case Config::ENABLED:
$modelData = [
diff --git a/app/code/Magento/NewRelicReporting/Model/NewRelicWrapper.php b/app/code/Magento/NewRelicReporting/Model/NewRelicWrapper.php
index ec21e06976b8b..9882a1ce9b0b8 100644
--- a/app/code/Magento/NewRelicReporting/Model/NewRelicWrapper.php
+++ b/app/code/Magento/NewRelicReporting/Model/NewRelicWrapper.php
@@ -31,7 +31,7 @@ public function addCustomParameter($param, $value)
/**
* Wrapper for 'newrelic_notice_error' function
*
- * @param Exception $exception
+ * @param \Exception $exception
* @return void
*/
public function reportError($exception)
diff --git a/app/code/Magento/Newsletter/Controller/Subscriber/Unsubscribe.php b/app/code/Magento/Newsletter/Controller/Subscriber/Unsubscribe.php
index efc469e15deaa..ed415d04450a6 100644
--- a/app/code/Magento/Newsletter/Controller/Subscriber/Unsubscribe.php
+++ b/app/code/Magento/Newsletter/Controller/Subscriber/Unsubscribe.php
@@ -4,13 +4,17 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
+
namespace Magento\Newsletter\Controller\Subscriber;
+/**
+ * Controller for unsubscribing customers.
+ */
class Unsubscribe extends \Magento\Newsletter\Controller\Subscriber
{
/**
* Unsubscribe newsletter
- * @return void
+ * @return \Magento\Backend\Model\View\Result\Redirect
*/
public function execute()
{
@@ -27,6 +31,10 @@ public function execute()
$this->messageManager->addException($e, __('Something went wrong while unsubscribing you.'));
}
}
- $this->getResponse()->setRedirect($this->_redirect->getRedirectUrl());
+ /** @var \Magento\Backend\Model\View\Result\Redirect $redirect */
+ $redirect = $this->resultFactory->create(\Magento\Framework\Controller\ResultFactory::TYPE_REDIRECT);
+ $redirectUrl = $this->_redirect->getRedirectUrl();
+
+ return $redirect->setUrl($redirectUrl);
}
}
diff --git a/app/code/Magento/Newsletter/Model/Subscriber.php b/app/code/Magento/Newsletter/Model/Subscriber.php
index b916f20dbe770..5e0d5448f11ca 100644
--- a/app/code/Magento/Newsletter/Model/Subscriber.php
+++ b/app/code/Magento/Newsletter/Model/Subscriber.php
@@ -594,6 +594,8 @@ protected function _updateCustomerSubscription($customerId, $subscribe)
} elseif (($this->getStatus() == self::STATUS_UNCONFIRMED) && ($customerData->getConfirmation() === null)) {
$status = self::STATUS_SUBSCRIBED;
$sendInformationEmail = true;
+ } elseif (($this->getStatus() == self::STATUS_NOT_ACTIVE) && ($customerData->getConfirmation() === null)) {
+ $status = self::STATUS_NOT_ACTIVE;
} else {
$status = self::STATUS_UNSUBSCRIBED;
}
diff --git a/app/code/Magento/OfflineShipping/Model/Carrier/Tablerate.php b/app/code/Magento/OfflineShipping/Model/Carrier/Tablerate.php
index b67eb1b45fe45..cead9f838f4c0 100644
--- a/app/code/Magento/OfflineShipping/Model/Carrier/Tablerate.php
+++ b/app/code/Magento/OfflineShipping/Model/Carrier/Tablerate.php
@@ -60,6 +60,7 @@ class Tablerate extends \Magento\Shipping\Model\Carrier\AbstractCarrier implemen
* @param \Magento\Quote\Model\Quote\Address\RateResult\MethodFactory $resultMethodFactory
* @param \Magento\OfflineShipping\Model\ResourceModel\Carrier\TablerateFactory $tablerateFactory
* @param array $data
+ * @throws LocalizedException
* @SuppressWarnings(PHPMD.UnusedLocalVariable)
*/
public function __construct(
diff --git a/app/code/Magento/Paypal/Model/Express/Checkout.php b/app/code/Magento/Paypal/Model/Express/Checkout.php
index 4639a2705a289..f0b86588f1cfa 100644
--- a/app/code/Magento/Paypal/Model/Express/Checkout.php
+++ b/app/code/Magento/Paypal/Model/Express/Checkout.php
@@ -14,8 +14,8 @@
use Magento\Sales\Model\Order\Email\Sender\OrderSender;
/**
- * Wrapper that performs Paypal Express and Checkout communication
- * Use current Paypal Express method instance
+ * Wrapper that performs Paypal Express and Checkout communication.
+ *
* @SuppressWarnings(PHPMD.TooManyFields)
* @SuppressWarnings(PHPMD.ExcessiveClassComplexity)
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
@@ -24,6 +24,7 @@ class Checkout
{
/**
* Cache ID prefix for "pal" lookup
+ *
* @var string
*/
const PAL_CACHE_ID = 'paypal_express_checkout_pal';
@@ -364,8 +365,9 @@ public function __construct(
}
/**
- * Checkout with PayPal image URL getter
- * Spares API calls of getting "pal" variable, by putting it into cache per store view
+ * Checkout with PayPal image URL getter.
+ *
+ * Spares API calls of getting "pal" variable, by putting it into cache per store view.
*
* @return string
*/
@@ -597,8 +599,8 @@ public function canSkipOrderReviewStep()
/**
* Update quote when returned from PayPal
- * rewrite billing address by paypal
- * save old billing address for new customer
+ *
+ * Rewrite billing address by paypal, save old billing address for new customer, and
* export shipping address in case address absence
*
* @param string $token
@@ -614,14 +616,15 @@ public function returnFromPaypal($token)
$this->ignoreAddressValidation();
+ // check if we came from the Express Checkout button
+ $isButton = (bool)$quote->getPayment()->getAdditionalInformation(self::PAYMENT_INFO_BUTTON);
+
// import shipping address
$exportedShippingAddress = $this->_getApi()->getExportedShippingAddress();
if (!$quote->getIsVirtual()) {
$shippingAddress = $quote->getShippingAddress();
if ($shippingAddress) {
- if ($exportedShippingAddress
- && $quote->getPayment()->getAdditionalInformation(self::PAYMENT_INFO_BUTTON) == 1
- ) {
+ if ($exportedShippingAddress && $isButton) {
$this->_setExportedAddressData($shippingAddress, $exportedShippingAddress);
// PayPal doesn't provide detailed shipping info: prefix, middlename, lastname, suffix
$shippingAddress->setPrefix(null);
@@ -649,12 +652,11 @@ public function returnFromPaypal($token)
}
// import billing address
- $portBillingFromShipping = $quote->getPayment()->getAdditionalInformation(self::PAYMENT_INFO_BUTTON) == 1
- && $this->_config->getValue(
- 'requireBillingAddress'
- ) != \Magento\Paypal\Model\Config::REQUIRE_BILLING_ADDRESS_ALL
- && !$quote->isVirtual();
- if ($portBillingFromShipping) {
+ $requireBillingAddress = (int)$this->_config->getValue(
+ 'requireBillingAddress'
+ ) === \Magento\Paypal\Model\Config::REQUIRE_BILLING_ADDRESS_ALL;
+
+ if ($isButton && !$requireBillingAddress && !$quote->isVirtual()) {
$billingAddress = clone $shippingAddress;
$billingAddress->unsAddressId()->unsAddressType()->setCustomerAddressId(null);
$data = $billingAddress->getData();
@@ -662,11 +664,17 @@ public function returnFromPaypal($token)
$quote->getBillingAddress()->addData($data);
$quote->getShippingAddress()->setSameAsBilling(1);
} else {
- $billingAddress = $quote->getBillingAddress();
+ $billingAddress = $quote->getBillingAddress()->setCustomerAddressId(null);
}
$exportedBillingAddress = $this->_getApi()->getExportedBillingAddress();
- $this->_setExportedAddressData($billingAddress, $exportedBillingAddress);
+ // Since country is required field for billing and shipping address,
+ // we consider the address information to be empty if country is empty.
+ $isEmptyAddress = ($billingAddress->getCountryId() === null);
+
+ if ($requireBillingAddress || $isEmptyAddress) {
+ $this->_setExportedAddressData($billingAddress, $exportedBillingAddress);
+ }
$billingAddress->setCustomerNote($exportedBillingAddress->getData('note'));
$quote->setBillingAddress($billingAddress);
$quote->setCheckoutMethod($this->getCheckoutMethod());
@@ -894,7 +902,7 @@ public function getCheckoutMethod()
}
/**
- * Sets address data from exported address
+ * Sets address data from exported address.
*
* @param Address $address
* @param array $exportedAddress
@@ -902,17 +910,6 @@ public function getCheckoutMethod()
*/
protected function _setExportedAddressData($address, $exportedAddress)
{
- // Exported data is more priority if we came from Express Checkout button
- $isButton = (bool)$this->_quote->getPayment()->getAdditionalInformation(self::PAYMENT_INFO_BUTTON);
-
- // Since country is required field for billing and shipping address,
- // we consider the address information to be empty if country is empty.
- $isEmptyAddress = ($address->getCountryId() === null);
-
- if (!$isButton && !$isEmptyAddress) {
- return;
- }
-
foreach ($exportedAddress->getExportedKeys() as $key) {
$data = $exportedAddress->getData($key);
if (!empty($data)) {
@@ -949,9 +946,11 @@ protected function _setBillingAgreementRequest()
}
/**
+ * Get api
+ *
* @return \Magento\Paypal\Model\Api\Nvp
*/
- protected function _getApi()
+ protected function _getApi(): \Magento\Paypal\Model\Api\Nvp
{
if (null === $this->_api) {
$this->_api = $this->_apiTypeFactory->create($this->_apiType)->setConfigObject($this->_config);
@@ -960,9 +959,10 @@ protected function _getApi()
}
/**
- * Attempt to collect address shipping rates and return them for further usage in instant update API
- * Returns empty array if it was impossible to obtain any shipping rate
- * If there are shipping rates obtained, the method must return one of them as default.
+ * Attempt to collect address shipping rates and return them for further usage in instant update API.
+ *
+ * Returns empty array if it was impossible to obtain any shipping rate and
+ * if there are shipping rates obtained, the method must return one of them as default.
*
* @param Address $address
* @param bool $mayReturnEmpty
@@ -1043,22 +1043,23 @@ protected function _prepareShippingOptions(Address $address, $mayReturnEmpty = f
}
/**
- * Compare two shipping options based on their amounts
+ * Compare two shipping options based on their amounts.
*
- * This function is used as a callback comparison function in shipping options sorting process
- * @see self::_prepareShippingOptions()
+ * This function is used as a callback comparison function in shipping options sorting process.
*
+ * @see self::_prepareShippingOptions()
* @param \Magento\Framework\DataObject $option1
* @param \Magento\Framework\DataObject $option2
* @return int
*/
- protected static function cmpShippingOptions(DataObject $option1, DataObject $option2)
+ protected static function cmpShippingOptions(DataObject $option1, DataObject $option2): int
{
return $option1->getAmount() <=> $option2->getAmount();
}
/**
- * Try to find whether the code provided by PayPal corresponds to any of possible shipping rates
+ * Try to find whether the code provided by PayPal corresponds to any of possible shipping rates.
+ *
* This method was created only because PayPal has issues with returning the selected code.
* If in future the issue is fixed, we don't need to attempt to match it. It would be enough to set the method code
* before collecting shipping rates
@@ -1067,7 +1068,7 @@ protected static function cmpShippingOptions(DataObject $option1, DataObject $op
* @param string $selectedCode
* @return string
*/
- protected function _matchShippingMethodCode(Address $address, $selectedCode)
+ protected function _matchShippingMethodCode(Address $address, $selectedCode): string
{
$options = $this->_prepareShippingOptions($address, false);
foreach ($options as $option) {
@@ -1083,7 +1084,8 @@ protected function _matchShippingMethodCode(Address $address, $selectedCode)
}
/**
- * Create payment redirect url
+ * Create payment redirect url.
+ *
* @param bool|null $button
* @param string $token
* @return void
@@ -1106,7 +1108,8 @@ public function getCustomerSession()
}
/**
- * Set shipping options to api
+ * Set shipping options to api.
+ *
* @param \Magento\Paypal\Model\Cart $cart
* @param \Magento\Quote\Model\Quote\Address|null $address
* @return void
diff --git a/app/code/Magento/ProductVideo/i18n/de_DE.csv b/app/code/Magento/ProductVideo/i18n/de_DE.csv
index 7047317396999..ca24668bb8d16 100644
--- a/app/code/Magento/ProductVideo/i18n/de_DE.csv
+++ b/app/code/Magento/ProductVideo/i18n/de_DE.csv
@@ -7,3 +7,4 @@
"Preview Image","Preview Image"
"Get Video Information","Get Video Information"
"Youtube or Vimeo supported","Youtube or Vimeo supported"
+"Delete image in all store views","Delete image in all store views"
diff --git a/app/code/Magento/ProductVideo/i18n/en_US.csv b/app/code/Magento/ProductVideo/i18n/en_US.csv
index 2d226c6daefa3..debcab151cc91 100644
--- a/app/code/Magento/ProductVideo/i18n/en_US.csv
+++ b/app/code/Magento/ProductVideo/i18n/en_US.csv
@@ -40,3 +40,4 @@ Delete,Delete
"Autostart base video","Autostart base video"
"Show related video","Show related video"
"Auto restart video","Auto restart video"
+"Delete image in all store views","Delete image in all store views"
diff --git a/app/code/Magento/ProductVideo/i18n/es_ES.csv b/app/code/Magento/ProductVideo/i18n/es_ES.csv
index 7047317396999..ca24668bb8d16 100644
--- a/app/code/Magento/ProductVideo/i18n/es_ES.csv
+++ b/app/code/Magento/ProductVideo/i18n/es_ES.csv
@@ -7,3 +7,4 @@
"Preview Image","Preview Image"
"Get Video Information","Get Video Information"
"Youtube or Vimeo supported","Youtube or Vimeo supported"
+"Delete image in all store views","Delete image in all store views"
diff --git a/app/code/Magento/ProductVideo/i18n/fr_FR.csv b/app/code/Magento/ProductVideo/i18n/fr_FR.csv
index 7047317396999..ca24668bb8d16 100644
--- a/app/code/Magento/ProductVideo/i18n/fr_FR.csv
+++ b/app/code/Magento/ProductVideo/i18n/fr_FR.csv
@@ -7,3 +7,4 @@
"Preview Image","Preview Image"
"Get Video Information","Get Video Information"
"Youtube or Vimeo supported","Youtube or Vimeo supported"
+"Delete image in all store views","Delete image in all store views"
diff --git a/app/code/Magento/ProductVideo/i18n/nl_NL.csv b/app/code/Magento/ProductVideo/i18n/nl_NL.csv
index 7047317396999..ca24668bb8d16 100644
--- a/app/code/Magento/ProductVideo/i18n/nl_NL.csv
+++ b/app/code/Magento/ProductVideo/i18n/nl_NL.csv
@@ -7,3 +7,4 @@
"Preview Image","Preview Image"
"Get Video Information","Get Video Information"
"Youtube or Vimeo supported","Youtube or Vimeo supported"
+"Delete image in all store views","Delete image in all store views"
diff --git a/app/code/Magento/ProductVideo/i18n/pt_BR.csv b/app/code/Magento/ProductVideo/i18n/pt_BR.csv
index 7047317396999..ca24668bb8d16 100644
--- a/app/code/Magento/ProductVideo/i18n/pt_BR.csv
+++ b/app/code/Magento/ProductVideo/i18n/pt_BR.csv
@@ -7,3 +7,4 @@
"Preview Image","Preview Image"
"Get Video Information","Get Video Information"
"Youtube or Vimeo supported","Youtube or Vimeo supported"
+"Delete image in all store views","Delete image in all store views"
diff --git a/app/code/Magento/ProductVideo/i18n/zh_Hans_CN.csv b/app/code/Magento/ProductVideo/i18n/zh_Hans_CN.csv
index 7047317396999..ca24668bb8d16 100644
--- a/app/code/Magento/ProductVideo/i18n/zh_Hans_CN.csv
+++ b/app/code/Magento/ProductVideo/i18n/zh_Hans_CN.csv
@@ -7,3 +7,4 @@
"Preview Image","Preview Image"
"Get Video Information","Get Video Information"
"Youtube or Vimeo supported","Youtube or Vimeo supported"
+"Delete image in all store views","Delete image in all store views"
diff --git a/app/code/Magento/ProductVideo/view/adminhtml/layout/catalog_product_new.xml b/app/code/Magento/ProductVideo/view/adminhtml/layout/catalog_product_new.xml
index f5a22c50e6d0d..63bd5321ad30b 100644
--- a/app/code/Magento/ProductVideo/view/adminhtml/layout/catalog_product_new.xml
+++ b/app/code/Magento/ProductVideo/view/adminhtml/layout/catalog_product_new.xml
@@ -6,6 +6,9 @@
*/
-->
+
+
+
diff --git a/app/code/Magento/ProductVideo/view/adminhtml/templates/helper/gallery.phtml b/app/code/Magento/ProductVideo/view/adminhtml/templates/helper/gallery.phtml
index f0ae057bc724d..2ceb9e9c9d4f2 100755
--- a/app/code/Magento/ProductVideo/view/adminhtml/templates/helper/gallery.phtml
+++ b/app/code/Magento/ProductVideo/view/adminhtml/templates/helper/gallery.phtml
@@ -34,7 +34,7 @@ $elementToggleCode = $element->getToggleCode() ? $element->getToggleCode() : 'to
class="gallery"
data-mage-init='{"openVideoModal":{}}'
data-parent-component="= $block->escapeHtml($block->getData('config/parentComponent')) ?>"
- data-images="= $block->escapeHtml($block->getImagesJson()) ?>"
+ data-images="= $block->escapeHtmlAttr($block->getImagesJson()) ?>"
data-types="= $block->escapeHtml(
$this->helper('Magento\Framework\Json\Helper\Data')->jsonEncode($block->getImageTypes())
) ?>"
@@ -140,30 +140,37 @@ $elementToggleCode = $element->getToggleCode() ? $element->getToggleCode() : 'to
alt="<%- data.label %>"/>
-
+
= $block->escapeHtml(
diff --git a/app/code/Magento/ProductVideo/view/adminhtml/web/css/gallery-delete-tooltip.css b/app/code/Magento/ProductVideo/view/adminhtml/web/css/gallery-delete-tooltip.css
new file mode 100644
index 0000000000000..835a22683b157
--- /dev/null
+++ b/app/code/Magento/ProductVideo/view/adminhtml/web/css/gallery-delete-tooltip.css
@@ -0,0 +1,20 @@
+/**
+ * Copyright © Magento, Inc. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+.gallery .tooltip .delete-tooltiptext {
+ visibility: hidden;
+ width: 112px;
+ background-color: #373330;
+ color: #F7F3EB;
+ text-align: center;
+ padding: 5px 0;
+ position: absolute;
+ z-index: 1;
+ left: 30px;
+ top: 91px;
+}
+
+.gallery .tooltip:hover .delete-tooltiptext {
+ visibility: visible;
+}
diff --git a/app/code/Magento/Quote/Model/BillingAddressManagement.php b/app/code/Magento/Quote/Model/BillingAddressManagement.php
index 2cbca917c26a1..c89fc37b44689 100644
--- a/app/code/Magento/Quote/Model/BillingAddressManagement.php
+++ b/app/code/Magento/Quote/Model/BillingAddressManagement.php
@@ -76,6 +76,7 @@ public function assign($cartId, \Magento\Quote\Api\Data\AddressInterface $addres
{
/** @var \Magento\Quote\Model\Quote $quote */
$quote = $this->quoteRepository->getActive($cartId);
+ $address->setCustomerId($quote->getCustomerId());
$quote->removeAddress($quote->getBillingAddress()->getId());
$quote->setBillingAddress($address);
try {
diff --git a/app/code/Magento/Quote/Model/Product/Plugin/MarkQuotesRecollectMassDisabled.php b/app/code/Magento/Quote/Model/Product/Plugin/MarkQuotesRecollectMassDisabled.php
new file mode 100644
index 0000000000000..f18bb46fa63fb
--- /dev/null
+++ b/app/code/Magento/Quote/Model/Product/Plugin/MarkQuotesRecollectMassDisabled.php
@@ -0,0 +1,55 @@
+quoteResource = $quoteResource;
+ }
+
+ /**
+ * Clean quote items after mass disabling product
+ *
+ * @param \Magento\Catalog\Model\Product\Action $subject
+ * @param \Magento\Catalog\Model\Product\Action $result
+ * @param int[] $productIds
+ * @param int[] $attrData
+ * @param int $storeId
+ * @return \Magento\Catalog\Model\Product\Action
+ * @SuppressWarnings(PHPMD.UnusedFormalParameter)
+ */
+ public function afterUpdateAttributes(
+ ProductAction $subject,
+ ProductAction $result,
+ $productIds,
+ $attrData,
+ $storeId
+ ): ProductAction {
+ if (isset($attrData['status']) && $attrData['status'] === Status::STATUS_DISABLED) {
+ $this->quoteResource->markQuotesRecollect($productIds);
+ }
+
+ return $result;
+ }
+}
diff --git a/app/code/Magento/Quote/Model/Quote/TotalsCollector.php b/app/code/Magento/Quote/Model/Quote/TotalsCollector.php
index 8fa03232a0e8d..8f18a04a102fa 100644
--- a/app/code/Magento/Quote/Model/Quote/TotalsCollector.php
+++ b/app/code/Magento/Quote/Model/Quote/TotalsCollector.php
@@ -203,11 +203,12 @@ protected function _validateCouponCode(\Magento\Quote\Model\Quote $quote)
*/
protected function _collectItemsQtys(\Magento\Quote\Model\Quote $quote)
{
+ $quoteItems = $quote->getAllVisibleItems();
$quote->setItemsCount(0);
$quote->setItemsQty(0);
$quote->setVirtualItemsQty(0);
- foreach ($quote->getAllVisibleItems() as $item) {
+ foreach ($quoteItems as $item) {
if ($item->getParentItem()) {
continue;
}
diff --git a/app/code/Magento/Quote/Model/ResourceModel/Quote/Item/Collection.php b/app/code/Magento/Quote/Model/ResourceModel/Quote/Item/Collection.php
index ef1705e0ad100..309c89e3702f5 100644
--- a/app/code/Magento/Quote/Model/ResourceModel/Quote/Item/Collection.php
+++ b/app/code/Magento/Quote/Model/ResourceModel/Quote/Item/Collection.php
@@ -3,9 +3,16 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
+declare(strict_types=1);
+
namespace Magento\Quote\Model\ResourceModel\Quote\Item;
-use \Magento\Catalog\Model\ResourceModel\Product\Collection as ProductCollection;
+use Magento\Catalog\Api\Data\ProductInterface;
+use Magento\Catalog\Model\ResourceModel\Product\Collection as ProductCollection;
+use Magento\Catalog\Model\Product\Attribute\Source\Status as ProductStatus;
+use Magento\Quote\Model\Quote;
+use Magento\Quote\Model\Quote\Item as QuoteItem;
+use Magento\Quote\Model\ResourceModel\Quote\Item as ResourceQuoteItem;
/**
* Quote item resource collection
@@ -50,6 +57,11 @@ class Collection extends \Magento\Framework\Model\ResourceModel\Db\VersionContro
*/
private $storeManager;
+ /**
+ * @var bool $recollectQuote
+ */
+ private $recollectQuote = false;
+
/**
* @param \Magento\Framework\Data\Collection\EntityFactory $entityFactory
* @param \Psr\Log\LoggerInterface $logger
@@ -102,7 +114,7 @@ public function __construct(
*/
protected function _construct()
{
- $this->_init(\Magento\Quote\Model\Quote\Item::class, \Magento\Quote\Model\ResourceModel\Quote\Item::class);
+ $this->_init(QuoteItem::class, ResourceQuoteItem::class);
}
/**
@@ -110,7 +122,7 @@ protected function _construct()
*
* @return int
*/
- public function getStoreId()
+ public function getStoreId(): int
{
// Fallback to current storeId if no quote is provided
// (see https://github.com/magento/magento2/commit/9d3be732a88884a66d667b443b3dc1655ddd0721)
@@ -119,12 +131,12 @@ public function getStoreId()
}
/**
- * Set Quote object to Collection
+ * Set Quote object to Collection.
*
- * @param \Magento\Quote\Model\Quote $quote
+ * @param Quote $quote
* @return $this
*/
- public function setQuote($quote)
+ public function setQuote($quote): self
{
$this->_quote = $quote;
$quoteId = $quote->getId();
@@ -138,14 +150,15 @@ public function setQuote($quote)
}
/**
- * Reset the collection and inner join it to quotes table
+ * Reset the collection and inner join it to quotes table.
+ *
* Optionally can select items with specified product id only
*
* @param string $quotesTableName
* @param int $productId
* @return $this
*/
- public function resetJoinQuotes($quotesTableName, $productId = null)
+ public function resetJoinQuotes($quotesTableName, $productId = null): self
{
$this->getSelect()->reset()->from(
['qi' => $this->getResource()->getMainTable()],
@@ -162,11 +175,11 @@ public function resetJoinQuotes($quotesTableName, $productId = null)
}
/**
- * After load processing
+ * After load processing.
*
* @return $this
*/
- protected function _afterLoad()
+ protected function _afterLoad(): self
{
parent::_afterLoad();
@@ -195,11 +208,11 @@ protected function _afterLoad()
}
/**
- * Add options to items
+ * Add options to items.
*
* @return $this
*/
- protected function _assignOptions()
+ protected function _assignOptions(): self
{
$itemIds = array_keys($this->_items);
$optionCollection = $this->_itemOptionCollectionFactory->create()->addItemFilter($itemIds);
@@ -213,12 +226,12 @@ protected function _assignOptions()
}
/**
- * Add products to items and item options
+ * Add products to items and item options.
*
* @return $this
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
*/
- protected function _assignProducts()
+ protected function _assignProducts(): self
{
\Magento\Framework\Profiler::start('QUOTE:' . __METHOD__, ['group' => 'QUOTE', 'method' => __METHOD__]);
$productCollection = $this->_productCollectionFactory->create()->setStoreId(
@@ -240,46 +253,30 @@ protected function _assignProducts()
['collection' => $productCollection]
);
- $recollectQuote = false;
foreach ($this as $item) {
+ /** @var ProductInterface $product */
$product = $productCollection->getItemById($item->getProductId());
- if ($product) {
+ $isValidProduct = $this->isValidProduct($product);
+ $qtyOptions = [];
+ if ($isValidProduct) {
$product->setCustomOptions([]);
- $qtyOptions = [];
- $optionProductIds = [];
- foreach ($item->getOptions() as $option) {
- /**
- * Call type-specific logic for product associated with quote item
- */
- $product->getTypeInstance()->assignProductToOption(
- $productCollection->getItemById($option->getProductId()),
- $option,
- $product
- );
-
- if (is_object($option->getProduct()) && $option->getProduct()->getId() != $product->getId()) {
- $optionProductIds[$option->getProduct()->getId()] = $option->getProduct()->getId();
- }
- }
-
- if ($optionProductIds) {
- foreach ($optionProductIds as $optionProductId) {
- $qtyOption = $item->getOptionByCode('product_qty_' . $optionProductId);
- if ($qtyOption) {
- $qtyOptions[$optionProductId] = $qtyOption;
- }
+ $optionProductIds = $this->getOptionProductIds($item, $product, $productCollection);
+ foreach ($optionProductIds as $optionProductId) {
+ $qtyOption = $item->getOptionByCode('product_qty_' . $optionProductId);
+ if ($qtyOption) {
+ $qtyOptions[$optionProductId] = $qtyOption;
}
}
-
- $item->setQtyOptions($qtyOptions)->setProduct($product);
} else {
$item->isDeleted(true);
- $recollectQuote = true;
+ $this->recollectQuote = true;
+ }
+ if (!$item->isDeleted()) {
+ $item->setQtyOptions($qtyOptions)->setProduct($product);
+ $item->checkData();
}
- $item->checkData();
}
-
- if ($recollectQuote && $this->_quote) {
+ if ($this->recollectQuote && $this->_quote) {
$this->_quote->collectTotals();
}
\Magento\Framework\Profiler::stop('QUOTE:' . __METHOD__);
@@ -287,6 +284,57 @@ protected function _assignProducts()
return $this;
}
+ /**
+ * Get product Ids from option.
+ *
+ * @param QuoteItem $item
+ * @param ProductInterface $product
+ * @param ProductCollection $productCollection
+ * @return array
+ */
+ private function getOptionProductIds(
+ QuoteItem $item,
+ ProductInterface $product,
+ ProductCollection $productCollection
+ ): array {
+ $optionProductIds = [];
+ foreach ($item->getOptions() as $option) {
+ /**
+ * Call type-specific logic for product associated with quote item
+ */
+ $product->getTypeInstance()->assignProductToOption(
+ $productCollection->getItemById($option->getProductId()),
+ $option,
+ $product
+ );
+
+ if (is_object($option->getProduct()) && $option->getProduct()->getId() != $product->getId()) {
+ $isValidProduct = $this->isValidProduct($option->getProduct());
+ if (!$isValidProduct && !$item->isDeleted()) {
+ $item->isDeleted(true);
+ $this->recollectQuote = true;
+ continue;
+ }
+ $optionProductIds[$option->getProduct()->getId()] = $option->getProduct()->getId();
+ }
+ }
+
+ return $optionProductIds;
+ }
+
+ /**
+ * Check is valid product.
+ *
+ * @param ProductInterface $product
+ * @return bool
+ */
+ private function isValidProduct(ProductInterface $product): bool
+ {
+ $result = ($product && (int)$product->getStatus() !== ProductStatus::STATUS_DISABLED);
+
+ return $result;
+ }
+
/**
* Prevents adding stock status filter to the collection of products.
*
diff --git a/app/code/Magento/Quote/Test/Mftf/ActionGroup/ChangeStatusProductUsingProductGridActionGroup.xml b/app/code/Magento/Quote/Test/Mftf/ActionGroup/ChangeStatusProductUsingProductGridActionGroup.xml
new file mode 100644
index 0000000000000..dba4a94f3db2a
--- /dev/null
+++ b/app/code/Magento/Quote/Test/Mftf/ActionGroup/ChangeStatusProductUsingProductGridActionGroup.xml
@@ -0,0 +1,33 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Quote/Test/Mftf/Section/AdminProductGridSection.xml b/app/code/Magento/Quote/Test/Mftf/Section/AdminProductGridSection.xml
new file mode 100644
index 0000000000000..32ac73aca7c03
--- /dev/null
+++ b/app/code/Magento/Quote/Test/Mftf/Section/AdminProductGridSection.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
diff --git a/app/code/Magento/Quote/Test/Mftf/Test/StorefrontGuestCheckoutDisabledProductTest.xml b/app/code/Magento/Quote/Test/Mftf/Test/StorefrontGuestCheckoutDisabledProductTest.xml
new file mode 100644
index 0000000000000..0c20eb54d5446
--- /dev/null
+++ b/app/code/Magento/Quote/Test/Mftf/Test/StorefrontGuestCheckoutDisabledProductTest.xml
@@ -0,0 +1,121 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ SimpleTwoOption
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Quote/etc/di.xml b/app/code/Magento/Quote/etc/di.xml
index ecc6426855679..8add3786eb9a3 100644
--- a/app/code/Magento/Quote/etc/di.xml
+++ b/app/code/Magento/Quote/etc/di.xml
@@ -96,6 +96,9 @@
+
+
+
diff --git a/app/code/Magento/Reports/Model/ResourceModel/Quote/Collection.php b/app/code/Magento/Reports/Model/ResourceModel/Quote/Collection.php
index 671acc9701012..7b3e165687bac 100644
--- a/app/code/Magento/Reports/Model/ResourceModel/Quote/Collection.php
+++ b/app/code/Magento/Reports/Model/ResourceModel/Quote/Collection.php
@@ -5,7 +5,11 @@
*/
namespace Magento\Reports\Model\ResourceModel\Quote;
+use Magento\Store\Model\Store;
+
/**
+ * Collection of abandoned quotes with reports join.
+ *
* @api
* @since 100.0.2
*/
@@ -48,6 +52,24 @@ public function __construct(
$this->customerResource = $customerResource;
}
+ /**
+ * Filter collections by stores.
+ *
+ * @param array $storeIds
+ * @param bool $withAdmin
+ * @return $this
+ */
+ public function addStoreFilter(array $storeIds, bool $withAdmin = true)
+ {
+ if ($withAdmin) {
+ $storeIds[] = Store::DEFAULT_STORE_ID;
+ }
+
+ $this->addFieldToFilter('store_id', ['in' => $storeIds]);
+
+ return $this;
+ }
+
/**
* Prepare for abandoned report
*
diff --git a/app/code/Magento/Sales/Block/Adminhtml/Order/View/History.php b/app/code/Magento/Sales/Block/Adminhtml/Order/View/History.php
index 1912655a9292d..10b80b6f4e527 100644
--- a/app/code/Magento/Sales/Block/Adminhtml/Order/View/History.php
+++ b/app/code/Magento/Sales/Block/Adminhtml/Order/View/History.php
@@ -88,7 +88,8 @@ public function getStatuses()
*/
public function canSendCommentEmail()
{
- return $this->_salesData->canSendOrderCommentEmail($this->getOrder()->getStore()->getId());
+ return $this->_salesData->canSendOrderCommentEmail($this->getOrder()->getStore()->getId())
+ && $this->_authorization->isAllowed('Magento_Sales::email');
}
/**
diff --git a/app/code/Magento/Sales/Model/AdminOrder/Create.php b/app/code/Magento/Sales/Model/AdminOrder/Create.php
index c30bb5e328e8b..12d2a5396ddc4 100644
--- a/app/code/Magento/Sales/Model/AdminOrder/Create.php
+++ b/app/code/Magento/Sales/Model/AdminOrder/Create.php
@@ -16,6 +16,7 @@
use Magento\Quote\Model\Quote\Item;
use Magento\Sales\Api\Data\OrderAddressInterface;
use Magento\Sales\Model\Order;
+use Magento\Store\Model\StoreManagerInterface;
/**
* Order create model
@@ -243,6 +244,11 @@ class Create extends \Magento\Framework\DataObject implements \Magento\Checkout\
*/
private $dataObjectConverter;
+ /**
+ * @var StoreManagerInterface
+ */
+ private $storeManager;
+
/**
* @param \Magento\Framework\ObjectManagerInterface $objectManager
* @param \Magento\Framework\Event\ManagerInterface $eventManager
@@ -274,6 +280,7 @@ class Create extends \Magento\Framework\DataObject implements \Magento\Checkout\
* @param array $data
* @param \Magento\Framework\Serialize\Serializer\Json|null $serializer
* @param ExtensibleDataObjectConverter|null $dataObjectConverter
+ * @param StoreManagerInterface $storeManager
* @SuppressWarnings(PHPMD.ExcessiveParameterList)
*/
public function __construct(
@@ -306,7 +313,8 @@ public function __construct(
\Magento\Quote\Model\QuoteFactory $quoteFactory,
array $data = [],
\Magento\Framework\Serialize\Serializer\Json $serializer = null,
- ExtensibleDataObjectConverter $dataObjectConverter = null
+ ExtensibleDataObjectConverter $dataObjectConverter = null,
+ StoreManagerInterface $storeManager = null
) {
$this->_objectManager = $objectManager;
$this->_eventManager = $eventManager;
@@ -340,6 +348,7 @@ public function __construct(
parent::__construct($data);
$this->dataObjectConverter = $dataObjectConverter ?: ObjectManager::getInstance()
->get(ExtensibleDataObjectConverter::class);
+ $this->storeManager = $storeManager ?: ObjectManager::getInstance()->get(StoreManagerInterface::class);
}
/**
@@ -417,7 +426,8 @@ public function setRecollect($flag)
/**
* Recollect totals for customer cart.
- * Set recollect totals flag for quote
+ *
+ * Set recollect totals flag for quote.
*
* @return $this
*/
@@ -1327,6 +1337,7 @@ protected function _createCustomerForm(\Magento\Customer\Api\Data\CustomerInterf
/**
* Set and validate Quote address
+ *
* All errors added to _errors
*
* @param \Magento\Quote\Model\Quote\Address $address
@@ -1530,6 +1541,8 @@ public function resetShippingMethod()
*/
public function collectShippingRates()
{
+ $store = $this->getQuote()->getStore();
+ $this->storeManager->setCurrentStore($store);
$this->getQuote()->getShippingAddress()->setCollectShippingRates(true);
$this->collectRates();
diff --git a/app/code/Magento/Sales/Model/Order/Creditmemo/Total/Shipping.php b/app/code/Magento/Sales/Model/Order/Creditmemo/Total/Shipping.php
index f00334f496b2a..f644d0c3a5a63 100644
--- a/app/code/Magento/Sales/Model/Order/Creditmemo/Total/Shipping.php
+++ b/app/code/Magento/Sales/Model/Order/Creditmemo/Total/Shipping.php
@@ -37,6 +37,8 @@ public function __construct(
}
/**
+ * Collects credit memo shipping totals.
+ *
* @param \Magento\Sales\Model\Order\Creditmemo $creditmemo
* @return $this
* @throws \Magento\Framework\Exception\LocalizedException
@@ -55,12 +57,10 @@ public function collect(\Magento\Sales\Model\Order\Creditmemo $creditmemo)
$orderShippingInclTax = $order->getShippingInclTax();
$orderBaseShippingInclTax = $order->getBaseShippingInclTax();
$allowedTaxAmount = $order->getShippingTaxAmount() - $order->getShippingTaxRefunded();
- $baseAllowedTaxAmount = $order->getBaseShippingTaxAmount() - $order->getBaseShippingTaxRefunded();
$allowedAmountInclTax = $allowedAmount + $allowedTaxAmount;
- $baseAllowedAmountInclTax = $baseAllowedAmount + $baseAllowedTaxAmount;
-
- // for the credit memo
- $shippingAmount = $baseShippingAmount = $shippingInclTax = $baseShippingInclTax = 0;
+ $baseAllowedAmountInclTax = $orderBaseShippingInclTax
+ - $order->getBaseShippingRefunded()
+ - $order->getBaseShippingTaxRefunded();
// Check if the desired shipping amount to refund was specified (from invoice or another source).
if ($creditmemo->hasBaseShippingAmount()) {
@@ -128,7 +128,6 @@ private function isSuppliedShippingAmountInclTax($order)
/**
* Get the Tax Config.
- * In a future release, will become a constructor parameter.
*
* @return \Magento\Tax\Model\Config
*
diff --git a/app/code/Magento/Sales/Model/Order/Creditmemo/Total/Tax.php b/app/code/Magento/Sales/Model/Order/Creditmemo/Total/Tax.php
index a842c0470ad85..5ab9469441bef 100644
--- a/app/code/Magento/Sales/Model/Order/Creditmemo/Total/Tax.php
+++ b/app/code/Magento/Sales/Model/Order/Creditmemo/Total/Tax.php
@@ -5,9 +5,14 @@
*/
namespace Magento\Sales\Model\Order\Creditmemo\Total;
+/**
+ * Collects credit memo taxes.
+ */
class Tax extends AbstractTotal
{
/**
+ * Collects credit memo taxes.
+ *
* @param \Magento\Sales\Model\Order\Creditmemo $creditmemo
* @return $this
*
@@ -79,18 +84,10 @@ public function collect(\Magento\Sales\Model\Order\Creditmemo $creditmemo)
$totalDiscountTaxCompensation += $invoice->getShippingDiscountTaxCompensationAmount() * $taxFactor;
$baseTotalDiscountTaxCompensation +=
$invoice->getBaseShippingDiscountTaxCompensationAmnt() * $taxFactor;
- $shippingDiscountTaxCompensationAmount =
- $invoice->getShippingDiscountTaxCompensationAmount() * $taxFactor;
- $baseShippingDiscountTaxCompensationAmount =
- $invoice->getBaseShippingDiscountTaxCompensationAmnt() * $taxFactor;
$shippingTaxAmount = $creditmemo->roundPrice($shippingTaxAmount);
$baseShippingTaxAmount = $creditmemo->roundPrice($baseShippingTaxAmount, 'base');
$totalDiscountTaxCompensation = $creditmemo->roundPrice($totalDiscountTaxCompensation);
$baseTotalDiscountTaxCompensation = $creditmemo->roundPrice($baseTotalDiscountTaxCompensation, 'base');
- $shippingDiscountTaxCompensationAmount =
- $creditmemo->roundPrice($shippingDiscountTaxCompensationAmount);
- $baseShippingDiscountTaxCompensationAmount =
- $creditmemo->roundPrice($baseShippingDiscountTaxCompensationAmount, 'base');
if ($taxFactor < 1 && $invoice->getShippingTaxAmount() > 0) {
$isPartialShippingRefunded = true;
}
diff --git a/app/code/Magento/Sales/Model/Order/Invoice/Total/Tax.php b/app/code/Magento/Sales/Model/Order/Invoice/Total/Tax.php
index fd5c015d9db4f..6e12f10f0c679 100644
--- a/app/code/Magento/Sales/Model/Order/Invoice/Total/Tax.php
+++ b/app/code/Magento/Sales/Model/Order/Invoice/Total/Tax.php
@@ -5,6 +5,9 @@
*/
namespace Magento\Sales\Model\Order\Invoice\Total;
+/**
+ * Collects invoice taxes.
+ */
class Tax extends AbstractTotal
{
/**
@@ -69,11 +72,24 @@ public function collect(\Magento\Sales\Model\Order\Invoice $invoice)
}
}
+ $taxDiscountCompensationAmt = $totalDiscountTaxCompensation;
+ $baseTaxDiscountCompensationAmt = $baseTotalDiscountTaxCompensation;
+ $allowedDiscountTaxCompensation = $order->getDiscountTaxCompensationAmount() -
+ $order->getDiscountTaxCompensationInvoiced();
+ $allowedBaseDiscountTaxCompensation = $order->getBaseDiscountTaxCompensationAmount() -
+ $order->getBaseDiscountTaxCompensationInvoiced();
+
if ($this->_canIncludeShipping($invoice)) {
$totalTax += $order->getShippingTaxAmount();
$baseTotalTax += $order->getBaseShippingTaxAmount();
$totalDiscountTaxCompensation += $order->getShippingDiscountTaxCompensationAmount();
$baseTotalDiscountTaxCompensation += $order->getBaseShippingDiscountTaxCompensationAmnt();
+
+ $allowedDiscountTaxCompensation += $order->getShippingDiscountTaxCompensationAmount() -
+ $order->getShippingDiscountTaxCompensationInvoiced();
+ $allowedBaseDiscountTaxCompensation += $order->getBaseShippingDiscountTaxCompensationAmnt() -
+ $order->getBaseShippingDiscountTaxCompensationInvoiced();
+
$invoice->setShippingTaxAmount($order->getShippingTaxAmount());
$invoice->setBaseShippingTaxAmount($order->getBaseShippingTaxAmount());
$invoice->setShippingDiscountTaxCompensationAmount($order->getShippingDiscountTaxCompensationAmount());
@@ -81,14 +97,6 @@ public function collect(\Magento\Sales\Model\Order\Invoice $invoice)
}
$allowedTax = $order->getTaxAmount() - $order->getTaxInvoiced();
$allowedBaseTax = $order->getBaseTaxAmount() - $order->getBaseTaxInvoiced();
- $allowedDiscountTaxCompensation = $order->getDiscountTaxCompensationAmount() +
- $order->getShippingDiscountTaxCompensationAmount() -
- $order->getDiscountTaxCompensationInvoiced() -
- $order->getShippingDiscountTaxCompensationInvoiced();
- $allowedBaseDiscountTaxCompensation = $order->getBaseDiscountTaxCompensationAmount() +
- $order->getBaseShippingDiscountTaxCompensationAmnt() -
- $order->getBaseDiscountTaxCompensationInvoiced() -
- $order->getBaseShippingDiscountTaxCompensationInvoiced();
if ($invoice->isLast()) {
$totalTax = $allowedTax;
@@ -107,8 +115,8 @@ public function collect(\Magento\Sales\Model\Order\Invoice $invoice)
$invoice->setTaxAmount($totalTax);
$invoice->setBaseTaxAmount($baseTotalTax);
- $invoice->setDiscountTaxCompensationAmount($totalDiscountTaxCompensation);
- $invoice->setBaseDiscountTaxCompensationAmount($baseTotalDiscountTaxCompensation);
+ $invoice->setDiscountTaxCompensationAmount($taxDiscountCompensationAmt);
+ $invoice->setBaseDiscountTaxCompensationAmount($baseTaxDiscountCompensationAmt);
$invoice->setGrandTotal($invoice->getGrandTotal() + $totalTax + $totalDiscountTaxCompensation);
$invoice->setBaseGrandTotal($invoice->getBaseGrandTotal() + $baseTotalTax + $baseTotalDiscountTaxCompensation);
diff --git a/app/code/Magento/Sales/Model/ResourceModel/Order/Grid/Collection.php b/app/code/Magento/Sales/Model/ResourceModel/Order/Grid/Collection.php
index f6dd8f8527a53..82c612c1a781d 100644
--- a/app/code/Magento/Sales/Model/ResourceModel/Order/Grid/Collection.php
+++ b/app/code/Magento/Sales/Model/ResourceModel/Order/Grid/Collection.php
@@ -35,4 +35,19 @@ public function __construct(
) {
parent::__construct($entityFactory, $logger, $fetchStrategy, $eventManager, $mainTable, $resourceModel);
}
+
+ /**
+ * @inheritdoc
+ */
+ protected function _initSelect()
+ {
+ parent::_initSelect();
+
+ $tableDescription = $this->getConnection()->describeTable($this->getMainTable());
+ foreach ($tableDescription as $columnInfo) {
+ $this->addFilterToMap($columnInfo['COLUMN_NAME'], 'main_table.' . $columnInfo['COLUMN_NAME']);
+ }
+
+ return $this;
+ }
}
diff --git a/app/code/Magento/Sales/Model/Service/CreditmemoService.php b/app/code/Magento/Sales/Model/Service/CreditmemoService.php
index 905e9cc4eae1c..1173fa4b7eb5a 100644
--- a/app/code/Magento/Sales/Model/Service/CreditmemoService.php
+++ b/app/code/Magento/Sales/Model/Service/CreditmemoService.php
@@ -189,6 +189,8 @@ public function refund(
}
/**
+ * Checks if credit memo is available for refund.
+ *
* @param \Magento\Sales\Api\Data\CreditmemoInterface $creditmemo
* @return bool
* @throws \Magento\Framework\Exception\LocalizedException
@@ -211,7 +213,7 @@ protected function validateForRefund(\Magento\Sales\Api\Data\CreditmemoInterface
throw new \Magento\Framework\Exception\LocalizedException(
__(
'The most money available to refund is %1.',
- $creditmemo->getOrder()->formatBasePrice($baseAvailableRefund)
+ $creditmemo->getOrder()->formatPriceTxt($baseAvailableRefund)
)
);
}
@@ -219,8 +221,9 @@ protected function validateForRefund(\Magento\Sales\Api\Data\CreditmemoInterface
}
/**
- * @return \Magento\Sales\Model\Order\RefundAdapterInterface
+ * Initializes RefundAdapterInterface dependency.
*
+ * @return \Magento\Sales\Model\Order\RefundAdapterInterface
* @deprecated 100.1.3
*/
private function getRefundAdapter()
@@ -233,8 +236,9 @@ private function getRefundAdapter()
}
/**
- * @return \Magento\Framework\App\ResourceConnection|mixed
+ * Initializes ResourceConnection dependency.
*
+ * @return \Magento\Framework\App\ResourceConnection|mixed
* @deprecated 100.1.3
*/
private function getResource()
@@ -247,8 +251,9 @@ private function getResource()
}
/**
- * @return \Magento\Sales\Api\OrderRepositoryInterface
+ * Initializes OrderRepositoryInterface dependency.
*
+ * @return \Magento\Sales\Api\OrderRepositoryInterface
* @deprecated 100.1.3
*/
private function getOrderRepository()
@@ -261,8 +266,9 @@ private function getOrderRepository()
}
/**
- * @return \Magento\Sales\Api\InvoiceRepositoryInterface
+ * Initializes InvoiceRepositoryInterface dependency.
*
+ * @return \Magento\Sales\Api\InvoiceRepositoryInterface
* @deprecated 100.1.3
*/
private function getInvoiceRepository()
diff --git a/app/code/Magento/Sales/Test/Mftf/ActionGroup/AdminOrderGridActionGroup.xml b/app/code/Magento/Sales/Test/Mftf/ActionGroup/AdminOrderGridActionGroup.xml
new file mode 100644
index 0000000000000..65a4b512394d8
--- /dev/null
+++ b/app/code/Magento/Sales/Test/Mftf/ActionGroup/AdminOrderGridActionGroup.xml
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Sales/Test/Mftf/Section/AdminOrdersGridSection.xml b/app/code/Magento/Sales/Test/Mftf/Section/AdminOrdersGridSection.xml
index 18823b2b0acc2..f9e954548a4d0 100644
--- a/app/code/Magento/Sales/Test/Mftf/Section/AdminOrdersGridSection.xml
+++ b/app/code/Magento/Sales/Test/Mftf/Section/AdminOrdersGridSection.xml
@@ -11,7 +11,12 @@
diff --git a/app/code/Magento/Sales/Test/Mftf/Test/AdminCreateInvoiceTest.xml b/app/code/Magento/Sales/Test/Mftf/Test/AdminCreateInvoiceTest.xml
index eaad9912c6de1..82ac6d9515ce6 100644
--- a/app/code/Magento/Sales/Test/Mftf/Test/AdminCreateInvoiceTest.xml
+++ b/app/code/Magento/Sales/Test/Mftf/Test/AdminCreateInvoiceTest.xml
@@ -61,6 +61,7 @@
+
diff --git a/app/code/Magento/Sales/Test/Unit/Model/Service/CreditmemoServiceTest.php b/app/code/Magento/Sales/Test/Unit/Model/Service/CreditmemoServiceTest.php
index 2e668f0b0d6f1..68681c6c5a66b 100644
--- a/app/code/Magento/Sales/Test/Unit/Model/Service/CreditmemoServiceTest.php
+++ b/app/code/Magento/Sales/Test/Unit/Model/Service/CreditmemoServiceTest.php
@@ -3,10 +3,15 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
+declare(strict_types=1);
+
namespace Magento\Sales\Test\Unit\Model\Service;
use Magento\Sales\Model\Order;
+use Magento\Sales\Api\Data\CreditmemoInterface;
+use PHPUnit_Framework_MockObject_MockObject as MockObject;
+
/**
* Class CreditmemoServiceTest
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
@@ -14,34 +19,34 @@
class CreditmemoServiceTest extends \PHPUnit\Framework\TestCase
{
/**
- * @var \Magento\Sales\Api\CreditmemoRepositoryInterface|\PHPUnit_Framework_MockObject_MockObject
+ * @var \Magento\Sales\Api\CreditmemoRepositoryInterface|MockObject
*/
protected $creditmemoRepositoryMock;
/**
- * @var \Magento\Sales\Api\CreditmemoCommentRepositoryInterface|\PHPUnit_Framework_MockObject_MockObject
+ * @var \Magento\Sales\Api\CreditmemoCommentRepositoryInterface|MockObject
*/
protected $creditmemoCommentRepositoryMock;
/**
- * @var \Magento\Framework\Api\SearchCriteriaBuilder|\PHPUnit_Framework_MockObject_MockObject
+ * @var \Magento\Framework\Api\SearchCriteriaBuilder|MockObject
*/
protected $searchCriteriaBuilderMock;
/**
- * @var \Magento\Framework\Api\FilterBuilder|\PHPUnit_Framework_MockObject_MockObject
+ * @var \Magento\Framework\Api\FilterBuilder|MockObject
*/
protected $filterBuilderMock;
/**
- * @var \Magento\Sales\Model\Order\CreditmemoNotifier|\PHPUnit_Framework_MockObject_MockObject
+ * @var \Magento\Sales\Model\Order\CreditmemoNotifier|MockObject
*/
protected $creditmemoNotifierMock;
/**
- * @var \Magento\Framework\Pricing\PriceCurrencyInterface|\PHPUnit_Framework_MockObject_MockObject
+ * @var \Magento\Framework\Pricing\PriceCurrencyInterface|MockObject
*/
- private $priceCurrencyMock;
+ private $priceCurrency;
/**
* @var \Magento\Sales\Model\Service\CreditmemoService
@@ -79,7 +84,7 @@ protected function setUp()
['setField', 'setValue', 'setConditionType', 'create']
);
$this->creditmemoNotifierMock = $this->createMock(\Magento\Sales\Model\Order\CreditmemoNotifier::class);
- $this->priceCurrencyMock = $this->getMockBuilder(\Magento\Framework\Pricing\PriceCurrencyInterface::class)
+ $this->priceCurrency = $this->getMockBuilder(\Magento\Framework\Pricing\PriceCurrencyInterface::class)
->getMockForAbstractClass();
$this->objectManagerHelper = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this);
@@ -91,7 +96,7 @@ protected function setUp()
'searchCriteriaBuilder' => $this->searchCriteriaBuilderMock,
'filterBuilder' => $this->filterBuilderMock,
'creditmemoNotifier' => $this->creditmemoNotifierMock,
- 'priceCurrency' => $this->priceCurrencyMock,
+ 'priceCurrency' => $this->priceCurrency,
]
);
}
@@ -187,7 +192,7 @@ public function testRefund()
$orderMock->expects($this->once())->method('getBaseTotalPaid')->willReturn(10);
$creditMemoMock->expects($this->once())->method('getBaseGrandTotal')->willReturn(10);
- $this->priceCurrencyMock->expects($this->any())
+ $this->priceCurrency->expects($this->any())
->method('round')
->willReturnArgument(0);
@@ -259,7 +264,7 @@ public function testRefundPendingCreditMemo()
$orderMock->expects($this->once())->method('getBaseTotalPaid')->willReturn(10);
$creditMemoMock->expects($this->once())->method('getBaseGrandTotal')->willReturn(10);
- $this->priceCurrencyMock->expects($this->any())
+ $this->priceCurrency->expects($this->any())
->method('round')
->willReturnArgument(0);
@@ -324,27 +329,32 @@ public function testRefundExpectsMoneyAvailableToReturn()
$baseGrandTotal = 10;
$baseTotalRefunded = 9;
$baseTotalPaid = 10;
- $creditMemoMock = $this->getMockBuilder(\Magento\Sales\Api\Data\CreditmemoInterface::class)
- ->setMethods(['getId', 'getOrder', 'formatBasePrice'])
+ /** @var CreditmemoInterface|MockObject $creditMemo */
+ $creditMemo = $this->getMockBuilder(CreditmemoInterface::class)
+ ->setMethods(['getId', 'getOrder'])
->getMockForAbstractClass();
- $creditMemoMock->expects($this->once())->method('getId')->willReturn(null);
- $orderMock = $this->getMockBuilder(Order::class)->disableOriginalConstructor()->getMock();
- $creditMemoMock->expects($this->atLeastOnce())->method('getOrder')->willReturn($orderMock);
- $creditMemoMock->expects($this->once())->method('getBaseGrandTotal')->willReturn($baseGrandTotal);
- $orderMock->expects($this->atLeastOnce())->method('getBaseTotalRefunded')->willReturn($baseTotalRefunded);
- $this->priceCurrencyMock->expects($this->exactly(2))->method('round')->withConsecutive(
- [$baseTotalRefunded + $baseGrandTotal],
- [$baseTotalPaid]
- )->willReturnOnConsecutiveCalls(
- $baseTotalRefunded + $baseGrandTotal,
- $baseTotalPaid
- );
- $orderMock->expects($this->atLeastOnce())->method('getBaseTotalPaid')->willReturn($baseTotalPaid);
+ $creditMemo->method('getId')
+ ->willReturn(null);
+ /** @var Order|MockObject $order */
+ $order = $this->getMockBuilder(Order::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+ $creditMemo->method('getOrder')
+ ->willReturn($order);
+ $creditMemo->method('getBaseGrandTotal')
+ ->willReturn($baseGrandTotal);
+ $order->method('getBaseTotalRefunded')
+ ->willReturn($baseTotalRefunded);
+ $this->priceCurrency->method('round')
+ ->withConsecutive([$baseTotalRefunded + $baseGrandTotal], [$baseTotalPaid])
+ ->willReturnOnConsecutiveCalls($baseTotalRefunded + $baseGrandTotal, $baseTotalPaid);
+ $order->method('getBaseTotalPaid')
+ ->willReturn($baseTotalPaid);
$baseAvailableRefund = $baseTotalPaid - $baseTotalRefunded;
- $orderMock->expects($this->once())->method('formatBasePrice')->with(
- $baseAvailableRefund
- )->willReturn($baseAvailableRefund);
- $this->creditmemoService->refund($creditMemoMock, true);
+ $order->method('formatPriceTxt')
+ ->with($baseAvailableRefund)
+ ->willReturn($baseAvailableRefund);
+ $this->creditmemoService->refund($creditMemo, true);
}
/**
diff --git a/app/code/Magento/SalesRule/Model/Rule/Condition/Product.php b/app/code/Magento/SalesRule/Model/Rule/Condition/Product.php
index d2e7cabe473f4..c90894febd44b 100644
--- a/app/code/Magento/SalesRule/Model/Rule/Condition/Product.php
+++ b/app/code/Magento/SalesRule/Model/Rule/Condition/Product.php
@@ -25,6 +25,136 @@ protected function _addSpecialAttributes(array &$attributes)
$attributes['quote_item_qty'] = __('Quantity in cart');
$attributes['quote_item_price'] = __('Price in cart');
$attributes['quote_item_row_total'] = __('Row total in cart');
+
+ $attributes['parent::category_ids'] = __('Category (Parent only)');
+ $attributes['children::category_ids'] = __('Category (Children Only)');
+ }
+
+ /**
+ * Retrieve attribute
+ *
+ * @return string
+ */
+ public function getAttribute(): string
+ {
+ $attribute = $this->getData('attribute');
+ if (strpos($attribute, '::') !== false) {
+ list(, $attribute) = explode('::', $attribute);
+ }
+
+ return $attribute;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function getAttributeName()
+ {
+ $attribute = $this->getAttribute();
+ if ($this->getAttributeScope()) {
+ $attribute = $this->getAttributeScope() . '::' . $attribute;
+ }
+
+ return $this->getAttributeOption($attribute);
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function loadAttributeOptions()
+ {
+ $productAttributes = $this->_productResource->loadAllAttributes()->getAttributesByCode();
+
+ $attributes = [];
+ foreach ($productAttributes as $attribute) {
+ /* @var $attribute \Magento\Catalog\Model\ResourceModel\Eav\Attribute */
+ if (!$attribute->isAllowedForRuleCondition()
+ || !$attribute->getDataUsingMethod($this->_isUsedForRuleProperty)
+ ) {
+ continue;
+ }
+ $frontLabel = $attribute->getFrontendLabel();
+ $attributes[$attribute->getAttributeCode()] = $frontLabel;
+ $attributes['parent::' . $attribute->getAttributeCode()] = $frontLabel . __('(Parent Only)');
+ $attributes['children::' . $attribute->getAttributeCode()] = $frontLabel . __('(Children Only)');
+ }
+
+ $this->_addSpecialAttributes($attributes);
+
+ asort($attributes);
+ $this->setAttributeOption($attributes);
+
+ return $this;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function getAttributeElementHtml()
+ {
+ $html = parent::getAttributeElementHtml() .
+ $this->getAttributeScopeElement()->getHtml();
+
+ return $html;
+ }
+
+ /**
+ * Retrieve form element for scope element
+ *
+ * @return \Magento\Framework\Data\Form\Element\AbstractElement
+ */
+ private function getAttributeScopeElement(): \Magento\Framework\Data\Form\Element\AbstractElement
+ {
+ return $this->getForm()->addField(
+ $this->getPrefix() . '__' . $this->getId() . '__attribute_scope',
+ 'hidden',
+ [
+ 'name' => $this->elementName . '[' . $this->getPrefix() . '][' . $this->getId() . '][attribute_scope]',
+ 'value' => $this->getAttributeScope(),
+ 'no_span' => true,
+ 'class' => 'hidden',
+ 'data-form-part' => $this->getFormName(),
+ ]
+ );
+ }
+
+ /**
+ * Set attribute value
+ *
+ * @param string $value
+ * @return void
+ */
+ public function setAttribute(string $value)
+ {
+ if (strpos($value, '::') !== false) {
+ list($scope, $attribute) = explode('::', $value);
+ $this->setData('attribute_scope', $scope);
+ $this->setData('attribute', $attribute);
+ } else {
+ $this->setData('attribute', $value);
+ }
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function loadArray($arr)
+ {
+ parent::loadArray($arr);
+ $this->setAttributeScope($arr['attribute_scope'] ?? null);
+
+ return $this;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function asArray(array $arrAttributes = [])
+ {
+ $out = parent::asArray($arrAttributes);
+ $out['attribute_scope'] = $this->getAttributeScope();
+
+ return $out;
}
/**
diff --git a/app/code/Magento/SalesRule/Model/Rule/Condition/Product/Combine.php b/app/code/Magento/SalesRule/Model/Rule/Condition/Product/Combine.php
index b5ac02e67b1e1..2277240eb8aad 100644
--- a/app/code/Magento/SalesRule/Model/Rule/Condition/Product/Combine.php
+++ b/app/code/Magento/SalesRule/Model/Rule/Condition/Product/Combine.php
@@ -8,6 +8,8 @@
use Magento\Catalog\Model\ResourceModel\Product\Collection;
/**
+ * Combine conditions for product.
+ *
* @api
* @since 100.0.2
*/
@@ -85,4 +87,76 @@ public function collectValidatedAttributes($productCollection)
}
return $this;
}
+
+ /**
+ * @inheritdoc
+ */
+ protected function _isValid($entity)
+ {
+ if (!$this->getConditions()) {
+ return true;
+ }
+
+ $all = $this->getAggregator() === 'all';
+ $true = (bool)$this->getValue();
+
+ foreach ($this->getConditions() as $cond) {
+ if ($entity instanceof \Magento\Framework\Model\AbstractModel) {
+ $validated = $this->validateEntity($entity, $cond);
+ } else {
+ $validated = $cond->validateByEntityId($entity);
+ }
+ if ($all && $validated !== $true) {
+ return false;
+ } elseif (!$all && $validated === $true) {
+ return true;
+ }
+ }
+
+ return $all ? true : false;
+ }
+
+ /**
+ * Validate entity.
+ *
+ * @param \Magento\Framework\Model\AbstractModel $entity
+ * @param mixed $cond
+ * @return bool
+ */
+ private function validateEntity(\Magento\Framework\Model\AbstractModel $entity, $cond): bool
+ {
+ $true = (bool)$this->getValue();
+ $validated = !$true;
+ foreach ($this->retrieveValidateEntities($entity, $cond->getAttributeScope()) as $validateEntity) {
+ $validated = $cond->validate($validateEntity);
+ if ($validated === $true) {
+ break;
+ }
+ }
+
+ return $validated;
+ }
+
+ /**
+ * Retrieve entities for validation by attribute scope
+ *
+ * @param \Magento\Framework\Model\AbstractModel $entity
+ * @param string|null $attributeScope
+ * @return \Magento\Framework\Model\AbstractModel[]
+ */
+ private function retrieveValidateEntities(
+ \Magento\Framework\Model\AbstractModel $entity,
+ $attributeScope
+ ): array {
+ if ($attributeScope === 'parent') {
+ $validateEntities = [$entity];
+ } elseif ($attributeScope === 'children') {
+ $validateEntities = $entity->getChildren() ?: [$entity];
+ } else {
+ $validateEntities = $entity->getChildren() ?: [];
+ $validateEntities[] = $entity;
+ }
+
+ return $validateEntities;
+ }
}
diff --git a/app/code/Magento/SalesRule/Test/Mftf/Data/SalesRuleData.xml b/app/code/Magento/SalesRule/Test/Mftf/Data/SalesRuleData.xml
index 65f69e582bc07..35ad7f96bd18d 100644
--- a/app/code/Magento/SalesRule/Test/Mftf/Data/SalesRuleData.xml
+++ b/app/code/Magento/SalesRule/Test/Mftf/Data/SalesRuleData.xml
@@ -45,6 +45,15 @@
Percent of product price discount
50
+
+ SimpleSalesRule
+ Main Website
+ 'NOT LOGGED IN'
+ true
+ Specific Coupon
+ ABCD
+ 70
+
SimpleSalesRule
Sales Rule Descritpion
diff --git a/app/code/Magento/SalesRule/Test/Mftf/Page/CheckoutCartPage.xml b/app/code/Magento/SalesRule/Test/Mftf/Page/CheckoutCartPage.xml
new file mode 100644
index 0000000000000..83ec6095d57ee
--- /dev/null
+++ b/app/code/Magento/SalesRule/Test/Mftf/Page/CheckoutCartPage.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/SalesRule/Test/Mftf/Section/AdminCartPriceRulesFormSection.xml b/app/code/Magento/SalesRule/Test/Mftf/Section/AdminCartPriceRulesFormSection.xml
index 8839a9da0ba14..14584acd03376 100644
--- a/app/code/Magento/SalesRule/Test/Mftf/Section/AdminCartPriceRulesFormSection.xml
+++ b/app/code/Magento/SalesRule/Test/Mftf/Section/AdminCartPriceRulesFormSection.xml
@@ -32,6 +32,11 @@
+
+
+
+
+
diff --git a/app/code/Magento/SalesRule/Test/Mftf/Section/StorefrontDiscountSection.xml b/app/code/Magento/SalesRule/Test/Mftf/Section/StorefrontDiscountSection.xml
new file mode 100644
index 0000000000000..2ae50489b6d12
--- /dev/null
+++ b/app/code/Magento/SalesRule/Test/Mftf/Section/StorefrontDiscountSection.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
diff --git a/app/code/Magento/SalesRule/Test/Mftf/Test/CartPriceRuleForConfigurableProductTest.xml b/app/code/Magento/SalesRule/Test/Mftf/Test/CartPriceRuleForConfigurableProductTest.xml
new file mode 100644
index 0000000000000..f7010822f5f43
--- /dev/null
+++ b/app/code/Magento/SalesRule/Test/Mftf/Test/CartPriceRuleForConfigurableProductTest.xml
@@ -0,0 +1,174 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ SimpleTwoOption
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ -$7.00
+ $getDiscount
+
+
+ $20.00
+ $getSubtotal
+
+
+
diff --git a/app/code/Magento/SalesRule/Test/Unit/Model/Rule/Condition/ProductTest.php b/app/code/Magento/SalesRule/Test/Unit/Model/Rule/Condition/ProductTest.php
index b217c4bdf8c8b..929cdc1e1ec7e 100644
--- a/app/code/Magento/SalesRule/Test/Unit/Model/Rule/Condition/ProductTest.php
+++ b/app/code/Magento/SalesRule/Test/Unit/Model/Rule/Condition/ProductTest.php
@@ -295,8 +295,8 @@ public function testQuoteLocaleFormatPrice($isValid, $conditionValue, $operator
->method('getProduct')
->willReturn($product);
- $this->model->setAttribute('quote_item_price')
- ->setOperator($operator);
+ $this->model->setAttribute('quote_item_price');
+ $this->model->setData('operator', $operator);
$this->assertEquals($isValid, $this->model->setValue($conditionValue)->validate($item));
}
diff --git a/app/code/Magento/SendFriend/view/frontend/templates/send.phtml b/app/code/Magento/SendFriend/view/frontend/templates/send.phtml
index 2b25e0efab84a..4922a9f365ced 100644
--- a/app/code/Magento/SendFriend/view/frontend/templates/send.phtml
+++ b/app/code/Magento/SendFriend/view/frontend/templates/send.phtml
@@ -17,13 +17,13 @@
- = $block->escapeJs($block->escapeHtml(__('Remove'))) ?>
+ = $block->escapeHtml(__('Remove')) ?>
|