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