Loading core/core.services.yml +1 −1 Original line number Diff line number Diff line Loading @@ -1426,7 +1426,7 @@ services: Drupal\Core\Site\MaintenanceModeInterface: '@maintenance_mode' maintenance_mode_subscriber: class: Drupal\Core\EventSubscriber\MaintenanceModeSubscriber arguments: ['@maintenance_mode', '@config.factory', '@string_translation', '@url_generator', '@current_user', '@bare_html_page_renderer', '@messenger', '@event_dispatcher'] autowire: true route_access_response_subscriber: class: Drupal\Core\EventSubscriber\RouteAccessResponseSubscriber client_error_response_subscriber: Loading core/lib/Drupal/Core/EventSubscriber/MaintenanceModeSubscriber.php +46 −77 Original line number Diff line number Diff line Loading @@ -10,11 +10,15 @@ use Drupal\Core\Session\AccountInterface; use Drupal\Core\Site\MaintenanceModeEvents; use Drupal\Core\Site\MaintenanceModeInterface; use Drupal\Core\State\StateInterface; use Drupal\Core\StringTranslation\StringTranslationTrait; use Drupal\Core\StringTranslation\TranslationInterface; use Psr\Log\LoggerInterface; use Symfony\Component\DependencyInjection\Attribute\AutowireServiceClosure; use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\Event\RequestEvent; use Symfony\Component\HttpKernel\Event\TerminateEvent; use Symfony\Component\HttpKernel\KernelEvents; use Symfony\Contracts\EventDispatcher\EventDispatcherInterface; Loading @@ -25,84 +29,20 @@ class MaintenanceModeSubscriber implements EventSubscriberInterface { use StringTranslationTrait; /** * The maintenance mode. * * @var \Drupal\Core\Site\MaintenanceModeInterface */ protected $maintenanceMode; /** * The current account. * * @var \Drupal\Core\Session\AccountInterface */ protected $account; /** * The config factory. * * @var \Drupal\Core\Config\ConfigFactoryInterface */ protected $config; /** * The URL generator. * * @var \Drupal\Core\Routing\UrlGeneratorInterface */ protected $urlGenerator; /** * The bare HTML page renderer. * * @var \Drupal\Core\Render\BareHtmlPageRendererInterface */ protected $bareHtmlPageRenderer; /** * The messenger. * * @var \Drupal\Core\Messenger\MessengerInterface */ protected $messenger; /** * An event dispatcher instance to use for configuration events. * * @var \Symfony\Contracts\EventDispatcher\EventDispatcherInterface */ protected $eventDispatcher; /** * Constructs a new MaintenanceModeSubscriber. * * @param \Drupal\Core\Site\MaintenanceModeInterface $maintenance_mode * The maintenance mode. * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory * The config factory. * @param \Drupal\Core\StringTranslation\TranslationInterface $translation * The string translation. * @param \Drupal\Core\Routing\UrlGeneratorInterface $url_generator * The URL generator. * @param \Drupal\Core\Session\AccountInterface $account * The current user. * @param \Drupal\Core\Render\BareHtmlPageRendererInterface $bare_html_page_renderer * The bare HTML page renderer. * @param \Drupal\Core\Messenger\MessengerInterface $messenger * The messenger. * @param \Symfony\Contracts\EventDispatcher\EventDispatcherInterface $event_dispatcher * The event dispatcher. */ public function __construct(MaintenanceModeInterface $maintenance_mode, ConfigFactoryInterface $config_factory, TranslationInterface $translation, UrlGeneratorInterface $url_generator, AccountInterface $account, BareHtmlPageRendererInterface $bare_html_page_renderer, MessengerInterface $messenger, EventDispatcherInterface $event_dispatcher) { $this->maintenanceMode = $maintenance_mode; $this->config = $config_factory; public function __construct( protected readonly MaintenanceModeInterface $maintenanceMode, protected readonly ConfigFactoryInterface $configFactory, TranslationInterface $translation, protected readonly UrlGeneratorInterface $urlGenerator, protected readonly AccountInterface $account, protected readonly BareHtmlPageRendererInterface $bareHtmlPageRenderer, protected readonly MessengerInterface $messenger, protected readonly EventDispatcherInterface $eventDispatcher, protected readonly StateInterface $state, #[AutowireServiceClosure('logger.channel.default')] private readonly \Closure $logger, ) { $this->stringTranslation = $translation; $this->urlGenerator = $url_generator; $this->account = $account; $this->bareHtmlPageRenderer = $bare_html_page_renderer; $this->messenger = $messenger; $this->eventDispatcher = $event_dispatcher; } /** Loading Loading @@ -167,6 +107,34 @@ public function onMaintenanceModeRequest(RequestEvent $event) { $event->setResponse($response); } /** * Logs changes to maintenance mode. * * @param \Symfony\Component\HttpKernel\Event\TerminateEvent $event * The event object. */ public function onTerminate(TerminateEvent $event): void { $values = $this->state->getValuesSetDuringRequest('system.maintenance_mode'); if ($values && $values['original'] !== $values['value']) { if ($values['value']) { $this->getLogger()->info('Maintenance mode enabled.'); } else { $this->getLogger()->info('Maintenance mode disabled.'); } } } /** * Gets the logging service. * * @return \Psr\Log\LoggerInterface * The logging service. */ protected function getLogger(): LoggerInterface { return ($this->logger)(); } /** * {@inheritdoc} */ Loading @@ -177,6 +145,7 @@ public static function getSubscribedEvents(): array { 'onMaintenanceModeRequest', -1000, ]; $events[KernelEvents::TERMINATE] = ['onTerminate']; return $events; } Loading core/lib/Drupal/Core/State/State.php +36 −0 Original line number Diff line number Diff line Loading @@ -30,6 +30,16 @@ class State extends CacheCollector implements StateInterface { */ protected $keyValueStore; /** * Tracks keys that have been modified during the request lifecycle. * * An associative array keyed by the state key name, where each value * is an array with the following keys: * - value: The last value set during the request. * - original: The initial value at the start of the request. */ protected array $keysSetDuringRequest = []; /** * Constructs a State object. * Loading Loading @@ -90,6 +100,7 @@ public function set($key, $value) { @trigger_error(self::$deprecatedState[$key]['message'], E_USER_DEPRECATED); $key = self::$deprecatedState[$key]['replacement']; } $this->registerKeySetDuringRequest($key, $value, parent::get($key)); $this->keyValueStore->set($key, $value); // If another request had a cache miss before this request, and also hasn't // written to cache yet, then it may already have read this value from the Loading @@ -108,6 +119,7 @@ public function set($key, $value) { public function setMultiple(array $data) { $this->keyValueStore->setMultiple($data); foreach ($data as $key => $value) { $this->registerKeySetDuringRequest($key, $value, parent::get($key)); parent::set($key, $value); $this->persist($key); } Loading Loading @@ -138,4 +150,28 @@ public function resetCache() { $this->clear(); } /** * {@inheritdoc} */ public function getValuesSetDuringRequest(string $key): ?array { return $this->keysSetDuringRequest[$key] ?? NULL; } /** * Registers a key that was set during the request. * * @param string $key * The key that was set. * @param mixed $value * The value that was set. * @param mixed $previousValue * The previous value that was stored. */ protected function registerKeySetDuringRequest(string $key, mixed $value, mixed $previousValue): void { $this->keysSetDuringRequest[$key]['value'] = $value; if (!array_key_exists('original', $this->keysSetDuringRequest[$key])) { $this->keysSetDuringRequest[$key]['original'] = $previousValue; } } } core/lib/Drupal/Core/State/StateInterface.php +14 −0 Original line number Diff line number Diff line Loading @@ -74,4 +74,18 @@ public function deleteMultiple(array $keys); */ public function resetCache(); /** * Returns any values modified for a given key during the request. * * @param string $key * The key to get the values for. * * @return array{value: mixed, original: mixed}|null * An array containing: * - value: The last value set during the request. * - original: The initial value at the start of the request. * If $key is not set, NULL is returned. */ public function getValuesSetDuringRequest(string $key): ?array; } core/modules/system/tests/src/Functional/System/SiteMaintenanceTest.php +29 −1 Original line number Diff line number Diff line Loading @@ -25,7 +25,7 @@ class SiteMaintenanceTest extends BrowserTestBase { /** * {@inheritdoc} */ protected static $modules = ['node']; protected static $modules = ['node', 'dblog']; /** * {@inheritdoc} Loading Loading @@ -65,6 +65,7 @@ protected function setUp(): void { $this->adminUser = $this->drupalCreateUser([ 'administer site configuration', 'access site in maintenance mode', 'access site reports', ]); $this->drupalLogin($this->adminUser); } Loading Loading @@ -105,6 +106,11 @@ public function testSiteMaintenance(): void { $this->assertSession()->linkExists('Go online.'); $this->assertSession()->linkByHrefExists(Url::fromRoute('system.site_maintenance_mode')->toString()); // Verify that the change to maintenance mode is logged. $this->drupalGet(Url::fromRoute('dblog.overview')); $this->assertSession()->statusCodeEquals(200); $this->assertSession()->pageTextContainsOnce('Maintenance mode enabled.'); // Logout and verify that offline message is displayed. $this->drupalLogout(); $this->drupalGet(''); Loading Loading @@ -178,6 +184,28 @@ public function testSiteMaintenance(): void { $this->drupalLogout(); $this->drupalGet(''); $this->assertEquals('Site under maintenance', $this->cssSelect('main h1')[0]->getText()); $this->drupalLogin($this->adminUser); // Re-save the form with maintenance mode on to ensure another log message is // not added. $edit = [ 'maintenance_mode' => 1, ]; $this->drupalGet('admin/config/development/maintenance'); $this->submitForm($edit, 'Save configuration'); $this->drupalGet(Url::fromRoute('dblog.overview')); $this->assertSession()->pageTextContainsOnce('Maintenance mode enabled.'); $this->assertSession()->pageTextNotContains('Maintenance mode disabled.'); // Turn off maintenance mode. $edit = [ 'maintenance_mode' => 0, ]; $this->drupalGet('admin/config/development/maintenance'); $this->submitForm($edit, 'Save configuration'); $this->drupalGet(Url::fromRoute('dblog.overview')); $this->assertSession()->pageTextContainsOnce('Maintenance mode disabled.'); } /** Loading Loading
core/core.services.yml +1 −1 Original line number Diff line number Diff line Loading @@ -1426,7 +1426,7 @@ services: Drupal\Core\Site\MaintenanceModeInterface: '@maintenance_mode' maintenance_mode_subscriber: class: Drupal\Core\EventSubscriber\MaintenanceModeSubscriber arguments: ['@maintenance_mode', '@config.factory', '@string_translation', '@url_generator', '@current_user', '@bare_html_page_renderer', '@messenger', '@event_dispatcher'] autowire: true route_access_response_subscriber: class: Drupal\Core\EventSubscriber\RouteAccessResponseSubscriber client_error_response_subscriber: Loading
core/lib/Drupal/Core/EventSubscriber/MaintenanceModeSubscriber.php +46 −77 Original line number Diff line number Diff line Loading @@ -10,11 +10,15 @@ use Drupal\Core\Session\AccountInterface; use Drupal\Core\Site\MaintenanceModeEvents; use Drupal\Core\Site\MaintenanceModeInterface; use Drupal\Core\State\StateInterface; use Drupal\Core\StringTranslation\StringTranslationTrait; use Drupal\Core\StringTranslation\TranslationInterface; use Psr\Log\LoggerInterface; use Symfony\Component\DependencyInjection\Attribute\AutowireServiceClosure; use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\Event\RequestEvent; use Symfony\Component\HttpKernel\Event\TerminateEvent; use Symfony\Component\HttpKernel\KernelEvents; use Symfony\Contracts\EventDispatcher\EventDispatcherInterface; Loading @@ -25,84 +29,20 @@ class MaintenanceModeSubscriber implements EventSubscriberInterface { use StringTranslationTrait; /** * The maintenance mode. * * @var \Drupal\Core\Site\MaintenanceModeInterface */ protected $maintenanceMode; /** * The current account. * * @var \Drupal\Core\Session\AccountInterface */ protected $account; /** * The config factory. * * @var \Drupal\Core\Config\ConfigFactoryInterface */ protected $config; /** * The URL generator. * * @var \Drupal\Core\Routing\UrlGeneratorInterface */ protected $urlGenerator; /** * The bare HTML page renderer. * * @var \Drupal\Core\Render\BareHtmlPageRendererInterface */ protected $bareHtmlPageRenderer; /** * The messenger. * * @var \Drupal\Core\Messenger\MessengerInterface */ protected $messenger; /** * An event dispatcher instance to use for configuration events. * * @var \Symfony\Contracts\EventDispatcher\EventDispatcherInterface */ protected $eventDispatcher; /** * Constructs a new MaintenanceModeSubscriber. * * @param \Drupal\Core\Site\MaintenanceModeInterface $maintenance_mode * The maintenance mode. * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory * The config factory. * @param \Drupal\Core\StringTranslation\TranslationInterface $translation * The string translation. * @param \Drupal\Core\Routing\UrlGeneratorInterface $url_generator * The URL generator. * @param \Drupal\Core\Session\AccountInterface $account * The current user. * @param \Drupal\Core\Render\BareHtmlPageRendererInterface $bare_html_page_renderer * The bare HTML page renderer. * @param \Drupal\Core\Messenger\MessengerInterface $messenger * The messenger. * @param \Symfony\Contracts\EventDispatcher\EventDispatcherInterface $event_dispatcher * The event dispatcher. */ public function __construct(MaintenanceModeInterface $maintenance_mode, ConfigFactoryInterface $config_factory, TranslationInterface $translation, UrlGeneratorInterface $url_generator, AccountInterface $account, BareHtmlPageRendererInterface $bare_html_page_renderer, MessengerInterface $messenger, EventDispatcherInterface $event_dispatcher) { $this->maintenanceMode = $maintenance_mode; $this->config = $config_factory; public function __construct( protected readonly MaintenanceModeInterface $maintenanceMode, protected readonly ConfigFactoryInterface $configFactory, TranslationInterface $translation, protected readonly UrlGeneratorInterface $urlGenerator, protected readonly AccountInterface $account, protected readonly BareHtmlPageRendererInterface $bareHtmlPageRenderer, protected readonly MessengerInterface $messenger, protected readonly EventDispatcherInterface $eventDispatcher, protected readonly StateInterface $state, #[AutowireServiceClosure('logger.channel.default')] private readonly \Closure $logger, ) { $this->stringTranslation = $translation; $this->urlGenerator = $url_generator; $this->account = $account; $this->bareHtmlPageRenderer = $bare_html_page_renderer; $this->messenger = $messenger; $this->eventDispatcher = $event_dispatcher; } /** Loading Loading @@ -167,6 +107,34 @@ public function onMaintenanceModeRequest(RequestEvent $event) { $event->setResponse($response); } /** * Logs changes to maintenance mode. * * @param \Symfony\Component\HttpKernel\Event\TerminateEvent $event * The event object. */ public function onTerminate(TerminateEvent $event): void { $values = $this->state->getValuesSetDuringRequest('system.maintenance_mode'); if ($values && $values['original'] !== $values['value']) { if ($values['value']) { $this->getLogger()->info('Maintenance mode enabled.'); } else { $this->getLogger()->info('Maintenance mode disabled.'); } } } /** * Gets the logging service. * * @return \Psr\Log\LoggerInterface * The logging service. */ protected function getLogger(): LoggerInterface { return ($this->logger)(); } /** * {@inheritdoc} */ Loading @@ -177,6 +145,7 @@ public static function getSubscribedEvents(): array { 'onMaintenanceModeRequest', -1000, ]; $events[KernelEvents::TERMINATE] = ['onTerminate']; return $events; } Loading
core/lib/Drupal/Core/State/State.php +36 −0 Original line number Diff line number Diff line Loading @@ -30,6 +30,16 @@ class State extends CacheCollector implements StateInterface { */ protected $keyValueStore; /** * Tracks keys that have been modified during the request lifecycle. * * An associative array keyed by the state key name, where each value * is an array with the following keys: * - value: The last value set during the request. * - original: The initial value at the start of the request. */ protected array $keysSetDuringRequest = []; /** * Constructs a State object. * Loading Loading @@ -90,6 +100,7 @@ public function set($key, $value) { @trigger_error(self::$deprecatedState[$key]['message'], E_USER_DEPRECATED); $key = self::$deprecatedState[$key]['replacement']; } $this->registerKeySetDuringRequest($key, $value, parent::get($key)); $this->keyValueStore->set($key, $value); // If another request had a cache miss before this request, and also hasn't // written to cache yet, then it may already have read this value from the Loading @@ -108,6 +119,7 @@ public function set($key, $value) { public function setMultiple(array $data) { $this->keyValueStore->setMultiple($data); foreach ($data as $key => $value) { $this->registerKeySetDuringRequest($key, $value, parent::get($key)); parent::set($key, $value); $this->persist($key); } Loading Loading @@ -138,4 +150,28 @@ public function resetCache() { $this->clear(); } /** * {@inheritdoc} */ public function getValuesSetDuringRequest(string $key): ?array { return $this->keysSetDuringRequest[$key] ?? NULL; } /** * Registers a key that was set during the request. * * @param string $key * The key that was set. * @param mixed $value * The value that was set. * @param mixed $previousValue * The previous value that was stored. */ protected function registerKeySetDuringRequest(string $key, mixed $value, mixed $previousValue): void { $this->keysSetDuringRequest[$key]['value'] = $value; if (!array_key_exists('original', $this->keysSetDuringRequest[$key])) { $this->keysSetDuringRequest[$key]['original'] = $previousValue; } } }
core/lib/Drupal/Core/State/StateInterface.php +14 −0 Original line number Diff line number Diff line Loading @@ -74,4 +74,18 @@ public function deleteMultiple(array $keys); */ public function resetCache(); /** * Returns any values modified for a given key during the request. * * @param string $key * The key to get the values for. * * @return array{value: mixed, original: mixed}|null * An array containing: * - value: The last value set during the request. * - original: The initial value at the start of the request. * If $key is not set, NULL is returned. */ public function getValuesSetDuringRequest(string $key): ?array; }
core/modules/system/tests/src/Functional/System/SiteMaintenanceTest.php +29 −1 Original line number Diff line number Diff line Loading @@ -25,7 +25,7 @@ class SiteMaintenanceTest extends BrowserTestBase { /** * {@inheritdoc} */ protected static $modules = ['node']; protected static $modules = ['node', 'dblog']; /** * {@inheritdoc} Loading Loading @@ -65,6 +65,7 @@ protected function setUp(): void { $this->adminUser = $this->drupalCreateUser([ 'administer site configuration', 'access site in maintenance mode', 'access site reports', ]); $this->drupalLogin($this->adminUser); } Loading Loading @@ -105,6 +106,11 @@ public function testSiteMaintenance(): void { $this->assertSession()->linkExists('Go online.'); $this->assertSession()->linkByHrefExists(Url::fromRoute('system.site_maintenance_mode')->toString()); // Verify that the change to maintenance mode is logged. $this->drupalGet(Url::fromRoute('dblog.overview')); $this->assertSession()->statusCodeEquals(200); $this->assertSession()->pageTextContainsOnce('Maintenance mode enabled.'); // Logout and verify that offline message is displayed. $this->drupalLogout(); $this->drupalGet(''); Loading Loading @@ -178,6 +184,28 @@ public function testSiteMaintenance(): void { $this->drupalLogout(); $this->drupalGet(''); $this->assertEquals('Site under maintenance', $this->cssSelect('main h1')[0]->getText()); $this->drupalLogin($this->adminUser); // Re-save the form with maintenance mode on to ensure another log message is // not added. $edit = [ 'maintenance_mode' => 1, ]; $this->drupalGet('admin/config/development/maintenance'); $this->submitForm($edit, 'Save configuration'); $this->drupalGet(Url::fromRoute('dblog.overview')); $this->assertSession()->pageTextContainsOnce('Maintenance mode enabled.'); $this->assertSession()->pageTextNotContains('Maintenance mode disabled.'); // Turn off maintenance mode. $edit = [ 'maintenance_mode' => 0, ]; $this->drupalGet('admin/config/development/maintenance'); $this->submitForm($edit, 'Save configuration'); $this->drupalGet(Url::fromRoute('dblog.overview')); $this->assertSession()->pageTextContainsOnce('Maintenance mode disabled.'); } /** Loading