From c7bde6431793c30d5fc75a1ea79c968b0399b516 Mon Sep 17 00:00:00 2001
From: Lauri Eskola <lauri.eskola@acquia.com>
Date: Mon, 24 Jul 2023 23:29:04 +0300
Subject: [PATCH] Issue #3375276 by DieterHolvoet: 4xx HTTP code theme
 suggestions are not applied if a node is set as 4xx path

---
 core/modules/system/system.module             | 13 +++---
 .../KernelTests/Core/Theme/RegistryTest.php   | 41 ++++++++++++++-----
 2 files changed, 35 insertions(+), 19 deletions(-)

diff --git a/core/modules/system/system.module b/core/modules/system/system.module
index 44253888c337..cb6a45294e45 100644
--- a/core/modules/system/system.module
+++ b/core/modules/system/system.module
@@ -37,6 +37,7 @@
 use Drupal\Core\Url;
 use GuzzleHttp\Exception\TransferException;
 use Symfony\Component\HttpFoundation\RedirectResponse;
+use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface;
 
 /**
  * Disabled option on forms and settings.
@@ -306,15 +307,11 @@ function system_theme_suggestions_page(array $variables) {
   $path_args = explode('/', trim(\Drupal::service('path.current')->getPath(), '/'));
   $suggestions = theme_get_suggestions($path_args, 'page');
 
-  $http_error_suggestions = [
-    'system.401' => 'page__401',
-    'system.403' => 'page__403',
-    'system.404' => 'page__404',
-  ];
-  $route_name = \Drupal::routeMatch()->getRouteName();
-  if (isset($http_error_suggestions[$route_name])) {
+  $supported_http_error_codes = [401, 403, 404];
+  $exception = \Drupal::requestStack()->getCurrentRequest()->attributes->get('exception');
+  if ($exception instanceof HttpExceptionInterface && in_array($exception->getStatusCode(), $supported_http_error_codes, TRUE)) {
     $suggestions[] = 'page__4xx';
-    $suggestions[] = $http_error_suggestions[$route_name];
+    $suggestions[] = 'page__' . $exception->getStatusCode();
   }
 
   return $suggestions;
diff --git a/core/tests/Drupal/KernelTests/Core/Theme/RegistryTest.php b/core/tests/Drupal/KernelTests/Core/Theme/RegistryTest.php
index 0649a22c8858..1a1328dd5973 100644
--- a/core/tests/Drupal/KernelTests/Core/Theme/RegistryTest.php
+++ b/core/tests/Drupal/KernelTests/Core/Theme/RegistryTest.php
@@ -5,10 +5,10 @@
 use Drupal\Core\Extension\ModuleExtensionList;
 use Drupal\Core\Path\CurrentPathStack;
 use Drupal\Core\Path\PathMatcherInterface;
-use Drupal\Core\Routing\RouteMatchInterface;
 use Drupal\Core\Theme\Registry;
 use Drupal\Core\Utility\ThemeRegistry;
 use Drupal\KernelTests\KernelTestBase;
+use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface;
 
 /**
  * Tests the behavior of the ThemeRegistry class.
@@ -192,6 +192,26 @@ public function testThemeSuggestions() {
     ], $suggestions, 'Found expected page node suggestions.');
   }
 
+  /**
+   * Tests page theme suggestions for 200 responses.
+   */
+  public function test200ThemeSuggestions() {
+    $path_matcher = $this->prophesize(PathMatcherInterface::class);
+    $path_matcher->isFrontPage()->willReturn(FALSE);
+    \Drupal::getContainer()->set('path.matcher', $path_matcher->reveal());
+
+    $path_current = $this->prophesize(CurrentPathStack::class);
+    $path_current->getPath()->willReturn('/node/123');
+    \Drupal::getContainer()->set('path.current', $path_current->reveal());
+
+    $suggestions = \Drupal::moduleHandler()->invokeAll('theme_suggestions_page', [[]]);
+    $this->assertSame([
+      'page__node',
+      'page__node__%',
+      'page__node__123',
+    ], $suggestions);
+  }
+
   /**
    * Data provider for test40xThemeSuggestions().
    *
@@ -200,9 +220,9 @@ public function testThemeSuggestions() {
    */
   public function provider40xThemeSuggestions() {
     return [
-      ['system.401', 'page__401'],
-      ['system.403', 'page__403'],
-      ['system.404', 'page__404'],
+      [401, 'page__401'],
+      [403, 'page__403'],
+      [404, 'page__404'],
     ];
   }
 
@@ -211,19 +231,18 @@ public function provider40xThemeSuggestions() {
    *
    * @dataProvider provider40xThemeSuggestions
    */
-  public function test40xThemeSuggestions($route, $suggestion) {
-    /** @var \Drupal\Core\Path\PathMatcherInterface $path_matcher */
+  public function test40xThemeSuggestions(int $httpCode, string $suggestion): void {
     $path_matcher = $this->prophesize(PathMatcherInterface::class);
     $path_matcher->isFrontPage()->willReturn(FALSE);
     \Drupal::getContainer()->set('path.matcher', $path_matcher->reveal());
-    /** @var \Drupal\Core\Path\CurrentPathStack $path_current */
+
     $path_current = $this->prophesize(CurrentPathStack::class);
     $path_current->getPath()->willReturn('/node/123');
     \Drupal::getContainer()->set('path.current', $path_current->reveal());
-    /** @var \Drupal\Core\Routing\RouteMatchInterface $route_matcher */
-    $route_matcher = $this->prophesize(RouteMatchInterface::class);
-    $route_matcher->getRouteName()->willReturn($route);
-    \Drupal::getContainer()->set('current_route_match', $route_matcher->reveal());
+
+    $exception = $this->prophesize(HttpExceptionInterface::class);
+    $exception->getStatusCode()->willReturn($httpCode);
+    \Drupal::requestStack()->getCurrentRequest()->attributes->set('exception', $exception->reveal());
 
     $suggestions = \Drupal::moduleHandler()->invokeAll('theme_suggestions_page', [[]]);
     $this->assertSame([
-- 
GitLab