diff --git a/core/includes/common.inc b/core/includes/common.inc index 88d6fd3df127d97b86405bf95b195d4d5171965c..5eac16e07f3e7287afdccd386dc05ef494c4c27c 100644 --- a/core/includes/common.inc +++ b/core/includes/common.inc @@ -2160,6 +2160,46 @@ function url_is_external($path) { } /** + * Returns TRUE if the given path is currently being viewed. + * + * @param string $path + * An internal path or external url as could be used by url(). + * + * @param array $options + * An associative array of additional options. Defaults to an empty array. It + * may contain the following elements. + * - '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). This element is also used by url(). + * - 'query': An array of query key/value-pairs (without any URL-encoding) to + * append to the URL. + * + * @return bool + * Boolean where TRUE indicates an active path. + * + * @see url() + */ +function url_is_active($path, $options = array()) { + // Merge in defaults. + $options += array( + 'query' => array(), + 'language' => NULL, + ); + + // A path is only active if it corresponds to the current path, the + // language of the path is equal to the current language, and if the + // query parameters of the url equal those of the current request, since the + // same request with different query parameters may yield a different page + // (e.g., pagers). + $path_match = ($path == current_path() || ($path == '' && drupal_is_front_page())); + $lang_match = (empty($options['language']) || $options['language']->langcode == language(LANGUAGE_TYPE_URL)->langcode); + $query_match = drupal_container()->get('request')->query->all() == $options['query']; + + return $path_match && $lang_match && $query_match; +} + +/** * Formats an attribute string for an HTTP header. * * @param $attributes @@ -2228,68 +2268,50 @@ function drupal_http_header_attributes(array $attributes = array()) { * An HTML string containing a link to the given path. * * @see url() + * @see theme_link() */ function l($text, $path, array $options = array()) { - static $use_theme = NULL; + // Build a variables array to keep the structure of the alter consistent with + // template_preprocess_link(). + $variables = array( + 'text' => $text, + 'path' => $path, + 'options' => $options, + ); // Merge in defaults. - $options += array( + $variables['options'] += array( 'attributes' => array(), 'query' => array(), 'html' => FALSE, ); - // Append active class. - // The link is only active, if its path corresponds to the current path, the - // language of the linked path is equal to the current language, and if the - // query parameters of the link equal those of the current request, since the - // same request with different query parameters may yield a different page - // (e.g., pagers). - $is_active = ($path == current_path() || ($path == '' && drupal_is_front_page())); - $is_active = $is_active && (empty($options['language']) || $options['language']->langcode == language(LANGUAGE_TYPE_URL)->langcode); - $is_active = $is_active && (drupal_container()->get('request')->query->all() == $options['query']); - if ($is_active) { - $options['attributes']['class'][] = 'active'; - } - - // Remove all HTML and PHP tags from a tooltip. For best performance, we act only - // if a quick strpos() pre-check gave a suspicion (because strip_tags() is expensive). - if (isset($options['attributes']['title']) && strpos($options['attributes']['title'], '<') !== FALSE) { - $options['attributes']['title'] = strip_tags($options['attributes']['title']); - } - - // Determine if rendering of the link is to be done with a theme function - // or the inline default. Inline is faster, but if the theme system has been - // loaded and a module or theme implements a preprocess or process function - // or overrides the theme_link() function, then invoke theme(). Preliminary - // benchmarks indicate that invoking theme() can slow down the l() function - // by 20% or more, and that some of the link-heavy Drupal pages spend more - // than 10% of the total page request time in the l() function. - if (!isset($use_theme) && function_exists('theme')) { - // Allow edge cases to prevent theme initialization and force inline link - // rendering. - if (config('system.performance')->get('theme_link')) { - drupal_theme_initialize(); - $registry = theme_get_registry(FALSE); - // We don't want to duplicate functionality that's in theme(), so any - // hint of a module or theme doing anything at all special with the 'link' - // theme hook should simply result in theme() being called. This includes - // the overriding of theme_link() with an alternate function or template, - // the presence of preprocess or process functions, or the presence of - // include files. - $use_theme = !isset($registry['link']['function']) || ($registry['link']['function'] != 'theme_link'); - $use_theme = $use_theme || !empty($registry['link']['preprocess functions']) || !empty($registry['link']['process functions']) || !empty($registry['link']['includes']); - } - else { - $use_theme = FALSE; - } + // Add 'active' class if appropriate. + if ($variables['url_is_active'] = url_is_active($variables['path'], $variables['options'])) { + $variables['options']['attributes']['class'][] = 'active'; } - if ($use_theme) { - return theme('link', array('text' => $text, 'path' => $path, 'options' => $options)); + + // 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. + drupal_container()->get('module_handler')->alter('link', $variables); + + // Move attributes out of options. url() doesn't need them. + $attributes = new Attribute($variables['options']['attributes']); + unset($variables['options']['attributes']); + // The result of url() is a plain-text URL. Because we are using it here // in an HTML argument context, we need to encode it properly. - return '' . ($options['html'] ? $text : check_plain($text)) . ''; + $url = check_plain(url($variables['path'], $variables['options'])); + + // Sanitize the link text if necessary. + $text = ($variables['options']['html']) ? $variables['text'] : check_plain($variables['text']); + + return '' . $text . ''; } /** diff --git a/core/includes/theme.inc b/core/includes/theme.inc index 8d527df2aae1606a01b7db85685a511306f68712..fe61d18a9e85510f3f77147eecb7c195b2c80de0 100644 --- a/core/includes/theme.inc +++ b/core/includes/theme.inc @@ -1681,7 +1681,9 @@ function theme_status_messages($variables) { } /** - * Returns HTML for a link. + * Prepares variables for link templates. + * + * Default template: link.html.twig. * * All Drupal code that outputs a link should call the l() function. That * function performs some initial preprocessing, and then, if necessary, calls @@ -1698,8 +1700,39 @@ function theme_status_messages($variables) { * * @see l() */ -function theme_link($variables) { - return '' . ($variables['options']['html'] ? $variables['text'] : check_plain($variables['text'])) . ''; +function template_preprocess_link(&$variables) { + // Merge in defaults. + $variables['options'] += array( + 'attributes' => array(), + 'query' => array(), + 'html' => FALSE, + ); + + // Add 'active' class if appropriate. + if ($variables['url_is_active'] = url_is_active($variables['path'], $variables['options'])) { + $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. + drupal_container()->get('module_handler')->alter('link', $variables); + + // Move attributes out of options. url() doesn't need them and they make more + // sense as top level variables in templates. + $variables['attributes'] = new Attribute($variables['options']['attributes']); + unset($variables['options']['attributes']); + + // The result of url() is a plain-text URL. Because we are using it here + // in an HTML argument context, we need to encode it properly. + $variables['url'] = check_plain(url($variables['path'], $variables['options'])); + + // Sanitize the link text if necessary. + $variables['text'] = ($variables['options']['html']) ? $variables['text'] : check_plain($variables['text']); } /** @@ -3152,6 +3185,7 @@ function drupal_common_theme() { ), 'link' => array( 'variables' => array('text' => NULL, 'path' => NULL, 'options' => array()), + 'template' => 'link', ), 'links' => array( 'variables' => array('links' => array(), 'attributes' => array('class' => array('links')), 'heading' => array()), diff --git a/core/modules/system/templates/link.html.twig b/core/modules/system/templates/link.html.twig new file mode 100644 index 0000000000000000000000000000000000000000..fdc19e20f54afe96777dde41c22a85c56f56459f --- /dev/null +++ b/core/modules/system/templates/link.html.twig @@ -0,0 +1,15 @@ +{# +/** + * Returns HTML for a link tag. + * + * Available variables + * - text: The content to display within the element. + * - url: The sanitized url for this link. + * - url_is_active: TRUE if the url for this link leads to the current page. + * - attributes: (optional) HTML attributes to apply to the element. + * + * @see template_preprocess_link() + * @ingroup themeable + */ +#} +{{ text }}