From eb8cd708869546a203b17b922c2ce573a6c7f35d Mon Sep 17 00:00:00 2001 From: Nikolay Sumrak Date: Thu, 9 Apr 2020 13:32:19 +0300 Subject: [PATCH 001/139] Fixed creating shipping labels in part-shipment --- .../view/adminhtml/templates/order/packaging/popup.phtml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/app/code/Magento/Shipping/view/adminhtml/templates/order/packaging/popup.phtml b/app/code/Magento/Shipping/view/adminhtml/templates/order/packaging/popup.phtml index 28322d9534926..592babecdbfd6 100644 --- a/app/code/Magento/Shipping/view/adminhtml/templates/order/packaging/popup.phtml +++ b/app/code/Magento/Shipping/view/adminhtml/templates/order/packaging/popup.phtml @@ -44,7 +44,11 @@ $girthEnabled = $block->isDisplayGirthValue() && $block->isGirthAllowed() ? 1 : } }); packaging.setItemQtyCallback(function(itemId){ - var item = $$('[name="shipment[items]['+itemId+']"]')[0]; + var item = $$('[name="shipment[items]['+itemId+']"]')[0], + itemTitle = $('order_item_' + itemId + '_title'); + if (!itemTitle && !item) { + return 0; + } if (item && !isNaN(item.value)) { return item.value; } From a128f392b45f559316460962444bd4b1834e8f14 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Szubert?= Date: Fri, 8 May 2020 12:22:00 +0200 Subject: [PATCH 002/139] Fix #24091 - Selected configurable product attribute options are not displaying in wishlist page. --- .../Magento/Wishlist/Block/AddToWishlist.php | 38 ++++++++------- .../frontend/layout/catalog_category_view.xml | 10 +++- .../layout/catalogsearch_result_index.xml | 12 +++++ .../view/frontend/web/js/add-to-wishlist.js | 48 +++++++++++++++++-- 4 files changed, 84 insertions(+), 24 deletions(-) diff --git a/app/code/Magento/Wishlist/Block/AddToWishlist.php b/app/code/Magento/Wishlist/Block/AddToWishlist.php index 3ba350af94176..dffd8cb027e74 100644 --- a/app/code/Magento/Wishlist/Block/AddToWishlist.php +++ b/app/code/Magento/Wishlist/Block/AddToWishlist.php @@ -6,13 +6,15 @@ namespace Magento\Wishlist\Block; +use Magento\Framework\View\Element\Template; + /** * Wishlist js plugin initialization block * * @api * @since 100.1.0 */ -class AddToWishlist extends \Magento\Framework\View\Element\Template +class AddToWishlist extends Template { /** * Product types @@ -21,20 +23,6 @@ class AddToWishlist extends \Magento\Framework\View\Element\Template */ private $productTypes; - /** - * @param \Magento\Framework\View\Element\Template\Context $context - * @param array $data - */ - public function __construct( - \Magento\Framework\View\Element\Template\Context $context, - array $data = [] - ) { - parent::__construct( - $context, - $data - ); - } - /** * Returns wishlist widget options * @@ -43,7 +31,10 @@ public function __construct( */ public function getWishlistOptions() { - return ['productType' => $this->getProductTypes()]; + return [ + 'productType' => $this->getProductTypes(), + 'isProductList' => (bool)$this->getData('is_product_list') + ]; } /** @@ -56,7 +47,7 @@ private function getProductTypes() { if ($this->productTypes === null) { $this->productTypes = []; - $block = $this->getLayout()->getBlock('category.products.list'); + $block = $this->getLayout()->getBlock($this->getProductListBlockName()); if ($block) { $productCollection = $block->getLoadedProductCollection(); $productTypes = []; @@ -71,7 +62,18 @@ private function getProductTypes() } /** - * {@inheritdoc} + * Get product list block name in layout + * + * @return string + */ + private function getProductListBlockName(): string + { + return $this->getData('product_list_block') ?: 'category.products.list'; + } + + /** + * @inheritDoc + * * @since 100.1.0 */ protected function _toHtml() diff --git a/app/code/Magento/Wishlist/view/frontend/layout/catalog_category_view.xml b/app/code/Magento/Wishlist/view/frontend/layout/catalog_category_view.xml index a4860ace166d8..8b784cfd31783 100644 --- a/app/code/Magento/Wishlist/view/frontend/layout/catalog_category_view.xml +++ b/app/code/Magento/Wishlist/view/frontend/layout/catalog_category_view.xml @@ -21,7 +21,15 @@ template="Magento_Wishlist::catalog/product/list/addto/wishlist.phtml"/> - + + + true + + diff --git a/app/code/Magento/Wishlist/view/frontend/layout/catalogsearch_result_index.xml b/app/code/Magento/Wishlist/view/frontend/layout/catalogsearch_result_index.xml index c293175ccceac..1f597a9ce1e3a 100644 --- a/app/code/Magento/Wishlist/view/frontend/layout/catalogsearch_result_index.xml +++ b/app/code/Magento/Wishlist/view/frontend/layout/catalogsearch_result_index.xml @@ -14,5 +14,17 @@ template="Magento_Wishlist::catalog/product/list/addto/wishlist.phtml"/> + + + + true + search_result_list + + + diff --git a/app/code/Magento/Wishlist/view/frontend/web/js/add-to-wishlist.js b/app/code/Magento/Wishlist/view/frontend/web/js/add-to-wishlist.js index 55cd77b196be5..1cdad4953b3c2 100644 --- a/app/code/Magento/Wishlist/view/frontend/web/js/add-to-wishlist.js +++ b/app/code/Magento/Wishlist/view/frontend/web/js/add-to-wishlist.js @@ -17,7 +17,10 @@ define([ downloadableInfo: '#downloadable-links-list input', customOptionsInfo: '.product-custom-option', qtyInfo: '#qty', - actionElement: '[data-action="add-to-wishlist"]' + actionElement: '[data-action="add-to-wishlist"]', + productListItem: '.item.product-item', + productListPriceBox: '.price-box', + isProductList: false }, /** @inheritdoc */ @@ -65,6 +68,7 @@ define([ _updateWishlistData: function (event) { var dataToAdd = {}, isFileUploaded = false, + productId = null, self = this; if (event.handleObj.selector == this.options.qtyInfo) { //eslint-disable-line eqeqeq @@ -83,7 +87,19 @@ define([ $(element).is('textarea') || $('#' + element.id + ' option:selected').length ) { - if ($(element).data('selector') || $(element).attr('name')) { + if (!($(element).data('selector') || $(element).attr('name'))) { + return; + } + + if (self.options.isProductList) { + productId = self.retrieveListProductId(this); + + dataToAdd[productId] = $.extend( + {}, + dataToAdd[productId] ? dataToAdd[productId] : {}, + self._getElementData(element) + ); + } else { dataToAdd = $.extend({}, dataToAdd, self._getElementData(element)); } @@ -107,10 +123,17 @@ define([ * @private */ _updateAddToWishlistButton: function (dataToAdd) { - var self = this; + var productId = null, + self = this; $('[data-action="add-to-wishlist"]').each(function (index, element) { - var params = $(element).data('post'); + var params = $(element).data('post'), + dataToAddObj = dataToAdd; + + if (self.options.isProductList) { + productId = self.retrieveListProductId(element); + dataToAddObj = typeof dataToAdd[productId] !== 'undefined' ? dataToAdd[productId] : {}; + } if (!params) { params = { @@ -118,7 +141,7 @@ define([ }; } - params.data = $.extend({}, params.data, dataToAdd, { + params.data = $.extend({}, params.data, dataToAddObj, { 'qty': $(self.options.qtyInfo).val() }); $(element).data('post', params); @@ -241,6 +264,21 @@ define([ return; } + }, + + /** + * Retrieve product id from element on products list + * + * @param {jQuery.Object} element + * @private + */ + retrieveListProductId: function (element) { + return parseInt( + $(element).closest(this.options.productListItem) + .find(this.options.productListPriceBox) + .data('product-id'), + 10 + ); } }); From 4fd92e7edc9a62c3e09f7ed0461da4f36aba4d27 Mon Sep 17 00:00:00 2001 From: Per Date: Sat, 9 May 2020 17:09:57 +0200 Subject: [PATCH 003/139] Issue #27925, moved the submit button to the inside of the
Placing the submit button inside the `
` makes implicit submission possible --- .../template/payment/purchaseorder-form.html | 44 ++++++++++--------- 1 file changed, 23 insertions(+), 21 deletions(-) diff --git a/app/code/Magento/OfflinePayments/view/frontend/web/template/payment/purchaseorder-form.html b/app/code/Magento/OfflinePayments/view/frontend/web/template/payment/purchaseorder-form.html index 89d16bd732e7c..3a42a84b620b8 100644 --- a/app/code/Magento/OfflinePayments/view/frontend/web/template/payment/purchaseorder-form.html +++ b/app/code/Magento/OfflinePayments/view/frontend/web/template/payment/purchaseorder-form.html @@ -42,27 +42,29 @@ - -
- - - -
-
-
- + +
+ + +
-
+ +
+
+ +
+
+
- + From cf5d73b89648c31fa0832fc6a1957f13b338a512 Mon Sep 17 00:00:00 2001 From: Alexander Steshuk Date: Mon, 18 May 2020 12:40:15 +0300 Subject: [PATCH 004/139] #28172: MFTF tests --- .../Checkout/Test/Mftf/Section/CheckoutPaymentSection.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutPaymentSection.xml b/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutPaymentSection.xml index 16fd373d3ae4d..bacf8ac4b9fb5 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutPaymentSection.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutPaymentSection.xml @@ -30,6 +30,7 @@ + From 9173df72586e897d4a624a59cc7465b5fbfb23bd Mon Sep 17 00:00:00 2001 From: Alexander Steshuk Date: Mon, 18 May 2020 12:41:18 +0300 Subject: [PATCH 005/139] #28172: MFTF tests --- ...thPurchaseOrderNumberPressKeyEnterTest.xml | 68 +++++++++++++++++++ ...ontCheckoutWithPurchaseOrderNumberTest.xml | 67 ++++++++++++++++++ 2 files changed, 135 insertions(+) create mode 100644 app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCheckoutWithPurchaseOrderNumberPressKeyEnterTest.xml create mode 100644 app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCheckoutWithPurchaseOrderNumberTest.xml diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCheckoutWithPurchaseOrderNumberPressKeyEnterTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCheckoutWithPurchaseOrderNumberPressKeyEnterTest.xml new file mode 100644 index 0000000000000..0959962d50d81 --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCheckoutWithPurchaseOrderNumberPressKeyEnterTest.xml @@ -0,0 +1,68 @@ + + + + + + + + + + <description value="Create Checkout with purchase order payment method. Press key Enter on field Purchase Order Number for create Order."/> + <severity value="MAJOR"/> + <group value="checkout"/> + </annotations> + + <before> + <createData entity="SimpleTwo" stepKey="createSimpleProduct"/> + + <!-- Enable payment method --> + <magentoCLI command="config:set {{PurchaseOrderEnableConfigData.path}} {{PurchaseOrderEnableConfigData.value}}" stepKey="enablePaymentMethod"/> + </before> + + <after> + <actionGroup ref="AdminLogoutActionGroup" stepKey="adminLogout"/> + <deleteData createDataKey="createSimpleProduct" stepKey="deleteProduct"/> + + <!-- Disable payment method --> + <magentoCLI command="config:set {{PurchaseOrderDisabledConfigData.path}} {{PurchaseOrderDisabledConfigData.value}}" stepKey="disablePaymentMethod"/> + </after> + + <!--Go to product page--> + <amOnPage url="$$createSimpleProduct.custom_attributes[url_key]$$.html" stepKey="navigateToSimpleProductPage"/> + <waitForPageLoad stepKey="waitForCatalogPageLoad"/> + + <!--Add Product to Shopping Cart--> + <actionGroup ref="AddToCartFromStorefrontProductPageActionGroup" stepKey="addToCartFromStorefrontProductPage"> + <argument name="productName" value="$$createSimpleProduct.name$$"/> + </actionGroup> + + <!--Go to Checkout--> + <actionGroup ref="GoToCheckoutFromMinicartActionGroup" stepKey="goToCheckoutFromMinicart"/> + + <actionGroup ref="GuestCheckoutFillingShippingSectionActionGroup" stepKey="guestCheckoutFillingShippingSection"> + <argument name="customerVar" value="CustomerEntityOne"/> + <argument name="customerAddressVar" value="CustomerAddressSimple"/> + </actionGroup> + + <!-- Checkout select Purchase Order payment --> + <actionGroup ref="CheckoutSelectPurchaseOrderPaymentActionGroup" stepKey="selectPurchaseOrderPayment"> + <argument name="purchaseOrderNumber" value="12345"/> + </actionGroup> + + <!--Press Key ENTER--> + <pressKey selector="{{StorefrontCheckoutPaymentMethodSection.purchaseOrderNumber}}" parameterArray="[\Facebook\WebDriver\WebDriverKeys::ENTER]" stepKey="pressKeyEnter"/> + <waitForPageLoad stepKey="waitForPageLoad"/> + + <!--See success messages--> + <see selector="{{CheckoutSuccessMainSection.successTitle}}" userInput="Thank you for your purchase!" stepKey="seeSuccessTitle"/> + <see selector="{{CheckoutSuccessMainSection.orderNumberText}}" userInput="Your order # is: " stepKey="seeOrderNumber"/> + + </test> + +</tests> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCheckoutWithPurchaseOrderNumberTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCheckoutWithPurchaseOrderNumberTest.xml new file mode 100644 index 0000000000000..0b46bbdb7db65 --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCheckoutWithPurchaseOrderNumberTest.xml @@ -0,0 +1,67 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="StorefrontCheckoutWithPurchaseOrderNumberTest"> + <annotations> + <features value="Checkout"/> + <stories value="Checkout with Purchase Order Payment"/> + <title value="Create Checkout with purchase order payment method test"/> + <description value="Create Checkout with purchase order payment method"/> + <severity value="MAJOR"/> + <group value="checkout"/> + </annotations> + + <before> + <createData entity="SimpleTwo" stepKey="createSimpleProduct"/> + + <!-- Enable payment method --> + <magentoCLI command="config:set {{PurchaseOrderEnableConfigData.path}} {{PurchaseOrderEnableConfigData.value}}" stepKey="enablePaymentMethod"/> + </before> + + <after> + <actionGroup ref="AdminLogoutActionGroup" stepKey="adminLogout"/> + <deleteData createDataKey="createSimpleProduct" stepKey="deleteProduct"/> + + <!-- Disable payment method --> + <magentoCLI command="config:set {{PurchaseOrderDisabledConfigData.path}} {{PurchaseOrderDisabledConfigData.value}}" stepKey="disablePaymentMethod"/> + </after> + + <!--Go to product page--> + <amOnPage url="$$createSimpleProduct.custom_attributes[url_key]$$.html" stepKey="navigateToSimpleProductPage"/> + <waitForPageLoad stepKey="waitForCatalogPageLoad"/> + + <!--Add Product to Shopping Cart--> + <actionGroup ref="AddToCartFromStorefrontProductPageActionGroup" stepKey="addToCartFromStorefrontProductPage"> + <argument name="productName" value="$$createSimpleProduct.name$$"/> + </actionGroup> + + <!--Go to Checkout--> + <actionGroup ref="GoToCheckoutFromMinicartActionGroup" stepKey="goToCheckoutFromMinicart"/> + + <actionGroup ref="GuestCheckoutFillingShippingSectionActionGroup" stepKey="guestCheckoutFillingShippingSection"> + <argument name="customerVar" value="CustomerEntityOne"/> + <argument name="customerAddressVar" value="CustomerAddressSimple"/> + </actionGroup> + + <!-- Checkout select Purchase Order payment --> + <actionGroup ref="CheckoutSelectPurchaseOrderPaymentActionGroup" stepKey="selectPurchaseOrderPayment"> + <argument name="purchaseOrderNumber" value="12345"/> + </actionGroup> + + <!--Click Place Order button--> + <click selector="{{CheckoutPaymentSection.placeOrder}}" stepKey="clickPlaceOrder"/> + <waitForPageLoad stepKey="waitForPageLoad"/> + + <!--See success messages--> + <see selector="{{CheckoutSuccessMainSection.successTitle}}" userInput="Thank you for your purchase!" stepKey="seeSuccessTitle"/> + <see selector="{{CheckoutSuccessMainSection.orderNumberText}}" userInput="Your order # is: " stepKey="seeOrderNumber"/> + + </test> + +</tests> From 2dee5655c8acce78a3fd146e34b2581215786324 Mon Sep 17 00:00:00 2001 From: Alexander Steshuk <grp-engcom-vendorworker-Kilo@adobe.com> Date: Mon, 18 May 2020 13:55:17 +0300 Subject: [PATCH 006/139] #28172: MFTF tests --- ...tSelectPurchaseOrderPaymentActionGroup.xml | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 app/code/Magento/Checkout/Test/Mftf/ActionGroup/CheckoutSelectPurchaseOrderPaymentActionGroup.xml diff --git a/app/code/Magento/Checkout/Test/Mftf/ActionGroup/CheckoutSelectPurchaseOrderPaymentActionGroup.xml b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/CheckoutSelectPurchaseOrderPaymentActionGroup.xml new file mode 100644 index 0000000000000..dbc9739a9247f --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/CheckoutSelectPurchaseOrderPaymentActionGroup.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="CheckoutSelectPurchaseOrderPaymentActionGroup"> + <annotations> + <description>Selects the 'Purchase Order' Payment Method on the Storefront Checkout page.</description> + </annotations> + + <arguments> + <argument name="purchaseOrderNumber" type="string"/> + </arguments> + <waitForLoadingMaskToDisappear stepKey="waitForLoadingMask"/> + <waitForPageLoad stepKey="waitForPageLoad"/> + <remove keyForRemoval="checkCheckMoneyOption"/> + <conditionalClick selector="{{CheckoutPaymentSection.purchaseOrderPayment}}" dependentSelector="{{CheckoutPaymentSection.purchaseOrderPayment}}" visible="true" stepKey="checkPurchaseOrderOption"/> + <fillField selector="{{StorefrontCheckoutPaymentMethodSection.purchaseOrderNumber}}" userInput="{{purchaseOrderNumber}}" stepKey="fillPurchaseOrderNumber"/> + <waitForLoadingMaskToDisappear stepKey="waitForLoadingMaskAfterPaymentMethodSelection"/> + </actionGroup> +</actionGroups> From 0ec2c916c5c8cb6464b3ddd5f3b20b4e27e1f331 Mon Sep 17 00:00:00 2001 From: Johan Lindahl <johan.lindahl@ecomero.com> Date: Mon, 29 Jun 2020 11:07:20 +0200 Subject: [PATCH 007/139] AppState emulateAreaCode was not respected by file collector --- lib/internal/Magento/Framework/View/File/Collector/Base.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/internal/Magento/Framework/View/File/Collector/Base.php b/lib/internal/Magento/Framework/View/File/Collector/Base.php index a5824b7321e84..f82ba6b0d7fab 100644 --- a/lib/internal/Magento/Framework/View/File/Collector/Base.php +++ b/lib/internal/Magento/Framework/View/File/Collector/Base.php @@ -65,7 +65,7 @@ public function getFiles(ThemeInterface $theme, $filePath) foreach ($sharedFiles as $file) { $result[] = $this->fileFactory->create($file->getFullPath(), $file->getComponentName(), null, true); } - $area = $theme->getData('area'); + $area = $theme->getArea(); $themeFiles = $this->componentDirSearch->collectFilesWithContext( ComponentRegistrar::MODULE, "view/{$area}/{$this->subDir}{$filePath}" From 7660725eddb33e837a6286bfe131c0a96e209144 Mon Sep 17 00:00:00 2001 From: Guillaume Quintard <guillaume@varnish-software.com> Date: Mon, 29 Jun 2020 19:01:56 -0700 Subject: [PATCH 008/139] [vcl] don't explicitly hash the host header Hashing `req.http.host`/`client.ip` is already handled by the [built-in vcl](https://github.com/varnishcache/varnish-cache/blob/6.0/bin/varnishd/builtin.vcl#L86) so there's no need to repeat it explicitly. It's also a bit confusing as `req.url` is not explicitly handled, even though it's a more important hash input than the host. note: all versions have been changed for the sake of consistency but both the 4.x and 5.x series have been EOL'd a (long) while ago and users should be encouraged to upgraded as soon as possible. --- app/code/Magento/PageCache/etc/varnish4.vcl | 7 ------- app/code/Magento/PageCache/etc/varnish5.vcl | 7 ------- app/code/Magento/PageCache/etc/varnish6.vcl | 7 ------- 3 files changed, 21 deletions(-) diff --git a/app/code/Magento/PageCache/etc/varnish4.vcl b/app/code/Magento/PageCache/etc/varnish4.vcl index f5e25ce36e973..7ae857c54e67c 100644 --- a/app/code/Magento/PageCache/etc/varnish4.vcl +++ b/app/code/Magento/PageCache/etc/varnish4.vcl @@ -121,13 +121,6 @@ sub vcl_hash { hash_data(regsub(req.http.cookie, "^.*?X-Magento-Vary=([^;]+);*.*$", "\1")); } - # For multi site configurations to not cache each other's content - if (req.http.host) { - hash_data(req.http.host); - } else { - hash_data(server.ip); - } - if (req.url ~ "/graphql") { call process_graphql_headers; } diff --git a/app/code/Magento/PageCache/etc/varnish5.vcl b/app/code/Magento/PageCache/etc/varnish5.vcl index 92bb3394486fc..7daa56c59fe63 100644 --- a/app/code/Magento/PageCache/etc/varnish5.vcl +++ b/app/code/Magento/PageCache/etc/varnish5.vcl @@ -122,13 +122,6 @@ sub vcl_hash { hash_data(regsub(req.http.cookie, "^.*?X-Magento-Vary=([^;]+);*.*$", "\1")); } - # For multi site configurations to not cache each other's content - if (req.http.host) { - hash_data(req.http.host); - } else { - hash_data(server.ip); - } - # To make sure http users don't see ssl warning if (req.http./* {{ ssl_offloaded_header }} */) { hash_data(req.http./* {{ ssl_offloaded_header }} */); diff --git a/app/code/Magento/PageCache/etc/varnish6.vcl b/app/code/Magento/PageCache/etc/varnish6.vcl index eef5e99862538..d603a8fed3cea 100644 --- a/app/code/Magento/PageCache/etc/varnish6.vcl +++ b/app/code/Magento/PageCache/etc/varnish6.vcl @@ -122,13 +122,6 @@ sub vcl_hash { hash_data(regsub(req.http.cookie, "^.*?X-Magento-Vary=([^;]+);*.*$", "\1")); } - # For multi site configurations to not cache each other's content - if (req.http.host) { - hash_data(req.http.host); - } else { - hash_data(server.ip); - } - # To make sure http users don't see ssl warning if (req.http./* {{ ssl_offloaded_header }} */) { hash_data(req.http./* {{ ssl_offloaded_header }} */); From e72478890ecbd2296789551fd2f46927d69750fc Mon Sep 17 00:00:00 2001 From: Johan Lindahl <johan.lindahl@ecomero.com> Date: Wed, 1 Jul 2020 17:29:56 +0200 Subject: [PATCH 009/139] Fixed broken unit test --- .../Framework/View/Test/Unit/File/Collector/BaseTest.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/internal/Magento/Framework/View/Test/Unit/File/Collector/BaseTest.php b/lib/internal/Magento/Framework/View/Test/Unit/File/Collector/BaseTest.php index 0edafcb125dd3..ea0ef1cc69e87 100644 --- a/lib/internal/Magento/Framework/View/Test/Unit/File/Collector/BaseTest.php +++ b/lib/internal/Magento/Framework/View/Test/Unit/File/Collector/BaseTest.php @@ -85,8 +85,7 @@ public function testGetFiles() ->method('create') ->willReturn($this->createFileMock()); $this->themeMock->expects($this->once()) - ->method('getData') - ->with('area') + ->method('getArea') ->willReturn('frontend'); $result = $this->fileCollector->getFiles($this->themeMock, '*.xml'); From 3f6d8c42a08cdf82f852d2c4e50903ad276488ee Mon Sep 17 00:00:00 2001 From: ogorkun <ogorkun@adobe.com> Date: Wed, 15 Jul 2020 14:32:40 -0500 Subject: [PATCH 010/139] MC-34385: Filter fields allowing HTML --- .../Attribute/Backend/DefaultBackend.php | 94 +++++++++++ .../Model/ResourceModel/Eav/Attribute.php | 15 ++ .../Attribute/Backend/DefaultBackendTest.php | 111 ++++++++++++ app/etc/di.xml | 17 ++ .../HTML/ConfigurableWYSIWYGValidatorTest.php | 113 +++++++++++++ .../HTML/ConfigurableWYSIWYGValidator.php | 158 ++++++++++++++++++ .../HTML/WYSIWYGValidatorInterface.php | 25 +++ 7 files changed, 533 insertions(+) create mode 100644 app/code/Magento/Catalog/Model/Attribute/Backend/DefaultBackend.php create mode 100644 app/code/Magento/Catalog/Test/Unit/Model/Attribute/Backend/DefaultBackendTest.php create mode 100644 lib/internal/Magento/Framework/Test/Unit/Validator/HTML/ConfigurableWYSIWYGValidatorTest.php create mode 100644 lib/internal/Magento/Framework/Validator/HTML/ConfigurableWYSIWYGValidator.php create mode 100644 lib/internal/Magento/Framework/Validator/HTML/WYSIWYGValidatorInterface.php diff --git a/app/code/Magento/Catalog/Model/Attribute/Backend/DefaultBackend.php b/app/code/Magento/Catalog/Model/Attribute/Backend/DefaultBackend.php new file mode 100644 index 0000000000000..e3b38bf7a578a --- /dev/null +++ b/app/code/Magento/Catalog/Model/Attribute/Backend/DefaultBackend.php @@ -0,0 +1,94 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\Catalog\Model\Attribute\Backend; + +use Magento\Catalog\Model\AbstractModel; +use Magento\Catalog\Model\ResourceModel\Eav\Attribute; +use Magento\Eav\Model\Entity\Attribute\Backend\DefaultBackend as ParentBackend; +use Magento\Eav\Model\Entity\Attribute\Exception; +use Magento\Framework\DataObject; +use Magento\Framework\Exception\LocalizedException; +use Magento\Framework\Validation\ValidationException; +use Magento\Framework\Validator\HTML\WYSIWYGValidatorInterface; + +/** + * Default backend model for catalog attributes. + */ +class DefaultBackend extends ParentBackend +{ + /** + * @var WYSIWYGValidatorInterface + */ + private $wysiwygValidator; + + /** + * @param WYSIWYGValidatorInterface $wysiwygValidator + */ + public function __construct(WYSIWYGValidatorInterface $wysiwygValidator) + { + $this->wysiwygValidator = $wysiwygValidator; + } + + /** + * Validate user HTML value. + * + * @param DataObject $object + * @return void + * @throws LocalizedException + */ + private function validateHtml(DataObject $object): void + { + $attribute = $this->getAttribute(); + $code = $attribute->getAttributeCode(); + if ($attribute instanceof Attribute && $attribute->getIsHtmlAllowedOnFront()) { + if ($object->getData($code) + && (!($object instanceof AbstractModel) || $object->getData($code) !== $object->getOrigData($code)) + ) { + try { + $this->wysiwygValidator->validate($object->getData($code)); + } catch (ValidationException $exception) { + $attributeException = new Exception( + __( + 'Using restricted HTML elements for "%1". %2', + $attribute->getName(), + $exception->getMessage() + ), + $exception + ); + $attributeException->setAttributeCode($code)->setPart('backend'); + throw $attributeException; + } + } + } + } + + /** + * @inheritDoc + */ + public function beforeSave($object) + { + parent::beforeSave($object); + $this->validateHtml($object); + + return $this; + } + + /** + * @inheritDoc + */ + public function validate($object) + { + $isValid = parent::validate($object); + if ($isValid) { + $this->validateHtml($object); + } + + return $isValid; + } +} diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Eav/Attribute.php b/app/code/Magento/Catalog/Model/ResourceModel/Eav/Attribute.php index e1c90017327cd..b803695a94702 100644 --- a/app/code/Magento/Catalog/Model/ResourceModel/Eav/Attribute.php +++ b/app/code/Magento/Catalog/Model/ResourceModel/Eav/Attribute.php @@ -6,7 +6,9 @@ namespace Magento\Catalog\Model\ResourceModel\Eav; +use Magento\Catalog\Model\Attribute\Backend\DefaultBackend; use Magento\Catalog\Model\Attribute\LockValidatorInterface; +use Magento\Eav\Model\Entity; use Magento\Framework\Api\AttributeValueFactory; use Magento\Framework\Stdlib\DateTime\DateTimeFormatterInterface; @@ -901,4 +903,17 @@ public function setIsFilterableInGrid($isFilterableInGrid) $this->setData(self::IS_FILTERABLE_IN_GRID, $isFilterableInGrid); return $this; } + + /** + * @inheritDoc + */ + protected function _getDefaultBackendModel() + { + $backend = parent::_getDefaultBackendModel(); + if ($backend === Entity::DEFAULT_BACKEND_MODEL) { + $backend = DefaultBackend::class; + } + + return $backend; + } } diff --git a/app/code/Magento/Catalog/Test/Unit/Model/Attribute/Backend/DefaultBackendTest.php b/app/code/Magento/Catalog/Test/Unit/Model/Attribute/Backend/DefaultBackendTest.php new file mode 100644 index 0000000000000..36ec38841b7cc --- /dev/null +++ b/app/code/Magento/Catalog/Test/Unit/Model/Attribute/Backend/DefaultBackendTest.php @@ -0,0 +1,111 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\Catalog\Test\Unit\Model\Attribute\Backend; + +use Magento\Catalog\Model\AbstractModel; +use Magento\Catalog\Model\Attribute\Backend\DefaultBackend; +use Magento\Framework\DataObject; +use Magento\Framework\Validation\ValidationException; +use Magento\Framework\Validator\HTML\WYSIWYGValidatorInterface; +use PHPUnit\Framework\TestCase; +use Magento\Eav\Model\Entity\Attribute as BasicAttribute; +use Magento\Catalog\Model\ResourceModel\Eav\Attribute; +use Magento\Eav\Model\Entity\Attribute\Exception as AttributeException; + +class DefaultBackendTest extends TestCase +{ + /** + * Different cases for attribute validation. + * + * @return array + */ + public function getAttributeConfigurations(): array + { + return [ + 'basic-attribute' => [true, false, true, 'basic', 'value', false, true, false], + 'non-html-attribute' => [false, false, false, 'non-html', 'value', false, false, false], + 'empty-html-attribute' => [false, false, true, 'html', null, false, true, false], + 'invalid-html-attribute' => [false, false, false, 'html', 'value', false, true, true], + 'valid-html-attribute' => [false, true, false, 'html', 'value', false, true, false], + 'changed-invalid-html-attribute' => [false, false, true, 'html', 'value', true, true, true], + 'changed-valid-html-attribute' => [false, true, true, 'html', 'value', true, true, false] + ]; + } + + /** + * Test attribute validation. + * + * @param bool $isBasic + * @param bool $isValidated + * @param bool $isCatalogEntity + * @param string $code + * @param mixed $value + * @param bool $isChanged + * @param bool $isHtmlAttribute + * @param bool $exceptionThrown + * @dataProvider getAttributeConfigurations + */ + public function testValidate( + bool $isBasic, + bool $isValidated, + bool $isCatalogEntity, + string $code, + $value, + bool $isChanged, + bool $isHtmlAttribute, + bool $exceptionThrown + ): void { + if ($isBasic) { + $attributeMock = $this->createMock(BasicAttribute::class); + } else { + $attributeMock = $this->createMock(Attribute::class); + $attributeMock->expects($this->any()) + ->method('getIsHtmlAllowedOnFront') + ->willReturn($isHtmlAttribute); + } + $attributeMock->expects($this->any())->method('getAttributeCode')->willReturn($code); + + $validatorMock = $this->getMockForAbstractClass(WYSIWYGValidatorInterface::class); + if (!$isValidated) { + $validatorMock->expects($this->any()) + ->method('validate') + ->willThrowException(new ValidationException(__('HTML is invalid'))); + } else { + $validatorMock->expects($this->any())->method('validate'); + } + + if ($isCatalogEntity) { + $objectMock = $this->createMock(AbstractModel::class); + $objectMock->expects($this->any()) + ->method('getOrigData') + ->willReturn($isChanged ? $value .'-OLD' : $value); + } else { + $objectMock = $this->createMock(DataObject::class); + } + $objectMock->expects($this->any())->method('getData')->with($code)->willReturn($value); + + $model = new DefaultBackend($validatorMock); + $model->setAttribute($attributeMock); + + $actuallyThrownForSave = false; + try { + $model->beforeSave($objectMock); + } catch (AttributeException $exception) { + $actuallyThrownForSave = true; + } + $actuallyThrownForValidate = false; + try { + $model->validate($objectMock); + } catch (AttributeException $exception) { + $actuallyThrownForValidate = true; + } + $this->assertEquals($actuallyThrownForSave, $actuallyThrownForValidate); + $this->assertEquals($actuallyThrownForSave, $exceptionThrown); + } +} diff --git a/app/etc/di.xml b/app/etc/di.xml index 31cc5caf3ba67..9b85e09ac9611 100644 --- a/app/etc/di.xml +++ b/app/etc/di.xml @@ -1832,4 +1832,21 @@ </argument> </arguments> </type> + <virtualType name="DefaultWYSIWYGValidator" type="Magento\Framework\Validator\HTML\ConfigurableWYSIWYGValidator"> + <arguments> + <argument name="allowedTags" xsi:type="array"> + <item name="div" xsi:type="string">div</item> + <item name="a" xsi:type="string">a</item> + </argument> + <argument name="allowedAttributes" xsi:type="array"> + <item name="class" xsi:type="string">class</item> + </argument> + <argument name="attributesAllowedByTags" xsi:type="array"> + <item name="a" xsi:type="array"> + <item name="href" xsi:type="string">href</item> + </item> + </argument> + </arguments> + </virtualType> + <preference for="Magento\Framework\Validator\HTML\WYSIWYGValidatorInterface" type="DefaultWYSIWYGValidator" /> </config> diff --git a/lib/internal/Magento/Framework/Test/Unit/Validator/HTML/ConfigurableWYSIWYGValidatorTest.php b/lib/internal/Magento/Framework/Test/Unit/Validator/HTML/ConfigurableWYSIWYGValidatorTest.php new file mode 100644 index 0000000000000..aef019b20f519 --- /dev/null +++ b/lib/internal/Magento/Framework/Test/Unit/Validator/HTML/ConfigurableWYSIWYGValidatorTest.php @@ -0,0 +1,113 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\Framework\Test\Unit\Validator\HTML; + +use Magento\Framework\Validation\ValidationException; +use Magento\Framework\Validator\HTML\ConfigurableWYSIWYGValidator; +use PHPUnit\Framework\TestCase; + +class ConfigurableWYSIWYGValidatorTest extends TestCase +{ + /** + * Configurations to test. + * + * @return array + */ + public function getConfigurations(): array + { + return [ + 'no-html' => [['div'], [], [], 'just text', true], + 'allowed-tag' => [['div'], [], [], 'just text and <div>a div</div>', true], + 'restricted-tag' => [['div', 'p'], [], [], 'text and <p>a p</p>, <div>a div</div>, <tr>a tr</tr>', false], + 'restricted-tag-wtih-attr' => [['div'], [], [], 'just text and <p class="fake-class">a p</p>', false], + 'allowed-tag-with-attr' => [['div'], [], [], 'just text and <div class="fake-class">a div</div>', false], + 'multiple-tags' => [['div', 'p'], [], [], 'just text and <div>a div</div> and <p>a p</p>', true], + 'tags-with-attrs' => [ + ['div', 'p'], + ['class', 'style'], + [], + 'text and <div class="fake-class">a div</div> and <p style="color: blue">a p</p>', + true + ], + 'tags-with-restricted-attrs' => [ + ['div', 'p'], + ['class', 'align'], + [], + 'text and <div class="fake-class">a div</div> and <p style="color: blue">a p</p>', + false + ], + 'tags-with-specific-attrs' => [ + ['div', 'a', 'p'], + ['class'], + ['a' => ['href'], 'div' => ['style']], + '<div class="fake-class" style="color: blue">a div</div>, <a href="/some-path" class="a">an a</a>' + .', <p class="p-class">a p</p>', + true + ], + 'tags-with-specific-restricted-attrs' => [ + ['div', 'a'], + ['class'], + ['a' => ['href']], + 'text and <div class="fake-class" href="what">a div</div> and <a href="/some-path" class="a">an a</a>', + false + ], + 'invalid-tag-with-full-config' => [ + ['div', 'a', 'p'], + ['class', 'src'], + ['a' => ['href'], 'div' => ['style']], + '<div class="fake-class" style="color: blue">a div</div>, <a href="/some-path" class="a">an a</a>' + .', <p class="p-class">a p</p>, <img src="test.jpg" />', + false + ], + 'invalid-html' => [ + ['div', 'a', 'p'], + ['class', 'src'], + ['a' => ['href'], 'div' => ['style']], + 'some </,none-> </html>', + true + ], + 'invalid-html-with-violations' => [ + ['div', 'a', 'p'], + ['class', 'src'], + ['a' => ['href'], 'div' => ['style']], + 'some </,none-> </html> <tr>some trs</tr>', + false + ] + ]; + } + + /** + * Test different configurations and content. + * + * @param array $allowedTags + * @param array $allowedAttr + * @param array $allowedTagAttrs + * @param string $html + * @param bool $isValid + * @return void + * @dataProvider getConfigurations + */ + public function testConfigurations( + array $allowedTags, + array $allowedAttr, + array $allowedTagAttrs, + string $html, + bool $isValid + ): void { + $validator = new ConfigurableWYSIWYGValidator($allowedTags, $allowedAttr, $allowedTagAttrs); + $valid = true; + try { + $validator->validate($html); + } catch (ValidationException $exception) { + $valid = false; + } + + self::assertEquals($isValid, $valid); + } +} diff --git a/lib/internal/Magento/Framework/Validator/HTML/ConfigurableWYSIWYGValidator.php b/lib/internal/Magento/Framework/Validator/HTML/ConfigurableWYSIWYGValidator.php new file mode 100644 index 0000000000000..0b1993c044f6f --- /dev/null +++ b/lib/internal/Magento/Framework/Validator/HTML/ConfigurableWYSIWYGValidator.php @@ -0,0 +1,158 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\Framework\Validator\HTML; + +use Magento\Framework\Validation\ValidationException; + +/** + * Validates user HTML based on configuration. + */ +class ConfigurableWYSIWYGValidator implements WYSIWYGValidatorInterface +{ + /** + * @var string[] + */ + private $allowedTags; + + /** + * @var string[] + */ + private $allowedAttributes; + + /** + * @var string[] + */ + private $attributesAllowedByTags; + + /** + * @param array $allowedTags + * @param array $allowedAttributes + * @param array $attributesAllowedByTags + */ + public function __construct(array $allowedTags, array $allowedAttributes = [], array $attributesAllowedByTags = []) + { + if (empty(array_filter($allowedTags))) { + throw new \InvalidArgumentException('List of allowed HTML tags cannot be empty'); + } + $this->allowedTags = array_unique($allowedTags); + $this->allowedAttributes = array_unique($allowedAttributes); + $this->attributesAllowedByTags = array_filter( + $attributesAllowedByTags, + function (string $tag) use ($allowedTags): bool { + return in_array($tag, $allowedTags, true); + }, + ARRAY_FILTER_USE_KEY + ); + } + + /** + * @inheritDoc + */ + public function validate(string $content): void + { + $dom = $this->loadHtml($content); + $xpath = new \DOMXPath($dom); + + //Validating tags + $found = $xpath->query( + $query='//*[' + . implode( + ' and ', + array_map( + function (string $tag): string { + return "name() != '$tag'"; + }, + array_merge($this->allowedTags, ['body', 'html']) + ) + ) + .']' + ); + if (count($found)) { + throw new ValidationException( + __('Allowed HTML tags are: %1', implode(', ', $this->allowedTags)) + ); + } + + //Validating attributes + if ($this->attributesAllowedByTags) { + foreach ($this->allowedTags as $tag) { + $allowed = $this->allowedAttributes; + if (!empty($this->attributesAllowedByTags[$tag])) { + $allowed = array_unique(array_merge($allowed, $this->attributesAllowedByTags[$tag])); + } + $allowedQuery = ''; + if ($allowed) { + $allowedQuery = '[' + . implode( + ' and ', + array_map( + function (string $attribute): string { + return "name() != '$attribute'"; + }, + $allowed + ) + ) + .']'; + } + $found = $xpath->query("//$tag/@*$allowedQuery"); + if (count($found)) { + throw new ValidationException( + __('Allowed HTML attributes for tag "%1" are: %2', $tag, implode(',', $allowed)) + ); + } + } + } else { + $allowed = ''; + if ($this->allowedAttributes) { + $allowed = '[' + . implode( + ' and ', + array_map( + function (string $attribute): string { + return "name() != '$attribute'"; + }, + $this->allowedAttributes + ) + ) + .']'; + } + $found = $xpath->query("//@*$allowed"); + if (count($found)) { + throw new ValidationException( + __('Allowed HTML attributes are: %1', implode(',', $this->allowedAttributes)) + ); + } + } + } + + /** + * Load DOM. + * + * @param string $content + * @return \DOMDocument + * @throws ValidationException + */ + private function loadHtml(string $content): \DOMDocument + { + $dom = new \DOMDocument('1.0', 'UTF-8'); + $loaded = true; + set_error_handler( + function () use (&$loaded) { + $loaded = false; + } + ); + $loaded = $dom->loadHTML("<html><body>$content</body></html>"); + restore_error_handler(); + if (!$loaded) { + throw new ValidationException(__('Invalid HTML content provided')); + } + + return $dom; + } +} diff --git a/lib/internal/Magento/Framework/Validator/HTML/WYSIWYGValidatorInterface.php b/lib/internal/Magento/Framework/Validator/HTML/WYSIWYGValidatorInterface.php new file mode 100644 index 0000000000000..8045bc6a86c0b --- /dev/null +++ b/lib/internal/Magento/Framework/Validator/HTML/WYSIWYGValidatorInterface.php @@ -0,0 +1,25 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\Framework\Validator\HTML; + +use Magento\Framework\Validation\ValidationException; + +/** + * Validates user HTML. + */ +interface WYSIWYGValidatorInterface +{ + /** + * Validate user HTML content. + * + * @param string $content + * @throws ValidationException + */ + public function validate(string $content): void; +} From 6b5bb102b597ae37f88f1addd5b15faabf918fa2 Mon Sep 17 00:00:00 2001 From: ogorkun <ogorkun@adobe.com> Date: Wed, 15 Jul 2020 16:23:21 -0500 Subject: [PATCH 011/139] MC-34385: Filter fields allowing HTML --- .../Cms/Command/WysiwygRestrictCommand.php | 70 +++++++++++++++ .../Magento/Cms/Model/Wysiwyg/Validator.php | 87 +++++++++++++++++++ .../Test/Unit/Model/Wysiwyg/ValidatorTest.php | 78 +++++++++++++++++ app/code/Magento/Cms/etc/config.xml | 1 + app/code/Magento/Cms/etc/di.xml | 6 ++ .../Command/WysiwygRestrictCommandTest.php | 78 +++++++++++++++++ 6 files changed, 320 insertions(+) create mode 100644 app/code/Magento/Cms/Command/WysiwygRestrictCommand.php create mode 100644 app/code/Magento/Cms/Model/Wysiwyg/Validator.php create mode 100644 app/code/Magento/Cms/Test/Unit/Model/Wysiwyg/ValidatorTest.php create mode 100644 dev/tests/integration/testsuite/Magento/Cms/Command/WysiwygRestrictCommandTest.php diff --git a/app/code/Magento/Cms/Command/WysiwygRestrictCommand.php b/app/code/Magento/Cms/Command/WysiwygRestrictCommand.php new file mode 100644 index 0000000000000..bafe98ad377f5 --- /dev/null +++ b/app/code/Magento/Cms/Command/WysiwygRestrictCommand.php @@ -0,0 +1,70 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\Cms\Command; + +use Magento\Cms\Model\Wysiwyg\Validator; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Magento\Framework\App\Config\ConfigResource\ConfigInterface as ConfigWriter; +use Magento\Framework\App\Cache\TypeListInterface as Cache; + +/** + * Command to toggle WYSIWYG content validation on/off. + */ +class WysiwygRestrictCommand extends Command +{ + /** + * @var ConfigWriter + */ + private $configWriter; + + /** + * @var Cache + */ + private $cache; + + /** + * @param ConfigWriter $configWriter + * @param Cache $cache + */ + public function __construct(ConfigWriter $configWriter, Cache $cache) + { + parent::__construct(); + + $this->configWriter = $configWriter; + $this->cache = $cache; + } + + /** + * @inheritDoc + */ + protected function configure() + { + $this->setName('cms:wysiwyg:restrict'); + $this->setDescription('Set whether to enforce user HTML content validation or show a warning instead'); + $this->setDefinition([new InputArgument('restrict', InputArgument::REQUIRED, 'y\n')]); + + parent::configure(); + } + + /** + * @inheritDoc + */ + protected function execute(InputInterface $input, OutputInterface $output) + { + $restrictArg = mb_strtolower((string)$input->getArgument('restrict')); + $restrict = $restrictArg === 'y' ? '1' : '0'; + $this->configWriter->saveConfig(Validator::CONFIG_PATH_THROW_EXCEPTION, $restrict); + $this->cache->cleanType('config'); + + $output->writeln('HTML user content validation is now ' .($restrictArg === 'y' ? 'enforced' : 'suggested')); + } +} diff --git a/app/code/Magento/Cms/Model/Wysiwyg/Validator.php b/app/code/Magento/Cms/Model/Wysiwyg/Validator.php new file mode 100644 index 0000000000000..c3eb14082ee98 --- /dev/null +++ b/app/code/Magento/Cms/Model/Wysiwyg/Validator.php @@ -0,0 +1,87 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\Cms\Model\Wysiwyg; + +use Magento\Framework\App\Config\ScopeConfigInterface; +use Magento\Framework\Message\ManagerInterface; +use Magento\Framework\Validation\ValidationException; +use Magento\Framework\Validator\HTML\WYSIWYGValidatorInterface; +use Psr\Log\LoggerInterface; + +/** + * Processes backend validator results. + */ +class Validator implements WYSIWYGValidatorInterface +{ + public const CONFIG_PATH_THROW_EXCEPTION = 'cms/wysiwyg/force_valid'; + + /** + * @var WYSIWYGValidatorInterface + */ + private $validator; + + /** + * @var ManagerInterface + */ + private $messages; + + /** + * @var ScopeConfigInterface + */ + private $config; + + /** + * @var LoggerInterface + */ + private $logger; + + /** + * @param WYSIWYGValidatorInterface $validator + * @param ManagerInterface $messages + * @param ScopeConfigInterface $config + * @param LoggerInterface $logger + */ + public function __construct( + WYSIWYGValidatorInterface $validator, + ManagerInterface $messages, + ScopeConfigInterface $config, + LoggerInterface $logger + ) { + $this->validator = $validator; + $this->messages = $messages; + $this->config = $config; + $this->logger = $logger; + } + + /** + * @inheritDoc + */ + public function validate(string $content): void + { + $throwException = $this->config->isSetFlag(self::CONFIG_PATH_THROW_EXCEPTION); + try { + $this->validator->validate($content); + } catch (ValidationException $exception) { + if ($throwException) { + throw $exception; + } else { + $this->messages->addWarningMessage( + __('Temporarily allowed to save restricted HTML value. %1', $exception->getMessage()) + ); + } + } catch (\Throwable $exception) { + if ($throwException) { + throw $exception; + } else { + $this->messages->addWarningMessage(__('Invalid HTML provided')->render()); + $this->logger->error($exception); + } + } + } +} diff --git a/app/code/Magento/Cms/Test/Unit/Model/Wysiwyg/ValidatorTest.php b/app/code/Magento/Cms/Test/Unit/Model/Wysiwyg/ValidatorTest.php new file mode 100644 index 0000000000000..b14ad81aa2c1a --- /dev/null +++ b/app/code/Magento/Cms/Test/Unit/Model/Wysiwyg/ValidatorTest.php @@ -0,0 +1,78 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\Cms\Test\Unit\Model\Wysiwyg; + +use Magento\Cms\Model\Wysiwyg\Validator; +use Magento\Framework\App\Config\ScopeConfigInterface; +use Magento\Framework\Message\ManagerInterface; +use Magento\Framework\Validation\ValidationException; +use Magento\Framework\Validator\HTML\WYSIWYGValidatorInterface; +use PHPUnit\Framework\TestCase; +use Psr\Log\LoggerInterface; + +class ValidatorTest extends TestCase +{ + /** + * Validation cases. + * + * @return array + */ + public function getValidationCases(): array + { + return [ + 'invalid-exception' => [true, new ValidationException(__('Invalid html')), true, false], + 'invalid-warning' => [false, new \RuntimeException('Invalid html'), false, true], + 'valid' => [false, null, false, false] + ]; + } + + /** + * Test validation. + * + * @param bool $isFlagSet + * @param \Throwable|null $thrown + * @param bool $exceptionThrown + * @param bool $warned + * @dataProvider getValidationCases + */ + public function testValidate(bool $isFlagSet, ?\Throwable $thrown, bool $exceptionThrown, bool $warned): void + { + $actuallyWarned = false; + + $configMock = $this->getMockForAbstractClass(ScopeConfigInterface::class); + $configMock->method('isSetFlag') + ->with(Validator::CONFIG_PATH_THROW_EXCEPTION) + ->willReturn($isFlagSet); + + $backendMock = $this->getMockForAbstractClass(WYSIWYGValidatorInterface::class); + if ($thrown) { + $backendMock->method('validate')->willThrowException($thrown); + } + + $messagesMock = $this->getMockForAbstractClass(ManagerInterface::class); + $messagesMock->method('addWarningMessage') + ->willReturnCallback( + function () use (&$actuallyWarned): void { + $actuallyWarned = true; + } + ); + + $loggerMock = $this->getMockForAbstractClass(LoggerInterface::class); + + $validator = new Validator($backendMock, $messagesMock, $configMock, $loggerMock); + try { + $validator->validate('content'); + $actuallyThrown = false; + } catch (\Throwable $exception) { + $actuallyThrown = true; + } + $this->assertEquals($exceptionThrown, $actuallyThrown); + $this->assertEquals($warned, $actuallyWarned); + } +} diff --git a/app/code/Magento/Cms/etc/config.xml b/app/code/Magento/Cms/etc/config.xml index 7090bb7a1fd25..d7a9e172f59a6 100644 --- a/app/code/Magento/Cms/etc/config.xml +++ b/app/code/Magento/Cms/etc/config.xml @@ -24,6 +24,7 @@ <wysiwyg> <enabled>enabled</enabled> <editor>mage/adminhtml/wysiwyg/tiny_mce/tinymce4Adapter</editor> + <force_valid>0</force_valid> </wysiwyg> </cms> <system> diff --git a/app/code/Magento/Cms/etc/di.xml b/app/code/Magento/Cms/etc/di.xml index 7fc8268eea5e0..67f88605a3e11 100644 --- a/app/code/Magento/Cms/etc/di.xml +++ b/app/code/Magento/Cms/etc/di.xml @@ -243,4 +243,10 @@ </arguments> </type> <preference for="Magento\Cms\Model\Page\CustomLayoutRepositoryInterface" type="Magento\Cms\Model\Page\CustomLayout\CustomLayoutRepository" /> + <type name="Magento\Cms\Model\Wysiwyg\Validator"> + <arguments> + <argument name="validator" xsi:type="object">DefaultWYSIWYGValidator</argument> + </arguments> + </type> + <preference for="Magento\Framework\Validator\HTML\WYSIWYGValidatorInterface" type="Magento\Cms\Model\Wysiwyg\Validator" /> </config> diff --git a/dev/tests/integration/testsuite/Magento/Cms/Command/WysiwygRestrictCommandTest.php b/dev/tests/integration/testsuite/Magento/Cms/Command/WysiwygRestrictCommandTest.php new file mode 100644 index 0000000000000..cd9844dc98811 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Cms/Command/WysiwygRestrictCommandTest.php @@ -0,0 +1,78 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\Cms\Command; + +use Magento\Cms\Model\Wysiwyg\Validator; +use Magento\Framework\App\Config\ReinitableConfigInterface; +use Magento\TestFramework\Helper\Bootstrap; +use PHPUnit\Framework\TestCase; +use Symfony\Component\Console\Tester\CommandTester; + +/** + * Test the command. + * + * @magentoAppIsolation enabled + * @magentoDbIsolation enabled + */ +class WysiwygRestrictCommandTest extends TestCase +{ + /** + * @var ReinitableConfigInterface + */ + private $config; + + /** + * @var WysiwygRestrictCommandFactory + */ + private $factory; + + /** + * @inheritDoc + */ + protected function setUp(): void + { + $objectManager = Bootstrap::getObjectManager(); + $this->config = $objectManager->get(ReinitableConfigInterface::class); + $this->factory = $objectManager->get(WysiwygRestrictCommandFactory::class); + } + + /** + * "Execute" method cases. + * + * @return array + */ + public function getExecuteCases(): array + { + return [ + 'yes' => ['y', true], + 'no' => ['n', false], + 'no-but-different' => ['what', false] + ]; + } + + /** + * Test the command. + * + * @param string $argument + * @param bool $expectedFlag + * @return void + * @dataProvider getExecuteCases + * @magentoConfigFixture default_store cms/wysiwyg/force_valid 0 + */ + public function testExecute(string $argument, bool $expectedFlag): void + { + /** @var WysiwygRestrictCommand $model */ + $model = $this->factory->create(); + $tester = new CommandTester($model); + $tester->execute(['restrict' => $argument]); + + $this->config->reinit(); + $this->assertEquals($expectedFlag, $this->config->isSetFlag(Validator::CONFIG_PATH_THROW_EXCEPTION)); + } +} From 2c1e0363e4d3064e4f766071f31587bb05a83b7d Mon Sep 17 00:00:00 2001 From: ogorkun <ogorkun@adobe.com> Date: Thu, 16 Jul 2020 12:35:34 -0500 Subject: [PATCH 012/139] MC-34385: Filter fields allowing HTML --- .../Magento/Cms/Model/BlockRepository.php | 49 +++++++++++++++- app/code/Magento/Cms/Model/PageRepository.php | 18 ++++-- .../PageRepository/ValidationComposite.php | 15 ++++- .../Validator/ContentValidator.php | 57 +++++++++++++++++++ app/code/Magento/Cms/etc/di.xml | 13 +++++ .../HTML/ConfigurableWYSIWYGValidator.php | 3 + 6 files changed, 146 insertions(+), 9 deletions(-) create mode 100644 app/code/Magento/Cms/Model/PageRepository/Validator/ContentValidator.php diff --git a/app/code/Magento/Cms/Model/BlockRepository.php b/app/code/Magento/Cms/Model/BlockRepository.php index fa29cc9ff7631..317c3eeb6dcfb 100644 --- a/app/code/Magento/Cms/Model/BlockRepository.php +++ b/app/code/Magento/Cms/Model/BlockRepository.php @@ -12,10 +12,13 @@ use Magento\Cms\Model\ResourceModel\Block\CollectionFactory as BlockCollectionFactory; use Magento\Framework\Api\DataObjectHelper; use Magento\Framework\Api\SearchCriteria\CollectionProcessorInterface; +use Magento\Framework\App\ObjectManager; use Magento\Framework\Exception\CouldNotDeleteException; use Magento\Framework\Exception\CouldNotSaveException; use Magento\Framework\Exception\NoSuchEntityException; use Magento\Framework\Reflection\DataObjectProcessor; +use Magento\Framework\Validation\ValidationException; +use Magento\Framework\Validator\HTML\WYSIWYGValidatorInterface; use Magento\Store\Model\StoreManagerInterface; /** @@ -69,6 +72,11 @@ class BlockRepository implements BlockRepositoryInterface */ private $collectionProcessor; + /** + * @var WYSIWYGValidatorInterface + */ + private $wysiwygValidator; + /** * @param ResourceBlock $resource * @param BlockFactory $blockFactory @@ -79,6 +87,7 @@ class BlockRepository implements BlockRepositoryInterface * @param DataObjectProcessor $dataObjectProcessor * @param StoreManagerInterface $storeManager * @param CollectionProcessorInterface $collectionProcessor + * @param WYSIWYGValidatorInterface|null $wysiwygValidator */ public function __construct( ResourceBlock $resource, @@ -89,7 +98,8 @@ public function __construct( DataObjectHelper $dataObjectHelper, DataObjectProcessor $dataObjectProcessor, StoreManagerInterface $storeManager, - CollectionProcessorInterface $collectionProcessor = null + CollectionProcessorInterface $collectionProcessor = null, + ?WYSIWYGValidatorInterface $wysiwygValidator = null ) { $this->resource = $resource; $this->blockFactory = $blockFactory; @@ -100,13 +110,46 @@ public function __construct( $this->dataObjectProcessor = $dataObjectProcessor; $this->storeManager = $storeManager; $this->collectionProcessor = $collectionProcessor ?: $this->getCollectionProcessor(); + $this->wysiwygValidator = $wysiwygValidator + ?? ObjectManager::getInstance()->get(WYSIWYGValidatorInterface::class); + } + + /** + * Validate block's content. + * + * @param Data\BlockInterface|Block $block + * @throws CouldNotSaveException + * @return void + */ + private function validateHtml(Data\BlockInterface $block): void + { + $oldContent = null; + if ($block->getId()) { + if ($block instanceof Block && $block->getOrigData()) { + $oldContent = $block->getOrigData(Data\BlockInterface::CONTENT); + } else { + $oldBlock = $this->getById($block->getId()); + $oldContent = $oldBlock->getContent(); + } + } + if ($block->getContent() && $block->getContent() !== $oldContent) { + //Validate HTML content. + try { + $this->wysiwygValidator->validate($block->getContent()); + } catch (ValidationException $exception) { + throw new CouldNotSaveException( + __('Content HTML has restricted elements. %1', $exception->getMessage()), + $exception + ); + } + } } /** * Save Block data * * @param \Magento\Cms\Api\Data\BlockInterface $block - * @return Block + * @return Block|Data\BlockInterface * @throws CouldNotSaveException */ public function save(Data\BlockInterface $block) @@ -115,6 +158,8 @@ public function save(Data\BlockInterface $block) $block->setStoreId($this->storeManager->getStore()->getId()); } + $this->validateHtml($block); + try { $this->resource->save($block); } catch (\Exception $exception) { diff --git a/app/code/Magento/Cms/Model/PageRepository.php b/app/code/Magento/Cms/Model/PageRepository.php index 2de44b6691274..b09e9283870bc 100644 --- a/app/code/Magento/Cms/Model/PageRepository.php +++ b/app/code/Magento/Cms/Model/PageRepository.php @@ -133,15 +133,21 @@ public function __construct( private function validateLayoutUpdate(Data\PageInterface $page): void { //Persisted data - $savedPage = $page->getId() ? $this->getById($page->getId()) : null; + $oldData = null; + if ($page->getId() && $page instanceof Page) { + $oldData = $page->getOrigData(); + } //Custom layout update can be removed or kept as is. if ($page->getCustomLayoutUpdateXml() - && (!$savedPage || $page->getCustomLayoutUpdateXml() !== $savedPage->getCustomLayoutUpdateXml()) + && ( + !$oldData + || $page->getCustomLayoutUpdateXml() !== $oldData[Data\PageInterface::CUSTOM_LAYOUT_UPDATE_XML] + ) ) { throw new \InvalidArgumentException('Custom layout updates must be selected from a file'); } if ($page->getLayoutUpdateXml() - && (!$savedPage || $page->getLayoutUpdateXml() !== $savedPage->getLayoutUpdateXml()) + && (!$oldData || $page->getLayoutUpdateXml() !== $oldData[Data\PageInterface::LAYOUT_UPDATE_XML]) ) { throw new \InvalidArgumentException('Custom layout updates must be selected from a file'); } @@ -161,12 +167,12 @@ public function save(\Magento\Cms\Api\Data\PageInterface $page) $page->setStoreId($storeId); } $pageId = $page->getId(); + if ($pageId && !($page instanceof Page && $page->getOrigData())) { + $page = $this->hydrator->hydrate($this->getById($pageId), $this->hydrator->extract($page)); + } try { $this->validateLayoutUpdate($page); - if ($pageId) { - $page = $this->hydrator->hydrate($this->getById($pageId), $this->hydrator->extract($page)); - } $this->resource->save($page); $this->identityMap->add($page); } catch (\Exception $exception) { diff --git a/app/code/Magento/Cms/Model/PageRepository/ValidationComposite.php b/app/code/Magento/Cms/Model/PageRepository/ValidationComposite.php index 9fd94d4c11e1c..fe8817f5f40b4 100644 --- a/app/code/Magento/Cms/Model/PageRepository/ValidationComposite.php +++ b/app/code/Magento/Cms/Model/PageRepository/ValidationComposite.php @@ -11,6 +11,8 @@ use Magento\Cms\Api\Data\PageInterface; use Magento\Cms\Api\PageRepositoryInterface; use Magento\Framework\Api\SearchCriteriaInterface; +use Magento\Framework\App\ObjectManager; +use Magento\Framework\EntityManager\HydratorInterface; /** * Validates and saves a page @@ -27,13 +29,20 @@ class ValidationComposite implements PageRepositoryInterface */ private $validators; + /** + * @var HydratorInterface + */ + private $hydrator; + /** * @param PageRepositoryInterface $repository * @param ValidatorInterface[] $validators + * @param HydratorInterface|null $hydrator */ public function __construct( PageRepositoryInterface $repository, - array $validators = [] + array $validators = [], + ?HydratorInterface $hydrator = null ) { foreach ($validators as $validator) { if (!$validator instanceof ValidatorInterface) { @@ -44,6 +53,7 @@ public function __construct( } $this->repository = $repository; $this->validators = $validators; + $this->hydrator = $hydrator ?? ObjectManager::getInstance()->get(HydratorInterface::class); } /** @@ -51,6 +61,9 @@ public function __construct( */ public function save(PageInterface $page) { + if ($page->getId()) { + $page = $this->hydrator->hydrate($this->getById($page->getId()), $this->hydrator->extract($page)); + } foreach ($this->validators as $validator) { $validator->validate($page); } diff --git a/app/code/Magento/Cms/Model/PageRepository/Validator/ContentValidator.php b/app/code/Magento/Cms/Model/PageRepository/Validator/ContentValidator.php new file mode 100644 index 0000000000000..6bca6103863fb --- /dev/null +++ b/app/code/Magento/Cms/Model/PageRepository/Validator/ContentValidator.php @@ -0,0 +1,57 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\Cms\Model\PageRepository\Validator; + +use Magento\Cms\Api\Data\PageInterface; +use Magento\Cms\Model\Page; +use Magento\Cms\Model\PageRepository\ValidatorInterface; +use Magento\Framework\Validation\ValidationException; +use Magento\Framework\Validator\HTML\WYSIWYGValidatorInterface; + +/** + * Validates pages' content. + */ +class ContentValidator implements ValidatorInterface +{ + + /** + * @var WYSIWYGValidatorInterface + */ + private $wysiwygValidator; + + /** + * @param WYSIWYGValidatorInterface $wysiwygValidator + */ + public function __construct(WYSIWYGValidatorInterface $wysiwygValidator) + { + $this->wysiwygValidator = $wysiwygValidator; + } + + /** + * @inheritDoc + */ + public function validate(PageInterface $page): void + { + $oldValue = null; + if ($page->getId() && $page instanceof Page && $page->getOrigData()) { + $oldValue = $page->getOrigData(PageInterface::CONTENT); + } + + if ($page->getContent() && $page->getContent() !== $oldValue) { + try { + $this->wysiwygValidator->validate($page->getContent()); + } catch (ValidationException $exception) { + throw new ValidationException( + __('Content HTML contains restricted elements. %1', $exception->getMessage()), + $exception + ); + } + } + } +} diff --git a/app/code/Magento/Cms/etc/di.xml b/app/code/Magento/Cms/etc/di.xml index 67f88605a3e11..1f2067a6e525b 100644 --- a/app/code/Magento/Cms/etc/di.xml +++ b/app/code/Magento/Cms/etc/di.xml @@ -233,6 +233,7 @@ <argument name="repository" xsi:type="object">Magento\Cms\Model\PageRepository</argument> <argument name="validators" xsi:type="array"> <item name="layout_update" xsi:type="object">Magento\Cms\Model\PageRepository\Validator\LayoutUpdateValidator</item> + <item name="content" xsi:type="object">Magento\Cms\Model\PageRepository\Validator\ContentValidator</item> </argument> </arguments> </type> @@ -249,4 +250,16 @@ </arguments> </type> <preference for="Magento\Framework\Validator\HTML\WYSIWYGValidatorInterface" type="Magento\Cms\Model\Wysiwyg\Validator" /> + <type name="Magento\Framework\Console\CommandListInterface"> + <arguments> + <argument name="commands" xsi:type="array"> + <item name="cms_wysiwyg_restrict" xsi:type="object">Magento\Cms\Command\WysiwygRestrictCommand</item> + </argument> + </arguments> + </type> + <type name="Magento\Cms\Model\PageRepository\Validator\ContentValidator"> + <arguments> + <argument name="hydrator" xsi:type="object">Magento\Framework\EntityManager\AbstractModelHydrator</argument> + </arguments> + </type> </config> diff --git a/lib/internal/Magento/Framework/Validator/HTML/ConfigurableWYSIWYGValidator.php b/lib/internal/Magento/Framework/Validator/HTML/ConfigurableWYSIWYGValidator.php index 0b1993c044f6f..0e317f071ab39 100644 --- a/lib/internal/Magento/Framework/Validator/HTML/ConfigurableWYSIWYGValidator.php +++ b/lib/internal/Magento/Framework/Validator/HTML/ConfigurableWYSIWYGValidator.php @@ -56,6 +56,9 @@ function (string $tag) use ($allowedTags): bool { */ public function validate(string $content): void { + if (mb_strlen($content) === 0) { + return; + } $dom = $this->loadHtml($content); $xpath = new \DOMXPath($dom); From 1e50290e8fd249a7163cf1f5bc381a2674275b82 Mon Sep 17 00:00:00 2001 From: ogorkun <ogorkun@adobe.com> Date: Fri, 17 Jul 2020 10:45:31 -0500 Subject: [PATCH 013/139] MC-34385: Filter fields allowing HTML --- app/code/Magento/Cms/Model/Page.php | 33 ++++++++++- .../Validator/ContentValidator.php | 57 ------------------- app/code/Magento/Cms/etc/di.xml | 7 +-- 3 files changed, 33 insertions(+), 64 deletions(-) delete mode 100644 app/code/Magento/Cms/Model/PageRepository/Validator/ContentValidator.php diff --git a/app/code/Magento/Cms/Model/Page.php b/app/code/Magento/Cms/Model/Page.php index 28d013f45f1fa..35e049caea203 100644 --- a/app/code/Magento/Cms/Model/Page.php +++ b/app/code/Magento/Cms/Model/Page.php @@ -13,6 +13,8 @@ use Magento\Framework\DataObject\IdentityInterface; use Magento\Framework\Exception\LocalizedException; use Magento\Framework\Model\AbstractModel; +use Magento\Framework\Validation\ValidationException; +use Magento\Framework\Validator\HTML\WYSIWYGValidatorInterface; /** * Cms Page Model @@ -64,6 +66,11 @@ class Page extends AbstractModel implements PageInterface, IdentityInterface */ private $customLayoutRepository; + /** + * @var WYSIWYGValidatorInterface + */ + private $wysiwygValidator; + /** * @param \Magento\Framework\Model\Context $context * @param \Magento\Framework\Registry $registry @@ -71,6 +78,7 @@ class Page extends AbstractModel implements PageInterface, IdentityInterface * @param \Magento\Framework\Data\Collection\AbstractDb|null $resourceCollection * @param array $data * @param CustomLayoutRepository|null $customLayoutRepository + * @param WYSIWYGValidatorInterface|null $wysiwygValidator */ public function __construct( \Magento\Framework\Model\Context $context, @@ -78,11 +86,14 @@ public function __construct( \Magento\Framework\Model\ResourceModel\AbstractResource $resource = null, \Magento\Framework\Data\Collection\AbstractDb $resourceCollection = null, array $data = [], - ?CustomLayoutRepository $customLayoutRepository = null + ?CustomLayoutRepository $customLayoutRepository = null, + ?WYSIWYGValidatorInterface $wysiwygValidator = null ) { parent::__construct($context, $registry, $resource, $resourceCollection, $data); $this->customLayoutRepository = $customLayoutRepository ?? ObjectManager::getInstance()->get(CustomLayoutRepository::class); + $this->wysiwygValidator = $wysiwygValidator + ?? ObjectManager::getInstance()->get(WYSIWYGValidatorInterface::class); } /** @@ -615,6 +626,26 @@ public function beforeSave() $this->setData('layout_update_selected', $layoutUpdate); $this->customLayoutRepository->validateLayoutSelectedFor($this); + //Validating Content HTML. + $oldValue = null; + if ($this->getId()) { + if ($this->getOrigData()) { + $oldValue = $this->getOrigData(self::CONTENT); + } elseif (array_key_exists(self::CONTENT, $this->getStoredData())) { + $oldValue = $this->getStoredData()[self::CONTENT]; + } + } + if ($this->getContent() && $this->getContent() !== $oldValue) { + try { + $this->wysiwygValidator->validate($this->getContent()); + } catch (ValidationException $exception) { + throw new ValidationException( + __('Content HTML contains restricted elements. %1', $exception->getMessage()), + $exception + ); + } + } + return parent::beforeSave(); } diff --git a/app/code/Magento/Cms/Model/PageRepository/Validator/ContentValidator.php b/app/code/Magento/Cms/Model/PageRepository/Validator/ContentValidator.php deleted file mode 100644 index 6bca6103863fb..0000000000000 --- a/app/code/Magento/Cms/Model/PageRepository/Validator/ContentValidator.php +++ /dev/null @@ -1,57 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ - -declare(strict_types=1); - -namespace Magento\Cms\Model\PageRepository\Validator; - -use Magento\Cms\Api\Data\PageInterface; -use Magento\Cms\Model\Page; -use Magento\Cms\Model\PageRepository\ValidatorInterface; -use Magento\Framework\Validation\ValidationException; -use Magento\Framework\Validator\HTML\WYSIWYGValidatorInterface; - -/** - * Validates pages' content. - */ -class ContentValidator implements ValidatorInterface -{ - - /** - * @var WYSIWYGValidatorInterface - */ - private $wysiwygValidator; - - /** - * @param WYSIWYGValidatorInterface $wysiwygValidator - */ - public function __construct(WYSIWYGValidatorInterface $wysiwygValidator) - { - $this->wysiwygValidator = $wysiwygValidator; - } - - /** - * @inheritDoc - */ - public function validate(PageInterface $page): void - { - $oldValue = null; - if ($page->getId() && $page instanceof Page && $page->getOrigData()) { - $oldValue = $page->getOrigData(PageInterface::CONTENT); - } - - if ($page->getContent() && $page->getContent() !== $oldValue) { - try { - $this->wysiwygValidator->validate($page->getContent()); - } catch (ValidationException $exception) { - throw new ValidationException( - __('Content HTML contains restricted elements. %1', $exception->getMessage()), - $exception - ); - } - } - } -} diff --git a/app/code/Magento/Cms/etc/di.xml b/app/code/Magento/Cms/etc/di.xml index 1f2067a6e525b..1837aaca74789 100644 --- a/app/code/Magento/Cms/etc/di.xml +++ b/app/code/Magento/Cms/etc/di.xml @@ -233,8 +233,8 @@ <argument name="repository" xsi:type="object">Magento\Cms\Model\PageRepository</argument> <argument name="validators" xsi:type="array"> <item name="layout_update" xsi:type="object">Magento\Cms\Model\PageRepository\Validator\LayoutUpdateValidator</item> - <item name="content" xsi:type="object">Magento\Cms\Model\PageRepository\Validator\ContentValidator</item> </argument> + <argument name="hydrator" xsi:type="object">Magento\Framework\EntityManager\AbstractModelHydrator</argument> </arguments> </type> <preference for="Magento\Cms\Model\Page\CustomLayoutManagerInterface" type="Magento\Cms\Model\Page\CustomLayout\CustomLayoutManager" /> @@ -257,9 +257,4 @@ </argument> </arguments> </type> - <type name="Magento\Cms\Model\PageRepository\Validator\ContentValidator"> - <arguments> - <argument name="hydrator" xsi:type="object">Magento\Framework\EntityManager\AbstractModelHydrator</argument> - </arguments> - </type> </config> From 9b094232f14e1677fac4898b6fff1d0e53f032eb Mon Sep 17 00:00:00 2001 From: ogorkun <ogorkun@adobe.com> Date: Fri, 17 Jul 2020 15:53:44 -0500 Subject: [PATCH 014/139] MC-34385: Filter fields allowing HTML --- app/code/Magento/Cms/Model/Block.php | 57 +++++++++++++++++-- .../Magento/Cms/Model/BlockRepository.php | 51 ++++------------- app/code/Magento/Cms/etc/di.xml | 1 + 3 files changed, 63 insertions(+), 46 deletions(-) diff --git a/app/code/Magento/Cms/Model/Block.php b/app/code/Magento/Cms/Model/Block.php index 9da444c72e80c..ab8d65399f37c 100644 --- a/app/code/Magento/Cms/Model/Block.php +++ b/app/code/Magento/Cms/Model/Block.php @@ -6,8 +6,15 @@ namespace Magento\Cms\Model; use Magento\Cms\Api\Data\BlockInterface; +use Magento\Framework\App\ObjectManager; use Magento\Framework\DataObject\IdentityInterface; use Magento\Framework\Model\AbstractModel; +use Magento\Framework\Validation\ValidationException; +use Magento\Framework\Validator\HTML\WYSIWYGValidatorInterface; +use Magento\Framework\Model\Context; +use Magento\Framework\Registry; +use Magento\Framework\Model\ResourceModel\AbstractResource; +use Magento\Framework\Data\Collection\AbstractDb; /** * CMS block model @@ -40,6 +47,32 @@ class Block extends AbstractModel implements BlockInterface, IdentityInterface */ protected $_eventPrefix = 'cms_block'; + /** + * @var WYSIWYGValidatorInterface + */ + private $wysiwygValidator; + + /** + * @param Context $context + * @param Registry $registry + * @param AbstractResource|null $resource + * @param AbstractDb|null $resourceCollection + * @param array $data + * @param WYSIWYGValidatorInterface|null $wysiwygValidator + */ + public function __construct( + Context $context, + Registry $registry, + AbstractResource $resource = null, + AbstractDb $resourceCollection = null, + array $data = [], + ?WYSIWYGValidatorInterface $wysiwygValidator = null + ) { + parent::__construct($context, $registry, $resource, $resourceCollection, $data); + $this->wysiwygValidator = $wysiwygValidator + ?? ObjectManager::getInstance()->get(WYSIWYGValidatorInterface::class); + } + /** * Construct. * @@ -63,12 +96,26 @@ public function beforeSave() } $needle = 'block_id="' . $this->getId() . '"'; - if (false == strstr($this->getContent(), (string) $needle)) { - return parent::beforeSave(); + if (strstr($this->getContent(), (string) $needle) !== false) { + throw new \Magento\Framework\Exception\LocalizedException( + __('Make sure that static block content does not reference the block itself.') + ); } - throw new \Magento\Framework\Exception\LocalizedException( - __('Make sure that static block content does not reference the block itself.') - ); + parent::beforeSave(); + + //Validating HTML content. + if ($this->getContent() && $this->getContent() !== $this->getOrigData(self::CONTENT)) { + try { + $this->wysiwygValidator->validate($this->getContent()); + } catch (ValidationException $exception) { + throw new ValidationException( + __('Content field contains restricted HTML elements. %1', $exception->getMessage()), + $exception + ); + } + } + + return $this; } /** diff --git a/app/code/Magento/Cms/Model/BlockRepository.php b/app/code/Magento/Cms/Model/BlockRepository.php index 317c3eeb6dcfb..f8129ca4a2961 100644 --- a/app/code/Magento/Cms/Model/BlockRepository.php +++ b/app/code/Magento/Cms/Model/BlockRepository.php @@ -17,9 +17,8 @@ use Magento\Framework\Exception\CouldNotSaveException; use Magento\Framework\Exception\NoSuchEntityException; use Magento\Framework\Reflection\DataObjectProcessor; -use Magento\Framework\Validation\ValidationException; -use Magento\Framework\Validator\HTML\WYSIWYGValidatorInterface; use Magento\Store\Model\StoreManagerInterface; +use Magento\Framework\EntityManager\HydratorInterface; /** * Class BlockRepository @@ -73,9 +72,9 @@ class BlockRepository implements BlockRepositoryInterface private $collectionProcessor; /** - * @var WYSIWYGValidatorInterface + * @var HydratorInterface */ - private $wysiwygValidator; + private $hydrator; /** * @param ResourceBlock $resource @@ -87,7 +86,7 @@ class BlockRepository implements BlockRepositoryInterface * @param DataObjectProcessor $dataObjectProcessor * @param StoreManagerInterface $storeManager * @param CollectionProcessorInterface $collectionProcessor - * @param WYSIWYGValidatorInterface|null $wysiwygValidator + * @param HydratorInterface|null $hydrator */ public function __construct( ResourceBlock $resource, @@ -99,7 +98,7 @@ public function __construct( DataObjectProcessor $dataObjectProcessor, StoreManagerInterface $storeManager, CollectionProcessorInterface $collectionProcessor = null, - ?WYSIWYGValidatorInterface $wysiwygValidator = null + ?HydratorInterface $hydrator = null ) { $this->resource = $resource; $this->blockFactory = $blockFactory; @@ -110,46 +109,14 @@ public function __construct( $this->dataObjectProcessor = $dataObjectProcessor; $this->storeManager = $storeManager; $this->collectionProcessor = $collectionProcessor ?: $this->getCollectionProcessor(); - $this->wysiwygValidator = $wysiwygValidator - ?? ObjectManager::getInstance()->get(WYSIWYGValidatorInterface::class); - } - - /** - * Validate block's content. - * - * @param Data\BlockInterface|Block $block - * @throws CouldNotSaveException - * @return void - */ - private function validateHtml(Data\BlockInterface $block): void - { - $oldContent = null; - if ($block->getId()) { - if ($block instanceof Block && $block->getOrigData()) { - $oldContent = $block->getOrigData(Data\BlockInterface::CONTENT); - } else { - $oldBlock = $this->getById($block->getId()); - $oldContent = $oldBlock->getContent(); - } - } - if ($block->getContent() && $block->getContent() !== $oldContent) { - //Validate HTML content. - try { - $this->wysiwygValidator->validate($block->getContent()); - } catch (ValidationException $exception) { - throw new CouldNotSaveException( - __('Content HTML has restricted elements. %1', $exception->getMessage()), - $exception - ); - } - } + $this->hydrator = $hydrator ?? ObjectManager::getInstance()->get(HydratorInterface::class); } /** * Save Block data * * @param \Magento\Cms\Api\Data\BlockInterface $block - * @return Block|Data\BlockInterface + * @return Block * @throws CouldNotSaveException */ public function save(Data\BlockInterface $block) @@ -158,7 +125,9 @@ public function save(Data\BlockInterface $block) $block->setStoreId($this->storeManager->getStore()->getId()); } - $this->validateHtml($block); + if ($block->getId() && $block instanceof Block && !$block->getOrigData()) { + $block = $this->hydrator->hydrate($this->getById($block->getId()), $this->hydrator->extract($block)); + } try { $this->resource->save($block); diff --git a/app/code/Magento/Cms/etc/di.xml b/app/code/Magento/Cms/etc/di.xml index 1837aaca74789..d79e805e25890 100644 --- a/app/code/Magento/Cms/etc/di.xml +++ b/app/code/Magento/Cms/etc/di.xml @@ -215,6 +215,7 @@ <type name="Magento\Cms\Model\BlockRepository"> <arguments> <argument name="collectionProcessor" xsi:type="object">Magento\Cms\Model\Api\SearchCriteria\BlockCollectionProcessor</argument> + <argument name="hydrator" xsi:type="object">Magento\Framework\EntityManager\AbstractModelHydrator</argument> </arguments> </type> From f0c645307e031f9c6b8beb31f18c801dc62c02e3 Mon Sep 17 00:00:00 2001 From: Pieter Hoste <hoste.pieter@gmail.com> Date: Sun, 19 Jul 2020 11:07:26 +0200 Subject: [PATCH 015/139] Avoids indefinite loop of indexers being marked as invalid. --- app/code/Magento/Indexer/Model/Processor.php | 80 ++++++++++++++++--- .../Indexer/Test/Unit/Model/ProcessorTest.php | 8 ++ 2 files changed, 76 insertions(+), 12 deletions(-) diff --git a/app/code/Magento/Indexer/Model/Processor.php b/app/code/Magento/Indexer/Model/Processor.php index 534ea805bb8fc..01f530488fbe7 100644 --- a/app/code/Magento/Indexer/Model/Processor.php +++ b/app/code/Magento/Indexer/Model/Processor.php @@ -15,6 +15,11 @@ */ class Processor { + /** + * @var array + */ + private $sharedIndexesComplete = []; + /** * @var ConfigInterface */ @@ -60,32 +65,83 @@ public function __construct( */ public function reindexAllInvalid() { - $sharedIndexesComplete = []; foreach (array_keys($this->config->getIndexers()) as $indexerId) { /** @var Indexer $indexer */ $indexer = $this->indexerFactory->create(); $indexer->load($indexerId); $indexerConfig = $this->config->getIndexer($indexerId); + $sharedIndex = $indexerConfig['shared_index']; + if ($indexer->isInvalid()) { // Skip indexers having shared index that was already complete $sharedIndex = $indexerConfig['shared_index'] ?? null; - if (!in_array($sharedIndex, $sharedIndexesComplete)) { + if (!in_array($sharedIndex, $this->sharedIndexesComplete)) { $indexer->reindexAll(); - } else { - /** @var \Magento\Indexer\Model\Indexer\State $state */ - $state = $indexer->getState(); - $state->setStatus(StateInterface::STATUS_WORKING); - $state->save(); - $state->setStatus(StateInterface::STATUS_VALID); - $state->save(); - } - if ($sharedIndex) { - $sharedIndexesComplete[] = $sharedIndex; + + if ($sharedIndex) { + $this->validateSharedIndex($sharedIndex); + } } } } } + /** + * Get indexer ids that have common shared index + * + * @param string $sharedIndex + * @return array + */ + private function getIndexerIdsBySharedIndex(string $sharedIndex): array + { + $indexers = $this->config->getIndexers(); + + $result = []; + foreach ($indexers as $indexerConfig) { + if ($indexerConfig['shared_index'] == $sharedIndex) { + $result[] = $indexerConfig['indexer_id']; + } + } + + return $result; + } + + /** + * Validate indexers by shared index ID + * + * @param string $sharedIndex + * @return $this + */ + private function validateSharedIndex(string $sharedIndex): self + { + if (empty($sharedIndex)) { + throw new \InvalidArgumentException( + 'The sharedIndex is an invalid shared index identifier. Verify the identifier and try again.' + ); + } + + $indexerIds = $this->getIndexerIdsBySharedIndex($sharedIndex); + if (empty($indexerIds)) { + return $this; + } + + foreach ($indexerIds as $indexerId) { + /** @var \Magento\Indexer\Model\Indexer $indexer */ + $indexer = $this->indexerFactory->create(); + $indexer->load($indexerId); + /** @var \Magento\Indexer\Model\Indexer\State $state */ + $state = $indexer->getState(); + $state->setStatus(StateInterface::STATUS_WORKING); + $state->save(); + $state->setStatus(StateInterface::STATUS_VALID); + $state->save(); + } + + $this->sharedIndexesComplete[] = $sharedIndex; + + return $this; + } + /** * Regenerate indexes for all indexers * diff --git a/app/code/Magento/Indexer/Test/Unit/Model/ProcessorTest.php b/app/code/Magento/Indexer/Test/Unit/Model/ProcessorTest.php index 7a06fb745ba89..9cc0277997289 100644 --- a/app/code/Magento/Indexer/Test/Unit/Model/ProcessorTest.php +++ b/app/code/Magento/Indexer/Test/Unit/Model/ProcessorTest.php @@ -85,6 +85,14 @@ public function testReindexAllInvalid() $this->configMock->expects($this->once())->method('getIndexers')->willReturn($indexers); + $this->configMock->expects($this->exactly(2)) + ->method('getIndexer') + ->willReturn( + [ + 'shared_index' => null + ] + ); + $state1Mock = $this->createPartialMock(State::class, ['getStatus', '__wakeup']); $state1Mock->expects( $this->once() From 3dcf9110e7147beebf1529880315dfc1daccedc7 Mon Sep 17 00:00:00 2001 From: ogorkun <ogorkun@adobe.com> Date: Mon, 20 Jul 2020 13:32:27 -0500 Subject: [PATCH 016/139] MC-34385: Filter fields allowing HTML --- app/code/Magento/Cms/Model/Wysiwyg/Validator.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/app/code/Magento/Cms/Model/Wysiwyg/Validator.php b/app/code/Magento/Cms/Model/Wysiwyg/Validator.php index c3eb14082ee98..eb17a0f3127ea 100644 --- a/app/code/Magento/Cms/Model/Wysiwyg/Validator.php +++ b/app/code/Magento/Cms/Model/Wysiwyg/Validator.php @@ -72,7 +72,10 @@ public function validate(string $content): void throw $exception; } else { $this->messages->addWarningMessage( - __('Temporarily allowed to save restricted HTML value. %1', $exception->getMessage()) + __( + 'Temporarily allowed to save HTML value that contains restricted elements. %1', + $exception->getMessage() + ) ); } } catch (\Throwable $exception) { From 3dd1811cd5e4dad3f05b3881a8a1b63490086be3 Mon Sep 17 00:00:00 2001 From: ogorkun <ogorkun@adobe.com> Date: Wed, 22 Jul 2020 16:41:14 -0500 Subject: [PATCH 017/139] MC-34385: Filter fields allowing HTML --- app/etc/di.xml | 31 +++++++ .../HTML/ConfigurableWYSIWYGValidatorTest.php | 89 +++++++++++++++---- .../HTML/StyleAttributeValidatorTest.php | 57 ++++++++++++ .../HTML/AttributeValidatorInterface.php | 28 ++++++ .../HTML/ConfigurableWYSIWYGValidator.php | 32 +++++-- .../HTML/StyleAttributeValidator.php | 31 +++++++ 6 files changed, 248 insertions(+), 20 deletions(-) create mode 100644 lib/internal/Magento/Framework/Test/Unit/Validator/HTML/StyleAttributeValidatorTest.php create mode 100644 lib/internal/Magento/Framework/Validator/HTML/AttributeValidatorInterface.php create mode 100644 lib/internal/Magento/Framework/Validator/HTML/StyleAttributeValidator.php diff --git a/app/etc/di.xml b/app/etc/di.xml index 9b85e09ac9611..fa1887cbe1372 100644 --- a/app/etc/di.xml +++ b/app/etc/di.xml @@ -1837,14 +1837,45 @@ <argument name="allowedTags" xsi:type="array"> <item name="div" xsi:type="string">div</item> <item name="a" xsi:type="string">a</item> + <item name="p" xsi:type="string">p</item> + <item name="span" xsi:type="string">span</item> + <item name="em" xsi:type="string">em</item> + <item name="strong" xsi:type="string">strong</item> + <item name="ul" xsi:type="string">ul</item> + <item name="li" xsi:type="string">li</item> + <item name="ol" xsi:type="string">ol</item> + <item name="h5" xsi:type="string">h5</item> + <item name="h4" xsi:type="string">h4</item> + <item name="h3" xsi:type="string">h3</item> + <item name="h2" xsi:type="string">h2</item> + <item name="h1" xsi:type="string">h1</item> + <item name="table" xsi:type="string">table</item> + <item name="tbody" xsi:type="string">tbody</item> + <item name="tr" xsi:type="string">tr</item> + <item name="td" xsi:type="string">td</item> + <item name="th" xsi:type="string">th</item> + <item name="tfoot" xsi:type="string">tfoot</item> + <item name="img" xsi:type="string">img</item> </argument> <argument name="allowedAttributes" xsi:type="array"> <item name="class" xsi:type="string">class</item> + <item name="width" xsi:type="string">width</item> + <item name="height" xsi:type="string">height</item> + <item name="style" xsi:type="string">style</item> + <item name="alt" xsi:type="string">alt</item> + <item name="title" xsi:type="string">title</item> + <item name="border" xsi:type="string">border</item> </argument> <argument name="attributesAllowedByTags" xsi:type="array"> <item name="a" xsi:type="array"> <item name="href" xsi:type="string">href</item> </item> + <item name="img" xsi:type="array"> + <item name="src" xsi:type="string">src</item> + </item> + </argument> + <argument name="attributeValidators" xsi:type="array"> + <item name="style" xsi:type="object">Magento\Framework\Validator\HTML\StyleAttributeValidator</item> </argument> </arguments> </virtualType> diff --git a/lib/internal/Magento/Framework/Test/Unit/Validator/HTML/ConfigurableWYSIWYGValidatorTest.php b/lib/internal/Magento/Framework/Test/Unit/Validator/HTML/ConfigurableWYSIWYGValidatorTest.php index aef019b20f519..43ff2ae1377b0 100644 --- a/lib/internal/Magento/Framework/Test/Unit/Validator/HTML/ConfigurableWYSIWYGValidatorTest.php +++ b/lib/internal/Magento/Framework/Test/Unit/Validator/HTML/ConfigurableWYSIWYGValidatorTest.php @@ -10,6 +10,7 @@ use Magento\Framework\Validation\ValidationException; use Magento\Framework\Validator\HTML\ConfigurableWYSIWYGValidator; +use Magento\Framework\Validator\HTML\AttributeValidatorInterface; use PHPUnit\Framework\TestCase; class ConfigurableWYSIWYGValidatorTest extends TestCase @@ -22,25 +23,34 @@ class ConfigurableWYSIWYGValidatorTest extends TestCase public function getConfigurations(): array { return [ - 'no-html' => [['div'], [], [], 'just text', true], - 'allowed-tag' => [['div'], [], [], 'just text and <div>a div</div>', true], - 'restricted-tag' => [['div', 'p'], [], [], 'text and <p>a p</p>, <div>a div</div>, <tr>a tr</tr>', false], - 'restricted-tag-wtih-attr' => [['div'], [], [], 'just text and <p class="fake-class">a p</p>', false], - 'allowed-tag-with-attr' => [['div'], [], [], 'just text and <div class="fake-class">a div</div>', false], - 'multiple-tags' => [['div', 'p'], [], [], 'just text and <div>a div</div> and <p>a p</p>', true], + 'no-html' => [['div'], [], [], 'just text', true, []], + 'allowed-tag' => [['div'], [], [], 'just text and <div>a div</div>', true, []], + 'restricted-tag' => [ + ['div', 'p'], + [], + [], + 'text and <p>a p</p>, <div>a div</div>, <tr>a tr</tr>', + false, + [] + ], + 'restricted-tag-wtih-attr' => [['div'], [], [], 'just text and <p class="fake-class">a p</p>', false, []], + 'allowed-tag-with-attr' => [['div'], [], [], 'just text and <div class="fake-class">a div</div>', false, []], + 'multiple-tags' => [['div', 'p'], [], [], 'just text and <div>a div</div> and <p>a p</p>', true, []], 'tags-with-attrs' => [ ['div', 'p'], ['class', 'style'], [], 'text and <div class="fake-class">a div</div> and <p style="color: blue">a p</p>', - true + true, + [] ], 'tags-with-restricted-attrs' => [ ['div', 'p'], ['class', 'align'], [], 'text and <div class="fake-class">a div</div> and <p style="color: blue">a p</p>', - false + false, + [] ], 'tags-with-specific-attrs' => [ ['div', 'a', 'p'], @@ -48,14 +58,16 @@ public function getConfigurations(): array ['a' => ['href'], 'div' => ['style']], '<div class="fake-class" style="color: blue">a div</div>, <a href="/some-path" class="a">an a</a>' .', <p class="p-class">a p</p>', - true + true, + [] ], 'tags-with-specific-restricted-attrs' => [ ['div', 'a'], ['class'], ['a' => ['href']], 'text and <div class="fake-class" href="what">a div</div> and <a href="/some-path" class="a">an a</a>', - false + false, + [] ], 'invalid-tag-with-full-config' => [ ['div', 'a', 'p'], @@ -63,21 +75,48 @@ public function getConfigurations(): array ['a' => ['href'], 'div' => ['style']], '<div class="fake-class" style="color: blue">a div</div>, <a href="/some-path" class="a">an a</a>' .', <p class="p-class">a p</p>, <img src="test.jpg" />', - false + false, + [] ], 'invalid-html' => [ ['div', 'a', 'p'], ['class', 'src'], ['a' => ['href'], 'div' => ['style']], 'some </,none-> </html>', - true + true, + [] ], 'invalid-html-with-violations' => [ ['div', 'a', 'p'], ['class', 'src'], ['a' => ['href'], 'div' => ['style']], 'some </,none-> </html> <tr>some trs</tr>', - false + false, + [] + ], + 'invalid-html-attributes' => [ + ['div', 'a', 'p'], + ['class', 'src'], + [], + 'some <div class="value">DIV</div>', + false, + ['class' => false] + ], + 'ignored-html-attributes' => [ + ['div', 'a', 'p'], + ['class', 'src'], + [], + 'some <div class="value">DIV</div>', + true, + ['src' => false, 'class' => true] + ], + 'valid-html-attributes' => [ + ['div', 'a', 'p'], + ['class', 'src'], + [], + 'some <div class="value">DIV</div>', + true, + ['src' => true, 'class' => true] ] ]; } @@ -90,6 +129,7 @@ public function getConfigurations(): array * @param array $allowedTagAttrs * @param string $html * @param bool $isValid + * @param array $attributeValidityMap * @return void * @dataProvider getConfigurations */ @@ -98,9 +138,28 @@ public function testConfigurations( array $allowedAttr, array $allowedTagAttrs, string $html, - bool $isValid + bool $isValid, + array $attributeValidityMap ): void { - $validator = new ConfigurableWYSIWYGValidator($allowedTags, $allowedAttr, $allowedTagAttrs); + $attributeValidator = $this->getMockForAbstractClass(AttributeValidatorInterface::class); + $attributeValidator->method('validate') + ->willReturnCallback( + function (string $tag, string $attribute, string $content) use ($attributeValidityMap): void { + if (array_key_exists($attribute, $attributeValidityMap) && !$attributeValidityMap[$attribute]) { + throw new ValidationException(__('Invalid attribute for %1', $tag)); + } + } + ); + $attrValidators = []; + foreach (array_keys($attributeValidityMap) as $attr) { + $attrValidators[$attr] = $attributeValidator; + } + $validator = new ConfigurableWYSIWYGValidator( + $allowedTags, + $allowedAttr, + $allowedTagAttrs, + $attrValidators + ); $valid = true; try { $validator->validate($html); diff --git a/lib/internal/Magento/Framework/Test/Unit/Validator/HTML/StyleAttributeValidatorTest.php b/lib/internal/Magento/Framework/Test/Unit/Validator/HTML/StyleAttributeValidatorTest.php new file mode 100644 index 0000000000000..b705939feec16 --- /dev/null +++ b/lib/internal/Magento/Framework/Test/Unit/Validator/HTML/StyleAttributeValidatorTest.php @@ -0,0 +1,57 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\Framework\Test\Unit\Validator\HTML; + +use Magento\Framework\Validation\ValidationException; +use Magento\Framework\Validator\HTML\StyleAttributeValidator; +use PHPUnit\Framework\TestCase; + +class StyleAttributeValidatorTest extends TestCase +{ + /** + * Cases for "validate" test. + * + * @return array + */ + public function getAttributes(): array + { + return [ + 'not a style' => ['class', 'value', true], + 'valid style' => ['style', 'color: blue', true], + 'invalid position style' => ['style', 'color: blue; position: absolute; width: 100%', false], + 'another invalid position style' => ['style', 'position: fixed; width: 100%', false], + 'valid position style' => ['style', 'color: blue; position: inherit; width: 100%', true], + 'valid background style' => ['style', 'color: blue; background-position: left; width: 100%', true], + 'invalid opacity style' => ['style', 'color: blue; width: 100%; opacity: 0.5', false], + 'invalid z-index style' => ['style', 'color: blue; width: 100%; z-index: 11', false] + ]; + } + + /** + * Test "validate" method. + * + * @param string $attr + * @param string $value + * @param bool $expectedValid + * @return void + * @dataProvider getAttributes + */ + public function testValidate(string $attr, string $value, bool $expectedValid): void + { + $validator = new StyleAttributeValidator(); + + try { + $validator->validate('does not matter', $attr, $value); + $actuallyValid = true; + } catch (ValidationException $exception) { + $actuallyValid = false; + } + $this->assertEquals($expectedValid, $actuallyValid); + } +} diff --git a/lib/internal/Magento/Framework/Validator/HTML/AttributeValidatorInterface.php b/lib/internal/Magento/Framework/Validator/HTML/AttributeValidatorInterface.php new file mode 100644 index 0000000000000..6426e19a537da --- /dev/null +++ b/lib/internal/Magento/Framework/Validator/HTML/AttributeValidatorInterface.php @@ -0,0 +1,28 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\Framework\Validator\HTML; + +use Magento\Framework\Validation\ValidationException; + +/** + * Validates HTML attributes content. + */ +interface AttributeValidatorInterface +{ + /** + * Validate attribute. + * + * @param string $tag + * @param string $attributeName + * @param string $value + * @return void + * @throws ValidationException + */ + public function validate(string $tag, string $attributeName, string $value): void; +} diff --git a/lib/internal/Magento/Framework/Validator/HTML/ConfigurableWYSIWYGValidator.php b/lib/internal/Magento/Framework/Validator/HTML/ConfigurableWYSIWYGValidator.php index 0e317f071ab39..5b9a73a5f2570 100644 --- a/lib/internal/Magento/Framework/Validator/HTML/ConfigurableWYSIWYGValidator.php +++ b/lib/internal/Magento/Framework/Validator/HTML/ConfigurableWYSIWYGValidator.php @@ -31,12 +31,22 @@ class ConfigurableWYSIWYGValidator implements WYSIWYGValidatorInterface private $attributesAllowedByTags; /** - * @param array $allowedTags - * @param array $allowedAttributes - * @param array $attributesAllowedByTags + * @var AttributeValidatorInterface[] */ - public function __construct(array $allowedTags, array $allowedAttributes = [], array $attributesAllowedByTags = []) - { + private $attributeValidators; + + /** + * @param string[] $allowedTags + * @param string[] $allowedAttributes + * @param string[] $attributesAllowedByTags + * @param AttributeValidatorInterface[] $attributeValidators + */ + public function __construct( + array $allowedTags, + array $allowedAttributes = [], + array $attributesAllowedByTags = [], + array $attributeValidators = [] + ) { if (empty(array_filter($allowedTags))) { throw new \InvalidArgumentException('List of allowed HTML tags cannot be empty'); } @@ -49,6 +59,7 @@ function (string $tag) use ($allowedTags): bool { }, ARRAY_FILTER_USE_KEY ); + $this->attributeValidators = $attributeValidators; } /** @@ -132,6 +143,17 @@ function (string $attribute): string { ); } } + + //Validating allowed attributes. + if ($this->attributeValidators) { + foreach ($this->attributeValidators as $attr => $validator) { + $found = $xpath->query("//@*[name() = '$attr']"); + foreach ($found as $attribute) { + $validator->validate($attribute->parentNode->tagName, $attribute->name, $attribute->value); + } + } + } + } /** diff --git a/lib/internal/Magento/Framework/Validator/HTML/StyleAttributeValidator.php b/lib/internal/Magento/Framework/Validator/HTML/StyleAttributeValidator.php new file mode 100644 index 0000000000000..4b5ccc9e32863 --- /dev/null +++ b/lib/internal/Magento/Framework/Validator/HTML/StyleAttributeValidator.php @@ -0,0 +1,31 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\Framework\Validator\HTML; + +use Magento\Framework\Validation\ValidationException; + +/** + * Validates "style" attribute. + */ +class StyleAttributeValidator implements AttributeValidatorInterface +{ + /** + * @inheritDoc + */ + public function validate(string $tag, string $attributeName, string $value): void + { + if ($attributeName !== 'style' || !$value) { + return; + } + + if (preg_match('/([^\-]position\s*?\:\s*?[^i\s][^n\s]\w)|(opacity)|(z-index)/ims', " $value")) { + throw new ValidationException(__('HTML attribute "style" contains restricted styles')); + } + } +} From 12f13238e729bc7ff374f12307cade7e8c5c8028 Mon Sep 17 00:00:00 2001 From: ogorkun <ogorkun@adobe.com> Date: Wed, 22 Jul 2020 16:49:37 -0500 Subject: [PATCH 018/139] MC-34385: Filter fields allowing HTML --- app/code/Magento/Cms/etc/di.xml | 47 +++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/app/code/Magento/Cms/etc/di.xml b/app/code/Magento/Cms/etc/di.xml index d79e805e25890..74ba239bca587 100644 --- a/app/code/Magento/Cms/etc/di.xml +++ b/app/code/Magento/Cms/etc/di.xml @@ -258,4 +258,51 @@ </argument> </arguments> </type> + <virtualType name="DefaultWYSIWYGValidator" type="Magento\Framework\Validator\HTML\ConfigurableWYSIWYGValidator"> + <arguments> + <argument name="allowedTags" xsi:type="array"> + <item name="div" xsi:type="string">div</item> + <item name="a" xsi:type="string">a</item> + <item name="p" xsi:type="string">p</item> + <item name="span" xsi:type="string">span</item> + <item name="em" xsi:type="string">em</item> + <item name="strong" xsi:type="string">strong</item> + <item name="ul" xsi:type="string">ul</item> + <item name="li" xsi:type="string">li</item> + <item name="ol" xsi:type="string">ol</item> + <item name="h5" xsi:type="string">h5</item> + <item name="h4" xsi:type="string">h4</item> + <item name="h3" xsi:type="string">h3</item> + <item name="h2" xsi:type="string">h2</item> + <item name="h1" xsi:type="string">h1</item> + <item name="table" xsi:type="string">table</item> + <item name="tbody" xsi:type="string">tbody</item> + <item name="tr" xsi:type="string">tr</item> + <item name="td" xsi:type="string">td</item> + <item name="th" xsi:type="string">th</item> + <item name="tfoot" xsi:type="string">tfoot</item> + <item name="img" xsi:type="string">img</item> + </argument> + <argument name="allowedAttributes" xsi:type="array"> + <item name="class" xsi:type="string">class</item> + <item name="width" xsi:type="string">width</item> + <item name="height" xsi:type="string">height</item> + <item name="style" xsi:type="string">style</item> + <item name="alt" xsi:type="string">alt</item> + <item name="title" xsi:type="string">title</item> + <item name="border" xsi:type="string">border</item> + </argument> + <argument name="attributesAllowedByTags" xsi:type="array"> + <item name="a" xsi:type="array"> + <item name="href" xsi:type="string">href</item> + </item> + <item name="img" xsi:type="array"> + <item name="src" xsi:type="string">src</item> + </item> + </argument> + <argument name="attributeValidators" xsi:type="array"> + <item name="style" xsi:type="object">Magento\Framework\Validator\HTML\StyleAttributeValidator</item> + </argument> + </arguments> + </virtualType> </config> From 62ccdbe6226d7d3c7a30ac4b909f89f258470203 Mon Sep 17 00:00:00 2001 From: ogorkun <ogorkun@adobe.com> Date: Thu, 23 Jul 2020 16:17:26 -0500 Subject: [PATCH 019/139] MC-34385: Filter fields allowing HTML --- app/code/Magento/Cms/etc/di.xml | 2 + app/etc/di.xml | 4 +- .../HTML/ConfigurableWYSIWYGValidatorTest.php | 97 ++++++++++++++++--- .../HTML/ConfigurableWYSIWYGValidator.php | 83 +++++++++++++--- .../Validator/HTML/TagValidatorInterface.php | 34 +++++++ 5 files changed, 188 insertions(+), 32 deletions(-) create mode 100644 lib/internal/Magento/Framework/Validator/HTML/TagValidatorInterface.php diff --git a/app/code/Magento/Cms/etc/di.xml b/app/code/Magento/Cms/etc/di.xml index 74ba239bca587..18d45980c6328 100644 --- a/app/code/Magento/Cms/etc/di.xml +++ b/app/code/Magento/Cms/etc/di.xml @@ -282,6 +282,8 @@ <item name="th" xsi:type="string">th</item> <item name="tfoot" xsi:type="string">tfoot</item> <item name="img" xsi:type="string">img</item> + <item name="hr" xsi:type="string">hr</item> + <item name="figure" xsi:type="string">figure</item> </argument> <argument name="allowedAttributes" xsi:type="array"> <item name="class" xsi:type="string">class</item> diff --git a/app/etc/di.xml b/app/etc/di.xml index fa1887cbe1372..16fb4b65700cb 100644 --- a/app/etc/di.xml +++ b/app/etc/di.xml @@ -1875,7 +1875,9 @@ </item> </argument> <argument name="attributeValidators" xsi:type="array"> - <item name="style" xsi:type="object">Magento\Framework\Validator\HTML\StyleAttributeValidator</item> + <item name="style" xsi:type="array"> + <item name="style" xsi:type="object">Magento\Framework\Validator\HTML\StyleAttributeValidator</item> + </item> </argument> </arguments> </virtualType> diff --git a/lib/internal/Magento/Framework/Test/Unit/Validator/HTML/ConfigurableWYSIWYGValidatorTest.php b/lib/internal/Magento/Framework/Test/Unit/Validator/HTML/ConfigurableWYSIWYGValidatorTest.php index 43ff2ae1377b0..029098a4252c6 100644 --- a/lib/internal/Magento/Framework/Test/Unit/Validator/HTML/ConfigurableWYSIWYGValidatorTest.php +++ b/lib/internal/Magento/Framework/Test/Unit/Validator/HTML/ConfigurableWYSIWYGValidatorTest.php @@ -11,6 +11,7 @@ use Magento\Framework\Validation\ValidationException; use Magento\Framework\Validator\HTML\ConfigurableWYSIWYGValidator; use Magento\Framework\Validator\HTML\AttributeValidatorInterface; +use Magento\Framework\Validator\HTML\TagValidatorInterface; use PHPUnit\Framework\TestCase; class ConfigurableWYSIWYGValidatorTest extends TestCase @@ -23,25 +24,43 @@ class ConfigurableWYSIWYGValidatorTest extends TestCase public function getConfigurations(): array { return [ - 'no-html' => [['div'], [], [], 'just text', true, []], - 'allowed-tag' => [['div'], [], [], 'just text and <div>a div</div>', true, []], + 'no-html' => [['div'], [], [], 'just text', true, [], []], + 'allowed-tag' => [['div'], [], [], 'just text and <div>a div</div>', true, [], []], 'restricted-tag' => [ ['div', 'p'], [], [], 'text and <p>a p</p>, <div>a div</div>, <tr>a tr</tr>', false, + [], + [] + ], + 'restricted-tag-wtih-attr' => [ + ['div'], + [], + [], + 'just text and <p class="fake-class">a p</p>', + false, + [], + [] + ], + 'allowed-tag-with-attr' => [ + ['div'], + [], + [], + 'just text and <div class="fake-class">a div</div>', + false, + [], [] ], - 'restricted-tag-wtih-attr' => [['div'], [], [], 'just text and <p class="fake-class">a p</p>', false, []], - 'allowed-tag-with-attr' => [['div'], [], [], 'just text and <div class="fake-class">a div</div>', false, []], - 'multiple-tags' => [['div', 'p'], [], [], 'just text and <div>a div</div> and <p>a p</p>', true, []], + 'multiple-tags' => [['div', 'p'], [], [], 'just text and <div>a div</div> and <p>a p</p>', true, [], []], 'tags-with-attrs' => [ ['div', 'p'], ['class', 'style'], [], 'text and <div class="fake-class">a div</div> and <p style="color: blue">a p</p>', true, + [], [] ], 'tags-with-restricted-attrs' => [ @@ -50,6 +69,7 @@ public function getConfigurations(): array [], 'text and <div class="fake-class">a div</div> and <p style="color: blue">a p</p>', false, + [], [] ], 'tags-with-specific-attrs' => [ @@ -59,6 +79,7 @@ public function getConfigurations(): array '<div class="fake-class" style="color: blue">a div</div>, <a href="/some-path" class="a">an a</a>' .', <p class="p-class">a p</p>', true, + [], [] ], 'tags-with-specific-restricted-attrs' => [ @@ -67,6 +88,7 @@ public function getConfigurations(): array ['a' => ['href']], 'text and <div class="fake-class" href="what">a div</div> and <a href="/some-path" class="a">an a</a>', false, + [], [] ], 'invalid-tag-with-full-config' => [ @@ -76,6 +98,7 @@ public function getConfigurations(): array '<div class="fake-class" style="color: blue">a div</div>, <a href="/some-path" class="a">an a</a>' .', <p class="p-class">a p</p>, <img src="test.jpg" />', false, + [], [] ], 'invalid-html' => [ @@ -84,6 +107,7 @@ public function getConfigurations(): array ['a' => ['href'], 'div' => ['style']], 'some </,none-> </html>', true, + [], [] ], 'invalid-html-with-violations' => [ @@ -92,6 +116,7 @@ public function getConfigurations(): array ['a' => ['href'], 'div' => ['style']], 'some </,none-> </html> <tr>some trs</tr>', false, + [], [] ], 'invalid-html-attributes' => [ @@ -100,7 +125,8 @@ public function getConfigurations(): array [], 'some <div class="value">DIV</div>', false, - ['class' => false] + ['class' => false], + [] ], 'ignored-html-attributes' => [ ['div', 'a', 'p'], @@ -108,7 +134,8 @@ public function getConfigurations(): array [], 'some <div class="value">DIV</div>', true, - ['src' => false, 'class' => true] + ['src' => false, 'class' => true], + [] ], 'valid-html-attributes' => [ ['div', 'a', 'p'], @@ -116,7 +143,26 @@ public function getConfigurations(): array [], 'some <div class="value">DIV</div>', true, - ['src' => true, 'class' => true] + ['src' => true, 'class' => true], + [] + ], + 'invalid-allowed-tag' => [ + ['div'], + ['class', 'src'], + [], + '<div class="some-class" src="some-src">IS A DIV</div>', + false, + [], + ['div' => ['class' => false]] + ], + 'valid-allowed-tag' => [ + ['div'], + ['class', 'src'], + [], + '<div class="some-class">IS A DIV</div>', + true, + [], + ['div' => ['src' => false]] ] ]; } @@ -124,12 +170,13 @@ public function getConfigurations(): array /** * Test different configurations and content. * - * @param array $allowedTags - * @param array $allowedAttr - * @param array $allowedTagAttrs + * @param string[] $allowedTags + * @param string[] $allowedAttr + * @param string[][] $allowedTagAttrs * @param string $html * @param bool $isValid - * @param array $attributeValidityMap + * @param bool[] $attributeValidityMap + * @param bool[][] $tagValidators * @return void * @dataProvider getConfigurations */ @@ -139,7 +186,8 @@ public function testConfigurations( array $allowedTagAttrs, string $html, bool $isValid, - array $attributeValidityMap + array $attributeValidityMap, + array $tagValidators ): void { $attributeValidator = $this->getMockForAbstractClass(AttributeValidatorInterface::class); $attributeValidator->method('validate') @@ -152,13 +200,32 @@ function (string $tag, string $attribute, string $content) use ($attributeValidi ); $attrValidators = []; foreach (array_keys($attributeValidityMap) as $attr) { - $attrValidators[$attr] = $attributeValidator; + $attrValidators[$attr] = [$attributeValidator]; + } + $tagValidatorsMocks = []; + foreach ($tagValidators as $tag => $allowedAttributes) { + $mock = $this->getMockForAbstractClass(TagValidatorInterface::class); + $mock->method('validate') + ->willReturnCallback( + function (string $givenTag, array $attrs, string $value) use($tag, $allowedAttributes): void { + if ($givenTag !== $tag) { + throw new \RuntimeException(); + } + foreach (array_keys($attrs) as $attr) { + if (array_key_exists($attr, $allowedAttributes) && !$allowedAttributes[$attr]) { + throw new ValidationException(__('Invalid tag')); + } + } + } + ); + $tagValidatorsMocks[$tag] = [$mock]; } $validator = new ConfigurableWYSIWYGValidator( $allowedTags, $allowedAttr, $allowedTagAttrs, - $attrValidators + $attrValidators, + $tagValidatorsMocks ); $valid = true; try { diff --git a/lib/internal/Magento/Framework/Validator/HTML/ConfigurableWYSIWYGValidator.php b/lib/internal/Magento/Framework/Validator/HTML/ConfigurableWYSIWYGValidator.php index 5b9a73a5f2570..caa32be4abc55 100644 --- a/lib/internal/Magento/Framework/Validator/HTML/ConfigurableWYSIWYGValidator.php +++ b/lib/internal/Magento/Framework/Validator/HTML/ConfigurableWYSIWYGValidator.php @@ -31,21 +31,28 @@ class ConfigurableWYSIWYGValidator implements WYSIWYGValidatorInterface private $attributesAllowedByTags; /** - * @var AttributeValidatorInterface[] + * @var AttributeValidatorInterface[][] */ private $attributeValidators; + /** + * @var TagValidatorInterface[][] + */ + private $tagValidators; + /** * @param string[] $allowedTags * @param string[] $allowedAttributes - * @param string[] $attributesAllowedByTags - * @param AttributeValidatorInterface[] $attributeValidators + * @param string[][] $attributesAllowedByTags + * @param AttributeValidatorInterface[][] $attributeValidators + * @param TagValidatorInterface[][] $tagValidators */ public function __construct( array $allowedTags, array $allowedAttributes = [], array $attributesAllowedByTags = [], - array $attributeValidators = [] + array $attributeValidators = [], + array $tagValidators = [] ) { if (empty(array_filter($allowedTags))) { throw new \InvalidArgumentException('List of allowed HTML tags cannot be empty'); @@ -60,6 +67,7 @@ function (string $tag) use ($allowedTags): bool { ARRAY_FILTER_USE_KEY ); $this->attributeValidators = $attributeValidators; + $this->tagValidators = $tagValidators; } /** @@ -73,19 +81,32 @@ public function validate(string $content): void $dom = $this->loadHtml($content); $xpath = new \DOMXPath($dom); + $this->validateConfigured($xpath); + $this->callDynamicValidators($xpath); + } + + /** + * Check declarative restrictions + * + * @param \DOMXPath $xpath + * @return void + * @throws ValidationException + */ + private function validateConfigured(\DOMXPath $xpath): void + { //Validating tags $found = $xpath->query( $query='//*[' - . implode( - ' and ', - array_map( - function (string $tag): string { - return "name() != '$tag'"; - }, - array_merge($this->allowedTags, ['body', 'html']) + . implode( + ' and ', + array_map( + function (string $tag): string { + return "name() != '$tag'"; + }, + array_merge($this->allowedTags, ['body', 'html']) + ) ) - ) - .']' + .']' ); if (count($found)) { throw new ValidationException( @@ -143,17 +164,48 @@ function (string $attribute): string { ); } } + } + /** + * Cycle dynamic validators. + * + * @param \DOMXPath $xpath + * @return void + * @throws ValidationException + */ + private function callDynamicValidators(\DOMXPath $xpath): void + { //Validating allowed attributes. if ($this->attributeValidators) { - foreach ($this->attributeValidators as $attr => $validator) { + foreach ($this->attributeValidators as $attr => $validators) { $found = $xpath->query("//@*[name() = '$attr']"); foreach ($found as $attribute) { - $validator->validate($attribute->parentNode->tagName, $attribute->name, $attribute->value); + foreach ($validators as $validator) { + $validator->validate($attribute->parentNode->tagName, $attribute->name, $attribute->value); + } } } } + //Validating allowed tags + if ($this->tagValidators) { + foreach ($this->tagValidators as $tag => $validators) { + $found = $xpath->query("//*[name() = '$tag']"); + /** @var \DOMElement $tagNode */ + foreach ($found as $tagNode) { + $attributes = []; + if ($tagNode->hasAttributes()) { + /** @var \DOMAttr $attributeNode */ + foreach ($tagNode->attributes as $attributeNode) { + $attributes[$attributeNode->name] = $attributeNode->value; + } + } + foreach ($validators as $validator) { + $validator->validate($tagNode->tagName, $attributes, $tagNode->textContent, $this); + } + } + } + } } /** @@ -166,7 +218,6 @@ function (string $attribute): string { private function loadHtml(string $content): \DOMDocument { $dom = new \DOMDocument('1.0', 'UTF-8'); - $loaded = true; set_error_handler( function () use (&$loaded) { $loaded = false; diff --git a/lib/internal/Magento/Framework/Validator/HTML/TagValidatorInterface.php b/lib/internal/Magento/Framework/Validator/HTML/TagValidatorInterface.php new file mode 100644 index 0000000000000..d81172edc87c9 --- /dev/null +++ b/lib/internal/Magento/Framework/Validator/HTML/TagValidatorInterface.php @@ -0,0 +1,34 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\Framework\Validator\HTML; + +use Magento\Framework\Validation\ValidationException; + +/** + * Validates tag for user HTML content. + */ +interface TagValidatorInterface +{ + /** + * Validate a tag. + * + * @param string $tag + * @param string[] $attributes + * @param string $value + * @param WYSIWYGValidatorInterface $recursiveValidator + * @return void + * @throws ValidationException + */ + public function validate( + string $tag, + array $attributes, + string $value, + WYSIWYGValidatorInterface $recursiveValidator + ): void; +} From ff9026000f545497efeb87033e9adbea426fb3e0 Mon Sep 17 00:00:00 2001 From: ogorkun <ogorkun@adobe.com> Date: Mon, 27 Jul 2020 15:07:55 -0500 Subject: [PATCH 020/139] MC-34385: Filter fields allowing HTML --- .../Cms/Command/WysiwygRestrictCommand.php | 2 ++ .../Magento/Cms/Model/BlockRepository.php | 6 ++-- app/code/Magento/Cms/Model/Page.php | 5 +++- .../HTML/ConfigurableWYSIWYGValidatorTest.php | 8 ++++-- .../HTML/ConfigurableWYSIWYGValidator.php | 28 +++++++++++++------ 5 files changed, 35 insertions(+), 14 deletions(-) diff --git a/app/code/Magento/Cms/Command/WysiwygRestrictCommand.php b/app/code/Magento/Cms/Command/WysiwygRestrictCommand.php index bafe98ad377f5..e676cb1fe0ee5 100644 --- a/app/code/Magento/Cms/Command/WysiwygRestrictCommand.php +++ b/app/code/Magento/Cms/Command/WysiwygRestrictCommand.php @@ -66,5 +66,7 @@ protected function execute(InputInterface $input, OutputInterface $output) $this->cache->cleanType('config'); $output->writeln('HTML user content validation is now ' .($restrictArg === 'y' ? 'enforced' : 'suggested')); + + return 0; } } diff --git a/app/code/Magento/Cms/Model/BlockRepository.php b/app/code/Magento/Cms/Model/BlockRepository.php index f8129ca4a2961..5d5a0b9f6bed9 100644 --- a/app/code/Magento/Cms/Model/BlockRepository.php +++ b/app/code/Magento/Cms/Model/BlockRepository.php @@ -21,7 +21,7 @@ use Magento\Framework\EntityManager\HydratorInterface; /** - * Class BlockRepository + * Default block repo impl. * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class BlockRepository implements BlockRepositoryInterface @@ -87,6 +87,8 @@ class BlockRepository implements BlockRepositoryInterface * @param StoreManagerInterface $storeManager * @param CollectionProcessorInterface $collectionProcessor * @param HydratorInterface|null $hydrator + * + * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ public function __construct( ResourceBlock $resource, @@ -217,7 +219,7 @@ private function getCollectionProcessor() { if (!$this->collectionProcessor) { $this->collectionProcessor = \Magento\Framework\App\ObjectManager::getInstance()->get( - 'Magento\Cms\Model\Api\SearchCriteria\BlockCollectionProcessor' + \Magento\Cms\Model\Api\SearchCriteria\BlockCollectionProcessor::class ); } return $this->collectionProcessor; diff --git a/app/code/Magento/Cms/Model/Page.php b/app/code/Magento/Cms/Model/Page.php index 35e049caea203..7e3e3ff44cfa0 100644 --- a/app/code/Magento/Cms/Model/Page.php +++ b/app/code/Magento/Cms/Model/Page.php @@ -23,12 +23,13 @@ * @method Page setStoreId(int $storeId) * @method int getStoreId() * @SuppressWarnings(PHPMD.ExcessivePublicCount) + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) * @since 100.0.2 */ class Page extends AbstractModel implements PageInterface, IdentityInterface { /** - * No route page id + * Page ID for the 404 page. */ const NOROUTE_PAGE_ID = 'no-route'; @@ -605,6 +606,8 @@ private function validateNewIdentifier(): void /** * @inheritdoc * @since 101.0.0 + * @SuppressWarnings(PHPMD.CyclomaticComplexity) + * @SuppressWarnings(PHPMD.NPathComplexity) */ public function beforeSave() { diff --git a/lib/internal/Magento/Framework/Test/Unit/Validator/HTML/ConfigurableWYSIWYGValidatorTest.php b/lib/internal/Magento/Framework/Test/Unit/Validator/HTML/ConfigurableWYSIWYGValidatorTest.php index 029098a4252c6..3c703c9f037d7 100644 --- a/lib/internal/Magento/Framework/Test/Unit/Validator/HTML/ConfigurableWYSIWYGValidatorTest.php +++ b/lib/internal/Magento/Framework/Test/Unit/Validator/HTML/ConfigurableWYSIWYGValidatorTest.php @@ -20,6 +20,8 @@ class ConfigurableWYSIWYGValidatorTest extends TestCase * Configurations to test. * * @return array + * + * @SuppressWarnings(PHPMD.ExcessiveMethodLength) */ public function getConfigurations(): array { @@ -178,7 +180,9 @@ public function getConfigurations(): array * @param bool[] $attributeValidityMap * @param bool[][] $tagValidators * @return void + * * @dataProvider getConfigurations + * @SuppressWarnings(PHPMD.CyclomaticComplexity) */ public function testConfigurations( array $allowedTags, @@ -192,7 +196,7 @@ public function testConfigurations( $attributeValidator = $this->getMockForAbstractClass(AttributeValidatorInterface::class); $attributeValidator->method('validate') ->willReturnCallback( - function (string $tag, string $attribute, string $content) use ($attributeValidityMap): void { + function (string $tag, string $attribute) use ($attributeValidityMap): void { if (array_key_exists($attribute, $attributeValidityMap) && !$attributeValidityMap[$attribute]) { throw new ValidationException(__('Invalid attribute for %1', $tag)); } @@ -207,7 +211,7 @@ function (string $tag, string $attribute, string $content) use ($attributeValidi $mock = $this->getMockForAbstractClass(TagValidatorInterface::class); $mock->method('validate') ->willReturnCallback( - function (string $givenTag, array $attrs, string $value) use($tag, $allowedAttributes): void { + function (string $givenTag, array $attrs) use ($tag, $allowedAttributes): void { if ($givenTag !== $tag) { throw new \RuntimeException(); } diff --git a/lib/internal/Magento/Framework/Validator/HTML/ConfigurableWYSIWYGValidator.php b/lib/internal/Magento/Framework/Validator/HTML/ConfigurableWYSIWYGValidator.php index caa32be4abc55..f436fddf26e8d 100644 --- a/lib/internal/Magento/Framework/Validator/HTML/ConfigurableWYSIWYGValidator.php +++ b/lib/internal/Magento/Framework/Validator/HTML/ConfigurableWYSIWYGValidator.php @@ -82,7 +82,8 @@ public function validate(string $content): void $xpath = new \DOMXPath($dom); $this->validateConfigured($xpath); - $this->callDynamicValidators($xpath); + $this->callAttributeValidators($xpath); + $this->callTagValidators($xpath); } /** @@ -96,7 +97,7 @@ private function validateConfigured(\DOMXPath $xpath): void { //Validating tags $found = $xpath->query( - $query='//*[' + '//*[' . implode( ' and ', array_map( @@ -117,10 +118,11 @@ function (string $tag): string { //Validating attributes if ($this->attributesAllowedByTags) { foreach ($this->allowedTags as $tag) { - $allowed = $this->allowedAttributes; + $allowed = [$this->allowedAttributes]; if (!empty($this->attributesAllowedByTags[$tag])) { - $allowed = array_unique(array_merge($allowed, $this->attributesAllowedByTags[$tag])); + $allowed[] = $this->attributesAllowedByTags[$tag]; } + $allowed = array_unique(array_merge(...$allowed)); $allowedQuery = ''; if ($allowed) { $allowedQuery = '[' @@ -167,15 +169,14 @@ function (string $attribute): string { } /** - * Cycle dynamic validators. + * Validate allowed HTML attributes' content. * * @param \DOMXPath $xpath - * @return void * @throws ValidationException + * @return void */ - private function callDynamicValidators(\DOMXPath $xpath): void + private function callAttributeValidators(\DOMXPath $xpath): void { - //Validating allowed attributes. if ($this->attributeValidators) { foreach ($this->attributeValidators as $attr => $validators) { $found = $xpath->query("//@*[name() = '$attr']"); @@ -186,8 +187,17 @@ private function callDynamicValidators(\DOMXPath $xpath): void } } } + } - //Validating allowed tags + /** + * Validate allowed tags. + * + * @param \DOMXPath $xpath + * @return void + * @throws ValidationException + */ + private function callTagValidators(\DOMXPath $xpath): void + { if ($this->tagValidators) { foreach ($this->tagValidators as $tag => $validators) { $found = $xpath->query("//*[name() = '$tag']"); From 49f7dae9013c1be18f61433973459f51567e59c4 Mon Sep 17 00:00:00 2001 From: ogorkun <ogorkun@adobe.com> Date: Mon, 27 Jul 2020 15:36:03 -0500 Subject: [PATCH 021/139] MC-34385: Filter fields allowing HTML --- .../Catalog/Model/Attribute/Backend/DefaultBackend.php | 4 +++- app/code/Magento/Cms/etc/di.xml | 1 + app/etc/di.xml | 7 ++++--- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/app/code/Magento/Catalog/Model/Attribute/Backend/DefaultBackend.php b/app/code/Magento/Catalog/Model/Attribute/Backend/DefaultBackend.php index e3b38bf7a578a..a02d589fae055 100644 --- a/app/code/Magento/Catalog/Model/Attribute/Backend/DefaultBackend.php +++ b/app/code/Magento/Catalog/Model/Attribute/Backend/DefaultBackend.php @@ -47,7 +47,9 @@ private function validateHtml(DataObject $object): void $attribute = $this->getAttribute(); $code = $attribute->getAttributeCode(); if ($attribute instanceof Attribute && $attribute->getIsHtmlAllowedOnFront()) { - if ($object->getData($code) + $value = $object->getData($code); + if ($value + && is_string($value) && (!($object instanceof AbstractModel) || $object->getData($code) !== $object->getOrigData($code)) ) { try { diff --git a/app/code/Magento/Cms/etc/di.xml b/app/code/Magento/Cms/etc/di.xml index 18d45980c6328..c18aadd3f6a80 100644 --- a/app/code/Magento/Cms/etc/di.xml +++ b/app/code/Magento/Cms/etc/di.xml @@ -293,6 +293,7 @@ <item name="alt" xsi:type="string">alt</item> <item name="title" xsi:type="string">title</item> <item name="border" xsi:type="string">border</item> + <item name="id" xsi:type="string">id</item> </argument> <argument name="attributesAllowedByTags" xsi:type="array"> <item name="a" xsi:type="array"> diff --git a/app/etc/di.xml b/app/etc/di.xml index 887ed6d96d7ad..f3dac922b5a2d 100644 --- a/app/etc/di.xml +++ b/app/etc/di.xml @@ -1856,6 +1856,8 @@ <item name="th" xsi:type="string">th</item> <item name="tfoot" xsi:type="string">tfoot</item> <item name="img" xsi:type="string">img</item> + <item name="hr" xsi:type="string">hr</item> + <item name="figure" xsi:type="string">figure</item> </argument> <argument name="allowedAttributes" xsi:type="array"> <item name="class" xsi:type="string">class</item> @@ -1865,6 +1867,7 @@ <item name="alt" xsi:type="string">alt</item> <item name="title" xsi:type="string">title</item> <item name="border" xsi:type="string">border</item> + <item name="id" xsi:type="string">id</item> </argument> <argument name="attributesAllowedByTags" xsi:type="array"> <item name="a" xsi:type="array"> @@ -1875,9 +1878,7 @@ </item> </argument> <argument name="attributeValidators" xsi:type="array"> - <item name="style" xsi:type="array"> - <item name="style" xsi:type="object">Magento\Framework\Validator\HTML\StyleAttributeValidator</item> - </item> + <item name="style" xsi:type="object">Magento\Framework\Validator\HTML\StyleAttributeValidator</item> </argument> </arguments> </virtualType> From efc25d74e4c8f34f11115add6c0eb5f0f60bbc1c Mon Sep 17 00:00:00 2001 From: ogorkun <ogorkun@adobe.com> Date: Tue, 28 Jul 2020 10:48:00 -0500 Subject: [PATCH 022/139] MC-34385: Filter fields allowing HTML --- app/code/Magento/Cms/Model/BlockRepository.php | 2 +- .../Framework/Validator/HTML/ConfigurableWYSIWYGValidator.php | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/app/code/Magento/Cms/Model/BlockRepository.php b/app/code/Magento/Cms/Model/BlockRepository.php index 7502b017584df..c26e2d809d996 100644 --- a/app/code/Magento/Cms/Model/BlockRepository.php +++ b/app/code/Magento/Cms/Model/BlockRepository.php @@ -219,7 +219,7 @@ private function getCollectionProcessor() { if (!$this->collectionProcessor) { $this->collectionProcessor = \Magento\Framework\App\ObjectManager::getInstance()->get( - \Magento\Cms\Model\Api\SearchCriteria\BlockCollectionProcessor::class + 'Magento\Cms\Model\Api\SearchCriteria\BlockCollectionProcessor' ); } return $this->collectionProcessor; diff --git a/lib/internal/Magento/Framework/Validator/HTML/ConfigurableWYSIWYGValidator.php b/lib/internal/Magento/Framework/Validator/HTML/ConfigurableWYSIWYGValidator.php index f436fddf26e8d..bfa6bc37600bf 100644 --- a/lib/internal/Magento/Framework/Validator/HTML/ConfigurableWYSIWYGValidator.php +++ b/lib/internal/Magento/Framework/Validator/HTML/ConfigurableWYSIWYGValidator.php @@ -122,6 +122,7 @@ function (string $tag): string { if (!empty($this->attributesAllowedByTags[$tag])) { $allowed[] = $this->attributesAllowedByTags[$tag]; } + // phpcs:ignore Magento2.Performance.ForeachArrayMerge $allowed = array_unique(array_merge(...$allowed)); $allowedQuery = ''; if ($allowed) { From 536a728213fd4a36634dd958bbde243fe9154734 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Szubert?= <bartlomiejszubert@gmail.com> Date: Fri, 7 Aug 2020 18:56:25 +0200 Subject: [PATCH 023/139] Fix #24091 - reduce amount of js code needed to set options for configurable product on wishlist --- .../view/frontend/web/js/add-to-wishlist.js | 66 ++++++------------- 1 file changed, 21 insertions(+), 45 deletions(-) diff --git a/app/code/Magento/Wishlist/view/frontend/web/js/add-to-wishlist.js b/app/code/Magento/Wishlist/view/frontend/web/js/add-to-wishlist.js index 1cdad4953b3c2..727a9751cc2f6 100644 --- a/app/code/Magento/Wishlist/view/frontend/web/js/add-to-wishlist.js +++ b/app/code/Magento/Wishlist/view/frontend/web/js/add-to-wishlist.js @@ -19,7 +19,6 @@ define([ qtyInfo: '#qty', actionElement: '[data-action="add-to-wishlist"]', productListItem: '.item.product-item', - productListPriceBox: '.price-box', isProductList: false }, @@ -68,16 +67,25 @@ define([ _updateWishlistData: function (event) { var dataToAdd = {}, isFileUploaded = false, - productId = null, + productItem = null, + handleObjSelector = null, self = this; if (event.handleObj.selector == this.options.qtyInfo) { //eslint-disable-line eqeqeq - this._updateAddToWishlistButton({}); + this._updateAddToWishlistButton({}, productItem); event.stopPropagation(); return; } - $(event.handleObj.selector).each(function (index, element) { + + if (this.options.isProductList) { + productItem = $(event.target).closest(this.options.productListItem); + handleObjSelector = productItem.find(event.handleObj.selector); + } else { + handleObjSelector = $(event.handleObj.selector); + } + + handleObjSelector.each(function (index, element) { if ($(element).is('input[type=text]') || $(element).is('input[type=email]') || $(element).is('input[type=number]') || @@ -87,19 +95,7 @@ define([ $(element).is('textarea') || $('#' + element.id + ' option:selected').length ) { - if (!($(element).data('selector') || $(element).attr('name'))) { - return; - } - - if (self.options.isProductList) { - productId = self.retrieveListProductId(this); - - dataToAdd[productId] = $.extend( - {}, - dataToAdd[productId] ? dataToAdd[productId] : {}, - self._getElementData(element) - ); - } else { + if ($(element).data('selector') || $(element).attr('name')) { dataToAdd = $.extend({}, dataToAdd, self._getElementData(element)); } @@ -114,26 +110,21 @@ define([ if (isFileUploaded) { this.bindFormSubmit(); } - this._updateAddToWishlistButton(dataToAdd); + this._updateAddToWishlistButton(dataToAdd, productItem); event.stopPropagation(); }, /** * @param {Object} dataToAdd + * @param {Object} productItem * @private */ - _updateAddToWishlistButton: function (dataToAdd) { - var productId = null, - self = this; + _updateAddToWishlistButton: function (dataToAdd, productItem) { + var self = this, + buttons = productItem ? productItem.find(this.options.actionElement) : $(this.options.actionElement); - $('[data-action="add-to-wishlist"]').each(function (index, element) { - var params = $(element).data('post'), - dataToAddObj = dataToAdd; - - if (self.options.isProductList) { - productId = self.retrieveListProductId(element); - dataToAddObj = typeof dataToAdd[productId] !== 'undefined' ? dataToAdd[productId] : {}; - } + buttons.each(function (index, element) { + var params = $(element).data('post'); if (!params) { params = { @@ -141,7 +132,7 @@ define([ }; } - params.data = $.extend({}, params.data, dataToAddObj, { + params.data = $.extend({}, params.data, dataToAdd, { 'qty': $(self.options.qtyInfo).val() }); $(element).data('post', params); @@ -264,21 +255,6 @@ define([ return; } - }, - - /** - * Retrieve product id from element on products list - * - * @param {jQuery.Object} element - * @private - */ - retrieveListProductId: function (element) { - return parseInt( - $(element).closest(this.options.productListItem) - .find(this.options.productListPriceBox) - .data('product-id'), - 10 - ); } }); From 3d69b132c603dbb0d04f3e8911b0b2a1416aed0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Szubert?= <bartlomiejszubert@gmail.com> Date: Tue, 11 Aug 2020 21:51:16 +0200 Subject: [PATCH 024/139] Update app/code/Magento/Wishlist/view/frontend/layout/catalogsearch_result_index.xml Co-authored-by: Gabriel da Gama <gabriel@gabrielgama.com.br> --- .../view/frontend/layout/catalogsearch_result_index.xml | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/app/code/Magento/Wishlist/view/frontend/layout/catalogsearch_result_index.xml b/app/code/Magento/Wishlist/view/frontend/layout/catalogsearch_result_index.xml index 1f597a9ce1e3a..b36a7cc2347af 100644 --- a/app/code/Magento/Wishlist/view/frontend/layout/catalogsearch_result_index.xml +++ b/app/code/Magento/Wishlist/view/frontend/layout/catalogsearch_result_index.xml @@ -15,11 +15,9 @@ </referenceBlock> </referenceContainer> <referenceBlock name="wishlist_page_head_components"> - <block - class="Magento\Wishlist\Block\AddToWishlist" + <block class="Magento\Wishlist\Block\AddToWishlist" name="catalogsearch.wishlist_addto" - template="Magento_Wishlist::addto.phtml" - > + template="Magento_Wishlist::addto.phtml"> <arguments> <argument name="is_product_list" xsi:type="boolean">true</argument> <argument name="product_list_block" xsi:type="string">search_result_list</argument> From 75a5f2523643e89aa9226d92551021924c75e44f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Szubert?= <bartlomiejszubert@gmail.com> Date: Tue, 11 Aug 2020 21:51:28 +0200 Subject: [PATCH 025/139] Update app/code/Magento/Wishlist/view/frontend/layout/catalog_category_view.xml Co-authored-by: Gabriel da Gama <gabriel@gabrielgama.com.br> --- .../Wishlist/view/frontend/layout/catalog_category_view.xml | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/app/code/Magento/Wishlist/view/frontend/layout/catalog_category_view.xml b/app/code/Magento/Wishlist/view/frontend/layout/catalog_category_view.xml index 8b784cfd31783..c305b7c489d59 100644 --- a/app/code/Magento/Wishlist/view/frontend/layout/catalog_category_view.xml +++ b/app/code/Magento/Wishlist/view/frontend/layout/catalog_category_view.xml @@ -21,11 +21,9 @@ template="Magento_Wishlist::catalog/product/list/addto/wishlist.phtml"/> </referenceBlock> <referenceContainer name="category.product.list.additional"> - <block - class="Magento\Wishlist\Block\AddToWishlist" + <block class="Magento\Wishlist\Block\AddToWishlist" name="category.product.list.additional.wishlist_addto" - template="Magento_Wishlist::addto.phtml" - > + template="Magento_Wishlist::addto.phtml"> <arguments> <argument name="is_product_list" xsi:type="boolean">true</argument> </arguments> From 0471e4e9bd296d6b4872cb450b6e95795060e928 Mon Sep 17 00:00:00 2001 From: engcom-Echo <engcom-vendorworker-echo@adobe.com> Date: Fri, 14 Aug 2020 15:15:57 +0300 Subject: [PATCH 026/139] add MFTF test --- ...atchToProductWithOutCreatedActionGroup.xml | 43 +++++++++ ...lSwatchOptionOnCategoryPageActionGroup.xml | 19 ++++ ...orefrontCategoryPageProductInfoSection.xml | 15 +++ ...oductToWishlistCategoryPageActionGroup.xml | 24 +++++ ...tionsConfigurableProductInWishlistTest.xml | 91 +++++++++++++++++++ 5 files changed, 192 insertions(+) create mode 100644 app/code/Magento/Swatches/Test/Mftf/ActionGroup/AddVisualSwatchToProductWithOutCreatedActionGroup.xml create mode 100644 app/code/Magento/Swatches/Test/Mftf/ActionGroup/StorefrontSelectVisualSwatchOptionOnCategoryPageActionGroup.xml create mode 100644 app/code/Magento/Swatches/Test/Mftf/Section/StorefrontCategoryPageProductInfoSection.xml create mode 100644 app/code/Magento/Wishlist/Test/Mftf/ActionGroup/StorefrontCustomerAddProductToWishlistCategoryPageActionGroup.xml create mode 100644 app/code/Magento/Wishlist/Test/Mftf/Test/StorefrontCheckOptionsConfigurableProductInWishlistTest.xml diff --git a/app/code/Magento/Swatches/Test/Mftf/ActionGroup/AddVisualSwatchToProductWithOutCreatedActionGroup.xml b/app/code/Magento/Swatches/Test/Mftf/ActionGroup/AddVisualSwatchToProductWithOutCreatedActionGroup.xml new file mode 100644 index 0000000000000..604ef606e94e5 --- /dev/null +++ b/app/code/Magento/Swatches/Test/Mftf/ActionGroup/AddVisualSwatchToProductWithOutCreatedActionGroup.xml @@ -0,0 +1,43 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AddVisualSwatchToProductWithOutCreatedActionGroup"> + <annotations> + <description>Does not create an attribute. Adds the provided Visual Swatch Attribute and Options (2) to a Product on the Admin Product creation/edit page. Clicks on Save. Validates that the Success Message is present. </description> + </annotations> + <arguments> + <argument name="attribute" defaultValue="visualSwatchAttribute"/> + </arguments> + + <seeInCurrentUrl url="{{ProductCatalogPage.url}}" stepKey="seeOnProductEditPage"/> + <conditionalClick selector="{{AdminProductFormConfigurationsSection.sectionHeader}}" dependentSelector="{{AdminProductFormConfigurationsSection.createConfigurations}}" visible="false" stepKey="openConfigurationSection"/> + <click selector="{{AdminProductFormConfigurationsSection.createConfigurations}}" stepKey="openConfigurationPanel"/> + <waitForElementVisible selector="{{AdminCreateProductConfigurationsPanel.createNewAttribute}}" stepKey="waitForSlideOut"/> + + <!--Find attribute in grid and select--> + <conditionalClick selector="{{AdminDataGridHeaderSection.clearFilters}}" dependentSelector="{{AdminDataGridHeaderSection.clearFilters}}" visible="true" stepKey="clearExistingFilters"/> + <click selector="{{AdminDataGridHeaderSection.filters}}" stepKey="clickOnFilters"/> + <fillField selector="{{AdminDataGridHeaderSection.attributeCodeFilterInput}}" userInput="{{attribute.default_label}}" stepKey="fillFilterAttributeCodeField"/> + <click selector="{{AdminDataGridHeaderSection.applyFilters}}" stepKey="clickApplyFiltersButton"/> + <click selector="{{AdminDataGridTableSection.rowCheckbox('1')}}" stepKey="clickOnFirstCheckbox"/> + + <click selector="{{AdminCreateProductConfigurationsPanel.next}}" stepKey="clickNextStep1"/> + + <click selector="{{AdminCreateProductConfigurationsPanel.selectAllByAttribute(attribute.default_label)}}" stepKey="clickSelectAll"/> + <click selector="{{AdminCreateProductConfigurationsPanel.next}}" stepKey="clickNextStep2"/> + + <click selector="{{AdminCreateProductConfigurationsPanel.applySingleQuantityToEachSkus}}" stepKey="clickOnApplySingleQuantityToEachSku"/> + <fillField selector="{{AdminCreateProductConfigurationsPanel.quantity}}" userInput="100" stepKey="enterAttributeQuantity"/> + <click selector="{{AdminCreateProductConfigurationsPanel.next}}" stepKey="clickOnNextStep3"/> + <click selector="{{AdminCreateProductConfigurationsPanel.next}}" stepKey="generateProducts"/> + <click selector="{{AdminProductFormActionSection.saveButton}}" stepKey="saveProduct"/> + <seeElement selector="{{AdminMessagesSection.success}}" stepKey="seeSaveProductMessage"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Swatches/Test/Mftf/ActionGroup/StorefrontSelectVisualSwatchOptionOnCategoryPageActionGroup.xml b/app/code/Magento/Swatches/Test/Mftf/ActionGroup/StorefrontSelectVisualSwatchOptionOnCategoryPageActionGroup.xml new file mode 100644 index 0000000000000..5722210abf211 --- /dev/null +++ b/app/code/Magento/Swatches/Test/Mftf/ActionGroup/StorefrontSelectVisualSwatchOptionOnCategoryPageActionGroup.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <!--Click a swatch option on product page--> + <actionGroup name="StorefrontSelectVisualSwatchOptionOnCategoryPageActionGroup"> + <arguments> + <argument name="productId" type="string"/> + <argument name="visualSwatchOptionLabel" type="string" /> + </arguments> + <click selector="{{StorefrontCategoryPageProductInfoSection.visualSwatchOption(productId,visualSwatchOptionLabel)}}" stepKey="clickSwatchOption"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Swatches/Test/Mftf/Section/StorefrontCategoryPageProductInfoSection.xml b/app/code/Magento/Swatches/Test/Mftf/Section/StorefrontCategoryPageProductInfoSection.xml new file mode 100644 index 0000000000000..5f321c7f17603 --- /dev/null +++ b/app/code/Magento/Swatches/Test/Mftf/Section/StorefrontCategoryPageProductInfoSection.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> + <section name="StorefrontCategoryPageProductInfoSection"> + <element name="visualSwatchOption" type="button" selector="#product-item-info_{{var1}} .swatch-option[data-option-label='{{var2}}']" parameterized="true"/> + <element name="productAddToWishlist" type="button" selector="#product-item-info_{{var1}} .action.towishlist" parameterized="true"/> + </section> +</sections> diff --git a/app/code/Magento/Wishlist/Test/Mftf/ActionGroup/StorefrontCustomerAddProductToWishlistCategoryPageActionGroup.xml b/app/code/Magento/Wishlist/Test/Mftf/ActionGroup/StorefrontCustomerAddProductToWishlistCategoryPageActionGroup.xml new file mode 100644 index 0000000000000..baa4bfcab4ebc --- /dev/null +++ b/app/code/Magento/Wishlist/Test/Mftf/ActionGroup/StorefrontCustomerAddProductToWishlistCategoryPageActionGroup.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="StorefrontCustomerAddProductToWishlistCategoryPageActionGroup"> + <annotations> + <description>Adds the provided Product to the Wish List from the Storefront Category page. Validates that the Success Message is present and correct.</description> + </annotations> + <arguments> + <argument name="productVar"/> + </arguments> + + <click selector="{{StorefrontCategoryPageProductInfoSection.productAddToWishlist(productVar.id)}}" stepKey="addProductToWishlistClickAddToWishlist"/> + <waitForElement selector="{{StorefrontCustomerWishlistSection.successMsg}}" time="30" stepKey="addProductToWishlistWaitForSuccessMessage"/> + <see selector="{{StorefrontCustomerWishlistSection.successMsg}}" userInput="{{productVar.name}} has been added to your Wish List. Click here to continue shopping." stepKey="addProductToWishlistSeeProductNameAddedToWishlist"/> + <seeCurrentUrlMatches regex="~/wishlist_id/\d+/$~" stepKey="seeCurrentUrlMatches"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Wishlist/Test/Mftf/Test/StorefrontCheckOptionsConfigurableProductInWishlistTest.xml b/app/code/Magento/Wishlist/Test/Mftf/Test/StorefrontCheckOptionsConfigurableProductInWishlistTest.xml new file mode 100644 index 0000000000000..638c8f4986a77 --- /dev/null +++ b/app/code/Magento/Wishlist/Test/Mftf/Test/StorefrontCheckOptionsConfigurableProductInWishlistTest.xml @@ -0,0 +1,91 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="StorefrontCheckOptionsConfigurableProductInWishlistTest"> + <annotations> + <stories value="Wishlist"/> + <title value="Move first Configurable Product with selected optional from Category Page to Wishlist."/> + <description value="Move first Configurable Product with selected optional from Category Page to Wishlist. On Page will be present minimum two Configurable Product"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-14211"/> + <group value="wishlist"/> + </annotations> + <before> + <createData entity="ApiCategory" stepKey="createCategory"/> + <createData entity="ApiConfigurableProduct" stepKey="createFirstConfigProduct"> + <requiredEntity createDataKey="createCategory"/> + </createData> + <createData entity="ApiConfigurableProduct" stepKey="createSecondConfigProduct"> + <requiredEntity createDataKey="createCategory"/> + </createData> + <createData entity="Simple_US_Customer" stepKey="customer"/> + <actionGroup ref="CliIndexerReindexActionGroup" stepKey="reindex"> + <argument name="indices" value=""/> + </actionGroup> + <actionGroup ref="CliCacheFlushActionGroup" stepKey="flushCache"> + <argument name="tags" value=""/> + </actionGroup> + <actionGroup ref="AdminLoginActionGroup" stepKey="loginAsAdmin"/> + </before> + <after> + <deleteData createDataKey="customer" stepKey="deleteCustomer"/> + <actionGroup ref="DeleteProductBySkuActionGroup" stepKey="deleteFirstProducts"> + <argument name="sku" value="$$createFirstConfigProduct.sku$$"/> + </actionGroup> + <actionGroup ref="DeleteProductBySkuActionGroup" stepKey="deleteSecondProducts"> + <argument name="sku" value="$$createSecondConfigProduct.sku$$"/> + </actionGroup> + <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> + <actionGroup ref="AdminDeleteProductAttributeByLabelActionGroup" stepKey="deleteAttribute" > + <argument name="productAttributeLabel" value="{{visualSwatchAttribute.default_label}}"/> + </actionGroup> + <actionGroup ref="AdminLogoutActionGroup" stepKey="logout"/> + <actionGroup ref="CliIndexerReindexActionGroup" stepKey="reindex"> + <argument name="indices" value=""/> + </actionGroup> + <actionGroup ref="CliCacheFlushActionGroup" stepKey="flushCache"> + <argument name="tags" value=""/> + </actionGroup> + </after> + <actionGroup ref="AdminProductPageOpenByIdActionGroup" stepKey="navigateToFirstConfigProductPage"> + <argument name="productId" value="$$createFirstConfigProduct.id$$"/> + </actionGroup> + <waitForPageLoad stepKey="waitForFirstProductPageLoad"/> + <actionGroup ref="AddVisualSwatchToProductWithStorefrontConfigActionGroup" stepKey="addSwatchToFirstProduct"> + <argument name="attribute" value="visualSwatchAttribute"/> + <argument name="option1" value="visualSwatchOption1"/> + <argument name="option2" value="visualSwatchOption2"/> + </actionGroup> + + <actionGroup ref="AdminProductPageOpenByIdActionGroup" stepKey="navigateToSecondConfigProductPage"> + <argument name="productId" value="$$createSecondConfigProduct.id$$"/> + </actionGroup> + <waitForPageLoad stepKey="waitForSecondProductPageLoad"/> + <actionGroup ref="AddVisualSwatchToProductWithOutCreatedActionGroup" stepKey="addSwatchToSecondProduct"> + <argument name="attribute" value="visualSwatchAttribute"/> + </actionGroup> + + <actionGroup ref="LoginToStorefrontActionGroup" stepKey="loginToStorefrontAccount"> + <argument name="Customer" value="$$customer$$"/> + </actionGroup> + <actionGroup ref="StorefrontGoToCategoryPageActionGroup" stepKey="openCategoryPage"> + <argument name="categoryName" value="$$createCategory.name$$"/> + </actionGroup> + <actionGroup ref="StorefrontSelectVisualSwatchOptionOnCategoryPageActionGroup" stepKey="selectVisualSwatch"> + <argument name="productId" value="$$createFirstConfigProduct.id$$" /> + <argument name="visualSwatchOptionLabel" value="{{visualSwatchOption1.default_label}}" /> + </actionGroup> + <actionGroup ref="StorefrontCustomerAddProductToWishlistCategoryPageActionGroup" stepKey="addToWishlistProduct"> + <argument name="productVar" value="$$createFirstConfigProduct$$"/> + </actionGroup> + + <seeElement selector="{{StorefrontCustomerWishlistProductSection.productSeeDetailsByName($$createFirstConfigProduct.name$$)}}" stepKey="seeDetails"/> + </test> +</tests> From 59b4c536405d3617d585b6837839d0c7c1d5930e Mon Sep 17 00:00:00 2001 From: ogorkun <ogorkun@adobe.com> Date: Fri, 14 Aug 2020 07:32:09 -0500 Subject: [PATCH 027/139] MC-34385: Filter fields allowing HTML --- app/code/Magento/Cms/etc/di.xml | 4 ++++ app/etc/di.xml | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/app/code/Magento/Cms/etc/di.xml b/app/code/Magento/Cms/etc/di.xml index c18aadd3f6a80..0d0839365cc6a 100644 --- a/app/code/Magento/Cms/etc/di.xml +++ b/app/code/Magento/Cms/etc/di.xml @@ -284,6 +284,7 @@ <item name="img" xsi:type="string">img</item> <item name="hr" xsi:type="string">hr</item> <item name="figure" xsi:type="string">figure</item> + <item name="button" xsi:type="string">button</item> </argument> <argument name="allowedAttributes" xsi:type="array"> <item name="class" xsi:type="string">class</item> @@ -302,6 +303,9 @@ <item name="img" xsi:type="array"> <item name="src" xsi:type="string">src</item> </item> + <item name="button" xsi:type="array"> + <item name="type" xsi:type="string">type</item> + </item> </argument> <argument name="attributeValidators" xsi:type="array"> <item name="style" xsi:type="object">Magento\Framework\Validator\HTML\StyleAttributeValidator</item> diff --git a/app/etc/di.xml b/app/etc/di.xml index f3dac922b5a2d..1a33ac6597a3f 100644 --- a/app/etc/di.xml +++ b/app/etc/di.xml @@ -1858,6 +1858,7 @@ <item name="img" xsi:type="string">img</item> <item name="hr" xsi:type="string">hr</item> <item name="figure" xsi:type="string">figure</item> + <item name="button" xsi:type="string">button</item> </argument> <argument name="allowedAttributes" xsi:type="array"> <item name="class" xsi:type="string">class</item> @@ -1876,6 +1877,9 @@ <item name="img" xsi:type="array"> <item name="src" xsi:type="string">src</item> </item> + <item name="button" xsi:type="array"> + <item name="type" xsi:type="string">type</item> + </item> </argument> <argument name="attributeValidators" xsi:type="array"> <item name="style" xsi:type="object">Magento\Framework\Validator\HTML\StyleAttributeValidator</item> From 2eaee52aeeb1bdb61d653e2a73bb91f2f8e7d1d5 Mon Sep 17 00:00:00 2001 From: ogorkun <ogorkun@adobe.com> Date: Tue, 18 Aug 2020 11:17:44 -0500 Subject: [PATCH 028/139] MC-34385: Filter fields allowing HTML --- app/code/Magento/Cms/Model/PageRepository.php | 25 +++++++++++-------- app/code/Magento/Cms/etc/di.xml | 2 ++ 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/app/code/Magento/Cms/Model/PageRepository.php b/app/code/Magento/Cms/Model/PageRepository.php index f5c64b26c42ec..301c0efa740bd 100644 --- a/app/code/Magento/Cms/Model/PageRepository.php +++ b/app/code/Magento/Cms/Model/PageRepository.php @@ -17,6 +17,7 @@ use Magento\Framework\EntityManager\HydratorInterface; use Magento\Framework\Exception\CouldNotDeleteException; use Magento\Framework\Exception\CouldNotSaveException; +use Magento\Framework\Exception\LocalizedException; use Magento\Framework\Exception\NoSuchEntityException; use Magento\Framework\Reflection\DataObjectProcessor; use Magento\Store\Model\StoreManagerInterface; @@ -162,24 +163,28 @@ private function validateLayoutUpdate(Data\PageInterface $page): void */ public function save(\Magento\Cms\Api\Data\PageInterface $page) { - if ($page->getStoreId() === null) { - $storeId = $this->storeManager->getStore()->getId(); - $page->setStoreId($storeId); - } - $pageId = $page->getId(); - if ($pageId && !($page instanceof Page && $page->getOrigData())) { - $page = $this->hydrator->hydrate($this->getById($pageId), $this->hydrator->extract($page)); - } - try { + $pageId = $page->getId(); + if ($pageId && !($page instanceof Page && $page->getOrigData())) { + $page = $this->hydrator->hydrate($this->getById($pageId), $this->hydrator->extract($page)); + } + if ($page->getStoreId() === null) { + $storeId = $this->storeManager->getStore()->getId(); + $page->setStoreId($storeId); + } $this->validateLayoutUpdate($page); $this->resource->save($page); $this->identityMap->add($page); - } catch (\Exception $exception) { + } catch (LocalizedException $exception) { throw new CouldNotSaveException( __('Could not save the page: %1', $exception->getMessage()), $exception ); + } catch (\Throwable $exception) { + throw new CouldNotSaveException( + __('Could not save the page: %1', __('Something went wrong while saving the page.')), + $exception + ); } return $page; } diff --git a/app/code/Magento/Cms/etc/di.xml b/app/code/Magento/Cms/etc/di.xml index 0d0839365cc6a..2c265f881acf8 100644 --- a/app/code/Magento/Cms/etc/di.xml +++ b/app/code/Magento/Cms/etc/di.xml @@ -285,6 +285,8 @@ <item name="hr" xsi:type="string">hr</item> <item name="figure" xsi:type="string">figure</item> <item name="button" xsi:type="string">button</item> + <item name="i" xsi:type="string">i</item> + <item name="u" xsi:type="string">u</item> </argument> <argument name="allowedAttributes" xsi:type="array"> <item name="class" xsi:type="string">class</item> From d60cbb9ed28d4b307cd2b82721e0f19e501134b4 Mon Sep 17 00:00:00 2001 From: Bartosz Kubicki <bartosz.kubicki@lizardmedia.pl> Date: Fri, 21 Feb 2020 17:11:00 +0100 Subject: [PATCH 029/139] Fix for numeric argument conversion --- .../Framework/Amqp/Topology/ArgumentProcessor.php | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/lib/internal/Magento/Framework/Amqp/Topology/ArgumentProcessor.php b/lib/internal/Magento/Framework/Amqp/Topology/ArgumentProcessor.php index caa5db4e7ef5c..2a7b6b939853f 100644 --- a/lib/internal/Magento/Framework/Amqp/Topology/ArgumentProcessor.php +++ b/lib/internal/Magento/Framework/Amqp/Topology/ArgumentProcessor.php @@ -1,10 +1,15 @@ <?php + +declare(strict_types=1); + /** * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ namespace Magento\Framework\Amqp\Topology; +use InvalidArgumentException; + /** * @deprecated 100.0.0 * see: https://github.com/php-amqplib/php-amqplib/issues/405 @@ -17,22 +22,23 @@ trait ArgumentProcessor * @param array $arguments * @return array */ - public function processArguments($arguments) + public function processArguments($arguments): array { $output = []; foreach ($arguments as $key => $value) { if (is_array($value)) { $output[$key] = ['A', $value]; - } elseif (is_int($value)) { - $output[$key] = ['I', $value]; + } elseif (is_numeric($value)) { + $output[$key] = ['I', (int) $value]; } elseif (is_bool($value)) { $output[$key] = ['t', $value]; } elseif (is_string($value)) { $output[$key] = ['S', $value]; } else { - throw new \InvalidArgumentException('Unknown argument type ' . gettype($value)); + throw new InvalidArgumentException('Unknown argument type ' . gettype($value)); } } + return $output; } } From 08e7e4faffca9330aaed009dbe6ddebb9f6c70ee Mon Sep 17 00:00:00 2001 From: Vadim Malesh <51680850+engcom-Charlie@users.noreply.github.com> Date: Tue, 1 Sep 2020 09:58:08 +0300 Subject: [PATCH 030/139] add testCaseId --- ...torefrontCheckoutWithPurchaseOrderNumberPressKeyEnterTest.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCheckoutWithPurchaseOrderNumberPressKeyEnterTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCheckoutWithPurchaseOrderNumberPressKeyEnterTest.xml index 0959962d50d81..1055ff25edaef 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCheckoutWithPurchaseOrderNumberPressKeyEnterTest.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCheckoutWithPurchaseOrderNumberPressKeyEnterTest.xml @@ -15,6 +15,7 @@ <title value="Create Checkout with purchase order payment method test. Press key Enter on field Purchase Order Number for create Order."/> <description value="Create Checkout with purchase order payment method. Press key Enter on field Purchase Order Number for create Order."/> <severity value="MAJOR"/> + <testCaseId value="MC-37227"/> <group value="checkout"/> </annotations> From 3efc95a409120fa2724be02bf2b5222c17b4ee23 Mon Sep 17 00:00:00 2001 From: Oleh Usik <o.usik@atwix.com> Date: Wed, 2 Sep 2020 15:10:15 +0300 Subject: [PATCH 031/139] remove wrong tags --- app/code/Magento/AdminNotification/Block/ToolbarEntry.php | 1 - app/code/Magento/AdminNotification/Model/Feed.php | 1 - app/code/Magento/AdminNotification/Model/InboxInterface.php | 1 - .../Magento/AdminNotification/Model/NotificationService.php | 1 - .../AdminNotification/Model/ResourceModel/Grid/Collection.php | 2 -- .../AdminNotification/Model/ResourceModel/Inbox/Collection.php | 2 -- .../Model/ResourceModel/Inbox/Collection/Unread.php | 2 -- .../Observer/PredispatchAdminActionControllerObserver.php | 2 -- 8 files changed, 12 deletions(-) diff --git a/app/code/Magento/AdminNotification/Block/ToolbarEntry.php b/app/code/Magento/AdminNotification/Block/ToolbarEntry.php index c097edfd8af65..42ca68177cb83 100644 --- a/app/code/Magento/AdminNotification/Block/ToolbarEntry.php +++ b/app/code/Magento/AdminNotification/Block/ToolbarEntry.php @@ -10,7 +10,6 @@ * Toolbar entry that shows latest notifications * * @api - * @author Magento Core Team <core@magentocommerce.com> * @since 100.0.2 */ class ToolbarEntry extends \Magento\Backend\Block\Template diff --git a/app/code/Magento/AdminNotification/Model/Feed.php b/app/code/Magento/AdminNotification/Model/Feed.php index b99a8bbbc9031..ac1e631cc3f33 100644 --- a/app/code/Magento/AdminNotification/Model/Feed.php +++ b/app/code/Magento/AdminNotification/Model/Feed.php @@ -12,7 +12,6 @@ /** * AdminNotification Feed model * - * @author Magento Core Team <core@magentocommerce.com> * @SuppressWarnings(PHPMD.CouplingBetweenObjects) * @api * @since 100.0.2 diff --git a/app/code/Magento/AdminNotification/Model/InboxInterface.php b/app/code/Magento/AdminNotification/Model/InboxInterface.php index 4e87822763fc3..5e61c3dd680c9 100644 --- a/app/code/Magento/AdminNotification/Model/InboxInterface.php +++ b/app/code/Magento/AdminNotification/Model/InboxInterface.php @@ -8,7 +8,6 @@ /** * AdminNotification Inbox interface * - * @author Magento Core Team <core@magentocommerce.com> * @api * @since 100.0.2 */ diff --git a/app/code/Magento/AdminNotification/Model/NotificationService.php b/app/code/Magento/AdminNotification/Model/NotificationService.php index d44e98aaf2203..a13efe2136a6f 100644 --- a/app/code/Magento/AdminNotification/Model/NotificationService.php +++ b/app/code/Magento/AdminNotification/Model/NotificationService.php @@ -8,7 +8,6 @@ /** * Notification service model * - * @author Magento Core Team <core@magentocommerce.com> * @api * @since 100.0.2 */ diff --git a/app/code/Magento/AdminNotification/Model/ResourceModel/Grid/Collection.php b/app/code/Magento/AdminNotification/Model/ResourceModel/Grid/Collection.php index e12419155d52b..1a59d15e40c7a 100644 --- a/app/code/Magento/AdminNotification/Model/ResourceModel/Grid/Collection.php +++ b/app/code/Magento/AdminNotification/Model/ResourceModel/Grid/Collection.php @@ -6,8 +6,6 @@ /** * AdminNotification Inbox model - * - * @author Magento Core Team <core@magentocommerce.com> */ namespace Magento\AdminNotification\Model\ResourceModel\Grid; diff --git a/app/code/Magento/AdminNotification/Model/ResourceModel/Inbox/Collection.php b/app/code/Magento/AdminNotification/Model/ResourceModel/Inbox/Collection.php index 44ec765b9d0a2..bf4f91cc6ae80 100644 --- a/app/code/Magento/AdminNotification/Model/ResourceModel/Inbox/Collection.php +++ b/app/code/Magento/AdminNotification/Model/ResourceModel/Inbox/Collection.php @@ -9,8 +9,6 @@ * AdminNotification Inbox model * * @api - * @author Magento Core Team <core@magentocommerce.com> - * @api * @since 100.0.2 */ class Collection extends \Magento\Framework\Model\ResourceModel\Db\Collection\AbstractCollection diff --git a/app/code/Magento/AdminNotification/Model/ResourceModel/Inbox/Collection/Unread.php b/app/code/Magento/AdminNotification/Model/ResourceModel/Inbox/Collection/Unread.php index b9e77f8a35295..9504c2f2d10f7 100644 --- a/app/code/Magento/AdminNotification/Model/ResourceModel/Inbox/Collection/Unread.php +++ b/app/code/Magento/AdminNotification/Model/ResourceModel/Inbox/Collection/Unread.php @@ -6,8 +6,6 @@ /** * Collection of unread notifications - * - * @author Magento Core Team <core@magentocommerce.com> */ namespace Magento\AdminNotification\Model\ResourceModel\Inbox\Collection; diff --git a/app/code/Magento/AdminNotification/Observer/PredispatchAdminActionControllerObserver.php b/app/code/Magento/AdminNotification/Observer/PredispatchAdminActionControllerObserver.php index 24ef712c0f61f..5c40ec88f0906 100644 --- a/app/code/Magento/AdminNotification/Observer/PredispatchAdminActionControllerObserver.php +++ b/app/code/Magento/AdminNotification/Observer/PredispatchAdminActionControllerObserver.php @@ -9,8 +9,6 @@ /** * AdminNotification observer - * - * @author Magento Core Team <core@magentocommerce.com> */ class PredispatchAdminActionControllerObserver implements ObserverInterface { From abff48fd8d26ab455154da5b1a102c5ec99a27b8 Mon Sep 17 00:00:00 2001 From: Oleh Usik <o.usik@atwix.com> Date: Wed, 2 Sep 2020 15:24:27 +0300 Subject: [PATCH 032/139] Remove wrong tag --- .../AdvancedPricingImportExport/Model/Export/AdvancedPricing.php | 1 - 1 file changed, 1 deletion(-) diff --git a/app/code/Magento/AdvancedPricingImportExport/Model/Export/AdvancedPricing.php b/app/code/Magento/AdvancedPricingImportExport/Model/Export/AdvancedPricing.php index 27e2713995653..a11f53aeb67b8 100644 --- a/app/code/Magento/AdvancedPricingImportExport/Model/Export/AdvancedPricing.php +++ b/app/code/Magento/AdvancedPricingImportExport/Model/Export/AdvancedPricing.php @@ -14,7 +14,6 @@ /** * Export Advanced Pricing * - * @author Magento Core Team <core@magentocommerce.com> * @SuppressWarnings(PHPMD.TooManyFields) * @SuppressWarnings(PHPMD.ExcessiveClassComplexity) * @SuppressWarnings(PHPMD.CouplingBetweenObjects) From 61b3c24f25a8b6833ae54fcb95229e599584115f Mon Sep 17 00:00:00 2001 From: Oleh Usik <o.usik@atwix.com> Date: Wed, 2 Sep 2020 15:30:36 +0300 Subject: [PATCH 033/139] removes wrong tags --- app/code/Magento/AdvancedSearch/Block/Adminhtml/Search/Edit.php | 1 - app/code/Magento/AdvancedSearch/Block/Adminhtml/Search/Grid.php | 1 - .../AdvancedSearch/Model/ResourceModel/Recommendations.php | 1 - 3 files changed, 3 deletions(-) diff --git a/app/code/Magento/AdvancedSearch/Block/Adminhtml/Search/Edit.php b/app/code/Magento/AdvancedSearch/Block/Adminhtml/Search/Edit.php index 403a4d12cc17b..401e9d666103e 100644 --- a/app/code/Magento/AdvancedSearch/Block/Adminhtml/Search/Edit.php +++ b/app/code/Magento/AdvancedSearch/Block/Adminhtml/Search/Edit.php @@ -9,7 +9,6 @@ * Search queries relations grid container * * @api - * @author Magento Core Team <core@magentocommerce.com> * @since 100.0.2 */ class Edit extends \Magento\Backend\Block\Widget\Grid\Container diff --git a/app/code/Magento/AdvancedSearch/Block/Adminhtml/Search/Grid.php b/app/code/Magento/AdvancedSearch/Block/Adminhtml/Search/Grid.php index 6bdfd3b0dd143..add3e244be851 100644 --- a/app/code/Magento/AdvancedSearch/Block/Adminhtml/Search/Grid.php +++ b/app/code/Magento/AdvancedSearch/Block/Adminhtml/Search/Grid.php @@ -9,7 +9,6 @@ * Search query relations edit grid * * @api - * @author Magento Core Team <core@magentocommerce.com> * @since 100.0.2 */ class Grid extends \Magento\Backend\Block\Widget\Grid diff --git a/app/code/Magento/AdvancedSearch/Model/ResourceModel/Recommendations.php b/app/code/Magento/AdvancedSearch/Model/ResourceModel/Recommendations.php index c19c1d67d81f7..9be5d0c201841 100644 --- a/app/code/Magento/AdvancedSearch/Model/ResourceModel/Recommendations.php +++ b/app/code/Magento/AdvancedSearch/Model/ResourceModel/Recommendations.php @@ -8,7 +8,6 @@ /** * Catalog search recommendations resource model * - * @author Magento Core Team <core@magentocommerce.com> * @api * @since 100.0.2 */ From 4666ac046457f733b79bf47dd1aede3b0ae4c76d Mon Sep 17 00:00:00 2001 From: Oleh Usik <o.usik@atwix.com> Date: Wed, 2 Sep 2020 16:19:37 +0300 Subject: [PATCH 034/139] remove some wrongs tags --- .../Setup/Patch/Data/InitializeAuthRoles.php | 25 ++++++++++--------- .../Model/Indexer/Stock/AbstractAction.php | 2 -- .../Model/Indexer/Stock/Action/Full.php | 2 -- .../Model/Indexer/Stock/Action/Row.php | 4 --- .../Model/Indexer/Stock/Action/Rows.php | 4 --- .../Model/Indexer/Stock/CacheCleaner.php | 2 -- .../Model/Indexer/Stock/Processor.php | 2 -- .../Model/Indexer/Stock/Action/FullTest.php | 3 --- .../Model/Indexer/Stock/Action/RowTest.php | 3 --- .../Model/Indexer/Stock/Action/RowsTest.php | 3 --- .../Indexer/Stock/Plugin/StoreGroupTest.php | 3 --- .../Sales/Block/Adminhtml/Order/View.php | 2 -- 12 files changed, 13 insertions(+), 42 deletions(-) diff --git a/app/code/Magento/Authorization/Setup/Patch/Data/InitializeAuthRoles.php b/app/code/Magento/Authorization/Setup/Patch/Data/InitializeAuthRoles.php index 84992badf65db..c133bae98f1c5 100644 --- a/app/code/Magento/Authorization/Setup/Patch/Data/InitializeAuthRoles.php +++ b/app/code/Magento/Authorization/Setup/Patch/Data/InitializeAuthRoles.php @@ -6,6 +6,9 @@ namespace Magento\Authorization\Setup\Patch\Data; +use Magento\Authorization\Model\ResourceModel\Role; +use Magento\Authorization\Model\Rules; +use Magento\Authorization\Setup\AuthorizationFactory; use Magento\Framework\App\ResourceConnection; use Magento\Framework\Setup\ModuleDataSetupInterface; use Magento\Framework\Setup\Patch\DataPatchInterface; @@ -14,8 +17,7 @@ use Magento\Authorization\Model\UserContextInterface; /** - * Class InitializeAuthRoles - * @package Magento\Authorization\Setup\Patch + * Class for Initialize Auth Roles */ class InitializeAuthRoles implements DataPatchInterface, PatchVersionInterface { @@ -25,25 +27,24 @@ class InitializeAuthRoles implements DataPatchInterface, PatchVersionInterface private $moduleDataSetup; /** - * @var \Magento\Authorization\Setup\AuthorizationFactory + * @var AuthorizationFactory */ private $authFactory; /** - * InitializeAuthRoles constructor. * @param ModuleDataSetupInterface $moduleDataSetup - * @param \Magento\Authorization\Setup\AuthorizationFactory $authorizationFactory + * @param AuthorizationFactory $authorizationFactory */ public function __construct( ModuleDataSetupInterface $moduleDataSetup, - \Magento\Authorization\Setup\AuthorizationFactory $authorizationFactory + AuthorizationFactory $authorizationFactory ) { $this->moduleDataSetup = $moduleDataSetup; $this->authFactory = $authorizationFactory; } /** - * {@inheritdoc} + * @inheritdoc */ public function apply() { @@ -68,7 +69,7 @@ public function apply() ] )->save(); } else { - /** @var \Magento\Authorization\Model\ResourceModel\Role $item */ + /** @var Role $item */ foreach ($roleCollection as $item) { $admGroupRole = $item; break; @@ -89,7 +90,7 @@ public function apply() ] )->save(); } else { - /** @var \Magento\Authorization\Model\Rules $rule */ + /** @var Rules $rule */ foreach ($rulesCollection as $rule) { $rule->setData('resource_id', 'Magento_Backend::all')->save(); } @@ -108,7 +109,7 @@ public function apply() } /** - * {@inheritdoc} + * @inheritdoc */ public static function getDependencies() { @@ -116,7 +117,7 @@ public static function getDependencies() } /** - * {@inheritdoc} + * @inheritdoc */ public static function getVersion() { @@ -124,7 +125,7 @@ public static function getVersion() } /** - * {@inheritdoc} + * @inheritdoc */ public function getAliases() { diff --git a/app/code/Magento/CatalogInventory/Model/Indexer/Stock/AbstractAction.php b/app/code/Magento/CatalogInventory/Model/Indexer/Stock/AbstractAction.php index 85fee62eb4303..54d92cf12e2b8 100644 --- a/app/code/Magento/CatalogInventory/Model/Indexer/Stock/AbstractAction.php +++ b/app/code/Magento/CatalogInventory/Model/Indexer/Stock/AbstractAction.php @@ -1,7 +1,5 @@ <?php /** - * @category Magento - * @package Magento_CatalogInventory * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ diff --git a/app/code/Magento/CatalogInventory/Model/Indexer/Stock/Action/Full.php b/app/code/Magento/CatalogInventory/Model/Indexer/Stock/Action/Full.php index 43a5aabee9779..e345ef2ee752b 100644 --- a/app/code/Magento/CatalogInventory/Model/Indexer/Stock/Action/Full.php +++ b/app/code/Magento/CatalogInventory/Model/Indexer/Stock/Action/Full.php @@ -1,7 +1,5 @@ <?php /** - * @category Magento - * @package Magento_CatalogInventory * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ diff --git a/app/code/Magento/CatalogInventory/Model/Indexer/Stock/Action/Row.php b/app/code/Magento/CatalogInventory/Model/Indexer/Stock/Action/Row.php index c7dfcffee3d31..9e5e39e4aeb53 100644 --- a/app/code/Magento/CatalogInventory/Model/Indexer/Stock/Action/Row.php +++ b/app/code/Magento/CatalogInventory/Model/Indexer/Stock/Action/Row.php @@ -1,7 +1,5 @@ <?php /** - * @category Magento - * @package Magento_CatalogInventory * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ @@ -10,8 +8,6 @@ /** * Class Row reindex action - * - * @package Magento\CatalogInventory\Model\Indexer\Stock\Action */ class Row extends \Magento\CatalogInventory\Model\Indexer\Stock\AbstractAction { diff --git a/app/code/Magento/CatalogInventory/Model/Indexer/Stock/Action/Rows.php b/app/code/Magento/CatalogInventory/Model/Indexer/Stock/Action/Rows.php index f107955f0201e..a6176df3b107e 100644 --- a/app/code/Magento/CatalogInventory/Model/Indexer/Stock/Action/Rows.php +++ b/app/code/Magento/CatalogInventory/Model/Indexer/Stock/Action/Rows.php @@ -1,7 +1,5 @@ <?php /** - * @category Magento - * @package Magento_CatalogInventory * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ @@ -10,8 +8,6 @@ /** * Class Rows reindex action for mass actions - * - * @package Magento\CatalogInventory\Model\Indexer\Stock\Action */ class Rows extends \Magento\CatalogInventory\Model\Indexer\Stock\AbstractAction { diff --git a/app/code/Magento/CatalogInventory/Model/Indexer/Stock/CacheCleaner.php b/app/code/Magento/CatalogInventory/Model/Indexer/Stock/CacheCleaner.php index b3fa07479a712..055185239e404 100644 --- a/app/code/Magento/CatalogInventory/Model/Indexer/Stock/CacheCleaner.php +++ b/app/code/Magento/CatalogInventory/Model/Indexer/Stock/CacheCleaner.php @@ -1,7 +1,5 @@ <?php /** - * @category Magento - * @package Magento_CatalogInventory * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ diff --git a/app/code/Magento/CatalogInventory/Model/Indexer/Stock/Processor.php b/app/code/Magento/CatalogInventory/Model/Indexer/Stock/Processor.php index 403f64e7f77f8..e59f81414f102 100644 --- a/app/code/Magento/CatalogInventory/Model/Indexer/Stock/Processor.php +++ b/app/code/Magento/CatalogInventory/Model/Indexer/Stock/Processor.php @@ -1,7 +1,5 @@ <?php /** - * @category Magento - * @package Magento_CatalogInventory * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ diff --git a/app/code/Magento/CatalogInventory/Test/Unit/Model/Indexer/Stock/Action/FullTest.php b/app/code/Magento/CatalogInventory/Test/Unit/Model/Indexer/Stock/Action/FullTest.php index ca89ac01f280f..c888d522d2e8b 100644 --- a/app/code/Magento/CatalogInventory/Test/Unit/Model/Indexer/Stock/Action/FullTest.php +++ b/app/code/Magento/CatalogInventory/Test/Unit/Model/Indexer/Stock/Action/FullTest.php @@ -1,8 +1,5 @@ <?php /** - * @category Magento - * @package Magento_CatalogInventory - * @subpackage unit_tests * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ diff --git a/app/code/Magento/CatalogInventory/Test/Unit/Model/Indexer/Stock/Action/RowTest.php b/app/code/Magento/CatalogInventory/Test/Unit/Model/Indexer/Stock/Action/RowTest.php index 25b0c2ef33ebe..c9f60bd61c2fb 100644 --- a/app/code/Magento/CatalogInventory/Test/Unit/Model/Indexer/Stock/Action/RowTest.php +++ b/app/code/Magento/CatalogInventory/Test/Unit/Model/Indexer/Stock/Action/RowTest.php @@ -1,8 +1,5 @@ <?php /** - * @category Magento - * @package Magento_CatalogInventory - * @subpackage unit_tests * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ diff --git a/app/code/Magento/CatalogInventory/Test/Unit/Model/Indexer/Stock/Action/RowsTest.php b/app/code/Magento/CatalogInventory/Test/Unit/Model/Indexer/Stock/Action/RowsTest.php index e01f371b829d6..42d578ec88ea8 100644 --- a/app/code/Magento/CatalogInventory/Test/Unit/Model/Indexer/Stock/Action/RowsTest.php +++ b/app/code/Magento/CatalogInventory/Test/Unit/Model/Indexer/Stock/Action/RowsTest.php @@ -1,8 +1,5 @@ <?php /** - * @category Magento - * @package Magento_CatalogInventory - * @subpackage unit_tests * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ diff --git a/app/code/Magento/CatalogInventory/Test/Unit/Model/Indexer/Stock/Plugin/StoreGroupTest.php b/app/code/Magento/CatalogInventory/Test/Unit/Model/Indexer/Stock/Plugin/StoreGroupTest.php index 0e2b6b2f329c1..a81a4cd34b87f 100644 --- a/app/code/Magento/CatalogInventory/Test/Unit/Model/Indexer/Stock/Plugin/StoreGroupTest.php +++ b/app/code/Magento/CatalogInventory/Test/Unit/Model/Indexer/Stock/Plugin/StoreGroupTest.php @@ -1,8 +1,5 @@ <?php /** - * @category Magento - * @package Magento_CatalogInventory - * @subpackage unit_tests * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ diff --git a/app/code/Magento/Sales/Block/Adminhtml/Order/View.php b/app/code/Magento/Sales/Block/Adminhtml/Order/View.php index e4b12c30e71b4..d70df80038193 100644 --- a/app/code/Magento/Sales/Block/Adminhtml/Order/View.php +++ b/app/code/Magento/Sales/Block/Adminhtml/Order/View.php @@ -1,7 +1,5 @@ <?php /** - * @category Magento - * @package Magento_Sales * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ From bce4a8cf95e55c6e6482332e0c9087d8ee453cfb Mon Sep 17 00:00:00 2001 From: Oleh Usik <o.usik@atwix.com> Date: Fri, 4 Sep 2020 21:41:54 +0300 Subject: [PATCH 035/139] fixed static issue --- .../Model/Export/AdvancedPricing.php | 4 +-- .../Setup/Patch/Data/InitializeAuthRoles.php | 25 +++++++++---------- .../Model/Indexer/Stock/AbstractAction.php | 4 +-- .../Model/Indexer/Stock/Action/Full.php | 2 ++ .../Model/Indexer/Stock/Processor.php | 2 +- 5 files changed, 19 insertions(+), 18 deletions(-) diff --git a/app/code/Magento/AdvancedPricingImportExport/Model/Export/AdvancedPricing.php b/app/code/Magento/AdvancedPricingImportExport/Model/Export/AdvancedPricing.php index a11f53aeb67b8..60f79987932ad 100644 --- a/app/code/Magento/AdvancedPricingImportExport/Model/Export/AdvancedPricing.php +++ b/app/code/Magento/AdvancedPricingImportExport/Model/Export/AdvancedPricing.php @@ -514,6 +514,7 @@ private function fetchTierPrices(array $productIds): array */ protected function getTierPrices(array $listSku, $table) { + $selectFields = []; if (isset($this->_parameters[\Magento\ImportExport\Model\Export::FILTER_ELEMENT_GROUP])) { $exportFilter = $this->_parameters[\Magento\ImportExport\Model\Export::FILTER_ELEMENT_GROUP]; } @@ -570,12 +571,11 @@ protected function getTierPrices(array $listSku, $table) if (isset($updatedAtTo) && !empty($updatedAtTo)) { $select->where('cpe.updated_at <= ?', $updatedAtTo); } - $exportData = $this->_connection->fetchAll($select); + return $this->_connection->fetchAll($select); } catch (\Exception $e) { return false; } } - return $exportData; } /** diff --git a/app/code/Magento/Authorization/Setup/Patch/Data/InitializeAuthRoles.php b/app/code/Magento/Authorization/Setup/Patch/Data/InitializeAuthRoles.php index c133bae98f1c5..c450dc7127d4e 100644 --- a/app/code/Magento/Authorization/Setup/Patch/Data/InitializeAuthRoles.php +++ b/app/code/Magento/Authorization/Setup/Patch/Data/InitializeAuthRoles.php @@ -6,9 +6,6 @@ namespace Magento\Authorization\Setup\Patch\Data; -use Magento\Authorization\Model\ResourceModel\Role; -use Magento\Authorization\Model\Rules; -use Magento\Authorization\Setup\AuthorizationFactory; use Magento\Framework\App\ResourceConnection; use Magento\Framework\Setup\ModuleDataSetupInterface; use Magento\Framework\Setup\Patch\DataPatchInterface; @@ -17,7 +14,8 @@ use Magento\Authorization\Model\UserContextInterface; /** - * Class for Initialize Auth Roles + * Class InitializeAuthRoles + * @package Magento\Authorization\Setup\Patch */ class InitializeAuthRoles implements DataPatchInterface, PatchVersionInterface { @@ -27,24 +25,25 @@ class InitializeAuthRoles implements DataPatchInterface, PatchVersionInterface private $moduleDataSetup; /** - * @var AuthorizationFactory + * @var \Magento\Authorization\Setup\AuthorizationFactor */ private $authFactory; /** + * InitializeAuthRoles constructor. * @param ModuleDataSetupInterface $moduleDataSetup - * @param AuthorizationFactory $authorizationFactory + * @param \Magento\Authorization\Setup\AuthorizationFactor $authorizationFactory */ public function __construct( ModuleDataSetupInterface $moduleDataSetup, - AuthorizationFactory $authorizationFactory + \Magento\Authorization\Setup\AuthorizationFactor $authorizationFactory ) { $this->moduleDataSetup = $moduleDataSetup; $this->authFactory = $authorizationFactory; } /** - * @inheritdoc + * {@inheritdoc} */ public function apply() { @@ -69,7 +68,7 @@ public function apply() ] )->save(); } else { - /** @var Role $item */ + /** @var \Magento\Authorization\Model\ResourceModel\Role $item */ foreach ($roleCollection as $item) { $admGroupRole = $item; break; @@ -90,7 +89,7 @@ public function apply() ] )->save(); } else { - /** @var Rules $rule */ + /** @var \Magento\Authorization\Model\Rules $rule */ foreach ($rulesCollection as $rule) { $rule->setData('resource_id', 'Magento_Backend::all')->save(); } @@ -109,7 +108,7 @@ public function apply() } /** - * @inheritdoc + * {@inheritdoc} */ public static function getDependencies() { @@ -117,7 +116,7 @@ public static function getDependencies() } /** - * @inheritdoc + * {@inheritdoc} */ public static function getVersion() { @@ -125,7 +124,7 @@ public static function getVersion() } /** - * @inheritdoc + * {@inheritdoc} */ public function getAliases() { diff --git a/app/code/Magento/CatalogInventory/Model/Indexer/Stock/AbstractAction.php b/app/code/Magento/CatalogInventory/Model/Indexer/Stock/AbstractAction.php index 54d92cf12e2b8..4ea6b6bcfde9a 100644 --- a/app/code/Magento/CatalogInventory/Model/Indexer/Stock/AbstractAction.php +++ b/app/code/Magento/CatalogInventory/Model/Indexer/Stock/AbstractAction.php @@ -12,8 +12,6 @@ /** * Abstract action reindex class - * - * @package Magento\CatalogInventory\Model\Indexer\Stock */ abstract class AbstractAction { @@ -281,6 +279,8 @@ private function doReindex($productIds = []) } /** + * Get cache cleaner object + * * @return CacheCleaner */ private function getCacheCleaner() diff --git a/app/code/Magento/CatalogInventory/Model/Indexer/Stock/Action/Full.php b/app/code/Magento/CatalogInventory/Model/Indexer/Stock/Action/Full.php index e345ef2ee752b..43a5aabee9779 100644 --- a/app/code/Magento/CatalogInventory/Model/Indexer/Stock/Action/Full.php +++ b/app/code/Magento/CatalogInventory/Model/Indexer/Stock/Action/Full.php @@ -1,5 +1,7 @@ <?php /** + * @category Magento + * @package Magento_CatalogInventory * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ diff --git a/app/code/Magento/CatalogInventory/Model/Indexer/Stock/Processor.php b/app/code/Magento/CatalogInventory/Model/Indexer/Stock/Processor.php index e59f81414f102..73c4a8833e433 100644 --- a/app/code/Magento/CatalogInventory/Model/Indexer/Stock/Processor.php +++ b/app/code/Magento/CatalogInventory/Model/Indexer/Stock/Processor.php @@ -9,7 +9,7 @@ class Processor extends \Magento\Framework\Indexer\AbstractProcessor { /** - * Indexer ID + * Get Indexer ID for cataloginventory_stock */ const INDEXER_ID = 'cataloginventory_stock'; } From 9e248695ced252adbfd6127577786d5b8b28ae22 Mon Sep 17 00:00:00 2001 From: Oleh Usik <o.usik@atwix.com> Date: Fri, 4 Sep 2020 21:43:30 +0300 Subject: [PATCH 036/139] minor changes --- .../Authorization/Setup/Patch/Data/InitializeAuthRoles.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/code/Magento/Authorization/Setup/Patch/Data/InitializeAuthRoles.php b/app/code/Magento/Authorization/Setup/Patch/Data/InitializeAuthRoles.php index c450dc7127d4e..84992badf65db 100644 --- a/app/code/Magento/Authorization/Setup/Patch/Data/InitializeAuthRoles.php +++ b/app/code/Magento/Authorization/Setup/Patch/Data/InitializeAuthRoles.php @@ -25,18 +25,18 @@ class InitializeAuthRoles implements DataPatchInterface, PatchVersionInterface private $moduleDataSetup; /** - * @var \Magento\Authorization\Setup\AuthorizationFactor + * @var \Magento\Authorization\Setup\AuthorizationFactory */ private $authFactory; /** * InitializeAuthRoles constructor. * @param ModuleDataSetupInterface $moduleDataSetup - * @param \Magento\Authorization\Setup\AuthorizationFactor $authorizationFactory + * @param \Magento\Authorization\Setup\AuthorizationFactory $authorizationFactory */ public function __construct( ModuleDataSetupInterface $moduleDataSetup, - \Magento\Authorization\Setup\AuthorizationFactor $authorizationFactory + \Magento\Authorization\Setup\AuthorizationFactory $authorizationFactory ) { $this->moduleDataSetup = $moduleDataSetup; $this->authFactory = $authorizationFactory; From ff97fe619d211ce80c4ecf73517f896c5a2cc2de Mon Sep 17 00:00:00 2001 From: Oleh Usik <o.usik@atwix.com> Date: Fri, 4 Sep 2020 22:30:39 +0300 Subject: [PATCH 037/139] fix some static issues --- .../Observer/PredispatchAdminActionControllerObserver.php | 1 + .../Model/Export/AdvancedPricing.php | 5 +++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/app/code/Magento/AdminNotification/Observer/PredispatchAdminActionControllerObserver.php b/app/code/Magento/AdminNotification/Observer/PredispatchAdminActionControllerObserver.php index 5c40ec88f0906..a244ad1fb9a0f 100644 --- a/app/code/Magento/AdminNotification/Observer/PredispatchAdminActionControllerObserver.php +++ b/app/code/Magento/AdminNotification/Observer/PredispatchAdminActionControllerObserver.php @@ -9,6 +9,7 @@ /** * AdminNotification observer + * @SuppressWarnings(PHPMD.CookieAndSessionMisuse) */ class PredispatchAdminActionControllerObserver implements ObserverInterface { diff --git a/app/code/Magento/AdvancedPricingImportExport/Model/Export/AdvancedPricing.php b/app/code/Magento/AdvancedPricingImportExport/Model/Export/AdvancedPricing.php index 60f79987932ad..27e2713995653 100644 --- a/app/code/Magento/AdvancedPricingImportExport/Model/Export/AdvancedPricing.php +++ b/app/code/Magento/AdvancedPricingImportExport/Model/Export/AdvancedPricing.php @@ -14,6 +14,7 @@ /** * Export Advanced Pricing * + * @author Magento Core Team <core@magentocommerce.com> * @SuppressWarnings(PHPMD.TooManyFields) * @SuppressWarnings(PHPMD.ExcessiveClassComplexity) * @SuppressWarnings(PHPMD.CouplingBetweenObjects) @@ -514,7 +515,6 @@ private function fetchTierPrices(array $productIds): array */ protected function getTierPrices(array $listSku, $table) { - $selectFields = []; if (isset($this->_parameters[\Magento\ImportExport\Model\Export::FILTER_ELEMENT_GROUP])) { $exportFilter = $this->_parameters[\Magento\ImportExport\Model\Export::FILTER_ELEMENT_GROUP]; } @@ -571,11 +571,12 @@ protected function getTierPrices(array $listSku, $table) if (isset($updatedAtTo) && !empty($updatedAtTo)) { $select->where('cpe.updated_at <= ?', $updatedAtTo); } - return $this->_connection->fetchAll($select); + $exportData = $this->_connection->fetchAll($select); } catch (\Exception $e) { return false; } } + return $exportData; } /** From 314ec92a7c82b70ac7460c21b2c82085e380d292 Mon Sep 17 00:00:00 2001 From: Bartosz Kubicki <bartosz.kubicki@lizardmedia.pl> Date: Sat, 5 Sep 2020 22:23:44 +0200 Subject: [PATCH 038/139] Adding unit test --- .../Unit/Topology/ArgumentProcessorTest.php | 73 +++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 lib/internal/Magento/Framework/Amqp/Test/Unit/Topology/ArgumentProcessorTest.php diff --git a/lib/internal/Magento/Framework/Amqp/Test/Unit/Topology/ArgumentProcessorTest.php b/lib/internal/Magento/Framework/Amqp/Test/Unit/Topology/ArgumentProcessorTest.php new file mode 100644 index 0000000000000..ef1098da318ab --- /dev/null +++ b/lib/internal/Magento/Framework/Amqp/Test/Unit/Topology/ArgumentProcessorTest.php @@ -0,0 +1,73 @@ +<?php + +declare(strict_types=1); + +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\Framework\Amqp\Test\Unit\Topology; + +use InvalidArgumentException; +use Magento\Framework\Amqp\Topology\ArgumentProcessor; +use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\TestCase; + +/** + * Class ArgumentProcessorTest + */ +class ArgumentProcessorTest extends TestCase +{ + /** + * @var ArgumentProcessor|MockObject + */ + private $argumentProcessor; + + /** + * @return void + */ + public function testProcessArgumentsWhenAnyArgumentIsIncorrect(): void + { + $arguments = [ + 'test' => new class { + } + ]; + + $this->expectException(InvalidArgumentException::class); + $this->argumentProcessor->processArguments($arguments); + } + + /** + * @return void + */ + public function testProcessArgumentsWhenAllArgumentAreCorrect(): void + { + $arguments = [ + 'array_type' => ['some_key' => 'some_value'], + 'numeric_value' => '25', + 'integer_value' => 26, + 'boolean_value' => false, + 'string_value' => 'test' + ]; + + $expected = [ + 'array_type' => ['A', ['some_key' => 'some_value']], + 'numeric_value' => ['I', 25], + 'integer_value' => ['I', 26], + 'boolean_value' => ['t', false], + 'string_value' => ['S', 'test'] + ]; + + $this->assertSame($expected, $this->argumentProcessor->processArguments($arguments)); + } + + /** + * @return void + */ + protected function setUp(): void + { + parent::setUp(); + $this->argumentProcessor = $this->getMockForTrait(ArgumentProcessor::class); + } +} From 69eca0706fdfd74222b8a5aaab5a57bf0c4f0ea7 Mon Sep 17 00:00:00 2001 From: Stanislav Idolov <sidolov@adobe.com> Date: Tue, 8 Sep 2020 09:40:50 -0500 Subject: [PATCH 039/139] Remove useless doc block --- .../Amqp/Test/Unit/Topology/ArgumentProcessorTest.php | 3 --- 1 file changed, 3 deletions(-) diff --git a/lib/internal/Magento/Framework/Amqp/Test/Unit/Topology/ArgumentProcessorTest.php b/lib/internal/Magento/Framework/Amqp/Test/Unit/Topology/ArgumentProcessorTest.php index ef1098da318ab..8aaf57bf36543 100644 --- a/lib/internal/Magento/Framework/Amqp/Test/Unit/Topology/ArgumentProcessorTest.php +++ b/lib/internal/Magento/Framework/Amqp/Test/Unit/Topology/ArgumentProcessorTest.php @@ -14,9 +14,6 @@ use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; -/** - * Class ArgumentProcessorTest - */ class ArgumentProcessorTest extends TestCase { /** From b97c6c3c4b63e6dc14f996dfcbf34d24051a6bd0 Mon Sep 17 00:00:00 2001 From: "vadim.malesh" <engcom-vendorworker-charlie@adobe.com> Date: Tue, 15 Sep 2020 16:25:09 +0300 Subject: [PATCH 040/139] add style for input --- .../web/css/source/module/checkout/_payments.less | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/app/design/frontend/Magento/luma/Magento_Checkout/web/css/source/module/checkout/_payments.less b/app/design/frontend/Magento/luma/Magento_Checkout/web/css/source/module/checkout/_payments.less index 494483ff60dda..8fbe67abe2960 100644 --- a/app/design/frontend/Magento/luma/Magento_Checkout/web/css/source/module/checkout/_payments.less +++ b/app/design/frontend/Magento/luma/Magento_Checkout/web/css/source/module/checkout/_payments.less @@ -49,6 +49,10 @@ form { &.form-purchase-order { margin-bottom: 15px; + + .input-text { + width: 40%; + } } } } @@ -119,7 +123,7 @@ margin: 0 0 @indent__base; .primary { - .action-update { + .action-update { margin-bottom: 20px; margin-right: 0; } @@ -133,7 +137,7 @@ .lib-css(line-height, @checkout-billing-address-details__line-height); .lib-css(padding, @checkout-billing-address-details__padding); } - + input[type="checkbox"] { vertical-align: top; } From 10bcde3d6a9f664670e2e944af0b447203e71c8c Mon Sep 17 00:00:00 2001 From: ogorkun <ogorkun@adobe.com> Date: Wed, 16 Sep 2020 14:53:38 -0500 Subject: [PATCH 041/139] MC-34385: Filter fields allowing HTML --- .../Magento/Cms/Model/Wysiwyg/Validator.php | 36 +++++++++++++++---- .../Test/Unit/Model/Wysiwyg/ValidatorTest.php | 13 +++++-- 2 files changed, 40 insertions(+), 9 deletions(-) diff --git a/app/code/Magento/Cms/Model/Wysiwyg/Validator.php b/app/code/Magento/Cms/Model/Wysiwyg/Validator.php index eb17a0f3127ea..39360e6350967 100644 --- a/app/code/Magento/Cms/Model/Wysiwyg/Validator.php +++ b/app/code/Magento/Cms/Model/Wysiwyg/Validator.php @@ -10,9 +10,11 @@ use Magento\Framework\App\Config\ScopeConfigInterface; use Magento\Framework\Message\ManagerInterface; +use Magento\Framework\Message\MessageInterface; use Magento\Framework\Validation\ValidationException; use Magento\Framework\Validator\HTML\WYSIWYGValidatorInterface; use Psr\Log\LoggerInterface; +use Magento\Framework\Message\Factory as MessageFactory; /** * Processes backend validator results. @@ -41,22 +43,30 @@ class Validator implements WYSIWYGValidatorInterface */ private $logger; + /** + * @var MessageFactory + */ + private $messageFactory; + /** * @param WYSIWYGValidatorInterface $validator * @param ManagerInterface $messages * @param ScopeConfigInterface $config * @param LoggerInterface $logger + * @param MessageFactory $messageFactory */ public function __construct( WYSIWYGValidatorInterface $validator, ManagerInterface $messages, ScopeConfigInterface $config, - LoggerInterface $logger + LoggerInterface $logger, + MessageFactory $messageFactory ) { $this->validator = $validator; $this->messages = $messages; $this->config = $config; $this->logger = $logger; + $this->messageFactory = $messageFactory; } /** @@ -71,18 +81,30 @@ public function validate(string $content): void if ($throwException) { throw $exception; } else { - $this->messages->addWarningMessage( - __( - 'Temporarily allowed to save HTML value that contains restricted elements. %1', - $exception->getMessage() - ) + $this->messages->addUniqueMessages( + [ + $this->messageFactory->create( + MessageInterface::TYPE_WARNING, + (string)__( + 'Temporarily allowed to save HTML value that contains restricted elements. %1', + $exception->getMessage() + ) + ) + ] ); } } catch (\Throwable $exception) { if ($throwException) { throw $exception; } else { - $this->messages->addWarningMessage(__('Invalid HTML provided')->render()); + $this->messages->addUniqueMessages( + [ + $this->messageFactory->create( + MessageInterface::TYPE_WARNING, + (string)__('Invalid HTML provided') + ) + ] + ); $this->logger->error($exception); } } diff --git a/app/code/Magento/Cms/Test/Unit/Model/Wysiwyg/ValidatorTest.php b/app/code/Magento/Cms/Test/Unit/Model/Wysiwyg/ValidatorTest.php index b14ad81aa2c1a..8e2fa44a24545 100644 --- a/app/code/Magento/Cms/Test/Unit/Model/Wysiwyg/ValidatorTest.php +++ b/app/code/Magento/Cms/Test/Unit/Model/Wysiwyg/ValidatorTest.php @@ -11,10 +11,12 @@ use Magento\Cms\Model\Wysiwyg\Validator; use Magento\Framework\App\Config\ScopeConfigInterface; use Magento\Framework\Message\ManagerInterface; +use Magento\Framework\Message\MessageInterface; use Magento\Framework\Validation\ValidationException; use Magento\Framework\Validator\HTML\WYSIWYGValidatorInterface; use PHPUnit\Framework\TestCase; use Psr\Log\LoggerInterface; +use Magento\Framework\Message\Factory as MessageFactory; class ValidatorTest extends TestCase { @@ -45,6 +47,13 @@ public function testValidate(bool $isFlagSet, ?\Throwable $thrown, bool $excepti { $actuallyWarned = false; + $messageFactoryMock = $this->createMock(MessageFactory::class); + $messageFactoryMock->method('create') + ->willReturnCallback( + function () { + return $this->getMockForAbstractClass(MessageInterface::class); + } + ); $configMock = $this->getMockForAbstractClass(ScopeConfigInterface::class); $configMock->method('isSetFlag') ->with(Validator::CONFIG_PATH_THROW_EXCEPTION) @@ -56,7 +65,7 @@ public function testValidate(bool $isFlagSet, ?\Throwable $thrown, bool $excepti } $messagesMock = $this->getMockForAbstractClass(ManagerInterface::class); - $messagesMock->method('addWarningMessage') + $messagesMock->method('addUniqueMessages') ->willReturnCallback( function () use (&$actuallyWarned): void { $actuallyWarned = true; @@ -65,7 +74,7 @@ function () use (&$actuallyWarned): void { $loggerMock = $this->getMockForAbstractClass(LoggerInterface::class); - $validator = new Validator($backendMock, $messagesMock, $configMock, $loggerMock); + $validator = new Validator($backendMock, $messagesMock, $configMock, $loggerMock, $messageFactoryMock); try { $validator->validate('content'); $actuallyThrown = false; From e8f7b175543c698959ae6e645b4277c7768135c6 Mon Sep 17 00:00:00 2001 From: Oleh Usik <o.usik@atwix.com> Date: Wed, 16 Sep 2020 23:21:19 +0300 Subject: [PATCH 042/139] add AdminOpenCustomersGridActionGroup --- .../Test/CheckTierPricingOfProductsTest.xml | 3 +-- .../AdminOpenCustomersGridActionGroup.xml | 19 +++++++++++++++++++ ...aultBillingShippingCustomerAddressTest.xml | 2 +- ...hangeCustomerGenderInCustomersGridTest.xml | 3 +-- .../Mftf/Test/AdminCreateCustomerTest.xml | 3 +-- ...stomerOnStorefrontSignupNewsletterTest.xml | 3 +-- ...DeleteCustomerAddressesFromTheGridTest.xml | 2 +- ...AddressesFromTheGridViaMassActionsTest.xml | 2 +- ...eleteDefaultBillingCustomerAddressTest.xml | 2 +- ...aultBillingShippingCustomerAddressTest.xml | 2 +- ...dminExactMatchSearchInCustomerGridTest.xml | 4 ++-- ...dminSearchCustomerAddressByKeywordTest.xml | 2 +- ...inSetCustomerDefaultBillingAddressTest.xml | 2 +- ...nSetCustomerDefaultShippingAddressTest.xml | 2 +- ...ustomerInfoFromDefaultToNonDefaultTest.xml | 8 ++------ ...tomerAddressStateContainValuesOnceTest.xml | 4 +--- ...CountriesRestrictionApplyOnBackendTest.xml | 3 +-- .../Test/SearchByEmailInCustomerGridTest.xml | 4 ++-- .../CreateOrderFromEditCustomerPageTest.xml | 2 +- ...rableProductsInComparedOnOrderPageTest.xml | 4 +--- ...impleProductsInComparedOnOrderPageTest.xml | 4 +--- .../AdminNavigateWhileUserExpiredTest.xml | 2 +- 22 files changed, 43 insertions(+), 39 deletions(-) create mode 100644 app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminOpenCustomersGridActionGroup.xml diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/CheckTierPricingOfProductsTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/CheckTierPricingOfProductsTest.xml index 55d697e35deba..8436c94acc685 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/CheckTierPricingOfProductsTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/CheckTierPricingOfProductsTest.xml @@ -127,8 +127,7 @@ <waitForPageLoad stepKey="waitForCustomersPage"/> <see userInput="You saved the customer." stepKey="CustomerIsSaved"/> - <amOnPage url="{{AdminCustomerPage.url}}" stepKey="navigateToCustomers"/> - <waitForPageLoad stepKey="waitForPageLoad1" /> + <actionGroup ref="AdminOpenCustomersGridActionGroup" stepKey="navigateToCustomers"/> <click selector="{{AdminCustomerFiltersSection.clearAll}}" stepKey="ClearFilters"/> <waitForPageLoad stepKey="waitForFiltersClear"/> diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminOpenCustomersGridActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminOpenCustomersGridActionGroup.xml new file mode 100644 index 0000000000000..c1dedfefda309 --- /dev/null +++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminOpenCustomersGridActionGroup.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AdminOpenCustomersGridActionGroup"> + <annotations> + <description>Open the Admin Customers grid page.</description> + </annotations> + + <amOnPage url="{{AdminCustomerPage.url}}" stepKey="openCustomersGridPage"/> + <waitForPageLoad stepKey="waitForPageLoad"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Customer/Test/Mftf/Test/AdminAddNewDefaultBillingShippingCustomerAddressTest.xml b/app/code/Magento/Customer/Test/Mftf/Test/AdminAddNewDefaultBillingShippingCustomerAddressTest.xml index b061b6a256471..2220f69700265 100644 --- a/app/code/Magento/Customer/Test/Mftf/Test/AdminAddNewDefaultBillingShippingCustomerAddressTest.xml +++ b/app/code/Magento/Customer/Test/Mftf/Test/AdminAddNewDefaultBillingShippingCustomerAddressTest.xml @@ -30,7 +30,7 @@ Step2. On *Customers* page choose customer from preconditions and open it to edit Step3. Open *Addresses* tab on edit customer page and press *Add New Address* button <!- --> - <amOnPage url="{{AdminCustomerPage.url}}" stepKey="openCustomersGridPage"/> + <actionGroup ref="AdminOpenCustomersGridActionGroup" stepKey="openCustomersGridPage"/> <actionGroup ref="OpenEditCustomerFromAdminActionGroup" stepKey="openEditCustomerPage"> <argument name="customer" value="Simple_US_Customer_Multiple_Addresses_No_Default_Address"/> </actionGroup> diff --git a/app/code/Magento/Customer/Test/Mftf/Test/AdminChangeCustomerGenderInCustomersGridTest.xml b/app/code/Magento/Customer/Test/Mftf/Test/AdminChangeCustomerGenderInCustomersGridTest.xml index 423954a7d9bf7..d1934a82bea0e 100644 --- a/app/code/Magento/Customer/Test/Mftf/Test/AdminChangeCustomerGenderInCustomersGridTest.xml +++ b/app/code/Magento/Customer/Test/Mftf/Test/AdminChangeCustomerGenderInCustomersGridTest.xml @@ -26,8 +26,7 @@ <after> <deleteData createDataKey="createCustomer" stepKey="deleteCustomer"/> <!-- Reset customer grid filter --> - <amOnPage url="{{AdminCustomerPage.url}}" stepKey="goToCustomersGridPage"/> - <waitForPageLoad stepKey="waitForCustomersGrid"/> + <actionGroup ref="AdminOpenCustomersGridActionGroup" stepKey="goToCustomersGridPage"/> <actionGroup ref="AdminResetFilterInCustomerGrid" stepKey="resetFilter"/> <actionGroup ref="AdminLogoutActionGroup" stepKey="logout"/> diff --git a/app/code/Magento/Customer/Test/Mftf/Test/AdminCreateCustomerTest.xml b/app/code/Magento/Customer/Test/Mftf/Test/AdminCreateCustomerTest.xml index cb003ed837294..d12a89f01cb96 100644 --- a/app/code/Magento/Customer/Test/Mftf/Test/AdminCreateCustomerTest.xml +++ b/app/code/Magento/Customer/Test/Mftf/Test/AdminCreateCustomerTest.xml @@ -29,8 +29,7 @@ </after> <actionGroup ref="AdminLoginActionGroup" stepKey="loginAsAdmin1"/> - <amOnPage url="{{AdminCustomerPage.url}}" stepKey="navigateToCustomers"/> - <waitForPageLoad stepKey="waitForLoad1"/> + <actionGroup ref="AdminOpenCustomersGridActionGroup" stepKey="navigateToCustomers"/> <click selector="{{AdminCustomerGridMainActionsSection.addNewCustomer}}" stepKey="clickCreateCustomer"/> <fillField userInput="{{CustomerEntityOne.firstname}}" selector="{{AdminCustomerAccountInformationSection.firstName}}" stepKey="fillFirstName"/> <fillField userInput="{{CustomerEntityOne.lastname}}" selector="{{AdminCustomerAccountInformationSection.lastName}}" stepKey="fillLastName"/> diff --git a/app/code/Magento/Customer/Test/Mftf/Test/AdminCreateNewCustomerOnStorefrontSignupNewsletterTest.xml b/app/code/Magento/Customer/Test/Mftf/Test/AdminCreateNewCustomerOnStorefrontSignupNewsletterTest.xml index 683b275ca1ed6..44eab9d0c19ae 100644 --- a/app/code/Magento/Customer/Test/Mftf/Test/AdminCreateNewCustomerOnStorefrontSignupNewsletterTest.xml +++ b/app/code/Magento/Customer/Test/Mftf/Test/AdminCreateNewCustomerOnStorefrontSignupNewsletterTest.xml @@ -35,8 +35,7 @@ </actionGroup> <!--Assert verify created new customer in grid--> - <amOnPage url="{{AdminCustomerPage.url}}" stepKey="navigateToCustomers"/> - <waitForPageLoad stepKey="waitForNavigateToCustomersPageLoad"/> + <actionGroup ref="AdminOpenCustomersGridActionGroup" stepKey="navigateToCustomers"/> <click selector="{{AdminCustomerFiltersSection.filtersButton}}" stepKey="clickFilterButton"/> <fillField userInput="{{CustomerEntityOne.email}}" selector="{{AdminCustomerFiltersSection.emailInput}}" stepKey="filterEmail"/> <click selector="{{AdminCustomerFiltersSection.apply}}" stepKey="clickApplyFilter"/> diff --git a/app/code/Magento/Customer/Test/Mftf/Test/AdminDeleteCustomerAddressesFromTheGridTest.xml b/app/code/Magento/Customer/Test/Mftf/Test/AdminDeleteCustomerAddressesFromTheGridTest.xml index 615a6ebcf24cc..62dcd6fc4d894 100644 --- a/app/code/Magento/Customer/Test/Mftf/Test/AdminDeleteCustomerAddressesFromTheGridTest.xml +++ b/app/code/Magento/Customer/Test/Mftf/Test/AdminDeleteCustomerAddressesFromTheGridTest.xml @@ -34,7 +34,7 @@ Step2. On *Customers* page choose customer from preconditions and open it to edit Step3. On edit customer page open *Addresses* tab and find a grid with the additional addresses <!- --> - <amOnPage url="{{AdminCustomerPage.url}}" stepKey="openCustomersGridPage"/> + <actionGroup ref="AdminOpenCustomersGridActionGroup" stepKey="openCustomersGridPage"/> <actionGroup ref="OpenEditCustomerFromAdminActionGroup" stepKey="openEditCustomerPage"> <argument name="customer" value="Simple_US_Customer_Multiple_Addresses"/> </actionGroup> diff --git a/app/code/Magento/Customer/Test/Mftf/Test/AdminDeleteCustomerAddressesFromTheGridViaMassActionsTest.xml b/app/code/Magento/Customer/Test/Mftf/Test/AdminDeleteCustomerAddressesFromTheGridViaMassActionsTest.xml index 57446a1ee0c72..c6e72901b062c 100644 --- a/app/code/Magento/Customer/Test/Mftf/Test/AdminDeleteCustomerAddressesFromTheGridViaMassActionsTest.xml +++ b/app/code/Magento/Customer/Test/Mftf/Test/AdminDeleteCustomerAddressesFromTheGridViaMassActionsTest.xml @@ -34,7 +34,7 @@ Step2. On *Customers* page choose customer from preconditions and open it to edit Step3. On edit customer page open *Addresses* tab and find a grid with the additional addresses <!- --> - <amOnPage url="{{AdminCustomerPage.url}}" stepKey="openCustomersGridPage"/> + <actionGroup ref="AdminOpenCustomersGridActionGroup" stepKey="openCustomersGridPage"/> <actionGroup ref="OpenEditCustomerFromAdminActionGroup" stepKey="openEditCustomerPage"> <argument name="customer" value="Simple_US_Customer_Multiple_Addresses"/> </actionGroup> diff --git a/app/code/Magento/Customer/Test/Mftf/Test/AdminDeleteDefaultBillingCustomerAddressTest.xml b/app/code/Magento/Customer/Test/Mftf/Test/AdminDeleteDefaultBillingCustomerAddressTest.xml index f08ea83a70da6..52c8029b8f778 100644 --- a/app/code/Magento/Customer/Test/Mftf/Test/AdminDeleteDefaultBillingCustomerAddressTest.xml +++ b/app/code/Magento/Customer/Test/Mftf/Test/AdminDeleteDefaultBillingCustomerAddressTest.xml @@ -33,7 +33,7 @@ Step1. Login to admin and go to Customers > All Customers. Step2. On *Customers* page choose customer from preconditions and open it to edit <!- --> - <amOnPage url="{{AdminCustomerPage.url}}" stepKey="openCustomersGridPage"/> + <actionGroup ref="AdminOpenCustomersGridActionGroup" stepKey="openCustomersGridPage"/> <actionGroup ref="OpenEditCustomerFromAdminActionGroup" stepKey="openEditCustomerPage"> <argument name="customer" value="Simple_US_Customer_Multiple_Addresses"/> </actionGroup> diff --git a/app/code/Magento/Customer/Test/Mftf/Test/AdminEditDefaultBillingShippingCustomerAddressTest.xml b/app/code/Magento/Customer/Test/Mftf/Test/AdminEditDefaultBillingShippingCustomerAddressTest.xml index 6e44fe96b0d7b..72bda91445256 100644 --- a/app/code/Magento/Customer/Test/Mftf/Test/AdminEditDefaultBillingShippingCustomerAddressTest.xml +++ b/app/code/Magento/Customer/Test/Mftf/Test/AdminEditDefaultBillingShippingCustomerAddressTest.xml @@ -30,7 +30,7 @@ Step2. On *Customers* page choose customer from preconditions and open it to edit Step3. Open *Addresses* tab on edit customer page and press *Add New Address* button <!- --> - <amOnPage url="{{AdminCustomerPage.url}}" stepKey="openCustomersGridPage"/> + <actionGroup ref="AdminOpenCustomersGridActionGroup" stepKey="openCustomersGridPage"/> <actionGroup ref="OpenEditCustomerFromAdminActionGroup" stepKey="openEditCustomerPage"> <argument name="customer" value="Simple_US_Customer_Multiple_Addresses"/> </actionGroup> diff --git a/app/code/Magento/Customer/Test/Mftf/Test/AdminExactMatchSearchInCustomerGridTest.xml b/app/code/Magento/Customer/Test/Mftf/Test/AdminExactMatchSearchInCustomerGridTest.xml index ea4b3645d371f..14d569ed9101d 100644 --- a/app/code/Magento/Customer/Test/Mftf/Test/AdminExactMatchSearchInCustomerGridTest.xml +++ b/app/code/Magento/Customer/Test/Mftf/Test/AdminExactMatchSearchInCustomerGridTest.xml @@ -28,12 +28,12 @@ <after> <deleteData createDataKey="createFirstCustomer" stepKey="deleteFirstCustomer"/> <deleteData createDataKey="createSecondCustomer" stepKey="deleteSecondCustomer"/> - <amOnPage url="{{AdminCustomerPage.url}}" stepKey="openCustomersGridPage"/> + <actionGroup ref="AdminOpenCustomersGridActionGroup" stepKey="openCustomersGridPage"/> <actionGroup ref="AdminResetFilterInCustomerAddressGrid" stepKey="clearCustomerGridFilter"/> <actionGroup ref="AdminLogoutActionGroup" stepKey="logout"/> </after> <!--Step 1: Go to Customers > All Customers--> - <amOnPage url="{{AdminCustomerPage.url}}" stepKey="openCustomersGridPage"/> + <actionGroup ref="AdminOpenCustomersGridActionGroup" stepKey="openCustomersGridPage"/> <!--Step 2: On Customers grid page search customer by keyword with quotes--> <actionGroup ref="SearchAdminDataGridByKeywordActionGroup" stepKey="searchCustomer"> <argument name="keyword" value="$$createSecondCustomer.firstname$$"/> diff --git a/app/code/Magento/Customer/Test/Mftf/Test/AdminSearchCustomerAddressByKeywordTest.xml b/app/code/Magento/Customer/Test/Mftf/Test/AdminSearchCustomerAddressByKeywordTest.xml index b13a06b9ef858..bac1c665cbe78 100644 --- a/app/code/Magento/Customer/Test/Mftf/Test/AdminSearchCustomerAddressByKeywordTest.xml +++ b/app/code/Magento/Customer/Test/Mftf/Test/AdminSearchCustomerAddressByKeywordTest.xml @@ -34,7 +34,7 @@ Step2. On *Customers* page choose customer from preconditions and open it to edit Step3. On edit customer page open *Addresses* tab and find a grid with the additional addresses <!- --> - <amOnPage url="{{AdminCustomerPage.url}}" stepKey="openCustomersGridPage"/> + <actionGroup ref="AdminOpenCustomersGridActionGroup" stepKey="openCustomersGridPage"/> <actionGroup ref="OpenEditCustomerFromAdminActionGroup" stepKey="openEditCustomerPage"> <argument name="customer" value="Simple_US_Customer_Multiple_Addresses"/> </actionGroup> diff --git a/app/code/Magento/Customer/Test/Mftf/Test/AdminSetCustomerDefaultBillingAddressTest.xml b/app/code/Magento/Customer/Test/Mftf/Test/AdminSetCustomerDefaultBillingAddressTest.xml index 5ce96a8dcab3c..65dcf572f19fb 100644 --- a/app/code/Magento/Customer/Test/Mftf/Test/AdminSetCustomerDefaultBillingAddressTest.xml +++ b/app/code/Magento/Customer/Test/Mftf/Test/AdminSetCustomerDefaultBillingAddressTest.xml @@ -30,7 +30,7 @@ Step2. On *Customers* page choose customer from preconditions and open it to edit Step3. On edit customer page open *Addresses* tab and find a grid with the additional addresses <!- --> - <amOnPage url="{{AdminCustomerPage.url}}" stepKey="openCustomersGridPage"/> + <actionGroup ref="AdminOpenCustomersGridActionGroup" stepKey="openCustomersGridPage"/> <actionGroup ref="OpenEditCustomerFromAdminActionGroup" stepKey="openEditCustomerPage"> <argument name="customer" value="Simple_US_Customer_Multiple_Addresses_No_Default_Address"/> </actionGroup> diff --git a/app/code/Magento/Customer/Test/Mftf/Test/AdminSetCustomerDefaultShippingAddressTest.xml b/app/code/Magento/Customer/Test/Mftf/Test/AdminSetCustomerDefaultShippingAddressTest.xml index a9832c86562f1..bf76e29b185ba 100644 --- a/app/code/Magento/Customer/Test/Mftf/Test/AdminSetCustomerDefaultShippingAddressTest.xml +++ b/app/code/Magento/Customer/Test/Mftf/Test/AdminSetCustomerDefaultShippingAddressTest.xml @@ -30,7 +30,7 @@ Step2. On *Customers* page choose customer from preconditions and open it to edit Step3. On edit customer page open *Addresses* tab and find a grid with the additional addresses <!- --> - <amOnPage url="{{AdminCustomerPage.url}}" stepKey="openCustomersGridPage"/> + <actionGroup ref="AdminOpenCustomersGridActionGroup" stepKey="openCustomersGridPage"/> <actionGroup ref="OpenEditCustomerFromAdminActionGroup" stepKey="openEditCustomerPage"> <argument name="customer" value="Simple_US_Customer_Multiple_Addresses_No_Default_Address"/> </actionGroup> diff --git a/app/code/Magento/Customer/Test/Mftf/Test/AdminUpdateCustomerTest/AdminUpdateCustomerInfoFromDefaultToNonDefaultTest.xml b/app/code/Magento/Customer/Test/Mftf/Test/AdminUpdateCustomerTest/AdminUpdateCustomerInfoFromDefaultToNonDefaultTest.xml index 8af07bc2c2d53..fb6793b1751a6 100644 --- a/app/code/Magento/Customer/Test/Mftf/Test/AdminUpdateCustomerTest/AdminUpdateCustomerInfoFromDefaultToNonDefaultTest.xml +++ b/app/code/Magento/Customer/Test/Mftf/Test/AdminUpdateCustomerTest/AdminUpdateCustomerInfoFromDefaultToNonDefaultTest.xml @@ -27,9 +27,7 @@ </before> <after> <deleteData stepKey="deleteCustomer" createDataKey="customer"/> - <!-- Reset customer grid filter --> - <amOnPage stepKey="goToCustomersGridPage" url="{{AdminCustomerPage.url}}"/> - <waitForPageLoad stepKey="waitForCustomersGrid"/> + <actionGroup ref="AdminOpenCustomersGridActionGroup" stepKey="goToCustomersGridPage"/> <actionGroup stepKey="resetFilter" ref="AdminResetFilterInCustomerGrid"/> <actionGroup ref="AdminLogoutActionGroup" stepKey="logout"/> </after> @@ -47,9 +45,7 @@ <argument name="customerAddress" value="CustomerAddressSimple"/> </actionGroup> <actionGroup stepKey="saveAndCheckSuccessMessage" ref="AdminSaveCustomerAndAssertSuccessMessage"/> - <!-- Assert Customer in Customer grid --> - <amOnPage stepKey="goToCustomersGridPage" url="{{AdminCustomerPage.url}}"/> - <waitForPageLoad stepKey="waitForCustomersGrid"/> + <actionGroup ref="AdminOpenCustomersGridActionGroup" stepKey="goToCustomersGridPage"/> <actionGroup stepKey="resetFilter" ref="AdminResetFilterInCustomerGrid"/> <actionGroup stepKey="filterByEamil" ref="AdminFilterCustomerGridByEmail"> <argument name="email" value="updated$$customer.email$$"/> diff --git a/app/code/Magento/Customer/Test/Mftf/Test/AdminVerifyCustomerAddressStateContainValuesOnceTest.xml b/app/code/Magento/Customer/Test/Mftf/Test/AdminVerifyCustomerAddressStateContainValuesOnceTest.xml index 2aa85f8c966a9..785ee1a02abe1 100644 --- a/app/code/Magento/Customer/Test/Mftf/Test/AdminVerifyCustomerAddressStateContainValuesOnceTest.xml +++ b/app/code/Magento/Customer/Test/Mftf/Test/AdminVerifyCustomerAddressStateContainValuesOnceTest.xml @@ -31,9 +31,7 @@ <actionGroup ref="AdminClearCustomersFiltersActionGroup" stepKey="clearFilters"/> <actionGroup ref="AdminLogoutActionGroup" stepKey="logout"/> </after> - - <!-- Go to Customers > All Customers.--> - <amOnPage url="{{AdminCustomerPage.url}}" stepKey="openCustomersGridPage"/> + <actionGroup ref="AdminOpenCustomersGridActionGroup" stepKey="openCustomersGridPage"/> <!--Select created customer, Click Edit mode--> <actionGroup ref="OpenEditCustomerFromAdminActionGroup" stepKey="openEditCustomerPageWithAddresses"> diff --git a/app/code/Magento/Customer/Test/Mftf/Test/AllowedCountriesRestrictionApplyOnBackendTest.xml b/app/code/Magento/Customer/Test/Mftf/Test/AllowedCountriesRestrictionApplyOnBackendTest.xml index 781d721fd5132..6a157c6312530 100644 --- a/app/code/Magento/Customer/Test/Mftf/Test/AllowedCountriesRestrictionApplyOnBackendTest.xml +++ b/app/code/Magento/Customer/Test/Mftf/Test/AllowedCountriesRestrictionApplyOnBackendTest.xml @@ -116,8 +116,7 @@ <waitForPageLoad stepKey="waitForCustomersPage"/> <!--Go to Customers grid and check that filter countries amount is the same as initial allowed countries amount--> <comment userInput="Go to Customers grid and check that filter countries amount is the same as initial allowed countries amount" stepKey="compareCountriesAmount"/> - <amOnPage url="{{AdminCustomerPage.url}}" stepKey="goToCustomersGrid"/> - <waitForPageLoad stepKey="waitForCustomersGrid"/> + <actionGroup ref="AdminOpenCustomersGridActionGroup" stepKey="goToCustomersGrid"/> <click selector="{{AdminDataGridHeaderSection.filters}}" stepKey="openFiltersSectionOnCustomersGrid"/> <executeJS function="var len = document.querySelectorAll('{{AdminCustomerFiltersSection.countryOptions}}').length; return len-1;" stepKey="countriesAmount2"/> <assertEquals stepKey="assertCountryAmounts"> diff --git a/app/code/Magento/Customer/Test/Mftf/Test/SearchByEmailInCustomerGridTest.xml b/app/code/Magento/Customer/Test/Mftf/Test/SearchByEmailInCustomerGridTest.xml index d4351c8bcdc84..b2a78686cdad5 100644 --- a/app/code/Magento/Customer/Test/Mftf/Test/SearchByEmailInCustomerGridTest.xml +++ b/app/code/Magento/Customer/Test/Mftf/Test/SearchByEmailInCustomerGridTest.xml @@ -26,12 +26,12 @@ <after> <deleteData createDataKey="createFirstCustomer" stepKey="deleteFirstCustomer"/> <deleteData createDataKey="createSecondCustomer" stepKey="deleteSecondCustomer"/> - <amOnPage url="{{AdminCustomerPage.url}}" stepKey="openCustomersGridPage"/> + <actionGroup ref="AdminOpenCustomersGridActionGroup" stepKey="openCustomersGridPage"/> <actionGroup ref="AdminResetFilterInCustomerAddressGrid" stepKey="clearCustomerGridFilter"/> <actionGroup ref="AdminLogoutActionGroup" stepKey="logout"/> </after> <!--Step 1: Go to Customers > All Customers--> - <amOnPage url="{{AdminCustomerPage.url}}" stepKey="openCustomersGridPage"/> + <actionGroup ref="AdminOpenCustomersGridActionGroup" stepKey="openCustomersGridPage"/> <!--Step 2: On Customers grid page search customer by keyword--> <actionGroup ref="SearchAdminDataGridByKeywordActionGroup" stepKey="searchCustomer"> <argument name="keyword" value="$$createSecondCustomer.email$$"/> diff --git a/app/code/Magento/Sales/Test/Mftf/Test/CreateOrderFromEditCustomerPageTest.xml b/app/code/Magento/Sales/Test/Mftf/Test/CreateOrderFromEditCustomerPageTest.xml index ca705405809bd..77b119dd583de 100644 --- a/app/code/Magento/Sales/Test/Mftf/Test/CreateOrderFromEditCustomerPageTest.xml +++ b/app/code/Magento/Sales/Test/Mftf/Test/CreateOrderFromEditCustomerPageTest.xml @@ -91,7 +91,7 @@ <deleteData createDataKey="createConfigProduct" stepKey="deleteConfigurableProduct"/> <deleteData createDataKey="createConfigProductAttribute" stepKey="deleteProductAttribute"/> <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> - <amOnPage url="{{AdminCustomerPage.url}}" stepKey="openCustomerIndexPage"/> + <actionGroup ref="AdminOpenCustomersGridActionGroup" stepKey="openCustomerIndexPage"/> <actionGroup ref="ClearFiltersAdminDataGridActionGroup" stepKey="clearCustomerGridFilter"/> <actionGroup ref="AdminLogoutActionGroup" stepKey="logout"/> diff --git a/app/code/Magento/Sales/Test/Mftf/Test/MoveConfigurableProductsInComparedOnOrderPageTest.xml b/app/code/Magento/Sales/Test/Mftf/Test/MoveConfigurableProductsInComparedOnOrderPageTest.xml index c3058ca6ede87..bace51cea17d5 100644 --- a/app/code/Magento/Sales/Test/Mftf/Test/MoveConfigurableProductsInComparedOnOrderPageTest.xml +++ b/app/code/Magento/Sales/Test/Mftf/Test/MoveConfigurableProductsInComparedOnOrderPageTest.xml @@ -132,9 +132,7 @@ <!-- Login as admin --> <actionGroup ref="AdminLoginActionGroup" stepKey="loginAsAdmin"/> - <!-- Open Customers -> All Customers --> - <amOnPage url="{{AdminCustomerPage.url}}" stepKey="openCustomersGridPage"/> - <waitForPageLoad stepKey="waitForCustomerPageLoad"/> + <actionGroup ref="AdminOpenCustomersGridActionGroup" stepKey="openCustomersGridPage"/> <actionGroup ref="OpenEditCustomerFromAdminActionGroup" stepKey="openEditCustomerPage"> <argument name="customer" value="$$createCustomer$$"/> </actionGroup> diff --git a/app/code/Magento/Sales/Test/Mftf/Test/MoveSimpleProductsInComparedOnOrderPageTest.xml b/app/code/Magento/Sales/Test/Mftf/Test/MoveSimpleProductsInComparedOnOrderPageTest.xml index 176fb05bc74b3..00e401941036e 100644 --- a/app/code/Magento/Sales/Test/Mftf/Test/MoveSimpleProductsInComparedOnOrderPageTest.xml +++ b/app/code/Magento/Sales/Test/Mftf/Test/MoveSimpleProductsInComparedOnOrderPageTest.xml @@ -70,9 +70,7 @@ <!-- Login as admin --> <actionGroup ref="AdminLoginActionGroup" stepKey="loginAsAdmin"/> - <!-- Open Customers -> All Customers --> - <amOnPage url="{{AdminCustomerPage.url}}" stepKey="openCustomersGridPage"/> - <waitForPageLoad stepKey="waitForCustomerPageLoad"/> + <actionGroup ref="AdminOpenCustomersGridActionGroup" stepKey="openCustomersGridPage"/> <actionGroup ref="OpenEditCustomerFromAdminActionGroup" stepKey="openEditCustomerPage"> <argument name="customer" value="$$createCustomer$$"/> </actionGroup> diff --git a/app/code/Magento/Security/Test/Mftf/Test/AdminNavigateWhileUserExpiredTest.xml b/app/code/Magento/Security/Test/Mftf/Test/AdminNavigateWhileUserExpiredTest.xml index c1a951afd87ec..dc88ad9d2cbf1 100644 --- a/app/code/Magento/Security/Test/Mftf/Test/AdminNavigateWhileUserExpiredTest.xml +++ b/app/code/Magento/Security/Test/Mftf/Test/AdminNavigateWhileUserExpiredTest.xml @@ -46,7 +46,7 @@ </actionGroup> <actionGroup ref="AssertAdminDashboardPageIsVisibleActionGroup" stepKey="seeDashboardPage"/> <wait time="120" stepKey="waitForUserToExpire"/> - <amOnPage url="{{AdminCustomerPage.url}}" stepKey="navigateToCustomers"/> + <actionGroup ref="AdminOpenCustomersGridActionGroup" stepKey="navigateToCustomers"/> <!-- Confirm that user is logged out --> <seeInCurrentUrl url="{{AdminLoginPage.url}}" stepKey="seeAdminLoginUrl"/> From b2139152bcdb50a3c4972c9c84f5af8b57b463e5 Mon Sep 17 00:00:00 2001 From: "taras.gamanov" <engcom-vendorworker-hotel@adobe.com> Date: Thu, 17 Sep 2020 18:05:52 +0300 Subject: [PATCH 043/139] Integration test has been added --- .../Adminhtml/Order/AddToPackageTest.php | 71 +++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 dev/tests/integration/testsuite/Magento/Shipping/Block/Adminhtml/Order/AddToPackageTest.php diff --git a/dev/tests/integration/testsuite/Magento/Shipping/Block/Adminhtml/Order/AddToPackageTest.php b/dev/tests/integration/testsuite/Magento/Shipping/Block/Adminhtml/Order/AddToPackageTest.php new file mode 100644 index 0000000000000..0455181d42b00 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Shipping/Block/Adminhtml/Order/AddToPackageTest.php @@ -0,0 +1,71 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Shipping\Block\Adminhtml\Order; + +use Magento\Backend\Block\Template; +use Magento\Framework\ObjectManagerInterface; +use Magento\Framework\Registry; +use Magento\Sales\Api\Data\OrderInterfaceFactory; +use Magento\Sales\Api\Data\ShipmentTrackInterface; +use Magento\TestFramework\Helper\Bootstrap; +use PHPUnit\Framework\TestCase; + +/** + * Class verifies packaging popup. + * + * @magentoAppArea adminhtml + */ +class AddToPackageTest extends TestCase +{ + /** @var ObjectManagerInterface */ + private $objectManager; + + /** @var Registry */ + private $registry; + /** + * @var OrderInterfaceFactory|mixed + */ + private $orderFactory; + + protected function setUp(): void + { + $this->objectManager = Bootstrap::getObjectManager(); + $this->registry = $this->objectManager->get(Registry::class); + $this->orderFactory = $this->objectManager->get(OrderInterfaceFactory::class); + } + + /** + * Test that Packaging popup renders + * + * @magentoDataFixture Magento/GraphQl/Sales/_files/customer_order_with_ups_shipping.php + */ + public function testGetCommentsHtml() + { + /** @var Template $block */ + $block = $this->objectManager->get(Packaging::class); + + $order = $this->orderFactory->create()->loadByIncrementId('100000001'); + + /** @var ShipmentTrackInterface $track */ + $shipment = $order->getShipmentsCollection()->getFirstItem(); + + $this->registry->register('current_shipment', $shipment); + + $block->setTemplate('Magento_Shipping::order/packaging/popup.phtml'); + $html = $block->toHtml(); + $expectedNeedle = "packaging.setItemQtyCallback(function(itemId){ + var item = $$('[name=\"shipment[items]['+itemId+']\"]')[0], + itemTitle = $('order_item_' + itemId + '_title'); + if (!itemTitle && !item) { + return 0; + } + if (item && !isNaN(item.value)) { + return item.value; + } + });"; + $this->assertStringContainsString($expectedNeedle, $html); + } +} From fbb40c25f05d6fdfea18087772e381805506b861 Mon Sep 17 00:00:00 2001 From: ogorkun <ogorkun@adobe.com> Date: Thu, 17 Sep 2020 10:39:53 -0500 Subject: [PATCH 044/139] MC-34385: Filter fields allowing HTML --- app/code/Magento/Cms/Model/BlockRepository.php | 1 + 1 file changed, 1 insertion(+) diff --git a/app/code/Magento/Cms/Model/BlockRepository.php b/app/code/Magento/Cms/Model/BlockRepository.php index c26e2d809d996..ef57f8ca7b849 100644 --- a/app/code/Magento/Cms/Model/BlockRepository.php +++ b/app/code/Magento/Cms/Model/BlockRepository.php @@ -217,6 +217,7 @@ public function deleteById($blockId) */ private function getCollectionProcessor() { + //phpcs:disable Magento2.PHP.LiteralNamespaces if (!$this->collectionProcessor) { $this->collectionProcessor = \Magento\Framework\App\ObjectManager::getInstance()->get( 'Magento\Cms\Model\Api\SearchCriteria\BlockCollectionProcessor' From fee7c0cf8b18a1fc2d4ef6b8f127189efff77565 Mon Sep 17 00:00:00 2001 From: "taras.gamanov" <engcom-vendorworker-hotel@adobe.com> Date: Tue, 22 Sep 2020 15:44:30 +0300 Subject: [PATCH 045/139] Code refactoring, fixture has been updated. --- .../Adminhtml/Order/AddToPackageTest.php | 39 ++++++++++++--- .../_files/shipping_with_carrier_data.php | 49 +++++++++++++++++++ .../shipping_with_carrier_data_rollback.php | 9 ++++ 3 files changed, 89 insertions(+), 8 deletions(-) create mode 100644 dev/tests/integration/testsuite/Magento/Shipping/_files/shipping_with_carrier_data.php create mode 100644 dev/tests/integration/testsuite/Magento/Shipping/_files/shipping_with_carrier_data_rollback.php diff --git a/dev/tests/integration/testsuite/Magento/Shipping/Block/Adminhtml/Order/AddToPackageTest.php b/dev/tests/integration/testsuite/Magento/Shipping/Block/Adminhtml/Order/AddToPackageTest.php index 0455181d42b00..fbbc6ef25cc09 100644 --- a/dev/tests/integration/testsuite/Magento/Shipping/Block/Adminhtml/Order/AddToPackageTest.php +++ b/dev/tests/integration/testsuite/Magento/Shipping/Block/Adminhtml/Order/AddToPackageTest.php @@ -6,12 +6,15 @@ namespace Magento\Shipping\Block\Adminhtml\Order; use Magento\Backend\Block\Template; +use Magento\Framework\Api\SearchCriteria; +use Magento\Framework\Api\SearchCriteriaBuilder; use Magento\Framework\ObjectManagerInterface; use Magento\Framework\Registry; -use Magento\Sales\Api\Data\OrderInterfaceFactory; +use Magento\Sales\Api\Data\OrderInterface; use Magento\Sales\Api\Data\ShipmentTrackInterface; use Magento\TestFramework\Helper\Bootstrap; use PHPUnit\Framework\TestCase; +use Magento\Sales\Api\OrderRepositoryInterface; /** * Class verifies packaging popup. @@ -20,34 +23,54 @@ */ class AddToPackageTest extends TestCase { + /** + * @var OrderRepositoryInterface + */ + private $orderRepository; + /** @var ObjectManagerInterface */ private $objectManager; /** @var Registry */ private $registry; - /** - * @var OrderInterfaceFactory|mixed - */ - private $orderFactory; protected function setUp(): void { $this->objectManager = Bootstrap::getObjectManager(); $this->registry = $this->objectManager->get(Registry::class); - $this->orderFactory = $this->objectManager->get(OrderInterfaceFactory::class); + $this->orderRepository = $this->objectManager->get(OrderRepositoryInterface::class); + } + + /** + * Loads order entity by provided order increment ID. + * + * @param string $incrementId + * @return OrderInterface + */ + private function getOrderByIncrementId(string $incrementId) : OrderInterface + { + /** @var SearchCriteria $searchCriteria */ + $searchCriteria = $this->objectManager->get(SearchCriteriaBuilder::class) + ->addFilter('increment_id', $incrementId) + ->create(); + + $items = $this->orderRepository->getList($searchCriteria) + ->getItems(); + + return array_pop($items); } /** * Test that Packaging popup renders * - * @magentoDataFixture Magento/GraphQl/Sales/_files/customer_order_with_ups_shipping.php + * @magentoDataFixture Magento/Shipping/_files/shipping_with_carrier_data.php */ public function testGetCommentsHtml() { /** @var Template $block */ $block = $this->objectManager->get(Packaging::class); - $order = $this->orderFactory->create()->loadByIncrementId('100000001'); + $order = $this->getOrderByIncrementId('100000001'); /** @var ShipmentTrackInterface $track */ $shipment = $order->getShipmentsCollection()->getFirstItem(); diff --git a/dev/tests/integration/testsuite/Magento/Shipping/_files/shipping_with_carrier_data.php b/dev/tests/integration/testsuite/Magento/Shipping/_files/shipping_with_carrier_data.php new file mode 100644 index 0000000000000..736487ac5c006 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Shipping/_files/shipping_with_carrier_data.php @@ -0,0 +1,49 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\Sales\Api\Data\OrderInterfaceFactory; +use Magento\Sales\Api\OrderRepositoryInterface; +use Magento\Sales\Model\Order; +use Magento\Sales\Model\Order\ShipmentFactory; +use Magento\TestFramework\Helper\Bootstrap; +use Magento\TestFramework\Workaround\Override\Fixture\Resolver; +use Magento\Framework\DB\Transaction; + +Resolver::getInstance()->requireDataFixture('Magento/Sales/_files/order_with_customer.php'); + +$objectManager = Bootstrap::getObjectManager(); +/** @var Transaction $transaction */ +$transaction = $objectManager->get(Transaction::class); +/** @var ProductRepositoryInterface $productRepository */ +$productRepository = $objectManager->create(ProductRepositoryInterface::class); +$product = $productRepository->get('simple'); +/** @var Order $order */ +$order = $objectManager->get(OrderInterfaceFactory::class)->create()->loadByIncrementId('100000001'); +$order->setShippingDescription('UPS Next Day Air') + ->setShippingMethod('ups_11') + ->setShippingAmount(0) + ->setCouponCode('1234567890') + ->setDiscountDescription('1234567890'); + +/** @var OrderRepositoryInterface $orderRepository */ +$orderRepository = $objectManager->create(OrderRepositoryInterface::class); +$orderRepository->save($order); + +$shipmentItems = []; +foreach ($order->getItems() as $orderItem) { + $shipmentItems[$orderItem->getId()] = $orderItem->getQtyOrdered(); +} +$tracking = [ + 'carrier_code' => 'ups', + 'title' => 'United Parcel Service', + 'number' => '987654321' +]; + +$shipment = $objectManager->get(ShipmentFactory::class)->create($order, $shipmentItems, [$tracking]); +$shipment->register(); +$transaction->addObject($shipment)->addObject($order)->save(); diff --git a/dev/tests/integration/testsuite/Magento/Shipping/_files/shipping_with_carrier_data_rollback.php b/dev/tests/integration/testsuite/Magento/Shipping/_files/shipping_with_carrier_data_rollback.php new file mode 100644 index 0000000000000..bbb90e0326aec --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Shipping/_files/shipping_with_carrier_data_rollback.php @@ -0,0 +1,9 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +use Magento\TestFramework\Workaround\Override\Fixture\Resolver; + +Resolver::getInstance()->requireDataFixture('Magento/Sales/_files/order_with_customer_rollback.php'); From 7b93b53a703415f706bf16cfeb4a3abf36756698 Mon Sep 17 00:00:00 2001 From: Oleh Usik <o.usik@atwix.com> Date: Wed, 23 Sep 2020 21:37:13 +0300 Subject: [PATCH 046/139] depricaded action group --- .../Test/Mftf/ActionGroup/NavigateCustomerActionGroup.xml | 2 +- .../Mftf/Test/AdminChangeSingleCustomerGroupViaGridTest.xml | 4 ++-- .../Customer/Test/Mftf/Test/AdminCreateCustomerTest.xml | 3 ++- .../Customer/Test/Mftf/Test/AdminGridSearchSelectAllTest.xml | 2 +- .../Customer/Test/Mftf/Test/AdminGridSelectAllOnPageTest.xml | 2 +- 5 files changed, 7 insertions(+), 6 deletions(-) diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/NavigateCustomerActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/NavigateCustomerActionGroup.xml index 73748b9a6bad6..ca6a1498c4215 100644 --- a/app/code/Magento/Customer/Test/Mftf/ActionGroup/NavigateCustomerActionGroup.xml +++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/NavigateCustomerActionGroup.xml @@ -8,7 +8,7 @@ <actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> - <actionGroup name="NavigateToAllCustomerPage"> + <actionGroup name="NavigateToAllCustomerPage" deprecated="Use AdminOpenCustomersGridActionGroup instead."> <annotations> <description>Goes to the Admin Customers grid page.</description> </annotations> diff --git a/app/code/Magento/Customer/Test/Mftf/Test/AdminChangeSingleCustomerGroupViaGridTest.xml b/app/code/Magento/Customer/Test/Mftf/Test/AdminChangeSingleCustomerGroupViaGridTest.xml index a7383af2d7eea..5833bf07aeae2 100644 --- a/app/code/Magento/Customer/Test/Mftf/Test/AdminChangeSingleCustomerGroupViaGridTest.xml +++ b/app/code/Magento/Customer/Test/Mftf/Test/AdminChangeSingleCustomerGroupViaGridTest.xml @@ -29,12 +29,12 @@ <!--Delete created data--> <deleteData createDataKey="createCustomer" stepKey="deleteCustomer"/> <deleteData createDataKey="createCustomerGroup" stepKey="deleteCustomerGroup"/> - <actionGroup ref="NavigateToAllCustomerPage" stepKey="navigateToCustomersPage"/> + <actionGroup ref="AdminOpenCustomersGridActionGroup" stepKey="navigateToCustomersPage"/> <actionGroup ref="ClearFiltersAdminDataGridActionGroup" stepKey="clearCustomersGridFilter"/> <actionGroup ref="AdminLogoutActionGroup" stepKey="logoutFromAdmin"/> </after> - <actionGroup ref="NavigateToAllCustomerPage" stepKey="navigateToCustomersPage"/> + <actionGroup ref="AdminOpenCustomersGridActionGroup" stepKey="navigateToCustomersPage"/> <actionGroup ref="AdminFilterCustomerGridByEmail" stepKey="filterCustomer"> <argument name="email" value="$$createCustomer.email$$"/> </actionGroup> diff --git a/app/code/Magento/Customer/Test/Mftf/Test/AdminCreateCustomerTest.xml b/app/code/Magento/Customer/Test/Mftf/Test/AdminCreateCustomerTest.xml index d12a89f01cb96..cb003ed837294 100644 --- a/app/code/Magento/Customer/Test/Mftf/Test/AdminCreateCustomerTest.xml +++ b/app/code/Magento/Customer/Test/Mftf/Test/AdminCreateCustomerTest.xml @@ -29,7 +29,8 @@ </after> <actionGroup ref="AdminLoginActionGroup" stepKey="loginAsAdmin1"/> - <actionGroup ref="AdminOpenCustomersGridActionGroup" stepKey="navigateToCustomers"/> + <amOnPage url="{{AdminCustomerPage.url}}" stepKey="navigateToCustomers"/> + <waitForPageLoad stepKey="waitForLoad1"/> <click selector="{{AdminCustomerGridMainActionsSection.addNewCustomer}}" stepKey="clickCreateCustomer"/> <fillField userInput="{{CustomerEntityOne.firstname}}" selector="{{AdminCustomerAccountInformationSection.firstName}}" stepKey="fillFirstName"/> <fillField userInput="{{CustomerEntityOne.lastname}}" selector="{{AdminCustomerAccountInformationSection.lastName}}" stepKey="fillLastName"/> diff --git a/app/code/Magento/Customer/Test/Mftf/Test/AdminGridSearchSelectAllTest.xml b/app/code/Magento/Customer/Test/Mftf/Test/AdminGridSearchSelectAllTest.xml index 64e9f6d10bdb3..7f1b1dfee7ce0 100644 --- a/app/code/Magento/Customer/Test/Mftf/Test/AdminGridSearchSelectAllTest.xml +++ b/app/code/Magento/Customer/Test/Mftf/Test/AdminGridSearchSelectAllTest.xml @@ -29,7 +29,7 @@ <deleteData createDataKey="secondCustomer" stepKey="deleteSecondCustomer"/> <actionGroup ref="AdminLogoutActionGroup" stepKey="logout"/> </after> - <amOnPage url="{{AdminCustomerPage.url}}" stepKey="openCustomerPage"/> + <actionGroup ref="AdminOpenCustomersGridActionGroup" stepKey="openCustomerPage"/> <!-- search Admin Data Grid By Keyword --> <conditionalClick selector="{{AdminDataGridHeaderSection.clearFilters}}" dependentSelector="{{AdminDataGridHeaderSection.clearFilters}}" visible="true" stepKey="clickClearFilters"/> <fillField selector="{{AdminDataGridHeaderSection.search}}" userInput="$$secondCustomer.email$$" stepKey="fillKeywordSearchFieldWithSecondCustomerEmail"/> diff --git a/app/code/Magento/Customer/Test/Mftf/Test/AdminGridSelectAllOnPageTest.xml b/app/code/Magento/Customer/Test/Mftf/Test/AdminGridSelectAllOnPageTest.xml index bfc49fd476dd0..aa7cdbb9207b9 100644 --- a/app/code/Magento/Customer/Test/Mftf/Test/AdminGridSelectAllOnPageTest.xml +++ b/app/code/Magento/Customer/Test/Mftf/Test/AdminGridSelectAllOnPageTest.xml @@ -30,7 +30,7 @@ <deleteData createDataKey="thirdCustomer" stepKey="deleteThirdCustomer"/> <actionGroup ref="AdminLogoutActionGroup" stepKey="logout"/> </after> - <amOnPage url="{{AdminCustomerPage.url}}" stepKey="openCustomerPage"/> + <actionGroup ref="AdminOpenCustomersGridActionGroup" stepKey="openCustomerPage"/> <!-- Select all from dropdown --> <actionGroup ref="AdminGridSelectAllActionGroup" stepKey="selectAllCustomers"/> <!-- Deselect third customer --> From eac6ba25636b525d909f5af43648fb101fab3d13 Mon Sep 17 00:00:00 2001 From: Stanislav Ilnytskyi <stailx1@gmail.com> Date: Sat, 26 Sep 2020 09:42:19 +0200 Subject: [PATCH 047/139] remove spaces caused patching problem in Product/View It was impossible to apply patch to this file because whitespaces --- app/code/Magento/Catalog/Helper/Product/View.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/Magento/Catalog/Helper/Product/View.php b/app/code/Magento/Catalog/Helper/Product/View.php index cf5b15cadc997..95698d382f09e 100644 --- a/app/code/Magento/Catalog/Helper/Product/View.php +++ b/app/code/Magento/Catalog/Helper/Product/View.php @@ -193,7 +193,7 @@ public function initProductLayout(ResultPage $resultPage, $product, $params = nu $resultPage->addPageLayoutHandles(['id' => $product->getId(), 'sku' => $urlSafeSku], $handle); } } - + $resultPage->addPageLayoutHandles(['type' => $product->getTypeId()], null, false); $resultPage->addPageLayoutHandles(['id' => $product->getId(), 'sku' => $urlSafeSku]); From 6a58f7ef0cf75b830f7a7c462d8fa7d010de317e Mon Sep 17 00:00:00 2001 From: Viktor Kopin <viktor.kopin@transoftgroup.com> Date: Mon, 28 Sep 2020 16:44:20 +0300 Subject: [PATCH 048/139] magento/magento2#29478: refactoring, test coverage --- .../Console/Command/IndexerReindexCommand.php | 79 ++----- app/code/Magento/Indexer/Model/Processor.php | 79 ++----- .../Model/Processor/MakeSharedIndexValid.php | 95 +++++++++ .../Command/IndexerReindexCommandTest.php | 26 ++- .../Indexer/Test/Unit/Model/ProcessorTest.php | 199 +++++++++++++++++- 5 files changed, 333 insertions(+), 145 deletions(-) create mode 100644 app/code/Magento/Indexer/Model/Processor/MakeSharedIndexValid.php diff --git a/app/code/Magento/Indexer/Console/Command/IndexerReindexCommand.php b/app/code/Magento/Indexer/Console/Command/IndexerReindexCommand.php index 775f585519947..67cde2e1d7d58 100644 --- a/app/code/Magento/Indexer/Console/Command/IndexerReindexCommand.php +++ b/app/code/Magento/Indexer/Console/Command/IndexerReindexCommand.php @@ -6,6 +6,7 @@ namespace Magento\Indexer\Console\Command; +use Magento\Framework\App\ObjectManager; use Magento\Framework\App\ObjectManagerFactory; use Magento\Framework\Console\Cli; use Magento\Framework\Exception\LocalizedException; @@ -14,11 +15,13 @@ use Magento\Framework\Indexer\IndexerInterface; use Magento\Framework\Indexer\IndexerRegistry; use Magento\Framework\Indexer\StateInterface; +use Magento\Indexer\Model\Processor\MakeSharedIndexValid; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; /** * Command to run indexers + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class IndexerReindexCommand extends AbstractIndexerManageCommand { @@ -42,18 +45,26 @@ class IndexerReindexCommand extends AbstractIndexerManageCommand */ private $dependencyInfoProvider; + /** + * @var MakeSharedIndexValid|null + */ + private $makeSharedValid; + /** * @param ObjectManagerFactory $objectManagerFactory * @param IndexerRegistry|null $indexerRegistry * @param DependencyInfoProvider|null $dependencyInfoProvider + * @param MakeSharedIndexValid|null $makeSharedValid */ public function __construct( ObjectManagerFactory $objectManagerFactory, IndexerRegistry $indexerRegistry = null, - DependencyInfoProvider $dependencyInfoProvider = null + DependencyInfoProvider $dependencyInfoProvider = null, + MakeSharedIndexValid $makeSharedValid = null ) { $this->indexerRegistry = $indexerRegistry; $this->dependencyInfoProvider = $dependencyInfoProvider; + $this->makeSharedValid = $makeSharedValid ?: ObjectManager::getInstance()->get(MakeSharedIndexValid::class); parent::__construct($objectManagerFactory); } @@ -88,8 +99,8 @@ protected function execute(InputInterface $input, OutputInterface $output) // Skip indexers having shared index that was already complete if (!in_array($sharedIndex, $this->sharedIndexesComplete)) { $indexer->reindexAll(); - if ($sharedIndex) { - $this->validateSharedIndex($sharedIndex); + if (!empty($sharedIndex) && $this->makeSharedValid->execute($sharedIndex)) { + $this->sharedIndexesComplete[] = $sharedIndex; } } $resultTime = microtime(true) - $startTime; @@ -214,54 +225,6 @@ private function validateIndexerStatus(IndexerInterface $indexer) } } - /** - * Get indexer ids that have common shared index - * - * @param string $sharedIndex - * @return array - */ - private function getIndexerIdsBySharedIndex($sharedIndex) - { - $indexers = $this->getConfig()->getIndexers(); - $result = []; - foreach ($indexers as $indexerConfig) { - if ($indexerConfig['shared_index'] == $sharedIndex) { - $result[] = $indexerConfig['indexer_id']; - } - } - return $result; - } - - /** - * Validate indexers by shared index ID - * - * @param string $sharedIndex - * @return $this - */ - private function validateSharedIndex($sharedIndex) - { - if (empty($sharedIndex)) { - throw new \InvalidArgumentException( - 'The sharedIndex is an invalid shared index identifier. Verify the identifier and try again.' - ); - } - $indexerIds = $this->getIndexerIdsBySharedIndex($sharedIndex); - if (empty($indexerIds)) { - return $this; - } - foreach ($indexerIds as $indexerId) { - $indexer = $this->getIndexerRegistry()->get($indexerId); - /** @var \Magento\Indexer\Model\Indexer\State $state */ - $state = $indexer->getState(); - $state->setStatus(StateInterface::STATUS_WORKING); - $state->save(); - $state->setStatus(StateInterface::STATUS_VALID); - $state->save(); - } - $this->sharedIndexesComplete[] = $sharedIndex; - return $this; - } - /** * Get config * @@ -276,20 +239,6 @@ private function getConfig() return $this->config; } - /** - * Get indexer registry - * - * @return IndexerRegistry - * @deprecated 100.2.0 - */ - private function getIndexerRegistry() - { - if (!$this->indexerRegistry) { - $this->indexerRegistry = $this->getObjectManager()->get(IndexerRegistry::class); - } - return $this->indexerRegistry; - } - /** * Get dependency info provider * diff --git a/app/code/Magento/Indexer/Model/Processor.php b/app/code/Magento/Indexer/Model/Processor.php index 01f530488fbe7..78b8fa070b155 100644 --- a/app/code/Magento/Indexer/Model/Processor.php +++ b/app/code/Magento/Indexer/Model/Processor.php @@ -5,10 +5,12 @@ */ namespace Magento\Indexer\Model; +use Magento\Framework\App\ObjectManager; use Magento\Framework\Indexer\ConfigInterface; use Magento\Framework\Indexer\IndexerInterface; use Magento\Framework\Indexer\IndexerInterfaceFactory; -use Magento\Framework\Indexer\StateInterface; +use Magento\Framework\Mview\ProcessorInterface; +use Magento\Indexer\Model\Processor\MakeSharedIndexValid; /** * Indexer processor @@ -36,26 +38,34 @@ class Processor protected $indexersFactory; /** - * @var \Magento\Framework\Mview\ProcessorInterface + * @var ProcessorInterface */ protected $mviewProcessor; + /** + * @var MakeSharedIndexValid + */ + protected $makeSharedValid; + /** * @param ConfigInterface $config * @param IndexerInterfaceFactory $indexerFactory * @param Indexer\CollectionFactory $indexersFactory - * @param \Magento\Framework\Mview\ProcessorInterface $mviewProcessor + * @param ProcessorInterface $mviewProcessor + * @param MakeSharedIndexValid|null $makeSharedValid */ public function __construct( ConfigInterface $config, IndexerInterfaceFactory $indexerFactory, Indexer\CollectionFactory $indexersFactory, - \Magento\Framework\Mview\ProcessorInterface $mviewProcessor + ProcessorInterface $mviewProcessor, + MakeSharedIndexValid $makeSharedValid = null ) { $this->config = $config; $this->indexerFactory = $indexerFactory; $this->indexersFactory = $indexersFactory; $this->mviewProcessor = $mviewProcessor; + $this->makeSharedValid = $makeSharedValid ?: ObjectManager::getInstance()->get(MakeSharedIndexValid::class); } /** @@ -70,7 +80,6 @@ public function reindexAllInvalid() $indexer = $this->indexerFactory->create(); $indexer->load($indexerId); $indexerConfig = $this->config->getIndexer($indexerId); - $sharedIndex = $indexerConfig['shared_index']; if ($indexer->isInvalid()) { // Skip indexers having shared index that was already complete @@ -78,70 +87,14 @@ public function reindexAllInvalid() if (!in_array($sharedIndex, $this->sharedIndexesComplete)) { $indexer->reindexAll(); - if ($sharedIndex) { - $this->validateSharedIndex($sharedIndex); + if (!empty($sharedIndex) && $this->makeSharedValid->execute($sharedIndex)) { + $this->sharedIndexesComplete[] = $sharedIndex; } } } } } - /** - * Get indexer ids that have common shared index - * - * @param string $sharedIndex - * @return array - */ - private function getIndexerIdsBySharedIndex(string $sharedIndex): array - { - $indexers = $this->config->getIndexers(); - - $result = []; - foreach ($indexers as $indexerConfig) { - if ($indexerConfig['shared_index'] == $sharedIndex) { - $result[] = $indexerConfig['indexer_id']; - } - } - - return $result; - } - - /** - * Validate indexers by shared index ID - * - * @param string $sharedIndex - * @return $this - */ - private function validateSharedIndex(string $sharedIndex): self - { - if (empty($sharedIndex)) { - throw new \InvalidArgumentException( - 'The sharedIndex is an invalid shared index identifier. Verify the identifier and try again.' - ); - } - - $indexerIds = $this->getIndexerIdsBySharedIndex($sharedIndex); - if (empty($indexerIds)) { - return $this; - } - - foreach ($indexerIds as $indexerId) { - /** @var \Magento\Indexer\Model\Indexer $indexer */ - $indexer = $this->indexerFactory->create(); - $indexer->load($indexerId); - /** @var \Magento\Indexer\Model\Indexer\State $state */ - $state = $indexer->getState(); - $state->setStatus(StateInterface::STATUS_WORKING); - $state->save(); - $state->setStatus(StateInterface::STATUS_VALID); - $state->save(); - } - - $this->sharedIndexesComplete[] = $sharedIndex; - - return $this; - } - /** * Regenerate indexes for all indexers * diff --git a/app/code/Magento/Indexer/Model/Processor/MakeSharedIndexValid.php b/app/code/Magento/Indexer/Model/Processor/MakeSharedIndexValid.php new file mode 100644 index 0000000000000..338891589bf33 --- /dev/null +++ b/app/code/Magento/Indexer/Model/Processor/MakeSharedIndexValid.php @@ -0,0 +1,95 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\Indexer\Model\Processor; + +use Magento\Framework\Indexer\ConfigInterface; +use Magento\Framework\Indexer\IndexerRegistry; +use Magento\Framework\Indexer\StateInterface; +use Magento\Indexer\Model\Indexer\State; + +/** + * Class processor makes indexers valid by shared index ID + */ +class MakeSharedIndexValid +{ + /** + * @var ConfigInterface + */ + private $config; + + /** + * @var IndexerRegistry + */ + private $indexerRegistry; + + /** + * ValidateSharedIndex constructor. + * + * @param ConfigInterface $config + * @param IndexerRegistry $indexerRegistry + */ + public function __construct(ConfigInterface $config, IndexerRegistry $indexerRegistry) + { + $this->config = $config; + $this->indexerRegistry = $indexerRegistry; + } + + /** + * Validate indexers by shared index ID + * + * @param string $sharedIndex + * @return bool + * @throws \Exception + */ + public function execute(string $sharedIndex): bool + { + if (empty($sharedIndex)) { + throw new \InvalidArgumentException( + "The '{$sharedIndex}' is an invalid shared index identifier. Verify the identifier and try again.", + ); + } + + $indexerIds = $this->getIndexerIdsBySharedIndex($sharedIndex); + if (empty($indexerIds)) { + return false; + } + + foreach ($indexerIds as $indexerId) { + $indexer = $this->indexerRegistry->get($indexerId); + /** @var State $state */ + $state = $indexer->getState(); + $state->setStatus(StateInterface::STATUS_WORKING); + $state->save(); + $state->setStatus(StateInterface::STATUS_VALID); + $state->save(); + } + + return true; + } + + /** + * Get indexer ids that have common shared index + * + * @param string $sharedIndex + * @return array + */ + private function getIndexerIdsBySharedIndex(string $sharedIndex): array + { + $indexers = $this->config->getIndexers(); + + $result = []; + foreach ($indexers as $indexerConfig) { + if ($indexerConfig['shared_index'] == $sharedIndex) { + $result[] = $indexerConfig['indexer_id']; + } + } + + return $result; + } +} diff --git a/app/code/Magento/Indexer/Test/Unit/Console/Command/IndexerReindexCommandTest.php b/app/code/Magento/Indexer/Test/Unit/Console/Command/IndexerReindexCommandTest.php index 6d96841bc3dab..8bdceb92b247b 100644 --- a/app/code/Magento/Indexer/Test/Unit/Console/Command/IndexerReindexCommandTest.php +++ b/app/code/Magento/Indexer/Test/Unit/Console/Command/IndexerReindexCommandTest.php @@ -18,6 +18,7 @@ use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper; use Magento\Indexer\Console\Command\IndexerReindexCommand; use Magento\Indexer\Model\Config; +use Magento\Indexer\Model\Processor\MakeSharedIndexValid; use PHPUnit\Framework\MockObject\MockObject; use Symfony\Component\Console\Tester\CommandTester; @@ -49,6 +50,11 @@ class IndexerReindexCommandTest extends AbstractIndexerCommandCommonSetup */ private $dependencyInfoProviderMock; + /** + * @var MakeSharedIndexValid|MockObject + */ + private $makeSharedValidMock; + /** * @var ObjectManagerHelper */ @@ -64,12 +70,12 @@ protected function setUp(): void $this->indexerRegistryMock = $this->getMockBuilder(IndexerRegistry::class) ->disableOriginalConstructor() ->getMock(); - $this->dependencyInfoProviderMock = $this->objectManagerHelper->getObject( - DependencyInfoProvider::class, - [ - 'config' => $this->configMock, - ] - ); + $this->makeSharedValidMock = $this->getMockBuilder(MakeSharedIndexValid::class) + ->disableOriginalConstructor() + ->getMock(); + $this->dependencyInfoProviderMock = $this->objectManagerHelper->getObject(DependencyInfoProvider::class, [ + 'config' => $this->configMock, + ]); parent::setUp(); } @@ -174,11 +180,17 @@ public function testExecuteWithIndex( $emptyIndexer->method('getState') ->willReturn($this->getStateMock(['setStatus', 'save'])); + $this->makeSharedValidMock = $this->objectManagerHelper->getObject(MakeSharedIndexValid::class, [ + 'config' => $this->configMock, + 'indexerRegistry' => $this->indexerRegistryMock + ]); $this->configureAdminArea(); $this->command = new IndexerReindexCommand( $this->objectManagerFactory, - $this->indexerRegistryMock + $this->indexerRegistryMock, + $this->dependencyInfoProviderMock, + $this->makeSharedValidMock ); $commandTester = new CommandTester($this->command); diff --git a/app/code/Magento/Indexer/Test/Unit/Model/ProcessorTest.php b/app/code/Magento/Indexer/Test/Unit/Model/ProcessorTest.php index 9cc0277997289..9f9b4c2157bb7 100644 --- a/app/code/Magento/Indexer/Test/Unit/Model/ProcessorTest.php +++ b/app/code/Magento/Indexer/Test/Unit/Model/ProcessorTest.php @@ -9,6 +9,7 @@ use Magento\Framework\Indexer\ConfigInterface; use Magento\Framework\Indexer\IndexerInterfaceFactory; +use Magento\Framework\Indexer\IndexerRegistry; use Magento\Framework\Indexer\StateInterface; use Magento\Framework\Mview\ProcessorInterface; use Magento\Indexer\Model\Indexer; @@ -16,6 +17,7 @@ use Magento\Indexer\Model\Indexer\CollectionFactory; use Magento\Indexer\Model\Indexer\State; use Magento\Indexer\Model\Processor; +use Magento\Indexer\Model\Processor\MakeSharedIndexValid; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; @@ -79,20 +81,15 @@ protected function setUp(): void ); } - public function testReindexAllInvalid() + /** + * @return void + */ + public function testReindexAllInvalid(): void { $indexers = ['indexer1' => [], 'indexer2' => []]; $this->configMock->expects($this->once())->method('getIndexers')->willReturn($indexers); - $this->configMock->expects($this->exactly(2)) - ->method('getIndexer') - ->willReturn( - [ - 'shared_index' => null - ] - ); - $state1Mock = $this->createPartialMock(State::class, ['getStatus', '__wakeup']); $state1Mock->expects( $this->once() @@ -129,7 +126,68 @@ public function testReindexAllInvalid() $this->model->reindexAllInvalid(); } - public function testReindexAll() + /** + * @dataProvider sharedIndexDataProvider + * @param array $indexers + * @param array $indexerStates + * @param array $expectedReindexAllCalls + * @param array $executedSharedIndexers + */ + public function testReindexAllInvalidWithSharedIndex( + array $indexers, + array $indexerStates, + array $expectedReindexAllCalls, + array $executedSharedIndexers + ): void { + $this->configMock->expects($this->any())->method('getIndexers')->willReturn($indexers); + $this->configMock + ->method('getIndexer') + ->willReturnMap( + array_map( + function ($elem) { + return [$elem['indexer_id'], $elem]; + }, + $indexers + ) + ); + $indexerMocks = []; + foreach ($indexers as $indexerData) { + $stateMock = $this->createPartialMock(State::class, ['getStatus', '__wakeup']); + $stateMock->expects($this->any()) + ->method('getStatus') + ->willReturn($indexerStates[$indexerData['indexer_id']]); + $indexerMock = $this->createPartialMock(Indexer::class, ['load', 'getState', 'reindexAll']); + $indexerMock->expects($this->any())->method('getState')->willReturn($stateMock); + $indexerMock->expects($expectedReindexAllCalls[$indexerData['indexer_id']])->method('reindexAll'); + + $this->indexerFactoryMock->expects($this->at(count($indexerMocks))) + ->method('create') + ->willReturn($indexerMock); + + $indexerMocks[] = $indexerMock; + } + $indexerRegistryMock = $this->getIndexRegistryMock($executedSharedIndexers); + + $makeSharedValidMock = new MakeSharedIndexValid( + $this->configMock, + $indexerRegistryMock + ); + $model = new Processor( + $this->configMock, + $this->indexerFactoryMock, + $this->indexersFactoryMock, + $this->viewProcessorMock, + $makeSharedValidMock + ); + $model->reindexAllInvalid(); + } + + /** + * Reindex all test + * + * return void + */ + public function testReindexAll(): void { $indexerMock = $this->createMock(Indexer::class); $indexerMock->expects($this->exactly(2))->method('reindexAll'); @@ -142,15 +200,136 @@ public function testReindexAll() $this->model->reindexAll(); } + /** + * Update mview test + * + * @return void + */ public function testUpdateMview() { $this->viewProcessorMock->expects($this->once())->method('update')->with('indexer')->willReturnSelf(); $this->model->updateMview(); } + /** + * Clear change log test + * + * @return void + */ public function testClearChangelog() { $this->viewProcessorMock->expects($this->once())->method('clearChangelog')->with('indexer')->willReturnSelf(); $this->model->clearChangelog(); } + + /** + * @return array + */ + public function sharedIndexDataProvider() + { + return [ + 'Without dependencies' => [ + 'indexers' => [ + 'indexer_1' => [ + 'indexer_id' => 'indexer_1', + 'title' => 'Title_indexer_1', + 'shared_index' => null, + 'dependencies' => [], + ], + 'indexer_2' => [ + 'indexer_id' => 'indexer_2', + 'title' => 'Title_indexer_2', + 'shared_index' => 'with_indexer_3', + 'dependencies' => [], + ], + 'indexer_3' => [ + 'indexer_id' => 'indexer_3', + 'title' => 'Title_indexer_3', + 'shared_index' => 'with_indexer_3', + 'dependencies' => [], + ], + ], + 'indexer_states' => [ + 'indexer_1' => StateInterface::STATUS_INVALID, + 'indexer_2' => StateInterface::STATUS_VALID, + 'indexer_3' => StateInterface::STATUS_VALID, + ], + 'expected_reindex_all_calls' => [ + 'indexer_1' => $this->once(), + 'indexer_2' => $this->never(), + 'indexer_3' => $this->never(), + ], + 'executed_shared_indexers' => [], + ], + 'With dependencies and some indexers is invalid' => [ + 'indexers' => [ + 'indexer_1' => [ + 'indexer_id' => 'indexer_1', + 'title' => 'Title_indexer_1', + 'shared_index' => null, + 'dependencies' => ['indexer_2', 'indexer_3'], + ], + 'indexer_2' => [ + 'indexer_id' => 'indexer_2', + 'title' => 'Title_indexer_2', + 'shared_index' => 'with_indexer_3', + 'dependencies' => [], + ], + 'indexer_3' => [ + 'indexer_id' => 'indexer_3', + 'title' => 'Title_indexer_3', + 'shared_index' => 'with_indexer_3', + 'dependencies' => [], + ], + 'indexer_4' => [ + 'indexer_id' => 'indexer_4', + 'title' => 'Title_indexer_4', + 'shared_index' => null, + 'dependencies' => ['indexer_1'], + ], + ], + 'indexer_states' => [ + 'indexer_1' => StateInterface::STATUS_INVALID, + 'indexer_2' => StateInterface::STATUS_VALID, + 'indexer_3' => StateInterface::STATUS_INVALID, + 'indexer_4' => StateInterface::STATUS_VALID, + ], + 'expected_reindex_all_calls' => [ + 'indexer_1' => $this->once(), + 'indexer_2' => $this->never(), + 'indexer_3' => $this->once(), + 'indexer_4' => $this->never(), + ], + 'executed_shared_indexers' => [['indexer_2'], ['indexer_3']], + ], + ]; + } + + /** + * @param array $executedSharedIndexers + * @return IndexerRegistry|MockObject + */ + private function getIndexRegistryMock(array $executedSharedIndexers) + { + /** @var MockObject|IndexerRegistry $indexerRegistryMock */ + $indexerRegistryMock = $this->getMockBuilder(IndexerRegistry::class) + ->disableOriginalConstructor() + ->getMock(); + $emptyIndexer = $this->createPartialMock(Indexer::class, ['load', 'getState', 'reindexAll']); + /** @var MockObject|StateInterface $state */ + $state = $this->getMockBuilder(StateInterface::class) + ->setMethods(['setStatus', 'save']) + ->disableOriginalConstructor() + ->getMockForAbstractClass(); + $state->method('getStatus') + ->willReturn(StateInterface::STATUS_INVALID); + $emptyIndexer->method('getState')->willReturn($state); + $indexerRegistryMock + ->expects($this->exactly(count($executedSharedIndexers))) + ->method('get') + ->withConsecutive(...$executedSharedIndexers) + ->willReturn($emptyIndexer); + + return $indexerRegistryMock; + } } From f9f3725e1326a375cecbd8fd1c70125d35adeaba Mon Sep 17 00:00:00 2001 From: Pavel Bystritsky <engcom-vendorworker-foxtrot@adobe.com> Date: Fri, 2 Oct 2020 13:56:37 +0300 Subject: [PATCH 049/139] magento/magento2#29196: Avoids endless loop of indexers being marked as invalid. --- .../Console/Command/IndexerReindexCommand.php | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/app/code/Magento/Indexer/Console/Command/IndexerReindexCommand.php b/app/code/Magento/Indexer/Console/Command/IndexerReindexCommand.php index 6abc2b242a9d1..ad286bcd1f81e 100644 --- a/app/code/Magento/Indexer/Console/Command/IndexerReindexCommand.php +++ b/app/code/Magento/Indexer/Console/Command/IndexerReindexCommand.php @@ -64,7 +64,7 @@ public function __construct( ) { $this->indexerRegistry = $indexerRegistry; $this->dependencyInfoProvider = $dependencyInfoProvider; - $this->makeSharedValid = $makeSharedValid ?: ObjectManager::getInstance()->get(MakeSharedIndexValid::class); + $this->makeSharedValid = $makeSharedValid; parent::__construct($objectManagerFactory); } @@ -99,7 +99,7 @@ protected function execute(InputInterface $input, OutputInterface $output) // Skip indexers having shared index that was already complete if (!in_array($sharedIndex, $this->sharedIndexesComplete)) { $indexer->reindexAll(); - if (!empty($sharedIndex) && $this->makeSharedValid->execute($sharedIndex)) { + if (!empty($sharedIndex) && $this->getMakeSharedValid()->execute($sharedIndex)) { $this->sharedIndexesComplete[] = $sharedIndex; } } @@ -253,4 +253,18 @@ private function getDependencyInfoProvider() } return $this->dependencyInfoProvider; } + + /** + * Get MakeSharedIndexValid processor. + * + * @return MakeSharedIndexValid + */ + private function getMakeSharedValid(): MakeSharedIndexValid + { + if (!$this->makeSharedValid) { + $this->makeSharedValid = $this->getObjectManager()->get(MakeSharedIndexValid::class); + } + + return $this->makeSharedValid; + } } From b1de332f0e5823379e2f049a38c10f4f38b412a8 Mon Sep 17 00:00:00 2001 From: Kate Kyzyma <kate@atwix.com> Date: Fri, 2 Oct 2020 14:13:54 +0300 Subject: [PATCH 050/139] Refactoring the test --- .../AdminOpenCmsBlocksGridActionGroup.xml | 19 +++++ ...inPressAddNewCmsBlockButtonActionGroup.xml | 19 +++++ ...dminPressSaveCmsBlockButtonActionGroup.xml | 19 +++++ ...dminSelectCMSBlockStoreViewActionGroup.xml | 17 ++++ ...AssertAdminProperUrlIsShownActionGroup.xml | 21 +++++ .../Test/Mftf/Test/CheckStaticBlocksTest.xml | 78 +++++++++++-------- 6 files changed, 139 insertions(+), 34 deletions(-) create mode 100644 app/code/Magento/Cms/Test/Mftf/ActionGroup/AdminOpenCmsBlocksGridActionGroup.xml create mode 100644 app/code/Magento/Cms/Test/Mftf/ActionGroup/AdminPressAddNewCmsBlockButtonActionGroup.xml create mode 100644 app/code/Magento/Cms/Test/Mftf/ActionGroup/AdminPressSaveCmsBlockButtonActionGroup.xml create mode 100644 app/code/Magento/Cms/Test/Mftf/ActionGroup/AdminSelectCMSBlockStoreViewActionGroup.xml create mode 100644 app/code/Magento/Cms/Test/Mftf/ActionGroup/AssertAdminProperUrlIsShownActionGroup.xml diff --git a/app/code/Magento/Cms/Test/Mftf/ActionGroup/AdminOpenCmsBlocksGridActionGroup.xml b/app/code/Magento/Cms/Test/Mftf/ActionGroup/AdminOpenCmsBlocksGridActionGroup.xml new file mode 100644 index 0000000000000..fca85651f7fda --- /dev/null +++ b/app/code/Magento/Cms/Test/Mftf/ActionGroup/AdminOpenCmsBlocksGridActionGroup.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="UTF-8"?> + <!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + --> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> +<actionGroup name="AdminOpenCmsBlocksGridActionGroup"> + <annotations> + <description>Goes to the Cms Blocks grid page.</description> + </annotations> + + <amOnPage url="{{CmsBlocksPage.url}}" stepKey="navigateToCMSBlocksGrid"/> + <waitForPageLoad stepKey="waitForPageLoad"/> +</actionGroup> +</actionGroups> diff --git a/app/code/Magento/Cms/Test/Mftf/ActionGroup/AdminPressAddNewCmsBlockButtonActionGroup.xml b/app/code/Magento/Cms/Test/Mftf/ActionGroup/AdminPressAddNewCmsBlockButtonActionGroup.xml new file mode 100644 index 0000000000000..17fc1cd7bdc50 --- /dev/null +++ b/app/code/Magento/Cms/Test/Mftf/ActionGroup/AdminPressAddNewCmsBlockButtonActionGroup.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AdminPressAddNewCmsBlockButtonActionGroup"> + <annotations> + <description>Press Add new block button on Cms Blocks gid page</description> + </annotations> + + <click selector="{{BlockPageActionsSection.addNewBlock}}" stepKey="clickOnAddNewBlockButton"/> + <waitForPageLoad stepKey="waitForPageLoad"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Cms/Test/Mftf/ActionGroup/AdminPressSaveCmsBlockButtonActionGroup.xml b/app/code/Magento/Cms/Test/Mftf/ActionGroup/AdminPressSaveCmsBlockButtonActionGroup.xml new file mode 100644 index 0000000000000..a45e5ed6b9fbf --- /dev/null +++ b/app/code/Magento/Cms/Test/Mftf/ActionGroup/AdminPressSaveCmsBlockButtonActionGroup.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AdminPressSaveCmsBlockButtonActionGroup"> + <annotations> + <description>Press save button on Cms Block page</description> + </annotations> + + <click selector="{{BlockNewPagePageActionsSection.saveBlock}}" stepKey="clickOnSaveBlock"/> + <waitForPageLoad stepKey="waitForPageLoad"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Cms/Test/Mftf/ActionGroup/AdminSelectCMSBlockStoreViewActionGroup.xml b/app/code/Magento/Cms/Test/Mftf/ActionGroup/AdminSelectCMSBlockStoreViewActionGroup.xml new file mode 100644 index 0000000000000..8c543e29c1ed7 --- /dev/null +++ b/app/code/Magento/Cms/Test/Mftf/ActionGroup/AdminSelectCMSBlockStoreViewActionGroup.xml @@ -0,0 +1,17 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AdminSelectCMSBlockStoreViewActionGroup"> + <arguments> + <argument name="storeViewName" type="string"/> + </arguments> + + <selectOption selector="{{BlockNewPageBasicFieldsSection.storeView}}" userInput="{{storeViewName}}" stepKey="selectStoreView" /> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Cms/Test/Mftf/ActionGroup/AssertAdminProperUrlIsShownActionGroup.xml b/app/code/Magento/Cms/Test/Mftf/ActionGroup/AssertAdminProperUrlIsShownActionGroup.xml new file mode 100644 index 0000000000000..fb97c9656aca2 --- /dev/null +++ b/app/code/Magento/Cms/Test/Mftf/ActionGroup/AssertAdminProperUrlIsShownActionGroup.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AssertAdminProperUrlIsShownActionGroup"> + <annotations> + <description>Assert current page has proper URL</description> + </annotations> + <arguments> + <argument name="target_path" type="string"/> + </arguments> + + <seeInCurrentUrl url="{{target_path}}" stepKey="seePropertUrl"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Cms/Test/Mftf/Test/CheckStaticBlocksTest.xml b/app/code/Magento/Cms/Test/Mftf/Test/CheckStaticBlocksTest.xml index eba7812e29a0c..490e11d3cc751 100644 --- a/app/code/Magento/Cms/Test/Mftf/Test/CheckStaticBlocksTest.xml +++ b/app/code/Magento/Cms/Test/Mftf/Test/CheckStaticBlocksTest.xml @@ -21,8 +21,7 @@ </annotations> <before> - <actionGroup ref="AdminLoginActionGroup" stepKey="LoginAsAdmin"/> - + <actionGroup ref="AdminLoginActionGroup" stepKey="loginAsAdmin"/> <actionGroup ref="AdminCreateWebsiteActionGroup" stepKey="createAdditionalWebsite"> <argument name="newWebsiteName" value="{{customWebsite.name}}"/> <argument name="websiteCode" value="{{customWebsite.code}}"/> @@ -39,49 +38,60 @@ </before> <after> - <actionGroup ref="AdminDeleteWebsiteActionGroup" stepKey="DeleteWebsite"> + <actionGroup ref="AdminDeleteWebsiteActionGroup" stepKey="deleteWebsite"> <argument name="websiteName" value="{{customWebsite.name}}"/> </actionGroup> - <actionGroup ref="DeleteCMSBlockActionGroup" stepKey="DeleteCMSBlockActionGroup"/> + <actionGroup ref="DeleteCMSBlockActionGroup" stepKey="deleteCMSBlock"/> + <actionGroup ref="DeleteCMSBlockActionGroup" stepKey="deleteSecondCMSBlock"/> <actionGroup ref="AdminClearFiltersActionGroup" stepKey="clearFilters"/> + <actionGroup ref="AdminLogoutActionGroup" stepKey="logout"/> </after> <!--Go to Cms blocks page--> - <amOnPage url="{{CmsBlocksPage.url}}" stepKey="navigateToCMSPagesGrid"/> - <waitForPageLoad stepKey="waitForPageLoad1"/> - <seeInCurrentUrl url="cms/block/" stepKey="VerifyPageIsOpened"/> + <actionGroup ref="AdminOpenCmsBlocksGridActionGroup" stepKey="navigateToCMSBlocksGrid"/> + <actionGroup ref="AssertAdminProperUrlIsShownActionGroup" stepKey="verifyPageIsOpened"> + <argument name="target_path" value="cms/block/"/> + </actionGroup> + <!--Click to create new block--> - <click selector="{{BlockPageActionsSection.addNewBlock}}" stepKey="ClickToAddNewBlock"/> - <waitForPageLoad stepKey="waitForPageLoad2"/> - <seeInCurrentUrl url="cms/block/new" stepKey="VerifyNewBlockPageIsOpened"/> + <actionGroup ref="AdminPressAddNewCmsBlockButtonActionGroup" stepKey="clickOnAddNewBlockButton"/> + <actionGroup ref="AssertAdminProperUrlIsShownActionGroup" stepKey="verifyNewCmsBlockPageIsOpened"> + <argument name="target_path" value="cms/block/new"/> + </actionGroup> <actionGroup ref="FillOutBlockContent" stepKey="FillOutBlockContent"/> - <click selector="{{BlockNewPagePageActionsSection.saveBlock}}" stepKey="ClickToSaveBlock"/> - <waitForPageLoad stepKey="waitForPageLoad3"/> - <see userInput="You saved the block." stepKey="VerifyBlockIsSaved"/> - <!--Click to go back and add new block--> - <click selector="{{BlockNewPagePageActionsSection.back}}" stepKey="ClickToGoBack"/> - <waitForPageLoad stepKey="waitForPageLoad4"/> - <click selector="{{BlockPageActionsSection.addNewBlock}}" stepKey="ClickToAddNewBlock1"/> - <waitForPageLoad stepKey="waitForPageLoad5"/> - <seeInCurrentUrl url="cms/block/new" stepKey="VerifyNewBlockPageIsOpened1"/> + <actionGroup ref="AdminPressSaveCmsBlockButtonActionGroup" stepKey="saveCmsBlock"/> + <actionGroup ref="AssertMessageInAdminPanelActionGroup" stepKey="assertSuccessMessage"> + <argument name="message" value="You saved the block."/> + </actionGroup> + <!--Add new BLock with the same data--> + <actionGroup ref="AdminOpenCmsBlocksGridActionGroup" stepKey="openCmsBlocksGrid"/> + <actionGroup ref="AdminPressAddNewCmsBlockButtonActionGroup" stepKey="pressAddNewBlockButton"/> + <actionGroup ref="AssertAdminProperUrlIsShownActionGroup" stepKey="assertNewCmsBlockPageIsOpened"> + <argument name="target_path" value="cms/block/new"/> + </actionGroup> <actionGroup ref="FillOutBlockContent" stepKey="FillOutBlockContent1"/> - <click selector="{{BlockNewPagePageActionsSection.saveBlock}}" stepKey="ClickToSaveBlock1"/> - <waitForPageLoad stepKey="waitForPageLoad6"/> - <!--Verify that corresponding message is displayed--> - <see userInput="A block identifier with the same properties already exists in the selected store." stepKey="VerifyBlockIsSaved1"/> - <!--Click to go back and add new block--> - <click selector="{{BlockNewPagePageActionsSection.back}}" stepKey="ClickToGoBack1"/> - <waitForPageLoad stepKey="waitForPageLoad7"/> - <click selector="{{BlockPageActionsSection.addNewBlock}}" stepKey="ClickToAddNewBlock2"/> - <waitForPageLoad stepKey="waitForPageLoad8"/> - <seeInCurrentUrl url="cms/block/new" stepKey="VerifyNewBlockPageIsOpened2"/> + <actionGroup ref="AdminPressSaveCmsBlockButtonActionGroup" stepKey="clickOnSaveButton"/> + <actionGroup ref="AssertMessageInAdminPanelActionGroup" stepKey="assertErrorMessage"> + <argument name="messageType" value="error"/> + <argument name="message" value="A block identifier with the same properties already exists in the selected store."/> + </actionGroup> + <!--Add new BLock with the same data for another store view--> + <actionGroup ref="AdminOpenCmsBlocksGridActionGroup" stepKey="goToCmsBlocksGrid"/> + <actionGroup ref="AdminPressAddNewCmsBlockButtonActionGroup" stepKey="clickToAddNewButton"/> + <actionGroup ref="AssertAdminProperUrlIsShownActionGroup" stepKey="confirmNewCmsBlockPageIsOpened"> + <argument name="target_path" value="cms/block/new"/> + </actionGroup> <actionGroup ref="FillOutBlockContent" stepKey="FillOutBlockContent2"/> - <selectOption selector="{{BlockNewPageBasicFieldsSection.storeView}}" userInput="Default Store View" stepKey="selectDefaultStoreView" /> - <selectOption selector="{{BlockNewPageBasicFieldsSection.storeView}}" userInput="{{customStore.name}}" stepKey="selectSecondStoreView1" /> - <click selector="{{BlockNewPagePageActionsSection.saveBlock}}" stepKey="ClickToSaveBlock2"/> - <waitForPageLoad stepKey="waitForPageLoad9"/> - <see userInput="You saved the block." stepKey="VerifyBlockIsSaved2"/> + + <actionGroup ref="AdminSelectCMSBlockStoreViewActionGroup" stepKey="selectCustomStoreView"> + <argument name="storeViewName" value="{{customStore.name}}"/> + </actionGroup> + + <actionGroup ref="AdminPressSaveCmsBlockButtonActionGroup" stepKey="saveNewCmsBlock"/> + <actionGroup ref="AssertMessageInAdminPanelActionGroup" stepKey="verifyBlockIsSaved"> + <argument name="message" value="You saved the block."/> + </actionGroup> </test> </tests> From c0a9c422053ab85c4f3f77bb337196764c4ed149 Mon Sep 17 00:00:00 2001 From: naitsirch <naitsirch@e.mail.de> Date: Fri, 2 Oct 2020 16:15:49 +0200 Subject: [PATCH 051/139] Fix array to string conversion error when saving row system config with defaults This PR solves issue #30314. The backend model that is designed to handle complex values serializes non-scalar values before they are persisted to database. But when the old value is loaded from config defaults (defined in config.xml) for comparison, the default value is fetched as array. When the array is casted to string it results in an array to string conversion error. This issue is fixed by serializing the default value coming from the config. --- .../Model/Config/Backend/Serialized.php | 23 ++++++++++++++ .../Model/Config/Backend/SerializedTest.php | 30 +++++++++++++++++++ 2 files changed, 53 insertions(+) diff --git a/app/code/Magento/Config/Model/Config/Backend/Serialized.php b/app/code/Magento/Config/Model/Config/Backend/Serialized.php index 6e0b6275db836..88fdae8908797 100644 --- a/app/code/Magento/Config/Model/Config/Backend/Serialized.php +++ b/app/code/Magento/Config/Model/Config/Backend/Serialized.php @@ -5,6 +5,7 @@ */ namespace Magento\Config\Model\Config\Backend; +use Magento\Framework\App\Config\ScopeConfigInterface; use Magento\Framework\App\ObjectManager; use Magento\Framework\Serialize\Serializer\Json; @@ -84,4 +85,26 @@ public function beforeSave() parent::beforeSave(); return $this; } + + /** + * Get old value from existing config + * + * @return string + */ + public function getOldValue() + { + // If the value is retrieved from defaults defined in config.xml + // it may be returned as an array. + $value = $this->_config->getValue( + $this->getPath(), + $this->getScope() ?: ScopeConfigInterface::SCOPE_TYPE_DEFAULT, + $this->getScopeCode() + ); + + if (is_array($value)) { + return $this->serializer->serialize($value); + } + + return (string)$value; + } } diff --git a/app/code/Magento/Config/Test/Unit/Model/Config/Backend/SerializedTest.php b/app/code/Magento/Config/Test/Unit/Model/Config/Backend/SerializedTest.php index c509b515b3112..90a381cff714b 100644 --- a/app/code/Magento/Config/Test/Unit/Model/Config/Backend/SerializedTest.php +++ b/app/code/Magento/Config/Test/Unit/Model/Config/Backend/SerializedTest.php @@ -8,6 +8,7 @@ namespace Magento\Config\Test\Unit\Model\Config\Backend; use Magento\Config\Model\Config\Backend\Serialized; +use Magento\Framework\App\Config\ScopeConfigInterface; use Magento\Framework\Event\ManagerInterface; use Magento\Framework\Model\Context; use Magento\Framework\Serialize\Serializer\Json; @@ -27,11 +28,14 @@ class SerializedTest extends TestCase /** @var LoggerInterface|MockObject */ private $loggerMock; + private $scopeConfigMock; + protected function setUp(): void { $objectManager = new ObjectManager($this); $this->serializerMock = $this->createMock(Json::class); $this->loggerMock = $this->getMockForAbstractClass(LoggerInterface::class); + $this->scopeConfigMock = $this->createMock(ScopeConfigInterface::class); $contextMock = $this->createMock(Context::class); $eventManagerMock = $this->getMockForAbstractClass(ManagerInterface::class); $contextMock->method('getEventDispatcher') @@ -43,6 +47,7 @@ protected function setUp(): void [ 'serializer' => $this->serializerMock, 'context' => $contextMock, + 'config' => $this->scopeConfigMock, ] ); } @@ -135,4 +140,29 @@ public function beforeSaveDataProvider() ] ]; } + + /** + * If a config value is not available in core_confid_data the defaults are + * loaded from the config.xml file. Those defaults may be arrays. + * The Serialized backend model has to override its parent + * getOldValue function, to prevent an array to string conversion error + * and serialize those values. + */ + public function testGetOldValueWithNonScalarDefaultValue(): void + { + $value = [ + ['foo' => '1', 'bar' => '2'], + ]; + $serializedValue = \json_encode($value); + + $this->scopeConfigMock->method('getValue')->willReturn($value); + $this->serializerMock->method('serialize')->willReturn($serializedValue); + + $this->serializedConfig->setData('value', $serializedValue); + + $oldValue = $this->serializedConfig->getOldValue(); + + $this->assertIsString($oldValue, 'Default value from the config is not serialized.'); + $this->assertSame($serializedValue, $oldValue); + } } From 47c21ab6bf8eed1f6dac3dbdb53019a698ba01ab Mon Sep 17 00:00:00 2001 From: Alin Alexandru <alin.alexandru@innobyte.com> Date: Mon, 5 Oct 2020 10:40:03 +0300 Subject: [PATCH 052/139] Allow to cache search results --- app/code/Magento/PageCache/etc/varnish4.vcl | 4 ++-- app/code/Magento/PageCache/etc/varnish5.vcl | 4 ++-- app/code/Magento/PageCache/etc/varnish6.vcl | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/app/code/Magento/PageCache/etc/varnish4.vcl b/app/code/Magento/PageCache/etc/varnish4.vcl index f5e25ce36e973..12c69b82ebada 100644 --- a/app/code/Magento/PageCache/etc/varnish4.vcl +++ b/app/code/Magento/PageCache/etc/varnish4.vcl @@ -57,8 +57,8 @@ sub vcl_recv { return (pass); } - # Bypass shopping cart, checkout and search requests - if (req.url ~ "/checkout" || req.url ~ "/catalogsearch") { + # Bypass shopping cart and checkout + if (req.url ~ "/checkout") { return (pass); } diff --git a/app/code/Magento/PageCache/etc/varnish5.vcl b/app/code/Magento/PageCache/etc/varnish5.vcl index 92bb3394486fc..355f358f58ed6 100644 --- a/app/code/Magento/PageCache/etc/varnish5.vcl +++ b/app/code/Magento/PageCache/etc/varnish5.vcl @@ -58,8 +58,8 @@ sub vcl_recv { return (pass); } - # Bypass shopping cart, checkout and search requests - if (req.url ~ "/checkout" || req.url ~ "/catalogsearch") { + # Bypass shopping cart and checkout + if (req.url ~ "/checkout") { return (pass); } diff --git a/app/code/Magento/PageCache/etc/varnish6.vcl b/app/code/Magento/PageCache/etc/varnish6.vcl index b23bec4c45fb8..427faafc84d2c 100644 --- a/app/code/Magento/PageCache/etc/varnish6.vcl +++ b/app/code/Magento/PageCache/etc/varnish6.vcl @@ -62,8 +62,8 @@ sub vcl_recv { return (pass); } - # Bypass shopping cart, checkout and search requests - if (req.url ~ "/checkout" || req.url ~ "/catalogsearch") { + # Bypass shopping cart and checkout + if (req.url ~ "/checkout") { return (pass); } From efc313151eb866c1e981fb05f9941969a19fadea Mon Sep 17 00:00:00 2001 From: Munkh-Ulzii Balidar <mbalidar@comwrap.com> Date: Thu, 10 Sep 2020 15:19:04 +0200 Subject: [PATCH 053/139] 29251 Configurable Options Selection :: New GraphQL Query #29251 29978 add code refactoring 29978 add more code refactoring 29251 fix test issues 29251 fix static test 29251 add code refactoring 29251 add backward compatible class 29251 add refactoring, fix media gallery 29251 fix static test 29251 fix empty options 29251 fix media gallery 29251 fix web-api test 29251 add debug in failing web-api test 29251 remove debug 29251 add debug in failing web-api test 29251 add debug in failing web-api test 29251 add variant data provider 29251 clean up code 29251 clean up code --- .../Model/Options/Collection.php | 78 +++- .../Model/Options/DataProvider/Variant.php | 67 +++ .../Model/Options/Metadata.php | 173 ++++++++ .../Model/Options/SelectionUidFormatter.php | 60 +++ .../Resolver/OptionsSelectionMetadata.php | 49 +++ .../Model/Resolver/SelectionMediaGallery.php | 45 ++ .../Model/Resolver/Variant/Variant.php | 34 ++ .../ConfigurableProductGraphQl/composer.json | 2 + .../etc/graphql/di.xml | 8 + .../ConfigurableProductGraphQl/etc/module.xml | 2 + .../etc/schema.graphqls | 14 + ...nfigurableOptionsSelectionMetadataTest.php | 410 ++++++++++++++++++ ...oducts_with_two_attributes_combination.php | 188 ++++++++ ...th_two_attributes_combination_rollback.php | 84 ++++ 14 files changed, 1213 insertions(+), 1 deletion(-) create mode 100644 app/code/Magento/ConfigurableProductGraphQl/Model/Options/DataProvider/Variant.php create mode 100644 app/code/Magento/ConfigurableProductGraphQl/Model/Options/Metadata.php create mode 100644 app/code/Magento/ConfigurableProductGraphQl/Model/Options/SelectionUidFormatter.php create mode 100644 app/code/Magento/ConfigurableProductGraphQl/Model/Resolver/OptionsSelectionMetadata.php create mode 100644 app/code/Magento/ConfigurableProductGraphQl/Model/Resolver/SelectionMediaGallery.php create mode 100644 app/code/Magento/ConfigurableProductGraphQl/Model/Resolver/Variant/Variant.php create mode 100644 dev/tests/api-functional/testsuite/Magento/GraphQl/ConfigurableProduct/ConfigurableOptionsSelectionMetadataTest.php create mode 100644 dev/tests/integration/testsuite/Magento/ConfigurableProduct/_files/configurable_products_with_two_attributes_combination.php create mode 100644 dev/tests/integration/testsuite/Magento/ConfigurableProduct/_files/configurable_products_with_two_attributes_combination_rollback.php diff --git a/app/code/Magento/ConfigurableProductGraphQl/Model/Options/Collection.php b/app/code/Magento/ConfigurableProductGraphQl/Model/Options/Collection.php index 5e3666407a383..c5c66a194503a 100644 --- a/app/code/Magento/ConfigurableProductGraphQl/Model/Options/Collection.php +++ b/app/code/Magento/ConfigurableProductGraphQl/Model/Options/Collection.php @@ -10,10 +10,14 @@ use Magento\Catalog\Api\Data\ProductInterface; use Magento\Catalog\Model\Product; use Magento\Catalog\Model\ProductFactory; +use Magento\Catalog\Model\ProductRepository; +use Magento\ConfigurableProduct\Helper\Data; use Magento\ConfigurableProduct\Model\Product\Type\Configurable\Attribute; use Magento\ConfigurableProduct\Model\ResourceModel\Product\Type\Configurable\Attribute\Collection as AttributeCollection; use Magento\ConfigurableProduct\Model\ResourceModel\Product\Type\Configurable\Attribute\CollectionFactory; +use Magento\Framework\Api\SearchCriteriaBuilder; +use Magento\Framework\App\ObjectManager; use Magento\Framework\EntityManager\MetadataPool; /** @@ -31,11 +35,36 @@ class Collection */ private $productFactory; + /** + * @var ProductRepository + */ + private $productRepository; + /** * @var MetadataPool */ private $metadataPool; + /** + * @var Data + */ + private $configurableProductHelper; + + /** + * @var Metadata + */ + private $optionsMetadata; + + /** + * @var SelectionUidFormatter + */ + private $selectionUidFormatter; + + /** + * @var SearchCriteriaBuilder + */ + private $searchCriteriaBuilder; + /** * @var int[] */ @@ -49,16 +78,32 @@ class Collection /** * @param CollectionFactory $attributeCollectionFactory * @param ProductFactory $productFactory + * @param ProductRepository $productRepository * @param MetadataPool $metadataPool + * @param Data $configurableProductHelper + * @param Metadata $optionsMetadata + * @param SelectionUidFormatter $selectionUidFormatter + * @param SearchCriteriaBuilder $searchCriteriaBuilder */ public function __construct( CollectionFactory $attributeCollectionFactory, ProductFactory $productFactory, - MetadataPool $metadataPool + ProductRepository $productRepository, + MetadataPool $metadataPool, + Data $configurableProductHelper, + Metadata $optionsMetadata, + SelectionUidFormatter $selectionUidFormatter, + SearchCriteriaBuilder $searchCriteriaBuilder ) { $this->attributeCollectionFactory = $attributeCollectionFactory; $this->productFactory = $productFactory; + $this->productRepository = $productRepository; $this->metadataPool = $metadataPool; + $this->configurableProductHelper = $configurableProductHelper; + $this->optionsMetadata = $optionsMetadata; + $this->selectionUidFormatter = $selectionUidFormatter; + $this->searchCriteriaBuilder = $searchCriteriaBuilder ?? + ObjectManager::getInstance()->get(SearchCriteriaBuilder::class); } /** @@ -111,6 +156,8 @@ private function fetch() : array $attributeCollection->setProductFilter($product); } + $products = $this->getProducts($this->productIds); + /** @var Attribute $attribute */ foreach ($attributeCollection->getItems() as $attribute) { $productId = (int)$attribute->getProductId(); @@ -128,8 +175,37 @@ private function fetch() : array $this->attributeMap[$productId][$attribute->getId()]['values'] = $attributeData['options']; $this->attributeMap[$productId][$attribute->getId()]['label'] = $attribute->getProductAttribute()->getStoreLabel(); + + if (isset($products[$productId])) { + $options = $this->configurableProductHelper->getOptions( + $products[$productId], + $this->optionsMetadata->getAllowProducts($products[$productId]) + ); + foreach ($attributeData['options'] as $index => $value) { + $this->attributeMap[$productId][$attribute->getId()]['values'][$index]['uid'] + = $this->selectionUidFormatter->encode((int)$attribute->getId(), (int)$value['value_index']); + $this->attributeMap[$productId][$attribute->getId()]['values'][$index] + ['is_available_for_selection'] = + isset($options[$attribute['attribute_id']][$value['value_index']]) + && $options[$attribute['attribute_id']][$value['value_index']]; + } + } } return $this->attributeMap; } + + /** + * Load products by entity ids + * + * @param int[] $productIds + * @return ProductInterface[] + */ + private function getProducts($productIds) + { + $this->searchCriteriaBuilder->addFilter('entity_id', $productIds, 'in'); + $searchCriteria = $this->searchCriteriaBuilder->create(); + $products = $this->productRepository->getList($searchCriteria)->getItems(); + return $products; + } } diff --git a/app/code/Magento/ConfigurableProductGraphQl/Model/Options/DataProvider/Variant.php b/app/code/Magento/ConfigurableProductGraphQl/Model/Options/DataProvider/Variant.php new file mode 100644 index 0000000000000..80fbdc76bacb3 --- /dev/null +++ b/app/code/Magento/ConfigurableProductGraphQl/Model/Options/DataProvider/Variant.php @@ -0,0 +1,67 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\ConfigurableProductGraphQl\Model\Options\DataProvider; + +use Magento\Catalog\Api\Data\ProductInterface; +use Magento\CatalogInventory\Model\ResourceModel\Stock\StatusFactory; +use Magento\ConfigurableProduct\Model\Product\Type\Configurable; + +/** + * Retrieve child products + */ +class Variant +{ + /** + * @var Configurable + */ + private $configurableType; + + /** + * @var StatusFactory + */ + private $stockStatusFactory; + + /** + * @param Configurable $configurableType + * @param StatusFactory $stockStatusFactory + */ + public function __construct( + Configurable $configurableType, + StatusFactory $stockStatusFactory + ) { + $this->configurableType = $configurableType; + $this->stockStatusFactory = $stockStatusFactory; + } + + /** + * Load available child products by parent + * + * @param ProductInterface $product + * @return ProductInterface[] + * @throws \Magento\Framework\Exception\LocalizedException + */ + public function getSalableVariantsByParent(ProductInterface $product) + { + $collection = $this->configurableType->getUsedProductCollection($product); + $collection + ->addAttributeToSelect('*') + ->addFilterByRequiredOptions(); + $collection->addMediaGalleryData(); + $collection->addTierPriceData(); + + $stockFlag = 'has_stock_status_filter'; + if (!$collection->hasFlag($stockFlag)) { + $stockStatusResource = $this->stockStatusFactory->create(); + $stockStatusResource->addStockDataToCollection($collection, true); + $collection->setFlag($stockFlag, true); + } + $collection->clear(); + + return $collection->getItems(); + } +} diff --git a/app/code/Magento/ConfigurableProductGraphQl/Model/Options/Metadata.php b/app/code/Magento/ConfigurableProductGraphQl/Model/Options/Metadata.php new file mode 100644 index 0000000000000..9fa6e4f23fa56 --- /dev/null +++ b/app/code/Magento/ConfigurableProductGraphQl/Model/Options/Metadata.php @@ -0,0 +1,173 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\ConfigurableProductGraphQl\Model\Options; + +use Magento\Catalog\Api\Data\ProductInterface; +use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\ConfigurableProduct\Helper\Data; +use Magento\ConfigurableProductGraphQl\Model\Options\DataProvider\Variant; +use Magento\Framework\Exception\NoSuchEntityException; + +/** + * Retrieve metadata for configurable option selection. + */ +class Metadata +{ + /** + * @var Data + */ + private $configurableProductHelper; + + /** + * @var SelectionUidFormatter + */ + private $selectionUidFormatter; + + /** + * @var ProductRepositoryInterface + */ + private $productRepository; + + /** + * @var Variant + */ + private $variant; + + /** + * @param Data $configurableProductHelper + * @param SelectionUidFormatter $selectionUidFormatter + * @param ProductRepositoryInterface $productRepository + * @param Variant $variant + */ + public function __construct( + Data $configurableProductHelper, + SelectionUidFormatter $selectionUidFormatter, + ProductRepositoryInterface $productRepository, + Variant $variant + ) { + $this->configurableProductHelper = $configurableProductHelper; + $this->selectionUidFormatter = $selectionUidFormatter; + $this->productRepository = $productRepository; + $this->variant = $variant; + } + + /** + * Load available selections from configurable options. + * + * @param ProductInterface $product + * @param array $selectedOptionsUid + * @return array + * @throws NoSuchEntityException + * @SuppressWarnings(PHPMD.CyclomaticComplexity) + */ + public function getAvailableSelections( + ProductInterface $product, + array $selectedOptionsUid + ): array { + $options = $this->configurableProductHelper->getOptions($product, $this->getAllowProducts($product)); + $selectedOptions = $this->selectionUidFormatter->extract($selectedOptionsUid); + $attributeCodes = $this->getAttributeCodes($product); + $availableSelections = $availableProducts = $variantData = []; + + if (isset($options['index']) && $options['index']) { + foreach ($options['index'] as $productId => $productOptions) { + if (!empty($selectedOptions) && !$this->hasProductRequiredOptions($selectedOptions, $productOptions)) { + continue; + } + + $availableProducts[] = $productId; + foreach ($productOptions as $attributeId => $optionIndex) { + $uid = $this->selectionUidFormatter->encode($attributeId, (int)$optionIndex); + + if (isset($availableSelections[$attributeId]['option_value_uids']) + && in_array($uid, $availableSelections[$attributeId]['option_value_uids']) + ) { + continue; + } + $availableSelections[$attributeId]['option_value_uids'][] = $uid; + $availableSelections[$attributeId]['attribute_code'] = $attributeCodes[$attributeId]; + } + + if ($this->hasSelectionProduct($selectedOptions, $productOptions)) { + $variantProduct = $this->productRepository->getById($productId); + $variantData = $variantProduct->getData(); + $variantData['model'] = $variantProduct; + } + } + } + + return [ + 'options_available_for_selection' => $availableSelections, + 'variant' => $variantData, + 'availableSelectionProducts' => array_unique($availableProducts), + 'product' => $product + ]; + } + + /** + * Get allowed products. + * + * @param ProductInterface $product + * @return ProductInterface[] + */ + public function getAllowProducts(ProductInterface $product): array + { + return $this->variant->getSalableVariantsByParent($product) ?? []; + } + + /** + * Check if a product has the selected options. + * + * @param array $requiredOptions + * @param array $productOptions + * @return bool + */ + private function hasProductRequiredOptions($requiredOptions, $productOptions): bool + { + $result = true; + foreach ($requiredOptions as $attributeId => $optionIndex) { + if (!isset($productOptions[$attributeId]) || !$productOptions[$attributeId] + || $optionIndex != $productOptions[$attributeId] + ) { + $result = false; + break; + } + } + + return $result; + } + + /** + * Check if selected options match a product. + * + * @param array $requiredOptions + * @param array $productOptions + * @return bool + */ + private function hasSelectionProduct($requiredOptions, $productOptions): bool + { + return $this->hasProductRequiredOptions($productOptions, $requiredOptions); + } + + /** + * Retrieve attribute codes + * + * @param ProductInterface $product + * @return string[] + */ + private function getAttributeCodes(ProductInterface $product): array + { + $allowedAttributes = $this->configurableProductHelper->getAllowAttributes($product); + $attributeCodes = []; + foreach ($allowedAttributes as $attribute) { + $attributeCodes[$attribute->getAttributeId()] = $attribute->getProductAttribute()->getAttributeCode(); + } + + return $attributeCodes; + } +} diff --git a/app/code/Magento/ConfigurableProductGraphQl/Model/Options/SelectionUidFormatter.php b/app/code/Magento/ConfigurableProductGraphQl/Model/Options/SelectionUidFormatter.php new file mode 100644 index 0000000000000..1d13ad75489a1 --- /dev/null +++ b/app/code/Magento/ConfigurableProductGraphQl/Model/Options/SelectionUidFormatter.php @@ -0,0 +1,60 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\ConfigurableProductGraphQl\Model\Options; + +/** + * Handle option selection uid. + */ +class SelectionUidFormatter +{ + /** + * Prefix of uid for encoding + */ + private const UID_PREFIX = 'configurable'; + + /** + * Separator of uid for encoding + */ + private const UID_SEPARATOR = '/'; + + /** + * Create uid and encode. + * + * @param int $attributeId + * @param int $indexId + * @return string + */ + public function encode(int $attributeId, int $indexId): string + { + // phpcs:ignore Magento2.Functions.DiscouragedFunction + return base64_encode(implode(self::UID_SEPARATOR, [ + self::UID_PREFIX, + $attributeId, + $indexId + ])); + } + + /** + * Retrieve attribute and option index from uid. Array key is the id of attribute and value is the index of option + * + * @param string $selectionUids + * @return array + * @SuppressWarnings(PHPMD.CyclomaticComplexity) + */ + public function extract(array $selectionUids): array + { + $attributeOption = []; + foreach ($selectionUids as $uid) { + // phpcs:ignore Magento2.Functions.DiscouragedFunction + $optionData = explode(self::UID_SEPARATOR, base64_decode($uid)); + if (count($optionData) == 3) { + $attributeOption[(int)$optionData[1]] = (int)$optionData[2]; + } + } + + return $attributeOption; + } +} diff --git a/app/code/Magento/ConfigurableProductGraphQl/Model/Resolver/OptionsSelectionMetadata.php b/app/code/Magento/ConfigurableProductGraphQl/Model/Resolver/OptionsSelectionMetadata.php new file mode 100644 index 0000000000000..f7d5a96ad2aba --- /dev/null +++ b/app/code/Magento/ConfigurableProductGraphQl/Model/Resolver/OptionsSelectionMetadata.php @@ -0,0 +1,49 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\ConfigurableProductGraphQl\Model\Resolver; + +use Magento\Catalog\Api\Data\ProductInterface; +use Magento\ConfigurableProductGraphQl\Model\Options\Metadata; +use Magento\Framework\Exception\LocalizedException; +use Magento\Framework\GraphQl\Config\Element\Field; +use Magento\Framework\GraphQl\Query\ResolverInterface; +use Magento\Framework\GraphQl\Schema\Type\ResolveInfo; + +/** + * Resolver class for option selection metadata. + */ +class OptionsSelectionMetadata implements ResolverInterface +{ + /** + * @var Metadata + */ + private $configurableSelectionMetadata; + + /** + * @param Metadata $configurableSelectionMetadata + */ + public function __construct( + Metadata $configurableSelectionMetadata + ) { + $this->configurableSelectionMetadata = $configurableSelectionMetadata; + } + + /** + * @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')); + } + + $selectedOptions = $args['selectedConfigurableOptionValues'] ?? []; + /** @var ProductInterface $product */ + $product = $value['model']; + + return $this->configurableSelectionMetadata->getAvailableSelections($product, $selectedOptions); + } +} diff --git a/app/code/Magento/ConfigurableProductGraphQl/Model/Resolver/SelectionMediaGallery.php b/app/code/Magento/ConfigurableProductGraphQl/Model/Resolver/SelectionMediaGallery.php new file mode 100644 index 0000000000000..7b3ddc4ac1417 --- /dev/null +++ b/app/code/Magento/ConfigurableProductGraphQl/Model/Resolver/SelectionMediaGallery.php @@ -0,0 +1,45 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\ConfigurableProductGraphQl\Model\Resolver; + +use Magento\Framework\GraphQl\Config\Element\Field; +use Magento\Framework\GraphQl\Query\ResolverInterface; +use Magento\Framework\GraphQl\Schema\Type\ResolveInfo; + +/** + * Resolver class for media gallery of child products. + */ +class SelectionMediaGallery implements ResolverInterface +{ + /** + * @inheritDoc + */ + public function resolve(Field $field, $context, ResolveInfo $info, array $value = null, array $args = null) + { + if (!isset($value['product']) || !$value['product']) { + return null; + } + + $product = $value['product']; + $availableSelectionProducts = $value['availableSelectionProducts']; + $mediaGalleryEntries = []; + $usedProducts = $product->getTypeInstance()->getUsedProducts($product, null); + foreach ($usedProducts as $usedProduct) { + if (in_array($usedProduct->getId(), $availableSelectionProducts)) { + foreach ($usedProduct->getMediaGalleryEntries() ?? [] as $key => $entry) { + $index = $usedProduct->getId() . '_' . $key; + $mediaGalleryEntries[$index] = $entry->getData(); + $mediaGalleryEntries[$index]['model'] = $usedProduct; + if ($entry->getExtensionAttributes() && $entry->getExtensionAttributes()->getVideoContent()) { + $mediaGalleryEntries[$index]['video_content'] + = $entry->getExtensionAttributes()->getVideoContent()->getData(); + } + } + } + } + return $mediaGalleryEntries; + } +} diff --git a/app/code/Magento/ConfigurableProductGraphQl/Model/Resolver/Variant/Variant.php b/app/code/Magento/ConfigurableProductGraphQl/Model/Resolver/Variant/Variant.php new file mode 100644 index 0000000000000..625c31a2680c8 --- /dev/null +++ b/app/code/Magento/ConfigurableProductGraphQl/Model/Resolver/Variant/Variant.php @@ -0,0 +1,34 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\ConfigurableProductGraphQl\Model\Resolver\Variant; + +use Magento\Framework\GraphQl\Config\Element\Field; +use Magento\Framework\GraphQl\Query\ResolverInterface; +use Magento\Framework\GraphQl\Schema\Type\ResolveInfo; + +/** + * Resolver class for product variant. + */ +class Variant implements ResolverInterface +{ + /** + * @inheritDoc + */ + public function resolve(Field $field, $context, ResolveInfo $info, array $value = null, array $args = null) + { + if (isset($value['variant']['model']) && $value['variant']['model']) { + return + array_merge( + $value['variant']['model']->getData(), + [ + 'model' => $value['variant']['model'] + ] + ); + } else { + return null; + } + } +} diff --git a/app/code/Magento/ConfigurableProductGraphQl/composer.json b/app/code/Magento/ConfigurableProductGraphQl/composer.json index 295efb65b1978..73e134e1522a4 100644 --- a/app/code/Magento/ConfigurableProductGraphQl/composer.json +++ b/app/code/Magento/ConfigurableProductGraphQl/composer.json @@ -6,10 +6,12 @@ "php": "~7.3.0||~7.4.0", "magento/module-catalog": "*", "magento/module-configurable-product": "*", + "magento/module-eav": "*", "magento/module-graph-ql": "*", "magento/module-catalog-graph-ql": "*", "magento/module-quote": "*", "magento/module-quote-graph-ql": "*", + "magento/module-catalog-inventory": "*", "magento/framework": "*" }, "license": [ diff --git a/app/code/Magento/ConfigurableProductGraphQl/etc/graphql/di.xml b/app/code/Magento/ConfigurableProductGraphQl/etc/graphql/di.xml index 808ca62f7e149..dc672b02e2f96 100644 --- a/app/code/Magento/ConfigurableProductGraphQl/etc/graphql/di.xml +++ b/app/code/Magento/ConfigurableProductGraphQl/etc/graphql/di.xml @@ -43,4 +43,12 @@ </argument> </arguments> </type> + + <type name="Magento\ConfigurableProduct\Model\Product\Type\Configurable"> + <plugin name="used_products_cache_graphql" type="Magento\ConfigurableProduct\Model\Plugin\Frontend\UsedProductsCache" /> + </type> + + <type name="Magento\ConfigurableProduct\Model\ResourceModel\Attribute\OptionSelectBuilderInterface"> + <plugin name="Magento_ConfigurableProduct_Plugin_Model_ResourceModel_Attribute_InStockOptionSelectBuilder_GraphQl" type="Magento\ConfigurableProduct\Plugin\Model\ResourceModel\Attribute\InStockOptionSelectBuilder"/> + </type> </config> diff --git a/app/code/Magento/ConfigurableProductGraphQl/etc/module.xml b/app/code/Magento/ConfigurableProductGraphQl/etc/module.xml index f249a417f1046..36b6fd40eea15 100644 --- a/app/code/Magento/ConfigurableProductGraphQl/etc/module.xml +++ b/app/code/Magento/ConfigurableProductGraphQl/etc/module.xml @@ -10,8 +10,10 @@ <sequence> <module name="Magento_Catalog"/> <module name="Magento_ConfigurableProduct"/> + <module name="Magento_Eav"/> <module name="Magento_GraphQl"/> <module name="Magento_CatalogGraphQl"/> + <module name="Magento_CatalogInventory"/> <module name="Magento_QuoteGraphQl"/> </sequence> </module> diff --git a/app/code/Magento/ConfigurableProductGraphQl/etc/schema.graphqls b/app/code/Magento/ConfigurableProductGraphQl/etc/schema.graphqls index 257bca11fb5b7..88d0f8e212acf 100644 --- a/app/code/Magento/ConfigurableProductGraphQl/etc/schema.graphqls +++ b/app/code/Magento/ConfigurableProductGraphQl/etc/schema.graphqls @@ -7,6 +7,7 @@ type Mutation { type ConfigurableProduct implements ProductInterface, PhysicalProductInterface, CustomizableProductInterface @doc(description: "ConfigurableProduct defines basic features of a configurable product and its simple product variants") { variants: [ConfigurableVariant] @doc(description: "An array of variants of products") @resolver(class: "Magento\\ConfigurableProductGraphQl\\Model\\Resolver\\ConfigurableVariant") configurable_options: [ConfigurableProductOptions] @doc(description: "An array of linked simple product items") @resolver(class: "Magento\\ConfigurableProductGraphQl\\Model\\Resolver\\Options") + configurable_options_selection_metadata(selectedConfigurableOptionValues: [ID!]): ConfigurableOptionsSelectionMetadata @doc(description: "Metadata for the specified configurable options selection") @resolver(class: "Magento\\ConfigurableProductGraphQl\\Model\\Resolver\\OptionsSelectionMetadata") } type ConfigurableVariant @doc(description: "An array containing all the simple product variants of a configurable product") { @@ -34,6 +35,8 @@ type ConfigurableProductOptions @doc(description: "ConfigurableProductOptions de } type ConfigurableProductOptionsValues @doc(description: "ConfigurableProductOptionsValues contains the index number assigned to a configurable product option") { + uid: ID! + is_available_for_selection: Boolean! value_index: Int @doc(description: "A unique index number assigned to the configurable product option") label: String @doc(description: "The label of the product") default_label: String @doc(description: "The label of the product on the default store") @@ -73,3 +76,14 @@ type ConfigurableWishlistItem implements WishlistItemInterface @doc(description: child_sku: String! @doc(description: "The SKU of the simple product corresponding to a set of selected configurable options") @resolver(class: "\\Magento\\ConfigurableProductGraphQl\\Model\\Wishlist\\ChildSku") configurable_options: [SelectedConfigurableOption!] @resolver(class: "\\Magento\\ConfigurableProductGraphQl\\Model\\Wishlist\\ConfigurableOptions") @doc (description: "An array of selected configurable options") } + +type ConfigurableOptionsSelectionMetadata @doc(description: "Metadata corresponding to the configurable options selection.") { + options_available_for_selection: [ConfigurableOptionAvailableForSelection!] @doc(description: "Configurable options available for further selection based on current selection.") + media_gallery: [MediaGalleryInterface!] @resolver(class: "Magento\\ConfigurableProductGraphQl\\Model\\Resolver\\SelectionMediaGallery") @doc(description: "Product images and videos corresponding to the specified configurable options selection.") + variant: SimpleProduct @resolver(class: "Magento\\ConfigurableProductGraphQl\\Model\\Resolver\\Variant\\Variant") @doc(description: "Variant represented by the specified configurable options selection. It is expected to be null, until selections are made for each configurable option.") +} + +type ConfigurableOptionAvailableForSelection @doc(description: "Configurable option available for further selection based on current selection.") { + option_value_uids: [ID!]! @doc(description: "Configurable option values available for further selection.") + attribute_code: String! @doc(description: "Attribute code that uniquely identifies configurable option.") +} diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/ConfigurableProduct/ConfigurableOptionsSelectionMetadataTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/ConfigurableProduct/ConfigurableOptionsSelectionMetadataTest.php new file mode 100644 index 0000000000000..f0e4df50794a3 --- /dev/null +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/ConfigurableProduct/ConfigurableOptionsSelectionMetadataTest.php @@ -0,0 +1,410 @@ +<?php + +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\GraphQl\ConfigurableProduct; + +use Magento\ConfigurableProductGraphQl\Model\Options\SelectionUidFormatter; +use Magento\Framework\Exception\NoSuchEntityException; +use Magento\Eav\Api\Data\AttributeInterface; +use Magento\Eav\Model\AttributeRepository; +use Magento\TestFramework\TestCase\GraphQlAbstract; +use Magento\TestFramework\Helper\Bootstrap; + +/** + * Test configurable product option selection. + */ +class ConfigurableOptionsSelectionMetadataTest extends GraphQlAbstract +{ + /** + * @var AttributeRepository + */ + private $attributeRepository; + + /** + * @var SelectionUidFormatter + */ + private $selectionUidFormatter; + + private $firstConfigurableAttribute = null; + + private $secondConfigurableAttribute = null; + + /** + * @inheritdoc + */ + protected function setUp(): void + { + $this->attributeRepository = Bootstrap::getObjectManager()->create(AttributeRepository::class); + $this->selectionUidFormatter = Bootstrap::getObjectManager()->create(SelectionUidFormatter::class); + } + + /** + * @magentoApiDataFixture Magento/ConfigurableProduct/_files/configurable_products_with_two_attributes_combination.php + */ + public function testWithoutSelectedOption() + { + $query = <<<QUERY +{ + products(filter:{ + sku: {eq: "configurable_12345"} + }) + { + items + { + id + sku + name + description { + html + } + ... on ConfigurableProduct { + configurable_options_selection_metadata( + selectedConfigurableOptionValues: [] + ) { + options_available_for_selection { + option_value_uids + attribute_code + } + } + } + } + } +} +QUERY; + $response = $this->graphQlQuery($query); + $this->assertEquals(1, count($response['products']['items'])); + $this->assertEquals(2, count($response['products']['items'][0]['configurable_options_selection_metadata'] + ['options_available_for_selection'])); + $this->assertEquals(4, count($response['products']['items'][0]['configurable_options_selection_metadata'] + ['options_available_for_selection'][0]['option_value_uids'])); + $this->assertEquals(4, count($response['products']['items'][0]['configurable_options_selection_metadata'] + ['options_available_for_selection'][1]['option_value_uids'])); + } + + /** + * @magentoApiDataFixture Magento/ConfigurableProduct/_files/configurable_products_with_two_attributes_combination.php + */ + public function testSelectedFirstAttributeFirstOption() + { + $attribute = $this->getFirstConfigurableAttribute(); + $options = $attribute->getOptions(); + $firstOptionUid = $this->selectionUidFormatter->encode( + (int)$attribute->getAttributeId(), + (int)$options[1]->getValue() + ); + $query = <<<QUERY +{ + products(filter:{ + sku: {eq: "configurable_12345"} + }) + { + items + { + id + sku + name + description { + html + } + ... on ConfigurableProduct { + configurable_options_selection_metadata( + selectedConfigurableOptionValues: ["{$firstOptionUid}"] + ) { + options_available_for_selection { + option_value_uids + attribute_code + } + } + } + } + } +} +QUERY; + + $response = $this->graphQlQuery($query); + $this->assertEquals(1, count($response['products']['items'])); + $this->assertEquals(2, count($response['products']['items'][0]['configurable_options_selection_metadata'] + ['options_available_for_selection'])); + $this->assertEquals(1, count($response['products']['items'][0]['configurable_options_selection_metadata'] + ['options_available_for_selection'][0]['option_value_uids'])); + $this->assertEquals($firstOptionUid, $response['products']['items'][0] + ['configurable_options_selection_metadata']['options_available_for_selection'][0]['option_value_uids'][0]); + $this->assertEquals(4, count($response['products']['items'][0]['configurable_options_selection_metadata'] + ['options_available_for_selection'][1]['option_value_uids'])); + + $secondAttributeOptions = $this->getSecondConfigurableAttribute()->getOptions(); + $this->assertAvailableOptionUids( + $this->getSecondConfigurableAttribute()->getAttributeId(), + $secondAttributeOptions, + $response['products']['items'][0]['configurable_options_selection_metadata'] + ['options_available_for_selection'][1]['option_value_uids'] + ); + } + + /** + * @magentoApiDataFixture Magento/ConfigurableProduct/_files/configurable_products_with_two_attributes_combination.php + */ + public function testSelectedFirstAttributeLastOption() + { + $attribute = $this->getFirstConfigurableAttribute(); + $options = $attribute->getOptions(); + $lastOptionUid = $this->selectionUidFormatter->encode( + (int)$attribute->getAttributeId(), + (int)$options[4]->getValue() + ); + $query = <<<QUERY +{ + products(filter:{ + sku: {eq: "configurable_12345"} + }) + { + items + { + id + sku + name + description { + html + } + ... on ConfigurableProduct { + configurable_options_selection_metadata( + selectedConfigurableOptionValues: ["{$lastOptionUid}"] + ) { + options_available_for_selection { + option_value_uids + attribute_code + } + } + } + } + } +} +QUERY; + + $response = $this->graphQlQuery($query); + $this->assertEquals(1, count($response['products']['items'])); + $this->assertEquals(2, count($response['products']['items'][0]['configurable_options_selection_metadata'] + ['options_available_for_selection'])); + $this->assertEquals(1, count($response['products']['items'][0]['configurable_options_selection_metadata'] + ['options_available_for_selection'][0]['option_value_uids'])); + $this->assertEquals($lastOptionUid, $response['products']['items'][0]['configurable_options_selection_metadata'] + ['options_available_for_selection'][0]['option_value_uids'][0]); + $this->assertEquals(2, count($response['products']['items'][0]['configurable_options_selection_metadata'] + ['options_available_for_selection'][1]['option_value_uids'])); + $secondAttributeOptions = $this->getSecondConfigurableAttribute()->getOptions(); + unset($secondAttributeOptions[0]); + unset($secondAttributeOptions[1]); + unset($secondAttributeOptions[2]); + $this->assertAvailableOptionUids( + $this->getSecondConfigurableAttribute()->getAttributeId(), + $secondAttributeOptions, + $response['products']['items'][0]['configurable_options_selection_metadata'] + ['options_available_for_selection'][1]['option_value_uids'] + ); + } + + /** + * @magentoApiDataFixture Magento/ConfigurableProduct/_files/configurable_products_with_two_attributes_combination.php + */ + public function testSelectedVariant() + { + $firstAttribute = $this->getFirstConfigurableAttribute(); + $firstOptions = $firstAttribute->getOptions(); + $firstAttributeFirstOptionUid = $this->selectionUidFormatter->encode( + (int)$firstAttribute->getAttributeId(), + (int)$firstOptions[1]->getValue() + ); + $secodnAttribute = $this->getSecondConfigurableAttribute(); + $secondOptions = $secodnAttribute->getOptions(); + $secondAttributeFirstOptionUid = $this->selectionUidFormatter->encode( + (int)$secodnAttribute->getAttributeId(), + (int)$secondOptions[1]->getValue() + ); + $query = <<<QUERY +{ + products(filter:{ + sku: {eq: "configurable_12345"} + }) + { + items + { + id + sku + name + description { + html + } + ... on ConfigurableProduct { + configurable_options_selection_metadata( + selectedConfigurableOptionValues: ["{$firstAttributeFirstOptionUid}", "{$secondAttributeFirstOptionUid}"] + ) { + options_available_for_selection { + option_value_uids + } + variant { + id + sku + name + } + } + } + } + } +} +QUERY; + $response = $this->graphQlQuery($query); + $this->assertEquals(1, count($response['products']['items'])); + $this->assertNotNull($response['products']['items'][0]['configurable_options_selection_metadata'] + ['variant']); + $this->assertEquals( + 'simple_' . $firstOptions[1]->getValue() . '_' . $secondOptions[1]->getValue(), + $response['products']['items'][0]['configurable_options_selection_metadata'] + ['variant']['sku'] + ); + } + + /** + * @magentoApiDataFixture Magento/ConfigurableProduct/_files/configurable_products_with_two_attributes_combination.php + */ + public function testMediaGalleryForAll() + { + $query = <<<QUERY +{ + products(filter:{ + sku: {eq: "configurable_12345"} + }) + { + items + { + id + sku + name + description { + html + } + ... on ConfigurableProduct { + configurable_options_selection_metadata( + selectedConfigurableOptionValues: [] + ) { + options_available_for_selection { + option_value_uids + attribute_code + } + media_gallery { + url + } + } + } + } + } +} +QUERY; + $response = $this->graphQlQuery($query); + $this->assertEquals(1, count($response['products']['items'])); + $this->assertEquals(14, count($response['products']['items'][0]['configurable_options_selection_metadata'] + ['media_gallery'])); + } + + /** + * @magentoApiDataFixture Magento/ConfigurableProduct/_files/configurable_products_with_two_attributes_combination.php + */ + public function testMediaGalleryWithSelection() + { + $attribute = $this->getFirstConfigurableAttribute(); + $options = $attribute->getOptions(); + $lastOptionUid = $this->selectionUidFormatter->encode( + (int)$attribute->getAttributeId(), + (int)$options[4]->getValue() + ); + $query = <<<QUERY +{ + products(filter:{ + sku: {eq: "configurable_12345"} + }) + { + items + { + id + sku + name + description { + html + } + ... on ConfigurableProduct { + configurable_options_selection_metadata( + selectedConfigurableOptionValues: ["$lastOptionUid"] + ) { + options_available_for_selection { + option_value_uids + attribute_code + } + media_gallery { + url + } + } + } + } + } +} +QUERY; + $response = $this->graphQlQuery($query); + $this->assertEquals(1, count($response['products']['items'])); + $this->assertEquals(2, count($response['products']['items'][0]['configurable_options_selection_metadata'] + ['media_gallery'])); + } + + /** + * Assert option uid. + * + * @param $attributeId + * @param $expectedOptions + * @param $selectedOptions + */ + private function assertAvailableOptionUids($attributeId, $expectedOptions, $selectedOptions) + { + unset($expectedOptions[0]); + foreach ($expectedOptions as $option) { + $this->assertContains( + $this->selectionUidFormatter->encode((int)$attributeId, (int)$option->getValue()), + $selectedOptions + ); + } + } + + /** + * Get first configurable attribute. + * + * @return AttributeInterface + * @throws NoSuchEntityException + */ + private function getFirstConfigurableAttribute() + { + if (!$this->firstConfigurableAttribute) { + $attributeCode = 'test_configurable_first'; + $this->firstConfigurableAttribute = $this->attributeRepository->get('catalog_product', $attributeCode); + } + + return $this->firstConfigurableAttribute; + } + + /** + * Get second configurable attribute. + * + * @return AttributeInterface + * @throws NoSuchEntityException + */ + private function getSecondConfigurableAttribute() + { + if (!$this->secondConfigurableAttribute) { + $attributeCode = 'test_configurable_second'; + $this->secondConfigurableAttribute = $this->attributeRepository->get('catalog_product', $attributeCode); + } + + return $this->secondConfigurableAttribute; + } +} diff --git a/dev/tests/integration/testsuite/Magento/ConfigurableProduct/_files/configurable_products_with_two_attributes_combination.php b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/_files/configurable_products_with_two_attributes_combination.php new file mode 100644 index 0000000000000..24e6010275bac --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/_files/configurable_products_with_two_attributes_combination.php @@ -0,0 +1,188 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\Catalog\Model\Indexer\Product\Price\Processor as PriceIndexerProcessor; +use Magento\Catalog\Model\Product; +use Magento\Catalog\Model\Product\Attribute\Source\Status; +use Magento\Catalog\Model\Product\Type; +use Magento\Catalog\Model\Product\Visibility; +use Magento\Catalog\Setup\CategorySetup; +use Magento\CatalogInventory\Model\Stock\Item; +use Magento\ConfigurableProduct\Helper\Product\Options\Factory; +use Magento\ConfigurableProduct\Model\Product\Type\Configurable; +use Magento\Eav\Api\Data\AttributeOptionInterface; +use Magento\Framework\Api\Data\ImageContentInterface; +use Magento\TestFramework\Helper\Bootstrap; +use Magento\TestFramework\Workaround\Override\Fixture\Resolver; + +Resolver::getInstance()->requireDataFixture( + 'Magento/ConfigurableProduct/_files/configurable_attribute_first.php' +); +Resolver::getInstance()->requireDataFixture( + 'Magento/ConfigurableProduct/_files/configurable_attribute_second.php' +); + +/** @var ProductRepositoryInterface $productRepository */ +$productRepository = Bootstrap::getObjectManager() + ->get(ProductRepositoryInterface::class); + +/** @var $installer CategorySetup */ +$installer = Bootstrap::getObjectManager()->create(CategorySetup::class); + +/** @var \Magento\Eav\Model\Config $eavConfig */ +$eavConfig = Bootstrap::getObjectManager()->get(\Magento\Eav\Model\Config::class); +$firstAttribute = $eavConfig->getAttribute(Product::ENTITY, 'test_configurable_first'); +$secondAttribute = $eavConfig->getAttribute(Product::ENTITY, 'test_configurable_second'); + +/* Create simple products per each option value*/ +/** @var AttributeOptionInterface[] $firstAttributeOptions */ +$firstAttributeOptions = $firstAttribute->getOptions(); +/** @var AttributeOptionInterface[] $secondAttributeOptions */ +$secondAttributeOptions = $secondAttribute->getOptions(); + +$attributeSetId = $installer->getAttributeSetId('catalog_product', 'Default'); +$associatedProductIds = []; +$firstAttributeValues = []; +$secondAttributeValues = []; +$testImagePath = __DIR__ . '/magento_image.jpg'; + +array_shift($firstAttributeOptions); +array_shift($secondAttributeOptions); +foreach ($firstAttributeOptions as $i => $firstAttributeOption) { + $firstAttributeValues[] = [ + 'label' => 'test first ' . $firstAttributeOption->getValue(), + 'attribute_id' => $firstAttribute->getId(), + 'value_index' => $firstAttributeOption->getValue(), + ]; + foreach ($secondAttributeOptions as $j => $secondAttributeOption) { + if ($i == 3 && in_array($j, [0, 1])) { + $qty = 0; + $isInStock = 0; + } else { + $qty = 100; + $isInStock = 1; + } + $product = Bootstrap::getObjectManager()->create(Product::class); + $product->setTypeId(Type::TYPE_SIMPLE) + ->setAttributeSetId($attributeSetId) + ->setWebsiteIds([1]) + ->setName( + 'Configurable Option ' . $firstAttributeOption->getLabel() . '-' . $secondAttributeOption->getLabel() + ) + ->setSku('simple_' . $firstAttributeOption->getValue() . '_' . $secondAttributeOption->getValue()) + ->setPrice($firstAttributeOption->getValue() + $secondAttributeOption->getValue()) + ->setVisibility(Visibility::VISIBILITY_NOT_VISIBLE) + ->setStatus(Status::STATUS_ENABLED) + ->setStockData( + ['use_config_manage_stock' => 1, 'qty' => $qty, 'is_qty_decimal' => 0, 'is_in_stock' => $isInStock] + ) + ->setImage('/m/a/magento_image.jpg') + ->setSmallImage('/m/a/magento_image.jpg') + ->setThumbnail('/m/a/magento_image.jpg') + ->setData( + 'media_gallery', + [ + 'images' => [ + [ + 'file' => '/m/a/magento_image.jpg', + 'position' => 1, + 'label' => 'Image Alt Text', + 'disabled' => 0, + 'media_type' => 'image', + 'content' => [ + 'data' => [ + ImageContentInterface::BASE64_ENCODED_DATA => base64_encode( + file_get_contents($testImagePath) + ), + ImageContentInterface::NAME => 'simple_' . $firstAttributeOption->getValue() . + '_' . $secondAttributeOption->getValue() . "_1.jpg", + ImageContentInterface::TYPE => "image/jpeg" + ] + ] + ], + ] + ] + ); + $customAttributes = [ + $firstAttribute->getAttributeCode() => $firstAttributeOption->getValue(), + $secondAttribute->getAttributeCode() => $secondAttributeOption->getValue() + ]; + foreach ($customAttributes as $attributeCode => $attributeValue) { + $product->setCustomAttributes($customAttributes); + } + $product = $productRepository->save($product); + $associatedProductIds[] = $product->getId(); + + /** @var \Magento\CatalogInventory\Model\Stock\Item $stockItem */ + $stockItem = Bootstrap::getObjectManager()->create(Item::class); + $stockItem->load($product->getId(), 'product_id'); + + if (!$stockItem->getProductId()) { + $stockItem->setProductId($product->getId()); + } + $stockItem->setUseConfigManageStock(1); + $stockItem->setQty($qty); + $stockItem->setIsQtyDecimal(0); + $stockItem->setIsInStock($isInStock); + $stockItem->save(); + + $secondAttributeValues[$j] = [ + 'label' => 'test second ' . $firstAttributeOption->getValue() . $secondAttributeOption->getValue(), + 'attribute_id' => $secondAttribute->getId(), + 'value_index' => $secondAttributeOption->getValue(), + ]; + } + +} + +$indexerProcessor = Bootstrap::getObjectManager()->get(PriceIndexerProcessor::class); +$indexerProcessor->reindexList($associatedProductIds, true); + +/** @var $product Product */ +$product = Bootstrap::getObjectManager()->create(Product::class); + +/** @var Factory $optionsFactory */ +$optionsFactory = Bootstrap::getObjectManager()->create(Factory::class); + +$configurableAttributesData = [ + [ + 'attribute_id' => $firstAttribute->getId(), + 'code' => $firstAttribute->getAttributeCode(), + 'label' => $firstAttribute->getStoreLabel(), + 'position' => '0', + 'values' => $firstAttributeValues, + ], + [ + 'attribute_id' => $secondAttribute->getId(), + 'code' => $secondAttribute->getAttributeCode(), + 'label' => $secondAttribute->getStoreLabel(), + 'position' => '1', + 'values' => $secondAttributeValues, + ], +]; + +$configurableOptions = $optionsFactory->create($configurableAttributesData); +$firstAttributeSetId = $installer->getAttributeSetId('catalog_product', 'Default'); + +$extensionConfigurableAttributes = $product->getExtensionAttributes(); +$extensionConfigurableAttributes->setConfigurableProductOptions($configurableOptions); +$extensionConfigurableAttributes->setConfigurableProductLinks($associatedProductIds); + +$product->setExtensionAttributes($extensionConfigurableAttributes); + +$product->setTypeId(Configurable::TYPE_CODE) + ->setAttributeSetId($firstAttributeSetId) + ->setWebsiteIds([1]) + ->setName('Configurable Product 12345') + ->setSku('configurable_12345') + ->setVisibility(Visibility::VISIBILITY_BOTH) + ->setStatus(Status::STATUS_ENABLED) + ->setStockData(['use_config_manage_stock' => 1, 'qty' => 100, 'is_qty_decimal' => 0, 'is_in_stock' => 1]); +$productRepository->cleanCache(); +$product = $productRepository->save($product); + +$indexerProcessor = Bootstrap::getObjectManager()->get(PriceIndexerProcessor::class); +$indexerProcessor->reindexRow($product->getId(), true); diff --git a/dev/tests/integration/testsuite/Magento/ConfigurableProduct/_files/configurable_products_with_two_attributes_combination_rollback.php b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/_files/configurable_products_with_two_attributes_combination_rollback.php new file mode 100644 index 0000000000000..b5527b8484a19 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/_files/configurable_products_with_two_attributes_combination_rollback.php @@ -0,0 +1,84 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\Catalog\Model\Product; +use Magento\Catalog\Setup\CategorySetup; +use Magento\CatalogInventory\Model\Stock\Status; +use Magento\Eav\Model\Config; +use Magento\Framework\Exception\NoSuchEntityException; +use Magento\Framework\Registry; +use Magento\Eav\Api\Data\AttributeOptionInterface; +use Magento\TestFramework\Helper\Bootstrap; +use Magento\TestFramework\Workaround\Override\Fixture\Resolver; + +$objectManager = Bootstrap::getObjectManager(); + +/** @var Registry $registry */ +$registry = $objectManager->get(Registry::class); + +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', true); + +/** @var ProductRepositoryInterface $productRepository */ +$productRepository = $objectManager->get(ProductRepositoryInterface::class); + +/** @var $installer CategorySetup */ +$installer = $objectManager->create(CategorySetup::class); + +/** @var Config $eavConfig */ +$eavConfig = $objectManager->get(Config::class); +$firstAttribute = $eavConfig->getAttribute(Product::ENTITY, 'test_configurable_first'); +$secondAttribute = $eavConfig->getAttribute(Product::ENTITY, 'test_configurable_second'); + +/** @var AttributeOptionInterface[] $firstAttributeOptions */ +$firstAttributeOptions = $firstAttribute->getOptions(); +/** @var AttributeOptionInterface[] $secondAttributeOptions */ +$secondAttributeOptions = $secondAttribute->getOptions(); + +array_shift($firstAttributeOptions); +array_shift($secondAttributeOptions); +foreach ($firstAttributeOptions as $i => $firstAttributeOption) { + foreach ($secondAttributeOptions as $j => $secondAttributeOption) { + /** @var ProductRepositoryInterface $productRepository */ + $productRepository = $objectManager->get(ProductRepositoryInterface::class); + try { + //delete child product + $sku = 'simple_' . $firstAttributeOption->getValue() . '_' . $secondAttributeOption->getValue(); + $product = $productRepository->get($sku, true); + $stockStatus = $objectManager->create(Status::class); + $stockStatus->load($product->getEntityId(), 'product_id'); + $stockStatus->delete(); + $productRepository->delete($product); + } catch (NoSuchEntityException $e) { + //Product already removed + } + } +} + +//delete configurable product +try { + $product = $productRepository->get('configurable_12345', true); + $stockStatus = $objectManager->create(Status::class); + $stockStatus->load($product->getEntityId(), 'product_id'); + $stockStatus->delete(); + + $productRepository->delete($product); +} catch (NoSuchEntityException $e) { + //Product already removed +} +Resolver::getInstance()->requireDataFixture( + 'Magento/ConfigurableProduct/_files/configurable_attribute_first_rollback.php' +); +Resolver::getInstance()->requireDataFixture( + 'Magento/ConfigurableProduct/_files/configurable_attribute_second_rollback.php' +); + +Resolver::getInstance()->requireDataFixture( + 'Magento/Catalog/_files/product_image_rollback.php' +); + +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', false); From bf014eb0e19b5f2f4a977aea3af9cb21e136fb1c Mon Sep 17 00:00:00 2001 From: Munkh-Ulzii Balidar <mbalidar@comwrap.com> Date: Tue, 6 Oct 2020 23:03:27 +0200 Subject: [PATCH 054/139] 29251 remove redundant dependency --- app/code/Magento/ConfigurableProductGraphQl/composer.json | 1 - app/code/Magento/ConfigurableProductGraphQl/etc/module.xml | 1 - 2 files changed, 2 deletions(-) diff --git a/app/code/Magento/ConfigurableProductGraphQl/composer.json b/app/code/Magento/ConfigurableProductGraphQl/composer.json index 73e134e1522a4..a6e1d1c822435 100644 --- a/app/code/Magento/ConfigurableProductGraphQl/composer.json +++ b/app/code/Magento/ConfigurableProductGraphQl/composer.json @@ -6,7 +6,6 @@ "php": "~7.3.0||~7.4.0", "magento/module-catalog": "*", "magento/module-configurable-product": "*", - "magento/module-eav": "*", "magento/module-graph-ql": "*", "magento/module-catalog-graph-ql": "*", "magento/module-quote": "*", diff --git a/app/code/Magento/ConfigurableProductGraphQl/etc/module.xml b/app/code/Magento/ConfigurableProductGraphQl/etc/module.xml index 36b6fd40eea15..3aa1658c9388d 100644 --- a/app/code/Magento/ConfigurableProductGraphQl/etc/module.xml +++ b/app/code/Magento/ConfigurableProductGraphQl/etc/module.xml @@ -10,7 +10,6 @@ <sequence> <module name="Magento_Catalog"/> <module name="Magento_ConfigurableProduct"/> - <module name="Magento_Eav"/> <module name="Magento_GraphQl"/> <module name="Magento_CatalogGraphQl"/> <module name="Magento_CatalogInventory"/> From e9e951d639b0f8eee1454385eaa24f628b4428c5 Mon Sep 17 00:00:00 2001 From: IvanPletnyov <ivan.pletnyov@transoftgroup.com> Date: Wed, 7 Oct 2020 11:36:45 +0300 Subject: [PATCH 055/139] MC-37102: Create automated test for "Create customer, with 2 websites and with different allowed countries" --- .../CreateAccountWithAddressTest.php | 113 ++++++++++++++++++ .../Model/Address/CreateAddressTest.php | 17 +++ 2 files changed, 130 insertions(+) create mode 100644 dev/tests/integration/testsuite/Magento/Customer/Model/AccountManagement/CreateAccountWithAddressTest.php diff --git a/dev/tests/integration/testsuite/Magento/Customer/Model/AccountManagement/CreateAccountWithAddressTest.php b/dev/tests/integration/testsuite/Magento/Customer/Model/AccountManagement/CreateAccountWithAddressTest.php new file mode 100644 index 0000000000000..351c84680389b --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Customer/Model/AccountManagement/CreateAccountWithAddressTest.php @@ -0,0 +1,113 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Customer\Model\AccountManagement; + +use Magento\Customer\Api\AccountManagementInterface; +use Magento\Customer\Api\CustomerRepositoryInterface; +use Magento\Customer\Api\Data\AddressInterfaceFactory; +use Magento\Customer\Api\Data\CustomerInterface; +use Magento\Customer\Api\Data\CustomerInterfaceFactory; +use Magento\Framework\ObjectManagerInterface; +use Magento\Framework\Registry; +use Magento\TestFramework\Helper\Bootstrap; +use PHPUnit\Framework\TestCase; + +/** + * Test for creation customer with address via customer account management service. + * + * @magentoDbIsolation enabled + */ +class CreateAccountWithAddressTest extends TestCase +{ + /** @var ObjectManagerInterface */ + private $objectManager; + + /** @var AccountManagementInterface */ + private $accountManagement; + + /** @var CustomerInterfaceFactory */ + private $customerFactory; + + /** @var CustomerRepositoryInterface */ + private $customerRepository; + + /** @var AddressInterfaceFactory */ + private $addressFactory; + + /** @var CustomerInterface */ + private $customer; + + /** @var Registry */ + private $registry; + + /** + * @inheritdoc + */ + protected function setUp(): void + { + parent::setUp(); + + $this->objectManager = Bootstrap::getObjectManager(); + $this->accountManagement = $this->objectManager->get(AccountManagementInterface::class); + $this->customerFactory = $this->objectManager->get(CustomerInterfaceFactory::class); + $this->customerRepository = $this->objectManager->get(CustomerRepositoryInterface::class); + $this->addressFactory = $this->objectManager->get(AddressInterfaceFactory::class); + $this->registry = $this->objectManager->get(Registry::class); + } + + /** + * @inheritdoc + */ + protected function tearDown(): void + { + if ($this->customer instanceof CustomerInterface) { + $this->registry->unregister('isSecureArea'); + $this->registry->register('isSecureArea', true); + $this->customerRepository->delete($this->customer); + $this->registry->unregister('isSecureArea'); + $this->registry->register('isSecureArea', false); + } + + parent::tearDown(); + } + + /** + * @magentoDataFixture Magento/Store/_files/second_website_with_store_group_and_store.php + * @magentoConfigFixture default_store general/country/allow BD,BB,AF + * @magentoConfigFixture fixture_second_store_store general/country/allow AS,BM + * @return void + */ + public function testCreateNewCustomerWithAddress(): void + { + $availableCountry = 'BD'; + $address = $this->addressFactory->create(); + $address->setCountryId($availableCountry) + ->setPostcode('75477') + ->setRegionId(1) + ->setStreet(['Green str, 67']) + ->setTelephone('3468676') + ->setCity('CityM') + ->setFirstname('John') + ->setLastname('Smith') + ->setIsDefaultShipping(true) + ->setIsDefaultBilling(true); + $customerEntity = $this->customerFactory->create(); + $customerEntity->setEmail('test@example.com') + ->setFirstname('John') + ->setLastname('Smith') + ->setStoreId(1); + $customerEntity->setAddresses([$address]); + $this->customer = $this->accountManagement->createAccount($customerEntity); + $this->assertCount(1, $this->customer->getAddresses(), 'The available address wasn\'t saved.'); + $this->assertSame( + $availableCountry, + $this->customer->getAddresses()[0]->getCountryId(), + 'The address was saved with disallowed country.' + ); + } +} diff --git a/dev/tests/integration/testsuite/Magento/Customer/Model/Address/CreateAddressTest.php b/dev/tests/integration/testsuite/Magento/Customer/Model/Address/CreateAddressTest.php index eb638eeb329aa..79f8b1466d8d3 100644 --- a/dev/tests/integration/testsuite/Magento/Customer/Model/Address/CreateAddressTest.php +++ b/dev/tests/integration/testsuite/Magento/Customer/Model/Address/CreateAddressTest.php @@ -424,6 +424,23 @@ public function testAddressCreatedWithGroupAssignByVatIdWithError(): void $this->assertEquals(2, $this->getCustomerGroupId('customer5@example.com')); } + /** + * @magentoDataFixture Magento/Customer/_files/customer_no_address.php + * @magentoDataFixture Magento/Store/_files/second_website_with_store_group_and_store.php + * @magentoConfigFixture default_store general/country/allow BD,BB,AF + * @magentoConfigFixture fixture_second_store_store general/country/allow AS,BM + * + * @return void + */ + public function testCreateAvailableAddress(): void + { + $countryId = 'BB'; + $addressData = array_merge(self::STATIC_CUSTOMER_ADDRESS_DATA, [AddressInterface::COUNTRY_ID => $countryId]); + $customer = $this->customerRepository->get('customer5@example.com'); + $address = $this->createAddress((int)$customer->getId(), $addressData); + $this->assertSame($countryId, $address->getCountryId()); + } + /** * Create customer address with provided address data. * From d00db30ea32c35aa6b5d76ca080a2f5b77c84ee7 Mon Sep 17 00:00:00 2001 From: IvanPletnyov <ivan.pletnyov@transoftgroup.com> Date: Fri, 9 Oct 2020 12:23:57 +0300 Subject: [PATCH 056/139] MC-37543: Create automated test for "Add static block on a category page" --- .../Catalog/Block/Category/ViewTest.php | 91 +++++++++++++++++++ .../Category/Save/SaveCategoryTest.php | 83 +++++++++++++++++ .../_files/category_with_cms_block.php | 46 ++++++++++ .../category_with_cms_block_rollback.php | 32 +++++++ 4 files changed, 252 insertions(+) create mode 100644 dev/tests/integration/testsuite/Magento/Catalog/Block/Category/ViewTest.php create mode 100644 dev/tests/integration/testsuite/Magento/Catalog/Controller/Adminhtml/Category/Save/SaveCategoryTest.php create mode 100644 dev/tests/integration/testsuite/Magento/Catalog/_files/category_with_cms_block.php create mode 100644 dev/tests/integration/testsuite/Magento/Catalog/_files/category_with_cms_block_rollback.php diff --git a/dev/tests/integration/testsuite/Magento/Catalog/Block/Category/ViewTest.php b/dev/tests/integration/testsuite/Magento/Catalog/Block/Category/ViewTest.php new file mode 100644 index 0000000000000..d08af2b85a67b --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Catalog/Block/Category/ViewTest.php @@ -0,0 +1,91 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Catalog\Block\Category; + +use Magento\Catalog\Api\CategoryRepositoryInterface; +use Magento\Catalog\Api\Data\CategoryInterface; +use Magento\Framework\ObjectManagerInterface; +use Magento\Framework\Registry; +use Magento\Framework\View\LayoutInterface; +use Magento\TestFramework\Catalog\Model\GetCategoryByName; +use Magento\TestFramework\Helper\Bootstrap; +use PHPUnit\Framework\TestCase; + +/** + * Test for view category block. + * + * @magentoAppArea frontend + * @magentoDbIsolation enabled + */ +class ViewTest extends TestCase +{ + /** @var ObjectManagerInterface */ + private $objectManager; + + /** @var Registry */ + private $registry; + + /** @var GetCategoryByName */ + private $getCategoryByName; + + /** @var CategoryRepositoryInterface */ + private $categoryRepository; + + /** @var LayoutInterface */ + private $layout; + + /** + * @inheritdoc + */ + protected function setUp(): void + { + parent::setUp(); + + $this->objectManager = Bootstrap::getObjectManager(); + $this->registry = $this->objectManager->get(Registry::class); + $this->getCategoryByName = $this->objectManager->get(GetCategoryByName::class); + $this->categoryRepository = $this->objectManager->get(CategoryRepositoryInterface::class); + $this->layout = $this->objectManager->get(LayoutInterface::class); + } + + /** + * @inheritdoc + */ + protected function tearDown(): void + { + $this->registry->unregister('current_category'); + + parent::tearDown(); + } + + /** + * @magentoDataFixture Magento/Catalog/_files/category_with_cms_block.php + * + * @return void + */ + public function testCmsBlockDisplayedOnCategory(): void + { + $categoryId = $this->getCategoryByName->execute('Category with cms block')->getId(); + $category = $this->categoryRepository->get($categoryId, 1); + $this->registerCategory($category); + $block = $this->layout->createBlock(View::class)->setTemplate('Magento_Catalog::category/cms.phtml'); + $this->assertStringContainsString('<h1>Fixture Block Title</h1>', $block->toHtml()); + } + + /** + * Register category in registry + * + * @param CategoryInterface $category + * @return void + */ + private function registerCategory(CategoryInterface $category): void + { + $this->registry->unregister('current_category'); + $this->registry->register('current_category', $category); + } +} diff --git a/dev/tests/integration/testsuite/Magento/Catalog/Controller/Adminhtml/Category/Save/SaveCategoryTest.php b/dev/tests/integration/testsuite/Magento/Catalog/Controller/Adminhtml/Category/Save/SaveCategoryTest.php new file mode 100644 index 0000000000000..36641e010dfc6 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Catalog/Controller/Adminhtml/Category/Save/SaveCategoryTest.php @@ -0,0 +1,83 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Catalog\Controller\Adminhtml\Category\Save; + +use Magento\Catalog\Api\CategoryRepositoryInterface; +use Magento\Catalog\Api\Data\CategoryInterface; +use Magento\Catalog\Model\Category; +use Magento\Cms\Api\GetBlockByIdentifierInterface; +use Magento\Framework\Exception\NoSuchEntityException; + +/** + * Test cases related to save category with enabled category flat. + * + * @magentoAppArea adminhtml + * @magentoDbIsolation disabled + */ +class SaveCategoryTest extends AbstractSaveCategoryTest +{ + /** @var CategoryRepositoryInterface */ + private $categoryRepository; + + /** @var GetBlockByIdentifierInterface */ + private $getBlockByIdentifier; + + /** @var string */ + private $createdCategoryId; + + /** + * @inheritdoc + */ + protected function setUp(): void + { + parent::setUp(); + + $this->categoryRepository = $this->_objectManager->get(CategoryRepositoryInterface::class); + $this->getBlockByIdentifier = $this->_objectManager->get(GetBlockByIdentifierInterface::class); + } + + /** + * @inheritdoc + */ + protected function tearDown(): void + { + try { + $this->categoryRepository->deleteByIdentifier($this->createdCategoryId); + } catch (NoSuchEntityException $e) { + //Category already deleted. + } + $this->createdCategoryId = null; + + parent::tearDown(); + + } + + /** + * @magentoDataFixture Magento/Cms/_files/block.php + * + * @return void + */ + public function testCreateCategoryWithCmsBlock(): void + { + $blockId = $this->getBlockByIdentifier->execute('fixture_block', 1)->getId(); + $postData = [ + CategoryInterface::KEY_NAME => 'Category with cms block', + CategoryInterface::KEY_IS_ACTIVE => 1, + CategoryInterface::KEY_INCLUDE_IN_MENU => 1, + 'display_mode' => Category::DM_MIXED, + 'landing_page' => $blockId, + 'available_sort_by' => 1, + 'default_sort_by' => 1, + ]; + $responseData = $this->performSaveCategoryRequest($postData); + $this->assertRequestIsSuccessfullyPerformed($responseData); + $this->createdCategoryId = $responseData['category']['entity_id']; + $category = $this->categoryRepository->get($this->createdCategoryId); + $this->assertEquals($blockId, $category->getLandingPage()); + } +} diff --git a/dev/tests/integration/testsuite/Magento/Catalog/_files/category_with_cms_block.php b/dev/tests/integration/testsuite/Magento/Catalog/_files/category_with_cms_block.php new file mode 100644 index 0000000000000..03eb767741579 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Catalog/_files/category_with_cms_block.php @@ -0,0 +1,46 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\Catalog\Api\CategoryRepositoryInterface; +use Magento\Catalog\Api\Data\CategoryInterfaceFactory; +use Magento\Catalog\Model\Category; +use Magento\Cms\Api\GetBlockByIdentifierInterface; +use Magento\Store\Model\Store; +use Magento\Store\Model\StoreManagerInterface; +use Magento\TestFramework\Helper\Bootstrap; +use Magento\Catalog\Helper\DefaultCategory; +use Magento\TestFramework\Workaround\Override\Fixture\Resolver; + +Resolver::getInstance()->requireDataFixture('Magento/Cms/_files/block.php'); + +$objectManager = Bootstrap::getObjectManager(); +/** @var StoreManagerInterface $storeManager */ +$storeManager = $objectManager->get(StoreManagerInterface::class); +/** @var CategoryInterfaceFactory $categoryFactory */ +$categoryFactory = $objectManager->get(CategoryInterfaceFactory::class); +/** @var CategoryRepositoryInterface $categoryRepository */ +$categoryRepository = $objectManager->get(CategoryRepositoryInterface::class); +/** @var DefaultCategory $categoryHelper */ +$categoryHelper = $objectManager->get(DefaultCategory::class); +$currentStoreId = (int)$storeManager->getStore()->getId(); +/** @var GetBlockByIdentifierInterface $getBlockByIdentifierInterface */ +$getBlockByIdentifier = $objectManager->get(GetBlockByIdentifierInterface::class); +$block = $getBlockByIdentifier->execute('fixture_block', $currentStoreId); + +$storeManager->setCurrentStore(Store::DEFAULT_STORE_ID); +$category = $categoryFactory->create(); +$category->setName('Category with cms block') + ->setParentId($categoryHelper->getId()) + ->setLevel(2) + ->setAvailableSortBy('name') + ->setDefaultSortBy('name') + ->setIsActive(true) + ->setPosition(1) + ->setDisplayMode(Category::DM_MIXED) + ->setLandingPage($block->getId()); +$categoryRepository->save($category); +$storeManager->setCurrentStore($currentStoreId); diff --git a/dev/tests/integration/testsuite/Magento/Catalog/_files/category_with_cms_block_rollback.php b/dev/tests/integration/testsuite/Magento/Catalog/_files/category_with_cms_block_rollback.php new file mode 100644 index 0000000000000..4725fde47818c --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Catalog/_files/category_with_cms_block_rollback.php @@ -0,0 +1,32 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\Catalog\Api\CategoryRepositoryInterface; +use Magento\Framework\Registry; +use Magento\TestFramework\Catalog\Model\GetCategoryByName; +use Magento\TestFramework\Helper\Bootstrap; +use Magento\TestFramework\Workaround\Override\Fixture\Resolver; + +$objectManager = Bootstrap::getObjectManager(); +/** @var Registry $registry */ +$registry = $objectManager->get(Registry::class); +/** @var CategoryRepositoryInterface $categoryRepository */ +$categoryRepository = $objectManager->get(CategoryRepositoryInterface::class); +/** @var GetCategoryByName $getCategoryByName */ +$getCategoryByName = $objectManager->get(GetCategoryByName::class); +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', true); + +$category = $getCategoryByName->execute('Category with cms block'); +if ($category->getId()) { + $categoryRepository->delete($category); +} + +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', false); + +Resolver::getInstance()->requireDataFixture('Magento/Cms/_files/block_rollback.php'); From fc47ae2420d64f3f070007a43335781d466340c5 Mon Sep 17 00:00:00 2001 From: Viktor Kopin <viktor.kopin@transoftgroup.com> Date: Fri, 9 Oct 2020 15:10:01 +0300 Subject: [PATCH 057/139] MC-37665: Updating a category through the REST API will uncheck "Use Default Value" on a bunch of attributes --- .../Catalog/Model/CategoryRepository.php | 57 +++++--- .../CategoryRepository/PopulateWithValues.php | 123 ++++++++++++++++++ .../Catalog/Api/CategoryRepositoryTest.php | 97 +++++++++++++- 3 files changed, 252 insertions(+), 25 deletions(-) create mode 100644 app/code/Magento/Catalog/Model/CategoryRepository/PopulateWithValues.php diff --git a/app/code/Magento/Catalog/Model/CategoryRepository.php b/app/code/Magento/Catalog/Model/CategoryRepository.php index 0ce52b966c32c..fe3ae4cc468a1 100644 --- a/app/code/Magento/Catalog/Model/CategoryRepository.php +++ b/app/code/Magento/Catalog/Model/CategoryRepository.php @@ -7,10 +7,16 @@ namespace Magento\Catalog\Model; +use Magento\Catalog\Model\CategoryRepository\PopulateWithValues; +use Magento\Catalog\Model\ResourceModel\Category as CategoryResource; +use Magento\Framework\Api\ExtensibleDataObjectConverter; +use Magento\Framework\App\ObjectManager; +use Magento\Framework\EntityManager\MetadataPool; use Magento\Framework\Exception\CouldNotSaveException; use Magento\Framework\Exception\NoSuchEntityException; use Magento\Framework\Exception\StateException; use Magento\Catalog\Api\Data\CategoryInterface; +use Magento\Store\Model\StoreManagerInterface; /** * Repository for categories. @@ -25,27 +31,27 @@ class CategoryRepository implements \Magento\Catalog\Api\CategoryRepositoryInter protected $instances = []; /** - * @var \Magento\Store\Model\StoreManagerInterface + * @var StoreManagerInterface */ protected $storeManager; /** - * @var \Magento\Catalog\Model\CategoryFactory + * @var CategoryFactory */ protected $categoryFactory; /** - * @var \Magento\Catalog\Model\ResourceModel\Category + * @var CategoryResource */ protected $categoryResource; /** - * @var \Magento\Framework\EntityManager\MetadataPool + * @var MetadataPool */ protected $metadataPool; /** - * @var \Magento\Framework\Api\ExtensibleDataObjectConverter + * @var ExtensibleDataObjectConverter */ private $extensibleDataObjectConverter; @@ -57,28 +63,37 @@ class CategoryRepository implements \Magento\Catalog\Api\CategoryRepositoryInter protected $useConfigFields = ['available_sort_by', 'default_sort_by', 'filter_price_range']; /** - * @param \Magento\Catalog\Model\CategoryFactory $categoryFactory - * @param \Magento\Catalog\Model\ResourceModel\Category $categoryResource - * @param \Magento\Store\Model\StoreManagerInterface $storeManager + * @var PopulateWithValues + */ + private $populateWithValues; + + /** + * @param CategoryFactory $categoryFactory + * @param CategoryResource $categoryResource + * @param StoreManagerInterface $storeManager + * @param PopulateWithValues $populateWithValues */ public function __construct( - \Magento\Catalog\Model\CategoryFactory $categoryFactory, - \Magento\Catalog\Model\ResourceModel\Category $categoryResource, - \Magento\Store\Model\StoreManagerInterface $storeManager + CategoryFactory $categoryFactory, + CategoryResource $categoryResource, + StoreManagerInterface $storeManager, + PopulateWithValues $populateWithValues ) { $this->categoryFactory = $categoryFactory; $this->categoryResource = $categoryResource; $this->storeManager = $storeManager; + $objectManager = ObjectManager::getInstance(); + $this->populateWithValues = $populateWithValues ?? $objectManager->get(PopulateWithValues::class); } /** * @inheritdoc */ - public function save(\Magento\Catalog\Api\Data\CategoryInterface $category) + public function save(CategoryInterface $category) { $storeId = (int)$this->storeManager->getStore()->getId(); $existingData = $this->getExtensibleDataObjectConverter() - ->toNestedArray($category, [], \Magento\Catalog\Api\Data\CategoryInterface::class); + ->toNestedArray($category, [], CategoryInterface::class); $existingData = array_diff_key($existingData, array_flip(['path', 'level', 'parent_id'])); $existingData['store_id'] = $storeId; @@ -110,7 +125,7 @@ public function save(\Magento\Catalog\Api\Data\CategoryInterface $category) $existingData['parent_id'] = $parentId; $existingData['level'] = null; } - $category->addData($existingData); + $this->populateWithValues->execute($category, $existingData); try { $this->validateCategory($category); $this->categoryResource->save($category); @@ -151,7 +166,7 @@ public function get($categoryId, $storeId = null) /** * @inheritdoc */ - public function delete(\Magento\Catalog\Api\Data\CategoryInterface $category) + public function delete(CategoryInterface $category) { try { $categoryId = $category->getId(); @@ -213,15 +228,15 @@ protected function validateCategory(Category $category) /** * Lazy loader for the converter. * - * @return \Magento\Framework\Api\ExtensibleDataObjectConverter + * @return ExtensibleDataObjectConverter * * @deprecated 101.0.0 */ private function getExtensibleDataObjectConverter() { if ($this->extensibleDataObjectConverter === null) { - $this->extensibleDataObjectConverter = \Magento\Framework\App\ObjectManager::getInstance() - ->get(\Magento\Framework\Api\ExtensibleDataObjectConverter::class); + $this->extensibleDataObjectConverter = ObjectManager::getInstance() + ->get(ExtensibleDataObjectConverter::class); } return $this->extensibleDataObjectConverter; } @@ -229,13 +244,13 @@ private function getExtensibleDataObjectConverter() /** * Lazy loader for the metadata pool. * - * @return \Magento\Framework\EntityManager\MetadataPool + * @return MetadataPool */ private function getMetadataPool() { if (null === $this->metadataPool) { - $this->metadataPool = \Magento\Framework\App\ObjectManager::getInstance() - ->get(\Magento\Framework\EntityManager\MetadataPool::class); + $this->metadataPool = ObjectManager::getInstance() + ->get(MetadataPool::class); } return $this->metadataPool; } diff --git a/app/code/Magento/Catalog/Model/CategoryRepository/PopulateWithValues.php b/app/code/Magento/Catalog/Model/CategoryRepository/PopulateWithValues.php new file mode 100644 index 0000000000000..6fdde51bd60de --- /dev/null +++ b/app/code/Magento/Catalog/Model/CategoryRepository/PopulateWithValues.php @@ -0,0 +1,123 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Catalog\Model\CategoryRepository; + +use Magento\Catalog\Api\Data\CategoryInterface; +use Magento\Catalog\Model\Attribute\ScopeOverriddenValue; +use Magento\Catalog\Model\Category; +use Magento\Eav\Api\AttributeRepositoryInterface as AttributeRepository; +use Magento\Eav\Api\Data\AttributeInterface; +use Magento\Eav\Model\Entity\Attribute\ScopedAttributeInterface; +use Magento\Framework\Api\FilterBuilder; +use Magento\Framework\Api\SearchCriteriaBuilder; + +/** + * Add data to category entity and populate with default values + */ +class PopulateWithValues +{ + /** + * @var ScopeOverriddenValue + */ + private $scopeOverriddenValue; + + /** + * @var AttributeRepository + */ + private $attributeRepository; + + /** + * @var SearchCriteriaBuilder + */ + private $searchCriteriaBuilder; + + /** + * @var FilterBuilder + */ + private $filterBuilder; + + /** + * @param ScopeOverriddenValue $scopeOverriddenValue + * @param AttributeRepository $attributeRepository + * @param SearchCriteriaBuilder $searchCriteriaBuilder + * @param FilterBuilder $filterBuilder + */ + public function __construct( + ScopeOverriddenValue $scopeOverriddenValue, + AttributeRepository $attributeRepository, + SearchCriteriaBuilder $searchCriteriaBuilder, + FilterBuilder $filterBuilder + ) { + $this->scopeOverriddenValue = $scopeOverriddenValue; + $this->attributeRepository = $attributeRepository; + $this->searchCriteriaBuilder = $searchCriteriaBuilder; + $this->filterBuilder = $filterBuilder; + } + + /** + * Set null to entity default values + * + * @param CategoryInterface $category + * @param array $existingData + * @return void + */ + public function execute(CategoryInterface $category, array $existingData): void + { + $storeId = $existingData['store_id']; + $overriddenValues = array_filter($category->getData(), function ($key) use ($category, $storeId) { + /** @var Category $category */ + return $this->scopeOverriddenValue->containsValue( + CategoryInterface::class, + $category, + $key, + $storeId + ); + }, ARRAY_FILTER_USE_KEY); + $defaultValues = array_diff_key($category->getData(), $overriddenValues); + array_walk($defaultValues, function (&$value, $key) { + $attributes = $this->getAttributes(); + if (isset($attributes[$key]) && !$attributes[$key]->isStatic()) { + $value = null; + } + }); + $category->addData($defaultValues); + $category->addData($existingData); + $useDefaultAttributes = array_filter($category->getData(), function ($attributeValue) { + return null === $attributeValue; + }); + $category->setData('use_default', array_map(function () { + return true; + }, $useDefaultAttributes)); + } + + /** + * Returns entity attributes. + * + * @return AttributeInterface[] + */ + private function getAttributes() + { + $searchResult = $this->attributeRepository->getList( + $this->searchCriteriaBuilder->addFilters( + [ + $this->filterBuilder + ->setField('is_global') + ->setConditionType('in') + ->setValue([ScopedAttributeInterface::SCOPE_STORE, ScopedAttributeInterface::SCOPE_WEBSITE]) + ->create() + ] + )->create() + ); + $result = []; + foreach ($searchResult->getItems() as $attribute) { + $result[$attribute->getAttributeCode()] = $attribute; + } + + return $result; + } +} diff --git a/dev/tests/api-functional/testsuite/Magento/Catalog/Api/CategoryRepositoryTest.php b/dev/tests/api-functional/testsuite/Magento/Catalog/Api/CategoryRepositoryTest.php index fd0519ab2b34e..aaf03e82551fd 100644 --- a/dev/tests/api-functional/testsuite/Magento/Catalog/Api/CategoryRepositoryTest.php +++ b/dev/tests/api-functional/testsuite/Magento/Catalog/Api/CategoryRepositoryTest.php @@ -10,10 +10,14 @@ use Magento\Authorization\Model\RoleFactory; use Magento\Authorization\Model\Rules; use Magento\Authorization\Model\RulesFactory; +use Magento\Catalog\Api\Data\CategoryInterface; +use Magento\Catalog\Model\Attribute\ScopeOverriddenValue; use Magento\CatalogUrlRewrite\Model\CategoryUrlRewriteGenerator; use Magento\Integration\Api\AdminTokenServiceInterface; +use Magento\Store\Model\Store; use Magento\TestFramework\Helper\Bootstrap; use Magento\TestFramework\TestCase\WebapiAbstract; +use Magento\UrlRewrite\Model\Storage\DbStorage; use Magento\UrlRewrite\Service\V1\Data\UrlRewrite; /** @@ -145,8 +149,8 @@ public function testCreate() */ public function testDelete() { - /** @var \Magento\UrlRewrite\Model\Storage\DbStorage $storage */ - $storage = Bootstrap::getObjectManager()->get(\Magento\UrlRewrite\Model\Storage\DbStorage::class); + /** @var DbStorage $storage */ + $storage = Bootstrap::getObjectManager()->get(DbStorage::class); $categoryId = $this->modelId; $data = [ UrlRewrite::ENTITY_ID => $categoryId, @@ -290,7 +294,7 @@ public function testUpdateUrlKey() $this->assertEquals("Update Category Test New Name", $category->getName()); // check for the url rewrite for the new name - $storage = Bootstrap::getObjectManager()->get(\Magento\UrlRewrite\Model\Storage\DbStorage::class); + $storage = Bootstrap::getObjectManager()->get(DbStorage::class); $data = [ UrlRewrite::ENTITY_ID => $categoryId, UrlRewrite::ENTITY_TYPE => CategoryUrlRewriteGenerator::ENTITY_TYPE, @@ -307,7 +311,7 @@ public function testUpdateUrlKey() $this->assertEquals('update-category-test-new-name.html', $urlRewrite->getRequestPath()); // check for the forward from the old name to the new name - $storage = Bootstrap::getObjectManager()->get(\Magento\UrlRewrite\Model\Storage\DbStorage::class); + $storage = Bootstrap::getObjectManager()->get(DbStorage::class); $data = [ UrlRewrite::ENTITY_ID => $categoryId, UrlRewrite::ENTITY_TYPE => CategoryUrlRewriteGenerator::ENTITY_TYPE, @@ -557,6 +561,91 @@ public function testSaveDesign(): void $this->createdCategories = [$result['id']]; } + /** + * Check if repository does not override default values for attributes out of request + * + * @throws \Exception + * @magentoApiDataFixture Magento/Catalog/_files/category.php + */ + public function testUpdateScopeAttribute() + { + $categoryId = 333; + $categoryData = [ + 'name' => 'Scope Specific Value', + ]; + $result = $this->updateCategoryForSpecificStore($categoryId, $categoryData); + $this->assertEquals($categoryId, $result['id']); + + /** @var \Magento\Catalog\Model\Category $model */ + $model = Bootstrap::getObjectManager()->get(\Magento\Catalog\Model\Category::class); + $category = $model->load($categoryId); + + /** @var ScopeOverriddenValue $scopeOverriddenValue */ + $scopeOverriddenValue = Bootstrap::getObjectManager()->get(ScopeOverriddenValue::class); + self::assertTrue($scopeOverriddenValue->containsValue( + CategoryInterface::class, + $category, + 'name', + Store::DISTRO_STORE_ID + ), 'Name is not saved for specific store'); + self::assertFalse($scopeOverriddenValue->containsValue( + CategoryInterface::class, + $category, + 'is_active', + Store::DISTRO_STORE_ID + ), 'is_active is overriden for default store'); + self::assertFalse($scopeOverriddenValue->containsValue( + CategoryInterface::class, + $category, + 'url_key', + Store::DISTRO_STORE_ID + ), 'url_key is overriden for default store'); + + $this->deleteCategory($categoryId); + } + + /** + * Update given category via web API for specific store code. + * + * @param int $id + * @param array $data + * @param string|null $token + * @param string $storeCode + * @return array + */ + protected function updateCategoryForSpecificStore( + int $id, + array $data, + ?string $token = null, + string $storeCode = 'default' + ) { + $serviceInfo = + [ + 'rest' => [ + 'resourcePath' => self::RESOURCE_PATH . '/' . $id, + 'httpMethod' => \Magento\Framework\Webapi\Rest\Request::HTTP_METHOD_PUT, + ], + 'soap' => [ + 'service' => self::SERVICE_NAME, + 'serviceVersion' => 'V1', + 'operation' => self::SERVICE_NAME . 'Save', + ], + ]; + if ($token) { + $serviceInfo['rest']['token'] = $serviceInfo['soap']['token'] = $token; + } + + if (TESTS_WEB_API_ADAPTER == self::ADAPTER_SOAP) { + $data['id'] = $id; + + return $this->_webApiCall($serviceInfo, ['id' => $id, 'category' => $data], null, $storeCode); + } else { + $data['id'] = $id; + + return $this->_webApiCall($serviceInfo, ['id' => $id, 'category' => $data], null, $storeCode); + } + } + /** * @inheritDoc * From 1fd701539fef71e05bdcfd7bcf5a09671482d199 Mon Sep 17 00:00:00 2001 From: Pavel Bystritsky <engcom-vendorworker-foxtrot@adobe.com> Date: Fri, 9 Oct 2020 16:18:07 +0300 Subject: [PATCH 058/139] magento/magento2#26967: Fix for queue numeric argument conversion - integration test update. --- .../Magento/Framework/MessageQueue/TopologyTest.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/dev/tests/integration/testsuite/Magento/Framework/MessageQueue/TopologyTest.php b/dev/tests/integration/testsuite/Magento/Framework/MessageQueue/TopologyTest.php index c8600e1e38faa..5c85d6ddb7c70 100644 --- a/dev/tests/integration/testsuite/Magento/Framework/MessageQueue/TopologyTest.php +++ b/dev/tests/integration/testsuite/Magento/Framework/MessageQueue/TopologyTest.php @@ -48,7 +48,7 @@ public function testTopologyInstallation(array $expectedConfig, array $bindingCo $this->assertArrayHasKey($name, $this->declaredExchanges); unset($this->declaredExchanges[$name]['message_stats']); unset($this->declaredExchanges[$name]['user_who_performed_action']); - $this->assertEquals( + $this->assertSame( $expectedConfig, $this->declaredExchanges[$name], 'Invalid exchange configuration: ' . $name @@ -59,7 +59,7 @@ public function testTopologyInstallation(array $expectedConfig, array $bindingCo unset($value['properties_key']); return $value; }, $bindings); - $this->assertEquals( + $this->assertSame( $bindingConfig, $bindings, 'Invalid exchange bindings configuration: ' . $name @@ -121,7 +121,7 @@ public function exchangeDataProvider() 'arguments' => [ 'argument1' => 'value', 'argument2' => true, - 'argument3' => '150', + 'argument3' => 150, ], ], ] From 78dd01ad38fdd662f954c933c9b8ea77d63e1a10 Mon Sep 17 00:00:00 2001 From: Jeroen <jeroen@reachdigital.nl> Date: Fri, 4 Sep 2020 16:34:59 +0200 Subject: [PATCH 059/139] Check if copy method is copy before sending comment email --- app/code/Magento/Sales/Model/Order/Email/NotifySender.php | 2 +- .../Unit/Model/Order/Email/Sender/AbstractSenderTest.php | 2 +- .../Order/Email/Sender/CreditmemoCommentSenderTest.php | 7 +++++-- .../Model/Order/Email/Sender/InvoiceCommentSenderTest.php | 7 +++++-- .../Model/Order/Email/Sender/ShipmentCommentSenderTest.php | 7 +++++-- 5 files changed, 17 insertions(+), 8 deletions(-) diff --git a/app/code/Magento/Sales/Model/Order/Email/NotifySender.php b/app/code/Magento/Sales/Model/Order/Email/NotifySender.php index 9e40ab769b0a6..1bb3053bf751f 100644 --- a/app/code/Magento/Sales/Model/Order/Email/NotifySender.php +++ b/app/code/Magento/Sales/Model/Order/Email/NotifySender.php @@ -35,7 +35,7 @@ protected function checkAndSend(Order $order, $notify = true) if ($notify) { $sender->send(); - } else { + } elseif ($this->identityContainer->getCopyMethod() === 'copy') { // Email copies are sent as separated emails if their copy method // is 'copy' or a customer should not be notified $sender->sendCopyTo(); diff --git a/app/code/Magento/Sales/Test/Unit/Model/Order/Email/Sender/AbstractSenderTest.php b/app/code/Magento/Sales/Test/Unit/Model/Order/Email/Sender/AbstractSenderTest.php index b826e058b679e..0494616b8cefd 100644 --- a/app/code/Magento/Sales/Test/Unit/Model/Order/Email/Sender/AbstractSenderTest.php +++ b/app/code/Magento/Sales/Test/Unit/Model/Order/Email/Sender/AbstractSenderTest.php @@ -192,7 +192,7 @@ public function stepIdentityContainerInit($identityMockClassName) { $this->identityContainerMock = $this->getMockBuilder($identityMockClassName) ->disableOriginalConstructor() - ->onlyMethods(['getStore', 'isEnabled', 'getConfigValue', 'getTemplateId', 'getGuestTemplateId']) + ->onlyMethods(['getStore', 'isEnabled', 'getConfigValue', 'getTemplateId', 'getGuestTemplateId', 'getCopyMethod']) ->getMock(); $this->identityContainerMock->expects($this->any()) ->method('getStore') diff --git a/app/code/Magento/Sales/Test/Unit/Model/Order/Email/Sender/CreditmemoCommentSenderTest.php b/app/code/Magento/Sales/Test/Unit/Model/Order/Email/Sender/CreditmemoCommentSenderTest.php index 0ed2a379f5b73..d191ae9356236 100644 --- a/app/code/Magento/Sales/Test/Unit/Model/Order/Email/Sender/CreditmemoCommentSenderTest.php +++ b/app/code/Magento/Sales/Test/Unit/Model/Order/Email/Sender/CreditmemoCommentSenderTest.php @@ -97,7 +97,7 @@ public function testSendVirtualOrder() $this->assertFalse($result); } - public function testSendTrueWithCustomerCopy() + public function testSendTrueWithoutCustomerCopy() { $billingAddress = $this->addressMock; $comment = 'comment_test'; @@ -140,7 +140,7 @@ public function testSendTrueWithCustomerCopy() $this->assertTrue($result); } - public function testSendTrueWithoutCustomerCopy() + public function testSendTrueWithCustomerCopy() { $billingAddress = $this->addressMock; $comment = 'comment_test'; @@ -161,6 +161,9 @@ public function testSendTrueWithoutCustomerCopy() $this->identityContainerMock->expects($this->once()) ->method('isEnabled') ->willReturn(true); + $this->identityContainerMock->expects($this->once()) + ->method('getCopyMethod') + ->willReturn('copy'); $this->templateContainerMock->expects($this->once()) ->method('setTemplateVars') ->with( diff --git a/app/code/Magento/Sales/Test/Unit/Model/Order/Email/Sender/InvoiceCommentSenderTest.php b/app/code/Magento/Sales/Test/Unit/Model/Order/Email/Sender/InvoiceCommentSenderTest.php index 68f2bd7b1a628..9e4e89766dfe1 100644 --- a/app/code/Magento/Sales/Test/Unit/Model/Order/Email/Sender/InvoiceCommentSenderTest.php +++ b/app/code/Magento/Sales/Test/Unit/Model/Order/Email/Sender/InvoiceCommentSenderTest.php @@ -65,7 +65,7 @@ public function testSendFalse() $this->assertFalse($result); } - public function testSendTrueWithCustomerCopy() + public function testSendTrueWithoutCustomerCopy() { $billingAddress = $this->addressMock; $this->stepAddressFormat($billingAddress); @@ -110,7 +110,7 @@ public function testSendTrueWithCustomerCopy() $this->assertTrue($result); } - public function testSendTrueWithoutCustomerCopy() + public function testSendTrueWithCustomerCopy() { $billingAddress = $this->addressMock; $customerName = 'Test Customer'; @@ -132,6 +132,9 @@ public function testSendTrueWithoutCustomerCopy() $this->identityContainerMock->expects($this->once()) ->method('isEnabled') ->willReturn(true); + $this->identityContainerMock->expects($this->once()) + ->method('getCopyMethod') + ->willReturn('copy'); $this->templateContainerMock->expects($this->once()) ->method('setTemplateVars') ->with( diff --git a/app/code/Magento/Sales/Test/Unit/Model/Order/Email/Sender/ShipmentCommentSenderTest.php b/app/code/Magento/Sales/Test/Unit/Model/Order/Email/Sender/ShipmentCommentSenderTest.php index 5421991fae848..91e7ae18d3550 100644 --- a/app/code/Magento/Sales/Test/Unit/Model/Order/Email/Sender/ShipmentCommentSenderTest.php +++ b/app/code/Magento/Sales/Test/Unit/Model/Order/Email/Sender/ShipmentCommentSenderTest.php @@ -58,7 +58,7 @@ public function testSendFalse() $this->assertFalse($result); } - public function testSendTrueWithCustomerCopy() + public function testSendTrueWithoutCustomerCopy() { $billingAddress = $this->addressMock; $comment = 'comment_test'; @@ -101,7 +101,7 @@ public function testSendTrueWithCustomerCopy() $this->assertTrue($result); } - public function testSendTrueWithoutCustomerCopy() + public function testSendTrueWithCustomerCopy() { $billingAddress = $this->addressMock; $comment = 'comment_test'; @@ -116,6 +116,9 @@ public function testSendTrueWithoutCustomerCopy() $this->identityContainerMock->expects($this->once()) ->method('isEnabled') ->willReturn(true); + $this->identityContainerMock->expects($this->once()) + ->method('getCopyMethod') + ->willReturn('copy'); $this->orderMock->expects($this->any()) ->method('getCustomerName') ->willReturn($customerName); From af2d29a16e8b7bc30a872d9e20914bdae5b24111 Mon Sep 17 00:00:00 2001 From: Sunil Patel <patelsunil42@gmail.com> Date: Sat, 10 Oct 2020 07:29:20 +0000 Subject: [PATCH 060/139] magento/magento2#30388 : fix js error on edit review page --- .../Magento/Review/Block/Adminhtml/Edit.php | 37 +++++++++++-------- 1 file changed, 21 insertions(+), 16 deletions(-) diff --git a/app/code/Magento/Review/Block/Adminhtml/Edit.php b/app/code/Magento/Review/Block/Adminhtml/Edit.php index c85374edb8d98..15110f4f89cad 100644 --- a/app/code/Magento/Review/Block/Adminhtml/Edit.php +++ b/app/code/Magento/Review/Block/Adminhtml/Edit.php @@ -3,6 +3,7 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + namespace Magento\Review\Block\Adminhtml; /** @@ -44,7 +45,8 @@ public function __construct( \Magento\Review\Helper\Action\Pager $reviewActionPager, \Magento\Framework\Registry $registry, array $data = [] - ) { + ) + { $this->_coreRegistry = $registry; $this->_reviewActionPager = $reviewActionPager; $this->_reviewFactory = $reviewFactory; @@ -78,12 +80,12 @@ protected function _construct() [ 'label' => __('Previous'), 'onclick' => 'setLocation(\'' . $this->getUrl( - 'review/*/*', - [ - 'id' => $prevId, - 'ret' => $this->getRequest()->getParam('ret'), - ] - ) . '\')' + 'review/*/*', + [ + 'id' => $prevId, + 'ret' => $this->getRequest()->getParam('ret'), + ] + ) . '\')' ], 3, 10 @@ -139,12 +141,12 @@ protected function _construct() [ 'label' => __('Next'), 'onclick' => 'setLocation(\'' . $this->getUrl( - 'review/*/*', - [ - 'id' => $nextId, - 'ret' => $this->getRequest()->getParam('ret'), - ] - ) . '\')' + 'review/*/*', + [ + 'id' => $nextId, + 'ret' => $this->getRequest()->getParam('ret'), + ] + ) . '\')' ], 3, 105 @@ -220,10 +222,13 @@ protected function _construct() ); } } - Event.observe(window, \'load\', function(){ - Event.observe($("select_stores"), \'change\', review.updateRating); - }); '; + if (!$this->_storeManager->hasSingleStore()) { + $this->_formInitScripts[] = 'Event.observe(window, \'load\', function(){ + Event.observe($("select_stores"), \'change\', review.updateRating); + }); + '; + } } /** From e08499bd5083956ac807c4bd93354714bc9005f7 Mon Sep 17 00:00:00 2001 From: Sunil Patel <patelsunil42@gmail.com> Date: Sat, 10 Oct 2020 07:40:39 +0000 Subject: [PATCH 061/139] magento/magento2#30388 : fix js error on edit review page --- .../Magento/Review/Block/Adminhtml/Edit.php | 28 +++++++++---------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/app/code/Magento/Review/Block/Adminhtml/Edit.php b/app/code/Magento/Review/Block/Adminhtml/Edit.php index 15110f4f89cad..c5667af20c82c 100644 --- a/app/code/Magento/Review/Block/Adminhtml/Edit.php +++ b/app/code/Magento/Review/Block/Adminhtml/Edit.php @@ -3,7 +3,6 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - namespace Magento\Review\Block\Adminhtml; /** @@ -45,8 +44,7 @@ public function __construct( \Magento\Review\Helper\Action\Pager $reviewActionPager, \Magento\Framework\Registry $registry, array $data = [] - ) - { + ) { $this->_coreRegistry = $registry; $this->_reviewActionPager = $reviewActionPager; $this->_reviewFactory = $reviewFactory; @@ -80,12 +78,12 @@ protected function _construct() [ 'label' => __('Previous'), 'onclick' => 'setLocation(\'' . $this->getUrl( - 'review/*/*', - [ - 'id' => $prevId, - 'ret' => $this->getRequest()->getParam('ret'), - ] - ) . '\')' + 'review/*/*', + [ + 'id' => $prevId, + 'ret' => $this->getRequest()->getParam('ret'), + ] + ) . '\')' ], 3, 10 @@ -141,12 +139,12 @@ protected function _construct() [ 'label' => __('Next'), 'onclick' => 'setLocation(\'' . $this->getUrl( - 'review/*/*', - [ - 'id' => $nextId, - 'ret' => $this->getRequest()->getParam('ret'), - ] - ) . '\')' + 'review/*/*', + [ + 'id' => $nextId, + 'ret' => $this->getRequest()->getParam('ret'), + ] + ) . '\')' ], 3, 105 From a4be3e0e51eae4f8d9ebc4c8b9c833ef656aa779 Mon Sep 17 00:00:00 2001 From: Sunil Patel <patelsunil42@gmail.com> Date: Sat, 10 Oct 2020 09:48:23 +0000 Subject: [PATCH 062/139] magento/magento2#30347 : fix js error on delete bundle options --- .../view/adminhtml/web/js/components/bundle-dynamic-rows.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/Magento/Bundle/view/adminhtml/web/js/components/bundle-dynamic-rows.js b/app/code/Magento/Bundle/view/adminhtml/web/js/components/bundle-dynamic-rows.js index e9b05182b855d..7f07750812497 100644 --- a/app/code/Magento/Bundle/view/adminhtml/web/js/components/bundle-dynamic-rows.js +++ b/app/code/Magento/Bundle/view/adminhtml/web/js/components/bundle-dynamic-rows.js @@ -72,7 +72,7 @@ define([ */ removeBundleItemsFromOption: function (index) { var bundleSelections = registry.get(this.name + '.' + index + '.' + this.bundleSelectionsName), - bundleSelectionsLength = (bundleSelections.elems() || []).length, + bundleSelectionsLength = bundleSelections ? (bundleSelections.elems() || []).length:[], i; if (bundleSelectionsLength) { From a88f5808e69a3823645988cb0a6e3f91fc682272 Mon Sep 17 00:00:00 2001 From: Sunil Patel <patelsunil42@gmail.com> Date: Sat, 10 Oct 2020 09:52:49 +0000 Subject: [PATCH 063/139] Revert "magento/magento2#30347 : fix js error on delete bundle options" This reverts commit a4be3e0e51eae4f8d9ebc4c8b9c833ef656aa779. --- .../view/adminhtml/web/js/components/bundle-dynamic-rows.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/Magento/Bundle/view/adminhtml/web/js/components/bundle-dynamic-rows.js b/app/code/Magento/Bundle/view/adminhtml/web/js/components/bundle-dynamic-rows.js index 7f07750812497..e9b05182b855d 100644 --- a/app/code/Magento/Bundle/view/adminhtml/web/js/components/bundle-dynamic-rows.js +++ b/app/code/Magento/Bundle/view/adminhtml/web/js/components/bundle-dynamic-rows.js @@ -72,7 +72,7 @@ define([ */ removeBundleItemsFromOption: function (index) { var bundleSelections = registry.get(this.name + '.' + index + '.' + this.bundleSelectionsName), - bundleSelectionsLength = bundleSelections ? (bundleSelections.elems() || []).length:[], + bundleSelectionsLength = (bundleSelections.elems() || []).length, i; if (bundleSelectionsLength) { From 7ea472187a74f57588ea5945646cadd356e9da95 Mon Sep 17 00:00:00 2001 From: Sunil Patel <patelsunil42@gmail.com> Date: Sat, 10 Oct 2020 10:58:30 +0000 Subject: [PATCH 064/139] magento/magento2#30388 : fix js error on edit review page --- app/code/Magento/Review/Block/Adminhtml/Edit.php | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/app/code/Magento/Review/Block/Adminhtml/Edit.php b/app/code/Magento/Review/Block/Adminhtml/Edit.php index c5667af20c82c..9162d293f9332 100644 --- a/app/code/Magento/Review/Block/Adminhtml/Edit.php +++ b/app/code/Magento/Review/Block/Adminhtml/Edit.php @@ -222,9 +222,12 @@ protected function _construct() } '; if (!$this->_storeManager->hasSingleStore()) { - $this->_formInitScripts[] = 'Event.observe(window, \'load\', function(){ - Event.observe($("select_stores"), \'change\', review.updateRating); - }); + $this->_formInitScripts[] = ' + require(["jquery","prototype"], function(jQuery){ + Event.observe(window, \'load\', function(){ + Event.observe($("select_stores"), \'change\', review.updateRating); + }); + }) '; } } From 5be9b846a007e0b08e4e3349db4e7903c88fe8ae Mon Sep 17 00:00:00 2001 From: Viktor Kopin <viktor.kopin@transoftgroup.com> Date: Sun, 11 Oct 2020 09:48:30 +0300 Subject: [PATCH 065/139] MC-38113: Same shipping address is repeating multiple times in storefront checkout when Reordered --- ...ckoutFillingShippingSectionActionGroup.xml | 1 + ...CustomerHasNoOtherAddressesActionGroup.xml | 17 ++++++ .../Magento/Sales/Model/AdminOrder/Create.php | 6 ++ .../ActionGroup/AdminReorderActionGroup.xml | 22 +++++++ ...eorderAddressNotSavedInAddressBookTest.xml | 58 +++++++++++++++++++ .../templates/order/create/form/address.phtml | 4 +- 6 files changed, 107 insertions(+), 1 deletion(-) create mode 100644 app/code/Magento/Customer/Test/Mftf/ActionGroup/AssertCustomerHasNoOtherAddressesActionGroup.xml create mode 100644 app/code/Magento/Sales/Test/Mftf/ActionGroup/AdminReorderActionGroup.xml create mode 100644 app/code/Magento/Sales/Test/Mftf/Test/AdminReorderAddressNotSavedInAddressBookTest.xml diff --git a/app/code/Magento/Checkout/Test/Mftf/ActionGroup/LoggedInUserCheckoutFillingShippingSectionActionGroup.xml b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/LoggedInUserCheckoutFillingShippingSectionActionGroup.xml index 60188224871eb..e1092a87e4a01 100644 --- a/app/code/Magento/Checkout/Test/Mftf/ActionGroup/LoggedInUserCheckoutFillingShippingSectionActionGroup.xml +++ b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/LoggedInUserCheckoutFillingShippingSectionActionGroup.xml @@ -27,6 +27,7 @@ <waitForLoadingMaskToDisappear stepKey="waitForLoadingMask"/> <click selector="{{CheckoutShippingSection.firstShippingMethod}}" stepKey="selectFirstShippingMethod"/> <waitForElement selector="{{CheckoutShippingSection.next}}" time="30" stepKey="waitForNextButton"/> + <waitForLoadingMaskToDisappear stepKey="waitForShippingLoadingMask"/> <click selector="{{CheckoutShippingSection.next}}" stepKey="clickNext"/> <waitForElement selector="{{CheckoutPaymentSection.paymentSectionTitle}}" time="30" stepKey="waitForPaymentSectionLoaded"/> <seeInCurrentUrl url="{{CheckoutPage.url}}/#payment" stepKey="assertCheckoutPaymentUrl"/> diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AssertCustomerHasNoOtherAddressesActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AssertCustomerHasNoOtherAddressesActionGroup.xml new file mode 100644 index 0000000000000..58a5069403b7f --- /dev/null +++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AssertCustomerHasNoOtherAddressesActionGroup.xml @@ -0,0 +1,17 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AssertCustomerHasNoOtherAddressesActionGroup"> + <annotations> + <description>Verifies customer no additional address in address book</description> + </annotations> + <amOnPage url="customer/address/" stepKey="goToAddressPage"/> + <waitForText userInput="You have no other address entries in your address book." selector=".block-addresses-list" stepKey="assertOtherAddresses"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Sales/Model/AdminOrder/Create.php b/app/code/Magento/Sales/Model/AdminOrder/Create.php index 393d61b69bf22..80e0ce168d7f5 100644 --- a/app/code/Magento/Sales/Model/AdminOrder/Create.php +++ b/app/code/Magento/Sales/Model/AdminOrder/Create.php @@ -550,6 +550,9 @@ public function initFromOrder(\Magento\Sales\Model\Order $order) $quote = $this->getQuote(); if (!$quote->isVirtual() && $this->getShippingAddress()->getSameAsBilling()) { + $quote->getBillingAddress()->setCustomerAddressId( + $quote->getShippingAddress()->getCustomerAddressId() + ); $this->setShippingAsBilling(1); } @@ -2120,6 +2123,9 @@ private function isAddressesAreEqual(Order $order) $billingData['address_type'], $billingData['entity_id'] ); + if (isset($shippingData['customer_address_id']) && !isset($billingData['customer_address_id'])) { + unset($shippingData['customer_address_id']); + } return $shippingData == $billingData; } diff --git a/app/code/Magento/Sales/Test/Mftf/ActionGroup/AdminReorderActionGroup.xml b/app/code/Magento/Sales/Test/Mftf/ActionGroup/AdminReorderActionGroup.xml new file mode 100644 index 0000000000000..f4f076f25af8b --- /dev/null +++ b/app/code/Magento/Sales/Test/Mftf/ActionGroup/AdminReorderActionGroup.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AdminReorderActionGroup"> + <annotations> + <description>Reorder existing order. Requires admin order page to be opened.</description> + </annotations> + + <click selector="{{AdminOrderDetailsMainActionsSection.reorder}}" stepKey="clickReorder"/> + <waitForPageLoad stepKey="waitPageLoad"/> + + <click selector="{{AdminOrderFormActionSection.SubmitOrder}}" stepKey="clickSubmit"/> + <waitForPageLoad stepKey="waitOrderCreated"/> + <waitForText selector="{{AdminMessagesSection.success}}" userInput="You created the order." stepKey="seeOrderCreatedMessage"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Sales/Test/Mftf/Test/AdminReorderAddressNotSavedInAddressBookTest.xml b/app/code/Magento/Sales/Test/Mftf/Test/AdminReorderAddressNotSavedInAddressBookTest.xml new file mode 100644 index 0000000000000..aca0c4e6a8f8a --- /dev/null +++ b/app/code/Magento/Sales/Test/Mftf/Test/AdminReorderAddressNotSavedInAddressBookTest.xml @@ -0,0 +1,58 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="AdminReorderAddressNotSavedInAddressBookTest"> + <annotations> + <title value="Same shipping address is repeating multiple times in storefront checkout when Reordered"/> + <stories value="MC-38113: Same shipping address is repeating multiple times in storefront checkout when Reordered"/> + <description value="Same shipping address is repeating multiple times in storefront checkout when Reordered"/> + <features value="Sales"/> + <testCaseId value="MC-38113"/> + <severity value="MAJOR"/> + <group value="Sales"/> + </annotations> + <before> + <createData entity="ApiCategory" stepKey="Category"/> + <createData entity="ApiSimpleProduct" stepKey="Product"> + <requiredEntity createDataKey="Category"/> + </createData> + <actionGroup ref="AdminLoginActionGroup" stepKey="loginAsAdmin"/> + <actionGroup ref="StorefrontOpenCustomerAccountCreatePageActionGroup" stepKey="openCreateAccountPage"/> + <actionGroup ref="StorefrontFillCustomerAccountCreationFormActionGroup" stepKey="fillCreateAccountForm"> + <argument name="customer" value="CustomerEntityOne"/> + </actionGroup> + <actionGroup ref="StorefrontClickCreateAnAccountCustomerAccountCreationFormActionGroup" stepKey="submitCreateAccountForm"/> + </before> + <after> + <actionGroup ref="StorefrontCustomerLogoutActionGroup" stepKey="customerLogout"/> + <actionGroup ref="DeleteCustomerFromAdminActionGroup" stepKey="deleteCustomer"/> + <actionGroup ref="AdminLogoutActionGroup" stepKey="adminLogout"/> + <deleteData createDataKey="Product" stepKey="deleteProduct"/> + <deleteData createDataKey="Category" stepKey="deleteCategory"/> + </after> + + <!-- Create order for registered customer --> + <actionGroup ref="AddSimpleProductToCartActionGroup" stepKey="addSimpleProductToOrder"> + <argument name="product" value="$Product$"/> + </actionGroup> + <actionGroup ref="GoToCheckoutFromMinicartActionGroup" stepKey="openCheckoutPage"/> + <actionGroup ref="LoggedInUserCheckoutFillingShippingSectionActionGroup" stepKey="fillAddressForm"/> + <actionGroup ref="ClickPlaceOrderActionGroup" stepKey="clickPlaceOrder"/> + <grabTextFrom selector="{{CheckoutSuccessMainSection.orderNumber22}}" stepKey="grabOrderNumber"/> + + <!-- Reorder created order --> + <actionGroup ref="OpenOrderByIdActionGroup" stepKey="openOrderById"> + <argument name="orderId" value="$grabOrderNumber"/> + </actionGroup> + <actionGroup ref="AdminReorderActionGroup" stepKey="reorder"/> + + <!-- Assert no additional addresses saved --> + <actionGroup ref="AssertCustomerHasNoOtherAddressesActionGroup" stepKey="assertAddresses"/> + </test> +</tests> diff --git a/app/code/Magento/Sales/view/adminhtml/templates/order/create/form/address.phtml b/app/code/Magento/Sales/view/adminhtml/templates/order/create/form/address.phtml index dc007e4801b41..12927dcf526a3 100644 --- a/app/code/Magento/Sales/view/adminhtml/templates/order/create/form/address.phtml +++ b/app/code/Magento/Sales/view/adminhtml/templates/order/create/form/address.phtml @@ -114,7 +114,9 @@ endif; ?> type="checkbox" id="<?= $block->escapeHtmlAttr($block->getForm()->getHtmlIdPrefix()) ?>save_in_address_book" value="1" - <?php if (!$block->getDontSaveInAddressBook()): ?> checked="checked"<?php endif; ?> + <?php if (!$block->getDontSaveInAddressBook() && !$block->getAddressId()): ?> + checked="checked" + <?php endif; ?> class="admin__control-checkbox"/> <label for="<?= $block->escapeHtmlAttr($block->getForm()->getHtmlIdPrefix()) ?>save_in_address_book" class="admin__field-label"><?= $block->escapeHtml(__('Save in address book')) ?></label> From 9d093c9d0e91512caa64b4a3476cbf4476d41b04 Mon Sep 17 00:00:00 2001 From: Viktor Kopin <viktor.kopin@transoftgroup.com> Date: Sun, 11 Oct 2020 10:23:43 +0300 Subject: [PATCH 066/139] MC-37665: Updating a category through the REST API will uncheck "Use Default Value" on a bunch of attributes --- .../Catalog/Model/CategoryRepository/PopulateWithValues.php | 2 +- .../testsuite/Magento/Catalog/Api/CategoryRepositoryTest.php | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/code/Magento/Catalog/Model/CategoryRepository/PopulateWithValues.php b/app/code/Magento/Catalog/Model/CategoryRepository/PopulateWithValues.php index 6fdde51bd60de..410aa3db1f255 100644 --- a/app/code/Magento/Catalog/Model/CategoryRepository/PopulateWithValues.php +++ b/app/code/Magento/Catalog/Model/CategoryRepository/PopulateWithValues.php @@ -10,7 +10,7 @@ use Magento\Catalog\Api\Data\CategoryInterface; use Magento\Catalog\Model\Attribute\ScopeOverriddenValue; use Magento\Catalog\Model\Category; -use Magento\Eav\Api\AttributeRepositoryInterface as AttributeRepository; +use Magento\Catalog\Api\CategoryAttributeRepositoryInterface as AttributeRepository; use Magento\Eav\Api\Data\AttributeInterface; use Magento\Eav\Model\Entity\Attribute\ScopedAttributeInterface; use Magento\Framework\Api\FilterBuilder; diff --git a/dev/tests/api-functional/testsuite/Magento/Catalog/Api/CategoryRepositoryTest.php b/dev/tests/api-functional/testsuite/Magento/Catalog/Api/CategoryRepositoryTest.php index aaf03e82551fd..e7d47ff64a109 100644 --- a/dev/tests/api-functional/testsuite/Magento/Catalog/Api/CategoryRepositoryTest.php +++ b/dev/tests/api-functional/testsuite/Magento/Catalog/Api/CategoryRepositoryTest.php @@ -593,13 +593,13 @@ public function testUpdateScopeAttribute() $category, 'is_active', Store::DISTRO_STORE_ID - ), 'is_active is overriden for default store'); + ), 'is_active is overridden for default store'); self::assertFalse($scopeOverriddenValue->containsValue( CategoryInterface::class, $category, 'url_key', Store::DISTRO_STORE_ID - ), 'url_key is overriden for default store'); + ), 'url_key is overridden for default store'); $this->deleteCategory($categoryId); } From 28bcc626117050e1306ac2e00ac190acd0e82f69 Mon Sep 17 00:00:00 2001 From: IvanPletnyov <ivan.pletnyov@transoftgroup.com> Date: Mon, 12 Oct 2020 09:05:43 +0300 Subject: [PATCH 067/139] MC-37543: Create automated test for "Add static block on a category page" --- .../Controller/Adminhtml/Category/Save/SaveCategoryTest.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/dev/tests/integration/testsuite/Magento/Catalog/Controller/Adminhtml/Category/Save/SaveCategoryTest.php b/dev/tests/integration/testsuite/Magento/Catalog/Controller/Adminhtml/Category/Save/SaveCategoryTest.php index 36641e010dfc6..155a5f255c15a 100644 --- a/dev/tests/integration/testsuite/Magento/Catalog/Controller/Adminhtml/Category/Save/SaveCategoryTest.php +++ b/dev/tests/integration/testsuite/Magento/Catalog/Controller/Adminhtml/Category/Save/SaveCategoryTest.php @@ -14,7 +14,7 @@ use Magento\Framework\Exception\NoSuchEntityException; /** - * Test cases related to save category with enabled category flat. + * Test cases for save category controller. * * @magentoAppArea adminhtml * @magentoDbIsolation disabled @@ -54,7 +54,6 @@ protected function tearDown(): void $this->createdCategoryId = null; parent::tearDown(); - } /** From 79c7f31fc0423fce8e9c1581112467c715af6b8b Mon Sep 17 00:00:00 2001 From: Viktor Kopin <viktor.kopin@transoftgroup.com> Date: Mon, 12 Oct 2020 10:37:28 +0300 Subject: [PATCH 068/139] MC-37665: Updating a category through the REST API will uncheck "Use Default Value" on a bunch of attributes --- .../Test/Unit/Model/CategoryRepositoryTest.php | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/app/code/Magento/Catalog/Test/Unit/Model/CategoryRepositoryTest.php b/app/code/Magento/Catalog/Test/Unit/Model/CategoryRepositoryTest.php index 900f630a7434d..61e8133da5759 100644 --- a/app/code/Magento/Catalog/Test/Unit/Model/CategoryRepositoryTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Model/CategoryRepositoryTest.php @@ -11,6 +11,7 @@ use Magento\Catalog\Model\Category as CategoryModel; use Magento\Catalog\Model\CategoryFactory; use Magento\Catalog\Model\CategoryRepository; +use Magento\Catalog\Model\CategoryRepository\PopulateWithValues; use Magento\Framework\Api\ExtensibleDataObjectConverter; use Magento\Framework\DataObject; use Magento\Framework\EntityManager\EntityMetadata; @@ -63,6 +64,11 @@ class CategoryRepositoryTest extends TestCase */ protected $metadataPoolMock; + /** + * @var MockObject + */ + protected $populateWithValuesMock; + protected function setUp(): void { $this->categoryFactoryMock = $this->createPartialMock( @@ -94,6 +100,12 @@ protected function setUp(): void ->with(CategoryInterface::class) ->willReturn($metadataMock); + $this->populateWithValuesMock = $this + ->getMockBuilder(PopulateWithValues::class) + ->setMethods(['execute']) + ->disableOriginalConstructor() + ->getMock(); + $this->model = (new ObjectManager($this))->getObject( CategoryRepository::class, [ @@ -102,6 +114,7 @@ protected function setUp(): void 'storeManager' => $this->storeManagerMock, 'metadataPool' => $this->metadataPoolMock, 'extensibleDataObjectConverter' => $this->extensibleDataObjectConverterMock, + 'populateWithValues' => $this->populateWithValuesMock, ] ); } @@ -202,7 +215,7 @@ public function testFilterExtraFieldsOnUpdateCategory($categoryId, $categoryData ->method('toNestedArray') ->willReturn($categoryData); $categoryMock->expects($this->once())->method('validate')->willReturn(true); - $categoryMock->expects($this->once())->method('addData')->with($dataForSave); + $this->populateWithValuesMock->expects($this->once())->method('execute')->with($categoryMock, $dataForSave); $this->categoryResourceMock->expects($this->once()) ->method('save') ->willReturn(DataObject::class); @@ -230,11 +243,11 @@ public function testCreateNewCategory() $categoryMock->expects($this->once())->method('getParentId')->willReturn($parentCategoryId); $parentCategoryMock->expects($this->once())->method('getPath')->willReturn('path'); - $categoryMock->expects($this->once())->method('addData')->with($dataForSave); $categoryMock->expects($this->once())->method('validate')->willReturn(true); $this->categoryResourceMock->expects($this->once()) ->method('save') ->willReturn(DataObject::class); + $this->populateWithValuesMock->expects($this->once())->method('execute')->with($categoryMock, $dataForSave); $this->assertEquals($categoryMock, $this->model->save($categoryMock)); } From c7c4ad60ecf20c04104891d7c0bdaa84bacb8903 Mon Sep 17 00:00:00 2001 From: "vadim.malesh" <engcom-vendorworker-charlie@adobe.com> Date: Mon, 12 Oct 2020 11:36:47 +0300 Subject: [PATCH 069/139] fix static --- app/code/Magento/Sales/Model/Order/Email/NotifySender.php | 1 + .../Test/Unit/Model/Order/Email/Sender/AbstractSenderTest.php | 4 +++- .../Model/Order/Email/Sender/InvoiceCommentSenderTest.php | 2 -- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/app/code/Magento/Sales/Model/Order/Email/NotifySender.php b/app/code/Magento/Sales/Model/Order/Email/NotifySender.php index 1bb3053bf751f..468842d7b2ce4 100644 --- a/app/code/Magento/Sales/Model/Order/Email/NotifySender.php +++ b/app/code/Magento/Sales/Model/Order/Email/NotifySender.php @@ -10,6 +10,7 @@ /** * Class NotifySender + * phpcs:disable Magento2.Classes.AbstractApi * @api * @since 100.0.2 */ diff --git a/app/code/Magento/Sales/Test/Unit/Model/Order/Email/Sender/AbstractSenderTest.php b/app/code/Magento/Sales/Test/Unit/Model/Order/Email/Sender/AbstractSenderTest.php index 0494616b8cefd..be7788783adc7 100644 --- a/app/code/Magento/Sales/Test/Unit/Model/Order/Email/Sender/AbstractSenderTest.php +++ b/app/code/Magento/Sales/Test/Unit/Model/Order/Email/Sender/AbstractSenderTest.php @@ -192,7 +192,9 @@ public function stepIdentityContainerInit($identityMockClassName) { $this->identityContainerMock = $this->getMockBuilder($identityMockClassName) ->disableOriginalConstructor() - ->onlyMethods(['getStore', 'isEnabled', 'getConfigValue', 'getTemplateId', 'getGuestTemplateId', 'getCopyMethod']) + ->onlyMethods( + ['getStore', 'isEnabled', 'getConfigValue', 'getTemplateId', 'getGuestTemplateId', 'getCopyMethod'] + ) ->getMock(); $this->identityContainerMock->expects($this->any()) ->method('getStore') diff --git a/app/code/Magento/Sales/Test/Unit/Model/Order/Email/Sender/InvoiceCommentSenderTest.php b/app/code/Magento/Sales/Test/Unit/Model/Order/Email/Sender/InvoiceCommentSenderTest.php index 9e4e89766dfe1..56d78789d7dda 100644 --- a/app/code/Magento/Sales/Test/Unit/Model/Order/Email/Sender/InvoiceCommentSenderTest.php +++ b/app/code/Magento/Sales/Test/Unit/Model/Order/Email/Sender/InvoiceCommentSenderTest.php @@ -31,8 +31,6 @@ protected function setUp(): void $this->stepMockSetup(); $this->paymentHelper = $this->createPartialMock(Data::class, ['getInfoBlockHtml']); - $this->invoiceResource = $this->createMock(Invoice::class); - $this->stepIdentityContainerInit(InvoiceCommentIdentity::class); $this->addressRenderer->expects($this->any())->method('format')->willReturn(1); From d46371a645fa0e62ac37a8c456eff963406119bc Mon Sep 17 00:00:00 2001 From: IvanPletnyov <ivan.pletnyov@transoftgroup.com> Date: Mon, 12 Oct 2020 12:10:29 +0300 Subject: [PATCH 070/139] MC-37543: Create automated test for "Add static block on a category page" --- .../Catalog/Block/Category/ViewTest.php | 8 +++++- .../Category/Save/SaveCategoryTest.php | 26 ++++++++++++------- .../_files/category_with_cms_block.php | 9 ++++--- 3 files changed, 30 insertions(+), 13 deletions(-) diff --git a/dev/tests/integration/testsuite/Magento/Catalog/Block/Category/ViewTest.php b/dev/tests/integration/testsuite/Magento/Catalog/Block/Category/ViewTest.php index d08af2b85a67b..8ff4e29b46dde 100644 --- a/dev/tests/integration/testsuite/Magento/Catalog/Block/Category/ViewTest.php +++ b/dev/tests/integration/testsuite/Magento/Catalog/Block/Category/ViewTest.php @@ -12,6 +12,7 @@ use Magento\Framework\ObjectManagerInterface; use Magento\Framework\Registry; use Magento\Framework\View\LayoutInterface; +use Magento\Store\Model\StoreManagerInterface; use Magento\TestFramework\Catalog\Model\GetCategoryByName; use Magento\TestFramework\Helper\Bootstrap; use PHPUnit\Framework\TestCase; @@ -39,6 +40,9 @@ class ViewTest extends TestCase /** @var LayoutInterface */ private $layout; + /** @var StoreManagerInterface */ + private $storeManager; + /** * @inheritdoc */ @@ -51,6 +55,7 @@ protected function setUp(): void $this->getCategoryByName = $this->objectManager->get(GetCategoryByName::class); $this->categoryRepository = $this->objectManager->get(CategoryRepositoryInterface::class); $this->layout = $this->objectManager->get(LayoutInterface::class); + $this->storeManager = $this->objectManager->get(StoreManagerInterface::class); } /** @@ -70,8 +75,9 @@ protected function tearDown(): void */ public function testCmsBlockDisplayedOnCategory(): void { + $storeId = (int)$this->storeManager->getStore('default')->getId(); $categoryId = $this->getCategoryByName->execute('Category with cms block')->getId(); - $category = $this->categoryRepository->get($categoryId, 1); + $category = $this->categoryRepository->get($categoryId, $storeId); $this->registerCategory($category); $block = $this->layout->createBlock(View::class)->setTemplate('Magento_Catalog::category/cms.phtml'); $this->assertStringContainsString('<h1>Fixture Block Title</h1>', $block->toHtml()); diff --git a/dev/tests/integration/testsuite/Magento/Catalog/Controller/Adminhtml/Category/Save/SaveCategoryTest.php b/dev/tests/integration/testsuite/Magento/Catalog/Controller/Adminhtml/Category/Save/SaveCategoryTest.php index 155a5f255c15a..adef25f88395c 100644 --- a/dev/tests/integration/testsuite/Magento/Catalog/Controller/Adminhtml/Category/Save/SaveCategoryTest.php +++ b/dev/tests/integration/testsuite/Magento/Catalog/Controller/Adminhtml/Category/Save/SaveCategoryTest.php @@ -12,12 +12,13 @@ use Magento\Catalog\Model\Category; use Magento\Cms\Api\GetBlockByIdentifierInterface; use Magento\Framework\Exception\NoSuchEntityException; +use Magento\Store\Model\StoreManagerInterface; /** * Test cases for save category controller. * * @magentoAppArea adminhtml - * @magentoDbIsolation disabled + * @magentoDbIsolation enabled */ class SaveCategoryTest extends AbstractSaveCategoryTest { @@ -30,6 +31,9 @@ class SaveCategoryTest extends AbstractSaveCategoryTest /** @var string */ private $createdCategoryId; + /** @var StoreManagerInterface */ + private $storeManager; + /** * @inheritdoc */ @@ -39,6 +43,7 @@ protected function setUp(): void $this->categoryRepository = $this->_objectManager->get(CategoryRepositoryInterface::class); $this->getBlockByIdentifier = $this->_objectManager->get(GetBlockByIdentifierInterface::class); + $this->storeManager = $this->_objectManager->get(StoreManagerInterface::class); } /** @@ -46,12 +51,14 @@ protected function setUp(): void */ protected function tearDown(): void { - try { - $this->categoryRepository->deleteByIdentifier($this->createdCategoryId); - } catch (NoSuchEntityException $e) { - //Category already deleted. + if(!empty($this->createdCategoryId)) { + try { + $this->categoryRepository->deleteByIdentifier($this->createdCategoryId); + } catch (NoSuchEntityException $e) { + //Category already deleted. + } + $this->createdCategoryId = null; } - $this->createdCategoryId = null; parent::tearDown(); } @@ -63,15 +70,16 @@ protected function tearDown(): void */ public function testCreateCategoryWithCmsBlock(): void { - $blockId = $this->getBlockByIdentifier->execute('fixture_block', 1)->getId(); + $storeId = (int)$this->storeManager->getStore('default')->getId(); + $blockId = $this->getBlockByIdentifier->execute('fixture_block', $storeId)->getId(); $postData = [ CategoryInterface::KEY_NAME => 'Category with cms block', CategoryInterface::KEY_IS_ACTIVE => 1, CategoryInterface::KEY_INCLUDE_IN_MENU => 1, 'display_mode' => Category::DM_MIXED, 'landing_page' => $blockId, - 'available_sort_by' => 1, - 'default_sort_by' => 1, + CategoryInterface::KEY_AVAILABLE_SORT_BY => ['position'], + 'default_sort_by' => 'position', ]; $responseData = $this->performSaveCategoryRequest($postData); $this->assertRequestIsSuccessfullyPerformed($responseData); diff --git a/dev/tests/integration/testsuite/Magento/Catalog/_files/category_with_cms_block.php b/dev/tests/integration/testsuite/Magento/Catalog/_files/category_with_cms_block.php index 03eb767741579..417b791eb376a 100644 --- a/dev/tests/integration/testsuite/Magento/Catalog/_files/category_with_cms_block.php +++ b/dev/tests/integration/testsuite/Magento/Catalog/_files/category_with_cms_block.php @@ -31,7 +31,6 @@ $getBlockByIdentifier = $objectManager->get(GetBlockByIdentifierInterface::class); $block = $getBlockByIdentifier->execute('fixture_block', $currentStoreId); -$storeManager->setCurrentStore(Store::DEFAULT_STORE_ID); $category = $categoryFactory->create(); $category->setName('Category with cms block') ->setParentId($categoryHelper->getId()) @@ -42,5 +41,9 @@ ->setPosition(1) ->setDisplayMode(Category::DM_MIXED) ->setLandingPage($block->getId()); -$categoryRepository->save($category); -$storeManager->setCurrentStore($currentStoreId); +try { + $storeManager->setCurrentStore(Store::DEFAULT_STORE_ID); + $categoryRepository->save($category); +} finally { + $storeManager->setCurrentStore($currentStoreId); +} From 4ffbd717b999d3976839cef63f927beefd65976e Mon Sep 17 00:00:00 2001 From: IvanPletnyov <ivan.pletnyov@transoftgroup.com> Date: Mon, 12 Oct 2020 14:47:33 +0300 Subject: [PATCH 071/139] MC-37558: Create automated test for "Override Category settings on Store View level" --- .../Category/Save/UpdateCategoryTest.php | 114 +++++++++++ .../Catalog/Model/CategoryRepositoryTest.php | 180 +++++++++++------- 2 files changed, 221 insertions(+), 73 deletions(-) create mode 100644 dev/tests/integration/testsuite/Magento/Catalog/Controller/Adminhtml/Category/Save/UpdateCategoryTest.php diff --git a/dev/tests/integration/testsuite/Magento/Catalog/Controller/Adminhtml/Category/Save/UpdateCategoryTest.php b/dev/tests/integration/testsuite/Magento/Catalog/Controller/Adminhtml/Category/Save/UpdateCategoryTest.php new file mode 100644 index 0000000000000..c3d5ed080bcf2 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Catalog/Controller/Adminhtml/Category/Save/UpdateCategoryTest.php @@ -0,0 +1,114 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Catalog\Controller\Adminhtml\Category\Save; + +use Magento\Catalog\Api\CategoryRepositoryInterface; +use Magento\Catalog\Api\Data\CategoryInterface; +use Magento\Catalog\Model\Category; +use Magento\Store\Model\StoreManagerInterface; + +/** + * Test related to update category. + * + * @magentoAppArea adminhtml + * @magentoDbIsolation enabled + */ +class UpdateCategoryTest extends AbstractSaveCategoryTest +{ + /** @var CategoryRepositoryInterface */ + private $categoryRepository; + + /** @var StoreManagerInterface */ + private $storeManager; + + /** + * @inheritdoc + */ + protected function setUp(): void + { + parent::setUp(); + + $this->categoryRepository = $this->_objectManager->get(CategoryRepositoryInterface::class); + $this->storeManager = $this->_objectManager->get(StoreManagerInterface::class); + } + + /** + * @dataProvider categoryDataProvider + * @magentoDataFixture Magento/Store/_files/second_store.php + * @magentoDataFixture Magento/Catalog/_files/category.php + * + * @param array $postData + * @return void + */ + public function testUpdateCategoryForDefaultStoreView($postData): void + { + $storeId = (int)$this->storeManager->getStore('default')->getId(); + $postData = array_merge($postData, ['store_id' => $storeId]); + $responseData = $this->performSaveCategoryRequest($postData); + $this->assertRequestIsSuccessfullyPerformed($responseData); + $category = $this->categoryRepository->get($postData['entity_id'], $postData['store_id']); + unset($postData['use_default']); + unset($postData['use_config']); + foreach ($postData as $key => $value) { + $this->assertEquals($value, $category->getData($key)); + } + } + + /** + * @return array + */ + public function categoryDataProvider(): array + { + return [ + [ + 'post_data' => [ + 'entity_id' => 333, + CategoryInterface::KEY_IS_ACTIVE => '0', + CategoryInterface::KEY_INCLUDE_IN_MENU => '0', + CategoryInterface::KEY_NAME => 'Category default store', + 'description' => 'Description for default store', + 'landing_page' => '', + 'display_mode' => Category::DM_MIXED, + CategoryInterface::KEY_AVAILABLE_SORT_BY => ['name', 'price'], + 'default_sort_by' => 'price', + 'filter_price_range' => 5, + 'url_key' => 'default-store-category', + 'meta_title' => 'meta_title default store', + 'meta_keywords' => 'meta_keywords default store', + 'meta_description' => 'meta_description default store', + 'custom_use_parent_settings' => '0', + 'custom_design' => '2', + 'page_layout' => '2columns-right', + 'custom_apply_to_products' => '1', + 'use_default' => [ + CategoryInterface::KEY_NAME => '0', + CategoryInterface::KEY_IS_ACTIVE => '0', + CategoryInterface::KEY_INCLUDE_IN_MENU => '0', + 'url_key' => '0', + 'meta_title' => '0', + 'custom_use_parent_settings' => '0', + 'custom_apply_to_products' => '0', + 'description' => '0', + 'landing_page' => '0', + 'display_mode' => '0', + 'custom_design' => '0', + 'page_layout' => '0', + 'meta_keywords' => '0', + 'meta_description' => '0', + 'custom_layout_update' => '0', + ], + 'use_config' => [ + CategoryInterface::KEY_AVAILABLE_SORT_BY => false, + 'default_sort_by' => false, + 'filter_price_range' => false, + ], + ], + ], + ]; + } +} diff --git a/dev/tests/integration/testsuite/Magento/Catalog/Model/CategoryRepositoryTest.php b/dev/tests/integration/testsuite/Magento/Catalog/Model/CategoryRepositoryTest.php index 7fd7627c738d6..6469f80ff49b8 100644 --- a/dev/tests/integration/testsuite/Magento/Catalog/Model/CategoryRepositoryTest.php +++ b/dev/tests/integration/testsuite/Magento/Catalog/Model/CategoryRepositoryTest.php @@ -8,93 +8,82 @@ namespace Magento\Catalog\Model; use Magento\Catalog\Api\CategoryRepositoryInterface; -use Magento\Catalog\Api\CategoryRepositoryInterfaceFactory; +use Magento\Catalog\Api\Data\CategoryInterface; use Magento\Catalog\Model\ResourceModel\Category\CollectionFactory as CategoryCollectionFactory; use Magento\Catalog\Model\ResourceModel\Product\CollectionFactory; +use Magento\Cms\Api\GetBlockByIdentifierInterface; use Magento\Framework\Exception\LocalizedException; +use Magento\Framework\ObjectManagerInterface; +use Magento\Store\Api\StoreManagementInterface; +use Magento\Store\Model\StoreManagerInterface; use Magento\TestFramework\Catalog\Model\CategoryLayoutUpdateManager; use Magento\TestFramework\Helper\Bootstrap; use PHPUnit\Framework\TestCase; /** * Provide tests for CategoryRepository model. + * + * @magentoDbIsolation enabled */ class CategoryRepositoryTest extends TestCase { - private const FIXTURE_CATEGORY_ID = 333; - private const FIXTURE_TWO_STORES_CATEGORY_ID = 555; - private const FIXTURE_SECOND_STORE_CODE = 'fixturestore'; - private const FIXTURE_FIRST_STORE_CODE = 'default'; + /** @var ObjectManagerInterface */ + private $objectManager; - /** - * @var CategoryLayoutUpdateManager - */ + /** @var CategoryLayoutUpdateManager */ private $layoutManager; - /** - * @var CategoryRepositoryInterfaceFactory - */ - private $repositoryFactory; + /** @var CategoryRepositoryInterface */ + private $categoryRepository; - /** - * @var CollectionFactory - */ + /** @var CollectionFactory */ private $productCollectionFactory; - /** - * @var CategoryCollectionFactory - */ + /** @var CategoryCollectionFactory */ private $categoryCollectionFactory; + /** @var StoreManagementInterface */ + private $storeManager; + + /** @var GetBlockByIdentifierInterface */ + private $getBlockByIdentifier; + /** - * Sets up common objects. - * - * @inheritDoc + * @inheritdoc */ protected function setUp(): void { - Bootstrap::getObjectManager()->configure([ + $this->objectManager = Bootstrap::getObjectManager(); + $this->objectManager->configure([ 'preferences' => [ \Magento\Catalog\Model\Category\Attribute\LayoutUpdateManager::class => \Magento\TestFramework\Catalog\Model\CategoryLayoutUpdateManager::class ] ]); - $this->repositoryFactory = Bootstrap::getObjectManager()->get(CategoryRepositoryInterfaceFactory::class); - $this->layoutManager = Bootstrap::getObjectManager()->get(CategoryLayoutUpdateManager::class); - $this->productCollectionFactory = Bootstrap::getObjectManager()->get(CollectionFactory::class); - $this->categoryCollectionFactory = Bootstrap::getObjectManager()->create(CategoryCollectionFactory::class); - } - - /** - * Create subject object. - * - * @return CategoryRepositoryInterface - */ - private function createRepo(): CategoryRepositoryInterface - { - return $this->repositoryFactory->create(); + $this->layoutManager = $this->objectManager->get(CategoryLayoutUpdateManager::class); + $this->productCollectionFactory = $this->objectManager->get(CollectionFactory::class); + $this->categoryCollectionFactory = $this->objectManager->get(CategoryCollectionFactory::class); + $this->categoryRepository = $this->objectManager->get(CategoryRepositoryInterface::class); + $this->storeManager = $this->objectManager->get(StoreManagerInterface::class); + $this->getBlockByIdentifier = $this->objectManager->get(GetBlockByIdentifierInterface::class); } /** * Test that custom layout file attribute is saved. * - * @return void - * @throws \Throwable * @magentoDataFixture Magento/Catalog/_files/category.php - * @magentoDbIsolation enabled * @magentoAppIsolation enabled + * + * @return void */ public function testCustomLayout(): void { - //New valid value - $repo = $this->createRepo(); - $category = $repo->get(self::FIXTURE_CATEGORY_ID); + $category = $this->categoryRepository->get(333); $newFile = 'test'; - $this->layoutManager->setCategoryFakeFiles(self::FIXTURE_CATEGORY_ID, [$newFile]); + $this->layoutManager->setCategoryFakeFiles(333, [$newFile]); $category->setCustomAttribute('custom_layout_update_file', $newFile); - $repo->save($category); - $repo = $this->createRepo(); - $category = $repo->get(self::FIXTURE_CATEGORY_ID); + $this->categoryRepository->save($category); + $category = $this->categoryRepository->get(333); $this->assertEquals($newFile, $category->getCustomAttribute('custom_layout_update_file')->getValue()); //Setting non-existent value @@ -102,7 +91,7 @@ public function testCustomLayout(): void $category->setCustomAttribute('custom_layout_update_file', $newFile); $caughtException = false; try { - $repo->save($category); + $this->categoryRepository->save($category); } catch (LocalizedException $exception) { $caughtException = true; } @@ -112,9 +101,9 @@ public function testCustomLayout(): void /** * Test removal of categories. * - * @magentoDbIsolation enabled * @magentoDataFixture Magento/Catalog/_files/categories.php * @magentoAppArea adminhtml + * * @return void */ public function testCategoryBehaviourAfterDelete(): void @@ -122,7 +111,7 @@ public function testCategoryBehaviourAfterDelete(): void $productCollection = $this->productCollectionFactory->create(); $deletedCategories = ['3', '4', '5', '13']; $categoryCollectionIds = $this->categoryCollectionFactory->create()->getAllIds(); - $this->createRepo()->deleteByIdentifier(3); + $this->categoryRepository->deleteByIdentifier(3); $this->assertEquals( 0, $productCollection->addCategoriesFilter(['in' => $deletedCategories])->getSize(), @@ -131,42 +120,87 @@ public function testCategoryBehaviourAfterDelete(): void $newCategoryCollectionIds = $this->categoryCollectionFactory->create()->getAllIds(); $difference = array_diff($categoryCollectionIds, $newCategoryCollectionIds); sort($difference); - $this->assertEquals( - $deletedCategories, - $difference, - 'Wrong categories was deleted' - ); + $this->assertEquals($deletedCategories, $difference, 'Wrong categories was deleted'); } /** * Verifies whether `get()` method `$storeId` attribute works as expected. * - * @magentoDbIsolation enabled * @magentoDataFixture Magento/Store/_files/core_fixturestore.php * @magentoDataFixture Magento/Catalog/_files/category_with_two_stores.php + * + * @return void */ - public function testGetCategoryForProvidedStore() + public function testGetCategoryForProvidedStore(): void { - $categoryRepository = $this->repositoryFactory->create(); - - $categoryDefault = $categoryRepository->get( - self::FIXTURE_TWO_STORES_CATEGORY_ID - ); - + $categoryId = 555; + $categoryDefault = $this->categoryRepository->get($categoryId); $this->assertSame('category-defaultstore', $categoryDefault->getUrlKey()); - - $categoryFirstStore = $categoryRepository->get( - self::FIXTURE_TWO_STORES_CATEGORY_ID, - self::FIXTURE_FIRST_STORE_CODE - ); - + $defaultStoreId = $this->storeManager->getStore('default')->getId(); + $categoryFirstStore = $this->categoryRepository->get($categoryId, $defaultStoreId); $this->assertSame('category-defaultstore', $categoryFirstStore->getUrlKey()); + $fixtureStoreId = $this->storeManager->getStore('fixturestore')->getId(); + $categorySecondStore = $this->categoryRepository->get($categoryId, $fixtureStoreId); + $this->assertSame('category-fixturestore', $categorySecondStore->getUrlKey()); + } - $categorySecondStore = $categoryRepository->get( - self::FIXTURE_TWO_STORES_CATEGORY_ID, - self::FIXTURE_SECOND_STORE_CODE - ); + /** + * @magentoDataFixture Magento/Store/_files/second_store.php + * @magentoDataFixture Magento/Catalog/_files/category.php + * @magentoDataFixture Magento/Cms/_files/block.php + * + * @return void + */ + public function testUpdateCategoryDefaultStoreView(): void + { + $categoryId = 333; + $defaultStoreId = (int)$this->storeManager->getStore('default')->getId(); + $secondStoreId = (int)$this->storeManager->getStore('fixture_second_store')->getId(); + $blockId = $this->getBlockByIdentifier->execute('fixture_block', $defaultStoreId)->getId(); + $origData = $this->categoryRepository->get($categoryId)->getData(); + unset($origData[CategoryInterface::KEY_UPDATED_AT]); + $category = $this->categoryRepository->get($categoryId, $defaultStoreId); + $dataForDefaultStore = [ + CategoryInterface::KEY_IS_ACTIVE => 0, + CategoryInterface::KEY_INCLUDE_IN_MENU => 0, + CategoryInterface::KEY_NAME => 'Category default store', + 'image' => 'test.png', + 'description' => 'Description for default store', + 'landing_page' => $blockId, + 'display_mode' => Category::DM_MIXED, + CategoryInterface::KEY_AVAILABLE_SORT_BY => ['name', 'price'], + 'default_sort_by' => 'price', + 'filter_price_range' => 5, + 'url_key' => 'default-store-category', + 'meta_title' => 'meta_title default store', + 'meta_keywords' => 'meta_keywords default store', + 'meta_description' => 'meta_description default store', + 'custom_use_parent_settings' => '0', + 'custom_design' => '2', + 'page_layout' => '2columns-right', + 'custom_apply_to_products' => '1', + ]; + $category->addData($dataForDefaultStore); + $updatedCategory = $this->categoryRepository->save($category); + $this->assertCategoryData($dataForDefaultStore, $updatedCategory); + $categorySecondStore = $this->categoryRepository->get($categoryId, $secondStoreId); + $this->assertCategoryData($origData, $categorySecondStore); + foreach ($dataForDefaultStore as $key => $value) { + $this->assertNotEquals($value, $categorySecondStore->getData($key)); + } + } - $this->assertSame('category-fixturestore', $categorySecondStore->getUrlKey()); + /** + * Assert category data. + * + * @param array $expectedData + * @param CategoryInterface $category + * @return void + */ + private function assertCategoryData(array $expectedData, CategoryInterface $category): void + { + foreach ($expectedData as $key => $value) { + $this->assertEquals($value, $category->getData($key)); + } } } From afe1fc243f2322515abadb0d1d9618324a74b626 Mon Sep 17 00:00:00 2001 From: Viktor Sevch <viktor.sevch@transoftgroup.com> Date: Mon, 12 Oct 2020 14:49:05 +0300 Subject: [PATCH 072/139] MC-34292: AdminCreateUserRoleWithReportsActionGroup needs to be refactored to be based in CE --- ...AdminChooseUserRoleResourceActionGroup.xml | 23 ++++ .../AdminSaveUserRoleActionGroup.xml | 18 +++ .../AdminStartCreateUserRoleActionGroup.xml | 26 +++++ .../AdminUserSaveRoleActionGroup.xml | 2 +- .../Mftf/Section/AdminCreateRoleSection.xml | 6 +- .../Mftf/Section/AdminEditRoleInfoSection.xml | 2 +- .../Section/AdminEditRoleResourcesSection.xml | 2 + ...inReviewOrderWithReportsPermissionTest.xml | 109 ++++++++++++++++++ 8 files changed, 183 insertions(+), 5 deletions(-) create mode 100644 app/code/Magento/User/Test/Mftf/ActionGroup/AdminChooseUserRoleResourceActionGroup.xml create mode 100644 app/code/Magento/User/Test/Mftf/ActionGroup/AdminSaveUserRoleActionGroup.xml create mode 100644 app/code/Magento/User/Test/Mftf/ActionGroup/AdminStartCreateUserRoleActionGroup.xml create mode 100644 app/code/Magento/User/Test/Mftf/Test/AdminReviewOrderWithReportsPermissionTest.xml diff --git a/app/code/Magento/User/Test/Mftf/ActionGroup/AdminChooseUserRoleResourceActionGroup.xml b/app/code/Magento/User/Test/Mftf/ActionGroup/AdminChooseUserRoleResourceActionGroup.xml new file mode 100644 index 0000000000000..7072830e2036b --- /dev/null +++ b/app/code/Magento/User/Test/Mftf/ActionGroup/AdminChooseUserRoleResourceActionGroup.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AdminChooseUserRoleResourceActionGroup"> + <annotations> + <description>Check the resource access checkbox. Leave the form open.</description> + </annotations> + <arguments> + <argument name="resourceId" type="string" defaultValue="Magento_Backend::dashboard"/> + <argument name="resourceName" type="string" defaultValue="Dashboard"/> + </arguments> + + <waitForElementVisible selector="{{AdminEditRoleResourcesSection.resourceCheckboxLink(resourceId, resourceName)}}" stepKey="waitForResourceCheckboxVisible"/> + <click selector="{{AdminEditRoleResourcesSection.resourceCheckboxLink(resourceId, resourceName)}}" stepKey="checkResource"/> + <seeCheckboxIsChecked selector="{{AdminEditRoleResourcesSection.resourceCheckbox(resourceId)}}" stepKey="seeCheckedResource"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/User/Test/Mftf/ActionGroup/AdminSaveUserRoleActionGroup.xml b/app/code/Magento/User/Test/Mftf/ActionGroup/AdminSaveUserRoleActionGroup.xml new file mode 100644 index 0000000000000..4a90630161e99 --- /dev/null +++ b/app/code/Magento/User/Test/Mftf/ActionGroup/AdminSaveUserRoleActionGroup.xml @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AdminSaveUserRoleActionGroup"> + <annotations> + <description>Click to Save Role</description> + </annotations> + <click selector="{{AdminEditRoleInfoSection.saveButton}}" stepKey="clickSaveRoleButton" /> + <waitForElementVisible selector="{{AdminMessagesSection.success}}" stepKey="waitForSuccessMessage"/> + <see selector="{{AdminMessagesSection.success}}" userInput="You saved the role." stepKey="seeSuccessMessageForSavedRole"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/User/Test/Mftf/ActionGroup/AdminStartCreateUserRoleActionGroup.xml b/app/code/Magento/User/Test/Mftf/ActionGroup/AdminStartCreateUserRoleActionGroup.xml new file mode 100644 index 0000000000000..2a1dec5b8574e --- /dev/null +++ b/app/code/Magento/User/Test/Mftf/ActionGroup/AdminStartCreateUserRoleActionGroup.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AdminStartCreateUserRoleActionGroup"> + <annotations> + <description>Open Admin Edit Role page. Fills role, user password, resource access. Leave the form open.</description> + </annotations> + <arguments> + <argument name="roleName" type="string" defaultValue="{{limitedRole.name}}"/> + <argument name="userPassword" type="string" defaultValue="123123q"/> + <argument name="resourceAccess" type="string" defaultValue="Custom"/> + </arguments> + <amOnPage url="{{AdminEditRolePage.url}}" stepKey="openNewAdminRolePage"/> + <waitForElementVisible selector="{{AdminCreateRoleSection.name}}" stepKey="waitForName"/> + <fillField selector="{{AdminCreateRoleSection.name}}" userInput="{{roleName}}" stepKey="setTheRoleName"/> + <fillField selector="{{AdminCreateRoleSection.password}}" userInput="{{userPassword}}" stepKey="setPassword"/> + <click selector="{{AdminCreateRoleSection.roleResources}}" stepKey="clickToOpenRoleResources"/> + <selectOption selector="{{AdminEditRoleResourcesSection.resourceAccess}}" userInput="{{resourceAccess}}" stepKey="chooseResourceAccess"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/User/Test/Mftf/ActionGroup/AdminUserSaveRoleActionGroup.xml b/app/code/Magento/User/Test/Mftf/ActionGroup/AdminUserSaveRoleActionGroup.xml index 824e9407125f5..e247db64deeab 100644 --- a/app/code/Magento/User/Test/Mftf/ActionGroup/AdminUserSaveRoleActionGroup.xml +++ b/app/code/Magento/User/Test/Mftf/ActionGroup/AdminUserSaveRoleActionGroup.xml @@ -9,7 +9,7 @@ xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> <actionGroup name="AdminUserSaveRoleActionGroup"> <annotations> - <description>Click to Save Role</description> + <description>Deprecated. Please use AdminSaveUserRoleActionGroup</description> </annotations> <click selector="{{AdminEditRoleInfoSection.saveButton}}" stepKey="clickSaveRoleButton" /> <see userInput="You saved the role." stepKey="seeUserRoleSavedMessage"/> diff --git a/app/code/Magento/User/Test/Mftf/Section/AdminCreateRoleSection.xml b/app/code/Magento/User/Test/Mftf/Section/AdminCreateRoleSection.xml index 93acfc2753b61..96aaf879e2054 100644 --- a/app/code/Magento/User/Test/Mftf/Section/AdminCreateRoleSection.xml +++ b/app/code/Magento/User/Test/Mftf/Section/AdminCreateRoleSection.xml @@ -9,13 +9,13 @@ <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="AdminCreateRoleSection"> - <element name="create" type="button" selector="#add"/> + <element name="create" type="button" selector="#add" timeout="30"/> <element name="name" type="button" selector="#role_name"/> <element name="password" type="input" selector="#current_password"/> <element name="resourceAccess" type="select" selector="[data-ui-id='adminhtml-user-editroles-tab-content-account'] [name='all']"/> <element name="resourceTree" type="block" selector="[data-ui-id='adminhtml-user-editroles-tab-content-account'] [data-role='resource-tree']"/> - <element name="roleResources" type="button" selector="#role_info_tabs_account"/> + <element name="roleResources" type="button" selector="#role_info_tabs_account" timeout="30"/> <element name="roleResource" type="button" selector="#gws_is_all"/> <element name="roleResourceNew" type="button" selector="#all"/> <element name="resourceValue" type="button" selector="//*[text()='{{arg1}}']" parameterized="true"/> @@ -24,7 +24,7 @@ <element name="scopeValue" type="button" selector="//select[@id='all']/*[text()='{{arg2}}']" parameterized="true"/> <element name="website" type="checkbox" selector="//*[contains(text(), '{{arg3}}')]" parameterized="true"/> <element name="selectWebsite" type="checkbox" selector="//label[contains(text(), '{{websiteName}}')]/preceding-sibling::input" parameterized="true"/> - <element name="save" type="button" selector="//button[@title='Save Role']"/> + <element name="save" type="button" selector="//button[@title='Save Role']" timeout="30"/> <element name="roleNameFilterTextField" type="input" selector="#permissionsUserRolesGrid_filter_role_name"/> <element name="searchButton" type="button" selector=".admin__data-grid-header button[title=Search]"/> <element name="searchResultFirstRow" type="text" selector=".data-grid>tbody>tr"/> diff --git a/app/code/Magento/User/Test/Mftf/Section/AdminEditRoleInfoSection.xml b/app/code/Magento/User/Test/Mftf/Section/AdminEditRoleInfoSection.xml index 57659e1aff075..b8430eb3b7313 100644 --- a/app/code/Magento/User/Test/Mftf/Section/AdminEditRoleInfoSection.xml +++ b/app/code/Magento/User/Test/Mftf/Section/AdminEditRoleInfoSection.xml @@ -13,7 +13,7 @@ <element name="backButton" type="button" selector="button[title='Back']"/> <element name="resetButton" type="button" selector="button[title='Reset']"/> <element name="deleteButton" type="button" selector="button[title='Delete Role']"/> - <element name="saveButton" type="button" selector="button[title='Save Role']"/> + <element name="saveButton" type="button" selector="button[title='Save Role']" timeout="30"/> <element name="message" type="text" selector=".modal-popup.confirm div.modal-content"/> <element name="cancel" type="button" selector=".modal-popup.confirm button.action-dismiss"/> <element name="ok" type="button" selector=".modal-popup.confirm button.action-accept" timeout="60"/> diff --git a/app/code/Magento/User/Test/Mftf/Section/AdminEditRoleResourcesSection.xml b/app/code/Magento/User/Test/Mftf/Section/AdminEditRoleResourcesSection.xml index 48873bd9d152e..2352575257afb 100644 --- a/app/code/Magento/User/Test/Mftf/Section/AdminEditRoleResourcesSection.xml +++ b/app/code/Magento/User/Test/Mftf/Section/AdminEditRoleResourcesSection.xml @@ -11,6 +11,8 @@ <element name="resources" type="checkbox" selector="#role_info_tabs_account"/> <element name="storeName" type="checkbox" selector="//label[contains(text(),'{{var1}}')]" parameterized="true"/> <element name="reportsCheckbox" type="text" selector="//li[@data-id='Magento_Reports::report']//a[text()='Reports']"/> + <element name="resourceCheckboxLink" type="checkbox" selector="//li[@data-id='{{resourceId}}']//a[text()='{{resourceName}}']" timeout="30" parameterized="true"/> + <element name="resourceCheckbox" type="checkbox" selector="//li[@data-id='{{resourceId}}']/input" timeout="30" parameterized="true"/> <element name="userRoles" type="text" selector="//span[contains(text(), 'User Roles')]"/> </section> </sections> diff --git a/app/code/Magento/User/Test/Mftf/Test/AdminReviewOrderWithReportsPermissionTest.xml b/app/code/Magento/User/Test/Mftf/Test/AdminReviewOrderWithReportsPermissionTest.xml new file mode 100644 index 0000000000000..8629187fe3ffb --- /dev/null +++ b/app/code/Magento/User/Test/Mftf/Test/AdminReviewOrderWithReportsPermissionTest.xml @@ -0,0 +1,109 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="AdminReviewOrderWithReportsPermissionTest"> + <annotations> + <features value="User"/> + <stories value="Admin with restricted permissions"/> + <title value="User should be able to review ordered products with only 'Reports' permission"/> + <description value="User should be able to review ordered products with only 'Reports' permission"/> + <severity value="MAJOR"/> + <testCaseId value="MAGETWO-25812"/> + <group value="user"/> + </annotations> + <before> + <createData entity="ApiCategory" stepKey="createCategory"/> + <createData entity="ApiSimpleProduct" stepKey="createProduct"> + <requiredEntity createDataKey="createCategory"/> + </createData> + + <actionGroup ref="AdminLoginActionGroup" stepKey="loginAsAdmin"/> + <actionGroup ref="AdminCreateWebsiteActionGroup" stepKey="createWebsite"> + <argument name="newWebsiteName" value="{{NewWebSiteData.name}}"/> + <argument name="websiteCode" value="{{NewWebSiteData.code}}"/> + </actionGroup> + <actionGroup ref="AdminCreateNewStoreGroupActionGroup" stepKey="createNewStore"> + <argument name="website" value="{{NewWebSiteData.name}}"/> + <argument name="storeGroupName" value="{{NewStoreData.name}}"/> + <argument name="storeGroupCode" value="{{NewStoreData.code}}"/> + </actionGroup> + <actionGroup ref="AdminCreateStoreViewActionGroup" stepKey="createCustomStoreView"> + <argument name="StoreGroup" value="NewStoreData"/> + <argument name="customStore" value="NewStoreViewData"/> + </actionGroup> + <actionGroup ref="AdminCreateCustomerWithWebsiteAndStoreViewActionGroup" stepKey="createCustomerWithWebsiteAndStoreView"> + <argument name="customerData" value="Simple_US_Customer"/> + <argument name="address" value="US_Address_NY"/> + <argument name="website" value="{{NewWebSiteData.name}}"/> + <argument name="storeView" value="{{NewStoreViewData.name}}"/> + </actionGroup> + <actionGroup ref="SearchForProductOnBackendActionGroup" stepKey="searchForProductOnAdmin"> + <argument name="product" value="$createProduct$"/> + </actionGroup> + <actionGroup ref="CreatedProductConnectToWebsiteActionGroup" stepKey="productConnectToWebsite"> + <argument name="website" value="NewWebSiteData"/> + <argument name="product" value="$createProduct$"/> + </actionGroup> + <actionGroup ref="CreateOrderInStoreChoosingPaymentMethodActionGroup" stepKey="createOrder"> + <argument name="product" value="$createProduct$"/> + <argument name="customer" value="Simple_US_Customer"/> + <argument name="storeView" value="NewStoreViewData"/> + </actionGroup> + <actionGroup ref="AdminStartCreateUserRoleActionGroup" stepKey="startCreateUserRole"> + <argument name="roleName" value="{{limitedRole.name}}"/> + <argument name="userPassword" value="{{_ENV.MAGENTO_ADMIN_PASSWORD}}"/> + <argument name="resourceAccess" value="Custom"/> + </actionGroup> + <actionGroup ref="AdminChooseUserRoleResourceActionGroup" stepKey="setResourceAccess"> + <argument name="resourceId" value="Magento_Reports::report"/> + <argument name="resourceName" value="Reports"/> + </actionGroup> + <actionGroup ref="AdminSaveUserRoleActionGroup" stepKey="saveRole"/> + <actionGroup ref="AdminCreateUserWithRoleActionGroup" stepKey="createUser"> + <argument name="role" value="limitedRole"/> + <argument name="user" value="NewAdminUser"/> + </actionGroup> + </before> + <after> + <deleteData createDataKey="createProduct" stepKey="deleteProduct"/> + <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> + + <actionGroup ref="AdminLogoutActionGroup" stepKey="logoutAdminUser"/> + <actionGroup ref="AdminLoginActionGroup" stepKey="loginAsAdmin"/> + <actionGroup ref="AdminDeleteCreatedUserActionGroup" stepKey="deleteUser"> + <argument name="user" value="NewAdminUser"/> + </actionGroup> + <actionGroup ref="ClearFiltersAdminDataGridActionGroup" stepKey="clearAdminUserGridFilters"/> + <amOnPage url="{{AdminRolesPage.url}}" stepKey="navigateToUserRoleGrid"/> + <actionGroup ref="AdminDeleteRoleActionGroup" stepKey="deleteRole"> + <argument name="role" value="limitedRole"/> + </actionGroup> + + <actionGroup ref="AdminDeleteCustomerActionGroup" stepKey="deleteCustomer"> + <argument name="customerEmail" value="Simple_US_Customer.email"/> + </actionGroup> + <actionGroup ref="AdminClearCustomersFiltersActionGroup" stepKey="clearCustomerFilters"/> + <actionGroup ref="AdminDeleteWebsiteActionGroup" stepKey="deleteWebsite"> + <argument name="websiteName" value="{{NewWebSiteData.name}}"/> + </actionGroup> + <actionGroup ref="AdminClearFiltersActionGroup" stepKey="clearProductFilters"/> + <actionGroup ref="AdminLogoutActionGroup" stepKey="logoutFromAdmin"/> + </after> + + <actionGroup ref="AdminLogoutActionGroup" stepKey="logoutFromAdmin"/> + <actionGroup ref="AdminLoginActionGroup" stepKey="logAsNewUser"> + <argument name="username" value="{{NewAdminUser.username}}"/> + <argument name="password" value="{{NewAdminUser.password}}"/> + </actionGroup> + <actionGroup ref="AdminReviewOrderActionGroup" stepKey="reviewOrder"> + <argument name="productName" value="$createProduct.name$"/> + </actionGroup> + </test> +</tests> From 732dfebe0a1ac65d264480c186984bc4c5259456 Mon Sep 17 00:00:00 2001 From: IvanPletnyov <ivan.pletnyov@transoftgroup.com> Date: Mon, 12 Oct 2020 14:54:13 +0300 Subject: [PATCH 073/139] MC-37543: Create automated test for "Add static block on a category page" --- .../Controller/Adminhtml/Category/Save/SaveCategoryTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dev/tests/integration/testsuite/Magento/Catalog/Controller/Adminhtml/Category/Save/SaveCategoryTest.php b/dev/tests/integration/testsuite/Magento/Catalog/Controller/Adminhtml/Category/Save/SaveCategoryTest.php index adef25f88395c..dc74a2c2cba7b 100644 --- a/dev/tests/integration/testsuite/Magento/Catalog/Controller/Adminhtml/Category/Save/SaveCategoryTest.php +++ b/dev/tests/integration/testsuite/Magento/Catalog/Controller/Adminhtml/Category/Save/SaveCategoryTest.php @@ -51,7 +51,7 @@ protected function setUp(): void */ protected function tearDown(): void { - if(!empty($this->createdCategoryId)) { + if (!empty($this->createdCategoryId)) { try { $this->categoryRepository->deleteByIdentifier($this->createdCategoryId); } catch (NoSuchEntityException $e) { From 20d7d416e252b8b27141ebdb5e2b768c85d3f84b Mon Sep 17 00:00:00 2001 From: Bohdan Shevchenko <1408sheva@gmail.com> Date: Mon, 12 Oct 2020 15:05:42 +0300 Subject: [PATCH 074/139] MC-37546: Create automated test for "Create new Category Update" --- ...rontCheckPresentSubCategoryActionGroup.xml | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontCheckPresentSubCategoryActionGroup.xml diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontCheckPresentSubCategoryActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontCheckPresentSubCategoryActionGroup.xml new file mode 100644 index 0000000000000..7d8113f05518b --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontCheckPresentSubCategoryActionGroup.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="StorefrontCheckPresentSubCategoryActionGroup"> + <annotations> + <description>Checks for a subcategory in topmenu</description> + </annotations> + <arguments> + <argument name="parenCategoryName" type="string"/> + <argument name="childCategoryName" type="string"/> + </arguments> + + <waitForElement selector="{{AdminCategorySidebarTreeSection.categoryHighlighted(parenCategoryName)}}" stepKey="waitForTopMenuLoaded"/> + <moveMouseOver selector="{{AdminCategorySidebarTreeSection.categoryHighlighted(parenCategoryName)}}" stepKey="moveMouseToParentCategory"/> + <seeElement selector="{{AdminCategorySidebarTreeSection.categoryHighlighted(childCategoryName)}}" stepKey="seeCategoryUpdated"/> + </actionGroup> +</actionGroups> From 372049ac123cfc1055850358adb5e88db67cc6ce Mon Sep 17 00:00:00 2001 From: Viktor Kopin <viktor.kopin@transoftgroup.com> Date: Mon, 12 Oct 2020 16:55:59 +0300 Subject: [PATCH 075/139] MC-38113: Same shipping address is repeating multiple times in storefront checkout when Reordered --- ...reFrontCustomerHasNoOtherAddressesActionGroup.xml} | 2 +- .../AdminReorderAddressNotSavedInAddressBookTest.xml | 11 +++++------ 2 files changed, 6 insertions(+), 7 deletions(-) rename app/code/Magento/Customer/Test/Mftf/ActionGroup/{AssertCustomerHasNoOtherAddressesActionGroup.xml => AssertStoreFrontCustomerHasNoOtherAddressesActionGroup.xml} (89%) diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AssertCustomerHasNoOtherAddressesActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AssertStoreFrontCustomerHasNoOtherAddressesActionGroup.xml similarity index 89% rename from app/code/Magento/Customer/Test/Mftf/ActionGroup/AssertCustomerHasNoOtherAddressesActionGroup.xml rename to app/code/Magento/Customer/Test/Mftf/ActionGroup/AssertStoreFrontCustomerHasNoOtherAddressesActionGroup.xml index 58a5069403b7f..2fde4d915c99f 100644 --- a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AssertCustomerHasNoOtherAddressesActionGroup.xml +++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AssertStoreFrontCustomerHasNoOtherAddressesActionGroup.xml @@ -7,7 +7,7 @@ --> <actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> - <actionGroup name="AssertCustomerHasNoOtherAddressesActionGroup"> + <actionGroup name="AssertStoreFrontCustomerHasNoOtherAddressesActionGroup"> <annotations> <description>Verifies customer no additional address in address book</description> </annotations> diff --git a/app/code/Magento/Sales/Test/Mftf/Test/AdminReorderAddressNotSavedInAddressBookTest.xml b/app/code/Magento/Sales/Test/Mftf/Test/AdminReorderAddressNotSavedInAddressBookTest.xml index aca0c4e6a8f8a..2b4bb43ec36cd 100644 --- a/app/code/Magento/Sales/Test/Mftf/Test/AdminReorderAddressNotSavedInAddressBookTest.xml +++ b/app/code/Magento/Sales/Test/Mftf/Test/AdminReorderAddressNotSavedInAddressBookTest.xml @@ -22,19 +22,18 @@ <createData entity="ApiSimpleProduct" stepKey="Product"> <requiredEntity createDataKey="Category"/> </createData> + <createData entity="Simple_Customer_Without_Address" stepKey="Customer"/> <actionGroup ref="AdminLoginActionGroup" stepKey="loginAsAdmin"/> - <actionGroup ref="StorefrontOpenCustomerAccountCreatePageActionGroup" stepKey="openCreateAccountPage"/> - <actionGroup ref="StorefrontFillCustomerAccountCreationFormActionGroup" stepKey="fillCreateAccountForm"> - <argument name="customer" value="CustomerEntityOne"/> + <actionGroup ref="LoginToStorefrontActionGroup" stepKey="loginToStorefrontAccount"> + <argument name="Customer" value="$Customer$"/> </actionGroup> - <actionGroup ref="StorefrontClickCreateAnAccountCustomerAccountCreationFormActionGroup" stepKey="submitCreateAccountForm"/> </before> <after> <actionGroup ref="StorefrontCustomerLogoutActionGroup" stepKey="customerLogout"/> - <actionGroup ref="DeleteCustomerFromAdminActionGroup" stepKey="deleteCustomer"/> <actionGroup ref="AdminLogoutActionGroup" stepKey="adminLogout"/> <deleteData createDataKey="Product" stepKey="deleteProduct"/> <deleteData createDataKey="Category" stepKey="deleteCategory"/> + <deleteData createDataKey="Customer" stepKey="deleteCustomer"/> </after> <!-- Create order for registered customer --> @@ -53,6 +52,6 @@ <actionGroup ref="AdminReorderActionGroup" stepKey="reorder"/> <!-- Assert no additional addresses saved --> - <actionGroup ref="AssertCustomerHasNoOtherAddressesActionGroup" stepKey="assertAddresses"/> + <actionGroup ref="AssertStoreFrontCustomerHasNoOtherAddressesActionGroup" stepKey="assertAddresses"/> </test> </tests> From a8743bbf0121d2e5cceb2f1df37524031501c5ed Mon Sep 17 00:00:00 2001 From: Yurii Sapiha <yurasapiga93@gmail.com> Date: Mon, 12 Oct 2020 17:27:59 +0300 Subject: [PATCH 076/139] MC-37070: Create automated test for "Import products with shared images" --- .../Product/Gallery/UpdateHandlerTest.php | 102 +++++++- .../Import/ImportWithSharedImagesTest.php | 236 ++++++++++++++++++ ...talog_import_products_with_same_images.csv | 3 + .../_files/magento_image.jpg | Bin 0 -> 13353 bytes 4 files changed, 339 insertions(+), 2 deletions(-) create mode 100644 dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/ImportWithSharedImagesTest.php create mode 100644 dev/tests/integration/testsuite/Magento/CatalogImportExport/_files/catalog_import_products_with_same_images.csv create mode 100644 dev/tests/integration/testsuite/Magento/CatalogImportExport/_files/magento_image.jpg diff --git a/dev/tests/integration/testsuite/Magento/Catalog/Model/Product/Gallery/UpdateHandlerTest.php b/dev/tests/integration/testsuite/Magento/Catalog/Model/Product/Gallery/UpdateHandlerTest.php index 7ee2c62453df5..ce36b27d51e7d 100644 --- a/dev/tests/integration/testsuite/Magento/Catalog/Model/Product/Gallery/UpdateHandlerTest.php +++ b/dev/tests/integration/testsuite/Magento/Catalog/Model/Product/Gallery/UpdateHandlerTest.php @@ -8,6 +8,7 @@ namespace Magento\Catalog\Model\Product\Gallery; +use Magento\Catalog\Api\Data\ProductAttributeMediaGalleryEntryInterface; use Magento\Catalog\Api\Data\ProductInterface; use Magento\Catalog\Api\ProductRepositoryInterface; use Magento\Catalog\Model\Product; @@ -351,6 +352,23 @@ public function testExecuteWithTwoImagesOnStoreView(): void } } + /** + * @magentoDataFixture Magento/Catalog/_files/product_with_image.php + * @magentoDataFixture Magento/Catalog/_files/second_product_simple.php + * + * @return void + */ + public function testDeleteSharedImage(): void + { + $product = $this->getProduct(null, 'simple'); + $this->duplicateMediaGalleryForProduct('/m/a/magento_image.jpg', 'simple2'); + $secondProduct = $this->getProduct(null, 'simple2'); + $this->updateHandler->execute($this->prepareRemoveImage($product), []); + $product = $this->getProduct(null, 'simple'); + $this->assertEmpty($product->getMediaGalleryImages()->getItems()); + $this->checkProductImageExist($secondProduct, '/m/a/magento_image.jpg'); + } + /** * @inheritdoc */ @@ -371,11 +389,13 @@ protected function tearDown(): void * Returns current product. * * @param int|null $storeId + * @param string|null $sku * @return ProductInterface|Product */ - private function getProduct(?int $storeId = null): ProductInterface + private function getProduct(?int $storeId = null, ?string $sku = null): ProductInterface { - return $this->productRepository->get('simple', false, $storeId, true); + $sku = $sku ?: 'simple'; + return $this->productRepository->get($sku, false, $storeId, true); } /** @@ -464,6 +484,84 @@ public function testDeleteWithMultiWebsites(): void $this->assertArrayNotHasKey($secondStoreId, $imageRolesPerStore); } + /** + * Check product image link and product image exist + * + * @param ProductInterface $product + * @param string $imagePath + * @return void + */ + private function checkProductImageExist(ProductInterface $product, string $imagePath): void + { + $productImageItem = $product->getMediaGalleryImages()->getFirstItem(); + $this->assertEquals($imagePath, $productImageItem->getFile()); + $productImageFile = $productImageItem->getPath(); + $this->assertNotEmpty($productImageFile); + $this->assertTrue($this->mediaDirectory->getDriver()->isExists($productImageFile)); + $this->fileName = $productImageFile; + } + + /** + * Prepare the product to remove image + * + * @param ProductInterface $product + * @return ProductInterface + */ + private function prepareRemoveImage(ProductInterface $product): ProductInterface + { + $item = $product->getMediaGalleryImages()->getFirstItem(); + $item->setRemoved('1'); + $galleryData = [ + 'images' => [ + (int)$item->getValueId() => $item->getData(), + ] + ]; + $product->setData(ProductInterface::MEDIA_GALLERY, $galleryData); + $product->setStoreId(0); + + return $product; + } + + /** + * Duplicate media gallery entries for a product + * + * @param string $imagePath + * @param string $productSku + * @return void + */ + private function duplicateMediaGalleryForProduct(string $imagePath, string $productSku): void + { + $product = $this->getProduct(null, $productSku); + $connect = $this->galleryResource->getConnection(); + $select = $connect->select()->from($this->galleryResource->getMainTable())->where('value = ?', $imagePath); + $res = $connect->fetchRow($select); + $value_id = $res['value_id']; + unset($res['value_id']); + $rows = [ + 'attribute_id' => $res['attribute_id'], + 'value' => $res['value'], + ProductAttributeMediaGalleryEntryInterface::MEDIA_TYPE => $res['media_type'], + ProductAttributeMediaGalleryEntryInterface::DISABLED => $res['disabled'], + ]; + $connect->insert($this->galleryResource->getMainTable(), $rows); + $select = $connect->select() + ->from($this->galleryResource->getTable(Gallery::GALLERY_VALUE_TABLE)) + ->where('value_id = ?', $value_id); + $res = $connect->fetchRow($select); + $newValueId = (int)$value_id + 1; + $rows = [ + 'value_id' => $newValueId, + 'store_id' => $res['store_id'], + ProductAttributeMediaGalleryEntryInterface::LABEL => $res['label'], + ProductAttributeMediaGalleryEntryInterface::POSITION => $res['position'], + ProductAttributeMediaGalleryEntryInterface::DISABLED => $res['disabled'], + 'row_id' => $product->getRowId(), + ]; + $connect->insert($this->galleryResource->getTable(Gallery::GALLERY_VALUE_TABLE), $rows); + $rows = ['value_id' => $newValueId, 'row_id' => $product->getRowId()]; + $connect->insert($this->galleryResource->getTable(Gallery::GALLERY_VALUE_TO_ENTITY_TABLE), $rows); + } + /** * @param Product $product * @param array $roles diff --git a/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/ImportWithSharedImagesTest.php b/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/ImportWithSharedImagesTest.php new file mode 100644 index 0000000000000..4c04e5a8814e5 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/ImportWithSharedImagesTest.php @@ -0,0 +1,236 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\CatalogImportExport\Model\Import; + +use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\Catalog\Model\Product as ProductEntity; +use Magento\Catalog\Model\Product\Media\ConfigInterface; +use Magento\Framework\App\Filesystem\DirectoryList; +use Magento\Framework\Exception\NoSuchEntityException; +use Magento\Framework\Filesystem; +use Magento\Framework\Filesystem\Driver\File; +use Magento\Framework\ObjectManagerInterface; +use Magento\ImportExport\Model\Import; +use Magento\ImportExport\Model\Import\Source\Csv; +use Magento\ImportExport\Model\Import\Source\CsvFactory; +use Magento\ImportExport\Model\ResourceModel\Import\Data; +use Magento\TestFramework\Helper\Bootstrap; +use PHPUnit\Framework\TestCase; + +/** + * Checks that product import with same images can be successfully done + * + * @magentoAppArea adminhtml + * @magentoDbIsolation enabled + * + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ +class ImportWithSharedImagesTest extends TestCase +{ + /** @var ObjectManagerInterface */ + private $objectManager; + + /** @var Filesystem */ + private $fileSystem; + + /** @var ProductRepositoryInterface */ + private $productRepository; + + /** @var File */ + private $fileDriver; + + /** @var Import */ + private $import; + + /** @var ConfigInterface */ + private $mediaConfig; + + /** @var array */ + private $appParams; + + /** @var array */ + private $createdProductsSkus = []; + + /** @var array */ + private $filesToRemove = []; + + /** @var CsvFactory */ + private $csvFactory; + + /** @var Data */ + private $importDataResource; + + /** + * @inheritdoc + */ + protected function setUp(): void + { + parent::setUp(); + + $this->objectManager = Bootstrap::getObjectManager(); + $this->fileSystem = $this->objectManager->get(Filesystem::class); + $this->fileDriver = $this->objectManager->get(File::class); + $this->mediaConfig = $this->objectManager->get(ConfigInterface::class); + $this->productRepository = $this->objectManager->get(ProductRepositoryInterface::class); + $this->productRepository->cleanCache(); + $this->import = $this->objectManager->get(ProductFactory::class)->create(); + $this->csvFactory = $this->objectManager->get(CsvFactory::class); + $this->importDataResource = $this->objectManager->get(Data::class); + $this->appParams = Bootstrap::getInstance()->getBootstrap()->getApplication() + ->getInitParams()[\Magento\Framework\App\Bootstrap::INIT_PARAM_FILESYSTEM_DIR_PATHS]; + } + + /** + * @inheritdoc + */ + protected function tearDown(): void + { + $this->removeFiles(); + $this->removeProducts(); + $this->importDataResource->cleanBunches(); + + parent::tearDown(); + } + + /** + * @return void + */ + public function testImportProductsWithSameImages(): void + { + $this->moveImages('magento_image.jpg'); + $source = $this->prepareFile('catalog_import_products_with_same_images.csv'); + $this->updateUploader(); + $errors = $this->import->setParameters([ + 'behavior' => Import::BEHAVIOR_ADD_UPDATE, + 'entity' => ProductEntity::ENTITY, + ]) + ->setSource($source)->validateData(); + $this->assertEmpty($errors->getAllErrors()); + $this->import->importData(); + $this->createdProductsSkus = ['SimpleProductForTest1', 'SimpleProductForTest2']; + $this->checkProductsImages('/m/a/magento_image.jpg', $this->createdProductsSkus); + } + + /** + * Check product images + * + * @param string $expectedImagePath + * @param array $productSkus + * @return void + */ + private function checkProductsImages(string $expectedImagePath, array $productSkus): void + { + foreach ($productSkus as $productSku) { + $product = $this->productRepository->get($productSku); + $productImageItem = $product->getMediaGalleryImages()->getFirstItem(); + $productImageFile = $productImageItem->getFile(); + $productImagePath = $productImageItem->getPath(); + $this->filesToRemove[] = $productImagePath; + $this->assertEquals($expectedImagePath, $productImageFile); + $this->assertNotEmpty($productImagePath); + $this->assertTrue($this->fileDriver->isExists($productImagePath)); + } + } + + /** + * Remove created files + * + * @return void + */ + private function removeFiles(): void + { + foreach ($this->filesToRemove as $file) { + if ($this->fileDriver->isExists($file)) { + $this->fileDriver->deleteFile($file); + } + } + } + + /** + * Remove created products + * + * @return void + */ + private function removeProducts(): void + { + foreach ($this->createdProductsSkus as $sku) { + try { + $this->productRepository->deleteById($sku); + } catch (NoSuchEntityException $e) { + //already removed + } + } + } + + /** + * Prepare file + * + * @param string $fileName + * @return Csv + */ + private function prepareFile(string $fileName): Csv + { + $tmpDirectory = $this->fileSystem->getDirectoryWrite(DirectoryList::VAR_DIR); + $fixtureDir = realpath(__DIR__ . '/../../_files'); + $filePath = $tmpDirectory->getAbsolutePath($fileName); + $this->filesToRemove[] = $filePath; + $tmpDirectory->getDriver()->copy($fixtureDir . DIRECTORY_SEPARATOR . $fileName, $filePath); + $source = $this->csvFactory->create( + [ + 'file' => $fileName, + 'directory' => $tmpDirectory + ] + ); + + return $source; + } + + /** + * Update upload to use sandbox folders + * + * @return void + */ + private function updateUploader(): void + { + $uploader = $this->import->getUploader(); + $rootDirectory = $this->fileSystem->getDirectoryWrite(DirectoryList::ROOT); + $destDir = $rootDirectory->getRelativePath( + $this->appParams[DirectoryList::MEDIA][DirectoryList::PATH] + . DS . $this->mediaConfig->getBaseMediaPath() + ); + $tmpDir = $rootDirectory->getRelativePath( + $this->appParams[DirectoryList::MEDIA][DirectoryList::PATH] + ); + $rootDirectory->create($destDir); + $rootDirectory->create($tmpDir); + $uploader->setDestDir($destDir); + $uploader->setTmpDir($tmpDir); + } + + /** + * Move images to appropriate folder + * + * @param string $fileName + * @return void + */ + private function moveImages(string $fileName): void + { + $rootDirectory = $this->fileSystem->getDirectoryWrite(DirectoryList::ROOT); + $tmpDir = $rootDirectory->getRelativePath( + $this->appParams[DirectoryList::MEDIA][DirectoryList::PATH] + ); + $fixtureDir = realpath(__DIR__ . '/../../_files'); + $tmpFilePath = $rootDirectory->getAbsolutePath($tmpDir . DS . $fileName); + $this->fileDriver->createDirectory($tmpDir); + $rootDirectory->getDriver()->copy( + $fixtureDir . DIRECTORY_SEPARATOR . $fileName, + $tmpFilePath + ); + $this->filesToRemove[] = $tmpFilePath; + } +} diff --git a/dev/tests/integration/testsuite/Magento/CatalogImportExport/_files/catalog_import_products_with_same_images.csv b/dev/tests/integration/testsuite/Magento/CatalogImportExport/_files/catalog_import_products_with_same_images.csv new file mode 100644 index 0000000000000..7761ed7ac2360 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/CatalogImportExport/_files/catalog_import_products_with_same_images.csv @@ -0,0 +1,3 @@ +sku,store_view_code,attribute_set_code,product_type,categories,product_websites,name,description,short_description,weight,product_online,tax_class_name,visibility,price,special_price,special_price_from_date,special_price_to_date,url_key,meta_title,meta_keywords,meta_description,base_image,base_image_label,small_image,small_image_label,thumbnail_image,thumbnail_image_label,swatch_image,swatch_image_label,created_at,updated_at,new_from_date,new_to_date,display_product_options_in,map_price,msrp_price,map_enabled,gift_message_available,custom_design,custom_design_from,custom_design_to,custom_layout_update,page_layout,product_options_container,msrp_display_actual_price_type,country_of_manufacture,additional_attributes,qty,out_of_stock_qty,use_config_min_qty,is_qty_decimal,allow_backorders,use_config_backorders,min_cart_qty,use_config_min_sale_qty,max_cart_qty,use_config_max_sale_qty,is_in_stock,notify_on_stock_below,use_config_notify_stock_qty,manage_stock,use_config_manage_stock,use_config_qty_increments,qty_increments,use_config_enable_qty_inc,enable_qty_increments,is_decimal_divided,website_id,related_skus,related_position,crosssell_skus,crosssell_position,upsell_skus,upsell_position,additional_images,additional_image_labels,hide_from_product_page,custom_options,bundle_price_type,bundle_sku_type,bundle_price_view,bundle_weight_type,bundle_values,bundle_shipment_type,configurable_variations,configurable_variation_labels,associated_skus +SimpleProductForTest1,,Default,simple,Default,base,SimpleProductAfterImport1,,,1,1,Taxable Goods,"Catalog, Search",250,,,,simple-product-for-test-1,,,,magento_image.jpg,BASE magento_image.jpg,magento_image.jpg,SMALL blueshirt,magento_image.jpg,Thumb Image,,,"3/4/19, 5:53 AM","3/4/19, 4:47 PM",,,Block after Info Column,,,,,,,,,,,Use config,,,100,0,1,0,0,1,1,1,0,1,1,,1,0,1,1,0,1,0,0,0,,,,,,,,,,,,,,,,,,, +SimpleProductForTest2,,Default,simple,Default,base,SimpleProductAfterImport2,,,1,1,Taxable Goods,"Catalog, Search",300,,,,simple-product-for-test-2,,,,magento_image.jpg,BASE magento_image.jpg,magento_image.jpg,SMALL blueshirt,magento_image.jpg,Thumb Image,,,"3/4/19, 5:53 AM","3/4/19, 4:47 PM",,,Block after Info Column,,,,,,,,,,,Use config,,,100,0,1,0,0,1,1,1,0,1,1,,1,0,1,1,0,1,0,0,0,,,,,,,,,,,,,,,,,,, diff --git a/dev/tests/integration/testsuite/Magento/CatalogImportExport/_files/magento_image.jpg b/dev/tests/integration/testsuite/Magento/CatalogImportExport/_files/magento_image.jpg new file mode 100644 index 0000000000000000000000000000000000000000..3b825a41b2101a758ee9c45b9304a6d08a90c729 GIT binary patch literal 13353 zcmZvC1yml*vhCm!2=49{+}+*X-Q8V-yL*5HcXubaySuwXkO1M4|D1E*z4yKP*7~N` z^z@$Uu07RN-Tl7&z6~HsiAjn9AfNzX^6>-S*MMLE4D_#j{DVP&fq_9lfP;a7LqJ0O z6-a1Es1JdJhK7NKg@u8J`$s<i6~zA%5HN6XNJvOHXlOX(&v2iSQUBJUBK=d3^sfuz zzc2sQ=6@94djTY95C{-NFc2gF6bS?j3FN&Wzym;lkDDMMz<&w!hh9)HV4xuHs{r)B zDh?F>tM;D)yR2E{M*&ii+5PA;E~Lc+0KirirL{|%2PlhWJ}Q>Jc>_Z}PC+ois}3Kd zfI=tiSK0`FT93T08aDy|sWt$>PmDBNdal9qW-X#95D#kY>VLa046l{$0odJz9^g|K zqAg`drr|?=8%$#eUshC|dG>Lh<pSV$X)ida(JkY@6a9Mb;&}eFjjVTrg<YcpGCIX( z)}@N<x1!|9n-h2~<%^WcNN#qO1G&>cdaHU--x>STB}^WxxnMkr!4+@F^Ya+3@J(dj z4-m$>@11E2ZN8l5<#Y83Fm4NIXEr*mo<)BcEt~*yJLO+BUiFp-q@$aR4<<yH@4)Q! zT+vD#eIvyoEnEQj(&!&&yAo|q&c;GM0!BqJw#dxyC7%9_u3?KbumJe(m>-j0u6C<* zz576sOD_DFur*!wLDuZj=5jI~fYMEW+kW2^l**a2TzYH;vc^h|HHTZCwd^WK1P*|| z1LLKgZLEUhG=us{F0GR~BnS;Iag;}c{JNn%0LTp7Vr*0{i_TI>_2_SjBR?>~of)`P za7CS(D))fTiRNnOZhnf+?N?{S$4n4A{`}03M1h-+J~-bDz=}P8l|$wv$3xus<dm>S z{CSgWf5q+Ea-cH;V9IGP+Iqc|dZwnLvaSVRov%(EwhRu9{UaHC05+SI+++4&`rGT@ zS>ej|j}PhW7C1X_jSRr2VgU-X;OpX{A}_oRE8oWss-wzi0I<tx`ZIGDlc8M3CTf30 zLoy8j$jtdzTd~Z0DKAF2m(hd(fR@DMp_A-jg|Zg#!5zbC?=YU4|HZfuwgLPfe8v4B z<i|Jk-{=ZJLV-hofPz8)LswA5zfb-T0i`-%__zVtRTg?f*Vq&Q0H0GXtfP6)y>UL2 zf!>fVbgsFm+-YhuB?Q1=D}o({<40cxNYW?G)T(*_C=FS|kfrR3I!%&8Zqx`XuZOXE z3f&$sZ6J2X=(j|nQ%rbIv?z(5re+<?pLswT3rhnZ8O!W3-mxH3oY(p0l}-Uf{FF@z zizklaJLYQ&&gQ=34qntyqqlbtp!hrXLF6pfMM2(MQp0f4gva?)#cR`J0FeI+?>u5j z!moL$F`zn!8^*tbyy13j69A2x_tI4Eu=?yy(U+d<4LuKgyCPIhfY*5pP};ILe&-x| ze(~fOP0QUN%*5F<{#5S6jxh{8zXP%t)^a?2zH6gR$_*%>R7G8E45Qz1obLhPc~A1n zZld-WF5~m<usRpV1Kveln2LsXcWw{V#BZLcOv@dCrz5G0F6xp{sLGT2usIIxsR96M zd=<C6*EB}Z^I5t|RT%dEzLQOnT3o;Dw+Y}gfH&4zg!vBe-z;i6lu6jz+3Oa_MJ*0@ zWdj6^;<s0lvGh}Prc22AAvXihjS(}MCs1=gK);na(SOuT>fAB4Ea2kA@_k(sIUfHc zbpk*i@S2U*`srV6y1P=X-WBT?JlsU~t|kWndqZG{=9&|W#Wa`RzBsLq`v744`gReQ zVf9;8>-;;-+K2yzOl^pBxxd)k@-(ymus)Zdb<QVP@6!)XFj)K3&=`Fb{+B@ghiPE{ z!!!P68uWi0BiR3kY5rF_0Uxf=(RW4a;p@x?C@&pOJ%h0_>QFcUpy&l9X2xZY5F8z2 z0>H`^Ti6*oj;g2DoJ?=ikNa(auM%g<{MU1C`57<4;pIq!3BNuEs;}}PfYWYgUO#mk zw4nHfF^#&B!V?pFIYjgafJd|!(ll08KB2yiBMmxqGjHnc91fhcLY84^4f$Z9P%_yg z_j?V0Y0cT?9}fU4+9zO9^>|HP!@Ng+Tvk=snm>K{6?aGY(E?!rP0wrjW!%2a(c$&L z(z-))<JSfLp#aFd69N}{$HeJr8%j%snp>*5mD8V0SO0zSX9vL5CTc9Fe0;MpdJ$i> zY*Y)mN_}>+je?qcQ~<2XB!_2B-}G097tB{B+UV!?q!gFAs^syQTL$2qijSXh={egz z2sv)nI#Zl(`L-(6ajx|ymKVT_p}amg{%o)PVwKBt-*vR+kFP#6;T6S4KST&tXR6WB z3OaH24#-=*WMt!*dwAZ4hFZ_JV^-q;pL<gF?%*)E>-hN%a3_z)m=8v{V7|*AiF!N% zuEF_JSq80d-)@fd@09Z@y5*}x;>NwC$r(p_Ku??eEimcajr{!7UpJ4fDNaV(VB!RS zFR%mv5|@#qI2p@Vm>BGB=)=F>_G56%7K@2{0DfQRwy?QVnkng9^SgryFMawCZ+zwU zWcqvYkDB=)K_h4N2MtV_*4|8O`B>vO6aWBfKGQJfE(0m*^@A308S>})|1W<4gb&`a zf82)z1qA_tfPexA`*`>xkoX%a08r3K$V7-Js0@r~!~%kbFz6)AZ0w2{giJ!L1`hw8 zt3lv^cVMGo3&U=WWmra<)I#cVch`4L4@t6ZMxGzy>GX4bo6U+sMDbwu&Myii%6t9L zjAoDJ!ye!V1f>gB(^J0Ca^9{HVy<5H^6%7NW&2&A8~7&5ugYxlq`IVoyb!ppb3P2d z<SOP5GqD4mR@HjNA|zny2Fcb44{|+1E4xd<HFZ*gp?f<P1)}DSFu^+%7-X<-7OkOW zb)HYY{6jd4tE%iHq>PAM?c&p{%uJ~*{pj%z0CYG2Wo*;6bPBj#%HPzf(njewy)<P9 z+hjx<rArwge_N?*xcFqvfZo({n=!e!hqkANrzs}Fa^ZiS+y>umIEIY=9kU@ShQ05! zlX6&IUnV=yq=4G=V>T#!kdzb?YQTa?v#hgqTAxLx#zrs7tVEjcFzmd*IL?vH{ITY6 zBXw2r{YwM4a7rh){a8<h1dR}Nn>`9665rh3_C~@k-(-Mr?l1U}{!G7Krn?kIGB>Z6 zO1idLl^J&E0Zp81_-~wEJ8aRjTxTbjpS3BHg`BNne&k!LL~h^{QDPTZF-FU!hb@7( zsEj)z61u0pb%W`NiWtHre{0D+99m~4b6YPf!3GPGB|;R_TNkvmfZeAx;^k;zE3nu! zLZ<c&tgVL+KWTd1zSqqtn^k4$ys2P`Nh&@Roh@_#kz22+FG;|`N;2B(5h&n*#thJ* z0{6?{R0(PtpoKCqh)#kZ`007PSIJ!>$7D-Nven<I#im#nz45w%(T_LK`AWKD{AZ}z zvWvu87k7d+H`BM2=N41ATgDaB=<y^LE5iGJ%(M(NJwukG<yADzaJ7kKr}}3jqxNwQ z`f7D7^0&I=Vrh*NYPWim1d=thzN_LFHoosAHbvA#bn~TcTRvp9bE#T=>Y(xQ>fU}e z@h`}R-9cV4gPkrQazu)fr*QpSsjShAZTEu?y}Vv#8@rRun@B}+lYKjd%~Nn}f1*S^ zk7NUXYCLB-t-8y)i&RtT`;c2!KSPOhWN@r}g}pCdHo*ULsfiR77~{yuc0vxv#!eg$ zT1p@)aWPhP@7BL|yN`zC5W!*(cdGTA`S}ZUQQZVKtI>LlW`T<0JK%Iod_Y3D9gyc; zA9jBcIpMmaAXy&seW)_1<N%e#?TjwAeP3|(t1&$#^;3$nux8%Ox9WB&!6-_Fdj$L; zgDWreE;N#F1Q#oQ5*IH`13xq(7_N5m|Cm+&*;u+30Oul5SZaTG2Sg=^hg-?^zC2FE zv{#PQdTr}*>|H_3yVRjuhnFZ6DNx^Y+}2bx6N8+mq(%oBRb=%QtTft>Nqx&6f+|f6 z^>A|GoIc1;ag7{2buC&9ajT0@ntqVDKOo#Fx~Z;Pk*eG<p1?6Q6Q{cU6c|zFTH&(g z;TTbWu8Ze{e!hVhRK7*bhBp!E7*UZ$LI?oIyv&yVcIO1_YVUyE*`I#?j4Z}DvquNF z69xqoYRe)eL?Cyc;E1nVrv1XBZ5S_F5>!rVgv_L^3u+OA+=0UI!Joci*Vi>W){WE0 z^yc-bGk?hw^t4Yt{za_q_yRruVi9Fq;sxdt4pENaABRmy5HJ^_$>BwrhX}a^26@zX zAP|3|@U6YZXF~(*PxjJX?q3zYu*a@3)_{WpPI7`BGS(yNdP0HTo=r2Mq=j4}MPV<P zMFc{78UPYPds-^tP==N)!{^Q_&7e(x{RG5wJOKduZ5>l7*;hK;W_qz!Jh4s!iBfC* z<&&-((-g+hwQi_$&!+a5&;kDKP_M&Tkas}tV*pf9Ivw?JQE!M{3~_fdI^4=adubj& zr3rs6bwix+HLq3PM@+5cQ;f=1Y&W#CDCiwk*)=uZ{V5k#Ejzl5eWxvk2v%+D3pl<A zKY$wH?FTDhbVYqrb`nrjP|V_#q@}N=L$~h8C^(;ry^#IlK$n#d3EP!18RTiB5)&v- z`YjgJvr%%fwI*(2e#E`fIw&v|R2O8ZVrTg2{1GgIn(sKnLA}j20E)2fTDqg?$xQ%v z{xO3x^mwS1q207ipEcpID#B@cQ{+KyqsjP1gbgLl>{{MhpVS109uJ>*B73R_&H<rd z>NRH3ve0Yp-T@9xJa^Lm+vw~1vPj1_R{R>|iMlkc#X){{g~EdhYvI6H$~Z}C&c2(Q z6}d1Z=U3D>r^TbT;3xO^GLHnl+oF4DC$9ryjSy<XgVe`>$8;{9ZQd6XydXaBb2JgJ zv5s0eRj@JIDJf@yC9;KE#xRzb*hVzc7IS_VU+2ud19uA>o3tcy<A7M(`3~2ZWIwws z;W0HeD2Of&@qxNs&VIo&V;)@5nYR(9$>XiI`Vib$j`)aJf#FCo`YM%ky|W&k+K%t5 zK238{z+-mY^W@O$;Xpj}ro)yF^sm%*f=;k|o9_vaMeJ343iA7$rCI5$vJ78CUHp+I zEpSHnhga~!Gg%{QaANIPcwB9>w%Da|4@yr5``F$XPw~HGe|9AGT9yw(a(qQL%@}fx z_yK3ZM}^23RZmWI7C&c{rte*`IIC}Q`(1^SWD{)#>LcJVK^SNDlYGTsxSR}dGIJU8 z^xAtgQxM0%?{QYG-m9R7=;L_F&l+V<X$;us!dJLC>}`5ucFEl<y&!*X<84mOE&W!( ztXC%s^2K=097VkR<)&W;p_QUaV+pBpEX*T<KUhY_i(sKWm*ObX@+K^iYsZm5Nsgde zv2T)~T5wXli{UnK^{88&Zrtz9QP@3{{zt4eL~z?`iQe@jRKKrtdj3Wcj}r(2C1#(Y z*5k9;we4PsZP?l2vDpt_Rt0tqvkbPiL2Qu?bCh?Wn$(fCJ5*k$hGxT0g^{#PCfk4_ z02Du?IBsVc7O92++JBirANn0w<NRFQv2UC8wjB^x1nXfEEa0p|wO37j&`0|yzQh@H zW*$3QZ_hO@+1A_e#_ZF0J*N)ldgt+bZAwxZKGgiTX-r9KH?>`T#5=H*$npJlEUEhm zOGcJ|2L*z!zN(X1*~@;E&hse8eyejhiq7@ffMRP2f?jaK`$Vu35@FgoonU^ZRm%2A zA%tB+&A-SAra3gR_1oc1eQE>8X+K|c7deBtg_)8pl0R-O9%~~T@*+8Wmi(WNg#LFR zRp;BUpd>iRU6o-%Oxk|`FH8hQR&^fstAyU}BR;JtCUDdQ``kTt{Cgdcy9W4ultg<X zRTxmmoZ|?m7VAIQP-Y2b7r%&L|Hv;Er>4BsPRTr&5Hr_Nie|&`XIHo-FtIEaC3C$R z5K-Y&Eo&MO(~YzTg>(*g)4=-lnrK6ix39p7Dp5rEfV{IV^40m=@tP9G;Rfor$8Lfd zf&kpoL`XAX2^oRB-I0LKt=M=vlt-54%ds#FqJMELI-%D+eI-NAWj1&YrBi&MF@oUu z8ZNGFCi4Z<2pbHatW}J3)izRM0*t2b$cS%JE4pIVx9Ym>ZV-)zWy>Vn3Rm;f!4b!; zheSpfH`kcyuf|zot2?xau-y?Z--_M=L*MC_mI4BOg#p5DnIMxd&lpt^i=fnT(?Nle zx$l7BMC7#YZQyjAh6Mc_O3-USiv<1D5cbr-l9l4wFrA3fzWfDIV36v8zCxbl&?9$a zxxrLx{kn;2Qo<_*PAhy}BUv32@`u~)7a|e!XcagGQIM6gaUz)DTtM5^BqaDLkeimN zTPiI=KA6`kl9bt(FwLH})#jGEr`BxCC#>e$w$*;q&l<X<Ql?90&!nwyNEygXDax$F zr3v~CrFgLA^BiL$8f9D}0x6FUMru~@3dY)7V8E^T4vl}<-Xo{mL#%8ESYx+WS(|%e zTf1NXt1Xl8hD7DIBY_P1$+uH1x5X6hHKts?r4cK>iqaWm&YfwdsV+RGKN|6fLH852 z!?dP(hJMRkl7Tc%P~W-D9g9v|wK(R=wj#pQQ>#asGXlLDyx*W+gb(s~2kboyJjGI^ zP3>-=SI3kRh19<65`qOIA%|*wL3Gs!V}1}n9u&K}S|!Zu*{#i;WfC;3omwMXmtWb( zNKSk;Yh3$=qBx#v7sS$G)P|FT{SKHWG`bslw`Iq9--T&gnXLa*{VZ+z&C2c~t+g<Q z#wSB{hySVjBY%)a1VBMRK_MZ*KEXi1{3m}v0*D|Gkr@RQQBa8uYwH;t6Ceqh1ca1~ zoN@~q&`3y)zvk8boMBcr3C>3+Q!#J|3`)HIcTNG%|B+K<XW7D@4300ris|Yx!H}FV ze6sD|B}HJFfiElb{#2Vt26{GRX>WL4dGm{;LM3N_2i8xeE@$+2a}Kb}fLl@i8ywmG zbzO$r#OJ7+q5Nw+m`XsiW|888d6Ms8u&MJvlF1FXhLb2=eB`Z?3dq~~53Pr1@^Oy1 zn8}}n#Ua>|q|WiGs^E#9a5y|#pirQBf*mfmv=1Q9<*Qf9;q)_e0_`-;o#|I*JQ|7e zouxR;O7ny+c49-0xX|McJ?+w$Zbj0K-F*{8Q>U%(Kzk@vdZ8$-5R21bUl0u-K1lyO z!K@NfxBDE9LPB%}PS4gyTgEb>ph>+?su8dVg8DiSICs676G8&#$HSPq2%mFg`i+kd zH~ReWdlbeWW7YWArEK7zXf#`G4NL^l2_Ivu+F4eWK8b5W(PjYb1fo@)%i7HcTo#k- zpL`dPKBY8IV(8tp?^XyT<ZFvU^HXcWCt!4j_iD~2nf>O=en}ZPFq{Y=5RXL#${+&3 z0+w>d6SLN1c3CjYc}E#(XJmw}B~(hs_LF9)_KO#n-QX;UyCxz!kJ1Re2e0%%%uty? zrzIfWLj`@4PRH7j++<nhjk+?#ALRxo^ApHC(RzSkjQTI?J73Hj!j{sAU9<NP+?=uf zLX_Tt$_R18xG8x}HI7vp2)Pj}%bsUNDef<{!|RS<yUYx+m_|yMmxDi5)%FuT;6SU| z@R=%gxvuIHT!Zo1+pp`c`Z+5QX{**Xmp3xv(2XP)LCi=4(8O*fhe7(Y2)SM5B=Q2H z>p8LHOU@s&mrIEFspK+Jwv(@y0VueD{m-QJqqt$^S$9tM^?U1#m;kBK(v@*X&RxV; z^+-{0ze(}#Po5-!FRYC09P3&S55I3D2Gk{b3ayB7)hv20z=pm&RY9LyVhWxn;cYZ3 z$jH8Qt0Mz9!v#!S+2a+>;P}S60uA$x(0B;M^ewUAW*i2IS3jt6X;p-Mb5n*jQ61of zeT^f{80VEHV{mKRuGGYUKu!V~iM2=3f`#28$D#5Ph(osLeDkk{F)^9nT~y1kb0g3V zVGnC^_gmL?n7v0Jc4nqEPj&FdRpOESlC;~Ny1eL8;>0*vRTeMs&%Dx*YKRKplH;=< zMA_;3a^3+oRLyc-vFS^}R`Infi3Zo1k`pLrpPhAw{j`!0X+e4-7Ed(Zj~E@VU5l$A zwJ@b6ooqP&ccmXuP2N@BDQu;f(0*ZE{AcU$toS#Gg&1wH0wfi|Yjs$amWVz>EeFOq z#H8O{usJnT!8?-|guER#_xNV@Y1@CR6^2jcQ`6Ia$#4|%rZ}O3Le^A+Er!fA<01J@ z2*aIn?(0<=a#Wt(M4~73GQxwZlLPj71KToU_7}3dvY>IDJm`|$!GN~y*f&W{-bph_ zIyb?+?3e2w47uTcHtc6O-UNT9kOFE4Bhgeo0|<WG<@tah)K@5ENP2PNO$>^|?@2q6 z&a<LmiH%4$S(#Y&t6-u2%}5NfvcysKx==1RtZmZHEp`w6TPyRub|AYI@EID8YCyEZ z7>p|mi(MzK42LY6Rkw;QyF*W5@<5&?Js#8TDn@(4M8sut;c0@0m55(tkc90p@rqDi zeu4&9bQk=^Is>MQ0>tsLMasYhDz!5k_hZf%hi&|Ng&*TRNA<`^Ci*KE0|ALfAgMwe zAK}XmMxtjrb?o_LQV-nCWi-=qC0oT;xYB0FFSK=|!Q?V9JoDBw$tS9X4J{V^juFPO zB1j^1v!;}*6lUx65qxm9ILo(&of7+^zv6{z(OoDaYsPWKK7E-68X03{erPT|_>D8$ zxRkW3G+MQus|YMXx!;qZ2qTpSiCqlNVUegA%+@o9kiH@c9Qo=h=_5!l5MM9UQlu`Q zshkRbI+!l)0RRqb3>?_4^zc8mgLn7hE5UyT0XCv=g7hGwvrW&PRkgdp+B{2~6-f~W zrv%YVPK>17^O|4V%P{o6S%!{lpGa@*?dtnLTg6SZeMGW1o{p(r3l|p1uJ1dQ=;5`I zJ#D|L%kR^bQJR*f6Smt3g^I0U=`5Q@goGD+OS=((`i@slNDwsRSIQ(2Y?fdT1{K-2 zEBNc`RVYVsl*b1&t=HnNSzIf?KO0NW32RHvR*l`OLX*lCP7V$+mMl>uJ5DP<OP7~P z@0@sL>0vjK-L8R_WxWGGT55*&Pv3z)K2%Dl&=cad?cvbVvvn~hg~!G(N14n%pnlc_ zia$3_e+D|g{Q8&~SDp|y?)@ryLU8Uq<T-WSzvs2yHDOo?WBd$7<0q#aG8JF-*tU?x zdQzFUP8+pxLH(n2dg}D#r7VsSnv2_|ZJ;8!Pxpm~@tLdpde&%cC%XD;V*gPyCO>1V zJ8q{2%iX?cIJYYnQl;-?U(T((`ph%P6y)g(<m@mfGkni2_F%}&_fT1}MxUl3^`KE* zT{(RODT}=8d6Kj-8`yB3kip^WpEC<vp-DJ2J6}tU(+S?nShFEyx>l{=aP_gaEo^a~ zR0d+<jh<~|tD#ccLQuQ#lV?PCN<`^Xmr?rm>Da_WKgU%Pxck_|!RvL1%~m%Tb4`X9 z$lH=3{n5JXJ+{T>J5R6uS)or`W4V1hm@CX4R>;EeWQ90!(8Oio^UwQpEB@797-^pC zMaj6bgi`s^rF#xaV+NbUcZJb7wkb#3U{3HAOd<DM-)ttd4=b$aYf(=o0g+=J4}(S7 zYgmusP8D_i_dlGjjjYh!&s_c|SCEl2dzU9s#+AjX%PiCWz&s`S^?kAU&z%xw6;IjN z)Q`r7L+y~U<#DI_==j(Jnz#Fl26<Dj1^Cw%Z6MvRTt@3Ri%VlR*c$u+R@hw54_?ao z2K;8Zl1lzFHZC>KpP~@pT&1eXY7NuQmlosF$h!jLuH&tnAY<}wQPj2m_Lt}np0L=| z&WVc-jHGxG43m;f3Db!Ljv~8n<Q)kbbe;0N&>5){$)`{?B|&gI641zs7oz_Q{rv-a zC)TTN=5~K+TJaA$f(MH$;zy+G>RNca1{sU;(h{O2X_^q2M;w)E#s+|NWLa9^Cl*Wg ztg)z!zKjkFo@k#w_@yDpOf)M0u>K?|@yMGO_!tv6Da3u%4kfOJG5uRKm1&&NJKT&- zl2?<pOAcM5sf`Nl;yFbR%5|XDFi2K`x0dY-Yoj24D|xM~gx?udhZd~hd58lfcBzf2 zt6(An_vDTbTU^M_^eStrXUtOlYzsG1(0$BBIuwq2l)w!07jUqIj5<s0GdKHI@qUXn zyPPt}6;qz@aYT<mXdVY!?3cV^s4~uBFc!}SksCwT-}z`Pvpmebe}`qzMm0HJu<Itu zpVpQJ={(tZ-tm+JRi*O0sCa1z_1+-^Z<)HRKxjWH9&}m2n9EN1&sD@DUO)j$zdK^L z#BOO*L5^}fE*h$)syKuK)PPH&3QMCGdgYihh$8Xe6yEUhTBCQ6@$J{26Pht2xIF|@ zxOIvT<_~)r7<_X+-kI%+(+fWs<TH#u>1f3Fo2Pm7i|2n6R!yv(-1~zq-gxyB!8&#E zRo<lFkzd3ugVa7~5zoGcFV@ZzB!{J62y%wS^0|Z<1Pv`cDYL@BMBcNDSB-DR24Bta z;pky6Tqkog|F!l9O<992T~wY^tz2>vi!|wZ!F}-g=1?8Sr=y$j$dc(cF>$83ekK}8 z#Z`+TGNPo!n5Ph7^<&tH^zxJ2@$^gKB7gW#^uD+Aj-Oo`uXAFHK$&rn;V%3j_fXas zPNa%XtcqyA=?)L0^k{eeK2Wk?5#>N$QcdWktYYeVG%Dd}B5PxXhpSw&%Hz~3DzIq` z-wa3dU|W0#Vw5-%Q=3x7LM^iFOk4<qP6v8LFY{7a&lJJhD}(}IfaekO<`L(}#?|7= zB8TJkpSzfL)HZ^ftTV-u>bk1L1E^M<^%`bQU=ahO(w9wDL=ECkTAm=yOY!>0@K5Eo zZJDdBT9!JG^@%nd@O^vj0#h=T&9VwmBbn%J9L<c5JwahB(ULT4VyeSVuj^B92^9Mh z-a)m!pn;jG0v48fE7%c>+wtxN?snisuaK)nGqp)*XbjuLU*(2t#w<4Cko}K`KGF?A zq>udxP;giXSSSGWKid@mI0O>1BP1db@khqtRQr>O#qjGj3JEC#vw%`=-3%cat58DT zj!~k4L+`(KZ9zc(CK(f@Iz&cF4HTT-kY7CyiYPlsjm#Ox2VP@_uCDIt&!zI_%F63C zTSO^>=TOjcEBgJ7Jr_1!_wpGMjkF4?d2v1`IKI5*2_>d|lPyZoo2k&_LGad^jhBiM z5Tyx`6tzel^&`n|vF;YVVPx1lqj40K;+onqJ;K(|f&sTi65Y{tCAR*vmww0}Zrk4y zUl1?2$h(_dY`Q<7OZG~VGs&AEp>)R_c^AgMgU!GB4)o0Da;nGs?5s%D<v+b5qZagM zmfLEw798_#!YO(P(z7ec89m^+<7@dY+PP+!XuAp^WVBdEs>5vhH87{gbho^<cc*i# z8cNUP+ei*m>4NAj;DM5v%!a5eH+8m!t=^$HMQE1J82Y2<KklI<dnL_bP+HvhaP=k> zi*5ZBH6*!RAR52-H+0<hQ>5VQpQUB_;msN~)KV${J1K~oikw@cH0abtD}2@al;OLy zsJ0uyTO&qVP%=*x!8P1K!wt?XPR5GSp2+zQ#1G)_1!#{8yyeS34`N%w7mbPv;o8#3 zroi^|idVM1<~YeEf5$TX%}Z0!7=I>spbv(EtcncORfeL5kj{&01cc%_7KZ$Y7pq0J zLm?9&H&F2-%I~ncd=^4a0hFwvmSmOS8H=fwiTxa(layj4pTy!G(8p;@l&FKHQcO9) zC1*yJtg%X;V%wYgvvQ=;F)E&(p;HrVv)WV}ZdabnOxC=qhTUTL&*dcJGjq_2l|mEt znn`76%A~AMC@f~_S9&q1#Z(RulZCK@nUPWCrE&X`%u13AwEf9LY<N0tNf?@nb?f{c zL(Q?BiR<OZh9Y-tdY3~BN0&@NXCgeob2Xip>J?tDLoIgmE%)?a#o``=0ieW>i6*S% zG^R*4sb2jVg$YLKw0xxc_jkPb`&wHy9W=alzw1!eUaYX9RcpdgKmBpVI~q6FwtFos zUR(Ml-A#_dVqBYr0vm?$vhVUK4)d{-+Qe$2QnCUX;tzb>h7oQnXr#WA?0UkI)S3W% zEv;O`>rkSUtXAyi6^5vzj@(?#82|pL<f@xVwOEUhVRM3Q9rWNGd|PHJO+*F+5v!{0 z2j%zb@RO9O=o+jgW<Mu9Mr16Z+Z^Mim&CR$UjJ(7)^+#T6_*f2b;iakj(A0BsKc5N zbLtX$2O4DB?nf&*thc%IV(t2Oxm))NXRLgMt_nojHqvy9Cd6ZQNiBo9GY_r0#4nzx zgcEj_IfXk(BE!!r?Gd!4e=WJklDY&d>OxlY$dOeLk%<eS^(7}{$l7KmtrU0hL=%PE zwli0HEILjQFOb%*yaS_#HsT4Pr4Z8Ojai$U3VpeYv%4;opxie~nfNVEVP4{BNK0Cq z_)Y12nU^<_JM~$_QOB&FN0HXc)h^eeIoftwf+hXYzwyxMiMzybtO#T?4m+$l&Q}X{ zHulpUOR{r>i3<+YN6E=?U0o}dP|cOC9eEi4g&pe|xf{lp`FB7@?vLD~)#0;td=z~@ zRF_dl;T!NS4*r}FVG*CMBq~lB5dtC4BsOV|jS4}%!;e5J@;aJKjRh{#dq05b3t!oP z9l4u4{i`pT&v{%c67#pScHiQh)cvD-v{q<J4n30SJ$A<pqejGcqt3aWq00zYk@|Qv zE7xl5N%j;~(u4IR6grXKTum9pY}vn9{YqXRosS4Qg42pb)64m*;chpByT?F@sh%Ty zETfpq2OLKcZ{(vq*rz$TR^+Sx{M4egAqlz(!ku$m6cmKM$zEAA%FUw1T@RyzdtQeF z6sfzin=)Ea$ZC0O53f~E{q)WbqjiRAj&DPl`CRaNo&%0dKBNEOp{+2?{St*cXf4o3 z+bza0b2Pgu5UyT=P>Uv~>^ru#qX>xHP5RqeIhrY&!IEZF*ltF153hAX^+S1k%x2`# z%K1%>0gP#@yGfdeq(k*K4@|`|NTwL1ph$94_5nv7L#FTy)x8s><dd(!0LJ|4x;zNl z0L6^g&L~C7(ZZgJb~J(dq0AM6b})Y_Z-=9lw~HQe^gmWN|7&#yIgM)c&%c7V&;}fX zI}O$23XG~iBI!#TTXMkA$o|4m;n!4X@cvHX?pxeJIF+}@Rm$1G2>;tnk1S=qpWbEL z23fUwUvST0y77^Jh3N4mM7C5>RuxR{dcdiqiJpZ~7I^j68a$r<ql5+(Q*#=_pl`mW z{3V9%-pVJT_`DR@@OdNUwY(ctbHVZ{lt}w!&bBH@TGJ>6G)y+@blIot^ygn6xDbj~ zw4zA!njc+^Og!#%I6#flP$47D3{^6QV$iar&oeylNz#x!!N1Ub_U3h^6_AoIo`g*+ zHI{$bLeXI6CjG4<+MtCsa>x>kS4%h+ll$X`m~wG9>Q;@js}7u}GRQH4*eB|_J#T!p zvawO#{FA_Yj2~@AVW)7H6epO)8QNdB<0VU+f(`c1&qpJbO<50=>ONnXw^2spCdZ={ zXz34D@~&|_yhH#eA?$VJVHtWS+Xk$OJkg$%KUG$I?2UW-7jLTxWIQ=;8Ta!d(IlDp zw{t*|H<tl;{4<6q*%Di*V0Q5qF`amm_-yhPWeF(42&(!o+Y0m65wN<Y0U*QG3GEky z6s2N?U$Ir|P5;nDJkPQ0C@#U+k{%$Eep8E%Kftd|;WUb|8!s0)smTG`q_8M5W4c#u za9%@=4!SMoG2w5q2m%+Drs5<zLak2)zM}dh%~6-u##?d)OU%GCDsqmly0{~FX(2Zx zm#D!hX)vXjOYif;%&faQv=Nxvl;j$Oz$>IHjKQ-tqap`LasiR-kXEJ61ix^n)}BLG zg)LzO0C?dov;B+=*)(JXB*#c$s2qb7_MGqM+>MX^NJIksOhRetu^Z$&<FC7NaYm}F zY?<w@AImgvj31$n_}@?m3=A3^=F`Us&VR400^mpxkVOA1tvWi@e#AL-#3W=4OiDjz zu2E14Sp|fQoD*_)Nb`D?4U8QE6O(TK33xuv75xo(yfxVQ?EGTC_<Yl=o%IlJ*g?M- z%}j(C^f5M;<t6H{Im7F=um2KJ`pAqas5bW~c2}M=TuaAOP>Ab4$hmY3HqUyce$2Gy zPABJ8#lE}FHIyg6E8n1SQK8jDx{-Bgeez>IH&Slzd~V^PX-#P!IsRiz`qOLdVAu2O zyFNB@WGs2a)pp`EA|%<!wBYOFlVgPU<n?DVQmqW@)=FU|XW_J$Km*!I-hqmo(>OA} zw%kJ<OViJ!g~AdXNDoo4r*c{%lyHM@uGTvQ-f21-k6uy}@{zKtmF8#!4yOcy&(}QY zH{7oN3<J43xP8^?H^35YrJ>|1W&X&JQYb85SD)en`mgD4^&?t7#(FiFu|(y5adj&X zCgW${$$Mer&e^lUn!ez~(Nl?=S?qn|+x6yqxce=DI{G?_jd`+CMaym2l%$>_SV|&! zzBEe9?RxYR?V0Vt_rU%Ux}LVV@C__ytIQ>4jZ-wumhHNnx@sPezqr8=PPXY146OcG zdx=`jt%_M}{m+6uc602s(QKVr0q(djxDCI{B^SbgUvF{>B2da`w4SC%<ZRB!S8^Zw z4yflGC#D*y)~r#%NX+vw@|uA}_AVxWq!J<vg(6vVnF)pBd(aK^gab^=+16a#G{v5M zJ~C4arm9n#v-vl#LhDKsDprP{lw<{SLn#IwNDoUa6r%WUkwnHHICy_J6BI~k85AHp zC%RA?zXQv{5gKK(B5E)ZMVh|<*ks;ltrb>~PBLrf6pzGlX)R7O3`Yr7>4zY;;M`Hc zm$1Va@xbijmF6@ZO>~=z_%aCfGd$jJ+}zgi<9+svi2K|;$=g}-7wDB8bFg`nUcX4K zw(7IAQrqLos&QF~xVU`}2{N_QEpYgvm}%_ANK7<S>!YhGux$mW{n(<7#ya46%D~c( z%3Sc)Jo}VS)L?u5Og9q^oqU!VC?}!e;xV{xq_o6;8>Ib&b)w@-dVxPvo~EB&Td3_d zZt}(VXKYJ)eU3Hilknpk?^XdTGra7ctRo{wYUY%uX6aL3bsa6vU^BAPRFrsyFG;&O zhnu^yf9V`}Qp6p++>SWo*%G?#u4BQ>6z1ku2k%?Uul|Z#eS6*%a|*NCg@w-(#=>92 z%#8E|k80Heb2@p0w$S_YWmc-qqMbi;y<TknZp=YSw41WrR?XEGZ?@4wuUCTF*OYV4 z_JX{tc8EBWm80hzFt2_QA5!7EU1wDsqo+H&gj|zUip@nylYF-1PtzK7xKFAvtkxWI z*VAS@?c}D^m+GOIkz2(!a&@(gC>1ZvqHRk|f$zqAFtuG^vr3XkY)B3{BGQa{`=HJt zDQ)VJ3Z^fM=CoK)y9R%Q<jQ&Tj+fM`tKGuIhzDP#ckuIC_b+Q665>vjHhOkxmp`bt zztMF!-OQpK4oO~OcP{ulBpGQ*qX(5`i#0d>z_qS?3e#_v5uOg(s}413v)}GeY{w?q z({F{sF4^%Etu}_pPMhSs_*=Xm<K~WaH<N#!@kFy8$HjQkLoHN0eCKxmQ)wP-qMqk2 zdUzstT^t|TCqegx(2^8k5wjMFw2dpX+H&R{u<uUx+3!no*lT@I85$~26<i;K&tLRD z4L#gDVq`WUc6G63(Ol#oQ@4?2D8kp4nhjKP50a?(c;DdNaGzfI(^BF!0(obz&&7s( z66Q8i#wYnecg&7X+D+73B#8uq`R4u{BXZMK&x$1o5iK{ZWir_($+w{{e7^+pjx%Cw z|5{GxI~z#(y?21Dj~OkBZQRLV!4KBoxqS;cv65la+9>PAXboS{{|;@FQKG$*D&{C5 zRCyaA#xVmUf=|Xj;CZd-W3i`)Fb`KepmLC&UfMl`r%uu(^eJf*v0IFScp+J8M4_iY z5cM0r1zuPGfg^;vZ#=L6t&XOXr64RvdSd7sX49XVzxbSVS5n5F`$oE~Ha^w?;T_?( z+OJTP8_Cbq`T>F>JaOoiL&<u2saLR`aEZP|U8Ik}NGkoSG$@q<Vzw#FH}zlIm@C-C zcX<!`qZBE_1@eqm|D`u4-Qz#FifE?%*n3R$Og_o)0LWdljwiW(fS8R8#Q=TqZPeK8 z#*REuqI5y?V9s?gY@|pZYecJ&kz>UelYx)bm?`^r;|8uNPe&>mi8QnL4hZ74-WmTp zj1R5<K`%*=50@XT$Ex*$N;Km@$ZqQ!L4F0LLILZq{s15O@8AKMn6x3#eoD0-*w5UY z7n{cgN_2htRAwyfk123cO32F+A+Q5&JcW$UQCGNW-i3@Ew~WO6#d24ck)kK~66xD~ zZl`aG^gT?n2BS~%lpWcBjg3X>rL1Fl1wB59Ooa+1qfZJx%qg#O7b!}-Pe?6VJ74u2 zJ<TE%Vvt9BbHEuluJ5tmzCrt8%qaf+V)usqPJI;_2Lf)_CFzeb5pW_;Q&Vpl*gvtm zE>4vMdHV;Ma*){oy2}@062V4^3IivXy;b)PT+9YsbQ@nK<8|RNR=Rt3dD9cKeo4~h zrxU%<naTJEqFb6YZ7c}Lu&=A7cC?;c>JhA`N&Rna9g8byE`uGc8K0?pItOp3b-&=v z1NU)rfe{bChX0P1tc!)H6E)hlVt@SscI8rUL#+7@fUzjhTxP#;y7t)@TEa?}aP2HS zLAiXue~`l@{zTTXxP!<lH+m03(R;!~XXoLd)hv<rQr)LVMGd)ZVEgy1`JMWQ<=z45 ze+cJdBa{uOWP)I%K1h}6I*mV>3WW&I;I+LloZT&|cC!=uXqClSa`DFw*I)5uol@xx z%+09R9P|9n+e)`wDOOnx41pxU(<F@{*WQ!&5QC(G#UXx-lxXd{7;_ph(O$254vwk) zh6&v)YfBaM4iLW#k%hn5%G4E>kb*jLN6YC+Qjx!snbRmK$6XUIbvQd8Msk+?f{fkh zXzS{QLBv2V%RlCi;OY0d>m}#&m36$Um*}`m`|>Ei^C)Um>#{Lkzs94xN5IYUl~>on zoUuY{Y%<&9LaTicc018;wD(R&a#@_iZC;|s7<9%_A);{(g@AhQf-22Avw{Y_aMMf0 zCy$5i&9J}`!J^NWc&vsX>v}3+tHSmAVk-}^s8B;1UY(U|YDkCLR;A}>gN_Wl6+9I5 zcV|A+UKTWbe~Z-bjvwbH6R0@W<|jB}wDx$8gDZL|bj?wZS738oZ(_j=W?&t9G?!4E zW$Pm+v$b~3rXANI!7d~fB}-TsscaI0TK>>Fj72!8lIPoSNkC$e7O3GV2$Zs5kIQLu zWxNAp8=tgNos+HP*EBL5lT@&U+4AtQ%04E@i-J&5l*l|I+oc#aZ&v@Axc8O+1L#ov A%K!iX literal 0 HcmV?d00001 From 8c8b9cf5220db0a8ffdc2ec16f1a092efcbe4fbd Mon Sep 17 00:00:00 2001 From: Bartosz Kubicki <bartosz.kubicki@lizardmedia.pl> Date: Mon, 12 Oct 2020 12:59:50 +0200 Subject: [PATCH 077/139] Possible fix for integration test --- .../Framework/MessageQueue/TopologyTest.php | 33 ++++++++++++------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/dev/tests/integration/testsuite/Magento/Framework/MessageQueue/TopologyTest.php b/dev/tests/integration/testsuite/Magento/Framework/MessageQueue/TopologyTest.php index 5c85d6ddb7c70..cbe48c99da020 100644 --- a/dev/tests/integration/testsuite/Magento/Framework/MessageQueue/TopologyTest.php +++ b/dev/tests/integration/testsuite/Magento/Framework/MessageQueue/TopologyTest.php @@ -3,16 +3,20 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + +declare(strict_types=1); + namespace Magento\Framework\MessageQueue; +use Magento\TestFramework\Helper\Amqp; use Magento\TestFramework\Helper\Bootstrap; -use Magento\TestFramework\MessageQueue\PreconditionFailedException; +use PHPUnit\Framework\TestCase; /** * @see dev/tests/integration/_files/Magento/TestModuleMessageQueueConfiguration * @see dev/tests/integration/_files/Magento/TestModuleMessageQueueConfigOverride */ -class TopologyTest extends \PHPUnit\Framework\TestCase +class TopologyTest extends TestCase { /** * List of declared exchanges. @@ -22,13 +26,16 @@ class TopologyTest extends \PHPUnit\Framework\TestCase private $declaredExchanges; /** - * @var \Magento\TestFramework\Helper\Amqp + * @var Amqp */ private $helper; + /** + * @return void + */ protected function setUp(): void { - $this->helper = Bootstrap::getObjectManager()->create(\Magento\TestFramework\Helper\Amqp::class); + $this->helper = Bootstrap::getObjectManager()->create(Amqp::class); if (!$this->helper->isAvailable()) { $this->fail('This test relies on RabbitMQ Management Plugin.'); @@ -42,24 +49,28 @@ protected function setUp(): void * @param array $expectedConfig * @param array $bindingConfig */ - public function testTopologyInstallation(array $expectedConfig, array $bindingConfig) + public function testTopologyInstallation(array $expectedConfig, array $bindingConfig): void { $name = $expectedConfig['name']; $this->assertArrayHasKey($name, $this->declaredExchanges); - unset($this->declaredExchanges[$name]['message_stats']); - unset($this->declaredExchanges[$name]['user_who_performed_action']); - $this->assertSame( + unset( + $this->declaredExchanges[$name]['message_stats'], + $this->declaredExchanges[$name]['user_who_performed_action'] + ); + + $this->assertEquals( $expectedConfig, $this->declaredExchanges[$name], 'Invalid exchange configuration: ' . $name ); $bindings = $this->helper->getExchangeBindings($name); - $bindings = array_map(function ($value) { + $bindings = array_map(static function ($value) { unset($value['properties_key']); return $value; }, $bindings); - $this->assertSame( + + $this->assertEquals( $bindingConfig, $bindings, 'Invalid exchange bindings configuration: ' . $name @@ -70,7 +81,7 @@ public function testTopologyInstallation(array $expectedConfig, array $bindingCo * @return array * @SuppressWarnings(PHPMD.ExcessiveMethodLength) */ - public function exchangeDataProvider() + public function exchangeDataProvider(): array { return [ 'magento-topic-based-exchange1' => [ From 2bee5bc62a08cba4a46100e5dcb2d1e3d6abbb67 Mon Sep 17 00:00:00 2001 From: Viktor Kopin <viktor.kopin@transoftgroup.com> Date: Tue, 13 Oct 2020 12:53:35 +0300 Subject: [PATCH 078/139] MC-38113: Same shipping address is repeating multiple times in storefront checkout when Reordered --- ...ckoutFillingShippingSectionActionGroup.xml | 2 +- ...ustomerHasNoOtherAddressesActionGroup.xml} | 4 +-- ...nStartReorderFromOrderPageActionGroup.xml} | 8 ++--- ...eorderAddressNotSavedInAddressBookTest.xml | 36 ++++++++++--------- 4 files changed, 25 insertions(+), 25 deletions(-) rename app/code/Magento/Customer/Test/Mftf/ActionGroup/{AssertStoreFrontCustomerHasNoOtherAddressesActionGroup.xml => AssertStorefrontCustomerHasNoOtherAddressesActionGroup.xml} (79%) rename app/code/Magento/Sales/Test/Mftf/ActionGroup/{AdminReorderActionGroup.xml => AdminStartReorderFromOrderPageActionGroup.xml} (66%) diff --git a/app/code/Magento/Checkout/Test/Mftf/ActionGroup/LoggedInUserCheckoutFillingShippingSectionActionGroup.xml b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/LoggedInUserCheckoutFillingShippingSectionActionGroup.xml index e1092a87e4a01..d3127362c637e 100644 --- a/app/code/Magento/Checkout/Test/Mftf/ActionGroup/LoggedInUserCheckoutFillingShippingSectionActionGroup.xml +++ b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/LoggedInUserCheckoutFillingShippingSectionActionGroup.xml @@ -29,7 +29,7 @@ <waitForElement selector="{{CheckoutShippingSection.next}}" time="30" stepKey="waitForNextButton"/> <waitForLoadingMaskToDisappear stepKey="waitForShippingLoadingMask"/> <click selector="{{CheckoutShippingSection.next}}" stepKey="clickNext"/> - <waitForElement selector="{{CheckoutPaymentSection.paymentSectionTitle}}" time="30" stepKey="waitForPaymentSectionLoaded"/> + <waitForElementVisible selector="{{CheckoutPaymentSection.paymentSectionTitle}}" time="30" stepKey="waitForPaymentSectionLoaded"/> <seeInCurrentUrl url="{{CheckoutPage.url}}/#payment" stepKey="assertCheckoutPaymentUrl"/> </actionGroup> </actionGroups> diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AssertStoreFrontCustomerHasNoOtherAddressesActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AssertStorefrontCustomerHasNoOtherAddressesActionGroup.xml similarity index 79% rename from app/code/Magento/Customer/Test/Mftf/ActionGroup/AssertStoreFrontCustomerHasNoOtherAddressesActionGroup.xml rename to app/code/Magento/Customer/Test/Mftf/ActionGroup/AssertStorefrontCustomerHasNoOtherAddressesActionGroup.xml index 2fde4d915c99f..8634ebb626e6d 100644 --- a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AssertStoreFrontCustomerHasNoOtherAddressesActionGroup.xml +++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AssertStorefrontCustomerHasNoOtherAddressesActionGroup.xml @@ -7,9 +7,9 @@ --> <actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> - <actionGroup name="AssertStoreFrontCustomerHasNoOtherAddressesActionGroup"> + <actionGroup name="AssertStorefrontCustomerHasNoOtherAddressesActionGroup"> <annotations> - <description>Verifies customer no additional address in address book</description> + <description>Verifies customer has no additional address in address book</description> </annotations> <amOnPage url="customer/address/" stepKey="goToAddressPage"/> <waitForText userInput="You have no other address entries in your address book." selector=".block-addresses-list" stepKey="assertOtherAddresses"/> diff --git a/app/code/Magento/Sales/Test/Mftf/ActionGroup/AdminReorderActionGroup.xml b/app/code/Magento/Sales/Test/Mftf/ActionGroup/AdminStartReorderFromOrderPageActionGroup.xml similarity index 66% rename from app/code/Magento/Sales/Test/Mftf/ActionGroup/AdminReorderActionGroup.xml rename to app/code/Magento/Sales/Test/Mftf/ActionGroup/AdminStartReorderFromOrderPageActionGroup.xml index f4f076f25af8b..28a179faff9ac 100644 --- a/app/code/Magento/Sales/Test/Mftf/ActionGroup/AdminReorderActionGroup.xml +++ b/app/code/Magento/Sales/Test/Mftf/ActionGroup/AdminStartReorderFromOrderPageActionGroup.xml @@ -7,16 +7,14 @@ --> <actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> - <actionGroup name="AdminReorderActionGroup"> + <actionGroup name="AdminStartReorderFromOrderPageActionGroup"> <annotations> <description>Reorder existing order. Requires admin order page to be opened.</description> </annotations> <click selector="{{AdminOrderDetailsMainActionsSection.reorder}}" stepKey="clickReorder"/> <waitForPageLoad stepKey="waitPageLoad"/> - - <click selector="{{AdminOrderFormActionSection.SubmitOrder}}" stepKey="clickSubmit"/> - <waitForPageLoad stepKey="waitOrderCreated"/> - <waitForText selector="{{AdminMessagesSection.success}}" userInput="You created the order." stepKey="seeOrderCreatedMessage"/> + <waitForElementVisible selector="{{AdminHeaderSection.pageTitle}}" stepKey="waitForPageTitle"/> + <see selector="{{AdminHeaderSection.pageTitle}}" userInput="Create New Order" stepKey="seeCreateNewOrderPageTitle"/> </actionGroup> </actionGroups> diff --git a/app/code/Magento/Sales/Test/Mftf/Test/AdminReorderAddressNotSavedInAddressBookTest.xml b/app/code/Magento/Sales/Test/Mftf/Test/AdminReorderAddressNotSavedInAddressBookTest.xml index 2b4bb43ec36cd..1c3ab70857151 100644 --- a/app/code/Magento/Sales/Test/Mftf/Test/AdminReorderAddressNotSavedInAddressBookTest.xml +++ b/app/code/Magento/Sales/Test/Mftf/Test/AdminReorderAddressNotSavedInAddressBookTest.xml @@ -9,36 +9,37 @@ xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="AdminReorderAddressNotSavedInAddressBookTest"> <annotations> - <title value="Same shipping address is repeating multiple times in storefront checkout when Reordered"/> - <stories value="MC-38113: Same shipping address is repeating multiple times in storefront checkout when Reordered"/> - <description value="Same shipping address is repeating multiple times in storefront checkout when Reordered"/> <features value="Sales"/> - <testCaseId value="MC-38113"/> + <stories value="Reorder"/> + <title value="Same shipping address is not repeating multiple times in storefront checkout when Reordered"/> + <description value="Same shipping address is not repeating multiple times in storefront checkout when Reordered"/> + <testCaseId value="MC-38412"/> + <useCaseId value="MC-38113"/> <severity value="MAJOR"/> - <group value="Sales"/> + <group value="sales"/> </annotations> <before> - <createData entity="ApiCategory" stepKey="Category"/> - <createData entity="ApiSimpleProduct" stepKey="Product"> - <requiredEntity createDataKey="Category"/> + <createData entity="ApiCategory" stepKey="category"/> + <createData entity="ApiSimpleProduct" stepKey="product"> + <requiredEntity createDataKey="category"/> </createData> - <createData entity="Simple_Customer_Without_Address" stepKey="Customer"/> + <createData entity="Simple_Customer_Without_Address" stepKey="customer"/> <actionGroup ref="AdminLoginActionGroup" stepKey="loginAsAdmin"/> <actionGroup ref="LoginToStorefrontActionGroup" stepKey="loginToStorefrontAccount"> - <argument name="Customer" value="$Customer$"/> + <argument name="Customer" value="$customer$"/> </actionGroup> </before> <after> + <deleteData createDataKey="product" stepKey="deleteProduct"/> + <deleteData createDataKey="category" stepKey="deleteCategory"/> <actionGroup ref="StorefrontCustomerLogoutActionGroup" stepKey="customerLogout"/> + <deleteData createDataKey="customer" stepKey="deleteCustomer"/> <actionGroup ref="AdminLogoutActionGroup" stepKey="adminLogout"/> - <deleteData createDataKey="Product" stepKey="deleteProduct"/> - <deleteData createDataKey="Category" stepKey="deleteCategory"/> - <deleteData createDataKey="Customer" stepKey="deleteCustomer"/> </after> <!-- Create order for registered customer --> <actionGroup ref="AddSimpleProductToCartActionGroup" stepKey="addSimpleProductToOrder"> - <argument name="product" value="$Product$"/> + <argument name="product" value="$product$"/> </actionGroup> <actionGroup ref="GoToCheckoutFromMinicartActionGroup" stepKey="openCheckoutPage"/> <actionGroup ref="LoggedInUserCheckoutFillingShippingSectionActionGroup" stepKey="fillAddressForm"/> @@ -47,11 +48,12 @@ <!-- Reorder created order --> <actionGroup ref="OpenOrderByIdActionGroup" stepKey="openOrderById"> - <argument name="orderId" value="$grabOrderNumber"/> + <argument name="orderId" value="{$grabOrderNumber}"/> </actionGroup> - <actionGroup ref="AdminReorderActionGroup" stepKey="reorder"/> + <actionGroup ref="AdminStartReorderFromOrderPageActionGroup" stepKey="startReorder"/> + <actionGroup ref="AdminSubmitOrderActionGroup" stepKey="submitOrder"/> <!-- Assert no additional addresses saved --> - <actionGroup ref="AssertStoreFrontCustomerHasNoOtherAddressesActionGroup" stepKey="assertAddresses"/> + <actionGroup ref="AssertStorefrontCustomerHasNoOtherAddressesActionGroup" stepKey="assertAddresses"/> </test> </tests> From 56d8eae58d9083552c4d48de39aab94a435b3580 Mon Sep 17 00:00:00 2001 From: IvanPletnyov <ivan.pletnyov@transoftgroup.com> Date: Tue, 13 Oct 2020 13:08:21 +0300 Subject: [PATCH 079/139] MC-38243: Create automated test for "Product Categories Indexer in Update on Schedule mode" --- ...inProductCategoryIndexerInUpdateOnScheduleModeTest.xml | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminProductCategoryIndexerInUpdateOnScheduleModeTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminProductCategoryIndexerInUpdateOnScheduleModeTest.xml index eebd3472cbd95..3008e89fd9dd1 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminProductCategoryIndexerInUpdateOnScheduleModeTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminProductCategoryIndexerInUpdateOnScheduleModeTest.xml @@ -10,17 +10,15 @@ xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="AdminProductCategoryIndexerInUpdateOnScheduleModeTest"> <annotations> + <features value="Catalog"/> <stories value="Product Categories Indexer"/> <title value="Product Categories Indexer in Update on Schedule mode"/> <description value="The test verifies that in Update on Schedule mode if displaying of category products on Storefront changes due to product properties change, the changes are NOT applied immediately, but applied only after cron runs (twice)."/> - <severity value="BLOCKER"/> - <testCaseId value="MC-11146"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-26119"/> <group value="catalog"/> <group value="indexer"/> - <skip> - <issueId value="MC-20392"/> - </skip> </annotations> <before> <actionGroup ref="AdminLoginActionGroup" stepKey="LoginAsAdmin"/> From c72589d3132aa145ae98fbac2095d0b9b3db2965 Mon Sep 17 00:00:00 2001 From: Yurii Sapiha <yurasapiga93@gmail.com> Date: Tue, 13 Oct 2020 13:12:38 +0300 Subject: [PATCH 080/139] MC-37070: Create automated test for "Import products with shared images" --- .../Product/Gallery/UpdateHandlerTest.php | 33 +++++++++++-------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/dev/tests/integration/testsuite/Magento/Catalog/Model/Product/Gallery/UpdateHandlerTest.php b/dev/tests/integration/testsuite/Magento/Catalog/Model/Product/Gallery/UpdateHandlerTest.php index ce36b27d51e7d..1101b7291528b 100644 --- a/dev/tests/integration/testsuite/Magento/Catalog/Model/Product/Gallery/UpdateHandlerTest.php +++ b/dev/tests/integration/testsuite/Magento/Catalog/Model/Product/Gallery/UpdateHandlerTest.php @@ -16,6 +16,7 @@ use Magento\Catalog\Model\ResourceModel\Product as ProductResource; use Magento\Catalog\Model\ResourceModel\Product\Gallery; use Magento\Framework\App\Filesystem\DirectoryList; +use Magento\Framework\EntityManager\MetadataPool; use Magento\Framework\Filesystem; use Magento\Framework\Filesystem\Directory\WriteInterface; use Magento\Framework\ObjectManagerInterface; @@ -90,6 +91,9 @@ class UpdateHandlerTest extends \PHPUnit\Framework\TestCase */ private $currentStoreId; + /** @var MetadataPool */ + private $metadataPool; + /** * @inheritdoc */ @@ -109,6 +113,7 @@ protected function setUp(): void $this->mediaDirectory = $this->objectManager->get(Filesystem::class) ->getDirectoryWrite(DirectoryList::MEDIA); $this->mediaDirectory->writeFile($this->fileName, 'Test'); + $this->metadataPool = $this->objectManager->get(MetadataPool::class); } /** @@ -534,28 +539,30 @@ private function duplicateMediaGalleryForProduct(string $imagePath, string $prod $product = $this->getProduct(null, $productSku); $connect = $this->galleryResource->getConnection(); $select = $connect->select()->from($this->galleryResource->getMainTable())->where('value = ?', $imagePath); - $res = $connect->fetchRow($select); - $value_id = $res['value_id']; - unset($res['value_id']); + $result = $connect->fetchRow($select); + $value_id = $result['value_id']; + unset($result['value_id']); $rows = [ - 'attribute_id' => $res['attribute_id'], - 'value' => $res['value'], - ProductAttributeMediaGalleryEntryInterface::MEDIA_TYPE => $res['media_type'], - ProductAttributeMediaGalleryEntryInterface::DISABLED => $res['disabled'], + 'attribute_id' => $result['attribute_id'], + 'value' => $result['value'], + ProductAttributeMediaGalleryEntryInterface::MEDIA_TYPE => $result['media_type'], + ProductAttributeMediaGalleryEntryInterface::DISABLED => $result['disabled'], ]; $connect->insert($this->galleryResource->getMainTable(), $rows); $select = $connect->select() ->from($this->galleryResource->getTable(Gallery::GALLERY_VALUE_TABLE)) ->where('value_id = ?', $value_id); - $res = $connect->fetchRow($select); + $result = $connect->fetchRow($select); $newValueId = (int)$value_id + 1; + $metadata = $this->metadataPool->getMetadata(ProductInterface::class); + $linkField = $metadata->getLinkField(); $rows = [ 'value_id' => $newValueId, - 'store_id' => $res['store_id'], - ProductAttributeMediaGalleryEntryInterface::LABEL => $res['label'], - ProductAttributeMediaGalleryEntryInterface::POSITION => $res['position'], - ProductAttributeMediaGalleryEntryInterface::DISABLED => $res['disabled'], - 'row_id' => $product->getRowId(), + 'store_id' => $result['store_id'], + ProductAttributeMediaGalleryEntryInterface::LABEL => $result['label'], + ProductAttributeMediaGalleryEntryInterface::POSITION => $result['position'], + ProductAttributeMediaGalleryEntryInterface::DISABLED => $result['disabled'], + $linkField => $product->getData($linkField), ]; $connect->insert($this->galleryResource->getTable(Gallery::GALLERY_VALUE_TABLE), $rows); $rows = ['value_id' => $newValueId, 'row_id' => $product->getRowId()]; From 12b60201014a997a72823b5cfeaac6ee126b3857 Mon Sep 17 00:00:00 2001 From: IvanPletnyov <ivan.pletnyov@transoftgroup.com> Date: Tue, 13 Oct 2020 13:38:50 +0300 Subject: [PATCH 081/139] MC-37558: Create automated test for "Override Category settings on Store View level" --- .../Controller/Adminhtml/Category/Save/UpdateCategoryTest.php | 2 +- .../testsuite/Magento/Catalog/Model/CategoryRepositoryTest.php | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/dev/tests/integration/testsuite/Magento/Catalog/Controller/Adminhtml/Category/Save/UpdateCategoryTest.php b/dev/tests/integration/testsuite/Magento/Catalog/Controller/Adminhtml/Category/Save/UpdateCategoryTest.php index c3d5ed080bcf2..75b96a1af3b09 100644 --- a/dev/tests/integration/testsuite/Magento/Catalog/Controller/Adminhtml/Category/Save/UpdateCategoryTest.php +++ b/dev/tests/integration/testsuite/Magento/Catalog/Controller/Adminhtml/Category/Save/UpdateCategoryTest.php @@ -45,7 +45,7 @@ protected function setUp(): void * @param array $postData * @return void */ - public function testUpdateCategoryForDefaultStoreView($postData): void + public function testUpdateCategoryForDefaultStoreView(array $postData): void { $storeId = (int)$this->storeManager->getStore('default')->getId(); $postData = array_merge($postData, ['store_id' => $storeId]); diff --git a/dev/tests/integration/testsuite/Magento/Catalog/Model/CategoryRepositoryTest.php b/dev/tests/integration/testsuite/Magento/Catalog/Model/CategoryRepositoryTest.php index 6469f80ff49b8..e829801d60e1a 100644 --- a/dev/tests/integration/testsuite/Magento/Catalog/Model/CategoryRepositoryTest.php +++ b/dev/tests/integration/testsuite/Magento/Catalog/Model/CategoryRepositoryTest.php @@ -24,6 +24,7 @@ * Provide tests for CategoryRepository model. * * @magentoDbIsolation enabled + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class CategoryRepositoryTest extends TestCase { From 71b027dedf046771e334fd2a9cbf2b648e62201c Mon Sep 17 00:00:00 2001 From: Myroslav Dobra <dmaraptor@gmail.com> Date: Tue, 13 Oct 2020 14:26:30 +0300 Subject: [PATCH 082/139] MC-29411: PHPStan: "Return typehint of method has invalid type " errors --- app/code/Magento/CatalogSearch/Model/Advanced.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/code/Magento/CatalogSearch/Model/Advanced.php b/app/code/Magento/CatalogSearch/Model/Advanced.php index 5143762a07e08..ba58066cae917 100644 --- a/app/code/Magento/CatalogSearch/Model/Advanced.php +++ b/app/code/Magento/CatalogSearch/Model/Advanced.php @@ -359,6 +359,8 @@ protected function getPreparedSearchCriteria($attribute, $value) if (is_array($value)) { if (isset($value['from']) && isset($value['to'])) { if (!empty($value['from']) || !empty($value['to'])) { + $from = ''; + $to = ''; if (isset($value['currency'])) { /** @var $currencyModel Currency */ $currencyModel = $this->_currencyFactory->create()->load($value['currency']); From 3ffc2e809d87b45a07dbd3b439523fb30ff1636b Mon Sep 17 00:00:00 2001 From: Yurii Sapiha <yurasapiga93@gmail.com> Date: Tue, 13 Oct 2020 15:17:09 +0300 Subject: [PATCH 083/139] MC-37070: Create automated test for "Import products with shared images" --- .../Magento/Catalog/Model/Product/Gallery/UpdateHandlerTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dev/tests/integration/testsuite/Magento/Catalog/Model/Product/Gallery/UpdateHandlerTest.php b/dev/tests/integration/testsuite/Magento/Catalog/Model/Product/Gallery/UpdateHandlerTest.php index 1101b7291528b..c5221f1ae5e76 100644 --- a/dev/tests/integration/testsuite/Magento/Catalog/Model/Product/Gallery/UpdateHandlerTest.php +++ b/dev/tests/integration/testsuite/Magento/Catalog/Model/Product/Gallery/UpdateHandlerTest.php @@ -565,7 +565,7 @@ private function duplicateMediaGalleryForProduct(string $imagePath, string $prod $linkField => $product->getData($linkField), ]; $connect->insert($this->galleryResource->getTable(Gallery::GALLERY_VALUE_TABLE), $rows); - $rows = ['value_id' => $newValueId, 'row_id' => $product->getRowId()]; + $rows = ['value_id' => $newValueId, $linkField => $product->getData($linkField)]; $connect->insert($this->galleryResource->getTable(Gallery::GALLERY_VALUE_TO_ENTITY_TABLE), $rows); } From 80e33111c3325423ea2fbced8094313a34d87cbb Mon Sep 17 00:00:00 2001 From: Bohdan Shevchenko <1408sheva@gmail.com> Date: Tue, 13 Oct 2020 15:51:36 +0300 Subject: [PATCH 084/139] MC-37546: Create automated test for "Create new Category Update" --- .../StorefrontCheckPresentSubCategoryActionGroup.xml | 2 +- .../AdminCategoryBasicFieldSection.xml | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontCheckPresentSubCategoryActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontCheckPresentSubCategoryActionGroup.xml index 7d8113f05518b..7cb3287614433 100644 --- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontCheckPresentSubCategoryActionGroup.xml +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontCheckPresentSubCategoryActionGroup.xml @@ -18,6 +18,6 @@ <waitForElement selector="{{AdminCategorySidebarTreeSection.categoryHighlighted(parenCategoryName)}}" stepKey="waitForTopMenuLoaded"/> <moveMouseOver selector="{{AdminCategorySidebarTreeSection.categoryHighlighted(parenCategoryName)}}" stepKey="moveMouseToParentCategory"/> - <seeElement selector="{{AdminCategorySidebarTreeSection.categoryHighlighted(childCategoryName)}}" stepKey="seeCategoryUpdated"/> + <seeElement selector="{{AdminCategorySidebarTreeSection.categoryHighlighted(childCategoryName)}}" stepKey="seeSubcategoryInTree"/> </actionGroup> </actionGroups> diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategoryBasicFieldSection/AdminCategoryBasicFieldSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategoryBasicFieldSection/AdminCategoryBasicFieldSection.xml index aff7ffe4d5763..1b041c5ca306f 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategoryBasicFieldSection/AdminCategoryBasicFieldSection.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategoryBasicFieldSection/AdminCategoryBasicFieldSection.xml @@ -22,5 +22,6 @@ <element name="FieldError" type="text" selector=".admin__field-error[data-bind='attr: {for: {{field}}}, text: error']" parameterized="true"/> <element name="panelFieldControl" type="input" selector="//aside//div[@data-index="{{arg1}}"]/descendant::*[@name="{{arg2}}"]" parameterized="true"/> <element name="productsInCategory" type="input" selector="div[data-index='assign_products']" timeout="30"/> + <element name="scheduleDesignUpdateTab" type="block" selector="div[data-index='schedule_design_update']" timeout="15"/> </section> </sections> From 3b2ece2b3f203c67bc0117e74bf5513f26ed2f35 Mon Sep 17 00:00:00 2001 From: Viktor Kopin <viktor.kopin@transoftgroup.com> Date: Tue, 13 Oct 2020 17:09:57 +0300 Subject: [PATCH 085/139] MC-38113: Same shipping address is repeating multiple times in storefront checkout when Reordered --- ...oggedInUserCheckoutFillingShippingSectionActionGroup.xml | 6 +++--- ...sertStorefrontCustomerHasNoOtherAddressesActionGroup.xml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/code/Magento/Checkout/Test/Mftf/ActionGroup/LoggedInUserCheckoutFillingShippingSectionActionGroup.xml b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/LoggedInUserCheckoutFillingShippingSectionActionGroup.xml index d3127362c637e..4b6680442a470 100644 --- a/app/code/Magento/Checkout/Test/Mftf/ActionGroup/LoggedInUserCheckoutFillingShippingSectionActionGroup.xml +++ b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/LoggedInUserCheckoutFillingShippingSectionActionGroup.xml @@ -24,10 +24,10 @@ <selectOption selector="{{CheckoutShippingSection.region}}" userInput="{{customerAddressVar.state}}" stepKey="selectRegion"/> <fillField selector="{{CheckoutShippingSection.postcode}}" userInput="{{customerAddressVar.postcode}}" stepKey="enterPostcode"/> <fillField selector="{{CheckoutShippingSection.telephone}}" userInput="{{customerAddressVar.telephone}}" stepKey="enterTelephone"/> - <waitForLoadingMaskToDisappear stepKey="waitForLoadingMask"/> + <waitForPageLoad stepKey="waitForLoadingMask"/> <click selector="{{CheckoutShippingSection.firstShippingMethod}}" stepKey="selectFirstShippingMethod"/> - <waitForElement selector="{{CheckoutShippingSection.next}}" time="30" stepKey="waitForNextButton"/> - <waitForLoadingMaskToDisappear stepKey="waitForShippingLoadingMask"/> + <waitForElementVisible selector="{{CheckoutShippingSection.next}}" time="30" stepKey="waitForNextButton"/> + <waitForPageLoad stepKey="waitForShippingLoadingMask"/> <click selector="{{CheckoutShippingSection.next}}" stepKey="clickNext"/> <waitForElementVisible selector="{{CheckoutPaymentSection.paymentSectionTitle}}" time="30" stepKey="waitForPaymentSectionLoaded"/> <seeInCurrentUrl url="{{CheckoutPage.url}}/#payment" stepKey="assertCheckoutPaymentUrl"/> diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AssertStorefrontCustomerHasNoOtherAddressesActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AssertStorefrontCustomerHasNoOtherAddressesActionGroup.xml index 8634ebb626e6d..1f56ba505128f 100644 --- a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AssertStorefrontCustomerHasNoOtherAddressesActionGroup.xml +++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AssertStorefrontCustomerHasNoOtherAddressesActionGroup.xml @@ -11,7 +11,7 @@ <annotations> <description>Verifies customer has no additional address in address book</description> </annotations> - <amOnPage url="customer/address/" stepKey="goToAddressPage"/> + <amOnPage url="{{StorefrontCustomerAddressesPage.url}}" stepKey="goToAddressPage"/> <waitForText userInput="You have no other address entries in your address book." selector=".block-addresses-list" stepKey="assertOtherAddresses"/> </actionGroup> </actionGroups> From 4e5ac6783aacb9e78b6cdb095ab2297723899e8d Mon Sep 17 00:00:00 2001 From: DmytroPaidych <dimonovp@gmail.com> Date: Tue, 13 Oct 2020 16:15:30 +0200 Subject: [PATCH 086/139] MC-37896: Create automated test for "Reset Widget" --- .../AdminSaveAndContinueWidgetActionGroup.xml | 21 ++++++ .../AdminSetInputTypeAndDesignActionGroup.xml | 22 +++++++ .../AdminSetWidgetNameAndStoreActionGroup.xml | 24 +++++++ .../Widget/Test/Mftf/Data/WidgetsData.xml | 1 + .../Mftf/Section/AdminNewWidgetSection.xml | 4 +- .../Test/Mftf/Test/AdminResetWidgetTest.xml | 65 +++++++++++++++++++ 6 files changed, 136 insertions(+), 1 deletion(-) create mode 100644 app/code/Magento/Widget/Test/Mftf/ActionGroup/AdminSaveAndContinueWidgetActionGroup.xml create mode 100644 app/code/Magento/Widget/Test/Mftf/ActionGroup/AdminSetInputTypeAndDesignActionGroup.xml create mode 100644 app/code/Magento/Widget/Test/Mftf/ActionGroup/AdminSetWidgetNameAndStoreActionGroup.xml create mode 100644 app/code/Magento/Widget/Test/Mftf/Test/AdminResetWidgetTest.xml diff --git a/app/code/Magento/Widget/Test/Mftf/ActionGroup/AdminSaveAndContinueWidgetActionGroup.xml b/app/code/Magento/Widget/Test/Mftf/ActionGroup/AdminSaveAndContinueWidgetActionGroup.xml new file mode 100644 index 0000000000000..d480ea685736d --- /dev/null +++ b/app/code/Magento/Widget/Test/Mftf/ActionGroup/AdminSaveAndContinueWidgetActionGroup.xml @@ -0,0 +1,21 @@ +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AdminSaveAndContinueWidgetActionGroup"> + <annotations> + <description>Click on the Save an Continue button and check the success message</description> + </annotations> + <scrollToTopOfPage stepKey="scrollToTopOfPage"/> + <click selector="{{AdminNewWidgetSection.saveAndContinue}}" stepKey="clickSaveWidget"/> + <waitForElementVisible selector="{{AdminMessagesSection.success}}" stepKey="waitForPageLoad"/> + <see selector="{{AdminMessagesSection.success}}" userInput="The widget instance has been saved" stepKey="seeSuccess"/> + </actionGroup> +</actionGroups> + + diff --git a/app/code/Magento/Widget/Test/Mftf/ActionGroup/AdminSetInputTypeAndDesignActionGroup.xml b/app/code/Magento/Widget/Test/Mftf/ActionGroup/AdminSetInputTypeAndDesignActionGroup.xml new file mode 100644 index 0000000000000..3071f60bbc9d6 --- /dev/null +++ b/app/code/Magento/Widget/Test/Mftf/ActionGroup/AdminSetInputTypeAndDesignActionGroup.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AdminSetInputTypeAndDesignActionGroup"> + <annotations> + <description>On the widget_instance page select widget type and design</description> + </annotations> + <arguments> + <argument name="widgetType" defaultValue="{{ProductsListWidget.type}}" type="string"/> + <argument name="widgetDesign" defaultValue="{{ProductsListWidget.design_theme}}" type="string"/> + </arguments> + <selectOption selector="{{AdminNewWidgetSection.widgetType}}" userInput="{{widgetType}}" stepKey="setWidgetType"/> + <selectOption selector="{{AdminNewWidgetSection.widgetDesignTheme}}" userInput="{{widgetDesign}}" stepKey="setWidgetDesignTheme"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Widget/Test/Mftf/ActionGroup/AdminSetWidgetNameAndStoreActionGroup.xml b/app/code/Magento/Widget/Test/Mftf/ActionGroup/AdminSetWidgetNameAndStoreActionGroup.xml new file mode 100644 index 0000000000000..80546b9d5e6df --- /dev/null +++ b/app/code/Magento/Widget/Test/Mftf/ActionGroup/AdminSetWidgetNameAndStoreActionGroup.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AdminSetWidgetNameAndStoreActionGroup"> + <annotations> + <description>On the widget creation page page set widget name, store add sort order.</description> + </annotations> + <arguments> + <argument name="widgetName" defaultValue="{{ProductsListWidget.name}}" type="string"/> + <argument name="widgetStore" defaultValue="{{ProductsListWidget.store_ids}}" type="string"/> + <argument name="widgetSortOrder" defaultValue="{{ProductsListWidget.sort_order}}" type="string"/> + </arguments> + <fillField selector="{{AdminNewWidgetSection.widgetTitle}}" userInput="{{widgetName}}" stepKey="fillTitle"/> + <selectOption selector="{{AdminNewWidgetSection.widgetStoreIds}}" parameterArray="[{{widgetStore}}]" stepKey="setWidgetStoreId"/> + <fillField selector="{{AdminNewWidgetSection.widgetSortOrder}}" userInput="{{widgetSortOrder}}" stepKey="fillSortOrder"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Widget/Test/Mftf/Data/WidgetsData.xml b/app/code/Magento/Widget/Test/Mftf/Data/WidgetsData.xml index ffd2d025548cf..0bac99731ef7b 100644 --- a/app/code/Magento/Widget/Test/Mftf/Data/WidgetsData.xml +++ b/app/code/Magento/Widget/Test/Mftf/Data/WidgetsData.xml @@ -12,6 +12,7 @@ <data key="type">Catalog Products List</data> <data key="design_theme">Magento Luma</data> <data key="name" unique="suffix">TestWidget</data> + <data key="sort_order">0</data> <array key="store_ids"> <item>All Store Views</item> </array> diff --git a/app/code/Magento/Widget/Test/Mftf/Section/AdminNewWidgetSection.xml b/app/code/Magento/Widget/Test/Mftf/Section/AdminNewWidgetSection.xml index c1ff351823823..2e455f4a3470b 100644 --- a/app/code/Magento/Widget/Test/Mftf/Section/AdminNewWidgetSection.xml +++ b/app/code/Magento/Widget/Test/Mftf/Section/AdminNewWidgetSection.xml @@ -12,6 +12,7 @@ <element name="widgetType" type="select" selector="#code"/> <element name="widgetDesignTheme" type="select" selector="#theme_id"/> <element name="continue" type="button" timeout="30" selector="#continue_button"/> + <element name="resetBtn" type="button" selector="#reset" timeout="30"/> <element name="widgetTitle" type="input" selector="#title"/> <element name="widgetStoreIds" type="select" selector="#store_ids"/> <element name="widgetSortOrder" type="input" selector="#sort_order"/> @@ -38,10 +39,11 @@ <element name="searchBlock" type="button" selector="//div[@class='admin__filter-actions']/button[@title='Search']"/> <element name="blockStatus" type="select" selector="//select[@name='chooser_is_active']"/> <element name="searchedBlock" type="button" selector="//*[@class='magento-message']//tbody/tr/td[1]"/> - <element name="saveWidget" type="select" selector="#save"/> + <element name="saveWidget" type="button" selector="#save"/> <element name="displayMode" type="select" selector="select[id*='display_mode']"/> <element name="restrictTypes" type="select" selector="select[id*='types']"/> <element name="saveAndContinue" type="button" selector="#save_and_edit_button" timeout="30"/> + <element name="widgetInstanceType" type="select" selector="#instance_code" /> <!-- Catalog Product List Widget Options --> <element name="title" type="input" selector="[name='parameters[title]']"/> <element name="displayPageControl" type="select" selector="[name='parameters[show_pager]']"/> diff --git a/app/code/Magento/Widget/Test/Mftf/Test/AdminResetWidgetTest.xml b/app/code/Magento/Widget/Test/Mftf/Test/AdminResetWidgetTest.xml new file mode 100644 index 0000000000000..3a94871d06e1c --- /dev/null +++ b/app/code/Magento/Widget/Test/Mftf/Test/AdminResetWidgetTest.xml @@ -0,0 +1,65 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="AdminResetWidgetTest"> + <annotations> + <features value="Widget"/> + <stories value="Reset widget"/> + <title value="[CMS Widgets] Reset Widget"/> + <description value="Check that admin user can reset widget form after filling out all information"/> + <severity value="MAJOR"/> + <testCaseId value="MC-37892"/> + <group value="widget"/> + </annotations> + <before> + <actionGroup ref="AdminLoginActionGroup" stepKey="loginAsAdmin"/> + </before> + <after> + <actionGroup ref="AdminDeleteWidgetActionGroup" stepKey="deleteWidget"> + <argument name="widget" value="ProductsListWidget"/> + </actionGroup> + <actionGroup ref="AdminLogoutActionGroup" stepKey="logoutFromAdmin"/> + </after> + <amOnPage url="{{AdminNewWidgetPage.url}}" stepKey="amOnAdminNewWidgetPage"/> + <actionGroup ref="AdminSetInputTypeAndDesignActionGroup" stepKey="firstSetTypeAndDesign"> + <argument name="widgetType" value="{{ProductsListWidget.type}}"/> + <argument name="widgetDesign" value="{{ProductsListWidget.design_theme}}"/> + </actionGroup> + <click selector="{{AdminNewWidgetSection.resetBtn}}" stepKey="resetInstance"/> + <dontSeeInField userInput="{{ProductsListWidget.type}}" selector="{{AdminNewWidgetSection.widgetType}}" stepKey="dontSeeTypeAfterReset"/> + <dontSeeInField userInput="{{ProductsListWidget.design_theme}}" selector="{{AdminNewWidgetSection.widgetDesignTheme}}" stepKey="dontSeeDesignAfterReset"/> + <actionGroup ref="AdminSetInputTypeAndDesignActionGroup" stepKey="setTypeAndDesignAfterReset"> + <argument name="widgetType" value="{{ProductsListWidget.type}}"/> + <argument name="widgetDesign" value="{{ProductsListWidget.design_theme}}"/> + </actionGroup> + <click selector="{{AdminNewWidgetSection.continue}}" stepKey="clickContinue"/> + <actionGroup ref="AdminSetWidgetNameAndStoreActionGroup" stepKey="setNameAndStore"> + <argument name="widgetName" value="{{ProductsListWidget.name}}"/> + <argument name="widgetStore" value="{{ProductsListWidget.store_ids}}"/> + <argument name="widgetSortOrder" value="{{ProductsListWidget.sort_order}}"/> + </actionGroup> + <click selector="{{AdminNewWidgetSection.resetBtn}}" stepKey="resetNameAndStore"/> + <dontSeeInField userInput="{{ProductsListWidget.name}}" selector="{{AdminNewWidgetSection.widgetTitle}}" stepKey="dontSeeNameAfterReset"/> + <dontSeeInField userInput="{{ProductsListWidget.store_ids[0]}}" selector="{{AdminNewWidgetSection.widgetStoreIds}}" stepKey="dontSeeStoreAfterReset"/> + <dontSeeInField userInput="{{ProductsListWidget.sort_order}}" selector="{{AdminNewWidgetSection.widgetSortOrder}}" stepKey="dontSeeSortOrderAfterReset"/> + <actionGroup ref="AdminSetWidgetNameAndStoreActionGroup" stepKey="setNameAndStoreAfterReset"> + <argument name="widgetName" value="{{ProductsListWidget.name}}"/> + <argument name="widgetStore" value="{{ProductsListWidget.store_ids}}"/> + <argument name="widgetSortOrder" value="{{ProductsListWidget.sort_order}}"/> + </actionGroup> + <actionGroup ref="AdminSaveAndContinueWidgetActionGroup" stepKey="saveWidget"/> + <click selector="{{AdminNewWidgetSection.resetBtn}}" stepKey="resetWidget"/> + <seeInField userInput="{{ProductsListWidget.name}}" selector="{{AdminNewWidgetSection.widgetTitle}}" stepKey="seeNameAfterReset"/> + <seeInField userInput="{{ProductsListWidget.store_ids[0]}}" selector="{{AdminNewWidgetSection.widgetStoreIds}}" stepKey="seeStoreAfterReset"/> + <seeInField userInput="{{ProductsListWidget.sort_order}}" selector="{{AdminNewWidgetSection.widgetSortOrder}}" stepKey="seeSortOrderAfterReset"/> + <seeInField userInput="{{ProductsListWidget.type}}" selector="{{AdminNewWidgetSection.widgetInstanceType}}" stepKey="seeTypeAfterReset"/> + <seeInField userInput="{{ProductsListWidget.design_theme}}" selector="{{AdminNewWidgetSection.widgetDesignTheme}}" stepKey="seeThemeAfterReset"/> + </test> +</tests> From fceacabac975f59d29ef75271c4ad638a349810c Mon Sep 17 00:00:00 2001 From: Mykhailo Matiola <mykhailo.matiola@transoftgroup.com> Date: Tue, 13 Oct 2020 17:31:58 +0300 Subject: [PATCH 087/139] MC-36960: Create automated test for "Create product for "all" store views using API service" --- .../ProductRepositoryAllStoreViewsTest.php | 236 ++++++++++++++++++ 1 file changed, 236 insertions(+) create mode 100644 dev/tests/api-functional/testsuite/Magento/Catalog/Api/ProductRepositoryAllStoreViewsTest.php diff --git a/dev/tests/api-functional/testsuite/Magento/Catalog/Api/ProductRepositoryAllStoreViewsTest.php b/dev/tests/api-functional/testsuite/Magento/Catalog/Api/ProductRepositoryAllStoreViewsTest.php new file mode 100644 index 0000000000000..1d3b4ca591c08 --- /dev/null +++ b/dev/tests/api-functional/testsuite/Magento/Catalog/Api/ProductRepositoryAllStoreViewsTest.php @@ -0,0 +1,236 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Catalog\Api; + +use Magento\Catalog\Model\Product\Type; +use Magento\Catalog\Model\ResourceModel\Product\Website\Link; +use Magento\Framework\ObjectManagerInterface; +use Magento\Framework\Registry; +use Magento\Framework\Webapi\Rest\Request; +use Magento\Store\Model\StoreManagerInterface; +use Magento\TestFramework\Helper\Bootstrap; +use Magento\TestFramework\TestCase\WebapiAbstract; + +/** + * Tests for products creation for all store views. + * + * @magentoAppIsolation enabled + */ +class ProductRepositoryAllStoreViewsTest extends WebapiAbstract +{ + const PRODUCT_SERVICE_NAME = 'catalogProductRepositoryV1'; + const SERVICE_VERSION = 'V1'; + const PRODUCTS_RESOURCE_PATH = '/V1/products'; + + /** + * @var ObjectManagerInterface + */ + private $objectManager; + + /** + * @var ProductRepositoryInterface + */ + private $productRepository; + + /** + * @var Registry + */ + private $registry; + + /** + * @var StoreManagerInterface + */ + private $storeManager; + + /** + * @var string + */ + private $productSku = 'simple'; + + /** + * @var Link + */ + private $productWebsiteLink; + + /** + * @inheritdoc + */ + protected function setUp(): void + { + parent::setUp(); + $this->objectManager = Bootstrap::getObjectManager(); + $this->productRepository = $this->objectManager->get(ProductRepositoryInterface::class); + $this->productRepository->cleanCache(); + $this->registry = $this->objectManager->get(Registry::class); + $this->storeManager = $this->objectManager->get(StoreManagerInterface::class); + $this->productWebsiteLink = $this->objectManager->get(Link::class); + } + + /** + * @inheritdoc + */ + protected function tearDown(): void + { + $this->registry->unregister('isSecureArea'); + $this->registry->register('isSecureArea', true); + $this->productRepository->delete( + $this->productRepository->get($this->productSku) + ); + $this->registry->unregister('isSecureArea'); + $this->registry->register('isSecureArea', false); + + parent::tearDown(); + } + + /** + * @magentoApiDataFixture Magento/Catalog/_files/category.php + */ + public function testCreateProduct(): void + { + $productData = $this->getProductData(); + $resultData = $this->saveProduct($productData); + $this->assertProductWebsites($this->productSku, $this->getAllWebsiteIds()); + $this->assertProductData($productData, $resultData, $this->getAllWebsiteIds()); + } + + /** + * @magentoApiDataFixture Magento/Catalog/_files/category.php + * @magentoApiDataFixture Magento/Store/_files/second_website_with_store_group_and_store.php + */ + public function testCreateProductOnMultipleWebsites(): void + { + $productData = $this->getProductData(); + $resultData = $this->saveProduct($productData); + $this->assertProductWebsites($this->productSku, $this->getAllWebsiteIds()); + $this->assertProductData($productData, $resultData, $this->getAllWebsiteIds()); + } + + /** + * Saves Product via API. + * + * @param $product + * @return array + */ + private function saveProduct($product): array + { + $serviceInfo = [ + 'rest' => ['resourcePath' =>self::PRODUCTS_RESOURCE_PATH, 'httpMethod' => Request::HTTP_METHOD_POST], + 'soap' => [ + 'service' => self::PRODUCT_SERVICE_NAME, + 'serviceVersion' => self::SERVICE_VERSION, + 'operation' => self::PRODUCT_SERVICE_NAME . 'Save' + ] + ]; + $requestData = ['product' => $product]; + return $this->_webApiCall($serviceInfo, $requestData, null, 'all'); + } + + /** + * Returns product data. + * + * @return array + */ + private function getProductData(): array + { + return [ + 'sku' => $this->productSku, + 'name' => 'simple', + 'type_id' => Type::TYPE_SIMPLE, + 'weight' => 1, + 'attribute_set_id' => 4, + 'price' => 10, + 'status' => 1, + 'visibility' => 4, + 'extension_attributes' => [ + 'stock_item' => ['is_in_stock' => true, 'qty' => 1000] + ], + 'custom_attributes' => [ + ['attribute_code' => 'url_key', 'value' => 'simple'], + ['attribute_code' => 'tax_class_id', 'value' => 2], + ['attribute_code' => 'category_ids', 'value' => [333]] + ] + ]; + } + + /** + * Asserts that product is linked to websites in 'catalog_product_website' table. + * + * @param string $sku + * @param array $websiteIds + * @return void + */ + private function assertProductWebsites(string $sku, array $websiteIds): void + { + $productId = $this->productRepository->get($sku)->getId(); + $this->assertEquals($websiteIds, $this->productWebsiteLink->getWebsiteIdsByProductId($productId)); + } + + /** + * Asserts result after product creation. + * + * @param array $productData + * @param array $resultData + * @param array $websiteIds + * @return void + */ + private function assertProductData(array $productData, array $resultData, array $websiteIds): void + { + foreach ($productData as $key => $value) { + if ($key == 'extension_attributes' || $key == 'custom_attributes') { + continue; + } + $this->assertEquals($value, $resultData[$key]); + } + foreach ($productData['custom_attributes'] as $attribute) { + $resultAttribute = $this->getCustomAttributeByCode( + $resultData['custom_attributes'], + $attribute['attribute_code'] + ); + $this->assertEquals($attribute['value'], $resultAttribute['value']); + } + foreach ($productData['extension_attributes']['stock_item'] as $key => $value) { + $this->assertEquals($value, $resultData['extension_attributes']['stock_item'][$key]); + } + $this->assertEquals($websiteIds, $resultData['extension_attributes']['website_ids']); + } + + /** + * Get list of all websites IDs. + * + * @return array + */ + private function getAllWebsiteIds(): array + { + $websiteIds = []; + $websites = $this->storeManager->getWebsites(); + foreach ($websites as $website) { + $websiteIds[] = $website->getId(); + } + + return $websiteIds; + } + + /** + * Returns custom attribute data by given code. + * + * @param array $attributes + * @param string $attributeCode + * @return array + */ + private function getCustomAttributeByCode(array $attributes, string $attributeCode): array + { + $items = array_filter( + $attributes, + function ($attribute) use ($attributeCode) { + return $attribute['attribute_code'] == $attributeCode; + } + ); + + return reset($items); + } +} From 4c1243c56ba28eb8e68a111b34115a91993066f4 Mon Sep 17 00:00:00 2001 From: Mykhailo Matiola <mykhailo.matiola@transoftgroup.com> Date: Tue, 13 Oct 2020 17:43:50 +0300 Subject: [PATCH 088/139] MC-36960: Create automated test for "Create product for "all" store views using API service" --- .../ProductRepositoryAllStoreViewsTest.php | 26 ++++++++++++++----- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/dev/tests/api-functional/testsuite/Magento/Catalog/Api/ProductRepositoryAllStoreViewsTest.php b/dev/tests/api-functional/testsuite/Magento/Catalog/Api/ProductRepositoryAllStoreViewsTest.php index 1d3b4ca591c08..2950dda4b3c52 100644 --- a/dev/tests/api-functional/testsuite/Magento/Catalog/Api/ProductRepositoryAllStoreViewsTest.php +++ b/dev/tests/api-functional/testsuite/Magento/Catalog/Api/ProductRepositoryAllStoreViewsTest.php @@ -7,8 +7,12 @@ namespace Magento\Catalog\Api; +use Magento\Catalog\Api\Data\ProductAttributeInterface; +use Magento\Catalog\Model\Product\Attribute\Source\Status; use Magento\Catalog\Model\Product\Type; +use Magento\Catalog\Model\Product\Visibility; use Magento\Catalog\Model\ResourceModel\Product\Website\Link; +use Magento\Eav\Model\Config; use Magento\Framework\ObjectManagerInterface; use Magento\Framework\Registry; use Magento\Framework\Webapi\Rest\Request; @@ -48,14 +52,18 @@ class ProductRepositoryAllStoreViewsTest extends WebapiAbstract private $storeManager; /** - * @var string + * @var Link */ - private $productSku = 'simple'; + private $productWebsiteLink; + /** + * @var Config + */ + private $eavConfig; /** - * @var Link + * @var string */ - private $productWebsiteLink; + private $productSku = 'simple'; /** * @inheritdoc @@ -66,6 +74,7 @@ protected function setUp(): void $this->objectManager = Bootstrap::getObjectManager(); $this->productRepository = $this->objectManager->get(ProductRepositoryInterface::class); $this->productRepository->cleanCache(); + $this->eavConfig = $this->objectManager->get(Config::class); $this->registry = $this->objectManager->get(Registry::class); $this->storeManager = $this->objectManager->get(StoreManagerInterface::class); $this->productWebsiteLink = $this->objectManager->get(Link::class); @@ -137,15 +146,18 @@ private function saveProduct($product): array */ private function getProductData(): array { + $setId =(int)$this->eavConfig->getEntityType(ProductAttributeInterface::ENTITY_TYPE_CODE) + ->getDefaultAttributeSetId(); + return [ 'sku' => $this->productSku, 'name' => 'simple', 'type_id' => Type::TYPE_SIMPLE, 'weight' => 1, - 'attribute_set_id' => 4, + 'attribute_set_id' => $setId, 'price' => 10, - 'status' => 1, - 'visibility' => 4, + 'status' => Status::STATUS_ENABLED, + 'visibility' => Visibility::VISIBILITY_BOTH, 'extension_attributes' => [ 'stock_item' => ['is_in_stock' => true, 'qty' => 1000] ], From 1c2d999aa225cb0f06c80ca34df6b0eb561e68eb Mon Sep 17 00:00:00 2001 From: Myroslav Dobra <dmaraptor@gmail.com> Date: Tue, 13 Oct 2020 18:17:17 +0300 Subject: [PATCH 089/139] MC-29405: PHPStan: "Class does not have a constructor and must be instantiated without any parameters" errors --- .../Attribute/Backend/BillingTest.php | 5 +--- .../Attribute/Backend/ShippingTest.php | 5 +--- .../Design/Config/Edit/SaveButtonTest.php | 23 +------------------ .../Test/Unit/Topology/QueueInstallerTest.php | 3 +-- .../Module/Dependency/ServiceLocator.php | 2 +- 5 files changed, 5 insertions(+), 33 deletions(-) diff --git a/app/code/Magento/Customer/Test/Unit/Model/Customer/Attribute/Backend/BillingTest.php b/app/code/Magento/Customer/Test/Unit/Model/Customer/Attribute/Backend/BillingTest.php index cd7154de14858..4f318948097cc 100644 --- a/app/code/Magento/Customer/Test/Unit/Model/Customer/Attribute/Backend/BillingTest.php +++ b/app/code/Magento/Customer/Test/Unit/Model/Customer/Attribute/Backend/BillingTest.php @@ -23,10 +23,7 @@ class BillingTest extends TestCase protected function setUp(): void { - $logger = $this->getMockBuilder(LoggerInterface::class) - ->getMock(); - /** @var LoggerInterface $logger */ - $this->testable = new Billing($logger); + $this->testable = new Billing(); } public function testBeforeSave() diff --git a/app/code/Magento/Customer/Test/Unit/Model/Customer/Attribute/Backend/ShippingTest.php b/app/code/Magento/Customer/Test/Unit/Model/Customer/Attribute/Backend/ShippingTest.php index 3947a01582313..6270905ca2e85 100644 --- a/app/code/Magento/Customer/Test/Unit/Model/Customer/Attribute/Backend/ShippingTest.php +++ b/app/code/Magento/Customer/Test/Unit/Model/Customer/Attribute/Backend/ShippingTest.php @@ -23,10 +23,7 @@ class ShippingTest extends TestCase protected function setUp(): void { - $logger = $this->getMockBuilder(LoggerInterface::class) - ->getMock(); - /** @var LoggerInterface $logger */ - $this->testable = new Shipping($logger); + $this->testable = new Shipping(); } public function testBeforeSave() diff --git a/app/code/Magento/Theme/Test/Unit/Block/Adminhtml/Design/Config/Edit/SaveButtonTest.php b/app/code/Magento/Theme/Test/Unit/Block/Adminhtml/Design/Config/Edit/SaveButtonTest.php index 65e2b934741ee..0ce450df39b59 100644 --- a/app/code/Magento/Theme/Test/Unit/Block/Adminhtml/Design/Config/Edit/SaveButtonTest.php +++ b/app/code/Magento/Theme/Test/Unit/Block/Adminhtml/Design/Config/Edit/SaveButtonTest.php @@ -20,16 +20,9 @@ class SaveButtonTest extends TestCase */ protected $block; - /** - * @var Context|MockObject - */ - protected $context; - protected function setUp(): void { - $this->initContext(); - - $this->block = new SaveButton($this->context); + $this->block = new SaveButton(); } public function testGetButtonData() @@ -41,18 +34,4 @@ public function testGetButtonData() $this->assertArrayHasKey('data_attribute', $result); $this->assertIsArray($result['data_attribute']); } - - protected function initContext() - { - $this->urlBuilder = $this->getMockBuilder(UrlInterface::class) - ->disableOriginalConstructor() - ->getMockForAbstractClass(); - - $this->context = $this->getMockBuilder(Context::class) - ->disableOriginalConstructor() - ->getMock(); - $this->context->expects($this->any()) - ->method('getUrlBuilder') - ->willReturn($this->urlBuilder); - } } diff --git a/lib/internal/Magento/Framework/Amqp/Test/Unit/Topology/QueueInstallerTest.php b/lib/internal/Magento/Framework/Amqp/Test/Unit/Topology/QueueInstallerTest.php index 7493d1691699a..44d33362ae26e 100644 --- a/lib/internal/Magento/Framework/Amqp/Test/Unit/Topology/QueueInstallerTest.php +++ b/lib/internal/Magento/Framework/Amqp/Test/Unit/Topology/QueueInstallerTest.php @@ -16,8 +16,7 @@ class QueueInstallerTest extends TestCase { public function testInstall() { - $bindingInstaller = $this->getMockForAbstractClass(QueueConfigItemInterface::class); - $model = new QueueInstaller($bindingInstaller); + $model = new QueueInstaller(); $channel = $this->createMock(AMQPChannel::class); $queue = $this->getMockForAbstractClass(QueueConfigItemInterface::class); diff --git a/setup/src/Magento/Setup/Module/Dependency/ServiceLocator.php b/setup/src/Magento/Setup/Module/Dependency/ServiceLocator.php index 27f0c7e8e616f..d162d07b38cf8 100644 --- a/setup/src/Magento/Setup/Module/Dependency/ServiceLocator.php +++ b/setup/src/Magento/Setup/Module/Dependency/ServiceLocator.php @@ -95,7 +95,7 @@ public static function getCircularDependenciesReportBuilder() self::$circularDependenciesReportBuilder = new CircularReport\Builder( self::getComposerJsonParser(), new CircularReport\Writer(self::getCsvWriter()), - new CircularTool([], null) + new CircularTool() ); } return self::$circularDependenciesReportBuilder; From 19da476f971c476909713ac6425f454b7ac4cc79 Mon Sep 17 00:00:00 2001 From: DmytroPaidych <dimonovp@gmail.com> Date: Tue, 13 Oct 2020 17:21:26 +0200 Subject: [PATCH 090/139] MC-37896: Create automated test for "Reset Widget" --- .../ActionGroup/AdminSaveAndContinueWidgetActionGroup.xml | 2 -- .../ActionGroup/AdminSetWidgetNameAndStoreActionGroup.xml | 4 ++-- .../Magento/Widget/Test/Mftf/Test/AdminResetWidgetTest.xml | 4 ++-- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/app/code/Magento/Widget/Test/Mftf/ActionGroup/AdminSaveAndContinueWidgetActionGroup.xml b/app/code/Magento/Widget/Test/Mftf/ActionGroup/AdminSaveAndContinueWidgetActionGroup.xml index d480ea685736d..cd9774f3b13ba 100644 --- a/app/code/Magento/Widget/Test/Mftf/ActionGroup/AdminSaveAndContinueWidgetActionGroup.xml +++ b/app/code/Magento/Widget/Test/Mftf/ActionGroup/AdminSaveAndContinueWidgetActionGroup.xml @@ -17,5 +17,3 @@ <see selector="{{AdminMessagesSection.success}}" userInput="The widget instance has been saved" stepKey="seeSuccess"/> </actionGroup> </actionGroups> - - diff --git a/app/code/Magento/Widget/Test/Mftf/ActionGroup/AdminSetWidgetNameAndStoreActionGroup.xml b/app/code/Magento/Widget/Test/Mftf/ActionGroup/AdminSetWidgetNameAndStoreActionGroup.xml index 80546b9d5e6df..f1faadb1e434e 100644 --- a/app/code/Magento/Widget/Test/Mftf/ActionGroup/AdminSetWidgetNameAndStoreActionGroup.xml +++ b/app/code/Magento/Widget/Test/Mftf/ActionGroup/AdminSetWidgetNameAndStoreActionGroup.xml @@ -14,11 +14,11 @@ </annotations> <arguments> <argument name="widgetName" defaultValue="{{ProductsListWidget.name}}" type="string"/> - <argument name="widgetStore" defaultValue="{{ProductsListWidget.store_ids}}" type="string"/> + <argument name="widgetStoreIds" defaultValue="{{ProductsListWidget.store_ids}}" type="string"/> <argument name="widgetSortOrder" defaultValue="{{ProductsListWidget.sort_order}}" type="string"/> </arguments> <fillField selector="{{AdminNewWidgetSection.widgetTitle}}" userInput="{{widgetName}}" stepKey="fillTitle"/> - <selectOption selector="{{AdminNewWidgetSection.widgetStoreIds}}" parameterArray="[{{widgetStore}}]" stepKey="setWidgetStoreId"/> + <selectOption selector="{{AdminNewWidgetSection.widgetStoreIds}}" parameterArray="{{widgetStoreIds}}" stepKey="setWidgetStoreId"/> <fillField selector="{{AdminNewWidgetSection.widgetSortOrder}}" userInput="{{widgetSortOrder}}" stepKey="fillSortOrder"/> </actionGroup> </actionGroups> diff --git a/app/code/Magento/Widget/Test/Mftf/Test/AdminResetWidgetTest.xml b/app/code/Magento/Widget/Test/Mftf/Test/AdminResetWidgetTest.xml index 3a94871d06e1c..88610d9143bb4 100644 --- a/app/code/Magento/Widget/Test/Mftf/Test/AdminResetWidgetTest.xml +++ b/app/code/Magento/Widget/Test/Mftf/Test/AdminResetWidgetTest.xml @@ -42,7 +42,7 @@ <click selector="{{AdminNewWidgetSection.continue}}" stepKey="clickContinue"/> <actionGroup ref="AdminSetWidgetNameAndStoreActionGroup" stepKey="setNameAndStore"> <argument name="widgetName" value="{{ProductsListWidget.name}}"/> - <argument name="widgetStore" value="{{ProductsListWidget.store_ids}}"/> + <argument name="widgetStoreIds" value="{{ProductsListWidget.store_ids}}"/> <argument name="widgetSortOrder" value="{{ProductsListWidget.sort_order}}"/> </actionGroup> <click selector="{{AdminNewWidgetSection.resetBtn}}" stepKey="resetNameAndStore"/> @@ -51,7 +51,7 @@ <dontSeeInField userInput="{{ProductsListWidget.sort_order}}" selector="{{AdminNewWidgetSection.widgetSortOrder}}" stepKey="dontSeeSortOrderAfterReset"/> <actionGroup ref="AdminSetWidgetNameAndStoreActionGroup" stepKey="setNameAndStoreAfterReset"> <argument name="widgetName" value="{{ProductsListWidget.name}}"/> - <argument name="widgetStore" value="{{ProductsListWidget.store_ids}}"/> + <argument name="widgetStoreIds" value="{{ProductsListWidget.store_ids}}"/> <argument name="widgetSortOrder" value="{{ProductsListWidget.sort_order}}"/> </actionGroup> <actionGroup ref="AdminSaveAndContinueWidgetActionGroup" stepKey="saveWidget"/> From 6442ec3100fe3b3f420602821e95e68cbb22aaae Mon Sep 17 00:00:00 2001 From: Myroslav Dobra <dmaraptor@gmail.com> Date: Tue, 13 Oct 2020 21:08:22 +0300 Subject: [PATCH 091/139] MC-29414: PHPStan: "Method invoked with X parameters, Y required" errors --- .../Model/Widget/Grid/AbstractTotals.php | 8 ++--- .../Backend/Test/Unit/Helper/DataTest.php | 1 - .../Catalog/Block/Product/View/Details.php | 3 +- .../Model/ResourceModel/Category/Tree.php | 9 +++++- .../Model/ResourceModel/Product/Action.php | 2 +- .../Product/Attribute/RepositoryTest.php | 11 +------ .../Product/Option/Validator/SelectTest.php | 2 +- .../Product/Price/PricePersistenceTest.php | 2 +- .../Model/Quote/Item/QuantityValidator.php | 2 +- .../Model/Plugin/AfterProductLoadTest.php | 16 +--------- .../Observer/ProcessCronQueueObserverTest.php | 4 ++- app/code/Magento/Customer/Model/Address.php | 4 +-- .../Customer/Model/ResourceModel/Customer.php | 2 +- .../Adminhtml/Index/ViewfileTest.php | 6 +--- .../Model/Product/TypeHandler/SampleTest.php | 2 +- .../Unit/Model/Entity/AttributeLoaderTest.php | 6 ++-- .../Widget/Grid/Column/Renderer/Link.php | 7 ++-- .../Integration/Model/Oauth/Consumer.php | 26 +++++++-------- .../Magento/Integration/Model/Oauth/Token.php | 24 +++++++------- .../Unit/Controller/Partners/IndexTest.php | 6 ++-- .../Test/Unit/Model/PartnersTest.php | 6 ++-- .../Model/Cron/ReportNewRelicCronTest.php | 11 +------ .../Subscriber/Grid/Collection.php | 3 +- .../Product/Gallery/RetrieveImageTest.php | 32 +++---------------- app/code/Magento/Quote/Model/QuoteIdMask.php | 2 +- .../Model/ResourceModel/Order/Collection.php | 2 +- .../Magento/Review/Block/Adminhtml/Grid.php | 15 ++++----- app/code/Magento/Sales/Model/Order.php | 16 +++++----- .../Controller/Adminhtml/Order/ViewTest.php | 5 ++- .../Sales/Test/Unit/Helper/DataTest.php | 27 +++------------- .../Swatches/Test/Unit/Helper/DataTest.php | 28 +++------------- .../Unit/Model/SwatchAttributeCodesTest.php | 9 ++---- .../System/Design/Theme/Edit/Tab/Css.php | 6 ++-- .../Design/Config/Collection.php | 7 ++-- .../System/Design/Theme/SaveTest.php | 2 +- .../frontend/templates/html/sections.phtml | 4 +-- .../Adapter/Rest/DocumentationGenerator.php | 2 +- .../Framework/Cache/Backend/MongoDbTest.php | 2 +- .../Magento/Framework/Session/ConfigTest.php | 10 +++--- .../Magento/Test/Legacy/ObsoleteCodeTest.php | 4 ++- .../Magento/Framework/Backup/Filesystem.php | 6 +++- lib/internal/Magento/Framework/DB/Tree.php | 12 +++++++ .../Indexer/Test/Unit/IndexStructureTest.php | 2 +- .../Test/Unit/TypeProcessorTest.php | 6 ++-- .../TestFramework/Unit/Block/Adminhtml.php | 11 ++++--- lib/internal/Magento/Framework/Url.php | 2 +- .../Magento/Framework/View/Context.php | 7 ++-- .../View/Test/Unit/Asset/ConfigTest.php | 10 +----- .../Message/MessageConfigurationsPoolTest.php | 12 +++---- .../Test/Unit/Model/ModuleUninstallerTest.php | 12 +------ .../Di/App/Task/OperationFactoryTest.php | 3 +- 51 files changed, 160 insertions(+), 259 deletions(-) diff --git a/app/code/Magento/Backend/Model/Widget/Grid/AbstractTotals.php b/app/code/Magento/Backend/Model/Widget/Grid/AbstractTotals.php index bb68853dcc08d..dd92661eb9452 100644 --- a/app/code/Magento/Backend/Model/Widget/Grid/AbstractTotals.php +++ b/app/code/Magento/Backend/Model/Widget/Grid/AbstractTotals.php @@ -117,7 +117,7 @@ protected function _countExpr($expr, $collection) foreach ($parsedExpression as $operand) { if ($this->_parser->isOperation($operand)) { $this->_checkOperandsSet($firstOperand, $secondOperand, $tmpResult, $result); - $result = $this->_operate($firstOperand, $secondOperand, $operand, $tmpResult, $result); + $result = $this->_operate($firstOperand, $secondOperand, $operand); $firstOperand = $secondOperand = null; } else { if (null === $firstOperand) { @@ -133,9 +133,9 @@ protected function _countExpr($expr, $collection) /** * Check if operands in not null and set operands values if they are empty * - * @param float|int &$firstOperand - * @param float|int &$secondOperand - * @param float|int &$tmpResult + * @param float|int $firstOperand + * @param float|int $secondOperand + * @param float|int $tmpResult * @param float|int $result * @return void */ diff --git a/app/code/Magento/Backend/Test/Unit/Helper/DataTest.php b/app/code/Magento/Backend/Test/Unit/Helper/DataTest.php index cfeed42b11ba1..58fec039a90fe 100644 --- a/app/code/Magento/Backend/Test/Unit/Helper/DataTest.php +++ b/app/code/Magento/Backend/Test/Unit/Helper/DataTest.php @@ -42,7 +42,6 @@ protected function setUp(): void $this->createMock(Auth::class), $this->_frontResolverMock, $this->createMock(Random::class), - $this->getMockForAbstractClass(RequestInterface::class) ); } diff --git a/app/code/Magento/Catalog/Block/Product/View/Details.php b/app/code/Magento/Catalog/Block/Product/View/Details.php index 67303d177e71e..8a569c268dd1d 100644 --- a/app/code/Magento/Catalog/Block/Product/View/Details.php +++ b/app/code/Magento/Catalog/Block/Product/View/Details.php @@ -27,10 +27,11 @@ class Details extends \Magento\Framework\View\Element\Template * * @return array * @since 103.0.1 + * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ public function getGroupSortedChildNames(string $groupName, string $callback): array { - $groupChildNames = $this->getGroupChildNames($groupName, $callback); + $groupChildNames = $this->getGroupChildNames($groupName); $layout = $this->getLayout(); $childNamesSortOrder = []; diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Category/Tree.php b/app/code/Magento/Catalog/Model/ResourceModel/Category/Tree.php index 972f11db7aae3..bcfa6ba3f8d0c 100644 --- a/app/code/Magento/Catalog/Model/ResourceModel/Category/Tree.php +++ b/app/code/Magento/Catalog/Model/ResourceModel/Category/Tree.php @@ -24,6 +24,11 @@ class Tree extends Dbp const LEVEL_FIELD = 'level'; + /** + * @var array + */ + private $_inactiveItems; + /** * @var \Magento\Framework\Event\ManagerInterface */ @@ -290,7 +295,7 @@ protected function _getDisabledIds($collection, $allIds) foreach ($allIds as $id) { $parents = $this->getNodeById($id)->getPath(); foreach ($parents as $parent) { - if (!$this->_getItemIsActive($parent->getId(), $storeId)) { + if (!$this->_getItemIsActive($parent->getId())) { $disabledIds[] = $id; continue; } @@ -680,6 +685,8 @@ public function getExistingCategoryIdsBySpecifiedIds($ids) } /** + * Get entity methadata pool. + * * @return \Magento\Framework\EntityManager\MetadataPool */ private function getMetadataPool() diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product/Action.php b/app/code/Magento/Catalog/Model/ResourceModel/Product/Action.php index 71ab9413a0d09..85f6269f63af0 100644 --- a/app/code/Magento/Catalog/Model/ResourceModel/Product/Action.php +++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/Action.php @@ -160,7 +160,7 @@ protected function _saveAttributeValue($object, $attribute, $value) $storeId = (int) $this->_storeManager->getStore($object->getStoreId())->getId(); $table = $attribute->getBackend()->getTable(); - $entityId = $this->resolveEntityId($object->getId(), $table); + $entityId = $this->resolveEntityId($object->getId()); /** * If we work in single store mode all values should be saved just diff --git a/app/code/Magento/Catalog/Test/Unit/Model/Product/Attribute/RepositoryTest.php b/app/code/Magento/Catalog/Test/Unit/Model/Product/Attribute/RepositoryTest.php index 0c2e5f361413e..673e12a5b42b5 100644 --- a/app/code/Magento/Catalog/Test/Unit/Model/Product/Attribute/RepositoryTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Model/Product/Attribute/RepositoryTest.php @@ -83,11 +83,6 @@ class RepositoryTest extends TestCase */ protected $searchResultMock; - /** - * @var AttributeOptionManagementInterface|MockObject - */ - private $optionManagementMock; - /** * @inheritdoc */ @@ -116,8 +111,6 @@ protected function setUp(): void ['getItems', 'getSearchCriteria', 'getTotalCount', 'setItems', 'setSearchCriteria', 'setTotalCount'] ) ->getMockForAbstractClass(); - $this->optionManagementMock = - $this->getMockForAbstractClass(ProductAttributeOptionManagementInterface::class); $this->model = new Repository( $this->attributeResourceMock, @@ -126,8 +119,7 @@ protected function setUp(): void $this->eavAttributeRepositoryMock, $this->eavConfigMock, $this->validatorFactoryMock, - $this->searchCriteriaBuilderMock, - $this->optionManagementMock + $this->searchCriteriaBuilderMock ); } @@ -291,7 +283,6 @@ public function testSaveDoesNotSaveAttributeOptionsIfOptionsAreAbsentInPayload() // Attribute code must not be changed after attribute creation $attributeMock->expects($this->once())->method('setAttributeCode')->with($attributeCode); $this->attributeResourceMock->expects($this->once())->method('save')->with($attributeMock); - $this->optionManagementMock->expects($this->never())->method('add'); $this->model->save($attributeMock); } diff --git a/app/code/Magento/Catalog/Test/Unit/Model/Product/Option/Validator/SelectTest.php b/app/code/Magento/Catalog/Test/Unit/Model/Product/Option/Validator/SelectTest.php index 11412324b8363..d10f4931a928f 100644 --- a/app/code/Magento/Catalog/Test/Unit/Model/Product/Option/Validator/SelectTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Model/Product/Option/Validator/SelectTest.php @@ -66,7 +66,7 @@ protected function setUp(): void ]; $configMock->expects($this->once())->method('getAll')->willReturn($config); $methods = ['getTitle', 'getType', 'getPriceType', 'getPrice', 'getData']; - $this->valueMock = $this->createPartialMock(Option::class, $methods, []); + $this->valueMock = $this->createPartialMock(Option::class, $methods); $this->validator = new Select( $configMock, $priceConfigMock, diff --git a/app/code/Magento/Catalog/Test/Unit/Model/Product/Price/PricePersistenceTest.php b/app/code/Magento/Catalog/Test/Unit/Model/Product/Price/PricePersistenceTest.php index cdd5f4d91b653..6f70f7973c10c 100644 --- a/app/code/Magento/Catalog/Test/Unit/Model/Product/Price/PricePersistenceTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Model/Product/Price/PricePersistenceTest.php @@ -301,7 +301,7 @@ public function testDeleteWithException() ->willReturn($idsBySku); $this->attributeRepository->expects($this->once())->method('get')->willReturn($this->productAttribute); $this->productAttribute->expects($this->once())->method('getAttributeId')->willReturn($attributeId); - $this->attributeResource->expects($this->atLeastOnce(2))->method('getConnection') + $this->attributeResource->expects($this->atLeastOnce())->method('getConnection') ->willReturn($this->connection); $this->connection->expects($this->once())->method('beginTransaction')->willReturnSelf(); $this->attributeResource diff --git a/app/code/Magento/CatalogInventory/Model/Quote/Item/QuantityValidator.php b/app/code/Magento/CatalogInventory/Model/Quote/Item/QuantityValidator.php index 2ccb726f2c625..317a573a653e9 100644 --- a/app/code/Magento/CatalogInventory/Model/Quote/Item/QuantityValidator.php +++ b/app/code/Magento/CatalogInventory/Model/Quote/Item/QuantityValidator.php @@ -193,7 +193,7 @@ public function validate(Observer $observer) $option->setHasError(true); //Setting this to false, so no error statuses are cleared $removeError = false; - $this->addErrorInfoToQuote($result, $quoteItem, $removeError); + $this->addErrorInfoToQuote($result, $quoteItem); } } if ($removeError) { diff --git a/app/code/Magento/CatalogInventory/Test/Unit/Model/Plugin/AfterProductLoadTest.php b/app/code/Magento/CatalogInventory/Test/Unit/Model/Plugin/AfterProductLoadTest.php index d0e34c89b897c..215b31e1b4dad 100644 --- a/app/code/Magento/CatalogInventory/Test/Unit/Model/Plugin/AfterProductLoadTest.php +++ b/app/code/Magento/CatalogInventory/Test/Unit/Model/Plugin/AfterProductLoadTest.php @@ -30,11 +30,6 @@ class AfterProductLoadTest extends TestCase */ protected $productMock; - /** - * @var ProductExtensionFactory|MockObject - */ - protected $productExtensionFactoryMock; - /** * @var ProductExtensionInterface|MockObject */ @@ -43,16 +38,9 @@ class AfterProductLoadTest extends TestCase protected function setUp(): void { $stockRegistryMock = $this->getMockForAbstractClass(StockRegistryInterface::class); - $this->productExtensionFactoryMock = $this->getMockBuilder( - ProductExtensionFactory::class - ) - ->setMethods(['create']) - ->disableOriginalConstructor() - ->getMock(); $this->plugin = new AfterProductLoad( - $stockRegistryMock, - $this->productExtensionFactoryMock + $stockRegistryMock ); $productId = 5494; @@ -88,8 +76,6 @@ public function testAfterLoad() $this->productMock->expects($this->once()) ->method('getExtensionAttributes') ->willReturn($this->productExtensionMock); - $this->productExtensionFactoryMock->expects($this->never()) - ->method('create'); $this->assertEquals( $this->productMock, diff --git a/app/code/Magento/Cron/Test/Unit/Observer/ProcessCronQueueObserverTest.php b/app/code/Magento/Cron/Test/Unit/Observer/ProcessCronQueueObserverTest.php index e1e28ff6f06a3..a48a3dd76b884 100644 --- a/app/code/Magento/Cron/Test/Unit/Observer/ProcessCronQueueObserverTest.php +++ b/app/code/Magento/Cron/Test/Unit/Observer/ProcessCronQueueObserverTest.php @@ -679,6 +679,8 @@ public function dispatchExceptionInCallbackDataProvider() /** * Test case, successfully run job + * + * @SuppressWarnings(PHPMD.ExcessiveMethodLength) */ public function testDispatchRunJob() { @@ -764,7 +766,7 @@ function ($callback) { ->method('getCollection')->willReturn($this->scheduleCollectionMock); $scheduleMock->expects($this->any()) ->method('getResource')->willReturn($this->scheduleResourceMock); - $this->scheduleFactoryMock->expects($this->once(2)) + $this->scheduleFactoryMock->expects($this->once()) ->method('create')->willReturn($scheduleMock); $testCronJob = $this->getMockBuilder('CronJob') diff --git a/app/code/Magento/Customer/Model/Address.php b/app/code/Magento/Customer/Model/Address.php index 241abbb06f8a1..c89821d5c984a 100644 --- a/app/code/Magento/Customer/Model/Address.php +++ b/app/code/Magento/Customer/Model/Address.php @@ -15,8 +15,8 @@ * Customer address model * * @api - * @method int getParentId() getParentId() - * @method \Magento\Customer\Model\Address setParentId() setParentId(int $parentId) + * @method int getParentId() + * @method \Magento\Customer\Model\Address setParentId(int $parentId) * @SuppressWarnings(PHPMD.CouplingBetweenObjects) * @since 100.0.2 */ diff --git a/app/code/Magento/Customer/Model/ResourceModel/Customer.php b/app/code/Magento/Customer/Model/ResourceModel/Customer.php index 1477287f79f4b..6ebd6b9410a0c 100644 --- a/app/code/Magento/Customer/Model/ResourceModel/Customer.php +++ b/app/code/Magento/Customer/Model/ResourceModel/Customer.php @@ -85,7 +85,7 @@ public function __construct( $this->accountConfirmation = $accountConfirmation ?: ObjectManager::getInstance() ->get(AccountConfirmation::class); $this->setType('customer'); - $this->setConnection('customer_read', 'customer_write'); + $this->setConnection('customer_read'); $this->storeManager = $storeManager; } diff --git a/app/code/Magento/Customer/Test/Unit/Controller/Adminhtml/Index/ViewfileTest.php b/app/code/Magento/Customer/Test/Unit/Controller/Adminhtml/Index/ViewfileTest.php index 8cb5957cbd672..ea8a37127e85d 100644 --- a/app/code/Magento/Customer/Test/Unit/Controller/Adminhtml/Index/ViewfileTest.php +++ b/app/code/Magento/Customer/Test/Unit/Controller/Adminhtml/Index/ViewfileTest.php @@ -257,11 +257,7 @@ public function testExecuteInvalidFile() $this->urlDecoderMock->expects($this->once())->method('decode')->with($decodedFile)->willReturn($file); $fileFactoryMock = $this->createMock( - FileFactory::class, - [], - [], - '', - false + FileFactory::class ); $controller = $this->objectManager->getObject( diff --git a/app/code/Magento/Downloadable/Test/Unit/Model/Product/TypeHandler/SampleTest.php b/app/code/Magento/Downloadable/Test/Unit/Model/Product/TypeHandler/SampleTest.php index a77ebf9ba7edb..675f900a8b9c1 100644 --- a/app/code/Magento/Downloadable/Test/Unit/Model/Product/TypeHandler/SampleTest.php +++ b/app/code/Magento/Downloadable/Test/Unit/Model/Product/TypeHandler/SampleTest.php @@ -96,7 +96,7 @@ protected function setUp(): void */ public function testSave($product, array $data, array $modelData) { - $link = $this->createSampleModel($product, $modelData, true); + $link = $this->createSampleModel($product, $modelData); $this->metadataMock->expects($this->once())->method('getLinkField')->willReturn('id'); $this->sampleFactory->expects($this->once()) ->method('create') diff --git a/app/code/Magento/Eav/Test/Unit/Model/Entity/AttributeLoaderTest.php b/app/code/Magento/Eav/Test/Unit/Model/Entity/AttributeLoaderTest.php index af6ce1bca8f58..6c3c61c4a6ec6 100644 --- a/app/code/Magento/Eav/Test/Unit/Model/Entity/AttributeLoaderTest.php +++ b/app/code/Magento/Eav/Test/Unit/Model/Entity/AttributeLoaderTest.php @@ -48,13 +48,13 @@ class AttributeLoaderTest extends TestCase protected function setUp(): void { - $this->configMock = $this->createMock(Config::class, [], [], '', false); + $this->configMock = $this->createMock(Config::class); $this->objectManagerMock = $this->getMockBuilder(ObjectManagerInterface::class) ->setMethods(['create']) ->disableOriginalConstructor() ->getMockForAbstractClass(); - $this->entityMock = $this->createMock(AbstractEntity::class, [], [], '', false); - $this->entityTypeMock = $this->createMock(Type::class, [], [], '', false); + $this->entityMock = $this->createMock(AbstractEntity::class); + $this->entityTypeMock = $this->createMock(Type::class); $this->attributeLoader = new AttributeLoader( $this->configMock, $this->objectManagerMock diff --git a/app/code/Magento/Integration/Block/Adminhtml/Widget/Grid/Column/Renderer/Link.php b/app/code/Magento/Integration/Block/Adminhtml/Widget/Grid/Column/Renderer/Link.php index 9a406945f3b96..76667f6060853 100644 --- a/app/code/Magento/Integration/Block/Adminhtml/Widget/Grid/Column/Renderer/Link.php +++ b/app/code/Magento/Integration/Block/Adminhtml/Widget/Grid/Column/Renderer/Link.php @@ -81,8 +81,9 @@ public function isDisabled() } /** - * Return URL pattern for action associated with the link e.g. "(star)(slash)(star)(slash)activate" -> - * will be translated to http://.../admin/integration/activate/id/X + * Return URL pattern for action associated with the link e.g. "(star)(slash)(star)(slash)activate" + * + * Will be translated to http://.../admin/integration/activate/id/X * * @return string */ @@ -164,6 +165,6 @@ protected function _getDataAttributes() */ protected function _getUrl(DataObject $row) { - return $this->isDisabled($row) ? '#' : $this->getUrl($this->getUrlPattern(), ['id' => $row->getId()]); + return $this->isDisabled() ? '#' : $this->getUrl($this->getUrlPattern(), ['id' => $row->getId()]); } } diff --git a/app/code/Magento/Integration/Model/Oauth/Consumer.php b/app/code/Magento/Integration/Model/Oauth/Consumer.php index 4442be310b967..b436defcd0cfa 100644 --- a/app/code/Magento/Integration/Model/Oauth/Consumer.php +++ b/app/code/Magento/Integration/Model/Oauth/Consumer.php @@ -13,15 +13,15 @@ * @api * @author Magento Core Team <core@magentocommerce.com> * @method string getName() - * @method Consumer setName() setName(string $name) - * @method Consumer setKey() setKey(string $key) - * @method Consumer setSecret() setSecret(string $secret) - * @method Consumer setCallbackUrl() setCallbackUrl(string $url) - * @method Consumer setCreatedAt() setCreatedAt(string $date) + * @method Consumer setName(string $name) + * @method Consumer setKey(string $key) + * @method Consumer setSecret(string $secret) + * @method Consumer setCallbackUrl(string $url) + * @method Consumer setCreatedAt(string $date) * @method string getUpdatedAt() - * @method Consumer setUpdatedAt() setUpdatedAt(string $date) + * @method Consumer setUpdatedAt(string $date) * @method string getRejectedCallbackUrl() - * @method Consumer setRejectedCallbackUrl() setRejectedCallbackUrl(string $rejectedCallbackUrl) + * @method Consumer setRejectedCallbackUrl(string $rejectedCallbackUrl) * @since 100.0.2 */ class Consumer extends \Magento\Framework\Model\AbstractModel implements ConsumerInterface @@ -112,7 +112,7 @@ public function beforeSave() } /** - * {@inheritdoc} + * @inheritDoc */ public function validate() { @@ -158,7 +158,7 @@ public function loadByKey($key) } /** - * {@inheritdoc} + * @inheritDoc */ public function getKey() { @@ -166,7 +166,7 @@ public function getKey() } /** - * {@inheritdoc} + * @inheritDoc */ public function getSecret() { @@ -174,7 +174,7 @@ public function getSecret() } /** - * {@inheritdoc} + * @inheritDoc */ public function getCallbackUrl() { @@ -182,7 +182,7 @@ public function getCallbackUrl() } /** - * {@inheritdoc} + * @inheritDoc */ public function getCreatedAt() { @@ -190,7 +190,7 @@ public function getCreatedAt() } /** - * {@inheritdoc} + * @inheritDoc */ public function isValidForTokenExchange() { diff --git a/app/code/Magento/Integration/Model/Oauth/Token.php b/app/code/Magento/Integration/Model/Oauth/Token.php index 53d9ec300a862..7a3580b8bd402 100644 --- a/app/code/Magento/Integration/Model/Oauth/Token.php +++ b/app/code/Magento/Integration/Model/Oauth/Token.php @@ -15,27 +15,27 @@ * * @method string getName() Consumer name (joined from consumer table) * @method int getConsumerId() - * @method Token setConsumerId() setConsumerId(int $consumerId) + * @method Token setConsumerId(int $consumerId) * @method int getAdminId() - * @method Token setAdminId() setAdminId(int $adminId) + * @method Token setAdminId(int $adminId) * @method int getCustomerId() - * @method Token setCustomerId() setCustomerId(int $customerId) + * @method Token setCustomerId(int $customerId) * @method int getUserType() - * @method Token setUserType() setUserType(int $userType) + * @method Token setUserType(int $userType) * @method string getType() - * @method Token setType() setType(string $type) + * @method Token setType(string $type) * @method string getCallbackUrl() - * @method Token setCallbackUrl() setCallbackUrl(string $callbackUrl) + * @method Token setCallbackUrl(string $callbackUrl) * @method string getCreatedAt() - * @method Token setCreatedAt() setCreatedAt(string $createdAt) + * @method Token setCreatedAt(string $createdAt) * @method string getToken() - * @method Token setToken() setToken(string $token) + * @method Token setToken(string $token) * @method string getSecret() - * @method Token setSecret() setSecret(string $tokenSecret) + * @method Token setSecret(string $tokenSecret) * @method int getRevoked() - * @method Token setRevoked() setRevoked(int $revoked) + * @method Token setRevoked(int $revoked) * @method int getAuthorized() - * @method Token setAuthorized() setAuthorized(int $authorized) + * @method Token setAuthorized(int $authorized) * @SuppressWarnings(PHPMD.CouplingBetweenObjects) * @api * @since 100.0.2 @@ -200,7 +200,7 @@ public function createAdminToken($userId) public function createCustomerToken($userId) { $this->setCustomerId($userId); - return $this->saveAccessToken(UserContextInterface::USER_TYPE_CUSTOMER, $userId); + return $this->saveAccessToken(UserContextInterface::USER_TYPE_CUSTOMER); } /** diff --git a/app/code/Magento/Marketplace/Test/Unit/Controller/Partners/IndexTest.php b/app/code/Magento/Marketplace/Test/Unit/Controller/Partners/IndexTest.php index ae4042536eb0c..3f44c5616d597 100644 --- a/app/code/Magento/Marketplace/Test/Unit/Controller/Partners/IndexTest.php +++ b/app/code/Magento/Marketplace/Test/Unit/Controller/Partners/IndexTest.php @@ -93,7 +93,7 @@ public function getControllerIndexMock($methods = null) */ public function getLayoutFactoryMock($methods = null) { - return $this->createPartialMock(LayoutFactory::class, $methods, []); + return $this->createPartialMock(LayoutFactory::class, $methods); } /** @@ -109,7 +109,7 @@ public function getLayoutMock() */ public function getResponseMock($methods = null) { - return $this->createPartialMock(Response::class, $methods, []); + return $this->createPartialMock(Response::class, $methods); } /** @@ -117,7 +117,7 @@ public function getResponseMock($methods = null) */ public function getRequestMock($methods = null) { - return $this->createPartialMock(Http::class, $methods, []); + return $this->createPartialMock(Http::class, $methods); } /** diff --git a/app/code/Magento/Marketplace/Test/Unit/Model/PartnersTest.php b/app/code/Magento/Marketplace/Test/Unit/Model/PartnersTest.php index 76bb7be6e8b19..59928f40ca599 100644 --- a/app/code/Magento/Marketplace/Test/Unit/Model/PartnersTest.php +++ b/app/code/Magento/Marketplace/Test/Unit/Model/PartnersTest.php @@ -140,7 +140,7 @@ public function getPartnersBlockMock($methods = null) */ public function getPartnersModelMock($methods) { - return $this->createPartialMock(Partners::class, $methods, []); + return $this->createPartialMock(Partners::class, $methods); } /** @@ -150,7 +150,7 @@ public function getPartnersModelMock($methods) */ public function getCurlMock($methods) { - return $this->createPartialMock(Curl::class, $methods, []); + return $this->createPartialMock(Curl::class, $methods); } /** @@ -160,6 +160,6 @@ public function getCurlMock($methods) */ public function getCacheMock($methods) { - return $this->createPartialMock(Cache::class, $methods, []); + return $this->createPartialMock(Cache::class, $methods); } } diff --git a/app/code/Magento/NewRelicReporting/Test/Unit/Model/Cron/ReportNewRelicCronTest.php b/app/code/Magento/NewRelicReporting/Test/Unit/Model/Cron/ReportNewRelicCronTest.php index 2a86a9d3018dd..b7ab4bda8da53 100644 --- a/app/code/Magento/NewRelicReporting/Test/Unit/Model/Cron/ReportNewRelicCronTest.php +++ b/app/code/Magento/NewRelicReporting/Test/Unit/Model/Cron/ReportNewRelicCronTest.php @@ -17,7 +17,6 @@ use Magento\NewRelicReporting\Model\Module\Collect; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; -use Psr\Log\LoggerInterface; class ReportNewRelicCronTest extends TestCase { @@ -61,11 +60,6 @@ class ReportNewRelicCronTest extends TestCase */ protected $deploymentsModel; - /** - * @var LoggerInterface|MockObject - */ - private $logger; - /** * Setup * @@ -117,15 +111,13 @@ protected function setUp(): void $this->deploymentsFactory->expects($this->any()) ->method('create') ->willReturn($this->deploymentsModel); - $this->logger = $this->getMockForAbstractClass(LoggerInterface::class); $this->model = new ReportNewRelicCron( $this->config, $this->collect, $this->counter, $this->cronEventFactory, - $this->deploymentsFactory, - $this->logger + $this->deploymentsFactory ); } @@ -215,7 +207,6 @@ public function testReportNewRelicCronRequestFailed() ->method('sendRequest'); $this->cronEventModel->expects($this->once())->method('sendRequest')->willThrowException(new \Exception()); - $this->logger->expects($this->never())->method('critical'); $this->deploymentsModel->expects($this->any()) ->method('setDeployment'); diff --git a/app/code/Magento/Newsletter/Model/ResourceModel/Subscriber/Grid/Collection.php b/app/code/Magento/Newsletter/Model/ResourceModel/Subscriber/Grid/Collection.php index e16effb8c7e12..d0da06553a5f3 100644 --- a/app/code/Magento/Newsletter/Model/ResourceModel/Subscriber/Grid/Collection.php +++ b/app/code/Magento/Newsletter/Model/ResourceModel/Subscriber/Grid/Collection.php @@ -17,7 +17,8 @@ class Collection extends \Magento\Newsletter\Model\ResourceModel\Subscriber\Coll protected function _initSelect() { parent::_initSelect(); - $this->showCustomerInfo(true)->addSubscriberTypeField()->showStoreInfo(); + $this->showCustomerInfo()->addSubscriberTypeField()->showStoreInfo(); + return $this; } } diff --git a/app/code/Magento/ProductVideo/Test/Unit/Controller/Adminhtml/Product/Gallery/RetrieveImageTest.php b/app/code/Magento/ProductVideo/Test/Unit/Controller/Adminhtml/Product/Gallery/RetrieveImageTest.php index 519a8cba014f2..f3d21e97eb9ee 100644 --- a/app/code/Magento/ProductVideo/Test/Unit/Controller/Adminhtml/Product/Gallery/RetrieveImageTest.php +++ b/app/code/Magento/ProductVideo/Test/Unit/Controller/Adminhtml/Product/Gallery/RetrieveImageTest.php @@ -181,20 +181,8 @@ public function testExecuteInvalidFileImage() $this->request->expects($this->any())->method('getParam')->willReturn( 'https://example.com/test.jpg' ); - $readInterface = $this->createMock( - ReadInterface::class, - [], - [], - '', - false - ); - $writeInterface = $this->createMock( - WriteInterface::class, - [], - [], - '', - false - ); + $readInterface = $this->createMock(ReadInterface::class); + $writeInterface = $this->createMock(WriteInterface::class); $this->filesystemMock->expects($this->any())->method('getDirectoryRead')->willReturn($readInterface); $readInterface->expects($this->any())->method('getAbsolutePath')->willReturn(''); $this->abstractAdapter->expects($this->any()) @@ -217,20 +205,8 @@ public function testExecuteInvalidFileType() $this->request->expects($this->any())->method('getParam')->willReturn( 'https://example.com/test.php' ); - $readInterface = $this->createMock( - ReadInterface::class, - [], - [], - '', - false - ); - $writeInterface = $this->createMock( - WriteInterface::class, - [], - [], - '', - false - ); + $readInterface = $this->createMock(ReadInterface::class); + $writeInterface = $this->createMock(WriteInterface::class); $this->filesystemMock->expects($this->any())->method('getDirectoryRead')->willReturn($readInterface); $readInterface->expects($this->any())->method('getAbsolutePath')->willReturn(''); $this->abstractAdapter->expects($this->never())->method('validateUploadFile'); diff --git a/app/code/Magento/Quote/Model/QuoteIdMask.php b/app/code/Magento/Quote/Model/QuoteIdMask.php index 47b02db7650df..8fa0b1fbba80c 100644 --- a/app/code/Magento/Quote/Model/QuoteIdMask.php +++ b/app/code/Magento/Quote/Model/QuoteIdMask.php @@ -10,7 +10,7 @@ * QuoteIdMask model * * @method string getMaskedId() - * @method QuoteIdMask setMaskedId() + * @method QuoteIdMask setMaskedId(string $id) */ class QuoteIdMask extends \Magento\Framework\Model\AbstractModel { diff --git a/app/code/Magento/Reports/Model/ResourceModel/Order/Collection.php b/app/code/Magento/Reports/Model/ResourceModel/Order/Collection.php index 44571550459c2..47583c27370fb 100644 --- a/app/code/Magento/Reports/Model/ResourceModel/Order/Collection.php +++ b/app/code/Magento/Reports/Model/ResourceModel/Order/Collection.php @@ -159,7 +159,7 @@ public function prepareSummary($range, $customStart, $customEnd, $isFilter = 0) if ($this->_isLive) { $this->_prepareSummaryLive($range, $customStart, $customEnd, $isFilter); } else { - $this->_prepareSummaryAggregated($range, $customStart, $customEnd, $isFilter); + $this->_prepareSummaryAggregated($range, $customStart, $customEnd); } return $this; diff --git a/app/code/Magento/Review/Block/Adminhtml/Grid.php b/app/code/Magento/Review/Block/Adminhtml/Grid.php index e20cb7554e094..798d6ae7148af 100644 --- a/app/code/Magento/Review/Block/Adminhtml/Grid.php +++ b/app/code/Magento/Review/Block/Adminhtml/Grid.php @@ -10,12 +10,11 @@ /** * Adminhtml reviews grid * - * @method int getProductId() getProductId() - * @method \Magento\Review\Block\Adminhtml\Grid setProductId() setProductId(int $productId) - * @method int getCustomerId() getCustomerId() - * @method \Magento\Review\Block\Adminhtml\Grid setCustomerId() setCustomerId(int $customerId) - * @method \Magento\Review\Block\Adminhtml\Grid setMassactionIdFieldOnlyIndexValue() - * setMassactionIdFieldOnlyIndexValue(bool $onlyIndex) + * @method int getProductId() + * @method Grid setProductId(int $productId) + * @method int getCustomerId() + * @method Grid setCustomerId(int $customerId) + * @method Grid setMassactionIdFieldOnlyIndexValue(bool $onlyIndex) */ class Grid extends \Magento\Backend\Block\Widget\Grid\Extended { @@ -110,9 +109,7 @@ protected function _afterLoadCollection() } /** - * Prepare collection - * - * @return \Magento\Review\Block\Adminhtml\Grid + * @inheritDoc */ protected function _prepareCollection() { diff --git a/app/code/Magento/Sales/Model/Order.php b/app/code/Magento/Sales/Model/Order.php index 0af42b0a99d09..7cb99bc75ba03 100644 --- a/app/code/Magento/Sales/Model/Order.php +++ b/app/code/Magento/Sales/Model/Order.php @@ -42,17 +42,17 @@ * * @api * @method int getGiftMessageId() - * @method \Magento\Sales\Model\Order setGiftMessageId(int $value) + * @method Order setGiftMessageId(int $value) * @method bool hasBillingAddressId() - * @method \Magento\Sales\Model\Order unsBillingAddressId() + * @method Order unsBillingAddressId() * @method bool hasShippingAddressId() - * @method \Magento\Sales\Model\Order unsShippingAddressId() + * @method Order unsShippingAddressId() * @method int getShippigAddressId() * @method bool hasCustomerNoteNotify() * @method bool hasForcedCanCreditmemo() * @method bool getIsInProcess() * @method \Magento\Customer\Model\Customer|null getCustomer() - * @method \Magento\Sales\Model\Order setSendEmail(bool $value) + * @method Order setSendEmail(bool $value) * @SuppressWarnings(PHPMD.ExcessivePublicCount) * @SuppressWarnings(PHPMD.TooManyFields) * @SuppressWarnings(PHPMD.ExcessiveClassComplexity) @@ -494,7 +494,7 @@ public function setCanSendNewEmailFlag($flag) * Load order by system increment identifier * * @param string $incrementId - * @return \Magento\Sales\Model\Order + * @return Order */ public function loadByIncrementId($incrementId) { @@ -506,7 +506,7 @@ public function loadByIncrementId($incrementId) * * @param string $incrementId * @param string $storeId - * @return \Magento\Sales\Model\Order + * @return Order */ public function loadByIncrementIdAndStoreId($incrementId, $storeId) { @@ -1818,7 +1818,7 @@ public function getTotalDue() $total = $this->priceCurrency->round($total); return max($total, 0); } - + /** * Retrieve order total due value * @@ -2052,7 +2052,7 @@ public function getStoreGroupName() { $storeId = $this->getStoreId(); if ($storeId === null) { - return $this->getStoreName(1); + return $this->getStoreName(); } return $this->getStore()->getGroup()->getName(); } diff --git a/app/code/Magento/Sales/Test/Unit/Controller/Adminhtml/Order/ViewTest.php b/app/code/Magento/Sales/Test/Unit/Controller/Adminhtml/Order/ViewTest.php index 69aa43ceca1e0..2ae5be872e6c4 100644 --- a/app/code/Magento/Sales/Test/Unit/Controller/Adminhtml/Order/ViewTest.php +++ b/app/code/Magento/Sales/Test/Unit/Controller/Adminhtml/Order/ViewTest.php @@ -203,7 +203,7 @@ public function testExecute() $id = 111; $titlePart = '#111'; $this->initOrder(); - $this->initOrderSuccess($id); + $this->initOrderSuccess(); $this->prepareRedirect(); $this->initAction(); @@ -264,10 +264,9 @@ public function testExecuteNoOrder() */ public function testGlobalException() { - $id = 111; $exception = new \Exception(); $this->initOrder(); - $this->initOrderSuccess($id); + $this->initOrderSuccess(); $this->prepareRedirect(); $this->resultPageFactoryMock->expects($this->once()) diff --git a/app/code/Magento/Sales/Test/Unit/Helper/DataTest.php b/app/code/Magento/Sales/Test/Unit/Helper/DataTest.php index 9b2f818aeebb3..75ab7ff0378da 100644 --- a/app/code/Magento/Sales/Test/Unit/Helper/DataTest.php +++ b/app/code/Magento/Sales/Test/Unit/Helper/DataTest.php @@ -9,8 +9,6 @@ use Magento\Framework\App\Config\ScopeConfigInterface; use Magento\Framework\App\Helper\Context; -use Magento\Framework\App\State; -use Magento\Framework\Pricing\PriceCurrencyInterface; use Magento\Sales\Helper\Data; use Magento\Sales\Model\Order\Email\Container\CreditmemoCommentIdentity; use Magento\Sales\Model\Order\Email\Container\CreditmemoIdentity; @@ -21,7 +19,7 @@ use Magento\Sales\Model\Order\Email\Container\ShipmentCommentIdentity; use Magento\Sales\Model\Order\Email\Container\ShipmentIdentity; use Magento\Store\Model\ScopeInterface; -use Magento\Store\Model\StoreManagerInterface; +use Magento\Store\Model\Store; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; @@ -41,7 +39,7 @@ class DataTest extends TestCase protected $scopeConfigMock; /** - * @var MockObject|\Magento\Sales\Model\Store + * @var MockObject|Store */ protected $storeMock; @@ -60,26 +58,9 @@ protected function setUp(): void ->method('getScopeConfig') ->willReturn($this->scopeConfigMock); - $storeManagerMock = $this->getMockBuilder(StoreManagerInterface::class) - ->disableOriginalConstructor() - ->getMockForAbstractClass(); - - $appStateMock = $this->getMockBuilder(State::class) - ->disableOriginalConstructor() - ->getMock(); - - $pricingCurrencyMock = $this->getMockBuilder(PriceCurrencyInterface::class) - ->disableOriginalConstructor() - ->getMockForAbstractClass(); - - $this->helper = new Data( - $contextMock, - $storeManagerMock, - $appStateMock, - $pricingCurrencyMock - ); + $this->helper = new Data($contextMock); - $this->storeMock = $this->getMockBuilder(\Magento\Sales\Model\Store::class) + $this->storeMock = $this->getMockBuilder(Store::class) ->disableOriginalConstructor() ->getMock(); } diff --git a/app/code/Magento/Swatches/Test/Unit/Helper/DataTest.php b/app/code/Magento/Swatches/Test/Unit/Helper/DataTest.php index 880fbca71dce3..4c1ceecf153dd 100644 --- a/app/code/Magento/Swatches/Test/Unit/Helper/DataTest.php +++ b/app/code/Magento/Swatches/Test/Unit/Helper/DataTest.php @@ -255,7 +255,7 @@ public function dataForAssembleEavAttribute() */ public function testLoadFirstVariationWithSwatchImage($imageTypes, $expected, $requiredAttributes) { - $this->getSwatchAttributes($this->productMock); + $this->getSwatchAttributes(); $this->getUsedProducts($imageTypes + $requiredAttributes, $imageTypes); $result = $this->swatchHelperObject->loadFirstVariationWithSwatchImage($this->productMock, $requiredAttributes); @@ -295,16 +295,13 @@ public function dataForVariationWithSwatchImage() ]; } - /** - * @dataProvider dataForCreateSwatchProductByFallback - */ - public function testLoadVariationByFallback($product) + public function testLoadVariationByFallback() { $metadataMock = $this->getMockForAbstractClass(EntityMetadataInterface::class); $this->metaDataPoolMock->expects($this->once())->method('getMetadata')->willReturn($metadataMock); $metadataMock->expects($this->once())->method('getLinkField')->willReturn('id'); - $this->getSwatchAttributes($product); + $this->getSwatchAttributes(); $this->prepareVariationCollection(); @@ -321,7 +318,7 @@ public function testLoadVariationByFallback($product) */ public function testLoadFirstVariationWithImage($imageTypes, $expected, $requiredAttributes) { - $this->getSwatchAttributes($this->productMock); + $this->getSwatchAttributes(); $this->getUsedProducts($imageTypes + $requiredAttributes, $imageTypes); $result = $this->swatchHelperObject->loadFirstVariationWithImage($this->productMock, $requiredAttributes); @@ -592,23 +589,6 @@ public function dataForCreateSwatchProduct() ]; } - /** - * @return array - */ - public function dataForCreateSwatchProductByFallback() - { - $productMock = $this->createMock(Product::class); - - return [ - [ - 95, - ], - [ - $productMock, - ], - ]; - } - /** * @dataProvider dataForGettingSwatchAsArray */ diff --git a/app/code/Magento/Swatches/Test/Unit/Model/SwatchAttributeCodesTest.php b/app/code/Magento/Swatches/Test/Unit/Model/SwatchAttributeCodesTest.php index 29eb752bb3c57..c952cd3c2e6a2 100644 --- a/app/code/Magento/Swatches/Test/Unit/Model/SwatchAttributeCodesTest.php +++ b/app/code/Magento/Swatches/Test/Unit/Model/SwatchAttributeCodesTest.php @@ -90,17 +90,12 @@ public function testGetCodes($swatchAttributeCodesCache, $expected) ->withConsecutive( [ self::identicalTo( - ['a' => self::ATTRIBUTE_TABLE], - [ - 'attribute_id' => 'a.attribute_id', - 'attribute_code' => 'a.attribute_code', - ] + ['a' => self::ATTRIBUTE_TABLE] ) ], [ self::identicalTo( - ['o' => self::ATTRIBUTE_OPTION_TABLE], - ['attribute_id' => 'o.attribute_id'] + ['o' => self::ATTRIBUTE_OPTION_TABLE] ) ] ) diff --git a/app/code/Magento/Theme/Block/Adminhtml/System/Design/Theme/Edit/Tab/Css.php b/app/code/Magento/Theme/Block/Adminhtml/System/Design/Theme/Edit/Tab/Css.php index 2d55bbce2ec2c..5e3bb8774d246 100644 --- a/app/code/Magento/Theme/Block/Adminhtml/System/Design/Theme/Edit/Tab/Css.php +++ b/app/code/Magento/Theme/Block/Adminhtml/System/Design/Theme/Edit/Tab/Css.php @@ -194,8 +194,7 @@ protected function _addCustomCssFieldset() Storage::PARAM_CONTENT_TYPE => \Magento\Theme\Model\Wysiwyg\Storage::TYPE_IMAGE ] ) . "', null, null,'" . $this->escapeJs( - __('Upload Images'), - true + __('Upload Images') ) . "');", ] ); @@ -222,8 +221,7 @@ protected function _addCustomCssFieldset() Storage::PARAM_CONTENT_TYPE => \Magento\Theme\Model\Wysiwyg\Storage::TYPE_FONT ] ) . "', null, null,'" . $this->escapeJs( - __('Upload Fonts'), - true + __('Upload Fonts') ) . "');", ] ); diff --git a/app/code/Magento/Theme/Model/ResourceModel/Design/Config/Collection.php b/app/code/Magento/Theme/Model/ResourceModel/Design/Config/Collection.php index 3a19ff99a9270..6db521978cfab 100644 --- a/app/code/Magento/Theme/Model/ResourceModel/Design/Config/Collection.php +++ b/app/code/Magento/Theme/Model/ResourceModel/Design/Config/Collection.php @@ -77,12 +77,13 @@ protected function _afterLoad() 'value', $this->valueProcessor->process( $item->getData('value'), - $this->getData('scope'), - $this->getData('scope_id'), + $item->getData('scope'), + $item->getData('scope_id'), $item->getData('path') ) ); } - parent::_afterLoad(); + + return parent::_afterLoad(); } } diff --git a/app/code/Magento/Theme/Test/Unit/Controller/Adminhtml/System/Design/Theme/SaveTest.php b/app/code/Magento/Theme/Test/Unit/Controller/Adminhtml/System/Design/Theme/SaveTest.php index b2641d304fb84..f14b176e95e5c 100644 --- a/app/code/Magento/Theme/Test/Unit/Controller/Adminhtml/System/Design/Theme/SaveTest.php +++ b/app/code/Magento/Theme/Test/Unit/Controller/Adminhtml/System/Design/Theme/SaveTest.php @@ -57,7 +57,7 @@ public function testSaveAction() ->with('js_order') ->willReturn($jsOrder); - $this->_request->expects($this->once(5))->method('getPostValue')->willReturn(true); + $this->_request->expects($this->once())->method('getPostValue')->willReturn(true); $themeMock = $this->getMockBuilder(Theme::class) ->addMethods(['setCustomization']) diff --git a/app/code/Magento/Theme/view/frontend/templates/html/sections.phtml b/app/code/Magento/Theme/view/frontend/templates/html/sections.phtml index 7f1128b3b07c7..9c6cf4fa07cf2 100644 --- a/app/code/Magento/Theme/view/frontend/templates/html/sections.phtml +++ b/app/code/Magento/Theme/view/frontend/templates/html/sections.phtml @@ -11,12 +11,12 @@ $group = $block->getGroupName(); $groupCss = $block->getGroupCss(); ?> -<?php if ($detailedInfoGroup = $block->getGroupChildNames($group, 'getChildHtml')) :?> +<?php if ($detailedInfoGroup = $block->getGroupChildNames($group)):?> <div class="sections <?= $block->escapeHtmlAttr($groupCss) ?>"> <?php $layout = $block->getLayout(); ?> <div class="section-items <?= $block->escapeHtmlAttr($groupCss) ?>-items" data-mage-init='{"tabs":{"openedState":"active"}}'> - <?php foreach ($detailedInfoGroup as $name) :?> + <?php foreach ($detailedInfoGroup as $name):?> <?php $html = $layout->renderElement($name); if (!trim($html) && ($block->getUseForce() != true)) { diff --git a/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/Webapi/Adapter/Rest/DocumentationGenerator.php b/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/Webapi/Adapter/Rest/DocumentationGenerator.php index 32f7f4aa3a8a6..945dc935d440c 100644 --- a/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/Webapi/Adapter/Rest/DocumentationGenerator.php +++ b/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/Webapi/Adapter/Rest/DocumentationGenerator.php @@ -23,7 +23,7 @@ class DocumentationGenerator public function generateDocumentation($httpMethod, $resourcePath, $arguments, $response) { $content = $this->generateHtmlContent($httpMethod, $resourcePath, $arguments, $response); - $filePath = $this->generateFileName($resourcePath); + $filePath = $this->generateFileName(); if ($filePath === null) { return; } diff --git a/dev/tests/integration/testsuite/Magento/Framework/Cache/Backend/MongoDbTest.php b/dev/tests/integration/testsuite/Magento/Framework/Cache/Backend/MongoDbTest.php index f4cec6e80aa36..82512c0bd37ff 100644 --- a/dev/tests/integration/testsuite/Magento/Framework/Cache/Backend/MongoDbTest.php +++ b/dev/tests/integration/testsuite/Magento/Framework/Cache/Backend/MongoDbTest.php @@ -203,7 +203,7 @@ public function testSave() $actualData = $this->_model->load($cacheId); $this->assertEquals($data, $actualData); $actualMetadata = $this->_model->getMetadatas($cacheId); - $this->arrayHasKey('tags', $actualMetadata); + $this->arrayHasKey('tags'); $this->assertEquals($tags, $actualMetadata['tags']); } diff --git a/dev/tests/integration/testsuite/Magento/Framework/Session/ConfigTest.php b/dev/tests/integration/testsuite/Magento/Framework/Session/ConfigTest.php index bed9a33c73148..095e70a6d0e1a 100644 --- a/dev/tests/integration/testsuite/Magento/Framework/Session/ConfigTest.php +++ b/dev/tests/integration/testsuite/Magento/Framework/Session/ConfigTest.php @@ -12,8 +12,6 @@ use Magento\Framework\App\Filesystem\DirectoryList; - // @codingStandardsIgnoreEnd - /** * Mock ini_get global function * @@ -37,6 +35,8 @@ function ini_get($varName) return call_user_func_array('\ini_get', func_get_args()); } + // @codingStandardsIgnoreEnd + /** * @magentoAppIsolation enabled */ @@ -181,7 +181,7 @@ public function testSettingInvalidCookieLifetime() $model->setCookieLifetime('foobar_bogus'); $this->assertEquals($preVal, $model->getCookieLifetime()); } - + public function testSettingInvalidCookieLifetime2() { $model = $this->getModel(); @@ -193,8 +193,8 @@ public function testSettingInvalidCookieLifetime2() public function testWrongMethodCall() { $model = $this->getModel(); - $this->expectException( - '\BadMethodCallException', + $this->expectException(\BadMethodCallException::class); + $this->expectExceptionMessage( 'Method "methodThatNotExist" does not exist in Magento\Framework\Session\Config' ); $model->methodThatNotExist(); diff --git a/dev/tests/static/testsuite/Magento/Test/Legacy/ObsoleteCodeTest.php b/dev/tests/static/testsuite/Magento/Test/Legacy/ObsoleteCodeTest.php index 62baa3ab07568..cd1cb7bf14b98 100644 --- a/dev/tests/static/testsuite/Magento/Test/Legacy/ObsoleteCodeTest.php +++ b/dev/tests/static/testsuite/Magento/Test/Legacy/ObsoleteCodeTest.php @@ -169,7 +169,7 @@ public function testXmlFiles() $invoker( function ($file) { $content = file_get_contents($file); - $this->_testObsoleteClasses($content, $file); + $this->_testObsoleteClasses($content); $this->_testObsoleteNamespaces($content); $this->_testObsoletePaths($file); }, @@ -950,8 +950,10 @@ private function getBlacklistFiles($absolutePath = false) $appPath = BP; foreach ($blackList as $file) { if ($absolutePath) { + // phpcs:ignore $ignored = array_merge($ignored, glob($appPath . DIRECTORY_SEPARATOR . $file, GLOB_NOSORT)); } else { + // phpcs:ignore $ignored = array_merge($ignored, $this->processPattern($appPath, $file)); } } diff --git a/lib/internal/Magento/Framework/Backup/Filesystem.php b/lib/internal/Magento/Framework/Backup/Filesystem.php index 72996cdd28fda..299bf88c1c409 100644 --- a/lib/internal/Magento/Framework/Backup/Filesystem.php +++ b/lib/internal/Magento/Framework/Backup/Filesystem.php @@ -294,7 +294,7 @@ protected function _checkBackupsDir() } mkdir($backupsDir); - chmod($backupsDir); + chmod($backupsDir, 0755); } if (!is_writable($backupsDir)) { @@ -316,6 +316,8 @@ protected function _getTarTmpPath() } /** + * Get rollback FTP + * * @return Ftp * @deprecated 101.0.0 */ @@ -332,6 +334,8 @@ protected function getRollBackFtp() } /** + * Get rollback FS + * * @return Fs * @deprecated 101.0.0 */ diff --git a/lib/internal/Magento/Framework/DB/Tree.php b/lib/internal/Magento/Framework/DB/Tree.php index 6fbd014213bc8..3718c55f61c8a 100644 --- a/lib/internal/Magento/Framework/DB/Tree.php +++ b/lib/internal/Magento/Framework/DB/Tree.php @@ -93,6 +93,7 @@ public function __construct($config = []) // use an object from the registry? if (is_string($connection)) { + /** @phpstan-ignore-next-line */ $connection = \Zend::registry($connection); } @@ -358,6 +359,7 @@ public function appendChild($nodeId, $data) } catch (\Exception $e) { $this->_db->rollBack(); echo $e->getMessage(); + /** @phpstan-ignore-next-line */ echo $sql; exit; } @@ -833,6 +835,7 @@ public function moveNodes($eId, $pId, $aId = 0) if ($pId == 0) { $levelUp = 0; } else { + /** @phpstan-ignore-next-line */ $levelUp = $pInfo[$this->_level]; } @@ -844,15 +847,20 @@ public function moveNodes($eId, $pId, $aId = 0) $rightKeyNear = $this->_db->fetchOne('SELECT MAX(' . $this->_right . ') FROM ' . $this->_table); } elseif ($aId != 0 && $pId == $eInfo[$this->_pid]) { // if we have after ID + /** @phpstan-ignore-next-line */ $rightKeyNear = $aInfo[$this->_right]; + /** @phpstan-ignore-next-line */ $leftKeyNear = $aInfo[$this->_left]; } elseif ($aId == 0 && $pId == $eInfo[$this->_pid]) { // if we do not have after ID + /** @phpstan-ignore-next-line */ $rightKeyNear = $pInfo[$this->_left]; } elseif ($pId != $eInfo[$this->_pid]) { + /** @phpstan-ignore-next-line */ $rightKeyNear = $pInfo[$this->_right] - 1; } + /** @phpstan-ignore-next-line */ $skewLevel = $pInfo[$this->_level] - $eInfo[$this->_level] + 1; $skewTree = $eInfo[$this->_right] - $eInfo[$this->_left] + 1; @@ -996,12 +1004,14 @@ public function moveNodes($eId, $pId, $aId = 0) $this->_db->beginTransaction(); try { + /** @phpstan-ignore-next-line */ $this->_db->query($sql); $this->_db->commit(); } catch (\Exception $e) { $this->_db->rollBack(); echo $e->getMessage(); echo "<br>\r\n"; + /** @phpstan-ignore-next-line */ echo $sql; echo "<br>\r\n"; exit; @@ -1052,6 +1062,7 @@ public function getChildren($nodeId, $startLevel = 0, $endLevel = 0) exit; } + /** @phpstan-ignore-next-line */ $dbSelect = new Select($this->_db); $dbSelect->from( $this->_table @@ -1092,6 +1103,7 @@ public function getChildren($nodeId, $startLevel = 0, $endLevel = 0) */ public function getNode($nodeId) { + /** @phpstan-ignore-next-line */ $dbSelect = new Select($this->_db); $dbSelect->from($this->_table)->where($this->_table . '.' . $this->_id . ' >= :id'); diff --git a/lib/internal/Magento/Framework/Indexer/Test/Unit/IndexStructureTest.php b/lib/internal/Magento/Framework/Indexer/Test/Unit/IndexStructureTest.php index da286bd8cd6e8..c03875f0a2fd2 100644 --- a/lib/internal/Magento/Framework/Indexer/Test/Unit/IndexStructureTest.php +++ b/lib/internal/Magento/Framework/Indexer/Test/Unit/IndexStructureTest.php @@ -167,7 +167,7 @@ public function testCreateWithEmptyFields() ->method('resolve') ->with($index, $dimensions) ->willReturn($index . '_flat'); - $position = $this->mockFulltextTable($position, $expectedTable, true); + $position = $this->mockFulltextTable($position, $expectedTable); $this->mockFlatTable($position, $index . '_flat'); $this->target->create($index, $fields, $dimensions); diff --git a/lib/internal/Magento/Framework/Reflection/Test/Unit/TypeProcessorTest.php b/lib/internal/Magento/Framework/Reflection/Test/Unit/TypeProcessorTest.php index f1707fff7786b..a955e5cff2df8 100644 --- a/lib/internal/Magento/Framework/Reflection/Test/Unit/TypeProcessorTest.php +++ b/lib/internal/Magento/Framework/Reflection/Test/Unit/TypeProcessorTest.php @@ -221,9 +221,9 @@ public function testProcessSimpleTypeStringArrayToIntArray() */ public function testProcessSimpleTypeException($value, $type) { - $this->expectException( - SerializationException::class, - 'Invalid type for value: "' . $value . '". Expected Type: "' . $type . '"' + $this->expectException(SerializationException::class); + $this->expectExceptionMessage( + "The \"$value\" value's type is invalid. The \"$type\" type was expected. Verify and try again." ); $this->typeProcessor->processSimpleAndAnyType($value, $type); } diff --git a/lib/internal/Magento/Framework/TestFramework/Unit/Block/Adminhtml.php b/lib/internal/Magento/Framework/TestFramework/Unit/Block/Adminhtml.php index f8bb912ea6c85..5b86d46452e33 100644 --- a/lib/internal/Magento/Framework/TestFramework/Unit/Block/Adminhtml.php +++ b/lib/internal/Magento/Framework/TestFramework/Unit/Block/Adminhtml.php @@ -108,6 +108,7 @@ class Adminhtml extends \PHPUnit\Framework\TestCase protected $_formKey; /** + * @inheritDoc */ protected function setUp(): void { @@ -150,6 +151,7 @@ protected function setUp(): void [$this, 'translateCallback'] ); + /** @phpstan-ignore-next-line */ $this->_context = new \Magento\Backend\Block\Template\Context( $this->_requestMock, $this->_layoutMock, @@ -194,11 +196,10 @@ protected function _makeMock($className) /** * Sets up a stubbed method with specified behavior and expectations * - * @param \PHPUnit_Framework_MockObject_MockObject $object - * @param string $stubName - * @param mixed $return - * @param \PHPUnit\Framework\MockObject\Matcher\InvokedCount|null $expects - * + * @param \PHPUnit_Framework_MockObject_MockObject $object + * @param string $stubName + * @param mixed $return + * @param \PHPUnit\Framework\MockObject\Matcher\InvokedCount|null $expects * @return \PHPUnit\Framework\MockObject\Builder\InvocationMocker */ protected function _setStub( diff --git a/lib/internal/Magento/Framework/Url.php b/lib/internal/Magento/Framework/Url.php index 1d00b732c5795..d63632fafc7d3 100644 --- a/lib/internal/Magento/Framework/Url.php +++ b/lib/internal/Magento/Framework/Url.php @@ -935,7 +935,7 @@ private function createUrl($routePath = null, array $routeParams = null) if (is_string($query)) { $this->_setQuery($query); } elseif (is_array($query)) { - $this->addQueryParams($query, !empty($routeParams['_current'])); + $this->addQueryParams($query); } if ($query === false) { $this->addQueryParams([]); diff --git a/lib/internal/Magento/Framework/View/Context.php b/lib/internal/Magento/Framework/View/Context.php index 8503c48d135c2..f781dc40ba39c 100644 --- a/lib/internal/Magento/Framework/View/Context.php +++ b/lib/internal/Magento/Framework/View/Context.php @@ -27,6 +27,7 @@ * * @SuppressWarnings(PHPMD.TooManyFields) * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + * @SuppressWarnings(PHPMD.CookieAndSessionMisuse) * @api * @since 100.0.2 */ @@ -383,7 +384,7 @@ public function getFullActionName() public function getAcceptType() { // TODO: do intelligence here - $type = $this->getHeader('Accept', 'html'); + $type = $this->getHeader('Accept') ?: 'html'; if (strpos($type, 'json') !== false) { return 'json'; } elseif (strpos($type, 'soap') !== false) { @@ -486,7 +487,9 @@ protected function getPhysicalTheme(Design\ThemeInterface $theme) $result = $result->getParentTheme(); } if (!$result) { - throw new \Exception("Unable to find a physical ancestor for a theme '{$theme->getThemeTitle()}'."); + throw new \InvalidArgumentException( + "Unable to find a physical ancestor for a theme '{$theme->getThemeTitle()}'." + ); } return $result; } diff --git a/lib/internal/Magento/Framework/View/Test/Unit/Asset/ConfigTest.php b/lib/internal/Magento/Framework/View/Test/Unit/Asset/ConfigTest.php index 3f7a700ffb212..a3ded21f2c907 100644 --- a/lib/internal/Magento/Framework/View/Test/Unit/Asset/ConfigTest.php +++ b/lib/internal/Magento/Framework/View/Test/Unit/Asset/ConfigTest.php @@ -24,11 +24,6 @@ class ConfigTest extends BaseTestCase */ private $scopeConfigMock; - /** - * @var MockObject|State - */ - private $appStateMock; - /** * @var Config */ @@ -41,10 +36,7 @@ protected function setUp(): void { $this->scopeConfigMock = $this->getMockBuilder(ScopeConfigInterface::class) ->getMockForAbstractClass(); - $this->appStateMock = $this->getMockBuilder(State::class) - ->disableOriginalConstructor() - ->getMock(); - $this->model = new Config($this->scopeConfigMock, $this->appStateMock); + $this->model = new Config($this->scopeConfigMock); } /** diff --git a/lib/internal/Magento/Framework/View/Test/Unit/Element/Message/MessageConfigurationsPoolTest.php b/lib/internal/Magento/Framework/View/Test/Unit/Element/Message/MessageConfigurationsPoolTest.php index 5974042ef9777..467f9485f77b6 100644 --- a/lib/internal/Magento/Framework/View/Test/Unit/Element/Message/MessageConfigurationsPoolTest.php +++ b/lib/internal/Magento/Framework/View/Test/Unit/Element/Message/MessageConfigurationsPoolTest.php @@ -41,10 +41,8 @@ public function testGetMessageConfiguration() */ public function testConstructNoRendererException(array $configuration) { - static::expectException( - '\InvalidArgumentException', - 'Renderer should be defined.' - ); + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage('Renderer should be defined.'); new MessageConfigurationsPool($configuration); } @@ -67,10 +65,8 @@ public function wrongRenderersDataProvider() */ public function testConstructWrongDataException(array $configuration) { - static::expectException( - '\InvalidArgumentException', - 'Data should be of array type.' - ); + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage('Data should be of array type.'); new MessageConfigurationsPool($configuration); } diff --git a/setup/src/Magento/Setup/Test/Unit/Model/ModuleUninstallerTest.php b/setup/src/Magento/Setup/Test/Unit/Model/ModuleUninstallerTest.php index bc0050513ad85..b5e616a95437c 100644 --- a/setup/src/Magento/Setup/Test/Unit/Model/ModuleUninstallerTest.php +++ b/setup/src/Magento/Setup/Test/Unit/Model/ModuleUninstallerTest.php @@ -15,7 +15,6 @@ use Magento\Framework\Setup\Patch\PatchApplier; use Magento\Framework\Setup\UninstallInterface; use Magento\Setup\Model\ModuleContext; -use Magento\Setup\Model\ModuleRegistryUninstaller; use Magento\Setup\Model\ModuleUninstaller; use Magento\Setup\Model\ObjectManagerProvider; use Magento\Setup\Model\UninstallCollector; @@ -60,11 +59,6 @@ class ModuleUninstallerTest extends TestCase */ private $output; - /** - * @var MockObject|ModuleRegistryUninstaller - */ - private $moduleRegistryUninstaller; - /** * @var PatchApplier|MockObject */ @@ -72,7 +66,6 @@ class ModuleUninstallerTest extends TestCase protected function setUp(): void { - $this->moduleRegistryUninstaller = $this->createMock(ModuleRegistryUninstaller::class); $this->objectManager = $this->getMockForAbstractClass( ObjectManagerInterface::class, [], @@ -94,8 +87,7 @@ protected function setUp(): void $objectManagerProvider, $this->remove, $this->collector, - $setupFactory, - $this->moduleRegistryUninstaller + $setupFactory ); $this->output = $this->getMockForAbstractClass(OutputInterface::class); @@ -103,7 +95,6 @@ protected function setUp(): void public function testUninstallRemoveData() { - $this->moduleRegistryUninstaller->expects($this->never())->method($this->anything()); $uninstall = $this->getMockForAbstractClass(UninstallInterface::class, [], '', false); $uninstall->expects($this->atLeastOnce()) ->method('uninstall') @@ -136,7 +127,6 @@ public function testUninstallRemoveData() public function testUninstallRemoveCode() { - $this->moduleRegistryUninstaller->expects($this->never())->method($this->anything()); $this->output->expects($this->once())->method('writeln'); $packageInfoFactory = $this->createMock(PackageInfoFactory::class); $packageInfo = $this->createMock(PackageInfo::class); diff --git a/setup/src/Magento/Setup/Test/Unit/Module/Di/App/Task/OperationFactoryTest.php b/setup/src/Magento/Setup/Test/Unit/Module/Di/App/Task/OperationFactoryTest.php index 4682b0b035798..b2af18b34959a 100644 --- a/setup/src/Magento/Setup/Test/Unit/Module/Di/App/Task/OperationFactoryTest.php +++ b/setup/src/Magento/Setup/Test/Unit/Module/Di/App/Task/OperationFactoryTest.php @@ -65,8 +65,7 @@ public function testCreateException() $notRegisteredOperation = 'coffee'; $this->expectException(OperationException::class); $this->expectExceptionMessage( - sprintf('Unrecognized operation "%s"', $notRegisteredOperation), - OperationException::UNAVAILABLE_OPERATION + sprintf('Unrecognized operation "%s"', $notRegisteredOperation) ); $this->factory->create($notRegisteredOperation); } From e3fd2463dba30032f6777c926bba96a40f53730c Mon Sep 17 00:00:00 2001 From: Gabriel Galvao da Gama <galvaoda@adobe.com> Date: Wed, 14 Oct 2020 10:52:52 +0100 Subject: [PATCH 092/139] Small changes to increase performance --- .../Magento/Wishlist/Block/AddToWishlist.php | 73 ++++++++----------- .../frontend/layout/catalog_category_view.xml | 6 +- .../layout/catalogsearch_result_index.xml | 7 +- .../view/frontend/web/js/add-to-wishlist.js | 36 +++++---- 4 files changed, 56 insertions(+), 66 deletions(-) diff --git a/app/code/Magento/Wishlist/Block/AddToWishlist.php b/app/code/Magento/Wishlist/Block/AddToWishlist.php index dffd8cb027e74..0d4d403034f2a 100644 --- a/app/code/Magento/Wishlist/Block/AddToWishlist.php +++ b/app/code/Magento/Wishlist/Block/AddToWishlist.php @@ -6,7 +6,11 @@ namespace Magento\Wishlist\Block; +use Magento\Catalog\Api\Data\ProductTypeInterface; +use Magento\Catalog\Api\ProductTypeListInterface; +use Magento\Framework\App\ObjectManager; use Magento\Framework\View\Element\Template; +use Magento\Framework\View\Element\Template\Context; /** * Wishlist js plugin initialization block @@ -24,63 +28,50 @@ class AddToWishlist extends Template private $productTypes; /** - * Returns wishlist widget options - * - * @return array - * @since 100.1.0 + * @var ProductTypeListInterface */ - public function getWishlistOptions() - { - return [ - 'productType' => $this->getProductTypes(), - 'isProductList' => (bool)$this->getData('is_product_list') - ]; - } + private $productTypeList; /** - * Returns an array of product types - * - * @return array|null - * @throws \Magento\Framework\Exception\LocalizedException + * AddToWishlist constructor. + * @param ProductTypeListInterface $productTypeList + * @param Context $context + * @param array $data */ - private function getProductTypes() - { - if ($this->productTypes === null) { - $this->productTypes = []; - $block = $this->getLayout()->getBlock($this->getProductListBlockName()); - if ($block) { - $productCollection = $block->getLoadedProductCollection(); - $productTypes = []; - /** @var $product \Magento\Catalog\Model\Product */ - foreach ($productCollection as $product) { - $productTypes[] = $this->escapeHtml($product->getTypeId()); - } - $this->productTypes = array_unique($productTypes); - } - } - return $this->productTypes; + public function __construct( + Context $context, + array $data = [], + ?ProductTypeListInterface $productTypeList = null + ) { + parent::__construct($context, $data); + $this->productTypes = []; + $this->productTypeList = $productTypeList ?: ObjectManager::getInstance()->get(ProductTypeListInterface::class); } /** - * Get product list block name in layout + * Returns wishlist widget options * - * @return string + * @return array + * @since 100.1.0 */ - private function getProductListBlockName(): string + public function getWishlistOptions() { - return $this->getData('product_list_block') ?: 'category.products.list'; + return ['productType' => $this->getProductTypes()]; } /** - * @inheritDoc + * Returns an array of product types * - * @since 100.1.0 + * @return array */ - protected function _toHtml() + private function getProductTypes(): array { - if (!$this->getProductTypes()) { - return ''; + if (count($this->productTypes) === 0) { + /** @var ProductTypeInterface productTypes */ + $this->productTypes = array_map(function ($productType) { + return $productType->getName(); + }, $this->productTypeList->getProductTypes()); } - return parent::_toHtml(); + return $this->productTypes; } } diff --git a/app/code/Magento/Wishlist/view/frontend/layout/catalog_category_view.xml b/app/code/Magento/Wishlist/view/frontend/layout/catalog_category_view.xml index c305b7c489d59..81bd966b904d7 100644 --- a/app/code/Magento/Wishlist/view/frontend/layout/catalog_category_view.xml +++ b/app/code/Magento/Wishlist/view/frontend/layout/catalog_category_view.xml @@ -23,11 +23,7 @@ <referenceContainer name="category.product.list.additional"> <block class="Magento\Wishlist\Block\AddToWishlist" name="category.product.list.additional.wishlist_addto" - template="Magento_Wishlist::addto.phtml"> - <arguments> - <argument name="is_product_list" xsi:type="boolean">true</argument> - </arguments> - </block> + template="Magento_Wishlist::addto.phtml"/> </referenceContainer> </referenceContainer> </body> diff --git a/app/code/Magento/Wishlist/view/frontend/layout/catalogsearch_result_index.xml b/app/code/Magento/Wishlist/view/frontend/layout/catalogsearch_result_index.xml index b36a7cc2347af..b26aa64ad89b1 100644 --- a/app/code/Magento/Wishlist/view/frontend/layout/catalogsearch_result_index.xml +++ b/app/code/Magento/Wishlist/view/frontend/layout/catalogsearch_result_index.xml @@ -17,12 +17,7 @@ <referenceBlock name="wishlist_page_head_components"> <block class="Magento\Wishlist\Block\AddToWishlist" name="catalogsearch.wishlist_addto" - template="Magento_Wishlist::addto.phtml"> - <arguments> - <argument name="is_product_list" xsi:type="boolean">true</argument> - <argument name="product_list_block" xsi:type="string">search_result_list</argument> - </arguments> - </block> + template="Magento_Wishlist::addto.phtml"/> </referenceBlock> </body> </page> diff --git a/app/code/Magento/Wishlist/view/frontend/web/js/add-to-wishlist.js b/app/code/Magento/Wishlist/view/frontend/web/js/add-to-wishlist.js index 727a9751cc2f6..38ed1f62cea66 100644 --- a/app/code/Magento/Wishlist/view/frontend/web/js/add-to-wishlist.js +++ b/app/code/Magento/Wishlist/view/frontend/web/js/add-to-wishlist.js @@ -18,8 +18,8 @@ define([ customOptionsInfo: '.product-custom-option', qtyInfo: '#qty', actionElement: '[data-action="add-to-wishlist"]', - productListItem: '.item.product-item', - isProductList: false + productListWrapper: '.product-item-info', + productPageWrapper: '.product-info-main' }, /** @inheritdoc */ @@ -67,23 +67,17 @@ define([ _updateWishlistData: function (event) { var dataToAdd = {}, isFileUploaded = false, - productItem = null, handleObjSelector = null, self = this; if (event.handleObj.selector == this.options.qtyInfo) { //eslint-disable-line eqeqeq - this._updateAddToWishlistButton({}, productItem); + this._updateAddToWishlistButton({}, event); event.stopPropagation(); return; } - if (this.options.isProductList) { - productItem = $(event.target).closest(this.options.productListItem); - handleObjSelector = productItem.find(event.handleObj.selector); - } else { - handleObjSelector = $(event.handleObj.selector); - } + handleObjSelector = $(event.currentTarget).closest('form').find(event.handleObj.selector) handleObjSelector.each(function (index, element) { if ($(element).is('input[type=text]') || @@ -110,18 +104,18 @@ define([ if (isFileUploaded) { this.bindFormSubmit(); } - this._updateAddToWishlistButton(dataToAdd, productItem); + this._updateAddToWishlistButton(dataToAdd, event); event.stopPropagation(); }, /** * @param {Object} dataToAdd - * @param {Object} productItem + * @param {jQuery.Event} event * @private */ - _updateAddToWishlistButton: function (dataToAdd, productItem) { + _updateAddToWishlistButton: function (dataToAdd, event) { var self = this, - buttons = productItem ? productItem.find(this.options.actionElement) : $(this.options.actionElement); + buttons = this._getAddToWishlistButton(event); buttons.each(function (index, element) { var params = $(element).data('post'); @@ -139,6 +133,20 @@ define([ }); }, + /** + * @param {jQuery.Event} event + * @private + */ + _getAddToWishlistButton: function (event) { + var productListWrapper = $(event.currentTarget).closest(this.options.productListWrapper); + + if (productListWrapper.length) { + return productListWrapper.find(this.options.actionElement); + } + + return $(event.currentTarget).closest(this.options.productPageWrapper).find(this.options.actionElement); + }, + /** * @param {Object} array1 * @param {Object} array2 From c40f065d7d518b8893188b11b7dd41fa46ad032c Mon Sep 17 00:00:00 2001 From: Munkh-Ulzii Balidar <mbalidar@comwrap.com> Date: Wed, 14 Oct 2020 13:44:26 +0200 Subject: [PATCH 093/139] 29251 fix product loading based row_id --- .../Magento/ConfigurableProduct/Helper/Data.php | 10 +++++++--- .../Model/Options/Collection.php | 15 ++++++++++----- .../Model/Variant/Collection.php | 17 ++++++++++------- 3 files changed, 27 insertions(+), 15 deletions(-) diff --git a/app/code/Magento/ConfigurableProduct/Helper/Data.php b/app/code/Magento/ConfigurableProduct/Helper/Data.php index a5fdcd62c7aa1..54b95bcd7bd90 100644 --- a/app/code/Magento/ConfigurableProduct/Helper/Data.php +++ b/app/code/Magento/ConfigurableProduct/Helper/Data.php @@ -7,9 +7,11 @@ namespace Magento\ConfigurableProduct\Helper; use Magento\Catalog\Model\Product\Image\UrlBuilder; +use Magento\ConfigurableProduct\Model\Product\Type\Configurable; use Magento\Framework\App\ObjectManager; use Magento\Catalog\Helper\Image as ImageHelper; use Magento\Catalog\Api\Data\ProductInterface; +use Magento\Catalog\Model\Product; use Magento\Catalog\Model\Product\Image; /** @@ -73,7 +75,7 @@ public function getGalleryImages(ProductInterface $product) /** * Get Options for Configurable Product Options * - * @param \Magento\Catalog\Model\Product $currentProduct + * @param Product $currentProduct * @param array $allowedProducts * @return array */ @@ -100,11 +102,13 @@ public function getOptions($currentProduct, $allowedProducts) /** * Get allowed attributes * - * @param \Magento\Catalog\Model\Product $product + * @param Product $product * @return array */ public function getAllowAttributes($product) { - return $product->getTypeInstance()->getConfigurableAttributes($product); + return ($product->getTypeId() == Configurable::TYPE_CODE) + ? $product->getTypeInstance()->getConfigurableAttributes($product) + : []; } } diff --git a/app/code/Magento/ConfigurableProductGraphQl/Model/Options/Collection.php b/app/code/Magento/ConfigurableProductGraphQl/Model/Options/Collection.php index c5c66a194503a..7a27964897828 100644 --- a/app/code/Magento/ConfigurableProductGraphQl/Model/Options/Collection.php +++ b/app/code/Magento/ConfigurableProductGraphQl/Model/Options/Collection.php @@ -186,8 +186,8 @@ private function fetch() : array = $this->selectionUidFormatter->encode((int)$attribute->getId(), (int)$value['value_index']); $this->attributeMap[$productId][$attribute->getId()]['values'][$index] ['is_available_for_selection'] = - isset($options[$attribute['attribute_id']][$value['value_index']]) - && $options[$attribute['attribute_id']][$value['value_index']]; + isset($options[$attribute->getAttributeId()][$value['value_index']]) + && $options[$attribute->getAttributeId()][$value['value_index']]; } } } @@ -196,16 +196,21 @@ private function fetch() : array } /** - * Load products by entity ids + * Load products by link field ids * * @param int[] $productIds * @return ProductInterface[] */ private function getProducts($productIds) { - $this->searchCriteriaBuilder->addFilter('entity_id', $productIds, 'in'); + $linkField = $this->metadataPool->getMetadata(ProductInterface::class)->getLinkField(); + $this->searchCriteriaBuilder->addFilter($linkField, $productIds, 'in'); $searchCriteria = $this->searchCriteriaBuilder->create(); $products = $this->productRepository->getList($searchCriteria)->getItems(); - return $products; + $productsLinkFieldMap = []; + foreach ($products as $product) { + $productsLinkFieldMap[$product->getData($linkField)] = $product; + } + return $productsLinkFieldMap; } } diff --git a/app/code/Magento/ConfigurableProductGraphQl/Model/Variant/Collection.php b/app/code/Magento/ConfigurableProductGraphQl/Model/Variant/Collection.php index b60a660251f4d..cd6d78e5c3ffb 100644 --- a/app/code/Magento/ConfigurableProductGraphQl/Model/Variant/Collection.php +++ b/app/code/Magento/ConfigurableProductGraphQl/Model/Variant/Collection.php @@ -9,6 +9,7 @@ use Magento\Catalog\Api\Data\ProductInterface; use Magento\Catalog\Model\Product; +use Magento\ConfigurableProduct\Model\Product\Type\Configurable; use Magento\ConfigurableProduct\Model\ResourceModel\Product\Type\Configurable\Product\Collection as ChildCollection; use Magento\ConfigurableProduct\Model\ResourceModel\Product\Type\Configurable\Product\CollectionFactory; use Magento\Framework\EntityManager\MetadataPool; @@ -175,19 +176,21 @@ private function fetch(ContextInterface $context = null) : array } /** - * Get attributes code + * Get attributes codes for given product * - * @param \Magento\Catalog\Model\Product $currentProduct + * @param Product $currentProduct * @return array */ private function getAttributesCodes(Product $currentProduct): array { $attributeCodes = $this->attributeCodes; - $allowAttributes = $currentProduct->getTypeInstance()->getConfigurableAttributes($currentProduct); - foreach ($allowAttributes as $attribute) { - $productAttribute = $attribute->getProductAttribute(); - if (!\in_array($productAttribute->getAttributeCode(), $attributeCodes)) { - $attributeCodes[] = $productAttribute->getAttributeCode(); + if ($currentProduct->getTypeId() == Configurable::TYPE_CODE) { + $allowAttributes = $currentProduct->getTypeInstance()->getConfigurableAttributes($currentProduct); + foreach ($allowAttributes as $attribute) { + $productAttribute = $attribute->getProductAttribute(); + if (!\in_array($productAttribute->getAttributeCode(), $attributeCodes)) { + $attributeCodes[] = $productAttribute->getAttributeCode(); + } } } From 8d025339e6df3bce298c231e920ddb243d44c95b Mon Sep 17 00:00:00 2001 From: Gabriel Galvao da Gama <galvaoda@adobe.com> Date: Wed, 14 Oct 2020 14:25:00 +0100 Subject: [PATCH 094/139] Fixed static tests --- app/code/Magento/Wishlist/Block/AddToWishlist.php | 3 ++- .../Magento/Wishlist/view/frontend/web/js/add-to-wishlist.js | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/app/code/Magento/Wishlist/Block/AddToWishlist.php b/app/code/Magento/Wishlist/Block/AddToWishlist.php index 0d4d403034f2a..7997a6ed99031 100644 --- a/app/code/Magento/Wishlist/Block/AddToWishlist.php +++ b/app/code/Magento/Wishlist/Block/AddToWishlist.php @@ -34,9 +34,10 @@ class AddToWishlist extends Template /** * AddToWishlist constructor. - * @param ProductTypeListInterface $productTypeList + * * @param Context $context * @param array $data + * @param ProductTypeListInterface|null $productTypeList */ public function __construct( Context $context, diff --git a/app/code/Magento/Wishlist/view/frontend/web/js/add-to-wishlist.js b/app/code/Magento/Wishlist/view/frontend/web/js/add-to-wishlist.js index 38ed1f62cea66..62756f7211cee 100644 --- a/app/code/Magento/Wishlist/view/frontend/web/js/add-to-wishlist.js +++ b/app/code/Magento/Wishlist/view/frontend/web/js/add-to-wishlist.js @@ -77,7 +77,7 @@ define([ return; } - handleObjSelector = $(event.currentTarget).closest('form').find(event.handleObj.selector) + handleObjSelector = $(event.currentTarget).closest('form').find(event.handleObj.selector); handleObjSelector.each(function (index, element) { if ($(element).is('input[type=text]') || From 59c1bc64ece9510009383c7b21196f9054bd32c7 Mon Sep 17 00:00:00 2001 From: Munkh-Ulzii Balidar <mbalidar@comwrap.com> Date: Wed, 14 Oct 2020 16:03:51 +0200 Subject: [PATCH 095/139] 29251 update unit test belongs to change --- .../Test/Unit/Helper/DataTest.php | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/app/code/Magento/ConfigurableProduct/Test/Unit/Helper/DataTest.php b/app/code/Magento/ConfigurableProduct/Test/Unit/Helper/DataTest.php index db72e1ca6ab4c..963322e2f2c57 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Unit/Helper/DataTest.php +++ b/app/code/Magento/ConfigurableProduct/Test/Unit/Helper/DataTest.php @@ -49,7 +49,7 @@ protected function setUp(): void ->getMock(); $this->_imageHelperMock = $this->createMock(Image::class); $this->_productMock = $this->createMock(Product::class); - + $this->_productMock->setTypeId(Configurable::TYPE_CODE); $this->_model = $objectManager->getObject( Data::class, [ @@ -66,6 +66,10 @@ public function testGetAllowAttributes() ->method('getConfigurableAttributes') ->with($this->_productMock); + $this->_productMock->expects($this->once()) + ->method('getTypeId') + ->willReturn(Configurable::TYPE_CODE); + $this->_productMock->expects($this->once()) ->method('getTypeInstance') ->willReturn($typeInstanceMock); @@ -119,7 +123,10 @@ public function getOptionsDataProvider() { $currentProductMock = $this->createPartialMock( Product::class, - ['getTypeInstance'] + [ + 'getTypeInstance', + 'getTypeId' + ] ); $provider = []; $provider[] = [ @@ -156,6 +163,9 @@ public function getOptionsDataProvider() $typeInstanceMock->expects($this->any()) ->method('getConfigurableAttributes') ->willReturn($attributes); + $currentProductMock->expects($this->any()) + ->method('getTypeId') + ->willReturn(Configurable::TYPE_CODE); $currentProductMock->expects($this->any()) ->method('getTypeInstance') ->willReturn($typeInstanceMock); From d10f1c881daed60d3d7bfae6cadf9fda7d54adb2 Mon Sep 17 00:00:00 2001 From: Yaroslav Bogutsky <y.bogutsky@vveb.pro> Date: Wed, 14 Oct 2020 21:04:55 +0300 Subject: [PATCH 096/139] Fixed placeholder translation in Magento UI grid search component --- app/code/Magento/Ui/view/base/web/js/grid/search/search.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/Magento/Ui/view/base/web/js/grid/search/search.js b/app/code/Magento/Ui/view/base/web/js/grid/search/search.js index ce53b23b79e11..3f5434761ba18 100644 --- a/app/code/Magento/Ui/view/base/web/js/grid/search/search.js +++ b/app/code/Magento/Ui/view/base/web/js/grid/search/search.js @@ -19,7 +19,7 @@ define([ return Element.extend({ defaults: { template: 'ui/grid/search/search', - placeholder: 'Search by keyword', + placeholder: $t('Search by keyword'), label: $t('Keyword'), value: '', previews: [], From 603dba357ddaeaf3c6767595ccd22d491c351eb0 Mon Sep 17 00:00:00 2001 From: SmVladyslav <vlatame.tsg@gmail.com> Date: Thu, 15 Oct 2020 10:40:58 +0300 Subject: [PATCH 097/139] MC-35783: "1 item(s) need your attention." still visible in mini cart after product remove --- .../Checkout/Test/Mftf/Section/StorefrontMinicartSection.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/app/code/Magento/Checkout/Test/Mftf/Section/StorefrontMinicartSection.xml b/app/code/Magento/Checkout/Test/Mftf/Section/StorefrontMinicartSection.xml index 668d33d26f37a..dcab48dbc5368 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Section/StorefrontMinicartSection.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Section/StorefrontMinicartSection.xml @@ -39,6 +39,7 @@ <element name="emptyMiniCart" type="text" selector="//div[@class='minicart-wrapper']//span[@class='counter qty empty']/../.."/> <element name="minicartContent" type="block" selector="#minicart-content-wrapper"/> <element name="messageEmptyCart" type="text" selector="//*[@id='minicart-content-wrapper']//*[contains(@class,'subtitle empty')]"/> + <element name="emptyCartMessageContent" type="text" selector="#minicart-content-wrapper .minicart.empty.text"/> <element name="visibleItemsCountText" type="text" selector="//div[@class='items-total']"/> <element name="productQuantity" type="input" selector="//*[@id='mini-cart']//a[contains(text(),'{{productName}}')]/../..//div[@class='details-qty qty']//input[@data-item-qty='{{qty}}']" parameterized="true"/> <element name="productImage" type="text" selector="//ol[@id='mini-cart']//img[@class='product-image-photo']"/> From e4c227d3caf825c787d4d84dc1e25f55177d4656 Mon Sep 17 00:00:00 2001 From: Yurii Sapiha <yurasapiga93@gmail.com> Date: Thu, 15 Oct 2020 11:44:07 +0300 Subject: [PATCH 098/139] MC-37070: Create automated test for "Import products with shared images" --- .../Catalog/Model/Product/Gallery/UpdateHandlerTest.php | 5 ++++- .../Model/Import/ImportWithSharedImagesTest.php | 3 ++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/dev/tests/integration/testsuite/Magento/Catalog/Model/Product/Gallery/UpdateHandlerTest.php b/dev/tests/integration/testsuite/Magento/Catalog/Model/Product/Gallery/UpdateHandlerTest.php index c5221f1ae5e76..2d94466939dbe 100644 --- a/dev/tests/integration/testsuite/Magento/Catalog/Model/Product/Gallery/UpdateHandlerTest.php +++ b/dev/tests/integration/testsuite/Magento/Catalog/Model/Product/Gallery/UpdateHandlerTest.php @@ -86,12 +86,15 @@ class UpdateHandlerTest extends \PHPUnit\Framework\TestCase * @var StoreManagerInterface */ private $storeManager; + /** * @var int */ private $currentStoreId; - /** @var MetadataPool */ + /** + * @var MetadataPool + */ private $metadataPool; /** diff --git a/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/ImportWithSharedImagesTest.php b/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/ImportWithSharedImagesTest.php index 4c04e5a8814e5..35d4cceb50845 100644 --- a/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/ImportWithSharedImagesTest.php +++ b/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/ImportWithSharedImagesTest.php @@ -10,6 +10,7 @@ use Magento\Catalog\Api\ProductRepositoryInterface; use Magento\Catalog\Model\Product as ProductEntity; use Magento\Catalog\Model\Product\Media\ConfigInterface; +use Magento\Framework\App\Bootstrap as AppBootstrap; use Magento\Framework\App\Filesystem\DirectoryList; use Magento\Framework\Exception\NoSuchEntityException; use Magento\Framework\Filesystem; @@ -82,7 +83,7 @@ protected function setUp(): void $this->csvFactory = $this->objectManager->get(CsvFactory::class); $this->importDataResource = $this->objectManager->get(Data::class); $this->appParams = Bootstrap::getInstance()->getBootstrap()->getApplication() - ->getInitParams()[\Magento\Framework\App\Bootstrap::INIT_PARAM_FILESYSTEM_DIR_PATHS]; + ->getInitParams()[AppBootstrap::INIT_PARAM_FILESYSTEM_DIR_PATHS]; } /** From 43e5b5c901f27478134fe6b2203648684498bb79 Mon Sep 17 00:00:00 2001 From: Mykhailo Matiola <mykhailo.matiola@transoftgroup.com> Date: Thu, 15 Oct 2020 11:50:32 +0300 Subject: [PATCH 099/139] MC-36960: Create automated test for "Create product for "all" store views using API service" --- .../ProductRepositoryAllStoreViewsTest.php | 45 +++++++++++-------- 1 file changed, 26 insertions(+), 19 deletions(-) diff --git a/dev/tests/api-functional/testsuite/Magento/Catalog/Api/ProductRepositoryAllStoreViewsTest.php b/dev/tests/api-functional/testsuite/Magento/Catalog/Api/ProductRepositoryAllStoreViewsTest.php index 2950dda4b3c52..2814a6ab05321 100644 --- a/dev/tests/api-functional/testsuite/Magento/Catalog/Api/ProductRepositoryAllStoreViewsTest.php +++ b/dev/tests/api-functional/testsuite/Magento/Catalog/Api/ProductRepositoryAllStoreViewsTest.php @@ -8,11 +8,13 @@ namespace Magento\Catalog\Api; use Magento\Catalog\Api\Data\ProductAttributeInterface; +use Magento\Catalog\Api\Data\ProductInterface; use Magento\Catalog\Model\Product\Attribute\Source\Status; use Magento\Catalog\Model\Product\Type; use Magento\Catalog\Model\Product\Visibility; use Magento\Catalog\Model\ResourceModel\Product\Website\Link; use Magento\Eav\Model\Config; +use Magento\Framework\Exception\NoSuchEntityException; use Magento\Framework\ObjectManagerInterface; use Magento\Framework\Registry; use Magento\Framework\Webapi\Rest\Request; @@ -24,6 +26,7 @@ * Tests for products creation for all store views. * * @magentoAppIsolation enabled + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class ProductRepositoryAllStoreViewsTest extends WebapiAbstract { @@ -55,6 +58,7 @@ class ProductRepositoryAllStoreViewsTest extends WebapiAbstract * @var Link */ private $productWebsiteLink; + /** * @var Config */ @@ -87,9 +91,11 @@ protected function tearDown(): void { $this->registry->unregister('isSecureArea'); $this->registry->register('isSecureArea', true); - $this->productRepository->delete( - $this->productRepository->get($this->productSku) - ); + try { + $this->productRepository->deleteById($this->productSku); + } catch (NoSuchEntityException $e) { + //already deleted + } $this->registry->unregister('isSecureArea'); $this->registry->register('isSecureArea', false); @@ -98,6 +104,7 @@ protected function tearDown(): void /** * @magentoApiDataFixture Magento/Catalog/_files/category.php + * @return void */ public function testCreateProduct(): void { @@ -110,6 +117,7 @@ public function testCreateProduct(): void /** * @magentoApiDataFixture Magento/Catalog/_files/category.php * @magentoApiDataFixture Magento/Store/_files/second_website_with_store_group_and_store.php + * @return void */ public function testCreateProductOnMultipleWebsites(): void { @@ -120,12 +128,12 @@ public function testCreateProductOnMultipleWebsites(): void } /** - * Saves Product via API. + * Saves product via API. * - * @param $product + * @param array $product * @return array */ - private function saveProduct($product): array + private function saveProduct(array $product): array { $serviceInfo = [ 'rest' => ['resourcePath' =>self::PRODUCTS_RESOURCE_PATH, 'httpMethod' => Request::HTTP_METHOD_POST], @@ -146,22 +154,22 @@ private function saveProduct($product): array */ private function getProductData(): array { - $setId =(int)$this->eavConfig->getEntityType(ProductAttributeInterface::ENTITY_TYPE_CODE) + $setId = (int)$this->eavConfig->getEntityType(ProductAttributeInterface::ENTITY_TYPE_CODE) ->getDefaultAttributeSetId(); return [ - 'sku' => $this->productSku, - 'name' => 'simple', - 'type_id' => Type::TYPE_SIMPLE, - 'weight' => 1, - 'attribute_set_id' => $setId, - 'price' => 10, - 'status' => Status::STATUS_ENABLED, - 'visibility' => Visibility::VISIBILITY_BOTH, - 'extension_attributes' => [ + ProductInterface::SKU => $this->productSku, + ProductInterface::NAME => 'simple', + ProductInterface::TYPE_ID => Type::TYPE_SIMPLE, + ProductInterface::WEIGHT => 1, + ProductInterface::ATTRIBUTE_SET_ID => $setId, + ProductInterface::PRICE => 10, + ProductInterface::STATUS => Status::STATUS_ENABLED, + ProductInterface::VISIBILITY => Visibility::VISIBILITY_BOTH, + ProductInterface::EXTENSION_ATTRIBUTES_KEY => [ 'stock_item' => ['is_in_stock' => true, 'qty' => 1000] ], - 'custom_attributes' => [ + ProductInterface::CUSTOM_ATTRIBUTES => [ ['attribute_code' => 'url_key', 'value' => 'simple'], ['attribute_code' => 'tax_class_id', 'value' => 2], ['attribute_code' => 'category_ids', 'value' => [333]] @@ -219,8 +227,7 @@ private function assertProductData(array $productData, array $resultData, array private function getAllWebsiteIds(): array { $websiteIds = []; - $websites = $this->storeManager->getWebsites(); - foreach ($websites as $website) { + foreach ($this->storeManager->getWebsites() as $website) { $websiteIds[] = $website->getId(); } From 0587310da6dd45e255df1b48aa4c6c829b166107 Mon Sep 17 00:00:00 2001 From: Viktor Kopin <viktor.kopin@transoftgroup.com> Date: Thu, 15 Oct 2020 12:57:59 +0300 Subject: [PATCH 100/139] MC-37665: Updating a category through the REST API will uncheck "Use Default Value" on a bunch of attributes --- .../Catalog/Model/CategoryRepository.php | 4 +- .../CategoryRepository/PopulateWithValues.php | 56 ++++++++++++------- .../Unit/Model/CategoryRepositoryTest.php | 9 ++- .../Catalog/Api/CategoryRepositoryTest.php | 20 +++---- 4 files changed, 54 insertions(+), 35 deletions(-) diff --git a/app/code/Magento/Catalog/Model/CategoryRepository.php b/app/code/Magento/Catalog/Model/CategoryRepository.php index fe3ae4cc468a1..7082fa4747fdc 100644 --- a/app/code/Magento/Catalog/Model/CategoryRepository.php +++ b/app/code/Magento/Catalog/Model/CategoryRepository.php @@ -71,13 +71,13 @@ class CategoryRepository implements \Magento\Catalog\Api\CategoryRepositoryInter * @param CategoryFactory $categoryFactory * @param CategoryResource $categoryResource * @param StoreManagerInterface $storeManager - * @param PopulateWithValues $populateWithValues + * @param PopulateWithValues|null $populateWithValues */ public function __construct( CategoryFactory $categoryFactory, CategoryResource $categoryResource, StoreManagerInterface $storeManager, - PopulateWithValues $populateWithValues + ?PopulateWithValues $populateWithValues ) { $this->categoryFactory = $categoryFactory; $this->categoryResource = $categoryResource; diff --git a/app/code/Magento/Catalog/Model/CategoryRepository/PopulateWithValues.php b/app/code/Magento/Catalog/Model/CategoryRepository/PopulateWithValues.php index 410aa3db1f255..2a313119f9c8e 100644 --- a/app/code/Magento/Catalog/Model/CategoryRepository/PopulateWithValues.php +++ b/app/code/Magento/Catalog/Model/CategoryRepository/PopulateWithValues.php @@ -69,30 +69,46 @@ public function __construct( public function execute(CategoryInterface $category, array $existingData): void { $storeId = $existingData['store_id']; - $overriddenValues = array_filter($category->getData(), function ($key) use ($category, $storeId) { - /** @var Category $category */ - return $this->scopeOverriddenValue->containsValue( - CategoryInterface::class, - $category, - $key, - $storeId - ); - }, ARRAY_FILTER_USE_KEY); + $overriddenValues = array_filter( + $category->getData(), + function ($key) use ($category, $storeId) { + /** @var Category $category */ + return $this->scopeOverriddenValue->containsValue( + CategoryInterface::class, + $category, + $key, + $storeId + ); + }, + ARRAY_FILTER_USE_KEY + ); $defaultValues = array_diff_key($category->getData(), $overriddenValues); - array_walk($defaultValues, function (&$value, $key) { - $attributes = $this->getAttributes(); - if (isset($attributes[$key]) && !$attributes[$key]->isStatic()) { - $value = null; + array_walk( + $defaultValues, + function (&$value, $key) { + $attributes = $this->getAttributes(); + if (isset($attributes[$key]) && !$attributes[$key]->isStatic()) { + $value = null; + } } - }); + ); $category->addData($defaultValues); $category->addData($existingData); - $useDefaultAttributes = array_filter($category->getData(), function ($attributeValue) { - return null === $attributeValue; - }); - $category->setData('use_default', array_map(function () { - return true; - }, $useDefaultAttributes)); + $useDefaultAttributes = array_filter( + $category->getData(), + function ($attributeValue) { + return null === $attributeValue; + } + ); + $category->setData( + 'use_default', + array_map( + function () { + return true; + }, + $useDefaultAttributes + ) + ); } /** diff --git a/app/code/Magento/Catalog/Test/Unit/Model/CategoryRepositoryTest.php b/app/code/Magento/Catalog/Test/Unit/Model/CategoryRepositoryTest.php index 61e8133da5759..8274ed9da5f32 100644 --- a/app/code/Magento/Catalog/Test/Unit/Model/CategoryRepositoryTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Model/CategoryRepositoryTest.php @@ -65,10 +65,13 @@ class CategoryRepositoryTest extends TestCase protected $metadataPoolMock; /** - * @var MockObject + * @var PopulateWithValues|MockObject */ - protected $populateWithValuesMock; + private $populateWithValuesMock; + /** + * @inheridoc + */ protected function setUp(): void { $this->categoryFactoryMock = $this->createPartialMock( @@ -102,7 +105,7 @@ protected function setUp(): void $this->populateWithValuesMock = $this ->getMockBuilder(PopulateWithValues::class) - ->setMethods(['execute']) + ->onlyMethods(['execute']) ->disableOriginalConstructor() ->getMock(); diff --git a/dev/tests/api-functional/testsuite/Magento/Catalog/Api/CategoryRepositoryTest.php b/dev/tests/api-functional/testsuite/Magento/Catalog/Api/CategoryRepositoryTest.php index e7d47ff64a109..6ce922eab21f1 100644 --- a/dev/tests/api-functional/testsuite/Magento/Catalog/Api/CategoryRepositoryTest.php +++ b/dev/tests/api-functional/testsuite/Magento/Catalog/Api/CategoryRepositoryTest.php @@ -12,6 +12,7 @@ use Magento\Authorization\Model\RulesFactory; use Magento\Catalog\Api\Data\CategoryInterface; use Magento\Catalog\Model\Attribute\ScopeOverriddenValue; +use Magento\Catalog\Model\Category; use Magento\CatalogUrlRewrite\Model\CategoryUrlRewriteGenerator; use Magento\Integration\Api\AdminTokenServiceInterface; use Magento\Store\Model\Store; @@ -193,7 +194,7 @@ public function testDeleteSystemOrRoot() public function deleteSystemOrRootDataProvider() { return [ - [\Magento\Catalog\Model\Category::TREE_ROOT_ID], + [Category::TREE_ROOT_ID], [2] //Default root category ]; } @@ -216,8 +217,8 @@ public function testUpdate() ]; $result = $this->updateCategory($categoryId, $categoryData); $this->assertEquals($categoryId, $result['id']); - /** @var \Magento\Catalog\Model\Category $model */ - $model = Bootstrap::getObjectManager()->get(\Magento\Catalog\Model\Category::class); + /** @var Category $model */ + $model = Bootstrap::getObjectManager()->get(Category::class); $category = $model->load($categoryId); $this->assertFalse((bool)$category->getIsActive(), 'Category "is_active" must equal to false'); $this->assertEquals("Update Category Test", $category->getName()); @@ -244,8 +245,8 @@ public function testUpdateWithDefaultSortByAttribute() ]; $result = $this->updateCategory($categoryId, $categoryData); $this->assertEquals($categoryId, $result['id']); - /** @var \Magento\Catalog\Model\Category $model */ - $model = Bootstrap::getObjectManager()->get(\Magento\Catalog\Model\Category::class); + /** @var Category $model */ + $model = Bootstrap::getObjectManager()->get(Category::class); $category = $model->load($categoryId); $this->assertTrue((bool)$category->getIsActive(), 'Category "is_active" must equal to true'); $this->assertEquals("Update Category Test With default_sort_by Attribute", $category->getName()); @@ -288,8 +289,8 @@ public function testUpdateUrlKey() ]; $result = $this->updateCategory($categoryId, $categoryData); $this->assertEquals($categoryId, $result['id']); - /** @var \Magento\Catalog\Model\Category $model */ - $model = Bootstrap::getObjectManager()->get(\Magento\Catalog\Model\Category::class); + /** @var Category $model */ + $model = Bootstrap::getObjectManager()->get(Category::class); $category = $model->load($categoryId); $this->assertEquals("Update Category Test New Name", $category->getName()); @@ -564,7 +565,6 @@ public function testSaveDesign(): void /** * Check if repository does not override default values for attributes out of request * - * @throws \Exception * @magentoApiDataFixture Magento/Catalog/_files/category.php */ public function testUpdateScopeAttribute() @@ -576,8 +576,8 @@ public function testUpdateScopeAttribute() $result = $this->updateCategoryForSpecificStore($categoryId, $categoryData); $this->assertEquals($categoryId, $result['id']); - /** @var \Magento\Catalog\Model\Category $model */ - $model = Bootstrap::getObjectManager()->get(\Magento\Catalog\Model\Category::class); + /** @var Category $model */ + $model = Bootstrap::getObjectManager()->get(Category::class); $category = $model->load($categoryId); /** @var ScopeOverriddenValue $scopeOverriddenValue */ From 3bacacefdd32ea4665e7d185689ecd345e43c50f Mon Sep 17 00:00:00 2001 From: Myroslav Dobra <dmaraptor@gmail.com> Date: Thu, 15 Oct 2020 15:27:46 +0300 Subject: [PATCH 101/139] MC-29402: PHPStan: "Anonymous function has an unused use" errors --- .../ProductRepository/TransactionWrapperTest.php | 2 +- .../Model/Resolver/TierPrices.php | 2 +- .../Magento/Customer/Controller/Adminhtml/Index.php | 2 +- .../CustomerRepository/TransactionWrapperTest.php | 2 +- .../Magento/Swatches/Model/AttributeCreateTest.php | 4 ++-- .../Swatches/Model/SwatchAttributeOptionAddTest.php | 2 +- .../Magento/Framework/Setup/ExternalFKSetup.php | 6 +++--- .../Setup/Console/Command/AdminUserCreateCommand.php | 7 ++++--- .../Setup/Console/Command/RollbackCommand.php | 6 +++--- .../Fixtures/AttributeSet/SwatchesGenerator.php | 3 ++- .../Magento/Setup/Fixtures/EavVariationsFixture.php | 12 ++++++++---- 11 files changed, 27 insertions(+), 21 deletions(-) diff --git a/app/code/Magento/Catalog/Test/Unit/Model/Plugin/ProductRepository/TransactionWrapperTest.php b/app/code/Magento/Catalog/Test/Unit/Model/Plugin/ProductRepository/TransactionWrapperTest.php index c60ef266b7ebb..89243ea30c9dc 100644 --- a/app/code/Magento/Catalog/Test/Unit/Model/Plugin/ProductRepository/TransactionWrapperTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Model/Plugin/ProductRepository/TransactionWrapperTest.php @@ -62,7 +62,7 @@ protected function setUp(): void $this->closureMock = function () use ($productMock) { return $productMock; }; - $this->rollbackClosureMock = function () use ($productMock) { + $this->rollbackClosureMock = function () { throw new \Exception(self::ERROR_MSG); }; diff --git a/app/code/Magento/CatalogCustomerGraphQl/Model/Resolver/TierPrices.php b/app/code/Magento/CatalogCustomerGraphQl/Model/Resolver/TierPrices.php index c449d0a2ba30b..675bdaa5f1db0 100644 --- a/app/code/Magento/CatalogCustomerGraphQl/Model/Resolver/TierPrices.php +++ b/app/code/Magento/CatalogCustomerGraphQl/Model/Resolver/TierPrices.php @@ -87,7 +87,7 @@ public function resolve( $this->tiers->addProductFilter($productId); return $this->valueFactory->create( - function () use ($productId, $context) { + function () use ($productId) { $tierPrices = $this->tiers->getProductTierPrices($productId); return $tierPrices ?? []; diff --git a/app/code/Magento/Customer/Controller/Adminhtml/Index.php b/app/code/Magento/Customer/Controller/Adminhtml/Index.php index 51dc39a2fc658..f03f55b16e0c7 100644 --- a/app/code/Magento/Customer/Controller/Adminhtml/Index.php +++ b/app/code/Magento/Customer/Controller/Adminhtml/Index.php @@ -291,7 +291,7 @@ protected function _addSessionErrorMessages($messages) $messages = (array)$messages; $session = $this->_getSession(); - $callback = function ($error) use ($session) { + $callback = function ($error) { if (!$error instanceof Error) { $error = new Error($error); } diff --git a/app/code/Magento/Customer/Test/Unit/Model/Plugin/CustomerRepository/TransactionWrapperTest.php b/app/code/Magento/Customer/Test/Unit/Model/Plugin/CustomerRepository/TransactionWrapperTest.php index c00b5cce02146..634b0d73219db 100644 --- a/app/code/Magento/Customer/Test/Unit/Model/Plugin/CustomerRepository/TransactionWrapperTest.php +++ b/app/code/Magento/Customer/Test/Unit/Model/Plugin/CustomerRepository/TransactionWrapperTest.php @@ -62,7 +62,7 @@ protected function setUp(): void $this->closureMock = function () use ($customerMock) { return $customerMock; }; - $this->rollbackClosureMock = function () use ($customerMock) { + $this->rollbackClosureMock = function () { throw new \Exception(self::ERROR_MSG); }; diff --git a/dev/tests/integration/testsuite/Magento/Swatches/Model/AttributeCreateTest.php b/dev/tests/integration/testsuite/Magento/Swatches/Model/AttributeCreateTest.php index 98297cd43041f..b9ec091003267 100644 --- a/dev/tests/integration/testsuite/Magento/Swatches/Model/AttributeCreateTest.php +++ b/dev/tests/integration/testsuite/Magento/Swatches/Model/AttributeCreateTest.php @@ -52,7 +52,7 @@ function ($values, $index) use ($optionsPerAttribute) { ); $data['optionvisual']['value'] = array_reduce( range(1, $optionsPerAttribute), - function ($values, $index) use ($optionsPerAttribute) { + function ($values, $index) { $values['option_' . $index] = ['option ' . $index]; return $values; }, @@ -61,7 +61,7 @@ function ($values, $index) use ($optionsPerAttribute) { $data['options']['option'] = array_reduce( range(1, $optionsPerAttribute), - function ($values, $index) use ($optionsPerAttribute) { + function ($values, $index) { $values[] = [ 'label' => 'option ' . $index, 'value' => 'option_' . $index diff --git a/dev/tests/integration/testsuite/Magento/Swatches/Model/SwatchAttributeOptionAddTest.php b/dev/tests/integration/testsuite/Magento/Swatches/Model/SwatchAttributeOptionAddTest.php index ccf25fd15c529..06ba28932eeb5 100644 --- a/dev/tests/integration/testsuite/Magento/Swatches/Model/SwatchAttributeOptionAddTest.php +++ b/dev/tests/integration/testsuite/Magento/Swatches/Model/SwatchAttributeOptionAddTest.php @@ -40,7 +40,7 @@ public function testSwatchOptionAdd() $data['options']['option'] = array_reduce( range(10, $optionsPerAttribute), - function ($values, $index) use ($optionsPerAttribute) { + function ($values, $index) { $values[] = [ 'label' => 'option ' . $index, 'value' => 'option_' . $index diff --git a/lib/internal/Magento/Framework/Setup/ExternalFKSetup.php b/lib/internal/Magento/Framework/Setup/ExternalFKSetup.php index 4247b7b1aab2f..3d3bb5b6578a9 100644 --- a/lib/internal/Magento/Framework/Setup/ExternalFKSetup.php +++ b/lib/internal/Magento/Framework/Setup/ExternalFKSetup.php @@ -92,10 +92,10 @@ protected function execute() /** * Get foreign keys for tables and columns * - * @param string $refTable - * @param string $refColumn * @param string $targetTable * @param string $targetColumn + * @param string $refTable + * @param string $refColumn * @return array * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ @@ -110,7 +110,7 @@ protected function getForeignKeys( ); $foreignKeys = array_filter( $foreignKeys, - function ($key) use ($targetColumn, $refTable, $refColumn) { + function ($key) use ($targetColumn, $refTable) { return $key['COLUMN_NAME'] == $targetColumn && $key['REF_TABLE_NAME'] == $refTable; } diff --git a/setup/src/Magento/Setup/Console/Command/AdminUserCreateCommand.php b/setup/src/Magento/Setup/Console/Command/AdminUserCreateCommand.php index 173ea9e49a8a4..8e64aae20573c 100644 --- a/setup/src/Magento/Setup/Console/Command/AdminUserCreateCommand.php +++ b/setup/src/Magento/Setup/Console/Command/AdminUserCreateCommand.php @@ -7,6 +7,7 @@ namespace Magento\Setup\Console\Command; use Magento\Framework\Setup\ConsoleLogger; +use Magento\Framework\Validation\ValidationException; use Magento\Setup\Model\AdminAccount; use Magento\Setup\Model\InstallerFactory; use Magento\User\Model\UserValidationRules; @@ -81,7 +82,7 @@ protected function interact(InputInterface $input, OutputInterface $output) $question = new Question('<question>Admin password:</question> ', ''); $question->setHidden(true); - $question->setValidator(function ($value) use ($output) { + $question->setValidator(function ($value) { $user = new \Magento\Framework\DataObject(); $user->setPassword($value); @@ -90,7 +91,7 @@ protected function interact(InputInterface $input, OutputInterface $output) $validator->isValid($user); foreach ($validator->getMessages() as $message) { - throw new \Exception($message); + throw new ValidationException(__($message)); } return $value; @@ -143,7 +144,7 @@ private function addNotEmptyValidator(Question $question) { $question->setValidator(function ($value) { if (trim($value) == '') { - throw new \Exception('The value cannot be empty'); + throw new ValidationException(__('The value cannot be empty')); } return $value; diff --git a/setup/src/Magento/Setup/Console/Command/RollbackCommand.php b/setup/src/Magento/Setup/Console/Command/RollbackCommand.php index a9138b9faefa1..e4616ae5e271b 100644 --- a/setup/src/Magento/Setup/Console/Command/RollbackCommand.php +++ b/setup/src/Magento/Setup/Console/Command/RollbackCommand.php @@ -80,7 +80,7 @@ public function __construct( } /** - * {@inheritdoc} + * @inheritDoc */ protected function configure() { @@ -111,7 +111,7 @@ protected function configure() } /** - * {@inheritdoc} + * @inheritDoc */ protected function execute(InputInterface $input, OutputInterface $output) { @@ -123,7 +123,7 @@ protected function execute(InputInterface $input, OutputInterface $output) return \Magento\Framework\Console\Cli::RETURN_FAILURE; } $returnValue = $this->maintenanceModeEnabler->executeInMaintenanceMode( - function () use ($input, $output, &$returnValue) { + function () use ($input, $output) { try { $helper = $this->getHelper('question'); $question = new ConfirmationQuestion( diff --git a/setup/src/Magento/Setup/Fixtures/AttributeSet/SwatchesGenerator.php b/setup/src/Magento/Setup/Fixtures/AttributeSet/SwatchesGenerator.php index 26e7857703b4f..56263d0ec0adb 100644 --- a/setup/src/Magento/Setup/Fixtures/AttributeSet/SwatchesGenerator.php +++ b/setup/src/Magento/Setup/Fixtures/AttributeSet/SwatchesGenerator.php @@ -91,7 +91,7 @@ function ($values, $index) use ($optionCount, $data, $type) { ); $attribute['optionvisual']['value'] = array_reduce( range(1, $optionCount), - function ($values, $index) use ($optionCount) { + function ($values, $index) { $values['option_' . $index] = ['option ' . $index]; return $values; }, @@ -129,6 +129,7 @@ private function generateSwatchImage($data) $this->imagesGenerator = $this->imagesGeneratorFactory->create(); } + // phpcs:ignore Magento2.Security.InsecureFunction $imageName = md5($data) . '.jpg'; $this->imagesGenerator->generate([ 'image-width' => self::GENERATED_SWATCH_WIDTH, diff --git a/setup/src/Magento/Setup/Fixtures/EavVariationsFixture.php b/setup/src/Magento/Setup/Fixtures/EavVariationsFixture.php index f143685f1903d..671627bcea8a9 100644 --- a/setup/src/Magento/Setup/Fixtures/EavVariationsFixture.php +++ b/setup/src/Magento/Setup/Fixtures/EavVariationsFixture.php @@ -77,7 +77,7 @@ public function __construct( } /** - * {@inheritdoc} + * @inheritDoc */ public function execute() { @@ -93,7 +93,7 @@ public function execute() } /** - * {@inheritdoc} + * @inheritDoc */ public function getActionTitle() { @@ -101,7 +101,7 @@ public function getActionTitle() } /** - * {@inheritdoc} + * @inheritDoc */ public function introduceParamLabels() { @@ -109,6 +109,8 @@ public function introduceParamLabels() } /** + * Generate Attribute + * * @param int $optionCount * @return void */ @@ -169,7 +171,7 @@ function ($values, $index) use ($optionCount) { ); $data['optionvisual']['value'] = array_reduce( range(1, $optionCount), - function ($values, $index) use ($optionCount) { + function ($values, $index) { $values['option_' . $index] = ['option ' . $index]; return $values; }, @@ -194,6 +196,8 @@ function ($values, $index) use ($optionCount) { } /** + * Get attribute code + * * @return string */ private function getAttributeCode() From 64978b41efdaf05ef59961278b0c882ac87c209b Mon Sep 17 00:00:00 2001 From: DmytroPaidych <dimonovp@gmail.com> Date: Thu, 15 Oct 2020 15:12:27 +0200 Subject: [PATCH 102/139] MC-37896: Create automated test for "Reset Widget" --- .../AdminSaveAndContinueWidgetActionGroup.xml | 2 +- .../AdminSetWidgetNameAndStoreActionGroup.xml | 7 ++++--- ...AdminSetWidgetTypeAndDesignActionGroup.xml} | 5 +++-- .../Mftf/Section/AdminNewWidgetSection.xml | 6 +++--- .../Test/Mftf/Test/AdminResetWidgetTest.xml | 18 +++++++++--------- 5 files changed, 20 insertions(+), 18 deletions(-) rename app/code/Magento/Widget/Test/Mftf/ActionGroup/{AdminSetInputTypeAndDesignActionGroup.xml => AdminSetWidgetTypeAndDesignActionGroup.xml} (77%) diff --git a/app/code/Magento/Widget/Test/Mftf/ActionGroup/AdminSaveAndContinueWidgetActionGroup.xml b/app/code/Magento/Widget/Test/Mftf/ActionGroup/AdminSaveAndContinueWidgetActionGroup.xml index cd9774f3b13ba..6d17a5c687b1a 100644 --- a/app/code/Magento/Widget/Test/Mftf/ActionGroup/AdminSaveAndContinueWidgetActionGroup.xml +++ b/app/code/Magento/Widget/Test/Mftf/ActionGroup/AdminSaveAndContinueWidgetActionGroup.xml @@ -13,7 +13,7 @@ </annotations> <scrollToTopOfPage stepKey="scrollToTopOfPage"/> <click selector="{{AdminNewWidgetSection.saveAndContinue}}" stepKey="clickSaveWidget"/> - <waitForElementVisible selector="{{AdminMessagesSection.success}}" stepKey="waitForPageLoad"/> + <waitForElementVisible selector="{{AdminMessagesSection.success}}" stepKey="waitForSuccessMessageAppeared"/> <see selector="{{AdminMessagesSection.success}}" userInput="The widget instance has been saved" stepKey="seeSuccess"/> </actionGroup> </actionGroups> diff --git a/app/code/Magento/Widget/Test/Mftf/ActionGroup/AdminSetWidgetNameAndStoreActionGroup.xml b/app/code/Magento/Widget/Test/Mftf/ActionGroup/AdminSetWidgetNameAndStoreActionGroup.xml index f1faadb1e434e..ce19c1b086328 100644 --- a/app/code/Magento/Widget/Test/Mftf/ActionGroup/AdminSetWidgetNameAndStoreActionGroup.xml +++ b/app/code/Magento/Widget/Test/Mftf/ActionGroup/AdminSetWidgetNameAndStoreActionGroup.xml @@ -10,14 +10,15 @@ xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> <actionGroup name="AdminSetWidgetNameAndStoreActionGroup"> <annotations> - <description>On the widget creation page page set widget name, store add sort order.</description> + <description>Set widget name, store IDs and sort order on Widget edit page</description> </annotations> <arguments> - <argument name="widgetName" defaultValue="{{ProductsListWidget.name}}" type="string"/> + <argument name="widgetTitle" defaultValue="{{ProductsListWidget.name}}" type="string"/> <argument name="widgetStoreIds" defaultValue="{{ProductsListWidget.store_ids}}" type="string"/> <argument name="widgetSortOrder" defaultValue="{{ProductsListWidget.sort_order}}" type="string"/> </arguments> - <fillField selector="{{AdminNewWidgetSection.widgetTitle}}" userInput="{{widgetName}}" stepKey="fillTitle"/> + <waitForElementVisible selector="{{AdminNewWidgetSection.widgetTitle}}" stepKey="waitForWidgetTitleInputVisible"/> + <fillField selector="{{AdminNewWidgetSection.widgetTitle}}" userInput="{{widgetTitle}}" stepKey="fillTitle"/> <selectOption selector="{{AdminNewWidgetSection.widgetStoreIds}}" parameterArray="{{widgetStoreIds}}" stepKey="setWidgetStoreId"/> <fillField selector="{{AdminNewWidgetSection.widgetSortOrder}}" userInput="{{widgetSortOrder}}" stepKey="fillSortOrder"/> </actionGroup> diff --git a/app/code/Magento/Widget/Test/Mftf/ActionGroup/AdminSetInputTypeAndDesignActionGroup.xml b/app/code/Magento/Widget/Test/Mftf/ActionGroup/AdminSetWidgetTypeAndDesignActionGroup.xml similarity index 77% rename from app/code/Magento/Widget/Test/Mftf/ActionGroup/AdminSetInputTypeAndDesignActionGroup.xml rename to app/code/Magento/Widget/Test/Mftf/ActionGroup/AdminSetWidgetTypeAndDesignActionGroup.xml index 3071f60bbc9d6..3a9b4c53572c7 100644 --- a/app/code/Magento/Widget/Test/Mftf/ActionGroup/AdminSetInputTypeAndDesignActionGroup.xml +++ b/app/code/Magento/Widget/Test/Mftf/ActionGroup/AdminSetWidgetTypeAndDesignActionGroup.xml @@ -8,14 +8,15 @@ <actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> - <actionGroup name="AdminSetInputTypeAndDesignActionGroup"> + <actionGroup name="AdminSetWidgetTypeAndDesignActionGroup"> <annotations> - <description>On the widget_instance page select widget type and design</description> + <description>Select type and design on Widget edit page</description> </annotations> <arguments> <argument name="widgetType" defaultValue="{{ProductsListWidget.type}}" type="string"/> <argument name="widgetDesign" defaultValue="{{ProductsListWidget.design_theme}}" type="string"/> </arguments> + <waitForElementVisible selector="{{AdminNewWidgetSection.widgetType}}" stepKey="waitForTypeInputVisible"/> <selectOption selector="{{AdminNewWidgetSection.widgetType}}" userInput="{{widgetType}}" stepKey="setWidgetType"/> <selectOption selector="{{AdminNewWidgetSection.widgetDesignTheme}}" userInput="{{widgetDesign}}" stepKey="setWidgetDesignTheme"/> </actionGroup> diff --git a/app/code/Magento/Widget/Test/Mftf/Section/AdminNewWidgetSection.xml b/app/code/Magento/Widget/Test/Mftf/Section/AdminNewWidgetSection.xml index 2e455f4a3470b..805c55f34ce9a 100644 --- a/app/code/Magento/Widget/Test/Mftf/Section/AdminNewWidgetSection.xml +++ b/app/code/Magento/Widget/Test/Mftf/Section/AdminNewWidgetSection.xml @@ -12,7 +12,7 @@ <element name="widgetType" type="select" selector="#code"/> <element name="widgetDesignTheme" type="select" selector="#theme_id"/> <element name="continue" type="button" timeout="30" selector="#continue_button"/> - <element name="resetBtn" type="button" selector="#reset" timeout="30"/> + <element name="resetBtn" type="button" selector="//*[@class='page-actions-buttons']/button[@id='reset']" timeout="30"/> <element name="widgetTitle" type="input" selector="#title"/> <element name="widgetStoreIds" type="select" selector="#store_ids"/> <element name="widgetSortOrder" type="input" selector="#sort_order"/> @@ -39,11 +39,11 @@ <element name="searchBlock" type="button" selector="//div[@class='admin__filter-actions']/button[@title='Search']"/> <element name="blockStatus" type="select" selector="//select[@name='chooser_is_active']"/> <element name="searchedBlock" type="button" selector="//*[@class='magento-message']//tbody/tr/td[1]"/> - <element name="saveWidget" type="button" selector="#save"/> + <element name="saveWidget" type="button" selector="#save" timeout="30"/> <element name="displayMode" type="select" selector="select[id*='display_mode']"/> <element name="restrictTypes" type="select" selector="select[id*='types']"/> <element name="saveAndContinue" type="button" selector="#save_and_edit_button" timeout="30"/> - <element name="widgetInstanceType" type="select" selector="#instance_code" /> + <element name="widgetInstanceType" type="select" selector="//*[@class='admin__field-control control']/select[@id='instance_code']" /> <!-- Catalog Product List Widget Options --> <element name="title" type="input" selector="[name='parameters[title]']"/> <element name="displayPageControl" type="select" selector="[name='parameters[show_pager]']"/> diff --git a/app/code/Magento/Widget/Test/Mftf/Test/AdminResetWidgetTest.xml b/app/code/Magento/Widget/Test/Mftf/Test/AdminResetWidgetTest.xml index 88610d9143bb4..fd9ce8f3c37e9 100644 --- a/app/code/Magento/Widget/Test/Mftf/Test/AdminResetWidgetTest.xml +++ b/app/code/Magento/Widget/Test/Mftf/Test/AdminResetWidgetTest.xml @@ -11,8 +11,8 @@ <test name="AdminResetWidgetTest"> <annotations> <features value="Widget"/> - <stories value="Reset widget"/> - <title value="[CMS Widgets] Reset Widget"/> + <stories value="CMS Widgets"/> + <title value="Reset Widget"/> <description value="Check that admin user can reset widget form after filling out all information"/> <severity value="MAJOR"/> <testCaseId value="MC-37892"/> @@ -23,25 +23,25 @@ </before> <after> <actionGroup ref="AdminDeleteWidgetActionGroup" stepKey="deleteWidget"> - <argument name="widget" value="ProductsListWidget"/> + <argument name="widget" value="{{ProductsListWidget}}"/> </actionGroup> <actionGroup ref="AdminLogoutActionGroup" stepKey="logoutFromAdmin"/> </after> <amOnPage url="{{AdminNewWidgetPage.url}}" stepKey="amOnAdminNewWidgetPage"/> - <actionGroup ref="AdminSetInputTypeAndDesignActionGroup" stepKey="firstSetTypeAndDesign"> + <actionGroup ref="AdminSetWidgetTypeAndDesignActionGroup" stepKey="firstSetTypeAndDesign"> <argument name="widgetType" value="{{ProductsListWidget.type}}"/> <argument name="widgetDesign" value="{{ProductsListWidget.design_theme}}"/> </actionGroup> <click selector="{{AdminNewWidgetSection.resetBtn}}" stepKey="resetInstance"/> <dontSeeInField userInput="{{ProductsListWidget.type}}" selector="{{AdminNewWidgetSection.widgetType}}" stepKey="dontSeeTypeAfterReset"/> <dontSeeInField userInput="{{ProductsListWidget.design_theme}}" selector="{{AdminNewWidgetSection.widgetDesignTheme}}" stepKey="dontSeeDesignAfterReset"/> - <actionGroup ref="AdminSetInputTypeAndDesignActionGroup" stepKey="setTypeAndDesignAfterReset"> + <actionGroup ref="AdminSetWidgetTypeAndDesignActionGroup" stepKey="setTypeAndDesignAfterReset"> <argument name="widgetType" value="{{ProductsListWidget.type}}"/> <argument name="widgetDesign" value="{{ProductsListWidget.design_theme}}"/> </actionGroup> <click selector="{{AdminNewWidgetSection.continue}}" stepKey="clickContinue"/> <actionGroup ref="AdminSetWidgetNameAndStoreActionGroup" stepKey="setNameAndStore"> - <argument name="widgetName" value="{{ProductsListWidget.name}}"/> + <argument name="widgetTitle" value="{{ProductsListWidget.name}}"/> <argument name="widgetStoreIds" value="{{ProductsListWidget.store_ids}}"/> <argument name="widgetSortOrder" value="{{ProductsListWidget.sort_order}}"/> </actionGroup> @@ -50,12 +50,12 @@ <dontSeeInField userInput="{{ProductsListWidget.store_ids[0]}}" selector="{{AdminNewWidgetSection.widgetStoreIds}}" stepKey="dontSeeStoreAfterReset"/> <dontSeeInField userInput="{{ProductsListWidget.sort_order}}" selector="{{AdminNewWidgetSection.widgetSortOrder}}" stepKey="dontSeeSortOrderAfterReset"/> <actionGroup ref="AdminSetWidgetNameAndStoreActionGroup" stepKey="setNameAndStoreAfterReset"> - <argument name="widgetName" value="{{ProductsListWidget.name}}"/> + <argument name="widgetTitle" value="{{ProductsListWidget.name}}"/> <argument name="widgetStoreIds" value="{{ProductsListWidget.store_ids}}"/> <argument name="widgetSortOrder" value="{{ProductsListWidget.sort_order}}"/> </actionGroup> - <actionGroup ref="AdminSaveAndContinueWidgetActionGroup" stepKey="saveWidget"/> - <click selector="{{AdminNewWidgetSection.resetBtn}}" stepKey="resetWidget"/> + <actionGroup ref="AdminSaveAndContinueWidgetActionGroup" stepKey="saveWidgetAndContinue"/> + <click selector="{{AdminNewWidgetSection.resetBtn}}" stepKey="resetWidgetForm"/> <seeInField userInput="{{ProductsListWidget.name}}" selector="{{AdminNewWidgetSection.widgetTitle}}" stepKey="seeNameAfterReset"/> <seeInField userInput="{{ProductsListWidget.store_ids[0]}}" selector="{{AdminNewWidgetSection.widgetStoreIds}}" stepKey="seeStoreAfterReset"/> <seeInField userInput="{{ProductsListWidget.sort_order}}" selector="{{AdminNewWidgetSection.widgetSortOrder}}" stepKey="seeSortOrderAfterReset"/> From e133e1f5ae6ba3dcc660da8fe823dfef46f69f33 Mon Sep 17 00:00:00 2001 From: Mykhailo Matiola <mykhailo.matiola@transoftgroup.com> Date: Thu, 15 Oct 2020 17:52:32 +0300 Subject: [PATCH 103/139] MC-36960: Create automated test for "Create product for "all" store views using API service" --- .../ProductRepositoryAllStoreViewsTest.php | 31 ++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/dev/tests/api-functional/testsuite/Magento/Catalog/Api/ProductRepositoryAllStoreViewsTest.php b/dev/tests/api-functional/testsuite/Magento/Catalog/Api/ProductRepositoryAllStoreViewsTest.php index 2814a6ab05321..fd815c6d2241b 100644 --- a/dev/tests/api-functional/testsuite/Magento/Catalog/Api/ProductRepositoryAllStoreViewsTest.php +++ b/dev/tests/api-functional/testsuite/Magento/Catalog/Api/ProductRepositoryAllStoreViewsTest.php @@ -13,6 +13,7 @@ use Magento\Catalog\Model\Product\Type; use Magento\Catalog\Model\Product\Visibility; use Magento\Catalog\Model\ResourceModel\Product\Website\Link; +use Magento\CatalogInventory\Api\Data\StockItemInterface; use Magento\Eav\Model\Config; use Magento\Framework\Exception\NoSuchEntityException; use Magento\Framework\ObjectManagerInterface; @@ -167,7 +168,31 @@ private function getProductData(): array ProductInterface::STATUS => Status::STATUS_ENABLED, ProductInterface::VISIBILITY => Visibility::VISIBILITY_BOTH, ProductInterface::EXTENSION_ATTRIBUTES_KEY => [ - 'stock_item' => ['is_in_stock' => true, 'qty' => 1000] + 'stock_item' => [ + StockItemInterface::IS_IN_STOCK => 1, + StockItemInterface::QTY => 1000, + StockItemInterface::IS_QTY_DECIMAL => 0, + StockItemInterface::SHOW_DEFAULT_NOTIFICATION_MESSAGE => 0, + StockItemInterface::USE_CONFIG_MIN_QTY => 0, + StockItemInterface::USE_CONFIG_MIN_SALE_QTY => 0, + StockItemInterface::MIN_QTY => 1, + StockItemInterface::MIN_SALE_QTY => 1, + StockItemInterface::MAX_SALE_QTY => 100, + StockItemInterface::USE_CONFIG_MAX_SALE_QTY => 0, + StockItemInterface::USE_CONFIG_BACKORDERS => 0, + StockItemInterface::BACKORDERS => 0, + StockItemInterface::USE_CONFIG_NOTIFY_STOCK_QTY => 0, + StockItemInterface::NOTIFY_STOCK_QTY => 0, + StockItemInterface::USE_CONFIG_QTY_INCREMENTS => 0, + StockItemInterface::QTY_INCREMENTS => 0, + StockItemInterface::USE_CONFIG_ENABLE_QTY_INC => 0, + StockItemInterface::ENABLE_QTY_INCREMENTS => 0, + StockItemInterface::USE_CONFIG_MANAGE_STOCK => 1, + StockItemInterface::MANAGE_STOCK => 1, + StockItemInterface::LOW_STOCK_DATE => null, + StockItemInterface::IS_DECIMAL_DIVIDED => 0, + StockItemInterface::STOCK_STATUS_CHANGED_AUTO => 0, + ], ], ProductInterface::CUSTOM_ATTRIBUTES => [ ['attribute_code' => 'url_key', 'value' => 'simple'], @@ -211,6 +236,10 @@ private function assertProductData(array $productData, array $resultData, array $resultData['custom_attributes'], $attribute['attribute_code'] ); + if ($attribute['attribute_code'] == 'category_ids') { + $this->assertEquals(array_values($attribute['value']), array_values($resultAttribute['value'])); + continue; + } $this->assertEquals($attribute['value'], $resultAttribute['value']); } foreach ($productData['extension_attributes']['stock_item'] as $key => $value) { From 5cec5498d29d23e1a19d66f69e085eed9896e935 Mon Sep 17 00:00:00 2001 From: DmytroPaidych <dimonovp@gmail.com> Date: Thu, 15 Oct 2020 17:13:46 +0200 Subject: [PATCH 104/139] MC-37896: Create automated test for "Reset Widget" --- .../Widget/Test/Mftf/Section/AdminNewWidgetSection.xml | 5 +++-- .../Magento/Widget/Test/Mftf/Test/AdminResetWidgetTest.xml | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/app/code/Magento/Widget/Test/Mftf/Section/AdminNewWidgetSection.xml b/app/code/Magento/Widget/Test/Mftf/Section/AdminNewWidgetSection.xml index 805c55f34ce9a..49eaf6b377859 100644 --- a/app/code/Magento/Widget/Test/Mftf/Section/AdminNewWidgetSection.xml +++ b/app/code/Magento/Widget/Test/Mftf/Section/AdminNewWidgetSection.xml @@ -12,7 +12,7 @@ <element name="widgetType" type="select" selector="#code"/> <element name="widgetDesignTheme" type="select" selector="#theme_id"/> <element name="continue" type="button" timeout="30" selector="#continue_button"/> - <element name="resetBtn" type="button" selector="//*[@class='page-actions-buttons']/button[@id='reset']" timeout="30"/> + <element name="resetBtn" type="button" selector=".page-actions-buttons .reset" timeout="30"/> <element name="widgetTitle" type="input" selector="#title"/> <element name="widgetStoreIds" type="select" selector="#store_ids"/> <element name="widgetSortOrder" type="input" selector="#sort_order"/> @@ -43,7 +43,7 @@ <element name="displayMode" type="select" selector="select[id*='display_mode']"/> <element name="restrictTypes" type="select" selector="select[id*='types']"/> <element name="saveAndContinue" type="button" selector="#save_and_edit_button" timeout="30"/> - <element name="widgetInstanceType" type="select" selector="//*[@class='admin__field-control control']/select[@id='instance_code']" /> + <element name="widgetInstanceType" type="select" selector=".admin__field-control .admin__control-select" /> <!-- Catalog Product List Widget Options --> <element name="title" type="input" selector="[name='parameters[title]']"/> <element name="displayPageControl" type="select" selector="[name='parameters[show_pager]']"/> @@ -51,3 +51,4 @@ <element name="cacheLifetime" type="input" selector="[name='parameters[cache_lifetime]']"/> </section> </sections> + diff --git a/app/code/Magento/Widget/Test/Mftf/Test/AdminResetWidgetTest.xml b/app/code/Magento/Widget/Test/Mftf/Test/AdminResetWidgetTest.xml index fd9ce8f3c37e9..5e053778fe7ed 100644 --- a/app/code/Magento/Widget/Test/Mftf/Test/AdminResetWidgetTest.xml +++ b/app/code/Magento/Widget/Test/Mftf/Test/AdminResetWidgetTest.xml @@ -23,7 +23,7 @@ </before> <after> <actionGroup ref="AdminDeleteWidgetActionGroup" stepKey="deleteWidget"> - <argument name="widget" value="{{ProductsListWidget}}"/> + <argument name="widget" value="ProductsListWidget"/> </actionGroup> <actionGroup ref="AdminLogoutActionGroup" stepKey="logoutFromAdmin"/> </after> From 1ff20ba12e960d937b7ebe49592a9b5619cb4035 Mon Sep 17 00:00:00 2001 From: Bohdan Shevchenko <1408sheva@gmail.com> Date: Thu, 15 Oct 2020 18:27:31 +0300 Subject: [PATCH 105/139] MC-37546: Create automated test for "Create new Category Update" --- ...torefrontCheckPresentSubCategoryActionGroup.xml | 6 +++--- .../Test/Mftf/Page/AdminCategoryEditPage.xml | 1 + .../AdminCategoryScheduleDesingUpdateSection.xml | 14 ++++++++++++++ 3 files changed, 18 insertions(+), 3 deletions(-) create mode 100644 app/code/Magento/Catalog/Test/Mftf/Section/AdminCategoryScheduleDesingUpdateSection.xml diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontCheckPresentSubCategoryActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontCheckPresentSubCategoryActionGroup.xml index 7cb3287614433..1799f6339a84d 100644 --- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontCheckPresentSubCategoryActionGroup.xml +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontCheckPresentSubCategoryActionGroup.xml @@ -16,8 +16,8 @@ <argument name="childCategoryName" type="string"/> </arguments> - <waitForElement selector="{{AdminCategorySidebarTreeSection.categoryHighlighted(parenCategoryName)}}" stepKey="waitForTopMenuLoaded"/> - <moveMouseOver selector="{{AdminCategorySidebarTreeSection.categoryHighlighted(parenCategoryName)}}" stepKey="moveMouseToParentCategory"/> - <seeElement selector="{{AdminCategorySidebarTreeSection.categoryHighlighted(childCategoryName)}}" stepKey="seeSubcategoryInTree"/> + <waitForElementVisible selector="{{StorefrontHeaderSection.NavigationCategoryByName(parenCategoryName)}}" stepKey="waitForTopMenuLoaded"/> + <moveMouseOver selector="{{StorefrontHeaderSection.NavigationCategoryByName(parenCategoryName)}}" stepKey="moveMouseToParentCategory"/> + <seeElement selector="{{StorefrontHeaderSection.NavigationCategoryByName(childCategoryName)}}" stepKey="seeSubcategoryInTree"/> </actionGroup> </actionGroups> diff --git a/app/code/Magento/Catalog/Test/Mftf/Page/AdminCategoryEditPage.xml b/app/code/Magento/Catalog/Test/Mftf/Page/AdminCategoryEditPage.xml index e1c8e5c75e9ac..5c5dfe8901563 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Page/AdminCategoryEditPage.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Page/AdminCategoryEditPage.xml @@ -19,5 +19,6 @@ <section name="AdminCategoryModalSection"/> <section name="AdminCategoryMessagesSection"/> <section name="AdminCategoryContentSection"/> + <section name="AdminCategoryScheduleDesingUpdateSection"/> </page> </pages> diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategoryScheduleDesingUpdateSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategoryScheduleDesingUpdateSection.xml new file mode 100644 index 0000000000000..e1b66b3c18260 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategoryScheduleDesingUpdateSection.xml @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> + <section name="AdminCategoryScheduleDesingUpdateSection"> + <element name="sectionHeader" type="button" selector="div[data-index='schedule_design_update'] .fieldset-wrapper-title" timeout="30"/> + <element name="sectionBody" type="text" selector="div[data-index='schedule_design_update'] .admin__fieldset-wrapper-content"/> + </section> +</sections> From 6f8021aef1a1cebc2c9ab5d0799dfc13a9638940 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20H=C3=BCnig?= <jhuenig@maxcluster.de> Date: Thu, 15 Oct 2020 20:01:53 +0200 Subject: [PATCH 106/139] add --no-tablespaces parameters to mysqldump --- .../integration/framework/Magento/TestFramework/Db/Mysql.php | 3 ++- .../Framework/Crontab/Test/Unit/CrontabManagerTest.php | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/dev/tests/integration/framework/Magento/TestFramework/Db/Mysql.php b/dev/tests/integration/framework/Magento/TestFramework/Db/Mysql.php index 7627d78df12dc..017669cba7e13 100644 --- a/dev/tests/integration/framework/Magento/TestFramework/Db/Mysql.php +++ b/dev/tests/integration/framework/Magento/TestFramework/Db/Mysql.php @@ -123,8 +123,9 @@ public function storeDbDump() } $format = sprintf( - '%s %s %s', + '%s %s %s %s', 'mysqldump --defaults-file=%s --host=%s --port=%s', + '--no-tablespaces' $additionalArguments, '%s > %s' ); diff --git a/lib/internal/Magento/Framework/Crontab/Test/Unit/CrontabManagerTest.php b/lib/internal/Magento/Framework/Crontab/Test/Unit/CrontabManagerTest.php index 904b76a1285e6..f990566f612a1 100644 --- a/lib/internal/Magento/Framework/Crontab/Test/Unit/CrontabManagerTest.php +++ b/lib/internal/Magento/Framework/Crontab/Test/Unit/CrontabManagerTest.php @@ -375,13 +375,13 @@ public function saveTasksDataProvider(): array ], [ 'tasks' => [ - ['command' => '{magentoRoot}run.php mysqldump db > db-$(date +%F).sql'] + ['command' => '{magentoRoot}run.php mysqldump --no-tablespaces db > db-$(date +%F).sql'] ], 'content' => '* * * * * /bin/php /var/www/cron.php', 'contentToSave' => '* * * * * /bin/php /var/www/cron.php' . PHP_EOL . CrontabManagerInterface::TASKS_BLOCK_START . ' ' . hash("sha256", BP) . PHP_EOL . '* * * * * ' . PHP_BINARY . ' /var/www/magento2/run.php' - . ' mysqldump db > db-\$(date +%%F).sql' . PHP_EOL + . ' mysqldump --no-tablespaces db > db-\$(date +%%F).sql' . PHP_EOL . CrontabManagerInterface::TASKS_BLOCK_END . ' ' . hash("sha256", BP) . PHP_EOL, ], ]; From 2bc78ad309c0f5b22d65dbfe260abfb739ca3e7b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20H=C3=BCnig?= <jonas@huenig.name> Date: Thu, 15 Oct 2020 22:33:20 +0200 Subject: [PATCH 107/139] Update Mysql.php add missing colon --- .../integration/framework/Magento/TestFramework/Db/Mysql.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dev/tests/integration/framework/Magento/TestFramework/Db/Mysql.php b/dev/tests/integration/framework/Magento/TestFramework/Db/Mysql.php index 017669cba7e13..54ae3dcb27a58 100644 --- a/dev/tests/integration/framework/Magento/TestFramework/Db/Mysql.php +++ b/dev/tests/integration/framework/Magento/TestFramework/Db/Mysql.php @@ -125,7 +125,7 @@ public function storeDbDump() $format = sprintf( '%s %s %s %s', 'mysqldump --defaults-file=%s --host=%s --port=%s', - '--no-tablespaces' + '--no-tablespaces', $additionalArguments, '%s > %s' ); From ab41eb8864b6c2980d71b4ee8b3d8e2c4ed93fd8 Mon Sep 17 00:00:00 2001 From: DmytroPaidych <dimonovp@gmail.com> Date: Fri, 16 Oct 2020 09:04:54 +0200 Subject: [PATCH 108/139] MC-37896: Create automated test for "Reset Widget" --- .../Widget/Test/Mftf/Section/AdminNewWidgetSection.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/code/Magento/Widget/Test/Mftf/Section/AdminNewWidgetSection.xml b/app/code/Magento/Widget/Test/Mftf/Section/AdminNewWidgetSection.xml index 49eaf6b377859..4064f8eb394ca 100644 --- a/app/code/Magento/Widget/Test/Mftf/Section/AdminNewWidgetSection.xml +++ b/app/code/Magento/Widget/Test/Mftf/Section/AdminNewWidgetSection.xml @@ -12,7 +12,7 @@ <element name="widgetType" type="select" selector="#code"/> <element name="widgetDesignTheme" type="select" selector="#theme_id"/> <element name="continue" type="button" timeout="30" selector="#continue_button"/> - <element name="resetBtn" type="button" selector=".page-actions-buttons .reset" timeout="30"/> + <element name="resetBtn" type="button" selector=".page-actions-buttons button#reset" timeout="30"/> <element name="widgetTitle" type="input" selector="#title"/> <element name="widgetStoreIds" type="select" selector="#store_ids"/> <element name="widgetSortOrder" type="input" selector="#sort_order"/> @@ -43,7 +43,7 @@ <element name="displayMode" type="select" selector="select[id*='display_mode']"/> <element name="restrictTypes" type="select" selector="select[id*='types']"/> <element name="saveAndContinue" type="button" selector="#save_and_edit_button" timeout="30"/> - <element name="widgetInstanceType" type="select" selector=".admin__field-control .admin__control-select" /> + <element name="widgetInstanceType" type="select" selector=".admin__field-control select#instance_code" /> <!-- Catalog Product List Widget Options --> <element name="title" type="input" selector="[name='parameters[title]']"/> <element name="displayPageControl" type="select" selector="[name='parameters[show_pager]']"/> From be5dbb9c032daa38628fafbbe3600af6cb4641e2 Mon Sep 17 00:00:00 2001 From: Bohdan Shevchenko <1408sheva@gmail.com> Date: Fri, 16 Oct 2020 10:55:38 +0300 Subject: [PATCH 109/139] MC-37546: Create automated test for "Create new Category Update" --- .../Magento/Catalog/Test/Mftf/Page/AdminCategoryEditPage.xml | 2 +- ...Section.xml => AdminCategoryScheduleDesignUpdateSection.xml} | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) rename app/code/Magento/Catalog/Test/Mftf/Section/{AdminCategoryScheduleDesingUpdateSection.xml => AdminCategoryScheduleDesignUpdateSection.xml} (90%) diff --git a/app/code/Magento/Catalog/Test/Mftf/Page/AdminCategoryEditPage.xml b/app/code/Magento/Catalog/Test/Mftf/Page/AdminCategoryEditPage.xml index 5c5dfe8901563..15fcf5f7d4000 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Page/AdminCategoryEditPage.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Page/AdminCategoryEditPage.xml @@ -19,6 +19,6 @@ <section name="AdminCategoryModalSection"/> <section name="AdminCategoryMessagesSection"/> <section name="AdminCategoryContentSection"/> - <section name="AdminCategoryScheduleDesingUpdateSection"/> + <section name="AdminCategoryScheduleDesignUpdateSection"/> </page> </pages> diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategoryScheduleDesingUpdateSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategoryScheduleDesignUpdateSection.xml similarity index 90% rename from app/code/Magento/Catalog/Test/Mftf/Section/AdminCategoryScheduleDesingUpdateSection.xml rename to app/code/Magento/Catalog/Test/Mftf/Section/AdminCategoryScheduleDesignUpdateSection.xml index e1b66b3c18260..a65d2c9e63bef 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategoryScheduleDesingUpdateSection.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategoryScheduleDesignUpdateSection.xml @@ -7,7 +7,7 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> - <section name="AdminCategoryScheduleDesingUpdateSection"> + <section name="AdminCategoryScheduleDesignUpdateSection"> <element name="sectionHeader" type="button" selector="div[data-index='schedule_design_update'] .fieldset-wrapper-title" timeout="30"/> <element name="sectionBody" type="text" selector="div[data-index='schedule_design_update'] .admin__fieldset-wrapper-content"/> </section> From 56fff4c5a8cf90c0d8914590fde7bd315b504c75 Mon Sep 17 00:00:00 2001 From: Myroslav Dobra <dmaraptor@gmail.com> Date: Fri, 16 Oct 2020 11:22:37 +0300 Subject: [PATCH 110/139] MC-29405: PHPStan: "Class does not have a constructor and must be instantiated without any parameters" errors --- .../Test/Unit/Model/Customer/Attribute/Backend/BillingTest.php | 1 - .../Unit/Model/Customer/Attribute/Backend/ShippingTest.php | 1 - .../Unit/Block/Adminhtml/Design/Config/Edit/SaveButtonTest.php | 3 --- 3 files changed, 5 deletions(-) diff --git a/app/code/Magento/Customer/Test/Unit/Model/Customer/Attribute/Backend/BillingTest.php b/app/code/Magento/Customer/Test/Unit/Model/Customer/Attribute/Backend/BillingTest.php index 4f318948097cc..65f9b62b426c0 100644 --- a/app/code/Magento/Customer/Test/Unit/Model/Customer/Attribute/Backend/BillingTest.php +++ b/app/code/Magento/Customer/Test/Unit/Model/Customer/Attribute/Backend/BillingTest.php @@ -12,7 +12,6 @@ use Magento\Eav\Model\Entity\Attribute\AbstractAttribute; use Magento\Framework\DataObject; use PHPUnit\Framework\TestCase; -use Psr\Log\LoggerInterface; class BillingTest extends TestCase { diff --git a/app/code/Magento/Customer/Test/Unit/Model/Customer/Attribute/Backend/ShippingTest.php b/app/code/Magento/Customer/Test/Unit/Model/Customer/Attribute/Backend/ShippingTest.php index 6270905ca2e85..1f5485309cc19 100644 --- a/app/code/Magento/Customer/Test/Unit/Model/Customer/Attribute/Backend/ShippingTest.php +++ b/app/code/Magento/Customer/Test/Unit/Model/Customer/Attribute/Backend/ShippingTest.php @@ -12,7 +12,6 @@ use Magento\Eav\Model\Entity\Attribute\AbstractAttribute; use Magento\Framework\DataObject; use PHPUnit\Framework\TestCase; -use Psr\Log\LoggerInterface; class ShippingTest extends TestCase { diff --git a/app/code/Magento/Theme/Test/Unit/Block/Adminhtml/Design/Config/Edit/SaveButtonTest.php b/app/code/Magento/Theme/Test/Unit/Block/Adminhtml/Design/Config/Edit/SaveButtonTest.php index 0ce450df39b59..b7f2def1c0fbd 100644 --- a/app/code/Magento/Theme/Test/Unit/Block/Adminhtml/Design/Config/Edit/SaveButtonTest.php +++ b/app/code/Magento/Theme/Test/Unit/Block/Adminhtml/Design/Config/Edit/SaveButtonTest.php @@ -7,10 +7,7 @@ namespace Magento\Theme\Test\Unit\Block\Adminhtml\Design\Config\Edit; -use Magento\Backend\Block\Widget\Context; -use Magento\Framework\UrlInterface; use Magento\Theme\Block\Adminhtml\Design\Config\Edit\SaveButton; -use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; class SaveButtonTest extends TestCase From 0a02873d0aa0889df7eb53ef5d4834c6c1fdedfd Mon Sep 17 00:00:00 2001 From: Viktor Kopin <viktor.kopin@transoftgroup.com> Date: Fri, 16 Oct 2020 11:50:18 +0300 Subject: [PATCH 111/139] MC-37665: Updating a category through the REST API will uncheck "Use Default Value" on a bunch of attributes --- .../CategoryRepository/PopulateWithValues.php | 52 ++++++++++--------- 1 file changed, 28 insertions(+), 24 deletions(-) diff --git a/app/code/Magento/Catalog/Model/CategoryRepository/PopulateWithValues.php b/app/code/Magento/Catalog/Model/CategoryRepository/PopulateWithValues.php index 2a313119f9c8e..847400ba77c26 100644 --- a/app/code/Magento/Catalog/Model/CategoryRepository/PopulateWithValues.php +++ b/app/code/Magento/Catalog/Model/CategoryRepository/PopulateWithValues.php @@ -15,6 +15,7 @@ use Magento\Eav\Model\Entity\Attribute\ScopedAttributeInterface; use Magento\Framework\Api\FilterBuilder; use Magento\Framework\Api\SearchCriteriaBuilder; +use Magento\Store\Model\Store; /** * Add data to category entity and populate with default values @@ -68,31 +69,34 @@ public function __construct( */ public function execute(CategoryInterface $category, array $existingData): void { - $storeId = $existingData['store_id']; - $overriddenValues = array_filter( - $category->getData(), - function ($key) use ($category, $storeId) { - /** @var Category $category */ - return $this->scopeOverriddenValue->containsValue( - CategoryInterface::class, - $category, - $key, - $storeId - ); - }, - ARRAY_FILTER_USE_KEY - ); - $defaultValues = array_diff_key($category->getData(), $overriddenValues); - array_walk( - $defaultValues, - function (&$value, $key) { - $attributes = $this->getAttributes(); - if (isset($attributes[$key]) && !$attributes[$key]->isStatic()) { - $value = null; + $storeId = $existingData['store_id'] ?? Store::DEFAULT_STORE_ID; + if ((int)$storeId !== Store::DEFAULT_STORE_ID) { + $overriddenValues = array_filter( + $category->getData(), + function ($key) use ($category, $storeId) { + /** @var Category $category */ + return $this->scopeOverriddenValue->containsValue( + CategoryInterface::class, + $category, + $key, + $storeId + ); + }, + ARRAY_FILTER_USE_KEY + ); + $defaultValues = array_diff_key($category->getData(), $overriddenValues); + array_walk( + $defaultValues, + function (&$value, $key) { + $attributes = $this->getAttributes(); + if (isset($attributes[$key]) && !$attributes[$key]->isStatic()) { + $value = null; + } } - } - ); - $category->addData($defaultValues); + ); + $category->addData($defaultValues); + } + $category->addData($existingData); $useDefaultAttributes = array_filter( $category->getData(), From 8d97104395928f7e328be329c46188992a616a2c Mon Sep 17 00:00:00 2001 From: Myroslav Dobra <dmaraptor@gmail.com> Date: Fri, 16 Oct 2020 14:43:20 +0300 Subject: [PATCH 112/139] MC-29402: PHPStan: "Anonymous function has an unused use" errors --- app/code/Magento/Customer/Controller/Adminhtml/Index.php | 1 - setup/src/Magento/Setup/Console/Command/RollbackCommand.php | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/app/code/Magento/Customer/Controller/Adminhtml/Index.php b/app/code/Magento/Customer/Controller/Adminhtml/Index.php index f03f55b16e0c7..9595e473c1869 100644 --- a/app/code/Magento/Customer/Controller/Adminhtml/Index.php +++ b/app/code/Magento/Customer/Controller/Adminhtml/Index.php @@ -289,7 +289,6 @@ protected function prepareDefaultCustomerTitle(\Magento\Backend\Model\View\Resul protected function _addSessionErrorMessages($messages) { $messages = (array)$messages; - $session = $this->_getSession(); $callback = function ($error) { if (!$error instanceof Error) { diff --git a/setup/src/Magento/Setup/Console/Command/RollbackCommand.php b/setup/src/Magento/Setup/Console/Command/RollbackCommand.php index e4616ae5e271b..e114c84ba79bc 100644 --- a/setup/src/Magento/Setup/Console/Command/RollbackCommand.php +++ b/setup/src/Magento/Setup/Console/Command/RollbackCommand.php @@ -122,7 +122,8 @@ protected function execute(InputInterface $input, OutputInterface $output) // we must have an exit code higher than zero to indicate something was wrong return \Magento\Framework\Console\Cli::RETURN_FAILURE; } - $returnValue = $this->maintenanceModeEnabler->executeInMaintenanceMode( + + return $this->maintenanceModeEnabler->executeInMaintenanceMode( function () use ($input, $output) { try { $helper = $this->getHelper('question'); @@ -152,7 +153,6 @@ function () use ($input, $output) { $output, false ); - return $returnValue; } /** From eaa08eef94eabd569ab29fb476df28591ae65e1a Mon Sep 17 00:00:00 2001 From: Viktor Kopin <viktor.kopin@transoftgroup.com> Date: Fri, 16 Oct 2020 15:32:29 +0300 Subject: [PATCH 113/139] MC-37665: Updating a category through the REST API will uncheck "Use Default Value" on a bunch of attributes --- .../CategoryRepository/PopulateWithValues.php | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/app/code/Magento/Catalog/Model/CategoryRepository/PopulateWithValues.php b/app/code/Magento/Catalog/Model/CategoryRepository/PopulateWithValues.php index 847400ba77c26..c6feb049e1a10 100644 --- a/app/code/Magento/Catalog/Model/CategoryRepository/PopulateWithValues.php +++ b/app/code/Magento/Catalog/Model/CategoryRepository/PopulateWithValues.php @@ -42,6 +42,11 @@ class PopulateWithValues */ private $filterBuilder; + /** + * @var AttributeInterface[] + */ + private $attributes; + /** * @param ScopeOverriddenValue $scopeOverriddenValue * @param AttributeRepository $attributeRepository @@ -120,8 +125,12 @@ function () { * * @return AttributeInterface[] */ - private function getAttributes() + private function getAttributes(): array { + if ($this->attributes) { + return $this->attributes; + } + $searchResult = $this->attributeRepository->getList( $this->searchCriteriaBuilder->addFilters( [ @@ -133,11 +142,12 @@ private function getAttributes() ] )->create() ); - $result = []; + + $this->attributes = []; foreach ($searchResult->getItems() as $attribute) { - $result[$attribute->getAttributeCode()] = $attribute; + $this->attributes[$attribute->getAttributeCode()] = $attribute; } - return $result; + return $this->attributes; } } From 40134a616f101df6e98283259810de813d4f66e7 Mon Sep 17 00:00:00 2001 From: TuNa <ladiesman9x@gmail.com> Date: Fri, 16 Oct 2020 21:49:51 +0700 Subject: [PATCH 114/139] Fix missing escape less calc up update up --- .../web/css/source/module/order/_payment-shipping.less | 7 ++++++- .../Magento/backend/web/css/source/forms/_fields.less | 6 ++++-- .../Magento/luma/Magento_Sales/web/css/source/_module.less | 6 +++++- 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/app/design/adminhtml/Magento/backend/Magento_Sales/web/css/source/module/order/_payment-shipping.less b/app/design/adminhtml/Magento/backend/Magento_Sales/web/css/source/module/order/_payment-shipping.less index 2c55d243ebe07..121de6e7b6d8a 100644 --- a/app/design/adminhtml/Magento/backend/Magento_Sales/web/css/source/module/order/_payment-shipping.less +++ b/app/design/adminhtml/Magento/backend/Magento_Sales/web/css/source/module/order/_payment-shipping.less @@ -9,13 +9,15 @@ .admin__payment-method-wrapper { margin: 0; - width: calc(50% - @indent__l); + width: ~'calc(50% - @{indent__l})'; + .admin__field { margin-left: 0; &:first-child { margin-top: 1.5rem; } } + .admin__payment-methods { margin: 0; } @@ -62,6 +64,7 @@ position: absolute; right: 0; top: 0; + span { background-color: @color-white; display: block; @@ -71,6 +74,7 @@ position: absolute; top: 43px; } + .order-shipping-address & { span { top: 0; @@ -102,6 +106,7 @@ + .order-payment-currency { margin-top: @indent__s; } + .admin__table-secondary { margin-top: @indent__s; &:extend(.abs-admin__table-secondary-edit-order all); diff --git a/app/design/adminhtml/Magento/backend/web/css/source/forms/_fields.less b/app/design/adminhtml/Magento/backend/web/css/source/forms/_fields.less index 256ac453578df..b86b0005e88fb 100644 --- a/app/design/adminhtml/Magento/backend/web/css/source/forms/_fields.less +++ b/app/design/adminhtml/Magento/backend/web/css/source/forms/_fields.less @@ -163,6 +163,7 @@ .admin__field-control { padding-top: 7px; } + .admin__field-option { padding-top: 0; } @@ -361,6 +362,7 @@ cursor: inherit; opacity: 1; outline: inherit; + .admin__action-multiselect-wrap { .admin__action-multiselect { .__form-control-pattern__disabled(); @@ -433,7 +435,7 @@ font-size: 1.7rem; font-weight: @font-weight__bold; padding: 1.7rem 0; - width: calc(100% - @indent__l); + width: ~'calc(100% - @{indent__l})'; } .admin__field-option { @@ -704,6 +706,7 @@ width: 100%; } } + & > .admin__field-label { text-align: left; } @@ -819,4 +822,3 @@ overflow: hidden; } } - diff --git a/app/design/frontend/Magento/luma/Magento_Sales/web/css/source/_module.less b/app/design/frontend/Magento/luma/Magento_Sales/web/css/source/_module.less index bab8a2abb9b93..f8ab8ddb088ec 100644 --- a/app/design/frontend/Magento/luma/Magento_Sales/web/css/source/_module.less +++ b/app/design/frontend/Magento/luma/Magento_Sales/web/css/source/_module.less @@ -210,6 +210,7 @@ .items-qty { &:extend(.abs-reset-list all); + .item { white-space: nowrap; } @@ -347,13 +348,15 @@ .product-item-name { float: left; - width: calc(100% - 20px); + width: calc(~'100% - 20px'); } + .product-item::after { clear: both; content: ''; display: table; } + .product-item { .label { &:extend(.abs-visually-hidden all); @@ -491,6 +494,7 @@ .data.table .col.options { padding: 0 10px 15px; + &:before { display: none; } From f882de3751ca673841eba17cfc7b7ea458314b2c Mon Sep 17 00:00:00 2001 From: Cristian Partica <cpartica@magento.com> Date: Fri, 16 Oct 2020 10:17:18 -0500 Subject: [PATCH 115/139] 29251 revert is_available_for_selection --- .../Model/Options/Collection.php | 83 +------------------ .../etc/schema.graphqls | 2 - 2 files changed, 1 insertion(+), 84 deletions(-) diff --git a/app/code/Magento/ConfigurableProductGraphQl/Model/Options/Collection.php b/app/code/Magento/ConfigurableProductGraphQl/Model/Options/Collection.php index 7a27964897828..5e3666407a383 100644 --- a/app/code/Magento/ConfigurableProductGraphQl/Model/Options/Collection.php +++ b/app/code/Magento/ConfigurableProductGraphQl/Model/Options/Collection.php @@ -10,14 +10,10 @@ use Magento\Catalog\Api\Data\ProductInterface; use Magento\Catalog\Model\Product; use Magento\Catalog\Model\ProductFactory; -use Magento\Catalog\Model\ProductRepository; -use Magento\ConfigurableProduct\Helper\Data; use Magento\ConfigurableProduct\Model\Product\Type\Configurable\Attribute; use Magento\ConfigurableProduct\Model\ResourceModel\Product\Type\Configurable\Attribute\Collection as AttributeCollection; use Magento\ConfigurableProduct\Model\ResourceModel\Product\Type\Configurable\Attribute\CollectionFactory; -use Magento\Framework\Api\SearchCriteriaBuilder; -use Magento\Framework\App\ObjectManager; use Magento\Framework\EntityManager\MetadataPool; /** @@ -35,36 +31,11 @@ class Collection */ private $productFactory; - /** - * @var ProductRepository - */ - private $productRepository; - /** * @var MetadataPool */ private $metadataPool; - /** - * @var Data - */ - private $configurableProductHelper; - - /** - * @var Metadata - */ - private $optionsMetadata; - - /** - * @var SelectionUidFormatter - */ - private $selectionUidFormatter; - - /** - * @var SearchCriteriaBuilder - */ - private $searchCriteriaBuilder; - /** * @var int[] */ @@ -78,32 +49,16 @@ class Collection /** * @param CollectionFactory $attributeCollectionFactory * @param ProductFactory $productFactory - * @param ProductRepository $productRepository * @param MetadataPool $metadataPool - * @param Data $configurableProductHelper - * @param Metadata $optionsMetadata - * @param SelectionUidFormatter $selectionUidFormatter - * @param SearchCriteriaBuilder $searchCriteriaBuilder */ public function __construct( CollectionFactory $attributeCollectionFactory, ProductFactory $productFactory, - ProductRepository $productRepository, - MetadataPool $metadataPool, - Data $configurableProductHelper, - Metadata $optionsMetadata, - SelectionUidFormatter $selectionUidFormatter, - SearchCriteriaBuilder $searchCriteriaBuilder + MetadataPool $metadataPool ) { $this->attributeCollectionFactory = $attributeCollectionFactory; $this->productFactory = $productFactory; - $this->productRepository = $productRepository; $this->metadataPool = $metadataPool; - $this->configurableProductHelper = $configurableProductHelper; - $this->optionsMetadata = $optionsMetadata; - $this->selectionUidFormatter = $selectionUidFormatter; - $this->searchCriteriaBuilder = $searchCriteriaBuilder ?? - ObjectManager::getInstance()->get(SearchCriteriaBuilder::class); } /** @@ -156,8 +111,6 @@ private function fetch() : array $attributeCollection->setProductFilter($product); } - $products = $this->getProducts($this->productIds); - /** @var Attribute $attribute */ foreach ($attributeCollection->getItems() as $attribute) { $productId = (int)$attribute->getProductId(); @@ -175,42 +128,8 @@ private function fetch() : array $this->attributeMap[$productId][$attribute->getId()]['values'] = $attributeData['options']; $this->attributeMap[$productId][$attribute->getId()]['label'] = $attribute->getProductAttribute()->getStoreLabel(); - - if (isset($products[$productId])) { - $options = $this->configurableProductHelper->getOptions( - $products[$productId], - $this->optionsMetadata->getAllowProducts($products[$productId]) - ); - foreach ($attributeData['options'] as $index => $value) { - $this->attributeMap[$productId][$attribute->getId()]['values'][$index]['uid'] - = $this->selectionUidFormatter->encode((int)$attribute->getId(), (int)$value['value_index']); - $this->attributeMap[$productId][$attribute->getId()]['values'][$index] - ['is_available_for_selection'] = - isset($options[$attribute->getAttributeId()][$value['value_index']]) - && $options[$attribute->getAttributeId()][$value['value_index']]; - } - } } return $this->attributeMap; } - - /** - * Load products by link field ids - * - * @param int[] $productIds - * @return ProductInterface[] - */ - private function getProducts($productIds) - { - $linkField = $this->metadataPool->getMetadata(ProductInterface::class)->getLinkField(); - $this->searchCriteriaBuilder->addFilter($linkField, $productIds, 'in'); - $searchCriteria = $this->searchCriteriaBuilder->create(); - $products = $this->productRepository->getList($searchCriteria)->getItems(); - $productsLinkFieldMap = []; - foreach ($products as $product) { - $productsLinkFieldMap[$product->getData($linkField)] = $product; - } - return $productsLinkFieldMap; - } } diff --git a/app/code/Magento/ConfigurableProductGraphQl/etc/schema.graphqls b/app/code/Magento/ConfigurableProductGraphQl/etc/schema.graphqls index 88d0f8e212acf..6a58f7a77dfe2 100644 --- a/app/code/Magento/ConfigurableProductGraphQl/etc/schema.graphqls +++ b/app/code/Magento/ConfigurableProductGraphQl/etc/schema.graphqls @@ -35,8 +35,6 @@ type ConfigurableProductOptions @doc(description: "ConfigurableProductOptions de } type ConfigurableProductOptionsValues @doc(description: "ConfigurableProductOptionsValues contains the index number assigned to a configurable product option") { - uid: ID! - is_available_for_selection: Boolean! value_index: Int @doc(description: "A unique index number assigned to the configurable product option") label: String @doc(description: "The label of the product") default_label: String @doc(description: "The label of the product on the default store") From d0249eecfd93e9d5725c77b31c76f31c3f08c6c9 Mon Sep 17 00:00:00 2001 From: Andrii Kasian <akasian@magento.com> Date: Fri, 16 Oct 2020 17:25:51 -0500 Subject: [PATCH 116/139] MessageValidator fails on hash arrays that dont have 0 element Magento\Framework\MessageQueue\Test\Unit\MessageValidatorTest::testInvalidMessageType with data set #11 (array('object_interface', 'Magento\Customer\Api\Data\Cus...face[]'), array(23, 545), 'Data in topic "topic" must be...e[]". ') PHPUnit\Framework\Exception: Notice: Undefined offset: 0 in /magento/lib/internal/Magento/Framework/MessageQueue/MessageValidator.php:157. --- .../Magento/Framework/MessageQueue/MessageValidator.php | 4 ++-- .../MessageQueue/Test/Unit/MessageValidatorTest.php | 8 ++++++++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/lib/internal/Magento/Framework/MessageQueue/MessageValidator.php b/lib/internal/Magento/Framework/MessageQueue/MessageValidator.php index 45ce351ed97bb..ffa980c882640 100644 --- a/lib/internal/Magento/Framework/MessageQueue/MessageValidator.php +++ b/lib/internal/Magento/Framework/MessageQueue/MessageValidator.php @@ -119,7 +119,7 @@ protected function validatePrimitiveType($message, $messageType, $topic) $realType = $this->getRealType($message); if ($realType == 'array' && count($message) == 0) { return; - } elseif ($realType == 'array' && count($message) > 0) { + } elseif ($realType == 'array' && isset($message[0])) { $realType = $this->getRealType($message[0]); $compareType = preg_replace('/\[\]/', '', $messageType); } @@ -153,7 +153,7 @@ protected function validateClassType($message, $messageType, $topic) $realType = $this->getRealType($message); if ($realType == 'array' && count($message) == 0) { return; - } elseif ($realType == 'array' && count($message) > 0) { + } elseif ($realType == 'array' && isset($message[0])) { $message = $message[0]; $compareType = preg_replace('/\[\]/', '', $messageType); } diff --git a/lib/internal/Magento/Framework/MessageQueue/Test/Unit/MessageValidatorTest.php b/lib/internal/Magento/Framework/MessageQueue/Test/Unit/MessageValidatorTest.php index d324a9dbcfda9..c7d3e35aee3de 100644 --- a/lib/internal/Magento/Framework/MessageQueue/Test/Unit/MessageValidatorTest.php +++ b/lib/internal/Magento/Framework/MessageQueue/Test/Unit/MessageValidatorTest.php @@ -255,6 +255,14 @@ public function getQueueConfigRequestType() $customerMock, 'Data in topic "topic" must be of type "Magento\Customer\Api\Data\CustomerInterface[]". ' ], + [ + [ + CommunicationConfig::TOPIC_REQUEST_TYPE => CommunicationConfig::TOPIC_REQUEST_TYPE_CLASS, + CommunicationConfig::TOPIC_REQUEST => 'Magento\Customer\Api\Data\CustomerInterface[]' + ], + [1=>23, 3=>545], + 'Data in topic "topic" must be of type "Magento\Customer\Api\Data\CustomerInterface[]". ' + ], ]; } } From f4d2ff81c74a62007cf6430b8371d06a54a0caa3 Mon Sep 17 00:00:00 2001 From: mage2pratik <magepratik@gmail.com> Date: Sun, 18 Oct 2020 01:30:55 +0530 Subject: [PATCH 117/139] XSD URN format --- .../TestModuleDefaultHydrator/etc/extension_attributes.xml | 2 +- .../_files/Magento/TestModuleDefaultHydrator/etc/module.xml | 2 +- .../Magento/Framework/View/_files/UiComponent/theme/theme.xml | 2 +- .../Magento/Framework/View/_files/static/theme/theme.xml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/dev/tests/api-functional/_files/Magento/TestModuleDefaultHydrator/etc/extension_attributes.xml b/dev/tests/api-functional/_files/Magento/TestModuleDefaultHydrator/etc/extension_attributes.xml index 96dd60809754a..e1154f407e857 100644 --- a/dev/tests/api-functional/_files/Magento/TestModuleDefaultHydrator/etc/extension_attributes.xml +++ b/dev/tests/api-functional/_files/Magento/TestModuleDefaultHydrator/etc/extension_attributes.xml @@ -5,7 +5,7 @@ * See COPYING.txt for license details. */ --> -<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../lib/internal/Magento/Framework/Api/etc/extension_attributes.xsd"> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Api/etc/extension_attributes.xsd"> <extension_attributes for="Magento\Customer\Api\Data\CustomerInterface"> <attribute code="extension_attribute" type="Magento\TestModuleDefaultHydrator\Api\Data\ExtensionAttributeInterface" /> </extension_attributes> diff --git a/dev/tests/api-functional/_files/Magento/TestModuleDefaultHydrator/etc/module.xml b/dev/tests/api-functional/_files/Magento/TestModuleDefaultHydrator/etc/module.xml index ca4ded8ff3190..a8acf9c4acc77 100644 --- a/dev/tests/api-functional/_files/Magento/TestModuleDefaultHydrator/etc/module.xml +++ b/dev/tests/api-functional/_files/Magento/TestModuleDefaultHydrator/etc/module.xml @@ -5,7 +5,7 @@ * See COPYING.txt for license details. */ --> -<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../lib/internal/Magento/Framework/Module/etc/module.xsd"> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd"> <module name="Magento_TestModuleDefaultHydrator"> </module> </config> diff --git a/dev/tests/integration/testsuite/Magento/Framework/View/_files/UiComponent/theme/theme.xml b/dev/tests/integration/testsuite/Magento/Framework/View/_files/UiComponent/theme/theme.xml index 532670df30dfe..f1360542ff011 100644 --- a/dev/tests/integration/testsuite/Magento/Framework/View/_files/UiComponent/theme/theme.xml +++ b/dev/tests/integration/testsuite/Magento/Framework/View/_files/UiComponent/theme/theme.xml @@ -4,6 +4,6 @@ * See COPYING.txt for license details. */ --> -<theme xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../lib/internal/Magento/Framework/Config/etc/theme.xsd"> +<theme xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Config/etc/theme.xsd"> <title>Test theme diff --git a/dev/tests/integration/testsuite/Magento/Framework/View/_files/static/theme/theme.xml b/dev/tests/integration/testsuite/Magento/Framework/View/_files/static/theme/theme.xml index 532670df30dfe..f1360542ff011 100644 --- a/dev/tests/integration/testsuite/Magento/Framework/View/_files/static/theme/theme.xml +++ b/dev/tests/integration/testsuite/Magento/Framework/View/_files/static/theme/theme.xml @@ -4,6 +4,6 @@ * See COPYING.txt for license details. */ --> - + Test theme From 3132e0ae2bceeb2de38c357b3072f927ea0f94a0 Mon Sep 17 00:00:00 2001 From: Kate Kyzyma Date: Mon, 19 Oct 2020 09:53:31 +0300 Subject: [PATCH 118/139] fix minor code style issues --- .../AdminOpenCmsBlocksGridActionGroup.xml | 27 +++++++++---------- ...dminSelectCMSBlockStoreViewActionGroup.xml | 3 ++- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/app/code/Magento/Cms/Test/Mftf/ActionGroup/AdminOpenCmsBlocksGridActionGroup.xml b/app/code/Magento/Cms/Test/Mftf/ActionGroup/AdminOpenCmsBlocksGridActionGroup.xml index fca85651f7fda..4b57e0c1274f6 100644 --- a/app/code/Magento/Cms/Test/Mftf/ActionGroup/AdminOpenCmsBlocksGridActionGroup.xml +++ b/app/code/Magento/Cms/Test/Mftf/ActionGroup/AdminOpenCmsBlocksGridActionGroup.xml @@ -1,19 +1,18 @@ - + - - - Goes to the Cms Blocks grid page. - - - - - + + + Goes to the Cms Blocks grid page. + + + + diff --git a/app/code/Magento/Cms/Test/Mftf/ActionGroup/AdminSelectCMSBlockStoreViewActionGroup.xml b/app/code/Magento/Cms/Test/Mftf/ActionGroup/AdminSelectCMSBlockStoreViewActionGroup.xml index 8c543e29c1ed7..e5b8caf1e209f 100644 --- a/app/code/Magento/Cms/Test/Mftf/ActionGroup/AdminSelectCMSBlockStoreViewActionGroup.xml +++ b/app/code/Magento/Cms/Test/Mftf/ActionGroup/AdminSelectCMSBlockStoreViewActionGroup.xml @@ -5,6 +5,7 @@ * See COPYING.txt for license details. */ --> + @@ -12,6 +13,6 @@ - + From b0a3cf71e5809ac1037ab803736126e0039b4460 Mon Sep 17 00:00:00 2001 From: SmVladyslav Date: Mon, 19 Oct 2020 11:12:58 +0300 Subject: [PATCH 119/139] MC-35783: "1 item(s) need your attention." still visible in mini cart after product remove --- .../Test/Mftf/Data/NonexistentProductData.xml | 19 +++++++++++++++++++ .../Section/StorefrontMinicartSection.xml | 2 +- 2 files changed, 20 insertions(+), 1 deletion(-) create mode 100644 app/code/Magento/Catalog/Test/Mftf/Data/NonexistentProductData.xml diff --git a/app/code/Magento/Catalog/Test/Mftf/Data/NonexistentProductData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/NonexistentProductData.xml new file mode 100644 index 0000000000000..73b0765394333 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/Data/NonexistentProductData.xml @@ -0,0 +1,19 @@ + + + + + + NonexistentProductSku + 1 + + + SecondNonexistentProductSku + 1 + + diff --git a/app/code/Magento/Checkout/Test/Mftf/Section/StorefrontMinicartSection.xml b/app/code/Magento/Checkout/Test/Mftf/Section/StorefrontMinicartSection.xml index dcab48dbc5368..1ecf97c50c81a 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Section/StorefrontMinicartSection.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Section/StorefrontMinicartSection.xml @@ -39,7 +39,7 @@ - + From 937295074db740e43fe83c3622300af176a4a6ba Mon Sep 17 00:00:00 2001 From: Namrata Vora Date: Mon, 19 Oct 2020 14:06:38 +0530 Subject: [PATCH 120/139] Removed sortOrder from messages, authentication, progressBar, estimation, and sidebar checkout components as they are already been rendered without those sortOrders and individual getRegions at app/code/Magento/Checkout/view/frontend/web/template/onepage.html --- .../Checkout/view/frontend/layout/checkout_index_index.xml | 5 ----- 1 file changed, 5 deletions(-) diff --git a/app/code/Magento/Checkout/view/frontend/layout/checkout_index_index.xml b/app/code/Magento/Checkout/view/frontend/layout/checkout_index_index.xml index 192f20653f8c3..9b2497efb12d0 100644 --- a/app/code/Magento/Checkout/view/frontend/layout/checkout_index_index.xml +++ b/app/code/Magento/Checkout/view/frontend/layout/checkout_index_index.xml @@ -32,12 +32,10 @@ - 0 Magento_Ui/js/view/messages messages - 1 Magento_Checkout/js/view/authentication authentication @@ -50,7 +48,6 @@ - 0 Magento_Checkout/js/view/progress-bar progressBar @@ -61,7 +58,6 @@ - 10 Magento_Checkout/js/view/estimation estimation @@ -335,7 +331,6 @@ - 50 Magento_Checkout/js/view/sidebar sidebar From c643d79b37f9501c7ab24451956c5e906bfd128c Mon Sep 17 00:00:00 2001 From: Viktor Kopin Date: Mon, 19 Oct 2020 17:00:39 +0300 Subject: [PATCH 121/139] MC-37665: Updating a category through the REST API will uncheck "Use Default Value" on a bunch of attributes --- .../Catalog/Api/CategoryRepositoryTest.php | 46 +++++++------------ 1 file changed, 17 insertions(+), 29 deletions(-) diff --git a/dev/tests/api-functional/testsuite/Magento/Catalog/Api/CategoryRepositoryTest.php b/dev/tests/api-functional/testsuite/Magento/Catalog/Api/CategoryRepositoryTest.php index 6ce922eab21f1..5623edca62b9a 100644 --- a/dev/tests/api-functional/testsuite/Magento/Catalog/Api/CategoryRepositoryTest.php +++ b/dev/tests/api-functional/testsuite/Magento/Catalog/Api/CategoryRepositoryTest.php @@ -4,6 +4,7 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + namespace Magento\Catalog\Api; use Magento\Authorization\Model\Role; @@ -157,7 +158,7 @@ public function testDelete() UrlRewrite::ENTITY_ID => $categoryId, UrlRewrite::ENTITY_TYPE => CategoryUrlRewriteGenerator::ENTITY_TYPE ]; - /** @var \Magento\UrlRewrite\Service\V1\Data\UrlRewrite $urlRewrite*/ + /** @var \Magento\UrlRewrite\Service\V1\Data\UrlRewrite $urlRewrite */ $urlRewrite = $storage->findOneByData($data); // Assert that a url rewrite is auto-generated for the category created from the data fixture @@ -432,14 +433,9 @@ protected function updateCategory($id, $data, ?string $token = null) if ($token) { $serviceInfo['rest']['token'] = $serviceInfo['soap']['token'] = $token; } + $data['id'] = $id; - if (TESTS_WEB_API_ADAPTER == self::ADAPTER_SOAP) { - $data['id'] = $id; - return $this->_webApiCall($serviceInfo, ['id' => $id, 'category' => $data]); - } else { - $data['id'] = $id; - return $this->_webApiCall($serviceInfo, ['id' => $id, 'category' => $data]); - } + return $this->_webApiCall($serviceInfo, ['id' => $id, 'category' => $data]); } /** @@ -619,31 +615,23 @@ protected function updateCategoryForSpecificStore( ?string $token = null, string $storeCode = 'default' ) { - $serviceInfo = - [ - 'rest' => [ - 'resourcePath' => self::RESOURCE_PATH . '/' . $id, - 'httpMethod' => \Magento\Framework\Webapi\Rest\Request::HTTP_METHOD_PUT, - ], - 'soap' => [ - 'service' => self::SERVICE_NAME, - 'serviceVersion' => 'V1', - 'operation' => self::SERVICE_NAME . 'Save', - ], - ]; + $serviceInfo = [ + 'rest' => [ + 'resourcePath' => self::RESOURCE_PATH . '/' . $id, + 'httpMethod' => \Magento\Framework\Webapi\Rest\Request::HTTP_METHOD_PUT, + ], + 'soap' => [ + 'service' => self::SERVICE_NAME, + 'serviceVersion' => 'V1', + 'operation' => self::SERVICE_NAME . 'Save', + ], + ]; if ($token) { $serviceInfo['rest']['token'] = $serviceInfo['soap']['token'] = $token; } + $data['id'] = $id; - if (TESTS_WEB_API_ADAPTER == self::ADAPTER_SOAP) { - $data['id'] = $id; - - return $this->_webApiCall($serviceInfo, ['id' => $id, 'category' => $data], null, $storeCode); - } else { - $data['id'] = $id; - - return $this->_webApiCall($serviceInfo, ['id' => $id, 'category' => $data], null, $storeCode); - } + return $this->_webApiCall($serviceInfo, ['id' => $id, 'category' => $data], null, $storeCode); } /** From 9d4c1e55f4ff581a7eb51542b5e1fcd50aff3663 Mon Sep 17 00:00:00 2001 From: sdzhepa Date: Mon, 19 Oct 2020 13:59:56 -0500 Subject: [PATCH 122/139] Fix mistake in stale.yml file formatting --- .github/stale.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/stale.yml b/.github/stale.yml index 10589b97ea9b3..0a89a432699e0 100644 --- a/.github/stale.yml +++ b/.github/stale.yml @@ -1,7 +1,7 @@ # Configuration for probot-stale - https://github.com/probot/stale # Number of days of inactivity before an Issue or Pull Request becomes stale - daysUntilStale: 76 +daysUntilStale: 76 # Number of days of inactivity before an Issue or Pull Request with the stale label is closed. # Set to false to disable. If disabled, issues still need to be closed manually, but will remain marked as stale. From a63cf9e824a9bf3d8ec14c409d9ac00e354e5640 Mon Sep 17 00:00:00 2001 From: Cristian Partica Date: Mon, 19 Oct 2020 14:24:43 -0500 Subject: [PATCH 123/139] 28550 fix static --- .../ConfigurableProduct/Test/Unit/Helper/DataTest.php | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/app/code/Magento/ConfigurableProduct/Test/Unit/Helper/DataTest.php b/app/code/Magento/ConfigurableProduct/Test/Unit/Helper/DataTest.php index 963322e2f2c57..87357d3c927da 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Unit/Helper/DataTest.php +++ b/app/code/Magento/ConfigurableProduct/Test/Unit/Helper/DataTest.php @@ -59,7 +59,7 @@ protected function setUp(): void $objectManager->setBackwardCompatibleProperty($this->_model, 'imageUrlBuilder', $this->imageUrlBuilder); } - public function testGetAllowAttributes() + public function testGetAllowAttributes(): void { $typeInstanceMock = $this->createMock(Configurable::class); $typeInstanceMock->expects($this->once()) @@ -82,7 +82,7 @@ public function testGetAllowAttributes() * @param array $data * @dataProvider getOptionsDataProvider */ - public function testGetOptions(array $expected, array $data) + public function testGetOptions(array $expected, array $data): array { if (count($data['allowed_products'])) { $imageHelper1 = $this->getMockBuilder(Image::class) @@ -118,8 +118,9 @@ public function testGetOptions(array $expected, array $data) /** * @return array + * @SuppressWarnings(PHPMD.ExcessiveMethodLength) */ - public function getOptionsDataProvider() + public function getOptionsDataProvider(): array { $currentProductMock = $this->createPartialMock( Product::class, @@ -225,7 +226,7 @@ public function getOptionsDataProvider() * @param string $key * @return string */ - public function getDataCallback($key) + public function getDataCallback($key): string { $map = []; for ($k = 1; $k < 3; $k++) { @@ -289,7 +290,7 @@ public function testGetGalleryImages() /** * @return Collection */ - private function getImagesCollection() + private function getImagesCollection(): MockObject { $collectionMock = $this->getMockBuilder(Collection::class) ->disableOriginalConstructor() From 6384b9668752a3918cd22f3bfadb0e307055bcb1 Mon Sep 17 00:00:00 2001 From: Cristian Partica Date: Mon, 19 Oct 2020 15:42:47 -0500 Subject: [PATCH 124/139] 29251 fix static --- .../Magento/ConfigurableProduct/Test/Unit/Helper/DataTest.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/code/Magento/ConfigurableProduct/Test/Unit/Helper/DataTest.php b/app/code/Magento/ConfigurableProduct/Test/Unit/Helper/DataTest.php index 87357d3c927da..d1cd57e59ffe6 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Unit/Helper/DataTest.php +++ b/app/code/Magento/ConfigurableProduct/Test/Unit/Helper/DataTest.php @@ -59,7 +59,7 @@ protected function setUp(): void $objectManager->setBackwardCompatibleProperty($this->_model, 'imageUrlBuilder', $this->imageUrlBuilder); } - public function testGetAllowAttributes(): void + public function testGetAllowAttributes() { $typeInstanceMock = $this->createMock(Configurable::class); $typeInstanceMock->expects($this->once()) @@ -82,7 +82,7 @@ public function testGetAllowAttributes(): void * @param array $data * @dataProvider getOptionsDataProvider */ - public function testGetOptions(array $expected, array $data): array + public function testGetOptions(array $expected, array $data) { if (count($data['allowed_products'])) { $imageHelper1 = $this->getMockBuilder(Image::class) From 606292dc833efdda124f211488a838d9d4746110 Mon Sep 17 00:00:00 2001 From: hanna_hnida Date: Tue, 20 Oct 2020 12:07:09 +0200 Subject: [PATCH 125/139] magento/magento2-page-builder#558: Developer can style content types output differently per viewport - Added body id in Storefront, in tinymce4 in admin --- app/code/Magento/Theme/view/frontend/layout/default.xml | 1 + lib/web/mage/adminhtml/wysiwyg/tiny_mce/tinymce4Adapter.js | 1 + 2 files changed, 2 insertions(+) diff --git a/app/code/Magento/Theme/view/frontend/layout/default.xml b/app/code/Magento/Theme/view/frontend/layout/default.xml index 8eaac4aa3e794..bf76933b356c0 100644 --- a/app/code/Magento/Theme/view/frontend/layout/default.xml +++ b/app/code/Magento/Theme/view/frontend/layout/default.xml @@ -8,6 +8,7 @@ + diff --git a/lib/web/mage/adminhtml/wysiwyg/tiny_mce/tinymce4Adapter.js b/lib/web/mage/adminhtml/wysiwyg/tiny_mce/tinymce4Adapter.js index 4393b6c882039..7e4d4f532f8d3 100644 --- a/lib/web/mage/adminhtml/wysiwyg/tiny_mce/tinymce4Adapter.js +++ b/lib/web/mage/adminhtml/wysiwyg/tiny_mce/tinymce4Adapter.js @@ -205,6 +205,7 @@ define([ plugins: this.config.tinymce4.plugins, toolbar: this.config.tinymce4.toolbar, adapter: this, + body_id: "html-body", /** * @param {Object} editor From 2c3b5b994dc94adfcf5051df84039f4d0eec4bc1 Mon Sep 17 00:00:00 2001 From: hanna_hnida Date: Tue, 20 Oct 2020 15:32:34 +0200 Subject: [PATCH 126/139] magento/magento2-page-builder#558: Developer can style content types output differently per viewport - Fixed quote marks --- lib/web/mage/adminhtml/wysiwyg/tiny_mce/tinymce4Adapter.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/web/mage/adminhtml/wysiwyg/tiny_mce/tinymce4Adapter.js b/lib/web/mage/adminhtml/wysiwyg/tiny_mce/tinymce4Adapter.js index 7e4d4f532f8d3..d74838b0c26bf 100644 --- a/lib/web/mage/adminhtml/wysiwyg/tiny_mce/tinymce4Adapter.js +++ b/lib/web/mage/adminhtml/wysiwyg/tiny_mce/tinymce4Adapter.js @@ -205,7 +205,7 @@ define([ plugins: this.config.tinymce4.plugins, toolbar: this.config.tinymce4.toolbar, adapter: this, - body_id: "html-body", + 'body_id': 'html-body', /** * @param {Object} editor From e3789f0906f45b565fdb8d13d28c305917cca1bd Mon Sep 17 00:00:00 2001 From: Pieter Hoste Date: Tue, 20 Oct 2020 15:36:01 +0200 Subject: [PATCH 127/139] Make sure the depends definition works for custom widgets. Also converted code from PrototypeJS to jQuery. --- lib/web/mage/adminhtml/form.js | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/lib/web/mage/adminhtml/form.js b/lib/web/mage/adminhtml/form.js index eae359c4b26a4..054594ff9e9f2 100644 --- a/lib/web/mage/adminhtml/form.js +++ b/lib/web/mage/adminhtml/form.js @@ -496,10 +496,15 @@ define([ } // toggle target row - headElement = $(idTo + '-head'); + headElement = jQuery('#' + idTo + '-head'); isInheritCheckboxChecked = $(idTo + '_inherit') && $(idTo + '_inherit').checked; target = $(idTo); + // Account for the chooser style parameters. + if (target === null && headElement.length === 0 && idTo.substring(0, 16) === 'options_fieldset') { + headElement = jQuery('.field-' + idTo).add('.field-chooser' + idTo); + } + // Target won't always exist (for example, if field type is "label") if (target) { inputs = target.up(this._config['levels_up']).select('input', 'select', 'td'); @@ -529,10 +534,10 @@ define([ }); } - if (headElement) { + if (headElement.length > 0) { headElement.show(); - if (headElement.hasClassName('open') && target) { + if (headElement.hasClass('open') && target) { target.show(); } else if (target) { target.hide(); @@ -567,7 +572,7 @@ define([ }); } - if (headElement) { + if (headElement.length > 0) { headElement.hide(); } From 754eb5a168e6b2bd78b972e482988fa0b695a612 Mon Sep 17 00:00:00 2001 From: hanna_hnida Date: Tue, 20 Oct 2020 18:11:04 +0200 Subject: [PATCH 128/139] magento/magento2-page-builder#558: Developer can style content types output differently per viewport - Fixed tinymce image selector --- .../CmsNewBlockBlockActionsSection/BlockContentSection.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/Magento/Cms/Test/Mftf/Section/CmsNewBlockBlockActionsSection/BlockContentSection.xml b/app/code/Magento/Cms/Test/Mftf/Section/CmsNewBlockBlockActionsSection/BlockContentSection.xml index 1d5e8541dd497..f4e26938d9008 100644 --- a/app/code/Magento/Cms/Test/Mftf/Section/CmsNewBlockBlockActionsSection/BlockContentSection.xml +++ b/app/code/Magento/Cms/Test/Mftf/Section/CmsNewBlockBlockActionsSection/BlockContentSection.xml @@ -9,7 +9,7 @@ xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd">
- +
From 7683dece30de99d5df537507487e1129cd85c023 Mon Sep 17 00:00:00 2001 From: hanna_hnida Date: Tue, 20 Oct 2020 18:12:21 +0200 Subject: [PATCH 129/139] magento/magento2-page-builder#558: Developer can style content types output differently per viewport - Fixed tinymce image selector --- .../CmsNewBlockBlockActionsSection/BlockContentSection.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/Magento/Cms/Test/Mftf/Section/CmsNewBlockBlockActionsSection/BlockContentSection.xml b/app/code/Magento/Cms/Test/Mftf/Section/CmsNewBlockBlockActionsSection/BlockContentSection.xml index 1d5e8541dd497..f4e26938d9008 100644 --- a/app/code/Magento/Cms/Test/Mftf/Section/CmsNewBlockBlockActionsSection/BlockContentSection.xml +++ b/app/code/Magento/Cms/Test/Mftf/Section/CmsNewBlockBlockActionsSection/BlockContentSection.xml @@ -9,7 +9,7 @@ xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd">
- +
From 0ab061a5da09ba5fc95a6cd3a1d87454c1f2a8d0 Mon Sep 17 00:00:00 2001 From: hanna_hnida Date: Tue, 20 Oct 2020 18:15:23 +0200 Subject: [PATCH 130/139] Revert "magento/magento2-page-builder#558: Developer can style content types output differently per viewport - Fixed tinymce image selector" This reverts commit 754eb5a168e6b2bd78b972e482988fa0b695a612. --- .../CmsNewBlockBlockActionsSection/BlockContentSection.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/Magento/Cms/Test/Mftf/Section/CmsNewBlockBlockActionsSection/BlockContentSection.xml b/app/code/Magento/Cms/Test/Mftf/Section/CmsNewBlockBlockActionsSection/BlockContentSection.xml index f4e26938d9008..1d5e8541dd497 100644 --- a/app/code/Magento/Cms/Test/Mftf/Section/CmsNewBlockBlockActionsSection/BlockContentSection.xml +++ b/app/code/Magento/Cms/Test/Mftf/Section/CmsNewBlockBlockActionsSection/BlockContentSection.xml @@ -9,7 +9,7 @@ xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd">
- +
From bc3d88a026e178162b547dfe933378a8119e9121 Mon Sep 17 00:00:00 2001 From: Sergii Ivashchenko Date: Tue, 20 Oct 2020 17:22:49 +0100 Subject: [PATCH 131/139] Removed extra spaces --- .github/stale.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/stale.yml b/.github/stale.yml index 0a89a432699e0..0b9283fde06c7 100644 --- a/.github/stale.yml +++ b/.github/stale.yml @@ -18,7 +18,7 @@ exemptLabels: - "Progress: dev in progress" - "Progress: PR in progress" - "Progress: done" - - "B2B: GraphQL" + - "B2B: GraphQL" - "Progress: PR Created" - "PAP" - "Project: Login as Customer" From f0ea57ee4d879380a1cd8dcb4c738ae84b9a2aef Mon Sep 17 00:00:00 2001 From: Cristian Partica Date: Tue, 20 Oct 2020 21:34:50 -0500 Subject: [PATCH 132/139] 29251 test web-api test fix --- .../etc/graphql/di.xml | 6 ++-- ...gurableProductToCartSingleMutationTest.php | 35 +++++++++++++++++++ 2 files changed, 38 insertions(+), 3 deletions(-) diff --git a/app/code/Magento/ConfigurableProductGraphQl/etc/graphql/di.xml b/app/code/Magento/ConfigurableProductGraphQl/etc/graphql/di.xml index dc672b02e2f96..ace37b54a2bf6 100644 --- a/app/code/Magento/ConfigurableProductGraphQl/etc/graphql/di.xml +++ b/app/code/Magento/ConfigurableProductGraphQl/etc/graphql/di.xml @@ -48,7 +48,7 @@ - - - + + + diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/ConfigurableProduct/AddConfigurableProductToCartSingleMutationTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/ConfigurableProduct/AddConfigurableProductToCartSingleMutationTest.php index a2b7b54fb875a..fb6b36b883e77 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/ConfigurableProduct/AddConfigurableProductToCartSingleMutationTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/ConfigurableProduct/AddConfigurableProductToCartSingleMutationTest.php @@ -8,9 +8,13 @@ namespace Magento\GraphQl\ConfigurableProduct; use Exception; +use Magento\Config\Model\ResourceModel\Config; +use Magento\Framework\App\Config\ReinitableConfigInterface; +use Magento\Framework\App\Config\ScopeConfigInterface; use Magento\GraphQl\Quote\GetMaskedQuoteIdByReservedOrderId; use Magento\TestFramework\Helper\Bootstrap; use Magento\TestFramework\TestCase\GraphQlAbstract; +use Magento\CatalogInventory\Model\Configuration; /** * Add configurable product to cart testcases @@ -22,6 +26,21 @@ class AddConfigurableProductToCartSingleMutationTest extends GraphQlAbstract */ private $getMaskedQuoteIdByReservedOrderId; + /** + * @var Config $config + */ + private $resourceConfig; + + /** + * @var ScopeConfigInterface + */ + private $scopeConfig; + + /** + * @var ReinitableConfigInterface + */ + private $reinitConfig; + /** * @inheritdoc */ @@ -29,6 +48,9 @@ protected function setUp(): void { $objectManager = Bootstrap::getObjectManager(); $this->getMaskedQuoteIdByReservedOrderId = $objectManager->get(GetMaskedQuoteIdByReservedOrderId::class); + $this->resourceConfig = $objectManager->get(Config::class); + $this->scopeConfig = $objectManager->get(ScopeConfigInterface::class); + $this->reinitConfig = $objectManager->get(ReinitableConfigInterface::class); } /** @@ -166,9 +188,20 @@ public function testAddNonExistentConfigurableProductParentToCart() */ public function testOutOfStockVariationToCart() { + $showOutOfStock = $this->scopeConfig->getValue(Configuration::XML_PATH_SHOW_OUT_OF_STOCK); + + // Changing SHOW_OUT_OF_STOCK to show the out of stock option, otherwise graphql won't display it. + $this->resourceConfig->saveConfig(Configuration::XML_PATH_SHOW_OUT_OF_STOCK, 1); + $this->reinitConfig->reinit(); + $product = $this->getConfigurableProductInfo(); $attributeId = (int) $product['configurable_options'][0]['attribute_id']; $valueIndex = $product['configurable_options'][0]['values'][0]['value_index']; + // Asserting that the first value is the right option we want to add to cart + $this->assertEquals( + $product['configurable_options'][0]['values'][0]['label'], + 'Option 1' + ); $parentSku = $product['sku']; $configurableOptionsQuery = $this->generateSuperAttributesUIDQuery($attributeId, $valueIndex); @@ -191,6 +224,8 @@ public function testOutOfStockVariationToCart() $response['addProductsToCart']['user_errors'][0]['message'], $expectedErrorMessages ); + $this->resourceConfig->saveConfig(Configuration::XML_PATH_SHOW_OUT_OF_STOCK, $showOutOfStock); + $this->reinitConfig->reinit(); } /** From 0eb8848fa1d67201d97726f701b7edc6f237e21c Mon Sep 17 00:00:00 2001 From: Myroslav Dobra Date: Wed, 21 Oct 2020 09:13:21 +0300 Subject: [PATCH 133/139] MC-34444: Configurable shows simple products which are no longer assigned to the website --- .../Frontend/UsedProductsWebsiteFilter.php | 34 +++++++++++++++++++ .../ConfigurableProduct/etc/frontend/di.xml | 1 + .../Product/View/Type/ConfigurableTest.php | 25 ++++++++++++++ 3 files changed, 60 insertions(+) create mode 100644 app/code/Magento/ConfigurableProduct/Model/Plugin/Frontend/UsedProductsWebsiteFilter.php diff --git a/app/code/Magento/ConfigurableProduct/Model/Plugin/Frontend/UsedProductsWebsiteFilter.php b/app/code/Magento/ConfigurableProduct/Model/Plugin/Frontend/UsedProductsWebsiteFilter.php new file mode 100644 index 0000000000000..9e1f3482d3c0f --- /dev/null +++ b/app/code/Magento/ConfigurableProduct/Model/Plugin/Frontend/UsedProductsWebsiteFilter.php @@ -0,0 +1,34 @@ +setStoreFilter($product->getStore(), $product); + } +} diff --git a/app/code/Magento/ConfigurableProduct/etc/frontend/di.xml b/app/code/Magento/ConfigurableProduct/etc/frontend/di.xml index f60234453dc60..3942ec52cbb8b 100644 --- a/app/code/Magento/ConfigurableProduct/etc/frontend/di.xml +++ b/app/code/Magento/ConfigurableProduct/etc/frontend/di.xml @@ -12,5 +12,6 @@ + diff --git a/dev/tests/integration/testsuite/Magento/ConfigurableProduct/Block/Product/View/Type/ConfigurableTest.php b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/Block/Product/View/Type/ConfigurableTest.php index 0344d467a3cc2..214613821afb6 100644 --- a/dev/tests/integration/testsuite/Magento/ConfigurableProduct/Block/Product/View/Type/ConfigurableTest.php +++ b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/Block/Product/View/Type/ConfigurableTest.php @@ -19,6 +19,8 @@ use Magento\Framework\ObjectManagerInterface; use Magento\Framework\Serialize\SerializerInterface; use Magento\Framework\View\LayoutInterface; +use Magento\Store\Api\WebsiteRepositoryInterface; +use Magento\Store\Model\StoreManagerInterface; use Magento\TestFramework\Helper\Bootstrap; use PHPUnit\Framework\TestCase; @@ -126,6 +128,29 @@ public function testGetAllowProducts(): void } } + /** + * Verify configurable option not assigned to current website won't be visible. + * + * @magentoDataFixture Magento/ConfigurableProduct/_files/configurable_product_two_websites.php + * @magentoDbIsolation disabled + * @magentoAppArea frontend + * + * @return void + */ + public function testGetAllowProductsNonDefaultWebsite(): void + { + // Set current website to non-default. + $storeManager = $this->objectManager->get(StoreManagerInterface::class); + $storeManager->setCurrentStore('fixture_second_store'); + // Un-assign simple product from non-default website. + $simple = $this->productRepository->get('simple_Option_1'); + $simple->setWebsiteIds([1]); + $this->productRepository->save($simple); + // Verify only one configurable option will be visible. + $products = $this->block->getAllowProducts(); + $this->assertEquals(1, count($products)); + } + /** * @return void */ From 6970b19c234d752d2f4137580c877b37d96f77d5 Mon Sep 17 00:00:00 2001 From: Ejaz Alam Date: Wed, 21 Oct 2020 14:23:44 +0500 Subject: [PATCH 134/139] Updated string with Static key. #30545 Update string with the static key in function. --- app/code/Magento/Quote/Model/Quote/Address.php | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/app/code/Magento/Quote/Model/Quote/Address.php b/app/code/Magento/Quote/Model/Quote/Address.php index 5476915d9d649..4773c5a391094 100644 --- a/app/code/Magento/Quote/Model/Quote/Address.php +++ b/app/code/Magento/Quote/Model/Quote/Address.php @@ -139,6 +139,8 @@ class Address extends AbstractAddress implements const ADDRESS_TYPE_BILLING = 'billing'; const ADDRESS_TYPE_SHIPPING = 'shipping'; + + const CACHED_ITEMS_ALL = 'cached_items_all'; /** * Prefix of model events @@ -636,8 +638,7 @@ public function getItemsCollection() public function getAllItems() { // We calculate item list once and cache it in three arrays - all items - $key = 'cached_items_all'; - if (!$this->hasData($key)) { + if (!$this->hasData(self::CACHED_ITEMS_ALL)) { $quoteItems = $this->getQuote()->getItemsCollection(); $addressItems = $this->getItemsCollection(); @@ -676,10 +677,10 @@ public function getAllItems() } // Cache calculated lists - $this->setData('cached_items_all', $items); + $this->setData(self::CACHED_ITEMS_ALL, $items); } - $items = $this->getData($key); + $items = $this->getData(self::CACHED_ITEMS_ALL); return $items; } From fd4a5a7cb63e2c712e03de61b820f92a47a33a2e Mon Sep 17 00:00:00 2001 From: Ejaz Alam Date: Wed, 21 Oct 2020 18:23:14 +0500 Subject: [PATCH 135/139] Apply suggestions from code review added scope for the const variable. Co-authored-by: Ihor Sviziev --- app/code/Magento/Quote/Model/Quote/Address.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/Magento/Quote/Model/Quote/Address.php b/app/code/Magento/Quote/Model/Quote/Address.php index 4773c5a391094..aee86eb1f8935 100644 --- a/app/code/Magento/Quote/Model/Quote/Address.php +++ b/app/code/Magento/Quote/Model/Quote/Address.php @@ -140,7 +140,7 @@ class Address extends AbstractAddress implements const ADDRESS_TYPE_SHIPPING = 'shipping'; - const CACHED_ITEMS_ALL = 'cached_items_all'; + private const CACHED_ITEMS_ALL = 'cached_items_all'; /** * Prefix of model events From 23df632a70ed050b709cf8f25d8a047ae4e69faa Mon Sep 17 00:00:00 2001 From: Cristian Partica Date: Wed, 21 Oct 2020 22:21:47 -0500 Subject: [PATCH 136/139] 29251 test web-api test fix --- .../etc/graphql/di.xml | 6 +- .../Model/Cart/AddSimpleProductToCart.php | 70 ++++++++++++++++++- 2 files changed, 70 insertions(+), 6 deletions(-) diff --git a/app/code/Magento/ConfigurableProductGraphQl/etc/graphql/di.xml b/app/code/Magento/ConfigurableProductGraphQl/etc/graphql/di.xml index ace37b54a2bf6..dc672b02e2f96 100644 --- a/app/code/Magento/ConfigurableProductGraphQl/etc/graphql/di.xml +++ b/app/code/Magento/ConfigurableProductGraphQl/etc/graphql/di.xml @@ -48,7 +48,7 @@ - - - + + + diff --git a/app/code/Magento/QuoteGraphQl/Model/Cart/AddSimpleProductToCart.php b/app/code/Magento/QuoteGraphQl/Model/Cart/AddSimpleProductToCart.php index 83c1d03f132db..4b76a1ea5bd15 100644 --- a/app/code/Magento/QuoteGraphQl/Model/Cart/AddSimpleProductToCart.php +++ b/app/code/Magento/QuoteGraphQl/Model/Cart/AddSimpleProductToCart.php @@ -9,11 +9,13 @@ use Exception; use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\Framework\Exception\LocalizedException; use Magento\Framework\Exception\NoSuchEntityException; use Magento\Framework\GraphQl\Exception\GraphQlInputException; use Magento\Framework\GraphQl\Exception\GraphQlNoSuchEntityException; use Magento\Quote\Model\Quote; use Magento\QuoteGraphQl\Model\Cart\BuyRequest\BuyRequestBuilder; +use Magento\CatalogInventory\Api\StockStateInterface; /** * Add simple product to cart @@ -30,16 +32,24 @@ class AddSimpleProductToCart */ private $buyRequestBuilder; + /** + * @var StockStateInterface + */ + private $stockState; + /** * @param ProductRepositoryInterface $productRepository * @param BuyRequestBuilder $buyRequestBuilder + * @param StockStateInterface $stockState */ public function __construct( ProductRepositoryInterface $productRepository, - BuyRequestBuilder $buyRequestBuilder + BuyRequestBuilder $buyRequestBuilder, + StockStateInterface $stockState ) { $this->productRepository = $productRepository; $this->buyRequestBuilder = $buyRequestBuilder; + $this->stockState = $stockState; } /** @@ -53,15 +63,40 @@ public function __construct( public function execute(Quote $cart, array $cartItemData): void { $sku = $this->extractSku($cartItemData); - + $childSku = $this->extractChildSku($cartItemData); + $childSkuQty = $this->extractChildSkuQuantity($cartItemData); try { $product = $this->productRepository->get($sku, false, null, true); } catch (NoSuchEntityException $e) { throw new GraphQlNoSuchEntityException(__('Could not find a product with SKU "%sku"', ['sku' => $sku])); } + if ($childSku) { + $childProduct = $this->productRepository->get($childSku, false, null, true); + + $result = $this->stockState->checkQuoteItemQty( + $childProduct->getId(), $childSkuQty, $childSkuQty, $childSkuQty, $cart->getStoreId() + ); + + if ($result->getHasError() ) { + throw new GraphQlInputException( + __( + 'Could not add the product with SKU %sku to the shopping cart: %message', + ['sku' => $childSku, 'message' => __($result->getMessage())] + ) + ); + } + } + try { - $result = $cart->addProduct($product, $this->buyRequestBuilder->build($cartItemData)); + $buyRequest = $this->buyRequestBuilder->build($cartItemData); + // Some options might be disabled and not available + if (empty($buyRequest['super_attribute'])) { + throw new LocalizedException( + __('The product with SKU %sku is out of stock.', ['sku' => $childSku]) + ); + } + $result = $cart->addProduct($product, $this->buyRequestBuilder->build($cartItemData)); } catch (Exception $e) { throw new GraphQlInputException( __( @@ -99,4 +134,33 @@ private function extractSku(array $cartItemData): string } return (string)$cartItemData['data']['sku']; } + + /** + * Extract option child SKU from cart item data + * + * @param array $cartItemData + * @return string + * @throws GraphQlInputException + */ + private function extractChildSku(array $cartItemData): ?string + { + if (isset($cartItemData['data']['sku'])) { + return (string)$cartItemData['data']['sku']; + } + } + + /** + * Extract option child SKU from cart item data + * + * @param array $cartItemData + * @return string + * @throws GraphQlInputException + */ + private function extractChildSkuQuantity(array $cartItemData): ?string + { + if (empty($cartItemData['data']['quantity'])) { + throw new GraphQlInputException(__('Missed "quantity" in cart item data')); + } + return (string)$cartItemData['data']['quantity']; + } } From 2a68e712b11d201e00bc4357a8c6e89a43151a4b Mon Sep 17 00:00:00 2001 From: Cristian Partica Date: Thu, 22 Oct 2020 12:45:34 -0500 Subject: [PATCH 137/139] 29251 test web-api test fix --- .../BuyRequest/SuperAttributeDataProvider.php | 39 +++++++++- .../Model/Cart/AddSimpleProductToCart.php | 71 ++----------------- 2 files changed, 41 insertions(+), 69 deletions(-) diff --git a/app/code/Magento/ConfigurableProductGraphQl/Model/Cart/BuyRequest/SuperAttributeDataProvider.php b/app/code/Magento/ConfigurableProductGraphQl/Model/Cart/BuyRequest/SuperAttributeDataProvider.php index 4a613254ddf84..d9c8ade39f621 100644 --- a/app/code/Magento/ConfigurableProductGraphQl/Model/Cart/BuyRequest/SuperAttributeDataProvider.php +++ b/app/code/Magento/ConfigurableProductGraphQl/Model/Cart/BuyRequest/SuperAttributeDataProvider.php @@ -8,6 +8,8 @@ namespace Magento\ConfigurableProductGraphQl\Model\Cart\BuyRequest; use Magento\Catalog\Api\Data\ProductInterface; +use Magento\CatalogInventory\Api\StockStateInterface; +use Magento\Framework\Exception\LocalizedException; use Magento\Framework\Exception\NoSuchEntityException; use Magento\Framework\GraphQl\Exception\GraphQlInputException; use Magento\Framework\GraphQl\Exception\GraphQlNoSuchEntityException; @@ -16,6 +18,7 @@ use Magento\Catalog\Api\ProductRepositoryInterface; use Magento\ConfigurableProductGraphQl\Model\Options\Collection as OptionCollection; use Magento\Framework\EntityManager\MetadataPool; +use Magento\Quote\Model\Quote; /** * DataProvider for building super attribute options in buy requests @@ -42,22 +45,30 @@ class SuperAttributeDataProvider implements BuyRequestDataProviderInterface */ private $metadataPool; + /** + * @var StockStateInterface + */ + private $stockState; + /** * @param ArrayManager $arrayManager * @param ProductRepositoryInterface $productRepository * @param OptionCollection $optionCollection * @param MetadataPool $metadataPool + * @param StockStateInterface $stockState */ public function __construct( ArrayManager $arrayManager, ProductRepositoryInterface $productRepository, OptionCollection $optionCollection, - MetadataPool $metadataPool + MetadataPool $metadataPool, + StockStateInterface $stockState ) { $this->arrayManager = $arrayManager; $this->productRepository = $productRepository; $this->optionCollection = $optionCollection; $this->metadataPool = $metadataPool; + $this->stockState = $stockState; } /** @@ -65,18 +76,36 @@ public function __construct( */ public function execute(array $cartItemData): array { + $parentSku = $this->arrayManager->get('parent_sku', $cartItemData); if ($parentSku === null) { return []; } $sku = $this->arrayManager->get('data/sku', $cartItemData); - + $qty = $this->arrayManager->get('data/quantity', $cartItemData); + $cart = $this->arrayManager->get('model', $cartItemData); + if (!$cart instanceof Quote) { + throw new LocalizedException(__('"model" value should be specified')); + } try { $parentProduct = $this->productRepository->get($parentSku); $product = $this->productRepository->get($sku); } catch (NoSuchEntityException $e) { throw new GraphQlNoSuchEntityException(__('Could not find specified product.')); } + + + // Child stock check has to be performed a catalog by default would not show/check it + $childProduct = $this->productRepository->get($sku, false, null, true); + + $result = $this->stockState->checkQuoteItemQty($childProduct->getId(), $qty, $qty, $qty, $cart->getStoreId()); + + if ($result->getHasError() ) { + throw new LocalizedException( + __($result->getMessage()) + ); + } + $configurableProductLinks = $parentProduct->getExtensionAttributes()->getConfigurableProductLinks(); if (!in_array($product->getId(), $configurableProductLinks)) { throw new GraphQlInputException(__('Could not find specified product.')); @@ -95,6 +124,12 @@ public function execute(array $cartItemData): array } } } + // Some options might be disabled and/or available when parent and child sku are provided + if (empty($superAttributesData)) { + throw new LocalizedException( + __('The product with SKU %sku is out of stock.', ['sku' => $parentSku]) + ); + } return ['super_attribute' => $superAttributesData]; } } diff --git a/app/code/Magento/QuoteGraphQl/Model/Cart/AddSimpleProductToCart.php b/app/code/Magento/QuoteGraphQl/Model/Cart/AddSimpleProductToCart.php index 4b76a1ea5bd15..12eebe9b926e8 100644 --- a/app/code/Magento/QuoteGraphQl/Model/Cart/AddSimpleProductToCart.php +++ b/app/code/Magento/QuoteGraphQl/Model/Cart/AddSimpleProductToCart.php @@ -9,13 +9,11 @@ use Exception; use Magento\Catalog\Api\ProductRepositoryInterface; -use Magento\Framework\Exception\LocalizedException; use Magento\Framework\Exception\NoSuchEntityException; use Magento\Framework\GraphQl\Exception\GraphQlInputException; use Magento\Framework\GraphQl\Exception\GraphQlNoSuchEntityException; use Magento\Quote\Model\Quote; use Magento\QuoteGraphQl\Model\Cart\BuyRequest\BuyRequestBuilder; -use Magento\CatalogInventory\Api\StockStateInterface; /** * Add simple product to cart @@ -32,24 +30,16 @@ class AddSimpleProductToCart */ private $buyRequestBuilder; - /** - * @var StockStateInterface - */ - private $stockState; - /** * @param ProductRepositoryInterface $productRepository * @param BuyRequestBuilder $buyRequestBuilder - * @param StockStateInterface $stockState */ public function __construct( ProductRepositoryInterface $productRepository, - BuyRequestBuilder $buyRequestBuilder, - StockStateInterface $stockState + BuyRequestBuilder $buyRequestBuilder ) { $this->productRepository = $productRepository; $this->buyRequestBuilder = $buyRequestBuilder; - $this->stockState = $stockState; } /** @@ -62,41 +52,17 @@ public function __construct( */ public function execute(Quote $cart, array $cartItemData): void { + $cartItemData['model'] = $cart; $sku = $this->extractSku($cartItemData); - $childSku = $this->extractChildSku($cartItemData); - $childSkuQty = $this->extractChildSkuQuantity($cartItemData); + try { $product = $this->productRepository->get($sku, false, null, true); } catch (NoSuchEntityException $e) { throw new GraphQlNoSuchEntityException(__('Could not find a product with SKU "%sku"', ['sku' => $sku])); } - if ($childSku) { - $childProduct = $this->productRepository->get($childSku, false, null, true); - - $result = $this->stockState->checkQuoteItemQty( - $childProduct->getId(), $childSkuQty, $childSkuQty, $childSkuQty, $cart->getStoreId() - ); - - if ($result->getHasError() ) { - throw new GraphQlInputException( - __( - 'Could not add the product with SKU %sku to the shopping cart: %message', - ['sku' => $childSku, 'message' => __($result->getMessage())] - ) - ); - } - } - try { - $buyRequest = $this->buyRequestBuilder->build($cartItemData); - // Some options might be disabled and not available - if (empty($buyRequest['super_attribute'])) { - throw new LocalizedException( - __('The product with SKU %sku is out of stock.', ['sku' => $childSku]) - ); - } - $result = $cart->addProduct($product, $this->buyRequestBuilder->build($cartItemData)); + $result = $cart->addProduct($product, $this->buyRequestBuilder->build($cartItemData)); } catch (Exception $e) { throw new GraphQlInputException( __( @@ -134,33 +100,4 @@ private function extractSku(array $cartItemData): string } return (string)$cartItemData['data']['sku']; } - - /** - * Extract option child SKU from cart item data - * - * @param array $cartItemData - * @return string - * @throws GraphQlInputException - */ - private function extractChildSku(array $cartItemData): ?string - { - if (isset($cartItemData['data']['sku'])) { - return (string)$cartItemData['data']['sku']; - } - } - - /** - * Extract option child SKU from cart item data - * - * @param array $cartItemData - * @return string - * @throws GraphQlInputException - */ - private function extractChildSkuQuantity(array $cartItemData): ?string - { - if (empty($cartItemData['data']['quantity'])) { - throw new GraphQlInputException(__('Missed "quantity" in cart item data')); - } - return (string)$cartItemData['data']['quantity']; - } } From 45ff6359d82a761562aa4a56782e1c262ab8bb33 Mon Sep 17 00:00:00 2001 From: Cristian Partica Date: Fri, 23 Oct 2020 12:56:17 -0500 Subject: [PATCH 138/139] 29251 fix static --- .../BuyRequest/SuperAttributeDataProvider.php | 60 +++++++++++++------ 1 file changed, 42 insertions(+), 18 deletions(-) diff --git a/app/code/Magento/ConfigurableProductGraphQl/Model/Cart/BuyRequest/SuperAttributeDataProvider.php b/app/code/Magento/ConfigurableProductGraphQl/Model/Cart/BuyRequest/SuperAttributeDataProvider.php index d9c8ade39f621..0fa4b8da50817 100644 --- a/app/code/Magento/ConfigurableProductGraphQl/Model/Cart/BuyRequest/SuperAttributeDataProvider.php +++ b/app/code/Magento/ConfigurableProductGraphQl/Model/Cart/BuyRequest/SuperAttributeDataProvider.php @@ -8,17 +8,17 @@ namespace Magento\ConfigurableProductGraphQl\Model\Cart\BuyRequest; use Magento\Catalog\Api\Data\ProductInterface; +use Magento\Catalog\Api\ProductRepositoryInterface; use Magento\CatalogInventory\Api\StockStateInterface; +use Magento\ConfigurableProductGraphQl\Model\Options\Collection as OptionCollection; +use Magento\Framework\EntityManager\MetadataPool; use Magento\Framework\Exception\LocalizedException; use Magento\Framework\Exception\NoSuchEntityException; use Magento\Framework\GraphQl\Exception\GraphQlInputException; use Magento\Framework\GraphQl\Exception\GraphQlNoSuchEntityException; use Magento\Framework\Stdlib\ArrayManager; -use Magento\QuoteGraphQl\Model\Cart\BuyRequest\BuyRequestDataProviderInterface; -use Magento\Catalog\Api\ProductRepositoryInterface; -use Magento\ConfigurableProductGraphQl\Model\Options\Collection as OptionCollection; -use Magento\Framework\EntityManager\MetadataPool; use Magento\Quote\Model\Quote; +use Magento\QuoteGraphQl\Model\Cart\BuyRequest\BuyRequestDataProviderInterface; /** * DataProvider for building super attribute options in buy requests @@ -76,7 +76,6 @@ public function __construct( */ public function execute(array $cartItemData): array { - $parentSku = $this->arrayManager->get('parent_sku', $cartItemData); if ($parentSku === null) { return []; @@ -94,17 +93,7 @@ public function execute(array $cartItemData): array throw new GraphQlNoSuchEntityException(__('Could not find specified product.')); } - - // Child stock check has to be performed a catalog by default would not show/check it - $childProduct = $this->productRepository->get($sku, false, null, true); - - $result = $this->stockState->checkQuoteItemQty($childProduct->getId(), $qty, $qty, $qty, $cart->getStoreId()); - - if ($result->getHasError() ) { - throw new LocalizedException( - __($result->getMessage()) - ); - } + $this->checkProductStock($sku, (float) $qty, (int) $cart->getStoreId()); $configurableProductLinks = $parentProduct->getExtensionAttributes()->getConfigurableProductLinks(); if (!in_array($product->getId(), $configurableProductLinks)) { @@ -124,12 +113,47 @@ public function execute(array $cartItemData): array } } } - // Some options might be disabled and/or available when parent and child sku are provided + $this->checkSuperAttributeData($parentSku, $superAttributesData); + + return ['super_attribute' => $superAttributesData]; + } + + /** + * Stock check for a product + * + * @param string $sku + * @param float $qty + * @param int $scopeId + */ + private function checkProductStock(string $sku, float $qty, int $scopeId): void + { + // Child stock check has to be performed a catalog by default would not show/check it + $childProduct = $this->productRepository->get($sku, false, null, true); + + $result = $this->stockState->checkQuoteItemQty($childProduct->getId(), $qty, $qty, $qty, $scopeId); + + if ($result->getHasError()) { + throw new LocalizedException( + __($result->getMessage()) + ); + } + } + + /** + * Check super attribute data. + * + * Some options might be disabled and/or available when parent and child sku are provided. + * + * @param string $parentSku + * @param array $superAttributesData + * @throws LocalizedException + */ + private function checkSuperAttributeData(string $parentSku, array $superAttributesData): void + { if (empty($superAttributesData)) { throw new LocalizedException( __('The product with SKU %sku is out of stock.', ['sku' => $parentSku]) ); } - return ['super_attribute' => $superAttributesData]; } } From badf7a17b2f76e1cd2ddb31aa26415668ef4640f Mon Sep 17 00:00:00 2001 From: Cristian Partica Date: Fri, 23 Oct 2020 14:01:57 -0500 Subject: [PATCH 139/139] 29251 fix static --- .../Magento/QuoteGraphQl/Model/Cart/AddSimpleProductToCart.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/Magento/QuoteGraphQl/Model/Cart/AddSimpleProductToCart.php b/app/code/Magento/QuoteGraphQl/Model/Cart/AddSimpleProductToCart.php index 12eebe9b926e8..f2dd6389d2c4a 100644 --- a/app/code/Magento/QuoteGraphQl/Model/Cart/AddSimpleProductToCart.php +++ b/app/code/Magento/QuoteGraphQl/Model/Cart/AddSimpleProductToCart.php @@ -16,7 +16,7 @@ use Magento\QuoteGraphQl\Model\Cart\BuyRequest\BuyRequestBuilder; /** - * Add simple product to cart + * Add simple product to cart mutation */ class AddSimpleProductToCart {