New Doc - CSS Aggregation with private downloads enabled

Created on 12 December 2010, over 14 years ago
Updated 21 September 2024, 7 months ago

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:

<!--break-->

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.

📌 Task
Status

Needs review

Component

New documentation

Created by

🇩🇪Germany x3cion

Live updates comments and jobs are added and updated live.
  • theming

    Used in Documentation issues related to theming

  • server stuff

    Used in Documentation issues for documentation problems related to servers, such as installing/configuring/debugging Apache, MySql, PostgreSQL, etc.

Sign in to follow issues

Comments & Activities

Not all content is available!

It's likely this issue predates Contrib.social: some issue and comment data are missing.

Production build 0.71.5 2024