Drupal 8.0.0 beta 12. More info: https://www.drupal.org/node/2514176
This commit is contained in:
commit
9921556621
13277 changed files with 1459781 additions and 0 deletions
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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="<front>"';
|
||||
|
||||
// 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="<front>">
|
||||
$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;
|
||||
}
|
||||
|
||||
}
|
117
core/lib/Drupal/Core/EventSubscriber/AjaxResponseSubscriber.php
Normal file
117
core/lib/Drupal/Core/EventSubscriber/AjaxResponseSubscriber.php
Normal 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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
332
core/lib/Drupal/Core/EventSubscriber/ConfigImportSubscriber.php
Normal file
332
core/lib/Drupal/Core/EventSubscriber/ConfigImportSubscriber.php
Normal 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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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++;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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)));
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
81
core/lib/Drupal/Core/EventSubscriber/PathRootsSubscriber.php
Normal file
81
core/lib/Drupal/Core/EventSubscriber/PathRootsSubscriber.php
Normal 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;
|
||||
}
|
||||
|
||||
}
|
83
core/lib/Drupal/Core/EventSubscriber/PathSubscriber.php
Normal file
83
core/lib/Drupal/Core/EventSubscriber/PathSubscriber.php
Normal 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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
Reference in a new issue