diff --git a/core/lib/Drupal/Core/Routing/RouteCompiler.php b/core/lib/Drupal/Core/Routing/RouteCompiler.php index 771669a..3f3af5a 100644 --- a/core/lib/Drupal/Core/Routing/RouteCompiler.php +++ b/core/lib/Drupal/Core/Routing/RouteCompiler.php @@ -124,7 +124,7 @@ public static function getFit($path) { * @return string * The path string, stripped of placeholders that have default values. */ - protected static function getPathWithoutDefaults(Route $route) { + public static function getPathWithoutDefaults(Route $route) { $path = $route->getPath(); $defaults = $route->getDefaults(); diff --git a/core/modules/rest/lib/Drupal/rest/Plugin/views/display/RestExport.php b/core/modules/rest/lib/Drupal/rest/Plugin/views/display/RestExport.php index b8d7f3b..ea33a9e 100644 --- a/core/modules/rest/lib/Drupal/rest/Plugin/views/display/RestExport.php +++ b/core/modules/rest/lib/Drupal/rest/Plugin/views/display/RestExport.php @@ -8,6 +8,7 @@ namespace Drupal\rest\Plugin\views\display; +use Drupal\Core\Routing\RouteProviderInterface; use Drupal\views\Annotation\ViewsDisplay; use Drupal\Core\Annotation\Translation; use Drupal\Core\ContentNegotiation; @@ -99,9 +100,11 @@ class RestExport extends PathPluginBase { * The content negotiation library. * @param \Symfony\Component\HttpFoundation\Request $request * The request object. + * @param \Drupal\Core\Routing\RouteProviderInterface $route_provider + * The route provider */ - public function __construct(array $configuration, $plugin_id, array $plugin_definition, ContentNegotiation $content_negotiation, Request $request) { - parent::__construct($configuration, $plugin_id, $plugin_definition); + public function __construct(array $configuration, $plugin_id, array $plugin_definition, RouteProviderInterface $route_provider, ContentNegotiation $content_negotiation, Request $request) { + parent::__construct($configuration, $plugin_id, $plugin_definition, $route_provider); $this->contentNegotiation = $content_negotiation; $this->request = $request; } @@ -114,6 +117,7 @@ public static function create(ContainerInterface $container, array $configuratio $configuration, $plugin_id, $plugin_definition, + $container->get('router.route_provider'), $container->get('content_negotiation'), $container->get('request') ); diff --git a/core/modules/rest/tests/Drupal/rest/Tests/CollectRoutesTest.php b/core/modules/rest/tests/Drupal/rest/Tests/CollectRoutesTest.php index 7f27170..cd417b4 100644 --- a/core/modules/rest/tests/Drupal/rest/Tests/CollectRoutesTest.php +++ b/core/modules/rest/tests/Drupal/rest/Tests/CollectRoutesTest.php @@ -77,6 +77,11 @@ protected function setUp() { ->getMock(); $container->set('plugin.manager.views.access', $access_manager); + $route_provider = $this->getMockBuilder('\Drupal\Core\Routing\RouteProviderInterface') + ->disableOriginalConstructor() + ->getMock(); + $container->set('router.route_provider', $route_provider); + $style_manager = $this->getMockBuilder('\Drupal\views\Plugin\ViewsPluginManager') ->disableOriginalConstructor() ->getMock(); diff --git a/core/modules/user/lib/Drupal/user/Controller/UserAdmin.php b/core/modules/user/lib/Drupal/user/Controller/UserAdmin.php new file mode 100644 index 0000000..c5da477 --- /dev/null +++ b/core/modules/user/lib/Drupal/user/Controller/UserAdmin.php @@ -0,0 +1,162 @@ +connection = $connection; + $this->moduleHandler = $module_handler; + $this->storageController = $storage_controller; + $this->entityQuery = $entity_query; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container) { + return new static( + $container->get('database'), + $container->get('module_handler'), + $container->get('entity.manager')->getStorageController('user'), + $container->get('entity.query')->get('user') + ); + } + + /** + * User administrative listing. + * + * @return array + * A render array as expected by drupal_render(). + */ + public function userList() { + $header = array( + 'username' => array('data' => $this->t('Username'), 'field' => 'name', 'specifier' => 'name'), + 'status' => array('data' => $this->t('Status'), 'field' => 'status', 'specifier' => 'status', 'class' => array(RESPONSIVE_PRIORITY_LOW)), + 'roles' => array('data' => $this->t('Roles'), 'class' => array(RESPONSIVE_PRIORITY_LOW)), + 'member_for' => array('data' => $this->t('Member for'), 'field' => 'created', 'specifier' => 'created', 'sort' => 'desc', 'class' => array(RESPONSIVE_PRIORITY_LOW)), + 'access' => array('data' => $this->t('Last access'), 'field' => 'access', 'specifier' => 'access', 'class' => array(RESPONSIVE_PRIORITY_LOW)), + 'operations' => $this->t('Operations'), + ); + + $this->entityQuery->condition('uid', 0, '<>'); + $this->entityQuery->pager(50); + $this->entityQuery->tableSort($header); + $uids = $this->entityQuery->execute(); + $accounts = $this->storageController->loadMultiple($uids); + + $destination = drupal_get_destination(); + $status = array($this->t('blocked'), $this->t('active')); + $roles = array_map('\Drupal\Component\Utility\String::checkPlain', user_role_names(TRUE)); + unset($roles[DRUPAL_AUTHENTICATED_RID]); + $options = array(); + foreach ($accounts as $account) { + $users_roles = array(); + foreach ($account->getRoles() as $role) { + if (isset($roles[$role])) { + $users_roles[] = $roles[$role]; + } + } + asort($users_roles); + $options[$account->id()]['username']['data'] = array( + '#theme' => 'username', + '#account' => $account, + ); + $options[$account->id()]['status'] = $status[$account->isActive()]; + $options[$account->id()]['roles']['data'] = array( + '#theme' => 'item_list', + '#items' => $users_roles, + ); + $options[$account->id()]['member_for'] = format_interval(REQUEST_TIME - $account->getCreatedTime()); + $options[$account->id()]['access'] = $account->access ? $this->t('@time ago', array('@time' => format_interval(REQUEST_TIME - $account->getLastAccessedTime()))) : t('never'); + $links = array(); + $links['edit'] = array( + 'title' => $this->t('Edit'), + 'href' => 'user/' . $account->id() . '/edit', + 'query' => $destination, + ); + if ($this->moduleHandler->invoke('content_translation', 'translate_access', array($account))) { + $links['translate'] = array( + 'title' => $this->t('Translate'), + 'href' => 'user/' . $account->id() . '/translations', + 'query' => $destination, + ); + } + $options[$account->id()]['operations']['data'] = array( + '#type' => 'operations', + '#links' => $links, + ); + } + + $build['accounts'] = array( + '#theme' => 'table', + '#header' => $header, + '#rows' => $options, + '#empty' => $this->t('No people available.'), + ); + $build['pager'] = array( + '#theme' =>'pager', + ); + + return $build; + } + +} diff --git a/core/modules/user/user.admin.inc b/core/modules/user/user.admin.inc index 4caec43..9a6ca2a 100644 --- a/core/modules/user/user.admin.inc +++ b/core/modules/user/user.admin.inc @@ -6,91 +6,6 @@ */ /** - * Page callback: User administration page. - */ -function user_admin_account() { - $header = array( - 'username' => array('data' => t('Username'), 'field' => 'u.name'), - 'status' => array('data' => t('Status'), 'field' => 'u.status', 'class' => array(RESPONSIVE_PRIORITY_LOW)), - 'roles' => array('data' => t('Roles'), 'class' => array(RESPONSIVE_PRIORITY_LOW)), - 'member_for' => array('data' => t('Member for'), 'field' => 'u.created', 'sort' => 'desc', 'class' => array(RESPONSIVE_PRIORITY_LOW)), - 'access' => array('data' => t('Last access'), 'field' => 'u.access', 'class' => array(RESPONSIVE_PRIORITY_LOW)), - 'operations' => t('Operations'), - ); - - $query = db_select('users', 'u'); - $query->condition('u.uid', 0, '<>'); - - $count_query = clone $query; - $count_query->addExpression('COUNT(u.uid)'); - - $query = $query - ->extend('Drupal\Core\Database\Query\PagerSelectExtender') - ->extend('Drupal\Core\Database\Query\TableSortExtender'); - $query - ->fields('u', array('uid')) - ->limit(50) - ->orderByHeader($header) - ->setCountQuery($count_query); - $uids = $query->execute() - ->fetchCol('uid'); - - $destination = drupal_get_destination(); - $accounts = user_load_multiple($uids); - - foreach ($accounts as $account) { - $users_roles = $account->getRoles(); - unset($users_roles[0]); - asort($users_roles); - $username = array( - '#theme' => 'username', - '#account' => $account, - ); - $item_list = array( - '#theme' => 'item_list', - '#items' => $users_roles, - ); - $options[$account->id()] = array( - 'username' => drupal_render($username), - 'status' => $account->isActive() ? t('active') : t('blocked'), - 'roles' => drupal_render($item_list), - 'member_for' => format_interval(REQUEST_TIME - $account->getCreatedTime()), - 'access' => $account->getLastAccessedTime() ? t('@time ago', array('@time' => format_interval(REQUEST_TIME - $account->getLastAccessedTime()))) : t('never'), - ); - $links = array(); - $links['edit'] = array( - 'title' => t('Edit'), - 'href' => 'user/' . $account->id() . '/edit', - 'query' => $destination, - ); - if (module_invoke('content_translation', 'translate_access', $account)) { - $links['translate'] = array( - 'title' => t('Translate'), - 'href' => 'user/' . $account->id() . '/translations', - 'query' => $destination, - ); - } - $options[$account->id()]['operations']['data'] = array( - '#type' => 'operations', - '#links' => $links, - ); - - } - - $form['accounts'] = array( - '#theme' => 'table', - '#header' => $header, - '#rows' => $options, - '#empty' => t('No people available.'), - ); - $form['pager'] = array( - '#theme' =>'pager', - ); - - return $form; -} - -/** * Returns HTML for an individual permission description. * * @param $variables diff --git a/core/modules/user/user.local_actions.yml b/core/modules/user/user.local_actions.yml new file mode 100644 index 0000000..a68edb2 --- /dev/null +++ b/core/modules/user/user.local_actions.yml @@ -0,0 +1,6 @@ +user_admin_create: + route_name: user.admin_create + title: 'Add user' + appears_on: + - user.admin_account + - view.user_admin_people.page_1 diff --git a/core/modules/user/user.module b/core/modules/user/user.module index 24aee74..85d9ca5 100644 --- a/core/modules/user/user.module +++ b/core/modules/user/user.module @@ -778,11 +778,13 @@ function user_menu() { $items['admin/people'] = array( 'title' => 'People', 'description' => 'Manage user accounts, roles, and permissions.', - 'page callback' => 'user_admin_account', - 'access arguments' => array('administer users'), + 'route_name' => 'user.admin_account', 'position' => 'left', 'weight' => -4, - 'file' => 'user.admin.inc', + ); + $items['admin/people/list'] = array( + 'title' => 'List', + 'type' => MENU_DEFAULT_LOCAL_TASK, ); // Permissions and role forms. $items['admin/people/permissions'] = array( @@ -821,7 +823,7 @@ function user_menu() { $items['admin/people/create'] = array( 'title' => 'Add user', 'route_name' => 'user.admin_create', - 'type' => MENU_LOCAL_ACTION, + 'type' => MENU_SIBLING_LOCAL_TASK, ); // Administration pages. diff --git a/core/modules/user/user.routing.yml b/core/modules/user/user.routing.yml index d220da7..169343b 100644 --- a/core/modules/user/user.routing.yml +++ b/core/modules/user/user.routing.yml @@ -40,6 +40,13 @@ user.account_settings: requirements: _permission: 'administer account settings' +user.admin_account: + path: '/admin/people' + defaults: + _controller: '\Drupal\user\Controller\UserAdmin::userList' + requirements: + _permission: 'administer users' + user.admin_create: path: '/admin/people/create' defaults: diff --git a/core/modules/views/lib/Drupal/views/EventSubscriber/RouteSubscriber.php b/core/modules/views/lib/Drupal/views/EventSubscriber/RouteSubscriber.php index e3dbde3..0ff9d42 100644 --- a/core/modules/views/lib/Drupal/views/EventSubscriber/RouteSubscriber.php +++ b/core/modules/views/lib/Drupal/views/EventSubscriber/RouteSubscriber.php @@ -7,25 +7,82 @@ namespace Drupal\views\EventSubscriber; +use Drupal\Core\Entity\EntityManager; use Drupal\Core\Routing\RouteBuildEvent; use Drupal\Core\Routing\RoutingEvents; use Drupal\views\Plugin\views\display\DisplayRouterInterface; +use Drupal\views\ViewExecutable; use Symfony\Component\EventDispatcher\EventSubscriberInterface; /** * Builds up the routes of all views. + * + * The general idea done here is to first execute + * + * @see \Drupal\views\Plugin\views\display\PathPluginBase */ class RouteSubscriber implements EventSubscriberInterface { /** + * Stores a list of view,display IDs containing routes and still has to be + * added. + * + * @var array + */ + protected $viewsDisplayPairs; + + /** + * The view storage controller. + * + * @var \Drupal\Core\Entity\EntityStorageControllerInterface + */ + protected $viewStorageController; + + /** + * Constructs a \Drupal\views\EventSubscriber\RouteSubscriber instance. + * + * @param \Drupal\Core\Entity\EntityManager $entity_manager + * The entity manager. + */ + public function __construct(EntityManager $entity_manager) { + $this->viewStorageController = $entity_manager->getStorageController('view'); + } + + /** + * Resets the internal state of the route subscriber + */ + public function reset() { + $this->viewsDisplayPairs = NULL; + } + + /** * {@inheritdoc} */ public static function getSubscribedEvents() { $events[RoutingEvents::DYNAMIC] = 'dynamicRoutes'; + $events[RoutingEvents::ALTER] = 'alterRoutes'; return $events; } /** + * Gets all the views and display IDs using a route. + */ + protected function getViewsDisplayIDsWithRoute() { + if (!isset($this->viewsDisplayPairs)) { + $this->viewsDisplayPairs = array(); + + // @todo Convert this method to some service. + $views = $this->getApplicableViews(); + foreach ($views as $data) { + list($view, $display_id) = $data; + $id = $view->storage->id(); + $this->viewsDisplayPairs[$id . '.' . $display_id] = $id . '.' . $display_id; + } + } + return $this->viewsDisplayPairs; + } + + /** * Adds routes defined by all views. * * @param \Drupal\Core\Routing\RouteBuildEvent $event @@ -34,16 +91,55 @@ public static function getSubscribedEvents() { public function dynamicRoutes(RouteBuildEvent $event) { $collection = $event->getRouteCollection(); - $views = views_get_applicable_views('uses_route'); - foreach ($views as $data) { - list($view, $display_id) = $data; - if ($view->setDisplay($display_id) && $display = $view->displayHandlers->get($display_id)) { - if ($display instanceof DisplayRouterInterface) { - $display->collectRoutes($collection); + foreach ($this->getViewsDisplayIDsWithRoute() as $pair) { + list($view_id, $display_id) = explode('.', $pair); + $view = $this->viewStorageController->load($view_id); + // @todo This should have a executable factory injected. + if (($view = $view->getExecutable()) && $view instanceof ViewExecutable) { + if ($view->setDisplay($display_id) && $display = $view->displayHandlers->get($display_id)) { + if ($display instanceof DisplayRouterInterface) { + $display->collectRoutes($collection); + } } + $view->destroy(); } - $view->destroy(); } } + /** + * Alters existing routes. + * + * @param \Drupal\Core\Routing\RouteBuildEvent $event + * The route building event. + */ + public function alterRoutes(RouteBuildEvent $event) { + foreach ($this->getViewsDisplayIDsWithRoute() as $pair) { + list($view_id, $display_id) = explode('.', $pair); + $view = $this->viewStorageController->load($view_id); + // @todo This should have a executable factory injected. + if (($view = $view->getExecutable()) && $view instanceof ViewExecutable) { + if ($view->setDisplay($display_id) && $display = $view->displayHandlers->get($display_id)) { + if ($display instanceof DisplayRouterInterface) { + // If the display returns TRUE a route item was found, so it does not + // have to be added. + $result = $display->alterRoutes($event->getRouteCollection()); + foreach ($result as $id_display) { + unset($this->viewsDisplayPairs[$id_display]); + } + } + } + $view->destroy(); + } + } + } + + /** + * Returns all views/display combinations with routes. + * + * @see views_get_applicable_views() + */ + protected function getApplicableViews() { + return views_get_applicable_views('uses_route'); + } + } diff --git a/core/modules/views/lib/Drupal/views/Plugin/views/display/DisplayRouterInterface.php b/core/modules/views/lib/Drupal/views/Plugin/views/display/DisplayRouterInterface.php index 68cbe68..3878bee 100644 --- a/core/modules/views/lib/Drupal/views/Plugin/views/display/DisplayRouterInterface.php +++ b/core/modules/views/lib/Drupal/views/Plugin/views/display/DisplayRouterInterface.php @@ -7,13 +7,14 @@ namespace Drupal\views\Plugin\views\display; +use Symfony\Component\Routing\RouteCollection; + /** * Defines an interface for displays that can collect routes. * * In addition to implementing the interface, specify 'uses_routes' in the * plugin definition. */ -use Symfony\Component\Routing\RouteCollection; interface DisplayRouterInterface { @@ -25,4 +26,17 @@ */ public function collectRoutes(RouteCollection $collection); + /** + * Alters a collection of routes and replaces definitions to the view. + * + * Most of the collections won't have the needed route, so by the return value + * the method can specify to break the search. + * + * @param \Symfony\Component\Routing\RouteCollection $collection + * + * @return array + * Returns a list of "$view_id.$display_id" elements which got overridden. + */ + public function alterRoutes(RouteCollection $collection); + } 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 79795d2..7038d29 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 @@ -7,8 +7,11 @@ namespace Drupal\views\Plugin\views\display; +use Drupal\Core\Routing\RouteCompiler; +use Drupal\Core\Routing\RouteProviderInterface; use Drupal\views\Views; +use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; use Symfony\Component\Routing\Route; @@ -16,10 +19,49 @@ /** * The base display plugin for path/callbacks. This is used for pages and feeds. + * + * @see \Drupal\views\EventSubscriber\RouteSubscriber */ abstract class PathPluginBase extends DisplayPluginBase implements DisplayRouterInterface { /** + * The route provider. + * + * @var \Drupal\Core\Routing\RouteProviderInterface + */ + protected $routeProvider; + + /** + * Constructs a PathPluginBase object. + * + * @param array $configuration + * A configuration array containing information about the plugin instance. + * @param string $plugin_id + * The plugin_id for the plugin instance. + * @param array $plugin_definition + * The plugin implementation definition. + * @param \Drupal\Core\Routing\RouteProviderInterface $route_provider + * The route provider. + */ + public function __construct(array $configuration, $plugin_id, array $plugin_definition, RouteProviderInterface $route_provider) { + parent::__construct($configuration, $plugin_id, $plugin_definition); + + $this->routeProvider = $route_provider; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container, array $configuration, $plugin_id, array $plugin_definition) { + return new static( + $configuration, + $plugin_id, + $plugin_definition, + $container->get('router.route_provider') + ); + } + + /** * Overrides \Drupal\views\Plugin\views\display\DisplayPluginBase::hasPath(). */ public function hasPath() { @@ -60,12 +102,17 @@ protected function defineOptions() { } /** - * {@inheritdoc} + * Generates a route entry for a given view and display. + * + * @param string $view_id + * The ID of the view. + * @param string $display_id + * The current display ID. + * + * @return \Symfony\Component\Routing\Route + * The route for the view. */ - public function collectRoutes(RouteCollection $collection) { - $view_id = $this->view->storage->id(); - $display_id = $this->display['id']; - + protected function getRoute($view_id, $display_id) { $defaults = array( '_controller' => 'Drupal\views\Routing\ViewPageController::handle', 'view_id' => $view_id, @@ -80,7 +127,7 @@ public function collectRoutes(RouteCollection $collection) { $arg_counter = 0; $this->view->initHandlers(); - $view_arguments = $this->view->argument; + $view_arguments = (array) $this->view->argument; $argument_ids = array_keys($view_arguments); $total_arguments = count($argument_ids); @@ -126,11 +173,52 @@ public function collectRoutes(RouteCollection $collection) { $access_plugin = Views::pluginManager('access')->createInstance('none'); } $access_plugin->alterRouteDefinition($route); + return $route; + } + + /** + * {@inheritdoc} + */ + public function collectRoutes(RouteCollection $collection) { + $view_id = $this->view->storage->id(); + $display_id = $this->display['id']; + + $route = $this->getRoute($view_id, $display_id); $collection->add("view.$view_id.$display_id", $route); } /** + * {@inheritdoc} + */ + public function alterRoutes(RouteCollection $collection) { + $altered_views = array(); + $view_path = $this->getPath(); + foreach ($collection->all() as $name => $route) { + // Find all paths which match the path of the current display.. + $route_path = RouteCompiler::getPathWithoutDefaults($route); + $route_path = RouteCompiler::getPatternOutline($route_path); + // Ensure that we don't override a route which is already controlled by + // views. + if (!$route->hasDefault('view_id') && '/' . $view_path == $route_path) { + // @todo Figure out whether we need to merge some settings (like + // requirements). + + // Replace the existing route with a new one based on views. + $collection->remove($name); + + $view_id = $this->view->storage->id(); + $display_id = $this->display['id']; + $route = $this->getRoute($view_id, $display_id); + $collection->add($name, $route); + $altered_views[] = $view_id . '.' . $display_id; + } + } + + return $altered_views; + } + + /** * Overrides \Drupal\views\Plugin\views\display\DisplayPluginBase::executeHookMenu(). */ public function executeHookMenu($callbacks) { diff --git a/core/modules/views/lib/Drupal/views/Tests/Plugin/DisplayPageTest.php b/core/modules/views/lib/Drupal/views/Tests/Plugin/DisplayPageTest.php index f597199..66b5e8d 100644 --- a/core/modules/views/lib/Drupal/views/Tests/Plugin/DisplayPageTest.php +++ b/core/modules/views/lib/Drupal/views/Tests/Plugin/DisplayPageTest.php @@ -93,7 +93,7 @@ public function testPageResponses() { * Checks that the router items are properly registered */ public function testPageRouterItems() { - $subscriber = new RouteSubscriber(); + $subscriber = new RouteSubscriber($this->container->get('entity.manager')); $collection = new RouteCollection(); $subscriber->dynamicRoutes(new RouteBuildEvent($collection, 'dynamic_routes')); diff --git a/core/modules/views/tests/Drupal/views/Tests/EventSubscriber/RouteSubscriberTest.php b/core/modules/views/tests/Drupal/views/Tests/EventSubscriber/RouteSubscriberTest.php new file mode 100644 index 0000000..dd22adb --- /dev/null +++ b/core/modules/views/tests/Drupal/views/Tests/EventSubscriber/RouteSubscriberTest.php @@ -0,0 +1,188 @@ + 'Views route subscriber', + 'description' => 'Tests the views route subscriber.', + 'group' => 'Views plugins', + ); + } + + protected function setUp() { + $this->entityManager = $this->getMockBuilder('\Drupal\Core\Entity\EntityManager') + ->disableOriginalConstructor() + ->getMock(); + $this->viewStorageController = $this->getMockBuilder('\Drupal\views\ViewStorageController') + ->disableOriginalConstructor() + ->getMock(); + $this->entityManager->expects($this->any()) + ->method('getStorageController') + ->with('view') + ->will($this->returnValue($this->viewStorageController)); + $this->routeSubscriber = new TestRouteSubscriber($this->entityManager); + } + + /** + * Tests the dynamicRoutes method. + * + * @see \Drupal\views\EventSubscriber\RouteSubscriber::dynamicRoutes() + */ + public function testDynamicRoutes() { + $collection = new RouteCollection(); + $route_event = new RouteBuildEvent($collection, 'views'); + + list($view, $executable, $display_1, $display_2) = $this->setupMocks(); + + $display_1->expects($this->once()) + ->method('collectRoutes'); + $display_2->expects($this->once()) + ->method('collectRoutes'); + + $this->assertNull($this->routeSubscriber->dynamicRoutes($route_event)); + } + + /** + * Tests the alterRoutes method. + * + * @see \Drupal\views\EventSubscriber\RouteSubscriber::alterRoutes() + */ + public function testAlterRoutes() { + $collection = new RouteCollection(); + $collection->add('test_route', new Route('test_route', array('_controller' => 'Drupal\Tests\Core\Controller\TestController'))); + $route_2 = new Route('test_route/example', array('_controller' => 'Drupal\Tests\Core\Controller\TestController')); + $collection->add('test_route_2', $route_2); + + $route_event = new RouteBuildEvent($collection, 'views'); + + list($view, $executable, $display_1, $display_2) = $this->setupMocks(); + + // The page_1 display overrides an existing route, so the dynamicRoutes + // should only call the second display. + $display_1->expects($this->once()) + ->method('alterRoutes') + ->will($this->returnValue(array('test_id.page_1'))); + $display_1->expects($this->never()) + ->method('collectRoutes'); + + $display_2->expects($this->once()) + ->method('alterRoutes') + ->will($this->returnValue(array())); + $display_2->expects($this->once()) + ->method('collectRoutes'); + + $this->assertNull($this->routeSubscriber->alterRoutes($route_event)); + + // Ensure that after the alterRoutes the collectRoutes method is just called + // once (not for page_1 anymore). + + $this->assertNull($this->routeSubscriber->dynamicRoutes($route_event)); + } + + protected function setupMocks() { + $executable = $this->getMockBuilder('Drupal\views\ViewExecutable') + ->disableOriginalConstructor() + ->getMock(); + $view = $this->getMockBuilder('Drupal\views\Entity\View') + ->disableOriginalConstructor() + ->getMock(); + $this->viewStorageController->expects($this->any()) + ->method('load') + ->will($this->returnValue($view)); + + $view->expects($this->any()) + ->method('getExecutable') + ->will($this->returnValue($executable)); + $view->expects($this->any()) + ->method('id') + ->will($this->returnValue('test_id')); + $executable->storage = $view; + + $executable->expects($this->any()) + ->method('setDisplay') + ->will($this->returnValueMap(array( + array('page_1', TRUE), + array('page_2', TRUE), + array('page_3', FALSE), + ))); + + // Ensure that only the first two displays are actually called. + $display_1 = $this->getMock('Drupal\views\Plugin\views\display\DisplayRouterInterface'); + $display_2 = $this->getMock('Drupal\views\Plugin\views\display\DisplayRouterInterface'); + + $display_bag = $this->getMockBuilder('Drupal\views\DisplayBag') + ->disableOriginalConstructor() + ->getMock(); + $display_bag->expects($this->any()) + ->method('get') + ->will($this->returnValueMap(array( + array('page_1', $display_1), + array('page_2', $display_2), + ))); + $executable->displayHandlers = $display_bag; + + $this->routeSubscriber->applicableViews = array(); + $this->routeSubscriber->applicableViews[] = array($executable, 'page_1'); + $this->routeSubscriber->applicableViews[] = array($executable, 'page_2'); + $this->routeSubscriber->applicableViews[] = array($executable, 'page_3'); + + return array($executable, $view, $display_1, $display_2); + } + +} + +class TestRouteSubscriber extends RouteSubscriber { + + /** + * The applicable views. + */ + public $applicableViews; + + /** + * {@inheritdoc} + */ + protected function getApplicableViews() { + return $this->applicableViews; + } + +} diff --git a/core/modules/views/tests/Drupal/views/Tests/Plugin/display/PathPluginBaseTest.php b/core/modules/views/tests/Drupal/views/Tests/Plugin/display/PathPluginBaseTest.php new file mode 100644 index 0000000..b030d08 --- /dev/null +++ b/core/modules/views/tests/Drupal/views/Tests/Plugin/display/PathPluginBaseTest.php @@ -0,0 +1,178 @@ + 'Display: Path plugin base.', + 'description' => 'Tests the abstract base class for path based display plugins.', + 'group' => 'Views Plugins', + ); + } + + /** + * {@inheritdoc} + */ + protected function setUp() { + parent::setUp(); + + $this->routeProvider = $this->getMock('Drupal\Core\Routing\RouteProviderInterface'); + $this->pathPlugin = $this->getMockBuilder('Drupal\views\Plugin\views\display\PathPluginBase') + ->setConstructorArgs(array(array(), 'path_base', array(), $this->routeProvider)) + ->setMethods(NULL) + ->getMock(); + $this->setupAccessPluginManager(); + } + + /** + * Setup access plugin manager in a Drupal class. + */ + public function setupAccessPluginManager() { + $this->accessPluginManager = $this->getMockBuilder('\Drupal\views\Plugin\ViewsPluginManager') + ->disableOriginalConstructor() + ->getMock(); + $container = new ContainerBuilder(); + $container->set('plugin.manager.views.access', $this->accessPluginManager); + \Drupal::setContainer($container); + } + + /** + * Tests the collectRoutes method. + * + * @see \Drupal\views\Plugin\views\display\PathPluginBase::collectRoutes() + */ + public function testCollectRoutes() { + list($view) = $this->setupViewExecutableAccessPlugin(); + + $display = array(); + $display['display_plugin'] = 'page'; + $display['id'] = 'page_1'; + $display['display_options'] = array( + 'path' => 'test_route', + ); + $this->pathPlugin->initDisplay($view, $display); + + $collection = new RouteCollection(); + $this->pathPlugin->collectRoutes($collection); + + $route = $collection->get('view.test_id.page_1'); + $this->assertTrue($route instanceof Route); + $this->assertEquals('test_id', $route->getDefault('view_id')); + $this->assertEquals('page_1', $route->getDefault('display_id')); + + } + + /** + * Tests the alter route method. + */ + public function testAlterRoute() { + $collection = new RouteCollection(); + $collection->add('test_route', new Route('test_route', array('_controller' => 'Drupal\Tests\Core\Controller\TestController'))); + $route_2 = new Route('test_route/example', array('_controller' => 'Drupal\Tests\Core\Controller\TestController')); + $collection->add('test_route_2', $route_2); + + list($view) = $this->setupViewExecutableAccessPlugin(); + + $display = array(); + $display['display_plugin'] = 'page'; + $display['id'] = 'page_1'; + $display['display_options'] = array( + 'path' => 'test_route', + ); + $this->pathPlugin->initDisplay($view, $display); + + $found_views = $this->pathPlugin->alterRoutes($collection); + $this->assertEquals(array('test_id.page_1'), $found_views); + + // Ensure that the test_route is overridden. + $route = $collection->get('test_route'); + $this->assertTrue($route instanceof Route); + $this->assertEquals('test_id', $route->getDefault('view_id')); + $this->assertEquals('page_1', $route->getDefault('display_id')); + + // Ensure that the test_route_2 is not overridden. + $route = $collection->get('test_route_2'); + $this->assertTrue($route instanceof Route); + $this->assertFalse($route->hasDefault('view_id')); + $this->assertFalse($route->hasDefault('display_id')); + $this->assertSame($collection->get('test_route_2'), $route_2); + } + + /** + * Returns some mocked view entity, view executable, and access plugin. + */ + protected function setupViewExecutableAccessPlugin() { + $view_entity = $this->getMockBuilder('Drupal\views\Entity\View') + ->disableOriginalConstructor() + ->getMock(); + $view_entity->expects($this->any()) + ->method('id') + ->will($this->returnValue('test_id')); + + $view = $this->getMockBuilder('Drupal\views\ViewExecutable') + ->disableOriginalConstructor() + ->getMock(); + $view->storage = $view_entity; + + // Skip views options caching. + $view->editing = TRUE; + + $access_plugin = $this->getMockBuilder('Drupal\views\Plugin\views\access\AccessPluginBase') + ->disableOriginalConstructor() + ->getMockForAbstractClass(); + $this->accessPluginManager->expects($this->any()) + ->method('createInstance') + ->will($this->returnValue($access_plugin)); + + return array($view, $view_entity, $access_plugin); + } + +} + +} + +namespace { + if (!function_exists('views_get_enabled_display_extenders')) { + function views_get_enabled_display_extenders() { + return array(); + } + } +} diff --git a/core/modules/views/views.module b/core/modules/views/views.module index 7a6b346..94d31e8 100644 --- a/core/modules/views/views.module +++ b/core/modules/views/views.module @@ -706,6 +706,9 @@ function views_invalidate_cache() { $module_handler = \Drupal::moduleHandler(); + // Reset the RouteSubscriber from views. + \Drupal::getContainer()->get('views.route_subscriber')->reset(); + // Set the router to be rebuild. // @todo Figure out why the cache rebuild is trigged but the route table // does not exist yet. diff --git a/core/modules/views/views.services.yml b/core/modules/views/views.services.yml index c77a27d..ee1b500 100644 --- a/core/modules/views/views.services.yml +++ b/core/modules/views/views.services.yml @@ -83,7 +83,7 @@ services: arguments: [views_results] views.route_subscriber: class: Drupal\views\EventSubscriber\RouteSubscriber - arguments: ['@config.factory'] + arguments: ['@entity.manager'] tags: - { name: 'event_subscriber' } views.route_access_check: diff --git a/core/modules/views_ui/tests/Drupal/views_ui/Tests/ViewListControllerTest.php b/core/modules/views_ui/tests/Drupal/views_ui/Tests/ViewListControllerTest.php index ba82c0d..958eadf 100644 --- a/core/modules/views_ui/tests/Drupal/views_ui/Tests/ViewListControllerTest.php +++ b/core/modules/views_ui/tests/Drupal/views_ui/Tests/ViewListControllerTest.php @@ -77,9 +77,10 @@ public function testBuildRowEntityList() { array('initDisplay'), array(array(), 'default', $display_manager->getDefinition('default')) ); + $route_provider = $this->getMock('Drupal\Core\Routing\RouteProviderInterface'); $page_display = $this->getMock('Drupal\views\Plugin\views\display\Page', array('initDisplay', 'getPath'), - array(array(), 'default', $display_manager->getDefinition('page')) + array(array(), 'default', $display_manager->getDefinition('page'), $route_provider) ); $page_display->expects($this->any()) ->method('getPath')