diff --git a/core/modules/views/src/Plugin/views/field/FieldPluginBase.php b/core/modules/views/src/Plugin/views/field/FieldPluginBase.php index 96416fe..7027411 100644 --- a/core/modules/views/src/Plugin/views/field/FieldPluginBase.php +++ b/core/modules/views/src/Plugin/views/field/FieldPluginBase.php @@ -1364,6 +1364,43 @@ protected function renderAsLink($alter, $text, $tokens) { ]; $path = $alter['path']; + // strip_tags() and viewsTokenReplace remove , so check whether it's + // different to front. + if ($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 SafeMarkup::checkPlain(). + $path = Html::decodeEntities($this->viewsTokenReplace($alter['path'], $tokens)); + + // Tokens might contain , so check for again. + if ($path != '') { + $path = strip_tags($path); + } + + // Tokens might have resolved URL's, as is the case for tokens provided by + // Link fields, so all internal paths will be prefixed by base_path(). For + // proper further handling reset this to internal:/. + if (strpos($path, base_path()) === 0) { + $path = 'internal:/' . substr($path, strlen(base_path())); + } + + // If we have no $path and no $alter['url'], we have nothing to work with, + // so we just return the text. + if (empty($path) && empty($alter['url'])) { + return $text; + } + + // If no scheme is provided in the $path, assign the default 'http://'. + // This allows a url of 'www.example.com' to be converted to + // 'http://www.example.com'. + // Only do this when flag for external has been set, $path doesn't contain + // a scheme and $path doesn't have a leading /. + if ($alter['external'] && !parse_url($path, PHP_URL_SCHEME) && strpos($path, '/') !== 0) { + // There is no scheme, add the default 'http://' to the $path. + $path = "http://" . $path; + } + } + if (empty($alter['url'])) { if (!parse_url($path, PHP_URL_SCHEME)) { // @todo Views should expect and store a leading /. See @@ -1379,28 +1416,12 @@ protected function renderAsLink($alter, $text, $tokens) { $path = $alter['url']->setOptions($options)->toUriString(); - // strip_tags() removes , so check whether its different to front. - 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 - // internal:/admin/content/files/usage/{{ fid }}, which will be escaped by - // the toUriString() call above. - $path = preg_replace(['/(\%7B){2}(\%20)*/', '/(\%20)*(\%7D){2}/'], ['{{','}}'], $path); - - // Use strip tags as there should never be HTML in the path. - // However, we need to preserve special characters like " that are escaped - // by \Drupal\Component\Utility\Html::escape(). - $path = strip_tags(Html::decodeEntities($this->viewsTokenReplace($path, $tokens))); - - 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['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'])) { - $path = str_replace(' ', '-', $path); - } + if (!empty($alter['replace_spaces'])) { + $path = str_replace(' ', '-', $path); } // Parse the URL and move any query and fragment parameters out of the path. @@ -1422,19 +1443,6 @@ protected function renderAsLink($alter, $text, $tokens) { // $path now so we don't get query strings or fragments in the path. $path = $url['path']; - // If no scheme is provided in the $path, assign the default 'http://'. - // This allows a url of 'www.example.com' to be converted to 'http://www.example.com'. - // Only do this on for external URLs. - if ($alter['external']) { - if (!isset($url['scheme'])) { - // There is no scheme, add the default 'http://' to the $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); - } - } - if (isset($url['query'])) { // Remove query parameters that were assigned a query string replacement // token for which there is no value available. diff --git a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_preview_error.yml b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_preview_error.yml new file mode 100644 index 0000000..1c4d390 --- /dev/null +++ b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_preview_error.yml @@ -0,0 +1,23 @@ +langcode: en +status: true +dependencies: + module: + - user +id: test_preview_error +label: test_preview_error +module: views +description: '' +tag: '' +base_table: views_test_data +base_field: id +core: 8.x +display: + default: + display_plugin: default + id: default + display_title: Master + position: null + display_options: + row: + type: fields + fields: { } diff --git a/core/modules/views/tests/src/Unit/Plugin/field/FieldPluginBaseTest.php b/core/modules/views/tests/src/Unit/Plugin/field/FieldPluginBaseTest.php index 98dcb1d..e3dc3f8 100644 --- a/core/modules/views/tests/src/Unit/Plugin/field/FieldPluginBaseTest.php +++ b/core/modules/views/tests/src/Unit/Plugin/field/FieldPluginBaseTest.php @@ -326,6 +326,8 @@ public function providerTestRenderAsLinkWithPathAndOptions() { // External URL. $data[] = ['https://www.drupal.org', [], [], 'value']; + $data[] = ['www.drupal.org', ['external' => TRUE], [], 'value']; + $data[] = ['', ['external' => TRUE], [], 'value']; return $data; } @@ -493,7 +495,7 @@ public function testRenderAsLinkWithPathAndTokens($path, $tokens, $link_html) { $build =[ '#type' => 'inline_template', - '#template' => 'base:test-path/' . explode('/', $path)[1], + '#template' => 'test-path/' . explode('/', $path)[1], '#context' => ['foo' => 123], '#post_render' => [function() {}], ]; @@ -533,6 +535,62 @@ public function providerTestRenderAsLinkWithPathAndTokens() { } /** + * Test rendering of a link with a path and options. + * + * @dataProvider providerTestRenderAsExternalLinkWithPathAndTokens + * @covers ::renderAsLink + */ + public function testRenderAsExternalLinkWithPathAndTokens($path, $tokens, $link_html, $context) { + $alter = [ + 'make_link' => TRUE, + 'path' => $path, + 'url' => '', + ]; + if (isset($context['alter'])) { + $alter += $context['alter']; + } + + $this->setUpUrlIntegrationServices(); + $this->setupDisplayWithEmptyArgumentsAndFields(); + $this->executable->build_info['substitutions'] = $tokens; + $field = $this->setupTestField(['alter' => $alter]); + $field->field_alias = 'key'; + $row = new ResultRow(['key' => 'value']); + + $build = [ + '#type' => 'inline_template', + '#template' => $path, + '#context' => ['foo' => $context['context_path']], + '#post_render' => [function() {}], + ]; + + $this->renderer->expects($this->once()) + ->method('renderPlain') + ->with($build) + ->willReturn($context['context_path']); + + $result = $field->advancedRender($row); + $this->assertEquals($link_html, $result); + } + + /** + * Data provider for ::testRenderAsExternalLinkWithPathAndTokens(). + * + * @return array + * Test data. + */ + public function providerTestRenderAsExternalLinkWithPathAndTokens() { + $data = []; + + $data[] = ['{{ foo }}', ['{{ foo }}' => 'http://www.drupal.org'], 'value', ['context_path' => 'http://www.drupal.org']]; + $data[] = ['{{ foo }}', ['{{ foo }}' => ''], 'value', ['context_path' => '']]; + $data[] = ['{{ foo }}', ['{{ foo }}' => ''], 'value', ['context_path' => '', 'alter' => ['external' => TRUE]]]; + $data[] = ['{{ foo }}', ['{{ foo }}' => '/test-path/123'], 'value', ['context_path' => '/test-path/123']]; + + return $data; + } + + /** * Sets up a test field. * * @return \Drupal\Tests\views\Unit\Plugin\field\FieldPluginBaseTestField|\PHPUnit_Framework_MockObject_MockObject