Commit baf2bbaa authored by alexpott's avatar alexpott

Issue #2288665 by znerol: Remove _maintenance request attribute and replace it...

Issue #2288665 by znerol: Remove _maintenance request attribute and replace it with a maintenance mode service.
parent 013a6e8c
...@@ -604,9 +604,12 @@ services: ...@@ -604,9 +604,12 @@ services:
tags: tags:
- { name: access_check, applies_to: _csrf_token } - { name: access_check, applies_to: _csrf_token }
arguments: ['@csrf_token'] arguments: ['@csrf_token']
maintenance_mode:
class: Drupal\Core\Site\MaintenanceMode
arguments: ['@state', '@current_user']
maintenance_mode_subscriber: maintenance_mode_subscriber:
class: Drupal\Core\EventSubscriber\MaintenanceModeSubscriber class: Drupal\Core\EventSubscriber\MaintenanceModeSubscriber
arguments: ['@state', '@config.factory', '@string_translation', '@url_generator', '@current_user'] arguments: ['@maintenance_mode', '@config.factory', '@string_translation', '@url_generator', '@current_user']
tags: tags:
- { name: event_subscriber } - { name: event_subscriber }
path_subscriber: path_subscriber:
......
...@@ -11,14 +11,13 @@ ...@@ -11,14 +11,13 @@
use Drupal\Component\Utility\Xss; use Drupal\Component\Utility\Xss;
use Drupal\Core\Config\ConfigFactoryInterface; use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Page\DefaultHtmlPageRenderer; use Drupal\Core\Page\DefaultHtmlPageRenderer;
use Drupal\Core\Routing\RouteMatch;
use Drupal\Core\Routing\UrlGeneratorInterface; use Drupal\Core\Routing\UrlGeneratorInterface;
use Drupal\Core\Session\AccountInterface; use Drupal\Core\Session\AccountInterface;
use Drupal\Core\State\StateInterface; use Drupal\Core\Site\MaintenanceModeInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait; use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\Core\StringTranslation\TranslationInterface; use Drupal\Core\StringTranslation\TranslationInterface;
use Symfony\Cmf\Component\Routing\RouteObjectInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Event\GetResponseEvent; use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\HttpKernel\KernelEvents; use Symfony\Component\HttpKernel\KernelEvents;
...@@ -31,18 +30,18 @@ class MaintenanceModeSubscriber implements EventSubscriberInterface { ...@@ -31,18 +30,18 @@ class MaintenanceModeSubscriber implements EventSubscriberInterface {
use StringTranslationTrait; use StringTranslationTrait;
/** /**
* The current account. * The maintenance mode.
* *
* @var \Drupal\Core\Session\AccountInterface * @var \Drupal\Core\Site\MaintenanceModeInterface
*/ */
protected $account; protected $maintenanceMode;
/** /**
* The state. * The current account.
* *
* @var \Drupal\Core\State\StateInterface * @var \Drupal\Core\Session\AccountInterface
*/ */
protected $state; protected $account;
/** /**
* The config factory. * The config factory.
...@@ -58,31 +57,11 @@ class MaintenanceModeSubscriber implements EventSubscriberInterface { ...@@ -58,31 +57,11 @@ class MaintenanceModeSubscriber implements EventSubscriberInterface {
*/ */
protected $urlGenerator; protected $urlGenerator;
/**
* @defgroup menu_status_codes Menu status codes
* @{
* Status codes to be used to check the maintenance code.
*/
/**
* Internal menu status code -- Menu item inaccessible because site is offline.
*/
const SITE_OFFLINE = 4;
/**
* Internal menu status code -- Everything is working fine.
*/
const SITE_ONLINE = 5;
/**
* @} End of "defgroup menu_status_codes".
*/
/** /**
* Constructs a new MaintenanceModeSubscriber. * Constructs a new MaintenanceModeSubscriber.
* *
* @param \Drupal\Core\State\StateInterface $state * @param \Drupal\Core\Site\MaintenanceModeInterface $maintenance_mode
* The state. * The maintenance mode.
* @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
* The config factory. * The config factory.
* @param \Drupal\Core\StringTranslation\TranslationInterface $translation * @param \Drupal\Core\StringTranslation\TranslationInterface $translation
...@@ -92,27 +71,14 @@ class MaintenanceModeSubscriber implements EventSubscriberInterface { ...@@ -92,27 +71,14 @@ class MaintenanceModeSubscriber implements EventSubscriberInterface {
* @param \Drupal\Core\Session\AccountInterface $account * @param \Drupal\Core\Session\AccountInterface $account
* The current user. * The current user.
*/ */
public function __construct(StateInterface $state, ConfigFactoryInterface $config_factory, TranslationInterface $translation, UrlGeneratorInterface $url_generator, AccountInterface $account) { public function __construct(MaintenanceModeInterface $maintenance_mode, ConfigFactoryInterface $config_factory, TranslationInterface $translation, UrlGeneratorInterface $url_generator, AccountInterface $account) {
$this->state = $state; $this->maintenanceMode = $maintenance_mode;
$this->config = $config_factory; $this->config = $config_factory;
$this->stringTranslation = $translation; $this->stringTranslation = $translation;
$this->urlGenerator = $url_generator; $this->urlGenerator = $url_generator;
$this->account = $account; $this->account = $account;
} }
/**
* Determine whether the page is configured to be offline.
*
* @param \Symfony\Component\HttpKernel\Event\GetResponseEvent $event
* The Event to process.
*/
public function onKernelRequestDetermineSiteStatus(GetResponseEvent $event) {
// Check if the site is offline.
$request = $event->getRequest();
$is_offline = $this->isSiteInMaintenance($request) ? static::SITE_OFFLINE : static::SITE_ONLINE;
$request->attributes->set('_maintenance', $is_offline);
}
/** /**
* Returns the site maintenance page if the site is offline. * Returns the site maintenance page if the site is offline.
* *
...@@ -120,51 +86,36 @@ public function onKernelRequestDetermineSiteStatus(GetResponseEvent $event) { ...@@ -120,51 +86,36 @@ public function onKernelRequestDetermineSiteStatus(GetResponseEvent $event) {
* The event to process. * The event to process.
*/ */
public function onKernelRequestMaintenance(GetResponseEvent $event) { public function onKernelRequestMaintenance(GetResponseEvent $event) {
$request = $event->getRequest(); $route_match = RouteMatch::createFromRequest($event->getRequest());
$response = $event->getResponse(); if ($this->maintenanceMode->applies($route_match)) {
// Continue if the site is online and the response is not a redirection. if (!$this->maintenanceMode->exempt($this->account)) {
if ($request->attributes->get('_maintenance') != static::SITE_ONLINE && !($response instanceof RedirectResponse)) { // Deliver the 503 page if the site is in maintenance mode and the
// Deliver the 503 page. // logged in user is not allowed to bypass it.
drupal_maintenance_theme(); drupal_maintenance_theme();
$content = Xss::filterAdmin(String::format($this->config->get('system.maintenance')->get('message'), array( $content = Xss::filterAdmin(String::format($this->config->get('system.maintenance')->get('message'), array(
'@site' => $this->config->get('system.site')->get('name'), '@site' => $this->config->get('system.site')->get('name'),
))); )));
$content = DefaultHtmlPageRenderer::renderPage($content, t('Site under maintenance')); // @todo Break the dependency on DefaultHtmlPageRenderer, see:
$response = new Response('Service unavailable', 503); // https://www.drupal.org/node/2295609
$response->setContent($content); $content = DefaultHtmlPageRenderer::renderPage($content, $this->t('Site under maintenance'));
$event->setResponse($response); $response = new Response('Service unavailable', 503);
} $response->setContent($content);
$event->setResponse($response);
$can_access_maintenance = $this->account->hasPermission('access site in maintenance mode');
$is_maintenance = $this->state->get('system.maintenance_mode');
// Ensure that the maintenance mode message is displayed only once
// (allowing for page redirects) and specifically suppress its display on
// the maintenance mode settings page.
$is_maintenance_route = $request->attributes->get(RouteObjectInterface::ROUTE_NAME) == 'system.site_maintenance_mode';
if ($is_maintenance && $can_access_maintenance && !$is_maintenance_route) {
if ($this->account->hasPermission('administer site configuration')) {
$this->drupalSetMessage($this->t('Operating in maintenance mode. <a href="@url">Go online.</a>', array('@url' => $this->urlGenerator->generate('system.site_maintenance_mode'))), 'status', FALSE);
} }
else { else {
$this->drupalSetMessage($this->t('Operating in maintenance mode.'), 'status', FALSE); // Display a message if the logged in user has access to the site in
} // maintenance mode. However, suppress it on the maintenance mode
} // settings page.
} if ($route_match->getRouteName() != 'system.site_maintenance_mode') {
if ($this->account->hasPermission('administer site configuration')) {
/** $this->drupalSetMessage($this->t('Operating in maintenance mode. <a href="@url">Go online.</a>', array('@url' => $this->urlGenerator->generate('system.site_maintenance_mode'))), 'status', FALSE);
* Checks whether the site is in maintenance mode. }
* else {
* @return bool $this->drupalSetMessage($this->t('Operating in maintenance mode.'), 'status', FALSE);
* FALSE if the site is not in maintenance mode }
*/ }
protected function isSiteInMaintenance() {
// Check if site is in maintenance mode.
if ($this->state->get('system.maintenance_mode')) {
if (!$this->account->hasPermission('access site in maintenance mode')) {
return TRUE;
} }
} }
return FALSE;
} }
/** /**
...@@ -177,10 +128,7 @@ protected function drupalSetMessage($message = NULL, $type = 'status', $repeat = ...@@ -177,10 +128,7 @@ protected function drupalSetMessage($message = NULL, $type = 'status', $repeat =
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */
static function getSubscribedEvents() { public static function getSubscribedEvents() {
// In order to change the maintenance status an event subscriber with a
// priority between 30 and 40 should be added.
$events[KernelEvents::REQUEST][] = array('onKernelRequestDetermineSiteStatus', 40);
$events[KernelEvents::REQUEST][] = array('onKernelRequestMaintenance', 30); $events[KernelEvents::REQUEST][] = array('onKernelRequestMaintenance', 30);
return $events; return $events;
} }
......
<?php
/**
* @file
* Contains \Drupal\Core\Site\MaintenanceMode.
*/
namespace Drupal\Core\Site;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\State\StateInterface;
/**
* Provides the default implementation of the maintenance mode service.
*/
class MaintenanceMode implements MaintenanceModeInterface {
/**
* The state.
*
* @var \Drupal\Core\State\StateInterface
*/
protected $state;
/**
* Constructs a new maintenance mode service.
*
* @param \Drupal\Core\State\StateInterface $state
* The state.
*/
public function __construct(StateInterface $state) {
$this->state = $state;
}
/**
* {@inheritdoc}
*/
public function applies(RouteMatchInterface $route_match) {
if (!$this->state->get('system.maintenance_mode')) {
return FALSE;
}
if ($route = $route_match->getRouteObject()) {
if ($route->getOption('_maintenance_access')) {
return FALSE;
}
}
return TRUE;
}
/**
* {@inheritdoc}
*/
public function exempt(AccountInterface $account) {
return $account->hasPermission('access site in maintenance mode');
}
}
<?php
/**
* @file
* Contains \Drupal\Core\Site\MaintenanceModeInterface.
*/
namespace Drupal\Core\Site;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\Core\Session\AccountInterface;
/**
* Defines the interface for the maintenance mode service.
*/
interface MaintenanceModeInterface {
/**
* Returns whether the site is in maintenance mode.
*
* @param \Drupal\Core\Routing\RouteMatchInterface $route_match
* The current route match.
*
* @return bool
* TRUE if the site is in maintenance mode.
*/
public function applies(RouteMatchInterface $route_match);
/**
* Determines whether a user has access to the site in maintenance mode.
*
* @param \Drupal\Core\Session\AccountInterface $account
* The logged in user.
*
* @return bool
* TRUE if the user should be exempted from maintenance mode.
*/
public function exempt(AccountInterface $account);
}
...@@ -20,6 +20,8 @@ menu_test.login_callback: ...@@ -20,6 +20,8 @@ menu_test.login_callback:
_content: '\Drupal\menu_test\TestControllers::testLogin' _content: '\Drupal\menu_test\TestControllers::testLogin'
requirements: requirements:
_access: 'TRUE' _access: 'TRUE'
options:
_maintenance_access: TRUE
menu_test.callback_description: menu_test.callback_description:
path: '/menu_callback_description' path: '/menu_callback_description'
......
services: services:
menu_test_maintenance_mode_subscriber:
class: Drupal\menu_test\EventSubscriber\MaintenanceModeSubscriber
tags:
- { name: event_subscriber }
theme.negotiator.test_theme: theme.negotiator.test_theme:
class: Drupal\menu_test\Theme\TestThemeNegotiator class: Drupal\menu_test\Theme\TestThemeNegotiator
tags: tags:
......
<?php
/**
* @file
* Contains \Drupal\menu_test\EventSubscriber\MaintenanceModeSubscriber.
*/
namespace Drupal\menu_test\EventSubscriber;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use Drupal\Core\EventSubscriber\MaintenanceModeSubscriber as CoreMaintenanceModeSubscriber;
/**
* Maintenance mode subscriber to set site online on a test.
*/
class MaintenanceModeSubscriber implements EventSubscriberInterface {
/**
* Set the page online if called from a certain path.
*
* @param \Symfony\Component\HttpKernel\Event\GetResponseEvent $event
* The event to process.
*/
public function onKernelRequestMaintenance(GetResponseEvent $event) {
$request = $event->getRequest();
// Allow access to menu_login_callback even if in maintenance mode.
if ($request->attributes->get('_maintenance') == CoreMaintenanceModeSubscriber::SITE_OFFLINE && $request->attributes->get('_system_path') == 'menu_login_callback') {
$request->attributes->set('_maintenance', CoreMaintenanceModeSubscriber::SITE_ONLINE);
}
}
/**
* {@inheritdoc}
*/
public static function getSubscribedEvents() {
$events[KernelEvents::REQUEST][] = array('onKernelRequestMaintenance', 35);
return $events;
}
}
...@@ -7,17 +7,46 @@ ...@@ -7,17 +7,46 @@
namespace Drupal\user\EventSubscriber; namespace Drupal\user\EventSubscriber;
use Drupal\Core\Routing\RouteMatch;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\Site\MaintenanceModeInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpKernel\Event\GetResponseEvent; use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\HttpKernel\KernelEvents; use Symfony\Component\HttpKernel\KernelEvents;
use Drupal\Core\EventSubscriber\MaintenanceModeSubscriber as CoreMaintenanceModeSubscriber;
/** /**
* Maintenance mode subscriber to logout users. * Maintenance mode subscriber to logout users.
*/ */
class MaintenanceModeSubscriber implements EventSubscriberInterface { class MaintenanceModeSubscriber implements EventSubscriberInterface {
/**
* The maintenance mode.
*
* @var \Drupal\Core\Site\MaintenanceMode
*/
protected $maintenanceMode;
/**
* The current account.
*
* @var \Drupal\Core\Session\AccountInterface
*/
protected $account;
/**
* Constructs a new MaintenanceModeSubscriber.
*
* @param \Drupal\Core\Site\MaintenanceModeInterface $maintenance_mode
* The maintenance mode.
* @param \Drupal\Core\Session\AccountInterface $account
* The current user.
*/
public function __construct(MaintenanceModeInterface $maintenance_mode, AccountInterface $account) {
$this->maintenanceMode = $maintenance_mode;
$this->account = $account;
}
/** /**
* Determine whether the page is configured to be offline. * Determine whether the page is configured to be offline.
* *
...@@ -25,42 +54,25 @@ class MaintenanceModeSubscriber implements EventSubscriberInterface { ...@@ -25,42 +54,25 @@ class MaintenanceModeSubscriber implements EventSubscriberInterface {
* The event to process. * The event to process.
*/ */
public function onKernelRequestMaintenance(GetResponseEvent $event) { public function onKernelRequestMaintenance(GetResponseEvent $event) {
$user = \Drupal::currentUser();
$request = $event->getRequest(); $request = $event->getRequest();
$site_status = $request->attributes->get('_maintenance'); $route_match = RouteMatch::createFromRequest($request);
// @todo Remove dependency on the internal _system_path attribute:
// https://www.drupal.org/node/2288911.
$path = $request->attributes->get('_system_path'); $path = $request->attributes->get('_system_path');
if ($site_status == CoreMaintenanceModeSubscriber::SITE_OFFLINE) { if ($this->maintenanceMode->applies($route_match)) {
// If the site is offline, log out unprivileged users. // If the site is offline, log out unprivileged users.
if ($user->isAuthenticated() && !$user->hasPermission('access site in maintenance mode')) { if ($this->account->isAuthenticated() && !$this->maintenanceMode->exempt($this->account)) {
user_logout(); user_logout();
// Redirect to homepage. // Redirect to homepage.
$event->setResponse(new RedirectResponse(url('<front>', array('absolute' => TRUE)))); $event->setResponse(new RedirectResponse(url('<front>', array('absolute' => TRUE))));
return; return;
} }
if ($user->isAnonymous()) { if ($this->account->isAnonymous() && $path == 'user') {
switch ($path) { // Forward anonymous user to login page.
case 'user': $event->setResponse(new RedirectResponse(url('user/login', array('absolute' => TRUE))));
// Forward anonymous user to login page. return;
$event->setResponse(new RedirectResponse(url('user/login', array('absolute' => TRUE))));
return;
case 'user/login':
case 'user/password':
// Disable offline mode.
$request->attributes->set('_maintenance', CoreMaintenanceModeSubscriber::SITE_ONLINE);
break;
default:
if (strpos($path, 'user/reset/') === 0) {
// Disable offline mode.
$request->attributes->set('_maintenance', CoreMaintenanceModeSubscriber::SITE_ONLINE);
}
break;
}
} }
} }
if ($user->isAuthenticated()) { if ($this->account->isAuthenticated()) {
if ($path == 'user/login') { if ($path == 'user/login') {
// If user is logged in, redirect to 'user' instead of giving 403. // If user is logged in, redirect to 'user' instead of giving 403.
$event->setResponse(new RedirectResponse(url('user', array('absolute' => TRUE)))); $event->setResponse(new RedirectResponse(url('user', array('absolute' => TRUE))));
...@@ -68,7 +80,7 @@ public function onKernelRequestMaintenance(GetResponseEvent $event) { ...@@ -68,7 +80,7 @@ public function onKernelRequestMaintenance(GetResponseEvent $event) {
} }
if ($path == 'user/register') { if ($path == 'user/register') {
// Authenticated user should be redirected to user edit page. // Authenticated user should be redirected to user edit page.
$event->setResponse(new RedirectResponse(url('user/' . $user->id() . '/edit', array('absolute' => TRUE)))); $event->setResponse(new RedirectResponse(url('user/' . $this->account->id() . '/edit', array('absolute' => TRUE))));
return; return;
} }
} }
......
...@@ -122,6 +122,8 @@ user.pass: ...@@ -122,6 +122,8 @@ user.pass:
_title: 'Request new password' _title: 'Request new password'
requirements: requirements:
_access: 'TRUE' _access: 'TRUE'
options:
_maintenance_access: TRUE
user.page: user.page:
path: '/user' path: '/user'
...@@ -146,6 +148,8 @@ user.login: ...@@ -146,6 +148,8 @@ user.login:
_title: 'Log in' _title: 'Log in'
requirements: requirements:
_access: 'TRUE' _access: 'TRUE'
options:
_maintenance_access: TRUE
user.edit: user.edit:
path: '/user/{user}/edit' path: '/user/{user}/edit'
...@@ -185,3 +189,5 @@ user.reset: ...@@ -185,3 +189,5 @@ user.reset:
operation: NULL