comparison core/lib/Drupal/Core/EventSubscriber/DefaultExceptionHtmlSubscriber.php @ 0:4c8ae668cc8c

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