diff --git a/core/includes/bootstrap.inc b/core/includes/bootstrap.inc index 1d05b4a..e6bd4c3 100644 --- a/core/includes/bootstrap.inc +++ b/core/includes/bootstrap.inc @@ -514,10 +514,10 @@ function watchdog_exception($type, Exception $exception, $message = NULL, $varia * drupal_set_message(t('An error occurred and processing did not complete.'), 'error'); * @endcode * - * @param string $message + * @param string|array|\Drupal\Component\Utility\SafeStringInterface $message * (optional) The translated message to be displayed to the user. For * consistency with other messages, it should begin with a capital letter and - * end with a period. + * end with a period. A render array may also be provided for markup. * @param string $type * (optional) The message's type. Defaults to 'status'. These values are * supported: @@ -561,12 +561,23 @@ function drupal_set_message($message = NULL, $type = 'status', $repeat = FALSE) $_SESSION['messages'][$type] = array(); } - $new = array( - 'safe' => SafeMarkup::isSafe($message), - 'message' => (string) $message, - ); - if ($repeat || !in_array($new, $_SESSION['messages'][$type])) { - $_SESSION['messages'][$type][] = $new; + // Convert strings which are in the safe markup list to SafeString objects. + if (is_string($message) && SafeMarkup::isSafe($message)) { + $message = \Drupal\Core\Render\SafeString::create($message); + } + + // If the message is a render array, render it to a string now to avoid + // storing render arrays in $_SESSION, because rendering across requests + // may have unpredictable results and some render array functionality + // (like assets) should not be supported. + if (is_array($message)) { + $message = \Drupal::service('renderer')->renderPlain($message); + } + + // Do not use strict type checking so that equivalent string and + // SafeStringInterface objects are detected. + if ($repeat || !in_array($message, $_SESSION['messages'][$type])) { + $_SESSION['messages'][$type][] = $message; } // Mark this page as being uncacheable. @@ -604,18 +615,6 @@ function drupal_set_message($message = NULL, $type = 'status', $repeat = FALSE) */ function drupal_get_messages($type = NULL, $clear_queue = TRUE) { if ($messages = drupal_set_message()) { - foreach ($messages as $message_type => $message_typed_messages) { - foreach ($message_typed_messages as $key => $message) { - // Because the messages are stored in the session, the safe status of - // the messages also needs to be stored in the session. We retrieve the - // safe status here and determine whether to mark the string as safe or - // let autoescape do its thing. See drupal_set_message(). - if ($message['safe']) { - $message['message'] = SafeMarkup::set($message['message']); - } - $messages[$message_type][$key] = $message['message']; - } - } if ($type) { if ($clear_queue) { unset($_SESSION['messages'][$type]); diff --git a/core/modules/system/src/Tests/Bootstrap/DrupalSetMessageTest.php b/core/modules/system/src/Tests/Bootstrap/DrupalSetMessageTest.php index 37a1663..3b414af 100644 --- a/core/modules/system/src/Tests/Bootstrap/DrupalSetMessageTest.php +++ b/core/modules/system/src/Tests/Bootstrap/DrupalSetMessageTest.php @@ -24,23 +24,40 @@ class DrupalSetMessageTest extends WebTestBase { public static $modules = array('system_test'); /** - * Tests setting messages and removing one before it is displayed. + * Tests drupal_set_message(). */ - function testSetRemoveMessages() { + function testDrupalSetMessage() { // The page at system-test/drupal-set-message sets two messages and then // removes the first before it is displayed. $this->drupalGet('system-test/drupal-set-message'); $this->assertNoText('First message (removed).'); - $this->assertText('Second message (not removed).'); - } + $this->assertRaw(t('Second message with markup! (not removed).')); - /** - * Tests setting duplicated messages. - */ - function testDuplicatedMessages() { - $this->drupalGet('system-test/drupal-set-message'); + // Ensure duplicate messages are handled as expected. $this->assertUniqueText('Non Duplicated message'); $this->assertNoUniqueText('Duplicated message'); + + // Ensure render arrays are rendered. + $this->assertRaw('Render array with markup!'); + $this->assertUniqueText('Render array with markup!'); + $this->assertRaw('Render array 2 with markup!'); + + // Ensure render arrays with objects are rendered. + $this->assertRaw('Render array with SafeString markup!'); + $this->assertUniqueText('Render array with SafeString markup!'); + + // Ensure SafeString objects are rendered as expected. + $this->assertRaw('SafeString with markup!'); + $this->assertUniqueText('SafeString with markup!'); + $this->assertRaw('SafeString2 with markup!'); + + // Ensure when the same message is of different types it is not duplicated. + $this->assertUniqueText('Non duplicate SafeString / string.'); + $this->assertNoUniqueText('Duplicate SafeString / string.'); + $this->assertUniqueText('Duplicate render array / string'); + + // Ensure that strings that are not marked as safe are escaped. + $this->assertEscaped('Thismarkup will be escaped.'); } } diff --git a/core/modules/system/tests/modules/system_test/src/Controller/SystemTestController.php b/core/modules/system/tests/modules/system_test/src/Controller/SystemTestController.php index 5ac7ca0..63cd091 100644 --- a/core/modules/system/tests/modules/system_test/src/Controller/SystemTestController.php +++ b/core/modules/system/tests/modules/system_test/src/Controller/SystemTestController.php @@ -10,6 +10,7 @@ use Drupal\Core\Access\AccessResult; use Drupal\Core\Controller\ControllerBase; use Drupal\Core\Render\RendererInterface; +use Drupal\Core\Render\SafeString; use Drupal\Core\Session\AccountInterface; use Drupal\Core\Url; use Symfony\Component\HttpFoundation\RedirectResponse; @@ -101,7 +102,7 @@ public function mainContentFallback() { public function drupalSetMessageTest() { // Set two messages. drupal_set_message('First message (removed).'); - drupal_set_message('Second message (not removed).'); + drupal_set_message(t('Second message with markup! (not removed).')); // Remove the first. unset($_SESSION['messages']['status'][0]); @@ -112,6 +113,37 @@ public function drupalSetMessageTest() { drupal_set_message('Duplicated message', 'status', TRUE); drupal_set_message('Duplicated message', 'status', TRUE); + + // Add a render array message. + drupal_set_message(['#markup' => 'Render array with markup!']); + // Test duplicate render arrays. + drupal_set_message(['#markup' => 'Render array with markup!']); + // Ensure that multiple render array messages work. + drupal_set_message(['#markup' => 'Render array 2 with markup!']); + + // Add a render array message containing objects. + drupal_set_message(['#markup' => SafeString::create('Render array with SafeString markup!')]); + // Test duplicate render array messages. + drupal_set_message(['#markup' => SafeString::create('Render array with SafeString markup!')]); + + // Add a SafeString message. + drupal_set_message(SafeString::create('SafeString with markup!')); + // Test duplicate SafeString messages. + drupal_set_message(SafeString::create('SafeString with markup!')); + // Ensure that multiple SafeString messages work. + drupal_set_message(SafeString::create('SafeString2 with markup!')); + + // Test mixing of types. + drupal_set_message(SafeString::create('Non duplicate SafeString / string.')); + drupal_set_message('Non duplicate SafeString / string.'); + drupal_set_message(SafeString::create('Duplicate SafeString / string.'), 'status', TRUE); + drupal_set_message('Duplicate SafeString / string.', 'status', TRUE); + drupal_set_message(['#markup' => 'Duplicate render array / string']); + drupal_set_message('Duplicate render array / string'); + + // Test auto-escape of non safe strings. + drupal_set_message('Thismarkup will be escaped.'); + return []; }