From 960c668e31ea6c8e24715aac9b349618f26cc9a2 Mon Sep 17 00:00:00 2001 From: Lukasz Bajsarowicz Date: Fri, 18 Jul 2025 11:35:16 +0200 Subject: [PATCH 01/11] SwiftOtter-SOP-348 Resolve issues with Product listing with negative Page # --- app/code/Magento/Catalog/Controller/Category/View.php | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/app/code/Magento/Catalog/Controller/Category/View.php b/app/code/Magento/Catalog/Controller/Category/View.php index da7c5326587e3..66f7b8801835e 100644 --- a/app/code/Magento/Catalog/Controller/Category/View.php +++ b/app/code/Magento/Catalog/Controller/Category/View.php @@ -212,6 +212,13 @@ public function execute() $category = $this->_initCategory(); if ($category) { + // Negative ?p= value is not supported, redirect to a base version of category page. + if ($this->_request->getParam(Toolbar::PAGE_PARM_NAME) < 0) { + return $this->resultRedirectFactory->create() + ->setHttpResponseCode(301) + ->setUrl($category->getUrl()); + } + $this->layerResolver->create(Resolver::CATALOG_LAYER_CATEGORY); $settings = $this->_catalogDesign->getDesignSettings($category); From d956c24a07ec7eb510dd6527531908410868f59e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Bajsarowicz?= Date: Sun, 3 Aug 2025 21:24:39 +0200 Subject: [PATCH 02/11] SwiftOtter-SOP-348 Add support for Catalog Search with negative page number --- ...aginationResetOnNegativePageNumberTest.xml | 51 +++++++++++++++++ .../CatalogSearch/Controller/Result/Index.php | 47 ++++++++++------ .../CatalogSearchWithNegativeQuantityTest.xml | 56 +++++++++++++++++++ 3 files changed, 136 insertions(+), 18 deletions(-) create mode 100644 app/code/Magento/Catalog/Test/Mftf/Test/StorefrontPaginationResetOnNegativePageNumberTest.xml create mode 100644 app/code/Magento/CatalogSearch/Test/Mftf/Test/CatalogSearchWithNegativeQuantityTest.xml diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontPaginationResetOnNegativePageNumberTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontPaginationResetOnNegativePageNumberTest.xml new file mode 100644 index 0000000000000..74ba73f8f3f39 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontPaginationResetOnNegativePageNumberTest.xml @@ -0,0 +1,51 @@ + + + + + + + + + <description value="Pagination should reset on storefront when negative page number requested"/> + <severity value="AVERAGE"/> + <testCaseId value=""/> + <useCaseId value=""/> + <group value="catalog"/> + </annotations> + <before> + <createData entity="SimpleSubCategory" stepKey="createCategory"/> + <createData entity="SimpleProduct" stepKey="createSimpleProductOne"> + <requiredEntity createDataKey="createCategory"/> + </createData> + <createData entity="SimpleProduct" stepKey="createSimpleProductTwo"> + <requiredEntity createDataKey="createCategory"/> + </createData> + </before> + <after> + <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> + <deleteData createDataKey="createSimpleProductOne" stepKey="deleteProductOne"/> + <deleteData createDataKey="createSimpleProductTwo" stepKey="deleteProductTwo"/> + </after> + <!-- Go to category page with `-1` as a Page Number --> + <amOnPage url="{{StorefrontCategoryPage.url($$createCategory.custom_attributes[url_key]$$)}}?p=-1" stepKey="goToStorefrontCreatedCategoryPage"/> + <!-- Expect redirect to the base URL of category, without any pagination --> + <actionGroup ref="AdminGridAssertCurrentPageNumberActionGroup" stepKey="assertCurrentPageIsTwoOnProductGridFirstSearch"> + <argument name="expectedCurrentPageNumber" value="1"/> + </actionGroup> + <!-- Validate that "no products found" error message is not present --> + <actionGroup ref="StorefrontDontSeeNoProductsFoundActionGroup" stepKey="dontSeeNoProdsFoundMessage"/> + <!-- Verify the products are visible on the Category Page --> + <actionGroup ref="AssertStorefrontProductIsPresentOnCategoryPageActionGroup" stepKey="seeProductOneOnGrid"> + <argument name="productName" value="$$createSimpleProductOne.name$$"/> + </actionGroup> + <actionGroup ref="AssertStorefrontProductIsPresentOnCategoryPageActionGroup" stepKey="seeProductTwoOnGrid"> + <argument name="productName" value="$$createSimpleProductTwo.name$$"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/CatalogSearch/Controller/Result/Index.php b/app/code/Magento/CatalogSearch/Controller/Result/Index.php index 61d55d10dd45b..da588c5940f82 100644 --- a/app/code/Magento/CatalogSearch/Controller/Result/Index.php +++ b/app/code/Magento/CatalogSearch/Controller/Result/Index.php @@ -3,8 +3,10 @@ * Copyright 2014 Adobe * All Rights Reserved. */ + namespace Magento\CatalogSearch\Controller\Result; +use Magento\Catalog\Model\Product\ProductList\Toolbar; use Magento\Framework\App\Action\HttpGetActionInterface; use Magento\Catalog\Model\Layer\Resolver; use Magento\Catalog\Model\Session; @@ -88,28 +90,37 @@ public function execute() $queryText = $query->getQueryText(); - if ($queryText != '') { - $catalogSearchHelper = $this->_objectManager->get(\Magento\CatalogSearch\Helper\Data::class); + if (empty($queryText)) { + $this->getResponse()->setRedirect($this->_redirect->getRedirectUrl()); + return; + } - $getAdditionalRequestParameters = $this->getRequest()->getParams(); - unset($getAdditionalRequestParameters[QueryFactory::QUERY_VAR_NAME]); + // Negative ?p= value is not supported, redirect to a base version of category page. + if ($this->_request->getParam(Toolbar::PAGE_PARM_NAME) < 0) { + $this->getResponse()->setRedirect( + $this->_url->getUrl('*/*', ['_current' => true, '_query' => [Toolbar::PAGE_PARM_NAME => null]]) + ); + return; + } - $handles = null; - if ($query->getNumResults() == 0) { - $this->_view->getPage()->initLayout(); - $handles = $this->_view->getLayout()->getUpdate()->getHandles(); - $handles[] = static::DEFAULT_NO_RESULT_HANDLE; - } + $catalogSearchHelper = $this->_objectManager->get(\Magento\CatalogSearch\Helper\Data::class); - if (empty($getAdditionalRequestParameters) && - $this->_objectManager->get(PopularSearchTerms::class)->isCacheable($queryText, $storeId) - ) { - $this->getCacheableResult($catalogSearchHelper, $query, $handles); - } else { - $this->getNotCacheableResult($catalogSearchHelper, $query, $handles); - } + $getAdditionalRequestParameters = $this->getRequest()->getParams(); + unset($getAdditionalRequestParameters[QueryFactory::QUERY_VAR_NAME]); + + $handles = null; + if ($query->getNumResults() == 0) { + $this->_view->getPage()->initLayout(); + $handles = $this->_view->getLayout()->getUpdate()->getHandles(); + $handles[] = static::DEFAULT_NO_RESULT_HANDLE; + } + + if (empty($getAdditionalRequestParameters) && + $this->_objectManager->get(PopularSearchTerms::class)->isCacheable($queryText, $storeId) + ) { + $this->getCacheableResult($catalogSearchHelper, $query, $handles); } else { - $this->getResponse()->setRedirect($this->_redirect->getRedirectUrl()); + $this->getNotCacheableResult($catalogSearchHelper, $query, $handles); } } diff --git a/app/code/Magento/CatalogSearch/Test/Mftf/Test/CatalogSearchWithNegativeQuantityTest.xml b/app/code/Magento/CatalogSearch/Test/Mftf/Test/CatalogSearchWithNegativeQuantityTest.xml new file mode 100644 index 0000000000000..b515c3a2e7539 --- /dev/null +++ b/app/code/Magento/CatalogSearch/Test/Mftf/Test/CatalogSearchWithNegativeQuantityTest.xml @@ -0,0 +1,56 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2019 Adobe + * All Rights Reserved. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="CatalogSearchWithNegativeQuantityTest"> + <annotations> + <features value="CatalogSearch"/> + <stories value="Catalog Search Results"/> + <title value="Pagination should reset on Storefront when negative page number requested"/> + <description value="Pagination should reset on Storefront when negative page number requested"/> + <severity value="AVERAGE"/> + <testCaseId value=""/> + <useCaseId value=""/> + <group value="CatalogSearch"/> + <group value="SearchEngine"/> + </annotations> + <before> + <createData entity="SimpleSubCategory" stepKey="createCategory"/> + <createData entity="SimpleProduct" stepKey="createSimpleProductOne"> + <requiredEntity createDataKey="createCategory"/> + </createData> + <createData entity="SimpleProduct" stepKey="createSimpleProductTwo"> + <requiredEntity createDataKey="createCategory"/> + </createData> + </before> + <after> + <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> + <deleteData createDataKey="createSimpleProductOne" stepKey="deleteProductOne"/> + <deleteData createDataKey="createSimpleProductTwo" stepKey="deleteProductTwo"/> + </after> + + <actionGroup ref="StorefrontQuickSearchWithPaginationActionGroup" stepKey="visitSearchResults"> + <argument name="phrase" value="simple"/> + <argument name="pageNumber" value="-1"/> + </actionGroup> + + <actionGroup ref="AdminGridAssertCurrentPageNumberActionGroup" stepKey="assertCurrentPageIsTwoOnProductGridFirstSearch"> + <argument name="expectedCurrentPageNumber" value="1"/> + </actionGroup> + <!-- Validate that "no products found" error message is not present --> + <actionGroup ref="StorefrontDontSeeNoProductsFoundActionGroup" stepKey="dontSeeNoProdsFoundMessage"/> + <!-- Verify the products are visible on the Category Page --> + <actionGroup ref="AssertStorefrontProductIsPresentOnCategoryPageActionGroup" stepKey="seeProductOneOnGrid"> + <argument name="productName" value="$$createSimpleProductOne.name$$"/> + </actionGroup> + <actionGroup ref="AssertStorefrontProductIsPresentOnCategoryPageActionGroup" stepKey="seeProductTwoOnGrid"> + <argument name="productName" value="$$createSimpleProductTwo.name$$"/> + </actionGroup> + </test> +</tests> From 5b25921fdeeffb2b533ef7f18d1b33342a6cdbdb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Bajsarowicz?= <lukasz@lbajsarowicz.me> Date: Mon, 4 Aug 2025 09:59:35 +0200 Subject: [PATCH 03/11] SwiftOtter-SOP-348 Fix invalid Action Group reference --- .../StorefrontPaginationResetOnNegativePageNumberTest.xml | 4 ++-- .../Test/Mftf/Test/CatalogSearchWithNegativeQuantityTest.xml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontPaginationResetOnNegativePageNumberTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontPaginationResetOnNegativePageNumberTest.xml index 74ba73f8f3f39..99354d290d01a 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontPaginationResetOnNegativePageNumberTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontPaginationResetOnNegativePageNumberTest.xml @@ -35,8 +35,8 @@ <!-- Go to category page with `-1` as a Page Number --> <amOnPage url="{{StorefrontCategoryPage.url($$createCategory.custom_attributes[url_key]$$)}}?p=-1" stepKey="goToStorefrontCreatedCategoryPage"/> <!-- Expect redirect to the base URL of category, without any pagination --> - <actionGroup ref="AdminGridAssertCurrentPageNumberActionGroup" stepKey="assertCurrentPageIsTwoOnProductGridFirstSearch"> - <argument name="expectedCurrentPageNumber" value="1"/> + <actionGroup ref="AssertStorefrontCategoryCurrentPageIsNthActionGroup" stepKey="assertCurrentPageIsTwoOnProductGridFirstSearch"> + <argument name="expectedPage" value="1"/> </actionGroup> <!-- Validate that "no products found" error message is not present --> <actionGroup ref="StorefrontDontSeeNoProductsFoundActionGroup" stepKey="dontSeeNoProdsFoundMessage"/> diff --git a/app/code/Magento/CatalogSearch/Test/Mftf/Test/CatalogSearchWithNegativeQuantityTest.xml b/app/code/Magento/CatalogSearch/Test/Mftf/Test/CatalogSearchWithNegativeQuantityTest.xml index b515c3a2e7539..bd44f7d63ae60 100644 --- a/app/code/Magento/CatalogSearch/Test/Mftf/Test/CatalogSearchWithNegativeQuantityTest.xml +++ b/app/code/Magento/CatalogSearch/Test/Mftf/Test/CatalogSearchWithNegativeQuantityTest.xml @@ -40,8 +40,8 @@ <argument name="pageNumber" value="-1"/> </actionGroup> - <actionGroup ref="AdminGridAssertCurrentPageNumberActionGroup" stepKey="assertCurrentPageIsTwoOnProductGridFirstSearch"> - <argument name="expectedCurrentPageNumber" value="1"/> + <actionGroup ref="AssertStorefrontCategoryCurrentPageIsNthActionGroup" stepKey="assertCurrentPageIsTwoOnProductGridFirstSearch"> + <argument name="expectedPage" value="1"/> </actionGroup> <!-- Validate that "no products found" error message is not present --> <actionGroup ref="StorefrontDontSeeNoProductsFoundActionGroup" stepKey="dontSeeNoProdsFoundMessage"/> From ab12b284ad82d69fdfa04cd78f8fcde0ac2a510c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Bajsarowicz?= <lukasz@lbajsarowicz.me> Date: Mon, 4 Aug 2025 15:18:43 +0200 Subject: [PATCH 04/11] SwiftOtter-SOP-348 Don't use Toolbar to determine Current Page --- .../StorefrontPaginationResetOnNegativePageNumberTest.xml | 4 +--- .../Test/Mftf/Test/CatalogSearchWithNegativeQuantityTest.xml | 4 +--- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontPaginationResetOnNegativePageNumberTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontPaginationResetOnNegativePageNumberTest.xml index 99354d290d01a..1655435366a01 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontPaginationResetOnNegativePageNumberTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontPaginationResetOnNegativePageNumberTest.xml @@ -35,9 +35,7 @@ <!-- Go to category page with `-1` as a Page Number --> <amOnPage url="{{StorefrontCategoryPage.url($$createCategory.custom_attributes[url_key]$$)}}?p=-1" stepKey="goToStorefrontCreatedCategoryPage"/> <!-- Expect redirect to the base URL of category, without any pagination --> - <actionGroup ref="AssertStorefrontCategoryCurrentPageIsNthActionGroup" stepKey="assertCurrentPageIsTwoOnProductGridFirstSearch"> - <argument name="expectedPage" value="1"/> - </actionGroup> + <seeCurrentUrlEquals url="https://{$hostname}{{StorefrontCategoryPage.url($$createCategory.custom_attributes[url_key]$$)}}" stepKey="seeUrlWithNoPage"/> <!-- Validate that "no products found" error message is not present --> <actionGroup ref="StorefrontDontSeeNoProductsFoundActionGroup" stepKey="dontSeeNoProdsFoundMessage"/> <!-- Verify the products are visible on the Category Page --> diff --git a/app/code/Magento/CatalogSearch/Test/Mftf/Test/CatalogSearchWithNegativeQuantityTest.xml b/app/code/Magento/CatalogSearch/Test/Mftf/Test/CatalogSearchWithNegativeQuantityTest.xml index bd44f7d63ae60..5360764f032f1 100644 --- a/app/code/Magento/CatalogSearch/Test/Mftf/Test/CatalogSearchWithNegativeQuantityTest.xml +++ b/app/code/Magento/CatalogSearch/Test/Mftf/Test/CatalogSearchWithNegativeQuantityTest.xml @@ -40,9 +40,7 @@ <argument name="pageNumber" value="-1"/> </actionGroup> - <actionGroup ref="AssertStorefrontCategoryCurrentPageIsNthActionGroup" stepKey="assertCurrentPageIsTwoOnProductGridFirstSearch"> - <argument name="expectedPage" value="1"/> - </actionGroup> + <seeCurrentUrlEquals url="https://{$hostname}{{StorefrontCatalogSearchPage.url}}?q={{phrase}}" stepKey="seeUrlWithNoPage"/> <!-- Validate that "no products found" error message is not present --> <actionGroup ref="StorefrontDontSeeNoProductsFoundActionGroup" stepKey="dontSeeNoProdsFoundMessage"/> <!-- Verify the products are visible on the Category Page --> From 3ec1e56eb0215943a59bab892901de6791fb40d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Bajsarowicz?= <lukasz@lbajsarowicz.me> Date: Mon, 4 Aug 2025 17:11:18 +0200 Subject: [PATCH 05/11] SwiftOtter-SOP-348 Change approach to verify current URL --- .../Test/StorefrontPaginationResetOnNegativePageNumberTest.xml | 2 +- .../Test/Mftf/Test/CatalogSearchWithNegativeQuantityTest.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontPaginationResetOnNegativePageNumberTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontPaginationResetOnNegativePageNumberTest.xml index 1655435366a01..c1329f84febfa 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontPaginationResetOnNegativePageNumberTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontPaginationResetOnNegativePageNumberTest.xml @@ -35,7 +35,7 @@ <!-- Go to category page with `-1` as a Page Number --> <amOnPage url="{{StorefrontCategoryPage.url($$createCategory.custom_attributes[url_key]$$)}}?p=-1" stepKey="goToStorefrontCreatedCategoryPage"/> <!-- Expect redirect to the base URL of category, without any pagination --> - <seeCurrentUrlEquals url="https://{$hostname}{{StorefrontCategoryPage.url($$createCategory.custom_attributes[url_key]$$)}}" stepKey="seeUrlWithNoPage"/> + <seeCurrentUrlMatches regex="~{{StorefrontCategoryPage.url($$createCategory.custom_attributes[url_key]$$)}}$~" stepKey="seeUrlWithNoPage"/> <!-- Validate that "no products found" error message is not present --> <actionGroup ref="StorefrontDontSeeNoProductsFoundActionGroup" stepKey="dontSeeNoProdsFoundMessage"/> <!-- Verify the products are visible on the Category Page --> diff --git a/app/code/Magento/CatalogSearch/Test/Mftf/Test/CatalogSearchWithNegativeQuantityTest.xml b/app/code/Magento/CatalogSearch/Test/Mftf/Test/CatalogSearchWithNegativeQuantityTest.xml index 5360764f032f1..9f996c5316bb0 100644 --- a/app/code/Magento/CatalogSearch/Test/Mftf/Test/CatalogSearchWithNegativeQuantityTest.xml +++ b/app/code/Magento/CatalogSearch/Test/Mftf/Test/CatalogSearchWithNegativeQuantityTest.xml @@ -40,7 +40,7 @@ <argument name="pageNumber" value="-1"/> </actionGroup> - <seeCurrentUrlEquals url="https://{$hostname}{{StorefrontCatalogSearchPage.url}}?q={{phrase}}" stepKey="seeUrlWithNoPage"/> + <seeCurrentUrlMatches regex="~{{StorefrontCatalogSearchPage.url}}?q={{phrase}}$~" stepKey="seeUrlWithNoPage"/> <!-- Validate that "no products found" error message is not present --> <actionGroup ref="StorefrontDontSeeNoProductsFoundActionGroup" stepKey="dontSeeNoProdsFoundMessage"/> <!-- Verify the products are visible on the Category Page --> From f9152d25a487048318fbbf3fbf4793c337e970b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Bajsarowicz?= <lukasz@lbajsarowicz.me> Date: Tue, 5 Aug 2025 11:39:50 +0200 Subject: [PATCH 06/11] SwiftOtter-SOP-348 Avoid referring to previously defined phrase --- .../Test/Mftf/Test/CatalogSearchWithNegativeQuantityTest.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/Magento/CatalogSearch/Test/Mftf/Test/CatalogSearchWithNegativeQuantityTest.xml b/app/code/Magento/CatalogSearch/Test/Mftf/Test/CatalogSearchWithNegativeQuantityTest.xml index 9f996c5316bb0..dde49a990bbae 100644 --- a/app/code/Magento/CatalogSearch/Test/Mftf/Test/CatalogSearchWithNegativeQuantityTest.xml +++ b/app/code/Magento/CatalogSearch/Test/Mftf/Test/CatalogSearchWithNegativeQuantityTest.xml @@ -40,7 +40,7 @@ <argument name="pageNumber" value="-1"/> </actionGroup> - <seeCurrentUrlMatches regex="~{{StorefrontCatalogSearchPage.url}}?q={{phrase}}$~" stepKey="seeUrlWithNoPage"/> + <seeCurrentUrlMatches regex="~{{StorefrontCatalogSearchPage.url}}?q=simple$~" stepKey="seeUrlWithNoPage"/> <!-- Validate that "no products found" error message is not present --> <actionGroup ref="StorefrontDontSeeNoProductsFoundActionGroup" stepKey="dontSeeNoProdsFoundMessage"/> <!-- Verify the products are visible on the Category Page --> From e648878a9051bb07b503492c0d5537a033e4914b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Bajsarowicz?= <lukasz@lbajsarowicz.me> Date: Tue, 5 Aug 2025 15:09:04 +0200 Subject: [PATCH 07/11] SwiftOtter-SOP-348 Replace variable with hardcoded URL in MFTF regex --- .../Test/Mftf/Test/CatalogSearchWithNegativeQuantityTest.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/Magento/CatalogSearch/Test/Mftf/Test/CatalogSearchWithNegativeQuantityTest.xml b/app/code/Magento/CatalogSearch/Test/Mftf/Test/CatalogSearchWithNegativeQuantityTest.xml index dde49a990bbae..bb6753b299f24 100644 --- a/app/code/Magento/CatalogSearch/Test/Mftf/Test/CatalogSearchWithNegativeQuantityTest.xml +++ b/app/code/Magento/CatalogSearch/Test/Mftf/Test/CatalogSearchWithNegativeQuantityTest.xml @@ -40,7 +40,7 @@ <argument name="pageNumber" value="-1"/> </actionGroup> - <seeCurrentUrlMatches regex="~{{StorefrontCatalogSearchPage.url}}?q=simple$~" stepKey="seeUrlWithNoPage"/> + <seeCurrentUrlMatches regex="~\/catalogsearch\/result\/?q=simple$~" stepKey="seeUrlWithNoPage"/> <!-- Validate that "no products found" error message is not present --> <actionGroup ref="StorefrontDontSeeNoProductsFoundActionGroup" stepKey="dontSeeNoProdsFoundMessage"/> <!-- Verify the products are visible on the Category Page --> From 1df0e7f2d398b7bf2326d07cfcdd24afca9c1e5f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Bajsarowicz?= <lukasz@lbajsarowicz.me> Date: Tue, 5 Aug 2025 22:58:25 +0200 Subject: [PATCH 08/11] SwiftOtter-SOP-348 Replace variable with hardcoded URL in MFTF regex --- .../Test/Mftf/Test/CatalogSearchWithNegativeQuantityTest.xml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/code/Magento/CatalogSearch/Test/Mftf/Test/CatalogSearchWithNegativeQuantityTest.xml b/app/code/Magento/CatalogSearch/Test/Mftf/Test/CatalogSearchWithNegativeQuantityTest.xml index bb6753b299f24..fc7eb7d833b1a 100644 --- a/app/code/Magento/CatalogSearch/Test/Mftf/Test/CatalogSearchWithNegativeQuantityTest.xml +++ b/app/code/Magento/CatalogSearch/Test/Mftf/Test/CatalogSearchWithNegativeQuantityTest.xml @@ -40,7 +40,9 @@ <argument name="pageNumber" value="-1"/> </actionGroup> - <seeCurrentUrlMatches regex="~\/catalogsearch\/result\/?q=simple$~" stepKey="seeUrlWithNoPage"/> + <!-- Expect redirect to the base URL of category, without any pagination --> + <seeCurrentUrlEquals url="{{_ENV.MAGENTO_BASE_URL}}{{StorefrontCatalogSearchPage.url}}?q=simple" stepKey="seeUrlWithNoPage"/> + <!-- Validate that "no products found" error message is not present --> <actionGroup ref="StorefrontDontSeeNoProductsFoundActionGroup" stepKey="dontSeeNoProdsFoundMessage"/> <!-- Verify the products are visible on the Category Page --> From 130d7d675758b21d4b5f4a6cd74a712e402a1221 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Bajsarowicz?= <lukasz@lbajsarowicz.me> Date: Wed, 6 Aug 2025 10:46:35 +0200 Subject: [PATCH 09/11] SwiftOtter-SOP-348 Yet another fix. --- .../StorefrontPaginationResetOnNegativePageNumberTest.xml | 2 -- ...yTest.xml => CatalogSearchWithNegativePageNumberTest.xml} | 5 +---- 2 files changed, 1 insertion(+), 6 deletions(-) rename app/code/Magento/CatalogSearch/Test/Mftf/Test/{CatalogSearchWithNegativeQuantityTest.xml => CatalogSearchWithNegativePageNumberTest.xml} (90%) diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontPaginationResetOnNegativePageNumberTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontPaginationResetOnNegativePageNumberTest.xml index c1329f84febfa..ff1e3d22a2479 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontPaginationResetOnNegativePageNumberTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontPaginationResetOnNegativePageNumberTest.xml @@ -34,8 +34,6 @@ </after> <!-- Go to category page with `-1` as a Page Number --> <amOnPage url="{{StorefrontCategoryPage.url($$createCategory.custom_attributes[url_key]$$)}}?p=-1" stepKey="goToStorefrontCreatedCategoryPage"/> - <!-- Expect redirect to the base URL of category, without any pagination --> - <seeCurrentUrlMatches regex="~{{StorefrontCategoryPage.url($$createCategory.custom_attributes[url_key]$$)}}$~" stepKey="seeUrlWithNoPage"/> <!-- Validate that "no products found" error message is not present --> <actionGroup ref="StorefrontDontSeeNoProductsFoundActionGroup" stepKey="dontSeeNoProdsFoundMessage"/> <!-- Verify the products are visible on the Category Page --> diff --git a/app/code/Magento/CatalogSearch/Test/Mftf/Test/CatalogSearchWithNegativeQuantityTest.xml b/app/code/Magento/CatalogSearch/Test/Mftf/Test/CatalogSearchWithNegativePageNumberTest.xml similarity index 90% rename from app/code/Magento/CatalogSearch/Test/Mftf/Test/CatalogSearchWithNegativeQuantityTest.xml rename to app/code/Magento/CatalogSearch/Test/Mftf/Test/CatalogSearchWithNegativePageNumberTest.xml index fc7eb7d833b1a..a4b9a90ab9bd7 100644 --- a/app/code/Magento/CatalogSearch/Test/Mftf/Test/CatalogSearchWithNegativeQuantityTest.xml +++ b/app/code/Magento/CatalogSearch/Test/Mftf/Test/CatalogSearchWithNegativePageNumberTest.xml @@ -8,7 +8,7 @@ <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> - <test name="CatalogSearchWithNegativeQuantityTest"> + <test name="CatalogSearchWithNegativePageNumberTest"> <annotations> <features value="CatalogSearch"/> <stories value="Catalog Search Results"/> @@ -40,9 +40,6 @@ <argument name="pageNumber" value="-1"/> </actionGroup> - <!-- Expect redirect to the base URL of category, without any pagination --> - <seeCurrentUrlEquals url="{{_ENV.MAGENTO_BASE_URL}}{{StorefrontCatalogSearchPage.url}}?q=simple" stepKey="seeUrlWithNoPage"/> - <!-- Validate that "no products found" error message is not present --> <actionGroup ref="StorefrontDontSeeNoProductsFoundActionGroup" stepKey="dontSeeNoProdsFoundMessage"/> <!-- Verify the products are visible on the Category Page --> From 58a66a2c8dd4d7d5d6ba2ef25d7d63b9f2af7d66 Mon Sep 17 00:00:00 2001 From: Lukasz Bajsarowicz <lukasz@swiftotter.com> Date: Thu, 21 Aug 2025 09:08:45 +0200 Subject: [PATCH 10/11] SwiftOtter-SOP-348 Reduce cyclomatic complexity (<10) + fix Static checks --- .../Catalog/Controller/Category/View.php | 157 ++++++++++-------- 1 file changed, 89 insertions(+), 68 deletions(-) diff --git a/app/code/Magento/Catalog/Controller/Category/View.php b/app/code/Magento/Catalog/Controller/Category/View.php index 66f7b8801835e..9945c6bb8d9ac 100644 --- a/app/code/Magento/Catalog/Controller/Category/View.php +++ b/app/code/Magento/Catalog/Controller/Category/View.php @@ -3,6 +3,9 @@ * Copyright 2014 Adobe * All Rights Reserved. */ + +declare(strict_types=1); + namespace Magento\Catalog\Controller\Category; use Magento\Catalog\Api\CategoryRepositoryInterface; @@ -11,8 +14,8 @@ use Magento\Catalog\Model\Category\Attribute\LayoutUpdateManager; use Magento\Catalog\Model\Design; use Magento\Catalog\Model\Layer\Resolver; -use Magento\Catalog\Model\Product\ProductList\ToolbarMemorizer; use Magento\Catalog\Model\Product\ProductList\Toolbar; +use Magento\Catalog\Model\Product\ProductList\ToolbarMemorizer; use Magento\Catalog\Model\Session; use Magento\CatalogUrlRewrite\Model\CategoryUrlPathGenerator; use Magento\Framework\App\Action\Action; @@ -21,8 +24,10 @@ use Magento\Framework\App\Action\HttpPostActionInterface; use Magento\Framework\App\ActionInterface; use Magento\Framework\App\ObjectManager; +use Magento\Framework\Controller\Result\Forward; use Magento\Framework\Controller\Result\ForwardFactory; -use Magento\Framework\Controller\ResultFactory; +use Magento\Framework\Controller\Result\Redirect; +use Magento\Framework\Controller\ResultInterface; use Magento\Framework\DataObject; use Magento\Framework\Exception\LocalizedException; use Magento\Framework\Exception\NoSuchEntityException; @@ -107,8 +112,6 @@ class View extends Action implements HttpGetActionInterface, HttpPostActionInter private $logger; /** - * Constructor - * * @param Context $context * @param Design $catalogDesign * @param Session $catalogSession @@ -121,8 +124,8 @@ class View extends Action implements HttpGetActionInterface, HttpPostActionInter * @param CategoryRepositoryInterface $categoryRepository * @param ToolbarMemorizer|null $toolbarMemorizer * @param LayoutUpdateManager|null $layoutUpdateManager - * @param CategoryHelper $categoryHelper - * @param LoggerInterface $logger + * @param CategoryHelper|null $categoryHelper + * @param LoggerInterface|null $logger * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ public function __construct( @@ -161,9 +164,7 @@ public function __construct( } /** - * Initialize requested category object - * - * @return Category|bool + * @return Category|false */ protected function _initCategory() { @@ -189,7 +190,7 @@ protected function _initCategory() ['category' => $category, 'controller_action' => $this] ); } catch (LocalizedException $e) { - $this->logger->critical($e); + $this->logger->critical((string)$e); return false; } @@ -197,93 +198,116 @@ protected function _initCategory() } /** - * Category view action - * + * @return ResultInterface * @throws NoSuchEntityException */ public function execute() { - $result = null; - if ($this->_request->getParam(ActionInterface::PARAM_NAME_URL_ENCODED)) { //phpcs:ignore Magento2.Legacy.ObsoleteResponse return $this->resultRedirectFactory->create()->setUrl($this->_redirect->getRedirectUrl()); } $category = $this->_initCategory(); - if ($category) { - // Negative ?p= value is not supported, redirect to a base version of category page. - if ($this->_request->getParam(Toolbar::PAGE_PARM_NAME) < 0) { - return $this->resultRedirectFactory->create() - ->setHttpResponseCode(301) - ->setUrl($category->getUrl()); - } + if (!$category) { + return $this->resultForwardFactory->create()->forward('noroute'); + } - $this->layerResolver->create(Resolver::CATALOG_LAYER_CATEGORY); - $settings = $this->_catalogDesign->getDesignSettings($category); + $pageRedirect = $this->handlePageRedirect($category); + if ($pageRedirect) { + return $pageRedirect; + } - // apply custom design - if ($settings->getCustomDesign()) { - $this->_catalogDesign->applyCustomDesign($settings->getCustomDesign()); - } + $page = $this->preparePage($category); - $this->_catalogSession->setLastViewedCategoryId($category->getId()); + if ($this->shouldRedirectOnToolbarAction()) { + $this->getResponse()->setRedirect($this->_redirect->getRedirectUrl()); + } - $page = $this->resultPageFactory->create(); - // apply custom layout (page) template once the blocks are generated - if ($settings->getPageLayout()) { - $page->getConfig()->setPageLayout($settings->getPageLayout()); - } + return $page; + } - $pageType = $this->getPageType($category); + /** + * @param Category $category + * @return Redirect|null + */ + private function handlePageRedirect(Category $category): ?Redirect + { + if ($this->_request->getParam(Toolbar::PAGE_PARM_NAME) < 0) { + return $this->resultRedirectFactory->create() + ->setHttpResponseCode(301) + ->setUrl($category->getUrl()); + } - if (!$category->hasChildren()) { - // Two levels removed from parent. Need to add default page type. - $parentPageType = strtok($pageType, '_'); - $page->addPageLayoutHandles(['type' => $parentPageType], null, false); - } - $page->addPageLayoutHandles(['type' => $pageType], null, false); - $categoryDisplayMode = is_string($category->getDisplayMode()) ? - strtolower($category->getDisplayMode()) : ''; - $page->addPageLayoutHandles(['displaymode' => $categoryDisplayMode], null, false); - $page->addPageLayoutHandles(['id' => $category->getId()]); + return null; + } - // apply custom layout update once layout is loaded - $this->applyLayoutUpdates($page, $settings); + /** + * @param Category $category + * @return Page + */ + private function preparePage(Category $category): Page + { + $this->layerResolver->create(Resolver::CATALOG_LAYER_CATEGORY); + $settings = $this->_catalogDesign->getDesignSettings($category); + + if ($settings->getCustomDesign()) { + $this->_catalogDesign->applyCustomDesign($settings->getCustomDesign()); + } - $page->getConfig()->addBodyClass('page-products') - ->addBodyClass('categorypath-' . $this->categoryUrlPathGenerator->getUrlPath($category)) - ->addBodyClass('category-' . $category->getUrlKey()); + $this->_catalogSession->setLastViewedCategoryId($category->getId()); - if ($this->shouldRedirectOnToolbarAction()) { - $this->getResponse()->setRedirect($this->_redirect->getRedirectUrl()); - } - return $page; - } elseif (!$this->getResponse()->isRedirect()) { - $result = $this->resultForwardFactory->create()->forward('noroute'); + $page = $this->resultPageFactory->create(); + if ($settings->getPageLayout()) { + $page->getConfig()->setPageLayout($settings->getPageLayout()); } - return $result; + + $this->addPageLayoutHandles($page, $category); + $this->applyLayoutUpdates($page, $settings); + + $page->getConfig()->addBodyClass('page-products') + ->addBodyClass('categorypath-' . $this->categoryUrlPathGenerator->getUrlPath($category)) + ->addBodyClass('category-' . $category->getUrlKey()); + + return $page; + } + + /** + * @param Page $page + * @param Category $category + * @return void + */ + private function addPageLayoutHandles(Page $page, Category $category): void + { + $pageType = $this->getPageType($category); + + if (!$category->hasChildren()) { + $parentPageType = strtok($pageType, '_'); + $page->addPageLayoutHandles(['type' => $parentPageType], null, false); + } + $page->addPageLayoutHandles(['type' => $pageType], null, false); + + $categoryDisplayMode = is_string($category->getDisplayMode()) ? + strtolower($category->getDisplayMode()) : ''; + $page->addPageLayoutHandles(['displaymode' => $categoryDisplayMode], null, false); + $page->addPageLayoutHandles(['id' => $category->getId()]); } /** - * Get page type based on category - * * @param Category $category * @return string */ - private function getPageType(Category $category) : string + private function getPageType(Category $category): string { $hasChildren = $category->hasChildren(); if ($category->getIsAnchor()) { - return $hasChildren ? 'layered' : 'layered_without_children'; + return $hasChildren ? 'layered' : 'layered_without_children'; } return $hasChildren ? 'default' : 'default_without_children'; } /** - * Apply custom layout updates - * * @param Page $page * @param DataObject $settings * @return void @@ -291,7 +315,7 @@ private function getPageType(Category $category) : string private function applyLayoutUpdates( Page $page, DataObject $settings - ) { + ): void { $layoutUpdates = $settings->getLayoutUpdates(); if ($layoutUpdates && is_array($layoutUpdates)) { foreach ($layoutUpdates as $layoutUpdate) { @@ -300,26 +324,23 @@ private function applyLayoutUpdates( } } - //Selected files if ($settings->getPageLayoutHandles()) { $page->addPageLayoutHandles($settings->getPageLayoutHandles()); } } /** - * Checks for toolbar actions - * * @return bool */ private function shouldRedirectOnToolbarAction(): bool { $params = $this->getRequest()->getParams(); - return $this->toolbarMemorizer->isMemorizingAllowed() && empty(array_intersect([ + return $this->toolbarMemorizer->isMemorizingAllowed() && !empty(array_intersect([ Toolbar::ORDER_PARAM_NAME, Toolbar::DIRECTION_PARAM_NAME, Toolbar::MODE_PARAM_NAME, Toolbar::LIMIT_PARAM_NAME - ], array_keys($params))) === false; + ], array_keys($params))); } } From 3cc831c2a2325b11d48f09a8e5487acda983bc67 Mon Sep 17 00:00:00 2001 From: Lukasz Bajsarowicz <lukasz@swiftotter.com> Date: Thu, 21 Aug 2025 17:13:58 +0200 Subject: [PATCH 11/11] SwiftOtter-SOP-348 Restore redundant PHPDoc blocks --- .../Magento/Catalog/Controller/Category/View.php | 14 +++++++++++++- .../CatalogSearch/Controller/Result/Index.php | 6 +----- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/app/code/Magento/Catalog/Controller/Category/View.php b/app/code/Magento/Catalog/Controller/Category/View.php index 9945c6bb8d9ac..10487e50c91cf 100644 --- a/app/code/Magento/Catalog/Controller/Category/View.php +++ b/app/code/Magento/Catalog/Controller/Category/View.php @@ -112,6 +112,8 @@ class View extends Action implements HttpGetActionInterface, HttpPostActionInter private $logger; /** + * Constructor + * * @param Context $context * @param Design $catalogDesign * @param Session $catalogSession @@ -164,7 +166,9 @@ public function __construct( } /** - * @return Category|false + * Initialize requested category object + * + * @return Category|bool */ protected function _initCategory() { @@ -198,6 +202,8 @@ protected function _initCategory() } /** + * Category view action + * * @return ResultInterface * @throws NoSuchEntityException */ @@ -294,6 +300,8 @@ private function addPageLayoutHandles(Page $page, Category $category): void } /** + * Get page type based on category + * * @param Category $category * @return string */ @@ -308,6 +316,8 @@ private function getPageType(Category $category): string } /** + * Apply custom layout updates + * * @param Page $page * @param DataObject $settings * @return void @@ -330,6 +340,8 @@ private function applyLayoutUpdates( } /** + * Checks for toolbar actions + * * @return bool */ private function shouldRedirectOnToolbarAction(): bool diff --git a/app/code/Magento/CatalogSearch/Controller/Result/Index.php b/app/code/Magento/CatalogSearch/Controller/Result/Index.php index da588c5940f82..ff32f01494769 100644 --- a/app/code/Magento/CatalogSearch/Controller/Result/Index.php +++ b/app/code/Magento/CatalogSearch/Controller/Result/Index.php @@ -24,11 +24,9 @@ class Index extends \Magento\Framework\App\Action\Action implements HttpGetActio /** * No results default handle. */ - const DEFAULT_NO_RESULT_HANDLE = 'catalogsearch_result_index_noresults'; + public const DEFAULT_NO_RESULT_HANDLE = 'catalogsearch_result_index_noresults'; /** - * Catalog session - * * @var Session */ protected $_catalogSession; @@ -44,8 +42,6 @@ class Index extends \Magento\Framework\App\Action\Action implements HttpGetActio private $_queryFactory; /** - * Catalog Layer Resolver - * * @var Resolver */ private $layerResolver;