Commit d6f33f14 authored by Crell's avatar Crell

Merge branch 'kernel-fabpot-review' into kernel

parents e13dd9cb 14881552
......@@ -716,6 +716,7 @@ function drupal_not_found() {
throw new NotFoundHttpException();
// @todo Remove this line.
//drupal_deliver_page(MENU_NOT_FOUND);
}
......@@ -732,6 +733,7 @@ function drupal_access_denied() {
throw new AccessDeniedException();
// @todo Remove this line.
//drupal_deliver_page(MENU_ACCESS_DENIED);
}
......
<?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';
}
}
}
......@@ -5,17 +5,15 @@
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\RequestContext;
use Symfony\Component\HttpKernel\HttpKernelInterface;
use Symfony\Component\HttpKernel\HttpKernel;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\HttpKernel\Controller\ControllerResolver;
use Symfony\Component\EventDispatcher\EventDispatcher;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\EventDispatcher\Event;
use Symfony\Component\HttpKernel\Controller\ControllerResolverInterface;
use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent;
use Symfony\Component\HttpKernel\EventListener\RouterListener;
use Drupal\Core\EventSubscriber\HtmlSubscriber;
use Drupal\Core\EventSubscriber\JsonSubscriber;
use Symfony\Component\HttpKernel\EventListener\ExceptionListener;
use Drupal\Core\EventSubscriber\ViewSubscriber;
use Drupal\Core\EventSubscriber\AccessSubscriber;
use Drupal\Core\EventSubscriber\PathSubscriber;
use Drupal\Core\EventSubscriber\LegacyControllerSubscriber;
......@@ -31,80 +29,44 @@
/**
* The DrupalApp class is the core of Drupal itself.
*/
class DrupalKernel implements HttpKernelInterface {
/**
*
* @param Request $request
* The request to process.
* @return Response
* The response object to return to the requesting user agent.
*/
function handle(Request $request, $type = self::MASTER_REQUEST, $catch = true) {
try {
$dispatcher = $this->getDispatcher();
class DrupalKernel extends HttpKernel {
protected $dispatcher;
protected $resolver;
/**
* Constructor
*
* @param EventDispatcherInterface $dispatcher An EventDispatcherInterface instance
* @param ControllerResolverInterface $resolver A ControllerResolverInterface instance
*
* @api
*/
public function __construct(EventDispatcherInterface $dispatcher, ControllerResolverInterface $resolver) {
parent::__construct($dispatcher, $resolver);
$this->dispatcher = $dispatcher;
$this->resolver = $resolver;
$context = new RequestContext();
$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 ViewSubscriber($negotiation));
$this->dispatcher->addSubscriber(new AccessSubscriber());
$this->dispatcher->addSubscriber(new PathSubscriber());
$this->dispatcher->addSubscriber(new LegacyControllerSubscriber());
$matcher = $this->getMatcher($request);
$dispatcher->addSubscriber(new RouterListener($matcher));
$dispatcher->addSubscriber(new AccessSubscriber());
$dispatcher->addSubscriber(new PathSubscriber());
$dispatcher->addSubscriber(new LegacyControllerSubscriber());
$resolver = new ControllerResolver();
$kernel = new HttpKernel($dispatcher, $resolver);
$response = $kernel->handle($request);
}
catch (Exception $e) {
// Some other form of error occured that wasn't handled by another kernel
// listener. That could mean that it's a method/mime-type/error
// combination that is not accounted for, or some other type of error.
// 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.
$response = new Response('A fatal error occurred: ' . $e->getMessage(), 500);
$this->dispatcher->addSubscriber(new ExceptionListener(array(new ExceptionController($this, $negotiation), 'execute')));
}
return $response;
}
/**
* Returns an EventDispatcher for the Kernel to use.
*
* The EventDispatcher is pre-wired with some event listeners/subscribers.
*
* @todo Make the listeners that get attached extensible, but without using
* hooks.
*
* @return EventDispatcher
*/
protected function getDispatcher() {
$dispatcher = new EventDispatcher();
// @todo Make this extensible rather than just hard coding some.
// @todo Add a subscriber to handle other things, too, like our Ajax
// replacement system.
$dispatcher->addSubscriber(new HtmlSubscriber());
$dispatcher->addSubscriber(new JsonSubscriber());
return $dispatcher;
}
/**
* Returns a UrlMatcher object for the specified request.
*
* @param Request $request
* The request object for this matcher to use.
* @return UrlMatcher
*/
protected function getMatcher(Request $request) {
// Resolve a routing context(path, etc) using the routes object to a
// Set a routing context to translate.
$context = new RequestContext();
$context->fromRequest($request);
$matcher = new UrlMatcher($context);
return $matcher;
}
}
......@@ -23,7 +23,7 @@
class AccessSubscriber implements EventSubscriberInterface {
/**
* Verifys that the current user can access the requested path.
* Verifies that the current user can access the requested path.
*
* @todo This is a total hack to keep our current access system working. It
* should be replaced with something robust and injected at some point.
......@@ -35,8 +35,8 @@ public function onKernelRequestAccessCheck(GetResponseEvent $event) {
$router_item = $event->getRequest()->attributes->get('drupal_menu_item');
if (!$router_item['access']) {
throw new AccessDeniedHttpException($message);
if (isset($router_item['access']) && !$router_item['access']) {
throw new AccessDeniedHttpException();
}
}
......
<?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 an AccessDenied exception into an HTTP 403 response.
*
* @param GetResponseEvent $event
* The Event to process.
*/
public function onAccessDeniedException(GetResponseEvent $event) {
if ($this->isHtmlRequestEvent($event) && $event->getException() instanceof AccessDeniedHttpException) {
$event->setResponse(new Response('Access Denied', 403));
}
}
/**
* Processes a NotFound exception into an HTTP 404 response.
*
* @param GetResponseEvent $event
* The Event to process.
*/
public function onNotFoundHttpException(GetResponseEvent $event) {
if ($this->isHtmlRequestEvent($event) && $event->getException() instanceof NotFoundHttpException) {
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 = $event->getRequest()->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, DrupalKernel::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' => $event->getRequest()->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);
}
$event->setResponse($response);
}
}
/**
* Processes a MethodNotAllowed exception into an HTTP 405 response.
*
* @param GetResponseEvent $event
* The Event to process.
*/
public function onMethodAllowedException(GetResponseEvent $event) {
if ($this->isHtmlRequestEvent($event) && $event->getException() instanceof MethodNotAllowedException) {
$event->setResponse(new Response('Method Not Allowed', 405));
}
}
/**
* 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::EXCEPTION][] = array('onNotFoundHttpException', -5);
$events[KernelEvents::EXCEPTION][] = array('onAccessDeniedException', -5);
$events[KernelEvents::EXCEPTION][] = array('onMethodAllowedException', -5);
$events[KernelEvents::VIEW][] = array('onView', -5);
return $events;
}
}
......@@ -42,7 +42,7 @@ public function onKernelControllerLegacy(FilterControllerEvent $event) {
$controller = $event->getController();
// This BC logic applies only to functions. Otherwise, skip it.
if (function_exists($controller)) {
if (is_string($controller) && function_exists($controller)) {
$new_controller = function() use ($router_item) {
return call_user_func_array($router_item['page_callback'], $router_item['page_arguments']);
};
......
......@@ -11,27 +11,23 @@
use Symfony\Component\Routing\Exception\MethodNotAllowedException;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Drupal\Core\ContentNegotiation;
/**
* @file
*
* Definition of Drupal\Core\EventSubscriber\HtmlSubscriber;
* Definition of Drupal\Core\EventSubscriber\ViewSubscriber;
*/
/**
* Main subscriber for JSON-type HTTP responses.
* Main subscriber for VIEW HTTP responses.
*/
class JsonSubscriber implements EventSubscriberInterface {
class ViewSubscriber 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());
protected $negotiation;
public function __construct(ContentNegotiation $negotiation) {
$this->negotiation = $negotiation;
}
protected function createJsonResponse() {
......@@ -41,46 +37,42 @@ protected function createJsonResponse() {
return $response;
}
/**
* Processes an AccessDenied exception into an HTTP 403 response.
*
* @param GetResponseEvent $event
* The Event to process.
*/
public function onAccessDeniedException(GetResponseEvent $event) {
if ($this->isJsonRequestEvent($event) && $event->getException() instanceof AccessDeniedHttpException) {
$response = $this->createJsonResponse();
$response->setStatusCode(403, 'Access Denied');
$event->setResponse($response);
}
}
/**
* Processes a NotFound exception into an HTTP 404 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 onNotFoundHttpException(GetResponseEvent $event) {
if ($this->isJsonRequestEvent($event) && $event->getException() instanceof NotFoundHttpException) {
$response = $this->createJsonResponse();
$response->setStatusCode(404, 'Not Found');
$event->setResponse($response);
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));
}
}
/**
* Processes a MethodNotAllowed exception into an HTTP 405 response.
*
* @param GetResponseEvent $event
* The Event to process.
*/
public function onMethodAllowedException(GetResponseEvent $event) {
if ($this->isJsonRequestEvent($event) && $event->getException() instanceof MethodNotAllowedException) {
$response = $this->createJsonResponse();
$response->setStatusCode(405, 'Method Not Allowed');
$event->setResponse($response);
}
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;
}
/**
......@@ -89,21 +81,15 @@ public function onMethodAllowedException(GetResponseEvent $event) {
* 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.
* 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->isJsonRequestEvent($event)) {
$page_callback_result = $event->getControllerResult();
$response = $this->createJsonResponse();
$response->setContent($page_callback_result);
$event->setResponse($response);
}
public function onHtml(GetResponseEvent $event) {
$page_callback_result = $event->getControllerResult();
return new Response(drupal_render_page($page_callback_result));
}
/**
......@@ -113,10 +99,6 @@ public function onView(GetResponseEvent $event) {
* 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;
......
<?php
namespace Drupal\Core;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\HttpKernelInterface;
use Symfony\Component\EventDispatcher\EventDispatcher;
use Symfony\Component\HttpKernel\Controller\ControllerResolver;
use Symfony\Component\HttpKernel\Exception\FlattenException;
use Exception;
/**
* Description of ExceptionController
*
*/
class ExceptionController {
protected $kernel;
protected $negotiation;
public function __construct(HttpKernelInterface $kernel, ContentNegotiation $negotiation) {
$this->kernel = $kernel;
$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) {
$system_path = $request->attributes->get('system_path');
$path = drupal_get_normal_path(variable_get('site_403', ''));
if ($path && $path != $system_path) {
// Keep old path for reference, and to allow forms to redirect to it.
if (!isset($_GET['destination'])) {
$_GET['destination'] = $system_path;
}
$subrequest = Request::create('/' . $path, 'get', array('destination' => $system_path), $request->cookies->all(), array(), $request->server->all());
$response = $this->kernel->handle($subrequest, DrupalKernel::SUB_REQUEST);
$response->setStatusCode(403, 'Access denied');
}
else {
$response = new Response('Access Denied', 403);