diff --git a/core/modules/aggregator/src/Plugin/views/field/TitleLink.php b/core/modules/aggregator/src/Plugin/views/field/TitleLink.php index 4fb924f..fa56ab2 100644 --- a/core/modules/aggregator/src/Plugin/views/field/TitleLink.php +++ b/core/modules/aggregator/src/Plugin/views/field/TitleLink.php @@ -8,6 +8,7 @@ namespace Drupal\aggregator\Plugin\views\field; use Drupal\Core\Form\FormStateInterface; +use Drupal\Core\Url; use Drupal\views\Plugin\views\display\DisplayPluginBase; use Drupal\views\Plugin\views\field\FieldPluginBase; use Drupal\views\ResultRow; @@ -78,7 +79,8 @@ protected function renderLink($data, ResultRow $values) { $link = $this->getValue($values, 'link'); if (!empty($this->options['display_as_link'])) { $this->options['alter']['make_link'] = TRUE; - $this->options['alter']['path'] = $link; + // Note: $link is an external URI, pointing to the feed itself. + $this->options['alter']['url'] = Url::fromUri($link); $this->options['alter']['html'] = TRUE; $this->options['alter']['absolute'] = TRUE; } diff --git a/core/modules/comment/src/Plugin/views/field/Comment.php b/core/modules/comment/src/Plugin/views/field/Comment.php index d804b13..31fa434 100644 --- a/core/modules/comment/src/Plugin/views/field/Comment.php +++ b/core/modules/comment/src/Plugin/views/field/Comment.php @@ -8,6 +8,7 @@ namespace Drupal\comment\Plugin\views\field; use Drupal\Core\Form\FormStateInterface; +use Drupal\Core\Url; use Drupal\views\ResultRow; use Drupal\views\ViewExecutable; use Drupal\views\Plugin\views\display\DisplayPluginBase; @@ -92,7 +93,7 @@ protected function renderLink($data, ResultRow $values) { $this->options['alter']['make_link'] = TRUE; $cid = $this->getValue($values, 'cid'); if (!empty($cid)) { - $this->options['alter']['path'] = "comment/" . $cid; + $this->options['alter']['url'] = Url::fromRoute('entity.comment.canonical', ['comment' => $cid]); $this->options['alter']['fragment'] = "comment-" . $cid; } // If there is no comment link to the entity. @@ -100,7 +101,7 @@ protected function renderLink($data, ResultRow $values) { $entity_id = $this->getValue($values, 'entity_id'); $entity_type = $this->getValue($values, 'entity_type'); $entity = entity_load($entity_type, $entity_id); - $this->options['alter']['path'] = $entity->getSystemPath(); + $this->options['alter']['url'] = $entity->urlInfo(); } } diff --git a/core/modules/comment/src/Plugin/views/field/Link.php b/core/modules/comment/src/Plugin/views/field/Link.php index cd5506d..adfaaa3 100644 --- a/core/modules/comment/src/Plugin/views/field/Link.php +++ b/core/modules/comment/src/Plugin/views/field/Link.php @@ -11,6 +11,7 @@ use Drupal\Core\Form\FormStateInterface; use Drupal\views\Plugin\views\field\FieldPluginBase; use Drupal\Core\Plugin\ContainerFactoryPluginInterface; +use Drupal\Core\Url; use Drupal\views\ResultRow; use Symfony\Component\DependencyInjection\ContainerInterface; @@ -111,13 +112,13 @@ protected function renderLink($data, ResultRow $values) { $this->options['alter']['html'] = TRUE; if (!empty($cid)) { - $this->options['alter']['path'] = "comment/" . $cid; + $this->options['alter']['url'] = Url::fromRoute('entity.comment.canonical', ['comment' => $cid]); $this->options['alter']['fragment'] = "comment-" . $cid; } // If there is no comment link to the node. elseif ($this->options['link_to_node']) { $entity = $comment->getCommentedEntity(); - $this->options['alter']['path'] = $entity->getSystemPath(); + $this->options['alter']['url'] = $entity->urlInfo(); } return $text; diff --git a/core/modules/comment/src/Plugin/views/field/LinkApprove.php b/core/modules/comment/src/Plugin/views/field/LinkApprove.php index 095eacd..b249a74 100644 --- a/core/modules/comment/src/Plugin/views/field/LinkApprove.php +++ b/core/modules/comment/src/Plugin/views/field/LinkApprove.php @@ -9,6 +9,7 @@ use Drupal\comment\CommentInterface; use Drupal\Core\Session\AccountInterface; +use Drupal\Core\Url; use Drupal\views\ResultRow; /** @@ -51,8 +52,8 @@ protected function renderLink($data, ResultRow $values) { $comment = $this->get_entity($values); $this->options['alter']['make_link'] = TRUE; - $this->options['alter']['path'] = "comment/" . $comment->id() . "/approve"; - $this->options['alter']['query'] = drupal_get_destination() + array('token' => \Drupal::csrfToken()->get($this->options['alter']['path'])); + $this->options['alter']['url'] = Url::fromRoute('comment.approve', ['comment' => $comment->id()]); + $this->options['alter']['query'] = drupal_get_destination() + array('token' => \Drupal::csrfToken()->get($this->options['alter']['url']->toString())); return $text; } diff --git a/core/modules/comment/src/Plugin/views/field/LinkDelete.php b/core/modules/comment/src/Plugin/views/field/LinkDelete.php index f22563b..b36c223 100644 --- a/core/modules/comment/src/Plugin/views/field/LinkDelete.php +++ b/core/modules/comment/src/Plugin/views/field/LinkDelete.php @@ -43,7 +43,7 @@ protected function renderLink($data, ResultRow $values) { $comment = $this->getEntity($values); $this->options['alter']['make_link'] = TRUE; - $this->options['alter']['path'] = $comment->getSystemPath('delete-form'); + $this->options['alter']['url'] = $comment->urlInfo('delete-form'); $this->options['alter']['query'] = drupal_get_destination(); return $text; diff --git a/core/modules/comment/src/Plugin/views/field/LinkEdit.php b/core/modules/comment/src/Plugin/views/field/LinkEdit.php index 4e687a3..316c272 100644 --- a/core/modules/comment/src/Plugin/views/field/LinkEdit.php +++ b/core/modules/comment/src/Plugin/views/field/LinkEdit.php @@ -63,7 +63,7 @@ protected function renderLink($data, ResultRow $values) { $this->options['alter']['query'] = drupal_get_destination(); } - $this->options['alter']['path'] = "comment/" . $comment->id() . "/edit"; + $this->options['alter']['url'] = $comment->urlInfo('edit-form'); return $text; } diff --git a/core/modules/comment/src/Plugin/views/field/LinkReply.php b/core/modules/comment/src/Plugin/views/field/LinkReply.php index d9f6914..65c7545 100644 --- a/core/modules/comment/src/Plugin/views/field/LinkReply.php +++ b/core/modules/comment/src/Plugin/views/field/LinkReply.php @@ -8,6 +8,7 @@ namespace Drupal\comment\Plugin\views\field; use Drupal\Core\Session\AccountInterface; +use Drupal\Core\Url; use Drupal\views\ResultRow; /** @@ -43,8 +44,12 @@ protected function renderLink($data, ResultRow $values) { $comment = $this->getEntity($values); $this->options['alter']['make_link'] = TRUE; - $this->options['alter']['path'] = "comment/reply/{$comment->getCommentedEntityTypeId()}/{$comment->getCommentedEntityId()}/{$comment->getFieldName()}/{$comment->id()}"; - + $this->options['alter']['url'] = Url::fromRoute('comment.reply', [ + 'entity_type' => $comment->getCommentedEntityTypeId(), + 'entity' => $comment->getCommentedEntityId(), + 'field_name' => $comment->getFieldName(), + 'pid' => $comment->id(), + ]); return $text; } diff --git a/core/modules/comment/src/Plugin/views/field/NodeNewComments.php b/core/modules/comment/src/Plugin/views/field/NodeNewComments.php index a95eaec..40b8a59 100644 --- a/core/modules/comment/src/Plugin/views/field/NodeNewComments.php +++ b/core/modules/comment/src/Plugin/views/field/NodeNewComments.php @@ -167,7 +167,7 @@ protected function renderLink($data, ResultRow $values) { $page_number = \Drupal::entityManager()->getStorage('comment') ->getNewCommentPageNumber($this->getValue($values, 'comment_count'), $this->getValue($values), $node); $this->options['alter']['make_link'] = TRUE; - $this->options['alter']['path'] = 'node/' . $node->id(); + $this->options['alter']['url'] = $node->urlInfo(); $this->options['alter']['query'] = $page_number ? array('page' => $page_number) : NULL; $this->options['alter']['fragment'] = 'new'; } diff --git a/core/modules/contact/src/Plugin/views/field/ContactLink.php b/core/modules/contact/src/Plugin/views/field/ContactLink.php index 118380d..f746e00 100644 --- a/core/modules/contact/src/Plugin/views/field/ContactLink.php +++ b/core/modules/contact/src/Plugin/views/field/ContactLink.php @@ -11,6 +11,7 @@ use Drupal\Core\Entity\EntityInterface; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Session\AccountInterface; +use Drupal\Core\Url; use Drupal\user\Plugin\views\field\Link; use Drupal\views\ResultRow; use Symfony\Component\DependencyInjection\ContainerInterface; @@ -117,7 +118,7 @@ protected function renderLink(EntityInterface $entity, ResultRow $values) { } $this->options['alter']['make_link'] = TRUE; - $this->options['alter']['path'] = "user/{$entity->id()}/contact"; + $this->options['alter']['url'] = Url::fromRoute('entity.user.contact_form', ['user' => $entity->id()]); $title = $this->t('Contact %user', array('%user' => $entity->name->value)); $this->options['alter']['attributes'] = array('title' => $title); diff --git a/core/modules/content_translation/src/Plugin/views/field/TranslationLink.php b/core/modules/content_translation/src/Plugin/views/field/TranslationLink.php index 3727fb7..473c3b4 100644 --- a/core/modules/content_translation/src/Plugin/views/field/TranslationLink.php +++ b/core/modules/content_translation/src/Plugin/views/field/TranslationLink.php @@ -65,7 +65,7 @@ protected function renderLink(EntityInterface $entity, ResultRow $values) { $text = !empty($this->options['text']) ? $this->options['text'] : $this->t('Translate'); $this->options['alter']['make_link'] = TRUE; - $this->options['alter']['path'] = $entity->getSystemPath('drupal:content-translation-overview'); + $this->options['alter']['url'] = $entity->urlInfo('drupal:content-translation-overview'); return $text; } diff --git a/core/modules/contextual/src/Plugin/views/field/ContextualLinks.php b/core/modules/contextual/src/Plugin/views/field/ContextualLinks.php index 684e4b7..f489d73 100644 --- a/core/modules/contextual/src/Plugin/views/field/ContextualLinks.php +++ b/core/modules/contextual/src/Plugin/views/field/ContextualLinks.php @@ -11,6 +11,7 @@ use Drupal\Component\Utility\String; use Drupal\Component\Utility\UrlHelper; use Drupal\Core\Form\FormStateInterface; +use Drupal\Core\Url; use Drupal\views\Plugin\views\field\FieldPluginBase; use Drupal\views\ResultRow; @@ -96,6 +97,9 @@ public function render(ResultRow $values) { if (!empty($this->view->field[$field]->options['alter']['path'])) { $path = $this->view->field[$field]->options['alter']['path']; } + elseif (!empty($this->view->field[$field]->options['alter']['url']) && $this->view->field[$field]->options['alter']['url'] instanceof Url) { + $path = $this->view->field[$field]->options['alter']['url']->toString(); + } if (!empty($title) && !empty($path)) { // Make sure that tokens are replaced for this paths as well. $tokens = $this->getRenderTokens(array()); diff --git a/core/modules/file/src/Plugin/views/field/File.php b/core/modules/file/src/Plugin/views/field/File.php index 816cc7b..d562178 100644 --- a/core/modules/file/src/Plugin/views/field/File.php +++ b/core/modules/file/src/Plugin/views/field/File.php @@ -8,6 +8,7 @@ namespace Drupal\file\Plugin\views\field; use Drupal\Core\Form\FormStateInterface; +use Drupal\Core\Url; use Drupal\views\ResultRow; use Drupal\views\ViewExecutable; use Drupal\views\Plugin\views\display\DisplayPluginBase; diff --git a/core/modules/node/src/Plugin/views/field/Link.php b/core/modules/node/src/Plugin/views/field/Link.php index d089889..04c2961 100644 --- a/core/modules/node/src/Plugin/views/field/Link.php +++ b/core/modules/node/src/Plugin/views/field/Link.php @@ -76,7 +76,7 @@ public function render(ResultRow $values) { protected function renderLink($node, ResultRow $values) { if ($node->access('view')) { $this->options['alter']['make_link'] = TRUE; - $this->options['alter']['path'] = 'node/' . $node->id(); + $this->options['alter']['url'] = $node->urlInfo(); $text = !empty($this->options['text']) ? $this->options['text'] : $this->t('View'); return $text; } diff --git a/core/modules/node/src/Plugin/views/field/LinkDelete.php b/core/modules/node/src/Plugin/views/field/LinkDelete.php index c5cf1f7..6bc5156 100644 --- a/core/modules/node/src/Plugin/views/field/LinkDelete.php +++ b/core/modules/node/src/Plugin/views/field/LinkDelete.php @@ -37,7 +37,7 @@ protected function renderLink($node, ResultRow $values) { } $this->options['alter']['make_link'] = TRUE; - $this->options['alter']['path'] = $node->getSystemPath('delete-form'); + $this->options['alter']['url'] = $node->urlInfo('delete-form'); $this->options['alter']['query'] = drupal_get_destination(); $text = !empty($this->options['text']) ? $this->options['text'] : $this->t('Delete'); diff --git a/core/modules/node/src/Plugin/views/field/LinkEdit.php b/core/modules/node/src/Plugin/views/field/LinkEdit.php index b59a389..eb8b974 100644 --- a/core/modules/node/src/Plugin/views/field/LinkEdit.php +++ b/core/modules/node/src/Plugin/views/field/LinkEdit.php @@ -7,6 +7,7 @@ namespace Drupal\node\Plugin\views\field; +use Drupal\Core\Url; use Drupal\node\Plugin\views\field\Link; use Drupal\views\ResultRow; @@ -37,7 +38,7 @@ protected function renderLink($node, ResultRow $values) { } $this->options['alter']['make_link'] = TRUE; - $this->options['alter']['path'] = "node/" . $node->id() . "/edit"; + $this->options['alter']['url'] = $node->urlInfo('edit-form'); $this->options['alter']['query'] = drupal_get_destination(); $text = !empty($this->options['text']) ? $this->options['text'] : $this->t('Edit'); diff --git a/core/modules/node/src/Plugin/views/field/Node.php b/core/modules/node/src/Plugin/views/field/Node.php index a62e0f2..ecf4e18 100644 --- a/core/modules/node/src/Plugin/views/field/Node.php +++ b/core/modules/node/src/Plugin/views/field/Node.php @@ -8,6 +8,7 @@ namespace Drupal\node\Plugin\views\field; use Drupal\Core\Form\FormStateInterface; +use Drupal\Core\Url; use Drupal\views\ResultRow; use Drupal\views\ViewExecutable; use Drupal\views\Plugin\views\display\DisplayPluginBase; @@ -71,7 +72,7 @@ protected function renderLink($data, ResultRow $values) { if (!empty($this->options['link_to_node']) && !empty($this->additional_fields['nid'])) { if ($data !== NULL && $data !== '') { $this->options['alter']['make_link'] = TRUE; - $this->options['alter']['path'] = "node/" . $this->getValue($values, 'nid'); + $this->options['alter']['url'] = Url::fromRoute('entity.node.canonical', ['node' => $this->getValue($values, 'nid')]); if (isset($this->aliases['langcode'])) { $languages = \Drupal::languageManager()->getLanguages(); $langcode = $this->getValue($values, 'langcode'); diff --git a/core/modules/node/src/Plugin/views/field/Revision.php b/core/modules/node/src/Plugin/views/field/Revision.php index dd5d443..7f99615 100644 --- a/core/modules/node/src/Plugin/views/field/Revision.php +++ b/core/modules/node/src/Plugin/views/field/Revision.php @@ -8,6 +8,7 @@ namespace Drupal\node\Plugin\views\field; use Drupal\Core\Form\FormStateInterface; +use Drupal\Core\Url; use Drupal\views\ResultRow; use Drupal\views\ViewExecutable; use Drupal\views\Plugin\views\display\DisplayPluginBase; @@ -68,7 +69,7 @@ protected function renderLink($data, ResultRow $values) { $this->options['alter']['make_link'] = TRUE; $nid = $this->getValue($values, 'nid'); $vid = $this->getValue($values, 'vid'); - $this->options['alter']['path'] = "node/" . $nid . '/revisions/' . $vid . '/view'; + $this->options['alter']['url'] = Url::fromRoute('node.revision_show', ['node' => $nid, 'node_revision' => $vid]); } else { return parent::renderLink($data, $values); diff --git a/core/modules/node/src/Plugin/views/field/RevisionLink.php b/core/modules/node/src/Plugin/views/field/RevisionLink.php index 0298a5d..569532f 100644 --- a/core/modules/node/src/Plugin/views/field/RevisionLink.php +++ b/core/modules/node/src/Plugin/views/field/RevisionLink.php @@ -8,6 +8,7 @@ namespace Drupal\node\Plugin\views\field; use Drupal\Core\Session\AccountInterface; +use Drupal\Core\Url; use Drupal\node\Plugin\views\field\Link; use Drupal\views\Plugin\views\display\DisplayPluginBase; use Drupal\views\ResultRow; @@ -56,13 +57,15 @@ protected function renderLink($data, ResultRow $values) { } // Current revision uses the node view path. - $path = 'node/' . $node->nid; if (!$node->isDefaultRevision()) { - $path .= "/revisions/$vid/view"; + $url = Url::fromRoute('node.revision_show', ['node' => $node->nid, 'node_revision' => $vid]); + } + else { + $url = $node->urlInfo(); } $this->options['alter']['make_link'] = TRUE; - $this->options['alter']['path'] = $path; + $this->options['alter']['url'] = $url; $this->options['alter']['query'] = drupal_get_destination(); return !empty($this->options['text']) ? $this->options['text'] : $this->t('View'); diff --git a/core/modules/node/src/Plugin/views/field/RevisionLinkDelete.php b/core/modules/node/src/Plugin/views/field/RevisionLinkDelete.php index dfc6891..1d5a14b 100644 --- a/core/modules/node/src/Plugin/views/field/RevisionLinkDelete.php +++ b/core/modules/node/src/Plugin/views/field/RevisionLinkDelete.php @@ -8,6 +8,7 @@ namespace Drupal\node\Plugin\views\field; use Drupal\Core\Session\AccountInterface; +use Drupal\Core\Url; use Drupal\node\Plugin\views\field\RevisionLink; use Drupal\views\ResultRow; @@ -50,7 +51,7 @@ protected function renderLink($data, ResultRow $values) { } $this->options['alter']['make_link'] = TRUE; - $this->options['alter']['path'] = 'node/' . $node->id() . "/revisions/$vid/delete"; + $this->options['alter']['url'] = Url::fromRoute('node.revision_delete_confirm', ['node' => $node->id(), 'node_revision' => $vid]); $this->options['alter']['query'] = drupal_get_destination(); return !empty($this->options['text']) ? $this->options['text'] : $this->t('Delete'); diff --git a/core/modules/node/src/Plugin/views/field/RevisionLinkRevert.php b/core/modules/node/src/Plugin/views/field/RevisionLinkRevert.php index 2ceb19f..17db8a8 100644 --- a/core/modules/node/src/Plugin/views/field/RevisionLinkRevert.php +++ b/core/modules/node/src/Plugin/views/field/RevisionLinkRevert.php @@ -8,6 +8,7 @@ namespace Drupal\node\Plugin\views\field; use Drupal\Core\Session\AccountInterface; +use Drupal\Core\Url; use Drupal\node\Plugin\views\field\RevisionLink; use Drupal\views\ResultRow; @@ -50,7 +51,7 @@ protected function renderLink($data, ResultRow $values) { } $this->options['alter']['make_link'] = TRUE; - $this->options['alter']['path'] = 'node/' . $node->id() . "/revisions/$vid/revert"; + $this->options['alter']['url'] = Url::fromRoute('node.revision_revert_confirm', ['node' => $node->id(), 'node_revision' => $vid]); $this->options['alter']['query'] = drupal_get_destination(); return !empty($this->options['text']) ? $this->options['text'] : $this->t('Revert'); diff --git a/core/modules/taxonomy/src/Plugin/views/field/Taxonomy.php b/core/modules/taxonomy/src/Plugin/views/field/Taxonomy.php index 7618e22..903f174 100644 --- a/core/modules/taxonomy/src/Plugin/views/field/Taxonomy.php +++ b/core/modules/taxonomy/src/Plugin/views/field/Taxonomy.php @@ -81,7 +81,7 @@ protected function renderLink($data, ResultRow $values) { if (!empty($this->options['link_to_taxonomy']) && $term && $data !== NULL && $data !== '') { $this->options['alter']['make_link'] = TRUE; - $this->options['alter']['path'] = $term->getSystemPath(); + $this->options['alter']['url'] = $term->urlInfo(); } if (!empty($this->options['convert_spaces'])) { diff --git a/core/modules/user/src/Plugin/views/field/Language.php b/core/modules/user/src/Plugin/views/field/Language.php index bf5abaa..708747d 100644 --- a/core/modules/user/src/Plugin/views/field/Language.php +++ b/core/modules/user/src/Plugin/views/field/Language.php @@ -7,6 +7,7 @@ namespace Drupal\user\Plugin\views\field; +use Drupal\Core\Url; use Drupal\views\ResultRow; /** @@ -26,7 +27,7 @@ protected function renderLink($data, ResultRow $values) { $uid = $this->getValue($values, 'uid'); if ($this->view->getUser()->hasPermission('access user profiles') && $uid) { $this->options['alter']['make_link'] = TRUE; - $this->options['alter']['path'] = 'user/' . $uid; + $this->options['alter']['url'] = Url::fromRoute('entity.user.canonical', ['user' => $uid]); } } if (empty($data)) { diff --git a/core/modules/user/src/Plugin/views/field/Link.php b/core/modules/user/src/Plugin/views/field/Link.php index b62d80e..d6f28e9 100644 --- a/core/modules/user/src/Plugin/views/field/Link.php +++ b/core/modules/user/src/Plugin/views/field/Link.php @@ -90,7 +90,7 @@ protected function renderLink(EntityInterface $entity, ResultRow $values) { $text = !empty($this->options['text']) ? $this->options['text'] : $this->t('View'); $this->options['alter']['make_link'] = TRUE; - $this->options['alter']['path'] = $entity->getSystemPath(); + $this->options['alter']['url'] = $entity->urlInfo(); return $text; } diff --git a/core/modules/user/src/Plugin/views/field/LinkCancel.php b/core/modules/user/src/Plugin/views/field/LinkCancel.php index acef491..abdbec7 100644 --- a/core/modules/user/src/Plugin/views/field/LinkCancel.php +++ b/core/modules/user/src/Plugin/views/field/LinkCancel.php @@ -28,7 +28,7 @@ protected function renderLink(EntityInterface $entity, ResultRow $values) { $text = !empty($this->options['text']) ? $this->options['text'] : $this->t('Cancel account'); - $this->options['alter']['path'] = $entity->getSystemPath('cancel-form'); + $this->options['alter']['url'] = $entity->urlInfo('cancel-form'); $this->options['alter']['query'] = drupal_get_destination(); return $text; diff --git a/core/modules/user/src/Plugin/views/field/Mail.php b/core/modules/user/src/Plugin/views/field/Mail.php index 55db278..e933c0d 100644 --- a/core/modules/user/src/Plugin/views/field/Mail.php +++ b/core/modules/user/src/Plugin/views/field/Mail.php @@ -8,6 +8,7 @@ namespace Drupal\user\Plugin\views\field; use Drupal\Core\Form\FormStateInterface; +use Drupal\Core\Url; use Drupal\views\ResultRow; /** diff --git a/core/modules/user/src/Plugin/views/field/User.php b/core/modules/user/src/Plugin/views/field/User.php index 9b33e1e..72d22c6 100644 --- a/core/modules/user/src/Plugin/views/field/User.php +++ b/core/modules/user/src/Plugin/views/field/User.php @@ -66,7 +66,7 @@ public function buildOptionsForm(&$form, FormStateInterface $form_state) { protected function renderLink($data, ResultRow $values) { if (!empty($this->options['link_to_user']) && $this->view->getUser()->hasPermission('access user profiles') && ($entity = $this->getEntity($values)) && $data !== NULL && $data !== '') { $this->options['alter']['make_link'] = TRUE; - $this->options['alter']['path'] = $entity->getSystemPath(); + $this->options['alter']['url'] = $entity->urlInfo(); } return $data; } diff --git a/core/modules/views/src/Plugin/views/PluginBase.php b/core/modules/views/src/Plugin/views/PluginBase.php index d26a5ea..8cd191f 100644 --- a/core/modules/views/src/Plugin/views/PluginBase.php +++ b/core/modules/views/src/Plugin/views/PluginBase.php @@ -370,6 +370,7 @@ protected function viewsTokenReplace($text, $tokens) { '#template' => $text, '#context' => $twig_tokens, ); + return drupal_render($build); } else { diff --git a/core/modules/views/src/Plugin/views/field/EntityLabel.php b/core/modules/views/src/Plugin/views/field/EntityLabel.php index 8b39f56..58b9687 100644 --- a/core/modules/views/src/Plugin/views/field/EntityLabel.php +++ b/core/modules/views/src/Plugin/views/field/EntityLabel.php @@ -112,7 +112,7 @@ public function render(ResultRow $values) { if (!empty($this->options['link_to_entity'])) { $this->options['alter']['make_link'] = TRUE; - $this->options['alter']['path'] = $entity->getSystemPath(); + $this->options['alter']['url'] = $entity->urlInfo(); } return $this->sanitizeValue($entity->label()); diff --git a/core/modules/views/src/Plugin/views/field/FieldPluginBase.php b/core/modules/views/src/Plugin/views/field/FieldPluginBase.php index d60fcba..d683ea7 100644 --- a/core/modules/views/src/Plugin/views/field/FieldPluginBase.php +++ b/core/modules/views/src/Plugin/views/field/FieldPluginBase.php @@ -1259,7 +1259,7 @@ public function renderText($alter) { } $this->last_render_text = $value; - if (!empty($alter['make_link']) && !empty($alter['path'])) { + if (!empty($alter['make_link']) && (!empty($alter['path']) || !empty($alter['url']))) { if (!isset($tokens)) { $tokens = $this->getRenderTokens($alter); } @@ -1295,34 +1295,52 @@ public function renderTrimText($alter, $value) { * the user. */ protected function renderAsLink($alter, $text, $tokens) { - $value = ''; - - if (!empty($alter['prefix'])) { - $value .= Xss::filterAdmin($this->viewsTokenReplace($alter['prefix'], $tokens)); - } - $options = array( - 'html' => TRUE, 'absolute' => !empty($alter['absolute']) ? TRUE : FALSE, + 'alias' => FALSE, + 'entity' => NULL, + 'entity_type' => NULL, + 'fragment' => NULL, + 'html' => TRUE, + 'language' => NULL, + 'query' => [], ); $alter += [ 'path' => NULL ]; - // $path will be run through check_url() by _l() so we do not need to - // sanitize it ourselves. $path = $alter['path']; + if (empty($alter['url'])) { + if (!parse_url($path, PHP_URL_SCHEME)) { + $alter['url'] = CoreUrl::fromUri('user-path:/' . ltrim($path, '/')); + } + else { + $alter['url'] = CoreUrl::fromUri($path); + } + } + + $options = $alter['url']->getOptions() + $options; + + $path = $alter['url']->setOptions($options)->toUriString(); // strip_tags() removes , so check whether its different to front. - if ($path != '') { + if ($path != 'route:') { + // Unescape Twig delimiters that may have been escaped by the + // Url::toUriString() call above, because we support twig tokens in + // rewrite settings of views fields. + // In that case the original path looks like + // user-path:/admin/content/files/usage/{{fid}}, which will be escaped by + // the toUriString() call above. + $path = str_replace(['%7B','%7D'], ['{','}'], $path); + // Use strip tags as there should never be HTML in the path. // However, we need to preserve special characters like " that // were removed by String::checkPlain(). $path = strip_tags(String::decodeEntities($this->viewsTokenReplace($path, $tokens))); - if (!empty($alter['path_case']) && $alter['path_case'] != 'none') { - $path = $this->caseTransform($path, $this->options['alter']['path_case']); + if (!empty($alter['path_case']) && $alter['path_case'] != 'none' && !$alter['url']->isRouted()) { + $path = str_replace($alter['path'], $this->caseTransform($alter['path'], $this->options['alter']['path_case']), $path); } if (!empty($alter['replace_spaces'])) { @@ -1355,7 +1373,8 @@ protected function renderAsLink($alter, $text, $tokens) { if ($alter['external']) { if (!isset($url['scheme'])) { // There is no scheme, add the default 'http://' to the $path. - $path = "http://$path"; + // Use the original $alter['path'] instead of the parsed version. + $path = "http://" . $alter['path']; // Reset the $url array to include the new scheme. $url = UrlHelper::parse($path); } @@ -1403,6 +1422,7 @@ protected function renderAsLink($alter, $text, $tokens) { $options['attributes']['rel'] = $rel; } + // Not sure if this String::checkPlain() is needed here? $target = String::checkPlain(trim($this->viewsTokenReplace($alter['target'], $tokens))); if (!empty($target)) { $options['attributes']['target'] = $target; @@ -1448,16 +1468,19 @@ protected function renderAsLink($alter, $text, $tokens) { $options['entity_type'] = $alter['entity_type']; } - // @todo Add proper support for url objects, see - // https://www.drupal.org/node/2404603 - // This means for example taking into account the options. - if (isset($options['url']) && $options['url'] instanceof Url) { - $value .= $this->linkGenerator()->generate($text, $options['url']); - } - else { - $value .= _l($text, $path, $options); + // The path has been heavily processed above, so it should be used as-is. + $final_url = CoreUrl::fromUri($path, $options); + + // Build the link based on our altered Url object, adding on the optional + // prefix and suffix + $value = ''; + + if (!empty($alter['prefix'])) { + $value .= Xss::filterAdmin($this->viewsTokenReplace($alter['prefix'], $tokens)); } + $value .= $this->linkGenerator()->generate($text, $final_url); + if (!empty($alter['suffix'])) { $value .= Xss::filterAdmin($this->viewsTokenReplace($alter['suffix'], $tokens)); } @@ -1474,7 +1497,7 @@ public function getRenderTokens($item) { $tokens = $this->view->build_info['substitutions']; } $count = 0; - foreach ($this->view->display_handler->getHandlers('argument') as $arg => $handler) { + foreach ($this->displayHandler->getHandlers('argument') as $arg => $handler) { $token = '%' . ++$count; if (!isset($tokens[$token])) { $tokens[$token] = ''; @@ -1487,10 +1510,12 @@ public function getRenderTokens($item) { } // Get flattened set of tokens for any array depth in query parameters. - $tokens += $this->getTokenValuesRecursive($this->view->getRequest()->query->all()); + if ($request = $this->view->getRequest()) { + $tokens += $this->getTokenValuesRecursive($request->query->all()); + } // Now add replacements for our fields. - foreach ($this->view->display_handler->getHandlers('field') as $field => $handler) { + foreach ($this->displayHandler->getHandlers('field') as $field => $handler) { if (isset($handler->last_render)) { $tokens["{{ $field }}"] = $handler->last_render; } @@ -1718,5 +1743,5 @@ protected function getRenderer() { } /** - * @} + * @} End of "defgroup views_field_handlers". */ diff --git a/core/modules/views/tests/src/Unit/Plugin/field/FieldPluginBaseTest.php b/core/modules/views/tests/src/Unit/Plugin/field/FieldPluginBaseTest.php new file mode 100644 index 0000000..78d8325 --- /dev/null +++ b/core/modules/views/tests/src/Unit/Plugin/field/FieldPluginBaseTest.php @@ -0,0 +1,447 @@ + FALSE, + 'alias' => FALSE, + 'entity' => NULL, + 'entity_type' => NULL, + 'html' => TRUE, + 'language' => NULL, + 'query' => [], + 'set_active_class' => FALSE, + ]; + + /** + * The mocked link generator. + * + * @var \Drupal\Core\Utility\LinkGeneratorInterface|\PHPUnit_Framework_MockObject_MockObject + */ + protected $linkGenerator; + + /** + * The mocked view executable. + * + * @var \Drupal\views\ViewExecutable|\PHPUnit_Framework_MockObject_MockObject + */ + protected $executable; + + /** + * The mocked display plugin instance. + * + * @var \Drupal\views\Plugin\views\display\DisplayPluginBase|\PHPUnit_Framework_MockObject_MockObject + */ + protected $display; + + /** + * The mocked url generator. + * + * @var \Drupal\Core\Routing\UrlGeneratorInterface|\PHPUnit_Framework_MockObject_MockObject + */ + protected $urlGenerator; + + /** + * The mocked path validator. + * + * @var \Drupal\Core\Path\PathValidatorInterface|\PHPUnit_Framework_MockObject_MockObject + */ + protected $pathValidator; + + /** + * The unrouted url assembler service. + * + * @var \Drupal\Core\Utility\UnroutedUrlAssemblerInterface|\PHPUnit_Framework_MockObject_MockObject + */ + protected $unroutedUrlAssembler; + + /** + * The request stack. + * + * @var \Symfony\Component\HttpFoundation\RequestStack + */ + protected $requestStack; + + /** + * The mocked path processor. + * + * @var \Drupal\Core\PathProcessor\OutboundPathProcessorInterface|\PHPUnit_Framework_MockObject_MockObject + */ + protected $pathProcessor; + + /** + * {@inheritdoc} + */ + protected function setUp() { + parent::setUp(); + + $this->executable = $this->getMockBuilder('Drupal\views\ViewExecutable') + ->disableOriginalConstructor() + ->getMock(); + $this->display = $this->getMockBuilder('Drupal\views\Plugin\views\display\DisplayPluginBase') + ->disableOriginalConstructor() + ->getMock(); + + $route_provider = $this->getMock('Drupal\Core\Routing\RouteProviderInterface'); + $route_provider->expects($this->any()) + ->method('getRouteByName') + ->with('test_route') + ->willReturn(new Route('/test-path')); + + $this->urlGenerator = $this->getMock('Drupal\Core\Routing\UrlGeneratorInterface'); + $this->pathValidator = $this->getMock('Drupal\Core\Path\PathValidatorInterface'); + + $this->requestStack = new RequestStack(); + $this->requestStack->push(new Request()); + + $this->unroutedUrlAssembler = $this->getMock('Drupal\Core\Utility\UnroutedUrlAssemblerInterface'); + $this->linkGenerator = $this->getMock('Drupal\Core\Utility\LinkGeneratorInterface'); + + $container_builder = new ContainerBuilder(); + $container_builder->set('url_generator', $this->urlGenerator); + $container_builder->set('path.validator', $this->pathValidator); + $container_builder->set('unrouted_url_assembler', $this->unroutedUrlAssembler); + $container_builder->set('request_stack', $this->requestStack); + \Drupal::setContainer($container_builder); + } + + /** + * Sets up the unrouted url assembler and the link generator. + */ + protected function setUpUrlIntegrationServices() { + $config = $this->getMockBuilder('Drupal\Core\Config\ImmutableConfig') + ->disableOriginalConstructor() + ->getMock(); + $config_factory = $this->getMock('\Drupal\Core\Config\ConfigFactoryInterface'); + $config_factory->expects($this->any()) + ->method('get') + ->willReturn($config); + + $this->pathProcessor = $this->getMock('Drupal\Core\PathProcessor\OutboundPathProcessorInterface'); + $this->unroutedUrlAssembler = new UnroutedUrlAssembler($this->requestStack, $config_factory, $this->pathProcessor); + + \Drupal::getContainer()->set('unrouted_url_assembler', $this->unroutedUrlAssembler); + + $this->linkGenerator = new LinkGenerator($this->urlGenerator, $this->getMock('Drupal\Core\Extension\ModuleHandlerInterface')); + } + + /** + * Sets up a display with empty arguments and fields. + */ + protected function setupDisplayWithEmptyArgumentsAndFields() { + $this->display->expects($this->any()) + ->method('getHandlers') + ->willReturnMap([ + ['argument', []], + ['field', []], + ]); + } + + /** + * Test rendering as a link without a path. + * + * @covers ::renderAsLink + */ + public function testRenderAsLinkWithoutPath() { + $alter = [ + 'make_link' => TRUE, + ]; + + $this->setUpUrlIntegrationServices(); + $field = $this->setupTestField(['alter' => $alter]); + $field->field_alias = 'key'; + $row = new ResultRow(['key' => 'value']); + + $expected_result = 'value'; + $result = $field->advancedRender($row); + $this->assertEquals($expected_result, $result); + } + + /** + * Test rendering of a link with a path and options. + * + * @dataProvider providerTestRenderAsLinkWithPathAndOptions + * @covers ::renderAsLink + */ + public function testRenderAsLinkWithPathAndOptions($path, $alter, $link_html, $final_html = NULL) { + $alter += [ + 'make_link' => TRUE, + 'path' => $path, + ]; + + $final_html = isset($final_html) ? $final_html : $link_html; + + $this->setUpUrlIntegrationServices(); + $this->setupDisplayWithEmptyArgumentsAndFields(); + $field = $this->setupTestField(['alter' => $alter]); + $field->field_alias = 'key'; + $row = new ResultRow(['key' => 'value']); + + $result = $field->advancedRender($row); + $this->assertEquals($final_html, $result); + } + + /** + * Data provider for ::testRenderAsLinkWithPathAndOptions(). + * + * @return array + * Test data. + */ + public function providerTestRenderAsLinkWithPathAndOptions() { + $data = []; + // Simple path with default options. + $data[] = ['test-path', [], [], 'value']; + // Add a fragment. + $data[] = ['test-path', ['fragment' => 'test'], 'value']; + // Rel attributes. + $data[] = ['test-path', ['rel' => 'up'], 'value']; + // Target attributes. + $data[] = ['test-path', ['target' => '_blank'], 'value']; + // Link attributes. + $data[] = ['test-path', ['link_attributes' => ['foo' => 'bar']], 'value']; + // Manual specified query. + $data[] = ['test-path', ['query' => ['foo' => 'bar']], 'value']; + // Query specified as part of the path. + $data[] = ['test-path?foo=bar', [], 'value']; + // Query specified as option and path. + // @todo Do we expect that options override all existing ones? + $data[] = ['test-path?foo=bar', ['query' => ['key' => 'value']], 'value']; + // Alias flag. + $data[] = ['test-path', ['alias' => TRUE], 'value']; + // Note: In contrast to the testRenderAsLinkWithUrlAndOptions test we don't + // test the language, because the path processor for the language won't be + // executed for paths which aren't routed. + + // Entity flag. + $entity = $this->getMock('Drupal\Core\Entity\EntityInterface'); + $data[] = ['test-path', ['entity' => $entity], 'value']; + // entity_type flag. + $entity_type_id = 'node'; + $data[] = ['test-path', ['entity_type' => $entity_type_id], 'value']; + // prefix + $data[] = ['test-path', ['prefix' => 'test_prefix'], 'value', 'test_prefixvalue']; + // suffix. + $data[] = ['test-path', ['suffix' => 'test_suffix'], 'value', 'valuetest_suffix']; + + // External URL. + $data[] = ['https://www.drupal.org', [], [], 'value']; + + return $data; + } + + /** + * Tests link rendering with a URL and options. + * + * @dataProvider providerTestRenderAsLinkWithUrlAndOptions + * @covers ::renderAsLink + */ + public function testRenderAsLinkWithUrlAndOptions(Url $url, $alter, Url $expected_url, $url_path, Url $expected_link_url, $link_html, $final_html = NULL) { + $alter += [ + 'make_link' => TRUE, + 'url' => $url, + ]; + + $final_html = isset($final_html) ? $final_html : $link_html; + + $this->setUpUrlIntegrationServices(); + $this->setupDisplayWithEmptyArgumentsAndFields(); + $field = $this->setupTestField(['alter' => $alter]); + $field->field_alias = 'key'; + $row = new ResultRow(['key' => 'value']); + + $expected_url->setOptions($expected_url->getOptions() + $this->defaultUrlOptions); + $expected_link_url->setUrlGenerator($this->urlGenerator); + + $expected_url_options = $expected_url->getOptions(); + unset($expected_url_options['attributes']); + + $this->urlGenerator->expects($this->once()) + ->method('generateFromRoute') + ->with($expected_url->getRouteName(), $expected_url->getRouteParameters(), $expected_url_options) + ->willReturn($url_path); + + $result = $field->advancedRender($row); + $this->assertEquals($final_html, $result); + } + + /** + * Data provider for ::testRenderAsLinkWithUrlAndOptions(). + * + * @return array + * Array of test data. + */ + public function providerTestRenderAsLinkWithUrlAndOptions() { + $data = []; + + // Simple path with default options. + $url = Url::fromRoute('test_route'); + $data[]= [$url, [], clone $url, '/test-path', clone $url, 'value']; + + // Simple url with parameters. + $url_parameters = Url::fromRoute('test_route', ['key' => 'value']); + $data[]= [$url_parameters, [], clone $url_parameters, '/test-path/value', clone $url_parameters, 'value']; + + // Add a fragment. + $url = Url::fromRoute('test_route'); + $url_with_fragment = Url::fromRoute('test_route'); + $options = ['fragment' => 'test'] + $this->defaultUrlOptions; + $url_with_fragment->setOptions($options); + $data[]= [$url, ['fragment' => 'test'], $url_with_fragment, '/test-path#test', clone $url_with_fragment, 'value']; + + // Rel attributes. + $url = Url::fromRoute('test_route'); + $url_with_rel = Url::fromRoute('test_route'); + $options = ['attributes' => ['rel' => 'up']] + $this->defaultUrlOptions; + $url_with_rel->setOptions($options); + $data[]= [$url, ['rel' => 'up'], clone $url, '/test-path', $url_with_rel, 'value']; + + // Target attributes. + $url = Url::fromRoute('test_route'); + $url_with_target = Url::fromRoute('test_route'); + $options = ['attributes' => ['target' => '_blank']] + $this->defaultUrlOptions; + $url_with_target->setOptions($options); + $data[]= [$url, ['target' => '_blank'], $url_with_target, '/test-path', clone $url_with_target, 'value']; + + // Link attributes. + $url = Url::fromRoute('test_route'); + $url_with_link_attributes = Url::fromRoute('test_route'); + $options = ['attributes' => ['foo' => 'bar']] + $this->defaultUrlOptions; + $url_with_link_attributes->setOptions($options); + $data[]= [$url, ['link_attributes' => ['foo' => 'bar']], clone $url, '/test-path', $url_with_link_attributes, 'value']; + + // Manual specified query. + $url = Url::fromRoute('test_route'); + $url_with_query = Url::fromRoute('test_route'); + $options = ['query' => ['foo' => 'bar']] + $this->defaultUrlOptions; + $url_with_query->setOptions($options); + $data[]= [$url, ['query' => ['foo' => 'bar']], clone $url_with_query, '/test-path?foo=bar', $url_with_query, 'value']; + + // Query specified as part of the path. + $url = Url::fromRoute('test_route')->setOption('query', ['foo' => 'bar']); + $url_with_query = clone $url; + $url_with_query->setOptions(['query' => ['foo' => 'bar']] + $url_with_query->getOptions()); + $data[] = [$url, [], $url_with_query, '/test-path?foo=bar', clone $url, 'value']; + + // Query specified as option and path. + $url = Url::fromRoute('test_route')->setOption('query', ['foo' => 'bar']); + $url_with_query = Url::fromRoute('test_route'); + $options = ['query' => ['key' => 'value']] + $this->defaultUrlOptions; + $url_with_query->setOptions($options); + $data[] = [$url, ['query' => ['key' => 'value']], $url_with_query, '/test-path?key=value', clone $url_with_query, 'value']; + + // Alias flag. + $url = Url::fromRoute('test_route'); + $url_without_alias = Url::fromRoute('test_route'); + $options = ['alias' => TRUE] + $this->defaultUrlOptions; + $url_without_alias->setOptions($options); + $data[] = [$url, ['alias' => TRUE], $url_without_alias, '/test-path', clone $url_without_alias, 'value']; + + // Language flag. + $language = new Language(['id' => 'fr']); + $url = Url::fromRoute('test_route'); + $url_with_language = Url::fromRoute('test_route'); + $options = ['language' => $language] + $this->defaultUrlOptions; + $url_with_language->setOptions($options); + $data[] = [$url, ['language' => $language], $url_with_language, '/fr/test-path', clone $url_with_language, 'value']; + + // Entity flag. + $entity = $this->getMock('Drupal\Core\Entity\EntityInterface'); + $url = Url::fromRoute('test_route'); + $url_with_entity = Url::fromRoute('test_route'); + $options = ['entity' => $entity] + $this->defaultUrlOptions; + $url_with_entity->setOptions($options); + $data[] = [$url, ['entity' => $entity], $url_with_entity, '/test-path', clone $url_with_entity, 'value']; + + // Test entity_type flag. + $entity_type_id = 'node'; + $url = Url::fromRoute('test_route'); + $url_with_entity_type = Url::fromRoute('test_route'); + $options = ['entity_type' => $entity_type_id] + $this->defaultUrlOptions; + $url_with_entity_type->setOptions($options); + $data[] = [$url, ['entity_type' => $entity_type_id], $url_with_entity_type, '/test-path', clone $url_with_entity_type, 'value']; + + // Test prefix. + $url = Url::fromRoute('test_route'); + $data[] = [$url, ['prefix' => 'test_prefix'], clone $url, '/test-path', clone $url, 'value', 'test_prefixvalue']; + + // Test suffix. + $url = Url::fromRoute('test_route'); + $data[] = [$url, ['suffix' => 'test_suffix'], clone $url, '/test-path', clone $url, 'value', 'valuetest_suffix']; + + return $data; + } + + /** + * Sets up a test field. + * + * @return \Drupal\Tests\views\Unit\Plugin\field\TestField|\PHPUnit_Framework_MockObject_MockObject + * The test field. + */ + protected function setupTestField(array $options = []) { + /** @var \Drupal\Tests\views\Unit\Plugin\field\TestField $field */ + $field = $this->getMock('Drupal\Tests\views\Unit\Plugin\field\TestField', ['l'], [$this->configuration, $this->pluginId, $this->pluginDefinition]); + $field->init($this->executable, $this->display, $options); + $field->setLinkGenerator($this->linkGenerator); + + return $field; + } + +} + +class TestField extends FieldPluginBase { + + public function setLinkGenerator(LinkGeneratorInterface $link_generator) { + $this->linkGenerator = $link_generator; + } + +}