Unverified Commit e2bebc04 authored by Alex Pott's avatar Alex Pott
Browse files

Issue #229778 by cburschka, mmedrano, panshulk, David_Rothstein, xjm,...

Issue #229778 by cburschka, mmedrano, panshulk, David_Rothstein, xjm, diamondsea, dawehner, joachim, smustgrave, poker10, vidorado, alexpott, godotislate, quietone, penyaskito, Race.it, john cook, catch, longwave: Log changes to maintenance mode

(cherry picked from commit e7c74513)
parent 51c0672b
Loading
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -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:
+46 −77
Original line number Diff line number Diff line
@@ -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;

@@ -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;
  }

  /**
@@ -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}
   */
@@ -177,6 +145,7 @@ public static function getSubscribedEvents(): array {
      'onMaintenanceModeRequest',
      -1000,
    ];
    $events[KernelEvents::TERMINATE] = ['onTerminate'];
    return $events;
  }

+36 −0
Original line number Diff line number Diff line
@@ -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.
   *
@@ -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
@@ -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);
    }
@@ -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;
    }
  }

}
+14 −0
Original line number Diff line number Diff line
@@ -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;

}
+29 −1
Original line number Diff line number Diff line
@@ -25,7 +25,7 @@ class SiteMaintenanceTest extends BrowserTestBase {
  /**
   * {@inheritdoc}
   */
  protected static $modules = ['node'];
  protected static $modules = ['node', 'dblog'];

  /**
   * {@inheritdoc}
@@ -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);
  }
@@ -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('');
@@ -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