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!';
+ }
+ }
+}