Chris@0: httpKernel = $http_kernel; Chris@0: $this->logger = $logger; Chris@0: $this->redirectDestination = $redirect_destination; Chris@0: $this->accessUnawareRouter = $access_unaware_router; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: protected static function getPriority() { Chris@0: // A very low priority so that custom handlers are almost certain to fire Chris@0: // before it, even if someone forgets to set a priority. Chris@0: return -128; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: protected function getHandledFormats() { Chris@0: return ['html']; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Handles a 4xx error for HTML. Chris@0: * Chris@0: * @param \Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent $event Chris@0: * The event to process. Chris@0: */ Chris@0: public function on4xx(GetResponseForExceptionEvent $event) { Chris@0: if (($exception = $event->getException()) && $exception instanceof HttpExceptionInterface) { Chris@0: $this->makeSubrequest($event, '/system/4xx', $exception->getStatusCode()); Chris@0: } Chris@0: } Chris@0: Chris@0: /** Chris@0: * Handles a 401 error for HTML. Chris@0: * Chris@0: * @param \Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent $event Chris@0: * The event to process. Chris@0: */ Chris@0: public function on401(GetResponseForExceptionEvent $event) { Chris@0: $this->makeSubrequest($event, '/system/401', Response::HTTP_UNAUTHORIZED); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Handles a 403 error for HTML. Chris@0: * Chris@0: * @param \Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent $event Chris@0: * The event to process. Chris@0: */ Chris@0: public function on403(GetResponseForExceptionEvent $event) { Chris@0: $this->makeSubrequest($event, '/system/403', Response::HTTP_FORBIDDEN); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Handles a 404 error for HTML. Chris@0: * Chris@0: * @param \Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent $event Chris@0: * The event to process. Chris@0: */ Chris@0: public function on404(GetResponseForExceptionEvent $event) { Chris@0: $this->makeSubrequest($event, '/system/404', Response::HTTP_NOT_FOUND); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Makes a subrequest to retrieve the default error page. Chris@0: * Chris@0: * @param \Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent $event Chris@0: * The event to process. Chris@0: * @param string $url Chris@0: * The path/url to which to make a subrequest for this error message. Chris@0: * @param int $status_code Chris@0: * The status code for the error being handled. Chris@0: */ Chris@0: protected function makeSubrequest(GetResponseForExceptionEvent $event, $url, $status_code) { Chris@0: $request = $event->getRequest(); Chris@0: $exception = $event->getException(); Chris@0: Chris@0: try { Chris@0: // Reuse the exact same request (so keep the same URL, keep the access Chris@0: // result, the exception, et cetera) but override the routing information. Chris@0: // This means that aside from routing, this is identical to the master Chris@0: // request. This allows us to generate a response that is executed on Chris@0: // behalf of the master request, i.e. for the original URL. This is what Chris@0: // allows us to e.g. generate a 404 response for the original URL; if we Chris@0: // would execute a subrequest with the 404 route's URL, then it'd be Chris@0: // generated for *that* URL, not the *original* URL. Chris@0: $sub_request = clone $request; Chris@0: Chris@0: // The routing to the 404 page should be done as GET request because it is Chris@0: // restricted to GET and POST requests only. Otherwise a DELETE request Chris@0: // would for example trigger a method not allowed exception. Chris@0: $request_context = clone ($this->accessUnawareRouter->getContext()); Chris@0: $request_context->setMethod('GET'); Chris@0: $this->accessUnawareRouter->setContext($request_context); Chris@0: Chris@0: $sub_request->attributes->add($this->accessUnawareRouter->match($url)); Chris@0: Chris@0: // Add to query (GET) or request (POST) parameters: Chris@0: // - 'destination' (to ensure e.g. the login form in a 403 response Chris@0: // redirects to the original URL) Chris@0: // - '_exception_statuscode' Chris@0: $parameters = $sub_request->isMethod('GET') ? $sub_request->query : $sub_request->request; Chris@0: $parameters->add($this->redirectDestination->getAsArray() + ['_exception_statuscode' => $status_code]); Chris@0: Chris@0: $response = $this->httpKernel->handle($sub_request, HttpKernelInterface::SUB_REQUEST); Chris@0: // Only 2xx responses should have their status code overridden; any Chris@0: // other status code should be passed on: redirects (3xx), error (5xx)… Chris@0: // @see https://www.drupal.org/node/2603788#comment-10504916 Chris@0: if ($response->isSuccessful()) { Chris@0: $response->setStatusCode($status_code); Chris@0: } Chris@0: Chris@14: // Persist the exception's cacheability metadata, if any. If the exception Chris@14: // itself isn't cacheable, then this will make the response uncacheable: Chris@14: // max-age=0 will be set. Chris@14: if ($response instanceof CacheableResponseInterface) { Chris@14: $response->addCacheableDependency($exception); Chris@14: } Chris@14: Chris@0: // Persist any special HTTP headers that were set on the exception. Chris@0: if ($exception instanceof HttpExceptionInterface) { Chris@0: $response->headers->add($exception->getHeaders()); Chris@0: } Chris@0: Chris@0: $event->setResponse($response); Chris@0: } Chris@0: catch (\Exception $e) { Chris@0: // If an error happened in the subrequest we can't do much else. Instead, Chris@0: // just log it. The DefaultExceptionSubscriber will catch the original Chris@0: // exception and handle it normally. Chris@0: $error = Error::decodeException($e); Chris@0: $this->logger->log($error['severity_level'], '%type: @message in %function (line %line of %file).', $error); Chris@0: } Chris@0: } Chris@0: Chris@0: }