From 1000822c16ddb32a483b3966bd5e01e7cfcad536 Mon Sep 17 00:00:00 2001 From: Rafael Kassner Date: Thu, 26 Mar 2020 12:10:12 +0100 Subject: [PATCH 001/133] Add missing order_data array to EmailSender classes --- .../Sales/Model/Order/Creditmemo/Sender/EmailSender.php | 6 ++++++ .../Sales/Model/Order/Invoice/Sender/EmailSender.php | 6 ++++++ .../Sales/Model/Order/Shipment/Sender/EmailSender.php | 8 +++++++- 3 files changed, 19 insertions(+), 1 deletion(-) diff --git a/app/code/Magento/Sales/Model/Order/Creditmemo/Sender/EmailSender.php b/app/code/Magento/Sales/Model/Order/Creditmemo/Sender/EmailSender.php index 93c8ed00f9daa..a92a1480bd023 100644 --- a/app/code/Magento/Sales/Model/Order/Creditmemo/Sender/EmailSender.php +++ b/app/code/Magento/Sales/Model/Order/Creditmemo/Sender/EmailSender.php @@ -111,6 +111,12 @@ public function send( 'store' => $order->getStore(), 'formattedShippingAddress' => $this->getFormattedShippingAddress($order), 'formattedBillingAddress' => $this->getFormattedBillingAddress($order), + 'order_data' => [ + 'customer_name' => $order->getCustomerName(), + 'is_not_virtual' => $order->getIsNotVirtual(), + 'email_customer_note' => $order->getEmailCustomerNote(), + 'frontend_status_label' => $order->getFrontendStatusLabel() + ] ]; $transportObject = new DataObject($transport); diff --git a/app/code/Magento/Sales/Model/Order/Invoice/Sender/EmailSender.php b/app/code/Magento/Sales/Model/Order/Invoice/Sender/EmailSender.php index 004f36c277028..44b4df17619d8 100644 --- a/app/code/Magento/Sales/Model/Order/Invoice/Sender/EmailSender.php +++ b/app/code/Magento/Sales/Model/Order/Invoice/Sender/EmailSender.php @@ -111,6 +111,12 @@ public function send( 'store' => $order->getStore(), 'formattedShippingAddress' => $this->getFormattedShippingAddress($order), 'formattedBillingAddress' => $this->getFormattedBillingAddress($order), + 'order_data' => [ + 'customer_name' => $order->getCustomerName(), + 'is_not_virtual' => $order->getIsNotVirtual(), + 'email_customer_note' => $order->getEmailCustomerNote(), + 'frontend_status_label' => $order->getFrontendStatusLabel() + ] ]; $transportObject = new DataObject($transport); diff --git a/app/code/Magento/Sales/Model/Order/Shipment/Sender/EmailSender.php b/app/code/Magento/Sales/Model/Order/Shipment/Sender/EmailSender.php index 1d4418c50047d..288cbd40b1e5b 100644 --- a/app/code/Magento/Sales/Model/Order/Shipment/Sender/EmailSender.php +++ b/app/code/Magento/Sales/Model/Order/Shipment/Sender/EmailSender.php @@ -110,7 +110,13 @@ public function send( 'payment_html' => $this->getPaymentHtml($order), 'store' => $order->getStore(), 'formattedShippingAddress' => $this->getFormattedShippingAddress($order), - 'formattedBillingAddress' => $this->getFormattedBillingAddress($order) + 'formattedBillingAddress' => $this->getFormattedBillingAddress($order), + 'order_data' => [ + 'customer_name' => $order->getCustomerName(), + 'is_not_virtual' => $order->getIsNotVirtual(), + 'email_customer_note' => $order->getEmailCustomerNote(), + 'frontend_status_label' => $order->getFrontendStatusLabel() + ] ]; $transportObject = new DataObject($transport); From 56f8bf8dcdfd47d656fc563c8d77c80997268923 Mon Sep 17 00:00:00 2001 From: Rafael Kassner Date: Thu, 26 Mar 2020 14:46:10 +0100 Subject: [PATCH 002/133] Implement unit tests --- .../Model/Order/Creditmemo/Sender/EmailSenderTest.php | 11 +++++++++++ .../Model/Order/Invoice/Sender/EmailSenderTest.php | 11 +++++++++++ .../Model/Order/Shipment/Sender/EmailSenderTest.php | 11 +++++++++++ 3 files changed, 33 insertions(+) diff --git a/app/code/Magento/Sales/Test/Unit/Model/Order/Creditmemo/Sender/EmailSenderTest.php b/app/code/Magento/Sales/Test/Unit/Model/Order/Creditmemo/Sender/EmailSenderTest.php index 13ed0739348b2..b97db473687fb 100644 --- a/app/code/Magento/Sales/Test/Unit/Model/Order/Creditmemo/Sender/EmailSenderTest.php +++ b/app/code/Magento/Sales/Test/Unit/Model/Order/Creditmemo/Sender/EmailSenderTest.php @@ -250,6 +250,11 @@ public function testSend($configValue, $forceSyncMode, $isComment, $emailSending ->method('setSendEmail') ->with($emailSendingResult); + $this->orderMock->method('getCustomerName')->willReturn('Customer name'); + $this->orderMock->method('getIsNotVirtual')->willReturn(true); + $this->orderMock->method('getEmailCustomerNote')->willReturn(null); + $this->orderMock->method('getFrontendStatusLabel')->willReturn('Pending'); + if (!$configValue || $forceSyncMode) { $transport = [ 'order' => $this->orderMock, @@ -260,6 +265,12 @@ public function testSend($configValue, $forceSyncMode, $isComment, $emailSending 'store' => $this->storeMock, 'formattedShippingAddress' => 'Formatted address', 'formattedBillingAddress' => 'Formatted address', + 'order_data' => [ + 'customer_name' => 'Customer name', + 'is_not_virtual' => true, + 'email_customer_note' => null, + 'frontend_status_label' => 'Pending', + ], ]; $transport = new \Magento\Framework\DataObject($transport); diff --git a/app/code/Magento/Sales/Test/Unit/Model/Order/Invoice/Sender/EmailSenderTest.php b/app/code/Magento/Sales/Test/Unit/Model/Order/Invoice/Sender/EmailSenderTest.php index 6db1ec0392e0e..0ab413229c703 100644 --- a/app/code/Magento/Sales/Test/Unit/Model/Order/Invoice/Sender/EmailSenderTest.php +++ b/app/code/Magento/Sales/Test/Unit/Model/Order/Invoice/Sender/EmailSenderTest.php @@ -249,6 +249,11 @@ public function testSend($configValue, $forceSyncMode, $isComment, $emailSending ->method('setSendEmail') ->with($emailSendingResult); + $this->orderMock->method('getCustomerName')->willReturn('Customer name'); + $this->orderMock->method('getIsNotVirtual')->willReturn(true); + $this->orderMock->method('getEmailCustomerNote')->willReturn(null); + $this->orderMock->method('getFrontendStatusLabel')->willReturn('Pending'); + if (!$configValue || $forceSyncMode) { $transport = [ 'order' => $this->orderMock, @@ -259,6 +264,12 @@ public function testSend($configValue, $forceSyncMode, $isComment, $emailSending 'store' => $this->storeMock, 'formattedShippingAddress' => 'Formatted address', 'formattedBillingAddress' => 'Formatted address', + 'order_data' => [ + 'customer_name' => 'Customer name', + 'is_not_virtual' => true, + 'email_customer_note' => null, + 'frontend_status_label' => 'Pending', + ], ]; $transport = new \Magento\Framework\DataObject($transport); diff --git a/app/code/Magento/Sales/Test/Unit/Model/Order/Shipment/Sender/EmailSenderTest.php b/app/code/Magento/Sales/Test/Unit/Model/Order/Shipment/Sender/EmailSenderTest.php index 2262fbf03c1a1..6a892e4af7972 100644 --- a/app/code/Magento/Sales/Test/Unit/Model/Order/Shipment/Sender/EmailSenderTest.php +++ b/app/code/Magento/Sales/Test/Unit/Model/Order/Shipment/Sender/EmailSenderTest.php @@ -251,6 +251,11 @@ public function testSend($configValue, $forceSyncMode, $isComment, $emailSending ->method('setSendEmail') ->with($emailSendingResult); + $this->orderMock->method('getCustomerName')->willReturn('Customer name'); + $this->orderMock->method('getIsNotVirtual')->willReturn(true); + $this->orderMock->method('getEmailCustomerNote')->willReturn(null); + $this->orderMock->method('getFrontendStatusLabel')->willReturn('Pending'); + if (!$configValue || $forceSyncMode) { $transport = [ 'order' => $this->orderMock, @@ -261,6 +266,12 @@ public function testSend($configValue, $forceSyncMode, $isComment, $emailSending 'store' => $this->storeMock, 'formattedShippingAddress' => 'Formatted address', 'formattedBillingAddress' => 'Formatted address', + 'order_data' => [ + 'customer_name' => 'Customer name', + 'is_not_virtual' => true, + 'email_customer_note' => null, + 'frontend_status_label' => 'Pending', + ], ]; $transport = new \Magento\Framework\DataObject($transport); From 59dd0db5300a4a4f580f51e2bd8d21db74ca05ed Mon Sep 17 00:00:00 2001 From: Eden Date: Sat, 4 Apr 2020 19:44:50 +0700 Subject: [PATCH 003/133] Fix wrong position of button list when load "New Attribute" page --- .../Adminhtml/Product/Attribute/Edit.php | 22 +++++++++++-------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/app/code/Magento/Catalog/Block/Adminhtml/Product/Attribute/Edit.php b/app/code/Magento/Catalog/Block/Adminhtml/Product/Attribute/Edit.php index 6ab039aa27849..7c680a108adf8 100644 --- a/app/code/Magento/Catalog/Block/Adminhtml/Product/Attribute/Edit.php +++ b/app/code/Magento/Catalog/Block/Adminhtml/Product/Attribute/Edit.php @@ -42,6 +42,8 @@ public function __construct( } /** + * Construct block + * * @return void */ protected function _construct() @@ -51,6 +53,14 @@ protected function _construct() parent::_construct(); + $this->buttonList->update('save', 'label', __('Save Attribute')); + $this->buttonList->update('save', 'class', 'save primary'); + $this->buttonList->update( + 'save', + 'data_attribute', + ['mage-init' => ['button' => ['event' => 'save', 'target' => '#edit_form']]] + ); + if ($this->getRequest()->getParam('popup')) { $this->buttonList->remove('back'); if ($this->getRequest()->getParam('product_tab') != 'variations') { @@ -64,6 +74,8 @@ protected function _construct() 100 ); } + $this->buttonList->update('reset', 'level', 10); + $this->buttonList->update('save', 'class', 'save action-secondary'); } else { $this->addButton( 'save_and_edit_button', @@ -79,14 +91,6 @@ protected function _construct() ); } - $this->buttonList->update('save', 'label', __('Save Attribute')); - $this->buttonList->update('save', 'class', 'save primary'); - $this->buttonList->update( - 'save', - 'data_attribute', - ['mage-init' => ['button' => ['event' => 'save', 'target' => '#edit_form']]] - ); - $entityAttribute = $this->_coreRegistry->registry('entity_attribute'); if (!$entityAttribute || !$entityAttribute->getIsUserDefined()) { $this->buttonList->remove('delete'); @@ -96,7 +100,7 @@ protected function _construct() } /** - * {@inheritdoc} + * @inheritdoc */ public function addButton($buttonId, $data, $level = 0, $sortOrder = 0, $region = 'toolbar') { From 1dd4e1bbd58525b83373da00f691bd2daf4ede60 Mon Sep 17 00:00:00 2001 From: Eden Date: Sat, 4 Apr 2020 21:16:10 +0700 Subject: [PATCH 004/133] Fix static test --- .../Magento/Catalog/Block/Adminhtml/Product/Attribute/Edit.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/Magento/Catalog/Block/Adminhtml/Product/Attribute/Edit.php b/app/code/Magento/Catalog/Block/Adminhtml/Product/Attribute/Edit.php index 7c680a108adf8..efb7d6dbbeff3 100644 --- a/app/code/Magento/Catalog/Block/Adminhtml/Product/Attribute/Edit.php +++ b/app/code/Magento/Catalog/Block/Adminhtml/Product/Attribute/Edit.php @@ -107,7 +107,7 @@ public function addButton($buttonId, $data, $level = 0, $sortOrder = 0, $region if ($this->getRequest()->getParam('popup')) { $region = 'header'; } - parent::addButton($buttonId, $data, $level, $sortOrder, $region); + return parent::addButton($buttonId, $data, $level, $sortOrder, $region); } /** From eb8cd708869546a203b17b922c2ce573a6c7f35d Mon Sep 17 00:00:00 2001 From: Nikolay Sumrak Date: Thu, 9 Apr 2020 13:32:19 +0300 Subject: [PATCH 005/133] 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 006/133] 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 007/133] 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 008/133] #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 009/133] #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 010/133] #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 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 011/133] [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 3f6d8c42a08cdf82f852d2c4e50903ad276488ee Mon Sep 17 00:00:00 2001 From: ogorkun <ogorkun@adobe.com> Date: Wed, 15 Jul 2020 14:32:40 -0500 Subject: [PATCH 012/133] 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 013/133] 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 014/133] 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 015/133] 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 016/133] 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 3dcf9110e7147beebf1529880315dfc1daccedc7 Mon Sep 17 00:00:00 2001 From: ogorkun <ogorkun@adobe.com> Date: Mon, 20 Jul 2020 13:32:27 -0500 Subject: [PATCH 017/133] 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 018/133] 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 019/133] 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 020/133] 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 021/133] 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 022/133] 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 023/133] 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 024/133] 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 025/133] 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 026/133] 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 027/133] 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 028/133] 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 029/133] 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 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/133] 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/133] 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/133] 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/133] 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/133] 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/133] 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/133] 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/133] 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 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 038/133] 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 039/133] 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 075ddb4b76367df4e123ab71e2d1fb37e821160e Mon Sep 17 00:00:00 2001 From: Ihor Sviziev <svizev.igor@gmail.com> Date: Mon, 14 Sep 2020 09:59:35 +0300 Subject: [PATCH 040/133] Fix URL generation for new store Emulate adminhtml area where url rewrites are created --- .../Command/App/ConfigImportCommand.php | 79 ++++++++++++++++--- .../Command/App/ConfigImportCommandTest.php | 76 +++++++++++++++++- app/code/Magento/Deploy/etc/di.xml | 6 ++ 3 files changed, 148 insertions(+), 13 deletions(-) diff --git a/app/code/Magento/Deploy/Console/Command/App/ConfigImportCommand.php b/app/code/Magento/Deploy/Console/Command/App/ConfigImportCommand.php index 8a75ad0def222..eb87a9c12125b 100644 --- a/app/code/Magento/Deploy/Console/Command/App/ConfigImportCommand.php +++ b/app/code/Magento/Deploy/Console/Command/App/ConfigImportCommand.php @@ -3,14 +3,21 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + namespace Magento\Deploy\Console\Command\App; +use Magento\Config\Console\Command\EmulatedAdminhtmlAreaProcessor; +use Magento\Deploy\Console\Command\App\ConfigImport\Processor; +use Magento\Framework\App\Area; +use Magento\Framework\App\AreaList; +use Magento\Framework\App\DeploymentConfig; +use Magento\Framework\App\ObjectManager; +use Magento\Framework\Console\Cli; +use Magento\Framework\Exception\FileSystemException; use Magento\Framework\Exception\RuntimeException; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; -use Magento\Framework\Console\Cli; -use Magento\Deploy\Console\Command\App\ConfigImport\Processor; /** * Runs the process of importing configuration data from shared source to appropriate application sources @@ -21,9 +28,6 @@ */ class ConfigImportCommand extends Command { - /** - * Command name. - */ const COMMAND_NAME = 'app:config:import'; /** @@ -33,12 +37,40 @@ class ConfigImportCommand extends Command */ private $processor; + /** + * @var EmulatedAdminhtmlAreaProcessor + */ + private $adminhtmlAreaProcessor; + + /** + * @var DeploymentConfig + */ + private $deploymentConfig; + + /** + * @var AreaList + */ + private $areaList; + /** * @param Processor $processor the configuration importer + * @param DeploymentConfig|null $deploymentConfig + * @param EmulatedAdminhtmlAreaProcessor|null $adminhtmlAreaProcessor + * @param AreaList|null $areaList */ - public function __construct(Processor $processor) - { + public function __construct( + Processor $processor, + DeploymentConfig $deploymentConfig = null, + EmulatedAdminhtmlAreaProcessor $adminhtmlAreaProcessor = null, + AreaList $areaList = null + ) { $this->processor = $processor; + $this->deploymentConfig = $deploymentConfig + ?? ObjectManager::getInstance()->get(DeploymentConfig::class); + $this->adminhtmlAreaProcessor = $adminhtmlAreaProcessor + ?? ObjectManager::getInstance()->get(EmulatedAdminhtmlAreaProcessor::class); + $this->areaList = $areaList + ?? ObjectManager::getInstance()->get(AreaList::class); parent::__construct(); } @@ -55,12 +87,26 @@ protected function configure() } /** - * Imports data from deployment configuration files to the DB. {@inheritdoc} + * Imports data from deployment configuration files to the DB. + * {@inheritdoc} + * + * @param InputInterface $input + * @param OutputInterface $output + * @return int + * @throws \Exception */ protected function execute(InputInterface $input, OutputInterface $output) { try { - $this->processor->execute($input, $output); + if ($this->canEmulateAdminhtmlArea()) { + // Emulate adminhtml area in order to execute all needed plugins declared only for this area + // For instance URL rewrite generation during creating store view + $this->adminhtmlAreaProcessor->process(function () use ($input, $output) { + $this->processor->execute($input, $output); + }); + } else { + $this->processor->execute($input, $output); + } } catch (RuntimeException $e) { $output->writeln('<error>' . $e->getMessage() . '</error>'); @@ -69,4 +115,19 @@ protected function execute(InputInterface $input, OutputInterface $output) return Cli::RETURN_SUCCESS; } + + /** + * Detects if we can emulate adminhtml area + * + * This area could be not available for instance during setup:install + * + * @return bool + * @throws RuntimeException + * @throws FileSystemException + */ + private function canEmulateAdminhtmlArea(): bool + { + return $this->deploymentConfig->isAvailable() + && in_array(Area::AREA_ADMINHTML, $this->areaList->getCodes()); + } } diff --git a/app/code/Magento/Deploy/Test/Unit/Console/Command/App/ConfigImportCommandTest.php b/app/code/Magento/Deploy/Test/Unit/Console/Command/App/ConfigImportCommandTest.php index da790a19f480a..32bdd63ef4638 100644 --- a/app/code/Magento/Deploy/Test/Unit/Console/Command/App/ConfigImportCommandTest.php +++ b/app/code/Magento/Deploy/Test/Unit/Console/Command/App/ConfigImportCommandTest.php @@ -7,8 +7,11 @@ namespace Magento\Deploy\Test\Unit\Console\Command\App; +use Magento\Config\Console\Command\EmulatedAdminhtmlAreaProcessor; use Magento\Deploy\Console\Command\App\ConfigImport\Processor; use Magento\Deploy\Console\Command\App\ConfigImportCommand; +use Magento\Framework\App\AreaList; +use Magento\Framework\App\DeploymentConfig; use Magento\Framework\Console\Cli; use Magento\Framework\Exception\RuntimeException; use PHPUnit\Framework\MockObject\MockObject; @@ -27,16 +30,37 @@ class ConfigImportCommandTest extends TestCase */ private $commandTester; + /** + * @var DeploymentConfig|MockObject + */ + private $deploymentConfigMock; + + /** + * @var EmulatedAdminhtmlAreaProcessor|MockObject + */ + private $adminhtmlAreaProcessorMock; + + /** + * @var AreaList|MockObject + */ + private $areaListMock; + /** * @return void */ protected function setUp(): void { - $this->processorMock = $this->getMockBuilder(Processor::class) - ->disableOriginalConstructor() - ->getMock(); + $this->processorMock = $this->createMock(Processor::class); + $this->deploymentConfigMock = $this->createMock(DeploymentConfig::class); + $this->adminhtmlAreaProcessorMock = $this->createMock(EmulatedAdminhtmlAreaProcessor::class); + $this->areaListMock = $this->createMock(AreaList::class); - $configImportCommand = new ConfigImportCommand($this->processorMock); + $configImportCommand = new ConfigImportCommand( + $this->processorMock, + $this->deploymentConfigMock, + $this->adminhtmlAreaProcessorMock, + $this->areaListMock + ); $this->commandTester = new CommandTester($configImportCommand); } @@ -46,6 +70,13 @@ protected function setUp(): void */ public function testExecute() { + $this->deploymentConfigMock->expects($this->once())->method('isAvailable')->willReturn(true); + $this->adminhtmlAreaProcessorMock->expects($this->once()) + ->method('process')->willReturnCallback(function (callable $callback, array $params = []) { + return $callback(...$params); + }); + $this->areaListMock->expects($this->once())->method('getCodes')->willReturn(['adminhtml']); + $this->processorMock->expects($this->once()) ->method('execute'); @@ -57,6 +88,13 @@ public function testExecute() */ public function testExecuteWithException() { + $this->deploymentConfigMock->expects($this->once())->method('isAvailable')->willReturn(true); + $this->adminhtmlAreaProcessorMock->expects($this->once()) + ->method('process')->willReturnCallback(function (callable $callback, array $params = []) { + return $callback(...$params); + }); + $this->areaListMock->expects($this->once())->method('getCodes')->willReturn(['adminhtml']); + $this->processorMock->expects($this->once()) ->method('execute') ->willThrowException(new RuntimeException(__('Some error'))); @@ -64,4 +102,34 @@ public function testExecuteWithException() $this->assertSame(Cli::RETURN_FAILURE, $this->commandTester->execute([])); $this->assertStringContainsString('Some error', $this->commandTester->getDisplay()); } + + /** + * @return void + */ + public function testExecuteWithDeploymentConfigNotAvailable() + { + $this->deploymentConfigMock->expects($this->once())->method('isAvailable')->willReturn(false); + $this->adminhtmlAreaProcessorMock->expects($this->never())->method('process'); + $this->areaListMock->expects($this->never())->method('getCodes'); + + $this->processorMock->expects($this->once()) + ->method('execute'); + + $this->assertSame(Cli::RETURN_SUCCESS, $this->commandTester->execute([])); + } + + /** + * @return void + */ + public function testExecuteWithMissingAdminhtmlLocale() + { + $this->deploymentConfigMock->expects($this->once())->method('isAvailable')->willReturn(true); + $this->adminhtmlAreaProcessorMock->expects($this->never())->method('process'); + $this->areaListMock->expects($this->once())->method('getCodes')->willReturn([]); + + $this->processorMock->expects($this->once()) + ->method('execute'); + + $this->assertSame(Cli::RETURN_SUCCESS, $this->commandTester->execute([])); + } } diff --git a/app/code/Magento/Deploy/etc/di.xml b/app/code/Magento/Deploy/etc/di.xml index 0c32baebf12df..d40ed3144e7e6 100644 --- a/app/code/Magento/Deploy/etc/di.xml +++ b/app/code/Magento/Deploy/etc/di.xml @@ -35,6 +35,12 @@ </argument> </arguments> </type> + <type name="Magento\Deploy\Console\Command\App\ConfigImportCommand"> + <arguments> + <argument name="adminhtmlAreaProcessor" xsi:type="object">Magento\Config\Console\Command\EmulatedAdminhtmlAreaProcessor\Proxy</argument> + <argument name="areaList" xsi:type="object">Magento\Framework\App\AreaList\Proxy</argument> + </arguments> + </type> <type name="Magento\Deploy\Model\Filesystem"> <arguments> <argument name="shell" xsi:type="object">Magento\Framework\App\Shell</argument> 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 041/133] 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 042/133] 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 043/133] 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 a2b93809864fa6dd645d44a5c818266459f53b95 Mon Sep 17 00:00:00 2001 From: Ihor Sviziev <svizev.igor@gmail.com> Date: Fri, 11 Sep 2020 16:02:15 +0300 Subject: [PATCH 044/133] Use one format in all places for array_merge Fix potential issues when no items in array Convert all array_merge into single format --- .../Catalog/Block/Product/ListProduct.php | 2 +- .../Block/Product/ProductList/Related.php | 4 ++-- .../Catalog/Block/Product/ProductList/Upsell.php | 10 +++++----- .../Indexer/Product/Category/Action/Rows.php | 4 ++-- .../Indexer/Product/Flat/FlatTableBuilder.php | 4 ++-- app/code/Magento/Catalog/Model/Product.php | 5 +---- .../Model/Product/Price/TierPriceStorage.php | 2 +- .../Model/ProductLink/ProductLinkQuery.php | 2 +- .../LinkedProductSelectBuilderComposite.php | 2 +- .../LayeredNavigation/Builder/Attribute.php | 6 +++++- .../Product/LayeredNavigation/LayerBuilder.php | 2 +- .../CatalogGraphQl/Model/AttributesJoiner.php | 2 +- .../FilterProcessor/CategoryFilter.php | 2 +- .../CatalogInventory/Model/StockIndex.php | 8 ++++---- .../Model/Layer/Filter/Attribute.php | 2 +- .../Model/Category/Plugin/Store/Group.php | 8 ++++---- .../Observer/AfterImportDataObserver.php | 3 +-- .../CatalogWidget/Block/Product/ProductsList.php | 4 ++-- .../Model/AgreementsValidator.php | 4 ++-- .../templates/browser/content/uploader.phtml | 4 ++-- app/code/Magento/Csp/Helper/InlineUtil.php | 4 ++-- .../Model/System/Currencysymbol.php | 4 ++-- .../Model/Address/CompositeValidator.php | 4 ++-- .../Magento/Customer/Model/Metadata/Form.php | 4 ++-- .../Attribute/Source/CountryWithWebsites.php | 4 ++-- .../Model/Import/Address.php | 4 ++-- .../Model/Import/Customer.php | 8 ++++---- app/code/Magento/Deploy/Package/Package.php | 8 ++++---- .../Command/XmlCatalogGenerateCommand.php | 4 ++-- app/code/Magento/Dhl/Model/Carrier.php | 7 +++---- .../Magento/Directory/Model/AllowedCountries.php | 4 ++-- .../Magento/Directory/Model/CurrencyConfig.php | 4 ++-- .../Adminhtml/Attribute/Edit/Options/Options.php | 4 ++-- app/code/Magento/Eav/Model/Form.php | 4 ++-- .../Magento/Eav/Model/ResourceModel/Helper.php | 2 +- .../Product/CompositeFieldProvider.php | 4 ++-- .../Block/Cart/Item/Renderer/Grouped.php | 2 +- .../Block/Stockqty/Type/Grouped.php | 4 ++-- .../ProcessingErrorAggregator.php | 6 +++--- .../Console/Command/IndexerReindexCommand.php | 16 ++++++++-------- .../Activate/Permissions/Tab/Webapi.php | 4 ++-- .../PageCache/Model/Layout/LayoutPlugin.php | 4 ++-- .../Gateway/Validator/ValidatorComposite.php | 8 ++++---- .../Config/Structure/PaymentSectionModifier.php | 4 ++-- .../Structure/PaymentSectionModifierTest.php | 4 ++-- .../Model/Cart/BuyRequest/BuyRequestBuilder.php | 2 +- .../Model/Cart/BuyRequest/BuyRequestBuilder.php | 4 ++-- .../Resolver/Batch/AbstractLikedProducts.php | 5 ++--- .../Magento/Reports/Block/Product/Viewed.php | 4 ++-- .../Adminhtml/Items/Column/DefaultColumn.php | 4 ++-- .../Block/Order/Email/Items/DefaultItems.php | 4 ++-- .../Order/Email/Items/Order/DefaultOrder.php | 4 ++-- .../Order/Item/Renderer/DefaultRenderer.php | 4 ++-- .../Model/Order/Pdf/Items/AbstractItems.php | 4 ++-- .../Provider/NotSyncedDataProvider.php | 4 ++-- app/code/Magento/Search/Model/Autocomplete.php | 4 ++-- .../Search/Model/SynonymGroupRepository.php | 4 ++-- .../Calculation/Rate/Collection.php | 4 ++-- .../Theme/Model/PageLayout/Config/Builder.php | 2 +- app/code/Magento/Ups/Model/Carrier.php | 3 ++- .../Webapi/Model/Rest/Swagger/Generator.php | 2 +- app/code/Magento/Weee/Model/Total/Quote/Weee.php | 4 ++-- .../Model/Import/ProductTest.php | 4 ++-- .../Magento/Framework/App/Utility/Files.php | 4 ++-- .../Model/EntitySnapshot/AttributeProvider.php | 2 +- .../Framework/Module/ModuleList/Loader.php | 2 +- .../ObjectManager/Factory/AbstractFactory.php | 2 +- .../Magento/Setup/Console/Style/MagentoStyle.php | 4 ++-- .../Setup/Module/Di/Code/Scanner/PhpScanner.php | 4 ++-- 69 files changed, 144 insertions(+), 145 deletions(-) diff --git a/app/code/Magento/Catalog/Block/Product/ListProduct.php b/app/code/Magento/Catalog/Block/Product/ListProduct.php index 6cec9bf3ef88a..b181a5392905b 100644 --- a/app/code/Magento/Catalog/Block/Product/ListProduct.php +++ b/app/code/Magento/Catalog/Block/Product/ListProduct.php @@ -367,7 +367,7 @@ public function getIdentities() $identities[] = $item->getIdentities(); } } - $identities = array_merge(...$identities); + $identities = array_merge([], ...$identities); return $identities; } diff --git a/app/code/Magento/Catalog/Block/Product/ProductList/Related.php b/app/code/Magento/Catalog/Block/Product/ProductList/Related.php index 387fac770c5bc..42f610f89768d 100644 --- a/app/code/Magento/Catalog/Block/Product/ProductList/Related.php +++ b/app/code/Magento/Catalog/Block/Product/ProductList/Related.php @@ -143,11 +143,11 @@ public function getItems() */ public function getIdentities() { - $identities = [[]]; + $identities = []; foreach ($this->getItems() as $item) { $identities[] = $item->getIdentities(); } - return array_merge(...$identities); + return array_merge([], ...$identities); } /** diff --git a/app/code/Magento/Catalog/Block/Product/ProductList/Upsell.php b/app/code/Magento/Catalog/Block/Product/ProductList/Upsell.php index ac66392efe5dc..adcb1b5666560 100644 --- a/app/code/Magento/Catalog/Block/Product/ProductList/Upsell.php +++ b/app/code/Magento/Catalog/Block/Product/ProductList/Upsell.php @@ -267,10 +267,10 @@ public function getItemLimit($type = '') */ public function getIdentities() { - $identities = array_map(function (DataObject $item) { - return $item->getIdentities(); - }, $this->getItems()) ?: [[]]; - - return array_merge(...$identities); + $identities = []; + foreach ($this->getItems() as $item) { + $identities[] = $item->getIdentities(); + } + return array_merge([], ...$identities); } } diff --git a/app/code/Magento/Catalog/Model/Indexer/Product/Category/Action/Rows.php b/app/code/Magento/Catalog/Model/Indexer/Product/Category/Action/Rows.php index edd68422ec4ac..861f7c9c1c50e 100644 --- a/app/code/Magento/Catalog/Model/Indexer/Product/Category/Action/Rows.php +++ b/app/code/Magento/Catalog/Model/Indexer/Product/Category/Action/Rows.php @@ -270,14 +270,14 @@ private function getCategoryIdsFromIndex(array $productIds): array ); $categoryIds[] = $storeCategories; } - $categoryIds = array_merge(...$categoryIds); + $categoryIds = array_merge([], ...$categoryIds); $parentCategories = [$categoryIds]; foreach ($categoryIds as $categoryId) { $parentIds = explode('/', $this->getPathFromCategoryId($categoryId)); $parentCategories[] = $parentIds; } - $categoryIds = array_unique(array_merge(...$parentCategories)); + $categoryIds = array_unique(array_merge([], ...$parentCategories)); return $categoryIds; } diff --git a/app/code/Magento/Catalog/Model/Indexer/Product/Flat/FlatTableBuilder.php b/app/code/Magento/Catalog/Model/Indexer/Product/Flat/FlatTableBuilder.php index 99d75186eca8c..a0af09edf14c5 100644 --- a/app/code/Magento/Catalog/Model/Indexer/Product/Flat/FlatTableBuilder.php +++ b/app/code/Magento/Catalog/Model/Indexer/Product/Flat/FlatTableBuilder.php @@ -261,7 +261,7 @@ protected function _fillTemporaryFlatTable(array $tables, $storeId, $valueFieldS $select->from( ['et' => $entityTemporaryTableName], - array_merge(...$allColumns) + array_merge([], ...$allColumns) )->joinInner( ['e' => $this->resource->getTableName('catalog_product_entity')], 'e.entity_id = et.entity_id', @@ -306,7 +306,7 @@ protected function _fillTemporaryFlatTable(array $tables, $storeId, $valueFieldS $allColumns[] = $columnValueNames; } } - $sql = $select->insertFromSelect($temporaryFlatTableName, array_merge(...$allColumns), false); + $sql = $select->insertFromSelect($temporaryFlatTableName, array_merge([], ...$allColumns), false); $this->_connection->query($sql); } diff --git a/app/code/Magento/Catalog/Model/Product.php b/app/code/Magento/Catalog/Model/Product.php index 7c463267e5a58..5e6f3ca5fe970 100644 --- a/app/code/Magento/Catalog/Model/Product.php +++ b/app/code/Magento/Catalog/Model/Product.php @@ -835,10 +835,7 @@ public function getStoreIds() $storeIds[] = $websiteStores; } } - if ($storeIds) { - $storeIds = array_merge(...$storeIds); - } - $this->setStoreIds($storeIds); + $this->setStoreIds(array_merge([], ...$storeIds)); } return $this->getData('store_ids'); } diff --git a/app/code/Magento/Catalog/Model/Product/Price/TierPriceStorage.php b/app/code/Magento/Catalog/Model/Product/Price/TierPriceStorage.php index 36ef1826462b0..7e410f0e5feb3 100644 --- a/app/code/Magento/Catalog/Model/Product/Price/TierPriceStorage.php +++ b/app/code/Magento/Catalog/Model/Product/Price/TierPriceStorage.php @@ -220,7 +220,7 @@ private function retrieveAffectedIds(array $skus): array $affectedIds[] = array_keys($productId); } - return $affectedIds ? array_unique(array_merge(...$affectedIds)) : []; + return array_unique(array_merge([], ...$affectedIds)); } /** diff --git a/app/code/Magento/Catalog/Model/ProductLink/ProductLinkQuery.php b/app/code/Magento/Catalog/Model/ProductLink/ProductLinkQuery.php index 4bc400605a429..1d5ef722db8b1 100644 --- a/app/code/Magento/Catalog/Model/ProductLink/ProductLinkQuery.php +++ b/app/code/Magento/Catalog/Model/ProductLink/ProductLinkQuery.php @@ -103,7 +103,7 @@ private function extractRequestedLinkTypes(array $criteria): array if (count($linkTypesToLoad) === 1) { $linkTypesToLoad = $linkTypesToLoad[0]; } else { - $linkTypesToLoad = array_merge(...$linkTypesToLoad); + $linkTypesToLoad = array_merge([], ...$linkTypesToLoad); } $linkTypesToLoad = array_flip($linkTypesToLoad); $linkTypes = array_filter( diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product/LinkedProductSelectBuilderComposite.php b/app/code/Magento/Catalog/Model/ResourceModel/Product/LinkedProductSelectBuilderComposite.php index 17ca389777c5b..c7c08bc805a1d 100644 --- a/app/code/Magento/Catalog/Model/ResourceModel/Product/LinkedProductSelectBuilderComposite.php +++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/LinkedProductSelectBuilderComposite.php @@ -33,7 +33,7 @@ public function build(int $productId, int $storeId) : array foreach ($this->linkedProductSelectBuilder as $productSelectBuilder) { $selects[] = $productSelectBuilder->build($productId, $storeId); } - $selects = array_merge(...$selects); + $selects = array_merge([], ...$selects); return $selects; } diff --git a/app/code/Magento/CatalogGraphQl/DataProvider/Product/LayeredNavigation/Builder/Attribute.php b/app/code/Magento/CatalogGraphQl/DataProvider/Product/LayeredNavigation/Builder/Attribute.php index 105e91320de49..5fce0fcdf3ca2 100644 --- a/app/code/Magento/CatalogGraphQl/DataProvider/Product/LayeredNavigation/Builder/Attribute.php +++ b/app/code/Magento/CatalogGraphQl/DataProvider/Product/LayeredNavigation/Builder/Attribute.php @@ -155,6 +155,10 @@ function (AggregationValueInterface $value) { return []; } - return $this->attributeOptionProvider->getOptions(\array_merge(...$attributeOptionIds), $storeId, $attributes); + return $this->attributeOptionProvider->getOptions( + \array_merge([], ...$attributeOptionIds), + $storeId, + $attributes + ); } } diff --git a/app/code/Magento/CatalogGraphQl/DataProvider/Product/LayeredNavigation/LayerBuilder.php b/app/code/Magento/CatalogGraphQl/DataProvider/Product/LayeredNavigation/LayerBuilder.php index ff661236be62f..ac3f396b45ef8 100644 --- a/app/code/Magento/CatalogGraphQl/DataProvider/Product/LayeredNavigation/LayerBuilder.php +++ b/app/code/Magento/CatalogGraphQl/DataProvider/Product/LayeredNavigation/LayerBuilder.php @@ -36,7 +36,7 @@ public function build(AggregationInterface $aggregation, ?int $storeId): array foreach ($this->builders as $builder) { $layers[] = $builder->build($aggregation, $storeId); } - $layers = \array_merge(...$layers); + $layers = \array_merge([], ...$layers); return \array_filter($layers); } diff --git a/app/code/Magento/CatalogGraphQl/Model/AttributesJoiner.php b/app/code/Magento/CatalogGraphQl/Model/AttributesJoiner.php index 0bfd9d58ec969..34f5dd831686c 100644 --- a/app/code/Magento/CatalogGraphQl/Model/AttributesJoiner.php +++ b/app/code/Magento/CatalogGraphQl/Model/AttributesJoiner.php @@ -88,7 +88,7 @@ public function getQueryFields(FieldNode $fieldNode, ResolveInfo $resolveInfo): } } if ($fragmentFields) { - $selectedFields = array_merge($selectedFields, array_merge(...$fragmentFields)); + $selectedFields = array_merge([], $selectedFields, ...$fragmentFields); } $this->setSelectionsForFieldNode($fieldNode, array_unique($selectedFields)); } diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/SearchCriteria/CollectionProcessor/FilterProcessor/CategoryFilter.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/SearchCriteria/CollectionProcessor/FilterProcessor/CategoryFilter.php index f709f8cd6eb72..8d584d15fff0e 100644 --- a/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/SearchCriteria/CollectionProcessor/FilterProcessor/CategoryFilter.php +++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/SearchCriteria/CollectionProcessor/FilterProcessor/CategoryFilter.php @@ -64,7 +64,7 @@ public function apply(Filter $filter, AbstractDb $collection) $collection->addCategoryFilter($category); } - $categoryProductIds = array_unique(array_merge(...$categoryProducts)); + $categoryProductIds = array_unique(array_merge([], ...$categoryProducts)); $collection->addIdFilter($categoryProductIds); return true; } diff --git a/app/code/Magento/CatalogInventory/Model/StockIndex.php b/app/code/Magento/CatalogInventory/Model/StockIndex.php index ad0cff43c6ac9..6b659073485ad 100644 --- a/app/code/Magento/CatalogInventory/Model/StockIndex.php +++ b/app/code/Magento/CatalogInventory/Model/StockIndex.php @@ -169,11 +169,11 @@ protected function processChildren( $requiredChildrenIds = $typeInstance->getChildrenIds($productId, true); if ($requiredChildrenIds) { - $childrenIds = [[]]; + $childrenIds = []; foreach ($requiredChildrenIds as $groupedChildrenIds) { $childrenIds[] = $groupedChildrenIds; } - $childrenIds = array_merge(...$childrenIds); + $childrenIds = array_merge([], ...$childrenIds); $childrenWebsites = $this->productWebsite->getWebsites($childrenIds); foreach ($websitesWithStores as $websiteId => $storeId) { @@ -232,13 +232,13 @@ protected function getWebsitesWithDefaultStores($websiteId = null) */ protected function processParents($productId, $websiteId) { - $parentIds = [[]]; + $parentIds = []; foreach ($this->getProductTypeInstances() as $typeInstance) { /* @var ProductType\AbstractType $typeInstance */ $parentIds[] = $typeInstance->getParentIdsByChild($productId); } - $parentIds = array_merge(...$parentIds); + $parentIds = array_merge([], ...$parentIds); if (empty($parentIds)) { return; diff --git a/app/code/Magento/CatalogSearch/Model/Layer/Filter/Attribute.php b/app/code/Magento/CatalogSearch/Model/Layer/Filter/Attribute.php index b1aecc6885bf0..080af5daa0322 100644 --- a/app/code/Magento/CatalogSearch/Model/Layer/Filter/Attribute.php +++ b/app/code/Magento/CatalogSearch/Model/Layer/Filter/Attribute.php @@ -70,7 +70,7 @@ public function apply(\Magento\Framework\App\RequestInterface $request) $label = $this->getOptionText($value); $labels[] = is_array($label) ? $label : [$label]; } - $label = implode(',', array_unique(array_merge(...$labels))); + $label = implode(',', array_unique(array_merge([], ...$labels))); $this->getLayer() ->getState() ->addFilter($this->_createItem($label, $attributeValue)); diff --git a/app/code/Magento/CatalogUrlRewrite/Model/Category/Plugin/Store/Group.php b/app/code/Magento/CatalogUrlRewrite/Model/Category/Plugin/Store/Group.php index 308b82e38c43a..50875b1a418d0 100644 --- a/app/code/Magento/CatalogUrlRewrite/Model/Category/Plugin/Store/Group.php +++ b/app/code/Magento/CatalogUrlRewrite/Model/Category/Plugin/Store/Group.php @@ -121,7 +121,7 @@ public function afterSave( */ protected function generateProductUrls($websiteId, $originWebsiteId) { - $urls = [[]]; + $urls = []; $websiteIds = $websiteId != $originWebsiteId ? [$websiteId, $originWebsiteId] : [$websiteId]; @@ -136,7 +136,7 @@ protected function generateProductUrls($websiteId, $originWebsiteId) $urls[] = $this->productUrlRewriteGenerator->generate($product); } - return array_merge(...$urls); + return array_merge([], ...$urls); } /** @@ -148,7 +148,7 @@ protected function generateProductUrls($websiteId, $originWebsiteId) */ protected function generateCategoryUrls($rootCategoryId, $storeIds) { - $urls = [[]]; + $urls = []; $categories = $this->categoryFactory->create()->getCategories($rootCategoryId, 1, false, true); foreach ($categories as $category) { /** @var \Magento\Catalog\Model\Category $category */ @@ -157,6 +157,6 @@ protected function generateCategoryUrls($rootCategoryId, $storeIds) $urls[] = $this->categoryUrlRewriteGenerator->generate($category); } - return array_merge(...$urls); + return array_merge([], ...$urls); } } diff --git a/app/code/Magento/CatalogUrlRewrite/Observer/AfterImportDataObserver.php b/app/code/Magento/CatalogUrlRewrite/Observer/AfterImportDataObserver.php index b1dfa79373a05..e8e6f55f80051 100644 --- a/app/code/Magento/CatalogUrlRewrite/Observer/AfterImportDataObserver.php +++ b/app/code/Magento/CatalogUrlRewrite/Observer/AfterImportDataObserver.php @@ -459,8 +459,7 @@ private function categoriesUrlRewriteGenerate(): array } } } - $result = !empty($urls) ? array_merge(...$urls) : []; - return $result; + return array_merge([], ...$urls); } /** diff --git a/app/code/Magento/CatalogWidget/Block/Product/ProductsList.php b/app/code/Magento/CatalogWidget/Block/Product/ProductsList.php index 9934cc9ad106a..0cd2a3137d39a 100644 --- a/app/code/Magento/CatalogWidget/Block/Product/ProductsList.php +++ b/app/code/Magento/CatalogWidget/Block/Product/ProductsList.php @@ -506,7 +506,7 @@ public function getPagerHtml() */ public function getIdentities() { - $identities = [[]]; + $identities = []; if ($this->getProductCollection()) { foreach ($this->getProductCollection() as $product) { if ($product instanceof IdentityInterface) { @@ -514,7 +514,7 @@ public function getIdentities() } } } - $identities = array_merge(...$identities); + $identities = array_merge([], ...$identities); return $identities ?: [Product::CACHE_TAG]; } diff --git a/app/code/Magento/CheckoutAgreements/Model/AgreementsValidator.php b/app/code/Magento/CheckoutAgreements/Model/AgreementsValidator.php index 2643e69ba1efd..c78c807f9ea20 100644 --- a/app/code/Magento/CheckoutAgreements/Model/AgreementsValidator.php +++ b/app/code/Magento/CheckoutAgreements/Model/AgreementsValidator.php @@ -35,12 +35,12 @@ public function __construct($list = null) public function isValid($agreementIds = []) { $agreementIds = $agreementIds === null ? [] : $agreementIds; - $requiredAgreements = [[]]; + $requiredAgreements = []; foreach ($this->agreementsProviders as $agreementsProvider) { $requiredAgreements[] = $agreementsProvider->getRequiredAgreementIds(); } - $agreementsDiff = array_diff(array_merge(...$requiredAgreements), $agreementIds); + $agreementsDiff = array_diff(array_merge([], ...$requiredAgreements), $agreementIds); return empty($agreementsDiff); } diff --git a/app/code/Magento/Cms/view/adminhtml/templates/browser/content/uploader.phtml b/app/code/Magento/Cms/view/adminhtml/templates/browser/content/uploader.phtml index d1c204c01ad1c..154e76bd93e41 100644 --- a/app/code/Magento/Cms/view/adminhtml/templates/browser/content/uploader.phtml +++ b/app/code/Magento/Cms/view/adminhtml/templates/browser/content/uploader.phtml @@ -11,14 +11,14 @@ $filters = $block->getConfig()->getFilters() ?? []; $allowedExtensions = []; $blockHtmlId = $block->getHtmlId(); -$listExtensions = [[]]; +$listExtensions = []; foreach ($filters as $media_type) { $listExtensions[] = array_map(function ($fileExt) { return ltrim($fileExt, '.*'); }, $media_type['files']); } -$allowedExtensions = array_merge(...$listExtensions); +$allowedExtensions = array_merge([], ...$listExtensions); $resizeConfig = $block->getImageUploadConfigData()->getIsResizeEnabled() ? "{action: 'resize', maxWidth: " diff --git a/app/code/Magento/Csp/Helper/InlineUtil.php b/app/code/Magento/Csp/Helper/InlineUtil.php index f9dd9aafa459e..648ba51e34f7d 100644 --- a/app/code/Magento/Csp/Helper/InlineUtil.php +++ b/app/code/Magento/Csp/Helper/InlineUtil.php @@ -110,14 +110,14 @@ private function extractHost(string $url): ?string */ private function extractRemoteFonts(string $styleContent): array { - $urlsFound = [[]]; + $urlsFound = []; preg_match_all('/\@font\-face\s*?\{([^\}]*)[^\}]*?\}/im', $styleContent, $fontFaces); foreach ($fontFaces[1] as $fontFaceContent) { preg_match_all('/url\([\'\"]?(http(s)?\:[^\)]+)[\'\"]?\)/i', $fontFaceContent, $urls); $urlsFound[] = $urls[1]; } - return array_map([$this, 'extractHost'], array_merge(...$urlsFound)); + return array_map([$this, 'extractHost'], array_merge([], ...$urlsFound)); } /** diff --git a/app/code/Magento/CurrencySymbol/Model/System/Currencysymbol.php b/app/code/Magento/CurrencySymbol/Model/System/Currencysymbol.php index d48df02d9de27..400aa56bc68e9 100644 --- a/app/code/Magento/CurrencySymbol/Model/System/Currencysymbol.php +++ b/app/code/Magento/CurrencySymbol/Model/System/Currencysymbol.php @@ -292,7 +292,7 @@ protected function _unserializeStoreConfig($configPath, $storeId = null) */ protected function getAllowedCurrencies() { - $allowedCurrencies = [[]]; + $allowedCurrencies = []; $allowedCurrencies[] = explode( self::ALLOWED_CURRENCIES_CONFIG_SEPARATOR, $this->_scopeConfig->getValue( @@ -330,6 +330,6 @@ protected function getAllowedCurrencies() } } } - return array_unique(array_merge(...$allowedCurrencies)); + return array_unique(array_merge([], ...$allowedCurrencies)); } } diff --git a/app/code/Magento/Customer/Model/Address/CompositeValidator.php b/app/code/Magento/Customer/Model/Address/CompositeValidator.php index 4c77f10c11de4..62308ba329d03 100644 --- a/app/code/Magento/Customer/Model/Address/CompositeValidator.php +++ b/app/code/Magento/Customer/Model/Address/CompositeValidator.php @@ -30,11 +30,11 @@ public function __construct( */ public function validate(AbstractAddress $address) { - $errors = [[]]; + $errors = []; foreach ($this->validators as $validator) { $errors[] = $validator->validate($address); } - return array_merge(...$errors); + return array_merge([], ...$errors); } } diff --git a/app/code/Magento/Customer/Model/Metadata/Form.php b/app/code/Magento/Customer/Model/Metadata/Form.php index 85637ebf508b8..81ded6dec071a 100644 --- a/app/code/Magento/Customer/Model/Metadata/Form.php +++ b/app/code/Magento/Customer/Model/Metadata/Form.php @@ -363,11 +363,11 @@ public function validateData(array $data) { $validator = $this->_getValidator($data); if (!$validator->isValid(false)) { - $messages = [[]]; + $messages = []; foreach ($validator->getMessages() as $errorMessages) { $messages[] = (array)$errorMessages; } - return array_merge(...$messages); + return array_merge([], ...$messages); } return true; } diff --git a/app/code/Magento/Customer/Model/ResourceModel/Address/Attribute/Source/CountryWithWebsites.php b/app/code/Magento/Customer/Model/ResourceModel/Address/Attribute/Source/CountryWithWebsites.php index 020067570efb4..1ca1c5622803f 100644 --- a/app/code/Magento/Customer/Model/ResourceModel/Address/Attribute/Source/CountryWithWebsites.php +++ b/app/code/Magento/Customer/Model/ResourceModel/Address/Attribute/Source/CountryWithWebsites.php @@ -84,7 +84,7 @@ public function getAllOptions($withEmpty = true, $defaultValues = false) $websiteIds = []; if (!$this->shareConfig->isGlobalScope()) { - $allowedCountries = [[]]; + $allowedCountries = []; foreach ($this->storeManager->getWebsites() as $website) { $countries = $this->allowedCountriesReader @@ -96,7 +96,7 @@ public function getAllOptions($withEmpty = true, $defaultValues = false) } } - $allowedCountries = array_unique(array_merge(...$allowedCountries)); + $allowedCountries = array_unique(array_merge([], ...$allowedCountries)); } else { $allowedCountries = $this->allowedCountriesReader->getAllowedCountries(); } diff --git a/app/code/Magento/CustomerImportExport/Model/Import/Address.php b/app/code/Magento/CustomerImportExport/Model/Import/Address.php index f15f920fe95f4..0c3be73ec5047 100644 --- a/app/code/Magento/CustomerImportExport/Model/Import/Address.php +++ b/app/code/Magento/CustomerImportExport/Model/Import/Address.php @@ -525,12 +525,12 @@ public function validateData() protected function _importData() { //Preparing data for mass validation/import. - $rows = [[]]; + $rows = []; while ($bunch = $this->_dataSourceModel->getNextBunch()) { $rows[] = $bunch; } - $this->prepareCustomerData(array_merge(...$rows)); + $this->prepareCustomerData(array_merge([], ...$rows)); unset($bunch, $rows); $this->_dataSourceModel->getIterator()->rewind(); diff --git a/app/code/Magento/CustomerImportExport/Model/Import/Customer.php b/app/code/Magento/CustomerImportExport/Model/Import/Customer.php index 5ebf242bd6ac4..2a02205bdc7e5 100644 --- a/app/code/Magento/CustomerImportExport/Model/Import/Customer.php +++ b/app/code/Magento/CustomerImportExport/Model/Import/Customer.php @@ -514,8 +514,8 @@ protected function _importData() { while ($bunch = $this->_dataSourceModel->getNextBunch()) { $this->prepareCustomerData($bunch); - $entitiesToCreate = [[]]; - $entitiesToUpdate = [[]]; + $entitiesToCreate = []; + $entitiesToUpdate = []; $entitiesToDelete = []; $attributesToSave = []; @@ -549,8 +549,8 @@ protected function _importData() } } - $entitiesToCreate = array_merge(...$entitiesToCreate); - $entitiesToUpdate = array_merge(...$entitiesToUpdate); + $entitiesToCreate = array_merge([], ...$entitiesToCreate); + $entitiesToUpdate = array_merge([], ...$entitiesToUpdate); $this->updateItemsCounterStats($entitiesToCreate, $entitiesToUpdate, $entitiesToDelete); /** diff --git a/app/code/Magento/Deploy/Package/Package.php b/app/code/Magento/Deploy/Package/Package.php index 2a83d0d4c56ec..3c4af7039b4a8 100644 --- a/app/code/Magento/Deploy/Package/Package.php +++ b/app/code/Magento/Deploy/Package/Package.php @@ -443,11 +443,11 @@ public function getResultMap() */ public function getParentMap() { - $map = [[]]; + $map = []; foreach ($this->getParentPackages() as $parentPackage) { $map[] = $parentPackage->getMap(); } - return array_merge(...$map); + return array_merge([], ...$map); } /** @@ -458,7 +458,7 @@ public function getParentMap() */ public function getParentFiles($type = null) { - $files = [[]]; + $files = []; foreach ($this->getParentPackages() as $parentPackage) { if ($type === null) { $files[] = $parentPackage->getFiles(); @@ -466,7 +466,7 @@ public function getParentFiles($type = null) $files[] = $parentPackage->getFilesByType($type); } } - return array_merge(...$files); + return array_merge([], ...$files); } /** diff --git a/app/code/Magento/Developer/Console/Command/XmlCatalogGenerateCommand.php b/app/code/Magento/Developer/Console/Command/XmlCatalogGenerateCommand.php index 9e473ccaa2d92..8bd827958df15 100644 --- a/app/code/Magento/Developer/Console/Command/XmlCatalogGenerateCommand.php +++ b/app/code/Magento/Developer/Console/Command/XmlCatalogGenerateCommand.php @@ -116,7 +116,7 @@ private function getUrnDictionary(OutputInterface $output) $files = $this->filesUtility->getXmlCatalogFiles('*.xml'); $files = array_merge($files, $this->filesUtility->getXmlCatalogFiles('*.xsd')); - $urns = [[]]; + $urns = []; foreach ($files as $file) { // phpcs:ignore Magento2.Functions.DiscouragedFunction $fileDir = dirname($file[0]); @@ -130,7 +130,7 @@ private function getUrnDictionary(OutputInterface $output) $urns[] = $matches[1]; } } - $urns = array_unique(array_merge(...$urns)); + $urns = array_unique(array_merge([], ...$urns)); $paths = []; foreach ($urns as $urn) { try { diff --git a/app/code/Magento/Dhl/Model/Carrier.php b/app/code/Magento/Dhl/Model/Carrier.php index 204094571ba3b..c5eb27b21e58b 100644 --- a/app/code/Magento/Dhl/Model/Carrier.php +++ b/app/code/Magento/Dhl/Model/Carrier.php @@ -826,10 +826,9 @@ protected function _getAllItems() $fullItems[] = array_fill(0, $qty, $this->_getWeight($itemWeight)); } } - if ($fullItems) { - $fullItems = array_merge(...$fullItems); - sort($fullItems); - } + + $fullItems = array_merge([], ...$fullItems); + sort($fullItems); return $fullItems; } diff --git a/app/code/Magento/Directory/Model/AllowedCountries.php b/app/code/Magento/Directory/Model/AllowedCountries.php index 2ceeb70ba5b01..69326439edc03 100644 --- a/app/code/Magento/Directory/Model/AllowedCountries.php +++ b/app/code/Magento/Directory/Model/AllowedCountries.php @@ -62,11 +62,11 @@ public function getAllowedCountries( switch ($scope) { case ScopeInterface::SCOPE_WEBSITES: case ScopeInterface::SCOPE_STORES: - $allowedCountries = [[]]; + $allowedCountries = []; foreach ($scopeCode as $singleFilter) { $allowedCountries[] = $this->getCountriesFromConfig($this->getSingleScope($scope), $singleFilter); } - $allowedCountries = array_merge(...$allowedCountries); + $allowedCountries = array_merge([], ...$allowedCountries); break; default: $allowedCountries = $this->getCountriesFromConfig($scope, $scopeCode); diff --git a/app/code/Magento/Directory/Model/CurrencyConfig.php b/app/code/Magento/Directory/Model/CurrencyConfig.php index f7230df6e86ea..b574170ac5d3c 100644 --- a/app/code/Magento/Directory/Model/CurrencyConfig.php +++ b/app/code/Magento/Directory/Model/CurrencyConfig.php @@ -73,7 +73,7 @@ public function getConfigCurrencies(string $path) */ private function getConfigForAllStores(string $path) { - $storesResult = [[]]; + $storesResult = []; foreach ($this->storeManager->getStores() as $store) { $storesResult[] = explode( ',', @@ -81,7 +81,7 @@ private function getConfigForAllStores(string $path) ); } - return array_merge(...$storesResult); + return array_merge([], ...$storesResult); } /** diff --git a/app/code/Magento/Eav/Block/Adminhtml/Attribute/Edit/Options/Options.php b/app/code/Magento/Eav/Block/Adminhtml/Attribute/Edit/Options/Options.php index 69f417e1ea732..f53f1e97a872d 100644 --- a/app/code/Magento/Eav/Block/Adminhtml/Attribute/Edit/Options/Options.php +++ b/app/code/Magento/Eav/Block/Adminhtml/Attribute/Edit/Options/Options.php @@ -152,7 +152,7 @@ protected function _prepareOptionValues( $inputType = ''; } - $values = [[]]; + $values = []; $isSystemAttribute = is_array($optionCollection); if ($isSystemAttribute) { $values[] = $this->getPreparedValues($optionCollection, $isSystemAttribute, $inputType, $defaultValues); @@ -168,7 +168,7 @@ protected function _prepareOptionValues( } } - return array_merge(...$values); + return array_merge([], ...$values); } /** diff --git a/app/code/Magento/Eav/Model/Form.php b/app/code/Magento/Eav/Model/Form.php index 074c6cf46a2f4..b06c084cf6675 100644 --- a/app/code/Magento/Eav/Model/Form.php +++ b/app/code/Magento/Eav/Model/Form.php @@ -487,11 +487,11 @@ public function validateData(array $data) { $validator = $this->_getValidator($data); if (!$validator->isValid($this->getEntity())) { - $messages = [[]]; + $messages = []; foreach ($validator->getMessages() as $errorMessages) { $messages[] = (array)$errorMessages; } - return array_merge(...$messages); + return array_merge([], ...$messages); } return true; } diff --git a/app/code/Magento/Eav/Model/ResourceModel/Helper.php b/app/code/Magento/Eav/Model/ResourceModel/Helper.php index fc8a47994a6aa..569da0ac2bb4f 100644 --- a/app/code/Magento/Eav/Model/ResourceModel/Helper.php +++ b/app/code/Magento/Eav/Model/ResourceModel/Helper.php @@ -117,7 +117,7 @@ public function getLoadAttributesSelectGroups($selects) if (array_key_exists('all', $mainGroup)) { // it is better to call array_merge once after loop instead of calling it on each loop - $mainGroup['all'] = array_merge(...$mainGroup['all']); + $mainGroup['all'] = array_merge([], ...$mainGroup['all']); } return array_values($mainGroup); diff --git a/app/code/Magento/Elasticsearch/Model/Adapter/FieldMapper/Product/CompositeFieldProvider.php b/app/code/Magento/Elasticsearch/Model/Adapter/FieldMapper/Product/CompositeFieldProvider.php index b276b67ff7fba..980842d6233b1 100644 --- a/app/code/Magento/Elasticsearch/Model/Adapter/FieldMapper/Product/CompositeFieldProvider.php +++ b/app/code/Magento/Elasticsearch/Model/Adapter/FieldMapper/Product/CompositeFieldProvider.php @@ -40,12 +40,12 @@ public function __construct(array $providers) */ public function getFields(array $context = []): array { - $allAttributes = [[]]; + $allAttributes = []; foreach ($this->providers as $provider) { $allAttributes[] = $provider->getFields($context); } - return array_merge(...$allAttributes); + return array_merge([], ...$allAttributes); } } diff --git a/app/code/Magento/GroupedProduct/Block/Cart/Item/Renderer/Grouped.php b/app/code/Magento/GroupedProduct/Block/Cart/Item/Renderer/Grouped.php index 197be38fb7f5f..8dc153f28c162 100644 --- a/app/code/Magento/GroupedProduct/Block/Cart/Item/Renderer/Grouped.php +++ b/app/code/Magento/GroupedProduct/Block/Cart/Item/Renderer/Grouped.php @@ -49,6 +49,6 @@ public function getIdentities() if ($this->getItem()) { $identities[] = $this->getGroupedProduct()->getIdentities(); } - return array_merge(...$identities); + return array_merge([], ...$identities); } } diff --git a/app/code/Magento/GroupedProduct/Block/Stockqty/Type/Grouped.php b/app/code/Magento/GroupedProduct/Block/Stockqty/Type/Grouped.php index 97dc90ec93493..78ae4047c0aad 100644 --- a/app/code/Magento/GroupedProduct/Block/Stockqty/Type/Grouped.php +++ b/app/code/Magento/GroupedProduct/Block/Stockqty/Type/Grouped.php @@ -32,10 +32,10 @@ protected function _getChildProducts() */ public function getIdentities() { - $identities = [[]]; + $identities = []; foreach ($this->getChildProducts() as $item) { $identities[] = $item->getIdentities(); } - return array_merge(...$identities); + return array_merge([], ...$identities); } } diff --git a/app/code/Magento/ImportExport/Model/Import/ErrorProcessing/ProcessingErrorAggregator.php b/app/code/Magento/ImportExport/Model/Import/ErrorProcessing/ProcessingErrorAggregator.php index 5ea6227231543..2f8bfdcf70a5e 100644 --- a/app/code/Magento/ImportExport/Model/Import/ErrorProcessing/ProcessingErrorAggregator.php +++ b/app/code/Magento/ImportExport/Model/Import/ErrorProcessing/ProcessingErrorAggregator.php @@ -242,7 +242,7 @@ public function getAllErrors() } $errors = array_values($this->items['rows']); - return array_merge(...$errors); + return array_merge([], ...$errors); } /** @@ -253,14 +253,14 @@ public function getAllErrors() */ public function getErrorsByCode(array $codes) { - $result = [[]]; + $result = []; foreach ($codes as $code) { if (isset($this->items['codes'][$code])) { $result[] = $this->items['codes'][$code]; } } - return array_merge(...$result); + return array_merge([], ...$result); } /** diff --git a/app/code/Magento/Indexer/Console/Command/IndexerReindexCommand.php b/app/code/Magento/Indexer/Console/Command/IndexerReindexCommand.php index 775f585519947..872ece7aaabb5 100644 --- a/app/code/Magento/Indexer/Console/Command/IndexerReindexCommand.php +++ b/app/code/Magento/Indexer/Console/Command/IndexerReindexCommand.php @@ -124,16 +124,16 @@ protected function getIndexers(InputInterface $input) return $indexers; } - $relatedIndexers = [[]]; - $dependentIndexers = [[]]; + $relatedIndexers = []; + $dependentIndexers = []; foreach ($indexers as $indexer) { $relatedIndexers[] = $this->getRelatedIndexerIds($indexer->getId()); $dependentIndexers[] = $this->getDependentIndexerIds($indexer->getId()); } - $relatedIndexers = $relatedIndexers ? array_unique(array_merge(...$relatedIndexers)) : []; - $dependentIndexers = $dependentIndexers ? array_merge(...$dependentIndexers) : []; + $relatedIndexers = array_unique(array_merge([], ...$relatedIndexers)); + $dependentIndexers = array_merge([], ...$dependentIndexers); $invalidRelatedIndexers = []; foreach ($relatedIndexers as $relatedIndexer) { @@ -164,12 +164,12 @@ protected function getIndexers(InputInterface $input) */ private function getRelatedIndexerIds(string $indexerId): array { - $relatedIndexerIds = [[]]; + $relatedIndexerIds = []; foreach ($this->getDependencyInfoProvider()->getIndexerIdsToRunBefore($indexerId) as $relatedIndexerId) { $relatedIndexerIds[] = [$relatedIndexerId]; $relatedIndexerIds[] = $this->getRelatedIndexerIds($relatedIndexerId); } - $relatedIndexerIds = $relatedIndexerIds ? array_unique(array_merge(...$relatedIndexerIds)) : []; + $relatedIndexerIds = array_unique(array_merge([], ...$relatedIndexerIds)); return $relatedIndexerIds; } @@ -182,7 +182,7 @@ private function getRelatedIndexerIds(string $indexerId): array */ private function getDependentIndexerIds(string $indexerId): array { - $dependentIndexerIds = [[]]; + $dependentIndexerIds = []; foreach (array_keys($this->getConfig()->getIndexers()) as $id) { $dependencies = $this->getDependencyInfoProvider()->getIndexerIdsToRunBefore($id); if (array_search($indexerId, $dependencies) !== false) { @@ -190,7 +190,7 @@ private function getDependentIndexerIds(string $indexerId): array $dependentIndexerIds[] = $this->getDependentIndexerIds($id); } } - $dependentIndexerIds = $dependentIndexerIds ? array_unique(array_merge(...$dependentIndexerIds)) : []; + $dependentIndexerIds = array_unique(array_merge([], ...$dependentIndexerIds)); return $dependentIndexerIds; } diff --git a/app/code/Magento/Integration/Block/Adminhtml/Integration/Activate/Permissions/Tab/Webapi.php b/app/code/Magento/Integration/Block/Adminhtml/Integration/Activate/Permissions/Tab/Webapi.php index 2d323fea34e7d..b6ea810666b9b 100644 --- a/app/code/Magento/Integration/Block/Adminhtml/Integration/Activate/Permissions/Tab/Webapi.php +++ b/app/code/Magento/Integration/Block/Adminhtml/Integration/Activate/Permissions/Tab/Webapi.php @@ -222,13 +222,13 @@ public function isTreeEmpty() */ protected function _getAllResourceIds(array $resources) { - $resourceIds = [[]]; + $resourceIds = []; foreach ($resources as $resource) { $resourceIds[] = [$resource['id']]; if (isset($resource['children'])) { $resourceIds[] = $this->_getAllResourceIds($resource['children']); } } - return array_merge(...$resourceIds); + return array_merge([], ...$resourceIds); } } diff --git a/app/code/Magento/PageCache/Model/Layout/LayoutPlugin.php b/app/code/Magento/PageCache/Model/Layout/LayoutPlugin.php index 1b64f3b635c03..6aff8aef2c2d9 100644 --- a/app/code/Magento/PageCache/Model/Layout/LayoutPlugin.php +++ b/app/code/Magento/PageCache/Model/Layout/LayoutPlugin.php @@ -84,7 +84,7 @@ public function afterGenerateElements(Layout $subject) public function afterGetOutput(Layout $subject, $result) { if ($subject->isCacheable() && $this->config->isEnabled()) { - $tags = [[]]; + $tags = []; $isVarnish = $this->config->getType() === Config::VARNISH; foreach ($subject->getAllBlocks() as $block) { @@ -96,7 +96,7 @@ public function afterGetOutput(Layout $subject, $result) $tags[] = $block->getIdentities(); } } - $tags = array_unique(array_merge(...$tags)); + $tags = array_unique(array_merge([], ...$tags)); $tags = $this->pageCacheTagsPreprocessor->process($tags); $this->response->setHeader('X-Magento-Tags', implode(',', $tags)); } diff --git a/app/code/Magento/Payment/Gateway/Validator/ValidatorComposite.php b/app/code/Magento/Payment/Gateway/Validator/ValidatorComposite.php index 8c8d13300849e..af42554484117 100644 --- a/app/code/Magento/Payment/Gateway/Validator/ValidatorComposite.php +++ b/app/code/Magento/Payment/Gateway/Validator/ValidatorComposite.php @@ -59,8 +59,8 @@ public function __construct( public function validate(array $validationSubject) { $isValid = true; - $failsDescriptionAggregate = [[]]; - $errorCodesAggregate = [[]]; + $failsDescriptionAggregate = []; + $errorCodesAggregate = []; foreach ($this->validators as $key => $validator) { $result = $validator->validate($validationSubject); if (!$result->isValid()) { @@ -76,8 +76,8 @@ public function validate(array $validationSubject) return $this->createResult( $isValid, - array_merge(...$failsDescriptionAggregate), - array_merge(...$errorCodesAggregate) + array_merge([], ...$failsDescriptionAggregate), + array_merge([], ...$errorCodesAggregate) ); } } diff --git a/app/code/Magento/Paypal/Model/Config/Structure/PaymentSectionModifier.php b/app/code/Magento/Paypal/Model/Config/Structure/PaymentSectionModifier.php index 61410499e956e..a3cef539dc17b 100644 --- a/app/code/Magento/Paypal/Model/Config/Structure/PaymentSectionModifier.php +++ b/app/code/Magento/Paypal/Model/Config/Structure/PaymentSectionModifier.php @@ -84,7 +84,7 @@ public function modify(array $initialStructure) */ private function getMoveInstructions($section, $data) { - $moved = [[]]; + $moved = []; if (array_key_exists('children', $data)) { foreach ($data['children'] as $childSection => $childData) { @@ -106,6 +106,6 @@ private function getMoveInstructions($section, $data) ]; } - return array_merge(...$moved); + return array_merge([], ...$moved); } } diff --git a/app/code/Magento/Paypal/Test/Unit/Model/Config/Structure/PaymentSectionModifierTest.php b/app/code/Magento/Paypal/Test/Unit/Model/Config/Structure/PaymentSectionModifierTest.php index dc54b71324a9b..a6a18418e92ac 100644 --- a/app/code/Magento/Paypal/Test/Unit/Model/Config/Structure/PaymentSectionModifierTest.php +++ b/app/code/Magento/Paypal/Test/Unit/Model/Config/Structure/PaymentSectionModifierTest.php @@ -162,14 +162,14 @@ public function testMovedToTargetSpecialGroup() */ private function fetchAllAvailableGroups($structure) { - $availableGroups = [[]]; + $availableGroups = []; foreach ($structure as $group => $data) { $availableGroups[] = [$group]; if (isset($data['children'])) { $availableGroups[] = $this->fetchAllAvailableGroups($data['children']); } } - $availableGroups = array_merge(...$availableGroups); + $availableGroups = array_merge([], ...$availableGroups); $availableGroups = array_values(array_unique($availableGroups)); sort($availableGroups); return $availableGroups; diff --git a/app/code/Magento/Quote/Model/Cart/BuyRequest/BuyRequestBuilder.php b/app/code/Magento/Quote/Model/Cart/BuyRequest/BuyRequestBuilder.php index 13b19e4f79c9a..7e8b4d916334f 100644 --- a/app/code/Magento/Quote/Model/Cart/BuyRequest/BuyRequestBuilder.php +++ b/app/code/Magento/Quote/Model/Cart/BuyRequest/BuyRequestBuilder.php @@ -56,6 +56,6 @@ public function build(CartItem $cartItem): DataObject $requestData[] = $provider->execute($cartItem); } - return $this->dataObjectFactory->create(['data' => array_merge(...$requestData)]); + return $this->dataObjectFactory->create(['data' => array_merge([], ...$requestData)]); } } diff --git a/app/code/Magento/QuoteGraphQl/Model/Cart/BuyRequest/BuyRequestBuilder.php b/app/code/Magento/QuoteGraphQl/Model/Cart/BuyRequest/BuyRequestBuilder.php index c14cc1324732c..c4909eef31287 100644 --- a/app/code/Magento/QuoteGraphQl/Model/Cart/BuyRequest/BuyRequestBuilder.php +++ b/app/code/Magento/QuoteGraphQl/Model/Cart/BuyRequest/BuyRequestBuilder.php @@ -45,11 +45,11 @@ public function __construct( */ public function build(array $cartItemData): DataObject { - $requestData = [[]]; + $requestData = []; foreach ($this->providers as $provider) { $requestData[] = $provider->execute($cartItemData); } - return $this->dataObjectFactory->create(['data' => array_merge(...$requestData)]); + return $this->dataObjectFactory->create(['data' => array_merge([], ...$requestData)]); } } diff --git a/app/code/Magento/RelatedProductGraphQl/Model/Resolver/Batch/AbstractLikedProducts.php b/app/code/Magento/RelatedProductGraphQl/Model/Resolver/Batch/AbstractLikedProducts.php index e14d8bde6be74..fac7b23d408e3 100644 --- a/app/code/Magento/RelatedProductGraphQl/Model/Resolver/Batch/AbstractLikedProducts.php +++ b/app/code/Magento/RelatedProductGraphQl/Model/Resolver/Batch/AbstractLikedProducts.php @@ -89,8 +89,7 @@ private function findRelations(array $products, array $loadAttributes, int $link if (!$relations) { return []; } - $relatedIds = array_values($relations); - $relatedIds = array_unique(array_merge(...$relatedIds)); + $relatedIds = array_unique(array_merge([], ...array_values($relations))); //Loading products data. $this->searchCriteriaBuilder->addFilter('entity_id', $relatedIds, 'in'); $relatedSearchResult = $this->productDataProvider->getList( @@ -142,7 +141,7 @@ public function resolve(ContextInterface $context, Field $field, array $requests $products[] = $request->getValue()['model']; $fields[] = $this->productFieldsSelector->getProductFieldsFromInfo($request->getInfo(), $this->getNode()); } - $fields = array_unique(array_merge(...$fields)); + $fields = array_unique(array_merge([], ...$fields)); //Finding relations. $related = $this->findRelations($products, $fields, $this->getLinkType()); diff --git a/app/code/Magento/Reports/Block/Product/Viewed.php b/app/code/Magento/Reports/Block/Product/Viewed.php index ba4d03182213a..09d59e475905b 100644 --- a/app/code/Magento/Reports/Block/Product/Viewed.php +++ b/app/code/Magento/Reports/Block/Product/Viewed.php @@ -76,10 +76,10 @@ protected function _toHtml() */ public function getIdentities() { - $identities = [[]]; + $identities = []; foreach ($this->getItemsCollection() as $item) { $identities[] = $item->getIdentities(); } - return array_merge(...$identities); + return array_merge([], ...$identities); } } diff --git a/app/code/Magento/Sales/Block/Adminhtml/Items/Column/DefaultColumn.php b/app/code/Magento/Sales/Block/Adminhtml/Items/Column/DefaultColumn.php index efef617acf900..81f670de91805 100644 --- a/app/code/Magento/Sales/Block/Adminhtml/Items/Column/DefaultColumn.php +++ b/app/code/Magento/Sales/Block/Adminhtml/Items/Column/DefaultColumn.php @@ -68,7 +68,7 @@ public function getItem() */ public function getOrderOptions() { - $result = [[]]; + $result = []; if ($options = $this->getItem()->getProductOptions()) { if (isset($options['options'])) { $result[] = $options['options']; @@ -80,7 +80,7 @@ public function getOrderOptions() $result[] = $options['attributes_info']; } } - return array_merge(...$result); + return array_merge([], ...$result); } /** diff --git a/app/code/Magento/Sales/Block/Order/Email/Items/DefaultItems.php b/app/code/Magento/Sales/Block/Order/Email/Items/DefaultItems.php index cbb79f188f231..57fc0441fe830 100644 --- a/app/code/Magento/Sales/Block/Order/Email/Items/DefaultItems.php +++ b/app/code/Magento/Sales/Block/Order/Email/Items/DefaultItems.php @@ -39,7 +39,7 @@ public function getOrder() */ public function getItemOptions() { - $result = [[]]; + $result = []; if ($options = $this->getItem()->getOrderItem()->getProductOptions()) { if (isset($options['options'])) { $result[] = $options['options']; @@ -52,7 +52,7 @@ public function getItemOptions() } } - return array_merge(...$result); + return array_merge([], ...$result); } /** diff --git a/app/code/Magento/Sales/Block/Order/Email/Items/Order/DefaultOrder.php b/app/code/Magento/Sales/Block/Order/Email/Items/Order/DefaultOrder.php index 0291a1275c350..cb9c7315244ac 100644 --- a/app/code/Magento/Sales/Block/Order/Email/Items/Order/DefaultOrder.php +++ b/app/code/Magento/Sales/Block/Order/Email/Items/Order/DefaultOrder.php @@ -34,7 +34,7 @@ public function getOrder() */ public function getItemOptions() { - $result = [[]]; + $result = []; if ($options = $this->getItem()->getProductOptions()) { if (isset($options['options'])) { $result[] = $options['options']; @@ -47,7 +47,7 @@ public function getItemOptions() } } - return array_merge(...$result); + return array_merge([], ...$result); } /** diff --git a/app/code/Magento/Sales/Block/Order/Item/Renderer/DefaultRenderer.php b/app/code/Magento/Sales/Block/Order/Item/Renderer/DefaultRenderer.php index bca6d49760d9a..010878559c2f0 100644 --- a/app/code/Magento/Sales/Block/Order/Item/Renderer/DefaultRenderer.php +++ b/app/code/Magento/Sales/Block/Order/Item/Renderer/DefaultRenderer.php @@ -105,7 +105,7 @@ public function getOrderItem() */ public function getItemOptions() { - $result = [[]]; + $result = []; $options = $this->getOrderItem()->getProductOptions(); if ($options) { if (isset($options['options'])) { @@ -118,7 +118,7 @@ public function getItemOptions() $result[] = $options['attributes_info']; } } - return array_merge(...$result); + return array_merge([], ...$result); } /** diff --git a/app/code/Magento/Sales/Model/Order/Pdf/Items/AbstractItems.php b/app/code/Magento/Sales/Model/Order/Pdf/Items/AbstractItems.php index 29e011217ef20..a7315aeb9e3be 100644 --- a/app/code/Magento/Sales/Model/Order/Pdf/Items/AbstractItems.php +++ b/app/code/Magento/Sales/Model/Order/Pdf/Items/AbstractItems.php @@ -326,7 +326,7 @@ public function getItemPricesForDisplay() */ public function getItemOptions() { - $result = [[]]; + $result = []; $options = $this->getItem()->getOrderItem()->getProductOptions(); if ($options) { if (isset($options['options'])) { @@ -339,7 +339,7 @@ public function getItemOptions() $result[] = $options['attributes_info']; } } - return array_merge(...$result); + return array_merge([], ...$result); } /** diff --git a/app/code/Magento/Sales/Model/ResourceModel/Provider/NotSyncedDataProvider.php b/app/code/Magento/Sales/Model/ResourceModel/Provider/NotSyncedDataProvider.php index 645e411b80b67..10b3ca1bde996 100644 --- a/app/code/Magento/Sales/Model/ResourceModel/Provider/NotSyncedDataProvider.php +++ b/app/code/Magento/Sales/Model/ResourceModel/Provider/NotSyncedDataProvider.php @@ -37,11 +37,11 @@ public function __construct(TMapFactory $tmapFactory, array $providers = []) */ public function getIds($mainTableName, $gridTableName) { - $result = [[]]; + $result = []; foreach ($this->providers as $provider) { $result[] = $provider->getIds($mainTableName, $gridTableName); } - return array_unique(array_merge(...$result)); + return array_unique(array_merge([], ...$result)); } } diff --git a/app/code/Magento/Search/Model/Autocomplete.php b/app/code/Magento/Search/Model/Autocomplete.php index 45957e8795744..57364e4c36bde 100644 --- a/app/code/Magento/Search/Model/Autocomplete.php +++ b/app/code/Magento/Search/Model/Autocomplete.php @@ -30,11 +30,11 @@ public function __construct( */ public function getItems() { - $data = [[]]; + $data = []; foreach ($this->dataProviders as $dataProvider) { $data[] = $dataProvider->getItems(); } - return array_merge(...$data); + return array_merge([], ...$data); } } diff --git a/app/code/Magento/Search/Model/SynonymGroupRepository.php b/app/code/Magento/Search/Model/SynonymGroupRepository.php index dbc2b66b1f047..c670235d67adb 100644 --- a/app/code/Magento/Search/Model/SynonymGroupRepository.php +++ b/app/code/Magento/Search/Model/SynonymGroupRepository.php @@ -150,7 +150,7 @@ private function create(SynonymGroupInterface $synonymGroup, $errorOnMergeConfli */ private function merge(SynonymGroupInterface $synonymGroupToMerge, array $matchingGroupIds) { - $mergedSynonyms = [[]]; + $mergedSynonyms = []; foreach ($matchingGroupIds as $groupId) { /** @var SynonymGroup $synonymGroupModel */ $synonymGroupModel = $this->synonymGroupFactory->create(); @@ -160,7 +160,7 @@ private function merge(SynonymGroupInterface $synonymGroupToMerge, array $matchi } $mergedSynonyms[] = explode(',', $synonymGroupToMerge->getSynonymGroup()); - return array_unique(array_merge(...$mergedSynonyms)); + return array_unique(array_merge([], ...$mergedSynonyms)); } /** diff --git a/app/code/Magento/Tax/Model/ResourceModel/Calculation/Rate/Collection.php b/app/code/Magento/Tax/Model/ResourceModel/Calculation/Rate/Collection.php index 7863b70f6626a..d34e863d56c54 100644 --- a/app/code/Magento/Tax/Model/ResourceModel/Calculation/Rate/Collection.php +++ b/app/code/Magento/Tax/Model/ResourceModel/Calculation/Rate/Collection.php @@ -206,7 +206,7 @@ public function getOptionRates() { $size = self::TAX_RULES_CHUNK_SIZE; $page = 1; - $rates = [[]]; + $rates = []; do { $offset = $size * ($page - 1); $this->getSelect()->reset(); @@ -222,6 +222,6 @@ public function getOptionRates() $page++; } while ($this->getSize() > $offset); - return array_merge(...$rates); + return array_merge([], ...$rates); } } diff --git a/app/code/Magento/Theme/Model/PageLayout/Config/Builder.php b/app/code/Magento/Theme/Model/PageLayout/Config/Builder.php index 13b8aa23073ce..fb47d415e88d5 100644 --- a/app/code/Magento/Theme/Model/PageLayout/Config/Builder.php +++ b/app/code/Magento/Theme/Model/PageLayout/Config/Builder.php @@ -68,7 +68,7 @@ protected function getConfigFiles() foreach ($this->themeCollection->loadRegisteredThemes() as $theme) { $configFiles[] = $this->fileCollector->getFilesContent($theme, 'layouts.xml'); } - $this->configFiles = array_merge(...$configFiles); + $this->configFiles = array_merge([], ...$configFiles); } return $this->configFiles; diff --git a/app/code/Magento/Ups/Model/Carrier.php b/app/code/Magento/Ups/Model/Carrier.php index b6e539bdadcb9..b14550451ad7e 100644 --- a/app/code/Magento/Ups/Model/Carrier.php +++ b/app/code/Magento/Ups/Model/Carrier.php @@ -1362,10 +1362,11 @@ public function getAllowedMethods() protected function _formShipmentRequest(DataObject $request) { $packages = $request->getPackages(); + $shipmentItems = []; foreach ($packages as $package) { $shipmentItems[] = $package['items']; } - $shipmentItems = array_merge(...$shipmentItems); + $shipmentItems = array_merge([], ...$shipmentItems); $xmlRequest = $this->_xmlElFactory->create( ['data' => '<?xml version = "1.0" ?><ShipmentConfirmRequest xml:lang="en-US"/>'] diff --git a/app/code/Magento/Webapi/Model/Rest/Swagger/Generator.php b/app/code/Magento/Webapi/Model/Rest/Swagger/Generator.php index f38c0f0978536..40fe97c61f051 100644 --- a/app/code/Magento/Webapi/Model/Rest/Swagger/Generator.php +++ b/app/code/Magento/Webapi/Model/Rest/Swagger/Generator.php @@ -758,7 +758,7 @@ private function handleComplex($name, $type, $prefix, $isArray) ); } - return empty($queryNames) ? [] : array_merge(...$queryNames); + return array_merge([], ...$queryNames); } /** diff --git a/app/code/Magento/Weee/Model/Total/Quote/Weee.php b/app/code/Magento/Weee/Model/Total/Quote/Weee.php index 449c6cd688668..e7ae84c15a51f 100644 --- a/app/code/Magento/Weee/Model/Total/Quote/Weee.php +++ b/app/code/Magento/Weee/Model/Total/Quote/Weee.php @@ -306,12 +306,12 @@ protected function getNextIncrement() */ protected function recalculateParent(AbstractItem $item) { - $associatedTaxables = [[]]; + $associatedTaxables = []; foreach ($item->getChildren() as $child) { $associatedTaxables[] = $child->getAssociatedTaxables(); } $item->setAssociatedTaxables( - array_unique(array_merge(...$associatedTaxables)) + array_unique(array_merge([], ...$associatedTaxables)) ); } diff --git a/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/ProductTest.php b/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/ProductTest.php index a9699ea4a8050..bb45f2574893c 100644 --- a/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/ProductTest.php +++ b/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/ProductTest.php @@ -729,7 +729,7 @@ function ($input) { ) ); // phpcs:ignore Magento2.Performance.ForeachArrayMerge - $option = array_merge(...$option); + $option = array_merge([], ...$option); if (!empty($option['type']) && !empty($option['name'])) { $lastOptionKey = $option['type'] . '|' . $option['name']; @@ -2252,7 +2252,7 @@ function (ProductInterface $item) { $collection = \Magento\TestFramework\Helper\Bootstrap::getObjectManager() ->create(\Magento\Catalog\Model\ResourceModel\Category\Collection::class); $collection - ->addAttributeToFilter('entity_id', ['in' => \array_unique(\array_merge(...$categoryIds))]) + ->addAttributeToFilter('entity_id', ['in' => \array_unique(\array_merge([], ...$categoryIds))]) ->load() ->delete(); diff --git a/lib/internal/Magento/Framework/App/Utility/Files.php b/lib/internal/Magento/Framework/App/Utility/Files.php index 4298577f3147b..96bd6d061a13f 100644 --- a/lib/internal/Magento/Framework/App/Utility/Files.php +++ b/lib/internal/Magento/Framework/App/Utility/Files.php @@ -371,11 +371,11 @@ public function getMainConfigFiles($asDataSet = true) } $globPaths = [BP . '/app/etc/config.xml', BP . '/app/etc/*/config.xml']; $configXmlPaths = array_merge($globPaths, $configXmlPaths); - $files = [[]]; + $files = []; foreach ($configXmlPaths as $xmlPath) { $files[] = glob($xmlPath, GLOB_NOSORT); } - self::$_cache[$cacheKey] = array_merge(...$files); + self::$_cache[$cacheKey] = array_merge([], ...$files); } if ($asDataSet) { return self::composeDataSets(self::$_cache[$cacheKey]); diff --git a/lib/internal/Magento/Framework/Model/EntitySnapshot/AttributeProvider.php b/lib/internal/Magento/Framework/Model/EntitySnapshot/AttributeProvider.php index 6b7fcd131ba8b..f702b71378bb9 100644 --- a/lib/internal/Magento/Framework/Model/EntitySnapshot/AttributeProvider.php +++ b/lib/internal/Magento/Framework/Model/EntitySnapshot/AttributeProvider.php @@ -74,7 +74,7 @@ public function getAttributes($entityType) $attributes[] = $provider->getAttributes($entityType); } - $this->registry[$entityType] = \array_merge(...$attributes); + $this->registry[$entityType] = \array_merge([], ...$attributes); } return $this->registry[$entityType]; diff --git a/lib/internal/Magento/Framework/Module/ModuleList/Loader.php b/lib/internal/Magento/Framework/Module/ModuleList/Loader.php index 7e484407d7a54..f30d38b6e112e 100644 --- a/lib/internal/Magento/Framework/Module/ModuleList/Loader.php +++ b/lib/internal/Magento/Framework/Module/ModuleList/Loader.php @@ -210,6 +210,6 @@ private function expandSequence($list, $name, $accumulated = []) $allResults[] = $this->expandSequence($list, $relatedName, $accumulated); } $allResults[] = $result; - return array_unique(array_merge(...$allResults)); + return array_unique(array_merge([], ...$allResults)); } } diff --git a/lib/internal/Magento/Framework/ObjectManager/Factory/AbstractFactory.php b/lib/internal/Magento/Framework/ObjectManager/Factory/AbstractFactory.php index b662a2a34c813..b57f4665aff21 100644 --- a/lib/internal/Magento/Framework/ObjectManager/Factory/AbstractFactory.php +++ b/lib/internal/Magento/Framework/ObjectManager/Factory/AbstractFactory.php @@ -239,7 +239,7 @@ protected function resolveArgumentsInRuntime($requestedType, array $parameters, $resolvedArguments[] = $this->getResolvedArgument((string)$requestedType, $parameter, $arguments); } - return empty($resolvedArguments) ? [] : array_merge(...$resolvedArguments); + return array_merge([], ...$resolvedArguments); } /** diff --git a/setup/src/Magento/Setup/Console/Style/MagentoStyle.php b/setup/src/Magento/Setup/Console/Style/MagentoStyle.php index 60cfcbb67c217..3fa9e9d50d24a 100644 --- a/setup/src/Magento/Setup/Console/Style/MagentoStyle.php +++ b/setup/src/Magento/Setup/Console/Style/MagentoStyle.php @@ -605,7 +605,7 @@ private function getBlockLines( int $prefixLength, int $indentLength ) { - $lines = [[]]; + $lines = []; foreach ($messages as $key => $message) { $message = OutputFormatter::escape($message); $wordwrap = wordwrap($message, $this->lineLength - $prefixLength - $indentLength, PHP_EOL, true); @@ -614,7 +614,7 @@ private function getBlockLines( $lines[][] = ''; } } - $lines = array_merge(...$lines); + $lines = array_merge([], ...$lines); return $lines; } diff --git a/setup/src/Magento/Setup/Module/Di/Code/Scanner/PhpScanner.php b/setup/src/Magento/Setup/Module/Di/Code/Scanner/PhpScanner.php index acb55e29afddd..7355ac30ac59d 100644 --- a/setup/src/Magento/Setup/Module/Di/Code/Scanner/PhpScanner.php +++ b/setup/src/Magento/Setup/Module/Di/Code/Scanner/PhpScanner.php @@ -175,7 +175,7 @@ protected function _fetchMissingExtensionAttributesClasses($reflectionClass, $fi */ public function collectEntities(array $files) { - $output = [[]]; + $output = []; foreach ($files as $file) { $classes = $this->getDeclaredClasses($file); foreach ($classes as $className) { @@ -184,7 +184,7 @@ public function collectEntities(array $files) $output[] = $this->_fetchMissingExtensionAttributesClasses($reflectionClass, $file); } } - return array_unique(array_merge(...$output)); + return array_unique(array_merge([], ...$output)); } /** From f6c4cbd23fca8a7cd7b73c4c609f5179d644d527 Mon Sep 17 00:00:00 2001 From: Ihor Sviziev <svizev.igor@gmail.com> Date: Wed, 23 Sep 2020 09:12:11 +0300 Subject: [PATCH 045/133] Use one format in all places for array_merge Fix failing static tests --- app/code/Magento/Catalog/Model/Product.php | 4 +-- .../Model/Product/Price/TierPriceStorage.php | 3 -- .../Observer/AfterImportDataObserver.php | 2 -- app/code/Magento/Deploy/Package/Package.php | 2 +- .../Eav/Model/ResourceModel/Helper.php | 1 + app/code/Magento/Ups/Model/Carrier.php | 32 +++++++------------ .../Webapi/Model/Rest/Swagger/Generator.php | 2 -- .../Setup/Console/Style/MagentoStyle.php | 1 + 8 files changed, 17 insertions(+), 30 deletions(-) diff --git a/app/code/Magento/Catalog/Model/Product.php b/app/code/Magento/Catalog/Model/Product.php index 5e6f3ca5fe970..f5363cc591c96 100644 --- a/app/code/Magento/Catalog/Model/Product.php +++ b/app/code/Magento/Catalog/Model/Product.php @@ -1030,7 +1030,7 @@ public function priceReindexCallback() */ public function eavReindexCallback() { - if ($this->isObjectNew() || $this->isDataChanged($this)) { + if ($this->isObjectNew() || $this->isDataChanged()) { $this->_productEavIndexerProcessor->reindexRow($this->getEntityId()); } } @@ -1176,7 +1176,7 @@ public function getTierPrice($qty = null) /** * Get formatted by currency product price * - * @return array|double + * @return array|double * @since 102.0.6 */ public function getFormattedPrice() diff --git a/app/code/Magento/Catalog/Model/Product/Price/TierPriceStorage.php b/app/code/Magento/Catalog/Model/Product/Price/TierPriceStorage.php index 7e410f0e5feb3..7d458401c950e 100644 --- a/app/code/Magento/Catalog/Model/Product/Price/TierPriceStorage.php +++ b/app/code/Magento/Catalog/Model/Product/Price/TierPriceStorage.php @@ -12,9 +12,6 @@ use Magento\Catalog\Model\Product\Price\Validation\TierPriceValidator; use Magento\Catalog\Model\ProductIdLocatorInterface; -/** - * Tier price storage. - */ class TierPriceStorage implements TierPriceStorageInterface { /** diff --git a/app/code/Magento/CatalogUrlRewrite/Observer/AfterImportDataObserver.php b/app/code/Magento/CatalogUrlRewrite/Observer/AfterImportDataObserver.php index e8e6f55f80051..b467771408ec0 100644 --- a/app/code/Magento/CatalogUrlRewrite/Observer/AfterImportDataObserver.php +++ b/app/code/Magento/CatalogUrlRewrite/Observer/AfterImportDataObserver.php @@ -37,8 +37,6 @@ use RuntimeException; /** - * Class AfterImportDataObserver - * * @SuppressWarnings(PHPMD.TooManyFields) * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ diff --git a/app/code/Magento/Deploy/Package/Package.php b/app/code/Magento/Deploy/Package/Package.php index 3c4af7039b4a8..5780b46365680 100644 --- a/app/code/Magento/Deploy/Package/Package.php +++ b/app/code/Magento/Deploy/Package/Package.php @@ -535,7 +535,7 @@ private function collectParentPaths( $area, $theme, $locale, - array & $result = [], + array &$result = [], ThemeInterface $themeModel = null ) { if (($package->getArea() != $area) || ($package->getTheme() != $theme) || ($package->getLocale() != $locale)) { diff --git a/app/code/Magento/Eav/Model/ResourceModel/Helper.php b/app/code/Magento/Eav/Model/ResourceModel/Helper.php index 569da0ac2bb4f..c81db40c608a8 100644 --- a/app/code/Magento/Eav/Model/ResourceModel/Helper.php +++ b/app/code/Magento/Eav/Model/ResourceModel/Helper.php @@ -19,6 +19,7 @@ class Helper extends \Magento\Framework\DB\Helper * @param \Magento\Framework\App\ResourceConnection $resource * @param string $modulePrefix * @codeCoverageIgnore + * phpcs:disable Generic.CodeAnalysis.UselessOverridingMethod */ public function __construct(\Magento\Framework\App\ResourceConnection $resource, $modulePrefix = 'Magento_Eav') { diff --git a/app/code/Magento/Ups/Model/Carrier.php b/app/code/Magento/Ups/Model/Carrier.php index b14550451ad7e..4d0e3efee35aa 100644 --- a/app/code/Magento/Ups/Model/Carrier.php +++ b/app/code/Magento/Ups/Model/Carrier.php @@ -1135,7 +1135,7 @@ protected function _getXmlTracking($trackings) </TrackRequest> XMLAuth; - $trackingResponses[] = $this->asyncHttpClient->request( + $trackingResponses[$tracking] = $this->asyncHttpClient->request( new Request( $url, Request::METHOD_POST, @@ -1144,13 +1144,9 @@ protected function _getXmlTracking($trackings) ) ); } - foreach ($trackingResponses as $response) { + foreach ($trackingResponses as $tracking => $response) { $httpResponse = $response->get(); - if ($httpResponse->getStatusCode() >= 400) { - $xmlResponse = ''; - } else { - $xmlResponse = $httpResponse->getBody(); - } + $xmlResponse = $httpResponse->getStatusCode() >= 400 ? '' : $httpResponse->getBody(); $this->_parseXmlTrackingResponse($tracking, $xmlResponse); } @@ -1529,24 +1525,18 @@ protected function _formShipmentRequest(DataObject $request) } if ($deliveryConfirmation && $deliveryConfirmationLevel === self::DELIVERY_CONFIRMATION_PACKAGE) { - $serviceOptionsNode = $packagePart[$packageId]->addChild('PackageServiceOptions'); - $serviceOptionsNode->addChild( - 'DeliveryConfirmation' - )->addChild( - 'DCISType', - $deliveryConfirmation - ); + $serviceOptionsNode = $packagePart[$packageId]->addChild('PackageServiceOptions'); + $serviceOptionsNode + ->addChild('DeliveryConfirmation') + ->addChild('DCISType', $deliveryConfirmation); } } if (isset($deliveryConfirmation) && $deliveryConfirmationLevel === self::DELIVERY_CONFIRMATION_SHIPMENT) { $serviceOptionsNode = $shipmentPart->addChild('ShipmentServiceOptions'); - $serviceOptionsNode->addChild( - 'DeliveryConfirmation' - )->addChild( - 'DCISType', - $deliveryConfirmation - ); + $serviceOptionsNode + ->addChild('DeliveryConfirmation') + ->addChild('DCISType', $deliveryConfirmation); } $shipmentPart->addChild('PaymentInformation') @@ -1628,6 +1618,7 @@ protected function _sendShipmentAcceptRequest(Element $shipmentConfirmResponse) try { $response = $this->_xmlElFactory->create(['data' => $xmlResponse]); } catch (Throwable $e) { + $response = $this->_xmlElFactory->create(['data' => '']); $debugData['result'] = ['error' => $e->getMessage(), 'code' => $e->getCode()]; } @@ -1801,6 +1792,7 @@ protected function _doShipmentRequest(DataObject $request) $this->setXMLAccessRequest(); $xmlRequest = $this->_xmlAccessRequest . $rawXmlRequest; $xmlResponse = $this->_getCachedQuotes($xmlRequest); + $debugData = []; if ($xmlResponse === null) { $debugData['request'] = $this->filterDebugData($this->_xmlAccessRequest) . $rawXmlRequest; diff --git a/app/code/Magento/Webapi/Model/Rest/Swagger/Generator.php b/app/code/Magento/Webapi/Model/Rest/Swagger/Generator.php index 40fe97c61f051..5ead1beb722dd 100644 --- a/app/code/Magento/Webapi/Model/Rest/Swagger/Generator.php +++ b/app/code/Magento/Webapi/Model/Rest/Swagger/Generator.php @@ -33,10 +33,8 @@ class Generator extends AbstractSchemaGenerator */ const ERROR_SCHEMA = '#/definitions/error-response'; - /** Unauthorized description */ const UNAUTHORIZED_DESCRIPTION = '401 Unauthorized'; - /** Array signifier */ const ARRAY_SIGNIFIER = '[0]'; /** diff --git a/setup/src/Magento/Setup/Console/Style/MagentoStyle.php b/setup/src/Magento/Setup/Console/Style/MagentoStyle.php index 3fa9e9d50d24a..43f85d092b0a7 100644 --- a/setup/src/Magento/Setup/Console/Style/MagentoStyle.php +++ b/setup/src/Magento/Setup/Console/Style/MagentoStyle.php @@ -565,6 +565,7 @@ private function createBlock( ) { $indentLength = 0; $prefixLength = Helper::strlenWithoutDecoration($this->getFormatter(), $prefix); + $lineIndentation = ''; if (null !== $type) { $type = sprintf('[%s] ', $type); $indentLength = strlen($type); From 5ce73ab1f2b04da9dacd274f6d71ea6bbcc65c1f Mon Sep 17 00:00:00 2001 From: Bas van Poppel <vanpoppel@redkiwi.nl> Date: Wed, 23 Sep 2020 14:17:03 +0200 Subject: [PATCH 046/133] Set correct discount package value for tablerate --- .../Magento/OfflineShipping/Model/Carrier/Tablerate.php | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/app/code/Magento/OfflineShipping/Model/Carrier/Tablerate.php b/app/code/Magento/OfflineShipping/Model/Carrier/Tablerate.php index bbc199c91263a..112accbae8070 100644 --- a/app/code/Magento/OfflineShipping/Model/Carrier/Tablerate.php +++ b/app/code/Magento/OfflineShipping/Model/Carrier/Tablerate.php @@ -140,10 +140,9 @@ public function collectRates(RateRequest $request) $freePackageValue += $item->getBaseRowTotal(); } } - $oldValue = $request->getPackageValue(); - $newPackageValue = $oldValue - $freePackageValue; - $request->setPackageValue($newPackageValue); - $request->setPackageValueWithDiscount($newPackageValue); + + $request->setPackageValue($request->getPackageValue() - $freePackageValue); + $request->setPackageValueWithDiscount($request->getPackageValueWithDiscount() - $freePackageValue); } if (!$request->getConditionName()) { From 6c11aeba2dfb8f96917ee4a834545dbb582dbd03 Mon Sep 17 00:00:00 2001 From: Bas van Poppel <vanpoppel@redkiwi.nl> Date: Mon, 28 Sep 2020 14:49:22 +0200 Subject: [PATCH 047/133] no message --- .../ResourceModel/Advanced/Collection.php | 56 +++++++--- .../CatalogSearch/etc/search_request.xml | 5 + .../ProductCollectionPrepareStrategy.php | 18 +++- .../Console/Command/IndexerReindexCommand.php | 5 +- .../Model/CatalogSearch/AdvancedTest.php | 101 ++++++++++++++++++ .../Command/IndexerReindexCommandTest.php | 25 +++-- .../Indexer/_files/wrong_config_data.php | 16 +++ .../_files/wrong_config_data_rollback.php | 17 +++ 8 files changed, 218 insertions(+), 25 deletions(-) create mode 100644 dev/tests/integration/testsuite/Magento/Elasticsearch/Model/CatalogSearch/AdvancedTest.php create mode 100644 dev/tests/integration/testsuite/Magento/Indexer/_files/wrong_config_data.php create mode 100644 dev/tests/integration/testsuite/Magento/Indexer/_files/wrong_config_data_rollback.php diff --git a/app/code/Magento/CatalogSearch/Model/ResourceModel/Advanced/Collection.php b/app/code/Magento/CatalogSearch/Model/ResourceModel/Advanced/Collection.php index 47160bff1d571..6005455a6ef83 100644 --- a/app/code/Magento/CatalogSearch/Model/ResourceModel/Advanced/Collection.php +++ b/app/code/Magento/CatalogSearch/Model/ResourceModel/Advanced/Collection.php @@ -6,27 +6,30 @@ namespace Magento\CatalogSearch\Model\ResourceModel\Advanced; +use Magento\Catalog\Model\Category; use Magento\Catalog\Model\Product; +use Magento\Catalog\Model\ResourceModel\Product\Collection\ProductLimitationFactory; +use Magento\CatalogSearch\Model\ResourceModel\Advanced; use Magento\CatalogSearch\Model\ResourceModel\Fulltext\Collection\DefaultFilterStrategyApplyChecker; use Magento\CatalogSearch\Model\ResourceModel\Fulltext\Collection\DefaultFilterStrategyApplyCheckerInterface; +use Magento\CatalogSearch\Model\ResourceModel\Fulltext\Collection\SearchCriteriaResolverFactory; use Magento\CatalogSearch\Model\ResourceModel\Fulltext\Collection\SearchCriteriaResolverInterface; +use Magento\CatalogSearch\Model\ResourceModel\Fulltext\Collection\SearchResultApplierFactory; use Magento\CatalogSearch\Model\ResourceModel\Fulltext\Collection\SearchResultApplierInterface; -use Magento\Framework\Search\EngineResolverInterface; -use Magento\CatalogSearch\Model\ResourceModel\Fulltext\Collection\TotalRecordsResolverInterface; use Magento\CatalogSearch\Model\ResourceModel\Fulltext\Collection\TotalRecordsResolverFactory; +use Magento\CatalogSearch\Model\ResourceModel\Fulltext\Collection\TotalRecordsResolverInterface; +use Magento\Eav\Model\Entity\Attribute\AbstractAttribute; use Magento\Framework\Api\FilterBuilder; -use Magento\Framework\DB\Select; use Magento\Framework\Api\Search\SearchCriteriaBuilder; use Magento\Framework\Api\Search\SearchResultFactory; +use Magento\Framework\Api\Search\SearchResultInterface; +use Magento\Framework\App\ObjectManager; +use Magento\Framework\DB\Select; use Magento\Framework\EntityManager\MetadataPool; use Magento\Framework\Exception\LocalizedException; +use Magento\Framework\Search\EngineResolverInterface; use Magento\Framework\Search\Request\EmptyRequestDataException; use Magento\Framework\Search\Request\NonExistingRequestNameException; -use Magento\Catalog\Model\ResourceModel\Product\Collection\ProductLimitationFactory; -use Magento\CatalogSearch\Model\ResourceModel\Fulltext\Collection\SearchCriteriaResolverFactory; -use Magento\CatalogSearch\Model\ResourceModel\Fulltext\Collection\SearchResultApplierFactory; -use Magento\Framework\App\ObjectManager; -use Magento\Framework\Api\Search\SearchResultInterface; /** * Advanced search collection @@ -106,6 +109,11 @@ class Collection extends \Magento\Catalog\Model\ResourceModel\Product\Collection */ private $defaultFilterStrategyApplyChecker; + /** + * @var Advanced + */ + private $advancedSearchResource; + /** * Collection constructor * @@ -141,6 +149,7 @@ class Collection extends \Magento\Catalog\Model\ResourceModel\Product\Collection * @param TotalRecordsResolverFactory|null $totalRecordsResolverFactory * @param EngineResolverInterface|null $engineResolver * @param DefaultFilterStrategyApplyCheckerInterface|null $defaultFilterStrategyApplyChecker + * @param Advanced|null $advancedSearchResource * @SuppressWarnings(PHPMD.ExcessiveParameterList) * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ @@ -176,7 +185,8 @@ public function __construct( SearchResultApplierFactory $searchResultApplierFactory = null, TotalRecordsResolverFactory $totalRecordsResolverFactory = null, EngineResolverInterface $engineResolver = null, - DefaultFilterStrategyApplyCheckerInterface $defaultFilterStrategyApplyChecker = null + DefaultFilterStrategyApplyCheckerInterface $defaultFilterStrategyApplyChecker = null, + Advanced $advancedSearchResource = null ) { $this->searchRequestName = $searchRequestName; if ($searchResultFactory === null) { @@ -193,6 +203,8 @@ public function __construct( ->get(EngineResolverInterface::class); $this->defaultFilterStrategyApplyChecker = $defaultFilterStrategyApplyChecker ?: ObjectManager::getInstance() ->get(DefaultFilterStrategyApplyChecker::class); + $this->advancedSearchResource = $advancedSearchResource ?: ObjectManager::getInstance() + ->get(Advanced::class); parent::__construct( $entityFactory, $logger, @@ -258,6 +270,7 @@ public function setOrder($attribute, $dir = Select::SQL_DESC) */ public function addCategoryFilter(\Magento\Catalog\Model\Category $category) { + $this->setAttributeFilterData(Category::ENTITY, 'category_ids', $category->getId()); /** * This changes need in backward compatible reasons for support dynamic improved algorithm * for price aggregation process. @@ -265,7 +278,6 @@ public function addCategoryFilter(\Magento\Catalog\Model\Category $category) if ($this->defaultFilterStrategyApplyChecker->isApplicable()) { parent::addCategoryFilter($category); } else { - $this->addFieldToFilter('category_ids', $category->getId()); $this->_productLimitationPrice(); } @@ -278,14 +290,13 @@ public function addCategoryFilter(\Magento\Catalog\Model\Category $category) */ public function setVisibility($visibility) { + $this->setAttributeFilterData(Product::ENTITY, 'visibility', $visibility); /** * This changes need in backward compatible reasons for support dynamic improved algorithm * for price aggregation process. */ if ($this->defaultFilterStrategyApplyChecker->isApplicable()) { parent::setVisibility($visibility); - } else { - $this->addFieldToFilter('visibility', $visibility); } return $this; @@ -306,6 +317,25 @@ private function setSearchOrder($field, $direction) $this->searchOrders[$field] = $direction; } + /** + * Prepare attribute data to filter. + * + * @param string $entityType + * @param string $attributeCode + * @param mixed $condition + * @return $this + */ + private function setAttributeFilterData(string $entityType, string $attributeCode, $condition): self + { + /** @var AbstractAttribute $attribute */ + $attribute = $this->_eavConfig->getAttribute($entityType, $attributeCode); + $table = $attribute->getBackend()->getTable(); + $condition = $this->advancedSearchResource->prepareCondition($attribute, $condition); + $this->addFieldsToFilter([$table => [$attributeCode => $condition]]); + + return $this; + } + /** * @inheritdoc */ @@ -377,7 +407,7 @@ public function _loadEntities($printQuery = false, $logQuery = false) $query = $this->getSelect(); $rows = $this->_fetchAll($query); } catch (\Exception $e) { - $this->printLogQuery(false, true, $query); + $this->printLogQuery(false, true, $query ?? null); throw $e; } diff --git a/app/code/Magento/CatalogSearch/etc/search_request.xml b/app/code/Magento/CatalogSearch/etc/search_request.xml index 2111c469986ec..ecda6112ba03e 100644 --- a/app/code/Magento/CatalogSearch/etc/search_request.xml +++ b/app/code/Magento/CatalogSearch/etc/search_request.xml @@ -66,6 +66,7 @@ <queryReference clause="should" ref="sku_query"/> <queryReference clause="should" ref="price_query"/> <queryReference clause="should" ref="category_query"/> + <queryReference clause="must" ref="visibility_query"/> </query> <query name="sku_query" xsi:type="filteredQuery"> <filterReference clause="must" ref="sku_query_filter"/> @@ -76,11 +77,15 @@ <query name="category_query" xsi:type="filteredQuery"> <filterReference clause="must" ref="category_filter"/> </query> + <query name="visibility_query" xsi:type="filteredQuery"> + <filterReference clause="must" ref="visibility_filter"/> + </query> </queries> <filters> <filter xsi:type="wildcardFilter" name="sku_query_filter" field="sku" value="$sku$"/> <filter xsi:type="rangeFilter" name="price_query_filter" field="price" from="$price.from$" to="$price.to$"/> <filter xsi:type="termFilter" name="category_filter" field="category_ids" value="$category_ids$"/> + <filter xsi:type="termFilter" name="visibility_filter" field="visibility" value="$visibility$"/> </filters> <from>0</from> <size>10000</size> diff --git a/app/code/Magento/Elasticsearch/Model/Advanced/ProductCollectionPrepareStrategy.php b/app/code/Magento/Elasticsearch/Model/Advanced/ProductCollectionPrepareStrategy.php index b3f8a56110f8d..d7054e2bb4b11 100644 --- a/app/code/Magento/Elasticsearch/Model/Advanced/ProductCollectionPrepareStrategy.php +++ b/app/code/Magento/Elasticsearch/Model/Advanced/ProductCollectionPrepareStrategy.php @@ -5,9 +5,11 @@ */ namespace Magento\Elasticsearch\Model\Advanced; -use Magento\Catalog\Model\ResourceModel\Product\Collection; use Magento\Catalog\Model\Config; +use Magento\Catalog\Model\Product\Visibility; +use Magento\Catalog\Model\ResourceModel\Product\Collection; use Magento\CatalogSearch\Model\Advanced\ProductCollectionPrepareStrategyInterface; +use Magento\Framework\App\ObjectManager; /** * Strategy interface for preparing product collection. @@ -19,13 +21,22 @@ class ProductCollectionPrepareStrategy implements ProductCollectionPrepareStrate */ private $catalogConfig; + /** + * @var Visibility + */ + private $catalogProductVisibility; + /** * @param Config $catalogConfig + * @param Visibility|null $catalogProductVisibility */ public function __construct( - Config $catalogConfig + Config $catalogConfig, + Visibility $catalogProductVisibility = null ) { $this->catalogConfig = $catalogConfig; + $this->catalogProductVisibility = $catalogProductVisibility + ?? ObjectManager::getInstance()->get(Visibility::class); } /** @@ -36,6 +47,7 @@ public function prepare(Collection $collection) $collection ->addAttributeToSelect($this->catalogConfig->getProductAttributes()) ->addMinimalPrice() - ->addTaxPercents(); + ->addTaxPercents() + ->setVisibility($this->catalogProductVisibility->getVisibleInSearchIds()); } } diff --git a/app/code/Magento/Indexer/Console/Command/IndexerReindexCommand.php b/app/code/Magento/Indexer/Console/Command/IndexerReindexCommand.php index 775f585519947..e7517ba0c8818 100644 --- a/app/code/Magento/Indexer/Console/Command/IndexerReindexCommand.php +++ b/app/code/Magento/Indexer/Console/Command/IndexerReindexCommand.php @@ -74,7 +74,7 @@ protected function configure() */ protected function execute(InputInterface $input, OutputInterface $output) { - $returnValue = Cli::RETURN_FAILURE; + $returnValue = Cli::RETURN_SUCCESS; foreach ($this->getIndexers($input) as $indexer) { try { $this->validateIndexerStatus($indexer); @@ -97,14 +97,15 @@ protected function execute(InputInterface $input, OutputInterface $output) $output->writeln( __('has been rebuilt successfully in %time', ['time' => gmdate('H:i:s', $resultTime)]) ); - $returnValue = Cli::RETURN_SUCCESS; } catch (LocalizedException $e) { $output->writeln(__('exception: %message', ['message' => $e->getMessage()])); + $returnValue = Cli::RETURN_FAILURE; } catch (\Exception $e) { $output->writeln('process unknown error:'); $output->writeln($e->getMessage()); $output->writeln($e->getTraceAsString(), OutputInterface::VERBOSITY_DEBUG); + $returnValue = Cli::RETURN_FAILURE; } } diff --git a/dev/tests/integration/testsuite/Magento/Elasticsearch/Model/CatalogSearch/AdvancedTest.php b/dev/tests/integration/testsuite/Magento/Elasticsearch/Model/CatalogSearch/AdvancedTest.php new file mode 100644 index 0000000000000..815f480cf7b53 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Elasticsearch/Model/CatalogSearch/AdvancedTest.php @@ -0,0 +1,101 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Elasticsearch\Model\CatalogSearch; + +use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\CatalogSearch\Model\Advanced; +use Magento\Catalog\Model\Product\Visibility; +use Magento\Catalog\Api\Data\ProductInterface; +use Magento\Framework\ObjectManagerInterface; +use Magento\Framework\Registry; +use Magento\TestFramework\Helper\Bootstrap; +use PHPUnit\Framework\TestCase; + +/** + * Check catalog Advanced Search process with Elasticsearch enabled. + */ +class AdvancedTest extends TestCase +{ + /** + * @var ObjectManagerInterface + */ + private $objectManager; + + /** + * @var Registry + */ + private $registry; + + /** + * @var Visibility + */ + private $productVisibility; + + /** + * @var ProductRepositoryInterface + */ + private $productRepository; + + /** + * @inheritDoc + */ + protected function setUp(): void + { + parent::setUp(); + + $this->objectManager = Bootstrap::getObjectManager(); + $this->registry = $this->objectManager->get(Registry::class); + $this->productVisibility = $this->objectManager->get(Visibility::class); + $this->productRepository = $this->objectManager->get(ProductRepositoryInterface::class); + } + + /** + * Check that Advanced Search does NOT return products that do NOT have search visibility. + * + * @magentoDbIsolation disabled + * @magentoConfigFixture default/catalog/search/engine elasticsearch7 + * @magentoDataFixture Magento/ConfigurableProduct/_files/configurable_product_with_two_child_products.php + * @return void + */ + public function testAddFilters(): void + { + $this->assertResultsAfterRequest(1); + + /** @var ProductInterface $configurableProductOption */ + $configurableProductOption = $this->productRepository->get('Simple option 1'); + $configurableProductOption->setVisibility(Visibility::VISIBILITY_IN_SEARCH); + $this->productRepository->save($configurableProductOption); + + $this->registry->unregister('advanced_search_conditions'); + $this->assertResultsAfterRequest(2); + } + + /** + * Do Elasticsearch query and assert results. + * + * @param int $count + * @return void + */ + private function assertResultsAfterRequest(int $count): void + { + /** @var Advanced $advancedSearch */ + $advancedSearch = $this->objectManager->create(Advanced::class); + $advancedSearch->addFilters(['name' => 'Configurable']); + + /** @var ProductInterface[] $itemsResult */ + $itemsResult = $advancedSearch->getProductCollection() + ->addAttributeToSelect(ProductInterface::VISIBILITY) + ->getItems(); + + $this->assertCount($count, $itemsResult); + foreach ($itemsResult as $product) { + $this->assertStringContainsString('Configurable', $product->getName()); + $this->assertContains((int)$product->getVisibility(), $this->productVisibility->getVisibleInSearchIds()); + } + } +} diff --git a/dev/tests/integration/testsuite/Magento/Indexer/Console/Command/IndexerReindexCommandTest.php b/dev/tests/integration/testsuite/Magento/Indexer/Console/Command/IndexerReindexCommandTest.php index f4730b0b32c18..67de7913b4603 100644 --- a/dev/tests/integration/testsuite/Magento/Indexer/Console/Command/IndexerReindexCommandTest.php +++ b/dev/tests/integration/testsuite/Magento/Indexer/Console/Command/IndexerReindexCommandTest.php @@ -7,9 +7,11 @@ namespace Magento\Indexer\Console\Command; +use Magento\Framework\Console\Cli; use Magento\Framework\ObjectManagerInterface; use Magento\TestFramework\Helper\Bootstrap; use PHPUnit\Framework\MockObject\MockObject as Mock; +use PHPUnit\Framework\TestCase; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; @@ -19,7 +21,7 @@ * @magentoDbIsolation disabled * @magentoAppIsolation enabled */ -class IndexerReindexCommandTest extends \PHPUnit\Framework\TestCase +class IndexerReindexCommandTest extends TestCase { /** * @var ObjectManagerInterface @@ -56,14 +58,23 @@ protected function setUp(): void /** * @magentoDataFixture Magento/Store/_files/second_store_group_with_second_website.php + * @return void */ - public function testReindexAll() + public function testReindexAll(): void { $status = $this->command->run($this->inputMock, $this->outputMock); - $this->assertEquals( - \Magento\Framework\Console\Cli::RETURN_SUCCESS, - $status, - 'Index wasn\'t success' - ); + $this->assertEquals(Cli::RETURN_SUCCESS, $status, 'Index wasn\'t success'); + } + + /** + * Check that 'indexer:reindex' command return right code. + * + * @magentoDataFixture Magento/Indexer/_files/wrong_config_data.php + * @return void + */ + public function testReindexAllWhenSomethingIsWrong(): void + { + $status = $this->command->run($this->inputMock, $this->outputMock); + $this->assertEquals(Cli::RETURN_FAILURE, $status, 'Index didn\'t return failure code'); } } diff --git a/dev/tests/integration/testsuite/Magento/Indexer/_files/wrong_config_data.php b/dev/tests/integration/testsuite/Magento/Indexer/_files/wrong_config_data.php new file mode 100644 index 0000000000000..c6ec6235b61e6 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Indexer/_files/wrong_config_data.php @@ -0,0 +1,16 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\Config\Model\Config\Factory; +use Magento\TestFramework\Helper\Bootstrap; + +/** @var Factory $configFactory */ +$configFactory = Bootstrap::getObjectManager()->get(Factory::class); +$config = $configFactory->create(); +$config->setScope('stores'); +$config->setDataByPath('catalog/search/elasticsearch7_server_port', 2309); +$config->save(); diff --git a/dev/tests/integration/testsuite/Magento/Indexer/_files/wrong_config_data_rollback.php b/dev/tests/integration/testsuite/Magento/Indexer/_files/wrong_config_data_rollback.php new file mode 100644 index 0000000000000..7ca0b986fc206 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Indexer/_files/wrong_config_data_rollback.php @@ -0,0 +1,17 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\Framework\App\ResourceConnection; +use Magento\TestFramework\Helper\Bootstrap; + +/** @var ResourceConnection $resource */ +$resource = Bootstrap::getObjectManager()->get(ResourceConnection::class); +$connection = $resource->getConnection(); +$tableName = $resource->getTableName('core_config_data'); + +$connection->query("DELETE FROM $tableName WHERE path = 'catalog/search/elasticsearch7_server_port'" + ." AND scope = 'stores';"); From 2602b9fdbc848a699ebdbde1d34157d159c8a851 Mon Sep 17 00:00:00 2001 From: Bas van Poppel <vanpoppel@redkiwi.nl> Date: Mon, 28 Sep 2020 14:50:01 +0200 Subject: [PATCH 048/133] Revert "no message" This reverts commit 6c11aeba2dfb8f96917ee4a834545dbb582dbd03. --- .../ResourceModel/Advanced/Collection.php | 56 +++------- .../CatalogSearch/etc/search_request.xml | 5 - .../ProductCollectionPrepareStrategy.php | 18 +--- .../Console/Command/IndexerReindexCommand.php | 5 +- .../Model/CatalogSearch/AdvancedTest.php | 101 ------------------ .../Command/IndexerReindexCommandTest.php | 25 ++--- .../Indexer/_files/wrong_config_data.php | 16 --- .../_files/wrong_config_data_rollback.php | 17 --- 8 files changed, 25 insertions(+), 218 deletions(-) delete mode 100644 dev/tests/integration/testsuite/Magento/Elasticsearch/Model/CatalogSearch/AdvancedTest.php delete mode 100644 dev/tests/integration/testsuite/Magento/Indexer/_files/wrong_config_data.php delete mode 100644 dev/tests/integration/testsuite/Magento/Indexer/_files/wrong_config_data_rollback.php diff --git a/app/code/Magento/CatalogSearch/Model/ResourceModel/Advanced/Collection.php b/app/code/Magento/CatalogSearch/Model/ResourceModel/Advanced/Collection.php index 6005455a6ef83..47160bff1d571 100644 --- a/app/code/Magento/CatalogSearch/Model/ResourceModel/Advanced/Collection.php +++ b/app/code/Magento/CatalogSearch/Model/ResourceModel/Advanced/Collection.php @@ -6,30 +6,27 @@ namespace Magento\CatalogSearch\Model\ResourceModel\Advanced; -use Magento\Catalog\Model\Category; use Magento\Catalog\Model\Product; -use Magento\Catalog\Model\ResourceModel\Product\Collection\ProductLimitationFactory; -use Magento\CatalogSearch\Model\ResourceModel\Advanced; use Magento\CatalogSearch\Model\ResourceModel\Fulltext\Collection\DefaultFilterStrategyApplyChecker; use Magento\CatalogSearch\Model\ResourceModel\Fulltext\Collection\DefaultFilterStrategyApplyCheckerInterface; -use Magento\CatalogSearch\Model\ResourceModel\Fulltext\Collection\SearchCriteriaResolverFactory; use Magento\CatalogSearch\Model\ResourceModel\Fulltext\Collection\SearchCriteriaResolverInterface; -use Magento\CatalogSearch\Model\ResourceModel\Fulltext\Collection\SearchResultApplierFactory; use Magento\CatalogSearch\Model\ResourceModel\Fulltext\Collection\SearchResultApplierInterface; -use Magento\CatalogSearch\Model\ResourceModel\Fulltext\Collection\TotalRecordsResolverFactory; +use Magento\Framework\Search\EngineResolverInterface; use Magento\CatalogSearch\Model\ResourceModel\Fulltext\Collection\TotalRecordsResolverInterface; -use Magento\Eav\Model\Entity\Attribute\AbstractAttribute; +use Magento\CatalogSearch\Model\ResourceModel\Fulltext\Collection\TotalRecordsResolverFactory; use Magento\Framework\Api\FilterBuilder; +use Magento\Framework\DB\Select; use Magento\Framework\Api\Search\SearchCriteriaBuilder; use Magento\Framework\Api\Search\SearchResultFactory; -use Magento\Framework\Api\Search\SearchResultInterface; -use Magento\Framework\App\ObjectManager; -use Magento\Framework\DB\Select; use Magento\Framework\EntityManager\MetadataPool; use Magento\Framework\Exception\LocalizedException; -use Magento\Framework\Search\EngineResolverInterface; use Magento\Framework\Search\Request\EmptyRequestDataException; use Magento\Framework\Search\Request\NonExistingRequestNameException; +use Magento\Catalog\Model\ResourceModel\Product\Collection\ProductLimitationFactory; +use Magento\CatalogSearch\Model\ResourceModel\Fulltext\Collection\SearchCriteriaResolverFactory; +use Magento\CatalogSearch\Model\ResourceModel\Fulltext\Collection\SearchResultApplierFactory; +use Magento\Framework\App\ObjectManager; +use Magento\Framework\Api\Search\SearchResultInterface; /** * Advanced search collection @@ -109,11 +106,6 @@ class Collection extends \Magento\Catalog\Model\ResourceModel\Product\Collection */ private $defaultFilterStrategyApplyChecker; - /** - * @var Advanced - */ - private $advancedSearchResource; - /** * Collection constructor * @@ -149,7 +141,6 @@ class Collection extends \Magento\Catalog\Model\ResourceModel\Product\Collection * @param TotalRecordsResolverFactory|null $totalRecordsResolverFactory * @param EngineResolverInterface|null $engineResolver * @param DefaultFilterStrategyApplyCheckerInterface|null $defaultFilterStrategyApplyChecker - * @param Advanced|null $advancedSearchResource * @SuppressWarnings(PHPMD.ExcessiveParameterList) * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ @@ -185,8 +176,7 @@ public function __construct( SearchResultApplierFactory $searchResultApplierFactory = null, TotalRecordsResolverFactory $totalRecordsResolverFactory = null, EngineResolverInterface $engineResolver = null, - DefaultFilterStrategyApplyCheckerInterface $defaultFilterStrategyApplyChecker = null, - Advanced $advancedSearchResource = null + DefaultFilterStrategyApplyCheckerInterface $defaultFilterStrategyApplyChecker = null ) { $this->searchRequestName = $searchRequestName; if ($searchResultFactory === null) { @@ -203,8 +193,6 @@ public function __construct( ->get(EngineResolverInterface::class); $this->defaultFilterStrategyApplyChecker = $defaultFilterStrategyApplyChecker ?: ObjectManager::getInstance() ->get(DefaultFilterStrategyApplyChecker::class); - $this->advancedSearchResource = $advancedSearchResource ?: ObjectManager::getInstance() - ->get(Advanced::class); parent::__construct( $entityFactory, $logger, @@ -270,7 +258,6 @@ public function setOrder($attribute, $dir = Select::SQL_DESC) */ public function addCategoryFilter(\Magento\Catalog\Model\Category $category) { - $this->setAttributeFilterData(Category::ENTITY, 'category_ids', $category->getId()); /** * This changes need in backward compatible reasons for support dynamic improved algorithm * for price aggregation process. @@ -278,6 +265,7 @@ public function addCategoryFilter(\Magento\Catalog\Model\Category $category) if ($this->defaultFilterStrategyApplyChecker->isApplicable()) { parent::addCategoryFilter($category); } else { + $this->addFieldToFilter('category_ids', $category->getId()); $this->_productLimitationPrice(); } @@ -290,13 +278,14 @@ public function addCategoryFilter(\Magento\Catalog\Model\Category $category) */ public function setVisibility($visibility) { - $this->setAttributeFilterData(Product::ENTITY, 'visibility', $visibility); /** * This changes need in backward compatible reasons for support dynamic improved algorithm * for price aggregation process. */ if ($this->defaultFilterStrategyApplyChecker->isApplicable()) { parent::setVisibility($visibility); + } else { + $this->addFieldToFilter('visibility', $visibility); } return $this; @@ -317,25 +306,6 @@ private function setSearchOrder($field, $direction) $this->searchOrders[$field] = $direction; } - /** - * Prepare attribute data to filter. - * - * @param string $entityType - * @param string $attributeCode - * @param mixed $condition - * @return $this - */ - private function setAttributeFilterData(string $entityType, string $attributeCode, $condition): self - { - /** @var AbstractAttribute $attribute */ - $attribute = $this->_eavConfig->getAttribute($entityType, $attributeCode); - $table = $attribute->getBackend()->getTable(); - $condition = $this->advancedSearchResource->prepareCondition($attribute, $condition); - $this->addFieldsToFilter([$table => [$attributeCode => $condition]]); - - return $this; - } - /** * @inheritdoc */ @@ -407,7 +377,7 @@ public function _loadEntities($printQuery = false, $logQuery = false) $query = $this->getSelect(); $rows = $this->_fetchAll($query); } catch (\Exception $e) { - $this->printLogQuery(false, true, $query ?? null); + $this->printLogQuery(false, true, $query); throw $e; } diff --git a/app/code/Magento/CatalogSearch/etc/search_request.xml b/app/code/Magento/CatalogSearch/etc/search_request.xml index ecda6112ba03e..2111c469986ec 100644 --- a/app/code/Magento/CatalogSearch/etc/search_request.xml +++ b/app/code/Magento/CatalogSearch/etc/search_request.xml @@ -66,7 +66,6 @@ <queryReference clause="should" ref="sku_query"/> <queryReference clause="should" ref="price_query"/> <queryReference clause="should" ref="category_query"/> - <queryReference clause="must" ref="visibility_query"/> </query> <query name="sku_query" xsi:type="filteredQuery"> <filterReference clause="must" ref="sku_query_filter"/> @@ -77,15 +76,11 @@ <query name="category_query" xsi:type="filteredQuery"> <filterReference clause="must" ref="category_filter"/> </query> - <query name="visibility_query" xsi:type="filteredQuery"> - <filterReference clause="must" ref="visibility_filter"/> - </query> </queries> <filters> <filter xsi:type="wildcardFilter" name="sku_query_filter" field="sku" value="$sku$"/> <filter xsi:type="rangeFilter" name="price_query_filter" field="price" from="$price.from$" to="$price.to$"/> <filter xsi:type="termFilter" name="category_filter" field="category_ids" value="$category_ids$"/> - <filter xsi:type="termFilter" name="visibility_filter" field="visibility" value="$visibility$"/> </filters> <from>0</from> <size>10000</size> diff --git a/app/code/Magento/Elasticsearch/Model/Advanced/ProductCollectionPrepareStrategy.php b/app/code/Magento/Elasticsearch/Model/Advanced/ProductCollectionPrepareStrategy.php index d7054e2bb4b11..b3f8a56110f8d 100644 --- a/app/code/Magento/Elasticsearch/Model/Advanced/ProductCollectionPrepareStrategy.php +++ b/app/code/Magento/Elasticsearch/Model/Advanced/ProductCollectionPrepareStrategy.php @@ -5,11 +5,9 @@ */ namespace Magento\Elasticsearch\Model\Advanced; -use Magento\Catalog\Model\Config; -use Magento\Catalog\Model\Product\Visibility; use Magento\Catalog\Model\ResourceModel\Product\Collection; +use Magento\Catalog\Model\Config; use Magento\CatalogSearch\Model\Advanced\ProductCollectionPrepareStrategyInterface; -use Magento\Framework\App\ObjectManager; /** * Strategy interface for preparing product collection. @@ -21,22 +19,13 @@ class ProductCollectionPrepareStrategy implements ProductCollectionPrepareStrate */ private $catalogConfig; - /** - * @var Visibility - */ - private $catalogProductVisibility; - /** * @param Config $catalogConfig - * @param Visibility|null $catalogProductVisibility */ public function __construct( - Config $catalogConfig, - Visibility $catalogProductVisibility = null + Config $catalogConfig ) { $this->catalogConfig = $catalogConfig; - $this->catalogProductVisibility = $catalogProductVisibility - ?? ObjectManager::getInstance()->get(Visibility::class); } /** @@ -47,7 +36,6 @@ public function prepare(Collection $collection) $collection ->addAttributeToSelect($this->catalogConfig->getProductAttributes()) ->addMinimalPrice() - ->addTaxPercents() - ->setVisibility($this->catalogProductVisibility->getVisibleInSearchIds()); + ->addTaxPercents(); } } diff --git a/app/code/Magento/Indexer/Console/Command/IndexerReindexCommand.php b/app/code/Magento/Indexer/Console/Command/IndexerReindexCommand.php index e7517ba0c8818..775f585519947 100644 --- a/app/code/Magento/Indexer/Console/Command/IndexerReindexCommand.php +++ b/app/code/Magento/Indexer/Console/Command/IndexerReindexCommand.php @@ -74,7 +74,7 @@ protected function configure() */ protected function execute(InputInterface $input, OutputInterface $output) { - $returnValue = Cli::RETURN_SUCCESS; + $returnValue = Cli::RETURN_FAILURE; foreach ($this->getIndexers($input) as $indexer) { try { $this->validateIndexerStatus($indexer); @@ -97,15 +97,14 @@ protected function execute(InputInterface $input, OutputInterface $output) $output->writeln( __('has been rebuilt successfully in %time', ['time' => gmdate('H:i:s', $resultTime)]) ); + $returnValue = Cli::RETURN_SUCCESS; } catch (LocalizedException $e) { $output->writeln(__('exception: %message', ['message' => $e->getMessage()])); - $returnValue = Cli::RETURN_FAILURE; } catch (\Exception $e) { $output->writeln('process unknown error:'); $output->writeln($e->getMessage()); $output->writeln($e->getTraceAsString(), OutputInterface::VERBOSITY_DEBUG); - $returnValue = Cli::RETURN_FAILURE; } } diff --git a/dev/tests/integration/testsuite/Magento/Elasticsearch/Model/CatalogSearch/AdvancedTest.php b/dev/tests/integration/testsuite/Magento/Elasticsearch/Model/CatalogSearch/AdvancedTest.php deleted file mode 100644 index 815f480cf7b53..0000000000000 --- a/dev/tests/integration/testsuite/Magento/Elasticsearch/Model/CatalogSearch/AdvancedTest.php +++ /dev/null @@ -1,101 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ -declare(strict_types=1); - -namespace Magento\Elasticsearch\Model\CatalogSearch; - -use Magento\Catalog\Api\ProductRepositoryInterface; -use Magento\CatalogSearch\Model\Advanced; -use Magento\Catalog\Model\Product\Visibility; -use Magento\Catalog\Api\Data\ProductInterface; -use Magento\Framework\ObjectManagerInterface; -use Magento\Framework\Registry; -use Magento\TestFramework\Helper\Bootstrap; -use PHPUnit\Framework\TestCase; - -/** - * Check catalog Advanced Search process with Elasticsearch enabled. - */ -class AdvancedTest extends TestCase -{ - /** - * @var ObjectManagerInterface - */ - private $objectManager; - - /** - * @var Registry - */ - private $registry; - - /** - * @var Visibility - */ - private $productVisibility; - - /** - * @var ProductRepositoryInterface - */ - private $productRepository; - - /** - * @inheritDoc - */ - protected function setUp(): void - { - parent::setUp(); - - $this->objectManager = Bootstrap::getObjectManager(); - $this->registry = $this->objectManager->get(Registry::class); - $this->productVisibility = $this->objectManager->get(Visibility::class); - $this->productRepository = $this->objectManager->get(ProductRepositoryInterface::class); - } - - /** - * Check that Advanced Search does NOT return products that do NOT have search visibility. - * - * @magentoDbIsolation disabled - * @magentoConfigFixture default/catalog/search/engine elasticsearch7 - * @magentoDataFixture Magento/ConfigurableProduct/_files/configurable_product_with_two_child_products.php - * @return void - */ - public function testAddFilters(): void - { - $this->assertResultsAfterRequest(1); - - /** @var ProductInterface $configurableProductOption */ - $configurableProductOption = $this->productRepository->get('Simple option 1'); - $configurableProductOption->setVisibility(Visibility::VISIBILITY_IN_SEARCH); - $this->productRepository->save($configurableProductOption); - - $this->registry->unregister('advanced_search_conditions'); - $this->assertResultsAfterRequest(2); - } - - /** - * Do Elasticsearch query and assert results. - * - * @param int $count - * @return void - */ - private function assertResultsAfterRequest(int $count): void - { - /** @var Advanced $advancedSearch */ - $advancedSearch = $this->objectManager->create(Advanced::class); - $advancedSearch->addFilters(['name' => 'Configurable']); - - /** @var ProductInterface[] $itemsResult */ - $itemsResult = $advancedSearch->getProductCollection() - ->addAttributeToSelect(ProductInterface::VISIBILITY) - ->getItems(); - - $this->assertCount($count, $itemsResult); - foreach ($itemsResult as $product) { - $this->assertStringContainsString('Configurable', $product->getName()); - $this->assertContains((int)$product->getVisibility(), $this->productVisibility->getVisibleInSearchIds()); - } - } -} diff --git a/dev/tests/integration/testsuite/Magento/Indexer/Console/Command/IndexerReindexCommandTest.php b/dev/tests/integration/testsuite/Magento/Indexer/Console/Command/IndexerReindexCommandTest.php index 67de7913b4603..f4730b0b32c18 100644 --- a/dev/tests/integration/testsuite/Magento/Indexer/Console/Command/IndexerReindexCommandTest.php +++ b/dev/tests/integration/testsuite/Magento/Indexer/Console/Command/IndexerReindexCommandTest.php @@ -7,11 +7,9 @@ namespace Magento\Indexer\Console\Command; -use Magento\Framework\Console\Cli; use Magento\Framework\ObjectManagerInterface; use Magento\TestFramework\Helper\Bootstrap; use PHPUnit\Framework\MockObject\MockObject as Mock; -use PHPUnit\Framework\TestCase; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; @@ -21,7 +19,7 @@ * @magentoDbIsolation disabled * @magentoAppIsolation enabled */ -class IndexerReindexCommandTest extends TestCase +class IndexerReindexCommandTest extends \PHPUnit\Framework\TestCase { /** * @var ObjectManagerInterface @@ -58,23 +56,14 @@ protected function setUp(): void /** * @magentoDataFixture Magento/Store/_files/second_store_group_with_second_website.php - * @return void */ - public function testReindexAll(): void + public function testReindexAll() { $status = $this->command->run($this->inputMock, $this->outputMock); - $this->assertEquals(Cli::RETURN_SUCCESS, $status, 'Index wasn\'t success'); - } - - /** - * Check that 'indexer:reindex' command return right code. - * - * @magentoDataFixture Magento/Indexer/_files/wrong_config_data.php - * @return void - */ - public function testReindexAllWhenSomethingIsWrong(): void - { - $status = $this->command->run($this->inputMock, $this->outputMock); - $this->assertEquals(Cli::RETURN_FAILURE, $status, 'Index didn\'t return failure code'); + $this->assertEquals( + \Magento\Framework\Console\Cli::RETURN_SUCCESS, + $status, + 'Index wasn\'t success' + ); } } diff --git a/dev/tests/integration/testsuite/Magento/Indexer/_files/wrong_config_data.php b/dev/tests/integration/testsuite/Magento/Indexer/_files/wrong_config_data.php deleted file mode 100644 index c6ec6235b61e6..0000000000000 --- a/dev/tests/integration/testsuite/Magento/Indexer/_files/wrong_config_data.php +++ /dev/null @@ -1,16 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ -declare(strict_types=1); - -use Magento\Config\Model\Config\Factory; -use Magento\TestFramework\Helper\Bootstrap; - -/** @var Factory $configFactory */ -$configFactory = Bootstrap::getObjectManager()->get(Factory::class); -$config = $configFactory->create(); -$config->setScope('stores'); -$config->setDataByPath('catalog/search/elasticsearch7_server_port', 2309); -$config->save(); diff --git a/dev/tests/integration/testsuite/Magento/Indexer/_files/wrong_config_data_rollback.php b/dev/tests/integration/testsuite/Magento/Indexer/_files/wrong_config_data_rollback.php deleted file mode 100644 index 7ca0b986fc206..0000000000000 --- a/dev/tests/integration/testsuite/Magento/Indexer/_files/wrong_config_data_rollback.php +++ /dev/null @@ -1,17 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ -declare(strict_types=1); - -use Magento\Framework\App\ResourceConnection; -use Magento\TestFramework\Helper\Bootstrap; - -/** @var ResourceConnection $resource */ -$resource = Bootstrap::getObjectManager()->get(ResourceConnection::class); -$connection = $resource->getConnection(); -$tableName = $resource->getTableName('core_config_data'); - -$connection->query("DELETE FROM $tableName WHERE path = 'catalog/search/elasticsearch7_server_port'" - ." AND scope = 'stores';"); From 09d13b8c2b13df0bd376c8b2ebbb3b81effdc252 Mon Sep 17 00:00:00 2001 From: Bas van Poppel <vanpoppel@redkiwi.nl> Date: Tue, 29 Sep 2020 15:57:59 +0200 Subject: [PATCH 049/133] Test showing that a discount applied by a salesrule affects the table rate shipping method --- ...sAppliedOnPackageValueForTableRateTest.xml | 94 +++++++++++++++++++ .../Data/TableRatesShippingMethodData.xml | 6 ++ 2 files changed, 100 insertions(+) create mode 100644 app/code/Magento/OfflineShipping/Test/Mftf/Test/SalesRuleDiscountIsAppliedOnPackageValueForTableRateTest.xml diff --git a/app/code/Magento/OfflineShipping/Test/Mftf/Test/SalesRuleDiscountIsAppliedOnPackageValueForTableRateTest.xml b/app/code/Magento/OfflineShipping/Test/Mftf/Test/SalesRuleDiscountIsAppliedOnPackageValueForTableRateTest.xml new file mode 100644 index 0000000000000..ef87696c37718 --- /dev/null +++ b/app/code/Magento/OfflineShipping/Test/Mftf/Test/SalesRuleDiscountIsAppliedOnPackageValueForTableRateTest.xml @@ -0,0 +1,94 @@ +<?xml version="1.0" encoding="UTF-8"?> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="SalesRuleDiscountIsAppliedOnPackageValueForTableRateTest"> + <annotations> + <features value="Shipping"/> + <stories value="Offline Shipping Methods"/> + <title value="SalesRule Discount Is Applied On PackageValue For TableRate"/> + <description value="SalesRule Discount Is Applied On PackageValue For TableRate"/> + <severity value="AVERAGE"/> + <group value="shipping"/> + </annotations> + <before> + <!-- Add simple product --> + <createData entity="SimpleProduct2" stepKey="createSimpleProduct"> + <field key="price">13.00</field> + </createData> + + <!-- Create cart price rule --> + <createData entity="ActiveSalesRuleForNotLoggedIn" stepKey="createCartPriceRule"/> + <createData entity="SimpleSalesRuleCoupon" stepKey="createCouponForCartPriceRule"> + <requiredEntity createDataKey="createCartPriceRule"/> + </createData> + + <!-- Login as admin --> + <actionGroup ref="AdminLoginActionGroup" stepKey="loginAsAdmin"/> + + <!-- Go to Stores > Configuration > Sales > Shipping Methods --> + <actionGroup ref="AdminOpenShippingMethodsConfigPageActionGroup" stepKey="openShippingMethodConfigPage"/> + + <!-- Switch to Website scope --> + <actionGroup ref="AdminSwitchWebsiteActionGroup" stepKey="AdminSwitchStoreView"> + <argument name="website" value="_defaultWebsite"/> + </actionGroup> + + <!-- Enable Table Rate method and save config --> + <actionGroup ref="AdminChangeTableRatesShippingMethodStatusActionGroup" stepKey="enableTableRatesShippingMethod"/> + + <!-- Uncheck Use Default checkbox for Default Condition --> + <uncheckOption selector="{{AdminShippingMethodTableRatesSection.carriersTableRateConditionName}}" stepKey="disableUseDefaultCondition"/> + + <!-- Make sure you have Condition Price vs. Destination --> + <selectOption selector="{{AdminShippingMethodTableRatesSection.condition}}" userInput="{{TableRateShippingMethodConfig.package_value_with_discount}}" stepKey="setCondition"/> + + <!-- Import file and save config --> + <attachFile selector="{{AdminShippingMethodTableRatesSection.importFile}}" userInput="usa_tablerates.csv" stepKey="attachFileForImport"/> + <actionGroup ref="AdminSaveConfigActionGroup" stepKey="saveConfigs"/> + </before> + <after> + <!-- Go to Stores > Configuration > Sales > Shipping Methods --> + <actionGroup ref="AdminOpenShippingMethodsConfigPageActionGroup" stepKey="openShippingMethodConfigPage"/> + + <!-- Switch to Website scope --> + <actionGroup ref="AdminSwitchWebsiteActionGroup" stepKey="AdminSwitchStoreView"> + <argument name="website" value="_defaultWebsite"/> + </actionGroup> + + <!-- Check Use Default checkbox for Default Condition and Active --> + <checkOption selector="{{AdminShippingMethodTableRatesSection.carriersTableRateConditionName}}" stepKey="enableUseDefaultCondition"/> + <checkOption selector="{{AdminShippingMethodTableRatesSection.enabledUseSystemValue}}" stepKey="enableUseDefaultActive"/> + + <actionGroup ref="AdminSaveConfigActionGroup" stepKey="saveConfigs"/> + + <!-- Log out --> + <actionGroup ref="AdminLogoutActionGroup" stepKey="logout"/> + + <!-- Remove simple product--> + <deleteData createDataKey="createSimpleProduct" stepKey="deleteSimpleProduct"/> + + <!-- Delete sales rule --> + <deleteData createDataKey="createCartPriceRule" stepKey="deleteCartPriceRule"/> + + </after> + <!-- Add simple product to cart --> + <actionGroup ref="AddSimpleProductToCartActionGroup" stepKey="addProductToCart"> + <argument name="product" value="$$createSimpleProduct$$"/> + </actionGroup> + + <!-- Assert that table rate value is correct for US --> + <actionGroup ref="StorefrontCartPageOpenActionGroup" stepKey="goToCheckout"/> + <waitForElement time="30" selector="{{CheckoutCartSummarySection.estimateShippingAndTaxForm}}" stepKey="waitForEstimateShippingAndTaxForm"/> + <waitForElement time="30" selector="{{CheckoutCartSummarySection.shippingMethodForm}}" stepKey="waitForShippingMethodForm"/> + <conditionalClick selector="{{CheckoutCartSummarySection.estimateShippingAndTax}}" dependentSelector="{{CheckoutCartSummarySection.country}}" visible="false" stepKey="expandEstimateShippingandTax" /> + <selectOption selector="{{CheckoutCartSummarySection.country}}" userInput="United States" stepKey="selectUSCountry"/> + <waitForPageLoad stepKey="waitForSelectCountry"/> + <see selector="{{CheckoutCartSummarySection.shippingPrice}}" userInput="$5.99" stepKey="seeShippingForUS"/> + + <!-- Apply Coupon --> + <actionGroup ref="StorefrontApplyCouponActionGroup" stepKey="applyDiscount"> + <argument name="coupon" value="$$createCouponForCartPriceRule$$"/> + </actionGroup> + + <see selector="{{CheckoutCartSummarySection.shippingPrice}}" userInput="$7.99" stepKey="seeShippingForUSWithDiscount"/> + </test> +</tests> diff --git a/app/code/Magento/Shipping/Test/Mftf/Data/TableRatesShippingMethodData.xml b/app/code/Magento/Shipping/Test/Mftf/Data/TableRatesShippingMethodData.xml index 47ef68cc9d765..ceae9c546bd3b 100644 --- a/app/code/Magento/Shipping/Test/Mftf/Data/TableRatesShippingMethodData.xml +++ b/app/code/Magento/Shipping/Test/Mftf/Data/TableRatesShippingMethodData.xml @@ -16,4 +16,10 @@ <data key="title">Best Way</data> <data key="methodName">Table Rate</data> </entity> + <!-- Set Table Rate Shipping method Condition --> + <entity name="TableRateShippingMethodConfig" type="shipping_method"> + <data key="package_weight">Weight vs. Destination</data> + <data key="package_value_with_discount">Price vs. Destination</data> + <data key="package_qty"># of Items vs. Destination</data> + </entity> </entities> From 460252f762bd9ee5af2fb880f4b3b242f471a193 Mon Sep 17 00:00:00 2001 From: Bas van Poppel <vanpoppel@redkiwi.nl> Date: Tue, 29 Sep 2020 16:59:43 +0200 Subject: [PATCH 050/133] Added copyright to test --- ...uleDiscountIsAppliedOnPackageValueForTableRateTest.xml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/app/code/Magento/OfflineShipping/Test/Mftf/Test/SalesRuleDiscountIsAppliedOnPackageValueForTableRateTest.xml b/app/code/Magento/OfflineShipping/Test/Mftf/Test/SalesRuleDiscountIsAppliedOnPackageValueForTableRateTest.xml index ef87696c37718..52d456bc6225d 100644 --- a/app/code/Magento/OfflineShipping/Test/Mftf/Test/SalesRuleDiscountIsAppliedOnPackageValueForTableRateTest.xml +++ b/app/code/Magento/OfflineShipping/Test/Mftf/Test/SalesRuleDiscountIsAppliedOnPackageValueForTableRateTest.xml @@ -1,4 +1,10 @@ -<?xml version="1.0" encoding="UTF-8"?> +<?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="SalesRuleDiscountIsAppliedOnPackageValueForTableRateTest"> <annotations> From d59ad58d924f50350908ae504bbff46ebdef59d6 Mon Sep 17 00:00:00 2001 From: Serhiy Yelahin <serhiy.yelahin@transoftgroup.com> Date: Fri, 2 Oct 2020 15:50:04 +0300 Subject: [PATCH 051/133] MC-38048: Incorrect default country displayed on shipping page when store view is changed in cart. Part 2 --- .../Checkout/view/frontend/web/js/view/shipping.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/app/code/Magento/Checkout/view/frontend/web/js/view/shipping.js b/app/code/Magento/Checkout/view/frontend/web/js/view/shipping.js index 646e6156ec646..4e82c05f0385a 100644 --- a/app/code/Magento/Checkout/view/frontend/web/js/view/shipping.js +++ b/app/code/Magento/Checkout/view/frontend/web/js/view/shipping.js @@ -121,7 +121,13 @@ define([ ); } checkoutProvider.on('shippingAddress', function (shippingAddrsData) { - checkoutData.setShippingAddressFromData(shippingAddrsData); + //jscs:disable requireCamelCaseOrUpperCaseIdentifiers + if (quote.shippingAddress().countryId !== shippingAddrsData.country_id && + (shippingAddrsData.postcode || shippingAddrsData.region_id) + ) { + checkoutData.setShippingAddressFromData(shippingAddrsData); + } + //jscs:enable requireCamelCaseOrUpperCaseIdentifiers }); shippingRatesValidator.initFields(fieldsetName); }); From 48216f3e13d04b8755b70f8b9b1399d87a33ab91 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Szubert?= <bartlomiejszubert@gmail.com> Date: Mon, 28 Sep 2020 23:52:32 +0200 Subject: [PATCH 052/133] Fix #11175 - i18n:collect-phrases -m can't find many important magento phrases --- .../Setup/Module/I18n/Parser/Adapter/Html.php | 30 ++++++++-- .../Module/I18n/Parser/Adapter/HtmlTest.php | 56 ++++++++++--------- .../I18n/Parser/Adapter/_files/email.html | 2 + 3 files changed, 57 insertions(+), 31 deletions(-) diff --git a/setup/src/Magento/Setup/Module/I18n/Parser/Adapter/Html.php b/setup/src/Magento/Setup/Module/I18n/Parser/Adapter/Html.php index cf38fd70884f3..ec62ab8b84482 100644 --- a/setup/src/Magento/Setup/Module/I18n/Parser/Adapter/Html.php +++ b/setup/src/Magento/Setup/Module/I18n/Parser/Adapter/Html.php @@ -3,8 +3,11 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\Setup\Module\I18n\Parser\Adapter; +use Exception; use Magento\Email\Model\Template\Filter; /** @@ -16,17 +19,30 @@ class Html extends AbstractAdapter * Covers * <span><!-- ko i18n: 'Next'--><!-- /ko --></span> * <th class="col col-method" data-bind="i18n: 'Select Method'"></th> + * @deprecated Not used anymore because of newly introduced constant + * @see self::HTML_REGEX_LIST */ const HTML_FILTER = "/i18n:\s?'(?<value>[^'\\\\]*(?:\\\\.[^'\\\\]*)*)'/i"; + private const HTML_REGEX_LIST = [ + // <span><!-- ko i18n: 'Next'--><!-- /ko --></span> + // <th class="col col-method" data-bind="i18n: 'Select Method'"></th> + "/i18n:\s?'(?<value>[^'\\\\]*(?:\\\\.[^'\\\\]*)*)'/i", + // <translate args="'System Messages'"/> + // <span translate="'Examples'"></span> + "/translate( args|)=\"'(?<value>[^\"\\\\]*(?:\\\\.[^\"\\\\]*)*)'\"/i" + ]; + /** * @inheritdoc */ protected function _parse() { + // phpcs:ignore Magento2.Functions.DiscouragedFunction $data = file_get_contents($this->_file); if ($data === false) { - throw new \Exception('Failed to load file from disk.'); + // phpcs:ignore Magento2.Exceptions.DirectThrow + throw new Exception('Failed to load file from disk.'); } $results = []; @@ -37,15 +53,19 @@ protected function _parse() if (preg_match(Filter::TRANS_DIRECTIVE_REGEX, $results[$i][2], $directive) !== 1) { continue; } + $quote = $directive[1]; $this->_addPhrase($quote . $directive[2] . $quote); } } - preg_match_all(self::HTML_FILTER, $data, $results, PREG_SET_ORDER); - for ($i = 0, $count = count($results); $i < $count; $i++) { - if (!empty($results[$i]['value'])) { - $this->_addPhrase($results[$i]['value']); + foreach (self::HTML_REGEX_LIST as $regex) { + preg_match_all($regex, $data, $results, PREG_SET_ORDER); + + for ($i = 0, $count = count($results); $i < $count; $i++) { + if (!empty($results[$i]['value'])) { + $this->_addPhrase($results[$i]['value']); + } } } } diff --git a/setup/src/Magento/Setup/Test/Unit/Module/I18n/Parser/Adapter/HtmlTest.php b/setup/src/Magento/Setup/Test/Unit/Module/I18n/Parser/Adapter/HtmlTest.php index 15c442e9bac98..d7a2f0b4a9397 100644 --- a/setup/src/Magento/Setup/Test/Unit/Module/I18n/Parser/Adapter/HtmlTest.php +++ b/setup/src/Magento/Setup/Test/Unit/Module/I18n/Parser/Adapter/HtmlTest.php @@ -7,33 +7,25 @@ namespace Magento\Setup\Test\Unit\Module\I18n\Parser\Adapter; -use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; use Magento\Setup\Module\I18n\Parser\Adapter\Html; use PHPUnit\Framework\TestCase; class HtmlTest extends TestCase { /** - * @var string - */ - protected $_testFile; - - /** - * @var int + * @var Html */ - protected $_stringsCount; + private $model; /** - * @var Html + * @var string */ - protected $_adapter; + private $testFile; protected function setUp(): void { - $this->_testFile = str_replace('\\', '/', realpath(dirname(__FILE__))) . '/_files/email.html'; - $this->_stringsCount = count(file($this->_testFile)); - - $this->_adapter = (new ObjectManager($this))->getObject(Html::class); + $this->testFile = str_replace('\\', '/', realpath(__DIR__)) . '/_files/email.html'; + $this->model = new Html(); } public function testParse() @@ -41,68 +33,80 @@ public function testParse() $expectedResult = [ [ 'phrase' => 'Phrase 1', - 'file' => $this->_testFile, + 'file' => $this->testFile, 'line' => '', 'quote' => '\'', ], [ 'phrase' => 'Phrase 2 with %a_lot of extra info for the brilliant %customer_name.', - 'file' => $this->_testFile, + 'file' => $this->testFile, 'line' => '', 'quote' => '"', ], [ 'phrase' => 'This is test data', - 'file' => $this->_testFile, + 'file' => $this->testFile, 'line' => '', 'quote' => '', ], [ 'phrase' => 'This is test data at right side of attr', - 'file' => $this->_testFile, + 'file' => $this->testFile, 'line' => '', 'quote' => '', ], [ 'phrase' => 'This is \\\' test \\\' data', - 'file' => $this->_testFile, + 'file' => $this->testFile, 'line' => '', 'quote' => '', ], [ 'phrase' => 'This is \\" test \\" data', - 'file' => $this->_testFile, + 'file' => $this->testFile, 'line' => '', 'quote' => '', ], [ 'phrase' => 'This is test data with a quote after', - 'file' => $this->_testFile, + 'file' => $this->testFile, 'line' => '', 'quote' => '', ], [ 'phrase' => 'This is test data with space after ', - 'file' => $this->_testFile, + 'file' => $this->testFile, 'line' => '', 'quote' => '', ], [ 'phrase' => '\\\'', - 'file' => $this->_testFile, + 'file' => $this->testFile, 'line' => '', 'quote' => '', ], [ 'phrase' => '\\\\\\\\ ', - 'file' => $this->_testFile, + 'file' => $this->testFile, + 'line' => '', + 'quote' => '', + ], + [ + 'phrase' => 'This is test content in translate tag', + 'file' => $this->testFile, + 'line' => '', + 'quote' => '', + ], + [ + 'phrase' => 'This is test content in translate attribute', + 'file' => $this->testFile, 'line' => '', 'quote' => '', ], ]; - $this->_adapter->parse($this->_testFile); + $this->model->parse($this->testFile); - $this->assertEquals($expectedResult, $this->_adapter->getPhrases()); + $this->assertEquals($expectedResult, $this->model->getPhrases()); } } diff --git a/setup/src/Magento/Setup/Test/Unit/Module/I18n/Parser/Adapter/_files/email.html b/setup/src/Magento/Setup/Test/Unit/Module/I18n/Parser/Adapter/_files/email.html index 90579b48a07b5..f5603768ef306 100644 --- a/setup/src/Magento/Setup/Test/Unit/Module/I18n/Parser/Adapter/_files/email.html +++ b/setup/src/Magento/Setup/Test/Unit/Module/I18n/Parser/Adapter/_files/email.html @@ -29,5 +29,7 @@ <label data-bind="i18n: ''"></label> <label data-bind="i18n: '\''"></label> <label data-bind="i18n: '\\\\ '"></label> + <span><translate args="'This is test content in translate tag'" /></span> + <span translate="'This is test content in translate attribute'"></span> </body> </html> From d45646954ac770058d081d408bddfa778f8b1c0f Mon Sep 17 00:00:00 2001 From: Serhiy Yelahin <serhiy.yelahin@transoftgroup.com> Date: Fri, 2 Oct 2020 17:57:03 +0300 Subject: [PATCH 053/133] MC-38048: Incorrect default country displayed on shipping page when store view is changed in cart. Part 2 --- .../Magento/Checkout/view/frontend/web/js/view/shipping.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/app/code/Magento/Checkout/view/frontend/web/js/view/shipping.js b/app/code/Magento/Checkout/view/frontend/web/js/view/shipping.js index 4e82c05f0385a..084c8ad59a8da 100644 --- a/app/code/Magento/Checkout/view/frontend/web/js/view/shipping.js +++ b/app/code/Magento/Checkout/view/frontend/web/js/view/shipping.js @@ -122,9 +122,7 @@ define([ } checkoutProvider.on('shippingAddress', function (shippingAddrsData) { //jscs:disable requireCamelCaseOrUpperCaseIdentifiers - if (quote.shippingAddress().countryId !== shippingAddrsData.country_id && - (shippingAddrsData.postcode || shippingAddrsData.region_id) - ) { + if (shippingAddrsData.street && shippingAddrsData.street[0].length > 0) { checkoutData.setShippingAddressFromData(shippingAddrsData); } //jscs:enable requireCamelCaseOrUpperCaseIdentifiers From 2588d77c4076b34a816e86bc6bf8f61ecd8c4672 Mon Sep 17 00:00:00 2001 From: Vasya Tsviklinskyi <tsviklinskyi@gmail.com> Date: Fri, 2 Oct 2020 18:05:58 +0300 Subject: [PATCH 054/133] MC-35771: Datepicker/calendar control does not use the store locale --- .../Magento/Customer/Block/Widget/Dob.php | 51 ++++++++++++++++++- .../view/frontend/templates/widget/dob.phtml | 19 +++++-- 2 files changed, 66 insertions(+), 4 deletions(-) diff --git a/app/code/Magento/Customer/Block/Widget/Dob.php b/app/code/Magento/Customer/Block/Widget/Dob.php index 90ce9ba210ed2..1ca15981b8370 100644 --- a/app/code/Magento/Customer/Block/Widget/Dob.php +++ b/app/code/Magento/Customer/Block/Widget/Dob.php @@ -7,11 +7,16 @@ use Magento\Customer\Api\CustomerMetadataInterface; use Magento\Framework\Api\ArrayObjectSearch; +use Magento\Framework\App\ObjectManager; +use Magento\Framework\Json\EncoderInterface; +use Magento\Framework\Locale\Bundle\DataBundle; +use Magento\Framework\Locale\ResolverInterface; /** * Customer date of birth attribute block * * @SuppressWarnings(PHPMD.DepthOfInheritance) + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class Dob extends AbstractWidget { @@ -39,6 +44,18 @@ class Dob extends AbstractWidget */ protected $filterFactory; + /** + * JSON Encoder + * + * @var EncoderInterface + */ + private $encoder; + + /** + * @var ResolverInterface + */ + private $localeResolver; + /** * @param \Magento\Framework\View\Element\Template\Context $context * @param \Magento\Customer\Helper\Address $addressHelper @@ -46,6 +63,8 @@ class Dob extends AbstractWidget * @param \Magento\Framework\View\Element\Html\Date $dateElement * @param \Magento\Framework\Data\Form\FilterFactory $filterFactory * @param array $data + * @param EncoderInterface|null $encoder + * @param ResolverInterface|null $localeResolver */ public function __construct( \Magento\Framework\View\Element\Template\Context $context, @@ -53,10 +72,14 @@ public function __construct( CustomerMetadataInterface $customerMetadata, \Magento\Framework\View\Element\Html\Date $dateElement, \Magento\Framework\Data\Form\FilterFactory $filterFactory, - array $data = [] + array $data = [], + ?EncoderInterface $encoder = null, + ?ResolverInterface $localeResolver = null ) { $this->dateElement = $dateElement; $this->filterFactory = $filterFactory; + $this->encoder = $encoder ?? ObjectManager::getInstance()->get(EncoderInterface::class); + $this->localeResolver = $localeResolver ?? ObjectManager::getInstance()->get(ResolverInterface::class); parent::__construct($context, $addressHelper, $customerMetadata, $data); } @@ -377,4 +400,30 @@ public function getFirstDay() \Magento\Store\Model\ScopeInterface::SCOPE_STORE ); } + + /** + * Get translated calendar config json formatted + * + * @return string + */ + public function getTranslatedCalendarConfigJson(): string + { + $localeData = (new DataBundle())->get($this->localeResolver->getLocale()); + $monthsData = $localeData['calendar']['gregorian']['monthNames']; + $daysData = $localeData['calendar']['gregorian']['dayNames']; + + return $this->encoder->encode( + [ + 'closeText' => __('Done'), + 'prevText' => __('Prev'), + 'nextText' => __('Next'), + 'currentText' => __('Today'), + 'monthNames' => array_values(iterator_to_array($monthsData['format']['wide'])), + 'monthNamesShort' => array_values(iterator_to_array($monthsData['format']['abbreviated'])), + 'dayNames' => array_values(iterator_to_array($daysData['format']['wide'])), + 'dayNamesShort' => array_values(iterator_to_array($daysData['format']['abbreviated'])), + 'dayNamesMin' => array_values(iterator_to_array($daysData['format']['short'])), + ] + ); + } } diff --git a/app/code/Magento/Customer/view/frontend/templates/widget/dob.phtml b/app/code/Magento/Customer/view/frontend/templates/widget/dob.phtml index 3c2f970faadee..fee577abadbf9 100644 --- a/app/code/Magento/Customer/view/frontend/templates/widget/dob.phtml +++ b/app/code/Magento/Customer/view/frontend/templates/widget/dob.phtml @@ -27,10 +27,12 @@ $fieldCssClass = 'field date field-' . $block->getHtmlId(); $fieldCssClass .= $block->isRequired() ? ' required' : ''; ?> <div class="<?= $block->escapeHtmlAttr($fieldCssClass) ?>"> - <label class="label" for="<?= $block->escapeHtmlAttr($block->getHtmlId()) ?>"><span><?= $block->escapeHtml($block->getStoreLabel('dob')) ?></span></label> + <label class="label" for="<?= $block->escapeHtmlAttr($block->getHtmlId()) ?>"> + <span><?= $block->escapeHtml($block->getStoreLabel('dob')) ?></span> + </label> <div class="control customer-dob"> <?= $block->getFieldHtml() ?> - <?php if ($_message = $block->getAdditionalDescription()) : ?> + <?php if ($_message = $block->getAdditionalDescription()): ?> <div class="note"><?= $block->escapeHtml($_message) ?></div> <?php endif; ?> </div> @@ -42,4 +44,15 @@ $fieldCssClass .= $block->isRequired() ? ' required' : ''; "Magento_Customer/js/validation": {} } } - </script> +</script> + +<script> + require([ + 'jquery', + 'jquery-ui-modules/datepicker' + ], function ( $ ) { + $.extend(true, $, { + calendarConfig: <?= $block->escapeJs($block->getTranslatedCalendarConfigJson()); ?> + }); + }); +</script> 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 055/133] 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 0c508ef7d05fc63c7345bf30a1e0b51fabb2be04 Mon Sep 17 00:00:00 2001 From: Vasya Tsviklinskyi <tsviklinskyi@gmail.com> Date: Mon, 5 Oct 2020 11:04:42 +0300 Subject: [PATCH 056/133] MC-35771: Datepicker/calendar control does not use the store locale --- .../Test/Unit/Block/Widget/DobTest.php | 59 ++++++++++++++++++- 1 file changed, 58 insertions(+), 1 deletion(-) diff --git a/app/code/Magento/Customer/Test/Unit/Block/Widget/DobTest.php b/app/code/Magento/Customer/Test/Unit/Block/Widget/DobTest.php index 70232e955a86d..1f5a6145e0eea 100644 --- a/app/code/Magento/Customer/Test/Unit/Block/Widget/DobTest.php +++ b/app/code/Magento/Customer/Test/Unit/Block/Widget/DobTest.php @@ -17,6 +17,7 @@ use Magento\Framework\Data\Form\FilterFactory; use Magento\Framework\Escaper; use Magento\Framework\Exception\NoSuchEntityException; +use Magento\Framework\Json\EncoderInterface; use Magento\Framework\Locale\Resolver; use Magento\Framework\Locale\ResolverInterface; use Magento\Framework\Stdlib\DateTime\Intl\DateFormatterFactory; @@ -91,6 +92,11 @@ class DobTest extends TestCase */ private $_locale; + /** + * @var EncoderInterface + */ + private $encoder; + /** * @inheritDoc */ @@ -157,12 +163,16 @@ function () use ($timezone, $localeResolver) { } ); + $this->encoder = $this->getMockForAbstractClass(EncoderInterface::class); + $this->_block = new Dob( $this->context, $this->createMock(Address::class), $this->customerMetadata, $this->createMock(Date::class), - $this->filterFactory + $this->filterFactory, + [], + $this->encoder ); } @@ -598,4 +608,51 @@ public function testGetHtmlExtraParamsWithRequiredOption() $this->_block->getHtmlExtraParams() ); } + + /** + * Tests getTranslatedCalendarConfigJson() + * + * @param array $expectedArray + * @param string $expectedJson + * @dataProvider getTranslatedCalendarConfigJsonDataProvider + * @return void + */ + public function testGetTranslatedCalendarConfigJson(array $expectedArray, string $expectedJson): void + { + $this->encoder->expects($this->once()) + ->method('encode') + ->with($expectedArray) + ->willReturn($expectedJson); + + $this->assertEquals( + $expectedJson, + $this->_block->getTranslatedCalendarConfigJson() + ); + } + + /** + * Provider for testGetTranslatedCalendarConfigJson + * + * @return array + */ + public function getTranslatedCalendarConfigJsonDataProvider() + { + return [ + [ + 'expectedArray' => [ + 'closeText' => 'Done', + 'prevText' => 'Prev', + 'nextText' => 'Next', + 'currentText' => 'Today', + 'monthNames' => ['January', 'February', 'March', 'April', 'May', 'June', + 'July', 'August', 'September', 'October', 'November', 'December'], + 'monthNamesShort' => ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'], + 'dayNames' => ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'], + 'dayNamesShort' => ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'], + 'dayNamesMin' => ['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa'], + ], + 'expectedJson' => '{"closeText":"Done","prevText":"Prev","nextText":"Next","currentText":"Today","monthNames":["January","February","March","April","May","June","July","August","September","October","November","December"],"monthNamesShort":["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],"dayNames":["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],"dayNamesShort":["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],"dayNamesMin":["Su","Mo","Tu","We","Th","Fr","Sa"]}' + ], + ]; + } } From e36b4d0f906081ba776cc15546504fef85a90b9a Mon Sep 17 00:00:00 2001 From: Vasya Tsviklinskyi <tsviklinskyi@gmail.com> Date: Mon, 5 Oct 2020 11:41:30 +0300 Subject: [PATCH 057/133] MC-35771: Datepicker/calendar control does not use the store locale --- .../Test/Unit/Block/Widget/DobTest.php | 35 ++++++++++++++++--- .../view/frontend/templates/widget/dob.phtml | 27 +++++++++----- 2 files changed, 49 insertions(+), 13 deletions(-) diff --git a/app/code/Magento/Customer/Test/Unit/Block/Widget/DobTest.php b/app/code/Magento/Customer/Test/Unit/Block/Widget/DobTest.php index 1f5a6145e0eea..020dbbbe4ff13 100644 --- a/app/code/Magento/Customer/Test/Unit/Block/Widget/DobTest.php +++ b/app/code/Magento/Customer/Test/Unit/Block/Widget/DobTest.php @@ -97,6 +97,11 @@ class DobTest extends TestCase */ private $encoder; + /** + * @var ResolverInterface + */ + private $localeResolver; + /** * @inheritDoc */ @@ -116,14 +121,15 @@ protected function setUp(): void $cache->expects($this->any())->method('getFrontend')->willReturn($frontendCache); $objectManager = new ObjectManager($this); - $localeResolver = $this->getMockForAbstractClass(ResolverInterface::class); - $localeResolver->expects($this->any()) + $this->localeResolver = $this->getMockForAbstractClass(ResolverInterface::class); + $this->localeResolver->expects($this->any()) ->method('getLocale') ->willReturnCallback( function () { return $this->_locale; } ); + $localeResolver = $this->localeResolver; $timezone = $objectManager->getObject( Timezone::class, ['localeResolver' => $localeResolver, 'dateFormatterFactory' => new DateFormatterFactory()] @@ -172,7 +178,8 @@ function () use ($timezone, $localeResolver) { $this->createMock(Date::class), $this->filterFactory, [], - $this->encoder + $this->encoder, + $this->localeResolver ); } @@ -612,13 +619,16 @@ public function testGetHtmlExtraParamsWithRequiredOption() /** * Tests getTranslatedCalendarConfigJson() * + * @param string $locale * @param array $expectedArray * @param string $expectedJson * @dataProvider getTranslatedCalendarConfigJsonDataProvider * @return void */ - public function testGetTranslatedCalendarConfigJson(array $expectedArray, string $expectedJson): void + public function testGetTranslatedCalendarConfigJson(string $locale, array $expectedArray, string $expectedJson): void { + $this->_locale = $locale; + $this->encoder->expects($this->once()) ->method('encode') ->with($expectedArray) @@ -639,6 +649,7 @@ public function getTranslatedCalendarConfigJsonDataProvider() { return [ [ + 'locale' => 'en_US', 'expectedArray' => [ 'closeText' => 'Done', 'prevText' => 'Prev', @@ -653,6 +664,22 @@ public function getTranslatedCalendarConfigJsonDataProvider() ], 'expectedJson' => '{"closeText":"Done","prevText":"Prev","nextText":"Next","currentText":"Today","monthNames":["January","February","March","April","May","June","July","August","September","October","November","December"],"monthNamesShort":["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],"dayNames":["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],"dayNamesShort":["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],"dayNamesMin":["Su","Mo","Tu","We","Th","Fr","Sa"]}' ], + [ + 'locale' => 'de_DE', + 'expectedArray' => [ + 'closeText' => 'Done', + 'prevText' => 'Prev', + 'nextText' => 'Next', + 'currentText' => 'Today', + 'monthNames' => ['Januar', 'Februar', 'März', 'April', 'Mai', 'Juni', + 'Juli', 'August', 'September', 'Oktober', 'November', 'Dezember'], + 'monthNamesShort' => ['Jan.', 'Feb.', 'März', 'Apr.', 'Mai', 'Juni', 'Juli', 'Aug.', 'Sept.', 'Okt.', 'Nov.', 'Dez.'], + 'dayNames' => ['Sonntag', 'Montag', 'Dienstag', 'Mittwoch', 'Donnerstag', 'Freitag', 'Samstag'], + 'dayNamesShort' => ['So.', 'Mo.', 'Di.', 'Mi.', 'Do.', 'Fr.', 'Sa.'], + 'dayNamesMin' => ['So.', 'Mo.', 'Di.', 'Mi.', 'Do.', 'Fr.', 'Sa.'], + ], + 'expectedJson' => '{"closeText":"Done","prevText":"Prev","nextText":"Next","currentText":"Today","monthNames":["Januar","Februar","M\u00e4rz","April","Mai","Juni","Juli","August","September","Oktober","November","Dezember"],"monthNamesShort":["Jan.","Feb.","M\u00e4rz","Apr.","Mai","Juni","Juli","Aug.","Sept.","Okt.","Nov.","Dez."],"dayNames":["Sonntag","Montag","Dienstag","Mittwoch","Donnerstag","Freitag","Samstag"],"dayNamesShort":["So.","Mo.","Di.","Mi.","Do.","Fr.","Sa."],"dayNamesMin":["So.","Mo.","Di.","Mi.","Do.","Fr.","Sa."]}' + ], ]; } } diff --git a/app/code/Magento/Customer/view/frontend/templates/widget/dob.phtml b/app/code/Magento/Customer/view/frontend/templates/widget/dob.phtml index fee577abadbf9..8a584938b1548 100644 --- a/app/code/Magento/Customer/view/frontend/templates/widget/dob.phtml +++ b/app/code/Magento/Customer/view/frontend/templates/widget/dob.phtml @@ -4,6 +4,7 @@ * See COPYING.txt for license details. */ +/** @var \Magento\Framework\View\Helper\SecureHtmlRenderer $secureRenderer */ /** @var \Magento\Customer\Block\Widget\Dob $block */ /* @@ -23,6 +24,7 @@ NOTE: Regarding styles - if we leave it this way, we'll move it to boxes.css. Al automatically using block input parameters. */ +$translatedCalendarConfigJson = $block->getTranslatedCalendarConfigJson(); $fieldCssClass = 'field date field-' . $block->getHtmlId(); $fieldCssClass .= $block->isRequired() ? ' required' : ''; ?> @@ -46,13 +48,20 @@ $fieldCssClass .= $block->isRequired() ? ' required' : ''; } </script> -<script> - require([ - 'jquery', - 'jquery-ui-modules/datepicker' - ], function ( $ ) { - $.extend(true, $, { - calendarConfig: <?= $block->escapeJs($block->getTranslatedCalendarConfigJson()); ?> - }); +<?php $scriptString = <<<script + +require([ + 'jquery', + 'jquery-ui-modules/datepicker' +], function($){ + +//<![CDATA[ + $.extend(true, $, { + calendarConfig: {$translatedCalendarConfigJson} }); -</script> +//]]> + +}); +script; +?> +<?= /* @noEscape */ $secureRenderer->renderTag('script', [], $scriptString, false) ?> From 8edca5fb3f6d49914dd7f11a9d418b2c2864bbe9 Mon Sep 17 00:00:00 2001 From: Vasya Tsviklinskyi <tsviklinskyi@gmail.com> Date: Mon, 5 Oct 2020 13:20:14 +0300 Subject: [PATCH 058/133] MC-35771: Datepicker/calendar control does not use the store locale --- .../Customer/Test/Unit/Block/Widget/DobTest.php | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/app/code/Magento/Customer/Test/Unit/Block/Widget/DobTest.php b/app/code/Magento/Customer/Test/Unit/Block/Widget/DobTest.php index 020dbbbe4ff13..bb2f350ae60a1 100644 --- a/app/code/Magento/Customer/Test/Unit/Block/Widget/DobTest.php +++ b/app/code/Magento/Customer/Test/Unit/Block/Widget/DobTest.php @@ -625,8 +625,11 @@ public function testGetHtmlExtraParamsWithRequiredOption() * @dataProvider getTranslatedCalendarConfigJsonDataProvider * @return void */ - public function testGetTranslatedCalendarConfigJson(string $locale, array $expectedArray, string $expectedJson): void - { + public function testGetTranslatedCalendarConfigJson( + string $locale, + array $expectedArray, + string $expectedJson + ): void { $this->_locale = $locale; $this->encoder->expects($this->once()) @@ -657,12 +660,15 @@ public function getTranslatedCalendarConfigJsonDataProvider() 'currentText' => 'Today', 'monthNames' => ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'], - 'monthNamesShort' => ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'], + 'monthNamesShort' => ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', + 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'], 'dayNames' => ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'], 'dayNamesShort' => ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'], 'dayNamesMin' => ['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa'], ], + // phpcs:disable Generic.Files.LineLength.TooLong 'expectedJson' => '{"closeText":"Done","prevText":"Prev","nextText":"Next","currentText":"Today","monthNames":["January","February","March","April","May","June","July","August","September","October","November","December"],"monthNamesShort":["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],"dayNames":["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],"dayNamesShort":["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],"dayNamesMin":["Su","Mo","Tu","We","Th","Fr","Sa"]}' + // phpcs:enable Generic.Files.LineLength.TooLong ], [ 'locale' => 'de_DE', @@ -673,12 +679,15 @@ public function getTranslatedCalendarConfigJsonDataProvider() 'currentText' => 'Today', 'monthNames' => ['Januar', 'Februar', 'März', 'April', 'Mai', 'Juni', 'Juli', 'August', 'September', 'Oktober', 'November', 'Dezember'], - 'monthNamesShort' => ['Jan.', 'Feb.', 'März', 'Apr.', 'Mai', 'Juni', 'Juli', 'Aug.', 'Sept.', 'Okt.', 'Nov.', 'Dez.'], + 'monthNamesShort' => ['Jan.', 'Feb.', 'März', 'Apr.', 'Mai', 'Juni', + 'Juli', 'Aug.', 'Sept.', 'Okt.', 'Nov.', 'Dez.'], 'dayNames' => ['Sonntag', 'Montag', 'Dienstag', 'Mittwoch', 'Donnerstag', 'Freitag', 'Samstag'], 'dayNamesShort' => ['So.', 'Mo.', 'Di.', 'Mi.', 'Do.', 'Fr.', 'Sa.'], 'dayNamesMin' => ['So.', 'Mo.', 'Di.', 'Mi.', 'Do.', 'Fr.', 'Sa.'], ], + // phpcs:disable Generic.Files.LineLength.TooLong 'expectedJson' => '{"closeText":"Done","prevText":"Prev","nextText":"Next","currentText":"Today","monthNames":["Januar","Februar","M\u00e4rz","April","Mai","Juni","Juli","August","September","Oktober","November","Dezember"],"monthNamesShort":["Jan.","Feb.","M\u00e4rz","Apr.","Mai","Juni","Juli","Aug.","Sept.","Okt.","Nov.","Dez."],"dayNames":["Sonntag","Montag","Dienstag","Mittwoch","Donnerstag","Freitag","Samstag"],"dayNamesShort":["So.","Mo.","Di.","Mi.","Do.","Fr.","Sa."],"dayNamesMin":["So.","Mo.","Di.","Mi.","Do.","Fr.","Sa."]}' + // phpcs:enable Generic.Files.LineLength.TooLong ], ]; } From 3dd1fbd51cfb424d71d53f414c49bb5c587974de Mon Sep 17 00:00:00 2001 From: Serhiy Yelahin <serhiy.yelahin@transoftgroup.com> Date: Mon, 5 Oct 2020 15:35:12 +0300 Subject: [PATCH 059/133] MC-38048: Incorrect default country displayed on shipping page when store view is changed in cart. Part 2 --- ...eckoutDifferentDefaultCountryPerStoreTest.xml | 16 ++++++++++++++-- .../view/frontend/web/js/view/shipping.js | 4 +--- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/CheckoutDifferentDefaultCountryPerStoreTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/CheckoutDifferentDefaultCountryPerStoreTest.xml index c4c70cef81b0b..e6a5f37c764fe 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Test/CheckoutDifferentDefaultCountryPerStoreTest.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Test/CheckoutDifferentDefaultCountryPerStoreTest.xml @@ -39,10 +39,10 @@ </after> <!-- Open product and add product to cart--> <actionGroup ref="OpenStoreFrontProductPageActionGroup" stepKey="openProductPage"> - <argument name="productUrlKey" value="$$createProduct.custom_attributes[url_key]$$"/> + <argument name="productUrlKey" value="$createProduct.custom_attributes[url_key]$"/> </actionGroup> <actionGroup ref="StorefrontAddProductToCartActionGroup" stepKey="addProductToCart"> - <argument name="product" value="$$createProduct$$"/> + <argument name="product" value="$createProduct$"/> <argument name="productCount" value="1"/> </actionGroup> <!-- Go to cart --> @@ -59,5 +59,17 @@ <actualResult type="const">$grabCountry</actualResult> <expectedResult type="string">{{DE_Address_Berlin_Not_Default_Address.country_id}}</expectedResult> </assertEquals> + <!-- Go to cart --> + <actionGroup ref="StorefrontCartPageOpenActionGroup" stepKey="returnToCartPage"/> + <!-- Switch to default store view --> + <actionGroup ref="StorefrontSwitchDefaultStoreViewActionGroup" stepKey="switchToDefaultStoreView"/> + <!-- Go to checkout page --> + <actionGroup ref="OpenStoreFrontCheckoutShippingPageActionGroup" stepKey="proceedToCheckoutWithDefaultStore"/> + <!-- Grab country code from checkout page and assert value with default country for default store view --> + <grabValueFrom selector="{{CheckoutShippingSection.country}}" stepKey="grabDefaultStoreCountry"/> + <assertEquals stepKey="assertDefaultCountryValue"> + <actualResult type="const">$grabDefaultStoreCountry</actualResult> + <expectedResult type="string">{{US_Address_TX.country_id}}</expectedResult> + </assertEquals> </test> </tests> diff --git a/app/code/Magento/Checkout/view/frontend/web/js/view/shipping.js b/app/code/Magento/Checkout/view/frontend/web/js/view/shipping.js index 084c8ad59a8da..f6590e60a9589 100644 --- a/app/code/Magento/Checkout/view/frontend/web/js/view/shipping.js +++ b/app/code/Magento/Checkout/view/frontend/web/js/view/shipping.js @@ -121,11 +121,9 @@ define([ ); } checkoutProvider.on('shippingAddress', function (shippingAddrsData) { - //jscs:disable requireCamelCaseOrUpperCaseIdentifiers - if (shippingAddrsData.street && shippingAddrsData.street[0].length > 0) { + if (!_.isEmpty(shippingAddrsData.street[0])) { checkoutData.setShippingAddressFromData(shippingAddrsData); } - //jscs:enable requireCamelCaseOrUpperCaseIdentifiers }); shippingRatesValidator.initFields(fieldsetName); }); From b4ee601db5141316c0d268f83e95f32d4e341162 Mon Sep 17 00:00:00 2001 From: Serhiy Yelahin <serhiy.yelahin@transoftgroup.com> Date: Mon, 5 Oct 2020 17:39:47 +0300 Subject: [PATCH 060/133] MC-38048: Incorrect default country displayed on shipping page when store view is changed in cart. Part 2 --- app/code/Magento/Checkout/view/frontend/web/js/view/shipping.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/Magento/Checkout/view/frontend/web/js/view/shipping.js b/app/code/Magento/Checkout/view/frontend/web/js/view/shipping.js index f6590e60a9589..2a52b64647749 100644 --- a/app/code/Magento/Checkout/view/frontend/web/js/view/shipping.js +++ b/app/code/Magento/Checkout/view/frontend/web/js/view/shipping.js @@ -121,7 +121,7 @@ define([ ); } checkoutProvider.on('shippingAddress', function (shippingAddrsData) { - if (!_.isEmpty(shippingAddrsData.street[0])) { + if (shippingAddrsData.street && !_.isEmpty(shippingAddrsData.street[0])) { checkoutData.setShippingAddressFromData(shippingAddrsData); } }); From e7cc0e39e8dea0981c5f219cbdc7df25755b7d03 Mon Sep 17 00:00:00 2001 From: Vasya Tsviklinskyi <tsviklinskyi@gmail.com> Date: Tue, 6 Oct 2020 10:43:01 +0300 Subject: [PATCH 061/133] MC-35771: Datepicker/calendar control does not use the store locale --- .../Magento/Customer/view/frontend/templates/widget/dob.phtml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/code/Magento/Customer/view/frontend/templates/widget/dob.phtml b/app/code/Magento/Customer/view/frontend/templates/widget/dob.phtml index 8a584938b1548..da1c85cce9856 100644 --- a/app/code/Magento/Customer/view/frontend/templates/widget/dob.phtml +++ b/app/code/Magento/Customer/view/frontend/templates/widget/dob.phtml @@ -48,7 +48,7 @@ $fieldCssClass .= $block->isRequired() ? ' required' : ''; } </script> -<?php $scriptString = <<<script +<?php $scriptString = <<<code require([ 'jquery', @@ -62,6 +62,6 @@ require([ //]]> }); -script; +code; ?> <?= /* @noEscape */ $secureRenderer->renderTag('script', [], $scriptString, false) ?> From 0e3b1dc678b071767857eecb7d18695c0d7cc11b Mon Sep 17 00:00:00 2001 From: Serhiy Yelahin <serhiy.yelahin@transoftgroup.com> Date: Tue, 6 Oct 2020 11:48:35 +0300 Subject: [PATCH 062/133] MC-38197: [Magento Cloud] Error when updating carts - fatal error Call to a member function getValue() on null in module-configurable-product CartItemProcessor.php --- .../Model/Quote/Item/CartItemProcessor.php | 45 ++++++++++--------- .../Quote/Item/CartItemProcessorTest.php | 23 +++++++++- 2 files changed, 46 insertions(+), 22 deletions(-) diff --git a/app/code/Magento/ConfigurableProduct/Model/Quote/Item/CartItemProcessor.php b/app/code/Magento/ConfigurableProduct/Model/Quote/Item/CartItemProcessor.php index 56993ecec1fbf..814c1e971ea33 100644 --- a/app/code/Magento/ConfigurableProduct/Model/Quote/Item/CartItemProcessor.php +++ b/app/code/Magento/ConfigurableProduct/Model/Quote/Item/CartItemProcessor.php @@ -82,31 +82,34 @@ public function convertToBuyRequest(CartItemInterface $cartItem) */ public function processOptions(CartItemInterface $cartItem) { - $attributesOption = $cartItem->getProduct()->getCustomOption('attributes'); - $selectedConfigurableOptions = $this->serializer->unserialize($attributesOption->getValue()); + $attributesOption = $cartItem->getProduct() + ->getCustomOption('attributes'); + if ($attributesOption) { + $selectedConfigurableOptions = $this->serializer->unserialize($attributesOption->getValue()); - if (is_array($selectedConfigurableOptions)) { - $configurableOptions = []; - foreach ($selectedConfigurableOptions as $optionId => $optionValue) { - /** @var \Magento\ConfigurableProduct\Api\Data\ConfigurableItemOptionValueInterface $option */ - $option = $this->itemOptionValueFactory->create(); - $option->setOptionId($optionId); - $option->setOptionValue($optionValue); - $configurableOptions[] = $option; - } + if (is_array($selectedConfigurableOptions)) { + $configurableOptions = []; + foreach ($selectedConfigurableOptions as $optionId => $optionValue) { + /** @var \Magento\ConfigurableProduct\Api\Data\ConfigurableItemOptionValueInterface $option */ + $option = $this->itemOptionValueFactory->create(); + $option->setOptionId($optionId); + $option->setOptionValue($optionValue); + $configurableOptions[] = $option; + } - $productOption = $cartItem->getProductOption() - ? $cartItem->getProductOption() - : $this->productOptionFactory->create(); + $productOption = $cartItem->getProductOption() + ? $cartItem->getProductOption() + : $this->productOptionFactory->create(); - /** @var \Magento\Quote\Api\Data\ProductOptionExtensionInterface $extensibleAttribute */ - $extensibleAttribute = $productOption->getExtensionAttributes() - ? $productOption->getExtensionAttributes() - : $this->extensionFactory->create(); + /** @var \Magento\Quote\Api\Data\ProductOptionExtensionInterface $extensibleAttribute */ + $extensibleAttribute = $productOption->getExtensionAttributes() + ? $productOption->getExtensionAttributes() + : $this->extensionFactory->create(); - $extensibleAttribute->setConfigurableItemOptions($configurableOptions); - $productOption->setExtensionAttributes($extensibleAttribute); - $cartItem->setProductOption($productOption); + $extensibleAttribute->setConfigurableItemOptions($configurableOptions); + $productOption->setExtensionAttributes($extensibleAttribute); + $cartItem->setProductOption($productOption); + } } return $cartItem; } diff --git a/app/code/Magento/ConfigurableProduct/Test/Unit/Model/Quote/Item/CartItemProcessorTest.php b/app/code/Magento/ConfigurableProduct/Test/Unit/Model/Quote/Item/CartItemProcessorTest.php index 10f5b1cbb344a..cd68e1dcfce24 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Unit/Model/Quote/Item/CartItemProcessorTest.php +++ b/app/code/Magento/ConfigurableProduct/Test/Unit/Model/Quote/Item/CartItemProcessorTest.php @@ -59,7 +59,7 @@ class CartItemProcessorTest extends TestCase */ private $productOptionExtensionAttributes; - /** @var \PHPUnit\Framework\MockObject\MockObject */ + /** @var MockObject */ private $serializer; protected function setUp(): void @@ -263,4 +263,25 @@ public function testProcessProductOptionsIfOptionsExist() $this->assertEquals($cartItemMock, $this->model->processOptions($cartItemMock)); } + + /** + * Checks processOptions method with the empty custom option + * + * @return void + */ + public function testProcessProductWithEmptyOption(): void + { + $customOption = $this->createMock(Option::class); + $productMock = $this->createMock(Product::class); + $productMock->method('getCustomOption') + ->with('attributes') + ->willReturn(null); + $customOption->expects($this->never()) + ->method('getValue'); + $cartItemMock = $this->createPartialMock(Item::class, ['getProduct']); + $cartItemMock->expects($this->once()) + ->method('getProduct') + ->willReturn($productMock); + $this->assertEquals($cartItemMock, $this->model->processOptions($cartItemMock)); + } } From fc8590177de2a810f9864995e7292c7c5e367481 Mon Sep 17 00:00:00 2001 From: Serhiy Yelahin <serhiy.yelahin@transoftgroup.com> Date: Tue, 6 Oct 2020 16:27:37 +0300 Subject: [PATCH 063/133] MC-38197: [Magento Cloud] Error when updating carts - fatal error Call to a member function getValue() on null in module-configurable-product CartItemProcessor.php --- .../Model/Quote/Item/CartItemProcessor.php | 50 ++++++++++--------- 1 file changed, 27 insertions(+), 23 deletions(-) diff --git a/app/code/Magento/ConfigurableProduct/Model/Quote/Item/CartItemProcessor.php b/app/code/Magento/ConfigurableProduct/Model/Quote/Item/CartItemProcessor.php index 814c1e971ea33..75592efc52dca 100644 --- a/app/code/Magento/ConfigurableProduct/Model/Quote/Item/CartItemProcessor.php +++ b/app/code/Magento/ConfigurableProduct/Model/Quote/Item/CartItemProcessor.php @@ -5,6 +5,8 @@ */ namespace Magento\ConfigurableProduct\Model\Quote\Item; +use Magento\ConfigurableProduct\Api\Data\ConfigurableItemOptionValueInterface; +use Magento\Quote\Api\Data\ProductOptionExtensionInterface; use Magento\Quote\Model\Quote\Item\CartItemProcessorInterface; use Magento\Quote\Api\Data\CartItemInterface; use Magento\Framework\Serialize\Serializer\Json; @@ -64,7 +66,7 @@ public function __construct( public function convertToBuyRequest(CartItemInterface $cartItem) { if ($cartItem->getProductOption() && $cartItem->getProductOption()->getExtensionAttributes()) { - /** @var \Magento\ConfigurableProduct\Api\Data\ConfigurableItemOptionValueInterface $options */ + /** @var ConfigurableItemOptionValueInterface $options */ $options = $cartItem->getProductOption()->getExtensionAttributes()->getConfigurableItemOptions(); if (is_array($options)) { $requestData = []; @@ -84,33 +86,35 @@ public function processOptions(CartItemInterface $cartItem) { $attributesOption = $cartItem->getProduct() ->getCustomOption('attributes'); - if ($attributesOption) { - $selectedConfigurableOptions = $this->serializer->unserialize($attributesOption->getValue()); + if (!$attributesOption) { + return $cartItem; + } + $selectedConfigurableOptions = $this->serializer->unserialize($attributesOption->getValue()); - if (is_array($selectedConfigurableOptions)) { - $configurableOptions = []; - foreach ($selectedConfigurableOptions as $optionId => $optionValue) { - /** @var \Magento\ConfigurableProduct\Api\Data\ConfigurableItemOptionValueInterface $option */ - $option = $this->itemOptionValueFactory->create(); - $option->setOptionId($optionId); - $option->setOptionValue($optionValue); - $configurableOptions[] = $option; - } + if (is_array($selectedConfigurableOptions)) { + $configurableOptions = []; + foreach ($selectedConfigurableOptions as $optionId => $optionValue) { + /** @var ConfigurableItemOptionValueInterface $option */ + $option = $this->itemOptionValueFactory->create(); + $option->setOptionId($optionId); + $option->setOptionValue($optionValue); + $configurableOptions[] = $option; + } - $productOption = $cartItem->getProductOption() - ? $cartItem->getProductOption() - : $this->productOptionFactory->create(); + $productOption = $cartItem->getProductOption() + ? $cartItem->getProductOption() + : $this->productOptionFactory->create(); - /** @var \Magento\Quote\Api\Data\ProductOptionExtensionInterface $extensibleAttribute */ - $extensibleAttribute = $productOption->getExtensionAttributes() - ? $productOption->getExtensionAttributes() - : $this->extensionFactory->create(); + /** @var ProductOptionExtensionInterface $extensibleAttribute */ + $extensibleAttribute = $productOption->getExtensionAttributes() + ? $productOption->getExtensionAttributes() + : $this->extensionFactory->create(); - $extensibleAttribute->setConfigurableItemOptions($configurableOptions); - $productOption->setExtensionAttributes($extensibleAttribute); - $cartItem->setProductOption($productOption); - } + $extensibleAttribute->setConfigurableItemOptions($configurableOptions); + $productOption->setExtensionAttributes($extensibleAttribute); + $cartItem->setProductOption($productOption); } + return $cartItem; } } From ccc83b21a25aee282be830891b5708017e992211 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Szubert?= <bartlomiejszubert@gmail.com> Date: Wed, 7 Oct 2020 10:03:28 +0200 Subject: [PATCH 064/133] Fix #30296 - Wrong ip value in sendfriend_log table --- .../Model/ResourceModel/SendFriend.php | 10 +++--- .../SendFriend/Model/SendFriendTest.php | 9 +++-- .../HTTP/PhpEnvironment/RemoteAddress.php | 10 +++--- .../Unit/PhpEnvironment/RemoteAddressTest.php | 33 +++++++------------ 4 files changed, 27 insertions(+), 35 deletions(-) diff --git a/app/code/Magento/SendFriend/Model/ResourceModel/SendFriend.php b/app/code/Magento/SendFriend/Model/ResourceModel/SendFriend.php index 618d941f7047e..468fb2ed1af66 100644 --- a/app/code/Magento/SendFriend/Model/ResourceModel/SendFriend.php +++ b/app/code/Magento/SendFriend/Model/ResourceModel/SendFriend.php @@ -6,10 +6,6 @@ namespace Magento\SendFriend\Model\ResourceModel; /** - * SendFriend Log Resource Model - * - * @author Magento Core Team <core@magentocommerce.com> - * * @api * @since 100.0.2 */ @@ -32,6 +28,7 @@ protected function _construct() * @param int $ip * @param int $startTime * @param int $websiteId + * * @return int * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ @@ -58,14 +55,16 @@ public function getSendCount($object, $ip, $startTime, $websiteId = null) * @param int $ip * @param int $startTime * @param int $websiteId + * * @return $this */ public function addSendItem($ip, $startTime, $websiteId) { $this->getConnection()->insert( $this->getMainTable(), - ['ip' => ip2long($ip), 'time' => $startTime, 'website_id' => $websiteId] + ['ip' => $ip, 'time' => $startTime, 'website_id' => $websiteId] ); + return $this; } @@ -73,6 +72,7 @@ public function addSendItem($ip, $startTime, $websiteId) * Delete Old logs * * @param int $time + * * @return $this */ public function deleteLogsBefore($time) diff --git a/dev/tests/integration/testsuite/Magento/SendFriend/Model/SendFriendTest.php b/dev/tests/integration/testsuite/Magento/SendFriend/Model/SendFriendTest.php index 7013346fd76e2..9117f088e6b8d 100644 --- a/dev/tests/integration/testsuite/Magento/SendFriend/Model/SendFriendTest.php +++ b/dev/tests/integration/testsuite/Magento/SendFriend/Model/SendFriendTest.php @@ -13,7 +13,7 @@ use Magento\SendFriend\Helper\Data as SendFriendHelper; use Magento\TestFramework\Helper\Bootstrap; use PHPUnit\Framework\TestCase; -use Zend\Stdlib\Parameters; +use Laminas\Stdlib\Parameters; /** * Class checks send friend model behavior @@ -55,6 +55,7 @@ protected function setUp(): void * @param array $sender * @param array $recipients * @param string|bool $expectedResult + * * @return void */ public function testValidate(array $sender, array $recipients, $expectedResult): void @@ -185,11 +186,11 @@ public function testisExceedLimitByCookies(): void * @magentoDataFixture Magento/SendFriend/_files/sendfriend_log_record_half_hour_before.php * * @magentoDbIsolation disabled + * * @return void */ public function testisExceedLimitByIp(): void { - $this->markTestSkipped('Blocked by MC-31968'); $parameters = $this->objectManager->create(Parameters::class); $parameters->set('REMOTE_ADDR', '127.0.0.1'); $this->request->setServer($parameters); @@ -197,10 +198,11 @@ public function testisExceedLimitByIp(): void } /** - * Check result + * Check test result * * @param array|bool $expectedResult * @param array|bool $result + * * @return void */ private function checkResult($expectedResult, $result): void @@ -217,6 +219,7 @@ private function checkResult($expectedResult, $result): void * * @param array $sender * @param array $recipients + * * @return void */ private function prepareData(array $sender, array $recipients): void diff --git a/lib/internal/Magento/Framework/HTTP/PhpEnvironment/RemoteAddress.php b/lib/internal/Magento/Framework/HTTP/PhpEnvironment/RemoteAddress.php index dfe4b759e85be..c505c82789f81 100644 --- a/lib/internal/Magento/Framework/HTTP/PhpEnvironment/RemoteAddress.php +++ b/lib/internal/Magento/Framework/HTTP/PhpEnvironment/RemoteAddress.php @@ -120,7 +120,7 @@ function (string $ip) { public function getRemoteAddress(bool $ipToLong = false) { if ($this->remoteAddress !== null) { - return $this->remoteAddress; + return $ipToLong ? ip2long($this->remoteAddress) : $this->remoteAddress; } $remoteAddress = $this->readAddress(); @@ -135,11 +135,11 @@ public function getRemoteAddress(bool $ipToLong = false) $this->remoteAddress = false; return false; - } else { - $this->remoteAddress = $remoteAddress; - - return $ipToLong ? ip2long($this->remoteAddress) : $this->remoteAddress; } + + $this->remoteAddress = $remoteAddress; + + return $ipToLong ? ip2long($this->remoteAddress) : $this->remoteAddress; } /** diff --git a/lib/internal/Magento/Framework/HTTP/Test/Unit/PhpEnvironment/RemoteAddressTest.php b/lib/internal/Magento/Framework/HTTP/Test/Unit/PhpEnvironment/RemoteAddressTest.php index 25f665ed70e84..20aafb797ce0e 100644 --- a/lib/internal/Magento/Framework/HTTP/Test/Unit/PhpEnvironment/RemoteAddressTest.php +++ b/lib/internal/Magento/Framework/HTTP/Test/Unit/PhpEnvironment/RemoteAddressTest.php @@ -9,13 +9,10 @@ use Magento\Framework\App\Request\Http as HttpRequest; use Magento\Framework\HTTP\PhpEnvironment\RemoteAddress; -use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; /** - * Test for - * * @see RemoteAddress */ class RemoteAddressTest extends TestCase @@ -23,24 +20,17 @@ class RemoteAddressTest extends TestCase /** * @var MockObject|HttpRequest */ - protected $_request; - - /** - * @var ObjectManager - */ - protected $_objectManager; + private $requestMock; /** * @inheritdoc */ protected function setUp(): void { - $this->_request = $this->getMockBuilder(HttpRequest::class) + $this->requestMock = $this->getMockBuilder(HttpRequest::class) ->disableOriginalConstructor() - ->setMethods(['getServer']) + ->onlyMethods(['getServer']) ->getMock(); - - $this->_objectManager = new ObjectManager($this); } /** @@ -49,6 +39,7 @@ protected function setUp(): void * @param string|bool $expected * @param bool $ipToLong * @param string[]|null $trustedProxies + * * @return void * @dataProvider getRemoteAddressProvider */ @@ -59,18 +50,16 @@ public function testGetRemoteAddress( bool $ipToLong, array $trustedProxies = null ): void { - $remoteAddress = $this->_objectManager->getObject( - RemoteAddress::class, - [ - 'httpRequest' => $this->_request, - 'alternativeHeaders' => $alternativeHeaders, - 'trustedProxies' => $trustedProxies, - ] + $remoteAddress = new RemoteAddress( + $this->requestMock, + $alternativeHeaders, + $trustedProxies ); - $this->_request->expects($this->any()) - ->method('getServer') + $this->requestMock->method('getServer') ->willReturnMap($serverValueMap); + // Check twice to verify if internal variable is cached correctly + $this->assertEquals($expected, $remoteAddress->getRemoteAddress($ipToLong)); $this->assertEquals($expected, $remoteAddress->getRemoteAddress($ipToLong)); } 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 065/133] 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 d1aaa1e7bed1ad20262075a1d06322ca57bd82eb Mon Sep 17 00:00:00 2001 From: Vasya Tsviklinskyi <tsviklinskyi@gmail.com> Date: Wed, 7 Oct 2020 14:31:50 +0300 Subject: [PATCH 066/133] MC-38031: Checkout with Multiple Addresses - Review Page does not follow the configured total sort order --- .../Multishipping/Block/Checkout/Overview.php | 37 +++++++++++++- .../Test/Unit/Block/Checkout/OverviewTest.php | 50 ++++++++++++++++++- 2 files changed, 85 insertions(+), 2 deletions(-) diff --git a/app/code/Magento/Multishipping/Block/Checkout/Overview.php b/app/code/Magento/Multishipping/Block/Checkout/Overview.php index 1ea2dc2618778..ef6dae2182f10 100644 --- a/app/code/Magento/Multishipping/Block/Checkout/Overview.php +++ b/app/code/Magento/Multishipping/Block/Checkout/Overview.php @@ -10,6 +10,8 @@ use Magento\Quote\Model\Quote\Address; use Magento\Checkout\Helper\Data as CheckoutHelper; use Magento\Framework\App\ObjectManager; +use Magento\Quote\Model\Quote\Address\Total\Collector; +use Magento\Store\Model\ScopeInterface; /** * Multishipping checkout overview information @@ -430,7 +432,7 @@ public function getBillingAddressTotals() public function renderTotals($totals, $colspan = null) { //check if the shipment is multi shipment - $totals = $this->getMultishippingTotals($totals); + $totals = $this->sortTotals($this->getMultishippingTotals($totals)); if ($colspan === null) { $colspan = 3; @@ -481,4 +483,37 @@ protected function _getRowItemRenderer($type) } return $renderer; } + + /** + * Sort total information based on configuration settings. + * + * @param array $totals + * @return array + */ + private function sortTotals($totals): array + { + $sortedTotals = []; + $sorts = $this->_scopeConfig->getValue( + Collector::XML_PATH_SALES_TOTALS_SORT, + ScopeInterface::SCOPE_STORES + ); + + foreach ($sorts as $code => $sortOrder) { + $sorted[$sortOrder] = $code; + } + ksort($sorted); + + foreach ($sorted as $code) { + if (isset($totals[$code])) { + $sortedTotals[$code] = $totals[$code]; + } + } + + $notSorted = array_diff(array_keys($totals), array_keys($sortedTotals)); + foreach ($notSorted as $code) { + $sortedTotals[$code] = $totals[$code]; + } + + return $sortedTotals; + } } diff --git a/app/code/Magento/Multishipping/Test/Unit/Block/Checkout/OverviewTest.php b/app/code/Magento/Multishipping/Test/Unit/Block/Checkout/OverviewTest.php index 7da77030f308a..2d044afd32c70 100644 --- a/app/code/Magento/Multishipping/Test/Unit/Block/Checkout/OverviewTest.php +++ b/app/code/Magento/Multishipping/Test/Unit/Block/Checkout/OverviewTest.php @@ -8,6 +8,7 @@ namespace Magento\Multishipping\Test\Unit\Block\Checkout; +use Magento\Framework\App\Config\ScopeConfigInterface; use Magento\Framework\Pricing\PriceCurrencyInterface; use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; use Magento\Framework\UrlInterface; @@ -67,6 +68,11 @@ class OverviewTest extends TestCase */ private $urlBuilderMock; + /** + * @var MockObject + */ + private $scopeConfigMock; + protected function setUp(): void { $objectManager = new ObjectManager($this); @@ -85,6 +91,7 @@ protected function setUp(): void $this->createMock(Multishipping::class); $this->quoteMock = $this->createMock(Quote::class); $this->urlBuilderMock = $this->getMockForAbstractClass(UrlInterface::class); + $this->scopeConfigMock = $this->getMockForAbstractClass(ScopeConfigInterface::class); $this->model = $objectManager->getObject( Overview::class, [ @@ -92,7 +99,8 @@ protected function setUp(): void 'totalsCollector' => $this->totalsCollectorMock, 'totalsReader' => $this->totalsReaderMock, 'multishipping' => $this->checkoutMock, - 'urlBuilder' => $this->urlBuilderMock + 'urlBuilder' => $this->urlBuilderMock, + '_scopeConfig' => $this->scopeConfigMock ] ); } @@ -187,4 +195,44 @@ public function testGetVirtualProductEditUrl() $this->urlBuilderMock->expects($this->once())->method('getUrl')->with('checkout/cart', [])->willReturn($url); $this->assertEquals($url, $this->model->getVirtualProductEditUrl()); } + + /** + * Test sort total information + * + * @return void + */ + public function testSortCollectors(): void + { + $sorts = [ + 'discount' => 40, + 'subtotal' => 10, + 'tax' => 20, + 'shipping' => 30, + ]; + + $this->scopeConfigMock->method('getValue') + ->with('sales/totals_sort', 'stores') + ->willReturn($sorts); + + $totalsNotSorted = [ + 'subtotal' => [], + 'shipping' => [], + 'tax' => [], + ]; + + $totalsExpected = [ + 'subtotal' => [], + 'tax' => [], + 'shipping' => [], + ]; + + $method = new \ReflectionMethod($this->model, 'sortTotals'); + $method->setAccessible(true); + $result = $method->invoke($this->model, $totalsNotSorted); + + $this->assertEquals( + $totalsExpected, + $result + ); + } } From 73c6045330654bbf075538a8ea1ca341ad56f5f8 Mon Sep 17 00:00:00 2001 From: Vasya Tsviklinskyi <tsviklinskyi@gmail.com> Date: Wed, 7 Oct 2020 15:14:48 +0300 Subject: [PATCH 067/133] MC-38031: Checkout with Multiple Addresses - Review Page does not follow the configured total sort order --- app/code/Magento/Multishipping/Block/Checkout/Overview.php | 1 + 1 file changed, 1 insertion(+) diff --git a/app/code/Magento/Multishipping/Block/Checkout/Overview.php b/app/code/Magento/Multishipping/Block/Checkout/Overview.php index ef6dae2182f10..3d6d4b195050d 100644 --- a/app/code/Magento/Multishipping/Block/Checkout/Overview.php +++ b/app/code/Magento/Multishipping/Block/Checkout/Overview.php @@ -498,6 +498,7 @@ private function sortTotals($totals): array ScopeInterface::SCOPE_STORES ); + $sorted = []; foreach ($sorts as $code => $sortOrder) { $sorted[$sortOrder] = $code; } From 88f63a6cc4d40e89bcbaabfe27f883981222d66b Mon Sep 17 00:00:00 2001 From: Vadim Malesh <51680850+engcom-Charlie@users.noreply.github.com> Date: Wed, 7 Oct 2020 16:29:02 +0300 Subject: [PATCH 068/133] add testCaseId --- .../SalesRuleDiscountIsAppliedOnPackageValueForTableRateTest.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/app/code/Magento/OfflineShipping/Test/Mftf/Test/SalesRuleDiscountIsAppliedOnPackageValueForTableRateTest.xml b/app/code/Magento/OfflineShipping/Test/Mftf/Test/SalesRuleDiscountIsAppliedOnPackageValueForTableRateTest.xml index 52d456bc6225d..d225e5fa28f97 100644 --- a/app/code/Magento/OfflineShipping/Test/Mftf/Test/SalesRuleDiscountIsAppliedOnPackageValueForTableRateTest.xml +++ b/app/code/Magento/OfflineShipping/Test/Mftf/Test/SalesRuleDiscountIsAppliedOnPackageValueForTableRateTest.xml @@ -13,6 +13,7 @@ <title value="SalesRule Discount Is Applied On PackageValue For TableRate"/> <description value="SalesRule Discount Is Applied On PackageValue For TableRate"/> <severity value="AVERAGE"/> + <testCaseId value="MC-38271"/> <group value="shipping"/> </annotations> <before> From ae19b0ddc8d05dc788ccb65c60b7518d48991978 Mon Sep 17 00:00:00 2001 From: Vasya Tsviklinskyi <tsviklinskyi@gmail.com> Date: Wed, 7 Oct 2020 20:41:01 +0300 Subject: [PATCH 069/133] MC-38031: Checkout with Multiple Addresses - Review Page does not follow the configured total sort order --- app/code/Magento/Multishipping/Block/Checkout/Overview.php | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/app/code/Magento/Multishipping/Block/Checkout/Overview.php b/app/code/Magento/Multishipping/Block/Checkout/Overview.php index 3d6d4b195050d..942741e9f7975 100644 --- a/app/code/Magento/Multishipping/Block/Checkout/Overview.php +++ b/app/code/Magento/Multishipping/Block/Checkout/Overview.php @@ -431,8 +431,11 @@ public function getBillingAddressTotals() */ public function renderTotals($totals, $colspan = null) { - //check if the shipment is multi shipment - $totals = $this->sortTotals($this->getMultishippingTotals($totals)); + // check if the shipment is multi shipment + $totals = $this->getMultishippingTotals($totals); + + // sort totals by configuration settings + $totals = $this->sortTotals($totals); if ($colspan === null) { $colspan = 3; From bc53649c4aaa7cf51ab32dc7fa1000c79263d4ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Szubert?= <bartlomiejszubert@gmail.com> Date: Wed, 7 Oct 2020 23:05:07 +0200 Subject: [PATCH 070/133] Fix #30296 - fix retrieving send count by ip, add verification of ip conversion into int to integration test --- .../Model/ResourceModel/SendFriend.php | 2 +- .../Magento/SendFriend/Model/SendFriendTest.php | 17 ++++++++++++++++- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/app/code/Magento/SendFriend/Model/ResourceModel/SendFriend.php b/app/code/Magento/SendFriend/Model/ResourceModel/SendFriend.php index 468fb2ed1af66..edb572dfdd4d1 100644 --- a/app/code/Magento/SendFriend/Model/ResourceModel/SendFriend.php +++ b/app/code/Magento/SendFriend/Model/ResourceModel/SendFriend.php @@ -43,7 +43,7 @@ public function getSendCount($object, $ip, $startTime, $websiteId = null) AND time>=:time AND website_id=:website_id' ); - $bind = ['ip' => ip2long($ip), 'time' => $startTime, 'website_id' => (int)$websiteId]; + $bind = ['ip' => $ip, 'time' => $startTime, 'website_id' => (int)$websiteId]; $row = $connection->fetchRow($select, $bind); return $row['count']; diff --git a/dev/tests/integration/testsuite/Magento/SendFriend/Model/SendFriendTest.php b/dev/tests/integration/testsuite/Magento/SendFriend/Model/SendFriendTest.php index 9117f088e6b8d..6098883959dd3 100644 --- a/dev/tests/integration/testsuite/Magento/SendFriend/Model/SendFriendTest.php +++ b/dev/tests/integration/testsuite/Magento/SendFriend/Model/SendFriendTest.php @@ -28,6 +28,9 @@ class SendFriendTest extends TestCase /** @var SendFriend */ private $sendFriend; + /** @var ResourceModel\SendFriend */ + private $sendFriendResource; + /** @var CookieManagerInterface */ private $cookieManager; @@ -43,6 +46,7 @@ protected function setUp(): void $this->objectManager = Bootstrap::getObjectManager(); $this->sendFriend = $this->objectManager->get(SendFriendFactory::class)->create(); + $this->sendFriendResource = $this->objectManager->get(ResourceModel\SendFriend::class); $this->cookieManager = $this->objectManager->get(CookieManagerInterface::class); $this->request = $this->objectManager->get(RequestInterface::class); } @@ -191,10 +195,21 @@ public function testisExceedLimitByCookies(): void */ public function testisExceedLimitByIp(): void { + $remoteAddr = '127.0.0.1'; $parameters = $this->objectManager->create(Parameters::class); - $parameters->set('REMOTE_ADDR', '127.0.0.1'); + $parameters->set('REMOTE_ADDR', $remoteAddr); $this->request->setServer($parameters); $this->assertTrue($this->sendFriend->isExceedLimit()); + // Verify that ip is saved correctly as integer value + $this->assertEquals( + 1, + (int)$this->sendFriendResource->getSendCount( + null, + ip2long($remoteAddr), + time() - (60 * 60 * 24 * 365), + 1 + ) + ); } /** From 61cb299119dacdb5f5553cb1b46f038ca32a39fa Mon Sep 17 00:00:00 2001 From: Vasya Tsviklinskyi <tsviklinskyi@gmail.com> Date: Thu, 8 Oct 2020 12:59:33 +0300 Subject: [PATCH 071/133] MC-36830: [Issue] Fix for empty category field values in REST calls --- .../Category/Product/Plugin/TableResolver.php | 5 +- .../Product/Plugin/TableResolverTest.php | 77 +++++++++++++++++++ .../Unit/Model/ResourceModel/IndexTest.php | 42 ++++++++++ .../ScopeResolver/IndexScopeResolver.php | 3 + 4 files changed, 126 insertions(+), 1 deletion(-) create mode 100644 app/code/Magento/Catalog/Test/Unit/Model/Indexer/Category/Product/Plugin/TableResolverTest.php diff --git a/app/code/Magento/Catalog/Model/Indexer/Category/Product/Plugin/TableResolver.php b/app/code/Magento/Catalog/Model/Indexer/Category/Product/Plugin/TableResolver.php index 936e6163cbcc5..0c0c72b0322dc 100644 --- a/app/code/Magento/Catalog/Model/Indexer/Category/Product/Plugin/TableResolver.php +++ b/app/code/Magento/Catalog/Model/Indexer/Category/Product/Plugin/TableResolver.php @@ -55,7 +55,10 @@ public function afterGetTableName( string $result, $modelEntity ) { - if (!is_array($modelEntity) && $modelEntity === AbstractAction::MAIN_INDEX_TABLE) { + if (!is_array($modelEntity) && + $modelEntity === AbstractAction::MAIN_INDEX_TABLE && + $this->storeManager->getStore()->getId() + ) { $catalogCategoryProductDimension = new Dimension( \Magento\Store\Model\Store::ENTITY, $this->storeManager->getStore()->getId() diff --git a/app/code/Magento/Catalog/Test/Unit/Model/Indexer/Category/Product/Plugin/TableResolverTest.php b/app/code/Magento/Catalog/Test/Unit/Model/Indexer/Category/Product/Plugin/TableResolverTest.php new file mode 100644 index 0000000000000..c5018f1aa6313 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Unit/Model/Indexer/Category/Product/Plugin/TableResolverTest.php @@ -0,0 +1,77 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Catalog\Test\Unit\Model\Indexer\Category\Product\Plugin; + +use Magento\Catalog\Model\Indexer\Category\Product\Plugin\TableResolver; +use Magento\Framework\App\ResourceConnection; +use Magento\Framework\Indexer\ScopeResolver\IndexScopeResolver; +use Magento\Store\Model\Store; +use Magento\Store\Model\StoreManagerInterface; +use PHPUnit\Framework\TestCase; + +class TableResolverTest extends TestCase +{ + /** + * Tests replacing catalog_category_product_index table name + * + * @param int $storeId + * @param string $tableName + * @param string $expected + * @dataProvider afterGetTableNameDataProvider + */ + public function testAfterGetTableName(int $storeId, string $tableName, string $expected): void + { + $storeManagerMock = $this->getMockForAbstractClass(StoreManagerInterface::class); + + $storeMock = $this->getMockBuilder(Store::class) + ->onlyMethods(['getId']) + ->disableOriginalConstructor() + ->getMock(); + $storeMock->method('getId') + ->willReturn($storeId); + + $storeManagerMock->method('getStore')->willReturn($storeMock); + + $tableResolverMock = $this->getMockBuilder(IndexScopeResolver::class) + ->disableOriginalConstructor() + ->getMock(); + $tableResolverMock->method('resolve')->willReturn('catalog_category_product_index_store1'); + + $subjectMock = $this->getMockBuilder(ResourceConnection::class) + ->disableOriginalConstructor() + ->getMock(); + + $model = new TableResolver($storeManagerMock, $tableResolverMock); + + $this->assertEquals( + $expected, + $model->afterGetTableName($subjectMock, $tableName, 'catalog_category_product_index') + ); + } + + /** + * Data provider for testAfterGetTableName + * + * @return array + */ + public function afterGetTableNameDataProvider(): array + { + return [ + [ + 'storeId' => 1, + 'tableName' => 'catalog_category_product_index', + 'expected' => 'catalog_category_product_index_store1' + ], + [ + 'storeId' => 0, + 'tableName' => 'catalog_category_product_index', + 'expected' => 'catalog_category_product_index' + ], + ]; + } +} diff --git a/app/code/Magento/Elasticsearch/Test/Unit/Model/ResourceModel/IndexTest.php b/app/code/Magento/Elasticsearch/Test/Unit/Model/ResourceModel/IndexTest.php index d6f5fb9368378..5aca7e6c2555b 100644 --- a/app/code/Magento/Elasticsearch/Test/Unit/Model/ResourceModel/IndexTest.php +++ b/app/code/Magento/Elasticsearch/Test/Unit/Model/ResourceModel/IndexTest.php @@ -417,6 +417,48 @@ public function testGetCategoryProductIndexData() ); } + /** + * Test getCategoryProductIndexData method for all stores + */ + public function testGetCategoryProductIndexDataForAllStores() + { + $connection = $this->connection; + $select = $this->select; + + $connection->expects($this->any()) + ->method('select') + ->willReturn($select); + + $select->expects($this->any()) + ->method('from') + ->with( + ['catalog_category_product_index'], + ['category_id', 'product_id', 'position', 'store_id'] + )->willReturnSelf(); + + $select->expects($this->any()) + ->method('where') + ->willReturnSelf(); + + $connection->expects($this->once()) + ->method('fetchAll') + ->with($select) + ->willReturn([[ + 'product_id' => 1, + 'category_id' => 1, + 'position' => 1, + ]]); + + $this->assertEquals( + [ + 1 => [ + 1 => 1, + ], + ], + $this->model->getCategoryProductIndexData(0, [1]) + ); + } + /** * Test getMovedCategoryProductIds method */ diff --git a/lib/internal/Magento/Framework/Indexer/ScopeResolver/IndexScopeResolver.php b/lib/internal/Magento/Framework/Indexer/ScopeResolver/IndexScopeResolver.php index a68de6ad36f9a..164d2da3d9123 100644 --- a/lib/internal/Magento/Framework/Indexer/ScopeResolver/IndexScopeResolver.php +++ b/lib/internal/Magento/Framework/Indexer/ScopeResolver/IndexScopeResolver.php @@ -44,6 +44,9 @@ public function resolve($index, array $dimensions) { $tableNameParts = []; foreach ($dimensions as $dimension) { + if (!$dimension->getValue()) { + continue; + } switch ($dimension->getName()) { case 'scope': $tableNameParts[$dimension->getName()] = $dimension->getName() . $this->getScopeId($dimension); From 4a59b3e205907feab9957450f5dc3fd972d396c0 Mon Sep 17 00:00:00 2001 From: Vasya Tsviklinskyi <tsviklinskyi@gmail.com> Date: Thu, 8 Oct 2020 15:54:23 +0300 Subject: [PATCH 072/133] MC-36830: [Issue] Fix for empty category field values in REST calls --- .../Unit/Model/ResourceModel/IndexTest.php | 42 ------------------- .../ScopeResolver/IndexScopeResolver.php | 3 -- 2 files changed, 45 deletions(-) diff --git a/app/code/Magento/Elasticsearch/Test/Unit/Model/ResourceModel/IndexTest.php b/app/code/Magento/Elasticsearch/Test/Unit/Model/ResourceModel/IndexTest.php index 5aca7e6c2555b..d6f5fb9368378 100644 --- a/app/code/Magento/Elasticsearch/Test/Unit/Model/ResourceModel/IndexTest.php +++ b/app/code/Magento/Elasticsearch/Test/Unit/Model/ResourceModel/IndexTest.php @@ -417,48 +417,6 @@ public function testGetCategoryProductIndexData() ); } - /** - * Test getCategoryProductIndexData method for all stores - */ - public function testGetCategoryProductIndexDataForAllStores() - { - $connection = $this->connection; - $select = $this->select; - - $connection->expects($this->any()) - ->method('select') - ->willReturn($select); - - $select->expects($this->any()) - ->method('from') - ->with( - ['catalog_category_product_index'], - ['category_id', 'product_id', 'position', 'store_id'] - )->willReturnSelf(); - - $select->expects($this->any()) - ->method('where') - ->willReturnSelf(); - - $connection->expects($this->once()) - ->method('fetchAll') - ->with($select) - ->willReturn([[ - 'product_id' => 1, - 'category_id' => 1, - 'position' => 1, - ]]); - - $this->assertEquals( - [ - 1 => [ - 1 => 1, - ], - ], - $this->model->getCategoryProductIndexData(0, [1]) - ); - } - /** * Test getMovedCategoryProductIds method */ diff --git a/lib/internal/Magento/Framework/Indexer/ScopeResolver/IndexScopeResolver.php b/lib/internal/Magento/Framework/Indexer/ScopeResolver/IndexScopeResolver.php index 164d2da3d9123..a68de6ad36f9a 100644 --- a/lib/internal/Magento/Framework/Indexer/ScopeResolver/IndexScopeResolver.php +++ b/lib/internal/Magento/Framework/Indexer/ScopeResolver/IndexScopeResolver.php @@ -44,9 +44,6 @@ public function resolve($index, array $dimensions) { $tableNameParts = []; foreach ($dimensions as $dimension) { - if (!$dimension->getValue()) { - continue; - } switch ($dimension->getName()) { case 'scope': $tableNameParts[$dimension->getName()] = $dimension->getName() . $this->getScopeId($dimension); From aa034d06859b1bff72121d98d094ec0944084140 Mon Sep 17 00:00:00 2001 From: Viktor Petryk <victor.petryk@transoftgroup.com> Date: Fri, 9 Oct 2020 12:01:48 +0300 Subject: [PATCH 073/133] MC-30631: Database sessions pile up --- .../Magento/Framework/Session/Config.php | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/lib/internal/Magento/Framework/Session/Config.php b/lib/internal/Magento/Framework/Session/Config.php index 296b7944ea4f6..3bb30d0f6ec3e 100644 --- a/lib/internal/Magento/Framework/Session/Config.php +++ b/lib/internal/Magento/Framework/Session/Config.php @@ -28,6 +28,18 @@ class Config implements ConfigInterface /** Configuration path for session cache limiter */ const PARAM_SESSION_CACHE_LIMITER = 'session/cache_limiter'; + /** Configuration path for session garbage collection probability */ + const PARAM_SESSION_GC_PROBABILITY = 'session/gc_probability'; + + /** Configuration path for session garbage collection divisor */ + const PARAM_SESSION_GC_DIVISOR = 'session/gc_divisor'; + + /** + * Configuration path for session garbage collection max lifetime. + * The number of seconds after which data will be seen as 'garbage'. + */ + const PARAM_SESSION_GC_MAXLIFETIME = 'session/gc_maxlifetime'; + /** Configuration path for cookie domain */ const XML_PATH_COOKIE_DOMAIN = 'web/cookie/cookie_domain'; @@ -102,6 +114,7 @@ class Config implements ConfigInterface * @param string $scopeType * @param string $lifetimePath * @SuppressWarnings(PHPMD.NPathComplexity) + * @SuppressWarnings(PHPMD.CyclomaticComplexity) */ public function __construct( \Magento\Framework\ValidatorFactory $validatorFactory, @@ -149,6 +162,30 @@ public function __construct( $this->setOption('session.cache_limiter', $cacheLimiter); } + /** + * Session garbage collection probability + */ + $gcProbability = $deploymentConfig->get(self::PARAM_SESSION_GC_PROBABILITY); + if ($gcProbability) { + $this->setOption('session.gc_probability', $gcProbability); + } + + /** + * Session garbage collection divisor + */ + $gcDivisor = $deploymentConfig->get(self::PARAM_SESSION_GC_DIVISOR); + if ($gcDivisor) { + $this->setOption('session.gc_divisor', $gcDivisor); + } + + /** + * Session garbage collection max lifetime + */ + $gcMaxlifetime = $deploymentConfig->get(self::PARAM_SESSION_GC_MAXLIFETIME); + if ($gcMaxlifetime) { + $this->setOption('session.gc_maxlifetime', $gcMaxlifetime); + } + /** * Cookie settings: lifetime, path, domain, httpOnly. These govern settings for the session cookie. */ 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 074/133] 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 075/133] 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 a535faa775d861570058fd80f9ff0f34bbd7515b Mon Sep 17 00:00:00 2001 From: Your Name <mmdhudasia@gmail.com> Date: Sat, 10 Oct 2020 12:26:16 +0530 Subject: [PATCH 076/133] 26133: Fixed - Coupon code text field not display in proper width in Internet Explorer/EDGE browser --- .../luma/Magento_Checkout/web/css/source/module/_cart.less | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/app/design/frontend/Magento/luma/Magento_Checkout/web/css/source/module/_cart.less b/app/design/frontend/Magento/luma/Magento_Checkout/web/css/source/module/_cart.less index 5d9746317af55..e8f2d1c5eb1ed 100644 --- a/app/design/frontend/Magento/luma/Magento_Checkout/web/css/source/module/_cart.less +++ b/app/design/frontend/Magento/luma/Magento_Checkout/web/css/source/module/_cart.less @@ -727,6 +727,13 @@ position: static; } } + .content { + .fieldset { + .actions-toolbar { + width: auto; + } + } + } &.discount { width: auto; } 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 077/133] 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 078/133] 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 079/133] 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 d8a15b946939ac1bc48997bea6b5a99e39057622 Mon Sep 17 00:00:00 2001 From: Myroslav Dobra <dmaraptor@gmail.com> Date: Mon, 12 Oct 2020 10:22:09 +0300 Subject: [PATCH 080/133] MC-25172: [MFTF] AppConfigDumpSuite Does Not Clean Up in After Steps Which Breaks CLI Configuration Steps In Tests That Run Later in Execution --- app/code/Magento/Config/Test/Mftf/Suite/AppConfigDumpSuite.xml | 1 + .../AdminCheckInputFieldsDisabledAfterAppConfigDumpTest.xml | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/app/code/Magento/Config/Test/Mftf/Suite/AppConfigDumpSuite.xml b/app/code/Magento/Config/Test/Mftf/Suite/AppConfigDumpSuite.xml index 762d17bdf87f1..127677ce05e0d 100644 --- a/app/code/Magento/Config/Test/Mftf/Suite/AppConfigDumpSuite.xml +++ b/app/code/Magento/Config/Test/Mftf/Suite/AppConfigDumpSuite.xml @@ -8,6 +8,7 @@ <suites xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Suite/etc/suiteSchema.xsd"> <suite name="AppConfigDumpSuite"> <before> + <!-- Command app:config:dump is not reversible and magento instance stays configuration read only after this test. You need to restore etc/env.php manually to make magento configuration writable again.--> <magentoCLI command="app:config:dump" stepKey="configDump"/> </before> <after> diff --git a/app/code/Magento/Shipping/Test/Mftf/Test/AdminCheckInputFieldsDisabledAfterAppConfigDumpTest.xml b/app/code/Magento/Shipping/Test/Mftf/Test/AdminCheckInputFieldsDisabledAfterAppConfigDumpTest.xml index 188b12c6a91c3..0c0372850a3c4 100644 --- a/app/code/Magento/Shipping/Test/Mftf/Test/AdminCheckInputFieldsDisabledAfterAppConfigDumpTest.xml +++ b/app/code/Magento/Shipping/Test/Mftf/Test/AdminCheckInputFieldsDisabledAfterAppConfigDumpTest.xml @@ -13,13 +13,14 @@ <features value="Configuration"/> <stories value="Disable configuration inputs"/> <title value="Check that all input fields disabled after executing CLI app:config:dump"/> - <description value="Check that all input fields disabled after executing CLI app:config:dump"/> + <description value="Check that all input fields disabled after executing CLI app:config:dump. Command app:config:dump is not reversible and magento instance stays configuration read only after this test. You need to restore etc/env.php manually to make magento configuration writable again."/> <severity value="MAJOR"/> <testCaseId value="MC-11158"/> <useCaseId value="MAGETWO-96428"/> <group value="configuration"/> </annotations> <before> + <!-- Command app:config:dump is not reversible and magento instance stays configuration read only after this test. You need to restore etc/env.php manually to make magento configuration writable again.--> <actionGroup ref="AdminLoginActionGroup" stepKey="loginAsAdmin"/> </before> <after> 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 081/133] 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 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 082/133] 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 083/133] 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 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 084/133] 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 085/133] 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 a37cf33bc2a02671302f66ededb5085f6e911ea4 Mon Sep 17 00:00:00 2001 From: Your Name <mmdhudasia@gmail.com> Date: Mon, 12 Oct 2020 17:51:47 +0530 Subject: [PATCH 086/133] fixed spacing issue --- .../web/css/source/module/_cart.less | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/app/design/frontend/Magento/luma/Magento_Checkout/web/css/source/module/_cart.less b/app/design/frontend/Magento/luma/Magento_Checkout/web/css/source/module/_cart.less index e8f2d1c5eb1ed..405bc1d2af373 100644 --- a/app/design/frontend/Magento/luma/Magento_Checkout/web/css/source/module/_cart.less +++ b/app/design/frontend/Magento/luma/Magento_Checkout/web/css/source/module/_cart.less @@ -267,7 +267,7 @@ .lib-icon-font-symbol( @_icon-font-content: @icon-trash ); - + &:hover { .lib-css(text-decoration, @link__text-decoration); } @@ -574,7 +574,7 @@ .widget { float: left; - + &.block { margin-bottom: @indent__base; } @@ -728,11 +728,11 @@ } } .content { - .fieldset { - .actions-toolbar { - width: auto; + .fieldset { + .actions-toolbar { + width: auto; + } } - } } &.discount { width: auto; 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 087/133] 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 763b5d303a701e440e7e0f7f700340f42d251865 Mon Sep 17 00:00:00 2001 From: Serhii Balko <serhii.balko@transoftgroup.com> Date: Mon, 12 Oct 2020 16:57:23 +0300 Subject: [PATCH 088/133] MC-38026: [SAQ] - Schedule update remove body class in category --- .../Controller/Adminhtml/CategoryTest.php | 23 ++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/dev/tests/integration/testsuite/Magento/Catalog/Controller/Adminhtml/CategoryTest.php b/dev/tests/integration/testsuite/Magento/Catalog/Controller/Adminhtml/CategoryTest.php index 6245e4e9f8de7..cd58cd2ac3819 100644 --- a/dev/tests/integration/testsuite/Magento/Catalog/Controller/Adminhtml/CategoryTest.php +++ b/dev/tests/integration/testsuite/Magento/Catalog/Controller/Adminhtml/CategoryTest.php @@ -10,6 +10,8 @@ use Magento\Framework\Acl\Builder; use Magento\Backend\App\Area\FrontNameResolver; use Magento\Catalog\Api\CategoryRepositoryInterface; +use Magento\Framework\App\ProductMetadata; +use Magento\Framework\App\ProductMetadataInterface; use Magento\Framework\App\Request\Http as HttpRequest; use Magento\Framework\Message\MessageInterface; use Magento\Framework\Registry; @@ -270,7 +272,7 @@ public function testSuggestCategoriesActionNoSuggestions(): void */ public function saveActionDataProvider(): array { - return [ + $result = [ 'default values' => [ [ 'id' => '2', @@ -390,6 +392,20 @@ public function saveActionDataProvider(): array ], ], ]; + + $productMetadataInterface = Bootstrap::getObjectManager()->get(ProductMetadataInterface::class); + if ($productMetadataInterface->getEdition() !== ProductMetadata::EDITION_NAME) { + /** + * Skip save custom_design_from and custom_design_to attributes, + * because this logic is rewritten on EE by Catalog Schedule + */ + foreach (array_keys($result['custom values']) as $index) { + unset($result['custom values'][$index]['custom_design_from']); + unset($result['custom values'][$index]['custom_design_to']); + } + } + + return $result; } /** @@ -398,6 +414,11 @@ public function saveActionDataProvider(): array */ public function testIncorrectDateFrom(): void { + $productMetadataInterface = Bootstrap::getObjectManager()->get(ProductMetadataInterface::class); + if ($productMetadataInterface->getEdition() !== ProductMetadata::EDITION_NAME) { + $this->markTestSkipped('Skipped, because this logic is rewritten on EE by Catalog Schedule'); + } + $data = [ 'name' => 'Test Category', 'attribute_set_id' => '3', 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 089/133] 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 df5ae12ecc26edee6bdb2c01cf5e0c6c3198f9f4 Mon Sep 17 00:00:00 2001 From: TuNa <ladiesman9x@gmail.com> Date: Mon, 12 Oct 2020 22:13:19 +0700 Subject: [PATCH 090/133] Remove incorrect use important in swatches option text up --- .../Magento_Swatches/web/css/source/_module.less | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/app/design/frontend/Magento/blank/Magento_Swatches/web/css/source/_module.less b/app/design/frontend/Magento/blank/Magento_Swatches/web/css/source/_module.less index 07317e1670a0b..ce1b009c24d42 100644 --- a/app/design/frontend/Magento/blank/Magento_Swatches/web/css/source/_module.less +++ b/app/design/frontend/Magento/blank/Magento_Swatches/web/css/source/_module.less @@ -65,7 +65,6 @@ // _____________________________________________ & when (@media-common = true) { - .swatch { &-attribute { &-label { @@ -155,7 +154,7 @@ padding: 4px 8px; &.selected { - .lib-css(background-color, @swatch-option-text__selected__background-color) !important; + .lib-css(background-color, @swatch-option-text__selected__background-color); } } @@ -201,6 +200,7 @@ top: 0; } } + &-disabled { border: 0; cursor: default; @@ -208,6 +208,7 @@ &:after { .lib-rotate(-30deg); + .lib-css(background, @swatch-option__disabled__background); content: ''; height: 2px; left: -4px; @@ -215,7 +216,6 @@ top: 10px; width: 42px; z-index: 995; - .lib-css(background, @swatch-option__disabled__background); } } @@ -226,6 +226,7 @@ &-tooltip { .lib-css(border, @swatch-option-tooltip__border); .lib-css(color, @swatch-option-tooltip__color); + .lib-css(background, @swatch-option-tooltip__background); display: none; max-height: 100%; min-height: 20px; @@ -234,7 +235,6 @@ position: absolute; text-align: center; z-index: 999; - .lib-css(background, @swatch-option-tooltip__background); &, &-layered { @@ -278,9 +278,9 @@ } &-layered { + .lib-css(background, @swatch-option-tooltip-layered__background); .lib-css(border, @swatch-option-tooltip-layered__border); .lib-css(color, @swatch-option-tooltip-layered__color); - .lib-css(background, @swatch-option-tooltip-layered__background); display: none; left: -47px; position: absolute; @@ -326,7 +326,6 @@ margin: 2px 0; padding: 2px; position: static; - z-index: 1; } &-visual-tooltip-layered { From daf0dc67926bde5de6e05f956ff0b09756ed0123 Mon Sep 17 00:00:00 2001 From: niravkrish <niravpatel5393@gmail.com> Date: Mon, 12 Oct 2020 22:08:35 +0530 Subject: [PATCH 091/133] removed unneccesary css to resolve the issue --- lib/web/css/source/components/_modals.less | 4 ---- 1 file changed, 4 deletions(-) diff --git a/lib/web/css/source/components/_modals.less b/lib/web/css/source/components/_modals.less index 58c9c0674b6ad..8513db545f1ec 100644 --- a/lib/web/css/source/components/_modals.less +++ b/lib/web/css/source/components/_modals.less @@ -103,10 +103,6 @@ &.confirm { .modal-inner-wrap { .lib-css(max-width, @modal-popup-confirm__width); - - .modal-content { - padding-right: 7rem; - } } } From d2d6c607bece5c137b1a5a8119974fc24571e6e3 Mon Sep 17 00:00:00 2001 From: Viktor Petryk <victor.petryk@transoftgroup.com> Date: Mon, 12 Oct 2020 22:50:39 +0300 Subject: [PATCH 092/133] MC-30631: Database sessions pile up --- lib/internal/Magento/Framework/Session/Config.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/internal/Magento/Framework/Session/Config.php b/lib/internal/Magento/Framework/Session/Config.php index 3bb30d0f6ec3e..1791ab09156fd 100644 --- a/lib/internal/Magento/Framework/Session/Config.php +++ b/lib/internal/Magento/Framework/Session/Config.php @@ -29,16 +29,16 @@ class Config implements ConfigInterface const PARAM_SESSION_CACHE_LIMITER = 'session/cache_limiter'; /** Configuration path for session garbage collection probability */ - const PARAM_SESSION_GC_PROBABILITY = 'session/gc_probability'; + private const PARAM_SESSION_GC_PROBABILITY = 'session/gc_probability'; /** Configuration path for session garbage collection divisor */ - const PARAM_SESSION_GC_DIVISOR = 'session/gc_divisor'; + private const PARAM_SESSION_GC_DIVISOR = 'session/gc_divisor'; /** * Configuration path for session garbage collection max lifetime. * The number of seconds after which data will be seen as 'garbage'. */ - const PARAM_SESSION_GC_MAXLIFETIME = 'session/gc_maxlifetime'; + private const PARAM_SESSION_GC_MAXLIFETIME = 'session/gc_maxlifetime'; /** Configuration path for cookie domain */ const XML_PATH_COOKIE_DOMAIN = 'web/cookie/cookie_domain'; 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 093/133] 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 094/133] 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 095/133] 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 096/133] 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 097/133] 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 098/133] 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 e8ad9aee3fe307fce2e0c4b0cdd0661567170ae7 Mon Sep 17 00:00:00 2001 From: Solwininfotech <stdabhoya@yahoo.com> Date: Tue, 13 Oct 2020 17:51:54 +0530 Subject: [PATCH 099/133] removed .content and .fieldset selectors --- .../web/css/source/module/_cart.less | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/app/design/frontend/Magento/luma/Magento_Checkout/web/css/source/module/_cart.less b/app/design/frontend/Magento/luma/Magento_Checkout/web/css/source/module/_cart.less index 405bc1d2af373..2c8c52bdb7af2 100644 --- a/app/design/frontend/Magento/luma/Magento_Checkout/web/css/source/module/_cart.less +++ b/app/design/frontend/Magento/luma/Magento_Checkout/web/css/source/module/_cart.less @@ -727,16 +727,14 @@ position: static; } } - .content { - .fieldset { - .actions-toolbar { - width: auto; - } - } - } + &.discount { width: auto; } + + .actions-toolbar { + width: auto; + } } } 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 100/133] 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 101/133] 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 102/133] 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 103/133] 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 104/133] 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 105/133] 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 106/133] 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 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 107/133] 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 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 108/133] 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 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 109/133] 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 110/133] 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 111/133] 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 112/133] 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 113/133] 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 114/133] 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 115/133] 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 116/133] 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 117/133] 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 ab41eb8864b6c2980d71b4ee8b3d8e2c4ed93fd8 Mon Sep 17 00:00:00 2001 From: DmytroPaidych <dimonovp@gmail.com> Date: Fri, 16 Oct 2020 09:04:54 +0200 Subject: [PATCH 118/133] 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 119/133] 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 120/133] 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 121/133] 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 122/133] 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 123/133] 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 b0a3cf71e5809ac1037ab803736126e0039b4460 Mon Sep 17 00:00:00 2001 From: SmVladyslav <vlatame.tsg@gmail.com> Date: Mon, 19 Oct 2020 11:12:58 +0300 Subject: [PATCH 124/133] 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 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> + <entity name="NonexistentProduct" type="product"> + <data key="sku">NonexistentProductSku</data> + <data key="qty">1</data> + </entity> + <entity name="SecondNonexistentProduct" type="product"> + <data key="sku">SecondNonexistentProductSku</data> + <data key="qty">1</data> + </entity> +</entities> 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 @@ <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="emptyCartMessageContent" type="text" selector="#minicart-content-wrapper .minicart.empty.text" timeout="30"/> <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 c643d79b37f9501c7ab24451956c5e906bfd128c Mon Sep 17 00:00:00 2001 From: Viktor Kopin <viktor.kopin@transoftgroup.com> Date: Mon, 19 Oct 2020 17:00:39 +0300 Subject: [PATCH 125/133] 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 <sdzhepa@adobe.com> Date: Mon, 19 Oct 2020 13:59:56 -0500 Subject: [PATCH 126/133] 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 606292dc833efdda124f211488a838d9d4746110 Mon Sep 17 00:00:00 2001 From: hanna_hnida <hanna.hnida@vaimo.com> Date: Tue, 20 Oct 2020 12:07:09 +0200 Subject: [PATCH 127/133] 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 @@ <page layout="3columns" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd"> <update handle="default_head_blocks"/> <body> + <attribute name="id" value="html-body"/> <block name="require.js" class="Magento\Framework\View\Element\Template" template="Magento_Theme::page/js/require_js.phtml" /> <referenceContainer name="after.body.start"> <block class="Magento\RequireJs\Block\Html\Head\Config" name="requirejs-config"/> 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 <hanna.hnida@vaimo.com> Date: Tue, 20 Oct 2020 15:32:34 +0200 Subject: [PATCH 128/133] 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 754eb5a168e6b2bd78b972e482988fa0b695a612 Mon Sep 17 00:00:00 2001 From: hanna_hnida <hanna.hnida@vaimo.com> Date: Tue, 20 Oct 2020 18:11:04 +0200 Subject: [PATCH 129/133] 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"> <section name="BlockContentSection"> <element name="TextArea" type="input" selector="#cms_block_form_content"/> - <element name="image" type="file" selector="#tinymce img"/> + <element name="image" type="file" selector=".mce-content-body img"/> <element name="contentIframe" type="iframe" selector="cms_block_form_content_ifr"/> </section> </sections> From 7683dece30de99d5df537507487e1129cd85c023 Mon Sep 17 00:00:00 2001 From: hanna_hnida <hanna.hnida@vaimo.com> Date: Tue, 20 Oct 2020 18:12:21 +0200 Subject: [PATCH 130/133] 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"> <section name="BlockContentSection"> <element name="TextArea" type="input" selector="#cms_block_form_content"/> - <element name="image" type="file" selector="#tinymce img"/> + <element name="image" type="file" selector=".mce-content-body img"/> <element name="contentIframe" type="iframe" selector="cms_block_form_content_ifr"/> </section> </sections> From 0ab061a5da09ba5fc95a6cd3a1d87454c1f2a8d0 Mon Sep 17 00:00:00 2001 From: hanna_hnida <hanna.hnida@vaimo.com> Date: Tue, 20 Oct 2020 18:15:23 +0200 Subject: [PATCH 131/133] 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"> <section name="BlockContentSection"> <element name="TextArea" type="input" selector="#cms_block_form_content"/> - <element name="image" type="file" selector=".mce-content-body img"/> + <element name="image" type="file" selector="#tinymce img"/> <element name="contentIframe" type="iframe" selector="cms_block_form_content_ifr"/> </section> </sections> From bc3d88a026e178162b547dfe933378a8119e9121 Mon Sep 17 00:00:00 2001 From: Sergii Ivashchenko <serg.ivashchenko@gmail.com> Date: Tue, 20 Oct 2020 17:22:49 +0100 Subject: [PATCH 132/133] 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 0eb8848fa1d67201d97726f701b7edc6f237e21c Mon Sep 17 00:00:00 2001 From: Myroslav Dobra <dmaraptor@gmail.com> Date: Wed, 21 Oct 2020 09:13:21 +0300 Subject: [PATCH 133/133] 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 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\ConfigurableProduct\Model\Plugin\Frontend; + +use Magento\Catalog\Api\Data\ProductInterface; +use Magento\ConfigurableProduct\Model\Product\Type\Configurable; + +/** + * Filter configurable options by current store plugin. + */ +class UsedProductsWebsiteFilter +{ + /** + * Filter configurable options not assigned to current website. + * + * @param Configurable $subject + * @param ProductInterface $product + * @param array|null $requiredAttributeIds + * @return void + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function beforeGetUsedProducts( + Configurable $subject, + ProductInterface $product, + array $requiredAttributeIds = null + ): void { + $subject->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 @@ </type> <type name="Magento\ConfigurableProduct\Model\Product\Type\Configurable"> <plugin name="used_products_cache" type="Magento\ConfigurableProduct\Model\Plugin\Frontend\UsedProductsCache" /> + <plugin name="used_products_website_filter" type="Magento\ConfigurableProduct\Model\Plugin\Frontend\UsedProductsWebsiteFilter" /> </type> </config> 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 */