diff --git a/core/core.services.yml b/core/core.services.yml index 891d0b7..4583366 100644 --- a/core/core.services.yml +++ b/core/core.services.yml @@ -853,6 +853,10 @@ services: arguments: ['@class_resolver', '@current_route_match', '%main_content_renderers%'] tags: - { name: event_subscriber } + accept_negotiation_406: + class: Drupal\Core\EventSubscriber\AcceptNegotiation406 + tags: + - { name: event_subscriber, priority: -10 } main_content_renderer.html: class: Drupal\Core\Render\MainContent\HtmlRenderer arguments: ['@title_resolver', '@plugin.manager.display_variant', '@event_dispatcher', '@element_info', '@module_handler', '@renderer', '@render_cache', '@cache_contexts_manager'] @@ -885,11 +889,6 @@ services: tags: - { name: event_subscriber } arguments: ['@router', '@router.request_context', NULL, '@request_stack'] - view_subscriber: - class: Drupal\Core\EventSubscriber\ViewSubscriber - arguments: ['@title_resolver'] - tags: - - { name: event_subscriber } bare_html_page_renderer: class: Drupal\Core\Render\BareHtmlPageRenderer arguments: ['@renderer'] diff --git a/core/lib/Drupal/Core/EventSubscriber/AcceptNegotiation406.php b/core/lib/Drupal/Core/EventSubscriber/AcceptNegotiation406.php new file mode 100644 index 0000000..db13594 --- /dev/null +++ b/core/lib/Drupal/Core/EventSubscriber/AcceptNegotiation406.php @@ -0,0 +1,47 @@ +getRequest(); + $result = $event->getControllerResult(); + + // If this is a render array then we assume that the router went with the + // generic controller and not one with a format. If the format requested is + // not HTML though we can also assume that the requested format is invalid + // so we provide a 406 response. + if (is_array($result) && $request->getRequestFormat() !== 'html') { + $result = new JsonResponse([ + 'message' => 'Not Acceptable.', + ], 406); + $event->setResponse($result); + } + } + + /** + * {@inheritdoc} + */ + static function getSubscribedEvents() { + $events[KernelEvents::VIEW][] = ['onViewDetect406', -10]; + + return $events; + } + +} diff --git a/core/lib/Drupal/Core/EventSubscriber/MainContentViewSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/MainContentViewSubscriber.php index cbdce65..0617064 100644 --- a/core/lib/Drupal/Core/EventSubscriber/MainContentViewSubscriber.php +++ b/core/lib/Drupal/Core/EventSubscriber/MainContentViewSubscriber.php @@ -10,8 +10,6 @@ use Drupal\Core\DependencyInjection\ClassResolverInterface; use Drupal\Core\Routing\RouteMatchInterface; use Symfony\Component\EventDispatcher\EventSubscriberInterface; -use Symfony\Component\HttpFoundation\JsonResponse; -use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\Event\GetResponseForControllerResultEvent; use Symfony\Component\HttpKernel\KernelEvents; @@ -84,23 +82,13 @@ public function onViewRenderArray(GetResponseForControllerResultEvent $event) { $result = $event->getControllerResult(); // Render the controller result into a response if it's a render array. - if (is_array($result)) { - // We default to getRequestFormat() because content negotiation uses this as - // its fallback method for displaying a 406 when we fail to match a route - // based on format. - // @todo this logic should be replaced with a more clear opt in to avoid - // cache poisoning. https://www.drupal.org/node/2364011 - $wrapper = $request->query->get(static::WRAPPER_ENVELOPE, $request->getRequestFormat()); - if (isset($this->mainContentRenderers[$wrapper])) { - $renderer = $this->classResolver->getInstanceFromDefinition($this->mainContentRenderers[$wrapper]); - $event->setResponse($renderer->renderResponse($result, $request, $this->routeMatch)); - } - else { - $event->setResponse(new JsonResponse([ - 'message' => 'Not Acceptable.', - 'supported_wrapper_envelopes' => array_keys($this->mainContentRenderers), - ], 406)); - } + if (is_array($result) && ($request->query->has(static::WRAPPER_ENVELOPE) || $request->getRequestFormat() == 'html')) { + $wrapper = $request->query->get(static::WRAPPER_ENVELOPE, 'html'); + // Fall back on HTML if we don't have a renderer. + $wrapper = isset($this->mainContentRenderers[$wrapper]) ? $wrapper : 'html'; + + $renderer = $this->classResolver->getInstanceFromDefinition($this->mainContentRenderers[$wrapper]); + $event->setResponse($renderer->renderResponse($result, $request, $this->routeMatch)); } } diff --git a/core/lib/Drupal/Core/EventSubscriber/ViewSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/ViewSubscriber.php deleted file mode 100644 index 3485f19..0000000 --- a/core/lib/Drupal/Core/EventSubscriber/ViewSubscriber.php +++ /dev/null @@ -1,92 +0,0 @@ -titleResolver = $title_resolver; - } - - /** - * 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(); - - if ($event->getRequestType() == HttpKernelInterface::MASTER_REQUEST) { - $method = 'on' . $request->getRequestFormat(); - - if (method_exists($this, $method)) { - $event->setResponse($this->$method($event)); - } - else { - $event->setResponse(new Response('Not Acceptable', 406)); - } - } - } - - public function onJson(GetResponseForControllerResultEvent $event) { - $page_callback_result = $event->getControllerResult(); - - $response = new JsonResponse(); - $response->setData($page_callback_result); - - return $response; - } - - /** - * Registers the methods in this class that should be listeners. - * - * @return array - * An array of event listener definitions. - */ - static function getSubscribedEvents() { - $events[KernelEvents::VIEW][] = array('onView'); - - return $events; - } -} diff --git a/core/misc/ajax.js b/core/misc/ajax.js index 1e26c9d..1c24c7e 100644 --- a/core/misc/ajax.js +++ b/core/misc/ajax.js @@ -17,13 +17,6 @@ Drupal.ajax = Drupal.ajax || {}; /** - * Url query attribute to indicate the wrapper used to render a request. - * - * This controls wrapping the HTML content for example in a modal dialog. - */ - Drupal.ajax.WRAPPER_ENVELOPE = '_wrapper_format'; - - /** * Attaches the Ajax behavior to each Ajax form element. */ Drupal.behaviors.AJAX = { @@ -305,6 +298,13 @@ }; /** + * Url query attribute to indicate the wrapper used to render a request. + * + * This controls wrapping the HTML content for example in a modal dialog. + */ + Drupal.ajax.WRAPPER_ENVELOPE = '_wrapper_format'; + + /** * Handle a key press. * * The Ajax object will, if instructed, bind to a key press response. This diff --git a/core/modules/rest/src/Tests/ReadTest.php b/core/modules/rest/src/Tests/ReadTest.php index f54c7de..6d8ff11 100644 --- a/core/modules/rest/src/Tests/ReadTest.php +++ b/core/modules/rest/src/Tests/ReadTest.php @@ -92,7 +92,9 @@ public function testRead() { // and hence when there is no matching REST route, the non-REST route is // used, but it can't render into application/hal+json, so it returns a 406. $this->assertResponse('406', 'HTTP response code is 406 when the resource does not define formats, because it falls back to the canonical, non-REST route.'); - $this->assertTrue(strpos($response, '{"message":"Not Acceptable.","supported_wrapper_envelopes":') !== FALSE); + $this->assertEqual($response, Json::encode([ + 'message' => 'Not Acceptable.', + ])); } /**