Index: includes/common.inc =================================================================== RCS file: /cvs/drupal/drupal/includes/common.inc,v retrieving revision 1.1040 diff -u -p -r1.1040 common.inc --- includes/common.inc 3 Nov 2009 06:47:22 -0000 1.1040 +++ includes/common.inc 4 Nov 2009 03:01:28 -0000 @@ -2683,6 +2683,7 @@ function drupal_attributes(array $attrib */ function l($text, $path, array $options = array()) { global $language_url; + static $use_theme = NULL; // Merge in defaults. $options += array( @@ -2702,6 +2703,35 @@ function l($text, $path, array $options $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 (variable_get('theme_link', TRUE)) { + drupal_theme_initialize(); + $registry = theme_get_registry(); + // 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; + } + } + if ($use_theme) { + return theme('link', array('text' => $text, 'path' => $path, 'options' => $options)); + } return '' . ($options['html'] ? $text : check_plain($text)) . ''; } @@ -5335,6 +5365,9 @@ function drupal_common_theme() { 'status_messages' => array( 'variables' => array('display' => NULL), ), + 'link' => array( + 'variables' => array('text' => NULL, 'path' => NULL, 'options' => array()), + ), 'links' => array( 'variables' => array('links' => NULL, 'attributes' => array('class' => array('links')), 'heading' => array()), ), Index: includes/theme.inc =================================================================== RCS file: /cvs/drupal/drupal/includes/theme.inc,v retrieving revision 1.547 diff -u -p -r1.547 theme.inc --- includes/theme.inc 3 Nov 2009 06:47:22 -0000 1.547 +++ includes/theme.inc 4 Nov 2009 03:01:29 -0000 @@ -1367,6 +1367,31 @@ function theme_status_messages($variable } /** + * Return a themed link. + * + * 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 $variables + * An associative array containing the keys 'text', 'path', and 'options'. + * See the l() function for information about these variables. + * + * @return + * An HTML string containing a link to the given path. + * + * @see l() + */ +function theme_link($variables) { + return '' . ($variables['options']['html'] ? $variables['text'] : check_plain($variables['text'])) . ''; +} + +/** * Return a themed set of links. * * @param $variables Index: modules/simpletest/tests/common.test =================================================================== RCS file: /cvs/drupal/drupal/modules/simpletest/tests/common.test,v retrieving revision 1.86 diff -u -p -r1.86 common.test --- modules/simpletest/tests/common.test 3 Nov 2009 06:47:23 -0000 1.86 +++ modules/simpletest/tests/common.test 4 Nov 2009 03:01:31 -0000 @@ -70,9 +70,24 @@ class CommonURLUnitTest extends DrupalUn * Confirm that invalid text given as $path is filtered. */ function testLXSS() { + global $conf; $text = $this->randomName(); $path = ""; + // We specifically want to test the default l() implementation, not the + // theme system. We don't want to permanently change the 'theme_link' + // configuration variable. Alternatively to messing with $conf in this way, + // we could make CommonURLUnitTest inherit from DrupalWebTestCase in order + // to give the test a proper sandbox for configuration variables and the + // theme system, but that seems like overkill just to test the unthemed + // version of l(). + if (isset($conf['theme_link'])) { + $theme_link = $conf['theme_link']; + } + $conf['theme_link'] = FALSE; $link = l($text, $path); + if (isset($theme_link)) { + $conf['theme_link'] = $theme_link; + } $sanitized_path = check_url(url($path)); $this->assertTrue(strpos($link, $sanitized_path) !== FALSE, t('XSS attack @path was filtered', array('@path' => $path))); }