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..f843f27754 100644 --- a/core/includes/bootstrap.inc +++ b/core/includes/bootstrap.inc @@ -455,8 +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) { + @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); /* @var \Drupal\Core\Messenger\MessengerInterface $messenger */ $messenger = \Drupal::service('messenger'); $messenger->addMessage($message, $type, $repeat); @@ -487,11 +492,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..ce45ebf3af --- /dev/null +++ b/core/lib/Drupal/Core/Messenger/BaseMessenger.php @@ -0,0 +1,50 @@ +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..e69f8472e9 --- /dev/null +++ b/core/lib/Drupal/Core/Messenger/ChainedMessenger.php @@ -0,0 +1,109 @@ +memoryMessenger = new MemoryMessenger(); + } + + /** + * {@inheritdoc} + */ + public function addMessage($message, $type = self::TYPE_STATUS, $repeat = FALSE) { + return $this->getMessenger()->addMessage($message, $type, $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->serviceMessenger)) { + return $this->serviceMessenger; + } + + // If the service exists, but we haven't set it yet. + if (\Drupal::hasService('messenger')) { + $this->serviceMessenger = \Drupal::service('messenger'); + $this->mergeMessages($this->memoryMessenger, $this->serviceMessenger); + return $this->serviceMessenger; + } + + // 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. + */ + protected 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..be97620acf --- /dev/null +++ b/core/lib/Drupal/Core/Messenger/MemoryMessenger.php @@ -0,0 +1,68 @@ +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..5dc8fa8bb7 --- /dev/null +++ b/core/lib/Drupal/Core/Messenger/Messenger.php @@ -0,0 +1,90 @@ +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/Listeners/DeprecationListener.php b/core/tests/Drupal/Tests/Listeners/DeprecationListener.php index 3673376d61..b326c1cd62 100644 --- a/core/tests/Drupal/Tests/Listeners/DeprecationListener.php +++ b/core/tests/Drupal/Tests/Listeners/DeprecationListener.php @@ -111,6 +111,8 @@ public static function getSkippedDeprecations() { 'The Drupal\migrate_drupal\Plugin\migrate\source\d6\i18nVariable is deprecated in Drupal 8.4.0 and will be removed before Drupal 9.0.0. Instead, use Drupal\migrate_drupal\Plugin\migrate\source\d6\VariableTranslation', 'Implicit cacheability metadata bubbling (onto the global render context) in normalizers is deprecated since Drupal 8.5.0 and will be removed in Drupal 9.0.0. Use the "cacheability" serialization context instead, for explicit cacheability metadata bubbling. See https://www.drupal.org/node/2918937', '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_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', ]; }