diff --git a/core/core.services.yml b/core/core.services.yml index f30cee7aaeb96f13d60ec118ceff7aa3df49b422..9b07193fee160ba9d437655ea5438f0147329b12 100644 --- a/core/core.services.yml +++ b/core/core.services.yml @@ -604,9 +604,12 @@ services: tags: - { name: access_check, applies_to: _csrf_token } arguments: ['@csrf_token'] + maintenance_mode: + class: Drupal\Core\Site\MaintenanceMode + arguments: ['@state', '@current_user'] maintenance_mode_subscriber: 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: - { name: event_subscriber } path_subscriber: diff --git a/core/lib/Drupal/Core/EventSubscriber/MaintenanceModeSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/MaintenanceModeSubscriber.php index 1b665f333432322bb68576a54c5a2d6afac04622..8556f9b7923401a704e104761bdb4f2333f9e94d 100644 --- a/core/lib/Drupal/Core/EventSubscriber/MaintenanceModeSubscriber.php +++ b/core/lib/Drupal/Core/EventSubscriber/MaintenanceModeSubscriber.php @@ -11,14 +11,13 @@ use Drupal\Component\Utility\Xss; use Drupal\Core\Config\ConfigFactoryInterface; use Drupal\Core\Page\DefaultHtmlPageRenderer; +use Drupal\Core\Routing\RouteMatch; use Drupal\Core\Routing\UrlGeneratorInterface; use Drupal\Core\Session\AccountInterface; -use Drupal\Core\State\StateInterface; +use Drupal\Core\Site\MaintenanceModeInterface; use Drupal\Core\StringTranslation\StringTranslationTrait; use Drupal\Core\StringTranslation\TranslationInterface; -use Symfony\Cmf\Component\Routing\RouteObjectInterface; use Symfony\Component\EventDispatcher\EventSubscriberInterface; -use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\Event\GetResponseEvent; use Symfony\Component\HttpKernel\KernelEvents; @@ -31,18 +30,18 @@ class MaintenanceModeSubscriber implements EventSubscriberInterface { 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. @@ -58,31 +57,11 @@ class MaintenanceModeSubscriber implements EventSubscriberInterface { */ 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. * - * @param \Drupal\Core\State\StateInterface $state - * The state. + * @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 @@ -92,27 +71,14 @@ class MaintenanceModeSubscriber implements EventSubscriberInterface { * @param \Drupal\Core\Session\AccountInterface $account * The current user. */ - public function __construct(StateInterface $state, ConfigFactoryInterface $config_factory, TranslationInterface $translation, UrlGeneratorInterface $url_generator, AccountInterface $account) { - $this->state = $state; + public function __construct(MaintenanceModeInterface $maintenance_mode, ConfigFactoryInterface $config_factory, TranslationInterface $translation, UrlGeneratorInterface $url_generator, AccountInterface $account) { + $this->maintenanceMode = $maintenance_mode; $this->config = $config_factory; $this->stringTranslation = $translation; $this->urlGenerator = $url_generator; $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. * @@ -120,51 +86,36 @@ public function onKernelRequestDetermineSiteStatus(GetResponseEvent $event) { * The event to process. */ public function onKernelRequestMaintenance(GetResponseEvent $event) { - $request = $event->getRequest(); - $response = $event->getResponse(); - // Continue if the site is online and the response is not a redirection. - if ($request->attributes->get('_maintenance') != static::SITE_ONLINE && !($response instanceof RedirectResponse)) { - // Deliver the 503 page. - drupal_maintenance_theme(); - $content = Xss::filterAdmin(String::format($this->config->get('system.maintenance')->get('message'), array( - '@site' => $this->config->get('system.site')->get('name'), - ))); - $content = DefaultHtmlPageRenderer::renderPage($content, t('Site under maintenance')); - $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. Go online.', array('@url' => $this->urlGenerator->generate('system.site_maintenance_mode'))), 'status', FALSE); + $route_match = RouteMatch::createFromRequest($event->getRequest()); + if ($this->maintenanceMode->applies($route_match)) { + if (!$this->maintenanceMode->exempt($this->account)) { + // Deliver the 503 page if the site is in maintenance mode and the + // logged in user is not allowed to bypass it. + drupal_maintenance_theme(); + $content = Xss::filterAdmin(String::format($this->config->get('system.maintenance')->get('message'), array( + '@site' => $this->config->get('system.site')->get('name'), + ))); + // @todo Break the dependency on DefaultHtmlPageRenderer, see: + // https://www.drupal.org/node/2295609 + $content = DefaultHtmlPageRenderer::renderPage($content, $this->t('Site under maintenance')); + $response = new Response('Service unavailable', 503); + $response->setContent($content); + $event->setResponse($response); } else { - $this->drupalSetMessage($this->t('Operating in maintenance mode.'), 'status', FALSE); - } - } - } - - /** - * Checks whether the site is in maintenance mode. - * - * @return bool - * 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; + // 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. Go online.', array('@url' => $this->urlGenerator->generate('system.site_maintenance_mode'))), 'status', FALSE); + } + else { + $this->drupalSetMessage($this->t('Operating in maintenance mode.'), 'status', FALSE); + } + } } } - return FALSE; } /** @@ -177,10 +128,7 @@ protected function drupalSetMessage($message = NULL, $type = 'status', $repeat = /** * {@inheritdoc} */ - 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); + public static function getSubscribedEvents() { $events[KernelEvents::REQUEST][] = array('onKernelRequestMaintenance', 30); return $events; } diff --git a/core/lib/Drupal/Core/Site/MaintenanceMode.php b/core/lib/Drupal/Core/Site/MaintenanceMode.php new file mode 100644 index 0000000000000000000000000000000000000000..cac817c773f925e4b384d468348bc9e03eec13f7 --- /dev/null +++ b/core/lib/Drupal/Core/Site/MaintenanceMode.php @@ -0,0 +1,60 @@ +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'); + } + +} diff --git a/core/lib/Drupal/Core/Site/MaintenanceModeInterface.php b/core/lib/Drupal/Core/Site/MaintenanceModeInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..45c4457403cd6161fa066e3a7887578b0daa107c --- /dev/null +++ b/core/lib/Drupal/Core/Site/MaintenanceModeInterface.php @@ -0,0 +1,40 @@ +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; - } - -} diff --git a/core/modules/user/src/EventSubscriber/MaintenanceModeSubscriber.php b/core/modules/user/src/EventSubscriber/MaintenanceModeSubscriber.php index 4d80474babe07a79c448d0cec3fd301822219ffb..f6aee6925652bfb1de87bfc15af2a4ef10a2f530 100644 --- a/core/modules/user/src/EventSubscriber/MaintenanceModeSubscriber.php +++ b/core/modules/user/src/EventSubscriber/MaintenanceModeSubscriber.php @@ -7,17 +7,46 @@ 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\HttpFoundation\RedirectResponse; use Symfony\Component\HttpKernel\Event\GetResponseEvent; use Symfony\Component\HttpKernel\KernelEvents; -use Drupal\Core\EventSubscriber\MaintenanceModeSubscriber as CoreMaintenanceModeSubscriber; /** * Maintenance mode subscriber to logout users. */ 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. * @@ -25,42 +54,25 @@ class MaintenanceModeSubscriber implements EventSubscriberInterface { * The event to process. */ public function onKernelRequestMaintenance(GetResponseEvent $event) { - $user = \Drupal::currentUser(); $request = $event->getRequest(); - $site_status = $request->attributes->get('_maintenance'); - // @todo Remove dependency on the internal _system_path attribute: - // https://www.drupal.org/node/2288911. + $route_match = RouteMatch::createFromRequest($request); $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 ($user->isAuthenticated() && !$user->hasPermission('access site in maintenance mode')) { + if ($this->account->isAuthenticated() && !$this->maintenanceMode->exempt($this->account)) { user_logout(); // Redirect to homepage. $event->setResponse(new RedirectResponse(url('', array('absolute' => TRUE)))); return; } - if ($user->isAnonymous()) { - switch ($path) { - case 'user': - // Forward anonymous user to login page. - $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 ($this->account->isAnonymous() && $path == 'user') { + // Forward anonymous user to login page. + $event->setResponse(new RedirectResponse(url('user/login', array('absolute' => TRUE)))); + return; } } - if ($user->isAuthenticated()) { + if ($this->account->isAuthenticated()) { if ($path == 'user/login') { // If user is logged in, redirect to 'user' instead of giving 403. $event->setResponse(new RedirectResponse(url('user', array('absolute' => TRUE)))); @@ -68,7 +80,7 @@ public function onKernelRequestMaintenance(GetResponseEvent $event) { } if ($path == 'user/register') { // 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; } } diff --git a/core/modules/user/user.routing.yml b/core/modules/user/user.routing.yml index 55c57d1ffb34f3b4a3601fc873f301dd9097bfdd..0a7b8420f69d924a679817a6c492fc384e27a084 100644 --- a/core/modules/user/user.routing.yml +++ b/core/modules/user/user.routing.yml @@ -122,6 +122,8 @@ user.pass: _title: 'Request new password' requirements: _access: 'TRUE' + options: + _maintenance_access: TRUE user.page: path: '/user' @@ -146,6 +148,8 @@ user.login: _title: 'Log in' requirements: _access: 'TRUE' + options: + _maintenance_access: TRUE user.edit: path: '/user/{user}/edit' @@ -185,3 +189,5 @@ user.reset: operation: NULL requirements: _access: 'TRUE' + options: + _maintenance_access: TRUE diff --git a/core/modules/user/user.services.yml b/core/modules/user/user.services.yml index 1e8f9b3e2049a190c657cb0141383bb01571dc7d..a408fcf977827735b3c09519dc43d55a3b13ed16 100644 --- a/core/modules/user/user.services.yml +++ b/core/modules/user/user.services.yml @@ -38,6 +38,7 @@ services: arguments: ['@database', '@config.factory', '@entity.manager', '@entity.query'] user_maintenance_mode_subscriber: class: Drupal\user\EventSubscriber\MaintenanceModeSubscriber + arguments: ['@maintenance_mode', '@current_user'] tags: - { name: event_subscriber } theme.negotiator.admin_theme: