diff --git a/core/lib/Drupal.php b/core/lib/Drupal.php index da3e02a..eac2c40 100644 --- a/core/lib/Drupal.php +++ b/core/lib/Drupal.php @@ -546,10 +546,10 @@ public static function linkGenerator() { * (optional) Defaults to FALSE. When TRUE, both the generated URL and its * associated bubbleable metadata are returned. * - * @return string|\Drupal\Core\GeneratedLink + * @return \Drupal\Core\GeneratedLink * An HTML string containing a link to the given route and parameters. - * When $collect_bubbleable_metadata is TRUE, a GeneratedLink object is - * returned, containing the generated link plus bubbleable metadata. + * When $collect_bubbleable_metadata is TRUE, the GeneratedLink object has + * bubbleable metadata. * * @see \Drupal\Core\Utility\LinkGeneratorInterface::generate() * @see \Drupal\Core\Url diff --git a/core/lib/Drupal/Core/Menu/menu.api.php b/core/lib/Drupal/Core/Menu/menu.api.php index 0ae528f..b6ad987 100644 --- a/core/lib/Drupal/Core/Menu/menu.api.php +++ b/core/lib/Drupal/Core/Menu/menu.api.php @@ -440,7 +440,11 @@ function hook_system_breadcrumb_alter(\Drupal\Core\Breadcrumb\Breadcrumb &$bread * exposed as the 'link_generator' service or a link generated by _l(). If the * link is a "route link", 'route_name' will be set, otherwise 'path' will be * set. The following keys can be altered: - * - text: The link text for the anchor tag as a translated string. + * - text: The link text for the anchor tag. If the hook implementation + * changes this text is needs to preserve the safeness of the original text. + * Using t() or \Drupal\Component\Utility\SafeMarkup::format() with + * @placeholder is recommended as this will escape the original text if + * necessary. If the resulting text is not marked safe it will be escaped. * - url_is_active: Whether or not the link points to the currently active * URL. * - url: The \Drupal\Core\Url object. @@ -468,7 +472,7 @@ function hook_system_breadcrumb_alter(\Drupal\Core\Breadcrumb\Breadcrumb &$bread function hook_link_alter(&$variables) { // Add a warning to the end of route links to the admin section. if (isset($variables['route_name']) && strpos($variables['route_name'], 'admin') !== FALSE) { - $variables['text'] .= ' (Warning!)'; + $variables['text'] = t('@text (Warning!)', ['@text' => $variables['text']]); } } diff --git a/core/lib/Drupal/Core/Utility/LinkGenerator.php b/core/lib/Drupal/Core/Utility/LinkGenerator.php index 21f7617..c920e28 100644 --- a/core/lib/Drupal/Core/Utility/LinkGenerator.php +++ b/core/lib/Drupal/Core/Utility/LinkGenerator.php @@ -90,9 +90,7 @@ public function generate($text, Url $url, $collect_bubbleable_metadata = FALSE) if (is_array($text)) { $text = $this->renderer->render($text); } - elseif (!SafeMarkup::isSafe($text)) { - $text = Html::escape($text); - } + // Start building a structured representation of our link to be altered later. $variables = array( 'text' => $text, @@ -170,12 +168,13 @@ public function generate($text, Url $url, $collect_bubbleable_metadata = FALSE) // 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']); + } $attributes = new Attribute($attributes); // This is safe because Attribute does escaping and $variables['text'] is // either rendered or escaped. - $result = SafeString::create('' . $variables['text'] . ''); - - return $generated_link->setGeneratedLink($result); + return $generated_link->setGeneratedLink('' . $variables['text'] . ''); } } diff --git a/core/lib/Drupal/Core/Utility/LinkGeneratorInterface.php b/core/lib/Drupal/Core/Utility/LinkGeneratorInterface.php index 28b80f2..00d2566 100644 --- a/core/lib/Drupal/Core/Utility/LinkGeneratorInterface.php +++ b/core/lib/Drupal/Core/Utility/LinkGeneratorInterface.php @@ -66,8 +66,8 @@ * * @return \Drupal\Core\GeneratedLink * An HTML string containing a link to the given route and parameters. - * When $collect_bubbleable_metadata is TRUE, a GeneratedLink object is - * returned, containing the generated link plus bubbleable metadata. + * When $collect_bubbleable_metadata is TRUE, the GeneratedLink object has + * bubbleable metadata. * * @throws \Symfony\Component\Routing\Exception\RouteNotFoundException * Thrown when the named route doesn't exist. @@ -88,10 +88,10 @@ public function generate($text, Url $url, $collect_bubbleable_metadata = FALSE); * (optional) Defaults to FALSE. When TRUE, both the generated link and its * associated bubbleable metadata are returned. * - * @return string|\Drupal\Core\GeneratedLink + * @return \Drupal\Core\GeneratedLink * An HTML string containing a link to the given route and parameters. - * When $collect_bubbleable_metadata is TRUE, a GeneratedLink object is - * returned, containing the generated link plus bubbleable metadata. + * When $collect_bubbleable_metadata is TRUE, the GeneratedLink object has + * bubbleable metadata. */ public function generateFromLink(Link $link, $collect_bubbleable_metadata = FALSE); diff --git a/core/modules/system/src/Tests/Utility/LinkGenerationTest.php b/core/modules/system/src/Tests/Utility/LinkGenerationTest.php new file mode 100644 index 0000000..62c4d23 --- /dev/null +++ b/core/modules/system/src/Tests/Utility/LinkGenerationTest.php @@ -0,0 +1,70 @@ +executeInRenderContext(new RenderContext(), function () use ($url) { + return \Drupal::l(['#markup' => 'link with markup'], $url); + }); + $this->setRawContent($link); + $this->assertTrue(SafeMarkup::isSafe($link), 'The output of link generation is marked safe as it is a link.'); + // Ensure the content of the link is not escaped. + $this->assertRaw('link with markup'); + + // Test just adding text to an already safe string. + \Drupal::state()->set('link_generation_test_link_alter', TRUE); + $link = $renderer->executeInRenderContext(new RenderContext(), function () use ($url) { + return \Drupal::l(['#markup' => 'link with markup'], $url); + }); + $this->setRawContent($link); + $this->assertTrue(SafeMarkup::isSafe($link), 'The output of link generation is marked safe as it is a link.'); + // Ensure the content of the link is escaped. + $this->assertEscaped('link with markup Test!'); + + // Test passing a safe string to t(). + \Drupal::state()->set('link_generation_test_link_alter_safe', TRUE); + $link = $renderer->executeInRenderContext(new RenderContext(), function () use ($url) { + return \Drupal::l(['#markup' => 'link with markup'], $url); + }); + $this->setRawContent($link); + $this->assertTrue(SafeMarkup::isSafe($link), 'The output of link generation is marked safe as it is a link.'); + // Ensure the content of the link is escaped. + $this->assertRaw('link with markup Test!'); + + // Test passing an unsafe string to t(). + $link = $renderer->executeInRenderContext(new RenderContext(), function () use ($url) { + return \Drupal::l('link with markup', $url); + }); + $this->setRawContent($link); + $this->assertTrue(SafeMarkup::isSafe($link), 'The output of link generation is marked safe as it is a link.'); + // Ensure the content of the link is escaped. + $this->assertEscaped('link with markup'); + $this->assertRaw('Test!'); + } + +} diff --git a/core/modules/system/tests/modules/link_generation_test/link_generation_test.info.yml b/core/modules/system/tests/modules/link_generation_test/link_generation_test.info.yml new file mode 100644 index 0000000..2f200a4 --- /dev/null +++ b/core/modules/system/tests/modules/link_generation_test/link_generation_test.info.yml @@ -0,0 +1,6 @@ +name: 'Link generation test support' +type: module +description: 'Test hooks fired in link generation.' +package: Testing +version: VERSION +core: 8.x diff --git a/core/modules/system/tests/modules/link_generation_test/link_generation_test.module b/core/modules/system/tests/modules/link_generation_test/link_generation_test.module new file mode 100644 index 0000000..622a403 --- /dev/null +++ b/core/modules/system/tests/modules/link_generation_test/link_generation_test.module @@ -0,0 +1,21 @@ +get('link_generation_test_link_alter', FALSE)) { + // Add a text to the end of links. + if (\Drupal::state()->get('link_generation_test_link_alter_safe', FALSE)) { + $variables['text'] = t('@text Test!', ['@text' => $variables['text']]); + } + else { + $variables['text'] .= ' Test!'; + } + } +}