Skip to content

Commit 406bf77

Browse files
committed
Merge branch '2.4-develop' of https://github.com/mage-os/mirror-magento2 into 2.4-develop
2 parents a9c8335 + b54387b commit 406bf77

File tree

17 files changed

+455
-75
lines changed

17 files changed

+455
-75
lines changed

app/code/Magento/Catalog/Controller/Category/View.php

Lines changed: 155 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@
33
* Copyright 2014 Adobe
44
* All Rights Reserved.
55
*/
6+
7+
declare(strict_types=1);
8+
69
namespace Magento\Catalog\Controller\Category;
710

811
use Magento\Catalog\Api\CategoryRepositoryInterface;
@@ -11,8 +14,8 @@
1114
use Magento\Catalog\Model\Category\Attribute\LayoutUpdateManager;
1215
use Magento\Catalog\Model\Design;
1316
use Magento\Catalog\Model\Layer\Resolver;
14-
use Magento\Catalog\Model\Product\ProductList\ToolbarMemorizer;
1517
use Magento\Catalog\Model\Product\ProductList\Toolbar;
18+
use Magento\Catalog\Model\Product\ProductList\ToolbarMemorizer;
1619
use Magento\Catalog\Model\Session;
1720
use Magento\CatalogUrlRewrite\Model\CategoryUrlPathGenerator;
1821
use Magento\Framework\App\Action\Action;
@@ -21,8 +24,10 @@
2124
use Magento\Framework\App\Action\HttpPostActionInterface;
2225
use Magento\Framework\App\ActionInterface;
2326
use Magento\Framework\App\ObjectManager;
27+
use Magento\Framework\Controller\Result\Forward;
2428
use Magento\Framework\Controller\Result\ForwardFactory;
25-
use Magento\Framework\Controller\ResultFactory;
29+
use Magento\Framework\Controller\Result\Redirect;
30+
use Magento\Framework\Controller\ResultInterface;
2631
use Magento\Framework\DataObject;
2732
use Magento\Framework\Exception\LocalizedException;
2833
use Magento\Framework\Exception\NoSuchEntityException;
@@ -121,8 +126,8 @@ class View extends Action implements HttpGetActionInterface, HttpPostActionInter
121126
* @param CategoryRepositoryInterface $categoryRepository
122127
* @param ToolbarMemorizer|null $toolbarMemorizer
123128
* @param LayoutUpdateManager|null $layoutUpdateManager
124-
* @param CategoryHelper $categoryHelper
125-
* @param LoggerInterface $logger
129+
* @param CategoryHelper|null $categoryHelper
130+
* @param LoggerInterface|null $logger
126131
* @SuppressWarnings(PHPMD.ExcessiveParameterList)
127132
*/
128133
public function __construct(
@@ -189,7 +194,7 @@ protected function _initCategory()
189194
['category' => $category, 'controller_action' => $this]
190195
);
191196
} catch (LocalizedException $e) {
192-
$this->logger->critical($e);
197+
$this->logger->critical((string)$e);
193198
return false;
194199
}
195200

@@ -199,63 +204,165 @@ protected function _initCategory()
199204
/**
200205
* Category view action
201206
*
207+
* @return ResultInterface
202208
* @throws NoSuchEntityException
203209
*/
204210
public function execute()
205211
{
206-
$result = null;
212+
$urlEncodedRedirect = $this->handleUrlEncodedRedirect();
213+
if ($urlEncodedRedirect) {
214+
return $urlEncodedRedirect;
215+
}
216+
217+
$category = $this->_initCategory();
218+
if (!$category) {
219+
return $this->handleCategoryNotFound();
220+
}
221+
222+
$pageRedirect = $this->handlePageRedirect($category);
223+
if ($pageRedirect) {
224+
return $pageRedirect;
225+
}
207226

227+
$page = $this->preparePage($category);
228+
229+
if ($this->shouldRedirectOnToolbarAction()) {
230+
$this->getResponse()->setRedirect($this->_redirect->getRedirectUrl());
231+
}
232+
233+
return $page;
234+
}
235+
236+
/**
237+
* Handle URL encoded redirect
238+
*
239+
* @return \Magento\Framework\Controller\Result\Redirect|null
240+
*/
241+
private function handleUrlEncodedRedirect()
242+
{
208243
if ($this->_request->getParam(ActionInterface::PARAM_NAME_URL_ENCODED)) {
209244
//phpcs:ignore Magento2.Legacy.ObsoleteResponse
210245
return $this->resultRedirectFactory->create()->setUrl($this->_redirect->getRedirectUrl());
211246
}
247+
return null;
248+
}
212249

213-
$category = $this->_initCategory();
214-
if ($category) {
215-
$this->layerResolver->create(Resolver::CATALOG_LAYER_CATEGORY);
216-
$settings = $this->_catalogDesign->getDesignSettings($category);
250+
/**
251+
* Handle category not found scenarios
252+
*
253+
* @return ResultInterface
254+
* @throws NoSuchEntityException
255+
*/
256+
private function handleCategoryNotFound()
257+
{
258+
if ($this->getResponse()->isRedirect()) {
259+
return $this->getResponse();
260+
}
261+
$categoryId = (int)$this->getRequest()->getParam('id', false);
262+
if ($categoryId && $this->isB2BPermissionDenial($categoryId)) {
263+
$this->getResponse()->setBody('');
264+
return $this->getResponse();
265+
}
266+
$resultForward = $this->resultForwardFactory->create();
267+
$resultForward->forward('noroute');
268+
return $resultForward;
269+
}
217270

218-
// apply custom design
219-
if ($settings->getCustomDesign()) {
220-
$this->_catalogDesign->applyCustomDesign($settings->getCustomDesign());
221-
}
271+
/**
272+
* Check if this is a B2B permission denial case
273+
*
274+
* @param int $categoryId
275+
* @return bool
276+
*/
277+
private function isB2BPermissionDenial(int $categoryId): bool
278+
{
279+
try {
280+
$existingCategory = $this->categoryRepository->get(
281+
$categoryId,
282+
$this->_storeManager->getStore()->getId()
283+
);
284+
return $existingCategory->getIsActive()
285+
&& $existingCategory->isInRootCategoryList()
286+
&& !$this->categoryHelper->canShow($existingCategory);
287+
} catch (NoSuchEntityException $e) {
288+
return false;
289+
}
290+
}
222291

223-
$this->_catalogSession->setLastViewedCategoryId($category->getId());
292+
/**
293+
* Category handle page redirect
294+
*
295+
* @param Category|false $category
296+
* @return Redirect|null
297+
*/
298+
private function handlePageRedirect($category): ?Redirect
299+
{
300+
if (!$category || !($category instanceof Category)) {
301+
return null;
302+
}
224303

225-
$page = $this->resultPageFactory->create();
226-
// apply custom layout (page) template once the blocks are generated
227-
if ($settings->getPageLayout()) {
228-
$page->getConfig()->setPageLayout($settings->getPageLayout());
229-
}
304+
if ($this->_request->getParam(Toolbar::PAGE_PARM_NAME) < 0) {
305+
return $this->resultRedirectFactory->create()
306+
->setHttpResponseCode(301)
307+
->setUrl($category->getUrl());
308+
}
230309

231-
$pageType = $this->getPageType($category);
310+
return null;
311+
}
232312

233-
if (!$category->hasChildren()) {
234-
// Two levels removed from parent. Need to add default page type.
235-
$parentPageType = strtok($pageType, '_');
236-
$page->addPageLayoutHandles(['type' => $parentPageType], null, false);
237-
}
238-
$page->addPageLayoutHandles(['type' => $pageType], null, false);
239-
$categoryDisplayMode = is_string($category->getDisplayMode()) ?
240-
strtolower($category->getDisplayMode()) : '';
241-
$page->addPageLayoutHandles(['displaymode' => $categoryDisplayMode], null, false);
242-
$page->addPageLayoutHandles(['id' => $category->getId()]);
313+
/**
314+
* Category prepare page
315+
*
316+
* @param Category $category
317+
* @return Page
318+
*/
319+
private function preparePage(Category $category): Page
320+
{
321+
$this->layerResolver->create(Resolver::CATALOG_LAYER_CATEGORY);
322+
$settings = $this->_catalogDesign->getDesignSettings($category);
243323

244-
// apply custom layout update once layout is loaded
245-
$this->applyLayoutUpdates($page, $settings);
324+
if ($settings->getCustomDesign()) {
325+
$this->_catalogDesign->applyCustomDesign($settings->getCustomDesign());
326+
}
246327

247-
$page->getConfig()->addBodyClass('page-products')
248-
->addBodyClass('categorypath-' . $this->categoryUrlPathGenerator->getUrlPath($category))
249-
->addBodyClass('category-' . $category->getUrlKey());
328+
$this->_catalogSession->setLastViewedCategoryId($category->getId());
250329

251-
if ($this->shouldRedirectOnToolbarAction()) {
252-
$this->getResponse()->setRedirect($this->_redirect->getRedirectUrl());
253-
}
254-
return $page;
255-
} elseif (!$this->getResponse()->isRedirect()) {
256-
$result = $this->resultForwardFactory->create()->forward('noroute');
330+
$page = $this->resultPageFactory->create();
331+
if ($settings->getPageLayout()) {
332+
$page->getConfig()->setPageLayout($settings->getPageLayout());
333+
}
334+
335+
$this->addPageLayoutHandles($page, $category);
336+
$this->applyLayoutUpdates($page, $settings);
337+
338+
$page->getConfig()->addBodyClass('page-products')
339+
->addBodyClass('categorypath-' . $this->categoryUrlPathGenerator->getUrlPath($category))
340+
->addBodyClass('category-' . $category->getUrlKey());
341+
342+
return $page;
343+
}
344+
345+
/**
346+
* Category add page layout handle
347+
*
348+
* @param Page $page
349+
* @param Category $category
350+
* @return void
351+
*/
352+
private function addPageLayoutHandles(Page $page, Category $category): void
353+
{
354+
$pageType = $this->getPageType($category);
355+
356+
if (!$category->hasChildren()) {
357+
$parentPageType = strtok($pageType, '_');
358+
$page->addPageLayoutHandles(['type' => $parentPageType], null, false);
257359
}
258-
return $result;
360+
$page->addPageLayoutHandles(['type' => $pageType], null, false);
361+
362+
$categoryDisplayMode = is_string($category->getDisplayMode()) ?
363+
strtolower($category->getDisplayMode()) : '';
364+
$page->addPageLayoutHandles(['displaymode' => $categoryDisplayMode], null, false);
365+
$page->addPageLayoutHandles(['id' => $category->getId()]);
259366
}
260367

261368
/**
@@ -264,11 +371,11 @@ public function execute()
264371
* @param Category $category
265372
* @return string
266373
*/
267-
private function getPageType(Category $category) : string
374+
private function getPageType(Category $category): string
268375
{
269376
$hasChildren = $category->hasChildren();
270377
if ($category->getIsAnchor()) {
271-
return $hasChildren ? 'layered' : 'layered_without_children';
378+
return $hasChildren ? 'layered' : 'layered_without_children';
272379
}
273380

274381
return $hasChildren ? 'default' : 'default_without_children';
@@ -284,7 +391,7 @@ private function getPageType(Category $category) : string
284391
private function applyLayoutUpdates(
285392
Page $page,
286393
DataObject $settings
287-
) {
394+
): void {
288395
$layoutUpdates = $settings->getLayoutUpdates();
289396
if ($layoutUpdates && is_array($layoutUpdates)) {
290397
foreach ($layoutUpdates as $layoutUpdate) {
@@ -293,7 +400,6 @@ private function applyLayoutUpdates(
293400
}
294401
}
295402

296-
//Selected files
297403
if ($settings->getPageLayoutHandles()) {
298404
$page->addPageLayoutHandles($settings->getPageLayoutHandles());
299405
}
@@ -308,11 +414,11 @@ private function shouldRedirectOnToolbarAction(): bool
308414
{
309415
$params = $this->getRequest()->getParams();
310416

311-
return $this->toolbarMemorizer->isMemorizingAllowed() && empty(array_intersect([
417+
return $this->toolbarMemorizer->isMemorizingAllowed() && !empty(array_intersect([
312418
Toolbar::ORDER_PARAM_NAME,
313419
Toolbar::DIRECTION_PARAM_NAME,
314420
Toolbar::MODE_PARAM_NAME,
315421
Toolbar::LIMIT_PARAM_NAME
316-
], array_keys($params))) === false;
422+
], array_keys($params)));
317423
}
318424
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<?php
2+
/**
3+
* Copyright 2025 Adobe
4+
* All Rights Reserved.
5+
*/
6+
declare(strict_types=1);
7+
8+
/**
9+
* Catalog Product List Sort Order
10+
*
11+
*/
12+
namespace Magento\Catalog\Model\Config\Source;
13+
14+
use Magento\Catalog\Model\ResourceModel\Product\Collection;
15+
16+
class SortOrder implements \Magento\Framework\Data\OptionSourceInterface
17+
{
18+
/**
19+
* Retrieve option values array
20+
*
21+
* @return array
22+
*/
23+
public function toOptionArray(): array
24+
{
25+
return [
26+
['value' => Collection::SORT_ORDER_ASC, 'label' => __('Ascending')],
27+
['value' => Collection::SORT_ORDER_DESC, 'label' => __('Descending')],
28+
];
29+
}
30+
}

app/code/Magento/Catalog/Test/Mftf/Data/CatalogStorefrontConfigData.xml

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,10 @@
105105
<data key="path">catalog/frontend/list_allow_all</data>
106106
<data key="value">1</data>
107107
</entity>
108+
<entity name="CustomStoreFrontProductsSortByOrder">
109+
<data key="path">catalog/frontend/default_sort_by_order</data>
110+
<data key="value">DESC</data>
111+
</entity>
108112
<entity name="DefaultListMode">
109113
<data key="path">catalog/frontend/list_mode</data>
110114
<data key="value">grid-list</data>
@@ -125,4 +129,8 @@
125129
<data key="path">catalog/frontend/list_allow_all</data>
126130
<data key="value">0</data>
127131
</entity>
128-
</entities>
132+
<entity name="DefaultStoreFrontProductsSortByOrder">
133+
<data key="path">catalog/frontend/default_sort_by_order</data>
134+
<data key="value">ASC</data>
135+
</entity>
136+
</entities>

0 commit comments

Comments
 (0)