diff --git a/core/modules/views/lib/Drupal/views/EventSubscriber/RouteSubscriber.php b/core/modules/views/lib/Drupal/views/EventSubscriber/RouteSubscriber.php new file mode 100644 index 0000000..f33b9b0 --- /dev/null +++ b/core/modules/views/lib/Drupal/views/EventSubscriber/RouteSubscriber.php @@ -0,0 +1,43 @@ +getRouteCollection(); + + $views = views_get_applicable_views('uses_hook_menu'); + foreach ($views as $data) { + list($view, $display_id) = $data; + $view->collectRoutes($display_id, $collection); + } + } + +} diff --git a/core/modules/views/lib/Drupal/views/Plugin/views/display/DisplayPluginBase.php b/core/modules/views/lib/Drupal/views/Plugin/views/display/DisplayPluginBase.php index 0c4d150..fac3deb 100644 --- a/core/modules/views/lib/Drupal/views/Plugin/views/display/DisplayPluginBase.php +++ b/core/modules/views/lib/Drupal/views/Plugin/views/display/DisplayPluginBase.php @@ -9,6 +9,7 @@ use Drupal\views\ViewExecutable; use \Drupal\views\Plugin\views\PluginBase; +use Symfony\Component\Routing\RouteCollection; /** * @defgroup views_display_plugins Views display plugins @@ -2433,6 +2434,17 @@ public function renderEmpty() { } /** + * Adds the route entry of a view to the collection.# + * + * @param \Symfony\Component\Routing\RouteCollection $collection + * A collection of routes that should be registered for this resource. + */ + public function collectRoutes(RouteCollection $collection) { + + } + + + /** * If this display creates a page with a menu item, implement it here. */ public function hookMenu() { return array(); } diff --git a/core/modules/views/lib/Drupal/views/Plugin/views/display/PathPluginBase.php b/core/modules/views/lib/Drupal/views/Plugin/views/display/PathPluginBase.php index 8a0a85e..8a62af6 100644 --- a/core/modules/views/lib/Drupal/views/Plugin/views/display/PathPluginBase.php +++ b/core/modules/views/lib/Drupal/views/Plugin/views/display/PathPluginBase.php @@ -9,6 +9,8 @@ use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; +use Symfony\Component\Routing\Route; +use Symfony\Component\Routing\RouteCollection; /** * The base display plugin for path/callbacks. This is used for pages and feeds. @@ -33,6 +35,50 @@ protected function defineOptions() { } /** + * Overrides \Drupal\views\Plugin\views\display\DisplayPluginBase::collectRoutes(). + */ + public function collectRoutes(RouteCollection $collection) { + $view_id = $this->view->storage->id(); + $display_id = $this->display['id']; + + // @todo How do we apply argument validation? + $bits = explode('/', $this->getOption('path')); + // @todo Figure out validation/argument loading. + // Replace % with %views_arg for menu autoloading and add to the + // page arguments so the argument actually comes through. + $arg_counter = 0; + foreach ($bits as $pos => &$bit) { + if ($bit == '%') { + $bit = '{arg' . $arg_counter++ . ' }'; + } + } + $route_path = '/' . implode('/', $bits); + + $route = new Route($route_path, array( + '_content' => 'views.page_controller:handle', + 'view_id' => $view_id, + 'display_id' => $display_id, + )); + + // Add access check parameters to the route. + $access_plugin = $this->getPlugin('access'); + if (!isset($access_plugin)) { + // @todo Do we want to support a default plugin in getPlugin itself? + $access_plugin = drupal_container()->get('plugin.manager.views.access')->createInstance('none'); + } + $callback = $access_plugin->get_access_callback(); + if (is_array($callback)) { + list($access_callback, $args) = $callback; + $route->addOptions(array( + '_access_callback' => $access_callback, + '_access_arguments' => $args, + )); + } + + $collection->add("$view_id.$display_id", $route); + } + + /** * Add this display's path information to Drupal's menu system. */ public function executeHookMenu($callbacks) { @@ -97,13 +143,13 @@ public function executeHookMenu($callbacks) { if ($path) { $items[$path] = array( // Default views page entry. - 'page callback' => 'views_page', - 'page arguments' => $page_arguments, + // _menu_router_build() denies access to paths without a page callback. + 'page callback' => 'NOT_USED', // Default access check (per display). - 'access callback' => 'views_access', - 'access arguments' => $access_arguments, + // 'access callback' => 'views_access', + // 'access arguments' => $access_arguments, // Identify URL embedded arguments and correlate them to a handler. - 'load arguments' => array($this->view->storage->id(), $this->display['id'], '%index'), + // 'load arguments' => array($this->view->storage->id(), $this->display['id'], '%index'), ); $menu = $this->getOption('menu'); if (empty($menu)) { @@ -158,14 +204,13 @@ public function executeHookMenu($callbacks) { $default_path = implode('/', $bits); $items[$default_path] = array( // Default views page entry. - 'page callback' => 'views_page', - 'page arguments' => $page_arguments, + 'page callback' => 'NOT_USED', // Default access check (per display). - 'access callback' => 'views_access', - 'access arguments' => $access_arguments, + // 'access callback' => 'views_access', + // 'access arguments' => $access_arguments, // Identify URL embedded arguments and correlate them to a // handler. - 'load arguments' => array($this->view->storage->id(), $this->display['id'], '%index'), + // 'load arguments' => array($this->view->storage->id(), $this->display['id'], '%index'), 'title' => $tab_options['title'], 'description' => $tab_options['description'], 'menu_name' => $tab_options['name'], diff --git a/core/modules/views/lib/Drupal/views/ViewExecutable.php b/core/modules/views/lib/Drupal/views/ViewExecutable.php index 3a82440..463dfbf 100644 --- a/core/modules/views/lib/Drupal/views/ViewExecutable.php +++ b/core/modules/views/lib/Drupal/views/ViewExecutable.php @@ -9,6 +9,7 @@ use Symfony\Component\HttpFoundation\Response; use Drupal\views\Plugin\Core\Entity\View; +use Symfony\Component\Routing\RouteCollection; /** * @defgroup views_objects Objects that represent a View or part of a view @@ -1458,6 +1459,30 @@ public function attachDisplays() { } /** + * Called to collect router entries from the view and the display_handler. + * + * @param string $display_id + * A display id. + * @param \Symfony\Component\Routing\RouteCollection $collection + * A menu callback array passed from views_menu_alter(). + * + * @return bool + * Returns FALSE if the display has not been found, TRUE otherwise. + */ + public function collectRoutes($display_id = NULL, RouteCollection $collection = NULL) { + + // This was probably already called, but it's good to be safe. + if (!$this->setDisplay($display_id)) { + return FALSE; + } + + if (isset($this->display_handler)) { + $this->display_handler->collectRoutes($collection); + return TRUE; + } + } + + /** * Called to get hook_menu() information from the view and the named display handler. * * @param $display_id diff --git a/core/modules/views/lib/Drupal/views/ViewPageController.php b/core/modules/views/lib/Drupal/views/ViewPageController.php new file mode 100644 index 0000000..7391add --- /dev/null +++ b/core/modules/views/lib/Drupal/views/ViewPageController.php @@ -0,0 +1,56 @@ +entityManager = $entity_manager; + } + + /** + * Handles a response for a view. + * + * @param string $view_id + * The ID of the view to execute. + * @param string $display_id + * The ID of the display to execute. + */ + public function handle($view_id, $display_id, $arg0 = NULL, $arg1 = NULL, $arg2 = NULL, $arg3 = NULL, $arg4 = NULL, $arg5 = NULL, $arg6 = NULL, $arg7 = NULL) { + // @todo: Handle args!! + $entities = $this->entityManager->getStorageController('view')->load(array($view_id)); + $entity = reset($entities); + if ($view = $entity->get('executable')) { + $func_args = func_get_args(); + // Drop view and display ID. + array_shift($func_args); + array_shift($func_args); + $args = array_filter($func_args, function($item) { + return $item !== NULL; + }); + + return $view->executeDisplay($display_id, $args); + } + } + +} diff --git a/core/modules/views/lib/Drupal/views/ViewsAccessCheck.php b/core/modules/views/lib/Drupal/views/ViewsAccessCheck.php new file mode 100644 index 0000000..15e5f43 --- /dev/null +++ b/core/modules/views/lib/Drupal/views/ViewsAccessCheck.php @@ -0,0 +1,40 @@ +getDefault('view_id') && $route->getDefault('display_id'); + } + + /** + * Implements \Drupal\Core\Access\AccessCheckInterface::applies(). + */ + public function access(Route $route, Request $request) { + $access_callback = $route->getOption('access_callback'); + $access_arguments = $route->getOption('access_arguments'); + + if (is_callable($access_callback)) { + return call_user_func($access_callback, $access_arguments); + } + // If there is no access check for the view, allow it. + return TRUE; + } + +} diff --git a/core/modules/views/lib/Drupal/views/ViewsBundle.php b/core/modules/views/lib/Drupal/views/ViewsBundle.php index dfb6dd6..1a737b5 100644 --- a/core/modules/views/lib/Drupal/views/ViewsBundle.php +++ b/core/modules/views/lib/Drupal/views/ViewsBundle.php @@ -37,6 +37,16 @@ public function build(ContainerBuilder $container) { ->addArgument(new Reference('config.factory')); $container->register('views.executable', 'Drupal\views\ViewExecutableFactory'); + + $container->register('views.page_controller', 'Drupal\views\ViewPageController') + ->addArgument(new Reference('plugin.manager.entity')); + + $container->register('views.route_subscriber', 'Drupal\views\EventSubscriber\RouteSubscriber') + ->addArgument(new Reference('config.factory')) + ->addTag('event_subscriber'); + + $container->register('views.route_access_check', 'Drupal\views\ViewsAccessCheck') + ->addTag('access_check'); } } diff --git a/core/modules/views/views.module b/core/modules/views/views.module index dc565b8..6d320af 100644 --- a/core/modules/views/views.module +++ b/core/modules/views/views.module @@ -694,6 +694,8 @@ function views_invalidate_cache() { // Set the menu as needed to be rebuilt. state()->set('menu_rebuild_needed', TRUE); + // Set the router to be rebuild. +// drupal_container()->get('router.builder')->rebuild(); // Invalidate the block cache to update views block derivatives. if (module_exists('block')) {