+
+ Requires navigation to the Product Creation page. Fills in the arguments listed below on the product page. Saves the changes.
+
@@ -27,7 +30,6 @@
1) Go to bundle product creation page
2) Will not Enable/Disable
-->
-
@@ -60,7 +62,7 @@
-
+
diff --git a/app/code/Magento/Bundle/Test/Mftf/ActionGroup/StoreFrontAddProductToCartFromBundleActionGroup.xml b/app/code/Magento/Bundle/Test/Mftf/ActionGroup/StoreFrontAddProductToCartFromBundleActionGroup.xml
index 441303e8f1b84..7038ad90b81b9 100644
--- a/app/code/Magento/Bundle/Test/Mftf/ActionGroup/StoreFrontAddProductToCartFromBundleActionGroup.xml
+++ b/app/code/Magento/Bundle/Test/Mftf/ActionGroup/StoreFrontAddProductToCartFromBundleActionGroup.xml
@@ -9,10 +9,14 @@
+
+ Adds a Bundled Product to the Cart with a specified Currency Value.
+
+
diff --git a/app/code/Magento/Bundle/Test/Mftf/ActionGroup/StorefrontProductCartActionGroup.xml b/app/code/Magento/Bundle/Test/Mftf/ActionGroup/StorefrontProductCartActionGroup.xml
index 1767db0a00974..b260068dedf7c 100644
--- a/app/code/Magento/Bundle/Test/Mftf/ActionGroup/StorefrontProductCartActionGroup.xml
+++ b/app/code/Magento/Bundle/Test/Mftf/ActionGroup/StorefrontProductCartActionGroup.xml
@@ -10,12 +10,16 @@
xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd">
+
+ Adds a Bundled Product to the Cart from the Category page.
+
-
-
+
+
+
@@ -27,9 +31,13 @@
+
+ Adds a Bundled Product to the Cart from the Product page. PLEASE NOTE: The Quantity selection is not available in the Action Group.
+
+
@@ -41,9 +49,13 @@
+
+ Adds a Bundled Product to the Cart from the Product page. PLEASE NOTE: The Quantity selection is not available in the Action Group.
+
+
@@ -52,10 +64,14 @@
+
+ Selects a Bundled Product option on the Bundled Product page. PLEASE NOTE: The Quantity selection is not available in the Action Group.
+
+
diff --git a/app/code/Magento/Bundle/Test/Mftf/ActionGroup/StorefrontSelectBundleProductDropDownOptionActionGroup.xml b/app/code/Magento/Bundle/Test/Mftf/ActionGroup/StorefrontSelectBundleProductDropDownOptionActionGroup.xml
index e6afdce6ab697..d4a2a3a32457d 100644
--- a/app/code/Magento/Bundle/Test/Mftf/ActionGroup/StorefrontSelectBundleProductDropDownOptionActionGroup.xml
+++ b/app/code/Magento/Bundle/Test/Mftf/ActionGroup/StorefrontSelectBundleProductDropDownOptionActionGroup.xml
@@ -9,9 +9,13 @@
+
+ Selects the provided Product Name on a Storefront Bundled Product page.
+
+
diff --git a/app/code/Magento/Bundle/Test/Mftf/ActionGroup/StorefrontSelectCustomizeAndAddToTheCartButtonActionGroup.xml b/app/code/Magento/Bundle/Test/Mftf/ActionGroup/StorefrontSelectCustomizeAndAddToTheCartButtonActionGroup.xml
index cf2ccfac47023..11e696c8a2d39 100644
--- a/app/code/Magento/Bundle/Test/Mftf/ActionGroup/StorefrontSelectCustomizeAndAddToTheCartButtonActionGroup.xml
+++ b/app/code/Magento/Bundle/Test/Mftf/ActionGroup/StorefrontSelectCustomizeAndAddToTheCartButtonActionGroup.xml
@@ -9,6 +9,10 @@
+
+ Clicks 'Customize and Add to Cart' on a Storefront Bundled Product page.
+
+
diff --git a/app/code/Magento/Bundle/Test/Mftf/ActionGroup/VerifyProductTypeOrderActionGroup.xml b/app/code/Magento/Bundle/Test/Mftf/ActionGroup/VerifyProductTypeOrderActionGroup.xml
index a00f1e367864e..caa45be8d297d 100644
--- a/app/code/Magento/Bundle/Test/Mftf/ActionGroup/VerifyProductTypeOrderActionGroup.xml
+++ b/app/code/Magento/Bundle/Test/Mftf/ActionGroup/VerifyProductTypeOrderActionGroup.xml
@@ -9,6 +9,10 @@
+
+ Checks that Bundle is in the correct order in comparison to other product types on the add product dropdown.
+
+
diff --git a/app/code/Magento/Bundle/Test/Mftf/Data/ProductData.xml b/app/code/Magento/Bundle/Test/Mftf/Data/ProductData.xml
index 5034c72b9ee3f..6e7e4a7a16573 100644
--- a/app/code/Magento/Bundle/Test/Mftf/Data/ProductData.xml
+++ b/app/code/Magento/Bundle/Test/Mftf/Data/ProductData.xml
@@ -107,4 +107,35 @@
CustomAttributeDynamicPrice
CustomAttributePriceViewRange
+
+ Test 123 Dynamic
+ test-dynamic-bundle-product
+ bundle
+ 4
+ 4
+ 1
+ test-dynamic-bundle-product
+ EavStockItem
+ CustomDynamicProductDescription
+ CustomDynamicProductShortDescription
+ CustomAttributeDynamicPrice
+ CustomAttributePriceViewRange
+
+
+ Test 123 Fixed
+ test-fixed-bundle-product
+ bundle
+ 4
+ 10
+ 4
+ 1
+ api-fixed-bundle-product
+ EavStockItem
+ CustomFixedProductDescription
+ CustomFixedProductShortDescription
+ CustomAttributePriceView
+ CustomAttributeFixPrice
+ CustomAttributeFixWeight
+ CustomAttributeFixSku
+
diff --git a/app/code/Magento/Bundle/Test/Mftf/Section/AdminProductFormBundleSection.xml b/app/code/Magento/Bundle/Test/Mftf/Section/AdminProductFormBundleSection.xml
index 516f40ac2e7b7..bd13f4daa0dbd 100644
--- a/app/code/Magento/Bundle/Test/Mftf/Section/AdminProductFormBundleSection.xml
+++ b/app/code/Magento/Bundle/Test/Mftf/Section/AdminProductFormBundleSection.xml
@@ -41,8 +41,9 @@
-
+
+
@@ -92,7 +93,7 @@
-
+
diff --git a/app/code/Magento/Bundle/Test/Mftf/Test/AdminAddBundleProductToCartFromWishListPageTest.xml b/app/code/Magento/Bundle/Test/Mftf/Test/AdminAddBundleProductToCartFromWishListPageTest.xml
new file mode 100644
index 0000000000000..2a4b119a5cabc
--- /dev/null
+++ b/app/code/Magento/Bundle/Test/Mftf/Test/AdminAddBundleProductToCartFromWishListPageTest.xml
@@ -0,0 +1,84 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Bundle/Test/Mftf/Test/AdminAssociateBundleProductToWebsitesTest.xml b/app/code/Magento/Bundle/Test/Mftf/Test/AdminAssociateBundleProductToWebsitesTest.xml
new file mode 100644
index 0000000000000..505a319c5c44f
--- /dev/null
+++ b/app/code/Magento/Bundle/Test/Mftf/Test/AdminAssociateBundleProductToWebsitesTest.xml
@@ -0,0 +1,126 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Bundle/Test/Mftf/Test/AdminCreateAndEditBundleProductSettingsTest.xml b/app/code/Magento/Bundle/Test/Mftf/Test/AdminCreateAndEditBundleProductSettingsTest.xml
new file mode 100644
index 0000000000000..f7a64f943f307
--- /dev/null
+++ b/app/code/Magento/Bundle/Test/Mftf/Test/AdminCreateAndEditBundleProductSettingsTest.xml
@@ -0,0 +1,145 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Bundle/Test/Mftf/Test/AdminShouldBeAbleToMassUpdateAttributesForBundleProductsTest.xml b/app/code/Magento/Bundle/Test/Mftf/Test/AdminShouldBeAbleToMassUpdateAttributesForBundleProductsTest.xml
new file mode 100644
index 0000000000000..fe55fda4d0e05
--- /dev/null
+++ b/app/code/Magento/Bundle/Test/Mftf/Test/AdminShouldBeAbleToMassUpdateAttributesForBundleProductsTest.xml
@@ -0,0 +1,76 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Bundle/Test/Mftf/Test/BundleProductWithTierPriceInCartTest.xml b/app/code/Magento/Bundle/Test/Mftf/Test/BundleProductWithTierPriceInCartTest.xml
new file mode 100644
index 0000000000000..46c6114637af6
--- /dev/null
+++ b/app/code/Magento/Bundle/Test/Mftf/Test/BundleProductWithTierPriceInCartTest.xml
@@ -0,0 +1,80 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontAddBundleOptionsToCartTest.xml b/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontAddBundleOptionsToCartTest.xml
index 779f1370c4da4..a1630128638d9 100644
--- a/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontAddBundleOptionsToCartTest.xml
+++ b/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontAddBundleOptionsToCartTest.xml
@@ -17,9 +17,6 @@
-
-
-
diff --git a/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontCustomerSearchBundleProductsByKeywordsTest.xml b/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontCustomerSearchBundleProductsByKeywordsTest.xml
new file mode 100644
index 0000000000000..d27cd0df88239
--- /dev/null
+++ b/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontCustomerSearchBundleProductsByKeywordsTest.xml
@@ -0,0 +1,137 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontSortBundleProductsByPriceTest.xml b/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontSortBundleProductsByPriceTest.xml
new file mode 100644
index 0000000000000..18316e41241e4
--- /dev/null
+++ b/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontSortBundleProductsByPriceTest.xml
@@ -0,0 +1,197 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 100.00
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 10.00
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 500.00
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontSpecialPriceBundleProductInCartTest.xml b/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontSpecialPriceBundleProductInCartTest.xml
new file mode 100644
index 0000000000000..44ac68a2759f3
--- /dev/null
+++ b/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontSpecialPriceBundleProductInCartTest.xml
@@ -0,0 +1,58 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Bundle/Ui/DataProvider/Product/Form/Modifier/BundlePanel.php b/app/code/Magento/Bundle/Ui/DataProvider/Product/Form/Modifier/BundlePanel.php
index ad6fc12712c17..18f970069c3c9 100644
--- a/app/code/Magento/Bundle/Ui/DataProvider/Product/Form/Modifier/BundlePanel.php
+++ b/app/code/Magento/Bundle/Ui/DataProvider/Product/Form/Modifier/BundlePanel.php
@@ -3,6 +3,8 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
+declare(strict_types=1);
+
namespace Magento\Bundle\Ui\DataProvider\Product\Form\Modifier;
use Magento\Bundle\Model\Product\Attribute\Source\Shipment\Type as ShipmentType;
@@ -16,6 +18,7 @@
use Magento\Ui\Component\Form;
use Magento\Ui\Component\Form\Fieldset;
use Magento\Ui\Component\Modal;
+use Magento\Store\Model\Store;
/**
* Create Ship Bundle Items and Affect Bundle Product Selections fields
@@ -814,6 +817,6 @@ protected function getSelectionPriceType()
*/
protected function isDefaultStore()
{
- return $this->locator->getProduct()->getStoreId() == 0;
+ return $this->locator->getProduct()->getStoreId() == Store::DEFAULT_STORE_ID;
}
}
diff --git a/app/code/Magento/Bundle/Ui/DataProvider/Product/Form/Modifier/BundlePrice.php b/app/code/Magento/Bundle/Ui/DataProvider/Product/Form/Modifier/BundlePrice.php
index f431012dc3fa5..92326bb1521b4 100644
--- a/app/code/Magento/Bundle/Ui/DataProvider/Product/Form/Modifier/BundlePrice.php
+++ b/app/code/Magento/Bundle/Ui/DataProvider/Product/Form/Modifier/BundlePrice.php
@@ -39,9 +39,9 @@ public function __construct(
$this->locator = $locator;
$this->arrayManager = $arrayManager;
}
-
+
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function modifyMeta(array $meta)
{
@@ -64,7 +64,7 @@ public function modifyMeta(array $meta)
$this->arrayManager->findPath(
ProductAttributeInterface::CODE_PRICE,
$meta,
- null,
+ self::DEFAULT_GENERAL_PANEL . '/children',
'children'
) . static::META_CONFIG_PATH,
$meta,
@@ -94,7 +94,7 @@ public function modifyMeta(array $meta)
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function modifyData(array $data)
{
diff --git a/app/code/Magento/Bundle/composer.json b/app/code/Magento/Bundle/composer.json
index 84c9a97698b71..f5d2832bf6c73 100644
--- a/app/code/Magento/Bundle/composer.json
+++ b/app/code/Magento/Bundle/composer.json
@@ -5,7 +5,7 @@
"sort-packages": true
},
"require": {
- "php": "~7.1.3||~7.2.0",
+ "php": "~7.1.3||~7.2.0||~7.3.0",
"magento/framework": "*",
"magento/module-backend": "*",
"magento/module-catalog": "*",
diff --git a/app/code/Magento/Bundle/etc/di.xml b/app/code/Magento/Bundle/etc/di.xml
index 72155d922a25f..d0e956efee694 100644
--- a/app/code/Magento/Bundle/etc/di.xml
+++ b/app/code/Magento/Bundle/etc/di.xml
@@ -123,9 +123,6 @@
-
-
-
diff --git a/app/code/Magento/Bundle/view/adminhtml/templates/sales/order/view/items/renderer.phtml b/app/code/Magento/Bundle/view/adminhtml/templates/sales/order/view/items/renderer.phtml
index 0a8b759441947..233e57a003397 100644
--- a/app/code/Magento/Bundle/view/adminhtml/templates/sales/order/view/items/renderer.phtml
+++ b/app/code/Magento/Bundle/view/adminhtml/templates/sales/order/view/items/renderer.phtml
@@ -154,7 +154,7 @@
canShowPriceInfo($_item)) : ?>
- = $block->escapeHtml($block->displayPriceAttribute('discount_amount')) ?>
+ = /* @noEscape */ $block->displayPriceAttribute('discount_amount') ?>
diff --git a/app/code/Magento/Bundle/view/frontend/templates/catalog/product/view/type/bundle/option/multi.phtml b/app/code/Magento/Bundle/view/frontend/templates/catalog/product/view/type/bundle/option/multi.phtml
index d6f9fdb74ef62..8cad6f094a3d8 100644
--- a/app/code/Magento/Bundle/view/frontend/templates/catalog/product/view/type/bundle/option/multi.phtml
+++ b/app/code/Magento/Bundle/view/frontend/templates/catalog/product/view/type/bundle/option/multi.phtml
@@ -16,7 +16,8 @@
= /* @noEscape */ $block->getSelectionQtyTitlePrice($_selections[0]) ?>
+ value="= $block->escapeHtmlAttr($_selections[0]->getSelectionId()) ?>"
+ class="bundle-option-= $block->escapeHtmlAttr($_option->getId()) ?> bundle option"/>
decorateArray($block->getOptions($stripSelection));
- = /* @noEscape */ __('Customize %1', $helper->productAttribute($product, $product->getName(), 'name')) ?>
+ = $block->escapeHtml(__('Customize %1', $helper->productAttribute($product, $product->getName(), 'name'))) ?>
= $block->getChildHtml('product_info_bundle_options_top') ?>
diff --git a/app/code/Magento/Bundle/view/frontend/web/js/float.js b/app/code/Magento/Bundle/view/frontend/web/js/float.js
index 8e78fd069cb3b..dd8b1443dcbad 100644
--- a/app/code/Magento/Bundle/view/frontend/web/js/float.js
+++ b/app/code/Magento/Bundle/view/frontend/web/js/float.js
@@ -9,7 +9,7 @@
*/
define([
'jquery',
- 'jquery/ui'
+ 'jquery-ui-modules/widget'
], function ($) {
'use strict';
diff --git a/app/code/Magento/Bundle/view/frontend/web/js/product-summary.js b/app/code/Magento/Bundle/view/frontend/web/js/product-summary.js
index 1e7fe6b6673d6..d5270569d5bbb 100644
--- a/app/code/Magento/Bundle/view/frontend/web/js/product-summary.js
+++ b/app/code/Magento/Bundle/view/frontend/web/js/product-summary.js
@@ -9,7 +9,7 @@
define([
'jquery',
'mage/template',
- 'jquery/ui',
+ 'jquery-ui-modules/widget',
'Magento_Bundle/js/price-bundle'
], function ($, mageTemplate) {
'use strict';
diff --git a/app/code/Magento/Bundle/view/frontend/web/js/slide.js b/app/code/Magento/Bundle/view/frontend/web/js/slide.js
index 99b01f340fb4d..5afe4ad8a9ea7 100644
--- a/app/code/Magento/Bundle/view/frontend/web/js/slide.js
+++ b/app/code/Magento/Bundle/view/frontend/web/js/slide.js
@@ -8,7 +8,7 @@
*/
define([
'jquery',
- 'jquery/ui'
+ 'jquery-ui-modules/widget'
], function ($) {
'use strict';
diff --git a/app/code/Magento/BundleGraphQl/Model/Resolver/Links/Collection.php b/app/code/Magento/BundleGraphQl/Model/Resolver/Links/Collection.php
index ee695c319501d..13bf10bc6aca7 100644
--- a/app/code/Magento/BundleGraphQl/Model/Resolver/Links/Collection.php
+++ b/app/code/Magento/BundleGraphQl/Model/Resolver/Links/Collection.php
@@ -117,7 +117,8 @@ private function fetch() : array
'price' => $link->getSelectionPriceValue(),
'position' => $link->getPosition(),
'id' => $link->getSelectionId(),
- 'qty' => (int)$link->getSelectionQty(),
+ 'qty' => (float)$link->getSelectionQty(),
+ 'quantity' => (float)$link->getSelectionQty(),
'is_default' => (bool)$link->getIsDefault(),
'price_type' => $this->enumLookup->getEnumValueFromField(
'PriceTypeEnum',
diff --git a/app/code/Magento/BundleGraphQl/composer.json b/app/code/Magento/BundleGraphQl/composer.json
index aea55cb5c644c..db85c2149ec18 100644
--- a/app/code/Magento/BundleGraphQl/composer.json
+++ b/app/code/Magento/BundleGraphQl/composer.json
@@ -3,7 +3,7 @@
"description": "N/A",
"type": "magento2-module",
"require": {
- "php": "~7.1.3||~7.2.0",
+ "php": "~7.1.3||~7.2.0||~7.3.0",
"magento/module-catalog": "*",
"magento/module-bundle": "*",
"magento/module-catalog-graph-ql": "*",
diff --git a/app/code/Magento/BundleGraphQl/etc/schema.graphqls b/app/code/Magento/BundleGraphQl/etc/schema.graphqls
index 26d2cbcd704fc..74e21d3feaba2 100644
--- a/app/code/Magento/BundleGraphQl/etc/schema.graphqls
+++ b/app/code/Magento/BundleGraphQl/etc/schema.graphqls
@@ -14,7 +14,8 @@ type BundleItem @doc(description: "BundleItem defines an individual item in a bu
type BundleItemOption @doc(description: "BundleItemOption defines characteristics and options for a specific bundle item.") {
id: Int @doc(description: "The ID assigned to the bundled item option.")
label: String @doc(description: "The text that identifies the bundled item option.") @resolver(class: "Magento\\BundleGraphQl\\Model\\Resolver\\Options\\Label")
- qty: Float @doc(description: "Indicates the quantity of this specific bundle item.")
+ qty: Float @deprecated(reason: "The `qty` is deprecated. Use `quantity` instead.") @doc(description: "Indicates the quantity of this specific bundle item.")
+ quantity: Float @doc(description: "Indicates the quantity of this specific bundle item.")
position: Int @doc(description: "When a bundle item contains multiple options, the relative position of this option compared to the other options.")
is_default: Boolean @doc(description: "Indicates whether this option is the default option.")
price: Float @doc(description: "The price of the selected option.")
diff --git a/app/code/Magento/BundleImportExport/composer.json b/app/code/Magento/BundleImportExport/composer.json
index 42d9dc558d31e..f7c8ce15a53d7 100644
--- a/app/code/Magento/BundleImportExport/composer.json
+++ b/app/code/Magento/BundleImportExport/composer.json
@@ -5,7 +5,7 @@
"sort-packages": true
},
"require": {
- "php": "~7.1.3||~7.2.0",
+ "php": "~7.1.3||~7.2.0||~7.3.0",
"magento/framework": "*",
"magento/module-bundle": "*",
"magento/module-store": "*",
diff --git a/app/code/Magento/CacheInvalidate/composer.json b/app/code/Magento/CacheInvalidate/composer.json
index 01ac4d30d844d..18e3f52ffcb88 100644
--- a/app/code/Magento/CacheInvalidate/composer.json
+++ b/app/code/Magento/CacheInvalidate/composer.json
@@ -5,7 +5,7 @@
"sort-packages": true
},
"require": {
- "php": "~7.1.3||~7.2.0",
+ "php": "~7.1.3||~7.2.0||~7.3.0",
"magento/framework": "*",
"magento/module-page-cache": "*"
},
diff --git a/app/code/Magento/Captcha/CustomerData/Captcha.php b/app/code/Magento/Captcha/CustomerData/Captcha.php
index a744daacdc673..e07bf953abaa3 100644
--- a/app/code/Magento/Captcha/CustomerData/Captcha.php
+++ b/app/code/Magento/Captcha/CustomerData/Captcha.php
@@ -9,11 +9,18 @@
namespace Magento\Captcha\CustomerData;
use Magento\Customer\CustomerData\SectionSourceInterface;
+use Magento\Customer\Model\Session as CustomerSession;
+use Magento\Captcha\Model\DefaultModel;
+use Magento\Captcha\Helper\Data as CaptchaHelper;
+use Magento\Framework\App\ObjectManager;
+use Magento\Framework\DataObject;
/**
- * Captcha section
+ * Captcha section.
+ *
+ * @SuppressWarnings(PHPMD.CookieAndSessionMisuse)
*/
-class Captcha extends \Magento\Framework\DataObject implements SectionSourceInterface
+class Captcha extends DataObject implements SectionSourceInterface
{
/**
* @var array
@@ -21,24 +28,31 @@ class Captcha extends \Magento\Framework\DataObject implements SectionSourceInte
private $formIds;
/**
- * @var \Magento\Captcha\Helper\Data
+ * @var CaptchaHelper
*/
private $helper;
/**
- * @param \Magento\Captcha\Helper\Data $helper
+ * @var CustomerSession
+ */
+ private $customerSession;
+
+ /**
+ * @param CaptchaHelper $helper
* @param array $formIds
* @param array $data
- * @codeCoverageIgnore
+ * @param CustomerSession|null $customerSession
*/
public function __construct(
- \Magento\Captcha\Helper\Data $helper,
+ CaptchaHelper $helper,
array $formIds,
- array $data = []
+ array $data = [],
+ ?CustomerSession $customerSession = null
) {
- parent::__construct($data);
$this->helper = $helper;
$this->formIds = $formIds;
+ parent::__construct($data);
+ $this->customerSession = $customerSession ?? ObjectManager::getInstance()->get(CustomerSession::class);
}
/**
@@ -49,9 +63,15 @@ public function getSectionData() :array
$data = [];
foreach ($this->formIds as $formId) {
+ /** @var DefaultModel $captchaModel */
$captchaModel = $this->helper->getCaptcha($formId);
+ $login = '';
+ if ($this->customerSession->isLoggedIn()) {
+ $login = $this->customerSession->getCustomerData()->getEmail();
+ }
+ $required = $captchaModel->isRequired($login);
$data[$formId] = [
- 'isRequired' => $captchaModel->isRequired(),
+ 'isRequired' => $required,
'timestamp' => time()
];
}
diff --git a/app/code/Magento/Captcha/Model/DefaultModel.php b/app/code/Magento/Captcha/Model/DefaultModel.php
index 483f9c3fb4d20..bbbbfb0a36e08 100644
--- a/app/code/Magento/Captcha/Model/DefaultModel.php
+++ b/app/code/Magento/Captcha/Model/DefaultModel.php
@@ -3,13 +3,18 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
+declare(strict_types=1);
+
namespace Magento\Captcha\Model;
use Magento\Captcha\Helper\Data;
+use Magento\Framework\Math\Random;
/**
* Implementation of \Zend\Captcha\Image
*
+ * @SuppressWarnings(PHPMD.CookieAndSessionMisuse)
+ *
* @api
* @since 100.0.2
*/
@@ -83,24 +88,32 @@ class DefaultModel extends \Zend\Captcha\Image implements \Magento\Captcha\Model
*/
private $words;
+ /**
+ * @var Random
+ */
+ private $randomMath;
+
/**
* @param \Magento\Framework\Session\SessionManagerInterface $session
* @param \Magento\Captcha\Helper\Data $captchaData
* @param ResourceModel\LogFactory $resLogFactory
* @param string $formId
+ * @param Random $randomMath
* @throws \Zend\Captcha\Exception\ExtensionNotLoadedException
*/
public function __construct(
\Magento\Framework\Session\SessionManagerInterface $session,
\Magento\Captcha\Helper\Data $captchaData,
\Magento\Captcha\Model\ResourceModel\LogFactory $resLogFactory,
- $formId
+ $formId,
+ Random $randomMath = null
) {
parent::__construct();
$this->session = $session;
$this->captchaData = $captchaData;
$this->resLogFactory = $resLogFactory;
$this->formId = $formId;
+ $this->randomMath = $randomMath ?? \Magento\Framework\App\ObjectManager::getInstance()->get(Random::class);
}
/**
@@ -382,23 +395,9 @@ public function setShowCaptchaInSession($value = true)
*/
protected function generateWord()
{
- $word = '';
- $symbols = $this->getSymbols();
+ $symbols = (string)$this->captchaData->getConfig('symbols');
$wordLen = $this->getWordLen();
- for ($i = 0; $i < $wordLen; $i++) {
- $word .= $symbols[array_rand($symbols)];
- }
- return $word;
- }
-
- /**
- * Get symbols array to use for word generation
- *
- * @return array
- */
- private function getSymbols()
- {
- return str_split((string)$this->captchaData->getConfig('symbols'));
+ return $this->randomMath->getRandomString($wordLen, $symbols);
}
/**
@@ -508,7 +507,7 @@ private function getWords()
/**
* Set captcha word
*
- * @param string $word
+ * @param string $word
* @return $this
* @since 100.2.0
*/
@@ -562,7 +561,7 @@ protected function randomSize()
*/
protected function gc()
{
- //do nothing
+ return; // required for static testing to pass
}
/**
diff --git a/app/code/Magento/Captcha/Observer/CaptchaStringResolver.php b/app/code/Magento/Captcha/Observer/CaptchaStringResolver.php
index 39579616fa928..d83abc7a6c7d1 100644
--- a/app/code/Magento/Captcha/Observer/CaptchaStringResolver.php
+++ b/app/code/Magento/Captcha/Observer/CaptchaStringResolver.php
@@ -5,19 +5,31 @@
*/
namespace Magento\Captcha\Observer;
+use Magento\Framework\App\RequestInterface;
+use Magento\Framework\App\Request\Http as HttpRequest;
+
+/**
+ * Extract given captcha word.
+ */
class CaptchaStringResolver
{
/**
* Get Captcha String
*
- * @param \Magento\Framework\App\RequestInterface $request
+ * @param \Magento\Framework\App\RequestInterface|HttpRequest $request
* @param string $formId
* @return string
*/
- public function resolve(\Magento\Framework\App\RequestInterface $request, $formId)
+ public function resolve(RequestInterface $request, $formId)
{
$captchaParams = $request->getPost(\Magento\Captcha\Helper\Data::INPUT_NAME_FIELD_VALUE);
+ if (!empty($captchaParams) && !empty($captchaParams[$formId])) {
+ $value = $captchaParams[$formId];
+ } else {
+ //For Web APIs
+ $value = $request->getHeader('X-Captcha');
+ }
- return $captchaParams[$formId] ?? '';
+ return $value;
}
}
diff --git a/app/code/Magento/Captcha/Observer/CheckContactUsFormObserver.php b/app/code/Magento/Captcha/Observer/CheckContactUsFormObserver.php
index 91737c1a3d779..8c1da0e1ef104 100644
--- a/app/code/Magento/Captcha/Observer/CheckContactUsFormObserver.php
+++ b/app/code/Magento/Captcha/Observer/CheckContactUsFormObserver.php
@@ -9,6 +9,9 @@
use Magento\Framework\App\Request\DataPersistorInterface;
use Magento\Framework\App\ObjectManager;
+/**
+ * Class CheckContactUsFormObserver
+ */
class CheckContactUsFormObserver implements ObserverInterface
{
/**
@@ -76,7 +79,7 @@ public function execute(\Magento\Framework\Event\Observer $observer)
/** @var \Magento\Framework\App\Action\Action $controller */
$controller = $observer->getControllerAction();
if (!$captcha->isCorrect($this->captchaStringResolver->resolve($controller->getRequest(), $formId))) {
- $this->messageManager->addError(__('Incorrect CAPTCHA.'));
+ $this->messageManager->addErrorMessage(__('Incorrect CAPTCHA.'));
$this->getDataPersistor()->set($formId, $controller->getRequest()->getPostValue());
$this->_actionFlag->set('', \Magento\Framework\App\Action\Action::FLAG_NO_DISPATCH, true);
$this->redirect->redirect($controller->getResponse(), 'contact/index/index');
diff --git a/app/code/Magento/Captcha/Observer/CheckForgotpasswordObserver.php b/app/code/Magento/Captcha/Observer/CheckForgotpasswordObserver.php
index 0736c7514a568..623d11903926e 100644
--- a/app/code/Magento/Captcha/Observer/CheckForgotpasswordObserver.php
+++ b/app/code/Magento/Captcha/Observer/CheckForgotpasswordObserver.php
@@ -7,6 +7,9 @@
use Magento\Framework\Event\ObserverInterface;
+/**
+ * Class CheckForgotpasswordObserver
+ */
class CheckForgotpasswordObserver implements ObserverInterface
{
/**
@@ -69,7 +72,7 @@ public function execute(\Magento\Framework\Event\Observer $observer)
/** @var \Magento\Framework\App\Action\Action $controller */
$controller = $observer->getControllerAction();
if (!$captchaModel->isCorrect($this->captchaStringResolver->resolve($controller->getRequest(), $formId))) {
- $this->messageManager->addError(__('Incorrect CAPTCHA'));
+ $this->messageManager->addErrorMessage(__('Incorrect CAPTCHA'));
$this->_actionFlag->set('', \Magento\Framework\App\Action\Action::FLAG_NO_DISPATCH, true);
$this->redirect->redirect($controller->getResponse(), '*/*/forgotpassword');
}
diff --git a/app/code/Magento/Captcha/Observer/CheckUserCreateObserver.php b/app/code/Magento/Captcha/Observer/CheckUserCreateObserver.php
index 6d2ed4d1050ca..ef66116432f55 100644
--- a/app/code/Magento/Captcha/Observer/CheckUserCreateObserver.php
+++ b/app/code/Magento/Captcha/Observer/CheckUserCreateObserver.php
@@ -7,6 +7,11 @@
use Magento\Framework\Event\ObserverInterface;
+/**
+ * Class CheckUserCreateObserver
+ *
+ * @SuppressWarnings(PHPMD.CookieAndSessionMisuse)
+ */
class CheckUserCreateObserver implements ObserverInterface
{
/**
@@ -86,7 +91,7 @@ public function execute(\Magento\Framework\Event\Observer $observer)
/** @var \Magento\Framework\App\Action\Action $controller */
$controller = $observer->getControllerAction();
if (!$captchaModel->isCorrect($this->captchaStringResolver->resolve($controller->getRequest(), $formId))) {
- $this->messageManager->addError(__('Incorrect CAPTCHA'));
+ $this->messageManager->addErrorMessage(__('Incorrect CAPTCHA'));
$this->_actionFlag->set('', \Magento\Framework\App\Action\Action::FLAG_NO_DISPATCH, true);
$this->_session->setCustomerFormData($controller->getRequest()->getPostValue());
$url = $this->_urlManager->getUrl('*/*/create', ['_nosecret' => true]);
diff --git a/app/code/Magento/Captcha/Observer/CheckUserEditObserver.php b/app/code/Magento/Captcha/Observer/CheckUserEditObserver.php
index 9d3cd8d367093..872bbec4ffa56 100644
--- a/app/code/Magento/Captcha/Observer/CheckUserEditObserver.php
+++ b/app/code/Magento/Captcha/Observer/CheckUserEditObserver.php
@@ -11,13 +11,12 @@
use Magento\Framework\App\Config\ScopeConfigInterface;
/**
- * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
+ * Class CheckUserEditObserver
+ *
+ * @SuppressWarnings(PHPMD.CookieAndSessionMisuse)
*/
class CheckUserEditObserver implements ObserverInterface
{
- /**
- * Form ID
- */
const FORM_ID = 'user_edit';
/**
@@ -96,7 +95,8 @@ public function __construct(
* Check Captcha On Forgot Password Page
*
* @param \Magento\Framework\Event\Observer $observer
- * @return $this
+ * @return $this|void
+ * @throws \Magento\Framework\Exception\SessionException
*/
public function execute(\Magento\Framework\Event\Observer $observer)
{
@@ -119,9 +119,9 @@ public function execute(\Magento\Framework\Event\Observer $observer)
'The account is locked. Please wait and try again or contact %1.',
$this->scopeConfig->getValue('contact/email/recipient_email')
);
- $this->messageManager->addError($message);
+ $this->messageManager->addErrorMessage($message);
}
- $this->messageManager->addError(__('Incorrect CAPTCHA'));
+ $this->messageManager->addErrorMessage(__('Incorrect CAPTCHA'));
$this->actionFlag->set('', \Magento\Framework\App\Action\Action::FLAG_NO_DISPATCH, true);
$this->redirect->redirect($controller->getResponse(), '*/*/edit');
}
diff --git a/app/code/Magento/Captcha/Observer/CheckUserForgotPasswordBackendObserver.php b/app/code/Magento/Captcha/Observer/CheckUserForgotPasswordBackendObserver.php
index 2de93dcf6b59b..e11e48a527169 100644
--- a/app/code/Magento/Captcha/Observer/CheckUserForgotPasswordBackendObserver.php
+++ b/app/code/Magento/Captcha/Observer/CheckUserForgotPasswordBackendObserver.php
@@ -7,6 +7,11 @@
use Magento\Framework\Event\ObserverInterface;
+/**
+ * Class CheckUserForgotPasswordBackendObserver
+ *
+ * @SuppressWarnings(PHPMD.CookieAndSessionMisuse)
+ */
class CheckUserForgotPasswordBackendObserver implements ObserverInterface
{
/**
@@ -76,7 +81,7 @@ public function execute(\Magento\Framework\Event\Observer $observer)
) {
$this->_session->setEmail((string)$controller->getRequest()->getPost('email'));
$this->_actionFlag->set('', \Magento\Framework\App\Action\Action::FLAG_NO_DISPATCH, true);
- $this->messageManager->addError(__('Incorrect CAPTCHA'));
+ $this->messageManager->addErrorMessage(__('Incorrect CAPTCHA'));
$controller->getResponse()->setRedirect(
$controller->getUrl('*/*/forgotpassword', ['_nosecret' => true])
);
diff --git a/app/code/Magento/Captcha/Observer/CheckUserLoginObserver.php b/app/code/Magento/Captcha/Observer/CheckUserLoginObserver.php
index dd4974c5d842c..27507423e77eb 100644
--- a/app/code/Magento/Captcha/Observer/CheckUserLoginObserver.php
+++ b/app/code/Magento/Captcha/Observer/CheckUserLoginObserver.php
@@ -6,10 +6,10 @@
namespace Magento\Captcha\Observer;
+use Magento\Customer\Api\CustomerRepositoryInterface;
use Magento\Customer\Model\AuthenticationInterface;
use Magento\Framework\Event\ObserverInterface;
use Magento\Framework\Exception\NoSuchEntityException;
-use Magento\Customer\Api\CustomerRepositoryInterface;
/**
* Check captcha on user login page observer.
@@ -64,6 +64,8 @@ class CheckUserLoginObserver implements ObserverInterface
protected $authentication;
/**
+ * CheckUserLoginObserver constructor.
+ *
* @param \Magento\Captcha\Helper\Data $helper
* @param \Magento\Framework\App\ActionFlag $actionFlag
* @param \Magento\Framework\Message\ManagerInterface $messageManager
@@ -125,8 +127,7 @@ private function getAuthentication()
* Check captcha on user login page
*
* @param \Magento\Framework\Event\Observer $observer
- * @throws NoSuchEntityException
- * @return $this
+ * @return $this|void
*/
public function execute(\Magento\Framework\Event\Observer $observer)
{
@@ -143,10 +144,11 @@ public function execute(\Magento\Framework\Event\Observer $observer)
try {
$customer = $this->getCustomerRepository()->get($login);
$this->getAuthentication()->processAuthenticationFailure($customer->getId());
+ // phpcs:ignore Magento2.CodeAnalysis.EmptyBlock
} catch (NoSuchEntityException $e) {
//do nothing as customer existence is validated later in authenticate method
}
- $this->messageManager->addError(__('Incorrect CAPTCHA'));
+ $this->messageManager->addErrorMessage(__('Incorrect CAPTCHA'));
$this->_actionFlag->set('', \Magento\Framework\App\Action\Action::FLAG_NO_DISPATCH, true);
$this->_session->setUsername($login);
$beforeUrl = $this->_session->getBeforeAuthUrl();
diff --git a/app/code/Magento/Captcha/Test/Mftf/ActionGroup/AdminLoginWithCaptchaActionGroup.xml b/app/code/Magento/Captcha/Test/Mftf/ActionGroup/AdminLoginWithCaptchaActionGroup.xml
index 07329e2659876..b84977ba4fcd8 100644
--- a/app/code/Magento/Captcha/Test/Mftf/ActionGroup/AdminLoginWithCaptchaActionGroup.xml
+++ b/app/code/Magento/Captcha/Test/Mftf/ActionGroup/AdminLoginWithCaptchaActionGroup.xml
@@ -9,9 +9,13 @@
+
+ EXTENDS: LoginAsAdmin. Fills in the Captcha field on the Backend Admin Login page.
+
-
+
-
+
+
diff --git a/app/code/Magento/Captcha/Test/Mftf/ActionGroup/AssertCaptchaNotVisibleOnCustomerLoginFormActionGroup.xml b/app/code/Magento/Captcha/Test/Mftf/ActionGroup/AssertCaptchaNotVisibleOnCustomerLoginFormActionGroup.xml
index a371f177e3552..c33bcb48c5ce7 100644
--- a/app/code/Magento/Captcha/Test/Mftf/ActionGroup/AssertCaptchaNotVisibleOnCustomerLoginFormActionGroup.xml
+++ b/app/code/Magento/Captcha/Test/Mftf/ActionGroup/AssertCaptchaNotVisibleOnCustomerLoginFormActionGroup.xml
@@ -9,12 +9,16 @@
-
+
+ Validate that the Captcha is NOT present on the Backend Admin Login page.
+
+
+
-
+
diff --git a/app/code/Magento/Captcha/Test/Mftf/ActionGroup/AssertCaptchaVisibleOnAdminLoginFormActionGroup.xml b/app/code/Magento/Captcha/Test/Mftf/ActionGroup/AssertCaptchaVisibleOnAdminLoginFormActionGroup.xml
index aa02588000d2b..135b687e4d438 100644
--- a/app/code/Magento/Captcha/Test/Mftf/ActionGroup/AssertCaptchaVisibleOnAdminLoginFormActionGroup.xml
+++ b/app/code/Magento/Captcha/Test/Mftf/ActionGroup/AssertCaptchaVisibleOnAdminLoginFormActionGroup.xml
@@ -9,11 +9,15 @@
+
+ Validate that the Captcha IS present on the Backend Admin Login page.
+
+
-
+
diff --git a/app/code/Magento/Captcha/Test/Mftf/ActionGroup/AssertCaptchaVisibleOnContactUsFormActionGroup.xml b/app/code/Magento/Captcha/Test/Mftf/ActionGroup/AssertCaptchaVisibleOnContactUsFormActionGroup.xml
index d800c65cabb60..6f6108333ab5e 100644
--- a/app/code/Magento/Captcha/Test/Mftf/ActionGroup/AssertCaptchaVisibleOnContactUsFormActionGroup.xml
+++ b/app/code/Magento/Captcha/Test/Mftf/ActionGroup/AssertCaptchaVisibleOnContactUsFormActionGroup.xml
@@ -9,11 +9,15 @@
+
+ Validate that the Captcha IS present on the Storefront Contact Us page.
+
+
-
+
diff --git a/app/code/Magento/Captcha/Test/Mftf/ActionGroup/AssertCaptchaVisibleOnCustomerAccountCreatePageActionGroup.xml b/app/code/Magento/Captcha/Test/Mftf/ActionGroup/AssertCaptchaVisibleOnCustomerAccountCreatePageActionGroup.xml
index 6c09d1d49381f..bf2f5c2908320 100644
--- a/app/code/Magento/Captcha/Test/Mftf/ActionGroup/AssertCaptchaVisibleOnCustomerAccountCreatePageActionGroup.xml
+++ b/app/code/Magento/Captcha/Test/Mftf/ActionGroup/AssertCaptchaVisibleOnCustomerAccountCreatePageActionGroup.xml
@@ -9,6 +9,10 @@
+
+ Validate that the Captcha IS present on the Storefront Create Customer page.
+
+
diff --git a/app/code/Magento/Captcha/Test/Mftf/ActionGroup/AssertCaptchaVisibleOnCustomerAccountInfoActionGroup.xml b/app/code/Magento/Captcha/Test/Mftf/ActionGroup/AssertCaptchaVisibleOnCustomerAccountInfoActionGroup.xml
index c68ffbfb5be4b..c2af71c46af4b 100644
--- a/app/code/Magento/Captcha/Test/Mftf/ActionGroup/AssertCaptchaVisibleOnCustomerAccountInfoActionGroup.xml
+++ b/app/code/Magento/Captcha/Test/Mftf/ActionGroup/AssertCaptchaVisibleOnCustomerAccountInfoActionGroup.xml
@@ -9,13 +9,17 @@
-
+
+ Validate that the Captcha IS present on the Storefront Customer Account Information page.
+
+
+
-
-
+
+
diff --git a/app/code/Magento/Captcha/Test/Mftf/ActionGroup/AssertCaptchaVisibleOnCustomerLoginFormActionGroup.xml b/app/code/Magento/Captcha/Test/Mftf/ActionGroup/AssertCaptchaVisibleOnCustomerLoginFormActionGroup.xml
index 5616b099c026d..a53f8f71e94c4 100644
--- a/app/code/Magento/Captcha/Test/Mftf/ActionGroup/AssertCaptchaVisibleOnCustomerLoginFormActionGroup.xml
+++ b/app/code/Magento/Captcha/Test/Mftf/ActionGroup/AssertCaptchaVisibleOnCustomerLoginFormActionGroup.xml
@@ -9,11 +9,15 @@
+
+ Validate that the Captcha IS present on the Storefront Customer Login page.
+
+
-
+
diff --git a/app/code/Magento/Captcha/Test/Mftf/ActionGroup/CaptchaFormsDisplayingActionGroup.xml b/app/code/Magento/Captcha/Test/Mftf/ActionGroup/CaptchaFormsDisplayingActionGroup.xml
index f3b6eb1d9af84..52c418153b4ef 100644
--- a/app/code/Magento/Captcha/Test/Mftf/ActionGroup/CaptchaFormsDisplayingActionGroup.xml
+++ b/app/code/Magento/Captcha/Test/Mftf/ActionGroup/CaptchaFormsDisplayingActionGroup.xml
@@ -9,6 +9,10 @@
+
+ Navigates to store configuration page through UI and expands the CAPTCHA section under Customers > Customer Configuration.
+
+
diff --git a/app/code/Magento/Captcha/Test/Mftf/ActionGroup/StorefrontCustomerChangeEmailWithCaptchaActionGroup.xml b/app/code/Magento/Captcha/Test/Mftf/ActionGroup/StorefrontCustomerChangeEmailWithCaptchaActionGroup.xml
index 8aff3d5482f2c..662fdf506d4f9 100644
--- a/app/code/Magento/Captcha/Test/Mftf/ActionGroup/StorefrontCustomerChangeEmailWithCaptchaActionGroup.xml
+++ b/app/code/Magento/Captcha/Test/Mftf/ActionGroup/StorefrontCustomerChangeEmailWithCaptchaActionGroup.xml
@@ -9,10 +9,13 @@
+
+ EXTENDS: StorefrontCustomerChangeEmailActionGroup. Fills in the Captcha field on the Storefront Customer Information page.
+
-
+
-
+
diff --git a/app/code/Magento/Captcha/Test/Mftf/ActionGroup/StorefrontFillContactUsFormWithCaptchaActionGroup.xml b/app/code/Magento/Captcha/Test/Mftf/ActionGroup/StorefrontFillContactUsFormWithCaptchaActionGroup.xml
index 3546fa2e57a33..70de13b9c9b05 100644
--- a/app/code/Magento/Captcha/Test/Mftf/ActionGroup/StorefrontFillContactUsFormWithCaptchaActionGroup.xml
+++ b/app/code/Magento/Captcha/Test/Mftf/ActionGroup/StorefrontFillContactUsFormWithCaptchaActionGroup.xml
@@ -9,9 +9,13 @@
+
+ EXTENDS: StorefrontFillContactUsFormActionGroup. Fills in the Captcha field on the Storefront Contact Us page.
+
-
+
-
+
+
diff --git a/app/code/Magento/Captcha/Test/Mftf/ActionGroup/StorefrontFillCustomerAccountCreationFormWithCaptchaActionGroup.xml b/app/code/Magento/Captcha/Test/Mftf/ActionGroup/StorefrontFillCustomerAccountCreationFormWithCaptchaActionGroup.xml
index d67ebc1a00768..6f45f1b2e3a1b 100644
--- a/app/code/Magento/Captcha/Test/Mftf/ActionGroup/StorefrontFillCustomerAccountCreationFormWithCaptchaActionGroup.xml
+++ b/app/code/Magento/Captcha/Test/Mftf/ActionGroup/StorefrontFillCustomerAccountCreationFormWithCaptchaActionGroup.xml
@@ -9,9 +9,13 @@
+
+ EXTENDS: StorefrontFillCustomerAccountCreationFormActionGroup. Fills in the Captcha field on the Storefront Create Customer page.
+
-
+
-
+
+
diff --git a/app/code/Magento/Captcha/Test/Mftf/ActionGroup/StorefrontFillCustomerLoginFormWithCaptchaActionGroup.xml b/app/code/Magento/Captcha/Test/Mftf/ActionGroup/StorefrontFillCustomerLoginFormWithCaptchaActionGroup.xml
index 5ad727a8fe99d..ace3132210eff 100644
--- a/app/code/Magento/Captcha/Test/Mftf/ActionGroup/StorefrontFillCustomerLoginFormWithCaptchaActionGroup.xml
+++ b/app/code/Magento/Captcha/Test/Mftf/ActionGroup/StorefrontFillCustomerLoginFormWithCaptchaActionGroup.xml
@@ -9,9 +9,13 @@
+
+ EXTENDS: StorefrontFillCustomerLoginFormActionGroup. Fills in the Captcha field on the Storefront Customer Login page.
+
-
+
-
+
+
diff --git a/app/code/Magento/Captcha/Test/Unit/Model/DefaultTest.php b/app/code/Magento/Captcha/Test/Unit/Model/DefaultTest.php
index eef75d2c01ec7..b569803078457 100644
--- a/app/code/Magento/Captcha/Test/Unit/Model/DefaultTest.php
+++ b/app/code/Magento/Captcha/Test/Unit/Model/DefaultTest.php
@@ -3,8 +3,12 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
+declare(strict_types=1);
+
namespace Magento\Captcha\Test\Unit\Model;
+use Magento\Framework\Math\Random;
+
/**
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
*/
@@ -253,13 +257,15 @@ protected function _getSessionStub()
->getMock();
$session->expects($this->any())->method('isLoggedIn')->will($this->returnValue(false));
- $session->setData([
- 'user_create_word' => [
- 'data' => 'AbCdEf5',
- 'words' => 'AbCdEf5',
- 'expires' => time() + self::EXPIRE_FRAME
+ $session->setData(
+ [
+ 'user_create_word' => [
+ 'data' => 'AbCdEf5',
+ 'words' => 'AbCdEf5',
+ 'expires' => time() + self::EXPIRE_FRAME
+ ]
]
- ]);
+ );
return $session;
}
@@ -375,4 +381,38 @@ public function isShownToLoggedInUserDataProvider()
[false, 'user_forgotpassword']
];
}
+
+ /**
+ * @param string $string
+ * @dataProvider generateWordProvider
+ * @throws \ReflectionException
+ */
+ public function testGenerateWord($string)
+ {
+ $randomMock = $this->createMock(Random::class);
+ $randomMock->expects($this->once())
+ ->method('getRandomString')
+ ->will($this->returnValue($string));
+ $captcha = new \Magento\Captcha\Model\DefaultModel(
+ $this->session,
+ $this->_getHelperStub(),
+ $this->_resLogFactory,
+ 'user_create',
+ $randomMock
+ );
+ $method = new \ReflectionMethod($captcha, 'generateWord');
+ $method->setAccessible(true);
+ $this->assertEquals($string, $method->invoke($captcha));
+ }
+ /**
+ * @return array
+ */
+ public function generateWordProvider()
+ {
+ return [
+ ['ABC123'],
+ ['1234567890'],
+ ['The quick brown fox jumps over the lazy dog.']
+ ];
+ }
}
diff --git a/app/code/Magento/Captcha/Test/Unit/Observer/CheckContactUsFormObserverTest.php b/app/code/Magento/Captcha/Test/Unit/Observer/CheckContactUsFormObserverTest.php
index 08f76aa74ac6d..83bfb2910f9f8 100644
--- a/app/code/Magento/Captcha/Test/Unit/Observer/CheckContactUsFormObserverTest.php
+++ b/app/code/Magento/Captcha/Test/Unit/Observer/CheckContactUsFormObserverTest.php
@@ -69,7 +69,10 @@ protected function setUp()
$this->messageManagerMock = $this->createMock(\Magento\Framework\Message\ManagerInterface::class);
$this->redirectMock = $this->createMock(\Magento\Framework\App\Response\RedirectInterface::class);
$this->captchaStringResolverMock = $this->createMock(\Magento\Captcha\Observer\CaptchaStringResolver::class);
- $this->sessionMock = $this->createPartialMock(\Magento\Framework\Session\SessionManager::class, ['addError']);
+ $this->sessionMock = $this->createPartialMock(
+ \Magento\Framework\Session\SessionManager::class,
+ ['addErrorMessage']
+ );
$this->dataPersistorMock = $this->getMockBuilder(\Magento\Framework\App\Request\DataPersistorInterface::class)
->getMockForAbstractClass();
@@ -116,7 +119,7 @@ public function testCheckContactUsFormWhenCaptchaIsRequiredAndValid()
$this->helperMock->expects($this->any())
->method('getCaptcha')
->with($formId)->willReturn($this->captchaMock);
- $this->sessionMock->expects($this->never())->method('addError');
+ $this->sessionMock->expects($this->never())->method('addErrorMessage');
$this->checkContactUsFormObserver->execute(
new \Magento\Framework\Event\Observer(['controller_action' => $controller])
@@ -163,7 +166,7 @@ public function testCheckContactUsFormRedirectsCustomerWithWarningMessageWhenCap
->method('getCaptcha')
->with($formId)
->willReturn($this->captchaMock);
- $this->messageManagerMock->expects($this->once())->method('addError')->with($warningMessage);
+ $this->messageManagerMock->expects($this->once())->method('addErrorMessage')->with($warningMessage);
$this->actionFlagMock->expects($this->once())
->method('set')
->with('', \Magento\Framework\App\Action\Action::FLAG_NO_DISPATCH, true);
diff --git a/app/code/Magento/Captcha/Test/Unit/Observer/CheckForgotpasswordObserverTest.php b/app/code/Magento/Captcha/Test/Unit/Observer/CheckForgotpasswordObserverTest.php
index b05a3b2e34af0..93b58191cc334 100644
--- a/app/code/Magento/Captcha/Test/Unit/Observer/CheckForgotpasswordObserverTest.php
+++ b/app/code/Magento/Captcha/Test/Unit/Observer/CheckForgotpasswordObserverTest.php
@@ -138,7 +138,7 @@ public function testCheckForgotpasswordRedirects()
)->will(
$this->returnValue($this->_captcha)
);
- $this->_messageManager->expects($this->once())->method('addError')->with($warningMessage);
+ $this->_messageManager->expects($this->once())->method('addErrorMessage')->with($warningMessage);
$this->_actionFlag->expects(
$this->once()
)->method(
diff --git a/app/code/Magento/Captcha/Test/Unit/Observer/CheckUserCreateObserverTest.php b/app/code/Magento/Captcha/Test/Unit/Observer/CheckUserCreateObserverTest.php
index 8dc67437f4879..a57faabda99eb 100644
--- a/app/code/Magento/Captcha/Test/Unit/Observer/CheckUserCreateObserverTest.php
+++ b/app/code/Magento/Captcha/Test/Unit/Observer/CheckUserCreateObserverTest.php
@@ -151,7 +151,7 @@ public function testCheckUserCreateRedirectsError()
)->will(
$this->returnValue($this->_captcha)
);
- $this->_messageManager->expects($this->once())->method('addError')->with($warningMessage);
+ $this->_messageManager->expects($this->once())->method('addErrorMessage')->with($warningMessage);
$this->_actionFlag->expects(
$this->once()
)->method(
diff --git a/app/code/Magento/Captcha/Test/Unit/Observer/CheckUserEditObserverTest.php b/app/code/Magento/Captcha/Test/Unit/Observer/CheckUserEditObserverTest.php
index 26fd8fd928c56..0f08e5c569dfc 100644
--- a/app/code/Magento/Captcha/Test/Unit/Observer/CheckUserEditObserverTest.php
+++ b/app/code/Magento/Captcha/Test/Unit/Observer/CheckUserEditObserverTest.php
@@ -146,7 +146,7 @@ public function testExecute()
$message = __('The account is locked. Please wait and try again or contact %1.', $email);
$this->messageManagerMock->expects($this->exactly(2))
- ->method('addError')
+ ->method('addErrorMessage')
->withConsecutive([$message], [__('Incorrect CAPTCHA')]);
$this->actionFlagMock->expects($this->once())
diff --git a/app/code/Magento/Captcha/Test/Unit/Observer/CheckUserLoginObserverTest.php b/app/code/Magento/Captcha/Test/Unit/Observer/CheckUserLoginObserverTest.php
index 19dc096b9ef66..0499ec3255c51 100644
--- a/app/code/Magento/Captcha/Test/Unit/Observer/CheckUserLoginObserverTest.php
+++ b/app/code/Magento/Captcha/Test/Unit/Observer/CheckUserLoginObserverTest.php
@@ -145,7 +145,7 @@ public function testExecute()
->with($customerId);
$this->messageManagerMock->expects($this->once())
- ->method('addError')
+ ->method('addErrorMessage')
->with(__('Incorrect CAPTCHA'));
$this->actionFlagMock->expects($this->once())
diff --git a/app/code/Magento/Captcha/composer.json b/app/code/Magento/Captcha/composer.json
index 62f586ba82ae0..294961118e93a 100644
--- a/app/code/Magento/Captcha/composer.json
+++ b/app/code/Magento/Captcha/composer.json
@@ -5,7 +5,7 @@
"sort-packages": true
},
"require": {
- "php": "~7.1.3||~7.2.0",
+ "php": "~7.1.3||~7.2.0||~7.3.0",
"magento/framework": "*",
"magento/module-backend": "*",
"magento/module-checkout": "*",
diff --git a/app/code/Magento/Captcha/view/adminhtml/templates/default.phtml b/app/code/Magento/Captcha/view/adminhtml/templates/default.phtml
index 1be4bd19cd4ba..88e0d5edc2a7d 100644
--- a/app/code/Magento/Captcha/view/adminhtml/templates/default.phtml
+++ b/app/code/Magento/Captcha/view/adminhtml/templates/default.phtml
@@ -4,8 +4,6 @@
* See COPYING.txt for license details.
*/
-// @codingStandardsIgnoreFile
-
/** @var \Magento\Captcha\Block\Captcha\DefaultCaptcha $block */
/** @var \Magento\Captcha\Model\DefaultModel $captcha */
diff --git a/app/code/Magento/Captcha/view/frontend/templates/default.phtml b/app/code/Magento/Captcha/view/frontend/templates/default.phtml
index 6c9a5fe85f596..ead8c590eee94 100644
--- a/app/code/Magento/Captcha/view/frontend/templates/default.phtml
+++ b/app/code/Magento/Captcha/view/frontend/templates/default.phtml
@@ -4,8 +4,6 @@
* See COPYING.txt for license details.
*/
-// @codingStandardsIgnoreFile
-
/** @var \Magento\Captcha\Block\Captcha\DefaultCaptcha $block */
/** @var \Magento\Captcha\Model\DefaultModel $captcha */
diff --git a/app/code/Magento/Captcha/view/frontend/templates/js/components.phtml b/app/code/Magento/Captcha/view/frontend/templates/js/components.phtml
index bad5acc209b5f..5902a9f25cc4b 100644
--- a/app/code/Magento/Captcha/view/frontend/templates/js/components.phtml
+++ b/app/code/Magento/Captcha/view/frontend/templates/js/components.phtml
@@ -3,8 +3,5 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
-
-// @codingStandardsIgnoreFile
-
?>
= $block->getChildHtml() ?>
diff --git a/app/code/Magento/Captcha/view/frontend/web/js/captcha.js b/app/code/Magento/Captcha/view/frontend/web/js/captcha.js
index 0d4d4812f64c3..b5e5e6b006bfc 100644
--- a/app/code/Magento/Captcha/view/frontend/web/js/captcha.js
+++ b/app/code/Magento/Captcha/view/frontend/web/js/captcha.js
@@ -5,7 +5,7 @@
define([
'jquery',
- 'jquery/ui'
+ 'jquery-ui-modules/widget'
], function ($) {
'use strict';
diff --git a/app/code/Magento/CardinalCommerce/Model/Response/JwtParser.php b/app/code/Magento/CardinalCommerce/Model/Response/JwtParser.php
index 1865605d50acc..6b1a67a02a18d 100644
--- a/app/code/Magento/CardinalCommerce/Model/Response/JwtParser.php
+++ b/app/code/Magento/CardinalCommerce/Model/Response/JwtParser.php
@@ -14,9 +14,9 @@
use Magento\Payment\Model\Method\Logger as PaymentLogger;
/**
- * Parse content of CardinalCommerce response JWT.
+ * Parses content of CardinalCommerce response JWT.
*/
-class JwtParser
+class JwtParser implements JwtParserInterface
{
/**
* @var JwtManagement
diff --git a/app/code/Magento/CardinalCommerce/Model/Response/JwtParserInterface.php b/app/code/Magento/CardinalCommerce/Model/Response/JwtParserInterface.php
new file mode 100644
index 0000000000000..c6f9a5f60d10d
--- /dev/null
+++ b/app/code/Magento/CardinalCommerce/Model/Response/JwtParserInterface.php
@@ -0,0 +1,21 @@
+Magento_Sales::three_d_secure
- Configuration
- support@cardinalcommerce.com.]]>
-
+ CardinalCommerce
+ www.cardinalcommerce.com to get the CardinalCommerce credentials and find out more details about PSD2 SCA requirements. For support contact support@cardinalcommerce.com .]]>
Environment
Magento\CardinalCommerce\Model\Adminhtml\Source\Environment
diff --git a/app/code/Magento/CardinalCommerce/etc/config.xml b/app/code/Magento/CardinalCommerce/etc/config.xml
index 60b111a59cbc9..605ba89c12850 100644
--- a/app/code/Magento/CardinalCommerce/etc/config.xml
+++ b/app/code/Magento/CardinalCommerce/etc/config.xml
@@ -7,6 +7,13 @@
-->
+
+
+
+ /v1/songbird
+
+
+
production
diff --git a/app/code/Magento/CardinalCommerce/etc/di.xml b/app/code/Magento/CardinalCommerce/etc/di.xml
index ffd3c50ef5043..410c16e91cb77 100644
--- a/app/code/Magento/CardinalCommerce/etc/di.xml
+++ b/app/code/Magento/CardinalCommerce/etc/di.xml
@@ -7,4 +7,5 @@
-->
+
diff --git a/app/code/Magento/Catalog/Block/Adminhtml/Product/Attribute/Set/Main/Formset.php b/app/code/Magento/Catalog/Block/Adminhtml/Product/Attribute/Set/Main/Formset.php
index 295ea141b8eef..484f630d4d03e 100644
--- a/app/code/Magento/Catalog/Block/Adminhtml/Product/Attribute/Set/Main/Formset.php
+++ b/app/code/Magento/Catalog/Block/Adminhtml/Product/Attribute/Set/Main/Formset.php
@@ -3,10 +3,17 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
+declare(strict_types=1);
+
namespace Magento\Catalog\Block\Adminhtml\Product\Attribute\Set\Main;
use Magento\Backend\Block\Widget\Form;
+/**
+ * Form attribute set
+ *
+ * Class \Magento\Catalog\Block\Adminhtml\Product\Attribute\Set\Main\Formset
+ */
class Formset extends \Magento\Backend\Block\Widget\Form\Generic
{
/**
@@ -43,7 +50,7 @@ protected function _prepareForm()
/** @var \Magento\Framework\Data\Form $form */
$form = $this->_formFactory->create();
- $fieldset = $form->addFieldset('set_name', ['legend' => __('Edit Attribute Set Name')]);
+ $fieldset = $form->addFieldset('set_name', ['legend' => $this->getAttributeSetLabel()]);
$fieldset->addField(
'attribute_set_name',
'text',
@@ -84,4 +91,18 @@ protected function _prepareForm()
$form->setOnsubmit('return false;');
$this->setForm($form);
}
+
+ /**
+ * Get Attribute Set Label
+ *
+ * @return \Magento\Framework\Phrase
+ */
+ private function getAttributeSetLabel()
+ {
+ if ($this->getRequest()->getParam('id', false)) {
+ return __('Edit Attribute Set Name');
+ }
+
+ return __('Attribute Set Information');
+ }
}
diff --git a/app/code/Magento/Catalog/Block/Adminhtml/Product/Edit/Tab/Options/Option.php b/app/code/Magento/Catalog/Block/Adminhtml/Product/Edit/Tab/Options/Option.php
index 49caddd1a1018..ccf207938ab06 100644
--- a/app/code/Magento/Catalog/Block/Adminhtml/Product/Edit/Tab/Options/Option.php
+++ b/app/code/Magento/Catalog/Block/Adminhtml/Product/Edit/Tab/Options/Option.php
@@ -7,13 +7,20 @@
/**
* Customers defined options
*/
+declare(strict_types=1);
+
namespace Magento\Catalog\Block\Adminhtml\Product\Edit\Tab\Options;
use Magento\Backend\Block\Widget;
use Magento\Catalog\Model\Product;
use Magento\Catalog\Api\Data\ProductCustomOptionInterface;
+use Magento\Store\Model\Store;
/**
+ * Block for rendering option of product
+ *
+ * Class \Magento\Catalog\Block\Adminhtml\Product\Edit\Tab\Options\Option
+ *
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
*/
class Option extends Widget
@@ -105,6 +112,8 @@ protected function _construct()
}
/**
+ * Get Item Count
+ *
* @return int
*/
public function getItemCount()
@@ -113,6 +122,8 @@ public function getItemCount()
}
/**
+ * Set Item Count
+ *
* @param int $itemCount
* @return $this
*/
@@ -142,6 +153,8 @@ public function getProduct()
}
/**
+ * Set Product
+ *
* @param Product $product
* @return $this
*/
@@ -182,6 +195,8 @@ public function isReadonly()
}
/**
+ * Prepare Layout
+ *
* @return $this
*/
protected function _prepareLayout()
@@ -194,6 +209,8 @@ protected function _prepareLayout()
}
/**
+ * Get Add Button Id
+ *
* @return mixed
*/
public function getAddButtonId()
@@ -203,6 +220,8 @@ public function getAddButtonId()
}
/**
+ * Get Type Select Html
+ *
* @return mixed
*/
public function getTypeSelectHtml()
@@ -224,6 +243,8 @@ public function getTypeSelectHtml()
}
/**
+ * Get Require Select Html
+ *
* @return mixed
*/
public function getRequireSelectHtml()
@@ -272,6 +293,8 @@ public function getTemplatesHtml()
}
/**
+ * Get Option Values
+ *
* @return \Magento\Framework\DataObject[]
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
* @SuppressWarnings(PHPMD.NPathComplexity)
@@ -307,7 +330,7 @@ public function getOptionValues()
$value['sort_order'] = $option->getSortOrder();
$value['can_edit_price'] = $this->getCanEditPrice();
- if ($this->getProduct()->getStoreId() != '0') {
+ if ($this->getProduct()->getStoreId() != Store::DEFAULT_STORE_ID) {
$value['checkboxScopeTitle'] = $this->getCheckboxScopeHtml(
$option->getOptionId(),
'title',
@@ -317,49 +340,7 @@ public function getOptionValues()
}
if ($option->getGroupByType() == ProductCustomOptionInterface::OPTION_GROUP_SELECT) {
- $i = 0;
- $itemCount = 0;
- foreach ($option->getValues() as $_value) {
- /* @var $_value \Magento\Catalog\Model\Product\Option\Value */
- $value['optionValues'][$i] = [
- 'item_count' => max($itemCount, $_value->getOptionTypeId()),
- 'option_id' => $_value->getOptionId(),
- 'option_type_id' => $_value->getOptionTypeId(),
- 'title' => $_value->getTitle(),
- 'price' => $showPrice ? $this->getPriceValue(
- $_value->getPrice(),
- $_value->getPriceType()
- ) : '',
- 'price_type' => $showPrice ? $_value->getPriceType() : 0,
- 'sku' => $_value->getSku(),
- 'sort_order' => $_value->getSortOrder(),
- ];
-
- if ($this->getProduct()->getStoreId() != '0') {
- $value['optionValues'][$i]['checkboxScopeTitle'] = $this->getCheckboxScopeHtml(
- $_value->getOptionId(),
- 'title',
- $_value->getStoreTitle() === null,
- $_value->getOptionTypeId()
- );
- $value['optionValues'][$i]['scopeTitleDisabled'] = $_value->getStoreTitle() === null
- ? 'disabled'
- : null;
- if ($scope == \Magento\Store\Model\Store::PRICE_SCOPE_WEBSITE) {
- $value['optionValues'][$i]['checkboxScopePrice'] = $this->getCheckboxScopeHtml(
- $_value->getOptionId(),
- 'price',
- $_value->getstorePrice() === null,
- $_value->getOptionTypeId(),
- ['$(this).up(1).previous()']
- );
- $value['optionValues'][$i]['scopePriceDisabled'] = $_value->getStorePrice() === null
- ? 'disabled'
- : null;
- }
- }
- $i++;
- }
+ $value = $this->getOptionValueOfGroupSelect($value, $option, $showPrice, $scope);
} else {
$value['price'] = $showPrice ? $this->getPriceValue(
$option->getPrice(),
@@ -371,7 +352,7 @@ public function getOptionValues()
$value['file_extension'] = $option->getFileExtension();
$value['image_size_x'] = $option->getImageSizeX();
$value['image_size_y'] = $option->getImageSizeY();
- if ($this->getProduct()->getStoreId() != '0'
+ if ($this->getProduct()->getStoreId() != Store::DEFAULT_STORE_ID
&& $scope == \Magento\Store\Model\Store::PRICE_SCOPE_WEBSITE
) {
$value['checkboxScopePrice'] = $this->getCheckboxScopeHtml(
@@ -390,6 +371,63 @@ public function getOptionValues()
return $this->_values;
}
+ /**
+ * Get Option Value Of Group Select
+ *
+ * @param array $value
+ * @param \Magento\Catalog\Model\Product\Option $option
+ * @param boolean $showPrice
+ * @param int $scope
+ * @return array
+ */
+ private function getOptionValueOfGroupSelect($value, $option, $showPrice, $scope)
+ {
+ $i = 0;
+ $itemCount = 0;
+ foreach ($option->getValues() as $_value) {
+ /* @var $_value \Magento\Catalog\Model\Product\Option\Value */
+ $value['optionValues'][$i] = [
+ 'item_count' => max($itemCount, $_value->getOptionTypeId()),
+ 'option_id' => $_value->getOptionId(),
+ 'option_type_id' => $_value->getOptionTypeId(),
+ 'title' => $_value->getTitle(),
+ 'price' => $showPrice ? $this->getPriceValue(
+ $_value->getPrice(),
+ $_value->getPriceType()
+ ) : '',
+ 'price_type' => $showPrice ? $_value->getPriceType() : 0,
+ 'sku' => $_value->getSku(),
+ 'sort_order' => $_value->getSortOrder(),
+ ];
+
+ if ($this->getProduct()->getStoreId() != Store::DEFAULT_STORE_ID) {
+ $value['optionValues'][$i]['checkboxScopeTitle'] = $this->getCheckboxScopeHtml(
+ $_value->getOptionId(),
+ 'title',
+ $_value->getStoreTitle() === null,
+ $_value->getOptionTypeId()
+ );
+ $value['optionValues'][$i]['scopeTitleDisabled'] = $_value->getStoreTitle() === null
+ ? 'disabled'
+ : null;
+ if ($scope == \Magento\Store\Model\Store::PRICE_SCOPE_WEBSITE) {
+ $value['optionValues'][$i]['checkboxScopePrice'] = $this->getCheckboxScopeHtml(
+ $_value->getOptionId(),
+ 'price',
+ $_value->getstorePrice() === null,
+ $_value->getOptionTypeId(),
+ ['$(this).up(1).previous()']
+ );
+ $value['optionValues'][$i]['scopePriceDisabled'] = $_value->getStorePrice() === null
+ ? 'disabled'
+ : null;
+ }
+ }
+ $i++;
+ }
+ return $value;
+ }
+
/**
* Retrieve html of scope checkbox
*
@@ -431,6 +469,8 @@ public function getCheckboxScopeHtml($id, $name, $checked = true, $select_id = '
}
/**
+ * Get Price Value
+ *
* @param float $value
* @param string $type
* @return string
@@ -438,9 +478,9 @@ public function getCheckboxScopeHtml($id, $name, $checked = true, $select_id = '
public function getPriceValue($value, $type)
{
if ($type == 'percent') {
- return number_format($value, 2, null, '');
+ return number_format((float)$value, 2, null, '');
} elseif ($type == 'fixed') {
- return number_format($value, 2, null, '');
+ return number_format((float)$value, 2, null, '');
}
}
diff --git a/app/code/Magento/Catalog/Block/Product/Context.php b/app/code/Magento/Catalog/Block/Product/Context.php
index 4ca9e6b290bb5..db18eb2bc8a7d 100644
--- a/app/code/Magento/Catalog/Block/Product/Context.php
+++ b/app/code/Magento/Catalog/Block/Product/Context.php
@@ -5,6 +5,8 @@
*/
namespace Magento\Catalog\Block\Product;
+use Magento\Framework\Cache\LockGuardedCacheLoader;
+
/**
* Constructor modification point for Magento\Catalog\Block\Product\AbstractProduct.
*
@@ -17,7 +19,7 @@
* the classes they were introduced for.
*
* @deprecated 101.1.0
- * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
+ * @SuppressWarnings(PHPMD)
*/
class Context extends \Magento\Framework\View\Element\Template\Context
{
@@ -124,6 +126,7 @@ class Context extends \Magento\Framework\View\Element\Template\Context
* @param ImageBuilder $imageBuilder
* @param ReviewRendererInterface $reviewRenderer
* @param \Magento\CatalogInventory\Api\StockRegistryInterface $stockRegistry
+ * @param LockGuardedCacheLoader|null $lockQuery
*
* @SuppressWarnings(PHPMD.ExcessiveParameterList)
*/
@@ -164,7 +167,8 @@ public function __construct(
\Magento\Catalog\Helper\Image $imageHelper,
\Magento\Catalog\Block\Product\ImageBuilder $imageBuilder,
ReviewRendererInterface $reviewRenderer,
- \Magento\CatalogInventory\Api\StockRegistryInterface $stockRegistry
+ \Magento\CatalogInventory\Api\StockRegistryInterface $stockRegistry,
+ LockGuardedCacheLoader $lockQuery = null
) {
$this->imageHelper = $imageHelper;
$this->imageBuilder = $imageBuilder;
@@ -203,11 +207,14 @@ public function __construct(
$storeManager,
$pageConfig,
$resolver,
- $validator
+ $validator,
+ $lockQuery
);
}
/**
+ * Get Stock registry.
+ *
* @return \Magento\CatalogInventory\Api\StockRegistryInterface
*/
public function getStockRegistry()
@@ -216,6 +223,8 @@ public function getStockRegistry()
}
/**
+ * Get cart helper.
+ *
* @return \Magento\Checkout\Helper\Cart
*/
public function getCartHelper()
@@ -224,6 +233,8 @@ public function getCartHelper()
}
/**
+ * Get catalog config.
+ *
* @return \Magento\Catalog\Model\Config
*/
public function getCatalogConfig()
@@ -232,6 +243,8 @@ public function getCatalogConfig()
}
/**
+ * Get catalog helper.
+ *
* @return \Magento\Catalog\Helper\Data
*/
public function getCatalogHelper()
@@ -240,6 +253,8 @@ public function getCatalogHelper()
}
/**
+ * Get compare product.
+ *
* @return \Magento\Catalog\Helper\Product\Compare
*/
public function getCompareProduct()
@@ -248,6 +263,8 @@ public function getCompareProduct()
}
/**
+ * Get image helper.
+ *
* @return \Magento\Catalog\Helper\Image
*/
public function getImageHelper()
@@ -256,6 +273,8 @@ public function getImageHelper()
}
/**
+ * Get image builder.
+ *
* @return \Magento\Catalog\Block\Product\ImageBuilder
*/
public function getImageBuilder()
@@ -264,6 +283,8 @@ public function getImageBuilder()
}
/**
+ * Get math random.
+ *
* @return \Magento\Framework\Math\Random
*/
public function getMathRandom()
@@ -272,6 +293,8 @@ public function getMathRandom()
}
/**
+ * Get registry.
+ *
* @return \Magento\Framework\Registry
*/
public function getRegistry()
@@ -280,6 +303,8 @@ public function getRegistry()
}
/**
+ * Get tax data.
+ *
* @return \Magento\Tax\Helper\Data
*/
public function getTaxData()
@@ -288,6 +313,8 @@ public function getTaxData()
}
/**
+ * Get wishlist helper.
+ *
* @return \Magento\Wishlist\Helper\Data
*/
public function getWishlistHelper()
@@ -296,6 +323,8 @@ public function getWishlistHelper()
}
/**
+ * Get review renderer.
+ *
* @return \Magento\Catalog\Block\Product\ReviewRendererInterface
*/
public function getReviewRenderer()
diff --git a/app/code/Magento/Catalog/Block/Product/ImageFactory.php b/app/code/Magento/Catalog/Block/Product/ImageFactory.php
index aa303af656a5b..172cd794edfb9 100644
--- a/app/code/Magento/Catalog/Block/Product/ImageFactory.php
+++ b/app/code/Magento/Catalog/Block/Product/ImageFactory.php
@@ -160,6 +160,8 @@ public function create(Product $product, string $imageId, array $attributes = nu
);
}
+ $attributes = $attributes === null ? [] : $attributes;
+
$data = [
'data' => [
'template' => 'Magento_Catalog::product/image_with_borders.phtml',
diff --git a/app/code/Magento/Catalog/Block/Product/View/Details.php b/app/code/Magento/Catalog/Block/Product/View/Details.php
index e76c5bf201334..38925e9ae3cd7 100644
--- a/app/code/Magento/Catalog/Block/Product/View/Details.php
+++ b/app/code/Magento/Catalog/Block/Product/View/Details.php
@@ -37,11 +37,11 @@ public function getGroupSortedChildNames(string $groupName, string $callback): a
$alias = $layout->getElementAlias($childName);
$sortOrder = (int)$this->getChildData($alias, 'sort_order') ?? 0;
- $childNamesSortOrder[$sortOrder] = $childName;
+ $childNamesSortOrder[$childName] = $sortOrder;
}
- ksort($childNamesSortOrder, SORT_NUMERIC);
+ asort($childNamesSortOrder, SORT_NUMERIC);
- return $childNamesSortOrder;
+ return array_keys($childNamesSortOrder);
}
}
diff --git a/app/code/Magento/Catalog/Block/Product/View/Options/Type/Select.php b/app/code/Magento/Catalog/Block/Product/View/Options/Type/Select.php
index d9d663b32f4de..81d7e18d45519 100644
--- a/app/code/Magento/Catalog/Block/Product/View/Options/Type/Select.php
+++ b/app/code/Magento/Catalog/Block/Product/View/Options/Type/Select.php
@@ -58,7 +58,7 @@ public function __construct(
*
* @return string
*/
- public function getValuesHtml()
+ public function getValuesHtml(): string
{
$option = $this->getOption();
$optionType = $option->getType();
diff --git a/app/code/Magento/Catalog/Block/Rss/Category.php b/app/code/Magento/Catalog/Block/Rss/Category.php
index fbfd494c336e6..50967d2eb8dca 100644
--- a/app/code/Magento/Catalog/Block/Rss/Category.php
+++ b/app/code/Magento/Catalog/Block/Rss/Category.php
@@ -11,14 +11,16 @@
/**
* Class Category
+ *
* @package Magento\Catalog\Block\Rss
+ *
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
*/
class Category extends \Magento\Framework\View\Element\AbstractBlock implements DataProviderInterface
{
- /**
- * @var \Magento\Catalog\Model\CategoryFactory
- */
+ /**
+ * @var \Magento\Catalog\Model\CategoryFactory
+ */
protected $categoryFactory;
/**
@@ -50,7 +52,6 @@ class Category extends \Magento\Framework\View\Element\AbstractBlock implements
* @var CategoryRepositoryInterface
*/
protected $categoryRepository;
-
/**
* @param \Magento\Framework\View\Element\Template\Context $context
* @param \Magento\Catalog\Model\CategoryFactory $categoryFactory
@@ -83,6 +84,8 @@ public function __construct(
}
/**
+ * Set Block cache key
+ *
* @return void
*/
protected function _construct()
@@ -97,7 +100,9 @@ protected function _construct()
}
/**
- * {@inheritdoc}
+ * Get info about category by category id
+ *
+ * @return array
*/
public function getRssData()
{
@@ -116,7 +121,9 @@ public function getRssData()
$newUrl = $category->getUrl();
$title = $category->getName();
$data = ['title' => $title, 'description' => $title, 'link' => $newUrl, 'charset' => 'UTF-8'];
-
+ $attributes = $this->_viewConfig
+ ->getViewConfig()
+ ->getMediaAttributes('Magento_Catalog', $this->imageHelper::MEDIA_TYPE_CONFIG_NODE, 'rss_thumbnail');
/** @var $product \Magento\Catalog\Model\Product */
foreach ($this->rssModel->getProductCollection($category, $this->getStoreId()) as $product) {
$product->setAllowedInRss(true);
@@ -130,7 +137,7 @@ public function getRssData()
$description = '
-
+
%s %s
';
@@ -139,6 +146,8 @@ public function getRssData()
$description,
$product->getProductUrl(),
$this->imageHelper->init($product, 'rss_thumbnail')->getUrl(),
+ isset($attributes['height']) ? $attributes['height'] : 75,
+ isset($attributes['width']) ? $attributes['width'] : 75,
$product->getDescription(),
$product->getAllowedPriceInRss() ? $this->renderPriceHtml($product) : ''
);
@@ -187,6 +196,8 @@ protected function renderPriceHtml(\Magento\Catalog\Model\Product $product)
}
/**
+ * Get current Store Id
+ *
* @return int
*/
protected function getStoreId()
@@ -199,6 +210,8 @@ protected function getStoreId()
}
/**
+ * Cache lifetime for RSS feed
+ *
* @return int
*/
public function getCacheLifetime()
@@ -207,7 +220,9 @@ public function getCacheLifetime()
}
/**
- * {@inheritdoc}
+ * Retrieve rss feed enable for category page
+ *
+ * @return bool
*/
public function isAllowed()
{
@@ -218,6 +233,8 @@ public function isAllowed()
}
/**
+ * Get category feed collection
+ *
* @return array
*/
public function getFeeds()
@@ -257,7 +274,9 @@ public function getFeeds()
}
/**
- * {@inheritdoc}
+ * Check isAuthRequired Required
+ *
+ * @return bool
*/
public function isAuthRequired()
{
diff --git a/app/code/Magento/Catalog/Block/Widget/Link.php b/app/code/Magento/Catalog/Block/Widget/Link.php
index 85e50dbd3dc27..a25af297111d2 100644
--- a/app/code/Magento/Catalog/Block/Widget/Link.php
+++ b/app/code/Magento/Catalog/Block/Widget/Link.php
@@ -4,17 +4,15 @@
* See COPYING.txt for license details.
*/
-/**
- * Widget to display catalog link
- *
- * @author Magento Core Team
- */
namespace Magento\Catalog\Block\Widget;
use Magento\CatalogUrlRewrite\Model\ProductUrlRewriteGenerator;
use Magento\UrlRewrite\Model\UrlFinderInterface;
use Magento\UrlRewrite\Service\V1\Data\UrlRewrite;
+/**
+ * Render the URL of given entity
+ */
class Link extends \Magento\Framework\View\Element\Html\Link implements \Magento\Widget\Block\BlockInterface
{
/**
@@ -63,10 +61,9 @@ public function __construct(
/**
* Prepare url using passed id path and return it
- * or return false if path was not found in url rewrites.
*
* @throws \RuntimeException
- * @return string|false
+ * @return string|false if path was not found in url rewrites.
* @SuppressWarnings(PHPMD.NPathComplexity)
*/
public function getHref()
@@ -93,7 +90,7 @@ public function getHref()
if ($rewrite) {
$href = $store->getUrl('', ['_direct' => $rewrite->getRequestPath()]);
- if (strpos($href, '___store') === false) {
+ if ($this->addStoreCodeParam($store, $href)) {
$href .= (strpos($href, '?') === false ? '?' : '&') . '___store=' . $store->getCode();
}
}
@@ -102,6 +99,22 @@ public function getHref()
return $this->_href;
}
+ /**
+ * Checks whether store code query param should be appended to the URL
+ *
+ * @param \Magento\Store\Model\Store $store
+ * @param string $url
+ * @return bool
+ * @throws \Magento\Framework\Exception\NoSuchEntityException
+ */
+ private function addStoreCodeParam(\Magento\Store\Model\Store $store, string $url): bool
+ {
+ return $this->getStoreId()
+ && !$store->isUseStoreInUrl()
+ && $store->getId() !== $this->_storeManager->getStore()->getId()
+ && strpos($url, '___store') === false;
+ }
+
/**
* Parse id_path
*
@@ -121,6 +134,7 @@ protected function parseIdPath($idPath)
/**
* Prepare label using passed text as parameter.
+ *
* If anchor text was not specified get entity name from DB.
*
* @return string
@@ -150,9 +164,8 @@ public function getLabel()
/**
* Render block HTML
- * or return empty string if url can't be prepared
*
- * @return string
+ * @return string empty string if url can't be prepared
*/
protected function _toHtml()
{
diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Product/MassDelete.php b/app/code/Magento/Catalog/Controller/Adminhtml/Product/MassDelete.php
index 8fceba3c45e2c..7fa9d36163502 100644
--- a/app/code/Magento/Catalog/Controller/Adminhtml/Product/MassDelete.php
+++ b/app/code/Magento/Catalog/Controller/Adminhtml/Product/MassDelete.php
@@ -4,6 +4,8 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
+declare(strict_types=1);
+
namespace Magento\Catalog\Controller\Adminhtml\Product;
use Magento\Framework\App\Action\HttpPostActionInterface as HttpPostActionInterface;
@@ -12,7 +14,14 @@
use Magento\Ui\Component\MassAction\Filter;
use Magento\Catalog\Model\ResourceModel\Product\CollectionFactory;
use Magento\Catalog\Api\ProductRepositoryInterface;
+use Magento\Framework\Exception\CouldNotSaveException;
+use Magento\Framework\Exception\StateException;
+use Magento\Framework\Exception\LocalizedException;
+use Psr\Log\LoggerInterface;
+/**
+ * Class \Magento\Catalog\Controller\Adminhtml\Product\MassDelete
+ */
class MassDelete extends \Magento\Catalog\Controller\Adminhtml\Product implements HttpPostActionInterface
{
/**
@@ -32,38 +41,55 @@ class MassDelete extends \Magento\Catalog\Controller\Adminhtml\Product implement
*/
private $productRepository;
+ /**
+ * @var LoggerInterface
+ */
+ private $logger;
+
/**
* @param Context $context
* @param Builder $productBuilder
* @param Filter $filter
* @param CollectionFactory $collectionFactory
* @param ProductRepositoryInterface $productRepository
+ * @param LoggerInterface $logger
*/
public function __construct(
Context $context,
Builder $productBuilder,
Filter $filter,
CollectionFactory $collectionFactory,
- ProductRepositoryInterface $productRepository = null
+ ProductRepositoryInterface $productRepository = null,
+ LoggerInterface $logger = null
) {
$this->filter = $filter;
$this->collectionFactory = $collectionFactory;
- $this->productRepository = $productRepository
- ?: \Magento\Framework\App\ObjectManager::getInstance()->create(ProductRepositoryInterface::class);
+ $this->productRepository = $productRepository ?:
+ \Magento\Framework\App\ObjectManager::getInstance()->create(ProductRepositoryInterface::class);
+ $this->logger = $logger ?:
+ \Magento\Framework\App\ObjectManager::getInstance()->create(LoggerInterface::class);
parent::__construct($context, $productBuilder);
}
/**
+ * Mass Delete Action
+ *
* @return \Magento\Backend\Model\View\Result\Redirect
*/
public function execute()
{
$collection = $this->filter->getCollection($this->collectionFactory->create());
$productDeleted = 0;
+ $productDeletedError = 0;
/** @var \Magento\Catalog\Model\Product $product */
foreach ($collection->getItems() as $product) {
- $this->productRepository->delete($product);
- $productDeleted++;
+ try {
+ $this->productRepository->delete($product);
+ $productDeleted++;
+ } catch (LocalizedException $exception) {
+ $this->logger->error($exception->getLogMessage());
+ $productDeletedError++;
+ }
}
if ($productDeleted) {
@@ -72,6 +98,15 @@ public function execute()
);
}
+ if ($productDeletedError) {
+ $this->messageManager->addErrorMessage(
+ __(
+ 'A total of %1 record(s) haven\'t been deleted. Please see server logs for more details.',
+ $productDeletedError
+ )
+ );
+ }
+
return $this->resultFactory->create(ResultFactory::TYPE_REDIRECT)->setPath('catalog/*/index');
}
}
diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Widget/Chooser.php b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Widget/Chooser.php
index 933b5eaafbb39..113b048f7c98b 100644
--- a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Widget/Chooser.php
+++ b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Widget/Chooser.php
@@ -1,12 +1,17 @@
resultRawFactory = $resultRawFactory;
$this->layoutFactory = $layoutFactory;
+ $this->escaper = $escaper ?: ObjectManager::getInstance()->get(\Magento\Framework\Escaper::class);
}
/**
- * Chooser Source action
+ * Chooser Source action.
*
* @return \Magento\Framework\Controller\Result\Raw
*/
@@ -55,11 +68,11 @@ public function execute()
'',
[
'data' => [
- 'id' => $uniqId,
+ 'id' => $this->escaper->escapeHtml($uniqId),
'use_massaction' => $massAction,
'product_type_id' => $productTypeId,
- 'category_id' => $this->getRequest()->getParam('category_id'),
- ]
+ 'category_id' => (int)$this->getRequest()->getParam('category_id'),
+ ],
]
);
@@ -71,10 +84,10 @@ public function execute()
'',
[
'data' => [
- 'id' => $uniqId . 'Tree',
+ 'id' => $this->escaper->escapeHtml($uniqId) . 'Tree',
'node_click_listener' => $productsGrid->getCategoryClickListenerJs(),
'with_empty_node' => true,
- ]
+ ],
]
);
@@ -86,6 +99,7 @@ public function execute()
/** @var \Magento\Framework\Controller\Result\Raw $resultRaw */
$resultRaw = $this->resultRawFactory->create();
+
return $resultRaw->setContents($html);
}
}
diff --git a/app/code/Magento/Catalog/Controller/Product/Compare/Add.php b/app/code/Magento/Catalog/Controller/Product/Compare/Add.php
index d99901c915a10..f5c3171a3fe90 100644
--- a/app/code/Magento/Catalog/Controller/Product/Compare/Add.php
+++ b/app/code/Magento/Catalog/Controller/Product/Compare/Add.php
@@ -9,10 +9,13 @@
use Magento\Framework\App\Action\HttpPostActionInterface as HttpPostActionInterface;
use Magento\Framework\Exception\NoSuchEntityException;
+/**
+ * Add item to compare list action.
+ */
class Add extends \Magento\Catalog\Controller\Product\Compare implements HttpPostActionInterface
{
/**
- * Add item to compare list
+ * Add item to compare list.
*
* @return \Magento\Framework\Controller\ResultInterface
*/
@@ -27,12 +30,13 @@ public function execute()
if ($productId && ($this->_customerVisitor->getId() || $this->_customerSession->isLoggedIn())) {
$storeId = $this->_storeManager->getStore()->getId();
try {
+ /** @var \Magento\Catalog\Model\Product $product */
$product = $this->productRepository->getById($productId, false, $storeId);
} catch (NoSuchEntityException $e) {
$product = null;
}
- if ($product) {
+ if ($product && $product->isSalable()) {
$this->_catalogProductCompareList->addProduct($product);
$productName = $this->_objectManager->get(
\Magento\Framework\Escaper::class
@@ -41,7 +45,7 @@ public function execute()
'addCompareSuccessMessage',
[
'product_name' => $productName,
- 'compare_list_url' => $this->_url->getUrl('catalog/product_compare')
+ 'compare_list_url' => $this->_url->getUrl('catalog/product_compare'),
]
);
@@ -50,6 +54,7 @@ public function execute()
$this->_objectManager->get(\Magento\Catalog\Helper\Product\Compare::class)->calculate();
}
+
return $resultRedirect->setRefererOrBaseUrl();
}
}
diff --git a/app/code/Magento/Catalog/Controller/Product/Compare/Remove.php b/app/code/Magento/Catalog/Controller/Product/Compare/Remove.php
index eac0ddf94af20..acf0f1b754c12 100644
--- a/app/code/Magento/Catalog/Controller/Product/Compare/Remove.php
+++ b/app/code/Magento/Catalog/Controller/Product/Compare/Remove.php
@@ -9,10 +9,13 @@
use Magento\Framework\App\Action\HttpPostActionInterface as HttpPostActionInterface;
use Magento\Framework\Exception\NoSuchEntityException;
+/**
+ * Remove item from compare list action.
+ */
class Remove extends \Magento\Catalog\Controller\Product\Compare implements HttpPostActionInterface
{
/**
- * Remove item from compare list
+ * Remove item from compare list.
*
* @return \Magento\Framework\Controller\ResultInterface
*/
@@ -22,12 +25,13 @@ public function execute()
if ($productId) {
$storeId = $this->_storeManager->getStore()->getId();
try {
+ /** @var \Magento\Catalog\Model\Product $product */
$product = $this->productRepository->getById($productId, false, $storeId);
} catch (NoSuchEntityException $e) {
$product = null;
}
- if ($product) {
+ if ($product && $product->isSalable()) {
/** @var $item \Magento\Catalog\Model\Product\Compare\Item */
$item = $this->_compareItemFactory->create();
if ($this->_customerSession->isLoggedIn()) {
@@ -59,6 +63,7 @@ public function execute()
if (!$this->getRequest()->getParam('isAjax', false)) {
$resultRedirect = $this->resultRedirectFactory->create();
+
return $resultRedirect->setRefererOrBaseUrl();
}
}
diff --git a/app/code/Magento/Catalog/Model/Category.php b/app/code/Magento/Catalog/Model/Category.php
index d911bec0aaac9..4ddfd1f3b63a8 100644
--- a/app/code/Magento/Catalog/Model/Category.php
+++ b/app/code/Magento/Catalog/Model/Category.php
@@ -5,10 +5,13 @@
*/
namespace Magento\Catalog\Model;
+use Magento\Authorization\Model\UserContextInterface;
use Magento\Catalog\Api\CategoryRepositoryInterface;
use Magento\Catalog\Api\Data\CategoryInterface;
use Magento\CatalogUrlRewrite\Model\CategoryUrlRewriteGenerator;
use Magento\Framework\Api\AttributeValueFactory;
+use Magento\Framework\App\ObjectManager;
+use Magento\Framework\AuthorizationInterface;
use Magento\Framework\Convert\ConvertArray;
use Magento\Framework\Exception\NoSuchEntityException;
use Magento\Framework\Profiler;
@@ -128,6 +131,7 @@ class Category extends \Magento\Catalog\Model\AbstractModel implements
'page_layout',
'custom_layout_update',
'custom_apply_to_products',
+ 'custom_use_parent_settings',
];
/**
@@ -211,6 +215,16 @@ class Category extends \Magento\Catalog\Model\AbstractModel implements
*/
protected $metadataService;
+ /**
+ * @var UserContextInterface
+ */
+ private $userContext;
+
+ /**
+ * @var AuthorizationInterface
+ */
+ private $authorization;
+
/**
* @param \Magento\Framework\Model\Context $context
* @param \Magento\Framework\Registry $registry
@@ -311,17 +325,21 @@ protected function getCustomAttributesCodes()
return $this->customAttributesCodes;
}
+ // phpcs:disable Generic.CodeAnalysis.UselessOverridingMethod
/**
* Returns model resource
*
* @throws \Magento\Framework\Exception\LocalizedException
* @return \Magento\Catalog\Model\ResourceModel\Category
* @deprecated because resource models should be used directly
+ * phpcs:disable Generic.CodeAnalysis.UselessOverridingMethod
*/
protected function _getResource()
{
+ //phpcs:enable Generic.CodeAnalysis.UselessOverridingMethod
return parent::_getResource();
}
+ // phpcs:enable
/**
* Get flat resource model flag
@@ -433,7 +451,9 @@ public function move($parentId, $afterCategoryId)
if ($this->flatState->isFlatEnabled()) {
$flatIndexer = $this->indexerRegistry->get(Indexer\Category\Flat\State::INDEXER_ID);
if (!$flatIndexer->isScheduled()) {
- $flatIndexer->reindexList([$this->getId(), $oldParentId, $parentId]);
+ $sameLevelCategories = explode(',', $this->getParentCategory()->getChildren());
+ $list = array_unique(array_merge($sameLevelCategories, [$this->getId(), $oldParentId, $parentId]));
+ $flatIndexer->reindexList($list);
}
}
$productIndexer = $this->indexerRegistry->get(Indexer\Category\Product::INDEXER_ID);
@@ -606,11 +626,13 @@ public function getUrl()
return $this->getData('url');
}
- $rewrite = $this->urlFinder->findOneByData([
- UrlRewrite::ENTITY_ID => $this->getId(),
- UrlRewrite::ENTITY_TYPE => CategoryUrlRewriteGenerator::ENTITY_TYPE,
- UrlRewrite::STORE_ID => $this->getStoreId(),
- ]);
+ $rewrite = $this->urlFinder->findOneByData(
+ [
+ UrlRewrite::ENTITY_ID => $this->getId(),
+ UrlRewrite::ENTITY_TYPE => CategoryUrlRewriteGenerator::ENTITY_TYPE,
+ UrlRewrite::STORE_ID => $this->getStoreId(),
+ ]
+ );
if ($rewrite) {
$this->setData('url', $this->getUrlInstance()->getDirectUrl($rewrite->getRequestPath()));
Profiler::stop('REWRITE: ' . __METHOD__);
@@ -914,6 +936,60 @@ public function beforeDelete()
return parent::beforeDelete();
}
+ /**
+ * Get user context.
+ *
+ * @return UserContextInterface
+ */
+ private function getUserContext(): UserContextInterface
+ {
+ if (!$this->userContext) {
+ $this->userContext = ObjectManager::getInstance()->get(UserContextInterface::class);
+ }
+
+ return $this->userContext;
+ }
+
+ /**
+ * Get authorization service.
+ *
+ * @return AuthorizationInterface
+ */
+ private function getAuthorization(): AuthorizationInterface
+ {
+ if (!$this->authorization) {
+ $this->authorization = ObjectManager::getInstance()->get(AuthorizationInterface::class);
+ }
+
+ return $this->authorization;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function beforeSave()
+ {
+ //Validate changing of design.
+ $userType = $this->getUserContext()->getUserType();
+ if ((
+ $userType === UserContextInterface::USER_TYPE_ADMIN
+ || $userType === UserContextInterface::USER_TYPE_INTEGRATION
+ )
+ && !$this->getAuthorization()->isAllowed('Magento_Catalog::edit_category_design')
+ ) {
+ foreach ($this->_designAttributes as $attributeCode) {
+ $this->setData($attributeCode, $value = $this->getOrigData($attributeCode));
+ if (!empty($this->_data[self::CUSTOM_ATTRIBUTES])
+ && array_key_exists($attributeCode, $this->_data[self::CUSTOM_ATTRIBUTES])) {
+ //In case custom attribute were used to update the entity.
+ $this->_data[self::CUSTOM_ATTRIBUTES][$attributeCode]->setValue($value);
+ }
+ }
+ }
+
+ return parent::beforeSave();
+ }
+
/**
* Retrieve anchors above
*
diff --git a/app/code/Magento/Catalog/Model/Category/Attribute/Backend/Image.php b/app/code/Magento/Catalog/Model/Category/Attribute/Backend/Image.php
index cd450e26cd832..4880214e5c6a6 100644
--- a/app/code/Magento/Catalog/Model/Category/Attribute/Backend/Image.php
+++ b/app/code/Magento/Catalog/Model/Category/Attribute/Backend/Image.php
@@ -6,6 +6,7 @@
namespace Magento\Catalog\Model\Category\Attribute\Backend;
use Magento\Framework\App\Filesystem\DirectoryList;
+use Magento\Framework\File\Uploader;
/**
* Catalog category image attribute backend model
@@ -70,7 +71,8 @@ public function __construct(
/**
* Gets image name from $value array.
- * Will return empty string in a case when $value is not an array
+ *
+ * Will return empty string in a case when $value is not an array.
*
* @param array $value Attribute value
* @return string
@@ -85,8 +87,28 @@ private function getUploadedImageName($value)
}
/**
- * Avoiding saving potential upload data to DB
- * Will set empty image attribute value if image was not uploaded
+ * Check that image name exists in catalog/category directory and return new image name if it already exists.
+ *
+ * @param string $imageName
+ * @return string
+ */
+ private function checkUniqueImageName(string $imageName): string
+ {
+ $imageUploader = $this->getImageUploader();
+ $mediaDirectory = $this->_filesystem->getDirectoryWrite(DirectoryList::MEDIA);
+ $imageAbsolutePath = $mediaDirectory->getAbsolutePath(
+ $imageUploader->getBasePath() . DIRECTORY_SEPARATOR . $imageName
+ );
+
+ $imageName = Uploader::getNewFilename($imageAbsolutePath);
+
+ return $imageName;
+ }
+
+ /**
+ * Avoiding saving potential upload data to DB.
+ *
+ * Will set empty image attribute value if image was not uploaded.
*
* @param \Magento\Framework\DataObject $object
* @return $this
@@ -99,10 +121,15 @@ public function beforeSave($object)
if ($this->fileResidesOutsideCategoryDir($value)) {
// use relative path for image attribute so we know it's outside of category dir when we fetch it
+ // phpcs:ignore Magento2.Functions.DiscouragedFunction
+ $value[0]['url'] = parse_url($value[0]['url'], PHP_URL_PATH);
$value[0]['name'] = $value[0]['url'];
}
if ($imageName = $this->getUploadedImageName($value)) {
+ if (!$this->fileResidesOutsideCategoryDir($value)) {
+ $imageName = $this->checkUniqueImageName($imageName);
+ }
$object->setData($this->additionalData . $attributeName, $value);
$object->setData($attributeName, $imageName);
} elseif (!is_string($value)) {
@@ -113,6 +140,8 @@ public function beforeSave($object)
}
/**
+ * Get Instance of Category Image Uploader.
+ *
* @return \Magento\Catalog\Model\ImageUploader
*
* @deprecated 101.0.0
@@ -153,9 +182,11 @@ private function fileResidesOutsideCategoryDir($value)
$fileUrl = ltrim($value[0]['url'], '/');
$baseMediaDir = $this->_filesystem->getUri(DirectoryList::MEDIA);
- $usingPathRelativeToBase = strpos($fileUrl, $baseMediaDir) === 0;
+ if (!$baseMediaDir) {
+ return false;
+ }
- return $usingPathRelativeToBase;
+ return strpos($fileUrl, $baseMediaDir) !== false;
}
/**
diff --git a/app/code/Magento/Catalog/Model/Category/DataProvider.php b/app/code/Magento/Catalog/Model/Category/DataProvider.php
index a4127c9a97ffd..c96b2aae36059 100644
--- a/app/code/Magento/Catalog/Model/Category/DataProvider.php
+++ b/app/code/Magento/Catalog/Model/Category/DataProvider.php
@@ -25,6 +25,7 @@
use Magento\Ui\Component\Form\Field;
use Magento\Ui\DataProvider\EavValidationRules;
use Magento\Ui\DataProvider\Modifier\PoolInterface;
+use Magento\Framework\AuthorizationInterface;
/**
* Class DataProvider
@@ -32,6 +33,7 @@
* @api
*
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
+ * @SuppressWarnings(PHPMD.TooManyFields)
* @since 101.0.0
*/
class DataProvider extends \Magento\Ui\DataProvider\ModifierPoolDataProvider
@@ -146,6 +148,11 @@ class DataProvider extends \Magento\Ui\DataProvider\ModifierPoolDataProvider
*/
private $fileInfo;
+ /**
+ * @var AuthorizationInterface
+ */
+ private $auth;
+
/**
* DataProvider constructor
*
@@ -162,6 +169,7 @@ class DataProvider extends \Magento\Ui\DataProvider\ModifierPoolDataProvider
* @param array $meta
* @param array $data
* @param PoolInterface|null $pool
+ * @param AuthorizationInterface|null $auth
* @SuppressWarnings(PHPMD.ExcessiveParameterList)
*/
public function __construct(
@@ -177,7 +185,8 @@ public function __construct(
CategoryFactory $categoryFactory,
array $meta = [],
array $data = [],
- PoolInterface $pool = null
+ PoolInterface $pool = null,
+ ?AuthorizationInterface $auth = null
) {
$this->eavValidationRules = $eavValidationRules;
$this->collection = $categoryCollectionFactory->create();
@@ -187,6 +196,7 @@ public function __construct(
$this->storeManager = $storeManager;
$this->request = $request;
$this->categoryFactory = $categoryFactory;
+ $this->auth = $auth ?? ObjectManager::getInstance()->get(AuthorizationInterface::class);
parent::__construct($name, $primaryFieldName, $requestFieldName, $meta, $data, $pool);
}
@@ -210,6 +220,8 @@ public function getMeta()
}
/**
+ * Disable fields if they are using default values.
+ *
* @param Category $category
* @param array $meta
* @return array
@@ -260,10 +272,13 @@ private function addUseDefaultValueCheckbox(Category $category, array $meta)
*/
public function prepareMeta($meta)
{
- $meta = array_replace_recursive($meta, $this->prepareFieldsMeta(
- $this->getFieldsMap(),
- $this->getAttributesMeta($this->eavConfig->getEntityType('catalog_category'))
- ));
+ $meta = array_replace_recursive(
+ $meta,
+ $this->prepareFieldsMeta(
+ $this->getFieldsMap(),
+ $this->getAttributesMeta($this->eavConfig->getEntityType('catalog_category'))
+ )
+ );
return $meta;
}
@@ -277,11 +292,20 @@ public function prepareMeta($meta)
*/
private function prepareFieldsMeta($fieldsMap, $fieldsMeta)
{
+ $canEditDesign = $this->auth->isAllowed('Magento_Catalog::edit_category_design');
+
$result = [];
foreach ($fieldsMap as $fieldSet => $fields) {
foreach ($fields as $field) {
if (isset($fieldsMeta[$field])) {
- $result[$fieldSet]['children'][$field]['arguments']['data']['config'] = $fieldsMeta[$field];
+ $config = $fieldsMeta[$field];
+ if (($fieldSet === 'design' || $fieldSet === 'schedule_design_update') && !$canEditDesign) {
+ $config['required'] = 1;
+ $config['disabled'] = 1;
+ $config['serviceDisabled'] = true;
+ }
+
+ $result[$fieldSet]['children'][$field]['arguments']['data']['config'] = $config;
}
}
}
@@ -498,6 +522,7 @@ private function convertValues($category, $categoryData)
$stat = $fileInfo->getStat($fileName);
$mime = $fileInfo->getMimeType($fileName);
+ // phpcs:ignore Magento2.Functions.DiscouragedFunction
$categoryData[$attributeCode][0]['name'] = basename($fileName);
if ($fileInfo->isBeginsWithMediaDirectoryPath($fileName)) {
@@ -533,6 +558,8 @@ public function getDefaultMetaData($result)
}
/**
+ * List of fields groups and fields.
+ *
* @return array
* @since 101.0.0
*/
diff --git a/app/code/Magento/Catalog/Model/Category/FileInfo.php b/app/code/Magento/Catalog/Model/Category/FileInfo.php
index cd3592ef40a0f..76b6a2e75d0ea 100644
--- a/app/code/Magento/Catalog/Model/Category/FileInfo.php
+++ b/app/code/Magento/Catalog/Model/Category/FileInfo.php
@@ -10,6 +10,8 @@
use Magento\Framework\Filesystem;
use Magento\Framework\Filesystem\Directory\WriteInterface;
use Magento\Framework\Filesystem\Directory\ReadInterface;
+use Magento\Framework\Exception\NoSuchEntityException;
+use Magento\Store\Model\StoreManagerInterface;
/**
* Class FileInfo
@@ -48,16 +50,26 @@ class FileInfo
*/
private $pubDirectory;
+ /**
+ * Store manager
+ *
+ * @var \Magento\Store\Model\StoreManagerInterface
+ */
+ private $storeManager;
+
/**
* @param Filesystem $filesystem
* @param Mime $mime
+ * @param StoreManagerInterface $storeManager
*/
public function __construct(
Filesystem $filesystem,
- Mime $mime
+ Mime $mime,
+ StoreManagerInterface $storeManager
) {
$this->filesystem = $filesystem;
$this->mime = $mime;
+ $this->storeManager = $storeManager;
}
/**
@@ -152,7 +164,8 @@ public function isExist($fileName)
*/
private function getFilePath($fileName)
{
- $filePath = ltrim($fileName, '/');
+ $filePath = $this->removeStorePath($fileName);
+ $filePath = ltrim($filePath, '/');
$mediaDirectoryRelativeSubpath = $this->getMediaDirectoryPathRelativeToBaseDirectoryPath($filePath);
$isFileNameBeginsWithMediaDirectoryPath = $this->isBeginsWithMediaDirectoryPath($fileName);
@@ -177,14 +190,39 @@ private function getFilePath($fileName)
*/
public function isBeginsWithMediaDirectoryPath($fileName)
{
- $filePath = ltrim($fileName, '/');
+ $filePath = $this->removeStorePath($fileName);
+ $filePath = ltrim($filePath, '/');
$mediaDirectoryRelativeSubpath = $this->getMediaDirectoryPathRelativeToBaseDirectoryPath($filePath);
- $isFileNameBeginsWithMediaDirectoryPath = strpos($filePath, $mediaDirectoryRelativeSubpath) === 0;
+ $isFileNameBeginsWithMediaDirectoryPath = strpos($filePath, (string) $mediaDirectoryRelativeSubpath) === 0;
return $isFileNameBeginsWithMediaDirectoryPath;
}
+ /**
+ * Clean store path in case if it's exists
+ *
+ * @param string $path
+ * @return string
+ */
+ private function removeStorePath(string $path): string
+ {
+ $result = $path;
+ try {
+ $storeUrl = $this->storeManager->getStore()->getBaseUrl();
+ } catch (NoSuchEntityException $e) {
+ return $result;
+ }
+ // phpcs:ignore Magento2.Functions.DiscouragedFunction
+ $path = parse_url($path, PHP_URL_PATH);
+ // phpcs:ignore Magento2.Functions.DiscouragedFunction
+ $storePath = parse_url($storeUrl, PHP_URL_PATH);
+ $storePath = rtrim($storePath, '/');
+
+ $result = preg_replace('/^' . preg_quote($storePath, '/') . '/', '', $path);
+ return $result;
+ }
+
/**
* Get media directory subpath relative to base directory path
*
diff --git a/app/code/Magento/Catalog/Model/Category/StoreCategories.php b/app/code/Magento/Catalog/Model/Category/StoreCategories.php
new file mode 100644
index 0000000000000..0d7afcd4a5000
--- /dev/null
+++ b/app/code/Magento/Catalog/Model/Category/StoreCategories.php
@@ -0,0 +1,64 @@
+categoryRepository = $categoryRepository;
+ $this->groupRepository = $groupRepository;
+ }
+
+ /**
+ * Get all category ids for store.
+ *
+ * @param int|null $storeGroupId
+ * @return int[]
+ * @throws \Magento\Framework\Exception\NoSuchEntityException
+ */
+ public function getCategoryIds(?int $storeGroupId = null): array
+ {
+ $rootCategoryId = $storeGroupId
+ ? $this->groupRepository->get($storeGroupId)->getRootCategoryId()
+ : Category::TREE_ROOT_ID;
+ /** @var Category $rootCategory */
+ $rootCategory = $this->categoryRepository->get($rootCategoryId);
+ $categoriesIds = array_map(
+ function ($value) {
+ return (int) $value;
+ },
+ (array) $rootCategory->getAllChildren(true)
+ );
+
+ return $categoriesIds;
+ }
+}
diff --git a/app/code/Magento/Catalog/Model/Category/Tree.php b/app/code/Magento/Catalog/Model/Category/Tree.php
index 0a9cb25d7b0e5..2b77e19fe3b8d 100644
--- a/app/code/Magento/Catalog/Model/Category/Tree.php
+++ b/app/code/Magento/Catalog/Model/Category/Tree.php
@@ -5,7 +5,16 @@
*/
namespace Magento\Catalog\Model\Category;
+use Magento\Catalog\Api\Data\CategoryTreeInterface;
+use Magento\Catalog\Api\Data\CategoryTreeInterfaceFactory;
+use Magento\Catalog\Model\Category;
+use Magento\Catalog\Model\ResourceModel\Category\Collection;
+use Magento\Catalog\Model\ResourceModel\Category\TreeFactory;
+use Magento\Framework\App\ObjectManager;
use Magento\Framework\Data\Tree\Node;
+use Magento\Framework\Exception\LocalizedException;
+use Magento\Framework\Exception\NoSuchEntityException;
+use Magento\Store\Model\StoreManagerInterface;
/**
* Retrieve category data represented in tree structure
@@ -18,54 +27,54 @@ class Tree
protected $categoryTree;
/**
- * @var \Magento\Store\Model\StoreManagerInterface
+ * @var StoreManagerInterface
*/
protected $storeManager;
/**
- * @var \Magento\Catalog\Model\ResourceModel\Category\Collection
+ * @var Collection
*/
protected $categoryCollection;
/**
- * @var \Magento\Catalog\Api\Data\CategoryTreeInterfaceFactory
+ * @var CategoryTreeInterfaceFactory
*/
protected $treeFactory;
/**
- * @var \Magento\Catalog\Model\ResourceModel\Category\TreeFactory
+ * @var TreeFactory
*/
private $treeResourceFactory;
/**
* @param \Magento\Catalog\Model\ResourceModel\Category\Tree $categoryTree
- * @param \Magento\Store\Model\StoreManagerInterface $storeManager
- * @param \Magento\Catalog\Model\ResourceModel\Category\Collection $categoryCollection
- * @param \Magento\Catalog\Api\Data\CategoryTreeInterfaceFactory $treeFactory
- * @param \Magento\Catalog\Model\ResourceModel\Category\TreeFactory|null $treeResourceFactory
+ * @param StoreManagerInterface $storeManager
+ * @param Collection $categoryCollection
+ * @param CategoryTreeInterfaceFactory $treeFactory
+ * @param TreeFactory|null $treeResourceFactory
*/
public function __construct(
\Magento\Catalog\Model\ResourceModel\Category\Tree $categoryTree,
- \Magento\Store\Model\StoreManagerInterface $storeManager,
- \Magento\Catalog\Model\ResourceModel\Category\Collection $categoryCollection,
- \Magento\Catalog\Api\Data\CategoryTreeInterfaceFactory $treeFactory,
- \Magento\Catalog\Model\ResourceModel\Category\TreeFactory $treeResourceFactory = null
+ StoreManagerInterface $storeManager,
+ Collection $categoryCollection,
+ CategoryTreeInterfaceFactory $treeFactory,
+ TreeFactory $treeResourceFactory = null
) {
$this->categoryTree = $categoryTree;
$this->storeManager = $storeManager;
$this->categoryCollection = $categoryCollection;
$this->treeFactory = $treeFactory;
- $this->treeResourceFactory = $treeResourceFactory ?? \Magento\Framework\App\ObjectManager::getInstance()
- ->get(\Magento\Catalog\Model\ResourceModel\Category\TreeFactory::class);
+ $this->treeResourceFactory = $treeResourceFactory ?? ObjectManager::getInstance()
+ ->get(TreeFactory::class);
}
/**
* Get root node by category.
*
- * @param \Magento\Catalog\Model\Category|null $category
+ * @param Category|null $category
* @return Node|null
- * @throws \Magento\Framework\Exception\LocalizedException
- * @throws \Magento\Framework\Exception\NoSuchEntityException
+ * @throws LocalizedException
+ * @throws NoSuchEntityException
*/
public function getRootNode($category = null)
{
@@ -86,19 +95,19 @@ public function getRootNode($category = null)
/**
* Get node by category.
*
- * @param \Magento\Catalog\Model\Category $category
+ * @param Category $category
* @return Node
- * @throws \Magento\Framework\Exception\LocalizedException
- * @throws \Magento\Framework\Exception\NoSuchEntityException
+ * @throws LocalizedException
+ * @throws NoSuchEntityException
*/
- protected function getNode(\Magento\Catalog\Model\Category $category)
+ protected function getNode(Category $category)
{
$nodeId = $category->getId();
$categoryTree = $this->treeResourceFactory->create();
$node = $categoryTree->loadNode($nodeId);
$node->loadChildren();
$this->prepareCollection();
- $this->categoryTree->addCollectionData($this->categoryCollection);
+ $categoryTree->addCollectionData($this->categoryCollection);
return $node;
}
@@ -106,8 +115,8 @@ protected function getNode(\Magento\Catalog\Model\Category $category)
* Prepare category collection.
*
* @return void
- * @throws \Magento\Framework\Exception\LocalizedException
- * @throws \Magento\Framework\Exception\NoSuchEntityException
+ * @throws LocalizedException
+ * @throws NoSuchEntityException
*/
protected function prepareCollection()
{
@@ -128,16 +137,16 @@ protected function prepareCollection()
/**
* Get tree by node.
*
- * @param \Magento\Framework\Data\Tree\Node $node
+ * @param Node $node
* @param int $depth
* @param int $currentLevel
- * @return \Magento\Catalog\Api\Data\CategoryTreeInterface
+ * @return CategoryTreeInterface
*/
public function getTree($node, $depth = null, $currentLevel = 0)
{
- /** @var \Magento\Catalog\Api\Data\CategoryTreeInterface[] $children */
+ /** @var CategoryTreeInterface[] $children */
$children = $this->getChildren($node, $depth, $currentLevel);
- /** @var \Magento\Catalog\Api\Data\CategoryTreeInterface $tree */
+ /** @var CategoryTreeInterface $tree */
$tree = $this->treeFactory->create();
$tree->setId($node->getId())
->setParentId($node->getParentId())
@@ -153,10 +162,10 @@ public function getTree($node, $depth = null, $currentLevel = 0)
/**
* Get node children.
*
- * @param \Magento\Framework\Data\Tree\Node $node
+ * @param Node $node
* @param int $depth
* @param int $currentLevel
- * @return \Magento\Catalog\Api\Data\CategoryTreeInterface[]|[]
+ * @return CategoryTreeInterface[]|[]
*/
protected function getChildren($node, $depth, $currentLevel)
{
diff --git a/app/code/Magento/Catalog/Model/CategoryList.php b/app/code/Magento/Catalog/Model/CategoryList.php
index cab8e013d9ba1..cc8920203526f 100644
--- a/app/code/Magento/Catalog/Model/CategoryList.php
+++ b/app/code/Magento/Catalog/Model/CategoryList.php
@@ -3,6 +3,9 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
+
+declare(strict_types=1);
+
namespace Magento\Catalog\Model;
use Magento\Catalog\Api\CategoryListInterface;
@@ -50,7 +53,7 @@ class CategoryList implements CategoryListInterface
* @param JoinProcessorInterface $extensionAttributesJoinProcessor
* @param CategorySearchResultsInterfaceFactory $categorySearchResultsFactory
* @param CategoryRepositoryInterface $categoryRepository
- * @param CollectionProcessorInterface $collectionProcessor
+ * @param CollectionProcessorInterface|null $collectionProcessor
*/
public function __construct(
CollectionFactory $categoryCollectionFactory,
@@ -74,12 +77,13 @@ public function getList(SearchCriteriaInterface $searchCriteria)
/** @var Collection $collection */
$collection = $this->categoryCollectionFactory->create();
$this->extensionAttributesJoinProcessor->process($collection);
-
$this->collectionProcessor->process($searchCriteria, $collection);
$items = [];
- foreach ($collection->getAllIds() as $id) {
- $items[] = $this->categoryRepository->get($id);
+ foreach ($collection->getData() as $categoryData) {
+ $items[] = $this->categoryRepository->get(
+ $categoryData[$collection->getEntity()->getIdFieldName()]
+ );
}
/** @var CategorySearchResultsInterface $searchResult */
diff --git a/app/code/Magento/Catalog/Model/CategoryManagement.php b/app/code/Magento/Catalog/Model/CategoryManagement.php
index fadb4c0c84745..bf6f26ebfb10a 100644
--- a/app/code/Magento/Catalog/Model/CategoryManagement.php
+++ b/app/code/Magento/Catalog/Model/CategoryManagement.php
@@ -1,14 +1,14 @@
$lastId) ? $lastId : $afterId;
}
-
- if (strpos($parentCategory->getPath(), $model->getPath()) === 0) {
+ $parentPath = $parentCategory->getPath();
+ $path = $model->getPath();
+ if ($path && strpos($parentPath, $path) === 0) {
throw new \Magento\Framework\Exception\LocalizedException(
__('Operation do not allow to move a parent category to any of children category')
);
@@ -127,7 +143,7 @@ public function move($categoryId, $parentId, $afterId = null)
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function getCount()
{
diff --git a/app/code/Magento/Catalog/Model/CategoryRepository.php b/app/code/Magento/Catalog/Model/CategoryRepository.php
index 7485d9f6cb247..a8636306f5e5b 100644
--- a/app/code/Magento/Catalog/Model/CategoryRepository.php
+++ b/app/code/Magento/Catalog/Model/CategoryRepository.php
@@ -13,6 +13,8 @@
use Magento\Catalog\Api\Data\CategoryInterface;
/**
+ * Repository for categories.
+ *
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
*/
class CategoryRepository implements \Magento\Catalog\Api\CategoryRepositoryInterface
@@ -70,7 +72,7 @@ public function __construct(
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function save(\Magento\Catalog\Api\Data\CategoryInterface $category)
{
@@ -125,7 +127,7 @@ public function save(\Magento\Catalog\Api\Data\CategoryInterface $category)
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function get($categoryId, $storeId = null)
{
@@ -146,7 +148,7 @@ public function get($categoryId, $storeId = null)
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function delete(\Magento\Catalog\Api\Data\CategoryInterface $category)
{
@@ -167,7 +169,7 @@ public function delete(\Magento\Catalog\Api\Data\CategoryInterface $category)
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function deleteByIdentifier($categoryId)
{
@@ -208,6 +210,8 @@ protected function validateCategory(Category $category)
}
/**
+ * Lazy loader for the converter.
+ *
* @return \Magento\Framework\Api\ExtensibleDataObjectConverter
*
* @deprecated 101.0.0
@@ -222,6 +226,8 @@ private function getExtensibleDataObjectConverter()
}
/**
+ * Lazy loader for the metadata pool.
+ *
* @return \Magento\Framework\EntityManager\MetadataPool
*/
private function getMetadataPool()
diff --git a/app/code/Magento/Catalog/Model/ImageUploader.php b/app/code/Magento/Catalog/Model/ImageUploader.php
index b5ca0895d6d1a..825823276261f 100644
--- a/app/code/Magento/Catalog/Model/ImageUploader.php
+++ b/app/code/Magento/Catalog/Model/ImageUploader.php
@@ -5,6 +5,8 @@
*/
namespace Magento\Catalog\Model;
+use Magento\Framework\File\Uploader;
+
/**
* Catalog image uploader
*/
@@ -199,7 +201,14 @@ public function moveFileFromTmp($imageName)
$baseTmpPath = $this->getBaseTmpPath();
$basePath = $this->getBasePath();
- $baseImagePath = $this->getFilePath($basePath, $imageName);
+ $baseImagePath = $this->getFilePath(
+ $basePath,
+ Uploader::getNewFileName(
+ $this->mediaDirectory->getAbsolutePath(
+ $this->getFilePath($basePath, $imageName)
+ )
+ )
+ );
$baseTmpImagePath = $this->getFilePath($baseTmpPath, $imageName);
try {
diff --git a/app/code/Magento/Catalog/Model/Indexer/Category/Product/Action/Full.php b/app/code/Magento/Catalog/Model/Indexer/Category/Product/Action/Full.php
index eb59acb56c356..eee347c36910d 100644
--- a/app/code/Magento/Catalog/Model/Indexer/Category/Product/Action/Full.php
+++ b/app/code/Magento/Catalog/Model/Indexer/Category/Product/Action/Full.php
@@ -152,7 +152,7 @@ private function switchTables(): void
*
* @return $this
*/
- public function execute(): self
+ public function execute(): Full
{
$this->createTables();
$this->clearReplicaTables();
diff --git a/app/code/Magento/Catalog/Model/Indexer/Product/Flat/TableBuilder.php b/app/code/Magento/Catalog/Model/Indexer/Product/Flat/TableBuilder.php
index e6c098ab0254e..c0722901e3b1c 100644
--- a/app/code/Magento/Catalog/Model/Indexer/Product/Flat/TableBuilder.php
+++ b/app/code/Magento/Catalog/Model/Indexer/Product/Flat/TableBuilder.php
@@ -6,9 +6,12 @@
namespace Magento\Catalog\Model\Indexer\Product\Flat;
use Magento\Catalog\Model\Indexer\Product\Flat\Table\BuilderInterfaceFactory;
+use Magento\Store\Model\Store;
/**
* Class TableBuilder
+ *
+ * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
*/
class TableBuilder
{
@@ -83,6 +86,7 @@ public function build($storeId, $changedIds, $valueFieldSuffix)
//Create list of temporary tables based on available attributes attributes
$valueTables = [];
foreach ($temporaryEavAttributes as $tableName => $columns) {
+ // phpcs:ignore Magento2.Performance.ForeachArrayMerge
$valueTables = array_merge(
$valueTables,
$this->_createTemporaryTable($this->_getTemporaryTableName($tableName), $columns, $valueFieldSuffix)
@@ -272,75 +276,71 @@ protected function _fillTemporaryTable(
$valueFieldSuffix,
$storeId
) {
- $metadata = $this->getMetadataPool()->getMetadata(\Magento\Catalog\Api\Data\ProductInterface::class);
if (!empty($tableColumns)) {
- $columnsChunks = array_chunk(
- $tableColumns,
- Action\Indexer::ATTRIBUTES_CHUNK_SIZE,
- true
- );
+ $columnsChunks = array_chunk($tableColumns, Action\Indexer::ATTRIBUTES_CHUNK_SIZE / 2, true);
+
+ $entityTableName = $this->_productIndexerHelper->getTable('catalog_product_entity');
+ $entityTemporaryTableName = $this->_getTemporaryTableName($entityTableName);
+ $temporaryTableName = $this->_getTemporaryTableName($tableName);
+ $temporaryValueTableName = $temporaryTableName . $valueFieldSuffix;
+ $attributeOptionValueTableName = $this->_productIndexerHelper->getTable('eav_attribute_option_value');
+
+ $flatColumns = $this->_productIndexerHelper->getFlatColumns();
+ $defaultStoreId = Store::DEFAULT_STORE_ID;
+ $linkField = $this->getMetadataPool()
+ ->getMetadata(\Magento\Catalog\Api\Data\ProductInterface::class)
+ ->getLinkField();
+
foreach ($columnsChunks as $columnsList) {
$select = $this->_connection->select();
$selectValue = $this->_connection->select();
- $entityTableName = $this->_getTemporaryTableName(
- $this->_productIndexerHelper->getTable('catalog_product_entity')
- );
- $temporaryTableName = $this->_getTemporaryTableName($tableName);
- $temporaryValueTableName = $temporaryTableName . $valueFieldSuffix;
- $keyColumn = array_unique([$metadata->getLinkField(), 'entity_id']);
+ $keyColumn = array_unique([$linkField, 'entity_id']);
+ // phpcs:ignore Magento2.Performance.ForeachArrayMerge
$columns = array_merge($keyColumn, array_keys($columnsList));
$valueColumns = $keyColumn;
- $flatColumns = $this->_productIndexerHelper->getFlatColumns();
$iterationNum = 1;
- $select->from(['et' => $entityTableName], $keyColumn)
- ->join(
- ['e' => $this->resource->getTableName('catalog_product_entity')],
- 'e.entity_id = et.entity_id',
- []
- );
+ $select->from(['et' => $entityTemporaryTableName], $keyColumn)
+ ->join(['e' => $entityTableName], 'e.entity_id = et.entity_id', []);
$selectValue->from(['e' => $temporaryTableName], $keyColumn);
/** @var $attribute \Magento\Catalog\Model\ResourceModel\Eav\Attribute */
foreach ($columnsList as $columnName => $attribute) {
$countTableName = 't' . ($iterationNum++);
- $joinCondition = sprintf(
- 'e.%3$s = %1$s.%3$s' .
- ' AND %1$s.attribute_id = %2$d' .
- ' AND (%1$s.store_id = %4$d' .
- ' OR %1$s.store_id = 0)',
- $countTableName,
- $attribute->getId(),
- $metadata->getLinkField(),
- $storeId
- );
-
+ $joinCondition = 'e.%3$s = %1$s.%3$s AND %1$s.attribute_id = %2$d AND %1$s.store_id = %4$d';
$select->joinLeft(
[$countTableName => $tableName],
- $joinCondition,
- [$columnName => 'value']
+ sprintf($joinCondition, $countTableName, $attribute->getId(), $linkField, $defaultStoreId),
+ []
+ )->joinLeft(
+ ['s' . $countTableName => $tableName],
+ sprintf($joinCondition, 's' . $countTableName, $attribute->getId(), $linkField, $storeId),
+ []
);
+ $columnValue = $this->_connection->getIfNullSql(
+ 's' . $countTableName . '.value',
+ $countTableName . '.value'
+ );
+ $select->columns([$columnName => $columnValue]);
+
if ($attribute->getFlatUpdateSelect($storeId) instanceof \Magento\Framework\DB\Select) {
$attributeCode = $attribute->getAttributeCode();
$columnValueName = $attributeCode . $valueFieldSuffix;
if (isset($flatColumns[$columnValueName])) {
- $valueJoinCondition = sprintf(
- 'e.%1$s = %2$s.option_id AND (%2$s.store_id = %3$d OR %2$s.store_id = 0)',
- $attributeCode,
- $countTableName,
- $storeId
- );
+ $valueJoinCondition = 'e.%1$s = %2$s.option_id AND %2$s.store_id = %3$d';
$selectValue->joinLeft(
- [
- $countTableName => $this->_productIndexerHelper->getTable(
- 'eav_attribute_option_value'
- ),
- ],
- $valueJoinCondition,
- [$columnValueName => $countTableName . '.value']
+ [$countTableName => $attributeOptionValueTableName],
+ sprintf($valueJoinCondition, $attributeCode, $countTableName, $defaultStoreId),
+ []
+ )->joinLeft(
+ ['s' . $countTableName => $attributeOptionValueTableName],
+ sprintf($valueJoinCondition, $attributeCode, 's' . $countTableName, $storeId),
+ []
);
+
+ $selectValue->columns([$columnValueName => $columnValue]);
$valueColumns[] = $columnValueName;
}
}
diff --git a/app/code/Magento/Catalog/Model/Indexer/Product/Price.php b/app/code/Magento/Catalog/Model/Indexer/Product/Price.php
index c9936f7e6c691..b703ba82a4052 100644
--- a/app/code/Magento/Catalog/Model/Indexer/Product/Price.php
+++ b/app/code/Magento/Catalog/Model/Indexer/Product/Price.php
@@ -5,43 +5,56 @@
*/
namespace Magento\Catalog\Model\Indexer\Product;
+use Magento\Catalog\Model\Category as CategoryModel;
+use Magento\Catalog\Model\Indexer\Product\Price\Action\Full as FullAction;
+use Magento\Catalog\Model\Indexer\Product\Price\Action\Row as RowAction;
+use Magento\Catalog\Model\Indexer\Product\Price\Action\Rows as RowsAction;
+use Magento\Catalog\Model\Product as ProductModel;
+use Magento\Framework\Indexer\ActionInterface as IndexerActionInterface;
use Magento\Framework\Indexer\CacheContext;
+use Magento\Framework\Mview\ActionInterface as MviewActionInterface;
-class Price implements \Magento\Framework\Indexer\ActionInterface, \Magento\Framework\Mview\ActionInterface
+/**
+ * Price indexer
+ */
+class Price implements IndexerActionInterface, MviewActionInterface
{
/**
- * @var \Magento\Catalog\Model\Indexer\Product\Price\Action\Row
+ * @var RowAction
*/
protected $_productPriceIndexerRow;
/**
- * @var \Magento\Catalog\Model\Indexer\Product\Price\Action\Rows
+ * @var RowsAction
*/
protected $_productPriceIndexerRows;
/**
- * @var \Magento\Catalog\Model\Indexer\Product\Price\Action\Full
+ * @var FullAction
*/
protected $_productPriceIndexerFull;
/**
- * @var \Magento\Framework\Indexer\CacheContext
+ * @var CacheContext
*/
private $cacheContext;
/**
- * @param Price\Action\Row $productPriceIndexerRow
- * @param Price\Action\Rows $productPriceIndexerRows
- * @param Price\Action\Full $productPriceIndexerFull
+ * @param RowAction $productPriceIndexerRow
+ * @param RowsAction $productPriceIndexerRows
+ * @param FullAction $productPriceIndexerFull
+ * @param CacheContext $cacheContext
*/
public function __construct(
- \Magento\Catalog\Model\Indexer\Product\Price\Action\Row $productPriceIndexerRow,
- \Magento\Catalog\Model\Indexer\Product\Price\Action\Rows $productPriceIndexerRows,
- \Magento\Catalog\Model\Indexer\Product\Price\Action\Full $productPriceIndexerFull
+ RowAction $productPriceIndexerRow,
+ RowsAction $productPriceIndexerRows,
+ FullAction $productPriceIndexerFull,
+ CacheContext $cacheContext
) {
$this->_productPriceIndexerRow = $productPriceIndexerRow;
$this->_productPriceIndexerRows = $productPriceIndexerRows;
$this->_productPriceIndexerFull = $productPriceIndexerFull;
+ $this->cacheContext = $cacheContext;
}
/**
@@ -53,7 +66,7 @@ public function __construct(
public function execute($ids)
{
$this->_productPriceIndexerRows->execute($ids);
- $this->getCacheContext()->registerEntities(\Magento\Catalog\Model\Product::CACHE_TAG, $ids);
+ $this->cacheContext->registerEntities(ProductModel::CACHE_TAG, $ids);
}
/**
@@ -64,10 +77,10 @@ public function execute($ids)
public function executeFull()
{
$this->_productPriceIndexerFull->execute();
- $this->getCacheContext()->registerTags(
+ $this->cacheContext->registerTags(
[
- \Magento\Catalog\Model\Category::CACHE_TAG,
- \Magento\Catalog\Model\Product::CACHE_TAG
+ CategoryModel::CACHE_TAG,
+ ProductModel::CACHE_TAG
]
);
}
@@ -81,6 +94,7 @@ public function executeFull()
public function executeList(array $ids)
{
$this->_productPriceIndexerRows->execute($ids);
+ $this->cacheContext->registerEntities(ProductModel::CACHE_TAG, $ids);
}
/**
@@ -92,20 +106,6 @@ public function executeList(array $ids)
public function executeRow($id)
{
$this->_productPriceIndexerRow->execute($id);
- }
-
- /**
- * Get cache context
- *
- * @return \Magento\Framework\Indexer\CacheContext
- * @deprecated 100.0.11
- */
- protected function getCacheContext()
- {
- if (!($this->cacheContext instanceof CacheContext)) {
- return \Magento\Framework\App\ObjectManager::getInstance()->get(CacheContext::class);
- } else {
- return $this->cacheContext;
- }
+ $this->cacheContext->registerEntities(ProductModel::CACHE_TAG, [$id]);
}
}
diff --git a/app/code/Magento/Catalog/Model/Indexer/Product/Price/Action/Full.php b/app/code/Magento/Catalog/Model/Indexer/Product/Price/Action/Full.php
index 858eba3ab217a..b30c85cfc52f0 100644
--- a/app/code/Magento/Catalog/Model/Indexer/Product/Price/Action/Full.php
+++ b/app/code/Magento/Catalog/Model/Indexer/Product/Price/Action/Full.php
@@ -279,6 +279,7 @@ private function getBatchesForIndexer(string $typeId): BatchIterator
$select = $connection->select();
$select->distinct(true);
$select->from(['e' => $entityMetadata->getEntityTable()], $entityMetadata->getIdentifierField());
+ $select->where('type_id = ?', $typeId);
return $this->batchQueryGenerator->generate(
$this->getProductMetaData()->getIdentifierField(),
diff --git a/app/code/Magento/Catalog/Model/Layer/FilterList.php b/app/code/Magento/Catalog/Model/Layer/FilterList.php
index 9d7b71c981c6b..b8e9b8ad4aaa5 100644
--- a/app/code/Magento/Catalog/Model/Layer/FilterList.php
+++ b/app/code/Magento/Catalog/Model/Layer/FilterList.php
@@ -3,9 +3,13 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
+declare(strict_types=1);
namespace Magento\Catalog\Model\Layer;
+/**
+ * Layer navigation filters
+ */
class FilterList
{
const CATEGORY_FILTER = 'category';
@@ -106,9 +110,9 @@ protected function getAttributeFilterClass(\Magento\Catalog\Model\ResourceModel\
{
$filterClassName = $this->filterTypes[self::ATTRIBUTE_FILTER];
- if ($attribute->getAttributeCode() == 'price') {
+ if ($attribute->getFrontendInput() === 'price') {
$filterClassName = $this->filterTypes[self::PRICE_FILTER];
- } elseif ($attribute->getBackendType() == 'decimal') {
+ } elseif ($attribute->getBackendType() === 'decimal') {
$filterClassName = $this->filterTypes[self::DECIMAL_FILTER];
}
diff --git a/app/code/Magento/Catalog/Model/Plugin/SetPageLayoutDefaultValue.php b/app/code/Magento/Catalog/Model/Plugin/SetPageLayoutDefaultValue.php
new file mode 100644
index 0000000000000..b812de1dfc2ae
--- /dev/null
+++ b/app/code/Magento/Catalog/Model/Plugin/SetPageLayoutDefaultValue.php
@@ -0,0 +1,50 @@
+defaultValue = $defaultValue;
+ }
+
+ /**
+ * Sets the default value for Category Design Layout in data provider if provided
+ *
+ * @param DataProvider $subject
+ * @param array $result
+ * @return array
+ *
+ * @throws NoSuchEntityException
+ */
+ public function afterGetDefaultMetaData(DataProvider $subject, array $result): array
+ {
+ $currentCategory = $subject->getCurrentCategory();
+
+ if ($currentCategory && !$currentCategory->getId() && array_key_exists('page_layout', $result)) {
+ $result['page_layout']['default'] = $this->defaultValue ?: null;
+ }
+
+ return $result;
+ }
+}
diff --git a/app/code/Magento/Catalog/Model/Product.php b/app/code/Magento/Catalog/Model/Product.php
index 61544f8fb5766..1b7552c82276d 100644
--- a/app/code/Magento/Catalog/Model/Product.php
+++ b/app/code/Magento/Catalog/Model/Product.php
@@ -5,6 +5,7 @@
*/
namespace Magento\Catalog\Model;
+use Magento\Authorization\Model\UserContextInterface;
use Magento\Catalog\Api\CategoryRepositoryInterface;
use Magento\Catalog\Api\Data\ProductAttributeMediaGalleryEntryInterface;
use Magento\Catalog\Api\Data\ProductInterface;
@@ -14,6 +15,7 @@
use Magento\Framework\Api\AttributeValueFactory;
use Magento\Framework\App\Filesystem\DirectoryList;
use Magento\Framework\App\ObjectManager;
+use Magento\Framework\AuthorizationInterface;
use Magento\Framework\DataObject\IdentityInterface;
use Magento\Framework\Pricing\SaleableInterface;
@@ -353,6 +355,16 @@ class Product extends \Magento\Catalog\Model\AbstractModel implements
*/
private $filterCustomAttribute;
+ /**
+ * @var UserContextInterface
+ */
+ private $userContext;
+
+ /**
+ * @var AuthorizationInterface
+ */
+ private $authorization;
+
/**
* @param \Magento\Framework\Model\Context $context
* @param \Magento\Framework\Registry $registry
@@ -485,6 +497,7 @@ protected function _construct()
$this->_init(\Magento\Catalog\Model\ResourceModel\Product::class);
}
+ // phpcs:disable Generic.CodeAnalysis.UselessOverridingMethod
/**
* Get resource instance
* phpcs:disable Generic.CodeAnalysis.UselessOverridingMethod
@@ -497,6 +510,7 @@ protected function _getResource()
{
return parent::_getResource();
}
+ // phpcs:enable
/**
* Get a list of custom attribute codes that belongs to product attribute set.
@@ -818,12 +832,14 @@ public function getStoreIds()
if (!$this->hasStoreIds()) {
$storeIds = [];
if ($websiteIds = $this->getWebsiteIds()) {
- if ($this->_storeManager->isSingleStoreMode()) {
+ if (!$this->isObjectNew() && $this->_storeManager->isSingleStoreMode()) {
$websiteIds = array_keys($websiteIds);
}
foreach ($websiteIds as $websiteId) {
$websiteStores = $this->_storeManager->getWebsite($websiteId)->getStoreIds();
- $storeIds = array_merge($storeIds, $websiteStores);
+ foreach ($websiteStores as $websiteStore) {
+ $storeIds []= $websiteStore;
+ }
}
}
$this->setStoreIds($storeIds);
@@ -858,6 +874,34 @@ public function getAttributes($groupId = null, $skipSuper = false)
return $attributes;
}
+ /**
+ * Get user context.
+ *
+ * @return UserContextInterface
+ */
+ private function getUserContext(): UserContextInterface
+ {
+ if (!$this->userContext) {
+ $this->userContext = ObjectManager::getInstance()->get(UserContextInterface::class);
+ }
+
+ return $this->userContext;
+ }
+
+ /**
+ * Get authorization service.
+ *
+ * @return AuthorizationInterface
+ */
+ private function getAuthorization(): AuthorizationInterface
+ {
+ if (!$this->authorization) {
+ $this->authorization = ObjectManager::getInstance()->get(AuthorizationInterface::class);
+ }
+
+ return $this->authorization;
+ }
+
/**
* Check product options and type options and save them, too
*
@@ -875,6 +919,22 @@ public function beforeSave()
$this->getTypeInstance()->beforeSave($this);
+ //Validate changing of design.
+ $userType = $this->getUserContext()->getUserType();
+ if ((
+ $userType === UserContextInterface::USER_TYPE_ADMIN
+ || $userType === UserContextInterface::USER_TYPE_INTEGRATION
+ )
+ && !$this->getAuthorization()->isAllowed('Magento_Catalog::edit_product_design')
+ ) {
+ $this->setData('custom_design', $this->getOrigData('custom_design'));
+ $this->setData('page_layout', $this->getOrigData('page_layout'));
+ $this->setData('options_container', $this->getOrigData('options_container'));
+ $this->setData('custom_layout_update', $this->getOrigData('custom_layout_update'));
+ $this->setData('custom_design_from', $this->getOrigData('custom_design_from'));
+ $this->setData('custom_design_to', $this->getOrigData('custom_design_to'));
+ }
+
$hasOptions = false;
$hasRequiredOptions = false;
@@ -1167,7 +1227,8 @@ public function getFormattedPrice()
/**
* Get formatted by currency product price
*
- * @return array|double*
+ * @return array|double
+ *
* @deprecated
* @see getFormattedPrice()
*/
diff --git a/app/code/Magento/Catalog/Model/Product/Attribute/OptionManagement.php b/app/code/Magento/Catalog/Model/Product/Attribute/OptionManagement.php
index 8b638feafaafc..b797308c30fb0 100644
--- a/app/code/Magento/Catalog/Model/Product/Attribute/OptionManagement.php
+++ b/app/code/Magento/Catalog/Model/Product/Attribute/OptionManagement.php
@@ -43,17 +43,6 @@ public function getItems($attributeCode)
*/
public function add($attributeCode, $option)
{
- /** @var \Magento\Eav\Api\Data\AttributeOptionInterface[] $currentOptions */
- $currentOptions = $this->getItems($attributeCode);
- if (is_array($currentOptions)) {
- array_walk($currentOptions, function (&$attributeOption) {
- /** @var \Magento\Eav\Api\Data\AttributeOptionInterface $attributeOption */
- $attributeOption = $attributeOption->getLabel();
- });
- if (in_array($option->getLabel(), $currentOptions, true)) {
- return false;
- }
- }
return $this->eavOptionManagement->add(
\Magento\Catalog\Api\Data\ProductAttributeInterface::ENTITY_TYPE_CODE,
$attributeCode,
diff --git a/app/code/Magento/Catalog/Model/Product/Gallery/GalleryManagement.php b/app/code/Magento/Catalog/Model/Product/Gallery/GalleryManagement.php
index c993e51c8bc09..9e5cf084c25a1 100644
--- a/app/code/Magento/Catalog/Model/Product/Gallery/GalleryManagement.php
+++ b/app/code/Magento/Catalog/Model/Product/Gallery/GalleryManagement.php
@@ -71,10 +71,8 @@ public function create($sku, ProductAttributeMediaGalleryEntryInterface $entry)
$product->setMediaGalleryEntries($existingMediaGalleryEntries);
try {
$product = $this->productRepository->save($product);
- // phpcs:ignore Magento2.Exceptions.ThrowCatch
} catch (InputException $inputException) {
throw $inputException;
- // phpcs:ignore Magento2.Exceptions.ThrowCatch
} catch (\Exception $e) {
throw new StateException(__("The product can't be saved."));
}
diff --git a/app/code/Magento/Catalog/Model/Product/Image/ParamsBuilder.php b/app/code/Magento/Catalog/Model/Product/Image/ParamsBuilder.php
index 4a55714a27ec5..4b7a623b15c19 100644
--- a/app/code/Magento/Catalog/Model/Product/Image/ParamsBuilder.php
+++ b/app/code/Magento/Catalog/Model/Product/Image/ParamsBuilder.php
@@ -68,11 +68,12 @@ public function __construct(
* Build image params
*
* @param array $imageArguments
+ * @param int $scopeId
* @return array
* @SuppressWarnings(PHPMD.NPathComplexity)
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
*/
- public function build(array $imageArguments): array
+ public function build(array $imageArguments, int $scopeId = null): array
{
$miscParams = [
'image_type' => $imageArguments['type'] ?? null,
@@ -81,7 +82,7 @@ public function build(array $imageArguments): array
];
$overwritten = $this->overwriteDefaultValues($imageArguments);
- $watermark = isset($miscParams['image_type']) ? $this->getWatermark($miscParams['image_type']) : [];
+ $watermark = isset($miscParams['image_type']) ? $this->getWatermark($miscParams['image_type'], $scopeId) : [];
return array_merge($miscParams, $overwritten, $watermark);
}
@@ -117,27 +118,32 @@ private function overwriteDefaultValues(array $imageArguments): array
* Get watermark
*
* @param string $type
+ * @param int $scopeId
* @return array
*/
- private function getWatermark(string $type): array
+ private function getWatermark(string $type, int $scopeId = null): array
{
$file = $this->scopeConfig->getValue(
"design/watermark/{$type}_image",
- ScopeInterface::SCOPE_STORE
+ ScopeInterface::SCOPE_STORE,
+ $scopeId
);
if ($file) {
$size = $this->scopeConfig->getValue(
"design/watermark/{$type}_size",
- ScopeInterface::SCOPE_STORE
+ ScopeInterface::SCOPE_STORE,
+ $scopeId
);
$opacity = $this->scopeConfig->getValue(
"design/watermark/{$type}_imageOpacity",
- ScopeInterface::SCOPE_STORE
+ ScopeInterface::SCOPE_STORE,
+ $scopeId
);
$position = $this->scopeConfig->getValue(
"design/watermark/{$type}_position",
- ScopeInterface::SCOPE_STORE
+ ScopeInterface::SCOPE_STORE,
+ $scopeId
);
$width = !empty($size['width']) ? $size['width'] : null;
$height = !empty($size['height']) ? $size['height'] : null;
diff --git a/app/code/Magento/Catalog/Model/Product/Option.php b/app/code/Magento/Catalog/Model/Product/Option.php
index b4a4ec08d390d..4f730834412e4 100644
--- a/app/code/Magento/Catalog/Model/Product/Option.php
+++ b/app/code/Magento/Catalog/Model/Product/Option.php
@@ -167,6 +167,8 @@ protected function _getResource()
}
/**
+ * Construct function
+ *
* @return void
*/
protected function _construct()
@@ -215,6 +217,8 @@ public function hasValues($type = null)
}
/**
+ * Get values
+ *
* @return ProductCustomOptionValuesInterface[]|null
*/
public function getValues()
@@ -345,7 +349,8 @@ public function groupFactory($type)
}
/**
- * {@inheritdoc}
+ * @inheritdoc
+ *
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
* @since 101.0.0
*/
@@ -396,6 +401,8 @@ public function beforeSave()
}
/**
+ * After save
+ *
* @return \Magento\Framework\Model\AbstractModel
* @throws \Magento\Framework\Exception\LocalizedException
*/
@@ -424,7 +431,8 @@ public function afterSave()
/**
* Return price. If $flag is true and price is percent
- * return converted percent to price
+ *
+ * Return converted percent to price
*
* @param bool $flag
* @return float
@@ -555,7 +563,7 @@ protected function _clearReferences()
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
protected function _getValidationRulesBeforeSave()
{
@@ -649,6 +657,8 @@ public function getSku()
}
/**
+ * Get file extension
+ *
* @return string|null
*/
public function getFileExtension()
@@ -657,6 +667,8 @@ public function getFileExtension()
}
/**
+ * Get Max Characters
+ *
* @return int|null
*/
public function getMaxCharacters()
@@ -665,6 +677,8 @@ public function getMaxCharacters()
}
/**
+ * Get image size X
+ *
* @return int|null
*/
public function getImageSizeX()
@@ -673,6 +687,8 @@ public function getImageSizeX()
}
/**
+ * Get image size Y
+ *
* @return int|null
*/
public function getImageSizeY()
@@ -780,6 +796,8 @@ public function setSku($sku)
}
/**
+ * Set File Extension
+ *
* @param string $fileExtension
* @return $this
*/
@@ -789,6 +807,8 @@ public function setFileExtension($fileExtension)
}
/**
+ * Set Max Characters
+ *
* @param int $maxCharacters
* @return $this
*/
@@ -798,6 +818,8 @@ public function setMaxCharacters($maxCharacters)
}
/**
+ * Set Image Size X
+ *
* @param int $imageSizeX
* @return $this
*/
@@ -807,6 +829,8 @@ public function setImageSizeX($imageSizeX)
}
/**
+ * Set Image Size Y
+ *
* @param int $imageSizeY
* @return $this
*/
@@ -816,6 +840,8 @@ public function setImageSizeY($imageSizeY)
}
/**
+ * Set value
+ *
* @param ProductCustomOptionValuesInterface[] $values
* @return $this
*/
@@ -826,7 +852,7 @@ public function setValues(array $values = null)
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*
* @return \Magento\Catalog\Api\Data\ProductCustomOptionExtensionInterface|null
*/
@@ -852,6 +878,8 @@ public function getRegularPrice()
}
/**
+ * Get Product Option Collection
+ *
* @param Product $product
* @return \Magento\Framework\Model\ResourceModel\Db\Collection\AbstractCollection
*/
@@ -882,7 +910,7 @@ public function getProductOptionCollection(Product $product)
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*
* @param \Magento\Catalog\Api\Data\ProductCustomOptionExtensionInterface $extensionAttributes
* @return $this
@@ -894,6 +922,8 @@ public function setExtensionAttributes(
}
/**
+ * Get option repository
+ *
* @return Option\Repository
*/
private function getOptionRepository()
@@ -906,6 +936,8 @@ private function getOptionRepository()
}
/**
+ * Get metadata pool
+ *
* @return \Magento\Framework\EntityManager\MetadataPool
*/
private function getMetadataPool()
@@ -931,7 +963,7 @@ private function cleanFileExtensions()
preg_match_all('/(?[a-z0-9]+)/i', strtolower($rawExtensions), $matches);
if (!empty($matches)) {
$extensions = implode(', ', array_unique($matches['extensions']));
+ $this->setFileExtension($extensions);
}
- $this->setFileExtension($extensions);
}
}
diff --git a/app/code/Magento/Catalog/Model/Product/Option/Type/Select.php b/app/code/Magento/Catalog/Model/Product/Option/Type/Select.php
index 31e178f0bd9b4..d2766b1bbb054 100644
--- a/app/code/Magento/Catalog/Model/Product/Option/Type/Select.php
+++ b/app/code/Magento/Catalog/Model/Product/Option/Type/Select.php
@@ -84,6 +84,9 @@ public function validateUserValue($values)
);
}
if (!$this->_isSingleSelection()) {
+ if (is_string($value)) {
+ $value = explode(',', $value);
+ }
$valuesCollection = $option->getOptionValuesByOptionId($value, $this->getProduct()->getStoreId())->load();
$valueCount = is_array($value) ? count($value) : 0;
if ($valuesCollection->count() != $valueCount) {
diff --git a/app/code/Magento/Catalog/Model/Product/Price/TierPriceStorage.php b/app/code/Magento/Catalog/Model/Product/Price/TierPriceStorage.php
index 3ee064670a460..36ef1826462b0 100644
--- a/app/code/Magento/Catalog/Model/Product/Price/TierPriceStorage.php
+++ b/app/code/Magento/Catalog/Model/Product/Price/TierPriceStorage.php
@@ -7,11 +7,15 @@
namespace Magento\Catalog\Model\Product\Price;
use Magento\Catalog\Api\Data\TierPriceInterface;
+use Magento\Catalog\Api\TierPriceStorageInterface;
+use Magento\Catalog\Model\Indexer\Product\Price\Processor as PriceIndexerProcessor;
+use Magento\Catalog\Model\Product\Price\Validation\TierPriceValidator;
+use Magento\Catalog\Model\ProductIdLocatorInterface;
/**
* Tier price storage.
*/
-class TierPriceStorage implements \Magento\Catalog\Api\TierPriceStorageInterface
+class TierPriceStorage implements TierPriceStorageInterface
{
/**
* Tier price resource model.
@@ -23,7 +27,7 @@ class TierPriceStorage implements \Magento\Catalog\Api\TierPriceStorageInterface
/**
* Tier price validator.
*
- * @var \Magento\Catalog\Model\Product\Price\Validation\TierPriceValidator
+ * @var TierPriceValidator
*/
private $tierPriceValidator;
@@ -35,65 +39,38 @@ class TierPriceStorage implements \Magento\Catalog\Api\TierPriceStorageInterface
private $tierPriceFactory;
/**
- * Price indexer.
+ * Price index processor.
*
- * @var \Magento\Catalog\Model\Indexer\Product\Price
+ * @var PriceIndexerProcessor
*/
- private $priceIndexer;
+ private $priceIndexProcessor;
/**
* Product ID locator.
*
- * @var \Magento\Catalog\Model\ProductIdLocatorInterface
+ * @var ProductIdLocatorInterface
*/
private $productIdLocator;
- /**
- * Page cache config.
- *
- * @var \Magento\PageCache\Model\Config
- */
- private $config;
-
- /**
- * Cache type list.
- *
- * @var \Magento\Framework\App\Cache\TypeListInterface
- */
- private $typeList;
-
- /**
- * Indexer chunk value.
- *
- * @var int
- */
- private $indexerChunkValue = 500;
-
/**
* @param TierPricePersistence $tierPricePersistence
- * @param \Magento\Catalog\Model\Product\Price\Validation\TierPriceValidator $tierPriceValidator
+ * @param TierPriceValidator $tierPriceValidator
* @param TierPriceFactory $tierPriceFactory
- * @param \Magento\Catalog\Model\Indexer\Product\Price $priceIndexer
- * @param \Magento\Catalog\Model\ProductIdLocatorInterface $productIdLocator
- * @param \Magento\PageCache\Model\Config $config
- * @param \Magento\Framework\App\Cache\TypeListInterface $typeList
+ * @param PriceIndexerProcessor $priceIndexProcessor
+ * @param ProductIdLocatorInterface $productIdLocator
*/
public function __construct(
TierPricePersistence $tierPricePersistence,
- \Magento\Catalog\Model\Product\Price\Validation\TierPriceValidator $tierPriceValidator,
+ TierPriceValidator $tierPriceValidator,
TierPriceFactory $tierPriceFactory,
- \Magento\Catalog\Model\Indexer\Product\Price $priceIndexer,
- \Magento\Catalog\Model\ProductIdLocatorInterface $productIdLocator,
- \Magento\PageCache\Model\Config $config,
- \Magento\Framework\App\Cache\TypeListInterface $typeList
+ PriceIndexerProcessor $priceIndexProcessor,
+ ProductIdLocatorInterface $productIdLocator
) {
$this->tierPricePersistence = $tierPricePersistence;
$this->tierPriceValidator = $tierPriceValidator;
$this->tierPriceFactory = $tierPriceFactory;
- $this->priceIndexer = $priceIndexer;
+ $this->priceIndexProcessor = $priceIndexProcessor;
$this->productIdLocator = $productIdLocator;
- $this->config = $config;
- $this->typeList = $typeList;
}
/**
@@ -113,16 +90,18 @@ public function update(array $prices)
{
$affectedIds = $this->retrieveAffectedProductIdsForPrices($prices);
$skus = array_unique(
- array_map(function ($price) {
- return $price->getSku();
- }, $prices)
+ array_map(
+ function (TierPriceInterface $price) {
+ return $price->getSku();
+ },
+ $prices
+ )
);
$result = $this->tierPriceValidator->retrieveValidationResult($prices, $this->getExistingPrices($skus, true));
$prices = $this->removeIncorrectPrices($prices, $result->getFailedRowIds());
$formattedPrices = $this->retrieveFormattedPrices($prices);
$this->tierPricePersistence->update($formattedPrices);
$this->reindexPrices($affectedIds);
- $this->invalidateFullPageCache();
return $result->getFailedItems();
}
@@ -138,7 +117,6 @@ public function replace(array $prices)
$formattedPrices = $this->retrieveFormattedPrices($prices);
$this->tierPricePersistence->replace($formattedPrices, $affectedIds);
$this->reindexPrices($affectedIds);
- $this->invalidateFullPageCache();
return $result->getFailedItems();
}
@@ -154,7 +132,6 @@ public function delete(array $prices)
$priceIds = $this->retrieveAffectedPriceIds($prices);
$this->tierPricePersistence->delete($priceIds);
$this->reindexPrices($affectedIds);
- $this->invalidateFullPageCache();
return $result->getFailedItems();
}
@@ -166,7 +143,7 @@ public function delete(array $prices)
* @param bool $groupBySku [optional]
* @return array
*/
- private function getExistingPrices(array $skus, $groupBySku = false)
+ private function getExistingPrices(array $skus, bool $groupBySku = false): array
{
$ids = $this->retrieveAffectedIds($skus);
$rawPrices = $this->tierPricePersistence->get($ids);
@@ -194,7 +171,7 @@ private function getExistingPrices(array $skus, $groupBySku = false)
* @param array $prices
* @return array
*/
- private function retrieveFormattedPrices(array $prices)
+ private function retrieveFormattedPrices(array $prices): array
{
$formattedPrices = [];
@@ -215,12 +192,15 @@ private function retrieveFormattedPrices(array $prices)
* @param TierPriceInterface[] $prices
* @return array
*/
- private function retrieveAffectedProductIdsForPrices(array $prices)
+ private function retrieveAffectedProductIdsForPrices(array $prices): array
{
$skus = array_unique(
- array_map(function ($price) {
- return $price->getSku();
- }, $prices)
+ array_map(
+ function (TierPriceInterface $price) {
+ return $price->getSku();
+ },
+ $prices
+ )
);
return $this->retrieveAffectedIds($skus);
@@ -232,15 +212,15 @@ private function retrieveAffectedProductIdsForPrices(array $prices)
* @param array $skus
* @return array
*/
- private function retrieveAffectedIds(array $skus)
+ private function retrieveAffectedIds(array $skus): array
{
$affectedIds = [];
foreach ($this->productIdLocator->retrieveProductIdsBySkus($skus) as $productId) {
- $affectedIds = array_merge($affectedIds, array_keys($productId));
+ $affectedIds[] = array_keys($productId);
}
- return array_unique($affectedIds);
+ return $affectedIds ? array_unique(array_merge(...$affectedIds)) : [];
}
/**
@@ -249,7 +229,7 @@ private function retrieveAffectedIds(array $skus)
* @param array $prices
* @return array
*/
- private function retrieveAffectedPriceIds(array $prices)
+ private function retrieveAffectedPriceIds(array $prices): array
{
$affectedIds = $this->retrieveAffectedProductIdsForPrices($prices);
$formattedPrices = $this->retrieveFormattedPrices($prices);
@@ -270,7 +250,7 @@ private function retrieveAffectedPriceIds(array $prices)
* @param array $existingPrices
* @return int|null
*/
- private function retrievePriceId(array $price, array $existingPrices)
+ private function retrievePriceId(array $price, array $existingPrices): ?int
{
$linkField = $this->tierPricePersistence->getEntityLinkField();
@@ -281,7 +261,7 @@ private function retrievePriceId(array $price, array $existingPrices)
&& $this->isCorrectPriceValue($existingPrice, $price)
&& $existingPrice[$linkField] == $price[$linkField]
) {
- return $existingPrice['value_id'];
+ return (int) $existingPrice['value_id'];
}
}
@@ -295,7 +275,7 @@ private function retrievePriceId(array $price, array $existingPrices)
* @param array $price
* @return bool
*/
- private function isCorrectPriceValue(array $existingPrice, array $price)
+ private function isCorrectPriceValue(array $existingPrice, array $price): bool
{
return ($existingPrice['value'] != 0 && $existingPrice['value'] == $price['value'])
|| ($existingPrice['percentage_value'] !== null
@@ -308,7 +288,7 @@ private function isCorrectPriceValue(array $existingPrice, array $price)
* @param array $skus
* @return array
*/
- private function buildSkuByIdLookup($skus)
+ private function buildSkuByIdLookup(array $skus): array
{
$lookup = [];
foreach ($this->productIdLocator->retrieveProductIdsBySkus($skus) as $sku => $ids) {
@@ -320,28 +300,16 @@ private function buildSkuByIdLookup($skus)
return $lookup;
}
- /**
- * Invalidate full page cache.
- *
- * @return void
- */
- private function invalidateFullPageCache()
- {
- if ($this->config->isEnabled()) {
- $this->typeList->invalidate('full_page');
- }
- }
-
/**
* Reindex prices.
*
* @param array $ids
* @return void
*/
- private function reindexPrices(array $ids)
+ private function reindexPrices(array $ids): void
{
- foreach (array_chunk($ids, $this->indexerChunkValue) as $affectedIds) {
- $this->priceIndexer->execute($affectedIds);
+ if (!empty($ids)) {
+ $this->priceIndexProcessor->reindexList($ids);
}
}
@@ -352,7 +320,7 @@ private function reindexPrices(array $ids)
* @param array $ids
* @return array
*/
- private function removeIncorrectPrices(array $prices, array $ids)
+ private function removeIncorrectPrices(array $prices, array $ids): array
{
foreach ($ids as $id) {
unset($prices[$id]);
diff --git a/app/code/Magento/Catalog/Model/Product/SalabilityChecker.php b/app/code/Magento/Catalog/Model/Product/SalabilityChecker.php
new file mode 100644
index 0000000000000..404760a51eff5
--- /dev/null
+++ b/app/code/Magento/Catalog/Model/Product/SalabilityChecker.php
@@ -0,0 +1,57 @@
+productRepository = $productRepository;
+ $this->storeManager = $storeManager;
+ }
+
+ /**
+ * Check if product is salable.
+ *
+ * @param int|string $productId
+ * @param int|null $storeId
+ * @return bool
+ */
+ public function isSalable($productId, $storeId = null): bool
+ {
+ if ($storeId === null) {
+ $storeId = $this->storeManager->getStore()->getId();
+ }
+ /** @var \Magento\Catalog\Model\Product $product */
+ $product = $this->productRepository->getById($productId, false, $storeId);
+
+ return $product->isSalable();
+ }
+}
diff --git a/app/code/Magento/Catalog/Model/Product/TierPriceManagement.php b/app/code/Magento/Catalog/Model/Product/TierPriceManagement.php
index f2da1e770279e..f078349c2a8f4 100644
--- a/app/code/Magento/Catalog/Model/Product/TierPriceManagement.php
+++ b/app/code/Magento/Catalog/Model/Product/TierPriceManagement.php
@@ -182,16 +182,19 @@ public function getList($sku, $customerGroupId)
: $customerGroupId);
$prices = [];
- foreach ($product->getData('tier_price') as $price) {
- if ((is_numeric($customerGroupId) && (int) $price['cust_group'] === (int) $customerGroupId)
- || ($customerGroupId === 'all' && $price['all_groups'])
- ) {
- /** @var \Magento\Catalog\Api\Data\ProductTierPriceInterface $tierPrice */
- $tierPrice = $this->priceFactory->create();
- $tierPrice->setValue($price[$priceKey])
- ->setQty($price['price_qty'])
- ->setCustomerGroupId($cgi);
- $prices[] = $tierPrice;
+ $tierPrices = $product->getData('tier_price');
+ if ($tierPrices !== null) {
+ foreach ($tierPrices as $price) {
+ if ((is_numeric($customerGroupId) && (int) $price['cust_group'] === (int) $customerGroupId)
+ || ($customerGroupId === 'all' && $price['all_groups'])
+ ) {
+ /** @var \Magento\Catalog\Api\Data\ProductTierPriceInterface $tierPrice */
+ $tierPrice = $this->priceFactory->create();
+ $tierPrice->setValue($price[$priceKey])
+ ->setQty($price['price_qty'])
+ ->setCustomerGroupId($cgi);
+ $prices[] = $tierPrice;
+ }
}
}
return $prices;
diff --git a/app/code/Magento/Catalog/Model/Product/Type/Price.php b/app/code/Magento/Catalog/Model/Product/Type/Price.php
index b30624b79dd51..814865fa75917 100644
--- a/app/code/Magento/Catalog/Model/Product/Type/Price.php
+++ b/app/code/Magento/Catalog/Model/Product/Type/Price.php
@@ -3,6 +3,8 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
+declare(strict_types=1);
+
namespace Magento\Catalog\Model\Product\Type;
use Magento\Catalog\Model\Product;
@@ -202,7 +204,7 @@ public function getChildFinalPrice($product, $productQty, $childProduct, $childP
}
/**
- * Gets the 'tear_price' array from the product
+ * Gets the 'tier_price' array from the product
*
* @param Product $product
* @param string $key
@@ -502,10 +504,10 @@ public function getFormattedTierPrice($qty, $product)
/**
* Get formatted by currency tier price
*
- * @param float $qty
- * @param Product $product
+ * @param float $qty
+ * @param Product $product
*
- * @return array|float
+ * @return array|float
*
* @deprecated
* @see getFormattedTierPrice()
@@ -529,8 +531,8 @@ public function getFormattedPrice($product)
/**
* Get formatted by currency product price
*
- * @param Product $product
- * @return array || float
+ * @param Product $product
+ * @return array || float
*
* @deprecated
* @see getFormattedPrice()
diff --git a/app/code/Magento/Catalog/Model/Product/Visibility.php b/app/code/Magento/Catalog/Model/Product/Visibility.php
index c863526898a24..c05bda7838d78 100644
--- a/app/code/Magento/Catalog/Model/Product/Visibility.php
+++ b/app/code/Magento/Catalog/Model/Product/Visibility.php
@@ -55,7 +55,7 @@ public function __construct(
/**
* Retrieve visible in catalog ids array
*
- * @return string[]
+ * @return int[]
*/
public function getVisibleInCatalogIds()
{
@@ -65,7 +65,7 @@ public function getVisibleInCatalogIds()
/**
* Retrieve visible in search ids array
*
- * @return string[]
+ * @return int[]
*/
public function getVisibleInSearchIds()
{
@@ -75,7 +75,7 @@ public function getVisibleInSearchIds()
/**
* Retrieve visible in site ids array
*
- * @return string[]
+ * @return int[]
*/
public function getVisibleInSiteIds()
{
@@ -86,6 +86,7 @@ public function getVisibleInSiteIds()
* Retrieve option array
*
* @return array
+ * phpcs:disable Magento2.Functions.StaticFunction
*/
public static function getOptionArray()
{
@@ -134,6 +135,7 @@ public static function getOptionText($optionId)
$options = self::getOptionArray();
return isset($options[$optionId]) ? $options[$optionId] : null;
}
+ //phpcs:enable Magento2.Functions.StaticFunction
/**
* Retrieve flat column definition
@@ -251,7 +253,7 @@ public function addValueSortToCollection($collection, $dir = 'asc')
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function toOptionArray()
{
diff --git a/app/code/Magento/Catalog/Model/ProductAttributeGroupRepository.php b/app/code/Magento/Catalog/Model/ProductAttributeGroupRepository.php
index f43ff45b93efa..a63780715c713 100644
--- a/app/code/Magento/Catalog/Model/ProductAttributeGroupRepository.php
+++ b/app/code/Magento/Catalog/Model/ProductAttributeGroupRepository.php
@@ -4,12 +4,16 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
+declare(strict_types=1);
namespace Magento\Catalog\Model;
use Magento\Framework\Exception\NoSuchEntityException;
use Magento\Framework\Exception\StateException;
+/**
+ * Class \Magento\Catalog\Model\ProductAttributeGroupRepository
+ */
class ProductAttributeGroupRepository implements \Magento\Catalog\Api\ProductAttributeGroupRepositoryInterface
{
/**
@@ -43,15 +47,21 @@ public function __construct(
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function save(\Magento\Eav\Api\Data\AttributeGroupInterface $group)
{
+ /** @var \Magento\Catalog\Model\Product\Attribute\Group $group */
+ $extensionAttributes = $group->getExtensionAttributes();
+ if ($extensionAttributes) {
+ $group->setSortOrder($extensionAttributes->getSortOrder());
+ $group->setAttributeGroupCode($extensionAttributes->getAttributeGroupCode());
+ }
return $this->groupRepository->save($group);
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function getList(\Magento\Framework\Api\SearchCriteriaInterface $searchCriteria)
{
@@ -59,7 +69,7 @@ public function getList(\Magento\Framework\Api\SearchCriteriaInterface $searchCr
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function get($groupId)
{
@@ -75,7 +85,7 @@ public function get($groupId)
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function deleteById($groupId)
{
@@ -86,7 +96,7 @@ public function deleteById($groupId)
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function delete(\Magento\Eav\Api\Data\AttributeGroupInterface $group)
{
diff --git a/app/code/Magento/Catalog/Model/ProductRepository.php b/app/code/Magento/Catalog/Model/ProductRepository.php
index e961db42d99fe..d656a0a9ac5b4 100644
--- a/app/code/Magento/Catalog/Model/ProductRepository.php
+++ b/app/code/Magento/Catalog/Model/ProductRepository.php
@@ -735,7 +735,7 @@ private function getCollectionProcessor()
{
if (!$this->collectionProcessor) {
$this->collectionProcessor = \Magento\Framework\App\ObjectManager::getInstance()->get(
- 'Magento\Catalog\Model\Api\SearchCriteria\ProductCollectionProcessor'
+ \Magento\Catalog\Model\Api\SearchCriteria\ProductCollectionProcessor::class
);
}
return $this->collectionProcessor;
@@ -845,7 +845,6 @@ private function saveProduct($product): void
throw new CouldNotSaveException(__($e->getMessage()));
} catch (LocalizedException $e) {
throw $e;
- // phpcs:disable Magento2.Exceptions.ThrowCatch
} catch (\Exception $e) {
throw new CouldNotSaveException(
__('The product was unable to be saved. Please try again.'),
diff --git a/app/code/Magento/Catalog/Model/ResourceModel/AbstractResource.php b/app/code/Magento/Catalog/Model/ResourceModel/AbstractResource.php
index 3d7f863b7c0d3..3946be32184ec 100644
--- a/app/code/Magento/Catalog/Model/ResourceModel/AbstractResource.php
+++ b/app/code/Magento/Catalog/Model/ResourceModel/AbstractResource.php
@@ -15,6 +15,7 @@
/**
* Catalog entity abstract model
*
+ * phpcs:disable Magento2.Classes.AbstractApi
* @api
*
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
@@ -468,7 +469,7 @@ protected function _getOrigObject($object)
*
* @param AbstractAttribute $attribute
* @param mixed $value New value of the attribute.
- * @param array &$origData
+ * @param array $origData
* @return bool
*/
protected function _canUpdateAttribute(AbstractAttribute $attribute, $value, array &$origData)
@@ -560,15 +561,19 @@ public function getAttributeRawValue($entityId, $attribute, $store)
$store = (int) $store;
if ($typedAttributes) {
foreach ($typedAttributes as $table => $_attributes) {
+ $defaultJoinCondition = [
+ $connection->quoteInto('default_value.attribute_id IN (?)', array_keys($_attributes)),
+ "default_value.{$this->getLinkField()} = e.{$this->getLinkField()}",
+ 'default_value.store_id = 0',
+ ];
+
$select = $connection->select()
- ->from(['default_value' => $table], ['attribute_id'])
- ->join(
- ['e' => $this->getTable($this->getEntityTable())],
- 'e.' . $this->getLinkField() . ' = ' . 'default_value.' . $this->getLinkField(),
- ''
- )->where('default_value.attribute_id IN (?)', array_keys($_attributes))
- ->where("e.entity_id = :entity_id")
- ->where('default_value.store_id = ?', 0);
+ ->from(['e' => $this->getTable($this->getEntityTable())], [])
+ ->joinLeft(
+ ['default_value' => $table],
+ implode(' AND ', $defaultJoinCondition),
+ []
+ )->where("e.entity_id = :entity_id");
$bind = ['entity_id' => $entityId];
@@ -578,6 +583,11 @@ public function getAttributeRawValue($entityId, $attribute, $store)
'default_value.value',
'store_value.value'
);
+ $attributeIdExpr = $connection->getCheckSql(
+ 'store_value.attribute_id IS NULL',
+ 'default_value.attribute_id',
+ 'store_value.attribute_id'
+ );
$joinCondition = [
$connection->quoteInto('store_value.attribute_id IN (?)', array_keys($_attributes)),
"store_value.{$this->getLinkField()} = e.{$this->getLinkField()}",
@@ -587,23 +597,28 @@ public function getAttributeRawValue($entityId, $attribute, $store)
$select->joinLeft(
['store_value' => $table],
implode(' AND ', $joinCondition),
- ['attr_value' => $valueExpr]
+ ['attribute_id' => $attributeIdExpr, 'attr_value' => $valueExpr]
);
$bind['store_id'] = $store;
} else {
- $select->columns(['attr_value' => 'value'], 'default_value');
+ $select->columns(
+ ['attribute_id' => 'attribute_id', 'attr_value' => 'value'],
+ 'default_value'
+ );
}
$result = $connection->fetchPairs($select, $bind);
foreach ($result as $attrId => $value) {
- $attrCode = $typedAttributes[$table][$attrId];
- $attributesData[$attrCode] = $value;
+ if ($attrId !== '') {
+ $attrCode = $typedAttributes[$table][$attrId];
+ $attributesData[$attrCode] = $value;
+ }
}
}
}
- if (is_array($attributesData) && sizeof($attributesData) == 1) {
+ if (is_array($attributesData) && count($attributesData) == 1) {
$attributesData = array_shift($attributesData);
}
diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Category.php b/app/code/Magento/Catalog/Model/ResourceModel/Category.php
index 536fda7e093d3..786cec391c460 100644
--- a/app/code/Magento/Catalog/Model/ResourceModel/Category.php
+++ b/app/code/Magento/Catalog/Model/ResourceModel/Category.php
@@ -12,8 +12,10 @@
namespace Magento\Catalog\Model\ResourceModel;
use Magento\Catalog\Model\Indexer\Category\Product\Processor;
+use Magento\Framework\App\ObjectManager;
use Magento\Framework\DataObject;
use Magento\Framework\EntityManager\EntityManager;
+use Magento\Catalog\Model\Category as CategoryEntity;
/**
* Resource model for category entity
@@ -90,7 +92,6 @@ class Category extends AbstractResource
* @var Processor
*/
private $indexerProcessor;
-
/**
* Category constructor.
* @param \Magento\Eav\Model\Entity\Context $context
@@ -102,6 +103,7 @@ class Category extends AbstractResource
* @param Processor $indexerProcessor
* @param array $data
* @param \Magento\Framework\Serialize\Serializer\Json|null $serializer
+ * @SuppressWarnings(PHPMD.ExcessiveParameterList)
*/
public function __construct(
\Magento\Eav\Model\Entity\Context $context,
@@ -125,7 +127,7 @@ public function __construct(
$this->_eventManager = $eventManager;
$this->connectionName = 'catalog';
$this->indexerProcessor = $indexerProcessor;
- $this->serializer = $serializer ?: \Magento\Framework\App\ObjectManager::getInstance()
+ $this->serializer = $serializer ?: ObjectManager::getInstance()
->get(\Magento\Framework\Serialize\Serializer\Json::class);
}
@@ -1026,7 +1028,7 @@ protected function _processPositions($category, $newParent, $afterCategoryId)
if ($afterCategoryId) {
$select = $connection->select()->from($table, 'position')->where('entity_id = :entity_id');
$position = $connection->fetchOne($select, ['entity_id' => $afterCategoryId]);
- $position += 1;
+ $position++;
} else {
$position = 1;
}
diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Eav/Attribute.php b/app/code/Magento/Catalog/Model/ResourceModel/Eav/Attribute.php
index d56cc40ad0fc2..23f612582f42e 100644
--- a/app/code/Magento/Catalog/Model/ResourceModel/Eav/Attribute.php
+++ b/app/code/Magento/Catalog/Model/ResourceModel/Eav/Attribute.php
@@ -845,14 +845,9 @@ public function afterDelete()
/**
* @inheritdoc
* @since 100.0.9
- *
- * @SuppressWarnings(PHPMD.SerializationAware)
- * @deprecated Do not use PHP serialization.
*/
public function __sleep()
{
- trigger_error('Using PHP serialization is deprecated', E_USER_DEPRECATED);
-
$this->unsetData('entity_type');
return array_diff(
parent::__sleep(),
@@ -863,14 +858,9 @@ public function __sleep()
/**
* @inheritdoc
* @since 100.0.9
- *
- * @SuppressWarnings(PHPMD.SerializationAware)
- * @deprecated Do not use PHP serialization.
*/
public function __wakeup()
{
- trigger_error('Using PHP serialization is deprecated', E_USER_DEPRECATED);
-
parent::__wakeup();
$objectManager = \Magento\Framework\App\ObjectManager::getInstance();
$this->_indexerEavProcessor = $objectManager->get(\Magento\Catalog\Model\Indexer\Product\Flat\Processor::class);
diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product.php b/app/code/Magento/Catalog/Model/ResourceModel/Product.php
index 99a7efe6c9895..b0b15cfd69d13 100644
--- a/app/code/Magento/Catalog/Model/ResourceModel/Product.php
+++ b/app/code/Magento/Catalog/Model/ResourceModel/Product.php
@@ -8,6 +8,7 @@
use Magento\Catalog\Model\ResourceModel\Product\Website\Link as ProductWebsiteLink;
use Magento\Framework\App\ObjectManager;
use Magento\Catalog\Model\Indexer\Category\Product\TableMaintainer;
+use Magento\Catalog\Model\Product as ProductEntity;
use Magento\Eav\Model\Entity\Attribute\UniqueValidationInterface;
use Magento\Framework\EntityManager\EntityManager;
use Magento\Framework\Model\AbstractModel;
@@ -607,7 +608,9 @@ public function countAll()
}
/**
- * @inheritdoc
+ * @inheritDoc
+ *
+ * @param ProductEntity|object $object
*/
public function validate($object)
{
@@ -689,7 +692,7 @@ public function save(AbstractModel $object)
}
/**
- * Retrieve entity manager object
+ * Retrieve entity manager.
*
* @return EntityManager
*/
@@ -703,7 +706,7 @@ private function getEntityManager()
}
/**
- * Retrieve ProductWebsiteLink object
+ * Retrieve ProductWebsiteLink instance.
*
* @deprecated 101.1.0
* @return ProductWebsiteLink
@@ -714,7 +717,7 @@ private function getProductWebsiteLink()
}
/**
- * Retrieve CategoryLink object
+ * Retrieve CategoryLink instance.
*
* @deprecated 101.1.0
* @return Product\CategoryLink
diff --git a/app/code/Magento/Catalog/Observer/InvalidateCacheOnCategoryDesignChange.php b/app/code/Magento/Catalog/Observer/InvalidateCacheOnCategoryDesignChange.php
new file mode 100644
index 0000000000000..0b30ff4f1c038
--- /dev/null
+++ b/app/code/Magento/Catalog/Observer/InvalidateCacheOnCategoryDesignChange.php
@@ -0,0 +1,102 @@
+cacheTypeList = $cacheTypeList;
+ $this->scopeConfig = $scopeConfig;
+ }
+
+ /**
+ * Get default category design attribute values
+ *
+ * @return array
+ */
+ private function getDefaultAttributeValues()
+ {
+ return [
+ 'custom_apply_to_products' => '0',
+ 'custom_use_parent_settings' => '0',
+ 'page_layout' => $this->scopeConfig->getValue(
+ 'web/default_layouts/default_category_layout',
+ \Magento\Store\Model\ScopeInterface::SCOPE_STORE
+ )
+ ];
+ }
+
+ /**
+ * Invalidate cache on category design attribute value changed
+ *
+ * @param \Magento\Framework\Event\Observer $observer
+ */
+ public function execute(Observer $observer)
+ {
+ $category = $observer->getEvent()->getEntity();
+ if (!$category->isObjectNew()) {
+ foreach ($category->getDesignAttributes() as $designAttribute) {
+ if ($this->isCategoryAttributeChanged($designAttribute->getAttributeCode(), $category)) {
+ $this->cacheTypeList->invalidate(
+ [
+ \Magento\PageCache\Model\Cache\Type::TYPE_IDENTIFIER,
+ \Magento\Framework\App\Cache\Type\Layout::TYPE_IDENTIFIER
+ ]
+ );
+ break;
+ }
+ }
+ }
+ }
+
+ /**
+ * Check if category attribute changed
+ *
+ * @param string $attributeCode
+ * @param \Magento\Catalog\Api\Data\CategoryInterface $category
+ * @return bool
+ */
+ private function isCategoryAttributeChanged($attributeCode, $category)
+ {
+ if (!array_key_exists($attributeCode, $category->getOrigData())) {
+ $defaultValue = $this->getDefaultAttributeValues()[$attributeCode] ?? null;
+ if ($category->getData($attributeCode) !== $defaultValue) {
+ return true;
+ }
+ } else {
+ if ($category->dataHasChangedFor($attributeCode)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+}
diff --git a/app/code/Magento/Catalog/Plugin/Block/Topmenu.php b/app/code/Magento/Catalog/Plugin/Block/Topmenu.php
index 35ce327c74ead..b4aa5bd960b01 100644
--- a/app/code/Magento/Catalog/Plugin/Block/Topmenu.php
+++ b/app/code/Magento/Catalog/Plugin/Block/Topmenu.php
@@ -3,6 +3,8 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
+declare(strict_types=1);
+
namespace Magento\Catalog\Plugin\Block;
use Magento\Catalog\Model\Category;
@@ -14,11 +16,6 @@
*/
class Topmenu
{
- /**
- * Cache tag for menu block
- */
- private $cacheTag = "top_menu";
-
/**
* Catalog category
*
@@ -124,7 +121,6 @@ public function beforeGetIdentities(\Magento\Theme\Block\Html\Topmenu $subject)
$subject->addIdentity(Category::CACHE_TAG);
$rootId = $this->storeManager->getStore()->getRootCategoryId();
$storeId = $this->storeManager->getStore()->getId();
- $currentCategory = $this->getCurrentCategory();
/** @var \Magento\Catalog\Model\ResourceModel\Category\Collection $collection */
$collection = $this->getCategoryTree($storeId, $rootId);
$mapping = [$rootId => $subject->getMenu()]; // use nodes stack to avoid recursion
@@ -134,9 +130,6 @@ public function beforeGetIdentities(\Magento\Theme\Block\Html\Topmenu $subject)
}
$subject->addIdentity(Category::CACHE_TAG . '_' . $category->getId());
}
- if ($currentCategory) {
- $subject->addIdentity($this->cacheTag . '_' . Category::CACHE_TAG . '_' . $currentCategory->getId());
- }
}
/**
@@ -165,12 +158,13 @@ private function getCurrentCategory()
*/
private function getCategoryAsArray($category, $currentCategory, $isParentActive)
{
+ $categoryId = $category->getId();
return [
'name' => $category->getName(),
- 'id' => 'category-node-' . $category->getId(),
+ 'id' => 'category-node-' . $categoryId,
'url' => $this->catalogCategory->getCategoryUrl($category),
- 'has_active' => in_array((string)$category->getId(), explode('/', $currentCategory->getPath()), true),
- 'is_active' => $category->getId() == $currentCategory->getId(),
+ 'has_active' => in_array((string)$categoryId, explode('/', (string)$currentCategory->getPath()), true),
+ 'is_active' => $categoryId == $currentCategory->getId(),
'is_category' => true,
'is_parent_active' => $isParentActive
];
@@ -202,4 +196,22 @@ protected function getCategoryTree($storeId, $rootId)
return $collection;
}
+
+ /**
+ * Add active
+ *
+ * @param \Magento\Theme\Block\Html\Topmenu $subject
+ * @param string[] $result
+ * @return string[]
+ * @SuppressWarnings(PHPMD.UnusedFormalParameter)
+ */
+ public function afterGetCacheKeyInfo(\Magento\Theme\Block\Html\Topmenu $subject, array $result)
+ {
+ $activeCategory = $this->getCurrentCategory();
+ if ($activeCategory) {
+ $result[] = Category::CACHE_TAG . '_' . $activeCategory->getId();
+ }
+
+ return $result;
+ }
}
diff --git a/app/code/Magento/Catalog/Pricing/Price/Collection.php b/app/code/Magento/Catalog/Pricing/Price/Collection.php
new file mode 100644
index 0000000000000..b48d7e9e38361
--- /dev/null
+++ b/app/code/Magento/Catalog/Pricing/Price/Collection.php
@@ -0,0 +1,63 @@
+storeManager = $storeManager ?? ObjectManager::getInstance()->get(StoreManagerInterface::class);
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function get($code)
+ {
+ $customerGroupId = $this->saleableItem->getCustomerGroupId() ?? '';
+ $websiteId = $this->storeManager->getStore($this->saleableItem->getStoreId())->getWebsiteId();
+ $codeKey = $code . '-' . $customerGroupId . '-' . $websiteId;
+
+ if (!isset($this->priceModels[$codeKey])) {
+ $this->priceModels[$codeKey] = $this->priceFactory->create(
+ $this->saleableItem,
+ $this->pool[$code],
+ $this->quantity
+ );
+ }
+
+ return $this->priceModels[$codeKey];
+ }
+}
diff --git a/app/code/Magento/Catalog/Pricing/Render/FinalPriceBox.php b/app/code/Magento/Catalog/Pricing/Render/FinalPriceBox.php
index e0a92ea0e0bea..71b5f505f97a8 100644
--- a/app/code/Magento/Catalog/Pricing/Render/FinalPriceBox.php
+++ b/app/code/Magento/Catalog/Pricing/Render/FinalPriceBox.php
@@ -60,7 +60,7 @@ public function __construct(
}
/**
- * @return string
+ * @inheritdoc
*/
protected function _toHtml()
{
@@ -182,25 +182,27 @@ public function showMinimalPrice()
*/
public function getCacheKey()
{
- return parent::getCacheKey() . ($this->getData('list_category_page') ? '-list-category-page': '');
+ return parent::getCacheKey()
+ . ($this->getData('list_category_page') ? '-list-category-page': '')
+ . ($this->getSaleableItem()->getCustomerGroupId() ?? '');
}
/**
- * {@inheritdoc}
- *
- * @return array
+ * @inheritdoc
*/
public function getCacheKeyInfo()
{
$cacheKeys = parent::getCacheKeyInfo();
$cacheKeys['display_minimal_price'] = $this->getDisplayMinimalPrice();
$cacheKeys['is_product_list'] = $this->isProductList();
+ $cacheKeys['customer_group_id'] = $this->getSaleableItem()->getCustomerGroupId();
return $cacheKeys;
}
/**
- * Get flag that price rendering should be done for the list of products
- * By default (if flag is not set) is false
+ * Get flag that price rendering should be done for the list of products.
+ *
+ * By default (if flag is not set) is false.
*
* @return bool
*/
diff --git a/app/code/Magento/Catalog/Setup/Patch/Schema/ChangeTmpTablesEngine.php b/app/code/Magento/Catalog/Setup/Patch/Schema/ChangeTmpTablesEngine.php
new file mode 100644
index 0000000000000..c39247f9b30df
--- /dev/null
+++ b/app/code/Magento/Catalog/Setup/Patch/Schema/ChangeTmpTablesEngine.php
@@ -0,0 +1,74 @@
+schemaSetup = $schemaSetup;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function apply()
+ {
+ $this->schemaSetup->startSetup();
+
+ $tables = [
+ 'catalog_product_index_price_cfg_opt_agr_tmp',
+ 'catalog_product_index_price_cfg_opt_tmp',
+ 'catalog_product_index_price_final_tmp',
+ 'catalog_product_index_price_opt_tmp',
+ 'catalog_product_index_price_opt_agr_tmp',
+ 'catalog_product_index_eav_tmp',
+ 'catalog_product_index_eav_decimal_tmp',
+ 'catalog_product_index_price_tmp',
+ 'catalog_category_product_index_tmp',
+ ];
+ foreach ($tables as $table) {
+ $tableName = $this->schemaSetup->getTable($table);
+ if ($this->schemaSetup->getConnection()->isTableExists($tableName)) {
+ $this->schemaSetup->getConnection()->changeTableEngine($tableName, 'InnoDB');
+ }
+ }
+
+ $this->schemaSetup->endSetup();
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public static function getDependencies()
+ {
+ return [];
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function getAliases()
+ {
+ return [];
+ }
+}
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AddProductToCartActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AddProductToCartActionGroup.xml
index b47e13a118272..bc7288d33bcb3 100644
--- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AddProductToCartActionGroup.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AddProductToCartActionGroup.xml
@@ -5,12 +5,17 @@
* See COPYING.txt for license details.
*/
-->
+
+ xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd">
+
+ Navigates to the Storefront Product page. Then adds the Product to the Cart. Validates that the Success Message is present and correct.
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AddWebsiteToProductActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AddWebsiteToProductActionGroup.xml
index 9a4d15a0909fd..8574d20df676d 100644
--- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AddWebsiteToProductActionGroup.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AddWebsiteToProductActionGroup.xml
@@ -9,9 +9,13 @@
+
+ Adds the provided Website to a Product on the Admin Product creation/edit page. Clicks on Save. Validates that the Success Message is present and correct.
+
+
@@ -20,4 +24,4 @@
-
\ No newline at end of file
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminAddAdvancedPricingToTheProductActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminAddAdvancedPricingToTheProductActionGroup.xml
index a8d2f7d9860f3..f5e5957507eeb 100644
--- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminAddAdvancedPricingToTheProductActionGroup.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminAddAdvancedPricingToTheProductActionGroup.xml
@@ -6,14 +6,19 @@
*/
-->
-
+
+
+ Clicks 'Advanced Pricing' on the Admin Product creation/edit page. Fills in the provided Group Price at the provided Index for CE/EE. Clicks on Done.
+
+
@@ -26,6 +31,10 @@
+
+ EXTENDS: AdminAddAdvancedPricingToTheProductActionGroup. Removes 'selectProductTierPriceCustomerGroupInput'. Selects the provided Group Price at the provided Index for B2B.
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminAddMinimumAdvertisedPriceActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminAddMinimumAdvertisedPriceActionGroup.xml
index f0eef98748f8d..f1ef3f9e26eab 100644
--- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminAddMinimumAdvertisedPriceActionGroup.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminAddMinimumAdvertisedPriceActionGroup.xml
@@ -5,11 +5,17 @@
* See COPYING.txt for license details.
*/
-->
-
+
+
+
+ Clicks 'Advanced Pricing' on the Admin Product creation/edit page. Fills in the provided MSRP details. Clicks on Done.
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminAddProductCustomOptionActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminAddProductCustomOptionActionGroup.xml
index 8e50ac6edc68e..3784a41b1e2a0 100644
--- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminAddProductCustomOptionActionGroup.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminAddProductCustomOptionActionGroup.xml
@@ -5,13 +5,19 @@
* See COPYING.txt for license details.
*/
-->
-
+
+
+
+ Adds the provided Custom Option Title/Type to a Product on the Admin Product creation/edit page under the 'Customizable Options' section.
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminAddTitleAndPriceValueToCustomOptionActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminAddTitleAndPriceValueToCustomOptionActionGroup.xml
index 3a03eb60555a3..6e8d8d885657d 100644
--- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminAddTitleAndPriceValueToCustomOptionActionGroup.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminAddTitleAndPriceValueToCustomOptionActionGroup.xml
@@ -5,12 +5,18 @@
* See COPYING.txt for license details.
*/
-->
-
+
+
+
+ Clicks on 'Add Option'. Fills in the provided Custom Option Title/Type on the Admin Product creation/edit page under the 'Customizable Options' section.
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminAssertProductsGridIsEmptyActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminAssertProductsGridIsEmptyActionGroup.xml
new file mode 100644
index 0000000000000..1ed186f34146f
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminAssertProductsGridIsEmptyActionGroup.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminAssignImageRolesActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminAssignImageRolesActionGroup.xml
index 90ceb1e4a1f96..c6492754515fb 100644
--- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminAssignImageRolesActionGroup.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminAssignImageRolesActionGroup.xml
@@ -9,9 +9,13 @@
+
+ Requires the navigation to the Product Creation page. Checks the Base, Small, Thumbnail, and Swatch Roles areas for images.
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminAssignProductInWebsiteActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminAssignProductInWebsiteActionGroup.xml
new file mode 100644
index 0000000000000..8bb15490f5895
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminAssignProductInWebsiteActionGroup.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminCategoryActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminCategoryActionGroup.xml
index 90d732c9654e1..b9b31b780c8f7 100644
--- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminCategoryActionGroup.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminCategoryActionGroup.xml
@@ -10,9 +10,13 @@
xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd">
+
+ Requires navigation to the Category creation page. Adds a new Subcategory. Validates that the Category was created.
+
+
@@ -27,9 +31,13 @@
+
+ Goes to the Category grid page. Clicks the Add Subcategory button.
+
+
@@ -39,9 +47,13 @@
+
+ Goes to the Category edit page for a specified Category ID.
+
+
@@ -49,9 +61,13 @@
+
+ Requires navigation to the Subcategory creation/edit page. Fills the Subcategory Name. Fills the Search Engine Optimization.
+
+
@@ -59,6 +75,10 @@
+
+ Requires navigation to the Category creation/edit page. Checks that the url contains the AdminCategoryPage url. Saves the Category.
+
+
@@ -66,20 +86,32 @@
+
+ Requires navigation to the Category creation/edit page. Adds the provided image to a Category. Validates that the Image exists.
+
+
-
+
+
+ /magento-logo(_[0-9]+)*?\.png$/
+ grabCategoryFileName
+
+
+ Requires navigation to the Category creation/edit page. Removes the current Category image. Validates that the Image does not exist.
+
+
@@ -90,17 +122,29 @@
+
+ Requires navigation to the Category creation/edit page. Click on the Upload button. Validates that the Image exists.
+
+
-
+
+
+ /magento-logo(_[0-9]+)*?\.png$/
+ grabCategoryFileName
+
+
+ Navigates to the category page and Opens the Media Gallery.
+
+
@@ -113,9 +157,13 @@
+
+ Navigates to the category page on the storefront and asserts that the title is correct for page and browser.
+
+
@@ -124,9 +172,13 @@
+
+ Navigates to the category page and deletes the specified category.
+
+
@@ -143,6 +195,9 @@
+
+ Actions to fill out a new category from the product page with specified category and parent category names.
+
@@ -151,7 +206,6 @@
-
@@ -160,50 +214,49 @@
-
+
+ Actions to delete the category last made (the last category on the list).
+
-
-
-
+
-
-
+
-
-
-
+
-
+
+ Navigates to category page, asserts category is there. Navigates to storefront category page and asserts category is there. This action group will not work categories where name does NOT equal SEO.
+
-
-
-
-
+
+ Navigates to category page, attempts to add subcategory without name. Expects required field prompt.
+
+
@@ -213,13 +266,17 @@
+
+ Navigates to category page, selects a category and changes store view to specified store.
+
+
-
+
@@ -232,10 +289,14 @@
+
+ Navigates to category page, selects a category and changes store view to all stores.
+
-
+
+
@@ -249,21 +310,29 @@
+
+ Navigates to category page, selects a category by specified category.
+
+
-
-
+
+
+
+ Requires navigation to category creation/edit. Updates the Search Engine Optimization.
+
+
@@ -271,19 +340,28 @@
+
+ Requires navigation to subcategory creation/edit. Updates the Search Engine Optimization.
+
+
+
+
+ Navigates to category page, selects a category by specified category. Replicates actionGroup:navigateToCreatedCategory.
+
+
@@ -292,21 +370,31 @@
+
+
+ Navigates to existing product page. Changes the category and saves the product.
+
+
+
+
+ Requires navigation to subcategory creation/edit. Fills the name, and sets the Search Engine Optimization for the category.
+
+
@@ -316,4 +404,44 @@
+
+
+
+ Requires navigation to category creation/edit page. Assign products to category - using "Products in Category" tab.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Deletes all children categories of Default Root Category.
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminChangeProductSEOSettingsActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminChangeProductSEOSettingsActionGroup.xml
new file mode 100644
index 0000000000000..ec0beac86554e
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminChangeProductSEOSettingsActionGroup.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminCheckProductsInGridActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminCheckProductsInGridActionGroup.xml
new file mode 100644
index 0000000000000..440739875c0c1
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminCheckProductsInGridActionGroup.xml
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminClickOnAdvancedInventoryLinkActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminClickOnAdvancedInventoryLinkActionGroup.xml
index 58164ce5f8989..60438e23e084c 100644
--- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminClickOnAdvancedInventoryLinkActionGroup.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminClickOnAdvancedInventoryLinkActionGroup.xml
@@ -6,10 +6,15 @@
*/
-->
-
+
+
+ Clicks on the 'Advanced Inventory' link on the Admin Product creation/edit page.
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminCreateCustomDropDownOptionsActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminCreateCustomDropDownOptionsActionGroup.xml
index a674647a5c85b..a62ff33c2a4ae 100644
--- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminCreateCustomDropDownOptionsActionGroup.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminCreateCustomDropDownOptionsActionGroup.xml
@@ -6,22 +6,29 @@
*/
-->
-
+
+
-
+
+ Clicks on 'Add Option'. Adds the 2 provided Options under the provided Custom Option Name to a Product on the Admin Product creation/edit page under the 'Customizable Options' section.
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminCreateRootCategoryActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminCreateRootCategoryActionGroup.xml
index a99420bcf95bb..e3297c366b2ab 100644
--- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminCreateRootCategoryActionGroup.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminCreateRootCategoryActionGroup.xml
@@ -10,9 +10,13 @@
xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd">
+
+ Requires navigation to Category creation. Adds a new root category and asserts on creation.
+
+
@@ -20,9 +24,9 @@
-
+
-
\ No newline at end of file
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminCreateWidgetActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminCreateWidgetActionGroup.xml
index dd66919640a73..45e2ed6205b20 100644
--- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminCreateWidgetActionGroup.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminCreateWidgetActionGroup.xml
@@ -6,8 +6,13 @@
*/
-->
-
+
+
+ EXTENDS: AdminCreateWidgetActionGroup. Adds Product Attributes/Buttons to a Widget. Clicks on the Save button.
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminFillAdvancedInventoryOutOfStockThresholdActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminFillAdvancedInventoryOutOfStockThresholdActionGroup.xml
index d0116467be5ea..c732369356125 100644
--- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminFillAdvancedInventoryOutOfStockThresholdActionGroup.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminFillAdvancedInventoryOutOfStockThresholdActionGroup.xml
@@ -6,12 +6,17 @@
*/
-->
-
+
+
+ Unchecks 'Use Config Setting' for the 'Minimum Qty Allowed in Shopping Cart' option in 'Advanced Inventory' panel on the Admin Product creation/edit page. Fills in the provided Quantity.
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminFillAdvancedInventoryQtyActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminFillAdvancedInventoryQtyActionGroup.xml
index 1faa3f04d366e..6567673e4c47a 100644
--- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminFillAdvancedInventoryQtyActionGroup.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminFillAdvancedInventoryQtyActionGroup.xml
@@ -6,12 +6,17 @@
*/
-->
-
+
+
+ Fills in the provided Quantity for the 'Qty' option in 'Advanced Inventory' panel on the Admin Product creation/edit page.
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminFillProductAttributePropertiesActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminFillProductAttributePropertiesActionGroup.xml
index cd850f8a7004d..ec73001976dc6 100644
--- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminFillProductAttributePropertiesActionGroup.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminFillProductAttributePropertiesActionGroup.xml
@@ -15,4 +15,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminOpenNewProductFormPageActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminOpenNewProductFormPageActionGroup.xml
index fe859fab52667..25aaa5c6cde91 100644
--- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminOpenNewProductFormPageActionGroup.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminOpenNewProductFormPageActionGroup.xml
@@ -5,15 +5,19 @@
* See COPYING.txt for license details.
*/
-->
+
+
+ Goes to the Product creation/edit page for the provided Product Type and Attribute Set ID.
+
-
-
+
+
-
-
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductActionGroup.xml
index 9deab90b9045b..5c5ee0f9cb321 100644
--- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductActionGroup.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductActionGroup.xml
@@ -5,13 +5,18 @@
* See COPYING.txt for license details.
*/
-->
+
+
+ Clicks on the 'Add Product' toggle on the Admin Products grid page. Clicks on the provided Product (Type).
+
+
@@ -22,17 +27,25 @@
+
+ Goes to the Product edit page for the provided Product ID.
+
+
+
+ Fills in the provided Product details (Name, SKU, Price, Quantity, Stock Status, Weight Type and Weight) on the Admin Products creation/edit page.
+
+
@@ -45,6 +58,9 @@
+
+ Fills in the provided Product Name, SKU, Price, Quantity, Stock Status and Weight on the Admin Products creation/edit page.
+
@@ -53,6 +69,7 @@
+
@@ -64,9 +81,13 @@
+
+ Fills in the provided Product details (Name, SKU, Price, Quantity, Stock Status and Weight Type) on the Admin Products creation/edit page.
+
+
@@ -77,15 +98,23 @@
+
+ Fills in the provided Product details (Name and SKU) on the Admin Products creation and edit page.
+
+
+
+ Validates that the 'Required Field' error message is present and correct for the Product Name, SKU and Price fields.
+
+
@@ -98,26 +127,44 @@
+
+ Clicks on the Save button. Validates that the Success Message is present and correct.
+
+
-
+
-
-
+
+
+
+ Clicks on the Enable Product toggle.
+
+
+
+
+ EXTENDS: saveProductForm. Removes 'waitProductSaveSuccessMessage' and 'seeSaveConfirmation'.
+
+
+
+
+ Adds the provided Product Image on the Admin Products creation/edit page.
+
+
@@ -128,6 +175,10 @@
+
+ Removes a Product Image on the Admin Products creation/edit page.
+
+
@@ -135,9 +186,13 @@
+
+ Validates that the provided Product Image is present and correct.
+
+
@@ -145,9 +200,13 @@
+
+ Validates that the provided Product Image is NOT present.
+
+
@@ -155,10 +214,14 @@
+
+ Goes to the Admin Product grid page. Clicks on Add. Fills the provided Product details (Name, SKU, Price, Quantity, Category and URL). Clicks on Save. Validates that the Product details are present and correct.
+
+
@@ -180,11 +243,15 @@
+
+ Goes to the Admin Product grid page. Clicks on Add. Fills the provided Product details (Name, SKU, Price, Quantity, Category and URL). Adds a Text Product Option with the provided Char Limits. Clicks on Save. Validates that the Product details are present and correct.
+
+
@@ -213,9 +280,13 @@
+
+ Sets the provided Website on the Admin Product creation/edit page.
+
+
@@ -227,6 +298,9 @@
+
+ Sets the provided Advanced Pricing on the Admin Product creation/edit page.
+
@@ -234,6 +308,7 @@
+
@@ -249,13 +324,21 @@
+
+
+
+
+
+ Validates that provided Text appears in the provided Element on the Admin Product creation/edit page under 'Related Products, Up-Sells, and Cross-Sells' section.
+
+
@@ -263,9 +346,13 @@
+
+ Adds the provided Product SKU as a Related Product on the Admin Product creation/edit page.
+
+
@@ -275,15 +362,20 @@
-
+
+
+
+ Adds the provided Product SKU as a Cross Sell Product on the Admin Product creation/edit page.
+
+
@@ -300,9 +392,13 @@
+
+ Sets the provided Special Price on the Admin Product creation/edit page.
+
+
@@ -315,19 +411,31 @@
+
+ Sets the provided Website on the Admin Product creation/edit page.
+
+
+
+
+
+
+
+ Sets the provided Special Price on the Admin Product creation/edit page. Clicks on Save.
+
+
@@ -340,9 +448,13 @@
+
+ Switches the New Store View.
+
+
@@ -353,10 +465,14 @@
+
+ Goes to the Admin Product grid page. Clicks on Add Product. Fills the provided Product Details (Name, SKU, Price, Quantity and Website). Clicks on Save.
+
+
@@ -373,10 +489,14 @@
+
+ Clicks on 'Edit' for the provided Product. Clicks on the provided Website. Clicks on Save.
+
+
@@ -387,11 +507,23 @@
+
+
+
+
+
+
+
+
+
+ Validates that the provided Product Tier Price is present and correct.
+
+
@@ -403,9 +535,13 @@
+
+ Goes to the Admin Product grid page. Clicks on the Add Product toggle. Clicks on the provided Product Type.
+
+
@@ -415,31 +551,47 @@
+
+ Goes to the Admin Products grid page.
+
+
+
+ Fills the Product details (URL) for the SEO section.
+
+
+
+ Fills the Product SEO URL Key.
+
+
+
+ Sets the provided Category Name for a Product on the Product creation/edit page.
+
+
@@ -453,19 +605,28 @@
+
+ Expand the provided Section Selector based on the provided dependant Section Selector.
+
+
+
+
+ Goes to the Admin Product grid page. Filters the Product grid based on the provided Product details (SKU). Edits the provided Product. Validates that the Product SKU is present and correct.
+
+
@@ -484,7 +645,12 @@
+
+
+ EXTENDS: addRelatedProductBySku. Add the provided Product as an Up Sell Product.
+
+
@@ -495,6 +661,19 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -524,4 +703,39 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductAttributeActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductAttributeActionGroup.xml
index ed0c4387cdedf..3e54574c553e3 100644
--- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductAttributeActionGroup.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductAttributeActionGroup.xml
@@ -9,33 +9,45 @@
+
+ Goes to the Product Attributes grid page. Filters the grid based on the provided Product Attribute. Clicks on the 1st row.
+
+
-
+
-
+
+
+
+ Goes to the Product Attributes grid page. Filters the grid based on the provided Product Attribute. Clicks on the 1st row.
+
+
-
-
-
-
-
+
+
+
+
+
+
+ From the Product creation/edit page, under 'Configurations', Clicks on 'Create Configurations'. Clicks on 'Create New Attribute'. Fills Label. Selects Type. Clicks on Save.
+
+
@@ -53,10 +65,14 @@
+
+ EXTENDS: AdminCreateAttributeFromProductPage. Adds the 2 provided Store Views to a Product. Clicks on Save.
+
+
@@ -74,11 +90,15 @@
+
+ Adds the provided Product Attribute Set to a Product on the Product creation/edit page.
+
+
@@ -96,10 +116,14 @@
+
+ EXTENDS: AdminProductPageCreateAttributeSetWithAttribute. Sets the provided Search Weight and Default Values.
+
+
@@ -109,44 +133,65 @@
+
+ Selects the provided Attribute Set from the Admin Product creation/edit page.
+
+
+
+ Fills in the Attribute Name field with the provided value.
+
+
+
+ Select the provided value for the 'Use for Promo Rule Conditions' dropdown menu. Clicks on Save. Validates that the Save message is present.
+
+
+
+
+ EXTENDS: navigateToCreatedProductAttribute. Deletes the Product Attribute. Validates that the success message is present.
+
+
-
+
+
+
+ Goes to the Admin Product Attributes grid page. Filters the grid for the provided Product Attribute (Label). Deletes the Product Attribute from the grid. Validates that the Success Message is present.
+
+
@@ -157,53 +202,83 @@
+
+
+ Goes to the Admin Product Attributes grid page. Filters the grid for the provided Product Attribute Code. Deletes the Product Attribute from the grid. Validates that the Success Message is present.
+
+
-
+
+
+
+ Filters the Product Attributes grid by the provided Product Attribute Code.
+
+
+
+
+ Filters the Product Attributes grid by the provided Product Attribute Label.
+
+
+
+
+ Clicks on Save. Validates that the Success Message is present.
+
+
+
+
+ Clicks on the Confirm button for the 'Product Data My Be Lost' modal.
+
+
+
+
+ Clicks on Save. Validates that the Success Message is present.
+
+
@@ -212,9 +287,13 @@
+
+ Adds the provided Attribute Code to the Product on the Admin Product creation/edit page.
+
+
@@ -227,9 +306,13 @@
+
+ Clicks on 'Add New Attribute'. Fills in the Attribute Details (Label, Input Type and Value Required). Clicks on Save.
+
+
@@ -239,6 +322,10 @@
+
+ EXTENDS: createProductAttribute. Fills in the Attribute Code and Default Value (Attribute Type: Text Field).
+
+
@@ -246,9 +333,13 @@
+
+ EXTENDS: createProductAttribute. Fills in the Attribute Code and Default Value (Attribute Type: Date Field).
+
+
@@ -256,11 +347,15 @@
+
+ Creates an Option for a Product Attribute (Attribute Type: Dropdown).
+
+
@@ -269,6 +364,10 @@
+
+ EXTENDS: createAttributeDropdownNthOption. Checks the 'Is Default' option.
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductAttributeMassUpdateActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductAttributeMassUpdateActionGroup.xml
new file mode 100644
index 0000000000000..57b180ada1536
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductAttributeMassUpdateActionGroup.xml
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductAttributeSetActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductAttributeSetActionGroup.xml
index 019d103a31cf2..e20b5f113a7ec 100644
--- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductAttributeSetActionGroup.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductAttributeSetActionGroup.xml
@@ -9,68 +9,107 @@
+
+ Assign the provided Attribute to an Attribute Set from the Attribute Sets creation/edit page.
+
+
+
+
+ Unassign the provided Attribute from an Attribute Set from the Attribute Sets creation/edit page.
+
+
+
+
+ Save an Attribute Set on the Attribute Set creation/edit page.
+
+
+
+
-
+
+ Goes to the Attribute Sets grid page. Clicks on Add. Fill Name. Clicks on Save.
+
+
+
+
+ Goes to the Attribute Sets grid page.
+
+
+
+
+ Searches for the provided Attribute Sets Name. Clicks on the 1st row.
+
+
+
+
+ Searches the Attribute Sets grid page for the provided Attribute Set Name.
+
+
+
+
+ Filters the Attribute Sets grid page for the provided Attribute Set Name.
+
+
@@ -80,9 +119,13 @@
+
+ Deletes the provided Attribute Set Name from the Attribute Sets grid page.
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductGridActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductGridActionGroup.xml
index 3e967cb9c6901..aef79e651b584 100644
--- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductGridActionGroup.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductGridActionGroup.xml
@@ -10,6 +10,10 @@
xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd">
+
+ Sets the Admin Products grid view to the 'Default View'.
+
+
@@ -19,9 +23,13 @@
+
+ Filters the Admin Products grid by the provided Product (SKU).
+
+
@@ -31,9 +39,13 @@
+
+ Filters the Admin Products grid by the provided Product SKU.
+
+
@@ -43,9 +55,13 @@
+
+ Filters the Admin Products grid by the provided Product (Name).
+
+
@@ -55,9 +71,13 @@
+
+ Filters the Admin Products grid by the provided Product Name.
+
+
@@ -67,6 +87,10 @@
+
+ Filters the Admin Products grid by the New From Data field. PLEASE NOTE: The Start Date is Hardcoded.
+
+
@@ -76,9 +100,13 @@
+
+ Filters the Admin Products grid by the provided Price Filter.
+
+
@@ -89,6 +117,10 @@
+
+ Filters the Admin Products grid by the 'Enabled' Status. PLEASE NOTE: The Filter is Hardcoded.
+
+
@@ -98,6 +130,10 @@
+
+ Filters the Admin Products grid by the 'Disabled' Status. PLEASE NOTE: The Filter is Hardcoded.
+
+
@@ -107,9 +143,13 @@
+
+ Searches the Admin Products grid for the provided Keyword.
+
+
@@ -118,9 +158,13 @@
+
+ Searches the Admin Products grid for the provided Keyword.
+
+
@@ -128,9 +172,13 @@
+
+ Filters the Admin Products grid by the provided Product (Name, SKU and Type).
+
+
@@ -146,9 +194,13 @@
+
+ Filters the Admin Products grid by the provided Product (Name and SKU).
+
+
@@ -158,9 +210,13 @@
+
+ Deletes the provided Product from the Admin Products grid page.
+
+
@@ -177,19 +233,28 @@
+
+
+ EXTENDS: deleteProductUsingProductGrid. Removes 'seeProductSkuInGrid'.
+
+
+
+ Goes to the Admin Products grid page. Deletes the provided Product SKU.
+
+
@@ -207,43 +272,84 @@
+
+ EXTENDS: deleteProductBySku. Deletes the provided Product Name.
+
+
+
+
+ Delete products by keyword
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Clicks on the 'Edit' button in the Admin Products grid by the provided Grid coordinates (X, Y).
+
+
+
+ Filters the ID column in Descending Order.
+
+
+
+ Filters the ID column in Ascending Order.
+
+
+
+ Goes to the Admin Products grid page. Changes the Product Status to the provided Status. Validates that the Success Message is present and correct.
+
-
+
+
@@ -264,20 +370,34 @@
+
+ EXTENDS: resetProductGridToDefaultView. Adds an action to go to the Admin Products grid page.
+
+
+
+
+ Goes to the Product Attributes grid. Clicks on 'Clear Filters'.
+
+
+
+
+ Goes to the Admin Products grid. Filters the Product grid by the provided Product SKU.
+
+
@@ -291,11 +411,35 @@
+
+ Deletes all Products in the Admin Products grid.
+
+
+
+
+
+
+
+ Deletes all products in Admin Products grid page.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminSetProductDesignSettingsActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminSetProductDesignSettingsActionGroup.xml
new file mode 100644
index 0000000000000..93776a31149fc
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminSetProductDesignSettingsActionGroup.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminSubmitAdvancedInventoryFormActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminSubmitAdvancedInventoryFormActionGroup.xml
index e27454fb60491..b859ed2ea5942 100644
--- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminSubmitAdvancedInventoryFormActionGroup.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminSubmitAdvancedInventoryFormActionGroup.xml
@@ -6,10 +6,15 @@
*/
-->
-
+
+
+ Clicks on Done on the Admin Product creation/edit page on the 'Advanced Inventory' panel.
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminSwitchProductGiftMessageStatusActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminSwitchProductGiftMessageStatusActionGroup.xml
new file mode 100644
index 0000000000000..4d650a727acc9
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminSwitchProductGiftMessageStatusActionGroup.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminUnassignProductInWebsiteActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminUnassignProductInWebsiteActionGroup.xml
new file mode 100644
index 0000000000000..bee05fed40cb2
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminUnassignProductInWebsiteActionGroup.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertAttributeDeletionErrorMessageActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertAttributeDeletionErrorMessageActionGroup.xml
index 9402ac05d79a5..d43d1275881e2 100644
--- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertAttributeDeletionErrorMessageActionGroup.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertAttributeDeletionErrorMessageActionGroup.xml
@@ -9,8 +9,11 @@
+
+ Validates that the Error Message is present and correct on the Backend Admin Attribute Sets creation/edit page when you try to delete an Attribute Set assigned to a Configurable Product. PLEASE NOTE: The Error Message is hardcoded.
+
+
-
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertDontSeeProductDetailsOnStorefrontActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertDontSeeProductDetailsOnStorefrontActionGroup.xml
index 3d0d16d105025..4c9aca4d961fd 100644
--- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertDontSeeProductDetailsOnStorefrontActionGroup.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertDontSeeProductDetailsOnStorefrontActionGroup.xml
@@ -9,10 +9,14 @@
+
+ Validates that the provided Product Info does NOT appear in the Product Number on a Storefront Category page.
+
+
-
\ No newline at end of file
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertErrorMessageAfterDeletingWebsiteActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertErrorMessageAfterDeletingWebsiteActionGroup.xml
index 2ae224c71a9bd..54377e56603e4 100644
--- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertErrorMessageAfterDeletingWebsiteActionGroup.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertErrorMessageAfterDeletingWebsiteActionGroup.xml
@@ -9,12 +9,16 @@
+
+ Goes to the Admin Products grid page. Clicks on Ok. Validates that the provided Error Message is present and correct.
+
+
-
\ No newline at end of file
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertProductAttributePresenceInCatalogProductGridActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertProductAttributePresenceInCatalogProductGridActionGroup.xml
index 25390e6b4a80b..10b794bbf3d41 100644
--- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertProductAttributePresenceInCatalogProductGridActionGroup.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertProductAttributePresenceInCatalogProductGridActionGroup.xml
@@ -9,11 +9,14 @@
+
+ Validates that the provided Attributes Sets is present on the Backend Attribute Sets grid.
+
+
-
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertProductDetailsOnStorefrontActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertProductDetailsOnStorefrontActionGroup.xml
index 68c6e92b93fa0..dce94a271ba02 100644
--- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertProductDetailsOnStorefrontActionGroup.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertProductDetailsOnStorefrontActionGroup.xml
@@ -9,10 +9,14 @@
+
+ Validates that the provided Product Info does NOT appear in the Product Number on a Storefront Category page.
+
+
\ No newline at end of file
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertProductInStorefrontCategoryPageActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertProductInStorefrontCategoryPageActionGroup.xml
index 8b657fa1b8aab..ce5f7ad48ef32 100644
--- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertProductInStorefrontCategoryPageActionGroup.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertProductInStorefrontCategoryPageActionGroup.xml
@@ -9,10 +9,14 @@
+
+ Goes to Storefront Category page for the provided Category. Validates that the Product details are present and correct.
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertProductInStorefrontProductPageActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertProductInStorefrontProductPageActionGroup.xml
index 391a1a7d670de..8db64ce7c49ac 100644
--- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertProductInStorefrontProductPageActionGroup.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertProductInStorefrontProductPageActionGroup.xml
@@ -9,9 +9,13 @@
+
+ Goes to the Storefront page. Validates that the provided Product details are present.
+
+
@@ -20,10 +24,15 @@
+
+
+ Goes to the Storefront Product page for the provided Product. Validates that the Product details are present and correct.
+
+
@@ -31,10 +40,15 @@
+
+
+ Goes to the Storefront Product page for the provided Product. Validates that the Product details are present and correct.
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertProductInfoOnEditPageActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertProductInfoOnEditPageActionGroup.xml
index 7917fe68aaebc..918e82390e309 100644
--- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertProductInfoOnEditPageActionGroup.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertProductInfoOnEditPageActionGroup.xml
@@ -9,9 +9,13 @@
+
+ Validate that the provided Product details are correct on the Product creation/edit page.
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertProductOnAdminGridActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertProductOnAdminGridActionGroup.xml
index 963c9d9f1c911..95f1cc2c23a37 100644
--- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertProductOnAdminGridActionGroup.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertProductOnAdminGridActionGroup.xml
@@ -9,9 +9,13 @@
+
+ EXTENDS: viewProductInAdminGrid. Removes the 'clickClearFiltersAfter' step from the Action Group.
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertStorefrontProductPricesActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertStorefrontProductPricesActionGroup.xml
index 6e90fe7324dc4..aa73ebb495bc5 100644
--- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertStorefrontProductPricesActionGroup.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertStorefrontProductPricesActionGroup.xml
@@ -9,12 +9,16 @@
+
+ Validates that the provided Product Price and Final Product Price are present on the Storefront Product page.
+
+
-
\ No newline at end of file
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertSubTotalOnStorefrontMinicartActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertSubTotalOnStorefrontMinicartActionGroup.xml
index 26693771bd1fd..750c93058c283 100644
--- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertSubTotalOnStorefrontMinicartActionGroup.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertSubTotalOnStorefrontMinicartActionGroup.xml
@@ -9,9 +9,13 @@
+
+ Validates that the provided Sub Total is present in the Storefront Mini Shopping Cart.
+
+
@@ -20,4 +24,4 @@
grabTextFromMiniCartSubtotalField
-
\ No newline at end of file
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/CheckItemInLayeredNavigationActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/CheckItemInLayeredNavigationActionGroup.xml
index f2a7a0acffefa..6d7214645b27a 100644
--- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/CheckItemInLayeredNavigationActionGroup.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/CheckItemInLayeredNavigationActionGroup.xml
@@ -6,12 +6,17 @@
*/
-->
-
+
+
+ Expands the Product Filters on a Storefront Category page if it is not expanded.
+
+
-
\ No newline at end of file
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/CheckProductsOrderActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/CheckProductsOrderActionGroup.xml
index f7cd2e7076288..7fbe71cbee301 100644
--- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/CheckProductsOrderActionGroup.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/CheckProductsOrderActionGroup.xml
@@ -5,14 +5,18 @@
* See COPYING.txt for license details.
*/
-->
+
-
+
+ Goes to the Storefront. Validates that the 2 provided Products appear in the correct order.
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/CreateNewProductActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/CreateNewProductActionGroup.xml
index 53de47f810600..77ecda5ceae84 100644
--- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/CreateNewProductActionGroup.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/CreateNewProductActionGroup.xml
@@ -9,6 +9,10 @@
+
+ Goes to the Product creation/edit page. Fills in Product Name, Price and Quantity. Then Saves the work.
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/CustomOptionsActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/CustomOptionsActionGroup.xml
index 7723f32d14093..753f06cd75e3e 100644
--- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/CustomOptionsActionGroup.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/CustomOptionsActionGroup.xml
@@ -6,8 +6,12 @@
*/
-->
-
+
+
+ Adds a custom Radio Product Option with 3 Radio Options to a Product based on the provided Options.
+
@@ -15,6 +19,7 @@
+
@@ -38,9 +43,13 @@
+
+ Add a custom File Product Option to a Product based on the provided File.
+
+
@@ -52,10 +61,15 @@
+
+
+ Add a custom Field Product Option to a Product based on the provided Option.
+
+
@@ -68,10 +82,15 @@
+
+
+ Import custom Product Options for the provided Product Name.
+
+
@@ -82,16 +101,26 @@
+
+
+ Click on the Reset Filters button for the Import Options filters on the Product grid page.
+
+
+
+
+ Validate that the Custom Product Option Import value is present and correct.
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/DeleteProductActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/DeleteProductActionGroup.xml
index 7491b39aa8f20..aa44a517593af 100644
--- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/DeleteProductActionGroup.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/DeleteProductActionGroup.xml
@@ -9,9 +9,13 @@
+
+ Deletes the provided Product Name from the Product grid page.
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/DeleteProductAttributeByAttributeCodeActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/DeleteProductAttributeByAttributeCodeActionGroup.xml
index 575cbdcd9b6dc..66d4378872605 100644
--- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/DeleteProductAttributeByAttributeCodeActionGroup.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/DeleteProductAttributeByAttributeCodeActionGroup.xml
@@ -8,13 +8,17 @@
-
-
-
-
-
-
-
-
-
+
+
+ Delete a Product Attribute from the Product Attribute creation/edit page.
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/FilterProductInGridByStoreViewAndNameActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/FilterProductInGridByStoreViewAndNameActionGroup.xml
index d5d378ad11bd9..5decc6c48f8d5 100644
--- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/FilterProductInGridByStoreViewAndNameActionGroup.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/FilterProductInGridByStoreViewAndNameActionGroup.xml
@@ -9,10 +9,14 @@
+
+ Goes to the Admin Products grid page. Filters the grid based on the provided Store View. Validates that the Product Name is present in the 1st row of the grid.
+
+
@@ -22,4 +26,4 @@
-
\ No newline at end of file
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/MoveCategoryActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/MoveCategoryActionGroup.xml
index 7bb9aa60ca628..16f0d9e2b0fad 100644
--- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/MoveCategoryActionGroup.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/MoveCategoryActionGroup.xml
@@ -9,10 +9,14 @@
+
+ Move a Category on the Backend Category page.
+
+
@@ -21,4 +25,4 @@
-
\ No newline at end of file
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/OpenEditProductOnBackendActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/OpenEditProductOnBackendActionGroup.xml
index ea2543cd5c2ab..2e1646ce463ef 100644
--- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/OpenEditProductOnBackendActionGroup.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/OpenEditProductOnBackendActionGroup.xml
@@ -5,12 +5,17 @@
* See COPYING.txt for license details.
*/
-->
+
+ xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd">
+
+ Click on Edit for the provided Product SKU.
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/OpenProductAttributeFromSearchResultInGridActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/OpenProductAttributeFromSearchResultInGridActionGroup.xml
index 31b024c82a9a0..2994533d79ed0 100644
--- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/OpenProductAttributeFromSearchResultInGridActionGroup.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/OpenProductAttributeFromSearchResultInGridActionGroup.xml
@@ -9,11 +9,15 @@
+
+ EXTENDS: SearchAttributeByCodeOnProductAttributeGridActionGroup. Click on Edit for the provided Product Attribute Code.
+
+
-
-
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/OpenProductFromCategoryPageActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/OpenProductFromCategoryPageActionGroup.xml
index c460dcbfbec91..6a9cde9c8e0b4 100644
--- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/OpenProductFromCategoryPageActionGroup.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/OpenProductFromCategoryPageActionGroup.xml
@@ -5,16 +5,22 @@
* See COPYING.txt for license details.
-->
-
+
+
+ Goto the provided Category page. Click on the provided Product page.
+
+
+
-
\ No newline at end of file
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/RestoreLayoutSettingActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/RestoreLayoutSettingActionGroup.xml
index 2f9d38516bd05..b2e371f140985 100644
--- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/RestoreLayoutSettingActionGroup.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/RestoreLayoutSettingActionGroup.xml
@@ -7,8 +7,12 @@
-->
+ xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd">
+
+ Goes to the 'Configuration' page for 'Web'. Selects 'No layout updates' for the Default Product/Category Layouts.
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/SearchAndMultiselectActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/SearchAndMultiselectActionGroup.xml
index 53e7ea3589d1e..5f82d32f67baf 100644
--- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/SearchAndMultiselectActionGroup.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/SearchAndMultiselectActionGroup.xml
@@ -9,10 +9,14 @@
+
+ Search for and select multiple items in the Magento dropdown UI component.
+
-
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/SearchAttributeByCodeOnProductAttributeGridActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/SearchAttributeByCodeOnProductAttributeGridActionGroup.xml
index 67cdd8192fcb4..de579a74d7361 100644
--- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/SearchAttributeByCodeOnProductAttributeGridActionGroup.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/SearchAttributeByCodeOnProductAttributeGridActionGroup.xml
@@ -9,9 +9,13 @@
+
+ Goto the Product Attribute grid page. Search for the provided Product Attribute Code.
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/SearchForProductOnBackendActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/SearchForProductOnBackendActionGroup.xml
index aca9ba24c1168..8b289f21f76b4 100644
--- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/SearchForProductOnBackendActionGroup.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/SearchForProductOnBackendActionGroup.xml
@@ -5,12 +5,17 @@
* See COPYING.txt for license details.
*/
-->
+
+ xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd">
+
+ Goto the Product grid page. Search for the provided Product.
+
+
@@ -20,14 +25,22 @@
+
+ Search for the provided Product Name.
+
+
+
+ Goto the Product grid page. Clear the Search Filters for the grid.
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontAddProductReviewActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontAddProductReviewActionGroup.xml
new file mode 100644
index 0000000000000..64e650580a233
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontAddProductReviewActionGroup.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontAddProductToCartWithQtyActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontAddProductToCartWithQtyActionGroup.xml
index 5432d547e8025..dcc8cdccb486b 100644
--- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontAddProductToCartWithQtyActionGroup.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontAddProductToCartWithQtyActionGroup.xml
@@ -9,9 +9,13 @@
+
+ Fills in the provided Product Quantity. Clicks on Add To Cart. Validates that the Success Message is present.
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontAddToCartCustomOptionsProductPageActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontAddToCartCustomOptionsProductPageActionGroup.xml
index 080b374c60b43..8fdd7abee7899 100644
--- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontAddToCartCustomOptionsProductPageActionGroup.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontAddToCartCustomOptionsProductPageActionGroup.xml
@@ -5,13 +5,18 @@
* See COPYING.txt for license details.
*/
-->
+
+
+ Add a Product to the Cart. Validate that the Success Message is present.
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontAssertCustomOptionByTitleActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontAssertCustomOptionByTitleActionGroup.xml
index bc922a40b05b7..eb9241426c367 100644
--- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontAssertCustomOptionByTitleActionGroup.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontAssertCustomOptionByTitleActionGroup.xml
@@ -6,11 +6,16 @@
*/
-->
-
+
+
+ Validates that the provided Product Option Title is present on the Storefront Product page.
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontAssertGiftMessageFieldsActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontAssertGiftMessageFieldsActionGroup.xml
new file mode 100644
index 0000000000000..a5f1b92862be3
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontAssertGiftMessageFieldsActionGroup.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontAssertPageNotFoundErrorOnProductDetailPageActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontAssertPageNotFoundErrorOnProductDetailPageActionGroup.xml
new file mode 100644
index 0000000000000..1de5933c6969a
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontAssertPageNotFoundErrorOnProductDetailPageActionGroup.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontAssertProductImagesOnProductPageActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontAssertProductImagesOnProductPageActionGroup.xml
index 1bb7c179dfca8..ad9fa9a576c7d 100644
--- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontAssertProductImagesOnProductPageActionGroup.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontAssertProductImagesOnProductPageActionGroup.xml
@@ -5,18 +5,31 @@
* See COPYING.txt for license details.
-->
-
+
+
+ Validate that the Product Image is present and correct. Validate that the Fullscreen Product Image is present and correct.
+
-
+
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontAssertProductInWidgetActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontAssertProductInWidgetActionGroup.xml
index c25b73bab21ae..3caa58e16654f 100644
--- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontAssertProductInWidgetActionGroup.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontAssertProductInWidgetActionGroup.xml
@@ -10,28 +10,40 @@
xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd">
+
+ Validate that the provided Product appears in the Recently Viewed Products widget.
+
+
+
+ Validate that the provided Product appears in the Recently Compared Products widget.
+
+
+
+ Validate that the provided Product appears in the Recently Ordered Products widget.
+
+
-
\ No newline at end of file
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontAssertProductNameOnProductMainPageActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontAssertProductNameOnProductMainPageActionGroup.xml
index 43a34448c8a68..157e0d726c38d 100644
--- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontAssertProductNameOnProductMainPageActionGroup.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontAssertProductNameOnProductMainPageActionGroup.xml
@@ -5,11 +5,16 @@
* See COPYING.txt for license details.
-->
-
+
+
+ Validates that the provided Product Name is present and correct on a Storefront Category page.
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontAssertProductNameOnProductPageActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontAssertProductNameOnProductPageActionGroup.xml
index 6cb156723b286..76183168a1ae1 100644
--- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontAssertProductNameOnProductPageActionGroup.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontAssertProductNameOnProductPageActionGroup.xml
@@ -5,11 +5,16 @@
* See COPYING.txt for license details.
-->
-
+
+
+ Validate that the provided Product Price is present and correct.
+
-
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontAssertProductPriceOnProductPageActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontAssertProductPriceOnProductPageActionGroup.xml
index 3c62ef89e584b..e11d9e78a4ff7 100644
--- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontAssertProductPriceOnProductPageActionGroup.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontAssertProductPriceOnProductPageActionGroup.xml
@@ -5,11 +5,16 @@
* See COPYING.txt for license details.
-->
-
+
+
+ Validate that the provided Product Price is present and correct.
+
-
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontAssertProductSkuOnProductPageActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontAssertProductSkuOnProductPageActionGroup.xml
index 85d3927a6d6d0..2ed1195f94ee0 100644
--- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontAssertProductSkuOnProductPageActionGroup.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontAssertProductSkuOnProductPageActionGroup.xml
@@ -5,11 +5,16 @@
* See COPYING.txt for license details.
-->
-
+
+
+ Validate that the provided Product Sku is present and correct.
+
-
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontAssertProductSpecialPriceOnProductPageActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontAssertProductSpecialPriceOnProductPageActionGroup.xml
index 9fefa71f10209..cb2753fa64fc6 100644
--- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontAssertProductSpecialPriceOnProductPageActionGroup.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontAssertProductSpecialPriceOnProductPageActionGroup.xml
@@ -5,12 +5,17 @@
* See COPYING.txt for license details.
-->
-
+
+
+ Goes to the provided Storefront Product page. Validates that the provided Special Price is present and correct.
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontCategoryActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontCategoryActionGroup.xml
index 7e79182616fd0..9393669f6e46d 100644
--- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontCategoryActionGroup.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontCategoryActionGroup.xml
@@ -10,6 +10,9 @@
xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd">
+
+ Goes to the Storefront Category page using URI Search Parameters.
+
@@ -17,18 +20,23 @@
+
+
+ Validate that the Category Page parameters are present and correct.
+
+
@@ -39,10 +47,14 @@
+
+ Validate that the Storefront Category is present and correct.
+
+
@@ -51,15 +63,28 @@
+
+ Validate that the provided Simple Product is present and correct on a Category page.
+
+
-
+
-
+
+
+
+
+
+ EXTENDS:StorefrontCheckCategorySimpleProduct. Removes 'AssertProductPrice', 'moveMouseOverProduct', 'AssertAddToCart'
+
+
+
+
@@ -70,16 +95,24 @@
+
+ Switch the Storefront Category view to List.
+
+
+
+ Goes to the Storefront page. Open the Parent Category menu in the Top Nav Menu. Click on a Subcategory. Validate that the Subcategory is present and correct.
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontCategoryPageSortProductActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontCategoryPageSortProductActionGroup.xml
new file mode 100644
index 0000000000000..64dd2c97a382f
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontCategoryPageSortProductActionGroup.xml
@@ -0,0 +1,32 @@
+
+
+
+
+
+
+ Select "Sort by" parameter for sorting Products on Category page
+
+
+
+
+
+
+
+
+ Set Ascending Direction for sorting Products on Category page
+
+
+
+
+
+ Set Descending Direction for sorting Products on Category page
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontCheckProductIsMissingInCategoryProductsPageActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontCheckProductIsMissingInCategoryProductsPageActionGroup.xml
new file mode 100644
index 0000000000000..01751a32d2e06
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontCheckProductIsMissingInCategoryProductsPageActionGroup.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontCheckProductPriceInCategoryActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontCheckProductPriceInCategoryActionGroup.xml
index 5c975998ab92e..ac33727564505 100644
--- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontCheckProductPriceInCategoryActionGroup.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontCheckProductPriceInCategoryActionGroup.xml
@@ -10,7 +10,21 @@
xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd">
+
+ EXTENDS: StorefrontCheckCategorySimpleProduct. Removes 'AssertProductPrice'. Validates that the provided Product Price is present and correct on a Storefront Product page.
+
+
+
+
+ Validate that the provided Product Price is correct on category page.
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontClickAddToCartOnProductPageActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontClickAddToCartOnProductPageActionGroup.xml
index 1dcbc738c7651..86ca70466c19c 100644
--- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontClickAddToCartOnProductPageActionGroup.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontClickAddToCartOnProductPageActionGroup.xml
@@ -5,11 +5,16 @@
* See COPYING.txt for license details.
*/
-->
+
-
+
+ Adds a Product to the Cart. Validate that the Success Message is present.
+
+
+
-
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontCompareActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontCompareActionGroup.xml
index 04e15da91777c..b10b74c919918 100644
--- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontCompareActionGroup.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontCompareActionGroup.xml
@@ -10,10 +10,14 @@
xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd">
+
+ Add a Product to the Compare Product list from a Category page.
+
-
+
+
@@ -21,26 +25,38 @@
+
+ Add a Product to the Compare Product list. Validate that the Success Message is present.
+
-
+
+
+
+ Validate that the Product Name is present and correct in the Compare Product list.
+
+
+
+ Open the Storefront Compare Product page. Validate that the Compare Product fields are present.
+
+
-
+
@@ -48,9 +64,13 @@
+
+ Validate that the Simple Product is present and correct in the Compare Product area.
+
+
@@ -60,6 +80,10 @@
+
+ Clear the Compare Products list. Validate that the Compare Products list is empty.
+
+
@@ -68,4 +92,4 @@
-
\ No newline at end of file
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontNavigateCategoryPageActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontNavigateCategoryPageActionGroup.xml
new file mode 100644
index 0000000000000..39cb9ef1a63d4
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontNavigateCategoryPageActionGroup.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontOpenHomePageActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontOpenHomePageActionGroup.xml
index 692d1f4266b98..ca15a755d0209 100644
--- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontOpenHomePageActionGroup.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontOpenHomePageActionGroup.xml
@@ -5,8 +5,13 @@
* See COPYING.txt for license details.
-->
-
+
+
+ Goes to the Storefront Homepage.
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontOpenProductPageActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontOpenProductPageActionGroup.xml
index f5fabae5fc4ce..899603aa27d75 100644
--- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontOpenProductPageActionGroup.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontOpenProductPageActionGroup.xml
@@ -5,11 +5,16 @@
* See COPYING.txt for license details.
-->
-
+
+
+ Goes to the Storefront Product page for the provided Product URL.
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontOpenProductPageUsingStoreCodeInUrlActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontOpenProductPageUsingStoreCodeInUrlActionGroup.xml
new file mode 100644
index 0000000000000..7c79a624d4d11
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontOpenProductPageUsingStoreCodeInUrlActionGroup.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontProductActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontProductActionGroup.xml
index 5f0d03597dab1..403b5b853d80c 100644
--- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontProductActionGroup.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontProductActionGroup.xml
@@ -10,26 +10,34 @@
xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd">
+
+ Validates that the provided Simple Product information is present and correct.
+
+
-
+
+
+ Validates that the provided Product Image is present.
+
+
@@ -37,10 +45,14 @@
+
+ Validates that the provided Product Image is present.
+
+
@@ -48,10 +60,14 @@
+
+ Validates that the provided Product Image is not present.
+
+
@@ -59,12 +75,16 @@
+
+ Validates that the provided Product Image is not present.
+
+
-
\ No newline at end of file
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontProductPageActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontProductPageActionGroup.xml
index e6392118f79b8..c960f14b1cd48 100644
--- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontProductPageActionGroup.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontProductPageActionGroup.xml
@@ -5,13 +5,18 @@
* See COPYING.txt for license details.
*/
-->
+
+
+ Click on the Add to Cart button. Validates that the Success Message is present.
+
+
@@ -22,18 +27,26 @@
+
+ EXTENDS: addToCartFromStorefrontProductPage. Fills in the provided Product Quantity for the provided Product Name.
+
+
+
+ Validates that the Product Text Option Text Length Hint displays the correct Count for multiple inputs based on the provided Character Limit.
+
+
@@ -45,19 +58,29 @@
+
+
+ Validates that the Product More Information area contains the provided Text.
+
+
+
+
+ Validate that the More Information area does not contain the provided Text.
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontSelectCustomOptionDropDownAndAssertPricesActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontSelectCustomOptionDropDownAndAssertPricesActionGroup.xml
index 39793cb8f68de..a35f84e984582 100644
--- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontSelectCustomOptionDropDownAndAssertPricesActionGroup.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontSelectCustomOptionDropDownAndAssertPricesActionGroup.xml
@@ -9,11 +9,15 @@
+
+ EXTENDS: AssertStorefrontProductPricesActionGroup. Selects the provided Custom Product Option on a Storefront Product page.
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontSelectCustomOptionRadioAndAssertPricesActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontSelectCustomOptionRadioAndAssertPricesActionGroup.xml
index 6f7bdc46640b7..96cb3c5744391 100644
--- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontSelectCustomOptionRadioAndAssertPricesActionGroup.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontSelectCustomOptionRadioAndAssertPricesActionGroup.xml
@@ -9,12 +9,16 @@
+
+ EXTENDS: AssertStorefrontProductPricesActionGroup. Clicks on the provided Custom Option on a Storefront Product page.
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/VerifyProductTypeOrderActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/VerifyProductTypeOrderActionGroup.xml
index aec21f3bc48c9..1d1aea13f6725 100644
--- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/VerifyProductTypeOrderActionGroup.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/VerifyProductTypeOrderActionGroup.xml
@@ -9,6 +9,10 @@
+
+ Clicks on the 'Add Product' toggle. Validates that the Simple/Virtual Products are listed in the correct order.
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Data/CategoryData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/CategoryData.xml
index 13951a0d197d1..6ffb4e1902424 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Data/CategoryData.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Data/CategoryData.xml
@@ -117,4 +117,14 @@
true
true
+
+ Default Category
+
+
+
+ CategoryExportImport
+
+
+ CustomAttributeCategoryNonAnchor
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Data/CustomAttributeData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/CustomAttributeData.xml
index 389c41abf0bd1..7bd392f0aa74a 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Data/CustomAttributeData.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Data/CustomAttributeData.xml
@@ -35,4 +35,24 @@
news_from_date
2018-05-17 00:00:00
+
+ description
+ Dynamicscription testing 321
+
+
+ short_description
+ Short dynamictest 555
+
+
+ description
+ Fixedscription testing 321
+
+
+ short_description
+ Short Fixedtest 555
+
+
+ is_anchor
+ 0
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Data/FrontendLabelData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/FrontendLabelData.xml
index a2bdaa7dbc62f..e4ffdbde4368d 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Data/FrontendLabelData.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Data/FrontendLabelData.xml
@@ -20,4 +20,8 @@
0
attributeThree
+
+ 0
+ attributeExportImport
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Data/ImageContentData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/ImageContentData.xml
index 1f4b1470098e2..6e40499d0efeb 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Data/ImageContentData.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Data/ImageContentData.xml
@@ -18,4 +18,7 @@
image/png
magento-logo.png
+
+ magento-logo.png
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Data/ProductAttributeData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/ProductAttributeData.xml
index 02e5ae5ae36a5..6614fa4b5dbeb 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Data/ProductAttributeData.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Data/ProductAttributeData.xml
@@ -278,6 +278,28 @@
false
ProductAttributeFrontendLabel
+
+ attribute
+ price
+ global
+ false
+ false
+ false
+ true
+ false
+ false
+ true
+ true
+ false
+ false
+ false
+ true
+ false
+ false
+ false
+ false
+ ProductAttributeFrontendLabel
+
text
defaultValue
@@ -343,4 +365,42 @@
true
ProductAttributeFrontendLabel
+
+ testattribute
+ select
+ Global
+ false
+ false
+ true
+ true
+ true
+ true
+ true
+ true
+ true
+ true
+ true
+ true
+ true
+ true
+ true
+ ProductAttributeFrontendLabel
+
+
+ swatch_visual
+ visual_swatch
+
+
+ Color
+ color_attr
+
+
+ Size
+ size_attr
+
+
+
+ attribute
+ ProductAttributeFrontendLabelForExportImport
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Data/ProductAttributeMassUpdateData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/ProductAttributeMassUpdateData.xml
new file mode 100644
index 0000000000000..99908f1c9df5f
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Data/ProductAttributeMassUpdateData.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+ New Bundle Product Name
+ This is the description
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Data/ProductAttributeMediaGalleryEntryData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/ProductAttributeMediaGalleryEntryData.xml
index 98c9a70e6aad4..75b4ef773a934 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Data/ProductAttributeMediaGalleryEntryData.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Data/ProductAttributeMediaGalleryEntryData.xml
@@ -30,4 +30,9 @@
false
MagentoLogoImageContent
+
+
+ Magento Logo
+ MagentoLogoImageContentExportImport
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Data/ProductAttributeOptionData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/ProductAttributeOptionData.xml
index fcb56cf298a98..bb0e85bcbb40b 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Data/ProductAttributeOptionData.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Data/ProductAttributeOptionData.xml
@@ -86,4 +86,11 @@
White
white
+
+
+ option1
+
+
+ option2
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Data/ProductData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/ProductData.xml
index 0b6a1e7d04439..e122615eb8aa4 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Data/ProductData.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Data/ProductData.xml
@@ -351,6 +351,11 @@
adobe-base
jpg
+
+ test_image
+ test_image.jpg
+ test_image
+
霁产品
simple
@@ -1165,4 +1170,58 @@
EavStock10
CustomAttributeProductAttribute
+
+
+ api-simple-one-export-import
+ Api Simple Product One Export Import
+
+
+ api-simple-two-export-import
+ Api Simple Product Two Export Import
+
+
+ simple-product_
+ simple
+ 4
+ 4
+ SimpleProduct_
+ 10.00
+ simple-product_
+ 1
+ EavStock1
+ CustomAttributeCategoryIds
+
+
+ virtual-product_
+ virtual
+ 4
+ VirtualProduct_
+ 10.00
+ 0
+ 1
+ EavStock1
+ CustomAttributeCategoryIds
+
+
+ simple-product-2_
+ simple
+ 4
+ SimpleProduct2_
+ 10.00
+ 4
+ 1
+ EavStock1
+
+
+ simple-product-3_
+ simple
+ 4
+ 4
+ SimpleProduct3_
+ 10.00
+ simple-product-3_
+ 1
+ EavStock1
+ CustomAttributeProductAttribute
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Data/ProductDesignData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/ProductDesignData.xml
new file mode 100644
index 0000000000000..f4c5d6e15f9ac
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Data/ProductDesignData.xml
@@ -0,0 +1,36 @@
+
+
+
+
+
+ Magento Blank
+ 2 columns with left bar
+ Product Info Column
+
+
+ Magento Luma
+ Empty
+ Block after Info Column
+
+
+ Magento Luma
+ 3 columns
+ Block after Info Column
+
+
+ Magento Blank
+ 1 column
+ Product Info Column
+
+
+ Magento Luma
+ 2 columns with right bar
+ Block after Info Column
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Page/AdminProductCreatePage.xml b/app/code/Magento/Catalog/Test/Mftf/Page/AdminProductCreatePage.xml
index e4c4ece5ac6cf..5c7cb4d51084f 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Page/AdminProductCreatePage.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Page/AdminProductCreatePage.xml
@@ -18,7 +18,6 @@
-
diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminCatalogStorefrontConfigSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminCatalogStorefrontConfigSection.xml
new file mode 100644
index 0000000000000..d0200f1e0a5b0
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminCatalogStorefrontConfigSection.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategoryBasicFieldSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategoryBasicFieldSection.xml
index 977e63b9ec927..92961cc48212a 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategoryBasicFieldSection.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategoryBasicFieldSection.xml
@@ -49,7 +49,7 @@
-
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategoryContentSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategoryContentSection.xml
index e3d224904671b..1cb095974d0fd 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategoryContentSection.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategoryContentSection.xml
@@ -24,5 +24,6 @@
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategoryProductsGridSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategoryProductsGridSection.xml
index df79ec61ef736..6618b0e1a48d8 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategoryProductsGridSection.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategoryProductsGridSection.xml
@@ -17,5 +17,6 @@
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategorySidebarTreeSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategorySidebarTreeSection.xml
index ce7d962f3ec77..bc552721e6ab8 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategorySidebarTreeSection.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategorySidebarTreeSection.xml
@@ -11,10 +11,10 @@
+
+
-
-
diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminCreateProductAttributeSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminCreateProductAttributeSection.xml
index 9b75f7e6908a2..462f721913b9c 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminCreateProductAttributeSection.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminCreateProductAttributeSection.xml
@@ -20,11 +20,11 @@
-
+
-
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductCustomizableOptionsSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductCustomizableOptionsSection.xml
index a7ab0c3d8e404..6d4d5d86ef798 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductCustomizableOptionsSection.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductCustomizableOptionsSection.xml
@@ -9,7 +9,7 @@
-
+
@@ -23,8 +23,8 @@
-
-
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFormAdvancedPricingSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFormAdvancedPricingSection.xml
index a17bd3794cffd..77b89a07fb76a 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFormAdvancedPricingSection.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFormAdvancedPricingSection.xml
@@ -21,9 +21,9 @@
-
+
-
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFormSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFormSection.xml
index 84dd83ed4ef2a..98b23a4669b72 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFormSection.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFormSection.xml
@@ -34,11 +34,11 @@
-
+
-
+
@@ -48,7 +48,7 @@
-
+
@@ -66,8 +66,8 @@
-
-
+
+
@@ -79,10 +79,12 @@
+
@@ -119,10 +121,10 @@
-
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductGiftOptionsSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductGiftOptionsSection.xml
new file mode 100644
index 0000000000000..63b745e522705
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductGiftOptionsSection.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductGridActionSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductGridActionSection.xml
index 3b74041684017..66e6f17be3430 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductGridActionSection.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductGridActionSection.xml
@@ -10,7 +10,7 @@
xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd">
-
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductRelatedUpSellCrossSellSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductRelatedUpSellCrossSellSection.xml
index ef596bed186e5..f3b0d3a895cb1 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductRelatedUpSellCrossSellSection.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductRelatedUpSellCrossSellSection.xml
@@ -20,6 +20,7 @@
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductSEOSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductSEOSection.xml
index 53231a2a68633..8685e84a347f2 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductSEOSection.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductSEOSection.xml
@@ -12,5 +12,8 @@
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminUpdateAttributesSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminUpdateAttributesSection.xml
index 53af1d5bd6eb1..69d7297628d56 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminUpdateAttributesSection.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminUpdateAttributesSection.xml
@@ -36,6 +36,7 @@
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontCategoryFilterSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontCategoryFilterSection.xml
index ddec4428f90e2..faee605e77319 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontCategoryFilterSection.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontCategoryFilterSection.xml
@@ -11,5 +11,6 @@
diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontCategoryMainSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontCategoryMainSection.xml
index 1cd64544d9636..ac7a15daf56aa 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontCategoryMainSection.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontCategoryMainSection.xml
@@ -24,7 +24,7 @@
-
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontProductActionSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontProductActionSection.xml
index 98dc5e764fd77..5ee754904b702 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontProductActionSection.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontProductActionSection.xml
@@ -9,7 +9,7 @@
xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd">
-
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontProductInfoDetailsSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontProductInfoDetailsSection.xml
index 0745c0d0819a0..148c3fbe96610 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontProductInfoDetailsSection.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontProductInfoDetailsSection.xml
@@ -11,5 +11,7 @@
diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontProductMediaSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontProductMediaSection.xml
index ea10e12fb73f5..6ed359e35ab59 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontProductMediaSection.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontProductMediaSection.xml
@@ -16,5 +16,6 @@
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontProductMoreInformationSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontProductMoreInformationSection.xml
index 7706c5f244bc9..0c6d667a5dd75 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontProductMoreInformationSection.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontProductMoreInformationSection.xml
@@ -16,5 +16,7 @@
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontProductPageDesignSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontProductPageDesignSection.xml
new file mode 100644
index 0000000000000..c8562ec08d6b3
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontProductPageDesignSection.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontProductReviewsSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontProductReviewsSection.xml
new file mode 100644
index 0000000000000..8b9b8ba289799
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontProductReviewsSection.xml
@@ -0,0 +1,24 @@
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AddOutOfStockProductToCompareListTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AddOutOfStockProductToCompareListTest.xml
index 044b38a19c4ea..31204c7b4b0bf 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/AddOutOfStockProductToCompareListTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AddOutOfStockProductToCompareListTest.xml
@@ -18,6 +18,9 @@
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminAddImageToWYSIWYGProductTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminAddImageToWYSIWYGProductTest.xml
index f3d3e653b260b..ee105320c5f29 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminAddImageToWYSIWYGProductTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminAddImageToWYSIWYGProductTest.xml
@@ -16,9 +16,6 @@
-
-
-
@@ -78,6 +75,8 @@
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCheckPaginationInStorefrontTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCheckPaginationInStorefrontTest.xml
index f40a62c164ecc..1b72458747067 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCheckPaginationInStorefrontTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCheckPaginationInStorefrontTest.xml
@@ -41,6 +41,16 @@
+
+
+
+
+
+
+
+
+
+
@@ -67,18 +77,36 @@
+
+
+
+
+
+
+
+
+
+
-
+
+
+
+
+
+
+
+
+
-
+
@@ -86,91 +114,92 @@
-
+
+
-
-
+
+
-
+
-
-
+
+
-
-
+
-
-
+
+
+
-
-
+
+
-
-
+
+
-
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
-
+
+
+
-
+
-
+
-
-
+
+
-
-
+
+
-
+
-
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCloneProductWithDuplicateUrlTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCloneProductWithDuplicateUrlTest.xml
new file mode 100644
index 0000000000000..99adaeb522786
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCloneProductWithDuplicateUrlTest.xml
@@ -0,0 +1,80 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateAndEditSimpleProductSettingsTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateAndEditSimpleProductSettingsTest.xml
new file mode 100644
index 0000000000000..8ffab42653b49
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateAndEditSimpleProductSettingsTest.xml
@@ -0,0 +1,136 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateAndEditVirtualProductSettingsTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateAndEditVirtualProductSettingsTest.xml
new file mode 100644
index 0000000000000..90cbab59bd71d
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateAndEditVirtualProductSettingsTest.xml
@@ -0,0 +1,190 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateAndSwitchProductType.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateAndSwitchProductType.xml
old mode 100755
new mode 100644
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateVirtualProductOutOfStockWithTierPriceTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateVirtualProductOutOfStockWithTierPriceTest.xml
index e516a046e489e..0b929eaddc96e 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateVirtualProductOutOfStockWithTierPriceTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateVirtualProductOutOfStockWithTierPriceTest.xml
@@ -17,16 +17,17 @@
-
-
-
-
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateVirtualProductWithCustomOptionsSuiteAndImportOptionsTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateVirtualProductWithCustomOptionsSuiteAndImportOptionsTest.xml
index 6ee3fa6c60b82..58737dd509743 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateVirtualProductWithCustomOptionsSuiteAndImportOptionsTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateVirtualProductWithCustomOptionsSuiteAndImportOptionsTest.xml
@@ -17,16 +17,17 @@
-
-
-
-
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateVirtualProductWithTierPriceForGeneralGroupTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateVirtualProductWithTierPriceForGeneralGroupTest.xml
index b777babf3b3aa..78247f4943596 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateVirtualProductWithTierPriceForGeneralGroupTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateVirtualProductWithTierPriceForGeneralGroupTest.xml
@@ -17,9 +17,6 @@
-
-
-
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateVirtualProductWithTierPriceTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateVirtualProductWithTierPriceTest.xml
index 28923674f4ab3..6ef2569945fa6 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateVirtualProductWithTierPriceTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateVirtualProductWithTierPriceTest.xml
@@ -17,9 +17,6 @@
-
-
-
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateVirtualProductWithoutManageStockTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateVirtualProductWithoutManageStockTest.xml
index 8c5085501b799..cb41b0292d33a 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateVirtualProductWithoutManageStockTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateVirtualProductWithoutManageStockTest.xml
@@ -17,9 +17,6 @@
-
-
-
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminGridPageNumberAfterSaveAndCloseActionTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminGridPageNumberAfterSaveAndCloseActionTest.xml
index 672205d935dab..df6ddfa169029 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminGridPageNumberAfterSaveAndCloseActionTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminGridPageNumberAfterSaveAndCloseActionTest.xml
@@ -15,12 +15,9 @@
-
-
+
+
-
-
-
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminMassProductPriceUpdateTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminMassProductPriceUpdateTest.xml
index 4d581bae700d7..f5ad5b8079d1f 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminMassProductPriceUpdateTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminMassProductPriceUpdateTest.xml
@@ -10,7 +10,7 @@
xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd">
-
+
@@ -24,8 +24,8 @@
-
-
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminProductCustomURLKeyPreservedWhenAssignedToCategoryWithoutCustomURLKey.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminProductCustomURLKeyPreservedWhenAssignedToCategoryWithoutCustomURLKey.xml
new file mode 100644
index 0000000000000..bae81513de632
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminProductCustomURLKeyPreservedWhenAssignedToCategoryWithoutCustomURLKey.xml
@@ -0,0 +1,117 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminProductTypeSwitchingOnEditingTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminProductTypeSwitchingOnEditingTest.xml
new file mode 100644
index 0000000000000..a11646cc46875
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminProductTypeSwitchingOnEditingTest.xml
@@ -0,0 +1,96 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminRestrictedUserAddCategoryFromProductPageTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminRestrictedUserAddCategoryFromProductPageTest.xml
index cd401b7a4651a..7b5455951fb27 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminRestrictedUserAddCategoryFromProductPageTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminRestrictedUserAddCategoryFromProductPageTest.xml
@@ -11,6 +11,7 @@
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminShouldBeAbleToAssociateSimpleProductToWebsitesTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminShouldBeAbleToAssociateSimpleProductToWebsitesTest.xml
new file mode 100644
index 0000000000000..061c30b224828
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminShouldBeAbleToAssociateSimpleProductToWebsitesTest.xml
@@ -0,0 +1,83 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminSimpleSetEditRelatedProductsTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminSimpleSetEditRelatedProductsTest.xml
index b06502ce94c65..4803c1be6c936 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminSimpleSetEditRelatedProductsTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminSimpleSetEditRelatedProductsTest.xml
@@ -22,6 +22,11 @@
+
+
+
+
+
@@ -31,6 +36,11 @@
+
+
+
+
+
@@ -69,6 +79,28 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminSortingByWebsitesTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminSortingByWebsitesTest.xml
index eb014ca7f884d..5b6207e135796 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminSortingByWebsitesTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminSortingByWebsitesTest.xml
@@ -40,6 +40,8 @@
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUnassignProductAttributeFromAttributeSetTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUnassignProductAttributeFromAttributeSetTest.xml
index a33c7bb12879a..bcc8636c65b1e 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUnassignProductAttributeFromAttributeSetTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUnassignProductAttributeFromAttributeSetTest.xml
@@ -34,6 +34,7 @@
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductTieredPriceTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductTieredPriceTest.xml
index d151bae3ee110..f317c66e5366a 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductTieredPriceTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductTieredPriceTest.xml
@@ -17,9 +17,6 @@
-
-
-
@@ -68,8 +65,10 @@
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductWithRegularPriceInStockEnabledFlatTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductWithRegularPriceInStockEnabledFlatTest.xml
index d30500de64a32..afb8b40a6dbd4 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductWithRegularPriceInStockEnabledFlatTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductWithRegularPriceInStockEnabledFlatTest.xml
@@ -17,9 +17,6 @@
-
-
-
@@ -65,8 +62,10 @@
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductWithRegularPriceInStockNotVisibleIndividuallyTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductWithRegularPriceInStockNotVisibleIndividuallyTest.xml
index cb7b3d6278aa8..2436fc0fc7f12 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductWithRegularPriceInStockNotVisibleIndividuallyTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductWithRegularPriceInStockNotVisibleIndividuallyTest.xml
@@ -17,9 +17,6 @@
-
-
-
@@ -56,8 +53,10 @@
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductWithRegularPriceInStockVisibleInCatalogAndSearchTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductWithRegularPriceInStockVisibleInCatalogAndSearchTest.xml
index 75b4a9728d08b..637ae790c16c8 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductWithRegularPriceInStockVisibleInCatalogAndSearchTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductWithRegularPriceInStockVisibleInCatalogAndSearchTest.xml
@@ -17,9 +17,6 @@
-
-
-
@@ -56,8 +53,10 @@
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductWithRegularPriceInStockVisibleInCatalogOnlyTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductWithRegularPriceInStockVisibleInCatalogOnlyTest.xml
index f8b0b17c06253..045b3f3420ff6 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductWithRegularPriceInStockVisibleInCatalogOnlyTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductWithRegularPriceInStockVisibleInCatalogOnlyTest.xml
@@ -17,9 +17,6 @@
-
-
-
@@ -56,8 +53,10 @@
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductWithRegularPriceInStockVisibleInSearchOnlyTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductWithRegularPriceInStockVisibleInSearchOnlyTest.xml
index ee2a2514c9c7e..214f9b0273b6a 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductWithRegularPriceInStockVisibleInSearchOnlyTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductWithRegularPriceInStockVisibleInSearchOnlyTest.xml
@@ -17,9 +17,6 @@
-
-
-
@@ -56,8 +53,10 @@
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductWithRegularPriceInStockWithCustomOptionsTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductWithRegularPriceInStockWithCustomOptionsTest.xml
index 7921ed6c3e459..b145328890a91 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductWithRegularPriceInStockWithCustomOptionsTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductWithRegularPriceInStockWithCustomOptionsTest.xml
@@ -17,9 +17,6 @@
-
-
-
@@ -56,8 +53,10 @@
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductWithRegularPriceOutOfStockTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductWithRegularPriceOutOfStockTest.xml
index 0125b4c1e713d..27c7e77a94ad1 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductWithRegularPriceOutOfStockTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductWithRegularPriceOutOfStockTest.xml
@@ -17,9 +17,6 @@
-
-
-
@@ -56,8 +53,10 @@
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithRegularPriceInStockVisibleInCategoryOnlyTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithRegularPriceInStockVisibleInCategoryOnlyTest.xml
index d7ceefb03d3b1..8ac56d09e5b42 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithRegularPriceInStockVisibleInCategoryOnlyTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithRegularPriceInStockVisibleInCategoryOnlyTest.xml
@@ -17,9 +17,6 @@
-
-
-
@@ -65,8 +62,10 @@
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithRegularPriceInStockWithCustomOptionsVisibleInSearchOnlyTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithRegularPriceInStockWithCustomOptionsVisibleInSearchOnlyTest.xml
index ef02b73ea16d4..f621813f4f8c0 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithRegularPriceInStockWithCustomOptionsVisibleInSearchOnlyTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithRegularPriceInStockWithCustomOptionsVisibleInSearchOnlyTest.xml
@@ -17,9 +17,6 @@
-
-
-
@@ -55,8 +52,10 @@
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithRegularPriceOutOfStockVisibleInCategoryOnlyTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithRegularPriceOutOfStockVisibleInCategoryOnlyTest.xml
index b5ee6d8112b50..ffbad0752b73c 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithRegularPriceOutOfStockVisibleInCategoryOnlyTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithRegularPriceOutOfStockVisibleInCategoryOnlyTest.xml
@@ -17,9 +17,6 @@
-
-
-
@@ -54,8 +51,10 @@
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithSpecialPriceInStockVisibleInCategoryAndSearchTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithSpecialPriceInStockVisibleInCategoryAndSearchTest.xml
index 64342d18198c5..3101c1e460322 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithSpecialPriceInStockVisibleInCategoryAndSearchTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithSpecialPriceInStockVisibleInCategoryAndSearchTest.xml
@@ -17,9 +17,6 @@
-
-
-
@@ -60,8 +57,10 @@
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithSpecialPriceOutOfStockVisibleInCategoryAndSearchTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithSpecialPriceOutOfStockVisibleInCategoryAndSearchTest.xml
index 7378a7f111c30..58978c31b5b40 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithSpecialPriceOutOfStockVisibleInCategoryAndSearchTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithSpecialPriceOutOfStockVisibleInCategoryAndSearchTest.xml
@@ -17,9 +17,6 @@
-
-
-
@@ -59,8 +56,10 @@
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithTierPriceInStockVisibleInCategoryAndSearchTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithTierPriceInStockVisibleInCategoryAndSearchTest.xml
index ddb4002a5dba3..d28e9ddbb1271 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithTierPriceInStockVisibleInCategoryAndSearchTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithTierPriceInStockVisibleInCategoryAndSearchTest.xml
@@ -17,9 +17,6 @@
-
-
-
@@ -65,8 +62,10 @@
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithTierPriceInStockVisibleInCategoryOnlyTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithTierPriceInStockVisibleInCategoryOnlyTest.xml
index 9aff504f58f81..22dd2b0054db4 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithTierPriceInStockVisibleInCategoryOnlyTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithTierPriceInStockVisibleInCategoryOnlyTest.xml
@@ -17,9 +17,6 @@
-
-
-
@@ -65,8 +62,10 @@
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithTierPriceOutOfStockVisibleInCategoryAndSearchTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithTierPriceOutOfStockVisibleInCategoryAndSearchTest.xml
index 41796bcc2c3a9..29c7536d21621 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithTierPriceOutOfStockVisibleInCategoryAndSearchTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithTierPriceOutOfStockVisibleInCategoryAndSearchTest.xml
@@ -17,9 +17,6 @@
-
-
-
@@ -65,8 +62,10 @@
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/DisplayRefreshCacheAfterChangingCategoryPageLayoutTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/DisplayRefreshCacheAfterChangingCategoryPageLayoutTest.xml
new file mode 100644
index 0000000000000..5a94dd4f04d24
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/DisplayRefreshCacheAfterChangingCategoryPageLayoutTest.xml
@@ -0,0 +1,51 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/EndToEndB2CGuestUserTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/EndToEndB2CGuestUserTest.xml
index e3924099d2f27..7c0de6da18caf 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/EndToEndB2CGuestUserTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/EndToEndB2CGuestUserTest.xml
@@ -17,9 +17,6 @@
-
-
-
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontCatalogNavigationMenuUIDesktopTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontCatalogNavigationMenuUIDesktopTest.xml
new file mode 100644
index 0000000000000..ae54b72a5a702
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontCatalogNavigationMenuUIDesktopTest.xml
@@ -0,0 +1,336 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Category A
+
+
+
+
+
+
+
+
+ TEST
+
+
+ _test2
+
+
+ test 3
+
+
+ Category with several products
+
+
+ test 5
+
+
+ test 8
+
+
+ This is a very very very very very looong title
+
+
+ test 6
+
+
+ test 7
+
+
+ test 4
+
+
+ Category with image
+
+
+ test 0
+
+
+ Category with description & custom title
+
+
+ Category with children
+
+
+ level 1 test category very very very long name
+
+
+
+ level 1 test category name
+
+
+
+ level 1 with children
+
+
+
+ level 2 with children
+
+
+
+ level 3 test
+
+
+
+ level 4
+
+
+
+ level 4 test
+
+
+
+ level 5
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/CheckCurrentCategoryIsHighlightedAndProductsDisplayed.xml b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontCategoryHighlightedAndProductDisplayedTest.xml
similarity index 82%
rename from app/code/Magento/Catalog/Test/Mftf/Test/CheckCurrentCategoryIsHighlightedAndProductsDisplayed.xml
rename to app/code/Magento/Catalog/Test/Mftf/Test/StorefrontCategoryHighlightedAndProductDisplayedTest.xml
index d2fe983cb82e9..3e72df9133898 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/CheckCurrentCategoryIsHighlightedAndProductsDisplayed.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontCategoryHighlightedAndProductDisplayedTest.xml
@@ -8,13 +8,14 @@
-
+
+
-
+
@@ -30,10 +31,18 @@
+
+
+
+
+
+
+
+
@@ -68,5 +77,9 @@
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontCheckDefaultNumberProductsToDisplayTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontCheckDefaultNumberProductsToDisplayTest.xml
new file mode 100644
index 0000000000000..ac2605ff5f3e2
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontCheckDefaultNumberProductsToDisplayTest.xml
@@ -0,0 +1,201 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontEnsureThatAccordionAnchorIsVisibleOnViewportOnceClickedTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontEnsureThatAccordionAnchorIsVisibleOnViewportOnceClickedTest.xml
new file mode 100644
index 0000000000000..0f9f542a97d02
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontEnsureThatAccordionAnchorIsVisibleOnViewportOnceClickedTest.xml
@@ -0,0 +1,178 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontPurchaseProductWithCustomOptionsWithLongValuesTitle.xml b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontPurchaseProductWithCustomOptionsWithLongValuesTitle.xml
index 294dcb8c1b81a..e9e23cf157a26 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontPurchaseProductWithCustomOptionsWithLongValuesTitle.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontPurchaseProductWithCustomOptionsWithLongValuesTitle.xml
@@ -17,6 +17,9 @@
+
+
+
@@ -114,6 +117,6 @@
-
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/VerifyCategoryProductAndProductCategoryPartialReindexTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/VerifyCategoryProductAndProductCategoryPartialReindexTest.xml
new file mode 100644
index 0000000000000..6184a220f047c
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/VerifyCategoryProductAndProductCategoryPartialReindexTest.xml
@@ -0,0 +1,226 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Unit/Block/Rss/CategoryTest.php b/app/code/Magento/Catalog/Test/Unit/Block/Rss/CategoryTest.php
index 1fec3d34c330b..978ebe944d7ed 100644
--- a/app/code/Magento/Catalog/Test/Unit/Block/Rss/CategoryTest.php
+++ b/app/code/Magento/Catalog/Test/Unit/Block/Rss/CategoryTest.php
@@ -3,6 +3,7 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
+
namespace Magento\Catalog\Test\Unit\Block\Rss;
use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper;
@@ -73,6 +74,16 @@ class CategoryTest extends \PHPUnit\Framework\TestCase
*/
protected $categoryRepository;
+ /**
+ * @var \Magento\Framework\View\ConfigInterface|\PHPUnit_Framework_MockObject_MockObject
+ */
+ protected $viewConfig;
+
+ /**
+ * @var \Magento\Framework\Config\View
+ */
+ protected $configView;
+
/**
* @var array
*/
@@ -115,6 +126,8 @@ protected function setUp()
$this->storeManager->expects($this->any())->method('getStore')->will($this->returnValue($store));
$this->scopeConfig = $this->createMock(\Magento\Framework\App\Config\ScopeConfigInterface::class);
$this->categoryRepository = $this->createMock(\Magento\Catalog\Api\CategoryRepositoryInterface::class);
+ $this->viewConfig = $this->getMockBuilder(\Magento\Framework\View\ConfigInterface::class)
+ ->getMockForAbstractClass();
$objectManagerHelper = new ObjectManagerHelper($this);
$this->block = $objectManagerHelper->getObject(
\Magento\Catalog\Block\Rss\Category::class,
@@ -130,6 +143,7 @@ protected function setUp()
'customerSession' => $this->customerSession,
'storeManager' => $this->storeManager,
'categoryRepository' => $this->categoryRepository,
+ 'viewConfig' => $this->viewConfig,
]
);
}
@@ -145,16 +159,26 @@ public function testGetRssData()
$this->categoryRepository->expects($this->once())->method('get')->will($this->returnValue($category));
+ $configViewMock = $this->getMockBuilder(\Magento\Framework\Config\View::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $this->viewConfig->expects($this->once())
+ ->method('getViewConfig')
+ ->willReturn($configViewMock);
+
$product = $this->getMockBuilder(\Magento\catalog\Model\Product::class)
- ->setMethods([
- '__sleep',
- '__wakeup',
- 'getName',
- 'getAllowedInRss',
- 'getProductUrl',
- 'getDescription',
- 'getAllowedPriceInRss',
- ])->disableOriginalConstructor()->getMock();
+ ->setMethods(
+ [
+ '__sleep',
+ '__wakeup',
+ 'getName',
+ 'getAllowedInRss',
+ 'getProductUrl',
+ 'getDescription',
+ 'getAllowedPriceInRss'
+ ]
+ )->disableOriginalConstructor()->getMock();
$product->expects($this->once())->method('getName')->will($this->returnValue('Product Name'));
$product->expects($this->once())->method('getAllowedInRss')->will($this->returnValue(true));
$product->expects($this->exactly(2))->method('getProductUrl')
@@ -213,23 +237,23 @@ public function testGetFeeds()
->disableOriginalConstructor()->getMock();
$collection = $this->getMockBuilder(\Magento\Catalog\Model\ResourceModel\Category\Collection::class)
- ->setMethods([
- 'addIdFilter',
- 'addAttributeToSelect',
- 'addAttributeToSort',
- 'load',
- 'addAttributeToFilter',
- 'getIterator',
- ])->disableOriginalConstructor()->getMock();
+ ->setMethods(
+ [
+ 'addIdFilter',
+ 'addAttributeToSelect',
+ 'addAttributeToSort',
+ 'load',
+ 'addAttributeToFilter',
+ 'getIterator'
+ ]
+ )->disableOriginalConstructor()->getMock();
$collection->expects($this->once())->method('addIdFilter')->will($this->returnSelf());
$collection->expects($this->exactly(3))->method('addAttributeToSelect')->will($this->returnSelf());
$collection->expects($this->once())->method('addAttributeToSort')->will($this->returnSelf());
$collection->expects($this->once())->method('addAttributeToFilter')->will($this->returnSelf());
$collection->expects($this->once())->method('load')->will($this->returnSelf());
- $collection->expects($this->once())->method('getIterator')->will($this->returnValue(
- new \ArrayIterator([$category])
- ));
-
+ $collection->expects($this->once())->method('getIterator')
+ ->will($this->returnValue(new \ArrayIterator([$category])));
$category->expects($this->once())->method('getId')->will($this->returnValue(1));
$category->expects($this->once())->method('getName')->will($this->returnValue('Category Name'));
$category->expects($this->once())->method('getResourceCollection')->will($this->returnValue($collection));
@@ -250,9 +274,12 @@ public function testGetFeeds()
$this->rssUrlBuilder->expects($this->once())->method('getUrl')
->will($this->returnValue('http://magento.com/category-name.html'));
- $feeds = ['group' => 'Categories', 'feeds' => [
- ['label' => 'Category Name', 'link' => 'http://magento.com/category-name.html'],
- ]];
+ $feeds = [
+ 'group' => 'Categories',
+ 'feeds' => [
+ ['label' => 'Category Name', 'link' => 'http://magento.com/category-name.html'],
+ ]
+ ];
$this->assertEquals($feeds, $this->block->getFeeds());
}
}
diff --git a/app/code/Magento/Catalog/Test/Unit/Block/Widget/LinkTest.php b/app/code/Magento/Catalog/Test/Unit/Block/Widget/LinkTest.php
index 8333ed22e1da0..dcbd3161733aa 100644
--- a/app/code/Magento/Catalog/Test/Unit/Block/Widget/LinkTest.php
+++ b/app/code/Magento/Catalog/Test/Unit/Block/Widget/LinkTest.php
@@ -5,54 +5,81 @@
*/
namespace Magento\Catalog\Test\Unit\Block\Widget;
+use Exception;
+use Magento\Catalog\Block\Widget\Link;
+use Magento\Catalog\Model\ResourceModel\AbstractResource;
use Magento\CatalogUrlRewrite\Model\ProductUrlRewriteGenerator;
+use Magento\Framework\App\Config\ReinitableConfigInterface;
use Magento\Framework\TestFramework\Unit\Helper\ObjectManager;
+use Magento\Framework\Url;
+use Magento\Framework\Url\ModifierInterface;
+use Magento\Framework\View\Element\Template\Context;
+use Magento\Store\Model\Store;
+use Magento\Store\Model\StoreManagerInterface;
+use Magento\UrlRewrite\Model\UrlFinderInterface;
use Magento\UrlRewrite\Service\V1\Data\UrlRewrite;
+use PHPUnit\Framework\TestCase;
+use PHPUnit_Framework_MockObject_MockObject;
+use ReflectionClass;
+use RuntimeException;
-class LinkTest extends \PHPUnit\Framework\TestCase
+/**
+ * @SuppressWarnings(PHPMD.UnusedFormalParameter)
+ * @SuppressWarnings(PHPMD.UnusedLocalVariable)
+ * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
+ */
+class LinkTest extends TestCase
{
/**
- * @var \PHPUnit_Framework_MockObject_MockObject|\Magento\Store\Model\StoreManagerInterface
+ * @var PHPUnit_Framework_MockObject_MockObject|StoreManagerInterface
*/
protected $storeManager;
/**
- * @var \PHPUnit_Framework_MockObject_MockObject|\Magento\UrlRewrite\Model\UrlFinderInterface
+ * @var PHPUnit_Framework_MockObject_MockObject|UrlFinderInterface
*/
protected $urlFinder;
/**
- * @var \Magento\Catalog\Block\Widget\Link
+ * @var Link
*/
protected $block;
/**
- * @var \Magento\Catalog\Model\ResourceModel\AbstractResource|\PHPUnit_Framework_MockObject_MockObject
+ * @var AbstractResource|PHPUnit_Framework_MockObject_MockObject
*/
protected $entityResource;
+ /**
+ * @inheritDoc
+ */
protected function setUp()
{
- $this->storeManager = $this->createMock(\Magento\Store\Model\StoreManagerInterface::class);
- $this->urlFinder = $this->createMock(\Magento\UrlRewrite\Model\UrlFinderInterface::class);
+ $this->storeManager = $this->createMock(StoreManagerInterface::class);
+ $this->urlFinder = $this->createMock(UrlFinderInterface::class);
- $context = $this->createMock(\Magento\Framework\View\Element\Template\Context::class);
+ $context = $this->createMock(Context::class);
$context->expects($this->any())
->method('getStoreManager')
->will($this->returnValue($this->storeManager));
$this->entityResource =
- $this->createMock(\Magento\Catalog\Model\ResourceModel\AbstractResource::class);
-
- $this->block = (new ObjectManager($this))->getObject(\Magento\Catalog\Block\Widget\Link::class, [
- 'context' => $context,
- 'urlFinder' => $this->urlFinder,
- 'entityResource' => $this->entityResource
- ]);
+ $this->createMock(AbstractResource::class);
+
+ $this->block = (new ObjectManager($this))->getObject(
+ Link::class,
+ [
+ 'context' => $context,
+ 'urlFinder' => $this->urlFinder,
+ 'entityResource' => $this->entityResource
+ ]
+ );
}
/**
- * @expectedException \RuntimeException
+ * Tests getHref with wrong id_path
+ *
+ * @expectedException RuntimeException
* @expectedExceptionMessage Parameter id_path is not set.
*/
public function testGetHrefWithoutSetIdPath()
@@ -61,7 +88,9 @@ public function testGetHrefWithoutSetIdPath()
}
/**
- * @expectedException \RuntimeException
+ * Tests getHref with wrong id_path
+ *
+ * @expectedException RuntimeException
* @expectedExceptionMessage Wrong id_path structure.
*/
public function testGetHrefIfSetWrongIdPath()
@@ -70,27 +99,30 @@ public function testGetHrefIfSetWrongIdPath()
$this->block->getHref();
}
+ /**
+ * Tests getHref with wrong store ID
+ *
+ * @expectedException Exception
+ */
public function testGetHrefWithSetStoreId()
{
$this->block->setData('id_path', 'type/id');
$this->block->setData('store_id', 'store_id');
-
$this->storeManager->expects($this->once())
- ->method('getStore')->with('store_id')
- // interrupt test execution
- ->will($this->throwException(new \Exception()));
-
- try {
- $this->block->getHref();
- } catch (\Exception $e) {
- }
+ ->method('getStore')
+ ->with('store_id')
+ ->will($this->throwException(new Exception()));
+ $this->block->getHref();
}
+ /**
+ * Tests getHref with not found URL
+ */
public function testGetHrefIfRewriteIsNotFound()
{
$this->block->setData('id_path', 'entity_type/entity_id');
- $store = $this->createPartialMock(\Magento\Store\Model\Store::class, ['getId', '__wakeUp']);
+ $store = $this->createPartialMock(Store::class, ['getId', '__wakeUp']);
$store->expects($this->any())
->method('getId');
@@ -105,52 +137,107 @@ public function testGetHrefIfRewriteIsNotFound()
}
/**
- * @param string $url
- * @param string $separator
+ * Tests getHref whether it should include the store code or not
+ *
* @dataProvider dataProviderForTestGetHrefWithoutUrlStoreSuffix
+ * @param string $path
+ * @param int|null $storeId
+ * @param bool $includeStoreCode
+ * @param string $expected
+ * @throws \ReflectionException
*/
- public function testGetHrefWithoutUrlStoreSuffix($url, $separator)
- {
- $storeId = 15;
- $storeCode = 'store-code';
- $requestPath = 'request-path';
+ public function testStoreCodeShouldBeIncludedInURLOnlyIfItIsConfiguredSo(
+ string $path,
+ ?int $storeId,
+ bool $includeStoreCode,
+ string $expected
+ ) {
$this->block->setData('id_path', 'entity_type/entity_id');
-
- $rewrite = $this->createMock(\Magento\UrlRewrite\Service\V1\Data\UrlRewrite::class);
- $rewrite->expects($this->once())
- ->method('getRequestPath')
- ->will($this->returnValue($requestPath));
-
- $store = $this->createPartialMock(
- \Magento\Store\Model\Store::class,
- ['getId', 'getUrl', 'getCode', '__wakeUp']
+ $this->block->setData('store_id', $storeId);
+ $objectManager = new ObjectManager($this);
+
+ $rewrite = $this->createPartialMock(UrlRewrite::class, ['getRequestPath']);
+ $url = $this->createPartialMock(Url::class, ['setScope', 'getUrl']);
+ $urlModifier = $this->getMockForAbstractClass(ModifierInterface::class);
+ $config = $this->getMockForAbstractClass(ReinitableConfigInterface::class);
+ $store = $objectManager->getObject(
+ Store::class,
+ [
+ 'storeManager' => $this->storeManager,
+ 'url' => $url,
+ 'config' => $config
+ ]
);
- $store->expects($this->once())
- ->method('getId')
- ->will($this->returnValue($storeId));
- $store->expects($this->once())
+ $property = (new ReflectionClass(get_class($store)))->getProperty('urlModifier');
+ $property->setAccessible(true);
+ $property->setValue($store, $urlModifier);
+
+ $urlModifier->expects($this->any())
+ ->method('execute')
+ ->willReturnArgument(0);
+ $config->expects($this->any())
+ ->method('getValue')
+ ->willReturnMap(
+ [
+ [Store::XML_PATH_USE_REWRITES, ReinitableConfigInterface::SCOPE_TYPE_DEFAULT, null, true],
+ [
+ Store::XML_PATH_STORE_IN_URL,
+ ReinitableConfigInterface::SCOPE_TYPE_DEFAULT,
+ null, $includeStoreCode
+ ]
+ ]
+ );
+
+ $url->expects($this->any())
+ ->method('setScope')
+ ->willReturnSelf();
+
+ $url->expects($this->any())
->method('getUrl')
- ->with('', ['_direct' => $requestPath])
- ->will($this->returnValue($url));
- $store->expects($this->once())
- ->method('getCode')
- ->will($this->returnValue($storeCode));
+ ->willReturnCallback(
+ function ($route, $params) use ($storeId) {
+ $baseUrl = rtrim($this->storeManager->getStore($storeId)->getBaseUrl(), '/');
+ return $baseUrl .'/' . ltrim($params['_direct'], '/');
+ }
+ );
- $this->storeManager->expects($this->once())
- ->method('getStore')
- ->will($this->returnValue($store));
+ $store->addData(['store_id' => 1, 'code' => 'french']);
- $this->urlFinder->expects($this->once())->method('findOneByData')
- ->with([
+ $store2 = clone $store;
+ $store2->addData(['store_id' => 2, 'code' => 'german']);
+
+ $this->storeManager
+ ->expects($this->any())
+ ->method('getStore')
+ ->willReturnMap(
+ [
+ [null, $store],
+ [1, $store],
+ [2, $store2],
+ ]
+ );
+
+ $this->urlFinder->expects($this->once())
+ ->method('findOneByData')
+ ->with(
+ [
UrlRewrite::ENTITY_ID => 'entity_id',
UrlRewrite::ENTITY_TYPE => 'entity_type',
- UrlRewrite::STORE_ID => $storeId,
- ])
+ UrlRewrite::STORE_ID => $this->storeManager->getStore($storeId)->getStoreId(),
+ ]
+ )
->will($this->returnValue($rewrite));
- $this->assertEquals($url . $separator . '___store=' . $storeCode, $this->block->getHref());
+ $rewrite->expects($this->once())
+ ->method('getRequestPath')
+ ->will($this->returnValue($path));
+
+ $this->assertEquals($expected, $this->block->getHref());
}
+ /**
+ * Tests getLabel with custom text
+ */
public function testGetLabelWithCustomText()
{
$customText = 'Some text';
@@ -158,6 +245,9 @@ public function testGetLabelWithCustomText()
$this->assertEquals($customText, $this->block->getLabel());
}
+ /**
+ * Tests getLabel without custom text
+ */
public function testGetLabelWithoutCustomText()
{
$category = 'Some text';
@@ -178,17 +268,25 @@ public function testGetLabelWithoutCustomText()
public function dataProviderForTestGetHrefWithoutUrlStoreSuffix()
{
return [
- ['url', '?'],
- ['url?some_parameter', '&'],
+ ['/accessories.html', null, true, 'french/accessories.html'],
+ ['/accessories.html', null, false, '/accessories.html'],
+ ['/accessories.html', 1, true, 'french/accessories.html'],
+ ['/accessories.html', 1, false, '/accessories.html'],
+ ['/accessories.html', 2, true, 'german/accessories.html'],
+ ['/accessories.html', 2, false, '/accessories.html?___store=german'],
+ ['/accessories.html?___store=german', 2, false, '/accessories.html?___store=german'],
];
}
+ /**
+ * Tests getHref with product entity and additional category id in the id_path
+ */
public function testGetHrefWithForProductWithCategoryIdParameter()
{
$storeId = 15;
$this->block->setData('id_path', ProductUrlRewriteGenerator::ENTITY_TYPE . '/entity_id/category_id');
- $store = $this->createPartialMock(\Magento\Store\Model\Store::class, ['getId', '__wakeUp']);
+ $store = $this->createPartialMock(Store::class, ['getId', '__wakeUp']);
$store->expects($this->any())
->method('getId')
->will($this->returnValue($storeId));
@@ -197,13 +295,16 @@ public function testGetHrefWithForProductWithCategoryIdParameter()
->method('getStore')
->will($this->returnValue($store));
- $this->urlFinder->expects($this->once())->method('findOneByData')
- ->with([
- UrlRewrite::ENTITY_ID => 'entity_id',
- UrlRewrite::ENTITY_TYPE => ProductUrlRewriteGenerator::ENTITY_TYPE,
- UrlRewrite::STORE_ID => $storeId,
- UrlRewrite::METADATA => ['category_id' => 'category_id'],
- ])
+ $this->urlFinder->expects($this->once())
+ ->method('findOneByData')
+ ->with(
+ [
+ UrlRewrite::ENTITY_ID => 'entity_id',
+ UrlRewrite::ENTITY_TYPE => ProductUrlRewriteGenerator::ENTITY_TYPE,
+ UrlRewrite::STORE_ID => $storeId,
+ UrlRewrite::METADATA => ['category_id' => 'category_id'],
+ ]
+ )
->will($this->returnValue(false));
$this->block->getHref();
diff --git a/app/code/Magento/Catalog/Test/Unit/Model/Category/Attribute/Backend/ImageTest.php b/app/code/Magento/Catalog/Test/Unit/Model/Category/Attribute/Backend/ImageTest.php
index f1672d842de4e..a76ae5244076f 100644
--- a/app/code/Magento/Catalog/Test/Unit/Model/Category/Attribute/Backend/ImageTest.php
+++ b/app/code/Magento/Catalog/Test/Unit/Model/Category/Attribute/Backend/ImageTest.php
@@ -6,7 +6,12 @@
namespace Magento\Catalog\Test\Unit\Model\Category\Attribute\Backend;
use Magento\Framework\App\Filesystem\DirectoryList;
+use Magento\Framework\Filesystem\Directory\WriteInterface;
+/**
+ * Test for Magento\Catalog\Model\Category\Attribute\Backend\Image class.
+ * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
+ */
class ImageTest extends \PHPUnit\Framework\TestCase
{
/**
@@ -67,7 +72,7 @@ protected function setUp()
$this->imageUploader = $this->createPartialMock(
\Magento\Catalog\Model\ImageUploader::class,
- ['moveFileFromTmp']
+ ['moveFileFromTmp', 'getBasePath']
);
$this->filesystem = $this->getMockBuilder(\Magento\Framework\Filesystem::class)->disableOriginalConstructor()
@@ -95,9 +100,7 @@ public function testBeforeSaveValueDeletion($value)
$model = $this->objectManager->getObject(\Magento\Catalog\Model\Category\Attribute\Backend\Image::class);
$model->setAttribute($this->attribute);
- $object = new \Magento\Framework\DataObject([
- 'test_attribute' => $value
- ]);
+ $object = new \Magento\Framework\DataObject(['test_attribute' => $value]);
$model->beforeSave($object);
@@ -132,9 +135,7 @@ public function testBeforeSaveValueInvalid($value)
$model = $this->objectManager->getObject(\Magento\Catalog\Model\Category\Attribute\Backend\Image::class);
$model->setAttribute($this->attribute);
- $object = new \Magento\Framework\DataObject([
- 'test_attribute' => $value
- ]);
+ $object = new \Magento\Framework\DataObject(['test_attribute' => $value]);
$model->beforeSave($object);
@@ -146,14 +147,25 @@ public function testBeforeSaveValueInvalid($value)
*/
public function testBeforeSaveAttributeFileName()
{
- $model = $this->objectManager->getObject(\Magento\Catalog\Model\Category\Attribute\Backend\Image::class);
- $model->setAttribute($this->attribute);
+ $model = $this->setUpModelForAfterSave();
+ $mediaDirectoryMock = $this->createMock(WriteInterface::class);
+ $this->filesystem->expects($this->once())
+ ->method('getDirectoryWrite')
+ ->with(DirectoryList::MEDIA)
+ ->willReturn($mediaDirectoryMock);
+ $this->imageUploader->expects($this->once())->method('getBasePath')->willReturn('base/path');
+ $mediaDirectoryMock->expects($this->once())
+ ->method('getAbsolutePath')
+ ->with('base/path/test123.jpg')
+ ->willReturn('absolute/path/base/path/test123.jpg');
- $object = new \Magento\Framework\DataObject([
- 'test_attribute' => [
- ['name' => 'test123.jpg']
+ $object = new \Magento\Framework\DataObject(
+ [
+ 'test_attribute' => [
+ ['name' => 'test123.jpg'],
+ ],
]
- ]);
+ );
$model->beforeSave($object);
@@ -165,32 +177,31 @@ public function testBeforeSaveAttributeFileName()
*/
public function testBeforeSaveAttributeFileNameOutsideOfCategoryDir()
{
- $model = $this->objectManager->getObject(\Magento\Catalog\Model\Category\Attribute\Backend\Image::class, [
- 'filesystem' => $this->filesystem
- ]);
-
+ $model = $this->setUpModelForAfterSave();
$model->setAttribute($this->attribute);
-
+ $imagePath = '/pub/media/wysiwyg/test123.jpg';
$this->filesystem
- ->expects($this->once())
+ ->expects($this->exactly(2))
->method('getUri')
->with(DirectoryList::MEDIA)
->willReturn('pub/media');
- $object = new \Magento\Framework\DataObject([
- 'test_attribute' => [
- [
- 'name' => '/test123.jpg',
- 'url' => '/pub/media/wysiwyg/test123.jpg',
- ]
+ $object = new \Magento\Framework\DataObject(
+ [
+ 'test_attribute' => [
+ [
+ 'name' => 'test123.jpg',
+ 'url' => $imagePath,
+ ],
+ ],
]
- ]);
+ );
$model->beforeSave($object);
- $this->assertEquals('/pub/media/wysiwyg/test123.jpg', $object->getTestAttribute());
+ $this->assertEquals($imagePath, $object->getTestAttribute());
$this->assertEquals(
- [['name' => '/pub/media/wysiwyg/test123.jpg', 'url' => '/pub/media/wysiwyg/test123.jpg']],
+ [['name' => $imagePath, 'url' => $imagePath]],
$object->getData('_additional_data_test_attribute')
);
}
@@ -200,20 +211,31 @@ public function testBeforeSaveAttributeFileNameOutsideOfCategoryDir()
*/
public function testBeforeSaveTemporaryAttribute()
{
- $model = $this->objectManager->getObject(\Magento\Catalog\Model\Category\Attribute\Backend\Image::class);
+ $model = $this->setUpModelForAfterSave();
$model->setAttribute($this->attribute);
- $object = new \Magento\Framework\DataObject([
- 'test_attribute' => [
- ['name' => 'test123.jpg', 'tmp_name' => 'abc123', 'url' => 'http://www.example.com/test123.jpg']
+ $mediaDirectoryMock = $this->createMock(WriteInterface::class);
+ $this->filesystem->expects($this->once())
+ ->method('getDirectoryWrite')
+ ->with(DirectoryList::MEDIA)
+ ->willReturn($mediaDirectoryMock);
+
+ $object = new \Magento\Framework\DataObject(
+ [
+ 'test_attribute' => [
+ ['name' => 'test123.jpg', 'tmp_name' => 'abc123', 'url' => 'http://www.example.com/test123.jpg'],
+ ],
]
- ]);
+ );
$model->beforeSave($object);
- $this->assertEquals([
- ['name' => 'test123.jpg', 'tmp_name' => 'abc123', 'url' => 'http://www.example.com/test123.jpg']
- ], $object->getData('_additional_data_test_attribute'));
+ $this->assertEquals(
+ [
+ ['name' => 'test123.jpg', 'tmp_name' => 'abc123', 'url' => 'http://www.example.com/test123.jpg'],
+ ],
+ $object->getData('_additional_data_test_attribute')
+ );
}
/**
@@ -224,9 +246,7 @@ public function testBeforeSaveAttributeStringValue()
$model = $this->objectManager->getObject(\Magento\Catalog\Model\Category\Attribute\Backend\Image::class);
$model->setAttribute($this->attribute);
- $object = new \Magento\Framework\DataObject([
- 'test_attribute' => 'test123.jpg'
- ]);
+ $object = new \Magento\Framework\DataObject(['test_attribute' => 'test123.jpg']);
$model->beforeSave($object);
@@ -245,18 +265,26 @@ private function setUpModelForAfterSave()
$objectManagerMock->expects($this->any())
->method('get')
- ->will($this->returnCallback(function ($class, $params = []) use ($imageUploaderMock) {
- if ($class == \Magento\Catalog\CategoryImageUpload::class) {
- return $imageUploaderMock;
- }
-
- return $this->objectManager->get($class, $params);
- }));
-
- $model = $this->objectManager->getObject(\Magento\Catalog\Model\Category\Attribute\Backend\Image::class, [
- 'objectManager' => $objectManagerMock,
- 'logger' => $this->logger
- ]);
+ ->will(
+ $this->returnCallback(
+ function ($class, $params = []) use ($imageUploaderMock) {
+ if ($class == \Magento\Catalog\CategoryImageUpload::class) {
+ return $imageUploaderMock;
+ }
+
+ return $this->objectManager->get($class, $params);
+ }
+ )
+ );
+
+ $model = $this->objectManager->getObject(
+ \Magento\Catalog\Model\Category\Attribute\Backend\Image::class,
+ [
+ 'objectManager' => $objectManagerMock,
+ 'logger' => $this->logger,
+ 'filesystem' => $this->filesystem,
+ ]
+ );
$this->objectManager->setBackwardCompatibleProperty($model, 'imageUploader', $this->imageUploader);
return $model->setAttribute($this->attribute);
diff --git a/app/code/Magento/Catalog/Test/Unit/Model/Category/FileInfoTest.php b/app/code/Magento/Catalog/Test/Unit/Model/Category/FileInfoTest.php
index 6c6a69ec39c85..71f5ca33d1303 100644
--- a/app/code/Magento/Catalog/Test/Unit/Model/Category/FileInfoTest.php
+++ b/app/code/Magento/Catalog/Test/Unit/Model/Category/FileInfoTest.php
@@ -13,6 +13,8 @@
use Magento\Framework\Filesystem\Directory\WriteInterface;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
+use Magento\Store\Model\StoreManagerInterface;
+use Magento\Store\Model\Store;
/**
* Test for Magento\Catalog\Model\Category\FileInfo class.
@@ -44,6 +46,16 @@ class FileInfoTest extends TestCase
*/
private $pubDirectory;
+ /**
+ * @var StoreManagerInterface|MockObject
+ */
+ private $storeManager;
+
+ /**
+ * @var Store|MockObject
+ */
+ private $store;
+
/**
* @var FileInfo
*/
@@ -60,6 +72,16 @@ protected function setUp()
$this->pubDirectory = $pubDirectory = $this->getMockBuilder(ReadInterface::class)
->getMockForAbstractClass();
+ $this->store = $this->getMockBuilder(Store::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+ $this->storeManager = $this->getMockBuilder(StoreManagerInterface::class)
+ ->setMethods(['getStore'])
+ ->getMockForAbstractClass();
+ $this->storeManager->expects($this->any())
+ ->method('getStore')
+ ->willReturn($this->store);
+
$this->filesystem = $this->getMockBuilder(Filesystem::class)
->disableOriginalConstructor()
->getMock();
@@ -94,7 +116,8 @@ function ($arg) use ($baseDirectory, $pubDirectory) {
$this->model = new FileInfo(
$this->filesystem,
- $this->mime
+ $this->mime,
+ $this->storeManager
);
}
diff --git a/app/code/Magento/Catalog/Test/Unit/Model/CategoryListTest.php b/app/code/Magento/Catalog/Test/Unit/Model/CategoryListTest.php
index b8b76524099f4..c7821f06985bc 100644
--- a/app/code/Magento/Catalog/Test/Unit/Model/CategoryListTest.php
+++ b/app/code/Magento/Catalog/Test/Unit/Model/CategoryListTest.php
@@ -3,6 +3,9 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
+
+declare(strict_types=1);
+
namespace Magento\Catalog\Test\Unit\Model;
use Magento\Catalog\Api\CategoryRepositoryInterface;
@@ -52,6 +55,9 @@ class CategoryListTest extends \PHPUnit\Framework\TestCase
*/
private $collectionProcessorMock;
+ /**
+ * @inheritdoc
+ */
protected function setUp()
{
$this->categoryCollectionFactory = $this->getMockBuilder(CollectionFactory::class)
@@ -93,7 +99,12 @@ public function testGetList()
$collection = $this->getMockBuilder(Collection::class)->disableOriginalConstructor()->getMock();
$collection->expects($this->once())->method('getSize')->willReturn($totalCount);
- $collection->expects($this->once())->method('getAllIds')->willReturn([$categoryIdFirst, $categoryIdSecond]);
+ $collection->expects($this->once())->method('getData')->willReturn(
+ [['entity_id' => $categoryIdFirst], ['entity_id' => $categoryIdSecond]]
+ );
+ $collection->expects($this->any())->method('getEntity')->willReturn(
+ new \Magento\Framework\DataObject(['id_field_name' => 'entity_id'])
+ );
$this->collectionProcessorMock->expects($this->once())
->method('process')
@@ -106,10 +117,7 @@ public function testGetList()
$this->categoryRepository->expects($this->exactly(2))
->method('get')
- ->willReturnMap([
- [$categoryIdFirst, $categoryFirst],
- [$categoryIdSecond, $categorySecond],
- ])
+ ->willReturnMap([[$categoryIdFirst, $categoryFirst], [$categoryIdSecond, $categorySecond]])
->willReturn($categoryFirst);
$this->categorySearchResultsFactory->expects($this->once())->method('create')->willReturn($searchResult);
diff --git a/app/code/Magento/Catalog/Test/Unit/Model/Layer/FilterListTest.php b/app/code/Magento/Catalog/Test/Unit/Model/Layer/FilterListTest.php
index 8733f305ce091..731c5efd99746 100644
--- a/app/code/Magento/Catalog/Test/Unit/Model/Layer/FilterListTest.php
+++ b/app/code/Magento/Catalog/Test/Unit/Model/Layer/FilterListTest.php
@@ -3,6 +3,7 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
+declare(strict_types=1);
namespace Magento\Catalog\Test\Unit\Model\Layer;
@@ -72,9 +73,13 @@ public function testGetFilters($method, $value, $expectedClass)
$this->objectManagerMock->expects($this->at(1))
->method('create')
- ->with($expectedClass, [
- 'data' => ['attribute_model' => $this->attributeMock],
- 'layer' => $this->layerMock])
+ ->with(
+ $expectedClass,
+ [
+ 'data' => ['attribute_model' => $this->attributeMock],
+ 'layer' => $this->layerMock
+ ]
+ )
->will($this->returnValue('filter'));
$this->attributeMock->expects($this->once())
@@ -95,8 +100,8 @@ public function getFiltersDataProvider()
{
return [
[
- 'method' => 'getAttributeCode',
- 'value' => FilterList::PRICE_FILTER,
+ 'method' => 'getFrontendInput',
+ 'value' => 'price',
'expectedClass' => 'PriceFilterClass',
],
[
@@ -105,8 +110,8 @@ public function getFiltersDataProvider()
'expectedClass' => 'DecimalFilterClass',
],
[
- 'method' => 'getAttributeCode',
- 'value' => null,
+ 'method' => 'getFrontendInput',
+ 'value' => 'text',
'expectedClass' => 'AttributeFilterClass',
]
];
diff --git a/app/code/Magento/Catalog/Test/Unit/Model/Product/Attribute/Backend/GroupPrice/AbstractTest.php b/app/code/Magento/Catalog/Test/Unit/Model/Product/Attribute/Backend/GroupPrice/AbstractTest.php
index 3003c2f8085e4..b2e1401479301 100644
--- a/app/code/Magento/Catalog/Test/Unit/Model/Product/Attribute/Backend/GroupPrice/AbstractTest.php
+++ b/app/code/Magento/Catalog/Test/Unit/Model/Product/Attribute/Backend/GroupPrice/AbstractTest.php
@@ -65,11 +65,11 @@ public function testGetAffectedFields()
$attribute->expects($this->any())->method('getAttributeId')->will($this->returnValue($attributeId));
$attribute->expects($this->any())->method('isStatic')->will($this->returnValue(false));
$attribute->expects($this->any())->method('getBackendTable')->will($this->returnValue('table'));
- $attribute->expects($this->any())->method('getName')->will($this->returnValue('tear_price'));
+ $attribute->expects($this->any())->method('getName')->will($this->returnValue('tier_price'));
$this->_model->setAttribute($attribute);
$object = new \Magento\Framework\DataObject();
- $object->setTearPrice([['price_id' => 10]]);
+ $object->setTierPrice([['price_id' => 10]]);
$object->setId(555);
$this->assertEquals(
diff --git a/app/code/Magento/Catalog/Test/Unit/Model/Product/Price/TierPriceStorageTest.php b/app/code/Magento/Catalog/Test/Unit/Model/Product/Price/TierPriceStorageTest.php
index a97f2281125a6..34f43b725da57 100644
--- a/app/code/Magento/Catalog/Test/Unit/Model/Product/Price/TierPriceStorageTest.php
+++ b/app/code/Magento/Catalog/Test/Unit/Model/Product/Price/TierPriceStorageTest.php
@@ -6,46 +6,44 @@
namespace Magento\Catalog\Test\Unit\Model\Product\Price;
+use Magento\Catalog\Api\Data\TierPriceInterface;
+use Magento\Catalog\Model\Indexer\Product\Price\Processor as PriceIndexerProcessor;
+use Magento\Catalog\Model\Product\Price\TierPriceFactory;
+use Magento\Catalog\Model\Product\Price\TierPricePersistence;
+use Magento\Catalog\Model\Product\Price\Validation\Result as PriceValidationResult;
+use Magento\Catalog\Model\Product\Price\Validation\TierPriceValidator;
+use Magento\Catalog\Model\ProductIdLocatorInterface;
+
/**
* TierPriceStorage test.
*/
class TierPriceStorageTest extends \PHPUnit\Framework\TestCase
{
/**
- * @var \Magento\Catalog\Model\Product\Price\TierPricePersistence|\PHPUnit_Framework_MockObject_MockObject
+ * @var TierPricePersistence|\PHPUnit_Framework_MockObject_MockObject
*/
private $tierPricePersistence;
/**
- * @var \Magento\Catalog\Model\Product\Price\Validation\TierPriceValidator|\PHPUnit_Framework_MockObject_MockObject
+ * @var TierPriceValidator|\PHPUnit_Framework_MockObject_MockObject
*/
private $tierPriceValidator;
/**
- * @var \Magento\Catalog\Model\Product\Price\TierPriceFactory|\PHPUnit_Framework_MockObject_MockObject
+ * @var TierPriceFactory|\PHPUnit_Framework_MockObject_MockObject
*/
private $tierPriceFactory;
/**
- * @var \Magento\Catalog\Model\Indexer\Product\Price|\PHPUnit_Framework_MockObject_MockObject
+ * @var PriceIndexerProcessor|\PHPUnit_Framework_MockObject_MockObject
*/
- private $priceIndexer;
+ private $priceIndexProcessor;
/**
- * @var \Magento\Catalog\Model\ProductIdLocatorInterface|\PHPUnit_Framework_MockObject_MockObject
+ * @var ProductIdLocatorInterface|\PHPUnit_Framework_MockObject_MockObject
*/
private $productIdLocator;
- /**
- * @var \Magento\PageCache\Model\Config|\PHPUnit_Framework_MockObject_MockObject
- */
- private $config;
-
- /**
- * @var \Magento\Framework\App\Cache\TypeListInterface|\PHPUnit_Framework_MockObject_MockObject
- */
- private $typeList;
-
/**
* @var \Magento\Catalog\Model\Product\Price\TierPriceStorage
*/
@@ -56,36 +54,13 @@ class TierPriceStorageTest extends \PHPUnit\Framework\TestCase
*/
protected function setUp()
{
- $this->tierPricePersistence = $this->getMockBuilder(
- \Magento\Catalog\Model\Product\Price\TierPricePersistence::class
- )
- ->disableOriginalConstructor()
- ->getMock();
- $this->tierPricePersistence->expects($this->any())
- ->method('getEntityLinkField')
- ->willReturn('row_id');
- $this->tierPriceValidator = $this->getMockBuilder(
- \Magento\Catalog\Model\Product\Price\Validation\TierPriceValidator::class
- )
- ->disableOriginalConstructor()
- ->getMock();
- $this->tierPriceFactory = $this->getMockBuilder(
- \Magento\Catalog\Model\Product\Price\TierPriceFactory::class
- )
- ->disableOriginalConstructor()
- ->getMock();
- $this->priceIndexer = $this->getMockBuilder(\Magento\Catalog\Model\Indexer\Product\Price::class)
- ->disableOriginalConstructor()
- ->getMock();
- $this->productIdLocator = $this->getMockBuilder(\Magento\Catalog\Model\ProductIdLocatorInterface::class)
- ->disableOriginalConstructor()
- ->getMockForAbstractClass();
- $this->config = $this->getMockBuilder(\Magento\PageCache\Model\Config::class)
- ->disableOriginalConstructor()
- ->getMock();
- $this->typeList = $this->getMockBuilder(\Magento\Framework\App\Cache\TypeListInterface::class)
- ->disableOriginalConstructor()
- ->getMockForAbstractClass();
+ $this->tierPricePersistence = $this->createMock(TierPricePersistence::class);
+ $this->tierPricePersistence->method('getEntityLinkField')
+ ->willReturn('entity_id');
+ $this->tierPriceValidator = $this->createMock(TierPriceValidator::class);
+ $this->tierPriceFactory = $this->createMock(TierPriceFactory::class);
+ $this->priceIndexProcessor = $this->createMock(PriceIndexerProcessor::class);
+ $this->productIdLocator = $this->createMock(ProductIdLocatorInterface::class);
$objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this);
$this->tierPriceStorage = $objectManager->getObject(
@@ -94,10 +69,8 @@ protected function setUp()
'tierPricePersistence' => $this->tierPricePersistence,
'tierPriceValidator' => $this->tierPriceValidator,
'tierPriceFactory' => $this->tierPriceFactory,
- 'priceIndexer' => $this->priceIndexer,
+ 'priceIndexProcessor' => $this->priceIndexProcessor,
'productIdLocator' => $this->productIdLocator,
- 'config' => $this->config,
- 'typeList' => $this->typeList,
]
);
}
@@ -125,7 +98,7 @@ public function testGet()
[
[
'value_id' => 1,
- 'row_id' => 2,
+ 'entity_id' => 2,
'all_groups' => 1,
'customer_group_id' => 0,
'qty' => 2.0000,
@@ -135,7 +108,7 @@ public function testGet()
],
[
'value_id' => 2,
- 'row_id' => 3,
+ 'entity_id' => 3,
'all_groups' => 1,
'customer_group_id' => 0,
'qty' => 3.0000,
@@ -145,7 +118,7 @@ public function testGet()
]
]
);
- $price = $this->getMockBuilder(\Magento\Catalog\Api\Data\TierPriceInterface::class)->getMockForAbstractClass();
+ $price = $this->getMockBuilder(TierPriceInterface::class)->getMockForAbstractClass();
$this->tierPriceFactory->expects($this->atLeastOnce())->method('create')->willReturn($price);
$prices = $this->tierPriceStorage->get($skus);
$this->assertNotEmpty($prices);
@@ -183,36 +156,37 @@ public function testGetWithoutTierPrices()
*/
public function testUpdate()
{
- $price = $this->getMockBuilder(\Magento\Catalog\Api\Data\TierPriceInterface::class)->getMockForAbstractClass();
- $result = $this->getMockBuilder(\Magento\Catalog\Model\Product\Price\Validation\Result::class)
- ->disableOriginalConstructor()
- ->getMock();
- $result->expects($this->atLeastOnce())->method('getFailedRowIds')->willReturn([]);
+ $price = $this->createMock(TierPriceInterface::class);
+ $result = $this->createMock(PriceValidationResult::class);
+ $result->expects($this->once())
+ ->method('getFailedRowIds')
+ ->willReturn([]);
$this->productIdLocator->expects($this->atLeastOnce())
->method('retrieveProductIdsBySkus')
->willReturn(['simple' => ['2' => 'simple'], 'virtual' => ['3' => 'virtual']]);
- $this->tierPriceValidator
- ->expects($this->atLeastOnce())
+ $this->tierPriceValidator->expects($this->once())
->method('retrieveValidationResult')
->willReturn($result);
- $this->tierPriceFactory->expects($this->atLeastOnce())->method('createSkeleton')->willReturn(
- [
- 'row_id' => 2,
- 'all_groups' => 1,
- 'customer_group_id' => 0,
- 'qty' => 2,
- 'value' => 3,
- 'percentage_value' => null,
- 'website_id' => 0
- ]
- );
+ $this->tierPriceFactory->expects($this->once())
+ ->method('createSkeleton')
+ ->willReturn(
+ [
+ 'entity_id' => 2,
+ 'all_groups' => 1,
+ 'customer_group_id' => 0,
+ 'qty' => 2,
+ 'value' => 3,
+ 'percentage_value' => null,
+ 'website_id' => 0
+ ]
+ );
$this->tierPricePersistence->expects($this->once())
->method('get')
->willReturn(
[
[
'value_id' => 1,
- 'row_id' => 2,
+ 'entity_id' => 2,
'all_groups' => 1,
'customer_group_id' => 0,
'qty' => 2.0000,
@@ -222,11 +196,15 @@ public function testUpdate()
]
]
);
- $this->tierPricePersistence->expects($this->atLeastOnce())->method('update');
- $this->priceIndexer->expects($this->atLeastOnce())->method('execute');
- $this->config->expects($this->atLeastOnce())->method('isEnabled')->willReturn(true);
- $this->typeList->expects($this->atLeastOnce())->method('invalidate');
- $price->expects($this->atLeastOnce())->method('getSku')->willReturn('simple');
+ $this->tierPricePersistence->expects($this->once())
+ ->method('update');
+ $this->priceIndexProcessor->expects($this->once())
+ ->method('reindexList')
+ ->with([2, 3]);
+ $price->expects($this->atLeastOnce())
+ ->method('getSku')
+ ->willReturn('simple');
+
$this->assertEmpty($this->tierPriceStorage->update([$price]));
}
@@ -237,35 +215,41 @@ public function testUpdate()
*/
public function testReplace()
{
- $price = $this->getMockBuilder(\Magento\Catalog\Api\Data\TierPriceInterface::class)->getMockForAbstractClass();
- $price->expects($this->atLeastOnce())->method('getSku')->willReturn('virtual');
- $result = $this->getMockBuilder(\Magento\Catalog\Model\Product\Price\Validation\Result::class)
- ->disableOriginalConstructor()
- ->getMock();
- $result->expects($this->atLeastOnce())->method('getFailedRowIds')->willReturn([]);
+ $price = $this->createMock(TierPriceInterface::class);
+ $price->expects($this->atLeastOnce())
+ ->method('getSku')
+ ->willReturn('virtual');
+ $result = $this->createMock(PriceValidationResult::class);
+ $result->expects($this->once())
+ ->method('getFailedRowIds')
+ ->willReturn([]);
$this->productIdLocator->expects($this->atLeastOnce())
->method('retrieveProductIdsBySkus')
->willReturn(['simple' => ['2' => 'simple'], 'virtual' => ['3' => 'virtual']]);
$this->tierPriceValidator
- ->expects($this->atLeastOnce())
+ ->expects($this->once())
->method('retrieveValidationResult')
->willReturn($result);
- $this->tierPriceFactory->expects($this->atLeastOnce())->method('createSkeleton')->willReturn(
- [
- 'row_id' => 3,
- 'all_groups' => 1,
- 'customer_group_id' => 0,
- 'qty' => 3,
- 'value' => 7,
- 'percentage_value' => null,
- 'website_id' => 0
- ]
- );
- $this->tierPricePersistence->expects($this->atLeastOnce())->method('replace');
- $this->priceIndexer->expects($this->atLeastOnce())->method('execute');
- $this->config->expects($this->atLeastOnce())->method('isEnabled')->willReturn(true);
- $this->typeList->expects($this->atLeastOnce())->method('invalidate');
+ $this->tierPriceFactory->expects($this->once())
+ ->method('createSkeleton')
+ ->willReturn(
+ [
+ 'entity_id' => 3,
+ 'all_groups' => 1,
+ 'customer_group_id' => 0,
+ 'qty' => 3,
+ 'value' => 7,
+ 'percentage_value' => null,
+ 'website_id' => 0
+ ]
+ );
+ $this->tierPricePersistence->expects($this->once())
+ ->method('replace');
+ $this->priceIndexProcessor->expects($this->once())
+ ->method('reindexList')
+ ->with([2, 3]);
+
$this->assertEmpty($this->tierPriceStorage->replace([$price]));
}
@@ -276,13 +260,15 @@ public function testReplace()
*/
public function testDelete()
{
- $price = $this->getMockBuilder(\Magento\Catalog\Api\Data\TierPriceInterface::class)->getMockForAbstractClass();
- $price->expects($this->atLeastOnce())->method('getSku')->willReturn('simple');
- $result = $this->getMockBuilder(\Magento\Catalog\Model\Product\Price\Validation\Result::class)
- ->disableOriginalConstructor()
- ->getMock();
- $result->expects($this->atLeastOnce())->method('getFailedRowIds')->willReturn([]);
- $this->tierPriceValidator->expects($this->atLeastOnce())
+ $price = $this->createMock(TierPriceInterface::class);
+ $price->expects($this->atLeastOnce())
+ ->method('getSku')
+ ->willReturn('simple');
+ $result = $this->createMock(PriceValidationResult::class);
+ $result->expects($this->once())
+ ->method('getFailedRowIds')
+ ->willReturn([]);
+ $this->tierPriceValidator->expects($this->once())
->method('retrieveValidationResult')
->willReturn($result);
$this->productIdLocator->expects($this->atLeastOnce())
@@ -294,7 +280,7 @@ public function testDelete()
[
[
'value_id' => 7,
- 'row_id' => 7,
+ 'entity_id' => 7,
'all_groups' => 1,
'customer_group_id' => 0,
'qty' => 5.0000,
@@ -304,21 +290,24 @@ public function testDelete()
]
]
);
- $this->tierPriceFactory->expects($this->atLeastOnce())->method('createSkeleton')->willReturn(
- [
- 'row_id' => 3,
- 'all_groups' => 1,
- 'customer_group_id' => 0,
- 'qty' => 3,
- 'value' => 7,
- 'percentage_value' => null,
- 'website_id' => 0
- ]
- );
- $this->tierPricePersistence->expects($this->atLeastOnce())->method('delete');
- $this->priceIndexer->expects($this->atLeastOnce())->method('execute');
- $this->config->expects($this->atLeastOnce())->method('isEnabled')->willReturn(true);
- $this->typeList->expects($this->atLeastOnce())->method('invalidate');
+ $this->tierPriceFactory->expects($this->once())
+ ->method('createSkeleton')->willReturn(
+ [
+ 'entity_id' => 3,
+ 'all_groups' => 1,
+ 'customer_group_id' => 0,
+ 'qty' => 3,
+ 'value' => 7,
+ 'percentage_value' => null,
+ 'website_id' => 0
+ ]
+ );
+ $this->tierPricePersistence->expects($this->once())
+ ->method('delete');
+ $this->priceIndexProcessor->expects($this->once())
+ ->method('reindexList')
+ ->with([2]);
+
$this->assertEmpty($this->tierPriceStorage->delete([$price]));
}
}
diff --git a/app/code/Magento/Catalog/Test/Unit/Model/ProductTest.php b/app/code/Magento/Catalog/Test/Unit/Model/ProductTest.php
index 8bf8473080c54..5eaf2422e95a9 100644
--- a/app/code/Magento/Catalog/Test/Unit/Model/ProductTest.php
+++ b/app/code/Magento/Catalog/Test/Unit/Model/ProductTest.php
@@ -13,6 +13,7 @@
use Magento\Framework\Api\ExtensibleDataInterface;
use Magento\Framework\Api\ExtensionAttributesFactory;
use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper;
+use Magento\Store\Model\StoreManagerInterface;
/**
* Product Test
@@ -207,6 +208,11 @@ class ProductTest extends \PHPUnit\Framework\TestCase
*/
private $eavConfig;
+ /**
+ * @var StoreManagerInterface|\PHPUnit_Framework_MockObject_MockObject
+ */
+ private $storeManager;
+
/**
* @SuppressWarnings(PHPMD.ExcessiveMethodLength)
*/
@@ -303,13 +309,13 @@ protected function setUp()
->disableOriginalConstructor()
->getMock();
- $storeManager = $this->getMockBuilder(\Magento\Store\Model\StoreManagerInterface::class)
+ $this->storeManager = $this->getMockBuilder(\Magento\Store\Model\StoreManagerInterface::class)
->disableOriginalConstructor()
->getMockForAbstractClass();
- $storeManager->expects($this->any())
+ $this->storeManager->expects($this->any())
->method('getStore')
->will($this->returnValue($this->store));
- $storeManager->expects($this->any())
+ $this->storeManager->expects($this->any())
->method('getWebsite')
->will($this->returnValue($this->website));
$this->indexerRegistryMock = $this->createPartialMock(
@@ -394,7 +400,7 @@ protected function setUp()
'extensionFactory' => $this->extensionAttributesFactory,
'productPriceIndexerProcessor' => $this->productPriceProcessor,
'catalogProductOptionFactory' => $optionFactory,
- 'storeManager' => $storeManager,
+ 'storeManager' => $this->storeManager,
'resource' => $this->resource,
'registry' => $this->registry,
'moduleManager' => $this->moduleManager,
@@ -450,6 +456,48 @@ public function testGetStoreIds()
$this->assertEquals($expectedStoreIds, $this->model->getStoreIds());
}
+ /**
+ * @dataProvider getSingleStoreIds
+ * @param bool $isObjectNew
+ */
+ public function testGetStoreSingleSiteModelIds(
+ bool $isObjectNew
+ ) {
+ $websiteIDs = [0 => 2];
+ $this->model->setWebsiteIds(
+ !$isObjectNew ? $websiteIDs : array_flip($websiteIDs)
+ );
+
+ $this->model->isObjectNew($isObjectNew);
+
+ $this->storeManager->expects(
+ $this->exactly(
+ (int) !$isObjectNew
+ )
+ )
+ ->method('isSingleStoreMode')
+ ->will($this->returnValue(true));
+
+ $this->website->expects(
+ $this->once()
+ )->method('getStoreIds')
+ ->will($this->returnValue($websiteIDs));
+
+ $this->assertEquals($websiteIDs, $this->model->getStoreIds());
+ }
+
+ public function getSingleStoreIds()
+ {
+ return [
+ [
+ false
+ ],
+ [
+ true
+ ],
+ ];
+ }
+
public function testGetStoreId()
{
$this->model->setStoreId(3);
@@ -1221,8 +1269,7 @@ public function testGetMediaGalleryImagesMerging()
{
$mediaEntries =
[
- 'images' =>
- [
+ 'images' => [
[
'value_id' => 1,
'file' => 'imageFile.jpg',
diff --git a/app/code/Magento/Catalog/Test/Unit/Pricing/Price/BasePriceTest.php b/app/code/Magento/Catalog/Test/Unit/Pricing/Price/BasePriceTest.php
index b823549391257..279c3c3ac8587 100644
--- a/app/code/Magento/Catalog/Test/Unit/Pricing/Price/BasePriceTest.php
+++ b/app/code/Magento/Catalog/Test/Unit/Pricing/Price/BasePriceTest.php
@@ -39,7 +39,7 @@ class BasePriceTest extends \PHPUnit\Framework\TestCase
/**
* @var \Magento\Catalog\Pricing\Price\TierPrice|\PHPUnit_Framework_MockObject_MockObject
*/
- protected $tearPriceMock;
+ protected $tierPriceMock;
/**
* @var \Magento\Catalog\Pricing\Price\SpecialPrice|\PHPUnit_Framework_MockObject_MockObject
@@ -60,7 +60,7 @@ protected function setUp()
$this->saleableItemMock = $this->createMock(\Magento\Catalog\Model\Product::class);
$this->priceInfoMock = $this->createMock(\Magento\Framework\Pricing\PriceInfo\Base::class);
$this->regularPriceMock = $this->createMock(\Magento\Catalog\Pricing\Price\RegularPrice::class);
- $this->tearPriceMock = $this->createMock(\Magento\Catalog\Pricing\Price\TierPrice::class);
+ $this->tierPriceMock = $this->createMock(\Magento\Catalog\Pricing\Price\TierPrice::class);
$this->specialPriceMock = $this->createMock(\Magento\Catalog\Pricing\Price\SpecialPrice::class);
$this->calculatorMock = $this->createMock(\Magento\Framework\Pricing\Adjustment\Calculator::class);
@@ -69,7 +69,7 @@ protected function setUp()
->will($this->returnValue($this->priceInfoMock));
$this->prices = [
'regular_price' => $this->regularPriceMock,
- 'tear_price' => $this->tearPriceMock,
+ 'tier_price' => $this->tierPriceMock,
'special_price' => $this->specialPriceMock,
];
@@ -97,7 +97,7 @@ public function testGetValue($specialPriceValue, $expectedResult)
$this->regularPriceMock->expects($this->exactly(3))
->method('getValue')
->will($this->returnValue(100));
- $this->tearPriceMock->expects($this->exactly(2))
+ $this->tierPriceMock->expects($this->exactly(2))
->method('getValue')
->will($this->returnValue(99));
$this->specialPriceMock->expects($this->any())
diff --git a/app/code/Magento/Catalog/Test/Unit/Ui/DataProvider/Product/Form/Modifier/EavTest.php b/app/code/Magento/Catalog/Test/Unit/Ui/DataProvider/Product/Form/Modifier/EavTest.php
old mode 100755
new mode 100644
index 8cb59b1a2ccec..88075b13f1430
--- a/app/code/Magento/Catalog/Test/Unit/Ui/DataProvider/Product/Form/Modifier/EavTest.php
+++ b/app/code/Magento/Catalog/Test/Unit/Ui/DataProvider/Product/Form/Modifier/EavTest.php
@@ -266,15 +266,17 @@ protected function setUp()
$this->searchResultsMock = $this->getMockBuilder(SearchResultsInterface::class)
->getMockForAbstractClass();
$this->eavAttributeMock = $this->getMockBuilder(Attribute::class)
- ->setMethods([
- 'load',
- 'getAttributeGroupCode',
- 'getApplyTo',
- 'getFrontendInput',
- 'getAttributeCode',
- 'usesSource',
- 'getSource',
- ])
+ ->setMethods(
+ [
+ 'load',
+ 'getAttributeGroupCode',
+ 'getApplyTo',
+ 'getFrontendInput',
+ 'getAttributeCode',
+ 'usesSource',
+ 'getSource',
+ ]
+ )
->disableOriginalConstructor()
->getMock();
$this->productAttributeMock = $this->getMockBuilder(ProductAttributeInterface::class)
@@ -307,9 +309,7 @@ protected function setUp()
->willReturnSelf();
$this->groupCollectionMock->expects($this->any())
->method('getIterator')
- ->willReturn(new \ArrayIterator([
- $this->groupMock,
- ]));
+ ->willReturn(new \ArrayIterator([$this->groupMock]));
$this->attributeCollectionMock->expects($this->any())
->method('addFieldToSelect')
->willReturnSelf();
@@ -324,9 +324,7 @@ protected function setUp()
->willReturn($this->attributeCollectionMock);
$this->productMock->expects($this->any())
->method('getAttributes')
- ->willReturn([
- $this->attributeMock,
- ]);
+ ->willReturn([$this->attributeMock,]);
$this->storeMock = $this->getMockBuilder(StoreInterface::class)
->setMethods(['load', 'getId', 'getConfig', 'getBaseCurrencyCode'])
->getMockForAbstractClass();
@@ -355,24 +353,27 @@ protected function setUp()
*/
protected function createModel()
{
- return $this->objectManager->getObject(Eav::class, [
- 'locator' => $this->locatorMock,
- 'eavValidationRules' => $this->eavValidationRulesMock,
- 'eavConfig' => $this->eavConfigMock,
- 'request' => $this->requestMock,
- 'groupCollectionFactory' => $this->groupCollectionFactoryMock,
- 'storeManager' => $this->storeManagerMock,
- 'formElementMapper' => $this->formElementMapperMock,
- 'metaPropertiesMapper' => $this->metaPropertiesMapperMock,
- 'searchCriteriaBuilder' => $this->searchCriteriaBuilderMock,
- 'attributeGroupRepository' => $this->attributeGroupRepositoryMock,
- 'sortOrderBuilder' => $this->sortOrderBuilderMock,
- 'attributeRepository' => $this->attributeRepositoryMock,
- 'arrayManager' => $this->arrayManagerMock,
- 'eavAttributeFactory' => $this->eavAttributeFactoryMock,
- '_eventManager' => $this->eventManagerMock,
- 'attributeCollectionFactory' => $this->attributeCollectionFactoryMock
- ]);
+ return $this->objectManager->getObject(
+ Eav::class,
+ [
+ 'locator' => $this->locatorMock,
+ 'eavValidationRules' => $this->eavValidationRulesMock,
+ 'eavConfig' => $this->eavConfigMock,
+ 'request' => $this->requestMock,
+ 'groupCollectionFactory' => $this->groupCollectionFactoryMock,
+ 'storeManager' => $this->storeManagerMock,
+ 'formElementMapper' => $this->formElementMapperMock,
+ 'metaPropertiesMapper' => $this->metaPropertiesMapperMock,
+ 'searchCriteriaBuilder' => $this->searchCriteriaBuilderMock,
+ 'attributeGroupRepository' => $this->attributeGroupRepositoryMock,
+ 'sortOrderBuilder' => $this->sortOrderBuilderMock,
+ 'attributeRepository' => $this->attributeRepositoryMock,
+ 'arrayManager' => $this->arrayManagerMock,
+ 'eavAttributeFactory' => $this->eavAttributeFactoryMock,
+ '_eventManager' => $this->eventManagerMock,
+ 'attributeCollectionFactory' => $this->attributeCollectionFactoryMock
+ ]
+ );
}
public function testModifyData()
@@ -389,9 +390,7 @@ public function testModifyData()
->willReturn($this->attributeCollectionMock);
$this->attributeCollectionMock->expects($this->any())->method('getItems')
- ->willReturn([
- $this->eavAttributeMock
- ]);
+ ->willReturn([$this->eavAttributeMock]);
$this->locatorMock->expects($this->any())->method('getProduct')
->willReturn($this->productMock);
@@ -480,11 +479,11 @@ public function testSetupAttributeMetaDefaultAttribute(
['value' => ['test1', 'test2'], 'label' => 'Array label'],
];
$attributeOptionsExpected = [
- ['value' => '1', 'label' => 'Int label'],
- ['value' => '1.5', 'label' => 'Float label'],
- ['value' => '1', 'label' => 'Boolean label'],
- ['value' => 'string', 'label' => 'String label'],
- ['value' => ['test1', 'test2'], 'label' => 'Array label'],
+ ['value' => '1', 'label' => 'Int label', '__disableTmpl' => true],
+ ['value' => '1.5', 'label' => 'Float label', '__disableTmpl' => true],
+ ['value' => '1', 'label' => 'Boolean label', '__disableTmpl' => true],
+ ['value' => 'string', 'label' => 'String label', '__disableTmpl' => true],
+ ['value' => ['test1', 'test2'], 'label' => 'Array label', '__disableTmpl' => true],
];
$this->productMock->method('getId')->willReturn($productId);
diff --git a/app/code/Magento/Catalog/Ui/Component/ColumnFactory.php b/app/code/Magento/Catalog/Ui/Component/ColumnFactory.php
index ea6b1fd47a0a5..9a6a22fcb0985 100644
--- a/app/code/Magento/Catalog/Ui/Component/ColumnFactory.php
+++ b/app/code/Magento/Catalog/Ui/Component/ColumnFactory.php
@@ -65,18 +65,24 @@ public function create($attribute, $context, array $config = [])
$filterModifiers = $context->getRequestParam(FilterModifier::FILTER_MODIFIER, []);
$columnName = $attribute->getAttributeCode();
- $config = array_merge([
- 'label' => __($attribute->getDefaultFrontendLabel()),
- 'dataType' => $this->getDataType($attribute),
- 'add_field' => true,
- 'visible' => $attribute->getIsVisibleInGrid(),
- 'filter' => ($attribute->getIsFilterableInGrid() || array_key_exists($columnName, $filterModifiers))
- ? $this->getFilterType($attribute->getFrontendInput())
- : null,
- ], $config);
+ $config = array_merge(
+ [
+ 'label' => __($attribute->getDefaultFrontendLabel()),
+ 'dataType' => $this->getDataType($attribute),
+ 'add_field' => true,
+ 'visible' => $attribute->getIsVisibleInGrid(),
+ 'filter' => ($attribute->getIsFilterableInGrid() || array_key_exists($columnName, $filterModifiers))
+ ? $this->getFilterType($attribute->getFrontendInput())
+ : null,
+ ],
+ $config
+ );
if ($attribute->usesSource()) {
$config['options'] = $attribute->getSource()->getAllOptions();
+ foreach ($config['options'] as &$optionData) {
+ $optionData['__disableTmpl'] = true;
+ }
}
$config['component'] = $this->getJsComponent($config['dataType']);
diff --git a/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/AbstractModifier.php b/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/AbstractModifier.php
index 249a00a98bff2..8338e898b7637 100644
--- a/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/AbstractModifier.php
+++ b/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/AbstractModifier.php
@@ -11,6 +11,7 @@
/**
* Class AbstractModifier
*
+ * phpcs:disable Magento2.Classes.AbstractApi
* @api
*
* @SuppressWarnings(PHPMD.NumberOfChildren)
@@ -132,7 +133,7 @@ private function _getNextAttributeSortOrder(array $meta, $attributeCodes, $defau
*/
protected function startsWith($haystack, $needle)
{
- return $needle === '' || strrpos($haystack, $needle, -strlen($haystack)) !== false;
+ return $needle === '' || strrpos($haystack, (string) $needle, -strlen($haystack)) !== false;
}
/**
diff --git a/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/Categories.php b/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/Categories.php
index 0b8f551988a80..5f1907344ce83 100644
--- a/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/Categories.php
+++ b/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/Categories.php
@@ -447,6 +447,7 @@ private function retrieveCategoriesTree(int $storeId, array $shownCategoriesIds)
$categoryById[$category->getId()]['is_active'] = $category->getIsActive();
$categoryById[$category->getId()]['label'] = $category->getName();
+ $categoryById[$category->getId()]['__disableTmpl'] = true;
$categoryById[$category->getParentId()]['optgroup'][] = &$categoryById[$category->getId()];
}
diff --git a/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/CustomOptions.php b/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/CustomOptions.php
old mode 100755
new mode 100644
index af43c84501f65..65792b395dc06
--- a/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/CustomOptions.php
+++ b/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/CustomOptions.php
@@ -1153,7 +1153,7 @@ protected function getProductOptionTypes()
if (count($group['optgroup'])) {
$options[] = $group;
- $groupIndex += 1;
+ $groupIndex++;
}
}
diff --git a/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/Eav.php b/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/Eav.php
index 8326c3b531892..5d1e853cef3d1 100644
--- a/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/Eav.php
+++ b/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/Eav.php
@@ -21,8 +21,10 @@
use Magento\Eav\Model\ResourceModel\Entity\Attribute\Group\CollectionFactory as GroupCollectionFactory;
use Magento\Framework\Api\SearchCriteriaBuilder;
use Magento\Framework\Api\SortOrderBuilder;
+use Magento\Framework\App\ObjectManager;
use Magento\Framework\App\Request\DataPersistorInterface;
use Magento\Framework\App\RequestInterface;
+use Magento\Framework\AuthorizationInterface;
use Magento\Framework\Filter\Translit;
use Magento\Framework\Locale\CurrencyInterface;
use Magento\Framework\Stdlib\ArrayManager;
@@ -213,6 +215,11 @@ class Eav extends AbstractModifier
*/
private $scopeConfig;
+ /**
+ * @var AuthorizationInterface
+ */
+ private $auth;
+
/**
* Eav constructor.
* @param LocatorInterface $locator
@@ -237,6 +244,7 @@ class Eav extends AbstractModifier
* @param CompositeConfigProcessor|null $wysiwygConfigProcessor
* @param ScopeConfigInterface|null $scopeConfig
* @param AttributeCollectionFactory $attributeCollectionFactory
+ * @param AuthorizationInterface|null $auth
* @SuppressWarnings(PHPMD.ExcessiveParameterList)
*/
public function __construct(
@@ -261,7 +269,8 @@ public function __construct(
$attributesToEliminate = [],
CompositeConfigProcessor $wysiwygConfigProcessor = null,
ScopeConfigInterface $scopeConfig = null,
- AttributeCollectionFactory $attributeCollectionFactory = null
+ AttributeCollectionFactory $attributeCollectionFactory = null,
+ ?AuthorizationInterface $auth = null
) {
$this->locator = $locator;
$this->catalogEavValidationRules = $catalogEavValidationRules;
@@ -282,12 +291,12 @@ public function __construct(
$this->dataPersistor = $dataPersistor;
$this->attributesToDisable = $attributesToDisable;
$this->attributesToEliminate = $attributesToEliminate;
- $this->wysiwygConfigProcessor = $wysiwygConfigProcessor ?: \Magento\Framework\App\ObjectManager::getInstance()
- ->get(CompositeConfigProcessor::class);
- $this->scopeConfig = $scopeConfig ?: \Magento\Framework\App\ObjectManager::getInstance()
- ->get(ScopeConfigInterface::class);
+ $this->wysiwygConfigProcessor = $wysiwygConfigProcessor
+ ?: ObjectManager::getInstance()->get(CompositeConfigProcessor::class);
+ $this->scopeConfig = $scopeConfig ?: ObjectManager::getInstance()->get(ScopeConfigInterface::class);
$this->attributeCollectionFactory = $attributeCollectionFactory
- ?: \Magento\Framework\App\ObjectManager::getInstance()->get(AttributeCollectionFactory::class);
+ ?: ObjectManager::getInstance()->get(AttributeCollectionFactory::class);
+ $this->auth = $auth ?? ObjectManager::getInstance()->get(AuthorizationInterface::class);
}
/**
@@ -651,6 +660,7 @@ private function isProductExists()
* @throws \Magento\Framework\Exception\LocalizedException
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
* @SuppressWarnings(PHPMD.NPathComplexity)
+ * @SuppressWarnings(PHPMD.ExcessiveMethodLength)
* @api
* @since 101.0.0
*/
@@ -658,58 +668,65 @@ public function setupAttributeMeta(ProductAttributeInterface $attribute, $groupC
{
$configPath = ltrim(static::META_CONFIG_PATH, ArrayManager::DEFAULT_PATH_DELIMITER);
$attributeCode = $attribute->getAttributeCode();
- $meta = $this->arrayManager->set($configPath, [], [
- 'dataType' => $attribute->getFrontendInput(),
- 'formElement' => $this->getFormElementsMapValue($attribute->getFrontendInput()),
- 'visible' => $attribute->getIsVisible(),
- 'required' => $attribute->getIsRequired(),
- 'notice' => $attribute->getNote() === null ? null : __($attribute->getNote()),
- 'default' => (!$this->isProductExists()) ? $this->getAttributeDefaultValue($attribute) : null,
- 'label' => __($attribute->getDefaultFrontendLabel()),
- 'code' => $attributeCode,
- 'source' => $groupCode,
- 'scopeLabel' => $this->getScopeLabel($attribute),
- 'globalScope' => $this->isScopeGlobal($attribute),
- 'sortOrder' => $sortOrder * self::SORT_ORDER_MULTIPLIER,
- ]);
+ $meta = $this->arrayManager->set(
+ $configPath,
+ [],
+ [
+ 'dataType' => $attribute->getFrontendInput(),
+ 'formElement' => $this->getFormElementsMapValue($attribute->getFrontendInput()),
+ 'visible' => $attribute->getIsVisible(),
+ 'required' => $attribute->getIsRequired(),
+ 'notice' => $attribute->getNote() === null ? null : __($attribute->getNote()),
+ 'default' => (!$this->isProductExists()) ? $this->getAttributeDefaultValue($attribute) : null,
+ 'label' => __($attribute->getDefaultFrontendLabel()),
+ 'code' => $attributeCode,
+ 'source' => $groupCode,
+ 'scopeLabel' => $this->getScopeLabel($attribute),
+ 'globalScope' => $this->isScopeGlobal($attribute),
+ 'sortOrder' => $sortOrder * self::SORT_ORDER_MULTIPLIER,
+ ]
+ );
// TODO: Refactor to $attribute->getOptions() when MAGETWO-48289 is done
$attributeModel = $this->getAttributeModel($attribute);
if ($attributeModel->usesSource()) {
$options = $attributeModel->getSource()->getAllOptions(true, true);
- $meta = $this->arrayManager->merge($configPath, $meta, [
- 'options' => $this->convertOptionsValueToString($options),
- ]);
+ foreach ($options as &$option) {
+ $option['__disableTmpl'] = true;
+ }
+ $meta = $this->arrayManager->merge(
+ $configPath,
+ $meta,
+ ['options' => $this->convertOptionsValueToString($options)]
+ );
}
if ($this->canDisplayUseDefault($attribute)) {
- $meta = $this->arrayManager->merge($configPath, $meta, [
- 'service' => [
- 'template' => 'ui/form/element/helper/service',
+ $meta = $this->arrayManager->merge(
+ $configPath,
+ $meta,
+ [
+ 'service' => [
+ 'template' => 'ui/form/element/helper/service',
+ ]
]
- ]);
+ );
}
if (!$this->arrayManager->exists($configPath . '/componentType', $meta)) {
- $meta = $this->arrayManager->merge($configPath, $meta, [
- 'componentType' => Field::NAME,
- ]);
+ $meta = $this->arrayManager->merge($configPath, $meta, ['componentType' => Field::NAME]);
}
$product = $this->locator->getProduct();
if (in_array($attributeCode, $this->attributesToDisable)
|| $product->isLockedAttribute($attributeCode)) {
- $meta = $this->arrayManager->merge($configPath, $meta, [
- 'disabled' => true,
- ]);
+ $meta = $this->arrayManager->merge($configPath, $meta, ['disabled' => true]);
}
// TODO: getAttributeModel() should not be used when MAGETWO-48284 is complete
$childData = $this->arrayManager->get($configPath, $meta, []);
if (($rules = $this->catalogEavValidationRules->build($this->getAttributeModel($attribute), $childData))) {
- $meta = $this->arrayManager->merge($configPath, $meta, [
- 'validation' => $rules,
- ]);
+ $meta = $this->arrayManager->merge($configPath, $meta, ['validation' => $rules]);
}
$meta = $this->addUseDefaultValueCheckbox($attribute, $meta);
@@ -730,6 +747,23 @@ public function setupAttributeMeta(ProductAttributeInterface $attribute, $groupC
break;
}
+ //Checking access to design config.
+ $designDesignGroups = ['design', 'schedule-design-update'];
+ if (in_array($groupCode, $designDesignGroups, true)) {
+ if (!$this->auth->isAllowed('Magento_Catalog::edit_product_design')) {
+ $meta = $this->arrayManager->merge(
+ $configPath,
+ $meta,
+ [
+ 'disabled' => true,
+ 'validation' => ['required' => false],
+ 'required' => false,
+ 'serviceDisabled' => true,
+ ]
+ );
+ }
+ }
+
return $meta;
}
@@ -760,11 +794,14 @@ private function getAttributeDefaultValue(ProductAttributeInterface $attribute)
*/
private function convertOptionsValueToString(array $options) : array
{
- array_walk($options, function (&$value) {
- if (isset($value['value']) && is_scalar($value['value'])) {
- $value['value'] = (string)$value['value'];
+ array_walk(
+ $options,
+ function (&$value) {
+ if (isset($value['value']) && is_scalar($value['value'])) {
+ $value['value'] = (string)$value['value'];
+ }
}
- });
+ );
return $options;
}
diff --git a/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/General.php b/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/General.php
old mode 100755
new mode 100644
index 26044eb91a309..91c74a2da5048
--- a/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/General.php
+++ b/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/General.php
@@ -3,13 +3,14 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
+
namespace Magento\Catalog\Ui\DataProvider\Product\Form\Modifier;
use Magento\Catalog\Api\Data\ProductAttributeInterface;
use Magento\Catalog\Model\Locator\LocatorInterface;
use Magento\Eav\Api\AttributeRepositoryInterface;
-use Magento\Ui\Component\Form;
use Magento\Framework\Stdlib\ArrayManager;
+use Magento\Ui\Component\Form;
/**
* Data provider for main panel of product page
@@ -60,10 +61,10 @@ public function __construct(
/**
* Customize number fields for advanced price and weight fields.
*
- * @since 101.0.0
* @param array $data
* @return array
* @throws \Magento\Framework\Exception\NoSuchEntityException
+ * @since 101.0.0
*/
public function modifyData(array $data)
{
@@ -125,7 +126,7 @@ protected function customizeAdvancedPriceFormat(array $data)
$value[ProductAttributeInterface::CODE_TIER_PRICE_FIELD_PRICE] =
$this->formatPrice($value[ProductAttributeInterface::CODE_TIER_PRICE_FIELD_PRICE]);
$value[ProductAttributeInterface::CODE_TIER_PRICE_FIELD_PRICE_QTY] =
- (float) $value[ProductAttributeInterface::CODE_TIER_PRICE_FIELD_PRICE_QTY];
+ (float)$value[ProductAttributeInterface::CODE_TIER_PRICE_FIELD_PRICE_QTY];
}
}
@@ -135,9 +136,9 @@ protected function customizeAdvancedPriceFormat(array $data)
/**
* Customize product form fields.
*
- * @since 101.0.0
* @param array $meta
* @return array
+ * @since 101.0.0
*/
public function modifyMeta(array $meta)
{
@@ -234,9 +235,13 @@ protected function customizeWeightField(array $meta)
null,
'children'
);
- $meta = $this->arrayManager->merge($containerPath . static::META_CONFIG_PATH, $meta, [
- 'component' => 'Magento_Ui/js/form/components/group',
- ]);
+ $meta = $this->arrayManager->merge(
+ $containerPath . static::META_CONFIG_PATH,
+ $meta,
+ [
+ 'component' => 'Magento_Ui/js/form/components/group',
+ ]
+ );
$hasWeightPath = $this->arrayManager->slicePath($weightPath, 0, -1) . '/'
. ProductAttributeInterface::CODE_HAS_WEIGHT;
@@ -438,8 +443,13 @@ protected function formatNumber($value)
$precision = strlen(substr(strrchr($value, "."), 1));
$store = $this->locator->getStore();
$currency = $this->getLocaleCurrency()->getCurrency($store->getBaseCurrencyCode());
- $value = $currency->toCurrency($value, ['display' => \Magento\Framework\Currency::NO_SYMBOL,
- 'precision' => $precision]);
+ $value = $currency->toCurrency(
+ $value,
+ [
+ 'display' => \Magento\Framework\Currency::NO_SYMBOL,
+ 'precision' => $precision
+ ]
+ );
return $value;
}
diff --git a/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/Websites.php b/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/Websites.php
index 7d1d5d4d77d1b..b9d8fc56a91d9 100644
--- a/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/Websites.php
+++ b/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/Websites.php
@@ -175,11 +175,9 @@ protected function getFieldsForFieldset()
$label = __('Websites');
$defaultWebsiteId = $this->websiteRepository->getDefault()->getId();
- $isOnlyOneWebsiteAvailable = count($websitesList) === 1;
foreach ($websitesList as $website) {
$isChecked = in_array($website['id'], $websiteIds)
- || ($defaultWebsiteId == $website['id'] && $isNewProduct)
- || $isOnlyOneWebsiteAvailable;
+ || ($defaultWebsiteId == $website['id'] && $isNewProduct);
$children[$website['id']] = [
'arguments' => [
'data' => [
diff --git a/app/code/Magento/Catalog/Ui/DataProvider/Product/ProductCollection.php b/app/code/Magento/Catalog/Ui/DataProvider/Product/ProductCollection.php
index e5451c8e49847..ea8fc6f2d83b2 100644
--- a/app/code/Magento/Catalog/Ui/DataProvider/Product/ProductCollection.php
+++ b/app/code/Magento/Catalog/Ui/DataProvider/Product/ProductCollection.php
@@ -84,8 +84,11 @@ private function addAttributeToFilterAllStores(Attribute $attributeModel, array
$entity = $this->getEntity();
$fKey = 'e.' . $this->getEntityPkName($entity);
$pKey = $tableName . '.' . $this->getEntityPkName($entity);
+ $attributeId = $attributeModel->getAttributeId();
$condition = "({$pKey} = {$fKey}) AND ("
. $this->_getConditionSql("{$tableName}.value", $condition)
+ . ') AND ('
+ . $this->_getConditionSql("{$tableName}.attribute_id", $attributeId)
. ')';
$selectExistsInAllStores = $this->getConnection()->select()->from($tableName);
$this->getSelect()->exists($selectExistsInAllStores, $condition);
diff --git a/app/code/Magento/Catalog/composer.json b/app/code/Magento/Catalog/composer.json
index 5c3ee3da8ca81..8023634fa074d 100644
--- a/app/code/Magento/Catalog/composer.json
+++ b/app/code/Magento/Catalog/composer.json
@@ -5,7 +5,7 @@
"sort-packages": true
},
"require": {
- "php": "~7.1.3||~7.2.0",
+ "php": "~7.1.3||~7.2.0||~7.3.0",
"magento/framework": "*",
"magento/module-authorization": "*",
"magento/module-asynchronous-operations": "*",
diff --git a/app/code/Magento/Catalog/etc/acl.xml b/app/code/Magento/Catalog/etc/acl.xml
index 4d4b7bdc672d1..c7c0f1f75872d 100644
--- a/app/code/Magento/Catalog/etc/acl.xml
+++ b/app/code/Magento/Catalog/etc/acl.xml
@@ -12,9 +12,12 @@
-
+
+
+
+
+
-
diff --git a/app/code/Magento/Catalog/etc/adminhtml/system.xml b/app/code/Magento/Catalog/etc/adminhtml/system.xml
index a6dd6cbd2e9a1..b83ed8591047a 100644
--- a/app/code/Magento/Catalog/etc/adminhtml/system.xml
+++ b/app/code/Magento/Catalog/etc/adminhtml/system.xml
@@ -41,9 +41,11 @@
Recently Viewed/Compared Products
Lifetime of products in Recently Viewed Widget
+ validate-number validate-zero-or-greater
Lifetime of products in Recently Compared Widget
+ validate-number validate-zero-or-greater
Synchronize widget products with backend storage
@@ -140,6 +142,7 @@
Category Top Navigation
Maximal Depth
+ validate-digits validate-zero-or-greater
@@ -158,6 +161,7 @@
Year Range
+ validate-digits validate-zero-or-greater validate-number-range number-range-1000-9999
Please use a four-digit year format.
Magento\Catalog\Block\Adminhtml\Form\Renderer\Config\YearRange
diff --git a/app/code/Magento/Catalog/etc/config.xml b/app/code/Magento/Catalog/etc/config.xml
index 3a842166a3825..20511f4ff2295 100644
--- a/app/code/Magento/Catalog/etc/config.xml
+++ b/app/code/Magento/Catalog/etc/config.xml
@@ -23,9 +23,9 @@
grid-list
- 9,15,30
+ 12,24,36
5,10,15,20,25
- 9
+ 12
10
0
position
diff --git a/app/code/Magento/Catalog/etc/di.xml b/app/code/Magento/Catalog/etc/di.xml
index e30577a397668..d4d20995a48b4 100644
--- a/app/code/Magento/Catalog/etc/di.xml
+++ b/app/code/Magento/Catalog/etc/di.xml
@@ -76,6 +76,9 @@
+
+
+
@@ -377,11 +380,11 @@
-
+
Magento\Catalog\Pricing\Price\Pool
-
+
diff --git a/app/code/Magento/Catalog/etc/events.xml b/app/code/Magento/Catalog/etc/events.xml
index 5bcdc88369064..f4345ce719a19 100644
--- a/app/code/Magento/Catalog/etc/events.xml
+++ b/app/code/Magento/Catalog/etc/events.xml
@@ -26,6 +26,7 @@
+
diff --git a/app/code/Magento/Catalog/i18n/en_US.csv b/app/code/Magento/Catalog/i18n/en_US.csv
index ed27dfd646cb2..9b7f8a2b07730 100644
--- a/app/code/Magento/Catalog/i18n/en_US.csv
+++ b/app/code/Magento/Catalog/i18n/en_US.csv
@@ -808,4 +808,10 @@ Details,Details
"Start typing to find products", "Start typing to find products"
"Product with ID: (%1) doesn't exist", "Product with ID: (%1) doesn't exist"
"Category with ID: (%1) doesn't exist", "Category with ID: (%1) doesn't exist"
-"You added product %1 to the comparison list .","You added product %1 to the comparison list ."
\ No newline at end of file
+"You added product %1 to the comparison list .","You added product %1 to the comparison list ."
+"Edit Product Design","Edit Product Design"
+"Edit Category Design","Edit Category Design"
+"A total of %1 record(s) haven't been deleted. Please see server logs for more details.","A total of %1 record(s) haven't been deleted. Please see server logs for more details."
+"Are you sure you want to delete this category?","Are you sure you want to delete this category?"
+"Attribute Set Information","Attribute Set Information"
+
diff --git a/app/code/Magento/Catalog/view/adminhtml/layout/catalog_product_attribute_edit_popup.xml b/app/code/Magento/Catalog/view/adminhtml/layout/catalog_product_attribute_edit_popup.xml
old mode 100755
new mode 100644
diff --git a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/attribute/js.phtml b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/attribute/js.phtml
index 2dc39b97c3d95..f020eddc35dbd 100644
--- a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/attribute/js.phtml
+++ b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/attribute/js.phtml
@@ -11,9 +11,10 @@ require([
"jquery",
'Magento_Ui/js/modal/alert',
'Magento_Ui/js/modal/prompt',
+ 'uiRegistry',
"collapsable",
"prototype"
-], function(jQuery, alert, prompt){
+], function(jQuery, alert, prompt, registry){
function toggleApplyVisibility(select) {
if ($(select).value == 1) {
@@ -39,15 +40,20 @@ function getFrontTab() {
function checkOptionsPanelVisibility(){
if($('manage-options-panel')){
- var panel = $('manage-options-panel').up('.fieldset'),
+ var panelId = 'manage-options-panel',
+ panel = $(panelId),
+ panelFieldSet = panel.up('.fieldset'),
activePanelClass = 'selected-type-options';
if($('frontend_input') && ($('frontend_input').value=='select' || $('frontend_input').value=='multiselect')){
- panel.show();
+ panelFieldSet.show();
jQuery(panel).addClass(activePanelClass);
+ registry.get(panelId, function () {
+ jQuery('#' + panelId).trigger('render');
+ });
}
else {
- panel.hide();
+ panelFieldSet.hide();
jQuery(panel).removeClass(activePanelClass);
}
}
diff --git a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/helper/gallery.phtml b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/helper/gallery.phtml
index ca77aa28ea667..d5db46f706ce3 100644
--- a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/helper/gallery.phtml
+++ b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/helper/gallery.phtml
@@ -37,7 +37,7 @@ $formName = $block->getFormName();
type="hidden"
value="= $block->escapeHtmlAttr($typeData['value']) ?>"/>
+ } ?>
diff --git a/app/code/Magento/Catalog/view/frontend/templates/product/gallery.phtml b/app/code/Magento/Catalog/view/frontend/templates/product/gallery.phtml
index e9551793c86f5..e0443d5a55d97 100644
--- a/app/code/Magento/Catalog/view/frontend/templates/product/gallery.phtml
+++ b/app/code/Magento/Catalog/view/frontend/templates/product/gallery.phtml
@@ -34,7 +34,7 @@
id="product-gallery-image"
class="image"
data-mage-init='{"catalogGallery":{}}'/>
-
+
getPreviousImageUrl() || $block->getNextImageUrl()) :?>
getPreviousImageUrl()) :?>
diff --git a/app/code/Magento/Catalog/view/frontend/templates/product/image.phtml b/app/code/Magento/Catalog/view/frontend/templates/product/image.phtml
index 5a1b102ff6362..5a31f3d125c81 100644
--- a/app/code/Magento/Catalog/view/frontend/templates/product/image.phtml
+++ b/app/code/Magento/Catalog/view/frontend/templates/product/image.phtml
@@ -6,7 +6,7 @@
?>
-
escapeHtml($block->getCustomAttributes()) ?>
src="= $block->escapeUrl($block->getImageUrl()) ?>"
width="= $block->escapeHtmlAttr($block->getWidth()) ?>"
diff --git a/app/code/Magento/Catalog/view/frontend/templates/product/list/items.phtml b/app/code/Magento/Catalog/view/frontend/templates/product/list/items.phtml
index 91e261900aef2..926e7c78a7df0 100644
--- a/app/code/Magento/Catalog/view/frontend/templates/product/list/items.phtml
+++ b/app/code/Magento/Catalog/view/frontend/templates/product/list/items.phtml
@@ -170,7 +170,7 @@ switch ($type = $block->getType()) {
= $block->escapeHtml(__('Check items to add to the cart or')) ?>
- = $block->escapeHtml(__('select all')) ?>
+ = $block->escapeHtml(__('select all')) ?>
diff --git a/app/code/Magento/Catalog/view/frontend/templates/product/list/toolbar.phtml b/app/code/Magento/Catalog/view/frontend/templates/product/list/toolbar.phtml
index b2ae8b9f7ab13..76ef6baf4993e 100644
--- a/app/code/Magento/Catalog/view/frontend/templates/product/list/toolbar.phtml
+++ b/app/code/Magento/Catalog/view/frontend/templates/product/list/toolbar.phtml
@@ -15,7 +15,10 @@
// phpcs:disable PSR2.Methods.FunctionCallSignature.SpaceBeforeOpenBracket
?>
getCollection()->getSize()) :?>
-
+ helper(\Magento\Framework\Json\Helper\Data::class)->jsonDecode($block->getWidgetOptionsJson());
+ $widgetOptions = $this->helper(\Magento\Framework\Json\Helper\Data::class)->jsonEncode($widget['productListToolbarForm']);
+ ?>
+
isExpanded()) :?>
getTemplateFile('Magento_Catalog::product/list/toolbar/viewmode.phtml')) ?>
diff --git a/app/code/Magento/Catalog/view/frontend/templates/product/view/attribute.phtml b/app/code/Magento/Catalog/view/frontend/templates/product/view/attribute.phtml
index 2e022a5df14ed..a045a21e55d27 100644
--- a/app/code/Magento/Catalog/view/frontend/templates/product/view/attribute.phtml
+++ b/app/code/Magento/Catalog/view/frontend/templates/product/view/attribute.phtml
@@ -15,6 +15,11 @@
helper(Magento\Catalog\Helper\Output::class);
$_product = $block->getProduct();
+
+if (!$_product instanceof \Magento\Catalog\Model\Product) {
+ return;
+}
+
$_call = $block->getAtCall();
$_code = $block->getAtCode();
$_className = $block->getCssClass();
diff --git a/app/code/Magento/Catalog/view/frontend/templates/product/view/form.phtml b/app/code/Magento/Catalog/view/frontend/templates/product/view/form.phtml
index 8d298aec9f1cb..c6d351b2a9571 100644
--- a/app/code/Magento/Catalog/view/frontend/templates/product/view/form.phtml
+++ b/app/code/Magento/Catalog/view/frontend/templates/product/view/form.phtml
@@ -39,19 +39,13 @@
= $block->getChildHtml('form_bottom') ?>
-
diff --git a/app/code/Magento/Catalog/view/frontend/web/js/catalog-add-to-cart.js b/app/code/Magento/Catalog/view/frontend/web/js/catalog-add-to-cart.js
index bcb7c668657d3..382b4ef98532b 100644
--- a/app/code/Magento/Catalog/view/frontend/web/js/catalog-add-to-cart.js
+++ b/app/code/Magento/Catalog/view/frontend/web/js/catalog-add-to-cart.js
@@ -8,7 +8,7 @@ define([
'mage/translate',
'underscore',
'Magento_Catalog/js/product/view/product-ids-resolver',
- 'jquery/ui'
+ 'jquery-ui-modules/widget'
], function ($, $t, _, idsResolver) {
'use strict';
@@ -135,7 +135,9 @@ define([
// trigger global event, so other modules will be able add parameters to redirect url
$('body').trigger('catalogCategoryAddToCartRedirect', eventData);
- if (eventData.redirectParameters.length > 0) {
+ if (eventData.redirectParameters.length > 0 &&
+ window.location.href.split(/[?#]/)[0] === res.backUrl
+ ) {
parameters = res.backUrl.split('#');
parameters.push(eventData.redirectParameters.join('&'));
res.backUrl = parameters.join('#');
diff --git a/app/code/Magento/Catalog/view/frontend/web/js/gallery.js b/app/code/Magento/Catalog/view/frontend/web/js/gallery.js
index df503cb42287b..f6be6fd58ca25 100644
--- a/app/code/Magento/Catalog/view/frontend/web/js/gallery.js
+++ b/app/code/Magento/Catalog/view/frontend/web/js/gallery.js
@@ -9,7 +9,7 @@
if (typeof define === 'function' && define.amd) {
define([
'jquery',
- 'jquery/ui'
+ 'jquery-ui-modules/widget'
], factory);
} else {
factory(jQuery);
diff --git a/app/code/Magento/Catalog/view/frontend/web/js/list.js b/app/code/Magento/Catalog/view/frontend/web/js/list.js
index 8017aef2413a6..6b1fb7f86a97a 100644
--- a/app/code/Magento/Catalog/view/frontend/web/js/list.js
+++ b/app/code/Magento/Catalog/view/frontend/web/js/list.js
@@ -5,7 +5,7 @@
define([
'jquery',
- 'jquery/ui'
+ 'jquery-ui-modules/widget'
], function ($) {
'use strict';
diff --git a/app/code/Magento/Catalog/view/frontend/web/js/product/list/toolbar.js b/app/code/Magento/Catalog/view/frontend/web/js/product/list/toolbar.js
index b8b6ff65be2b4..6589f7eb0ba48 100644
--- a/app/code/Magento/Catalog/view/frontend/web/js/product/list/toolbar.js
+++ b/app/code/Magento/Catalog/view/frontend/web/js/product/list/toolbar.js
@@ -5,7 +5,7 @@
define([
'jquery',
- 'jquery/ui'
+ 'jquery-ui-modules/widget'
], function ($) {
'use strict';
diff --git a/app/code/Magento/Catalog/view/frontend/web/js/product/remaining-characters.js b/app/code/Magento/Catalog/view/frontend/web/js/product/remaining-characters.js
index 3e29e1ebd4d9c..da03643864442 100644
--- a/app/code/Magento/Catalog/view/frontend/web/js/product/remaining-characters.js
+++ b/app/code/Magento/Catalog/view/frontend/web/js/product/remaining-characters.js
@@ -6,7 +6,7 @@
define([
'jquery',
'mage/translate',
- 'jquery/ui'
+ 'jquery-ui-modules/widget'
], function ($, $t) {
'use strict';
diff --git a/app/code/Magento/Catalog/view/frontend/web/js/related-products.js b/app/code/Magento/Catalog/view/frontend/web/js/related-products.js
index 66df48c28bfab..822dd5b9a7b13 100644
--- a/app/code/Magento/Catalog/view/frontend/web/js/related-products.js
+++ b/app/code/Magento/Catalog/view/frontend/web/js/related-products.js
@@ -5,7 +5,7 @@
define([
'jquery',
- 'jquery/ui',
+ 'jquery-ui-modules/widget',
'mage/translate'
], function ($) {
'use strict';
@@ -17,7 +17,7 @@ define([
relatedProductsField: '#related-products-field', // Hidden input field that stores related products.
selectAllMessage: $.mage.__('select all'),
unselectAllMessage: $.mage.__('unselect all'),
- selectAllLink: '[role="button"]',
+ selectAllLink: '[data-role="select-all"]',
elementsSelector: '.item.product'
},
diff --git a/app/code/Magento/Catalog/view/frontend/web/js/upsell-products.js b/app/code/Magento/Catalog/view/frontend/web/js/upsell-products.js
index 28e5daabdc3b2..da2526d5679c8 100644
--- a/app/code/Magento/Catalog/view/frontend/web/js/upsell-products.js
+++ b/app/code/Magento/Catalog/view/frontend/web/js/upsell-products.js
@@ -5,7 +5,7 @@
define([
'jquery',
- 'jquery/ui'
+ 'jquery-ui-modules/widget'
], function ($) {
'use strict';
diff --git a/app/code/Magento/Catalog/view/frontend/web/js/zoom.js b/app/code/Magento/Catalog/view/frontend/web/js/zoom.js
index f6444223e2c54..1c68818ed9c4e 100644
--- a/app/code/Magento/Catalog/view/frontend/web/js/zoom.js
+++ b/app/code/Magento/Catalog/view/frontend/web/js/zoom.js
@@ -8,7 +8,7 @@
*/
define([
'jquery',
- 'jquery/ui'
+ 'jquery-ui-modules/widget'
], function ($) {
'use strict';
diff --git a/app/code/Magento/Catalog/view/frontend/web/product/view/validation.js b/app/code/Magento/Catalog/view/frontend/web/product/view/validation.js
index ad652b8ef36fe..ab1753e7b9ed3 100644
--- a/app/code/Magento/Catalog/view/frontend/web/product/view/validation.js
+++ b/app/code/Magento/Catalog/view/frontend/web/product/view/validation.js
@@ -9,7 +9,7 @@
if (typeof define === 'function' && define.amd) {
define([
'jquery',
- 'jquery/ui',
+ 'jquery-ui-modules/widget',
'mage/validation/validation'
], factory);
} else {
diff --git a/app/code/Magento/CatalogAnalytics/README.md b/app/code/Magento/CatalogAnalytics/README.md
index f93b223c342d7..0c4ee155c4f27 100644
--- a/app/code/Magento/CatalogAnalytics/README.md
+++ b/app/code/Magento/CatalogAnalytics/README.md
@@ -1,3 +1,3 @@
# Magento_CatalogAnalytics module
-The Magento_CatalogAnalytics module configures data definitions for a data collection related to the Catalog module entities to be used in [Advanced Reporting](https://devdocs.magento.com/guides/v2.2/advanced-reporting/modules.html).
+The Magento_CatalogAnalytics module configures data definitions for a data collection related to the Catalog module entities to be used in [Advanced Reporting](https://devdocs.magento.com/guides/v2.3/advanced-reporting/modules.html).
diff --git a/app/code/Magento/CatalogAnalytics/composer.json b/app/code/Magento/CatalogAnalytics/composer.json
index 805be8a17765f..c6f5c35437089 100644
--- a/app/code/Magento/CatalogAnalytics/composer.json
+++ b/app/code/Magento/CatalogAnalytics/composer.json
@@ -2,7 +2,7 @@
"name": "magento/module-catalog-analytics",
"description": "N/A",
"require": {
- "php": "~7.1.3||~7.2.0",
+ "php": "~7.1.3||~7.2.0||~7.3.0",
"magento/framework": "*",
"magento/module-catalog": "*",
"magento/module-analytics": "*"
diff --git a/app/code/Magento/CatalogGraphQl/DataProvider/AttributeQuery.php b/app/code/Magento/CatalogGraphQl/DataProvider/AttributeQuery.php
new file mode 100644
index 0000000000000..b0f085932bb8e
--- /dev/null
+++ b/app/code/Magento/CatalogGraphQl/DataProvider/AttributeQuery.php
@@ -0,0 +1,354 @@
+resourceConnection = $resourceConnection;
+ $this->metadataPool = $metadataPool;
+ $this->entityType = $entityType;
+ $this->linkedAttributes = $linkedAttributes;
+ $this->eavConfig = $eavConfig;
+ }
+
+ /**
+ * Form and return query to get eav entity $attributes for given $entityIds.
+ *
+ * If eav entities were not found, then data is fetching from $entityTableName.
+ *
+ * @param array $entityIds
+ * @param array $attributes
+ * @param int $storeId
+ * @return Select
+ * @throws \Zend_Db_Select_Exception
+ * @throws \Exception
+ */
+ public function getQuery(array $entityIds, array $attributes, int $storeId): Select
+ {
+ /** @var \Magento\Framework\EntityManager\EntityMetadataInterface $metadata */
+ $metadata = $this->metadataPool->getMetadata($this->entityType);
+ $entityTableName = $metadata->getEntityTable();
+
+ /** @var \Magento\Framework\DB\Adapter\AdapterInterface $connection */
+ $connection = $this->resourceConnection->getConnection();
+ $entityTableAttributes = \array_keys($connection->describeTable($entityTableName));
+
+ $attributeMetadataTable = $this->resourceConnection->getTableName('eav_attribute');
+ $eavAttributes = $this->getEavAttributeCodes($attributes, $entityTableAttributes);
+ $entityTableAttributes = \array_intersect($attributes, $entityTableAttributes);
+
+ $eavAttributesMetaData = $this->getAttributesMetaData($connection, $attributeMetadataTable, $eavAttributes);
+
+ if ($eavAttributesMetaData) {
+ $select = $this->getEavAttributes(
+ $connection,
+ $metadata,
+ $entityTableAttributes,
+ $entityIds,
+ $eavAttributesMetaData,
+ $entityTableName,
+ $storeId
+ );
+ } else {
+ $select = $this->getAttributesFromEntityTable(
+ $connection,
+ $entityTableAttributes,
+ $entityIds,
+ $entityTableName
+ );
+ }
+
+ return $select;
+ }
+
+ /**
+ * Form and return query to get entity $entityTableAttributes for given $entityIds
+ *
+ * @param \Magento\Framework\DB\Adapter\AdapterInterface $connection
+ * @param array $entityTableAttributes
+ * @param array $entityIds
+ * @param string $entityTableName
+ * @return Select
+ */
+ private function getAttributesFromEntityTable(
+ \Magento\Framework\DB\Adapter\AdapterInterface $connection,
+ array $entityTableAttributes,
+ array $entityIds,
+ string $entityTableName
+ ): Select {
+ $select = $connection->select()
+ ->from(['e' => $entityTableName], $entityTableAttributes)
+ ->where('e.entity_id IN (?)', $entityIds);
+
+ return $select;
+ }
+
+ /**
+ * Return ids of eav attributes by $eavAttributeCodes.
+ *
+ * @param \Magento\Framework\DB\Adapter\AdapterInterface $connection
+ * @param string $attributeMetadataTable
+ * @param array $eavAttributeCodes
+ * @return array
+ */
+ private function getAttributesMetaData(
+ \Magento\Framework\DB\Adapter\AdapterInterface $connection,
+ string $attributeMetadataTable,
+ array $eavAttributeCodes
+ ): array {
+ $eavAttributeIdsSelect = $connection->select()
+ ->from(['a' => $attributeMetadataTable], ['attribute_id', 'backend_type', 'attribute_code'])
+ ->where('a.attribute_code IN (?)', $eavAttributeCodes)
+ ->where('a.entity_type_id = ?', $this->getEntityTypeId());
+
+ return $connection->fetchAssoc($eavAttributeIdsSelect);
+ }
+
+ /**
+ * Form and return query to get eav entity $attributes for given $entityIds.
+ *
+ * @param \Magento\Framework\DB\Adapter\AdapterInterface $connection
+ * @param \Magento\Framework\EntityManager\EntityMetadataInterface $metadata
+ * @param array $entityTableAttributes
+ * @param array $entityIds
+ * @param array $eavAttributesMetaData
+ * @param string $entityTableName
+ * @param int $storeId
+ * @return Select
+ * @throws \Zend_Db_Select_Exception
+ */
+ private function getEavAttributes(
+ \Magento\Framework\DB\Adapter\AdapterInterface $connection,
+ \Magento\Framework\EntityManager\EntityMetadataInterface $metadata,
+ array $entityTableAttributes,
+ array $entityIds,
+ array $eavAttributesMetaData,
+ string $entityTableName,
+ int $storeId
+ ): Select {
+ $selects = [];
+ $attributeValueExpression = $connection->getCheckSql(
+ $connection->getIfNullSql('store_eav.value_id', -1) . ' > 0',
+ 'store_eav.value',
+ 'eav.value'
+ );
+ $linkField = $metadata->getLinkField();
+ $attributesPerTable = $this->getAttributeCodeTables($entityTableName, $eavAttributesMetaData);
+ foreach ($attributesPerTable as $attributeTable => $eavAttributes) {
+ $attributeCodeExpression = $this->buildAttributeCodeExpression($eavAttributes);
+
+ $selects[] = $connection->select()
+ ->from(['e' => $entityTableName], $entityTableAttributes)
+ ->joinLeft(
+ ['eav' => $this->resourceConnection->getTableName($attributeTable)],
+ \sprintf('e.%1$s = eav.%1$s', $linkField) .
+ $connection->quoteInto(' AND eav.attribute_id IN (?)', \array_keys($eavAttributesMetaData)) .
+ $connection->quoteInto(' AND eav.store_id = ?', \Magento\Store\Model\Store::DEFAULT_STORE_ID),
+ []
+ )
+ ->joinLeft(
+ ['store_eav' => $this->resourceConnection->getTableName($attributeTable)],
+ \sprintf(
+ 'e.%1$s = store_eav.%1$s AND store_eav.attribute_id = ' .
+ 'eav.attribute_id and store_eav.store_id = %2$d',
+ $linkField,
+ $storeId
+ ),
+ []
+ )
+ ->where('e.entity_id IN (?)', $entityIds)
+ ->columns(
+ [
+ 'attribute_code' => $attributeCodeExpression,
+ 'value' => $attributeValueExpression
+ ]
+ );
+ }
+
+ return $connection->select()->union($selects, Select::SQL_UNION_ALL);
+ }
+
+ /**
+ * Build expression for attribute code field.
+ *
+ * An example:
+ *
+ * ```
+ * CASE
+ * WHEN eav.attribute_id = '73' THEN 'name'
+ * WHEN eav.attribute_id = '121' THEN 'url_key'
+ * END
+ * ```
+ *
+ * @param array $eavAttributes
+ * @return \Zend_Db_Expr
+ */
+ private function buildAttributeCodeExpression(array $eavAttributes): \Zend_Db_Expr
+ {
+ $dbConnection = $this->resourceConnection->getConnection();
+ $expressionParts = ['CASE'];
+
+ foreach ($eavAttributes as $attribute) {
+ $expressionParts[]=
+ $dbConnection->quoteInto('WHEN eav.attribute_id = ?', $attribute['attribute_id'], \Zend_Db::INT_TYPE) .
+ $dbConnection->quoteInto(' THEN ?', $attribute['attribute_code'], 'string');
+ }
+
+ $expressionParts[]= 'END';
+
+ return new \Zend_Db_Expr(implode(' ', $expressionParts));
+ }
+
+ /**
+ * Get list of attribute tables.
+ *
+ * Returns result in the following format: *
+ * ```
+ * $attributeAttributeCodeTables = [
+ * 'm2_catalog_product_entity_varchar' =>
+ * '45' => [
+ * 'attribute_id' => 45,
+ * 'backend_type' => 'varchar',
+ * 'name' => attribute_code,
+ * ]
+ * ]
+ * ];
+ * ```
+ *
+ * @param string $entityTable
+ * @param array $eavAttributesMetaData
+ * @return array
+ */
+ private function getAttributeCodeTables($entityTable, $eavAttributesMetaData): array
+ {
+ $attributeAttributeCodeTables = [];
+ $metaTypes = \array_unique(\array_column($eavAttributesMetaData, 'backend_type'));
+
+ foreach ($metaTypes as $type) {
+ if (\in_array($type, self::SUPPORTED_BACKEND_TYPES, true)) {
+ $tableName = \sprintf('%s_%s', $entityTable, $type);
+ $attributeAttributeCodeTables[$tableName] = array_filter(
+ $eavAttributesMetaData,
+ function ($attribute) use ($type) {
+ return $attribute['backend_type'] === $type;
+ }
+ );
+ }
+ }
+
+ return $attributeAttributeCodeTables;
+ }
+
+ /**
+ * Get EAV attribute codes
+ * Remove attributes from entity table and attributes from exclude list
+ * Add linked attributes to output
+ *
+ * @param array $attributes
+ * @param array $entityTableAttributes
+ * @return array
+ */
+ private function getEavAttributeCodes($attributes, $entityTableAttributes): array
+ {
+ $attributes = \array_diff($attributes, $entityTableAttributes);
+ $unusedAttributeList = [];
+ $newAttributes = [];
+ foreach ($this->linkedAttributes as $attribute => $linkedAttributes) {
+ if (null === $linkedAttributes) {
+ $unusedAttributeList[] = $attribute;
+ } elseif (\is_array($linkedAttributes) && \in_array($attribute, $attributes, true)) {
+ $newAttributes[] = $linkedAttributes;
+ }
+ }
+ $attributes = \array_diff($attributes, $unusedAttributeList);
+
+ return \array_unique(\array_merge($attributes, ...$newAttributes));
+ }
+
+ /**
+ * Retrieve entity type id
+ *
+ * @return int
+ * @throws \Exception
+ */
+ private function getEntityTypeId(): int
+ {
+ if (!isset($this->entityTypeIdMap[$this->entityType])) {
+ $this->entityTypeIdMap[$this->entityType] = (int)$this->eavConfig->getEntityType(
+ $this->metadataPool->getMetadata($this->entityType)->getEavEntityType()
+ )->getId();
+ }
+
+ return $this->entityTypeIdMap[$this->entityType];
+ }
+}
diff --git a/app/code/Magento/CatalogGraphQl/DataProvider/Category/Query/CategoryAttributeQuery.php b/app/code/Magento/CatalogGraphQl/DataProvider/Category/Query/CategoryAttributeQuery.php
new file mode 100644
index 0000000000000..e3dfa38c78258
--- /dev/null
+++ b/app/code/Magento/CatalogGraphQl/DataProvider/Category/Query/CategoryAttributeQuery.php
@@ -0,0 +1,60 @@
+attributeQueryFactory = $attributeQueryFactory;
+ }
+
+ /**
+ * Form and return query to get eav attributes for given categories
+ *
+ * @param array $categoryIds
+ * @param array $categoryAttributes
+ * @param int $storeId
+ * @return Select
+ * @throws \Zend_Db_Select_Exception
+ */
+ public function getQuery(array $categoryIds, array $categoryAttributes, int $storeId): Select
+ {
+ $categoryAttributes = \array_merge($categoryAttributes, self::$requiredAttributes);
+
+ $attributeQuery = $this->attributeQueryFactory->create(
+ [
+ 'entityType' => CategoryInterface::class
+ ]
+ );
+
+ return $attributeQuery->getQuery($categoryIds, $categoryAttributes, $storeId);
+ }
+}
diff --git a/app/code/Magento/CatalogGraphQl/DataProvider/CategoryAttributesMapper.php b/app/code/Magento/CatalogGraphQl/DataProvider/CategoryAttributesMapper.php
new file mode 100644
index 0000000000000..ea3c0b608d212
--- /dev/null
+++ b/app/code/Magento/CatalogGraphQl/DataProvider/CategoryAttributesMapper.php
@@ -0,0 +1,117 @@
+graphqlConfig = $graphqlConfig;
+ }
+
+ /**
+ * Returns attribute values for given attribute codes.
+ *
+ * @param array $fetchResult
+ * @return array
+ */
+ public function getAttributesValues(array $fetchResult): array
+ {
+ $attributes = [];
+
+ foreach ($fetchResult as $row) {
+ if (!isset($attributes[$row['entity_id']])) {
+ $attributes[$row['entity_id']] = $row;
+ //TODO: do we need to introduce field mapping?
+ $attributes[$row['entity_id']]['id'] = $row['entity_id'];
+ }
+ if (isset($row['attribute_code'])) {
+ $attributes[$row['entity_id']][$row['attribute_code']] = $row['value'];
+ }
+ }
+
+ return $this->formatAttributes($attributes);
+ }
+
+ /**
+ * Format attributes that should be converted to array type
+ *
+ * @param array $attributes
+ * @return array
+ */
+ private function formatAttributes(array $attributes): array
+ {
+ $arrayTypeAttributes = $this->getFieldsOfArrayType();
+
+ return $arrayTypeAttributes
+ ? array_map(
+ function ($data) use ($arrayTypeAttributes) {
+ foreach ($arrayTypeAttributes as $attributeCode) {
+ $data[$attributeCode] = $this->valueToArray($data[$attributeCode] ?? null);
+ }
+ return $data;
+ },
+ $attributes
+ )
+ : $attributes;
+ }
+
+ /**
+ * Cast string to array
+ *
+ * @param string|null $value
+ * @return array
+ */
+ private function valueToArray($value): array
+ {
+ return $value ? \explode(',', $value) : [];
+ }
+
+ /**
+ * Get fields that should be converted to array type
+ *
+ * @return array
+ */
+ private function getFieldsOfArrayType(): array
+ {
+ $categoryTreeSchema = $this->graphqlConfig->getConfigElement('CategoryTree');
+ if (!$categoryTreeSchema instanceof Type) {
+ throw new \LogicException('CategoryTree type not defined in schema.');
+ }
+
+ $fields = [];
+ foreach ($categoryTreeSchema->getInterfaces() as $interface) {
+ /** @var InterfaceType $configElement */
+ $configElement = $this->graphqlConfig->getConfigElement($interface['interface']);
+
+ foreach ($configElement->getFields() as $field) {
+ if ($field->isList()) {
+ $fields[] = $field->getName();
+ }
+ }
+ }
+
+ return $fields;
+ }
+}
diff --git a/app/code/Magento/CatalogGraphQl/DataProvider/Product/LayeredNavigation/AttributeOptionProvider.php b/app/code/Magento/CatalogGraphQl/DataProvider/Product/LayeredNavigation/AttributeOptionProvider.php
new file mode 100644
index 0000000000000..7781473128754
--- /dev/null
+++ b/app/code/Magento/CatalogGraphQl/DataProvider/Product/LayeredNavigation/AttributeOptionProvider.php
@@ -0,0 +1,107 @@
+ [
+ * attribute_code => code,
+ * attribute_label => attribute label,
+ * option_label => option label,
+ * options => [option_id => 'option label', ...],
+ * ]
+ * ...
+ * ]
+ */
+class AttributeOptionProvider
+{
+ /**
+ * @var ResourceConnection
+ */
+ private $resourceConnection;
+
+ /**
+ * @param ResourceConnection $resourceConnection
+ */
+ public function __construct(ResourceConnection $resourceConnection)
+ {
+ $this->resourceConnection = $resourceConnection;
+ }
+
+ /**
+ * Get option data. Return list of attributes with option data
+ *
+ * @param array $optionIds
+ * @return array
+ * @throws \Zend_Db_Statement_Exception
+ */
+ public function getOptions(array $optionIds): array
+ {
+ if (!$optionIds) {
+ return [];
+ }
+
+ $connection = $this->resourceConnection->getConnection();
+ $select = $connection->select()
+ ->from(
+ ['a' => $this->resourceConnection->getTableName('eav_attribute')],
+ [
+ 'attribute_id' => 'a.attribute_id',
+ 'attribute_code' => 'a.attribute_code',
+ 'attribute_label' => 'a.frontend_label',
+ ]
+ )
+ ->joinInner(
+ ['options' => $this->resourceConnection->getTableName('eav_attribute_option')],
+ 'a.attribute_id = options.attribute_id',
+ []
+ )
+ ->joinInner(
+ ['option_value' => $this->resourceConnection->getTableName('eav_attribute_option_value')],
+ 'options.option_id = option_value.option_id',
+ [
+ 'option_label' => 'option_value.value',
+ 'option_id' => 'option_value.option_id',
+ ]
+ )
+ ->where('option_value.option_id IN (?)', $optionIds);
+
+ return $this->formatResult($select);
+ }
+
+ /**
+ * Format result
+ *
+ * @param \Magento\Framework\DB\Select $select
+ * @return array
+ * @throws \Zend_Db_Statement_Exception
+ */
+ private function formatResult(\Magento\Framework\DB\Select $select): array
+ {
+ $statement = $this->resourceConnection->getConnection()->query($select);
+
+ $result = [];
+ while ($option = $statement->fetch()) {
+ if (!isset($result[$option['attribute_code']])) {
+ $result[$option['attribute_code']] = [
+ 'attribute_id' => $option['attribute_id'],
+ 'attribute_code' => $option['attribute_code'],
+ 'attribute_label' => $option['attribute_label'],
+ 'options' => [],
+ ];
+ }
+ $result[$option['attribute_code']]['options'][$option['option_id']] = $option['option_label'];
+ }
+
+ return $result;
+ }
+}
diff --git a/app/code/Magento/CatalogGraphQl/DataProvider/Product/LayeredNavigation/Builder/Attribute.php b/app/code/Magento/CatalogGraphQl/DataProvider/Product/LayeredNavigation/Builder/Attribute.php
new file mode 100644
index 0000000000000..b70c9f6165fc6
--- /dev/null
+++ b/app/code/Magento/CatalogGraphQl/DataProvider/Product/LayeredNavigation/Builder/Attribute.php
@@ -0,0 +1,157 @@
+attributeOptionProvider = $attributeOptionProvider;
+ $this->layerFormatter = $layerFormatter;
+ $this->bucketNameFilter = \array_merge($this->bucketNameFilter, $bucketNameFilter);
+ }
+
+ /**
+ * @inheritdoc
+ * @SuppressWarnings(PHPMD.UnusedFormalParameter)
+ * @throws \Zend_Db_Statement_Exception
+ */
+ public function build(AggregationInterface $aggregation, ?int $storeId): array
+ {
+ $attributeOptions = $this->getAttributeOptions($aggregation);
+
+ // build layer per attribute
+ $result = [];
+ foreach ($this->getAttributeBuckets($aggregation) as $bucket) {
+ $bucketName = $bucket->getName();
+ $attributeCode = \preg_replace('~_bucket$~', '', $bucketName);
+ $attribute = $attributeOptions[$attributeCode] ?? [];
+
+ $result[$bucketName] = $this->layerFormatter->buildLayer(
+ $attribute['attribute_label'] ?? $bucketName,
+ \count($bucket->getValues()),
+ $attribute['attribute_code'] ?? $bucketName
+ );
+
+ foreach ($bucket->getValues() as $value) {
+ $metrics = $value->getMetrics();
+ $result[$bucketName]['options'][] = $this->layerFormatter->buildItem(
+ $attribute['options'][$metrics['value']] ?? $metrics['value'],
+ $metrics['value'],
+ $metrics['count']
+ );
+ }
+ }
+
+ return $result;
+ }
+
+ /**
+ * Get attribute buckets excluding specified bucket names
+ *
+ * @param AggregationInterface $aggregation
+ * @return \Generator|BucketInterface[]
+ */
+ private function getAttributeBuckets(AggregationInterface $aggregation)
+ {
+ foreach ($aggregation->getBuckets() as $bucket) {
+ if (\in_array($bucket->getName(), $this->bucketNameFilter, true)) {
+ continue;
+ }
+ if ($this->isBucketEmpty($bucket)) {
+ continue;
+ }
+ yield $bucket;
+ }
+ }
+
+ /**
+ * Check that bucket contains data
+ *
+ * @param BucketInterface|null $bucket
+ * @return bool
+ */
+ private function isBucketEmpty(?BucketInterface $bucket): bool
+ {
+ return null === $bucket || !$bucket->getValues();
+ }
+
+ /**
+ * Get list of attributes with options
+ *
+ * @param AggregationInterface $aggregation
+ * @return array
+ * @throws \Zend_Db_Statement_Exception
+ */
+ private function getAttributeOptions(AggregationInterface $aggregation): array
+ {
+ $attributeOptionIds = [];
+ foreach ($this->getAttributeBuckets($aggregation) as $bucket) {
+ $attributeOptionIds[] = \array_map(
+ function (AggregationValueInterface $value) {
+ return $value->getValue();
+ },
+ $bucket->getValues()
+ );
+ }
+
+ if (!$attributeOptionIds) {
+ return [];
+ }
+
+ return $this->attributeOptionProvider->getOptions(\array_merge(...$attributeOptionIds));
+ }
+}
diff --git a/app/code/Magento/CatalogGraphQl/DataProvider/Product/LayeredNavigation/Builder/Category.php b/app/code/Magento/CatalogGraphQl/DataProvider/Product/LayeredNavigation/Builder/Category.php
new file mode 100644
index 0000000000000..b0e67d72e25ba
--- /dev/null
+++ b/app/code/Magento/CatalogGraphQl/DataProvider/Product/LayeredNavigation/Builder/Category.php
@@ -0,0 +1,151 @@
+ [
+ 'request_name' => 'category_id',
+ 'label' => 'Category'
+ ],
+ ];
+
+ /**
+ * @var CategoryAttributeQuery
+ */
+ private $categoryAttributeQuery;
+
+ /**
+ * @var CategoryAttributesMapper
+ */
+ private $attributesMapper;
+
+ /**
+ * @var ResourceConnection
+ */
+ private $resourceConnection;
+
+ /**
+ * @var RootCategoryProvider
+ */
+ private $rootCategoryProvider;
+
+ /**
+ * @var LayerFormatter
+ */
+ private $layerFormatter;
+
+ /**
+ * @param CategoryAttributeQuery $categoryAttributeQuery
+ * @param CategoryAttributesMapper $attributesMapper
+ * @param RootCategoryProvider $rootCategoryProvider
+ * @param ResourceConnection $resourceConnection
+ * @param LayerFormatter $layerFormatter
+ */
+ public function __construct(
+ CategoryAttributeQuery $categoryAttributeQuery,
+ CategoryAttributesMapper $attributesMapper,
+ RootCategoryProvider $rootCategoryProvider,
+ ResourceConnection $resourceConnection,
+ LayerFormatter $layerFormatter
+ ) {
+ $this->categoryAttributeQuery = $categoryAttributeQuery;
+ $this->attributesMapper = $attributesMapper;
+ $this->resourceConnection = $resourceConnection;
+ $this->rootCategoryProvider = $rootCategoryProvider;
+ $this->layerFormatter = $layerFormatter;
+ }
+
+ /**
+ * @inheritdoc
+ * @throws \Magento\Framework\Exception\LocalizedException
+ * @throws \Zend_Db_Select_Exception
+ */
+ public function build(AggregationInterface $aggregation, ?int $storeId): array
+ {
+ $bucket = $aggregation->getBucket(self::CATEGORY_BUCKET);
+ if ($this->isBucketEmpty($bucket)) {
+ return [];
+ }
+
+ $categoryIds = \array_map(
+ function (AggregationValueInterface $value) {
+ return (int)$value->getValue();
+ },
+ $bucket->getValues()
+ );
+
+ $categoryIds = \array_diff($categoryIds, [$this->rootCategoryProvider->getRootCategory($storeId)]);
+ $categoryLabels = \array_column(
+ $this->attributesMapper->getAttributesValues(
+ $this->resourceConnection->getConnection()->fetchAll(
+ $this->categoryAttributeQuery->getQuery($categoryIds, ['name'], $storeId)
+ )
+ ),
+ 'name',
+ 'entity_id'
+ );
+
+ if (!$categoryLabels) {
+ return [];
+ }
+
+ $result = $this->layerFormatter->buildLayer(
+ self::$bucketMap[self::CATEGORY_BUCKET]['label'],
+ \count($categoryIds),
+ self::$bucketMap[self::CATEGORY_BUCKET]['request_name']
+ );
+
+ foreach ($bucket->getValues() as $value) {
+ $categoryId = $value->getValue();
+ if (!\in_array($categoryId, $categoryIds, true)) {
+ continue ;
+ }
+ $result['options'][] = $this->layerFormatter->buildItem(
+ $categoryLabels[$categoryId] ?? $categoryId,
+ $categoryId,
+ $value->getMetrics()['count']
+ );
+ }
+
+ return [$result];
+ }
+
+ /**
+ * Check that bucket contains data
+ *
+ * @param BucketInterface|null $bucket
+ * @return bool
+ */
+ private function isBucketEmpty(?BucketInterface $bucket): bool
+ {
+ return null === $bucket || !$bucket->getValues();
+ }
+}
diff --git a/app/code/Magento/CatalogGraphQl/DataProvider/Product/LayeredNavigation/Builder/Price.php b/app/code/Magento/CatalogGraphQl/DataProvider/Product/LayeredNavigation/Builder/Price.php
new file mode 100644
index 0000000000000..02b638edbdce8
--- /dev/null
+++ b/app/code/Magento/CatalogGraphQl/DataProvider/Product/LayeredNavigation/Builder/Price.php
@@ -0,0 +1,88 @@
+ [
+ 'request_name' => 'price',
+ 'label' => 'Price'
+ ],
+ ];
+
+ /**
+ * @param LayerFormatter $layerFormatter
+ */
+ public function __construct(
+ LayerFormatter $layerFormatter
+ ) {
+ $this->layerFormatter = $layerFormatter;
+ }
+
+ /**
+ * @inheritdoc
+ * @SuppressWarnings(PHPMD.UnusedFormalParameter)
+ */
+ public function build(AggregationInterface $aggregation, ?int $storeId): array
+ {
+ $bucket = $aggregation->getBucket(self::PRICE_BUCKET);
+ if ($this->isBucketEmpty($bucket)) {
+ return [];
+ }
+
+ $result = $this->layerFormatter->buildLayer(
+ self::$bucketMap[self::PRICE_BUCKET]['label'],
+ \count($bucket->getValues()),
+ self::$bucketMap[self::PRICE_BUCKET]['request_name']
+ );
+
+ foreach ($bucket->getValues() as $value) {
+ $metrics = $value->getMetrics();
+ $result['options'][] = $this->layerFormatter->buildItem(
+ \str_replace('_', '-', $metrics['value']),
+ $metrics['value'],
+ $metrics['count']
+ );
+ }
+
+ return [$result];
+ }
+
+ /**
+ * Check that bucket contains data
+ *
+ * @param BucketInterface|null $bucket
+ * @return bool
+ */
+ private function isBucketEmpty(?BucketInterface $bucket): bool
+ {
+ return null === $bucket || !$bucket->getValues();
+ }
+}
diff --git a/app/code/Magento/CatalogGraphQl/DataProvider/Product/LayeredNavigation/Formatter/LayerFormatter.php b/app/code/Magento/CatalogGraphQl/DataProvider/Product/LayeredNavigation/Formatter/LayerFormatter.php
new file mode 100644
index 0000000000000..48a1265b10fc3
--- /dev/null
+++ b/app/code/Magento/CatalogGraphQl/DataProvider/Product/LayeredNavigation/Formatter/LayerFormatter.php
@@ -0,0 +1,48 @@
+ $layerName,
+ 'count' => $itemsCount,
+ 'attribute_code' => $requestName
+ ];
+ }
+
+ /**
+ * Format layer item data
+ *
+ * @param string $label
+ * @param string|int $value
+ * @param string|int $count
+ * @return array
+ */
+ public function buildItem($label, $value, $count): array
+ {
+ return [
+ 'label' => $label,
+ 'value' => $value,
+ 'count' => $count,
+ ];
+ }
+}
diff --git a/app/code/Magento/CatalogGraphQl/DataProvider/Product/LayeredNavigation/LayerBuilder.php b/app/code/Magento/CatalogGraphQl/DataProvider/Product/LayeredNavigation/LayerBuilder.php
new file mode 100644
index 0000000000000..ff661236be62f
--- /dev/null
+++ b/app/code/Magento/CatalogGraphQl/DataProvider/Product/LayeredNavigation/LayerBuilder.php
@@ -0,0 +1,43 @@
+builders = $builders;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function build(AggregationInterface $aggregation, ?int $storeId): array
+ {
+ $layers = [];
+ foreach ($this->builders as $builder) {
+ $layers[] = $builder->build($aggregation, $storeId);
+ }
+ $layers = \array_merge(...$layers);
+
+ return \array_filter($layers);
+ }
+}
diff --git a/app/code/Magento/CatalogGraphQl/DataProvider/Product/LayeredNavigation/LayerBuilderInterface.php b/app/code/Magento/CatalogGraphQl/DataProvider/Product/LayeredNavigation/LayerBuilderInterface.php
new file mode 100644
index 0000000000000..bd55bc6938b39
--- /dev/null
+++ b/app/code/Magento/CatalogGraphQl/DataProvider/Product/LayeredNavigation/LayerBuilderInterface.php
@@ -0,0 +1,40 @@
+ 'layer name',
+ * 'filter_items_count' => 'filter items count',
+ * 'request_var' => 'filter name in request',
+ * 'filter_items' => [
+ * 'label' => 'item name',
+ * 'value_string' => 'item value, e.g. category ID',
+ * 'items_count' => 'product count',
+ * ],
+ * ],
+ * ...
+ * ];
+ */
+interface LayerBuilderInterface
+{
+ /**
+ * Build layer data
+ *
+ * @param AggregationInterface $aggregation
+ * @param int|null $storeId
+ * @return array [[{layer data}], ...]
+ */
+ public function build(AggregationInterface $aggregation, ?int $storeId): array;
+}
diff --git a/app/code/Magento/CatalogGraphQl/DataProvider/Product/LayeredNavigation/RootCategoryProvider.php b/app/code/Magento/CatalogGraphQl/DataProvider/Product/LayeredNavigation/RootCategoryProvider.php
new file mode 100644
index 0000000000000..4b8a4a31b3c35
--- /dev/null
+++ b/app/code/Magento/CatalogGraphQl/DataProvider/Product/LayeredNavigation/RootCategoryProvider.php
@@ -0,0 +1,55 @@
+resourceConnection = $resourceConnection;
+ }
+
+ /**
+ * Get root category for specified store id
+ *
+ * @param int $storeId
+ * @return int
+ */
+ public function getRootCategory(int $storeId): int
+ {
+ $connection = $this->resourceConnection->getConnection();
+
+ $select = $connection->select()
+ ->from(
+ ['store' => $this->resourceConnection->getTableName('store')],
+ []
+ )
+ ->join(
+ ['store_group' => $this->resourceConnection->getTableName('store_group')],
+ 'store.group_id = store_group.group_id',
+ ['root_category_id' => 'store_group.root_category_id']
+ )
+ ->where('store.store_id = ?', $storeId);
+
+ return (int)$connection->fetchOne($select);
+ }
+}
diff --git a/app/code/Magento/CatalogGraphQl/DataProvider/Product/SearchCriteriaBuilder.php b/app/code/Magento/CatalogGraphQl/DataProvider/Product/SearchCriteriaBuilder.php
new file mode 100644
index 0000000000000..0e92bbbab4259
--- /dev/null
+++ b/app/code/Magento/CatalogGraphQl/DataProvider/Product/SearchCriteriaBuilder.php
@@ -0,0 +1,208 @@
+scopeConfig = $scopeConfig;
+ $this->filterBuilder = $filterBuilder;
+ $this->filterGroupBuilder = $filterGroupBuilder;
+ $this->builder = $builder;
+ $this->visibility = $visibility;
+ $this->sortOrderBuilder = $sortOrderBuilder;
+ }
+
+ /**
+ * Build search criteria
+ *
+ * @param array $args
+ * @param bool $includeAggregation
+ * @return SearchCriteriaInterface
+ */
+ public function build(array $args, bool $includeAggregation): SearchCriteriaInterface
+ {
+ $searchCriteria = $this->builder->build('products', $args);
+ $isSearch = !empty($args['search']);
+ $this->updateRangeFilters($searchCriteria);
+
+ if ($includeAggregation) {
+ $this->preparePriceAggregation($searchCriteria);
+ $requestName = 'graphql_product_search_with_aggregation';
+ } else {
+ $requestName = 'graphql_product_search';
+ }
+ $searchCriteria->setRequestName($requestName);
+
+ if ($isSearch) {
+ $this->addFilter($searchCriteria, 'search_term', $args['search']);
+ }
+
+ if (!$searchCriteria->getSortOrders()) {
+ $this->addDefaultSortOrder($searchCriteria, $isSearch);
+ }
+
+ $this->addVisibilityFilter($searchCriteria, $isSearch, !empty($args['filter']));
+
+ $searchCriteria->setCurrentPage($args['currentPage']);
+ $searchCriteria->setPageSize($args['pageSize']);
+
+ return $searchCriteria;
+ }
+
+ /**
+ * Add filter by visibility
+ *
+ * @param SearchCriteriaInterface $searchCriteria
+ * @param bool $isSearch
+ * @param bool $isFilter
+ */
+ private function addVisibilityFilter(SearchCriteriaInterface $searchCriteria, bool $isSearch, bool $isFilter): void
+ {
+ if ($isFilter && $isSearch) {
+ // Index already contains products filtered by visibility: catalog, search, both
+ return ;
+ }
+ $visibilityIds = $isSearch
+ ? $this->visibility->getVisibleInSearchIds()
+ : $this->visibility->getVisibleInCatalogIds();
+
+ $this->addFilter($searchCriteria, 'visibility', $visibilityIds);
+ }
+
+ /**
+ * Prepare price aggregation algorithm
+ *
+ * @param SearchCriteriaInterface $searchCriteria
+ * @return void
+ */
+ private function preparePriceAggregation(SearchCriteriaInterface $searchCriteria): void
+ {
+ $priceRangeCalculation = $this->scopeConfig->getValue(
+ \Magento\Catalog\Model\Layer\Filter\Dynamic\AlgorithmFactory::XML_PATH_RANGE_CALCULATION,
+ \Magento\Store\Model\ScopeInterface::SCOPE_STORE
+ );
+ if ($priceRangeCalculation) {
+ $this->addFilter($searchCriteria, 'price_dynamic_algorithm', $priceRangeCalculation);
+ }
+ }
+
+ /**
+ * Add filter to search criteria
+ *
+ * @param SearchCriteriaInterface $searchCriteria
+ * @param string $field
+ * @param mixed $value
+ */
+ private function addFilter(SearchCriteriaInterface $searchCriteria, string $field, $value): void
+ {
+ $filter = $this->filterBuilder
+ ->setField($field)
+ ->setValue($value)
+ ->create();
+ $this->filterGroupBuilder->addFilter($filter);
+ $filterGroups = $searchCriteria->getFilterGroups();
+ $filterGroups[] = $this->filterGroupBuilder->create();
+ $searchCriteria->setFilterGroups($filterGroups);
+ }
+
+ /**
+ * Sort by relevance DESC by default
+ *
+ * @param SearchCriteriaInterface $searchCriteria
+ * @param bool $isSearch
+ */
+ private function addDefaultSortOrder(SearchCriteriaInterface $searchCriteria, $isSearch = false): void
+ {
+ $sortField = $isSearch ? 'relevance' : EavAttributeInterface::POSITION;
+ $sortDirection = $isSearch ? SortOrder::SORT_DESC : SortOrder::SORT_ASC;
+ $defaultSortOrder = $this->sortOrderBuilder
+ ->setField($sortField)
+ ->setDirection($sortDirection)
+ ->create();
+
+ $searchCriteria->setSortOrders([$defaultSortOrder]);
+ }
+
+ /**
+ * Format range filters so replacement works
+ *
+ * Range filter fields in search request must replace value like '%field.from%' or '%field.to%'
+ *
+ * @param SearchCriteriaInterface $searchCriteria
+ */
+ private function updateRangeFilters(SearchCriteriaInterface $searchCriteria): void
+ {
+ $filterGroups = $searchCriteria->getFilterGroups();
+ foreach ($filterGroups as $filterGroup) {
+ $filters = $filterGroup->getFilters();
+ foreach ($filters as $filter) {
+ if (in_array($filter->getConditionType(), ['from', 'to'])) {
+ $filter->setField($filter->getField() . '.' . $filter->getConditionType());
+ }
+ }
+ }
+ }
+}
diff --git a/app/code/Magento/CatalogGraphQl/Model/AggregationOptionTypeResolver.php b/app/code/Magento/CatalogGraphQl/Model/AggregationOptionTypeResolver.php
new file mode 100644
index 0000000000000..3a532a1a6c760
--- /dev/null
+++ b/app/code/Magento/CatalogGraphQl/Model/AggregationOptionTypeResolver.php
@@ -0,0 +1,29 @@
+typeResolvers = $typeResolvers;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function resolveType(array $data) : string
+ {
+ /** @var TypeResolverInterface $typeResolver */
+ foreach ($this->typeResolvers as $typeResolver) {
+ $resolvedType = $typeResolver->resolveType($data);
+ if ($resolvedType) {
+ return $resolvedType;
+ }
+ }
+ throw new GraphQlInputException(__('Cannot resolve aggregation option type'));
+ }
+}
diff --git a/app/code/Magento/CatalogGraphQl/Model/Category/LevelCalculator.php b/app/code/Magento/CatalogGraphQl/Model/Category/LevelCalculator.php
index f587be245c99d..67ca3b85d6f2f 100644
--- a/app/code/Magento/CatalogGraphQl/Model/Category/LevelCalculator.php
+++ b/app/code/Magento/CatalogGraphQl/Model/Category/LevelCalculator.php
@@ -48,7 +48,7 @@ public function calculate(int $rootCategoryId) : int
$connection = $this->resourceConnection->getConnection();
$select = $connection->select()
->from($this->resourceConnection->getTableName('catalog_category_entity'), 'level')
- ->where($this->resourceCategory->getLinkField() . " = ?", $rootCategoryId);
+ ->where($this->resourceCategory->getEntityIdField() . " = ?", $rootCategoryId);
return (int) $connection->fetchOne($select);
}
diff --git a/app/code/Magento/CatalogGraphQl/Model/Config/FilterAttributeReader.php b/app/code/Magento/CatalogGraphQl/Model/Config/FilterAttributeReader.php
new file mode 100644
index 0000000000000..4f3a88cc788df
--- /dev/null
+++ b/app/code/Magento/CatalogGraphQl/Model/Config/FilterAttributeReader.php
@@ -0,0 +1,134 @@
+mapper = $mapper;
+ $this->collectionFactory = $collectionFactory;
+ $this->exactMatchAttributes = array_merge($this->exactMatchAttributes, $exactMatchAttributes);
+ }
+
+ /**
+ * Read configuration scope
+ *
+ * @param string|null $scope
+ * @return array
+ * @SuppressWarnings(PHPMD.UnusedFormalParameter)
+ */
+ public function read($scope = null) : array
+ {
+ $typeNames = $this->mapper->getMappedTypes(self::ENTITY_TYPE);
+ $config = [];
+
+ foreach ($this->getAttributeCollection() as $attribute) {
+ $attributeCode = $attribute->getAttributeCode();
+
+ foreach ($typeNames as $typeName) {
+ $config[$typeName]['fields'][$attributeCode] = [
+ 'name' => $attributeCode,
+ 'type' => $this->getFilterType($attribute),
+ 'arguments' => [],
+ 'required' => false,
+ 'description' => sprintf('Attribute label: %s', $attribute->getDefaultFrontendLabel())
+ ];
+ }
+ }
+
+ return $config;
+ }
+
+ /**
+ * Map attribute type to filter type
+ *
+ * @param Attribute $attribute
+ * @return string
+ */
+ private function getFilterType(Attribute $attribute): string
+ {
+ if (in_array($attribute->getAttributeCode(), $this->exactMatchAttributes)) {
+ return self::FILTER_EQUAL_TYPE;
+ }
+
+ $filterTypeMap = [
+ 'price' => self::FILTER_RANGE_TYPE,
+ 'date' => self::FILTER_RANGE_TYPE,
+ 'select' => self::FILTER_EQUAL_TYPE,
+ 'multiselect' => self::FILTER_EQUAL_TYPE,
+ 'boolean' => self::FILTER_EQUAL_TYPE,
+ 'text' => self::FILTER_MATCH_TYPE,
+ 'textarea' => self::FILTER_MATCH_TYPE,
+ ];
+
+ return $filterTypeMap[$attribute->getFrontendInput()] ?? self::FILTER_MATCH_TYPE;
+ }
+
+ /**
+ * Create attribute collection
+ *
+ * @return Collection|\Magento\Catalog\Model\ResourceModel\Eav\Attribute[]
+ */
+ private function getAttributeCollection()
+ {
+ return $this->collectionFactory->create()
+ ->addHasOptionsFilter()
+ ->addIsSearchableFilter()
+ ->addDisplayInAdvancedSearchFilter();
+ }
+}
diff --git a/app/code/Magento/CatalogGraphQl/Model/Config/SortAttributeReader.php b/app/code/Magento/CatalogGraphQl/Model/Config/SortAttributeReader.php
new file mode 100644
index 0000000000000..215b28be0579c
--- /dev/null
+++ b/app/code/Magento/CatalogGraphQl/Model/Config/SortAttributeReader.php
@@ -0,0 +1,79 @@
+mapper = $mapper;
+ $this->attributesCollection = $attributesCollection;
+ }
+
+ /**
+ * Read configuration scope
+ *
+ * @param string|null $scope
+ * @return array
+ * @SuppressWarnings(PHPMD.UnusedFormalParameter)
+ */
+ public function read($scope = null) : array
+ {
+ $map = $this->mapper->getMappedTypes(self::ENTITY_TYPE);
+ $config =[];
+ $attributes = $this->attributesCollection->addSearchableAttributeFilter()->addFilter('used_for_sort_by', 1);
+ /** @var \Magento\Catalog\Model\ResourceModel\Eav\Attribute $attribute */
+ foreach ($attributes as $attribute) {
+ $attributeCode = $attribute->getAttributeCode();
+ $attributeLabel = $attribute->getDefaultFrontendLabel();
+ foreach ($map as $type) {
+ $config[$type]['fields'][$attributeCode] = [
+ 'name' => $attributeCode,
+ 'type' => self::FIELD_TYPE,
+ 'arguments' => [],
+ 'required' => false,
+ 'description' => __('Attribute label: ') . $attributeLabel
+ ];
+ }
+ }
+
+ return $config;
+ }
+}
diff --git a/app/code/Magento/CatalogGraphQl/Model/MediaGalleryTypeResolver.php b/app/code/Magento/CatalogGraphQl/Model/MediaGalleryTypeResolver.php
new file mode 100644
index 0000000000000..7cdeb33e39db8
--- /dev/null
+++ b/app/code/Magento/CatalogGraphQl/Model/MediaGalleryTypeResolver.php
@@ -0,0 +1,33 @@
+filtersDataProvider = $filtersDataProvider;
+ $this->layerBuilder = $layerBuilder;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function resolve(
+ Field $field,
+ $context,
+ ResolveInfo $info,
+ array $value = null,
+ array $args = null
+ ) {
+ if (!isset($value['layer_type']) || !isset($value['search_result'])) {
+ return null;
+ }
+
+ $aggregations = $value['search_result']->getSearchAggregation();
+
+ if ($aggregations) {
+ /** @var StoreInterface $store */
+ $store = $context->getExtensionAttributes()->getStore();
+ $storeId = (int)$store->getId();
+ return $this->layerBuilder->build($aggregations, $storeId);
+ } else {
+ return [];
+ }
+ }
+}
diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/Categories.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/Categories.php
index cb392a7b2295d..535fe3a80cd25 100644
--- a/app/code/Magento/CatalogGraphQl/Model/Resolver/Categories.php
+++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/Categories.php
@@ -7,6 +7,7 @@
namespace Magento\CatalogGraphQl\Model\Resolver;
+use Magento\CatalogGraphQl\Model\Resolver\Product\ProductCategories;
use Magento\Framework\Exception\LocalizedException;
use Magento\Framework\GraphQl\Schema\Type\ResolveInfo;
use Magento\Catalog\Api\Data\CategoryInterface;
@@ -18,6 +19,7 @@
use Magento\Framework\GraphQl\Query\ResolverInterface;
use Magento\Framework\GraphQl\Query\Resolver\ValueFactory;
use Magento\CatalogGraphQl\Model\Category\Hydrator as CategoryHydrator;
+use Magento\Store\Model\StoreManagerInterface;
/**
* Resolver for category objects the product is assigned to.
@@ -56,25 +58,41 @@ class Categories implements ResolverInterface
*/
private $categoryHydrator;
+ /**
+ * @var ProductCategories
+ */
+ private $productCategories;
+
+ /**
+ * @var StoreManagerInterface
+ */
+ private $storeManager;
+
/**
* @param CollectionFactory $collectionFactory
* @param AttributesJoiner $attributesJoiner
* @param CustomAttributesFlattener $customAttributesFlattener
* @param ValueFactory $valueFactory
* @param CategoryHydrator $categoryHydrator
+ * @param ProductCategories $productCategories
+ * @param StoreManagerInterface $storeManager
*/
public function __construct(
CollectionFactory $collectionFactory,
AttributesJoiner $attributesJoiner,
CustomAttributesFlattener $customAttributesFlattener,
ValueFactory $valueFactory,
- CategoryHydrator $categoryHydrator
+ CategoryHydrator $categoryHydrator,
+ ProductCategories $productCategories,
+ StoreManagerInterface $storeManager
) {
$this->collection = $collectionFactory->create();
$this->attributesJoiner = $attributesJoiner;
$this->customAttributesFlattener = $customAttributesFlattener;
$this->valueFactory = $valueFactory;
$this->categoryHydrator = $categoryHydrator;
+ $this->productCategories = $productCategories;
+ $this->storeManager = $storeManager;
}
/**
@@ -90,39 +108,42 @@ public function resolve(Field $field, $context, ResolveInfo $info, array $value
/** @var \Magento\Catalog\Model\Product $product */
$product = $value['model'];
- $categoryIds = $product->getCategoryIds();
+ $storeId = $this->storeManager->getStore()->getId();
+ $categoryIds = $this->productCategories->getCategoryIdsByProduct((int)$product->getId(), (int)$storeId);
$this->categoryIds = array_merge($this->categoryIds, $categoryIds);
$that = $this;
- return $this->valueFactory->create(function () use ($that, $categoryIds, $info) {
- $categories = [];
- if (empty($that->categoryIds)) {
- return [];
- }
+ return $this->valueFactory->create(
+ function () use ($that, $categoryIds, $info) {
+ $categories = [];
+ if (empty($that->categoryIds)) {
+ return [];
+ }
- if (!$this->collection->isLoaded()) {
- $that->attributesJoiner->join($info->fieldNodes[0], $this->collection);
- $this->collection->addIdFilter($this->categoryIds);
- }
- /** @var CategoryInterface | \Magento\Catalog\Model\Category $item */
- foreach ($this->collection as $item) {
- if (in_array($item->getId(), $categoryIds)) {
- // Try to extract all requested fields from the loaded collection data
- $categories[$item->getId()] = $this->categoryHydrator->hydrateCategory($item, true);
- $categories[$item->getId()]['model'] = $item;
- $requestedFields = $that->attributesJoiner->getQueryFields($info->fieldNodes[0]);
- $extractedFields = array_keys($categories[$item->getId()]);
- $foundFields = array_intersect($requestedFields, $extractedFields);
- if (count($requestedFields) === count($foundFields)) {
- continue;
- }
+ if (!$this->collection->isLoaded()) {
+ $that->attributesJoiner->join($info->fieldNodes[0], $this->collection);
+ $this->collection->addIdFilter($this->categoryIds);
+ }
+ /** @var CategoryInterface | \Magento\Catalog\Model\Category $item */
+ foreach ($this->collection as $item) {
+ if (in_array($item->getId(), $categoryIds)) {
+ // Try to extract all requested fields from the loaded collection data
+ $categories[$item->getId()] = $this->categoryHydrator->hydrateCategory($item, true);
+ $categories[$item->getId()]['model'] = $item;
+ $requestedFields = $that->attributesJoiner->getQueryFields($info->fieldNodes[0]);
+ $extractedFields = array_keys($categories[$item->getId()]);
+ $foundFields = array_intersect($requestedFields, $extractedFields);
+ if (count($requestedFields) === count($foundFields)) {
+ continue;
+ }
- // If not all requested fields were extracted from the collection, start more complex extraction
- $categories[$item->getId()] = $this->categoryHydrator->hydrateCategory($item);
+ // If not all requested fields were extracted from the collection, start more complex extraction
+ $categories[$item->getId()] = $this->categoryHydrator->hydrateCategory($item);
+ }
}
- }
- return $categories;
- });
+ return $categories;
+ }
+ );
}
}
diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/Category/CategoriesIdentity.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/Category/CategoriesIdentity.php
index aba2d7b198dbd..dd18c463b98de 100644
--- a/app/code/Magento/CatalogGraphQl/Model/Resolver/Category/CategoriesIdentity.php
+++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/Category/CategoriesIdentity.php
@@ -14,6 +14,9 @@
*/
class CategoriesIdentity implements IdentityInterface
{
+ /** @var string */
+ private $cacheTag = \Magento\Catalog\Model\Category::CACHE_TAG;
+
/**
* Get category IDs from resolved data
*
@@ -25,7 +28,10 @@ public function getIdentities(array $resolvedData): array
$ids = [];
if (!empty($resolvedData)) {
foreach ($resolvedData as $category) {
- $ids[] = $category['id'];
+ $ids[] = sprintf('%s_%s', $this->cacheTag, $category['id']);
+ }
+ if (!empty($ids)) {
+ array_unshift($ids, $this->cacheTag);
}
}
return $ids;
diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/Category/CategoryTreeIdentity.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/Category/CategoryTreeIdentity.php
index e4970f08b3eb7..017a7d280c195 100644
--- a/app/code/Magento/CatalogGraphQl/Model/Resolver/Category/CategoryTreeIdentity.php
+++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/Category/CategoryTreeIdentity.php
@@ -14,6 +14,9 @@
*/
class CategoryTreeIdentity implements IdentityInterface
{
+ /** @var string */
+ private $cacheTag = \Magento\Catalog\Model\Category::CACHE_TAG;
+
/**
* Get category ID from resolved data
*
@@ -22,6 +25,7 @@ class CategoryTreeIdentity implements IdentityInterface
*/
public function getIdentities(array $resolvedData): array
{
- return empty($resolvedData['id']) ? [] : [$resolvedData['id']];
+ return empty($resolvedData['id']) ?
+ [] : [$this->cacheTag, sprintf('%s_%s', $this->cacheTag, $resolvedData['id'])];
}
}
diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/Category/Products.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/Category/Products.php
index e0580213ddea7..abc5ae7e1da7f 100644
--- a/app/code/Magento/CatalogGraphQl/Model/Resolver/Category/Products.php
+++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/Category/Products.php
@@ -8,6 +8,9 @@
namespace Magento\CatalogGraphQl\Model\Resolver\Category;
use Magento\Catalog\Api\ProductRepositoryInterface;
+use Magento\CatalogGraphQl\DataProvider\Product\SearchCriteriaBuilder;
+use Magento\CatalogGraphQl\Model\Resolver\Products\Query\Search;
+use Magento\Framework\App\ObjectManager;
use Magento\Framework\GraphQl\Config\Element\Field;
use Magento\Framework\GraphQl\Query\ResolverInterface;
use Magento\Framework\GraphQl\Schema\Type\ResolveInfo;
@@ -27,27 +30,46 @@ class Products implements ResolverInterface
/**
* @var Builder
+ * @deprecated
*/
private $searchCriteriaBuilder;
/**
* @var Filter
+ * @deprecated
*/
private $filterQuery;
+ /**
+ * @var Search
+ */
+ private $searchQuery;
+
+ /**
+ * @var SearchCriteriaBuilder
+ */
+ private $searchApiCriteriaBuilder;
+
/**
* @param ProductRepositoryInterface $productRepository
* @param Builder $searchCriteriaBuilder
* @param Filter $filterQuery
+ * @param Search $searchQuery
+ * @param SearchCriteriaBuilder $searchApiCriteriaBuilder
*/
public function __construct(
ProductRepositoryInterface $productRepository,
Builder $searchCriteriaBuilder,
- Filter $filterQuery
+ Filter $filterQuery,
+ Search $searchQuery = null,
+ SearchCriteriaBuilder $searchApiCriteriaBuilder = null
) {
$this->productRepository = $productRepository;
$this->searchCriteriaBuilder = $searchCriteriaBuilder;
$this->filterQuery = $filterQuery;
+ $this->searchQuery = $searchQuery ?? ObjectManager::getInstance()->get(Search::class);
+ $this->searchApiCriteriaBuilder = $searchApiCriteriaBuilder ??
+ ObjectManager::getInstance()->get(SearchCriteriaBuilder::class);
}
/**
@@ -60,21 +82,20 @@ public function resolve(
array $value = null,
array $args = null
) {
- $args['filter'] = [
- 'category_id' => [
- 'eq' => $value['id']
- ]
- ];
- $searchCriteria = $this->searchCriteriaBuilder->build($field->getName(), $args);
if ($args['currentPage'] < 1) {
throw new GraphQlInputException(__('currentPage value must be greater than 0.'));
}
if ($args['pageSize'] < 1) {
throw new GraphQlInputException(__('pageSize value must be greater than 0.'));
}
- $searchCriteria->setCurrentPage($args['currentPage']);
- $searchCriteria->setPageSize($args['pageSize']);
- $searchResult = $this->filterQuery->getResult($searchCriteria, $info);
+
+ $args['filter'] = [
+ 'category_id' => [
+ 'eq' => $value['id']
+ ]
+ ];
+ $searchCriteria = $this->searchApiCriteriaBuilder->build($args, false);
+ $searchResult = $this->searchQuery->getResult($searchCriteria, $info);
//possible division by 0
if ($searchCriteria->getPageSize()) {
diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/Category/SortFields.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/Category/SortFields.php
index cb5553bb03701..44ea0222ba59d 100644
--- a/app/code/Magento/CatalogGraphQl/Model/Resolver/Category/SortFields.php
+++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/Category/SortFields.php
@@ -7,6 +7,8 @@
namespace Magento\CatalogGraphQl\Model\Resolver\Category;
+use Magento\Catalog\Model\Category\Attribute\Source\Sortby;
+use Magento\Catalog\Model\Config;
use Magento\Framework\GraphQl\Schema\Type\ResolveInfo;
use Magento\Framework\GraphQl\Config\Element\Field;
use Magento\Framework\GraphQl\Query\ResolverInterface;
@@ -17,32 +19,24 @@
class SortFields implements ResolverInterface
{
/**
- * @var \Magento\Catalog\Model\Config
+ * @var Config
*/
private $catalogConfig;
-
- /**
- * @var \Magento\Store\Model\StoreManagerInterface
- */
- private $storeManager;
-
+
/**
- * @var \Magento\Catalog\Model\Category\Attribute\Source\Sortby
+ * @var Sortby
*/
private $sortbyAttributeSource;
/**
- * @param \Magento\Catalog\Model\Config $catalogConfig
- * @param \Magento\Store\Model\StoreManagerInterface $storeManager
- * @oaram \Magento\Catalog\Model\Category\Attribute\Source\Sortby $sortbyAttributeSource
+ * @param Config $catalogConfig
+ * @param Sortby $sortbyAttributeSource
*/
public function __construct(
- \Magento\Catalog\Model\Config $catalogConfig,
- \Magento\Store\Model\StoreManagerInterface $storeManager,
- \Magento\Catalog\Model\Category\Attribute\Source\Sortby $sortbyAttributeSource
+ Config $catalogConfig,
+ Sortby $sortbyAttributeSource
) {
$this->catalogConfig = $catalogConfig;
- $this->storeManager = $storeManager;
$this->sortbyAttributeSource = $sortbyAttributeSource;
}
@@ -52,6 +46,8 @@ public function __construct(
public function resolve(Field $field, $context, ResolveInfo $info, array $value = null, array $args = null)
{
$sortFieldsOptions = $this->sortbyAttributeSource->getAllOptions();
+ $storeId = (int)$context->getExtensionAttributes()->getStore()->getId();
+
array_walk(
$sortFieldsOptions,
function (&$option) {
@@ -59,10 +55,10 @@ function (&$option) {
}
);
$data = [
- 'default' => $this->catalogConfig->getProductListDefaultSortBy($this->storeManager->getStore()->getId()),
+ 'default' => $this->catalogConfig->getProductListDefaultSortBy($storeId),
'options' => $sortFieldsOptions,
];
-
+
return $data;
}
}
diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/CategoryTree.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/CategoryTree.php
index 89d3805383e1a..4284aed610848 100644
--- a/app/code/Magento/CatalogGraphQl/Model/Resolver/CategoryTree.php
+++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/CategoryTree.php
@@ -10,11 +10,11 @@
use Magento\Catalog\Model\Category;
use Magento\CatalogGraphQl\Model\Resolver\Category\CheckCategoryIsActive;
use Magento\CatalogGraphQl\Model\Resolver\Products\DataProvider\ExtractDataFromCategoryTree;
-use Magento\Framework\GraphQl\Schema\Type\ResolveInfo;
use Magento\Framework\GraphQl\Config\Element\Field;
-use Magento\Framework\GraphQl\Exception\GraphQlInputException;
use Magento\Framework\GraphQl\Exception\GraphQlNoSuchEntityException;
use Magento\Framework\GraphQl\Query\ResolverInterface;
+use Magento\Framework\GraphQl\Schema\Type\ResolveInfo;
+use Magento\CatalogGraphQl\Model\Resolver\Products\DataProvider\CategoryTree as CategoryTreeDataProvider;
/**
* Category tree field resolver, used for GraphQL request processing.
@@ -27,7 +27,7 @@ class CategoryTree implements ResolverInterface
const CATEGORY_INTERFACE = 'CategoryInterface';
/**
- * @var \Magento\CatalogGraphQl\Model\Resolver\Products\DataProvider\CategoryTree
+ * @var CategoryTreeDataProvider
*/
private $categoryTree;
@@ -42,12 +42,12 @@ class CategoryTree implements ResolverInterface
private $checkCategoryIsActive;
/**
- * @param \Magento\CatalogGraphQl\Model\Resolver\Products\DataProvider\CategoryTree $categoryTree
+ * @param CategoryTreeDataProvider $categoryTree
* @param ExtractDataFromCategoryTree $extractDataFromCategoryTree
* @param CheckCategoryIsActive $checkCategoryIsActive
*/
public function __construct(
- \Magento\CatalogGraphQl\Model\Resolver\Products\DataProvider\CategoryTree $categoryTree,
+ CategoryTreeDataProvider $categoryTree,
ExtractDataFromCategoryTree $extractDataFromCategoryTree,
CheckCategoryIsActive $checkCategoryIsActive
) {
@@ -56,22 +56,6 @@ public function __construct(
$this->checkCategoryIsActive = $checkCategoryIsActive;
}
- /**
- * Get category id
- *
- * @param array $args
- * @return int
- * @throws GraphQlInputException
- */
- private function getCategoryId(array $args) : int
- {
- if (!isset($args['id'])) {
- throw new GraphQlInputException(__('"id for category should be specified'));
- }
-
- return (int)$args['id'];
- }
-
/**
* @inheritdoc
*/
@@ -81,7 +65,9 @@ public function resolve(Field $field, $context, ResolveInfo $info, array $value
return $value[$field->getName()];
}
- $rootCategoryId = $this->getCategoryId($args);
+ $rootCategoryId = isset($args['id']) ? (int)$args['id'] :
+ (int)$context->getExtensionAttributes()->getStore()->getRootCategoryId();
+
if ($rootCategoryId !== Category::TREE_ROOT_ID) {
$this->checkCategoryIsActive->execute($rootCategoryId);
}
diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/Product/Identity.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/Product/Identity.php
index 198b1c112dca2..7aec66ccb699f 100644
--- a/app/code/Magento/CatalogGraphQl/Model/Resolver/Product/Identity.php
+++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/Product/Identity.php
@@ -14,6 +14,9 @@
*/
class Identity implements IdentityInterface
{
+ /** @var string */
+ private $cacheTag = \Magento\Catalog\Model\Product::CACHE_TAG;
+
/**
* Get product ids for cache tag
*
@@ -25,7 +28,10 @@ public function getIdentities(array $resolvedData): array
$ids = [];
$items = $resolvedData['items'] ?? [];
foreach ($items as $item) {
- $ids[] = $item['entity_id'];
+ $ids[] = sprintf('%s_%s', $this->cacheTag, $item['entity_id']);
+ }
+ if (!empty($ids)) {
+ array_unshift($ids, $this->cacheTag);
}
return $ids;
diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/Product/MediaGallery.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/Product/MediaGallery.php
new file mode 100644
index 0000000000000..810de0f1f4b57
--- /dev/null
+++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/Product/MediaGallery.php
@@ -0,0 +1,62 @@
+getMediaGalleryEntries() ?? [] as $key => $entry) {
+ $mediaGalleryEntries[$key] = $entry->getData();
+ $mediaGalleryEntries[$key]['model'] = $product;
+ if ($entry->getExtensionAttributes() && $entry->getExtensionAttributes()->getVideoContent()) {
+ $mediaGalleryEntries[$key]['video_content']
+ = $entry->getExtensionAttributes()->getVideoContent()->getData();
+ }
+ }
+ return $mediaGalleryEntries;
+ }
+}
diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/Product/ProductImage/Label.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/Product/MediaGallery/Label.php
similarity index 69%
rename from app/code/Magento/CatalogGraphQl/Model/Resolver/Product/ProductImage/Label.php
rename to app/code/Magento/CatalogGraphQl/Model/Resolver/Product/MediaGallery/Label.php
index f971e35742628..4ec76fe59ca88 100644
--- a/app/code/Magento/CatalogGraphQl/Model/Resolver/Product/ProductImage/Label.php
+++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/Product/MediaGallery/Label.php
@@ -5,7 +5,7 @@
*/
declare(strict_types=1);
-namespace Magento\CatalogGraphQl\Model\Resolver\Product\ProductImage;
+namespace Magento\CatalogGraphQl\Model\Resolver\Product\MediaGallery;
use Magento\Catalog\Model\Product;
use Magento\Catalog\Model\ResourceModel\Product as ProductResourceModel;
@@ -13,10 +13,10 @@
use Magento\Framework\GraphQl\Config\Element\Field;
use Magento\Framework\GraphQl\Query\ResolverInterface;
use Magento\Framework\GraphQl\Schema\Type\ResolveInfo;
-use Magento\Store\Model\StoreManagerInterface;
+use Magento\Store\Api\Data\StoreInterface;
/**
- * Returns product's image label
+ * Return media label
*/
class Label implements ResolverInterface
{
@@ -25,21 +25,13 @@ class Label implements ResolverInterface
*/
private $productResource;
- /**
- * @var StoreManagerInterface
- */
- private $storeManager;
-
/**
* @param ProductResourceModel $productResource
- * @param StoreManagerInterface $storeManager
*/
public function __construct(
- ProductResourceModel $productResource,
- StoreManagerInterface $storeManager
+ ProductResourceModel $productResource
) {
$this->productResource = $productResource;
- $this->storeManager = $storeManager;
}
/**
@@ -52,8 +44,9 @@ public function resolve(
array $value = null,
array $args = null
) {
- if (!isset($value['image_type'])) {
- throw new LocalizedException(__('"image_type" value should be specified'));
+
+ if (isset($value['label'])) {
+ return $value['label'];
}
if (!isset($value['model'])) {
@@ -62,18 +55,17 @@ public function resolve(
/** @var Product $product */
$product = $value['model'];
- $imageType = $value['image_type'];
- $imagePath = $product->getData($imageType);
$productId = (int)$product->getEntityId();
-
- // null if image is not set
- if (null === $imagePath) {
- return $this->getAttributeValue($productId, 'name');
+ /** @var StoreInterface $store */
+ $store = $context->getExtensionAttributes()->getStore();
+ $storeId = (int)$store->getId();
+ if (!isset($value['image_type'])) {
+ return $this->getAttributeValue($productId, 'name', $storeId);
}
-
- $imageLabel = $this->getAttributeValue($productId, $imageType . '_label');
- if (null === $imageLabel) {
- $imageLabel = $this->getAttributeValue($productId, 'name');
+ $imageType = $value['image_type'];
+ $imageLabel = $this->getAttributeValue($productId, $imageType . '_label', $storeId);
+ if ($imageLabel == null) {
+ $imageLabel = $this->getAttributeValue($productId, 'name', $storeId);
}
return $imageLabel;
@@ -84,12 +76,11 @@ public function resolve(
*
* @param int $productId
* @param string $attributeCode
+ * @param int $storeId
* @return null|string Null if attribute value is not exists
*/
- private function getAttributeValue(int $productId, string $attributeCode): ?string
+ private function getAttributeValue(int $productId, string $attributeCode, int $storeId): ?string
{
- $storeId = $this->storeManager->getStore()->getId();
-
$value = $this->productResource->getAttributeRawValue($productId, $attributeCode, $storeId);
return is_array($value) && empty($value) ? null : $value;
}
diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/Product/ProductImage/Url.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/Product/MediaGallery/Url.php
similarity index 77%
rename from app/code/Magento/CatalogGraphQl/Model/Resolver/Product/ProductImage/Url.php
rename to app/code/Magento/CatalogGraphQl/Model/Resolver/Product/MediaGallery/Url.php
index 23a8c2d15c09e..eaab159cddae6 100644
--- a/app/code/Magento/CatalogGraphQl/Model/Resolver/Product/ProductImage/Url.php
+++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/Product/MediaGallery/Url.php
@@ -5,7 +5,7 @@
*/
declare(strict_types=1);
-namespace Magento\CatalogGraphQl\Model\Resolver\Product\ProductImage;
+namespace Magento\CatalogGraphQl\Model\Resolver\Product\MediaGallery;
use Magento\Catalog\Model\Product;
use Magento\Catalog\Model\Product\ImageFactory;
@@ -16,7 +16,7 @@
use Magento\Framework\GraphQl\Schema\Type\ResolveInfo;
/**
- * Returns product's image url
+ * Returns media url
*/
class Url implements ResolverInterface
{
@@ -51,7 +51,7 @@ public function resolve(
array $value = null,
array $args = null
) {
- if (!isset($value['image_type'])) {
+ if (!isset($value['image_type']) && !isset($value['file'])) {
throw new LocalizedException(__('"image_type" value should be specified'));
}
@@ -61,9 +61,17 @@ public function resolve(
/** @var Product $product */
$product = $value['model'];
- $imagePath = $product->getData($value['image_type']);
-
- return $this->getImageUrl($value['image_type'], $imagePath);
+ if (isset($value['image_type'])) {
+ $imagePath = $product->getData($value['image_type']);
+ return $this->getImageUrl($value['image_type'], $imagePath);
+ }
+ if (isset($value['file'])) {
+ $image = $this->productImageFactory->create();
+ $image->setDestinationSubdir('image')->setBaseFile($value['file']);
+ $imageUrl = $image->getUrl();
+ return $imageUrl;
+ }
+ return [];
}
/**
diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/Product/MediaGalleryEntries.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/Product/MediaGalleryEntries.php
index c8f167da583d3..e1338930afe5d 100644
--- a/app/code/Magento/CatalogGraphQl/Model/Resolver/Product/MediaGalleryEntries.php
+++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/Product/MediaGalleryEntries.php
@@ -18,6 +18,7 @@
* @inheritdoc
*
* Format a product's media gallery information to conform to GraphQL schema representation
+ * @deprecated
*/
class MediaGalleryEntries implements ResolverInterface
{
diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/Product/Price.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/Product/Price.php
index 55d930101fb60..c542fc26495f7 100644
--- a/app/code/Magento/CatalogGraphQl/Model/Resolver/Product/Price.php
+++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/Product/Price.php
@@ -7,43 +7,35 @@
namespace Magento\CatalogGraphQl\Model\Resolver\Product;
-use Magento\Framework\Exception\LocalizedException;
-use Magento\Framework\GraphQl\Query\Resolver\ContextInterface;
-use Magento\Framework\GraphQl\Schema\Type\ResolveInfo;
use Magento\Catalog\Model\Product;
use Magento\Catalog\Pricing\Price\FinalPrice;
use Magento\Catalog\Pricing\Price\RegularPrice;
+use Magento\Framework\Exception\LocalizedException;
use Magento\Framework\GraphQl\Config\Element\Field;
+use Magento\Framework\GraphQl\Query\Resolver\ContextInterface;
use Magento\Framework\GraphQl\Query\ResolverInterface;
+use Magento\Framework\GraphQl\Schema\Type\ResolveInfo;
use Magento\Framework\Pricing\Adjustment\AdjustmentInterface;
use Magento\Framework\Pricing\Amount\AmountInterface;
use Magento\Framework\Pricing\PriceInfo\Factory as PriceInfoFactory;
-use Magento\Store\Model\StoreManagerInterface;
+use Magento\Store\Api\Data\StoreInterface;
/**
* Format a product's price information to conform to GraphQL schema representation
*/
class Price implements ResolverInterface
{
- /**
- * @var StoreManagerInterface
- */
- private $storeManager;
-
/**
* @var PriceInfoFactory
*/
private $priceInfoFactory;
/**
- * @param StoreManagerInterface $storeManager
* @param PriceInfoFactory $priceInfoFactory
*/
public function __construct(
- StoreManagerInterface $storeManager,
PriceInfoFactory $priceInfoFactory
) {
- $this->storeManager = $storeManager;
$this->priceInfoFactory = $priceInfoFactory;
}
@@ -80,11 +72,20 @@ public function resolve(
$minimalPriceAmount = $finalPrice->getMinimalPrice();
$maximalPriceAmount = $finalPrice->getMaximalPrice();
$regularPriceAmount = $priceInfo->getPrice(RegularPrice::PRICE_CODE)->getAmount();
+ $store = $context->getExtensionAttributes()->getStore();
$prices = [
- 'minimalPrice' => $this->createAdjustmentsArray($priceInfo->getAdjustments(), $minimalPriceAmount),
- 'regularPrice' => $this->createAdjustmentsArray($priceInfo->getAdjustments(), $regularPriceAmount),
- 'maximalPrice' => $this->createAdjustmentsArray($priceInfo->getAdjustments(), $maximalPriceAmount)
+ 'minimalPrice' => $this->createAdjustmentsArray(
+ $priceInfo->getAdjustments(),
+ $minimalPriceAmount,
+ $store
+ ),
+ 'regularPrice' => $this->createAdjustmentsArray(
+ $priceInfo->getAdjustments(),
+ $regularPriceAmount,
+ $store
+ ),
+ 'maximalPrice' => $this->createAdjustmentsArray($priceInfo->getAdjustments(), $maximalPriceAmount, $store)
];
return $prices;
@@ -95,13 +96,11 @@ public function resolve(
*
* @param AdjustmentInterface[] $adjustments
* @param AmountInterface $amount
+ * @param StoreInterface $store
* @return array
*/
- private function createAdjustmentsArray(array $adjustments, AmountInterface $amount) : array
+ private function createAdjustmentsArray(array $adjustments, AmountInterface $amount, StoreInterface $store) : array
{
- /** @var \Magento\Store\Model\Store $store */
- $store = $this->storeManager->getStore();
-
$priceArray = [
'amount' => [
'value' => $amount->getValue(),
diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/Product/ProductCategories.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/Product/ProductCategories.php
new file mode 100644
index 0000000000000..86984a29149ef
--- /dev/null
+++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/Product/ProductCategories.php
@@ -0,0 +1,97 @@
+tableResolver = $tableResolver;
+ $this->resourceConnection = $resourceConnection;
+ $this->dimensionFactory = $dimensionFactory;
+ }
+
+ /**
+ * Get category ids for product
+ *
+ * @param int $productId
+ * @param int $storeId
+ * @return array
+ */
+ public function getCategoryIdsByProduct(int $productId, int $storeId)
+ {
+ $connection = $this->resourceConnection->getConnection();
+ $categoryProductTable = $this->getCatalogCategoryProductTableName($storeId);
+ $storeTable = $this->resourceConnection->getTableName(Store::ENTITY);
+ $storeGroupTable = $this->resourceConnection->getTableName(Group::ENTITY);
+
+ $select = $connection->select()
+ ->from(['cat_index' => $categoryProductTable], ['category_id'])
+ ->joinInner(['store' => $storeTable], $connection->quoteInto('store.store_id = ?', $storeId), [])
+ ->joinInner(
+ ['store_group' => $storeGroupTable],
+ 'store.group_id = store_group.group_id AND cat_index.category_id != store_group.root_category_id',
+ []
+ )
+ ->where('product_id = ?', $productId);
+
+ $categoryIds = $connection->fetchCol($select);
+
+ return $categoryIds;
+ }
+
+ /**
+ * Get catalog_category_product table name
+ *
+ * @param int $storeId
+ * @return string
+ */
+ private function getCatalogCategoryProductTableName(int $storeId)
+ {
+ $dimension = $this->dimensionFactory->create(Store::ENTITY, (string)$storeId);
+ $tableName = $this->tableResolver->resolve(
+ AbstractAction::MAIN_INDEX_TABLE,
+ [$dimension]
+ );
+
+ return $tableName;
+ }
+}
diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/Products.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products.php
index a75a9d2cf50a0..691f93e4148bc 100644
--- a/app/code/Magento/CatalogGraphQl/Model/Resolver/Products.php
+++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products.php
@@ -16,6 +16,7 @@
use Magento\Framework\GraphQl\Query\Resolver\Argument\SearchCriteria\SearchFilter;
use Magento\Framework\GraphQl\Query\ResolverInterface;
use Magento\Catalog\Model\Layer\Resolver;
+use Magento\CatalogGraphQl\DataProvider\Product\SearchCriteriaBuilder;
/**
* Products field resolver, used for GraphQL request processing.
@@ -24,6 +25,7 @@ class Products implements ResolverInterface
{
/**
* @var Builder
+ * @deprecated
*/
private $searchCriteriaBuilder;
@@ -34,30 +36,41 @@ class Products implements ResolverInterface
/**
* @var Filter
+ * @deprecated
*/
private $filterQuery;
/**
* @var SearchFilter
+ * @deprecated
*/
private $searchFilter;
+ /**
+ * @var SearchCriteriaBuilder
+ */
+ private $searchApiCriteriaBuilder;
+
/**
* @param Builder $searchCriteriaBuilder
* @param Search $searchQuery
* @param Filter $filterQuery
* @param SearchFilter $searchFilter
+ * @param SearchCriteriaBuilder|null $searchApiCriteriaBuilder
*/
public function __construct(
Builder $searchCriteriaBuilder,
Search $searchQuery,
Filter $filterQuery,
- SearchFilter $searchFilter
+ SearchFilter $searchFilter,
+ SearchCriteriaBuilder $searchApiCriteriaBuilder = null
) {
$this->searchCriteriaBuilder = $searchCriteriaBuilder;
$this->searchQuery = $searchQuery;
$this->filterQuery = $filterQuery;
$this->searchFilter = $searchFilter;
+ $this->searchApiCriteriaBuilder = $searchApiCriteriaBuilder ??
+ \Magento\Framework\App\ObjectManager::getInstance()->get(SearchCriteriaBuilder::class);
}
/**
@@ -70,40 +83,29 @@ public function resolve(
array $value = null,
array $args = null
) {
- $searchCriteria = $this->searchCriteriaBuilder->build($field->getName(), $args);
if ($args['currentPage'] < 1) {
throw new GraphQlInputException(__('currentPage value must be greater than 0.'));
}
if ($args['pageSize'] < 1) {
throw new GraphQlInputException(__('pageSize value must be greater than 0.'));
}
- $searchCriteria->setCurrentPage($args['currentPage']);
- $searchCriteria->setPageSize($args['pageSize']);
if (!isset($args['search']) && !isset($args['filter'])) {
throw new GraphQlInputException(
__("'search' or 'filter' input argument is required.")
);
- } elseif (isset($args['search'])) {
- $layerType = Resolver::CATALOG_LAYER_SEARCH;
- $this->searchFilter->add($args['search'], $searchCriteria);
- $searchResult = $this->searchQuery->getResult($searchCriteria, $info);
- } else {
- $layerType = Resolver::CATALOG_LAYER_CATEGORY;
- $searchResult = $this->filterQuery->getResult($searchCriteria, $info);
- }
- //possible division by 0
- if ($searchCriteria->getPageSize()) {
- $maxPages = ceil($searchResult->getTotalCount() / $searchCriteria->getPageSize());
- } else {
- $maxPages = 0;
}
- $currentPage = $searchCriteria->getCurrentPage();
- if ($searchCriteria->getCurrentPage() > $maxPages && $searchResult->getTotalCount() > 0) {
+ //get product children fields queried
+ $productFields = (array)$info->getFieldSelection(1);
+ $includeAggregations = isset($productFields['filters']) || isset($productFields['aggregations']);
+ $searchCriteria = $this->searchApiCriteriaBuilder->build($args, $includeAggregations);
+ $searchResult = $this->searchQuery->getResult($searchCriteria, $info, $args);
+
+ if ($searchResult->getCurrentPage() > $searchResult->getTotalPages() && $searchResult->getTotalCount() > 0) {
throw new GraphQlInputException(
__(
'currentPage value %1 specified is greater than the %2 page(s) available.',
- [$currentPage, $maxPages]
+ [$searchResult->getCurrentPage(), $searchResult->getTotalPages()]
)
);
}
@@ -112,11 +114,12 @@ public function resolve(
'total_count' => $searchResult->getTotalCount(),
'items' => $searchResult->getProductsSearchResult(),
'page_info' => [
- 'page_size' => $searchCriteria->getPageSize(),
- 'current_page' => $currentPage,
- 'total_pages' => $maxPages
+ 'page_size' => $searchResult->getPageSize(),
+ 'current_page' => $searchResult->getCurrentPage(),
+ 'total_pages' => $searchResult->getTotalPages()
],
- 'layer_type' => $layerType
+ 'search_result' => $searchResult,
+ 'layer_type' => isset($args['search']) ? Resolver::CATALOG_LAYER_SEARCH : Resolver::CATALOG_LAYER_CATEGORY,
];
return $data;
diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/DataProvider/Product.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/DataProvider/Product.php
index 7f1fd71942253..2076ec6726988 100644
--- a/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/DataProvider/Product.php
+++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/DataProvider/Product.php
@@ -8,6 +8,7 @@
namespace Magento\CatalogGraphQl\Model\Resolver\Products\DataProvider;
use Magento\Catalog\Model\Product\Visibility;
+use Magento\CatalogGraphQl\Model\Resolver\Products\DataProvider\Product\CollectionPostProcessor;
use Magento\Framework\Api\SearchCriteriaInterface;
use Magento\Catalog\Model\ResourceModel\Product\CollectionFactory;
use Magento\Catalog\Api\Data\ProductSearchResultsInterfaceFactory;
@@ -32,7 +33,12 @@ class Product
/**
* @var CollectionProcessorInterface
*/
- private $collectionProcessor;
+ private $collectionPreProcessor;
+
+ /**
+ * @var CollectionPostProcessor
+ */
+ private $collectionPostProcessor;
/**
* @var Visibility
@@ -44,17 +50,20 @@ class Product
* @param ProductSearchResultsInterfaceFactory $searchResultsFactory
* @param Visibility $visibility
* @param CollectionProcessorInterface $collectionProcessor
+ * @param CollectionPostProcessor $collectionPostProcessor
*/
public function __construct(
CollectionFactory $collectionFactory,
ProductSearchResultsInterfaceFactory $searchResultsFactory,
Visibility $visibility,
- CollectionProcessorInterface $collectionProcessor
+ CollectionProcessorInterface $collectionProcessor,
+ CollectionPostProcessor $collectionPostProcessor
) {
$this->collectionFactory = $collectionFactory;
$this->searchResultsFactory = $searchResultsFactory;
$this->visibility = $visibility;
- $this->collectionProcessor = $collectionProcessor;
+ $this->collectionPreProcessor = $collectionProcessor;
+ $this->collectionPostProcessor = $collectionPostProcessor;
}
/**
@@ -75,7 +84,7 @@ public function getList(
/** @var \Magento\Catalog\Model\ResourceModel\Product\Collection $collection */
$collection = $this->collectionFactory->create();
- $this->collectionProcessor->process($collection, $searchCriteria, $attributes);
+ $this->collectionPreProcessor->process($collection, $searchCriteria, $attributes);
if (!$isChildSearch) {
$visibilityIds = $isSearch
@@ -83,15 +92,9 @@ public function getList(
: $this->visibility->getVisibleInCatalogIds();
$collection->setVisibility($visibilityIds);
}
- $collection->load();
- // Methods that perform extra fetches post-load
- if (in_array('media_gallery_entries', $attributes)) {
- $collection->addMediaGalleryData();
- }
- if (in_array('options', $attributes)) {
- $collection->addOptionsToResult();
- }
+ $collection->load();
+ $this->collectionPostProcessor->process($collection, $attributes);
$searchResult = $this->searchResultsFactory->create();
$searchResult->setSearchCriteria($searchCriteria);
diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/DataProvider/Product/CollectionPostProcessor.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/DataProvider/Product/CollectionPostProcessor.php
new file mode 100644
index 0000000000000..fadf22e7643af
--- /dev/null
+++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/DataProvider/Product/CollectionPostProcessor.php
@@ -0,0 +1,42 @@
+isLoaded()) {
+ $collection->load();
+ }
+ // Methods that perform extra fetches post-load
+ if (in_array('media_gallery_entries', $attributeNames)) {
+ $collection->addMediaGalleryData();
+ }
+ if (in_array('media_gallery', $attributeNames)) {
+ $collection->addMediaGalleryData();
+ }
+ if (in_array('options', $attributeNames)) {
+ $collection->addOptionsToResult();
+ }
+
+ return $collection;
+ }
+}
diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/DataProvider/Product/CollectionProcessor/MediaGalleryProcessor.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/DataProvider/Product/CollectionProcessor/MediaGalleryProcessor.php
new file mode 100644
index 0000000000000..be300e11f12ec
--- /dev/null
+++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/DataProvider/Product/CollectionProcessor/MediaGalleryProcessor.php
@@ -0,0 +1,56 @@
+mediaConfig = $mediaConfig;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function process(
+ Collection $collection,
+ SearchCriteriaInterface $searchCriteria,
+ array $attributeNames
+ ): Collection {
+ if (in_array('media_gallery_entries', $attributeNames)) {
+ $mediaAttributes = $this->mediaConfig->getMediaAttributeCodes();
+ foreach ($mediaAttributes as $mediaAttribute) {
+ if (!in_array($mediaAttribute, $attributeNames)) {
+ $collection->addAttributeToSelect($mediaAttribute);
+ }
+ }
+ }
+
+ return $collection;
+ }
+}
diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/DataProvider/ProductSearch.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/DataProvider/ProductSearch.php
new file mode 100644
index 0000000000000..ff845f4796763
--- /dev/null
+++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/DataProvider/ProductSearch.php
@@ -0,0 +1,144 @@
+collectionFactory = $collectionFactory;
+ $this->searchResultsFactory = $searchResultsFactory;
+ $this->collectionPreProcessor = $collectionPreProcessor;
+ $this->collectionPostProcessor = $collectionPostProcessor;
+ $this->searchResultApplierFactory = $searchResultsApplierFactory;
+ }
+
+ /**
+ * Get list of product data with full data set. Adds eav attributes to result set from passed in array
+ *
+ * @param SearchCriteriaInterface $searchCriteria
+ * @param SearchResultInterface $searchResult
+ * @param array $attributes
+ * @return SearchResultsInterface
+ */
+ public function getList(
+ SearchCriteriaInterface $searchCriteria,
+ SearchResultInterface $searchResult,
+ array $attributes = []
+ ): SearchResultsInterface {
+ /** @var Collection $collection */
+ $collection = $this->collectionFactory->create();
+
+ //Join search results
+ $this->getSearchResultsApplier($searchResult, $collection, $this->getSortOrderArray($searchCriteria))->apply();
+
+ $this->collectionPreProcessor->process($collection, $searchCriteria, $attributes);
+ $collection->load();
+ $this->collectionPostProcessor->process($collection, $attributes);
+
+ $searchResults = $this->searchResultsFactory->create();
+ $searchResults->setSearchCriteria($searchCriteria);
+ $searchResults->setItems($collection->getItems());
+ $searchResults->setTotalCount($searchResult->getTotalCount());
+ return $searchResults;
+ }
+
+ /**
+ * Create searchResultApplier
+ *
+ * @param SearchResultInterface $searchResult
+ * @param Collection $collection
+ * @param array $orders
+ * @return SearchResultApplierInterface
+ */
+ private function getSearchResultsApplier(
+ SearchResultInterface $searchResult,
+ Collection $collection,
+ array $orders
+ ): SearchResultApplierInterface {
+ return $this->searchResultApplierFactory->create(
+ [
+ 'collection' => $collection,
+ 'searchResult' => $searchResult,
+ 'orders' => $orders
+ ]
+ );
+ }
+
+ /**
+ * Format sort orders into associative array
+ *
+ * E.g. ['field1' => 'DESC', 'field2' => 'ASC", ...]
+ *
+ * @param SearchCriteriaInterface $searchCriteria
+ * @return array
+ */
+ private function getSortOrderArray(SearchCriteriaInterface $searchCriteria)
+ {
+ $ordersArray = [];
+ $sortOrders = $searchCriteria->getSortOrders();
+ if (is_array($sortOrders)) {
+ foreach ($sortOrders as $sortOrder) {
+ $ordersArray[$sortOrder->getField()] = $sortOrder->getDirection();
+ }
+ }
+
+ return $ordersArray;
+ }
+}
diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/FilterArgument/ProductEntityAttributesForAst.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/FilterArgument/ProductEntityAttributesForAst.php
index a547f63b217fe..973b8fbcd6b0f 100644
--- a/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/FilterArgument/ProductEntityAttributesForAst.php
+++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/FilterArgument/ProductEntityAttributesForAst.php
@@ -23,13 +23,15 @@ class ProductEntityAttributesForAst implements FieldEntityAttributesInterface
private $config;
/**
+ * Additional attributes that are not retrieved by getting fields from ProductInterface
+ *
* @var array
*/
private $additionalAttributes = ['min_price', 'max_price', 'category_id'];
/**
* @param ConfigInterface $config
- * @param array $additionalAttributes
+ * @param string[] $additionalAttributes
*/
public function __construct(
ConfigInterface $config,
@@ -40,7 +42,12 @@ public function __construct(
}
/**
- * {@inheritdoc}
+ * @inheritdoc
+ *
+ * Gather all the product entity attributes that can be filtered by search criteria.
+ * Example format ['attributeNameInGraphQl' => ['type' => 'String'. 'fieldName' => 'attributeNameInSearchCriteria']]
+ *
+ * @return array
*/
public function getEntityAttributes() : array
{
@@ -55,14 +62,20 @@ public function getEntityAttributes() : array
$configElement = $this->config->getConfigElement($interface['interface']);
foreach ($configElement->getFields() as $field) {
- $fields[$field->getName()] = 'String';
+ $fields[$field->getName()] = [
+ 'type' => 'String',
+ 'fieldName' => $field->getName(),
+ ];
}
}
- foreach ($this->additionalAttributes as $attribute) {
- $fields[$attribute] = 'String';
+ foreach ($this->additionalAttributes as $attributeName) {
+ $fields[$attributeName] = [
+ 'type' => 'String',
+ 'fieldName' => $attributeName,
+ ];
}
- return array_keys($fields);
+ return $fields;
}
}
diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/Query/FieldSelection.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/Query/FieldSelection.php
new file mode 100644
index 0000000000000..3912bab05ebbe
--- /dev/null
+++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/Query/FieldSelection.php
@@ -0,0 +1,93 @@
+fieldTranslator = $fieldTranslator;
+ }
+
+ /**
+ * Get requested fields from products query
+ *
+ * @param ResolveInfo $resolveInfo
+ * @return string[]
+ */
+ public function getProductsFieldSelection(ResolveInfo $resolveInfo): array
+ {
+ return $this->getProductFields($resolveInfo);
+ }
+
+ /**
+ * Return field names for all requested product fields.
+ *
+ * @param ResolveInfo $info
+ * @return string[]
+ */
+ private function getProductFields(ResolveInfo $info): array
+ {
+ $fieldNames = [];
+ foreach ($info->fieldNodes as $node) {
+ if ($node->name->value !== 'products') {
+ continue;
+ }
+ foreach ($node->selectionSet->selections as $selection) {
+ if ($selection->name->value !== 'items') {
+ continue;
+ }
+ $fieldNames[] = $this->collectProductFieldNames($selection, $fieldNames);
+ }
+ }
+
+ $fieldNames = array_merge(...$fieldNames);
+
+ return $fieldNames;
+ }
+
+ /**
+ * Collect field names for each node in selection
+ *
+ * @param SelectionNode $selection
+ * @param array $fieldNames
+ * @return array
+ */
+ private function collectProductFieldNames(SelectionNode $selection, array $fieldNames = []): array
+ {
+ foreach ($selection->selectionSet->selections as $itemSelection) {
+ if ($itemSelection->kind === 'InlineFragment') {
+ foreach ($itemSelection->selectionSet->selections as $inlineSelection) {
+ if ($inlineSelection->kind === 'InlineFragment') {
+ continue;
+ }
+ $fieldNames[] = $this->fieldTranslator->translate($inlineSelection->name->value);
+ }
+ continue;
+ }
+ $fieldNames[] = $this->fieldTranslator->translate($itemSelection->name->value);
+ }
+
+ return $fieldNames;
+ }
+}
diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/Query/Filter.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/Query/Filter.php
index 62e2f0c488c6c..cc25af44fdfbe 100644
--- a/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/Query/Filter.php
+++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/Query/Filter.php
@@ -12,7 +12,6 @@
use Magento\CatalogGraphQl\Model\Resolver\Products\DataProvider\Product;
use Magento\CatalogGraphQl\Model\Resolver\Products\SearchResult;
use Magento\CatalogGraphQl\Model\Resolver\Products\SearchResultFactory;
-use Magento\Framework\GraphQl\Query\FieldTranslator;
/**
* Retrieve filtered product data based off given search criteria in a format that GraphQL can interpret.
@@ -30,31 +29,31 @@ class Filter
private $productDataProvider;
/**
- * @var FieldTranslator
+ * @var \Magento\Catalog\Model\Layer\Resolver
*/
- private $fieldTranslator;
+ private $layerResolver;
/**
- * @var \Magento\Catalog\Model\Layer\Resolver
+ * FieldSelection
*/
- private $layerResolver;
+ private $fieldSelection;
/**
* @param SearchResultFactory $searchResultFactory
* @param Product $productDataProvider
* @param \Magento\Catalog\Model\Layer\Resolver $layerResolver
- * @param FieldTranslator $fieldTranslator
+ * @param FieldSelection $fieldSelection
*/
public function __construct(
SearchResultFactory $searchResultFactory,
Product $productDataProvider,
\Magento\Catalog\Model\Layer\Resolver $layerResolver,
- FieldTranslator $fieldTranslator
+ FieldSelection $fieldSelection
) {
$this->searchResultFactory = $searchResultFactory;
$this->productDataProvider = $productDataProvider;
- $this->fieldTranslator = $fieldTranslator;
$this->layerResolver = $layerResolver;
+ $this->fieldSelection = $fieldSelection;
}
/**
@@ -70,7 +69,7 @@ public function getResult(
ResolveInfo $info,
bool $isSearch = false
): SearchResult {
- $fields = $this->getProductFields($info);
+ $fields = $this->fieldSelection->getProductsFieldSelection($info);
$products = $this->productDataProvider->getList($searchCriteria, $fields, $isSearch);
$productArray = [];
/** @var \Magento\Catalog\Model\Product $product */
@@ -79,42 +78,11 @@ public function getResult(
$productArray[$product->getId()]['model'] = $product;
}
- return $this->searchResultFactory->create($products->getTotalCount(), $productArray);
- }
-
- /**
- * Return field names for all requested product fields.
- *
- * @param ResolveInfo $info
- * @return string[]
- */
- private function getProductFields(ResolveInfo $info) : array
- {
- $fieldNames = [];
- foreach ($info->fieldNodes as $node) {
- if ($node->name->value !== 'products') {
- continue;
- }
- foreach ($node->selectionSet->selections as $selection) {
- if ($selection->name->value !== 'items') {
- continue;
- }
-
- foreach ($selection->selectionSet->selections as $itemSelection) {
- if ($itemSelection->kind === 'InlineFragment') {
- foreach ($itemSelection->selectionSet->selections as $inlineSelection) {
- if ($inlineSelection->kind === 'InlineFragment') {
- continue;
- }
- $fieldNames[] = $this->fieldTranslator->translate($inlineSelection->name->value);
- }
- continue;
- }
- $fieldNames[] = $this->fieldTranslator->translate($itemSelection->name->value);
- }
- }
- }
-
- return $fieldNames;
+ return $this->searchResultFactory->create(
+ [
+ 'totalCount' => $products->getTotalCount(),
+ 'productsSearchResult' => $productArray
+ ]
+ );
}
}
diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/Query/Search.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/Query/Search.php
index bc40c664425ff..ef83cc6132ecc 100644
--- a/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/Query/Search.php
+++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/Query/Search.php
@@ -7,12 +7,13 @@
namespace Magento\CatalogGraphQl\Model\Resolver\Products\Query;
+use Magento\CatalogGraphQl\Model\Resolver\Products\DataProvider\ProductSearch;
use Magento\Framework\GraphQl\Schema\Type\ResolveInfo;
use Magento\Framework\Api\Search\SearchCriteriaInterface;
-use Magento\CatalogGraphQl\Model\Resolver\Products\SearchCriteria\Helper\Filter as FilterHelper;
use Magento\CatalogGraphQl\Model\Resolver\Products\SearchResult;
use Magento\CatalogGraphQl\Model\Resolver\Products\SearchResultFactory;
use Magento\Search\Api\SearchInterface;
+use Magento\Framework\Api\Search\SearchCriteriaInterfaceFactory;
/**
* Full text search for catalog using given search criteria.
@@ -25,52 +26,52 @@ class Search
private $search;
/**
- * @var FilterHelper
+ * @var SearchResultFactory
*/
- private $filterHelper;
+ private $searchResultFactory;
/**
- * @var Filter
+ * @var \Magento\Search\Model\Search\PageSizeProvider
*/
- private $filterQuery;
+ private $pageSizeProvider;
/**
- * @var SearchResultFactory
+ * @var SearchCriteriaInterfaceFactory
*/
- private $searchResultFactory;
+ private $searchCriteriaFactory;
/**
- * @var \Magento\Framework\EntityManager\MetadataPool
+ * @var FieldSelection
*/
- private $metadataPool;
+ private $fieldSelection;
/**
- * @var \Magento\Search\Model\Search\PageSizeProvider
+ * @var ProductSearch
*/
- private $pageSizeProvider;
+ private $productsProvider;
/**
* @param SearchInterface $search
- * @param FilterHelper $filterHelper
- * @param Filter $filterQuery
* @param SearchResultFactory $searchResultFactory
- * @param \Magento\Framework\EntityManager\MetadataPool $metadataPool
* @param \Magento\Search\Model\Search\PageSizeProvider $pageSize
+ * @param SearchCriteriaInterfaceFactory $searchCriteriaFactory
+ * @param FieldSelection $fieldSelection
+ * @param ProductSearch $productsProvider
*/
public function __construct(
SearchInterface $search,
- FilterHelper $filterHelper,
- Filter $filterQuery,
SearchResultFactory $searchResultFactory,
- \Magento\Framework\EntityManager\MetadataPool $metadataPool,
- \Magento\Search\Model\Search\PageSizeProvider $pageSize
+ \Magento\Search\Model\Search\PageSizeProvider $pageSize,
+ SearchCriteriaInterfaceFactory $searchCriteriaFactory,
+ FieldSelection $fieldSelection,
+ ProductSearch $productsProvider
) {
$this->search = $search;
- $this->filterHelper = $filterHelper;
- $this->filterQuery = $filterQuery;
$this->searchResultFactory = $searchResultFactory;
- $this->metadataPool = $metadataPool;
$this->pageSizeProvider = $pageSize;
+ $this->searchCriteriaFactory = $searchCriteriaFactory;
+ $this->fieldSelection = $fieldSelection;
+ $this->productsProvider = $productsProvider;
}
/**
@@ -81,11 +82,12 @@ public function __construct(
* @return SearchResult
* @throws \Exception
*/
- public function getResult(SearchCriteriaInterface $searchCriteria, ResolveInfo $info) : SearchResult
- {
- $idField = $this->metadataPool->getMetadata(
- \Magento\Catalog\Api\Data\ProductInterface::class
- )->getIdentifierField();
+ public function getResult(
+ SearchCriteriaInterface $searchCriteria,
+ ResolveInfo $info
+ ): SearchResult {
+ $queryFields = $this->fieldSelection->getProductsFieldSelection($info);
+
$realPageSize = $searchCriteria->getPageSize();
$realCurrentPage = $searchCriteria->getCurrentPage();
// Current page must be set to 0 and page size to max for search to grab all ID's as temporary workaround
@@ -94,64 +96,39 @@ public function getResult(SearchCriteriaInterface $searchCriteria, ResolveInfo $
$searchCriteria->setCurrentPage(0);
$itemsResults = $this->search->search($searchCriteria);
- $ids = [];
- $searchIds = [];
- foreach ($itemsResults->getItems() as $item) {
- $ids[$item->getId()] = null;
- $searchIds[] = $item->getId();
- }
-
- $filter = $this->filterHelper->generate($idField, 'in', $searchIds);
- $searchCriteria = $this->filterHelper->remove($searchCriteria, 'search_term');
- $searchCriteria = $this->filterHelper->add($searchCriteria, $filter);
- $searchResult = $this->filterQuery->getResult($searchCriteria, $info, true);
-
- $searchCriteria->setPageSize($realPageSize);
- $searchCriteria->setCurrentPage($realCurrentPage);
- $paginatedProducts = $this->paginateList($searchResult, $searchCriteria);
-
- $products = [];
- if (!isset($searchCriteria->getSortOrders()[0])) {
- foreach ($paginatedProducts as $product) {
- if (in_array($product[$idField], $searchIds)) {
- $ids[$product[$idField]] = $product;
- }
- }
- $products = array_filter($ids);
- } else {
- foreach ($paginatedProducts as $product) {
- $productId = isset($product['entity_id']) ? $product['entity_id'] : $product[$idField];
- if (in_array($productId, $searchIds)) {
- $products[] = $product;
- }
- }
- }
+ //Create copy of search criteria without conditions (conditions will be applied by joining search result)
+ $searchCriteriaCopy = $this->searchCriteriaFactory->create()
+ ->setSortOrders($searchCriteria->getSortOrders())
+ ->setPageSize($realPageSize)
+ ->setCurrentPage($realCurrentPage);
- return $this->searchResultFactory->create($searchResult->getTotalCount(), $products);
- }
+ $searchResults = $this->productsProvider->getList($searchCriteriaCopy, $itemsResults, $queryFields);
- /**
- * Paginate an array of Ids that get pulled back in search based off search criteria and total count.
- *
- * @param SearchResult $searchResult
- * @param SearchCriteriaInterface $searchCriteria
- * @return int[]
- */
- private function paginateList(SearchResult $searchResult, SearchCriteriaInterface $searchCriteria) : array
- {
- $length = $searchCriteria->getPageSize();
- // Search starts pages from 0
- $offset = $length * ($searchCriteria->getCurrentPage() - 1);
-
- if ($searchCriteria->getPageSize()) {
- $maxPages = ceil($searchResult->getTotalCount() / $searchCriteria->getPageSize());
+ //possible division by 0
+ if ($realPageSize) {
+ $maxPages = (int)ceil($searchResults->getTotalCount() / $realPageSize);
} else {
$maxPages = 0;
}
+ $searchCriteria->setPageSize($realPageSize);
+ $searchCriteria->setCurrentPage($realCurrentPage);
- if ($searchCriteria->getCurrentPage() > $maxPages && $searchResult->getTotalCount() > 0) {
- $offset = (int)$maxPages;
+ $productArray = [];
+ /** @var \Magento\Catalog\Model\Product $product */
+ foreach ($searchResults->getItems() as $product) {
+ $productArray[$product->getId()] = $product->getData();
+ $productArray[$product->getId()]['model'] = $product;
}
- return array_slice($searchResult->getProductsSearchResult(), $offset, $length);
+
+ return $this->searchResultFactory->create(
+ [
+ 'totalCount' => $searchResults->getTotalCount(),
+ 'productsSearchResult' => $productArray,
+ 'searchAggregation' => $itemsResults->getAggregations(),
+ 'pageSize' => $realPageSize,
+ 'currentPage' => $realCurrentPage,
+ 'totalPages' => $maxPages,
+ ]
+ );
}
}
diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/SearchResult.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/SearchResult.php
index 6e229bdc38a31..e4a137413b4c5 100644
--- a/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/SearchResult.php
+++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/SearchResult.php
@@ -7,31 +7,21 @@
namespace Magento\CatalogGraphQl\Model\Resolver\Products;
-use Magento\Framework\Api\SearchResultsInterface;
+use Magento\Framework\Api\Search\AggregationInterface;
/**
* Container for a product search holding the item result and the array in the GraphQL-readable product type format.
*/
class SearchResult
{
- /**
- * @var SearchResultsInterface
- */
- private $totalCount;
-
- /**
- * @var array
- */
- private $productsSearchResult;
+ private $data;
/**
- * @param int $totalCount
- * @param array $productsSearchResult
+ * @param array $data
*/
- public function __construct(int $totalCount, array $productsSearchResult)
+ public function __construct(array $data)
{
- $this->totalCount = $totalCount;
- $this->productsSearchResult = $productsSearchResult;
+ $this->data = $data;
}
/**
@@ -41,7 +31,7 @@ public function __construct(int $totalCount, array $productsSearchResult)
*/
public function getTotalCount() : int
{
- return $this->totalCount;
+ return $this->data['totalCount'] ?? 0;
}
/**
@@ -51,6 +41,46 @@ public function getTotalCount() : int
*/
public function getProductsSearchResult() : array
{
- return $this->productsSearchResult;
+ return $this->data['productsSearchResult'] ?? [];
+ }
+
+ /**
+ * Retrieve aggregated search results
+ *
+ * @return AggregationInterface|null
+ */
+ public function getSearchAggregation(): ?AggregationInterface
+ {
+ return $this->data['searchAggregation'] ?? null;
+ }
+
+ /**
+ * Retrieve the page size for the search
+ *
+ * @return int
+ */
+ public function getPageSize(): int
+ {
+ return $this->data['pageSize'] ?? 0;
+ }
+
+ /**
+ * Retrieve the current page for the search
+ *
+ * @return int
+ */
+ public function getCurrentPage(): int
+ {
+ return $this->data['currentPage'] ?? 0;
+ }
+
+ /**
+ * Retrieve total pages for the search
+ *
+ * @return int
+ */
+ public function getTotalPages(): int
+ {
+ return $this->data['totalPages'] ?? 0;
}
}
diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/SearchResultFactory.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/SearchResultFactory.php
index aec9362f47c3a..479e6a3f96235 100644
--- a/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/SearchResultFactory.php
+++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/SearchResultFactory.php
@@ -30,15 +30,15 @@ public function __construct(ObjectManagerInterface $objectManager)
/**
* Instantiate SearchResult
*
- * @param int $totalCount
- * @param array $productsSearchResult
+ * @param array $data
* @return SearchResult
*/
- public function create(int $totalCount, array $productsSearchResult) : SearchResult
- {
+ public function create(
+ array $data
+ ): SearchResult {
return $this->objectManager->create(
SearchResult::class,
- ['totalCount' => $totalCount, 'productsSearchResult' => $productsSearchResult]
+ ['data' => $data]
);
}
}
diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/RootCategoryId.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/RootCategoryId.php
new file mode 100644
index 0000000000000..4b3e0a1a58dfd
--- /dev/null
+++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/RootCategoryId.php
@@ -0,0 +1,26 @@
+getExtensionAttributes()->getStore()->getRootCategoryId();
+ }
+}
diff --git a/app/code/Magento/CatalogGraphQl/Plugin/Search/Request/ConfigReader.php b/app/code/Magento/CatalogGraphQl/Plugin/Search/Request/ConfigReader.php
new file mode 100644
index 0000000000000..992ab50467c72
--- /dev/null
+++ b/app/code/Magento/CatalogGraphQl/Plugin/Search/Request/ConfigReader.php
@@ -0,0 +1,286 @@
+generatorResolver = $generatorResolver;
+ $this->productAttributeCollectionFactory = $productAttributeCollectionFactory;
+ $this->exactMatchAttributes = array_merge($this->exactMatchAttributes, $exactMatchAttributes);
+ }
+
+ /**
+ * Merge reader's value with generated
+ *
+ * @param \Magento\Framework\Config\ReaderInterface $subject
+ * @param array $result
+ * @return array
+ * @SuppressWarnings(PHPMD.UnusedFormalParameter)
+ */
+ public function afterRead(
+ \Magento\Framework\Config\ReaderInterface $subject,
+ array $result
+ ) {
+ $searchRequestNameWithAggregation = $this->generateRequest();
+ $searchRequest = $searchRequestNameWithAggregation;
+ $searchRequest['queries'][$this->requestName] = $searchRequest['queries'][$this->requestNameWithAggregation];
+ unset($searchRequest['queries'][$this->requestNameWithAggregation], $searchRequest['aggregations']);
+
+ return array_merge_recursive(
+ $result,
+ [
+ $this->requestNameWithAggregation => $searchRequestNameWithAggregation,
+ $this->requestName => $searchRequest,
+ ]
+ );
+ }
+
+ /**
+ * Retrieve searchable attributes
+ *
+ * @return Attribute[]
+ */
+ private function getSearchableAttributes(): array
+ {
+ $attributes = [];
+ /** @var Collection $productAttributes */
+ $productAttributes = $this->productAttributeCollectionFactory->create();
+ $productAttributes->addFieldToFilter(
+ ['is_searchable', 'is_visible_in_advanced_search', 'is_filterable', 'is_filterable_in_search'],
+ [1, 1, [1, 2], 1]
+ );
+
+ /** @var Attribute $attribute */
+ foreach ($productAttributes->getItems() as $attribute) {
+ $attributes[$attribute->getAttributeCode()] = $attribute;
+ }
+
+ return $attributes;
+ }
+
+ /**
+ * Generate search request for search products via GraphQL
+ *
+ * @return array
+ * @SuppressWarnings(PHPMD.CyclomaticComplexity)
+ */
+ private function generateRequest()
+ {
+ $request = [];
+ foreach ($this->getSearchableAttributes() as $attribute) {
+ if (\in_array($attribute->getAttributeCode(), ['price', 'visibility', 'category_ids'])) {
+ //some fields have special semantics
+ continue;
+ }
+ $queryName = $attribute->getAttributeCode() . '_query';
+ $filterName = $attribute->getAttributeCode() . RequestGenerator::FILTER_SUFFIX;
+ $request['queries'][$this->requestNameWithAggregation]['queryReference'][] = [
+ 'clause' => 'must',
+ 'ref' => $queryName,
+ ];
+
+ switch ($attribute->getBackendType()) {
+ case 'static':
+ case 'text':
+ case 'varchar':
+ if ($this->isExactMatchAttribute($attribute)) {
+ $request['queries'][$queryName] = $this->generateFilterQuery($queryName, $filterName);
+ $request['filters'][$filterName] = $this->generateTermFilter($filterName, $attribute);
+ } else {
+ $request['queries'][$queryName] = $this->generateMatchQuery($queryName, $attribute);
+ }
+ break;
+ case 'decimal':
+ case 'datetime':
+ case 'date':
+ $request['queries'][$queryName] = $this->generateFilterQuery($queryName, $filterName);
+ $request['filters'][$filterName] = $this->generateRangeFilter($filterName, $attribute);
+ break;
+ default:
+ $request['queries'][$queryName] = $this->generateFilterQuery($queryName, $filterName);
+ $request['filters'][$filterName] = $this->generateTermFilter($filterName, $attribute);
+ }
+ $generator = $this->generatorResolver->getGeneratorForType($attribute->getBackendType());
+
+ if ($attribute->getData(EavAttributeInterface::IS_FILTERABLE)) {
+ $bucketName = $attribute->getAttributeCode() . self::BUCKET_SUFFIX;
+ $request['aggregations'][$bucketName] = $generator->getAggregationData($attribute, $bucketName);
+ }
+
+ $this->addSearchAttributeToFullTextSearch($attribute, $request);
+ }
+
+ return $request;
+ }
+
+ /**
+ * Add attribute with specified boost to "search" query used in full text search
+ *
+ * @param Attribute $attribute
+ * @param array $request
+ * @return void
+ */
+ private function addSearchAttributeToFullTextSearch(Attribute $attribute, &$request): void
+ {
+ // Match search by custom price attribute isn't supported
+ if ($attribute->getFrontendInput() !== 'price') {
+ $request['queries']['search']['match'][] = [
+ 'field' => $attribute->getAttributeCode(),
+ 'boost' => $attribute->getSearchWeight() ?: 1,
+ ];
+ }
+ }
+
+ /**
+ * Return array representation of range filter
+ *
+ * @param string $filterName
+ * @param Attribute $attribute
+ * @return array
+ */
+ private function generateRangeFilter(string $filterName, Attribute $attribute)
+ {
+ return [
+ 'field' => $attribute->getAttributeCode(),
+ 'name' => $filterName,
+ 'type' => FilterInterface::TYPE_RANGE,
+ 'from' => '$' . $attribute->getAttributeCode() . '.from$',
+ 'to' => '$' . $attribute->getAttributeCode() . '.to$',
+ ];
+ }
+
+ /**
+ * Return array representation of term filter
+ *
+ * @param string $filterName
+ * @param Attribute $attribute
+ * @return array
+ */
+ private function generateTermFilter(string $filterName, Attribute $attribute)
+ {
+ return [
+ 'type' => FilterInterface::TYPE_TERM,
+ 'name' => $filterName,
+ 'field' => $attribute->getAttributeCode(),
+ 'value' => '$' . $attribute->getAttributeCode() . '$',
+ ];
+ }
+
+ /**
+ * Return array representation of query based on filter
+ *
+ * @param string $queryName
+ * @param string $filterName
+ * @return array
+ */
+ private function generateFilterQuery(string $queryName, string $filterName)
+ {
+ return [
+ 'name' => $queryName,
+ 'type' => QueryInterface::TYPE_FILTER,
+ 'filterReference' => [
+ [
+ 'ref' => $filterName,
+ ],
+ ],
+ ];
+ }
+
+ /**
+ * Return array representation of match query
+ *
+ * @param string $queryName
+ * @param Attribute $attribute
+ * @return array
+ */
+ private function generateMatchQuery(string $queryName, Attribute $attribute)
+ {
+ return [
+ 'name' => $queryName,
+ 'type' => 'matchQuery',
+ 'value' => '$' . $attribute->getAttributeCode() . '$',
+ 'match' => [
+ [
+ 'field' => $attribute->getAttributeCode(),
+ 'boost' => $attribute->getSearchWeight() ?: 1,
+ ],
+ ],
+ ];
+ }
+
+ /**
+ * Check if attribute's filter should use exact match
+ *
+ * @param Attribute $attribute
+ * @return bool
+ */
+ private function isExactMatchAttribute(Attribute $attribute)
+ {
+ if (in_array($attribute->getFrontendInput(), ['select', 'multiselect'])) {
+ return true;
+ }
+ if (in_array($attribute->getAttributeCode(), $this->exactMatchAttributes)) {
+ return true;
+ }
+
+ return false;
+ }
+}
diff --git a/app/code/Magento/CatalogGraphQl/composer.json b/app/code/Magento/CatalogGraphQl/composer.json
index 950b496263ffd..1582f29c25951 100644
--- a/app/code/Magento/CatalogGraphQl/composer.json
+++ b/app/code/Magento/CatalogGraphQl/composer.json
@@ -3,13 +3,14 @@
"description": "N/A",
"type": "magento2-module",
"require": {
- "php": "~7.1.3||~7.2.0",
+ "php": "~7.1.3||~7.2.0||~7.3.0",
"magento/module-eav": "*",
"magento/module-catalog": "*",
"magento/module-catalog-inventory": "*",
"magento/module-search": "*",
"magento/module-store": "*",
"magento/module-eav-graph-ql": "*",
+ "magento/module-catalog-search": "*",
"magento/framework": "*"
},
"suggest": {
diff --git a/app/code/Magento/CatalogGraphQl/etc/di.xml b/app/code/Magento/CatalogGraphQl/etc/di.xml
index 406d37b2ea200..485ae792193e3 100644
--- a/app/code/Magento/CatalogGraphQl/etc/di.xml
+++ b/app/code/Magento/CatalogGraphQl/etc/di.xml
@@ -19,6 +19,8 @@
- Magento\CatalogGraphQl\Model\Config\AttributeReader
- Magento\CatalogGraphQl\Model\Config\CategoryAttributeReader
+ - Magento\CatalogGraphQl\Model\Config\SortAttributeReader
+ - Magento\CatalogGraphQl\Model\Config\FilterAttributeReader
@@ -46,6 +48,7 @@
- Magento\CatalogGraphQl\Model\Resolver\Products\DataProvider\Product\CollectionProcessor\SearchCriteriaProcessor
- Magento\CatalogGraphQl\Model\Resolver\Products\DataProvider\Product\CollectionProcessor\StockProcessor
- Magento\CatalogGraphQl\Model\Resolver\Products\DataProvider\Product\CollectionProcessor\VisibilityStatusProcessor
+
- Magento\CatalogGraphQl\Model\Resolver\Products\DataProvider\Product\CollectionProcessor\MediaGalleryProcessor
@@ -54,4 +57,16 @@
Magento\Catalog\Model\Api\SearchCriteria\ProductCollectionProcessor
+
+
+
+
+ - sku
+
+
+
+
+
+
+
diff --git a/app/code/Magento/CatalogGraphQl/etc/graphql/di.xml b/app/code/Magento/CatalogGraphQl/etc/graphql/di.xml
index 2292004f3cf01..fe3413dc3b218 100644
--- a/app/code/Magento/CatalogGraphQl/etc/graphql/di.xml
+++ b/app/code/Magento/CatalogGraphQl/etc/graphql/di.xml
@@ -28,6 +28,13 @@
+
+
+
+ - Magento\CatalogGraphQl\Model\AggregationOptionTypeResolver
+
+
+
@@ -48,6 +55,12 @@
- CustomizableRadioOption
- CustomizableCheckboxOption
+ -
+
- ProductAttributeSortInput
+
+ -
+
- ProductAttributeFilterInput
+
@@ -95,4 +108,14 @@
+
+
+
+
+ - Magento\CatalogGraphQl\DataProvider\Product\LayeredNavigation\Builder\Price
+ - Magento\CatalogGraphQl\DataProvider\Product\LayeredNavigation\Builder\Category
+ - Magento\CatalogGraphQl\DataProvider\Product\LayeredNavigation\Builder\Attribute
+
+
+
diff --git a/app/code/Magento/CatalogGraphQl/etc/schema.graphqls b/app/code/Magento/CatalogGraphQl/etc/schema.graphqls
index f4d0990b17049..76a58857cebc2 100644
--- a/app/code/Magento/CatalogGraphQl/etc/schema.graphqls
+++ b/app/code/Magento/CatalogGraphQl/etc/schema.graphqls
@@ -4,16 +4,16 @@
type Query {
products (
search: String @doc(description: "Performs a full-text search using the specified key words."),
- filter: ProductFilterInput @doc(description: "Identifies which product attributes to search for and return."),
+ filter: ProductAttributeFilterInput @doc(description: "Identifies which product attributes to search for and return."),
pageSize: Int = 20 @doc(description: "Specifies the maximum number of results to return at once. This attribute is optional."),
currentPage: Int = 1 @doc(description: "Specifies which page of results to return. The default value is 1."),
- sort: ProductSortInput @doc(description: "Specifies which attribute to sort on, and whether to return the results in ascending or descending order.")
- ): Products
- @resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\Products") @doc(description: "The products query searches for products that match the criteria specified in the search and filter attributes.") @cache(cacheTag: "cat_p", cacheIdentity: "Magento\\CatalogGraphQl\\Model\\Resolver\\Product\\Identity")
+ sort: ProductAttributeSortInput @doc(description: "Specifies which attributes to sort on, and whether to return the results in ascending or descending order.")
+ ): Products
+ @resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\Products") @doc(description: "The products query searches for products that match the criteria specified in the search and filter attributes.") @cache(cacheIdentity: "Magento\\CatalogGraphQl\\Model\\Resolver\\Product\\Identity")
category (
id: Int @doc(description: "Id of the category.")
): CategoryTree
- @resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\CategoryTree") @doc(description: "The category query searches for categories that match the criteria specified in the search and filter attributes.") @cache(cacheTag: "cat_c", cacheIdentity: "Magento\\CatalogGraphQl\\Model\\Resolver\\Category\\CategoryTreeIdentity")
+ @resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\CategoryTree") @doc(description: "The category query searches for categories that match the criteria specified in the search and filter attributes.") @cache(cacheIdentity: "Magento\\CatalogGraphQl\\Model\\Resolver\\Category\\CategoryTreeIdentity")
}
type Price @doc(description: "The Price object defines the price of a product as well as any tax-related adjustments.") {
@@ -92,13 +92,14 @@ interface ProductInterface @typeResolver(class: "Magento\\CatalogGraphQl\\Model\
type_id: String @doc(description: "One of simple, virtual, bundle, downloadable, grouped, or configurable.")
websites: [Website] @doc(description: "An array of websites in which the product is available.") @resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\Product\\Websites")
product_links: [ProductLinksInterface] @doc(description: "An array of ProductLinks objects.") @resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\Product\\ProductLinks")
- media_gallery_entries: [MediaGalleryEntry] @doc(description: "An array of MediaGalleryEntry objects.") @resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\Product\\MediaGalleryEntries")
+ media_gallery_entries: [MediaGalleryEntry] @deprecated(reason: "Use product's `media_gallery` instead") @doc(description: "An array of MediaGalleryEntry objects.") @resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\Product\\MediaGalleryEntries")
tier_prices: [ProductTierPrices] @doc(description: "An array of ProductTierPrices objects.") @resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\Product\\TierPrices")
price: ProductPrices @doc(description: "A ProductPrices object, indicating the price of an item.") @resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\Product\\Price")
gift_message_available: String @doc(description: "Indicates whether a gift message is available.")
manufacturer: Int @doc(description: "A number representing the product's manufacturer.")
- categories: [CategoryInterface] @doc(description: "The categories assigned to a product.") @cache(cacheTag: "cat_c", cacheIdentity: "Magento\\CatalogGraphQl\\Model\\Resolver\\Category\\CategoriesIdentity") @resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\Categories")
+ categories: [CategoryInterface] @doc(description: "The categories assigned to a product.") @resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\Categories") @cache(cacheIdentity: "Magento\\CatalogGraphQl\\Model\\Resolver\\Category\\CategoriesIdentity")
canonical_url: String @doc(description: "Canonical URL.") @resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\Product\\CanonicalUrl")
+ media_gallery: [MediaGalleryInterface] @doc(description: "An array of Media Gallery objects.") @resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\Product\\MediaGallery")
}
interface PhysicalProductInterface @typeResolver(class: "Magento\\CatalogGraphQl\\Model\\ProductInterfaceTypeResolverComposite") @doc(description: "PhysicalProductInterface contains attributes specific to tangible products.") {
@@ -184,9 +185,12 @@ type CustomizableFileValue @doc(description: "CustomizableFileValue defines the
image_size_y: Int @doc(description: "The maximum height of an image.")
}
-type ProductImage @doc(description: "Product image information. Contains image relative path, URL and label.") {
- url: String @resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\Product\\ProductImage\\Url")
- label: String @resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\Product\\ProductImage\\Label")
+interface MediaGalleryInterface @doc(description: "Contains basic information about a product image or video.") @typeResolver(class: "Magento\\CatalogGraphQl\\Model\\MediaGalleryTypeResolver") {
+ url: String @doc(description: "The URL of the product image or video.") @resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\Product\\MediaGallery\\Url")
+ label: String @doc(description: "The label of the product image or video.") @resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\Product\\MediaGallery\\Label")
+}
+
+type ProductImage implements MediaGalleryInterface @doc(description: "Product image information. Contains the image URL and label.") {
}
interface CustomizableOptionInterface @typeResolver(class: "Magento\\CatalogGraphQl\\Model\\CustomizableOptionTypeResolver") @doc(description: "The CustomizableOptionInterface contains basic information about a customizable option. It can be implemented by several types of configurable options.") {
@@ -217,8 +221,8 @@ interface CategoryInterface @typeResolver(class: "Magento\\CatalogGraphQl\\Model
products(
pageSize: Int = 20 @doc(description: "Specifies the maximum number of results to return at once. This attribute is optional."),
currentPage: Int = 1 @doc(description: "Specifies which page of results to return. The default value is 1."),
- sort: ProductSortInput @doc(description: "Specifies which attribute to sort on, and whether to return the results in ascending or descending order.")
- ): CategoryProducts @doc(description: "The list of products assigned to the category.") @cache(cacheTag: "cat_p", cacheIdentity: "Magento\\CatalogGraphQl\\Model\\Resolver\\Product\\Identity") @resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\Category\\Products")
+ sort: ProductAttributeSortInput @doc(description: "Specifies which attributes to sort on, and whether to return the results in ascending or descending order.")
+ ): CategoryProducts @doc(description: "The list of products assigned to the category.") @cache(cacheIdentity: "Magento\\CatalogGraphQl\\Model\\Resolver\\Product\\Identity") @resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\Category\\Products")
breadcrumbs: [Breadcrumb] @doc(description: "Breadcrumbs, parent categories info.") @resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\Category\\Breadcrumbs")
}
@@ -233,7 +237,7 @@ type CustomizableRadioOption implements CustomizableOptionInterface @doc(descrip
value: [CustomizableRadioValue] @doc(description: "An array that defines a set of radio buttons.")
}
-type CustomizableRadioValue @doc(description: "CustomizableRadioValue defines the price and sku of a product whose page contains a customized set of radio buttons.") {
+type CustomizableRadioValue @doc(description: "CustomizableRadioValue defines the price and sku of a product whose page contains a customized set of radio buttons.") {
option_type_id: Int @doc(description: "The ID assigned to the value.")
price: Float @doc(description: "The price assigned to this option.")
price_type: PriceTypeEnum @doc(description: "FIXED, PERCENT, or DYNAMIC.")
@@ -246,7 +250,7 @@ type CustomizableCheckboxOption implements CustomizableOptionInterface @doc(desc
value: [CustomizableCheckboxValue] @doc(description: "An array that defines a set of checkbox values.")
}
-type CustomizableCheckboxValue @doc(description: "CustomizableCheckboxValue defines the price and sku of a product whose page contains a customized set of checkbox values.") {
+type CustomizableCheckboxValue @doc(description: "CustomizableCheckboxValue defines the price and sku of a product whose page contains a customized set of checkbox values.") {
option_type_id: Int @doc(description: "The ID assigned to the value.")
price: Float @doc(description: "The price assigned to this option.")
price_type: PriceTypeEnum @doc(description: "FIXED, PERCENT, or DYNAMIC.")
@@ -266,7 +270,8 @@ type Products @doc(description: "The Products object is the top-level object ret
items: [ProductInterface] @doc(description: "An array of products that match the specified search criteria.")
page_info: SearchResultPageInfo @doc(description: "An object that includes the page_info and currentPage values specified in the query.")
total_count: Int @doc(description: "The number of products returned.")
- filters: [LayerFilter] @resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\LayerFilters") @doc(description: "Layered navigation filters array.")
+ filters: [LayerFilter] @resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\LayerFilters") @doc(description: "Layered navigation filters array.") @deprecated(reason: "Use aggregations instead")
+ aggregations: [Aggregation] @doc(description: "Layered navigation aggregations.") @resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\Aggregations")
sort_fields: SortFields @doc(description: "An object that includes the default sort field and all available sort fields.") @resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\Category\\SortFields")
}
@@ -276,7 +281,11 @@ type CategoryProducts @doc(description: "The category products object returned i
total_count: Int @doc(description: "The number of products returned.")
}
-input ProductFilterInput @doc(description: "ProductFilterInput defines the filters to be used in the search. A filter contains at least one attribute, a comparison operator, and the value that is being searched for.") {
+input ProductAttributeFilterInput @doc(description: "ProductAttributeFilterInput defines the filters to be used in the search. A filter contains at least one attribute, a comparison operator, and the value that is being searched for.") {
+ category_id: FilterEqualTypeInput @doc(description: "Filter product by category id")
+}
+
+input ProductFilterInput @doc(description: "ProductFilterInput is deprecated, use @ProductAttributeFilterInput instead. ProductFilterInput defines the filters to be used in the search. A filter contains at least one attribute, a comparison operator, and the value that is being searched for.") {
name: FilterTypeInput @doc(description: "The product name. Customers use this name to identify the product.")
sku: FilterTypeInput @doc(description: "A number or code assigned to a product to identify the product, options, price, and manufacturer.")
description: FilterTypeInput @doc(description: "Detailed information about the product. The value can include simple HTML tags.")
@@ -329,7 +338,7 @@ type ProductMediaGalleryEntriesVideoContent @doc(description: "ProductMediaGalle
video_metadata: String @doc(description: "Optional data about the video.")
}
-input ProductSortInput @doc(description: "ProductSortInput specifies the attribute to use for sorting search results and indicates whether the results are sorted in ascending or descending order.") {
+input ProductSortInput @doc(description: "ProductSortInput is deprecated, use @ProductAttributeSortInput instead. ProductSortInput specifies the attribute to use for sorting search results and indicates whether the results are sorted in ascending or descending order.") {
name: SortEnum @doc(description: "The product name. Customers use this name to identify the product.")
sku: SortEnum @doc(description: "A number or code assigned to a product to identify the product, options, price, and manufacturer.")
description: SortEnum @doc(description: "Detailed information about the product. The value can include simple HTML tags.")
@@ -363,7 +372,13 @@ input ProductSortInput @doc(description: "ProductSortInput specifies the attrib
gift_message_available: SortEnum @doc(description: "Indicates whether a gift message is available.")
}
-type MediaGalleryEntry @doc(description: "MediaGalleryEntry defines characteristics about images and videos associated with a specific product.") {
+input ProductAttributeSortInput @doc(description: "ProductAttributeSortInput specifies the attribute to use for sorting search results and indicates whether the results are sorted in ascending or descending order. It's possible to sort products using searchable attributes with enabled 'Use in Filter Options' option")
+{
+ relevance: SortEnum @doc(description: "Sort by the search relevance score (default).")
+ position: SortEnum @doc(description: "Sort by the position assigned to each product.")
+}
+
+type MediaGalleryEntry @doc(description: "MediaGalleryEntry defines characteristics about images and videos associated with a specific product.") {
id: Int @doc(description: "The identifier assigned to the object.")
media_type: String @doc(description: "image or video.")
label: String @doc(description: "The alt text displayed on the UI when the user points to the image.")
@@ -376,22 +391,39 @@ type MediaGalleryEntry @doc(description: "MediaGalleryEntry defines characteris
}
type LayerFilter {
- name: String @doc(description: "Layered navigation filter name.")
- request_var: String @doc(description: "Request variable name for filter query.")
- filter_items_count: Int @doc(description: "Count of filter items in filter group.")
- filter_items: [LayerFilterItemInterface] @doc(description: "Array of filter items.")
+ name: String @doc(description: "Layered navigation filter name.") @deprecated(reason: "Use Aggregation.label instead.")
+ request_var: String @doc(description: "Request variable name for filter query.") @deprecated(reason: "Use Aggregation.attribute_code instead.")
+ filter_items_count: Int @doc(description: "Count of filter items in filter group.") @deprecated(reason: "Use Aggregation.count instead.")
+ filter_items: [LayerFilterItemInterface] @doc(description: "Array of filter items.") @deprecated(reason: "Use Aggregation.options instead.")
}
interface LayerFilterItemInterface @typeResolver(class: "Magento\\CatalogGraphQl\\Model\\LayerFilterItemTypeResolverComposite") {
- label: String @doc(description: "Filter label.")
- value_string: String @doc(description: "Value for filter request variable to be used in query.")
- items_count: Int @doc(description: "Count of items by filter.")
+ label: String @doc(description: "Filter label.") @deprecated(reason: "Use AggregationOption.label instead.")
+ value_string: String @doc(description: "Value for filter request variable to be used in query.") @deprecated(reason: "Use AggregationOption.value instead.")
+ items_count: Int @doc(description: "Count of items by filter.") @deprecated(reason: "Use AggregationOption.count instead.")
}
type LayerFilterItem implements LayerFilterItemInterface {
}
+type Aggregation @doc(description: "A bucket that contains information for each filterable option (such as price, category ID, and custom attributes).") {
+ count: Int @doc(description: "The number of options in the aggregation group.")
+ label: String @doc(description: "The aggregation display name.")
+ attribute_code: String! @doc(description: "Attribute code of the aggregation group.")
+ options: [AggregationOption] @doc(description: "Array of options for the aggregation.")
+}
+
+interface AggregationOptionInterface @typeResolver(class: "Magento\\CatalogGraphQl\\Model\\AggregationOptionTypeResolverComposite") {
+ count: Int @doc(description: "The number of items that match the aggregation option.")
+ label: String @doc(description: "Aggregation option display label.")
+ value: String! @doc(description: "The internal ID that represents the value of the option.")
+}
+
+type AggregationOption implements AggregationOptionInterface {
+
+}
+
type SortField {
value: String @doc(description: "Attribute code of sort field.")
label: String @doc(description: "Label of sort field.")
@@ -412,4 +444,9 @@ type StoreConfig @doc(description: "The type contains information about a store
grid_per_page : Int @doc(description: "Products per Page on Grid Default Value.")
list_per_page : Int @doc(description: "Products per Page on List Default Value.")
catalog_default_sort_by : String @doc(description: "Default Sort By.")
+ root_category_id: Int @doc(description: "The ID of the root category") @resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\RootCategoryId")
+}
+
+type ProductVideo @doc(description: "Contains information about a product video.") implements MediaGalleryInterface {
+ video_content: ProductMediaGalleryEntriesVideoContent @doc(description: "Contains a ProductMediaGalleryEntriesVideoContent object.")
}
diff --git a/app/code/Magento/CatalogGraphQl/etc/search_request.xml b/app/code/Magento/CatalogGraphQl/etc/search_request.xml
new file mode 100644
index 0000000000000..ab1eea9eb6fda
--- /dev/null
+++ b/app/code/Magento/CatalogGraphQl/etc/search_request.xml
@@ -0,0 +1,90 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 0
+ 10000
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 0
+ 10000
+
+
diff --git a/app/code/Magento/CatalogImportExport/Model/Import/Product.php b/app/code/Magento/CatalogImportExport/Model/Import/Product.php
index 5c083d421f0e1..1ae993ed99060 100644
--- a/app/code/Magento/CatalogImportExport/Model/Import/Product.php
+++ b/app/code/Magento/CatalogImportExport/Model/Import/Product.php
@@ -141,7 +141,7 @@ class Product extends \Magento\ImportExport\Model\Import\Entity\AbstractEntity
const COL_PRODUCT_WEBSITES = '_product_websites';
/**
- * Media gallery attribute code.
+ * Attribute code for media gallery.
*/
const MEDIA_GALLERY_ATTRIBUTE_CODE = 'media_gallery';
@@ -151,12 +151,12 @@ class Product extends \Magento\ImportExport\Model\Import\Entity\AbstractEntity
const COL_MEDIA_IMAGE = '_media_image';
/**
- * Inventory use config.
+ * Inventory use config label.
*/
const INVENTORY_USE_CONFIG = 'Use Config';
/**
- * Inventory use config prefix.
+ * Prefix for inventory use config.
*/
const INVENTORY_USE_CONFIG_PREFIX = 'use_config_';
@@ -1192,8 +1192,10 @@ protected function _initTypeModels()
if ($model->isSuitable()) {
$this->_productTypeModels[$productTypeName] = $model;
}
+ // phpcs:disable Magento2.Performance.ForeachArrayMerge.ForeachArrayMerge
$this->_fieldsMap = array_merge($this->_fieldsMap, $model->getCustomFieldsMapping());
$this->_specialAttributes = array_merge($this->_specialAttributes, $model->getParticularAttributes());
+ // phpcs:enable
}
$this->_initErrorTemplates();
// remove doubles
@@ -1884,6 +1886,7 @@ protected function _saveProducts()
return $this;
}
+ //phpcs:enable Generic.Metrics.NestingLevel
/**
* Prepare array with image states (visible or hidden from product page)
@@ -2734,8 +2737,6 @@ protected function _saveValidatedBunches()
try {
$rowData = $source->current();
} catch (\InvalidArgumentException $e) {
- $this->addRowError($e->getMessage(), $this->_processedRowsCount);
- $this->_processedRowsCount++;
$source->next();
continue;
}
@@ -2972,6 +2973,10 @@ private function formatStockDataForRow(array $rowData): array
$stockItemDo = $this->stockRegistry->getStockItem($row['product_id'], $row['website_id']);
$existStockData = $stockItemDo->getData();
+ if (isset($rowData['qty']) && $rowData['qty'] == 0 && !isset($rowData['is_in_stock'])) {
+ $rowData['is_in_stock'] = 0;
+ }
+
$row = array_merge(
$this->defaultStockData,
array_intersect_key($existStockData, $this->defaultStockData),
diff --git a/app/code/Magento/CatalogImportExport/Model/Import/Product/MediaGalleryProcessor.php b/app/code/Magento/CatalogImportExport/Model/Import/Product/MediaGalleryProcessor.php
index d43dc11a68fcf..00e6da0ebe077 100644
--- a/app/code/Magento/CatalogImportExport/Model/Import/Product/MediaGalleryProcessor.php
+++ b/app/code/Magento/CatalogImportExport/Model/Import/Product/MediaGalleryProcessor.php
@@ -284,6 +284,42 @@ private function initMediaGalleryResources()
}
}
+ /**
+ * Get the last media position for each product from the given list
+ *
+ * @param int $storeId
+ * @param array $productIds
+ * @return array
+ */
+ private function getLastMediaPositionPerProduct(int $storeId, array $productIds): array
+ {
+ $result = [];
+ if ($productIds) {
+ $productKeyName = $this->getProductEntityLinkField();
+ // this result could be achieved by using GROUP BY. But there is no index on position column, therefore
+ // it can be slower than the implementation below
+ $positions = $this->connection->fetchAll(
+ $this->connection
+ ->select()
+ ->from($this->mediaGalleryValueTableName, [$productKeyName, 'position'])
+ ->where("$productKeyName IN (?)", $productIds)
+ ->where('value_id is not null')
+ ->where('store_id = ?', $storeId)
+ );
+ // Make sure the result contains all product ids even if the product has no media files
+ $result = array_fill_keys($productIds, 0);
+ // Find the largest position for each product
+ foreach ($positions as $record) {
+ $productId = $record[$productKeyName];
+ $result[$productId] = $result[$productId] < $record['position']
+ ? $record['position']
+ : $result[$productId];
+ }
+ }
+
+ return $result;
+ }
+
/**
* Save media gallery data per store.
*
@@ -301,24 +337,30 @@ private function processMediaPerStore(
) {
$multiInsertData = [];
$dataForSkinnyTable = [];
+ $lastMediaPositionPerProduct = $this->getLastMediaPositionPerProduct(
+ $storeId,
+ array_unique(array_merge(...array_values($valueToProductId)))
+ );
+
foreach ($mediaGalleryData as $mediaGalleryRows) {
foreach ($mediaGalleryRows as $insertValue) {
- foreach ($newMediaValues as $value_id => $values) {
+ foreach ($newMediaValues as $valueId => $values) {
if ($values['value'] == $insertValue['value']) {
- $insertValue['value_id'] = $value_id;
+ $insertValue['value_id'] = $valueId;
$insertValue[$this->getProductEntityLinkField()]
= array_shift($valueToProductId[$values['value']]);
- unset($newMediaValues[$value_id]);
+ unset($newMediaValues[$valueId]);
break;
}
}
if (isset($insertValue['value_id'])) {
+ $productId = $insertValue[$this->getProductEntityLinkField()];
$valueArr = [
'value_id' => $insertValue['value_id'],
'store_id' => $storeId,
- $this->getProductEntityLinkField() => $insertValue[$this->getProductEntityLinkField()],
+ $this->getProductEntityLinkField() => $productId,
'label' => $insertValue['label'],
- 'position' => $insertValue['position'],
+ 'position' => $lastMediaPositionPerProduct[$productId] + $insertValue['position'],
'disabled' => $insertValue['disabled'],
];
$multiInsertData[] = $valueArr;
diff --git a/app/code/Magento/CatalogImportExport/Model/Import/Product/Option.php b/app/code/Magento/CatalogImportExport/Model/Import/Product/Option.php
index 7435c0bebfc14..4d8088a235402 100644
--- a/app/code/Magento/CatalogImportExport/Model/Import/Product/Option.php
+++ b/app/code/Magento/CatalogImportExport/Model/Import/Product/Option.php
@@ -669,6 +669,7 @@ protected function _findNewOptionsWithTheSameTitles()
*
* @param array $sourceProductData
* @return array
+ * phpcs:disable Generic.Metrics.NestingLevel
*/
protected function _getNewOptionsWithTheSameTitlesErrorRows(array $sourceProductData)
{
@@ -683,7 +684,12 @@ protected function _getNewOptionsWithTheSameTitlesErrorRows(array $sourceProduct
ksort($outerTitles);
ksort($innerTitles);
if ($outerTitles === $innerTitles) {
- $errorRows = array_merge($errorRows, $innerData['rows'], $outerData['rows']);
+ foreach ($innerData['rows'] as $innerDataRow) {
+ $errorRows[] = $innerDataRow;
+ }
+ foreach ($outerData['rows'] as $outerDataRow) {
+ $errorRows[] = $outerDataRow;
+ }
}
}
}
@@ -697,6 +703,7 @@ protected function _getNewOptionsWithTheSameTitlesErrorRows(array $sourceProduct
* Find options with the same titles in DB
*
* @return array
+ * phpcs:disable Generic.Metrics.NestingLevel
*/
protected function _findOldOptionsWithTheSameTitles()
{
@@ -717,7 +724,9 @@ protected function _findOldOptionsWithTheSameTitles()
}
}
if ($optionsCount > 1) {
- $errorRows = array_merge($errorRows, $outerData['rows']);
+ foreach ($outerData['rows'] as $dataRow) {
+ $errorRows[] = $dataRow;
+ }
}
}
}
@@ -730,6 +739,7 @@ protected function _findOldOptionsWithTheSameTitles()
* Find source file options, which have analogs in DB with the same name, but with different type
*
* @return array
+ * phpcs:disable Generic.Metrics.NestingLevel
*/
protected function _findNewOldOptionsTypeMismatch()
{
@@ -744,7 +754,9 @@ protected function _findNewOldOptionsTypeMismatch()
ksort($outerTitles);
ksort($innerTitles);
if ($outerTitles === $innerTitles && $outerData['type'] != $innerData['type']) {
- $errorRows = array_merge($errorRows, $outerData['rows']);
+ foreach ($outerData['rows'] as $dataRow) {
+ $errorRows[] = $dataRow;
+ }
}
}
}
@@ -956,8 +968,10 @@ public function validateRow(array $rowData, $rowNumber)
$multiRowData = $this->_getMultiRowFormat($rowData);
- foreach ($multiRowData as $optionData) {
- $combinedData = array_merge($rowData, $optionData);
+ foreach ($multiRowData as $combinedData) {
+ foreach ($rowData as $key => $field) {
+ $combinedData[$key] = $field;
+ }
if ($this->_isRowWithCustomOption($combinedData)) {
if ($this->_isMainOptionRow($combinedData)) {
@@ -1067,7 +1081,7 @@ protected function _isSecondaryOptionRow(array $rowData)
*
* @param array &$options
* @param array &$titles
- * @param array $typeValues
+ * @param array $typeValues
* @return bool
*/
protected function _isReadyForSaving(array &$options, array &$titles, array $typeValues)
@@ -1106,15 +1120,15 @@ protected function _getMultiRowFormat($rowData)
foreach ($rowData['custom_options'] as $name => $customOption) {
$i++;
foreach ($customOption as $rowOrder => $optionRow) {
- $row = array_merge(
- [
- self::COLUMN_STORE => '',
- self::COLUMN_TITLE => $name,
- self::COLUMN_SORT_ORDER => $i,
- self::COLUMN_ROW_SORT => $rowOrder
- ],
- $this->processOptionRow($name, $optionRow)
- );
+ $row = [
+ self::COLUMN_STORE => '',
+ self::COLUMN_TITLE => $name,
+ self::COLUMN_SORT_ORDER => $i,
+ self::COLUMN_ROW_SORT => $rowOrder
+ ];
+ foreach ($this->processOptionRow($name, $optionRow) as $key => $value) {
+ $row[$key] = $value;
+ }
$name = '';
$multiRow[] = $row;
}
@@ -1212,6 +1226,8 @@ private function addFileOptions($result, $optionRow)
*
* @return boolean
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
+ * @SuppressWarnings(PHPMD.ExcessiveMethodLength)
+ * @SuppressWarnings(PHPMD.NPathComplexity)
*/
protected function _importData()
{
@@ -1253,9 +1269,11 @@ protected function _importData()
$optionsToRemove[] = $this->_rowProductId;
}
}
- foreach ($multiRowData as $optionData) {
- $combinedData = array_merge($rowData, $optionData);
+ foreach ($multiRowData as $combinedData) {
+ foreach ($rowData as $key => $field) {
+ $combinedData[$key] = $field;
+ }
if (!$this->isRowAllowedToImport($combinedData, $rowNumber)
|| !$this->_parseRequiredData($combinedData)
) {
@@ -1414,9 +1432,9 @@ protected function _initProductsSku()
/**
* Collect custom option main data to import
*
- * @param array $rowData
- * @param int &$prevOptionId
- * @param int &$nextOptionId
+ * @param array $rowData
+ * @param int &$prevOptionId
+ * @param int &$nextOptionId
* @param array &$products
* @param array &$prices
* @return array|null
@@ -1438,7 +1456,11 @@ protected function _collectOptionMainData(
if (!$this->_isRowHasSpecificType($this->_rowType)
&& ($priceData = $this->_getPriceData($rowData, $nextOptionId, $this->_rowType))
) {
- $prices[$nextOptionId] = $priceData;
+ if ($this->_isPriceGlobal) {
+ $prices[$nextOptionId][Store::DEFAULT_STORE_ID] = $priceData;
+ } else {
+ $prices[$nextOptionId][$this->_rowStoreId] = $priceData;
+ }
}
if (!isset($products[$this->_rowProductId])) {
@@ -1454,9 +1476,9 @@ protected function _collectOptionMainData(
/**
* Collect custom option type data to import
*
- * @param array $rowData
- * @param int &$prevOptionId
- * @param int &$nextValueId
+ * @param array $rowData
+ * @param int &$prevOptionId
+ * @param int &$nextValueId
* @param array &$typeValues
* @param array &$typePrices
* @param array &$typeTitles
@@ -1504,6 +1526,9 @@ protected function _collectOptionTypeData(
$specificTypeData = $this->_getSpecificTypeData($rowData, 0, false);
//For others stores
if ($specificTypeData) {
+ if (isset($specificTypeData['price'])) {
+ $typePrices[$nextValueId][$this->_rowStoreId] = $specificTypeData['price'];
+ }
$typeTitles[$nextValueId++][$this->_rowStoreId] = $specificTypeData['title'];
}
}
@@ -1512,8 +1537,8 @@ protected function _collectOptionTypeData(
/**
* Collect custom option title to import
*
- * @param array $rowData
- * @param int $prevOptionId
+ * @param array $rowData
+ * @param int $prevOptionId
* @param array &$titles
* @return void
*/
@@ -1541,6 +1566,7 @@ protected function _collectOptionTitle(array $rowData, $prevOptionId, array &$ti
* @param array &$prices
* @param array &$typeValues
* @return $this
+ * @SuppressWarnings(PHPMD.UnusedLocalVariable)
*/
protected function _compareOptionsWithExisting(array &$options, array &$titles, array &$prices, array &$typeValues)
{
@@ -1551,7 +1577,9 @@ protected function _compareOptionsWithExisting(array &$options, array &$titles,
$titles[$optionId] = $titles[$newOptionId];
unset($titles[$newOptionId]);
if (isset($prices[$newOptionId])) {
- $prices[$newOptionId]['option_id'] = $optionId;
+ foreach ($prices[$newOptionId] as $storeId => $priceStoreData) {
+ $prices[$newOptionId][$storeId]['option_id'] = $optionId;
+ }
}
if (isset($typeValues[$newOptionId])) {
$typeValues[$optionId] = $typeValues[$newOptionId];
@@ -1584,8 +1612,10 @@ private function restoreOriginalOptionTypeIds(array &$typeValues, array &$typePr
$optionType['option_type_id'] = $existingTypeId;
$typeTitles[$existingTypeId] = $typeTitles[$optionTypeId];
unset($typeTitles[$optionTypeId]);
- $typePrices[$existingTypeId] = $typePrices[$optionTypeId];
- unset($typePrices[$optionTypeId]);
+ if (isset($typePrices[$optionTypeId])) {
+ $typePrices[$existingTypeId] = $typePrices[$optionTypeId];
+ unset($typePrices[$optionTypeId]);
+ }
// If option type titles match at least in one store, consider current option type as existing
break;
}
@@ -1645,7 +1675,7 @@ protected function _parseRequiredData(array $rowData)
if (!isset($this->_storeCodeToId[$rowData[self::COLUMN_STORE]])) {
return false;
}
- $this->_rowStoreId = $this->_storeCodeToId[$rowData[self::COLUMN_STORE]];
+ $this->_rowStoreId = (int)$this->_storeCodeToId[$rowData[self::COLUMN_STORE]];
} else {
$this->_rowStoreId = Store::DEFAULT_STORE_ID;
}
@@ -1761,7 +1791,7 @@ protected function _getPriceData(array $rowData, $optionId, $type)
) {
$priceData = [
'option_id' => $optionId,
- 'store_id' => Store::DEFAULT_STORE_ID,
+ 'store_id' => $this->_rowStoreId,
'price_type' => 'fixed',
];
@@ -1788,29 +1818,30 @@ protected function _getPriceData(array $rowData, $optionId, $type)
*/
protected function _getSpecificTypeData(array $rowData, $optionTypeId, $defaultStore = true)
{
+ $data = [];
+ $priceData = [];
+ $customOptionRowPrice = $rowData[self::COLUMN_ROW_PRICE];
+ if (!empty($customOptionRowPrice) || $customOptionRowPrice === '0') {
+ $priceData['price'] = (double)rtrim($rowData[self::COLUMN_ROW_PRICE], '%');
+ $priceData['price_type'] = ('%' == substr($rowData[self::COLUMN_ROW_PRICE], -1)) ? 'percent' : 'fixed';
+ }
if (!empty($rowData[self::COLUMN_ROW_TITLE]) && $defaultStore && empty($rowData[self::COLUMN_STORE])) {
$valueData = [
'option_type_id' => $optionTypeId,
'sort_order' => empty($rowData[self::COLUMN_ROW_SORT]) ? 0 : abs($rowData[self::COLUMN_ROW_SORT]),
'sku' => !empty($rowData[self::COLUMN_ROW_SKU]) ? $rowData[self::COLUMN_ROW_SKU] : '',
];
-
- $priceData = false;
- $customOptionRowPrice = $rowData[self::COLUMN_ROW_PRICE];
- if (!empty($customOptionRowPrice) || $customOptionRowPrice === '0') {
- $priceData = [
- 'price' => (double)rtrim($rowData[self::COLUMN_ROW_PRICE], '%'),
- 'price_type' => 'fixed',
- ];
- if ('%' == substr($rowData[self::COLUMN_ROW_PRICE], -1)) {
- $priceData['price_type'] = 'percent';
- }
- }
- return ['value' => $valueData, 'title' => $rowData[self::COLUMN_ROW_TITLE], 'price' => $priceData];
+ $data['value'] = $valueData;
+ $data['title'] = $rowData[self::COLUMN_ROW_TITLE];
+ $data['price'] = $priceData;
} elseif (!empty($rowData[self::COLUMN_ROW_TITLE]) && !$defaultStore && !empty($rowData[self::COLUMN_STORE])) {
- return ['title' => $rowData[self::COLUMN_ROW_TITLE]];
+ if ($priceData) {
+ $data['price'] = $priceData;
+ }
+ $data['title'] = $rowData[self::COLUMN_ROW_TITLE];
}
- return false;
+
+ return $data ?: false;
}
/**
@@ -1868,7 +1899,9 @@ protected function _saveTitles(array $titles)
{
$titleRows = [];
foreach ($titles as $optionId => $storeInfo) {
- foreach ($storeInfo as $storeId => $title) {
+ //for use default
+ $uniqStoreInfo = array_unique($storeInfo);
+ foreach ($uniqStoreInfo as $storeId => $title) {
$titleRows[] = ['option_id' => $optionId, 'store_id' => $storeId, 'title' => $title];
}
}
@@ -1892,11 +1925,19 @@ protected function _saveTitles(array $titles)
protected function _savePrices(array $prices)
{
if ($prices) {
- $this->_connection->insertOnDuplicate(
- $this->_tables['catalog_product_option_price'],
- $prices,
- ['price', 'price_type']
- );
+ $optionPriceRows = [];
+ foreach ($prices as $storesData) {
+ foreach ($storesData as $row) {
+ $optionPriceRows[] = $row;
+ }
+ }
+ if ($optionPriceRows) {
+ $this->_connection->insertOnDuplicate(
+ $this->_tables['catalog_product_option_price'],
+ $optionPriceRows,
+ ['price', 'price_type']
+ );
+ }
}
return $this;
@@ -1963,7 +2004,9 @@ protected function _saveSpecificTypeTitles(array $typeTitles)
{
$optionTypeTitleRows = [];
foreach ($typeTitles as $optionTypeId => $storesData) {
- foreach ($storesData as $storeId => $title) {
+ //for use default
+ $uniqStoresData = array_unique($storesData);
+ foreach ($uniqStoresData as $storeId => $title) {
$optionTypeTitleRows[] = [
'option_type_id' => $optionTypeId,
'store_id' => $storeId,
diff --git a/app/code/Magento/CatalogImportExport/Model/Import/Uploader.php b/app/code/Magento/CatalogImportExport/Model/Import/Uploader.php
index 3ac7f98818d70..4ce1c0e39d6de 100644
--- a/app/code/Magento/CatalogImportExport/Model/Import/Uploader.php
+++ b/app/code/Magento/CatalogImportExport/Model/Import/Uploader.php
@@ -6,6 +6,7 @@
namespace Magento\CatalogImportExport\Model\Import;
use Magento\Framework\App\Filesystem\DirectoryList;
+use Magento\Framework\App\ObjectManager;
use Magento\Framework\Filesystem\DriverPool;
/**
@@ -13,6 +14,8 @@
*
* @api
* @since 100.0.2
+ * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
+ * phpcs:disable Magento2.Functions.DiscouragedFunction
*/
class Uploader extends \Magento\MediaStorage\Model\File\Uploader
{
@@ -31,6 +34,13 @@ class Uploader extends \Magento\MediaStorage\Model\File\Uploader
*/
protected $_tmpDir = '';
+ /**
+ * Download directory for url-based resources.
+ *
+ * @var string
+ */
+ private $downloadDir;
+
/**
* Destination directory.
*
@@ -94,6 +104,13 @@ class Uploader extends \Magento\MediaStorage\Model\File\Uploader
*/
protected $_coreFileStorage;
+ /**
+ * Instance of random data generator.
+ *
+ * @var \Magento\Framework\Math\Random
+ */
+ private $random;
+
/**
* @param \Magento\MediaStorage\Helper\File\Storage\Database $coreFileStorageDb
* @param \Magento\MediaStorage\Helper\File\Storage $coreFileStorage
@@ -102,6 +119,8 @@ class Uploader extends \Magento\MediaStorage\Model\File\Uploader
* @param \Magento\Framework\Filesystem $filesystem
* @param \Magento\Framework\Filesystem\File\ReadFactory $readFactory
* @param string|null $filePath
+ * @param \Magento\Framework\Math\Random|null $random
+ * @throws \Magento\Framework\Exception\FileSystemException
* @throws \Magento\Framework\Exception\LocalizedException
*/
public function __construct(
@@ -111,7 +130,8 @@ public function __construct(
\Magento\MediaStorage\Model\File\Validator\NotProtectedExtension $validator,
\Magento\Framework\Filesystem $filesystem,
\Magento\Framework\Filesystem\File\ReadFactory $readFactory,
- $filePath = null
+ $filePath = null,
+ \Magento\Framework\Math\Random $random = null
) {
$this->_imageFactory = $imageFactory;
$this->_coreFileStorageDb = $coreFileStorageDb;
@@ -122,6 +142,8 @@ public function __construct(
if ($filePath !== null) {
$this->_setUploadFile($filePath);
}
+ $this->random = $random ?: ObjectManager::getInstance()->get(\Magento\Framework\Math\Random::class);
+ $this->downloadDir = DirectoryList::getDefaultConfig()[DirectoryList::TMP][DirectoryList::PATH];
}
/**
@@ -150,52 +172,61 @@ public function init()
*/
public function move($fileName, $renameFileOff = false)
{
- if ($renameFileOff) {
- $this->setAllowRenameFiles(false);
- }
-
- if ($this->getTmpDir()) {
- $filePath = $this->getTmpDir() . '/';
- } else {
- $filePath = '';
- }
+ $this->setAllowRenameFiles(!$renameFileOff);
if (preg_match('/\bhttps?:\/\//i', $fileName, $matches)) {
$url = str_replace($matches[0], '', $fileName);
- $driver = $matches[0] === $this->httpScheme ? DriverPool::HTTP : DriverPool::HTTPS;
- $read = $this->_readFactory->create($url, $driver);
-
- //only use filename (for URI with query parameters)
- $parsedUrlPath = parse_url($url, PHP_URL_PATH);
- if ($parsedUrlPath) {
- $urlPathValues = explode('/', $parsedUrlPath);
- if (!empty($urlPathValues)) {
- $fileName = end($urlPathValues);
- }
- }
-
- $fileExtension = pathinfo($fileName, PATHINFO_EXTENSION);
- if ($fileExtension && !$this->checkAllowedExtension($fileExtension)) {
- throw new \Magento\Framework\Exception\LocalizedException(__('Disallowed file type.'));
- }
-
- $fileName = preg_replace('/[^a-z0-9\._-]+/i', '', $fileName);
- $relativePath = $this->_directory->getRelativePath($filePath . $fileName);
- $this->_directory->writeFile(
- $relativePath,
- $read->readAll()
- );
+ $driver = ($matches[0] === $this->httpScheme) ? DriverPool::HTTP : DriverPool::HTTPS;
+ $tmpFilePath = $this->downloadFileFromUrl($url, $driver);
+ } else {
+ $tmpDir = $this->getTmpDir() ? ($this->getTmpDir() . '/') : '';
+ $tmpFilePath = $this->_directory->getRelativePath($tmpDir . $fileName);
}
- $filePath = $this->_directory->getRelativePath($filePath . $fileName);
- $this->_setUploadFile($filePath);
+ $this->_setUploadFile($tmpFilePath);
$destDir = $this->_directory->getAbsolutePath($this->getDestDir());
$result = $this->save($destDir);
unset($result['path']);
$result['name'] = self::getCorrectFileName($result['name']);
+
return $result;
}
+ /**
+ * Writes a url-based file to the temp directory.
+ *
+ * @param string $url
+ * @param string $driver
+ * @return string
+ * @throws \Magento\Framework\Exception\LocalizedException
+ */
+ private function downloadFileFromUrl($url, $driver)
+ {
+ $parsedUrlPath = parse_url($url, PHP_URL_PATH);
+ if (!$parsedUrlPath) {
+ throw new \Magento\Framework\Exception\LocalizedException(__('Could not parse resource url.'));
+ }
+ $urlPathValues = explode('/', $parsedUrlPath);
+ $fileName = preg_replace('/[^a-z0-9\._-]+/i', '', end($urlPathValues));
+
+ $fileExtension = pathinfo($fileName, PATHINFO_EXTENSION);
+ if ($fileExtension && !$this->checkAllowedExtension($fileExtension)) {
+ throw new \Magento\Framework\Exception\LocalizedException(__('Disallowed file type.'));
+ }
+
+ $tmpFileName = str_replace(".$fileExtension", '', $fileName);
+ $tmpFileName .= '_' . $this->random->getRandomString(16);
+ $tmpFileName .= $fileExtension ? ".$fileExtension" : '';
+ $tmpFilePath = $this->_directory->getRelativePath($this->downloadDir . '/' . $tmpFileName);
+
+ $this->_directory->writeFile(
+ $tmpFilePath,
+ $this->_readFactory->create($url, $driver)->readAll()
+ );
+
+ return $tmpFilePath;
+ }
+
/**
* Prepare information about the file for moving
*
@@ -238,7 +269,7 @@ protected function _readFileInfo($filePath)
* Validate uploaded file by type and etc.
*
* @return void
- * @throws \Exception
+ * @throws \Magento\Framework\Exception\LocalizedException
*/
protected function _validateFile()
{
@@ -251,8 +282,7 @@ protected function _validateFile()
$fileExtension = pathinfo($filePath, PATHINFO_EXTENSION);
if (!$this->checkAllowedExtension($fileExtension)) {
- $this->_directory->delete($filePath);
- throw new \Exception('Disallowed file type.');
+ throw new \Magento\Framework\Exception\LocalizedException(__('Disallowed file type.'));
}
//run validate callbacks
foreach ($this->_validateCallbacks as $params) {
@@ -356,6 +386,7 @@ protected function _moveFile($tmpPath, $destPath)
*/
protected function chmod($file)
{
+ //phpcs:ignore Squiz.PHP.NonExecutableCode.ReturnNotRequired
return;
}
}
diff --git a/app/code/Magento/CatalogImportExport/Test/Mftf/ActionGroup/AdminExportActionGroup.xml b/app/code/Magento/CatalogImportExport/Test/Mftf/ActionGroup/AdminExportActionGroup.xml
index 65588daa96cc4..f792b0be2eb6b 100644
--- a/app/code/Magento/CatalogImportExport/Test/Mftf/ActionGroup/AdminExportActionGroup.xml
+++ b/app/code/Magento/CatalogImportExport/Test/Mftf/ActionGroup/AdminExportActionGroup.xml
@@ -8,26 +8,33 @@
-
+
+ Filters Products by the provided Attribute. Exports the filtered Products list. Validates that the Success Message is present.
+
+
-
+
-
+
+
+ Exports the unfiltered Products list. Validates that the Success Message is present.
+
+
@@ -39,9 +46,13 @@
+
+ Downloads the provided Grid Index on the Exports grid page.
+
+
@@ -50,10 +61,15 @@
+
+ Deletes the provided Grid Index on the Exports grid page.
+
+
+
diff --git a/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportBundleProductTest.xml b/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportBundleProductTest.xml
index 281c8c0db307a..74345e64a7c9a 100644
--- a/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportBundleProductTest.xml
+++ b/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportBundleProductTest.xml
@@ -18,9 +18,6 @@
-
-
-
diff --git a/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportGroupedProductWithSpecialPriceTest.xml b/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportGroupedProductWithSpecialPriceTest.xml
index c47b7dc83af28..b0ac6a4bc95ac 100644
--- a/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportGroupedProductWithSpecialPriceTest.xml
+++ b/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportGroupedProductWithSpecialPriceTest.xml
@@ -18,9 +18,6 @@
-
-
-
diff --git a/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportImportConfigurableProductWithImagesTest.xml b/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportImportConfigurableProductWithImagesTest.xml
new file mode 100644
index 0000000000000..88f6e6c9f9039
--- /dev/null
+++ b/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportImportConfigurableProductWithImagesTest.xml
@@ -0,0 +1,216 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportSimpleAndConfigurableProductsWithCustomOptionsTest.xml b/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportSimpleAndConfigurableProductsWithCustomOptionsTest.xml
index 160abe617995d..1870cb21bd55b 100644
--- a/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportSimpleAndConfigurableProductsWithCustomOptionsTest.xml
+++ b/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportSimpleAndConfigurableProductsWithCustomOptionsTest.xml
@@ -18,9 +18,6 @@
-
-
-
diff --git a/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportSimpleProductAndConfigurableProductsWithAssignedImagesTest.xml b/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportSimpleProductAndConfigurableProductsWithAssignedImagesTest.xml
index 3b8da4055ab7e..f6690199d63fe 100644
--- a/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportSimpleProductAndConfigurableProductsWithAssignedImagesTest.xml
+++ b/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportSimpleProductAndConfigurableProductsWithAssignedImagesTest.xml
@@ -18,9 +18,6 @@
-
-
-
@@ -118,7 +115,6 @@
-
diff --git a/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportSimpleProductWithCustomAttributeTest.xml b/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportSimpleProductWithCustomAttributeTest.xml
index f958978a9efae..238a3286dc40d 100644
--- a/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportSimpleProductWithCustomAttributeTest.xml
+++ b/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportSimpleProductWithCustomAttributeTest.xml
@@ -18,9 +18,6 @@
-
-
-
diff --git a/app/code/Magento/CatalogImportExport/Test/Unit/Model/Import/Product/Type/OptionTest.php b/app/code/Magento/CatalogImportExport/Test/Unit/Model/Import/Product/Type/OptionTest.php
index f0a52a67e0095..9f63decac5ff7 100644
--- a/app/code/Magento/CatalogImportExport/Test/Unit/Model/Import/Product/Type/OptionTest.php
+++ b/app/code/Magento/CatalogImportExport/Test/Unit/Model/Import/Product/Type/OptionTest.php
@@ -79,8 +79,8 @@ class OptionTest extends \Magento\ImportExport\Test\Unit\Model\Import\AbstractIm
* @var array
*/
protected $_expectedPrices = [
- 2 => ['option_id' => 2, 'store_id' => 0, 'price_type' => 'fixed', 'price' => 0],
- 3 => ['option_id' => 3, 'store_id' => 0, 'price_type' => 'fixed', 'price' => 2]
+ 0 => ['option_id' => 2, 'store_id' => 0, 'price_type' => 'fixed', 'price' => 0],
+ 1 => ['option_id' => 3, 'store_id' => 0, 'price_type' => 'fixed', 'price' => 2]
];
/**
diff --git a/app/code/Magento/CatalogImportExport/Test/Unit/Model/Import/UploaderTest.php b/app/code/Magento/CatalogImportExport/Test/Unit/Model/Import/UploaderTest.php
index f734596de014b..2c6aa6535c10e 100644
--- a/app/code/Magento/CatalogImportExport/Test/Unit/Model/Import/UploaderTest.php
+++ b/app/code/Magento/CatalogImportExport/Test/Unit/Model/Import/UploaderTest.php
@@ -6,6 +6,11 @@
*/
namespace Magento\CatalogImportExport\Test\Unit\Model\Import;
+/**
+ * Class UploaderTest
+ *
+ * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
+ */
class UploaderTest extends \PHPUnit\Framework\TestCase
{
/**
@@ -43,6 +48,11 @@ class UploaderTest extends \PHPUnit\Framework\TestCase
*/
protected $directoryMock;
+ /**
+ * @var \Magento\Framework\Math\Random|\PHPUnit_Framework_MockObject_MockObject
+ */
+ private $random;
+
/**
* @var \Magento\CatalogImportExport\Model\Import\Uploader|\PHPUnit_Framework_MockObject_MockObject
*/
@@ -84,56 +94,90 @@ protected function setUp()
->method('getDirectoryWrite')
->will($this->returnValue($this->directoryMock));
+ $this->random = $this->getMockBuilder(\Magento\Framework\Math\Random::class)
+ ->disableOriginalConstructor()
+ ->setMethods(['getRandomString'])
+ ->getMock();
+
$this->uploader = $this->getMockBuilder(\Magento\CatalogImportExport\Model\Import\Uploader::class)
- ->setConstructorArgs([
- $this->coreFileStorageDb,
- $this->coreFileStorage,
- $this->imageFactory,
- $this->validator,
- $this->filesystem,
- $this->readFactory,
- ])
+ ->setConstructorArgs(
+ [
+ $this->coreFileStorageDb,
+ $this->coreFileStorage,
+ $this->imageFactory,
+ $this->validator,
+ $this->filesystem,
+ $this->readFactory,
+ null,
+ $this->random
+ ]
+ )
->setMethods(['_setUploadFile', 'save', 'getTmpDir', 'checkAllowedExtension'])
->getMock();
}
/**
* @dataProvider moveFileUrlDataProvider
+ * @param $fileUrl
+ * @param $expectedHost
+ * @param $expectedFileName
+ * @param $checkAllowedExtension
+ * @throws \Magento\Framework\Exception\LocalizedException
*/
public function testMoveFileUrl($fileUrl, $expectedHost, $expectedFileName, $checkAllowedExtension)
{
+ $tmpDir = 'var/tmp';
$destDir = 'var/dest/dir';
- $expectedRelativeFilePath = $expectedFileName;
- $this->directoryMock->expects($this->once())->method('isWritable')->with($destDir)->willReturn(true);
- $this->directoryMock->expects($this->any())->method('getRelativePath')->with($expectedRelativeFilePath);
- $this->directoryMock->expects($this->once())->method('getAbsolutePath')->with($destDir)
- ->willReturn($destDir . '/' . $expectedFileName);
- // Check writeFile() method invoking.
- $this->directoryMock->expects($this->any())->method('writeFile')->will($this->returnValue($expectedFileName));
+
+ // Expected invocation to validate file extension
+ $this->uploader->expects($this->exactly($checkAllowedExtension))->method('checkAllowedExtension')
+ ->willReturn(true);
+
+ // Expected invocation to generate random string for file name postfix
+ $this->random->expects($this->once())->method('getRandomString')
+ ->with(16)
+ ->willReturn('38GcEmPFKXXR8NMj');
+
+ // Expected invocation to build the temp file path with the correct directory and filename
+ $this->directoryMock->expects($this->any())->method('getRelativePath')
+ ->with($tmpDir . '/' . $expectedFileName);
// Create adjusted reader which does not validate path.
$readMock = $this->getMockBuilder(\Magento\Framework\Filesystem\File\Read::class)
->disableOriginalConstructor()
->setMethods(['readAll'])
->getMock();
- // Check readAll() method invoking.
- $readMock->expects($this->once())->method('readAll')->will($this->returnValue(null));
- // Check create() method invoking with expected argument.
- $this->readFactory->expects($this->once())
- ->method('create')
- ->will($this->returnValue($readMock))->with($expectedHost);
- //Check invoking of getTmpDir(), _setUploadFile(), save() methods.
- $this->uploader->expects($this->any())->method('getTmpDir')->will($this->returnValue(''));
- $this->uploader->expects($this->once())->method('_setUploadFile')->will($this->returnSelf());
- $this->uploader->expects($this->once())->method('save')->with($destDir . '/' . $expectedFileName)
- ->willReturn(['name' => $expectedFileName, 'path' => 'absPath']);
- $this->uploader->expects($this->exactly($checkAllowedExtension))
- ->method('checkAllowedExtension')
+ // Expected invocations to create reader and read contents from url
+ $this->readFactory->expects($this->once())->method('create')
+ ->with($expectedHost)
+ ->will($this->returnValue($readMock));
+ $readMock->expects($this->once())->method('readAll')
+ ->will($this->returnValue(null));
+
+ // Expected invocation to write the temp file
+ $this->directoryMock->expects($this->any())->method('writeFile')
+ ->will($this->returnValue($expectedFileName));
+
+ // Expected invocations to move the temp file to the destination directory
+ $this->directoryMock->expects($this->once())->method('isWritable')
+ ->with($destDir)
->willReturn(true);
+ $this->directoryMock->expects($this->once())->method('getAbsolutePath')
+ ->with($destDir)
+ ->willReturn($destDir . '/' . $expectedFileName);
+ $this->uploader->expects($this->once())->method('_setUploadFile')
+ ->willReturnSelf();
+ $this->uploader->expects($this->once())->method('save')
+ ->with($destDir . '/' . $expectedFileName)
+ ->willReturn(['name' => $expectedFileName, 'path' => 'absPath']);
+
+ // Do not use configured temp directory
+ $this->uploader->expects($this->never())->method('getTmpDir');
$this->uploader->setDestDir($destDir);
$result = $this->uploader->move($fileUrl);
+
$this->assertEquals(['name' => $expectedFileName], $result);
$this->assertArrayNotHasKey('path', $result);
}
@@ -182,14 +226,16 @@ public function testMoveFileUrlDrivePool($fileUrl, $expectedHost, $expectedDrive
->willReturn($driverMock);
$uploaderMock = $this->getMockBuilder(\Magento\CatalogImportExport\Model\Import\Uploader::class)
- ->setConstructorArgs([
- $this->coreFileStorageDb,
- $this->coreFileStorage,
- $this->imageFactory,
- $this->validator,
- $this->filesystem,
- $readFactory,
- ])
+ ->setConstructorArgs(
+ [
+ $this->coreFileStorageDb,
+ $this->coreFileStorage,
+ $this->imageFactory,
+ $this->validator,
+ $this->filesystem,
+ $readFactory,
+ ]
+ )
->getMock();
$result = $uploaderMock->move($fileUrl);
@@ -223,42 +269,66 @@ public function moveFileUrlDriverPoolDataProvider()
public function moveFileUrlDataProvider()
{
return [
- [
- '$fileUrl' => 'http://test_uploader_file',
+ 'https_no_file_ext' => [
+ '$fileUrl' => 'https://test_uploader_file',
'$expectedHost' => 'test_uploader_file',
- '$expectedFileName' => 'test_uploader_file',
+ '$expectedFileName' => 'test_uploader_file_38GcEmPFKXXR8NMj',
'$checkAllowedExtension' => 0
],
- [
- '$fileUrl' => 'https://!:^&`;file',
- '$expectedHost' => '!:^&`;file',
- '$expectedFileName' => 'file',
+ 'https_invalid_chars' => [
+ '$fileUrl' => 'https://www.google.com/!:^&`;image.jpg',
+ '$expectedHost' => 'www.google.com/!:^&`;image.jpg',
+ '$expectedFileName' => 'image_38GcEmPFKXXR8NMj.jpg',
+ '$checkAllowedExtension' => 1
+ ],
+ 'https_invalid_chars_no_file_ext' => [
+ '$fileUrl' => 'https://!:^&`;image',
+ '$expectedHost' => '!:^&`;image',
+ '$expectedFileName' => 'image_38GcEmPFKXXR8NMj',
'$checkAllowedExtension' => 0
],
- [
+ 'http_jpg' => [
+ '$fileUrl' => 'http://www.google.com/image.jpg',
+ '$expectedHost' => 'www.google.com/image.jpg',
+ '$expectedFileName' => 'image_38GcEmPFKXXR8NMj.jpg',
+ '$checkAllowedExtension' => 1
+ ],
+ 'https_jpg' => [
'$fileUrl' => 'https://www.google.com/image.jpg',
'$expectedHost' => 'www.google.com/image.jpg',
- '$expectedFileName' => 'image.jpg',
+ '$expectedFileName' => 'image_38GcEmPFKXXR8NMj.jpg',
'$checkAllowedExtension' => 1
],
- [
+ 'https_jpeg' => [
+ '$fileUrl' => 'https://www.google.com/image.jpeg',
+ '$expectedHost' => 'www.google.com/image.jpeg',
+ '$expectedFileName' => 'image_38GcEmPFKXXR8NMj.jpeg',
+ '$checkAllowedExtension' => 1
+ ],
+ 'https_png' => [
+ '$fileUrl' => 'https://www.google.com/image.png',
+ '$expectedHost' => 'www.google.com/image.png',
+ '$expectedFileName' => 'image_38GcEmPFKXXR8NMj.png',
+ '$checkAllowedExtension' => 1
+ ],
+ 'https_gif' => [
+ '$fileUrl' => 'https://www.google.com/image.gif',
+ '$expectedHost' => 'www.google.com/image.gif',
+ '$expectedFileName' => 'image_38GcEmPFKXXR8NMj.gif',
+ '$checkAllowedExtension' => 1
+ ],
+ 'https_one_query_param' => [
'$fileUrl' => 'https://www.google.com/image.jpg?param=1',
'$expectedHost' => 'www.google.com/image.jpg?param=1',
- '$expectedFileName' => 'image.jpg',
+ '$expectedFileName' => 'image_38GcEmPFKXXR8NMj.jpg',
'$checkAllowedExtension' => 1
],
- [
+ 'https_two_query_params' => [
'$fileUrl' => 'https://www.google.com/image.jpg?param=1¶m=2',
'$expectedHost' => 'www.google.com/image.jpg?param=1¶m=2',
- '$expectedFileName' => 'image.jpg',
- '$checkAllowedExtension' => 1
- ],
- [
- '$fileUrl' => 'http://www.google.com/image.jpg?param=1¶m=2',
- '$expectedHost' => 'www.google.com/image.jpg?param=1¶m=2',
- '$expectedFileName' => 'image.jpg',
+ '$expectedFileName' => 'image_38GcEmPFKXXR8NMj.jpg',
'$checkAllowedExtension' => 1
- ],
+ ]
];
}
}
diff --git a/app/code/Magento/CatalogImportExport/composer.json b/app/code/Magento/CatalogImportExport/composer.json
index 56307a01e1cb6..6af2bbaf45e3c 100644
--- a/app/code/Magento/CatalogImportExport/composer.json
+++ b/app/code/Magento/CatalogImportExport/composer.json
@@ -5,7 +5,7 @@
"sort-packages": true
},
"require": {
- "php": "~7.1.3||~7.2.0",
+ "php": "~7.1.3||~7.2.0||~7.3.0",
"ext-ctype": "*",
"magento/framework": "*",
"magento/module-catalog": "*",
diff --git a/app/code/Magento/CatalogInventory/Block/Adminhtml/Form/Field/Minsaleqty.php b/app/code/Magento/CatalogInventory/Block/Adminhtml/Form/Field/Minsaleqty.php
index d3c165bbde1a8..6c4f6a0f46a59 100644
--- a/app/code/Magento/CatalogInventory/Block/Adminhtml/Form/Field/Minsaleqty.php
+++ b/app/code/Magento/CatalogInventory/Block/Adminhtml/Form/Field/Minsaleqty.php
@@ -3,6 +3,8 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
+declare(strict_types=1);
+
namespace Magento\CatalogInventory\Block\Adminhtml\Form\Field;
/**
@@ -51,7 +53,13 @@ protected function _prepareToRender()
'customer_group_id',
['label' => __('Customer Group'), 'renderer' => $this->_getGroupRenderer()]
);
- $this->addColumn('min_sale_qty', ['label' => __('Minimum Qty')]);
+ $this->addColumn(
+ 'min_sale_qty',
+ [
+ 'label' => __('Minimum Qty'),
+ 'class' => 'required-entry validate-number validate-greater-than-zero'
+ ]
+ );
$this->_addAfter = false;
$this->_addButtonLabel = __('Add Minimum Qty');
}
diff --git a/app/code/Magento/CatalogInventory/Model/AddStockStatusToCollection.php b/app/code/Magento/CatalogInventory/Model/AddStockStatusToCollection.php
index 0a02d4eb6a9a6..6f3e40b622f42 100644
--- a/app/code/Magento/CatalogInventory/Model/AddStockStatusToCollection.php
+++ b/app/code/Magento/CatalogInventory/Model/AddStockStatusToCollection.php
@@ -7,8 +7,6 @@
namespace Magento\CatalogInventory\Model;
use Magento\Catalog\Model\ResourceModel\Product\Collection;
-use Magento\Framework\Search\EngineResolverInterface;
-use Magento\Search\Model\EngineResolver;
/**
* Catalog inventory module plugin
@@ -20,21 +18,13 @@ class AddStockStatusToCollection
*/
protected $stockHelper;
- /**
- * @var EngineResolverInterface
- */
- private $engineResolver;
-
/**
* @param \Magento\CatalogInventory\Helper\Stock $stockHelper
- * @param EngineResolverInterface $engineResolver
*/
public function __construct(
- \Magento\CatalogInventory\Helper\Stock $stockHelper,
- EngineResolverInterface $engineResolver
+ \Magento\CatalogInventory\Helper\Stock $stockHelper
) {
$this->stockHelper = $stockHelper;
- $this->engineResolver = $engineResolver;
}
/**
@@ -47,9 +37,7 @@ public function __construct(
*/
public function beforeLoad(Collection $productCollection, $printQuery = false, $logQuery = false)
{
- if ($this->engineResolver->getCurrentSearchEngine() === EngineResolver::CATALOG_SEARCH_MYSQL_ENGINE) {
- $this->stockHelper->addIsInStockFilterToCollection($productCollection);
- }
+ $this->stockHelper->addIsInStockFilterToCollection($productCollection);
return [$printQuery, $logQuery];
}
}
diff --git a/app/code/Magento/CatalogInventory/Model/Plugin/Layer.php b/app/code/Magento/CatalogInventory/Model/Plugin/Layer.php
deleted file mode 100644
index 168e947b8fa57..0000000000000
--- a/app/code/Magento/CatalogInventory/Model/Plugin/Layer.php
+++ /dev/null
@@ -1,92 +0,0 @@
-stockHelper = $stockHelper;
- $this->scopeConfig = $scopeConfig;
- $this->engineResolver = $engineResolver;
- }
-
- /**
- * Before prepare product collection handler
- *
- * @param \Magento\Catalog\Model\Layer $subject
- * @param \Magento\Catalog\Model\ResourceModel\Collection\AbstractCollection $collection
- *
- * @return void
- * @SuppressWarnings(PHPMD.UnusedFormalParameter)
- */
- public function beforePrepareProductCollection(
- \Magento\Catalog\Model\Layer $subject,
- \Magento\Catalog\Model\ResourceModel\Collection\AbstractCollection $collection
- ) {
- if (!$this->isCurrentEngineMysql() || $this->_isEnabledShowOutOfStock()) {
- return;
- }
- $this->stockHelper->addIsInStockFilterToCollection($collection);
- }
-
- /**
- * Check if current engine is MYSQL.
- *
- * @return bool
- */
- private function isCurrentEngineMysql()
- {
- return $this->engineResolver->getCurrentSearchEngine() === EngineResolver::CATALOG_SEARCH_MYSQL_ENGINE;
- }
-
- /**
- * Get config value for 'display out of stock' option
- *
- * @return bool
- */
- protected function _isEnabledShowOutOfStock()
- {
- return $this->scopeConfig->isSetFlag(
- 'cataloginventory/options/show_out_of_stock',
- \Magento\Store\Model\ScopeInterface::SCOPE_STORE
- );
- }
-}
diff --git a/app/code/Magento/CatalogInventory/Model/ResourceModel/Indexer/Stock/DefaultStock.php b/app/code/Magento/CatalogInventory/Model/ResourceModel/Indexer/Stock/DefaultStock.php
index ba3b62f554767..3670b93b8cb48 100644
--- a/app/code/Magento/CatalogInventory/Model/ResourceModel/Indexer/Stock/DefaultStock.php
+++ b/app/code/Magento/CatalogInventory/Model/ResourceModel/Indexer/Stock/DefaultStock.php
@@ -230,8 +230,6 @@ protected function _getStockStatusSelect($entityIds = null, $usePrimaryTable = f
{
$connection = $this->getConnection();
$qtyExpr = $connection->getCheckSql('cisi.qty > 0', 'cisi.qty', 0);
- $metadata = $this->getMetadataPool()->getMetadata(\Magento\Catalog\Api\Data\ProductInterface::class);
- $linkField = $metadata->getLinkField();
$select = $connection->select()->from(
['e' => $this->getTable('catalog_product_entity')],
@@ -245,12 +243,6 @@ protected function _getStockStatusSelect($entityIds = null, $usePrimaryTable = f
['cisi' => $this->getTable('cataloginventory_stock_item')],
'cisi.stock_id = cis.stock_id AND cisi.product_id = e.entity_id',
[]
- )->joinInner(
- ['mcpei' => $this->getTable('catalog_product_entity_int')],
- 'e.' . $linkField . ' = mcpei.' . $linkField
- . ' AND mcpei.attribute_id = ' . $this->_getAttribute('status')->getId()
- . ' AND mcpei.value = ' . ProductStatus::STATUS_ENABLED,
- []
)->columns(
['qty' => $qtyExpr]
)->where(
@@ -292,6 +284,7 @@ protected function _prepareIndexTable($entityIds = null)
*/
protected function _updateIndex($entityIds)
{
+ $this->deleteOldRecords($entityIds);
$connection = $this->getConnection();
$select = $this->_getStockStatusSelect($entityIds, true);
$select = $this->getQueryProcessorComposite()->processQuery($select, $entityIds, true);
@@ -314,7 +307,6 @@ protected function _updateIndex($entityIds)
}
}
- $this->deleteOldRecords($entityIds);
$this->_updateIndexTable($data);
return $this;
@@ -322,6 +314,7 @@ protected function _updateIndex($entityIds)
/**
* Delete records by their ids from index table
+ *
* Used to clean table before re-indexation
*
* @param array $ids
@@ -366,6 +359,8 @@ public function getIdxTable($table = null)
}
/**
+ * Get status expression
+ *
* @param AdapterInterface $connection
* @param bool $isAggregate
* @return mixed
@@ -391,6 +386,8 @@ protected function getStatusExpression(AdapterInterface $connection, $isAggregat
}
/**
+ * Get stock configuration
+ *
* @return StockConfigurationInterface
*
* @deprecated 100.1.0
@@ -406,6 +403,8 @@ protected function getStockConfiguration()
}
/**
+ * Get query processor composite
+ *
* @return QueryProcessorComposite
*/
private function getQueryProcessorComposite()
diff --git a/app/code/Magento/CatalogInventory/Setup/Patch/Schema/ChangeTmpTablesEngine.php b/app/code/Magento/CatalogInventory/Setup/Patch/Schema/ChangeTmpTablesEngine.php
new file mode 100644
index 0000000000000..7f43cd279d4e3
--- /dev/null
+++ b/app/code/Magento/CatalogInventory/Setup/Patch/Schema/ChangeTmpTablesEngine.php
@@ -0,0 +1,61 @@
+schemaSetup = $schemaSetup;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function apply()
+ {
+ $this->schemaSetup->startSetup();
+
+ $tableName = $this->schemaSetup->getTable('cataloginventory_stock_status_tmp');
+ if ($this->schemaSetup->getConnection()->isTableExists($tableName)) {
+ $this->schemaSetup->getConnection()->changeTableEngine($tableName, 'InnoDB');
+ }
+
+ $this->schemaSetup->endSetup();
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public static function getDependencies()
+ {
+ return [];
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function getAliases()
+ {
+ return [];
+ }
+}
diff --git a/app/code/Magento/CatalogInventory/Test/Mftf/ActionGroup/AdminCatalogInventoryConfigurationActionGroup.xml b/app/code/Magento/CatalogInventory/Test/Mftf/ActionGroup/AdminCatalogInventoryConfigurationActionGroup.xml
new file mode 100644
index 0000000000000..49956473132ec
--- /dev/null
+++ b/app/code/Magento/CatalogInventory/Test/Mftf/ActionGroup/AdminCatalogInventoryConfigurationActionGroup.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/CatalogInventory/Test/Mftf/ActionGroup/AdminProductActionGroup.xml b/app/code/Magento/CatalogInventory/Test/Mftf/ActionGroup/AdminProductActionGroup.xml
new file mode 100644
index 0000000000000..84dc6b93c885f
--- /dev/null
+++ b/app/code/Magento/CatalogInventory/Test/Mftf/ActionGroup/AdminProductActionGroup.xml
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/CatalogInventory/Test/Mftf/ActionGroup/DisplayOutOfStockProductActionGroup.xml b/app/code/Magento/CatalogInventory/Test/Mftf/ActionGroup/DisplayOutOfStockProductActionGroup.xml
index 2850b8d069201..13cb9089bf920 100644
--- a/app/code/Magento/CatalogInventory/Test/Mftf/ActionGroup/DisplayOutOfStockProductActionGroup.xml
+++ b/app/code/Magento/CatalogInventory/Test/Mftf/ActionGroup/DisplayOutOfStockProductActionGroup.xml
@@ -7,24 +7,33 @@
-->
+ xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd">
+
+ Goes to the 'Configuration' page for 'Inventory'. Enables 'Display Out of Stock Products'. Clicks on the Save button.
+
+
-
-
+
+
-
-
-
+
+
+
+
+
+ Goes to the 'Configuration' page for 'Inventory'. Disables 'Display Out of Stock Products'. Clicks on the Save button.
+
+
-
+
-
-
-
+
+
+
diff --git a/app/code/Magento/CatalogInventory/Test/Mftf/ActionGroup/StorefrontAssertProductStockStatusActionGroup.xml b/app/code/Magento/CatalogInventory/Test/Mftf/ActionGroup/StorefrontAssertProductStockStatusActionGroup.xml
new file mode 100644
index 0000000000000..30fddba4241b7
--- /dev/null
+++ b/app/code/Magento/CatalogInventory/Test/Mftf/ActionGroup/StorefrontAssertProductStockStatusActionGroup.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/code/Magento/CatalogInventory/Test/Mftf/Data/CatalogInventoryConfigData.xml b/app/code/Magento/CatalogInventory/Test/Mftf/Data/CatalogInventoryConfigData.xml
index e14c36446fc2b..cd5a8cf5bbac9 100644
--- a/app/code/Magento/CatalogInventory/Test/Mftf/Data/CatalogInventoryConfigData.xml
+++ b/app/code/Magento/CatalogInventory/Test/Mftf/Data/CatalogInventoryConfigData.xml
@@ -20,4 +20,17 @@
No
0
+
+
+ cataloginventory/options/can_subtract
+ 0
+ Yes
+ 1
+
+
+ cataloginventory/options/can_subtract
+ 0
+ No
+ 0
+
diff --git a/app/code/Magento/CatalogInventory/Test/Mftf/Data/CatalogInventoryItemOptionsData.xml b/app/code/Magento/CatalogInventory/Test/Mftf/Data/CatalogInventoryItemOptionsData.xml
new file mode 100644
index 0000000000000..767d65f9facca
--- /dev/null
+++ b/app/code/Magento/CatalogInventory/Test/Mftf/Data/CatalogInventoryItemOptionsData.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+ MaxSaleQtyDefaultValue
+
+
+ 10000
+
+
diff --git a/app/code/Magento/CatalogInventory/Test/Mftf/Metadata/cataloginventory_item_options-meta.xml b/app/code/Magento/CatalogInventory/Test/Mftf/Metadata/cataloginventory_item_options-meta.xml
new file mode 100644
index 0000000000000..7672cb7478f1a
--- /dev/null
+++ b/app/code/Magento/CatalogInventory/Test/Mftf/Metadata/cataloginventory_item_options-meta.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+ integer
+
+
+
+
+
+
diff --git a/app/code/Magento/CatalogInventory/Test/Mftf/Page/AdminInventoryProductStockOptionsConfigPage.xml b/app/code/Magento/CatalogInventory/Test/Mftf/Page/AdminInventoryProductStockOptionsConfigPage.xml
new file mode 100644
index 0000000000000..3d8c3ef3cf9f8
--- /dev/null
+++ b/app/code/Magento/CatalogInventory/Test/Mftf/Page/AdminInventoryProductStockOptionsConfigPage.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/CatalogInventory/Test/Mftf/Page/AdminProductCreatePage.xml b/app/code/Magento/CatalogInventory/Test/Mftf/Page/AdminProductCreatePage.xml
new file mode 100644
index 0000000000000..5835e7564c172
--- /dev/null
+++ b/app/code/Magento/CatalogInventory/Test/Mftf/Page/AdminProductCreatePage.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/CatalogInventory/Test/Mftf/Section/AdminInventoryProductStockOptionsConfigSection.xml b/app/code/Magento/CatalogInventory/Test/Mftf/Section/AdminInventoryProductStockOptionsConfigSection.xml
new file mode 100644
index 0000000000000..ef7fe30f4970b
--- /dev/null
+++ b/app/code/Magento/CatalogInventory/Test/Mftf/Section/AdminInventoryProductStockOptionsConfigSection.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFormAdvancedInventorySection.xml b/app/code/Magento/CatalogInventory/Test/Mftf/Section/AdminProductFormAdvancedInventorySection.xml
similarity index 90%
rename from app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFormAdvancedInventorySection.xml
rename to app/code/Magento/CatalogInventory/Test/Mftf/Section/AdminProductFormAdvancedInventorySection.xml
index a4517cfd46168..7ff9c2d70755f 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFormAdvancedInventorySection.xml
+++ b/app/code/Magento/CatalogInventory/Test/Mftf/Section/AdminProductFormAdvancedInventorySection.xml
@@ -18,7 +18,7 @@
-
+
@@ -30,5 +30,7 @@
+
+
diff --git a/app/code/Magento/CatalogInventory/Test/Mftf/Section/AdminProductFormSection.xml b/app/code/Magento/CatalogInventory/Test/Mftf/Section/AdminProductFormSection.xml
new file mode 100644
index 0000000000000..f4b79b17b3fc3
--- /dev/null
+++ b/app/code/Magento/CatalogInventory/Test/Mftf/Section/AdminProductFormSection.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
diff --git a/app/code/Magento/CatalogInventory/Test/Mftf/Test/AdminCreateProductWithZeroMaximumQtyAllowedInShoppingCartTest.xml b/app/code/Magento/CatalogInventory/Test/Mftf/Test/AdminCreateProductWithZeroMaximumQtyAllowedInShoppingCartTest.xml
new file mode 100644
index 0000000000000..f7cf0a4deba4b
--- /dev/null
+++ b/app/code/Magento/CatalogInventory/Test/Mftf/Test/AdminCreateProductWithZeroMaximumQtyAllowedInShoppingCartTest.xml
@@ -0,0 +1,80 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/CatalogInventory/Test/Unit/Model/Plugin/LayerTest.php b/app/code/Magento/CatalogInventory/Test/Unit/Model/Plugin/LayerTest.php
deleted file mode 100644
index b64563a35176d..0000000000000
--- a/app/code/Magento/CatalogInventory/Test/Unit/Model/Plugin/LayerTest.php
+++ /dev/null
@@ -1,106 +0,0 @@
-_scopeConfigMock = $this->createMock(\Magento\Framework\App\Config\ScopeConfigInterface::class);
- $this->_stockHelperMock = $this->createMock(\Magento\CatalogInventory\Helper\Stock::class);
- $this->engineResolver = $this->getMockBuilder(EngineResolverInterface::class)
- ->disableOriginalConstructor()
- ->setMethods(['getCurrentSearchEngine'])
- ->getMockForAbstractClass();
-
- $this->_model = new \Magento\CatalogInventory\Model\Plugin\Layer(
- $this->_stockHelperMock,
- $this->_scopeConfigMock,
- $this->engineResolver
- );
- }
-
- /**
- * Test add stock status to collection with disabled 'display out of stock' option
- */
- public function testAddStockStatusDisabledShow()
- {
- $this->engineResolver->expects($this->any())
- ->method('getCurrentSearchEngine')
- ->willReturn('mysql');
-
- $this->_scopeConfigMock->expects(
- $this->once()
- )->method(
- 'isSetFlag'
- )->with(
- 'cataloginventory/options/show_out_of_stock'
- )->will(
- $this->returnValue(true)
- );
- /** @var \Magento\Catalog\Model\ResourceModel\Product\Collection $collectionMock */
- $collectionMock = $this->createMock(\Magento\Catalog\Model\ResourceModel\Product\Collection::class);
- $this->_stockHelperMock->expects($this->never())->method('addIsInStockFilterToCollection');
- /** @var \Magento\Catalog\Model\Layer $subjectMock */
- $subjectMock = $this->createMock(\Magento\Catalog\Model\Layer::class);
- $this->_model->beforePrepareProductCollection($subjectMock, $collectionMock);
- }
-
- /**
- * Test add stock status to collection with 'display out of stock' option enabled
- */
- public function testAddStockStatusEnabledShow()
- {
- $this->engineResolver->expects($this->any())
- ->method('getCurrentSearchEngine')
- ->willReturn('mysql');
-
- $this->_scopeConfigMock->expects(
- $this->once()
- )->method(
- 'isSetFlag'
- )->with(
- 'cataloginventory/options/show_out_of_stock'
- )->will(
- $this->returnValue(false)
- );
-
- $collectionMock = $this->createMock(\Magento\Catalog\Model\ResourceModel\Product\Collection::class);
-
- $this->_stockHelperMock->expects(
- $this->once()
- )->method(
- 'addIsInStockFilterToCollection'
- )->with(
- $collectionMock
- );
-
- $subjectMock = $this->createMock(\Magento\Catalog\Model\Layer::class);
- $this->_model->beforePrepareProductCollection($subjectMock, $collectionMock);
- }
-}
diff --git a/app/code/Magento/CatalogInventory/Ui/DataProvider/Product/Form/Modifier/AdvancedInventory.php b/app/code/Magento/CatalogInventory/Ui/DataProvider/Product/Form/Modifier/AdvancedInventory.php
index 7386f133b569a..da465f5bdd3dc 100644
--- a/app/code/Magento/CatalogInventory/Ui/DataProvider/Product/Form/Modifier/AdvancedInventory.php
+++ b/app/code/Magento/CatalogInventory/Ui/DataProvider/Product/Form/Modifier/AdvancedInventory.php
@@ -1,4 +1,7 @@
$this->locator->getProduct()->isLockedAttribute($fieldCode),
];
- $qty['arguments']['data']['config'] = [
- 'component' => 'Magento_CatalogInventory/js/components/qty-validator-changer',
- 'group' => 'quantity_and_stock_status_qty',
- 'dataType' => 'number',
- 'formElement' => 'input',
- 'componentType' => 'field',
- 'visible' => '1',
- 'require' => '0',
- 'additionalClasses' => 'admin__field-small',
- 'label' => __('Quantity'),
- 'scopeLabel' => '[GLOBAL]',
- 'dataScope' => 'qty',
- 'validation' => [
- 'validate-number' => true,
- 'less-than-equals-to' => StockDataFilter::MAX_QTY_VALUE,
- ],
- 'imports' => [
- 'handleChanges' => '${$.provider}:data.product.stock_data.is_qty_decimal',
- ],
- 'sortOrder' => 10,
- ];
- $advancedInventoryButton['arguments']['data']['config'] = [
- 'displayAsLink' => true,
- 'formElement' => 'container',
- 'componentType' => 'container',
- 'component' => 'Magento_Ui/js/form/components/button',
- 'template' => 'ui/form/components/button/container',
- 'actions' => [
- [
- 'targetName' => 'product_form.product_form.advanced_inventory_modal',
- 'actionName' => 'toggleModal',
- ],
- ],
- 'title' => __('Advanced Inventory'),
- 'provider' => false,
- 'additionalForGroup' => true,
- 'source' => 'product_details',
- 'sortOrder' => 20,
- ];
$container['children'] = [
- 'qty' => $qty,
- 'advanced_inventory_button' => $advancedInventoryButton,
+ 'qty' => $this->getQtyMetaStructure(),
+ 'advanced_inventory_button' => $this->getAdvancedInventoryButtonMetaStructure(),
];
$this->meta = $this->arrayManager->merge(
$fieldsetPath . '/children',
$this->meta,
- ['quantity_and_stock_status_qty' => $container]
+ ['container_quantity_and_stock_status_qty' => $container]
);
}
}
+
+ /**
+ * Get Qty meta structure
+ *
+ * @return array
+ */
+ private function getQtyMetaStructure()
+ {
+ return [
+ 'arguments' => [
+ 'data' => [
+ 'config' => [
+ 'component' => 'Magento_CatalogInventory/js/components/qty-validator-changer',
+ 'group' => 'quantity_and_stock_status_qty',
+ 'dataType' => 'number',
+ 'formElement' => 'input',
+ 'componentType' => 'field',
+ 'visible' => '1',
+ 'require' => '0',
+ 'additionalClasses' => 'admin__field-small',
+ 'label' => __('Quantity'),
+ 'scopeLabel' => '[GLOBAL]',
+ 'dataScope' => 'qty',
+ 'validation' => [
+ 'validate-number' => true,
+ 'less-than-equals-to' => StockDataFilter::MAX_QTY_VALUE,
+ ],
+ 'imports' => [
+ 'handleChanges' => '${$.provider}:data.product.stock_data.is_qty_decimal',
+ ],
+ 'sortOrder' => 10,
+ 'disabled' => $this->locator->getProduct()->isLockedAttribute('quantity_and_stock_status'),
+ ]
+ ]
+ ]
+ ];
+ }
+
+ /**
+ * Get advances inventory button meta structure
+ *
+ * @return array
+ */
+ private function getAdvancedInventoryButtonMetaStructure()
+ {
+ return [
+ 'arguments' => [
+ 'data' => [
+ 'config' => [
+ 'displayAsLink' => true,
+ 'formElement' => 'container',
+ 'componentType' => 'container',
+ 'component' => 'Magento_Ui/js/form/components/button',
+ 'template' => 'ui/form/components/button/container',
+ 'actions' => [
+ [
+ 'targetName' => 'product_form.product_form.advanced_inventory_modal',
+ 'actionName' => 'toggleModal',
+ ],
+ ],
+ 'title' => __('Advanced Inventory'),
+ 'provider' => false,
+ 'additionalForGroup' => true,
+ 'source' => 'product_details',
+ 'sortOrder' => 20,
+ ]
+ ]
+ ]
+ ];
+ }
}
diff --git a/app/code/Magento/CatalogInventory/composer.json b/app/code/Magento/CatalogInventory/composer.json
index eb6239ea87ef0..ab586f409c8f8 100644
--- a/app/code/Magento/CatalogInventory/composer.json
+++ b/app/code/Magento/CatalogInventory/composer.json
@@ -5,10 +5,9 @@
"sort-packages": true
},
"require": {
- "php": "~7.1.3||~7.2.0",
+ "php": "~7.1.3||~7.2.0||~7.3.0",
"magento/framework": "*",
"magento/module-catalog": "*",
- "magento/module-search": "*",
"magento/module-config": "*",
"magento/module-customer": "*",
"magento/module-eav": "*",
diff --git a/app/code/Magento/CatalogInventory/etc/adminhtml/system.xml b/app/code/Magento/CatalogInventory/etc/adminhtml/system.xml
index 08ed0a8f49470..546f838b9b428 100644
--- a/app/code/Magento/CatalogInventory/etc/adminhtml/system.xml
+++ b/app/code/Magento/CatalogInventory/etc/adminhtml/system.xml
@@ -55,7 +55,7 @@
Maximum Qty Allowed in Shopping Cart
- validate-number
+ validate-number validate-greater-than-zero
Out-of-Stock Threshold
diff --git a/app/code/Magento/CatalogInventory/etc/di.xml b/app/code/Magento/CatalogInventory/etc/di.xml
index e7d79c593b8c7..78a0c2b734315 100644
--- a/app/code/Magento/CatalogInventory/etc/di.xml
+++ b/app/code/Magento/CatalogInventory/etc/di.xml
@@ -47,9 +47,6 @@
Magento\CatalogInventory\Model\ResourceModel\Stock\Item\Proxy
-
-
-
diff --git a/app/code/Magento/CatalogInventory/view/adminhtml/ui_component/product_form.xml b/app/code/Magento/CatalogInventory/view/adminhtml/ui_component/product_form.xml
index 0a7f0fdc32d40..b813aa5d356cb 100644
--- a/app/code/Magento/CatalogInventory/view/adminhtml/ui_component/product_form.xml
+++ b/app/code/Magento/CatalogInventory/view/adminhtml/ui_component/product_form.xml
@@ -74,8 +74,12 @@
${$.provider}:data.product.stock_data.manage_stock
+ ${$.parentName}.manage_stock:disabled
${$.parentName}.manage_stock:disabled
+
+ ns = ${ $.ns }, index = qty, group = quantity_and_stock_status_qty:disabled
+
@@ -101,6 +105,7 @@
quantity_and_stock_status.qty
ns = ${ $.ns }, index = qty, group = quantity_and_stock_status_qty:value
+ ns = ${ $.ns }, index = qty, group = quantity_and_stock_status_qty:disabled
${$.provider}:data.product.stock_data.is_qty_decimal
@@ -149,8 +154,12 @@
${$.provider}:data.product.stock_data.min_qty
+ ${$.parentName}.min_qty:disabled
${$.parentName}.min_qty:disabled
+
+ ns = ${ $.ns }, index = qty, group = quantity_and_stock_status_qty:disabled
+
@@ -211,6 +220,13 @@
true
use_config_min_sale_qty
+
+ ${$.parentName}.min_sale_qty:disabled
+ ${$.parentName}.min_sale_qty:disabled
+
+
+ ns = ${ $.ns }, index = qty, group = quantity_and_stock_status_qty:disabled
+
@@ -288,6 +304,7 @@
[GLOBAL]
+ true
true
Maximum Qty Allowed in Shopping Cart
@@ -308,8 +325,12 @@
${$.provider}:data.product.stock_data.max_sale_qty
+ ${$.parentName}.max_sale_qty:disabled
${$.parentName}.max_sale_qty:disabled
+
+ ns = ${ $.ns }, index = qty, group = quantity_and_stock_status_qty:disabled
+
@@ -337,6 +358,7 @@
stock_data.is_qty_decimal
${$.provider}:data.product.stock_data.manage_stock
+ ns = ${ $.ns }, index = qty, group = quantity_and_stock_status_qty:disabled
@@ -359,6 +381,7 @@
stock_data.is_decimal_divided
${$.provider}:data.product.stock_data.manage_stock
+ ns = ${ $.ns }, index = qty, group = quantity_and_stock_status_qty:disabled
@@ -418,8 +441,12 @@
${$.provider}:data.product.stock_data.backorders
+ ${$.parentName}.backorders:disabled
${$.parentName}.backorders:disabled
+
+ ns = ${ $.ns }, index = qty, group = quantity_and_stock_status_qty:disabled
+
@@ -475,8 +502,12 @@
${$.provider}:data.product.stock_data.notify_stock_qty
+ ${$.parentName}.notify_stock_qty:disabled
${$.parentName}.notify_stock_qty:disabled
+
+ ns = ${ $.ns }, index = qty, group = quantity_and_stock_status_qty:disabled
+
@@ -534,8 +565,12 @@
${$.provider}:data.product.stock_data.enable_qty_increments
+ ${$.parentName}.enable_qty_increments:disabled
${$.parentName}.enable_qty_increments:disabled
+
+ ns = ${ $.ns }, index = qty, group = quantity_and_stock_status_qty:disabled
+
@@ -595,8 +630,12 @@
${$.provider}:data.product.stock_data.qty_increments
+ ${$.parentName}.qty_increments:disabled
${$.parentName}.qty_increments:disabled
+
+ ns = ${ $.ns }, index = qty, group = quantity_and_stock_status_qty:disabled
+
@@ -633,6 +672,9 @@
[GLOBAL]
Stock Status
is_in_stock
+
+ ns = ${ $.ns }, index = qty, group = quantity_and_stock_status_qty:disabled
+
diff --git a/app/code/Magento/CatalogInventoryGraphQl/composer.json b/app/code/Magento/CatalogInventoryGraphQl/composer.json
index 5e85c3ae12f93..644e3e2b6c34b 100644
--- a/app/code/Magento/CatalogInventoryGraphQl/composer.json
+++ b/app/code/Magento/CatalogInventoryGraphQl/composer.json
@@ -3,7 +3,7 @@
"description": "N/A",
"type": "magento2-module",
"require": {
- "php": "~7.1.3||~7.2.0",
+ "php": "~7.1.3||~7.2.0||~7.3.0",
"magento/framework": "*",
"magento/module-store": "*",
"magento/module-catalog": "*",
diff --git a/app/code/Magento/CatalogRule/Controller/Adminhtml/Promo/Catalog/Save.php b/app/code/Magento/CatalogRule/Controller/Adminhtml/Promo/Catalog/Save.php
index 0ff12faf54cbf..6d499b93e411f 100644
--- a/app/code/Magento/CatalogRule/Controller/Adminhtml/Promo/Catalog/Save.php
+++ b/app/code/Magento/CatalogRule/Controller/Adminhtml/Promo/Catalog/Save.php
@@ -12,6 +12,7 @@
use Magento\Framework\Registry;
use Magento\Framework\Stdlib\DateTime\Filter\Date;
use Magento\Framework\App\Request\DataPersistorInterface;
+use Magento\Framework\Stdlib\DateTime\TimezoneInterface;
/**
* Save action for catalog rule
@@ -25,19 +26,27 @@ class Save extends \Magento\CatalogRule\Controller\Adminhtml\Promo\Catalog imple
*/
protected $dataPersistor;
+ /**
+ * @var TimezoneInterface
+ */
+ private $localeDate;
+
/**
* @param Context $context
* @param Registry $coreRegistry
* @param Date $dateFilter
* @param DataPersistorInterface $dataPersistor
+ * @param TimezoneInterface $localeDate
*/
public function __construct(
Context $context,
Registry $coreRegistry,
Date $dateFilter,
- DataPersistorInterface $dataPersistor
+ DataPersistorInterface $dataPersistor,
+ TimezoneInterface $localeDate
) {
$this->dataPersistor = $dataPersistor;
+ $this->localeDate = $localeDate;
parent::__construct($context, $coreRegistry, $dateFilter);
}
@@ -46,16 +55,15 @@ public function __construct(
*
* @return \Magento\Framework\App\ResponseInterface|\Magento\Framework\Controller\ResultInterface|void
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
+ * @SuppressWarnings(PHPMD.NPathComplexity)
*/
public function execute()
{
if ($this->getRequest()->getPostValue()) {
-
/** @var \Magento\CatalogRule\Api\CatalogRuleRepositoryInterface $ruleRepository */
$ruleRepository = $this->_objectManager->get(
\Magento\CatalogRule\Api\CatalogRuleRepositoryInterface::class
);
-
/** @var \Magento\CatalogRule\Model\Rule $model */
$model = $this->_objectManager->create(\Magento\CatalogRule\Model\Rule::class);
@@ -65,7 +73,9 @@ public function execute()
['request' => $this->getRequest()]
);
$data = $this->getRequest()->getPostValue();
-
+ if (!$this->getRequest()->getParam('from_date')) {
+ $data['from_date'] = $this->localeDate->formatDate();
+ }
$filterValues = ['from_date' => $this->_dateFilter];
if ($this->getRequest()->getParam('to_date')) {
$filterValues['to_date'] = $this->_dateFilter;
@@ -97,6 +107,9 @@ public function execute()
unset($data['rule']);
}
+ unset($data['conditions_serialized']);
+ unset($data['actions_serialized']);
+
$model->loadPost($data);
$this->_objectManager->get(\Magento\Backend\Model\Session::class)->setPageData($data);
diff --git a/app/code/Magento/CatalogRule/Model/Indexer/ReindexRuleProduct.php b/app/code/Magento/CatalogRule/Model/Indexer/ReindexRuleProduct.php
index 55a234bb8ae27..944710773123f 100644
--- a/app/code/Magento/CatalogRule/Model/Indexer/ReindexRuleProduct.php
+++ b/app/code/Magento/CatalogRule/Model/Indexer/ReindexRuleProduct.php
@@ -8,7 +8,10 @@
use Magento\CatalogRule\Model\Indexer\IndexerTableSwapperInterface as TableSwapper;
use Magento\Catalog\Model\ResourceModel\Indexer\ActiveTableSwitcher;
-use Magento\Framework\App\ObjectManager;
+use Magento\CatalogRule\Model\Rule;
+use Magento\Framework\App\ResourceConnection;
+use Magento\Framework\Stdlib\DateTime\TimezoneInterface;
+use Magento\Store\Model\ScopeInterface;
/**
* Reindex rule relations with products.
@@ -16,7 +19,7 @@
class ReindexRuleProduct
{
/**
- * @var \Magento\Framework\App\ResourceConnection
+ * @var ResourceConnection
*/
private $resource;
@@ -31,36 +34,40 @@ class ReindexRuleProduct
private $tableSwapper;
/**
- * @param \Magento\Framework\App\ResourceConnection $resource
+ * @var TimezoneInterface
+ */
+ private $localeDate;
+
+ /**
+ * @param ResourceConnection $resource
* @param ActiveTableSwitcher $activeTableSwitcher
- * @param TableSwapper|null $tableSwapper
+ * @param TableSwapper $tableSwapper
+ * @param TimezoneInterface $localeDate
*/
public function __construct(
- \Magento\Framework\App\ResourceConnection $resource,
+ ResourceConnection $resource,
ActiveTableSwitcher $activeTableSwitcher,
- TableSwapper $tableSwapper = null
+ TableSwapper $tableSwapper,
+ TimezoneInterface $localeDate
) {
$this->resource = $resource;
$this->activeTableSwitcher = $activeTableSwitcher;
- $this->tableSwapper = $tableSwapper ??
- ObjectManager::getInstance()->get(TableSwapper::class);
+ $this->tableSwapper = $tableSwapper;
+ $this->localeDate = $localeDate;
}
/**
* Reindex information about rule relations with products.
*
- * @param \Magento\CatalogRule\Model\Rule $rule
+ * @param Rule $rule
* @param int $batchCount
* @param bool $useAdditionalTable
* @return bool
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
* @SuppressWarnings(PHPMD.NPathComplexity)
*/
- public function execute(
- \Magento\CatalogRule\Model\Rule $rule,
- $batchCount,
- $useAdditionalTable = false
- ) {
+ public function execute(Rule $rule, $batchCount, $useAdditionalTable = false)
+ {
if (!$rule->getIsActive() || empty($rule->getWebsiteIds())) {
return false;
}
@@ -84,21 +91,28 @@ public function execute(
$ruleId = $rule->getId();
$customerGroupIds = $rule->getCustomerGroupIds();
- $fromTime = strtotime($rule->getFromDate());
- $toTime = strtotime($rule->getToDate());
- $toTime = $toTime ? $toTime + \Magento\CatalogRule\Model\Indexer\IndexBuilder::SECONDS_IN_DAY - 1 : 0;
$sortOrder = (int)$rule->getSortOrder();
$actionOperator = $rule->getSimpleAction();
$actionAmount = $rule->getDiscountAmount();
$actionStop = $rule->getStopRulesProcessing();
$rows = [];
+ foreach ($websiteIds as $websiteId) {
+ $scopeTz = new \DateTimeZone(
+ $this->localeDate->getConfigTimezone(ScopeInterface::SCOPE_WEBSITE, $websiteId)
+ );
+ $fromTime = $rule->getFromDate()
+ ? (new \DateTime($rule->getFromDate(), $scopeTz))->getTimestamp()
+ : 0;
+ $toTime = $rule->getToDate()
+ ? (new \DateTime($rule->getToDate(), $scopeTz))->getTimestamp() + IndexBuilder::SECONDS_IN_DAY - 1
+ : 0;
- foreach ($productIds as $productId => $validationByWebsite) {
- foreach ($websiteIds as $websiteId) {
+ foreach ($productIds as $productId => $validationByWebsite) {
if (empty($validationByWebsite[$websiteId])) {
continue;
}
+
foreach ($customerGroupIds as $customerGroupId) {
$rows[] = [
'rule_id' => $ruleId,
@@ -123,6 +137,7 @@ public function execute(
if (!empty($rows)) {
$connection->insertMultiple($indexTable, $rows);
}
+
return true;
}
}
diff --git a/app/code/Magento/CatalogRule/Model/Indexer/ReindexRuleProductPrice.php b/app/code/Magento/CatalogRule/Model/Indexer/ReindexRuleProductPrice.php
index 6a87be3c50a64..11ba87730bec1 100644
--- a/app/code/Magento/CatalogRule/Model/Indexer/ReindexRuleProductPrice.php
+++ b/app/code/Magento/CatalogRule/Model/Indexer/ReindexRuleProductPrice.php
@@ -6,54 +6,58 @@
namespace Magento\CatalogRule\Model\Indexer;
+use Magento\Catalog\Model\Product;
+use Magento\Framework\Stdlib\DateTime\TimezoneInterface;
+use Magento\Store\Model\StoreManagerInterface;
+
/**
* Reindex product prices according rule settings.
*/
class ReindexRuleProductPrice
{
/**
- * @var \Magento\Store\Model\StoreManagerInterface
+ * @var StoreManagerInterface
*/
private $storeManager;
/**
- * @var \Magento\CatalogRule\Model\Indexer\RuleProductsSelectBuilder
+ * @var RuleProductsSelectBuilder
*/
private $ruleProductsSelectBuilder;
/**
- * @var \Magento\CatalogRule\Model\Indexer\ProductPriceCalculator
+ * @var ProductPriceCalculator
*/
private $productPriceCalculator;
/**
- * @var \Magento\Framework\Stdlib\DateTime\DateTime
+ * @var TimezoneInterface
*/
- private $dateTime;
+ private $localeDate;
/**
- * @var \Magento\CatalogRule\Model\Indexer\RuleProductPricesPersistor
+ * @var RuleProductPricesPersistor
*/
private $pricesPersistor;
/**
- * @param \Magento\Store\Model\StoreManagerInterface $storeManager
+ * @param StoreManagerInterface $storeManager
* @param RuleProductsSelectBuilder $ruleProductsSelectBuilder
* @param ProductPriceCalculator $productPriceCalculator
- * @param \Magento\Framework\Stdlib\DateTime\DateTime $dateTime
- * @param \Magento\CatalogRule\Model\Indexer\RuleProductPricesPersistor $pricesPersistor
+ * @param TimezoneInterface $localeDate
+ * @param RuleProductPricesPersistor $pricesPersistor
*/
public function __construct(
- \Magento\Store\Model\StoreManagerInterface $storeManager,
- \Magento\CatalogRule\Model\Indexer\RuleProductsSelectBuilder $ruleProductsSelectBuilder,
- \Magento\CatalogRule\Model\Indexer\ProductPriceCalculator $productPriceCalculator,
- \Magento\Framework\Stdlib\DateTime\DateTime $dateTime,
- \Magento\CatalogRule\Model\Indexer\RuleProductPricesPersistor $pricesPersistor
+ StoreManagerInterface $storeManager,
+ RuleProductsSelectBuilder $ruleProductsSelectBuilder,
+ ProductPriceCalculator $productPriceCalculator,
+ TimezoneInterface $localeDate,
+ RuleProductPricesPersistor $pricesPersistor
) {
$this->storeManager = $storeManager;
$this->ruleProductsSelectBuilder = $ruleProductsSelectBuilder;
$this->productPriceCalculator = $productPriceCalculator;
- $this->dateTime = $dateTime;
+ $this->localeDate = $localeDate;
$this->pricesPersistor = $pricesPersistor;
}
@@ -61,22 +65,16 @@ public function __construct(
* Reindex product prices.
*
* @param int $batchCount
- * @param \Magento\Catalog\Model\Product|null $product
+ * @param Product|null $product
* @param bool $useAdditionalTable
* @return bool
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
*/
- public function execute(
- $batchCount,
- \Magento\Catalog\Model\Product $product = null,
- $useAdditionalTable = false
- ) {
- $fromDate = mktime(0, 0, 0, date('m'), date('d') - 1);
- $toDate = mktime(0, 0, 0, date('m'), date('d') + 1);
-
+ public function execute($batchCount, Product $product = null, $useAdditionalTable = false)
+ {
/**
* Update products rules prices per each website separately
- * because of max join limit in mysql
+ * because for each website date in website's timezone should be used
*/
foreach ($this->storeManager->getWebsites() as $website) {
$productsStmt = $this->ruleProductsSelectBuilder->build($website->getId(), $product, $useAdditionalTable);
@@ -84,6 +82,13 @@ public function execute(
$stopFlags = [];
$prevKey = null;
+ $storeGroup = $this->storeManager->getGroup($website->getDefaultGroupId());
+ $currentDate = $this->localeDate->scopeDate($storeGroup->getDefaultStoreId(), null, true);
+ $previousDate = (clone $currentDate)->modify('-1 day');
+ $previousDate->setTime(23, 59, 59);
+ $nextDate = (clone $currentDate)->modify('+1 day');
+ $nextDate->setTime(0, 0, 0);
+
while ($ruleData = $productsStmt->fetch()) {
$ruleProductId = $ruleData['product_id'];
$productKey = $ruleProductId .
@@ -100,12 +105,11 @@ public function execute(
}
}
- $ruleData['from_time'] = $this->roundTime($ruleData['from_time']);
- $ruleData['to_time'] = $this->roundTime($ruleData['to_time']);
/**
* Build prices for each day
*/
- for ($time = $fromDate; $time <= $toDate; $time += IndexBuilder::SECONDS_IN_DAY) {
+ foreach ([$previousDate, $currentDate, $nextDate] as $date) {
+ $time = $date->getTimestamp();
if (($ruleData['from_time'] == 0 ||
$time >= $ruleData['from_time']) && ($ruleData['to_time'] == 0 ||
$time <= $ruleData['to_time'])
@@ -118,7 +122,7 @@ public function execute(
if (!isset($dayPrices[$priceKey])) {
$dayPrices[$priceKey] = [
- 'rule_date' => $time,
+ 'rule_date' => $date,
'website_id' => $ruleData['website_id'],
'customer_group_id' => $ruleData['customer_group_id'],
'product_id' => $ruleProductId,
@@ -151,18 +155,7 @@ public function execute(
}
$this->pricesPersistor->execute($dayPrices, $useAdditionalTable);
}
- return true;
- }
- /**
- * @param int $timeStamp
- * @return int
- */
- private function roundTime($timeStamp)
- {
- if (is_numeric($timeStamp) && $timeStamp != 0) {
- $timeStamp = $this->dateTime->timestamp($this->dateTime->date('Y-m-d 00:00:00', $timeStamp));
- }
- return $timeStamp;
+ return true;
}
}
diff --git a/app/code/Magento/CatalogRule/Model/ResourceModel/Product/CollectionProcessor.php b/app/code/Magento/CatalogRule/Model/ResourceModel/Product/CollectionProcessor.php
index 0ea31f5dbafb2..1fd6f0cbc986f 100644
--- a/app/code/Magento/CatalogRule/Model/ResourceModel/Product/CollectionProcessor.php
+++ b/app/code/Magento/CatalogRule/Model/ResourceModel/Product/CollectionProcessor.php
@@ -4,6 +4,8 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
+declare(strict_types=1);
+
namespace Magento\CatalogRule\Model\ResourceModel\Product;
use Magento\Catalog\Model\ResourceModel\Product\Collection as ProductCollection;
@@ -11,6 +13,8 @@
/**
* Add catalog rule prices to collection
+ *
+ * @SuppressWarnings(PHPMD.CookieAndSessionMisuse)
*/
class CollectionProcessor
{
@@ -61,6 +65,8 @@ public function __construct(
}
/**
+ * Join prices to collection
+ *
* @param ProductCollection $productCollection
* @param string $joinColumn
* @return ProductCollection
@@ -73,18 +79,21 @@ public function addPriceData(ProductCollection $productCollection, $joinColumn =
$productCollection->getSelect()
->joinLeft(
['catalog_rule' => $this->resource->getTableName('catalogrule_product_price')],
- implode(' AND ', [
- 'catalog_rule.product_id = ' . $connection->quoteIdentifier($joinColumn),
- $connection->quoteInto('catalog_rule.website_id = ?', $store->getWebsiteId()),
- $connection->quoteInto(
- 'catalog_rule.customer_group_id = ?',
- $this->customerSession->getCustomerGroupId()
- ),
- $connection->quoteInto(
- 'catalog_rule.rule_date = ?',
- $this->dateTime->formatDate($this->localeDate->scopeDate($store->getId()), false)
- ),
- ]),
+ implode(
+ ' AND ',
+ [
+ 'catalog_rule.product_id = ' . $connection->quoteIdentifier($joinColumn),
+ $connection->quoteInto('catalog_rule.website_id = ?', $store->getWebsiteId()),
+ $connection->quoteInto(
+ 'catalog_rule.customer_group_id = ?',
+ $this->customerSession->getCustomerGroupId()
+ ),
+ $connection->quoteInto(
+ 'catalog_rule.rule_date = ?',
+ $this->dateTime->formatDate($this->localeDate->scopeDate($store->getId()), false)
+ ),
+ ]
+ ),
[CatalogRulePrice::PRICE_CODE => 'rule_price']
);
$productCollection->setFlag('catalog_rule_loaded', true);
diff --git a/app/code/Magento/CatalogRule/Model/ResourceModel/Product/LinkedProductSelectBuilderByCatalogRulePrice.php b/app/code/Magento/CatalogRule/Model/ResourceModel/Product/LinkedProductSelectBuilderByCatalogRulePrice.php
index 3f396cacd37da..48c463fc18b80 100644
--- a/app/code/Magento/CatalogRule/Model/ResourceModel/Product/LinkedProductSelectBuilderByCatalogRulePrice.php
+++ b/app/code/Magento/CatalogRule/Model/ResourceModel/Product/LinkedProductSelectBuilderByCatalogRulePrice.php
@@ -3,6 +3,8 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
+declare(strict_types=1);
+
namespace Magento\CatalogRule\Model\ResourceModel\Product;
use Magento\Catalog\Api\Data\ProductInterface;
@@ -11,6 +13,11 @@
use Magento\Framework\DB\Select;
use Magento\Catalog\Model\ResourceModel\Product\LinkedProductSelectBuilderInterface;
+/**
+ * Provide Select object for retrieve product id with minimal price
+ *
+ * @SuppressWarnings(PHPMD.CookieAndSessionMisuse)
+ */
class LinkedProductSelectBuilderByCatalogRulePrice implements LinkedProductSelectBuilderInterface
{
/**
@@ -77,7 +84,7 @@ public function __construct(
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function build($productId)
{
diff --git a/app/code/Magento/CatalogRule/Model/Rule.php b/app/code/Magento/CatalogRule/Model/Rule.php
index d927d6f4d0c82..f2e8e54d34665 100644
--- a/app/code/Magento/CatalogRule/Model/Rule.php
+++ b/app/code/Magento/CatalogRule/Model/Rule.php
@@ -46,6 +46,7 @@
* @SuppressWarnings(PHPMD.TooManyFields)
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
* @SuppressWarnings(PHPMD.ExcessivePublicCount)
+ * @SuppressWarnings(PHPMD.CookieAndSessionMisuse)
*/
class Rule extends \Magento\Rule\Model\AbstractModel implements RuleInterface, IdentityInterface
{
@@ -433,7 +434,7 @@ protected function _getWebsitesMap()
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function validateData(DataObject $dataObject)
{
@@ -582,12 +583,16 @@ protected function _invalidateCache()
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*
* @return $this
*/
public function afterSave()
{
+ if (!$this->getIsActive()) {
+ return parent::afterSave();
+ }
+
if ($this->isObjectNew() && !$this->_ruleProductProcessor->isIndexerScheduled()) {
$productIds = $this->getMatchingProductIds();
if (!empty($productIds) && is_array($productIds)) {
@@ -606,14 +611,19 @@ public function afterSave()
*/
public function reindex()
{
- $productIds = $this->_productIds ? array_keys(array_filter($this->_productIds, function (array $data) {
- return array_filter($data);
- })) : [];
+ $productIds = $this->_productIds ? array_keys(
+ array_filter(
+ $this->_productIds,
+ function (array $data) {
+ return array_filter($data);
+ }
+ )
+ ) : [];
$this->_ruleProductProcessor->reindexList($productIds);
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*
* @return $this
*/
@@ -643,6 +653,7 @@ public function isRuleBehaviorChanged()
/**
* Get array with data differences
+ *
* @param array $array1
* @param array $array2
*
@@ -664,6 +675,8 @@ protected function dataDiff($array1, $array2)
}
/**
+ * Getter for conditions field set ID
+ *
* @param string $formName
* @return string
*/
@@ -672,10 +685,8 @@ public function getConditionsFieldSetId($formName = '')
return $formName . 'rule_conditions_fieldset_' . $this->getId();
}
- //@codeCoverageIgnoreStart
-
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function getRuleId()
{
@@ -683,7 +694,7 @@ public function getRuleId()
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function setRuleId($ruleId)
{
@@ -691,7 +702,7 @@ public function setRuleId($ruleId)
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function getName()
{
@@ -699,7 +710,7 @@ public function getName()
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function setName($name)
{
@@ -707,7 +718,7 @@ public function setName($name)
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function getDescription()
{
@@ -715,7 +726,7 @@ public function getDescription()
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function setDescription($description)
{
@@ -723,7 +734,7 @@ public function setDescription($description)
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function getIsActive()
{
@@ -731,7 +742,7 @@ public function getIsActive()
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function setIsActive($isActive)
{
@@ -739,7 +750,7 @@ public function setIsActive($isActive)
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function getRuleCondition()
{
@@ -747,7 +758,7 @@ public function getRuleCondition()
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function setRuleCondition($condition)
{
@@ -758,7 +769,7 @@ public function setRuleCondition($condition)
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function getStopRulesProcessing()
{
@@ -766,7 +777,7 @@ public function getStopRulesProcessing()
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function setStopRulesProcessing($isStopProcessing)
{
@@ -774,7 +785,7 @@ public function setStopRulesProcessing($isStopProcessing)
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function getSortOrder()
{
@@ -782,7 +793,7 @@ public function getSortOrder()
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function setSortOrder($sortOrder)
{
@@ -790,7 +801,7 @@ public function setSortOrder($sortOrder)
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function getSimpleAction()
{
@@ -798,7 +809,7 @@ public function getSimpleAction()
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function setSimpleAction($action)
{
@@ -806,7 +817,7 @@ public function setSimpleAction($action)
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function getDiscountAmount()
{
@@ -814,7 +825,7 @@ public function getDiscountAmount()
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function setDiscountAmount($amount)
{
@@ -822,6 +833,8 @@ public function setDiscountAmount($amount)
}
/**
+ * Get from date
+ *
* @return string
*/
public function getFromDate()
@@ -830,6 +843,8 @@ public function getFromDate()
}
/**
+ * Get to date
+ *
* @return string
*/
public function getToDate()
@@ -838,7 +853,7 @@ public function getToDate()
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*
* @return \Magento\CatalogRule\Api\Data\RuleExtensionInterface|null
*/
@@ -848,7 +863,7 @@ public function getExtensionAttributes()
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*
* @param \Magento\CatalogRule\Api\Data\RuleExtensionInterface $extensionAttributes
* @return $this
@@ -859,6 +874,8 @@ public function setExtensionAttributes(RuleExtensionInterface $extensionAttribut
}
/**
+ * Getter for the rule condition converter
+ *
* @return Data\Condition\Converter
* @deprecated 100.1.0
*/
@@ -871,8 +888,6 @@ private function getRuleConditionConverter()
return $this->ruleConditionConverter;
}
- //@codeCoverageIgnoreEnd
-
/**
* @inheritDoc
*/
diff --git a/app/code/Magento/CatalogRule/Observer/PrepareCatalogProductCollectionPricesObserver.php b/app/code/Magento/CatalogRule/Observer/PrepareCatalogProductCollectionPricesObserver.php
index 75a208e87100b..bf0c85e671dd7 100644
--- a/app/code/Magento/CatalogRule/Observer/PrepareCatalogProductCollectionPricesObserver.php
+++ b/app/code/Magento/CatalogRule/Observer/PrepareCatalogProductCollectionPricesObserver.php
@@ -7,19 +7,23 @@
/**
* Catalog Price rules observer model
*/
+declare(strict_types=1);
+
namespace Magento\CatalogRule\Observer;
use Magento\Catalog\Model\Product;
-use Magento\CatalogRule\Model\Rule;
+use Magento\Catalog\Model\ResourceModel\Product\Collection as ProductCollection;
use Magento\Store\Model\StoreManagerInterface;
use Magento\Framework\Stdlib\DateTime\TimezoneInterface;
use Magento\Customer\Model\Session as CustomerModelSession;
-use Magento\Framework\Event\Observer as EventObserver;
use Magento\Customer\Api\GroupManagementInterface;
use Magento\Framework\Event\ObserverInterface;
/**
+ * Observer for applying catalog rules on product collection
+ *
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
+ * @SuppressWarnings(PHPMD.CookieAndSessionMisuse)
*/
class PrepareCatalogProductCollectionPricesObserver implements ObserverInterface
{
@@ -85,7 +89,7 @@ public function __construct(
*/
public function execute(\Magento\Framework\Event\Observer $observer)
{
- /* @var $collection ProductCollection */
+ /** @var ProductCollection $collection */
$collection = $observer->getEvent()->getCollection();
$store = $this->storeManager->getStore($observer->getEvent()->getStoreId());
$websiteId = $store->getWebsiteId();
diff --git a/app/code/Magento/CatalogRule/Observer/ProcessAdminFinalPriceObserver.php b/app/code/Magento/CatalogRule/Observer/ProcessAdminFinalPriceObserver.php
index 2dce2cb2f5b1c..89ed519cfb8c8 100644
--- a/app/code/Magento/CatalogRule/Observer/ProcessAdminFinalPriceObserver.php
+++ b/app/code/Magento/CatalogRule/Observer/ProcessAdminFinalPriceObserver.php
@@ -3,20 +3,17 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
+declare(strict_types=1);
-/**
- * Catalog Price rules observer model
- */
namespace Magento\CatalogRule\Observer;
-use Magento\Catalog\Model\Product;
-use Magento\CatalogRule\Model\Rule;
use Magento\Framework\Stdlib\DateTime\TimezoneInterface;
-use Magento\Customer\Model\Session as CustomerModelSession;
-use Magento\Framework\Event\Observer as EventObserver;
use Magento\Framework\Registry;
use Magento\Framework\Event\ObserverInterface;
+/**
+ * Observer for applying catalog rules on product for admin area
+ */
class ProcessAdminFinalPriceObserver implements ObserverInterface
{
/**
diff --git a/app/code/Magento/CatalogRule/Observer/ProcessFrontFinalPriceObserver.php b/app/code/Magento/CatalogRule/Observer/ProcessFrontFinalPriceObserver.php
index 2d4042f691502..075fe9e51f7dc 100644
--- a/app/code/Magento/CatalogRule/Observer/ProcessFrontFinalPriceObserver.php
+++ b/app/code/Magento/CatalogRule/Observer/ProcessFrontFinalPriceObserver.php
@@ -3,20 +3,20 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
+declare(strict_types=1);
-/**
- * Catalog Price rules observer model
- */
namespace Magento\CatalogRule\Observer;
use Magento\Framework\Event\ObserverInterface;
-use Magento\Catalog\Model\Product;
-use Magento\CatalogRule\Model\Rule;
use Magento\Store\Model\StoreManagerInterface;
use Magento\Framework\Stdlib\DateTime\TimezoneInterface;
use Magento\Customer\Model\Session as CustomerModelSession;
-use Magento\Framework\Event\Observer as EventObserver;
+/**
+ * Observer for applying catalog rules on product for frontend area
+ *
+ * @SuppressWarnings(PHPMD.CookieAndSessionMisuse)
+ */
class ProcessFrontFinalPriceObserver implements ObserverInterface
{
/**
diff --git a/app/code/Magento/CatalogRule/Pricing/Price/CatalogRulePrice.php b/app/code/Magento/CatalogRule/Pricing/Price/CatalogRulePrice.php
index c71b51317fd59..7cbbc547571ab 100644
--- a/app/code/Magento/CatalogRule/Pricing/Price/CatalogRulePrice.php
+++ b/app/code/Magento/CatalogRule/Pricing/Price/CatalogRulePrice.php
@@ -3,22 +3,24 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
+declare(strict_types=1);
namespace Magento\CatalogRule\Pricing\Price;
use Magento\Catalog\Model\Product;
use Magento\CatalogRule\Model\ResourceModel\Rule;
-use Magento\CatalogRule\Model\ResourceModel\RuleFactory;
use Magento\Customer\Model\Session;
-use Magento\Framework\App\ObjectManager;
use Magento\Framework\Pricing\Adjustment\Calculator;
use Magento\Framework\Pricing\Price\AbstractPrice;
use Magento\Framework\Pricing\Price\BasePriceProviderInterface;
+use Magento\Framework\Pricing\PriceCurrencyInterface;
use Magento\Framework\Stdlib\DateTime\TimezoneInterface;
-use Magento\Store\Model\StoreManager;
+use Magento\Store\Model\StoreManagerInterface;
/**
* Class CatalogRulePrice
+ *
+ * @SuppressWarnings(PHPMD.CookieAndSessionMisuse)
*/
class CatalogRulePrice extends AbstractPrice implements BasePriceProviderInterface
{
@@ -28,28 +30,22 @@ class CatalogRulePrice extends AbstractPrice implements BasePriceProviderInterfa
const PRICE_CODE = 'catalog_rule_price';
/**
- * @var \Magento\Framework\Stdlib\DateTime\TimezoneInterface
+ * @var TimezoneInterface
*/
protected $dateTime;
/**
- * @var \Magento\Store\Model\StoreManager
+ * @var StoreManagerInterface
*/
protected $storeManager;
/**
- * @var \Magento\Customer\Model\Session
+ * @var Session
*/
protected $customerSession;
/**
- * @var \Magento\CatalogRule\Model\ResourceModel\RuleFactory
- * @deprecated 100.1.1
- */
- protected $resourceRuleFactory;
-
- /**
- * @var \Magento\CatalogRule\Model\ResourceModel\Rule
+ * @var Rule
*/
private $ruleResource;
@@ -57,27 +53,27 @@ class CatalogRulePrice extends AbstractPrice implements BasePriceProviderInterfa
* @param Product $saleableItem
* @param float $quantity
* @param Calculator $calculator
- * @param RuleFactory $catalogRuleResourceFactory
+ * @param PriceCurrencyInterface $priceCurrency
* @param TimezoneInterface $dateTime
- * @param StoreManager $storeManager
+ * @param StoreManagerInterface $storeManager
* @param Session $customerSession
- * @param \Magento\Framework\Pricing\PriceCurrencyInterface $priceCurrency
+ * @param Rule $ruleResource
*/
public function __construct(
Product $saleableItem,
$quantity,
Calculator $calculator,
- \Magento\Framework\Pricing\PriceCurrencyInterface $priceCurrency,
+ PriceCurrencyInterface $priceCurrency,
TimezoneInterface $dateTime,
- StoreManager $storeManager,
+ StoreManagerInterface $storeManager,
Session $customerSession,
- RuleFactory $catalogRuleResourceFactory
+ Rule $ruleResource
) {
parent::__construct($saleableItem, $quantity, $calculator, $priceCurrency);
$this->dateTime = $dateTime;
$this->storeManager = $storeManager;
$this->customerSession = $customerSession;
- $this->resourceRuleFactory = $catalogRuleResourceFactory;
+ $this->ruleResource = $ruleResource;
}
/**
@@ -91,13 +87,12 @@ public function getValue()
if ($this->product->hasData(self::PRICE_CODE)) {
$this->value = (float)$this->product->getData(self::PRICE_CODE) ?: false;
} else {
- $this->value = $this->getRuleResource()
- ->getRulePrice(
- $this->dateTime->scopeDate($this->storeManager->getStore()->getId()),
- $this->storeManager->getStore()->getWebsiteId(),
- $this->customerSession->getCustomerGroupId(),
- $this->product->getId()
- );
+ $this->value = $this->ruleResource->getRulePrice(
+ $this->dateTime->scopeDate($this->storeManager->getStore()->getId()),
+ $this->storeManager->getStore()->getWebsiteId(),
+ $this->customerSession->getCustomerGroupId(),
+ $this->product->getId()
+ );
$this->value = $this->value ? (float)$this->value : false;
}
if ($this->value) {
@@ -107,17 +102,4 @@ public function getValue()
return $this->value;
}
-
- /**
- * @return Rule
- * @deprecated 100.1.1
- */
- private function getRuleResource()
- {
- if (null === $this->ruleResource) {
- $this->ruleResource = ObjectManager::getInstance()->get(Rule::class);
- }
-
- return $this->ruleResource;
- }
}
diff --git a/app/code/Magento/CatalogRule/Test/Mftf/ActionGroup/AdminCreateNewCatalogPriceRuleActionGroup.xml b/app/code/Magento/CatalogRule/Test/Mftf/ActionGroup/AdminCreateNewCatalogPriceRuleActionGroup.xml
index 4599e325e39cb..00dcb68089b73 100644
--- a/app/code/Magento/CatalogRule/Test/Mftf/ActionGroup/AdminCreateNewCatalogPriceRuleActionGroup.xml
+++ b/app/code/Magento/CatalogRule/Test/Mftf/ActionGroup/AdminCreateNewCatalogPriceRuleActionGroup.xml
@@ -9,10 +9,14 @@
+
+ Goes to the Admin Catalog Price Rule creation page. Fills in the provided Catalog Rule details. Selects the provided Customer Group.
+
+
@@ -27,4 +31,4 @@
-
\ No newline at end of file
+
diff --git a/app/code/Magento/CatalogRule/Test/Mftf/ActionGroup/AdminDeleteCatalogRuleActionGroup.xml b/app/code/Magento/CatalogRule/Test/Mftf/ActionGroup/AdminDeleteCatalogRuleActionGroup.xml
index ea2d6821917e3..5e870dfba3b43 100644
--- a/app/code/Magento/CatalogRule/Test/Mftf/ActionGroup/AdminDeleteCatalogRuleActionGroup.xml
+++ b/app/code/Magento/CatalogRule/Test/Mftf/ActionGroup/AdminDeleteCatalogRuleActionGroup.xml
@@ -9,10 +9,14 @@
+
+ Clicks on the Delete button on a Admin Catalog Price Rule edit page. Clicks on Ok. Validates that the provided Success Message is present and correct.
+
+
-
\ No newline at end of file
+
diff --git a/app/code/Magento/CatalogRule/Test/Mftf/ActionGroup/AdminOpenCatalogPriceRulePageActionGroup.xml b/app/code/Magento/CatalogRule/Test/Mftf/ActionGroup/AdminOpenCatalogPriceRulePageActionGroup.xml
new file mode 100644
index 0000000000000..2b1943e51e398
--- /dev/null
+++ b/app/code/Magento/CatalogRule/Test/Mftf/ActionGroup/AdminOpenCatalogPriceRulePageActionGroup.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/CatalogRule/Test/Mftf/ActionGroup/AdminOpenNewCatalogPriceRuleFormPageActionGroup.xml b/app/code/Magento/CatalogRule/Test/Mftf/ActionGroup/AdminOpenNewCatalogPriceRuleFormPageActionGroup.xml
index 8651a17cb969e..c978d18f76d6c 100644
--- a/app/code/Magento/CatalogRule/Test/Mftf/ActionGroup/AdminOpenNewCatalogPriceRuleFormPageActionGroup.xml
+++ b/app/code/Magento/CatalogRule/Test/Mftf/ActionGroup/AdminOpenNewCatalogPriceRuleFormPageActionGroup.xml
@@ -5,10 +5,15 @@
* See COPYING.txt for license details.
*/
-->
+
-
-
+
+ Goes to the create Catalog Price Rule page.
+
+
+
+
diff --git a/app/code/Magento/CatalogRule/Test/Mftf/ActionGroup/AdminSaveAndApplyRulesActionGroup.xml b/app/code/Magento/CatalogRule/Test/Mftf/ActionGroup/AdminSaveAndApplyRulesActionGroup.xml
index 2584b8b36b769..82e7a6979e34b 100644
--- a/app/code/Magento/CatalogRule/Test/Mftf/ActionGroup/AdminSaveAndApplyRulesActionGroup.xml
+++ b/app/code/Magento/CatalogRule/Test/Mftf/ActionGroup/AdminSaveAndApplyRulesActionGroup.xml
@@ -9,6 +9,10 @@
+
+ Clicks Save on a Admin Catalog Price Rule creation/edit page. Validates that the Success Message is present. Clicks Apply Rules. Validates that the Success Message is present.
+
+
@@ -18,4 +22,4 @@
-
\ No newline at end of file
+
diff --git a/app/code/Magento/CatalogRule/Test/Mftf/ActionGroup/AdminSearchCatalogRuleInGridActionGroup.xml b/app/code/Magento/CatalogRule/Test/Mftf/ActionGroup/AdminSearchCatalogRuleInGridActionGroup.xml
index c7d5d853642c0..6529889180599 100644
--- a/app/code/Magento/CatalogRule/Test/Mftf/ActionGroup/AdminSearchCatalogRuleInGridActionGroup.xml
+++ b/app/code/Magento/CatalogRule/Test/Mftf/ActionGroup/AdminSearchCatalogRuleInGridActionGroup.xml
@@ -9,9 +9,13 @@
+
+ Goes to the Admin Catalog Price Rules grid page. Searches the grid for the provided Catalog Price Rule name.
+
+
@@ -20,4 +24,4 @@
-
\ No newline at end of file
+
diff --git a/app/code/Magento/CatalogRule/Test/Mftf/ActionGroup/AdminSelectCatalogRuleFromGridActionGroup.xml b/app/code/Magento/CatalogRule/Test/Mftf/ActionGroup/AdminSelectCatalogRuleFromGridActionGroup.xml
index f0e927ea84048..593296c9fe0cc 100644
--- a/app/code/Magento/CatalogRule/Test/Mftf/ActionGroup/AdminSelectCatalogRuleFromGridActionGroup.xml
+++ b/app/code/Magento/CatalogRule/Test/Mftf/ActionGroup/AdminSelectCatalogRuleFromGridActionGroup.xml
@@ -9,10 +9,14 @@
+
+ Clicks on the Admin Catalog Price Rule row that contains the provided Catalog Price Rule name.
+
+
-
\ No newline at end of file
+
diff --git a/app/code/Magento/CatalogRule/Test/Mftf/ActionGroup/AssertCatalogPriceRuleFormActionGroup.xml b/app/code/Magento/CatalogRule/Test/Mftf/ActionGroup/AssertCatalogPriceRuleFormActionGroup.xml
index 330c2ad7e15f6..77fe0f50653c7 100644
--- a/app/code/Magento/CatalogRule/Test/Mftf/ActionGroup/AssertCatalogPriceRuleFormActionGroup.xml
+++ b/app/code/Magento/CatalogRule/Test/Mftf/ActionGroup/AssertCatalogPriceRuleFormActionGroup.xml
@@ -9,12 +9,16 @@
+
+ Validates that the provided Catalog Rule, Status, Websites and Customer Group details are present and correct on a Admin Catalog Price Rule creation/edit page.
+
-
+
+
@@ -26,4 +30,4 @@
-
\ No newline at end of file
+
diff --git a/app/code/Magento/CatalogRule/Test/Mftf/ActionGroup/AssertCatalogRuleInGridActionGroup.xml b/app/code/Magento/CatalogRule/Test/Mftf/ActionGroup/AssertCatalogRuleInGridActionGroup.xml
index 8be41c3b07af6..f9207bb62969e 100644
--- a/app/code/Magento/CatalogRule/Test/Mftf/ActionGroup/AssertCatalogRuleInGridActionGroup.xml
+++ b/app/code/Magento/CatalogRule/Test/Mftf/ActionGroup/AssertCatalogRuleInGridActionGroup.xml
@@ -9,15 +9,19 @@
+
+ Validates that the provided Catalog Rule Name, Status, Websites and Catalog Rule ID are present in the 1st row of the Admin Catalog Price Rule grid.
+
+
-
\ No newline at end of file
+
diff --git a/app/code/Magento/CatalogRule/Test/Mftf/ActionGroup/AssertCustomerGroupNotOnCatalogPriceRuleFormActionGroup.xml b/app/code/Magento/CatalogRule/Test/Mftf/ActionGroup/AssertCustomerGroupNotOnCatalogPriceRuleFormActionGroup.xml
index 93a2a8a610951..3fd39ed5b49fd 100644
--- a/app/code/Magento/CatalogRule/Test/Mftf/ActionGroup/AssertCustomerGroupNotOnCatalogPriceRuleFormActionGroup.xml
+++ b/app/code/Magento/CatalogRule/Test/Mftf/ActionGroup/AssertCustomerGroupNotOnCatalogPriceRuleFormActionGroup.xml
@@ -5,13 +5,18 @@
* See COPYING.txt for license details.
*/
-->
+
+
+ Validate that the provided Customer Group is not present on the Catalog Price Rules creation/edit page.
+
-
+
-
+
+
customerGroups
{{customerGroup.code}}
diff --git a/app/code/Magento/CatalogRule/Test/Mftf/ActionGroup/CatalogPriceRuleActionGroup.xml b/app/code/Magento/CatalogRule/Test/Mftf/ActionGroup/CatalogPriceRuleActionGroup.xml
index de0d2baee2dd1..a7500858fc94e 100644
--- a/app/code/Magento/CatalogRule/Test/Mftf/ActionGroup/CatalogPriceRuleActionGroup.xml
+++ b/app/code/Magento/CatalogRule/Test/Mftf/ActionGroup/CatalogPriceRuleActionGroup.xml
@@ -10,6 +10,9 @@
xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd">
+
+ Goes to the Catalog Price Rule grid. Clicks on Add. Fills in the provided Catalog Rule details.
+
@@ -38,17 +41,19 @@
+
+ Clicks on the Add button. Fills Rule Name, Description, Website and Discount Value.
+
-
-
-
+
+
+
-
@@ -74,6 +79,9 @@
+
+ Add Conditional Requirements to a Catalog Price Rule from the creation/edit page.
+
@@ -93,6 +101,10 @@
+
+ Goes to the Catalog Price Rule grid page. Clicks on Apply Rules. Validates that the Success Message is present and correct.
+
+
@@ -101,9 +113,13 @@
+
+ EXTENDS: newCatalogPriceRuleByUI. Add a Catalog Price Rule Condition based on the provided SKU.
+
+
@@ -116,9 +132,13 @@
+
+ EXTENDS: newCatalogPriceRuleByUI. Add a Catalog Price Rule Condition based on the provided Category ID.
+
+
@@ -130,10 +150,18 @@
+
+ Selects the 'General' Customer Group for a Catalog Price Rule on the creation/edit page.
+
+
+
+ Selects the 'NOT LOGGED IN' Customer Group for a Catalog Price Rule on the creation/edit page.
+
+
diff --git a/app/code/Magento/CatalogRule/Test/Mftf/ActionGroup/CatalogSelectCustomerGroupActionGroup.xml b/app/code/Magento/CatalogRule/Test/Mftf/ActionGroup/CatalogSelectCustomerGroupActionGroup.xml
index da961abac304b..cd2f7a207a3e2 100644
--- a/app/code/Magento/CatalogRule/Test/Mftf/ActionGroup/CatalogSelectCustomerGroupActionGroup.xml
+++ b/app/code/Magento/CatalogRule/Test/Mftf/ActionGroup/CatalogSelectCustomerGroupActionGroup.xml
@@ -9,9 +9,13 @@
+
+ Selects the provided Customer Group Name on the Admin Catalog Price Rule creation/edit page.
+
+
diff --git a/app/code/Magento/CatalogRule/Test/Mftf/ActionGroup/CreateCatalogPriceRuleConditionWithAttributeAndOptionActionGroup.xml b/app/code/Magento/CatalogRule/Test/Mftf/ActionGroup/CreateCatalogPriceRuleConditionWithAttributeAndOptionActionGroup.xml
index 41323b8f3b4dc..bdc09c56353df 100644
--- a/app/code/Magento/CatalogRule/Test/Mftf/ActionGroup/CreateCatalogPriceRuleConditionWithAttributeAndOptionActionGroup.xml
+++ b/app/code/Magento/CatalogRule/Test/Mftf/ActionGroup/CreateCatalogPriceRuleConditionWithAttributeAndOptionActionGroup.xml
@@ -9,12 +9,16 @@
+
+ Adds the provided Attribute Name, Select Value, Index A and Index B details to the 'Conditions' section on the Admin Catalog Price Rule creation/edit page.
+
+
diff --git a/app/code/Magento/CatalogRule/Test/Mftf/ActionGroup/SaveAndApplyCatalogPriceRuleActionGroup.xml b/app/code/Magento/CatalogRule/Test/Mftf/ActionGroup/SaveAndApplyCatalogPriceRuleActionGroup.xml
index bc75414e0de21..c885520c92976 100644
--- a/app/code/Magento/CatalogRule/Test/Mftf/ActionGroup/SaveAndApplyCatalogPriceRuleActionGroup.xml
+++ b/app/code/Magento/CatalogRule/Test/Mftf/ActionGroup/SaveAndApplyCatalogPriceRuleActionGroup.xml
@@ -9,8 +9,12 @@
+
+ Clicks on Save and Apply. Validates that the Success Message is present and correct on the Admin Catalog Price Rule creation/edit page.
+
+
-
\ No newline at end of file
+
diff --git a/app/code/Magento/CatalogRule/Test/Mftf/Data/CatalogRuleData.xml b/app/code/Magento/CatalogRule/Test/Mftf/Data/CatalogRuleData.xml
index 5c6ea970d3b7a..75a7484324576 100644
--- a/app/code/Magento/CatalogRule/Test/Mftf/Data/CatalogRuleData.xml
+++ b/app/code/Magento/CatalogRule/Test/Mftf/Data/CatalogRuleData.xml
@@ -157,4 +157,20 @@
by_percent
10
+
+ Catalog Price Rule1
+ Description for Cart Price Rule
+ Yes
+ Main Website
+ NOT LOGGED IN
+ No Coupon
+ Percent of product price discount
+ 50
+ 0
+ 0
+ 0
+ For matching items only
+ Free Shipping in conditions
+ Free Shipping in conditions
+
diff --git a/app/code/Magento/CatalogRule/Test/Mftf/Section/AdminNewCatalogPriceRuleSection.xml b/app/code/Magento/CatalogRule/Test/Mftf/Section/AdminNewCatalogPriceRuleSection.xml
index 5cb69cb6d7f45..ba0493d8e995b 100644
--- a/app/code/Magento/CatalogRule/Test/Mftf/Section/AdminNewCatalogPriceRuleSection.xml
+++ b/app/code/Magento/CatalogRule/Test/Mftf/Section/AdminNewCatalogPriceRuleSection.xml
@@ -17,6 +17,7 @@
+
@@ -26,6 +27,7 @@
+
diff --git a/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminApplyCatalogRuleByCategoryTest.xml b/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminApplyCatalogRuleByCategoryTest.xml
index 875a7842f21ff..741da96179b8c 100644
--- a/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminApplyCatalogRuleByCategoryTest.xml
+++ b/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminApplyCatalogRuleByCategoryTest.xml
@@ -16,9 +16,6 @@
-
-
-
diff --git a/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminApplyCatalogRuleForConfigurableProductWithSpecialPricesTest.xml b/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminApplyCatalogRuleForConfigurableProductWithSpecialPricesTest.xml
new file mode 100644
index 0000000000000..4df08fcca696b
--- /dev/null
+++ b/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminApplyCatalogRuleForConfigurableProductWithSpecialPricesTest.xml
@@ -0,0 +1,152 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminCreateCatalogPriceRuleTest.xml b/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminCreateCatalogPriceRuleTest.xml
index 10db68e9053d7..befe0b0ce7f98 100644
--- a/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminCreateCatalogPriceRuleTest.xml
+++ b/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminCreateCatalogPriceRuleTest.xml
@@ -17,9 +17,6 @@
-
-
-
@@ -77,9 +74,6 @@
-
-
-
@@ -103,9 +97,6 @@
-
-
-
@@ -129,9 +120,6 @@
-
-
-
@@ -155,9 +143,6 @@
-
-
-
diff --git a/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminDeleteCatalogPriceRuleTest.xml b/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminDeleteCatalogPriceRuleTest.xml
index 06f3682aedd85..d3546d06492be 100644
--- a/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminDeleteCatalogPriceRuleTest.xml
+++ b/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminDeleteCatalogPriceRuleTest.xml
@@ -17,9 +17,6 @@
-
-
-
diff --git a/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminEnableAttributeIsUndefinedCatalogPriceRuleTest.xml b/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminEnableAttributeIsUndefinedCatalogPriceRuleTest.xml
index 0ff7e0cd85217..54bf243c4cde6 100644
--- a/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminEnableAttributeIsUndefinedCatalogPriceRuleTest.xml
+++ b/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminEnableAttributeIsUndefinedCatalogPriceRuleTest.xml
@@ -63,6 +63,8 @@
+
+
@@ -78,8 +80,10 @@
+
+
@@ -119,8 +123,10 @@
+
+
diff --git a/app/code/Magento/CatalogRule/Test/Mftf/Test/ApplyCatalogPriceRuleByProductAttributeTest.xml b/app/code/Magento/CatalogRule/Test/Mftf/Test/ApplyCatalogPriceRuleByProductAttributeTest.xml
new file mode 100644
index 0000000000000..b7a231df5045d
--- /dev/null
+++ b/app/code/Magento/CatalogRule/Test/Mftf/Test/ApplyCatalogPriceRuleByProductAttributeTest.xml
@@ -0,0 +1,259 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 40.00
+
+
+
+ 40.00
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 60.00
+
+
+
+
+ 60.00
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/CatalogRule/Test/Mftf/Test/ApplyCatalogRuleForSimpleProductWithCustomOptionsTest.xml b/app/code/Magento/CatalogRule/Test/Mftf/Test/ApplyCatalogRuleForSimpleProductWithCustomOptionsTest.xml
index 055eacaeb2b78..5b7e722c92a02 100644
--- a/app/code/Magento/CatalogRule/Test/Mftf/Test/ApplyCatalogRuleForSimpleProductWithCustomOptionsTest.xml
+++ b/app/code/Magento/CatalogRule/Test/Mftf/Test/ApplyCatalogRuleForSimpleProductWithCustomOptionsTest.xml
@@ -17,9 +17,6 @@
-
-
-
diff --git a/app/code/Magento/CatalogRule/Test/Mftf/Test/CatalogPriceRuleAndCustomerGroupMembershipArePersistedUnderLongTermCookieTest.xml b/app/code/Magento/CatalogRule/Test/Mftf/Test/CatalogPriceRuleAndCustomerGroupMembershipArePersistedUnderLongTermCookieTest.xml
index e3eac52a8d40b..738f193fcc511 100644
--- a/app/code/Magento/CatalogRule/Test/Mftf/Test/CatalogPriceRuleAndCustomerGroupMembershipArePersistedUnderLongTermCookieTest.xml
+++ b/app/code/Magento/CatalogRule/Test/Mftf/Test/CatalogPriceRuleAndCustomerGroupMembershipArePersistedUnderLongTermCookieTest.xml
@@ -17,9 +17,6 @@
-
-
-
diff --git a/app/code/Magento/CatalogRule/Test/Mftf/Test/StorefrontInactiveCatalogRuleTest.xml b/app/code/Magento/CatalogRule/Test/Mftf/Test/StorefrontInactiveCatalogRuleTest.xml
index 22fcf6870c19d..b486654fe9acf 100644
--- a/app/code/Magento/CatalogRule/Test/Mftf/Test/StorefrontInactiveCatalogRuleTest.xml
+++ b/app/code/Magento/CatalogRule/Test/Mftf/Test/StorefrontInactiveCatalogRuleTest.xml
@@ -17,9 +17,6 @@
-
-
-
diff --git a/app/code/Magento/CatalogRule/Test/Unit/Model/Indexer/IndexBuilderTest.php b/app/code/Magento/CatalogRule/Test/Unit/Model/Indexer/IndexBuilderTest.php
index 920dcb8e1ede5..78668366bccdc 100644
--- a/app/code/Magento/CatalogRule/Test/Unit/Model/Indexer/IndexBuilderTest.php
+++ b/app/code/Magento/CatalogRule/Test/Unit/Model/Indexer/IndexBuilderTest.php
@@ -252,7 +252,7 @@ public function testUpdateCatalogRuleGroupWebsiteData()
);
$resourceMock->expects($this->any())
->method('getMainTable')
- ->will($this->returnValue('catalog_product_entity_tear_price'));
+ ->will($this->returnValue('catalog_product_entity_tier_price'));
$backendModelMock->expects($this->any())
->method('getResource')
->will($this->returnValue($resourceMock));
diff --git a/app/code/Magento/CatalogRule/Test/Unit/Model/Indexer/ReindexRuleProductPriceTest.php b/app/code/Magento/CatalogRule/Test/Unit/Model/Indexer/ReindexRuleProductPriceTest.php
index 6d7f0673ed281..5f63283df6760 100644
--- a/app/code/Magento/CatalogRule/Test/Unit/Model/Indexer/ReindexRuleProductPriceTest.php
+++ b/app/code/Magento/CatalogRule/Test/Unit/Model/Indexer/ReindexRuleProductPriceTest.php
@@ -6,65 +6,62 @@
namespace Magento\CatalogRule\Test\Unit\Model\Indexer;
-use Magento\CatalogRule\Model\Indexer\IndexBuilder;
+use Magento\Catalog\Model\Product;
+use Magento\CatalogRule\Model\Indexer\ProductPriceCalculator;
+use Magento\CatalogRule\Model\Indexer\ReindexRuleProductPrice;
+use Magento\CatalogRule\Model\Indexer\RuleProductPricesPersistor;
+use Magento\CatalogRule\Model\Indexer\RuleProductsSelectBuilder;
+use Magento\Framework\Stdlib\DateTime\TimezoneInterface;
+use Magento\Store\Api\Data\GroupInterface;
+use Magento\Store\Api\Data\WebsiteInterface;
+use Magento\Store\Model\StoreManagerInterface;
+use PHPUnit\Framework\MockObject\MockObject;
class ReindexRuleProductPriceTest extends \PHPUnit\Framework\TestCase
{
/**
- * @var \Magento\CatalogRule\Model\Indexer\ReindexRuleProductPrice
+ * @var ReindexRuleProductPrice
*/
private $model;
/**
- * @var \Magento\Store\Model\StoreManagerInterface|\PHPUnit_Framework_MockObject_MockObject
+ * @var StoreManagerInterface|MockObject
*/
private $storeManagerMock;
/**
- * @var \Magento\CatalogRule\Model\Indexer\RuleProductsSelectBuilder|\PHPUnit_Framework_MockObject_MockObject
+ * @var RuleProductsSelectBuilder|MockObject
*/
private $ruleProductsSelectBuilderMock;
/**
- * @var \Magento\CatalogRule\Model\Indexer\ProductPriceCalculator|\PHPUnit_Framework_MockObject_MockObject
+ * @var ProductPriceCalculator|MockObject
*/
private $productPriceCalculatorMock;
/**
- * @var \Magento\Framework\Stdlib\DateTime\DateTime|\PHPUnit_Framework_MockObject_MockObject
+ * @var TimezoneInterface|MockObject
*/
- private $dateTimeMock;
+ private $localeDate;
/**
- * @var \Magento\CatalogRule\Model\Indexer\RuleProductPricesPersistor|\PHPUnit_Framework_MockObject_MockObject
+ * @var RuleProductPricesPersistor|MockObject
*/
private $pricesPersistorMock;
protected function setUp()
{
- $this->storeManagerMock = $this->getMockBuilder(\Magento\Store\Model\StoreManagerInterface::class)
- ->disableOriginalConstructor()
- ->getMock();
- $this->ruleProductsSelectBuilderMock =
- $this->getMockBuilder(\Magento\CatalogRule\Model\Indexer\RuleProductsSelectBuilder::class)
- ->disableOriginalConstructor()
- ->getMock();
- $this->productPriceCalculatorMock =
- $this->getMockBuilder(\Magento\CatalogRule\Model\Indexer\ProductPriceCalculator::class)
- ->disableOriginalConstructor()
- ->getMock();
- $this->dateTimeMock = $this->getMockBuilder(\Magento\Framework\Stdlib\DateTime\DateTime::class)
- ->disableOriginalConstructor()
- ->getMock();
- $this->pricesPersistorMock =
- $this->getMockBuilder(\Magento\CatalogRule\Model\Indexer\RuleProductPricesPersistor::class)
- ->disableOriginalConstructor()
- ->getMock();
- $this->model = new \Magento\CatalogRule\Model\Indexer\ReindexRuleProductPrice(
+ $this->storeManagerMock = $this->createMock(StoreManagerInterface::class);
+ $this->ruleProductsSelectBuilderMock = $this->createMock(RuleProductsSelectBuilder::class);
+ $this->productPriceCalculatorMock = $this->createMock(ProductPriceCalculator::class);
+ $this->localeDate = $this->createMock(TimezoneInterface::class);
+ $this->pricesPersistorMock = $this->createMock(RuleProductPricesPersistor::class);
+
+ $this->model = new ReindexRuleProductPrice(
$this->storeManagerMock,
$this->ruleProductsSelectBuilderMock,
$this->productPriceCalculatorMock,
- $this->dateTimeMock,
+ $this->localeDate,
$this->pricesPersistorMock
);
}
@@ -72,19 +69,32 @@ protected function setUp()
public function testExecute()
{
$websiteId = 234;
- $productMock = $this->getMockBuilder(\Magento\Catalog\Model\Product::class)
- ->disableOriginalConstructor()
- ->getMock();
-
- $websiteMock = $this->getMockBuilder(\Magento\Store\Api\Data\WebsiteInterface::class)
- ->disableOriginalConstructor()
- ->getMock();
- $websiteMock->expects($this->once())->method('getId')->willReturn($websiteId);
- $this->storeManagerMock->expects($this->once())->method('getWebsites')->willReturn([$websiteMock]);
-
- $statementMock = $this->getMockBuilder(\Zend_Db_Statement_Interface::class)
- ->disableOriginalConstructor()
- ->getMock();
+ $defaultGroupId = 11;
+ $defaultStoreId = 22;
+
+ $websiteMock = $this->createMock(WebsiteInterface::class);
+ $websiteMock->expects($this->once())
+ ->method('getId')
+ ->willReturn($websiteId);
+ $websiteMock->expects($this->once())
+ ->method('getDefaultGroupId')
+ ->willReturn($defaultGroupId);
+ $this->storeManagerMock->expects($this->once())
+ ->method('getWebsites')
+ ->willReturn([$websiteMock]);
+ $groupMock = $this->createMock(GroupInterface::class);
+ $groupMock->method('getId')
+ ->willReturn($defaultStoreId);
+ $groupMock->expects($this->once())
+ ->method('getDefaultStoreId')
+ ->willReturn($defaultStoreId);
+ $this->storeManagerMock->expects($this->once())
+ ->method('getGroup')
+ ->with($defaultGroupId)
+ ->willReturn($groupMock);
+
+ $productMock = $this->createMock(Product::class);
+ $statementMock = $this->createMock(\Zend_Db_Statement_Interface::class);
$this->ruleProductsSelectBuilderMock->expects($this->once())
->method('build')
->with($websiteId, $productMock, true)
@@ -99,29 +109,22 @@ public function testExecute()
'action_stop' => true
];
- $this->dateTimeMock->expects($this->at(0))
- ->method('date')
- ->with('Y-m-d 00:00:00', $ruleData['from_time'])
- ->willReturn($ruleData['from_time']);
- $this->dateTimeMock->expects($this->at(1))
- ->method('timestamp')
- ->with($ruleData['from_time'])
- ->willReturn($ruleData['from_time']);
-
- $this->dateTimeMock->expects($this->at(2))
- ->method('date')
- ->with('Y-m-d 00:00:00', $ruleData['to_time'])
- ->willReturn($ruleData['to_time']);
- $this->dateTimeMock->expects($this->at(3))
- ->method('timestamp')
- ->with($ruleData['to_time'])
- ->willReturn($ruleData['to_time']);
-
- $statementMock->expects($this->at(0))->method('fetch')->willReturn($ruleData);
- $statementMock->expects($this->at(1))->method('fetch')->willReturn(false);
-
- $this->productPriceCalculatorMock->expects($this->atLeastOnce())->method('calculate');
- $this->pricesPersistorMock->expects($this->once())->method('execute');
+ $this->localeDate->expects($this->once())
+ ->method('scopeDate')
+ ->with($defaultStoreId, null, true)
+ ->willReturn(new \DateTime());
+
+ $statementMock->expects($this->at(0))
+ ->method('fetch')
+ ->willReturn($ruleData);
+ $statementMock->expects($this->at(1))
+ ->method('fetch')
+ ->willReturn(false);
+
+ $this->productPriceCalculatorMock->expects($this->atLeastOnce())
+ ->method('calculate');
+ $this->pricesPersistorMock->expects($this->once())
+ ->method('execute');
$this->assertTrue($this->model->execute(1, $productMock, true));
}
diff --git a/app/code/Magento/CatalogRule/Test/Unit/Model/Indexer/ReindexRuleProductTest.php b/app/code/Magento/CatalogRule/Test/Unit/Model/Indexer/ReindexRuleProductTest.php
index 0dbbaee8d2871..a86ab736fb289 100644
--- a/app/code/Magento/CatalogRule/Test/Unit/Model/Indexer/ReindexRuleProductTest.php
+++ b/app/code/Magento/CatalogRule/Test/Unit/Model/Indexer/ReindexRuleProductTest.php
@@ -8,89 +8,96 @@
use Magento\Catalog\Model\ResourceModel\Indexer\ActiveTableSwitcher;
use Magento\CatalogRule\Model\Indexer\IndexerTableSwapperInterface;
+use Magento\CatalogRule\Model\Indexer\ReindexRuleProduct;
+use Magento\CatalogRule\Model\Rule;
+use Magento\Framework\App\ResourceConnection;
+use Magento\Framework\DB\Adapter\AdapterInterface;
+use Magento\Framework\Stdlib\DateTime\TimezoneInterface;
+use Magento\Store\Model\ScopeInterface;
+use PHPUnit\Framework\MockObject\MockObject;
class ReindexRuleProductTest extends \PHPUnit\Framework\TestCase
{
/**
- * @var \Magento\CatalogRule\Model\Indexer\ReindexRuleProduct
+ * @var ReindexRuleProduct
*/
private $model;
/**
- * @var \Magento\Framework\App\ResourceConnection|\PHPUnit_Framework_MockObject_MockObject
+ * @var ResourceConnection|MockObject
*/
private $resourceMock;
/**
- * @var ActiveTableSwitcher|\PHPUnit_Framework_MockObject_MockObject
+ * @var ActiveTableSwitcher|MockObject
*/
private $activeTableSwitcherMock;
/**
- * @var IndexerTableSwapperInterface|\PHPUnit_Framework_MockObject_MockObject
+ * @var IndexerTableSwapperInterface|MockObject
*/
private $tableSwapperMock;
+ /**
+ * @var TimezoneInterface|MockObject
+ */
+ private $localeDateMock;
+
protected function setUp()
{
- $this->resourceMock = $this->getMockBuilder(\Magento\Framework\App\ResourceConnection::class)
- ->disableOriginalConstructor()
- ->getMock();
- $this->activeTableSwitcherMock = $this->getMockBuilder(ActiveTableSwitcher::class)
- ->disableOriginalConstructor()
- ->getMock();
- $this->tableSwapperMock = $this->getMockForAbstractClass(
- IndexerTableSwapperInterface::class
- );
- $this->model = new \Magento\CatalogRule\Model\Indexer\ReindexRuleProduct(
+ $this->resourceMock = $this->createMock(ResourceConnection::class);
+ $this->activeTableSwitcherMock = $this->createMock(ActiveTableSwitcher::class);
+ $this->tableSwapperMock = $this->createMock(IndexerTableSwapperInterface::class);
+ $this->localeDateMock = $this->createMock(TimezoneInterface::class);
+
+ $this->model = new ReindexRuleProduct(
$this->resourceMock,
$this->activeTableSwitcherMock,
- $this->tableSwapperMock
+ $this->tableSwapperMock,
+ $this->localeDateMock
);
}
public function testExecuteIfRuleInactive()
{
- $ruleMock = $this->getMockBuilder(\Magento\CatalogRule\Model\Rule::class)
- ->disableOriginalConstructor()
- ->getMock();
- $ruleMock->expects($this->once())->method('getIsActive')->willReturn(false);
+ $ruleMock = $this->createMock(Rule::class);
+ $ruleMock->expects($this->once())
+ ->method('getIsActive')
+ ->willReturn(false);
$this->assertFalse($this->model->execute($ruleMock, 100, true));
}
public function testExecuteIfRuleWithoutWebsiteIds()
{
- $ruleMock = $this->getMockBuilder(\Magento\CatalogRule\Model\Rule::class)
- ->disableOriginalConstructor()
- ->getMock();
- $ruleMock->expects($this->once())->method('getIsActive')->willReturn(true);
- $ruleMock->expects($this->once())->method('getWebsiteIds')->willReturn(null);
+ $ruleMock = $this->createMock(Rule::class);
+ $ruleMock->expects($this->once())
+ ->method('getIsActive')
+ ->willReturn(true);
+ $ruleMock->expects($this->once())
+ ->method('getWebsiteIds')
+ ->willReturn(null);
$this->assertFalse($this->model->execute($ruleMock, 100, true));
}
public function testExecute()
{
+ $websiteId = 3;
+ $websiteTz = 'America/Los_Angeles';
$productIds = [
- 4 => [1 => 1],
- 5 => [1 => 1],
- 6 => [1 => 1],
+ 4 => [$websiteId => 1],
+ 5 => [$websiteId => 1],
+ 6 => [$websiteId => 1],
];
- $ruleMock = $this->getMockBuilder(\Magento\CatalogRule\Model\Rule::class)
- ->disableOriginalConstructor()
- ->getMock();
- $ruleMock->expects($this->once())->method('getIsActive')->willReturn(true);
- $ruleMock->expects($this->exactly(2))->method('getWebsiteIds')->willReturn(1);
- $ruleMock->expects($this->once())->method('getMatchingProductIds')->willReturn($productIds);
$this->tableSwapperMock->expects($this->once())
->method('getWorkingTableName')
->with('catalogrule_product')
->willReturn('catalogrule_product_replica');
- $connectionMock = $this->getMockBuilder(\Magento\Framework\DB\Adapter\AdapterInterface::class)
- ->disableOriginalConstructor()
- ->getMock();
- $this->resourceMock->expects($this->at(0))->method('getConnection')->willReturn($connectionMock);
+ $connectionMock = $this->createMock(AdapterInterface::class);
+ $this->resourceMock->expects($this->at(0))
+ ->method('getConnection')
+ ->willReturn($connectionMock);
$this->resourceMock->expects($this->at(1))
->method('getTableName')
->with('catalogrule_product')
@@ -100,21 +107,30 @@ public function testExecute()
->with('catalogrule_product_replica')
->willReturn('catalogrule_product_replica');
+ $ruleMock = $this->createMock(Rule::class);
+ $ruleMock->expects($this->once())->method('getIsActive')->willReturn(true);
+ $ruleMock->expects($this->exactly(2))->method('getWebsiteIds')->willReturn([$websiteId]);
+ $ruleMock->expects($this->once())->method('getMatchingProductIds')->willReturn($productIds);
$ruleMock->expects($this->once())->method('getId')->willReturn(100);
$ruleMock->expects($this->once())->method('getCustomerGroupIds')->willReturn([10]);
- $ruleMock->expects($this->once())->method('getFromDate')->willReturn('2017-06-21');
- $ruleMock->expects($this->once())->method('getToDate')->willReturn('2017-06-30');
+ $ruleMock->expects($this->atLeastOnce())->method('getFromDate')->willReturn('2017-06-21');
+ $ruleMock->expects($this->atLeastOnce())->method('getToDate')->willReturn('2017-06-30');
$ruleMock->expects($this->once())->method('getSortOrder')->willReturn(1);
$ruleMock->expects($this->once())->method('getSimpleAction')->willReturn('simple_action');
$ruleMock->expects($this->once())->method('getDiscountAmount')->willReturn(43);
$ruleMock->expects($this->once())->method('getStopRulesProcessing')->willReturn(true);
+ $this->localeDateMock->expects($this->once())
+ ->method('getConfigTimezone')
+ ->with(ScopeInterface::SCOPE_WEBSITE, $websiteId)
+ ->willReturn($websiteTz);
+
$batchRows = [
[
'rule_id' => 100,
'from_time' => 1498028400,
'to_time' => 1498892399,
- 'website_id' => 1,
+ 'website_id' => $websiteId,
'customer_group_id' => 10,
'product_id' => 4,
'action_operator' => 'simple_action',
@@ -126,7 +142,7 @@ public function testExecute()
'rule_id' => 100,
'from_time' => 1498028400,
'to_time' => 1498892399,
- 'website_id' => 1,
+ 'website_id' => $websiteId,
'customer_group_id' => 10,
'product_id' => 5,
'action_operator' => 'simple_action',
@@ -141,7 +157,7 @@ public function testExecute()
'rule_id' => 100,
'from_time' => 1498028400,
'to_time' => 1498892399,
- 'website_id' => 1,
+ 'website_id' => $websiteId,
'customer_group_id' => 10,
'product_id' => 6,
'action_operator' => 'simple_action',
diff --git a/app/code/Magento/CatalogRule/Test/Unit/Model/RuleTest.php b/app/code/Magento/CatalogRule/Test/Unit/Model/RuleTest.php
index d03517ba04137..f1dede6ec7e06 100644
--- a/app/code/Magento/CatalogRule/Test/Unit/Model/RuleTest.php
+++ b/app/code/Magento/CatalogRule/Test/Unit/Model/RuleTest.php
@@ -311,13 +311,27 @@ public function testAfterDelete()
}
/**
- * Test after update action
+ * Test after update action for inactive rule
*
* @return void
*/
- public function testAfterUpdate()
+ public function testAfterUpdateInactive()
{
$this->rule->isObjectNew(false);
+ $this->rule->setIsActive(0);
+ $this->_ruleProductProcessor->expects($this->never())->method('getIndexer');
+ $this->rule->afterSave();
+ }
+
+ /**
+ * Test after update action for active rule
+ *
+ * @return void
+ */
+ public function testAfterUpdateActive()
+ {
+ $this->rule->isObjectNew(false);
+ $this->rule->setIsActive(1);
$indexer = $this->createMock(\Magento\Framework\Indexer\IndexerInterface::class);
$indexer->expects($this->once())->method('invalidate');
$this->_ruleProductProcessor->expects($this->once())->method('getIndexer')->will($this->returnValue($indexer));
diff --git a/app/code/Magento/CatalogRule/Test/Unit/Pricing/Price/CatalogRulePriceTest.php b/app/code/Magento/CatalogRule/Test/Unit/Pricing/Price/CatalogRulePriceTest.php
index 797097f8a5346..7514d2bc4b5c5 100644
--- a/app/code/Magento/CatalogRule/Test/Unit/Pricing/Price/CatalogRulePriceTest.php
+++ b/app/code/Magento/CatalogRule/Test/Unit/Pricing/Price/CatalogRulePriceTest.php
@@ -3,11 +3,21 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
+declare(strict_types=1);
namespace Magento\CatalogRule\Test\Unit\Pricing\Price;
+use Magento\Catalog\Model\Product;
+use Magento\CatalogRule\Model\ResourceModel\Rule;
use Magento\CatalogRule\Pricing\Price\CatalogRulePrice;
-use Magento\Framework\TestFramework\Unit\Helper\ObjectManager;
+use Magento\Customer\Model\Session;
+use Magento\Framework\Pricing\Adjustment\Calculator;
+use Magento\Framework\Pricing\PriceCurrencyInterface;
+use Magento\Framework\Stdlib\DateTime\TimezoneInterface;
+use Magento\Store\Api\Data\StoreInterface;
+use Magento\Store\Api\Data\WebsiteInterface;
+use Magento\Store\Model\StoreManagerInterface;
+use PHPUnit\Framework\MockObject\MockObject;
/**
* Class CatalogRulePriceTest
@@ -17,120 +27,73 @@
class CatalogRulePriceTest extends \PHPUnit\Framework\TestCase
{
/**
- * @var \Magento\CatalogRule\Pricing\Price\CatalogRulePrice
+ * @var CatalogRulePrice
*/
- protected $object;
+ private $object;
/**
- * @var \Magento\Framework\Pricing\SaleableInterface|\PHPUnit_Framework_MockObject_MockObject
+ * @var Product|MockObject
*/
- protected $saleableItemMock;
+ private $saleableItemMock;
/**
- * @var \Magento\Framework\Stdlib\DateTime\TimezoneInterface|\PHPUnit_Framework_MockObject_MockObject
+ * @var TimezoneInterface|MockObject
*/
- protected $dataTimeMock;
+ private $dataTimeMock;
/**
- * @var \Magento\Store\Model\StoreManager|\PHPUnit_Framework_MockObject_MockObject
+ * @var StoreManagerInterface|MockObject
*/
- protected $storeManagerMock;
+ private $storeManagerMock;
/**
- * @var \Magento\Customer\Model\Session|\PHPUnit_Framework_MockObject_MockObject
+ * @var Session|MockObject
*/
- protected $customerSessionMock;
+ private $customerSessionMock;
/**
- * @var \Magento\Framework\Pricing\PriceInfo\Base | \PHPUnit_Framework_MockObject_MockObject
+ * @var Rule|MockObject
*/
- protected $priceInfoMock;
+ private $catalogRuleResourceMock;
/**
- * @var \Magento\CatalogRule\Model\ResourceModel\RuleFactory|\PHPUnit_Framework_MockObject_MockObject
+ * @var WebsiteInterface|MockObject
*/
- protected $catalogRuleResourceFactoryMock;
+ private $coreWebsiteMock;
/**
- * @var \Magento\CatalogRule\Model\ResourceModel\Rule|\PHPUnit_Framework_MockObject_MockObject
+ * @var StoreInterface|MockObject
*/
- protected $catalogRuleResourceMock;
+ private $coreStoreMock;
/**
- * @var \Magento\Store\Model\Store|\PHPUnit_Framework_MockObject_MockObject
+ * @var Calculator|MockObject
*/
- protected $coreWebsiteMock;
+ private $calculator;
/**
- * @var \Magento\Store\Model\Website|\PHPUnit_Framework_MockObject_MockObject
+ * @var PriceCurrencyInterface|MockObject
*/
- protected $coreStoreMock;
-
- /**
- * @var \Magento\Framework\Pricing\Adjustment\Calculator|\PHPUnit_Framework_MockObject_MockObject
- */
- protected $calculator;
-
- /**
- * @var \Magento\Framework\Pricing\PriceCurrencyInterface|\PHPUnit_Framework_MockObject_MockObject
- */
- protected $priceCurrencyMock;
+ private $priceCurrencyMock;
/**
* Set up
*/
protected function setUp()
{
- $this->saleableItemMock = $this->createPartialMock(
- \Magento\Catalog\Model\Product::class,
- ['getId', '__wakeup', 'getPriceInfo', 'hasData', 'getData']
- );
- $this->dataTimeMock = $this->getMockForAbstractClass(
- \Magento\Framework\Stdlib\DateTime\TimezoneInterface::class,
- [],
- '',
- false,
- true,
- true,
- []
- );
-
- $this->coreStoreMock = $this->createMock(\Magento\Store\Model\Store::class);
- $this->storeManagerMock = $this->createMock(\Magento\Store\Model\StoreManager::class);
+ $this->saleableItemMock = $this->createMock(Product::class);
+ $this->dataTimeMock = $this->createMock(TimezoneInterface::class);
+ $this->coreStoreMock = $this->createMock(StoreInterface::class);
+ $this->storeManagerMock = $this->createMock(StoreManagerInterface::class);
$this->storeManagerMock->expects($this->any())
->method('getStore')
- ->will($this->returnValue($this->coreStoreMock));
-
- $this->customerSessionMock = $this->createMock(\Magento\Customer\Model\Session::class);
- $this->priceInfoMock = $this->getMockBuilder(\Magento\Framework\Pricing\PriceInfo::class)
- ->setMethods(['getAdjustments'])
- ->disableOriginalConstructor()
- ->getMock();
- $this->catalogRuleResourceFactoryMock = $this->createPartialMock(
- \Magento\CatalogRule\Model\ResourceModel\RuleFactory::class,
- ['create']
- );
- $this->catalogRuleResourceMock = $this->createMock(\Magento\CatalogRule\Model\ResourceModel\Rule::class);
-
- $this->coreWebsiteMock = $this->createMock(\Magento\Store\Model\Website::class);
-
- $this->priceInfoMock->expects($this->any())
- ->method('getAdjustments')
- ->will($this->returnValue([]));
- $this->saleableItemMock->expects($this->any())
- ->method('getPriceInfo')
- ->will($this->returnValue($this->priceInfoMock));
-
- $this->catalogRuleResourceFactoryMock->expects($this->any())
- ->method('create')
- ->will($this->returnValue($this->catalogRuleResourceMock));
-
- $this->calculator = $this->getMockBuilder(\Magento\Framework\Pricing\Adjustment\Calculator::class)
- ->disableOriginalConstructor()
- ->getMock();
+ ->willReturn($this->coreStoreMock);
+ $this->customerSessionMock = $this->createMock(Session::class);
+ $this->catalogRuleResourceMock = $this->createMock(Rule::class);
+ $this->coreWebsiteMock = $this->createMock(WebsiteInterface::class);
+ $this->calculator = $this->createMock(Calculator::class);
$qty = 1;
-
- $this->priceCurrencyMock = $this->createMock(\Magento\Framework\Pricing\PriceCurrencyInterface::class);
+ $this->priceCurrencyMock = $this->createMock(PriceCurrencyInterface::class);
$this->object = new CatalogRulePrice(
$this->saleableItemMock,
@@ -140,12 +103,6 @@ protected function setUp()
$this->dataTimeMock,
$this->storeManagerMock,
$this->customerSessionMock,
- $this->catalogRuleResourceFactoryMock
- );
-
- (new ObjectManager($this))->setBackwardCompatibleProperty(
- $this->object,
- 'ruleResource',
$this->catalogRuleResourceMock
);
}
@@ -155,38 +112,39 @@ protected function setUp()
*/
public function testGetValue()
{
- $coreStoreId = 1;
- $coreWebsiteId = 1;
- $productId = 1;
- $customerGroupId = 1;
- $dateTime = time();
+ $storeId = 5;
+ $coreWebsiteId = 2;
+ $productId = 4;
+ $customerGroupId = 3;
+ $date = new \DateTime();
$catalogRulePrice = 55.12;
$convertedPrice = 45.34;
$this->coreStoreMock->expects($this->once())
->method('getId')
- ->will($this->returnValue($coreStoreId));
- $this->coreStoreMock->expects($this->once())
- ->method('getWebsiteId')
- ->will($this->returnValue($coreWebsiteId));
+ ->willReturn($storeId);
$this->dataTimeMock->expects($this->once())
->method('scopeDate')
- ->with($this->equalTo($coreStoreId))
- ->will($this->returnValue($dateTime));
+ ->with($storeId)
+ ->willReturn($date);
+ $this->coreStoreMock->expects($this->once())
+ ->method('getWebsiteId')
+ ->willReturn($coreWebsiteId);
$this->customerSessionMock->expects($this->once())
->method('getCustomerGroupId')
- ->will($this->returnValue($customerGroupId));
+ ->willReturn($customerGroupId);
$this->catalogRuleResourceMock->expects($this->once())
->method('getRulePrice')
- ->will($this->returnValue($catalogRulePrice));
- $this->saleableItemMock->expects($this->any())
+ ->with($date, $coreWebsiteId, $customerGroupId, $productId)
+ ->willReturn($catalogRulePrice);
+ $this->saleableItemMock->expects($this->once())
->method('getId')
- ->will($this->returnValue($productId));
- $this->priceCurrencyMock->expects($this->any())
+ ->willReturn($productId);
+ $this->priceCurrencyMock->expects($this->once())
->method('convertAndRound')
->with($catalogRulePrice)
- ->will($this->returnValue($convertedPrice));
+ ->willReturn($convertedPrice);
$this->assertEquals($convertedPrice, $this->object->getValue());
}
diff --git a/app/code/Magento/CatalogRule/composer.json b/app/code/Magento/CatalogRule/composer.json
index 5b09765d9ae51..1b171c8d3658e 100644
--- a/app/code/Magento/CatalogRule/composer.json
+++ b/app/code/Magento/CatalogRule/composer.json
@@ -5,7 +5,7 @@
"sort-packages": true
},
"require": {
- "php": "~7.1.3||~7.2.0",
+ "php": "~7.1.3||~7.2.0||~7.3.0",
"magento/framework": "*",
"magento/module-backend": "*",
"magento/module-catalog": "*",
diff --git a/app/code/Magento/CatalogRule/view/adminhtml/ui_component/catalog_rule_form.xml b/app/code/Magento/CatalogRule/view/adminhtml/ui_component/catalog_rule_form.xml
index 8174434a06c49..c114f6b1d77cd 100644
--- a/app/code/Magento/CatalogRule/view/adminhtml/ui_component/catalog_rule_form.xml
+++ b/app/code/Magento/CatalogRule/view/adminhtml/ui_component/catalog_rule_form.xml
@@ -307,6 +307,7 @@
true
+ 0.00-100.00
text
Discount Amount
diff --git a/app/code/Magento/CatalogRuleConfigurable/Test/Mftf/ActionGroup/StorefrontAssertCatalogPriceRuleAppliedToProductOptionActionGroup.xml b/app/code/Magento/CatalogRuleConfigurable/Test/Mftf/ActionGroup/StorefrontAssertCatalogPriceRuleAppliedToProductOptionActionGroup.xml
index 4345d575a3320..635d3d6c2efa8 100644
--- a/app/code/Magento/CatalogRuleConfigurable/Test/Mftf/ActionGroup/StorefrontAssertCatalogPriceRuleAppliedToProductOptionActionGroup.xml
+++ b/app/code/Magento/CatalogRuleConfigurable/Test/Mftf/ActionGroup/StorefrontAssertCatalogPriceRuleAppliedToProductOptionActionGroup.xml
@@ -9,10 +9,14 @@
+
+ Selects the provided Product Option on a Storefront Product page. Validates the provided Expected Price is present and correct.
+
+
diff --git a/app/code/Magento/CatalogRuleConfigurable/Test/Mftf/ActionGroup/StorefrontAssertUpdatedProductPriceInStorefrontProductPageActionGroup.xml b/app/code/Magento/CatalogRuleConfigurable/Test/Mftf/ActionGroup/StorefrontAssertUpdatedProductPriceInStorefrontProductPageActionGroup.xml
index 56480583af7b4..8a022bc20427d 100644
--- a/app/code/Magento/CatalogRuleConfigurable/Test/Mftf/ActionGroup/StorefrontAssertUpdatedProductPriceInStorefrontProductPageActionGroup.xml
+++ b/app/code/Magento/CatalogRuleConfigurable/Test/Mftf/ActionGroup/StorefrontAssertUpdatedProductPriceInStorefrontProductPageActionGroup.xml
@@ -9,10 +9,14 @@
+
+ Validates that the provided Product Name and Price are present on a Storefront Product page.
+
+
diff --git a/app/code/Magento/CatalogRuleConfigurable/composer.json b/app/code/Magento/CatalogRuleConfigurable/composer.json
index 657e6efb4e44c..2e5468cb11103 100644
--- a/app/code/Magento/CatalogRuleConfigurable/composer.json
+++ b/app/code/Magento/CatalogRuleConfigurable/composer.json
@@ -5,7 +5,7 @@
"sort-packages": true
},
"require": {
- "php": "~7.1.3||~7.2.0",
+ "php": "~7.1.3||~7.2.0||~7.3.0",
"magento/framework": "*",
"magento/magento-composer-installer": "*",
"magento/module-catalog": "*",
diff --git a/app/code/Magento/CatalogSearch/Model/Adapter/Mysql/Filter/Preprocessor.php b/app/code/Magento/CatalogSearch/Model/Adapter/Mysql/Filter/Preprocessor.php
index c758e773f43c1..a97d362c5de7f 100644
--- a/app/code/Magento/CatalogSearch/Model/Adapter/Mysql/Filter/Preprocessor.php
+++ b/app/code/Magento/CatalogSearch/Model/Adapter/Mysql/Filter/Preprocessor.php
@@ -165,7 +165,10 @@ private function processQueryWithField(FilterInterface $filter, $isNegation, $qu
$this->customerSession->getCustomerGroupId()
);
} elseif ($filter->getField() === 'category_ids') {
- return 'category_ids_index.category_id = ' . (int) $filter->getValue();
+ return $this->connection->quoteInto(
+ 'category_ids_index.category_id in (?)',
+ $filter->getValue()
+ );
} elseif ($attribute->isStatic()) {
$alias = $this->aliasResolver->getAlias($filter);
$resultQuery = str_replace(
@@ -198,8 +201,9 @@ private function processQueryWithField(FilterInterface $filter, $isNegation, $qu
)
->joinLeft(
['current_store' => $table],
- 'current_store.attribute_id = main_table.attribute_id AND current_store.store_id = '
- . $currentStoreId,
+ "current_store.{$linkIdField} = main_table.{$linkIdField} AND "
+ . "current_store.attribute_id = main_table.attribute_id AND current_store.store_id = "
+ . $currentStoreId,
null
)
->columns([$filter->getField() => $ifNullCondition])
diff --git a/app/code/Magento/CatalogSearch/Model/Advanced.php b/app/code/Magento/CatalogSearch/Model/Advanced.php
index 5b96a8c21cbea..8ce2e0140f528 100644
--- a/app/code/Magento/CatalogSearch/Model/Advanced.php
+++ b/app/code/Magento/CatalogSearch/Model/Advanced.php
@@ -181,8 +181,8 @@ public function __construct(
/**
* Add advanced search filters to product collection
*
- * @param array $values
- * @return $this
+ * @param array $values
+ * @return $this
* @throws LocalizedException
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
* @SuppressWarnings(PHPMD.NPathComplexity)
@@ -197,6 +197,11 @@ public function addFilters($values)
if (!isset($values[$attribute->getAttributeCode()])) {
continue;
}
+ if ($attribute->getFrontendInput() == 'text' || $attribute->getFrontendInput() == 'textarea') {
+ if (!trim($values[$attribute->getAttributeCode()])) {
+ continue;
+ }
+ }
$value = $values[$attribute->getAttributeCode()];
$preparedSearchValue = $this->getPreparedSearchCriteria($attribute, $value);
if (false === $preparedSearchValue) {
@@ -343,9 +348,9 @@ protected function addSearchCriteria($attribute, $value)
*
* @todo: Move this code to block
*
- * @param EntityAttribute $attribute
- * @param mixed $value
- * @return string|bool
+ * @param EntityAttribute $attribute
+ * @param mixed $value
+ * @return string|bool
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
* @SuppressWarnings(PHPMD.NPathComplexity)
*/
diff --git a/app/code/Magento/CatalogSearch/Model/Advanced/ProductCollectionPrepareStrategyProvider.php b/app/code/Magento/CatalogSearch/Model/Advanced/ProductCollectionPrepareStrategyProvider.php
index 6e963ea1aa8ac..8527ef56c509b 100644
--- a/app/code/Magento/CatalogSearch/Model/Advanced/ProductCollectionPrepareStrategyProvider.php
+++ b/app/code/Magento/CatalogSearch/Model/Advanced/ProductCollectionPrepareStrategyProvider.php
@@ -42,7 +42,7 @@ public function __construct(
public function getStrategy(): ProductCollectionPrepareStrategyInterface
{
if (!isset($this->strategies[$this->engineResolver->getCurrentSearchEngine()])) {
- throw new \DomainException('Undefined strategy ' . $this->engineResolver->getCurrentSearchEngine());
+ return $this->strategies['default'];
}
return $this->strategies[$this->engineResolver->getCurrentSearchEngine()];
}
diff --git a/app/code/Magento/CatalogSearch/Model/Indexer/Fulltext.php b/app/code/Magento/CatalogSearch/Model/Indexer/Fulltext.php
index 21d8b7297da7d..912dec8666191 100644
--- a/app/code/Magento/CatalogSearch/Model/Indexer/Fulltext.php
+++ b/app/code/Magento/CatalogSearch/Model/Indexer/Fulltext.php
@@ -3,11 +3,14 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
+
namespace Magento\CatalogSearch\Model\Indexer;
use Magento\CatalogSearch\Model\Indexer\Fulltext\Action\FullFactory;
+use Magento\CatalogSearch\Model\Indexer\Scope\State;
use Magento\CatalogSearch\Model\Indexer\Scope\StateFactory;
use Magento\CatalogSearch\Model\ResourceModel\Fulltext as FulltextResource;
+use Magento\Framework\App\ObjectManager;
use Magento\Framework\Indexer\DimensionProviderInterface;
use Magento\Store\Model\StoreDimensionProvider;
use Magento\Indexer\Model\ProcessManager;
@@ -79,6 +82,7 @@ class Fulltext implements
* @param DimensionProviderInterface $dimensionProvider
* @param array $data
* @param ProcessManager $processManager
+ * @SuppressWarnings(PHPMD.UnusedFormalParameter)
*/
public function __construct(
FullFactory $fullActionFactory,
@@ -95,11 +99,9 @@ public function __construct(
$this->fulltextResource = $fulltextResource;
$this->data = $data;
$this->indexSwitcher = $indexSwitcher;
- $this->indexScopeState = $indexScopeStateFactory->create();
+ $this->indexScopeState = ObjectManager::getInstance()->get(State::class);
$this->dimensionProvider = $dimensionProvider;
- $this->processManager = $processManager ?: \Magento\Framework\App\ObjectManager::getInstance()->get(
- ProcessManager::class
- );
+ $this->processManager = $processManager ?: ObjectManager::getInstance()->get(ProcessManager::class);
}
/**
@@ -127,9 +129,11 @@ public function executeByDimensions(array $dimensions, \Traversable $entityIds =
throw new \InvalidArgumentException('Indexer "' . self::INDEXER_ID . '" support only Store dimension');
}
$storeId = $dimensions[StoreDimensionProvider::DIMENSION_NAME]->getValue();
- $saveHandler = $this->indexerHandlerFactory->create([
- 'data' => $this->data
- ]);
+ $saveHandler = $this->indexerHandlerFactory->create(
+ [
+ 'data' => $this->data,
+ ]
+ );
if (null === $entityIds) {
$this->indexScopeState->useTemporaryIndex();
diff --git a/app/code/Magento/CatalogSearch/Model/Indexer/Fulltext/Action/DataProvider.php b/app/code/Magento/CatalogSearch/Model/Indexer/Fulltext/Action/DataProvider.php
index cd2529a8fd725..09d4f0068459a 100644
--- a/app/code/Magento/CatalogSearch/Model/Indexer/Fulltext/Action/DataProvider.php
+++ b/app/code/Magento/CatalogSearch/Model/Indexer/Fulltext/Action/DataProvider.php
@@ -7,9 +7,14 @@
use Magento\Catalog\Api\Data\ProductInterface;
use Magento\Catalog\Model\Product\Attribute\Source\Status;
+use Magento\CatalogInventory\Api\Data\StockStatusInterface;
+use Magento\CatalogInventory\Api\StockConfigurationInterface;
+use Magento\CatalogInventory\Api\StockStatusCriteriaInterface;
+use Magento\CatalogInventory\Api\StockStatusRepositoryInterface;
use Magento\Framework\App\ResourceConnection;
use Magento\Framework\DB\Select;
use Magento\Store\Model\Store;
+use Magento\Framework\App\ObjectManager;
/**
* Catalog search full test search data provider.
@@ -124,6 +129,16 @@ class DataProvider
*/
private $antiGapMultiplier;
+ /**
+ * @var StockConfigurationInterface
+ */
+ private $stockConfiguration;
+
+ /**
+ * @var StockStatusRepositoryInterface
+ */
+ private $stockStatusRepository;
+
/**
* @param ResourceConnection $resource
* @param \Magento\Catalog\Model\Product\Type $catalogProductType
@@ -548,6 +563,8 @@ public function prepareProductIndex($indexData, $productData, $storeId)
{
$index = [];
+ $indexData = $this->filterOutOfStockProducts($indexData, $storeId);
+
foreach ($this->getSearchableAttributes('static') as $attribute) {
$attributeCode = $attribute->getAttributeCode();
@@ -672,4 +689,68 @@ private function filterAttributeValue($value)
{
return preg_replace('/\s+/iu', ' ', trim(strip_tags($value)));
}
+
+ /**
+ * Filter out of stock products for products.
+ *
+ * @param array $indexData
+ * @param int $storeId
+ * @return array
+ */
+ private function filterOutOfStockProducts($indexData, $storeId): array
+ {
+ if (!$this->getStockConfiguration()->isShowOutOfStock($storeId)) {
+ $productIds = array_keys($indexData);
+ $stockStatusCriteria = $this->createStockStatusCriteria();
+ $stockStatusCriteria->setProductsFilter($productIds);
+ $stockStatusCollection = $this->getStockStatusRepository()->getList($stockStatusCriteria);
+ $stockStatuses = $stockStatusCollection->getItems();
+ $stockStatuses = array_filter(
+ $stockStatuses,
+ function (StockStatusInterface $stockStatus) {
+ return StockStatusInterface::STATUS_IN_STOCK == $stockStatus->getStockStatus();
+ }
+ );
+ $indexData = array_intersect_key($indexData, $stockStatuses);
+ }
+ return $indexData;
+ }
+
+ /**
+ * Get stock configuration.
+ *
+ * @return StockConfigurationInterface
+ */
+ private function getStockConfiguration()
+ {
+ if (null === $this->stockConfiguration) {
+ $this->stockConfiguration = ObjectManager::getInstance()->get(StockConfigurationInterface::class);
+ }
+ return $this->stockConfiguration;
+ }
+
+ /**
+ * Create stock status criteria.
+ *
+ * Substitution of autogenerated factory in backward compatibility reasons.
+ *
+ * @return StockStatusCriteriaInterface
+ */
+ private function createStockStatusCriteria()
+ {
+ return ObjectManager::getInstance()->create(StockStatusCriteriaInterface::class);
+ }
+
+ /**
+ * Get stock status repository.
+ *
+ * @return StockStatusRepositoryInterface
+ */
+ private function getStockStatusRepository()
+ {
+ if (null === $this->stockStatusRepository) {
+ $this->stockStatusRepository = ObjectManager::getInstance()->get(StockStatusRepositoryInterface::class);
+ }
+ return $this->stockStatusRepository;
+ }
}
diff --git a/app/code/Magento/CatalogSearch/Model/Layer/Filter/Attribute.php b/app/code/Magento/CatalogSearch/Model/Layer/Filter/Attribute.php
index 794d0ac971536..85a32dc60b119 100644
--- a/app/code/Magento/CatalogSearch/Model/Layer/Filter/Attribute.php
+++ b/app/code/Magento/CatalogSearch/Model/Layer/Filter/Attribute.php
@@ -3,6 +3,8 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
+declare(strict_types=1);
+
namespace Magento\CatalogSearch\Model\Layer\Filter;
use Magento\Catalog\Model\Layer\Filter\AbstractFilter;
@@ -62,6 +64,9 @@ public function apply(\Magento\Framework\App\RequestInterface $request)
->getProductCollection();
$productCollection->addFieldToFilter($attribute->getAttributeCode(), $attributeValue);
$label = $this->getOptionText($attributeValue);
+ if (is_array($label)) {
+ $label = implode(',', $label);
+ }
$this->getLayer()
->getState()
->addFilter($this->_createItem($label, $attributeValue));
diff --git a/app/code/Magento/CatalogSearch/Model/Layer/Filter/Decimal.php b/app/code/Magento/CatalogSearch/Model/Layer/Filter/Decimal.php
index e9fb1070fedd5..3b0c4dfb6df2f 100644
--- a/app/code/Magento/CatalogSearch/Model/Layer/Filter/Decimal.php
+++ b/app/code/Magento/CatalogSearch/Model/Layer/Filter/Decimal.php
@@ -3,6 +3,8 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
+declare(strict_types=1);
+
namespace Magento\CatalogSearch\Model\Layer\Filter;
use Magento\Catalog\Model\Layer\Filter\AbstractFilter;
@@ -12,6 +14,9 @@
*/
class Decimal extends AbstractFilter
{
+ /** Decimal delta for filter */
+ private const DECIMAL_DELTA = 0.001;
+
/**
* @var \Magento\Framework\Pricing\PriceCurrencyInterface
*/
@@ -70,11 +75,17 @@ public function apply(\Magento\Framework\App\RequestInterface $request)
list($from, $to) = explode('-', $filter);
+ // When the range is 10-20 we only need to get products that are in the 10-19.99 range.
+ $toValue = $to;
+ if (!empty($toValue) && $from !== $toValue) {
+ $toValue -= self::DECIMAL_DELTA;
+ }
+
$this->getLayer()
->getProductCollection()
->addFieldToFilter(
$this->getAttributeModel()->getAttributeCode(),
- ['from' => $from, 'to' => $to]
+ ['from' => $from, 'to' => $toValue]
);
$this->getLayer()->getState()->addFilter(
@@ -111,7 +122,7 @@ protected function _getItemsData()
$from = '';
}
if ($to == '*') {
- $to = null;
+ $to = '';
}
$label = $this->renderRangeLabel(empty($from) ? 0 : $from, $to);
$value = $from . '-' . $to;
@@ -138,7 +149,7 @@ protected function _getItemsData()
protected function renderRangeLabel($fromPrice, $toPrice)
{
$formattedFromPrice = $this->priceCurrency->format($fromPrice);
- if ($toPrice === null) {
+ if ($toPrice === '') {
return __('%1 and above', $formattedFromPrice);
} else {
if ($fromPrice != $toPrice) {
diff --git a/app/code/Magento/CatalogSearch/Model/Layer/Filter/Price.php b/app/code/Magento/CatalogSearch/Model/Layer/Filter/Price.php
index a19f53469ae01..66d9281ed38e2 100644
--- a/app/code/Magento/CatalogSearch/Model/Layer/Filter/Price.php
+++ b/app/code/Magento/CatalogSearch/Model/Layer/Filter/Price.php
@@ -3,6 +3,8 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
+declare(strict_types=1);
+
namespace Magento\CatalogSearch\Model\Layer\Filter;
use Magento\Catalog\Model\Layer\Filter\AbstractFilter;
@@ -11,6 +13,7 @@
* Layer price filter based on Search API
*
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
+ * @SuppressWarnings(PHPMD.CookieAndSessionMisuse)
*/
class Price extends AbstractFilter
{
@@ -138,7 +141,7 @@ public function apply(\Magento\Framework\App\RequestInterface $request)
list($from, $to) = $filter;
$this->getLayer()->getProductCollection()->addFieldToFilter(
- 'price',
+ $this->getAttributeModel()->getAttributeCode(),
['from' => $from, 'to' => empty($to) || $from == $to ? $to : $to - self::PRICE_DELTA]
);
diff --git a/app/code/Magento/CatalogSearch/Model/ResourceModel/Advanced/Collection.php b/app/code/Magento/CatalogSearch/Model/ResourceModel/Advanced/Collection.php
index 9bd9a895a2af9..595bc12ca956a 100644
--- a/app/code/Magento/CatalogSearch/Model/ResourceModel/Advanced/Collection.php
+++ b/app/code/Magento/CatalogSearch/Model/ResourceModel/Advanced/Collection.php
@@ -7,10 +7,11 @@
namespace Magento\CatalogSearch\Model\ResourceModel\Advanced;
use Magento\Catalog\Model\Product;
+use Magento\CatalogSearch\Model\ResourceModel\Fulltext\Collection\DefaultFilterStrategyApplyChecker;
+use Magento\CatalogSearch\Model\ResourceModel\Fulltext\Collection\DefaultFilterStrategyApplyCheckerInterface;
use Magento\CatalogSearch\Model\ResourceModel\Fulltext\Collection\SearchCriteriaResolverInterface;
use Magento\CatalogSearch\Model\ResourceModel\Fulltext\Collection\SearchResultApplierInterface;
use Magento\Framework\Search\EngineResolverInterface;
-use Magento\Search\Model\EngineResolver;
use Magento\CatalogSearch\Model\ResourceModel\Fulltext\Collection\TotalRecordsResolverInterface;
use Magento\CatalogSearch\Model\ResourceModel\Fulltext\Collection\TotalRecordsResolverFactory;
use Magento\Framework\Api\FilterBuilder;
@@ -106,6 +107,11 @@ class Collection extends \Magento\Catalog\Model\ResourceModel\Product\Collection
*/
private $searchOrders;
+ /**
+ * @var DefaultFilterStrategyApplyCheckerInterface
+ */
+ private $defaultFilterStrategyApplyChecker;
+
/**
* Collection constructor
*
@@ -140,6 +146,7 @@ class Collection extends \Magento\Catalog\Model\ResourceModel\Product\Collection
* @param SearchResultApplierFactory|null $searchResultApplierFactory
* @param TotalRecordsResolverFactory|null $totalRecordsResolverFactory
* @param EngineResolverInterface|null $engineResolver
+ * @param DefaultFilterStrategyApplyCheckerInterface|null $defaultFilterStrategyApplyChecker
* @SuppressWarnings(PHPMD.ExcessiveParameterList)
*/
public function __construct(
@@ -173,7 +180,8 @@ public function __construct(
SearchCriteriaResolverFactory $searchCriteriaResolverFactory = null,
SearchResultApplierFactory $searchResultApplierFactory = null,
TotalRecordsResolverFactory $totalRecordsResolverFactory = null,
- EngineResolverInterface $engineResolver = null
+ EngineResolverInterface $engineResolver = null,
+ DefaultFilterStrategyApplyCheckerInterface $defaultFilterStrategyApplyChecker = null
) {
$this->requestBuilder = $requestBuilder;
$this->searchEngine = $searchEngine;
@@ -191,6 +199,8 @@ public function __construct(
->get(TotalRecordsResolverFactory::class);
$this->engineResolver = $engineResolver ?: ObjectManager::getInstance()
->get(EngineResolverInterface::class);
+ $this->defaultFilterStrategyApplyChecker = $defaultFilterStrategyApplyChecker ?: ObjectManager::getInstance()
+ ->get(DefaultFilterStrategyApplyChecker::class);
parent::__construct(
$entityFactory,
$logger,
@@ -237,8 +247,12 @@ public function addFieldsToFilter($fields)
*/
public function setOrder($attribute, $dir = Select::SQL_DESC)
{
+ /**
+ * This changes need in backward compatible reasons for support dynamic improved algorithm
+ * for price aggregation process.
+ */
$this->setSearchOrder($attribute, $dir);
- if ($this->isCurrentEngineMysql()) {
+ if ($this->defaultFilterStrategyApplyChecker->isApplicable()) {
parent::setOrder($attribute, $dir);
}
@@ -254,7 +268,7 @@ public function addCategoryFilter(\Magento\Catalog\Model\Category $category)
* This changes need in backward compatible reasons for support dynamic improved algorithm
* for price aggregation process.
*/
- if ($this->isCurrentEngineMysql()) {
+ if ($this->defaultFilterStrategyApplyChecker->isApplicable()) {
parent::addCategoryFilter($category);
} else {
$this->addFieldToFilter('category_ids', $category->getId());
@@ -273,7 +287,7 @@ public function setVisibility($visibility)
* This changes need in backward compatible reasons for support dynamic improved algorithm
* for price aggregation process.
*/
- if ($this->isCurrentEngineMysql()) {
+ if ($this->defaultFilterStrategyApplyChecker->isApplicable()) {
parent::setVisibility($visibility);
} else {
$this->addFieldToFilter('visibility', $visibility);
@@ -297,16 +311,6 @@ private function setSearchOrder($field, $direction)
$this->searchOrders[$field] = $direction;
}
- /**
- * Check if current engine is MYSQL.
- *
- * @return bool
- */
- private function isCurrentEngineMysql()
- {
- return $this->engineResolver->getCurrentSearchEngine() === EngineResolver::CATALOG_SEARCH_MYSQL_ENGINE;
- }
-
/**
* @inheritdoc
*/
@@ -387,7 +391,8 @@ private function getSearchResultApplier(SearchResultInterface $searchResult): Se
'collection' => $this,
'searchResult' => $searchResult,
/** This variable sets by serOrder method, but doesn't have a getter method. */
- 'orders' => $this->_orders
+ 'orders' => $this->_orders,
+ 'size' => $this->getPageSize(),
]
);
}
diff --git a/app/code/Magento/CatalogSearch/Model/ResourceModel/Fulltext/Collection.php b/app/code/Magento/CatalogSearch/Model/ResourceModel/Fulltext/Collection.php
index 7e4b4f764f64b..14305359a71b3 100644
--- a/app/code/Magento/CatalogSearch/Model/ResourceModel/Fulltext/Collection.php
+++ b/app/code/Magento/CatalogSearch/Model/ResourceModel/Fulltext/Collection.php
@@ -6,13 +6,14 @@
namespace Magento\CatalogSearch\Model\ResourceModel\Fulltext;
+use Magento\CatalogSearch\Model\ResourceModel\Fulltext\Collection\DefaultFilterStrategyApplyChecker;
+use Magento\CatalogSearch\Model\ResourceModel\Fulltext\Collection\DefaultFilterStrategyApplyCheckerInterface;
use Magento\CatalogSearch\Model\ResourceModel\Fulltext\Collection\TotalRecordsResolverInterface;
use Magento\CatalogSearch\Model\ResourceModel\Fulltext\Collection\TotalRecordsResolverFactory;
use Magento\CatalogSearch\Model\ResourceModel\Fulltext\Collection\SearchCriteriaResolverInterface;
use Magento\CatalogSearch\Model\ResourceModel\Fulltext\Collection\SearchCriteriaResolverFactory;
use Magento\CatalogSearch\Model\ResourceModel\Fulltext\Collection\SearchResultApplierFactory;
use Magento\CatalogSearch\Model\ResourceModel\Fulltext\Collection\SearchResultApplierInterface;
-use Magento\Framework\Search\EngineResolverInterface;
use Magento\Framework\Data\Collection\Db\SizeResolverInterfaceFactory;
use Magento\Framework\DB\Select;
use Magento\Framework\Api\Search\SearchResultInterface;
@@ -26,7 +27,6 @@
use Magento\Framework\Exception\LocalizedException;
use Magento\Framework\App\ObjectManager;
use Magento\Catalog\Model\ResourceModel\Product\Collection\ProductLimitationFactory;
-use Magento\Search\Model\EngineResolver;
/**
* Fulltext Collection
@@ -124,14 +124,14 @@ class Collection extends \Magento\Catalog\Model\ResourceModel\Product\Collection
private $totalRecordsResolverFactory;
/**
- * @var EngineResolverInterface
+ * @var array
*/
- private $engineResolver;
+ private $searchOrders;
/**
- * @var array
+ * @var DefaultFilterStrategyApplyCheckerInterface
*/
- private $searchOrders;
+ private $defaultFilterStrategyApplyChecker;
/**
* Collection constructor
@@ -170,7 +170,7 @@ class Collection extends \Magento\Catalog\Model\ResourceModel\Product\Collection
* @param SearchCriteriaResolverFactory|null $searchCriteriaResolverFactory
* @param SearchResultApplierFactory|null $searchResultApplierFactory
* @param TotalRecordsResolverFactory|null $totalRecordsResolverFactory
- * @param EngineResolverInterface|null $engineResolver
+ * @param DefaultFilterStrategyApplyCheckerInterface|null $defaultFilterStrategyApplyChecker
* @SuppressWarnings(PHPMD.ExcessiveParameterList)
* @SuppressWarnings(PHPMD.NPathComplexity)
*/
@@ -209,7 +209,7 @@ public function __construct(
SearchCriteriaResolverFactory $searchCriteriaResolverFactory = null,
SearchResultApplierFactory $searchResultApplierFactory = null,
TotalRecordsResolverFactory $totalRecordsResolverFactory = null,
- EngineResolverInterface $engineResolver = null
+ DefaultFilterStrategyApplyCheckerInterface $defaultFilterStrategyApplyChecker = null
) {
$this->queryFactory = $catalogSearchData;
if ($searchResultFactory === null) {
@@ -255,8 +255,8 @@ public function __construct(
->get(SearchResultApplierFactory::class);
$this->totalRecordsResolverFactory = $totalRecordsResolverFactory ?: ObjectManager::getInstance()
->get(TotalRecordsResolverFactory::class);
- $this->engineResolver = $engineResolver ?: ObjectManager::getInstance()
- ->get(EngineResolverInterface::class);
+ $this->defaultFilterStrategyApplyChecker = $defaultFilterStrategyApplyChecker ?: ObjectManager::getInstance()
+ ->get(DefaultFilterStrategyApplyChecker::class);
}
/**
@@ -393,13 +393,31 @@ public function addSearchFilter($query)
public function setOrder($attribute, $dir = Select::SQL_DESC)
{
$this->setSearchOrder($attribute, $dir);
- if ($this->isCurrentEngineMysql()) {
+ if ($this->defaultFilterStrategyApplyChecker->isApplicable()) {
parent::setOrder($attribute, $dir);
}
return $this;
}
+ /**
+ * Add attribute to sort order.
+ *
+ * @param string $attribute
+ * @param string $dir
+ * @return $this
+ */
+ public function addAttributeToSort($attribute, $dir = self::SORT_ORDER_ASC)
+ {
+ if ($this->defaultFilterStrategyApplyChecker->isApplicable()) {
+ parent::addAttributeToSort($attribute, $dir);
+ } else {
+ $this->setOrder($attribute, $dir);
+ }
+
+ return $this;
+ }
+
/**
* @inheritdoc
*/
@@ -443,16 +461,6 @@ private function setSearchOrder($field, $direction)
$this->searchOrders[$field] = $direction;
}
- /**
- * Check if current engine is MYSQL.
- *
- * @return bool
- */
- private function isCurrentEngineMysql()
- {
- return $this->engineResolver->getCurrentSearchEngine() === EngineResolver::CATALOG_SEARCH_MYSQL_ENGINE;
- }
-
/**
* Get total records resolver.
*
@@ -477,12 +485,12 @@ private function getSearchCriteriaResolver(): SearchCriteriaResolverInterface
{
return $this->searchCriteriaResolverFactory->create(
[
- 'builder' => $this->getSearchCriteriaBuilder(),
- 'collection' => $this,
- 'searchRequestName' => $this->searchRequestName,
- 'currentPage' => $this->_curPage,
- 'size' => $this->getPageSize(),
- 'orders' => $this->searchOrders,
+ 'builder' => $this->getSearchCriteriaBuilder(),
+ 'collection' => $this,
+ 'searchRequestName' => $this->searchRequestName,
+ 'currentPage' => (int)$this->_curPage,
+ 'size' => $this->getPageSize(),
+ 'orders' => $this->searchOrders,
]
);
}
@@ -497,10 +505,12 @@ private function getSearchResultApplier(SearchResultInterface $searchResult): Se
{
return $this->searchResultApplierFactory->create(
[
- 'collection' => $this,
- 'searchResult' => $searchResult,
- /** This variable sets by serOrder method, but doesn't have a getter method. */
- 'orders' => $this->_orders,
+ 'collection' => $this,
+ 'searchResult' => $searchResult,
+ /** This variable sets by serOrder method, but doesn't have a getter method. */
+ 'orders' => $this->_orders,
+ 'size' => $this->getPageSize(),
+ 'currentPage' => (int)$this->_curPage,
]
);
}
@@ -580,7 +590,7 @@ public function addCategoryFilter(\Magento\Catalog\Model\Category $category)
* This changes need in backward compatible reasons for support dynamic improved algorithm
* for price aggregation process.
*/
- if ($this->isCurrentEngineMysql()) {
+ if ($this->defaultFilterStrategyApplyChecker->isApplicable()) {
parent::addCategoryFilter($category);
} else {
$this->_productLimitationPrice();
@@ -602,7 +612,7 @@ public function setVisibility($visibility)
* This changes need in backward compatible reasons for support dynamic improved algorithm
* for price aggregation process.
*/
- if ($this->isCurrentEngineMysql()) {
+ if ($this->defaultFilterStrategyApplyChecker->isApplicable()) {
parent::setVisibility($visibility);
}
diff --git a/app/code/Magento/CatalogSearch/Model/ResourceModel/Fulltext/Collection/DefaultFilterStrategyApplyChecker.php b/app/code/Magento/CatalogSearch/Model/ResourceModel/Fulltext/Collection/DefaultFilterStrategyApplyChecker.php
new file mode 100644
index 0000000000000..b396437fc66c7
--- /dev/null
+++ b/app/code/Magento/CatalogSearch/Model/ResourceModel/Fulltext/Collection/DefaultFilterStrategyApplyChecker.php
@@ -0,0 +1,24 @@
+factories[$this->engineResolver->getCurrentSearchEngine()])) {
- throw new \DomainException('Undefined factory ' . $this->engineResolver->getCurrentSearchEngine());
+ return $this->factories['default'];
}
return $this->factories[$this->engineResolver->getCurrentSearchEngine()]->create();
}
diff --git a/app/code/Magento/CatalogSearch/Model/Search/RequestGenerator.php b/app/code/Magento/CatalogSearch/Model/Search/RequestGenerator.php
index 8f8ba39ebd329..5ac252677ff79 100644
--- a/app/code/Magento/CatalogSearch/Model/Search/RequestGenerator.php
+++ b/app/code/Magento/CatalogSearch/Model/Search/RequestGenerator.php
@@ -3,6 +3,8 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
+declare(strict_types=1);
+
namespace Magento\CatalogSearch\Model\Search;
use Magento\Catalog\Api\Data\EavAttributeInterface;
@@ -78,6 +80,7 @@ private function generateRequest($attributeType, $container, $useFulltext)
{
$request = [];
foreach ($this->getSearchableAttributes() as $attribute) {
+ /** @var $attribute Attribute */
if ($attribute->getData($attributeType)) {
if (!in_array($attribute->getAttributeCode(), ['price', 'category_ids'], true)) {
$queryName = $attribute->getAttributeCode() . '_query';
@@ -97,12 +100,14 @@ private function generateRequest($attributeType, $container, $useFulltext)
],
];
$bucketName = $attribute->getAttributeCode() . self::BUCKET_SUFFIX;
- $generator = $this->generatorResolver->getGeneratorForType($attribute->getBackendType());
+ $generatorType = $attribute->getFrontendInput() === 'price'
+ ? $attribute->getFrontendInput()
+ : $attribute->getBackendType();
+ $generator = $this->generatorResolver->getGeneratorForType($generatorType);
$request['filters'][$filterName] = $generator->getFilterData($attribute, $filterName);
$request['aggregations'][$bucketName] = $generator->getAggregationData($attribute, $bucketName);
}
}
- /** @var $attribute Attribute */
if (!$attribute->getIsSearchable() || in_array($attribute->getAttributeCode(), ['price'], true)) {
// Some fields have their own specific handlers
continue;
diff --git a/app/code/Magento/CatalogSearch/Model/Search/RequestGenerator/Decimal.php b/app/code/Magento/CatalogSearch/Model/Search/RequestGenerator/Decimal.php
index b3d39a48fe9fc..73d011cc532db 100644
--- a/app/code/Magento/CatalogSearch/Model/Search/RequestGenerator/Decimal.php
+++ b/app/code/Magento/CatalogSearch/Model/Search/RequestGenerator/Decimal.php
@@ -3,6 +3,7 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
+declare(strict_types=1);
namespace Magento\CatalogSearch\Model\Search\RequestGenerator;
diff --git a/app/code/Magento/CatalogSearch/Model/Search/RequestGenerator/Price.php b/app/code/Magento/CatalogSearch/Model/Search/RequestGenerator/Price.php
new file mode 100644
index 0000000000000..949806d14f45a
--- /dev/null
+++ b/app/code/Magento/CatalogSearch/Model/Search/RequestGenerator/Price.php
@@ -0,0 +1,46 @@
+ FilterInterface::TYPE_RANGE,
+ 'name' => $filterName,
+ 'field' => $attribute->getAttributeCode(),
+ 'from' => '$' . $attribute->getAttributeCode() . '.from$',
+ 'to' => '$' . $attribute->getAttributeCode() . '.to$',
+ ];
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function getAggregationData(Attribute $attribute, $bucketName): array
+ {
+ return [
+ 'type' => BucketInterface::TYPE_DYNAMIC,
+ 'name' => $bucketName,
+ 'field' => $attribute->getAttributeCode(),
+ 'method' => '$price_dynamic_algorithm$',
+ 'metric' => [['type' => 'count']],
+ ];
+ }
+}
diff --git a/app/code/Magento/CatalogSearch/Test/Mftf/ActionGroup/AdminCatalogSearchTermActionGroup.xml b/app/code/Magento/CatalogSearch/Test/Mftf/ActionGroup/AdminCatalogSearchTermActionGroup.xml
index 33ffa4fe1b296..932c45c0d5aeb 100644
--- a/app/code/Magento/CatalogSearch/Test/Mftf/ActionGroup/AdminCatalogSearchTermActionGroup.xml
+++ b/app/code/Magento/CatalogSearch/Test/Mftf/ActionGroup/AdminCatalogSearchTermActionGroup.xml
@@ -9,12 +9,16 @@
+
+ Goes to the Catalog Search Term grid page. Adds the provided Search Term. Validates that the Success Message is present and correct.
+
+
@@ -26,10 +30,15 @@
+
+
+ Goes to the Catalog Search Term grid page. Deletes the provided Search Term. Validates that the Success Message is present and correct.
+
+
@@ -43,10 +52,15 @@
+
+
+ Goes to the Catalog Search Term grid page. Searches for the provided Search Term. Validates that it is NOT present in the grid.
+
+
@@ -56,4 +70,4 @@
-
\ No newline at end of file
+
diff --git a/app/code/Magento/CatalogSearch/Test/Mftf/ActionGroup/AdminSetMinimalQueryLengthActionGroup.xml b/app/code/Magento/CatalogSearch/Test/Mftf/ActionGroup/AdminSetMinimalQueryLengthActionGroup.xml
index b9ef37cb4effe..a6e3dfd7eaad4 100644
--- a/app/code/Magento/CatalogSearch/Test/Mftf/ActionGroup/AdminSetMinimalQueryLengthActionGroup.xml
+++ b/app/code/Magento/CatalogSearch/Test/Mftf/ActionGroup/AdminSetMinimalQueryLengthActionGroup.xml
@@ -9,9 +9,13 @@
+
+ Goes to the 'Configuration' page for 'Catalog'. Sets the Minimal Query Length. CLicks on the Save button.
+
+
diff --git a/app/code/Magento/CatalogSearch/Test/Mftf/ActionGroup/StorefrontCatalogSearchActionGroup.xml b/app/code/Magento/CatalogSearch/Test/Mftf/ActionGroup/StorefrontCatalogSearchActionGroup.xml
index 067d76821d687..a72762ff796e0 100644
--- a/app/code/Magento/CatalogSearch/Test/Mftf/ActionGroup/StorefrontCatalogSearchActionGroup.xml
+++ b/app/code/Magento/CatalogSearch/Test/Mftf/ActionGroup/StorefrontCatalogSearchActionGroup.xml
@@ -10,10 +10,14 @@
xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd">
+
+ Submits Form POST for the Storefront Search feature. Validates that the URL is correct. Validates that the Title is present and correct.
+
-
+
-
+
+
@@ -22,11 +26,15 @@
+
+ Fill the Storefront Search field. Submits the Form. Validates that the provided Search Phrase is present and correct.
+
+
-
+
@@ -35,21 +43,29 @@
+
+ Clicks on the provided Product Name from the Storefront Quick Search Results page.
+
+
-
+
+
+ Adds the provided Product Name to the Shopping Cart from the Storefront Quick Search Results page. Validates that the Success Message is present and correct.
+
+
@@ -57,23 +73,42 @@
+
+ Validates that the provided Product Name appears at the correct index on the Storefront Quick Search page.
+
+
+
+
+
+
+
+
+
+
+ Validates that the provided Product Name does NOT appear on the Storefront Quick Search page.
+
+
-
+
+ Clicks on 'Advanced Search' in the Storefront Footer. Validates that the URL and Title are present and correct.
+
+
+
@@ -81,6 +116,10 @@
+
+ Validates that the URL and Title are present and correct on the Storefront Advanced Search Results page.
+
+
@@ -88,25 +127,37 @@
+
+ Clicks on Category Filter. Clicks on the provided Category.
+
-
+
+
+
+ Goes to the Storefront 'Advanced Search' page.
+
+
+
+ Fills the Product Name field on the Storefront 'Advanced Search' page. Clicks on the Submit button.
+
+
@@ -114,9 +165,13 @@
+
+ Fills the Product SKU field on the Storefront 'Advanced Search' page. Clicks on the Submit button.
+
+
@@ -124,9 +179,13 @@
+
+ Fills the Product Description field on the Storefront 'Advanced Search' page. Clicks on the Submit button.
+
+
@@ -134,9 +193,13 @@
+
+ Fills the Product Short Description field on the Storefront 'Advanced Search' page. Clicks on the Submit button.
+
+
@@ -144,11 +207,15 @@
+
+ Fills the Product Name, Price From and Price To fields on the Storefront 'Advanced Search' page. Clicks on the Submit button.
+
+
@@ -158,10 +225,14 @@
+
+ Fills the Product Name and Description fields on the Storefront 'Advanced Search' page. Clicks on the Submit button.
+
+
@@ -170,6 +241,10 @@
+
+ Validates that the 'No Results' message is present and correct on the Storefront Search Results page. PLEASE NOTE: The expected message is Hardcoded.
+
+
-
\ No newline at end of file
+
diff --git a/app/code/Magento/CatalogSearch/Test/Mftf/ActionGroup/StorefrontCatalogSearchTermActionGroup.xml b/app/code/Magento/CatalogSearch/Test/Mftf/ActionGroup/StorefrontCatalogSearchTermActionGroup.xml
index 83e4ac50a74e6..e1f0396b68c15 100644
--- a/app/code/Magento/CatalogSearch/Test/Mftf/ActionGroup/StorefrontCatalogSearchTermActionGroup.xml
+++ b/app/code/Magento/CatalogSearch/Test/Mftf/ActionGroup/StorefrontCatalogSearchTermActionGroup.xml
@@ -10,10 +10,14 @@
xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd">
+
+ Goes to the Storefront. Fills the Search field with the provided Search Query. Clicks on Search. Validates that there are no results.
+
+
@@ -24,14 +28,18 @@
+
+ Fills the Storefront Search field with the provided Search Query. Clicks on Search. Validates that the URL is correct.
+
+
-
\ No newline at end of file
+
diff --git a/app/code/Magento/CatalogSearch/Test/Mftf/Section/AdminCatalogSearchTermIndexSection.xml b/app/code/Magento/CatalogSearch/Test/Mftf/Section/AdminCatalogSearchTermIndexSection.xml
index aa0145b9f96cd..dcaf7fb3a561d 100644
--- a/app/code/Magento/CatalogSearch/Test/Mftf/Section/AdminCatalogSearchTermIndexSection.xml
+++ b/app/code/Magento/CatalogSearch/Test/Mftf/Section/AdminCatalogSearchTermIndexSection.xml
@@ -21,5 +21,6 @@
+
\ No newline at end of file
diff --git a/app/code/Magento/CatalogSearch/Test/Mftf/Test/LayerNavigationOfCatalogSearchTest.xml b/app/code/Magento/CatalogSearch/Test/Mftf/Test/LayerNavigationOfCatalogSearchTest.xml
new file mode 100644
index 0000000000000..210b474af2e02
--- /dev/null
+++ b/app/code/Magento/CatalogSearch/Test/Mftf/Test/LayerNavigationOfCatalogSearchTest.xml
@@ -0,0 +1,88 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/CatalogSearch/Test/Unit/Model/Adapter/Mysql/Filter/PreprocessorTest.php b/app/code/Magento/CatalogSearch/Test/Unit/Model/Adapter/Mysql/Filter/PreprocessorTest.php
index 7e3de7534e8c4..a79ffcc33cabe 100644
--- a/app/code/Magento/CatalogSearch/Test/Unit/Model/Adapter/Mysql/Filter/PreprocessorTest.php
+++ b/app/code/Magento/CatalogSearch/Test/Unit/Model/Adapter/Mysql/Filter/PreprocessorTest.php
@@ -129,7 +129,7 @@ protected function setUp()
->getMock();
$this->connection = $this->getMockBuilder(\Magento\Framework\DB\Adapter\AdapterInterface::class)
->disableOriginalConstructor()
- ->setMethods(['select', 'getIfNullSql', 'quote'])
+ ->setMethods(['select', 'getIfNullSql', 'quote', 'quoteInto'])
->getMockForAbstractClass();
$this->select = $this->getMockBuilder(\Magento\Framework\DB\Select::class)
->disableOriginalConstructor()
@@ -222,9 +222,10 @@ public function testProcessPrice()
public function processCategoryIdsDataProvider()
{
return [
- ['5', 'category_ids_index.category_id = 5'],
- [3, 'category_ids_index.category_id = 3'],
- ["' and 1 = 0", 'category_ids_index.category_id = 0'],
+ ['5', "category_ids_index.category_id in ('5')"],
+ [3, "category_ids_index.category_id in (3)"],
+ ["' and 1 = 0", "category_ids_index.category_id in ('\' and 1 = 0')"],
+ [['5', '10'], "category_ids_index.category_id in ('5', '10')"]
];
}
@@ -251,6 +252,12 @@ public function testProcessCategoryIds($categoryId, $expectedResult)
->with(\Magento\Catalog\Model\Product::ENTITY, 'category_ids')
->will($this->returnValue($this->attribute));
+ $this->connection
+ ->expects($this->once())
+ ->method('quoteInto')
+ ->with('category_ids_index.category_id in (?)', $categoryId)
+ ->willReturn($expectedResult);
+
$actualResult = $this->target->process($this->filter, $isNegation, $query);
$this->assertSame($expectedResult, $this->removeWhitespaces($actualResult));
}
diff --git a/app/code/Magento/CatalogSearch/Test/Unit/Model/Layer/Filter/PriceTest.php b/app/code/Magento/CatalogSearch/Test/Unit/Model/Layer/Filter/PriceTest.php
index abad58a6876d3..f783f75a170e3 100644
--- a/app/code/Magento/CatalogSearch/Test/Unit/Model/Layer/Filter/PriceTest.php
+++ b/app/code/Magento/CatalogSearch/Test/Unit/Model/Layer/Filter/PriceTest.php
@@ -3,6 +3,7 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
+declare(strict_types=1);
namespace Magento\CatalogSearch\Test\Unit\Model\Layer\Filter;
@@ -208,6 +209,12 @@ public function testApply()
$priceId = '15-50';
$requestVar = 'test_request_var';
+ $this->target->setAttributeModel($this->attribute);
+ $attributeCode = 'price';
+ $this->attribute->expects($this->any())
+ ->method('getAttributeCode')
+ ->will($this->returnValue($attributeCode));
+
$this->target->setRequestVar($requestVar);
$this->request->expects($this->exactly(1))
->method('getParam')
diff --git a/app/code/Magento/CatalogSearch/Test/Unit/Model/ResourceModel/Advanced/CollectionTest.php b/app/code/Magento/CatalogSearch/Test/Unit/Model/ResourceModel/Advanced/CollectionTest.php
index 683070c286239..f5e5a34047aff 100644
--- a/app/code/Magento/CatalogSearch/Test/Unit/Model/ResourceModel/Advanced/CollectionTest.php
+++ b/app/code/Magento/CatalogSearch/Test/Unit/Model/ResourceModel/Advanced/CollectionTest.php
@@ -14,6 +14,7 @@
use Magento\CatalogSearch\Test\Unit\Model\ResourceModel\BaseCollection;
use Magento\CatalogSearch\Model\ResourceModel\Fulltext\Collection\SearchResultApplierFactory;
use Magento\CatalogSearch\Model\ResourceModel\Fulltext\Collection\TotalRecordsResolverFactory;
+use PHPUnit\Framework\MockObject\MockObject;
/**
* Tests Magento\CatalogSearch\Model\ResourceModel\Advanced\Collection
@@ -35,32 +36,37 @@ class CollectionTest extends BaseCollection
private $advancedCollection;
/**
- * @var \Magento\Framework\Api\FilterBuilder|\PHPUnit_Framework_MockObject_MockObject
+ * @var \Magento\Framework\Api\FilterBuilder|MockObject
*/
private $filterBuilder;
/**
- * @var \Magento\Framework\Api\Search\SearchCriteriaBuilder|\PHPUnit_Framework_MockObject_MockObject
+ * @var \Magento\Framework\Api\Search\SearchCriteriaBuilder|MockObject
*/
private $criteriaBuilder;
/**
- * @var \Magento\Framework\Search\Adapter\Mysql\TemporaryStorageFactory|\PHPUnit_Framework_MockObject_MockObject
+ * @var \Magento\Framework\Search\Adapter\Mysql\TemporaryStorageFactory|MockObject
*/
private $temporaryStorageFactory;
/**
- * @var \Magento\Search\Api\SearchInterface|\PHPUnit_Framework_MockObject_MockObject
+ * @var \Magento\Search\Api\SearchInterface|MockObject
*/
private $search;
/**
- * @var \Magento\Eav\Model\Config|\PHPUnit_Framework_MockObject_MockObject
+ * @var \Magento\Eav\Model\Config|MockObject
*/
private $eavConfig;
/**
- * setUp method for CollectionTest
+ * @var SearchResultApplierFactory|MockObject
+ */
+ private $searchResultApplierFactory;
+
+ /**
+ * @inheritdoc
*/
protected function setUp()
{
@@ -97,17 +103,10 @@ protected function setUp()
->method('create')
->willReturn($searchCriteriaResolver);
- $searchResultApplier = $this->getMockBuilder(SearchResultApplierInterface::class)
- ->disableOriginalConstructor()
- ->setMethods(['apply'])
- ->getMockForAbstractClass();
- $searchResultApplierFactory = $this->getMockBuilder(SearchResultApplierFactory::class)
+ $this->searchResultApplierFactory = $this->getMockBuilder(SearchResultApplierFactory::class)
->disableOriginalConstructor()
->setMethods(['create'])
->getMock();
- $searchResultApplierFactory->expects($this->any())
- ->method('create')
- ->willReturn($searchResultApplier);
$totalRecordsResolver = $this->getMockBuilder(TotalRecordsResolverInterface::class)
->disableOriginalConstructor()
@@ -134,12 +133,15 @@ protected function setUp()
'productLimitationFactory' => $productLimitationFactoryMock,
'collectionProvider' => null,
'searchCriteriaResolverFactory' => $searchCriteriaResolverFactory,
- 'searchResultApplierFactory' => $searchResultApplierFactory,
+ 'searchResultApplierFactory' => $this->searchResultApplierFactory,
'totalRecordsResolverFactory' => $totalRecordsResolverFactory
]
);
}
+ /**
+ * Test to Load data with filter in place
+ */
public function testLoadWithFilterNoFilters()
{
$this->advancedCollection->loadWithFilter();
@@ -150,6 +152,7 @@ public function testLoadWithFilterNoFilters()
*/
public function testLike()
{
+ $pageSize = 10;
$attributeCode = 'description';
$attributeCodeId = 42;
$attribute = $this->createMock(\Magento\Catalog\Model\ResourceModel\Eav\Attribute::class);
@@ -168,6 +171,22 @@ public function testLike()
$searchResult = $this->createMock(\Magento\Framework\Api\Search\SearchResultInterface::class);
$this->search->expects($this->once())->method('search')->willReturn($searchResult);
+ $this->advancedCollection->setPageSize($pageSize);
+ $this->advancedCollection->setCurPage(0);
+
+ $searchResultApplier = $this->createMock(SearchResultApplierInterface::class);
+ $this->searchResultApplierFactory->expects($this->once())
+ ->method('create')
+ ->with(
+ [
+ 'collection' => $this->advancedCollection,
+ 'searchResult' => $searchResult,
+ 'orders' => [],
+ 'size' => $pageSize,
+ ]
+ )
+ ->willReturn($searchResultApplier);
+
// addFieldsToFilter will load filters,
// then loadWithFilter will trigger _renderFiltersBefore code in Advanced/Collection
$this->assertSame(
@@ -177,7 +196,7 @@ public function testLike()
}
/**
- * @return \PHPUnit_Framework_MockObject_MockObject
+ * @return MockObject
*/
protected function getCriteriaBuilder()
{
@@ -185,6 +204,7 @@ protected function getCriteriaBuilder()
->setMethods(['addFilter', 'create', 'setRequestName'])
->disableOriginalConstructor()
->getMock();
+
return $criteriaBuilder;
}
}
diff --git a/app/code/Magento/CatalogSearch/Test/Unit/Model/ResourceModel/Fulltext/CollectionTest.php b/app/code/Magento/CatalogSearch/Test/Unit/Model/ResourceModel/Fulltext/CollectionTest.php
index 9170b81dc3182..9b4010cfae453 100644
--- a/app/code/Magento/CatalogSearch/Test/Unit/Model/ResourceModel/Fulltext/CollectionTest.php
+++ b/app/code/Magento/CatalogSearch/Test/Unit/Model/ResourceModel/Fulltext/CollectionTest.php
@@ -5,6 +5,7 @@
*/
namespace Magento\CatalogSearch\Test\Unit\Model\ResourceModel\Fulltext;
+use Magento\Catalog\Model\ResourceModel\Product\Collection\ProductLimitationFactory;
use Magento\CatalogSearch\Model\ResourceModel\Fulltext\Collection\SearchCriteriaResolverFactory;
use Magento\CatalogSearch\Model\ResourceModel\Fulltext\Collection\SearchCriteriaResolverInterface;
use Magento\CatalogSearch\Model\ResourceModel\Fulltext\Collection\SearchResultApplierFactory;
@@ -12,11 +13,12 @@
use Magento\CatalogSearch\Model\ResourceModel\Fulltext\Collection\SearchResultApplierInterface;
use Magento\CatalogSearch\Model\ResourceModel\Fulltext\Collection\TotalRecordsResolverInterface;
use Magento\CatalogSearch\Test\Unit\Model\ResourceModel\BaseCollection;
+use PHPUnit\Framework\MockObject\MockObject;
use Magento\Framework\Search\Adapter\Mysql\TemporaryStorageFactory;
-use PHPUnit_Framework_MockObject_MockObject as MockObject;
-use Magento\Catalog\Model\ResourceModel\Product\Collection\ProductLimitationFactory;
/**
+ * Test class for Fulltext Collection
+ *
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
*/
class CollectionTest extends BaseCollection
@@ -27,12 +29,12 @@ class CollectionTest extends BaseCollection
private $objectManager;
/**
- * @var \Magento\Framework\Search\Adapter\Mysql\TemporaryStorage|\PHPUnit_Framework_MockObject_MockObject
+ * @var \Magento\Framework\Search\Adapter\Mysql\TemporaryStorage|MockObject
*/
private $temporaryStorage;
/**
- * @var \Magento\Search\Api\SearchInterface|\PHPUnit_Framework_MockObject_MockObject
+ * @var \Magento\Search\Api\SearchInterface|MockObject
*/
private $search;
@@ -61,6 +63,11 @@ class CollectionTest extends BaseCollection
*/
private $filterBuilder;
+ /**
+ * @var SearchResultApplierFactory|MockObject
+ */
+ private $searchResultApplierFactory;
+
/**
* @var \Magento\CatalogSearch\Model\ResourceModel\Fulltext\Collection
*/
@@ -72,7 +79,7 @@ class CollectionTest extends BaseCollection
private $filter;
/**
- * setUp method for CollectionTest
+ * @inheritdoc
*/
protected function setUp()
{
@@ -115,17 +122,10 @@ protected function setUp()
->method('create')
->willReturn($searchCriteriaResolver);
- $searchResultApplier = $this->getMockBuilder(SearchResultApplierInterface::class)
- ->disableOriginalConstructor()
- ->setMethods(['apply'])
- ->getMockForAbstractClass();
- $searchResultApplierFactory = $this->getMockBuilder(SearchResultApplierFactory::class)
+ $this->searchResultApplierFactory = $this->getMockBuilder(SearchResultApplierFactory::class)
->disableOriginalConstructor()
->setMethods(['create'])
->getMock();
- $searchResultApplierFactory->expects($this->any())
- ->method('create')
- ->willReturn($searchResultApplier);
$totalRecordsResolver = $this->getMockBuilder(TotalRecordsResolverInterface::class)
->disableOriginalConstructor()
@@ -148,7 +148,7 @@ protected function setUp()
'temporaryStorageFactory' => $temporaryStorageFactory,
'productLimitationFactory' => $productLimitationFactoryMock,
'searchCriteriaResolverFactory' => $searchCriteriaResolverFactory,
- 'searchResultApplierFactory' => $searchResultApplierFactory,
+ 'searchResultApplierFactory' => $this->searchResultApplierFactory,
'totalRecordsResolverFactory' => $totalRecordsResolverFactory,
]
);
@@ -161,6 +161,9 @@ protected function setUp()
$this->model->setFilterBuilder($this->filterBuilder);
}
+ /**
+ * @inheritdoc
+ */
protected function tearDown()
{
$reflectionProperty = new \ReflectionProperty(\Magento\Framework\App\ObjectManager::class, '_instance');
@@ -168,16 +171,49 @@ protected function tearDown()
$reflectionProperty->setValue(null);
}
+ /**
+ * Test to Return field faceted data from faceted search result
+ */
public function testGetFacetedDataWithEmptyAggregations()
{
+ $pageSize = 10;
+
$searchResult = $this->getMockBuilder(\Magento\Framework\Api\Search\SearchResultInterface::class)
->getMockForAbstractClass();
$this->search->expects($this->once())
->method('search')
->willReturn($searchResult);
+
+ $searchResultApplier = $this->getMockBuilder(SearchResultApplierInterface::class)
+ ->disableOriginalConstructor()
+ ->setMethods(['apply'])
+ ->getMockForAbstractClass();
+ $this->searchResultApplierFactory->expects($this->any())
+ ->method('create')
+ ->willReturn($searchResultApplier);
+
+ $this->model->setPageSize($pageSize);
+ $this->model->setCurPage(0);
+
+ $this->searchResultApplierFactory->expects($this->once())
+ ->method('create')
+ ->with(
+ [
+ 'collection' => $this->model,
+ 'searchResult' => $searchResult,
+ 'orders' => [],
+ 'size' => $pageSize,
+ 'currentPage' => 0,
+ ]
+ )
+ ->willReturn($searchResultApplier);
+
$this->model->getFacetedData('field');
}
+ /**
+ * Test to Apply attribute filter to facet collection
+ */
public function testAddFieldToFilter()
{
$this->filter = $this->createFilter();
@@ -220,6 +256,7 @@ protected function getCriteriaBuilder()
protected function getFilterBuilder()
{
$filterBuilder = $this->createMock(\Magento\Framework\Api\FilterBuilder::class);
+
return $filterBuilder;
}
@@ -241,6 +278,7 @@ protected function addFiltersToFilterBuilder(MockObject $filterBuilder, array $f
->with($value)
->willReturnSelf();
}
+
return $filterBuilder;
}
@@ -252,6 +290,7 @@ protected function createFilter()
$filter = $this->getMockBuilder(\Magento\Framework\Api\Filter::class)
->disableOriginalConstructor()
->getMock();
+
return $filter;
}
}
diff --git a/app/code/Magento/CatalogSearch/Test/Unit/Model/Search/RequestGenerator/DecimalTest.php b/app/code/Magento/CatalogSearch/Test/Unit/Model/Search/RequestGenerator/DecimalTest.php
index 8157c1fa8fa82..350344372612a 100644
--- a/app/code/Magento/CatalogSearch/Test/Unit/Model/Search/RequestGenerator/DecimalTest.php
+++ b/app/code/Magento/CatalogSearch/Test/Unit/Model/Search/RequestGenerator/DecimalTest.php
@@ -3,6 +3,7 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
+declare(strict_types=1);
namespace Magento\CatalogSearch\Test\Unit\Model\Search\RequestGenerator;
@@ -11,6 +12,9 @@
use Magento\Framework\Search\Request\BucketInterface;
use Magento\Framework\Search\Request\FilterInterface;
+/**
+ * Test catalog search range request generator.
+ */
class DecimalTest extends \PHPUnit\Framework\TestCase
{
/** @var Decimal */
diff --git a/app/code/Magento/CatalogSearch/Test/Unit/Model/Search/RequestGenerator/PriceTest.php b/app/code/Magento/CatalogSearch/Test/Unit/Model/Search/RequestGenerator/PriceTest.php
new file mode 100644
index 0000000000000..3635430197591
--- /dev/null
+++ b/app/code/Magento/CatalogSearch/Test/Unit/Model/Search/RequestGenerator/PriceTest.php
@@ -0,0 +1,82 @@
+attribute = $this->getMockBuilder(Attribute::class)
+ ->disableOriginalConstructor()
+ ->setMethods(['getAttributeCode'])
+ ->getMockForAbstractClass();
+ $this->scopeConfigMock = $this->getMockBuilder(ScopeConfigInterface::class)
+ ->setMethods(['getValue'])
+ ->getMockForAbstractClass();
+ $objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this);
+ $this->price = $objectManager->getObject(
+ Price::class,
+ ['scopeConfig' => $this->scopeConfigMock]
+ );
+ }
+
+ public function testGetFilterData()
+ {
+ $filterName = 'test_filter_name';
+ $attributeCode = 'test_attribute_code';
+ $expected = [
+ 'type' => FilterInterface::TYPE_RANGE,
+ 'name' => $filterName,
+ 'field' => $attributeCode,
+ 'from' => '$' . $attributeCode . '.from$',
+ 'to' => '$' . $attributeCode . '.to$',
+ ];
+ $this->attribute->expects($this->atLeastOnce())
+ ->method('getAttributeCode')
+ ->willReturn($attributeCode);
+ $actual = $this->price->getFilterData($this->attribute, $filterName);
+ $this->assertEquals($expected, $actual);
+ }
+
+ public function testGetAggregationData()
+ {
+ $bucketName = 'test_bucket_name';
+ $attributeCode = 'test_attribute_code';
+ $method = 'price_dynamic_algorithm';
+ $expected = [
+ 'type' => BucketInterface::TYPE_DYNAMIC,
+ 'name' => $bucketName,
+ 'field' => $attributeCode,
+ 'method' => '$'. $method . '$',
+ 'metric' => [['type' => 'count']],
+ ];
+ $this->attribute->expects($this->atLeastOnce())
+ ->method('getAttributeCode')
+ ->willReturn($attributeCode);
+ $actual = $this->price->getAggregationData($this->attribute, $bucketName);
+ $this->assertEquals($expected, $actual);
+ }
+}
diff --git a/app/code/Magento/CatalogSearch/Ui/DataProvider/Product/AddFulltextFilterToCollection.php b/app/code/Magento/CatalogSearch/Ui/DataProvider/Product/AddFulltextFilterToCollection.php
index 1b05152903aa9..f312178e0bf0b 100644
--- a/app/code/Magento/CatalogSearch/Ui/DataProvider/Product/AddFulltextFilterToCollection.php
+++ b/app/code/Magento/CatalogSearch/Ui/DataProvider/Product/AddFulltextFilterToCollection.php
@@ -5,8 +5,8 @@
*/
namespace Magento\CatalogSearch\Ui\DataProvider\Product;
-use Magento\Framework\Data\Collection;
use Magento\CatalogSearch\Model\ResourceModel\Search\Collection as SearchCollection;
+use Magento\Framework\Data\Collection;
use Magento\Ui\DataProvider\AddFilterToCollectionInterface;
/**
@@ -30,14 +30,14 @@ public function __construct(SearchCollection $searchCollection)
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
*/
public function addFilter(Collection $collection, $field, $condition = null)
{
/** @var $collection \Magento\Catalog\Model\ResourceModel\Product\Collection */
- if (isset($condition['fulltext']) && !empty($condition['fulltext'])) {
+ if (isset($condition['fulltext']) && (string)$condition['fulltext'] !== '') {
$this->searchCollection->addBackendSearchFilter($condition['fulltext']);
$productIds = $this->searchCollection->load()->getAllIds();
$collection->addIdFilter($productIds);
diff --git a/app/code/Magento/CatalogSearch/composer.json b/app/code/Magento/CatalogSearch/composer.json
index 7bcb91e945417..5bc04e0420c3a 100644
--- a/app/code/Magento/CatalogSearch/composer.json
+++ b/app/code/Magento/CatalogSearch/composer.json
@@ -5,7 +5,7 @@
"sort-packages": true
},
"require": {
- "php": "~7.1.3||~7.2.0",
+ "php": "~7.1.3||~7.2.0||~7.3.0",
"magento/framework": "*",
"magento/module-backend": "*",
"magento/module-catalog": "*",
diff --git a/app/code/Magento/CatalogSearch/etc/di.xml b/app/code/Magento/CatalogSearch/etc/di.xml
index 7359bd6b454b9..da0a60dad1f77 100644
--- a/app/code/Magento/CatalogSearch/etc/di.xml
+++ b/app/code/Magento/CatalogSearch/etc/di.xml
@@ -200,6 +200,7 @@
+ - Magento\CatalogSearch\Model\ResourceModel\Advanced\CollectionFactory
- Magento\CatalogSearch\Model\ResourceModel\Advanced\CollectionFactory
@@ -207,6 +208,7 @@
+ - Magento\CatalogSearch\Model\Advanced\ProductCollectionPrepareStrategy
- Magento\CatalogSearch\Model\Advanced\ProductCollectionPrepareStrategy
@@ -279,6 +281,7 @@
\Magento\CatalogSearch\Model\Search\RequestGenerator\General
- Magento\CatalogSearch\Model\Search\RequestGenerator\Decimal
+ - Magento\CatalogSearch\Model\Search\RequestGenerator\Price
diff --git a/app/code/Magento/CatalogUrlRewrite/Observer/AfterImportDataObserver.php b/app/code/Magento/CatalogUrlRewrite/Observer/AfterImportDataObserver.php
index 33c0cafc8f081..704b60a8aaf2a 100644
--- a/app/code/Magento/CatalogUrlRewrite/Observer/AfterImportDataObserver.php
+++ b/app/code/Magento/CatalogUrlRewrite/Observer/AfterImportDataObserver.php
@@ -299,12 +299,16 @@ protected function _populateForUrlGeneration($rowData)
*/
private function isNeedToPopulateForUrlGeneration($rowData, $newSku, $oldSku): bool
{
- if ((empty($newSku) || !isset($newSku['entity_id']))
- || ($this->import->getRowScope($rowData) == ImportProduct::SCOPE_STORE
- && empty($rowData[self::URL_KEY_ATTRIBUTE_CODE]))
- || (array_key_exists($rowData[ImportProduct::COL_SKU], $oldSku)
- && !isset($rowData[self::URL_KEY_ATTRIBUTE_CODE])
- && $this->import->getBehavior() === ImportExport::BEHAVIOR_APPEND)) {
+ if ((
+ (empty($newSku) || !isset($newSku['entity_id']))
+ || ($this->import->getRowScope($rowData) == ImportProduct::SCOPE_STORE
+ && empty($rowData[self::URL_KEY_ATTRIBUTE_CODE]))
+ || (array_key_exists(strtolower($rowData[ImportProduct::COL_SKU]), $oldSku)
+ && !isset($rowData[self::URL_KEY_ATTRIBUTE_CODE])
+ && $this->import->getBehavior() === ImportExport::BEHAVIOR_APPEND)
+ )
+ && !isset($rowData["categories"])
+ ) {
return false;
}
return true;
@@ -477,7 +481,7 @@ protected function currentUrlRewritesRegenerate()
$url = $currentUrlRewrite->getIsAutogenerated()
? $this->generateForAutogenerated($currentUrlRewrite, $category)
: $this->generateForCustom($currentUrlRewrite, $category);
- $urlRewrites = array_merge($urlRewrites, $url);
+ $urlRewrites = $url + $urlRewrites;
}
$this->product = null;
diff --git a/app/code/Magento/CatalogUrlRewrite/Observer/CategoryUrlPathAutogeneratorObserver.php b/app/code/Magento/CatalogUrlRewrite/Observer/CategoryUrlPathAutogeneratorObserver.php
index 713dd6ac0c736..7f987124040fd 100644
--- a/app/code/Magento/CatalogUrlRewrite/Observer/CategoryUrlPathAutogeneratorObserver.php
+++ b/app/code/Magento/CatalogUrlRewrite/Observer/CategoryUrlPathAutogeneratorObserver.php
@@ -9,7 +9,6 @@
use Magento\CatalogUrlRewrite\Model\CategoryUrlPathGenerator;
use Magento\CatalogUrlRewrite\Service\V1\StoreViewService;
use Magento\Catalog\Api\CategoryRepositoryInterface;
-use Magento\Framework\Event\Observer;
use Magento\CatalogUrlRewrite\Model\Category\ChildrenCategoriesProvider;
use Magento\Framework\Event\ObserverInterface;
use Magento\Store\Model\Store;
@@ -68,13 +67,15 @@ public function execute(\Magento\Framework\Event\Observer $observer)
{
/** @var Category $category */
$category = $observer->getEvent()->getCategory();
- $useDefaultAttribute = !$category->isObjectNew() && !empty($category->getData('use_default')['url_key']);
+ $useDefaultAttribute = !empty($category->getData('use_default')['url_key']);
if ($category->getUrlKey() !== false && !$useDefaultAttribute) {
$resultUrlKey = $this->categoryUrlPathGenerator->getUrlKey($category);
$this->updateUrlKey($category, $resultUrlKey);
- } else if ($useDefaultAttribute) {
- $resultUrlKey = $category->formatUrlKey($category->getOrigData('name'));
- $this->updateUrlKey($category, $resultUrlKey);
+ } elseif ($useDefaultAttribute) {
+ if (!$category->isObjectNew()) {
+ $resultUrlKey = $category->formatUrlKey($category->getOrigData('name'));
+ $this->updateUrlKey($category, $resultUrlKey);
+ }
$category->setUrlKey(null)->setUrlPath(null);
}
}
diff --git a/app/code/Magento/CatalogUrlRewrite/Setup/Patch/Data/UpdateUrlKeySearchable.php b/app/code/Magento/CatalogUrlRewrite/Setup/Patch/Data/UpdateUrlKeySearchable.php
new file mode 100644
index 0000000000000..75f88a8573069
--- /dev/null
+++ b/app/code/Magento/CatalogUrlRewrite/Setup/Patch/Data/UpdateUrlKeySearchable.php
@@ -0,0 +1,79 @@
+moduleDataSetup = $moduleDataSetup;
+ $this->categorySetupFactory = $categorySetupFactory;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function apply()
+ {
+ /** @var CategorySetup $categorySetup */
+ $categorySetup = $this->categorySetupFactory->create(['setup' => $this->moduleDataSetup]);
+
+ $categorySetup->updateAttribute(
+ \Magento\Catalog\Model\Product::ENTITY,
+ 'url_key',
+ 'is_searchable',
+ true
+ );
+
+ $categorySetup->updateAttribute(
+ \Magento\Catalog\Model\Category::ENTITY,
+ 'url_key',
+ 'is_searchable',
+ true
+ );
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public static function getDependencies()
+ {
+ return [CreateUrlAttributes::class];
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function getAliases()
+ {
+ return [];
+ }
+}
diff --git a/app/code/Magento/CatalogUrlRewrite/Test/Unit/Observer/CategoryUrlPathAutogeneratorObserverTest.php b/app/code/Magento/CatalogUrlRewrite/Test/Unit/Observer/CategoryUrlPathAutogeneratorObserverTest.php
index 1b4d1e08aa208..0a570adab309a 100644
--- a/app/code/Magento/CatalogUrlRewrite/Test/Unit/Observer/CategoryUrlPathAutogeneratorObserverTest.php
+++ b/app/code/Magento/CatalogUrlRewrite/Test/Unit/Observer/CategoryUrlPathAutogeneratorObserverTest.php
@@ -3,37 +3,51 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
+
namespace Magento\CatalogUrlRewrite\Test\Unit\Observer;
use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper;
class CategoryUrlPathAutogeneratorObserverTest extends \PHPUnit\Framework\TestCase
{
- /** @var \Magento\CatalogUrlRewrite\Observer\CategoryUrlPathAutogeneratorObserver */
- protected $categoryUrlPathAutogeneratorObserver;
+ /**
+ * @var \Magento\CatalogUrlRewrite\Observer\CategoryUrlPathAutogeneratorObserver
+ */
+ private $categoryUrlPathAutogeneratorObserver;
- /** @var \PHPUnit_Framework_MockObject_MockObject */
- protected $categoryUrlPathGenerator;
+ /**
+ * @var \PHPUnit_Framework_MockObject_MockObject
+ */
+ private $categoryUrlPathGenerator;
- /** @var \PHPUnit_Framework_MockObject_MockObject */
- protected $childrenCategoriesProvider;
+ /**
+ * @var \PHPUnit_Framework_MockObject_MockObject
+ */
+ private $childrenCategoriesProvider;
- /** @var \PHPUnit_Framework_MockObject_MockObject */
- protected $observer;
+ /**
+ * @var \PHPUnit_Framework_MockObject_MockObject
+ */
+ private $observer;
- /** @var \PHPUnit_Framework_MockObject_MockObject */
- protected $category;
+ /**
+ * @var \PHPUnit_Framework_MockObject_MockObject
+ */
+ private $category;
/**
* @var \Magento\CatalogUrlRewrite\Service\V1\StoreViewService|\PHPUnit_Framework_MockObject_MockObject
*/
- protected $storeViewService;
+ private $storeViewService;
/**
* @var \Magento\Catalog\Model\ResourceModel\Category|\PHPUnit_Framework_MockObject_MockObject
*/
- protected $categoryResource;
+ private $categoryResource;
+ /**
+ * @inheritDoc
+ */
protected function setUp()
{
$this->observer = $this->createPartialMock(
@@ -41,16 +55,15 @@ protected function setUp()
['getEvent', 'getCategory']
);
$this->categoryResource = $this->createMock(\Magento\Catalog\Model\ResourceModel\Category::class);
- $this->category = $this->createPartialMock(\Magento\Catalog\Model\Category::class, [
- 'setUrlKey',
- 'setUrlPath',
+ $this->category = $this->createPartialMock(
+ \Magento\Catalog\Model\Category::class,
+ [
'dataHasChangedFor',
- 'isObjectNew',
'getResource',
- 'getUrlKey',
'getStoreId',
- 'getData'
- ]);
+ 'formatUrlKey'
+ ]
+ );
$this->category->expects($this->any())->method('getResource')->willReturn($this->categoryResource);
$this->observer->expects($this->any())->method('getEvent')->willReturnSelf();
$this->observer->expects($this->any())->method('getCategory')->willReturn($this->category);
@@ -73,106 +86,125 @@ protected function setUp()
);
}
- public function testSetCategoryUrlAndCategoryPath()
+ /**
+ * @param $isObjectNew
+ * @throws \Magento\Framework\Exception\LocalizedException
+ * @dataProvider shouldFormatUrlKeyAndGenerateUrlPathIfUrlKeyIsNotUsingDefaultValueDataProvider
+ */
+ public function testShouldFormatUrlKeyAndGenerateUrlPathIfUrlKeyIsNotUsingDefaultValue($isObjectNew)
{
- $this->category->expects($this->once())->method('getUrlKey')->willReturn('category');
- $this->categoryUrlPathGenerator->expects($this->once())->method('getUrlKey')->willReturn('urk_key');
- $this->category->expects($this->once())->method('setUrlKey')->with('urk_key')->willReturnSelf();
- $this->categoryUrlPathGenerator->expects($this->once())->method('getUrlPath')->willReturn('url_path');
- $this->category->expects($this->once())->method('setUrlPath')->with('url_path')->willReturnSelf();
- $this->category->expects($this->exactly(2))->method('isObjectNew')->willReturn(true);
-
+ $expectedUrlKey = 'formatted_url_key';
+ $expectedUrlPath = 'generated_url_path';
+ $categoryData = ['use_default' => ['url_key' => 0], 'url_key' => 'some_key', 'url_path' => ''];
+ $this->category->setData($categoryData);
+ $this->category->isObjectNew($isObjectNew);
+ $this->categoryUrlPathGenerator->expects($this->once())->method('getUrlKey')->willReturn($expectedUrlKey);
+ $this->categoryUrlPathGenerator->expects($this->once())->method('getUrlPath')->willReturn($expectedUrlPath);
+ $this->assertEquals($categoryData['url_key'], $this->category->getUrlKey());
+ $this->assertEquals($categoryData['url_path'], $this->category->getUrlPath());
$this->categoryUrlPathAutogeneratorObserver->execute($this->observer);
+ $this->assertEquals($expectedUrlKey, $this->category->getUrlKey());
+ $this->assertEquals($expectedUrlPath, $this->category->getUrlPath());
+ $this->categoryResource->expects($this->never())->method('saveAttribute');
}
- public function testExecuteWithoutUrlKeyAndUrlPathUpdating()
+ /**
+ * @return array
+ */
+ public function shouldFormatUrlKeyAndGenerateUrlPathIfUrlKeyIsNotUsingDefaultValueDataProvider()
{
- $this->category->expects($this->once())->method('getUrlKey')->willReturn(false);
- $this->category->expects($this->never())->method('setUrlKey');
- $this->category->expects($this->never())->method('setUrlPath');
- $this->categoryUrlPathAutogeneratorObserver->execute($this->observer);
+ return [
+ [true],
+ [false],
+ ];
}
/**
- * @expectedException \Magento\Framework\Exception\LocalizedException
- * @expectedExceptionMessage Invalid URL key
+ * @param $isObjectNew
+ * @throws \Magento\Framework\Exception\LocalizedException
+ * @dataProvider shouldResetUrlPathAndUrlKeyIfUrlKeyIsUsingDefaultValueDataProvider
*/
- public function testExecuteWithException()
+ public function testShouldResetUrlPathAndUrlKeyIfUrlKeyIsUsingDefaultValue($isObjectNew)
{
- $categoryName = 'test';
- $categoryData = ['url_key' => 0];
- $this->category->expects($this->once())->method('getUrlKey')->willReturn($categoryName);
- $this->category->expects($this->once())
- ->method('getData')
- ->with('use_default')
- ->willReturn($categoryData);
- $this->categoryUrlPathGenerator->expects($this->once())
- ->method('getUrlKey')
- ->with($this->category)
- ->willReturn(null);
+ $categoryData = ['use_default' => ['url_key' => 1], 'url_key' => 'some_key', 'url_path' => 'some_path'];
+ $this->category->setData($categoryData);
+ $this->category->isObjectNew($isObjectNew);
+ $this->category->expects($this->any())->method('formatUrlKey')->willReturn('formatted_key');
+ $this->assertEquals($categoryData['url_key'], $this->category->getUrlKey());
+ $this->assertEquals($categoryData['url_path'], $this->category->getUrlPath());
$this->categoryUrlPathAutogeneratorObserver->execute($this->observer);
+ $this->assertNull($this->category->getUrlKey());
+ $this->assertNull($this->category->getUrlPath());
}
- public function testUrlKeyAndUrlPathUpdating()
+ /**
+ * @return array
+ */
+ public function shouldResetUrlPathAndUrlKeyIfUrlKeyIsUsingDefaultValueDataProvider()
{
- $this->categoryUrlPathGenerator->expects($this->once())->method('getUrlKey')->with($this->category)
- ->willReturn('url_key');
- $this->categoryUrlPathGenerator->expects($this->once())->method('getUrlPath')->with($this->category)
- ->willReturn('url_path');
-
- $this->category->expects($this->once())->method('getUrlKey')->willReturn('not_formatted_url_key');
- $this->category->expects($this->once())->method('setUrlKey')->with('url_key')->willReturnSelf();
- $this->category->expects($this->once())->method('setUrlPath')->with('url_path')->willReturnSelf();
- // break code execution
- $this->category->expects($this->exactly(2))->method('isObjectNew')->willReturn(true);
+ return [
+ [true],
+ [false],
+ ];
+ }
+ /**
+ * @param $useDefaultUrlKey
+ * @param $isObjectNew
+ * @throws \Magento\Framework\Exception\LocalizedException
+ * @dataProvider shouldThrowExceptionIfUrlKeyIsEmptyDataProvider
+ */
+ public function testShouldThrowExceptionIfUrlKeyIsEmpty($useDefaultUrlKey, $isObjectNew)
+ {
+ $this->expectExceptionMessage('Invalid URL key');
+ $categoryData = ['use_default' => ['url_key' => $useDefaultUrlKey], 'url_key' => '', 'url_path' => ''];
+ $this->category->setData($categoryData);
+ $this->category->isObjectNew($isObjectNew);
+ $this->assertEquals($isObjectNew, $this->category->isObjectNew());
+ $this->assertEquals($categoryData['url_key'], $this->category->getUrlKey());
+ $this->assertEquals($categoryData['url_path'], $this->category->getUrlPath());
$this->categoryUrlPathAutogeneratorObserver->execute($this->observer);
+ $this->assertEquals($categoryData['url_key'], $this->category->getUrlKey());
+ $this->assertEquals($categoryData['url_path'], $this->category->getUrlPath());
}
- public function testUrlPathAttributeNoUpdatingIfCategoryIsNew()
+ /**
+ * @return array
+ */
+ public function shouldThrowExceptionIfUrlKeyIsEmptyDataProvider()
{
- $this->categoryUrlPathGenerator->expects($this->any())->method('getUrlKey')->willReturn('url_key');
- $this->categoryUrlPathGenerator->expects($this->any())->method('getUrlPath')->willReturn('url_path');
-
- $this->category->expects($this->any())->method('getUrlKey')->willReturn('not_formatted_url_key');
- $this->category->expects($this->any())->method('setUrlKey')->willReturnSelf();
- $this->category->expects($this->any())->method('setUrlPath')->willReturnSelf();
-
- $this->category->expects($this->exactly(2))->method('isObjectNew')->willReturn(true);
- $this->categoryResource->expects($this->never())->method('saveAttribute');
-
- $this->categoryUrlPathAutogeneratorObserver->execute($this->observer);
+ return [
+ [0, false],
+ [0, true],
+ [1, false],
+ ];
}
public function testUrlPathAttributeUpdating()
{
- $this->categoryUrlPathGenerator->expects($this->any())->method('getUrlKey')->willReturn('url_key');
- $this->categoryUrlPathGenerator->expects($this->any())->method('getUrlPath')->willReturn('url_path');
-
- $this->category->expects($this->any())->method('getUrlKey')->willReturn('not_formatted_url_key');
- $this->category->expects($this->any())->method('setUrlKey')->willReturnSelf();
- $this->category->expects($this->any())->method('setUrlPath')->willReturnSelf();
- $this->category->expects($this->exactly(2))->method('isObjectNew')->willReturn(false);
-
+ $categoryData = ['url_key' => 'some_key', 'url_path' => ''];
+ $this->category->setData($categoryData);
+ $this->category->isObjectNew(false);
+ $expectedUrlKey = 'formatted_url_key';
+ $expectedUrlPath = 'generated_url_path';
+ $this->categoryUrlPathGenerator->expects($this->any())->method('getUrlKey')->willReturn($expectedUrlKey);
+ $this->categoryUrlPathGenerator->expects($this->any())->method('getUrlPath')->willReturn($expectedUrlPath);
$this->categoryResource->expects($this->once())->method('saveAttribute')->with($this->category, 'url_path');
-
- // break code execution
$this->category->expects($this->once())->method('dataHasChangedFor')->with('url_path')->willReturn(false);
-
$this->categoryUrlPathAutogeneratorObserver->execute($this->observer);
}
public function testChildrenUrlPathAttributeNoUpdatingIfParentUrlPathIsNotChanged()
{
+ $categoryData = ['url_key' => 'some_key', 'url_path' => ''];
+ $this->category->setData($categoryData);
+ $this->category->isObjectNew(false);
+
$this->categoryUrlPathGenerator->expects($this->any())->method('getUrlKey')->willReturn('url_key');
$this->categoryUrlPathGenerator->expects($this->any())->method('getUrlPath')->willReturn('url_path');
$this->categoryResource->expects($this->once())->method('saveAttribute')->with($this->category, 'url_path');
- $this->category->expects($this->any())->method('getUrlKey')->willReturn('not_formatted_url_key');
- $this->category->expects($this->any())->method('setUrlKey')->willReturnSelf();
- $this->category->expects($this->any())->method('setUrlPath')->willReturnSelf();
- $this->category->expects($this->exactly(2))->method('isObjectNew')->willReturn(false);
// break code execution
$this->category->expects($this->once())->method('dataHasChangedFor')->with('url_path')->willReturn(false);
@@ -181,13 +213,12 @@ public function testChildrenUrlPathAttributeNoUpdatingIfParentUrlPathIsNotChange
public function testChildrenUrlPathAttributeUpdatingForSpecificStore()
{
+ $categoryData = ['url_key' => 'some_key', 'url_path' => ''];
+ $this->category->setData($categoryData);
+ $this->category->isObjectNew(false);
+
$this->categoryUrlPathGenerator->expects($this->any())->method('getUrlKey')->willReturn('generated_url_key');
$this->categoryUrlPathGenerator->expects($this->any())->method('getUrlPath')->willReturn('generated_url_path');
-
- $this->category->expects($this->any())->method('getUrlKey')->willReturn('not_formatted_url_key');
- $this->category->expects($this->any())->method('setUrlKey')->willReturnSelf();
- $this->category->expects($this->any())->method('setUrlPath')->willReturnSelf();
- $this->category->expects($this->exactly(2))->method('isObjectNew')->willReturn(false);
$this->category->expects($this->any())->method('dataHasChangedFor')->willReturn(true);
// only for specific store
$this->category->expects($this->atLeastOnce())->method('getStoreId')->willReturn(1);
@@ -195,15 +226,18 @@ public function testChildrenUrlPathAttributeUpdatingForSpecificStore()
$childCategoryResource = $this->getMockBuilder(\Magento\Catalog\Model\ResourceModel\Category::class)
->disableOriginalConstructor()->getMock();
$childCategory = $this->getMockBuilder(\Magento\Catalog\Model\Category::class)
- ->setMethods([
- 'getUrlPath',
- 'setUrlPath',
- 'getResource',
- 'getStore',
- 'getStoreId',
- 'setStoreId'
- ])
- ->disableOriginalConstructor()->getMock();
+ ->setMethods(
+ [
+ 'getUrlPath',
+ 'setUrlPath',
+ 'getResource',
+ 'getStore',
+ 'getStoreId',
+ 'setStoreId'
+ ]
+ )
+ ->disableOriginalConstructor()
+ ->getMock();
$childCategory->expects($this->any())->method('getResource')->willReturn($childCategoryResource);
$childCategory->expects($this->once())->method('setStoreId')->with(1);
diff --git a/app/code/Magento/CatalogUrlRewrite/composer.json b/app/code/Magento/CatalogUrlRewrite/composer.json
index b4ceff96b50b7..0245449384c4c 100644
--- a/app/code/Magento/CatalogUrlRewrite/composer.json
+++ b/app/code/Magento/CatalogUrlRewrite/composer.json
@@ -5,7 +5,7 @@
"sort-packages": true
},
"require": {
- "php": "~7.1.3||~7.2.0",
+ "php": "~7.1.3||~7.2.0||~7.3.0",
"magento/framework": "*",
"magento/module-backend": "*",
"magento/module-catalog": "*",
diff --git a/app/code/Magento/CatalogUrlRewrite/view/adminhtml/templates/confirm.phtml b/app/code/Magento/CatalogUrlRewrite/view/adminhtml/templates/confirm.phtml
index 698872054faec..800ecfd8a6e2f 100644
--- a/app/code/Magento/CatalogUrlRewrite/view/adminhtml/templates/confirm.phtml
+++ b/app/code/Magento/CatalogUrlRewrite/view/adminhtml/templates/confirm.phtml
@@ -3,9 +3,6 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
-
-// @codingStandardsIgnoreFile
-
?>
diff --git a/app/code/Magento/CurrencySymbol/view/adminhtml/web/js/symbols-form.js b/app/code/Magento/CurrencySymbol/view/adminhtml/web/js/symbols-form.js
new file mode 100644
index 0000000000000..68f914ddb1b4d
--- /dev/null
+++ b/app/code/Magento/CurrencySymbol/view/adminhtml/web/js/symbols-form.js
@@ -0,0 +1,39 @@
+/**
+ * Copyright © Magento, Inc. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+
+define([
+ 'jquery',
+ 'mage/mage'
+], function ($) {
+ 'use strict';
+
+ return function (config, element) {
+ $(element)
+ .mage('form')
+ .mage('validation');
+
+ /**
+ * Toggle the field to use the default value
+ *
+ * @param {String} code
+ * @param {String} value
+ */
+ function toggleUseDefault(code, value) {
+ var checkbox = $('#custom_currency_symbol_inherit' + code),
+ input = $('#custom_currency_symbol' + code);
+
+ if (checkbox.is(':checked')) {
+ input.addClass('disabled');
+ input.val(value);
+ input.prop('readonly', true);
+ } else {
+ input.removeClass('disabled');
+ input.prop('readonly', false);
+ }
+ }
+
+ window.toggleUseDefault = toggleUseDefault;
+ };
+});
diff --git a/app/code/Magento/Customer/Block/Address/Edit.php b/app/code/Magento/Customer/Block/Address/Edit.php
index afefb1138deac..ef9937a0cde8b 100644
--- a/app/code/Magento/Customer/Block/Address/Edit.php
+++ b/app/code/Magento/Customer/Block/Address/Edit.php
@@ -5,7 +5,8 @@
*/
namespace Magento\Customer\Block\Address;
-use Magento\Framework\Exception\LocalizedException;
+use Magento\Customer\Api\AddressMetadataInterface;
+use Magento\Framework\App\ObjectManager;
use Magento\Framework\Exception\NoSuchEntityException;
/**
@@ -48,7 +49,7 @@ class Edit extends \Magento\Directory\Block\Data
protected $dataObjectHelper;
/**
- * @var \Magento\Customer\Api\AddressMetadataInterface
+ * @var AddressMetadataInterface
*/
private $addressMetadata;
@@ -67,7 +68,7 @@ class Edit extends \Magento\Directory\Block\Data
* @param \Magento\Customer\Helper\Session\CurrentCustomer $currentCustomer
* @param \Magento\Framework\Api\DataObjectHelper $dataObjectHelper
* @param array $data
- * @param \Magento\Customer\Api\AddressMetadataInterface|null $addressMetadata
+ * @param AddressMetadataInterface|null $addressMetadata
*
* @SuppressWarnings(PHPMD.ExcessiveParameterList)
*/
@@ -84,14 +85,14 @@ public function __construct(
\Magento\Customer\Helper\Session\CurrentCustomer $currentCustomer,
\Magento\Framework\Api\DataObjectHelper $dataObjectHelper,
array $data = [],
- \Magento\Customer\Api\AddressMetadataInterface $addressMetadata = null
+ AddressMetadataInterface $addressMetadata = null
) {
$this->_customerSession = $customerSession;
$this->_addressRepository = $addressRepository;
$this->addressDataFactory = $addressDataFactory;
$this->currentCustomer = $currentCustomer;
$this->dataObjectHelper = $dataObjectHelper;
- $this->addressMetadata = $addressMetadata;
+ $this->addressMetadata = $addressMetadata ?: ObjectManager::getInstance()->get(AddressMetadataInterface::class);
parent::__construct(
$context,
$directoryHelper,
diff --git a/app/code/Magento/Customer/Block/Adminhtml/Edit/Tab/Orders.php b/app/code/Magento/Customer/Block/Adminhtml/Edit/Tab/Orders.php
index 3d4ccb789dc72..56f5e07670e5b 100644
--- a/app/code/Magento/Customer/Block/Adminhtml/Edit/Tab/Orders.php
+++ b/app/code/Magento/Customer/Block/Adminhtml/Edit/Tab/Orders.php
@@ -160,7 +160,10 @@ protected function _prepareColumns()
*/
public function getRowUrl($row)
{
- return $this->getUrl('sales/order/view', ['order_id' => $row->getId()]);
+ return $this->getUrl(
+ 'sales/order/view',
+ ['order_id' => $row->getId(), 'customer_id' => $this->getRequest()->getParam('id')]
+ );
}
/**
diff --git a/app/code/Magento/Customer/Block/Form/Register.php b/app/code/Magento/Customer/Block/Form/Register.php
index a190ccde50b5a..be16046d69075 100644
--- a/app/code/Magento/Customer/Block/Form/Register.php
+++ b/app/code/Magento/Customer/Block/Form/Register.php
@@ -6,7 +6,8 @@
namespace Magento\Customer\Block\Form;
use Magento\Customer\Model\AccountManagement;
-use Magento\Newsletter\Observer\PredispatchNewsletterObserver;
+use Magento\Framework\App\ObjectManager;
+use Magento\Newsletter\Model\Config;
/**
* Customer register form block
@@ -32,6 +33,11 @@ class Register extends \Magento\Directory\Block\Data
*/
protected $_customerUrl;
+ /**
+ * @var Config
+ */
+ private $newsLetterConfig;
+
/**
* Constructor
*
@@ -45,6 +51,7 @@ class Register extends \Magento\Directory\Block\Data
* @param \Magento\Customer\Model\Session $customerSession
* @param \Magento\Customer\Model\Url $customerUrl
* @param array $data
+ * @param Config $newsLetterConfig
*
* @SuppressWarnings(PHPMD.ExcessiveParameterList)
*/
@@ -58,11 +65,13 @@ public function __construct(
\Magento\Framework\Module\ModuleManagerInterface $moduleManager,
\Magento\Customer\Model\Session $customerSession,
\Magento\Customer\Model\Url $customerUrl,
- array $data = []
+ array $data = [],
+ Config $newsLetterConfig = null
) {
$this->_customerUrl = $customerUrl;
$this->_moduleManager = $moduleManager;
$this->_customerSession = $customerSession;
+ $this->newsLetterConfig = $newsLetterConfig ?: ObjectManager::getInstance()->get(Config::class);
parent::__construct(
$context,
$directoryHelper,
@@ -170,7 +179,7 @@ public function getRegion()
public function isNewsletterEnabled()
{
return $this->_moduleManager->isOutputEnabled('Magento_Newsletter')
- && $this->getConfig(PredispatchNewsletterObserver::XML_PATH_NEWSLETTER_ACTIVE);
+ && $this->newsLetterConfig->isActive();
}
/**
diff --git a/app/code/Magento/Customer/Block/Widget/Dob.php b/app/code/Magento/Customer/Block/Widget/Dob.php
index 55101fb82afd0..d874729d9132e 100644
--- a/app/code/Magento/Customer/Block/Widget/Dob.php
+++ b/app/code/Magento/Customer/Block/Widget/Dob.php
@@ -99,11 +99,34 @@ public function isRequired()
*/
public function setDate($date)
{
- $this->setTime($date ? strtotime($date) : false);
+ $this->setTime($this->filterTime($date));
$this->setValue($this->applyOutputFilter($date));
return $this;
}
+ /**
+ * Sanitizes time
+ *
+ * @param mixed $value
+ * @return bool|int
+ */
+ private function filterTime($value)
+ {
+ $time = false;
+ if ($value) {
+ if ($value instanceof \DateTimeInterface) {
+ $time = $value->getTimestamp();
+ } elseif (is_numeric($value)) {
+ $time = $value;
+ } elseif (is_string($value)) {
+ $time = strtotime($value);
+ $time = $time === false ? $this->_localeDate->date($value, null, false, false)->getTimestamp() : $time;
+ }
+ }
+
+ return $time;
+ }
+
/**
* Return Data Form Filter or false
*
@@ -200,21 +223,23 @@ public function getStoreLabel($attributeCode)
*/
public function getFieldHtml()
{
- $this->dateElement->setData([
- 'extra_params' => $this->getHtmlExtraParams(),
- 'name' => $this->getHtmlId(),
- 'id' => $this->getHtmlId(),
- 'class' => $this->getHtmlClass(),
- 'value' => $this->getValue(),
- 'date_format' => $this->getDateFormat(),
- 'image' => $this->getViewFileUrl('Magento_Theme::calendar.png'),
- 'years_range' => '-120y:c+nn',
- 'max_date' => '-1d',
- 'change_month' => 'true',
- 'change_year' => 'true',
- 'show_on' => 'both',
- 'first_day' => $this->getFirstDay()
- ]);
+ $this->dateElement->setData(
+ [
+ 'extra_params' => $this->getHtmlExtraParams(),
+ 'name' => $this->getHtmlId(),
+ 'id' => $this->getHtmlId(),
+ 'class' => $this->getHtmlClass(),
+ 'value' => $this->getValue(),
+ 'date_format' => $this->getDateFormat(),
+ 'image' => $this->getViewFileUrl('Magento_Theme::calendar.png'),
+ 'years_range' => '-120y:c+nn',
+ 'max_date' => '-1d',
+ 'change_month' => 'true',
+ 'change_year' => 'true',
+ 'show_on' => 'both',
+ 'first_day' => $this->getFirstDay()
+ ]
+ );
return $this->dateElement->getHtml();
}
diff --git a/app/code/Magento/Customer/Console/Command/UpgradeHashAlgorithmCommand.php b/app/code/Magento/Customer/Console/Command/UpgradeHashAlgorithmCommand.php
index dd8dec0b94c15..c980fe1fe7769 100644
--- a/app/code/Magento/Customer/Console/Command/UpgradeHashAlgorithmCommand.php
+++ b/app/code/Magento/Customer/Console/Command/UpgradeHashAlgorithmCommand.php
@@ -13,6 +13,9 @@
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
+/**
+ * Upgrade users passwords to the new algorithm
+ */
class UpgradeHashAlgorithmCommand extends Command
{
/**
@@ -65,8 +68,11 @@ protected function execute(InputInterface $input, OutputInterface $output)
$customer->load($customer->getId());
if (!$this->encryptor->validateHashVersion($customer->getPasswordHash())) {
list($hash, $salt, $version) = explode(Encryptor::DELIMITER, $customer->getPasswordHash(), 3);
- $version .= Encryptor::DELIMITER . Encryptor::HASH_VERSION_LATEST;
- $customer->setPasswordHash($this->encryptor->getHash($hash, $salt, $version));
+ $version .= Encryptor::DELIMITER . $this->encryptor->getLatestHashVersion();
+ $hash = $this->encryptor->getHash($hash, $salt, $this->encryptor->getLatestHashVersion());
+ list($hash, $salt) = explode(Encryptor::DELIMITER, $hash, 3);
+ $hash = implode(Encryptor::DELIMITER, [$hash, $salt, $version]);
+ $customer->setPasswordHash($hash);
$customer->save();
$output->write(".");
}
diff --git a/app/code/Magento/Customer/Controller/Account/Confirm.php b/app/code/Magento/Customer/Controller/Account/Confirm.php
index 2b3cb9aa61ab5..adca90c5e5f24 100644
--- a/app/code/Magento/Customer/Controller/Account/Confirm.php
+++ b/app/code/Magento/Customer/Controller/Account/Confirm.php
@@ -6,25 +6,27 @@
*/
namespace Magento\Customer\Controller\Account;
-use Magento\Customer\Model\Url;
-use Magento\Framework\App\Action\Context;
-use Magento\Customer\Model\Session;
-use Magento\Framework\App\Config\ScopeConfigInterface;
-use Magento\Store\Model\StoreManagerInterface;
use Magento\Customer\Api\AccountManagementInterface;
use Magento\Customer\Api\CustomerRepositoryInterface;
+use Magento\Customer\Controller\AbstractAccount;
use Magento\Customer\Helper\Address;
+use Magento\Customer\Model\Session;
+use Magento\Customer\Model\Url;
+use Magento\Framework\App\Action\Context;
+use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface;
+use Magento\Framework\App\Config\ScopeConfigInterface;
+use Magento\Framework\Controller\ResultFactory;
use Magento\Framework\UrlFactory;
use Magento\Framework\Exception\StateException;
use Magento\Store\Model\ScopeInterface;
-use Magento\Framework\Controller\ResultFactory;
+use Magento\Store\Model\StoreManagerInterface;
/**
* Class Confirm
*
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
*/
-class Confirm extends \Magento\Customer\Controller\AbstractAccount
+class Confirm extends AbstractAccount implements HttpGetActionInterface
{
/**
* @var \Magento\Framework\App\Config\ScopeConfigInterface
@@ -147,13 +149,16 @@ public function execute()
$resultRedirect->setPath('*/*/');
return $resultRedirect;
}
- try {
- $customerId = $this->getRequest()->getParam('id', false);
- $key = $this->getRequest()->getParam('key', false);
- if (empty($customerId) || empty($key)) {
- throw new \Exception(__('Bad request.'));
- }
+ $customerId = $this->getRequest()->getParam('id', false);
+ $key = $this->getRequest()->getParam('key', false);
+ if (empty($customerId) || empty($key)) {
+ $this->messageManager->addErrorMessage(__('Bad request.'));
+ $url = $this->urlModel->getUrl('*/*/index', ['_secure' => true]);
+ return $resultRedirect->setUrl($this->_redirect->error($url));
+ }
+
+ try {
// log in and send greeting email
$customerEmail = $this->customerRepository->getById($customerId)->getEmail();
$customer = $this->customerAccountManagement->activate($customerEmail, $key);
diff --git a/app/code/Magento/Customer/Controller/Account/Confirmation.php b/app/code/Magento/Customer/Controller/Account/Confirmation.php
index a3e2db0207630..59def8640328c 100644
--- a/app/code/Magento/Customer/Controller/Account/Confirmation.php
+++ b/app/code/Magento/Customer/Controller/Account/Confirmation.php
@@ -1,21 +1,26 @@
storeManager->getStore()->getWebsiteId()
);
- $this->messageManager->addSuccess(__('Please check your email for confirmation key.'));
+ $this->messageManager->addSuccessMessage(__('Please check your email for confirmation key.'));
} catch (InvalidTransitionException $e) {
- $this->messageManager->addSuccess(__('This email does not require confirmation.'));
+ $this->messageManager->addSuccessMessage(__('This email does not require confirmation.'));
} catch (\Exception $e) {
- $this->messageManager->addException($e, __('Wrong email.'));
+ $this->messageManager->addExceptionMessage($e, __('Wrong email.'));
$resultRedirect->setPath('*/*/*', ['email' => $email, '_secure' => true]);
return $resultRedirect;
}
diff --git a/app/code/Magento/Customer/Controller/Account/CreatePost.php b/app/code/Magento/Customer/Controller/Account/CreatePost.php
index 4c9c25b5f33d9..a2be0f68b56cb 100644
--- a/app/code/Magento/Customer/Controller/Account/CreatePost.php
+++ b/app/code/Magento/Customer/Controller/Account/CreatePost.php
@@ -3,6 +3,8 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
+declare(strict_types=1);
+
namespace Magento\Customer\Controller\Account;
use Magento\Customer\Api\CustomerRepositoryInterface as CustomerRepository;
@@ -349,15 +351,14 @@ public function execute()
$confirmation = $this->getRequest()->getParam('password_confirmation');
$redirectUrl = $this->session->getBeforeAuthUrl();
$this->checkPasswordConfirmation($password, $confirmation);
+
+ $extensionAttributes = $customer->getExtensionAttributes();
+ $extensionAttributes->setIsSubscribed($this->getRequest()->getParam('is_subscribed', false));
+ $customer->setExtensionAttributes($extensionAttributes);
+
$customer = $this->accountManagement
->createAccount($customer, $password, $redirectUrl);
- if ($this->getRequest()->getParam('is_subscribed', false)) {
- $extensionAttributes = $customer->getExtensionAttributes();
- $extensionAttributes->setIsSubscribed(true);
- $customer->setExtensionAttributes($extensionAttributes);
- $this->customerRepository->save($customer);
- }
$this->_eventManager->dispatch(
'customer_register_success',
['account_controller' => $this, 'customer' => $customer]
diff --git a/app/code/Magento/Customer/Controller/Adminhtml/Group/Save.php b/app/code/Magento/Customer/Controller/Adminhtml/Group/Save.php
index 7549315f9ffcd..5ffce4cbcd989 100644
--- a/app/code/Magento/Customer/Controller/Adminhtml/Group/Save.php
+++ b/app/code/Magento/Customer/Controller/Adminhtml/Group/Save.php
@@ -1,6 +1,5 @@
resultRedirectFactory->create();
try {
$customerGroupCode = (string)$this->getRequest()->getParam('code');
+
if ($id !== null) {
$customerGroup = $this->groupRepository->getById((int)$id);
$customerGroupCode = $customerGroupCode ?: $customerGroup->getCode();
diff --git a/app/code/Magento/Customer/Controller/Adminhtml/Index.php b/app/code/Magento/Customer/Controller/Adminhtml/Index.php
index a0317a51260da..ffae1e9f8bf1e 100644
--- a/app/code/Magento/Customer/Controller/Adminhtml/Index.php
+++ b/app/code/Magento/Customer/Controller/Adminhtml/Index.php
@@ -311,7 +311,7 @@ protected function _addSessionErrorMessages($messages)
protected function actUponMultipleCustomers(callable $singleAction, $customerIds)
{
if (!is_array($customerIds)) {
- $this->messageManager->addError(__('Please select customer(s).'));
+ $this->messageManager->addErrorMessage(__('Please select customer(s).'));
return 0;
}
$customersUpdated = 0;
@@ -320,7 +320,7 @@ protected function actUponMultipleCustomers(callable $singleAction, $customerIds
$singleAction($customerId);
$customersUpdated++;
} catch (\Exception $exception) {
- $this->messageManager->addError($exception->getMessage());
+ $this->messageManager->addErrorMessage($exception->getMessage());
}
}
return $customersUpdated;
diff --git a/app/code/Magento/Customer/Controller/Adminhtml/Index/MassAssignGroup.php b/app/code/Magento/Customer/Controller/Adminhtml/Index/MassAssignGroup.php
index 5a9c52bf9b1c0..f55c81da7e0b9 100644
--- a/app/code/Magento/Customer/Controller/Adminhtml/Index/MassAssignGroup.php
+++ b/app/code/Magento/Customer/Controller/Adminhtml/Index/MassAssignGroup.php
@@ -60,7 +60,7 @@ protected function massAction(AbstractCollection $collection)
}
if ($customersUpdated) {
- $this->messageManager->addSuccess(__('A total of %1 record(s) were updated.', $customersUpdated));
+ $this->messageManager->addSuccessMessage(__('A total of %1 record(s) were updated.', $customersUpdated));
}
/** @var \Magento\Backend\Model\View\Result\Redirect $resultRedirect */
$resultRedirect = $this->resultFactory->create(ResultFactory::TYPE_REDIRECT);
diff --git a/app/code/Magento/Customer/Controller/Adminhtml/Index/MassDelete.php b/app/code/Magento/Customer/Controller/Adminhtml/Index/MassDelete.php
index edaeea6a15eb2..85286573bc5e7 100644
--- a/app/code/Magento/Customer/Controller/Adminhtml/Index/MassDelete.php
+++ b/app/code/Magento/Customer/Controller/Adminhtml/Index/MassDelete.php
@@ -58,7 +58,7 @@ protected function massAction(AbstractCollection $collection)
}
if ($customersDeleted) {
- $this->messageManager->addSuccess(__('A total of %1 record(s) were deleted.', $customersDeleted));
+ $this->messageManager->addSuccessMessage(__('A total of %1 record(s) were deleted.', $customersDeleted));
}
/** @var \Magento\Backend\Model\View\Result\Redirect $resultRedirect */
$resultRedirect = $this->resultFactory->create(ResultFactory::TYPE_REDIRECT);
diff --git a/app/code/Magento/Customer/Controller/Adminhtml/Index/MassSubscribe.php b/app/code/Magento/Customer/Controller/Adminhtml/Index/MassSubscribe.php
index 25c56ac60c14b..29a66bf1ff933 100644
--- a/app/code/Magento/Customer/Controller/Adminhtml/Index/MassSubscribe.php
+++ b/app/code/Magento/Customer/Controller/Adminhtml/Index/MassSubscribe.php
@@ -6,6 +6,7 @@
namespace Magento\Customer\Controller\Adminhtml\Index;
use Magento\Backend\App\Action\Context;
+use Magento\Framework\App\Action\HttpPostActionInterface as HttpPostActionInterface;
use Magento\Ui\Component\MassAction\Filter;
use Magento\Customer\Model\ResourceModel\Customer\CollectionFactory;
use Magento\Customer\Api\CustomerRepositoryInterface;
@@ -16,7 +17,7 @@
/**
* Class MassSubscribe
*/
-class MassSubscribe extends AbstractMassAction
+class MassSubscribe extends AbstractMassAction implements HttpPostActionInterface
{
/**
* @var CustomerRepositoryInterface
@@ -64,7 +65,7 @@ protected function massAction(AbstractCollection $collection)
}
if ($customersUpdated) {
- $this->messageManager->addSuccess(__('A total of %1 record(s) were updated.', $customersUpdated));
+ $this->messageManager->addSuccessMessage(__('A total of %1 record(s) were updated.', $customersUpdated));
}
/** @var \Magento\Backend\Model\View\Result\Redirect $resultRedirect */
$resultRedirect = $this->resultFactory->create(ResultFactory::TYPE_REDIRECT);
diff --git a/app/code/Magento/Customer/Controller/Adminhtml/Index/MassUnsubscribe.php b/app/code/Magento/Customer/Controller/Adminhtml/Index/MassUnsubscribe.php
index 4b40722ba9ab2..fddf18489b9a5 100644
--- a/app/code/Magento/Customer/Controller/Adminhtml/Index/MassUnsubscribe.php
+++ b/app/code/Magento/Customer/Controller/Adminhtml/Index/MassUnsubscribe.php
@@ -6,6 +6,7 @@
namespace Magento\Customer\Controller\Adminhtml\Index;
use Magento\Customer\Api\CustomerRepositoryInterface;
+use Magento\Framework\App\Action\HttpPostActionInterface as HttpPostActionInterface;
use Magento\Framework\Controller\ResultFactory;
use Magento\Backend\App\Action\Context;
use Magento\Newsletter\Model\SubscriberFactory;
@@ -16,7 +17,7 @@
/**
* Class MassUnsubscribe
*/
-class MassUnsubscribe extends AbstractMassAction
+class MassUnsubscribe extends AbstractMassAction implements HttpPostActionInterface
{
/**
* @var CustomerRepositoryInterface
@@ -64,7 +65,7 @@ protected function massAction(AbstractCollection $collection)
}
if ($customersUpdated) {
- $this->messageManager->addSuccess(__('A total of %1 record(s) were updated.', $customersUpdated));
+ $this->messageManager->addSuccessMessage(__('A total of %1 record(s) were updated.', $customersUpdated));
}
/** @var \Magento\Backend\Model\View\Result\Redirect $resultRedirect */
$resultRedirect = $this->resultFactory->create(ResultFactory::TYPE_REDIRECT);
diff --git a/app/code/Magento/Customer/Controller/Adminhtml/Index/ResetPassword.php b/app/code/Magento/Customer/Controller/Adminhtml/Index/ResetPassword.php
index 1e4fa91cbf899..3b9370c32bf6d 100644
--- a/app/code/Magento/Customer/Controller/Adminhtml/Index/ResetPassword.php
+++ b/app/code/Magento/Customer/Controller/Adminhtml/Index/ResetPassword.php
@@ -44,7 +44,9 @@ public function execute()
\Magento\Customer\Model\AccountManagement::EMAIL_REMINDER,
$customer->getWebsiteId()
);
- $this->messageManager->addSuccess(__('The customer will receive an email with a link to reset password.'));
+ $this->messageManager->addSuccessMessage(
+ __('The customer will receive an email with a link to reset password.')
+ );
} catch (NoSuchEntityException $exception) {
$resultRedirect->setPath('customer/index');
return $resultRedirect;
@@ -57,7 +59,7 @@ public function execute()
} catch (SecurityViolationException $exception) {
$this->messageManager->addErrorMessage($exception->getMessage());
} catch (\Exception $exception) {
- $this->messageManager->addException(
+ $this->messageManager->addExceptionMessage(
$exception,
__('Something went wrong while resetting customer password.')
);
diff --git a/app/code/Magento/Customer/Controller/Adminhtml/Index/Save.php b/app/code/Magento/Customer/Controller/Adminhtml/Index/Save.php
index 38ed688a835bc..3ee33af9ec073 100644
--- a/app/code/Magento/Customer/Controller/Adminhtml/Index/Save.php
+++ b/app/code/Magento/Customer/Controller/Adminhtml/Index/Save.php
@@ -354,7 +354,7 @@ public function execute()
$this->_getSession()->unsCustomerFormData();
// Done Saving customer, finish save action
$this->_coreRegistry->register(RegistryConstants::CURRENT_CUSTOMER_ID, $customerId);
- $this->messageManager->addSuccess(__('You saved the customer.'));
+ $this->messageManager->addSuccessMessage(__('You saved the customer.'));
$returnToEdit = (bool)$this->getRequest()->getParam('back', false);
} catch (\Magento\Framework\Validator\Exception $exception) {
$messages = $exception->getMessages();
@@ -378,7 +378,10 @@ public function execute()
$this->_getSession()->setCustomerFormData($this->retrieveFormattedFormData());
$returnToEdit = true;
} catch (\Exception $exception) {
- $this->messageManager->addException($exception, __('Something went wrong while saving the customer.'));
+ $this->messageManager->addExceptionMessage(
+ $exception,
+ __('Something went wrong while saving the customer.')
+ );
$this->_getSession()->setCustomerFormData($this->retrieveFormattedFormData());
$returnToEdit = true;
}
diff --git a/app/code/Magento/Customer/Controller/Adminhtml/Locks/Unlock.php b/app/code/Magento/Customer/Controller/Adminhtml/Locks/Unlock.php
index 1fd06a3182948..32299de777c31 100644
--- a/app/code/Magento/Customer/Controller/Adminhtml/Locks/Unlock.php
+++ b/app/code/Magento/Customer/Controller/Adminhtml/Locks/Unlock.php
@@ -7,13 +7,14 @@
namespace Magento\Customer\Controller\Adminhtml\Locks;
use Magento\Customer\Model\AuthenticationInterface;
+use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface;
use Magento\Framework\Controller\ResultFactory;
use Magento\Backend\App\Action;
/**
* Unlock Customer Controller
*/
-class Unlock extends \Magento\Backend\App\Action
+class Unlock extends \Magento\Backend\App\Action implements HttpGetActionInterface
{
/**
* Authorization level of a basic admin session
@@ -55,10 +56,10 @@ public function execute()
// unlock customer
if ($customerId) {
$this->authentication->unlock($customerId);
- $this->getMessageManager()->addSuccess(__('Customer has been unlocked successfully.'));
+ $this->getMessageManager()->addSuccessMessage(__('Customer has been unlocked successfully.'));
}
} catch (\Exception $e) {
- $this->messageManager->addError($e->getMessage());
+ $this->messageManager->addErrorMessage($e->getMessage());
}
/** @var \Magento\Backend\Model\View\Result\Redirect $resultRedirect */
diff --git a/app/code/Magento/Customer/Model/AccountManagement.php b/app/code/Magento/Customer/Model/AccountManagement.php
index 15d98af86b72e..7be8699495bf5 100644
--- a/app/code/Magento/Customer/Model/AccountManagement.php
+++ b/app/code/Magento/Customer/Model/AccountManagement.php
@@ -66,49 +66,65 @@
class AccountManagement implements AccountManagementInterface
{
/**
- * Configuration paths for email templates and identities
+ * Configuration paths for create account email template
*
* @deprecated
*/
const XML_PATH_REGISTER_EMAIL_TEMPLATE = 'customer/create_account/email_template';
/**
+ * Configuration paths for register no password email template
+ *
* @deprecated
*/
const XML_PATH_REGISTER_NO_PASSWORD_EMAIL_TEMPLATE = 'customer/create_account/email_no_password_template';
/**
+ * Configuration paths for remind email identity
+ *
* @deprecated
*/
const XML_PATH_REGISTER_EMAIL_IDENTITY = 'customer/create_account/email_identity';
/**
+ * Configuration paths for remind email template
+ *
* @deprecated
*/
const XML_PATH_REMIND_EMAIL_TEMPLATE = 'customer/password/remind_email_template';
/**
+ * Configuration paths for forgot email email template
+ *
* @deprecated
*/
const XML_PATH_FORGOT_EMAIL_TEMPLATE = 'customer/password/forgot_email_template';
/**
+ * Configuration paths for forgot email identity
+ *
* @deprecated
*/
const XML_PATH_FORGOT_EMAIL_IDENTITY = 'customer/password/forgot_email_identity';
/**
+ * Configuration paths for account confirmation required
+ *
* @deprecated
* @see AccountConfirmation::XML_PATH_IS_CONFIRM
*/
const XML_PATH_IS_CONFIRM = 'customer/create_account/confirm';
/**
+ * Configuration paths for account confirmation email template
+ *
* @deprecated
*/
const XML_PATH_CONFIRM_EMAIL_TEMPLATE = 'customer/create_account/email_confirmation_template';
/**
+ * Configuration paths for confirmation confirmed email template
+ *
* @deprecated
*/
const XML_PATH_CONFIRMED_EMAIL_TEMPLATE = 'customer/create_account/email_confirmed_template';
@@ -161,11 +177,15 @@ class AccountManagement implements AccountManagementInterface
const XML_PATH_REQUIRED_CHARACTER_CLASSES_NUMBER = 'customer/password/required_character_classes_number';
/**
+ * Configuration path to customer reset password email template
+ *
* @deprecated
*/
const XML_PATH_RESET_PASSWORD_TEMPLATE = 'customer/password/reset_password_template';
/**
+ * Minimum password length
+ *
* @deprecated
*/
const MIN_PASSWORD_LENGTH = 6;
@@ -579,7 +599,6 @@ public function authenticate($username, $password)
}
try {
$this->getAuthentication()->authenticate($customerId, $password);
- // phpcs:disable Magento2.Exceptions.ThrowCatch
} catch (InvalidEmailOrPasswordException $e) {
throw new InvalidEmailOrPasswordException(__('Invalid login or password.'));
}
@@ -890,7 +909,6 @@ public function createAccountWithPasswordHash(CustomerInterface $customer, $hash
throw new InputMismatchException(
__('A customer with the same email address already exists in an associated website.')
);
- // phpcs:disable Magento2.Exceptions.ThrowCatch
} catch (LocalizedException $e) {
throw $e;
}
@@ -910,7 +928,6 @@ public function createAccountWithPasswordHash(CustomerInterface $customer, $hash
}
}
$this->customerRegistry->remove($customer->getId());
- // phpcs:disable Magento2.Exceptions.ThrowCatch
} catch (InputException $e) {
$this->customerRepository->delete($customer);
throw $e;
@@ -1017,7 +1034,6 @@ private function changePasswordForCustomer($customer, $currentPassword, $newPass
{
try {
$this->getAuthentication()->authenticate($customer->getId(), $currentPassword);
- // phpcs:disable Magento2.Exceptions.ThrowCatch
} catch (InvalidEmailOrPasswordException $e) {
throw new InvalidEmailOrPasswordException(
__("The password doesn't match this account. Verify the password and try again.")
@@ -1549,7 +1565,7 @@ protected function getFullCustomerObject($customer)
*/
public function getPasswordHash($password)
{
- return $this->encryptor->getHash($password);
+ return $this->encryptor->getHash($password, true);
}
/**
diff --git a/app/code/Magento/Customer/Model/Address/CustomerAddressDataFormatter.php b/app/code/Magento/Customer/Model/Address/CustomerAddressDataFormatter.php
index 9202d7492040c..dfda2bd324828 100644
--- a/app/code/Magento/Customer/Model/Address/CustomerAddressDataFormatter.php
+++ b/app/code/Magento/Customer/Model/Address/CustomerAddressDataFormatter.php
@@ -81,6 +81,7 @@ public function prepareAddress(AddressInterface $customerAddress)
'inline' => $this->getCustomerAddressInline($customerAddress),
'custom_attributes' => [],
'extension_attributes' => $customerAddress->getExtensionAttributes(),
+ 'vat_id' => $customerAddress->getVatId()
];
if ($customerAddress->getCustomAttributes()) {
diff --git a/app/code/Magento/Customer/Model/Attribute.php b/app/code/Magento/Customer/Model/Attribute.php
index ae714f993082e..98a97872f15f4 100644
--- a/app/code/Magento/Customer/Model/Attribute.php
+++ b/app/code/Magento/Customer/Model/Attribute.php
@@ -202,14 +202,9 @@ public function canBeFilterableInGrid()
/**
* @inheritdoc
- *
- * @SuppressWarnings(PHPMD.SerializationAware)
- * @deprecated Do not use PHP serialization.
*/
public function __sleep()
{
- trigger_error('Using PHP serialization is deprecated', E_USER_DEPRECATED);
-
$this->unsetData('entity_type');
return array_diff(
parent::__sleep(),
@@ -219,14 +214,9 @@ public function __sleep()
/**
* @inheritdoc
- *
- * @SuppressWarnings(PHPMD.SerializationAware)
- * @deprecated Do not use PHP serialization.
*/
public function __wakeup()
{
- trigger_error('Using PHP serialization is deprecated', E_USER_DEPRECATED);
-
parent::__wakeup();
$objectManager = \Magento\Framework\App\ObjectManager::getInstance();
$this->indexerRegistry = $objectManager->get(\Magento\Framework\Indexer\IndexerRegistry::class);
diff --git a/app/code/Magento/Customer/Model/AttributeMetadataConverter.php b/app/code/Magento/Customer/Model/AttributeMetadataConverter.php
index 44d104659e069..0407aebf9d670 100644
--- a/app/code/Magento/Customer/Model/AttributeMetadataConverter.php
+++ b/app/code/Magento/Customer/Model/AttributeMetadataConverter.php
@@ -3,18 +3,36 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
+declare(strict_types=1);
+
namespace Magento\Customer\Model;
use Magento\Customer\Api\Data\OptionInterfaceFactory;
use Magento\Customer\Api\Data\ValidationRuleInterfaceFactory;
use Magento\Customer\Api\Data\AttributeMetadataInterfaceFactory;
use Magento\Eav\Api\Data\AttributeDefaultValueInterface;
+use Magento\Framework\App\Config\ScopeConfigInterface;
+use Magento\Framework\App\ObjectManager;
/**
* Converter for AttributeMetadata
*/
class AttributeMetadataConverter
{
+ /**
+ * Attribute Code get options from system config
+ *
+ * @var array
+ */
+ private const ATTRIBUTE_CODE_LIST_FROM_SYSTEM_CONFIG = ['prefix', 'suffix'];
+
+ /**
+ * XML Path to get address config
+ *
+ * @var string
+ */
+ private const XML_CUSTOMER_ADDRESS = 'customer/address/';
+
/**
* @var OptionInterfaceFactory
*/
@@ -35,6 +53,11 @@ class AttributeMetadataConverter
*/
protected $dataObjectHelper;
+ /**
+ * @var ScopeConfigInterface
+ */
+ private $scopeConfig;
+
/**
* Initialize the Converter
*
@@ -42,17 +65,20 @@ class AttributeMetadataConverter
* @param ValidationRuleInterfaceFactory $validationRuleFactory
* @param AttributeMetadataInterfaceFactory $attributeMetadataFactory
* @param \Magento\Framework\Api\DataObjectHelper $dataObjectHelper
+ * @param ScopeConfigInterface $scopeConfig
*/
public function __construct(
OptionInterfaceFactory $optionFactory,
ValidationRuleInterfaceFactory $validationRuleFactory,
AttributeMetadataInterfaceFactory $attributeMetadataFactory,
- \Magento\Framework\Api\DataObjectHelper $dataObjectHelper
+ \Magento\Framework\Api\DataObjectHelper $dataObjectHelper,
+ ScopeConfigInterface $scopeConfig = null
) {
$this->optionFactory = $optionFactory;
$this->validationRuleFactory = $validationRuleFactory;
$this->attributeMetadataFactory = $attributeMetadataFactory;
$this->dataObjectHelper = $dataObjectHelper;
+ $this->scopeConfig = $scopeConfig ?? ObjectManager::getInstance()->get(ScopeConfigInterface::class);
}
/**
@@ -64,28 +90,34 @@ public function __construct(
public function createMetadataAttribute($attribute)
{
$options = [];
- if ($attribute->usesSource()) {
- foreach ($attribute->getSource()->getAllOptions() as $option) {
- $optionDataObject = $this->optionFactory->create();
- if (!is_array($option['value'])) {
- $optionDataObject->setValue($option['value']);
- } else {
- $optionArray = [];
- foreach ($option['value'] as $optionArrayValues) {
- $optionObject = $this->optionFactory->create();
- $this->dataObjectHelper->populateWithArray(
- $optionObject,
- $optionArrayValues,
- \Magento\Customer\Api\Data\OptionInterface::class
- );
- $optionArray[] = $optionObject;
+
+ if (in_array($attribute->getAttributeCode(), self::ATTRIBUTE_CODE_LIST_FROM_SYSTEM_CONFIG)) {
+ $options = $this->getOptionFromConfig($attribute->getAttributeCode());
+ } else {
+ if ($attribute->usesSource()) {
+ foreach ($attribute->getSource()->getAllOptions() as $option) {
+ $optionDataObject = $this->optionFactory->create();
+ if (!is_array($option['value'])) {
+ $optionDataObject->setValue($option['value']);
+ } else {
+ $optionArray = [];
+ foreach ($option['value'] as $optionArrayValues) {
+ $optionObject = $this->optionFactory->create();
+ $this->dataObjectHelper->populateWithArray(
+ $optionObject,
+ $optionArrayValues,
+ \Magento\Customer\Api\Data\OptionInterface::class
+ );
+ $optionArray[] = $optionObject;
+ }
+ $optionDataObject->setOptions($optionArray);
}
- $optionDataObject->setOptions($optionArray);
+ $optionDataObject->setLabel($option['label']);
+ $options[] = $optionDataObject;
}
- $optionDataObject->setLabel($option['label']);
- $options[] = $optionDataObject;
}
}
+
$validationRules = [];
foreach ((array)$attribute->getValidateRules() as $name => $value) {
$validationRule = $this->validationRuleFactory->create()
@@ -122,4 +154,26 @@ public function createMetadataAttribute($attribute)
->setIsFilterableInGrid($attribute->getIsFilterableInGrid())
->setIsSearchableInGrid($attribute->getIsSearchableInGrid());
}
+
+ /**
+ * Get option from System Config instead of Use Source (Prefix, Suffix)
+ *
+ * @param string $attributeCode
+ * @return \Magento\Customer\Api\Data\OptionInterface[]
+ */
+ private function getOptionFromConfig($attributeCode)
+ {
+ $result = [];
+ $value = $this->scopeConfig->getValue(self::XML_CUSTOMER_ADDRESS . $attributeCode . '_options');
+ if ($value) {
+ $optionArray = explode(';', $value);
+ foreach ($optionArray as $value) {
+ $optionObject = $this->optionFactory->create();
+ $optionObject->setLabel($value);
+ $optionObject->setValue($value);
+ $result[] = $optionObject;
+ }
+ }
+ return $result;
+ }
}
diff --git a/app/code/Magento/Customer/Model/Checkout/ConfigProvider.php b/app/code/Magento/Customer/Model/Checkout/ConfigProvider.php
index 36eabe3571ceb..ef0b8b7163ad8 100644
--- a/app/code/Magento/Customer/Model/Checkout/ConfigProvider.php
+++ b/app/code/Magento/Customer/Model/Checkout/ConfigProvider.php
@@ -14,6 +14,9 @@
use Magento\Customer\Model\Form;
use Magento\Store\Model\ScopeInterface;
+/**
+ * Provides some configurations for customer.
+ */
class ConfigProvider implements ConfigProviderInterface
{
/**
@@ -57,7 +60,7 @@ public function __construct(
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function getConfig()
{
@@ -96,12 +99,14 @@ protected function getLoginUrl()
* Whether redirect to login page is required
*
* @return bool
+ *
+ * @throws \Magento\Framework\Exception\NoSuchEntityException
*/
protected function isRedirectRequired()
{
$baseUrl = $this->storeManager->getStore()->getBaseUrl();
- if (strpos($this->getLoginUrl(), $baseUrl) !== false) {
+ if (strpos($this->getLoginUrl(), (string) $baseUrl) !== false) {
return false;
}
diff --git a/app/code/Magento/Customer/Model/Customer.php b/app/code/Magento/Customer/Model/Customer.php
index 1287dbe5df708..1f8f7d90f6d0d 100644
--- a/app/code/Magento/Customer/Model/Customer.php
+++ b/app/code/Magento/Customer/Model/Customer.php
@@ -394,7 +394,9 @@ public function getSharingConfig()
public function authenticate($login, $password)
{
$this->loadByEmail($login);
- if ($this->getConfirmation() && $this->isConfirmationRequired()) {
+ if ($this->getConfirmation() &&
+ $this->accountConfirmation->isConfirmationRequired($this->getWebsiteId(), $this->getId(), $this->getEmail())
+ ) {
throw new EmailNotConfirmedException(
__("This account isn't confirmed. Verify and try again.")
);
@@ -415,8 +417,9 @@ public function authenticate($login, $password)
/**
* Load customer by email
*
- * @param string $customerEmail
- * @return $this
+ * @param string $customerEmail
+ * @return $this
+ * @throws \Magento\Framework\Exception\LocalizedException
*/
public function loadByEmail($customerEmail)
{
@@ -427,8 +430,9 @@ public function loadByEmail($customerEmail)
/**
* Change customer password
*
- * @param string $newPassword
- * @return $this
+ * @param string $newPassword
+ * @return $this
+ * @throws \Magento\Framework\Exception\LocalizedException
*/
public function changePassword($newPassword)
{
@@ -440,6 +444,7 @@ public function changePassword($newPassword)
* Get full customer name
*
* @return string
+ * @throws \Magento\Framework\Exception\LocalizedException
*/
public function getName()
{
@@ -462,8 +467,9 @@ public function getName()
/**
* Add address to address collection
*
- * @param Address $address
- * @return $this
+ * @param Address $address
+ * @return $this
+ * @throws \Magento\Framework\Exception\LocalizedException
*/
public function addAddress(Address $address)
{
@@ -487,6 +493,7 @@ public function getAddressById($addressId)
*
* @param int $addressId
* @return Address
+ * @throws \Magento\Framework\Exception\LocalizedException
*/
public function getAddressItemById($addressId)
{
@@ -507,6 +514,7 @@ public function getAddressCollection()
* Customer addresses collection
*
* @return \Magento\Customer\Model\ResourceModel\Address\Collection
+ * @throws \Magento\Framework\Exception\LocalizedException
*/
public function getAddressesCollection()
{
@@ -538,6 +546,7 @@ public function getAddresses()
* Retrieve all customer attributes
*
* @return Attribute[]
+ * @throws \Magento\Framework\Exception\LocalizedException
*/
public function getAttributes()
{
@@ -592,6 +601,7 @@ public function hashPassword($password, $salt = true)
*
* @param string $password
* @return boolean
+ * @throws \Exception
*/
public function validatePassword($password)
{
@@ -805,6 +815,7 @@ public function isConfirmationRequired()
*/
public function getRandomConfirmationKey()
{
+ // phpcs:ignore Magento2.Security.InsecureFunction
return md5(uniqid());
}
diff --git a/app/code/Magento/Customer/Model/Customer/Attribute/Source/Group.php b/app/code/Magento/Customer/Model/Customer/Attribute/Source/Group.php
index e590e5e213acd..296d2877df8ea 100644
--- a/app/code/Magento/Customer/Model/Customer/Attribute/Source/Group.php
+++ b/app/code/Magento/Customer/Model/Customer/Attribute/Source/Group.php
@@ -48,7 +48,15 @@ public function getAllOptions($withEmpty = true, $defaultValues = false)
{
if (!$this->_options) {
$groups = $this->_groupManagement->getLoggedInGroups();
+
$this->_options = $this->_converter->toOptionArray($groups, 'id', 'code');
+
+ array_walk(
+ $this->_options,
+ function (&$item) {
+ $item['__disableTmpl'] = true;
+ }
+ );
}
return $this->_options;
diff --git a/app/code/Magento/Customer/Model/CustomerExtractor.php b/app/code/Magento/Customer/Model/CustomerExtractor.php
index be30b97994787..5d6f3245a0439 100644
--- a/app/code/Magento/Customer/Model/CustomerExtractor.php
+++ b/app/code/Magento/Customer/Model/CustomerExtractor.php
@@ -1,9 +1,9 @@
compactData($customerData);
$allowedAttributes = $customerForm->getAllowedAttributes();
- $isGroupIdEmpty = isset($allowedAttributes['group_id']);
+ $isGroupIdEmpty = !isset($allowedAttributes['group_id']);
$customerDataObject = $this->customerFactory->create();
$this->dataObjectHelper->populateWithArray(
@@ -88,15 +93,18 @@ public function extract(
$customerData,
\Magento\Customer\Api\Data\CustomerInterface::class
);
+
$store = $this->storeManager->getStore();
+ $storeId = $store->getId();
+
if ($isGroupIdEmpty) {
$customerDataObject->setGroupId(
- $this->customerGroupManagement->getDefaultGroup($store->getId())->getId()
+ $this->customerGroupManagement->getDefaultGroup($storeId)->getId()
);
}
$customerDataObject->setWebsiteId($store->getWebsiteId());
- $customerDataObject->setStoreId($store->getId());
+ $customerDataObject->setStoreId($storeId);
return $customerDataObject;
}
diff --git a/app/code/Magento/Customer/Model/CustomerIdProvider.php b/app/code/Magento/Customer/Model/CustomerIdProvider.php
new file mode 100644
index 0000000000000..e670d5eb02d9c
--- /dev/null
+++ b/app/code/Magento/Customer/Model/CustomerIdProvider.php
@@ -0,0 +1,40 @@
+request = $request;
+ }
+
+ /**
+ * Get customer id from request.
+ *
+ * @return int
+ */
+ public function getCustomerId(): int
+ {
+ return (int)$this->request->getParam('id');
+ }
+}
diff --git a/app/code/Magento/Customer/Model/Data/Address.php b/app/code/Magento/Customer/Model/Data/Address.php
index f4cf228fb557c..b399a144368d3 100644
--- a/app/code/Magento/Customer/Model/Data/Address.php
+++ b/app/code/Magento/Customer/Model/Data/Address.php
@@ -42,7 +42,7 @@ public function __construct(
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
protected function getCustomAttributesCodes()
{
@@ -327,7 +327,7 @@ public function setCompany($company)
*/
public function setTelephone($telephone)
{
- return $this->setData(self::TELEPHONE, $telephone);
+ return $this->setData(self::TELEPHONE, trim($telephone));
}
/**
@@ -452,7 +452,7 @@ public function setIsDefaultBilling($isDefaultBilling)
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*
* @return \Magento\Customer\Api\Data\AddressExtensionInterface|null
*/
@@ -462,7 +462,7 @@ public function getExtensionAttributes()
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*
* @param \Magento\Customer\Api\Data\AddressExtensionInterface $extensionAttributes
* @return $this
diff --git a/app/code/Magento/Customer/Model/Delegation/Storage.php b/app/code/Magento/Customer/Model/Delegation/Storage.php
index 71a61d59057cb..d1eae3f56001c 100644
--- a/app/code/Magento/Customer/Model/Delegation/Storage.php
+++ b/app/code/Magento/Customer/Model/Delegation/Storage.php
@@ -19,6 +19,7 @@
use Magento\Customer\Model\Delegation\Data\NewOperationFactory;
use Magento\Customer\Api\Data\CustomerInterfaceFactory;
use Magento\Customer\Api\Data\AddressInterfaceFactory;
+use Magento\Framework\Api\CustomAttributesDataInterface;
use Psr\Log\LoggerInterface;
/**
@@ -100,11 +101,13 @@ public function storeNewOperation(CustomerInterface $customer, array $delegatedD
}
}
$this->session->setCustomerFormData($customerData);
- $this->session->setDelegatedNewCustomerData([
- 'customer' => $customerData,
- 'addresses' => $addressesData,
- 'delegated_data' => $delegatedData,
- ]);
+ $this->session->setDelegatedNewCustomerData(
+ [
+ 'customer' => $customerData,
+ 'addresses' => $addressesData,
+ 'delegated_data' => $delegatedData,
+ ]
+ );
}
/**
@@ -134,18 +137,31 @@ public function consumeNewOperation()
);
$addressData['region'] = $region;
}
- $addresses[] = $this->addressFactory->create(
+
+ $customAttributes = [];
+ if (!empty($addressData[CustomAttributesDataInterface::CUSTOM_ATTRIBUTES])) {
+ $customAttributes = $addressData[CustomAttributesDataInterface::CUSTOM_ATTRIBUTES];
+ unset($addressData[CustomAttributesDataInterface::CUSTOM_ATTRIBUTES]);
+ }
+
+ $address = $this->addressFactory->create(
['data' => $addressData]
);
+
+ foreach ($customAttributes as $attributeCode => $attributeValue) {
+ $address->setCustomAttribute($attributeCode, $attributeValue);
+ }
+
+ $addresses[] = $address;
}
$customerData = $serialized['customer'];
$customerData['addresses'] = $addresses;
- return $this->newFactory->create([
- 'customer' => $this->customerFactory->create(
- ['data' => $customerData]
- ),
- 'additionalData' => $serialized['delegated_data'],
- ]);
+ return $this->newFactory->create(
+ [
+ 'customer' => $this->customerFactory->create(['data' => $customerData]),
+ 'additionalData' => $serialized['delegated_data'],
+ ]
+ );
}
}
diff --git a/app/code/Magento/Customer/Model/EmailNotification.php b/app/code/Magento/Customer/Model/EmailNotification.php
index 144c24f8e8355..573f86247e0c3 100644
--- a/app/code/Magento/Customer/Model/EmailNotification.php
+++ b/app/code/Magento/Customer/Model/EmailNotification.php
@@ -362,7 +362,7 @@ public function passwordResetConfirmation(CustomerInterface $customer)
* @param CustomerInterface $customer
* @param string $type
* @param string $backUrl
- * @param string $storeId
+ * @param int $storeId
* @param string $sendemailStoreId
* @return void
* @throws LocalizedException
diff --git a/app/code/Magento/Customer/Model/EmailNotificationInterface.php b/app/code/Magento/Customer/Model/EmailNotificationInterface.php
index 0821abff04750..028322acdd9d7 100644
--- a/app/code/Magento/Customer/Model/EmailNotificationInterface.php
+++ b/app/code/Magento/Customer/Model/EmailNotificationInterface.php
@@ -73,7 +73,7 @@ public function passwordResetConfirmation(CustomerInterface $customer);
* @param CustomerInterface $customer
* @param string $type
* @param string $backUrl
- * @param string $storeId
+ * @param int $storeId
* @param string $sendemailStoreId
* @return void
* @throws LocalizedException
diff --git a/app/code/Magento/Customer/Model/FileUploaderDataResolver.php b/app/code/Magento/Customer/Model/FileUploaderDataResolver.php
index 535bfe97bc457..b9e9125724894 100644
--- a/app/code/Magento/Customer/Model/FileUploaderDataResolver.php
+++ b/app/code/Magento/Customer/Model/FileUploaderDataResolver.php
@@ -88,9 +88,7 @@ private function getFileUploaderData(
$file = $customerData[$attributeCode] ?? null;
/** @var FileProcessor $fileProcessor */
- $fileProcessor = $this->fileProcessorFactory->create([
- 'entityTypeCode' => $entityType->getEntityTypeCode(),
- ]);
+ $fileProcessor = $this->fileProcessorFactory->create(['entityTypeCode' => $entityType->getEntityTypeCode()]);
if (!empty($file)
&& $fileProcessor->isExist($file)
@@ -103,6 +101,7 @@ private function getFileUploaderData(
'file' => $file,
'size' => null !== $stat ? $stat['size'] : 0,
'url' => $viewUrl ?? '',
+ // phpcs:ignore Magento2.Functions.DiscouragedFunction
'name' => basename($file),
'type' => $fileProcessor->getMimeType($file),
],
@@ -138,9 +137,12 @@ public function overrideFileUploaderMetadata(
if (isset($config['validation']['file_extensions'])) {
$allowedExtensions = explode(',', $config['validation']['file_extensions']);
- array_walk($allowedExtensions, function (&$value) {
- $value = strtolower(trim($value));
- });
+ array_walk(
+ $allowedExtensions,
+ function (&$value) {
+ $value = strtolower(trim($value));
+ }
+ );
}
$allowedExtensions = implode(' ', $allowedExtensions);
@@ -149,6 +151,7 @@ public function overrideFileUploaderMetadata(
$url = $this->getFileUploadUrl($entityTypeCode);
$config = [
+ 'dataType' => $this->getMetadataValue($config, 'dataType'),
'formElement' => 'fileUploader',
'componentType' => 'fileUploader',
'maxFileSize' => $maxFileSize,
diff --git a/app/code/Magento/Customer/Model/Indexer/Source.php b/app/code/Magento/Customer/Model/Indexer/Source.php
index a8878e2084ea0..89e17e01bb48e 100644
--- a/app/code/Magento/Customer/Model/Indexer/Source.php
+++ b/app/code/Magento/Customer/Model/Indexer/Source.php
@@ -96,7 +96,7 @@ public function getIterator()
{
$this->customerCollection->setPageSize($this->batchSize);
$lastPage = $this->customerCollection->getLastPageNumber();
- $pageNumber = 0;
+ $pageNumber = 1;
do {
$this->customerCollection->clear();
$this->customerCollection->setCurPage($pageNumber);
diff --git a/app/code/Magento/Customer/Model/ResourceModel/Customer.php b/app/code/Magento/Customer/Model/ResourceModel/Customer.php
index 94196df6fe093..1477287f79f4b 100644
--- a/app/code/Magento/Customer/Model/ResourceModel/Customer.php
+++ b/app/code/Magento/Customer/Model/ResourceModel/Customer.php
@@ -6,6 +6,7 @@
namespace Magento\Customer\Model\ResourceModel;
+use Magento\Customer\Model\AccountConfirmation;
use Magento\Customer\Model\Customer\NotificationStorage;
use Magento\Framework\App\ObjectManager;
use Magento\Framework\Validator\Exception as ValidatorException;
@@ -42,12 +43,19 @@ class Customer extends \Magento\Eav\Model\Entity\VersionControl\AbstractEntity
*/
protected $storeManager;
+ /**
+ * @var AccountConfirmation
+ */
+ private $accountConfirmation;
+
/**
* @var NotificationStorage
*/
private $notificationStorage;
/**
+ * Customer constructor.
+ *
* @param \Magento\Eav\Model\Entity\Context $context
* @param \Magento\Framework\Model\ResourceModel\Db\VersionControl\Snapshot $entitySnapshot
* @param \Magento\Framework\Model\ResourceModel\Db\VersionControl\RelationComposite $entityRelationComposite
@@ -56,6 +64,7 @@ class Customer extends \Magento\Eav\Model\Entity\VersionControl\AbstractEntity
* @param \Magento\Framework\Stdlib\DateTime $dateTime
* @param \Magento\Store\Model\StoreManagerInterface $storeManager
* @param array $data
+ * @param AccountConfirmation $accountConfirmation
*/
public function __construct(
\Magento\Eav\Model\Entity\Context $context,
@@ -65,15 +74,19 @@ public function __construct(
\Magento\Framework\Validator\Factory $validatorFactory,
\Magento\Framework\Stdlib\DateTime $dateTime,
\Magento\Store\Model\StoreManagerInterface $storeManager,
- $data = []
+ $data = [],
+ AccountConfirmation $accountConfirmation = null
) {
parent::__construct($context, $entitySnapshot, $entityRelationComposite, $data);
+
$this->_scopeConfig = $scopeConfig;
$this->_validatorFactory = $validatorFactory;
$this->dateTime = $dateTime;
- $this->storeManager = $storeManager;
+ $this->accountConfirmation = $accountConfirmation ?: ObjectManager::getInstance()
+ ->get(AccountConfirmation::class);
$this->setType('customer');
$this->setConnection('customer_read', 'customer_write');
+ $this->storeManager = $storeManager;
}
/**
@@ -144,7 +157,13 @@ protected function _beforeSave(\Magento\Framework\DataObject $customer)
}
// set confirmation key logic
- if (!$customer->getId() && $customer->isConfirmationRequired()) {
+ if (!$customer->getId() &&
+ $this->accountConfirmation->isConfirmationRequired(
+ $customer->getWebsiteId(),
+ $customer->getId(),
+ $customer->getEmail()
+ )
+ ) {
$customer->setConfirmation($customer->getRandomConfirmationKey());
}
// remove customer confirmation key from database, if empty
diff --git a/app/code/Magento/Customer/Model/ResourceModel/GroupRepository.php b/app/code/Magento/Customer/Model/ResourceModel/GroupRepository.php
index 31e0e2727436b..664f85f841e3f 100644
--- a/app/code/Magento/Customer/Model/ResourceModel/GroupRepository.php
+++ b/app/code/Magento/Customer/Model/ResourceModel/GroupRepository.php
@@ -109,7 +109,7 @@ public function __construct(
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function save(\Magento\Customer\Api\Data\GroupInterface $group)
{
@@ -165,7 +165,7 @@ public function save(\Magento\Customer\Api\Data\GroupInterface $group)
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function getById($id)
{
@@ -179,7 +179,7 @@ public function getById($id)
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function getList(SearchCriteriaInterface $searchCriteria)
{
@@ -301,6 +301,7 @@ public function deleteById($id)
*
* @param \Magento\Customer\Api\Data\GroupInterface $group
* @throws InputException
+ * @throws \Zend_Validate_Exception
* @return void
*
* @SuppressWarnings(PHPMD.NPathComplexity)
diff --git a/app/code/Magento/Customer/Model/Session.php b/app/code/Magento/Customer/Model/Session.php
index 5900fed218edf..047327a0b6c29 100644
--- a/app/code/Magento/Customer/Model/Session.php
+++ b/app/code/Magento/Customer/Model/Session.php
@@ -10,6 +10,7 @@
use Magento\Customer\Api\GroupManagementInterface;
use Magento\Customer\Model\Config\Share;
use Magento\Customer\Model\ResourceModel\Customer as ResourceCustomer;
+use Magento\Framework\App\ObjectManager;
/**
* Customer session model
@@ -17,6 +18,7 @@
* @api
* @method string getNoReferer()
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
+ * @SuppressWarnings(PHPMD.CookieAndSessionMisuse)
* @since 100.0.2
*/
class Session extends \Magento\Framework\Session\SessionManager
@@ -107,6 +109,8 @@ class Session extends \Magento\Framework\Session\SessionManager
protected $response;
/**
+ * Session constructor.
+ *
* @param \Magento\Framework\App\Request\Http $request
* @param \Magento\Framework\Session\SidResolverInterface $sidResolver
* @param \Magento\Framework\Session\Config\ConfigInterface $sessionConfig
@@ -118,7 +122,7 @@ class Session extends \Magento\Framework\Session\SessionManager
* @param \Magento\Framework\App\State $appState
* @param Share $configShare
* @param \Magento\Framework\Url\Helper\Data $coreUrl
- * @param \Magento\Customer\Model\Url $customerUrl
+ * @param Url $customerUrl
* @param ResourceCustomer $customerResource
* @param CustomerFactory $customerFactory
* @param \Magento\Framework\UrlFactory $urlFactory
@@ -128,6 +132,7 @@ class Session extends \Magento\Framework\Session\SessionManager
* @param CustomerRepositoryInterface $customerRepository
* @param GroupManagementInterface $groupManagement
* @param \Magento\Framework\App\Response\Http $response
+ * @param AccountConfirmation $accountConfirmation
* @throws \Magento\Framework\Exception\SessionException
* @SuppressWarnings(PHPMD.ExcessiveParameterList)
*/
@@ -152,7 +157,8 @@ public function __construct(
\Magento\Framework\App\Http\Context $httpContext,
CustomerRepositoryInterface $customerRepository,
GroupManagementInterface $groupManagement,
- \Magento\Framework\App\Response\Http $response
+ \Magento\Framework\App\Response\Http $response,
+ AccountConfirmation $accountConfirmation = null
) {
$this->_coreUrl = $coreUrl;
$this->_customerUrl = $customerUrl;
@@ -177,6 +183,8 @@ public function __construct(
);
$this->groupManagement = $groupManagement;
$this->response = $response;
+ $this->accountConfirmation = $accountConfirmation ?: ObjectManager::getInstance()
+ ->get(AccountConfirmation::class);
$this->_eventManager->dispatch('customer_session_init', ['customer_session' => $this]);
}
@@ -216,6 +224,8 @@ public function setCustomerData(CustomerData $customer)
* Retrieve customer model object
*
* @return CustomerData
+ * @throws \Magento\Framework\Exception\LocalizedException
+ * @throws \Magento\Framework\Exception\NoSuchEntityException
*/
public function getCustomerData()
{
@@ -266,8 +276,14 @@ public function setCustomer(Customer $customerModel)
\Magento\Customer\Model\Group::NOT_LOGGED_IN_ID
);
$this->setCustomerId($customerModel->getId());
- if (!$customerModel->isConfirmationRequired() && $customerModel->getConfirmation()) {
- $customerModel->setConfirmation(null)->save();
+ $accountConfirmationRequired = $this->accountConfirmation->isConfirmationRequired(
+ $customerModel->getWebsiteId(),
+ $customerModel->getId(),
+ $customerModel->getEmail()
+ );
+ if (!$accountConfirmationRequired && $customerModel->getConfirmation() && $customerModel->getId()) {
+ $customerModel->setConfirmation(null);
+ $this->_customerResource->save($customerModel);
}
/**
@@ -354,10 +370,11 @@ public function setCustomerGroupId($id)
}
/**
- * Get customer group id
- * If customer is not logged in system, 'not logged in' group id will be returned
+ * Get customer group id. If customer is not logged in system, 'not logged in' group id will be returned.
*
* @return int
+ * @throws \Magento\Framework\Exception\LocalizedException
+ * @throws \Magento\Framework\Exception\NoSuchEntityException
*/
public function getCustomerGroupId()
{
@@ -407,6 +424,8 @@ public function checkCustomerId($customerId)
}
/**
+ * Sets customer as logged in
+ *
* @param Customer $customer
* @return $this
*/
@@ -420,6 +439,8 @@ public function setCustomerAsLoggedIn($customer)
}
/**
+ * Sets customer data as logged in
+ *
* @param CustomerData $customer
* @return $this
*/
@@ -521,6 +542,8 @@ protected function _setAuthUrl($key, $url)
* Logout without dispatching event
*
* @return $this
+ * @throws \Magento\Framework\Exception\LocalizedException
+ * @throws \Magento\Framework\Exception\NoSuchEntityException
*/
protected function _logout()
{
@@ -567,6 +590,8 @@ public function regenerateId()
}
/**
+ * Creates URL factory
+ *
* @return \Magento\Framework\UrlInterface
*/
protected function _createUrl()
diff --git a/app/code/Magento/Customer/Observer/UpgradeQuoteCustomerEmailObserver.php b/app/code/Magento/Customer/Observer/UpgradeQuoteCustomerEmailObserver.php
new file mode 100644
index 0000000000000..e0c9552b9f508
--- /dev/null
+++ b/app/code/Magento/Customer/Observer/UpgradeQuoteCustomerEmailObserver.php
@@ -0,0 +1,66 @@
+quoteRepository = $quoteRepository;
+ }
+
+ /**
+ * Upgrade quote customer email when customer has changed email
+ *
+ * @param Observer $observer
+ * @return void
+ */
+ public function execute(Observer $observer): void
+ {
+ /** @var \Magento\Customer\Model\Data\Customer $customerOrig */
+ $customerOrig = $observer->getEvent()->getOrigCustomerDataObject();
+ if (!$customerOrig) {
+ return;
+ }
+
+ $emailOrig = $customerOrig->getEmail();
+
+ /** @var \Magento\Customer\Model\Data\Customer $customer */
+ $customer = $observer->getEvent()->getCustomerDataObject();
+ $email = $customer->getEmail();
+
+ if ($email == $emailOrig) {
+ return;
+ }
+
+ try {
+ $quote = $this->quoteRepository->getForCustomer($customer->getId());
+ $quote->setCustomerEmail($email);
+ $this->quoteRepository->save($quote);
+ } catch (NoSuchEntityException $e) {
+ return;
+ }
+ }
+}
diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminAssertAddressInCustomersAddressGridActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminAssertAddressInCustomersAddressGridActionGroup.xml
index 53dba774d6c43..de9feca8c1c37 100644
--- a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminAssertAddressInCustomersAddressGridActionGroup.xml
+++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminAssertAddressInCustomersAddressGridActionGroup.xml
@@ -10,9 +10,13 @@
xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd">
+
+ Validates that the provided Customer Address is present in the Customer Address grid on the Customer creation/edit page. Under the 'Addresses' section.
+
+
diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminAssertCustomerAccountInformationActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminAssertCustomerAccountInformationActionGroup.xml
index a908d042fcc59..59889fb8c4d03 100644
--- a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminAssertCustomerAccountInformationActionGroup.xml
+++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminAssertCustomerAccountInformationActionGroup.xml
@@ -5,15 +5,20 @@
* See COPYING.txt for license details.
*/
-->
+
-
+
+
+ Validates that the provided Customer details are present and correct on the Customer creation/edit page. Under the 'Account Information' section.
+
+
diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminAssertCustomerDefaultBillingAddressActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminAssertCustomerDefaultBillingAddressActionGroup.xml
index 32b624706102f..0f15e92834ff2 100644
--- a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminAssertCustomerDefaultBillingAddressActionGroup.xml
+++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminAssertCustomerDefaultBillingAddressActionGroup.xml
@@ -5,10 +5,14 @@
* See COPYING.txt for license details.
*/
-->
+
-
+
+
+ Validates that the provided Default Customer Billing Address details are present and correct on the Customer creation/edit page. Under the section 'Addresses'.
+
@@ -18,6 +22,7 @@
+
diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminAssertCustomerDefaultShippingAddressActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminAssertCustomerDefaultShippingAddressActionGroup.xml
index 9d7c209121fd6..bca159079afd5 100644
--- a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminAssertCustomerDefaultShippingAddressActionGroup.xml
+++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminAssertCustomerDefaultShippingAddressActionGroup.xml
@@ -5,19 +5,24 @@
* See COPYING.txt for license details.
*/
-->
+
-
+
+
+ Validates that the provided Default Customer Shipping Address details are present and correct on the Customer creation/edit page. Under the section 'Addresses'.
+
-
+
+
diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminAssertCustomerInCustomersGridActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminAssertCustomerInCustomersGridActionGroup.xml
index d7529b3bdd58e..624051dd77eaf 100644
--- a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminAssertCustomerInCustomersGridActionGroup.xml
+++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminAssertCustomerInCustomersGridActionGroup.xml
@@ -10,10 +10,14 @@
xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd">
+
+ Validates that the provided Customer is present and correct in the Backend Customer grid page.
+
+
diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminAssertCustomerNoDefaultBillingAddressActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminAssertCustomerNoDefaultBillingAddressActionGroup.xml
index 5557025c4b1de..aaae471c70232 100644
--- a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminAssertCustomerNoDefaultBillingAddressActionGroup.xml
+++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminAssertCustomerNoDefaultBillingAddressActionGroup.xml
@@ -5,10 +5,15 @@
* See COPYING.txt for license details.
*/
-->
+
-
+
+
+ Validates that a Customer does NOT have a Default Billing Address assigned. PLEASE NOTE: The error message is hardcoded.
+
+
diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminAssertCustomerNoDefaultShippingAddressActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminAssertCustomerNoDefaultShippingAddressActionGroup.xml
index e33ebbb96ee19..0703ac5f5095e 100644
--- a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminAssertCustomerNoDefaultShippingAddressActionGroup.xml
+++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminAssertCustomerNoDefaultShippingAddressActionGroup.xml
@@ -5,10 +5,15 @@
* See COPYING.txt for license details.
*/
-->
+
-
+
+
+ Validates that a Customer does NOT have a Default Shipping Address assigned. PLEASE NOTE: The error message is hardcoded.
+
+
diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminAssertNumberOfRecordsInCustomersAddressGridActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminAssertNumberOfRecordsInCustomersAddressGridActionGroup.xml
index 390f723d91f17..9469e6e59a0d8 100644
--- a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminAssertNumberOfRecordsInCustomersAddressGridActionGroup.xml
+++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminAssertNumberOfRecordsInCustomersAddressGridActionGroup.xml
@@ -10,9 +10,13 @@
xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd">
+
+ Validates that the Number of Records listed on the Customer grid page is present and correct.
+
+
diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminConfigCustomerActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminConfigCustomerActionGroup.xml
index 8a3ab7068696c..a28d87f484a98 100644
--- a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminConfigCustomerActionGroup.xml
+++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminConfigCustomerActionGroup.xml
@@ -9,10 +9,16 @@
+
+ Goes to the Customer Configuration page. Fills Customer Data Lifetime. Clicks on Save. Validates that the Success message is present.
+
+
+
+
diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminCreateCustomerGroupActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminCreateCustomerGroupActionGroup.xml
index ba984a4d82562..a6a8daeb7ba87 100644
--- a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminCreateCustomerGroupActionGroup.xml
+++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminCreateCustomerGroupActionGroup.xml
@@ -5,13 +5,18 @@
* See COPYING.txt for license details.
*/
-->
+
+
+ Goes to the Customer Groups creation page. Fills Name. Selects Tax Class. Clicks on Save. Validate that the Success Message is present and correct.
+
+
diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminCreateCustomerWithDefaultAddressWithoutPhoneActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminCreateCustomerWithDefaultAddressWithoutPhoneActionGroup.xml
new file mode 100644
index 0000000000000..ff6512ba5fc0f
--- /dev/null
+++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminCreateCustomerWithDefaultAddressWithoutPhoneActionGroup.xml
@@ -0,0 +1,43 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminCreateCustomerWithWebsiteAndStoreViewActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminCreateCustomerWithWebsiteAndStoreViewActionGroup.xml
index 02366415d9edb..9741d4e4133bf 100644
--- a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminCreateCustomerWithWebsiteAndStoreViewActionGroup.xml
+++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminCreateCustomerWithWebsiteAndStoreViewActionGroup.xml
@@ -5,15 +5,20 @@
* See COPYING.txt for license details.
*/
-->
+
+
+ Goes to the Customer grid page. Click on 'Add New Customer'. Fills provided Customer Data. Fill provided Customer Address data. Assigns Product to Website and Store View. Click on Save Address.
+
+
@@ -44,11 +49,15 @@
+
+ Goes to the Customer grid page. Click on 'Add New Customer'. Fills provided Customer Data. Fill provided Customer Address data. Assigns Product to Website and Store View. Clicks on Save.
+
+
diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminCustomerGridActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminCustomerGridActionGroup.xml
index 86039056999b0..0443caeb63fd0 100644
--- a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminCustomerGridActionGroup.xml
+++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminCustomerGridActionGroup.xml
@@ -9,9 +9,13 @@
+
+ Goes to the Customer grid page. Filter results based on provided Customer Email Address.
+
+
diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminCustomerSaveAndContinueActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminCustomerSaveAndContinueActionGroup.xml
index 03b950a6dbe6f..662c487e56c46 100644
--- a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminCustomerSaveAndContinueActionGroup.xml
+++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminCustomerSaveAndContinueActionGroup.xml
@@ -5,10 +5,15 @@
* See COPYING.txt for license details.
*/
-->
+
-
+
+
+ Clicks on 'Save and Continue' on the Customer creation/edit page.
+
+
diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminCustomerShopingCartActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminCustomerShopingCartActionGroup.xml
index f5d5682e374f2..d4155c2e19d2c 100644
--- a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminCustomerShopingCartActionGroup.xml
+++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminCustomerShopingCartActionGroup.xml
@@ -9,9 +9,13 @@
+
+ Adds the provided Product Name to the Cart on the Admin Customer creation/edit page.
+
+
diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminDeleteAddressInCustomersAddressGridActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminDeleteAddressInCustomersAddressGridActionGroup.xml
index b61b353714e19..a6b072b3ffe48 100644
--- a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminDeleteAddressInCustomersAddressGridActionGroup.xml
+++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminDeleteAddressInCustomersAddressGridActionGroup.xml
@@ -10,9 +10,13 @@
xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd">
+
+ Deletes a Customer Address from the Customer creation/edit page under the 'Addresses' section.
+
+
diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminDeleteCustomerActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminDeleteCustomerActionGroup.xml
index d08f10b22419d..ebbd9ce469587 100644
--- a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminDeleteCustomerActionGroup.xml
+++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminDeleteCustomerActionGroup.xml
@@ -5,12 +5,17 @@
* See COPYING.txt for license details.
*/
-->
+
+
+ Goes to the Customers grid page. Check the row for the provided Customer Email Address. Delete the Customer.
+
+
diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminDeleteCustomerGroupActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminDeleteCustomerGroupActionGroup.xml
index 788e5f8967f43..ab5ae53fd4caa 100644
--- a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminDeleteCustomerGroupActionGroup.xml
+++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminDeleteCustomerGroupActionGroup.xml
@@ -9,9 +9,13 @@
+
+ Goes to the Customer Groups grid page. Filter results based on provided Customer Group Name. Delete Customer from grid page.
+
+
diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminEditCustomerAddressNoZipNoStateActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminEditCustomerAddressNoZipNoStateActionGroup.xml
index 954b83bead1d3..4c3660ac605a6 100644
--- a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminEditCustomerAddressNoZipNoStateActionGroup.xml
+++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminEditCustomerAddressNoZipNoStateActionGroup.xml
@@ -5,9 +5,14 @@
* See COPYING.txt for license details.
*/
-->
+
+
+ EXTENDS: AdminEditCustomerAddressesFrom. Removes 'selectState' and 'fillZipCode'. Clicks on 'Set Default' for Billing/Shipping.
+
+
diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminEditCustomerAddressSetDefaultShippingAndBillingActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminEditCustomerAddressSetDefaultShippingAndBillingActionGroup.xml
index 0c1af1cb5b67c..3551375c0e76b 100644
--- a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminEditCustomerAddressSetDefaultShippingAndBillingActionGroup.xml
+++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminEditCustomerAddressSetDefaultShippingAndBillingActionGroup.xml
@@ -5,9 +5,14 @@
* See COPYING.txt for license details.
*/
-->
+
+
+ EXTENDS: AdminEditCustomerAddressesFrom. Removes 'selectState' and 'fillZipCode'.
+
+
diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminEditCustomerAddressesFromActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminEditCustomerAddressesFromActionGroup.xml
index 9c38f23739b10..8ac4779de7a9a 100644
--- a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminEditCustomerAddressesFromActionGroup.xml
+++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminEditCustomerAddressesFromActionGroup.xml
@@ -5,16 +5,21 @@
* See COPYING.txt for license details.
*/
-->
+
-
+
+
+ Adds the provided Address to a Customer from the Admin Customers creation/edit page.
+
+
-
+
@@ -29,21 +34,36 @@
+
+
+ EXTENDS: AdminEditCustomerAddressesFrom. Clicks on 'Set Default' for Billing/Shipping.
+
+
+
+
+ EXTENDS: AdminEditCustomerAddressesFrom. Removes 'selectState' and 'fillZipCode'. Clicks on 'Set Default' for Billing/Shipping.
+
+
+
+
+ Selects the provided Option in the provided Customer Address Attribute drop down menu. Clicks on Save.
+
+
diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminEditCustomerInformationFromActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminEditCustomerInformationFromActionGroup.xml
index ddeefeb3c3742..09033955ecc60 100644
--- a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminEditCustomerInformationFromActionGroup.xml
+++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminEditCustomerInformationFromActionGroup.xml
@@ -5,15 +5,20 @@
* See COPYING.txt for license details.
*/
-->
+
-
+
+
+ Fills in the Customer First Name, Last Name and Email. Clicks on Save and Continue.
+
+
diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminFilterCustomerAddressGridByPhoneNumberActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminFilterCustomerAddressGridByPhoneNumberActionGroup.xml
index 2d0d44a4cc529..ce039a805b97a 100644
--- a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminFilterCustomerAddressGridByPhoneNumberActionGroup.xml
+++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminFilterCustomerAddressGridByPhoneNumberActionGroup.xml
@@ -10,9 +10,13 @@
xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd">
+
+ Filters the Admin Customers grid by the provided Phone Number.
+
+
diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminFilterCustomerByNameActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminFilterCustomerByNameActionGroup.xml
index c49a0dbe20ae7..958648c7fa4c2 100644
--- a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminFilterCustomerByNameActionGroup.xml
+++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminFilterCustomerByNameActionGroup.xml
@@ -9,9 +9,13 @@
+
+ Filters the Admin Customers grid by the provided Name.
+
+
diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminFilterCustomerGridByEmailActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminFilterCustomerGridByEmailActionGroup.xml
index 9cab8a790ff58..90492a9588fc8 100644
--- a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminFilterCustomerGridByEmailActionGroup.xml
+++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminFilterCustomerGridByEmailActionGroup.xml
@@ -10,9 +10,13 @@
xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd">
+
+ Filters the Admin Customers grid by the provided Email Address.
+
+
diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminFilterCustomerGroupByNameActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminFilterCustomerGroupByNameActionGroup.xml
index 1681a8e850ca2..5d9c250018297 100644
--- a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminFilterCustomerGroupByNameActionGroup.xml
+++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminFilterCustomerGroupByNameActionGroup.xml
@@ -9,9 +9,13 @@
+
+ Filters the Admin Customers grid by the provided Customer Group Name.
+
+
diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminOpenCustomerEditPageActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminOpenCustomerEditPageActionGroup.xml
index 8e6b56b19d674..e606cadcbdae0 100644
--- a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminOpenCustomerEditPageActionGroup.xml
+++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminOpenCustomerEditPageActionGroup.xml
@@ -5,13 +5,18 @@
* See COPYING.txt for license details.
*/
-->
+
+
+ Goes to the Admin Customer Edit page for the provided Customer ID #.
+
-
+
-
-
+
+
+
diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminResetFilterInCustomerAddressGridActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminResetFilterInCustomerAddressGridActionGroup.xml
index 135f010784199..b8f5f5efc2bb4 100644
--- a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminResetFilterInCustomerAddressGridActionGroup.xml
+++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminResetFilterInCustomerAddressGridActionGroup.xml
@@ -10,6 +10,10 @@
xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd">
+
+ Clears the Admin Customer Address grid Filters. Sets the grid view to 'Default View'.
+
+
diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminResetFilterInCustomerGridActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminResetFilterInCustomerGridActionGroup.xml
index 5c6ff347d565a..3649be6ed38db 100644
--- a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminResetFilterInCustomerGridActionGroup.xml
+++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminResetFilterInCustomerGridActionGroup.xml
@@ -10,6 +10,10 @@
xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd">
+
+ Clears the Admin Customer grid Filters. Sets the grid view to 'Default View'.
+
+
diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminSaveCustomerAndAssertSuccessMessageActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminSaveCustomerAndAssertSuccessMessageActionGroup.xml
index d3907e96b0d77..535c3421867ce 100644
--- a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminSaveCustomerAndAssertSuccessMessageActionGroup.xml
+++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminSaveCustomerAndAssertSuccessMessageActionGroup.xml
@@ -5,10 +5,15 @@
* See COPYING.txt for license details.
*/
-->
+
-
+
+
+ Clicks on the Save button. Validates that the Success Message is present and correct. PLEASE NOTE: The message is Hardcoded.
+
+
diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminSelectAllCustomersActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminSelectAllCustomersActionGroup.xml
index 1a8b4da67e74a..5b92990c545f0 100644
--- a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminSelectAllCustomersActionGroup.xml
+++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminSelectAllCustomersActionGroup.xml
@@ -9,6 +9,10 @@
+
+ Selects All of the Customers in the Admin Customers grid.
+
+
diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminSelectCustomerByEmailActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminSelectCustomerByEmailActionGroup.xml
index bb84d578fd9ed..6f25b43e99940 100644
--- a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminSelectCustomerByEmailActionGroup.xml
+++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminSelectCustomerByEmailActionGroup.xml
@@ -9,9 +9,13 @@
+
+ Checks the Customer for the provided Customer Email Address in the Admin Customers grid.
+
+
diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminUpdateCustomerGroupActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminUpdateCustomerGroupActionGroup.xml
index b1b82fb9fb74c..a71f81f807768 100644
--- a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminUpdateCustomerGroupActionGroup.xml
+++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminUpdateCustomerGroupActionGroup.xml
@@ -9,6 +9,9 @@
+
+ Goes to the Admin Customers grid page. Filters the grid for the provided Email Address. Edits the Customer. Updates the Customer Group with the provided Customer Group. Clicks on Save.
+
diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AssertAuthorizationPopUpPasswordAutoCompleteOffActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AssertAuthorizationPopUpPasswordAutoCompleteOffActionGroup.xml
index 186d0244e8c71..c5c9c0a6c9cf8 100644
--- a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AssertAuthorizationPopUpPasswordAutoCompleteOffActionGroup.xml
+++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AssertAuthorizationPopUpPasswordAutoCompleteOffActionGroup.xml
@@ -5,9 +5,14 @@
* See COPYING.txt for license details.
*/
-->
+
+
+ Validates that the Storefront Customer Sign-In popup form does NOT contain the 'autocomplete=off' attribute.
+
+
diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AssertCustomerAccountPageTitleActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AssertCustomerAccountPageTitleActionGroup.xml
index 132b5ca81886f..269d3a83aa47c 100644
--- a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AssertCustomerAccountPageTitleActionGroup.xml
+++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AssertCustomerAccountPageTitleActionGroup.xml
@@ -5,12 +5,17 @@
* See COPYING.txt for license details.
*/
-->
+
+
+ Validates that the provided Page Title is present and correct on the Storefront Customer Dashboard.
+
-
+
-
+
+
diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AssertCustomerGroupNotInGridActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AssertCustomerGroupNotInGridActionGroup.xml
index 26c4f23fc9a77..e4575624f45f3 100644
--- a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AssertCustomerGroupNotInGridActionGroup.xml
+++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AssertCustomerGroupNotInGridActionGroup.xml
@@ -5,12 +5,17 @@
* See COPYING.txt for license details.
*/
-->
+
+
+ Filters the Admin Customers grid for the Customer Group (Code). Validates that the grid is empty.
+
-
+
+
diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AssertCustomerGroupNotOnProductFormActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AssertCustomerGroupNotOnProductFormActionGroup.xml
index 94e01db5c1ff8..51f38065b9cbc 100644
--- a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AssertCustomerGroupNotOnProductFormActionGroup.xml
+++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AssertCustomerGroupNotOnProductFormActionGroup.xml
@@ -5,16 +5,22 @@
* See COPYING.txt for license details.
*/
-->
+
+
+ Clicks on 'Advanced Pricing' on the Admin Customers creation/edit page. Validates that the provided Customer Group does NOT appear in the dropdown menu.
+
-
+
+
-
+
+
customerGroups
{{customerGroup.code}}
diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AssertCustomerGroupOnCustomerFormActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AssertCustomerGroupOnCustomerFormActionGroup.xml
index 2c8e0081e5e90..410e07a3e3d2e 100644
--- a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AssertCustomerGroupOnCustomerFormActionGroup.xml
+++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AssertCustomerGroupOnCustomerFormActionGroup.xml
@@ -5,14 +5,19 @@
* See COPYING.txt for license details.
*/
-->
+
+
+ Validates that the provided Customer Group is selected on the Admin Customer edit page.
+
-
+
-
-
-
+
+
+
+
diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AssertCustomerLoggedInActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AssertCustomerLoggedInActionGroup.xml
index d2d4d86d7f964..6772fa5d68cd2 100644
--- a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AssertCustomerLoggedInActionGroup.xml
+++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AssertCustomerLoggedInActionGroup.xml
@@ -5,14 +5,18 @@
* See COPYING.txt for license details.
*/
-->
-
+
+
+ Validates that the Welcome message is present and correct, including the provided Customers Full Name.
+
-
+
+
-
+
diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AssertCustomerResetPasswordActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AssertCustomerResetPasswordActionGroup.xml
index 644254443d129..d48a001676ca0 100644
--- a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AssertCustomerResetPasswordActionGroup.xml
+++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AssertCustomerResetPasswordActionGroup.xml
@@ -9,13 +9,16 @@
+
+ Validates that the provided URL is present and correct. Validates that the provided Message/Message Type is present and correct.
+
-
-
+
+
-
+
diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AssertMessageCustomerChangeAccountInfoActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AssertMessageCustomerChangeAccountInfoActionGroup.xml
index ab48184ed98a8..22db0aabd24f5 100644
--- a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AssertMessageCustomerChangeAccountInfoActionGroup.xml
+++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AssertMessageCustomerChangeAccountInfoActionGroup.xml
@@ -5,14 +5,18 @@
* See COPYING.txt for license details.
*/
-->
-
+
+
+ Validates that the provided Message/Message Type are present and correct.
+
-
-
+
+
-
+
+
diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AssertMessageCustomerCreateAccountActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AssertMessageCustomerCreateAccountActionGroup.xml
index 65c9b025a9c2d..3a6dca174382f 100644
--- a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AssertMessageCustomerCreateAccountActionGroup.xml
+++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AssertMessageCustomerCreateAccountActionGroup.xml
@@ -5,14 +5,18 @@
* See COPYING.txt for license details.
*/
-->
-
+
+
+ Validates that the provided Message/Message Type are present and correct.
+
-
-
+
+
-
+
+
diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AssertMessageCustomerLoginActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AssertMessageCustomerLoginActionGroup.xml
index 6b88661985873..b53f8f55f01da 100644
--- a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AssertMessageCustomerLoginActionGroup.xml
+++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AssertMessageCustomerLoginActionGroup.xml
@@ -5,14 +5,18 @@
* See COPYING.txt for license details.
*/
-->
-
+
+
+ Validates that the provided Message/Message Type are present and correct.
+
-
-
+
+
-
+
+
diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AssertStorefrontCustomerSavedCardActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AssertStorefrontCustomerSavedCardActionGroup.xml
new file mode 100644
index 0000000000000..94cd759e5cef5
--- /dev/null
+++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AssertStorefrontCustomerSavedCardActionGroup.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AssertStorefrontPasswordAutocompleteOffActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AssertStorefrontPasswordAutocompleteOffActionGroup.xml
index 23a067cd94eea..1b2ec80f87340 100644
--- a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AssertStorefrontPasswordAutocompleteOffActionGroup.xml
+++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AssertStorefrontPasswordAutocompleteOffActionGroup.xml
@@ -5,9 +5,14 @@
* See COPYING.txt for license details.
*/
-->
+
+
+ Goes to the Storefront Customer Sign-In page. Validates that the Password field does NOT contain the 'autocomplete' attribute.
+
+
diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/CreateCustomerActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/CreateCustomerActionGroup.xml
index 047f656f5eabe..43827f0ee9b59 100644
--- a/app/code/Magento/Customer/Test/Mftf/ActionGroup/CreateCustomerActionGroup.xml
+++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/CreateCustomerActionGroup.xml
@@ -9,6 +9,10 @@
+
+ Goes to the Add Customers page via the Admin Nav Menu. Fills in the Customer details. Clicks on Save. PLEASE NOTE: The Customer data is Hardcoded using 'NewCustomerData'.
+
+
diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/DeleteCustomerActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/DeleteCustomerActionGroup.xml
index 06659dae156a4..58777ec0d3515 100644
--- a/app/code/Magento/Customer/Test/Mftf/ActionGroup/DeleteCustomerActionGroup.xml
+++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/DeleteCustomerActionGroup.xml
@@ -5,12 +5,17 @@
* See COPYING.txt for license details.
*/
-->
+
+
+ Goes to the Admin Customers grid page. Deletes a Customer based on the provided Last Name.
+
+
@@ -24,10 +29,15 @@
+
+
+ Goes to the Admin Customers grid page. Deletes a Customer based on the provided Email Address.
+
+
diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/EditCustomerAddressesFromAdminActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/EditCustomerAddressesFromAdminActionGroup.xml
index 617c895bc1201..cec668c4e7a23 100644
--- a/app/code/Magento/Customer/Test/Mftf/ActionGroup/EditCustomerAddressesFromAdminActionGroup.xml
+++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/EditCustomerAddressesFromAdminActionGroup.xml
@@ -5,15 +5,20 @@
* See COPYING.txt for license details.
*/
-->
+
-
+
+
+ Adds the provided Address to a Customer from the Admin Customers creation/edit page.
+
+
-
+
diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/FillCustomerSignInPopupFormActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/FillCustomerSignInPopupFormActionGroup.xml
index 56e9cfa2c0ab7..71c19b6d138d1 100644
--- a/app/code/Magento/Customer/Test/Mftf/ActionGroup/FillCustomerSignInPopupFormActionGroup.xml
+++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/FillCustomerSignInPopupFormActionGroup.xml
@@ -5,12 +5,17 @@
* See COPYING.txt for license details.
*/
-->
+
-
+
+
+ Fills in the provided Customer details (Email and Password) in the Customer Sign In modal on the Storefront Checkout page. Clicks on Sign In.
+
+
diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/LoginToStorefrontActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/LoginToStorefrontActionGroup.xml
index 703b9f542f81a..8b2a130d61021 100644
--- a/app/code/Magento/Customer/Test/Mftf/ActionGroup/LoginToStorefrontActionGroup.xml
+++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/LoginToStorefrontActionGroup.xml
@@ -5,18 +5,23 @@
* See COPYING.txt for license details.
*/
-->
+
+
+ Goes to the Storefront Customer Sign In page. Logs in using the provided Customer.
+
+
-
+
diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/LoginToStorefrontWithEmailAndPasswordActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/LoginToStorefrontWithEmailAndPasswordActionGroup.xml
index 071450001051e..d76bec63e735d 100644
--- a/app/code/Magento/Customer/Test/Mftf/ActionGroup/LoginToStorefrontWithEmailAndPasswordActionGroup.xml
+++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/LoginToStorefrontWithEmailAndPasswordActionGroup.xml
@@ -5,14 +5,19 @@
* See COPYING.txt for license details.
*/
-->
+
+
+ Goes to the Storefront Customer Sign In page. Logs in using the provided Email and Password.
+
-
+
+
diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/NavigateCustomerActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/NavigateCustomerActionGroup.xml
index be639d245f022..73748b9a6bad6 100644
--- a/app/code/Magento/Customer/Test/Mftf/ActionGroup/NavigateCustomerActionGroup.xml
+++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/NavigateCustomerActionGroup.xml
@@ -9,6 +9,10 @@
+
+ Goes to the Admin Customers grid page.
+
+
diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/NavigateCustomerGroupActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/NavigateCustomerGroupActionGroup.xml
index 076797f349107..78e9a2f18da95 100644
--- a/app/code/Magento/Customer/Test/Mftf/ActionGroup/NavigateCustomerGroupActionGroup.xml
+++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/NavigateCustomerGroupActionGroup.xml
@@ -9,6 +9,10 @@
+
+ Goes to the Admin Customer Groups page.
+
+
diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/NavigateThroughCustomerTabsActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/NavigateThroughCustomerTabsActionGroup.xml
index 5591bee529690..60833d6e61e30 100644
--- a/app/code/Magento/Customer/Test/Mftf/ActionGroup/NavigateThroughCustomerTabsActionGroup.xml
+++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/NavigateThroughCustomerTabsActionGroup.xml
@@ -5,13 +5,18 @@
* See COPYING.txt for license details.
*/
-->
+
+
+ Clicks on the provided Storefront Customer Dashboard Side Bar tab names.
+
-
+
-
+
+
diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/OpenEditCustomerFromAdminActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/OpenEditCustomerFromAdminActionGroup.xml
old mode 100755
new mode 100644
index 208f4f51e38e6..60a8a49954bab
--- a/app/code/Magento/Customer/Test/Mftf/ActionGroup/OpenEditCustomerFromAdminActionGroup.xml
+++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/OpenEditCustomerFromAdminActionGroup.xml
@@ -5,12 +5,17 @@
* See COPYING.txt for license details.
*/
-->
+
+
+ Goes to the Admin Customers grid page. Filters the grid based on the provided Customer. Clicks on Edit.
+
+
@@ -21,10 +26,15 @@
+
+
+ Filters the Admin Customers Addresses based on the provided Address. Clicks on Edit.
+
+
@@ -36,12 +46,17 @@
-
+
+
+
+ Goes to the Admin Customers grid page. Deletes the provided Customer from the grid. Validates that the Success message is present and correct.
+
+
@@ -54,7 +69,12 @@
+
+
+ Goes to the Admin Customers grid page. Clicks on 'Clear Filters'.
+
+
diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/OpenMyAccountPageActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/OpenMyAccountPageActionGroup.xml
index 6ca0f612deeaa..ca1b3469afac8 100644
--- a/app/code/Magento/Customer/Test/Mftf/ActionGroup/OpenMyAccountPageActionGroup.xml
+++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/OpenMyAccountPageActionGroup.xml
@@ -5,9 +5,14 @@
* See COPYING.txt for license details.
*/
-->
+
+
+ Clicks on the Storefront Customer menu in the Header. Clicks on 'My Account'.
+
+
diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/OpenStorefrontStoredPaymentMethodsPageActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/OpenStorefrontStoredPaymentMethodsPageActionGroup.xml
new file mode 100644
index 0000000000000..9975431e9fe6e
--- /dev/null
+++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/OpenStorefrontStoredPaymentMethodsPageActionGroup.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/SetGroupCustomerActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/SetGroupCustomerActionGroup.xml
index ca5e16c4ddb40..b3aabe4a52326 100644
--- a/app/code/Magento/Customer/Test/Mftf/ActionGroup/SetGroupCustomerActionGroup.xml
+++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/SetGroupCustomerActionGroup.xml
@@ -9,9 +9,13 @@
+
+ Clicks on the 'Actions' dropdown menu. Clicks on 'Assign a Customer Group'. Clicks on 'Ok'.
+
+
diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/SignUpNewUserFromStorefrontActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/SignUpNewUserFromStorefrontActionGroup.xml
index da01887133997..44988b202ab57 100644
--- a/app/code/Magento/Customer/Test/Mftf/ActionGroup/SignUpNewUserFromStorefrontActionGroup.xml
+++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/SignUpNewUserFromStorefrontActionGroup.xml
@@ -5,12 +5,17 @@
* See COPYING.txt for license details.
*/
-->
+
+
+ Goes to the Storefront. Clicks on 'Create Account'. Fills in the provided Customer details, excluding Newsletter Sign-Up. Clicks on 'Create Account' button. Validate that the Customer details are present and correct.
+
+
@@ -20,15 +25,19 @@
-
-
-
+
+
+
+
+ Goes to the Storefront. Clicks on 'Create Account'. Fills in the provided Customer details, including Newsletter Sign-Up. Clicks on 'Create Account' button.
+
+
@@ -39,7 +48,7 @@
-
+
@@ -54,18 +63,25 @@
+
+ Validates that the provided Customer details are present and correct on the Storefront Customer Dashboard page.
+
+
-
-
-
+
+
+
+
+ Fills in the provided Customer details (First/Last Name, Company, Phone # and Address) on the Admin Customer creation/edit page. Clicks on the Save button.
+
@@ -84,17 +100,27 @@
+
+
+ EXTENDS: EnterCustomerAddressInfo. Fills the State field.
+
+
+
+ Goes to the Storefront Customer Dashboard Address area. Validates that the provided Customer Billing Address is present and correct on the Storefront Customer Dashboard Address section.
+
+
+
@@ -104,10 +130,15 @@
+
+
+ Goes to the Storefront Customer Dashboard Address area. Validates that the provided Customer Shipping Address is present and correct.
+
+
@@ -119,12 +150,18 @@
+
+
+ Goes to the Storefront Customer Dashboard Address area. Validates that the provided Customer Billing Address, including the State, is present and correct.
+
+
+
@@ -134,12 +171,18 @@
+
+
+ Goes to the Storefront Customer Dashboard Address area. Validates that the provided Customer Shipping Address, including the State, is present and correct.
+
+
+
@@ -149,10 +192,15 @@
+
+
+ Goes to the Storefront Customer Dashboard page. Validates that the Customer First/Last Name is present and correct.
+
+
@@ -163,6 +211,10 @@
+
+ EXTENDS: SignUpNewUserFromStorefrontActionGroup. Adds a waitForPageLoad action to the Action Group. Removes the action for 'seeThankYouMessage'.
+
+
diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontAddCustomerAddressActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontAddCustomerAddressActionGroup.xml
index a45fcf31f7b3f..dc21ce5f52d73 100644
--- a/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontAddCustomerAddressActionGroup.xml
+++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontAddCustomerAddressActionGroup.xml
@@ -9,10 +9,14 @@
-
+
+ Goes to the Storefront Customer Add New Address page. Fills in the provided Address details. Clicks on Save.
+
+
+
@@ -25,11 +29,16 @@
+
-
+
+ Goes to the Storefront Customer Add New Default Address page. Fills in the provided Address details. Clicks on Save.
+
+
+
diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontAssertSuccessLoginToStorefrontActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontAssertSuccessLoginToStorefrontActionGroup.xml
index 475702ad69221..20d7b7d238755 100644
--- a/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontAssertSuccessLoginToStorefrontActionGroup.xml
+++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontAssertSuccessLoginToStorefrontActionGroup.xml
@@ -5,12 +5,17 @@
* See COPYING.txt for license details.
*/
-->
+
+
+ EXTENDS: LoginToStorefrontActionGroup. Validates that the provided Customer name is present and correct.
+
+
diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontClickCreateAnAccountCustomerAccountCreationFormActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontClickCreateAnAccountCustomerAccountCreationFormActionGroup.xml
index dfb9d1b2c259a..3c82426e81246 100644
--- a/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontClickCreateAnAccountCustomerAccountCreationFormActionGroup.xml
+++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontClickCreateAnAccountCustomerAccountCreationFormActionGroup.xml
@@ -9,7 +9,11 @@
-
-
+
+ Clicks on the Storefront Header 'Create Account' link.
+
+
+
+
diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontClickSignInButtonActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontClickSignInButtonActionGroup.xml
index b12858fc1037e..9f69c456b0af2 100644
--- a/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontClickSignInButtonActionGroup.xml
+++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontClickSignInButtonActionGroup.xml
@@ -7,9 +7,13 @@
-->
+ xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd">
-
+
+ Click on the Storefront Header 'Sign-In' link.
+
+
+
diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontClickSignOnCustomerLoginFormActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontClickSignOnCustomerLoginFormActionGroup.xml
index 9cd52b841fca4..bdc023563dd76 100644
--- a/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontClickSignOnCustomerLoginFormActionGroup.xml
+++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontClickSignOnCustomerLoginFormActionGroup.xml
@@ -9,7 +9,11 @@
-
-
+
+ Click on the Customer Sign In button on the Storefront Customer Sign-In page.
+
+
+
+
diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontCustomerActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontCustomerActionGroup.xml
index c3b92b1af7f82..5103e7ce80746 100644
--- a/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontCustomerActionGroup.xml
+++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontCustomerActionGroup.xml
@@ -7,12 +7,13 @@
-->
+ xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd">
-
-
+
+ Click on the Storefront Current Customer menu. Click on Logout.
+
+
+
+
diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontCustomerAddressBookContainsActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontCustomerAddressBookContainsActionGroup.xml
index 8385dc17ecf98..ebd0750c81522 100644
--- a/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontCustomerAddressBookContainsActionGroup.xml
+++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontCustomerAddressBookContainsActionGroup.xml
@@ -10,9 +10,13 @@
xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd">
+
+ Validate that the provided Address appears in the Storefront Customer Address list.
+
+
diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontCustomerAddressBookNotContainsActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontCustomerAddressBookNotContainsActionGroup.xml
index afef2d9a04e34..6bf5d69c30b19 100644
--- a/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontCustomerAddressBookNotContainsActionGroup.xml
+++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontCustomerAddressBookNotContainsActionGroup.xml
@@ -10,9 +10,13 @@
xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd">
+
+ Validate that the provided Address does NOT appear in the Storefront Customer Address list.
+
+
diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontCustomerAddressBookNumberOfAddressesActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontCustomerAddressBookNumberOfAddressesActionGroup.xml
index febc482d62e8b..700722c929b6f 100644
--- a/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontCustomerAddressBookNumberOfAddressesActionGroup.xml
+++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontCustomerAddressBookNumberOfAddressesActionGroup.xml
@@ -10,9 +10,13 @@
xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd">
+
+ Validate that the Customer Address count is present and correct on the Storefront Customer Dashboard page.
+
+
diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontCustomerChangeEmailActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontCustomerChangeEmailActionGroup.xml
index 844d13aa1fe43..47314a09378bd 100644
--- a/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontCustomerChangeEmailActionGroup.xml
+++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontCustomerChangeEmailActionGroup.xml
@@ -5,20 +5,24 @@
* See COPYING.txt for license details.
*/
-->
-
+
+
+ Change the Customer Email Address for a Customer Account via the Storefront Customer Dashboard page.
+
-
-
+
+
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontCustomerGoToSidebarMenuActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontCustomerGoToSidebarMenuActionGroup.xml
index 84d2f353b51d2..43e120602b471 100644
--- a/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontCustomerGoToSidebarMenuActionGroup.xml
+++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontCustomerGoToSidebarMenuActionGroup.xml
@@ -10,10 +10,14 @@
xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd">
+
+ Click on the Storefront Customer Account Dashboard sub-menu based on the provided Menu Name.
+
+
-
+
diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontCustomerLogoutActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontCustomerLogoutActionGroup.xml
index de97bb47de796..ed221350918a0 100644
--- a/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontCustomerLogoutActionGroup.xml
+++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontCustomerLogoutActionGroup.xml
@@ -9,10 +9,19 @@
+
+ Goes to the Storefront Customer Logout page.
+
+
+
+
+ Clicks on Customer Account. Clicks on 'Sign-Out'. Validates that the success message is present and correct. PLEASE NOTE: The Success Message is hardcoded.
+
+
diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontCustomerResetPasswordActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontCustomerResetPasswordActionGroup.xml
index a28593f1b77b7..d5f92ef5e528c 100644
--- a/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontCustomerResetPasswordActionGroup.xml
+++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontCustomerResetPasswordActionGroup.xml
@@ -9,16 +9,20 @@
+
+ Goes to the Storefront Customer Sign-In page. Clicks on 'Forgot Password'. Fills Email Address. Clicks on Reset Password.
+
-
+
+
-
+
diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontFillBillingAddressActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontFillBillingAddressActionGroup.xml
new file mode 100644
index 0000000000000..accf8f40bb282
--- /dev/null
+++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontFillBillingAddressActionGroup.xml
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontFillCustomerAccountCreationFormActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontFillCustomerAccountCreationFormActionGroup.xml
index 62d77d4548cce..acfa752e30a65 100644
--- a/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontFillCustomerAccountCreationFormActionGroup.xml
+++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontFillCustomerAccountCreationFormActionGroup.xml
@@ -9,12 +9,15 @@
+
+ Fills in the provided Customer details on the Storefront Customer creation page.
+
-
+
-
-
+
+
diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontFillCustomerLoginFormActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontFillCustomerLoginFormActionGroup.xml
index 22883ada7c2b1..0a45b07beeaad 100644
--- a/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontFillCustomerLoginFormActionGroup.xml
+++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontFillCustomerLoginFormActionGroup.xml
@@ -9,8 +9,11 @@
+
+ Fills Storefront Customer Login Email Address. Fills Storefront Customer Login Password.
+
-
+
diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontFillCustomerLoginFormWithWrongPasswordActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontFillCustomerLoginFormWithWrongPasswordActionGroup.xml
index 16d7fd197b52f..3a0e21d3e6ba5 100644
--- a/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontFillCustomerLoginFormWithWrongPasswordActionGroup.xml
+++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontFillCustomerLoginFormWithWrongPasswordActionGroup.xml
@@ -7,8 +7,12 @@
-->
+ xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd">
+
+ EXTENDS: StorefrontFillCustomerLoginFormActionGroup. Removes 'fillPassword'. Fills in an invalid Password.
+
+
diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontOpenCustomerAccountCreatePageActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontOpenCustomerAccountCreatePageActionGroup.xml
index 0ae470d0497ab..b013b1db1c8e7 100644
--- a/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontOpenCustomerAccountCreatePageActionGroup.xml
+++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontOpenCustomerAccountCreatePageActionGroup.xml
@@ -5,10 +5,14 @@
* See COPYING.txt for license details.
*/
-->
-
+
+
+ Goes to the Storefront Customer Create page.
+
+
diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontOpenCustomerAccountInfoEditPageActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontOpenCustomerAccountInfoEditPageActionGroup.xml
index c1ea2da8a9519..38dfe4613eb37 100644
--- a/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontOpenCustomerAccountInfoEditPageActionGroup.xml
+++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontOpenCustomerAccountInfoEditPageActionGroup.xml
@@ -5,10 +5,14 @@
* See COPYING.txt for license details.
*/
-->
-
+
+
+ Goes to the Storefront Customer Edit page.
+
+
diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontOpenCustomerLoginPageActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontOpenCustomerLoginPageActionGroup.xml
index 0a5c72265528a..3f045d1e23e87 100644
--- a/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontOpenCustomerLoginPageActionGroup.xml
+++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontOpenCustomerLoginPageActionGroup.xml
@@ -5,10 +5,14 @@
* See COPYING.txt for license details.
*/
-->
-
+
+
+ Goes to the Storefront Customer Sign-In page.
+
+
diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontOpenMyAccountPageActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontOpenMyAccountPageActionGroup.xml
new file mode 100644
index 0000000000000..57c350700d57d
--- /dev/null
+++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontOpenMyAccountPageActionGroup.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontRegisterCustomerFromOrderSuccessPageActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontRegisterCustomerFromOrderSuccessPageActionGroup.xml
index 5e24592bf017f..11c3c19b27789 100644
--- a/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontRegisterCustomerFromOrderSuccessPageActionGroup.xml
+++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontRegisterCustomerFromOrderSuccessPageActionGroup.xml
@@ -9,9 +9,13 @@
+
+ Clicks on 'Create Account' on the Storefront Checkout Success page. Fills in the provided Customer details. Clicks on 'Create Account'. Validates that the Success Message is present and correct.
+
-
+
+
diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/SwitchAccountActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/SwitchAccountActionGroup.xml
index 85e23940e1409..530912f6bed54 100644
--- a/app/code/Magento/Customer/Test/Mftf/ActionGroup/SwitchAccountActionGroup.xml
+++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/SwitchAccountActionGroup.xml
@@ -10,10 +10,14 @@
xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd">
+
+ Click on the Backend Admin current Admin User menu. Click on 'Logout'. Validate that you are logged out.
+
+
-
-
+
+
diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/VerifyGroupCustomerActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/VerifyGroupCustomerActionGroup.xml
index 712d3a59a2144..1f06c5a36cd38 100644
--- a/app/code/Magento/Customer/Test/Mftf/ActionGroup/VerifyGroupCustomerActionGroup.xml
+++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/VerifyGroupCustomerActionGroup.xml
@@ -9,10 +9,14 @@
+
+ Clicks on the Edit link for the provided Customer Email Address.
+
+
diff --git a/app/code/Magento/Customer/Test/Mftf/Data/AddressData.xml b/app/code/Magento/Customer/Test/Mftf/Data/AddressData.xml
old mode 100755
new mode 100644
index 88f86e456e5bf..4d7a39b3246e1
--- a/app/code/Magento/Customer/Test/Mftf/Data/AddressData.xml
+++ b/app/code/Magento/Customer/Test/Mftf/Data/AddressData.xml
@@ -271,6 +271,20 @@
Yes
RegionUT
+
+ Jane
+ Miller
+ Magento
+ 44 20 7123 1234
+
+ - 1 London Bridge Street
+
+ GB
+ United Kingdom
+ London
+
+ SE12 9GF
+
John
Doe
diff --git a/app/code/Magento/Customer/Test/Mftf/Data/CustomerConfigData.xml b/app/code/Magento/Customer/Test/Mftf/Data/CustomerConfigData.xml
index 870bd929eea9a..ab4307082595d 100644
--- a/app/code/Magento/Customer/Test/Mftf/Data/CustomerConfigData.xml
+++ b/app/code/Magento/Customer/Test/Mftf/Data/CustomerConfigData.xml
@@ -14,14 +14,24 @@
1
-
GlobalCustomerAccountSharing
0
-
+
+ customer/startup/redirect_dashboard
+ 0
+ Yes
+ 1
+
+
+ customer/startup/redirect_dashboard
+ 0
+ No
+ 0
+
CustomerAccountSharingInherit
diff --git a/app/code/Magento/Customer/Test/Mftf/Data/CustomerData.xml b/app/code/Magento/Customer/Test/Mftf/Data/CustomerData.xml
index 55bc9b56c7917..c7a73b61dc48a 100644
--- a/app/code/Magento/Customer/Test/Mftf/Data/CustomerData.xml
+++ b/app/code/Magento/Customer/Test/Mftf/Data/CustomerData.xml
@@ -230,6 +230,20 @@
0
0
+
+ 0
+ true
+ true
+ david@email.com
+ David
+ Mill
+ David Mill
+ pwdTest123!
+ 0
+ 0
+ 0
+ updateCustomerUKAddress
+
0
true
diff --git a/app/code/Magento/Customer/Test/Mftf/Page/StorefrontCustomerStoredPaymentMethodsPage.xml b/app/code/Magento/Customer/Test/Mftf/Page/StorefrontCustomerStoredPaymentMethodsPage.xml
new file mode 100644
index 0000000000000..bec802689a6cc
--- /dev/null
+++ b/app/code/Magento/Customer/Test/Mftf/Page/StorefrontCustomerStoredPaymentMethodsPage.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Customer/Test/Mftf/Section/AdminCreateUserSection.xml b/app/code/Magento/Customer/Test/Mftf/Section/AdminCreateUserSection.xml
index 376b0b9f66db9..9548fb3175496 100644
--- a/app/code/Magento/Customer/Test/Mftf/Section/AdminCreateUserSection.xml
+++ b/app/code/Magento/Customer/Test/Mftf/Section/AdminCreateUserSection.xml
@@ -19,6 +19,7 @@
+
diff --git a/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerAccountInformationSection.xml b/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerAccountInformationSection.xml
index 1de280ff4d114..4b36486f0bd17 100644
--- a/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerAccountInformationSection.xml
+++ b/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerAccountInformationSection.xml
@@ -33,6 +33,7 @@
+
diff --git a/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerGridMainActionsSection.xml b/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerGridMainActionsSection.xml
old mode 100755
new mode 100644
diff --git a/app/code/Magento/Customer/Test/Mftf/Section/AdminEditCustomerOrdersSection.xml b/app/code/Magento/Customer/Test/Mftf/Section/AdminEditCustomerOrdersSection.xml
index 89fed43184b84..fb3d1570848e4 100644
--- a/app/code/Magento/Customer/Test/Mftf/Section/AdminEditCustomerOrdersSection.xml
+++ b/app/code/Magento/Customer/Test/Mftf/Section/AdminEditCustomerOrdersSection.xml
@@ -10,5 +10,6 @@
xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd">
diff --git a/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerAccountInformationSection.xml b/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerAccountInformationSection.xml
index 8a633ec5bc271..9828c42211e41 100644
--- a/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerAccountInformationSection.xml
+++ b/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerAccountInformationSection.xml
@@ -20,5 +20,8 @@
+
+
+
diff --git a/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerAccountMainSection.xml b/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerAccountMainSection.xml
index c00b54b3964da..a501a7ac4a618 100644
--- a/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerAccountMainSection.xml
+++ b/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerAccountMainSection.xml
@@ -11,5 +11,6 @@
diff --git a/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerAddressesSection.xml b/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerAddressesSection.xml
index 6e01742938e05..dee62070ab9bc 100644
--- a/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerAddressesSection.xml
+++ b/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerAddressesSection.xml
@@ -20,5 +20,7 @@
+
+
diff --git a/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerCreateFormSection.xml b/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerCreateFormSection.xml
index 8881a2a012ce8..ba1f10a480745 100644
--- a/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerCreateFormSection.xml
+++ b/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerCreateFormSection.xml
@@ -30,5 +30,6 @@
+
diff --git a/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerDashboardAccountInformationSection.xml b/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerDashboardAccountInformationSection.xml
index f83bd64a2a8d2..85d0fd166a77e 100644
--- a/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerDashboardAccountInformationSection.xml
+++ b/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerDashboardAccountInformationSection.xml
@@ -12,6 +12,7 @@
+
diff --git a/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerSidebarSection.xml b/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerSidebarSection.xml
index 407c6480e9dde..c6b9aa0372edc 100644
--- a/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerSidebarSection.xml
+++ b/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerSidebarSection.xml
@@ -9,7 +9,7 @@
diff --git a/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerSignInFormSection.xml b/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerSignInFormSection.xml
index b2c583e008acc..6b333a0c714c0 100644
--- a/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerSignInFormSection.xml
+++ b/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerSignInFormSection.xml
@@ -20,6 +20,7 @@
+
diff --git a/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerStoredPaymentMethodsSection.xml b/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerStoredPaymentMethodsSection.xml
new file mode 100644
index 0000000000000..d6b586e42f28c
--- /dev/null
+++ b/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerStoredPaymentMethodsSection.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
diff --git a/app/code/Magento/Customer/Test/Mftf/Test/SearchByEmailInCustomerGridTest.xml b/app/code/Magento/Customer/Test/Mftf/Test/SearchByEmailInCustomerGridTest.xml
new file mode 100644
index 0000000000000..e16ec92e507e6
--- /dev/null
+++ b/app/code/Magento/Customer/Test/Mftf/Test/SearchByEmailInCustomerGridTest.xml
@@ -0,0 +1,45 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Customer/Test/Mftf/Test/StorefrontCheckTaxAddingValidVATIdTest.xml b/app/code/Magento/Customer/Test/Mftf/Test/StorefrontCheckTaxAddingValidVATIdTest.xml
index 0cba9159dd5ac..8469126547eb1 100644
--- a/app/code/Magento/Customer/Test/Mftf/Test/StorefrontCheckTaxAddingValidVATIdTest.xml
+++ b/app/code/Magento/Customer/Test/Mftf/Test/StorefrontCheckTaxAddingValidVATIdTest.xml
@@ -17,9 +17,6 @@
-
-
-
diff --git a/app/code/Magento/Customer/Test/Mftf/Test/StorefrontClearAllCompareProductsTest.xml b/app/code/Magento/Customer/Test/Mftf/Test/StorefrontClearAllCompareProductsTest.xml
index 2b88657c6ca2b..ada3adbfeb83b 100644
--- a/app/code/Magento/Customer/Test/Mftf/Test/StorefrontClearAllCompareProductsTest.xml
+++ b/app/code/Magento/Customer/Test/Mftf/Test/StorefrontClearAllCompareProductsTest.xml
@@ -38,6 +38,30 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -77,6 +101,9 @@
+
+
+
@@ -89,6 +116,7 @@
+
diff --git a/app/code/Magento/Customer/Test/Mftf/Test/StorefrontForgotPasswordTest.xml b/app/code/Magento/Customer/Test/Mftf/Test/StorefrontForgotPasswordTest.xml
index 8d4be5fda3c79..0c6c7dd9b800c 100644
--- a/app/code/Magento/Customer/Test/Mftf/Test/StorefrontForgotPasswordTest.xml
+++ b/app/code/Magento/Customer/Test/Mftf/Test/StorefrontForgotPasswordTest.xml
@@ -21,6 +21,8 @@
+
+
diff --git a/app/code/Magento/Customer/Test/Mftf/Test/StorefrontVerifySecureURLRedirectCustomerTest.xml b/app/code/Magento/Customer/Test/Mftf/Test/StorefrontVerifySecureURLRedirectCustomerTest.xml
new file mode 100644
index 0000000000000..da9dddf0539d3
--- /dev/null
+++ b/app/code/Magento/Customer/Test/Mftf/Test/StorefrontVerifySecureURLRedirectCustomerTest.xml
@@ -0,0 +1,40 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Customer/Test/Mftf/Test/VerifyDisabledCustomerGroupFieldTest.xml b/app/code/Magento/Customer/Test/Mftf/Test/VerifyDisabledCustomerGroupFieldTest.xml
index 648c30b1ca0bb..0f98184aafb4f 100644
--- a/app/code/Magento/Customer/Test/Mftf/Test/VerifyDisabledCustomerGroupFieldTest.xml
+++ b/app/code/Magento/Customer/Test/Mftf/Test/VerifyDisabledCustomerGroupFieldTest.xml
@@ -26,7 +26,7 @@
-
+
diff --git a/app/code/Magento/Customer/Test/Unit/Block/Form/RegisterTest.php b/app/code/Magento/Customer/Test/Unit/Block/Form/RegisterTest.php
index b93b9f40d75b2..3022177ffb9e1 100644
--- a/app/code/Magento/Customer/Test/Unit/Block/Form/RegisterTest.php
+++ b/app/code/Magento/Customer/Test/Unit/Block/Form/RegisterTest.php
@@ -7,7 +7,6 @@
use Magento\Customer\Block\Form\Register;
use Magento\Customer\Model\AccountManagement;
-use Magento\Newsletter\Observer\PredispatchNewsletterObserver;
/**
* Test class for \Magento\Customer\Block\Form\Register.
@@ -49,6 +48,9 @@ class RegisterTest extends \PHPUnit\Framework\TestCase
/** @var Register */
private $_block;
+ /** @var \PHPUnit_Framework_MockObject_MockObject | \Magento\Newsletter\Model\Config */
+ private $newsletterConfig;
+
protected function setUp()
{
$this->_scopeConfig = $this->createMock(\Magento\Framework\App\Config\ScopeConfigInterface::class);
@@ -59,6 +61,7 @@ protected function setUp()
\Magento\Customer\Model\Session::class,
['getCustomerFormData']
);
+ $this->newsletterConfig = $this->createMock(\Magento\Newsletter\Model\Config::class);
$context = $this->createMock(\Magento\Framework\View\Element\Template\Context::class);
$context->expects($this->any())->method('getScopeConfig')->will($this->returnValue($this->_scopeConfig));
@@ -71,7 +74,9 @@ protected function setUp()
$this->createMock(\Magento\Directory\Model\ResourceModel\Country\CollectionFactory::class),
$this->_moduleManager,
$this->_customerSession,
- $this->_customerUrl
+ $this->_customerUrl,
+ [],
+ $this->newsletterConfig
);
}
@@ -293,12 +298,10 @@ public function testIsNewsletterEnabled($isNewsletterEnabled, $isNewsletterActiv
$this->returnValue($isNewsletterEnabled)
);
- $this->_scopeConfig->expects(
+ $this->newsletterConfig->expects(
$this->any()
)->method(
- 'getValue'
- )->with(
- PredispatchNewsletterObserver::XML_PATH_NEWSLETTER_ACTIVE
+ 'isActive'
)->will(
$this->returnValue($isNewsletterActive)
);
diff --git a/app/code/Magento/Customer/Test/Unit/Block/Widget/DobTest.php b/app/code/Magento/Customer/Test/Unit/Block/Widget/DobTest.php
index 6926eee4f28f2..8bfddac3cef8f 100644
--- a/app/code/Magento/Customer/Test/Unit/Block/Widget/DobTest.php
+++ b/app/code/Magento/Customer/Test/Unit/Block/Widget/DobTest.php
@@ -6,14 +6,31 @@
namespace Magento\Customer\Test\Unit\Block\Widget;
+use Magento\Customer\Api\CustomerMetadataInterface;
+use Magento\Customer\Api\Data\AttributeMetadataInterface;
+use Magento\Customer\Api\Data\ValidationRuleInterface;
+use Magento\Customer\Helper\Address;
+use Magento\Framework\App\CacheInterface;
+use Magento\Framework\Cache\FrontendInterface;
+use Magento\Framework\Data\Form\FilterFactory;
+use Magento\Framework\Escaper;
use Magento\Framework\Exception\NoSuchEntityException;
use Magento\Customer\Block\Widget\Dob;
use Magento\Framework\Locale\Resolver;
+use Magento\Framework\Locale\ResolverInterface;
+use Magento\Framework\Stdlib\DateTime\Timezone;
+use Magento\Framework\TestFramework\Unit\Helper\ObjectManager;
+use Magento\Framework\View\Element\Html\Date;
+use Magento\Framework\View\Element\Template\Context;
+use PHPUnit\Framework\TestCase;
+use PHPUnit_Framework_MockObject_MockObject;
+use Zend_Cache_Backend_BlackHole;
+use Zend_Cache_Core;
/**
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
*/
-class DobTest extends \PHPUnit\Framework\TestCase
+class DobTest extends TestCase
{
/** Constants used in the unit tests */
const MIN_DATE = '01/01/2010';
@@ -43,82 +60,105 @@ class DobTest extends \PHPUnit\Framework\TestCase
const YEAR_HTML =
'yy
';
- /** @var \PHPUnit_Framework_MockObject_MockObject|\Magento\Customer\Api\Data\AttributeMetadataInterface */
+ /** @var PHPUnit_Framework_MockObject_MockObject|AttributeMetadataInterface */
protected $attribute;
/** @var Dob */
protected $_block;
- /** @var \PHPUnit_Framework_MockObject_MockObject|\Magento\Customer\Api\CustomerMetadataInterface */
+ /** @var PHPUnit_Framework_MockObject_MockObject|CustomerMetadataInterface */
protected $customerMetadata;
/**
- * @var \Magento\Framework\Data\Form\FilterFactory|\PHPUnit_Framework_MockObject_MockObject
+ * @var FilterFactory|PHPUnit_Framework_MockObject_MockObject
*/
protected $filterFactory;
/**
- * @var \Magento\Framework\Escaper
+ * @var Escaper
*/
private $escaper;
/**
- * @var \Magento\Framework\View\Element\Template\Context
+ * @var Context
*/
private $context;
+ /**
+ * @var string
+ */
+ private $_locale;
+ /**
+ * @inheritDoc
+ */
protected function setUp()
{
- $zendCacheCore = new \Zend_Cache_Core();
- $zendCacheCore->setBackend(new \Zend_Cache_Backend_BlackHole());
+ $zendCacheCore = new Zend_Cache_Core();
+ $zendCacheCore->setBackend(new Zend_Cache_Backend_BlackHole());
$frontendCache = $this->getMockForAbstractClass(
- \Magento\Framework\Cache\FrontendInterface::class,
+ FrontendInterface::class,
[],
'',
false
);
$frontendCache->expects($this->any())->method('getLowLevelFrontend')->will($this->returnValue($zendCacheCore));
- $cache = $this->createMock(\Magento\Framework\App\CacheInterface::class);
+ $cache = $this->createMock(CacheInterface::class);
$cache->expects($this->any())->method('getFrontend')->will($this->returnValue($frontendCache));
- $objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this);
- $localeResolver = $this->createMock(\Magento\Framework\Locale\ResolverInterface::class);
+ $objectManager = new ObjectManager($this);
+ $localeResolver = $this->createMock(ResolverInterface::class);
$localeResolver->expects($this->any())
->method('getLocale')
- ->willReturn(Resolver::DEFAULT_LOCALE);
+ ->willReturnCallback(
+ function () {
+ return $this->_locale;
+ }
+ );
$timezone = $objectManager->getObject(
- \Magento\Framework\Stdlib\DateTime\Timezone::class,
+ Timezone::class,
['localeResolver' => $localeResolver]
);
- $this->context = $this->createMock(\Magento\Framework\View\Element\Template\Context::class);
+ $this->_locale = Resolver::DEFAULT_LOCALE;
+ $this->context = $this->createMock(Context::class);
$this->context->expects($this->any())->method('getLocaleDate')->will($this->returnValue($timezone));
- $this->escaper = $this->getMockBuilder(\Magento\Framework\Escaper::class)
+ $this->escaper = $this->getMockBuilder(Escaper::class)
->disableOriginalConstructor()
->setMethods(['escapeHtml'])
->getMock();
$this->context->expects($this->any())->method('getEscaper')->will($this->returnValue($this->escaper));
- $this->attribute = $this->getMockBuilder(\Magento\Customer\Api\Data\AttributeMetadataInterface::class)
+ $this->attribute = $this->getMockBuilder(AttributeMetadataInterface::class)
->getMockForAbstractClass();
- $this->customerMetadata = $this->getMockBuilder(\Magento\Customer\Api\CustomerMetadataInterface::class)
+ $this->attribute
+ ->expects($this->any())
+ ->method('getInputFilter')
+ ->willReturn('date');
+ $this->customerMetadata = $this->getMockBuilder(CustomerMetadataInterface::class)
->getMockForAbstractClass();
$this->customerMetadata->expects($this->any())
->method('getAttributeMetadata')
->will($this->returnValue($this->attribute));
- date_default_timezone_set('America/Los_Angeles');
-
- $this->filterFactory = $this->getMockBuilder(\Magento\Framework\Data\Form\FilterFactory::class)
- ->disableOriginalConstructor()
- ->getMock();
+ $this->filterFactory = $this->createMock(FilterFactory::class);
+ $this->filterFactory
+ ->expects($this->any())
+ ->method('create')
+ ->willReturnCallback(
+ function () use ($timezone, $localeResolver) {
+ return new \Magento\Framework\Data\Form\Filter\Date(
+ $timezone->getDateFormatWithLongYear(),
+ $localeResolver
+ );
+ }
+ );
- $this->_block = new \Magento\Customer\Block\Widget\Dob(
+ $this->_block = new Dob(
$this->context,
- $this->createMock(\Magento\Customer\Helper\Address::class),
+ $this->createMock(Address::class),
$this->customerMetadata,
- $this->createMock(\Magento\Framework\View\Element\Html\Date::class),
+ $this->createMock(Date::class),
$this->filterFactory
);
}
@@ -143,17 +183,22 @@ public function isEnabledDataProvider()
return [[true, true], [false, false]];
}
+ /**
+ * Tests isEnabled()
+ */
public function testIsEnabledWithException()
{
$this->customerMetadata->expects($this->any())
->method('getAttributeMetadata')
->will(
- $this->throwException(new NoSuchEntityException(
- __(
- 'No such entity with %fieldName = %fieldValue',
- ['fieldName' => 'field', 'fieldValue' => 'value']
+ $this->throwException(
+ new NoSuchEntityException(
+ __(
+ 'No such entity with %fieldName = %fieldValue',
+ ['fieldName' => 'field', 'fieldValue' => 'value']
+ )
)
- ))
+ )
);
$this->assertSame(false, $this->_block->isEnabled());
}
@@ -175,12 +220,14 @@ public function testIsRequiredWithException()
$this->customerMetadata->expects($this->any())
->method('getAttributeMetadata')
->will(
- $this->throwException(new NoSuchEntityException(
- __(
- 'No such entity with %fieldName = %fieldValue',
- ['fieldName' => 'field', 'fieldValue' => 'value']
+ $this->throwException(
+ new NoSuchEntityException(
+ __(
+ 'No such entity with %fieldName = %fieldValue',
+ ['fieldName' => 'field', 'fieldValue' => 'value']
+ )
)
- ))
+ )
);
$this->assertSame(false, $this->_block->isRequired());
}
@@ -197,14 +244,15 @@ public function isRequiredDataProvider()
* @param string|bool $date Date (e.g. '01/01/2020' or false for no date)
* @param int|bool $expectedTime The value we expect from Dob::getTime()
* @param string|bool $expectedDate The value we expect from Dob::getData('date')
- *
+ * @param string $locale
* @dataProvider setDateDataProvider
*/
- public function testSetDate($date, $expectedTime, $expectedDate)
+ public function testSetDate($date, $expectedTime, $expectedDate, $locale = Resolver::DEFAULT_LOCALE)
{
+ $this->_locale = $locale;
$this->assertSame($this->_block, $this->_block->setDate($date));
- $this->assertEquals($expectedTime, $this->_block->getTime());
- $this->assertEquals($expectedDate, $this->_block->getValue());
+ $this->assertSame($expectedTime, $this->_block->getTime());
+ $this->assertSame($expectedDate, $this->_block->getValue());
}
/**
@@ -212,32 +260,19 @@ public function testSetDate($date, $expectedTime, $expectedDate)
*/
public function setDateDataProvider()
{
- return [[self::DATE, strtotime(self::DATE), self::DATE], [false, false, false]];
- }
-
- public function testSetDateWithFilter()
- {
- $date = '2014-01-01';
- $filterCode = 'date';
-
- $this->attribute->expects($this->once())
- ->method('getInputFilter')
- ->willReturn($filterCode);
-
- $filterMock = $this->getMockBuilder(\Magento\Framework\Data\Form\Filter\Date::class)
- ->disableOriginalConstructor()
- ->getMock();
- $filterMock->expects($this->once())
- ->method('outputFilter')
- ->with($date)
- ->willReturn(self::DATE);
-
- $this->filterFactory->expects($this->once())
- ->method('create')
- ->with($filterCode, ['format' => self::DATE_FORMAT])
- ->willReturn($filterMock);
-
- $this->_block->setDate($date);
+ return [
+ [false, false, false],
+ ['', false, ''],
+ ['12/31/1999', strtotime('1999-12-31'), '12/31/1999', 'en_US'],
+ ['31-12-1999', strtotime('1999-12-31'), '12/31/1999', 'en_US'],
+ ['1999-12-31', strtotime('1999-12-31'), '12/31/1999', 'en_US'],
+ ['31 December 1999', strtotime('1999-12-31'), '12/31/1999', 'en_US'],
+ ['12/31/1999', strtotime('1999-12-31'), '31/12/1999', 'fr_FR'],
+ ['31-12-1999', strtotime('1999-12-31'), '31/12/1999', 'fr_FR'],
+ ['31/12/1999', strtotime('1999-12-31'), '31/12/1999', 'fr_FR'],
+ ['1999-12-31', strtotime('1999-12-31'), '31/12/1999', 'fr_FR'],
+ ['31 Décembre 1999', strtotime('1999-12-31'), '31/12/1999', 'fr_FR'],
+ ];
}
/**
@@ -301,7 +336,6 @@ public function getYearDataProvider()
}
/**
- * The \Magento\Framework\Locale\ResolverInterface::DEFAULT_LOCALE
* is used to derive the Locale that is used to determine the
* value of Dob::getDateFormat() for that Locale.
*/
@@ -358,12 +392,12 @@ public function testGetMinDateRange($validationRules, $expectedValue)
*/
public function getMinDateRangeDataProvider()
{
- $emptyValidationRule = $this->getMockBuilder(\Magento\Customer\Api\Data\ValidationRuleInterface::class)
+ $emptyValidationRule = $this->getMockBuilder(ValidationRuleInterface::class)
->disableOriginalConstructor()
->setMethods(['getName', 'getValue'])
->getMockForAbstractClass();
- $validationRule = $this->getMockBuilder(\Magento\Customer\Api\Data\ValidationRuleInterface::class)
+ $validationRule = $this->getMockBuilder(ValidationRuleInterface::class)
->disableOriginalConstructor()
->setMethods(['getName', 'getValue'])
->getMockForAbstractClass();
@@ -390,17 +424,22 @@ public function getMinDateRangeDataProvider()
];
}
+ /**
+ * Tests getMinDateRange()
+ */
public function testGetMinDateRangeWithException()
{
$this->customerMetadata->expects($this->any())
->method('getAttributeMetadata')
->will(
- $this->throwException(new NoSuchEntityException(
- __(
- 'No such entity with %fieldName = %fieldValue',
- ['fieldName' => 'field', 'fieldValue' => 'value']
+ $this->throwException(
+ new NoSuchEntityException(
+ __(
+ 'No such entity with %fieldName = %fieldValue',
+ ['fieldName' => 'field', 'fieldValue' => 'value']
+ )
)
- ))
+ )
);
$this->assertNull($this->_block->getMinDateRange());
}
@@ -424,12 +463,12 @@ public function testGetMaxDateRange($validationRules, $expectedValue)
*/
public function getMaxDateRangeDataProvider()
{
- $emptyValidationRule = $this->getMockBuilder(\Magento\Customer\Api\Data\ValidationRuleInterface::class)
+ $emptyValidationRule = $this->getMockBuilder(ValidationRuleInterface::class)
->disableOriginalConstructor()
->setMethods(['getName', 'getValue'])
->getMockForAbstractClass();
- $validationRule = $this->getMockBuilder(\Magento\Customer\Api\Data\ValidationRuleInterface::class)
+ $validationRule = $this->getMockBuilder(ValidationRuleInterface::class)
->disableOriginalConstructor()
->setMethods(['getName', 'getValue'])
->getMockForAbstractClass();
@@ -455,21 +494,29 @@ public function getMaxDateRangeDataProvider()
];
}
+ /**
+ * Tests getMaxDateRange()
+ */
public function testGetMaxDateRangeWithException()
{
$this->customerMetadata->expects($this->any())
->method('getAttributeMetadata')
->will(
- $this->throwException(new NoSuchEntityException(
- __(
- 'No such entity with %fieldName = %fieldValue',
- ['fieldName' => 'field', 'fieldValue' => 'value']
+ $this->throwException(
+ new NoSuchEntityException(
+ __(
+ 'No such entity with %fieldName = %fieldValue',
+ ['fieldName' => 'field', 'fieldValue' => 'value']
+ )
)
- ))
+ )
);
$this->assertNull($this->_block->getMaxDateRange());
}
+ /**
+ * Tests getHtmlExtraParams() without required options
+ */
public function testGetHtmlExtraParamsWithoutRequiredOption()
{
$this->escaper->expects($this->any())
@@ -487,6 +534,9 @@ public function testGetHtmlExtraParamsWithoutRequiredOption()
);
}
+ /**
+ * Tests getHtmlExtraParams() with required options
+ */
public function testGetHtmlExtraParamsWithRequiredOption()
{
$this->attribute->expects($this->once())
diff --git a/app/code/Magento/Customer/Test/Unit/Controller/Account/ConfirmTest.php b/app/code/Magento/Customer/Test/Unit/Controller/Account/ConfirmTest.php
index 01fc465d4ae84..28f897adf9176 100644
--- a/app/code/Magento/Customer/Test/Unit/Controller/Account/ConfirmTest.php
+++ b/app/code/Magento/Customer/Test/Unit/Controller/Account/ConfirmTest.php
@@ -203,10 +203,9 @@ public function testNoCustomerIdInRequest($customerId, $key)
->with($this->equalTo('key'), false)
->will($this->returnValue($key));
- $exception = new \Exception('Bad request.');
$this->messageManagerMock->expects($this->once())
- ->method('addException')
- ->with($this->equalTo($exception), $this->equalTo('There was an error confirming the account'));
+ ->method('addErrorMessage')
+ ->with(__('Bad request.'));
$testUrl = 'http://example.com';
$this->urlMock->expects($this->once())
@@ -255,10 +254,12 @@ public function testSuccessMessage($customerId, $key, $vatValidationEnabled, $ad
$this->requestMock->expects($this->any())
->method('getParam')
- ->willReturnMap([
- ['id', false, $customerId],
- ['key', false, $key],
- ]);
+ ->willReturnMap(
+ [
+ ['id', false, $customerId],
+ ['key', false, $key],
+ ]
+ );
$this->customerRepositoryMock->expects($this->any())
->method('getById')
@@ -372,11 +373,13 @@ public function testSuccessRedirect(
$this->requestMock->expects($this->any())
->method('getParam')
- ->willReturnMap([
- ['id', false, $customerId],
- ['key', false, $key],
- ['back_url', false, $backUrl],
- ]);
+ ->willReturnMap(
+ [
+ ['id', false, $customerId],
+ ['key', false, $key],
+ ['back_url', false, $backUrl],
+ ]
+ );
$this->customerRepositoryMock->expects($this->any())
->method('getById')
diff --git a/app/code/Magento/Customer/Test/Unit/Controller/Adminhtml/Index/MassAssignGroupTest.php b/app/code/Magento/Customer/Test/Unit/Controller/Adminhtml/Index/MassAssignGroupTest.php
index 10144bdc318c1..cb5ff88ab704a 100644
--- a/app/code/Magento/Customer/Test/Unit/Controller/Adminhtml/Index/MassAssignGroupTest.php
+++ b/app/code/Magento/Customer/Test/Unit/Controller/Adminhtml/Index/MassAssignGroupTest.php
@@ -170,7 +170,7 @@ public function testExecute()
->willReturnMap([[10, $customerMock], [11, $customerMock], [12, $customerMock]]);
$this->messageManagerMock->expects($this->once())
- ->method('addSuccess')
+ ->method('addSuccessMessage')
->with(__('A total of %1 record(s) were updated.', count($customersIds)));
$this->resultRedirectMock->expects($this->any())
diff --git a/app/code/Magento/Customer/Test/Unit/Controller/Adminhtml/Index/MassDeleteTest.php b/app/code/Magento/Customer/Test/Unit/Controller/Adminhtml/Index/MassDeleteTest.php
index 190ff2c06618f..1f39e6306b996 100644
--- a/app/code/Magento/Customer/Test/Unit/Controller/Adminhtml/Index/MassDeleteTest.php
+++ b/app/code/Magento/Customer/Test/Unit/Controller/Adminhtml/Index/MassDeleteTest.php
@@ -155,7 +155,7 @@ public function testExecute()
->willReturnMap([[10, true], [11, true], [12, true]]);
$this->messageManagerMock->expects($this->once())
- ->method('addSuccess')
+ ->method('addSuccessMessage')
->with(__('A total of %1 record(s) were deleted.', count($customersIds)));
$this->resultRedirectMock->expects($this->any())
diff --git a/app/code/Magento/Customer/Test/Unit/Controller/Adminhtml/Index/MassSubscribeTest.php b/app/code/Magento/Customer/Test/Unit/Controller/Adminhtml/Index/MassSubscribeTest.php
index daf9c64fe7b7b..90bff0b61bcbf 100644
--- a/app/code/Magento/Customer/Test/Unit/Controller/Adminhtml/Index/MassSubscribeTest.php
+++ b/app/code/Magento/Customer/Test/Unit/Controller/Adminhtml/Index/MassSubscribeTest.php
@@ -171,7 +171,7 @@ public function testExecute()
->willReturnMap([[10, true], [11, true], [12, true]]);
$this->messageManagerMock->expects($this->once())
- ->method('addSuccess')
+ ->method('addSuccessMessage')
->with(__('A total of %1 record(s) were updated.', count($customersIds)));
$this->resultRedirectMock->expects($this->any())
diff --git a/app/code/Magento/Customer/Test/Unit/Controller/Adminhtml/Index/MassUnsubscribeTest.php b/app/code/Magento/Customer/Test/Unit/Controller/Adminhtml/Index/MassUnsubscribeTest.php
index 05624661a2de4..1bffa836f5034 100644
--- a/app/code/Magento/Customer/Test/Unit/Controller/Adminhtml/Index/MassUnsubscribeTest.php
+++ b/app/code/Magento/Customer/Test/Unit/Controller/Adminhtml/Index/MassUnsubscribeTest.php
@@ -171,7 +171,7 @@ public function testExecute()
->willReturnMap([[10, true], [11, true], [12, true]]);
$this->messageManagerMock->expects($this->once())
- ->method('addSuccess')
+ ->method('addSuccessMessage')
->with(__('A total of %1 record(s) were updated.', count($customersIds)));
$this->resultRedirectMock->expects($this->any())
diff --git a/app/code/Magento/Customer/Test/Unit/Controller/Adminhtml/Index/ResetPasswordTest.php b/app/code/Magento/Customer/Test/Unit/Controller/Adminhtml/Index/ResetPasswordTest.php
index 66e5b57eaa424..67ac60e6b9057 100644
--- a/app/code/Magento/Customer/Test/Unit/Controller/Adminhtml/Index/ResetPasswordTest.php
+++ b/app/code/Magento/Customer/Test/Unit/Controller/Adminhtml/Index/ResetPasswordTest.php
@@ -141,7 +141,7 @@ protected function setUp()
$this->messageManager = $this->getMockBuilder(
\Magento\Framework\Message\Manager::class
)->disableOriginalConstructor()->setMethods(
- ['addSuccess', 'addMessage', 'addException', 'addErrorMessage']
+ ['addSuccessMessage', 'addMessage', 'addExceptionMessage', 'addErrorMessage']
)->getMock();
$this->resultRedirectFactoryMock = $this->getMockBuilder(
@@ -442,7 +442,7 @@ public function testResetPasswordActionException()
$this->messageManager->expects(
$this->once()
)->method(
- 'addException'
+ 'addExceptionMessage'
)->with(
$this->equalTo($exception),
$this->equalTo('Something went wrong while resetting customer password.')
@@ -502,7 +502,7 @@ public function testResetPasswordActionSendEmail()
$this->messageManager->expects(
$this->once()
)->method(
- 'addSuccess'
+ 'addSuccessMessage'
)->with(
$this->equalTo('The customer will receive an email with a link to reset password.')
);
diff --git a/app/code/Magento/Customer/Test/Unit/Controller/Adminhtml/Index/SaveTest.php b/app/code/Magento/Customer/Test/Unit/Controller/Adminhtml/Index/SaveTest.php
index 57f384d32d980..9724ac13dde8c 100644
--- a/app/code/Magento/Customer/Test/Unit/Controller/Adminhtml/Index/SaveTest.php
+++ b/app/code/Magento/Customer/Test/Unit/Controller/Adminhtml/Index/SaveTest.php
@@ -338,10 +338,12 @@ public function testExecuteWithExistentCustomer()
$this->requestMock->expects($this->atLeastOnce())
->method('getPostValue')
- ->willReturnMap([
- [null, null, $postValue],
- [CustomerMetadataInterface::ENTITY_TYPE_CUSTOMER, null, $postValue['customer']],
- ]);
+ ->willReturnMap(
+ [
+ [null, null, $postValue],
+ [CustomerMetadataInterface::ENTITY_TYPE_CUSTOMER, null, $postValue['customer']],
+ ]
+ );
$this->requestMock->expects($this->atLeastOnce())
->method('getPost')
->willReturnMap(
@@ -475,7 +477,7 @@ public function testExecuteWithExistentCustomer()
->with(RegistryConstants::CURRENT_CUSTOMER_ID, $customerId);
$this->messageManagerMock->expects($this->once())
- ->method('addSuccess')
+ ->method('addSuccessMessage')
->with(__('You saved the customer.'))
->willReturnSelf();
@@ -542,10 +544,12 @@ public function testExecuteWithNewCustomer()
$this->requestMock->expects($this->any())
->method('getPostValue')
- ->willReturnMap([
- [null, null, $postValue],
- [CustomerMetadataInterface::ENTITY_TYPE_CUSTOMER, null, $postValue['customer']],
- ]);
+ ->willReturnMap(
+ [
+ [null, null, $postValue],
+ [CustomerMetadataInterface::ENTITY_TYPE_CUSTOMER, null, $postValue['customer']],
+ ]
+ );
$this->requestMock->expects($this->atLeastOnce())
->method('getPost')
->willReturnMap(
@@ -662,7 +666,7 @@ public function testExecuteWithNewCustomer()
->with(RegistryConstants::CURRENT_CUSTOMER_ID, $customerId);
$this->messageManagerMock->expects($this->once())
- ->method('addSuccess')
+ ->method('addSuccessMessage')
->with(__('You saved the customer.'))
->willReturnSelf();
@@ -723,10 +727,12 @@ public function testExecuteWithNewCustomerAndValidationException()
$this->requestMock->expects($this->any())
->method('getPostValue')
- ->willReturnMap([
- [null, null, $postValue],
- [CustomerMetadataInterface::ENTITY_TYPE_CUSTOMER, null, $postValue['customer']],
- ]);
+ ->willReturnMap(
+ [
+ [null, null, $postValue],
+ [CustomerMetadataInterface::ENTITY_TYPE_CUSTOMER, null, $postValue['customer']],
+ ]
+ );
$this->requestMock->expects($this->atLeastOnce())
->method('getPost')
->willReturnMap(
@@ -804,7 +810,7 @@ public function testExecuteWithNewCustomerAndValidationException()
->method('register');
$this->messageManagerMock->expects($this->never())
- ->method('addSuccess');
+ ->method('addSuccessMessage');
$this->messageManagerMock->expects($this->once())
->method('addMessage')
@@ -812,10 +818,12 @@ public function testExecuteWithNewCustomerAndValidationException()
$this->sessionMock->expects($this->once())
->method('setCustomerFormData')
- ->with([
- 'customer' => $extractedData,
- 'subscription' => $subscription,
- ]);
+ ->with(
+ [
+ 'customer' => $extractedData,
+ 'subscription' => $subscription,
+ ]
+ );
/** @var Redirect|\PHPUnit_Framework_MockObject_MockObject $redirectMock */
$redirectMock = $this->getMockBuilder(\Magento\Framework\Controller\Result\Redirect::class)
@@ -870,10 +878,12 @@ public function testExecuteWithNewCustomerAndLocalizedException()
$this->requestMock->expects($this->any())
->method('getPostValue')
- ->willReturnMap([
- [null, null, $postValue],
- [CustomerMetadataInterface::ENTITY_TYPE_CUSTOMER, null, $postValue['customer']],
- ]);
+ ->willReturnMap(
+ [
+ [null, null, $postValue],
+ [CustomerMetadataInterface::ENTITY_TYPE_CUSTOMER, null, $postValue['customer']],
+ ]
+ );
$this->requestMock->expects($this->atLeastOnce())
->method('getPost')
->willReturnMap(
@@ -951,7 +961,7 @@ public function testExecuteWithNewCustomerAndLocalizedException()
->method('register');
$this->messageManagerMock->expects($this->never())
- ->method('addSuccess');
+ ->method('addSuccessMessage');
$this->messageManagerMock->expects($this->once())
->method('addMessage')
@@ -959,10 +969,12 @@ public function testExecuteWithNewCustomerAndLocalizedException()
$this->sessionMock->expects($this->once())
->method('setCustomerFormData')
- ->with([
- 'customer' => $extractedData,
- 'subscription' => $subscription,
- ]);
+ ->with(
+ [
+ 'customer' => $extractedData,
+ 'subscription' => $subscription,
+ ]
+ );
/** @var Redirect|\PHPUnit_Framework_MockObject_MockObject $redirectMock */
$redirectMock = $this->getMockBuilder(\Magento\Framework\Controller\Result\Redirect::class)
@@ -1017,10 +1029,12 @@ public function testExecuteWithNewCustomerAndException()
$this->requestMock->expects($this->any())
->method('getPostValue')
- ->willReturnMap([
- [null, null, $postValue],
- [CustomerMetadataInterface::ENTITY_TYPE_CUSTOMER, null, $postValue['customer']],
- ]);
+ ->willReturnMap(
+ [
+ [null, null, $postValue],
+ [CustomerMetadataInterface::ENTITY_TYPE_CUSTOMER, null, $postValue['customer']],
+ ]
+ );
$this->requestMock->expects($this->atLeastOnce())
->method('getPost')
->willReturnMap(
@@ -1099,18 +1113,20 @@ public function testExecuteWithNewCustomerAndException()
->method('register');
$this->messageManagerMock->expects($this->never())
- ->method('addSuccess');
+ ->method('addSuccessMessage');
$this->messageManagerMock->expects($this->once())
- ->method('addException')
+ ->method('addExceptionMessage')
->with($exception, __('Something went wrong while saving the customer.'));
$this->sessionMock->expects($this->once())
->method('setCustomerFormData')
- ->with([
- 'customer' => $extractedData,
- 'subscription' => $subscription,
- ]);
+ ->with(
+ [
+ 'customer' => $extractedData,
+ 'subscription' => $subscription,
+ ]
+ );
/** @var Redirect|\PHPUnit_Framework_MockObject_MockObject $redirectMock */
$redirectMock = $this->getMockBuilder(\Magento\Framework\Controller\Result\Redirect::class)
diff --git a/app/code/Magento/Customer/Test/Unit/Controller/Adminhtml/Locks/UnlockTest.php b/app/code/Magento/Customer/Test/Unit/Controller/Adminhtml/Locks/UnlockTest.php
index c92d4ed7812ba..55b4092af7141 100644
--- a/app/code/Magento/Customer/Test/Unit/Controller/Adminhtml/Locks/UnlockTest.php
+++ b/app/code/Magento/Customer/Test/Unit/Controller/Adminhtml/Locks/UnlockTest.php
@@ -118,7 +118,7 @@ public function testExecute()
->with($this->equalTo('customer_id'))
->will($this->returnValue($customerId));
$this->authenticationMock->expects($this->once())->method('unlock')->with($customerId);
- $this->messageManagerMock->expects($this->once())->method('addSuccess');
+ $this->messageManagerMock->expects($this->once())->method('addSuccessMessage');
$this->redirectMock->expects($this->once())
->method('setPath')
->with($this->equalTo('customer/index/edit'))
@@ -141,7 +141,7 @@ public function testExecuteWithException()
->method('unlock')
->with($customerId)
->willThrowException(new \Exception($phrase));
- $this->messageManagerMock->expects($this->once())->method('addError');
+ $this->messageManagerMock->expects($this->once())->method('addErrorMessage');
$this->controller->execute();
}
}
diff --git a/app/code/Magento/Customer/Test/Unit/Model/AccountManagementTest.php b/app/code/Magento/Customer/Test/Unit/Model/AccountManagementTest.php
index 5eda0c52c1db2..3c38cd0f7b4e2 100644
--- a/app/code/Magento/Customer/Test/Unit/Model/AccountManagementTest.php
+++ b/app/code/Magento/Customer/Test/Unit/Model/AccountManagementTest.php
@@ -816,14 +816,18 @@ public function testCreateAccountWithPasswordInputException(
if ($testNumber == 1) {
$this->expectException(\Magento\Framework\Exception\InputException::class);
- $this->expectExceptionMessage('The password needs at least ' . $minPasswordLength . ' characters. '
- . 'Create a new password and try again.');
+ $this->expectExceptionMessage(
+ 'The password needs at least ' . $minPasswordLength . ' characters. '
+ . 'Create a new password and try again.'
+ );
}
if ($testNumber == 2) {
$this->expectException(\Magento\Framework\Exception\InputException::class);
- $this->expectExceptionMessage('Minimum of different classes of characters in password is ' .
- $minCharacterSetsNum . '. Classes of characters: Lower Case, Upper Case, Digits, Special Characters.');
+ $this->expectExceptionMessage(
+ 'Minimum of different classes of characters in password is ' .
+ $minCharacterSetsNum . '. Classes of characters: Lower Case, Upper Case, Digits, Special Characters.'
+ );
}
$customer = $this->getMockBuilder(Customer::class)->disableOriginalConstructor()->getMock();
@@ -1269,7 +1273,7 @@ public function testInitiatePasswordResetEmailReminder()
$storeId = 1;
- $hash = hash('sha256', microtime() . random_int(PHP_INT_MIN, PHP_INT_MAX));
+ $hash = hash("sha256", uniqid(microtime() . random_int(0, PHP_INT_MAX), true));
$this->emailNotificationMock->expects($this->once())
->method('passwordReminder')
@@ -1293,7 +1297,7 @@ public function testInitiatePasswordResetEmailReset()
$templateIdentifier = 'Template Identifier';
$sender = 'Sender';
- $hash = hash('sha256', microtime() . random_int(PHP_INT_MIN, PHP_INT_MAX));
+ $hash = hash("sha256", uniqid(microtime() . random_int(0, PHP_INT_MAX), true));
$this->emailNotificationMock->expects($this->once())
->method('passwordResetConfirmation')
@@ -1317,7 +1321,7 @@ public function testInitiatePasswordResetNoTemplate()
$templateIdentifier = 'Template Identifier';
$sender = 'Sender';
- $hash = hash('sha256', microtime() . random_int(PHP_INT_MIN, PHP_INT_MAX));
+ $hash = hash("sha256", uniqid(microtime() . random_int(0, PHP_INT_MAX), true));
$this->prepareInitiatePasswordReset($email, $templateIdentifier, $sender, $storeId, $customerId, $hash);
diff --git a/app/code/Magento/Customer/Test/Unit/Model/Customer/DataProviderTest.php b/app/code/Magento/Customer/Test/Unit/Model/Customer/DataProviderTest.php
index 3cbcbdf80b2a1..8ad7363a1c310 100644
--- a/app/code/Magento/Customer/Test/Unit/Model/Customer/DataProviderTest.php
+++ b/app/code/Magento/Customer/Test/Unit/Model/Customer/DataProviderTest.php
@@ -3,23 +3,33 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
+
namespace Magento\Customer\Test\Unit\Model\Customer;
use Magento\Customer\Api\CustomerMetadataInterface;
+use Magento\Customer\Model\Address;
use Magento\Customer\Model\Config\Share;
+use Magento\Customer\Model\Customer;
+use Magento\Customer\Model\Customer\DataProvider as CustomerDataProvider;
+use Magento\Customer\Model\FileProcessor;
+use Magento\Customer\Model\FileUploaderDataResolver;
use Magento\Customer\Model\ResourceModel\Address\Attribute\Source\CountryWithWebsites;
+use Magento\Customer\Model\ResourceModel\Customer\Collection;
use Magento\Customer\Model\ResourceModel\Customer\CollectionFactory;
use Magento\Eav\Model\Config;
use Magento\Eav\Model\Entity\Attribute\AbstractAttribute;
+use Magento\Eav\Model\Entity\Attribute\Source\AbstractSource;
use Magento\Eav\Model\Entity\Type;
+use Magento\Framework\ObjectManagerInterface;
+use Magento\Framework\Session\SessionManagerInterface;
use Magento\Framework\TestFramework\Unit\Helper\ObjectManager;
+use Magento\Framework\View\Element\UiComponent\ContextInterface;
use Magento\Ui\Component\Form\Field;
use Magento\Ui\DataProvider\EavValidationRules;
+use PHPUnit_Framework_MockObject_MockObject as MockObject;
/**
- * Class DataProviderTest
- *
- * Test for class \Magento\Customer\Model\Customer\DataProvider
+ * Unit tests for \Magento\Customer\Model\Customer\DataProvider class.
*
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
*/
@@ -29,69 +39,64 @@ class DataProviderTest extends \PHPUnit\Framework\TestCase
const OPTIONS_RESULT = 'test-options';
/**
- * @var Config|\PHPUnit_Framework_MockObject_MockObject
+ * @var Config|MockObject
*/
protected $eavConfigMock;
/**
- * @var CollectionFactory|\PHPUnit_Framework_MockObject_MockObject
+ * @var CollectionFactory|MockObject
*/
protected $customerCollectionFactoryMock;
/**
- * @var EavValidationRules|\PHPUnit_Framework_MockObject_MockObject
+ * @var EavValidationRules|MockObject
*/
protected $eavValidationRulesMock;
/**
- * @var \Magento\Framework\Session\SessionManagerInterface|\PHPUnit_Framework_MockObject_MockObject
+ * @var SessionManagerInterface|MockObject
*/
protected $sessionMock;
/**
- * @var \Magento\Customer\Model\FileProcessor|\PHPUnit_Framework_MockObject_MockObject
+ * @var FileProcessor|MockObject
*/
protected $fileProcessor;
/**
- * @var \Magento\Customer\Model\FileUploaderDataResolver|\PHPUnit_Framework_MockObject_MockObject
+ * @var FileUploaderDataResolver|MockObject
*/
private $fileUploaderDataResolver;
/**
- * Set up
- *
- * @return void
+ * @inheritdoc
*/
protected function setUp()
{
- $this->eavConfigMock = $this->getMockBuilder(\Magento\Eav\Model\Config::class)
+ $this->eavConfigMock = $this->getMockBuilder(Config::class)
->disableOriginalConstructor()
->getMock();
- $this->customerCollectionFactoryMock = $this->createPartialMock(
- \Magento\Customer\Model\ResourceModel\Customer\CollectionFactory::class,
- ['create']
- );
+ $this->customerCollectionFactoryMock = $this->createPartialMock(CollectionFactory::class, ['create']);
$this->eavValidationRulesMock = $this
- ->getMockBuilder(\Magento\Ui\DataProvider\EavValidationRules::class)
+ ->getMockBuilder(EavValidationRules::class)
->disableOriginalConstructor()
->getMock();
$this->sessionMock = $this
- ->getMockBuilder(\Magento\Framework\Session\SessionManagerInterface::class)
+ ->getMockBuilder(SessionManagerInterface::class)
->setMethods(['getCustomerFormData', 'unsCustomerFormData'])
->getMockForAbstractClass();
- $this->fileProcessor = $this->getMockBuilder(\Magento\Customer\Model\FileProcessor::class)
+ $this->fileProcessor = $this->getMockBuilder(FileProcessor::class)
->disableOriginalConstructor()
->getMock();
- $this->fileUploaderDataResolver = $this->getMockBuilder(\Magento\Customer\Model\FileUploaderDataResolver::class)
+ $this->fileUploaderDataResolver = $this->getMockBuilder(FileUploaderDataResolver::class)
->disableOriginalConstructor()
->setMethods(['overrideFileUploaderMetadata', 'overrideFileUploaderData'])
->getMock();
}
/**
- * Run test getAttributesMeta method
+ * Run test getAttributesMeta method.
*
* @param array $expected
* @return void
@@ -101,9 +106,9 @@ protected function setUp()
public function testGetAttributesMetaWithOptions(array $expected)
{
$helper = new ObjectManager($this);
- /** @var \Magento\Customer\Model\Customer\DataProvider $dataProvider */
+ /** @var CustomerDataProvider $dataProvider */
$dataProvider = $helper->getObject(
- \Magento\Customer\Model\Customer\DataProvider::class,
+ CustomerDataProvider::class,
[
'name' => 'test-name',
'primaryFieldName' => 'primary-field-name',
@@ -111,7 +116,7 @@ public function testGetAttributesMetaWithOptions(array $expected)
'eavValidationRules' => $this->eavValidationRulesMock,
'customerCollectionFactory' => $this->getCustomerCollectionFactoryMock(),
'eavConfig' => $this->getEavConfigMock(),
- 'fileUploaderDataResolver' => $this->fileUploaderDataResolver
+ 'fileUploaderDataResolver' => $this->fileUploaderDataResolver,
]
);
@@ -121,7 +126,7 @@ public function testGetAttributesMetaWithOptions(array $expected)
}
/**
- * Data provider for testGetAttributesMeta
+ * Data provider for testGetAttributesMeta.
*
* @return array
* @SuppressWarnings(PHPMD.ExcessiveMethodLength)
@@ -238,8 +243,8 @@ public function getAttributesMetaDataProvider()
'componentType' => Field::NAME,
'filterBy' => [
'target' => '${ $.provider }:data.customer.website_id',
- 'field' => 'website_ids'
- ]
+ 'field' => 'website_ids',
+ ],
],
],
],
@@ -252,11 +257,11 @@ public function getAttributesMetaDataProvider()
}
/**
- * @return CollectionFactory|\PHPUnit_Framework_MockObject_MockObject
+ * @return CollectionFactory|MockObject
*/
protected function getCustomerCollectionFactoryMock()
{
- $collectionMock = $this->getMockBuilder(\Magento\Customer\Model\ResourceModel\Customer\Collection::class)
+ $collectionMock = $this->getMockBuilder(Collection::class)
->disableOriginalConstructor()
->getMock();
@@ -272,7 +277,7 @@ protected function getCustomerCollectionFactoryMock()
}
/**
- * @return Config|\PHPUnit_Framework_MockObject_MockObject
+ * @return Config|MockObject
*/
protected function getEavConfigMock($customerAttributes = [])
{
@@ -289,11 +294,11 @@ protected function getEavConfigMock($customerAttributes = [])
}
/**
- * @return Type|\PHPUnit_Framework_MockObject_MockObject
+ * @return Type|MockObject
*/
protected function getTypeCustomerMock($customerAttributes = [])
{
- $typeCustomerMock = $this->getMockBuilder(\Magento\Eav\Model\Entity\Type::class)
+ $typeCustomerMock = $this->getMockBuilder(Type::class)
->disableOriginalConstructor()
->getMock();
$attributesCollection = !empty($customerAttributes) ? $customerAttributes : $this->getAttributeMock();
@@ -314,11 +319,11 @@ protected function getTypeCustomerMock($customerAttributes = [])
}
/**
- * @return Type|\PHPUnit_Framework_MockObject_MockObject
+ * @return Type|MockObject
*/
protected function getTypeAddressMock()
{
- $typeAddressMock = $this->getMockBuilder(\Magento\Eav\Model\Entity\Type::class)
+ $typeAddressMock = $this->getMockBuilder(Type::class)
->disableOriginalConstructor()
->getMock();
@@ -330,13 +335,13 @@ protected function getTypeAddressMock()
}
/**
- * @param \PHPUnit_Framework_MockObject_MockObject $attributeMock
- * @param \PHPUnit_Framework_MockObject_MockObject $attributeBooleanMock
+ * @param MockObject $attributeMock
+ * @param MockObject $attributeBooleanMock
* @param array $options
*/
private function injectVisibilityProps(
- \PHPUnit_Framework_MockObject_MockObject $attributeMock,
- \PHPUnit_Framework_MockObject_MockObject $attributeBooleanMock,
+ MockObject $attributeMock,
+ MockObject $attributeBooleanMock,
array $options = []
) {
if (isset($options[self::ATTRIBUTE_CODE]['visible'])) {
@@ -377,11 +382,11 @@ private function injectVisibilityProps(
}
/**
- * @return AbstractAttribute[]|\PHPUnit_Framework_MockObject_MockObject[]
+ * @return AbstractAttribute[]|MockObject[]
*/
protected function getAttributeMock($type = 'customer', $options = [])
{
- $attributeMock = $this->getMockBuilder(\Magento\Eav\Model\Entity\Attribute\AbstractAttribute::class)
+ $attributeMock = $this->getMockBuilder(AbstractAttribute::class)
->setMethods(
[
'getAttributeCode',
@@ -397,7 +402,7 @@ protected function getAttributeMock($type = 'customer', $options = [])
)
->disableOriginalConstructor()
->getMockForAbstractClass();
- $sourceMock = $this->getMockBuilder(\Magento\Eav\Model\Entity\Attribute\Source\AbstractSource::class)
+ $sourceMock = $this->getMockBuilder(AbstractSource::class)
->disableOriginalConstructor()
->getMockForAbstractClass();
@@ -425,7 +430,7 @@ protected function getAttributeMock($type = 'customer', $options = [])
->method('getSource')
->willReturn($sourceMock);
- $attributeBooleanMock = $this->getMockBuilder(\Magento\Eav\Model\Entity\Attribute\AbstractAttribute::class)
+ $attributeBooleanMock = $this->getMockBuilder(AbstractAttribute::class)
->setMethods(
[
'getAttributeCode',
@@ -463,10 +468,12 @@ protected function getAttributeMock($type = 'customer', $options = [])
$this->eavValidationRulesMock->expects($this->any())
->method('build')
- ->willReturnMap([
- [$attributeMock, $this->logicalNot($this->isEmpty()), []],
- [$attributeBooleanMock, $this->logicalNot($this->isEmpty()), []],
- ]);
+ ->willReturnMap(
+ [
+ [$attributeMock, $this->logicalNot($this->isEmpty()), []],
+ [$attributeBooleanMock, $this->logicalNot($this->isEmpty()), []],
+ ]
+ );
$mocks = [$attributeMock, $attributeBooleanMock];
$this->injectVisibilityProps($attributeMock, $attributeBooleanMock, $options);
if ($type == "address") {
@@ -476,7 +483,7 @@ protected function getAttributeMock($type = 'customer', $options = [])
}
/**
- * Callback for ::getDataUsingMethod
+ * Callback for ::getDataUsingMethod.
*
* @return \Closure
*/
@@ -488,7 +495,7 @@ private function attributeGetUsingMethodCallback()
}
/**
- * @return \PHPUnit_Framework_MockObject_MockObject
+ * @return MockObject
*/
private function getCountryAttrMock()
{
@@ -501,15 +508,17 @@ private function getCountryAttrMock()
$shareMock = $this->getMockBuilder(Share::class)
->disableOriginalConstructor()
->getMock();
- $objectManagerMock = $this->createMock(\Magento\Framework\ObjectManagerInterface::class);
+ $objectManagerMock = $this->createMock(ObjectManagerInterface::class);
$objectManagerMock->expects($this->any())
->method('get')
- ->willReturnMap([
- [CountryWithWebsites::class, $countryByWebsiteMock],
- [Share::class, $shareMock],
- ]);
+ ->willReturnMap(
+ [
+ [CountryWithWebsites::class, $countryByWebsiteMock],
+ [Share::class, $shareMock],
+ ]
+ );
\Magento\Framework\App\ObjectManager::setInstance($objectManagerMock);
- $countryAttrMock = $this->getMockBuilder(\Magento\Eav\Model\Entity\Attribute\AbstractAttribute::class)
+ $countryAttrMock = $this->getMockBuilder(AbstractAttribute::class)
->setMethods(['getAttributeCode', 'getDataUsingMethod', 'usesSource', 'getSource', 'getLabel'])
->disableOriginalConstructor()
->getMockForAbstractClass();
@@ -558,13 +567,13 @@ public function testGetData()
'street' => "street\nstreet",
];
- $customer = $this->getMockBuilder(\Magento\Customer\Model\Customer::class)
+ $customer = $this->getMockBuilder(Customer::class)
->disableOriginalConstructor()
->getMock();
- $address = $this->getMockBuilder(\Magento\Customer\Model\Address::class)
+ $address = $this->getMockBuilder(Address::class)
->disableOriginalConstructor()
->getMock();
- $collectionMock = $this->getMockBuilder(\Magento\Customer\Model\ResourceModel\Customer\Collection::class)
+ $collectionMock = $this->getMockBuilder(Collection::class)
->disableOriginalConstructor()
->getMock();
@@ -598,7 +607,7 @@ public function testGetData()
$helper = new ObjectManager($this);
$dataProvider = $helper->getObject(
- \Magento\Customer\Model\Customer\DataProvider::class,
+ CustomerDataProvider::class,
[
'name' => 'test-name',
'primaryFieldName' => 'primary-field-name',
@@ -606,7 +615,7 @@ public function testGetData()
'eavValidationRules' => $this->eavValidationRulesMock,
'customerCollectionFactory' => $this->customerCollectionFactoryMock,
'eavConfig' => $this->getEavConfigMock(),
- 'fileUploaderDataResolver' => $this->fileUploaderDataResolver
+ 'fileUploaderDataResolver' => $this->fileUploaderDataResolver,
]
);
@@ -635,9 +644,9 @@ public function testGetData()
'street' => "street\nstreet",
'default_billing' => 2,
'default_shipping' => 2,
- ]
- ]
- ]
+ ],
+ ],
+ ],
],
$dataProvider->getData()
);
@@ -671,13 +680,13 @@ public function testGetDataWithCustomerFormData()
],
];
- $customer = $this->getMockBuilder(\Magento\Customer\Model\Customer::class)
+ $customer = $this->getMockBuilder(Customer::class)
->disableOriginalConstructor()
->getMock();
- $address = $this->getMockBuilder(\Magento\Customer\Model\Address::class)
+ $address = $this->getMockBuilder(Address::class)
->disableOriginalConstructor()
->getMock();
- $collectionMock = $this->getMockBuilder(\Magento\Customer\Model\ResourceModel\Customer\Collection::class)
+ $collectionMock = $this->getMockBuilder(Collection::class)
->disableOriginalConstructor()
->getMock();
@@ -694,11 +703,13 @@ public function testGetDataWithCustomerFormData()
->willReturn([$customer]);
$customer->expects($this->once())
->method('getData')
- ->willReturn([
- 'email' => 'test@test.ua',
- 'default_billing' => 2,
- 'default_shipping' => 2,
- ]);
+ ->willReturn(
+ [
+ 'email' => 'test@test.ua',
+ 'default_billing' => 2,
+ 'default_shipping' => 2,
+ ]
+ );
$customer->expects($this->once())
->method('getId')
->willReturn($customerId);
@@ -714,14 +725,16 @@ public function testGetDataWithCustomerFormData()
->willReturnSelf();
$address->expects($this->once())
->method('getData')
- ->willReturn([
- 'firstname' => 'firstname',
- 'lastname' => 'lastname',
- 'street' => "street\nstreet",
- ]);
+ ->willReturn(
+ [
+ 'firstname' => 'firstname',
+ 'lastname' => 'lastname',
+ 'street' => "street\nstreet",
+ ]
+ );
$helper = new ObjectManager($this);
$dataProvider = $helper->getObject(
- \Magento\Customer\Model\Customer\DataProvider::class,
+ CustomerDataProvider::class,
[
'name' => 'test-name',
'primaryFieldName' => 'primary-field-name',
@@ -729,7 +742,7 @@ public function testGetDataWithCustomerFormData()
'eavValidationRules' => $this->eavValidationRulesMock,
'customerCollectionFactory' => $this->customerCollectionFactoryMock,
'eavConfig' => $this->getEavConfigMock(),
- 'fileUploaderDataResolver' => $this->fileUploaderDataResolver
+ 'fileUploaderDataResolver' => $this->fileUploaderDataResolver,
]
);
@@ -758,22 +771,24 @@ public function testGetDataWithCustomAttributeImage()
$filename = '/filename.ext1';
- $customerMock = $this->getMockBuilder(\Magento\Customer\Model\Customer::class)
+ $customerMock = $this->getMockBuilder(Customer::class)
->disableOriginalConstructor()
->getMock();
$customerMock->expects($this->once())
->method('getData')
- ->willReturn([
- 'email' => $customerEmail,
- 'img1' => $filename,
- ]);
+ ->willReturn(
+ [
+ 'email' => $customerEmail,
+ 'img1' => $filename,
+ ]
+ );
$customerMock->expects($this->once())
->method('getAddresses')
->willReturn([]);
$customerMock->expects($this->once())
->method('getId')
->willReturn($customerId);
- $collectionMock = $this->getMockBuilder(\Magento\Customer\Model\ResourceModel\Customer\Collection::class)
+ $collectionMock = $this->getMockBuilder(Collection::class)
->disableOriginalConstructor()
->getMock();
$collectionMock->expects($this->once())
@@ -790,7 +805,7 @@ public function testGetDataWithCustomAttributeImage()
$objectManager = new ObjectManager($this);
$dataProvider = $objectManager->getObject(
- \Magento\Customer\Model\Customer\DataProvider::class,
+ CustomerDataProvider::class,
[
'name' => 'test-name',
'primaryFieldName' => 'primary-field-name',
@@ -798,7 +813,7 @@ public function testGetDataWithCustomAttributeImage()
'eavValidationRules' => $this->eavValidationRulesMock,
'customerCollectionFactory' => $this->customerCollectionFactoryMock,
'eavConfig' => $this->getEavConfigMock(),
- 'fileUploaderDataResolver' => $this->fileUploaderDataResolver
+ 'fileUploaderDataResolver' => $this->fileUploaderDataResolver,
]
);
@@ -830,7 +845,7 @@ public function testGetAttributesMetaWithCustomAttributeImage()
$attributeCode = 'img1';
- $collectionMock = $this->getMockBuilder(\Magento\Customer\Model\ResourceModel\Customer\Collection::class)
+ $collectionMock = $this->getMockBuilder(Collection::class)
->disableOriginalConstructor()
->getMock();
$collectionMock->expects($this->once())
@@ -841,12 +856,14 @@ public function testGetAttributesMetaWithCustomAttributeImage()
->method('create')
->willReturn($collectionMock);
- $attributeMock = $this->getMockBuilder(\Magento\Eav\Model\Entity\Attribute\AbstractAttribute::class)
- ->setMethods([
- 'getAttributeCode',
- 'getFrontendInput',
- 'getDataUsingMethod',
- ])
+ $attributeMock = $this->getMockBuilder(AbstractAttribute::class)
+ ->setMethods(
+ [
+ 'getAttributeCode',
+ 'getFrontendInput',
+ 'getDataUsingMethod',
+ ]
+ )
->disableOriginalConstructor()
->getMockForAbstractClass();
$attributeMock->expects($this->any())
@@ -863,7 +880,7 @@ function ($origName) {
}
);
- $typeCustomerMock = $this->getMockBuilder(\Magento\Eav\Model\Entity\Type::class)
+ $typeCustomerMock = $this->getMockBuilder(Type::class)
->disableOriginalConstructor()
->getMock();
$typeCustomerMock->expects($this->once())
@@ -873,7 +890,7 @@ function ($origName) {
->method('getEntityTypeCode')
->willReturn(CustomerMetadataInterface::ENTITY_TYPE_CUSTOMER);
- $typeAddressMock = $this->getMockBuilder(\Magento\Eav\Model\Entity\Type::class)
+ $typeAddressMock = $this->getMockBuilder(Type::class)
->disableOriginalConstructor()
->getMock();
$typeAddressMock->expects($this->once())
@@ -891,32 +908,37 @@ function ($origName) {
$this->eavValidationRulesMock->expects($this->once())
->method('build')
- ->with($attributeMock, [
- 'dataType' => 'frontend_input',
- 'formElement' => 'frontend_input',
- 'visible' => 'is_visible',
- 'required' => 'is_required',
- 'sortOrder' => 'sort_order',
- 'notice' => 'note',
- 'default' => 'default_value',
- 'size' => 'multiline_count',
- 'label' => __('frontend_label'),
- ])
- ->willReturn([
- 'max_file_size' => $maxFileSize,
- 'file_extensions' => 'ext1, eXt2 ', // Added spaces and upper-cases
- ]);
+ ->with(
+ $attributeMock,
+ [
+ 'dataType' => 'frontend_input',
+ 'formElement' => 'frontend_input',
+ 'visible' => 'is_visible',
+ 'required' => 'is_required',
+ 'sortOrder' => 'sort_order',
+ 'notice' => 'note',
+ 'default' => 'default_value',
+ 'size' => 'multiline_count',
+ 'label' => __('frontend_label'),
+ ]
+ )
+ ->willReturn(
+ [
+ 'max_file_size' => $maxFileSize,
+ 'file_extensions' => 'ext1, eXt2 ', // Added spaces and upper-cases
+ ]
+ );
$objectManager = new ObjectManager($this);
$dataProvider = $objectManager->getObject(
- \Magento\Customer\Model\Customer\DataProvider::class,
+ CustomerDataProvider::class,
[
'name' => 'test-name',
'primaryFieldName' => 'primary-field-name',
'requestFieldName' => 'request-field-name',
'eavValidationRules' => $this->eavValidationRulesMock,
'customerCollectionFactory' => $this->customerCollectionFactoryMock,
- 'eavConfig' => $this->eavConfigMock
+ 'eavConfig' => $this->eavConfigMock,
]
);
@@ -931,6 +953,7 @@ function ($origName) {
'arguments' => [
'data' => [
'config' => [
+ 'dataType' => 'frontend_input',
'formElement' => 'fileUploader',
'componentType' => 'fileUploader',
'maxFileSize' => $maxFileSize,
@@ -973,13 +996,13 @@ public function testGetDataWithVisibleAttributes()
'visible' => true,
'is_used_in_forms' => ['customer_account_edit'],
'user_defined' => true,
- 'specific_code_prefix' => "_1"
+ 'specific_code_prefix' => "_1",
],
'test-code-boolean' => [
'visible' => true,
'is_used_in_forms' => ['customer_account_create'],
'user_defined' => true,
- 'specific_code_prefix' => "_1"
+ 'specific_code_prefix' => "_1",
]
]
);
@@ -990,21 +1013,21 @@ public function testGetDataWithVisibleAttributes()
'visible' => true,
'is_used_in_forms' => ['customer_account_create'],
'user_defined' => false,
- 'specific_code_prefix' => "_2"
+ 'specific_code_prefix' => "_2",
],
'test-code-boolean' => [
'visible' => true,
'is_used_in_forms' => ['customer_account_create'],
'user_defined' => true,
- 'specific_code_prefix' => "_2"
+ 'specific_code_prefix' => "_2",
]
]
);
$helper = new ObjectManager($this);
- /** @var \Magento\Customer\Model\Customer\DataProvider $dataProvider */
+ /** @var DataProvider $dataProvider */
$dataProvider = $helper->getObject(
- \Magento\Customer\Model\Customer\DataProvider::class,
+ CustomerDataProvider::class,
[
'name' => 'test-name',
'primaryFieldName' => 'primary-field-name',
@@ -1012,7 +1035,7 @@ public function testGetDataWithVisibleAttributes()
'eavValidationRules' => $this->eavValidationRulesMock,
'customerCollectionFactory' => $this->getCustomerCollectionFactoryMock(),
'eavConfig' => $this->getEavConfigMock(array_merge($firstAttributesBundle, $secondAttributesBundle)),
- 'fileUploaderDataResolver' => $this->fileUploaderDataResolver
+ 'fileUploaderDataResolver' => $this->fileUploaderDataResolver,
]
);
@@ -1033,13 +1056,13 @@ public function testGetDataWithVisibleAttributesWithAccountEdit()
'visible' => true,
'is_used_in_forms' => ['customer_account_edit'],
'user_defined' => true,
- 'specific_code_prefix' => "_1"
+ 'specific_code_prefix' => "_1",
],
'test-code-boolean' => [
'visible' => true,
'is_used_in_forms' => ['customer_account_create'],
'user_defined' => true,
- 'specific_code_prefix' => "_1"
+ 'specific_code_prefix' => "_1",
]
]
);
@@ -1050,28 +1073,28 @@ public function testGetDataWithVisibleAttributesWithAccountEdit()
'visible' => true,
'is_used_in_forms' => ['customer_account_create'],
'user_defined' => false,
- 'specific_code_prefix' => "_2"
+ 'specific_code_prefix' => "_2",
],
'test-code-boolean' => [
'visible' => true,
'is_used_in_forms' => ['customer_account_create'],
'user_defined' => true,
- 'specific_code_prefix' => "_2"
+ 'specific_code_prefix' => "_2",
]
]
);
$helper = new ObjectManager($this);
- $context = $this->getMockBuilder(\Magento\Framework\View\Element\UiComponent\ContextInterface::class)
+ $context = $this->getMockBuilder(ContextInterface::class)
->setMethods(['getRequestParam'])
->getMockForAbstractClass();
$context->expects($this->any())
->method('getRequestParam')
->with('request-field-name')
->willReturn(1);
- /** @var \Magento\Customer\Model\Customer\DataProvider $dataProvider */
+ /** @var DataProvider $dataProvider */
$dataProvider = $helper->getObject(
- \Magento\Customer\Model\Customer\DataProvider::class,
+ CustomerDataProvider::class,
[
'name' => 'test-name',
'primaryFieldName' => 'primary-field-name',
@@ -1080,7 +1103,7 @@ public function testGetDataWithVisibleAttributesWithAccountEdit()
'customerCollectionFactory' => $this->getCustomerCollectionFactoryMock(),
'context' => $context,
'eavConfig' => $this->getEavConfigMock(array_merge($firstAttributesBundle, $secondAttributesBundle)),
- 'fileUploaderDataResolver' => $this->fileUploaderDataResolver
+ 'fileUploaderDataResolver' => $this->fileUploaderDataResolver,
]
);
@@ -1091,7 +1114,7 @@ public function testGetDataWithVisibleAttributesWithAccountEdit()
}
/**
- * Retrieve all customer variations of attributes with all variations of visibility
+ * Retrieve all customer variations of attributes with all variations of visibility.
*
* @return array
*/
@@ -1186,7 +1209,7 @@ private function getCustomerAttributeExpectations()
}
/**
- * Retrieve all variations of attributes with all variations of visibility
+ * Retrieve all variations of attributes with all variations of visibility.
*
* @return array
*/
@@ -1257,12 +1280,12 @@ private function getExpectationForVisibleAttributes()
'componentType' => Field::NAME,
'filterBy' => [
'target' => '${ $.provider }:data.customer.website_id',
- 'field' => 'website_ids'
- ]
+ 'field' => 'website_ids',
+ ],
],
],
],
- ]
+ ],
],
],
];
diff --git a/app/code/Magento/Customer/Test/Unit/Model/CustomerExtractorTest.php b/app/code/Magento/Customer/Test/Unit/Model/CustomerExtractorTest.php
index 351c3435c73fc..6fd5c76da81c0 100644
--- a/app/code/Magento/Customer/Test/Unit/Model/CustomerExtractorTest.php
+++ b/app/code/Magento/Customer/Test/Unit/Model/CustomerExtractorTest.php
@@ -3,10 +3,14 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
+
namespace Magento\Customer\Test\Unit\Model;
use Magento\Customer\Model\CustomerExtractor;
+/**
+ * Unit test CustomerExtractorTest
+ */
class CustomerExtractorTest extends \PHPUnit\Framework\TestCase
{
/** @var CustomerExtractor */
@@ -137,19 +141,9 @@ public function testExtract()
$this->storeManager->expects($this->once())
->method('getStore')
->willReturn($this->store);
- $this->store->expects($this->exactly(2))
- ->method('getId')
- ->willReturn(1);
- $this->customerGroupManagement->expects($this->once())
- ->method('getDefaultGroup')
- ->with(1)
- ->willReturn($this->customerGroup);
- $this->customerGroup->expects($this->once())
+ $this->store->expects($this->once())
->method('getId')
->willReturn(1);
- $this->customerData->expects($this->once())
- ->method('setGroupId')
- ->with(1);
$this->store->expects($this->once())
->method('getWebsiteId')
->willReturn(1);
diff --git a/app/code/Magento/Customer/Test/Unit/Model/ResourceModel/GroupTest.php b/app/code/Magento/Customer/Test/Unit/Model/ResourceModel/GroupTest.php
index b245702ce07f9..069ddc63d74d7 100644
--- a/app/code/Magento/Customer/Test/Unit/Model/ResourceModel/GroupTest.php
+++ b/app/code/Magento/Customer/Test/Unit/Model/ResourceModel/GroupTest.php
@@ -127,7 +127,7 @@ public function testSaveWithReservedId()
]
)
->getMockForAbstractClass();
- $dbAdapter->expects($this->any())->method('describeTable')->willReturn([]);
+ $dbAdapter->expects($this->any())->method('describeTable')->willReturn(['customer_group_id' => []]);
$dbAdapter->expects($this->any())->method('update')->willReturnSelf();
$dbAdapter->expects($this->once())->method('lastInsertId')->willReturn($expectedId);
$selectMock = $this->getMockBuilder(\Magento\Framework\DB\Select::class)
diff --git a/app/code/Magento/Customer/Test/Unit/Model/SessionTest.php b/app/code/Magento/Customer/Test/Unit/Model/SessionTest.php
index 7efc61af800d3..8565790990df1 100644
--- a/app/code/Magento/Customer/Test/Unit/Model/SessionTest.php
+++ b/app/code/Magento/Customer/Test/Unit/Model/SessionTest.php
@@ -66,7 +66,7 @@ protected function setUp()
$this->urlFactoryMock = $this->createMock(\Magento\Framework\UrlFactory::class);
$this->customerFactoryMock = $this->getMockBuilder(\Magento\Customer\Model\CustomerFactory::class)
->disableOriginalConstructor()
- ->setMethods(['create'])
+ ->setMethods(['create', 'save'])
->getMock();
$this->customerRepositoryMock = $this->createMock(\Magento\Customer\Api\CustomerRepositoryInterface::class);
$helper = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this);
@@ -192,15 +192,12 @@ protected function prepareLoginDataMock($customerId)
$customerMock = $this->createPartialMock(
\Magento\Customer\Model\Customer::class,
- ['getId', 'isConfirmationRequired', 'getConfirmation', 'updateData', 'getGroupId']
+ ['getId', 'getConfirmation', 'updateData', 'getGroupId']
);
- $customerMock->expects($this->once())
+ $customerMock->expects($this->exactly(3))
->method('getId')
->will($this->returnValue($customerId));
$customerMock->expects($this->once())
- ->method('isConfirmationRequired')
- ->will($this->returnValue(true));
- $customerMock->expects($this->never())
->method('getConfirmation')
->will($this->returnValue($customerId));
diff --git a/app/code/Magento/Customer/Test/Unit/Observer/UpgradeQuoteCustomerEmailObserverTest.php b/app/code/Magento/Customer/Test/Unit/Observer/UpgradeQuoteCustomerEmailObserverTest.php
new file mode 100644
index 0000000000000..f41c0ed9f0fb4
--- /dev/null
+++ b/app/code/Magento/Customer/Test/Unit/Observer/UpgradeQuoteCustomerEmailObserverTest.php
@@ -0,0 +1,107 @@
+observerMock = $this->getMockBuilder(\Magento\Framework\Event\Observer::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $this->eventMock = $this->getMockBuilder(\Magento\Framework\Event::class)
+ ->disableOriginalConstructor()
+ ->setMethods(['getCustomerDataObject', 'getOrigCustomerDataObject'])
+ ->getMock();
+
+ $this->observerMock->expects($this->any())->method('getEvent')->will($this->returnValue($this->eventMock));
+
+ $this->quoteRepositoryMock = $this
+ ->getMockBuilder(\Magento\Quote\Api\CartRepositoryInterface::class)
+ ->getMockForAbstractClass();
+ $this->model = new UpgradeQuoteCustomerEmailObserver($this->quoteRepositoryMock);
+ }
+
+ /**
+ * Unit test for verifying quote customers email upgrade observer
+ */
+ public function testUpgradeQuoteCustomerEmail()
+ {
+ $email = "test@test.com";
+ $origEmail = "origtest@test.com";
+
+ $customer = $this->getMockBuilder(\Magento\Customer\Api\Data\CustomerInterface::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+ $customerOrig = $this->getMockBuilder(\Magento\Customer\Api\Data\CustomerInterface::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $quoteMock = $this->getMockBuilder(\Magento\Quote\Model\Quote::class)
+ ->setMethods(['setCustomerEmail'])
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $this->eventMock->expects($this->any())
+ ->method('getCustomerDataObject')
+ ->will($this->returnValue($customer));
+ $this->eventMock->expects($this->any())
+ ->method('getOrigCustomerDataObject')
+ ->will($this->returnValue($customerOrig));
+
+ $customerOrig->expects($this->any())
+ ->method('getEmail')
+ ->willReturn($this->returnValue($origEmail));
+
+ $customer->expects($this->any())
+ ->method('getEmail')
+ ->willReturn($this->returnValue($email));
+
+ $this->quoteRepositoryMock->expects($this->once())
+ ->method('getForCustomer')
+ ->willReturn($quoteMock);
+
+ $quoteMock->expects($this->once())
+ ->method('setCustomerEmail');
+
+ $this->quoteRepositoryMock->expects($this->once())
+ ->method('save')
+ ->with($quoteMock);
+
+ $this->model->execute($this->observerMock);
+ }
+}
diff --git a/app/code/Magento/Customer/Test/Unit/Ui/Component/ColumnFactoryTest.php b/app/code/Magento/Customer/Test/Unit/Ui/Component/ColumnFactoryTest.php
index 131b1ee94cc14..d917cc4908ac8 100644
--- a/app/code/Magento/Customer/Test/Unit/Ui/Component/ColumnFactoryTest.php
+++ b/app/code/Magento/Customer/Test/Unit/Ui/Component/ColumnFactoryTest.php
@@ -7,6 +7,9 @@
use Magento\Customer\Ui\Component\ColumnFactory;
+/**
+ * Test ColumnFactory Class
+ */
class ColumnFactoryTest extends \PHPUnit\Framework\TestCase
{
/** @var \Magento\Customer\Api\Data\OptionInterface|\PHPUnit_Framework_MockObject_MockObject */
@@ -90,6 +93,7 @@ public function testCreate()
]
],
'component' => 'Magento_Ui/js/grid/columns/column',
+ '__disableTmpl' => 'true'
],
],
'context' => $this->context,
diff --git a/app/code/Magento/Customer/Test/Unit/Ui/Component/FilterFactoryTest.php b/app/code/Magento/Customer/Test/Unit/Ui/Component/FilterFactoryTest.php
index 7fbf9d2a2a10a..f3c0a56262622 100644
--- a/app/code/Magento/Customer/Test/Unit/Ui/Component/FilterFactoryTest.php
+++ b/app/code/Magento/Customer/Test/Unit/Ui/Component/FilterFactoryTest.php
@@ -7,6 +7,9 @@
use Magento\Customer\Ui\Component\FilterFactory;
+/**
+ * Test FilterFactory Class
+ */
class FilterFactoryTest extends \PHPUnit\Framework\TestCase
{
/** @var \Magento\Customer\Api\Data\OptionInterface|\PHPUnit_Framework_MockObject_MockObject */
@@ -69,6 +72,7 @@ public function testCreate()
'config' => [
'dataScope' => $filterName,
'label' => __('Label'),
+ '__disableTmpl' => 'true',
'options' => [['value' => 'Value', 'label' => 'Label']],
'caption' => __('Select...'),
],
diff --git a/app/code/Magento/Customer/Test/Unit/Ui/Component/Listing/AttributeRepositoryTest.php b/app/code/Magento/Customer/Test/Unit/Ui/Component/Listing/AttributeRepositoryTest.php
index 0662235c0d5ac..c12dec865cde8 100644
--- a/app/code/Magento/Customer/Test/Unit/Ui/Component/Listing/AttributeRepositoryTest.php
+++ b/app/code/Magento/Customer/Test/Unit/Ui/Component/Listing/AttributeRepositoryTest.php
@@ -7,6 +7,9 @@
use Magento\Customer\Ui\Component\Listing\AttributeRepository;
+/**
+ * Test AttributeRepository Class
+ */
class AttributeRepositoryTest extends \PHPUnit\Framework\TestCase
{
/** @var \Magento\Customer\Api\CustomerMetadataManagementInterface|\PHPUnit_Framework_MockObject_MockObject */
@@ -144,7 +147,8 @@ public function testGetList()
'options' => [
[
'label' => 'Label',
- 'value' => 'Value'
+ 'value' => 'Value',
+ '__disableTmpl' => true
]
],
'is_used_in_grid' => true,
diff --git a/app/code/Magento/Customer/Ui/Component/ColumnFactory.php b/app/code/Magento/Customer/Ui/Component/ColumnFactory.php
index 60bf3ea26b78c..cb66dc3db7c77 100644
--- a/app/code/Magento/Customer/Ui/Component/ColumnFactory.php
+++ b/app/code/Magento/Customer/Ui/Component/ColumnFactory.php
@@ -9,6 +9,9 @@
use Magento\Customer\Ui\Component\Listing\Column\InlineEditUpdater;
use Magento\Customer\Api\CustomerMetadataInterface;
+/**
+ * Class ColumnFactory. Responsible for the column object generation
+ */
class ColumnFactory
{
/**
@@ -55,6 +58,8 @@ public function __construct(
}
/**
+ * Creates column object for grid ui component
+ *
* @param array $attributeData
* @param string $columnName
* @param \Magento\Framework\View\Element\UiComponent\ContextInterface $context
@@ -63,13 +68,19 @@ public function __construct(
*/
public function create(array $attributeData, $columnName, $context, array $config = [])
{
- $config = array_merge([
- 'label' => __($attributeData[AttributeMetadata::FRONTEND_LABEL]),
- 'dataType' => $this->getDataType($attributeData[AttributeMetadata::FRONTEND_INPUT]),
- 'align' => 'left',
- 'visible' => (bool)$attributeData[AttributeMetadata::IS_VISIBLE_IN_GRID],
- 'component' => $this->getJsComponent($this->getDataType($attributeData[AttributeMetadata::FRONTEND_INPUT])),
- ], $config);
+ $config = array_merge(
+ [
+ 'label' => __($attributeData[AttributeMetadata::FRONTEND_LABEL]),
+ 'dataType' => $this->getDataType($attributeData[AttributeMetadata::FRONTEND_INPUT]),
+ 'align' => 'left',
+ 'visible' => (bool)$attributeData[AttributeMetadata::IS_VISIBLE_IN_GRID],
+ 'component' => $this->getJsComponent(
+ $this->getDataType($attributeData[AttributeMetadata::FRONTEND_INPUT])
+ ),
+ '__disableTmpl' => 'true'
+ ],
+ $config
+ );
if ($attributeData[AttributeMetadata::FRONTEND_INPUT] == 'date') {
$config['dateFormat'] = 'MMM d, y';
$config['timezone'] = false;
@@ -101,6 +112,8 @@ public function create(array $attributeData, $columnName, $context, array $confi
}
/**
+ * Returns component map
+ *
* @param string $dataType
* @return string
*/
@@ -110,6 +123,8 @@ protected function getJsComponent($dataType)
}
/**
+ * Returns component map depends on data type
+ *
* @param string $frontendType
* @return string
*/
diff --git a/app/code/Magento/Customer/Ui/Component/FilterFactory.php b/app/code/Magento/Customer/Ui/Component/FilterFactory.php
index 9d8fcdb9715ca..9bf07b877cc07 100644
--- a/app/code/Magento/Customer/Ui/Component/FilterFactory.php
+++ b/app/code/Magento/Customer/Ui/Component/FilterFactory.php
@@ -7,6 +7,9 @@
use Magento\Customer\Api\Data\AttributeMetadataInterface as AttributeMetadata;
+/**
+ * Class FilterFactory. Responsible for generation filter object
+ */
class FilterFactory
{
/**
@@ -34,6 +37,8 @@ public function __construct(\Magento\Framework\View\Element\UiComponentFactory $
}
/**
+ * Creates filter object
+ *
* @param array $attributeData
* @param \Magento\Framework\View\Element\UiComponent\ContextInterface $context
* @return \Magento\Ui\Component\Listing\Columns\ColumnInterface
@@ -43,6 +48,7 @@ public function create(array $attributeData, $context)
$config = [
'dataScope' => $attributeData[AttributeMetadata::ATTRIBUTE_CODE],
'label' => __($attributeData[AttributeMetadata::FRONTEND_LABEL]),
+ '__disableTmpl' => 'true'
];
if ($attributeData[AttributeMetadata::OPTIONS]) {
$config['options'] = $attributeData[AttributeMetadata::OPTIONS];
@@ -63,6 +69,8 @@ public function create(array $attributeData, $context)
}
/**
+ * Returns filter type
+ *
* @param string $frontendInput
* @return string
*/
diff --git a/app/code/Magento/Customer/Ui/Component/Listing/AttributeRepository.php b/app/code/Magento/Customer/Ui/Component/Listing/AttributeRepository.php
index d0af1ec21467f..eb8359de93f32 100644
--- a/app/code/Magento/Customer/Ui/Component/Listing/AttributeRepository.php
+++ b/app/code/Magento/Customer/Ui/Component/Listing/AttributeRepository.php
@@ -13,6 +13,9 @@
use Magento\Customer\Api\MetadataManagementInterface;
use Magento\Customer\Model\Indexer\Attribute\Filter;
+/**
+ * Class AttributeRepository
+ */
class AttributeRepository
{
const BILLING_ADDRESS_PREFIX = 'billing_';
@@ -69,6 +72,8 @@ public function __construct(
}
/**
+ * Returns attribute list for current customer
+ *
* @return array
*/
public function getList()
@@ -93,6 +98,8 @@ public function getList()
}
/**
+ * Returns attribute list for given entity type code
+ *
* @param AttributeMetadataInterface[] $metadata
* @param string $entityTypeCode
* @param MetadataManagementInterface $management
@@ -136,12 +143,18 @@ protected function getOptionArray(array $options)
{
/** @var \Magento\Customer\Api\Data\OptionInterface $option */
foreach ($options as &$option) {
- $option = ['label' => (string)$option->getLabel(), 'value' => $option->getValue()];
+ $option = [
+ 'label' => (string)$option->getLabel(),
+ 'value' => $option->getValue(),
+ '__disableTmpl' => true
+ ];
}
return $options;
}
/**
+ * Return customer group's metadata by given group code
+ *
* @param string $code
* @return []
*/
diff --git a/app/code/Magento/Customer/Ui/Component/Listing/Column/Group/Options.php b/app/code/Magento/Customer/Ui/Component/Listing/Column/Group/Options.php
index f521a95e1e616..61cb06cf77e0d 100644
--- a/app/code/Magento/Customer/Ui/Component/Listing/Column/Group/Options.php
+++ b/app/code/Magento/Customer/Ui/Component/Listing/Column/Group/Options.php
@@ -43,6 +43,14 @@ public function toOptionArray()
if ($this->options === null) {
$this->options = $this->collectionFactory->create()->toOptionArray();
}
+
+ array_walk(
+ $this->options,
+ function (&$item) {
+ $item['__disableTmpl'] = true;
+ }
+ );
+
return $this->options;
}
}
diff --git a/app/code/Magento/Customer/Ui/Component/Listing/Column/GroupActions.php b/app/code/Magento/Customer/Ui/Component/Listing/Column/GroupActions.php
index 00c5f99fab46c..6870bd1136d10 100644
--- a/app/code/Magento/Customer/Ui/Component/Listing/Column/GroupActions.php
+++ b/app/code/Magento/Customer/Ui/Component/Listing/Column/GroupActions.php
@@ -96,8 +96,11 @@ public function prepareDataSource(array $dataSource)
),
'label' => __('Delete'),
'confirm' => [
- 'title' => __('Delete %1', $title),
- 'message' => __('Are you sure you want to delete a %1 record?', $title)
+ 'title' => __('Delete %1', $this->escaper->escapeJs($title)),
+ 'message' => __(
+ 'Are you sure you want to delete a %1 record?',
+ $this->escaper->escapeJs($title)
+ )
],
'post' => true
];
diff --git a/app/code/Magento/Customer/Ui/Component/MassAction/Group/Options.php b/app/code/Magento/Customer/Ui/Component/MassAction/Group/Options.php
index 146adacac9553..e5739317bca8d 100644
--- a/app/code/Magento/Customer/Ui/Component/MassAction/Group/Options.php
+++ b/app/code/Magento/Customer/Ui/Component/MassAction/Group/Options.php
@@ -88,6 +88,7 @@ public function jsonSerialize()
$this->options[$optionCode['value']] = [
'type' => 'customer_group_' . $optionCode['value'],
'label' => __($optionCode['label']),
+ '__disableTmpl' => true
];
if ($this->urlPath && $this->paramName) {
diff --git a/app/code/Magento/Customer/composer.json b/app/code/Magento/Customer/composer.json
index b9a7aca73fe34..3e98818a67b74 100644
--- a/app/code/Magento/Customer/composer.json
+++ b/app/code/Magento/Customer/composer.json
@@ -5,7 +5,7 @@
"sort-packages": true
},
"require": {
- "php": "~7.1.3||~7.2.0",
+ "php": "~7.1.3||~7.2.0||~7.3.0",
"magento/framework": "*",
"magento/module-authorization": "*",
"magento/module-backend": "*",
diff --git a/app/code/Magento/Customer/etc/acl.xml b/app/code/Magento/Customer/etc/acl.xml
index 1d45aa6445db8..1583c190d5c06 100644
--- a/app/code/Magento/Customer/etc/acl.xml
+++ b/app/code/Magento/Customer/etc/acl.xml
@@ -26,7 +26,7 @@
-
+
diff --git a/app/code/Magento/Customer/etc/adminhtml/system.xml b/app/code/Magento/Customer/etc/adminhtml/system.xml
index 86e5852d67aeb..2bd1041214801 100644
--- a/app/code/Magento/Customer/etc/adminhtml/system.xml
+++ b/app/code/Magento/Customer/etc/adminhtml/system.xml
@@ -280,6 +280,7 @@
HTML
+ Only 'b', 'br', 'em', 'i', 'li', 'ol', 'p', 'strong', 'sub', 'sup', 'ul' tags are allowed
PDF
@@ -289,10 +290,12 @@
Online Customers Options
Online Minutes Interval
+ validate-number validate-greater-than-zero
Leave empty for default (15 minutes).
Customer Data Lifetime
+ validate-number validate-greater-than-zero
Please specify value in minutes.
diff --git a/app/code/Magento/Customer/etc/events.xml b/app/code/Magento/Customer/etc/events.xml
index d841d8faa9c38..2a724498a0359 100644
--- a/app/code/Magento/Customer/etc/events.xml
+++ b/app/code/Magento/Customer/etc/events.xml
@@ -15,4 +15,7 @@
+
+
+
diff --git a/app/code/Magento/Customer/etc/webapi.xml b/app/code/Magento/Customer/etc/webapi.xml
index c536e26bcc82a..38717619406aa 100644
--- a/app/code/Magento/Customer/etc/webapi.xml
+++ b/app/code/Magento/Customer/etc/webapi.xml
@@ -134,7 +134,7 @@
-
+
@@ -143,7 +143,7 @@
%customer_id%
-
+
@@ -244,7 +244,7 @@
-
+
@@ -259,7 +259,7 @@
-
+
diff --git a/app/code/Magento/Customer/i18n/en_US.csv b/app/code/Magento/Customer/i18n/en_US.csv
index e1c68f3d81e9d..3495feb925cb3 100644
--- a/app/code/Magento/Customer/i18n/en_US.csv
+++ b/app/code/Magento/Customer/i18n/en_US.csv
@@ -500,6 +500,7 @@ Strong,Strong
"Address Templates","Address Templates"
"Online Customers Options","Online Customers Options"
"Online Minutes Interval","Online Minutes Interval"
+"Only 'b', 'br', 'em', 'i', 'li', 'ol', 'p', 'strong', 'sub', 'sup', 'ul' tags are allowed","Only 'b', 'br', 'em', 'i', 'li', 'ol', 'p', 'strong', 'sub', 'sup', 'ul' tags are allowed"
"Leave empty for default (15 minutes).","Leave empty for default (15 minutes)."
"Customer Notification","Customer Notification"
"Customer Grid","Customer Grid"
diff --git a/app/code/Magento/Customer/view/adminhtml/layout/customer_index_edit.xml b/app/code/Magento/Customer/view/adminhtml/layout/customer_index_edit.xml
index 23b5718870229..e982d8bb0960e 100644
--- a/app/code/Magento/Customer/view/adminhtml/layout/customer_index_edit.xml
+++ b/app/code/Magento/Customer/view/adminhtml/layout/customer_index_edit.xml
@@ -15,5 +15,8 @@
+
+
+
diff --git a/app/code/Magento/Customer/view/adminhtml/templates/edit/js.phtml b/app/code/Magento/Customer/view/adminhtml/templates/edit/js.phtml
index 143b4be507af9..14aa3f4763e6c 100644
--- a/app/code/Magento/Customer/view/adminhtml/templates/edit/js.phtml
+++ b/app/code/Magento/Customer/view/adminhtml/templates/edit/js.phtml
@@ -3,7 +3,4 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
-
-// @codingStandardsIgnoreFile
-
?>
diff --git a/app/code/Magento/Customer/view/adminhtml/templates/sales/order/create/address/form/renderer/vat.phtml b/app/code/Magento/Customer/view/adminhtml/templates/sales/order/create/address/form/renderer/vat.phtml
index f55562682d9be..b792bc27f5b64 100644
--- a/app/code/Magento/Customer/view/adminhtml/templates/sales/order/create/address/form/renderer/vat.phtml
+++ b/app/code/Magento/Customer/view/adminhtml/templates/sales/order/create/address/form/renderer/vat.phtml
@@ -4,8 +4,6 @@
* See COPYING.txt for license details.
*/
-// @codingStandardsIgnoreFile
-
/** @var \Magento\Customer\Block\Adminhtml\Sales\Order\Address\Form\Renderer\Vat $block */
$_element = $block->getElement();
@@ -13,16 +11,16 @@ $_note = $_element->getNote();
$_class = $_element->getFieldsetHtmlClass();
$_validateButton = $block->getValidateButton();
?>
-getNoDisplay()): ?>
+getNoDisplay()) : ?>
- getType() == 'hidden'): ?>
+ getType() == 'hidden') : ?>
= $_element->getElementHtml() ?>
-
+
= $_element->getLabelHtml() ?>
= $_element->getElementHtml() ?>
-
-
" id="note_= $block->escapeHtmlAttr($_element->getId()) ?>">
+
+
escapeHtmlAttr($_class)}-note" : '' ?>" id="note_= $block->escapeHtmlAttr($_element->getId()) ?>">
= $block->escapeHtml($_note) ?>
diff --git a/app/code/Magento/Customer/view/adminhtml/templates/system/config/validatevat.phtml b/app/code/Magento/Customer/view/adminhtml/templates/system/config/validatevat.phtml
index 8eb3057d0a390..6525e7f29f36b 100644
--- a/app/code/Magento/Customer/view/adminhtml/templates/system/config/validatevat.phtml
+++ b/app/code/Magento/Customer/view/adminhtml/templates/system/config/validatevat.phtml
@@ -4,8 +4,6 @@
* See COPYING.txt for license details.
*/
-// @codingStandardsIgnoreFile
-
/** @var \Magento\Customer\Block\Adminhtml\System\Config\Validatevat $block */
?>
-
+
= $block->escapeHtml($block->getButtonLabel()) ?>
+
diff --git a/app/code/Magento/Customer/view/adminhtml/templates/tab/cart.phtml b/app/code/Magento/Customer/view/adminhtml/templates/tab/cart.phtml
index 76fa53c12548d..434e5606cd032 100644
--- a/app/code/Magento/Customer/view/adminhtml/templates/tab/cart.phtml
+++ b/app/code/Magento/Customer/view/adminhtml/templates/tab/cart.phtml
@@ -4,11 +4,9 @@
* See COPYING.txt for license details.
*/
-// @codingStandardsIgnoreFile
-
/* @var \Magento\Customer\Block\Adminhtml\Edit\Tab\Cart $block */
?>
-getCartHeader()): ?>
+getCartHeader()) : ?>
diff --git a/app/code/Magento/Customer/view/adminhtml/templates/tab/view/sales.phtml b/app/code/Magento/Customer/view/adminhtml/templates/tab/view/sales.phtml
index 12eae5cac9b1a..7b888c040463f 100644
--- a/app/code/Magento/Customer/view/adminhtml/templates/tab/view/sales.phtml
+++ b/app/code/Magento/Customer/view/adminhtml/templates/tab/view/sales.phtml
@@ -4,8 +4,6 @@
* See COPYING.txt for license details.
*/
-// @codingStandardsIgnoreFile
-
/** @var \Magento\Customer\Block\Adminhtml\Edit\Tab\View\Sales $block */
$singleStoreMode = $block->isSingleStoreMode();
@@ -19,7 +17,7 @@ $singleStoreMode = $block->isSingleStoreMode();
-
+
= $block->escapeHtml(__('Web Site')) ?>
= $block->escapeHtml(__('Store')) ?>
= $block->escapeHtml(__('Store View')) ?>
@@ -28,7 +26,7 @@ $singleStoreMode = $block->isSingleStoreMode();
= $block->escapeHtml(__('Average Sale')) ?>
-
+
= $block->escapeHtml(__('All Store Views')) ?>
@@ -37,40 +35,40 @@ $singleStoreMode = $block->isSingleStoreMode();
- getRows()): ?>
+ getRows()) : ?>
- getRows() as $_websiteId => $_groups): ?>
-
- $_stores): ?>
-
-
-
- getStoreId() == 0): ?>
- = $block->escapeHtml($_row->getStoreName()) ?>
-
- >
-
- = $block->escapeHtml($_row->getWebsiteName()) ?>
-
+ getRows() as $_websiteId => $_groups) : ?>
+
+ $_stores) : ?>
+
+
+
+ getStoreId() == 0) : ?>
+ = $block->escapeHtml($_row->getStoreName()) ?>
+
+ >
+
+ = $block->escapeHtml($_row->getWebsiteName()) ?>
+
-
- = $block->escapeHtml($_row->getGroupName()) ?>
-
+
+ = $block->escapeHtml($_row->getGroupName()) ?>
+
- = $block->escapeHtml($_row->getStoreName()) ?>
+ = $block->escapeHtml($_row->getStoreName()) ?>
-
-
+
+
- = $block->escapeHtml($block->formatCurrency($_row->getLifetime(), $_row->getWebsiteId())) ?>
- = $block->escapeHtml($block->formatCurrency($_row->getAvgsale(), $_row->getWebsiteId())) ?>
-
+ = $block->escapeHtml($block->formatCurrency($_row->getLifetime(), $_row->getWebsiteId())) ?>
+ = $block->escapeHtml($block->formatCurrency($_row->getAvgsale(), $_row->getWebsiteId())) ?>
+
-
+
diff --git a/app/code/Magento/Customer/view/adminhtml/ui_component/customer_address_form.xml b/app/code/Magento/Customer/view/adminhtml/ui_component/customer_address_form.xml
index 692cb2ecb964d..3af0172b3fca8 100644
--- a/app/code/Magento/Customer/view/adminhtml/ui_component/customer_address_form.xml
+++ b/app/code/Magento/Customer/view/adminhtml/ui_component/customer_address_form.xml
@@ -191,13 +191,6 @@
text
-
-
-
-
-
-
-
diff --git a/app/code/Magento/Customer/view/adminhtml/ui_component/customer_listing.xml b/app/code/Magento/Customer/view/adminhtml/ui_component/customer_listing.xml
index f845d407d401a..97ae9a9953eb6 100644
--- a/app/code/Magento/Customer/view/adminhtml/ui_component/customer_listing.xml
+++ b/app/code/Magento/Customer/view/adminhtml/ui_component/customer_listing.xml
@@ -157,18 +157,12 @@
text
-
- text
-
Phone
text
-
- text
-
ZIP
@@ -269,9 +263,6 @@
text
-
- text
-
City
false
@@ -279,9 +270,6 @@
text
-
- text
-
Fax
false
@@ -289,9 +277,6 @@
text
-
- text
-
VAT Number
false
@@ -299,9 +284,6 @@
text
-
- text
-
Company
false
@@ -309,9 +291,6 @@
text
-
- text
-
Billing Firstname
false
@@ -319,9 +298,6 @@
text
-
- text
-
Billing Lastname
false
diff --git a/app/code/Magento/Customer/view/adminhtml/web/js/grid/columns/actions.js b/app/code/Magento/Customer/view/adminhtml/web/js/grid/columns/actions.js
index 66ef89c9413c7..42f427cf8a094 100644
--- a/app/code/Magento/Customer/view/adminhtml/web/js/grid/columns/actions.js
+++ b/app/code/Magento/Customer/view/adminhtml/web/js/grid/columns/actions.js
@@ -20,6 +20,11 @@ define([
},
listens: {
action: 'onAction'
+ },
+ ignoreTmpls: {
+ fieldAction: true,
+ options: true,
+ action: true
}
},
diff --git a/app/code/Magento/Customer/view/frontend/templates/account/authentication-popup.phtml b/app/code/Magento/Customer/view/frontend/templates/account/authentication-popup.phtml
index ca7393f2129e0..0d4cf3c721d14 100644
--- a/app/code/Magento/Customer/view/frontend/templates/account/authentication-popup.phtml
+++ b/app/code/Magento/Customer/view/frontend/templates/account/authentication-popup.phtml
@@ -4,8 +4,6 @@
* See COPYING.txt for license details.
*/
-// @codingStandardsIgnoreFile
-
/** @var \Magento\Customer\Block\Account\AuthenticationPopup $block */
?>
- = /* @noEscape */ __('Alphanumeric, dash and underscore characters are recommended for filenames. Improper characters are replaced with \'_\'.') ?>
+ = $block->escapeHtml(__('Alphanumeric, dash and underscore characters are recommended for filenames. Improper characters are replaced with \'_\'.')) ?>
diff --git a/app/code/Magento/Downloadable/view/frontend/web/js/downloadable.js b/app/code/Magento/Downloadable/view/frontend/web/js/downloadable.js
index 64cca1efd8b03..09a5ad1afa9ec 100644
--- a/app/code/Magento/Downloadable/view/frontend/web/js/downloadable.js
+++ b/app/code/Magento/Downloadable/view/frontend/web/js/downloadable.js
@@ -7,7 +7,7 @@
*/
define([
'jquery',
- 'jquery/ui',
+ 'jquery-ui-modules/widget',
'Magento_Catalog/js/price-box'
], function ($) {
'use strict';
diff --git a/app/code/Magento/DownloadableGraphQl/Model/Cart/BuyRequest/DownloadableLinksDataProvider.php b/app/code/Magento/DownloadableGraphQl/Model/Cart/BuyRequest/DownloadableLinksDataProvider.php
new file mode 100644
index 0000000000000..18f883b61516d
--- /dev/null
+++ b/app/code/Magento/DownloadableGraphQl/Model/Cart/BuyRequest/DownloadableLinksDataProvider.php
@@ -0,0 +1,58 @@
+productRepository = $productRepository;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function execute(array $cartItemData): array
+ {
+ $linksData = [];
+
+ if (isset($cartItemData['data']) && isset($cartItemData['data']['sku'])) {
+ $sku = $cartItemData['data']['sku'];
+
+ try {
+ $product = $this->productRepository->get($sku);
+ } catch (NoSuchEntityException $e) {
+ throw new GraphQlNoSuchEntityException(__('Could not find specified product.'));
+ }
+
+ if ($product->getLinksPurchasedSeparately() && isset($cartItemData['downloadable_product_links'])) {
+ $downloadableLinks = $cartItemData['downloadable_product_links'];
+ $linksData = array_unique(array_column($downloadableLinks, 'link_id'));
+ }
+ }
+
+ return (count($linksData) > 0 ? ['links' => $linksData] : []);
+ }
+}
diff --git a/app/code/Magento/DownloadableGraphQl/Model/ConvertLinksToArray.php b/app/code/Magento/DownloadableGraphQl/Model/ConvertLinksToArray.php
new file mode 100644
index 0000000000000..5768a82fe3729
--- /dev/null
+++ b/app/code/Magento/DownloadableGraphQl/Model/ConvertLinksToArray.php
@@ -0,0 +1,55 @@
+urlBuilder = $urlBuilder;
+ }
+
+ /**
+ * Format links from collection as array
+ *
+ * @param LinkInterface[] $links
+ * @return array
+ */
+ public function execute(array $links): array
+ {
+ $data = [];
+ foreach ($links as $key => $link) {
+ $data[$key] = [
+ 'id' => $link->getId(),
+ 'sort_order' => $link->getSortOrder(),
+ 'title' => $link->getTitle(),
+ 'sample_url' => $this->urlBuilder->getUrl(
+ 'downloadable/download/linkSample',
+ ['link_id' => $link->getId()]
+ ),
+ 'price' => $link->getPrice(),
+ ];
+ }
+ return $data;
+ }
+}
diff --git a/app/code/Magento/DownloadableGraphQl/Model/ConvertSamplesToArray.php b/app/code/Magento/DownloadableGraphQl/Model/ConvertSamplesToArray.php
new file mode 100644
index 0000000000000..7313b2413c2b8
--- /dev/null
+++ b/app/code/Magento/DownloadableGraphQl/Model/ConvertSamplesToArray.php
@@ -0,0 +1,54 @@
+urlBuilder = $urlBuilder;
+ }
+
+ /**
+ * Format samples from collection as array
+ *
+ * @param SampleInterface[] $samples
+ * @return array
+ */
+ public function execute(array $samples): array
+ {
+ $data = [];
+ foreach ($samples as $key => $sample) {
+ $data[$key] = [
+ 'id' => $sample->getId(),
+ 'sort_order' => $sample->getSortOrder(),
+ 'title' => $sample->getTitle(),
+ 'sample_url' => $this->urlBuilder->getUrl(
+ 'downloadable/download/sample',
+ ['sample_id' => $sample->getId()]
+ ),
+ ];
+ }
+ return $data;
+ }
+}
diff --git a/app/code/Magento/DownloadableGraphQl/Model/GetDownloadableProductLinks.php b/app/code/Magento/DownloadableGraphQl/Model/GetDownloadableProductLinks.php
new file mode 100644
index 0000000000000..d2a821397da5f
--- /dev/null
+++ b/app/code/Magento/DownloadableGraphQl/Model/GetDownloadableProductLinks.php
@@ -0,0 +1,55 @@
+linkCollectionFactory = $linkCollectionFactory;
+ }
+
+ /**
+ * Returns downloadable product links
+ *
+ * @param Product $product
+ * @param array $selectedLinksIds
+ * @return LinkInterface[]
+ * @throws \Magento\Framework\Exception\LocalizedException
+ */
+ public function execute(Product $product, array $selectedLinksIds = []): array
+ {
+ /** @var Collection */
+ $links = $this->linkCollectionFactory->create();
+ $links->addTitleToResult($product->getStoreId())
+ ->addPriceToResult($product->getStore()->getWebsiteId())
+ ->addProductToFilter($product->getId());
+
+ if (count($selectedLinksIds) > 0) {
+ $links->addFieldToFilter('main_table.link_id', ['in' => $selectedLinksIds]);
+ }
+ return $links->getItems();
+ }
+}
diff --git a/app/code/Magento/DownloadableGraphQl/Model/GetDownloadableProductSamples.php b/app/code/Magento/DownloadableGraphQl/Model/GetDownloadableProductSamples.php
new file mode 100644
index 0000000000000..c542b7036345c
--- /dev/null
+++ b/app/code/Magento/DownloadableGraphQl/Model/GetDownloadableProductSamples.php
@@ -0,0 +1,46 @@
+sampleCollectionFactory = $sampleCollectionFactory;
+ }
+
+ /**
+ * Returns downloadable product samples
+ *
+ * @param Product $product
+ * @return SampleInterface[]
+ */
+ public function execute(Product $product): array
+ {
+ $samples = $this->sampleCollectionFactory->create()
+ ->addTitleToResult($product->getStoreId())
+ ->addProductToFilter($product->getId());
+ return $samples->getItems();
+ }
+}
diff --git a/app/code/Magento/DownloadableGraphQl/Model/Resolver/Product/DownloadableOptions.php b/app/code/Magento/DownloadableGraphQl/Model/Resolver/Product/DownloadableOptions.php
deleted file mode 100644
index a1e25663a9c3d..0000000000000
--- a/app/code/Magento/DownloadableGraphQl/Model/Resolver/Product/DownloadableOptions.php
+++ /dev/null
@@ -1,173 +0,0 @@
-enumLookup = $enumLookup;
- $this->downloadableHelper = $downloadableHelper;
- $this->sampleCollection = $sampleCollection;
- $this->linkCollection = $linkCollection;
- }
-
- /**
- * @inheritdoc
- *
- * Add downloadable options to configurable types
- *
- * @param \Magento\Framework\GraphQl\Config\Element\Field $field
- * @param ContextInterface $context
- * @param ResolveInfo $info
- * @param array|null $value
- * @param array|null $args
- * @throws \Exception
- * @return null|array
- */
- public function resolve(
- Field $field,
- $context,
- ResolveInfo $info,
- array $value = null,
- array $args = null
- ) {
- if (!isset($value['model'])) {
- throw new LocalizedException(__('"model" value should be specified'));
- }
-
- /** @var Product $product */
- $product = $value['model'];
-
- $data = null;
- if ($product->getTypeId() === Downloadable::TYPE_DOWNLOADABLE) {
- if ($field->getName() === 'downloadable_product_links') {
- $links = $this->linkCollection->addTitleToResult($product->getStoreId())
- ->addPriceToResult($product->getStore()->getWebsiteId())
- ->addProductToFilter($product->getId());
- $data = $this->formatLinks(
- $links
- );
- } elseif ($field->getName() === 'downloadable_product_samples') {
- $samples = $this->sampleCollection->addTitleToResult($product->getStoreId())
- ->addProductToFilter($product->getId());
- $data = $this->formatSamples(
- $samples
- );
- }
- }
-
- return $data;
- }
-
- /**
- * Format links from collection as array
- *
- * @param LinkCollection $links
- * @return array
- */
- private function formatLinks(LinkCollection $links) : array
- {
- $resultData = [];
- foreach ($links as $linkKey => $link) {
- /** @var \Magento\Downloadable\Model\Link $link */
- $resultData[$linkKey]['id'] = $link->getId();
- $resultData[$linkKey]['title'] = $link->getTitle();
- $resultData[$linkKey]['sort_order'] = $link->getSortOrder();
- $resultData[$linkKey]['is_shareable'] = $this->downloadableHelper->getIsShareable($link);
- $resultData[$linkKey]['price'] = $link->getPrice();
- $resultData[$linkKey]['number_of_downloads'] = $link->getNumberOfDownloads();
- $sampleType = $link->getSampleType();
- $linkType = $link->getLinkType();
-
- if ($linkType !== null) {
- $resultData[$linkKey]['link_type']
- = $this->enumLookup->getEnumValueFromField('DownloadableFileTypeEnum', $linkType);
- }
-
- if ($sampleType !== null) {
- $resultData[$linkKey]['sample_type']
- = $this->enumLookup->getEnumValueFromField('DownloadableFileTypeEnum', $sampleType);
- }
-
- $resultData[$linkKey]['sample_file'] = $link->getSampleFile();
- $resultData[$linkKey]['sample_url'] = $link->getSampleUrl();
- }
- return $resultData;
- }
-
- /**
- * Format links from collection as array
- *
- * @param Collection $samples
- * @return array
- */
- private function formatSamples(Collection $samples) : array
- {
- $resultData = [];
- foreach ($samples as $sampleKey => $sample) {
- /** @var \Magento\Downloadable\Model\Sample $sample */
- $resultData[$sampleKey]['id'] = $sample->getId();
- $resultData[$sampleKey]['title'] = $sample->getTitle();
- $resultData[$sampleKey]['sort_order'] = $sample->getSortOrder();
- $resultData[$sampleKey]['sample_type']
- = $this->enumLookup->getEnumValueFromField('DownloadableFileTypeEnum', $sample->getSampleType());
- $resultData[$sampleKey]['sample_file'] = $sample->getSampleFile();
- $resultData[$sampleKey]['sample_url'] = $sample->getSampleUrl();
- }
- return $resultData;
- }
-}
diff --git a/app/code/Magento/DownloadableGraphQl/Model/ResourceModel/GetPurchasedDownloadableProducts.php b/app/code/Magento/DownloadableGraphQl/Model/ResourceModel/GetPurchasedDownloadableProducts.php
index e8c29e90609f8..82f448ec450f6 100644
--- a/app/code/Magento/DownloadableGraphQl/Model/ResourceModel/GetPurchasedDownloadableProducts.php
+++ b/app/code/Magento/DownloadableGraphQl/Model/ResourceModel/GetPurchasedDownloadableProducts.php
@@ -11,8 +11,6 @@
use Magento\Downloadable\Model\Link\Purchased\Item;
/**
- * Class GetPurchasedDownloadableProducts
- *
* The model returns all purchased products for the specified customer
*/
class GetPurchasedDownloadableProducts
diff --git a/app/code/Magento/DownloadableGraphQl/Resolver/DownloadableCartItem/Links.php b/app/code/Magento/DownloadableGraphQl/Resolver/DownloadableCartItem/Links.php
new file mode 100644
index 0000000000000..366a69e5100d8
--- /dev/null
+++ b/app/code/Magento/DownloadableGraphQl/Resolver/DownloadableCartItem/Links.php
@@ -0,0 +1,73 @@
+getDownloadableProductLinks = $getDownloadableProductLinks;
+ $this->convertLinksToArray = $convertLinksToArray;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function resolve(
+ Field $field,
+ $context,
+ ResolveInfo $info,
+ array $value = null,
+ array $args = null
+ ) {
+ if (!isset($value['model'])) {
+ throw new LocalizedException(__('"model" value should be specified'));
+ }
+
+ /** @var QuoteItem $quoteItem */
+ $quoteItem = $value['model'];
+
+ /** @var Product $product */
+ $product = $quoteItem->getProduct();
+
+ $selectedLinksIds = $product->getLinksPurchasedSeparately()
+ ? explode(',', $quoteItem->getOptionByCode('downloadable_link_ids')->getValue()) : [];
+ $links = $this->getDownloadableProductLinks->execute($product, $selectedLinksIds);
+
+ $data = $this->convertLinksToArray->execute($links);
+ return $data;
+ }
+}
diff --git a/app/code/Magento/DownloadableGraphQl/Resolver/DownloadableCartItem/Samples.php b/app/code/Magento/DownloadableGraphQl/Resolver/DownloadableCartItem/Samples.php
new file mode 100644
index 0000000000000..983707033fe81
--- /dev/null
+++ b/app/code/Magento/DownloadableGraphQl/Resolver/DownloadableCartItem/Samples.php
@@ -0,0 +1,77 @@
+getDownloadableProductSamples = $getDownloadableProductSamples;
+ $this->convertSamplesToArray = $convertSamplesToArray;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function resolve(
+ Field $field,
+ $context,
+ ResolveInfo $info,
+ array $value = null,
+ array $args = null
+ ) {
+ if (!isset($value['model'])) {
+ throw new LocalizedException(__('"model" value should be specified'));
+ }
+
+ /** @var QuoteItem $quoteItem */
+ $quoteItem = $value['model'];
+
+ /** @var Product $product */
+ $product = $quoteItem->getProduct();
+
+ if (!in_array($product->getTypeId(), ['downloadable', 'virtual'])) {
+ throw new GraphQlInputException(
+ __('Wrong product type. Links are available for Downloadable and Virtual product types')
+ );
+ }
+
+ $samples = $this->getDownloadableProductSamples->execute($product);
+ $data = $this->convertSamplesToArray->execute($samples);
+ return $data;
+ }
+}
diff --git a/app/code/Magento/DownloadableGraphQl/Resolver/Product/Links.php b/app/code/Magento/DownloadableGraphQl/Resolver/Product/Links.php
new file mode 100644
index 0000000000000..2d5741f127ecb
--- /dev/null
+++ b/app/code/Magento/DownloadableGraphQl/Resolver/Product/Links.php
@@ -0,0 +1,66 @@
+getDownloadableProductLinks = $getDownloadableProductLinks;
+ $this->convertLinksToArray = $convertLinksToArray;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function resolve(
+ Field $field,
+ $context,
+ ResolveInfo $info,
+ array $value = null,
+ array $args = null
+ ) {
+ if (!isset($value['model'])) {
+ throw new LocalizedException(__('"model" value should be specified'));
+ }
+
+ /** @var Product $product */
+ $product = $value['model'];
+
+ $links = $this->getDownloadableProductLinks->execute($product);
+ $data = $this->convertLinksToArray->execute($links);
+ return $data;
+ }
+}
diff --git a/app/code/Magento/DownloadableGraphQl/Resolver/Product/Samples.php b/app/code/Magento/DownloadableGraphQl/Resolver/Product/Samples.php
new file mode 100644
index 0000000000000..3a5e6904c9e62
--- /dev/null
+++ b/app/code/Magento/DownloadableGraphQl/Resolver/Product/Samples.php
@@ -0,0 +1,66 @@
+getDownloadableProductSamples = $getDownloadableProductSamples;
+ $this->convertSamplesToArray = $convertSamplesToArray;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function resolve(
+ Field $field,
+ $context,
+ ResolveInfo $info,
+ array $value = null,
+ array $args = null
+ ) {
+ if (!isset($value['model'])) {
+ throw new LocalizedException(__('"model" value should be specified'));
+ }
+
+ /** @var Product $product */
+ $product = $value['model'];
+
+ $samples = $this->getDownloadableProductSamples->execute($product);
+ $data = $this->convertSamplesToArray->execute($samples);
+ return $data;
+ }
+}
diff --git a/app/code/Magento/DownloadableGraphQl/Test/Mftf/README.md b/app/code/Magento/DownloadableGraphQl/Test/Mftf/README.md
deleted file mode 100644
index 621abd2f1cba5..0000000000000
--- a/app/code/Magento/DownloadableGraphQl/Test/Mftf/README.md
+++ /dev/null
@@ -1,3 +0,0 @@
-# Downloadable Graph Ql Functional Tests
-
-The Functional Test Module for **Magento Downloadable Graph Ql** module.
diff --git a/app/code/Magento/DownloadableGraphQl/composer.json b/app/code/Magento/DownloadableGraphQl/composer.json
index 1c18b75a6579e..e2e1098031766 100644
--- a/app/code/Magento/DownloadableGraphQl/composer.json
+++ b/app/code/Magento/DownloadableGraphQl/composer.json
@@ -3,10 +3,11 @@
"description": "N/A",
"type": "magento2-module",
"require": {
- "php": "~7.1.3||~7.2.0",
+ "php": "~7.1.3||~7.2.0||~7.3.0",
"magento/module-catalog": "*",
"magento/module-downloadable": "*",
- "magento/module-graph-ql": "*",
+ "magento/module-quote": "*",
+ "magento/module-quote-graph-ql": "*",
"magento/framework": "*"
},
"suggest": {
diff --git a/app/code/Magento/DownloadableGraphQl/etc/graphql/di.xml b/app/code/Magento/DownloadableGraphQl/etc/graphql/di.xml
index 457ace068de11..c95667de15ac3 100644
--- a/app/code/Magento/DownloadableGraphQl/etc/graphql/di.xml
+++ b/app/code/Magento/DownloadableGraphQl/etc/graphql/di.xml
@@ -32,4 +32,11 @@
+
+
+
+ - Magento\DownloadableGraphQl\Model\Cart\BuyRequest\DownloadableLinksDataProvider
+
+
+
diff --git a/app/code/Magento/DownloadableGraphQl/etc/module.xml b/app/code/Magento/DownloadableGraphQl/etc/module.xml
index 0431b5cbba9c3..e6158fdbe64eb 100644
--- a/app/code/Magento/DownloadableGraphQl/etc/module.xml
+++ b/app/code/Magento/DownloadableGraphQl/etc/module.xml
@@ -8,10 +8,11 @@
-
-
+
+
+
diff --git a/app/code/Magento/DownloadableGraphQl/etc/schema.graphqls b/app/code/Magento/DownloadableGraphQl/etc/schema.graphqls
index 788a5fc601ee1..db452d1e5ace1 100644
--- a/app/code/Magento/DownloadableGraphQl/etc/schema.graphqls
+++ b/app/code/Magento/DownloadableGraphQl/etc/schema.graphqls
@@ -1,52 +1,65 @@
# Copyright © Magento, Inc. All rights reserved.
# See COPYING.txt for license details.
-type Query {
- customerDownloadableProducts: CustomerDownloadableProducts @resolver(class: "Magento\\DownloadableGraphQl\\Model\\Resolver\\CustomerDownloadableProducts") @doc(description: "The query returns the contents of a customer's downloadable products") @cache(cacheable: false)
+type Mutation {
+ addDownloadableProductsToCart(input: AddDownloadableProductsToCartInput): AddDownloadableProductsToCartOutput @resolver(class: "Magento\\QuoteGraphQl\\Model\\Resolver\\AddSimpleProductsToCart")
}
-type CustomerDownloadableProducts {
- items: [CustomerDownloadableProduct] @doc(description: "List of purchased downloadable items")
+input AddDownloadableProductsToCartInput {
+ cart_id: String!
+ cart_items: [DownloadableProductCartItemInput!]!
}
-type CustomerDownloadableProduct {
- order_increment_id: String
- date: String
- status: String
- download_url: String
- remaining_downloads: String
+input DownloadableProductCartItemInput {
+ data: CartItemInput!
+ downloadable_product_links: [DownloadableProductLinksInput!]
+ customizable_options:[CustomizableOptionInput!]
+}
+
+input DownloadableProductLinksInput {
+ link_id: Int!
+}
+
+type AddDownloadableProductsToCartOutput {
+ cart: Cart!
+}
+
+type DownloadableCartItem implements CartItemInterface @doc(description: "Downloadable Cart Item") {
+ customizable_options: [SelectedCustomizableOption] @resolver(class: "Magento\\QuoteGraphQl\\Model\\Resolver\\CustomizableOptions")
+ links: [DownloadableProductLinks] @resolver(class: "Magento\\DownloadableGraphQl\\Resolver\\DownloadableCartItem\\Links") @doc(description: "An array containing information about the links for the added to cart downloadable product")
+ samples: [DownloadableProductSamples] @resolver(class: "Magento\\DownloadableGraphQl\\Resolver\\DownloadableCartItem\\Samples") @doc(description: "DownloadableProductSamples defines characteristics of a downloadable product")
}
type DownloadableProduct implements ProductInterface, CustomizableProductInterface @doc(description: "DownloadableProduct defines a product that the customer downloads") {
- downloadable_product_samples: [DownloadableProductSamples] @resolver(class: "Magento\\DownloadableGraphQl\\Model\\Resolver\\Product\\DownloadableOptions") @doc(description: "An array containing information about samples of this downloadable product.")
- downloadable_product_links: [DownloadableProductLinks] @resolver(class: "Magento\\DownloadableGraphQl\\Model\\Resolver\\Product\\DownloadableOptions") @doc(description: "An array containing information about the links for this downloadable product")
+ downloadable_product_samples: [DownloadableProductSamples] @resolver(class: "Magento\\DownloadableGraphQl\\Resolver\\Product\\Samples") @doc(description: "An array containing information about samples of this downloadable product.")
+ downloadable_product_links: [DownloadableProductLinks] @resolver(class: "Magento\\DownloadableGraphQl\\Resolver\\Product\\Links") @doc(description: "An array containing information about the links for this downloadable product")
links_purchased_separately: Int @doc(description: "A value of 1 indicates that each link in the array must be purchased separately")
links_title: String @doc(description: "The heading above the list of downloadable products")
}
-enum DownloadableFileTypeEnum @doc(description: "This enumeration specifies whether a link or sample is a file or URL") {
- FILE
- URL
+enum DownloadableFileTypeEnum @deprecated(reason: "`sample_url` serves to get the downloadable sample") {
+ FILE @deprecated(reason: "`sample_url` serves to get the downloadable sample")
+ URL @deprecated(reason: "`sample_url` serves to get the downloadable sample")
}
type DownloadableProductLinks @doc(description: "DownloadableProductLinks defines characteristics of a downloadable product") {
- id: Int @doc(description: "The unique ID for the link to the downloadable product")
+ id: Int @deprecated(reason: "This information shoud not be exposed on frontend")
title: String @doc(description: "The display name of the link")
sort_order: Int @doc(description: "A number indicating the sort order")
- is_shareable: Boolean @doc(description: "Indicates whether the link is shareable")
price: Float @doc(description: "The price of the downloadable product")
- number_of_downloads: Int @doc(description: "The maximum number of times the product can be downloaded. A value of 0 means unlimited.")
- link_type: DownloadableFileTypeEnum @doc(description: "Either FILE or URL")
- sample_type: DownloadableFileTypeEnum @doc(description: "Either FILE or URL")
- sample_file: String @doc(description: "The relative path to the downloadable sample")
- sample_url: String @doc(description: "The relative URL to the downloadable sample")
+ sample_url: String @doc(description: "URL to the downloadable sample")
+ is_shareable: Boolean @deprecated(reason: "This information shoud not be exposed on frontend")
+ number_of_downloads: Int @deprecated(reason: "This information shoud not be exposed on frontend")
+ link_type: DownloadableFileTypeEnum @deprecated(reason: "`sample_url` serves to get the downloadable sample")
+ sample_type: DownloadableFileTypeEnum @deprecated(reason: "`sample_url` serves to get the downloadable sample")
+ sample_file: String @deprecated(reason: "`sample_url` serves to get the downloadable sample")
}
type DownloadableProductSamples @doc(description: "DownloadableProductSamples defines characteristics of a downloadable product") {
- id: Int @doc(description: "The unique ID for the downloadable product sample")
+ id: Int @deprecated(reason: "This information shoud not be exposed on frontend")
title: String @doc(description: "The display name of the sample")
sort_order: Int @doc(description: "A number indicating the sort order")
- sample_type: DownloadableFileTypeEnum @doc(description: "Either FILE or URL")
- sample_file: String @doc(description: "The relative path to the downloadable sample")
- sample_url: String @doc(description: "The relative URL to the downloadable sample")
+ sample_url: String @doc(description: "URL to the downloadable sample")
+ sample_type: DownloadableFileTypeEnum @deprecated(reason: "`sample_url` serves to get the downloadable sample")
+ sample_file: String @deprecated(reason: "`sample_url` serves to get the downloadable sample")
}
diff --git a/app/code/Magento/DownloadableImportExport/composer.json b/app/code/Magento/DownloadableImportExport/composer.json
index 860303b0c86aa..50f039688149e 100644
--- a/app/code/Magento/DownloadableImportExport/composer.json
+++ b/app/code/Magento/DownloadableImportExport/composer.json
@@ -5,7 +5,7 @@
"sort-packages": true
},
"require": {
- "php": "~7.1.3||~7.2.0",
+ "php": "~7.1.3||~7.2.0||~7.3.0",
"magento/framework": "*",
"magento/module-catalog": "*",
"magento/module-catalog-import-export": "*",
diff --git a/app/code/Magento/Eav/Block/Adminhtml/Attribute/Edit/Options/Options.php b/app/code/Magento/Eav/Block/Adminhtml/Attribute/Edit/Options/Options.php
index 72f4086c1c56b..7af7bf447c45a 100644
--- a/app/code/Magento/Eav/Block/Adminhtml/Attribute/Edit/Options/Options.php
+++ b/app/code/Magento/Eav/Block/Adminhtml/Attribute/Edit/Options/Options.php
@@ -4,16 +4,14 @@
* See COPYING.txt for license details.
*/
-/**
- * Attribute add/edit form options tab
- *
- * @author Magento Core Team
- */
namespace Magento\Eav\Block\Adminhtml\Attribute\Edit\Options;
use Magento\Store\Model\ResourceModel\Store\Collection;
+use Magento\Eav\Model\Entity\Attribute\AbstractAttribute;
/**
+ * Attribute add/edit form options tab
+ *
* @api
* @since 100.0.2
*/
@@ -61,6 +59,7 @@ public function __construct(
/**
* Is true only for system attributes which use source model
+ *
* Option labels and position for such attributes are kept in source model and thus cannot be overridden
*
* @return bool
@@ -96,12 +95,16 @@ public function getStoresSortedBySortOrder()
{
$stores = $this->getStores();
if (is_array($stores)) {
- usort($stores, function ($storeA, $storeB) {
- if ($storeA->getSortOrder() == $storeB->getSortOrder()) {
- return $storeA->getId() < $storeB->getId() ? -1 : 1;
+ usort(
+ $stores,
+ function ($storeA, $storeB) {
+ if ($storeA->getSortOrder() == $storeB->getSortOrder()) {
+ return $storeA->getId() < $storeB->getId() ? -1 : 1;
+ }
+
+ return ($storeA->getSortOrder() < $storeB->getSortOrder()) ? -1 : 1;
}
- return ($storeA->getSortOrder() < $storeB->getSortOrder()) ? -1 : 1;
- });
+ );
}
return $stores;
}
@@ -130,12 +133,14 @@ public function getOptionValues()
}
/**
- * @param \Magento\Eav\Model\Entity\Attribute\AbstractAttribute $attribute
+ * Preparing values of attribute options
+ *
+ * @param AbstractAttribute $attribute
* @param array|\Magento\Eav\Model\ResourceModel\Entity\Attribute\Option\Collection $optionCollection
* @return array
*/
protected function _prepareOptionValues(
- \Magento\Eav\Model\Entity\Attribute\AbstractAttribute $attribute,
+ AbstractAttribute $attribute,
$optionCollection
) {
$type = $attribute->getFrontendInput();
@@ -149,6 +154,41 @@ protected function _prepareOptionValues(
$values = [];
$isSystemAttribute = is_array($optionCollection);
+ if ($isSystemAttribute) {
+ $values = $this->getPreparedValues($optionCollection, $isSystemAttribute, $inputType, $defaultValues);
+ } else {
+ $optionCollection->setPageSize(200);
+ $pageCount = $optionCollection->getLastPageNumber();
+ $currentPage = 1;
+ while ($currentPage <= $pageCount) {
+ $optionCollection->clear();
+ $optionCollection->setCurPage($currentPage);
+ $values = array_merge(
+ $values,
+ $this->getPreparedValues($optionCollection, $isSystemAttribute, $inputType, $defaultValues)
+ );
+ $currentPage++;
+ }
+ }
+
+ return $values;
+ }
+
+ /**
+ * Return prepared values of system or user defined attribute options
+ *
+ * @param array|\Magento\Eav\Model\ResourceModel\Entity\Attribute\Option\Collection $optionCollection
+ * @param bool $isSystemAttribute
+ * @param string $inputType
+ * @param array $defaultValues
+ */
+ private function getPreparedValues(
+ $optionCollection,
+ bool $isSystemAttribute,
+ string $inputType,
+ array $defaultValues
+ ) {
+ $values = [];
foreach ($optionCollection as $option) {
$bunch = $isSystemAttribute ? $this->_prepareSystemAttributeOptionValues(
$option,
@@ -169,12 +209,13 @@ protected function _prepareOptionValues(
/**
* Retrieve option values collection
+ *
* It is represented by an array in case of system attribute
*
- * @param \Magento\Eav\Model\Entity\Attribute\AbstractAttribute $attribute
+ * @param AbstractAttribute $attribute
* @return array|\Magento\Eav\Model\ResourceModel\Entity\Attribute\Option\Collection
*/
- protected function _getOptionValuesCollection(\Magento\Eav\Model\Entity\Attribute\AbstractAttribute $attribute)
+ protected function _getOptionValuesCollection(AbstractAttribute $attribute)
{
if ($this->canManageOptionDefaultOnly()) {
$options = $this->_universalFactory->create(
@@ -226,7 +267,7 @@ protected function _prepareSystemAttributeOptionValues($option, $inputType, $def
foreach ($this->getStores() as $store) {
$storeId = $store->getId();
$value['store' . $storeId] = $storeId ==
- \Magento\Store\Model\Store::DEFAULT_STORE_ID ? $valuePrefix . $this->escapeHtml($option['label']) : '';
+ \Magento\Store\Model\Store::DEFAULT_STORE_ID ? $valuePrefix . $this->escapeHtml($option['label']) : '';
}
return [$value];
diff --git a/app/code/Magento/Eav/Model/Attribute/Data/Text.php b/app/code/Magento/Eav/Model/Attribute/Data/Text.php
index 071b2b83e3963..22cac884491ae 100644
--- a/app/code/Magento/Eav/Model/Attribute/Data/Text.php
+++ b/app/code/Magento/Eav/Model/Attribute/Data/Text.php
@@ -45,7 +45,7 @@ public function __construct(
*/
public function extractValue(RequestInterface $request)
{
- $value = $this->_getRequestValue($request);
+ $value = trim($this->_getRequestValue($request));
return $this->_applyInputFilter($value);
}
@@ -75,6 +75,8 @@ public function validateValue($value)
if (empty($value) && $value !== '0' && $attribute->getDefaultValue() === null) {
$label = __($attribute->getStoreLabel());
$errors[] = __('"%1" is a required value.', $label);
+
+ return $errors;
}
$validateLengthResult = $this->validateLength($attribute, $value);
@@ -168,7 +170,7 @@ private function validateLength(\Magento\Eav\Model\Attribute $attribute, string
*/
private function validateInputRule(string $value): array
{
- $result = $this->_validateInputRule(trim($value));
+ $result = $this->_validateInputRule($value);
return \is_array($result) ? $result : [];
}
}
diff --git a/app/code/Magento/Eav/Model/AttributeProvider.php b/app/code/Magento/Eav/Model/AttributeProvider.php
index 7f9d0620b6096..419a33664e0eb 100644
--- a/app/code/Magento/Eav/Model/AttributeProvider.php
+++ b/app/code/Magento/Eav/Model/AttributeProvider.php
@@ -3,6 +3,7 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
+declare(strict_types=1);
namespace Magento\Eav\Model;
@@ -52,7 +53,7 @@ public function __construct(
* Returns array of fields
*
* @param string $entityType
- * @return array
+ * @return string[]
* @throws \Exception
*/
public function getAttributes($entityType)
@@ -66,6 +67,7 @@ public function getAttributes($entityType)
foreach ($searchResult->getItems() as $attribute) {
$attributes[] = $attribute->getAttributeCode();
}
+
return $attributes;
}
}
diff --git a/app/code/Magento/Eav/Model/Entity/Attribute.php b/app/code/Magento/Eav/Model/Entity/Attribute.php
index e23f81607a0c0..bb2477d4df827 100644
--- a/app/code/Magento/Eav/Model/Entity/Attribute.php
+++ b/app/code/Magento/Eav/Model/Entity/Attribute.php
@@ -203,6 +203,7 @@ protected function _getDefaultSourceModel()
* Delete entity
*
* @return \Magento\Eav\Model\ResourceModel\Entity\Attribute
+ * @throws LocalizedException
* @codeCoverageIgnore
*/
public function deleteEntity()
@@ -310,9 +311,10 @@ public function beforeSave()
}
/**
- * @inheritdoc
+ * Save additional data
*
- * Save additional data.
+ * @return $this
+ * @throws LocalizedException
*/
public function afterSave()
{
@@ -320,15 +322,6 @@ public function afterSave()
return parent::afterSave();
}
- /**
- * @inheritdoc
- * @since 100.0.7
- */
- public function afterDelete()
- {
- return parent::afterDelete();
- }
-
/**
* Detect backend storage type using frontend input type
*
@@ -496,14 +489,9 @@ public function getIdentities()
/**
* @inheritdoc
* @since 100.0.7
- *
- * @SuppressWarnings(PHPMD.SerializationAware)
- * @deprecated Do not use PHP serialization.
*/
public function __sleep()
{
- trigger_error('Using PHP serialization is deprecated', E_USER_DEPRECATED);
-
$this->unsetData('attribute_set_info');
return array_diff(
parent::__sleep(),
@@ -514,14 +502,9 @@ public function __sleep()
/**
* @inheritdoc
* @since 100.0.7
- *
- * @SuppressWarnings(PHPMD.SerializationAware)
- * @deprecated Do not use PHP serialization.
*/
public function __wakeup()
{
- trigger_error('Using PHP serialization is deprecated', E_USER_DEPRECATED);
-
parent::__wakeup();
$objectManager = ObjectManager::getInstance();
$this->_localeDate = $objectManager->get(\Magento\Framework\Stdlib\DateTime\TimezoneInterface::class);
diff --git a/app/code/Magento/Eav/Model/Entity/Attribute/AbstractAttribute.php b/app/code/Magento/Eav/Model/Entity/Attribute/AbstractAttribute.php
index 9ed4ac5293681..3857118ae67ca 100644
--- a/app/code/Magento/Eav/Model/Entity/Attribute/AbstractAttribute.php
+++ b/app/code/Magento/Eav/Model/Entity/Attribute/AbstractAttribute.php
@@ -13,6 +13,7 @@
/**
* Entity/Attribute/Model - attribute abstract
+ * phpcs:disable Magento2.Classes.AbstractApi
* @api
* @SuppressWarnings(PHPMD.ExcessivePublicCount)
* @SuppressWarnings(PHPMD.ExcessiveClassComplexity)
@@ -1404,14 +1405,9 @@ public function setExtensionAttributes(\Magento\Eav\Api\Data\AttributeExtensionI
/**
* @inheritdoc
* @since 100.0.7
- *
- * @SuppressWarnings(PHPMD.SerializationAware)
- * @deprecated Do not use PHP serialization.
*/
public function __sleep()
{
- trigger_error('Using PHP serialization is deprecated', E_USER_DEPRECATED);
-
return array_diff(
parent::__sleep(),
[
@@ -1434,14 +1430,9 @@ public function __sleep()
/**
* @inheritdoc
* @since 100.0.7
- *
- * @SuppressWarnings(PHPMD.SerializationAware)
- * @deprecated Do not use PHP serialization.
*/
public function __wakeup()
{
- trigger_error('Using PHP serialization is deprecated', E_USER_DEPRECATED);
-
parent::__wakeup();
$objectManager = \Magento\Framework\App\ObjectManager::getInstance();
$this->_eavConfig = $objectManager->get(\Magento\Eav\Model\Config::class);
diff --git a/app/code/Magento/Eav/Model/Entity/Attribute/OptionManagement.php b/app/code/Magento/Eav/Model/Entity/Attribute/OptionManagement.php
index 3c3bc083fdf8f..0ea4c324fe5c9 100644
--- a/app/code/Magento/Eav/Model/Entity/Attribute/OptionManagement.php
+++ b/app/code/Magento/Eav/Model/Entity/Attribute/OptionManagement.php
@@ -3,9 +3,11 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
+declare(strict_types=1);
namespace Magento\Eav\Model\Entity\Attribute;
+use Magento\Eav\Api\Data\AttributeInterface as EavAttributeInterface;
use Magento\Framework\Exception\InputException;
use Magento\Framework\Exception\NoSuchEntityException;
use Magento\Framework\Exception\StateException;
@@ -39,7 +41,16 @@ public function __construct(
}
/**
- * @inheritdoc
+ * Add option to attribute.
+ *
+ * @param int $entityType
+ * @param string $attributeCode
+ * @param \Magento\Eav\Api\Data\AttributeOptionInterface $option
+ * @return string
+ * @throws InputException
+ * @throws NoSuchEntityException
+ * @throws StateException
+ * @SuppressWarnings(PHPMD.CyclomaticComplexity)
*/
public function add($entityType, $attributeCode, $option)
{
@@ -64,6 +75,15 @@ public function add($entityType, $attributeCode, $option)
}
}
+ if (!$this->isAttributeOptionLabelExists($attribute, (string) $options['value'][$optionId][0])) {
+ throw new InputException(
+ __(
+ 'Admin store attribute option label "%1" is already exists.',
+ $options['value'][$optionId][0]
+ )
+ );
+ }
+
if ($option->getIsDefault()) {
$attribute->setDefault([$optionId]);
}
@@ -134,10 +154,10 @@ public function getItems($entityType, $attributeCode)
/**
* Validate option
*
- * @param \Magento\Eav\Api\Data\AttributeInterface $attribute
+ * @param EavAttributeInterface $attribute
* @param int $optionId
- * @throws NoSuchEntityException
* @return void
+ * @throws NoSuchEntityException
*/
protected function validateOption($attribute, $optionId)
{
@@ -167,13 +187,13 @@ private function getOptionId(\Magento\Eav\Api\Data\AttributeOptionInterface $opt
* Set option value
*
* @param \Magento\Eav\Api\Data\AttributeOptionInterface $option
- * @param \Magento\Eav\Api\Data\AttributeInterface $attribute
+ * @param EavAttributeInterface $attribute
* @param string $optionLabel
* @return void
*/
private function setOptionValue(
\Magento\Eav\Api\Data\AttributeOptionInterface $option,
- \Magento\Eav\Api\Data\AttributeInterface $attribute,
+ EavAttributeInterface $attribute,
string $optionLabel
) {
$optionId = $attribute->getSource()->getOptionId($optionLabel);
@@ -188,4 +208,28 @@ private function setOptionValue(
}
}
}
+
+ /**
+ * Checks if the incoming attribute option label for admin store is already exists.
+ *
+ * @param EavAttributeInterface $attribute
+ * @param string $adminStoreLabel
+ * @param int $storeId
+ * @return bool
+ */
+ private function isAttributeOptionLabelExists(
+ EavAttributeInterface $attribute,
+ string $adminStoreLabel,
+ int $storeId = 0
+ ) :bool {
+ $attribute->setStoreId($storeId);
+
+ foreach ($attribute->getSource()->toOptionArray() as $existingAttributeOption) {
+ if ($existingAttributeOption['label'] === $adminStoreLabel) {
+ return false;
+ }
+ }
+
+ return true;
+ }
}
diff --git a/app/code/Magento/Eav/Model/Entity/Increment/Alphanum.php b/app/code/Magento/Eav/Model/Entity/Increment/Alphanum.php
index 4e44cef825d72..8116717676f53 100644
--- a/app/code/Magento/Eav/Model/Entity/Increment/Alphanum.php
+++ b/app/code/Magento/Eav/Model/Entity/Increment/Alphanum.php
@@ -15,6 +15,9 @@
*/
namespace Magento\Eav\Model\Entity\Increment;
+/**
+ * Handle alphanumeric ids.
+ */
class Alphanum extends \Magento\Eav\Model\Entity\Increment\AbstractIncrement
{
/**
@@ -38,7 +41,7 @@ public function getNextId()
{
$lastId = $this->getLastId();
- if (strpos($lastId, $this->getPrefix()) === 0) {
+ if (strpos($lastId, (string) $this->getPrefix()) === 0) {
$lastId = substr($lastId, strlen($this->getPrefix()));
}
@@ -51,7 +54,7 @@ public function getNextId()
$lid = strlen($lastId) - 1;
for ($i = $lid; $i >= 0; $i--) {
- $p = strpos($chars, $lastId[$i]);
+ $p = strpos($chars, (string) $lastId[$i]);
if (false === $p) {
throw new \Magento\Framework\Exception\LocalizedException(
__('Invalid character encountered in increment ID: %1', $lastId)
diff --git a/app/code/Magento/Eav/Model/Entity/Increment/NumericValue.php b/app/code/Magento/Eav/Model/Entity/Increment/NumericValue.php
index b86b1e33c5859..1bd3b29204f8f 100644
--- a/app/code/Magento/Eav/Model/Entity/Increment/NumericValue.php
+++ b/app/code/Magento/Eav/Model/Entity/Increment/NumericValue.php
@@ -28,7 +28,7 @@ public function getNextId()
{
$last = $this->getLastId();
- if (strpos($last, $this->getPrefix()) === 0) {
+ if (strpos($last, (string) $this->getPrefix()) === 0) {
$last = (int)substr($last, strlen($this->getPrefix()));
} else {
$last = (int)$last;
diff --git a/app/code/Magento/Eav/Model/ResourceModel/Entity/Attribute.php b/app/code/Magento/Eav/Model/ResourceModel/Entity/Attribute.php
index 5e7226e7a36dd..d05a7e1e2baa4 100644
--- a/app/code/Magento/Eav/Model/ResourceModel/Entity/Attribute.php
+++ b/app/code/Magento/Eav/Model/ResourceModel/Entity/Attribute.php
@@ -457,6 +457,7 @@ protected function _updateAttributeOption($object, $optionId, $option)
if (!empty($option['delete'][$optionId])) {
if ($intOptionId) {
$connection->delete($table, ['option_id = ?' => $intOptionId]);
+ $this->clearSelectedOptionInEntities($object, $intOptionId);
}
return false;
}
@@ -475,6 +476,41 @@ protected function _updateAttributeOption($object, $optionId, $option)
return $intOptionId;
}
+ /**
+ * Clear selected option in entities
+ *
+ * @param EntityAttribute|AbstractModel $object
+ * @param int $optionId
+ * @return void
+ */
+ private function clearSelectedOptionInEntities(AbstractModel $object, int $optionId)
+ {
+ $backendTable = $object->getBackendTable();
+ $attributeId = $object->getAttributeId();
+ if (!$backendTable || !$attributeId) {
+ return;
+ }
+
+ $connection = $this->getConnection();
+ $where = $connection->quoteInto('attribute_id = ?', $attributeId);
+ $update = [];
+
+ if ($object->getBackendType() === 'varchar') {
+ $where.= ' AND ' . $connection->prepareSqlCondition('value', ['finset' => $optionId]);
+ $concat = $connection->getConcatSql(["','", 'value', "','"]);
+ $expr = $connection->quoteInto(
+ "TRIM(BOTH ',' FROM REPLACE($concat,',?,',','))",
+ $optionId
+ );
+ $update['value'] = new \Zend_Db_Expr($expr);
+ } else {
+ $where.= $connection->quoteInto(' AND value = ?', $optionId);
+ $update['value'] = null;
+ }
+
+ $connection->update($backendTable, $update, $where);
+ }
+
/**
* Save option values records per store
*
@@ -725,14 +761,9 @@ public function getValidAttributeIds($attributeIds)
*
* @return array
* @since 100.0.7
- *
- * @SuppressWarnings(PHPMD.SerializationAware)
- * @deprecated Do not use PHP serialization.
*/
public function __sleep()
{
- trigger_error('Using PHP serialization is deprecated', E_USER_DEPRECATED);
-
$properties = parent::__sleep();
$properties = array_diff($properties, ['_storeManager']);
return $properties;
@@ -743,14 +774,9 @@ public function __sleep()
*
* @return void
* @since 100.0.7
- *
- * @SuppressWarnings(PHPMD.SerializationAware)
- * @deprecated Do not use PHP serialization.
*/
public function __wakeup()
{
- trigger_error('Using PHP serialization is deprecated', E_USER_DEPRECATED);
-
parent::__wakeup();
$this->_storeManager = \Magento\Framework\App\ObjectManager::getInstance()
->get(\Magento\Store\Model\StoreManagerInterface::class);
diff --git a/app/code/Magento/Eav/Model/ResourceModel/ReadHandler.php b/app/code/Magento/Eav/Model/ResourceModel/ReadHandler.php
index 7f6dfa2a5e9ab..bf1405fa64122 100644
--- a/app/code/Magento/Eav/Model/ResourceModel/ReadHandler.php
+++ b/app/code/Magento/Eav/Model/ResourceModel/ReadHandler.php
@@ -3,20 +3,27 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
+
namespace Magento\Eav\Model\ResourceModel;
+use Exception;
use Magento\Eav\Model\Config;
+use Magento\Eav\Model\Entity\Attribute\AbstractAttribute;
use Magento\Framework\DataObject;
use Magento\Framework\DB\Select;
use Magento\Framework\DB\Sql\UnionExpression;
use Magento\Framework\EntityManager\MetadataPool;
use Magento\Framework\EntityManager\Operation\AttributeInterface;
+use Magento\Framework\Exception\ConfigurationMismatchException;
+use Magento\Framework\Exception\LocalizedException;
use Magento\Framework\Model\Entity\ScopeInterface;
use Magento\Framework\Model\Entity\ScopeResolver;
use Psr\Log\LoggerInterface;
/**
* EAV read handler
+ *
+ * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
*/
class ReadHandler implements AttributeInterface
{
@@ -63,7 +70,7 @@ public function __construct(
*
* @param string $entityType
* @return \Magento\Eav\Api\Data\AttributeInterface[]
- * @throws \Exception if for unknown entity type
+ * @throws Exception if for unknown entity type
* @deprecated Not used anymore
* @see ReadHandler::getEntityAttributes
*/
@@ -80,7 +87,7 @@ protected function getAttributes($entityType)
* @param string $entityType
* @param DataObject $entity
* @return \Magento\Eav\Api\Data\AttributeInterface[]
- * @throws \Exception if for unknown entity type
+ * @throws Exception if for unknown entity type
*/
private function getEntityAttributes(string $entityType, DataObject $entity): array
{
@@ -111,9 +118,9 @@ protected function getContextVariables(ScopeInterface $scope)
* @param array $entityData
* @param array $arguments
* @return array
- * @throws \Exception
- * @throws \Magento\Framework\Exception\ConfigurationMismatchException
- * @throws \Magento\Framework\Exception\LocalizedException
+ * @throws Exception
+ * @throws ConfigurationMismatchException
+ * @throws LocalizedException
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
*/
public function execute($entityType, $entityData, $arguments = [])
@@ -129,7 +136,7 @@ public function execute($entityType, $entityData, $arguments = [])
$attributesMap = [];
$selects = [];
- /** @var \Magento\Eav\Model\Entity\Attribute\AbstractAttribute $attribute */
+ /** @var AbstractAttribute $attribute */
foreach ($this->getEntityAttributes($entityType, new DataObject($entityData)) as $attribute) {
if (!$attribute->isStatic()) {
$attributeTables[$attribute->getBackend()->getTable()][] = $attribute->getAttributeId();
@@ -170,8 +177,11 @@ public function execute($entityType, $entityData, $arguments = [])
$entityData[$attributesMap[$attributeValue['attribute_id']]] = $attributeValue['value'];
} else {
$this->logger->warning(
- "Attempt to load value of nonexistent EAV attribute '{$attributeValue['attribute_id']}'
- for entity type '$entityType'."
+ "Attempt to load value of nonexistent EAV attribute",
+ [
+ 'attribute_id' => $attributeValue['attribute_id'],
+ 'entity_type' => $entityType
+ ]
);
}
}
@@ -184,8 +194,9 @@ public function execute($entityType, $entityData, $arguments = [])
*
* @param Select[] $selects
* @param array $identifiers
+ * @return void
*/
- private function applyIdentifierForSelects(array $selects, array $identifiers)
+ private function applyIdentifierForSelects(array $selects, array $identifiers): void
{
foreach ($selects as $select) {
foreach ($identifiers as $identifier) {
diff --git a/app/code/Magento/Eav/Setup/AddOptionToAttribute.php b/app/code/Magento/Eav/Setup/AddOptionToAttribute.php
new file mode 100644
index 0000000000000..c6b13f8a6e3ec
--- /dev/null
+++ b/app/code/Magento/Eav/Setup/AddOptionToAttribute.php
@@ -0,0 +1,210 @@
+setup = $setup;
+ }
+
+ /**
+ * Add Attribute Option
+ *
+ * @param array $option
+ *
+ * @return void
+ * @throws LocalizedException
+ */
+ public function execute(array $option): void
+ {
+ $optionTable = $this->setup->getTable('eav_attribute_option');
+ $optionValueTable = $this->setup->getTable('eav_attribute_option_value');
+
+ if (isset($option['value'])) {
+ $this->addValue($option, $optionTable, $optionValueTable);
+ } elseif (isset($option['values'])) {
+ $this->addValues($option, $optionTable, $optionValueTable);
+ }
+ }
+
+ /**
+ * Add option value
+ *
+ * @param array $option
+ * @param string $optionTable
+ * @param string $optionValueTable
+ *
+ * @return void
+ * @throws LocalizedException
+ */
+ private function addValue(array $option, string $optionTable, string $optionValueTable): void
+ {
+ $value = $option['value'];
+ foreach ($value as $optionId => $values) {
+ $intOptionId = (int)$optionId;
+ if (!empty($option['delete'][$optionId])) {
+ if ($intOptionId) {
+ $condition = ['option_id =?' => $intOptionId];
+ $this->setup->getConnection()->delete($optionTable, $condition);
+ }
+ continue;
+ }
+
+ if (!$intOptionId) {
+ $data = [
+ 'attribute_id' => $option['attribute_id'],
+ 'sort_order' => isset($option['order'][$optionId]) ? $option['order'][$optionId] : 0,
+ ];
+ $this->setup->getConnection()->insert($optionTable, $data);
+ $intOptionId = $this->setup->getConnection()->lastInsertId($optionTable);
+ } else {
+ $data = [
+ 'sort_order' => isset($option['order'][$optionId]) ? $option['order'][$optionId] : 0,
+ ];
+ $this->setup->getConnection()->update(
+ $optionTable,
+ $data,
+ ['option_id=?' => $intOptionId]
+ );
+ }
+
+ // Default value
+ if (!isset($values[0])) {
+ throw new LocalizedException(
+ __("The default option isn't defined. Set the option and try again.")
+ );
+ }
+ $condition = ['option_id =?' => $intOptionId];
+ $this->setup->getConnection()->delete($optionValueTable, $condition);
+ foreach ($values as $storeId => $value) {
+ $data = ['option_id' => $intOptionId, 'store_id' => $storeId, 'value' => $value];
+ $this->setup->getConnection()->insert($optionValueTable, $data);
+ }
+ }
+ }
+
+ /**
+ * Add option values
+ *
+ * @param array $option
+ * @param string $optionTable
+ * @param string $optionValueTable
+ *
+ * @return void
+ */
+ private function addValues(array $option, string $optionTable, string $optionValueTable): void
+ {
+ $values = $option['values'];
+ $attributeId = (int)$option['attribute_id'];
+ $existingOptions = $this->getExistingAttributeOptions($attributeId, $optionTable, $optionValueTable);
+ foreach ($values as $sortOrder => $value) {
+ // add option
+ $data = ['attribute_id' => $attributeId, 'sort_order' => $sortOrder];
+ if (!$this->isExistingOptionValue($value, $existingOptions)) {
+ $this->setup->getConnection()->insert($optionTable, $data);
+
+ //add option value
+ $intOptionId = $this->setup->getConnection()->lastInsertId($optionTable);
+ $data = ['option_id' => $intOptionId, 'store_id' => 0, 'value' => $value];
+ $this->setup->getConnection()->insert($optionValueTable, $data);
+ } elseif ($optionId = $this->getExistingOptionIdWithDiffSortOrder(
+ $sortOrder,
+ $value,
+ $existingOptions
+ )
+ ) {
+ $this->setup->getConnection()->update(
+ $optionTable,
+ ['sort_order' => $sortOrder],
+ ['option_id = ?' => $optionId]
+ );
+ }
+ }
+ }
+
+ /**
+ * Check if option value already exists
+ *
+ * @param string $value
+ * @param array $existingOptions
+ *
+ * @return bool
+ */
+ private function isExistingOptionValue(string $value, array $existingOptions): bool
+ {
+ foreach ($existingOptions as $option) {
+ if ($option['value'] == $value) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Get existing attribute options
+ *
+ * @param int $attributeId
+ * @param string $optionTable
+ * @param string $optionValueTable
+ *
+ * @return array
+ */
+ private function getExistingAttributeOptions(int $attributeId, string $optionTable, string $optionValueTable): array
+ {
+ $select = $this->setup
+ ->getConnection()
+ ->select()
+ ->from(['o' => $optionTable])
+ ->reset('columns')
+ ->columns(['option_id', 'sort_order'])
+ ->join(['ov' => $optionValueTable], 'o.option_id = ov.option_id', 'value')
+ ->where(AttributeInterface::ATTRIBUTE_ID . ' = ?', $attributeId)
+ ->where('store_id = 0');
+
+ return $this->setup->getConnection()->fetchAll($select);
+ }
+
+ /**
+ * Check if option already exists, but sort_order differs
+ *
+ * @param int $sortOrder
+ * @param string $value
+ * @param array $existingOptions
+ *
+ * @return int|null
+ */
+ private function getExistingOptionIdWithDiffSortOrder(int $sortOrder, string $value, array $existingOptions): ?int
+ {
+ foreach ($existingOptions as $option) {
+ if ($option['value'] == $value && $option['sort_order'] != $sortOrder) {
+ return (int)$option['option_id'];
+ }
+ }
+
+ return null;
+ }
+}
diff --git a/app/code/Magento/Eav/Setup/EavSetup.php b/app/code/Magento/Eav/Setup/EavSetup.php
index de285e81b1d03..d440a84fc8e65 100644
--- a/app/code/Magento/Eav/Setup/EavSetup.php
+++ b/app/code/Magento/Eav/Setup/EavSetup.php
@@ -82,6 +82,11 @@ class EavSetup
*/
private $_defaultAttributeSetName = 'Default';
+ /**
+ * @var AddOptionToAttribute
+ */
+ private $addAttributeOption;
+
/**
* @var Code
*/
@@ -95,18 +100,23 @@ class EavSetup
* @param CacheInterface $cache
* @param CollectionFactory $attrGroupCollectionFactory
* @param Code|null $attributeCodeValidator
+ * @param AddOptionToAttribute|null $addAttributeOption
+ * @SuppressWarnings(PHPMD.LongVariable)
*/
public function __construct(
ModuleDataSetupInterface $setup,
Context $context,
CacheInterface $cache,
CollectionFactory $attrGroupCollectionFactory,
- Code $attributeCodeValidator = null
+ Code $attributeCodeValidator = null,
+ AddOptionToAttribute $addAttributeOption = null
) {
$this->cache = $cache;
$this->attrGroupCollectionFactory = $attrGroupCollectionFactory;
$this->attributeMapper = $context->getAttributeMapper();
$this->setup = $setup;
+ $this->addAttributeOption = $addAttributeOption
+ ?? ObjectManager::getInstance()->get(AddOptionToAttribute::class);
$this->attributeCodeValidator = $attributeCodeValidator ?: ObjectManager::getInstance()->get(
Code::class
);
@@ -567,6 +577,7 @@ public function addAttributeGroup($entityTypeId, $setId, $name, $sortOrder = nul
if (empty($data['attribute_group_code'])) {
if (empty($attributeGroupCode)) {
// in the following code md5 is not used for security purposes
+ // phpcs:disable Magento2.Security.InsecureFunction
$attributeGroupCode = md5($name);
}
$data['attribute_group_code'] = $attributeGroupCode;
@@ -868,62 +879,7 @@ public function addAttribute($entityTypeId, $code, array $attr)
*/
public function addAttributeOption($option)
{
- $optionTable = $this->setup->getTable('eav_attribute_option');
- $optionValueTable = $this->setup->getTable('eav_attribute_option_value');
-
- if (isset($option['value'])) {
- foreach ($option['value'] as $optionId => $values) {
- $intOptionId = (int)$optionId;
- if (!empty($option['delete'][$optionId])) {
- if ($intOptionId) {
- $condition = ['option_id =?' => $intOptionId];
- $this->setup->getConnection()->delete($optionTable, $condition);
- }
- continue;
- }
-
- if (!$intOptionId) {
- $data = [
- 'attribute_id' => $option['attribute_id'],
- 'sort_order' => isset($option['order'][$optionId]) ? $option['order'][$optionId] : 0,
- ];
- $this->setup->getConnection()->insert($optionTable, $data);
- $intOptionId = $this->setup->getConnection()->lastInsertId($optionTable);
- } else {
- $data = [
- 'sort_order' => isset($option['order'][$optionId]) ? $option['order'][$optionId] : 0,
- ];
- $this->setup->getConnection()->update(
- $optionTable,
- $data,
- ['option_id=?' => $intOptionId]
- );
- }
-
- // Default value
- if (!isset($values[0])) {
- throw new \Magento\Framework\Exception\LocalizedException(
- __("The default option isn't defined. Set the option and try again.")
- );
- }
- $condition = ['option_id =?' => $intOptionId];
- $this->setup->getConnection()->delete($optionValueTable, $condition);
- foreach ($values as $storeId => $value) {
- $data = ['option_id' => $intOptionId, 'store_id' => $storeId, 'value' => $value];
- $this->setup->getConnection()->insert($optionValueTable, $data);
- }
- }
- } elseif (isset($option['values'])) {
- foreach ($option['values'] as $sortOrder => $label) {
- // add option
- $data = ['attribute_id' => $option['attribute_id'], 'sort_order' => $sortOrder];
- $this->setup->getConnection()->insert($optionTable, $data);
- $intOptionId = $this->setup->getConnection()->lastInsertId($optionTable);
-
- $data = ['option_id' => $intOptionId, 'store_id' => 0, 'value' => $label];
- $this->setup->getConnection()->insert($optionValueTable, $data);
- }
- }
+ $this->addAttributeOption->execute($option);
}
/**
diff --git a/app/code/Magento/Eav/Test/Unit/Model/Attribute/Data/TextTest.php b/app/code/Magento/Eav/Test/Unit/Model/Attribute/Data/TextTest.php
index 785c386b439d8..f4c2ad43ab9e3 100644
--- a/app/code/Magento/Eav/Test/Unit/Model/Attribute/Data/TextTest.php
+++ b/app/code/Magento/Eav/Test/Unit/Model/Attribute/Data/TextTest.php
@@ -138,7 +138,6 @@ public function alphanumDataProvider(): array
['QazWsx12345', [
__('"%1" length must be equal or less than %2 characters.', 'Test', 10)]
],
- [' 12345 ', true],
];
}
diff --git a/app/code/Magento/Eav/Test/Unit/Model/Entity/Attribute/OptionManagementTest.php b/app/code/Magento/Eav/Test/Unit/Model/Entity/Attribute/OptionManagementTest.php
index b63a4dd2c9ae6..f23814e0de0c4 100644
--- a/app/code/Magento/Eav/Test/Unit/Model/Entity/Attribute/OptionManagementTest.php
+++ b/app/code/Magento/Eav/Test/Unit/Model/Entity/Attribute/OptionManagementTest.php
@@ -6,6 +6,12 @@
namespace Magento\Eav\Test\Unit\Model\Entity\Attribute;
+use Magento\Eav\Api\Data\AttributeOptionInterface as EavAttributeOptionInterface;
+use Magento\Eav\Api\Data\AttributeOptionLabelInterface as EavAttributeOptionLabelInterface;
+use Magento\Eav\Model\Entity\Attribute\AbstractAttribute as EavAbstractAttribute;
+use Magento\Eav\Model\Entity\Attribute\Source\Table as EavAttributeSource;
+use PHPUnit\Framework\MockObject\MockObject as MockObject;
+
class OptionManagementTest extends \PHPUnit\Framework\TestCase
{
/**
@@ -38,25 +44,9 @@ public function testAdd()
{
$entityType = 42;
$attributeCode = 'atrCde';
- $optionMock = $this->getMockForAbstractClass(
- \Magento\Eav\Api\Data\AttributeOptionInterface::class,
- [],
- '',
- false,
- false,
- true,
- ['getSourceLabels']
- );
- $attributeMock = $this->getMockForAbstractClass(
- \Magento\Framework\Model\AbstractModel::class,
- [],
- '',
- false,
- false,
- true,
- ['usesSource', 'setDefault', 'setOption']
- );
- $labelMock = $this->createMock(\Magento\Eav\Api\Data\AttributeOptionLabelInterface::class);
+ $attributeMock = $this->getAttribute();
+ $optionMock = $this->getAttributeOption();
+ $labelMock = $this->getAttributeOptionLabel();
$option =
['value' => [
'id_new_option' => [
@@ -92,15 +82,7 @@ public function testAddWithEmptyAttributeCode()
{
$entityType = 42;
$attributeCode = '';
- $optionMock = $this->getMockForAbstractClass(
- \Magento\Eav\Api\Data\AttributeOptionInterface::class,
- [],
- '',
- false,
- false,
- true,
- ['getSourceLabels']
- );
+ $optionMock = $this->getAttributeOption();
$this->resourceModelMock->expects($this->never())->method('save');
$this->model->add($entityType, $attributeCode, $optionMock);
}
@@ -113,24 +95,8 @@ public function testAddWithWrongOptions()
{
$entityType = 42;
$attributeCode = 'testAttribute';
- $optionMock = $this->getMockForAbstractClass(
- \Magento\Eav\Api\Data\AttributeOptionInterface::class,
- [],
- '',
- false,
- false,
- true,
- ['getSourceLabels']
- );
- $attributeMock = $this->getMockForAbstractClass(
- \Magento\Framework\Model\AbstractModel::class,
- [],
- '',
- false,
- false,
- true,
- ['usesSource', 'setDefault', 'setOption']
- );
+ $attributeMock = $this->getAttribute();
+ $optionMock = $this->getAttributeOption();
$this->attributeRepositoryMock->expects($this->once())->method('get')->with($entityType, $attributeCode)
->willReturn($attributeMock);
$attributeMock->expects($this->once())->method('usesSource')->willReturn(false);
@@ -146,25 +112,9 @@ public function testAddWithCannotSaveException()
{
$entityType = 42;
$attributeCode = 'atrCde';
- $optionMock = $this->getMockForAbstractClass(
- \Magento\Eav\Api\Data\AttributeOptionInterface::class,
- [],
- '',
- false,
- false,
- true,
- ['getSourceLabels']
- );
- $attributeMock = $this->getMockForAbstractClass(
- \Magento\Framework\Model\AbstractModel::class,
- [],
- '',
- false,
- false,
- true,
- ['usesSource', 'setDefault', 'setOption']
- );
- $labelMock = $this->createMock(\Magento\Eav\Api\Data\AttributeOptionLabelInterface::class);
+ $optionMock = $this->getAttributeOption();
+ $attributeMock = $this->getAttribute();
+ $labelMock = $this->getAttributeOptionLabel();
$option =
['value' => [
'id_new_option' => [
@@ -340,7 +290,7 @@ public function testGetItems()
true,
['getOptions']
);
- $optionsMock = [$this->createMock(\Magento\Eav\Api\Data\AttributeOptionInterface::class)];
+ $optionsMock = [$this->createMock(EavAttributeOptionInterface::class)];
$this->attributeRepositoryMock->expects($this->once())->method('get')->with($entityType, $attributeCode)
->willReturn($attributeMock);
$attributeMock->expects($this->once())->method('getOptions')->willReturn($optionsMock);
@@ -380,4 +330,55 @@ public function testGetItemsWithEmptyAttributeCode()
$attributeCode = '';
$this->model->getItems($entityType, $attributeCode);
}
+
+ /**
+ * Returns attribute entity mock.
+ *
+ * @param array $attributeOptions attribute options for return
+ * @return MockObject|EavAbstractAttribute
+ */
+ private function getAttribute(array $attributeOptions = [])
+ {
+ $attribute = $this->getMockBuilder(EavAbstractAttribute::class)
+ ->disableOriginalConstructor()
+ ->setMethods(
+ [
+ 'usesSource',
+ 'setDefault',
+ 'setOption',
+ 'setStoreId',
+ 'getSource',
+ ]
+ )
+ ->getMock();
+ $source = $this->getMockBuilder(EavAttributeSource::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $attribute->method('getSource')->willReturn($source);
+ $source->method('toOptionArray')->willReturn($attributeOptions);
+
+ return $attribute;
+ }
+
+ /**
+ * Return attribute option entity mock.
+ *
+ * @return MockObject|EavAttributeOptionInterface
+ */
+ private function getAttributeOption()
+ {
+ return $this->getMockBuilder(EavAttributeOptionInterface::class)
+ ->setMethods(['getSourceLabels'])
+ ->getMockForAbstractClass();
+ }
+
+ /**
+ * @return MockObject|EavAttributeOptionLabelInterface
+ */
+ private function getAttributeOptionLabel()
+ {
+ return $this->getMockBuilder(EavAttributeOptionLabelInterface::class)
+ ->getMockForAbstractClass();
+ }
}
diff --git a/app/code/Magento/Eav/Test/Unit/Setup/AddAttributeOptionTest.php b/app/code/Magento/Eav/Test/Unit/Setup/AddAttributeOptionTest.php
new file mode 100644
index 0000000000000..17376ceebbcb4
--- /dev/null
+++ b/app/code/Magento/Eav/Test/Unit/Setup/AddAttributeOptionTest.php
@@ -0,0 +1,200 @@
+createMock(ModuleDataSetupInterface::class);
+ $this->connectionMock = $this->createMock(Mysql::class);
+ $this->connectionMock->method('select')
+ ->willReturn($objectManager->getObject(Select::class));
+
+ $setupMock->method('getTable')->willReturn('some_table');
+ $setupMock->method('getConnection')->willReturn($this->connectionMock);
+
+ $this->operation = new AddOptionToAttribute($setupMock);
+ }
+
+ /**
+ * @throws LocalizedException
+ */
+ public function testAddNewOptions()
+ {
+ $this->connectionMock->method('fetchAll')->willReturn([]);
+ $this->connectionMock->expects($this->exactly(4))->method('insert');
+
+ $this->operation->execute(
+ [
+ 'values' => ['Black', 'White'],
+ 'attribute_id' => 4
+ ]
+ );
+ }
+
+ /**
+ * @throws LocalizedException
+ */
+ public function testAddExistingOptionsWithTheSameSortOrder()
+ {
+ $this->connectionMock->method('fetchAll')->willReturn(
+ [
+ ['option_id' => 1, 'sort_order' => 0, 'value' => 'Black'],
+ ['option_id' => 2, 'sort_order' => 1, 'value' => 'White'],
+ ]
+ );
+
+ $this->connectionMock->expects($this->never())->method('insert');
+ $this->connectionMock->expects($this->never())->method('update');
+
+ $this->operation->execute(
+ [
+ 'values' => ['Black', 'White'],
+ 'attribute_id' => 4
+ ]
+ );
+ }
+
+ /**
+ * @throws LocalizedException
+ */
+ public function testAddExistingOptionsWithDifferentSortOrder()
+ {
+ $this->connectionMock->method('fetchAll')->willReturn(
+ [
+ ['option_id' => 1, 'sort_order' => 13, 'value' => 'Black'],
+ ['option_id' => 2, 'sort_order' => 666, 'value' => 'White'],
+ ]
+ );
+
+ $this->connectionMock->expects($this->never())->method('insert');
+ $this->connectionMock->expects($this->exactly(2))->method('update');
+
+ $this->operation->execute(
+ [
+ 'values' => ['Black', 'White'],
+ 'attribute_id' => 4
+ ]
+ );
+ }
+
+ /**
+ * @throws LocalizedException
+ */
+ public function testAddMixedOptions()
+ {
+ $this->connectionMock->method('fetchAll')->willReturn(
+ [
+ ['option_id' => 1, 'sort_order' => 13, 'value' => 'Black'],
+ ]
+ );
+
+ $this->connectionMock->expects($this->exactly(2))->method('insert');
+ $this->connectionMock->expects($this->once())->method('update');
+
+ $this->operation->execute(
+ [
+ 'values' => ['Black', 'White'],
+ 'attribute_id' => 4
+ ]
+ );
+ }
+
+ /**
+ * @throws LocalizedException
+ */
+ public function testAddNewOption()
+ {
+ $this->connectionMock->expects($this->exactly(2))->method('insert');
+ $this->connectionMock->expects($this->once())->method('delete');
+
+ $this->operation->execute(
+ [
+ 'attribute_id' => 1,
+ 'order' => [0 => 13],
+ 'value' => [
+ [
+ 0 => 'zzz',
+ ],
+ ],
+ ]
+ );
+ }
+
+ /**
+ * @expectedException \Magento\Framework\Exception\LocalizedException
+ * @expectedExceptionMessage The default option isn't defined. Set the option and try again.
+ */
+ public function testAddNewOptionWithoutDefaultValue()
+ {
+ $this->operation->execute(
+ [
+ 'attribute_id' => 1,
+ 'order' => [0 => 13],
+ 'value' => [[]],
+ ]
+ );
+ }
+
+ public function testDeleteOption()
+ {
+ $this->connectionMock->expects($this->never())->method('insert');
+ $this->connectionMock->expects($this->never())->method('update');
+ $this->connectionMock->expects($this->once())->method('delete');
+
+ $this->operation->execute(
+ [
+ 'attribute_id' => 1,
+ 'delete' => [13 => true],
+ 'value' => [
+ 13 => null,
+ ],
+ ]
+ );
+ }
+
+ public function testUpdateOption()
+ {
+ $this->connectionMock->expects($this->once())->method('insert');
+ $this->connectionMock->expects($this->once())->method('update');
+ $this->connectionMock->expects($this->once())->method('delete');
+
+ $this->operation->execute(
+ [
+ 'attribute_id' => 1,
+ 'value' => [
+ 13 => ['zzz'],
+ ],
+ ]
+ );
+ }
+}
diff --git a/app/code/Magento/Eav/composer.json b/app/code/Magento/Eav/composer.json
index 80692bf37bc25..5661748714e88 100644
--- a/app/code/Magento/Eav/composer.json
+++ b/app/code/Magento/Eav/composer.json
@@ -5,7 +5,7 @@
"sort-packages": true
},
"require": {
- "php": "~7.1.3||~7.2.0",
+ "php": "~7.1.3||~7.2.0||~7.3.0",
"magento/framework": "*",
"magento/module-backend": "*",
"magento/module-catalog": "*",
diff --git a/app/code/Magento/Eav/i18n/en_US.csv b/app/code/Magento/Eav/i18n/en_US.csv
index 73f8b359d1c1b..fa4b026501d1b 100644
--- a/app/code/Magento/Eav/i18n/en_US.csv
+++ b/app/code/Magento/Eav/i18n/en_US.csv
@@ -143,3 +143,4 @@ hello,hello
"The value of attribute not valid","The value of attribute not valid"
"EAV types and attributes","EAV types and attributes"
"Entity types declaration cache","Entity types declaration cache"
+"Admin store attribute option label ""%1"" is already exists.","Admin store attribute option label ""%1"" is already exists."
diff --git a/app/code/Magento/Eav/view/adminhtml/templates/attribute/edit/js.phtml b/app/code/Magento/Eav/view/adminhtml/templates/attribute/edit/js.phtml
index b7b29d7cdcd10..ff74eb50f722f 100644
--- a/app/code/Magento/Eav/view/adminhtml/templates/attribute/edit/js.phtml
+++ b/app/code/Magento/Eav/view/adminhtml/templates/attribute/edit/js.phtml
@@ -5,15 +5,13 @@
*/
/** @var \Magento\Eav\Block\Adminhtml\Attribute\Edit\Js $block */
-
-// @codingStandardsIgnoreFile
?>
-
\ No newline at end of file
+
diff --git a/app/code/Magento/Integration/Block/Adminhtml/Integration/Edit/Tab/Info.php b/app/code/Magento/Integration/Block/Adminhtml/Integration/Edit/Tab/Info.php
index d8684e635b943..4042c2ebde87d 100644
--- a/app/code/Magento/Integration/Block/Adminhtml/Integration/Edit/Tab/Info.php
+++ b/app/code/Magento/Integration/Block/Adminhtml/Integration/Edit/Tab/Info.php
@@ -146,6 +146,7 @@ protected function _addGeneralFieldset($form, $integrationData)
'label' => __('Callback URL'),
'name' => self::DATA_ENDPOINT,
'disabled' => $disabled,
+ 'class' => 'validate-url',
// @codingStandardsIgnoreStart
'note' => __(
'Enter URL where Oauth credentials can be sent when using Oauth for token exchange. We strongly recommend using https://.'
diff --git a/app/code/Magento/Integration/Test/Mftf/ActionGroup/AdminFillIntegrationFormActionGroup.xml b/app/code/Magento/Integration/Test/Mftf/ActionGroup/AdminFillIntegrationFormActionGroup.xml
new file mode 100644
index 0000000000000..85e4efb495955
--- /dev/null
+++ b/app/code/Magento/Integration/Test/Mftf/ActionGroup/AdminFillIntegrationFormActionGroup.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Integration/Test/Mftf/ActionGroup/AdminOpenNewIntegrationPageActionGroup.xml b/app/code/Magento/Integration/Test/Mftf/ActionGroup/AdminOpenNewIntegrationPageActionGroup.xml
new file mode 100644
index 0000000000000..0ddb0664b9798
--- /dev/null
+++ b/app/code/Magento/Integration/Test/Mftf/ActionGroup/AdminOpenNewIntegrationPageActionGroup.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Integration/Test/Mftf/ActionGroup/AdminSaveIntegrationActionGroup.xml b/app/code/Magento/Integration/Test/Mftf/ActionGroup/AdminSaveIntegrationActionGroup.xml
new file mode 100644
index 0000000000000..e6e3f3397d4b6
--- /dev/null
+++ b/app/code/Magento/Integration/Test/Mftf/ActionGroup/AdminSaveIntegrationActionGroup.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Integration/Test/Mftf/Data/IntegrationData.xml b/app/code/Magento/Integration/Test/Mftf/Data/IntegrationData.xml
new file mode 100644
index 0000000000000..21fefcdf0b26c
--- /dev/null
+++ b/app/code/Magento/Integration/Test/Mftf/Data/IntegrationData.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+ Integration
+ All
+ []
+
+
diff --git a/app/code/Magento/Integration/Test/Mftf/Page/AdminNewIntegrationPage.xml b/app/code/Magento/Integration/Test/Mftf/Page/AdminNewIntegrationPage.xml
new file mode 100644
index 0000000000000..a3511887b2cdf
--- /dev/null
+++ b/app/code/Magento/Integration/Test/Mftf/Page/AdminNewIntegrationPage.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Integration/Test/Mftf/Section/AdminNewIntegrationFormSection.xml b/app/code/Magento/Integration/Test/Mftf/Section/AdminNewIntegrationFormSection.xml
new file mode 100644
index 0000000000000..edb9e5dd5eb51
--- /dev/null
+++ b/app/code/Magento/Integration/Test/Mftf/Section/AdminNewIntegrationFormSection.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
diff --git a/app/code/Magento/Integration/composer.json b/app/code/Magento/Integration/composer.json
index 6a63854775ac3..5ecd792a5e7bf 100644
--- a/app/code/Magento/Integration/composer.json
+++ b/app/code/Magento/Integration/composer.json
@@ -5,7 +5,7 @@
"sort-packages": true
},
"require": {
- "php": "~7.1.3||~7.2.0",
+ "php": "~7.1.3||~7.2.0||~7.3.0",
"magento/framework": "*",
"magento/module-authorization": "*",
"magento/module-backend": "*",
diff --git a/app/code/Magento/Integration/etc/adminhtml/system.xml b/app/code/Magento/Integration/etc/adminhtml/system.xml
index fe80fe105493a..ddaae76700255 100644
--- a/app/code/Magento/Integration/etc/adminhtml/system.xml
+++ b/app/code/Magento/Integration/etc/adminhtml/system.xml
@@ -16,10 +16,12 @@
Customer Token Lifetime (hours)
We will disable this feature if the value is empty.
+ required-entry validate-zero-or-greater validate-number
Admin Token Lifetime (hours)
We will disable this feature if the value is empty.
+ required-entry validate-zero-or-greater validate-number
@@ -27,10 +29,12 @@
Cleanup Probability
Integer. Launch cleanup in X OAuth requests. 0 (not recommended) - to disable cleanup
+ required-entry validate-zero-or-greater validate-digits
Expiration Period
Cleanup entries older than X minutes.
+ required-entry validate-zero-or-greater validate-number
@@ -38,14 +42,17 @@
Expiration Period
Consumer key/secret will expire if not used within X seconds after Oauth token exchange starts.
+ required-entry validate-zero-or-greater validate-number
OAuth consumer credentials HTTP Post maxredirects
Number of maximum redirects for OAuth consumer credentials Post request.
+ required-entry validate-zero-or-greater validate-digits
OAuth consumer credentials HTTP Post timeout
Timeout for OAuth consumer credentials Post request within X seconds.
+ required-entry validate-zero-or-greater validate-number
@@ -53,13 +60,14 @@
Maximum Login Failures to Lock Out Account
Maximum Number of authentication failures to lock out account.
+ required-entry validate-zero-or-greater validate-digits
Lockout Time (seconds)
Period of time in seconds after which account will be unlocked.
+ required-entry validate-zero-or-greater validate-number
-
diff --git a/app/code/Magento/Integration/view/adminhtml/layout/adminhtml_integration_grid_block.xml b/app/code/Magento/Integration/view/adminhtml/layout/adminhtml_integration_grid_block.xml
index 43b67d6904f1a..2978fd37294cb 100644
--- a/app/code/Magento/Integration/view/adminhtml/layout/adminhtml_integration_grid_block.xml
+++ b/app/code/Magento/Integration/view/adminhtml/layout/adminhtml_integration_grid_block.xml
@@ -49,6 +49,7 @@
+ Activate
Magento\Integration\Block\Adminhtml\Widget\Grid\Column\Renderer\Link\Activate
activate
activate
@@ -58,6 +59,7 @@
+ Edit
Magento\Integration\Block\Adminhtml\Widget\Grid\Column\Renderer\Button\Edit
edit
edit
@@ -67,6 +69,7 @@
+ Delete
Magento\Integration\Block\Adminhtml\Widget\Grid\Column\Renderer\Button\Delete
action delete
delete
diff --git a/app/code/Magento/Integration/view/adminhtml/web/js/integration.js b/app/code/Magento/Integration/view/adminhtml/web/js/integration.js
index a2acc40c6b86e..d5830a442987b 100644
--- a/app/code/Magento/Integration/view/adminhtml/web/js/integration.js
+++ b/app/code/Magento/Integration/view/adminhtml/web/js/integration.js
@@ -204,7 +204,7 @@ define([
clearInterval(IdentityLogin.checker);
$('body').trigger('processStart');
//Check for window closed
- window.location.reload();
+ window.location.href = url.grid;
IdentityLogin.jqInfoDialog.modal('closeModal');
}
} catch (e) {
diff --git a/app/code/Magento/LayeredNavigation/Block/Navigation.php b/app/code/Magento/LayeredNavigation/Block/Navigation.php
index 4173469da8e42..e394fe7f6cf5b 100644
--- a/app/code/Magento/LayeredNavigation/Block/Navigation.php
+++ b/app/code/Magento/LayeredNavigation/Block/Navigation.php
@@ -3,22 +3,25 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
-
-/**
- * Catalog layered navigation view block
- *
- * @author Magento Core Team
- */
namespace Magento\LayeredNavigation\Block;
use Magento\Framework\View\Element\Template;
+use Magento\Catalog\Model\ResourceModel\Product\Collection;
+use Magento\Catalog\Block\Product\ProductList\Toolbar;
/**
+ * Catalog layered navigation view block
+ *
* @api
* @since 100.0.2
*/
class Navigation extends \Magento\Framework\View\Element\Template
{
+ /**
+ * Product listing toolbar block name
+ */
+ private const PRODUCT_LISTING_TOOLBAR_BLOCK = 'product_list_toolbar';
+
/**
* Catalog layer
*
@@ -67,9 +70,20 @@ protected function _prepareLayout()
$filter->apply($this->getRequest());
}
$this->getLayer()->apply();
+
return parent::_prepareLayout();
}
+ /**
+ * @inheritdoc
+ */
+ protected function _beforeToHtml()
+ {
+ $this->configureToolbarBlock();
+
+ return parent::_beforeToHtml();
+ }
+
/**
* Get layer object
*
@@ -107,7 +121,8 @@ public function getFilters()
*/
public function canShowBlock()
{
- return $this->visibilityFlag->isEnabled($this->getLayer(), $this->getFilters());
+ return $this->getLayer()->getCurrentCategory()->getDisplayMode() !== \Magento\Catalog\Model\Category::DM_PAGE
+ && $this->visibilityFlag->isEnabled($this->getLayer(), $this->getFilters());
}
/**
@@ -119,4 +134,20 @@ public function getClearUrl()
{
return $this->getChildBlock('state')->getClearUrl();
}
+
+ /**
+ * Configures the Toolbar block
+ *
+ * @return void
+ */
+ private function configureToolbarBlock(): void
+ {
+ /** @var Toolbar $toolbarBlock */
+ $toolbarBlock = $this->getLayout()->getBlock(self::PRODUCT_LISTING_TOOLBAR_BLOCK);
+ if ($toolbarBlock) {
+ /** @var Collection $collection */
+ $collection = $this->getLayer()->getProductCollection();
+ $toolbarBlock->setCollection($collection);
+ }
+ }
}
diff --git a/app/code/Magento/LayeredNavigation/Test/Mftf/Section/LayeredNavigationSection.xml b/app/code/Magento/LayeredNavigation/Test/Mftf/Section/LayeredNavigationSection.xml
index b44ee9ddbd734..1e4137beacd88 100644
--- a/app/code/Magento/LayeredNavigation/Test/Mftf/Section/LayeredNavigationSection.xml
+++ b/app/code/Magento/LayeredNavigation/Test/Mftf/Section/LayeredNavigationSection.xml
@@ -10,6 +10,7 @@
xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd">
+
diff --git a/app/code/Magento/LayeredNavigation/Test/Mftf/Test/AdminSpecifyLayerNavigationConfigurationTest.xml b/app/code/Magento/LayeredNavigation/Test/Mftf/Test/AdminSpecifyLayerNavigationConfigurationTest.xml
index 0a09f17d66f62..fd8763891af93 100644
--- a/app/code/Magento/LayeredNavigation/Test/Mftf/Test/AdminSpecifyLayerNavigationConfigurationTest.xml
+++ b/app/code/Magento/LayeredNavigation/Test/Mftf/Test/AdminSpecifyLayerNavigationConfigurationTest.xml
@@ -15,31 +15,40 @@
-
-
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/LayeredNavigation/Test/Unit/Block/NavigationTest.php b/app/code/Magento/LayeredNavigation/Test/Unit/Block/NavigationTest.php
index e37e58b14f027..f0243784dd618 100644
--- a/app/code/Magento/LayeredNavigation/Test/Unit/Block/NavigationTest.php
+++ b/app/code/Magento/LayeredNavigation/Test/Unit/Block/NavigationTest.php
@@ -6,6 +6,8 @@
namespace Magento\LayeredNavigation\Test\Unit\Block;
+use Magento\Catalog\Model\Category;
+
class NavigationTest extends \PHPUnit\Framework\TestCase
{
/**
@@ -98,9 +100,61 @@ public function testCanShowBlock()
->method('isEnabled')
->with($this->catalogLayerMock, $filters)
->will($this->returnValue($enabled));
+
+ $category = $this->createMock(Category::class);
+ $this->catalogLayerMock->expects($this->atLeastOnce())->method('getCurrentCategory')->willReturn($category);
+ $category->expects($this->once())->method('getDisplayMode')->willReturn(Category::DM_PRODUCT);
+
$this->assertEquals($enabled, $this->model->canShowBlock());
}
+ /**
+ * Test canShowBlock() with different category display types.
+ *
+ * @param string $mode
+ * @param bool $result
+ *
+ * @dataProvider canShowBlockDataProvider
+ */
+ public function testCanShowBlockWithDifferentDisplayModes(string $mode, bool $result)
+ {
+ $filters = ['To' => 'be', 'or' => 'not', 'to' => 'be'];
+
+ $this->filterListMock->expects($this->atLeastOnce())->method('getFilters')
+ ->with($this->catalogLayerMock)
+ ->will($this->returnValue($filters));
+ $this->assertEquals($filters, $this->model->getFilters());
+
+ $this->visibilityFlagMock
+ ->expects($this->any())
+ ->method('isEnabled')
+ ->with($this->catalogLayerMock, $filters)
+ ->will($this->returnValue(true));
+
+ $category = $this->createMock(Category::class);
+ $this->catalogLayerMock->expects($this->atLeastOnce())->method('getCurrentCategory')->willReturn($category);
+ $category->expects($this->once())->method('getDisplayMode')->willReturn($mode);
+ $this->assertEquals($result, $this->model->canShowBlock());
+ }
+
+ public function canShowBlockDataProvider()
+ {
+ return [
+ [
+ Category::DM_PRODUCT,
+ true,
+ ],
+ [
+ Category::DM_PAGE,
+ false,
+ ],
+ [
+ Category::DM_MIXED,
+ true,
+ ],
+ ];
+ }
+
public function testGetClearUrl()
{
$this->filterListMock->expects($this->any())->method('getFilters')->will($this->returnValue([]));
diff --git a/app/code/Magento/LayeredNavigation/composer.json b/app/code/Magento/LayeredNavigation/composer.json
index 6d322fc3ab50c..67712fac2a239 100644
--- a/app/code/Magento/LayeredNavigation/composer.json
+++ b/app/code/Magento/LayeredNavigation/composer.json
@@ -5,7 +5,7 @@
"sort-packages": true
},
"require": {
- "php": "~7.1.3||~7.2.0",
+ "php": "~7.1.3||~7.2.0||~7.3.0",
"magento/framework": "*",
"magento/module-catalog": "*",
"magento/module-config": "*"
diff --git a/app/code/Magento/Marketplace/composer.json b/app/code/Magento/Marketplace/composer.json
index b52d507f825fc..71b7a8156cf60 100644
--- a/app/code/Magento/Marketplace/composer.json
+++ b/app/code/Magento/Marketplace/composer.json
@@ -5,7 +5,7 @@
"sort-packages": true
},
"require": {
- "php": "~7.1.3||~7.2.0",
+ "php": "~7.1.3||~7.2.0||~7.3.0",
"magento/framework": "*",
"magento/module-backend": "*"
},
diff --git a/app/code/Magento/MediaStorage/Console/Command/ImagesResizeCommand.php b/app/code/Magento/MediaStorage/Console/Command/ImagesResizeCommand.php
index 51eba1facb90c..ba12d60cb0bc8 100644
--- a/app/code/Magento/MediaStorage/Console/Command/ImagesResizeCommand.php
+++ b/app/code/Magento/MediaStorage/Console/Command/ImagesResizeCommand.php
@@ -76,6 +76,7 @@ protected function configure()
protected function execute(InputInterface $input, OutputInterface $output)
{
try {
+ $errors = [];
$this->appState->setAreaCode(Area::AREA_GLOBAL);
$generator = $this->resize->resizeFromThemes();
@@ -95,7 +96,15 @@ protected function execute(InputInterface $input, OutputInterface $output)
}
while ($generator->valid()) {
- $progress->setMessage($generator->key());
+ $resizeInfo = $generator->key();
+ $error = $resizeInfo['error'];
+ $filename = $resizeInfo['filename'];
+
+ if ($error !== '') {
+ $errors[$filename] = $error;
+ }
+
+ $progress->setMessage($filename);
$progress->advance();
$generator->next();
}
@@ -106,7 +115,14 @@ protected function execute(InputInterface $input, OutputInterface $output)
}
$output->write(PHP_EOL);
- $output->writeln("Product images resized successfully ");
+ if (count($errors)) {
+ $output->writeln("Product images resized with errors: ");
+ foreach ($errors as $error) {
+ $output->writeln("{$error} ");
+ }
+ } else {
+ $output->writeln("Product images resized successfully ");
+ }
return \Magento\Framework\Console\Cli::RETURN_SUCCESS;
}
diff --git a/app/code/Magento/MediaStorage/Model/ResourceModel/File/Storage/Database.php b/app/code/Magento/MediaStorage/Model/ResourceModel/File/Storage/Database.php
index 1e88e081d6ece..ae896395b8eb5 100644
--- a/app/code/Magento/MediaStorage/Model/ResourceModel/File/Storage/Database.php
+++ b/app/code/Magento/MediaStorage/Model/ResourceModel/File/Storage/Database.php
@@ -341,10 +341,14 @@ public function deleteFolder($folderName = '')
return;
}
- $likeExpression = $this->_resourceHelper->addLikeEscape($folderName . '/', ['position' => 'start']);
$this->getConnection()->delete(
$this->getMainTable(),
- new \Zend_Db_Expr('filename LIKE ' . $likeExpression)
+ new \Zend_Db_Expr(
+ 'directory LIKE ' .
+ $this->_resourceHelper->addLikeEscape($folderName . '/', ['position' => 'start'])
+ . ' ' . \Magento\Framework\DB\Select::SQL_OR . ' ' .
+ $this->getConnection()->prepareSqlCondition('directory', ['seq' => $folderName])
+ )
);
}
diff --git a/app/code/Magento/MediaStorage/Service/ImageResize.php b/app/code/Magento/MediaStorage/Service/ImageResize.php
index d3f4fc01e387b..63353b2536a5a 100644
--- a/app/code/Magento/MediaStorage/Service/ImageResize.php
+++ b/app/code/Magento/MediaStorage/Service/ImageResize.php
@@ -7,10 +7,12 @@
namespace Magento\MediaStorage\Service;
+use Generator;
use Magento\Catalog\Helper\Image as ImageHelper;
use Magento\Catalog\Model\Product\Image\ParamsBuilder;
use Magento\Catalog\Model\View\Asset\ImageFactory as AssertImageFactory;
use Magento\Framework\App\Area;
+use Magento\Framework\App\ObjectManager;
use Magento\Framework\Exception\NotFoundException;
use Magento\Framework\Filesystem;
use Magento\Framework\Image;
@@ -19,9 +21,12 @@
use Magento\Framework\App\State;
use Magento\Framework\View\ConfigInterface as ViewConfig;
use \Magento\Catalog\Model\ResourceModel\Product\Image as ProductImage;
+use Magento\Store\Model\StoreManagerInterface;
use Magento\Theme\Model\Config\Customization as ThemeCustomizationConfig;
use Magento\Theme\Model\ResourceModel\Theme\Collection;
use Magento\Framework\App\Filesystem\DirectoryList;
+use Magento\MediaStorage\Helper\File\Storage\Database;
+use Magento\Theme\Model\Theme;
/**
* Image resize service.
@@ -85,6 +90,15 @@ class ImageResize
*/
private $filesystem;
+ /**
+ * @var Database
+ */
+ private $fileStorageDatabase;
+ /**
+ * @var StoreManagerInterface
+ */
+ private $storeManager;
+
/**
* @param State $appState
* @param MediaConfig $imageConfig
@@ -96,6 +110,9 @@ class ImageResize
* @param ThemeCustomizationConfig $themeCustomizationConfig
* @param Collection $themeCollection
* @param Filesystem $filesystem
+ * @param Database $fileStorageDatabase
+ * @param StoreManagerInterface $storeManager
+ * @throws \Magento\Framework\Exception\FileSystemException
* @internal param ProductImage $gallery
* @SuppressWarnings(PHPMD.ExcessiveParameterList)
*/
@@ -109,7 +126,9 @@ public function __construct(
AssertImageFactory $assertImageFactory,
ThemeCustomizationConfig $themeCustomizationConfig,
Collection $themeCollection,
- Filesystem $filesystem
+ Filesystem $filesystem,
+ Database $fileStorageDatabase = null,
+ StoreManagerInterface $storeManager = null
) {
$this->appState = $appState;
$this->imageConfig = $imageConfig;
@@ -122,6 +141,9 @@ public function __construct(
$this->themeCollection = $themeCollection;
$this->mediaDirectory = $filesystem->getDirectoryWrite(DirectoryList::MEDIA);
$this->filesystem = $filesystem;
+ $this->fileStorageDatabase = $fileStorageDatabase ?:
+ ObjectManager::getInstance()->get(Database::class);
+ $this->storeManager = $storeManager ?? ObjectManager::getInstance()->get(StoreManagerInterface::class);
}
/**
@@ -132,9 +154,15 @@ public function __construct(
*/
public function resizeFromImageName(string $originalImageName)
{
- $originalImagePath = $this->mediaDirectory->getAbsolutePath(
- $this->imageConfig->getMediaPath($originalImageName)
- );
+ $mediastoragefilename = $this->imageConfig->getMediaPath($originalImageName);
+ $originalImagePath = $this->mediaDirectory->getAbsolutePath($mediastoragefilename);
+
+ if ($this->fileStorageDatabase->checkDbUsage() &&
+ !$this->mediaDirectory->isFile($mediastoragefilename)
+ ) {
+ $this->fileStorageDatabase->saveFileToFilesystem($mediastoragefilename);
+ }
+
if (!$this->mediaDirectory->isFile($originalImagePath)) {
throw new NotFoundException(__('Cannot resize image "%1" - original image not found', $originalImagePath));
}
@@ -147,10 +175,10 @@ public function resizeFromImageName(string $originalImageName)
* Create resized images of different sizes from themes.
*
* @param array|null $themes
- * @return \Generator
+ * @return Generator
* @throws NotFoundException
*/
- public function resizeFromThemes(array $themes = null): \Generator
+ public function resizeFromThemes(array $themes = null): Generator
{
$count = $this->productImage->getCountUsedProductImages();
if (!$count) {
@@ -161,14 +189,24 @@ public function resizeFromThemes(array $themes = null): \Generator
$viewImages = $this->getViewImages($themes ?? $this->getThemesInUse());
foreach ($productImages as $image) {
+ $error = '';
$originalImageName = $image['filepath'];
- $originalImagePath = $this->mediaDirectory->getAbsolutePath(
- $this->imageConfig->getMediaPath($originalImageName)
- );
- foreach ($viewImages as $viewImage) {
- $this->resize($viewImage, $originalImagePath, $originalImageName);
+
+ $mediastoragefilename = $this->imageConfig->getMediaPath($originalImageName);
+ $originalImagePath = $this->mediaDirectory->getAbsolutePath($mediastoragefilename);
+
+ if ($this->fileStorageDatabase->checkDbUsage()) {
+ $this->fileStorageDatabase->saveFileToFilesystem($mediastoragefilename);
+ }
+ if ($this->mediaDirectory->isFile($originalImagePath)) {
+ foreach ($viewImages as $viewImage) {
+ $this->resize($viewImage, $originalImagePath, $originalImageName);
+ }
+ } else {
+ $error = __('Cannot resize image "%1" - original image not found', $originalImagePath);
}
- yield $originalImageName => $count;
+
+ yield ['filename' => $originalImageName, 'error' => $error] => $count;
}
}
@@ -200,7 +238,8 @@ private function getThemesInUse(): array
private function getViewImages(array $themes): array
{
$viewImages = [];
- /** @var \Magento\Theme\Model\Theme $theme */
+ $stores = $this->storeManager->getStores(true);
+ /** @var Theme $theme */
foreach ($themes as $theme) {
$config = $this->viewConfig->getViewConfig(
[
@@ -210,9 +249,12 @@ private function getViewImages(array $themes): array
);
$images = $config->getMediaEntities('Magento_Catalog', ImageHelper::MEDIA_TYPE_CONFIG_NODE);
foreach ($images as $imageId => $imageData) {
- $uniqIndex = $this->getUniqueImageIndex($imageData);
- $imageData['id'] = $imageId;
- $viewImages[$uniqIndex] = $imageData;
+ foreach ($stores as $store) {
+ $data = $this->paramsBuilder->build($imageData, (int) $store->getId());
+ $uniqIndex = $this->getUniqueImageIndex($data);
+ $data['id'] = $imageId;
+ $viewImages[$uniqIndex] = $data;
+ }
}
}
return $viewImages;
@@ -254,13 +296,13 @@ private function makeImage(string $originalImagePath, array $imageParams): Image
/**
* Resize image.
*
- * @param array $viewImage
+ * @param array $imageParams
* @param string $originalImagePath
* @param string $originalImageName
*/
- private function resize(array $viewImage, string $originalImagePath, string $originalImageName)
+ private function resize(array $imageParams, string $originalImagePath, string $originalImageName)
{
- $imageParams = $this->paramsBuilder->build($viewImage);
+ unset($imageParams['id']);
$image = $this->makeImage($originalImagePath, $imageParams);
$imageAsset = $this->assertImageFactory->create(
[
@@ -293,6 +335,11 @@ private function resize(array $viewImage, string $originalImagePath, string $ori
$image->resize($imageParams['image_width'], $imageParams['image_height']);
}
$image->save($imageAsset->getPath());
+
+ if ($this->fileStorageDatabase->checkDbUsage()) {
+ $mediastoragefilename = $this->mediaDirectory->getRelativePath($imageAsset->getPath());
+ $this->fileStorageDatabase->saveFile($mediastoragefilename);
+ }
}
/**
diff --git a/app/code/Magento/MediaStorage/Test/Unit/Service/ImageResizeTest.php b/app/code/Magento/MediaStorage/Test/Unit/Service/ImageResizeTest.php
new file mode 100644
index 0000000000000..f0e1efa7806e4
--- /dev/null
+++ b/app/code/Magento/MediaStorage/Test/Unit/Service/ImageResizeTest.php
@@ -0,0 +1,321 @@
+testfilename = "image.jpg";
+ $this->testfilepath = "/image.jpg";
+
+ $this->appStateMock = $this->createMock(State::class);
+ $this->imageConfigMock = $this->createMock(MediaConfig::class);
+ $this->productImageMock = $this->createMock(ProductImage::class);
+ $this->imageMock = $this->createMock(Image::class);
+ $this->imageFactoryMock = $this->createMock(ImageFactory::class);
+ $this->paramsBuilderMock = $this->createMock(ParamsBuilder::class);
+ $this->viewMock = $this->createMock(View::class);
+ $this->viewConfigMock = $this->createMock(ViewConfig::class);
+ $this->assetImageMock = $this->createMock(AssetImage::class);
+ $this->assetImageFactoryMock = $this->createMock(AssetImageFactory::class);
+ $this->themeCustomizationConfigMock = $this->createMock(ThemeCustomizationConfig::class);
+ $this->themeCollectionMock = $this->createMock(Collection::class);
+ $this->filesystemMock = $this->createMock(Filesystem::class);
+ $this->databaseMock = $this->createMock(Database::class);
+ $this->storeManager = $this->getMockForAbstractClass(StoreManagerInterface::class);
+
+ $this->mediaDirectoryMock = $this->getMockBuilder(Filesystem::class)
+ ->disableOriginalConstructor()
+ ->setMethods(['getAbsolutePath','isFile','getRelativePath'])
+ ->getMock();
+
+ $this->filesystemMock->expects($this->any())
+ ->method('getDirectoryWrite')
+ ->with(DirectoryList::MEDIA)
+ ->willReturn($this->mediaDirectoryMock);
+
+ $this->imageFactoryMock->expects($this->any())
+ ->method('create')
+ ->willReturn($this->imageMock);
+ $this->assetImageMock->expects($this->any())
+ ->method('getPath')
+ ->will($this->returnValue($this->testfilepath));
+ $this->assetImageFactoryMock->expects($this->any())
+ ->method('create')
+ ->willReturn($this->assetImageMock);
+
+ $this->paramsBuilderMock->expects($this->any())
+ ->method('build')
+ ->willReturn(
+ [
+ 'keep_aspect_ratio' => null,
+ 'keep_frame' => null,
+ 'keep_transparency' => null,
+ 'constrain_only' => null,
+ 'background' => null,
+ 'quality' => null,
+ 'image_width' => null,
+ 'image_height' => null
+ ]
+ );
+
+ $this->imageConfigMock->expects($this->any())
+ ->method('getMediaPath')
+ ->with($this->testfilename)
+ ->willReturn($this->testfilepath);
+ $this->mediaDirectoryMock->expects($this->any())
+ ->method('getAbsolutePath')
+ ->with($this->testfilepath)
+ ->willReturn($this->testfilepath);
+ $this->mediaDirectoryMock->expects($this->any())
+ ->method('getRelativePath')
+ ->with($this->testfilepath)
+ ->willReturn($this->testfilepath);
+
+ $this->viewMock->expects($this->any())
+ ->method('getMediaEntities')
+ ->willReturn(
+ ['product_small_image' =>
+ [
+ 'type' => 'small_image',
+ 'width' => 75,
+ 'height' => 75
+ ]
+ ]
+ );
+ $this->viewConfigMock->expects($this->any())
+ ->method('getViewConfig')
+ ->willReturn($this->viewMock);
+
+ $store = $this->getMockForAbstractClass(\Magento\Store\Api\Data\StoreInterface::class);
+ $store
+ ->expects($this->any())
+ ->method('getId')
+ ->willReturn(1);
+ $this->storeManager
+ ->expects($this->any())
+ ->method('getStores')
+ ->willReturn([$store]);
+
+ $this->service = new \Magento\MediaStorage\Service\ImageResize(
+ $this->appStateMock,
+ $this->imageConfigMock,
+ $this->productImageMock,
+ $this->imageFactoryMock,
+ $this->paramsBuilderMock,
+ $this->viewConfigMock,
+ $this->assetImageFactoryMock,
+ $this->themeCustomizationConfigMock,
+ $this->themeCollectionMock,
+ $this->filesystemMock,
+ $this->databaseMock,
+ $this->storeManager
+ );
+ }
+
+ protected function tearDown()
+ {
+ unset($this->service);
+ }
+
+ public function testResizeFromThemesMediaStorageDatabase()
+ {
+ $this->databaseMock->expects($this->any())
+ ->method('checkDbUsage')
+ ->will($this->returnValue(true));
+
+ $this->productImageMock->expects($this->any())
+ ->method('getCountUsedProductImages')
+ ->willReturn(1);
+ $this->productImageMock->expects($this->any())
+ ->method('getUsedProductImages')
+ ->will(
+ $this->returnCallback(
+ function () {
+ $data = [[ 'filepath' => $this->testfilename ]];
+ foreach ($data as $e) {
+ yield $e;
+ }
+ }
+ )
+ );
+
+ $this->mediaDirectoryMock->expects($this->any())
+ ->method('isFile')
+ ->with($this->testfilepath)
+ ->will($this->returnValue(true));
+
+ $this->databaseMock->expects($this->once())
+ ->method('saveFileToFilesystem')
+ ->with($this->testfilepath);
+ $this->databaseMock->expects($this->once())
+ ->method('saveFile')
+ ->with($this->testfilepath);
+
+ $generator = $this->service->resizeFromThemes(['test-theme']);
+ while ($generator->valid()) {
+ $generator->next();
+ }
+ }
+
+ public function testResizeFromImageNameMediaStorageDatabase()
+ {
+ $this->databaseMock->expects($this->any())
+ ->method('checkDbUsage')
+ ->will($this->returnValue(true));
+
+ $this->mediaDirectoryMock->expects($this->any())
+ ->method('isFile')
+ ->with($this->testfilepath)
+ ->willReturnOnConsecutiveCalls(
+ $this->returnValue(false),
+ $this->returnValue(true)
+ );
+
+ $this->themeCollectionMock->expects($this->any())
+ ->method('loadRegisteredThemes')
+ ->willReturn(
+ [ new DataObject(['id' => '0']) ]
+ );
+ $this->themeCustomizationConfigMock->expects($this->any())
+ ->method('getStoresByThemes')
+ ->willReturn(
+ ['0' => []]
+ );
+
+ $this->databaseMock->expects($this->once())
+ ->method('saveFileToFilesystem')
+ ->with($this->testfilepath);
+ $this->databaseMock->expects($this->once())
+ ->method('saveFile')
+ ->with($this->testfilepath);
+
+ $this->service->resizeFromImageName($this->testfilename);
+ }
+}
diff --git a/app/code/Magento/MediaStorage/composer.json b/app/code/Magento/MediaStorage/composer.json
index 7d27c88b3dcb2..95c48f3fdc581 100644
--- a/app/code/Magento/MediaStorage/composer.json
+++ b/app/code/Magento/MediaStorage/composer.json
@@ -5,7 +5,7 @@
"sort-packages": true
},
"require": {
- "php": "~7.1.3||~7.2.0",
+ "php": "~7.1.3||~7.2.0||~7.3.0",
"magento/framework": "*",
"magento/module-backend": "*",
"magento/module-config": "*",
diff --git a/app/code/Magento/MediaStorage/etc/adminhtml/system.xml b/app/code/Magento/MediaStorage/etc/adminhtml/system.xml
index d7244a5d4fd01..0f6e7f93aea11 100644
--- a/app/code/Magento/MediaStorage/etc/adminhtml/system.xml
+++ b/app/code/Magento/MediaStorage/etc/adminhtml/system.xml
@@ -10,11 +10,11 @@
Storage Configuration for Media
-
+
Media Storage
Magento\MediaStorage\Model\Config\Source\Storage\Media\Storage
-
+
Select Media Database
Magento\MediaStorage\Model\Config\Source\Storage\Media\Database
Magento\MediaStorage\Model\Config\Backend\Storage\Media\Database
@@ -24,7 +24,7 @@
Magento\MediaStorage\Block\System\Config\System\Storage\Media\Synchronize
- After selecting a new media storage location, press the Synchronize button to transfer all media to that location. Media will not be available in the new location until the synchronization process is complete.
+ After selecting a new media storage location, press the Synchronize button to transfer all media to that location and then "Save Config". Media will not be available in the new location until the synchronization process is complete.
Environment Update Time
diff --git a/app/code/Magento/MessageQueue/Console/StartConsumerCommand.php b/app/code/Magento/MessageQueue/Console/StartConsumerCommand.php
index 571b725e7335c..fc2207dcd7c86 100644
--- a/app/code/Magento/MessageQueue/Console/StartConsumerCommand.php
+++ b/app/code/Magento/MessageQueue/Console/StartConsumerCommand.php
@@ -11,7 +11,7 @@
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Magento\Framework\MessageQueue\ConsumerFactory;
-use Magento\MessageQueue\Model\Cron\ConsumersRunner\PidConsumerManager;
+use Magento\Framework\Lock\LockManagerInterface;
/**
* Command for starting MessageQueue consumers.
@@ -22,6 +22,7 @@ class StartConsumerCommand extends Command
const OPTION_NUMBER_OF_MESSAGES = 'max-messages';
const OPTION_BATCH_SIZE = 'batch-size';
const OPTION_AREACODE = 'area-code';
+ const OPTION_SINGLE_THREAD = 'single-thread';
const PID_FILE_PATH = 'pid-file-path';
const COMMAND_QUEUE_CONSUMERS_START = 'queue:consumers:start';
@@ -36,9 +37,9 @@ class StartConsumerCommand extends Command
private $appState;
/**
- * @var PidConsumerManager
+ * @var LockManagerInterface
*/
- private $pidConsumerManager;
+ private $lockManager;
/**
* StartConsumerCommand constructor.
@@ -47,23 +48,23 @@ class StartConsumerCommand extends Command
* @param \Magento\Framework\App\State $appState
* @param ConsumerFactory $consumerFactory
* @param string $name
- * @param PidConsumerManager $pidConsumerManager
+ * @param LockManagerInterface $lockManager
*/
public function __construct(
\Magento\Framework\App\State $appState,
ConsumerFactory $consumerFactory,
$name = null,
- PidConsumerManager $pidConsumerManager = null
+ LockManagerInterface $lockManager = null
) {
$this->appState = $appState;
$this->consumerFactory = $consumerFactory;
- $this->pidConsumerManager = $pidConsumerManager ?: \Magento\Framework\App\ObjectManager::getInstance()
- ->get(PidConsumerManager::class);
+ $this->lockManager = $lockManager ?: \Magento\Framework\App\ObjectManager::getInstance()
+ ->get(LockManagerInterface::class);
parent::__construct($name);
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
@@ -71,30 +72,36 @@ protected function execute(InputInterface $input, OutputInterface $output)
$numberOfMessages = $input->getOption(self::OPTION_NUMBER_OF_MESSAGES);
$batchSize = (int)$input->getOption(self::OPTION_BATCH_SIZE);
$areaCode = $input->getOption(self::OPTION_AREACODE);
- $pidFilePath = $input->getOption(self::PID_FILE_PATH);
- if ($pidFilePath && $this->pidConsumerManager->isRun($pidFilePath)) {
- $output->writeln('Consumer with the same PID is running ');
- return \Magento\Framework\Console\Cli::RETURN_FAILURE;
+ if ($input->getOption(self::PID_FILE_PATH)) {
+ $input->setOption(self::OPTION_SINGLE_THREAD, true);
}
- if ($pidFilePath) {
- $this->pidConsumerManager->savePid($pidFilePath);
+ $singleThread = $input->getOption(self::OPTION_SINGLE_THREAD);
+
+ if ($singleThread && $this->lockManager->isLocked(md5($consumerName))) { //phpcs:ignore
+ $output->writeln('Consumer with the same name is running ');
+ return \Magento\Framework\Console\Cli::RETURN_FAILURE;
}
- if ($areaCode !== null) {
- $this->appState->setAreaCode($areaCode);
- } else {
- $this->appState->setAreaCode('global');
+ if ($singleThread) {
+ $this->lockManager->lock(md5($consumerName)); //phpcs:ignore
}
+ $this->appState->setAreaCode($areaCode ?? 'global');
+
$consumer = $this->consumerFactory->get($consumerName, $batchSize);
$consumer->process($numberOfMessages);
+
+ if ($singleThread) {
+ $this->lockManager->unlock(md5($consumerName)); //phpcs:ignore
+ }
+
return \Magento\Framework\Console\Cli::RETURN_SUCCESS;
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
protected function configure()
{
@@ -125,11 +132,17 @@ protected function configure()
'The preferred area (global, adminhtml, etc...) '
. 'default is global.'
);
+ $this->addOption(
+ self::OPTION_SINGLE_THREAD,
+ null,
+ InputOption::VALUE_NONE,
+ 'This option prevents running multiple copies of one consumer simultaneously.'
+ );
$this->addOption(
self::PID_FILE_PATH,
null,
InputOption::VALUE_REQUIRED,
- 'The file path for saving PID'
+ 'The file path for saving PID (This option is deprecated, use --single-thread instead)'
);
$this->setHelp(
<<%command.full_name% someConsumer --area-code='adminhtml'
+
+To do not run multiple copies of one consumer simultaneously:
+
+ %command.full_name% someConsumer --single-thread'
-To save PID enter path:
+To save PID enter path (This option is deprecated, use --single-thread instead):
%command.full_name% someConsumer --pid-file-path='/var/someConsumer.pid'
HELP
diff --git a/app/code/Magento/MessageQueue/Model/Cron/ConsumersRunner.php b/app/code/Magento/MessageQueue/Model/Cron/ConsumersRunner.php
index f5011248d7772..056cf4fc57a2e 100644
--- a/app/code/Magento/MessageQueue/Model/Cron/ConsumersRunner.php
+++ b/app/code/Magento/MessageQueue/Model/Cron/ConsumersRunner.php
@@ -13,18 +13,13 @@
use Magento\Framework\App\DeploymentConfig;
use Psr\Log\LoggerInterface;
use Symfony\Component\Process\PhpExecutableFinder;
-use Magento\MessageQueue\Model\Cron\ConsumersRunner\PidConsumerManager;
+use Magento\Framework\Lock\LockManagerInterface;
/**
* Class for running consumers processes by cron
*/
class ConsumersRunner
{
- /**
- * Extension of PID file
- */
- const PID_FILE_EXT = '.pid';
-
/**
* Shell command line wrapper for executing command in background
*
@@ -53,13 +48,6 @@ class ConsumersRunner
*/
private $phpExecutableFinder;
- /**
- * The class for checking status of process by PID
- *
- * @var PidConsumerManager
- */
- private $pidConsumerManager;
-
/**
* @var ConnectionTypeResolver
*/
@@ -70,13 +58,20 @@ class ConsumersRunner
*/
private $logger;
+ /**
+ * Lock Manager
+ *
+ * @var LockManagerInterface
+ */
+ private $lockManager;
+
/**
* @param PhpExecutableFinder $phpExecutableFinder The executable finder specifically designed
* for the PHP executable
* @param ConsumerConfigInterface $consumerConfig The consumer config provider
* @param DeploymentConfig $deploymentConfig The application deployment configuration
* @param ShellInterface $shellBackground The shell command line wrapper for executing command in background
- * @param PidConsumerManager $pidConsumerManager The class for checking status of process by PID
+ * @param LockManagerInterface $lockManager The lock manager
* @param ConnectionTypeResolver $mqConnectionTypeResolver Consumer connection resolver
* @param LoggerInterface $logger Logger
*/
@@ -85,7 +80,7 @@ public function __construct(
ConsumerConfigInterface $consumerConfig,
DeploymentConfig $deploymentConfig,
ShellInterface $shellBackground,
- PidConsumerManager $pidConsumerManager,
+ LockManagerInterface $lockManager,
ConnectionTypeResolver $mqConnectionTypeResolver = null,
LoggerInterface $logger = null
) {
@@ -93,7 +88,7 @@ public function __construct(
$this->consumerConfig = $consumerConfig;
$this->deploymentConfig = $deploymentConfig;
$this->shellBackground = $shellBackground;
- $this->pidConsumerManager = $pidConsumerManager;
+ $this->lockManager = $lockManager;
$this->mqConnectionTypeResolver = $mqConnectionTypeResolver
?: ObjectManager::getInstance()->get(ConnectionTypeResolver::class);
$this->logger = $logger
@@ -120,11 +115,9 @@ public function run()
continue;
}
- $consumerName = $consumer->getName();
-
$arguments = [
- $consumerName,
- '--pid-file-path=' . $this->getPidFilePath($consumerName),
+ $consumer->getName(),
+ '--single-thread'
];
if ($maxMessages) {
@@ -154,7 +147,7 @@ private function canBeRun(ConsumerConfigItemInterface $consumerConfig, array $al
return false;
}
- if ($this->pidConsumerManager->isRun($this->getPidFilePath($consumerName))) {
+ if ($this->lockManager->isLocked(md5($consumerName))) { //phpcs:ignore
return false;
}
@@ -162,28 +155,17 @@ private function canBeRun(ConsumerConfigItemInterface $consumerConfig, array $al
try {
$this->mqConnectionTypeResolver->getConnectionType($connectionName);
} catch (\LogicException $e) {
- $this->logger->info(sprintf(
- 'Consumer "%s" skipped as required connection "%s" is not configured. %s',
- $consumerName,
- $connectionName,
- $e->getMessage()
- ));
+ $this->logger->info(
+ sprintf(
+ 'Consumer "%s" skipped as required connection "%s" is not configured. %s',
+ $consumerName,
+ $connectionName,
+ $e->getMessage()
+ )
+ );
return false;
}
return true;
}
-
- /**
- * Returns default path to file with PID by consumers name
- *
- * @param string $consumerName The consumers name
- * @return string The path to file with PID
- */
- private function getPidFilePath($consumerName)
- {
- $sanitizedHostname = preg_replace('/[^a-z0-9]/i', '', gethostname());
-
- return $consumerName . '-' . $sanitizedHostname . static::PID_FILE_EXT;
- }
}
diff --git a/app/code/Magento/MessageQueue/Model/Cron/ConsumersRunner/PidConsumerManager.php b/app/code/Magento/MessageQueue/Model/Cron/ConsumersRunner/PidConsumerManager.php
deleted file mode 100644
index d5f827320ac74..0000000000000
--- a/app/code/Magento/MessageQueue/Model/Cron/ConsumersRunner/PidConsumerManager.php
+++ /dev/null
@@ -1,127 +0,0 @@
-filesystem = $filesystem;
- }
-
- /**
- * Checks if consumer process is run by pid from pidFile
- *
- * @param string $pidFilePath The path to file with PID
- * @return bool Returns true if consumer process is run
- * @throws FileSystemException
- */
- public function isRun($pidFilePath)
- {
- $pid = $this->getPid($pidFilePath);
- if ($pid) {
- if (function_exists('posix_getpgid')) {
- return (bool) posix_getpgid($pid);
- } else {
- return $this->checkIsProcessExists($pid);
- }
- }
-
- return false;
- }
-
- /**
- * Checks that process is running
- *
- * If php function exec is not available throws RuntimeException
- * If shell command returns non-zero code and this code is not 1 throws RuntimeException
- *
- * @param int $pid A pid of process
- * @return bool Returns true if consumer process is run
- * @throws \RuntimeException
- * @SuppressWarnings(PHPMD.UnusedLocalVariable)
- */
- private function checkIsProcessExists($pid)
- {
- if (!function_exists('exec')) {
- throw new \RuntimeException('Function exec is not available');
- }
-
- exec(escapeshellcmd('ps -p ' . $pid), $output, $code);
-
- $code = (int) $code;
-
- switch ($code) {
- case 0:
- return true;
- break;
- case 1:
- return false;
- break;
- default:
- throw new \RuntimeException('Exec returned non-zero code', $code);
- break;
- }
- }
-
- /**
- * Returns pid by pidFile path
- *
- * @param string $pidFilePath The path to file with PID
- * @return int Returns pid if pid file exists for consumer else returns 0
- * @throws FileSystemException
- */
- public function getPid($pidFilePath)
- {
- /** @var ReadInterface $directory */
- $directory = $this->filesystem->getDirectoryRead(DirectoryList::VAR_DIR);
-
- if ($directory->isExist($pidFilePath)) {
- return (int) $directory->readFile($pidFilePath);
- }
-
- return 0;
- }
-
- /**
- * Saves pid of current process to file
- *
- * @param string $pidFilePath The path to file with pid
- * @throws FileSystemException
- */
- public function savePid($pidFilePath)
- {
- /** @var WriteInterface $directory */
- $directory = $this->filesystem->getDirectoryWrite(DirectoryList::VAR_DIR);
- $directory->writeFile($pidFilePath, function_exists('posix_getpid') ? posix_getpid() : getmypid(), 'w');
- }
-}
diff --git a/app/code/Magento/MessageQueue/Setup/ConfigOptionsList.php b/app/code/Magento/MessageQueue/Setup/ConfigOptionsList.php
new file mode 100644
index 0000000000000..b23dc7fbbf532
--- /dev/null
+++ b/app/code/Magento/MessageQueue/Setup/ConfigOptionsList.php
@@ -0,0 +1,108 @@
+selectOptions,
+ self::CONFIG_PATH_QUEUE_CONSUMERS_WAIT_FOR_MESSAGES,
+ 'Should consumers wait for a message from the queue? 1 - Yes, 0 - No',
+ self::DEFAULT_CONSUMERS_WAIT_FOR_MESSAGES
+ ),
+ ];
+ }
+
+ /**
+ * @inheritdoc
+ * @SuppressWarnings(PHPMD.UnusedFormalParameter)
+ */
+ public function createConfig(array $data, DeploymentConfig $deploymentConfig)
+ {
+ $configData = new ConfigData(ConfigFilePool::APP_ENV);
+
+ if (!$this->isDataEmpty($data, self::INPUT_KEY_QUEUE_CONSUMERS_WAIT_FOR_MESSAGES)) {
+ $configData->set(
+ self::CONFIG_PATH_QUEUE_CONSUMERS_WAIT_FOR_MESSAGES,
+ (int)$data[self::INPUT_KEY_QUEUE_CONSUMERS_WAIT_FOR_MESSAGES]
+ );
+ }
+
+ return [$configData];
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function validate(array $options, DeploymentConfig $deploymentConfig)
+ {
+ $errors = [];
+
+ if (!$this->isDataEmpty($options, self::INPUT_KEY_QUEUE_CONSUMERS_WAIT_FOR_MESSAGES)
+ && !in_array($options[self::INPUT_KEY_QUEUE_CONSUMERS_WAIT_FOR_MESSAGES], $this->selectOptions)) {
+ $errors[] = 'You can use only 1 or 0 for ' . self::INPUT_KEY_QUEUE_CONSUMERS_WAIT_FOR_MESSAGES . ' option';
+ }
+
+ return $errors;
+ }
+
+ /**
+ * Check if data ($data) with key ($key) is empty
+ *
+ * @param array $data
+ * @param string $key
+ * @return bool
+ */
+ private function isDataEmpty(array $data, $key)
+ {
+ if (isset($data[$key]) && $data[$key] !== '') {
+ return false;
+ }
+
+ return true;
+ }
+}
diff --git a/app/code/Magento/MessageQueue/Test/Unit/Console/StartConsumerCommandTest.php b/app/code/Magento/MessageQueue/Test/Unit/Console/StartConsumerCommandTest.php
index 922da3bfc8773..f62a7afe323f2 100644
--- a/app/code/Magento/MessageQueue/Test/Unit/Console/StartConsumerCommandTest.php
+++ b/app/code/Magento/MessageQueue/Test/Unit/Console/StartConsumerCommandTest.php
@@ -8,7 +8,7 @@
use Magento\MessageQueue\Console\StartConsumerCommand;
use Magento\Framework\Filesystem\File\WriteFactory;
-use Magento\MessageQueue\Model\Cron\ConsumersRunner\PidConsumerManager;
+use Magento\Framework\Lock\LockManagerInterface;
/**
* Unit tests for StartConsumerCommand.
@@ -36,9 +36,9 @@ class StartConsumerCommandTest extends \PHPUnit\Framework\TestCase
private $writeFactoryMock;
/**
- * @var PidConsumerManager|\PHPUnit_Framework_MockObject_MockObject
+ * @var LockManagerInterface|\PHPUnit_Framework_MockObject_MockObject
*/
- private $pidConsumerManagerMock;
+ private $lockManagerMock;
/**
* @var StartConsumerCommand
@@ -50,9 +50,8 @@ class StartConsumerCommandTest extends \PHPUnit\Framework\TestCase
*/
protected function setUp()
{
- $this->pidConsumerManagerMock = $this->getMockBuilder(PidConsumerManager::class)
- ->disableOriginalConstructor()
- ->getMock();
+ $this->lockManagerMock = $this->getMockBuilder(LockManagerInterface::class)
+ ->getMockForAbstractClass();
$this->consumerFactory = $this->getMockBuilder(\Magento\Framework\MessageQueue\ConsumerFactory::class)
->disableOriginalConstructor()->getMock();
$this->appState = $this->getMockBuilder(\Magento\Framework\App\State::class)
@@ -68,7 +67,7 @@ protected function setUp()
'consumerFactory' => $this->consumerFactory,
'appState' => $this->appState,
'writeFactory' => $this->writeFactoryMock,
- 'pidConsumerManager' => $this->pidConsumerManagerMock,
+ 'lockManager' => $this->lockManagerMock,
]
);
parent::setUp();
@@ -78,9 +77,11 @@ protected function setUp()
* Test for execute method.
*
* @param string|null $pidFilePath
- * @param int $savePidExpects
- * @param int $isRunExpects
- * @param bool $isRun
+ * @param bool $singleThread
+ * @param int $lockExpects
+ * @param int $isLockedExpects
+ * @param bool $isLocked
+ * @param int $unlockExpects
* @param int $runProcessExpects
* @param int $expectedReturn
* @return void
@@ -88,9 +89,11 @@ protected function setUp()
*/
public function testExecute(
$pidFilePath,
- $savePidExpects,
- $isRunExpects,
- $isRun,
+ $singleThread,
+ $lockExpects,
+ $isLockedExpects,
+ $isLocked,
+ $unlockExpects,
$runProcessExpects,
$expectedReturn
) {
@@ -105,17 +108,19 @@ public function testExecute(
$input->expects($this->once())->method('getArgument')
->with(\Magento\MessageQueue\Console\StartConsumerCommand::ARGUMENT_CONSUMER)
->willReturn($consumerName);
- $input->expects($this->exactly(4))->method('getOption')
+ $input->expects($this->exactly(5))->method('getOption')
->withConsecutive(
[\Magento\MessageQueue\Console\StartConsumerCommand::OPTION_NUMBER_OF_MESSAGES],
[\Magento\MessageQueue\Console\StartConsumerCommand::OPTION_BATCH_SIZE],
[\Magento\MessageQueue\Console\StartConsumerCommand::OPTION_AREACODE],
- [\Magento\MessageQueue\Console\StartConsumerCommand::PID_FILE_PATH]
+ [\Magento\MessageQueue\Console\StartConsumerCommand::PID_FILE_PATH],
+ [\Magento\MessageQueue\Console\StartConsumerCommand::OPTION_SINGLE_THREAD]
)->willReturnOnConsecutiveCalls(
$numberOfMessages,
$batchSize,
$areaCode,
- $pidFilePath
+ $pidFilePath,
+ $singleThread
);
$this->appState->expects($this->exactly($runProcessExpects))->method('setAreaCode')->with($areaCode);
$consumer = $this->getMockBuilder(\Magento\Framework\MessageQueue\ConsumerInterface::class)
@@ -124,14 +129,17 @@ public function testExecute(
->method('get')->with($consumerName, $batchSize)->willReturn($consumer);
$consumer->expects($this->exactly($runProcessExpects))->method('process')->with($numberOfMessages);
- $this->pidConsumerManagerMock->expects($this->exactly($isRunExpects))
- ->method('isRun')
- ->with($pidFilePath)
- ->willReturn($isRun);
+ $this->lockManagerMock->expects($this->exactly($isLockedExpects))
+ ->method('isLocked')
+ ->with(md5($consumerName)) //phpcs:ignore
+ ->willReturn($isLocked);
- $this->pidConsumerManagerMock->expects($this->exactly($savePidExpects))
- ->method('savePid')
- ->with($pidFilePath);
+ $this->lockManagerMock->expects($this->exactly($lockExpects))
+ ->method('lock')
+ ->with(md5($consumerName)); //phpcs:ignore
+ $this->lockManagerMock->expects($this->exactly($unlockExpects))
+ ->method('unlock')
+ ->with(md5($consumerName)); //phpcs:ignore
$this->assertEquals(
$expectedReturn,
@@ -147,25 +155,31 @@ public function executeDataProvider()
return [
[
'pidFilePath' => null,
- 'savePidExpects' => 0,
- 'isRunExpects' => 0,
- 'isRun' => false,
+ 'singleThread' => false,
+ 'lockExpects' => 0,
+ 'isLockedExpects' => 0,
+ 'isLocked' => false,
+ 'unlockExpects' => 0,
'runProcessExpects' => 1,
'expectedReturn' => \Magento\Framework\Console\Cli::RETURN_SUCCESS,
],
[
'pidFilePath' => '/var/consumer.pid',
- 'savePidExpects' => 1,
- 'isRunExpects' => 1,
- 'isRun' => false,
+ 'singleThread' => true,
+ 'lockExpects' => 1,
+ 'isLockedExpects' => 1,
+ 'isLocked' => false,
+ 'unlockExpects' => 1,
'runProcessExpects' => 1,
'expectedReturn' => \Magento\Framework\Console\Cli::RETURN_SUCCESS,
],
[
'pidFilePath' => '/var/consumer.pid',
- 'savePidExpects' => 0,
- 'isRunExpects' => 1,
- 'isRun' => true,
+ 'singleThread' => true,
+ 'lockExpects' => 0,
+ 'isLockedExpects' => 1,
+ 'isLocked' => true,
+ 'unlockExpects' => 0,
'runProcessExpects' => 0,
'expectedReturn' => \Magento\Framework\Console\Cli::RETURN_FAILURE,
],
@@ -186,6 +200,7 @@ public function testConfigure()
$this->command->getDefinition()->getOption(StartConsumerCommand::OPTION_NUMBER_OF_MESSAGES);
$this->command->getDefinition()->getOption(StartConsumerCommand::OPTION_AREACODE);
$this->command->getDefinition()->getOption(StartConsumerCommand::PID_FILE_PATH);
+ $this->command->getDefinition()->getOption(StartConsumerCommand::OPTION_SINGLE_THREAD);
$this->assertContains('To start consumer which will process', $this->command->getHelp());
}
}
diff --git a/app/code/Magento/MessageQueue/Test/Unit/Model/Cron/ConsumersRunner/PidConsumerManagerTest.php b/app/code/Magento/MessageQueue/Test/Unit/Model/Cron/ConsumersRunner/PidConsumerManagerTest.php
deleted file mode 100644
index 3d48e0b19ef2c..0000000000000
--- a/app/code/Magento/MessageQueue/Test/Unit/Model/Cron/ConsumersRunner/PidConsumerManagerTest.php
+++ /dev/null
@@ -1,104 +0,0 @@
-filesystemMock = $this->getMockBuilder(Filesystem::class)
- ->disableOriginalConstructor()
- ->getMock();
-
- $this->pidConsumerManager = new PidConsumerManager($this->filesystemMock);
- }
-
- /**
- * @param bool $fileExists
- * @param int|null $pid
- * @param bool $expectedResult
- * @dataProvider isRunDataProvider
- */
- public function testIsRun($fileExists, $pid, $expectedResult)
- {
- $pidFilePath = 'somepath/consumerName.pid';
-
- /** @var ReadInterface|MockObject $directoryMock */
- $directoryMock = $this->getMockBuilder(ReadInterface::class)
- ->getMockForAbstractClass();
- $directoryMock->expects($this->once())
- ->method('isExist')
- ->willReturn($fileExists);
- $directoryMock->expects($this->any())
- ->method('readFile')
- ->with($pidFilePath)
- ->willReturn($pid);
-
- $this->filesystemMock->expects($this->once())
- ->method('getDirectoryRead')
- ->with(DirectoryList::VAR_DIR)
- ->willReturn($directoryMock);
-
- $this->assertSame($expectedResult, $this->pidConsumerManager->isRun($pidFilePath));
- }
-
- /**
- * @return array
- */
- public function isRunDataProvider()
- {
- return [
- ['fileExists' => false, 'pid' => null, false],
- ['fileExists' => false, 'pid' => 11111, false],
- ['fileExists' => true, 'pid' => 11111, true],
- ['fileExists' => true, 'pid' => 77777, false],
- ];
- }
-
- public function testSavePid()
- {
- $pidFilePath = '/var/somePath/pidfile.pid';
-
- /** @var WriteInterface|MockObject $writeMock */
- $writeMock = $this->getMockBuilder(WriteInterface::class)
- ->getMockForAbstractClass();
- $this->filesystemMock->expects($this->once())
- ->method('getDirectoryWrite')
- ->with(DirectoryList::VAR_DIR)
- ->willReturn($writeMock);
- $writeMock->expects($this->once())
- ->method('writeFile')
- ->with(
- $pidFilePath,
- function_exists('posix_getpid') ? posix_getpid() : getmypid(),
- 'w'
- );
-
- $this->pidConsumerManager->savePid($pidFilePath);
- }
-}
diff --git a/app/code/Magento/MessageQueue/Test/Unit/Model/Cron/ConsumersRunnerTest.php b/app/code/Magento/MessageQueue/Test/Unit/Model/Cron/ConsumersRunnerTest.php
index 006354b997d3a..e19467f798a1f 100644
--- a/app/code/Magento/MessageQueue/Test/Unit/Model/Cron/ConsumersRunnerTest.php
+++ b/app/code/Magento/MessageQueue/Test/Unit/Model/Cron/ConsumersRunnerTest.php
@@ -12,15 +12,18 @@
use Magento\Framework\MessageQueue\Consumer\Config\ConsumerConfigItemInterface;
use Magento\Framework\App\DeploymentConfig;
use Magento\MessageQueue\Model\Cron\ConsumersRunner;
-use Magento\MessageQueue\Model\Cron\ConsumersRunner\PidConsumerManager;
use Symfony\Component\Process\PhpExecutableFinder;
+use Magento\Framework\Lock\LockManagerInterface;
+/**
+ * Unit tests for ConsumersRunner.
+ */
class ConsumersRunnerTest extends \PHPUnit\Framework\TestCase
{
/**
- * @var PidConsumerManager|MockObject
+ * @var LockManagerInterface|MockObject
*/
- private $pidConsumerManagerMock;
+ private $lockManagerMock;
/**
* @var ShellInterface|MockObject
@@ -62,9 +65,8 @@ protected function setUp()
$this->phpExecutableFinderMock = $this->getMockBuilder(phpExecutableFinder::class)
->disableOriginalConstructor()
->getMock();
- $this->pidConsumerManagerMock = $this->getMockBuilder(PidConsumerManager::class)
- ->disableOriginalConstructor()
- ->getMock();
+ $this->lockManagerMock = $this->getMockBuilder(LockManagerInterface::class)
+ ->getMockForAbstractClass();
$this->shellBackgroundMock = $this->getMockBuilder(ShellInterface::class)
->getMockForAbstractClass();
$this->consumerConfigMock = $this->getMockBuilder(ConsumerConfigInterface::class)
@@ -82,7 +84,7 @@ protected function setUp()
$this->consumerConfigMock,
$this->deploymentConfigMock,
$this->shellBackgroundMock,
- $this->pidConsumerManagerMock,
+ $this->lockManagerMock,
$this->connectionTypeResover
);
}
@@ -91,16 +93,18 @@ public function testRunDisabled()
{
$this->deploymentConfigMock->expects($this->once())
->method('get')
- ->willReturnMap([
- ['cron_consumers_runner/cron_run', true, false],
- ['cron_consumers_runner/max_messages', 10000, 10000],
- ['cron_consumers_runner/consumers', [], []],
- ]);
+ ->willReturnMap(
+ [
+ ['cron_consumers_runner/cron_run', true, false],
+ ['cron_consumers_runner/max_messages', 10000, 10000],
+ ['cron_consumers_runner/consumers', [], []],
+ ]
+ );
$this->consumerConfigMock->expects($this->never())
->method('getConsumers');
- $this->pidConsumerManagerMock->expects($this->never())
- ->method('isRun');
+ $this->lockManagerMock->expects($this->never())
+ ->method('isLocked');
$this->shellBackgroundMock->expects($this->never())
->method('execute');
@@ -109,7 +113,7 @@ public function testRunDisabled()
/**
* @param int $maxMessages
- * @param bool $isRun
+ * @param bool $isLocked
* @param string $php
* @param string $command
* @param array $arguments
@@ -120,7 +124,7 @@ public function testRunDisabled()
*/
public function testRun(
$maxMessages,
- $isRun,
+ $isLocked,
$php,
$command,
$arguments,
@@ -129,15 +133,16 @@ public function testRun(
$isRunExpects
) {
$consumerName = 'consumerName';
- $pidFilePath = 'consumerName-myHostName.pid';
$this->deploymentConfigMock->expects($this->exactly(3))
->method('get')
- ->willReturnMap([
- ['cron_consumers_runner/cron_run', true, true],
- ['cron_consumers_runner/max_messages', 10000, $maxMessages],
- ['cron_consumers_runner/consumers', [], $allowedConsumers],
- ]);
+ ->willReturnMap(
+ [
+ ['cron_consumers_runner/cron_run', true, true],
+ ['cron_consumers_runner/max_messages', 10000, $maxMessages],
+ ['cron_consumers_runner/consumers', [], $allowedConsumers],
+ ]
+ );
/** @var ConsumerConfigInterface|MockObject $firstCunsumer */
$consumer = $this->getMockBuilder(ConsumerConfigItemInterface::class)
@@ -154,10 +159,10 @@ public function testRun(
->method('getConsumers')
->willReturn([$consumer]);
- $this->pidConsumerManagerMock->expects($this->exactly($isRunExpects))
- ->method('isRun')
- ->with($pidFilePath)
- ->willReturn($isRun);
+ $this->lockManagerMock->expects($this->exactly($isRunExpects))
+ ->method('isLocked')
+ ->with(md5($consumerName)) //phpcs:ignore
+ ->willReturn($isLocked);
$this->shellBackgroundMock->expects($this->exactly($shellBackgroundExpects))
->method('execute')
@@ -174,80 +179,80 @@ public function runDataProvider()
return [
[
'maxMessages' => 20000,
- 'isRun' => false,
+ 'isLocked' => false,
'php' => '',
'command' => 'php '. BP . '/bin/magento queue:consumers:start %s %s %s',
- 'arguments' => ['consumerName', '--pid-file-path=consumerName-myHostName.pid', '--max-messages=20000'],
+ 'arguments' => ['consumerName', '--single-thread', '--max-messages=20000'],
'allowedConsumers' => [],
'shellBackgroundExpects' => 1,
'isRunExpects' => 1,
],
[
'maxMessages' => 10000,
- 'isRun' => false,
+ 'isLocked' => false,
'php' => '',
'command' => 'php '. BP . '/bin/magento queue:consumers:start %s %s %s',
- 'arguments' => ['consumerName', '--pid-file-path=consumerName-myHostName.pid', '--max-messages=10000'],
+ 'arguments' => ['consumerName', '--single-thread', '--max-messages=10000'],
'allowedConsumers' => [],
'shellBackgroundExpects' => 1,
'isRunExpects' => 1,
],
[
'maxMessages' => 10000,
- 'isRun' => false,
+ 'isLocked' => false,
'php' => '',
'command' => 'php '. BP . '/bin/magento queue:consumers:start %s %s %s',
- 'arguments' => ['consumerName', '--pid-file-path=consumerName-myHostName.pid', '--max-messages=10000'],
+ 'arguments' => ['consumerName', '--single-thread', '--max-messages=10000'],
'allowedConsumers' => ['someConsumer'],
'shellBackgroundExpects' => 0,
'isRunExpects' => 0,
],
[
'maxMessages' => 10000,
- 'isRun' => true,
+ 'isLocked' => true,
'php' => '',
'command' => 'php '. BP . '/bin/magento queue:consumers:start %s %s %s',
- 'arguments' => ['consumerName', '--pid-file-path=consumerName-myHostName.pid', '--max-messages=10000'],
+ 'arguments' => ['consumerName', '--single-thread', '--max-messages=10000'],
'allowedConsumers' => ['someConsumer'],
'shellBackgroundExpects' => 0,
'isRunExpects' => 0,
],
[
'maxMessages' => 10000,
- 'isRun' => true,
+ 'isLocked' => true,
'php' => '',
'command' => 'php '. BP . '/bin/magento queue:consumers:start %s %s %s',
- 'arguments' => ['consumerName', '--pid-file-path=consumerName-myHostName.pid', '--max-messages=10000'],
+ 'arguments' => ['consumerName', '--single-thread', '--max-messages=10000'],
'allowedConsumers' => [],
'shellBackgroundExpects' => 0,
'isRunExpects' => 1,
],
[
'maxMessages' => 10000,
- 'isRun' => true,
+ 'isLocked' => true,
'php' => '',
'command' => 'php '. BP . '/bin/magento queue:consumers:start %s %s %s',
- 'arguments' => ['consumerName', '--pid-file-path=consumerName-myHostName.pid', '--max-messages=10000'],
+ 'arguments' => ['consumerName', '--single-thread', '--max-messages=10000'],
'allowedConsumers' => ['consumerName'],
'shellBackgroundExpects' => 0,
'isRunExpects' => 1,
],
[
'maxMessages' => 10000,
- 'isRun' => false,
+ 'isLocked' => false,
'php' => '',
'command' => 'php '. BP . '/bin/magento queue:consumers:start %s %s %s',
- 'arguments' => ['consumerName', '--pid-file-path=consumerName-myHostName.pid', '--max-messages=10000'],
+ 'arguments' => ['consumerName', '--single-thread', '--max-messages=10000'],
'allowedConsumers' => ['consumerName'],
'shellBackgroundExpects' => 1,
'isRunExpects' => 1,
],
[
'maxMessages' => 0,
- 'isRun' => false,
+ 'isLocked' => false,
'php' => '/bin/php',
'command' => '/bin/php '. BP . '/bin/magento queue:consumers:start %s %s',
- 'arguments' => ['consumerName', '--pid-file-path=consumerName-myHostName.pid'],
+ 'arguments' => ['consumerName', '--single-thread'],
'allowedConsumers' => ['consumerName'],
'shellBackgroundExpects' => 1,
'isRunExpects' => 1,
diff --git a/app/code/Magento/MessageQueue/composer.json b/app/code/Magento/MessageQueue/composer.json
index 9d3c77fa0184a..92744126900a4 100644
--- a/app/code/Magento/MessageQueue/composer.json
+++ b/app/code/Magento/MessageQueue/composer.json
@@ -7,7 +7,7 @@
"require": {
"magento/framework": "*",
"magento/magento-composer-installer": "*",
- "php": "~7.1.3||~7.2.0"
+ "php": "~7.1.3||~7.2.0||~7.3.0"
},
"type": "magento2-module",
"license": [
diff --git a/app/code/Magento/Msrp/Setup/Patch/Data/ChangeMsrpAttributeLabel.php b/app/code/Magento/Msrp/Setup/Patch/Data/ChangeMsrpAttributeLabel.php
new file mode 100644
index 0000000000000..547287066a30f
--- /dev/null
+++ b/app/code/Magento/Msrp/Setup/Patch/Data/ChangeMsrpAttributeLabel.php
@@ -0,0 +1,66 @@
+categorySetupFactory = $categorySetupFactory;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function apply()
+ {
+ /** @var \Magento\Catalog\Setup\CategorySetup $categorySetup */
+ $categorySetup = $this->categorySetupFactory->create();
+ $entityTypeId = $categorySetup->getEntityTypeId(Product::ENTITY);
+ $msrpAttribute = $categorySetup->getAttribute($entityTypeId, 'msrp');
+ $categorySetup->updateAttribute(
+ $entityTypeId,
+ $msrpAttribute['attribute_id'],
+ 'frontend_label',
+ 'Minimum Advertised Price'
+ );
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public static function getDependencies()
+ {
+ return [
+ InitializeMsrpAttributes::class,
+ ];
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function getAliases()
+ {
+ return [];
+ }
+}
diff --git a/app/code/Magento/Msrp/composer.json b/app/code/Magento/Msrp/composer.json
index a2e6da6de5387..81bb423c00a6d 100644
--- a/app/code/Magento/Msrp/composer.json
+++ b/app/code/Magento/Msrp/composer.json
@@ -5,7 +5,7 @@
"sort-packages": true
},
"require": {
- "php": "~7.1.3||~7.2.0",
+ "php": "~7.1.3||~7.2.0||~7.3.0",
"magento/framework": "*",
"magento/module-catalog": "*",
"magento/module-downloadable": "*",
diff --git a/app/code/Magento/Msrp/view/base/templates/product/price/msrp.phtml b/app/code/Magento/Msrp/view/base/templates/product/price/msrp.phtml
index 2ab40a7ec8299..b062e911876c3 100644
--- a/app/code/Magento/Msrp/view/base/templates/product/price/msrp.phtml
+++ b/app/code/Magento/Msrp/view/base/templates/product/price/msrp.phtml
@@ -59,7 +59,7 @@ $priceElementIdPrefix = $block->getPriceElementIdPrefix() ? $block->getPriceElem
$priceElementId = $priceElementIdPrefix . $productId . $block->getIdSuffix();
$popupId = 'msrp-popup-' . $productId . $block->getRandomString(20);
- $data = ['addToCart' => [
+ $data = [
'origin'=> 'msrp',
'popupId' => '#' . $popupId,
'productName' => $block->escapeJs($block->escapeHtml($product->getName())),
@@ -72,11 +72,11 @@ $priceElementIdPrefix = $block->getPriceElementIdPrefix() ? $block->getPriceElem
'closeButtonId' => '#map-popup-close',
'addToCartUrl' => $addToCartUrl,
'paymentButtons' => '[data-label=or]'
- ]];
+ ];
if ($block->getRequest()->getFullActionName() === 'catalog_product_view') {
- $data['addToCart']['addToCartButton'] = '#product_addtocart_form [type=submit]';
+ $data['addToCartButton'] = '#product_addtocart_form [type=submit]';
} else {
- $data['addToCart']['addToCartButton'] = sprintf(
+ $data['addToCartButton'] = sprintf(
'form:has(input[type="hidden"][name="product"][value="%s"]) button[type="submit"]',
(int) $productId . ',' .
sprintf(
@@ -91,7 +91,7 @@ $priceElementIdPrefix = $block->getPriceElementIdPrefix() ? $block->getPriceElem
id="= /* @noEscape */ ($popupId) ?>"
class="action map-show-info"
- data-mage-init='= /* @noEscape */ $this->helper(\Magento\Framework\Json\Helper\Data::class)->jsonEncode($data) ?>'>
+ data-mage-init='{"addToCart":= /* @noEscape */ $this->helper(\Magento\Framework\Json\Helper\Data::class)->jsonEncode($data) ?>}'>
= $block->escapeHtml(__('Click for price')) ?>
diff --git a/app/code/Magento/Msrp/view/base/web/js/msrp.js b/app/code/Magento/Msrp/view/base/web/js/msrp.js
index a0bd3ec132de6..2789491137bc1 100644
--- a/app/code/Magento/Msrp/view/base/web/js/msrp.js
+++ b/app/code/Magento/Msrp/view/base/web/js/msrp.js
@@ -6,7 +6,7 @@ define([
'jquery',
'Magento_Catalog/js/price-utils',
'underscore',
- 'jquery/ui',
+ 'jquery-ui-modules/widget',
'mage/dropdown',
'mage/template'
], function ($, priceUtils, _) {
diff --git a/app/code/Magento/Msrp/view/frontend/layout/catalog_product_view.xml b/app/code/Magento/Msrp/view/frontend/layout/catalog_product_view.xml
index 78ec742cc09c6..18a5cb3e09359 100644
--- a/app/code/Magento/Msrp/view/frontend/layout/catalog_product_view.xml
+++ b/app/code/Magento/Msrp/view/frontend/layout/catalog_product_view.xml
@@ -5,7 +5,7 @@
* See COPYING.txt for license details.
*/
-->
-
+
diff --git a/app/code/Magento/MsrpConfigurableProduct/composer.json b/app/code/Magento/MsrpConfigurableProduct/composer.json
index 00c3cf6b03078..e2ef230f3caff 100644
--- a/app/code/Magento/MsrpConfigurableProduct/composer.json
+++ b/app/code/Magento/MsrpConfigurableProduct/composer.json
@@ -5,7 +5,7 @@
"sort-packages": true
},
"require": {
- "php": "~7.1.3||~7.2.0",
+ "php": "~7.1.3||~7.2.0||~7.3.0",
"magento/framework": "*",
"magento/module-catalog": "*",
"magento/module-msrp": "*",
diff --git a/app/code/Magento/MsrpGroupedProduct/composer.json b/app/code/Magento/MsrpGroupedProduct/composer.json
index a626f199ad6cc..76e2db1693551 100644
--- a/app/code/Magento/MsrpGroupedProduct/composer.json
+++ b/app/code/Magento/MsrpGroupedProduct/composer.json
@@ -5,7 +5,7 @@
"sort-packages": true
},
"require": {
- "php": "~7.1.3||~7.2.0",
+ "php": "~7.1.3||~7.2.0||~7.3.0",
"magento/framework": "*",
"magento/module-catalog": "*",
"magento/module-msrp": "*",
diff --git a/app/code/Magento/Multishipping/Block/Checkout/Overview.php b/app/code/Magento/Multishipping/Block/Checkout/Overview.php
index 5963e62e948f9..d17da90c58bef 100644
--- a/app/code/Magento/Multishipping/Block/Checkout/Overview.php
+++ b/app/code/Magento/Multishipping/Block/Checkout/Overview.php
@@ -3,6 +3,7 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
+
namespace Magento\Multishipping\Block\Checkout;
use Magento\Framework\Pricing\PriceCurrencyInterface;
@@ -12,8 +13,8 @@
* Multishipping checkout overview information
*
* @api
- * @author Magento Core Team
- * @since 100.0.2
+ * @author Magento Core Team
+ * @since 100.0.2
*/
class Overview extends \Magento\Sales\Block\Items\AbstractItems
{
@@ -48,13 +49,13 @@ class Overview extends \Magento\Sales\Block\Items\AbstractItems
protected $totalsReader;
/**
- * @param \Magento\Framework\View\Element\Template\Context $context
+ * @param \Magento\Framework\View\Element\Template\Context $context
* @param \Magento\Multishipping\Model\Checkout\Type\Multishipping $multishipping
- * @param \Magento\Tax\Helper\Data $taxHelper
- * @param PriceCurrencyInterface $priceCurrency
- * @param \Magento\Quote\Model\Quote\TotalsCollector $totalsCollector
- * @param \Magento\Quote\Model\Quote\TotalsReader $totalsReader
- * @param array $data
+ * @param \Magento\Tax\Helper\Data $taxHelper
+ * @param PriceCurrencyInterface $priceCurrency
+ * @param \Magento\Quote\Model\Quote\TotalsCollector $totalsCollector
+ * @param \Magento\Quote\Model\Quote\TotalsReader $totalsReader
+ * @param array $data
*/
public function __construct(
\Magento\Framework\View\Element\Template\Context $context,
@@ -74,6 +75,37 @@ public function __construct(
$this->totalsReader = $totalsReader;
}
+ /**
+ * Overwrite the total value of shipping amount for viewing purpose
+ *
+ * @param array $totals
+ * @return mixed
+ * @throws \Exception
+ */
+ private function getMultishippingTotals($totals)
+ {
+ if (isset($totals['shipping']) && !empty($totals['shipping'])) {
+ $total = $totals['shipping'];
+ $shippingMethod = $total->getAddress()->getShippingMethod();
+ if (isset($shippingMethod) && !empty($shippingMethod)) {
+ $shippingRate = $total->getAddress()->getShippingRateByCode($shippingMethod);
+ $shippingPrice = $shippingRate->getPrice();
+ } else {
+ $shippingPrice = $total->getAddress()->getShippingAmount();
+ }
+ /**
+ * @var \Magento\Store\Api\Data\StoreInterface
+ */
+ $store = $this->getQuote()->getStore();
+ $amountPrice = $store->getBaseCurrency()
+ ->convert($shippingPrice, $store->getCurrentCurrencyCode());
+ $total->setBaseShippingAmount($shippingPrice);
+ $total->setShippingAmount($amountPrice);
+ $total->setValue($amountPrice);
+ }
+ return $totals;
+ }
+
/**
* Initialize default item renderer
*
@@ -98,6 +130,8 @@ public function getCheckout()
}
/**
+ * Get billing address
+ *
* @return Address
*/
public function getBillingAddress()
@@ -106,6 +140,8 @@ public function getBillingAddress()
}
/**
+ * Get payment info
+ *
* @return string
*/
public function getPaymentHtml()
@@ -124,6 +160,8 @@ public function getPayment()
}
/**
+ * Get shipping addresses
+ *
* @return array
*/
public function getShippingAddresses()
@@ -132,6 +170,8 @@ public function getShippingAddresses()
}
/**
+ * Get number of shipping addresses
+ *
* @return int|mixed
*/
public function getShippingAddressCount()
@@ -145,8 +185,10 @@ public function getShippingAddressCount()
}
/**
- * @param Address $address
- * @return bool
+ * Get shipping address rate
+ *
+ * @param Address $address
+ * @return bool
* @SuppressWarnings(PHPMD.BooleanGetMethodName)
*/
public function getShippingAddressRate($address)
@@ -159,27 +201,36 @@ public function getShippingAddressRate($address)
}
/**
- * @param Address $address
+ * Get shipping price including tax
+ *
+ * @param Address $address
* @return mixed
*/
public function getShippingPriceInclTax($address)
{
- $exclTax = $address->getShippingAmount();
+ $rate = $address->getShippingRateByCode($address->getShippingMethod());
+ $exclTax = $rate->getPrice();
$taxAmount = $address->getShippingTaxAmount();
return $this->formatPrice($exclTax + $taxAmount);
}
/**
- * @param Address $address
+ * Get shipping price excluding tax
+ *
+ * @param Address $address
* @return mixed
*/
public function getShippingPriceExclTax($address)
{
- return $this->formatPrice($address->getShippingAmount());
+ $rate = $address->getShippingRateByCode($address->getShippingMethod());
+ $shippingAmount = $rate->getPrice();
+ return $this->formatPrice($shippingAmount);
}
/**
- * @param float $price
+ * Format price
+ *
+ * @param float $price
* @return mixed
*
* @codeCoverageIgnore
@@ -195,7 +246,9 @@ public function formatPrice($price)
}
/**
- * @param Address $address
+ * Get shipping address items
+ *
+ * @param Address $address
* @return array
*/
public function getShippingAddressItems($address): array
@@ -204,7 +257,9 @@ public function getShippingAddressItems($address): array
}
/**
- * @param Address $address
+ * Get shipping address totals
+ *
+ * @param Address $address
* @return mixed
*/
public function getShippingAddressTotals($address)
@@ -223,6 +278,8 @@ public function getShippingAddressTotals($address)
}
/**
+ * Get total price
+ *
* @return float
*/
public function getTotal()
@@ -231,6 +288,8 @@ public function getTotal()
}
/**
+ * Get the Edit addresses URL
+ *
* @return string
*/
public function getAddressesEditUrl()
@@ -239,7 +298,9 @@ public function getAddressesEditUrl()
}
/**
- * @param Address $address
+ * Get the Edit shipping address URL
+ *
+ * @param Address $address
* @return string
*/
public function getEditShippingAddressUrl($address)
@@ -248,7 +309,9 @@ public function getEditShippingAddressUrl($address)
}
/**
- * @param Address $address
+ * Get the Edit billing address URL
+ *
+ * @param Address $address
* @return string
*/
public function getEditBillingAddressUrl($address)
@@ -257,6 +320,8 @@ public function getEditBillingAddressUrl($address)
}
/**
+ * Get the Edit shipping URL
+ *
* @return string
*/
public function getEditShippingUrl()
@@ -265,6 +330,8 @@ public function getEditShippingUrl()
}
/**
+ * Get Post ACtion URL
+ *
* @return string
*/
public function getPostActionUrl()
@@ -273,6 +340,8 @@ public function getPostActionUrl()
}
/**
+ * Get the Edit billing URL
+ *
* @return string
*/
public function getEditBillingUrl()
@@ -281,6 +350,8 @@ public function getEditBillingUrl()
}
/**
+ * Get back button URL
+ *
* @return string
*/
public function getBackUrl()
@@ -319,9 +390,11 @@ public function getQuote()
}
/**
+ * Get billin address totals
+ *
+ * @return mixed
* @deprecated
* typo in method name, see getBillingAddressTotals()
- * @return mixed
*/
public function getBillinAddressTotals()
{
@@ -329,6 +402,8 @@ public function getBillinAddressTotals()
}
/**
+ * Get billing address totals
+ *
* @return mixed
*/
public function getBillingAddressTotals()
@@ -338,12 +413,17 @@ public function getBillingAddressTotals()
}
/**
- * @param mixed $totals
- * @param null $colspan
+ * Render total block
+ *
+ * @param mixed $totals
+ * @param null $colspan
* @return string
*/
public function renderTotals($totals, $colspan = null)
{
+ //check if the shipment is multi shipment
+ $totals = $this->getMultishippingTotals($totals);
+
if ($colspan === null) {
$colspan = 3;
}
@@ -368,7 +448,7 @@ public function renderTotals($totals, $colspan = null)
/**
* Return row-level item html
*
- * @param \Magento\Framework\DataObject $item
+ * @param \Magento\Framework\DataObject $item
* @return string
*/
public function getRowItemHtml(\Magento\Framework\DataObject $item)
@@ -382,7 +462,7 @@ public function getRowItemHtml(\Magento\Framework\DataObject $item)
/**
* Retrieve renderer block for row-level item output
*
- * @param string $type
+ * @param string $type
* @return \Magento\Framework\View\Element\AbstractBlock
*/
protected function _getRowItemRenderer($type)
diff --git a/app/code/Magento/Multishipping/Controller/Checkout/Plugin.php b/app/code/Magento/Multishipping/Controller/Checkout/Plugin.php
index edd313c106ed3..f88cdfc26fa9f 100644
--- a/app/code/Magento/Multishipping/Controller/Checkout/Plugin.php
+++ b/app/code/Magento/Multishipping/Controller/Checkout/Plugin.php
@@ -1,11 +1,15 @@
cart->getQuote()->setIsMultiShipping(0);
+ $quote = $this->cart->getQuote();
+ if ($quote->getIsMultiShipping()) {
+ $quote->setIsMultiShipping(0);
+ $this->cart->saveQuote();
+ }
}
}
diff --git a/app/code/Magento/Multishipping/Model/Cart/CartTotalRepositoryPlugin.php b/app/code/Magento/Multishipping/Model/Cart/CartTotalRepositoryPlugin.php
new file mode 100644
index 0000000000000..732bdee314f7c
--- /dev/null
+++ b/app/code/Magento/Multishipping/Model/Cart/CartTotalRepositoryPlugin.php
@@ -0,0 +1,71 @@
+quoteRepository = $quoteRepository;
+ }
+
+ /**
+ * Overwrite the CartTotalRepository quoteTotal and update the shipping price
+ *
+ * @param CartTotalRepository $subject
+ * @param Totals $quoteTotals
+ * @param String $cartId
+ * @return Totals
+ * @throws \Magento\Framework\Exception\NoSuchEntityException
+ * @SuppressWarnings(PHPMD.UnusedLocalVariable)
+ * @SuppressWarnings(PHPMD.UnusedFormalParameter)
+ */
+ public function afterGet(
+ CartTotalRepository $subject,
+ Totals $quoteTotals,
+ String $cartId
+ ) {
+ $quote = $this->quoteRepository->getActive($cartId);
+ if ($quote->getIsMultiShipping()) {
+ $shippingMethod = $quote->getShippingAddress()->getShippingMethod();
+ if (isset($shippingMethod) && !empty($shippingMethod)) {
+ $shippingRate = $quote->getShippingAddress()->getShippingRateByCode($shippingMethod);
+ $shippingPrice = $shippingRate->getPrice();
+ } else {
+ $shippingPrice = $quote->getShippingAddress()->getShippingAmount();
+ }
+ /**
+ * @var \Magento\Store\Api\Data\StoreInterface
+ */
+ $store = $quote->getStore();
+ $amountPrice = $store->getBaseCurrency()
+ ->convert($shippingPrice, $store->getCurrentCurrencyCode());
+ $quoteTotals->setBaseShippingAmount($shippingPrice);
+ $quoteTotals->setShippingAmount($amountPrice);
+ }
+ return $quoteTotals;
+ }
+}
diff --git a/app/code/Magento/Multishipping/Model/Checkout/Type/Multishipping.php b/app/code/Magento/Multishipping/Model/Checkout/Type/Multishipping.php
index 42f5289d2109a..7105fd4e9d26d 100644
--- a/app/code/Magento/Multishipping/Model/Checkout/Type/Multishipping.php
+++ b/app/code/Magento/Multishipping/Model/Checkout/Type/Multishipping.php
@@ -23,6 +23,7 @@
* @SuppressWarnings(PHPMD.TooManyFields)
* @SuppressWarnings(PHPMD.ExcessiveClassComplexity)
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
+ * @SuppressWarnings(PHPMD.CookieAndSessionMisuse)
* @since 100.0.2
*/
class Multishipping extends \Magento\Framework\DataObject
@@ -526,6 +527,7 @@ protected function _addShippingItem($quoteItemId, $data)
$quoteItem->setQty($quoteItem->getMultishippingQty());
try {
$address = $this->addressRepository->getById($addressId);
+ // phpcs:ignore Magento2.CodeAnalysis.EmptyBlock
} catch (\Exception $e) {
}
if (isset($address)) {
@@ -565,6 +567,7 @@ public function updateQuoteCustomerShippingAddress($addressId)
}
try {
$address = $this->addressRepository->getById($addressId);
+ // phpcs:ignore Magento2.CodeAnalysis.EmptyBlock
} catch (\Exception $e) {
//
}
@@ -592,6 +595,7 @@ public function setQuoteCustomerBillingAddress($addressId)
}
try {
$address = $this->addressRepository->getById($addressId);
+ // phpcs:ignore Magento2.CodeAnalysis.EmptyBlock
} catch (\Exception $e) {
//
}
@@ -716,7 +720,7 @@ protected function _prepareOrder(\Magento\Quote\Model\Quote\Address $address)
);
$orderItem = $this->quoteItemToOrderItem->convert($item);
if ($item->getParentItem()) {
- $orderItem->setParentItem($order->getItemByQuoteItemId($item->getParentItem()->getId()));
+ $orderItem->setParentItem($order->getItemByQuoteItemId($_quoteItem->getParentItem()->getId()));
}
$order->addItem($orderItem);
}
@@ -825,7 +829,7 @@ public function createOrders()
if ($order->getCanSendNewEmailFlag()) {
$this->orderSender->send($order);
}
- $placedAddressItems = array_merge($placedAddressItems, $this->getQuoteAddressItems($order));
+ $placedAddressItems = $this->getPlacedAddressItems($order);
}
$addressErrors = [];
@@ -1090,10 +1094,14 @@ public function getCustomer()
*/
protected function isAddressIdApplicable($addressId)
{
- $applicableAddressIds = array_map(function ($address) {
- /** @var \Magento\Customer\Api\Data\AddressInterface $address */
- return $address->getId();
- }, $this->getCustomer()->getAddresses());
+ $applicableAddressIds = array_map(
+ function ($address) {
+ /** @var \Magento\Customer\Api\Data\AddressInterface $address */
+ return $address->getId();
+ },
+ $this->getCustomer()->getAddresses()
+ );
+
return !is_numeric($addressId) || in_array($addressId, $applicableAddressIds);
}
@@ -1279,4 +1287,20 @@ private function getQuoteAddressItems(OrderInterface $order): array
return $placedAddressItems;
}
+
+ /**
+ * Returns placed address items
+ *
+ * @param OrderInterface $order
+ * @return array
+ */
+ private function getPlacedAddressItems(OrderInterface $order): array
+ {
+ $placedAddressItems = [];
+ foreach ($this->getQuoteAddressItems($order) as $key => $quoteAddressItem) {
+ $placedAddressItems[$key] = $quoteAddressItem;
+ }
+
+ return $placedAddressItems;
+ }
}
diff --git a/app/code/Magento/Multishipping/Test/Mftf/ActionGroup/CheckingWithMinicartActionGroup.xml b/app/code/Magento/Multishipping/Test/Mftf/ActionGroup/CheckingWithMinicartActionGroup.xml
new file mode 100644
index 0000000000000..f648c1026b539
--- /dev/null
+++ b/app/code/Magento/Multishipping/Test/Mftf/ActionGroup/CheckingWithMinicartActionGroup.xml
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+
+
+
+
+
+ $shippingMethodSubtotalPrice
+ $shippingMethodRadioText
+
+
+
+
diff --git a/app/code/Magento/Multishipping/Test/Mftf/ActionGroup/CheckingWithMultipleAddressesActionGroup.xml b/app/code/Magento/Multishipping/Test/Mftf/ActionGroup/CheckingWithMultipleAddressesActionGroup.xml
new file mode 100644
index 0000000000000..333c2aec6c28e
--- /dev/null
+++ b/app/code/Magento/Multishipping/Test/Mftf/ActionGroup/CheckingWithMultipleAddressesActionGroup.xml
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Multishipping/Test/Mftf/ActionGroup/PlaceOrderActionGroup.xml b/app/code/Magento/Multishipping/Test/Mftf/ActionGroup/PlaceOrderActionGroup.xml
new file mode 100644
index 0000000000000..efb860e314780
--- /dev/null
+++ b/app/code/Magento/Multishipping/Test/Mftf/ActionGroup/PlaceOrderActionGroup.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Multishipping/Test/Mftf/ActionGroup/ReviewOrderActionGroup.xml b/app/code/Magento/Multishipping/Test/Mftf/ActionGroup/ReviewOrderActionGroup.xml
new file mode 100644
index 0000000000000..af7d897910ca3
--- /dev/null
+++ b/app/code/Magento/Multishipping/Test/Mftf/ActionGroup/ReviewOrderActionGroup.xml
@@ -0,0 +1,39 @@
+
+
+
+
+
+
+
+
+
+ $shippingMethodSubtotalPrice
+ $shippingMethodBasePrice
+
+
+
+
+
+
+
+
+ $firstShippingMethodSubtotalPrice
+ $firstShippingMethodBasePrice
+
+
+
+
+
+ $secondShippingMethodSubtotalPrice
+ $secondShippingMethodBasePrice
+
+
+
+
+
diff --git a/app/code/Magento/Multishipping/Test/Mftf/ActionGroup/SelectBillingInfoActionGroup.xml b/app/code/Magento/Multishipping/Test/Mftf/ActionGroup/SelectBillingInfoActionGroup.xml
new file mode 100644
index 0000000000000..3f7578953df70
--- /dev/null
+++ b/app/code/Magento/Multishipping/Test/Mftf/ActionGroup/SelectBillingInfoActionGroup.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Multishipping/Test/Mftf/ActionGroup/SelectShippingInfoActionGroup.xml b/app/code/Magento/Multishipping/Test/Mftf/ActionGroup/SelectShippingInfoActionGroup.xml
new file mode 100644
index 0000000000000..af0b2467862ba
--- /dev/null
+++ b/app/code/Magento/Multishipping/Test/Mftf/ActionGroup/SelectShippingInfoActionGroup.xml
@@ -0,0 +1,33 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Multishipping/Test/Mftf/Page/MultishippingCheckoutPage.xml b/app/code/Magento/Multishipping/Test/Mftf/Page/MultishippingCheckoutPage.xml
new file mode 100644
index 0000000000000..001002e98271c
--- /dev/null
+++ b/app/code/Magento/Multishipping/Test/Mftf/Page/MultishippingCheckoutPage.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Multishipping/Test/Mftf/Section/MinicartSection.xml b/app/code/Magento/Multishipping/Test/Mftf/Section/MinicartSection.xml
new file mode 100644
index 0000000000000..1a31911bd185e
--- /dev/null
+++ b/app/code/Magento/Multishipping/Test/Mftf/Section/MinicartSection.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Multishipping/Test/Mftf/Section/MultishippingSection.xml b/app/code/Magento/Multishipping/Test/Mftf/Section/MultishippingSection.xml
index e7d57af1172c6..45fafc3105c38 100644
--- a/app/code/Magento/Multishipping/Test/Mftf/Section/MultishippingSection.xml
+++ b/app/code/Magento/Multishipping/Test/Mftf/Section/MultishippingSection.xml
@@ -8,7 +8,17 @@
+
diff --git a/app/code/Magento/Multishipping/Test/Mftf/Section/PaymentMethodSection.xml b/app/code/Magento/Multishipping/Test/Mftf/Section/PaymentMethodSection.xml
new file mode 100644
index 0000000000000..4e7f4a497ad4d
--- /dev/null
+++ b/app/code/Magento/Multishipping/Test/Mftf/Section/PaymentMethodSection.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
diff --git a/app/code/Magento/Multishipping/Test/Mftf/Section/ReviewOrderSection.xml b/app/code/Magento/Multishipping/Test/Mftf/Section/ReviewOrderSection.xml
new file mode 100644
index 0000000000000..e13f28929dcc8
--- /dev/null
+++ b/app/code/Magento/Multishipping/Test/Mftf/Section/ReviewOrderSection.xml
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Multishipping/Test/Mftf/Section/ShippingMethodSection.xml b/app/code/Magento/Multishipping/Test/Mftf/Section/ShippingMethodSection.xml
new file mode 100644
index 0000000000000..6a2290bcf1a43
--- /dev/null
+++ b/app/code/Magento/Multishipping/Test/Mftf/Section/ShippingMethodSection.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Multishipping/Test/Mftf/Test/StoreFrontCheckingWithMultishipmentTest.xml b/app/code/Magento/Multishipping/Test/Mftf/Test/StoreFrontCheckingWithMultishipmentTest.xml
new file mode 100644
index 0000000000000..3a58ead3b6dfa
--- /dev/null
+++ b/app/code/Magento/Multishipping/Test/Mftf/Test/StoreFrontCheckingWithMultishipmentTest.xml
@@ -0,0 +1,64 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Multishipping/Test/Mftf/Test/StoreFrontCheckingWithSingleShipmentTest.xml b/app/code/Magento/Multishipping/Test/Mftf/Test/StoreFrontCheckingWithSingleShipmentTest.xml
new file mode 100644
index 0000000000000..c9f1856249762
--- /dev/null
+++ b/app/code/Magento/Multishipping/Test/Mftf/Test/StoreFrontCheckingWithSingleShipmentTest.xml
@@ -0,0 +1,64 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Multishipping/Test/Mftf/Test/StoreFrontMinicartWithMultishipmentTest.xml b/app/code/Magento/Multishipping/Test/Mftf/Test/StoreFrontMinicartWithMultishipmentTest.xml
new file mode 100644
index 0000000000000..d52ddb11212aa
--- /dev/null
+++ b/app/code/Magento/Multishipping/Test/Mftf/Test/StoreFrontMinicartWithMultishipmentTest.xml
@@ -0,0 +1,67 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Multishipping/Test/Mftf/Test/StorefrontVerifySecureURLRedirectMultishippingTest.xml b/app/code/Magento/Multishipping/Test/Mftf/Test/StorefrontVerifySecureURLRedirectMultishippingTest.xml
new file mode 100644
index 0000000000000..085a710f2671c
--- /dev/null
+++ b/app/code/Magento/Multishipping/Test/Mftf/Test/StorefrontVerifySecureURLRedirectMultishippingTest.xml
@@ -0,0 +1,57 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Multishipping/Test/Unit/Controller/Checkout/PluginTest.php b/app/code/Magento/Multishipping/Test/Unit/Controller/Checkout/PluginTest.php
index 1b1474dbed28a..a26f2661ebab1 100644
--- a/app/code/Magento/Multishipping/Test/Unit/Controller/Checkout/PluginTest.php
+++ b/app/code/Magento/Multishipping/Test/Unit/Controller/Checkout/PluginTest.php
@@ -4,6 +4,9 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
+
+declare(strict_types=1);
+
namespace Magento\Multishipping\Test\Unit\Controller\Checkout;
use Magento\Multishipping\Controller\Checkout\Plugin;
@@ -30,16 +33,27 @@ protected function setUp()
$this->cartMock = $this->createMock(\Magento\Checkout\Model\Cart::class);
$this->quoteMock = $this->createPartialMock(
\Magento\Quote\Model\Quote::class,
- ['__wakeUp', 'setIsMultiShipping']
+ ['__wakeUp', 'setIsMultiShipping', 'getIsMultiShipping']
);
$this->cartMock->expects($this->once())->method('getQuote')->will($this->returnValue($this->quoteMock));
$this->object = new \Magento\Multishipping\Controller\Checkout\Plugin($this->cartMock);
}
- public function testExecuteTurnsOffMultishippingModeOnQuote()
+ public function testExecuteTurnsOffMultishippingModeOnMultishippingQuote(): void
{
$subject = $this->createMock(\Magento\Checkout\Controller\Index\Index::class);
+ $this->quoteMock->expects($this->once())->method('getIsMultiShipping')->willReturn(1);
$this->quoteMock->expects($this->once())->method('setIsMultiShipping')->with(0);
+ $this->cartMock->expects($this->once())->method('saveQuote');
+ $this->object->beforeExecute($subject);
+ }
+
+ public function testExecuteTurnsOffMultishippingModeOnNotMultishippingQuote(): void
+ {
+ $subject = $this->createMock(\Magento\Checkout\Controller\Index\Index::class);
+ $this->quoteMock->expects($this->once())->method('getIsMultiShipping')->willReturn(0);
+ $this->quoteMock->expects($this->never())->method('setIsMultiShipping');
+ $this->cartMock->expects($this->never())->method('saveQuote');
$this->object->beforeExecute($subject);
}
}
diff --git a/app/code/Magento/Multishipping/Test/Unit/Model/Cart/CartTotalRepositoryPluginTest.php b/app/code/Magento/Multishipping/Test/Unit/Model/Cart/CartTotalRepositoryPluginTest.php
new file mode 100644
index 0000000000000..73b0b9ef3ca7a
--- /dev/null
+++ b/app/code/Magento/Multishipping/Test/Unit/Model/Cart/CartTotalRepositoryPluginTest.php
@@ -0,0 +1,81 @@
+quoteRepositoryMock = $this->createMock(\Magento\Quote\Api\CartRepositoryInterface::class);
+ $this->modelRepository = new \Magento\Multishipping\Model\Cart\CartTotalRepositoryPlugin(
+ $this->quoteRepositoryMock
+ );
+ }
+
+ /**
+ * Test quotTotal from cartRepository after get($cartId) function is called
+ */
+ public function testAfterGet()
+ {
+ $cartId = "10";
+ $shippingMethod = 'flatrate_flatrate';
+ $shippingPrice = '10.00';
+ $quoteMock = $this->createPartialMock(
+ \Magento\Quote\Model\Cart\Totals::class,
+ [
+ 'getStore',
+ 'getShippingAddress',
+ 'getIsMultiShipping'
+ ]
+ );
+ $this->quoteRepositoryMock->expects($this->once())->method('getActive')->with($cartId)->willReturn($quoteMock);
+ $quoteMock->expects($this->once())->method('getIsMultiShipping')->willReturn(true);
+ $shippingAddressMock = $this->createPartialMock(
+ \Magento\Quote\Model\Quote\Address::class,
+ [
+ 'getShippingMethod',
+ 'getShippingRateByCode',
+ 'getShippingAmount'
+ ]
+ );
+ $quoteMock->expects($this->any())->method('getShippingAddress')->willReturn($shippingAddressMock);
+
+ $shippingAddressMock->expects($this->once())->method('getShippingMethod')->willReturn($shippingMethod);
+ $shippingAddressMock->expects($this->any())->method('getShippingAmount')->willReturn($shippingPrice);
+ $shippingRateMock = $this->createPartialMock(
+ \Magento\Quote\Model\Quote\Address\Rate::class,
+ [
+ 'getPrice'
+ ]
+ );
+ $shippingAddressMock->expects($this->once())->method('getShippingRateByCode')->willReturn($shippingRateMock);
+
+ $shippingRateMock->expects($this->once())->method('getPrice')->willReturn($shippingPrice);
+
+ $storeMock = $this->getMockBuilder(\Magento\Store\Model\Store::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+ $quoteMock->expects($this->any())->method('getStore')->willReturn($storeMock);
+ $storeMock->expects($this->any())->method('getBaseCurrency')->willReturnSelf();
+
+ $this->modelRepository->afterGet(
+ $this->createMock(\Magento\Quote\Model\Cart\CartTotalRepository::class),
+ $quoteMock,
+ $cartId
+ );
+ }
+}
diff --git a/app/code/Magento/Multishipping/composer.json b/app/code/Magento/Multishipping/composer.json
index 0d7982518d2c8..61f4aafe567db 100644
--- a/app/code/Magento/Multishipping/composer.json
+++ b/app/code/Magento/Multishipping/composer.json
@@ -5,7 +5,7 @@
"sort-packages": true
},
"require": {
- "php": "~7.1.3||~7.2.0",
+ "php": "~7.1.3||~7.2.0||~7.3.0",
"magento/framework": "*",
"magento/module-checkout": "*",
"magento/module-customer": "*",
diff --git a/app/code/Magento/Multishipping/etc/di.xml b/app/code/Magento/Multishipping/etc/di.xml
new file mode 100644
index 0000000000000..3bccf0b74bcd8
--- /dev/null
+++ b/app/code/Magento/Multishipping/etc/di.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Multishipping/view/frontend/templates/checkout/addresses.phtml b/app/code/Magento/Multishipping/view/frontend/templates/checkout/addresses.phtml
index faf08f77c02f3..418efa5033263 100644
--- a/app/code/Magento/Multishipping/view/frontend/templates/checkout/addresses.phtml
+++ b/app/code/Magento/Multishipping/view/frontend/templates/checkout/addresses.phtml
@@ -18,7 +18,7 @@
data-mage-init='{
"multiShipping":{},
"cartUpdate": {
- "validationURL": "/multishipping/checkout/checkItems",
+ "validationURL": "= $block->escapeUrl($block->getUrl('multishipping/checkout/checkItems')) ?>",
"eventName": "updateMulticartItemQty"
}}'
action="= $block->escapeUrl($block->getPostActionUrl()) ?>"
@@ -62,8 +62,9 @@
name="ship[= $block->escapeHtml($_index) ?>][= $block->escapeHtml($_item->getQuoteItemId()) ?>][qty]"
value="= $block->escapeHtml($_item->getQty()) ?>"
size="2"
+ min="0"
class="input-text qty"
- data-validate="{number: true}"/>
+ data-validate="{number: true, required:true, 'validate-greater-than-zero':true}"/>
diff --git a/app/code/Magento/Multishipping/view/frontend/templates/checkout/item/default.phtml b/app/code/Magento/Multishipping/view/frontend/templates/checkout/item/default.phtml
index 51d964957c4d5..a696a693fa002 100644
--- a/app/code/Magento/Multishipping/view/frontend/templates/checkout/item/default.phtml
+++ b/app/code/Magento/Multishipping/view/frontend/templates/checkout/item/default.phtml
@@ -12,12 +12,12 @@
getFormatedOptionValue($_option) ?>
= $block->escapeHtml($_option['label']) ?>
-
- = $block->escapeHtml($_formatedOptionValue['value']) ?>
+ >
+ = $block->escapeHtml($_formatedOptionValue['value'], ['span']) ?>
= $block->escapeHtml($_option['label']) ?>
- = $block->escapeHtml($_formatedOptionValue['full_view']) ?>
+ = $block->escapeHtml($_formatedOptionValue['full_view'], ['span']) ?>
diff --git a/app/code/Magento/Multishipping/view/frontend/web/js/multi-shipping.js b/app/code/Magento/Multishipping/view/frontend/web/js/multi-shipping.js
index 88f384758f715..537abb3aa2071 100644
--- a/app/code/Magento/Multishipping/view/frontend/web/js/multi-shipping.js
+++ b/app/code/Magento/Multishipping/view/frontend/web/js/multi-shipping.js
@@ -5,7 +5,7 @@
define([
'jquery',
- 'jquery/ui'
+ 'jquery-ui-modules/widget'
], function ($) {
'use strict';
diff --git a/app/code/Magento/Multishipping/view/frontend/web/js/overview.js b/app/code/Magento/Multishipping/view/frontend/web/js/overview.js
index 3a6d73e304974..4906a2066cecd 100644
--- a/app/code/Magento/Multishipping/view/frontend/web/js/overview.js
+++ b/app/code/Magento/Multishipping/view/frontend/web/js/overview.js
@@ -5,7 +5,7 @@
define([
'jquery',
- 'jquery/ui',
+ 'jquery-ui-modules/widget',
'mage/translate'
], function ($) {
'use strict';
diff --git a/app/code/Magento/Multishipping/view/frontend/web/js/payment.js b/app/code/Magento/Multishipping/view/frontend/web/js/payment.js
index da24b99597d42..e185123372e23 100644
--- a/app/code/Magento/Multishipping/view/frontend/web/js/payment.js
+++ b/app/code/Magento/Multishipping/view/frontend/web/js/payment.js
@@ -7,7 +7,7 @@ define([
'jquery',
'mage/template',
'Magento_Ui/js/modal/alert',
- 'jquery/ui',
+ 'jquery-ui-modules/widget',
'mage/translate'
], function ($, mageTemplate, alert) {
'use strict';
diff --git a/app/code/Magento/MysqlMq/composer.json b/app/code/Magento/MysqlMq/composer.json
index d9bcf307a5bf9..413195c6f6d7c 100644
--- a/app/code/Magento/MysqlMq/composer.json
+++ b/app/code/Magento/MysqlMq/composer.json
@@ -8,7 +8,7 @@
"magento/framework": "*",
"magento/magento-composer-installer": "*",
"magento/module-store": "*",
- "php": "~7.1.3||~7.2.0"
+ "php": "~7.1.3||~7.2.0||~7.3.0"
},
"type": "magento2-module",
"license": [
diff --git a/app/code/Magento/MysqlMq/etc/adminhtml/system.xml b/app/code/Magento/MysqlMq/etc/adminhtml/system.xml
index 2684f2e0c98bf..045a176a48e87 100644
--- a/app/code/Magento/MysqlMq/etc/adminhtml/system.xml
+++ b/app/code/Magento/MysqlMq/etc/adminhtml/system.xml
@@ -13,15 +13,19 @@
All the times are in minutes. Use "0" if you want to skip automatic clearance.
Retry Messages In Progress After
+ validate-zero-or-greater validate-digits
Successful Messages Lifetime
+ validate-zero-or-greater validate-digits
Failed Messages Lifetime
+ validate-zero-or-greater validate-digits
New Messages Lifetime
+ validate-zero-or-greater validate-digits
diff --git a/app/code/Magento/NewRelicReporting/composer.json b/app/code/Magento/NewRelicReporting/composer.json
index 25e7193ce0e2f..f5301ba2b6a39 100644
--- a/app/code/Magento/NewRelicReporting/composer.json
+++ b/app/code/Magento/NewRelicReporting/composer.json
@@ -5,7 +5,7 @@
"sort-packages": true
},
"require": {
- "php": "~7.1.3||~7.2.0",
+ "php": "~7.1.3||~7.2.0||~7.3.0",
"magento/framework": "*",
"magento/magento-composer-installer": "*",
"magento/module-backend": "*",
diff --git a/app/code/Magento/Newsletter/Model/Config.php b/app/code/Magento/Newsletter/Model/Config.php
new file mode 100644
index 0000000000000..c469d35e74f72
--- /dev/null
+++ b/app/code/Magento/Newsletter/Model/Config.php
@@ -0,0 +1,48 @@
+scopeConfig = $scopeConfig;
+ }
+
+ /**
+ * Returns newsletter's enabled status
+ *
+ * @param string $scopeType
+ * @return bool
+ */
+ public function isActive(string $scopeType = ScopeConfigInterface::SCOPE_TYPE_DEFAULT): bool
+ {
+ return $this->scopeConfig->isSetFlag(self::XML_PATH_NEWSLETTER_ACTIVE, $scopeType);
+ }
+}
diff --git a/app/code/Magento/Newsletter/Model/Queue/TransportBuilder.php b/app/code/Magento/Newsletter/Model/Queue/TransportBuilder.php
index 502a19d298c46..aa3a2bcfe0f59 100644
--- a/app/code/Magento/Newsletter/Model/Queue/TransportBuilder.php
+++ b/app/code/Magento/Newsletter/Model/Queue/TransportBuilder.php
@@ -3,10 +3,29 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
+declare(strict_types=1);
+
namespace Magento\Newsletter\Model\Queue;
use Magento\Email\Model\AbstractTemplate;
+use Magento\Framework\Exception\MailException;
+use Magento\Framework\Mail\EmailMessageInterfaceFactory;
+use Magento\Framework\Mail\AddressConverter;
+use Magento\Framework\Mail\MessageInterface;
+use Magento\Framework\Mail\MessageInterfaceFactory;
+use Magento\Framework\Mail\MimeMessageInterfaceFactory;
+use Magento\Framework\Mail\MimePartInterfaceFactory;
+use Magento\Framework\Mail\Template\FactoryInterface;
+use Magento\Framework\Mail\Template\SenderResolverInterface;
+use Magento\Framework\Mail\TemplateInterface;
+use Magento\Framework\Mail\TransportInterfaceFactory;
+use Magento\Framework\ObjectManagerInterface;
+/**
+ * Class TransportBuilder
+ *
+ * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
+ */
class TransportBuilder extends \Magento\Framework\Mail\Template\TransportBuilder
{
/**
@@ -16,6 +35,194 @@ class TransportBuilder extends \Magento\Framework\Mail\Template\TransportBuilder
*/
protected $templateData = [];
+ /**
+ * Param that used for storing all message data until it will be used
+ *
+ * @var array
+ */
+ private $messageData = [];
+
+ /**
+ * @var EmailMessageInterfaceFactory
+ */
+ private $emailMessageInterfaceFactory;
+
+ /**
+ * @var MimeMessageInterfaceFactory
+ */
+ private $mimeMessageInterfaceFactory;
+
+ /**
+ * @var MimePartInterfaceFactory
+ */
+ private $mimePartInterfaceFactory;
+
+ /**
+ * @var AddressConverter|null
+ */
+ private $addressConverter;
+
+ /**
+ * TransportBuilder constructor
+ *
+ * @param FactoryInterface $templateFactory
+ * @param MessageInterface $message
+ * @param SenderResolverInterface $senderResolver
+ * @param ObjectManagerInterface $objectManager
+ * @param TransportInterfaceFactory $mailTransportFactory
+ * @param MessageInterfaceFactory|null $messageFactory
+ * @param EmailMessageInterfaceFactory|null $emailMessageInterfaceFactory
+ * @param MimeMessageInterfaceFactory|null $mimeMessageInterfaceFactory
+ * @param MimePartInterfaceFactory|null $mimePartInterfaceFactory
+ * @param AddressConverter|null $addressConverter
+ * @SuppressWarnings(PHPMD.UnusedFormalParameter)
+ * @SuppressWarnings(PHPMD.ExcessiveParameterList)
+ */
+ public function __construct(
+ FactoryInterface $templateFactory,
+ MessageInterface $message,
+ SenderResolverInterface $senderResolver,
+ ObjectManagerInterface $objectManager,
+ TransportInterfaceFactory $mailTransportFactory,
+ MessageInterfaceFactory $messageFactory = null,
+ EmailMessageInterfaceFactory $emailMessageInterfaceFactory = null,
+ MimeMessageInterfaceFactory $mimeMessageInterfaceFactory = null,
+ MimePartInterfaceFactory $mimePartInterfaceFactory = null,
+ AddressConverter $addressConverter = null
+ ) {
+ parent::__construct(
+ $templateFactory,
+ $message,
+ $senderResolver,
+ $objectManager,
+ $mailTransportFactory,
+ $messageFactory,
+ $emailMessageInterfaceFactory,
+ $mimeMessageInterfaceFactory,
+ $mimePartInterfaceFactory,
+ $addressConverter
+ );
+ $this->emailMessageInterfaceFactory = $emailMessageInterfaceFactory ?: $this->objectManager
+ ->get(EmailMessageInterfaceFactory::class);
+ $this->mimeMessageInterfaceFactory = $mimeMessageInterfaceFactory ?: $this->objectManager
+ ->get(MimeMessageInterfaceFactory::class);
+ $this->mimePartInterfaceFactory = $mimePartInterfaceFactory ?: $this->objectManager
+ ->get(MimePartInterfaceFactory::class);
+ $this->addressConverter = $addressConverter ?: $this->objectManager
+ ->get(AddressConverter::class);
+ }
+
+ /**
+ * Add cc address
+ *
+ * @param array|string $address
+ * @param string $name
+ *
+ * @return \Magento\Framework\Mail\Template\TransportBuilder
+ * @throws MailException
+ */
+ public function addCc($address, $name = '')
+ {
+ $this->addAddressByType('cc', $address, $name);
+
+ return $this;
+ }
+
+ /**
+ * Add to address
+ *
+ * @param array|string $address
+ * @param string $name
+ *
+ * @return $this
+ * @throws MailException
+ */
+ public function addTo($address, $name = '')
+ {
+ $this->addAddressByType('to', $address, $name);
+
+ return $this;
+ }
+
+ /**
+ * Add bcc address
+ *
+ * @param array|string $address
+ *
+ * @return $this
+ * @throws MailException
+ */
+ public function addBcc($address)
+ {
+ $this->addAddressByType('bcc', $address);
+
+ return $this;
+ }
+
+ /**
+ * Set Reply-To Header
+ *
+ * @param string $email
+ * @param string|null $name
+ *
+ * @return $this
+ * @throws MailException
+ */
+ public function setReplyTo($email, $name = null)
+ {
+
+ $this->addAddressByType('replyTo', $email, $name);
+
+ return $this;
+ }
+
+ /**
+ * Set mail from address
+ *
+ * @param string|array $from
+ *
+ * @return $this
+ * @throws MailException
+ * @see setFromByScope()
+ *
+ * @deprecated This function sets the from address but does not provide
+ * a way of setting the correct from addresses based on the scope.
+ */
+ public function setFrom($from)
+ {
+ return $this->setFromByScope($from);
+ }
+
+ /**
+ * Set mail from address by scopeId
+ *
+ * @param string|array $from
+ * @param string|int $scopeId
+ *
+ * @return $this
+ * @throws MailException
+ */
+ public function setFromByScope($from, $scopeId = null)
+ {
+ $result = $this->_senderResolver->resolve($from, $scopeId);
+ $this->addAddressByType('from', $result['email'], $result['name']);
+
+ return $this;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ protected function reset()
+ {
+ $this->messageData = [];
+ $this->templateIdentifier = null;
+ $this->templateVars = null;
+ $this->templateOptions = null;
+
+ return $this;
+ }
+
/**
* Set template data
*
@@ -25,11 +232,15 @@ class TransportBuilder extends \Magento\Framework\Mail\Template\TransportBuilder
public function setTemplateData($data)
{
$this->templateData = $data;
+
return $this;
}
/**
+ * Sets up template filter
+ *
* @param AbstractTemplate $template
+ *
* @return void
*/
protected function setTemplateFilter(AbstractTemplate $template)
@@ -44,16 +255,44 @@ protected function setTemplateFilter(AbstractTemplate $template)
*/
protected function prepareMessage()
{
- /** @var AbstractTemplate $template */
+ /** @var AbstractTemplate|TemplateInterface $template */
$template = $this->getTemplate()->setData($this->templateData);
$this->setTemplateFilter($template);
+ $content = $template->getProcessedTemplate($this->templateVars);
+ $this->messageData['subject'] = $template->getSubject();
- $this->message->setBodyHtml(
- $template->getProcessedTemplate($this->templateVars)
- )->setSubject(
- $template->getSubject()
+ $mimePart = $this->mimePartInterfaceFactory->create(
+ ['content' => $content]
+ );
+ $this->messageData['body'] = $this->mimeMessageInterfaceFactory->create(
+ ['parts' => [$mimePart]]
);
+ $this->message = $this->emailMessageInterfaceFactory->create($this->messageData);
+
return $this;
}
+
+ /**
+ * Handles possible incoming types of email (string or array)
+ *
+ * @param string $addressType
+ * @param string|array $email
+ * @param string|null $name
+ *
+ * @return void
+ * @throws MailException
+ */
+ private function addAddressByType(string $addressType, $email, ?string $name = null): void
+ {
+ if (is_array($email)) {
+ $this->messageData[$addressType] = array_merge(
+ $this->messageData[$addressType],
+ $this->addressConverter->convertMany($email)
+ );
+
+ return;
+ }
+ $this->messageData[$addressType][] = $this->addressConverter->convert($email, $name);
+ }
}
diff --git a/app/code/Magento/Newsletter/Model/Subscriber.php b/app/code/Magento/Newsletter/Model/Subscriber.php
index 117783495406a..c5eee5e3cf771 100644
--- a/app/code/Magento/Newsletter/Model/Subscriber.php
+++ b/app/code/Magento/Newsletter/Model/Subscriber.php
@@ -353,11 +353,7 @@ public function isStatusChanged()
*/
public function isSubscribed()
{
- if ($this->getId() && $this->getStatus() == self::STATUS_SUBSCRIBED) {
- return true;
- }
-
- return false;
+ return $this->getId() && (int)$this->getStatus() === self::STATUS_SUBSCRIBED;
}
/**
@@ -495,7 +491,6 @@ public function subscribe($email)
$this->sendConfirmationSuccessEmail();
}
return $this->getStatus();
- // phpcs:ignore Magento2.Exceptions.ThrowCatch
} catch (\Exception $e) {
// phpcs:ignore Magento2.Exceptions.DirectThrow
throw new \Exception($e->getMessage());
diff --git a/app/code/Magento/Newsletter/Observer/PredispatchNewsletterObserver.php b/app/code/Magento/Newsletter/Observer/PredispatchNewsletterObserver.php
index 9860798b2b9f3..f63520e79496f 100644
--- a/app/code/Magento/Newsletter/Observer/PredispatchNewsletterObserver.php
+++ b/app/code/Magento/Newsletter/Observer/PredispatchNewsletterObserver.php
@@ -12,6 +12,8 @@
use Magento\Framework\Event\ObserverInterface;
use Magento\Framework\UrlInterface;
use Magento\Store\Model\ScopeInterface;
+use Magento\Newsletter\Model\Config;
+use Magento\Framework\App\ObjectManager;
/**
* Class PredispatchNewsletterObserver
@@ -19,10 +21,16 @@
class PredispatchNewsletterObserver implements ObserverInterface
{
/**
- * Configuration path to newsletter active setting
+ * @deprecated
+ * @see \Magento\Newsletter\Model\Config::isActive()
*/
const XML_PATH_NEWSLETTER_ACTIVE = 'newsletter/general/active';
+ /**
+ * @var Config
+ */
+ private $newsletterConfig;
+
/**
* @var ScopeConfigInterface
*/
@@ -38,11 +46,16 @@ class PredispatchNewsletterObserver implements ObserverInterface
*
* @param ScopeConfigInterface $scopeConfig
* @param UrlInterface $url
+ * @param Config|null $newsletterConfig
*/
- public function __construct(ScopeConfigInterface $scopeConfig, UrlInterface $url)
- {
+ public function __construct(
+ ScopeConfigInterface $scopeConfig,
+ UrlInterface $url,
+ Config $newsletterConfig = null
+ ) {
$this->scopeConfig = $scopeConfig;
$this->url = $url;
+ $this->newsletterConfig = $newsletterConfig ?: ObjectManager::getInstance()->get(Config::class);
}
/**
@@ -52,11 +65,7 @@ public function __construct(ScopeConfigInterface $scopeConfig, UrlInterface $url
*/
public function execute(Observer $observer) : void
{
- if (!$this->scopeConfig->getValue(
- self::XML_PATH_NEWSLETTER_ACTIVE,
- ScopeInterface::SCOPE_STORE
- )
- ) {
+ if (!$this->newsletterConfig->isActive(ScopeInterface::SCOPE_STORE)) {
$defaultNoRouteUrl = $this->scopeConfig->getValue(
'web/default/no_route',
ScopeInterface::SCOPE_STORE
diff --git a/app/code/Magento/Newsletter/Test/Mftf/ActionGroup/AdminNewsletterTemplateActionGroup.xml b/app/code/Magento/Newsletter/Test/Mftf/ActionGroup/AdminNewsletterTemplateActionGroup.xml
new file mode 100644
index 0000000000000..bd6842f785ecf
--- /dev/null
+++ b/app/code/Magento/Newsletter/Test/Mftf/ActionGroup/AdminNewsletterTemplateActionGroup.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Newsletter/Test/Mftf/ActionGroup/VerifySubscribedNewsletterDisplayedActionGroup.xml b/app/code/Magento/Newsletter/Test/Mftf/ActionGroup/VerifySubscribedNewsletterDisplayedActionGroup.xml
index 059f157c407c9..69b973dbddbda 100644
--- a/app/code/Magento/Newsletter/Test/Mftf/ActionGroup/VerifySubscribedNewsletterDisplayedActionGroup.xml
+++ b/app/code/Magento/Newsletter/Test/Mftf/ActionGroup/VerifySubscribedNewsletterDisplayedActionGroup.xml
@@ -5,28 +5,40 @@
* See COPYING.txt for license details.
*/
-->
+
-
+
+ EXTENDS: SignUpNewUserFromStorefrontActionGroup. Clicks on 'Sign Up for Newsletter'. Validates that the Subscription Confirmation message is present and correct.
+
+
-
+
+
+ EXTENDS: SignUpNewUserFromStorefrontActionGroup. Validates that the you are NOT subscribed message is present and correct.
+
+
-
+
+
+ Goes to the Storefront Newsletter Management page. Validates that the 'Subscription' checkbox is checked.
+
+
diff --git a/app/code/Magento/Newsletter/Test/Mftf/Section/NewsletterTemplateSection.xml b/app/code/Magento/Newsletter/Test/Mftf/Section/NewsletterTemplateSection.xml
index d0dfd21cc2e1f..4c8f641f78ae3 100644
--- a/app/code/Magento/Newsletter/Test/Mftf/Section/NewsletterTemplateSection.xml
+++ b/app/code/Magento/Newsletter/Test/Mftf/Section/NewsletterTemplateSection.xml
@@ -12,14 +12,14 @@
-
+
-
-
+
+
@@ -51,6 +51,6 @@
-
+
diff --git a/app/code/Magento/Newsletter/Test/Mftf/Test/AdminAddImageToWYSIWYGNewsletterTest.xml b/app/code/Magento/Newsletter/Test/Mftf/Test/AdminAddImageToWYSIWYGNewsletterTest.xml
index a343a20a6d57c..0371c0265d149 100644
--- a/app/code/Magento/Newsletter/Test/Mftf/Test/AdminAddImageToWYSIWYGNewsletterTest.xml
+++ b/app/code/Magento/Newsletter/Test/Mftf/Test/AdminAddImageToWYSIWYGNewsletterTest.xml
@@ -16,9 +16,6 @@
-
-
-
@@ -54,8 +51,7 @@
-
-
+
diff --git a/app/code/Magento/Newsletter/Test/Mftf/Test/AdminAddVariableToWYSIWYGNewsletterTest.xml b/app/code/Magento/Newsletter/Test/Mftf/Test/AdminAddVariableToWYSIWYGNewsletterTest.xml
index 841d202d518ab..b6f78b6f33792 100644
--- a/app/code/Magento/Newsletter/Test/Mftf/Test/AdminAddVariableToWYSIWYGNewsletterTest.xml
+++ b/app/code/Magento/Newsletter/Test/Mftf/Test/AdminAddVariableToWYSIWYGNewsletterTest.xml
@@ -83,8 +83,7 @@
-
-
+
@@ -95,8 +94,7 @@
-
-
+
diff --git a/app/code/Magento/Newsletter/Test/Mftf/Test/AdminAddWidgetToWYSIWYGNewsletterTest.xml b/app/code/Magento/Newsletter/Test/Mftf/Test/AdminAddWidgetToWYSIWYGNewsletterTest.xml
index 016f07b8a2f44..a7ac9e38d4b07 100644
--- a/app/code/Magento/Newsletter/Test/Mftf/Test/AdminAddWidgetToWYSIWYGNewsletterTest.xml
+++ b/app/code/Magento/Newsletter/Test/Mftf/Test/AdminAddWidgetToWYSIWYGNewsletterTest.xml
@@ -51,7 +51,7 @@
-
+
diff --git a/app/code/Magento/Newsletter/Test/Mftf/Test/StorefrontVerifySecureURLRedirectNewsletterTest.xml b/app/code/Magento/Newsletter/Test/Mftf/Test/StorefrontVerifySecureURLRedirectNewsletterTest.xml
new file mode 100644
index 0000000000000..01b5e706fcefb
--- /dev/null
+++ b/app/code/Magento/Newsletter/Test/Mftf/Test/StorefrontVerifySecureURLRedirectNewsletterTest.xml
@@ -0,0 +1,42 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Newsletter/Test/Mftf/Test/VerifyTinyMCEv4IsNativeWYSIWYGOnNewsletterTest.xml b/app/code/Magento/Newsletter/Test/Mftf/Test/VerifyTinyMCEv4IsNativeWYSIWYGOnNewsletterTest.xml
index 3c19a3fa99d3c..722a0dd22119d 100644
--- a/app/code/Magento/Newsletter/Test/Mftf/Test/VerifyTinyMCEv4IsNativeWYSIWYGOnNewsletterTest.xml
+++ b/app/code/Magento/Newsletter/Test/Mftf/Test/VerifyTinyMCEv4IsNativeWYSIWYGOnNewsletterTest.xml
@@ -29,6 +29,7 @@
+
@@ -42,7 +43,7 @@
-
+
diff --git a/app/code/Magento/Newsletter/Test/Unit/Model/Queue/TransportBuilderTest.php b/app/code/Magento/Newsletter/Test/Unit/Model/Queue/TransportBuilderTest.php
index e8b141a24c9e8..8f5626b42ff3e 100644
--- a/app/code/Magento/Newsletter/Test/Unit/Model/Queue/TransportBuilderTest.php
+++ b/app/code/Magento/Newsletter/Test/Unit/Model/Queue/TransportBuilderTest.php
@@ -1,78 +1,112 @@
-templateFactoryMock = $this->createMock(\Magento\Framework\Mail\Template\FactoryInterface::class);
- $this->messageMock = $this->getMockBuilder(\Magento\Framework\Mail\MessageInterface::class)
+ $objectManagerHelper = new ObjectManager($this);
+ $this->templateFactoryMock = $this->createMock(FactoryInterface::class);
+ $this->messageMock = $this->getMockBuilder(MessageInterface::class)
->disableOriginalConstructor()
->setMethods(['setBodyHtml', 'setSubject'])
->getMockForAbstractClass();
- $this->objectManagerMock = $this->createMock(\Magento\Framework\ObjectManagerInterface::class);
- $this->senderResolverMock = $this->createMock(\Magento\Framework\Mail\Template\SenderResolverInterface::class);
- $this->mailTransportFactoryMock = $this->getMockBuilder(
- \Magento\Framework\Mail\TransportInterfaceFactory::class
- )->disableOriginalConstructor()
- ->setMethods(['create'])
- ->getMock();
- $this->messageFactoryMock = $this->getMockBuilder(\Magento\Framework\Mail\MessageInterfaceFactory::class)
+
+ $this->emailMessageInterfaceFactoryMock = $this->createMock(EmailMessageInterfaceFactory::class);
+ $this->mimePartFactoryMock = $this->createMock(MimePartInterfaceFactory::class);
+
+ $this->objectManagerMock = $this->createMock(ObjectManagerInterface::class);
+ $this->senderResolverMock = $this->createMock(SenderResolverInterface::class);
+ $this->mailTransportFactoryMock = $this->getMockBuilder(TransportInterfaceFactory::class)
->disableOriginalConstructor()
->setMethods(['create'])
- ->getMockForAbstractClass();
- $this->messageFactoryMock->expects($this->atLeastOnce())->method('create')->willReturn($this->messageMock);
+ ->getMock();
+
$this->builder = $objectManagerHelper->getObject(
$this->builderClassName,
[
@@ -81,7 +115,9 @@ public function setUp()
'objectManager' => $this->objectManagerMock,
'senderResolver' => $this->senderResolverMock,
'mailTransportFactory' => $this->mailTransportFactoryMock,
- 'messageFactory' => $this->messageFactoryMock
+ 'messageFactory' => $this->messageFactoryMock,
+ 'emailMessageInterfaceFactory' => $this->emailMessageInterfaceFactoryMock,
+ 'mimePartInterfaceFactory' => $this->mimePartFactoryMock,
]
);
}
@@ -91,12 +127,13 @@ public function setUp()
* @param string $bodyText
* @return void
* @SuppressWarnings(PHPMD.ExcessiveMethodLength)
+ * @throws \Magento\Framework\Exception\LocalizedException
*/
public function testGetTransport(
$templateType = TemplateTypesInterface::TYPE_HTML,
$bodyText = 'Html message '
- ) {
- $filter = $this->createMock(\Magento\Email\Model\Template\Filter::class);
+ ): void {
+ $filter = $this->createMock(Filter::class);
$data = [
'template_subject' => 'Email Subject',
'template_text' => $bodyText,
@@ -106,39 +143,39 @@ public function testGetTransport(
];
$vars = ['reason' => 'Reason', 'customer' => 'Customer'];
$options = ['area' => 'frontend', 'store' => 1];
- $template = $this->createMock(\Magento\Email\Model\Template::class);
- $template->expects($this->once())->method('setVars')->with($this->equalTo($vars))->will($this->returnSelf());
- $template->expects(
- $this->once()
- )->method(
- 'setOptions'
- )->with(
- $this->equalTo($options)
- )->will(
- $this->returnSelf()
- );
- $template->expects($this->once())->method('getSubject')->will($this->returnValue('Email Subject'));
- $template->expects($this->once())->method('setData')->with($this->equalTo($data))->will($this->returnSelf());
- $template->expects($this->once())
- ->method('getProcessedTemplate')
- ->with($vars)
- ->willReturn($bodyText);
- $template->expects($this->once())
- ->method('setTemplateFilter')
- ->with($filter);
- $this->templateFactoryMock->expects(
- $this->once()
- )->method(
- 'get'
- )->with(
- $this->equalTo('identifier')
- )->will(
- $this->returnValue($template)
- );
+ /** @var MimePartInterface|MockObject $mimePartMock */
+ $mimePartMock = $this->createMock(MimePartInterface::class);
+
+ $this->mimePartFactoryMock->expects($this->any())
+ ->method('create')
+ ->willReturn($mimePartMock);
+
+ /** @var EmailMessageInterface|MockObject $emailMessage */
+ $emailMessage = $this->createMock(EmailMessageInterface::class);
+
+ $this->emailMessageInterfaceFactoryMock->expects($this->any())
+ ->method('create')
+ ->willReturn($emailMessage);
+
+ $template = $this->createMock(Template::class);
+ $template->expects($this->once())->method('setVars')
+ ->with($this->equalTo($vars))->will($this->returnSelf());
+ $template->expects($this->once())->method('setOptions')
+ ->with($this->equalTo($options))->will($this->returnSelf());
+ $template->expects($this->once())->method('getSubject')
+ ->willReturn('Email Subject');
+ $template->expects($this->once())->method('setData')
+ ->with($this->equalTo($data))->will($this->returnSelf());
+ $template->expects($this->once())->method('getProcessedTemplate')
+ ->with($vars)->willReturn($bodyText);
+ $template->expects($this->once())->method('setTemplateFilter')
+ ->with($filter);
- $this->messageMock->expects($this->once())->method('setBodyHtml')->willReturnSelf();
- $this->messageMock->expects($this->once())->method('setSubject')->willReturnSelf();
+ $this->templateFactoryMock->expects($this->once())
+ ->method('get')
+ ->with($this->equalTo('identifier'))
+ ->willReturn($template);
$this->builder->setTemplateIdentifier(
'identifier'
diff --git a/app/code/Magento/Newsletter/Test/Unit/Observer/PredispatchNewsletterObserverTest.php b/app/code/Magento/Newsletter/Test/Unit/Observer/PredispatchNewsletterObserverTest.php
index 38d69e5128af1..6846231319d69 100644
--- a/app/code/Magento/Newsletter/Test/Unit/Observer/PredispatchNewsletterObserverTest.php
+++ b/app/code/Magento/Newsletter/Test/Unit/Observer/PredispatchNewsletterObserverTest.php
@@ -13,6 +13,7 @@
use Magento\Framework\Event\Observer;
use Magento\Framework\TestFramework\Unit\Helper\ObjectManager;
use Magento\Framework\UrlInterface;
+use Magento\Newsletter\Model\Config;
use Magento\Newsletter\Observer\PredispatchNewsletterObserver;
use Magento\Store\Model\ScopeInterface;
use PHPUnit\Framework\TestCase;
@@ -52,30 +53,29 @@ class PredispatchNewsletterObserverTest extends TestCase
*/
private $objectManager;
+ /**
+ * @var Config
+ */
+ private $newsletterConfig;
+
/**
* @inheritdoc
*/
protected function setUp() : void
{
- $this->configMock = $this->getMockBuilder(ScopeConfigInterface::class)
- ->disableOriginalConstructor()
- ->getMock();
- $this->urlMock = $this->getMockBuilder(UrlInterface::class)
- ->disableOriginalConstructor()
- ->getMock();
+ $this->configMock = $this->createMock(ScopeConfigInterface::class);
+ $this->urlMock = $this->createMock(UrlInterface::class);
$this->responseMock = $this->getMockBuilder(ResponseInterface::class)
->disableOriginalConstructor()
->setMethods(['setRedirect'])
->getMockForAbstractClass();
- $this->redirectMock = $this->getMockBuilder(RedirectInterface::class)
- ->getMock();
+ $this->redirectMock = $this->createMock(RedirectInterface::class);
+ $this->newsletterConfig = $this->createMock(Config::class);
$this->objectManager = new ObjectManager($this);
- $this->mockObject = $this->objectManager->getObject(
- PredispatchNewsletterObserver::class,
- [
- 'scopeConfig' => $this->configMock,
- 'url' => $this->urlMock
- ]
+ $this->mockObject = new PredispatchNewsletterObserver(
+ $this->configMock,
+ $this->urlMock,
+ $this->newsletterConfig
);
}
@@ -89,8 +89,9 @@ public function testNewsletterEnabled() : void
->setMethods(['getResponse', 'getData', 'setRedirect'])
->getMockForAbstractClass();
- $this->configMock->method('getValue')
- ->with(PredispatchNewsletterObserver::XML_PATH_NEWSLETTER_ACTIVE, ScopeInterface::SCOPE_STORE)
+ $this->newsletterConfig->expects($this->once())
+ ->method('isActive')
+ ->with(ScopeInterface::SCOPE_STORE)
->willReturn(true);
$observerMock->expects($this->never())
->method('getData')
@@ -114,14 +115,13 @@ public function testNewsletterDisabled() : void
->setMethods(['getControllerAction', 'getResponse'])
->getMockForAbstractClass();
- $this->configMock->expects($this->at(0))
- ->method('getValue')
- ->with(PredispatchNewsletterObserver::XML_PATH_NEWSLETTER_ACTIVE, ScopeInterface::SCOPE_STORE)
+ $this->newsletterConfig->expects($this->once())
+ ->method('isActive')
+ ->with(ScopeInterface::SCOPE_STORE)
->willReturn(false);
$expectedRedirectUrl = 'https://test.com/index';
-
- $this->configMock->expects($this->at(1))
+ $this->configMock->expects($this->once())
->method('getValue')
->with('web/default/no_route', ScopeInterface::SCOPE_STORE)
->willReturn($expectedRedirectUrl);
diff --git a/app/code/Magento/Newsletter/composer.json b/app/code/Magento/Newsletter/composer.json
index dc1334af295c8..e6f22c4ab339b 100644
--- a/app/code/Magento/Newsletter/composer.json
+++ b/app/code/Magento/Newsletter/composer.json
@@ -5,7 +5,7 @@
"sort-packages": true
},
"require": {
- "php": "~7.1.3||~7.2.0",
+ "php": "~7.1.3||~7.2.0||~7.3.0",
"magento/framework": "*",
"magento/module-backend": "*",
"magento/module-cms": "*",
diff --git a/app/code/Magento/Newsletter/i18n/en_US.csv b/app/code/Magento/Newsletter/i18n/en_US.csv
index 388b583f990b1..f390f6792635d 100644
--- a/app/code/Magento/Newsletter/i18n/en_US.csv
+++ b/app/code/Magento/Newsletter/i18n/en_US.csv
@@ -152,3 +152,4 @@ Store,Store
"Store View","Store View"
"Newsletter Subscriptions","Newsletter Subscriptions"
"We have updated your subscription.","We have updated your subscription."
+"Are you sure you want to delete the selected subscriber(s)?","Are you sure you want to delete the selected subscriber(s)?"
diff --git a/app/code/Magento/Newsletter/view/adminhtml/layout/newsletter_subscriber_block.xml b/app/code/Magento/Newsletter/view/adminhtml/layout/newsletter_subscriber_block.xml
index e8600c2d49d7b..aef84c068100a 100644
--- a/app/code/Magento/Newsletter/view/adminhtml/layout/newsletter_subscriber_block.xml
+++ b/app/code/Magento/Newsletter/view/adminhtml/layout/newsletter_subscriber_block.xml
@@ -29,6 +29,7 @@
-
- Delete
- */*/massDelete
+ - Are you sure you want to delete the selected subscriber(s)?
diff --git a/app/code/Magento/Newsletter/view/adminhtml/templates/preview/iframeswitcher.phtml b/app/code/Magento/Newsletter/view/adminhtml/templates/preview/iframeswitcher.phtml
index 532ecde456077..20ff63a60a263 100644
--- a/app/code/Magento/Newsletter/view/adminhtml/templates/preview/iframeswitcher.phtml
+++ b/app/code/Magento/Newsletter/view/adminhtml/templates/preview/iframeswitcher.phtml
@@ -4,8 +4,6 @@
* See COPYING.txt for license details.
*/
-// @codingStandardsIgnoreFile
-
/** @var \Magento\Backend\Block\Page $block */
?>