diff -u b/core/includes/bootstrap.inc b/core/includes/bootstrap.inc --- b/core/includes/bootstrap.inc +++ b/core/includes/bootstrap.inc @@ -514,7 +514,7 @@ * drupal_set_message(t('An error occurred and processing did not complete.'), 'error'); * @endcode * - * @param string|array $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. A render array may also be provided for markup. @@ -526,7 +526,10 @@ * - 'error' * @param bool $repeat * (optional) If this is FALSE and the message is already set, then the - * message won't be repeated. Defaults to FALSE. + * message won't be repeated. Defaults to FALSE. SafeStrings and strings which + * are equivalent will not be repeated as the in_array() comparison is not + * strict. However, if a string is passed once as a render array and once as + * string or SafeString this will repeated regardless of this setting. * * @return array|null * A multidimensional array with keys corresponding to the set message types. @@ -562,9 +565,13 @@ } $new = array( - 'safe' => (!is_array($message) && SafeMarkup::isSafe($message)), + // Only preserve safeness for strings. Objects and render arrays do not + // need to be marked as safe. + 'safe' => (is_string($message) && SafeMarkup::isSafe($message)), 'message' => $message, ); + // Do not use strict type checking so that equivalent string and + // SafeStringInterface objects are detected. if ($repeat || !in_array($new, $_SESSION['messages'][$type])) { $_SESSION['messages'][$type][] = $new; } diff -u b/core/modules/system/src/Tests/Bootstrap/DrupalSetMessageTest.php b/core/modules/system/src/Tests/Bootstrap/DrupalSetMessageTest.php --- b/core/modules/system/src/Tests/Bootstrap/DrupalSetMessageTest.php +++ b/core/modules/system/src/Tests/Bootstrap/DrupalSetMessageTest.php @@ -24,31 +24,40 @@ 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->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'); - } - /** - * Tests setting render array messages. - */ - function testRenderArrayMessages() { - $this->drupalGet('system-test/drupal-set-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.'); + + // Render arrays and strings which are equivalent can not be de-duplicated. + // Test this known shortcoming. + $this->assertNoUniqueText('Duplicate render array / string'); } } diff -u b/core/modules/system/tests/modules/system_test/src/Controller/SystemTestController.php b/core/modules/system/tests/modules/system_test/src/Controller/SystemTestController.php --- b/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; @@ -104,7 +105,4 @@ drupal_set_message(t('Second message with markup! (not removed).')); - // Add a render array message. - drupal_set_message(['#markup' => 'Render array with markup!']); - // Remove the first. unset($_SESSION['messages']['status'][0]); @@ -116,6 +114,37 @@ 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); + + // This mixing of types will cause a duplicate even though it is not + // intended. + drupal_set_message(['#markup' => 'Duplicate render array / string']); + drupal_set_message('Duplicate render array / string'); + return []; }