diff --git a/.htaccess b/.htaccess index a69bdd4..ca69678 100644 --- a/.htaccess +++ b/.htaccess @@ -95,7 +95,7 @@ DirectoryIndex index.php index.html index.htm # # If your site is running in a VirtualDocumentRoot at http://example.com/, # uncomment the following line: - # RewriteBase / + #RewriteBase / # Redirect common PHP files to their new locations. RewriteCond %{REQUEST_URI} ^(.*)?/(update.php) [OR] @@ -109,7 +109,7 @@ DirectoryIndex index.php index.html index.htm RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-d RewriteCond %{REQUEST_URI} !=/favicon.ico - RewriteRule ^ index.php [L] + RewriteRule ^(.*)$ index.php [L] # Rules to correctly serve gzip compressed CSS and JS files. # Requires both mod_rewrite and mod_headers to be enabled. diff --git a/core/includes/common.inc b/core/includes/common.inc index 52a3a43..54a290c 100644 --- a/core/includes/common.inc +++ b/core/includes/common.inc @@ -2200,31 +2200,20 @@ function url($path = NULL, array $options = array()) { $base = $options['absolute'] ? $options['base_url'] . '/' : base_path(); $prefix = empty($path) ? rtrim($options['prefix'], '/') : $options['prefix']; - // With Clean URLs. - if (!empty($GLOBALS['conf']['clean_url'])) { - $path = drupal_encode_path($prefix . $path); - if ($options['query']) { - return $base . $path . '?' . drupal_http_build_query($options['query']) . $options['fragment']; - } - else { - return $base . $path . $options['fragment']; - } + // If Clean URLs are not enabled, we need to prefix the script name onto + // the link. + // @todo: Make this dynamic based on the request object without using a global + // request object. + if (empty($GLOBALS['conf']['clean_url'])) { + $base .= 'index.php/'; + } + + $path = drupal_encode_path($prefix . $path); + if ($options['query']) { + return $base . $path . '?' . drupal_http_build_query($options['query']) . $options['fragment']; } - // Without Clean URLs. else { - $path = $prefix . $path; - $query = array(); - if (!empty($path)) { - $query['q'] = $path; - } - if ($options['query']) { - // We do not use array_merge() here to prevent overriding $path via query - // parameters. - $query += $options['query']; - } - $query = $query ? ('?' . drupal_http_build_query($query)) : ''; - $script = isset($options['script']) ? $options['script'] : ''; - return $base . $script . $query . $options['fragment']; + return $base . $path . $options['fragment']; } } diff --git a/core/lib/Drupal/Core/DrupalKernel.php b/core/lib/Drupal/Core/DrupalKernel.php new file mode 100644 index 0000000..9a6fce5 --- /dev/null +++ b/core/lib/Drupal/Core/DrupalKernel.php @@ -0,0 +1,108 @@ +getDispatcher(); + + $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); + } + + 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()); + + 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; + } +} diff --git a/core/lib/Drupal/Core/EventSubscriber/AccessSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/AccessSubscriber.php new file mode 100644 index 0000000..88ce042 --- /dev/null +++ b/core/lib/Drupal/Core/EventSubscriber/AccessSubscriber.php @@ -0,0 +1,54 @@ +getRequest()->attributes->get('drupal_menu_item'); + + if (!$router_item['access']) { + throw new AccessDeniedHttpException($message); + } + } + + /** + * Registers the methods in this class that should be listeners. + * + * @return array + * An array of event listener definitions. + */ + static function getSubscribedEvents() { + $events[KernelEvents::REQUEST][] = array('onKernelRequestAccessCheck', 30); + + return $events; + } +} diff --git a/core/lib/Drupal/Core/EventSubscriber/HtmlSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/HtmlSubscriber.php new file mode 100644 index 0000000..3c2e67f --- /dev/null +++ b/core/lib/Drupal/Core/EventSubscriber/HtmlSubscriber.php @@ -0,0 +1,107 @@ +getRequest()->getAcceptableContentTypes()); + } + + /** + * 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) { + $event->setResponse(new Response('Not Found', 404)); + } + } + + /** + * 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))); + } + } + + /** + * 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/LegacyControllerSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/LegacyControllerSubscriber.php new file mode 100644 index 0000000..e85d89f --- /dev/null +++ b/core/lib/Drupal/Core/EventSubscriber/LegacyControllerSubscriber.php @@ -0,0 +1,64 @@ +getRequest()->attributes->get('drupal_menu_item'); + $controller = $event->getController(); + + // This BC logic applies only to functions. Otherwise, skip it. + if (function_exists($controller)) { + $new_controller = function() use ($router_item) { + return call_user_func_array($router_item['page_callback'], $router_item['page_arguments']); + }; + $event->setController($new_controller); + } + } + + /** + * Registers the methods in this class that should be listeners. + * + * @return array + * An array of event listener definitions. + */ + static function getSubscribedEvents() { + $events[KernelEvents::CONTROLLER][] = array('onKernelControllerLegacy', 30); + + return $events; + } +} diff --git a/core/lib/Drupal/Core/EventSubscriber/PathSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/PathSubscriber.php new file mode 100644 index 0000000..1487128 --- /dev/null +++ b/core/lib/Drupal/Core/EventSubscriber/PathSubscriber.php @@ -0,0 +1,77 @@ +getRequest(); + + $path = ltrim($request->getPathInfo(), '/'); + + // Temporary BC shiv to support automated tests that still rely on old- + // style dirty URLs. + if (isset($_GET['q'])) { + $path = $_GET['q']; + $reflection = new \ReflectionObject($request); + $property = $reflection->getProperty('pathInfo'); + $property->setAccessible(TRUE); + $property->setValue($request, '/' . $path); + } + + if (empty($path)) { + // @todo Temporary hack. Fix when configuration is injectable. + $path = variable_get('site_frontpage', 'user'); + } + $system_path = drupal_get_normal_path($path); + + $request->attributes->set('system_path', $system_path); + + // @todo Remove this line. + // Drupal uses $_GET['q'] directly in over 100 places at present, + // including writing back to it at times. Those are all critical bugs, + // even by Drupal 7 standards, but as many of the places that it does so + // are slated to be rewritten anyway we will save time and include this + // temporary hack. Removal of this line is a critical, Drupal-release + // blocking bug. + $_GET['q'] = $system_path; + } + + /** + * Registers the methods in this class that should be listeners. + * + * @return array + * An array of event listener definitions. + */ + static function getSubscribedEvents() { + $events[KernelEvents::REQUEST][] = array('onKernelRequestPathResolve', 100); + + return $events; + } +} diff --git a/core/lib/Drupal/Core/UrlMatcher.php b/core/lib/Drupal/Core/UrlMatcher.php new file mode 100644 index 0000000..366405a --- /dev/null +++ b/core/lib/Drupal/Core/UrlMatcher.php @@ -0,0 +1,99 @@ +context = $context; + } + + /** + * {@inheritDoc} + * + * @api + */ + public function match($pathinfo) { + + $this->allow = array(); + + // Symfony uses a prefixing / but we don't yet. + $dpathinfo = ltrim($pathinfo, '/'); + + // Do our fancy frontpage logic. + if (empty($dpathinfo)) { + $dpathinfo = variable_get('site_frontpage', 'user'); + $pathinfo = '/' . $dpathinfo; + } + + if ($router_item = $this->matchDrupalItem($dpathinfo)) { + + $routes = new RouteCollection(); + $routes->add(hash('sha256', $router_item['path']), $this->convertDrupalItem($router_item)); + + if ($ret = $this->matchCollection($pathinfo, $routes)) { + //drupal_set_message('
' . var_export('test', TRUE) . '
'); + // Stash the router item in the attributes while we're transitioning. + $ret['drupal_menu_item'] = $router_item; + + // Most legacy controllers (aka page callbacks) are in a separate file, + // so we have to include that. + if ($router_item['include_file']) { + require_once DRUPAL_ROOT . '/' . $router_item['include_file']; + } + + return $ret; + } + } + + throw 0 < count($this->allow) + ? new MethodNotAllowedException(array_unique(array_map('strtoupper', $this->allow))) + : new ResourceNotFoundException(); + } + + /** + * Get a drupal menu item. + * + * @todo Make this return multiple possible candidates for the resolver to + * consider. + * + * @param string $path + * The path being looked up by + */ + protected function matchDrupalItem($path) { + // For now we can just proxy our procedural method. At some point this will + // become more complicated because we'll need to get back candidates for a + // path and them resolve them based on things like method and scheme which + // we currently can't do. + return menu_get_item($path); + } + + protected function convertDrupalItem($router_item) { + $route = array( + '_controller' => $router_item['page_callback'] + ); + // Place argument defaults on the route. + foreach ($router_item['page_arguments'] as $k => $v) { + $route[$k] = $v; + } + return new Route($router_item['href'], $route); + } +} diff --git a/index.php b/index.php index b91fb1e..7609b3e 100644 --- a/index.php +++ b/index.php @@ -1,5 +1,8 @@ handle($request)->send();