Commit ded07e9a authored by catch's avatar catch

Issue #2047619 by dawehner, pwolanin, tstoeckler, thedavidmeister: Add a link...

Issue #2047619 by dawehner, pwolanin, tstoeckler, thedavidmeister: Add a link generator service for route-based links.
parent cf98e43a
......@@ -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']
......
......@@ -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;
}
......
......@@ -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.
*
......
......@@ -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.
*
......
......@@ -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 {
......
<?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>';
}
}
<?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());
}
......@@ -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(
......
......@@ -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".
*/
......
......@@ -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']);
}
......
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment