diff --git a/core/lib/Drupal.php b/core/lib/Drupal.php index 0ec88d7..f98d607 100644 --- a/core/lib/Drupal.php +++ b/core/lib/Drupal.php @@ -5,6 +5,7 @@ * Contains Drupal. */ +use Drupal\Core\Url; use Symfony\Component\DependencyInjection\ContainerInterface; /** @@ -517,7 +518,13 @@ public static function linkGenerator() { * @see \Drupal\Core\Utility\LinkGeneratorInterface::generate() */ public static function l($text, $route_name, array $parameters = array(), array $options = array()) { - return static::$container->get('link_generator')->generate($text, $route_name, $parameters, $options); + $link_generator = static::linkGenerator(); + if ($route_name instanceof Url) { + return $link_generator->generateFromUrl($text, $route_name); + } + else { + return $link_generator->generate($text, $route_name, $parameters, $options); + } } /** diff --git a/core/lib/Drupal/Core/Url.php b/core/lib/Drupal/Core/Url.php index 8b79112..4a55f55 100644 --- a/core/lib/Drupal/Core/Url.php +++ b/core/lib/Drupal/Core/Url.php @@ -7,6 +7,8 @@ namespace Drupal\Core; +use Drupal\Core\Routing\UrlGeneratorInterface; + /** * Defines an object that holds information about an internal route. */ @@ -206,6 +208,18 @@ public function toArray() { } /** + * Returns the internal path for this route. + * + * This path will not include any prefixes, fragments, or query strings. + * + * @return string + * The internal path for this route. + */ + public function getInternalPath() { + return $this->urlGenerator()->getPathFromRoute($this->getRouteName(), $this->getRouteParameters()); + } + + /** * {@inheritdoc} */ public function __sleep() { @@ -226,4 +240,17 @@ protected function urlGenerator() { return $this->urlGenerator; } + /** + * Sets the URL generator. + * + * @param \Drupal\Core\Routing\UrlGeneratorInterface + * The URL generator. + * + * @return $this + */ + public function setUrlGenerator(UrlGeneratorInterface $url_generator) { + $this->urlGenerator = $url_generator; + return $this; + } + } diff --git a/core/lib/Drupal/Core/Utility/LinkGenerator.php b/core/lib/Drupal/Core/Utility/LinkGenerator.php index 2874da4..bea2f0d 100644 --- a/core/lib/Drupal/Core/Utility/LinkGenerator.php +++ b/core/lib/Drupal/Core/Utility/LinkGenerator.php @@ -15,7 +15,7 @@ use Drupal\Core\Path\AliasManagerInterface; use Drupal\Core\Template\Attribute; use Drupal\Core\Routing\UrlGeneratorInterface; -use Drupal\Core\Session\AccountInterface; +use Drupal\Core\Url; use Symfony\Cmf\Component\Routing\RouteObjectInterface; use Symfony\Component\HttpFoundation\Request; @@ -99,6 +99,74 @@ public function getActive() { /** * {@inheritdoc} + */ + public function generateFromUrl($text, Url $url) { + // @todo Inject the service when drupal_render() is converted to one. + $text = is_array($text) ? drupal_render($text) : $text; + + // Merge in default options. + $options = $url->getRouteOptions(); + $options += array( + 'attributes' => array(), + 'query' => array(), + 'html' => FALSE, + 'language' => NULL, + 'set_active_class' => FALSE, + 'absolute' => FALSE, + ); + + // Add a hreflang attribute if we know the language of this link's url and + // hreflang has not already been set. + if (!empty($options['language']) && !isset($options['attributes']['hreflang'])) { + $options['attributes']['hreflang'] = $options['language']->id; + } + + // Set the "active" class if the 'set_active_class' option is not empty. + if (!empty($options['set_active_class'])) { + // Add a "data-drupal-link-query" attribute to let the + // drupal.active-link library know the query in a standardized manner. + if (!empty($options['query'])) { + $query = $options['query']; + ksort($query); + $options['attributes']['data-drupal-link-query'] = Json::encode($query); + } + + // Add a "data-drupal-link-system-path" attribute to let the + // drupal.active-link library know the path in a standardized manner. + if (!isset($options['attributes']['data-drupal-link-system-path'])) { + $options['attributes']['data-drupal-link-system-path'] = $this->aliasManager->getSystemPath($url->getInternalPath()); + } + } + + // 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($options['attributes']['title']) && strpos($options['attributes']['title'], '<') !== FALSE) { + $options['attributes']['title'] = strip_tags($options['attributes']['title']); + } + + // Update the options before allowing altering. + $url->setRouteOptions($options); + + // Allow other modules to modify the structure of the link. + $this->moduleHandler->alter('link', $text, $url); + + // Move attributes out of options. generateFromRoute(() doesn't need them. + $attributes = new Attribute($options['attributes']); + unset($options['attributes']); + $url->setRouteOptions($options); + + // 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($url->toString()); + + // Sanitize the link text if necessary. + $text = $options['html'] ? $text : String::checkPlain($text); + + return '' . $text . ''; + } + + /** + * {@inheritdoc} * * For anonymous users, the "active" class will be calculated on the server, * because most sites serve each anonymous user the same cached page anyway. @@ -110,69 +178,9 @@ public function getActive() { * @see system_page_build() */ 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, - 'set_active_class' => FALSE, - ); - - // 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; - } - - // Set the "active" class if the 'set_active_class' option is not empty. - if (!empty($variables['options']['set_active_class'])) { - // Add a "data-drupal-link-query" attribute to let the - // drupal.active-link library know the query in a standardized manner. - if (!empty($variables['options']['query'])) { - $query = $variables['options']['query']; - ksort($query); - $variables['options']['attributes']['data-drupal-link-query'] = Json::encode($query); - } - - // Add a "data-drupal-link-system-path" attribute to let the - // drupal.active-link library know the path in a standardized manner. - if (!isset($variables['options']['attributes']['data-drupal-link-system-path'])) { - $path = $this->urlGenerator->getPathFromRoute($route_name, $parameters); - $variables['options']['attributes']['data-drupal-link-system-path'] = $this->aliasManager->getSystemPath($path); - } - } - - // 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 '' . $text . ''; + $url = new Url($route_name, $parameters, $options); + $url->setUrlGenerator($this->urlGenerator); + return $this->generateFromUrl($text, $url); } } diff --git a/core/lib/Drupal/Core/Utility/LinkGeneratorInterface.php b/core/lib/Drupal/Core/Utility/LinkGeneratorInterface.php index 02e31af..851100d 100644 --- a/core/lib/Drupal/Core/Utility/LinkGeneratorInterface.php +++ b/core/lib/Drupal/Core/Utility/LinkGeneratorInterface.php @@ -7,6 +7,8 @@ namespace Drupal\Core\Utility; +use Drupal\Core\Url; + /** * Defines an interface for generating links from route names and parameters. */ @@ -78,6 +80,19 @@ public function generate($text, $route_name, array $parameters = array(), array $options = array()); /** + * Renders a link to a URL. + * + * @param string $text + * The link text for the anchor tag as a translated string. + * @param \Drupal\Core\Url $url + * The URL object used for the link. + * + * @return string + * An HTML string containing a link to the given route and parameters. + */ + public function generateFromUrl($text, Url $url); + + /** * Returns information for the currently active route. * * @return array diff --git a/core/modules/system/system.api.php b/core/modules/system/system.api.php index d4c1039..ce889f1 100644 --- a/core/modules/system/system.api.php +++ b/core/modules/system/system.api.php @@ -5,6 +5,7 @@ * Hooks provided by Drupal core and the System module. */ +use Drupal\Core\Url; use Drupal\Core\Utility\UpdateException; /** @@ -3091,50 +3092,35 @@ 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. + * @param string $text + * The link text for the anchor tag as a translated string. + * @param \Drupal\Core\Url $url + * The URL object used for the link. Url::getRouteOptions() can contain many + * keys, but the following are guaranteed to exist: + * - 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. + * - set_active_class: Whether to set the 'active' class or not. * * @see \Drupal\Core\Routing\UrlGenerator::generateFromPath() * @see \Drupal\Core\Routing\UrlGenerator::generateFromRoute() */ -function hook_link_alter(&$variables) { +function hook_link_alter(&$text, Url $url) { // 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!)'; + if (strpos($url->getRouteName(), 'admin') !== FALSE) { + $text .= ' (Warning!)'; } } diff --git a/core/tests/Drupal/Tests/Core/DrupalTest.php b/core/tests/Drupal/Tests/Core/DrupalTest.php index b37010f..2a9e764 100644 --- a/core/tests/Drupal/Tests/Core/DrupalTest.php +++ b/core/tests/Drupal/Tests/Core/DrupalTest.php @@ -7,6 +7,7 @@ namespace Drupal\Tests\Core; +use Drupal\Core\Url; use Drupal\Tests\UnitTestCase; /** @@ -296,7 +297,30 @@ public function testL() { ->will($this->returnValue('link_html_string')); $this->setMockContainerService('link_generator', $generator); - $this->assertInternalType('string', \Drupal::l('Test title', 'test_route', $route_parameters, $options)); + $link = \Drupal::l('Test title', 'test_route', $route_parameters, $options); + $this->assertSame('link_html_string', $link); + $this->assertInternalType('string', $link); + } + + /** + * Tests the l() method. + * + * @see \Drupal\Core\Utility\LinkGeneratorInterface::generateFromUrl() + */ + public function testLWithUrl() { + $route_parameters = array('test_parameter' => 'test'); + $options = array('test_option' => 'test'); + $url = new Url('test_route', $route_parameters, $options); + $generator = $this->getMock('Drupal\Core\Utility\LinkGeneratorInterface'); + $generator->expects($this->once()) + ->method('generateFromUrl') + ->with('Test title', $url) + ->will($this->returnValue('link_html_string')); + $this->setMockContainerService('link_generator', $generator); + + $link = \Drupal::l('Test title', $url); + $this->assertSame('link_html_string', $link); + $this->assertInternalType('string', $link); } /**