diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md
index 54479c5d99c38..e4633e187480f 100644
--- a/.github/ISSUE_TEMPLATE/bug_report.md
+++ b/.github/ISSUE_TEMPLATE/bug_report.md
@@ -11,7 +11,7 @@ Fields marked with (*) are required. Please don't remove the template.
### Preconditions (*)
1.
2.
@@ -32,3 +32,12 @@ Important: Provide a set of clear steps to reproduce this bug. We can not provid
1. [Screenshots, logs or description]
2.
+
+---
+Please provide [Severity](https://devdocs.magento.com/guides/v2.3/contributor-guide/contributing.html#backlog) assessment for the Issue as Reporter. This information will help during Confirmation and Issue triage processes.
+
+- [ ] Severity: **S0** _- Affects critical data or functionality and leaves users without workaround._
+- [ ] Severity: **S1** _- Affects critical data or functionality and forces users to employ a workaround._
+- [ ] Severity: **S2** _- Affects non-critical data or functionality and forces users to employ a workaround._
+- [ ] Severity: **S3** _- Affects non-critical data or functionality and does not force users to employ a workaround._
+- [ ] Severity: **S4** _- Affects aesthetics, professional look and feel, “quality” or “usability”._
diff --git a/.github/ISSUE_TEMPLATE/developer-experience-issue.md b/.github/ISSUE_TEMPLATE/developer-experience-issue.md
index 713ce410a16e1..db5fca78965d6 100644
--- a/.github/ISSUE_TEMPLATE/developer-experience-issue.md
+++ b/.github/ISSUE_TEMPLATE/developer-experience-issue.md
@@ -18,3 +18,12 @@ Fields marked with (*) are required. Please don't remove the template.
### Proposed solution
+
+---
+Please provide [Severity](https://devdocs.magento.com/guides/v2.3/contributor-guide/contributing.html#backlog) assessment for the Issue as Reporter. This information will help during Confirmation and Issue triage processes.
+
+- [ ] Severity: **S0** _- Affects critical data or functionality and leaves users with no workaround._
+- [ ] Severity: **S1** _- Affects critical data or functionality and forces users to employ a workaround._
+- [ ] Severity: **S2** _- Affects non-critical data or functionality and forces users to employ a workaround._
+- [ ] Severity: **S3** _- Affects non-critical data or functionality and does not force users to employ a workaround._
+- [ ] Severity: **S4** _- Affects aesthetics, professional look and feel, “quality” or “usability”._
\ No newline at end of file
diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
index 5d6620ce19228..2856223c5ed1b 100644
--- a/.github/PULL_REQUEST_TEMPLATE.md
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -23,7 +23,7 @@
If relevant, please provide a list of fixed issues in the format magento/magento2#.
There could be 1 or more issues linked here and it will help us find some more information about the reasoning behind this change.
-->
-1. magento/magento2#: Issue title
+1. Fixes magento/magento2#
### Manual testing scenarios (*)
+
+
+
+
+ Run cache:clean by CLI with specified cache tags (space separated).
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Backend/Test/Mftf/ActionGroup/CliCacheFlushActionGroup.xml b/app/code/Magento/Backend/Test/Mftf/ActionGroup/CliCacheFlushActionGroup.xml
new file mode 100644
index 0000000000000..4dc18d1215139
--- /dev/null
+++ b/app/code/Magento/Backend/Test/Mftf/ActionGroup/CliCacheFlushActionGroup.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+ Run cache:flush by CLI with specified cache tags (space separated).
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Backend/Test/Mftf/Test/AdminLoginSuccessfulTest.xml b/app/code/Magento/Backend/Test/Mftf/Test/AdminLoginSuccessfulTest.xml
index a8de04f4342de..7bb249e974c31 100644
--- a/app/code/Magento/Backend/Test/Mftf/Test/AdminLoginSuccessfulTest.xml
+++ b/app/code/Magento/Backend/Test/Mftf/Test/AdminLoginSuccessfulTest.xml
@@ -20,6 +20,7 @@
+
diff --git a/app/code/Magento/Backend/etc/adminhtml/system.xml b/app/code/Magento/Backend/etc/adminhtml/system.xml
index 1e8ec226d6b88..45ed50fd49b7e 100644
--- a/app/code/Magento/Backend/etc/adminhtml/system.xml
+++ b/app/code/Magento/Backend/etc/adminhtml/system.xml
@@ -341,7 +341,7 @@
Images Upload Configuration
-
+
Enable Frontend Resize
Magento\Config\Model\Config\Source\Yesno
Resize performed via javascript before file upload.
@@ -459,9 +459,7 @@
Add Store Code to Urls
Magento\Config\Model\Config\Source\Yesno
Magento\Config\Model\Config\Backend\Store
-
- Warning! When using Store Code in URLs, in some cases system may not work properly if URLs without Store Codes are specified in the third-party services (e.g. PayPal etc.).]]>
-
+ Warning! When using Store Code in URLs, in some cases system may not work properly if URLs without Store Codes are specified in the third-party services (e.g. PayPal etc.).]]>
Auto-redirect to Base URL
diff --git a/app/code/Magento/Backend/i18n/en_US.csv b/app/code/Magento/Backend/i18n/en_US.csv
index e0643518bde30..59bbe7f69985a 100644
--- a/app/code/Magento/Backend/i18n/en_US.csv
+++ b/app/code/Magento/Backend/i18n/en_US.csv
@@ -403,11 +403,9 @@ Security,Security
Web,Web
"Url Options","Url Options"
"Add Store Code to Urls","Add Store Code to Urls"
-"
- Warning! When using Store Code in URLs, in some cases system may not work properly if URLs without Store Codes are specified in the third-party services (e.g. PayPal etc.).
- ","
- Warning! When using Store Code in URLs, in some cases system may not work properly if URLs without Store Codes are specified in the third-party services (e.g. PayPal etc.).
- "
+"Warning! When using Store Code in URLs, in some cases system may not work properly if URLs without Store Codes are specified in the third-party services (e.g. PayPal etc.).","Warning! When using Store Code in URLs, in some cases system may not work properly if URLs without Store Codes are specified in the third-party services (e.g. PayPal etc.)."
+"Enable Frontend Resize","Enable Frontend Resize"
+"Resize performed via javascript before file upload.","Resize performed via javascript before file upload."
"Auto-redirect to Base URL","Auto-redirect to Base URL"
"Search Engine Optimization","Search Engine Optimization"
"Use Web Server Rewrites","Use Web Server Rewrites"
diff --git a/app/code/Magento/Bundle/Block/Adminhtml/Sales/Order/Items/Renderer.php b/app/code/Magento/Bundle/Block/Adminhtml/Sales/Order/Items/Renderer.php
index 82a0086ad67ec..b4134e7e3a97e 100644
--- a/app/code/Magento/Bundle/Block/Adminhtml/Sales/Order/Items/Renderer.php
+++ b/app/code/Magento/Bundle/Block/Adminhtml/Sales/Order/Items/Renderer.php
@@ -51,7 +51,7 @@ public function __construct(
* @param string $value
* @param int $length
* @param string $etc
- * @param string &$remainder
+ * @param string $remainder
* @param bool $breakWords
* @return string
*/
@@ -83,6 +83,7 @@ public function getChildren($item)
}
if ($items) {
+ $itemsArray[$item->getOrderItem()->getId()][$item->getOrderItemId()] = $item;
foreach ($items as $value) {
$parentItem = $value->getOrderItem()->getParentItem();
if ($parentItem) {
diff --git a/app/code/Magento/Bundle/Test/Mftf/Test/AdminShouldBeAbleToMassUpdateAttributesForBundleProductsTest.xml b/app/code/Magento/Bundle/Test/Mftf/Test/AdminShouldBeAbleToMassUpdateAttributesForBundleProductsTest.xml
index caf500762883c..daa3351073e9b 100644
--- a/app/code/Magento/Bundle/Test/Mftf/Test/AdminShouldBeAbleToMassUpdateAttributesForBundleProductsTest.xml
+++ b/app/code/Magento/Bundle/Test/Mftf/Test/AdminShouldBeAbleToMassUpdateAttributesForBundleProductsTest.xml
@@ -34,7 +34,7 @@
-
+
@@ -56,9 +56,11 @@
-
-
-
+
+
+
+
+
diff --git a/app/code/Magento/Bundle/Test/Unit/Block/Adminhtml/Sales/Order/Items/RendererTest.php b/app/code/Magento/Bundle/Test/Unit/Block/Adminhtml/Sales/Order/Items/RendererTest.php
index 07460549aea18..daf90f9a07af3 100644
--- a/app/code/Magento/Bundle/Test/Unit/Block/Adminhtml/Sales/Order/Items/RendererTest.php
+++ b/app/code/Magento/Bundle/Test/Unit/Block/Adminhtml/Sales/Order/Items/RendererTest.php
@@ -17,6 +17,9 @@
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
+/**
+ * Test Renderer order item
+ */
class RendererTest extends TestCase
{
/** @var Item|MockObject */
@@ -98,25 +101,27 @@ public function testGetChildren($parentItem)
$parentItem = $this->createPartialMock(Item::class, ['getId', '__wakeup']);
$parentItem->expects($this->any())->method('getId')->willReturn(1);
}
- $this->orderItem->expects($this->any())->method('getOrderItem')->willReturnSelf();
- $this->orderItem->expects($this->any())->method('getParentItem')->willReturn($parentItem);
- $this->orderItem->expects($this->any())->method('getOrderItemId')->willReturn(2);
- $this->orderItem->expects($this->any())->method('getId')->willReturn(1);
+ $this->orderItem->method('getOrderItem')->willReturnSelf();
+ $this->orderItem->method('getParentItem')->willReturn($parentItem);
+ $this->orderItem->method('getOrderItemId')->willReturn(2);
+ $this->orderItem->method('getId')->willReturn(1);
$salesModel = $this->createPartialMock(
Invoice::class,
['getAllItems', '__wakeup']
);
- $salesModel->expects($this->once())->method('getAllItems')->willReturn([$this->orderItem]);
+ $salesModel->method('getAllItems')->willReturn([$this->orderItem]);
$item = $this->createPartialMock(
\Magento\Sales\Model\Order\Invoice\Item::class,
- ['getInvoice', 'getOrderItem', '__wakeup']
+ ['getInvoice', 'getOrderItem', 'getOrderItemId', '__wakeup']
);
- $item->expects($this->once())->method('getInvoice')->willReturn($salesModel);
- $item->expects($this->any())->method('getOrderItem')->willReturn($this->orderItem);
+ $item->method('getInvoice')->willReturn($salesModel);
+ $item->method('getOrderItem')->willReturn($this->orderItem);
+ $item->method('getOrderItemId')->willReturn($this->orderItem->getOrderItemId());
- $this->assertSame([2 => $this->orderItem], $this->model->getChildren($item));
+ $orderItem = $this->model->getChildren($item);
+ $this->assertSame([2 => $this->orderItem], $orderItem);
}
/**
diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Category.php b/app/code/Magento/Catalog/Model/ResourceModel/Category.php
index c4980c917d069..298ca059c572e 100644
--- a/app/code/Magento/Catalog/Model/ResourceModel/Category.php
+++ b/app/code/Magento/Catalog/Model/ResourceModel/Category.php
@@ -469,10 +469,6 @@ protected function _saveCategoryProducts($category)
if (!empty($insert) || !empty($delete)) {
$productIds = array_unique(array_merge(array_keys($insert), array_keys($delete)));
- $this->_eventManager->dispatch(
- 'catalog_category_change_products',
- ['category' => $category, 'product_ids' => $productIds]
- );
$category->setChangedProductIds($productIds);
}
@@ -484,6 +480,10 @@ protected function _saveCategoryProducts($category)
* Setting affected products to category for third party engine index refresh
*/
$productIds = array_keys($insert + $delete + $update);
+ $this->_eventManager->dispatch(
+ 'catalog_category_change_products',
+ ['category' => $category, 'product_ids' => $productIds]
+ );
$category->setAffectedProductIds($productIds);
}
return $this;
@@ -1078,7 +1078,6 @@ public function countVisible()
*/
public function load($object, $entityId, $attributes = [])
{
- $this->_attributes = [];
$select = $this->_getLoadRowSelect($object, $entityId);
$row = $this->getConnection()->fetchRow($select);
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminCreateSimpleProductActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminCreateSimpleProductActionGroup.xml
new file mode 100644
index 0000000000000..b1ad890c121d3
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminCreateSimpleProductActionGroup.xml
@@ -0,0 +1,39 @@
+
+
+
+
+
+
+ Goes to the Admin Product grid page. Clicks on Add. Fills the provided Product details (Name, SKU, Price, Quantity, Category and URL). Clicks on Save. Validates that the Product details are present and correct.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/DeleteProductActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/DeleteProductActionGroup.xml
index 22209b61d5316..43732c137c35c 100644
--- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/DeleteProductActionGroup.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/DeleteProductActionGroup.xml
@@ -20,7 +20,9 @@
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Data/QueueConsumerData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/QueueConsumerData.xml
new file mode 100644
index 0000000000000..cd53fede3ab58
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Data/QueueConsumerData.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+ product_action_attribute.update
+ 100
+
+
+ product_action_attribute.website.update
+ 100
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateInactiveFlatCategoryAndUpdateAsInactiveTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateInactiveFlatCategoryAndUpdateAsInactiveTest.xml
index 1c12b048e96d0..f8346f5a9dd5c 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateInactiveFlatCategoryAndUpdateAsInactiveTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateInactiveFlatCategoryAndUpdateAsInactiveTest.xml
@@ -38,8 +38,8 @@
-
-
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateInactiveFlatCategoryTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateInactiveFlatCategoryTest.xml
index ff30c46a51c3a..0aa89bdfd45b6 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateInactiveFlatCategoryTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateInactiveFlatCategoryTest.xml
@@ -38,8 +38,8 @@
-
-
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateInactiveInMenuFlatCategoryTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateInactiveInMenuFlatCategoryTest.xml
index 2b4437aed1bb2..171d15fe6ed4f 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateInactiveInMenuFlatCategoryTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateInactiveInMenuFlatCategoryTest.xml
@@ -38,8 +38,8 @@
-
-
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminMassProductPriceUpdateTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminMassProductPriceUpdateTest.xml
index e8e0d449aee4e..845ce340451d1 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminMassProductPriceUpdateTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminMassProductPriceUpdateTest.xml
@@ -56,11 +56,13 @@
-
-
-
-
-
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminMassUpdateProductAttributesGlobalScopeTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminMassUpdateProductAttributesGlobalScopeTest.xml
index 31b5961edaaaa..cd34741b6a68c 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminMassUpdateProductAttributesGlobalScopeTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminMassUpdateProductAttributesGlobalScopeTest.xml
@@ -30,6 +30,7 @@
+
@@ -39,6 +40,7 @@
+
@@ -59,22 +61,23 @@
-
+
-
-
-
-
+
+
+
+
+
-
-
-
+
+
+
@@ -84,9 +87,9 @@
-
-
-
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminMoveProductBetweenCategoriesTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminMoveProductBetweenCategoriesTest.xml
index fe31456aca334..055f4e23cd9e7 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminMoveProductBetweenCategoriesTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminMoveProductBetweenCategoriesTest.xml
@@ -116,7 +116,7 @@
-
+
@@ -188,7 +188,7 @@
-
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateFlatCategoryIncludeInNavigationTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateFlatCategoryIncludeInNavigationTest.xml
index 6ee1fd6a58e42..1214ba879f211 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateFlatCategoryIncludeInNavigationTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateFlatCategoryIncludeInNavigationTest.xml
@@ -29,21 +29,17 @@
-
-
-
-
-
-
+
+
+
+
+
-
-
-
@@ -52,6 +48,9 @@
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateFlatCategoryNameAndDescriptionTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateFlatCategoryNameAndDescriptionTest.xml
index dd79dd6824bbb..490f8dbdc4f81 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateFlatCategoryNameAndDescriptionTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateFlatCategoryNameAndDescriptionTest.xml
@@ -30,16 +30,17 @@
-
-
-
-
-
-
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontPurchaseProductWithCustomOptionsWithLongValuesTitleTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontPurchaseProductWithCustomOptionsWithLongValuesTitleTest.xml
index fbecc15a59b1f..95e48e63419d3 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontPurchaseProductWithCustomOptionsWithLongValuesTitleTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontPurchaseProductWithCustomOptionsWithLongValuesTitleTest.xml
@@ -41,11 +41,11 @@
-
-
-
-
-
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/edit/action/inventory.phtml b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/edit/action/inventory.phtml
index 792af12494af6..1d22624751b32 100644
--- a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/edit/action/inventory.phtml
+++ b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/edit/action/inventory.phtml
@@ -33,7 +33,7 @@
$defaultMinSaleQty = $block->getDefaultConfigValue('min_sale_qty');
if (!is_numeric($defaultMinSaleQty)) {
$defaultMinSaleQty = json_decode($defaultMinSaleQty, true);
- $defaultMinSaleQty = (float) $defaultMinSaleQty[\Magento\Customer\Api\Data\GroupInterface::CUST_GROUP_ALL] ?? 1;
+ $defaultMinSaleQty = (float) ($defaultMinSaleQty[\Magento\Customer\Api\Data\GroupInterface::CUST_GROUP_ALL] ?? 1);
}
?>
-
+
= $block->escapeHtml(__('Change')) ?>
@@ -364,7 +374,9 @@ if (!is_numeric($defaultMinSaleQty)) {
class="label">= $block->escapeHtml(__('Use Config Settings')) ?>
-
+
= $block->escapeHtml(__('Change')) ?>
@@ -385,11 +397,15 @@ if (!is_numeric($defaultMinSaleQty)) {
name="= /* @noEscape */ $block->getFieldSuffix() ?>[is_in_stock]" class="select"
disabled="disabled">
= $block->escapeHtml(__('In Stock')) ?>
- getDefaultConfigValue('is_in_stock') == 0) :?> selected>= $block->escapeHtml(__('Out of Stock')) ?>
+ getDefaultConfigValue('is_in_stock') == 0):?>
+ selected>= $block->escapeHtml(__('Out of Stock')) ?>
+
-
+
= $block->escapeHtml(__('Change')) ?>
diff --git a/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportBundleProductTest.xml b/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportBundleProductTest.xml
index 677752be02eb5..bdb562ab0205d 100644
--- a/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportBundleProductTest.xml
+++ b/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportBundleProductTest.xml
@@ -80,7 +80,13 @@
-
+
+
+
+
+
+
+
@@ -96,7 +102,7 @@
-
+
diff --git a/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportGroupedProductWithSpecialPriceTest.xml b/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportGroupedProductWithSpecialPriceTest.xml
index 5078fa5c571db..5ae94f050eb30 100644
--- a/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportGroupedProductWithSpecialPriceTest.xml
+++ b/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportGroupedProductWithSpecialPriceTest.xml
@@ -48,7 +48,6 @@
-
@@ -68,7 +67,13 @@
-
+
+
+
+
+
+
+
diff --git a/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportImportConfigurableProductWithImagesTest.xml b/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportImportConfigurableProductWithImagesTest.xml
index 8eba6a39f6199..e0dfb8250c738 100644
--- a/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportImportConfigurableProductWithImagesTest.xml
+++ b/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportImportConfigurableProductWithImagesTest.xml
@@ -164,9 +164,13 @@
-
-
-
+
+
+
+
+
+
+
diff --git a/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportSimpleAndConfigurableProductsWithCustomOptionsTest.xml b/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportSimpleAndConfigurableProductsWithCustomOptionsTest.xml
index 44f7b91324025..c82451eb9dbb5 100644
--- a/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportSimpleAndConfigurableProductsWithCustomOptionsTest.xml
+++ b/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportSimpleAndConfigurableProductsWithCustomOptionsTest.xml
@@ -98,7 +98,13 @@
-
+
+
+
+
+
+
+
diff --git a/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportSimpleProductAndConfigurableProductsWithAssignedImagesTest.xml b/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportSimpleProductAndConfigurableProductsWithAssignedImagesTest.xml
index 744e51bfe8896..dc556a6d0a899 100644
--- a/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportSimpleProductAndConfigurableProductsWithAssignedImagesTest.xml
+++ b/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportSimpleProductAndConfigurableProductsWithAssignedImagesTest.xml
@@ -89,7 +89,7 @@
-
+
@@ -113,7 +113,13 @@
-
+
+
+
+
+
+
+
diff --git a/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportSimpleProductAssignedToMainWebsiteAndConfigurableProductAssignedToCustomWebsiteTest.xml b/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportSimpleProductAssignedToMainWebsiteAndConfigurableProductAssignedToCustomWebsiteTest.xml
index 09f37a10fb14d..65f9ff80f7e39 100644
--- a/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportSimpleProductAssignedToMainWebsiteAndConfigurableProductAssignedToCustomWebsiteTest.xml
+++ b/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportSimpleProductAssignedToMainWebsiteAndConfigurableProductAssignedToCustomWebsiteTest.xml
@@ -97,7 +97,13 @@
-
+
+
+
+
+
+
+
diff --git a/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportSimpleProductWithCustomAttributeTest.xml b/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportSimpleProductWithCustomAttributeTest.xml
index d345b0b0de116..e684f80d8bd05 100644
--- a/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportSimpleProductWithCustomAttributeTest.xml
+++ b/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportSimpleProductWithCustomAttributeTest.xml
@@ -28,7 +28,8 @@
-
+
+
@@ -48,12 +49,13 @@
-
-
-
+
+
+
+
+
-
diff --git a/app/code/Magento/CatalogInventory/i18n/en_US.csv b/app/code/Magento/CatalogInventory/i18n/en_US.csv
index 19b73f847b46d..af989dc06d47e 100644
--- a/app/code/Magento/CatalogInventory/i18n/en_US.csv
+++ b/app/code/Magento/CatalogInventory/i18n/en_US.csv
@@ -70,3 +70,4 @@ Stock,Stock
"Use Config Settings","Use Config Settings"
"Qty Uses Decimals","Qty Uses Decimals"
"Allow Multiple Boxes for Shipping","Allow Multiple Boxes for Shipping"
+"Done","Done"
diff --git a/app/code/Magento/CatalogInventory/view/adminhtml/ui_component/product_form.xml b/app/code/Magento/CatalogInventory/view/adminhtml/ui_component/product_form.xml
index 27ce26cabc627..6c33233704148 100644
--- a/app/code/Magento/CatalogInventory/view/adminhtml/ui_component/product_form.xml
+++ b/app/code/Magento/CatalogInventory/view/adminhtml/ui_component/product_form.xml
@@ -11,7 +11,7 @@
-
-
- Done
+ - Done
- action-primary
-
-
@@ -21,7 +21,7 @@
- Advanced Inventory
+ Advanced Inventory
actionDone
data.product
diff --git a/app/code/Magento/CatalogRule/Model/Rule/Condition/ConditionsToSearchCriteriaMapper.php b/app/code/Magento/CatalogRule/Model/Rule/Condition/ConditionsToSearchCriteriaMapper.php
index fabe504fbe31c..662a2fd6b38fc 100644
--- a/app/code/Magento/CatalogRule/Model/Rule/Condition/ConditionsToSearchCriteriaMapper.php
+++ b/app/code/Magento/CatalogRule/Model/Rule/Condition/ConditionsToSearchCriteriaMapper.php
@@ -7,13 +7,16 @@
namespace Magento\CatalogRule\Model\Rule\Condition;
-use Magento\Framework\Exception\InputException;
-use Magento\Rule\Model\Condition\ConditionInterface;
use Magento\CatalogRule\Model\Rule\Condition\Combine as CombinedCondition;
use Magento\CatalogRule\Model\Rule\Condition\Product as SimpleCondition;
use Magento\Framework\Api\CombinedFilterGroup as FilterGroup;
+use Magento\Framework\Api\CombinedFilterGroupFactory;
use Magento\Framework\Api\Filter;
+use Magento\Framework\Api\FilterFactory;
use Magento\Framework\Api\SearchCriteria;
+use Magento\Framework\Api\SearchCriteriaBuilderFactory;
+use Magento\Framework\Exception\InputException;
+use Magento\Rule\Model\Condition\ConditionInterface;
/**
* Maps catalog price rule conditions to search criteria
@@ -21,29 +24,29 @@
class ConditionsToSearchCriteriaMapper
{
/**
- * @var \Magento\Framework\Api\SearchCriteriaBuilderFactory
+ * @var SearchCriteriaBuilderFactory
*/
private $searchCriteriaBuilderFactory;
/**
- * @var \Magento\Framework\Api\CombinedFilterGroupFactory
+ * @var CombinedFilterGroupFactory
*/
private $combinedFilterGroupFactory;
/**
- * @var \Magento\Framework\Api\FilterFactory
+ * @var FilterFactory
*/
private $filterFactory;
/**
- * @param \Magento\Framework\Api\SearchCriteriaBuilderFactory $searchCriteriaBuilderFactory
- * @param \Magento\Framework\Api\CombinedFilterGroupFactory $combinedFilterGroupFactory
- * @param \Magento\Framework\Api\FilterFactory $filterFactory
+ * @param SearchCriteriaBuilderFactory $searchCriteriaBuilderFactory
+ * @param CombinedFilterGroupFactory $combinedFilterGroupFactory
+ * @param FilterFactory $filterFactory
*/
public function __construct(
- \Magento\Framework\Api\SearchCriteriaBuilderFactory $searchCriteriaBuilderFactory,
- \Magento\Framework\Api\CombinedFilterGroupFactory $combinedFilterGroupFactory,
- \Magento\Framework\Api\FilterFactory $filterFactory
+ SearchCriteriaBuilderFactory $searchCriteriaBuilderFactory,
+ CombinedFilterGroupFactory $combinedFilterGroupFactory,
+ FilterFactory $filterFactory
) {
$this->searchCriteriaBuilderFactory = $searchCriteriaBuilderFactory;
$this->combinedFilterGroupFactory = $combinedFilterGroupFactory;
@@ -74,7 +77,7 @@ public function mapConditionsToSearchCriteria(CombinedCondition $conditions): Se
* Convert condition to filter group
*
* @param ConditionInterface $condition
- * @return null|\Magento\Framework\Api\CombinedFilterGroup|\Magento\Framework\Api\Filter
+ * @return null|FilterGroup|Filter
* @throws InputException
*/
private function mapConditionToFilterGroup(ConditionInterface $condition)
@@ -94,7 +97,7 @@ private function mapConditionToFilterGroup(ConditionInterface $condition)
* Convert combined condition to filter group
*
* @param Combine $combinedCondition
- * @return null|\Magento\Framework\Api\CombinedFilterGroup
+ * @return null|FilterGroup
* @throws InputException
*/
private function mapCombinedConditionToFilterGroup(CombinedCondition $combinedCondition)
@@ -111,7 +114,7 @@ private function mapCombinedConditionToFilterGroup(CombinedCondition $combinedCo
// This required to solve cases when condition is configured like:
// "If ALL/ANY of these conditions are FALSE" - we need to reverse SQL operator for this "FALSE"
if ((bool)$combinedCondition->getValue() === false) {
- $this->reverseSqlOperatorInFilter($filter);
+ $this->reverseSqlOperatorInFilterRecursively($filter);
}
$filters[] = $filter;
@@ -183,6 +186,24 @@ private function getGlueForArrayValues(string $operator): string
return 'any';
}
+ /**
+ * Recursively reverse sql conditions to their corresponding negative analog for the entire FilterGroup
+ *
+ * @param Filter|FilterGroup $filter
+ * @return void
+ * @throws InputException
+ */
+ private function reverseSqlOperatorInFilterRecursively($filter): void
+ {
+ if ($filter instanceof FilterGroup) {
+ foreach ($filter->getFilters() as &$currentFilter) {
+ $this->reverseSqlOperatorInFilterRecursively($currentFilter);
+ }
+ } else {
+ $this->reverseSqlOperatorInFilter($filter);
+ }
+ }
+
/**
* Reverse sql conditions to their corresponding negative analog
*
diff --git a/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminEnableAttributeIsUndefinedCatalogPriceRuleTest.xml b/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminEnableAttributeIsUndefinedCatalogPriceRuleTest.xml
index 6c436fee808a7..9d7607d7521c9 100644
--- a/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminEnableAttributeIsUndefinedCatalogPriceRuleTest.xml
+++ b/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminEnableAttributeIsUndefinedCatalogPriceRuleTest.xml
@@ -44,7 +44,7 @@
website
-
+
@@ -64,7 +64,7 @@
-
+
@@ -80,7 +80,6 @@
-
@@ -129,7 +128,6 @@
-
diff --git a/app/code/Magento/CatalogSearch/Test/Mftf/ActionGroup/StorefrontAssertUnableSearchNegativeForPriceFieldActionGroup.xml b/app/code/Magento/CatalogSearch/Test/Mftf/ActionGroup/StorefrontAssertUnableSearchNegativeForPriceFieldActionGroup.xml
new file mode 100644
index 0000000000000..003cd0a0806e7
--- /dev/null
+++ b/app/code/Magento/CatalogSearch/Test/Mftf/ActionGroup/StorefrontAssertUnableSearchNegativeForPriceFieldActionGroup.xml
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+
+
+ {{UnableNegativePrice.Error_message}}
+ $grabPriceFromError
+
+
+ {{UnableNegativePrice.Error_message}}
+ $grabPriceToError
+
+
+
diff --git a/app/code/Magento/CatalogSearch/Test/Mftf/Data/MinMaxQueryLengthHintsData.xml b/app/code/Magento/CatalogSearch/Test/Mftf/Data/MessageAndHintData.xml
similarity index 76%
rename from app/code/Magento/CatalogSearch/Test/Mftf/Data/MinMaxQueryLengthHintsData.xml
rename to app/code/Magento/CatalogSearch/Test/Mftf/Data/MessageAndHintData.xml
index 6fb254afea347..98ef8de72b633 100644
--- a/app/code/Magento/CatalogSearch/Test/Mftf/Data/MinMaxQueryLengthHintsData.xml
+++ b/app/code/Magento/CatalogSearch/Test/Mftf/Data/MessageAndHintData.xml
@@ -11,4 +11,7 @@
This value must be compatible with the corresponding setting in the configured search engine
+
+ Please enter a number 0 or greater in this field.
+
diff --git a/app/code/Magento/CatalogSearch/Test/Mftf/Section/StorefrontCatalogSearchAdvancedFormSection.xml b/app/code/Magento/CatalogSearch/Test/Mftf/Section/StorefrontCatalogSearchAdvancedFormSection.xml
index 6889025530098..540243abc6ad1 100644
--- a/app/code/Magento/CatalogSearch/Test/Mftf/Section/StorefrontCatalogSearchAdvancedFormSection.xml
+++ b/app/code/Magento/CatalogSearch/Test/Mftf/Section/StorefrontCatalogSearchAdvancedFormSection.xml
@@ -16,6 +16,8 @@
+
+
diff --git a/app/code/Magento/CatalogSearch/Test/Mftf/Test/StorefrontCheckUnableAdvancedSearchWithNegativePriceTest.xml b/app/code/Magento/CatalogSearch/Test/Mftf/Test/StorefrontCheckUnableAdvancedSearchWithNegativePriceTest.xml
new file mode 100644
index 0000000000000..67e9fdd43f5fe
--- /dev/null
+++ b/app/code/Magento/CatalogSearch/Test/Mftf/Test/StorefrontCheckUnableAdvancedSearchWithNegativePriceTest.xml
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/CatalogSearch/view/frontend/templates/advanced/form.phtml b/app/code/Magento/CatalogSearch/view/frontend/templates/advanced/form.phtml
index 3712f221233ee..f158cebf41aae 100644
--- a/app/code/Magento/CatalogSearch/view/frontend/templates/advanced/form.phtml
+++ b/app/code/Magento/CatalogSearch/view/frontend/templates/advanced/form.phtml
@@ -5,6 +5,7 @@
*/
// phpcs:disable Magento2.Templates.ThisInTemplate.FoundThis
+// @codingStandardsIgnoreFile
?>
escapeHtmlAttr($maxQueryLength) ?>"
- data-validate="{number:true, 'less-than-equals-to':'#= $block->escapeHtmlAttr($_code) ?>_to'}" />
+ data-validate="{number:true, 'validate-not-negative-number':true, 'less-than-equals-to':'#= $block->escapeHtmlAttr($_code) ?>_to'}" />
@@ -81,7 +82,7 @@
class="input-text"
type="text"
maxlength="= $block->escapeHtmlAttr($maxQueryLength) ?>"
- data-validate="{number:true, 'greater-than-equals-to':'#= $block->escapeHtmlAttr($_code) ?>'}" />
+ data-validate="{number:true, 'validate-not-negative-number':true, 'greater-than-equals-to':'#= $block->escapeHtmlAttr($_code) ?>'}" />
= $block->escapeHtml($block->getCurrency($_attribute)) ?>
diff --git a/app/code/Magento/CatalogUrlRewrite/Test/Mftf/Test/AdminUrlForProductRewrittenCorrectlyTest.xml b/app/code/Magento/CatalogUrlRewrite/Test/Mftf/Test/AdminUrlForProductRewrittenCorrectlyTest.xml
index 75ae9d821c356..329f5e8cae3f6 100644
--- a/app/code/Magento/CatalogUrlRewrite/Test/Mftf/Test/AdminUrlForProductRewrittenCorrectlyTest.xml
+++ b/app/code/Magento/CatalogUrlRewrite/Test/Mftf/Test/AdminUrlForProductRewrittenCorrectlyTest.xml
@@ -72,11 +72,13 @@
-
-
-
-
-
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Checkout/Block/Cart/Crosssell.php b/app/code/Magento/Checkout/Block/Cart/Crosssell.php
index 06be50d05aefc..99408003b981b 100644
--- a/app/code/Magento/Checkout/Block/Cart/Crosssell.php
+++ b/app/code/Magento/Checkout/Block/Cart/Crosssell.php
@@ -5,15 +5,29 @@
*/
namespace Magento\Checkout\Block\Cart;
+use Magento\Catalog\Api\Data\ProductInterface;
+use Magento\Catalog\Api\ProductRepositoryInterface;
+use Magento\Catalog\Block\Product\AbstractProduct;
+use Magento\Catalog\Block\Product\Context;
+use Magento\Catalog\Model\Product;
+use Magento\Catalog\Model\Product\LinkFactory;
+use Magento\Catalog\Model\Product\Visibility;
+use Magento\Catalog\Model\ResourceModel\Product\Collection;
+use Magento\Catalog\Model\ResourceModel\Product\CollectionFactory;
use Magento\CatalogInventory\Helper\Stock as StockHelper;
+use Magento\Checkout\Model\Session;
+use Magento\Framework\App\ObjectManager;
+use Magento\Framework\Exception\NoSuchEntityException;
+use Magento\Quote\Model\Quote\Item\RelatedProducts;
/**
* Cart crosssell list
*
* @api
* @author Magento Core Team
+ * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
*/
-class Crosssell extends \Magento\Catalog\Block\Product\AbstractProduct
+class Crosssell extends AbstractProduct
{
/**
* Items quantity will be capped to this value
@@ -23,12 +37,12 @@ class Crosssell extends \Magento\Catalog\Block\Product\AbstractProduct
protected $_maxItemCount = 4;
/**
- * @var \Magento\Checkout\Model\Session
+ * @var Session
*/
protected $_checkoutSession;
/**
- * @var \Magento\Catalog\Model\Product\Visibility
+ * @var Visibility
*/
protected $_productVisibility;
@@ -38,35 +52,53 @@ class Crosssell extends \Magento\Catalog\Block\Product\AbstractProduct
protected $stockHelper;
/**
- * @var \Magento\Catalog\Model\Product\LinkFactory
+ * @var LinkFactory
*/
protected $_productLinkFactory;
/**
- * @var \Magento\Quote\Model\Quote\Item\RelatedProducts
+ * @var RelatedProducts
*/
protected $_itemRelationsList;
/**
- * @param \Magento\Catalog\Block\Product\Context $context
- * @param \Magento\Checkout\Model\Session $checkoutSession
- * @param \Magento\Catalog\Model\Product\Visibility $productVisibility
- * @param \Magento\Catalog\Model\Product\LinkFactory $productLinkFactory
- * @param \Magento\Quote\Model\Quote\Item\RelatedProducts $itemRelationsList
+ * @var CollectionFactory|null
+ */
+ private $productCollectionFactory;
+
+ /**
+ * @var ProductRepositoryInterface|null
+ */
+ private $productRepository;
+
+ /**
+ * @var Product[]
+ */
+ private $cartProducts;
+
+ /**
+ * @param Context $context
+ * @param Session $checkoutSession
+ * @param Visibility $productVisibility
+ * @param LinkFactory $productLinkFactory
+ * @param RelatedProducts $itemRelationsList
* @param StockHelper $stockHelper
* @param array $data
- *
+ * @param CollectionFactory|null $productCollectionFactory
+ * @param ProductRepositoryInterface|null $productRepository
* @codeCoverageIgnore
* @SuppressWarnings(PHPMD.ExcessiveParameterList)
*/
public function __construct(
- \Magento\Catalog\Block\Product\Context $context,
- \Magento\Checkout\Model\Session $checkoutSession,
- \Magento\Catalog\Model\Product\Visibility $productVisibility,
- \Magento\Catalog\Model\Product\LinkFactory $productLinkFactory,
- \Magento\Quote\Model\Quote\Item\RelatedProducts $itemRelationsList,
+ Context $context,
+ Session $checkoutSession,
+ Visibility $productVisibility,
+ LinkFactory $productLinkFactory,
+ RelatedProducts $itemRelationsList,
StockHelper $stockHelper,
- array $data = []
+ array $data = [],
+ ?CollectionFactory $productCollectionFactory = null,
+ ?ProductRepositoryInterface $productRepository = null
) {
$this->_checkoutSession = $checkoutSession;
$this->_productVisibility = $productVisibility;
@@ -78,6 +110,10 @@ public function __construct(
$data
);
$this->_isScopePrivate = true;
+ $this->productCollectionFactory = $productCollectionFactory
+ ?? ObjectManager::getInstance()->get(CollectionFactory::class);
+ $this->productRepository = $productRepository
+ ?? ObjectManager::getInstance()->get(ProductRepositoryInterface::class);
}
/**
@@ -92,9 +128,10 @@ public function getItems()
$items = [];
$ninProductIds = $this->_getCartProductIds();
if ($ninProductIds) {
- $lastAdded = (int)$this->_getLastAddedProductId();
- if ($lastAdded) {
- $collection = $this->_getCollection()->addProductFilter($lastAdded);
+ $lastAddedProduct = $this->getLastAddedProduct();
+ if ($lastAddedProduct) {
+ $collection = $this->_getCollection()
+ ->addProductFilter($lastAddedProduct->getData($this->getProductLinkField()));
if (!empty($ninProductIds)) {
$collection->addExcludeProductFilter($ninProductIds);
}
@@ -108,8 +145,8 @@ public function getItems()
if (count($items) < $this->_maxItemCount) {
$filterProductIds = array_merge(
- $this->_getCartProductIds(),
- $this->_itemRelationsList->getRelatedProductIds($this->getQuote()->getAllItems())
+ $this->getCartProductLinkIds(),
+ $this->getCartRelatedProductLinkIds()
);
$collection = $this->_getCollection()->addProductFilter(
$filterProductIds
@@ -150,11 +187,8 @@ protected function _getCartProductIds()
$ids = $this->getData('_cart_product_ids');
if ($ids === null) {
$ids = [];
- foreach ($this->getQuote()->getAllItems() as $item) {
- $product = $item->getProduct();
- if ($product) {
- $ids[] = $product->getId();
- }
+ foreach ($this->getCartProducts() as $product) {
+ $ids[] = $product->getId();
}
$this->setData('_cart_product_ids', $ids);
}
@@ -202,4 +236,93 @@ protected function _getCollection()
return $collection;
}
+
+ /**
+ * Get product link ID field
+ *
+ * @return string
+ */
+ private function getProductLinkField(): string
+ {
+ /* @var $collection Collection */
+ $collection = $this->productCollectionFactory->create();
+ return $collection->getProductEntityMetadata()->getLinkField();
+ }
+
+ /**
+ * Get cart products link IDs
+ *
+ * @return array
+ */
+ private function getCartProductLinkIds(): array
+ {
+ $linkField = $this->getProductLinkField();
+ $linkIds = [];
+ foreach ($this->getCartProducts() as $product) {
+ /** * @var Product $product */
+ $linkIds[] = $product->getData($linkField);
+ }
+ return $linkIds;
+ }
+
+ /**
+ * Get cart related products link IDs
+ *
+ * @return array
+ */
+ private function getCartRelatedProductLinkIds(): array
+ {
+ $productIds = $this->_itemRelationsList->getRelatedProductIds($this->getQuote()->getAllItems());
+ $linkIds = [];
+ if (!empty($productIds)) {
+ $linkField = $this->getProductLinkField();
+ /* @var $collection Collection */
+ $collection = $this->productCollectionFactory->create();
+ $collection->addIdFilter($productIds);
+ foreach ($collection as $product) {
+ /** * @var Product $product */
+ $linkIds[] = $product->getData($linkField);
+ }
+ }
+ return $linkIds;
+ }
+
+ /**
+ * Retrieve just added to cart product object
+ *
+ * @return ProductInterface|null
+ */
+ private function getLastAddedProduct(): ?ProductInterface
+ {
+ $product = null;
+ $productId = $this->_getLastAddedProductId();
+ if ($productId) {
+ try {
+ $product = $this->productRepository->getById($productId);
+ } catch (NoSuchEntityException $e) {
+ $product = null;
+ }
+ }
+ return $product;
+ }
+
+ /**
+ * Retrieve Array of Product instances in Cart
+ *
+ * @return array
+ */
+ private function getCartProducts(): array
+ {
+ if ($this->cartProducts === null) {
+ $this->cartProducts = [];
+ foreach ($this->getQuote()->getAllItems() as $quoteItem) {
+ /* @var $quoteItem \Magento\Quote\Model\Quote\Item */
+ $product = $quoteItem->getProduct();
+ if ($product) {
+ $this->cartProducts[$product->getEntityId()] = $product;
+ }
+ }
+ }
+ return $this->cartProducts;
+ }
}
diff --git a/app/code/Magento/Checkout/Block/Checkout/AttributeMerger.php b/app/code/Magento/Checkout/Block/Checkout/AttributeMerger.php
index 1dd1131cde1f1..0e7931146b4c4 100644
--- a/app/code/Magento/Checkout/Block/Checkout/AttributeMerger.php
+++ b/app/code/Magento/Checkout/Block/Checkout/AttributeMerger.php
@@ -10,6 +10,8 @@
use Magento\Customer\Helper\Address as AddressHelper;
use Magento\Customer\Model\Session;
use Magento\Directory\Helper\Data as DirectoryHelper;
+use Magento\Directory\Model\AllowedCountries;
+use Magento\Framework\App\ObjectManager;
use Magento\Framework\Exception\LocalizedException;
use Magento\Framework\Exception\NoSuchEntityException;
@@ -92,23 +94,32 @@ class AttributeMerger
*/
private $topCountryCodes;
+ /**
+ * @var AllowedCountries|null
+ */
+ private $allowedCountryReader;
+
/**
* @param AddressHelper $addressHelper
* @param Session $customerSession
* @param CustomerRepository $customerRepository
* @param DirectoryHelper $directoryHelper
+ * @param AllowedCountries $allowedCountryReader
*/
public function __construct(
AddressHelper $addressHelper,
Session $customerSession,
CustomerRepository $customerRepository,
- DirectoryHelper $directoryHelper
+ DirectoryHelper $directoryHelper,
+ ?AllowedCountries $allowedCountryReader = null
) {
$this->addressHelper = $addressHelper;
$this->customerSession = $customerSession;
$this->customerRepository = $customerRepository;
$this->directoryHelper = $directoryHelper;
$this->topCountryCodes = $directoryHelper->getTopCountryCodes();
+ $this->allowedCountryReader =
+ $allowedCountryReader ?: ObjectManager::getInstance()->get(AllowedCountries::class);
}
/**
@@ -289,7 +300,7 @@ protected function getMultilineFieldConfig($attributeCode, array $attributeConfi
'dataScope' => $lineIndex,
'provider' => $providerName,
'validation' => $isFirstLine
- //phpcs:ignore Magento2.Performance.ForeachArrayMerge
+ // phpcs:ignore Magento2.Performance.ForeachArrayMerge
? array_merge(
['required-entry' => (bool)$attributeConfig['required']],
$attributeConfig['validation']
@@ -330,7 +341,11 @@ protected function getMultilineFieldConfig($attributeCode, array $attributeConfi
protected function getDefaultValue($attributeCode): ?string
{
if ($attributeCode === 'country_id') {
- return $this->directoryHelper->getDefaultCountry();
+ $defaultCountryId = $this->directoryHelper->getDefaultCountry();
+ if (!in_array($defaultCountryId, $this->allowedCountryReader->getAllowedCountries())) {
+ $defaultCountryId = null;
+ }
+ return $defaultCountryId;
}
$customer = $this->getCustomer();
diff --git a/app/code/Magento/Checkout/Test/Unit/Block/Cart/CrosssellTest.php b/app/code/Magento/Checkout/Test/Unit/Block/Cart/CrosssellTest.php
new file mode 100644
index 0000000000000..5eedb42744902
--- /dev/null
+++ b/app/code/Magento/Checkout/Test/Unit/Block/Cart/CrosssellTest.php
@@ -0,0 +1,348 @@
+storeManager = $this->createMock(
+ StoreManagerInterface::class
+ );
+ $this->context = $objectManager->getObject(
+ Context::class,
+ [
+ 'storeManager' => $this->storeManager
+ ]
+ );
+ $this->checkoutSession = $this->getMockBuilder(Session::class)
+ ->addMethods(['getLastAddedProductId'])
+ ->onlyMethods(['getQuote'])
+ ->disableOriginalConstructor()
+ ->getMock();
+ $this->productRepository = $this->createMock(
+ ProductRepositoryInterface::class
+ );
+ $this->productLinkFactory = $this->createMock(
+ LinkFactory::class
+ );
+ $this->productLinkFactory = $this->createMock(
+ LinkFactory::class
+ );
+ $this->productCollectionFactory = $this->createMock(
+ CollectionFactory::class
+ );
+ $this->model = $objectManager->getObject(
+ Crosssell::class,
+ [
+ 'context' => $this->context,
+ 'checkoutSession' => $this->checkoutSession,
+ 'productRepository' => $this->productRepository,
+ 'productLinkFactory' => $this->productLinkFactory,
+ 'productCollectionFactory' => $this->productCollectionFactory,
+ ]
+ );
+ }
+
+ /**
+ * @dataProvider getItemsDataProvider
+ * @param array $productLinks
+ * @param array $cartProductIds
+ * @param int|null $lastAddedProductId
+ * @param array $expected
+ */
+ public function testGetItems(
+ array $productLinks,
+ array $cartProductIds,
+ ?int $lastAddedProductId,
+ array $expected
+ ) {
+ $this->productLinks = $productLinks;
+ $cartProducts = array_map(
+ function ($id) {
+ return $this->getProduct(['entity_id' => $id]);
+ },
+ $cartProductIds
+ );
+ $cartItems = array_map(
+ function ($product) {
+ return new DataObject(['product' => $product]);
+ },
+ $cartProducts
+ );
+ $quote = new DataObject(['all_items' => $cartItems]);
+ $this->checkoutSession->method('getQuote')
+ ->willReturn($quote);
+ $this->checkoutSession->method('getLastAddedProductId')
+ ->willReturn($lastAddedProductId);
+ $this->productRepository->method('getById')
+ ->willReturnCallback(
+ function ($id) {
+ return $this->getProduct(['entity_id' => $id]);
+ }
+ );
+ $link = $this->createMock(Link::class);
+ $this->productLinkFactory->method('create')
+ ->willReturn($link);
+ $link->method('useCrossSellLinks')
+ ->willReturnSelf();
+ $link->method('getProductCollection')
+ ->willReturnCallback(
+ function () {
+ return $this->createLinkCollection();
+ }
+ );
+ $this->productCollectionFactory->method('create')
+ ->willReturnCallback(
+ function () {
+ return $this->createProductCollection();
+ }
+ );
+ $store = $this->createMock(StoreInterface::class);
+ $this->storeManager->method('getStore')
+ ->willReturn($store);
+ $actual = array_map(
+ function ($product) {
+ return $product->getId();
+ },
+ $this->model->getItems()
+ );
+ $this->assertEquals($expected, $actual);
+ }
+
+ /**
+ * @return array
+ */
+ public function getItemsDataProvider(): array
+ {
+ $links = [
+ 1001 => [
+ 1003,
+ 1005
+ ],
+ 1006 => [
+ 1002,
+ ]
+ ];
+ return [
+ [
+ 'productLinks' => $links,
+ 'cartProducts' => [
+ 1001,
+ 1006,
+ ],
+ 'lastAddedProduct' => 1006,
+ 'cross-sells' => [
+ 1002,
+ 1003,
+ 1005
+ ]
+ ],
+ [
+ 'productLinks' => $links,
+ 'cartProducts' => [
+ 1001,
+ 1006,
+ ],
+ 'lastAddedProduct' => null,
+ 'cross-sells' => [
+ 1003,
+ 1005,
+ 1002,
+ ]
+ ],
+ [
+ 'productLinks' => $links,
+ 'cartProducts' => [
+ 1001,
+ 1005,
+ ],
+ 'lastAddedProduct' => null,
+ 'cross-sells' => [
+ 1003
+ ]
+ ],
+ [
+ 'productLinks' => $links,
+ 'cartProducts' => [
+ 1002,
+ 1003,
+ ],
+ 'lastAddedProduct' => null,
+ 'cross-sells' => [
+ ]
+ ]
+ ];
+ }
+
+ /**
+ * @param array $data
+ * @return MockObject
+ */
+ private function getProduct(array $data): MockObject
+ {
+ $product = $this->createPartialMock(Product::class, []);
+ $product->setData($data);
+ return $product;
+ }
+
+ /**
+ * @return MockObject
+ */
+ private function createLinkCollection(): MockObject
+ {
+ $returnSelfMethods = [
+ 'setStoreId',
+ 'setPageSize',
+ 'setGroupBy',
+ 'setVisibility',
+ 'addMinimalPrice',
+ 'addFinalPrice',
+ 'addTaxPercents',
+ 'addAttributeToSelect',
+ 'addStoreFilter',
+ 'setPositionOrder',
+ 'addUrlRewrite',
+ 'load',
+ ];
+ $linkCollection = $this->createPartialMock(
+ Collection::class,
+ array_merge(
+ $returnSelfMethods,
+ [
+ 'addProductFilter',
+ 'addExcludeProductFilter',
+ 'getSelect',
+ 'getIterator',
+ ]
+ )
+ );
+ foreach ($returnSelfMethods as $method) {
+ $linkCollection->method($method)
+ ->willReturnSelf();
+ }
+ $linkCollection->method('addProductFilter')
+ ->willReturnCallback(
+ function ($products) use ($linkCollection) {
+ if (!is_array($products)) {
+ $products = [$products];
+ }
+ $linkCollection->setFlag('test_product_ids', $products);
+ return $linkCollection;
+ }
+ );
+ $linkCollection->method('addExcludeProductFilter')
+ ->willReturnCallback(
+ function ($products) use ($linkCollection) {
+ if (!is_array($products)) {
+ $products = [$products];
+ }
+ $linkCollection->setFlag('test_exclude_product_ids', $products);
+ return $linkCollection;
+ }
+ );
+ $linkCollection->method('getSelect')->willReturn(
+ $this->createMock(Select::class)
+ );
+ $linkCollection->method('getIterator')
+ ->willReturnCallback(
+ function () use ($linkCollection) {
+ $productIds = $linkCollection->getFlag('test_product_ids') ?? [];
+ $excludeProductIds = $linkCollection->getFlag('test_exclude_product_ids') ?? [];
+ $links = [];
+ foreach ($productIds as $id) {
+ if (isset($this->productLinks[$id])) {
+ array_push($links, ...$this->productLinks[$id]);
+ }
+ }
+ $links = array_values(array_unique(array_diff($links, $excludeProductIds)));
+ $links = array_combine($links, $links);
+ $products = array_map(
+ function ($id) {
+ return $this->getProduct(['entity_id' => $id]);
+ },
+ $links
+ );
+ return new \ArrayIterator($products);
+ }
+ );
+ return $linkCollection;
+ }
+
+ /**
+ * @return MockObject
+ */
+ private function createProductCollection(): MockObject
+ {
+ $productCollection = $this->createMock(\Magento\Catalog\Model\ResourceModel\Product\Collection::class);
+ $entityMetadataInterface =$this->createMock(EntityMetadataInterface::class);
+ $entityMetadataInterface->method('getLinkField')
+ ->willReturn('entity_id');
+ $productCollection->method('getProductEntityMetadata')
+ ->willReturn($entityMetadataInterface);
+ return $productCollection;
+ }
+}
diff --git a/app/code/Magento/Cms/Test/Mftf/ActionGroup/AdminDeleteCmsPageFromGridActionGroup.xml b/app/code/Magento/Cms/Test/Mftf/ActionGroup/AdminDeleteCmsPageFromGridActionGroup.xml
new file mode 100644
index 0000000000000..2bd81104c7560
--- /dev/null
+++ b/app/code/Magento/Cms/Test/Mftf/ActionGroup/AdminDeleteCmsPageFromGridActionGroup.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Cms/Test/Mftf/ActionGroup/AdminSearchCmsPageInGridByUrlKeyActionGroup.xml b/app/code/Magento/Cms/Test/Mftf/ActionGroup/AdminSearchCmsPageInGridByUrlKeyActionGroup.xml
new file mode 100644
index 0000000000000..d18d92efbe757
--- /dev/null
+++ b/app/code/Magento/Cms/Test/Mftf/ActionGroup/AdminSearchCmsPageInGridByUrlKeyActionGroup.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Cms/Test/Mftf/ActionGroup/AssertAdminCmsPageIsNotInGridActionGroup.xml b/app/code/Magento/Cms/Test/Mftf/ActionGroup/AssertAdminCmsPageIsNotInGridActionGroup.xml
new file mode 100644
index 0000000000000..7884a9d865a2d
--- /dev/null
+++ b/app/code/Magento/Cms/Test/Mftf/ActionGroup/AssertAdminCmsPageIsNotInGridActionGroup.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Cms/Test/Mftf/Section/CmsPagesPageActionsSection.xml b/app/code/Magento/Cms/Test/Mftf/Section/CmsPagesPageActionsSection.xml
index 494c98ca44e7f..6f16fa54a6ebf 100644
--- a/app/code/Magento/Cms/Test/Mftf/Section/CmsPagesPageActionsSection.xml
+++ b/app/code/Magento/Cms/Test/Mftf/Section/CmsPagesPageActionsSection.xml
@@ -30,5 +30,6 @@
+
diff --git a/app/code/Magento/Cms/Test/Mftf/Test/AdminDeleteCmsPageTest.xml b/app/code/Magento/Cms/Test/Mftf/Test/AdminDeleteCmsPageTest.xml
new file mode 100644
index 0000000000000..c46410dce919e
--- /dev/null
+++ b/app/code/Magento/Cms/Test/Mftf/Test/AdminDeleteCmsPageTest.xml
@@ -0,0 +1,49 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminConfigurableProductUpdateTest/AdminConfigurableProductBulkUpdateTest.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminConfigurableProductUpdateTest/AdminConfigurableProductBulkUpdateTest.xml
index bd409d0e4bfde..186799bf4626b 100644
--- a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminConfigurableProductUpdateTest/AdminConfigurableProductBulkUpdateTest.xml
+++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminConfigurableProductUpdateTest/AdminConfigurableProductBulkUpdateTest.xml
@@ -65,10 +65,11 @@
-
-
-
-
+
+
+
+
+
diff --git a/app/code/Magento/ConfigurableProduct/view/adminhtml/web/js/components/dynamic-rows-configurable.js b/app/code/Magento/ConfigurableProduct/view/adminhtml/web/js/components/dynamic-rows-configurable.js
index 4624f07323d59..68e7d146d33e0 100644
--- a/app/code/Magento/ConfigurableProduct/view/adminhtml/web/js/components/dynamic-rows-configurable.js
+++ b/app/code/Magento/ConfigurableProduct/view/adminhtml/web/js/components/dynamic-rows-configurable.js
@@ -212,7 +212,7 @@ define([
);
tmpData = data.slice(this.pageSize * (this.currentPage() - 1),
- this.pageSize * (this.currentPage() - 1) + this.pageSize);
+ this.pageSize * (this.currentPage() - 1) + parseInt(this.pageSize, 10));
this.source.set(this.dataScope + '.' + this.index, []);
diff --git a/app/code/Magento/ConfigurableProduct/view/adminhtml/web/template/variations/steps/summary-grid.html b/app/code/Magento/ConfigurableProduct/view/adminhtml/web/template/variations/steps/summary-grid.html
index ff247c14bf52b..bf1e1ad5fe1e1 100644
--- a/app/code/Magento/ConfigurableProduct/view/adminhtml/web/template/variations/steps/summary-grid.html
+++ b/app/code/Magento/ConfigurableProduct/view/adminhtml/web/template/variations/steps/summary-grid.html
@@ -5,14 +5,14 @@
*/
-->
-
-
+
+
-
+
diff --git a/app/code/Magento/ConfigurableProduct/view/frontend/web/js/configurable.js b/app/code/Magento/ConfigurableProduct/view/frontend/web/js/configurable.js
index 8fd1d761040d7..f705b6a95987c 100644
--- a/app/code/Magento/ConfigurableProduct/view/frontend/web/js/configurable.js
+++ b/app/code/Magento/ConfigurableProduct/view/frontend/web/js/configurable.js
@@ -33,7 +33,7 @@ define([
mediaGallerySelector: '[data-gallery-role=gallery-placeholder]',
mediaGalleryInitial: null,
slyOldPriceSelector: '.sly-old-price',
- normalPriceLabelSelector: '.normal-price .price-label',
+ normalPriceLabelSelector: '.product-info-main .normal-price .price-label',
/**
* Defines the mechanism of how images of a gallery should be
diff --git a/app/code/Magento/Customer/Controller/Adminhtml/Index/Save.php b/app/code/Magento/Customer/Controller/Adminhtml/Index/Save.php
index a65bfa5d77f9e..977d3753ded65 100644
--- a/app/code/Magento/Customer/Controller/Adminhtml/Index/Save.php
+++ b/app/code/Magento/Customer/Controller/Adminhtml/Index/Save.php
@@ -248,7 +248,7 @@ protected function _extractData(
* @param array $extractedCustomerData
* @return array
*/
- protected function saveDefaultFlags(array $addressIdList, array & $extractedCustomerData)
+ protected function saveDefaultFlags(array $addressIdList, array &$extractedCustomerData)
{
$result = [];
$extractedCustomerData[CustomerInterface::DEFAULT_BILLING] = null;
@@ -290,7 +290,7 @@ protected function saveDefaultFlags(array $addressIdList, array & $extractedCust
* @param array $extractedCustomerData
* @return array
*/
- protected function _extractCustomerAddressData(array & $extractedCustomerData)
+ protected function _extractCustomerAddressData(array &$extractedCustomerData)
{
$addresses = $this->getRequest()->getPost('address');
$result = [];
@@ -380,6 +380,12 @@ public function execute()
$this->_coreRegistry->register(RegistryConstants::CURRENT_CUSTOMER_ID, $customerId);
$this->messageManager->addSuccessMessage(__('You saved the customer.'));
$returnToEdit = (bool)$this->getRequest()->getParam('back', false);
+ } catch (NoSuchEntityException $exception) {
+ $this->messageManager->addExceptionMessage(
+ $exception,
+ __('Something went wrong while saving the customer.')
+ );
+ $returnToEdit = false;
} catch (\Magento\Framework\Validator\Exception $exception) {
$messages = $exception->getMessages();
if (empty($messages)) {
diff --git a/app/code/Magento/Customer/Model/Customer/DataProviderWithDefaultAddresses.php b/app/code/Magento/Customer/Model/Customer/DataProviderWithDefaultAddresses.php
index fbf9cf4cbbf9a..604295cc0c078 100644
--- a/app/code/Magento/Customer/Model/Customer/DataProviderWithDefaultAddresses.php
+++ b/app/code/Magento/Customer/Model/Customer/DataProviderWithDefaultAddresses.php
@@ -9,17 +9,20 @@
use Magento\Customer\Model\Address;
use Magento\Customer\Model\Customer;
use Magento\Customer\Model\ResourceModel\Customer\CollectionFactory as CustomerCollectionFactory;
+use Magento\Directory\Model\CountryFactory;
use Magento\Eav\Model\Config;
use Magento\Eav\Model\Entity\Attribute\AbstractAttribute;
use Magento\Eav\Model\Entity\Type;
+use Magento\Framework\Exception\LocalizedException;
use Magento\Framework\Session\SessionManagerInterface;
use Magento\Customer\Model\FileUploaderDataResolver;
use Magento\Customer\Model\AttributeMetadataResolver;
+use Magento\Ui\DataProvider\AbstractDataProvider;
/**
* Refactored version of Magento\Customer\Model\Customer\DataProvider with eliminated usage of addresses collection.
*/
-class DataProviderWithDefaultAddresses extends \Magento\Ui\DataProvider\AbstractDataProvider
+class DataProviderWithDefaultAddresses extends AbstractDataProvider
{
/**
* @var array
@@ -49,7 +52,7 @@ class DataProviderWithDefaultAddresses extends \Magento\Ui\DataProvider\Abstract
private $allowToShowHiddenAttributes;
/**
- * @var \Magento\Directory\Model\CountryFactory
+ * @var CountryFactory
*/
private $countryFactory;
@@ -69,14 +72,14 @@ class DataProviderWithDefaultAddresses extends \Magento\Ui\DataProvider\Abstract
* @param string $requestFieldName
* @param CustomerCollectionFactory $customerCollectionFactory
* @param Config $eavConfig
- * @param \Magento\Directory\Model\CountryFactory $countryFactory
+ * @param CountryFactory $countryFactory
* @param SessionManagerInterface $session
* @param FileUploaderDataResolver $fileUploaderDataResolver
* @param AttributeMetadataResolver $attributeMetadataResolver
* @param bool $allowToShowHiddenAttributes
* @param array $meta
* @param array $data
- * @throws \Magento\Framework\Exception\LocalizedException
+ * @throws LocalizedException
* @SuppressWarnings(PHPMD.ExcessiveParameterList)
*/
public function __construct(
@@ -85,7 +88,7 @@ public function __construct(
string $requestFieldName,
CustomerCollectionFactory $customerCollectionFactory,
Config $eavConfig,
- \Magento\Directory\Model\CountryFactory $countryFactory,
+ CountryFactory $countryFactory,
SessionManagerInterface $session,
FileUploaderDataResolver $fileUploaderDataResolver,
AttributeMetadataResolver $attributeMetadataResolver,
@@ -158,17 +161,21 @@ public function getData(): array
*/
private function prepareDefaultAddress($address): array
{
- $addressData = [];
-
- if (!empty($address)) {
- $addressData = $address->getData();
- if (isset($addressData['street']) && !\is_array($address['street'])) {
- $addressData['street'] = explode("\n", $addressData['street']);
- }
- $countryId = $addressData['country_id'] ?? null;
- $addressData['country'] = $this->countryFactory->create()->loadByCode($countryId)->getName();
+ if (!$address) {
+ return [];
}
+ $addressData = $address->getData();
+ if (isset($addressData['street']) && !is_array($addressData['street'])) {
+ $addressData['street'] = explode("\n", $addressData['street']);
+ }
+ if (!empty($addressData['country_id'])) {
+ $addressData['country'] = $this->countryFactory->create()
+ ->loadByCode($addressData['country_id'])
+ ->getName();
+ }
+ $addressData['region'] = $address->getRegion();
+
return $addressData;
}
@@ -177,7 +184,7 @@ private function prepareDefaultAddress($address): array
*
* @param Type $entityType
* @return array
- * @throws \Magento\Framework\Exception\LocalizedException
+ * @throws LocalizedException
*/
private function getAttributesMeta(Type $entityType): array
{
diff --git a/app/code/Magento/Customer/Model/CustomerExtractor.php b/app/code/Magento/Customer/Model/CustomerExtractor.php
index 5d6f3245a0439..b9739ce0424a1 100644
--- a/app/code/Magento/Customer/Model/CustomerExtractor.php
+++ b/app/code/Magento/Customer/Model/CustomerExtractor.php
@@ -93,13 +93,15 @@ public function extract(
$customerData,
\Magento\Customer\Api\Data\CustomerInterface::class
);
-
+
$store = $this->storeManager->getStore();
$storeId = $store->getId();
-
+
if ($isGroupIdEmpty) {
+ $groupId = isset($customerData['group_id']) ? $customerData['group_id']
+ : $this->customerGroupManagement->getDefaultGroup($storeId)->getId();
$customerDataObject->setGroupId(
- $this->customerGroupManagement->getDefaultGroup($storeId)->getId()
+ $groupId
);
}
diff --git a/app/code/Magento/Customer/Model/ResourceModel/Address/Grid/Collection.php b/app/code/Magento/Customer/Model/ResourceModel/Address/Grid/Collection.php
index 4e0347059086f..0e2eb3e1d8e65 100644
--- a/app/code/Magento/Customer/Model/ResourceModel/Address/Grid/Collection.php
+++ b/app/code/Magento/Customer/Model/ResourceModel/Address/Grid/Collection.php
@@ -6,53 +6,84 @@
*/
namespace Magento\Customer\Model\ResourceModel\Address\Grid;
+use Magento\Framework\Api\ExtensibleDataInterface;
use Magento\Framework\Api\Search\SearchResultInterface;
use Magento\Framework\Api\Search\AggregationInterface;
+use Magento\Framework\Api\SearchCriteriaInterface;
+use Magento\Framework\Data\Collection\Db\FetchStrategyInterface;
+use Magento\Framework\Data\Collection\EntityFactoryInterface;
+use Magento\Framework\DB\Adapter\AdapterInterface;
+use Magento\Framework\Event\ManagerInterface;
+use Magento\Framework\Locale\ResolverInterface;
+use Magento\Framework\Model\ResourceModel\Db\AbstractDb;
use Magento\Framework\Model\ResourceModel\Db\Collection\AbstractCollection;
+use Magento\Framework\View\Element\UiComponent\DataProvider\Document;
+use Psr\Log\LoggerInterface;
/**
* Class getting collection of addresses assigned to customer
*/
class Collection extends AbstractCollection implements SearchResultInterface
{
+ /**
+ * List of fields to fulltext search
+ */
+ private const FIELDS_TO_FULLTEXT_SEARCH = [
+ 'firstname',
+ 'lastname',
+ 'street',
+ 'city',
+ 'region',
+ 'postcode',
+ 'telephone',
+ ];
+
/**
* @var AggregationInterface
*/
private $aggregations;
/**
- * @param \Magento\Framework\Data\Collection\EntityFactoryInterface $entityFactory
- * @param \Psr\Log\LoggerInterface $logger
- * @param \Magento\Framework\Data\Collection\Db\FetchStrategyInterface $fetchStrategy
- * @param \Magento\Framework\Event\ManagerInterface $eventManager
+ * @var ResolverInterface
+ */
+ private $localeResolver;
+
+ /**
+ * @param EntityFactoryInterface $entityFactory
+ * @param LoggerInterface $logger
+ * @param FetchStrategyInterface $fetchStrategy
+ * @param ManagerInterface $eventManager
* @param string $mainTable
* @param string $eventPrefix
* @param string $eventObject
* @param string $resourceModel
+ * @param ResolverInterface $localeResolver
* @param string $model
- * @param \Magento\Framework\DB\Adapter\AdapterInterface|string|null $connection
- * @param \Magento\Framework\Model\ResourceModel\Db\AbstractDb $resource
+ * @param AdapterInterface|string|null $connection
+ * @param AbstractDb $resource
*
* @SuppressWarnings(PHPMD.ExcessiveParameterList)
*/
public function __construct(
- \Magento\Framework\Data\Collection\EntityFactoryInterface $entityFactory,
- \Psr\Log\LoggerInterface $logger,
- \Magento\Framework\Data\Collection\Db\FetchStrategyInterface $fetchStrategy,
- \Magento\Framework\Event\ManagerInterface $eventManager,
+ EntityFactoryInterface $entityFactory,
+ LoggerInterface $logger,
+ FetchStrategyInterface $fetchStrategy,
+ ManagerInterface $eventManager,
$mainTable,
$eventPrefix,
$eventObject,
$resourceModel,
- $model = \Magento\Framework\View\Element\UiComponent\DataProvider\Document::class,
+ ResolverInterface $localeResolver,
+ $model = Document::class,
$connection = null,
- \Magento\Framework\Model\ResourceModel\Db\AbstractDb $resource = null
+ AbstractDb $resource = null
) {
$this->_eventPrefix = $eventPrefix;
$this->_eventObject = $eventObject;
+ $this->_idFieldName = 'entity_id';
+ $this->localeResolver = $localeResolver;
$this->_init($model, $resourceModel);
$this->setMainTable($mainTable);
- $this->_idFieldName = 'entity_id';
parent::__construct(
$entityFactory,
$logger,
@@ -65,8 +96,17 @@ public function __construct(
/**
* @inheritdoc
- *
- * @return AggregationInterface
+ */
+ protected function _initSelect()
+ {
+ parent::_initSelect();
+ $this->joinRegionNameTable();
+
+ return $this;
+ }
+
+ /**
+ * @inheritdoc
*/
public function getAggregations()
{
@@ -75,9 +115,6 @@ public function getAggregations()
/**
* @inheritdoc
- *
- * @param AggregationInterface $aggregations
- * @return $this
*/
public function setAggregations($aggregations)
{
@@ -88,7 +125,7 @@ public function setAggregations($aggregations)
/**
* Get search criteria.
*
- * @return \Magento\Framework\Api\SearchCriteriaInterface|null
+ * @return SearchCriteriaInterface|null
*/
public function getSearchCriteria()
{
@@ -98,11 +135,11 @@ public function getSearchCriteria()
/**
* Set search criteria.
*
- * @param \Magento\Framework\Api\SearchCriteriaInterface $searchCriteria
+ * @param SearchCriteriaInterface $searchCriteria
* @return $this
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
*/
- public function setSearchCriteria(\Magento\Framework\Api\SearchCriteriaInterface $searchCriteria = null)
+ public function setSearchCriteria(SearchCriteriaInterface $searchCriteria = null)
{
return $this;
}
@@ -132,7 +169,7 @@ public function setTotalCount($totalCount)
/**
* Set items list.
*
- * @param \Magento\Framework\Api\ExtensibleDataInterface[] $items
+ * @param ExtensibleDataInterface[] $items
* @return $this
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
*/
@@ -140,4 +177,97 @@ public function setItems(array $items = null)
{
return $this;
}
+
+ /**
+ * @inheritdoc
+ */
+ public function addFieldToFilter($field, $condition = null)
+ {
+ if ($field === 'region') {
+ $conditionSql = $this->_getConditionSql(
+ $this->getRegionNameExpresion(),
+ $condition
+ );
+ $this->getSelect()->where($conditionSql);
+ return $this;
+ }
+
+ if (is_string($field) && count(explode('.', $field)) === 1) {
+ $field = 'main_table.' . $field;
+ }
+
+ return parent::addFieldToFilter($field, $condition);
+ }
+
+ /**
+ * Add fulltext filter
+ *
+ * @param string $value
+ * @return $this
+ */
+ public function addFullTextFilter(string $value)
+ {
+ $fields = self::FIELDS_TO_FULLTEXT_SEARCH;
+ $whereCondition = '';
+ foreach ($fields as $key => $field) {
+ $field = $field === 'region'
+ ? $this->getRegionNameExpresion()
+ : 'main_table.' . $field;
+ $condition = $this->_getConditionSql(
+ $this->getConnection()->quoteIdentifier($field),
+ ['like' => "%$value%"]
+ );
+ $whereCondition .= ($key === 0 ? '' : ' OR ') . $condition;
+ }
+ if ($whereCondition) {
+ $this->getSelect()->where($whereCondition);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Join region name table by current locale
+ *
+ * @return $this
+ */
+ private function joinRegionNameTable()
+ {
+ $locale = $this->localeResolver->getLocale();
+ $connection = $this->getConnection();
+ $regionIdField = $connection->quoteIdentifier('main_table.region_id');
+ $localeCondition = $connection->quoteInto("rnt.locale=?", $locale);
+
+ $this->getSelect()
+ ->joinLeft(
+ ['rct' => $this->getTable('directory_country_region')],
+ "rct.region_id={$regionIdField}",
+ []
+ )->joinLeft(
+ ['rnt' => $this->getTable('directory_country_region_name')],
+ "rnt.region_id={$regionIdField} AND {$localeCondition}",
+ ['region' => $this->getRegionNameExpresion()]
+ );
+
+ return $this;
+ }
+
+ /**
+ * Get SQL Expresion to define Region Name field by locale
+ *
+ * @return \Zend_Db_Expr
+ */
+ private function getRegionNameExpresion(): \Zend_Db_Expr
+ {
+ $connection = $this->getConnection();
+ $defaultNameExpr = $connection->getIfNullSql(
+ $connection->quoteIdentifier('rct.default_name'),
+ $connection->quoteIdentifier('main_table.region')
+ );
+
+ return $connection->getIfNullSql(
+ $connection->quoteIdentifier('rnt.name'),
+ $defaultNameExpr
+ );
+ }
}
diff --git a/app/code/Magento/Customer/Model/ResourceModel/Grid/Collection.php b/app/code/Magento/Customer/Model/ResourceModel/Grid/Collection.php
index 15678bd3dfd3f..bf8ef767063bd 100644
--- a/app/code/Magento/Customer/Model/ResourceModel/Grid/Collection.php
+++ b/app/code/Magento/Customer/Model/ResourceModel/Grid/Collection.php
@@ -3,17 +3,27 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
-
namespace Magento\Customer\Model\ResourceModel\Grid;
+use Magento\Customer\Model\ResourceModel\Customer;
use Magento\Customer\Ui\Component\DataProvider\Document;
use Magento\Framework\Data\Collection\Db\FetchStrategyInterface as FetchStrategy;
use Magento\Framework\Data\Collection\EntityFactoryInterface as EntityFactory;
use Magento\Framework\Event\ManagerInterface as EventManager;
+use Magento\Framework\Locale\ResolverInterface;
+use Magento\Framework\View\Element\UiComponent\DataProvider\SearchResult;
use Psr\Log\LoggerInterface as Logger;
-class Collection extends \Magento\Framework\View\Element\UiComponent\DataProvider\SearchResult
+/**
+ * Collection to present grid of customers on admin area
+ */
+class Collection extends SearchResult
{
+ /**
+ * @var ResolverInterface
+ */
+ private $localeResolver;
+
/**
* @inheritdoc
*/
@@ -25,12 +35,11 @@ class Collection extends \Magento\Framework\View\Element\UiComponent\DataProvide
protected $_map = ['fields' => ['entity_id' => 'main_table.entity_id']];
/**
- * Initialize dependencies.
- *
* @param EntityFactory $entityFactory
* @param Logger $logger
* @param FetchStrategy $fetchStrategy
* @param EventManager $eventManager
+ * @param ResolverInterface $localeResolver
* @param string $mainTable
* @param string $resourceModel
*/
@@ -39,9 +48,132 @@ public function __construct(
Logger $logger,
FetchStrategy $fetchStrategy,
EventManager $eventManager,
+ ResolverInterface $localeResolver,
$mainTable = 'customer_grid_flat',
- $resourceModel = \Magento\Customer\Model\ResourceModel\Customer::class
+ $resourceModel = Customer::class
) {
+ $this->localeResolver = $localeResolver;
parent::__construct($entityFactory, $logger, $fetchStrategy, $eventManager, $mainTable, $resourceModel);
}
+
+ /**
+ * @inheritdoc
+ */
+ protected function _initSelect()
+ {
+ parent::_initSelect();
+ $this->joinRegionNameTable();
+
+ return $this;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function addFieldToFilter($field, $condition = null)
+ {
+ if ($field === 'billing_region') {
+ $conditionSql = $this->_getConditionSql(
+ $this->getRegionNameExpresion(),
+ $condition
+ );
+ $this->getSelect()->where($conditionSql);
+ return $this;
+ }
+
+ if (is_string($field) && count(explode('.', $field)) === 1) {
+ $field = 'main_table.' . $field;
+ }
+
+ return parent::addFieldToFilter($field, $condition);
+ }
+
+ /**
+ * Add fulltext filter
+ *
+ * @param string $value
+ * @return $this
+ */
+ public function addFullTextFilter(string $value)
+ {
+ $fields = $this->getFulltextIndexColumns();
+ $whereCondition = '';
+ foreach ($fields as $key => $field) {
+ $field = $field === 'billing_region'
+ ? $this->getRegionNameExpresion()
+ : 'main_table.' . $field;
+ $condition = $this->_getConditionSql(
+ $this->getConnection()->quoteIdentifier($field),
+ ['like' => "%$value%"]
+ );
+ $whereCondition .= ($key === 0 ? '' : ' OR ') . $condition;
+ }
+
+ if ($whereCondition) {
+ $this->getSelect()->where($whereCondition);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Returns list of columns from fulltext index
+ *
+ * @return array
+ */
+ private function getFulltextIndexColumns(): array
+ {
+ $indexes = $this->getConnection()->getIndexList($this->getMainTable());
+ foreach ($indexes as $index) {
+ if (strtoupper($index['INDEX_TYPE']) == 'FULLTEXT') {
+ return $index['COLUMNS_LIST'];
+ }
+ }
+ return [];
+ }
+
+ /**
+ * Join region name table by current locale
+ *
+ * @return $this
+ */
+ private function joinRegionNameTable()
+ {
+ $locale = $this->localeResolver->getLocale();
+ $connection = $this->getConnection();
+ $regionIdField = $connection->quoteIdentifier('main_table.billing_region_id');
+ $localeCondition = $connection->quoteInto("rnt.locale=?", $locale);
+
+ $this->getSelect()
+ ->joinLeft(
+ ['rct' => $this->getTable('directory_country_region')],
+ "rct.region_id={$regionIdField}",
+ []
+ )->joinLeft(
+ ['rnt' => $this->getTable('directory_country_region_name')],
+ "rnt.region_id={$regionIdField} AND {$localeCondition}",
+ ['billing_region' => $this->getRegionNameExpresion()]
+ );
+
+ return $this;
+ }
+
+ /**
+ * Get SQL Expresion to define Region Name field by locale
+ *
+ * @return \Zend_Db_Expr
+ */
+ private function getRegionNameExpresion(): \Zend_Db_Expr
+ {
+ $connection = $this->getConnection();
+ $defaultNameExpr = $connection->getIfNullSql(
+ $connection->quoteIdentifier('rct.default_name'),
+ $connection->quoteIdentifier('main_table.billing_region')
+ );
+
+ return $connection->getIfNullSql(
+ $connection->quoteIdentifier('rnt.name'),
+ $defaultNameExpr
+ );
+ }
}
diff --git a/app/code/Magento/Customer/Test/Mftf/Section/AdminDeleteUserSection.xml b/app/code/Magento/Customer/Test/Mftf/Section/AdminDeleteUserSection.xml
index 0ba197999be6c..f43666a032956 100644
--- a/app/code/Magento/Customer/Test/Mftf/Section/AdminDeleteUserSection.xml
+++ b/app/code/Magento/Customer/Test/Mftf/Section/AdminDeleteUserSection.xml
@@ -11,7 +11,7 @@
diff --git a/app/code/Magento/Customer/Test/Mftf/Test/StorefrontDeleteCustomerAddressTest.xml b/app/code/Magento/Customer/Test/Mftf/Test/StorefrontDeleteCustomerAddressTest.xml
index fb08a07a7c319..51efd4e23f5d3 100644
--- a/app/code/Magento/Customer/Test/Mftf/Test/StorefrontDeleteCustomerAddressTest.xml
+++ b/app/code/Magento/Customer/Test/Mftf/Test/StorefrontDeleteCustomerAddressTest.xml
@@ -23,10 +23,11 @@
-
-
-
-
+
+
+
+
+
diff --git a/app/code/Magento/Customer/Test/Mftf/Test/StorefrontLoginWithIncorrectCredentialsTest.xml b/app/code/Magento/Customer/Test/Mftf/Test/StorefrontLoginWithIncorrectCredentialsTest.xml
index 104b5d56314ba..a7dc3c7fde7f4 100644
--- a/app/code/Magento/Customer/Test/Mftf/Test/StorefrontLoginWithIncorrectCredentialsTest.xml
+++ b/app/code/Magento/Customer/Test/Mftf/Test/StorefrontLoginWithIncorrectCredentialsTest.xml
@@ -26,10 +26,14 @@
-
-
-
-
-
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Customer/Test/Mftf/Test/StorefrontPersistedCustomerLoginTest.xml b/app/code/Magento/Customer/Test/Mftf/Test/StorefrontPersistedCustomerLoginTest.xml
index 250da68786688..7845d3cee44ef 100644
--- a/app/code/Magento/Customer/Test/Mftf/Test/StorefrontPersistedCustomerLoginTest.xml
+++ b/app/code/Magento/Customer/Test/Mftf/Test/StorefrontPersistedCustomerLoginTest.xml
@@ -24,11 +24,11 @@
-
-
-
-
-
+
+
+
+
+
diff --git a/app/code/Magento/Customer/Test/Unit/Model/Customer/DataProviderWithDefaultAddressesTest.php b/app/code/Magento/Customer/Test/Unit/Model/Customer/DataProviderWithDefaultAddressesTest.php
index 0cb135ee66a12..fbeefb26efedb 100644
--- a/app/code/Magento/Customer/Test/Unit/Model/Customer/DataProviderWithDefaultAddressesTest.php
+++ b/app/code/Magento/Customer/Test/Unit/Model/Customer/DataProviderWithDefaultAddressesTest.php
@@ -1,5 +1,4 @@
eavConfigMock = $this->getMockBuilder(Config::class)
- ->disableOriginalConstructor()
- ->getMock();
+ $this->eavConfigMock = $this->getMockBuilder(Config::class)->disableOriginalConstructor()->getMock();
$this->customerCollectionFactoryMock = $this->createPartialMock(CustomerCollectionFactory::class, ['create']);
$this->sessionMock = $this->getMockBuilder(SessionManagerInterface::class)
->setMethods(['getCustomerFormData', 'unsCustomerFormData'])
@@ -161,20 +164,21 @@ protected function setUp(): void
],
]
);
+
$helper = new ObjectManager($this);
$this->dataProvider = $helper->getObject(
DataProviderWithDefaultAddresses::class,
[
- 'name' => 'test-name',
- 'primaryFieldName' => 'primary-field-name',
- 'requestFieldName' => 'request-field-name',
+ 'name' => 'test-name',
+ 'primaryFieldName' => 'primary-field-name',
+ 'requestFieldName' => 'request-field-name',
'customerCollectionFactory' => $this->customerCollectionFactoryMock,
- 'eavConfig' => $this->eavConfigMock,
- 'countryFactory' => $this->countryFactoryMock,
- 'session' => $this->sessionMock,
- 'fileUploaderDataResolver' => $this->fileUploaderDataResolver,
+ 'eavConfig' => $this->eavConfigMock,
+ 'countryFactory' => $this->countryFactoryMock,
+ 'session' => $this->sessionMock,
+ 'fileUploaderDataResolver' => $this->fileUploaderDataResolver,
'attributeMetadataResolver' => $this->attributeMetadataResolver,
- true
+ 'allowToShowHiddenAttributes' => true,
]
);
}
@@ -343,6 +347,7 @@ protected function getAttributeMock($options = []): array
*/
public function testGetData(): void
{
+ $customerId = 1;
$customerData = [
'email' => 'test@test.ua',
'default_billing' => 2,
@@ -350,18 +355,35 @@ public function testGetData(): void
'password_hash' => 'password_hash',
'rp_token' => 'rp_token',
];
+ $addressData = [
+ 'country_id' => 'code',
+ 'entity_id' => 2,
+ 'parent_id' => $customerId,
+ 'street' => "line 1\nline 2",
+ 'region' => 'Region Name',
+ ];
+ $localeRegionName = 'Locale Region Name';
- $address = $this->getMockBuilder(Address::class)
- ->disableOriginalConstructor()
- ->getMock();
- $this->customerCollectionMock->expects($this->once())->method('getItems')->willReturn([$this->customerMock]);
- $this->customerMock->expects($this->once())->method('getData')->willReturn($customerData);
- $this->customerMock->expects($this->atLeastOnce())->method('getId')->willReturn(1);
+ $address = $this->createMock(Address::class);
+ $address->method('getData')->willReturn($addressData);
+ $address->method('getRegion')->willReturn($localeRegionName);
+
+ $this->customerCollectionMock->method('getItems')->willReturn([$this->customerMock]);
+ $this->customerMock->method('getDefaultBillingAddress')->willReturn($address);
+ $this->customerMock->method('getDefaultShippingAddress')->willReturn(false);
+ $this->customerMock->method('getData')->willReturn($customerData);
+ $this->customerMock->method('getId')->willReturn($customerId);
- $this->customerMock->expects($this->once())->method('getDefaultBillingAddress')->willReturn($address);
- $this->countryFactoryMock->expects($this->once())->method('create')->willReturnSelf();
- $this->countryFactoryMock->expects($this->once())->method('loadByCode')->willReturnSelf();
- $this->countryFactoryMock->expects($this->once())->method('getName')->willReturn('Ukraine');
+ $this->countryFactoryMock->expects($this->once())
+ ->method('create')
+ ->willReturnSelf();
+ $this->countryFactoryMock->expects($this->once())
+ ->method('loadByCode')
+ ->with('code')
+ ->willReturnSelf();
+ $this->countryFactoryMock->expects($this->once())
+ ->method('getName')
+ ->willReturn('Ukraine');
$this->sessionMock->expects($this->once())
->method('getCustomerFormData')
@@ -369,7 +391,7 @@ public function testGetData(): void
$this->assertEquals(
[
- 1 => [
+ $customerId => [
'customer' => [
'email' => 'test@test.ua',
'default_billing' => 2,
@@ -377,9 +399,14 @@ public function testGetData(): void
],
'default_billing_address' => [
'country' => 'Ukraine',
+ 'country_id' => 'code',
+ 'entity_id' => 2,
+ 'parent_id' => $customerId,
+ 'street' => ['line 1', 'line 2'],
+ 'region' => $localeRegionName,
],
'default_shipping_address' => [],
- 'customer_id' => 1
+ 'customer_id' => $customerId
]
],
$this->dataProvider->getData()
diff --git a/app/code/Magento/Customer/Test/Unit/Model/CustomerExtractorTest.php b/app/code/Magento/Customer/Test/Unit/Model/CustomerExtractorTest.php
index 0bc45ab97e6e0..3613298ab85f2 100644
--- a/app/code/Magento/Customer/Test/Unit/Model/CustomerExtractorTest.php
+++ b/app/code/Magento/Customer/Test/Unit/Model/CustomerExtractorTest.php
@@ -121,14 +121,27 @@ protected function setUp(): void
);
}
- public function testExtract()
+ /**
+ * @param int $storeId
+ * @param int $websiteId
+ * @param array $customerData
+ * @dataProvider getDataProvider
+ * @return void
+ */
+ public function testExtract(int $storeId, int $websiteId, array $customerData)
{
- $customerData = [
- 'firstname' => 'firstname',
- 'lastname' => 'firstname',
- 'email' => 'email.example.com',
- ];
+ $this->initializeExpectation($storeId, $websiteId, $customerData);
+ $this->assertSame($this->customerData, $this->customerExtractor->extract('form-code', $this->request));
+ }
+
+ /**
+ * @param int $storeId
+ * @param int $websiteId
+ * @param array $customerData
+ */
+ private function initializeExpectation(int $storeId, int $websiteId, array $customerData): void
+ {
$this->formFactory->expects($this->once())
->method('create')
->with('customer', 'form-code')
@@ -156,17 +169,54 @@ public function testExtract()
->willReturn($this->store);
$this->store->expects($this->once())
->method('getId')
- ->willReturn(1);
+ ->willReturn($storeId);
$this->store->expects($this->once())
->method('getWebsiteId')
- ->willReturn(1);
+ ->willReturn($websiteId);
$this->customerData->expects($this->once())
->method('setWebsiteId')
- ->with(1);
+ ->with($websiteId);
$this->customerData->expects($this->once())
->method('setStoreId')
- ->with(1);
+ ->with($storeId);
+ }
- $this->assertSame($this->customerData, $this->customerExtractor->extract('form-code', $this->request));
+ /**
+ * @return array
+ */
+ public function getDataProvider()
+ {
+ return [
+ 'extract data when group id is null' => [
+ 1,
+ 1,
+ [
+ 'firstname' => 'firstname-1',
+ 'lastname' => 'firstname-1',
+ 'email' => 'email-1.example.com',
+ 'group_id' => null
+ ]
+ ],
+ 'extract data when group id is not null and default' => [
+ 1,
+ 2,
+ [
+ 'firstname' => 'firstname-2',
+ 'lastname' => 'firstname-3',
+ 'email' => 'email-2.example.com',
+ 'group_id' => 1
+ ]
+ ],
+ 'extract data when group id is different from default' => [
+ 1,
+ 1,
+ [
+ 'firstname' => 'firstname-3',
+ 'lastname' => 'firstname-3',
+ 'email' => 'email-3.example.com',
+ 'group_id' => 2
+ ]
+ ],
+ ];
}
}
diff --git a/app/code/Magento/Customer/Ui/Component/Listing/Address/DataProvider.php b/app/code/Magento/Customer/Ui/Component/Listing/Address/DataProvider.php
index c70e25ee99ec3..e26a32ec15c62 100644
--- a/app/code/Magento/Customer/Ui/Component/Listing/Address/DataProvider.php
+++ b/app/code/Magento/Customer/Ui/Component/Listing/Address/DataProvider.php
@@ -5,17 +5,20 @@
*/
namespace Magento\Customer\Ui\Component\Listing\Address;
+use Magento\Customer\Model\ResourceModel\Address\Grid\Collection as GridCollection;
use Magento\Customer\Model\ResourceModel\Address\Grid\CollectionFactory;
use Magento\Directory\Model\CountryFactory;
use Magento\Framework\Api\Filter;
+use Magento\Framework\App\RequestInterface;
+use Magento\Ui\DataProvider\AbstractDataProvider;
/**
* Custom DataProvider for customer addresses listing
*/
-class DataProvider extends \Magento\Ui\DataProvider\AbstractDataProvider
+class DataProvider extends AbstractDataProvider
{
/**
- * @var \Magento\Framework\App\RequestInterface $request,
+ * @var RequestInterface $request,
*/
private $request;
@@ -29,7 +32,7 @@ class DataProvider extends \Magento\Ui\DataProvider\AbstractDataProvider
* @param string $primaryFieldName
* @param string $requestFieldName
* @param CollectionFactory $collectionFactory
- * @param \Magento\Framework\App\RequestInterface $request
+ * @param RequestInterface $request
* @param CountryFactory $countryFactory
* @param array $meta
* @param array $data
@@ -39,7 +42,7 @@ public function __construct(
$primaryFieldName,
$requestFieldName,
CollectionFactory $collectionFactory,
- \Magento\Framework\App\RequestInterface $request,
+ RequestInterface $request,
CountryFactory $countryFactory,
array $meta = [],
array $data = []
@@ -57,6 +60,7 @@ public function __construct(
*/
public function getData(): array
{
+ /** @var GridCollection $collection */
$collection = $this->getCollection();
$data['items'] = [];
if ($this->request->getParam('parent_id')) {
@@ -80,34 +84,15 @@ public function getData(): array
*/
public function addFilter(Filter $filter): void
{
- if ($filter->getField() !== 'fulltext') {
- $this->collection->addFieldToFilter(
+ /** @var GridCollection $collection */
+ $collection = $this->getCollection();
+ if ($filter->getField() === 'fulltext') {
+ $collection->addFullTextFilter(trim($filter->getValue()));
+ } else {
+ $collection->addFieldToFilter(
$filter->getField(),
[$filter->getConditionType() => $filter->getValue()]
);
- } else {
- $value = trim($filter->getValue());
- $this->collection->addFieldToFilter(
- [
- ['attribute' => 'firstname'],
- ['attribute' => 'lastname'],
- ['attribute' => 'street'],
- ['attribute' => 'city'],
- ['attribute' => 'region'],
- ['attribute' => 'postcode'],
- ['attribute' => 'telephone']
- ],
- [
- ['like' => "%{$value}%"],
- ['like' => "%{$value}%"],
- ['like' => "%{$value}%"],
- ['like' => "%{$value}%"],
- ['like' => "%{$value}%"],
- ['like' => "%{$value}%"],
- ['like' => "%{$value}%"],
- ['like' => "%{$value}%"],
- ]
- );
}
}
}
diff --git a/app/code/Magento/Customer/Ui/Component/Listing/FulltextFilter.php b/app/code/Magento/Customer/Ui/Component/Listing/FulltextFilter.php
new file mode 100644
index 0000000000000..b6821e002f645
--- /dev/null
+++ b/app/code/Magento/Customer/Ui/Component/Listing/FulltextFilter.php
@@ -0,0 +1,34 @@
+addFullTextFilter(trim($filter->getValue()));
+ }
+}
diff --git a/app/code/Magento/Customer/etc/adminhtml/di.xml b/app/code/Magento/Customer/etc/adminhtml/di.xml
index 575fd639fa539..9f207ea8bebd1 100644
--- a/app/code/Magento/Customer/etc/adminhtml/di.xml
+++ b/app/code/Magento/Customer/etc/adminhtml/di.xml
@@ -23,4 +23,22 @@
+
+
+
+ - Magento\Framework\View\Element\UiComponent\DataProvider\RegularFilter
+ - Magento\Customer\Ui\Component\Listing\FulltextFilter
+
+
+
+
+
+ CustomerGirdFilterPool
+
+
+
+
+ CustomerGridCollectionReporting
+
+
diff --git a/app/code/Magento/Customer/etc/indexer.xml b/app/code/Magento/Customer/etc/indexer.xml
index fdc0ab0eb16f6..cd3b41471a3bb 100644
--- a/app/code/Magento/Customer/etc/indexer.xml
+++ b/app/code/Magento/Customer/etc/indexer.xml
@@ -41,6 +41,7 @@
+
diff --git a/app/code/Magento/Customer/i18n/en_US.csv b/app/code/Magento/Customer/i18n/en_US.csv
index bb6c4b10bf75e..7c88ffec1170a 100644
--- a/app/code/Magento/Customer/i18n/en_US.csv
+++ b/app/code/Magento/Customer/i18n/en_US.csv
@@ -541,3 +541,4 @@ Addresses,Addresses
"The Date of Birth should not be greater than today.","The Date of Birth should not be greater than today."
"The store view is not in the associated website.","The store view is not in the associated website."
"The Store View selected for sending Welcome email from is not related to the customer's associated website.","The Store View selected for sending Welcome email from is not related to the customer's associated website."
+"Add/Update Address","Add/Update Address"
diff --git a/app/code/Magento/Customer/view/base/ui_component/customer_form.xml b/app/code/Magento/Customer/view/base/ui_component/customer_form.xml
index b9487037da2cc..78bbd612f5b70 100644
--- a/app/code/Magento/Customer/view/base/ui_component/customer_form.xml
+++ b/app/code/Magento/Customer/view/base/ui_component/customer_form.xml
@@ -474,7 +474,7 @@
- Add/Update Address
+ Add/Update Address
diff --git a/app/code/Magento/CustomerImportExport/Model/Import/Address.php b/app/code/Magento/CustomerImportExport/Model/Import/Address.php
index 09d76ec3fb71f..a5947f48bea5f 100644
--- a/app/code/Magento/CustomerImportExport/Model/Import/Address.php
+++ b/app/code/Magento/CustomerImportExport/Model/Import/Address.php
@@ -54,6 +54,8 @@ class Address extends AbstractCustomer
/**#@-*/
+ const COLUMN_REGION_ID = 'region_id';
+
/**#@+
* Particular columns that contains of customer default addresses
*/
@@ -666,13 +668,17 @@ protected function _prepareDataForUpdate(array $rowData): array
}
}
// let's try to find region ID
- $entityRow['region_id'] = null;
+ $entityRow[self::COLUMN_REGION_ID] = null;
- if (!empty($rowData[self::COLUMN_REGION])
- && $this->getCountryRegionId($rowData[self::COLUMN_COUNTRY_ID], $rowData[self::COLUMN_REGION]) !== false) {
- $regionId = $this->getCountryRegionId($rowData[self::COLUMN_COUNTRY_ID], $rowData[self::COLUMN_REGION]);
- $entityRow[self::COLUMN_REGION] = $this->_regions[$regionId];
- $entityRow['region_id'] = $regionId;
+ if (!empty($entityRow[self::COLUMN_REGION]) && !empty($entityRow[self::COLUMN_COUNTRY_ID])) {
+ $entityRow[self::COLUMN_REGION_ID] = $this->getCountryRegionId(
+ $entityRow[self::COLUMN_COUNTRY_ID],
+ $entityRow[self::COLUMN_REGION]
+ );
+ // override the region name with its proper name if region ID is found
+ $entityRow[self::COLUMN_REGION] = $entityRow[self::COLUMN_REGION_ID] !== null
+ ? $this->_regions[$entityRow[self::COLUMN_REGION_ID]]
+ : $entityRow[self::COLUMN_REGION];
}
if ($newAddress) {
$entityRowNew = $entityRow;
@@ -833,7 +839,7 @@ public static function getDefaultAddressAttributeMapping()
* Check if address for import is empty (for customer composite mode)
*
* @param array $rowData
- * @return array
+ * @return bool
*/
protected function _isOptionalAddressEmpty(array $rowData)
{
@@ -914,7 +920,8 @@ protected function _validateRowForUpdate(array $rowData, $rowNumber)
if (isset($rowData[self::COLUMN_REGION])
&& !empty($rowData[self::COLUMN_REGION])
- && false === $this->getCountryRegionId(
+ && count($this->getCountryRegions($rowData[self::COLUMN_COUNTRY_ID])) > 0
+ && null === $this->getCountryRegionId(
$rowData[self::COLUMN_COUNTRY_ID],
$rowData[self::COLUMN_REGION]
)
@@ -994,18 +1001,22 @@ public function setCustomerAttributes($customerAttributes)
*
* @param string $countryId
* @param string $region
- * @return bool|int
+ * @return int|null
*/
- private function getCountryRegionId(string $countryId, string $region)
+ private function getCountryRegionId(string $countryId, string $region): ?int
{
- $countryNormalized = strtolower($countryId);
- $regionNormalized = strtolower($region);
-
- if (isset($this->_countryRegions[$countryNormalized])
- && isset($this->_countryRegions[$countryNormalized][$regionNormalized])) {
- return $this->_countryRegions[$countryNormalized][$regionNormalized];
- }
+ $countryRegions = $this->getCountryRegions($countryId);
+ return $countryRegions[strtolower($region)] ?? null;
+ }
- return false;
+ /**
+ * Get country regions
+ *
+ * @param string $countryId
+ * @return array
+ */
+ private function getCountryRegions(string $countryId): array
+ {
+ return $this->_countryRegions[strtolower($countryId)] ?? [];
}
}
diff --git a/app/code/Magento/Directory/Setup/Patch/Data/AddDataForBulgaria.php b/app/code/Magento/Directory/Setup/Patch/Data/AddDataForBulgaria.php
new file mode 100644
index 0000000000000..7f0e401927a41
--- /dev/null
+++ b/app/code/Magento/Directory/Setup/Patch/Data/AddDataForBulgaria.php
@@ -0,0 +1,115 @@
+moduleDataSetup = $moduleDataSetup;
+ $this->dataInstallerFactory = $dataInstallerFactory;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function apply()
+ {
+ /** @var DataInstaller $dataInstaller */
+ $dataInstaller = $this->dataInstallerFactory->create();
+ $dataInstaller->addCountryRegions(
+ $this->moduleDataSetup->getConnection(),
+ $this->getDataForBulgaria()
+ );
+
+ return $this;
+ }
+
+ /**
+ * Bulgarian states data.
+ *
+ * @return array
+ */
+ private function getDataForBulgaria()
+ {
+ return [
+ ['BG', 'BG-01', 'Blagoevgrad'],
+ ['BG', 'BG-02', 'Burgas'],
+ ['BG', 'BG-03', 'Varna'],
+ ['BG', 'BG-04', 'Veliko Tarnovo'],
+ ['BG', 'BG-05', 'Vidin'],
+ ['BG', 'BG-06', 'Vratsa'],
+ ['BG', 'BG-07', 'Gabrovo'],
+ ['BG', 'BG-08', 'Dobrich'],
+ ['BG', 'BG-09', 'Kardzhali'],
+ ['BG', 'BG-10', 'Kyustendil'],
+ ['BG', 'BG-11', 'Lovech'],
+ ['BG', 'BG-12', 'Montana'],
+ ['BG', 'BG-13', 'Pazardzhik'],
+ ['BG', 'BG-14', 'Pernik'],
+ ['BG', 'BG-15', 'Pleven'],
+ ['BG', 'BG-16', 'Plovdiv'],
+ ['BG', 'BG-17', 'Razgrad'],
+ ['BG', 'BG-18', 'Ruse'],
+ ['BG', 'BG-19', 'Silistra'],
+ ['BG', 'BG-20', 'Sliven'],
+ ['BG', 'BG-21', 'Smolyan'],
+ ['BG', 'BG-22', 'Sofia City'],
+ ['BG', 'BG-23', 'Sofia Province'],
+ ['BG', 'BG-24', 'Stara Zagora'],
+ ['BG', 'BG-25', 'Targovishte'],
+ ['BG', 'BG-26', 'Haskovo'],
+ ['BG', 'BG-27', 'Shumen'],
+ ['BG', 'BG-28', 'Yambol'],
+ ];
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public static function getDependencies()
+ {
+ return [
+ InitializeDirectoryData::class,
+ ];
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function getAliases()
+ {
+ return [];
+ }
+}
diff --git a/app/code/Magento/Email/view/adminhtml/layout/adminhtml_email_template_preview.xml b/app/code/Magento/Email/view/adminhtml/layout/adminhtml_email_template_preview.xml
index 886a76b3af6f8..de3f2b97d5c3b 100644
--- a/app/code/Magento/Email/view/adminhtml/layout/adminhtml_email_template_preview.xml
+++ b/app/code/Magento/Email/view/adminhtml/layout/adminhtml_email_template_preview.xml
@@ -6,6 +6,13 @@
*/
-->
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Email/view/adminhtml/templates/template/edit.phtml b/app/code/Magento/Email/view/adminhtml/templates/template/edit.phtml
index 7378fa4b2e47f..0dceb9d51a99e 100644
--- a/app/code/Magento/Email/view/adminhtml/templates/template/edit.phtml
+++ b/app/code/Magento/Email/view/adminhtml/templates/template/edit.phtml
@@ -6,9 +6,11 @@
use Magento\Framework\App\TemplateTypesInterface;
+// phpcs:disable Generic.Files.LineLength.TooLong
+
/** @var $block \Magento\Email\Block\Adminhtml\Template\Edit */
?>
-getEditMode()) : ?>
+getEditMode()): ?>
@@ -116,7 +119,7 @@ $_items = $block->getCreditmemo()->getAllItems();
= $block->escapeHtml(__('Append Comments')) ?>
- canSendCreditmemoEmail()) :?>
+ canSendCreditmemoEmail()):?>
getBaseShippingDiscountAmount() + $baseDiscountAmount,
$baseShippingAmount
);
- $address->setShippingDiscountAmount($discountAmount);
- $address->setBaseShippingDiscountAmount($baseDiscountAmount);
+ $address->setShippingDiscountAmount($this->priceCurrency->roundPrice($discountAmount));
+ $address->setBaseShippingDiscountAmount($this->priceCurrency->roundPrice($baseDiscountAmount));
$appliedRuleIds[$rule->getRuleId()] = $rule->getRuleId();
$this->rulesApplier->maintainAddressCouponCode($address, $rule, $this->getCouponCode());
diff --git a/app/code/Magento/SalesRule/Test/Mftf/ActionGroup/StorefrontClickOnMiniCartActionGroup.xml b/app/code/Magento/SalesRule/Test/Mftf/ActionGroup/StorefrontClickOnMiniCartActionGroup.xml
deleted file mode 100644
index 498695dee9336..0000000000000
--- a/app/code/Magento/SalesRule/Test/Mftf/ActionGroup/StorefrontClickOnMiniCartActionGroup.xml
+++ /dev/null
@@ -1,16 +0,0 @@
-
-
-
-
-
-
-
-
-
-
diff --git a/app/code/Magento/SalesRule/Test/Mftf/Test/AdminCreateCartPriceRuleForGeneratedCouponTest.xml b/app/code/Magento/SalesRule/Test/Mftf/Test/AdminCreateCartPriceRuleForGeneratedCouponTest.xml
index 24c3a7cd44bc8..9f4168575595a 100644
--- a/app/code/Magento/SalesRule/Test/Mftf/Test/AdminCreateCartPriceRuleForGeneratedCouponTest.xml
+++ b/app/code/Magento/SalesRule/Test/Mftf/Test/AdminCreateCartPriceRuleForGeneratedCouponTest.xml
@@ -55,9 +55,11 @@
-
-
-
+
+
+
+
+
diff --git a/app/code/Magento/SalesRule/Test/Mftf/Test/StorefrontAutoGeneratedCouponCodeTest.xml b/app/code/Magento/SalesRule/Test/Mftf/Test/StorefrontAutoGeneratedCouponCodeTest.xml
index 09b45cd554056..b77cfaf02d232 100644
--- a/app/code/Magento/SalesRule/Test/Mftf/Test/StorefrontAutoGeneratedCouponCodeTest.xml
+++ b/app/code/Magento/SalesRule/Test/Mftf/Test/StorefrontAutoGeneratedCouponCodeTest.xml
@@ -59,9 +59,11 @@
-
-
-
+
+
+
+
+
prepareRuleCollectionMock($this->ruleCollection);
$this->priceCurrency = $this->getMockBuilder(PriceCurrencyInterface::class)
->disableOriginalConstructor()
+ ->setMethods(['roundPrice'])
->getMockForAbstractClass();
/** @var Validator|MockObject $validator */
@@ -542,6 +543,9 @@ public function testProcessShippingAmountActions($action, $ruleDiscount, $shippi
$this->priceCurrency->method('convert')
->willReturn($ruleDiscount);
+ $this->priceCurrency->method('roundPrice')
+ ->willReturn(round($shippingDiscount, 2));
+
$this->model->init(
$this->model->getWebsiteId(),
$this->model->getCustomerGroupId(),
diff --git a/app/code/Magento/Store/Model/Config/Importer/DataDifferenceCalculator.php b/app/code/Magento/Store/Model/Config/Importer/DataDifferenceCalculator.php
index 75fbe23d78f8b..fac02bfb92b3f 100644
--- a/app/code/Magento/Store/Model/Config/Importer/DataDifferenceCalculator.php
+++ b/app/code/Magento/Store/Model/Config/Importer/DataDifferenceCalculator.php
@@ -20,6 +20,17 @@ class DataDifferenceCalculator
*/
private $runtimeConfigSource;
+ /**
+ * Scopes identifier
+ *
+ * @var string[]
+ */
+ private $identifiers = [
+ 'websites' => 'website_id',
+ 'groups' => 'group_id',
+ 'stores' => 'store_id',
+ ];
+
/**
* @param ConfigSourceInterface $runtimeConfigSource The config source to retrieve current config
*/
@@ -28,6 +39,31 @@ public function __construct(ConfigSourceInterface $runtimeConfigSource)
$this->runtimeConfigSource = $runtimeConfigSource;
}
+ /**
+ * Update data by checking ID
+ *
+ * @param string $scope
+ * @param array $data
+ * @param array $runtimeScopeData
+ * @return array
+ */
+ private function updateDataById(string $scope, array $data, array $runtimeScopeData): array
+ {
+ $diffData = array_diff_key($data, $runtimeScopeData);
+ foreach ($diffData as $code => $datum) {
+ foreach ($runtimeScopeData as $runTimeScopeCode => $runtimeScopeDatum) {
+ if (isset($datum[$this->identifiers[$scope]])
+ && $datum[$this->identifiers[$scope]] === $runtimeScopeDatum[$this->identifiers[$scope]]
+ ) {
+ $data[$runTimeScopeCode] = $data[$code];
+ unset($data[$code]);
+ }
+ }
+ }
+
+ return $data;
+ }
+
/**
* Calculates items to delete.
*
@@ -41,6 +77,7 @@ public function getItemsToDelete($scope, array $data)
$runtimeScopeData = $this->changeDataKeyToCode(
$this->getRuntimeData($scope)
);
+ $data = $this->updateDataById($scope, $data, $runtimeScopeData);
return array_diff_key($runtimeScopeData, $data);
}
@@ -58,6 +95,7 @@ public function getItemsToCreate($scope, array $data)
$runtimeScopeData = $this->changeDataKeyToCode(
$this->getRuntimeData($scope)
);
+ $data = $this->updateDataById($scope, $data, $runtimeScopeData);
return array_diff_key($data, $runtimeScopeData);
}
@@ -77,7 +115,7 @@ public function getItemsToUpdate($scope, array $data)
$runtimeScopeData = $this->changeDataKeyToCode(
$this->getRuntimeData($scope)
);
-
+ $data = $this->updateDataById($scope, $data, $runtimeScopeData);
foreach ($runtimeScopeData as $entityCode => $entityData) {
if (isset($data[$entityCode]) && array_diff_assoc($entityData, $data[$entityCode])) {
$itemsToUpdate[$entityCode] = array_replace($entityData, $data[$entityCode]);
diff --git a/app/code/Magento/Swatches/Test/Mftf/Test/StorefrontConfigurableProductSwatchTierPriceTest.xml b/app/code/Magento/Swatches/Test/Mftf/Test/StorefrontConfigurableProductSwatchTierPriceTest.xml
new file mode 100644
index 0000000000000..b3fa367313789
--- /dev/null
+++ b/app/code/Magento/Swatches/Test/Mftf/Test/StorefrontConfigurableProductSwatchTierPriceTest.xml
@@ -0,0 +1,84 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Swatches/view/base/web/js/swatch-renderer.js b/app/code/Magento/Swatches/view/base/web/js/swatch-renderer.js
index 84ab345394a92..ad5926d451e88 100644
--- a/app/code/Magento/Swatches/view/base/web/js/swatch-renderer.js
+++ b/app/code/Magento/Swatches/view/base/web/js/swatch-renderer.js
@@ -278,7 +278,7 @@ define([
// tier prise selectors end
// A price label selector
- normalPriceLabelSelector: '.normal-price .price-label'
+ normalPriceLabelSelector: '.product-info-main .normal-price .price-label'
},
/**
@@ -1029,22 +1029,11 @@ define([
*/
_getNewPrices: function () {
var $widget = this,
- optionPriceDiff = 0,
- allowedProduct = this._getAllowedProductWithMinPrice(this._CalcProducts()),
- optionPrices = this.options.jsonConfig.optionPrices,
- basePrice = parseFloat(this.options.jsonConfig.prices.basePrice.amount),
- optionFinalPrice,
- newPrices;
+ newPrices = $widget.options.jsonConfig.prices,
+ allowedProduct = this._getAllowedProductWithMinPrice(this._CalcProducts());
if (!_.isEmpty(allowedProduct)) {
- optionFinalPrice = parseFloat(optionPrices[allowedProduct].finalPrice.amount);
- optionPriceDiff = optionFinalPrice - basePrice;
- }
-
- if (optionPriceDiff !== 0) {
- newPrices = this.options.jsonConfig.optionPrices[allowedProduct];
- } else {
- newPrices = $widget.options.jsonConfig.prices;
+ newPrices = this.options.jsonConfig.optionPrices[allowedProduct];
}
return newPrices;
diff --git a/app/code/Magento/Theme/Controller/Result/JsFooterPlugin.php b/app/code/Magento/Theme/Controller/Result/JsFooterPlugin.php
index 6a80dec460660..dd490cd02b61c 100644
--- a/app/code/Magento/Theme/Controller/Result/JsFooterPlugin.php
+++ b/app/code/Magento/Theme/Controller/Result/JsFooterPlugin.php
@@ -8,11 +8,13 @@
namespace Magento\Theme\Controller\Result;
use Magento\Framework\App\Config\ScopeConfigInterface;
+use Magento\Framework\App\Response\HttpInterface as HttpResponseInterface;
+use Magento\Framework\App\ResponseInterface;
+use Magento\Framework\View\Result\Layout;
use Magento\Store\Model\ScopeInterface;
-use Magento\Framework\App\Response\Http;
/**
- * Plugin for putting all js to footer.
+ * Plugin for putting all JavaScript tags to the end of body.
*/
class JsFooterPlugin
{
@@ -32,34 +34,77 @@ public function __construct(ScopeConfigInterface $scopeConfig)
}
/**
- * Put all javascript to footer before sending the response.
+ * Moves all JavaScript tags to the end of body if this feature is enabled.
*
- * @param Http $subject
- * @return void
+ * @param Layout $subject
+ * @param Layout $result
+ * @param HttpResponseInterface|ResponseInterface $httpResponse
+ * @return Layout (That should be void, actually)
+ * @SuppressWarnings(PHPMD.UnusedFormalParameter)
*/
- public function beforeSendResponse(Http $subject)
+ public function afterRenderResult(Layout $subject, Layout $result, ResponseInterface $httpResponse)
{
- $content = $subject->getContent();
- $script = [];
- if (is_string($content) && strpos($content, 'scopeConfig->isSetFlag(
- self::XML_PATH_DEV_MOVE_JS_TO_BOTTOM,
- ScopeInterface::SCOPE_STORE
- )
- ) {
- $pattern = '##is';
- $content = preg_replace_callback(
- $pattern,
- function ($matchPart) use (&$script) {
- $script[] = $matchPart[0];
- return '';
- },
- $content
- );
- $subject->setContent(
- str_replace('
Test Content