If you have a look at drupal_add_js, you notice that you're able to specify a $type of 'inline'. This means that instead of requiring yourself to stick your Javascript into a file, you can have it appear inline on the page in its own < script > tag. Having this capability for drupal_add_css would be very helpful when you want to display dynamic CSS:

$color = variable_get('mymodule_color', '#000000');
$css = "p {background-color: $color }";
drupal_add_css($css, 'inline');

As you can see, here we're setting the background color of every p tag to a user chosen color (in this case, black) from the variable table. Although this is a horrible example of what you could do with having include CSS capabilities with drupal_add_css, I believe it gets the point across.

This doesn't have to be actual inline CSS either, as we could just add it to the end of the already aggregated CSS file.

Comments

robloach’s picture

Many modules would benefit from this, including CSS Injector.

I see inline CSS through drupal_add_css working exactly how it does with drupal_add_js. When aggregation is enabled, the CSS would be added to the aggregated CSS file. When aggregation is disabled, the CSS would be added directly to the page that's being rendered.

robloach’s picture

Status: Active » Needs review
StatusFileSize
new10.88 KB

First hack at it to get this issue going. To test, do the following:

  1. Install Drupal HEAD
  2. Apply the patch
  3. Make sure CSS optimization is disabled in admin/settings/performance
  4. Visit admin/build/modules and enable the PHP Input Filter
  5. Create a new page node with the following content using the PHP input filter:
$color = variable_get('mymodule_color', '#999');
$css = "p {background-color: $color }";
drupal_add_css($css, 'inline');

echo '<p>This text should appear with a greyish background.</p>';
  1. Make sure you have the PHP tags in there when you save the node and view it. The text should have a grey background.
  2. View the source to see that it adds the CSS code using a < style> tag.
  3. Now go back to the admin/settings/performace page and enable the CSS optimizations.
  4. Visit the test node you just created and see the text still grey.
  5. Take a look at the source and notice that instead of it using a < style> tag, it adds it to the end of the aggregated CSS file.
  6. Dance and rejoice with the new drupal_add_css that can add inline dynamic CSS.
robloach’s picture

StatusFileSize
new10.7 KB

Removal of some debugging information.

a_c_m’s picture

Ugly hack/work around, which i'm sure is the "wrong" way to do it, but it works for me.

$color = variable_get('mymodule_color', '#999');
$css = "p {background-color: $color }";

drupal_set_html_head(drupal_get_html_head() . "<style type="text/css">$css</script>");


echo '<p>This text should appear with a greyish background.</p>';
guardian’s picture

drupal_set_html_head imho has a bad name since it's really literally "Add output to the head tag of the HTML page."

what you want is drupal_set_html_head("<style type="text/css">$css</script>");

robloach’s picture

drupal_set_html_head doesn't add the CSS to the aggregated CSS file on the page, and is a horrible hack for adding straight CSS. If you use this hack eight different times to add single lines of CSS, then you end up with eight different < style > tags.

Mind giving #3 a review? The patch still applies cleanly.

senpai’s picture

Marking for a review sometime this weekend.

senpai’s picture

Assigned: Unassigned » senpai
robloach’s picture

Component: base system » theme system
Status: Needs review » Needs work

Needs update now that $options went in.

mfer’s picture

subscribe.

robloach’s picture

Status: Needs work » Needs review
StatusFileSize
new30.18 KB
new12.78 KB

Sticking the following in a node with CSS Optimization/Aggregation either on or off colours the special text red with a blue imaged backgrounds.

$path = base_path() . drupal_get_path('theme', 'garland');
drupal_add_css("#greeting { color:red; background:#EDF5FA url('$path/images/body.png') repeat-x scroll 50% 0pt;}", 'inline');
drupal_add_css("#response { color:green; background:#444 url('$path/logo.png') 0pt;}", 'inline');
echo '<div id="greeting">Hello...</div><div id="response">Hey, what is up!</div>';

Attached is both the patch and a screenshot of what it looks like with aggregation both on and off.

robloach’s picture

Assigned: senpai » Unassigned
StatusFileSize
new13.43 KB

Forgot to preprocess/compress the inline scripts when CSS Optimization is off.

robloach’s picture

Status: Needs review » Needs work

Whoops, @import's in .css files broke....

robloach’s picture

Status: Needs work » Needs review
StatusFileSize
new13.46 KB

There we go. Forgot to change to the stylesheet's directory.

mfer’s picture

Status: Needs review » Needs work

This looks to work but, there are no tests against this functionality to test that it works. Can we get a test?

When CSS preprocessing is turned off the added css is inline. When CSS preprocessing is turned on the added CSS is appended to the preprocessed file. Is this the intent?

senpai’s picture

That shouldn't be the case, no.

Also, what should we test?

robloach’s picture

Preprocessed 'inline' CSS shouldn't be added to the aggregated file?

robloach’s picture

Status: Needs work » Needs review
StatusFileSize
new13.35 KB
  • The inline stylesheets now arn't added to the aggregated CSS file.
  • Added a couple tests for the inline stylesheets

Sticking the following in a node with the PHP filter on and then hitting refresh a bunch of times is a good way to manually test it:

$hellocolor = rand(100, 999);
$goodbyecolor = rand(100, 999);
drupal_add_css("/* Preprocess is on! */ #greeting { color:#$hellocolor; }", 'inline');
drupal_add_css("/* Preprocess is off! */ #response { color:#$goodbyecolor; }", array('type' => 'inline','preprocess' => FALSE));
echo '<div id="greeting">Hello...</div><div id="response">Goodbye!</div>';
dawehner’s picture

Status: Needs review » Needs work

this patch seems to work here

there are some wrong spaces see

   if (file_exists($file)) {
     // Load the local CSS stylesheet.
     $contents = file_get_contents($file);
-
+    
     // Change to the current stylesheet's directory.
     $cwd = getcwd();
     chdir(dirname($file));
 
-    // Replaces @import commands with the actual stylesheet content.

i wonders why you wrote "# Added a couple tests for the inline stylesheets" but there is nothing in the patch file

robloach’s picture

Assigned: Unassigned » robloach

Ah damn, I created the tests, but forgot to send them into the patch. I'll hopefully have time to give this another hack this week sometime.

blueyed’s picture

What about letting the "preprocess" option default to TRUE for files and FALSE for inline code?
The reason for it is that "inline" style is probably more often random (and causes too much cache poisoning therefore).
Is that too much black magic?

You appear to use $data for both the outer and inner foreach loop now ("foreach ($types as $type => $data)" and "foreach ($types[$type] as $data => $preprocess)").
Since the first $data does not get used (but $types[$type] instead), it does no harm, but causes confusion and since it's not being used, I suggest changing it to "foreach (array_keys($types) as $type)".

robloach’s picture

Status: Needs work » Needs review
StatusFileSize
new15.03 KB

When preprocessing is on, it will compress the inline CSS. When preprocessing is off, it won't compress the CSS. It shouldn't add it to the aggregated CSS file because the inline CSS should be able to change on every request made.

  • Adds tests to check inline CSS when preprocessing is both on and off.
  • Fixes previous spacing issues.
  • Doesn't use $data for nested loops

@blueyed, the ugly $data loops will be fixed when we hit #92877: Add numeric weight to drupal_add_css.

mfer’s picture

Status: Needs review » Needs work

In one of the test it says

t("Rendering preprocessed inline CSS adds it to the page. '$css_preprocessed'")

I don't believe $css_preprocessed should be listed here.

Other than that, it looks good to me.

robloach’s picture

Status: Needs work » Needs review
StatusFileSize
new15.01 KB

Whoops, forgot to get rid of some debugging information. Thanks for eagle eyeing that.

AltaVida’s picture

Subscribe

mfer’s picture

Status: Needs review » Reviewed & tested by the community

Looks good, tests pass, manually tested and that passes as well.

--
Matt
www.mattfarina.com
www.innovatingtomorrow.net
www.geeksandgod.com
www.superaveragepodcast.com

Status: Reviewed & tested by the community » Needs work

The last submitted patch failed testing.

robloach’s picture

Status: Needs work » Needs review
StatusFileSize
new14.62 KB

Fixed the conflict with HEAD, needs a retest.

robloach’s picture

StatusFileSize
new14.54 KB

Sorry about that..... Forgot one $path to $data variable.

robloach’s picture

Status: Needs review » Needs work

Needs update to HEAD.

robloach’s picture

Status: Needs work » Needs review
Issue tags: +Patch Spotlight
StatusFileSize
new14.91 KB

Status: Needs review » Needs work

The last submitted patch failed testing.

robloach’s picture

Status: Needs work » Needs review
Issue tags: +CSS, +drupal_add_css

Resubmitting for review because it still applies cleanly, and the test failures were seemingly unrelated.

sun’s picture

+ * @param $data
+ *   (optional) If given, the value depends on the type passed through to the
+ *   $options parameter:

I had to read this sentence 3 times to understand it. Especially, since "type" seems to refer to $options['type'].

+ *     For example a CSS file called 'name.css' will have a 'name-rtl.css'

A few lines above, we are recommending to prefix filenames with the module's name. This example should adhere to that.

+ *   - 'inline': The CSS that should be placed in the given scope.

Can we stuff a "string" into that description to make the data type clear?

+ * @param $data
+ *   - 'module' or 'theme': The path to the CSS file relative to the base_path(),
...
  * @param $options
  *     - 'type'
  *       The type of stylesheet that is being added. Types are: 'module',

@param docs are not only using different indentation, but also a different layout for descriptions.

+ *       when being outputed on the page.

Not sure about the past tense of "output" here.

+      // Don't aggregate inline stylesheets.
+      if ($type != 'inline') {

Please do not use abbreviations in comments. Also, this comment just duplicates what is obvious from reading in the next line. Comments should describe the how's and why's instead.

 /**
+ * Processes the content of a stylesheet for optimization.

First line of PHPDoc blocks should not use the third form. Replace with "Process". "for optimization" is a bit confusing here.

+ * @param $contents
+ *   The content of the stylesheet.

$contents is the content? ;)

Lastly, there should be at least one test that verifies the real output and not a direct call to drupal_get_css().

mfer’s picture

Status: Needs review » Needs work
robloach’s picture

Status: Needs work » Needs review
StatusFileSize
new16.35 KB

This patch takes tha_sun's documentation comments at #34 into consideration. It also adds a test that creates a node using the PHP filter which hits drupal_add_css('inline'), seeing if rendering the node results in the compressed CSS.

sun’s picture

Status: Needs review » Needs work

Sorry, I should have made my point clearer:

Not so good:

+ * @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.

Good:

+ * @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.

Also, I think we should test the real, expected output, i.e.

'<style type="text/css">' . $css . '</style>'

Oh, and on a second thought, I think we want to throw a notice to

+ *   - 'inline':
+ *       A string of CSS that should be placed in the given scope.

just like we do for inline JavaScript - in general, usage of 'inline' is not recommended.

sun’s picture

Dumdedumdedum....

+ *   - 'module' or 'theme': The path to the CSS file relative to the base_path(),
+ *     e.g., /modules/devel/devel.css.

"The path to the CSS file relative to the base_path()" - but "/modules/devel/devel.css" is _absolute_. :P

robloach’s picture

Status: Needs work » Needs review
StatusFileSize
new18.01 KB

Applies the indentation that sun recommended and tests against the full style tag with assertRaw.

robloach’s picture

StatusFileSize
new18.06 KB

Completing sentences is a good thing.

sun’s picture

Status: Needs review » Needs work
+ *   - '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
+ *     then 'inline' as the CSS would then be aggregated and cached.

then => than

  * @param $options
  *   (optional) A string defining the type of CSS that is being added in the

Can we stuff single quotes around type here?

+        // Include inline stylesheets

Missing punctuation.

+ * @return
+ *   Contents of the stylesheet including the imported stylesheets.

"including any imported stylesheets via @import" (or similar) would make this a bit more grokable on first sight.

Afterwards, RTBC.

robloach’s picture

Status: Needs work » Needs review
StatusFileSize
new18.62 KB

Daniel gets "Drupal's Most Awesome Nitpicker Award!" :-)

sun’s picture

Status: Needs review » Needs work

Sorry, just ...

  * @param $optimize
- *   Defines if CSS contents should be compressed or not.
+ *   (optional) Defines if CSS contents should be compressed or not.
...
 function drupal_load_stylesheet($file, $optimize = NULL) {
...
+ * Process the content of a stylesheet for aggregation.
...
+ * @param $optimize
+ *   (optional) Defines if CSS contents should be compressed or not.
...
+function drupal_load_stylesheet_content($contents, $optimize = FALSE) {  

"compressed" should probably be replaced by "aggregated". However, after reading the code, it seems like $optimize is neither of both, but the CSS is "minified" instead. Proposal:

 * @param $optimize
 *   (optional) Boolean whether CSS contents should be minified.

Originally, I wanted to append "Defaults to FALSE.", but the recursive logic does not allow that...

Reading even more code, I would even say that for both functions, in reality, $optimize is not optional at all. It was just made optional to allow for the recursive calls (for imported stylesheets), but when directly called from elsewhere, the argument should always be passed?

robloach’s picture

Status: Needs work » Needs review
StatusFileSize
new19.05 KB

Now $_optimize defaults to FALSE, so we can essentially say that $optimize defaults to FALSE.....

 * @param $file
 *   Name of the stylesheet to be processed.
 * @param $optimize
 *   (optional) Boolean whether CSS contents should be minified. Defaults to
 *   FALSE. Setting a value will overwrite the $optimize value of future calls
 *   to the function. This is done to allow recursive calls through
 *   preg_replace_callback().
 * @return
 *   Contents of the stylesheet, including any resolved @import commands.
 * @see drupal_load_stylesheet_content()
 */
function drupal_load_stylesheet($file, $optimize = NULL) {
  static $_optimize = FALSE; // <------------ THIS!!!!!  Yay static defaults!
  // Store optimization parameter for preg_replace_callback with nested @import loops.
  if (isset($optimize)) {
    $_optimize = $optimize;
  }

This seems kind of bike sheddy, but you're right, it seems kind of weird. If you manage to write something better, then please do!

sun’s picture

/me grins

Unfortunately, the second instance is not yet updated:

+ * @param $optimize
+ *   (optional) Defines if CSS contents should be compressed or not.
sun’s picture

Hrm. Basically, this would also make this obsolete:

  // Store optimization parameter for preg_replace_callback with nested @import loops.
  if (isset($optimize)) {
    $_optimize = $optimize;
  }

However, I am not entirely sure whether we are overlooking a part of the logic that may was the reason for using NULL instead of FALSE by default. I grepped over core, but could not find a function that (is not in common.inc) and invokes drupal_load_stylesheet() without the second argument.

The only other is _drupal_load_stylesheet(). So I have to wonder whether it was implemented this way, because drupal_add_css() processed all CSS in a certain order, invoking drupal_load_stylesheet() subsequently, and (perhaps?) relying on a previously defined $optimize state?

Hint: This is what inline comments should really describe. "Store optimization parameter for preg_replace_callback with nested @import loops." explains nothing.

robloach’s picture

I'm playing the Bike Shed/Kittens card here. The $optimization documentation error isn't really in scope with this issue anyway. It was a problem before this patch came into existence, and seems like a documentation/logic quirk that could be easily be resolved in a separate issue with a refined scope. Anyone mind giving a final review?

sun’s picture

ok, let's revert anything that has to do with the default value of $optimize (but keeping the "minified" term improvement). I'll do the final review.

robloach’s picture

Status: Reviewed & tested by the community » Needs review
StatusFileSize
new18.51 KB

This patch provides "minified" for drupal_load_stylesheet_content, but leaves the documentation the same for drupal_load_stylesheet as that function hasn't really changed at all on the API front.

+ * @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) {

If this goes in, webchick, please add both mfer and tha_sun to the commit credit list. They were crazy awesome with code reviews and suggestions.

sun’s picture

Status: Needs review » Reviewed & tested by the community

Thanks!

This is ready to hit the core.

hass’s picture

Status: Needs review » Reviewed & tested by the community

I really hope you have thought about #228818: IE: Stylesheets ignored after 31 link/style tags... this is OT here, but as more style tags we add to a page - the sooner we are running into this critical issue.

robloach’s picture

Hi Hass.... All inline CSS is added into one style tag, so we're okay on that front.

hass’s picture

Excellent. THX.

owen barton’s picture

Subscribe - great work guys :)

sun’s picture

+   * Tests rendering inline stylesheets with preprocessing on.
...
+   * Tests rendering the stylesheets with preprocessing off.
...
+   * Tests rendering the inline stylesheets through a full page request.

Can we make this a bit more consistent while core maintainers are unavailable?

Otherwise, this looks really, really RTBCRTBC.

mattgilbert’s picture

subscribe

webchick’s picture

Status: Reviewed & tested by the community » Needs work
Issue tags: +Needs documentation

Great work, folks! Committed to HEAD with some comment standardization, per sun.

Marking needs work until this is in the upgrade documentation.

robloach’s picture

Status: Fixed » Closed (fixed)

Automatically closed -- issue fixed for 2 weeks with no activity.

b-prod’s picture

Version: 7.x-dev » 6.x-dev
Status: Closed (fixed) » Needs review
StatusFileSize
new12.18 KB

For those who are interested, here is a patch against 6.x-dev, adaptated from #49. I tagged the issue as "need review" but I don't really hope it will be back-ported.

yannickoo’s picture

StatusFileSize
new12.19 KB

Thank you B-Prod for your patch. I just added the \n to line 148.

robloach’s picture

Assigned: robloach » Unassigned

Nicely done on the patch. Think it needs a reroll?

robloach’s picture

kenorb’s picture

Status: Needs review » Closed (outdated)

Automatically closed because Drupal 6 is no longer supported. If the issue verifiably applies to later versions, please reopen with details and update the version.