summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAngie Byron <webchick@24967.no-reply.drupal.org>2009-03-30 05:13:45 +0000
committerAngie Byron <webchick@24967.no-reply.drupal.org>2009-03-30 05:13:45 +0000
commita0cca9a472b7764f54ad692c8e7943278f09a0be (patch)
treefc54501632883cd88221efea2ba1eabf179d294b
parent39c3344ac6bb252d4f5f9aa2afd13c2cf989562d (diff)
downloadbrdo-a0cca9a472b7764f54ad692c8e7943278f09a0be.tar.gz
brdo-a0cca9a472b7764f54ad692c8e7943278f09a0be.tar.bz2
#259368 by Rob Loach, mfer, and sun: Allow drupal_add_css() to add/aggregate inline CSS.
-rw-r--r--includes/common.inc218
-rw-r--r--modules/simpletest/tests/common.test46
2 files changed, 168 insertions, 96 deletions
diff --git a/includes/common.inc b/includes/common.inc
index d80aefeee..b36c4b2c4 100644
--- a/includes/common.inc
+++ b/includes/common.inc
@@ -1976,66 +1976,69 @@ function drupal_add_link($attributes) {
}
/**
- * Adds a CSS file to the stylesheet queue.
+ * Adds a cascading stylesheet to the stylesheet queue.
+ *
+ * @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(),
+ * e.g., "modules/devel/devel.css".
+ *
+ * Modules should always prefix the names of their CSS files with the
+ * module name, for example: system-menus.css rather than simply menus.css.
+ * Themes can override module-supplied CSS files based on their filenames,
+ * and this prefixing helps prevent confusing name collisions for theme
+ * developers. See drupal_get_css where the overrides are performed.
+ *
+ * If the direction of the current language is right-to-left (Hebrew,
+ * Arabic, etc.), the function will also look for an RTL CSS file and append
+ * it to the list. The name of this file should have an '-rtl.css' suffix.
+ * For example a CSS file called 'mymodule-name.css' will have a
+ * 'mymodule-name-rtl.css' file added to the list, if exists in the same
+ * 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.
+ * - 'reset': Any previously added CSS will be reset and $data will be ignored.
*
- * @param $path
- * (optional) 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 module
- * name, for example: system-menus.css rather than simply menus.css. Themes
- * can override module-supplied CSS files based on their filenames, and this
- * prefixing helps prevent confusing name collisions for theme developers.
- * See drupal_get_css where the overrides are performed.
- *
- * If the direction of the current language is right-to-left (Hebrew,
- * Arabic, etc.), the function will also look for an RTL CSS file and append
- * it to the list. The name of this file should have an '-rtl.css' suffix.
- * For example a CSS file called 'name.css' will have a 'name-rtl.css'
- * file added to the list, if exists in the same directory. This CSS file
- * should contain overrides for properties which should be reversed or
- * otherwise different in a right-to-left display.
- *
- * Note that if $options or $options['type'] is 'reset', then $path will be
- * ignored.
* @param $options
- * (optional) A string defining the type of CSS that is being added in the
- * $path parameter ('module' or 'theme'), or an associative array of
+ * (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 'reset'. Defaults to 'module'. If the type is 'reset',
- * then the CSS will be reset, ignoring $path and other $options.
- * - 'media'
- * The media type for the stylesheet, e.g., all, print, screen. Defaults
- * to 'all'.
- * - 'preprocess':
- * Allow this CSS file to be aggregated and compressed if the Optimize
- * CSS feature has been turned on under the performance section. Defaults
- * to TRUE.
- *
- * What does this actually mean?
- * CSS preprocessing is the process of aggregating a bunch of separate CSS
- * files into one file that is then compressed by removing all extraneous
- * white space.
- *
- * The reason for merging the CSS files is outlined quite thoroughly here:
- * http://www.die.net/musings/page_load_time/
- * "Load fewer external objects. Due to request overhead, one bigger file
- * just loads faster than two smaller ones half its size."
- *
- * However, you should *not* preprocess every file as this can lead to
- * redundant caches. You should set $preprocess = FALSE when your styles
- * are only used rarely on the site. This could be a special admin page,
- * the homepage, or a handful of pages that does not represent the
- * majority of the pages on your site.
- *
- * Typical candidates for caching are for example styles for nodes across
- * the site, or used in the theme.
+ * - 'type': The type of stylesheet that is being added. Types are: 'module',
+ * 'theme', 'inline' or 'reset'. Defaults to 'module'. If the type is
+ * 'reset', then the CSS will be reset, ignoring $path and other $options.
+ * - '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
+ * Optimize CSS feature has been turned on under the performance section.
+ * Defaults to TRUE.
+ *
+ * What does this actually mean?
+ * CSS preprocessing is the process of aggregating a bunch of separate CSS
+ * files into one file that is then compressed by removing all extraneous
+ * white space. Note that preprocessed inline stylesheets will not be
+ * aggregated into this single file, instead it will just be compressed
+ * when being output on the page.
+ *
+ * The reason for merging the CSS files is outlined quite thoroughly here:
+ * http://www.die.net/musings/page_load_time/
+ * "Load fewer external objects. Due to request overhead, one bigger file
+ * just loads faster than two smaller ones half its size."
+ *
+ * However, you should *not* preprocess every file as this can lead to
+ * redundant caches. You should set $preprocess = FALSE when your styles
+ * are only used rarely on the site. This could be a special admin page,
+ * the homepage, or a handful of pages that does not represent the
+ * majority of the pages on your site.
+ *
+ * Typical candidates for caching are for example styles for nodes across
+ * the site, or used in the theme.
* @return
- * An array of CSS files.
+ * An array of queued cascading stylesheets.
*/
-function drupal_add_css($path = NULL, $options = NULL) {
+function drupal_add_css($data = NULL, $options = NULL) {
static $css = array();
global $language;
@@ -2056,7 +2059,7 @@ function drupal_add_css($path = NULL, $options = NULL) {
// Create an array of CSS files for each media type first, since each type needs to be served
// to the browser differently.
- if (isset($path)) {
+ if (isset($data)) {
$options += array(
'type' => 'module',
'media' => 'all',
@@ -2067,13 +2070,13 @@ function drupal_add_css($path = NULL, $options = NULL) {
// 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());
+ $css[$media] = array('module' => array(), 'theme' => array(), 'inline' => array());
}
- $css[$media][$type][$path] = $options['preprocess'];
+ $css[$media][$type][$data] = $options['preprocess'];
// If the current language is RTL, add the CSS file with RTL overrides.
- if ($language->direction == LANGUAGE_RTL) {
- $rtl_path = str_replace('.css', '-rtl.css', $path);
+ 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'];
}
@@ -2112,6 +2115,7 @@ function drupal_get_css($css = NULL) {
}
$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();
@@ -2126,7 +2130,7 @@ function drupal_get_css($css = NULL) {
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 => $files) {
+ foreach ($types as $type => $information) {
if ($type == 'module') {
// Setup theme overrides for module styles.
$theme_styles = array();
@@ -2134,29 +2138,33 @@ function drupal_get_css($css = NULL) {
$theme_styles[] = basename($theme_style);
}
}
- foreach ($types[$type] as $file => $preprocess) {
+ 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($file)), $theme_styles)) {
+ 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][$file]);
+ unset($types[$type][$data]);
continue;
}
+ // Include inline stylesheets.
+ if ($type == 'inline') {
+ $no_inline_preprocess .= drupal_load_stylesheet_content($data, $preprocess);
+ }
// Only include the stylesheet if it exists.
- if (file_exists($file)) {
+ 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() . $file . $query_string . '" />' . "\n";
+ $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() . $file . $query_string . '" />' . "\n";
+ $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() . $file . $query_string . '" />' . "\n";
+ $output .= '<link type="text/css" rel="stylesheet" media="' . $media . '" href="' . base_path() . $data . $query_string . '" />' . "\n";
}
}
}
@@ -2169,8 +2177,10 @@ function drupal_get_css($css = NULL) {
$output .= '<link type="text/css" rel="stylesheet" media="' . $media . '" href="' . base_path() . $preprocess_file . '" />' . "\n";
}
}
-
- return $no_module_preprocess . $output . $no_theme_preprocess;
+ if (!empty($no_inline_preprocess)) {
+ $no_inline_preprocess = '<style type="text/css">' . $no_inline_preprocess . '</style>';
+ }
+ return $no_module_preprocess . $output . $no_theme_preprocess . $no_inline_preprocess;
}
/**
@@ -2193,15 +2203,18 @@ function drupal_build_css_cache($types, $filename) {
if (!file_exists($csspath . '/' . $filename)) {
// Build aggregate CSS file.
- foreach ($types as $type) {
- foreach ($type as $file => $cache) {
- if ($cache) {
- $contents = drupal_load_stylesheet($file, TRUE);
- // Return the path to where this CSS file originated from.
- $base = base_path() . dirname($file) . '/';
- _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 ($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);
+ }
}
}
}
@@ -2257,7 +2270,7 @@ function _drupal_build_css_path($matches, $base = NULL) {
* @param $optimize
* Defines if CSS contents should be compressed or not.
* @return
- * Contents of the stylesheet including the imported stylesheets.
+ * Contents of the stylesheet, including any resolved @import commands.
*/
function drupal_load_stylesheet($file, $optimize = NULL) {
static $_optimize;
@@ -2275,20 +2288,8 @@ function drupal_load_stylesheet($file, $optimize = NULL) {
$cwd = getcwd();
chdir(dirname($file));
- // Replaces @import commands with the actual stylesheet content.
- // This happens recursively but omits external files.
- $contents = preg_replace_callback('/@import\s*(?:url\()?[\'"]?(?![a-z]+:)([^\'"\()]+)[\'"]?\)?;/', '_drupal_load_stylesheet', $contents);
- // Remove multiple charset declarations for standards compliance (and fixing Safari problems).
- $contents = preg_replace('/^@charset\s+[\'"](\S*)\b[\'"];/i', '', $contents);
-
- if ($_optimize) {
- // Perform some safe CSS optimizations.
- $contents = preg_replace('<
- \s*([@{}:;,]|\)\s|\s\()\s* | # Remove whitespace around separators, but keep space around parentheses.
- /\*([^*\\\\]|\*(?!/))+\*/ | # Remove comments that are not CSS hacks.
- [\n\r] # Remove line breaks.
- >x', '\1', $contents);
- }
+ // Process the stylesheet.
+ $contents = drupal_load_stylesheet_content($contents, $_optimize);
// Change back directory.
chdir($cwd);
@@ -2298,6 +2299,35 @@ function drupal_load_stylesheet($file, $optimize = NULL) {
}
/**
+ * Process the contents of a stylesheet for aggregation.
+ *
+ * @param $contents
+ * The contents of the stylesheet.
+ * @param $optimize
+ * (optional) Boolean whether CSS contents should be minified. Defaults to
+ * FALSE.
+ * @return
+ * Contents of the stylesheet including the imported stylesheets.
+ */
+function drupal_load_stylesheet_content($contents, $optimize = FALSE) {
+ // Replaces @import commands with the actual stylesheet content.
+ // This happens recursively but omits external files.
+ $contents = preg_replace_callback('/@import\s*(?:url\()?[\'"]?(?![a-z]+:)([^\'"\()]+)[\'"]?\)?;/', '_drupal_load_stylesheet', $contents);
+ // Remove multiple charset declarations for standards compliance (and fixing Safari problems).
+ $contents = preg_replace('/^@charset\s+[\'"](\S*)\b[\'"];/i', '', $contents);
+
+ if ($optimize) {
+ // Perform some safe CSS optimizations.
+ $contents = preg_replace('<
+ \s*([@{}:;,]|\)\s|\s\()\s* | # Remove whitespace around separators, but keep space around parentheses.
+ /\*([^*\\\\]|\*(?!/))+\*/ | # Remove comments that are not CSS hacks.
+ [\n\r] # Remove line breaks.
+ >x', '\1', $contents);
+ }
+ return $contents;
+}
+
+/**
* Loads stylesheets recursively and returns contents with corrected paths.
*
* This function is used for recursive loading of stylesheets and
diff --git a/modules/simpletest/tests/common.test b/modules/simpletest/tests/common.test
index 0f3fd2cbb..2fa9b1687 100644
--- a/modules/simpletest/tests/common.test
+++ b/modules/simpletest/tests/common.test
@@ -188,12 +188,12 @@ class CascadingStylesheetsTestCase extends DrupalWebTestCase {
return array(
'name' => t('Cascading stylesheets'),
'description' => t('Tests adding various cascading stylesheets to the page.'),
- 'group' => t('System')
+ 'group' => t('System'),
);
}
function setUp() {
- parent::setUp();
+ parent::setUp('php');
// Reset drupal_add_css() before each test.
drupal_add_css(NULL, 'reset');
}
@@ -230,6 +230,48 @@ class CascadingStylesheetsTestCase extends DrupalWebTestCase {
drupal_add_css($css);
$this->assertTrue(strpos(drupal_get_css(), $css) > 0, t('Rendered CSS includes the added stylesheet.'));
}
+
+ /**
+ * Tests rendering inline stylesheets with preprocessing on.
+ */
+ function testRenderInlinePreprocess() {
+ $css = 'body { padding: 0px; }';
+ $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.'));
+ }
+
+ /**
+ * Tests rendering inline stylesheets with preprocessing off.
+ */
+ function testRenderInlineNoPreprocess() {
+ $css = 'body { padding: 0px; }';
+ drupal_add_css($css, array('type' => 'inline', 'preprocess' => FALSE));
+ $this->assertTrue(strpos(drupal_get_css(), $css) > 0, t('Rendering non-preprocessed inline CSS adds it to the page.'));
+ }
+
+ /**
+ * Tests rendering inline stylesheets through a full page request.
+ */
+ function testRenderInlineFullPage() {
+ $css = 'body { padding: 0px; }';
+ $compressed_css = '<style type="text/css">' . drupal_load_stylesheet_content($css, TRUE) . '</style>';
+
+ // Create a node, using the PHP filter that tests drupal_add_css().
+ $settings = array(
+ 'type' => 'page',
+ 'format' => 3, // PHP filter.
+ 'body_format' => 3,
+ 'body' => t('This tests the inline CSS!') . "<?php drupal_add_css('$css', 'inline'); ?>",
+ 'promote' => 1,
+ );
+ $node = $this->drupalCreateNode($settings);
+
+ // Fetch the page.
+ $this->drupalGet('node/' . $node->nid);
+ $this->assertRaw($compressed_css, t('Inline stylesheets appear in the full page rendering.'));
+ }
}
/**