Commit 1a5c71e2 authored by Dries's avatar Dries
Browse files

- Patch #92877 by mfer, Rob Loach, Damien Tournoud, et al: add numeric weight to drupal_add_css.

parent eb34d299
......@@ -39,6 +39,21 @@
*/
define('SAVED_DELETED', 3);
/**
* The default weight of system CSS files added to the page.
*/
define('CSS_SYSTEM', -100);
/**
* The default weight of CSS files added to the page.
*/
define('CSS_DEFAULT', 0);
/**
* The default weight of theme CSS files added to the page.
*/
define('CSS_THEME', 100);
/**
* The weight of JavaScript libraries, settings or jQuery plugins being
* added to the page.
......@@ -2328,7 +2343,7 @@ function drupal_add_link($attributes) {
* @param $data
* (optional) The stylesheet data to be added, depending on what is passed
* through to the $options['type'] parameter:
* - 'module' or 'theme': The path to the CSS file relative to the base_path(),
* - 'file': The path to the CSS file relative to the base_path(),
* e.g., "modules/devel/devel.css".
*
* Modules should always prefix the names of their CSS files with the
......@@ -2345,15 +2360,28 @@ function drupal_add_link($attributes) {
* directory. This CSS file should contain overrides for properties which
* should be reversed or otherwise different in a right-to-left display.
* - 'inline': A string of CSS that should be placed in the given scope. Note
* that it is better practice to use 'module' or 'theme' stylesheets, rather
* than 'inline' as the CSS would then be aggregated and cached.
* that it is better practice to use 'file' stylesheets, rather than 'inline'
* as the CSS would then be aggregated and cached.
*
* @param $options
* (optional) A string defining the 'type' of CSS that is being added in the
* $data parameter ('module', 'theme' or 'inline'), or an associative array of
* additional options, with the following keys:
* - 'type': The type of stylesheet that is being added. Types are: 'module',
* 'theme' or 'inline'. Defaults to 'module'.
* $data parameter ('file'/'inline'), or an array which can have any or all of
* the following keys:
* - 'type': The type of stylesheet being added. Available options are 'file'
* or 'inline'. Defaults to 'file'.
* - 'weight': The weight of the stylesheet specifies the order in which the
* CSS will appear when presented on the page.
*
* Available constants are:
* - CSS_SYSTEM: Any system-layer CSS.
* - CSS_DEFAULT: Any module-layer CSS.
* - CSS_THEME: Any theme-layer CSS.
*
* If you need to embed a CSS file before any other module's stylesheets,
* for example, you would use CSS_DEFAULT - 1. Note that inline CSS is
* simply appended to the end of the specified scope (region), so they
* always come last.
*
* - 'media': The media type for the stylesheet, e.g., all, print, screen.
* Defaults to 'all'.
* - 'preprocess': Allows the CSS to be aggregated and compressed if the
......@@ -2385,7 +2413,6 @@ function drupal_add_link($attributes) {
*/
function drupal_add_css($data = NULL, $options = NULL) {
$css = &drupal_static(__FUNCTION__, array());
global $language;
// Construct the options, taking the defaults into consideration.
if (isset($options)) {
......@@ -2401,25 +2428,26 @@ function drupal_add_css($data = NULL, $options = NULL) {
// to the browser differently.
if (isset($data)) {
$options += array(
'type' => 'module',
'type' => 'file',
'weight' => CSS_DEFAULT,
'media' => 'all',
'preprocess' => TRUE
'preprocess' => TRUE,
'data' => $data,
);
$media = $options['media'];
$type = $options['type'];
// This check is necessary to ensure proper cascading of styles and is faster than an asort().
if (!isset($css[$media])) {
$css[$media] = array('module' => array(), 'theme' => array(), 'inline' => array());
}
$css[$media][$type][$data] = $options['preprocess'];
// Always add a tiny value to the weight, to conserve the insertion order.
$options['weight'] += count($css) / 1000;
// If the current language is RTL, add the CSS file with RTL overrides.
if ($type != 'inline' && $language->direction == LANGUAGE_RTL) {
$rtl_path = str_replace('.css', '-rtl.css', $data);
if (file_exists($rtl_path)) {
$css[$media][$type][$rtl_path] = $options['preprocess'];
}
// Add the data to the CSS array depending on the type.
switch ($options['type']) {
case 'file':
$css[$data] = $options;
break;
case 'inline':
// For inline stylesheets, we don't want to use the $data as the array
// key as $data could be a very long string of CSS.
$css[] = $options;
break;
}
}
......@@ -2453,9 +2481,6 @@ function drupal_get_css($css = NULL) {
if (!isset($css)) {
$css = drupal_add_css();
}
$no_module_preprocess = '';
$no_theme_preprocess = '';
$no_inline_preprocess = '';
$preprocess_css = (variable_get('preprocess_css', FALSE) && (!defined('MAINTENANCE_MODE') || MAINTENANCE_MODE != 'update'));
$directory = file_directory_path();
......@@ -2467,76 +2492,82 @@ function drupal_get_css($css = NULL) {
// URL changed.
$query_string = '?' . substr(variable_get('css_js_query_string', '0'), 0, 1);
foreach ($css as $media => $types) {
// 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.
foreach ($types as $type => $information) {
if ($type == 'module') {
// Setup theme overrides for module styles.
$theme_styles = array();
foreach (array_keys($css[$media]['theme']) as $theme_style) {
$theme_styles[] = basename($theme_style);
}
// Allow modules to alter the css items.
drupal_alter('css', $css);
// Sort css items according to their weights.
uasort($css, 'drupal_sort_weight');
// Remove the overriden CSS files. Later CSS files override former ones.
$previous_item = array();
foreach ($css as $key => $item) {
if ($item['type'] == 'file') {
$basename = basename($item['data']);
if (isset($previous_item[$basename])) {
// Remove the previous item that shared the same base name.
unset($css[$previous_item[$basename]]);
}
foreach ($types[$type] as $data => $preprocess) {
// If the theme supplies its own style using the name of the module style, skip its inclusion.
// This includes any RTL styles associated with its main LTR counterpart.
if ($type == 'module' && in_array(str_replace('-rtl.css', '.css', basename($data)), $theme_styles)) {
// Unset the file to prevent its inclusion when CSS aggregation is enabled.
unset($types[$type][$data]);
continue;
}
// Include inline stylesheets.
if ($type == 'inline') {
$no_inline_preprocess .= drupal_load_stylesheet_content($data, $preprocess);
$previous_item[$basename] = $key;
}
}
// 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.
$rendered_css = array();
$inline_css = '';
$preprocess_items = 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)) {
$rendered_css[] = '<link type="text/css" rel="stylesheet" media="' . $item['media'] . '" href="' . base_path() . $item['data'] . $query_string . '" />';
}
// Only include the stylesheet if it exists.
elseif (file_exists($data)) {
if (!$preprocess || !($is_writable && $preprocess_css)) {
// If a CSS file is not to be preprocessed and it's a module CSS file, it needs to *always* appear at the *top*,
// regardless of whether preprocessing is on or off.
if (!$preprocess && $type == 'module') {
$no_module_preprocess .= '<link type="text/css" rel="stylesheet" media="' . $media . '" href="' . base_path() . $data . $query_string . '" />' . "\n";
}
// If a CSS file is not to be preprocessed and it's a theme CSS file, it needs to *always* appear at the *bottom*,
// regardless of whether preprocessing is on or off.
elseif (!$preprocess && $type == 'theme') {
$no_theme_preprocess .= '<link type="text/css" rel="stylesheet" media="' . $media . '" href="' . base_path() . $data . $query_string . '" />' . "\n";
}
else {
$output .= '<link type="text/css" rel="stylesheet" media="' . $media . '" href="' . base_path() . $data . $query_string . '" />' . "\n";
}
}
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'] = '';
}
}
break;
case 'inline':
// Include inline stylesheets.
$inline_css .= drupal_load_stylesheet_content($item['data'], $item['preprocess']);
break;
}
}
if ($is_writable && $preprocess_css) {
if (!empty($preprocess_items)) {
foreach ($preprocess_items as $media => $items) {
// Prefix filename to prevent blocking by firewalls which reject files
// starting with "ad*".
$filename = 'css_' . md5(serialize($types) . $query_string) . '.css';
$preprocess_file = drupal_build_css_cache($types, $filename);
$output .= '<link type="text/css" rel="stylesheet" media="' . $media . '" href="' . base_path() . $preprocess_file . '" />' . "\n";
$filename = 'css_' . md5(serialize($items) . $query_string) . '.css';
$preprocess_file = drupal_build_css_cache($items, $filename);
$rendered_css['preprocess'] .= '<link type="text/css" rel="stylesheet" media="' . $media . '" href="' . base_path() . $preprocess_file . '" />' . "\n";
}
}
if (!empty($no_inline_preprocess)) {
$no_inline_preprocess = '<style type="text/css">' . $no_inline_preprocess . '</style>';
// Enclose the inline CSS with the style tag if required.
if (!empty($inline_css)) {
$inline_css = "\n" . '<style type="text/css">' . $inline_css .'</style>';
}
return $no_module_preprocess . $output . $no_theme_preprocess . $no_inline_preprocess;
// Output all the CSS files with the inline stylesheets showing up last.
return implode("\n", $rendered_css) . $inline_css;
}
/**
* Aggregate and optimize CSS files, putting them in the files directory.
*
* @param $types
* An array of types of CSS files (e.g., screen, print) to aggregate and
* compress into one file.
* @param $css
* An array of CSS files to aggregate and compress into one file.
* @param $filename
* The name of the aggregate CSS file.
* @return
* The name of the CSS file.
*/
function drupal_build_css_cache($types, $filename) {
function drupal_build_css_cache($css, $filename) {
$data = '';
// Create the css/ within the files folder.
......@@ -2545,19 +2576,15 @@ function drupal_build_css_cache($types, $filename) {
if (!file_exists($csspath . '/' . $filename)) {
// Build aggregate CSS file.
foreach ($types as $type => $css) {
// Only 'module' or 'theme' stylesheets can be aggregated.
if ($type == 'module' || $type == 'theme') {
foreach ($css as $stylesheet => $cache) {
if ($cache) {
$contents = drupal_load_stylesheet($stylesheet, TRUE);
// Return the path to where this CSS file originated from.
$base = base_path() . dirname($stylesheet) . '/';
_drupal_build_css_path(NULL, $base);
// Prefix all paths within this CSS file, ignoring external and absolute paths.
$data .= preg_replace_callback('/url\([\'"]?(?![a-z]+:|\/+)([^\'")]+)[\'"]?\)/i', '_drupal_build_css_path', $contents);
}
}
foreach ($css as $stylesheet) {
// Only 'file' stylesheets can be aggregated.
if ($stylesheet['type'] == 'file') {
$contents = drupal_load_stylesheet($stylesheet['data'], TRUE);
// Return the path to where this CSS file originated from.
$base = base_path() . dirname($stylesheet['data']) . '/';
_drupal_build_css_path(NULL, $base);
// Prefix all paths within this CSS file, ignoring external and absolute paths.
$data .= preg_replace_callback('/url\([\'"]?(?![a-z]+:|\/+)([^\'")]+)[\'"]?\)/i', '_drupal_build_css_path', $contents);
}
}
......
......@@ -132,7 +132,7 @@ function _drupal_theme_initialize($theme, $base_theme = array(), $registry_callb
// And now add the stylesheets properly
foreach ($final_stylesheets as $media => $stylesheets) {
foreach ($stylesheets as $stylesheet) {
drupal_add_css($stylesheet, array('type' => 'theme', 'media' => $media));
drupal_add_css($stylesheet, array('weight' => CSS_THEME, 'media' => $media));
}
}
......
......@@ -583,6 +583,33 @@ function locale_js_alter(&$javascript) {
}
}
/*
* Implement hook_css_alter().
*
* This function checks all CSS files currently added via drupal_add_css() and
* and checks to see if a related right to left CSS file should be included.
*/
function locale_css_alter(&$css) {
global $language;
// If the current language is RTL, add the CSS file with the RTL overrides.
if ($language->direction == LANGUAGE_RTL) {
foreach ($css as $data => $item) {
// Only provide RTL overrides for files.
if ($item['type'] == 'file') {
$rtl_path = str_replace('.css', '-rtl.css', $item['data']);
if (file_exists($rtl_path) && !isset($css[$rtl_path])) {
// Replicate the same item, but with the RTL path and a little larger
// weight so that it appears directly after the original CSS file.
$item['data'] = $rtl_path;
$item['weight'] += 0.01;
$css[$rtl_path] = $item;
}
}
}
}
}
// ---------------------------------------------------------------------------------
// Language switcher block
......
......@@ -203,7 +203,7 @@ class CascadingStylesheetsTestCase extends DrupalWebTestCase {
}
function setUp() {
parent::setUp('php');
parent::setUp('php', 'locale');
// Reset drupal_add_css() before each test.
drupal_static_reset('drupal_add_css');
}
......@@ -221,7 +221,7 @@ class CascadingStylesheetsTestCase extends DrupalWebTestCase {
function testAddFile() {
$path = drupal_get_path('module', 'simpletest') . '/simpletest.css';
$css = drupal_add_css($path);
$this->assertEqual($css['all']['module'][$path], TRUE, t('Adding a CSS file caches it properly.'));
$this->assertEqual($css[$path]['data'], $path, t('Adding a CSS file caches it properly.'));
}
/**
......@@ -249,7 +249,7 @@ class CascadingStylesheetsTestCase extends DrupalWebTestCase {
$css_preprocessed = '<style type="text/css">' . drupal_load_stylesheet_content($css, TRUE) . '</style>';
drupal_add_css($css, 'inline');
$css = drupal_get_css();
$this->assertEqual($css, $css_preprocessed, t('Rendering preprocessed inline CSS adds it to the page.'));
$this->assertEqual($css, "\n" . $css_preprocessed, t('Rendering preprocessed inline CSS adds it to the page.'));
}
/**
......@@ -280,6 +280,74 @@ class CascadingStylesheetsTestCase extends DrupalWebTestCase {
$this->drupalGet('node/' . $node->nid);
$this->assertRaw($expected, t('Inline stylesheets appear in the full page rendering.'));
}
/**
* Test CSS ordering.
*/
function testRenderOrder() {
// A module CSS file.
drupal_add_css(drupal_get_path('module', 'simpletest') . '/simpletest.css');
// A few system CSS files, ordered in a strange way.
$system_path = drupal_get_path('module', 'system');
drupal_add_css($system_path . '/defaults.css', array('weight' => CSS_SYSTEM));
drupal_add_css($system_path . '/system.css', array('weight' => CSS_SYSTEM - 10));
drupal_add_css($system_path . '/system-menus.css', array('weight' => CSS_SYSTEM));
$expected = array(
$system_path . '/system.css',
$system_path . '/defaults.css',
$system_path . '/system-menus.css',
drupal_get_path('module', 'simpletest') . '/simpletest.css',
);
$css = drupal_get_css();
if (preg_match_all('/href="' . preg_quote(base_path(), '/') . '([^?]+)\?/', $css, $matches)) {
$result = $matches[1];
}
else {
$result = array();
}
$this->assertIdentical($result, $expected, t('The CSS files are in the expected order.'));
}
/**
* Test CSS override.
*/
function testRenderOverride() {
drupal_add_css(drupal_get_path('module', 'system') . '/system.css');
drupal_add_css(drupal_get_path('module', 'simpletest') . '/tests/system.css');
// The dummy stylesheet should be the only one included.
$css = drupal_get_css();
$this->assert(strpos($css, drupal_get_path('module', 'simpletest') . '/tests/system.css') !== FALSE, t('The overriding CSS file is output.'));
$this->assert(strpos($css, drupal_get_path('module', 'system') . '/system.css') === FALSE, t('The overriden CSS file is not output.'));
drupal_add_css(drupal_get_path('module', 'simpletest') . '/tests/system.css');
drupal_add_css(drupal_get_path('module', 'system') . '/system.css');
// The standard stylesheet should be the only one included.
$css = drupal_get_css();
$this->assert(strpos($css, drupal_get_path('module', 'system') . '/system.css') !== FALSE, t('The overriding CSS file is output.'));
$this->assert(strpos($css, drupal_get_path('module', 'simpletest') . '/tests/system.css') === FALSE, t('The overriden CSS file is not output.'));
}
/**
* Tests Locale module's CSS Alter to include RTL overrides.
*/
function testAlter() {
// Switch the language to a right to left language and add system.css.
global $language;
$language->direction = LANGUAGE_RTL;
drupal_add_css(drupal_get_path('module', 'system') . '/system.css');
// Check to see if system-rtl.css was also added.
$css = drupal_get_css();
$this->assert(strpos($css, drupal_get_path('module', 'system') . '/system-rtl.css') !== FALSE, t('CSS is alterable as right to left overrides are added.'));
// Change the language back to left to right.
$language->direction = LANGUAGE_LTR;
}
}
/**
......
......@@ -280,6 +280,19 @@ function hook_library_alter(&$libraries, $module) {
}
}
/**
* Alter CSS files before they are output on the page.
*
* @param $css
* An array of all CSS items (files and inline CSS) being requested on the page.
* @see drupal_add_css()
* @see drupal_get_css()
*/
function hook_css_alter(&$css) {
// Remove defaults.css file.
unset($css[drupal_get_path('module', 'system') . '/defaults.css']);
}
/**
* Perform alterations before a page is rendered.
*
......
......@@ -1327,13 +1327,13 @@ function system_init() {
if (arg(0) == 'admin' || (variable_get('node_admin_theme', '0') && arg(0) == 'node' && (arg(1) == 'add' || arg(2) == 'edit'))) {
global $custom_theme;
$custom_theme = variable_get('admin_theme', 0);
drupal_add_css(drupal_get_path('module', 'system') . '/admin.css');
drupal_add_css(drupal_get_path('module', 'system') . '/admin.css', array('weight' => CSS_SYSTEM));
}
// Add the CSS for this module.
drupal_add_css(drupal_get_path('module', 'system') . '/defaults.css');
drupal_add_css(drupal_get_path('module', 'system') . '/system.css');
drupal_add_css(drupal_get_path('module', 'system') . '/system-menus.css');
drupal_add_css(drupal_get_path('module', 'system') . '/defaults.css', array('weight' => CSS_SYSTEM));
drupal_add_css(drupal_get_path('module', 'system') . '/system.css', array('weight' => CSS_SYSTEM));
drupal_add_css(drupal_get_path('module', 'system') . '/system-menus.css', array('weight' => CSS_SYSTEM));
// Ignore slave database servers for this request.
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment