From 274abbdd53bf3af61a555fb5b6531b1c72ae373f Mon Sep 17 00:00:00 2001
From: binoli-lalani <binoliblalani@gmail.com>
Date: Mon, 13 May 2024 13:06:16 +0530
Subject: [PATCH 1/6] Issue #2720109 by Spokje, kndr, sharma.amitt16, maacl,
 felribeiro, Neslee Canil Pinto, yogeshmpawar, quietone, joey-santiago, zeuty,
 Brian-C, ranjith_kumar_k_u, seppelM, jenny.cha, kiwimind, NikLP:
 maintenance-page--offline.html.twig is not picked up when system is offline

---
 core/includes/errors.inc                      |  12 +-
 .../FinalExceptionSubscriber.php              |  19 +-
 core/lib/Drupal/Core/Utility/Error.php        |  70 +++++-
 .../tests/src/Functional/BigPipeTest.php      |   4 +-
 .../maintenance-page--offline.html.twig       |  41 ++++
 .../System/MaintenancePageOfflineTest.php     | 210 ++++++++++++++++++
 .../maintenance-page--offline.html.twig       |  41 ++++
 sites/default/default.settings.php            |   5 +
 8 files changed, 394 insertions(+), 8 deletions(-)
 create mode 100644 core/modules/system/templates/maintenance-page--offline.html.twig
 create mode 100644 core/modules/system/tests/src/Functional/System/MaintenancePageOfflineTest.php
 create mode 100644 core/modules/system/tests/themes/test_theme/templates/maintenance-page--offline.html.twig

diff --git a/core/includes/errors.inc b/core/includes/errors.inc
index d82ed9bdd165..38138b738ad6 100644
--- a/core/includes/errors.inc
+++ b/core/includes/errors.inc
@@ -262,7 +262,7 @@ function _drupal_log_error($error, $fatal = FALSE): void {
       // We fallback to a maintenance page at this point, because the page
       // generation itself can generate errors.
       // Should not translate the string to avoid errors producing more errors.
-      $message = 'The website encountered an unexpected error. Try again later.' . '<br />' . $message;
+      $message = 'The website encountered an unexpected error. Please try again later.' . '<br /><br />' . $message;
 
       if ($is_installer) {
         // install_display_output() prints the output and ends script execution.
@@ -279,6 +279,16 @@ function _drupal_log_error($error, $fatal = FALSE): void {
         }
       }
 
+      $html = Error::renderFatalError([
+        'title' => 'Service unavailable',
+        'content' => $message,
+        'displayable' => error_displayable($error),
+      ]);
+      if (!empty($html)) {
+        $message = $html;
+        $response->headers->set('Content-Type', 'text/html');
+      }
+
       $response->setContent($message);
       $response->setStatusCode(500, '500 Service unavailable (with message)');
 
diff --git a/core/lib/Drupal/Core/EventSubscriber/FinalExceptionSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/FinalExceptionSubscriber.php
index 3473a1b5775d..a75bdac1393b 100644
--- a/core/lib/Drupal/Core/EventSubscriber/FinalExceptionSubscriber.php
+++ b/core/lib/Drupal/Core/EventSubscriber/FinalExceptionSubscriber.php
@@ -30,9 +30,10 @@
  * handled by a single exception subscriber:: serialization.exception.default.
  *
  * This exception subscriber runs after all the above (it has a lower priority),
- * which makes it the last-chance exception handler. It always sends a plain
- * text response. If it's a displayable error and the error level is configured
- * to be verbose, then a helpful backtrace is also printed.
+ * which makes it the last-chance exception handler. It sends a html response
+ * if a request format is html and a plain text otherwise. If it's a displayable
+ * error and the error level is configured to be verbose, then a helpful
+ * backtrace is also printed.
  */
 class FinalExceptionSubscriber implements EventSubscriberInterface {
   use StringTranslationTrait;
@@ -129,6 +130,18 @@ public function onException(ExceptionEvent $event) {
     $content_type = $event->getRequest()->getRequestFormat() == 'html' ? 'text/html' : 'text/plain';
     $content = $this->t('The website encountered an unexpected error. Try again later.');
     $content .= $message ? '<br><br>' . $message : '';
+
+    if ($content_type == 'text/html') {
+      $html = Error::renderFatalError([
+        'title' => $this->t('Service unavailable'),
+        'content' => $content,
+        'displayable' => $this->isErrorDisplayable($error),
+      ]);
+      if (!empty($html)) {
+        $content = $html;
+      }
+    }
+
     $response = new Response($content, 500, ['Content-Type' => $content_type]);
 
     if ($exception instanceof HttpExceptionInterface) {
diff --git a/core/lib/Drupal/Core/Utility/Error.php b/core/lib/Drupal/Core/Utility/Error.php
index 459af44d8c51..6e7d29ef0f76 100644
--- a/core/lib/Drupal/Core/Utility/Error.php
+++ b/core/lib/Drupal/Core/Utility/Error.php
@@ -2,13 +2,18 @@
 
 namespace Drupal\Core\Utility;
 
-use Drupal\Component\Render\FormattableMarkup;
+use Drupal\Core\DependencyInjection\ContainerNotInitializedException;
+use Drupal\Core\Extension\ExtensionDiscovery;
+use Drupal\Core\Site\Settings;
 use Drupal\Component\Utility\Xss;
+use Drupal\Component\Render\FormattableMarkup;
 use Drupal\Core\Database\Connection;
 use Drupal\Core\Database\Database;
 use Drupal\Core\Database\DatabaseExceptionWrapper;
 use Psr\Log\LoggerInterface;
 use Psr\Log\LogLevel;
+use Twig\Environment;
+use Twig\Loader\ArrayLoader;
 
 /**
  * Drupal error utility class.
@@ -120,6 +125,69 @@ public static function renderExceptionSafe($exception) {
     return new FormattableMarkup(Error::DEFAULT_ERROR_MESSAGE . ' <pre class="backtrace">@backtrace</pre>', $decode);
   }
 
+  /**
+   * Renders fatal error page from twig template using Symfony twig engine.
+   *
+   * @param array $context
+   *   Variables to be passed to twig template.
+   *
+   * @return string
+   *   An html code with rendered fatal error page.
+   */
+  public static function renderFatalError(array $context) {
+    $template_path = '/templates/maintenance-page--offline.html.twig';
+    $system_path = 'core/modules/system';
+    $theme = '';
+
+    // Get offline theme from settings.php and check if the template exists.
+    try {
+      $theme = Settings::get('maintenance_theme', '');
+      if (!$theme) {
+        $theme_path = $system_path;
+      }
+      else {
+        $theme_path = \Drupal::service('extension.list.theme')->getPath($theme);
+      }
+    }
+    catch (ContainerNotInitializedException $e) {
+      // The maintenance theme is set but the container doesn't exist
+      // since the database is inactive. Hence there are no services available
+      // to retrieve a maintenance theme path. The path can be obtained by using
+      // ExtensionDiscovery::scan() but the app root should be guessed first
+      // in the same way as DrupalKernel::guessApplicationRoot() does.
+      $app_root = dirname(substr(__DIR__, 0, -strlen(__NAMESPACE__)), 2);
+      $listing = new ExtensionDiscovery($app_root, FALSE, NULL, NULL);
+      // An empty profile directory prevents ExtensionDiscovery::scan()
+      // from calling \Drupal::installProfile() that needs working container.
+      $listing->setProfileDirectories([]);
+      $themes = $listing->scan('theme');
+      $theme_path = isset($themes[$theme]) ? $themes[$theme]->getPath() : $system_path;
+      if ($context['displayable']) {
+        $context['content'] = $context['content'] . "<pre>" . $e . "</pre>";
+      }
+    }
+    catch (\Throwable $error) {
+      // Handle any other cases.
+      $theme_path = $system_path;
+      if ($context['displayable']) {
+        $context['content'] = $context['content'] . "<pre>" . $error . "</pre>";
+      }
+    }
+
+    $path = $theme_path . $template_path;
+    if (!file_exists($path)) {
+      $path = $system_path . $template_path;
+    }
+
+    // Directly use Symfony twig engine without Drupal wrapper to minimize
+    // possibility of nested exception.
+    $template = file_get_contents($path);
+    $loader = new ArrayLoader(['maintenance_page_offline' => $template]);
+    $environment = new Environment($loader);
+
+    return $environment->render('maintenance_page_offline', $context);
+  }
+
   /**
    * Gets the last caller from a backtrace.
    *
diff --git a/core/modules/big_pipe/tests/src/Functional/BigPipeTest.php b/core/modules/big_pipe/tests/src/Functional/BigPipeTest.php
index 26e92fc14982..38f6083fbb99 100644
--- a/core/modules/big_pipe/tests/src/Functional/BigPipeTest.php
+++ b/core/modules/big_pipe/tests/src/Functional/BigPipeTest.php
@@ -212,9 +212,8 @@ public function testBigPipe(): void {
     // The 'edge_case__html_exception' case throws an exception.
     $this->assertSession()->pageTextContains('The website encountered an unexpected error. Try again later');
     $this->assertSession()->pageTextContains('You are not allowed to say llamas are not cool!');
-    // Check that stop signal and closing body tag are absent.
+     // Check that stop signal is absent.
     $this->assertSession()->responseNotContains(BigPipe::STOP_SIGNAL);
-    $this->assertSession()->responseNotContains('</body>');
     // The exception is expected. Do not interpret it as a test failure.
     unlink($this->root . '/' . $this->siteDirectory . '/error.log');
 
@@ -294,7 +293,6 @@ public function testBigPipeNoJs(): void {
     // The 'edge_case__html_exception' case throws an exception.
     $this->assertSession()->pageTextContains('The website encountered an unexpected error. Try again later');
     $this->assertSession()->pageTextContains('You are not allowed to say llamas are not cool!');
-    $this->assertSession()->responseNotContains('</body>');
     // The exception is expected. Do not interpret it as a test failure.
     unlink($this->root . '/' . $this->siteDirectory . '/error.log');
   }
diff --git a/core/modules/system/templates/maintenance-page--offline.html.twig b/core/modules/system/templates/maintenance-page--offline.html.twig
new file mode 100644
index 000000000000..f7ba55ddecd4
--- /dev/null
+++ b/core/modules/system/templates/maintenance-page--offline.html.twig
@@ -0,0 +1,41 @@
+{#
+/**
+ * @file
+ * Default theme implementation to display a single Drupal page while offline.
+ *
+ * @see template_preprocess_maintenance_page()
+ *
+ * @ingroup themeable
+ */
+#}
+<!DOCTYPE html>
+<html lang="en">
+  <head>
+    <title>
+      {% if title %}
+        {{ title }}
+      {% else %}
+        Service Unavailable
+      {% endif %}
+    </title>
+  </head>
+  <body class="maintenance-page layout-no-sidebars path-frontpage">
+    <div id="page-wrapper">
+      <div id="page">
+        <div id="main-wrapper">
+          <div id="main" class="clearfix">
+            <main id="content" class="column" role="main">
+              <section class="section">
+                <a id="main-content"></a>
+                {% if title %}
+                  <h1 class="title" id="page-title">{{ title }}</h1>
+                {% endif %}
+                {{ content|striptags('<p>,<br>,<em>,<pre>')|raw }}
+              </section>
+            </main>
+          </div>
+        </div>
+      </div>
+    </div>
+  </body>
+</html>
\ No newline at end of file
diff --git a/core/modules/system/tests/src/Functional/System/MaintenancePageOfflineTest.php b/core/modules/system/tests/src/Functional/System/MaintenancePageOfflineTest.php
new file mode 100644
index 000000000000..2db794b10e55
--- /dev/null
+++ b/core/modules/system/tests/src/Functional/System/MaintenancePageOfflineTest.php
@@ -0,0 +1,210 @@
+<?php
+
+namespace Drupal\Tests\system\Functional\System;
+
+use Drupal\Core\Database\Database;
+use Drupal\Tests\BrowserTestBase;
+
+/**
+ * Tests if the Maintenance page is served when the site is offline.
+ *
+ * @group system
+ */
+class MaintenancePageOfflineTest extends BrowserTestBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected $defaultTheme = 'test_theme';
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp(): void {
+    parent::setUp();
+    $settings_filename = $this->siteDirectory . '/settings.php';
+    chmod($settings_filename, 0777);
+    $settings_php = file_get_contents($settings_filename);
+    // Ensure we can test errors rather than being caught in
+    // \Drupal\Core\Test\HttpClientMiddleware\TestHttpClientMiddleware.
+    $settings_php .= "\ndefine('SIMPLETEST_COLLECT_ERRORS', FALSE);\n";
+    file_put_contents($settings_filename, $settings_php);
+  }
+
+  /**
+   * Prepare settings values before a test case.
+   *
+   * @param string $maintenance_theme
+   *   The name of a maintenance theme. Empty if there is no maintenance theme.
+   * @param string $error_level
+   *   The name of an error level.
+   * @param bool $active_database
+   *   TRUE if a database should be active.
+   * @param bool $valid_hash_salt
+   *   TRUE if a hash_salt should be valid.
+   */
+  protected function prepareCaseSettings($maintenance_theme, $error_level, $active_database = TRUE, $valid_hash_salt = TRUE) {
+    $settings = [];
+    if (!empty($maintenance_theme)) {
+      $settings['settings']['maintenance_theme'] = (object) [
+        'value' => $maintenance_theme,
+        'required' => TRUE,
+      ];
+    }
+    $settings['config']['system.logging']['error_level'] = (object) [
+      'value' => $error_level,
+      'required' => TRUE,
+    ];
+    if (!$active_database) {
+      // Make a database inactive by setting an invalid password.
+      $connection_info = Database::getConnectionInfo();
+      $settings['databases']['default']['default']['password'] = (object) [
+        'value' => $connection_info['default']['password'] . $this->randomMachineName(),
+        'required' => TRUE,
+      ];
+    }
+    if (!$valid_hash_salt) {
+      // Set a hash_salt to invalid value.
+      $settings['settings']['hash_salt'] = (object) [
+        'value' => NULL,
+        'required' => TRUE,
+      ];
+    }
+    $this->writeSettings($settings);
+  }
+
+  /**
+   * Tests cases when settings.php contains invalid database settings.
+   *
+   * Tests if the maintenance offline page is served when settings.php
+   * contains invalid database settings.
+   */
+  public function testInvalidDatabaseSettings() {
+    // Open a frontpage without any error.
+    $this->drupalGet('');
+    $this->assertSession()->statusCodeEquals(200);
+    $this->assertSession()->pageTextContains('Log in');
+
+    // CASE 1 - a maintenance theme's template is picked up
+    // and no errors are displayed.
+    $this->prepareCaseSettings('test_theme', ERROR_REPORTING_HIDE, FALSE);
+    $this->drupalGet('');
+    $this->assertSession()->statusCodeEquals(500);
+    // A maintenance theme's offline template should be picked up.
+    $this->assertSession()->pageTextContains('Service unavailable');
+    $this->assertSession()->responseContains('<h1 class="title test-theme"');
+    // A fatal error message and a backtrace should be hidden.
+    $this->assertSession()->pageTextContains('The website encountered an unexpected error. Please try again later.');
+    $this->assertSession()->pageTextNotContains('Access denied for user');
+    $this->assertSession()->responseNotContains('<pre class="backtrace">');
+
+    // CASE 2 - a maintenance theme's template is picked up
+    // and all errors with a backtrace are displayed.
+    $this->prepareCaseSettings('test_theme', ERROR_REPORTING_DISPLAY_VERBOSE, FALSE);
+    $this->drupalGet('');
+    $this->assertSession()->statusCodeEquals(500);
+    // A maintenance theme's offline template should be picked up.
+    $this->assertSession()->pageTextContains('Service unavailable');
+    $this->assertSession()->responseContains('<h1 class="title test-theme"');
+    // A fatal error message and a backtrace should be shown.
+    $this->assertSession()->pageTextContains('The website encountered an unexpected error. Please try again later.');
+    $this->assertSession()->pageTextContains('Access denied for user');
+    $this->assertSession()->responseContains('<pre class="backtrace">');
+
+    // CASE 3 - a system's template is picked up
+    // since a maintenance theme doesn't have the template
+    // and no errors are displayed.
+    $this->prepareCaseSettings('test_subtheme', ERROR_REPORTING_HIDE, FALSE);
+    $this->drupalGet('');
+    $this->assertSession()->statusCodeEquals(500);
+    // A system's offline template should be picked up.
+    $this->assertSession()->pageTextContains('Service unavailable');
+    $this->assertSession()->responseContains('<h1 class="title"');
+    // A fatal error message and a backtrace should be hidden.
+    $this->assertSession()->pageTextContains('The website encountered an unexpected error. Please try again later.');
+    $this->assertSession()->pageTextNotContains('Access denied for user');
+    $this->assertSession()->responseNotContains('<pre class="backtrace">');
+
+    // CASE 4 - a system's template is picked up
+    // since a maintenance theme is not set
+    // and all errors with a backtrace are displayed.
+    $this->prepareCaseSettings('', ERROR_REPORTING_DISPLAY_VERBOSE, FALSE);
+    $this->drupalGet('');
+    $this->assertSession()->statusCodeEquals(500);
+    // A system's offline template should be picked up.
+    $this->assertSession()->pageTextContains('Service unavailable');
+    $this->assertSession()->responseContains('<h1 class="title"');
+    // A fatal error message and a backtrace should be shown.
+    $this->assertSession()->pageTextContains('The website encountered an unexpected error. Please try again later.');
+    $this->assertSession()->pageTextContains('Access denied for user');
+    $this->assertSession()->responseContains('<pre class="backtrace">');
+  }
+
+  /**
+   * Tests cases when settings.php doesn't have a hash_salt defined.
+   *
+   * Tests if the maintenance offline page is served when settings.php
+   * originally did have a hash_salt defined, which is emptied out afterwards.
+   */
+  public function testRemovedHashSalt() {
+    // Open a frontpage without any error.
+    $this->drupalGet('');
+    $this->assertSession()->statusCodeEquals(200);
+    $this->assertSession()->pageTextContains('Log in');
+
+    // CASE 1 - a maintenance theme's template is picked up
+    // and no errors are displayed.
+    $this->prepareCaseSettings('test_theme', ERROR_REPORTING_HIDE, TRUE, FALSE);
+    $this->drupalGet('');
+    $this->assertSession()->statusCodeEquals(500);
+    // A maintenance theme's offline template should be picked up.
+    $this->assertSession()->pageTextContains('Service unavailable');
+    $this->assertSession()->responseContains('<h1 class="title test-theme"');
+    // A fatal error message and a backtrace should be hidden.
+    $this->assertSession()->pageTextContains('The website encountered an unexpected error. Please try again later.');
+    $this->assertSession()->pageTextNotContains('Missing $settings[\'hash_salt\'] in settings.php');
+    $this->assertSession()->responseNotContains('<pre class="backtrace">');
+
+    // CASE 2 - a maintenance theme's template is picked up
+    // and all errors with a backtrace are displayed.
+    $this->prepareCaseSettings('test_theme', ERROR_REPORTING_DISPLAY_VERBOSE, TRUE, FALSE);
+    $this->drupalGet('');
+    $this->assertSession()->statusCodeEquals(500);
+    // A maintenance theme's offline template should be picked up.
+    $this->assertSession()->pageTextContains('Service unavailable');
+    $this->assertSession()->responseContains('<h1 class="title test-theme"');
+    // A fatal error message and a backtrace should be shown.
+    $this->assertSession()->pageTextContains('The website encountered an unexpected error. Please try again later.');
+    $this->assertSession()->pageTextContains('Missing $settings[\'hash_salt\'] in settings.php');
+    $this->assertSession()->responseContains('<pre class="backtrace">');
+
+    // CASE 3 - a system's template is picked up
+    // since a maintenance theme doesn't have the template
+    // and no errors are displayed.
+    $this->prepareCaseSettings('test_subtheme', ERROR_REPORTING_HIDE, TRUE, FALSE);
+    $this->drupalGet('');
+    $this->assertSession()->statusCodeEquals(500);
+    // A system's offline template should be picked up.
+    $this->assertSession()->pageTextContains('Service unavailable');
+    $this->assertSession()->responseContains('<h1 class="title"');
+    // A fatal error message and a backtrace should be hidden.
+    $this->assertSession()->pageTextContains('The website encountered an unexpected error. Please try again later.');
+    $this->assertSession()->pageTextNotContains('Missing $settings[\'hash_salt\'] in settings.php');
+    $this->assertSession()->responseNotContains('<pre class="backtrace">');
+
+    // CASE 4 - a system's template is picked up
+    // since a maintenance theme is not set
+    // and all errors with a backtrace are displayed.
+    $this->prepareCaseSettings('', ERROR_REPORTING_DISPLAY_VERBOSE, TRUE, FALSE);
+    $this->drupalGet('');
+    $this->assertSession()->statusCodeEquals(500);
+    // A system's offline template should be picked up.
+    $this->assertSession()->pageTextContains('Service unavailable');
+    $this->assertSession()->responseContains('<h1 class="title"');
+    // A fatal error message and a backtrace should be shown.
+    $this->assertSession()->pageTextContains('The website encountered an unexpected error. Please try again later.');
+    $this->assertSession()->pageTextContains('Missing $settings[\'hash_salt\'] in settings.php');
+    $this->assertSession()->responseContains('<pre class="backtrace">');
+  }
+
+}
\ No newline at end of file
diff --git a/core/modules/system/tests/themes/test_theme/templates/maintenance-page--offline.html.twig b/core/modules/system/tests/themes/test_theme/templates/maintenance-page--offline.html.twig
new file mode 100644
index 000000000000..f73983c4eb2d
--- /dev/null
+++ b/core/modules/system/tests/themes/test_theme/templates/maintenance-page--offline.html.twig
@@ -0,0 +1,41 @@
+{#
+/**
+ * @file
+ * Default theme implementation to display a single Drupal page while offline.
+ *
+ * @see template_preprocess_maintenance_page()
+ *
+ * @ingroup themeable
+ */
+#}
+<!DOCTYPE html>
+<html lang="en">
+  <head>
+    <title>
+      {% if title %}
+        {{ title }}
+      {% else %}
+        Service Unavailable
+      {% endif %}
+    </title>
+  </head>
+  <body class="maintenance-page layout-no-sidebars path-frontpage">
+    <div id="page-wrapper">
+      <div id="page">
+        <div id="main-wrapper">
+          <div id="main" class="clearfix">
+            <main id="content" class="column" role="main">
+              <section class="section">
+                <a id="main-content"></a>
+                {% if title %}
+                  <h1 class="title test-theme" id="page-title">{{ title }}</h1>
+                {% endif %}
+                {{ content|striptags('<p>,<br>,<em>,<pre>')|raw }}
+              </section>
+            </main>
+          </div>
+        </div>
+      </div>
+    </div>
+  </body>
+</html>
\ No newline at end of file
diff --git a/sites/default/default.settings.php b/sites/default/default.settings.php
index e121075454fd..9f336a2b651c 100644
--- a/sites/default/default.settings.php
+++ b/sites/default/default.settings.php
@@ -658,6 +658,11 @@
  * The template file should also be copied into the theme. It is located inside
  * 'core/modules/system/templates/maintenance-page.html.twig'.
  *
+ * This applies also when the database is inactive due to an error
+ * or when a fatal error is thrown.
+ * The template file should also be copied into the theme. It is located at
+ * 'core/modules/system/templates/maintenance-page--offline.html.twig'.
+ *
  * Note: This setting does not apply to installation and update pages.
  */
 # $settings['maintenance_theme'] = 'claro';
-- 
GitLab


From 42d1b10cb4cbc557d9c8257b7a843885c3f2d5d9 Mon Sep 17 00:00:00 2001
From: binoli-lalani <binoliblalani@gmail.com>
Date: Mon, 13 May 2024 15:57:33 +0530
Subject: [PATCH 2/6] Issue #2720109 by Spokje, kndr, sharma.amitt16, maacl,
 felribeiro, Neslee Canil Pinto, yogeshmpawar, quietone, zeuty, joey-santiago,
 Brian-C, ranjith_kumar_k_u, seppelM, jenny.cha, kiwimind, NikLP:
 maintenance-page--offline.html.twig is not picked up when system is offline

---
 core/modules/big_pipe/tests/src/Functional/BigPipeTest.php      | 2 +-
 .../tests/src/Functional/System/MaintenancePageOfflineTest.php  | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/core/modules/big_pipe/tests/src/Functional/BigPipeTest.php b/core/modules/big_pipe/tests/src/Functional/BigPipeTest.php
index 38f6083fbb99..e7aa0d4fa730 100644
--- a/core/modules/big_pipe/tests/src/Functional/BigPipeTest.php
+++ b/core/modules/big_pipe/tests/src/Functional/BigPipeTest.php
@@ -212,7 +212,7 @@ public function testBigPipe(): void {
     // The 'edge_case__html_exception' case throws an exception.
     $this->assertSession()->pageTextContains('The website encountered an unexpected error. Try again later');
     $this->assertSession()->pageTextContains('You are not allowed to say llamas are not cool!');
-     // Check that stop signal is absent.
+    // Check that stop signal is absent.
     $this->assertSession()->responseNotContains(BigPipe::STOP_SIGNAL);
     // The exception is expected. Do not interpret it as a test failure.
     unlink($this->root . '/' . $this->siteDirectory . '/error.log');
diff --git a/core/modules/system/tests/src/Functional/System/MaintenancePageOfflineTest.php b/core/modules/system/tests/src/Functional/System/MaintenancePageOfflineTest.php
index 2db794b10e55..2b0de0be9c66 100644
--- a/core/modules/system/tests/src/Functional/System/MaintenancePageOfflineTest.php
+++ b/core/modules/system/tests/src/Functional/System/MaintenancePageOfflineTest.php
@@ -207,4 +207,4 @@ public function testRemovedHashSalt() {
     $this->assertSession()->responseContains('<pre class="backtrace">');
   }
 
-}
\ No newline at end of file
+}
-- 
GitLab


From 16fd4e4a125cd593ea35864eaa55a2677960c89f Mon Sep 17 00:00:00 2001
From: Brandon Lira <brandonlira98@gmail.com>
Date: Thu, 27 Mar 2025 00:37:27 -0300
Subject: [PATCH 3/6] Issue #2720109: Add declare(strict_types=1); to fix PHPCS
 error

---
 .../tests/src/Functional/System/MaintenancePageOfflineTest.php  | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/core/modules/system/tests/src/Functional/System/MaintenancePageOfflineTest.php b/core/modules/system/tests/src/Functional/System/MaintenancePageOfflineTest.php
index 2b0de0be9c66..ce1c6cbed578 100644
--- a/core/modules/system/tests/src/Functional/System/MaintenancePageOfflineTest.php
+++ b/core/modules/system/tests/src/Functional/System/MaintenancePageOfflineTest.php
@@ -1,5 +1,7 @@
 <?php
 
+declare(strict_types=1);
+
 namespace Drupal\Tests\system\Functional\System;
 
 use Drupal\Core\Database\Database;
-- 
GitLab


From f29e322d7a23afc198f650039f08c91e9120a821 Mon Sep 17 00:00:00 2001
From: Brandon Lira <brandonlira98@gmail.com>
Date: Thu, 27 Mar 2025 01:03:39 -0300
Subject: [PATCH 4/6] Issue #2720109: Add missing return type declarations to
 test methods

---
 .../src/Functional/System/MaintenancePageOfflineTest.php    | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/core/modules/system/tests/src/Functional/System/MaintenancePageOfflineTest.php b/core/modules/system/tests/src/Functional/System/MaintenancePageOfflineTest.php
index ce1c6cbed578..f205230a3949 100644
--- a/core/modules/system/tests/src/Functional/System/MaintenancePageOfflineTest.php
+++ b/core/modules/system/tests/src/Functional/System/MaintenancePageOfflineTest.php
@@ -45,7 +45,7 @@ protected function setUp(): void {
    * @param bool $valid_hash_salt
    *   TRUE if a hash_salt should be valid.
    */
-  protected function prepareCaseSettings($maintenance_theme, $error_level, $active_database = TRUE, $valid_hash_salt = TRUE) {
+  protected function prepareCaseSettings($maintenance_theme, $error_level, $active_database = TRUE, $valid_hash_salt = TRUE): void {
     $settings = [];
     if (!empty($maintenance_theme)) {
       $settings['settings']['maintenance_theme'] = (object) [
@@ -81,7 +81,7 @@ protected function prepareCaseSettings($maintenance_theme, $error_level, $active
    * Tests if the maintenance offline page is served when settings.php
    * contains invalid database settings.
    */
-  public function testInvalidDatabaseSettings() {
+  public function testInvalidDatabaseSettings(): void {
     // Open a frontpage without any error.
     $this->drupalGet('');
     $this->assertSession()->statusCodeEquals(200);
@@ -148,7 +148,7 @@ public function testInvalidDatabaseSettings() {
    * Tests if the maintenance offline page is served when settings.php
    * originally did have a hash_salt defined, which is emptied out afterwards.
    */
-  public function testRemovedHashSalt() {
+  public function testRemovedHashSalt(): void {
     // Open a frontpage without any error.
     $this->drupalGet('');
     $this->assertSession()->statusCodeEquals(200);
-- 
GitLab


From c1844ef79b42281048a1603795da709c9b37e8d2 Mon Sep 17 00:00:00 2001
From: Brandon Lira <brandonlira98@gmail.com>
Date: Thu, 27 Mar 2025 01:13:10 -0300
Subject: [PATCH 5/6] Issue #2720109: fix spellcheck violation by removing
 'Please' from error messages

---
 .../System/MaintenancePageOfflineTest.php        | 16 ++++++++--------
 1 file changed, 8 insertions(+), 8 deletions(-)

diff --git a/core/modules/system/tests/src/Functional/System/MaintenancePageOfflineTest.php b/core/modules/system/tests/src/Functional/System/MaintenancePageOfflineTest.php
index f205230a3949..06e2f5da9a22 100644
--- a/core/modules/system/tests/src/Functional/System/MaintenancePageOfflineTest.php
+++ b/core/modules/system/tests/src/Functional/System/MaintenancePageOfflineTest.php
@@ -96,7 +96,7 @@ public function testInvalidDatabaseSettings(): void {
     $this->assertSession()->pageTextContains('Service unavailable');
     $this->assertSession()->responseContains('<h1 class="title test-theme"');
     // A fatal error message and a backtrace should be hidden.
-    $this->assertSession()->pageTextContains('The website encountered an unexpected error. Please try again later.');
+    $this->assertSession()->pageTextContains('The website encountered an unexpected error. Try again later.');
     $this->assertSession()->pageTextNotContains('Access denied for user');
     $this->assertSession()->responseNotContains('<pre class="backtrace">');
 
@@ -109,7 +109,7 @@ public function testInvalidDatabaseSettings(): void {
     $this->assertSession()->pageTextContains('Service unavailable');
     $this->assertSession()->responseContains('<h1 class="title test-theme"');
     // A fatal error message and a backtrace should be shown.
-    $this->assertSession()->pageTextContains('The website encountered an unexpected error. Please try again later.');
+    $this->assertSession()->pageTextContains('The website encountered an unexpected error. Try again later.');
     $this->assertSession()->pageTextContains('Access denied for user');
     $this->assertSession()->responseContains('<pre class="backtrace">');
 
@@ -123,7 +123,7 @@ public function testInvalidDatabaseSettings(): void {
     $this->assertSession()->pageTextContains('Service unavailable');
     $this->assertSession()->responseContains('<h1 class="title"');
     // A fatal error message and a backtrace should be hidden.
-    $this->assertSession()->pageTextContains('The website encountered an unexpected error. Please try again later.');
+    $this->assertSession()->pageTextContains('The website encountered an unexpected error. Try again later.');
     $this->assertSession()->pageTextNotContains('Access denied for user');
     $this->assertSession()->responseNotContains('<pre class="backtrace">');
 
@@ -137,7 +137,7 @@ public function testInvalidDatabaseSettings(): void {
     $this->assertSession()->pageTextContains('Service unavailable');
     $this->assertSession()->responseContains('<h1 class="title"');
     // A fatal error message and a backtrace should be shown.
-    $this->assertSession()->pageTextContains('The website encountered an unexpected error. Please try again later.');
+    $this->assertSession()->pageTextContains('The website encountered an unexpected error. Try again later.');
     $this->assertSession()->pageTextContains('Access denied for user');
     $this->assertSession()->responseContains('<pre class="backtrace">');
   }
@@ -163,7 +163,7 @@ public function testRemovedHashSalt(): void {
     $this->assertSession()->pageTextContains('Service unavailable');
     $this->assertSession()->responseContains('<h1 class="title test-theme"');
     // A fatal error message and a backtrace should be hidden.
-    $this->assertSession()->pageTextContains('The website encountered an unexpected error. Please try again later.');
+    $this->assertSession()->pageTextContains('The website encountered an unexpected error. Try again later.');
     $this->assertSession()->pageTextNotContains('Missing $settings[\'hash_salt\'] in settings.php');
     $this->assertSession()->responseNotContains('<pre class="backtrace">');
 
@@ -176,7 +176,7 @@ public function testRemovedHashSalt(): void {
     $this->assertSession()->pageTextContains('Service unavailable');
     $this->assertSession()->responseContains('<h1 class="title test-theme"');
     // A fatal error message and a backtrace should be shown.
-    $this->assertSession()->pageTextContains('The website encountered an unexpected error. Please try again later.');
+    $this->assertSession()->pageTextContains('The website encountered an unexpected error. Try again later.');
     $this->assertSession()->pageTextContains('Missing $settings[\'hash_salt\'] in settings.php');
     $this->assertSession()->responseContains('<pre class="backtrace">');
 
@@ -190,7 +190,7 @@ public function testRemovedHashSalt(): void {
     $this->assertSession()->pageTextContains('Service unavailable');
     $this->assertSession()->responseContains('<h1 class="title"');
     // A fatal error message and a backtrace should be hidden.
-    $this->assertSession()->pageTextContains('The website encountered an unexpected error. Please try again later.');
+    $this->assertSession()->pageTextContains('The website encountered an unexpected error. Try again later.');
     $this->assertSession()->pageTextNotContains('Missing $settings[\'hash_salt\'] in settings.php');
     $this->assertSession()->responseNotContains('<pre class="backtrace">');
 
@@ -204,7 +204,7 @@ public function testRemovedHashSalt(): void {
     $this->assertSession()->pageTextContains('Service unavailable');
     $this->assertSession()->responseContains('<h1 class="title"');
     // A fatal error message and a backtrace should be shown.
-    $this->assertSession()->pageTextContains('The website encountered an unexpected error. Please try again later.');
+    $this->assertSession()->pageTextContains('The website encountered an unexpected error. Try again later.');
     $this->assertSession()->pageTextContains('Missing $settings[\'hash_salt\'] in settings.php');
     $this->assertSession()->responseContains('<pre class="backtrace">');
   }
-- 
GitLab


From 49e2a38a556bceeffa51c3cb09d902fb8248af83 Mon Sep 17 00:00:00 2001
From: Brandon Lira <brandonlira98@gmail.com>
Date: Thu, 27 Mar 2025 01:20:33 -0300
Subject: [PATCH 6/6] Issue #2720109: fix spellcheck violation by removing
 'Please' from error messages

---
 core/includes/errors.inc           | 2 +-
 sites/default/default.settings.php | 5 -----
 2 files changed, 1 insertion(+), 6 deletions(-)

diff --git a/core/includes/errors.inc b/core/includes/errors.inc
index 38138b738ad6..ea27cbef93a5 100644
--- a/core/includes/errors.inc
+++ b/core/includes/errors.inc
@@ -262,7 +262,7 @@ function _drupal_log_error($error, $fatal = FALSE): void {
       // We fallback to a maintenance page at this point, because the page
       // generation itself can generate errors.
       // Should not translate the string to avoid errors producing more errors.
-      $message = 'The website encountered an unexpected error. Please try again later.' . '<br /><br />' . $message;
+      $message = 'The website encountered an unexpected error. Try again later.' . '<br /><br />' . $message;
 
       if ($is_installer) {
         // install_display_output() prints the output and ends script execution.
diff --git a/sites/default/default.settings.php b/sites/default/default.settings.php
index 9f336a2b651c..e121075454fd 100644
--- a/sites/default/default.settings.php
+++ b/sites/default/default.settings.php
@@ -658,11 +658,6 @@
  * The template file should also be copied into the theme. It is located inside
  * 'core/modules/system/templates/maintenance-page.html.twig'.
  *
- * This applies also when the database is inactive due to an error
- * or when a fatal error is thrown.
- * The template file should also be copied into the theme. It is located at
- * 'core/modules/system/templates/maintenance-page--offline.html.twig'.
- *
  * Note: This setting does not apply to installation and update pages.
  */
 # $settings['maintenance_theme'] = 'claro';
-- 
GitLab