/** * Processes a successful controller into an HTTP 200 response. * * Some controllers may not return a response object but simply the body of * one. The VIEW event is called in that case, to allow us to mutate that * body into a Response object. In particular we assume that the return * from an JSON-type response is a JSON string, so just wrap it into a * Response object. * * @param Symfony\Component\HttpKernel\Event\GetResponseForControllerResultEvent $event * The Event to process. */ public function onView(GetResponseForControllerResultEvent $event) { $request = $event->getRequest(); // For a master request, we process the result and wrap it as needed. // For a subrequest, all we want is the string value. We assume that // is just an HTML string from a controller, so wrap that into a response // object. The subrequest's response will get dissected and placed into // the larger page as needed. if ($event->getRequestType() == HttpKernelInterface::MASTER_REQUEST) { $method = 'on' . $this->negotiation->getContentType($request); if (method_exists($this, $method)) { $event->setResponse($this->{$method}($event)); } else { $event->setResponse(new Response('Not Acceptable', 406)); } } else { // This is a new-style Symfony-esque subrequest, which means we assume // the body is not supposed to be a complete page but just a page // fragment. $page_result = $event->getControllerResult(); if ($page_result instanceof HtmlPage || $page_result instanceof Response) { return $page_result; } if (!is_array($page_result)) { $page_result = array('#markup' => $page_result); } // If no title was returned fall back to one defined in the route. if (!isset($page_result['#title'])) { $page_result['#title'] = $this->titleResolver->getTitle($request, $request->attributes->get(RouteObjectInterface::ROUTE_OBJECT)); } $event->setResponse(new Response(drupal_render_root($page_result))); } }
/** * Loads all non-admin routes right before the actual page is rendered. * * @param \Symfony\Component\HttpKernel\Event\KernelEvent $event * The event to process. */ public function onRequest(KernelEvent $event) { // Just preload on normal HTML pages, as they will display menu links. if ($this->negotiation->getContentType($event->getRequest()) == 'html') { $this->loadNonAdminRoutes(); } }
/** * Sets the derived request format on the request. * * @param \Symfony\Component\HttpKernel\Event\GetResponseEvent $event * The event to process. */ public function onRequestDeriveFormat(GetResponseEvent $event) { $request = $event->getRequest(); if (!$request->attributes->get('_format')) { $request->setRequestFormat($this->negotiation->getContentType($request)); } }
/** * {@inheritdoc} */ public function filter(RouteCollection $collection, Request $request) { // Generates a list of Symfony formats matching the acceptable MIME types. // @todo replace by proper content negotiation library. $acceptable_mime_types = $request->getAcceptableContentTypes(); $acceptable_formats = array_filter(array_map(array($request, 'getFormat'), $acceptable_mime_types)); $primary_format = $this->contentNegotiation->getContentType($request); foreach ($collection as $name => $route) { // _format could be a |-delimited list of supported formats. $supported_formats = array_filter(explode('|', $route->getRequirement('_format'))); if (empty($supported_formats)) { // No format restriction on the route, so it always matches. Move it to // the end of the collection by re-adding it. $collection->add($name, $route); } elseif (in_array($primary_format, $supported_formats)) { // Perfect match, which will get a higher priority by leaving the route // on top of the list. } elseif (in_array('*/*', $acceptable_mime_types) || array_intersect($acceptable_formats, $supported_formats)) { // Move it to the end of the list. $collection->add($name, $route); } else { // Remove the route if it does not match at all. $collection->remove($name); } } if (count($collection)) { return $collection; } // We do not throw a // \Symfony\Component\Routing\Exception\ResourceNotFoundException here // because we don't want to return a 404 status code, but rather a 406. throw new NotAcceptableHttpException(String::format('No route found for the specified formats @formats.', array('@formats' => implode(' ', $acceptable_mime_types)))); }
/** * {@inheritdoc} */ public function handle(Request $request, $type = self::MASTER_REQUEST, $catch = true) { foreach ($this->formats as $format => $mime_type) { $request->setFormat($format, $mime_type); } $request->setRequestFormat($this->negotiator->getContentType($request)); return $this->app->handle($request, $type, $catch); }
/** * {@inheritdoc} */ public function handle(Request $request, $type = self::MASTER_REQUEST, $catch = true) { // Register available mime types. foreach ($this->formats as $format => $mime_type) { $request->setFormat($format, $mime_type); } // Determine the request format using the negotiator. $request->setRequestFormat($this->negotiator->getContentType($request)); return $this->app->handle($request, $type, $catch); }
/** * Handles an exception on a request. * * @param \Symfony\Component\Debug\Exception\FlattenException $exception * The flattened exception. * @param \Symfony\Component\HttpFoundation\Request $request * The request that generated the exception. * * @return \Symfony\Component\HttpFoundation\Response * A response object. */ public function execute(FlattenException $exception, Request $request) { $method = 'on' . $exception->getStatusCode() . $this->negotiation->getContentType($request); if (method_exists($this, $method)) { return $this->{$method}($exception, $request); } return new Response('A fatal error occurred: ' . $exception->getMessage(), $exception->getStatusCode(), $exception->getHeaders()); }
/** * Tests onRequest on a html request. */ public function testOnRequestOnHtml() { $event = $this->getMockBuilder('\\Symfony\\Component\\HttpKernel\\Event\\KernelEvent')->disableOriginalConstructor()->getMock(); $request = new Request(); $event->expects($this->any())->method('getRequest')->will($this->returnValue($request)); $this->negotiation->expects($this->once())->method('getContentType')->will($this->returnValue('html')); $this->routeProvider->expects($this->once())->method('getRoutesByNames')->with(array('test2')); $this->state->expects($this->once())->method('get')->with('routing.non_admin_routes')->will($this->returnValue(array('test2'))); $this->preloader->onRequest($event); }
/** * {@inheritdoc} */ public function initDisplay(ViewExecutable $view, array &$display, array &$options = NULL) { parent::initDisplay($view, $display, $options); $request_content_type = $this->contentNegotiation->getContentType($this->view->getRequest()); // Only use the requested content type if it's not 'html'. If it is then // default to 'json' to aid debugging. // @todo Remove the need for this when we have better content negotiation. if ($request_content_type != 'html') { $this->setContentType($request_content_type); } $this->setMimeType($this->view->getRequest()->getMimeType($this->contentType)); }
/** * Tests the getContentType() method when no priority format is found but it's an AJAX request. * * @covers ::getContentType */ public function testUnknowContentTypeButAjaxRequest() { $request = new Request(); $request->headers->set('X-Requested-With', 'XMLHttpRequest'); $this->assertSame('ajax', $this->contentNegotiation->getContentType($request)); }
/** * 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) { // @todo We are trying to switch to a more robust content negotiation // library in https://www.drupal.org/node/1505080 that will make // $request->getRequestFormat() reliable as a better alternative // to this code. We therefore use this style for now on the expectation // that it will get replaced with better code later. This approach makes // that change easier when we get to it. $conneg = new ContentNegotiation(); $format = $conneg->getContentType($request); // 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; }
/** * 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. $event->getRequest()->attributes->set('exception', FlattenException::create($exception)); $handled_formats = $this->getHandledFormats(); // @todo Injecting this service would force all implementing classes to also // handle its injection. However, we are trying to switch to a more robust // content negotiation library in https://www.drupal.org/node/1505080 that // will make $request->getRequestFormat() reliable as a better alternative // to this code. We therefore use this style for now on the expectation // that it will get replaced with better code later. That change will NOT // be an API change for any implementing classes. (Whereas if we injected // this class it would be an API change. That's why we're not doing it.) $conneg = new ContentNegotiation(); $format = $conneg->getContentType($event->getRequest()); 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); } } }