Chris@0
|
1 <?php
|
Chris@0
|
2
|
Chris@0
|
3 namespace Drupal\Core\EventSubscriber;
|
Chris@0
|
4
|
Chris@14
|
5 use Drupal\Core\Cache\CacheableResponseInterface;
|
Chris@0
|
6 use Drupal\Core\Routing\RedirectDestinationInterface;
|
Chris@0
|
7 use Drupal\Core\Utility\Error;
|
Chris@0
|
8 use Psr\Log\LoggerInterface;
|
Chris@0
|
9 use Symfony\Component\HttpFoundation\Response;
|
Chris@0
|
10 use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent;
|
Chris@0
|
11 use Symfony\Component\HttpKernel\HttpKernelInterface;
|
Chris@0
|
12 use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface;
|
Chris@0
|
13 use Symfony\Component\Routing\Matcher\UrlMatcherInterface;
|
Chris@0
|
14
|
Chris@0
|
15 /**
|
Chris@0
|
16 * Exception subscriber for handling core default HTML error pages.
|
Chris@0
|
17 */
|
Chris@0
|
18 class DefaultExceptionHtmlSubscriber extends HttpExceptionSubscriberBase {
|
Chris@0
|
19
|
Chris@0
|
20 /**
|
Chris@0
|
21 * The HTTP kernel.
|
Chris@0
|
22 *
|
Chris@0
|
23 * @var \Symfony\Component\HttpKernel\HttpKernelInterface
|
Chris@0
|
24 */
|
Chris@0
|
25 protected $httpKernel;
|
Chris@0
|
26
|
Chris@0
|
27 /**
|
Chris@0
|
28 * The logger instance.
|
Chris@0
|
29 *
|
Chris@0
|
30 * @var \Psr\Log\LoggerInterface
|
Chris@0
|
31 */
|
Chris@0
|
32 protected $logger;
|
Chris@0
|
33
|
Chris@0
|
34 /**
|
Chris@0
|
35 * The redirect destination service.
|
Chris@0
|
36 *
|
Chris@0
|
37 * @var \Drupal\Core\Routing\RedirectDestinationInterface
|
Chris@0
|
38 */
|
Chris@0
|
39 protected $redirectDestination;
|
Chris@0
|
40
|
Chris@0
|
41 /**
|
Chris@0
|
42 * A router implementation which does not check access.
|
Chris@0
|
43 *
|
Chris@0
|
44 * @var \Symfony\Component\Routing\Matcher\UrlMatcherInterface
|
Chris@0
|
45 */
|
Chris@0
|
46 protected $accessUnawareRouter;
|
Chris@0
|
47
|
Chris@0
|
48 /**
|
Chris@0
|
49 * Constructs a new DefaultExceptionHtmlSubscriber.
|
Chris@0
|
50 *
|
Chris@0
|
51 * @param \Symfony\Component\HttpKernel\HttpKernelInterface $http_kernel
|
Chris@0
|
52 * The HTTP kernel.
|
Chris@0
|
53 * @param \Psr\Log\LoggerInterface $logger
|
Chris@0
|
54 * The logger service.
|
Chris@0
|
55 * @param \Drupal\Core\Routing\RedirectDestinationInterface $redirect_destination
|
Chris@0
|
56 * The redirect destination service.
|
Chris@0
|
57 * @param \Symfony\Component\Routing\Matcher\UrlMatcherInterface $access_unaware_router
|
Chris@0
|
58 * A router implementation which does not check access.
|
Chris@0
|
59 */
|
Chris@0
|
60 public function __construct(HttpKernelInterface $http_kernel, LoggerInterface $logger, RedirectDestinationInterface $redirect_destination, UrlMatcherInterface $access_unaware_router) {
|
Chris@0
|
61 $this->httpKernel = $http_kernel;
|
Chris@0
|
62 $this->logger = $logger;
|
Chris@0
|
63 $this->redirectDestination = $redirect_destination;
|
Chris@0
|
64 $this->accessUnawareRouter = $access_unaware_router;
|
Chris@0
|
65 }
|
Chris@0
|
66
|
Chris@0
|
67 /**
|
Chris@0
|
68 * {@inheritdoc}
|
Chris@0
|
69 */
|
Chris@0
|
70 protected static function getPriority() {
|
Chris@0
|
71 // A very low priority so that custom handlers are almost certain to fire
|
Chris@0
|
72 // before it, even if someone forgets to set a priority.
|
Chris@0
|
73 return -128;
|
Chris@0
|
74 }
|
Chris@0
|
75
|
Chris@0
|
76 /**
|
Chris@0
|
77 * {@inheritdoc}
|
Chris@0
|
78 */
|
Chris@0
|
79 protected function getHandledFormats() {
|
Chris@0
|
80 return ['html'];
|
Chris@0
|
81 }
|
Chris@0
|
82
|
Chris@0
|
83 /**
|
Chris@0
|
84 * Handles a 4xx error for HTML.
|
Chris@0
|
85 *
|
Chris@0
|
86 * @param \Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent $event
|
Chris@0
|
87 * The event to process.
|
Chris@0
|
88 */
|
Chris@0
|
89 public function on4xx(GetResponseForExceptionEvent $event) {
|
Chris@0
|
90 if (($exception = $event->getException()) && $exception instanceof HttpExceptionInterface) {
|
Chris@0
|
91 $this->makeSubrequest($event, '/system/4xx', $exception->getStatusCode());
|
Chris@0
|
92 }
|
Chris@0
|
93 }
|
Chris@0
|
94
|
Chris@0
|
95 /**
|
Chris@0
|
96 * Handles a 401 error for HTML.
|
Chris@0
|
97 *
|
Chris@0
|
98 * @param \Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent $event
|
Chris@0
|
99 * The event to process.
|
Chris@0
|
100 */
|
Chris@0
|
101 public function on401(GetResponseForExceptionEvent $event) {
|
Chris@0
|
102 $this->makeSubrequest($event, '/system/401', Response::HTTP_UNAUTHORIZED);
|
Chris@0
|
103 }
|
Chris@0
|
104
|
Chris@0
|
105 /**
|
Chris@0
|
106 * Handles a 403 error for HTML.
|
Chris@0
|
107 *
|
Chris@0
|
108 * @param \Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent $event
|
Chris@0
|
109 * The event to process.
|
Chris@0
|
110 */
|
Chris@0
|
111 public function on403(GetResponseForExceptionEvent $event) {
|
Chris@0
|
112 $this->makeSubrequest($event, '/system/403', Response::HTTP_FORBIDDEN);
|
Chris@0
|
113 }
|
Chris@0
|
114
|
Chris@0
|
115 /**
|
Chris@0
|
116 * Handles a 404 error for HTML.
|
Chris@0
|
117 *
|
Chris@0
|
118 * @param \Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent $event
|
Chris@0
|
119 * The event to process.
|
Chris@0
|
120 */
|
Chris@0
|
121 public function on404(GetResponseForExceptionEvent $event) {
|
Chris@0
|
122 $this->makeSubrequest($event, '/system/404', Response::HTTP_NOT_FOUND);
|
Chris@0
|
123 }
|
Chris@0
|
124
|
Chris@0
|
125 /**
|
Chris@0
|
126 * Makes a subrequest to retrieve the default error page.
|
Chris@0
|
127 *
|
Chris@0
|
128 * @param \Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent $event
|
Chris@0
|
129 * The event to process.
|
Chris@0
|
130 * @param string $url
|
Chris@0
|
131 * The path/url to which to make a subrequest for this error message.
|
Chris@0
|
132 * @param int $status_code
|
Chris@0
|
133 * The status code for the error being handled.
|
Chris@0
|
134 */
|
Chris@0
|
135 protected function makeSubrequest(GetResponseForExceptionEvent $event, $url, $status_code) {
|
Chris@0
|
136 $request = $event->getRequest();
|
Chris@0
|
137 $exception = $event->getException();
|
Chris@0
|
138
|
Chris@0
|
139 try {
|
Chris@0
|
140 // Reuse the exact same request (so keep the same URL, keep the access
|
Chris@0
|
141 // result, the exception, et cetera) but override the routing information.
|
Chris@0
|
142 // This means that aside from routing, this is identical to the master
|
Chris@0
|
143 // request. This allows us to generate a response that is executed on
|
Chris@0
|
144 // behalf of the master request, i.e. for the original URL. This is what
|
Chris@0
|
145 // allows us to e.g. generate a 404 response for the original URL; if we
|
Chris@0
|
146 // would execute a subrequest with the 404 route's URL, then it'd be
|
Chris@0
|
147 // generated for *that* URL, not the *original* URL.
|
Chris@0
|
148 $sub_request = clone $request;
|
Chris@0
|
149
|
Chris@0
|
150 // The routing to the 404 page should be done as GET request because it is
|
Chris@0
|
151 // restricted to GET and POST requests only. Otherwise a DELETE request
|
Chris@0
|
152 // would for example trigger a method not allowed exception.
|
Chris@0
|
153 $request_context = clone ($this->accessUnawareRouter->getContext());
|
Chris@0
|
154 $request_context->setMethod('GET');
|
Chris@0
|
155 $this->accessUnawareRouter->setContext($request_context);
|
Chris@0
|
156
|
Chris@0
|
157 $sub_request->attributes->add($this->accessUnawareRouter->match($url));
|
Chris@0
|
158
|
Chris@0
|
159 // Add to query (GET) or request (POST) parameters:
|
Chris@0
|
160 // - 'destination' (to ensure e.g. the login form in a 403 response
|
Chris@0
|
161 // redirects to the original URL)
|
Chris@0
|
162 // - '_exception_statuscode'
|
Chris@0
|
163 $parameters = $sub_request->isMethod('GET') ? $sub_request->query : $sub_request->request;
|
Chris@0
|
164 $parameters->add($this->redirectDestination->getAsArray() + ['_exception_statuscode' => $status_code]);
|
Chris@0
|
165
|
Chris@0
|
166 $response = $this->httpKernel->handle($sub_request, HttpKernelInterface::SUB_REQUEST);
|
Chris@0
|
167 // Only 2xx responses should have their status code overridden; any
|
Chris@0
|
168 // other status code should be passed on: redirects (3xx), error (5xx)…
|
Chris@0
|
169 // @see https://www.drupal.org/node/2603788#comment-10504916
|
Chris@0
|
170 if ($response->isSuccessful()) {
|
Chris@0
|
171 $response->setStatusCode($status_code);
|
Chris@0
|
172 }
|
Chris@0
|
173
|
Chris@14
|
174 // Persist the exception's cacheability metadata, if any. If the exception
|
Chris@14
|
175 // itself isn't cacheable, then this will make the response uncacheable:
|
Chris@14
|
176 // max-age=0 will be set.
|
Chris@14
|
177 if ($response instanceof CacheableResponseInterface) {
|
Chris@14
|
178 $response->addCacheableDependency($exception);
|
Chris@14
|
179 }
|
Chris@14
|
180
|
Chris@0
|
181 // Persist any special HTTP headers that were set on the exception.
|
Chris@0
|
182 if ($exception instanceof HttpExceptionInterface) {
|
Chris@0
|
183 $response->headers->add($exception->getHeaders());
|
Chris@0
|
184 }
|
Chris@0
|
185
|
Chris@0
|
186 $event->setResponse($response);
|
Chris@0
|
187 }
|
Chris@0
|
188 catch (\Exception $e) {
|
Chris@0
|
189 // If an error happened in the subrequest we can't do much else. Instead,
|
Chris@0
|
190 // just log it. The DefaultExceptionSubscriber will catch the original
|
Chris@0
|
191 // exception and handle it normally.
|
Chris@0
|
192 $error = Error::decodeException($e);
|
Chris@0
|
193 $this->logger->log($error['severity_level'], '%type: @message in %function (line %line of %file).', $error);
|
Chris@0
|
194 }
|
Chris@0
|
195 }
|
Chris@0
|
196
|
Chris@0
|
197 }
|