base64 Plugin for CSS Cacheer with support for IE6+
<?php
/******************************************************************************
Prevent direct access
******************************************************************************/
if (!defined('CSS_CACHEER')) { header('Location:/'); }
/******************************************************************************
Grab the modified CSS file
******************************************************************************/
$css = file_get_contents($relative_file);
$prepend_css = ""; // prepend to css before caching
// Pre-process for importers
foreach($plugins as $plugin)
{
$css = $plugin->pre_process($css);
}
// Process for heavy lifting
foreach($plugins as $plugin)
{
$css = $plugin->process($css);
}
// Post-process for formatters
foreach($plugins as $plugin)
{
$css = $plugin->post_process($css);
}
/******************************************************************************/
$header = '/* Processed and cached by Shaun Inman\'s CSS Cacheer';
$header .= ' (with '.str_replace('Plugin', '', preg_replace('#,([^,]+)$#', " &$1", join(', ', array_keys($plugins)))).' enabled)';
$header .= ' on '.gmdate('r').' <http://shauninman.com/search/?q=cacheer> */'."\r\n";
$css = $prepend_css.$header.$css;
/******************************************************************************
Make sure the target directory exists
******************************************************************************/
if ($cached_file != $cached_dir && !is_dir($cached_dir))
{
$path = $cssc_cache_dir;
$dirs = explode('/', $relative_dir);
foreach ($dirs as $dir)
{
$path .= '/'.$dir;
mkdir($path, 0777);
}
}
/******************************************************************************
Cache parsed CSS
******************************************************************************/
$css_handle = fopen($cached_file, 'w');
fwrite($css_handle, $css);
fclose($css_handle);
chmod($cached_file, 0777);
touch($cached_file, $requested_mod_time);
<?php
error_reporting(0);
/******************************************************************************
Used to prevent direct access to CSS Cacheer files
******************************************************************************/
define('CSS_CACHEER', true);
/******************************************************************************
Received request from mod_rewrite
******************************************************************************/
// absolute path to requested file, eg. /css/nested/sample.css
$requested_file = isset($_GET['cssc_request']) ? $_GET['cssc_request'] : '';
// absolute path to directory containing requested file, eg. /css/nested
$requested_dir = preg_replace('#/[^/]*$#', '', $requested_file);
// absolute path to css directory, eg. /css
$css_dir = preg_replace('#/[^/]*$#', '', (isset($_SERVER['PHP_SELF'])) ? $_SERVER['PHP_SELF'] : $_SERVER['SCRIPT_URL']);
/******************************************************************************
Limit processing to existing css files within this and nested directories
******************************************************************************/
if
(
substr($requested_file, -4) != '.css' ||
substr($requested_file, 0, strlen($css_dir)) != $css_dir ||
!file_exists(substr($requested_file, strlen($css_dir) + 1))
)
{
echo '/* Invalid Request */';
exit();
}
/******************************************************************************
Load plugins
******************************************************************************/
include('css-cacheer/plugin.php');
$flags = array();
$plugins = array();
$plugin_path = 'css-cacheer/plugins';
if (is_dir($plugin_path))
{
if ($dir_handle = opendir($plugin_path))
{
while (($plugin_file = readdir($dir_handle)) !== false)
{
if (substr($plugin_file, 0, 1) == '.' || substr($plugin_file, 0, 1) == '-')
{
continue;
}
include($plugin_path.'/'.$plugin_file);
if (isset($plugin_class) && class_exists($plugin_class))
{
$plugins[$plugin_class] = new $plugin_class($flags);
$flags = array_merge($flags, $plugins[$plugin_class]->flags);
}
}
closedir($dir_handle);
}
}
/******************************************************************************
Create hash of query string to allow variables to be cached
******************************************************************************/
$isIE = eregi("MSIE", $_SERVER['HTTP_USER_AGENT']);
$isIEOnVista = $isIE && eregi("Windows NT 6.0", $_SERVER['HTTP_USER_AGENT']);
$recache = isset($_GET['recache']);
$args = $flags;
ksort($args);
$checksum = md5(serialize($args.$isIE.)); // Gives two different checksums for IE and non-IE Browsers
/******************************************************************************
Determine relative and cache paths
******************************************************************************/
$cssc_cache_dir = 'css-cacheer/cache/';
// path to requested file, relative to css directory, eg. nested/sample.css
$relative_file = substr($requested_file, strlen($css_dir) + 1);
// path to directory containing requested file, relative to css directory, eg. nested
$relative_dir = (strpos($relative_file, '/') === false) ? '' : preg_replace("/\/[^\/]*$/", '', $relative_file);
// path to cache of requested file, relative to css directory, eg. css-cacheer/cache/nested/sample.css
$cached_file = $cssc_cache_dir.preg_replace('#(.+)(\.css)$#i', "$1-{$checksum}$2", $relative_file);
// path to directory containing cache of requested CSS file, relative from the directory containing cache.php, eg. cache/nested
$cached_dir = $cssc_cache_dir.$relative_dir;
/******************************************************************************
Delete file cache
******************************************************************************/
if ($recache && file_exists($cached_file))
{
unlink($cached_file);
}
/******************************************************************************
Get modified time for requested file and if available, its cache
******************************************************************************/
$requested_mod_time = filemtime($relative_file);
$cached_mod_time = (int) @filemtime($cached_file);
// cache may not exist, silence error with @
/******************************************************************************
Recreate the cache if stale or nonexistent
******************************************************************************/
if ($cached_mod_time < $requested_mod_time)
{
include_once('css-cacheer/index.php');
}
/******************************************************************************
Or send 304 header if appropriate
******************************************************************************/
else if
(
isset($_SERVER['HTTP_IF_MODIFIED_SINCE'], $_SERVER['SERVER_PROTOCOL']) &&
$requested_mod_time <= strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE'])
)
{
header("{$_SERVER['SERVER_PROTOCOL']} 304 Not Modified");
exit();
}
/******************************************************************************
Send cached file to browser, Due to security restrictions on Windows Vista, a
MHTML document has to have the MIME type 'text/plain'.
******************************************************************************/
if ($isIEOnVista)
{
header('Content-type: text/plain');
}
else
{
header('Content-type: text/css');
}
header("Vary: User-Agent, Accept");
header('Last-Modified: '.gmdate('D, d M Y H:i:s', $requested_mod_time).' GMT');
@include($cached_file);
<?php
/******************************************************************************
Prevent direct access
******************************************************************************/
if (!defined('CSS_CACHEER')) { header('Location:/'); }
$plugin_class = 'Base64Plugin';
class Base64Plugin extends CacheerPlugin
{
function Base64Plugin()
{
if (isset($_SERVER['HTTP_USER_AGENT']))
{
$ua = $this->parse_user_agent($_SERVER['HTTP_USER_AGENT']);
// Safari (WebKit), Firefox & Opera are known to support data: urls so embed base64-encoded images
if
(
($ua['browser'] == 'applewebkit' && $ua['version'] >= 125) || // Safari and ilk
($ua['browser'] == 'firefox') || // Firefox et al
($ua['browser'] == 'opera' && $ua['version'] >= 7.2) || // quell vociferous Opera evangelists
($ua['browser'] == 'ie' && $ua['version'] >= 6.0)
)
{
$this->flags['Base64'] = true;
}
}
}
function post_process($css)
{
global $prepend_css;
$ua = $this->parse_user_agent($_SERVER['HTTP_USER_AGENT']);
if (isset($this->flags['Base64']))
{
global $requested_dir;
$root_dir = $_SERVER['DOCUMENT_ROOT'];
$images = array();
if (preg_match_all('#url\(([^\)]+)\)#i', $css, $matches))
{
foreach($matches[1] as $relative_img)
{
if (!preg_match('#\.(gif|jpg|png)$#', $relative_img, $ext))
{
continue;
}
$images[$relative_img] = $ext[1];
}
}
if ($ua['browser']!="ie") {
foreach($images as $relative_img => $img_ext)
{
$up = substr_count($relative_img, '../');
$absolute_img = $root_dir.preg_replace('#([^/]+/){'.$up.'}(\.\./){'.$up.'}#', '', $requested_dir.'/'.$relative_img);
if (file_exists($absolute_img))
{
$img_raw = file_get_contents($absolute_img);
$img_data = 'data:image/'.$img_ext.';base64,'.base64_encode($img_raw);
$css = str_replace("url({$relative_img})", "url({$img_data})", $css);
}
}
}
else if ($ua['browser']=="ie") {
$full_request_uri = 'http://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI']; // absolute url for the requested css file
$delimiter = "DELIMITER_STRING"; // Delimiter for each base64 encoded image declaration
$prepend_css .="/*\n".'Content-Type: multipart/related; boundary="'.$delimiter.'"'."\n\n"; // set the required Content-Type for mhtml
foreach($images as $relative_img => $img_ext)
{
$up = substr_count($relative_img, '../');
$absolute_img = $root_dir.preg_replace('#([^/]+/){'.$up.'}(\.\./){'.$up.'}#', '', $requested_dir.'/'.$relative_img);
if (file_exists($absolute_img))
{
$img_raw = file_get_contents($absolute_img);
$img_data = trim(base64_encode($img_raw));
// Content-Location: ....
$content_location = str_replace(array("/", "."), "", $relative_img);
// url(mhtlm:http://......)
$mhtml_uri = "mhtml:".$full_request_uri."!".$content_location;
$css = str_replace("url({$relative_img})", "url({$mhtml_uri})", $css);
// insert base64 encoded images into the css-file
$prepend_css .= "--".$delimiter."\n";
$prepend_css .= "Content-Location:".$content_location."\n";
$prepend_css .= "Content-Transfer-Encoding:base64\n\n";
$prepend_css .= $img_data."\n\n";
}
}
$prepend_css .= "*/\n\n"; // Close comment for the mhtml part
}
}
return $css;
}
// really simple (read: imperfect) rendering engine detection
function parse_user_agent($user_agent)
{
$ua['browser'] = '';
$ua['version'] = 0;
if (preg_match('/(firefox|opera|applewebkit)(?: \(|\/|[^\/]*\/| )v?([0-9.]*)/i', $user_agent, $m))
{
$ua['browser'] = strtolower($m[1]);
$ua['version'] = $m[2];
}
else if (preg_match('/MSIE ?([0-9.]*)/i', $user_agent, $v) && !preg_match('/(bot|(?<!mytotal)search|seeker)/i', $user_agent))
{
$ua['browser'] = 'ie';
$ua['version'] = $v[1];
}
return $ua;
}
}