diff --git a/core/core.services.yml b/core/core.services.yml index 459503e44c..49b27089b5 100644 --- a/core/core.services.yml +++ b/core/core.services.yml @@ -1646,5 +1646,5 @@ services: tags: - { name: event_subscriber } messenger: - class: Drupal\Core\Messenger\LegacyMessenger - arguments: ['@page_cache_kill_switch'] + class: Drupal\Core\Messenger\Messenger + arguments: ['@session.flash_bag', '@page_cache_kill_switch'] diff --git a/core/includes/bootstrap.inc b/core/includes/bootstrap.inc index 6c8ee5e5f2..22b49234c8 100644 --- a/core/includes/bootstrap.inc +++ b/core/includes/bootstrap.inc @@ -455,9 +455,13 @@ function watchdog_exception($type, Exception $exception, $message = NULL, $varia * * @see drupal_get_messages() * @see status-messages.html.twig + * @see https://www.drupal.org/node/2774931 + * + * @deprecated in Drupal 8.5.0 and will be removed before Drupal 9.0.0. + * Use \Drupal\Core\Messenger\MessengerInterface::addMessage() instead. */ function drupal_set_message($message = NULL, $type = 'status', $repeat = FALSE) { - /* @var \Drupal\Core\Messenger\MessengerInterface $messenger */ + @trigger_error('drupal_set_message() is deprecated Drupal 8.5.0 and will be removed before Drupal 9.0.0. Use \Drupal\Core\Messenger\MessengerInterface::addMessage() instead. See https://www.drupal.org/node/2774931', E_USER_DEPRECATED); $messenger = \Drupal::service('messenger'); $messenger->addMessage($message, $type, $repeat); return $messenger->all(); @@ -487,11 +491,16 @@ function drupal_set_message($message = NULL, $type = 'status', $repeat = FALSE) * * @see drupal_set_message() * @see status-messages.html.twig + * @see https://www.drupal.org/node/2774931 + * + * @deprecated in Drupal 8.5.0 and will be removed before Drupal 9.0.0. + * Use \Drupal\Core\Messenger\MessengerInterface::all() or + * \Drupal\Core\Messenger\MessengerInterface::messagesByType() instead. */ function drupal_get_messages($type = NULL, $clear_queue = TRUE) { - /** @var \Drupal\Core\Messenger\MessengerInterface $messenger */ - $messenger = \Drupal::hasService('messenger') ? \Drupal::service('messenger') : NULL; - if ($messenger && ($messages = $messenger->all())) { + @trigger_error('drupal_get_message() is deprecated Drupal 8.5.0 and will be removed before Drupal 9.0.0. Use \Drupal\Core\Messenger\MessengerInterface::all() or \Drupal\Core\Messenger\MessengerInterface::messagesByType() instead. See https://www.drupal.org/node/2774931', E_USER_DEPRECATED); + $messenger = \Drupal::messenger(); + if ($messages = $messenger->all()) { if ($type) { if ($clear_queue) { $messenger->deleteByType($type); diff --git a/core/lib/Drupal.php b/core/lib/Drupal.php index 07dc12f1f3..825d2730d0 100644 --- a/core/lib/Drupal.php +++ b/core/lib/Drupal.php @@ -6,8 +6,9 @@ */ use Drupal\Core\DependencyInjection\ContainerNotInitializedException; -use Symfony\Component\DependencyInjection\ContainerInterface; +use Drupal\Core\Messenger\ChainedMessenger; use Drupal\Core\Url; +use Symfony\Component\DependencyInjection\ContainerInterface; /** * Static Service Container wrapper. @@ -100,6 +101,13 @@ class Drupal { */ protected static $container; + /** + * The messenger. + * + * @var \Drupal\Core\Messenger\MessengerInterface + */ + protected static $messenger; + /** * Sets a new global container. * @@ -757,4 +765,17 @@ public static function time() { return static::getContainer()->get('datetime.time'); } + /** + * Returns the messenger. + * + * @return \Drupal\Core\Messenger\MessengerInterface + * The messenger. + */ + public static function messenger() { + if (static::$messenger === NULL) { + static::$messenger = new ChainedMessenger(); + } + return static::$messenger; + } + } diff --git a/core/lib/Drupal/Core/Messenger/BaseMessenger.php b/core/lib/Drupal/Core/Messenger/BaseMessenger.php new file mode 100644 index 0000000000..f76c295301 --- /dev/null +++ b/core/lib/Drupal/Core/Messenger/BaseMessenger.php @@ -0,0 +1,52 @@ +addMessage($message, static::TYPE_STATUS); + } + + /** + * {@inheritdoc} + */ + public function addError($message, $repeat = FALSE) { + return $this->addMessage($message, static::TYPE_ERROR); + } + + /** + * {@inheritdoc} + */ + public function addWarning($message, $repeat = FALSE) { + return $this->addMessage($message, static::TYPE_WARNING); + } + + /** + * Convert any safe strings to markup. + * + * @param string|\Drupal\Component\Render\MarkupInterface $message + * The message. + * + * @return string|\Drupal\Component\Render\MarkupInterface + * The markup. + */ + protected function convertToMarkup($message) { + if (!($message instanceof Markup) && $message instanceof MarkupInterface) { + $message = Markup::create((string) $message); + } + return $message; + } + +} diff --git a/core/lib/Drupal/Core/Messenger/ChainedMessenger.php b/core/lib/Drupal/Core/Messenger/ChainedMessenger.php new file mode 100644 index 0000000000..b056a2ba7d --- /dev/null +++ b/core/lib/Drupal/Core/Messenger/ChainedMessenger.php @@ -0,0 +1,134 @@ +memoryMessenger = new MemoryMessenger(); + } + + /** + * {@inheritdoc} + */ + public function addMessage($message, $type = self::TYPE_STATUS, $repeat = FALSE) { + return $this->getMessenger()->addMessage($message, $type, $repeat); + } + + /** + * {@inheritdoc} + */ + public function addStatus($message, $repeat = FALSE) { + return $this->getMessenger()->addStatus($message, $repeat); + } + + /** + * {@inheritdoc} + */ + public function addError($message, $repeat = FALSE) { + return $this->getMessenger()->addError($message, $repeat); + } + + /** + * {@inheritdoc} + */ + public function addWarning($message, $repeat = FALSE) { + return $this->getMessenger()->addWarning($message, $repeat); + } + + /** + * {@inheritdoc} + */ + public function all() { + return $this->getMessenger()->all(); + } + + /** + * {@inheritdoc} + */ + public function messagesByType($type) { + return $this->getMessenger()->messagesByType($type); + } + + /** + * {@inheritdoc} + */ + public function deleteAll() { + return $this->getMessenger()->deleteAll(); + } + + /** + * {@inheritdoc} + */ + public function deleteByType($type) { + return $this->getMessenger()->deleteByType($type); + } + + /** + * Get the messenger to use. + * + * @return \Drupal\Core\Messenger\MessengerInterface + * The messenger. + */ + protected function getMessenger() { + // Shortcut if we have a messenger service. + if (isset($this->messengerService)) { + return $this->messengerService; + } + + // If the service exists, but we haven't set it yet. + if (\Drupal::hasService('messenger')) { + $this->messengerService = \Drupal::service('messenger'); + $this->mergeMessages($this->memoryMessenger, $this->messengerService); + return $this->messengerService; + } + + // Fallback to memory messenger. + return $this->memoryMessenger; + } + + /** + * Merges messages from one messenger to another. + * + * @param \Drupal\Core\Messenger\MessengerInterface $from + * The messenger to merge from. + * @param \Drupal\Core\Messenger\MessengerInterface $to + * The messenger to merge to. + */ + public function mergeMessages(MessengerInterface $from, MessengerInterface $to) { + foreach ($from->all() as $type => $messages) { + foreach ($messages as $message) { + $to->addMessage($message, $type); + } + } + } + +} diff --git a/core/lib/Drupal/Core/Messenger/LegacyMessenger.php b/core/lib/Drupal/Core/Messenger/LegacyMessenger.php deleted file mode 100644 index 8c9751f3d8..0000000000 --- a/core/lib/Drupal/Core/Messenger/LegacyMessenger.php +++ /dev/null @@ -1,212 +0,0 @@ -killSwitch = $killSwitch; - } - - /** - * {@inheritdoc} - */ - public function addMessage($message, $type = self::TYPE_STATUS, $repeat = FALSE) { - $this->setMessage($message, $type, $repeat); - } - - /** - * {@inheritdoc} - */ - public function addStatus($message, $repeat = FALSE) { - return $this->addMessage($message, static::TYPE_STATUS); - } - - /** - * {@inheritdoc} - */ - public function addError($message, $repeat = FALSE) { - return $this->addMessage($message, static::TYPE_ERROR); - } - - /** - * {@inheritdoc} - */ - public function addWarning($message, $repeat = FALSE) { - return $this->addMessage($message, static::TYPE_WARNING); - } - - /** - * {@inheritdoc} - */ - public function all() { - return $this->getMessages(NULL, FALSE); - } - - /** - * {@inheritdoc} - */ - public function messagesByType($type) { - return $this->getMessages($type, FALSE); - } - - /** - * {@inheritdoc} - */ - public function deleteAll() { - return $this->getMessages(NULL, TRUE); - } - - /** - * {@inheritdoc} - */ - public function deleteByType($type) { - return $this->getMessages($type, TRUE); - } - - /** - * Sets a message to display to the user. - * - * Messages are stored in a session variable and displayed in the page template - * via the $messages theme variable. - * - * Example usage: - * @code - * drupal_set_message(t('An error occurred and processing did not complete.'), 'error'); - * @endcode - * - * @param string|\Drupal\Component\Render\MarkupInterface $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. - * @param string $type - * (optional) The message's type. Defaults to 'status'. These values are - * supported: - * - 'status' - * - 'warning' - * - '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. - * - * @return array|null - * A multidimensional array with keys corresponding to the set message types. - * The indexed array values of each contain the set messages for that type, - * and each message is an associative array with the following format: - * - safe: Boolean indicating whether the message string has been marked as - * safe. Non-safe strings will be escaped automatically. - * - message: The message string. - * So, the following is an example of the full return array structure: - * @code - * array( - * 'status' => array( - * array( - * 'safe' => TRUE, - * 'message' => 'A safe markup string.', - * ), - * array( - * 'safe' => FALSE, - * 'message' => "$arbitrary_user_input to escape.", - * ), - * ), - * ); - * @endcode - * If there are no messages set, the function returns NULL. - * - * @internal - */ - private function setMessage($message = NULL, $type = 'status', $repeat = FALSE) { - if (isset($message)) { - if (!isset($_SESSION['messages'][$type])) { - $_SESSION['messages'][$type] = []; - } - - // Convert strings which are safe to the simplest Markup objects. - if (!($message instanceof Markup) && $message instanceof MarkupInterface) { - $message = Markup::create((string) $message); - } - - // Do not use strict type checking so that equivalent string and - // MarkupInterface objects are detected. - if ($repeat || !in_array($message, $_SESSION['messages'][$type])) { - $_SESSION['messages'][$type][] = $message; - } - - // Mark this page as being uncacheable. - $this->killSwitch->trigger(); - } - - // Messages not set when DB connection fails. - return isset($_SESSION['messages']) ? $_SESSION['messages'] : NULL; - } - - /** - * Returns all messages that have been set with drupal_set_message(). - * - * @param string $type - * (optional) Limit the messages returned by type. Defaults to NULL, meaning - * all types. These values are supported: - * - NULL - * - 'status' - * - 'warning' - * - 'error' - * @param bool $clear_queue - * (optional) If this is TRUE, the queue will be cleared of messages of the - * type specified in the $type parameter. Otherwise the queue will be left - * intact. Defaults to TRUE. - * - * @return array - * An associative, nested array of messages grouped by message type, with - * the top-level keys as the message type. The messages returned are - * limited to the type specified in the $type parameter, if any. If there - * are no messages of the specified type, an empty array is returned. See - * drupal_set_message() for the array structure of individual messages. - * - * @see drupal_set_message() - * @see status-messages.html.twig - * - * @internal - */ - private function getMessages($type = NULL, $clear_queue = TRUE) { - if ($messages = $this->setMessage()) { - if ($type) { - if ($clear_queue) { - unset($_SESSION['messages'][$type]); - } - if (isset($messages[$type])) { - return [$type => $messages[$type]]; - } - } - else { - if ($clear_queue) { - unset($_SESSION['messages']); - } - return $messages; - } - } - return []; - } - -} diff --git a/core/lib/Drupal/Core/Messenger/MemoryMessenger.php b/core/lib/Drupal/Core/Messenger/MemoryMessenger.php new file mode 100644 index 0000000000..494a23a8e5 --- /dev/null +++ b/core/lib/Drupal/Core/Messenger/MemoryMessenger.php @@ -0,0 +1,71 @@ + [], + MessengerInterface::TYPE_STATUS => [], + MessengerInterface::TYPE_WARNING => [], + ]; + + /** + * {@inheritdoc} + */ + public function addMessage($message, $type = self::TYPE_STATUS, $repeat = FALSE) { + $message = $this->convertToMarkup($message); + // Do not use strict type checking so that equivalent string and + // MarkupInterface objects are detected. + if ($repeat || !in_array($message, $this->messages[$type])) { + $this->messages[$type][] = $message; + } + + return $this; + } + + /** + * {@inheritdoc} + */ + public function all() { + return $this->messages; + } + + /** + * {@inheritdoc} + */ + public function messagesByType($type) { + return $this->messages[$type]; + } + + /** + * {@inheritdoc} + */ + public function deleteAll() { + $messages = $this->messages; + $this->messages = []; + return $messages; + } + + /** + * {@inheritdoc} + */ + public function deleteByType($type) { + $messages = $this->messages[$type]; + $this->messages[$type] = []; + return $messages; + } + +} diff --git a/core/lib/Drupal/Core/Messenger/Messenger.php b/core/lib/Drupal/Core/Messenger/Messenger.php new file mode 100644 index 0000000000..a846defd01 --- /dev/null +++ b/core/lib/Drupal/Core/Messenger/Messenger.php @@ -0,0 +1,88 @@ +flashBag = $flash_bag; + $this->killSwitch = $killSwitch; + } + + /** + * {@inheritdoc} + */ + public function addMessage($message, $type = self::TYPE_STATUS, $repeat = FALSE) { + $message = $this->convertToMarkup($message); + // Do not use strict type checking so that equivalent string and + // MarkupInterface objects are detected. + if ($repeat || !in_array($message, $this->flashBag->peek($type))) { + $this->flashBag->add($type, $message); + } + + // Mark this page as being uncacheable. + $this->killSwitch->trigger(); + + return $this; + } + + /** + * {@inheritdoc} + */ + public function all() { + return $this->flashBag->peekAll(); + } + + /** + * {@inheritdoc} + */ + public function messagesByType($type) { + return $this->flashBag->peek($type); + } + + /** + * {@inheritdoc} + */ + public function deleteAll() { + return $this->flashBag->clear(); + } + + /** + * {@inheritdoc} + */ + public function deleteByType($type) { + // Flash bag gets and clears flash messages from the stack. + return $this->flashBag->get($type); + } + +} diff --git a/core/lib/Drupal/Core/Messenger/MessengerInterface.php b/core/lib/Drupal/Core/Messenger/MessengerInterface.php index 216835bcea..809c93d118 100644 --- a/core/lib/Drupal/Core/Messenger/MessengerInterface.php +++ b/core/lib/Drupal/Core/Messenger/MessengerInterface.php @@ -109,11 +109,15 @@ public function all(); * or self::TYPE_ERROR. * * @return string[]|\Drupal\Component\Render\MarkupInterface[] + * The messages of given type. */ public function messagesByType($type); /** * Deletes all messages. + * + * @return string[]|\Drupal\Component\Render\MarkupInterface[] + * The deleted messages. */ public function deleteAll(); @@ -123,6 +127,9 @@ public function deleteAll(); * @param string $type * The messages' type. Either self::TYPE_STATUS, self::TYPE_WARNING, or * self::TYPE_ERROR. + * + * @return string[]|\Drupal\Component\Render\MarkupInterface[] + * The deleted messages of given type.. */ public function deleteByType($type); 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 37eb87585d..e350bc2eaf 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 @@ -5,6 +5,7 @@ use Drupal\Core\Access\AccessResult; use Drupal\Core\Cache\CacheableResponse; use Drupal\Core\Controller\ControllerBase; +use Drupal\Core\Messenger\MessengerInterface; use Drupal\Core\Render\RendererInterface; use Drupal\Core\Render\Markup; use Drupal\Core\Session\AccountInterface; @@ -48,6 +49,13 @@ class SystemTestController extends ControllerBase { */ protected $renderer; + /** + * The messenger service. + * + * @var \Drupal\Core\Messenger\MessengerInterface + */ + protected $messenger; + /** * Constructs the SystemTestController. * @@ -59,12 +67,15 @@ class SystemTestController extends ControllerBase { * The current user. * @param \Drupal\Core\Render\RendererInterface $renderer * The renderer. + * @param \Drupal\Core\Messenger\MessengerInterface $messenger + * The messenger service. */ - public function __construct(LockBackendInterface $lock, LockBackendInterface $persistent_lock, AccountInterface $current_user, RendererInterface $renderer) { + public function __construct(LockBackendInterface $lock, LockBackendInterface $persistent_lock, AccountInterface $current_user, RendererInterface $renderer, MessengerInterface $messenger) { $this->lock = $lock; $this->persistentLock = $persistent_lock; $this->currentUser = $current_user; $this->renderer = $renderer; + $this->messenger = $messenger; } /** @@ -75,7 +86,8 @@ public static function create(ContainerInterface $container) { $container->get('lock'), $container->get('lock.persistent'), $container->get('current_user'), - $container->get('renderer') + $container->get('renderer'), + $container->get('messenger') ); } @@ -99,9 +111,13 @@ public function drupalSetMessageTest() { // Set two messages. drupal_set_message('First message (removed).'); drupal_set_message(t('Second message with markup! (not removed).')); - + $messages = $this->messenger->deleteByType('status'); // Remove the first. - unset($_SESSION['messages']['status'][0]); + unset($messages[0]); + + foreach ($messages as $message) { + $this->messenger->addStatus($message); + } // Duplicate message check. drupal_set_message('Non Duplicated message', 'status', FALSE); diff --git a/core/tests/Drupal/KernelTests/Core/Common/DrupalSetMessageTest.php b/core/tests/Drupal/KernelTests/Core/Common/DrupalSetMessageTest.php index 59470e6563..7a15fc3e04 100644 --- a/core/tests/Drupal/KernelTests/Core/Common/DrupalSetMessageTest.php +++ b/core/tests/Drupal/KernelTests/Core/Common/DrupalSetMessageTest.php @@ -20,10 +20,4 @@ public function testDrupalSetMessage() { $this->assertEquals('A message: bar', (string) $messages['status'][0]); } - protected function tearDown() { - // Clear session to prevent global leakage. - unset($_SESSION['messages']); - parent::tearDown(); - } - } diff --git a/core/tests/Drupal/Tests/Core/Messenger/ChainedMessengerTest.php b/core/tests/Drupal/Tests/Core/Messenger/ChainedMessengerTest.php new file mode 100644 index 0000000000..d79e50ae74 --- /dev/null +++ b/core/tests/Drupal/Tests/Core/Messenger/ChainedMessengerTest.php @@ -0,0 +1,41 @@ +addWarning("Foo"); + $messengerFrom->addStatus("Bar"); + $messengerFrom->addError("Fiz"); + + $messengerTo = new MemoryMessenger(); + $messengerTo->addWarning("Platypus"); + + $chainedMessenger = new ChainedMessenger(); + $chainedMessenger->mergeMessages($messengerFrom, $messengerTo); + + $messages = $messengerTo->all(); + + $this->assertContains("Platypus", $messages[MessengerInterface::TYPE_WARNING]); + $this->assertContains("Foo", $messages[MessengerInterface::TYPE_WARNING]); + $this->assertContains("Bar", $messages[MessengerInterface::TYPE_STATUS]); + $this->assertContains("Fiz", $messages[MessengerInterface::TYPE_ERROR]); + + } + +} diff --git a/core/tests/Drupal/Tests/Listeners/DeprecationListener.php b/core/tests/Drupal/Tests/Listeners/DeprecationListener.php index 80b3d31c97..99f133480f 100644 --- a/core/tests/Drupal/Tests/Listeners/DeprecationListener.php +++ b/core/tests/Drupal/Tests/Listeners/DeprecationListener.php @@ -113,6 +113,8 @@ public static function getSkippedDeprecations() { 'Automatically creating the first item for computed fields is deprecated in Drupal 8.5.x and will be removed before Drupal 9.0.0. Use \Drupal\Core\TypedData\ComputedItemListTrait instead.', '"\Drupal\Core\Entity\ContentEntityStorageBase::doLoadRevisionFieldItems()" is deprecated in Drupal 8.5.x and will be removed before Drupal 9.0.0. "\Drupal\Core\Entity\ContentEntityStorageBase::doLoadMultipleRevisionsFieldItems()" should be implemented instead. See https://www.drupal.org/node/2924915.', 'Passing a single revision ID to "\Drupal\Core\Entity\Sql\SqlContentEntityStorage::buildQuery()" is deprecated in Drupal 8.5.x and will be removed before Drupal 9.0.0. An array of revision IDs should be given instead. See https://www.drupal.org/node/2924915.', + 'drupal_set_message() is deprecated Drupal 8.5.0 and will be removed before Drupal 9.0.0. Use \Drupal\Core\Messenger\MessengerInterface::addMessage() instead. See https://www.drupal.org/node/2774931', + 'drupal_get_message() is deprecated Drupal 8.5.0 and will be removed before Drupal 9.0.0. Use \Drupal\Core\Messenger\MessengerInterface::all() or \Drupal\Core\Messenger\MessengerInterface::messagesByType() instead. See https://www.drupal.org/node/2774931', ]; }