cakebook / branches / master / vendors / mi_compressor.php

history
<?php
/* SVN FILE: $Id: mi_compressor.php 939 2009-04-16 21:25:05Z ad7six $ */
/**
 * MiCompressor, a class used for shrinking CSS and JS files
 *
 * MiCompressor is a utility which serves 2 purposes:
 * 	Ask the class to return the request(s) necessary for a set of css/js files
 * 	Process a request for css/js file(s)
 *
 * PHP version 5
 *
 * Copyright (c) 2008, Andy Dawson
 *
 * Licensed under The MIT License
 * Redistributions of files must retain the above copyright notice.
 *
 * @filesource
 * @copyright     Copyright (c) 2008, Andy Dawson
 * @link          www.ad7six.com
 * @package       base
 * @subpackage    base.vendors
 * @since         v 1.0
 * @version       $Revision: 939 $
 * @modifiedby    $LastChangedBy: ad7six $
 * @lastmodified  $Date: 2009-04-16 23:25:05 +0200 (Thu, 16 Apr 2009) $
 * @license       http://www.opensource.org/licenses/mit-license.php The MIT License
 */
/**
 * MiCompressor class
 *
 * Compress and minify multiple css and js files into a single file on demand.
 *
 * By default, in debug mode it only concatonates, in production mode contents are also runs the output through a
 * minifying routine. Some term definitions:
 * 	compress is used within the class to refer to data-compression (such as gzip etc.);
 * 	minify means stripping whitespace, rewriting to be less chars etc.
 *
 * This class is designed to be used with CakePHP but can be used alone (dependent on Minify only, if minified assets
 * don't already exist). If Minify can't be found minification will be skipped
 *
 * Note:
 * 	To avoid stale js/css files being served, The default salt is a constant SITE_VERSION (if defined). The salt
 * 	is used for the hash generation/comparison logic and allows you to avoid stale css/js e.g.:
 * 		define('SITE_VERSION', filemtime('somefilethatgetsupdatedwhenyoudeploy'));
 * 	In CakePHP a suggestion would be:
 * 		define('SITE_VERSION', filemtime('/app/config/bootstrap.php'));
 *	Or call this before using the class:
 * 		MiCompressor::config(array('salt' => 'some value'));
 *
 *
 * @abstract
 * @package       base
 * @subpackage    base.vendors
 */
abstract class MiCompressor {
/**
 * map property
 *
 * To allow disassociation from parameters used in the request, and file path locations
 *
 * @static
 * @var array
 * @access public
 */
	public static $map = array(
		'js' => array(
			'jquery' => array(
				'aplugin' => 'subfolder/path/here.js'
			)
		),
		'css' => array(
			'jquery' => array(
				'aplugin' => 'subfolder/path/here.css',
				'aplugin' => array(
					'subfolder/path/moreThan.css',
					'subfolder/path/oneFileRequired.css'
				)
			)
		)

	);
/**
 * settings property
 *
 * Active settings - edit/set/see via MiCompressor::config()
 *
 * @var array
 * @access protected
 */
	protected static $settings = array();
/**
 * defaultSettings property
 *
 * Don't edit this.
 * The settings which can be auto-set are set here, these settings are used as a fallback if a requested setting
 * hasn't been set, or the Configure class (if it exists) return null
 *
 * @var array
 * @access protected
 */
	protected static $defaultSettings = array(
		'MiCompressor.debug' => null,
		'MiCompressor.log' => null,
		'MiCompressor.cacheClear' => null,
		'MiCompressor.cacheDuration' => null,
		'MiCompressor.cacheDir' => null,
		'MiCompressor.salt' => null,
		'MiCompressor.bypassLoadMinifyLib' => null,
		'MiCompressor.minify' => null,
		'MiCompressor.minify.css' => null,
		'MiCompressor.minify.js' => null,
		'Asset.compress' => null,
	);
/**
 * initialized property
 *
 * Flag to know if the class variables have been initialized yet.
 *
 * @var bool false
 * @access protected
 */
	protected static $initialized = false;
/**
 * start property
 *
 * Start time
 *
 * @static
 * @var mixed null
 * @access protected
 */
	protected static $start = null;
/**
 * loadedFiles property
 *
 * To prevent a valid request which includes the same file twice (either expicitly or through @import logic)
 * this variable holds the names of the files already loaded for the current request
 *
 * @var array
 * @access protected
 */
	protected static $loadedFiles = array();
/**
 * config method
 *
 * Set/See settings
 *
 * @param array $settings array()
 * @param bool $reset false - reset to defaults before porcessing $settings?
 * @static
 * @return current settings
 * @access public
 */
	public static function config($settings = array(), $reset = false) {
		if ($reset) {
			MiCompressor::$settings = array();
		}
		return MiCompressor::$settings = array_merge(MiCompressor::$settings, $settings);
	}
/**
 * log method
 *
 * Record to the log (the head doc block in debug mode) or output the log (call with no params)
 *
 * @param mixed $string
 * @static
 * @return logs contents if requested, otherwise null
 * @access public
 */
	public static function log($string = null) {
		if (MiCompressor::$start === null) {
			MiCompressor::$start = getMicrotime();
		}
		static $log = array();
		if ($string === null) {
			$settings = MiCompressor::$settings;
			ksort($settings);
			foreach ($settings as $k => &$v) {
				$v = ' ' . str_pad(str_replace('MiCompressor.', '', $k), 15, ' ', STR_PAD_RIGHT) . "\t: " . $v;
			}
			$settings[] = '';
			$head = array_merge(array(
				'MiCompressor log - (only generated in debug mode, or if MiCompressor.log is set to true) ' . date("D, M jS Y, H:i:s"),
				null), $settings);
			$log = array_merge($head, $log);
			$return = "/**\r\n * " . implode("\r\n * ", $log) . "\r\n */\r\n";
			$log = array();
			return $return;
		}
		$time = getMicrotime() - MiCompressor::$start;
		$log[] = str_pad(number_format($time, 3, '.', ''), 6, ' ', STR_PAD_LEFT) . 's ' . $string;
	}
/**
 * minifyCss method
 *
 * Pass in a string of css, get out a minified version
 *
 * @param mixed $css
 * @param string $logPrefix the indent
 * @static
 * @return string minified css
 * @access public
 */
	public static function minifyCss($css, $logPrefix = '') {
		MiCompressor::log("{$logPrefix}minifying combined css file");
		return Minify_CSS::minify($css);
	}
/**
 * minifyJs method
 *
 * Pass in a string of javascript, and get out a minified version
 * This can be rather intensive - use with care
 *
 * @TODO don't strip license blocks
 * @param mixed $js
 * @param string $file
 * @param string $logPrefix the indent
 * @static
 * @return minified js
 * @access public
 */
	public static function minifyJs($js, $file = '', $logPrefix = '') {
		MiCompressor::log("$logPrefix	minifying $file.js");
		return JSMin::minify($js);
	}
/**
 * process method
 *
 * For each of the requested files, find them, concatonate them - if requested minify them - and return
 * For js files, each individual file is minifyed. For css, their combined contents are minifyed
 * Called internally by serve, public to allow other (external) parse logic if necessary
 *
 * @param mixed $files
 * @param mixed $type
 * @param mixed $minify
 * @static
 * @return string the files contents, optionally minifyed, as a string
 * @access public
 */
	public static function process($files, $type = null, $minify = null) {
		if ($type === null) {
			if (strpos($_GET['url'], 'js/') === 0) {
				$type = 'js';
			} else {
				$type = 'css';
			}
		}
		if ($minify === null) {
			$minify = MiCompressor::cRead('MiCompressor.minify.' . $type, 'minify');
		}

		if ($minify && !MiCompressor::loadMinifyLib($type)) {
			MiCompressor::log("PROBLEM: Unable to load $type . No minifying");
			$minify = false;
		}

		$files = (array)$files;
		$return = '';
		foreach ($files as $filename => $params) {
			if (is_string($params) && is_numeric($filename)) {
				$filename = $params;
				$params = array();
			}
			if (substr($filename, - strlen($type)) === $type) {
				$filename = substr($filename, 0, - strlen($type) - 1);
			}
			$iType = $type;
			$iType[0] = strtoupper($iType[0]);
			$iFilename = $filename;
			$iFilename[0] = strtoupper($iFilename[0]);
			$method = 'load' . $iType . $iFilename;
			if (method_exists('MiCompressor', $method)) {
				MiCompressor::log("Loading $filename.$type with method $method");
				$return .= MiCompressor::$method($params, $minify) . "\r\n";
			} elseif (strpos($filename, 'jquery.') === 0) {
				$method = "load{$iType}JqueryPlugin";
				MiCompressor::log("Loading $filename.$type with method $method");
				$return .= MiCompressor::$method(str_replace('jquery.', '', $filename), $minify) . "\r\n";
			} else {
				MiCompressor::log("Loading $filename.$type with method loadFile");
				$return .= MiCompressor::loadFile($filename, $params, $minify, $type) . "\r\n";

			}
		}
		if ($minify && $type === 'css') {
			$return = MiCompressor::minifyCss($return, "\t");
		}
		return $return;
	}
/**
 * serve. The main entry point for this class
 *
 * Parse the request, check the cache and send the content to the browser directly
 * If the request is invalid issue a 404 header and return no content
 * otherwise generate the content and send to the browser
 *
 * Example Cake use (contents of mini.css file):
 *  	App::import('Vendor', 'MiCompressor');
 *  	list($_, $request) = explode('?', $_SERVER['REQUEST_URI']);
 *  	echo MiCompressor::serve($request, 'css');
 *
 * Example Standalone use (contents of mini.css file):
 * 	$base = '/path/to/mi_compressor/containing/folder/';
 * 	include($base . 'mi_compressor.php');
 *      // Optional Start
 * 	ini_set('include_path', $base . 'minify/lib:' . ini_get('include_path'));
 * 	include($base . 'minify/lib/JSMin.php');
 * 	include($base . 'minify/lib/Minify/CSS.php');
 * 	include($base . 'minify/lib/HTTP/ConditionalGet.php');
 *      // Optional End
 * 	MiCompressor::config(array(
 * 		'MiCompressor.debug' => 0,
 * 		'MiCompressor.minify' => 1,
 *	));
 * 	echo MiCompressor::serve($_GET, 'css');
 *
 * @param string $request
 * @param mixed $type
 * @static
 * @return contents to be served up
 * @access public
 */
	public static function serve($request = '', $type = null) {
		MiCompressor::$loadedFiles = array();

		if (is_array($request)) {
			if (count($request) == 1 && !isset($request['request'])) {
				$hash = current($request);
				$request = key($request);
			} else {
				extract($request);
			}
		}
		MiCompressor::log('Request String: ' . $request);
		$start = getMicrotime();
		ob_start();
		if (MiCompressor::cRead('Asset.compress') && @ini_get("zlib.output_compression") != true && extension_loaded("zlib") &&
			(strpos(env('HTTP_ACCEPT_ENCODING'), 'gzip') !== false)) {
			MiCompressor::log('Enabling compression, turn on zlib.output_compression if possible');
			ob_start('ob_gzhandler');
		}
		$requests = MiCompressor::parse($request, $hash);

		if (!$requests) {
			header('HTTP/1.0 404 Not Found');
			return false;
		}
		if ($type === null) {
			if (strpos(ltrim($_GET['url'], '/'), 'js') === 0) {
				$type = 'js';
			} else {
				$type = 'css';
			}
		}
		if ($cachePath = MiCompressor::cachePath($type, $hash, true)) {
			MiCompressor::log('Cache Path: ' . $cachePath);
			if (MiCompressor::cRead('cacheClear')) {
				$path = MiCompressor::cRead('cacheDir') . $type . '_*';
				$files = glob($path);
				foreach ($files as $file) {
					MiCompressor::log("Removing Cache File " . str_replace(MiCompressor::cRead('cacheDir'), '', $file));
					@unlink($file);
				}
			}
			$cached = cache($cachePath, null, MiCompressor::cRead('cacheDuration'));
			if ($cached) {
				MiCompressor::log("Cache File: $cachePath found and returned");
				return MiCompressor::out($cachePath, $cached);
			}
		}
		$return = MiCompressor::process($requests, $type);
		$eTag = md5($return);
		if ($cachePath) {
			MiCompressor::log("Generating cache file $cachePath to expire in " . MiCompressor::cRead('cacheDuration'));
		}
		MiCompressor::log('eTag: ' . $eTag);
		MiCompressor::log('Finished');
		if (MiCompressor::cRead('log')) {
			$return = MiCompressor::log() . $return;
		}
		$return = '/* eTag:' . $eTag . ' */' . $return;
		// Slightly out of sequence so the cache file contains the log
		if ($cachePath) {
			cache($cachePath, $return, MiCompressor::cRead('cacheDuration'));
		}
		return MiCompressor::out(MiCompressor::cRead('cacheDir') . $cachePath, $return);
	}
/**
 * url method
 *
 * Generate the url(s) corresponding to the requested files
 *
 * @param array $request
 * @param array $params
 * @static
 * @return mixed string or array of strings correpsonding to the request
 * @access public
 */
	public static function url($request = array(), $params = array()) {
		extract(am(array(
			'sendAlone' => MiCompressor::cRead(),
			'type' => 'js',
			'sizeLimit' => false,
		), $params));
		$stack = array();
		$i = 0;
		foreach ($request as $key => &$value) {
			$_sendAlone = $sendAlone;
			if (is_string($key)) {
				if (is_array($value) && isset($value['sendAlone'])) {
					$_sendAlone = $value['sendAlone'];
					unset($value['sendAlone']);
				}
				if (!$value) {
					$value = $key;
				} elseif($type === 'js' && $key === 'jquery' && ($sizeLimit || $_sendAlone)) {
					$i++;
					$stack[$i][] = 'jquery';
					foreach ($value as $plugin) {
						$i++;
						$stack[$i][] = 'jquery.' . $plugin;
						$i++;
					}
					continue;
				} else {
					foreach ($value as $_k => &$_v) {
						if (!is_numeric($_k)) {
							$_v = $_k . '=' . $_v;
						}
					}
					$value = $key . ',' . implode(',', $value);
				}
			}
			if ($_sendAlone) {
				$i++;
			}
			$stack[$i][] = $value;
			if ($_sendAlone) {
				$i++;
			}
		}
		$return = array();
		foreach ($stack as &$files) {
			$url = MiCompressor::_url($files, $type, $sizeLimit);
			foreach((array)$url as $u) {
				$return[] = $u;
			}
		}
		if (count($return) === 1) {
			return $return[0];
		}
		return $return;
	}
/**
 * cachePath method
 *
 * @param mixed $type
 * @param mixed $hash
 * @param bool $relative return a relative or absolute path
 * @static
 * @return string the absolute/relative path to the corresponding cache file
 * @access protected
 */
	protected static function cachePath($type, $hash, $relative = false) {
		$debug = MiCompressor::cRead();
		$clear = MiCompressor::cRead('cacheClear');
		$return = $type . '_' . $hash . '_' . ($debug?'d':'p') . ($clear?'t':'p');
		$return = MiCompressor::cRead('cacheDir') . $return;
		if ($relative) {
			return str_replace(CACHE, '', $return);
		}
		return $return;
	}
/**
 * c(onfigure)Read method
 *
 * Write default settings as appropriate on first read
 * Use the Configure class if it exists, otherwise use default setting
 * First none-null result encountered wins.
 *
 * Call with multiple paramters naming fallback settings e.g.
 * $debug = MiCompressor::cRead('cacheClear', 'debug', 'Asset.someothersetting');
 * Again,  first none-null result wins.
 *
 * @param string $setting 'debug'
 * @param bool $prefix true
 * @static
 * @return mixed, config value
 * @access protected
 */
	protected static function cRead($setting = 'debug', $prefix = true) {
		if (!strpos($setting, '.') && $prefix) {
			$setting = 'MiCompressor.' . $setting;
		}
		if (!MiCompressor::$initialized) {
			MiCompressor::$initialized = true;

			$debug = MiCompressor::cRead('debug');
			if ($debug === null) {
				$debug = MiCompressor::cRead('debug', false);
			}
			if ($debug) {
				MiCompressor::$defaultSettings['MiCompressor.cacheDuration'] = '+10 minutes';
			} else {
				MiCompressor::$defaultSettings['MiCompressor.cacheDuration'] = '+1 year';
			}
			MiCompressor::$settings['MiCompressor.debug'] = $debug;
			unset (MiCompressor::$settings['debug']);

			MiCompressor::cRead('log', 'debug');
			MiCompressor::cRead('cacheClear', 'debug');

			$minify = MiCompressor::cRead('minify');
			if ($minify === null) {
				MiCompressor::$settings['MiCompressor.minify'] = !$debug;
			}

			$dir = MiCompressor::cRead('cacheDir');
			if (!$dir) {
				MiCompressor::$defaultSettings['MiCompressor.cacheDir'] = CACHE;
			}
			MiCompressor::$defaultSettings['MiCompressor.webrootDir'] = WWW_ROOT;
			MiCompressor::$defaultSettings['MiCompressor.cssDir'] = CSS;
			MiCompressor::$defaultSettings['MiCompressor.jsDir'] = JS;
			$salt = MiCompressor::cRead('salt');
			if (!$salt) {
				MiCompressor::$defaultSettings['MiCompressor.salt'] = SITE_VERSION;
			}

			if (!class_exists('App')) {
				MiCompressor::$defaultSettings['MiCompressor.bypassLoadMinifyLib'] = true;
			}
		}
		if (array_key_exists($setting, MiCompressor::$settings)) {
			return MiCompressor::$settings[$setting];
		}

		if (isset(MiCompressor::$defaultSettings[$setting])) {
			return MiCompressor::$settings[$setting] = MiCompressor::$defaultSettings[$setting];
		}

		if (class_exists('Configure')) {
			$return = Configure::read($setting);
			$fallbacks = func_get_args();
			array_shift($fallbacks);
			if ($return === null && $fallbacks) {
				$return = call_user_func_array(array('MiCompressor', 'cRead'), $fallbacks);
			}
			return MiCompressor::$settings[$setting] = $return;
		}

		$return = MiCompressor::$defaultSettings[$setting];
		$fallbacks = func_get_args();
		array_shift($fallbacks);
		if ($return === null && $fallbacks) {
			$return = call_user_func_array(array('MiCompressor', 'cRead'), $fallbacks);
		}
		return MiCompressor::$settings[$setting] = $return;
	}
/**
 * hash method
 *
 * Return the hash to be used for the passed arg
 *
 * @param mixed $arg array or string of requests
 * @static
 * @return string the hash of the passed arg
 * @access protected
 */
	protected static function hash($arg) {
		$salt = MiCompressor::cRead('salt');
		if (is_array($arg)) {
			$arg = implode('|', $arg);
		}
		if (function_exists('uses')) {
			uses('Security');
			return Security::hash($salt . $arg, null, true);
		}
		return sha1($salt . $arg);
	}
/**
 * loadMinifyLib method
 *
 * @param mixed $type
 * @static
 * @return bool true on success
 * @access protected
 */
	protected static function loadMinifyLib($type) {
		if (MiCompressor::cRead('bypassLoadMinifyLib')) {
			if (!class_exists('HTTP_ConditionalGet')) {
				MiCompressor::log('PROBLEM: HTTP_ConditionalGet (part of the Minify lib) not found, couldn\'t load the minify classes.');
				return false;
			}
			return true;
		}
		if (!class_exists('App')) {
			MiCompressor::log('PROBLEM: App class (part of CakePHP lib) not found, couldn\'t load the minify classes.');
		       	MiCompressor::log('	To Resolve set MiCompressor.bypassLoadMinifyLib to true and include the minify classes manually.');
			return false;
		}
		if($type === 'js') {
			return App::import('Vendor', $type . 'Min', array('file' => 'minify/lib/JSMin.php'));
		} elseif ($type === 'css') {
			ini_set('include_path', dirname(__FILE__) . '/minify/lib'. PATH_SEPARATOR . ini_get('include_path'));
			return App::import('Vendor', $type . 'Min', array('file' => 'minify/lib/Minify/CSS.php'));
		} else {
			return App::import('Vendor', $type . 'Min', array('file' => 'minify/lib/HTTP/ConditionalGet.php'));
		}
	}
/**
 * loadCssJqueryPlugin method
 *
 * @param string $plugin ''
 * @param bool $minify false
 * @return void
 * @access protected
 */
	protected static function loadCssJqueryPlugin($plugin = '', $minify = false) {
		if (isset(MiCompressor::$map['css']['jquery'][$plugin])) {
			$filename = MiCompressor::$map['css']['jquery'][$plugin];
		} else {
			$filename = "jquery.$plugin.css";
		}
		if (is_array($filename)) {
			$return = '';
			foreach($filename as $f) {
				$return .= MiCompressor::loadCssJqueryPlugin($f, $minify);
			}
			return $return;
		}
		if ($minify) {
			$found = true;
			$filename = str_replace('.css', '.min.css', $filename);
			if (file_exists(CSS . $filename)) {
				MiCompressor::log('	' . $filename);
				MiCompressor::log('	File ' . CSS . "$filename found");
				echo file_get_contents(CSS . $filename);
			} elseif (App::import('Vendor', 'css/jquery/' . $plugin, array('file' =>	"css/jquery/$filename"))) {
				MiCompressor::log('	' . $filename);
				MiCompressor::log("	Found vendor version css/jquery/$filename for Jquery $plugin");
			} elseif (App::import('Vendor', 'css/jquery/' . $plugin, array('file' =>	"css/$filename"))) {
				MiCompressor::log('	' . $filename);
				MiCompressor::log("	Found vendor version css/$filename for Jquery $plugin");
			} elseif (App::import('Vendor', 'css/jquery/' . $plugin, array('file' => "jquery/plugins/$plugin/$filename"))) {
				MiCompressor::log('	' . $filename);
				MiCompressor::log("	Found vendor version jquery/plugins/$plugin/$filename for Jquery $plugin");
			} else {
				$found = false;
			}
			if ($found) {
				return ob_get_clean();
			}
			$filename = str_replace('.min.css', '.css', $filename);
		}
		MiCompressor::log('	' . $filename);
		if (file_exists(CSS . $filename)) {
			MiCompressor::log('	File ' . CSS . "$filename found");
			echo file_get_contents(CSS . $filename);
		} elseif (App::import('Vendor', 'css/jquery/' . $plugin, array('file' =>	"css/jquery/$filename"))) {
			MiCompressor::log("	Found vendor version css/jquery/$filename for Jquery $plugin");
		} elseif (App::import('Vendor', 'css/jquery/' . $plugin, array('file' =>	"css/$filename"))) {
			MiCompressor::log("	Found vendor version css/$filename for Jquery $plugin");
		} elseif (App::import('Vendor', 'css/jquery/' . $plugin, array('file' => "jquery/plugins/$plugin/$filename"))) {
			MiCompressor::log("	Found vendor version jquery/plugins/$plugin/$filename for Jquery $plugin");
		} else {
			MiCompressor::log("	PROBLEM: The jquery plugin jquery/plugins/$plugin/$filename could not be loaded.");
		}
		return ob_get_clean();

	}
/**
 * loadFile method
 *
 * For the requested file, find it and return it. if minify is set to true send through minify(Css|Js) as
 * appropriate first.
 *
 * For CSS files, check for @import declarations and auto-correct any url() references in the file
 *
 * @param string $filename
 * @param mixed $params
 * @param bool $minify
 * @static
 * @return string the file's contents if appropriate
 * @access protected
 */
	protected static function loadFile($filename = '', $params = array(), $minify = false, $type = 'js') {
		if ($filename[0] === '/') {
			$base = MiCompressor::cRead('webrootDir');
		} elseif ($type === 'js') {
			$base = MiCompressor::cRead('jsDir');
		} elseif($type === 'css') {
			$base = MiCompressor::cRead('cssDir');
		} else {
			var_dump($base); die;
		}
		$file = $base . $filename . '.' . $type;
		$minFile = $base . $filename . '.min.' . $type;
		if (in_array($filename . '.' . $type, MiCompressor::$loadedFiles)) {
			MiCompressor::log("File $filename.type already loaded");
			return;
		}
		MiCompressor::$loadedFiles[] = $filename . '.' . $type;

		if ($minify && file_exists($minFile)) {
			$file = $minFile;
			MiCompressor::log("File $file found");
			return file_get_contents($file);
		} elseif (file_exists($file)) {
			MiCompressor::log("File $file found");
			$return = file_get_contents($file);
		} else {
			if ($filename[0] === '/') {
				$vendorFile = substr($filename . '.' . $type, 1);
			} else {
				$vendorFile = $type . '/' . $filename . '.' . $type;
			}
			ob_start();
			if (class_exists('App') &&
				!App::import('Vendor', str_replace('.', '_', $filename . $type), array('file' => $vendorFile))) {
				$minify = false;
				MiCompressor::log("PROBLEM: No file for $vendorFile could be found");
			}
			$return = ob_get_clean();
		}
		if ($type === 'css') {
			if (strpos($filename, '/', 1)) {
				$baseFolder = dirname($filename) . '/';
			} else {
				$baseFolder = '';
			}
			preg_match_all('/@import\s*(?:url\()?(?:["\'])([^"\']*)\.css(?:["\'])\)?;/', $return, $matches);
			foreach ($matches[1] as $i => $cssFile) {
				if ($minify) {
					$return = str_replace($matches[0][$i], '', $return);
					$return .= MiCompressor::loadFile($baseFolder . $cssFile, $params, $minify, 'css');
				} elseif ($baseFolder) {
					$replace = str_replace($cssFile, $baseFolder . $cssFile, $matches[0][$i]);
					$return = str_replace($matches[0][$i], $replace, $return);
				}
			}
			if ($baseFolder && strpos($return, 'url')) {
				preg_match_all('@url\s*\((?:[\s"\']*)([^\s"\']*)(?:[\s"\']*)\)@', $return, $matches);
				$corrected = false;
				$urls = array_unique($matches[1]);
				foreach ($urls as $url) {
					if (strpos($url, $baseFolder) !== 0 && $url[0] !== '/') {
						$corrected = true;
						$return = str_replace($url, $baseFolder . $url, $return);
					}
				}
				if ($corrected) {
					MiCompressor::log("\t Auto corrected url paths in $filename");
				}
			}
		} elseif ($minify) {
			$minifyMethod = 'minify' . Inflector::camelize($type);
			return MiCompressor::$minifyMethod($return, $filename);
		}
		return $return;
	}
/**
 * Bespoke load method for Jquery.
 *
 * Allow for loading the distributed, already minifyed, version of jquery; and load plugins from
 * parameters for ease in views. requesting 'mini.js?...|jquery,abc,xyz|...' will load jquery, with the abc and xyz
 * plugins
 *
 * @param array $plugins
 * @param bool $minify
 * @static
 * @return void
 * @access protected
 */
	protected static function loadJsJquery($plugins = array(), $minify = false) {
		ob_start();
		if ($minify && file_exists(JS . 'jquery.min.js')) {
			MiCompressor::log('	File ' . JS . 'jquery.min.js found');
			echo file_get_contents(JS . 'jquery.min.js');
		} elseif (file_exists(JS . 'jquery.js')) {
			MiCompressor::log('	File ' . JS . 'jquery.js found');
			$return = file_get_contents(JS . 'jquery.js');
			if ($minify) {
				MiCompressor::log('	WARNING: ' . JS . 'jquery.min.js not found. minifying on the fly');
				echo MiCompressor::minifyJs($return, 'jquery');
			} else {
				echo $return;
			}
		} elseif ($minify && App::import('Vendor', 'jquery', array('file' => 'jquery/jquery/dist/jquery.min.js'))) {
			MiCompressor::log('	Found vendor version for jquery/jquery/dist/jquery.min.js');
		} elseif (App::import('Vendor', 'jquery', array('file' => 'jquery/jquery/dist/jquery.js'))) {
			if ($minify) {
				MiCompressor::log('	WARNING: No vendor version for jquery/jquery/dist/jquery.min.js.'
					. ' minifying on the fly');
				$contents = ob_get_clean();
				ob_start();
				echo MiCompressor::minifyJs($contents, 'jquery', "\t");
			} else {
				MiCompressor::log('	Found vendor version for jquery/jquery/dist/jquery.js');
			}
		} else {
			if ($minify) {
				MiCompressor::log('	PROBLEM: No vendor version for jquery/jquery/dist/jquery.min.js');
			}
			MiCompressor::log('	PROBLEM: No vendor version for jquery/jquery/dist/jquery.js found');
			MiCompressor::log('	To correct this problem obtain jquery.js from jquery.com and place in your webroot OR');
			MiCompressor::log('	Run the following to generate from the jquery vendor folder:');
			MiCompressor::log('		cd /base/vendor/jquery/');
			MiCompressor::log('		make');
		}
		foreach ($plugins as $plugin) {
			echo MiCompressor::loadJsJqueryPlugin($plugin, $minify);
		}
		return ob_get_clean();
	}
/**
 * Bespoke load method for a Jquery plugin file
 *
 * @param stirng $plugin
 * @param bool $minify
 * @static
 * @return void
 * @access protected
 */
	protected static function loadJsJqueryPlugin($plugin = '', $minify = false) {
		if (isset(MiCompressor::$map['js']['jquery'][$plugin])) {
			$filename = MiCompressor::$map['js']['jquery'][$plugin];
		} else {
			$filename = "jquery.$plugin.js";
		}
		if (is_array($filename)) {
			$return = '';
			foreach($filename as $f) {
				$return .= MiCompressor::loadJsJqueryPlugin($f, $minify);
			}
			return $return;
		}

		$_minify = $minify;
		echo "\r\n";
		if ($minify) {
			$found = true;
			$filename = str_replace('.js', '.min.js', $filename);
			if (file_exists(JS . $filename)) {
				MiCompressor::log('	' . $filename);
				MiCompressor::log('	File ' . JS . "$filename found");
				echo file_get_contents(JS . $filename);
			} elseif (App::import('Vendor', 'js/jquery/' . $plugin, array('file' =>	"js/jquery/$filename"))) {
				MiCompressor::log('	' . $filename);
				MiCompressor::log("	Found vendor version js/jquery/$filename for Jquery $plugin");
			} elseif (App::import('Vendor', 'js/jquery/' . $plugin, array('file' =>	"js/$filename"))) {
				MiCompressor::log('	' . $filename);
				MiCompressor::log("	Found vendor version js/$filename for Jquery $plugin");
			} elseif (App::import('Vendor', 'js/jquery/' . $plugin, array('file' => "jquery/plugins/$plugin/$filename"))) {
				MiCompressor::log('	' . $filename);
				MiCompressor::log("	Found vendor version jquery/plugins/$plugin/$filename for Jquery $plugin");
			} else {
				$found = false;
			}
			if ($found) {
				echo ob_get_clean();
				return;
			}
			$filename = str_replace('.min.js', '.js', $filename);
		}
		ob_start();
		MiCompressor::log('	' . $filename);
		if (file_exists(JS . $filename)) {
			MiCompressor::log('	File ' . JS . "$filename found");
			echo file_get_contents(JS . $filename);
		} elseif (App::import('Vendor', 'js/jquery/' . $plugin, array('file' =>	"js/jquery/$filename"))) {
			MiCompressor::log("	Found vendor version js/jquery/$filename for Jquery $plugin");
		} elseif (App::import('Vendor', 'js/jquery/' . $plugin, array('file' =>	"js/$filename"))) {
			MiCompressor::log("	Found vendor version js/$filename for Jquery $plugin");
		} elseif (App::import('Vendor', 'jquery/' . $plugin, array('file' => "jquery/plugins/$plugin/$filename"))) {
			MiCompressor::log("	Found vendor version jquery/plugins/$plugin/$filename for Jquery $plugin");
		} else {
			MiCompressor::log("	PROBLEM: The jquery plugin jquery/plugins/$plugin/$filename could not be loaded.");
			$_minify = false;
		}
		if ($_minify) {
			$pluginContents = ob_get_clean();
			ob_start();
			echo MiCompressor::minifyJs($pluginContents, 'jquery.' . $plugin, "\t");
		}
		return ob_get_clean();
	}
/**
 * out method
 *
 * Send appropriate headers based on the request, and if necessary send the contents
 *
 * @param mixed $file
 * @param string $contents
 * @static
 * @return string the contents to output, or null if no output is to be sent (to trigger a 304 header upstream)
 * @access protected
 */
	protected static function out($file, $contents = null, $eTag = null) {
		if (preg_match('@^/\* eTag:(\S+) \*/@', $contents, $match)) {
			$eTag = $match['1'];
		}
		if ($eTag) {
			$contents = str_replace('/* eTag:' . $eTag .' */', '', $contents);
		}
		if (!MiCompressor::loadMinifyLib('headers')) {
			return $contents;
		}
		if (file_exists($file)) {
			$fileCreated = filectime($file);
		} else {
			$fileCreated = time();
		}
		$fileExpires = strtotime(MiCompressor::cRead('cacheDuration'), $fileCreated);

		$params = array(
			'lastModifiedTime' => $fileCreated,
			'setExpires' => $fileExpires
		);
		if ($eTag) {
			$params['eTag'] = $eTag;
		}
		$cg = new HTTP_ConditionalGet($params);
		$cg->sendHeaders();
		if ($cg->cacheIsValid) {
			return;
		}
		return $contents;
	}
/**
 * parse method
 *
 * Used to determine which component files, and sub files have been requested
 * For the request string of the form: file|file2|file3|file4,x,y=300,z|file5.. generate an array of the form:
 * array(
 * 	file => array(),
 * 	file2 => array(),
 * 	file3 => array(),
 *	file4 => array('x', 'y' => 300, 'z'),
 *	file5 => array()
 * )
 * This format allows passing parameters to scripts when included (accessible as $params);
 *
 * A hash is automatically added to the url by the MiHtml and MiJavascript helpers. The hash is checked in this
 * method and if it doesn't match (this should never happen for a valid request), will either add a log message if not
 * in production mode or return false. Compressing (in particular Js files) is expensive, therefore using a hash
 * prevents a malicious user from requesting arbritary varying urls and tying up the server
 *
 * The hash is returned primarily to be used as the default cache key
 *
 * @param string $requestString
 * @param mixed $hash
 * @static
 * @return mixed array of files to process, or false for an invalid/doctored request
 * @access protected
 */
	protected static function parse($requestString = '', &$hash = null) {
		if ($hash === null) {
			$requests = explode('|', $requestString);
			$hash = array_pop($requests);
		} else {
			$requests = explode('|', $requestString);
			if ($key = array_search('hash', $requests)) {
				unset ($requests[$key]);
				$hash = 'hash=' . $hash;
			}
		}
		$problem = false;
		if (strpos($hash, 'hash=') !== 0) {
			$problem = true;
			MiCompressor::log('PROBLEM: No hash in the request string');
		} else {
			list(,$hash) = explode('=', $hash);
			$_hash = MiCompressor::hash($requests);
			if ($hash !== $_hash) {
				$problem = true;
				MiCompressor::log('PROBLEM: Hash doesn\'t match. Expected: ' . $_hash);
			}
		}
		if ($problem && !MiCompressor::cRead()) {
			return false;
		}

		$return = array();
		foreach ($requests as $filename) {
			$params = array();
			if (strpos($filename, ',')) {
				$params = explode(',', $filename);
				$filename = array_shift($params);
				foreach ($params as $k => $v) {
					if (strpos($v, '=')) {
						unset ($params[$k]);
						list($k, $v) = explode('=', $v);
						$params[$k] = $v;
					}
				}
			}
			$return[$filename] = $params;
		}
		return $return;
	}
/**
 * url method
 *
 * @param mixed $files
 * @param string $type
 * @param bool $sizeLimit
 * @static
 * @return mixed the url or urls corresponding to the requested files
 * @access protected
 */
	protected static function _url($files, $type = 'js', $sizeLimit = false) {
		$string = implode('|', $files);
		$hash = MiCompressor::hash($string);
		if ($sizeLimit && count($files) > 1) {
			$cachePath = MiCompressor::cachePath($type, $hash);
			if (file_exists($cachePath) && filesize($cachePath) > ($sizeLimit - 45)) {
				foreach($files as $file) {
					$return[] = MiCompressor::_url(array($file), $type);
				}
				return $return;
			}
		}
		$string .= '|hash=' . $hash;
		return "/$type/mini.$type?" . $string;
	}
}
/**
 * Included for compatibility to allow use and testing in a standalone manner
 */
if (!defined('DS')) {
	define('DS', DIRECTORY_SEPARATOR);
}
if (!defined('WWW_ROOT')) {
	define('WWW_ROOT', dirname(__FILE__));
}
if (!defined('CSS')) {
	define('CSS', WWW_ROOT . 'css' . DS);
}
if (!defined('JS')) {
	define('JS', WWW_ROOT . 'js' . DS);
}
if (!defined('CACHE')) {
	define('CACHE', dirname(__FILE__) . DS . 'cache' . DS);
}
if (!defined('SITE_VERSION')) {
	define('SITE_VERSION', 42);
}

if (!function_exists('getMicrotime')) {
/**
 * Returns microtime for execution time checking
 *
 * @return float Microtime
 */
	function getMicrotime() {
		list($usec, $sec) = explode(" ", microtime());
		return ((float)$usec + (float)$sec);
	}
}
if (!function_exists('cache')) {
/**
 * cache method
 *
 * Duplicate of CakePHP cache method to allow using this script standalone
 *
 * @param mixed $path
 * @param mixed $data
 * @param mixed $expires null
 * @return string cache contents
 * @access public
 */
	function cache($path, $data, $expires = null) {
		$now = time();
		if (!is_numeric($expires)) {
			$expires = strtotime($expires, $now);
		}

		$filename = MiCompressor::cRead('cacheDir') . $path;
		$timediff = $expires - $now;
		$filetime = false;
		if (file_exists($filename)) {
			$filetime = @filemtime($filename);
		}
		if ($data === null) {
			if (file_exists($filename) && $filetime !== false) {
				if ($filetime + $timediff < $now) {
					@unlink($filename);
				} else {
					$data = @file_get_contents($filename);
				}
			}
		} elseif (is_writable(dirname($filename))) {
			@file_put_contents($filename, $data);
		}
		return $data;
	}
}
?>