diff --git a/core/modules/views/src/Form/ViewsExposedForm.php b/core/modules/views/src/Form/ViewsExposedForm.php index 72f754b..5e2eee9 100644 --- a/core/modules/views/src/Form/ViewsExposedForm.php +++ b/core/modules/views/src/Form/ViewsExposedForm.php @@ -11,6 +11,7 @@ use Drupal\Component\Utility\String; use Drupal\Core\Form\FormBase; use Drupal\Core\Form\FormStateInterface; +use Drupal\Core\Url; use Drupal\views\ExposedFormCache; use Symfony\Component\DependencyInjection\ContainerInterface; @@ -115,7 +116,7 @@ public function buildForm(array $form, FormStateInterface $form_state) { '#id' => drupal_html_id('edit-submit-' . $view->storage->id()), ); - $form['#action'] = _url($view->display_handler->getUrl()); + $form['#action'] = $view->hasUrl() ? $view->getUrl()->toString() : Url::fromRoute('')->toString(); $form['#theme'] = $view->buildThemeFunctions('views_exposed_form'); $form['#id'] = Html::cleanCssIdentifier('views_exposed_form-' . String::checkPlain($view->storage->id()) . '-' . String::checkPlain($display['id'])); diff --git a/core/modules/views/src/Form/ViewsForm.php b/core/modules/views/src/Form/ViewsForm.php index c1296b2..cdf39eb 100644 --- a/core/modules/views/src/Form/ViewsForm.php +++ b/core/modules/views/src/Form/ViewsForm.php @@ -133,7 +133,8 @@ public function buildForm(array $form, FormStateInterface $form_state, ViewExecu $query = $this->requestStack->getCurrentRequest()->query->all(); $query = UrlHelper::filterQueryParameters($query, array(), ''); - $form['#action'] = $this->urlGenerator->generateFromPath($view->getUrl(), array('query' => $query)); + $options = array('query' => $query); + $form['#action'] = $view->hasUrl() ? $view->getUrl()->setOptions($options)->toString() : Url::fromRoute('')->setOptions($options)->toString(); // Tell the preprocessor whether it should hide the header, footer, pager... $form['show_view_elements'] = array( '#type' => 'value', diff --git a/core/modules/views/src/Plugin/views/display/DisplayPluginBase.php b/core/modules/views/src/Plugin/views/display/DisplayPluginBase.php index 7595977..f6bf9e1 100644 --- a/core/modules/views/src/Plugin/views/display/DisplayPluginBase.php +++ b/core/modules/views/src/Plugin/views/display/DisplayPluginBase.php @@ -726,8 +726,28 @@ public function getPath() { /** * {@inheritdoc} */ + public function getRoutedDisplay() { + // If this display has a route, return this display. + if ($this instanceof DisplayRouterInterface) { + return $this; + } + + // If the display does not have a route (e.g. a block display), get the + // route for the linked display. + $display_id = $this->getLinkDisplay(); + if ($display_id && $this->view->displayHandlers->has($display_id) && is_object($this->view->displayHandlers->get($display_id))) { + return $this->view->displayHandlers->get($display_id)->getRoutedDisplay(); + } + + // No routed display exists, so return null + return null; + } + + /** + * {@inheritdoc} + */ public function getUrl() { - return $this->view->getUrl(); + return $this->view->getUrl(NULL, $this->display['id']); } /** @@ -1973,27 +1993,31 @@ public function renderPager() { */ public function renderMoreLink() { if ($this->isMoreEnabled() && ($this->useMoreAlways() || (!empty($this->view->pager) && $this->view->pager->hasMoreRecords()))) { - $path = $this->getPath(); - + // If the user has supplied a custom "More" link path, replace any + // argument tokens and use that for the URL. if ($this->getOption('link_display') == 'custom_url' && $override_path = $this->getOption('link_url')) { $tokens = $this->getArgumentsTokens(); $path = $this->viewsTokenReplace($override_path, $tokens); + $url = Url::fromUri('user-path:/' . $path); + } + // Otherwise, use the URL for the display. + else { + $url = $this->view->getUrl(NULL, $this->display['id']); } - if ($path) { - if (empty($override_path)) { - $path = $this->view->getUrl(NULL, $path); - } + // If a URL is available (either from the display or a custom path), + // render the "More" link. + if ($url) { $url_options = array(); if (!empty($this->view->exposed_raw_input)) { $url_options['query'] = $this->view->exposed_raw_input; } + $url->setOptions($url_options); $theme = $this->view->buildThemeFunctions('views_more'); - $path = check_url(_url($path, $url_options)); return array( '#theme' => $theme, - '#more_url' => $path, + '#more_url' => $url->toString(), '#link_text' => String::checkPlain($this->useMoreText()), '#view' => $this->view, ); diff --git a/core/modules/views/src/Plugin/views/display/DisplayPluginInterface.php b/core/modules/views/src/Plugin/views/display/DisplayPluginInterface.php index e84348c..ab930ce 100644 --- a/core/modules/views/src/Plugin/views/display/DisplayPluginInterface.php +++ b/core/modules/views/src/Plugin/views/display/DisplayPluginInterface.php @@ -213,6 +213,23 @@ public function getLinkDisplay(); */ public function getPath(); + /** + * Points to the display which can be linked by this display. + * + * If the display has route information, the display itself is returned. + * Otherwise, the configured linked display is returned. For example, if a + * block display links to a page display, the page display will be returned + * in both cases. + * + * @return \Drupal\views\Plugin\views\display\DisplayRouterInterface|null + */ + public function getRoutedDisplay(); + + /** + * Returns a URL to $this display or its configured linked display. + * + * @return \Drupal\Core\Url|null + */ public function getUrl(); /** diff --git a/core/modules/views/src/Plugin/views/display/DisplayRouterInterface.php b/core/modules/views/src/Plugin/views/display/DisplayRouterInterface.php index 6223c52..65328ed 100644 --- a/core/modules/views/src/Plugin/views/display/DisplayRouterInterface.php +++ b/core/modules/views/src/Plugin/views/display/DisplayRouterInterface.php @@ -46,4 +46,29 @@ public function alterRoutes(RouteCollection $collection); */ public function getUrlInfo(); + /** + * Returns the route name for the display. + * + * The default route name for a display is views.$view_id.$display_id. Some + * displays may override existing routes; in these cases, the route that is + * overridden is returned instead. + * + * @return string + * The name of the route + * + * @see \Drupal\views\Plugin\views\display\DisplayRouterInterface::alterRoutes() + * @see \Drupal\views\Plugin\views\display\DisplayRouterInterface::getAlteredRouteNames() + */ + public function getRouteName(); + + /** + * Returns the list of routes overridden by Views. + * + * @return string[] + * An array of overridden route names. The keys are in the form + * view_id.display_id and the values are the route names. + * + * @see \Drupal\views\Plugin\views\display\DisplayRouterInterface::alterRoutes() + */ + public function getAlteredRouteNames(); } diff --git a/core/modules/views/src/Plugin/views/display/Feed.php b/core/modules/views/src/Plugin/views/display/Feed.php index 67171ab..24e76ba 100644 --- a/core/modules/views/src/Plugin/views/display/Feed.php +++ b/core/modules/views/src/Plugin/views/display/Feed.php @@ -268,7 +268,7 @@ public function attachTo(ViewExecutable $clone, $display_id, array &$build) { $clone->setDisplay($this->display['id']); $clone->buildTitle(); if ($plugin = $clone->display_handler->getPlugin('style')) { - $plugin->attachTo($build, $display_id, $this->getPath(), $clone->getTitle()); + $plugin->attachTo($build, $display_id, $clone->getUrl(), $clone->getTitle()); foreach ($clone->feedIcons as $feed_icon) { $this->view->feedIcons[] = $feed_icon; } diff --git a/core/modules/views/src/Plugin/views/display/PathPluginBase.php b/core/modules/views/src/Plugin/views/display/PathPluginBase.php index 7738933..3a8473e 100644 --- a/core/modules/views/src/Plugin/views/display/PathPluginBase.php +++ b/core/modules/views/src/Plugin/views/display/PathPluginBase.php @@ -294,8 +294,6 @@ public function getMenuLinks() { } } - $view_route_names = $this->state->get('views.view_route_names') ?: array(); - $path = implode('/', $bits); $view_id = $this->view->storage->id(); $display_id = $this->display['id']; @@ -309,7 +307,7 @@ public function getMenuLinks() { // Some views might override existing paths, so we have to set the route // name based upon the altering. $links[$menu_link_id] = array( - 'route_name' => isset($view_route_names[$view_id_display]) ? $view_route_names[$view_id_display] : "view.$view_id_display", + 'route_name' => $this->getRouteName(), // Identify URL embedded arguments and correlate them to a handler. 'load arguments' => array($this->view->storage->id(), $this->display['id'], '%index'), 'id' => $menu_link_id, @@ -490,11 +488,28 @@ public function validate() { * {@inheritdoc} */ public function getUrlInfo() { - if (strpos($this->getOption('path'), '%') !== FALSE) { - throw new \InvalidArgumentException('No placeholders supported yet.'); - } + return Url::fromRoute($this->getRouteName()); + } - return Url::fromRoute($this->getRoute($this->view->storage->id(), $this->display['id'])); + /** + * {@inheritdoc} + */ + public function getRouteName() { + $view_id = $this->view->storage->id(); + $display_id = $this->display['id']; + $view_route_key = "$view_id.$display_id"; + + // Check for overridden route names. + $view_route_names = $this->getAlteredRouteNames(); + + return (isset($view_route_names[$view_route_key]) ? $view_route_names[$view_route_key] : "views.$view_route_key"); + } + + /** + * {@inheritdoc} + */ + public function getAlteredRouteNames() { + return $this->state->get('views.view_route_names') ?: array(); } } diff --git a/core/modules/views/src/Plugin/views/row/RssFields.php b/core/modules/views/src/Plugin/views/row/RssFields.php index 6d0ae5b..6e7a2bc 100644 --- a/core/modules/views/src/Plugin/views/row/RssFields.php +++ b/core/modules/views/src/Plugin/views/row/RssFields.php @@ -8,6 +8,7 @@ namespace Drupal\views\Plugin\views\row; use Drupal\Core\Form\FormStateInterface; +use Drupal\Core\Url; /** * Renders an RSS item based on fields. @@ -143,7 +144,7 @@ public function render($row) { // Create the RSS item object. $item = new \stdClass(); $item->title = $this->getField($row_index, $this->options['title_field']); - $item->link = _url($this->getField($row_index, $this->options['link_field']), array('absolute' => TRUE)); + $item->link = Url::fromUri('user-path:/' . $this->getField($row_index, $this->options['link_field']))->setAbsolute()->toString(); $item->description = $this->getField($row_index, $this->options['description_field']); $item->elements = array( array('key' => 'pubDate', 'value' => $this->getField($row_index, $this->options['date_field'])), @@ -157,7 +158,7 @@ public function render($row) { $item_guid = $this->getField($row_index, $this->options['guid_field_options']['guid_field']); if ($this->options['guid_field_options']['guid_field_is_permalink']) { $guid_is_permalink_string = 'true'; - $item_guid = _url($item_guid, array('absolute' => TRUE)); + $item_guid = Url::fromUri('user-path:/' . $item_guid)->setAbsolute()->toString(); } $item->elements[] = array( 'key' => 'guid', diff --git a/core/modules/views/src/Plugin/views/style/Opml.php b/core/modules/views/src/Plugin/views/style/Opml.php index f41378f..e0b9fb5 100644 --- a/core/modules/views/src/Plugin/views/style/Opml.php +++ b/core/modules/views/src/Plugin/views/style/Opml.php @@ -7,6 +7,8 @@ namespace Drupal\views\Plugin\views\style; +use Drupal\Core\Url; + /** * Default style plugin to render an OPML feed. * @@ -32,7 +34,7 @@ class Opml extends StylePluginBase { /** * {@inheritdoc} */ - public function attachTo(array &$build, $display_id, $path, $title) { + public function attachTo(array &$build, $display_id, Url $feed_url, $title) { $display = $this->view->displayHandlers->get($display_id); $url_options = array(); $input = $this->view->getExposedInput(); @@ -41,7 +43,7 @@ public function attachTo(array &$build, $display_id, $path, $title) { } $url_options['absolute'] = TRUE; - $url = _url($this->view->getUrl(NULL, $path), $url_options); + $url = $feed_url->setOptions($url_options)->toString(); if ($display->hasPath()) { if (empty($this->preview)) { $build['#attached']['feed'][] = array($url, $title); diff --git a/core/modules/views/src/Plugin/views/style/Rss.php b/core/modules/views/src/Plugin/views/style/Rss.php index f597116..fbbf4dd 100644 --- a/core/modules/views/src/Plugin/views/style/Rss.php +++ b/core/modules/views/src/Plugin/views/style/Rss.php @@ -9,6 +9,7 @@ use Drupal\Component\Utility\SafeMarkup; use Drupal\Core\Form\FormStateInterface; +use Drupal\Core\Url; /** * Default style plugin to render an RSS feed. @@ -32,7 +33,7 @@ class Rss extends StylePluginBase { */ protected $usesRowPlugin = TRUE; - public function attachTo(array &$build, $display_id, $path, $title) { + public function attachTo(array &$build, $display_id, Url $feed_url, $title) { $url_options = array(); $input = $this->view->getExposedInput(); if ($input) { @@ -40,7 +41,7 @@ public function attachTo(array &$build, $display_id, $path, $title) { } $url_options['absolute'] = TRUE; - $url = _url($this->view->getUrl(NULL, $path), $url_options); + $url = $feed_url->setOptions($url_options)->toString(); // Add the RSS icon to the view. $this->view->feedIcons[] = [ diff --git a/core/modules/views/src/Tests/Handler/FilterEqualityTest.php b/core/modules/views/src/Tests/Handler/FilterEqualityTest.php index 2a59e9f..bc0eb0d 100644 --- a/core/modules/views/src/Tests/Handler/FilterEqualityTest.php +++ b/core/modules/views/src/Tests/Handler/FilterEqualityTest.php @@ -76,6 +76,8 @@ public function testEqualGroupedExposed() { $filters['name']['group_info']['default_group'] = 1; $view->setDisplay('page_1'); $view->displayHandlers->get('page_1')->overrideOption('filters', $filters); + $view->save(); + $this->container->get('router.builder')->rebuild(); $this->executeView($view); $resultset = array( @@ -129,6 +131,8 @@ public function testEqualGroupedNotExposed() { $filters['name']['group_info']['default_group'] = 2; $view->setDisplay('page_1'); $view->displayHandlers->get('page_1')->overrideOption('filters', $filters); + $view->save(); + $this->container->get('router.builder')->rebuild(); $this->executeView($view); $resultset = array( @@ -153,6 +157,7 @@ protected function getGroupedExposedFilters() { $filters = array( 'name' => array( 'id' => 'name', + 'plugin_id' => 'equality', 'table' => 'views_test_data', 'field' => 'name', 'relationship' => 'none', diff --git a/core/modules/views/src/Tests/Handler/FilterNumericTest.php b/core/modules/views/src/Tests/Handler/FilterNumericTest.php index 9277da5..e4914e6 100644 --- a/core/modules/views/src/Tests/Handler/FilterNumericTest.php +++ b/core/modules/views/src/Tests/Handler/FilterNumericTest.php @@ -80,6 +80,8 @@ public function testFilterNumericExposedGroupedSimple() { $filters['age']['group_info']['default_group'] = 1; $view->setDisplay('page_1'); $view->displayHandlers->get('page_1')->overrideOption('filters', $filters); + $view->save(); + $this->container->get('router.builder')->rebuild(); $this->executeView($view); $resultset = array( @@ -173,6 +175,8 @@ public function testFilterNumericExposedGroupedBetween() { $filters['age']['group_info']['default_group'] = 2; $view->setDisplay('page_1'); $view->displayHandlers->get('page_1')->overrideOption('filters', $filters); + $view->save(); + $this->container->get('router.builder')->rebuild(); $this->executeView($view); $resultset = array( @@ -201,6 +205,8 @@ public function testFilterNumericExposedGroupedNotBetween() { $filters['age']['group_info']['default_group'] = 3; $view->setDisplay('page_1'); $view->displayHandlers->get('page_1')->overrideOption('filters', $filters); + $view->save(); + $this->container->get('router.builder')->rebuild(); $this->executeView($view); $resultset = array( @@ -289,6 +295,8 @@ public function testFilterNumericExposedGroupedEmpty() { $filters['age']['group_info']['default_group'] = 4; $view->setDisplay('page_1'); $view->displayHandlers->get('page_1')->overrideOption('filters', $filters); + $view->save(); + $this->container->get('router.builder')->rebuild(); $this->executeView($view); $resultset = array( @@ -305,6 +313,8 @@ public function testFilterNumericExposedGroupedNotEmpty() { $filters['age']['group_info']['default_group'] = 5; $view->setDisplay('page_1'); $view->displayHandlers->get('page_1')->overrideOption('filters', $filters); + $view->save(); + $this->container->get('router.builder')->rebuild(); $this->executeView($view); $resultset = array( @@ -366,6 +376,7 @@ protected function getGroupedExposedFilters() { $filters = array( 'age' => array( 'id' => 'age', + 'plugin_id' => 'numeric', 'table' => 'views_test_data', 'field' => 'age', 'relationship' => 'none', diff --git a/core/modules/views/src/Tests/Handler/FilterStringTest.php b/core/modules/views/src/Tests/Handler/FilterStringTest.php index baa3489..f67eb22 100644 --- a/core/modules/views/src/Tests/Handler/FilterStringTest.php +++ b/core/modules/views/src/Tests/Handler/FilterStringTest.php @@ -120,6 +120,8 @@ function testFilterStringGroupedExposedEqual() { $filters['name']['group_info']['default_group'] = 1; $view->setDisplay('page_1'); $view->displayHandlers->get('page_1')->overrideOption('filters', $filters); + $view->save(); + $this->container->get('router.builder')->rebuild(); $this->executeView($view); @@ -175,6 +177,8 @@ function testFilterStringGroupedExposedNotEqual() { $view->setDisplay('page_1'); $view->displayHandlers->get('page_1')->overrideOption('filters', $filters); + $view->save(); + $this->container->get('router.builder')->rebuild(); $this->executeView($view); @@ -230,6 +234,8 @@ function testFilterStringGroupedExposedContains() { $filters['name']['group_info']['default_group'] = '3'; $view->setDisplay('page_1'); $view->displayHandlers->get('page_1')->overrideOption('filters', $filters); + $view->save(); + $this->container->get('router.builder')->rebuild(); $this->executeView($view); @@ -304,6 +310,8 @@ function testFilterStringGroupedExposedWord() { $filters['name']['group_info']['default_group'] = '3'; $view->setDisplay('page_1'); $view->displayHandlers->get('page_1')->overrideOption('filters', $filters); + $view->save(); + $this->container->get('router.builder')->rebuild(); $this->executeView($view); @@ -369,6 +377,8 @@ function testFilterStringGroupedExposedStarts() { $filters['description']['group_info']['default_group'] = 2; $view->setDisplay('page_1'); $view->displayHandlers->get('page_1')->overrideOption('filters', $filters); + $view->save(); + $this->container->get('router.builder')->rebuild(); $this->executeView($view); @@ -420,6 +430,8 @@ function testFilterStringGroupedExposedNotStarts() { $filters['description']['group_info']['default_group'] = 3; $view->setDisplay('page_1'); $view->displayHandlers->get('page_1')->overrideOption('filters', $filters); + $view->save(); + $this->container->get('router.builder')->rebuild(); $this->executeView($view); @@ -474,6 +486,8 @@ function testFilterStringGroupedExposedEnds() { $filters['description']['group_info']['default_group'] = 4; $view->setDisplay('page_1'); $view->displayHandlers->get('page_1')->overrideOption('filters', $filters); + $view->save(); + $this->container->get('router.builder')->rebuild(); $this->executeView($view); @@ -525,6 +539,8 @@ function testFilterStringGroupedExposedNotEnds() { $filters['description']['group_info']['default_group'] = 5; $view->setDisplay('page_1'); $view->displayHandlers->get('page_1')->overrideOption('filters', $filters); + $view->save(); + $this->container->get('router.builder')->rebuild(); $this->executeView($view); @@ -578,6 +594,8 @@ function testFilterStringGroupedExposedNot() { $filters['description']['group_info']['default_group'] = 6; $view->setDisplay('page_1'); $view->displayHandlers->get('page_1')->overrideOption('filters', $filters); + $view->save(); + $this->container->get('router.builder')->rebuild(); $this->executeView($view); @@ -630,6 +648,8 @@ function testFilterStringGroupedExposedShorter() { $filters['name']['group_info']['default_group'] = 4; $view->setDisplay('page_1'); $view->displayHandlers->get('page_1')->overrideOption('filters', $filters); + $view->save(); + $this->container->get('router.builder')->rebuild(); $this->executeView($view); $resultset = array( @@ -676,6 +696,8 @@ function testFilterStringGroupedExposedLonger() { $filters['name']['group_info']['default_group'] = 5; $view->setDisplay('page_1'); $view->displayHandlers->get('page_1')->overrideOption('filters', $filters); + $view->save(); + $this->container->get('router.builder')->rebuild(); $this->executeView($view); $resultset = array( @@ -719,6 +741,8 @@ function testFilterStringGroupedExposedEmpty() { $filters['description']['group_info']['default_group'] = 7; $view->setDisplay('page_1'); $view->displayHandlers->get('page_1')->overrideOption('filters', $filters); + $view->save(); + $this->container->get('router.builder')->rebuild(); $this->executeView($view); $resultset = array( @@ -733,6 +757,7 @@ protected function getGroupedExposedFilters() { $filters = array( 'name' => array( 'id' => 'name', + 'plugin_id' => 'string', 'table' => 'views_test_data', 'field' => 'name', 'relationship' => 'none', @@ -778,6 +803,7 @@ protected function getGroupedExposedFilters() { ), 'description' => array( 'id' => 'description', + 'plugin_id' => 'string', 'table' => 'views_test_data', 'field' => 'description', 'relationship' => 'none', diff --git a/core/modules/views/src/Tests/TokenReplaceTest.php b/core/modules/views/src/Tests/TokenReplaceTest.php index f3f0028..d4bcc67 100644 --- a/core/modules/views/src/Tests/TokenReplaceTest.php +++ b/core/modules/views/src/Tests/TokenReplaceTest.php @@ -28,6 +28,7 @@ class TokenReplaceTest extends ViewUnitTestBase { protected function setUp() { parent::setUp(); $this->installSchema('system', 'url_alias'); + $this->container->get('router.builder')->rebuild(); } /** @@ -44,7 +45,7 @@ function testTokenReplacement() { '[view:description]' => 'Test view to token replacement tests.', '[view:id]' => 'test_tokens', '[view:title]' => 'Test token page', - '[view:url]' => $view->getUrlInfo('page_1')->setAbsolute(TRUE)->toString(), + '[view:url]' => $view->getUrl(NULL, 'page_1')->setAbsolute(TRUE)->toString(), '[view:total-rows]' => (string) $view->total_rows, '[view:base-table]' => 'views_test_data', '[view:base-field]' => 'id', diff --git a/core/modules/views/src/Tests/ViewExecutableTest.php b/core/modules/views/src/Tests/ViewExecutableTest.php index e99ca1d..27c4e55 100644 --- a/core/modules/views/src/Tests/ViewExecutableTest.php +++ b/core/modules/views/src/Tests/ViewExecutableTest.php @@ -322,18 +322,6 @@ public function testPropertyMethods() { $view->override_path = $override_path; $this->assertEqual($view->getPath(), $override_path); - // Test the getUrl method(). - $url = 'foo'; - $this->assertEqual($view->getUrl(NULL, $url), $url); - // Test with arguments. - $arg1 = 'bar'; - $arg2 = 12345; - $this->assertEqual($view->getUrl(array($arg1, $arg2), $url), "$url/$arg1/$arg2"); - // Test the override_url property override. - $override_url = 'baz'; - $view->override_url = $override_url; - $this->assertEqual($view->getUrl(NULL, $url), $override_url); - // Test the title methods. $title = $this->randomString(); $view->setTitle($title); diff --git a/core/modules/views/src/ViewExecutable.php b/core/modules/views/src/ViewExecutable.php index 9791a88..db01ad8 100644 --- a/core/modules/views/src/ViewExecutable.php +++ b/core/modules/views/src/ViewExecutable.php @@ -10,7 +10,9 @@ use Drupal\Component\Utility\String; use Drupal\Core\DependencyInjection\DependencySerializationTrait; use Drupal\Core\Form\FormState; +use Drupal\Core\Routing\RouteProviderInterface; use Drupal\Core\Session\AccountInterface; +use Drupal\Core\Url; use Drupal\views\Plugin\views\display\DisplayRouterInterface; use Drupal\views\Plugin\views\query\QueryPluginBase; use Drupal\views\ViewEntityInterface; @@ -239,9 +241,9 @@ class ViewExecutable { /** * Allow to override the url of the current view. * - * @var string + * @var \Drupal\Core\Url */ - public $override_url = NULL; + public $override_url; /** * Allow to override the path used for generated urls. @@ -426,6 +428,13 @@ class ViewExecutable { protected $viewsData; /** + * The route provider. + * + * @var \Drupal\Core\Routing\RouteProviderInterface + */ + protected $routeProvider; + + /** * Constructs a new ViewExecutable object. * * @param \Drupal\views\ViewEntityInterface $storage @@ -434,13 +443,16 @@ class ViewExecutable { * The current user. * @param \Drupal\views\ViewsData $views_data * The views data. + * @param \Drupal\Core\Routing\RouteProviderInterface $route_provider + * The route provider. */ - public function __construct(ViewEntityInterface $storage, AccountInterface $user, ViewsData $views_data) { + public function __construct(ViewEntityInterface $storage, AccountInterface $user, ViewsData $views_data, RouteProviderInterface $route_provider) { // Reference the storage and the executable to each other. $this->storage = $storage; $this->storage->set('executable', $this); $this->user = $user; $this->viewsData = $views_data; + $this->routeProvider = $route_provider; // Add the default css for a view. $this->element['#attached']['library'][] = 'views/views.module'; @@ -1693,11 +1705,46 @@ public function buildTitle() { } /** + * Determines whether you can link to the view or a particular display. + * + * Some displays (e.g. block displays) do not have their own route, but may + * optionally provide a link to another display that does have a route. + * + * @param array $args + * (optional) The arguments. + * @param string $display_id + * (optional) The display ID. The current display will be used by default. + * + * @return bool + */ + public function hasUrl($args = NULL, $display_id = NULL) { + if (!empty($this->override_url)) { + return TRUE; + } + + // If the display has a valid route available (either its own or for a + // linked display), then we can provide a URL for it. + $display_handler = $this->displayHandlers->get($display_id ?: $this->current_display)->getRoutedDisplay(); + if (!$display_handler instanceof DisplayRouterInterface) { + return FALSE; + } + + return TRUE; + } + + /** * Get the URL for the current view. * * This URL will be adjusted for arguments. + * + * @param array $args + * (optional) Passed in arguments. + * @param string $display_id + * (optional) Specify the display ID to link to, fallback to the current ID. + * + * @return \Drupal\Core\Url */ - public function getUrl($args = NULL, $path = NULL) { + public function getUrl($args = NULL, $display_id = NULL) { if (!empty($this->override_url)) { return $this->override_url; } @@ -1705,6 +1752,12 @@ public function getUrl($args = NULL, $path = NULL) { if (!isset($path)) { $path = $this->getPath(); } + + $display_handler = $this->displayHandlers->get($display_id ?: $this->current_display)->getRoutedDisplay(); + if (!$display_handler instanceof DisplayRouterInterface) { + throw new \InvalidArgumentException('You cannot create a URL to a display without routes.'); + } + if (!isset($args)) { $args = $this->args; @@ -1721,41 +1774,41 @@ public function getUrl($args = NULL, $path = NULL) { } // Don't bother working if there's nothing to do: if (empty($path) || (empty($args) && strpos($path, '%') === FALSE)) { - return $path; + return $display_handler->getUrlInfo(); } - $pieces = array(); $argument_keys = isset($this->argument) ? array_keys($this->argument) : array(); $id = current($argument_keys); - foreach (explode('/', $path) as $piece) { - if ($piece != '%') { - $pieces[] = $piece; - } - else { - if (empty($args)) { - // Try to never put % in a url; use the wildcard instead. - if ($id && !empty($this->argument[$id]->options['exception']['value'])) { - $pieces[] = $this->argument[$id]->options['exception']['value']; - } - else { - $pieces[] = '*'; // gotta put something if there just isn't one. - } + /** @var \Drupal\Core\Url $url */ + $url = $display_handler->getUrlInfo(); + $route = $this->routeProvider->getRouteByName($url->getRouteName()); + + $variables = $route->compile()->getVariables(); + $parameters = $url->getRouteParameters(); + + foreach ($variables as $variable_name) { + if (empty($args)) { + // Try to never put % in a URL; use the wildcard instead. + if ($id && !empty($this->argument[$id]->options['exception']['value'])) { + $parameters[$variable_name] = $this->argument[$id]->options['exception']['value']; } else { - $pieces[] = array_shift($args); + // Provide some fallback in case no exception value could be found. + $parameters[$variable_name] = '*'; } + } + else { + $parameters[$variable_name] = array_shift($args); + } - if ($id) { - $id = next($argument_keys); - } + if ($id) { + $id = next($argument_keys); } } - if (!empty($args)) { - $pieces = array_merge($pieces, $args); - } - return implode('/', $pieces); + $url->setRouteParameters($parameters); + return $url; } /** diff --git a/core/modules/views/src/ViewExecutableFactory.php b/core/modules/views/src/ViewExecutableFactory.php index 04f6196..0975a48 100644 --- a/core/modules/views/src/ViewExecutableFactory.php +++ b/core/modules/views/src/ViewExecutableFactory.php @@ -7,6 +7,7 @@ namespace Drupal\views; +use Drupal\Core\Routing\RouteProviderInterface; use Drupal\Core\Session\AccountInterface; use Drupal\views\ViewEntityInterface; use Symfony\Component\HttpFoundation\RequestStack; @@ -38,6 +39,13 @@ class ViewExecutableFactory { protected $viewsData; /** + * The route provider. + * + * @var \Drupal\Core\Routing\RouteProviderInterface + */ + protected $routeProvider; + + /** * Constructs a new ViewExecutableFactory * * @param \Drupal\Core\Session\AccountInterface $user @@ -46,11 +54,14 @@ class ViewExecutableFactory { * The request stack. * @param \Drupal\views\ViewsData $views_data * The views data. + * @param \Drupal\Core\Routing\RouteProviderInterface $route_provider + * The route provider. */ - public function __construct(AccountInterface $user, RequestStack $request_stack, ViewsData $views_data) { + public function __construct(AccountInterface $user, RequestStack $request_stack, ViewsData $views_data, RouteProviderInterface $route_provider) { $this->user = $user; $this->requestStack = $request_stack; $this->viewsData = $views_data; + $this->routeProvider = $route_provider; } /** @@ -63,7 +74,7 @@ public function __construct(AccountInterface $user, RequestStack $request_stack, * A ViewExecutable instance. */ public function get(ViewEntityInterface $view) { - $view = new ViewExecutable($view, $this->user, $this->viewsData); + $view = new ViewExecutable($view, $this->user, $this->viewsData, $this->routeProvider); $view->setRequest($this->requestStack->getCurrentRequest()); return $view; } diff --git a/core/modules/views/tests/src/Unit/Plugin/area/ResultTest.php b/core/modules/views/tests/src/Unit/Plugin/area/ResultTest.php index a289d8e..87c79a3d 100644 --- a/core/modules/views/tests/src/Unit/Plugin/area/ResultTest.php +++ b/core/modules/views/tests/src/Unit/Plugin/area/ResultTest.php @@ -46,7 +46,8 @@ protected function setUp() { $views_data = $this->getMockBuilder('Drupal\views\ViewsData') ->disableOriginalConstructor() ->getMock(); - $this->view = new ViewExecutable($storage, $user, $views_data); + $route_provider = $this->getMock('Drupal\Core\Routing\RouteProviderInterface'); + $this->view = new ViewExecutable($storage, $user, $views_data, $route_provider); $this->resultHandler = new Result(array(), 'result', array()); $this->resultHandler->view = $this->view; diff --git a/core/modules/views/tests/src/Unit/Plugin/field/CounterTest.php b/core/modules/views/tests/src/Unit/Plugin/field/CounterTest.php index 959ce3d..7b0c14d 100644 --- a/core/modules/views/tests/src/Unit/Plugin/field/CounterTest.php +++ b/core/modules/views/tests/src/Unit/Plugin/field/CounterTest.php @@ -75,7 +75,8 @@ protected function setUp() { $views_data = $this->getMockBuilder('Drupal\views\ViewsData') ->disableOriginalConstructor() ->getMock(); - $this->view = $this->getMock('Drupal\views\ViewExecutable', NULL, array($storage, $user, $views_data)); + $route_provider = $this->getMock('Drupal\Core\Routing\RouteProviderInterface'); + $this->view = $this->getMock('Drupal\views\ViewExecutable', NULL, array($storage, $user, $views_data, $route_provider)); $this->display = $this->getMockBuilder('Drupal\views\Plugin\views\display\DisplayPluginBase') ->disableOriginalConstructor() diff --git a/core/modules/views/tests/src/Unit/ViewExecutableFactoryTest.php b/core/modules/views/tests/src/Unit/ViewExecutableFactoryTest.php index e82468e..17b61c8 100644 --- a/core/modules/views/tests/src/Unit/ViewExecutableFactoryTest.php +++ b/core/modules/views/tests/src/Unit/ViewExecutableFactoryTest.php @@ -54,6 +54,13 @@ class ViewExecutableFactoryTest extends UnitTestCase { protected $viewsData; /** + * The mocked route provider. + * + * @var \Drupal\Core\Routing\RouteProviderInterface|\PHPUnit_Framework_MockObject_MockObject + */ + protected $routeProvider; + + /** * {@inheritdoc} */ protected function setUp() { @@ -65,7 +72,8 @@ protected function setUp() { $this->viewsData = $this->getMockBuilder('Drupal\views\ViewsData') ->disableOriginalConstructor() ->getMock(); - $this->viewExecutableFactory = new ViewExecutableFactory($this->user, $this->requestStack, $this->viewsData); + $this->routeProvider = $this->getMock('Drupal\Core\Routing\RouteProviderInterface'); + $this->viewExecutableFactory = new ViewExecutableFactory($this->user, $this->requestStack, $this->viewsData, $this->routeProvider); } /** diff --git a/core/modules/views/tests/src/Unit/ViewExecutableTest.php b/core/modules/views/tests/src/Unit/ViewExecutableTest.php new file mode 100644 index 0000000..d980f82 --- /dev/null +++ b/core/modules/views/tests/src/Unit/ViewExecutableTest.php @@ -0,0 +1,476 @@ +view = $this->getMock('Drupal\views\ViewEntityInterface'); + $this->user = $this->getMock('Drupal\Core\Session\AccountInterface'); + $this->viewsData = $this->getMockBuilder('Drupal\views\ViewsData') + ->disableOriginalConstructor() + ->getMock(); + $this->displayHandler = $this->getMockBuilder('Drupal\views\Plugin\views\display\DisplayRouterInterface') + ->disableOriginalConstructor() + ->getMock(); + $this->routeProvider = $this->getMock('Drupal\Core\Routing\RouteProviderInterface'); + $this->displayHandlers = $this->getMockBuilder('Drupal\views\DisplayPluginCollection') + ->disableOriginalConstructor() + ->getMock(); + + $this->executable = new ViewExecutable($this->view, $this->user, $this->viewsData, $this->routeProvider); + $this->executable->display_handler = $this->displayHandler; + $this->executable->displayHandlers = $this->displayHandlers; + + $this->viewExecutableFactory = $this->getMockBuilder('Drupal\views\ViewExecutableFactory') + ->disableOriginalConstructor() + ->getMock(); + + $translation = $this->getStringTranslationStub(); + $container = new ContainerBuilder(); + $container->set('string_translation', $translation); + $container->set('views.executable', $this->viewExecutableFactory); + \Drupal::setContainer($container); + } + + /** + * @covers ::getUrl + */ + public function testGetUrlWithOverriddenUrl() { + $url = Url::fromRoute('example'); + $this->executable->override_url = $url; + + $this->assertSame($url, $this->executable->getUrl()); + } + + /** + * @covers ::getUrl + */ + public function testGetUrlWithPathNoPlaceholders() { + $this->displayHandler->expects($this->any()) + ->method('getRoutedDisplay') + ->willReturn($this->displayHandler); + $this->displayHandlers->expects($this->any()) + ->method('get') + ->willReturn($this->displayHandler); + $this->displayHandler->expects($this->any()) + ->method('getUrlInfo') + ->willReturn(Url::fromRoute('views.test.page_1')); + $this->displayHandler->expects($this->any()) + ->method('getPath') + ->willReturn('test-path'); + + $this->assertEquals(Url::fromRoute('views.test.page_1'), $this->executable->getUrl()); + } + + /** + * @expectedException \InvalidArgumentException + * + * @covers ::getUrl + */ + public function testGetUrlWithoutRouterDisplay() { + $this->displayHandler = $this->getMock('Drupal\views\Plugin\views\display\DisplayPluginInterface'); + $this->displayHandlers->expects($this->any()) + ->method('get') + ->willReturn($this->displayHandler); + $this->executable->display_handler = $this->displayHandler; + + $this->executable->getUrl(); + } + + /** + * @covers ::getUrl + */ + public function testGetUrlWithPlaceholdersAndArgs() { + $this->displayHandler->expects($this->any()) + ->method('getRoutedDisplay') + ->willReturn($this->displayHandler); + $this->displayHandlers->expects($this->any()) + ->method('get') + ->willReturn($this->displayHandler); + $this->displayHandler->expects($this->any()) + ->method('getUrlInfo') + ->willReturn(Url::fromRoute('views.test.page_1')); + $this->displayHandler->expects($this->any()) + ->method('getPath') + ->willReturn('test-path/%'); + + $route = new Route('/test-path/{arg_0}'); + $this->routeProvider->expects($this->any()) + ->method('getRouteByName') + ->with('views.test.page_1') + ->willReturn($route); + + $this->assertEquals(Url::fromRoute('views.test.page_1', ['arg_0' => 'test']), $this->executable->getUrl(['test'])); + } + + /** + * @covers ::getUrl + */ + public function testGetUrlWithPlaceholdersAndWithoutArgs() { + $this->displayHandler->expects($this->any()) + ->method('getRoutedDisplay') + ->willReturn($this->displayHandler); + $this->displayHandlers->expects($this->any()) + ->method('get') + ->willReturn($this->displayHandler); + $this->displayHandler->expects($this->any()) + ->method('getUrlInfo') + ->willReturn(Url::fromRoute('views.test.page_1')); + $this->displayHandler->expects($this->any()) + ->method('getPath') + ->willReturn('test-path/%/%'); + + $route = new Route('/test-path/{arg_0}/{arg_1}'); + $this->routeProvider->expects($this->any()) + ->method('getRouteByName') + ->with('views.test.page_1') + ->willReturn($route); + + $this->assertEquals(Url::fromRoute('views.test.page_1', ['arg_0' => '*', 'arg_1' => '*']), $this->executable->getUrl()); + } + + /** + * @covers ::getUrl + */ + public function testGetUrlWithPlaceholdersAndWithoutArgsAndExceptionValue() { + $this->displayHandler->expects($this->any()) + ->method('getRoutedDisplay') + ->willReturn($this->displayHandler); + $this->displayHandlers->expects($this->any()) + ->method('get') + ->willReturn($this->displayHandler); + $this->displayHandler->expects($this->any()) + ->method('getUrlInfo') + ->willReturn(Url::fromRoute('views.test.page_1')); + $this->displayHandler->expects($this->any()) + ->method('getPath') + ->willReturn('test-path/%/%'); + + $route = new Route('/test-path/{arg_0}/{arg_1}'); + $this->routeProvider->expects($this->any()) + ->method('getRouteByName') + ->with('views.test.page_1') + ->willReturn($route); + + $argument_handler = $this->getMockBuilder('Drupal\views\Plugin\views\argument\ArgumentPluginBase') + ->disableOriginalConstructor() + ->getMock(); + $argument_handler->options['exception']['value'] = 'exception_0'; + $this->executable->argument['key_1'] = $argument_handler; + $argument_handler = $this->getMockBuilder('Drupal\views\Plugin\views\argument\ArgumentPluginBase') + ->disableOriginalConstructor() + ->getMock(); + $argument_handler->options['exception']['value'] = 'exception_1'; + $this->executable->argument['key_2'] = $argument_handler; + + $this->assertEquals(Url::fromRoute('views.test.page_1', ['arg_0' => 'exception_0', 'arg_1' => 'exception_1']), $this->executable->getUrl()); + } + + /** + * Tests the buildThemeFunctions() method. + */ + public function testBuildThemeFunctions() { + /** @var \Drupal\views\ViewExecutable|\PHPUnit_Framework_MockObject_MockObject $view */ + /** @var \Drupal\views\Plugin\views\display\DisplayPluginBase|\PHPUnit_Framework_MockObject_MockObject $display */ + list($view, $display) = $this->setupBaseViewAndDisplay(); + + unset($view->display_handler); + $expected = [ + 'test_hook__test_view', + 'test_hook' + ]; + $this->assertEquals($expected, $view->buildThemeFunctions('test_hook')); + + $view->display_handler = $display; + $expected = [ + 'test_hook__test_view__default', + 'test_hook__default', + 'test_hook__one', + 'test_hook__two', + 'test_hook__and_three', + 'test_hook__test_view', + 'test_hook' + ]; + $this->assertEquals($expected, $view->buildThemeFunctions('test_hook')); + + //Change the name of the display plugin and make sure that is in the array. + $view->display_handler->display['display_plugin'] = 'default2'; + + $expected = [ + 'test_hook__test_view__default', + 'test_hook__default', + 'test_hook__one', + 'test_hook__two', + 'test_hook__and_three', + 'test_hook__test_view__default2', + 'test_hook__default2', + 'test_hook__test_view', + 'test_hook' + ]; + $this->assertEquals($expected, $view->buildThemeFunctions('test_hook')); + } + + /** + * Tests the generateHandlerId method(). + * + * @covers ::generateHandlerId() + */ + public function testGenerateHandlerId() { + // Test the generateHandlerId() method. + $test_ids = ['test' => 'test', 'test_1' => 'test_1']; + $this->assertEquals(ViewExecutable::generateHandlerId('new', $test_ids), 'new'); + $this->assertEquals(ViewExecutable::generateHandlerId('test', $test_ids), 'test_2'); + } + + /** + * Tests the addHandler method(). + * + * @covers ::addHandler() + */ + public function testAddHandler() { + /** @var \Drupal\views\ViewExecutable|\PHPUnit_Framework_MockObject_MockObject $view */ + /** @var \Drupal\views\Plugin\views\display\DisplayPluginBase|\PHPUnit_Framework_MockObject_MockObject $display */ + list($view, $display) = $this->setupBaseViewAndDisplay(); + + $views_data = []; + $views_data['test_field'] = [ + 'field' => ['id' => 'standard'], + 'filter' => ['id' => 'standard'], + 'argument' => ['id' => 'standard'], + 'sort' => ['id' => 'standard'], + ]; + + $this->viewsData->expects($this->atLeastOnce()) + ->method('get') + ->with('test_entity') + ->willReturn($views_data); + + foreach (['field', 'filter', 'argument', 'sort'] as $handler_type) { + $display->expects($this->atLeastOnce()) + ->method('setOption') + ->with($this->callback(function($argument) { + return $argument; + }), ['test_field' => [ + 'id' => 'test_field', + 'table' => 'test_entity', + 'field' => 'test_field', + 'plugin_id' => 'standard', + ]]); + } + + foreach (['field', 'filter', 'argument', 'sort'] as $handler_type) { + $view->addHandler('default', $handler_type, 'test_entity', 'test_field'); + } + } + + /** + * Tests the addHandler method() with an entity field. + * + * @covers ::addHandler() + */ + public function testAddHandlerWithEntityField() { + /** @var \Drupal\views\ViewExecutable|\PHPUnit_Framework_MockObject_MockObject $view */ + /** @var \Drupal\views\Plugin\views\display\DisplayPluginBase|\PHPUnit_Framework_MockObject_MockObject $display */ + list($view, $display) = $this->setupBaseViewAndDisplay(); + + $views_data = []; + $views_data['table']['entity type'] = 'test_entity_type'; + $views_data['test_field'] = [ + 'entity field' => 'test_field', + 'field' => ['id' => 'standard'], + 'filter' => ['id' => 'standard'], + 'argument' => ['id' => 'standard'], + 'sort' => ['id' => 'standard'], + ]; + + $this->viewsData->expects($this->atLeastOnce()) + ->method('get') + ->with('test_entity') + ->willReturn($views_data); + + foreach (['field', 'filter', 'argument', 'sort'] as $handler_type) { + $display->expects($this->atLeastOnce()) + ->method('setOption') + ->with($this->callback(function($argument) { + return $argument; + }), ['test_field' => [ + 'id' => 'test_field', + 'table' => 'test_entity', + 'field' => 'test_field', + 'entity_type' => 'test_entity_type', + 'entity_field' => 'test_field', + 'plugin_id' => 'standard', + ]]); + } + + foreach (['field', 'filter', 'argument', 'sort'] as $handler_type) { + $view->addHandler('default', $handler_type, 'test_entity', 'test_field'); + } + } + + /** + * Tests attachDisplays(). + * + * @covers ::attachDisplays() + */ + public function testAttachDisplays() { + /** @var \Drupal\views\ViewExecutable|\PHPUnit_Framework_MockObject_MockObject $view */ + /** @var \Drupal\views\Plugin\views\display\DisplayPluginBase|\PHPUnit_Framework_MockObject_MockObject $display */ + list($view, $display) = $this->setupBaseViewAndDisplay(); + + $display->expects($this->atLeastOnce()) + ->method('acceptAttachments') + ->willReturn(TRUE); + $display->expects($this->atLeastOnce()) + ->method('getAttachedDisplays') + ->willReturn(['page_1']); + + $cloned_view = $this->getMockBuilder('Drupal\views\ViewExecutable') + ->disableOriginalConstructor() + ->getMock(); + $this->viewExecutableFactory->expects($this->atLeastOnce()) + ->method('get') + ->willReturn($cloned_view); + + $page_display = $this->getMockBuilder('Drupal\views\Plugin\views\display\DisplayPluginBase') + ->disableOriginalConstructor() + ->getMock(); + + $display_collection = $this->getMockBuilder('Drupal\views\DisplayPluginCollection') + ->disableOriginalConstructor() + ->getMock(); + + $display_collection->expects($this->atLeastOnce()) + ->method('get') + ->with('page_1') + ->willReturn($page_display); + $view->displayHandlers = $display_collection; + + // Setup the expectations. + $page_display->expects($this->once()) + ->method('attachTo') + ->with($cloned_view, 'default', $view->element); + + $view->attachDisplays(); + } + + /** + * Setups a view executable and default display. + * + * @return array + * Returns the view executable and default display. + */ + protected function setupBaseViewAndDisplay() { + $config = array( + 'id' => 'test_view', + 'tag' => 'OnE, TWO, and three', + 'display' => [ + 'default' => [ + 'id' => 'default', + 'display_plugin' => 'default', + 'display_title' => 'Default', + ], + ], + ); + + $storage = new View($config, 'view'); + $view = new ViewExecutable($storage, $this->user, $this->viewsData, $this->routeProvider); + $display = $this->getMockBuilder('Drupal\views\Plugin\views\display\DisplayPluginBase') + ->disableOriginalConstructor() + ->getMock(); + $display->display = $config['display']['default']; + + $view->current_display = 'default'; + $view->display_handler = $display; + $view->displayHandlers = $this->displayHandlers; + $view->displayHandlers->expects($this->any()) + ->method('get') + ->with('default') + ->willReturn($display); + $view->displayHandlers->expects($this->any()) + ->method('has') + ->with('default') + ->willReturn(TRUE); + + return array($view, $display); + } + +} diff --git a/core/modules/views/tests/src/Unit/ViewExecutableUnitTest.php b/core/modules/views/tests/src/Unit/ViewExecutableUnitTest.php deleted file mode 100644 index ecabe3f..0000000 --- a/core/modules/views/tests/src/Unit/ViewExecutableUnitTest.php +++ /dev/null @@ -1,304 +0,0 @@ -viewsData = $this->getMockBuilder('Drupal\views\ViewsData') - ->disableOriginalConstructor() - ->getMock(); - $this->user = $this->getMock('Drupal\Core\Session\AccountInterface'); - - $this->displayCollection = $this->getMockBuilder('Drupal\views\DisplayPluginCollection') - ->disableOriginalConstructor() - ->getMock(); - - $this->viewExecutableFactory = $this->getMockBuilder('Drupal\views\ViewExecutableFactory') - ->disableOriginalConstructor() - ->getMock(); - - $translation = $this->getStringTranslationStub(); - $container = new ContainerBuilder(); - $container->set('string_translation', $translation); - $container->set('views.executable', $this->viewExecutableFactory); - \Drupal::setContainer($container); - } - - /** - * Tests the buildThemeFunctions() method. - */ - public function testBuildThemeFunctions() { - /** @var \Drupal\views\ViewExecutable|\PHPUnit_Framework_MockObject_MockObject $view */ - /** @var \Drupal\views\Plugin\views\display\DisplayPluginBase|\PHPUnit_Framework_MockObject_MockObject $display */ - list($view, $display) = $this->setupBaseViewAndDisplay(); - - unset($view->display_handler); - $expected = array( - 'test_hook__test_view', - 'test_hook' - ); - $this->assertEquals($expected, $view->buildThemeFunctions('test_hook')); - - $view->display_handler = $display; - $expected = array( - 'test_hook__test_view__default', - 'test_hook__default', - 'test_hook__one', - 'test_hook__two', - 'test_hook__and_three', - 'test_hook__test_view', - 'test_hook' - ); - $this->assertEquals($expected, $view->buildThemeFunctions('test_hook')); - - //Change the name of the display plugin and make sure that is in the array. - $view->display_handler->display['display_plugin'] = 'default2'; - - $expected = array( - 'test_hook__test_view__default', - 'test_hook__default', - 'test_hook__one', - 'test_hook__two', - 'test_hook__and_three', - 'test_hook__test_view__default2', - 'test_hook__default2', - 'test_hook__test_view', - 'test_hook' - ); - $this->assertEquals($expected, $view->buildThemeFunctions('test_hook')); - } - - /** - * Tests the generateHandlerId method(). - * - * @covers ::generateHandlerId() - */ - public function testGenerateHandlerId() { - // Test the generateHandlerId() method. - $test_ids = ['test' => 'test', 'test_1' => 'test_1']; - $this->assertEquals(ViewExecutable::generateHandlerId('new', $test_ids), 'new'); - $this->assertEquals(ViewExecutable::generateHandlerId('test', $test_ids), 'test_2'); - } - - /** - * Tests the addHandler method(). - * - * @covers ::addHandler() - */ - public function testAddHandler() { - /** @var \Drupal\views\ViewExecutable|\PHPUnit_Framework_MockObject_MockObject $view */ - /** @var \Drupal\views\Plugin\views\display\DisplayPluginBase|\PHPUnit_Framework_MockObject_MockObject $display */ - list($view, $display) = $this->setupBaseViewAndDisplay(); - - $views_data = []; - $views_data['test_field'] = [ - 'field' => ['id' => 'standard'], - 'filter' => ['id' => 'standard'], - 'argument' => ['id' => 'standard'], - 'sort' => ['id' => 'standard'], - ]; - - $this->viewsData->expects($this->atLeastOnce()) - ->method('get') - ->with('test_entity') - ->willReturn($views_data); - - foreach (['field', 'filter', 'argument', 'sort'] as $handler_type) { - $display->expects($this->atLeastOnce()) - ->method('setOption') - ->with($this->callback(function($argument) { - return $argument; - }), ['test_field' => [ - 'id' => 'test_field', - 'table' => 'test_entity', - 'field' => 'test_field', - 'plugin_id' => 'standard', - ]]); - } - - foreach (['field', 'filter', 'argument', 'sort'] as $handler_type) { - $view->addHandler('default', $handler_type, 'test_entity', 'test_field'); - } - } - - /** - * Tests the addHandler method() with an entity field. - * - * @covers ::addHandler() - */ - public function testAddHandlerWithEntityField() { - /** @var \Drupal\views\ViewExecutable|\PHPUnit_Framework_MockObject_MockObject $view */ - /** @var \Drupal\views\Plugin\views\display\DisplayPluginBase|\PHPUnit_Framework_MockObject_MockObject $display */ - list($view, $display) = $this->setupBaseViewAndDisplay(); - - $views_data = []; - $views_data['table']['entity type'] = 'test_entity_type'; - $views_data['test_field'] = [ - 'entity field' => 'test_field', - 'field' => ['id' => 'standard'], - 'filter' => ['id' => 'standard'], - 'argument' => ['id' => 'standard'], - 'sort' => ['id' => 'standard'], - ]; - - $this->viewsData->expects($this->atLeastOnce()) - ->method('get') - ->with('test_entity') - ->willReturn($views_data); - - foreach (['field', 'filter', 'argument', 'sort'] as $handler_type) { - $display->expects($this->atLeastOnce()) - ->method('setOption') - ->with($this->callback(function($argument) { - return $argument; - }), ['test_field' => [ - 'id' => 'test_field', - 'table' => 'test_entity', - 'field' => 'test_field', - 'entity_type' => 'test_entity_type', - 'entity_field' => 'test_field', - 'plugin_id' => 'standard', - ]]); - } - - foreach (['field', 'filter', 'argument', 'sort'] as $handler_type) { - $view->addHandler('default', $handler_type, 'test_entity', 'test_field'); - } - } - - /** - * Tests attachDisplays(). - * - * @covers ::attachDisplays() - */ - public function testAttachDisplays() { - /** @var \Drupal\views\ViewExecutable|\PHPUnit_Framework_MockObject_MockObject $view */ - /** @var \Drupal\views\Plugin\views\display\DisplayPluginBase|\PHPUnit_Framework_MockObject_MockObject $display */ - list($view, $display) = $this->setupBaseViewAndDisplay(); - - $display->expects($this->atLeastOnce()) - ->method('acceptAttachments') - ->willReturn(TRUE); - $display->expects($this->atLeastOnce()) - ->method('getAttachedDisplays') - ->willReturn(['page_1']); - - $cloned_view = $this->getMockBuilder('Drupal\views\ViewExecutable') - ->disableOriginalConstructor() - ->getMock(); - $this->viewExecutableFactory->expects($this->atLeastOnce()) - ->method('get') - ->willReturn($cloned_view); - - $page_display = $this->getMockBuilder('Drupal\views\Plugin\views\display\DisplayPluginBase') - ->disableOriginalConstructor() - ->getMock(); - - $display_collection = $this->getMockBuilder('Drupal\views\DisplayPluginCollection') - ->disableOriginalConstructor() - ->getMock(); - - $display_collection->expects($this->atLeastOnce()) - ->method('get') - ->with('page_1') - ->willReturn($page_display); - $view->displayHandlers = $display_collection; - - // Setup the expectations. - $page_display->expects($this->once()) - ->method('attachTo') - ->with($cloned_view, 'default', $view->element); - - $view->attachDisplays(); - } - - /** - * Setups a view executable and default display. - * - * @return array - * Returns the view executable and default display. - */ - protected function setupBaseViewAndDisplay() { - $config = array( - 'id' => 'test_view', - 'tag' => 'OnE, TWO, and three', - 'display' => [ - 'default' => [ - 'id' => 'default', - 'display_plugin' => 'default', - 'display_title' => 'Default', - ], - ], - ); - - $storage = new View($config, 'view'); - $view = new ViewExecutable($storage, $this->user, $this->viewsData); - $display = $this->getMockBuilder('Drupal\views\Plugin\views\display\DisplayPluginBase') - ->disableOriginalConstructor() - ->getMock(); - $display->display = $config['display']['default']; - - $view->current_display = 'default'; - $view->display_handler = $display; - $view->displayHandlers = $this->displayCollection; - $view->displayHandlers->expects($this->any()) - ->method('get') - ->with('default') - ->willReturn($display); - $view->displayHandlers->expects($this->any()) - ->method('has') - ->with('default') - ->willReturn(TRUE); - - return array($view, $display); - } - -} diff --git a/core/modules/views/tests/src/Unit/ViewsTest.php b/core/modules/views/tests/src/Unit/ViewsTest.php index ab9bdf8..aa7a8c9 100644 --- a/core/modules/views/tests/src/Unit/ViewsTest.php +++ b/core/modules/views/tests/src/Unit/ViewsTest.php @@ -34,7 +34,8 @@ protected function setUp() { $views_data = $this->getMockBuilder('Drupal\views\ViewsData') ->disableOriginalConstructor() ->getMock(); - $container->set('views.executable', new ViewExecutableFactory($user, $request_stack, $views_data)); + $route_provider = $this->getMock('Drupal\Core\Routing\RouteProviderInterface'); + $container->set('views.executable', new ViewExecutableFactory($user, $request_stack, $views_data, $route_provider)); $this->view = new View(array('id' => 'test_view'), 'view'); diff --git a/core/modules/views/views.services.yml b/core/modules/views/views.services.yml index f98ad1c..1a01543 100644 --- a/core/modules/views/views.services.yml +++ b/core/modules/views/views.services.yml @@ -64,7 +64,7 @@ services: arguments: ['@views.views_data'] views.executable: class: Drupal\views\ViewExecutableFactory - arguments: ['@current_user', '@request_stack', '@views.views_data'] + arguments: ['@current_user', '@request_stack', '@views.views_data', '@router.route_provider'] views.analyzer: class: Drupal\views\Analyzer arguments: ['@module_handler'] diff --git a/core/modules/views/views.theme.inc b/core/modules/views/views.theme.inc index 0e70dd4..896bdeb 100644 --- a/core/modules/views/views.theme.inc +++ b/core/modules/views/views.theme.inc @@ -294,6 +294,7 @@ function template_preprocess_views_view_field(&$variables) { * - rows: The raw row data. */ function template_preprocess_views_view_summary(&$variables) { + /** @var \Drupal\views\ViewExecutable $view */ $view = $variables['view']; $argument = $view->argument[$view->build_info['summary_level']]; @@ -331,11 +332,16 @@ function template_preprocess_views_view_summary(&$variables) { $args = $view->args; $args[$argument->position] = $row_args[$id]; - $base_path = NULL; if (!empty($argument->options['summary_options']['base_path'])) { $base_path = $argument->options['summary_options']['base_path']; + $tokens = $this->getArgumentsTokens(); + $base_path = $this->viewsTokenReplace($base_path, $tokens); + $url = Url::fromUri('user-path:/' . $base_path); } - $variables['rows'][$id]->url = _url($view->getUrl($args, $base_path), $url_options); + else { + $url = $view->getUrl($args)->setOptions($url_options); + } + $variables['rows'][$id]->url = $url->toString(); $variables['rows'][$id]->count = intval($row->{$argument->count_alias}); if (isset($active_urls[$variables['rows'][$id]->url])) { $variables['rows'][$id]->attributes['class'][] = 'active'; @@ -395,11 +401,16 @@ function template_preprocess_views_view_summary_unformatted(&$variables) { $args = $view->args; $args[$argument->position] = $row_args[$id]; - $base_path = NULL; if (!empty($argument->options['summary_options']['base_path'])) { $base_path = $argument->options['summary_options']['base_path']; + $tokens = $this->getArgumentsTokens(); + $base_path = $this->viewsTokenReplace($base_path, $tokens); + $url = Url::fromUri('user-path:/' . $base_path); + } + else { + $url = $view->getUrl($args)->setOptions($url_options); } - $variables['rows'][$id]->url = _url($view->getUrl($args, $base_path), $url_options); + $variables['rows'][$id]->url = $url->toString(); $variables['rows'][$id]->count = intval($row->{$argument->count_alias}); if (isset($active_urls[$variables['rows'][$id]->url])) { $variables['rows'][$id]->attributes['class'][] = 'active'; @@ -891,11 +902,11 @@ function template_preprocess_views_view_rss(&$variables) { // there isn't one, use the global $base_url $link_display_id = $view->display_handler->getLinkDisplay(); if ($link_display_id && $display = $view->displayHandlers->get($link_display_id)) { - $path = $view->displayHandlers->get($link_display_id)->getPath(); + $url = $view->getUrl(NULL, $link_display_id); } - if ($path) { - $path = $view->getUrl(NULL, $path); + /** @var \Drupal\Core\Url $url */ + if ($url) { $url_options = array('absolute' => TRUE); if (!empty($view->exposed_raw_input)) { $url_options['query'] = $view->exposed_raw_input; @@ -903,11 +914,12 @@ function template_preprocess_views_view_rss(&$variables) { // Compare the link to the default home page; if it's the default home page, // just use $base_url. - if ($path == $config->get('page.front')) { - $path = ''; + $url_string = $url->setOptions($url_options)->toString(); + if ($url_string === Url::fromUri('user-path:/' . $config->get('page.front'))->toString()) { + $url_string = Url::fromRoute('')->setAbsolute()->toString(); } - $variables['link'] = check_url(_url($path, $url_options)); + $variables['link'] = $url_string; } $variables['langcode'] = String::checkPlain(\Drupal::languageManager()->getCurrentLanguage()->getId()); @@ -935,7 +947,7 @@ function template_preprocess_views_view_row_rss(&$variables) { $item = $variables['row']; $variables['title'] = String::checkPlain($item->title); - $variables['link'] = check_url($item->link); + $variables['link'] = $item->link; $variables['description'] = String::checkPlain($item->description); $variables['item_elements'] = empty($item->elements) ? '' : format_xml_elements($item->elements); } diff --git a/core/modules/views/views.tokens.inc b/core/modules/views/views.tokens.inc index 4160d85..85f6ec0 100644 --- a/core/modules/views/views.tokens.inc +++ b/core/modules/views/views.tokens.inc @@ -80,6 +80,7 @@ function views_tokens($type, $tokens, array $data = array(), array $options = ar $replacements = array(); if ($type == 'view' && !empty($data['view'])) { + /** @var \Drupal\views\ViewExecutable $view */ $view = $data['view']; foreach ($tokens as $name => $original) { @@ -102,8 +103,8 @@ function views_tokens($type, $tokens, array $data = array(), array $options = ar break; case 'url': - if ($path = $view->getUrl()) { - $replacements[$original] = _url($path, $url_options); + if ($url = $view->getUrl()) { + $replacements[$original] = $url->setOptions($url_options)->toString(); } break; case 'base-table': @@ -132,7 +133,7 @@ function views_tokens($type, $tokens, array $data = array(), array $options = ar // [view:url:*] nested tokens. This only works if Token module is installed. if ($url_tokens = $token_service->findWithPrefix($tokens, 'url')) { if ($path = $view->getUrl()) { - $replacements += $token_service->generate('url', $url_tokens, array('path' => $path), $options); + $replacements += $token_service->generate('url', $url_tokens, array('path' => $url->getInternalPath()), $options); } } } diff --git a/core/modules/views_ui/tests/src/Unit/ViewListBuilderTest.php b/core/modules/views_ui/tests/src/Unit/ViewListBuilderTest.php index b77f595..a42a879 100644 --- a/core/modules/views_ui/tests/src/Unit/ViewListBuilderTest.php +++ b/core/modules/views_ui/tests/src/Unit/ViewListBuilderTest.php @@ -125,7 +125,8 @@ public function testBuildRowEntityList() { $views_data = $this->getMockBuilder('Drupal\views\ViewsData') ->disableOriginalConstructor() ->getMock(); - $executable_factory = new ViewExecutableFactory($user, $request_stack, $views_data); + $route_provider = $this->getMock('Drupal\Core\Routing\RouteProviderInterface'); + $executable_factory = new ViewExecutableFactory($user, $request_stack, $views_data, $route_provider); $container->set('views.executable', $executable_factory); $container->set('plugin.manager.views.display', $display_manager); \Drupal::setContainer($container);