diff --git a/core/lib/Drupal.php b/core/lib/Drupal.php index eac2c40..455cd7a 100644 --- a/core/lib/Drupal.php +++ b/core/lib/Drupal.php @@ -547,15 +547,14 @@ public static function linkGenerator() { * associated bubbleable metadata are returned. * * @return \Drupal\Core\GeneratedLink - * An HTML string containing a link to the given route and parameters. - * When $collect_bubbleable_metadata is TRUE, the GeneratedLink object has - * bubbleable metadata. + * A GeneratedLink object containing a link to the given route and + * parameters and bubbleable metadata. * * @see \Drupal\Core\Utility\LinkGeneratorInterface::generate() * @see \Drupal\Core\Url */ - public static function l($text, Url $url, $collect_bubbleable_metadata = FALSE) { - return static::getContainer()->get('link_generator')->generate($text, $url, $collect_bubbleable_metadata); + public static function l($text, Url $url) { + return static::getContainer()->get('link_generator')->generate($text, $url); } /** diff --git a/core/lib/Drupal/Core/Link.php b/core/lib/Drupal/Core/Link.php index ed541e2..8db6591 100644 --- a/core/lib/Drupal/Core/Link.php +++ b/core/lib/Drupal/Core/Link.php @@ -150,8 +150,8 @@ public function setUrl(Url $url) { * @deprecated in Drupal 8.0.x-dev, will be removed before Drupal 9.0.0. Use * self::toRenderable() instead. */ - public function toString($collect_bubbleable_metadata = FALSE) { - return $this->getLinkGenerator()->generateFromLink($this, $collect_bubbleable_metadata); + public function toString() { + return $this->getLinkGenerator()->generateFromLink($this); } /** diff --git a/core/lib/Drupal/Core/Render/Element/Link.php b/core/lib/Drupal/Core/Render/Element/Link.php index bec2341..93ab693 100644 --- a/core/lib/Drupal/Core/Render/Element/Link.php +++ b/core/lib/Drupal/Core/Render/Element/Link.php @@ -82,7 +82,7 @@ public static function preRenderLink($element) { $options = NestedArray::mergeDeep($element['#url']->getOptions(), $element['#options']); /** @var \Drupal\Core\Utility\LinkGenerator $link_generator */ $link_generator = \Drupal::service('link_generator'); - $generated_link = $link_generator->generate($element['#title'], $element['#url']->setOptions($options), TRUE); + $generated_link = $link_generator->generate($element['#title'], $element['#url']->setOptions($options)); $element['#markup'] = $generated_link->getGeneratedLink(); $generated_link->merge(BubbleableMetadata::createFromRenderArray($element)) ->applyTo($element); diff --git a/core/lib/Drupal/Core/Utility/LinkGenerator.php b/core/lib/Drupal/Core/Utility/LinkGenerator.php index c920e28..a3d991a 100644 --- a/core/lib/Drupal/Core/Utility/LinkGenerator.php +++ b/core/lib/Drupal/Core/Utility/LinkGenerator.php @@ -13,10 +13,10 @@ use Drupal\Component\Utility\SafeStringInterface; use Drupal\Core\Extension\ModuleHandlerInterface; use Drupal\Core\GeneratedLink; +use Drupal\Core\GeneratedUrl; use Drupal\Core\Link; use Drupal\Core\Path\AliasManagerInterface; use Drupal\Core\Render\RendererInterface; -use Drupal\Core\Render\SafeString; use Drupal\Core\Routing\UrlGeneratorInterface; use Drupal\Core\Template\Attribute; use Drupal\Core\Url; @@ -66,8 +66,8 @@ public function __construct(UrlGeneratorInterface $url_generator, ModuleHandlerI /** * {@inheritdoc} */ - public function generateFromLink(Link $link, $collect_bubbleable_metadata = FALSE) { - return $this->generate($link->getText(), $link->getUrl(), $collect_bubbleable_metadata); + public function generateFromLink(Link $link) { + return $this->generate($link->getText(), $link->getUrl()); } /** @@ -82,7 +82,7 @@ public function generateFromLink(Link $link, $collect_bubbleable_metadata = FALS * * @see system_page_attachments() */ - public function generate($text, Url $url, $collect_bubbleable_metadata = FALSE) { + public function generate($text, Url $url) { // Performance: avoid Url::toString() needing to retrieve the URL generator // service from the container. $url->setUrlGenerator($this->urlGenerator); @@ -155,18 +155,18 @@ public function generate($text, Url $url, $collect_bubbleable_metadata = FALSE) unset($variables['options']['attributes']); $url->setOptions($variables['options']); - if (!$collect_bubbleable_metadata) { - $url_string = $url->toString($collect_bubbleable_metadata); + // External urls can not have cacheable metadata. + if ($url->isExternal()) { $generated_link = new GeneratedLink(); + $attributes['href'] = $url->toString(FALSE); } else { - $generated_url = $url->toString($collect_bubbleable_metadata); - $url_string = $generated_url->getGeneratedUrl(); + $generated_url = $url->toString(TRUE); $generated_link = GeneratedLink::createFromObject($generated_url); + // The result of the URL generator is a plain-text URL to use as the href + // attribute, and it is escaped by \Drupal\Core\Template\Attribute. + $attributes['href'] = $generated_url->getGeneratedUrl(); } - // The result of the URL generator is a plain-text URL to use as the href - // attribute, and it is escaped by \Drupal\Core\Template\Attribute. - $attributes['href'] = $url_string; if (!SafeMarkup::isSafe($variables['text'])) { $variables['text'] = Html::escape($variables['text']); diff --git a/core/lib/Drupal/Core/Utility/LinkGeneratorInterface.php b/core/lib/Drupal/Core/Utility/LinkGeneratorInterface.php index 00d2566..383bf83 100644 --- a/core/lib/Drupal/Core/Utility/LinkGeneratorInterface.php +++ b/core/lib/Drupal/Core/Utility/LinkGeneratorInterface.php @@ -60,14 +60,10 @@ * class will be applied to the link. It is important to use this * sparingly since it is usually unnecessary and requires extra * processing. - * @param bool $collect_bubbleable_metadata - * (optional) Defaults to FALSE. When TRUE, both the generated link and its - * associated bubbleable metadata are returned. * * @return \Drupal\Core\GeneratedLink - * An HTML string containing a link to the given route and parameters. - * When $collect_bubbleable_metadata is TRUE, the GeneratedLink object has - * bubbleable metadata. + * A GeneratedLink object containing a link to the given route and + * parameters and bubbleable metadata. * * @throws \Symfony\Component\Routing\Exception\RouteNotFoundException * Thrown when the named route doesn't exist. @@ -77,22 +73,18 @@ * Thrown when a parameter value for a placeholder is not correct because it * does not match the requirement. */ - public function generate($text, Url $url, $collect_bubbleable_metadata = FALSE); + public function generate($text, Url $url); /** * Renders a link from a link object. * * @param \Drupal\Core\Link $link * A link object to convert to a string. - * @param bool $collect_bubbleable_metadata - * (optional) Defaults to FALSE. When TRUE, both the generated link and its - * associated bubbleable metadata are returned. * * @return \Drupal\Core\GeneratedLink - * An HTML string containing a link to the given route and parameters. - * When $collect_bubbleable_metadata is TRUE, the GeneratedLink object has - * bubbleable metadata. + * A GeneratedLink object containing a link to the given route and + * parameters and bubbleable metadata. */ - public function generateFromLink(Link $link, $collect_bubbleable_metadata = FALSE); + public function generateFromLink(Link $link); } diff --git a/core/modules/views/src/Plugin/views/field/FieldPluginBase.php b/core/modules/views/src/Plugin/views/field/FieldPluginBase.php index 29abf88..f168e6b 100644 --- a/core/modules/views/src/Plugin/views/field/FieldPluginBase.php +++ b/core/modules/views/src/Plugin/views/field/FieldPluginBase.php @@ -1307,8 +1307,10 @@ public function renderText($alter) { } // Preserve whether or not the string is safe. Since $suffix comes from - // \Drupal::l(), it is safe to append. - if ($value_is_safe) { + // \Drupal::l(), it is safe to append. Use SafeMarkup::isSafe() here because + // $value_is_safe is safe has been used above and renderAsLink() can return + // both safe and unsafe values. + if (SafeMarkup::isSafe($value)) { return ViewsRenderPipelineSafeString::create($value . $suffix); } else { @@ -1533,19 +1535,20 @@ protected function renderAsLink($alter, $text, $tokens) { // Build the link based on our altered Url object, adding on the optional // prefix and suffix - $value = ''; + $render = [ + '#type' => 'link', + '#title' => $text, + '#url' => $final_url, + ]; if (!empty($alter['prefix'])) { - $value .= Xss::filterAdmin($this->viewsTokenReplace($alter['prefix'], $tokens)); + $render['#prefix'] = $this->viewsTokenReplace($alter['prefix'], $tokens); } - - $value .= $this->linkGenerator()->generate($text, $final_url); - if (!empty($alter['suffix'])) { - $value .= Xss::filterAdmin($this->viewsTokenReplace($alter['suffix'], $tokens)); + $render['#suffix'] = $this->viewsTokenReplace($alter['suffix'], $tokens); } + return $this->getRenderer()->render($render); - return $value; } /** 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 65471b7..9dfbb06 100644 --- a/core/modules/views/tests/src/Unit/Plugin/field/FieldPluginBaseTest.php +++ b/core/modules/views/tests/src/Unit/Plugin/field/FieldPluginBaseTest.php @@ -7,7 +7,10 @@ namespace Drupal\Tests\views\Unit\Plugin\field; +use Drupal\Core\GeneratedUrl; use Drupal\Core\Language\Language; +use Drupal\Core\Render\RendererInterface; +use Drupal\Core\Render\SafeString; use Drupal\Core\Url; use Drupal\Core\Utility\LinkGenerator; use Drupal\Core\Utility\LinkGeneratorInterface; @@ -174,6 +177,22 @@ protected function setUpUrlIntegrationServices() { \Drupal::getContainer()->set('unrouted_url_assembler', $this->unroutedUrlAssembler); $this->linkGenerator = new LinkGenerator($this->urlGenerator, $this->getMock('Drupal\Core\Extension\ModuleHandlerInterface'), $this->renderer); + $this->renderer + ->method('render') + ->willReturnCallback( + // Pretend to do a render. + function (&$elements, $is_root_call = FALSE) { + // Mock the ability to theme links + $link = $this->linkGenerator->generate($elements['#title'], $elements['#url']); + if (isset($elements['#prefix'])) { + $link = $elements['#prefix'] . $link; + } + if (isset($elements['#suffix'])) { + $link = $link . $elements['#suffix']; + } + return SafeString::create($link); + } + ); } /** @@ -229,7 +248,7 @@ public function testRenderAsLinkWithPathAndOptions($path, $alter, $link_html, $f $row = new ResultRow(['key' => 'value']); $result = $field->advancedRender($row); - $this->assertEquals($final_html, $result); + $this->assertEquals($final_html, (string) $result); } /** @@ -308,8 +327,8 @@ public function testRenderAsLinkWithUrlAndOptions(Url $url, $alter, Url $expecte $this->urlGenerator->expects($this->once()) ->method('generateFromRoute') - ->with($expected_url->getRouteName(), $expected_url->getRouteParameters(), $expected_url_options) - ->willReturn($url_path); + ->with($expected_url->getRouteName(), $expected_url->getRouteParameters(), $expected_url_options, TRUE) + ->willReturn((new GeneratedUrl())->setGeneratedUrl($url_path)); $result = $field->advancedRender($row); $this->assertEquals($final_html, $result); diff --git a/core/tests/Drupal/Tests/Core/Utility/LinkGeneratorTest.php b/core/tests/Drupal/Tests/Core/Utility/LinkGeneratorTest.php index 4d1cee0..eb296cf 100644 --- a/core/tests/Drupal/Tests/Core/Utility/LinkGeneratorTest.php +++ b/core/tests/Drupal/Tests/Core/Utility/LinkGeneratorTest.php @@ -112,8 +112,10 @@ public function testGenerateHrefs($route_name, array $parameters, $absolute, $ex $this->urlGenerator->expects($this->once()) ->method('generateFromRoute') ->with($route_name, $parameters, array('absolute' => $absolute) + $this->defaultOptions) - ->will($this->returnValue($expected_url)); - + ->willReturnCallback(function() use ($expected_url) { + $url = new GeneratedUrl(); + return $url->setGeneratedUrl($expected_url); + }); $this->moduleHandler->expects($this->once()) ->method('alter'); @@ -134,7 +136,10 @@ public function testGenerate() { $this->urlGenerator->expects($this->once()) ->method('generateFromRoute') ->with('test_route_1', array(), array('fragment' => 'the-fragment') + $this->defaultOptions) - ->will($this->returnValue('/test-route-1#the-fragment')); + ->willReturnCallback(function() { + $url = new GeneratedUrl(); + return $url->setGeneratedUrl('/test-route-1#the-fragment'); + }); $this->moduleHandler->expects($this->once()) ->method('alter') @@ -198,7 +203,7 @@ public function testGenerateUrlWithQuotes() { $this->urlAssembler->expects($this->once()) ->method('assemble') ->with('base:example', array('query' => array('foo' => '"bar"', 'zoo' => 'baz')) + $this->defaultOptions) - ->will($this->returnValue('/example?foo=%22bar%22&zoo=baz')); + ->willReturn((new GeneratedUrl())->setGeneratedUrl('/example?foo=%22bar%22&zoo=baz')); $path_validator = $this->getMock('Drupal\Core\Path\PathValidatorInterface'); $container_builder = new ContainerBuilder(); @@ -229,9 +234,7 @@ public function testGenerateAttributes() { $this->urlGenerator->expects($this->once()) ->method('generateFromRoute') ->with('test_route_1', array(), $this->defaultOptions) - ->will($this->returnValue( - '/test-route-1' - )); + ->willReturn((new GeneratedUrl())->setGeneratedUrl('/test-route-1')); // Test that HTML attributes are added to the anchor. $url = new Url('test_route_1', array(), array( @@ -256,9 +259,10 @@ public function testGenerateQuery() { $this->urlGenerator->expects($this->once()) ->method('generateFromRoute') ->with('test_route_1', array(), array('query' => array('test' => 'value')) + $this->defaultOptions) - ->will($this->returnValue( - '/test-route-1?test=value' - )); + ->willReturnCallback(function() { + $url = new GeneratedUrl(); + return $url->setGeneratedUrl('/test-route-1?test=value'); + }); $url = new Url('test_route_1', array(), array( 'query' => array('test' => 'value'), @@ -281,9 +285,10 @@ public function testGenerateParametersAsQuery() { $this->urlGenerator->expects($this->once()) ->method('generateFromRoute') ->with('test_route_1', array('test' => 'value'), $this->defaultOptions) - ->will($this->returnValue( - '/test-route-1?test=value' - )); + ->willReturnCallback(function() { + $url = new GeneratedUrl(); + return $url->setGeneratedUrl('/test-route-1?test=value'); + }); $url = new Url('test_route_1', array('test' => 'value'), array()); $url->setUrlGenerator($this->urlGenerator); @@ -304,10 +309,10 @@ public function testGenerateOptions() { $this->urlGenerator->expects($this->once()) ->method('generateFromRoute') ->with('test_route_1', array(), array('key' => 'value') + $this->defaultOptions) - ->will($this->returnValue( - '/test-route-1?test=value' - )); - + ->willReturnCallback(function() { + $url = new GeneratedUrl(); + return $url->setGeneratedUrl('/test-route-1?test=value'); + }); $url = new Url('test_route_1', array(), array( 'key' => 'value', )); @@ -329,9 +334,10 @@ public function testGenerateXss() { $this->urlGenerator->expects($this->once()) ->method('generateFromRoute') ->with('test_route_4', array(), $this->defaultOptions) - ->will($this->returnValue( - '/test-route-4' - )); + ->willReturnCallback(function() { + $url = new GeneratedUrl(); + return $url->setGeneratedUrl('/test-route-4'); + }); // Test that HTML link text is escaped by default. $url = new Url('test_route_4'); @@ -349,15 +355,17 @@ public function testGenerateWithHtml() { $this->urlGenerator->expects($this->at(0)) ->method('generateFromRoute') ->with('test_route_5', array(), $this->defaultOptions) - ->will($this->returnValue( - '/test-route-5' - )); + ->willReturnCallback(function() { + $url = new GeneratedUrl(); + return $url->setGeneratedUrl('/test-route-5'); + }); $this->urlGenerator->expects($this->at(1)) ->method('generateFromRoute') ->with('test_route_5', array(), $this->defaultOptions) - ->will($this->returnValue( - '/test-route-5' - )); + ->willReturnCallback(function() { + $url = new GeneratedUrl(); + return $url->setGeneratedUrl('/test-route-5'); + }); // Test that HTML tags are stripped from the 'title' attribute. $url = new Url('test_route_5', array(), array( @@ -395,11 +403,18 @@ public function testGenerateWithHtml() { public function testGenerateActive() { $this->urlGenerator->expects($this->exactly(5)) ->method('generateFromRoute') - ->will($this->returnValueMap(array( - array('test_route_1', array(), FALSE, '/test-route-1'), - array('test_route_3', array(), FALSE, '/test-route-3'), - array('test_route_4', array('object' => '1'), FALSE, '/test-route-4/1'), - ))); + ->willReturnCallback(function($name, $parameters = array(), $options = array(), $collect_bubbleable_metadata = FALSE) { + switch ($name) { + case 'test_route_1': + return (new GeneratedUrl())->setGeneratedUrl('/test-route-1'); + case 'test_route_3': + return (new GeneratedUrl())->setGeneratedUrl('/test-route-3'); + case 'test_route_4': + if ($parameters['object'] == '1') { + return (new GeneratedUrl())->setGeneratedUrl('/test-route-4/1'); + } + } + }); $this->urlGenerator->expects($this->exactly(4)) ->method('getPathFromRoute') @@ -480,7 +495,6 @@ public function testGenerateBubbleableMetadata() { $this->urlGenerator->expects($this->any()) ->method('generateFromRoute') ->will($this->returnValueMap([ - ['test_route_1', [], $options, FALSE, '/test-route-1'], ['test_route_1', [], $options, TRUE, (new GeneratedUrl())->setGeneratedUrl('/test-route-1')], ])); @@ -490,14 +504,14 @@ public function testGenerateBubbleableMetadata() { // Test ::generate(). $this->assertSame($expected_link_markup, (string) $this->linkGenerator->generate('Test', $url)); - $generated_link = $this->linkGenerator->generate('Test', $url, TRUE); + $generated_link = $this->linkGenerator->generate('Test', $url); $this->assertSame($expected_link_markup, (string) $generated_link->getGeneratedLink()); $this->assertInstanceOf('\Drupal\Core\Render\BubbleableMetadata', $generated_link); // Test ::generateFromLink(). $link = new Link('Test', $url); $this->assertSame($expected_link_markup, (string) $this->linkGenerator->generateFromLink($link)); - $generated_link = $this->linkGenerator->generateFromLink($link, TRUE); + $generated_link = $this->linkGenerator->generateFromLink($link); $this->assertSame($expected_link_markup, (string) $generated_link->getGeneratedLink()); $this->assertInstanceOf('\Drupal\Core\Render\BubbleableMetadata', $generated_link); }