diff --git a/core/includes/bootstrap.inc b/core/includes/bootstrap.inc index 5300d76..18635a6 100644 --- a/core/includes/bootstrap.inc +++ b/core/includes/bootstrap.inc @@ -514,10 +514,11 @@ 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|\Drupal\Component\Utility\SafeStringInterface $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, but + * #attached is not supported. * @param string $type * (optional) The message's type. Defaults to 'status'. These values are * supported: @@ -552,6 +553,9 @@ function watchdog_exception($type, Exception $exception, $message = NULL, $varia * @endcode * If there are no messages set, the function returns NULL. * + * @throws \UnexpectedValueException + * When passed a render array with #attached. + * * @see drupal_get_messages() * @see status-messages.html.twig */ @@ -566,6 +570,20 @@ function drupal_set_message($message = NULL, $type = 'status', $repeat = FALSE) $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)) { + $rendered_message = \Drupal::service('renderer')->renderPlain($message); + // The rendering will have bubbled any #attached assets to the top level + // of $message. Throw an exception if there were any. + if (!empty($message['#attached'])) { + throw new \UnexpectedValueException('Render arrays passed to drupal_set_message() cannot have attached assets.'); + } + $message = $rendered_message; + } + // Do not use strict type checking so that equivalent string and // SafeStringInterface objects are detected. if ($repeat || !in_array($message, $_SESSION['messages'][$type])) { diff --git a/core/modules/system/src/Tests/Bootstrap/DrupalSetMessageTest.php b/core/modules/system/src/Tests/Bootstrap/DrupalSetMessageTest.php index 1b63324..85a66e0 100644 --- a/core/modules/system/src/Tests/Bootstrap/DrupalSetMessageTest.php +++ b/core/modules/system/src/Tests/Bootstrap/DrupalSetMessageTest.php @@ -37,6 +37,15 @@ function testDrupalSetMessage() { $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!'); @@ -45,9 +54,30 @@ function testDrupalSetMessage() { // 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.'); + + // Test render arrays that incorrectly add #attached. + try { + drupal_set_message(['#attached' => ['drupalSettings' => ['pecary' => TRUE]]]); + $this->fail('Render arrays passed to drupal_set_message() cannot have attached assets.'); + } + catch (\UnexpectedValueException $e) { + $this->pass('Render arrays passed to drupal_set_message() cannot have attached assets.'); + } + + // Test render arrays that incorrectly add #attached in a child element. + try { + $array_with_attachments['foo']['bar']['#markup'] = t('Render array with assets'); + $array_with_attachments['foo']['bar']['#attached']['drupalSettings'] = ['pecary' => TRUE]; + drupal_set_message($array_with_attachments); + $this->fail('Render arrays passed to drupal_set_message() cannot have attached assets in child elements.'); + } + catch (\UnexpectedValueException $e) { + $this->pass('Render arrays passed to drupal_set_message() cannot have attached assets in child elements.'); + } } } 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 15ab478..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 @@ -114,6 +114,18 @@ 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. @@ -126,6 +138,8 @@ public function drupalSetMessageTest() { 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.');