Hello,

I'm new to this and I found some of the documentation a bit confusing. I guess an attachment wouldn't get highlighted, so I'll just put the page in here.

I came up with this studying this site about CSS aggregation within the theme. I needed this for Drupal 6, since I use the private download method and can't activate Drupals built in CSS optimization.

I would have placed the page under the page Working with CSS with a title similar to "CSS Aggregation with private downloads enabled".

So finally, here's what I came up with this:

Drupal won't let you use the CSS optimization option, if you have private downloads activated. But since this is a good way to speed up your pages loading time, you probably need a way around that.

A solution to this is to move CSS aggregation duties to template.php and have css files cached under the theme directory. Each time a user hits a page a string is created from the md5 of the file names and their last modified time. If a file already exists with this string as a name, then it's served up, otherwise it's generated and saved in the 'cache' directory.

So firstly make a subdirectory of your theme named 'cache' and change the permissions appropriately so whichever user is executing the code can write to it.

If you're using the Zen theme add this to your sub-theme's template.php (check to see if you already have the function, just add this code into the bottom of it if you do, minus the function declaration) and change STARTERKIT to the name of your theme.

What it (should do) does in short:

  • replacing url() paths with base64 data. This saves a lot of requests.
  • Aggregating all files of a media type into one css file
  • Use some compressions normally done by drupal
  • Cleaning up the cache directory if there was a change of the css files and the css files have to be reaggregated.
function YOURSTYLE_preprocess_page(&$vars, $hook) {
	$css = drupal_add_css(); // We need all CSS Files to process
	foreach ($css as $cssMedia => $cssSource) {
		foreach ($cssSource as $cssSourceName => $cssFileArr) {
			foreach ($cssFileArr as $cssFile => $cssFileOn) {
				$GLOBALS['cssFile'] = $cssFile;
				if (file_exists($cssFile)) {
					$cssFileContent = file_get_contents($cssFile);
					$cssFileContent = preg_replace_callback('#url\((\'|\")(.+)\1\)#', '_inline_css_image', $cssFileContent); // See function _inline_css_image
					$GLOBALS['cssOutput'][$cssMedia]['fileContent'] .= $cssFileContent . "\n";
					$GLOBALS['cssOutput'][$cssMedia]['modifiedDates'] .= filemtime($cssFile);
					$GLOBALS['cssOutput'][$cssMedia]['fileString'] .= $cssFile . ',';
				}
			}
		}
		// COPIED FROM common.inc -> drupal_load_stylesheet AND MODIFIED
		// Perform some safe CSS optimizations.
		// Regexp to match comment blocks.
		$comment = '/\*[^*]*\*+(?:[^/*][^*]*\*+)*/';
		// Regexp to match double quoted strings.
		$double_quot = '"[^"\\\\]*(?:\\\\.[^"\\\\]*)*"';
		// Regexp to match single quoted strings.
		$single_quot = "'[^'\\\\]*(?:\\\\.[^'\\\\]*)*'";
		$GLOBALS['cssOutput'][$cssMedia]['fileContent'] = preg_replace_callback(
						"<$double_quot|$single_quot|$comment>Sus", // Match all comment blocks along
						"_process_comment", // with double/single quoted strings
						$GLOBALS['cssOutput'][$cssMedia]['fileContent']);   // and feed them to _process_comment().
		$GLOBALS['cssOutput'][$cssMedia]['fileContent'] = preg_replace(
						'<\s*([@{}:;,]|\)\s|\s\()\s*>S', // Remove whitespace around separators,
						'\1', $GLOBALS['cssOutput'][$cssMedia]['fileContent']);   // but keep space around parentheses.
		////////////////////////////////////////////////////////////////////////////////////////////
	}
	unset($GLOBALS['cssFile']);
	$overallSuccess = true;
	foreach ($GLOBALS['cssOutput'] as $cssMedia => $cssFileData) {
		$cssCacheFilePath = base_path() . path_to_theme() . '/cache/' . $cssMedia . "_" . md5($cssFileData['fileString'] . $cssFileData['modifiedDates']) . ".css";
		$cssCacheFile = $_SERVER['DOCUMENT_ROOT'] . $cssCacheFilePath;
		$fileCreationSuccess = true;
		if (!file_exists($cssCacheFile)) {
			// Delete old cache files
			$cacheLs = scandir(dirname($cssCacheFile));
			$oldCssCacheFiles = preg_grep("#^" . $cssMedia . "\_#", $cacheLs);
			foreach ($oldCssCacheFiles as $oldCssCacheFile) {
				unlink(dirname($cssCacheFile) . "/" . $oldCssCacheFile);
			}
			// Create new file
			$fileCreationSuccess = file_put_contents($cssCacheFile, $cssFileData['fileContent']);
		}
		// Lets get sure everything went right
		if ($fileCreationSuccess == true || $fileCreationSuccess != false) {
			$vars['styles_optimized'] .= '<link type="text/css" rel="stylesheet" media="' . $cssMedia . '" href="' . $cssCacheFilePath . '" />';
		} else { // Or take a note
			$overallSuccess = false;
		}
	}
	if (!$overallSuccess) { // If something went wrong, it's better to fall back
		$vars['styles_optimized'] = $vars['styles'];
	}
	unset($GLOBALS['cssOutput']);
	return $vars;
}

/**
 * Used as Callback function for preg_replace_callback in our hook_preprocess_page function
 * @param array $match This will be the Matches and stuff we get
 * @return string Override of the url('/path') part, or the exact part if url'ed file not found.
 */
function _inline_css_image($match) {// If the string is a
	if (strpos($match[2], "/") == 0 && file_exists($_SERVER['DOCUMENT_ROOT'] . $match[2])) { // Check for file relative to root.
		$match[2] = $_SERVER['DOCUMENT_ROOT'] . $match[2];
	} else if (file_exists(dirname($GLOBALS['cssFile']) . "/" . $match[2])) { // Check for file relative from css file
		$match[2] = dirname($GLOBALS['cssFile']) . "/" . $match[2];
	} else { // Fallback if nothing catched
		return 'url(\'' . $match[2] . '\')';
	}
	return 'url(data:' . file_get_mimetype($match[2]) . ';base64,' . base64_encode(file_get_contents($match[2])) . ')';
}

Please review the documentation and the code itself. I'm using Zen, so you might try to use it with other themes.
Please tell me if I took any wrong option on this issue report stuff. As I said, I'm new to this, every hint appreciated.

CommentFileSizeAuthor
#1 css_aggregation.txt5.22 KBx3cion
Support from Acquia helps fund testing for Drupal Acquia logo

Comments

x3cion’s picture

FileSize
5.22 KB

Hi,

I had to change the regexp and now that I know why I should upload the content as attachment (no quote-like option on issues), I do so.

Drupal won't let you use the CSS optimization option, if you have private downloads activated. But since this is a good way to speed up your pages loading time, you probably need a way around that.

A solution to this is to move CSS aggregation duties to template.php and have css files cached under the theme directory. Each time a user hits a page a string is created from the md5 of the file names and their last modified time. If a file already exists with this string as a name then it's served up, otherwise it's generated and saved in the 'cache' directory.

So firstly make a subdirectory of your theme named 'cache' and change the permissions appropriately so whichever user is executing the code can write to it.

If you're using the Zen theme add this to your sub-theme's template.php (check to see if you already have the function, just add this code into the bottom of it if you do, minus the function declaration) and change STARTERKIT to the name of your theme.

What it (should do) does in short:

  • replacing url() paths with base64 data. This saves alot of requests.
  • Aggregating all files of a media type into one css file
  • Use some compressions normaly done by drupal
  • Cleaning up the cache directory if there was a change of the css files and the css files have to be reaggregated.
function YOURSTYLE_preprocess_page(&$vars, $hook) {
	$css = drupal_add_css(); // We need all CSS Files to process
	foreach ($css as $cssMedia => $cssSource) {
		foreach ($cssSource as $cssSourceName => $cssFileArr) {
			foreach ($cssFileArr as $cssFile => $cssFileOn) {
				$GLOBALS['cssFile'] = $cssFile;
				if (file_exists($cssFile)) {
					$cssFileContent = file_get_contents($cssFile);
					$cssFileContent = preg_replace_callback('#url\((\'|\")?(.+[^\"\'])\1?\)#', '_inline_css_image', $cssFileContent); // See function _inline_css_image
					$GLOBALS['cssOutput'][$cssMedia]['fileContent'] .= $cssFileContent . "\n";
					$GLOBALS['cssOutput'][$cssMedia]['modifiedDates'] .= filemtime($cssFile);
					$GLOBALS['cssOutput'][$cssMedia]['fileString'] .= $cssFile . ',';
				}
			}
		}
		// COPIED FROM common.inc -> drupal_load_stylesheet AND MODIFIED
		// Perform some safe CSS optimizations.
		// Regexp to match comment blocks.
		$comment = '/\*[^*]*\*+(?:[^/*][^*]*\*+)*/';
		// Regexp to match double quoted strings.
		$double_quot = '"[^"\\\\]*(?:\\\\.[^"\\\\]*)*"';
		// Regexp to match single quoted strings.
		$single_quot = "'[^'\\\\]*(?:\\\\.[^'\\\\]*)*'";
		$GLOBALS['cssOutput'][$cssMedia]['fileContent'] = preg_replace_callback(
						"<$double_quot|$single_quot|$comment>Sus", // Match all comment blocks along
						"_process_comment", // with double/single quoted strings
						$GLOBALS['cssOutput'][$cssMedia]['fileContent']);   // and feed them to _process_comment().
		$GLOBALS['cssOutput'][$cssMedia]['fileContent'] = preg_replace(
						'<\s*([@{}:;,]|\)\s|\s\()\s*>S', // Remove whitespace around separators,
						'\1', $GLOBALS['cssOutput'][$cssMedia]['fileContent']);   // but keep space around parentheses.
		////////////////////////////////////////////////////////////////////////////////////////////
	}
	unset($GLOBALS['cssFile']);
	$overallSuccess = true;
	foreach ($GLOBALS['cssOutput'] as $cssMedia => $cssFileData) {
		$cssCacheFilePath = base_path() . path_to_theme() . '/cache/' . $cssMedia . "_" . md5($cssFileData['fileString'] . $cssFileData['modifiedDates']) . ".css";
		$cssCacheFile = $_SERVER['DOCUMENT_ROOT'] . $cssCacheFilePath;
		$fileCreationSuccess = true;
		if (!file_exists($cssCacheFile)) {
			// Delete old cache files
			$cacheLs = scandir(dirname($cssCacheFile));
			$oldCssCacheFiles = preg_grep("#^" . $cssMedia . "\_#", $cacheLs);
			foreach ($oldCssCacheFiles as $oldCssCacheFile) {
				unlink(dirname($cssCacheFile) . "/" . $oldCssCacheFile);
			}
			// Create new file
			$fileCreationSuccess = file_put_contents($cssCacheFile, $cssFileData['fileContent']);
		}
		// Lets get sure everything went right
		if ($fileCreationSuccess == true || $fileCreationSuccess != false) {
			$vars['styles_optimized'] .= '<link type="text/css" rel="stylesheet" media="' . $cssMedia . '" href="' . $cssCacheFilePath . '" />';
		} else { // Or take a note
			$overallSuccess = false;
		}
	}
	if (!$overallSuccess) { // If something went wrong, it's better to fall back
		$vars['styles_optimized'] = $vars['styles'];
	}
	unset($GLOBALS['cssOutput']);
	return $vars;
}

/**
 * Used as Callback function for preg_replace_callback in our hook_preprocess_page function
 * @param array $match This will be the Matches and stuff we get
 * @return string Override of the url('/path') part, or the exact part if url'ed file not found.
 */
function _inline_css_image($match) {// If the string is a
	if (strpos($match[2], "/") == 0 && file_exists($_SERVER['DOCUMENT_ROOT'] . $match[2])) { // Check for file relative to root.
		$match[2] = $_SERVER['DOCUMENT_ROOT'] . $match[2];
	} else if (file_exists(dirname($GLOBALS['cssFile']) . "/" . $match[2])) { // Check for file relative from css file
		$match[2] = dirname($GLOBALS['cssFile']) . "/" . $match[2];
	} else { // Fallback if nothing catched
		return 'url(\'' . $match[2] . '\')';
	}
	return 'url(data:' . file_get_mimetype($match[2]) . ';base64,' . base64_encode(file_get_contents($match[2])) . ')';
}
HLopes’s picture

Thank you SOOOOOOOOOOOOOOO much!

I've been having issues with private file system + css aggregation + IE and that solved the problem.

The only issue i'm having with this solution is that now i need to rewrite the relative url css properties like

url(images/....)

in my css to represent current folder structure, like so

url(../images/....)

i guess there's not much else to do about it... unless the generated files went to root theme folder, but we don't want 777 permissions there.

#EDIT

And i have no clue to what to do with images from the contrib modules... As is, it will render fivestar unusable ( well, not unusable, just invisible :D ).

This issue needs a solution, as IE9 still doesn't solve rules limit. One would think that after 20+ drupal releases it would get fixed, but no. If it's fixed in D7, why not backport? Why not develop a mixed filesystem [ files/private/ | files/public/ ] solution?

#END EDIT

Why don't you make a module out of this? Because of the folder permissions? I'm sure it would be of great adherence.

PS:
Sorry for all the whyning :D

arianek’s picture

Issue tags: +theming, +server stuff

tags

HLopes’s picture

Well, just tried this out and it's working perfectly.

arianek’s picture

Component: Placement/Navigation/Strucure » Correction/Clarification

x3cion - do you want to post this page as a child book page in the section you suggested, then mark the page "needs technical review" then we can try and get someone to do a final review on it?

x3cion’s picture

@ #4
I don't get it. I tried it and it did absolutely nothing to my JS or CSS files. :/

HLopes’s picture

x3cion:

Edit your page.tpl.php and replace $styles with $styles_optimized.

I think this is only for CSS files. If you're looking for both CSS and JS, use Advanced CSS/JS Aggregation by mikeytown2. Still under development, but it works.

x3cion’s picture

@HLopes:

You got me wrong. I know how my fix works. :P I just can't get the Advanced Aggregation module to work and thus don't see how it compares.

HLopes’s picture

Oh sorry.... Come to think of it, your name really sounded familiar... :D

Anyway, i can confirm that AdvAgg works...

I'm using it in my private filesystem enabled site and it does work...
Reduced my loaded CSS files to 4.

I don't have full understanding of the inner workings of the module, but its doing it's job with only the main module activated.
Module configuration can be bit puzzling, if you wish i can screenshot mine.

I have disabled your fix before activating the module, just in case. If you didn't, then... there's no surprise there. :D

If none helps, you can always try on a clean install...

Cheers

x3cion’s picture

@ arianek :

I tried out advagg ( https://drupal.org/project/advagg ). It still has issues but it's insanly fast growing atm. I would rather suggest to suggest users to use this module as soon as it's stable. This code was a workaround. But the module is more powerful.

arianek’s picture

sure - if that seems promising, feel free to put a note about it. if you want to, you can even write a basic docs page about the module, as it doesn't seem like it has docs right now

krina.addweb’s picture

Issue summary: View changes