From ded07e9aa86b6a77b88801500e76295fd972188f Mon Sep 17 00:00:00 2001
From: Nathaniel Catchpole <catch@35733.no-reply.drupal.org>
Date: Fri, 30 Aug 2013 16:28:49 +0100
Subject: [PATCH] Issue #2047619 by dawehner, pwolanin, tstoeckler,
 thedavidmeister: Add a link generator service for route-based links.

---
 core/core.services.yml                        |   5 +
 core/includes/common.inc                      |  22 +-
 core/lib/Drupal.php                           |   9 +
 .../Drupal/Core/Controller/ControllerBase.php |  54 +++
 core/lib/Drupal/Core/Routing/UrlGenerator.php |   2 +-
 .../lib/Drupal/Core/Utility/LinkGenerator.php | 152 +++++++
 .../Core/Utility/LinkGeneratorInterface.php   |  66 +++
 .../dblog/Controller/DbLogController.php      |   5 +-
 core/modules/system/system.api.php            |  50 +++
 .../views_ui/Controller/ViewsUIController.php |  24 +-
 .../Tests/Core/Utility/LinkGeneratorTest.php  | 407 ++++++++++++++++++
 11 files changed, 783 insertions(+), 13 deletions(-)
 create mode 100644 core/lib/Drupal/Core/Utility/LinkGenerator.php
 create mode 100644 core/lib/Drupal/Core/Utility/LinkGeneratorInterface.php
 create mode 100644 core/tests/Drupal/Tests/Core/Utility/LinkGeneratorTest.php

diff --git a/core/core.services.yml b/core/core.services.yml
index 8a24ee77ae9e..f941e333fabd 100644
--- a/core/core.services.yml
+++ b/core/core.services.yml
@@ -242,6 +242,11 @@ services:
       - [setContext, ['@?router.request_context']]
     tags:
       - { name: persist }
+  link_generator:
+    class: Drupal\Core\Utility\LinkGenerator
+    arguments: ['@url_generator', '@module_handler', '@language_manager']
+    calls:
+      - [setRequest, ['@?request']]
   router.dynamic:
     class: Symfony\Cmf\Component\Routing\DynamicRouter
     arguments: ['@router.request_context', '@router.matcher', '@url_generator']
diff --git a/core/includes/common.inc b/core/includes/common.inc
index c6f6688dff47..d6886f2fd3d9 100644
--- a/core/includes/common.inc
+++ b/core/includes/common.inc
@@ -1266,6 +1266,12 @@ function drupal_http_header_attributes(array $attributes = array()) {
  * This keeps the context of the link title ('settings' in the example) for
  * translators.
  *
+ * This function does not support generating links from internal routes. For
+ * that use \Drupal\Core\Utility\LinkGenerator::generate(), which is exposed via
+ * the 'link_generator' service. It requires an internal route name and does not
+ * support external URLs. Using Drupal 7 style system paths should be avoided if
+ * possible but l() should still be used when rendering links to external URLs.
+ *
  * @param string|array $text
  *   The link text for the anchor tag as a translated string or render array.
  * @param string $path
@@ -3471,8 +3477,12 @@ function drupal_pre_render_html_tag($element) {
  * @param $elements
  *   A structured array whose keys form the arguments to l():
  *   - #title: The link text to pass as argument to l().
- *   - #href: The URL path component to pass as argument to l().
- *   - #options: (optional) An array of options to pass to l().
+ *   - One of the following
+ *     - #route_name and (optionally) and a #route_parameters array; The route
+ *       name and route parameters which will be passed into the link generator.
+ *     - #href: The system path or URL to pass as argument to l().
+ *   - #options: (optional) An array of options to pass to l() or the link
+ *     generator.
  *
  * @return
  *   The passed-in elements containing a rendered link in '#markup'.
@@ -3513,7 +3523,13 @@ function drupal_pre_render_link($element) {
     $element = ajax_pre_render_element($element);
   }
 
-  $element['#markup'] = l($element['#title'], $element['#href'], $element['#options']);
+  if (isset($element['#route_name'])) {
+    $element['#route_parameters'] = empty($element['#route_parameters']) ? array() : $element['#route_parameters'];
+    $element['#markup'] = Drupal::linkGenerator()->generate($element['#title'], $element['#route_name'], $element['#route_parameters'], $element['#options']);
+  }
+  else {
+    $element['#markup'] = l($element['#title'], $element['#href'], $element['#options']);
+  }
   return $element;
 }
 
diff --git a/core/lib/Drupal.php b/core/lib/Drupal.php
index 0d9ed964aeae..04fac027ca61 100644
--- a/core/lib/Drupal.php
+++ b/core/lib/Drupal.php
@@ -380,6 +380,15 @@ public static function urlGenerator() {
     return static::$container->get('url_generator');
   }
 
+  /**
+   * Returns the link generator service.
+   *
+   * @return \Drupal\Core\Utility\LinkGeneratorInterface
+   */
+  public static function linkGenerator() {
+    return static::$container->get('link_generator');
+  }
+
   /**
    * Returns the string translation service.
    *
diff --git a/core/lib/Drupal/Core/Controller/ControllerBase.php b/core/lib/Drupal/Core/Controller/ControllerBase.php
index d0718ff33667..7209c40d5e31 100644
--- a/core/lib/Drupal/Core/Controller/ControllerBase.php
+++ b/core/lib/Drupal/Core/Controller/ControllerBase.php
@@ -121,6 +121,60 @@ protected function urlGenerator() {
     return $this->container->get('url_generator');
   }
 
+  /**
+   * Renders a link to a route given a route name and its parameters.
+   *
+   * This function correctly handles aliased paths and sanitizing text, so all
+   * internal links output by modules should be generated by this function if
+   * possible.
+   *
+   * However, for links enclosed in translatable text you should use t() and
+   * embed the HTML anchor tag directly in the translated string. For example:
+   * @code
+   * t('Visit the <a href="@url">content types</a> page', array('@url' => Drupal::urlGenerator()->generate('node_overview_types')));
+   * @endcode
+   * This keeps the context of the link title ('settings' in the example) for
+   * translators.
+   *
+   * @param string|array $text
+   *   The link text for the anchor tag as a translated string or render array.
+   * @param string $route_name
+   *   The name of the route to use to generate the link.
+   * @param array $parameters
+   *   (optional) Any parameters needed to render the route path pattern.
+   * @param array $options
+   *   (optional) An associative array of additional options. Defaults to an
+   *   empty array. It may contain the following elements:
+   *   - 'query': An array of query key/value-pairs (without any URL-encoding) to
+   *     append to the URL.
+   *   - absolute: Whether to force the output to be an absolute link (beginning
+   *     with http:). Useful for links that will be displayed outside the site,
+   *     such as in an RSS feed. Defaults to FALSE.
+   *   - attributes: An associative array of HTML attributes to apply to the
+   *     anchor tag. If element 'class' is included, it must be an array; 'title'
+   *     must be a string; other elements are more flexible, as they just need
+   *     to work as an argument for the constructor of the class
+   *     Drupal\Core\Template\Attribute($options['attributes']).
+   *   - html: Whether $text is HTML or just plain-text. For
+   *     example, to make an image tag into a link, this must be set to TRUE, or
+   *     you will see the escaped HTML image tag. $text is not sanitized if
+   *     'html' is TRUE. The calling function must ensure that $text is already
+   *     safe. Defaults to FALSE.
+   *   - language: An optional language object. If the path being linked to is
+   *     internal to the site, $options['language'] is used to determine whether
+   *     the link is "active", or pointing to the current page (the language as
+   *     well as the path must match).
+   *
+   * @return string
+   *   An HTML string containing a link to the given route and parameters.
+   *
+   * @see \Drupal\Core\Routing\UrlGenerator::generateFromRoute()
+   * @see \Drupal\Core\Utility\LinkGenerator::generate()
+   */
+  public function l($text, $route_name, array $parameters = array(), array $options = array()) {
+    return $this->container->get('link_generator')->generate($text, $route_name, $parameters, $options);
+  }
+
   /**
    * Returns the current user.
    *
diff --git a/core/lib/Drupal/Core/Routing/UrlGenerator.php b/core/lib/Drupal/Core/Routing/UrlGenerator.php
index ac5d5c8beccf..53ac95dbb38a 100644
--- a/core/lib/Drupal/Core/Routing/UrlGenerator.php
+++ b/core/lib/Drupal/Core/Routing/UrlGenerator.php
@@ -21,7 +21,7 @@
 use Drupal\Core\PathProcessor\OutboundPathProcessorInterface;
 
 /**
- * A Generator creates URL strings based on a specified route.
+ * Defines an interface which generates a link with route names and parameters.
  */
 class UrlGenerator extends ProviderBasedGenerator implements UrlGeneratorInterface {
 
diff --git a/core/lib/Drupal/Core/Utility/LinkGenerator.php b/core/lib/Drupal/Core/Utility/LinkGenerator.php
new file mode 100644
index 000000000000..c2a249c8e6ed
--- /dev/null
+++ b/core/lib/Drupal/Core/Utility/LinkGenerator.php
@@ -0,0 +1,152 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Utility\LinkGenerator.
+ */
+
+namespace Drupal\Core\Utility;
+
+use Drupal\Component\Utility\String;
+use Drupal\Core\Extension\ModuleHandlerInterface;
+use Drupal\Core\Language\Language;
+use Drupal\Core\Language\LanguageManager;
+use Drupal\Core\Template\Attribute;
+use Drupal\Core\Routing\UrlGeneratorInterface;
+use Symfony\Cmf\Component\Routing\RouteObjectInterface;
+use Symfony\Component\HttpFoundation\Request;
+
+/**
+ * Provides a class which generates a link with route names and parameters.
+ */
+class LinkGenerator implements LinkGeneratorInterface {
+
+  /**
+   * Stores some information about the current request, like the language.
+   *
+   * @var array
+   */
+  protected $active;
+
+  /**
+   * The url generator.
+   *
+   * @var \Drupal\Core\Routing\UrlGeneratorInterface
+   */
+  protected $urlGenerator;
+
+  /**
+   * The module handler firing the route_link alter hook.
+   *
+   * @var \Drupal\Core\Extension\ModuleHandlerInterface
+   */
+  protected $moduleHandler;
+
+  /**
+   * The language manager.
+   *
+   * @var \Drupal\Core\Language\LanguageManager
+   */
+  protected $languageManager;
+
+  /**
+   * Constructs a LinkGenerator instance.
+   *
+   * @param \Drupal\Core\Routing\UrlGeneratorInterface $url_generator
+   *   The url generator.
+   * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
+   *   The module handler.
+   * @param \Drupal\Core\Language\LanguageManager $language_manager
+   *   The language manager.
+   */
+  public function __construct(UrlGeneratorInterface $url_generator, ModuleHandlerInterface $module_handler, LanguageManager $language_manager) {
+    $this->urlGenerator = $url_generator;
+    $this->moduleHandler = $module_handler;
+    $this->languageManager = $language_manager;
+  }
+
+  /**
+   * Sets the $request property.
+   *
+   * @param \Symfony\Component\HttpFoundation\Request $request
+   *   The HttpRequest object representing the current request.
+   */
+  public function setRequest(Request $request) {
+    // Pre-calculate and store values based on the request that may be used
+    // repeatedly in generate().
+    $this->active = array(
+      'route_name' => $request->attributes->get(RouteObjectInterface::ROUTE_NAME),
+      'language' => $this->languageManager->getLanguage(Language::TYPE_URL)->id,
+      'parameters' => (array) $request->attributes->get('_raw_variables') + (array) $request->query->all(),
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function generate($text, $route_name, array $parameters = array(), array $options = array()) {
+    // Start building a structured representation of our link to be altered later.
+    $variables = array(
+      // @todo Inject the service when drupal_render() is converted to one.
+      'text' => is_array($text) ? drupal_render($text) : $text,
+      'route_name' => $route_name,
+      'parameters' => $parameters,
+      'options' => $options,
+    );
+
+    // Merge in default options.
+    $variables['options'] += array(
+      'attributes' => array(),
+      'query' => array(),
+      'html' => FALSE,
+      'language' => NULL,
+    );
+    // Add a hreflang attribute if we know the language of this link's url and
+    // hreflang has not already been set.
+    if (!empty($variables['options']['language']) && !isset($variables['options']['attributes']['hreflang'])) {
+      $variables['options']['attributes']['hreflang'] = $variables['options']['language']->id;
+    }
+
+    // This is only needed for the active class. The generator also combines
+    // the parameters and $options['query'] and adds parameters that are not
+    // path slugs as query strings.
+    $full_parameters = $parameters + (array) $variables['options']['query'];
+
+    // Determine whether this link is "active", meaning that it has the same
+    // URL path and query string as the current page. Note that this may be
+    // removed from l() in https://drupal.org/node/1979468 and would be removed
+    // or altered here also.
+    $variables['url_is_active'] = $route_name == $this->active['route_name']
+      // The language of an active link is equal to the current language.
+      && (empty($variables['options']['language']) || $variables['options']['language']->id == $this->active['language'])
+      && $full_parameters == $this->active['parameters'];
+
+    // Add the "active" class if appropriate.
+    if ($variables['url_is_active']) {
+      $variables['options']['attributes']['class'][] = 'active';
+    }
+
+    // Remove all HTML and PHP tags from a tooltip, calling expensive strip_tags()
+    // only when a quick strpos() gives suspicion tags are present.
+    if (isset($variables['options']['attributes']['title']) && strpos($variables['options']['attributes']['title'], '<') !== FALSE) {
+      $variables['options']['attributes']['title'] = strip_tags($variables['options']['attributes']['title']);
+    }
+
+    // Allow other modules to modify the structure of the link.
+    $this->moduleHandler->alter('link', $variables);
+
+    // Move attributes out of options. generateFromRoute(() doesn't need them.
+    $attributes = new Attribute($variables['options']['attributes']);
+    unset($variables['options']['attributes']);
+
+    // The result of the url generator is a plain-text URL. Because we are using
+    // it here in an HTML argument context, we need to encode it properly.
+    $url = String::checkPlain($this->urlGenerator->generateFromRoute($variables['route_name'], $variables['parameters'], $variables['options']));
+
+    // Sanitize the link text if necessary.
+    $text = $variables['options']['html'] ? $variables['text'] : String::checkPlain($variables['text']);
+
+    return '<a href="' . $url . '"' . $attributes . '>' . $text . '</a>';
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Utility/LinkGeneratorInterface.php b/core/lib/Drupal/Core/Utility/LinkGeneratorInterface.php
new file mode 100644
index 000000000000..2157a5fbd2a7
--- /dev/null
+++ b/core/lib/Drupal/Core/Utility/LinkGeneratorInterface.php
@@ -0,0 +1,66 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Utility\LinkGeneratorInterface.
+ */
+
+namespace Drupal\Core\Utility;
+
+/**
+ * Defines an interface for a service which generates a link out of a
+ */
+interface LinkGeneratorInterface {
+
+  /**
+   * Renders a link to a route given a route name and its parameters.
+   *
+   * This function correctly handles aliased paths and sanitizing text, so all
+   * internal links output by modules should be generated by this function if
+   * possible.
+   *
+   * However, for links enclosed in translatable text you should use t() and
+   * embed the HTML anchor tag directly in the translated string. For example:
+   * @code
+   * t('Visit the <a href="@url">content types</a> page', array('@url' => Drupal::urlGenerator()->generate('node_overview_types')));
+   * @endcode
+   * This keeps the context of the link title ('settings' in the example) for
+   * translators.
+   *
+   * @param string|array $text
+   *   The link text for the anchor tag as a translated string or render array.
+   * @param string $route_name
+   *   The name of the route to use to generate the link.
+   * @param array $parameters
+   *   (optional) Any parameters needed to render the route path pattern.
+   * @param array $options
+   *   (optional) An associative array of additional options. Defaults to an
+   *   empty array. It may contain the following elements:
+   *   - 'query': An array of query key/value-pairs (without any URL-encoding) to
+   *     append to the URL.
+   *   - absolute: Whether to force the output to be an absolute link (beginning
+   *     with http:). Useful for links that will be displayed outside the site,
+   *     such as in an RSS feed. Defaults to FALSE.
+   *   - attributes: An associative array of HTML attributes to apply to the
+   *     anchor tag. If element 'class' is included, it must be an array; 'title'
+   *     must be a string; other elements are more flexible, as they just need
+   *     to work as an argument for the constructor of the class
+   *     Drupal\Core\Template\Attribute($options['attributes']).
+   *   - html: Whether $text is HTML or just plain-text. For
+   *     example, to make an image tag into a link, this must be set to TRUE, or
+   *     you will see the escaped HTML image tag. $text is not sanitized if
+   *     'html' is TRUE. The calling function must ensure that $text is already
+   *     safe. Defaults to FALSE.
+   *   - language: An optional language object. If the path being linked to is
+   *     internal to the site, $options['language'] is used to determine whether
+   *     the link is "active", or pointing to the current page (the language as
+   *     well as the path must match).
+   *
+   * @return string
+   *   An HTML string containing a link to the given route and parameters.
+   *
+   * @see \Drupal\Core\Routing\UrlGenerator::generateFromRoute()
+   */
+  public function generate($text, $route_name, array $parameters = array(), array $options = array());
+
+}
diff --git a/core/modules/dblog/lib/Drupal/dblog/Controller/DbLogController.php b/core/modules/dblog/lib/Drupal/dblog/Controller/DbLogController.php
index a0103635c868..5123f04f49c7 100644
--- a/core/modules/dblog/lib/Drupal/dblog/Controller/DbLogController.php
+++ b/core/modules/dblog/lib/Drupal/dblog/Controller/DbLogController.php
@@ -10,6 +10,7 @@
 use Drupal\Component\Utility\Unicode;
 use Drupal\Component\Utility\String;
 use Drupal\Component\Utility\Xss;
+use Drupal\Core\Controller\ControllerBase;
 use Drupal\Core\Database\Connection;
 use Drupal\Core\Controller\ControllerInterface;
 use Drupal\Core\Datetime\Date;
@@ -20,7 +21,7 @@
 /**
  * Returns responses for dblog routes.
  */
-class DbLogController implements ControllerInterface {
+class DbLogController extends ControllerBase implements ControllerInterface {
 
   /**
    * The database service.
@@ -176,7 +177,7 @@ public function overview() {
         if (isset($dblog->wid)) {
           // Truncate link_text to 56 chars of message.
           $log_text = Unicode::truncate(filter_xss($message, array()), 56, TRUE, TRUE);
-          $message = l($log_text, 'admin/reports/event/' . $dblog->wid, array('html' => TRUE));
+          $message = $this->l($log_text, 'dblog_event',  array('event_id' => $dblog->wid), array('html' => TRUE));
         }
       }
       $username = array(
diff --git a/core/modules/system/system.api.php b/core/modules/system/system.api.php
index 6339d1c17cde..49e87a08db55 100644
--- a/core/modules/system/system.api.php
+++ b/core/modules/system/system.api.php
@@ -3489,6 +3489,56 @@ function hook_filetransfer_info_alter(&$filetransfer_info) {
   }
 }
 
+/**
+ * Alter the parameters for links.
+ *
+ * @param array $variables
+ *   An associative array of variables defining a link. The link may be either a
+ *   "route link" using \Drupal\Core\Utility\LinkGenerator::link(), which is
+ *   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.
+ *   - url_is_active: Whether or not the link points to the currently active
+ *     URL.
+ *   - path: If this link is being generated by l(), this system path, relative
+ *     path, or external URL will be passed to url() to generate the href
+ *     attribute for this link.
+ *   - route_name: The name of the route to use to generate the link, if
+ *     this is a "route link".
+ *   - parameters: Any parameters needed to render the route path pattern, if
+ *     this is a "route link".
+ *   - options: An associative array of additional options that will be passed
+ *     to either \Drupal\Core\Routing\UrlGenerator::generateFromPath() or
+ *     \Drupal\Core\Routing\UrlGenerator::generateFromRoute() to generate the
+ *     href attribute for this link, and also used when generating the link.
+ *     Defaults to an empty array. It may contain the following elements:
+ *     - 'query': An array of query key/value-pairs (without any URL-encoding) to
+ *       append to the URL.
+ *     - absolute: Whether to force the output to be an absolute link (beginning
+ *       with http:). Useful for links that will be displayed outside the site,
+ *       such as in an RSS feed. Defaults to FALSE.
+ *     - language: An optional language object. May affect the rendering of
+ *       the anchor tag, such as by adding a language prefix to the path.
+ *     - attributes: An associative array of HTML attributes to apply to the
+ *       anchor tag. If element 'class' is included, it must be an array; 'title'
+ *       must be a string; other elements are more flexible, as they just need
+ *       to work as an argument for the constructor of the class
+ *       Drupal\Core\Template\Attribute($options['attributes']).
+ *     - html: Whether or not HTML should be allowed as the link text. If FALSE,
+ *       the text will be run through
+ *       \Drupal\Component\Utility\String::checkPlain() before being output.
+ *
+ * @see \Drupal\Core\Routing\UrlGenerator::generateFromPath()
+ * @see \Drupal\Core\Routing\UrlGenerator::generateFromRoute()
+ */
+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!)';
+  }
+}
+
 /**
  * @} End of "addtogroup hooks".
  */
diff --git a/core/modules/views_ui/lib/Drupal/views_ui/Controller/ViewsUIController.php b/core/modules/views_ui/lib/Drupal/views_ui/Controller/ViewsUIController.php
index a78c75741fdd..987148adb265 100644
--- a/core/modules/views_ui/lib/Drupal/views_ui/Controller/ViewsUIController.php
+++ b/core/modules/views_ui/lib/Drupal/views_ui/Controller/ViewsUIController.php
@@ -20,7 +20,8 @@
 use Drupal\Core\Ajax\AjaxResponse;
 use Drupal\Core\Ajax\ReplaceCommand;
 use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
-use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
+use Drupal\Core\Routing\UrlGeneratorInterface;
+use Drupal\Core\Utility\LinkGeneratorInterface;
 
 /**
  * Returns responses for Views UI routes.
@@ -44,10 +45,17 @@ class ViewsUIController implements ControllerInterface {
   /**
    * The URL generator to use.
    *
-   * @var \Symfony\Component\Routing\Generator\UrlGeneratorInterface
+   * @var \Drupal\Core\Routing\UrlGeneratorInterface
    */
   protected $urlGenerator;
 
+  /**
+   * The link generator to use.
+   *
+   * @var \Drupal\Core\Utility\LinkGeneratorInterface
+   */
+  protected $linkGenerator;
+
   /**
    * Constructs a new \Drupal\views_ui\Controller\ViewsUIController object.
    *
@@ -55,13 +63,14 @@ class ViewsUIController implements ControllerInterface {
    *   The Entity manager.
    * @param \Drupal\views\ViewsData views_data
    *   The Views data cache object.
-   * @param \Symfony\Component\Routing\Generator\UrlGeneratorInterface
+   * @param \Drupal\Core\Routing\UrlGeneratorInterface
    *   The URL generator.
    */
-  public function __construct(EntityManager $entity_manager, ViewsData $views_data, UrlGeneratorInterface $url_generator) {
+  public function __construct(EntityManager $entity_manager, ViewsData $views_data, UrlGeneratorInterface $url_generator, LinkGeneratorInterface $link_generator) {
     $this->entityManager = $entity_manager;
     $this->viewsData = $views_data;
     $this->urlGenerator = $url_generator;
+    $this->linkGenerator = $link_generator;
   }
 
   /**
@@ -71,7 +80,8 @@ public static function create(ContainerInterface $container) {
     return new static(
       $container->get('plugin.manager.entity'),
       $container->get('views.views_data'),
-      $container->get('url_generator')
+      $container->get('url_generator'),
+      $container->get('link_generator')
     );
   }
 
@@ -114,7 +124,7 @@ public function reportFields() {
     foreach ($fields as $field_name => $views) {
       $rows[$field_name]['data'][0] = check_plain($field_name);
       foreach ($views as $view) {
-        $rows[$field_name]['data'][1][] = l($view, "admin/structure/views/view/$view");
+        $rows[$field_name]['data'][1][] = $this->linkGenerator->generate($view, 'views_ui.edit', array('view' => $view));
       }
       $rows[$field_name]['data'][1] = implode(', ', $rows[$field_name]['data'][1]);
     }
@@ -142,7 +152,7 @@ public function reportPlugins() {
     foreach ($rows as &$row) {
       // Link each view name to the view itself.
       foreach ($row['views'] as $row_name => $view) {
-        $row['views'][$row_name] = l($view, "admin/structure/views/view/$view");
+        $row['views'][$row_name] = $this->linkGenerator->generate($view, 'views_ui.edit', array('view' => $view));
       }
       $row['views'] = implode(', ', $row['views']);
     }
diff --git a/core/tests/Drupal/Tests/Core/Utility/LinkGeneratorTest.php b/core/tests/Drupal/Tests/Core/Utility/LinkGeneratorTest.php
new file mode 100644
index 000000000000..eb68524fdf87
--- /dev/null
+++ b/core/tests/Drupal/Tests/Core/Utility/LinkGeneratorTest.php
@@ -0,0 +1,407 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Tests\Core\Utility\LinkGeneratorTest.
+ */
+
+namespace Drupal\Tests\Core\Utility {
+
+  use Drupal\Core\Language\Language;
+  use Drupal\Core\Utility\LinkGenerator;
+  use Drupal\Tests\UnitTestCase;
+  use Symfony\Cmf\Component\Routing\RouteObjectInterface;
+  use Symfony\Component\HttpFoundation\Request;
+
+/**
+ * Tests the link generator.
+ *
+ * @see \Drupal\Core\Utility\LinkGenerator
+ */
+class LinkGeneratorTest extends UnitTestCase {
+
+  /**
+   * The tested link generator.
+   *
+   * @var \Drupal\Core\Utility\LinkGenerator
+   */
+  protected $linkGenerator;
+
+  /**
+   * The mocked url generator.
+   *
+   * @var \PHPUnit_Framework_MockObject_MockObject
+   */
+  protected $urlGenerator;
+
+  /**
+   * The mocked module handler.
+   *
+   * @var \PHPUnit_Framework_MockObject_MockObject
+   */
+  protected $moduleHandler;
+
+  /**
+   *
+   * The mocked language manager.
+   *
+   * @var \PHPUnit_Framework_MockObject_MockObject
+   */
+  protected $languageManager;
+
+  /**
+   * Contains the LinkGenerator default options.
+   */
+  protected $defaultOptions = array(
+    'query' => array(),
+    'html' => FALSE,
+    'language' => NULL,
+  );
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function getInfo() {
+    return array(
+      'name' => 'Link generator',
+      'description' => 'Tests the link generator.',
+      'group' => 'Common',
+    );
+  }
+
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp() {
+    parent::setUp();
+
+    $this->urlGenerator = $this->getMock('\Drupal\Core\Routing\UrlGenerator', array(), array(), '', FALSE);
+    $this->moduleHandler = $this->getMock('Drupal\Core\Extension\ModuleHandlerInterface');
+    $this->languageManager = $this->getMock('Drupal\Core\Language\LanguageManager');
+
+    $this->linkGenerator = new LinkGenerator($this->urlGenerator, $this->moduleHandler, $this->languageManager);
+  }
+
+  /**
+   * Setup a proper language manager.
+   */
+  public function setUpLanguageManager() {
+    $this->languageManager->expects($this->any())
+      ->method('getLanguage')
+      ->will($this->returnValue(new Language(array('id' => 'en'))));
+  }
+
+  /**
+   * Provides test data for testing the link method.
+   *
+   * @see \Drupal\Tests\Core\Utility\LinkGeneratorTest::testGenerateHrefs()
+   *
+   * @return array
+   *   Returns some test data.
+   */
+  public function providerTestGenerateHrefs() {
+    return array(
+      // Test that the url returned by the URL generator is used.
+      array('test_route_1', array(), FALSE, '/test-route-1'),
+        // Test that $parameters is passed to the URL generator.
+      array('test_route_2', array('value' => 'example'), FALSE, '/test-route-2/example'),
+        // Test that the 'absolute' option is passed to the URL generator.
+      array('test_route_3', array(), TRUE, 'http://example.com/test-route-3'),
+    );
+  }
+
+  /**
+   * Tests the link method with certain hrefs.
+   *
+   * @see \Drupal\Core\Utility\LinkGenerator::generate()
+   * @see \Drupal\Tests\Core\Utility\LinkGeneratorTest::providerTestGenerate()
+   *
+   * @dataProvider providerTestGenerateHrefs
+   */
+  public function testGenerateHrefs($route_name, array $parameters, $absolute, $url) {
+    $this->urlGenerator->expects($this->once())
+      ->method('generateFromRoute')
+      ->with($route_name, $parameters, array('absolute' => $absolute) + $this->defaultOptions)
+      ->will($this->returnValue($url));
+
+    $this->moduleHandler->expects($this->once())
+      ->method('alter');
+
+    $this->setUpLanguageManager();
+    $request = new Request();
+    $this->linkGenerator->setRequest($request);
+
+    $result = $this->linkGenerator->generate('Test', $route_name, $parameters, array('absolute' => $absolute));
+    $this->assertTag(array(
+      'tag' => 'a',
+      'attributes' => array('href' => $url),
+      ), $result);
+  }
+
+  /**
+   * Tests the link method with additional attributes.
+   *
+   * @see \Drupal\Core\Utility\LinkGenerator::generate()
+   */
+  public function testGenerateAttributes() {
+    $this->urlGenerator->expects($this->once())
+      ->method('generateFromRoute')
+      ->with('test_route_1', array(), $this->defaultOptions)
+      ->will($this->returnValue(
+        '/test-route-1'
+      ));
+
+    // Test that HTML attributes are added to the anchor.
+    $result = $this->linkGenerator->generate('Test', 'test_route_1', array(), array(
+      'attributes' => array('title' => 'Tooltip'),
+    ));
+    $this->assertTag(array(
+      'tag' => 'a',
+      'attributes' => array(
+        'href' => '/test-route-1',
+        'title' => 'Tooltip',
+      ),
+    ), $result);
+  }
+
+  /**
+   * Tests the link method with passed query options.
+   *
+   * @see \Drupal\Core\Utility\LinkGenerator::generate()
+   */
+  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'
+      ));
+
+    $result = $this->linkGenerator->generate('Test', 'test_route_1', array(), array(
+      'query' => array('test' => 'value'),
+    ));
+    $this->assertTag(array(
+      'tag' => 'a',
+      'attributes' => array(
+        'href' => '/test-route-1?test=value',
+      ),
+    ), $result);
+  }
+
+  /**
+   * Tests the link method with passed query options via parameters.
+   *
+   * @see \Drupal\Core\Utility\LinkGenerator::generate()
+   */
+  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'
+      ));
+
+    $result = $this->linkGenerator->generate('Test', 'test_route_1', array('test' => 'value'), array());
+    $this->assertTag(array(
+      'tag' => 'a',
+      'attributes' => array(
+        'href' => '/test-route-1?test=value',
+      ),
+    ), $result);
+  }
+
+  /**
+   * Tests the link method with arbitrary passed options.
+   *
+   * @see \Drupal\Core\Utility\LinkGenerator::generate()
+   */
+  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'
+      ));
+
+    $result = $this->linkGenerator->generate('Test', 'test_route_1', array(), array(
+      'key' => 'value',
+    ));
+    $this->assertTag(array(
+      'tag' => 'a',
+      'attributes' => array(
+        'href' => '/test-route-1?test=value',
+      ),
+    ), $result);
+  }
+
+  /**
+   * Tests the link method with a script tab.
+   *
+   * @see \Drupal\Core\Utility\LinkGenerator::generate()
+   */
+  public function testGenerateXss() {
+    $this->urlGenerator->expects($this->once())
+      ->method('generateFromRoute')
+      ->with('test_route_4', array(), $this->defaultOptions)
+      ->will($this->returnValue(
+        '/test-route-4'
+      ));
+
+    // Test that HTML link text is escaped by default.
+    $result = $this->linkGenerator->generate("<script>alert('XSS!')</script>", 'test_route_4');
+    $this->assertNotTag(array(
+      'tag' => 'a',
+      'attributes' => array('href' => '/test-route-4'),
+      'child' => array(
+        'tag' => 'script',
+      ),
+    ), $result);
+  }
+
+  /**
+   * Tests the link method with html.
+   *
+   * @see \Drupal\Core\Utility\LinkGenerator::generate()
+   */
+  public function testGenerateWithHtml() {
+    $this->urlGenerator->expects($this->at(0))
+      ->method('generateFromRoute')
+      ->with('test_route_5', array(), $this->defaultOptions)
+      ->will($this->returnValue(
+        '/test-route-5'
+      ));
+    $this->urlGenerator->expects($this->at(1))
+      ->method('generateFromRoute')
+      ->with('test_route_5', array(), array('html' => TRUE) + $this->defaultOptions)
+      ->will($this->returnValue(
+        '/test-route-5'
+      ));
+
+    // Test that HTML tags are stripped from the 'title' attribute.
+    $result = $this->linkGenerator->generate('Test', 'test_route_5', array(), array(
+      'attributes' => array('title' => '<em>HTML Tooltip</em>'),
+    ));
+    $this->assertTag(array(
+      'tag' => 'a',
+      'attributes' => array(
+        'href' => '/test-route-5',
+        'title' => 'HTML Tooltip',
+      ),
+    ), $result);
+
+    // Test that the 'html' option allows unsanitized HTML link text.
+    $result = $this->linkGenerator->generate('<em>HTML output</em>', 'test_route_5', array(), array('html' => TRUE));
+    $this->assertTag(array(
+      'tag' => 'a',
+      'attributes' => array('href' => '/test-route-5'),
+      'child' => array(
+        'tag' => 'em',
+      ),
+    ), $result);
+  }
+
+  /**
+   * Tests the active class on the link method.
+   *
+   * @see \Drupal\Core\Utility\LinkGenerator::generate()
+   *
+   * @todo Test that the active class is added on the front page when generating
+   *   links to the front page when drupal_is_front_page() is converted to a
+   *   service.
+   */
+  public function testGenerateActive() {
+    $this->urlGenerator->expects($this->exactly(6))
+      ->method('generateFromRoute')
+      ->will($this->returnValueMap(array(
+        array('test_route_1', array(), FALSE, '/test-route-1'),
+        array('test_route_1', array(), FALSE, '/test-route-1'),
+        array('test_route_1', array(), FALSE, '/test-route-1'),
+        array('test_route_1', array(), FALSE, '/test-route-1'),
+        array('test_route_3', array(), FALSE, '/test-route-3'),
+        array('test_route_3', array(), FALSE, '/test-route-3'),
+      )));
+
+    $this->moduleHandler->expects($this->exactly(6))
+      ->method('alter');
+
+    $this->setUpLanguageManager();
+
+    // Render a link with a path different from the current path.
+    $request = new Request(array(), array(), array('system_path' => 'test-route-2'));
+    $this->linkGenerator->setRequest($request);
+    $result = $this->linkGenerator->generate('Test', 'test_route_1');
+    $this->assertNotTag(array(
+      'tag' => 'a',
+      'attributes' => array('class' => 'active'),
+    ), $result);
+
+    // Render a link with the same path as the current path.
+    $request = new Request(array(), array(), array('system_path' => 'test-route-1', RouteObjectInterface::ROUTE_NAME => 'test_route_1'));
+    $this->linkGenerator->setRequest($request);
+    $result = $this->linkGenerator->generate('Test', 'test_route_1');
+    $this->assertTag(array(
+      'tag' => 'a',
+      'attributes' => array('class' => 'active'),
+    ), $result);
+
+    // Render a link with the same path and language as the current path.
+    $result = $this->linkGenerator->generate('Test', 'test_route_1');
+    $this->assertTag(array(
+      'tag' => 'a',
+      'attributes' => array('class' => 'active'),
+    ), $result);
+
+    // Render a link with the same path but a different language than the current
+    // path.
+    $result = $this->linkGenerator->generate(
+      'Test',
+      'test_route_1',
+      array(),
+      array('language' => new Language(array('id' => 'de')))
+    );
+    $this->assertNotTag(array(
+      'tag' => 'a',
+      'attributes' => array('class' => 'active'),
+    ), $result);
+
+    // Render a link with the same path and query parameter as the current path.
+    $request = new Request(array('value' => 'example_1'), array(), array('system_path' => 'test-route-3', RouteObjectInterface::ROUTE_NAME => 'test_route_3'));
+    $parameters = $request->query->all();
+    $this->linkGenerator->setRequest($request);
+    $result = $this->linkGenerator->generate(
+      'Test',
+      'test_route_3',
+      array(),
+      array('query' => array('value' => 'example_1')
+    ));
+    $this->assertTag(array(
+      'tag' => 'a',
+      'attributes' => array('class' => 'active'),
+    ), $result);
+
+    // Render a link with the same path but a different query parameter than the
+    // current path.
+    $result = $this->linkGenerator->generate(
+      'Test',
+      'test_route_3',
+      array(),
+      array('query' => array('value' => 'example_2'))
+    );
+    $this->assertNotTag(array(
+      'tag' => 'a',
+      'attributes' => array('class' => 'active'),
+    ), $result);
+  }
+
+}
+
+}
+namespace {
+  // @todo Remove this once there is a service for drupal_is_front_page().
+  if (!function_exists('drupal_is_front_page')) {
+    function drupal_is_front_page() {
+      return FALSE;
+    }
+  }
+}
-- 
GitLab