diff --git a/core/lib/Drupal/Core/ContentNegotiation.php b/core/lib/Drupal/Core/ContentNegotiation.php new file mode 100644 index 0000000000000000000000000000000000000000..f1a33d724d232b5e1d4dbdf0465c4ac4b9cb3348 --- /dev/null +++ b/core/lib/Drupal/Core/ContentNegotiation.php @@ -0,0 +1,24 @@ +<?php + +namespace Drupal\Core; + +use Symfony\Component\HttpFoundation\Request; + +/** + * Description of ContentNegotiation + * + */ +class ContentNegotiation { + + public function getContentType(Request $request) { + $acceptable_content_types = $request->getAcceptableContentTypes(); + if (in_array('application/json', $request->getAcceptableContentTypes())) { + return 'json'; + } + if(in_array('text/html', $acceptable_content_types) || in_array('*/*', $acceptable_content_types)) { + return 'html'; + } + } + +} + diff --git a/core/lib/Drupal/Core/DrupalKernel.php b/core/lib/Drupal/Core/DrupalKernel.php index d601f7d7fef61da329b1181d5d93d2029d35004d..605f2ce84e85b7db2334b3f1bbdd684aff59a891 100644 --- a/core/lib/Drupal/Core/DrupalKernel.php +++ b/core/lib/Drupal/Core/DrupalKernel.php @@ -13,8 +13,7 @@ use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent; use Symfony\Component\HttpKernel\EventListener\RouterListener; use Symfony\Component\HttpKernel\EventListener\ExceptionListener; -use Drupal\Core\EventSubscriber\HtmlSubscriber; -use Drupal\Core\EventSubscriber\JsonSubscriber; +use Drupal\Core\EventSubscriber\ViewSubscriber; use Drupal\Core\EventSubscriber\AccessSubscriber; use Drupal\Core\EventSubscriber\PathSubscriber; use Drupal\Core\EventSubscriber\LegacyControllerSubscriber; @@ -52,11 +51,12 @@ public function __construct(EventDispatcherInterface $dispatcher, ControllerReso $this->matcher = new UrlMatcher($context); $this->dispatcher->addSubscriber(new RouterListener($this->matcher)); + $negotiation = new ContentNegotiation(); + // @todo Make this extensible rather than just hard coding some. // @todo Add a subscriber to handle other things, too, like our Ajax // replacement system. - $this->dispatcher->addSubscriber(new HtmlSubscriber()); - $this->dispatcher->addSubscriber(new JsonSubscriber()); + $this->dispatcher->addSubscriber(new ViewSubscriber($negotiation)); $this->dispatcher->addSubscriber(new AccessSubscriber()); $this->dispatcher->addSubscriber(new PathSubscriber()); $this->dispatcher->addSubscriber(new LegacyControllerSubscriber()); @@ -67,6 +67,6 @@ public function __construct(EventDispatcherInterface $dispatcher, ControllerReso // Either way, treat it as a server-level error and return an HTTP 500. // By default, this will be an HTML-type response because that's a decent // best guess if we don't know otherwise. - $this->dispatcher->addSubscriber(new ExceptionListener(array(new ExceptionController(), 'execute'))); + $this->dispatcher->addSubscriber(new ExceptionListener(array(new ExceptionController($negotiation), 'execute'))); } } diff --git a/core/lib/Drupal/Core/EventSubscriber/HtmlSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/HtmlSubscriber.php deleted file mode 100644 index e10f97d68db2948a7b95fd13394f93ba3f6316ad..0000000000000000000000000000000000000000 --- a/core/lib/Drupal/Core/EventSubscriber/HtmlSubscriber.php +++ /dev/null @@ -1,77 +0,0 @@ -<?php - -namespace Drupal\Core\EventSubscriber; - -use Symfony\Component\HttpFoundation\Request; -use Symfony\Component\HttpFoundation\Response; -use Symfony\Component\HttpKernel\KernelEvents; -use Symfony\Component\HttpKernel\Event\GetResponseEvent; -use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; -use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; -use Symfony\Component\Routing\Exception\ResourceNotFoundException; -use Symfony\Component\Routing\Exception\MethodNotAllowedException; -use Symfony\Component\EventDispatcher\EventSubscriberInterface; - -use Drupal\Core\DrupalKernel; - -/** - * @file - * - * Definition of Drupal\Core\EventSubscriber\HtmlSubscriber; - */ - -/** - * Main subscriber for HTML-type HTTP responses. - */ -class HtmlSubscriber implements EventSubscriberInterface { - - /** - * Determines if we are dealing with an HTML-style response. - * - * @param GetResponseEvent $event - * The Event to process. - * @return boolean - * True if it is an event we should process as HTML, False otherwise. - */ - protected function isHtmlRequestEvent(GetResponseEvent $event) { - $acceptable_content_types = $event->getRequest()->getAcceptableContentTypes(); - return in_array('text/html', $acceptable_content_types) || in_array('*/*', $acceptable_content_types); - } - - /** - * 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 HTML-type response is a render array from a legacy page callback - * and render it. - * - * @param GetResponseEvent $event - * The Event to process. - */ - public function onView(GetResponseEvent $event) { - if ($this->isHtmlRequestEvent($event)) { - $page_callback_result = $event->getControllerResult(); - $event->setResponse(new Response(drupal_render_page($page_callback_result))); - } - else { - $event->setResponse(new Response('Unsupported Media Type', 415)); - } - } - - /** - * Registers the methods in this class that should be listeners. - * - * @return array - * An array of event listener definitions. - */ - static function getSubscribedEvents() { - // Since we want HTML to be our default, catch-all response type, give its - // listeners a very low priority so that they always check last. - - $events[KernelEvents::VIEW][] = array('onView', -5); - - return $events; - } -} diff --git a/core/lib/Drupal/Core/EventSubscriber/JsonSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/JsonSubscriber.php deleted file mode 100644 index cc64290cc7bbab30d1c5c9c50a20ce7d534ef9f6..0000000000000000000000000000000000000000 --- a/core/lib/Drupal/Core/EventSubscriber/JsonSubscriber.php +++ /dev/null @@ -1,76 +0,0 @@ -<?php - -namespace Drupal\Core\EventSubscriber; - -use Symfony\Component\HttpFoundation\Response; -use Symfony\Component\HttpKernel\KernelEvents; -use Symfony\Component\HttpKernel\Event\GetResponseEvent; -use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; -use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; -use Symfony\Component\Routing\Exception\ResourceNotFoundException; -use Symfony\Component\Routing\Exception\MethodNotAllowedException; -use Symfony\Component\EventDispatcher\EventSubscriberInterface; - -/** - * @file - * - * Definition of Drupal\Core\EventSubscriber\HtmlSubscriber; - */ - -/** - * Main subscriber for JSON-type HTTP responses. - */ -class JsonSubscriber implements EventSubscriberInterface { - - /** - * Determines if we are dealing with an JSON-style response. - * - * @param GetResponseEvent $event - * The Event to process. - * @return boolean - * True if it is an event we should process as JSON, False otherwise. - */ - protected function isJsonRequestEvent(GetResponseEvent $event) { - return in_array('application/json', $event->getRequest()->getAcceptableContentTypes()); - } - - - /** - * 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 GetResponseEvent $event - * The Event to process. - */ - public function onView(GetResponseEvent $event) { - if ($this->isJsonRequestEvent($event)) { - $page_callback_result = $event->getControllerResult(); - - $response = $this->createJsonResponse(); - $response->setContent($page_callback_result); - - $event->setResponse($response); - } - } - - /** - * Registers the methods in this class that should be listeners. - * - * @return array - * An array of event listener definitions. - */ - static function getSubscribedEvents() { - //$events[KernelEvents::EXCEPTION][] = array('onNotFoundHttpException'); - //$events[KernelEvents::EXCEPTION][] = array('onAccessDeniedException'); - //$events[KernelEvents::EXCEPTION][] = array('onMethodAllowedException'); - - $events[KernelEvents::VIEW][] = array('onView'); - - return $events; - } -} diff --git a/core/lib/Drupal/Core/EventSubscriber/ViewSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/ViewSubscriber.php new file mode 100644 index 0000000000000000000000000000000000000000..8ade061551a526456eaa1e30c9b13165d7063263 --- /dev/null +++ b/core/lib/Drupal/Core/EventSubscriber/ViewSubscriber.php @@ -0,0 +1,106 @@ +<?php + +namespace Drupal\Core\EventSubscriber; + +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\KernelEvents; +use Symfony\Component\HttpKernel\Event\GetResponseEvent; +use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; +use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; +use Symfony\Component\Routing\Exception\ResourceNotFoundException; +use Symfony\Component\Routing\Exception\MethodNotAllowedException; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; + +use Drupal\Core\ContentNegotiation; + +/** + * @file + * + * Definition of Drupal\Core\EventSubscriber\ViewSubscriber; + */ + +/** + * Main subscriber for VIEW HTTP responses. + */ +class ViewSubscriber implements EventSubscriberInterface { + + protected $negotiation; + + public function __construct(ContentNegotiation $negotiation) { + $this->negotiation = $negotiation; + } + + protected function createJsonResponse() { + $response = new Response(); + $response->headers->set('Content-Type', 'application/json; charset=utf-8'); + + return $response; + } + + + /** + * 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 GetResponseEvent $event + * The Event to process. + */ + public function onView(GetResponseEvent $event) { + + $request = $event->getRequest(); + + $method = 'on' . $this->negotiation->getContentType($request); + + if (method_exists($this, $method)) { + $event->setResponse($this->$method($event)); + } + else { + $event->setResponse(new Response('Unsupported Media Type', 415)); + } + } + + public function onJson(GetResponseEvent $event) { + $page_callback_result = $event->getControllerResult(); + + print_r($page_callback_result); + + $response = $this->createJsonResponse(); + $response->setContent($page_callback_result); + + return $response; + } + + /** + * 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 HTML-type response is a render array from a legacy page callback + * and render it. + * + * @param GetResponseEvent $event + * The Event to process. + */ + public function onHtml(GetResponseEvent $event) { + $page_callback_result = $event->getControllerResult(); + return new Response(drupal_render_page($page_callback_result)); + } + + /** + * 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/lib/Drupal/Core/ExceptionController.php b/core/lib/Drupal/Core/ExceptionController.php new file mode 100644 index 0000000000000000000000000000000000000000..5c867c727bc363bb0e9e61c513a44508f135e58a --- /dev/null +++ b/core/lib/Drupal/Core/ExceptionController.php @@ -0,0 +1,141 @@ +<?php + + +namespace Drupal\Core; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; + +use Symfony\Component\HttpKernel\HttpKernelInterface; +use Symfony\Component\HttpKernel\Exception\FlattenException; + +use Exception; + +/** + * Description of ExceptionController + * + */ +class ExceptionController { + + protected $negotiation; + + public function __construct(ContentNegotiation $negotiation) { + $this->negotiation = $negotiation; + } + + 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()); + + } + + /** + * Processes a MethodNotAllowed exception into an HTTP 405 response. + * + * @param GetResponseEvent $event + * The Event to process. + */ + public function on405Html(FlattenException $exception, Request $request) { + $event->setResponse(new Response('Method Not Allowed', 405)); + } + + /** + * Processes an AccessDenied exception into an HTTP 403 response. + * + * @param GetResponseEvent $event + * The Event to process. + */ + public function on403Html(FlattenException $exception, Request $request) { + return new Response('Access Denied', 403); + } + + public function on404Html(FlattenException $exception, Request $request) { + watchdog('page not found', check_plain($_GET['q']), NULL, WATCHDOG_WARNING); + + // Check for and return a fast 404 page if configured. + // @todo Inline this rather than using a function. + drupal_fast_404(); + + $system_path = $request->attributes->get('system_path'); + + // Keep old path for reference, and to allow forms to redirect to it. + if (!isset($_GET['destination'])) { + $_GET['destination'] = $system_path; + } + + $path = drupal_get_normal_path(variable_get('site_404', '')); + if ($path && $path != $system_path) { + // @TODO: Um, how do I specify an override URL again? Totally not clear. + // Do that and sub-call the kernel rather than using meah(). + $request = Request::create($path); + + $kernel = new DrupalKernel(); + $response = $kernel->handle($request, HttpKernelInterface::SUB_REQUEST); + $response->setStatusCode(404, 'Not Found'); + } + else { + $response = new Response('Not Found', 404); + + // @todo Replace this block with something cleaner. + $return = t('The requested page "@path" could not be found.', array('@path' => $request->getPathInfo())); + drupal_set_title(t('Page not found')); + drupal_set_page_content($return); + $page = element_info('page'); + $content = drupal_render_page($page); + + $response->setContent($content); + } + + return $response; + } + + protected function createJsonResponse() { + $response = new Response(); + $response->headers->set('Content-Type', 'application/json; charset=utf-8'); + + return $response; + } + + /** + * Processes an AccessDenied exception into an HTTP 403 response. + * + * @param GetResponseEvent $event + * The Event to process. + */ + public function on403Json(FlattenException $exception, Request $request) { + $response = $this->createJsonResponse(); + $response->setStatusCode(403, 'Access Denied'); + return $response; + } + + /** + * Processes a NotFound exception into an HTTP 404 response. + * + * @param GetResponseEvent $event + * The Event to process. + */ + public function on404Json(FlattenException $exception, Request $request) { + $response = $this->createJsonResponse(); + $response->setStatusCode(404, 'Not Found'); + return $response; + } + + /** + * Processes a MethodNotAllowed exception into an HTTP 405 response. + * + * @param GetResponseEvent $event + * The Event to process. + */ + public function on405Json(FlattenException $exception, Request $request) { + $response = $this->createJsonResponse(); + $response->setStatusCode(405, 'Method Not Allowed'); + return $response; + } + +}