Commit 7b5e5a95 authored by alexpott's avatar alexpott

Issue #2404603 by mpdonadio, dawehner, larowlan, RavindraSingh: Add proper...

Issue #2404603 by mpdonadio, dawehner, larowlan, RavindraSingh: Add proper support for Url objects in FieldPluginBase::renderAsLink(), so we can remove EntityInterface::getSystemPath()
parent 203ead62
......@@ -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;
}
......
......@@ -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();
}
}
......
......@@ -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;
......
......@@ -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;
}
......
......@@ -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;
......
......@@ -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;
}
......
......@@ -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;
}
......
......@@ -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';
}
......
......@@ -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);
......
......@@ -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;
}
......
......@@ -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());
......
......@@ -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;
......
......@@ -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;
}
......
......@@ -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');
......
......@@ -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');
......
......@@ -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');
......
......@@ -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);
......
......@@ -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');
......
......@@ -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');
......
......@@ -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');
......
......@@ -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'])) {
......
......@@ -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)) {
......
......@@ -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;
}
......
......@@ -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;
......
......@@ -8,6 +8,7 @@
namespace Drupal\user\Plugin\views\field;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Url;
use Drupal\views\ResultRow;
/**
......
......@@ -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;
}
......
......@@ -370,6 +370,7 @@ protected function viewsTokenReplace($text, $tokens) {
'#template' => $text,
'#context' => $twig_tokens,
);
return drupal_render($build);
}
else {
......
......@@ -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());
......
......@@ -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 <front>, so check whether its different to front.
if ($path != '<front>') {
if ($path != 'route:<front>') {
// 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".
*/
<?php
/**
* @file
* Contains \Drupal\Tests\views\Unit\Plugin\field\FieldPluginBaseTest.
*/
namespace Drupal\Tests\views\Unit\Plugin\field;
use Drupal\Core\Language\Language;
use Drupal\Core\Url;
use Drupal\Core\Utility\LinkGenerator;
use Drupal\Core\Utility\LinkGeneratorInterface;
use Drupal\Core\Utility\UnroutedUrlAssembler;
use Drupal\Tests\UnitTestCase;
use Drupal\views\Plugin\views\field\FieldPluginBase;
use Drupal\views\ResultRow;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\Routing\Route;
/**
* @coversDefaultClass \Drupal\views\Plugin\views\field\FieldPluginBase
* @group views
*/
class FieldPluginBaseTest extends UnitTestCase {
/**
* The configuration of the plugin under test.