Index: includes/common.inc =================================================================== RCS file: /cvs/drupal/drupal/includes/common.inc,v retrieving revision 1.1080 diff -u -p -r1.1080 common.inc --- includes/common.inc 7 Jan 2010 07:45:03 -0000 1.1080 +++ includes/common.inc 7 Jan 2010 18:54:08 -0000 @@ -3239,21 +3239,10 @@ function drupal_add_css($data = NULL, $o * A string of XHTML CSS tags. */ function drupal_get_css($css = NULL) { - $output = ''; if (!isset($css)) { $css = drupal_add_css(); } - $preprocess_css = (variable_get('preprocess_css', FALSE) && (!defined('MAINTENANCE_MODE') || MAINTENANCE_MODE != 'update')); - $directory = file_directory_path('public'); - $is_writable = is_dir($directory) && is_writable($directory); - - // A dummy query-string is added to filenames, to gain control over - // browser-caching. The string changes on every update or full cache - // flush, forcing browsers to load a new copy of the files, as the - // URL changed. - $query_string = '?' . substr(variable_get('css_js_query_string', '0'), 0, 1); - // Allow modules to alter the css items. drupal_alter('css', $css); @@ -3273,75 +3262,213 @@ function drupal_get_css($css = NULL) { } } - // If CSS preprocessing is off, we still need to output the styles. - // Additionally, go through any remaining styles if CSS preprocessing is on - // and output the non-cached ones. - $css_element = array( - '#tag' => 'link', - '#attributes' => array( - 'type' => 'text/css', - 'rel' => 'stylesheet', - ), - ); - $rendered_css = array(); - $inline_css = ''; - $external_css = ''; - $preprocess_items = array(); + // Loop through each item in the $css array and place it into a group. + $file_groups = array(); + $inline_groups = array(); + $external_groups = array(); foreach ($css as $data => $item) { - // Loop through each of the stylesheets, including them appropriately based - // on their type. switch ($item['type']) { case 'file': - // Depending on whether aggregation is desired, include the file. - if (!$item['preprocess'] || !($is_writable && $preprocess_css)) { - $element = $css_element; - $element['#attributes']['media'] = $item['media']; - $element['#attributes']['href'] = file_create_url($item['data']) . $query_string; - $rendered_css[] = theme('html_tag', array('element' => $element)); + // If the item can't be aggregated, put it in its own group. + if (!$item['preprocess']) { + $file_groups[] = array( + 'preprocess' => $item['preprocess'], + 'media' => $item['media'], + 'type' => $item['type'], + 'items' => array($item), + ); } + // Put all the items for the same media that can be aggregated into the + // same group. else { - $preprocess_items[$item['media']][] = $item; - // Mark the position of the preprocess element, - // it should be at the position of the first preprocessed file. - $rendered_css['preprocess'] = ''; + if (!isset($file_groups[$item['media']])) { + $file_groups[$item['media']] = array( + 'preprocess' => $item['preprocess'], + 'media' => $item['media'], + 'type' => $item['type'], + 'items' => array(), + ); + } + $file_groups[$item['media']]['items'][] = $item; } break; case 'inline': - // Include inline stylesheets. - $inline_css .= drupal_load_stylesheet_content($item['data'], $item['preprocess']); + // Put all inline items into one group. + if (!isset($inline_groups[0])) { + $inline_groups[0] = array( + 'preprocess' => $item['preprocess'], + 'type' => $item['type'], + 'items' => array(), + ); + } + $inline_groups[0]['items'][] = $item; break; case 'external': - // Preprocessing for external CSS files is ignored. - $element = $css_element; - $element['#attributes']['media'] = $item['media']; - $element['#attributes']['href'] = $item['data']; - $external_css .= theme('html_tag', array('element' => $element)); + // Put each external item in its own group. + $external_groups[] = array( + 'preprocess' => $item['preprocess'], + 'media' => $item['media'], + 'type' => $item['type'], + 'items' => array($item), + ); break; } } + $css_groups = array('groups' => array_merge($file_groups, $external_groups, $inline_groups)); - if (!empty($preprocess_items)) { - foreach ($preprocess_items as $media => $items) { - // Prefix filename to prevent blocking by firewalls which reject files - // starting with "ad*". - $element = $css_element; - $element['#attributes']['media'] = $media; - $filename = 'css_' . md5(serialize($items) . $query_string) . '.css'; - $element['#attributes']['href'] = file_create_url(drupal_build_css_cache($items, $filename)); - $rendered_css['preprocess'] .= theme('html_tag', array('element' => $element)); - } - } - // Enclose the inline CSS with the style tag if required. - if (!empty($inline_css)) { - $element = $css_element; - $element['#tag'] = 'style'; - $element['#value'] = $inline_css; - unset($element['#attributes']['rel']); - $inline_css = "\n" . theme('html_tag', array('element' => $element)); + // Allow modules to alter the groupings and register alternate handlers for + // the remaining steps. + drupal_alter('css_groups', $css_groups, $css); + + // Call the preprocess handler to preprocess the groups (for example, to + // aggregate files into fewer files). + $preprocess_handler = (!empty($css_groups['preprocess handler']) && function_exists($css_groups['preprocess handler'])) ? $css_groups['preprocess handler'] : '_drupal_css_preprocess'; + $preprocess_handler($css_groups); + + // Call the markup handler to generate the needed LINK and STYLE tags. + $markup_handler = (!empty($css_groups['markup handler']) && function_exists($css_groups['markup handler'])) ? $css_groups['markup handler'] : '_drupal_css_markup'; + return $markup_handler($css_groups); +} + +/** + * Default CSS group preprocess handler. + * + * @todo Add documentation. + */ +function _drupal_css_preprocess(&$css_groups) { + $preprocess_css = (variable_get('preprocess_css', FALSE) && (!defined('MAINTENANCE_MODE') || MAINTENANCE_MODE != 'update')); + $directory = file_directory_path('public'); + $is_writable = is_dir($directory) && is_writable($directory); + + // A dummy query-string is added to filenames, to gain control over + // browser-caching. The string changes on every update or full cache + // flush, forcing browsers to load a new copy of the files, as the + // URL changed. + $query_string = '?' . substr(variable_get('css_js_query_string', '0'), 0, 1); + + // Loop through each group and prepare the group and its items with + // information that will be needed by the markup handler. + foreach ($css_groups['groups'] as &$group) { + switch ($group['type']) { + case 'file': + // If the group can be aggregated into a single file, do so, and set the + // group's href to the URL of that file. + if ($group['preprocess'] && $preprocess_css && $is_writable) { + $filename = 'css_' . md5(serialize($group['items']) . $query_string) . '.css'; + $group['href'] = file_create_url(drupal_build_css_cache($group['items'], $filename)); + } + // Otherwise, set the href of each item. + else { + foreach ($group['items'] as &$item) { + $item['href'] = file_create_url($item['data']) . $query_string; + } + } + break; + case 'inline': + // Merge all item CSS content into the group's data property. + $group['data'] = ''; + foreach ($group['items'] as $item) { + $group['data'] .= drupal_load_stylesheet_content($item['data'], $item['preprocess']); + } + break; + case 'external': + // External files can't be aggregated. Each item's 'data' property + // already contains the full URL. + $group['preprocess'] = FALSE; + foreach ($group['items'] as &$item) { + $item['href'] = $item['data']; + } + break; + } } +} - // Output all the CSS files with the inline stylesheets showing up last. - return implode($rendered_css) . $external_css . $inline_css; +/** + * Returns a themed representation of all stylesheets that should be attached to the page. + * + * @param $css_groups + * @todo Add description of parameter. + * @return + * A string of XHTML CSS tags. + */ +function _drupal_css_markup($css_groups) { + $output = ''; + + // Defaults for LINK and STYLE elements. + $link_element_defaults = array( + '#tag' => 'link', + '#attributes' => array( + 'type' => 'text/css', + 'rel' => 'stylesheet', + ), + ); + $style_element_defaults = array( + '#tag' => 'style', + '#attributes' => array( + 'type' => 'text/css', + ), + ); + + // Loop through each group. + foreach ($css_groups['groups'] as $group) { + // If the group has an href, it means the preprocess handler successfully + // created an aggregate file, so output a single LINK tag for it. + if (isset($group['href'])) { + $element = $link_element_defaults; + $element['#attributes']['href'] = $group['href']; + $element['#attributes']['media'] = $group['media']; + $output .= theme('html_tag', array('element' => $element)); + } + // If the group contains inline content, output 1 or more STYLE tags with + // that content. + elseif ($group['type'] == 'inline') { + // If the group's data property is set, it contains the merged content and + // can be output with a single STYLE tag. + if (isset($group['data'])) { + $element = $style_element_defaults; + $element['#value'] = $group['data']; + $output .= "\n" . theme('html_tag', array('element' => $element)); + } + // Otherwise, output each item's content in its own STYLE tag. + else { + foreach ($group['items'] as $item) { + $element = $style_element_defaults; + $element['#value'] = $item['data']; + $output .= "\n" . theme('html_tag', array('element' => $element)); + } + } + } + // If the group can be aggregated, but hasn't been (perhaps because the + // site administrator disabled aggregation), output its items in a few + // STYLE tags using @import statements. This helps keep within IE's limit of + // 31. + elseif ($group['preprocess']) { + $import = array(); + foreach ($group['items'] as $item) { + $import[] = '@import url("' . $item['href'] . '");'; + } + while (!empty($import)) { + // IE limits us to no more than 31 @import statements per STYLE tag. + $import_batch = array_slice($import, 0, 31); + $import = array_slice($import, 31); + $element = $style_element_defaults; + $element['#value'] = implode("\n", $import_batch); + $element['#attributes']['media'] = $group['media']; + $output .= theme('html_tag', array('element' => $element)); + } + } + // If the group can't be aggregated, output each item with a LINK tag. + else { + foreach ($group['items'] as $item) { + $element = $link_element_defaults; + $element['#attributes']['href'] = $item['href']; + $element['#attributes']['media'] = $item['media']; + $output .= theme('html_tag', array('element' => $element)); + } + } + } + + return $output; } /** Index: modules/simpletest/tests/common.test =================================================================== RCS file: /cvs/drupal/drupal/modules/simpletest/tests/common.test,v retrieving revision 1.97 diff -u -p -r1.97 common.test --- modules/simpletest/tests/common.test 15 Dec 2009 05:22:05 -0000 1.97 +++ modules/simpletest/tests/common.test 6 Jan 2010 19:10:26 -0000 @@ -558,7 +558,9 @@ class CascadingStylesheetsTestCase exten $css = 'http://example.com/style.css'; drupal_add_css($css, 'external'); $styles = drupal_get_css(); - $this->assertTrue(strpos($styles, 'href="' . $css) > 0, t('Rendering an external CSS file.')); + // Stylesheet URL may be the href of a LINK tag or in an @import statement + // of a STYLE tag. + $this->assertTrue(strpos($styles, 'href="' . $css) > 0 || strpos($styles, '@import url("' . $css . '")') > 0, t('Rendering an external CSS file.')); } /** @@ -631,8 +633,10 @@ class CascadingStylesheetsTestCase exten $styles = drupal_get_css(); - if (preg_match_all('/href="' . preg_quote($GLOBALS['base_url'] . '/', '/') . '([^?]+)\?/', $styles, $matches)) { - $result = $matches[1]; + // Stylesheet URL may be the href of a LINK tag or in an @import statement + // of a STYLE tag. + if (preg_match_all('/(href="|url\(")' . preg_quote($GLOBALS['base_url'] . '/', '/') . '([^?]+)\?/', $styles, $matches)) { + $result = $matches[2]; } else { $result = array();