0df41287b68ea3a9fa64ed17a94036725160f357

Author: AD7six

Date: 2009-02-13 13:32:19 +0100

adding cached, on the fly css and js compression

diff --git a/vendors/css/mini.css b/vendors/css/mini.css new file mode 100644 index 0000000..ca0f11b --- /dev/null +++ b/vendors/css/mini.css @@ -0,0 +1,33 @@ +<?php /* SVN FILE: $Id: mini.css 483 2008-10-29 10:12:11Z ad7six $ */ +/** + * mini.css is the entry point for concatonating, and optionally compressing, several css files into one response + * + * 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.css + * @since v 1.0 + * @version $Revision: 483 $ + * @modifiedby $LastChangedBy: ad7six $ + * @lastmodified $Date: 2008-10-29 11:12:11 +0100 (Wed, 29 Oct 2008) $ + * @license http://www.opensource.org/licenses/mit-license.php The MIT License + */ +App::import('Vendor', 'MiCompressor'); +list($_, $request) = explode('?', $_SERVER['REQUEST_URI']); +echo MiCompressor::serve(compact('request')); return; +echo MiCompressor::serve(array( + 'request' => $request, + 'compress' => Configure::read() < 2, + 'clear' => false, + 'log' => true, + 'type' => 'css' +)); +?> \ No newline at end of file diff --git a/vendors/js/mini.js b/vendors/js/mini.js new file mode 100644 index 0000000..a5c8024 --- /dev/null +++ b/vendors/js/mini.js @@ -0,0 +1,33 @@ +<?php /* SVN FILE: $Id: mini.js 483 2008-10-29 10:12:11Z ad7six $ */ +/** + * mini.js is the entry point for concatonating, and optionally compressing, several js files into one response + * + * 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.js + * @since v 1.0 + * @version $Revision: 483 $ + * @modifiedby $LastChangedBy: ad7six $ + * @lastmodified $Date: 2008-10-29 11:12:11 +0100 (Wed, 29 Oct 2008) $ + * @license http://www.opensource.org/licenses/mit-license.php The MIT License + */ +App::import('Vendor', 'MiCompressor'); +list($_, $request) = explode('?', $_SERVER['REQUEST_URI']); +echo MiCompressor::serve(compact('request')); return; +echo MiCompressor::serve(array( + 'request' => $request, + 'compress' => Configure::read() < 2, + 'clear' => true, + 'log' => true, + 'type' => 'js' +)); +?> \ No newline at end of file diff --git a/vendors/mi_compressor.php b/vendors/mi_compressor.php new file mode 100644 index 0000000..bfbc3c8 --- /dev/null +++ b/vendors/mi_compressor.php @@ -0,0 +1,716 @@ +<?php +/* SVN FILE: $Id: mi_compressor.php 736 2009-01-16 16:54:44Z ad7six $ */ +/** + * Short description for mi_compressor.php + * + * Long description for mi_compressor.php + * + * 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: 736 $ + * @modifiedby $LastChangedBy: ad7six $ + * @lastmodified $Date: 2009-01-16 17:54:44 +0100 (Fri, 16 Jan 2009) $ + * @license http://www.opensource.org/licenses/mit-license.php The MIT License + */ +/** + * MiCompressor class + * + * Compress 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 + * + * @abstract + * @package base + * @subpackage base.vendors + */ +abstract class MiCompressor { +/** + * start property + * + * @static + * @var mixed null + * @access public + */ + public static $start = null; +/** + * cacheDuration property + * + * @static + * @var string '+1 year' + * @access public + */ + public static $cacheDuration = '+1 year'; +/** + * 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' + ) + ) + ); +/** + * compressCss method + * + * @param mixed $css + * @param string $file + * @param string $logPrefix + * @static + * @return void + * @access public + */ + public static function compressCss($css, $file = '', $logPrefix = '') { + MiCompressor::log("$logPrefix compress $file.css"); + return Minify_CSS::minify($css); + } +/** + * compressJs method + * + * This can be rather intensive - use with care + * + * @TODO don't strip license blocks + * @param mixed $js + * @param string $file + * @param string $logPrefix + * @static + * @return void + * @access public + */ + public static function compressJs($js, $file = '', $logPrefix = '') { + MiCompressor::log("$logPrefix compressing $file.js"); + return JSMin::minify($js); + } +/** + * 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 void + * @access public + */ + public static function log($string = null) { + if (MiCompressor::$start === null) { + MiCompressor::$start = getMicrotime(); + } + static $log = array(); + if ($string === null) { + $log = am(array( + 'MiCompressor log - only generated in debug mode. Generated ' . date("D, M jS Y, H:i:s"), + null), $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; + } +/** + * 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 public + */ + public static function parse($requestString = '', &$hash = null) { + $debug = (class_exists('Configure')?Configure::read():false); + $requests = explode('|', $requestString); + $hash = array_pop($requests); + $problem = false; + if (strpos($hash, 'hash=') !== 0) { + $problem = true; + MiCompressor::log('PROBLEM: No hash in the request string'); + } else { + list(,$hash) = explode('=', $hash); + uses('Security'); + $salt = defined('SITE_VERSION')?SITE_VERSION:''; + if ($hash != Security::hash($salt . implode('|', $requests), null, true)) { + $problem = true; + MiCompressor::log('PROBLEM: Hash doesn\'t match'); + } + } + if ($problem && $debug) { + 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; + } +/** + * process method + * + * For each of the requested files, find them, concatonate them - if requested compress them - and return + * For js files, each individual file is compressed. For css, their combined contents are compressed + * + * @param mixed $files + * @param mixed $type + * @param mixed $compress + * @static + * @return string the files' contents, optionally compressed, as a string + * @access public + */ + public static function process($files, $type = null, $compress = null) { + if ($type === null) { + if (strpos($_GET['url'], 'js/') === 0) { + $type = 'js'; + } else { + $type = 'css'; + } + } + $debug = (class_exists('Configure')?Configure::read():false); + if ($compress === null) { + $compress = !$debug; + } + if ($compress && !MiCompressor::loadCompressLib($type)) { + MiCompressor::log("PROBLEM: Unable to load $type compressor. No minifying"); + $compress = false; + } + + $files = (array)$files; + $return = ''; + if ($type === 'css') { + $_compress = $compress; + $compress = false; + } + 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); + } + $method = 'load' . Inflector::camelize($type) . Inflector::camelize($filename); + if (method_exists('MiCompressor', $method)) { + MiCompressor::log("Loading $filename.$type with method $method"); + $return .= MiCompressor::$method($params, $compress) . "\r\n"; + } else { + MiCompressor::log("Loading $filename.$type with method loadFile"); + $return .= MiCompressor::loadFile($filename, $params, $compress, $type) . "\r\n"; + + } + } + if (!empty($_compress) && $type === 'css') { + $return = MiCompressor::compressCss($return, 'combined'); + } + 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 + * + * If in debug mode, the cache duration is set to +10 minutes. otherwise it is +1 year + * $compress defaults to true in production mode, false in debug mode + * $log defaults to true in production mode, false in debug mode + * $cachePath defaults to $type _ the hash _ d(ebug)|p(roduction) . t(emp)|p(ermanent) + * $clear defaults to false in production mode, true in debug mode + * + * A cache file is always generated, but if $clear is true, the cache is cleared for all cache files of the same type + * The log is always generated - but it's only included in the content if $log is true. Otherwise it is available + * for debugging purposes + * + * @param string $request + * @param mixed $type + * @param mixed $compress + * @param mixed $log + * @param mixed $cachePath + * @param mixed $clear + * @static + * @return void + * @access public + */ + public static function serve($request = '', $type = null, $compress = null, $log = null, $cachePath = null, $clear = null) { + if (is_array($request)) { + extract($request); + } + MiCompressor::log('Request String: ' . $request); + $start = getMicrotime(); + ob_start(); + if (@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'; + } + } + $appDebug = (class_exists('Configure')?Configure::read():false); + if ($compress === null) { + if (!class_exists('Configure')) { + $compress = true; + } else { + $compress = Configure::read('Asset.compress.' . $type); + if ($compress === null) { + $compress = Configure::read('Asset.compress'); + } + if ($compress === null) { + $compress = !$appDebug; + } + } + } + if ($log === null) { + $log = $appDebug; + } + if ($clear === null) { + $clear = $appDebug; + } + if ($cachePath === null) { + $cachePath = MiCompressor::__cachePath($type, $hash, $appDebug, $clear, true); + } + MiCompressor::log('Cache Path: ' . $cachePath); + if ($appDebug) { + MiCompressor::$cacheDuration = '+10 minutes'; + } + + if ($clear && function_exists('uses')) { + uses('Folder'); + if (class_exists('Folder')) { + $folder = new Folder(CACHE . 'views/'); + $files = $folder->find($type . '_' . '.*'); + foreach ($files as $file) { + MiCompressor::log("Removing Cache File " . $file); + @unlink(CACHE . 'views/' . $file); + } + } + } + if ($cachePath) { + $cached = cache($cachePath, null, MiCompressor::$cacheDuration); + if ($cached) { + MiCompressor::log("Cache File: $cachePath found and returned"); + return MiCompressor::out($cachePath, $cached); + } + } + $return = MiCompressor::process($requests, $type, $compress); + $eTag = md5($return); + if ($cachePath) { + MiCompressor::log("Generating cache file $cachePath to expire in " . MiCompressor::$cacheDuration); + } + MiCompressor::log('eTag: ' . $eTag); + MiCompressor::log('Finished'); + if ($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::$cacheDuration); + } + return MiCompressor::out(CACHE . $cachePath, $return); + } +/** + * loadCompressLib method + * + * @param mixed $type + * @static + * @return void + * @access private + */ + private static function loadCompressLib($type) { + 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:' . 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')); + } + } +/** + * loadFile method + * + * For the requested file, find it and return it. if compress is set to true send through compress(Css|Js) as + * appropriate first + * + * @param string $filename + * @param mixed $params + * @param bool $compress + * @static + * @return void + * @access private + */ + private static function loadFile($filename = '', $params = array(), $compress = false, $type = 'js') { + if ($type === 'js') { + $base = JS; + } elseif($type === 'css') { + $base = CSS; + } else { + var_dump($base); die; + } + $file = $base . $filename . '.' . $type; + if ($compress && file_exists($base . $filename . '.min.' . $type)) { + $file = $base . $filename . '.min.' . $type; + 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 { + ob_start(); + if (!App::import('Vendor', str_replace('.', '_', $filename . $type), array('file' => $type . '/' . $filename . '.' . $type))) { + $compress = false; + MiCompressor::log("PROBLEM: No file for $type/$filename.{$type} could be found"); + } + $return = ob_get_clean(); + } + if ($compress) { + $compressMethod = 'compress' . Inflector::camelize($type); + return MiCompressor::$compressMethod($return, $filename); + } + return $return; + } +/** + * Bespoke load method for Jquery. + * + * Allow for loading the distributed, already compressed, 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 $compress + * @static + * @return void + * @access private + */ + private static function loadJsJquery($plugins = array(), $compress = false) { + ob_start(); + if ($compress && 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 ($compress) { + MiCompressor::log(' WARNING: ' . JS . 'jquery.min.js not found. compressing on the fly'); + echo MiCompressor::compressJs($return, 'jquery'); + } else { + echo $return; + } + } elseif ($compress && 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 ($compress) { + MiCompressor::log(' WARNING: No vendor version for jquery/jquery/dist/jquery.min.js.' + . ' compressing on the fly'); + $contents = ob_get_clean(); + ob_start(); + echo MiCompressor::compressJs($contents, 'jquery', "\t"); + } else { + MiCompressor::log(' Found vendor version for jquery/jquery/dist/jquery.js'); + } + } else { + if ($compress) { + 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) { + if (isset(MiCompressor::$map['js']['jquery'][$plugin])) { + $filename = MiCompressor::$map['js']['jquery'][$plugin]; + } else { + $filename = "jquery.$plugin.js"; + } + $_compress = $compress; + ob_start(); + echo "\r\n"; + if ($compress) { + $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', '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(); + continue; + } + $filename = str_replace('.min.js', '.js', $filename); + } + 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(' ' . $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', '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."); + $_compress = false; + } + if ($_compress) { + $pluginContents = ob_get_clean(); + ob_start(); + echo MiCompressor::compressJs($pluginContents, 'jquery.' . $plugin, "\t"); + } + echo ob_get_clean(); + } + 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 private + */ + private static function out($file, $contents = null, $eTag = null) { + if ($contents === null) { + $contents = file_get_contents($file); + debug ($contents); + die; + } + if (preg_match('@^/\* eTag:(\S+) \*/@', $contents, $match)) { + $eTag = $match['1']; + } + if ($eTag) { + $contents = str_replace('/* eTag:' . $eTag .' */', '', $contents); + } + if (!MiCompressor::loadCompressLib('headers')) { + return $contents; + } + if (file_exists($file)) { + $fileCreated = filectime($file); + } else { + $fileCreated = time(); + } + $fileExpires = strtotime(MiCompressor::$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; + } +/** + * url method + * + * @param array $request + * @param array $params + * @return void + * @access public + */ + function url($request = array(), $params = array()) { + extract(am(array( + 'salt' => defined('SITE_VERSION')?SITE_VERSION:'', + 'sendAlone' => Configure::read(), + '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, $salt, $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 mixed $appDebug + * @param bool $clear + * @param bool $relative + * @return void + * @access private + */ + function __cachePath($type, $hash, $appDebug = null, $clear = false, $relative = false) { + if ($appDebug === null) { + $appDebug = (class_exists('Configure')?Configure::read():false); + } + if ($clear === null) { + $clear = $appDebug; + } + $return = 'views/' . $type . '_' . $hash . '_' . ($appDebug?'d':'p') . ($clear?'t':'p'); + if ($relative) { + return $return; + } + return CACHE . $return; + } +/** + * url method + * + * @param mixed $files + * @param string $type + * @param string $salt + * @param bool $sizeLimit + * @return void + * @access private + */ + function __url($files, $type = 'js', $salt = '', $sizeLimit = false) { + $string = implode('|', $files); + $hash = Security::hash($salt . $string, null, true); + 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, $salt); + } + return $return; + } + } + $string .= '|hash=' . $hash; + return "/$type/mini.$type?" . $string; + } +} +/** + * Included for compatibility to allow use and testing in a standalone manner + */ +if (!defined('CACHE')) { + define('CACHE', 'cache'); + define('CSS', 'css'); + define('JS', 'js'); +} +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); + } +} +?> \ No newline at end of file diff --git a/vendors/minify/.htaccess b/vendors/minify/.htaccess new file mode 100644 index 0000000..42f13eb --- /dev/null +++ b/vendors/minify/.htaccess @@ -0,0 +1,4 @@ +<IfModule mod_rewrite.c> +RewriteEngine on +RewriteRule ^([a-z]=.*) index.php?$1 [L,NE] +</IfModule> \ No newline at end of file diff --git a/vendors/minify/README.txt b/vendors/minify/README.txt new file mode 100644 index 0000000..a7cf774 --- /dev/null +++ b/vendors/minify/README.txt @@ -0,0 +1,132 @@ +The files in this directory represent the default Minify setup designed to ease +integration with your site. This app will combine and minify your Javascript or +CSS files and serve them with HTTP compression and cache headers. + + +RECOMMENDED + +It's recommended to edit config.php to set $min_cachePath to a writeable +(by PHP) directory on your system. This will improve performance. + + +GETTING STARTED + +The quickest way to get started is to use the Minify URI Builder application +on your website: http://example.com/min/builder/ + + +MINIFYING A SINGLE FILE + +Let's say you want to serve this file: + http://example.com/wp-content/themes/default/default.css + +Here's the "Minify URL" for this file: + http://example.com/min/?f=wp-content/themes/default/default.css + +In other words, the "f" argument is set to the file path from root without the +initial "/". As CSS files may contain relative URIs, Minify will automatically +"fix" these by rewriting them as root relative. + + +COMBINING MULTIPLE FILES IN ONE DOWNLOAD + +Separate the paths given to "f" with commas. + +Let's say you have CSS files at these URLs: + http://example.com/scripts/jquery-1.2.6.js + http://example.com/scripts/site.js + +You can combine these files through Minify by requesting this URL: + http://example.com/min/?f=scripts/jquery-1.2.6.js,scripts/site.js + + +SIMPLIFYING URLS WITH A BASE PATH + +If you're combining files that share the same ancestor directory, you can use +the "b" argument to set the base directory for the "f" argument. Do not include +the leading or trailing "/" characters. + +E.g., the following URLs will serve the exact same content: + http://example.com/min/?f=scripts/jquery-1.2.6.js,scripts/site.js,scripts/home.js + http://example.com/min/?b=scripts&f=jquery-1.2.6.js,site.js,home.js + + +MINIFY URLS IN HTML + +In (X)HTML files, don't forget to replace any "&" characters with "&amp;". + + +SPECIFYING ALLOWED DIRECTORIES + +By default, Minify will serve any *.css/*.js files within the DOCUMENT_ROOT. If +you'd prefer to limit Minify's access to certain directories, set the +$min_serveOptions['minApp']['allowDirs'] array in config.php. E.g. to limit +to the /js and /themes/default directories, use: + +$min_serveOptions['minApp']['allowDirs'] = array('//js', '//themes/default'); + + +GROUPS: FASTER PERFORMANCE AND BETTER URLS + +For the best performance, edit groupsConfig.php to pre-specify groups of files +to be combined under preset keys. E.g., here's an example configuration in +groupsConfig.php: + +return array( + 'js' => array('//js/Class.js', '//js/email.js') +); + +This pre-selects the following files to be combined under the key "js": + http://example.com/js/Class.js + http://example.com/js/email.js + +You can now serve these files with this simple URL: + http://example.com/min/?g=js + + +GROUPS: SPECIFYING FILES OUTSIDE THE DOC_ROOT + +In the groupsConfig.php array, the "//" in the file paths is a shortcut for +the DOCUMENT_ROOT, but you can also specify paths from the root of the filesystem +or relative to the DOC_ROOT: + +return array( + 'js' => array( + '//js/file.js' // file within DOC_ROOT + ,'//../file.js' // file in parent directory of DOC_ROOT + ,'C:/Users/Steve/file.js' // file anywhere on filesystem + ) +); + + +FAR-FUTURE EXPIRES HEADERS + +Minify can send far-future (one year) Expires headers. To enable this you must +add a number to the querystring (e.g. /min/?g=js&1234 or /min/f=file.js&1234) +and alter it whenever a source file is changed. If you have a build process you +can use a build/source control revision number. + +If you serve files as a group, you can use the utility function Minify_groupUri() +to get a "versioned" Minify URI for use in your HTML. E.g.: + +<?php +// add /min/lib to your include_path first! +require $_SERVER['DOCUMENT_ROOT'] . '/min/utils.php'; + +$jsUri = Minify_groupUri('js'); +echo "<script type='text/javascript' src='{$jsUri}'></script>"; + + +DEBUG MODE + +In debug mode, instead of compressing files, Minify sends combined files with +comments prepended to each line to show the line number in the original source +file. To enable this, set $min_allowDebugFlag to true in config.php and append +"&debug=1" to your URIs. E.g. /min/?f=script1.js,script2.js&debug=1 + +Known issue: files with comment-like strings/regexps can cause problems in this mode. + + +QUESTIONS? + +http://groups.google.com/group/minify \ No newline at end of file diff --git a/vendors/minify/addExplicitEtag.diff b/vendors/minify/addExplicitEtag.diff new file mode 100644 index 0000000..11349aa --- /dev/null +++ b/vendors/minify/addExplicitEtag.diff @@ -0,0 +1,33 @@ +Index: lib/HTTP/ConditionalGet.php +=================================================================== +--- lib/HTTP/ConditionalGet.php (revision 255) ++++ lib/HTTP/ConditionalGet.php (working copy) +@@ -109,16 +109,18 @@ + $_SERVER['REQUEST_TIME'] + $spec['maxAge'] + ); + } +- if (isset($spec['lastModifiedTime'])) { +- // base both headers on time +- $this->_setLastModified($spec['lastModifiedTime']); +- $this->_setEtag($spec['lastModifiedTime'], $scope); +- } else { +- // hope to use ETag +- if (isset($spec['contentHash'])) { +- $this->_setEtag($spec['contentHash'], $scope); +- } +- } ++ if (isset($spec['lastModifiedTime'])) { ++ $this->_setLastModified($spec['lastModifiedTime']); ++ if (isset($spec['eTag'])) { // Use it ++ $this->_setEtag($spec['eTag'], $scope); ++ } else { // base both headers on time ++ $this->_setEtag($spec['lastModifiedTime'], $scope); ++ } ++ } elseif (isset($spec['eTag'])) { // Use it ++ $this->_setEtag($spec['eTag'], $scope); ++ } elseif (isset($spec['contentHash'])) { // Use the hash as the ETag ++ $this->_setEtag($spec['contentHash'], $scope); ++ } + $this->_headers['Cache-Control'] = "max-age={$maxAge}, {$scope}, must-revalidate"; + // invalidate cache if disabled, otherwise check + $this->cacheIsValid = (isset($spec['invalidate']) && $spec['invalidate']) diff --git a/vendors/minify/builder/_index.js b/vendors/minify/builder/_index.js new file mode 100644 index 0000000..8e5313a --- /dev/null +++ b/vendors/minify/builder/_index.js @@ -0,0 +1,242 @@ +var MUB = { + _uid : 0 + ,_minRoot : '/min/?' + ,checkRewrite : function () { + var testUri = location.pathname.replace(/\/[^\/]*$/, '/rewriteTest.js').substr(1); + function fail() { + $('#minRewriteFailed')[0].className = 'topNote'; + }; + $.ajax({ + url : '../f=' + testUri + '&' + (new Date()).getTime() + ,success : function (data) { + if (data === '1') { + MUB._minRoot = '/min/'; + $('span.minRoot').html('/min/'); + } else + fail(); + } + ,error : fail + }); + } + /** + * Get markup for new source LI element + */ + ,newLi : function () { + return '<li id="li' + MUB._uid + '">http://' + location.host + '/<input type=text size=20>' + + ' <button title="Remove">x</button> <button title="Include Earlier">&uarr;</button>' + + ' <button title="Include Later">&darr;</button> <span></span></li>'; + } + /** + * Add new empty source LI and attach handlers to buttons + */ + ,addLi : function () { + $('#sources').append(MUB.newLi()); + var li = $('#li' + MUB._uid)[0]; + $('button[title=Remove]', li).click(function () { + $('#results').hide(); + var hadValue = !!$('input', li)[0].value; + $(li).remove(); + }); + $('button[title$=Earlier]', li).click(function () { + $(li).prev('li').find('input').each(function () { + $('#results').hide(); + // this = previous li input + var tmp = this.value; + this.value = $('input', li).val(); + $('input', li).val(tmp); + MUB.updateAllTestLinks(); + }); + }); + $('button[title$=Later]', li).click(function () { + $(li).next('li').find('input').each(function () { + $('#results').hide(); + // this = next li input + var tmp = this.value; + this.value = $('input', li).val(); + $('input', li).val(tmp); + MUB.updateAllTestLinks(); + }); + }); + ++MUB._uid; + } + /** + * In the context of a source LI element, this will analyze the URI in + * the INPUT and check the URL on the site. + */ + ,liUpdateTestLink : function () { // call in context of li element + if (! $('input', this)[0].value) + return; + var li = this; + $('span', this).html(''); + var url = 'http://' + location.host + '/' + + $('input', this)[0].value.replace(/^\//, ''); + $.ajax({ + url : url + ,complete : function (xhr, stat) { + if ('success' == stat) + $('span', li).html('&#x2713;'); + else { + $('span', li).html('<button><b>404! </b> recheck</button>') + .find('button').click(function () { + MUB.liUpdateTestLink.call(li); + }); + } + } + ,dataType : 'text' + }); + } + /** + * Check all source URLs + */ + ,updateAllTestLinks : function () { + $('#sources li').each(MUB.liUpdateTestLink); + } + /** + * In a given array of strings, find the character they all have at + * a particular index + * @param Array arr array of strings + * @param Number pos index to check + * @return mixed a common char or '' if any do not match + */ + ,getCommonCharAtPos : function (arr, pos) { + var i + ,l = arr.length + ,c = arr[0].charAt(pos); + if (c === '' || l === 1) + return c; + for (i = 1; i < l; ++i) + if (arr[i].charAt(pos) !== c) + return ''; + return c; + } + /** + * Get the shortest URI to minify the set of source files + * @param Array sources URIs + */ + ,getBestUri : function (sources) { + var pos = 0 + ,base = '' + ,c; + while (true) { + c = MUB.getCommonCharAtPos(sources, pos); + if (c === '') + break; + else + base += c; + ++pos; + } + base = base.replace(/[^\/]+$/, ''); + var uri = MUB._minRoot + 'f=' + sources.join(','); + if (base.charAt(base.length - 1) === '/') { + // we have a base dir! + var basedSources = sources + ,i + ,l = sources.length; + for (i = 0; i < l; ++i) { + basedSources[i] = sources[i].substr(base.length); + } + base = base.substr(0, base.length - 1); + var bUri = MUB._minRoot + 'b=' + base + '&f=' + basedSources.join(','); + //window.console && console.log([uri, bUri]); + uri = uri.length < bUri.length + ? uri + : bUri; + } + return uri; + } + /** + * Create the Minify URI for the sources + */ + ,update : function () { + MUB.updateAllTestLinks(); + var sources = [] + ,ext = false + ,fail = false; + $('#sources input').each(function () { + var m, val; + if (! fail && this.value && (m = this.value.match(/\.(css|js)$/))) { + var thisExt = m[1]; + if (ext === false) + ext = thisExt; + else if (thisExt !== ext) { + fail = true; + return alert('extensions must match!'); + } + this.value = this.value.replace(/^\//, ''); + if (-1 != $.inArray(this.value, sources)) { + fail = true; + return alert('duplicate file!'); + } + sources.push(this.value); + } + }); + if (fail || ! sources.length) + return; + $('#groupConfig').val(" 'keyName' => array('//" + sources.join("', '//") + "'),"); + var uri = MUB.getBestUri(sources) + ,uriH = uri.replace(/</, '&lt;').replace(/>/, '&gt;').replace(/&/, '&amp;'); + $('#uriA').html(uriH)[0].href = uri; + $('#uriHtml').val( + ext === 'js' + ? '<script type="text/javascript" src="' + uriH + '"></script>' + : '<link type="text/css" rel="stylesheet" href="' + uriH + '" />' + ); + $('#results').show(); + } + /** + * Handler for the "Add file +" button + */ + ,addButtonClick : function () { + $('#results').hide(); + MUB.addLi(); + MUB.updateAllTestLinks(); + $('#update').show().click(MUB.update); + $('#sources li:last input')[0].focus(); + } + /** + * Runs on DOMready + */ + ,init : function () { + $('#app').show(); + $('#sources').html(''); + $('#add button').click(MUB.addButtonClick); + // make easier to copy text out of + $('#uriHtml, #groupConfig').click(function () { + this.select(); + }).focus(function () { + this.select(); + }); + $('a.ext').attr({target:'_blank'}); + if (location.hash) { + // make links out of URIs from bookmarklet + $('#getBm').hide(); + $('#bmUris').html('<p><strong>Found by bookmarklet:</strong> /<a href=#>' + + location.hash.substr(1).split(',').join('</a> | /<a href=#>') + + '</a></p>' + ); + $('#bmUris a').click(function () { + MUB.addButtonClick(); + $('#sources li:last input').val(this.innerHTML) + MUB.liUpdateTestLink.call($('#sources li:last')[0]); + $('#results').hide(); + return false; + }).attr({title:'Add file +'}); + } else { + // copy bookmarklet code into href + var bmUri = location.pathname.replace(/\/[^\/]*$/, '/bm.js').substr(1); + $.ajax({ + url : '../?f=' + bmUri + ,success : function (code) { + $('#bm')[0].href = code + .replace('%BUILDER_URL%', location.href) + .replace(/\n/g, ' '); + } + ,dataType : 'text' + }); + $.browser.msie && $('#getBm p:last').append(' Sorry, not supported in MSIE!'); + MUB.addButtonClick(); + } + MUB.checkRewrite(); + } +}; +window.onload = MUB.init; \ No newline at end of file diff --git a/vendors/minify/builder/bm.js b/vendors/minify/builder/bm.js new file mode 100644 index 0000000..10d1943 --- /dev/null +++ b/vendors/minify/builder/bm.js @@ -0,0 +1,36 @@ +javascript:(function() { + var d = document + ,uris = [] + ,i = 0 + ,o + ,home = (location + '').split('/').splice(0, 3).join('/') + '/'; + function add(uri) { + return (0 === uri.indexOf(home)) + && (!/[\?&]/.test(uri)) + && uris.push(escape(uri.substr(home.length))); + }; + function sheet(ss) { + // we must check the domain with add() before accessing ss.cssRules + // otherwise a security exception will be thrown + if (ss.href && add(ss.href) && ss.cssRules) { + var i = 0, r; + while (r = ss.cssRules[i++]) + r.styleSheet && sheet(r.styleSheet); + } + }; + while (o = d.getElementsByTagName('script')[i++]) + o.src && !(o.type && /vbs/i.test(o.type)) && add(o.src); + i = 0; + while (o = d.styleSheets[i++]) + /* http://www.w3.org/TR/DOM-Level-2-Style/stylesheets.html#StyleSheets-DocumentStyle-styleSheets + document.styleSheet is a list property where [0] accesses the 1st element and + [outOfRange] returns null. In IE, styleSheets is a function, and also throws an + exception when you check the out of bounds index. (sigh) */ + sheet(o); + if (uris.length) + window.open('%BUILDER_URL%#' + uris.join(',')); + else + alert('No js/css files found with URLs within "' + + home.split('/')[2] + + '".\n(This tool is limited to URLs with the same domain.)'); +})(); \ No newline at end of file diff --git a/vendors/minify/builder/index.php b/vendors/minify/builder/index.php new file mode 100644 index 0000000..a37d70b --- /dev/null +++ b/vendors/minify/builder/index.php @@ -0,0 +1,178 @@ +<?php + +// check for auto-encoding +$encodeOutput = (function_exists('gzdeflate') + && !ini_get('zlib.output_compression')); + +require dirname(__FILE__) . '/../config.php'; + +if (! $min_enableBuilder) { + header('Location: /'); + exit(); +} + +ob_start(); +?> +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd"> +<head> + <meta name="ROBOTS" content="NOINDEX, NOFOLLOW"> + <title>Minify URI Builder</title> + <style type="text/css"> +body {margin:1em 60px;} +h1, h2, h3 {margin-left:-25px; position:relative;} +h1 {margin-top:0;} +#sources {margin:0; padding:0;} +#sources li {margin:0 0 0 40px} +#sources li input {margin-left:2px} +#add {margin:5px 0 1em 40px} +.hide {display:none} +#uriTable {border-collapse:collapse;} +#uriTable td, #uriTable th {padding-top:10px;} +#uriTable th {padding-right:10px;} +#groupConfig {font-family:monospace;} +b {color:#c00} +.topNote {background: #ff9; display:inline-block; padding:.5em .6em; margin:0 0 1em;} +.topWarning {background:#c00; color:#fff; padding:.5em .6em; margin:0 0 1em;} + </style> +</head> + +<?php if (! isset($min_cachePath)): ?> +<p class=topNote><strong>Note:</strong> Please set <code>$min_cachePath</code> +in /min/config.php to improve performance.</p> +<?php endIf; ?> + +<p id=minRewriteFailed class="hide"><strong>Note:</strong> Your webserver does not seem to + support mod_rewrite (used in /min/.htaccess). Your Minify URIs will contain "?", which +<a href="http://www.stevesouders.com/blog/2008/08/23/revving-filenames-dont-use-querystring/" +>may reduce the benefit of proxy cache servers</a>.</p> + +<h1>Minify URI Builder</h1> + +<noscript><p class="topNote">Javascript and a browser supported by jQuery 1.2.6 is required +for this application.</p></noscript> + +<div id=app class=hide> + +<p>Create a list of Javascript or CSS files (or 1 is fine) you'd like to combine +and click [Update].</p> + +<ol id=sources><li></li></ol> +<div id=add><button>Add file +</button></div> + +<div id=bmUris></div> + +<p><button id=update class=hide>Update</button></p> + +<div id=results class=hide> + +<h2>Minify URI</h2> +<p>Place this URI in your HTML to serve the files above combined, minified, compressed and +with cache headers.</p> +<table id=uriTable> + <tr><th>URI</th><td><a id=uriA class=ext>/min</a> <small>(opens in new window)</small></td></tr> + <tr><th>HTML</th><td><input id=uriHtml type=text size=100 readonly></td></tr> +</table> + +<h2>How to serve these files as a group</h2> +<p>For the best performance you can serve these files as a pre-defined group with a URI +like: <code><span class=minRoot>/min/?</span>g=keyName</code></p> +<p>To do this, add a line like this to /min/groupsConfig.php:</p> + +<pre><code>return array( + <span style="color:#666">... your existing groups here ...</span> +<input id=groupConfig size=100 type=text readonly> +);</code></pre> + +<p><em>Make sure to replace <code>keyName</code> with a unique key for this group.</em></p> +</div> + +<div id=getBm> +<h3>Find URIs on a Page</h3> +<p>You can use the bookmarklet below to fetch all CSS &amp; Javascript URIs from a page +on your site. When you active it, this page will open in a new window with a list of +available URIs to add.</p> + +<p><a id=bm>Create Minify URIs</a> <small>(right-click, add to bookmarks)</small></p> +</div> + +<h3>Combining CSS files that contain <code>@import</code></h3> +<p>If your CSS files contain <code>@import</code> declarations, Minify will not +remove them. Therefore, you will want to remove those that point to files already +in your list, and move any others to the top of the first file in your list +(imports below any styles will be ignored by browsers as invalid).</p> +<p>If you desire, you can use Minify URIs in imports and they will not be touched +by Minify. E.g. <code>@import "<span class=minRoot>/min/?</span>g=css2";</code></p> + +</div><!-- #app --> + +<hr> +<p>Need help? Search or post to the <a class=ext +href="http://groups.google.com/group/minify">Minify discussion list</a>.</p> +<p><small>This app is minified :) <a class=ext +href="http://code.google.com/p/minify/source/browse/trunk/min/builder/index.php">view +source</a></small></p> + +<script type="text/javascript" +src="http://ajax.googleapis.com/ajax/libs/jquery/1.2.6/jquery.min.js"></script> + +<script type="text/javascript"> +$(function () { + // detection of double output encoding + var msg = '<\p class=topWarning><\strong>Warning:<\/strong> '; + var url = 'ocCheck.php?' + (new Date()).getTime(); + $.get(url, function (ocStatus) { + $.get(url + '&hello=1', function (ocHello) { + if (ocHello != 'World!') { + msg += 'It appears output is being automatically compressed, interfering ' + + ' with Minify\'s own compression. '; + if (ocStatus == '1') + msg += 'The option "zlib.output_compression" is enabled in your PHP configuration. ' + + 'Minify set this to "0", but it had no effect. This option must be disabled ' + + 'in php.ini or .htaccess.'; + else + msg += 'The option "zlib.output_compression" is disabled in your PHP configuration ' + + 'so this behavior is likely due to a server option.'; + $(document.body).prepend(msg + '<\/p>'); + } else + if (ocStatus == '1') + $(document.body).prepend('<\p class=topNote><\strong>Note:</\strong> The option ' + + '"zlib.output_compression" is enabled in your PHP configuration, but has been ' + + 'successfully disabled via ini_set(). If you experience mangled output you ' + + 'may want to consider disabling this option in your PHP configuration.<\/p>' + ); + }); + }); +}); +</script> +<script type="text/javascript"> + // workaround required to test when /min isn't child of web root + var src = location.pathname.replace(/\/[^\/]*$/, '/_index.js').substr(1); + document.write('<\script type="text/javascript" src="../?f=' + src + '"><\/script>'); +</script> + +<?php + +$serveOpts = array( + 'content' => ob_get_contents() + ,'id' => __FILE__ + ,'lastModifiedTime' => max( + // regenerate cache if either of these change + filemtime(__FILE__) + ,filemtime(dirname(__FILE__) . '/../config.php') + ) + ,'minifyAll' => true + ,'encodeOutput' => $encodeOutput +); +ob_end_clean(); + +set_include_path(dirname(__FILE__) . '/../lib' . PATH_SEPARATOR . get_include_path()); + +require 'Minify.php'; + +if (0 === stripos(PHP_OS, 'win')) { + Minify::setDocRoot(); // we may be on IIS +} +Minify::setCache(isset($min_cachePath) ? $min_cachePath : null); +Minify::$uploaderHoursBehind = $min_uploaderHoursBehind; + +Minify::serve('Page', $serveOpts); diff --git a/vendors/minify/builder/ocCheck.php b/vendors/minify/builder/ocCheck.php new file mode 100644 index 0000000..c47baa3 --- /dev/null +++ b/vendors/minify/builder/ocCheck.php @@ -0,0 +1,36 @@ +<?php +/** + * AJAX checks for zlib.output_compression + * + * @package Minify + */ + +$_oc = ini_get('zlib.output_compression'); + +// allow access only if builder is enabled +require dirname(__FILE__) . '/../config.php'; +if (! $min_enableBuilder) { + header('Location: /'); + exit(); +} + +if (isset($_GET['hello'])) { + // echo 'World!' + + // try to prevent double encoding (may not have an effect) + ini_set('zlib.output_compression', '0'); + + require $min_libPath . '/HTTP/Encoder.php'; + HTTP_Encoder::$encodeToIe6 = true; // just in case + $he = new HTTP_Encoder(array( + 'content' => 'World!' + ,'method' => 'deflate' + )); + $he->encode(); + $he->sendAll(); + +} else { + // echo status "0" or "1" + header('Content-Type: text/plain'); + echo (int)$_oc; +} diff --git a/vendors/minify/builder/rewriteTest.js b/vendors/minify/builder/rewriteTest.js new file mode 100644 index 0000000..56a6051 --- /dev/null +++ b/vendors/minify/builder/rewriteTest.js @@ -0,0 +1 @@ +1 \ No newline at end of file diff --git a/vendors/minify/config.php b/vendors/minify/config.php new file mode 100644 index 0000000..04941ac --- /dev/null +++ b/vendors/minify/config.php @@ -0,0 +1,99 @@ +<?php +/** + * Configuration for default Minify implementation + * @package Minify + */ + + +/** + * Path to Minify's lib folder. If you happen to move it, change + * this accordingly. + */ +$min_libPath = dirname(__FILE__) . '/lib'; + + +/** + * For best performance, specify your temp directory here. Otherwise + * Minify will have to load extra code to guess. Commented out below + * are a few possible choices. + */ +//$min_cachePath = 'c:\\WINDOWS\Temp'; +//$min_cachePath = '/tmp'; +//$min_cachePath = preg_replace('/^\\d+;/', '', session_save_path()); + + +/** + * Cache file locking. Set to false if filesystem is NFS. + */ +$min_cacheFileLocking = true; + + +/** + * Allow use of the Minify URI Builder app. If you no longer need + * this, set to false. + **/ +$min_enableBuilder = true; + + +/** + * In 'debug' mode, Minify can combine files with no minification and + * add comments to indicate line #s of the original files. + * + * To allow debugging, set this option to true and add "&debug=1" to + * a URI. E.g. /min/?f=script1.js,script2.js&debug=1 + */ +$min_allowDebugFlag = false; + + +/** + * Maximum age of browser cache in seconds. After this period, + * the browser will send another conditional GET. You might + * want to shorten this before making changes if it's crucial + * those changes are seen immediately. + */ +$min_serveOptions['maxAge'] = 1800; + + +/** + * If you'd like to restrict the "f" option to files within/below + * particular directories below DOCUMENT_ROOT, set this here. + * You will still need to include the directory in the + * f or b GET parameters. + * + * // = DOCUMENT_ROOT + */ +//$min_serveOptions['minApp']['allowDirs'] = array('//js', '//css'); + +/** + * Set to true to disable the "f" GET parameter for specifying files. + * Only the "g" parameter will be considered. + */ +$min_serveOptions['minApp']['groupsOnly'] = false; + +/** + * Maximum # of files that can be specified in the "f" GET parameter + */ +$min_serveOptions['minApp']['maxFiles'] = 10; + + +/** + * If you upload files from Windows to a non-Windows server, Windows may report + * incorrect mtimes for the files. This may cause Minify to keep serving stale + * cache files when source file changes are made too frequently (e.g. more than + * once an hour). + * + * Immediately after modifying and uploading a file, use the touch command to + * update the mtime on the server. If the mtime jumps ahead by a number of hours, + * set this variable to that number. If the mtime moves back, this should not be + * needed. + * + * In the Windows SFTP client WinSCP, there's an option that may fix this + * issue without changing the variable below. Under login > environment, + * select the option "Adjust remote timestamp with DST". + * @link http://winscp.net/eng/docs/ui_login_environment#daylight_saving_time + */ +$min_uploaderHoursBehind = 0; + + +// try to disable output_compression (may not have an effect) +ini_set('zlib.output_compression', '0'); diff --git a/vendors/minify/groupsConfig.php b/vendors/minify/groupsConfig.php new file mode 100644 index 0000000..9e2514d --- /dev/null +++ b/vendors/minify/groupsConfig.php @@ -0,0 +1,34 @@ +<?php +/** + * Groups configuration for default Minify implementation + * @package Minify + */ + +/** + * You may wish to use the Minify URI Builder app to suggest + * changes. http://yourdomain/min/builder/ + **/ + +return array( + // 'js' => array('//js/file1.js', '//js/file2.js'), + // 'css' => array('//css/file1.css', '//css/file2.css'), + + // custom source example + /*'js2' => array( + dirname(__FILE__) . '/../min_unit_tests/_test_files/js/before.js', + // do NOT process this file + new Minify_Source(array( + 'filepath' => dirname(__FILE__) . '/../min_unit_tests/_test_files/js/before.js', + 'minifier' => create_function('$a', 'return $a;') + )) + ),//*/ + + /*'js3' => array( + dirname(__FILE__) . '/../min_unit_tests/_test_files/js/before.js', + // do NOT process this file + new Minify_Source(array( + 'filepath' => dirname(__FILE__) . '/../min_unit_tests/_test_files/js/before.js', + 'minifier' => array('Minify_Packer', 'minify') + )) + ),//*/ +); \ No newline at end of file diff --git a/vendors/minify/index.php b/vendors/minify/index.php new file mode 100644 index 0000000..3a19a21 --- /dev/null +++ b/vendors/minify/index.php @@ -0,0 +1,50 @@ +<?php +/** + * Front controller for default Minify implementation + * + * DO NOT EDIT! Configure this utility via config.php and groupsConfig.php + * + * @package Minify + */ + +define('MINIFY_MIN_DIR', dirname(__FILE__)); + +// load config +require MINIFY_MIN_DIR . '/config.php'; + +// setup include path +set_include_path($min_libPath . PATH_SEPARATOR . get_include_path()); + +require 'Minify.php'; + +Minify::$uploaderHoursBehind = $min_uploaderHoursBehind; +Minify::setCache( + isset($min_cachePath) ? $min_cachePath : null + ,$min_cacheFileLocking +); + +if (0 === stripos(PHP_OS, 'win')) { + Minify::setDocRoot(); // we may be on IIS +} +if ($min_allowDebugFlag && isset($_GET['debug'])) { + $min_serveOptions['debug'] = true; +} +// check for URI versioning +if (preg_match('/&\\d/', $_SERVER['QUERY_STRING'])) { + $min_serveOptions['maxAge'] = 31536000; +} +if (isset($_GET['g'])) { + // well need groups config + $min_serveOptions['minApp']['groups'] = (require MINIFY_MIN_DIR . '/groupsConfig.php'); +} +if (isset($_GET['f']) || isset($_GET['g'])) { + // serve! + Minify::serve('MinApp', $min_serveOptions); + +} elseif ($min_enableBuilder) { + header('Location: builder/'); + exit(); +} else { + header("Location: /"); + exit(); +} diff --git a/vendors/minify/lib/HTTP/ConditionalGet.php b/vendors/minify/lib/HTTP/ConditionalGet.php new file mode 100644 index 0000000..51b9b84 --- /dev/null +++ b/vendors/minify/lib/HTTP/ConditionalGet.php @@ -0,0 +1,267 @@ +<?php +/** + * Class HTTP_ConditionalGet + * @package Minify + * @subpackage HTTP + */ + +/** + * Implement conditional GET via a timestamp or hash of content + * + * E.g. Content from DB with update time: + * <code> + * list($updateTime, $content) = getDbUpdateAndContent(); + * $cg = new HTTP_ConditionalGet(array( + * 'lastModifiedTime' => $updateTime + * )); + * $cg->sendHeaders(); + * if ($cg->cacheIsValid) { + * exit(); + * } + * echo $content; + * </code> + * + * E.g. Content from DB with no update time: + * <code> + * $content = getContentFromDB(); + * $cg = new HTTP_ConditionalGet(array( + * 'contentHash' => md5($content) + * )); + * $cg->sendHeaders(); + * if ($cg->cacheIsValid) { + * exit(); + * } + * echo $content; + * </code> + * + * E.g. Static content with some static includes: + * <code> + * // before content + * $cg = new HTTP_ConditionalGet(array( + * 'lastUpdateTime' => max( + * filemtime(__FILE__) + * ,filemtime('/path/to/header.inc') + * ,filemtime('/path/to/footer.inc') + * ) + * )); + * $cg->sendHeaders(); + * if ($cg->cacheIsValid) { + * exit(); + * } + * </code> + * @package Minify + * @subpackage HTTP + * @author Stephen Clay <steve@mrclay.org> + */ +class HTTP_ConditionalGet { + + /** + * Does the client have a valid copy of the requested resource? + * + * You'll want to check this after instantiating the object. If true, do + * not send content, just call sendHeaders() if you haven't already. + * + * @var bool + */ + public $cacheIsValid = null; + + /** + * @param array $spec options + * + * 'isPublic': (bool) if true, the Cache-Control header will contain + * "public", allowing proxy caches to cache the content. Otherwise + * "private" will be sent, allowing only browsers to cache. (default false) + * + * 'lastModifiedTime': (int) if given, both ETag AND Last-Modified headers + * will be sent with content. This is recommended. + * + * 'eTag': (string) if given, this will be used as the ETag header rather + * than values based on lastModifiedTime or contentHash. + * + * 'contentHash': (string) if given, only the ETag header can be sent with + * content (only HTTP1.1 clients can conditionally GET). The given string + * should be short with no quote characters and always change when the + * resource changes (recommend md5()). This is not needed/used if + * lastModifiedTime is given. + * + * 'invalidate': (bool) if true, the client cache will be considered invalid + * without testing. Effectively this disables conditional GET. + * (default false) + * + * 'maxAge': (int) if given, this will set the Cache-Control max-age in + * seconds, and also set the Expires header to the equivalent GMT date. + * After the max-age period has passed, the browser will again send a + * conditional GET to revalidate its cache. + * + * @return null + */ + public function __construct($spec) { + $scope = (isset($spec['isPublic']) && $spec['isPublic']) + ? 'public' + : 'private'; + $maxAge = 0; + // backwards compatibility (can be removed later) + if (isset($spec['setExpires']) + && is_numeric($spec['setExpires']) + && ! isset($spec['maxAge'])) { + $spec['maxAge'] = $spec['setExpires'] - $_SERVER['REQUEST_TIME']; + } + if (isset($spec['maxAge'])) { + $maxAge = $spec['maxAge']; + $this->_headers['Expires'] = self::gmtDate( + $_SERVER['REQUEST_TIME'] + $spec['maxAge'] + ); + } + if (isset($spec['lastModifiedTime'])) { + $this->_setLastModified($spec['lastModifiedTime']); + if (isset($spec['eTag'])) { // Use it + $this->_setEtag($spec['eTag'], $scope); + } else { // base both headers on time + $this->_setEtag($spec['lastModifiedTime'], $scope); + } + } elseif (isset($spec['eTag'])) { // Use it + $this->_setEtag($spec['eTag'], $scope); + } elseif (isset($spec['contentHash'])) { // Use the hash as the ETag + $this->_setEtag($spec['contentHash'], $scope); + } + $this->_headers['Cache-Control'] = "max-age={$maxAge}, {$scope}, must-revalidate"; + // invalidate cache if disabled, otherwise check + $this->cacheIsValid = (isset($spec['invalidate']) && $spec['invalidate']) + ? false + : $this->_isCacheValid(); + } + + /** + * Get array of output headers to be sent + * + * In the case of 304 responses, this array will only contain the response + * code header: array('_responseCode' => 'HTTP/1.0 304 Not Modified') + * + * Otherwise something like: + * <code> + * array( + * 'Cache-Control' => 'max-age=0, public, must-revalidate' + * ,'ETag' => '"foobar"' + * ) + * </code> + * + * @return array + */ + public function getHeaders() { + return $this->_headers; + } + + /** + * Set the Content-Length header in bytes + * + * With most PHP configs, as long as you don't flush() output, this method + * is not needed and PHP will buffer all output and set Content-Length for + * you. Otherwise you'll want to call this to let the client know up front. + * + * @param int $bytes + * + * @return int copy of input $bytes + */ + public function setContentLength($bytes) { + return $this->_headers['Content-Length'] = $bytes; + } + + /** + * Send headers + * + * @see getHeaders() + * + * Note this doesn't "clear" the headers. Calling sendHeaders() will + * call header() again (but probably have not effect) and getHeaders() will + * still return the headers. + * + * @return null + */ + public function sendHeaders() { + $headers = $this->_headers; + if (array_key_exists('_responseCode', $headers)) { + header($headers['_responseCode']); + unset($headers['_responseCode']); + } + foreach ($headers as $name => $val) { + header($name . ': ' . $val); + } + } + + /** + * Get a GMT formatted date for use in HTTP headers + * + * <code> + * header('Expires: ' . HTTP_ConditionalGet::gmtdate($time)); + * </code> + * + * @param int $time unix timestamp + * + * @return string + */ + public static function gmtDate($time) { + return gmdate('D, d M Y H:i:s \G\M\T', $time); + } + + protected $_headers = array(); + protected $_lmTime = null; + protected $_etag = null; + + protected function _setEtag($hash, $scope) { + $this->_etag = '"' . $hash + . substr($scope, 0, 3) + . '"'; + $this->_headers['ETag'] = $this->_etag; + } + + protected function _setLastModified($time) { + $this->_lmTime = (int)$time; + $this->_headers['Last-Modified'] = self::gmtDate($time); + } + + /** + * Determine validity of client cache and queue 304 header if valid + */ + protected function _isCacheValid() + { + if (null === $this->_etag) { + // lmTime is copied to ETag, so this condition implies that the + // server sent neither ETag nor Last-Modified, so the client can't + // possibly has a valid cache. + return false; + } + $isValid = ($this->resourceMatchedEtag() || $this->resourceNotModified()); + if ($isValid) { + $this->_headers['_responseCode'] = 'HTTP/1.0 304 Not Modified'; + } + return $isValid; + } + + protected function resourceMatchedEtag() { + if (!isset($_SERVER['HTTP_IF_NONE_MATCH'])) { + return false; + } + $cachedEtagList = get_magic_quotes_gpc() + ? stripslashes($_SERVER['HTTP_IF_NONE_MATCH']) + : $_SERVER['HTTP_IF_NONE_MATCH']; + $cachedEtags = split(',', $cachedEtagList); + foreach ($cachedEtags as $cachedEtag) { + if (trim($cachedEtag) == $this->_etag) { + return true; + } + } + return false; + } + + protected function resourceNotModified() { + if (!isset($_SERVER['HTTP_IF_MODIFIED_SINCE'])) { + return false; + } + $ifModifiedSince = $_SERVER['HTTP_IF_MODIFIED_SINCE']; + if (false !== ($semicolon = strrpos($ifModifiedSince, ';'))) { + // IE has tacked on extra data to this header, strip it + $ifModifiedSince = substr($ifModifiedSince, 0, $semicolon); + } + return ($ifModifiedSince == self::gmtDate($this->_lmTime)); + } +} diff --git a/vendors/minify/lib/HTTP/Encoder.php b/vendors/minify/lib/HTTP/Encoder.php new file mode 100644 index 0000000..f25cd83 --- /dev/null +++ b/vendors/minify/lib/HTTP/Encoder.php @@ -0,0 +1,283 @@ +<?php +/** + * Class HTTP_Encoder + * @package Minify + * @subpackage HTTP + */ + +/** + * Encode and send gzipped/deflated content + * + * <code> + * // Send a CSS file, compressed if possible + * $he = new HTTP_Encoder(array( + * 'content' => file_get_contents($cssFile) + * ,'type' => 'text/css' + * )); + * $he->encode(); + * $he->sendAll(); + * </code> + * + * <code> + * // Just sniff for the accepted encoding + * $encoding = HTTP_Encoder::getAcceptedEncoding(); + * </code> + * + * For more control over headers, use getHeaders() and getData() and send your + * own output. + * + * Note: If you don't need header mgmt, use PHP's native gzencode, gzdeflate, + * and gzcompress functions for gzip, deflate, and compress-encoding + * respectively. + * + * @package Minify + * @subpackage HTTP + * @author Stephen Clay <steve@mrclay.org> + */ +class HTTP_Encoder { + + /** + * Should the encoder allow HTTP encoding to IE6? + * + * If you have many IE6 users and the bandwidth savings is worth troubling + * some of them, set this to true. + * + * By default, encoding is only offered to IE7+. When this is true, + * getAcceptedEncoding() will return an encoding for IE6 if its user agent + * string contains "SV1". This has been documented in many places as "safe", + * but there seem to be remaining, intermittent encoding bugs in patched + * IE6 on the wild web. + * + * @var bool + */ + public static $encodeToIe6 = false; + + + /** + * Default compression level for zlib operations + * + * This level is used if encode() is not given a $compressionLevel + * + * @var int + */ + public static $compressionLevel = 6; + + + /** + * Get an HTTP Encoder object + * + * @param array $spec options + * + * 'content': (string required) content to be encoded + * + * 'type': (string) if set, the Content-Type header will have this value. + * + * 'method: (string) only set this if you are forcing a particular encoding + * method. If not set, the best method will be chosen by getAcceptedEncoding() + * The available methods are 'gzip', 'deflate', 'compress', and '' (no + * encoding) + * + * @return null + */ + public function __construct($spec) + { + $this->_content = $spec['content']; + $this->_headers['Content-Length'] = (string)strlen($this->_content); + if (isset($spec['type'])) { + $this->_headers['Content-Type'] = $spec['type']; + } + if (isset($spec['method']) + && in_array($spec['method'], array('gzip', 'deflate', 'compress', ''))) + { + $this->_encodeMethod = array($spec['method'], $spec['method']); + } else { + $this->_encodeMethod = self::getAcceptedEncoding(); + } + } + + /** + * Get content in current form + * + * Call after encode() for encoded content. + * + * return string + */ + public function getContent() + { + return $this->_content; + } + + /** + * Get array of output headers to be sent + * + * E.g. + * <code> + * array( + * 'Content-Length' => '615' + * ,'Content-Encoding' => 'x-gzip' + * ,'Vary' => 'Accept-Encoding' + * ) + * </code> + * + * @return array + */ + public function getHeaders() + { + return $this->_headers; + } + + /** + * Send output headers + * + * You must call this before headers are sent and it probably cannot be + * used in conjunction with zlib output buffering / mod_gzip. Errors are + * not handled purposefully. + * + * @see getHeaders() + * + * @return null + */ + public function sendHeaders() + { + foreach ($this->_headers as $name => $val) { + header($name . ': ' . $val); + } + } + + /** + * Send output headers and content + * + * A shortcut for sendHeaders() and echo getContent() + * + * You must call this before headers are sent and it probably cannot be + * used in conjunction with zlib output buffering / mod_gzip. Errors are + * not handled purposefully. + * + * @return null + */ + public function sendAll() + { + $this->sendHeaders(); + echo $this->_content; + } + + /** + * Determine the client's best encoding method from the HTTP Accept-Encoding + * header. + * + * If no Accept-Encoding header is set, or the browser is IE before v6 SP2, + * this will return ('', ''), the "identity" encoding. + * + * A syntax-aware scan is done of the Accept-Encoding, so the method must + * be non 0. The methods are favored in order of deflate, gzip, then + * compress. Yes, deflate is always smaller and faster! + * + * @param bool $allowCompress allow the older compress encoding + * + * @return array two values, 1st is the actual encoding method, 2nd is the + * alias of that method to use in the Content-Encoding header (some browsers + * call gzip "x-gzip" etc.) + */ + public static function getAcceptedEncoding($allowCompress = true) + { + // @link http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html + + if (! isset($_SERVER['HTTP_ACCEPT_ENCODING']) + || self::_isBuggyIe()) + { + return array('', ''); + } + $ae = $_SERVER['HTTP_ACCEPT_ENCODING']; + $aeRev = strrev($ae); + // Fast tests for common AEs. If these don't pass we have to do + // slow regex parsing + if (0 === strpos($aeRev, 'etalfed ,') // ie, webkit + || 0 === strpos($aeRev, 'etalfed,') // gecko + || 0 === strpos($ae, 'deflate,') // opera 9.5b + // slow parsing + || preg_match( + '@(?:^|,)\\s*deflate\\s*(?:$|,|;\\s*q=(?:0\\.|1))@', $ae)) { + return array('deflate', 'deflate'); + } + if (preg_match( + '@(?:^|,)\\s*((?:x-)?gzip)\\s*(?:$|,|;\\s*q=(?:0\\.|1))@' + ,$ae + ,$m)) { + return array('gzip', $m[1]); + } + if ($allowCompress && preg_match( + '@(?:^|,)\\s*((?:x-)?compress)\\s*(?:$|,|;\\s*q=(?:0\\.|1))@' + ,$ae + ,$m)) { + return array('compress', $m[1]); + } + return array('', ''); + } + + /** + * Encode (compress) the content + * + * If the encode method is '' (none) or compression level is 0, or the 'zlib' + * extension isn't loaded, we return false. + * + * Then the appropriate gz_* function is called to compress the content. If + * this fails, false is returned. + * + * If successful, the Content-Length header is updated, and Content-Encoding + * and Vary headers are added. + * + * @param int $compressionLevel given to zlib functions. If not given, the + * class default will be used. + * + * @return bool success true if the content was actually compressed + */ + public function encode($compressionLevel = null) + { + if (null === $compressionLevel) { + $compressionLevel = self::$compressionLevel; + } + if ('' === $this->_encodeMethod[0] + || ($compressionLevel == 0) + || !extension_loaded('zlib')) + { + return false; + } + if ($this->_encodeMethod[0] === 'deflate') { + $encoded = gzdeflate($this->_content, $compressionLevel); + } elseif ($this->_encodeMethod[0] === 'gzip') { + $encoded = gzencode($this->_content, $compressionLevel); + } else { + $encoded = gzcompress($this->_content, $compressionLevel); + } + if (false === $encoded) { + return false; + } + $this->_headers['Content-Length'] = strlen($encoded); + $this->_headers['Content-Encoding'] = $this->_encodeMethod[1]; + $this->_headers['Vary'] = 'Accept-Encoding'; + $this->_content = $encoded; + return true; + } + + protected $_content = ''; + protected $_headers = array(); + protected $_encodeMethod = array('', ''); + + /** + * Is the browser an IE version earlier than 6 SP2? + */ + protected static function _isBuggyIe() + { + $ua = $_SERVER['HTTP_USER_AGENT']; + // quick escape for non-IEs + if (0 !== strpos($ua, 'Mozilla/4.0 (compatible; MSIE ') + || false !== strpos($ua, 'Opera')) { + return false; + } + // no regex = faaast + $version = (float)substr($ua, 30); + return self::$encodeToIe6 + ? ($version < 6 || ($version == 6 && false === strpos($ua, 'SV1'))) + : ($version < 7); + } +} diff --git a/vendors/minify/lib/JSMin.php b/vendors/minify/lib/JSMin.php new file mode 100644 index 0000000..c3a1f3b --- /dev/null +++ b/vendors/minify/lib/JSMin.php @@ -0,0 +1,313 @@ +<?php +/** + * jsmin.php - PHP implementation of Douglas Crockford's JSMin. + * + * This is a direct port of jsmin.c to PHP with a few PHP performance tweaks and + * modifications to preserve some comments (see below). Also, rather than using + * stdin/stdout, JSMin::minify() accepts a string as input and returns another + * string as output. + * + * Comments containing IE conditional compilation are preserved, as are multi-line + * comments that begin with "/*!" (for documentation purposes). In the latter case + * newlines are inserted around the comment to enhance readability. + * + * Known issue: regular expressions containing quote characters must be proceeded + * by one of the following characters: (,=:[!&|? + * E.g. JSMin will fail on the following: return /'/; + * The simple workaround is to wrap the expression in parenthesis: return (/'/); + * + * PHP 5 or higher is required. + * + * Permission is hereby granted to use this version of the library under the + * same terms as jsmin.c, which has the following license: + * + * -- + * Copyright (c) 2002 Douglas Crockford (www.crockford.com) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + * of the Software, and to permit persons to whom the Software is furnished to do + * so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * The Software shall be used for Good, not Evil. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * -- + * + * @package JSMin + * @author Ryan Grove <ryan@wonko.com> + * @author Steve Clay <steve@mrclay.org> (modifications) + * @copyright 2002 Douglas Crockford <douglas@crockford.com> (jsmin.c) + * @copyright 2008 Ryan Grove <ryan@wonko.com> (PHP port) + * @license http://opensource.org/licenses/mit-license.php MIT License + * @version 1.1.1 (2008-03-02) + * @link http://code.google.com/p/jsmin-php/ + */ + +class JSMin { + const ORD_LF = 10; + const ORD_SPACE = 32; + + protected $a = ''; + protected $b = ''; + protected $input = ''; + protected $inputIndex = 0; + protected $inputLength = 0; + protected $lookAhead = null; + protected $output = ''; + + // -- Public Static Methods -------------------------------------------------- + + public static function minify($js) { + $jsmin = new JSMin($js); + return $jsmin->min(); + } + + // -- Public Instance Methods ------------------------------------------------ + + public function __construct($input) { + $this->input = str_replace("\r\n", "\n", $input); + $this->inputLength = strlen($this->input); + } + + // -- Protected Instance Methods --------------------------------------------- + + protected function action($d) { + switch($d) { + case 1: + $this->output .= $this->a; + + case 2: + $this->a = $this->b; + + if ($this->a === "'" || $this->a === '"') { + for (;;) { + $this->output .= $this->a; + $this->a = $this->get(); + + if ($this->a === $this->b) { + break; + } + + if (ord($this->a) <= self::ORD_LF) { + throw new JSMinException('Unterminated string literal.'); + } + + if ($this->a === '\\') { + $this->output .= $this->a; + $this->a = $this->get(); + } + } + } + + case 3: + $this->b = $this->next(); + + if ($this->b === '/' && ( + $this->a === '(' || $this->a === ',' || $this->a === '=' || + $this->a === ':' || $this->a === '[' || $this->a === '!' || + $this->a === '&' || $this->a === '|' || $this->a === '?')) { + + $this->output .= $this->a . $this->b; + + for (;;) { + $this->a = $this->get(); + + if ($this->a === '/') { + break; + } elseif ($this->a === '\\') { + $this->output .= $this->a; + $this->a = $this->get(); + } elseif (ord($this->a) <= self::ORD_LF) { + throw new JSMinException('Unterminated regular expression '. + 'literal.'); + } + + $this->output .= $this->a; + } + + $this->b = $this->next(); + } + } + } + + protected function get() { + $c = $this->lookAhead; + $this->lookAhead = null; + + if ($c === null) { + if ($this->inputIndex < $this->inputLength) { + $c = $this->input[$this->inputIndex]; + $this->inputIndex += 1; + } else { + $c = null; + } + } + + if ($c === "\r") { + return "\n"; + } + + if ($c === null || $c === "\n" || ord($c) >= self::ORD_SPACE) { + return $c; + } + + return ' '; + } + + protected function isAlphaNum($c) { + return ord($c) > 126 || $c === '\\' || preg_match('/^[\w\$]$/', $c) === 1; + } + + protected function min() { + $this->a = "\n"; + $this->action(3); + + while ($this->a !== null) { + switch ($this->a) { + case ' ': + if ($this->isAlphaNum($this->b)) { + $this->action(1); + } else { + $this->action(2); + } + break; + + case "\n": + switch ($this->b) { + case '{': + case '[': + case '(': + case '+': + case '-': + $this->action(1); + break; + + case ' ': + $this->action(3); + break; + + default: + if ($this->isAlphaNum($this->b)) { + $this->action(1); + } + else { + $this->action(2); + } + } + break; + + default: + switch ($this->b) { + case ' ': + if ($this->isAlphaNum($this->a)) { + $this->action(1); + break; + } + + $this->action(3); + break; + + case "\n": + switch ($this->a) { + case '}': + case ']': + case ')': + case '+': + case '-': + case '"': + case "'": + $this->action(1); + break; + + default: + if ($this->isAlphaNum($this->a)) { + $this->action(1); + } + else { + $this->action(3); + } + } + break; + + default: + $this->action(1); + break; + } + } + } + + return $this->output; + } + + protected function next() { + $get = $this->get(); + + if ($get === '/') { + $commentContents = ''; + switch($this->peek()) { + case '/': + // "//" comment + for (;;) { + $get = $this->get(); + $commentContents .= $get; + if (ord($get) <= self::ORD_LF) { + return preg_match('/^\\/@(?:cc_on|if|elif|else|end)\\b/', $commentContents) + ? "/{$commentContents}" + : $get; + } + } + + case '*': + // "/* */" comment + $this->get(); + for (;;) { + $get = $this->get(); + switch($get) { + case '*': + if ($this->peek() === '/') { + $this->get(); + if (0 === strpos($commentContents, '!')) { + // YUI Compressor style + return "\n/*" . substr($commentContents, 1) . "*/\n"; + } + return preg_match('/^@(?:cc_on|if|elif|else|end)\\b/', $commentContents) + ? "/*{$commentContents}*/" // IE conditional compilation + : ' '; + } + break; + + case null: + throw new JSMinException('Unterminated comment.'); + } + $commentContents .= $get; + } + + default: + return $get; + } + } + + return $get; + } + + protected function peek() { + $this->lookAhead = $this->get(); + return $this->lookAhead; + } +} + +// -- Exceptions --------------------------------------------------------------- +class JSMinException extends Exception {} +?> \ No newline at end of file diff --git a/vendors/minify/lib/Minify.php b/vendors/minify/lib/Minify.php new file mode 100644 index 0000000..6dcced8 --- /dev/null +++ b/vendors/minify/lib/Minify.php @@ -0,0 +1,479 @@ +<?php +/** + * Class Minify + * @package Minify + */ + +/** + * Minify_Source + */ +require_once 'Minify/Source.php'; + +/** + * Minify - Combines, minifies, and caches JavaScript and CSS files on demand. + * + * See README for usage instructions (for now). + * + * This library was inspired by {@link mailto:flashkot@mail.ru jscsscomp by Maxim Martynyuk} + * and by the article {@link http://www.hunlock.com/blogs/Supercharged_Javascript "Supercharged JavaScript" by Patrick Hunlock}. + * + * Requires PHP 5.1.0. + * Tested on PHP 5.1.6. + * + * @package Minify + * @author Ryan Grove <ryan@wonko.com> + * @author Stephen Clay <steve@mrclay.org> + * @copyright 2008 Ryan Grove, Stephen Clay. All rights reserved. + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @link http://code.google.com/p/minify/ + */ +class Minify { + + const TYPE_CSS = 'text/css'; + const TYPE_HTML = 'text/html'; + // there is some debate over the ideal JS Content-Type, but this is the + // Apache default and what Yahoo! uses.. + const TYPE_JS = 'application/x-javascript'; + + /** + * How many hours behind are the file modification times of uploaded files? + * + * If you upload files from Windows to a non-Windows server, Windows may report + * incorrect mtimes for the files. Immediately after modifying and uploading a + * file, use the touch command to update the mtime on the server. If the mtime + * jumps ahead by a number of hours, set this variable to that number. If the mtime + * moves back, this should not be needed. + * + * @var int $uploaderHoursBehind + */ + public static $uploaderHoursBehind = 0; + + /** + * Specify a cache object (with identical interface as Minify_Cache_File) or + * a path to use with Minify_Cache_File. + * + * If not called, Minify will not use a cache and, for each 200 response, will + * need to recombine files, minify and encode the output. + * + * @param mixed $cache object with identical interface as Minify_Cache_File or + * a directory path, or null to disable caching. (default = '') + * + * @param bool $fileLocking (default = true) This only applies if the first + * parameter is a string. + * + * @return null + */ + public static function setCache($cache = '', $fileLocking = true) + { + if (is_string($cache)) { + require_once 'Minify/Cache/File.php'; + self::$_cache = new Minify_Cache_File($cache, $fileLocking); + } else { + self::$_cache = $cache; + } + } + + /** + * Serve a request for a minified file. + * + * Here are the available options and defaults in the base controller: + * + * 'isPublic' : send "public" instead of "private" in Cache-Control + * headers, allowing shared caches to cache the output. (default true) + * + * 'quiet' : set to true to have serve() return an array rather than sending + * any headers/output (default false) + * + * 'encodeOutput' : to disable content encoding, set this to false (default true) + * + * 'encodeMethod' : generally you should let this be determined by + * HTTP_Encoder (leave null), but you can force a particular encoding + * to be returned, by setting this to 'gzip', 'deflate', or '' (no encoding) + * + * 'encodeLevel' : level of encoding compression (0 to 9, default 9) + * + * 'contentTypeCharset' : appended to the Content-Type header sent. Set to a falsey + * value to remove. (default 'UTF-8') + * + * 'maxAge' : set this to the number of seconds the client should use its cache + * before revalidating with the server. This sets Cache-Control: max-age and the + * Expires header. Unlike the old 'setExpires' setting, this setting will NOT + * prevent conditional GETs. Note this has nothing to do with server-side caching. + * + * 'rewriteCssUris' : If true, serve() will automatically set the 'currentDir' + * minifier option to enable URI rewriting in CSS files (default true) + * + * 'debug' : set to true to minify all sources with the 'Lines' controller, which + * eases the debugging of combined files. This also prevents 304 responses. + * @see Minify_Lines::minify() + * + * 'minifiers' : to override Minify's default choice of minifier function for + * a particular content-type, specify your callback under the key of the + * content-type: + * <code> + * // call customCssMinifier($css) for all CSS minification + * $options['minifiers'][Minify::TYPE_CSS] = 'customCssMinifier'; + * + * // don't minify Javascript at all + * $options['minifiers'][Minify::TYPE_JS] = ''; + * </code> + * + * 'minifierOptions' : to send options to the minifier function, specify your options + * under the key of the content-type. E.g. To send the CSS minifier an option: + * <code> + * // give CSS minifier array('optionName' => 'optionValue') as 2nd argument + * $options['minifierOptions'][Minify::TYPE_CSS]['optionName'] = 'optionValue'; + * </code> + * + * 'contentType' : (optional) this is only needed if your file extension is not + * js/css/html. The given content-type will be sent regardless of source file + * extension, so this should not be used in a Groups config with other + * Javascript/CSS files. + * + * Any controller options are documented in that controller's setupSources() method. + * + * @param mixed instance of subclass of Minify_Controller_Base or string name of + * controller. E.g. 'Files' + * + * @param array $options controller/serve options + * + * @return mixed null, or, if the 'quiet' option is set to true, an array + * with keys "success" (bool), "statusCode" (int), "content" (string), and + * "headers" (array). + */ + public static function serve($controller, $options = array()) { + if (is_string($controller)) { + // make $controller into object + $class = 'Minify_Controller_' . $controller; + if (! class_exists($class, false)) { + require_once "Minify/Controller/" + . str_replace('_', '/', $controller) . ".php"; + } + $controller = new $class(); + } + + // set up controller sources and mix remaining options with + // controller defaults + $options = $controller->setupSources($options); + $options = $controller->analyzeSources($options); + self::$_options = $controller->mixInDefaultOptions($options); + + // check request validity + if (! $controller->sources) { + // invalid request! + if (! self::$_options['quiet']) { + header(self::$_options['badRequestHeader']); + echo self::$_options['badRequestHeader']; + return; + } else { + list(,$statusCode) = explode(' ', self::$_options['badRequestHeader']); + return array( + 'success' => false + ,'statusCode' => (int)$statusCode + ,'content' => '' + ,'headers' => array() + ); + } + } + + self::$_controller = $controller; + + if (self::$_options['debug']) { + self::_setupDebug($controller->sources); + self::$_options['maxAge'] = 0; + } + + // check client cache + require_once 'HTTP/ConditionalGet.php'; + $cgOptions = array( + 'lastModifiedTime' => self::$_options['lastModifiedTime'] + ,'isPublic' => self::$_options['isPublic'] + ); + if (self::$_options['maxAge'] > 0) { + $cgOptions['maxAge'] = self::$_options['maxAge']; + } + $cg = new HTTP_ConditionalGet($cgOptions); + if ($cg->cacheIsValid) { + // client's cache is valid + if (! self::$_options['quiet']) { + $cg->sendHeaders(); + return; + } else { + return array( + 'success' => true + ,'statusCode' => 304 + ,'content' => '' + ,'headers' => $cg->getHeaders() + ); + } + } else { + // client will need output + $headers = $cg->getHeaders(); + unset($cg); + } + + // determine encoding + if (self::$_options['encodeOutput']) { + if (self::$_options['encodeMethod'] !== null) { + // controller specifically requested this + $contentEncoding = self::$_options['encodeMethod']; + } else { + // sniff request header + require_once 'HTTP/Encoder.php'; + // depending on what the client accepts, $contentEncoding may be + // 'x-gzip' while our internal encodeMethod is 'gzip'. Calling + // getAcceptedEncoding() with false leaves out compress as an option. + list(self::$_options['encodeMethod'], $contentEncoding) = HTTP_Encoder::getAcceptedEncoding(false); + } + } else { + self::$_options['encodeMethod'] = ''; // identity (no encoding) + } + + if (self::$_options['contentType'] === self::TYPE_CSS + && self::$_options['rewriteCssUris']) { + reset($controller->sources); + while (list($key, $source) = each($controller->sources)) { + if ($source->filepath + && !isset($source->minifyOptions['currentDir']) + && !isset($source->minifyOptions['prependRelativePath']) + ) { + $source->minifyOptions['currentDir'] = dirname($source->filepath); + } + } + } + + // check server cache + if (null !== self::$_cache) { + // using cache + // the goal is to use only the cache methods to sniff the length and + // output the content, as they do not require ever loading the file into + // memory. + $cacheId = 'minify_' . self::_getCacheId(); + $encodingExtension = self::$_options['encodeMethod'] + ? ('deflate' === self::$_options['encodeMethod'] + ? '.zd' + : '.zg') + : ''; + $fullCacheId = $cacheId . $encodingExtension; + // check cache for valid entry + $cacheIsReady = self::$_cache->isValid($fullCacheId, self::$_options['lastModifiedTime']); + if ($cacheIsReady) { + $cacheContentLength = self::$_cache->getSize($fullCacheId); + } else { + // generate & cache content + $content = self::_combineMinify(); + self::$_cache->store($cacheId, $content); + if (function_exists('gzdeflate')) { + self::$_cache->store($cacheId . '.zd', gzdeflate($content, self::$_options['encodeLevel'])); + self::$_cache->store($cacheId . '.zg', gzencode($content, self::$_options['encodeLevel'])); + } + } + } else { + // no cache + $cacheIsReady = false; + $content = self::_combineMinify(); + } + if (! $cacheIsReady && self::$_options['encodeMethod']) { + // still need to encode + $content = ('deflate' === self::$_options['encodeMethod']) + ? gzdeflate($content, self::$_options['encodeLevel']) + : gzencode($content, self::$_options['encodeLevel']); + } + + // add headers + $headers['Content-Length'] = $cacheIsReady + ? $cacheContentLength + : strlen($content); + $headers['Content-Type'] = self::$_options['contentTypeCharset'] + ? self::$_options['contentType'] . '; charset=' . self::$_options['contentTypeCharset'] + : self::$_options['contentType']; + if (self::$_options['encodeMethod'] !== '') { + $headers['Content-Encoding'] = $contentEncoding; + $headers['Vary'] = 'Accept-Encoding'; + } + + if (! self::$_options['quiet']) { + // output headers & content + foreach ($headers as $name => $val) { + header($name . ': ' . $val); + } + if ($cacheIsReady) { + self::$_cache->display($fullCacheId); + } else { + echo $content; + } + } else { + return array( + 'success' => true + ,'statusCode' => 200 + ,'content' => $cacheIsReady + ? self::$_cache->fetch($fullCacheId) + : $content + ,'headers' => $headers + ); + } + } + + /** + * Return combined minified content for a set of sources + * + * No internal caching will be used and the content will not be HTTP encoded. + * + * @param array $sources array of filepaths and/or Minify_Source objects + * + * @return string + */ + public static function combine($sources) + { + $cache = self::$_cache; + self::$_cache = null; + $out = self::serve('Files', array( + 'files' => (array)$sources + ,'quiet' => true + ,'encodeMethod' => '' + ,'lastModifiedTime' => 0 + )); + self::$_cache = $cache; + return $out['content']; + } + + /** + * On IIS, create $_SERVER['DOCUMENT_ROOT'] + * + * @param bool $unsetPathInfo (default false) if true, $_SERVER['PATH_INFO'] + * will be unset (it is inconsistent with Apache's setting) + * + * @return null + */ + public static function setDocRoot($unsetPathInfo = false) + { + if (isset($_SERVER['SERVER_SOFTWARE']) + && 0 === strpos($_SERVER['SERVER_SOFTWARE'], 'Microsoft-IIS/') + ) { + $_SERVER['DOCUMENT_ROOT'] = substr( + $_SERVER['PATH_TRANSLATED'] + ,0 + ,strlen($_SERVER['PATH_TRANSLATED']) - strlen($_SERVER['SCRIPT_NAME']) + ); + if ($unsetPathInfo) { + unset($_SERVER['PATH_INFO']); + } + } + } + + /** + * @var mixed Minify_Cache_* object or null (i.e. no server cache is used) + */ + private static $_cache = null; + + /** + * @var Minify_Controller active controller for current request + */ + protected static $_controller = null; + + /** + * @var array options for current request + */ + protected static $_options = null; + + /** + * Set up sources to use Minify_Lines + * + * @param array $sources Minify_Source instances + * + * @return null + */ + protected static function _setupDebug($sources) + { + foreach ($sources as $source) { + $source->minifier = array('Minify_Lines', 'minify'); + $id = $source->getId(); + $source->minifyOptions = array( + 'id' => (is_file($id) ? basename($id) : $id) + ); + } + } + + /** + * Combines sources and minifies the result. + * + * @return string + */ + protected static function _combineMinify() { + $type = self::$_options['contentType']; // ease readability + + // when combining scripts, make sure all statements separated and + // trailing single line comment is terminated + $implodeSeparator = ($type === self::TYPE_JS) + ? "\n;" + : ''; + // allow the user to pass a particular array of options to each + // minifier (designated by type). source objects may still override + // these + $defaultOptions = isset(self::$_options['minifierOptions'][$type]) + ? self::$_options['minifierOptions'][$type] + : array(); + // if minifier not set, default is no minification. source objects + // may still override this + $defaultMinifier = isset(self::$_options['minifiers'][$type]) + ? self::$_options['minifiers'][$type] + : false; + + if (Minify_Source::haveNoMinifyPrefs(self::$_controller->sources)) { + // all source have same options/minifier, better performance + // to combine, then minify once + foreach (self::$_controller->sources as $source) { + $pieces[] = $source->getContent(); + } + $content = implode($implodeSeparator, $pieces); + if ($defaultMinifier) { + self::$_controller->loadMinifier($defaultMinifier); + $content = call_user_func($defaultMinifier, $content, $defaultOptions); + } + } else { + // minify each source with its own options and minifier, then combine + foreach (self::$_controller->sources as $source) { + // allow the source to override our minifier and options + $minifier = (null !== $source->minifier) + ? $source->minifier + : $defaultMinifier; + $options = (null !== $source->minifyOptions) + ? array_merge($defaultOptions, $source->minifyOptions) + : $defaultOptions; + if ($minifier) { + self::$_controller->loadMinifier($minifier); + // get source content and minify it + $pieces[] = call_user_func($minifier, $source->getContent(), $options); + } else { + $pieces[] = $source->getContent(); + } + } + $content = implode($implodeSeparator, $pieces); + } + + // do any post-processing (esp. for editing build URIs) + if (self::$_options['postprocessorRequire']) { + require_once self::$_options['postprocessorRequire']; + } + if (self::$_options['postprocessor']) { + $content = call_user_func(self::$_options['postprocessor'], $content, $type); + } + return $content; + } + + /** + * Make a unique cache id for for this request. + * + * Any settings that could affect output are taken into consideration + * + * @return string + */ + protected static function _getCacheId() { + return md5(serialize(array( + Minify_Source::getDigest(self::$_controller->sources) + ,self::$_options['minifiers'] + ,self::$_options['minifierOptions'] + ,self::$_options['postprocessor'] + ))); + } +} diff --git a/vendors/minify/lib/Minify/Build.php b/vendors/minify/lib/Minify/Build.php new file mode 100644 index 0000000..e625165 --- /dev/null +++ b/vendors/minify/lib/Minify/Build.php @@ -0,0 +1,103 @@ +<?php +/** + * Class Minify_Build + * @package Minify + */ + +require_once 'Minify/Source.php'; + +/** + * Maintain a single last modification time for a group of Minify sources to + * allow use of far off Expires headers in Minify. + * + * <code> + * // in config file + * $groupSources = array( + * 'js' => array('file1.js', 'file2.js') + * ,'css' => array('file1.css', 'file2.css', 'file3.css') + * ) + * + * // during HTML generation + * $jsBuild = new Minify_Build($groupSources['js']); + * $cssBuild = new Minify_Build($groupSources['css']); + * + * $script = "<script type='text/javascript' src='" + * . $jsBuild->uri('/min.php/js') . "'></script>"; + * $link = "<link rel='stylesheet' type='text/css' href='" + * . $cssBuild->uri('/min.php/css') . "'>"; + * + * // in min.php + * Minify::serve('Groups', array( + * 'groups' => $groupSources + * ,'setExpires' => (time() + 86400 * 365) + * )); + * </code> + * + * @package Minify + * @author Stephen Clay <steve@mrclay.org> + */ +class Minify_Build { + + /** + * Last modification time of all files in the build + * + * @var int + */ + public $lastModified = 0; + + /** + * String to use as ampersand in uri(). Set this to '&' if + * you are not HTML-escaping URIs. + * + * @var string + */ + public static $ampersand = '&amp;'; + + /** + * Get a time-stamped URI + * + * <code> + * echo $b->uri('/site.js'); + * // outputs "/site.js?1678242" + * + * echo $b->uri('/scriptaculous.js?load=effects'); + * // outputs "/scriptaculous.js?load=effects&amp1678242" + * </code> + * + * @param string $uri + * @param boolean $forceAmpersand (default = false) Force the use of ampersand to + * append the timestamp to the URI. + * @return string + */ + public function uri($uri, $forceAmpersand = false) { + $sep = ($forceAmpersand || strpos($uri, '?') !== false) + ? self::$ampersand + : '?'; + return "{$uri}{$sep}{$this->lastModified}"; + } + + /** + * Create a build object + * + * @param array $sources array of Minify_Source objects and/or file paths + * + * @return null + */ + public function __construct($sources) + { + $max = 0; + foreach ((array)$sources as $source) { + if ($source instanceof Minify_Source) { + $max = max($max, $source->lastModified); + } elseif (is_string($source)) { + if (0 === strpos($source, '//')) { + $source = $_SERVER['DOCUMENT_ROOT'] . substr($source, 1); + } + if (is_file($source)) { + $max = max($max, filemtime($source)); + } + } + } + $this->lastModified = $max; + } +} diff --git a/vendors/minify/lib/Minify/CSS.php b/vendors/minify/lib/Minify/CSS.php new file mode 100644 index 0000000..2ce6352 --- /dev/null +++ b/vendors/minify/lib/Minify/CSS.php @@ -0,0 +1,330 @@ +<?php +/** + * Class Minify_CSS + * @package Minify + */ + +/** + * Compress CSS + * + * This is a heavy regex-based removal of whitespace, unnecessary + * comments and tokens, and some CSS value minimization, where practical. + * Many steps have been taken to avoid breaking comment-based hacks, + * including the ie5/mac filter (and its inversion), but expect tricky + * hacks involving comment tokens in 'content' value strings to break + * minimization badly. A test suite is available. + * + * @package Minify + * @author Stephen Clay <steve@mrclay.org> + * @author http://code.google.com/u/1stvamp/ (Issue 64 patch) + */ +class Minify_CSS { + + /** + * Defines which class to call as part of callbacks, change this + * if you extend Minify_CSS + * @var string + */ + protected static $className = 'Minify_CSS'; + + /** + * Minify a CSS string + * + * @param string $css + * + * @param array $options available options: + * + * 'preserveComments': (default true) multi-line comments that begin + * with "/*!" will be preserved with newlines before and after to + * enhance readability. + * + * 'prependRelativePath': (default null) if given, this string will be + * prepended to all relative URIs in import/url declarations + * + * 'currentDir': (default null) if given, this is assumed to be the + * directory of the current CSS file. Using this, minify will rewrite + * all relative URIs in import/url declarations to correctly point to + * the desired files. For this to work, the files *must* exist and be + * visible by the PHP process. + * + * @return string + */ + public static function minify($css, $options = array()) + { + if (isset($options['preserveComments']) + && !$options['preserveComments']) { + return self::_minify($css, $options); + } + require_once 'Minify/CommentPreserver.php'; + // recursive calls don't preserve comments + $options['preserveComments'] = false; + return Minify_CommentPreserver::process( + $css + ,array(self::$className, 'minify') + ,array($options) + ); + } + + /** + * Minify a CSS string + * + * @param string $css + * + * @param array $options To enable URL rewriting, set the value + * for key 'prependRelativePath'. + * + * @return string + */ + protected static function _minify($css, $options) + { + $css = str_replace("\r\n", "\n", $css); + + // preserve empty comment after '>' + // http://www.webdevout.net/css-hacks#in_css-selectors + $css = preg_replace('@>/\\*\\s*\\*/@', '>/*keep*/', $css); + + // preserve empty comment between property and value + // http://css-discuss.incutio.com/?page=BoxModelHack + $css = preg_replace('@/\\*\\s*\\*/\\s*:@', '/*keep*/:', $css); + $css = preg_replace('@:\\s*/\\*\\s*\\*/@', ':/*keep*/', $css); + + // apply callback to all valid comments (and strip out surrounding ws + self::$_inHack = false; + $css = preg_replace_callback('@\\s*/\\*([\\s\\S]*?)\\*/\\s*@' + ,array(self::$className, '_commentCB'), $css); + + // remove ws around { } and last semicolon in declaration block + $css = preg_replace('/\\s*{\\s*/', '{', $css); + $css = preg_replace('/;?\\s*}\\s*/', '}', $css); + + // remove ws surrounding semicolons + $css = preg_replace('/\\s*;\\s*/', ';', $css); + + // remove ws around urls + $css = preg_replace('/ + url\\( # url( + \\s* + ([^\\)]+?) # 1 = the URL (really just a bunch of non right parenthesis) + \\s* + \\) # ) + /x', 'url($1)', $css); + + // remove ws between rules and colons + $css = preg_replace('/ + \\s* + ([{;]) # 1 = beginning of block or rule separator + \\s* + ([\\*_]?[\\w\\-]+) # 2 = property (and maybe IE filter) + \\s* + : + \\s* + (\\b|[#\'"]) # 3 = first character of a value + /x', '$1$2:$3', $css); + + // remove ws in selectors + $css = preg_replace_callback('/ + (?: # non-capture + \\s* + [^~>+,\\s]+ # selector part + \\s* + [,>+~] # combinators + )+ + \\s* + [^~>+,\\s]+ # selector part + { # open declaration block + /x' + ,array(self::$className, '_selectorsCB'), $css); + + // minimize hex colors + $css = preg_replace('/([^=])#([a-f\\d])\\2([a-f\\d])\\3([a-f\\d])\\4([\\s;\\}])/i' + , '$1#$2$3$4$5', $css); + + // remove spaces between font families + $css = preg_replace_callback('/font-family:([^;}]+)([;}])/' + ,array(self::$className, '_fontFamilyCB'), $css); + + $css = preg_replace('/@import\\s+url/', '@import url', $css); + + // replace any ws involving newlines with a single newline + $css = preg_replace('/[ \\t]*\\n+\\s*/', "\n", $css); + + // separate common descendent selectors w/ newlines (to limit line lengths) + $css = preg_replace('/([\\w#\\.\\*]+)\\s+([\\w#\\.\\*]+){/', "$1\n$2{", $css); + + // Use newline after 1st numeric value (to limit line lengths). + $css = preg_replace('/ + ((?:padding|margin|border|outline):\\d+(?:px|em)?) # 1 = prop : 1st numeric value + \\s+ + /x' + ,"$1\n", $css); + + $rewrite = false; + if (isset($options['prependRelativePath'])) { + self::$_tempPrepend = $options['prependRelativePath']; + $rewrite = true; + } elseif (isset($options['currentDir'])) { + self::$_tempCurrentDir = $options['currentDir']; + $rewrite = true; + } + if ($rewrite) { + $css = preg_replace_callback('/@import\\s+([\'"])(.*?)[\'"]/' + ,array(self::$className, '_urlCB'), $css); + $css = preg_replace_callback('/url\\(\\s*([^\\)\\s]+)\\s*\\)/' + ,array(self::$className, '_urlCB'), $css); + } + self::$_tempPrepend = self::$_tempCurrentDir = ''; + return trim($css); + } + + /** + * Replace what looks like a set of selectors + * + * @param array $m regex matches + * + * @return string + */ + protected static function _selectorsCB($m) + { + // remove ws around the combinators + return preg_replace('/\\s*([,>+~])\\s*/', '$1', $m[0]); + } + + /** + * @var bool Are we "in" a hack? + * + * I.e. are some browsers targetted until the next comment? + */ + protected static $_inHack = false; + + /** + * @var string string to be prepended to relative URIs + */ + protected static $_tempPrepend = ''; + + /** + * @var string directory of this stylesheet for rewriting purposes + */ + protected static $_tempCurrentDir = ''; + + /** + * Process a comment and return a replacement + * + * @param array $m regex matches + * + * @return string + */ + protected static function _commentCB($m) + { + $m = $m[1]; + // $m is the comment content w/o the surrounding tokens, + // but the return value will replace the entire comment. + if ($m === 'keep') { + return '/**/'; + } + if ($m === '" "') { + // component of http://tantek.com/CSS/Examples/midpass.html + return '/*" "*/'; + } + if (preg_match('@";\\}\\s*\\}/\\*\\s+@', $m)) { + // component of http://tantek.com/CSS/Examples/midpass.html + return '/*";}}/* */'; + } + if (self::$_inHack) { + // inversion: feeding only to one browser + if (preg_match('@ + ^/ # comment started like /*/ + \\s* + (\\S[\\s\\S]+?) # has at least some non-ws content + \\s* + /\\* # ends like /*/ or /**/ + @x', $m, $n)) { + // end hack mode after this comment, but preserve the hack and comment content + self::$_inHack = false; + return "/*/{$n[1]}/**/"; + } + } + if (substr($m, -1) === '\\') { // comment ends like \*/ + // begin hack mode and preserve hack + self::$_inHack = true; + return '/*\\*/'; + } + if ($m !== '' && $m[0] === '/') { // comment looks like /*/ foo */ + // begin hack mode and preserve hack + self::$_inHack = true; + return '/*/*/'; + } + if (self::$_inHack) { + // a regular comment ends hack mode but should be preserved + self::$_inHack = false; + return '/**/'; + } + return ''; // remove all other comments + } + + protected static function _urlCB($m) + { + $isImport = (0 === strpos($m[0], '@import')); + if ($isImport) { + $quote = $m[1]; + $url = $m[2]; + } else { + // is url() + // $m[1] is either quoted or not + $quote = ($m[1][0] === "'" || $m[1][0] === '"') + ? $m[1][0] + : ''; + $url = ($quote === '') + ? $m[1] + : substr($m[1], 1, strlen($m[1]) - 2); + } + if ('/' !== $url[0]) { + if (strpos($url, '//') > 0) { + // probably starts with protocol, do not alter + } else { + // relative URI, rewrite! + if (self::$_tempPrepend) { + $url = self::$_tempPrepend . $url; + } else { + // rewrite absolute url from scratch! + // prepend path with current dir separator (OS-independent) + $path = self::$_tempCurrentDir + . DIRECTORY_SEPARATOR . strtr($url, '/', DIRECTORY_SEPARATOR); + // strip doc root + $path = substr($path, strlen(realpath($_SERVER['DOCUMENT_ROOT']))); + // fix to absolute URL + $url = strtr($path, DIRECTORY_SEPARATOR, '/'); + // remove /./ and /../ where possible + $url = str_replace('/./', '/', $url); + // inspired by patch from Oleg Cherniy + do { + $url = preg_replace('@/[^/]+/\\.\\./@', '/', $url, -1, $changed); + } while ($changed); + } + } + } + return $isImport + ? "@import {$quote}{$url}{$quote}" + : "url({$quote}{$url}{$quote})"; + } + + /** + * Process a font-family listing and return a replacement + * + * @param array $m regex matches + * + * @return string + */ + protected static function _fontFamilyCB($m) + { + $m[1] = preg_replace('/ + \\s* + ( + "[^"]+" # 1 = family in double qutoes + |\'[^\']+\' # or 1 = family in single quotes + |[\\w\\-]+ # or 1 = unquoted family + ) + \\s* + /x', '$1', $m[1]); + return 'font-family:' . $m[1] . $m[2]; + } +} diff --git a/vendors/minify/lib/Minify/CSS/UriRewriter.php b/vendors/minify/lib/Minify/CSS/UriRewriter.php new file mode 100644 index 0000000..65d915a --- /dev/null +++ b/vendors/minify/lib/Minify/CSS/UriRewriter.php @@ -0,0 +1,107 @@ +<?php +/** + * Class Minify_CSS_UriRewriter + * @package Minify + */ + +/** + * Rewrite file-relative URIs as root-relative in CSS files + * + * @todo: prepend() method + * + * @package Minify + * @author Stephen Clay <steve@mrclay.org> + */ +class Minify_CSS_UriRewriter { + + /** + * Rewrite file relative URIs as root relative in CSS files + * + * @param string $css + * + * @param string $currentDir The directory of the current CSS file. + * + * @param string $docRoot The document root of the web site in which + * the CSS file resides (default = $_SERVER['DOCUMENT_ROOT']). + * + * @return string + */ + public static function rewrite($css, $currentDir, $docRoot = null) + { + self::$_docRoot = $docRoot + ? $docRoot + : $_SERVER['DOCUMENT_ROOT']; + self::$_docRoot = realpath(self::$_docRoot); + self::$_currentDir = realpath($currentDir); + + // remove ws around urls + $css = preg_replace('/ + url\\( # url( + \\s* + ([^\\)]+?) # 1 = URI (really just a bunch of non right parenthesis) + \\s* + \\) # ) + /x', 'url($1)', $css); + + // rewrite + $css = preg_replace_callback('/@import\\s+([\'"])(.*?)[\'"]/' + ,array('Minify_CSS_UriRewriter', '_uriCB'), $css); + $css = preg_replace_callback('/url\\(\\s*([^\\)\\s]+)\\s*\\)/' + ,array('Minify_CSS_UriRewriter', '_uriCB'), $css); + + return $css; + } + + /** + * @var string directory of this stylesheet + */ + private static $_currentDir = ''; + + /** + * @var string DOC_ROOT + */ + private static $_docRoot = ''; + + private static function _uriCB($m) + { + $isImport = ($m[0][0] === '@'); + if ($isImport) { + $quoteChar = $m[1]; + $uri = $m[2]; + } else { + // is url() + // $m[1] is either quoted or not + $quoteChar = ($m[1][0] === "'" || $m[1][0] === '"') + ? $m[1][0] + : ''; + $uri = ($quoteChar === '') + ? $m[1] + : substr($m[1], 1, strlen($m[1]) - 2); + } + if ('/' !== $uri[0]) { + if (strpos($uri, '//') > 0) { + // probably starts with protocol, do not alter + } else { + // it's a file relative URI! + // prepend path with current dir separator (OS-independent) + $path = strtr(self::$_currentDir, '/', DIRECTORY_SEPARATOR) + . DIRECTORY_SEPARATOR . strtr($uri, '/', DIRECTORY_SEPARATOR); + // strip doc root + $path = substr($path, strlen(self::$_docRoot)); + // fix to root-relative URI + $uri = strtr($path, DIRECTORY_SEPARATOR, '/'); + // remove /./ and /../ where possible + $uri = str_replace('/./', '/', $uri); + // inspired by patch from Oleg Cherniy + do { + $uri = preg_replace('@/[^/]+/\\.\\./@', '/', $uri, -1, $changed); + } while ($changed); + } + } + if ($isImport) { + return "@import {$quoteChar}{$uri}{$quoteChar}"; + } else { + return "url({$quoteChar}{$uri}{$quoteChar})"; + } + } +} diff --git a/vendors/minify/lib/Minify/Cache/APC.php b/vendors/minify/lib/Minify/Cache/APC.php new file mode 100644 index 0000000..ca84d29 --- /dev/null +++ b/vendors/minify/lib/Minify/Cache/APC.php @@ -0,0 +1,130 @@ +<?php +/** + * Class Minify_Cache_APC + * @package Minify + */ + +/** + * APC-based cache class for Minify + * + * <code> + * Minify::setCache(new Minify_Cache_APC()); + * </code> + * + * @package Minify + * @author Chris Edwards + **/ +class Minify_Cache_APC { + + /** + * Create a Minify_Cache_APC object, to be passed to + * Minify::setCache(). + * + * + * @param int $expire seconds until expiration (default = 0 + * meaning the item will not get an expiration date) + * + * @return null + */ + public function __construct($expire = 0) + { + $this->_exp = $expire; + } + + /** + * Write data to cache. + * + * @param string $id cache id + * + * @param string $data + * + * @return bool success + */ + public function store($id, $data) + { + return apc_store($id, "{$_SERVER['REQUEST_TIME']}|{$data}", $this->_exp); + } + + /** + * Get the size of a cache entry + * + * @param string $id cache id + * + * @return int size in bytes + */ + public function getSize($id) + { + return $this->_fetch($id) + ? strlen($this->_data) + : false; + } + + /** + * Does a valid cache entry exist? + * + * @param string $id cache id + * + * @param int $srcMtime mtime of the original source file(s) + * + * @return bool exists + */ + public function isValid($id, $srcMtime) + { + return ($this->_fetch($id) && ($this->_lm >= $srcMtime)); + } + + /** + * Send the cached content to output + * + * @param string $id cache id + */ + public function display($id) + { + echo $this->_fetch($id) + ? $this->_data + : ''; + } + + /** + * Fetch the cached content + * + * @param string $id cache id + * + * @return string + */ + public function fetch($id) + { + return $this->_fetch($id) + ? $this->_data + : ''; + } + + private $_exp = null; + + // cache of most recently fetched id + private $_lm = null; + private $_data = null; + private $_id = null; + + /** + * Fetch data and timestamp from apc, store in instance + * + * @param string $id + * + * @return bool success + */ + private function _fetch($id) + { + if ($this->_id === $id) { + return true; + } + $ret = apc_fetch($id); + if (false === $ret) { + $this->_id = null; + return false; + } + list($this->_lm, $this->_data) = explode('|', $ret, 2); + $this->_id = $id; + return true; + } +} diff --git a/vendors/minify/lib/Minify/Cache/File.php b/vendors/minify/lib/Minify/Cache/File.php new file mode 100644 index 0000000..fd4222a --- /dev/null +++ b/vendors/minify/lib/Minify/Cache/File.php @@ -0,0 +1,115 @@ +<?php +/** + * Class Minify_Cache_File + * @package Minify + */ + +class Minify_Cache_File { + + public function __construct($path = '', $fileLocking = false) + { + if (! $path) { + require_once 'Solar/Dir.php'; + $path = rtrim(Solar_Dir::tmp(), DIRECTORY_SEPARATOR); + } + $this->_locking = $fileLocking; + $this->_path = $path; + } + + /** + * Write data to cache. + * + * @param string $id cache id (e.g. a filename) + * + * @param string $data + * + * @return bool success + */ + public function store($id, $data) + { + $flag = $this->_locking + ? LOCK_EX + : null; + if (is_file($this->_path . '/' . $id)) { + @unlink($this->_path . '/' . $id); + } + if (! @file_put_contents($this->_path . '/' . $id, $data, $flag)) { + return false; + } + // write control + if ($data !== $this->fetch($id)) { + @unlink($file); + return false; + } + return true; + } + + /** + * Get the size of a cache entry + * + * @param string $id cache id (e.g. a filename) + * + * @return int size in bytes + */ + public function getSize($id) + { + return filesize($this->_path . '/' . $id); + } + + /** + * Does a valid cache entry exist? + * + * @param string $id cache id (e.g. a filename) + * + * @param int $srcMtime mtime of the original source file(s) + * + * @return bool exists + */ + public function isValid($id, $srcMtime) + { + $file = $this->_path . '/' . $id; + return (is_file($file) && (filemtime($file) >= $srcMtime)); + } + + /** + * Send the cached content to output + * + * @param string $id cache id (e.g. a filename) + */ + public function display($id) + { + if ($this->_locking) { + $fp = fopen($this->_path . '/' . $id, 'rb'); + flock($fp, LOCK_SH); + fpassthru($fp); + flock($fp, LOCK_UN); + fclose($fp); + } else { + readfile($this->_path . '/' . $id); + } + } + + /** + * Fetch the cached content + * + * @param string $id cache id (e.g. a filename) + * + * @return string + */ + public function fetch($id) + { + if ($this->_locking) { + $fp = fopen($this->_path . '/' . $id, 'rb'); + flock($fp, LOCK_SH); + $ret = stream_get_contents($fp); + flock($fp, LOCK_UN); + fclose($fp); + return $ret; + } else { + return file_get_contents($this->_path . '/' . $id); + } + } + + private $_path = null; + private $_locking = null; +} diff --git a/vendors/minify/lib/Minify/Cache/Memcache.php b/vendors/minify/lib/Minify/Cache/Memcache.php new file mode 100644 index 0000000..2b81e7a --- /dev/null +++ b/vendors/minify/lib/Minify/Cache/Memcache.php @@ -0,0 +1,137 @@ +<?php +/** + * Class Minify_Cache_Memcache + * @package Minify + */ + +/** + * Memcache-based cache class for Minify + * + * <code> + * // fall back to disk caching if memcache can't connect + * $memcache = new Memcache; + * if ($memcache->connect('localhost', 11211)) { + * Minify::setCache(new Minify_Cache_Memcache($memcache)); + * } else { + * Minify::setCache(); + * } + * </code> + **/ +class Minify_Cache_Memcache { + + /** + * Create a Minify_Cache_Memcache object, to be passed to + * Minify::setCache(). + * + * @param Memcache $memcache already-connected instance + * + * @param int $expire seconds until expiration (default = 0 + * meaning the item will not get an expiration date) + * + * @return null + */ + public function __construct($memcache, $expire = 0) + { + $this->_mc = $memcache; + $this->_exp = $expire; + } + + /** + * Write data to cache. + * + * @param string $id cache id + * + * @param string $data + * + * @return bool success + */ + public function store($id, $data) + { + return $this->_mc->set($id, "{$_SERVER['REQUEST_TIME']}|{$data}", 0, $this->_exp); + } + + + /** + * Get the size of a cache entry + * + * @param string $id cache id + * + * @return int size in bytes + */ + public function getSize($id) + { + return $this->_fetch($id) + ? strlen($this->_data) + : false; + } + + /** + * Does a valid cache entry exist? + * + * @param string $id cache id + * + * @param int $srcMtime mtime of the original source file(s) + * + * @return bool exists + */ + public function isValid($id, $srcMtime) + { + return ($this->_fetch($id) && ($this->_lm >= $srcMtime)); + } + + /** + * Send the cached content to output + * + * @param string $id cache id + */ + public function display($id) + { + echo $this->_fetch($id) + ? $this->_data + : ''; + } + + /** + * Fetch the cached content + * + * @param string $id cache id + * + * @return string + */ + public function fetch($id) + { + return $this->_fetch($id) + ? $this->_data + : ''; + } + + private $_mc = null; + private $_exp = null; + + // cache of most recently fetched id + private $_lm = null; + private $_data = null; + private $_id = null; + + /** + * Fetch data and timestamp from memcache, store in instance + * + * @param string $id + * + * @return bool success + */ + private function _fetch($id) + { + if ($this->_id === $id) { + return true; + } + $ret = $this->_mc->get($id); + if (false === $ret) { + $this->_id = null; + return false; + } + list($this->_lm, $this->_data) = explode('|', $ret, 2); + $this->_id = $id; + return true; + } +} diff --git a/vendors/minify/lib/Minify/CommentPreserver.php b/vendors/minify/lib/Minify/CommentPreserver.php new file mode 100644 index 0000000..f56eb34 --- /dev/null +++ b/vendors/minify/lib/Minify/CommentPreserver.php @@ -0,0 +1,90 @@ +<?php +/** + * Class Minify_CommentPreserver + * @package Minify + */ + +/** + * Process a string in pieces preserving C-style comments that begin with "/*!" + * + * @package Minify + * @author Stephen Clay <steve@mrclay.org> + */ +class Minify_CommentPreserver { + + /** + * String to be prepended to each preserved comment + * + * @var string + */ + public static $prepend = "\n"; + + /** + * String to be appended to each preserved comment + * + * @var string + */ + public static $append = "\n"; + + /** + * Process a string outside of C-style comments that begin with "/*!" + * + * On each non-empty string outside these comments, the given processor + * function will be called. The first "!" will be removed from the + * preserved comments, and the comments will be surrounded by + * Minify_CommentPreserver::$preprend and Minify_CommentPreserver::$append. + * + * @param string $content + * @param callback $processor function + * @param array $args array of extra arguments to pass to the processor + * function (default = array()) + * @return string + */ + public static function process($content, $processor, $args = array()) + { + $ret = ''; + while (true) { + list($beforeComment, $comment, $afterComment) = self::_nextComment($content); + if ('' !== $beforeComment) { + $callArgs = $args; + array_unshift($callArgs, $beforeComment); + $ret .= call_user_func_array($processor, $callArgs); + } + if (false === $comment) { + break; + } + $ret .= $comment; + $content = $afterComment; + } + return $ret; + } + + /** + * Extract comments that YUI Compressor preserves. + * + * @param string $in input + * + * @return array 3 elements are returned. If a YUI comment is found, the + * 2nd element is the comment and the 1st and 2nd are the surrounding + * strings. If no comment is found, the entire string is returned as the + * 1st element and the other two are false. + */ + private static function _nextComment($in) + { + if ( + false === ($start = strpos($in, '/*!')) + || false === ($end = strpos($in, '*/', $start + 3)) + ) { + return array($in, false, false); + } + $ret = array( + substr($in, 0, $start) + ,self::$prepend . '/*' . substr($in, $start + 3, $end - $start - 1) . self::$append + ); + $endChars = (strlen($in) - $end - 2); + $ret[] = (0 === $endChars) + ? '' + : substr($in, -$endChars); + return $ret; + } +} diff --git a/vendors/minify/lib/Minify/Controller/Base.php b/vendors/minify/lib/Minify/Controller/Base.php new file mode 100644 index 0000000..014196d --- /dev/null +++ b/vendors/minify/lib/Minify/Controller/Base.php @@ -0,0 +1,191 @@ +<?php +/** + * Class Minify_Controller_Base + * @package Minify + */ + +/** + * Base class for Minify controller + * + * The controller class validates a request and uses it to create sources + * for minification and set options like contentType. It's also responsible + * for loading minifier code upon request. + * + * @package Minify + * @author Stephen Clay <steve@mrclay.org> + */ +abstract class Minify_Controller_Base { + + /** + * Setup controller sources and set an needed options for Minify::source + * + * You must override this method in your subclass controller to set + * $this->sources. If the request is NOT valid, make sure $this->sources + * is left an empty array. Then strip any controller-specific options from + * $options and return it. To serve files, $this->sources must be an array of + * Minify_Source objects. + * + * @param array $options controller and Minify options + * + * return array $options Minify::serve options + */ + abstract public function setupSources($options); + + /** + * Get default Minify options for this controller. + * + * Override in subclass to change defaults + * + * @return array options for Minify + */ + public function getDefaultMinifyOptions() { + return array( + 'isPublic' => true + ,'encodeOutput' => function_exists('gzdeflate') + ,'encodeMethod' => null // determine later + ,'encodeLevel' => 9 + ,'minifierOptions' => array() // no minifier options + ,'contentTypeCharset' => 'UTF-8' + ,'maxAge' => 1800 // 30 minutes + ,'rewriteCssUris' => true + ,'quiet' => false // serve() will send headers and output + ,'debug' => false + + // if you override this, the response code MUST be directly after + // the first space. + ,'badRequestHeader' => 'HTTP/1.0 400 Bad Request' + + // callback function to see/modify content of all sources + ,'postprocessor' => null + // file to require to load preprocessor + ,'postprocessorRequire' => null + ); + } + + /** + * Get default minifiers for this controller. + * + * Override in subclass to change defaults + * + * @return array minifier callbacks for common types + */ + public function getDefaultMinifers() { + $ret[Minify::TYPE_JS] = array('Minify_Javascript', 'minify'); + $ret[Minify::TYPE_CSS] = array('Minify_CSS', 'minify'); + $ret[Minify::TYPE_HTML] = array('Minify_HTML', 'minify'); + return $ret; + } + + /** + * Load any code necessary to execute the given minifier callback. + * + * The controller is responsible for loading minification code on demand + * via this method. This built-in function will only load classes for + * static method callbacks where the class isn't already defined. It uses + * the PEAR convention, so, given array('Jimmy_Minifier', 'minCss'), this + * function will include 'Jimmy/Minifier.php'. + * + * If you need code loaded on demand and this doesn't suit you, you'll need + * to override this function in your subclass. + * @see Minify_Controller_Page::loadMinifier() + * + * @param callback $minifierCallback callback of minifier function + * + * @return null + */ + public function loadMinifier($minifierCallback) + { + if (is_array($minifierCallback) + && is_string($minifierCallback[0]) + && !class_exists($minifierCallback[0], false)) { + + require str_replace('_', '/', $minifierCallback[0]) . '.php'; + } + } + + /** + * Is a user-given file within an allowable directory, existing, + * and having an extension js/css/html/txt ? + * + * This is a convenience function for controllers that have to accept + * user-given paths + * + * @param string $file full file path (already processed by realpath()) + * + * @param array $safeDirs directories where files are safe to serve. Files can also + * be in subdirectories of these directories. + * + * @return bool file is safe + */ + public static function _fileIsSafe($file, $safeDirs) + { + $pathOk = false; + foreach ((array)$safeDirs as $safeDir) { + if (strpos($file, $safeDir) === 0) { + $pathOk = true; + break; + } + } + $base = basename($file); + if (! $pathOk || ! is_file($file) || $base[0] === '.') { + return false; + } + list($revExt) = explode('.', strrev($base)); + return in_array(strrev($revExt), array('js', 'css', 'html', 'txt')); + } + + /** + * @var array instances of Minify_Source, which provide content and + * any individual minification needs. + * + * @see Minify_Source + */ + public $sources = array(); + + /** + * Mix in default controller options with user-given options + * + * @param array $options user options + * + * @return array mixed options + */ + public final function mixInDefaultOptions($options) + { + $ret = array_merge( + $this->getDefaultMinifyOptions(), $options + ); + if (! isset($options['minifiers'])) { + $options['minifiers'] = array(); + } + $ret['minifiers'] = array_merge( + $this->getDefaultMinifers(), $options['minifiers'] + ); + return $ret; + } + + /** + * Analyze sources (if there are any) and set $options 'contentType' + * and 'lastModifiedTime' if they already aren't. + * + * @param array $options options for Minify + * + * @return array options for Minify + */ + public final function analyzeSources($options = array()) + { + if ($this->sources) { + if (! isset($options['contentType'])) { + $options['contentType'] = Minify_Source::getContentType($this->sources); + } + // last modified is needed for caching, even if setExpires is set + if (! isset($options['lastModifiedTime'])) { + $max = 0; + foreach ($this->sources as $source) { + $max = max($source->lastModified, $max); + } + $options['lastModifiedTime'] = $max; + } + } + return $options; + } +} diff --git a/vendors/minify/lib/Minify/Controller/Files.php b/vendors/minify/lib/Minify/Controller/Files.php new file mode 100644 index 0000000..25cf933 --- /dev/null +++ b/vendors/minify/lib/Minify/Controller/Files.php @@ -0,0 +1,71 @@ +<?php +/** + * Class Minify_Controller_Files + * @package Minify + */ + +require_once 'Minify/Controller/Base.php'; + +/** + * Controller class for minifying a set of files + * + * E.g. the following would serve the minified Javascript for a site + * <code> + * Minify::serve('Files', array( + * 'files' => array( + * '//js/jquery.js' + * ,'//js/plugins.js' + * ,'/home/username/file.js' + * ) + * )); + * </code> + * + * As a shortcut, the controller will replace "//" at the beginning + * of a filename with $_SERVER['DOCUMENT_ROOT'] . '/'. + * + * @package Minify + * @author Stephen Clay <steve@mrclay.org> + */ +class Minify_Controller_Files extends Minify_Controller_Base { + + /** + * Set up file sources + * + * @param array $options controller and Minify options + * @return array Minify options + * + * Controller options: + * + * 'files': (required) array of complete file paths, or a single path + */ + public function setupSources($options) { + // strip controller options + $files = (array)$options['files']; + unset($options['files']); + + $sources = array(); + foreach ($files as $file) { + if ($file instanceof Minify_Source) { + $sources[] = $file; + continue; + } + if (0 === strpos($file, '//')) { + $file = $_SERVER['DOCUMENT_ROOT'] . substr($file, 1); + } + $file = realpath($file); + if (file_exists($file)) { + $sources[] = new Minify_Source(array( + 'filepath' => $file + )); + } else { + // file not found + return $options; + } + } + if ($sources) { + $this->sources = $sources; + } + return $options; + } +} + diff --git a/vendors/minify/lib/Minify/Controller/Groups.php b/vendors/minify/lib/Minify/Controller/Groups.php new file mode 100644 index 0000000..eb828e0 --- /dev/null +++ b/vendors/minify/lib/Minify/Controller/Groups.php @@ -0,0 +1,85 @@ +<?php +/** + * Class Minify_Controller_Groups + * @package Minify + */ + +require_once 'Minify/Controller/Base.php'; + +/** + * Controller class for serving predetermined groups of minimized sets, selected + * by PATH_INFO + * + * <code> + * Minify::serve('Groups', array( + * 'groups' => array( + * 'css' => array('//css/type.css', '//css/layout.css') + * ,'js' => array('//js/jquery.js', '//js/site.js') + * ) + * )); + * </code> + * + * If the above code were placed in /serve.php, it would enable the URLs + * /serve.php/js and /serve.php/css + * + * As a shortcut, the controller will replace "//" at the beginning + * of a filename with $_SERVER['DOCUMENT_ROOT'] . '/'. + * + * @package Minify + * @author Stephen Clay <steve@mrclay.org> + */ +class Minify_Controller_Groups extends Minify_Controller_Base { + + /** + * Set up groups of files as sources + * + * @param array $options controller and Minify options + * @return array Minify options + * + * Controller options: + * + * 'groups': (required) array mapping PATH_INFO strings to arrays + * of complete file paths. @see Minify_Controller_Groups + */ + public function setupSources($options) { + // strip controller options + $groups = $options['groups']; + unset($options['groups']); + + // mod_fcgid places PATH_INFO in ORIG_PATH_INFO + $pi = isset($_SERVER['ORIG_PATH_INFO']) + ? substr($_SERVER['ORIG_PATH_INFO'], 1) + : (isset($_SERVER['PATH_INFO']) + ? substr($_SERVER['PATH_INFO'], 1) + : false + ); + if (false === $pi || ! isset($groups[$pi])) { + // no PATH_INFO or not a valid group + return $options; + } + $sources = array(); + foreach ((array)$groups[$pi] as $file) { + if ($file instanceof Minify_Source) { + $sources[] = $file; + continue; + } + if (0 === strpos($file, '//')) { + $file = $_SERVER['DOCUMENT_ROOT'] . substr($file, 1); + } + $file = realpath($file); + if (file_exists($file)) { + $sources[] = new Minify_Source(array( + 'filepath' => $file + )); + } else { + // file doesn't exist + return $options; + } + } + if ($sources) { + $this->sources = $sources; + } + return $options; + } +} + diff --git a/vendors/minify/lib/Minify/Controller/MinApp.php b/vendors/minify/lib/Minify/Controller/MinApp.php new file mode 100644 index 0000000..0e6d31d --- /dev/null +++ b/vendors/minify/lib/Minify/Controller/MinApp.php @@ -0,0 +1,116 @@ +<?php +/** + * Class Minify_Controller_MinApp + * @package Minify + */ + +require_once 'Minify/Controller/Base.php'; + +/** + * Controller class for requests to /min/index.php + * + * @package Minify + * @author Stephen Clay <steve@mrclay.org> + */ +class Minify_Controller_MinApp extends Minify_Controller_Base { + + /** + * Set up groups of files as sources + * + * @param array $options controller and Minify options + * @return array Minify options + * + */ + public function setupSources($options) { + // filter controller options + $cOptions = array_merge( + array( + 'allowDirs' => '//' + ,'groupsOnly' => false + ,'groups' => array() + ,'maxFiles' => 10 + ) + ,(isset($options['minApp']) ? $options['minApp'] : array()) + ); + unset($options['minApp']); + $sources = array(); + if (isset($_GET['g'])) { + // try groups + if (! isset($cOptions['groups'][$_GET['g']])) { + return $options; + } + foreach ((array)$cOptions['groups'][$_GET['g']] as $file) { + if ($file instanceof Minify_Source) { + $sources[] = $file; + continue; + } + if (0 === strpos($file, '//')) { + $file = $_SERVER['DOCUMENT_ROOT'] . substr($file, 1); + } + $file = realpath($file); + if (is_file($file)) { + $sources[] = new Minify_Source(array( + 'filepath' => $file + )); + } else { + // file doesn't exist + return $options; + } + } + } elseif (! $cOptions['groupsOnly'] && isset($_GET['f'])) { + // try user files + // The following restrictions are to limit the URLs that minify will + // respond to. Ideally there should be only one way to reference a file. + if (// verify at least one file, files are single comma separated, + // and are all same extension + ! preg_match('/^[^,]+\\.(css|js)(?:,[^,]+\\.\\1)*$/', $_GET['f']) + // no "//" + || strpos($_GET['f'], '//') !== false + // no "\" + || strpos($_GET['f'], '\\') !== false + // no "./" + || preg_match('/(?:^|[^\\.])\\.\\//', $_GET['f']) + ) { + return $options; + } + $files = explode(',', $_GET['f']); + if (count($files) > $cOptions['maxFiles'] || $files != array_unique($files)) { + // too many or duplicate files + return $options; + } + if (isset($_GET['b'])) { + // check for validity + if (preg_match('@^[^/]+(?:/[^/]+)*$@', $_GET['b']) + && false === strpos($_GET['b'], '..') + && $_GET['b'] !== '.') { + // valid base + $base = "/{$_GET['b']}/"; + } else { + return $options; + } + } else { + $base = '/'; + } + $allowDirs = array(); + foreach ((array)$cOptions['allowDirs'] as $allowDir) { + $allowDirs[] = realpath(str_replace('//', $_SERVER['DOCUMENT_ROOT'] . '/', $allowDir)); + } + foreach ($files as $file) { + $file = realpath($_SERVER['DOCUMENT_ROOT'] . $base . $file); + // don't allow unsafe or duplicate files + if (parent::_fileIsSafe($file, $allowDirs)) { + $sources[] = new Minify_Source(array( + 'filepath' => $file + )); + } else { + // unsafe file + return $options; + } + } + } + if ($sources) { + $this->sources = $sources; + } + return $options; + } +} diff --git a/vendors/minify/lib/Minify/Controller/Page.php b/vendors/minify/lib/Minify/Controller/Page.php new file mode 100644 index 0000000..b1ff9fd --- /dev/null +++ b/vendors/minify/lib/Minify/Controller/Page.php @@ -0,0 +1,85 @@ +<?php +/** + * Class Minify_Controller_Page + * @package Minify + */ + +require_once 'Minify/Controller/Base.php'; + +/** + * Controller class for serving a single HTML page + * + * @link http://code.google.com/p/minify/source/browse/trunk/web/examples/1/index.php#59 + * @package Minify + * @author Stephen Clay <steve@mrclay.org> + */ +class Minify_Controller_Page extends Minify_Controller_Base { + + /** + * Set up source of HTML content + * + * @param array $options controller and Minify options + * @return array Minify options + * + * Controller options: + * + * 'content': (required) HTML markup + * + * 'id': (required) id of page (string for use in server-side caching) + * + * 'lastModifiedTime': timestamp of when this content changed. This + * is recommended to allow both server and client-side caching. + * + * 'minifyAll': should all CSS and Javascript blocks be individually + * minified? (default false) + * + * @todo Add 'file' option to read HTML file. + */ + public function setupSources($options) { + if (isset($options['file'])) { + $sourceSpec = array( + 'filepath' => $options['file'] + ); + } else { + // strip controller options + $sourceSpec = array( + 'content' => $options['content'] + ,'id' => $options['id'] + ); + unset($options['content'], $options['id']); + } + if (isset($options['minifyAll'])) { + // this will be the 2nd argument passed to Minify_HTML::minify() + $sourceSpec['minifyOptions'] = array( + 'cssMinifier' => array('Minify_CSS', 'minify') + ,'jsMinifier' => array('Minify_Javascript', 'minify') + ); + $this->_loadCssJsMinifiers = true; + unset($options['minifyAll']); + } + $this->sources[] = new Minify_Source($sourceSpec); + + // may not be needed + //$options['minifier'] = array('Minify_HTML', 'minify'); + + $options['contentType'] = Minify::TYPE_HTML; + return $options; + } + + protected $_loadCssJsMinifiers = false; + + /** + * @see Minify_Controller_Base::loadMinifier() + */ + public function loadMinifier($minifierCallback) + { + if ($this->_loadCssJsMinifiers) { + // Minify will not call for these so we must manually load + // them when Minify/HTML.php is called for. + require 'Minify/CSS.php'; + require 'Minify/Javascript.php'; + } + parent::loadMinifier($minifierCallback); // load Minify/HTML.php + } +} + diff --git a/vendors/minify/lib/Minify/Controller/Version1.php b/vendors/minify/lib/Minify/Controller/Version1.php new file mode 100644 index 0000000..1861aab --- /dev/null +++ b/vendors/minify/lib/Minify/Controller/Version1.php @@ -0,0 +1,118 @@ +<?php +/** + * Class Minify_Controller_Version1 + * @package Minify + */ + +require_once 'Minify/Controller/Base.php'; + +/** + * Controller class for emulating version 1 of minify.php + * + * <code> + * Minify::serve('Version1'); + * </code> + * + * @package Minify + * @author Stephen Clay <steve@mrclay.org> + */ +class Minify_Controller_Version1 extends Minify_Controller_Base { + + /** + * Set up groups of files as sources + * + * @param array $options controller and Minify options + * @return array Minify options + * + */ + public function setupSources($options) { + self::_setupDefines(); + if (MINIFY_USE_CACHE) { + $cacheDir = defined('MINIFY_CACHE_DIR') + ? MINIFY_CACHE_DIR + : ''; + Minify::setCache($cacheDir); + } + $options['badRequestHeader'] = 'HTTP/1.0 404 Not Found'; + $options['contentTypeCharset'] = MINIFY_ENCODING; + + // The following restrictions are to limit the URLs that minify will + // respond to. Ideally there should be only one way to reference a file. + if (! isset($_GET['files']) + // verify at least one file, files are single comma separated, + // and are all same extension + || ! preg_match('/^[^,]+\\.(css|js)(,[^,]+\\.\\1)*$/', $_GET['files'], $m) + // no "//" (makes URL rewriting easier) + || strpos($_GET['files'], '//') !== false + // no "\" + || strpos($_GET['files'], '\\') !== false + // no "./" + || preg_match('/(?:^|[^\\.])\\.\\//', $_GET['files']) + ) { + return $options; + } + $extension = $m[1]; + + $files = explode(',', $_GET['files']); + if (count($files) > MINIFY_MAX_FILES) { + return $options; + } + + // strings for prepending to relative/absolute paths + $prependRelPaths = dirname($_SERVER['SCRIPT_FILENAME']) + . DIRECTORY_SEPARATOR; + $prependAbsPaths = $_SERVER['DOCUMENT_ROOT']; + + $sources = array(); + $goodFiles = array(); + $hasBadSource = false; + + $allowDirs = isset($options['allowDirs']) + ? $options['allowDirs'] + : MINIFY_BASE_DIR; + + foreach ($files as $file) { + // prepend appropriate string for abs/rel paths + $file = ($file[0] === '/' ? $prependAbsPaths : $prependRelPaths) . $file; + // make sure a real file! + $file = realpath($file); + // don't allow unsafe or duplicate files + if (parent::_fileIsSafe($file, $allowDirs) + && !in_array($file, $goodFiles)) + { + $goodFiles[] = $file; + $srcOptions = array( + 'filepath' => $file + ); + $this->sources[] = new Minify_Source($srcOptions); + } else { + $hasBadSource = true; + break; + } + } + if ($hasBadSource) { + $this->sources = array(); + } + if (! MINIFY_REWRITE_CSS_URLS) { + $options['rewriteCssUris'] = false; + } + return $options; + } + + private static function _setupDefines() + { + $defaults = array( + 'MINIFY_BASE_DIR' => realpath($_SERVER['DOCUMENT_ROOT']) + ,'MINIFY_ENCODING' => 'utf-8' + ,'MINIFY_MAX_FILES' => 16 + ,'MINIFY_REWRITE_CSS_URLS' => true + ,'MINIFY_USE_CACHE' => true + ); + foreach ($defaults as $const => $val) { + if (! defined($const)) { + define($const, $val); + } + } + } +} + diff --git a/vendors/minify/lib/Minify/HTML.php b/vendors/minify/lib/Minify/HTML.php new file mode 100644 index 0000000..8998187 --- /dev/null +++ b/vendors/minify/lib/Minify/HTML.php @@ -0,0 +1,218 @@ +<?php +/** + * Class Minify_HTML + * @package Minify + */ + +/** + * Compress HTML + * + * This is a heavy regex-based removal of whitespace, unnecessary comments and + * tokens. IE conditional comments are preserved. There are also options to have + * STYLE and SCRIPT blocks compressed by callback functions. + * + * A test suite is available. + * + * @package Minify + * @author Stephen Clay <steve@mrclay.org> + */ +class Minify_HTML { + + /** + * Defines which class to call as part of callbacks, change this + * if you extend Minify_HTML + * @var string + */ + protected static $className = 'Minify_HTML'; + + /** + * "Minify" an HTML page + * + * @param string $html + * + * @param array $options + * + * 'cssMinifier' : (optional) callback function to process content of STYLE + * elements. + * + * 'jsMinifier' : (optional) callback function to process content of SCRIPT + * elements. Note: the type attribute is ignored. + * + * 'xhtml' : (optional boolean) should content be treated as XHTML1.0? If + * unset, minify will sniff for an XHTML doctype. + * + * @return string + */ + public static function minify($html, $options = array()) { + + if (isset($options['cssMinifier'])) { + self::$_cssMinifier = $options['cssMinifier']; + } + if (isset($options['jsMinifier'])) { + self::$_jsMinifier = $options['jsMinifier']; + } + + $html = str_replace("\r\n", "\n", trim($html)); + + self::$_isXhtml = ( + isset($options['xhtml']) + ? (bool)$options['xhtml'] + : (false !== strpos($html, '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML')) + ); + + self::$_replacementHash = 'MINIFYHTML' . md5(time()); + self::$_placeholders = array(); + + // replace SCRIPTs (and minify) with placeholders + $html = preg_replace_callback( + '/\\s*(<script\\b[^>]*?>)([\\s\\S]*?)<\\/script>\\s*/i' + ,array(self::$className, '_removeScriptCB') + ,$html); + + // replace STYLEs (and minify) with placeholders + $html = preg_replace_callback( + '/\\s*(<style\\b[^>]*?>)([\\s\\S]*?)<\\/style>\\s*/i' + ,array(self::$className, '_removeStyleCB') + ,$html); + + // remove HTML comments (not containing IE conditional comments). + $html = preg_replace_callback( + '/<!--([\\s\\S]*?)-->/' + ,array(self::$className, '_commentCB') + ,$html); + + // replace PREs with placeholders + $html = preg_replace_callback('/\\s*(<pre\\b[^>]*?>[\\s\\S]*?<\\/pre>)\\s*/i' + ,array(self::$className, '_removePreCB') + , $html); + + // replace TEXTAREAs with placeholders + $html = preg_replace_callback( + '/\\s*(<textarea\\b[^>]*?>[\\s\\S]*?<\\/textarea>)\\s*/i' + ,array(self::$className, '_removeTaCB') + , $html); + + // trim each line. + // @todo take into account attribute values that span multiple lines. + $html = preg_replace('/^\\s+|\\s+$/m', '', $html); + + // remove ws around block/undisplayed elements + $html = preg_replace('/\\s+(<\\/?(?:area|base(?:font)?|blockquote|body' + .'|caption|center|cite|col(?:group)?|dd|dir|div|dl|dt|fieldset|form' + .'|frame(?:set)?|h[1-6]|head|hr|html|legend|li|link|map|menu|meta' + .'|ol|opt(?:group|ion)|p|param|t(?:able|body|head|d|h||r|foot|itle)' + .'|ul)\\b[^>]*>)/i', '$1', $html); + + // remove ws outside of all elements + $html = preg_replace_callback( + '/>([^<]+)</' + ,array(self::$className, '_outsideTagCB') + ,$html); + + // use newlines before 1st attribute in open tags (to limit line lengths) + $html = preg_replace('/(<[a-z\\-]+)\\s+([^>]+>)/i', "$1\n$2", $html); + + // fill placeholders + $html = str_replace( + array_keys(self::$_placeholders) + ,array_values(self::$_placeholders) + ,$html + ); + self::$_placeholders = array(); + + self::$_cssMinifier = self::$_jsMinifier = null; + return $html; + } + + protected static function _commentCB($m) + { + return (0 === strpos($m[1], '[') || false !== strpos($m[1], '<![')) + ? $m[0] + : ''; + } + + protected static function _reservePlace($content) + { + $placeholder = '%' . self::$_replacementHash . count(self::$_placeholders) . '%'; + self::$_placeholders[$placeholder] = $content; + return $placeholder; + } + + protected static $_isXhtml = false; + protected static $_replacementHash = null; + protected static $_placeholders = array(); + protected static $_cssMinifier = null; + protected static $_jsMinifier = null; + + protected static function _outsideTagCB($m) + { + return '>' . preg_replace('/^\\s+|\\s+$/', ' ', $m[1]) . '<'; + } + + protected static function _removePreCB($m) + { + return self::_reservePlace($m[1]); + } + + protected static function _removeTaCB($m) + { + return self::_reservePlace($m[1]); + } + + protected static function _removeStyleCB($m) + { + $openStyle = $m[1]; + $css = $m[2]; + // remove HTML comments + $css = preg_replace('/(?:^\\s*<!--|-->\\s*$)/', '', $css); + + // remove CDATA section markers + $css = self::_removeCdata($css); + + // minify + $minifier = self::$_cssMinifier + ? self::$_cssMinifier + : 'trim'; + $css = call_user_func($minifier, $css); + + return self::_reservePlace(self::_needsCdata($css) + ? "{$openStyle}/*<![CDATA[*/{$css}/*]]>*/</style>" + : "{$openStyle}{$css}</style>" + ); + } + + protected static function _removeScriptCB($m) + { + $openScript = $m[1]; + $js = $m[2]; + + // remove HTML comments (and ending "//" if present) + $js = preg_replace('/(?:^\\s*<!--\\s*|\\s*(?:\\/\\/)?\\s*-->\\s*$)/', '', $js); + + // remove CDATA section markers + $js = self::_removeCdata($js); + + // minify + $minifier = self::$_jsMinifier + ? self::$_jsMinifier + : 'trim'; + $js = call_user_func($minifier, $js); + + return self::_reservePlace(self::_needsCdata($js) + ? "{$openScript}/*<![CDATA[*/{$js}/*]]>*/</script>" + : "{$openScript}{$js}</script>" + ); + } + + protected static function _removeCdata($str) + { + return (false !== strpos($str, '<![CDATA[')) + ? str_replace(array('<![CDATA[', ']]>'), '', $str) + : $str; + } + + protected static function _needsCdata($str) + { + return (self::$_isXhtml && preg_match('/(?:[<&]|\\-\\-|\\]\\]>)/', $str)); + } +} diff --git a/vendors/minify/lib/Minify/ImportProcessor.php b/vendors/minify/lib/Minify/ImportProcessor.php new file mode 100644 index 0000000..be7efb7 --- /dev/null +++ b/vendors/minify/lib/Minify/ImportProcessor.php @@ -0,0 +1,157 @@ +<?php +/** + * Class Minify_ImportProcessor + * @package Minify + */ + +/** + * Linearize a CSS/JS file by including content specified by CSS import + * declarations. In CSS files, relative URIs are fixed. + * + * @imports will be processed regardless of where they appear in the source + * files; i.e. @imports commented out or in string content will still be + * processed! + * + * This has a unit test but should be considered "experimental". + * + * @package Minify + * @author Stephen Clay <steve@mrclay.org> + */ +class Minify_ImportProcessor { + + public static $filesIncluded = array(); + + public static function process($file) + { + self::$filesIncluded = array(); + self::$_isCss = (strtolower(substr($file, -4)) === '.css'); + $obj = new Minify_ImportProcessor(dirname($file)); + return $obj->_getContent($file); + } + + // allows callback funcs to know the current directory + private $_currentDir = null; + + // allows _importCB to write the fetched content back to the obj + private $_importedContent = ''; + + private static $_isCss = null; + + private function __construct($currentDir) + { + $this->_currentDir = $currentDir; + } + + private function _getContent($file) + { + $file = realpath($file); + if (! $file + || in_array($file, self::$filesIncluded) + || false === ($content = @file_get_contents($file)) + ) { + // file missing, already included, or failed read + return ''; + } + self::$filesIncluded[] = realpath($file); + $this->_currentDir = dirname($file); + + // remove UTF-8 BOM if present + if (pack("CCC",0xef,0xbb,0xbf) === substr($content, 0, 3)) { + $content = substr($content, 3); + } + // ensure uniform EOLs + $content = str_replace("\r\n", "\n", $content); + + // process @imports + $content = preg_replace_callback( + '/ + @import\\s+ + (?:url\\(\\s*)? # maybe url( + [\'"]? # maybe quote + (.*?) # 1 = URI + [\'"]? # maybe end quote + (?:\\s*\\))? # maybe ) + ([a-zA-Z,\\s]*)? # 2 = media list + ; # end token + /x' + ,array($this, '_importCB') + ,$content + ); + + if (self::$_isCss) { + // rewrite remaining relative URIs + $content = preg_replace_callback( + '/url\\(\\s*([^\\)\\s]+)\\s*\\)/' + ,array($this, '_urlCB') + ,$content + ); + } + + return $this->_importedContent . $content; + } + + private function _importCB($m) + { + $url = $m[1]; + $mediaList = preg_replace('/\\s+/', '', $m[2]); + + if (strpos($url, '://') > 0) { + // protocol, leave in place for CSS, comment for JS + return self::$_isCss + ? $m[0] + : "/* Minify_ImportProcessor will not include remote content */"; + } + if ('/' === $url[0]) { + // protocol-relative or root path + $url = ltrim($url, '/'); + $file = realpath($_SERVER['DOCUMENT_ROOT']) . DIRECTORY_SEPARATOR + . strtr($url, '/', DIRECTORY_SEPARATOR); + } else { + // relative to current path + $file = $this->_currentDir . DIRECTORY_SEPARATOR + . strtr($url, '/', DIRECTORY_SEPARATOR); + } + $obj = new Minify_ImportProcessor(dirname($file)); + $content = $obj->_getContent($file); + if ('' === $content) { + // failed. leave in place for CSS, comment for JS + return self::$_isCss + ? $m[0] + : "/* Minify_ImportProcessor could not fetch '{$file}' */";; + } + return (!self::$_isCss || preg_match('@(?:^$|\\ball\\b)@', $mediaList)) + ? $content + : "@media {$mediaList} {\n{$content}\n}\n"; + } + + private function _urlCB($m) + { + // $m[1] is either quoted or not + $quote = ($m[1][0] === "'" || $m[1][0] === '"') + ? $m[1][0] + : ''; + $url = ($quote === '') + ? $m[1] + : substr($m[1], 1, strlen($m[1]) - 2); + if ('/' !== $url[0]) { + if (strpos($url, '//') > 0) { + // probably starts with protocol, do not alter + } else { + // prepend path with current dir separator (OS-independent) + $path = $this->_currentDir + . DIRECTORY_SEPARATOR . strtr($url, '/', DIRECTORY_SEPARATOR); + // strip doc root + $path = substr($path, strlen(realpath($_SERVER['DOCUMENT_ROOT']))); + // fix to absolute URL + $url = strtr($path, DIRECTORY_SEPARATOR, '/'); + // remove /./ and /../ where possible + $url = str_replace('/./', '/', $url); + // inspired by patch from Oleg Cherniy + do { + $url = preg_replace('@/[^/]+/\\.\\./@', '/', $url, -1, $changed); + } while ($changed); + } + } + return "url({$quote}{$url}{$quote})"; + } +} diff --git a/vendors/minify/lib/Minify/Javascript.php b/vendors/minify/lib/Minify/Javascript.php new file mode 100644 index 0000000..c372f9e --- /dev/null +++ b/vendors/minify/lib/Minify/Javascript.php @@ -0,0 +1,31 @@ +<?php +/** + * Class Minify_Javascript + * @package Minify + */ + +require 'JSMin.php'; + +/** + * Compress Javascript using Ryan Grove's JSMin class + * + * @package Minify + * @author Stephen Clay <steve@mrclay.org> + */ +class Minify_Javascript { + + /** + * Minify a Javascript string + * + * @param string $js + * + * @param array $options available options (none currently) + * + * @return string + */ + public static function minify($js, $options = array()) + { + return trim(JSMin::minify($js)); + } +} + diff --git a/vendors/minify/lib/Minify/Lines.php b/vendors/minify/lib/Minify/Lines.php new file mode 100644 index 0000000..f10d318 --- /dev/null +++ b/vendors/minify/lib/Minify/Lines.php @@ -0,0 +1,108 @@ +<?php +/** + * Class Minify_Lines + * @package Minify + */ + +/** + * Add line numbers in C-style comments for easier debugging of combined content + * + * @package Minify + * @author Stephen Clay <steve@mrclay.org> + * @author Adam Pedersen (Issue 55 fix) + */ +class Minify_Lines { + + /** + * Add line numbers in C-style comments + * + * This uses a very basic parser easily fooled by comment tokens inside + * strings or regexes, but, otherwise, generally clean code will not be + * mangled. + * + * @param string $content + * + * @param array $options available options: + * + * 'id': (optional) string to identify file. E.g. file name/path + * + * @return string + */ + public static function minify($content, $options = array()) + { + $id = (isset($options['id']) && $options['id']) + ? $options['id'] + : ''; + $content = str_replace("\r\n", "\n", $content); + $lines = explode("\n", $content); + $numLines = count($lines); + // determine left padding + $padTo = strlen($numLines); + $inComment = false; + $i = 0; + $newLines = array(); + while (null !== ($line = array_shift($lines))) { + if (('' !== $id) && (0 == $i % 50)) { + array_push($newLines, '', "/* {$id} */", ''); + } + ++$i; + $newLines[] = self::_addNote($line, $i, $inComment, $padTo); + $inComment = self::_eolInComment($line, $inComment); + } + return implode("\n", $newLines) . "\n"; + } + + /** + * Is the parser within a C-style comment at the end of this line? + * + * @param string $line current line of code + * + * @param bool $inComment was the parser in a comment at the + * beginning of the line? + * + * @return bool + */ + private static function _eolInComment($line, $inComment) + { + while (strlen($line)) { + $search = $inComment + ? '*/' + : '/*'; + $pos = strpos($line, $search); + if (false === $pos) { + return $inComment; + } else { + if ($pos == 0 + || ($inComment + ? substr($line, $pos, 3) + : substr($line, $pos-1, 3)) != '*/*') + { + $inComment = ! $inComment; + } + $line = substr($line, $pos + 2); + } + } + return $inComment; + } + + /** + * Prepend a comment (or note) to the given line + * + * @param string $line current line of code + * + * @param string $note content of note/comment + * + * @param bool $inComment was the parser in a comment at the + * beginning of the line? + * + * @param int $padTo minimum width of comment + * + * @return string + */ + private static function _addNote($line, $note, $inComment, $padTo) + { + return $inComment + ? '/* ' . str_pad($note, $padTo, ' ', STR_PAD_RIGHT) . ' *| ' . $line + : '/* ' . str_pad($note, $padTo, ' ', STR_PAD_RIGHT) . ' */ ' . $line; + } +} diff --git a/vendors/minify/lib/Minify/Packer.php b/vendors/minify/lib/Minify/Packer.php new file mode 100644 index 0000000..380689b --- /dev/null +++ b/vendors/minify/lib/Minify/Packer.php @@ -0,0 +1,37 @@ +<?php +/** + * Class Minify_Packer + * + * To use this class you must first download the PHP port of Packer + * and place the file "class.JavaScriptPacker.php" in /lib (or your + * include_path). + * @link http://joliclic.free.fr/php/javascript-packer/en/ + * + * Be aware that, as long as HTTP encoding is used, scripts minified + * with Minify_Javascript (JSMin) will provide better client-side + * performance, as they need not be unpacked in client-side code. + * + * @package Minify + */ + +if (false === (@include 'class.JavaScriptPacker.php')) { + trigger_error( + 'The script "class.JavaScriptPacker.php" is required. Please see: http:' + .'//code.google.com/p/minify/source/browse/trunk/min/lib/Minify/Packer.php' + ,E_USER_ERROR + ); +} + +/** + * Minify Javascript using Dean Edward's Packer + * + * @package Minify + */ +class Minify_Packer { + public static function minify($code, $options = array()) + { + // @todo: set encoding options based on $options :) + $packer = new JavascriptPacker($code, 'Normal', true, false); + return trim($packer->pack()); + } +} diff --git a/vendors/minify/lib/Minify/Source.php b/vendors/minify/lib/Minify/Source.php new file mode 100644 index 0000000..cbffae5 --- /dev/null +++ b/vendors/minify/lib/Minify/Source.php @@ -0,0 +1,177 @@ +<?php +/** + * Class Minify_Source + * @package Minify + */ + +/** + * A content source to be minified by Minify. + * + * This allows per-source minification options and the mixing of files with + * content from other sources. + * + * @package Minify + * @author Stephen Clay <steve@mrclay.org> + */ +class Minify_Source { + + /** + * @var int time of last modification + */ + public $lastModified = null; + + /** + * @var callback minifier function specifically for this source. + */ + public $minifier = null; + + /** + * @var array minification options specific to this source. + */ + public $minifyOptions = null; + + /** + * @var string full path of file + */ + public $filepath = null; + + /** + * Create a Minify_Source + * + * In the $spec array(), you can either provide a 'filepath' to an existing + * file (existence will not be checked!) or give 'id' (unique string for + * the content), 'content' (the string content) and 'lastModified' + * (unixtime of last update). + * + * As a shortcut, the controller will replace "//" at the beginning + * of a filepath with $_SERVER['DOCUMENT_ROOT'] . '/'. + * + * @param array $spec options + */ + public function __construct($spec) + { + if (isset($spec['filepath'])) { + if (0 === strpos($spec['filepath'], '//')) { + $spec['filepath'] = $_SERVER['DOCUMENT_ROOT'] . substr($spec['filepath'], 1); + } + $this->filepath = $spec['filepath']; + $this->_id = $spec['filepath']; + $this->lastModified = filemtime($spec['filepath']) + // offset for Windows uploaders with out of sync clocks + + round(Minify::$uploaderHoursBehind * 3600); + } elseif (isset($spec['id'])) { + $this->_id = 'id::' . $spec['id']; + if (isset($spec['content'])) { + $this->_content = $spec['content']; + } else { + $this->_getContentFunc = $spec['getContentFunc']; + } + $this->lastModified = isset($spec['lastModified']) + ? $spec['lastModified'] + : time(); + } + if (isset($spec['minifier'])) { + $this->minifier = $spec['minifier']; + } + if (isset($spec['minifyOptions'])) { + $this->minifyOptions = $spec['minifyOptions']; + } + } + + /** + * Get content + * + * @return string + */ + public function getContent() + { + $content = (null !== $this->filepath) + ? file_get_contents($this->filepath) + : ((null !== $this->_content) + ? $this->_content + : call_user_func($this->_getContentFunc, $this->_id) + ); + // remove UTF-8 BOM if present + return (pack("CCC",0xef,0xbb,0xbf) === substr($content, 0, 3)) + ? substr($content, 3) + : $content; + } + + /** + * Get id + * + * @return string + */ + public function getId() + { + return $this->_id; + } + + /** + * Verifies a single minification call can handle all sources + * + * @param array $sources Minify_Source instances + * + * @return bool true iff there no sources with specific minifier preferences. + */ + public static function haveNoMinifyPrefs($sources) + { + foreach ($sources as $source) { + if (null !== $source->minifier + || null !== $source->minifyOptions) { + return false; + } + } + return true; + } + + /** + * Get unique string for a set of sources + * + * @param array $sources Minify_Source instances + * + * @return string + */ + public static function getDigest($sources) + { + foreach ($sources as $source) { + $info[] = array( + $source->_id, $source->minifier, $source->minifyOptions + ); + } + return md5(serialize($info)); + } + + /** + * Guess content type from the first filename extension available + * + * This is called if the user doesn't pass in a 'contentType' options + * + * @param array $sources Minify_Source instances + * + * @return string content type. e.g. 'text/css' + */ + public static function getContentType($sources) + { + $exts = array( + 'css' => Minify::TYPE_CSS + ,'js' => Minify::TYPE_JS + ,'html' => Minify::TYPE_HTML + ); + foreach ($sources as $source) { + if (null !== $source->filepath) { + $segments = explode('.', $source->filepath); + $ext = array_pop($segments); + if (isset($exts[$ext])) { + return $exts[$ext]; + } + } + } + return 'text/plain'; + } + + protected $_content = null; + protected $_getContentFunc = null; + protected $_id = null; +} + diff --git a/vendors/minify/lib/Minify/YUICompressor.php b/vendors/minify/lib/Minify/YUICompressor.php new file mode 100644 index 0000000..7cb61ad --- /dev/null +++ b/vendors/minify/lib/Minify/YUICompressor.php @@ -0,0 +1,139 @@ +<?php +/** + * Class Minify_YUICompressor + * @package Minify + */ + +/** + * Compress Javascript/CSS using the YUI Compressor + * + * You must set $jarFile and $tempDir before calling the minify functions. + * Also, depending on your shell's environment, you may need to specify + * the full path to java in $javaExecutable or use putenv() to setup the + * Java environment. + * + * <code> + * Minify_YUICompressor::$jarFile = '/path/to/yuicompressor-2.3.5.jar'; + * Minify_YUICompressor::$tempDir = '/tmp'; + * $code = Minify_YUICompressor::minifyJs( + * $code + * ,array('nomunge' => true, 'line-break' => 1000) + * ); + * </code> + * + * @todo unit tests, $options docs + * + * @package Minify + * @author Stephen Clay <steve@mrclay.org> + */ +class Minify_YUICompressor { + + /** + * Filepath of the YUI Compressor jar file. This must be set before + * calling minifyJs() or minifyCss(). + * + * @var string + */ + public static $jarFile = null; + + /** + * Writable temp directory. This must be set before calling minifyJs() + * or minifyCss(). + * + * @var string + */ + public static $tempDir = null; + + /** + * Filepath of "java" executable (may be needed if not in shell's PATH) + * + * @var string + */ + public static $javaExecutable = 'java'; + + /** + * Minify a Javascript string + * + * @param string $js + * + * @param array $options (verbose is ignored) + * + * @see http://www.julienlecomte.net/yuicompressor/README + * + * @return string + */ + public static function minifyJs($js, $options = array()) + { + return self::_minify('js', $js, $options); + } + + /** + * Minify a CSS string + * + * @param string $css + * + * @param array $options (verbose is ignored) + * + * @see http://www.julienlecomte.net/yuicompressor/README + * + * @return string + */ + public static function minifyCss($css, $options = array()) + { + return self::_minify('css', $css, $options); + } + + private static function _minify($type, $content, $options) + { + self::_prepare(); + if (! ($tmpFile = tempnam(self::$tempDir, 'yuic_'))) { + throw new Exception('Minify_YUICompressor : could not create temp file.'); + } + file_put_contents($tmpFile, $content); + exec(self::_getCmd($options, $type, $tmpFile), $output); + unlink($tmpFile); + return implode("\n", $output); + } + + private static function _getCmd($userOptions, $type, $tmpFile) + { + $o = array_merge( + array( + 'charset' => '' + ,'line-break' => 5000 + ,'type' => $type + ,'nomunge' => false + ,'preserve-semi' => false + ,'disable-optimizations' => false + ) + ,$userOptions + ); + $cmd = self::$javaExecutable . ' -jar ' . escapeshellarg(self::$jarFile) + . " --type {$type}" + . (preg_match('/^[a-zA-Z\\-]+$/', $o['charset']) + ? " --charset {$o['charset']}" + : '') + . (is_numeric($o['line-break']) && $o['line-break'] >= 0 + ? ' --line-break ' . (int)$o['line-break'] + : ''); + if ($type === 'js') { + foreach (array('nomunge', 'preserve-semi', 'disable-optimizations') as $opt) { + $cmd .= $o[$opt] + ? " --{$opt}" + : ''; + } + } + return $cmd . ' ' . escapeshellarg($tmpFile); + } + + private static function _prepare() + { + if (! is_file(self::$jarFile) + || ! is_dir(self::$tempDir) + || ! is_writable(self::$tempDir) + ) { + throw new Exception('Minify_YUICompressor : $jarFile and $tempDir must be set.'); + } + } +} + diff --git a/vendors/minify/lib/MyMin.php b/vendors/minify/lib/MyMin.php new file mode 100644 index 0000000..15e688a --- /dev/null +++ b/vendors/minify/lib/MyMin.php @@ -0,0 +1,289 @@ +<?php +/** + * MyMin - JSMin like alternative parser for JavaScript + * + * This class is a jsmin alternative, based on same parser logic but modified + * to mantain performances and to parse correctly JavaScript conditional comments too. + * + * SERVER SIDE + * PHP 5 or greater is required. + * This code is compatible with every error_reporting level (E_ALL | E_STRICT) + * The best practice to use this code is caching results without run-time + * evaluation (your server should be stressed too much with big files) + * + * Permission is hereby granted to use this version of the library under the + * same terms as jsmin.php, which has the following license: + * + * -- + * Copyright (c) 2002 Douglas Crockford (www.crockford.com) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + * of the Software, and to permit persons to whom the Software is furnished to do + * so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * The Software shall be used for Good, not Evil. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * -- + * + * @class MyMin + * @author Andrea Giammarchi <http://www.3site.eu> + * @copyright 2002 Douglas Crockford <douglas@crockford.com> (jsmin.c) + * @copyright 2007 Ryan Grove <ryan@wonko.com> (PHP port) + * @copyright 2007 Andrea Giammarchi (improvements + MyMinCompressor + MyMinCSS) + * @license http://opensource.org/licenses/mit-license.php MIT License + * @version 1.0.1 (2007-10-05) - updated 2008-02-17 + */ + +// -- Class MyMin -------------------------------------------------------------- +class MyMin { + + const /* char */ LF = "\n", + SPACE = ' ', + EOS = "\x00"; + + protected /* boolean */ $cc_on; + + protected /* char */ $a, + $ahead, + $b; + + protected /* int */ $index = 0, + $length; + + protected /* string */ $input, + $output = ""; + + // -- Public Static Methods ---------------------------------------------------- + static public final function /* string */ parse(/* string */ $input, /* boolean */ $cc_on = true){ + return "".(new MyMin($input, $cc_on)); + } + + // -- Public Instance Methods -------------------------------------------------- + public final function /* object */ __construct(/* string */ $input, /* boolean */ $cc_on = true){ + $this->input = preg_replace("/(\r\n|\n\r|\r|\n)+/", self::LF, trim($input)); + $this->length = strlen($this->input); + $this->cc_on = $cc_on; + $this->b = $this->ahead = self::SPACE; + $this->a = self::LF; + $this->action(3); + while($this->a !== self::EOS){ + switch($this->a){ + case self::SPACE: + $this->action($this->isAlNum($this->b) ? 1 : 2); + break; + case self::LF: + switch($this->b){ + case '{': + case '[': + case '(': + case '+': + case '-': + $this->action(1); + break; + case self::SPACE: + $this->action(3); + break; + default: + $this->action($this->isAlNum($this->b) ? 1 : 2); + break; + } + break; + default: + switch($this->b){ + case self::SPACE: + $this->action($this->isAlNum($this->a) ? 1 : 3); + break; + case self::LF: + switch($this->a){ + case '}': + case ']': + case ')': + case '+': + case '-': + case '"': + case '\'': + $this->action(1); + break; + default: + $this->action($this->isAlNum($this->a) ? 1 : 3); + break; + } + break; + default: + $this->action(1); + break; + } + break; + } + } + } + + public final function /* string */ __toString(/* void */){ + return str_replace("\n\n", "\n", ltrim($this->output)); + } + + // -- Protected Instance Methods ----------------------------------------------- + protected function /* void */ action(/* int */ $i){ + switch($i){ + case 1: + $this->output .= $this->a; + case 2: + $this->a = $this->b; + if($this->a === '\'' || $this->a === '"'){ + while(true){ + $this->output .= $this->a; + if(!$this->nextCharNoSlash($this->b, "Unterminated string literal.")) + break; + } + } + case 3: + $this->b = $this->next(); + if($this->b === '/'){ + switch($this->a){ + case self::LF: + case self::SPACE: + if(!$this->spaceBeforeRegExp($this->output)) + break; + + case '{': + case ';': + + case '(': + case ',': + case '=': + case ':': + case '[': + case '!': + case '&': + case '|': + case '?': + $this->output .= $this->a.$this->b; + while($this->nextCharNoSlash('/', "Unterminated regular expression literal.")) + $this->output .= $this->a; + $this->b = $this->next(); + break; + } + } + break; + } + } + + protected function /* void */ appendComment(/* int */ $pos, /* string */ $open, /* string */ $close) { + $this->output .= $this->a.$open.(new MyMin(substr($this->input, $this->index, $pos - $this->index), $this->cc_on)).$close; + $this->index = $pos; + $this->a = self::LF; + } + + protected function /* void */ conditionalComment(/* char */ $find) { + $single = $find === self::LF; + $pos = strpos($this->input, $find, $this->index); + if($pos === false){ + if($single) + $pos = $this->length; + else + throw new MyMinException("Unterminated comment."); + } + $this->appendComment($pos, $single ? "//" : "/*", $find); + } + + protected function /* char */ get(/* void */) { + $c = $this->ahead; + $this->ahead = self::EOS; + if($c === self::EOS && $this->index < $this->length) + $c = $this->input{$this->index++}; + return ($c === self::EOS || $c === self::LF || $c >= self::SPACE) ? $c : self::SPACE; + } + + protected function /* boolean */ isAlNum(/* char */ $c) { + return $c > 126 || $c === '\\' || preg_match('/^(\w|\$)$/', $c); + } + + protected function /* char */ next(/* void */) { + $c = $this->get(); + $loop = true; + if($c === '/'){ + switch($this->ahead = $this->get()){ + case '/': + if($this->cc_on && $this->input{$this->index} === '@') + $this->conditionalComment(self::LF); + while($loop){ + $c = $this->get(); + if($c <= self::LF) + $loop = false; + } + break; + case '*': + $this->get(); + if($this->cc_on && $this->input{$this->index} === '@') + $this->conditionalComment("*/"); + while($loop){ + switch($this->get()){ + case '*': + if(($this->ahead = $this->get()) === '/'){ + $this->get(); + $c = self::SPACE; + $loop = false; + } + break; + case self::EOS: + throw new MyMinException("Unterminated comment."); + } + } + break; + } + } + return $c; + } + + protected function /* boolean */ nextCharNoSlash(/* char */ $c, /* string */ $message) { + $loop = true; + $this->a = $this->get(); + if($this->a === $c) + $loop = false; + else{ + if($this->a === '\\'){ + $this->output .= $this->a; + $this->a = $this->get(); + } + if($this->a <= self::LF) + throw new MyMinException($message); + } + return $loop; + } + + protected function /* boolean */ spaceBeforeRegExp(/* string */ $output){ + for( + $i = 0, + $length = strlen($output), + $reserved = array("case", "else", "in", "return", "typeof"), + $result = false, + $tmp = ""; + $i < 5 && !$result; + $i++ + ){ + if($length === strlen($reserved[$i])) + $result = $reserved[$i] === $output; + else if($length > strlen($reserved[$i])){ + $tmp = substr($output, $length - strlen($reserved[$i]) - 1); + $result = substr($tmp, 1) === $reserved[$i] && !$this->isAlNum($tmp{0}); + } + }; + return $length < 2 ? true : $result; + } +} + +// -- MyMin Exceptions --------------------------------------------------------- +class MyMinException extends Exception {} diff --git a/vendors/minify/lib/Solar/Dir.php b/vendors/minify/lib/Solar/Dir.php new file mode 100644 index 0000000..37f7169 --- /dev/null +++ b/vendors/minify/lib/Solar/Dir.php @@ -0,0 +1,199 @@ +<?php +/** + * + * Utility class for static directory methods. + * + * @category Solar + * + * @package Solar + * + * @author Paul M. Jones <pmjones@solarphp.com> + * + * @license http://opensource.org/licenses/bsd-license.php BSD + * + * @version $Id: Dir.php 2926 2007-11-09 16:25:44Z pmjones $ + * + */ +class Solar_Dir { + + /** + * + * The OS-specific temporary directory location. + * + * @var string + * + */ + protected static $_tmp; + + /** + * + * Hack for [[php::is_dir() | ]] that checks the include_path. + * + * Use this to see if a directory exists anywhere in the include_path. + * + * {{code: php + * $dir = Solar_Dir::exists('path/to/dir') + * if ($dir) { + * $files = scandir($dir); + * } else { + * echo "Not found in the include-path."; + * } + * }} + * + * @param string $dir Check for this directory in the include_path. + * + * @return mixed If the directory exists in the include_path, returns the + * absolute path; if not, returns boolean false. + * + */ + public static function exists($dir) + { + // no file requested? + $dir = trim($dir); + if (! $dir) { + return false; + } + + // using an absolute path for the file? + // dual check for Unix '/' and Windows '\', + // or Windows drive letter and a ':'. + $abs = ($dir[0] == '/' || $dir[0] == '\\' || $dir[1] == ':'); + if ($abs && is_dir($dir)) { + return $dir; + } + + // using a relative path on the file + $path = explode(PATH_SEPARATOR, ini_get('include_path')); + foreach ($path as $base) { + // strip Unix '/' and Windows '\' + $target = rtrim($base, '\\/') . DIRECTORY_SEPARATOR . $dir; + if (is_dir($target)) { + return $target; + } + } + + // never found it + return false; + } + + /** + * + * "Fixes" a directory string for the operating system. + * + * Use slashes anywhere you need a directory separator. Then run the + * string through fixdir() and the slashes will be converted to the + * proper separator (for example '\' on Windows). + * + * Always adds a final trailing separator. + * + * @param string $dir The directory string to 'fix'. + * + * @return string The "fixed" directory string. + * + */ + public static function fix($dir) + { + $dir = str_replace('/', DIRECTORY_SEPARATOR, $dir); + return rtrim($dir, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR; + } + + /** + * + * Convenience method for dirname() and higher-level directories. + * + * @param string $file Get the dirname() of this file. + * + * @param int $up Move up in the directory structure this many + * times, default 0. + * + * @return string The dirname() of the file. + * + */ + public static function name($file, $up = 0) + { + $dir = dirname($file); + while ($up --) { + $dir = dirname($dir); + } + return $dir; + } + + /** + * + * Returns the OS-specific directory for temporary files. + * + * @param string $sub Add this subdirectory to the returned temporary + * directory name. + * + * @return string The temporary directory path. + * + */ + public static function tmp($sub = '') + { + // find the tmp dir if needed + if (! Solar_Dir::$_tmp) { + + // use the system if we can + if (function_exists('sys_get_temp_dir')) { + $tmp = sys_get_temp_dir(); + } else { + $tmp = Solar_Dir::_tmp(); + } + + // remove trailing separator and save + Solar_Dir::$_tmp = rtrim($tmp, DIRECTORY_SEPARATOR); + } + + // do we have a subdirectory request? + $sub = trim($sub); + if ($sub) { + // remove leading and trailing separators, and force exactly + // one trailing separator + $sub = trim($sub, DIRECTORY_SEPARATOR) + . DIRECTORY_SEPARATOR; + } + + return Solar_Dir::$_tmp . DIRECTORY_SEPARATOR . $sub; + } + + /** + * + * Returns the OS-specific temporary directory location. + * + * @return string The temp directory path. + * + */ + protected static function _tmp() + { + // non-Windows system? + if (strtolower(substr(PHP_OS, 0, 3)) != 'win') { + $tmp = empty($_ENV['TMPDIR']) ? getenv('TMPDIR') : $_ENV['TMPDIR']; + if ($tmp) { + return $tmp; + } else { + return '/tmp'; + } + } + + // Windows 'TEMP' + $tmp = empty($_ENV['TEMP']) ? getenv('TEMP') : $_ENV['TEMP']; + if ($tmp) { + return $tmp; + } + + // Windows 'TMP' + $tmp = empty($_ENV['TMP']) ? getenv('TMP') : $_ENV['TMP']; + if ($tmp) { + return $tmp; + } + + // Windows 'windir' + $tmp = empty($_ENV['windir']) ? getenv('windir') : $_ENV['windir']; + if ($tmp) { + return $tmp; + } + + // final fallback for Windows + return getenv('SystemRoot') . '\\temp'; + } +} \ No newline at end of file diff --git a/vendors/minify/utils.php b/vendors/minify/utils.php new file mode 100644 index 0000000..c735941 --- /dev/null +++ b/vendors/minify/utils.php @@ -0,0 +1,90 @@ +<?php +/** + * Utility functions for generating group URIs in HTML files + * + * Before including this file, /min/lib must be in your include_path. + * + * @package Minify + */ + +require_once 'Minify/Build.php'; + + +/** + * Get a timestamped URI to a minified resource using the default Minify install + * + * <code> + * <link rel="stylesheet" type="text/css" href="<?php echo Minify_groupUri('css'); ?>" /> + * <script type="text/javascript" src="<?php echo Minify_groupUri('js'); ?>"></script> + * </code> + * + * If you do not want ampersands as HTML entities, set Minify_Build::$ampersand = "&" + * before using this function. + * + * @param string $group a key from groupsConfig.php + * @param boolean $forceAmpersand (default false) Set to true if the RewriteRule + * directives in .htaccess are functional. This will remove the "?" from URIs, making them + * more cacheable by proxies. + * @return string + */ +function Minify_groupUri($group, $forceAmpersand = false) +{ + $path = $forceAmpersand + ? "/g={$group}" + : "/?g={$group}"; + return _Minify_getBuild($group)->uri( + '/' . basename(dirname(__FILE__)) . $path + ,$forceAmpersand + ); +} + + +/** + * Get the last modification time of the source js/css files used by Minify to + * build the page. + * + * If you're caching the output of Minify_groupUri(), you'll want to rebuild + * the cache if it's older than this timestamp. + * + * <code> + * // simplistic HTML cache system + * $file = '/path/to/cache/file'; + * if (! file_exists($file) || filemtime($file) < Minify_groupsMtime(array('js', 'css'))) { + * // (re)build cache + * $page = buildPage(); // this calls Minify_groupUri() for js and css + * file_put_contents($file, $page); + * echo $page; + * exit(); + * } + * readfile($file); + * </code> + * + * @param array $groups an array of keys from groupsConfig.php + * @return int Unix timestamp of the latest modification + */ +function Minify_groupsMtime($groups) +{ + $max = 0; + foreach ((array)$groups as $group) { + $max = max($max, _Minify_getBuild($group)->lastModified); + } + return $max; +} + +/** + * @param string $group a key from groupsConfig.php + * @return Minify_Build + * @private + */ +function _Minify_getBuild($group) +{ + static $builds = array(); + static $gc = false; + if (false === $gc) { + $gc = (require dirname(__FILE__) . '/groupsConfig.php'); + } + if (! isset($builds[$group])) { + $builds[$group] = new Minify_Build($gc[$group]); + } + return $builds[$group]; +} diff --git a/views/helpers/mi_html.php b/views/helpers/mi_html.php new file mode 100644 index 0000000..b68ba78 --- /dev/null +++ b/views/helpers/mi_html.php @@ -0,0 +1,179 @@ +<?php +/* SVN FILE: $Id: mi_html.php 736 2009-01-16 16:54:44Z ad7six $ */ +/** + * Short description for mi_html.php + * + * Long description for mi_html.php + * + * PHP versions 4 and 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.views.helpers + * @since v 1.0 + * @version $Revision: 736 $ + * @modifiedby $LastChangedBy: ad7six $ + * @lastmodified $Date: 2009-01-16 17:54:44 +0100 (Fri, 16 Jan 2009) $ + * @license http://www.opensource.org/licenses/mit-license.php The MIT License + */ +App::import('Component', 'RequestHandler'); +App::import('Helper', 'Html'); +App::import('Vendor', 'MiCompressor'); +/** + * MiHtmlHelper class + * + * @uses HtmlHelper + * @package base + * @subpackage base.views.helpers + */ +class MiHtmlHelper extends HtmlHelper { +/** + * name property + * + * @var string 'MiHtml' + * @access public + */ + var $name = 'MiHtml'; +/** + * css property + * + * @var array + * @access private + */ + var $__css = array(); +/** + * css method + * + * Example usage, from anywhere at all: + * $html->css('this', null, null, null, false); + * .... + * $html->css('that', null, null, null, false); + * ... + * $html->css('other', null, null, null, false); + * + * In the layout, call with no parameters to output: + * echo $html->css(); + * + * With the given example it would generate a link to /app/css/mini.css?this|that|other to be picked up by the + * mi_compressor vendor class + * + * If $sendAlone is true (defaults to true in development mode) each file is output individually + * If $sizeLimit is set (defaults to null for browsers and 25K for mobile devices in production mode) + * file concatonation is bypassed if the request cache file exists and is greater than the size limit + * + * @param mixed $path + * @param mixed $rel + * @param array $htmlAttributes + * @param bool $inline + * @param mixed $sendAlone + * @param mixed $sizeLimit Maximum filesize in bytes + * @return void + * @access public + */ + function css($path = null, $rel = null, $htmlAttributes = array(), $inline = true, $sendAlone = null, $sizeLimit = null) { + if ($inline && $path) { + return parent::css($path, $rel, $htmlAttributes, $inline); + } + if ($path === null) { + if (!$this->__css) { + return; + } + if ($sendAlone === null) { + $sendAlone = Configure::read(); + } + if (!$sendAlone && $sizeLimit === null) { + if (!isset($this->__RequestHandler)) { + $this->__RequestHandler = new RequestHandlerComponent(); + } + if ($this->__RequestHandler->isMobile()) { + $sizeLimit = 25 * 1024; + } + } + $return = ''; + foreach ($this->__css as $result) { + extract($result); + $url = MiCompressor::url($files, array( + 'type' => 'css', 'sendAlone' => $sendAlone, 'sizeLimit' => $sizeLimit)); + foreach((array)$url as $u) { + $return .= parent::css($u, $rel, $htmlAttributes, true); + } + continue; + } + $this->__css = array(); + return $return; + } + if (is_array($path)) { + foreach ($path as $url) { + $this->css($url, $rel, $htmlAttributes, false); + } + return; + } + if (!$rel) { + $rel = 'stylesheet'; + } + if (!$htmlAttributes) { + $htmlAttributes = array ('title' => 'Standard', 'media' => 'screen'); + } + $key = Inflector::slug($rel . serialize($htmlAttributes)); + $this->__css[$key]['rel'] = $rel; + $this->__css[$key]['htmlAttributes'] = $htmlAttributes; + if (empty($this->__css[$key]['files']) || !in_array($path, $this->__css[$key]['files'])) { + $this->__css[$key]['files'][] = $path; + } + } +/** + * link method + * + * For any link to an action that only works by post - add the class confirm. + * + * @param mixed $title + * @param mixed $url + * @param array $htmlAttributes + * @param bool $confirmMessage + * @param bool $escapeTitle + * @return void + * @access public + */ + function link($title, $url = null, $htmlAttributes = array(), $confirmMessage = false, $escapeTitle = true) { + if (!isset($this->__view)) { + $this->__view =& ClassRegistry::getObject('view'); + } + if (isset($this->__view->viewVars['postActions']) && is_array($url)) { + $controller = $this->__view->name; + if (isset($url['controller'])) { + $controller = $url['controller']; + } + $controller = Inflector::underscore($controller); + if (isset($this->__view->viewVars['postActions'][$controller])) { + $postActions = $this->__view->viewVars['postActions'][$controller]; + if (isset($url['admin']) || isset($this->__view->params['admin'])) { + $prefix = 'admin_'; + } else { + $prefix = ''; + } + if (isset($url['action'])) { + $action = $url['action']; + } else { + $action = $this->__view->action; + } + $action = $prefix . $action; + if (in_array($action, $postActions)) { + if (isset($htmlAttributes['class'])) { + $htmlAttributes['class'] .= ' confirm'; + } else { + $htmlAttributes['class'] = 'confirm'; + } + } + } + } + return parent::link($title, $url, $htmlAttributes, $confirmMessage, $escapeTitle); + } +} +?> \ No newline at end of file diff --git a/views/helpers/mi_javascript.php b/views/helpers/mi_javascript.php new file mode 100644 index 0000000..5e9bb55 --- /dev/null +++ b/views/helpers/mi_javascript.php @@ -0,0 +1,133 @@ +<?php +/* SVN FILE: $Id: mi_javascript.php 736 2009-01-16 16:54:44Z ad7six $ */ +/** + * Short description for mi_javascript.php + * + * Long description for mi_javascript.php + * + * PHP versions 4 and 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.views.helpers + * @since v 1.0 + * @version $Revision: 736 $ + * @modifiedby $LastChangedBy: ad7six $ + * @lastmodified $Date: 2009-01-16 17:54:44 +0100 (Fri, 16 Jan 2009) $ + * @license http://www.opensource.org/licenses/mit-license.php The MIT License + */ +App::import('Component', 'RequestHandler'); +App::import('Helper', 'Javascript'); +App::import('Vendor', 'MiCompressor'); +/** + * MiJavascriptHelper class + * + * @uses JavascriptHelper + * @package base + * @subpackage base.views.helpers + */ +class MiJavascriptHelper extends JavascriptHelper { +/** + * name property + * + * @var string 'MiHtml' + * @access public + */ + var $name = 'MiJavascript'; +/** + * js property + * + * @var array + * @access private + */ + var $__js = array(); +/** + * link method + * + * Example usage, from anywhere at all: + * $javascript->link('this', false); + * .... + * $javascript->link('that', false); + * ... + * $javascript->link(array('jquery' => 'plugin1'), false); + * ... + * $javascript->link(array('jquery' => 'plugin2'), false); + * + * In the layout (preferably right at the end), call with no parameters to output: + * echo $javascript->link(); + * + * With the given example it would generate a link to /app/js/mini.js?jquery,plugin1,plugin2|this|that to be picked up + * by the mi_compressor vendor class. Note that jquery and plugins will always be first if included + * + * If $sendAlone is true (defaults to true in development mode) each file is output individually + * If $sizeLimit is set (defaults to null for browsers and 25K for mobile devices in production mode) + * file concatonation is bypassed if the request cache file exists and is greater than the size limit + * + * @param mixed $url + * @param bool $inline + * @param mixed $sendAlone + * @param mixed $sizeLimit Maximum filesize in bytes + * @return void + * @access public + */ + function link($url = null, $inline = true, $sendAlone = null, $sizeLimit = null) { + if ($url && $inline) { + return parent::link($url, $inline); + } + if ($url === null) { + if (!$this->__js) { + return; + } + if ($sendAlone === null) { + $sendAlone = Configure::read(); + } + if (!$sendAlone && $sizeLimit === null) { + if (!isset($this->__RequestHandler)) { + $this->__RequestHandler = new RequestHandlerComponent(); + } + if ($this->__RequestHandler->isMobile()) { + $sizeLimit = 25 * 1024; + } + } + if (isset($this->__js['jquery'])) { + $this->__js = am(array('jquery' => $this->__js['jquery']), $this->__js); + } + $url = MiCompressor::url($this->__js, array( + 'type' => 'js', 'sendAlone' => $sendAlone, 'sizeLimit' => $sizeLimit)); + $this->__js = array(); + $return = ''; + foreach((array)$url as $u) { + $return .= parent::link($u); + } + return $return; + } + if (is_array($url)) { + foreach ($url as $key => $value) { + if (is_numeric($key)) { + if (!in_array($value, $this->__js)) { + $this->__js[] = $value; + } + continue; + } + if (!isset($this->__js[$key])) { + $this->__js[$key] = (array)$value; + } else { + $this->__js[$key] = array_unique(am($this->__js[$key], (array)$value)); + } + } + } elseif ($url) { + if (!in_array($url, $this->__js)) { + $this->__js[] = $url; + } + return; + } + } +} +?> \ No newline at end of file