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 [];
}