commit 95f3512906929707e3a98e42e65bf938f9a67d94 Author: Joel Pittet Date: Tue Apr 16 22:00:18 2013 -0700 merge again diff --git a/core/includes/common.inc b/core/includes/common.inc index d4d9961..8ea60f3 100644 --- a/core/includes/common.inc +++ b/core/includes/common.inc @@ -1849,6 +1849,38 @@ function url_is_external($path) { } /** + * Returns TRUE if the current URL matches the given path and options. + * + * @param string $path + * An internal path or external url as could be used by url(). + * @param array $options + * An associative array of options as could be used by url(). + * + * @return bool + * Boolean TRUE or FALSE, 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::service('request')->query->all() == $options['query']; + + return $path_match && $lang_match && $query_match; +} + +/** * Formats an attribute string for an HTTP header. * * @param $attributes @@ -1917,68 +1949,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 + // theme_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::service('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 4bc57ef..5d90008 100644 --- a/core/includes/theme.inc +++ b/core/includes/theme.inc @@ -1680,33 +1680,54 @@ function theme_status_messages($variables) { return $output; } -/** - * Prepares variables for HTML link templates. - * - * All Drupal code that outputs a link should call the l() function. That - * function performs some initial preprocessing, and then, if necessary, calls - * theme('link') for rendering the anchor tag. - * - * To optimize performance for sites that don't need custom theming of links, - * the l() function includes an inline copy of this function, and uses that - * copy if none of the enabled modules or the active theme implement any - * preprocess or process functions or override this theme implementation. - * - * @param array $variables - * An associative array containing the keys 'text', 'path', and 'options'. - * See the l() function for information about these variables. - * - * @see l() - */ + /** + * 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 + * + * @see l() + */ function template_preprocess_link(&$variables) { - $rendered_text = is_array($variables['text']) ? drupal_render($variables['text']) : $variables['text']; + // 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. $attributes = array(); if (isset($variables['options']['attributes'])) { $attributes = $variables['options']['attributes']; + unset($variables['options']['attributes']); } $variables['attributes'] = new Attribute($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'])); - $variables['text'] = (!empty($variables['options']['html']) ? $rendered_text : check_plain($rendered_text)); + + // Sanitize the link text if necessary. + $rendered_text = is_array($variables['text']) ? drupal_render($variables['text']) : $variables['text']; + $variables['text'] = ($variables['options']['html']) ? $rendered_text : check_plain($rendered_text); } /** diff --git a/core/modules/language/lib/Drupal/language/Tests/LanguageSwitchingTest.php b/core/modules/language/lib/Drupal/language/Tests/LanguageSwitchingTest.php index ac834d1..8088f6a 100644 --- a/core/modules/language/lib/Drupal/language/Tests/LanguageSwitchingTest.php +++ b/core/modules/language/lib/Drupal/language/Tests/LanguageSwitchingTest.php @@ -19,7 +19,7 @@ class LanguageSwitchingTest extends WebTestBase { * * @var array */ - public static $modules = array('language', 'block'); + public static $modules = array('language', 'block', 'language_test'); public static function getInfo() { return array( @@ -89,4 +89,99 @@ function testLanguageBlock() { $this->assertIdentical($anchors, array('active' => array('en'), 'inactive' => array('fr')), 'Only the current language anchor is marked as active on the language switcher block.'); } + /** + * Test active class on links when switching languages. + */ + function testLanguageLinkActiveClass() { + // Add language. + $edit = array( + 'predefined_langcode' => 'fr', + ); + $this->drupalPost('admin/config/regional/language/add', $edit, t('Add language')); + + // Enable URL language detection and selection. + $edit = array('language_interface[enabled][language-url]' => '1'); + $this->drupalPost('admin/config/regional/language/detection', $edit, t('Save settings')); + + $function_name = 'l()'; + + // Test links generated by l() on an English page. + $current_language = 'English'; + $this->drupalGet('language_test/l-active-class'); + + // Language code 'none' link should be active. + $langcode = 'none'; + $links = $this->xpath('//a[@id = :id and contains(@class, :class)]', array(':id' => 'no_lang_link', ':class' => 'active')); + $this->assertTrue(isset($links[0]), t('A link generated by :function to the current :language page with langcode :langcode is marked active.', array(':function' => $function_name, ':language' => $current_language, ':langcode' => $langcode))); + + // Language code 'en' link should be active. + $langcode = 'en'; + $links = $this->xpath('//a[@id = :id and contains(@class, :class)]', array(':id' => 'en_link', ':class' => 'active')); + $this->assertTrue(isset($links[0]), t('A link generated by :function to the current :language page with langcode :langcode is marked active.', array(':function' => $function_name, ':language' => $current_language, ':langcode' => $langcode))); + + // Language code 'fr' link should not be active. + $langcode = 'fr'; + $links = $this->xpath('//a[@id = :id and not(contains(@class, :class))]', array(':id' => 'fr_link', ':class' => 'active')); + $this->assertTrue(isset($links[0]), t('A link generated by :function to the current :language page with langcode :langcode is NOT marked active.', array(':function' => $function_name, ':language' => $current_language, ':langcode' => $langcode))); + + // Test links generated by l() on a French page. + $current_language = 'French'; + $this->drupalGet('fr/language_test/l-active-class'); + + // Language code 'none' link should be active. + $langcode = 'none'; + $links = $this->xpath('//a[@id = :id and contains(@class, :class)]', array(':id' => 'no_lang_link', ':class' => 'active')); + $this->assertTrue(isset($links[0]), t('A link generated by :function to the current :language page with langcode :langcode is marked active.', array(':function' => $function_name, ':language' => $current_language, ':langcode' => $langcode))); + + // Language code 'en' link should not be active. + $langcode = 'en'; + $links = $this->xpath('//a[@id = :id and not(contains(@class, :class))]', array(':id' => 'en_link', ':class' => 'active')); + $this->assertTrue(isset($links[0]), t('A link generated by :function to the current :language page with langcode :langcode is NOT marked active.', array(':function' => $function_name, ':language' => $current_language, ':langcode' => $langcode))); + + // Language code 'fr' link should be active. + $langcode = 'fr'; + $links = $this->xpath('//a[@id = :id and contains(@class, :class)]', array(':id' => 'fr_link', ':class' => 'active')); + $this->assertTrue(isset($links[0]), t('A link generated by :function to the current :language page with langcode :langcode is marked active.', array(':function' => $function_name, ':language' => $current_language, ':langcode' => $langcode))); + + $function_name = "theme('link')"; + + // Test links generated by theme('link') on an English page. + $current_language = 'English'; + $this->drupalGet('language_test/theme-link-active-class'); + + // Language code 'none' link should be active. + $langcode = 'none'; + $links = $this->xpath('//a[@id = :id and contains(@class, :class)]', array(':id' => 'no_lang_link', ':class' => 'active')); + $this->assertTrue(isset($links[0]), t('A link generated by :function to the current :language page with langcode :langcode is marked active.', array(':function' => $function_name, ':language' => $current_language, ':langcode' => $langcode))); + + // Language code 'en' link should be active. + $langcode = 'en'; + $links = $this->xpath('//a[@id = :id and contains(@class, :class)]', array(':id' => 'en_link', ':class' => 'active')); + $this->assertTrue(isset($links[0]), t('A link generated by :function to the current :language page with langcode :langcode is marked active.', array(':function' => $function_name, ':language' => $current_language, ':langcode' => $langcode))); + + // Language code 'fr' link should not be active. + $langcode = 'fr'; + $links = $this->xpath('//a[@id = :id and not(contains(@class, :class))]', array(':id' => 'fr_link', ':class' => 'active')); + $this->assertTrue(isset($links[0]), t('A link generated by :function to the current :language page with langcode :langcode is NOT marked active.', array(':function' => $function_name, ':language' => $current_language, ':langcode' => $langcode))); + + // Test links generated by theme('link') on a French page. + $current_language = 'French'; + $this->drupalGet('fr/language_test/theme-link-active-class'); + + // Language code 'none' link should be active. + $langcode = 'none'; + $links = $this->xpath('//a[@id = :id and contains(@class, :class)]', array(':id' => 'no_lang_link', ':class' => 'active')); + $this->assertTrue(isset($links[0]), t('A link generated by :function to the current :language page with langcode :langcode is marked active.', array(':function' => $function_name, ':language' => $current_language, ':langcode' => $langcode))); + + // Language code 'en' link should not be active. + $langcode = 'en'; + $links = $this->xpath('//a[@id = :id and not(contains(@class, :class))]', array(':id' => 'en_link', ':class' => 'active')); + $this->assertTrue(isset($links[0]), t('A link generated by :function to the current :language page with langcode :langcode is NOT marked active.', array(':function' => $function_name, ':language' => $current_language, ':langcode' => $langcode))); + + // Language code 'fr' link should be active. + $langcode = 'fr'; + $links = $this->xpath('//a[@id = :id and contains(@class, :class)]', array(':id' => 'fr_link', ':class' => 'active')); + $this->assertTrue(isset($links[0]), t('A link generated by :function to the current :language page with langcode :langcode is marked active.', array(':function' => $function_name, ':language' => $current_language, ':langcode' => $langcode))); + } + } diff --git a/core/modules/language/tests/language_test/language_test.routing.yml b/core/modules/language/tests/language_test/language_test.routing.yml new file mode 100644 index 0000000..d95722b --- /dev/null +++ b/core/modules/language/tests/language_test/language_test.routing.yml @@ -0,0 +1,13 @@ +language_test_l_active_class: + pattern: '/language_test/l-active-class' + defaults: + _content: '\Drupal\language_test\Controller\LanguageTestController::lActiveClass' + requirements: + _access: 'TRUE' + +language_test_theme_link_active_class: + pattern: '/language_test/theme-link-active-class' + defaults: + _content: '\Drupal\language_test\Controller\LanguageTestController::themeLinkActiveClass' + requirements: + _access: 'TRUE' diff --git a/core/modules/language/tests/language_test/lib/Drupal/language_test/Controller/LanguageTestController.php b/core/modules/language/tests/language_test/lib/Drupal/language_test/Controller/LanguageTestController.php new file mode 100644 index 0000000..da5ad64 --- /dev/null +++ b/core/modules/language/tests/language_test/lib/Drupal/language_test/Controller/LanguageTestController.php @@ -0,0 +1,113 @@ + array( + '#theme' => 'link', + '#text' => t('Link to the current path with no langcode provided.'), + '#path' => current_path(), + '#options' => array( + 'attributes' => array( + 'id' => 'no_lang_link', + ), + ), + ), + 'fr' => array( + '#theme' => 'link', + '#text' => t('Link to a French version of the current path.'), + '#path' => current_path(), + '#options' => array( + 'language' => $languages['fr'], + 'attributes' => array( + 'id' => 'fr_link', + ), + ), + ), + 'en' => array( + '#theme' => 'link', + '#text' => t('Link to an English version of the current path.'), + '#path' => current_path(), + '#options' => array( + 'language' => $languages['en'], + 'attributes' => array( + 'id' => 'en_link', + ), + ), + ), + ); + } + + /** + * Returns links to the current page with different langcodes. + * + * Using #type causes these links to be rendered with l(). + */ + public function lActiveClass() { + // We assume that 'en' and 'fr' have been configured. + $languages = language_list(); + return array( + 'no_language' => array( + '#type' => 'link', + '#title' => t('Link to the current path with no langcode provided.'), + '#href' => current_path(), + '#options' => array( + 'attributes' => array( + 'id' => 'no_lang_link', + ), + ), + ), + 'fr' => array( + '#type' => 'link', + '#title' => t('Link to a French version of the current path.'), + '#href' => current_path(), + '#options' => array( + 'language' => $languages['fr'], + 'attributes' => array( + 'id' => 'fr_link', + ), + ), + ), + 'en' => array( + '#type' => 'link', + '#title' => t('Link to an English version of the current path.'), + '#href' => current_path(), + '#options' => array( + 'language' => $languages['en'], + 'attributes' => array( + 'id' => 'en_link', + ), + ), + ), + ); + } + +} diff --git a/core/modules/system/lib/Drupal/system/Tests/Common/UrlTest.php b/core/modules/system/lib/Drupal/system/Tests/Common/UrlTest.php index eeb49cc..a241a31 100644 --- a/core/modules/system/lib/Drupal/system/Tests/Common/UrlTest.php +++ b/core/modules/system/lib/Drupal/system/Tests/Common/UrlTest.php @@ -28,47 +28,135 @@ public static function getInfo() { } /** - * Confirms that invalid URLs are filtered. + * Confirms that invalid URLs are filtered in link generating functions. */ - function testLXSS() { + function testLinkXSS() { + // Test l(). $text = $this->randomName(); $path = ""; $link = l($text, $path); $sanitized_path = check_url(url($path)); - $this->assertTrue(strpos($link, $sanitized_path) !== FALSE, format_string('XSS attack @path was filtered', array('@path' => $path))); + $this->assertTrue(strpos($link, $sanitized_path) !== FALSE, format_string('XSS attack @path was filtered by l().', array('@path' => $path))); + + // Test #theme. + $link_array = array( + '#theme' => 'link', + '#text' => $this->randomName(), + '#path' => $path, + ); + $theme_link = drupal_render($link_array); + $sanitized_path = check_url(url($path)); + $this->assertTrue(strpos($theme_link, $sanitized_path) !== FALSE, format_string('XSS attack @path was filtered by #theme', array('@path' => $path))); } /** - * Tests for active class in l() function. + * Tests for active class in links produced by l() and theme_link() functions. */ - function testLActiveClass() { + function testLinkActiveClass() { + $options_no_query = array(); + $options_query = array( + 'query' => array( + 'foo' => 'bar', + 'one' => 'two', + ), + ); + $options_query_reverse = array( + 'query' => array( + 'one' => 'two', + 'foo' => 'bar', + ), + ); + + // Test l(). $path = 'common-test/l-active-class'; - $options = array(); - $this->drupalGet($path, $options); - $links = $this->xpath('//a[@href = :href and contains(@class, :class)]', array(':href' => url($path, $options), ':class' => 'active')); - $this->assertTrue(isset($links[0]), 'A link to the current page is marked active.'); + $this->drupalGet($path, $options_no_query); + $links = $this->xpath('//a[@href = :href and contains(@class, :class)]', array(':href' => url($path, $options_no_query), ':class' => 'active')); + $this->assertTrue(isset($links[0]), 'A link generated by l() to the current page is marked active.'); + + $links = $this->xpath('//a[@href = :href and not(contains(@class, :class))]', array(':href' => url($path, $options_query), ':class' => 'active')); + $this->assertTrue(isset($links[0]), 'A link generated by l() to the current page with a query string when the current page has no query string is not marked active.'); + + $this->drupalGet($path, $options_query); + $links = $this->xpath('//a[@href = :href and contains(@class, :class)]', array(':href' => url($path, $options_query), ':class' => 'active')); + $this->assertTrue(isset($links[0]), 'A link generated by l() to the current page with a query string that matches the current query string is marked active.'); + + $links = $this->xpath('//a[@href = :href and contains(@class, :class)]', array(':href' => url($path, $options_query_reverse), ':class' => 'active')); + $this->assertTrue(isset($links[0]), 'A link generated by l() to the current page with a query string that has matching parameters to the current query string but in a different order is marked active.'); + + $links = $this->xpath('//a[@href = :href and not(contains(@class, :class))]', array(':href' => url($path, $options_no_query), ':class' => 'active')); + $this->assertTrue(isset($links[0]), 'A link generated by l() to the current page without a query string when the current page has a query string is not marked active.'); - $options = array('query' => array('foo' => 'bar')); - $links = $this->xpath('//a[@href = :href and not(contains(@class, :class))]', array(':href' => url($path, $options), ':class' => 'active')); - $this->assertTrue(isset($links[0]), 'A link to the current page with a query string when the current page has no query string is not marked active.'); + // Test #theme. + $path = 'common-test/theme-link-active-class'; - $this->drupalGet($path, $options); - $links = $this->xpath('//a[@href = :href and contains(@class, :class)]', array(':href' => url($path, $options), ':class' => 'active')); - $this->assertTrue(isset($links[0]), 'A link to the current page with a query string that matches the current query string is marked active.'); + $this->drupalGet($path, $options_no_query); + $links = $this->xpath('//a[@href = :href and contains(@class, :class)]', array(':href' => url($path, $options_no_query), ':class' => 'active')); + $this->assertTrue(isset($links[0]), 'A link generated by #theme to the current page is marked active.'); - $options = array(); - $links = $this->xpath('//a[@href = :href and not(contains(@class, :class))]', array(':href' => url($path, $options), ':class' => 'active')); - $this->assertTrue(isset($links[0]), 'A link to the current page without a query string when the current page has a query string is not marked active.'); + $links = $this->xpath('//a[@href = :href and not(contains(@class, :class))]', array(':href' => url($path, $options_query), ':class' => 'active')); + $this->assertTrue(isset($links[0]), 'A link generated by #theme to the current page with a query string when the current page has no query string is not marked active.'); + + $this->drupalGet($path, $options_query); + $links = $this->xpath('//a[@href = :href and contains(@class, :class)]', array(':href' => url($path, $options_query), ':class' => 'active')); + $this->assertTrue(isset($links[0]), 'A link generated by #theme to the current page with a query string that matches the current query string is marked active.'); + + $links = $this->xpath('//a[@href = :href and contains(@class, :class)]', array(':href' => url($path, $options_query_reverse), ':class' => 'active')); + $this->assertTrue(isset($links[0]), 'A link generated by #theme to the current page with a query string that has matching parameters to the current query string but in a different order is marked active.'); + + $links = $this->xpath('//a[@href = :href and not(contains(@class, :class))]', array(':href' => url($path, $options_no_query), ':class' => 'active')); + $this->assertTrue(isset($links[0]), 'A link generated by #theme to the current page without a query string when the current page has a query string is not marked active.'); + } + + /** + * Tests for custom class in links produced by l() and theme_link() functions. + */ + function testLinkCustomClass() { + // Test l(). + $class_l = $this->randomName(); + $link_l = l($this->randomName(), current_path(), array('attributes' => array('class' => array($class_l)))); + $this->assertTrue($this->hasClass($link_l, $class_l), format_string('Custom class @class is present on link when requested by l()', array('@class' => $class_l))); + + // Test #theme. + $class_theme = $this->randomName(); + $theme_link = array( + '#theme' => 'link', + '#text' => $this->randomName(), + '#path' => current_path(), + '#options' => array( + 'attributes' => array( + 'class' => array($class_theme), + ), + ), + ); + $link_theme = drupal_render($theme_link); + $this->assertTrue($this->hasClass($link_theme, $class_theme), format_string('Custom class @class is present on link when requested by #theme', array('@class' => $class_theme))); } /** - * Tests for custom class in l() function. + * Tests that theme_link() supports render arrays in 'text' parameter. */ - function testLCustomClass() { - $class = $this->randomName(); - $link = l($this->randomName(), current_path(), array('attributes' => array('class' => array($class)))); - $this->assertTrue($this->hasClass($link, $class), format_string('Custom class @class is present on link when requested', array('@class' => $class))); + function testLinkNestedRenderArrays() { + // Build a link with l() for reference. + $l = l('foo', 'http://drupal.org'); + + // Test a themed link with plain text 'text'. + $theme_link_plain_array = array( + '#theme' => 'link', + '#text' => 'foo', + '#path' => 'http://drupal.org', + ); + $theme_link_plain = drupal_render($theme_link_plain_array); + $this->assertEqual($theme_link_plain, $l); + + // Build a themed link with renderable 'text'. + $theme_link_nested_array = array( + '#theme' => 'link', + '#text' => array('#markup' => 'foo'), + '#path' => 'http://drupal.org', + ); + $theme_link_nested = drupal_render($theme_link_nested_array); + $this->assertEqual($theme_link_nested, $l); } /** diff --git a/core/modules/system/templates/link.html.twig b/core/modules/system/templates/link.html.twig index bafc57d..57c54bd 100644 --- a/core/modules/system/templates/link.html.twig +++ b/core/modules/system/templates/link.html.twig @@ -5,7 +5,8 @@ * * Available variables: * - text: The translated link text for the anchor tag. - * - url: The url of the link. + * - url: The sanitized url for this link. + * - url_is_active: TRUE if the url for this link leads to the current page. * - attributes: Remaining html attributes for the containing element. * * @see template_preprocess() diff --git a/core/modules/system/tests/modules/common_test/common_test.module b/core/modules/system/tests/modules/common_test/common_test.module index 203ead2..caf40b6 100644 --- a/core/modules/system/tests/modules/common_test/common_test.module +++ b/core/modules/system/tests/modules/common_test/common_test.module @@ -64,12 +64,6 @@ function common_test_menu() { 'access arguments' => array('access content'), 'type' => MENU_CALLBACK, ); - $items['common-test/l-active-class'] = array( - 'title' => 'Test l() adding of active class', - 'page callback' => 'common_test_l_active_class', - 'access arguments' => array('access content'), - 'type' => MENU_CALLBACK, - ); return $items; } @@ -332,26 +326,3 @@ function common_test_js_and_css_querystring() { function common_test_cron() { throw new Exception(t('Uncaught exception')); } - -/** - * Page callback: Displays links to the current page, one with a query string. - */ -function common_test_l_active_class() { - return array( - 'no_query' => array( - '#type' => 'link', - '#title' => t('Link with no query string'), - '#href' => current_path(), - ), - 'with_query' => array( - '#type' => 'link', - '#title' => t('Link with a query string'), - '#href' => current_path(), - '#options' => array( - 'query' => array( - 'foo' => 'bar', - ), - ), - ), - ); -} diff --git a/core/modules/system/tests/modules/common_test/common_test.routing.yml b/core/modules/system/tests/modules/common_test/common_test.routing.yml new file mode 100644 index 0000000..840297f --- /dev/null +++ b/core/modules/system/tests/modules/common_test/common_test.routing.yml @@ -0,0 +1,14 @@ +common_test_l_active_class: + pattern: '/common-test/l-active-class' + defaults: + _content: '\Drupal\common_test\Controller\CommonTestController::lActiveClass' + requirements: + _access: 'TRUE' + +common_test_theme_link_active_class: + pattern: '/common-test/theme-link-active-class' + defaults: + _content: '\Drupal\common_test\Controller\CommonTestController::themeLinkActiveClass' + requirements: + _access: 'TRUE' + diff --git a/core/modules/system/tests/modules/common_test/lib/Drupal/common_test/Controller/CommonTestController.php b/core/modules/system/tests/modules/common_test/lib/Drupal/common_test/Controller/CommonTestController.php new file mode 100644 index 0000000..da963ab --- /dev/null +++ b/core/modules/system/tests/modules/common_test/lib/Drupal/common_test/Controller/CommonTestController.php @@ -0,0 +1,99 @@ + array( + '#type' => 'link', + '#title' => t('Link with no query string'), + '#href' => current_path(), + ), + 'with_query' => array( + '#type' => 'link', + '#title' => t('Link with a query string'), + '#href' => current_path(), + '#options' => array( + 'query' => array( + 'foo' => 'bar', + 'one' => 'two', + ), + ), + ), + 'with_query_reversed' => array( + '#type' => 'link', + '#title' => t('Link with the same query string in reverse order'), + '#href' => current_path(), + '#options' => array( + 'query' => array( + 'one' => 'two', + 'foo' => 'bar', + ), + ), + ), + ); + } + + /** + * Returns links to the current page, with and without query strings. + * + * Using #theme causes these links to be rendered with theme_link(). + */ + public function themeLinkActiveClass() { + return array( + 'no_query' => array( + '#theme' => 'link', + '#text' => t('Link with no query string'), + '#path' => current_path(), + ), + 'with_query' => array( + '#theme' => 'link', + '#text' => t('Link with a query string'), + '#path' => current_path(), + '#options' => array( + 'query' => array( + 'foo' => 'bar', + 'one' => 'two', + ), + ), + ), + 'with_query_reversed' => array( + '#theme' => 'link', + '#text' => t('Link with the same query string in reverse order'), + '#path' => current_path(), + '#options' => array( + 'query' => array( + 'one' => 'two', + 'foo' => 'bar', + ), + ), + ), + ); + } + +}