Drupal 8.0.0 beta 12. More info: https://www.drupal.org/node/2514176

This commit is contained in:
Pantheon Automation 2015-08-17 17:00:26 -07:00 committed by Greg Anderson
commit 9921556621
13277 changed files with 1459781 additions and 0 deletions

View file

@ -0,0 +1,50 @@
<?php
/**
* @file
* Contains \Drupal\Core\EventSubscriber\AcceptNegotiation406.
*/
namespace Drupal\Core\EventSubscriber;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\GetResponseForControllerResultEvent;
use Symfony\Component\HttpKernel\Exception\NotAcceptableHttpException;
use Symfony\Component\HttpKernel\KernelEvents;
/**
* View subscriber rendering a 406 if we could not route or render a request.
*
* @todo fix or replace this in https://www.drupal.org/node/2364011
*/
class AcceptNegotiation406 implements EventSubscriberInterface {
/**
* Throws an HTTP 406 error if we get this far, which we normally shouldn't.
*
* @param \Symfony\Component\HttpKernel\Event\GetResponseForControllerResultEvent $event
* The event to process.
*/
public function onViewDetect406(GetResponseForControllerResultEvent $event) {
$request = $event->getRequest();
$result = $event->getControllerResult();
// If this is a render array then we assume that the router went with the
// generic controller and not one with a format. If the format requested is
// not HTML though we can also assume that the requested format is invalid
// so we provide a 406 response.
if (is_array($result) && $request->getRequestFormat() !== 'html') {
throw new NotAcceptableHttpException('Not acceptable');
}
}
/**
* {@inheritdoc}
*/
static function getSubscribedEvents() {
$events[KernelEvents::VIEW][] = ['onViewDetect406', -10];
return $events;
}
}

View file

@ -0,0 +1,247 @@
<?php
/**
* @file
* Contains \Drupal\Core\EventSubscriber\ActiveLinkResponseFilter.
*/
namespace Drupal\Core\EventSubscriber;
use Drupal\Component\Serialization\Json;
use Drupal\Core\Language\LanguageInterface;
use Drupal\Core\Language\LanguageManagerInterface;
use Drupal\Core\Path\CurrentPathStack;
use Drupal\Core\Path\PathMatcherInterface;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\Url;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
use Symfony\Component\HttpKernel\KernelEvents;
/**
* Subscribes to filter HTML responses, to set the 'is-active' class on links.
*
* Only for anonymous users; for authenticated users, the active-link asset
* library is loaded.
*
* @see system_page_attachments()
*/
class ActiveLinkResponseFilter implements EventSubscriberInterface {
/**
* The current user.
*
* @var \Drupal\Core\Session\AccountInterface
*/
protected $currentUser;
/**
* The current path.
*
* @var \Drupal\Core\Path\CurrentPathStack
*/
protected $currentPath;
/**
* The path matcher.
*
* @var \Drupal\Core\Path\PathMatcherInterface
*/
protected $pathMatcher;
/**
* The language manager.
*
* @var \Drupal\Core\Language\LanguageManagerInterface
*/
protected $languageManager;
/**
* Constructs a new ActiveLinkResponseFilter instance.
*
* @param \Drupal\Core\Session\AccountInterface $current_user
* The current user.
* @param \Drupal\Core\Path\CurrentPathStack $current_path
* The current path.
* @param \Drupal\Core\Path\PathMatcherInterface $path_matcher
* The path matcher.
* @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
* The language manager.
*/
public function __construct(AccountInterface $current_user, CurrentPathStack $current_path, PathMatcherInterface $path_matcher, LanguageManagerInterface $language_manager) {
$this->currentUser = $current_user;
$this->currentPath = $current_path;
$this->pathMatcher = $path_matcher;
$this->languageManager = $language_manager;
}
/**
* Sets the 'is-active' class on links.
*
* @param \Symfony\Component\HttpKernel\Event\FilterResponseEvent $event
* The response event.
*/
public function onResponse(FilterResponseEvent $event) {
// Only care about HTML responses.
if (stripos($event->getResponse()->headers->get('Content-Type'), 'text/html') === FALSE) {
return;
}
// For authenticated users, the 'is-active' class is set in JavaScript.
// @see system_page_attachments()
if ($this->currentUser->isAuthenticated()) {
return;
}
$response = $event->getResponse();
$response->setContent(static::setLinkActiveClass(
$response->getContent(),
ltrim($this->currentPath->getPath(), '/'),
$this->pathMatcher->isFrontPage(),
$this->languageManager->getCurrentLanguage(LanguageInterface::TYPE_URL)->getId(),
$event->getRequest()->query->all()
));
}
/**
* Sets the "is-active" class on relevant links.
*
* This is a PHP implementation of the drupal.active-link JavaScript library.
*
* @param string $html_markup.
* The HTML markup to update.
* @param string $current_path
* The system path of the currently active page.
* @param bool $is_front
* Whether the current page is the front page (which implies the current
* path might also be <front>).
* @param string $url_language
* The language code of the current URL.
* @param array $query
* The query string for the current URL.
*
* @return string
* The updated HTML markup.
*
* @todo Once a future version of PHP supports parsing HTML5 properly
* (i.e. doesn't fail on
* https://www.drupal.org/comment/7938201#comment-7938201) then we can get
* rid of this manual parsing and use DOMDocument instead.
*/
public static function setLinkActiveClass($html_markup, $current_path, $is_front, $url_language, array $query) {
$search_key_current_path = 'data-drupal-link-system-path="' . $current_path . '"';
$search_key_front = 'data-drupal-link-system-path="&lt;front&gt;"';
// An active link's path is equal to the current path, so search the HTML
// for an attribute with that value.
$offset = 0;
while (strpos($html_markup, $search_key_current_path, $offset) !== FALSE || ($is_front && strpos($html_markup, $search_key_front, $offset) !== FALSE)) {
$pos_current_path = strpos($html_markup, $search_key_current_path, $offset);
$pos_front = strpos($html_markup, $search_key_front, $offset);
// Determine which of the two values is the next match: the exact path, or
// the <front> special case.
$pos_match = NULL;
if ($pos_front === FALSE) {
$pos_match = $pos_current_path;
}
elseif ($pos_current_path === FALSE) {
$pos_match = $pos_front;
}
elseif ($pos_current_path < $pos_front) {
$pos_match = $pos_current_path;
}
else {
$pos_match = $pos_front;
}
// Find beginning and ending of opening tag.
$pos_tag_start = NULL;
for ($i = $pos_match; $pos_tag_start === NULL && $i > 0; $i--) {
if ($html_markup[$i] === '<') {
$pos_tag_start = $i;
}
}
$pos_tag_end = NULL;
for ($i = $pos_match; $pos_tag_end === NULL && $i < strlen($html_markup); $i++) {
if ($html_markup[$i] === '>') {
$pos_tag_end = $i;
}
}
// Get the HTML: this will be the opening part of a single tag, e.g.:
// <a href="/" data-drupal-link-system-path="&lt;front&gt;">
$tag = substr($html_markup, $pos_tag_start, $pos_tag_end - $pos_tag_start + 1);
// Parse it into a DOMDocument so we can reliably read and modify
// attributes.
$dom = new \DOMDocument();
@$dom->loadHTML('<!DOCTYPE html><html><head><meta http-equiv="Content-Type" content="text/html; charset=utf-8" /></head><body>' . $tag . '</body></html>');
$node = $dom->getElementsByTagName('body')->item(0)->firstChild;
// Ensure we don't set the "active" class twice on the same element.
$class = $node->getAttribute('class');
$add_active = !in_array('is-active', explode(' ', $class));
// The language of an active link is equal to the current language.
if ($add_active && $url_language) {
if ($node->hasAttribute('hreflang') && $node->getAttribute('hreflang') !== $url_language) {
$add_active = FALSE;
}
}
// The query parameters of an active link are equal to the current
// parameters.
if ($add_active) {
if ($query) {
if (!$node->hasAttribute('data-drupal-link-query') || $node->getAttribute('data-drupal-link-query') !== Json::encode($query)) {
$add_active = FALSE;
}
}
else {
if ($node->hasAttribute('data-drupal-link-query')) {
$add_active = FALSE;
}
}
}
// Only if the path, the language and the query match, we set the
// "is-active" class.
if ($add_active) {
if (strlen($class) > 0) {
$class .= ' ';
}
$class .= 'is-active';
$node->setAttribute('class', $class);
// Get the updated tag.
$updated_tag = $dom->saveXML($node, LIBXML_NOEMPTYTAG);
// saveXML() added a closing tag, remove it.
$updated_tag = substr($updated_tag, 0, strrpos($updated_tag, '<'));
$html_markup = str_replace($tag, $updated_tag, $html_markup);
// Ensure we only search the remaining HTML.
$offset = $pos_tag_end - strlen($tag) + strlen($updated_tag);
}
else {
// Ensure we only search the remaining HTML.
$offset = $pos_tag_end + 1;
}
}
return $html_markup;
}
/**
* {@inheritdoc}
*/
public static function getSubscribedEvents() {
// Should run after any other response subscriber that modifies the markup.
$events[KernelEvents::RESPONSE][] = ['onResponse', -512];
return $events;
}
}

View file

@ -0,0 +1,117 @@
<?php
/**
* @file
* Contains \Drupal\Core\EventSubscriber\AjaxResponseSubscriber.
*/
namespace Drupal\Core\EventSubscriber;
use Drupal\Component\Utility\Html;
use Drupal\Core\Ajax\AjaxResponse;
use Drupal\Core\Render\AttachmentsResponseProcessorInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\HttpKernel\KernelEvents;
/**
* Response subscriber to handle AJAX responses.
*/
class AjaxResponseSubscriber implements EventSubscriberInterface {
/**
* The AJAX response attachments processor service.
*
* @var \Drupal\Core\Render\AttachmentsResponseProcessorInterface
*/
protected $ajaxResponseAttachmentsProcessor;
/**
* Constructs an AjaxResponseSubscriber object.
*
* @param \Drupal\Core\Render\AttachmentsResponseProcessorInterface $ajax_response_attachments_processor
* The AJAX response attachments processor service.
*/
public function __construct(AttachmentsResponseProcessorInterface $ajax_response_attachments_processor) {
$this->ajaxResponseAttachmentsProcessor = $ajax_response_attachments_processor;
}
/**
* Request parameter to indicate that a request is a Drupal Ajax request.
*/
const AJAX_REQUEST_PARAMETER = '_drupal_ajax';
/**
* Sets the AJAX parameter from the current request.
*
* @param \Symfony\Component\HttpKernel\Event\GetResponseEvent $event
* The response event, which contains the current request.
*/
public function onRequest(GetResponseEvent $event) {
// Pass to the Html class that the current request is an Ajax request.
if ($event->getRequest()->request->get(static::AJAX_REQUEST_PARAMETER)) {
Html::setIsAjax(TRUE);
}
}
/**
* Renders the ajax commands right before preparing the result.
*
* @param \Symfony\Component\HttpKernel\Event\FilterResponseEvent $event
* The response event, which contains the possible AjaxResponse object.
*/
public function onResponse(FilterResponseEvent $event) {
$response = $event->getResponse();
if ($response instanceof AjaxResponse) {
$this->ajaxResponseAttachmentsProcessor->processAttachments($response);
// IE 9 does not support XHR 2 (http://caniuse.com/#feat=xhr2), so
// for that browser, jquery.form submits requests containing a file upload
// via an IFRAME rather than via XHR. Since the response is being sent to
// an IFRAME, it must be formatted as HTML. Specifically:
// - It must use the text/html content type or else the browser will
// present a download prompt. Note: This applies to both file uploads
// as well as any ajax request in a form with a file upload form.
// - It must place the JSON data into a textarea to prevent browser
// extensions such as Linkification and Skype's Browser Highlighter
// from applying HTML transformations such as URL or phone number to
// link conversions on the data values.
//
// Since this affects the format of the output, it could be argued that
// this should be implemented as a separate Accept MIME type. However,
// that would require separate variants for each type of AJAX request
// (e.g., drupal-ajax, drupal-dialog, drupal-modal), so for expediency,
// this browser workaround is implemented via a GET or POST parameter.
//
// @see http://malsup.com/jquery/form/#file-upload
// @see https://www.drupal.org/node/1009382
// @see https://www.drupal.org/node/2339491
// @see Drupal.ajax.prototype.beforeSend()
$accept = $event->getRequest()->headers->get('accept');
if (strpos($accept, 'text/html') !== FALSE) {
$response->headers->set('Content-Type', 'text/html; charset=utf-8');
// Browser IFRAMEs expect HTML. Browser extensions, such as Linkification
// and Skype's Browser Highlighter, convert URLs, phone numbers, etc.
// into links. This corrupts the JSON response. Protect the integrity of
// the JSON data by making it the value of a textarea.
// @see http://malsup.com/jquery/form/#file-upload
// @see https://www.drupal.org/node/1009382
$response->setContent('<textarea>' . $response->getContent() . '</textarea>');
}
}
}
/**
* {@inheritdoc}
*/
public static function getSubscribedEvents() {
$events[KernelEvents::RESPONSE][] = array('onResponse', -100);
$events[KernelEvents::REQUEST][] = array('onRequest', 50);
return $events;
}
}

View file

@ -0,0 +1,88 @@
<?php
/**
* @file
* Contains \Drupal\Core\EventSubscriber\AnonymousUserResponseSubscriber.
*/
namespace Drupal\Core\EventSubscriber;
use Drupal\Core\Cache\CacheableMetadata;
use Drupal\Core\Cache\CacheableResponseInterface;
use Drupal\Core\Session\AccountInterface;
use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
use Symfony\Component\HttpKernel\HttpKernelInterface;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
/**
* Response subscriber to handle finished responses for the anonymous user.
*/
class AnonymousUserResponseSubscriber implements EventSubscriberInterface {
/**
* The current user.
*
* @var \Drupal\Core\Session\AccountInterface
*/
protected $currentUser;
/**
* Constructs an AnonymousUserResponseSubscriber object.
*
* @param \Drupal\Core\Session\AccountInterface $current_user
* The current user.
*/
public function __construct(AccountInterface $current_user) {
$this->currentUser = $current_user;
}
/**
* Adds a cache tag if the 'user.permissions' cache context is present.
*
* @param \Symfony\Component\HttpKernel\Event\FilterResponseEvent $event
* The event to process.
*/
public function onRespond(FilterResponseEvent $event) {
if (!$event->isMasterRequest()) {
return;
}
if (!$this->currentUser->isAnonymous()) {
return;
}
$response = $event->getResponse();
if (!$response instanceof CacheableResponseInterface) {
return;
}
// The 'user.permissions' cache context ensures that if the permissions for
// a role are modified, users are not served stale render cache content.
// But, when entire responses are cached in reverse proxies, the value for
// the cache context is never calculated, causing the stale response to not
// be invalidated. Therefore, when varying by permissions and the current
// user is the anonymous user, also add the cache tag for the 'anonymous'
// role.
if (in_array('user.permissions', $response->getCacheableMetadata()->getCacheContexts())) {
$per_permissions_response_for_anon = new CacheableMetadata();
$per_permissions_response_for_anon->setCacheTags(['config:user.role.anonymous']);
$response->addCacheableDependency($per_permissions_response_for_anon);
}
}
/**
* Registers the methods in this class that should be listeners.
*
* @return array
* An array of event listener definitions.
*/
public static function getSubscribedEvents() {
// Priority 5, so that it runs before FinishResponseSubscriber, but after
// event subscribers that add the associated cacheability metadata (which
// have priority 10). This one is conditional, so must run after those.
$events[KernelEvents::RESPONSE][] = ['onRespond', 5];
return $events;
}
}

View file

@ -0,0 +1,145 @@
<?php
/**
* @file
* Contains \Drupal\Core\EventSubscriber\AuthenticationSubscriber.
*/
namespace Drupal\Core\EventSubscriber;
use Drupal\Core\Authentication\AuthenticationProviderFilterInterface;
use Drupal\Core\Authentication\AuthenticationProviderChallengeInterface;
use Drupal\Core\Authentication\AuthenticationProviderInterface;
use Drupal\Core\Session\AccountProxyInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
use Symfony\Component\HttpKernel\HttpKernelInterface;
use Symfony\Component\HttpKernel\KernelEvents;
/**
* Authentication subscriber.
*
* Trigger authentication during the request.
*/
class AuthenticationSubscriber implements EventSubscriberInterface {
/**
* Authentication provider.
*
* @var \Drupal\Core\Authentication\AuthenticationProviderInterface
*/
protected $authenticationProvider;
/**
* Authentication provider filter.
*
* @var \Drupal\Core\Authentication\AuthenticationProviderFilterInterface|NULL
*/
protected $filter;
/**
* Authentication challenge provider.
*
* @var \Drupal\Core\Authentication\AuthenticationProviderChallengeInterface|NULL
*/
protected $challengeProvider;
/**
* Account proxy.
*
* @var \Drupal\Core\Session\AccountProxyInterface
*/
protected $accountProxy;
/**
* Constructs an authentication subscriber.
*
* @param \Drupal\Core\Authentication\AuthenticationProviderInterface $authentication_provider
* An authentication provider.
* @param \Drupal\Core\Session\AccountProxyInterface $account_proxy
* Account proxy.
*/
public function __construct(AuthenticationProviderInterface $authentication_provider, AccountProxyInterface $account_proxy) {
$this->authenticationProvider = $authentication_provider;
$this->filter = ($authentication_provider instanceof AuthenticationProviderFilterInterface) ? $authentication_provider : NULL;
$this->challengeProvider = ($authentication_provider instanceof AuthenticationProviderChallengeInterface) ? $authentication_provider : NULL;
$this->accountProxy = $account_proxy;
}
/**
* Authenticates user on request.
*
* @param \Symfony\Component\HttpKernel\Event\GetResponseEvent $event
* The request event.
*
* @see \Drupal\Core\Authentication\AuthenticationProviderInterface::authenticate()
*/
public function onKernelRequestAuthenticate(GetResponseEvent $event) {
if ($event->getRequestType() === HttpKernelInterface::MASTER_REQUEST) {
$request = $event->getRequest();
if ($this->authenticationProvider->applies($request)) {
$account = $this->authenticationProvider->authenticate($request);
if ($account) {
$this->accountProxy->setAccount($account);
}
}
}
}
/**
* Denies access if authentication provider is not allowed on this route.
*
* @param \Symfony\Component\HttpKernel\Event\GetResponseEvent $event
* The request event.
*/
public function onKernelRequestFilterProvider(GetResponseEvent $event) {
if (isset($this->filter) && $event->getRequestType() === HttpKernelInterface::MASTER_REQUEST) {
$request = $event->getRequest();
if ($this->authenticationProvider->applies($request) && !$this->filter->appliesToRoutedRequest($request, TRUE)) {
throw new AccessDeniedHttpException();
}
}
}
/**
* Respond with a challenge on access denied exceptions if appropriate.
*
* On a 403 (access denied), if there are no credentials on the request, some
* authentication methods (e.g. basic auth) require that a challenge is sent
* to the client.
*
* @param \Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent $event
* The exception event.
*/
public function onExceptionSendChallenge(GetResponseForExceptionEvent $event) {
if (isset($this->challengeProvider) && $event->getRequestType() === HttpKernelInterface::MASTER_REQUEST) {
$request = $event->getRequest();
$exception = $event->getException();
if ($exception instanceof AccessDeniedHttpException && !$this->authenticationProvider->applies($request) && (!isset($this->filter) || $this->filter->appliesToRoutedRequest($request, FALSE))) {
$challenge_exception = $this->challengeProvider->challengeException($request, $exception);
if ($challenge_exception) {
$event->setException($challenge_exception);
}
}
}
}
/**
* {@inheritdoc}
*/
public static function getSubscribedEvents() {
// The priority for authentication must be higher than the highest event
// subscriber accessing the current user. Especially it must be higher than
// LanguageRequestSubscriber as LanguageManager accesses the current user if
// the language module is enabled.
$events[KernelEvents::REQUEST][] = ['onKernelRequestAuthenticate', 300];
// Access check must be performed after routing.
$events[KernelEvents::REQUEST][] = ['onKernelRequestFilterProvider', 31];
$events[KernelEvents::EXCEPTION][] = ['onExceptionSendChallenge', 75];
return $events;
}
}

View file

@ -0,0 +1,38 @@
<?php
/**
* @file
* Contains \Drupal\Core\EventSubscriber\CacheRouterRebuildSubscriber.
*/
namespace Drupal\Core\EventSubscriber;
use Drupal\Core\Cache\Cache;
use Drupal\Core\Routing\RoutingEvents;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
/**
* Clear cache tags when the router is rebuilt.
*/
class CacheRouterRebuildSubscriber implements EventSubscriberInterface {
/**
* {@inheritdoc}
*/
public function onRouterFinished() {
// Requested URLs that formerly gave a 403/404 may now be valid.
// Also invalidate all cached routing.
Cache::invalidateTags(['4xx-response', 'route_match']);
}
/**
* {@inheritdoc}
*/
public static function getSubscribedEvents() {
$events = [];
// Act only when the router rebuild is finished.
$events[RoutingEvents::FINISHED][] = ['onRouterFinished', 200];
return $events;
}
}

View file

@ -0,0 +1,54 @@
<?php
/**
* @file
* Contains \Drupal\Core\EventSubscriber\ClientErrorResponseSubscriber.
*/
namespace Drupal\Core\EventSubscriber;
use Drupal\Core\Cache\CacheableMetadata;
use Drupal\Core\Cache\CacheableResponseInterface;
use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
/**
* Response subscriber to set the '4xx-response' cache tag on 4xx responses.
*/
class ClientErrorResponseSubscriber implements EventSubscriberInterface {
/**
* Sets the '4xx-response' cache tag on 4xx responses.
*
* @param \Symfony\Component\HttpKernel\Event\FilterResponseEvent $event
* The event to process.
*/
public function onRespond(FilterResponseEvent $event) {
if (!$event->isMasterRequest()) {
return;
}
$response = $event->getResponse();
if (!$response instanceof CacheableResponseInterface) {
return;
}
if ($response->isClientError()) {
$http_4xx_response_cacheability = new CacheableMetadata();
$http_4xx_response_cacheability->setCacheTags(['4xx-response']);
$response->addCacheableDependency($http_4xx_response_cacheability);
}
}
/**
* {@inheritdoc}
*/
public static function getSubscribedEvents() {
// Priority 10, so that it runs before FinishResponseSubscriber, which will
// expose the cacheability metadata in the form of headers.
$events[KernelEvents::RESPONSE][] = ['onRespond', 10];
return $events;
}
}

View file

@ -0,0 +1,332 @@
<?php
/**
* @file
* Contains \Drupal\Core\EventSubscriber\ConfigImportSubscriber.
*/
namespace Drupal\Core\EventSubscriber;
use Drupal\Core\Config\Config;
use Drupal\Core\Config\ConfigImporter;
use Drupal\Core\Config\ConfigImporterEvent;
use Drupal\Core\Config\ConfigImportValidateEventSubscriberBase;
use Drupal\Core\Config\ConfigNameException;
use Drupal\Core\Extension\ThemeHandlerInterface;
use Drupal\Core\Site\Settings;
/**
* Config import subscriber for config import events.
*/
class ConfigImportSubscriber extends ConfigImportValidateEventSubscriberBase {
/**
* Theme data.
*
* @var \Drupal\Core\Extension\Extension[]
*/
protected $themeData;
/**
* Module data.
*
* @var \Drupal\Core\Extension\Extension[]
*/
protected $moduleData;
/**
* The theme handler.
*
* @var \Drupal\Core\Extension\ThemeHandlerInterface
*/
protected $themeHandler;
/**
* Constructs the ConfigImportSubscriber.
*
* @param \Drupal\Core\Extension\ThemeHandlerInterface $theme_handler
* The theme handler.
*/
public function __construct(ThemeHandlerInterface $theme_handler) {
$this->themeHandler = $theme_handler;
}
/**
* Validates the configuration to be imported.
*
* @param \Drupal\Core\Config\ConfigImporterEvent $event
* The Event to process.
*
* @throws \Drupal\Core\Config\ConfigNameException
*/
public function onConfigImporterValidate(ConfigImporterEvent $event) {
foreach (array('delete', 'create', 'update') as $op) {
foreach ($event->getConfigImporter()->getUnprocessedConfiguration($op) as $name) {
try {
Config::validateName($name);
}
catch (ConfigNameException $e) {
$message = $this->t('The config name @config_name is invalid.', array('@config_name' => $name));
$event->getConfigImporter()->logError($message);
}
}
}
$config_importer = $event->getConfigImporter();
if ($config_importer->getStorageComparer()->getSourceStorage()->exists('core.extension')) {
$this->validateModules($config_importer);
$this->validateThemes($config_importer);
$this->validateDependencies($config_importer);
}
else {
$config_importer->logError($this->t('The core.extension configuration does not exist.'));
}
}
/**
* Validates module installations and uninstallations.
*
* @param \Drupal\Core\Config\ConfigImporter $config_importer
* The configuration importer.
*/
protected function validateModules(ConfigImporter $config_importer) {
$core_extension = $config_importer->getStorageComparer()->getSourceStorage()->read('core.extension');
// Get a list of modules with dependency weights as values.
$module_data = $this->getModuleData();
$nonexistent_modules = array_keys(array_diff_key($core_extension['module'], $module_data));
foreach ($nonexistent_modules as $module) {
$config_importer->logError($this->t('Unable to install the %module module since it does not exist.', array('%module' => $module)));
}
// Ensure that all modules being installed have their dependencies met.
$installs = $config_importer->getExtensionChangelist('module', 'install');
foreach ($installs as $module) {
$missing_dependencies = [];
foreach (array_keys($module_data[$module]->requires) as $required_module) {
if (!isset($core_extension['module'][$required_module])) {
$missing_dependencies[] = $module_data[$required_module]->info['name'];
}
}
if (!empty($missing_dependencies)) {
$module_name = $module_data[$module]->info['name'];
$message = $this->formatPlural(count($missing_dependencies),
'Unable to install the %module module since it requires the %required_module module.',
'Unable to install the %module module since it requires the %required_module modules.',
array('%module' => $module_name, '%required_module' => implode(', ', $missing_dependencies))
);
$config_importer->logError($message);
}
}
// Settings is safe to use because settings.php is written before any module
// is installed.
$install_profile = Settings::get('install_profile');
// Ensure that all modules being uninstalled are not required by modules
// that will be installed after the import.
$uninstalls = $config_importer->getExtensionChangelist('module', 'uninstall');
foreach ($uninstalls as $module) {
foreach (array_keys($module_data[$module]->required_by) as $dependent_module) {
if ($module_data[$dependent_module]->status && !in_array($dependent_module, $uninstalls, TRUE) && $dependent_module !== $install_profile) {
$module_name = $module_data[$module]->info['name'];
$dependent_module_name = $module_data[$dependent_module]->info['name'];
$config_importer->logError($this->t('Unable to uninstall the %module module since the %dependent_module module is installed.', array('%module' => $module_name, '%dependent_module' => $dependent_module_name)));
}
}
}
// Ensure that the install profile is not being uninstalled.
if (in_array($install_profile, $uninstalls)) {
$profile_name = $module_data[$install_profile]->info['name'];
$config_importer->logError($this->t('Unable to uninstall the %profile profile since it is the install profile.', array('%profile' => $profile_name)));
}
}
/**
* Validates theme installations and uninstallations.
*
* @param \Drupal\Core\Config\ConfigImporter $config_importer
* The configuration importer.
*/
protected function validateThemes(ConfigImporter $config_importer) {
$core_extension = $config_importer->getStorageComparer()->getSourceStorage()->read('core.extension');
// Get all themes including those that are not installed.
$theme_data = $this->getThemeData();
$installs = $config_importer->getExtensionChangelist('theme', 'install');
foreach ($installs as $key => $theme) {
if (!isset($theme_data[$theme])) {
$config_importer->logError($this->t('Unable to install the %theme theme since it does not exist.', array('%theme' => $theme)));
// Remove non-existing installs from the list so we can validate theme
// dependencies later.
unset($installs[$key]);
}
}
// Ensure that all themes being installed have their dependencies met.
foreach ($installs as $theme) {
foreach (array_keys($theme_data[$theme]->requires) as $required_theme) {
if (!isset($core_extension['theme'][$required_theme])) {
$theme_name = $theme_data[$theme]->info['name'];
$required_theme_name = $theme_data[$required_theme]->info['name'];
$config_importer->logError($this->t('Unable to install the %theme theme since it requires the %required_theme theme.', array('%theme' => $theme_name, '%required_theme' => $required_theme_name)));
}
}
}
// Ensure that all themes being uninstalled are not required by themes that
// will be installed after the import.
$uninstalls = $config_importer->getExtensionChangelist('theme', 'uninstall');
foreach ($uninstalls as $theme) {
foreach (array_keys($theme_data[$theme]->required_by) as $dependent_theme) {
if ($theme_data[$dependent_theme]->status && !in_array($dependent_theme, $uninstalls, TRUE)) {
$theme_name = $theme_data[$theme]->info['name'];
$dependent_theme_name = $theme_data[$dependent_theme]->info['name'];
$config_importer->logError($this->t('Unable to uninstall the %theme theme since the %dependent_theme theme is installed.', array('%theme' => $theme_name, '%dependent_theme' => $dependent_theme_name)));
}
}
}
}
/**
* Validates configuration being imported does not have unmet dependencies.
*
* @param \Drupal\Core\Config\ConfigImporter $config_importer
* The configuration importer.
*/
protected function validateDependencies(ConfigImporter $config_importer) {
$core_extension = $config_importer->getStorageComparer()->getSourceStorage()->read('core.extension');
$existing_dependencies = [
'config' => $config_importer->getStorageComparer()->getSourceStorage()->listAll(),
'module' => array_keys($core_extension['module']),
'theme' => array_keys($core_extension['theme']),
];
$theme_data = $this->getThemeData();
$module_data = $this->getModuleData();
// Validate the dependencies of all the configuration. We have to validate
// the entire tree because existing configuration might depend on
// configuration that is being deleted.
foreach ($config_importer->getStorageComparer()->getSourceStorage()->listAll() as $name) {
// Ensure that the config owner is installed. This checks all
// configuration including configuration entities.
list($owner,) = explode('.', $name, 2);
if ($owner !== 'core') {
$message = FALSE;
if (!isset($core_extension['module'][$owner]) && isset($module_data[$owner])) {
$message = $this->t('Configuration %name depends on the %owner module that will not be installed after import.', array(
'%name' => $name,
'%owner' => $module_data[$owner]->info['name']
));
}
elseif (!isset($core_extension['theme'][$owner]) && isset($theme_data[$owner])) {
$message = $this->t('Configuration %name depends on the %owner theme that will not be installed after import.', array(
'%name' => $name,
'%owner' => $theme_data[$owner]->info['name']
));
}
elseif (!isset($core_extension['module'][$owner]) && !isset($core_extension['theme'][$owner])) {
$message = $this->t('Configuration %name depends on the %owner extension that will not be installed after import.', array(
'%name' => $name,
'%owner' => $owner
));
}
if ($message) {
$config_importer->logError($message);
continue;
}
}
$data = $config_importer->getStorageComparer()->getSourceStorage()->read($name);
// Configuration entities have dependencies on modules, themes, and other
// configuration entities that we can validate. Their content dependencies
// are not validated since we assume that they are soft dependencies.
// Configuration entities can be identified by having 'dependencies' and
// 'uuid' keys.
if (isset($data['dependencies']) && isset($data['uuid'])) {
$dependencies_to_check = array_intersect_key($data['dependencies'], array_flip(['module', 'theme', 'config']));
foreach ($dependencies_to_check as $type => $dependencies) {
$diffs = array_diff($dependencies, $existing_dependencies[$type]);
if (!empty($diffs)) {
$message = FALSE;
switch ($type) {
case 'module':
$message = $this->formatPlural(
count($diffs),
'Configuration %name depends on the %module module that will not be installed after import.',
'Configuration %name depends on modules (%module) that will not be installed after import.',
array('%name' => $name, '%module' => implode(', ', $this->getNames($diffs, $module_data)))
);
break;
case 'theme':
$message = $this->formatPlural(
count($diffs),
'Configuration %name depends on the %theme theme that will not be installed after import.',
'Configuration %name depends on themes (%theme) that will not be installed after import.',
array('%name' => $name, '%theme' => implode(', ', $this->getNames($diffs, $theme_data)))
);
break;
case 'config':
$message = $this->formatPlural(
count($diffs),
'Configuration %name depends on the %config configuration that will not exist after import.',
'Configuration %name depends on configuration (%config) that will not exist after import.',
array('%name' => $name, '%config' => implode(', ', $diffs))
);
break;
}
if ($message) {
$config_importer->logError($message);
}
}
}
}
}
}
/**
* Gets theme data.
*
* @return \Drupal\Core\Extension\Extension[]
*/
protected function getThemeData() {
if (!isset($this->themeData)) {
$this->themeData = $this->themeHandler->rebuildThemeData();
}
return $this->themeData;
}
/**
* Gets module data.
*
* @return \Drupal\Core\Extension\Extension[]
*/
protected function getModuleData() {
if (!isset($this->moduleData)) {
$this->moduleData = system_rebuild_module_data();
}
return $this->moduleData;
}
/**
* Gets human readable extension names.
*
* @param array $names
* A list of extension machine names.
* @param \Drupal\Core\Extension\Extension[] $extension_data
* Extension data.
*
* @return array
* A list of human-readable extension names, or machine names if
* human-readable names are not available.
*/
protected function getNames(array $names, array $extension_data) {
return array_map(function ($name) use ($extension_data) {
if (isset($extension_data[$name])) {
$name = $extension_data[$name]->info['name'];
}
return $name;
}, $names);
}
}

View file

@ -0,0 +1,77 @@
<?php
/**
* @file
* Contains \Drupal\Core\EventSubscriber\ConfigSnapshotSubscriber.
*/
namespace Drupal\Core\EventSubscriber;
use Drupal\Core\Config\ConfigEvents;
use Drupal\Core\Config\ConfigManagerInterface;
use Drupal\Core\Config\StorageInterface;
use Drupal\Core\Config\ConfigImporterEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
/**
* Create a snapshot when config is imported.
*/
class ConfigSnapshotSubscriber implements EventSubscriberInterface {
/**
* The configuration manager.
*
* @var \Drupal\Core\Config\ConfigManagerInterface
*/
protected $configManager;
/**
* The source storage used to discover configuration changes.
*
* @var \Drupal\Core\Config\StorageInterface
*/
protected $sourceStorage;
/**
* The snapshot storage used to write configuration changes.
*
* @var \Drupal\Core\Config\StorageInterface
*/
protected $snapshotStorage;
/**
* Constructs the ConfigSnapshotSubscriber object.
*
* @param StorageInterface $source_storage
* The source storage used to discover configuration changes.
* @param StorageInterface $snapshot_storage
* The snapshot storage used to write configuration changes.
*/
public function __construct(ConfigManagerInterface $config_manager, StorageInterface $source_storage, StorageInterface $snapshot_storage) {
$this->configManager = $config_manager;
$this->sourceStorage = $source_storage;
$this->snapshotStorage = $snapshot_storage;
}
/**
* Creates a config snapshot.
*
* @param \Drupal\Core\Config\ConfigImporterEvent $event
* The Event to process.
*/
public function onConfigImporterImport(ConfigImporterEvent $event) {
$this->configManager->createSnapshot($this->sourceStorage, $this->snapshotStorage);
}
/**
* Registers the methods in this class that should be listeners.
*
* @return array
* An array of event listener definitions.
*/
static function getSubscribedEvents() {
$events[ConfigEvents::IMPORT][] = array('onConfigImporterImport', 40);
return $events;
}
}

View file

@ -0,0 +1,48 @@
<?php
/**
* @file
* Contains \Drupal\Core\EventSubscriber\ContentControllerSubscriber.
*/
namespace Drupal\Core\EventSubscriber;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\HttpKernel\KernelEvents;
/**
* Sets the request format onto the request object.
*
* @todo Remove this event subscriber after
* https://www.drupal.org/node/2092647 has landed.
*/
class ContentControllerSubscriber implements EventSubscriberInterface {
/**
* Sets the _controller on a request when a _form is defined.
*
* @param \Symfony\Component\HttpKernel\Event\GetResponseEvent $event
* The event to process.
*/
public function onRequestDeriveFormWrapper(GetResponseEvent $event) {
$request = $event->getRequest();
if ($request->attributes->has('_form')) {
$request->attributes->set('_controller', 'controller.form:getContentResult');
}
}
/**
* Registers the methods in this class that should be listeners.
*
* @return array
* An array of event listener definitions.
*/
static function getSubscribedEvents() {
$events[KernelEvents::REQUEST][] = array('onRequestDeriveFormWrapper', 29);
return $events;
}
}

View file

@ -0,0 +1,80 @@
<?php
/**
* @file
* Contains \Drupal\Core\EventSubscriber\CustomPageExceptionHtmlSubscriber.
*/
namespace Drupal\Core\EventSubscriber;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Path\AliasManagerInterface;
use Drupal\Core\Routing\RedirectDestinationInterface;
use Psr\Log\LoggerInterface;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent;
use Symfony\Component\HttpKernel\HttpKernelInterface;
/**
* Exception subscriber for handling core custom HTML error pages.
*/
class CustomPageExceptionHtmlSubscriber extends DefaultExceptionHtmlSubscriber {
/**
* The configuration factory.
*
* @var \Drupal\Core\Config\ConfigFactoryInterface
*/
protected $configFactory;
/**
* The page alias manager.
*
* @var \Drupal\Core\Path\AliasManagerInterface
*/
protected $aliasManager;
/**
* Constructs a new CustomPageExceptionHtmlSubscriber.
*
* @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
* The configuration factory.
* @param \Drupal\Core\Path\AliasManagerInterface $alias_manager
* The alias manager service.
* @param \Symfony\Component\HttpKernel\HttpKernelInterface $http_kernel
* The HTTP Kernel service.
* @param \Psr\Log\LoggerInterface $logger
* The logger service.
* @param \Drupal\Core\Routing\RedirectDestinationInterface $redirect_destination
* The redirect destination service.
*/
public function __construct(ConfigFactoryInterface $config_factory, AliasManagerInterface $alias_manager, HttpKernelInterface $http_kernel, LoggerInterface $logger, RedirectDestinationInterface $redirect_destination) {
parent::__construct($http_kernel, $logger, $redirect_destination);
$this->configFactory = $config_factory;
$this->aliasManager = $alias_manager;
}
/**
* {@inheritdoc}
*/
protected static function getPriority() {
return -50;
}
/**
* {@inheritdoc}
*/
public function on403(GetResponseForExceptionEvent $event) {
$path = $this->aliasManager->getPathByAlias($this->configFactory->get('system.site')->get('page.403'));
$this->makeSubrequest($event, trim($path, '/'), Response::HTTP_FORBIDDEN);
}
/**
* {@inheritdoc}
*/
public function on404(GetResponseForExceptionEvent $event) {
$path = $this->aliasManager->getPathByAlias($this->configFactory->get('system.site')->get('page.404'));
$this->makeSubrequest($event, trim($path, '/'), Response::HTTP_NOT_FOUND);
}
}

View file

@ -0,0 +1,169 @@
<?php
/**
* @file
* Contains \Drupal\Core\EventSubscriber\DefaultExceptionHtmlSubscriber.
*/
namespace Drupal\Core\EventSubscriber;
use Drupal\Core\Routing\AccessAwareRouterInterface;
use Drupal\Core\Routing\RedirectDestinationInterface;
use Drupal\Core\Url;
use Drupal\Core\Utility\Error;
use Psr\Log\LoggerInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent;
use Symfony\Component\HttpKernel\HttpKernelInterface;
use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface;
/**
* Exception subscriber for handling core default HTML error pages.
*/
class DefaultExceptionHtmlSubscriber extends HttpExceptionSubscriberBase {
/**
* The HTTP kernel.
*
* @var \Symfony\Component\HttpKernel\HttpKernelInterface
*/
protected $httpKernel;
/**
* The logger instance.
*
* @var \Psr\Log\LoggerInterface
*/
protected $logger;
/**
* The redirect destination service.
*
* @var \Drupal\Core\Routing\RedirectDestinationInterface
*/
protected $redirectDestination;
/**
* Constructs a new DefaultExceptionHtmlSubscriber.
*
* @param \Symfony\Component\HttpKernel\HttpKernelInterface $http_kernel
* The HTTP kernel.
* @param \Psr\Log\LoggerInterface $logger
* The logger service.
* @param \Drupal\Core\Routing\RedirectDestinationInterface $redirect_destination
* The redirect destination service.
*/
public function __construct(HttpKernelInterface $http_kernel, LoggerInterface $logger, RedirectDestinationInterface $redirect_destination) {
$this->httpKernel = $http_kernel;
$this->logger = $logger;
$this->redirectDestination = $redirect_destination;
}
/**
* {@inheritdoc}
*/
protected static function getPriority() {
// A very low priority so that custom handlers are almost certain to fire
// before it, even if someone forgets to set a priority.
return -128;
}
/**
* {@inheritDoc}
*/
protected function getHandledFormats() {
return ['html'];
}
/**
* Handles a 401 error for HTML.
*
* @param \Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent $event
* The event to process.
*/
public function on401(GetResponseForExceptionEvent $event) {
$this->makeSubrequest($event, Url::fromRoute('system.401')->toString(), Response::HTTP_UNAUTHORIZED);
}
/**
* Handles a 403 error for HTML.
*
* @param \Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent $event
* The event to process.
*/
public function on403(GetResponseForExceptionEvent $event) {
$this->makeSubrequest($event, Url::fromRoute('system.403')->toString(), Response::HTTP_FORBIDDEN);
}
/**
* Handles a 404 error for HTML.
*
* @param \Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent $event
* The event to process.
*/
public function on404(GetResponseForExceptionEvent $event) {
$this->makeSubrequest($event, Url::fromRoute('system.404')->toString(), Response::HTTP_NOT_FOUND);
}
/**
* Makes a subrequest to retrieve the default error page.
*
* @param \Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent $event
* The event to process
* @param string $url
* The path/url to which to make a subrequest for this error message.
* @param int $status_code
* The status code for the error being handled.
*/
protected function makeSubrequest(GetResponseForExceptionEvent $event, $url, $status_code) {
$request = $event->getRequest();
$exception = $event->getException();
if (!($url && $url[0] == '/')) {
$url = $request->getBasePath() . '/' . $url;
}
$current_url = $request->getBasePath() . $request->getPathInfo();
if ($url != $request->getBasePath() . '/' && $url != $current_url) {
if ($request->getMethod() === 'POST') {
$sub_request = Request::create($url, 'POST', $this->redirectDestination->getAsArray() + ['_exception_statuscode' => $status_code] + $request->request->all(), $request->cookies->all(), [], $request->server->all());
}
else {
$sub_request = Request::create($url, 'GET', $request->query->all() + $this->redirectDestination->getAsArray() + ['_exception_statuscode' => $status_code], $request->cookies->all(), [], $request->server->all());
}
try {
// Persist the 'exception' attribute to the subrequest.
$sub_request->attributes->set('exception', $request->attributes->get('exception'));
// Persist the access result attribute to the subrequest, so that the
// error page inherits the access result of the master request.
$sub_request->attributes->set(AccessAwareRouterInterface::ACCESS_RESULT, $request->attributes->get(AccessAwareRouterInterface::ACCESS_RESULT));
// Carry over the session to the subrequest.
if ($session = $request->getSession()) {
$sub_request->setSession($session);
}
$response = $this->httpKernel->handle($sub_request, HttpKernelInterface::SUB_REQUEST);
$response->setStatusCode($status_code);
// Persist any special HTTP headers that were set on the exception.
if ($exception instanceof HttpExceptionInterface) {
$response->headers->add($exception->getHeaders());
}
$event->setResponse($response);
}
catch (\Exception $e) {
// If an error happened in the subrequest we can't do much else. Instead,
// just log it. The DefaultExceptionSubscriber will catch the original
// exception and handle it normally.
$error = Error::decodeException($e);
$this->logger->log($error['severity_level'], '%type: !message in %function (line %line of %file).', $error);
}
}
}
}

View file

@ -0,0 +1,232 @@
<?php
/**
* @file
* Contains \Drupal\Core\EventSubscriber\DefaultExceptionSubscriber.
*/
namespace Drupal\Core\EventSubscriber;
use Drupal\Component\Utility\SafeMarkup;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Render\BareHtmlPageRendererInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\Core\Utility\Error;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent;
use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface;
use Symfony\Component\HttpKernel\KernelEvents;
/**
* Last-chance handler for exceptions.
*
* This handler will catch any exceptions not caught elsewhere and report
* them as an error page.
*/
class DefaultExceptionSubscriber implements EventSubscriberInterface {
use StringTranslationTrait;
/**
* @var string
*
* One of the error level constants defined in bootstrap.inc.
*/
protected $errorLevel;
/**
* The config factory.
*
* @var \Drupal\Core\Config\ConfigFactoryInterface
*/
protected $configFactory;
/**
* The bare HTML page renderer.
*
* @var \Drupal\Core\Render\BareHtmlPageRendererInterface
*/
protected $bareHtmlPageRenderer;
/**
* Constructs a new DefaultExceptionSubscriber.
*
* @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
* The configuration factory.
* @param \Drupal\Core\Render\BareHtmlPageRendererInterface $bare_html_page_renderer
* The bare HTML page renderer.
*/
public function __construct(ConfigFactoryInterface $config_factory, BareHtmlPageRendererInterface $bare_html_page_renderer) {
$this->configFactory = $config_factory;
$this->bareHtmlPageRenderer = $bare_html_page_renderer;
}
/**
* Gets the configured error level.
*
* @return string
*/
protected function getErrorLevel() {
if (!isset($this->errorLevel)) {
$this->errorLevel = $this->configFactory->get('system.logging')->get('error_level');
}
return $this->errorLevel;
}
/**
* Handles any exception as a generic error page for HTML.
*
* @param \Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent $event
* The event to process.
*/
protected function onHtml(GetResponseForExceptionEvent $event) {
$exception = $event->getException();
$error = Error::decodeException($exception);
// Display the message if the current error reporting level allows this type
// of message to be displayed, and unconditionally in update.php.
if (error_displayable($error)) {
$class = 'error';
// If error type is 'User notice' then treat it as debug information
// instead of an error message.
// @see debug()
if ($error['%type'] == 'User notice') {
$error['%type'] = 'Debug';
$class = 'status';
}
// Attempt to reduce verbosity by removing DRUPAL_ROOT from the file path
// in the message. This does not happen for (false) security.
$root_length = strlen(DRUPAL_ROOT);
if (substr($error['%file'], 0, $root_length) == DRUPAL_ROOT) {
$error['%file'] = substr($error['%file'], $root_length + 1);
}
// Do not translate the string to avoid errors producing more errors.
unset($error['backtrace']);
$message = SafeMarkup::format('%type: !message in %function (line %line of %file).', $error);
// Check if verbose error reporting is on.
if ($this->getErrorLevel() == ERROR_REPORTING_DISPLAY_VERBOSE) {
$backtrace_exception = $exception;
while ($backtrace_exception->getPrevious()) {
$backtrace_exception = $backtrace_exception->getPrevious();
}
$backtrace = $backtrace_exception->getTrace();
// First trace is the error itself, already contained in the message.
// While the second trace is the error source and also contained in the
// message, the message doesn't contain argument values, so we output it
// once more in the backtrace.
array_shift($backtrace);
// Generate a backtrace containing only scalar argument values. Make
// sure the backtrace is escaped as it can contain user submitted data.
$message .= '<pre class="backtrace">' . SafeMarkup::escape(Error::formatBacktrace($backtrace)) . '</pre>';
}
drupal_set_message(SafeMarkup::set($message), $class, TRUE);
}
$content = $this->t('The website encountered an unexpected error. Please try again later.');
$response = $this->bareHtmlPageRenderer->renderBarePage(['#markup' => $content], $this->t('Error'), 'maintenance_page');
if ($exception instanceof HttpExceptionInterface) {
$response->setStatusCode($exception->getStatusCode());
$response->headers->add($exception->getHeaders());
}
else {
$response->setStatusCode(Response::HTTP_INTERNAL_SERVER_ERROR, '500 Service unavailable (with message)');
}
$event->setResponse($response);
}
/**
* Handles any exception as a generic error page for JSON.
*
* @todo This should probably check the error reporting level.
*
* @param \Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent $event
* The event to process.
*/
protected function onJson(GetResponseForExceptionEvent $event) {
$exception = $event->getException();
$error = Error::decodeException($exception);
// Display the message if the current error reporting level allows this type
// of message to be displayed,
$data = NULL;
if (error_displayable($error) && $message = $exception->getMessage()) {
$data = ['message' => sprintf('A fatal error occurred: %s', $message)];
}
$response = new JsonResponse($data, Response::HTTP_INTERNAL_SERVER_ERROR);
if ($exception instanceof HttpExceptionInterface) {
$response->setStatusCode($exception->getStatusCode());
$response->headers->add($exception->getHeaders());
}
$event->setResponse($response);
}
/**
* Handles errors for this subscriber.
*
* @param \Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent $event
* The event to process.
*/
public function onException(GetResponseForExceptionEvent $event) {
$format = $this->getFormat($event->getRequest());
// If it's an unrecognized format, assume HTML.
$method = 'on' . $format;
if (!method_exists($this, $method)) {
$method = 'onHtml';
}
$this->$method($event);
}
/**
* Gets the error-relevant format from the request.
*
* @param \Symfony\Component\HttpFoundation\Request $request
* The request object.
*
* @return string
* The format as which to treat the exception.
*/
protected function getFormat(Request $request) {
$format = $request->query->get(MainContentViewSubscriber::WRAPPER_FORMAT, $request->getRequestFormat());
// These are all JSON errors for our purposes. Any special handling for
// them can/should happen in earlier listeners if desired.
if (in_array($format, ['drupal_modal', 'drupal_dialog', 'drupal_ajax'])) {
$format = 'json';
}
// Make an educated guess that any Accept header type that includes "json"
// can probably handle a generic JSON response for errors. As above, for
// any format this doesn't catch or that wants custom handling should
// register its own exception listener.
foreach ($request->getAcceptableContentTypes() as $mime) {
if (strpos($mime, 'html') === FALSE && strpos($mime, 'json') !== FALSE) {
$format = 'json';
}
}
return $format;
}
/**
* Registers the methods in this class that should be listeners.
*
* @return array
* An array of event listener definitions.
*/
public static function getSubscribedEvents() {
$events[KernelEvents::EXCEPTION][] = ['onException', -256];
return $events;
}
}

View file

@ -0,0 +1,53 @@
<?php
/**
* @file
* Contains \Drupal\Core\EventSubscriber\EnforcedFormResponseSubscriber.
*/
namespace Drupal\Core\EventSubscriber;
use Drupal\Core\Form\EnforcedResponse;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent;
use Symfony\Component\HttpKernel\HttpKernelInterface;
use Symfony\Component\HttpKernel\KernelEvents;
/**
* Handle the EnforcedResponseException and deliver an EnforcedResponse.
*/
class EnforcedFormResponseSubscriber implements EventSubscriberInterface {
/**
* Replaces the response in case an EnforcedResponseException was thrown.
*/
public function onKernelException(GetResponseForExceptionEvent $event) {
if ($response = EnforcedResponse::createFromException($event->getException())) {
// Setting the response stops the event propagation.
$event->setResponse($response);
}
}
/**
* Unwraps an enforced response.
*/
public function onKernelResponse(FilterResponseEvent $event) {
$response = $event->getResponse();
if ($response instanceof EnforcedResponse && $event->getRequestType() === HttpKernelInterface::MASTER_REQUEST) {
$event->setResponse($response->getResponse());
}
}
/**
* {@inheritdoc}
*/
public static function getSubscribedEvents() {
$events[KernelEvents::EXCEPTION] = array('onKernelException', 128);
$events[KernelEvents::RESPONSE] = array('onKernelResponse', 128);
return $events;
}
}

View file

@ -0,0 +1,64 @@
<?php
/**
* @file
* Contains \Drupal\Core\EventSubscriber\EntityRouteAlterSubscriber.
*/
namespace Drupal\Core\EventSubscriber;
use Drupal\Core\Entity\EntityResolverManager;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Drupal\Core\Routing\RoutingEvents;
use Drupal\Core\Routing\RouteBuildEvent;
/**
* Registers the 'type' of route parameter names that match an entity type.
*
* @todo Matching on parameter *name* is not ideal, because it breaks
* encapsulation: parameter names are local to the controller and route, and
* controllers and routes can't be expected to know what all possible entity
* types might exist across all modules in order to pick names that don't
* conflict. Instead, the 'type' should be determined from introspecting what
* kind of PHP variable (e.g., a type hinted interface) the controller
* requires: https://www.drupal.org/node/2041907.
*/
class EntityRouteAlterSubscriber implements EventSubscriberInterface {
/**
* The entity resolver manager.
*
* @var \Drupal\Core\Entity\EntityResolverManager
*/
protected $resolverManager;
/**
* Constructs an EntityRouteAlterSubscriber instance.
*
* @param \Drupal\Core\Entity\EntityResolverManager
* The entity resolver manager.
*/
public function __construct(EntityResolverManager $entity_resolver_manager) {
$this->resolverManager = $entity_resolver_manager;
}
/**
* Applies parameter converters to route parameters.
*
* @param \Drupal\Core\Routing\RouteBuildEvent $event
* The event to process.
*/
public function onRoutingRouteAlterSetType(RouteBuildEvent $event) {
foreach ($event->getRouteCollection() as $route) {
$this->resolverManager->setRouteOptions($route);
}
}
/**
* {@inheritdoc}
*/
static function getSubscribedEvents() {
$events[RoutingEvents::ALTER][] = array('onRoutingRouteAlterSetType', -150);
return $events;
}
}

View file

@ -0,0 +1,74 @@
<?php
/**
* @file
* Contains \Drupal\Core\EventSubscriber\EntityRouteProviderSubscriber.
*/
namespace Drupal\Core\EventSubscriber;
use Drupal\Core\Entity\EntityManagerInterface;
use Drupal\Core\Routing\RouteBuildEvent;
use Drupal\Core\Routing\RoutingEvents;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Routing\RouteCollection;
/**
* Ensures that routes can be provided by entity types.
*/
class EntityRouteProviderSubscriber implements EventSubscriberInterface {
/**
* The entity manager.
*
* @var \Drupal\Core\Entity\EntityManagerInterface
*/
protected $entityManager;
/**
* Constructs a new EntityRouteProviderSubscriber instance.
*
* @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
* The entity manager.
*/
public function __construct(EntityManagerInterface $entity_manager) {
$this->entityManager = $entity_manager;
}
/**
* Provides routes on route rebuild time.
*
* @param \Drupal\Core\Routing\RouteBuildEvent $event
* The route build event.
*/
public function onDynamicRouteEvent(RouteBuildEvent $event) {
$route_collection = $event->getRouteCollection();
foreach ($this->entityManager->getDefinitions() as $entity_type) {
if ($entity_type->hasRouteProviders()) {
foreach ($this->entityManager->getRouteProviders($entity_type->id()) as $route_provider) {
// Allow to both return an array of routes or a route collection,
// like route_callbacks in the routing.yml file.
$routes = $route_provider->getRoutes($entity_type);
if ($routes instanceof RouteCollection) {
$route_collection->addCollection($routes);
}
elseif (is_array($routes)) {
foreach ($routes as $route_name => $route) {
$route_collection->add($route_name, $route);
}
}
}
}
}
}
/**
* {@inheritdoc}
*/
public static function getSubscribedEvents() {
$events[RoutingEvents::DYNAMIC][] = ['onDynamicRouteEvent'];
return $events;
}
}

View file

@ -0,0 +1,79 @@
<?php
/**
* @file
* Contains \Drupal\Core\EventSubscriber\ExceptionJsonSubscriber.
*/
namespace Drupal\Core\EventSubscriber;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent;
/**
* Default handling for JSON errors.
*/
class ExceptionJsonSubscriber extends HttpExceptionSubscriberBase {
/**
* {@inheritDoc}
*/
protected function getHandledFormats() {
return ['json'];
}
/**
* {@inheritdoc}
*/
protected static function getPriority() {
// This will fire after the most common HTML handler, since HTML requests
// are still more common than JSON requests.
return -75;
}
/**
* Handles a 403 error for JSON.
*
* @param \Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent $event
* The event to process.
*/
public function on403(GetResponseForExceptionEvent $event) {
$response = new JsonResponse(array('message' => $event->getException()->getMessage()), Response::HTTP_FORBIDDEN);
$event->setResponse($response);
}
/**
* Handles a 404 error for JSON.
*
* @param \Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent $event
* The event to process.
*/
public function on404(GetResponseForExceptionEvent $event) {
$response = new JsonResponse(array('message' => $event->getException()->getMessage()), Response::HTTP_NOT_FOUND);
$event->setResponse($response);
}
/**
* Handles a 405 error for JSON.
*
* @param \Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent $event
* The event to process.
*/
public function on405(GetResponseForExceptionEvent $event) {
$response = new JsonResponse(array('message' => $event->getException()->getMessage()), Response::HTTP_METHOD_NOT_ALLOWED);
$event->setResponse($response);
}
/**
* Handles a 406 error for JSON.
*
* @param \Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent $event
* The event to process.
*/
public function on406(GetResponseForExceptionEvent $event) {
$response = new JsonResponse(['message' => $event->getException()->getMessage()], Response::HTTP_NOT_ACCEPTABLE);
$event->setResponse($response);
}
}

View file

@ -0,0 +1,109 @@
<?php
/**
* @file
* Contains \Drupal\Core\EventSubscriber\ExceptionLoggingSubscriber.
*/
namespace Drupal\Core\EventSubscriber;
use Drupal\Component\Utility\SafeMarkup;
use Drupal\Core\Logger\LoggerChannelFactoryInterface;
use Drupal\Core\Utility\Error;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent;
use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface;
use Symfony\Component\HttpKernel\KernelEvents;
/**
* Log exceptions without further handling.
*/
class ExceptionLoggingSubscriber implements EventSubscriberInterface {
/**
* The logger channel factory.
*
* @var \Drupal\Core\Logger\LoggerChannelFactoryInterface
*/
protected $logger;
/**
* Constructs a new ExceptionLoggingSubscriber.
*
* @param \Drupal\Core\Logger\LoggerChannelFactoryInterface $logger
* The logger channel factory.
*/
public function __construct(LoggerChannelFactoryInterface $logger) {
$this->logger = $logger;
}
/**
* Log 403 errors.
*
* @param \Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent $event
* The event to process.
*/
public function on403(GetResponseForExceptionEvent $event) {
$request = $event->getRequest();
$this->logger->get('access denied')->warning(SafeMarkup::checkPlain($request->getRequestUri()));
}
/**
* Log 404 errors.
*
* @param \Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent $event
* The event to process.
*/
public function on404(GetResponseForExceptionEvent $event) {
$request = $event->getRequest();
$this->logger->get('page not found')->warning(SafeMarkup::checkPlain($request->getRequestUri()));
}
/**
* Log not-otherwise-specified errors, including HTTP 500.
*
* @param \Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent $event
* The event to process.
*/
public function onError(GetResponseForExceptionEvent $event) {
$exception = $event->getException();
$error = Error::decodeException($exception);
$this->logger->get('php')->log($error['severity_level'], '%type: !message in %function (line %line of %file).', $error);
$is_critical = !$exception instanceof HttpExceptionInterface || $exception->getStatusCode() >= 500;
if ($is_critical) {
error_log(sprintf('Uncaught PHP Exception %s: "%s" at %s line %s', get_class($exception), $exception->getMessage(), $exception->getFile(), $exception->getLine()));
}
}
/**
* Log all exceptions.
*
* @param \Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent $event
* The event to process.
*/
public function onException(GetResponseForExceptionEvent $event) {
$exception = $event->getException();
$method = 'onError';
// Treat any non-HTTP exception as if it were one, so we log it the same.
if ($exception instanceof HttpExceptionInterface) {
$possible_method = 'on' . $exception->getStatusCode();
if (method_exists($this, $possible_method)) {
$method = $possible_method;
}
}
$this->$method($event);
}
/**
* {@inheritdoc}
*/
public static function getSubscribedEvents() {
$events[KernelEvents::EXCEPTION][] = ['onException', 50];
return $events;
}
}

View file

@ -0,0 +1,67 @@
<?php
/**
* @file
* Contains \Drupal\Core\EventSubscriber\ExceptionTestSiteSubscriber.
*/
namespace Drupal\Core\EventSubscriber;
use Drupal\Core\Utility\Error;
use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent;
/**
* Custom handling of errors when in a system-under-test.
*/
class ExceptionTestSiteSubscriber extends HttpExceptionSubscriberBase {
/**
* {@inheritdoc}
*/
protected static function getPriority() {
return 3;
}
/**
* {@inheritDoc}
*/
protected function getHandledFormats() {
return ['html'];
}
/**
* Checks for special handling of errors inside Simpletest.
*
* @todo The $headers array appears to not actually get used at all in the
* original code. It's quite possible that this entire method is now
* vestigial and can be removed.
*
* @param \Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent $event
*/
public function on500(GetResponseForExceptionEvent $event) {
$exception = $event->getException();
$error = Error::decodeException($exception);
$headers = array();
// When running inside the testing framework, we relay the errors
// to the tested site by the way of HTTP headers.
if (DRUPAL_TEST_IN_CHILD_SITE && !headers_sent() && (!defined('SIMPLETEST_COLLECT_ERRORS') || SIMPLETEST_COLLECT_ERRORS)) {
// $number does not use drupal_static as it should not be reset
// as it uniquely identifies each PHP error.
static $number = 0;
$assertion = array(
$error['!message'],
$error['%type'],
array(
'function' => $error['%function'],
'file' => $error['%file'],
'line' => $error['%line'],
),
);
$headers['X-Drupal-Assertion-' . $number] = rawurlencode(serialize($assertion));
$number++;
}
}
}

View file

@ -0,0 +1,89 @@
<?php
/**
* @file
* Contains \Drupal\Core\EventSubscriber\Fast404ExceptionHtmlSubscriber.
*/
namespace Drupal\Core\EventSubscriber;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Component\Utility\SafeMarkup;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent;
use Symfony\Component\HttpKernel\HttpKernelInterface;
/**
* High-performance 404 exception subscriber.
*
* This subscriber will return a minimalist 404 response for HTML requests
* without running a full page theming operation.
*/
class Fast404ExceptionHtmlSubscriber extends HttpExceptionSubscriberBase {
/**
* The HTTP kernel.
*
* @var \Symfony\Component\HttpKernel\HttpKernelInterface
*/
protected $httpKernel;
/**
* The config factory.
*
* @var \Drupal\Core\Config\ConfigFactoryInterface
*/
protected $configFactory;
/**
* Constructs a new Fast404ExceptionHtmlSubscriber.
*
* @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
* The configuration factory.
* @param \Symfony\Component\HttpKernel\HttpKernelInterface $http_kernel
* The HTTP Kernel service.
*/
public function __construct(ConfigFactoryInterface $config_factory, HttpKernelInterface $http_kernel) {
$this->configFactory = $config_factory;
$this->httpKernel = $http_kernel;
}
/**
* {@inheritdoc}
*/
protected static function getPriority() {
// A very high priority so that it can take precedent over anything else,
// and thus be fast.
return 200;
}
/**
* {@inheritDoc}
*/
protected function getHandledFormats() {
return ['html'];
}
/**
* Handles a 404 error for HTML.
*
* @param \Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent $event
* The event to process.
*/
public function on404(GetResponseForExceptionEvent $event) {
$request = $event->getRequest();
$config = $this->configFactory->get('system.performance');
$exclude_paths = $config->get('fast_404.exclude_paths');
if ($config->get('fast_404.enabled') && $exclude_paths && !preg_match($exclude_paths, $request->getPathInfo())) {
$fast_paths = $config->get('fast_404.paths');
if ($fast_paths && preg_match($fast_paths, $request->getPathInfo())) {
$fast_404_html = strtr($config->get('fast_404.html'), ['@path' => SafeMarkup::checkPlain($request->getUri())]);
$response = new Response($fast_404_html, Response::HTTP_NOT_FOUND);
$event->setResponse($response);
}
}
}
}

View file

@ -0,0 +1,268 @@
<?php
/**
* @file
* Contains \Drupal\Core\EventSubscriber\FinishResponseSubscriber.
*/
namespace Drupal\Core\EventSubscriber;
use Drupal\Component\Datetime\DateTimePlus;
use Drupal\Core\Cache\CacheableResponseInterface;
use Drupal\Core\Cache\Context\CacheContextsManager;
use Drupal\Core\Config\Config;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Language\LanguageManagerInterface;
use Drupal\Core\PageCache\RequestPolicyInterface;
use Drupal\Core\PageCache\ResponsePolicyInterface;
use Drupal\Core\Site\Settings;
use Symfony\Component\HttpFoundation\BinaryFileResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\StreamedResponse;
use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\HttpKernel\HttpKernelInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
/**
* Response subscriber to handle finished responses.
*/
class FinishResponseSubscriber implements EventSubscriberInterface {
/**
* The language manager object for retrieving the correct language code.
*
* @var \Drupal\Core\Language\LanguageManagerInterface
*/
protected $languageManager;
/**
* A config object for the system performance configuration.
*
* @var \Drupal\Core\Config\Config
*/
protected $config;
/**
* A policy rule determining the cacheability of a request.
*
* @var \Drupal\Core\PageCache\RequestPolicyInterface
*/
protected $requestPolicy;
/**
* A policy rule determining the cacheability of the response.
*
* @var \Drupal\Core\PageCache\ResponsePolicyInterface
*/
protected $responsePolicy;
/**
* The cache contexts manager service.
*
* @var \Drupal\Core\Cache\Context\CacheContextsManager
*/
protected $cacheContexts;
/**
* Constructs a FinishResponseSubscriber object.
*
* @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
* The language manager object for retrieving the correct language code.
* @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
* A config factory for retrieving required config objects.
* @param \Drupal\Core\PageCache\RequestPolicyInterface $request_policy
* A policy rule determining the cacheability of a request.
* @param \Drupal\Core\PageCache\ResponsePolicyInterface $response_policy
* A policy rule determining the cacheability of a response.
* @param \Drupal\Core\Cache\Context\CacheContextsManager $cache_contexts_manager
* The cache contexts manager service.
*/
public function __construct(LanguageManagerInterface $language_manager, ConfigFactoryInterface $config_factory, RequestPolicyInterface $request_policy, ResponsePolicyInterface $response_policy, CacheContextsManager $cache_contexts_manager) {
$this->languageManager = $language_manager;
$this->config = $config_factory->get('system.performance');
$this->requestPolicy = $request_policy;
$this->responsePolicy = $response_policy;
$this->cacheContextsManager = $cache_contexts_manager;
}
/**
* Sets extra headers on successful responses.
*
* @param Symfony\Component\HttpKernel\Event\FilterResponseEvent $event
* The event to process.
*/
public function onRespond(FilterResponseEvent $event) {
if (!$event->isMasterRequest()) {
return;
}
$request = $event->getRequest();
$response = $event->getResponse();
// Set the X-UA-Compatible HTTP header to force IE to use the most recent
// rendering engine.
$response->headers->set('X-UA-Compatible', 'IE=edge', FALSE);
// Set the Content-language header.
$response->headers->set('Content-language', $this->languageManager->getCurrentLanguage()->getId());
// Prevent browsers from sniffing a response and picking a MIME type
// different from the declared content-type, since that can lead to
// XSS and other vulnerabilities.
// https://www.owasp.org/index.php/List_of_useful_HTTP_headers
$response->headers->set('X-Content-Type-Options', 'nosniff', FALSE);
// Expose the cache contexts and cache tags associated with this page in a
// X-Drupal-Cache-Contexts and X-Drupal-Cache-Tags header respectively.
if ($response instanceof CacheableResponseInterface) {
$response_cacheability = $response->getCacheableMetadata();
$response->headers->set('X-Drupal-Cache-Tags', implode(' ', $response_cacheability->getCacheTags()));
$response->headers->set('X-Drupal-Cache-Contexts', implode(' ', $this->cacheContextsManager->optimizeTokens($response_cacheability->getCacheContexts())));
}
$is_cacheable = ($this->requestPolicy->check($request) === RequestPolicyInterface::ALLOW) && ($this->responsePolicy->check($response, $request) !== ResponsePolicyInterface::DENY);
// Add headers necessary to specify whether the response should be cached by
// proxies and/or the browser.
if ($is_cacheable && $this->config->get('cache.page.max_age') > 0) {
if (!$this->isCacheControlCustomized($response)) {
// Only add the default Cache-Control header if the controller did not
// specify one on the response.
$this->setResponseCacheable($response, $request);
}
}
else {
// If either the policy forbids caching or the sites configuration does
// not allow to add a max-age directive, then enforce a Cache-Control
// header declaring the response as not cacheable.
$this->setResponseNotCacheable($response, $request);
}
}
/**
* Determine whether the given response has a custom Cache-Control header.
*
* Upon construction, the ResponseHeaderBag is initialized with an empty
* Cache-Control header. Consequently it is not possible to check whether the
* header was set explicitly by simply checking its presence. Instead, it is
* necessary to examine the computed Cache-Control header and compare with
* values known to be present only when Cache-Control was never set
* explicitly.
*
* When neither Cache-Control nor any of the ETag, Last-Modified, Expires
* headers are set on the response, ::get('Cache-Control') returns the value
* 'no-cache'. If any of ETag, Last-Modified or Expires are set but not
* Cache-Control, then 'private, must-revalidate' (in exactly this order) is
* returned.
*
* @see \Symfony\Component\HttpFoundation\ResponseHeaderBag::computeCacheControlValue()
*
* @param \Symfony\Component\HttpFoundation\Response $response
*
* @return bool
* TRUE when Cache-Control header was set explicitly on the given response.
*/
protected function isCacheControlCustomized(Response $response) {
$cache_control = $response->headers->get('Cache-Control');
return $cache_control != 'no-cache' && $cache_control != 'private, must-revalidate';
}
/**
* Add Cache-Control and Expires headers to a response which is not cacheable.
*
* @param \Symfony\Component\HttpFoundation\Response $response
* A response object.
* @param \Symfony\Component\HttpFoundation\Request $request
* A request object.
*/
protected function setResponseNotCacheable(Response $response, Request $request) {
$this->setCacheControlNoCache($response);
$this->setExpiresNoCache($response);
// There is no point in sending along headers necessary for cache
// revalidation, if caching by proxies and browsers is denied in the first
// place. Therefore remove ETag, Last-Modified and Vary in that case.
$response->setEtag(NULL);
$response->setLastModified(NULL);
$response->setVary(NULL);
}
/**
* Add Cache-Control and Expires headers to a cacheable response.
*
* @param \Symfony\Component\HttpFoundation\Response $response
* A response object.
* @param \Symfony\Component\HttpFoundation\Request $request
* A request object.
*/
protected function setResponseCacheable(Response $response, Request $request) {
// HTTP/1.0 proxies do not support the Vary header, so prevent any caching
// by sending an Expires date in the past. HTTP/1.1 clients ignore the
// Expires header if a Cache-Control: max-age directive is specified (see
// RFC 2616, section 14.9.3).
if (!$response->headers->has('Expires')) {
$this->setExpiresNoCache($response);
}
$max_age = $this->config->get('cache.page.max_age');
$response->headers->set('Cache-Control', 'public, max-age=' . $max_age);
// In order to support HTTP cache-revalidation, ensure that there is a
// Last-Modified and an ETag header on the response.
if (!$response->headers->has('Last-Modified')) {
$timestamp = REQUEST_TIME;
$response->setLastModified(new \DateTime(gmdate(DateTimePlus::RFC7231, REQUEST_TIME)));
}
else {
$timestamp = $response->getLastModified()->getTimestamp();
}
$response->setEtag($timestamp);
// Allow HTTP proxies to cache pages for anonymous users without a session
// cookie. The Vary header is used to indicates the set of request-header
// fields that fully determines whether a cache is permitted to use the
// response to reply to a subsequent request for a given URL without
// revalidation.
if (!$response->hasVary() && !Settings::get('omit_vary_cookie')) {
$response->setVary('Cookie', FALSE);
}
}
/**
* Disable caching in the browser and for HTTP/1.1 proxies and clients.
*
* @param \Symfony\Component\HttpFoundation\Response $response
* A response object.
*/
protected function setCacheControlNoCache(Response $response) {
$response->headers->set('Cache-Control', 'no-cache, must-revalidate, post-check=0, pre-check=0');
}
/**
* Disable caching in ancient browsers and for HTTP/1.0 proxies and clients.
*
* HTTP/1.0 proxies do not support the Vary header, so prevent any caching by
* sending an Expires date in the past. HTTP/1.1 clients ignore the Expires
* header if a Cache-Control: max-age= directive is specified (see RFC 2616,
* section 14.9.3).
*
* @param \Symfony\Component\HttpFoundation\Response $response
* A response object.
*/
protected function setExpiresNoCache(Response $response) {
$response->setExpires(\DateTime::createFromFormat('j-M-Y H:i:s T', '19-Nov-1978 05:00:00 UTC'));
}
/**
* Registers the methods in this class that should be listeners.
*
* @return array
* An array of event listener definitions.
*/
public static function getSubscribedEvents() {
$events[KernelEvents::RESPONSE][] = array('onRespond');
return $events;
}
}

View file

@ -0,0 +1,65 @@
<?php
/**
* @file
* Contains \Drupal\Core\EventSubscriber\HtmlResponseSubscriber.
*/
namespace Drupal\Core\EventSubscriber;
use Drupal\Core\Render\HtmlResponse;
use Drupal\Core\Render\AttachmentsResponseProcessorInterface;
use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
/**
* Response subscriber to handle HTML responses.
*/
class HtmlResponseSubscriber implements EventSubscriberInterface {
/**
* The HTML response attachments processor service.
*
* @var \Drupal\Core\Render\AttachmentsResponseProcessorInterface
*/
protected $htmlResponseAttachmentsProcessor;
/**
* Constructs a HtmlResponseSubscriber object.
*
* @param \Drupal\Core\Render\AttachmentsResponseProcessorInterface $html_response_attachments_processor
* The HTML response attachments processor service.
*/
public function __construct(AttachmentsResponseProcessorInterface $html_response_attachments_processor) {
$this->htmlResponseAttachmentsProcessor = $html_response_attachments_processor;
}
/**
* Processes attachments for HtmlResponse responses.
*
* @param \Symfony\Component\HttpKernel\Event\FilterResponseEvent $event
* The event to process.
*/
public function onRespond(FilterResponseEvent $event) {
if (!$event->isMasterRequest()) {
return;
}
$response = $event->getResponse();
if (!$response instanceof HtmlResponse) {
return;
}
$event->setResponse($this->htmlResponseAttachmentsProcessor->processAttachments($response));
}
/**
* {@inheritdoc}
*/
public static function getSubscribedEvents() {
$events[KernelEvents::RESPONSE][] = ['onRespond'];
return $events;
}
}

View file

@ -0,0 +1,119 @@
<?php
/**
* @file
* Contains \Drupal\Core\EventSubscriber\HttpExceptionSubscriberBase.
*/
namespace Drupal\Core\EventSubscriber;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent;
use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface;
use Symfony\Component\HttpKernel\KernelEvents;
/**
* Utility base class for exception subscribers.
*
* A subscriber may extend this class and implement getHandledFormats() to
* indicate which request formats it will respond to. Then implement an on*()
* method for any error code (HTTP response code) that should be handled. For
* example, to handle 404 Not Found messages add a method:
*
* @code
* public function on404(GetResponseForExceptionEvent $event) {}
* @endcode
*
* That method should then call $event->setResponse() to set the response object
* for the exception. Alternatively, it may opt not to do so and then other
* listeners will have the opportunity to handle the exception.
*
* Note: Core provides several important exception listeners by default. In most
* cases, setting the priority of a contrib listener to the default of 0 will
* do what you expect and handle the exceptions you'd expect it to handle.
* If a custom priority is set, be aware of the following core-registered
* listeners.
*
* - Fast404ExceptionHtmlSubscriber: 200. This subscriber will return a
* minimalist, high-performance 404 page for HTML requests. It is not
* recommended to have a priority higher than this one as it will only slow
* down that use case.
* - ExceptionLoggingSubscriber: 50. This subscriber logs all exceptions but
* does not handle them. Do not register a listener with a higher priority
* unless you want exceptions to not get logged, which makes debugging more
* difficult.
* - DefaultExceptionSubscriber: -256. The subscriber of last resort, this will
* provide generic handling for any exception. A listener with a lower
* priority will never get called.
*
* All other core-provided exception handlers have negative priorities so most
* module-provided listeners will naturally take precedence over them.
*/
abstract class HttpExceptionSubscriberBase implements EventSubscriberInterface {
/**
* Specifies the request formats this subscriber will respond to.
*
* @return array
* An indexed array of the format machine names that this subscriber will
* attempt ot process,such as "html" or "json". Returning an empty array
* will apply to all formats.
*
* @see \Symfony\Component\HttpFoundation\Request
*/
abstract protected function getHandledFormats();
/**
* Specifies the priority of all listeners in this class.
*
* The default priority is 1, which is very low. To have listeners that have
* a "first attempt" at handling exceptions return a higher priority.
*
* @return int
* The event priority of this subscriber.
*/
protected static function getPriority() {
return 0;
}
/**
* Handles errors for this subscriber.
*
* @param \Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent $event
* The event to process.
*/
public function onException(GetResponseForExceptionEvent $event) {
$exception = $event->getException();
// Make the exception available for example when rendering a block.
$request = $event->getRequest();
$request->attributes->set('exception', $exception);
$handled_formats = $this->getHandledFormats();
$format = $request->query->get(MainContentViewSubscriber::WRAPPER_FORMAT, $request->getRequestFormat());
if ($exception instanceof HttpExceptionInterface && (empty($handled_formats) || in_array($format, $handled_formats))) {
$method = 'on' . $exception->getStatusCode();
// We want to allow the method to be called and still not set a response
// if it has additional filtering logic to determine when it will apply.
// It is therefore the method's responsibility to set the response on the
// event if appropriate.
if (method_exists($this, $method)) {
$this->$method($event);
}
}
}
/**
* Registers the methods in this class that should be listeners.
*
* @return array
* An array of event listener definitions.
*/
public static function getSubscribedEvents() {
$events[KernelEvents::EXCEPTION][] = ['onException', static::getPriority()];
return $events;
}
}

View file

@ -0,0 +1,70 @@
<?php
/**
* @file
* Contains \Drupal\Core\EventSubscriber\KernelDestructionSubscriber.
*/
namespace Drupal\Core\EventSubscriber;
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
use Symfony\Component\DependencyInjection\ContainerAwareTrait;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\PostResponseEvent;
use Symfony\Component\HttpKernel\KernelEvents;
/**
* Destructs services that are initiated and tagged with "needs_destruction".
*
* @see \Drupal\Core\DestructableInterface
*/
class KernelDestructionSubscriber implements EventSubscriberInterface, ContainerAwareInterface {
use ContainerAwareTrait;
/**
* Holds an array of service ID's that will require destruction.
*
* @var array
*/
protected $services = array();
/**
* Registers a service for destruction.
*
* Calls to this method are set up in
* RegisterServicesForDestructionPass::process().
*
* @param string $id
* Name of the service.
*/
public function registerService($id) {
$this->services[] = $id;
}
/**
* Invoked by the terminate kernel event.
*
* @param \Symfony\Component\HttpKernel\Event\PostResponseEvent $event
* The event object.
*/
public function onKernelTerminate(PostResponseEvent $event) {
foreach ($this->services as $id) {
// Check if the service was initialized during this request, destruction
// is not necessary if the service was not used.
if ($this->container->initialized($id)) {
$service = $this->container->get($id);
$service->destruct();
}
}
}
/**
* Registers the methods in this class that should be listeners.
*
* @return array
* An array of event listener definitions.
*/
static function getSubscribedEvents() {
$events[KernelEvents::TERMINATE][] = array('onKernelTerminate', 100);
return $events;
}
}

View file

@ -0,0 +1,106 @@
<?php
/**
* @file
* Contains \Drupal\Core\EventSubscriber\MainContentViewSubscriber.
*/
namespace Drupal\Core\EventSubscriber;
use Drupal\Core\DependencyInjection\ClassResolverInterface;
use Drupal\Core\Routing\RouteMatchInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\GetResponseForControllerResultEvent;
use Symfony\Component\HttpKernel\KernelEvents;
/**
* View subscriber rendering main content render arrays into responses.
*
* Additional target rendering formats can be defined by adding another service
* that implements \Drupal\Core\Render\MainContent\MainContentRendererInterface
* and tagging it as a @code render.main_content_renderer @endcode, then
* \Drupal\Core\Render\MainContent\MainContentRenderersPass will detect it and
* use it when appropriate.
*
* @see \Drupal\Core\Render\MainContent\MainContentRendererInterface
* @see \Drupal\Core\Render\MainContentControllerPass
*/
class MainContentViewSubscriber implements EventSubscriberInterface {
/**
* The class resolver service.
*
* @var \Drupal\Core\Controller\ControllerResolverInterface
*/
protected $classResolver;
/**
* The current route match.
*
* @var \Drupal\Core\Routing\RouteMatchInterface
*/
protected $routeMatch;
/**
* The available main content renderer services, keyed per format.
*
* @var array
*/
protected $mainContentRenderers;
/**
* URL query attribute to indicate the wrapper used to render a request.
*
* The wrapper format determines how the HTML is wrapped, for example in a
* modal dialog.
*/
const WRAPPER_FORMAT = '_wrapper_format';
/**
* Constructs a new MainContentViewSubscriber object.
*
* @param \Drupal\Core\DependencyInjection\ClassResolverInterface $class_resolver
* The class resolver service.
* @param \Drupal\Core\Routing\RouteMatchInterface $route_match
* The current route match.
* @param array $main_content_renderers
* The available main content renderer service IDs, keyed by format.
*/
public function __construct(ClassResolverInterface $class_resolver, RouteMatchInterface $route_match, array $main_content_renderers) {
$this->classResolver = $class_resolver;
$this->routeMatch = $route_match;
$this->mainContentRenderers = $main_content_renderers;
}
/**
* Sets a response given a (main content) render array.
*
* @param \Symfony\Component\HttpKernel\Event\GetResponseForControllerResultEvent $event
* The event to process.
*/
public function onViewRenderArray(GetResponseForControllerResultEvent $event) {
$request = $event->getRequest();
$result = $event->getControllerResult();
// Render the controller result into a response if it's a render array.
if (is_array($result) && ($request->query->has(static::WRAPPER_FORMAT) || $request->getRequestFormat() == 'html')) {
$wrapper = $request->query->get(static::WRAPPER_FORMAT, 'html');
// Fall back to HTML if the requested wrapper envelope is not available.
$wrapper = isset($this->mainContentRenderers[$wrapper]) ? $wrapper : 'html';
$renderer = $this->classResolver->getInstanceFromDefinition($this->mainContentRenderers[$wrapper]);
$event->setResponse($renderer->renderResponse($result, $request, $this->routeMatch));
}
}
/**
* {@inheritdoc}
*/
static function getSubscribedEvents() {
$events[KernelEvents::VIEW][] = ['onViewRenderArray'];
return $events;
}
}

View file

@ -0,0 +1,145 @@
<?php
/**
* @file
* Contains \Drupal\Core\EventSubscriber\MaintenanceModeSubscriber.
*/
namespace Drupal\Core\EventSubscriber;
use Drupal\Component\Utility\SafeMarkup;
use Drupal\Component\Utility\Xss;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Render\BareHtmlPageRendererInterface;
use Drupal\Core\Routing\RouteMatch;
use Drupal\Core\Routing\UrlGeneratorInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\Site\MaintenanceModeInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\Core\StringTranslation\TranslationInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\HttpKernel\KernelEvents;
/**
* Maintenance mode subscriber for controller requests.
*/
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;
/**
* 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.
*/
public function __construct(MaintenanceModeInterface $maintenance_mode, ConfigFactoryInterface $config_factory, TranslationInterface $translation, UrlGeneratorInterface $url_generator, AccountInterface $account, BareHtmlPageRendererInterface $bare_html_page_renderer) {
$this->maintenanceMode = $maintenance_mode;
$this->config = $config_factory;
$this->stringTranslation = $translation;
$this->urlGenerator = $url_generator;
$this->account = $account;
$this->bareHtmlPageRenderer = $bare_html_page_renderer;
}
/**
* Returns the site maintenance page if the site is offline.
*
* @param \Symfony\Component\HttpKernel\Event\GetResponseEvent $event
* The event to process.
*/
public function onKernelRequestMaintenance(GetResponseEvent $event) {
$route_match = RouteMatch::createFromRequest($event->getRequest());
if ($this->maintenanceMode->applies($route_match)) {
// Don't cache maintenance mode pages.
\Drupal::service('page_cache_kill_switch')->trigger();
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(SafeMarkup::format($this->config->get('system.maintenance')->get('message'), array(
'@site' => $this->config->get('system.site')->get('name'),
)));
$response = $this->bareHtmlPageRenderer->renderBarePage(['#markup' => $content], $this->t('Site under maintenance'), 'maintenance_page');
$response->setStatusCode(503);
$event->setResponse($response);
}
else {
// 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);
}
else {
$this->drupalSetMessage($this->t('Operating in maintenance mode.'), 'status', FALSE);
}
}
}
}
}
/**
* Wraps the drupal_set_message function.
*/
protected function drupalSetMessage($message = NULL, $type = 'status', $repeat = FALSE) {
return drupal_set_message($message, $type, $repeat);
}
/**
* {@inheritdoc}
*/
public static function getSubscribedEvents() {
$events[KernelEvents::REQUEST][] = array('onKernelRequestMaintenance', 30);
$events[KernelEvents::EXCEPTION][] = array('onKernelRequestMaintenance');
return $events;
}
}

View file

@ -0,0 +1,94 @@
<?php
/**
* @file
* Contains \Drupal\Core\EventSubscriber\MenuRouterRebuildSubscriber.
*/
namespace Drupal\Core\EventSubscriber;
use Drupal\Core\Cache\Cache;
use Drupal\Core\Lock\LockBackendInterface;
use Drupal\Core\Menu\MenuLinkManagerInterface;
use Drupal\Core\Routing\RoutingEvents;
use Symfony\Component\EventDispatcher\Event;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
/**
* Rebuilds the default menu links and runs menu-specific code if necessary.
*/
class MenuRouterRebuildSubscriber implements EventSubscriberInterface {
/**
* @var \Drupal\Core\Lock\LockBackendInterface
*/
protected $lock;
/**
* The menu link plugin manager.
*
* @var \Drupal\Core\Menu\MenuLinkManagerInterface $menuLinkManager
*/
protected $menuLinkManager;
/**
* Constructs the MenuRouterRebuildSubscriber object.
*
* @param \Drupal\Core\Lock\LockBackendInterface $lock
* The lock backend.
* @param \Drupal\Core\Menu\MenuLinkManagerInterface $menu_link_manager
* The menu link plugin manager.
*/
public function __construct(LockBackendInterface $lock, MenuLinkManagerInterface $menu_link_manager) {
$this->lock = $lock;
$this->menuLinkManager = $menu_link_manager;
}
/**
* Rebuilds the menu links and deletes the local_task cache tag.
*
* @param \Symfony\Component\EventDispatcher\Event $event
* The event object.
*/
public function onRouterRebuild(Event $event) {
$this->menuLinksRebuild();
Cache::invalidateTags(array('local_task'));
}
/**
* Perform menu-specific rebuilding.
*/
protected function menuLinksRebuild() {
if ($this->lock->acquire(__FUNCTION__)) {
$transaction = db_transaction();
try {
// Ensure the menu links are up to date.
$this->menuLinkManager->rebuild();
// Ignore any database replicas temporarily.
db_ignore_replica();
}
catch (\Exception $e) {
$transaction->rollback();
watchdog_exception('menu', $e);
}
$this->lock->release(__FUNCTION__);
}
else {
// Wait for another request that is already doing this work.
// We choose to block here since otherwise the router item may not
// be available during routing resulting in a 404.
$this->lock->wait(__FUNCTION__);
}
}
/**
* {@inheritdoc}
*/
static function getSubscribedEvents() {
// Run after CachedRouteRebuildSubscriber.
$events[RoutingEvents::FINISHED][] = array('onRouterRebuild', 100);
return $events;
}
}

View file

@ -0,0 +1,84 @@
<?php
/**
* @file
* Contains \Drupal\Core\EventSubscriber\ModuleRouteSubscriber.
*/
namespace Drupal\Core\EventSubscriber;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Routing\RouteSubscriberBase;
use Symfony\Component\Routing\RouteCollection;
/**
* A route subscriber to remove routes that depend on modules being enabled.
*/
class ModuleRouteSubscriber extends RouteSubscriberBase {
/**
* The module handler.
*
* @var \Drupal\Core\Extension\ModuleHandlerInterface
*/
protected $moduleHandler;
/**
* Constructs a ModuleRouteSubscriber object.
*
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
* The module handler.
*/
public function __construct(ModuleHandlerInterface $module_handler) {
$this->moduleHandler = $module_handler;
}
/**
* {@inheritdoc}
*/
protected function alterRoutes(RouteCollection $collection) {
foreach ($collection as $name => $route) {
if ($route->hasRequirement('_module_dependencies')) {
$modules = $route->getRequirement('_module_dependencies');
$explode_and = $this->explodeString($modules, '+');
if (count($explode_and) > 1) {
foreach ($explode_and as $module) {
// If any moduleExists() call returns FALSE, remove the route and
// move on to the next.
if (!$this->moduleHandler->moduleExists($module)) {
$collection->remove($name);
continue 2;
}
}
}
else {
// OR condition, exploding on ',' character.
foreach ($this->explodeString($modules, ',') as $module) {
if ($this->moduleHandler->moduleExists($module)) {
continue 2;
}
}
// If no modules are found, and we get this far, remove the route.
$collection->remove($name);
}
}
}
}
/**
* Explodes a string based on a separator.
*
* @param string $string
* The string to explode.
* @param string $separator
* The string separator to explode with.
*
* @return array
* An array of exploded (and trimmed) values.
*/
protected function explodeString($string, $separator = ',') {
return array_filter(array_map('trim', explode($separator, $string)));
}
}

View file

@ -0,0 +1,56 @@
<?php
/**
* @file
* Contains \Drupal\Core\EventSubscriber\ParamConverterSubscriber.
*/
namespace Drupal\Core\EventSubscriber;
use Drupal\Core\ParamConverter\ParamConverterManagerInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Drupal\Core\Routing\RoutingEvents;
use Drupal\Core\Routing\RouteBuildEvent;
/**
* Event subscriber for registering parameter converters with routes.
*/
class ParamConverterSubscriber implements EventSubscriberInterface {
/**
* The parameter converter manager.
*
* @var \Drupal\Core\ParamConverter\ParamConverterManagerInterface
*/
protected $paramConverterManager;
/**
* Constructs a new ParamConverterSubscriber.
*
* @param \Drupal\Core\ParamConverter\ParamConverterManagerInterface $param_converter_manager
* The parameter converter manager that will be responsible for upcasting
* request attributes.
*/
public function __construct(ParamConverterManagerInterface $param_converter_manager) {
$this->paramConverterManager = $param_converter_manager;
}
/**
* Applies parameter converters to route parameters.
*
* @param \Drupal\Core\Routing\RouteBuildEvent $event
* The event to process.
*/
public function onRoutingRouteAlterSetParameterConverters(RouteBuildEvent $event) {
$this->paramConverterManager->setRouteParameterConverters($event->getRouteCollection());
}
/**
* {@inheritdoc}
*/
static function getSubscribedEvents() {
// Run after \Drupal\system\EventSubscriber\AdminRouteSubscriber.
$events[RoutingEvents::ALTER][] = array('onRoutingRouteAlterSetParameterConverters', -220);
return $events;
}
}

View file

@ -0,0 +1,81 @@
<?php
/**
* @file
* Contains \Drupal\Core\EventSubscriber\PathRootsSubscriber.
*/
namespace Drupal\Core\EventSubscriber;
use Drupal\Core\State\StateInterface;
use Drupal\Core\Routing\RouteBuildEvent;
use Drupal\Core\Routing\RoutingEvents;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Routing\RouteCollection;
/**
* Provides all available first bits of all route paths.
*/
class PathRootsSubscriber implements EventSubscriberInterface {
/**
* Stores the path roots available in the router.
*
* A path root is the first virtual directory of a path, like 'admin', 'node'
* or 'user'.
*
* @var array
*/
protected $pathRoots;
/**
* The state key value store.
*
* @var \Drupal\Core\State\StateInterface
*/
protected $state;
/**
* Constructs a new PathRootsSubscriber instance.
*
* @param \Drupal\Core\State\StateInterface $state
* The state key value store.
*/
public function __construct(StateInterface $state) {
$this->state = $state;
}
/**
* Collects all path roots.
*
* @param \Drupal\Core\Routing\RouteBuildEvent $event
* The route build event.
*/
public function onRouteAlter(RouteBuildEvent $event) {
$collection = $event->getRouteCollection();
foreach ($collection->all() as $route) {
$bits = explode('/', ltrim($route->getPath(), '/'));
$this->pathRoots[$bits[0]] = $bits[0];
}
}
/**
* {@inheritdoc}
*/
public function onRouteFinished() {
$this->state->set('router.path_roots', array_keys($this->pathRoots));
unset($this->pathRoots);
}
/**
* {@inheritdoc}
*/
public static function getSubscribedEvents() {
$events = array();
// Try to set a low priority to ensure that all routes are already added.
$events[RoutingEvents::ALTER][] = array('onRouteAlter', -1024);
$events[RoutingEvents::FINISHED][] = array('onRouteFinished');
return $events;
}
}

View file

@ -0,0 +1,83 @@
<?php
/**
* @file
* Contains \Drupal\Core\EventSubscriber\PathSubscriber.
*/
namespace Drupal\Core\EventSubscriber;
use Drupal\Core\Path\AliasManagerInterface;
use Drupal\Core\Path\CurrentPathStack;
use Symfony\Component\HttpKernel\HttpKernelInterface;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\HttpKernel\Event\FilterControllerEvent;
use Symfony\Component\HttpKernel\Event\PostResponseEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
/**
* Provides a path subscriber that converts path aliases.
*/
class PathSubscriber implements EventSubscriberInterface {
/**
* The alias manager that caches alias lookups based on the request.
*
* @var \Drupal\Core\Path\AliasManagerInterface
*/
protected $aliasManager;
/**
* The current path.
*
* @var \Drupal\Core\Path\CurrentPathStack
*/
protected $currentPath;
/**
* Constructs a new PathSubscriber instance.
*
* @param \Drupal\Core\Path\AliasManagerInterface $alias_manager
* The alias manager.
* @param \Drupal\Core\Path\CurrentPathStack $current_path
* The current path.
*/
public function __construct(AliasManagerInterface $alias_manager, CurrentPathStack $current_path) {
$this->aliasManager = $alias_manager;
$this->currentPath = $current_path;
}
/**
* Sets the cache key on the alias manager cache decorator.
*
* KernelEvents::CONTROLLER is used in order to be executed after routing.
*
* @param \Symfony\Component\HttpKernel\Event\FilterControllerEvent $event
* The Event to process.
*/
public function onKernelController(FilterControllerEvent $event) {
// Set the cache key on the alias manager cache decorator.
if ($event->getRequestType() == HttpKernelInterface::MASTER_REQUEST) {
$this->aliasManager->setCacheKey(rtrim($this->currentPath->getPath($event->getRequest()), '/'));
}
}
/**
* Ensures system paths for the request get cached.
*/
public function onKernelTerminate(PostResponseEvent $event) {
$this->aliasManager->writeCache();
}
/**
* Registers the methods in this class that should be listeners.
*
* @return array
* An array of event listener definitions.
*/
static function getSubscribedEvents() {
$events[KernelEvents::CONTROLLER][] = array('onKernelController', 200);
$events[KernelEvents::TERMINATE][] = array('onKernelTerminate', 200);
return $events;
}
}

View file

@ -0,0 +1,137 @@
<?php
/**
* @file
* Contains \Drupal\Core\EventSubscriber\RedirectResponseSubscriber.
*/
namespace Drupal\Core\EventSubscriber;
use Drupal\Component\Utility\UrlHelper;
use Drupal\Core\Routing\RequestContext;
use Drupal\Core\Routing\UrlGeneratorInterface;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
use Symfony\Component\HttpKernel\Event\GetResponseForControllerResultEvent;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
/**
* Allows manipulation of the response object when performing a redirect.
*/
class RedirectResponseSubscriber implements EventSubscriberInterface {
/**
* The url generator service.
*
* @var \Drupal\Core\Routing\UrlGeneratorInterface
*/
protected $urlGenerator;
/**
* Constructs a RedirectResponseSubscriber object.
*
* @param \Drupal\Core\Routing\UrlGeneratorInterface $url_generator
* The url generator service.
* @param \Drupal\Core\Routing\RequestContext $request_context
* The request context.
*/
public function __construct(UrlGeneratorInterface $url_generator, RequestContext $request_context) {
$this->urlGenerator = $url_generator;
$this->requestContext = $request_context;
}
/**
* Allows manipulation of the response object when performing a redirect.
*
* @param \Symfony\Component\HttpKernel\Event\FilterResponseEvent $event
* The Event to process.
*/
public function checkRedirectUrl(FilterResponseEvent $event) {
$response = $event->getResponse();
if ($response instanceOf RedirectResponse) {
$options = array();
$request = $event->getRequest();
$destination = $request->query->get('destination');
// A destination from \Drupal::request()->query always overrides the
// current RedirectResponse. We do not allow absolute URLs to be passed
// via \Drupal::request()->query, as this can be an attack vector, with
// the following exception:
// - Absolute URLs that point to this site (i.e. same base URL and
// base path) are allowed.
if ($destination) {
if (!UrlHelper::isExternal($destination)) {
// The destination query parameter can be a relative URL in the sense
// of not including the scheme and host, but its path is expected to
// be absolute (start with a '/'). For such a case, prepend the
// scheme and host, because the 'Location' header must be absolute.
if (strpos($destination, '/') === 0) {
$destination = $request->getSchemeAndHttpHost() . $destination;
}
else {
// Legacy destination query parameters can be relative paths that
// have not yet been converted to URLs (outbound path processors
// and other URL handling still needs to be performed).
// @todo As generateFromPath() is deprecated, remove this in
// https://www.drupal.org/node/2418219.
$destination = UrlHelper::parse($destination);
$path = $destination['path'];
$options['query'] = $destination['query'];
$options['fragment'] = $destination['fragment'];
// The 'Location' HTTP header must always be absolute.
$options['absolute'] = TRUE;
$destination = $this->urlGenerator->generateFromPath($path, $options);
}
$response->setTargetUrl($destination);
}
elseif (UrlHelper::externalIsLocal($destination, $this->requestContext->getCompleteBaseUrl())) {
$response->setTargetUrl($destination);
}
}
}
}
/**
* Sanitize the destination parameter to prevent open redirect attacks.
*
* @param \Symfony\Component\HttpKernel\Event\GetResponseEvent $event
* The Event to process.
*/
public function sanitizeDestination(GetResponseEvent $event) {
$request = $event->getRequest();
// Sanitize the destination parameter (which is often used for redirects) to
// prevent open redirect attacks leading to other domains. Sanitize both
// $_GET['destination'] and $_REQUEST['destination'] to protect code that
// relies on either, but do not sanitize $_POST to avoid interfering with
// unrelated form submissions. The sanitization happens here because
// url_is_external() requires the variable system to be available.
$query_info = $request->query;
$request_info = $request->request;
if ($query_info->has('destination') || $request_info->has('destination')) {
// If the destination is an external URL, remove it.
if ($query_info->has('destination') && UrlHelper::isExternal($query_info->get('destination'))) {
$query_info->remove('destination');
$request_info->remove('destination');
}
// If there's still something in $_REQUEST['destination'] that didn't come
// from $_GET, check it too.
if ($request_info->has('destination') && (!$query_info->has('destination') || $request_info->get('destination') != $query_info->get('destination')) && UrlHelper::isExternal($request_info->get('destination'))) {
$request_info->remove('destination');
}
}
}
/**
* Registers the methods in this class that should be listeners.
*
* @return array
* An array of event listener definitions.
*/
static function getSubscribedEvents() {
$events[KernelEvents::RESPONSE][] = array('checkRedirectUrl');
$events[KernelEvents::REQUEST][] = array('sanitizeDestination', 100);
return $events;
}
}

View file

@ -0,0 +1,60 @@
<?php
/**
* @file
* Contains \Drupal\Core\EventSubscriber\ReplicaDatabaseIgnoreSubscriber.
*/
namespace Drupal\Core\EventSubscriber;
use Drupal\Core\Database\Database;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
/**
* System subscriber for controller requests.
*/
class ReplicaDatabaseIgnoreSubscriber implements EventSubscriberInterface {
/**
* Checks and disables the replica database server if appropriate.
*
* @param \Symfony\Component\HttpKernel\Event\GetResponseEvent $event
* The Event to process.
*/
public function checkReplicaServer(GetResponseEvent $event) {
// Ignore replica database servers for this request.
//
// In Drupal's distributed database structure, new data is written to the
// master and then propagated to the replica servers. This means there is a
// lag between when data is written to the master and when it is available
// on the replica. At these times, we will want to avoid using a replica server
// temporarily. For example, if a user posts a new node then we want to
// disable the replica server for that user temporarily to allow the replica
// server to catch up.
// That way, that user will see their changes immediately while for other
// users we still get the benefits of having a replica server, just with
// slightly stale data. Code that wants to disable the replica server should
// use the db_set_ignore_replica() function to set
// $_SESSION['ignore_replica_server'] to the timestamp after which the replica
// can be re-enabled.
if (isset($_SESSION['ignore_replica_server'])) {
if ($_SESSION['ignore_replica_server'] >= REQUEST_TIME) {
Database::ignoreTarget('default', 'replica');
}
else {
unset($_SESSION['ignore_replica_server']);
}
}
}
/**
* {@inheritdoc}
*/
static function getSubscribedEvents() {
$events[KernelEvents::REQUEST][] = array('checkReplicaServer');
return $events;
}
}

View file

@ -0,0 +1,61 @@
<?php
/**
* @file
* Contains \Drupal\Core\EventSubscriber\RequestCloseSubscriber.
*/
namespace Drupal\Core\EventSubscriber;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\HttpKernel\Event\PostResponseEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
/**
* Subscriber for all responses.
*/
class RequestCloseSubscriber implements EventSubscriberInterface {
/**
* @var \Drupal\Core\Extension\ModuleHandlerInterface
*/
protected $moduleHandler;
/**
* Constructs a new RequestCloseSubscriber instance.
*
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
* The module handler.
*/
function __construct(ModuleHandlerInterface $module_handler) {
$this->moduleHandler = $module_handler;
}
/**
* Performs end of request tasks.
*
* @todo The body of this function has just been copied almost verbatim from
* drupal_page_footer(). There's probably a lot in here that needs to get
* removed/changed. Also, if possible, do more light-weight shutdowns on
* AJAX requests.
*
* @param Symfony\Component\HttpKernel\Event\PostResponseEvent $event
* The Event to process.
*/
public function onTerminate(PostResponseEvent $event) {
$this->moduleHandler->writeCache();
}
/**
* Registers the methods in this class that should be listeners.
*
* @return array
* An array of event listener definitions.
*/
static function getSubscribedEvents() {
$events[KernelEvents::TERMINATE][] = array('onTerminate', 100);
return $events;
}
}

View file

@ -0,0 +1,45 @@
<?php
/**
* @file
* Contains \Drupal\Core\EventSubscriber\ResponseGeneratorSubscriber.
*/
namespace Drupal\Core\EventSubscriber;
use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
/**
* Response subscriber to add X-Generator header tag.
*/
class ResponseGeneratorSubscriber implements EventSubscriberInterface {
/**
* Sets extra X-Generator header on successful responses.
*
* @param \Symfony\Component\HttpKernel\Event\FilterResponseEvent $event
* The event to process.
*/
public function onRespond(FilterResponseEvent $event) {
if (!$event->isMasterRequest()) {
return;
}
$response = $event->getResponse();
// Set the generator in the HTTP header.
list($version) = explode('.', \Drupal::VERSION, 2);
$response->headers->set('X-Generator', 'Drupal ' . $version . ' (https://www.drupal.org)');
}
/**
* {@inheritdoc}
*/
public static function getSubscribedEvents() {
$events[KernelEvents::RESPONSE][] = ['onRespond'];
return $events;
}
}

View file

@ -0,0 +1,62 @@
<?php
/**
* @file
* Contains \Drupal\Core\EventSubscriber\RouteAccessResponseSubscriber.
*/
namespace Drupal\Core\EventSubscriber;
use Drupal\Core\Cache\CacheableResponseInterface;
use Drupal\Core\Routing\AccessAwareRouterInterface;
use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
/**
* Response subscriber to bubble the route's access result's cacheability.
*
* During routing, access checking is performed. The corresponding access result
* is stored in the Request object's attributes, just like the matching route
* object is. In case of a cacheable response, the route's access result also
* determined the content of the response, and therefore the cacheability of the
* route's access result should also be applied to the resulting response.
*
* @see \Drupal\Core\Routing\AccessAwareRouterInterface::ACCESS_RESULT
* @see \Drupal\Core\Routing\AccessAwareRouter::matchRequest()
* @see \Drupal\Core\Routing\AccessAwareRouter::checkAccess()
*/
class RouteAccessResponseSubscriber implements EventSubscriberInterface {
/**
* Bubbles the route's access result' cacheability metadata.
*
* @param \Symfony\Component\HttpKernel\Event\FilterResponseEvent $event
* The event to process.
*/
public function onRespond(FilterResponseEvent $event) {
if (!$event->isMasterRequest()) {
return;
}
$response = $event->getResponse();
if (!$response instanceof CacheableResponseInterface) {
return;
}
$request = $event->getRequest();
$access_result = $request->attributes->get(AccessAwareRouterInterface::ACCESS_RESULT);
$response->addCacheableDependency($access_result);
}
/**
* {@inheritdoc}
*/
public static function getSubscribedEvents() {
// Priority 10, so that it runs before FinishResponseSubscriber, which will
// expose the cacheability metadata in the form of headers.
$events[KernelEvents::RESPONSE][] = ['onRespond', 10];
return $events;
}
}

View file

@ -0,0 +1,53 @@
<?php
/**
* @file
* Contains \Drupal\Core\EventSubscriber\RouteEnhancerSubscriber.
*/
namespace Drupal\Core\EventSubscriber;
use Drupal\Core\Routing\LazyRouteEnhancer;
use Drupal\Core\Routing\RouteBuildEvent;
use Drupal\Core\Routing\RoutingEvents;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
/**
* Listens to the new routes before they get saved.
*/
class RouteEnhancerSubscriber implements EventSubscriberInterface {
/**
* @var \Drupal\Core\Routing\LazyRouteEnhancer
*/
protected $routeEnhancer;
/**
* Constructs the RouteEnhancerSubscriber object.
*
* @param \Drupal\Core\Routing\LazyRouteEnhancer $route_enhancer
* The lazy route enhancer.
*/
public function __construct(LazyRouteEnhancer $route_enhancer) {
$this->routeEnhancer = $route_enhancer;
}
/**
* Adds the route_enhancer object to the route collection.
*
* @param \Drupal\Core\Routing\RouteBuildEvent $event
* The route build event.
*/
public function onRouteAlter(RouteBuildEvent $event) {
$this->routeEnhancer->setEnhancers($event->getRouteCollection());
}
/**
* {@inheritdoc}
*/
static function getSubscribedEvents() {
$events[RoutingEvents::ALTER][] = array('onRouteAlter', -300);
return $events;
}
}

View file

@ -0,0 +1,55 @@
<?php
/**
* @file
* Contains \Drupal\Core\EventSubscriber\RouteFilterSubscriber.
*/
namespace Drupal\Core\EventSubscriber;
use Drupal\Core\Routing\LazyRouteFilter;
use Drupal\Core\Routing\RouteBuildEvent;
use Drupal\Core\Routing\RoutingEvents;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
/**
* Listens to the filtered collection of route instances.
*/
class RouteFilterSubscriber implements EventSubscriberInterface {
/**
* The lazy route filter.
*
* @var \Drupal\Core\Routing\LazyRouteFilter
*/
protected $routeFilter;
/**
* Constructs the RouteFilterSubscriber object.
*
* @param \Drupal\Core\Routing\LazyRouteFilter $route_filter
* The lazy route filter.
*/
public function __construct(LazyRouteFilter $route_filter) {
$this->routeFilter = $route_filter;
}
/**
* Get the Response object from filtered route collection.
*
* @param \Drupal\Core\Routing\RouteBuildEvent $event
* The route build event.
*/
public function onRouteAlter(RouteBuildEvent $event) {
$this->routeFilter->setFilters($event->getRouteCollection());
}
/**
* {@inheritdoc}
*/
static function getSubscribedEvents() {
$events[RoutingEvents::ALTER][] = array('onRouteAlter', -300);
return $events;
}
}

View file

@ -0,0 +1,50 @@
<?php
/**
* @file
* Contains \Drupal\Core\EventSubscriber\RouteMethodSubscriber.
*/
namespace Drupal\Core\EventSubscriber;
use Drupal\Core\Routing\RouteBuildEvent;
use Drupal\Core\Routing\RoutingEvents;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
/**
* Provides a default value for the HTTP method restriction on routes.
*
* Most routes will only deal with GET and POST requests, so we restrict them to
* those two if nothing else is specified. This is necessary to give other
* routes a chance during the route matching process when they are listening
* for example to DELETE requests on the same path. A typical use case are REST
* web service routes that use the full spectrum of HTTP methods.
*/
class RouteMethodSubscriber implements EventSubscriberInterface {
/**
* Sets a default value of GET|POST for the _method route property.
*
* @param \Drupal\Core\Routing\RouteBuildEvent $event
* The event containing the build routes.
*/
public function onRouteBuilding(RouteBuildEvent $event) {
foreach ($event->getRouteCollection() as $route) {
$methods = $route->getMethods();
if (empty($methods)) {
$route->setMethods(array('GET', 'POST'));
}
}
}
/**
* {@inheritdoc}
*/
static function getSubscribedEvents() {
// Set a higher priority to ensure that routes get the default HTTP methods
// as early as possible.
$events[RoutingEvents::ALTER][] = array('onRouteBuilding', 5000);
return $events;
}
}

View file

@ -0,0 +1,57 @@
<?php
/**
* @file
* Contains \Drupal\Core\EventSubscriber\RouterRebuildSubscriber.
*/
namespace Drupal\Core\EventSubscriber;
use Drupal\Core\Cache\Cache;
use Drupal\Core\Lock\LockBackendInterface;
use Drupal\Core\Routing\RouteBuilderInterface;
use Drupal\Core\Routing\RoutingEvents;
use Symfony\Component\EventDispatcher\Event;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\PostResponseEvent;
use Symfony\Component\HttpKernel\KernelEvents;
/**
* Rebuilds the router if needed at the end of the request.
*/
class RouterRebuildSubscriber implements EventSubscriberInterface {
/**
* @var \Drupal\Core\Routing\RouteBuilderInterface
*/
protected $routeBuilder;
/**
* Constructs the RouterRebuildSubscriber object.
*
* @param \Drupal\Core\Routing\RouteBuilderInterface $route_builder
* The route builder.
*/
public function __construct(RouteBuilderInterface $route_builder) {
$this->routeBuilder = $route_builder;
}
/**
* Rebuilds routers if necessary.
*
* @param \Symfony\Component\HttpKernel\Event\PostResponseEvent $event
* The event object.
*/
public function onKernelTerminate(PostResponseEvent $event) {
$this->routeBuilder->rebuildIfNeeded();
}
/**
* {@inheritdoc}
*/
static function getSubscribedEvents() {
$events[KernelEvents::TERMINATE][] = array('onKernelTerminate', 200);
return $events;
}
}

View file

@ -0,0 +1,57 @@
<?php
/**
* @file
* Contains \Drupal\Core\EventSubscriber\SpecialAttributesRouteSubscriber.
*/
namespace Drupal\Core\EventSubscriber;
use Drupal\Core\Routing\RouteBuildEvent;
use Drupal\Core\Routing\RouteSubscriberBase;
use Symfony\Cmf\Component\Routing\RouteObjectInterface;
use Symfony\Component\Routing\RouteCollection;
/**
* Provides a route subscriber which checks for invalid pattern variables.
*/
class SpecialAttributesRouteSubscriber extends RouteSubscriberBase {
/**
* {@inheritdoc}
*/
protected function alterRoutes(RouteCollection $collection) {
$special_variables = array(
'system_path',
'_legacy',
'_raw_variables',
RouteObjectInterface::ROUTE_OBJECT,
RouteObjectInterface::ROUTE_NAME,
'_content',
'_controller',
'_form',
);
foreach ($collection->all() as $name => $route) {
if ($not_allowed_variables = array_intersect($route->compile()->getVariables(), $special_variables)) {
$reserved = implode(', ', $not_allowed_variables);
trigger_error(sprintf('Route %s uses reserved variable names: %s', $name, $reserved), E_USER_WARNING);
}
}
}
/**
* Delegates the route altering to self::alterRoutes().
*
* @param \Drupal\Core\Routing\RouteBuildEvent $event
* The route build event.
*
* @return bool
* Returns TRUE if the variables were successfully replaced, otherwise
* FALSE.
*/
public function onAlterRoutes(RouteBuildEvent $event) {
$collection = $event->getRouteCollection();
return $this->alterRoutes($collection);
}
}