summaryrefslogtreecommitdiff
path: root/inc/contrib/minify/Minify
diff options
context:
space:
mode:
authorMichael Save <savetheinternet@tinyboard.org>2012-03-14 21:44:41 +1100
committerMichael Save <savetheinternet@tinyboard.org>2012-03-14 21:48:31 +1100
commit2a760de2896a02af98caa013537166b5d3fcc717 (patch)
tree1fccf2193f881239a0424fdd5fdb67f9e320a033 /inc/contrib/minify/Minify
parent60aab92f47e651b214f0b993aab03d08e033134e (diff)
Multiple changes.
Added $config['minify_js'] to compress Javascript with minify Added $config['additional_javascript_compile'] to put all Javascript files/addons into one file. Moved delete/report post controls to a shared template. Ability to have different Javascript files between boards.
Diffstat (limited to 'inc/contrib/minify/Minify')
-rwxr-xr-xinc/contrib/minify/Minify/Build.php103
-rwxr-xr-xinc/contrib/minify/Minify/CSS.php99
-rwxr-xr-xinc/contrib/minify/Minify/CSS/Compressor.php249
-rwxr-xr-xinc/contrib/minify/Minify/CSS/UriRewriter.php310
-rwxr-xr-xinc/contrib/minify/Minify/Cache/APC.php133
-rwxr-xr-xinc/contrib/minify/Minify/Cache/File.php195
-rwxr-xr-xinc/contrib/minify/Minify/Cache/Memcache.php140
-rwxr-xr-xinc/contrib/minify/Minify/Cache/ZendPlatform.php142
-rwxr-xr-xinc/contrib/minify/Minify/CommentPreserver.php89
-rwxr-xr-xinc/contrib/minify/Minify/Controller/Base.php250
-rwxr-xr-xinc/contrib/minify/Minify/Controller/Files.php78
-rwxr-xr-xinc/contrib/minify/Minify/Controller/Groups.php93
-rwxr-xr-xinc/contrib/minify/Minify/Controller/MinApp.php231
-rwxr-xr-xinc/contrib/minify/Minify/Controller/Page.php87
-rwxr-xr-xinc/contrib/minify/Minify/Controller/Version1.php118
-rwxr-xr-xinc/contrib/minify/Minify/DebugDetector.php26
-rwxr-xr-xinc/contrib/minify/Minify/HTML.php246
-rwxr-xr-xinc/contrib/minify/Minify/HTML/Helper.php193
-rwxr-xr-xinc/contrib/minify/Minify/ImportProcessor.php216
-rwxr-xr-xinc/contrib/minify/Minify/JS/ClosureCompiler.php133
-rwxr-xr-xinc/contrib/minify/Minify/Lines.php137
-rwxr-xr-xinc/contrib/minify/Minify/Logger.php47
-rwxr-xr-xinc/contrib/minify/Minify/Packer.php37
-rwxr-xr-xinc/contrib/minify/Minify/Source.php187
-rwxr-xr-xinc/contrib/minify/Minify/YUI/CssCompressor.java382
-rwxr-xr-xinc/contrib/minify/Minify/YUI/CssCompressor.php171
-rwxr-xr-xinc/contrib/minify/Minify/YUICompressor.php148
27 files changed, 4240 insertions, 0 deletions
diff --git a/inc/contrib/minify/Minify/Build.php b/inc/contrib/minify/Minify/Build.php
new file mode 100755
index 00000000..e625165e
--- /dev/null
+++ b/inc/contrib/minify/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/inc/contrib/minify/Minify/CSS.php b/inc/contrib/minify/Minify/CSS.php
new file mode 100755
index 00000000..49882e9d
--- /dev/null
+++ b/inc/contrib/minify/Minify/CSS.php
@@ -0,0 +1,99 @@
+<?php
+/**
+ * Class Minify_CSS
+ * @package Minify
+ */
+
+/**
+ * Minify CSS
+ *
+ * This class uses Minify_CSS_Compressor and Minify_CSS_UriRewriter to
+ * minify CSS and rewrite relative URIs.
+ *
+ * @package Minify
+ * @author Stephen Clay <steve@mrclay.org>
+ * @author http://code.google.com/u/1stvamp/ (Issue 64 patch)
+ */
+class 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.
+ *
+ * 'removeCharsets': (default true) remove all @charset at-rules
+ *
+ * '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.
+ *
+ * 'symlinks': (default = array()) If the CSS file is stored in
+ * a symlink-ed directory, provide an array of link paths to
+ * target paths, where the link paths are within the document root. Because
+ * paths need to be normalized for this to work, use "//" to substitute
+ * the doc root in the link paths (the array keys). E.g.:
+ * <code>
+ * array('//symlink' => '/real/target/path') // unix
+ * array('//static' => 'D:\\staticStorage') // Windows
+ * </code>
+ *
+ * 'docRoot': (default = $_SERVER['DOCUMENT_ROOT'])
+ * see Minify_CSS_UriRewriter::rewrite
+ *
+ * @return string
+ */
+ public static function minify($css, $options = array())
+ {
+ $options = array_merge(array(
+ 'removeCharsets' => true,
+ 'preserveComments' => true,
+ 'currentDir' => null,
+ 'docRoot' => $_SERVER['DOCUMENT_ROOT'],
+ 'prependRelativePath' => null,
+ 'symlinks' => array(),
+ ), $options);
+
+ if ($options['removeCharsets']) {
+ $css = preg_replace('/@charset[^;]+;\\s*/', '', $css);
+ }
+ require_once 'Minify/CSS/Compressor.php';
+ if (! $options['preserveComments']) {
+ $css = Minify_CSS_Compressor::process($css, $options);
+ } else {
+ require_once 'Minify/CommentPreserver.php';
+ $css = Minify_CommentPreserver::process(
+ $css
+ ,array('Minify_CSS_Compressor', 'process')
+ ,array($options)
+ );
+ }
+ if (! $options['currentDir'] && ! $options['prependRelativePath']) {
+ return $css;
+ }
+ require_once 'Minify/CSS/UriRewriter.php';
+ if ($options['currentDir']) {
+ return Minify_CSS_UriRewriter::rewrite(
+ $css
+ ,$options['currentDir']
+ ,$options['docRoot']
+ ,$options['symlinks']
+ );
+ } else {
+ return Minify_CSS_UriRewriter::prepend(
+ $css
+ ,$options['prependRelativePath']
+ );
+ }
+ }
+}
diff --git a/inc/contrib/minify/Minify/CSS/Compressor.php b/inc/contrib/minify/Minify/CSS/Compressor.php
new file mode 100755
index 00000000..c6cdd8b7
--- /dev/null
+++ b/inc/contrib/minify/Minify/CSS/Compressor.php
@@ -0,0 +1,249 @@
+<?php
+/**
+ * Class Minify_CSS_Compressor
+ * @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_Compressor {
+
+ /**
+ * Minify a CSS string
+ *
+ * @param string $css
+ *
+ * @param array $options (currently ignored)
+ *
+ * @return string
+ */
+ public static function process($css, $options = array())
+ {
+ $obj = new Minify_CSS_Compressor($options);
+ return $obj->_process($css);
+ }
+
+ /**
+ * @var array
+ */
+ protected $_options = null;
+
+ /**
+ * Are we "in" a hack? I.e. are some browsers targetted until the next comment?
+ *
+ * @var bool
+ */
+ protected $_inHack = false;
+
+
+ /**
+ * Constructor
+ *
+ * @param array $options (currently ignored)
+ */
+ private function __construct($options) {
+ $this->_options = $options;
+ }
+
+ /**
+ * Minify a CSS string
+ *
+ * @param string $css
+ *
+ * @return string
+ */
+ protected function _process($css)
+ {
+ $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
+ $css = preg_replace_callback('@\\s*/\\*([\\s\\S]*?)\\*/\\s*@'
+ ,array($this, '_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($this, '_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($this, '_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);
+
+ // prevent triggering IE6 bug: http://www.crankygeek.com/ie6pebug/
+ $css = preg_replace('/:first-l(etter|ine)\\{/', ':first-l$1 {', $css);
+
+ return trim($css);
+ }
+
+ /**
+ * Replace what looks like a set of selectors
+ *
+ * @param array $m regex matches
+ *
+ * @return string
+ */
+ protected function _selectorsCB($m)
+ {
+ // remove ws around the combinators
+ return preg_replace('/\\s*([,>+~])\\s*/', '$1', $m[0]);
+ }
+
+ /**
+ * Process a comment and return a replacement
+ *
+ * @param array $m regex matches
+ *
+ * @return string
+ */
+ protected function _commentCB($m)
+ {
+ $hasSurroundingWs = (trim($m[0]) !== $m[1]);
+ $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 ($this->_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
+ $this->_inHack = false;
+ return "/*/{$n[1]}/**/";
+ }
+ }
+ if (substr($m, -1) === '\\') { // comment ends like \*/
+ // begin hack mode and preserve hack
+ $this->_inHack = true;
+ return '/*\\*/';
+ }
+ if ($m !== '' && $m[0] === '/') { // comment looks like /*/ foo */
+ // begin hack mode and preserve hack
+ $this->_inHack = true;
+ return '/*/*/';
+ }
+ if ($this->_inHack) {
+ // a regular comment ends hack mode but should be preserved
+ $this->_inHack = false;
+ return '/**/';
+ }
+ // Issue 107: if there's any surrounding whitespace, it may be important, so
+ // replace the comment with a single space
+ return $hasSurroundingWs // remove all other comments
+ ? ' '
+ : '';
+ }
+
+ /**
+ * Process a font-family listing and return a replacement
+ *
+ * @param array $m regex matches
+ *
+ * @return string
+ */
+ protected function _fontFamilyCB($m)
+ {
+ // Issue 210: must not eliminate WS between words in unquoted families
+ $pieces = preg_split('/(\'[^\']+\'|"[^"]+")/', $m[1], null, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
+ $out = 'font-family:';
+ while (null !== ($piece = array_shift($pieces))) {
+ if ($piece[0] !== '"' && $piece[0] !== "'") {
+ $piece = preg_replace('/\\s+/', ' ', $piece);
+ $piece = preg_replace('/\\s?,\\s?/', ',', $piece);
+ }
+ $out .= $piece;
+ }
+ return $out . $m[2];
+ }
+}
diff --git a/inc/contrib/minify/Minify/CSS/UriRewriter.php b/inc/contrib/minify/Minify/CSS/UriRewriter.php
new file mode 100755
index 00000000..8845f156
--- /dev/null
+++ b/inc/contrib/minify/Minify/CSS/UriRewriter.php
@@ -0,0 +1,310 @@
+<?php
+/**
+ * Class Minify_CSS_UriRewriter
+ * @package Minify
+ */
+
+/**
+ * Rewrite file-relative URIs as root-relative in CSS files
+ *
+ * @package Minify
+ * @author Stephen Clay <steve@mrclay.org>
+ */
+class Minify_CSS_UriRewriter {
+
+ /**
+ * rewrite() and rewriteRelative() append debugging information here
+ *
+ * @var string
+ */
+ public static $debugText = '';
+
+ /**
+ * In CSS content, rewrite file relative URIs as root relative
+ *
+ * @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']).
+ *
+ * @param array $symlinks (default = array()) If the CSS file is stored in
+ * a symlink-ed directory, provide an array of link paths to
+ * target paths, where the link paths are within the document root. Because
+ * paths need to be normalized for this to work, use "//" to substitute
+ * the doc root in the link paths (the array keys). E.g.:
+ * <code>
+ * array('//symlink' => '/real/target/path') // unix
+ * array('//static' => 'D:\\staticStorage') // Windows
+ * </code>
+ *
+ * @return string
+ */
+ public static function rewrite($css, $currentDir, $docRoot = null, $symlinks = array())
+ {
+ self::$_docRoot = self::_realpath(
+ $docRoot ? $docRoot : $_SERVER['DOCUMENT_ROOT']
+ );
+ self::$_currentDir = self::_realpath($currentDir);
+ self::$_symlinks = array();
+
+ // normalize symlinks
+ foreach ($symlinks as $link => $target) {
+ $link = ($link === '//')
+ ? self::$_docRoot
+ : str_replace('//', self::$_docRoot . '/', $link);
+ $link = strtr($link, '/', DIRECTORY_SEPARATOR);
+ self::$_symlinks[$link] = self::_realpath($target);
+ }
+
+ self::$debugText .= "docRoot : " . self::$_docRoot . "\n"
+ . "currentDir : " . self::$_currentDir . "\n";
+ if (self::$_symlinks) {
+ self::$debugText .= "symlinks : " . var_export(self::$_symlinks, 1) . "\n";
+ }
+ self::$debugText .= "\n";
+
+ $css = self::_trimUrls($css);
+
+ // rewrite
+ $css = preg_replace_callback('/@import\\s+([\'"])(.*?)[\'"]/'
+ ,array(self::$className, '_processUriCB'), $css);
+ $css = preg_replace_callback('/url\\(\\s*([^\\)\\s]+)\\s*\\)/'
+ ,array(self::$className, '_processUriCB'), $css);
+
+ return $css;
+ }
+
+ /**
+ * In CSS content, prepend a path to relative URIs
+ *
+ * @param string $css
+ *
+ * @param string $path The path to prepend.
+ *
+ * @return string
+ */
+ public static function prepend($css, $path)
+ {
+ self::$_prependPath = $path;
+
+ $css = self::_trimUrls($css);
+
+ // append
+ $css = preg_replace_callback('/@import\\s+([\'"])(.*?)[\'"]/'
+ ,array(self::$className, '_processUriCB'), $css);
+ $css = preg_replace_callback('/url\\(\\s*([^\\)\\s]+)\\s*\\)/'
+ ,array(self::$className, '_processUriCB'), $css);
+
+ self::$_prependPath = null;
+ return $css;
+ }
+
+ /**
+ * Get a root relative URI from a file relative URI
+ *
+ * <code>
+ * Minify_CSS_UriRewriter::rewriteRelative(
+ * '../img/hello.gif'
+ * , '/home/user/www/css' // path of CSS file
+ * , '/home/user/www' // doc root
+ * );
+ * // returns '/img/hello.gif'
+ *
+ * // example where static files are stored in a symlinked directory
+ * Minify_CSS_UriRewriter::rewriteRelative(
+ * 'hello.gif'
+ * , '/var/staticFiles/theme'
+ * , '/home/user/www'
+ * , array('/home/user/www/static' => '/var/staticFiles')
+ * );
+ * // returns '/static/theme/hello.gif'
+ * </code>
+ *
+ * @param string $uri file relative URI
+ *
+ * @param string $realCurrentDir realpath of the current file's directory.
+ *
+ * @param string $realDocRoot realpath of the site document root.
+ *
+ * @param array $symlinks (default = array()) If the file is stored in
+ * a symlink-ed directory, provide an array of link paths to
+ * real target paths, where the link paths "appear" to be within the document
+ * root. E.g.:
+ * <code>
+ * array('/home/foo/www/not/real/path' => '/real/target/path') // unix
+ * array('C:\\htdocs\\not\\real' => 'D:\\real\\target\\path') // Windows
+ * </code>
+ *
+ * @return string
+ */
+ public static function rewriteRelative($uri, $realCurrentDir, $realDocRoot, $symlinks = array())
+ {
+ // prepend path with current dir separator (OS-independent)
+ $path = strtr($realCurrentDir, '/', DIRECTORY_SEPARATOR)
+ . DIRECTORY_SEPARATOR . strtr($uri, '/', DIRECTORY_SEPARATOR);
+
+ self::$debugText .= "file-relative URI : {$uri}\n"
+ . "path prepended : {$path}\n";
+
+ // "unresolve" a symlink back to doc root
+ foreach ($symlinks as $link => $target) {
+ if (0 === strpos($path, $target)) {
+ // replace $target with $link
+ $path = $link . substr($path, strlen($target));
+
+ self::$debugText .= "symlink unresolved : {$path}\n";
+
+ break;
+ }
+ }
+ // strip doc root
+ $path = substr($path, strlen($realDocRoot));
+
+ self::$debugText .= "docroot stripped : {$path}\n";
+
+ // fix to root-relative URI
+ $uri = strtr($path, '/\\', '//');
+ $uri = self::removeDots($uri);
+
+ self::$debugText .= "traversals removed : {$uri}\n\n";
+
+ return $uri;
+ }
+
+ /**
+ * Remove instances of "./" and "../" where possible from a root-relative URI
+ *
+ * @param string $uri
+ *
+ * @return string
+ */
+ public static function removeDots($uri)
+ {
+ $uri = str_replace('/./', '/', $uri);
+ // inspired by patch from Oleg Cherniy
+ do {
+ $uri = preg_replace('@/[^/]+/\\.\\./@', '/', $uri, 1, $changed);
+ } while ($changed);
+ return $uri;
+ }
+
+ /**
+ * Defines which class to call as part of callbacks, change this
+ * if you extend Minify_CSS_UriRewriter
+ *
+ * @var string
+ */
+ protected static $className = 'Minify_CSS_UriRewriter';
+
+ /**
+ * Get realpath with any trailing slash removed. If realpath() fails,
+ * just remove the trailing slash.
+ *
+ * @param string $path
+ *
+ * @return mixed path with no trailing slash
+ */
+ protected static function _realpath($path)
+ {
+ $realPath = realpath($path);
+ if ($realPath !== false) {
+ $path = $realPath;
+ }
+ return rtrim($path, '/\\');
+ }
+
+ /**
+ * Directory of this stylesheet
+ *
+ * @var string
+ */
+ private static $_currentDir = '';
+
+ /**
+ * DOC_ROOT
+ *
+ * @var string
+ */
+ private static $_docRoot = '';
+
+ /**
+ * directory replacements to map symlink targets back to their
+ * source (within the document root) E.g. '/var/www/symlink' => '/var/realpath'
+ *
+ * @var array
+ */
+ private static $_symlinks = array();
+
+ /**
+ * Path to prepend
+ *
+ * @var string
+ */
+ private static $_prependPath = null;
+
+ /**
+ * @param string $css
+ *
+ * @return string
+ */
+ private static function _trimUrls($css)
+ {
+ return preg_replace('/
+ url\\( # url(
+ \\s*
+ ([^\\)]+?) # 1 = URI (assuming does not contain ")")
+ \\s*
+ \\) # )
+ /x', 'url($1)', $css);
+ }
+
+ /**
+ * @param array $m
+ *
+ * @return string
+ */
+ private static function _processUriCB($m)
+ {
+ // $m matched either '/@import\\s+([\'"])(.*?)[\'"]/' or '/url\\(\\s*([^\\)\\s]+)\\s*\\)/'
+ $isImport = ($m[0][0] === '@');
+ // determine URI and the quote character (if any)
+ if ($isImport) {
+ $quoteChar = $m[1];
+ $uri = $m[2];
+ } else {
+ // $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);
+ }
+ // analyze URI
+ if ('/' !== $uri[0] // root-relative
+ && false === strpos($uri, '//') // protocol (non-data)
+ && 0 !== strpos($uri, 'data:') // data protocol
+ ) {
+ // URI is file-relative: rewrite depending on options
+ if (self::$_prependPath === null) {
+ $uri = self::rewriteRelative($uri, self::$_currentDir, self::$_docRoot, self::$_symlinks);
+ } else {
+ $uri = self::$_prependPath . $uri;
+ if ($uri[0] === '/') {
+ $root = '';
+ $rootRelative = $uri;
+ $uri = $root . self::removeDots($rootRelative);
+ } elseif (preg_match('@^((https?\:)?//([^/]+))/@', $uri, $m) && (false !== strpos($m[3], '.'))) {
+ $root = $m[1];
+ $rootRelative = substr($uri, strlen($root));
+ $uri = $root . self::removeDots($rootRelative);
+ }
+ }
+ }
+ return $isImport
+ ? "@import {$quoteChar}{$uri}{$quoteChar}"
+ : "url({$quoteChar}{$uri}{$quoteChar})";
+ }
+}
diff --git a/inc/contrib/minify/Minify/Cache/APC.php b/inc/contrib/minify/Minify/Cache/APC.php
new file mode 100755
index 00000000..24ab0462
--- /dev/null
+++ b/inc/contrib/minify/Minify/Cache/APC.php
@@ -0,0 +1,133 @@
+<?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)
+ {
+ if (! $this->_fetch($id)) {
+ return false;
+ }
+ return (function_exists('mb_strlen') && ((int)ini_get('mbstring.func_overload') & 2))
+ ? mb_strlen($this->_data, '8bit')
+ : strlen($this->_data);
+ }
+
+ /**
+ * 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/inc/contrib/minify/Minify/Cache/File.php b/inc/contrib/minify/Minify/Cache/File.php
new file mode 100755
index 00000000..3fa6b18f
--- /dev/null
+++ b/inc/contrib/minify/Minify/Cache/File.php
@@ -0,0 +1,195 @@
+<?php
+/**
+ * Class Minify_Cache_File
+ * @package Minify
+ */
+
+class Minify_Cache_File {
+
+ public function __construct($path = '', $fileLocking = false)
+ {
+ if (! $path) {
+ $path = self::tmp();
+ }
+ $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;
+ $file = $this->_path . '/' . $id;
+ if (! @file_put_contents($file, $data, $flag)) {
+ $this->_log("Minify_Cache_File: Write failed to '$file'");
+ }
+ // write control
+ if ($data !== $this->fetch($id)) {
+ @unlink($file);
+ $this->_log("Minify_Cache_File: Post-write read failed for '$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);
+ }
+ }
+
+ /**
+ * Fetch the cache path used
+ *
+ * @return string
+ */
+ public function getPath()
+ {
+ return $this->_path;
+ }
+
+ /**
+ * Get a usable temp directory
+ *
+ * Adapted from Solar/Dir.php
+ * @author Paul M. Jones <pmjones@solarphp.com>
+ * @license http://opensource.org/licenses/bsd-license.php BSD
+ * @link http://solarphp.com/trac/core/browser/trunk/Solar/Dir.php
+ *
+ * @return string
+ */
+ public static function tmp()
+ {
+ static $tmp = null;
+ if (! $tmp) {
+ $tmp = function_exists('sys_get_temp_dir')
+ ? sys_get_temp_dir()
+ : self::_tmp();
+ $tmp = rtrim($tmp, DIRECTORY_SEPARATOR);
+ }
+ return $tmp;
+ }
+
+ /**
+ * Returns the OS-specific directory for temporary files
+ *
+ * @author Paul M. Jones <pmjones@solarphp.com>
+ * @license http://opensource.org/licenses/bsd-license.php BSD
+ * @link http://solarphp.com/trac/core/browser/trunk/Solar/Dir.php
+ *
+ * @return string
+ */
+ 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';
+ }
+
+ /**
+ * Send message to the Minify logger
+ * @param string $msg
+ * @return null
+ */
+ protected function _log($msg)
+ {
+ require_once 'Minify/Logger.php';
+ Minify_Logger::log($msg);
+ }
+
+ private $_path = null;
+ private $_locking = null;
+}
diff --git a/inc/contrib/minify/Minify/Cache/Memcache.php b/inc/contrib/minify/Minify/Cache/Memcache.php
new file mode 100755
index 00000000..72bf454b
--- /dev/null
+++ b/inc/contrib/minify/Minify/Cache/Memcache.php
@@ -0,0 +1,140 @@
+<?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)
+ {
+ if (! $this->_fetch($id)) {
+ return false;
+ }
+ return (function_exists('mb_strlen') && ((int)ini_get('mbstring.func_overload') & 2))
+ ? mb_strlen($this->_data, '8bit')
+ : strlen($this->_data);
+ }
+
+ /**
+ * 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/inc/contrib/minify/Minify/Cache/ZendPlatform.php b/inc/contrib/minify/Minify/Cache/ZendPlatform.php
new file mode 100755
index 00000000..3130d69a
--- /dev/null
+++ b/inc/contrib/minify/Minify/Cache/ZendPlatform.php
@@ -0,0 +1,142 @@
+<?php
+/**
+ * Class Minify_Cache_ZendPlatform
+ * @package Minify
+ */
+
+
+/**
+ * ZendPlatform-based cache class for Minify
+ *
+ * Based on Minify_Cache_APC, uses output_cache_get/put (currently deprecated)
+ *
+ * <code>
+ * Minify::setCache(new Minify_Cache_ZendPlatform());
+ * </code>
+ *
+ * @package Minify
+ * @author Patrick van Dissel
+ */
+class Minify_Cache_ZendPlatform {
+
+
+ /**
+ * Create a Minify_Cache_ZendPlatform 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 output_cache_put($id, "{$_SERVER['REQUEST_TIME']}|{$data}");
+ }
+
+
+ /**
+ * 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)
+ {
+ $ret = ($this->_fetch($id) && ($this->_lm >= $srcMtime));
+ return $ret;
+ }
+
+
+ /**
+ * 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 ZendPlatform, store in instance
+ *
+ * @param string $id
+ *
+ * @return bool success
+ */
+ private function _fetch($id)
+ {
+ if ($this->_id === $id) {
+ return true;
+ }
+ $ret = output_cache_get($id, $this->_exp);
+ if (false === $ret) {
+ $this->_id = null;
+ return false;
+ }
+ list($this->_lm, $this->_data) = explode('|', $ret, 2);
+ $this->_id = $id;
+ return true;
+ }
+}
diff --git a/inc/contrib/minify/Minify/CommentPreserver.php b/inc/contrib/minify/Minify/CommentPreserver.php
new file mode 100755
index 00000000..7a359bf9
--- /dev/null
+++ b/inc/contrib/minify/Minify/CommentPreserver.php
@@ -0,0 +1,89 @@
+<?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 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 3rd 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/inc/contrib/minify/Minify/Controller/Base.php b/inc/contrib/minify/Minify/Controller/Base.php
new file mode 100755
index 00000000..240b5449
--- /dev/null
+++ b/inc/contrib/minify/Minify/Controller/Base.php
@@ -0,0 +1,250 @@
+<?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
+ ,'bubbleCssImports' => false
+ ,'quiet' => false // serve() will send headers and output
+ ,'debug' => false
+
+ // if you override these, the response codes MUST be directly after
+ // the first space.
+ ,'badRequestHeader' => 'HTTP/1.0 400 Bad Request'
+ ,'errorHeader' => 'HTTP/1.0 500 Internal Server Error'
+
+ // 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('JSMin', '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
+ *
+ * @deprecated use checkAllowDirs, checkNotHidden instead
+ */
+ 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'));
+ }
+
+ /**
+ * @param string $file
+ * @param array $allowDirs
+ * @param string $uri
+ * @return bool
+ * @throws Exception
+ */
+ public static function checkAllowDirs($file, $allowDirs, $uri)
+ {
+ foreach ((array)$allowDirs as $allowDir) {
+ if (strpos($file, $allowDir) === 0) {
+ return true;
+ }
+ }
+ throw new Exception("File '$file' is outside \$allowDirs. If the path is"
+ . " resolved via an alias/symlink, look into the \$min_symlinks option."
+ . " E.g. \$min_symlinks['/" . dirname($uri) . "'] = '" . dirname($file) . "';");
+ }
+
+ /**
+ * @param string $file
+ * @throws Exception
+ */
+ public static function checkNotHidden($file)
+ {
+ $b = basename($file);
+ if (0 === strpos($b, '.')) {
+ throw new Exception("Filename '$b' starts with period (may be hidden)");
+ }
+ }
+
+ /**
+ * instances of Minify_Source, which provide content and any individual minification needs.
+ *
+ * @var array
+ *
+ * @see Minify_Source
+ */
+ public $sources = array();
+
+ /**
+ * Short name to place inside cache id
+ *
+ * The setupSources() method may choose to set this, making it easier to
+ * recognize a particular set of sources/settings in the cache folder. It
+ * will be filtered and truncated to make the final cache id <= 250 bytes.
+ *
+ * @var string
+ */
+ public $selectionId = '';
+
+ /**
+ * 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;
+ }
+
+ /**
+ * Send message to the Minify logger
+ *
+ * @param string $msg
+ *
+ * @return null
+ */
+ public function log($msg) {
+ require_once 'Minify/Logger.php';
+ Minify_Logger::log($msg);
+ }
+}
diff --git a/inc/contrib/minify/Minify/Controller/Files.php b/inc/contrib/minify/Minify/Controller/Files.php
new file mode 100755
index 00000000..83f028ad
--- /dev/null
+++ b/inc/contrib/minify/Minify/Controller/Files.php
@@ -0,0 +1,78 @@
+<?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 = $options['files'];
+ // if $files is a single object, casting will break it
+ if (is_object($files)) {
+ $files = array($files);
+ } elseif (! is_array($files)) {
+ $files = (array)$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);
+ }
+ $realPath = realpath($file);
+ if (is_file($realPath)) {
+ $sources[] = new Minify_Source(array(
+ 'filepath' => $realPath
+ ));
+ } else {
+ $this->log("The path \"{$file}\" could not be found (or was not a file)");
+ return $options;
+ }
+ }
+ if ($sources) {
+ $this->sources = $sources;
+ }
+ return $options;
+ }
+}
+
diff --git a/inc/contrib/minify/Minify/Controller/Groups.php b/inc/contrib/minify/Minify/Controller/Groups.php
new file mode 100755
index 00000000..2d4e43b2
--- /dev/null
+++ b/inc/contrib/minify/Minify/Controller/Groups.php
@@ -0,0 +1,93 @@
+<?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
+ *
+ * 'groups': (required) array mapping PATH_INFO strings to arrays
+ * of complete file paths. @see Minify_Controller_Groups
+ *
+ * @return array Minify options
+ */
+ 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
+ $this->log("Missing PATH_INFO or no group set for \"$pi\"");
+ return $options;
+ }
+ $sources = array();
+
+ $files = $groups[$pi];
+ // if $files is a single object, casting will break it
+ if (is_object($files)) {
+ $files = array($files);
+ } elseif (! is_array($files)) {
+ $files = (array)$files;
+ }
+ foreach ($files as $file) {
+ if ($file instanceof Minify_Source) {
+ $sources[] = $file;
+ continue;
+ }
+ if (0 === strpos($file, '//')) {
+ $file = $_SERVER['DOCUMENT_ROOT'] . substr($file, 1);
+ }
+ $realPath = realpath($file);
+ if (is_file($realPath)) {
+ $sources[] = new Minify_Source(array(
+ 'filepath' => $realPath
+ ));
+ } else {
+ $this->log("The path \"{$file}\" could not be found (or was not a file)");
+ return $options;
+ }
+ }
+ if ($sources) {
+ $this->sources = $sources;
+ }
+ return $options;
+ }
+}
+
diff --git a/inc/contrib/minify/Minify/Controller/MinApp.php b/inc/contrib/minify/Minify/Controller/MinApp.php
new file mode 100755
index 00000000..d47c60d9
--- /dev/null
+++ b/inc/contrib/minify/Minify/Controller/MinApp.php
@@ -0,0 +1,231 @@
+<?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()
+ ,'noMinPattern' => '@[-\\.]min\\.(?:js|css)$@i' // matched against basename
+ )
+ ,(isset($options['minApp']) ? $options['minApp'] : array())
+ );
+ unset($options['minApp']);
+ $sources = array();
+ $this->selectionId = '';
+ $firstMissingResource = null;
+
+ if (isset($_GET['g'])) {
+ // add group(s)
+ $this->selectionId .= 'g=' . $_GET['g'];
+ $keys = explode(',', $_GET['g']);
+ if ($keys != array_unique($keys)) {
+ $this->log("Duplicate group key found.");
+ return $options;
+ }
+ $keys = explode(',', $_GET['g']);
+ foreach ($keys as $key) {
+ if (! isset($cOptions['groups'][$key])) {
+ $this->log("A group configuration for \"{$key}\" was not found");
+ return $options;
+ }
+ $files = $cOptions['groups'][$key];
+ // if $files is a single object, casting will break it
+ if (is_object($files)) {
+ $files = array($files);
+ } elseif (! is_array($files)) {
+ $files = (array)$files;
+ }
+ foreach ($files as $file) {
+ if ($file instanceof Minify_Source) {
+ $sources[] = $file;
+ continue;
+ }
+ if (0 === strpos($file, '//')) {
+ $file = $_SERVER['DOCUMENT_ROOT'] . substr($file, 1);
+ }
+ $realpath = realpath($file);
+ if ($realpath && is_file($realpath)) {
+ $sources[] = $this->_getFileSource($realpath, $cOptions);
+ } else {
+ $this->log("The path \"{$file}\" (realpath \"{$realpath}\") could not be found (or was not a file)");
+ if (null === $firstMissingResource) {
+ $firstMissingResource = basename($file);
+ continue;
+ } else {
+ $secondMissingResource = basename($file);
+ $this->log("More than one file was missing: '$firstMissingResource', '$secondMissingResource'");
+ return $options;
+ }
+ }
+ }
+ if ($sources) {
+ try {
+ $this->checkType($sources[0]);
+ } catch (Exception $e) {
+ $this->log($e->getMessage());
+ return $options;
+ }
+ }
+ }
+ }
+ if (! $cOptions['groupsOnly'] && isset($_GET['f'])) {
+ // try user files
+ // The following restrictions are to limit the URLs that minify will
+ // respond to.
+ if (// verify at least one file, files are single comma separated,
+ // and are all same extension
+ ! preg_match('/^[^,]+\\.(css|js)(?:,[^,]+\\.\\1)*$/', $_GET['f'], $m)
+ // no "//"
+ || strpos($_GET['f'], '//') !== false
+ // no "\"
+ || strpos($_GET['f'], '\\') !== false
+ ) {
+ $this->log("GET param 'f' was invalid");
+ return $options;
+ }
+ $ext = ".{$m[1]}";
+ try {
+ $this->checkType($m[1]);
+ } catch (Exception $e) {
+ $this->log($e->getMessage());
+ return $options;
+ }
+ $files = explode(',', $_GET['f']);
+ if ($files != array_unique($files)) {
+ $this->log("Duplicate files were specified");
+ 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 {
+ $this->log("GET param 'b' was invalid");
+ return $options;
+ }
+ } else {
+ $base = '/';
+ }
+ $allowDirs = array();
+ foreach ((array)$cOptions['allowDirs'] as $allowDir) {
+ $allowDirs[] = realpath(str_replace('//', $_SERVER['DOCUMENT_ROOT'] . '/', $allowDir));
+ }
+ $basenames = array(); // just for cache id
+ foreach ($files as $file) {
+ $uri = $base . $file;
+ $path = $_SERVER['DOCUMENT_ROOT'] . $uri;
+ $realpath = realpath($path);
+ if (false === $realpath || ! is_file($realpath)) {
+ $this->log("The path \"{$path}\" (realpath \"{$realpath}\") could not be found (or was not a file)");
+ if (null === $firstMissingResource) {
+ $firstMissingResource = $uri;
+ continue;
+ } else {
+ $secondMissingResource = $uri;
+ $this->log("More than one file was missing: '$firstMissingResource', '$secondMissingResource`'");
+ return $options;
+ }
+ }
+ try {
+ parent::checkNotHidden($realpath);
+ parent::checkAllowDirs($realpath, $allowDirs, $uri);
+ } catch (Exception $e) {
+ $this->log($e->getMessage());
+ return $options;
+ }
+ $sources[] = $this->_getFileSource($realpath, $cOptions);
+ $basenames[] = basename($realpath, $ext);
+ }
+ if ($this->selectionId) {
+ $this->selectionId .= '_f=';
+ }
+ $this->selectionId .= implode(',', $basenames) . $ext;
+ }
+ if ($sources) {
+ if (null !== $firstMissingResource) {
+ array_unshift($sources, new Minify_Source(array(
+ 'id' => 'missingFile'
+ // should not cause cache invalidation
+ ,'lastModified' => 0
+ // due to caching, filename is unreliable.
+ ,'content' => "/* Minify: at least one missing file. See " . Minify::URL_DEBUG . " */\n"
+ ,'minifier' => ''
+ )));
+ }
+ $this->sources = $sources;
+ } else {
+ $this->log("No sources to serve");
+ }
+ return $options;
+ }
+
+ /**
+ * @param string $file
+ *
+ * @param array $cOptions
+ *
+ * @return Minify_Source
+ */
+ protected function _getFileSource($file, $cOptions)
+ {
+ $spec['filepath'] = $file;
+ if ($cOptions['noMinPattern']
+ && preg_match($cOptions['noMinPattern'], basename($file))) {
+ $spec['minifier'] = '';
+ }
+ return new Minify_Source($spec);
+ }
+
+ protected $_type = null;
+
+ /**
+ * Make sure that only source files of a single type are registered
+ *
+ * @param string $sourceOrExt
+ *
+ * @throws Exception
+ */
+ public function checkType($sourceOrExt)
+ {
+ if ($sourceOrExt === 'js') {
+ $type = Minify::TYPE_JS;
+ } elseif ($sourceOrExt === 'css') {
+ $type = Minify::TYPE_CSS;
+ } elseif ($sourceOrExt->contentType !== null) {
+ $type = $sourceOrExt->contentType;
+ } else {
+ return;
+ }
+ if ($this->_type === null) {
+ $this->_type = $type;
+ } elseif ($this->_type !== $type) {
+ throw new Exception('Content-Type mismatch');
+ }
+ }
+}
diff --git a/inc/contrib/minify/Minify/Controller/Page.php b/inc/contrib/minify/Minify/Controller/Page.php
new file mode 100755
index 00000000..de471e12
--- /dev/null
+++ b/inc/contrib/minify/Minify/Controller/Page.php
@@ -0,0 +1,87 @@
+<?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']
+ );
+ $f = $options['file'];
+ } else {
+ // strip controller options
+ $sourceSpec = array(
+ 'content' => $options['content']
+ ,'id' => $options['id']
+ );
+ $f = $options['id'];
+ unset($options['content'], $options['id']);
+ }
+ // something like "builder,index.php" or "directory,file.html"
+ $this->selectionId = strtr(substr($f, 1 + strlen(dirname(dirname($f)))), '/\\', ',,');
+
+ 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('JSMin', 'minify')
+ );
+ $this->_loadCssJsMinifiers = true;
+ unset($options['minifyAll']);
+ }
+ $this->sources[] = new Minify_Source($sourceSpec);
+
+ $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_once 'Minify/CSS.php';
+ require_once 'JSMin.php';
+ }
+ parent::loadMinifier($minifierCallback); // load Minify/HTML.php
+ }
+}
+
diff --git a/inc/contrib/minify/Minify/Controller/Version1.php b/inc/contrib/minify/Minify/Controller/Version1.php
new file mode 100755
index 00000000..5279d36d
--- /dev/null
+++ b/inc/contrib/minify/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 (mostly a proof-of-concept)
+ *
+ * <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/inc/contrib/minify/Minify/DebugDetector.php b/inc/contrib/minify/Minify/DebugDetector.php
new file mode 100755
index 00000000..1def9748
--- /dev/null
+++ b/inc/contrib/minify/Minify/DebugDetector.php
@@ -0,0 +1,26 @@
+<?php
+
+/**
+ * Detect whether request should be debugged
+ *
+ * @package Minify
+ * @author Stephen Clay <steve@mrclay.org>
+ */
+class Minify_DebugDetector {
+ public static function shouldDebugRequest($cookie, $get, $requestUri)
+ {
+ if (isset($get['debug'])) {
+ return true;
+ }
+ if (! empty($cookie['minifyDebug'])) {
+ foreach (preg_split('/\\s+/', $cookie['minifyDebug']) as $debugUri) {
+ $pattern = '@' . preg_quote($debugUri, '@') . '@i';
+ $pattern = str_replace(array('\\*', '\\?'), array('.*', '.'), $pattern);
+ if (preg_match($pattern, $requestUri)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+}
diff --git a/inc/contrib/minify/Minify/HTML.php b/inc/contrib/minify/Minify/HTML.php
new file mode 100755
index 00000000..e9453ffb
--- /dev/null
+++ b/inc/contrib/minify/Minify/HTML.php
@@ -0,0 +1,246 @@
+<?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 {
+
+ /**
+ * "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()) {
+ $min = new Minify_HTML($html, $options);
+ return $min->process();
+ }
+
+
+ /**
+ * Create a minifier object
+ *
+ * @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 null
+ */
+ public function __construct($html, $options = array())
+ {
+ $this->_html = str_replace("\r\n", "\n", trim($html));
+ if (isset($options['xhtml'])) {
+ $this->_isXhtml = (bool)$options['xhtml'];
+ }
+ if (isset($options['cssMinifier'])) {
+ $this->_cssMinifier = $options['cssMinifier'];
+ }
+ if (isset($options['jsMinifier'])) {
+ $this->_jsMinifier = $options['jsMinifier'];
+ }
+ }
+
+
+ /**
+ * Minify the markeup given in the constructor
+ *
+ * @return string
+ */
+ public function process()
+ {
+ if ($this->_isXhtml === null) {
+ $this->_isXhtml = (false !== strpos($this->_html, '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML'));
+ }
+
+ $this->_replacementHash = 'MINIFYHTML' . md5($_SERVER['REQUEST_TIME']);
+ $this->_placeholders = array();
+
+ // replace SCRIPTs (and minify) with placeholders
+ $this->_html = preg_replace_callback(
+ '/(\\s*)<script(\\b[^>]*?>)([\\s\\S]*?)<\\/script>(\\s*)/i'
+ ,array($this, '_removeScriptCB')
+ ,$this->_html);
+
+ // replace STYLEs (and minify) with placeholders
+ $this->_html = preg_replace_callback(
+ '/\\s*<style(\\b[^>]*>)([\\s\\S]*?)<\\/style>\\s*/i'
+ ,array($this, '_removeStyleCB')
+ ,$this->_html);
+
+ // remove HTML comments (not containing IE conditional comments).
+ $this->_html = preg_replace_callback(
+ '/<!--([\\s\\S]*?)-->/'
+ ,array($this, '_commentCB')
+ ,$this->_html);
+
+ // replace PREs with placeholders
+ $this->_html = preg_replace_callback('/\\s*<pre(\\b[^>]*?>[\\s\\S]*?<\\/pre>)\\s*/i'
+ ,array($this, '_removePreCB')
+ ,$this->_html);
+
+ // replace TEXTAREAs with placeholders
+ $this->_html = preg_replace_callback(
+ '/\\s*<textarea(\\b[^>]*?>[\\s\\S]*?<\\/textarea>)\\s*/i'
+ ,array($this, '_removeTextareaCB')
+ ,$this->_html);
+
+ // trim each line.
+ // @todo take into account attribute values that span multiple lines.
+ $this->_html = preg_replace('/^\\s+|\\s+$/m', '', $this->_html);
+
+ // remove ws around block/undisplayed elements
+ $this->_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', $this->_html);
+
+ // remove ws outside of all elements
+ $this->_html = preg_replace(
+ '/>(\\s(?:\\s*))?([^<]+)(\\s(?:\s*))?</'
+ ,'>$1$2$3<'
+ ,$this->_html);
+
+ // use newlines before 1st attribute in open tags (to limit line lengths)
+ $this->_html = preg_replace('/(<[a-z\\-]+)\\s+([^>]+>)/i', "$1\n$2", $this->_html);
+
+ // fill placeholders
+ $this->_html = str_replace(
+ array_keys($this->_placeholders)
+ ,array_values($this->_placeholders)
+ ,$this->_html
+ );
+ // issue 229: multi-pass to catch scripts that didn't get replaced in textareas
+ $this->_html = str_replace(
+ array_keys($this->_placeholders)
+ ,array_values($this->_placeholders)
+ ,$this->_html
+ );
+ return $this->_html;
+ }
+
+ protected function _commentCB($m)
+ {
+ return (0 === strpos($m[1], '[') || false !== strpos($m[1], '<!['))
+ ? $m[0]
+ : '';
+ }
+
+ protected function _reservePlace($content)
+ {
+ $placeholder = '%' . $this->_replacementHash . count($this->_placeholders) . '%';
+ $this->_placeholders[$placeholder] = $content;
+ return $placeholder;
+ }
+
+ protected $_isXhtml = null;
+ protected $_replacementHash = null;
+ protected $_placeholders = array();
+ protected $_cssMinifier = null;
+ protected $_jsMinifier = null;
+
+ protected function _removePreCB($m)
+ {
+ return $this->_reservePlace("<pre{$m[1]}");
+ }
+
+ protected function _removeTextareaCB($m)
+ {
+ return $this->_reservePlace("<textarea{$m[1]}");
+ }
+
+ protected function _removeStyleCB($m)
+ {
+ $openStyle = "<style{$m[1]}";
+ $css = $m[2];
+ // remove HTML comments
+ $css = preg_replace('/(?:^\\s*<!--|-->\\s*$)/', '', $css);
+
+ // remove CDATA section markers
+ $css = $this->_removeCdata($css);
+
+ // minify
+ $minifier = $this->_cssMinifier
+ ? $this->_cssMinifier
+ : 'trim';
+ $css = call_user_func($minifier, $css);
+
+ return $this->_reservePlace($this->_needsCdata($css)
+ ? "{$openStyle}/*<![CDATA[*/{$css}/*]]>*/</style>"
+ : "{$openStyle}{$css}</style>"
+ );
+ }
+
+ protected function _removeScriptCB($m)
+ {
+ $openScript = "<script{$m[2]}";
+ $js = $m[3];
+
+ // whitespace surrounding? preserve at least one space
+ $ws1 = ($m[1] === '') ? '' : ' ';
+ $ws2 = ($m[4] === '') ? '' : ' ';
+
+ // remove HTML comments (and ending "//" if present)
+ $js = preg_replace('/(?:^\\s*<!--\\s*|\\s*(?:\\/\\/)?\\s*-->\\s*$)/', '', $js);
+
+ // remove CDATA section markers
+ $js = $this->_removeCdata($js);
+
+ // minify
+ $minifier = $this->_jsMinifier
+ ? $this->_jsMinifier
+ : 'trim';
+ $js = call_user_func($minifier, $js);
+
+ return $this->_reservePlace($this->_needsCdata($js)
+ ? "{$ws1}{$openScript}/*<![CDATA[*/{$js}/*]]>*/</script>{$ws2}"
+ : "{$ws1}{$openScript}{$js}</script>{$ws2}"
+ );
+ }
+
+ protected function _removeCdata($str)
+ {
+ return (false !== strpos($str, '<![CDATA['))
+ ? str_replace(array('<![CDATA[', ']]>'), '', $str)
+ : $str;
+ }
+
+ protected function _needsCdata($str)
+ {
+ return ($this->_isXhtml && preg_match('/(?:[<&]|\\-\\-|\\]\\]>)/', $str));
+ }
+}
diff --git a/inc/contrib/minify/Minify/HTML/Helper.php b/inc/contrib/minify/Minify/HTML/Helper.php
new file mode 100755
index 00000000..807fdc9c
--- /dev/null
+++ b/inc/contrib/minify/Minify/HTML/Helper.php
@@ -0,0 +1,193 @@
+<?php
+/**
+ * Class Minify_HTML_Helper
+ * @package Minify
+ */
+
+/**
+ * Helpers for writing Minfy URIs into HTML
+ *
+ * @package Minify
+ * @author Stephen Clay <steve@mrclay.org>
+ */
+class Minify_HTML_Helper {
+ public $rewriteWorks = true;
+ public $minAppUri = '/min';
+ public $groupsConfigFile = '';
+
+ /*
+ * Get an HTML-escaped Minify URI for a group or set of files
+ *
+ * @param mixed $keyOrFiles a group key or array of filepaths/URIs
+ * @param array $opts options:
+ * 'farExpires' : (default true) append a modified timestamp for cache revving
+ * 'debug' : (default false) append debug flag
+ * 'charset' : (default 'UTF-8') for htmlspecialchars
+ * 'minAppUri' : (default '/min') URI of min directory
+ * 'rewriteWorks' : (default true) does mod_rewrite work in min app?
+ * 'groupsConfigFile' : specify if different
+ * @return string
+ */
+ public static function getUri($keyOrFiles, $opts = array())
+ {
+ $opts = array_merge(array( // default options
+ 'farExpires' => true
+ ,'debug' => false
+ ,'charset' => 'UTF-8'
+ ,'minAppUri' => '/min'
+ ,'rewriteWorks' => true
+ ,'groupsConfigFile' => ''
+ ), $opts);
+ $h = new self;
+ $h->minAppUri = $opts['minAppUri'];
+ $h->rewriteWorks = $opts['rewriteWorks'];
+ $h->groupsConfigFile = $opts['groupsConfigFile'];
+ if (is_array($keyOrFiles)) {
+ $h->setFiles($keyOrFiles, $opts['farExpires']);
+ } else {
+ $h->setGroup($keyOrFiles, $opts['farExpires']);
+ }
+ $uri = $h->getRawUri($opts['farExpires'], $opts['debug']);
+ return htmlspecialchars($uri, ENT_QUOTES, $opts['charset']);
+ }
+
+ /*
+ * Get non-HTML-escaped URI to minify the specified files
+ */
+ public function getRawUri($farExpires = true, $debug = false)
+ {
+ $path = rtrim($this->minAppUri, '/') . '/';
+ if (! $this->rewriteWorks) {
+ $path .= '?';
+ }
+ if (null === $this->_groupKey) {
+ // @todo: implement shortest uri
+ $path = self::_getShortestUri($this->_filePaths, $path);
+ } else {
+ $path .= "g=" . $this->_groupKey;
+ }
+ if ($debug) {
+ $path .= "&debug";
+ } elseif ($farExpires && $this->_lastModified) {
+ $path .= "&" . $this->_lastModified;
+ }
+ return $path;
+ }
+
+ public function setFiles($files, $checkLastModified = true)
+ {
+ $this->_groupKey = null;
+ if ($checkLastModified) {
+ $this->_lastModified = self::getLastModified($files);
+ }
+ // normalize paths like in /min/f=<paths>
+ foreach ($files as $k => $file) {
+ if (0 === strpos($file, '//')) {
+ $file = substr($file, 2);
+ } elseif (0 === strpos($file, '/')
+ || 1 === strpos($file, ':\\')) {
+ $file = substr($file, strlen($_SERVER['DOCUMENT_ROOT']) + 1);
+ }
+ $file = strtr($file, '\\', '/');
+ $files[$k] = $file;
+ }
+ $this->_filePaths = $files;
+ }
+
+ public function setGroup($key, $checkLastModified = true)
+ {
+ $this->_groupKey = $key;
+ if ($checkLastModified) {
+ if (! $this->groupsConfigFile) {
+ $this->groupsConfigFile = dirname(dirname(dirname(dirname(__FILE__)))) . '/groupsConfig.php';
+ }
+ if (is_file($this->groupsConfigFile)) {
+ $gc = (require $this->groupsConfigFile);
+ if (isset($gc[$key])) {
+ $this->_lastModified = self::getLastModified($gc[$key]);
+ }
+ }
+ }
+ }
+
+ public static function getLastModified($sources, $lastModified = 0)
+ {
+ $max = $lastModified;
+ foreach ((array)$sources as $source) {
+ if (is_object($source) && isset($source->lastModified)) {
+ $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));
+ }
+ }
+ }
+ return $max;
+ }
+
+ protected $_groupKey = null; // if present, URI will be like g=...
+ protected $_filePaths = array();
+ protected $_lastModified = null;
+
+
+ /**
+ * In a given array of strings, find the character they all have at
+ * a particular index
+ *
+ * @param array $arr array of strings
+ * @param int $pos index to check
+ * @return mixed a common char or '' if any do not match
+ */
+ protected static function _getCommonCharAtPos($arr, $pos) {
+ $l = count($arr);
+ $c = $arr[0][$pos];
+ if ($c === '' || $l === 1)
+ return $c;
+ for ($i = 1; $i < $l; ++$i)
+ if ($arr[$i][$pos] !== $c)
+ return '';
+ return $c;
+ }
+
+ /**
+ * Get the shortest URI to minify the set of source files
+ *
+ * @param array $paths root-relative URIs of files
+ * @param string $minRoot root-relative URI of the "min" application
+ */
+ protected static function _getShortestUri($paths, $minRoot = '/min/') {
+ $pos = 0;
+ $base = '';
+ $c;
+ while (true) {
+ $c = self::_getCommonCharAtPos($paths, $pos);
+ if ($c === '') {
+ break;
+ } else {
+ $base .= $c;
+ }
+ ++$pos;
+ }
+ $base = preg_replace('@[^/]+$@', '', $base);
+ $uri = $minRoot . 'f=' . implode(',', $paths);
+
+ if (substr($base, -1) === '/') {
+ // we have a base dir!
+ $basedPaths = $paths;
+ $l = count($paths);
+ for ($i = 0; $i < $l; ++$i) {
+ $basedPaths[$i] = substr($paths[$i], strlen($base));
+ }
+ $base = substr($base, 0, strlen($base) - 1);
+ $bUri = $minRoot . 'b=' . $base . '&f=' . implode(',', $basedPaths);
+
+ $uri = strlen($uri) < strlen($bUri)
+ ? $uri
+ : $bUri;
+ }
+ return $uri;
+ }
+}
diff --git a/inc/contrib/minify/Minify/ImportProcessor.php b/inc/contrib/minify/Minify/ImportProcessor.php
new file mode 100755
index 00000000..bdfae547
--- /dev/null
+++ b/inc/contrib/minify/Minify/ImportProcessor.php
@@ -0,0 +1,216 @@
+<?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>
+ * @author Simon Schick <simonsimcity@gmail.com>
+ */
+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 callback funcs to know the directory of the file that inherits this one
+ private $_previewsDir = null;
+
+ // allows _importCB to write the fetched content back to the obj
+ private $_importedContent = '';
+
+ private static $_isCss = null;
+
+ /**
+ * @param String $currentDir
+ * @param String $previewsDir Is only used internally
+ */
+ private function __construct($currentDir, $previewsDir = "")
+ {
+ $this->_currentDir = $currentDir;
+ $this->_previewsDir = $previewsDir;
+ }
+
+ private function _getContent($file, $is_imported = false)
+ {
+ $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
+ );
+
+ // You only need to rework the import-path if the script is imported
+ if (self::$_isCss && $is_imported) {
+ // 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), $this->_currentDir);
+ $content = $obj->_getContent($file, true);
+ 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);
+ // update the relative path by the directory of the file that imported this one
+ $url = self::getPathDiff(realpath($this->_previewsDir), $path);
+ }
+ }
+ return "url({$quote}{$url}{$quote})";
+ }
+
+ /**
+ * @param string $from
+ * @param string $to
+ * @param string $ps
+ * @return string
+ */
+ private function getPathDiff($from, $to, $ps = DIRECTORY_SEPARATOR)
+ {
+ $realFrom = $this->truepath($from);
+ $realTo = $this->truepath($to);
+
+ $arFrom = explode($ps, rtrim($realFrom, $ps));
+ $arTo = explode($ps, rtrim($realTo, $ps));
+ while (count($arFrom) && count($arTo) && ($arFrom[0] == $arTo[0]))
+ {
+ array_shift($arFrom);
+ array_shift($arTo);
+ }
+ return str_pad("", count($arFrom) * 3, '..' . $ps) . implode($ps, $arTo);
+ }
+
+ /**
+ * This function is to replace PHP's extremely buggy realpath().
+ * @param string $path The original path, can be relative etc.
+ * @return string The resolved path, it might not exist.
+ * @see http://stackoverflow.com/questions/4049856/replace-phps-realpath
+ */
+ function truepath($path)
+ {
+ // whether $path is unix or not
+ $unipath = strlen($path) == 0 || $path{0} != '/';
+ // attempts to detect if path is relative in which case, add cwd
+ if (strpos($path, ':') === false && $unipath)
+ $path = $this->_currentDir . DIRECTORY_SEPARATOR . $path;
+
+ // resolve path parts (single dot, double dot and double delimiters)
+ $path = str_replace(array('/', '\\'), DIRECTORY_SEPARATOR, $path);
+ $parts = array_filter(explode(DIRECTORY_SEPARATOR, $path), 'strlen');
+ $absolutes = array();
+ foreach ($parts as $part) {
+ if ('.' == $part)
+ continue;
+ if ('..' == $part) {
+ array_pop($absolutes);
+ } else {
+ $absolutes[] = $part;
+ }
+ }
+ $path = implode(DIRECTORY_SEPARATOR, $absolutes);
+ // resolve any symlinks
+ if (file_exists($path) && linkinfo($path) > 0)
+ $path = readlink($path);
+ // put initial separator that could have been lost
+ $path = !$unipath ? '/' . $path : $path;
+ return $path;
+ }
+}
diff --git a/inc/contrib/minify/Minify/JS/ClosureCompiler.php b/inc/contrib/minify/Minify/JS/ClosureCompiler.php
new file mode 100755
index 00000000..f4ed5bdb
--- /dev/null
+++ b/inc/contrib/minify/Minify/JS/ClosureCompiler.php
@@ -0,0 +1,133 @@
+<?php
+/**
+ * Class Minify_JS_ClosureCompiler
+ * @package Minify
+ */
+
+/**
+ * Minify Javascript using Google's Closure Compiler API
+ *
+ * @link http://code.google.com/closure/compiler/
+ * @package Minify
+ * @author Stephen Clay <steve@mrclay.org>
+ *
+ * @todo can use a stream wrapper to unit test this?
+ */
+class Minify_JS_ClosureCompiler {
+ const URL = 'http://closure-compiler.appspot.com/compile';
+
+ /**
+ * Minify Javascript code via HTTP request to the Closure Compiler API
+ *
+ * @param string $js input code
+ * @param array $options unused at this point
+ * @return string
+ */
+ public static function minify($js, array $options = array())
+ {
+ $obj = new self($options);
+ return $obj->min($js);
+ }
+
+ /**
+ *
+ * @param array $options
+ *
+ * fallbackFunc : default array($this, 'fallback');
+ */
+ public function __construct(array $options = array())
+ {
+ $this->_fallbackFunc = isset($options['fallbackMinifier'])
+ ? $options['fallbackMinifier']
+ : array($this, '_fallback');
+ }
+
+ public function min($js)
+ {
+ $postBody = $this->_buildPostBody($js);
+ $bytes = (function_exists('mb_strlen') && ((int)ini_get('mbstring.func_overload') & 2))
+ ? mb_strlen($postBody, '8bit')
+ : strlen($postBody);
+ if ($bytes > 200000) {
+ throw new Minify_JS_ClosureCompiler_Exception(
+ 'POST content larger than 200000 bytes'
+ );
+ }
+ $response = $this->_getResponse($postBody);
+ if (preg_match('/^Error\(\d\d?\):/', $response)) {
+ if (is_callable($this->_fallbackFunc)) {
+ $response = "/* Received errors from Closure Compiler API:\n$response"
+ . "\n(Using fallback minifier)\n*/\n";
+ $response .= call_user_func($this->_fallbackFunc, $js);
+ } else {
+ throw new Minify_JS_ClosureCompiler_Exception($response);
+ }
+ }
+ if ($response === '') {
+ $errors = $this->_getResponse($this->_buildPostBody($js, true));
+ throw new Minify_JS_ClosureCompiler_Exception($errors);
+ }
+ return $response;
+ }
+
+ protected $_fallbackFunc = null;
+
+ protected function _getResponse($postBody)
+ {
+ $allowUrlFopen = preg_match('/1|yes|on|true/i', ini_get('allow_url_fopen'));
+ if ($allowUrlFopen) {
+ $contents = file_get_contents(self::URL, false, stream_context_create(array(
+ 'http' => array(
+ 'method' => 'POST',
+ 'header' => 'Content-type: application/x-www-form-urlencoded',
+ 'content' => $postBody,
+ 'max_redirects' => 0,
+ 'timeout' => 15,
+ )
+ )));
+ } elseif (defined('CURLOPT_POST')) {
+ $ch = curl_init(self::URL);
+ curl_setopt($ch, CURLOPT_POST, true);
+ curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
+ curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-type: application/x-www-form-urlencoded'));
+ curl_setopt($ch, CURLOPT_POSTFIELDS, $postBody);
+ curl_setopt($ch, CURLOPT_FOLLOWLOCATION, false);
+ curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 15);
+ $contents = curl_exec($ch);
+ curl_close($ch);
+ } else {
+ throw new Minify_JS_ClosureCompiler_Exception(
+ "Could not make HTTP request: allow_url_open is false and cURL not available"
+ );
+ }
+ if (false === $contents) {
+ throw new Minify_JS_ClosureCompiler_Exception(
+ "No HTTP response from server"
+ );
+ }
+ return trim($contents);
+ }
+
+ protected function _buildPostBody($js, $returnErrors = false)
+ {
+ return http_build_query(array(
+ 'js_code' => $js,
+ 'output_info' => ($returnErrors ? 'errors' : 'compiled_code'),
+ 'output_format' => 'text',
+ 'compilation_level' => 'SIMPLE_OPTIMIZATIONS'
+ ), null, '&');
+ }
+
+ /**
+ * Default fallback function if CC API fails
+ * @param string $js
+ * @return string
+ */
+ protected function _fallback($js)
+ {
+ require_once 'JSMin.php';
+ return JSMin::minify($js);
+ }
+}
+
+class Minify_JS_ClosureCompiler_Exception extends Exception {}
diff --git a/inc/contrib/minify/Minify/Lines.php b/inc/contrib/minify/Minify/Lines.php
new file mode 100755
index 00000000..ca8afa1b
--- /dev/null
+++ b/inc/contrib/minify/Minify/Lines.php
@@ -0,0 +1,137 @@
+<?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. URI rewriting can also be performed.
+ *
+ * @param string $content
+ *
+ * @param array $options available options:
+ *
+ * 'id': (optional) string to identify file. E.g. file name/path
+ *
+ * '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, and prepend a comment with debugging information about
+ * this process.
+ *
+ * @return string
+ */
+ public static function minify($content, $options = array())
+ {
+ $id = (isset($options['id']) && $options['id'])
+ ? $options['id']
+ : '';
+ $content = str_replace("\r\n", "\n", $content);
+
+ // Hackily rewrite strings with XPath expressions that are
+ // likely to throw off our dumb parser (for Prototype 1.6.1).
+ $content = str_replace('"/*"', '"/"+"*"', $content);
+ $content = preg_replace('@([\'"])(\\.?//?)\\*@', '$1$2$1+$1*', $content);
+
+ $lines = explode("\n", $content);
+ $numLines = count($lines);
+ // determine left padding
+ $padTo = strlen((string) $numLines); // e.g. 103 lines = 3 digits
+ $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);
+ }
+ $content = implode("\n", $newLines) . "\n";
+
+ // check for desired URI rewriting
+ if (isset($options['currentDir'])) {
+ require_once 'Minify/CSS/UriRewriter.php';
+ Minify_CSS_UriRewriter::$debugText = '';
+ $content = Minify_CSS_UriRewriter::rewrite(
+ $content
+ ,$options['currentDir']
+ ,isset($options['docRoot']) ? $options['docRoot'] : $_SERVER['DOCUMENT_ROOT']
+ ,isset($options['symlinks']) ? $options['symlinks'] : array()
+ );
+ $content = "/* Minify_CSS_UriRewriter::\$debugText\n\n"
+ . Minify_CSS_UriRewriter::$debugText . "*/\n"
+ . $content;
+ }
+
+ return $content;
+ }
+
+ /**
+ * 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/inc/contrib/minify/Minify/Logger.php b/inc/contrib/minify/Minify/Logger.php
new file mode 100755
index 00000000..8eb72f45
--- /dev/null
+++ b/inc/contrib/minify/Minify/Logger.php
@@ -0,0 +1,47 @@
+<?php
+/**
+ * Class Minify_Logger
+ * @package Minify
+ */
+
+/**
+ * Message logging class
+ *
+ * @package Minify
+ * @author Stephen Clay <steve@mrclay.org>
+ *
+ * @todo lose this singleton! pass log object in Minify::serve and distribute to others
+ */
+class Minify_Logger {
+
+ /**
+ * Set logger object.
+ *
+ * The object should have a method "log" that accepts a value as 1st argument and
+ * an optional string label as the 2nd.
+ *
+ * @param mixed $obj or a "falsey" value to disable
+ * @return null
+ */
+ public static function setLogger($obj = null) {
+ self::$_logger = $obj
+ ? $obj
+ : null;
+ }
+
+ /**
+ * Pass a message to the logger (if set)
+ *
+ * @param string $msg message to log
+ * @return null
+ */
+ public static function log($msg, $label = 'Minify') {
+ if (! self::$_logger) return;
+ self::$_logger->log($msg, $label);
+ }
+
+ /**
+ * @var mixed logger object (like FirePHP) or null (i.e. no logger available)
+ */
+ private static $_logger = null;
+}
diff --git a/inc/contrib/minify/Minify/Packer.php b/inc/contrib/minify/Minify/Packer.php
new file mode 100755
index 00000000..949c3eef
--- /dev/null
+++ b/inc/contrib/minify/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 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/inc/contrib/minify/Minify/Source.php b/inc/contrib/minify/Minify/Source.php
new file mode 100755
index 00000000..5a85d10d
--- /dev/null
+++ b/inc/contrib/minify/Minify/Source.php
@@ -0,0 +1,187 @@
+<?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;
+
+ /**
+ * @var string HTTP Content Type (Minify requires one of the constants Minify::TYPE_*)
+ */
+ public $contentType = 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);
+ }
+ $segments = explode('.', $spec['filepath']);
+ $ext = strtolower(array_pop($segments));
+ switch ($ext) {
+ case 'js' : $this->contentType = 'application/x-javascript';
+ break;
+ case 'css' : $this->contentType = 'text/css';
+ break;
+ case 'htm' : // fallthrough
+ case 'html' : $this->contentType = 'text/html';
+ break;
+ }
+ $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['contentType'])) {
+ $this->contentType = $spec['contentType'];
+ }
+ 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));
+ }
+
+ /**
+ * Get content type from a group of sources
+ *
+ * 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)
+ {
+ foreach ($sources as $source) {
+ if ($source->contentType !== null) {
+ return $source->contentType;
+ }
+ }
+ return 'text/plain';
+ }
+
+ protected $_content = null;
+ protected $_getContentFunc = null;
+ protected $_id = null;
+}
+
diff --git a/inc/contrib/minify/Minify/YUI/CssCompressor.java b/inc/contrib/minify/Minify/YUI/CssCompressor.java
new file mode 100755
index 00000000..3fb497a9
--- /dev/null
+++ b/inc/contrib/minify/Minify/YUI/CssCompressor.java
@@ -0,0 +1,382 @@
+/*
+ * YUI Compressor
+ * http://developer.yahoo.com/yui/compressor/
+ * Author: Julien Lecomte - http://www.julienlecomte.net/
+ * Author: Isaac Schlueter - http://foohack.com/
+ * Author: Stoyan Stefanov - http://phpied.com/
+ * Copyright (c) 2011 Yahoo! Inc. All rights reserved.
+ * The copyrights embodied in the content of this file are licensed
+ * by Yahoo! Inc. under the BSD (revised) open source license.
+ */
+package com.yahoo.platform.yui.compressor;
+
+import java.io.IOException;
+import java.io.Reader;
+import java.io.Writer;
+import java.util.regex.Pattern;
+import java.util.regex.Matcher;
+import java.util.ArrayList;
+
+public class CssCompressor {
+
+ private StringBuffer srcsb = new StringBuffer();
+
+ public CssCompressor(Reader in) throws IOException {
+ // Read the stream...
+ int c;
+ while ((c = in.read()) != -1) {
+ srcsb.append((char) c);
+ }
+ }
+
+ // Leave data urls alone to increase parse performance.
+ protected String extractDataUrls(String css, ArrayList preservedTokens) {
+
+ int maxIndex = css.length() - 1;
+ int appendIndex = 0;
+
+ StringBuffer sb = new StringBuffer();
+
+ Pattern p = Pattern.compile("url\\(\\s*([\"']?)data\\:");
+ Matcher m = p.matcher(css);
+
+ /*
+ * Since we need to account for non-base64 data urls, we need to handle
+ * ' and ) being part of the data string. Hence switching to indexOf,
+ * to determine whether or not we have matching string terminators and
+ * handling sb appends directly, instead of using matcher.append* methods.
+ */
+
+ while (m.find()) {
+
+ int startIndex = m.start() + 4; // "url(".length()
+ String terminator = m.group(1); // ', " or empty (not quoted)
+
+ if (terminator.length() == 0) {
+ terminator = ")";
+ }
+
+ boolean foundTerminator = false;
+
+ int endIndex = m.end() - 1;
+ while(foundTerminator == false && endIndex+1 <= maxIndex) {
+ endIndex = css.indexOf(terminator, endIndex+1);
+
+ if ((endIndex > 0) && (css.charAt(endIndex-1) != '\\')) {
+ foundTerminator = true;
+ if (!")".equals(terminator)) {
+ endIndex = css.indexOf(")", endIndex);
+ }
+ }
+ }
+
+ // Enough searching, start moving stuff over to the buffer
+ sb.append(css.substring(appendIndex, m.start()));
+
+ if (foundTerminator) {
+ String token = css.substring(startIndex, endIndex);
+ token = token.replaceAll("\\s+", "");
+ preservedTokens.add(token);
+
+ String preserver = "url(___YUICSSMIN_PRESERVED_TOKEN_" + (preservedTokens.size() - 1) + "___)";
+ sb.append(preserver);
+
+ appendIndex = endIndex + 1;
+ } else {
+ // No end terminator found, re-add the whole match. Should we throw/warn here?
+ sb.append(css.substring(m.start(), m.end()));
+ appendIndex = m.end();
+ }
+ }
+
+ sb.append(css.substring(appendIndex));
+
+ return sb.toString();
+ }
+
+ public void compress(Writer out, int linebreakpos)
+ throws IOException {
+
+ Pattern p;
+ Matcher m;
+ String css = srcsb.toString();
+
+ int startIndex = 0;
+ int endIndex = 0;
+ int i = 0;
+ int max = 0;
+ ArrayList preservedTokens = new ArrayList(0);
+ ArrayList comments = new ArrayList(0);
+ String token;
+ int totallen = css.length();
+ String placeholder;
+
+ css = this.extractDataUrls(css, preservedTokens);
+
+ StringBuffer sb = new StringBuffer(css);
+
+ // collect all comment blocks...
+ while ((startIndex = sb.indexOf("/*", startIndex)) >= 0) {
+ endIndex = sb.indexOf("*/", startIndex + 2);
+ if (endIndex < 0) {
+ endIndex = totallen;
+ }
+
+ token = sb.substring(startIndex + 2, endIndex);
+ comments.add(token);
+ sb.replace(startIndex + 2, endIndex, "___YUICSSMIN_PRESERVE_CANDIDATE_COMMENT_" + (comments.size() - 1) + "___");
+ startIndex += 2;
+ }
+ css = sb.toString();
+
+ // preserve strings so their content doesn't get accidentally minified
+ sb = new StringBuffer();
+ p = Pattern.compile("(\"([^\\\\\"]|\\\\.|\\\\)*\")|(\'([^\\\\\']|\\\\.|\\\\)*\')");
+ m = p.matcher(css);
+ while (m.find()) {
+ token = m.group();
+ char quote = token.charAt(0);
+ token = token.substring(1, token.length() - 1);
+
+ // maybe the string contains a comment-like substring?
+ // one, maybe more? put'em back then
+ if (token.indexOf("___YUICSSMIN_PRESERVE_CANDIDATE_COMMENT_") >= 0) {
+ for (i = 0, max = comments.size(); i < max; i += 1) {
+ token = token.replace("___YUICSSMIN_PRESERVE_CANDIDATE_COMMENT_" + i + "___", comments.get(i).toString());
+ }
+ }
+
+ // minify alpha opacity in filter strings
+ token = token.replaceAll("(?i)progid:DXImageTransform.Microsoft.Alpha\\(Opacity=", "alpha(opacity=");
+
+ preservedTokens.add(token);
+ String preserver = quote + "___YUICSSMIN_PRESERVED_TOKEN_" + (preservedTokens.size() - 1) + "___" + quote;
+ m.appendReplacement(sb, preserver);
+ }
+ m.appendTail(sb);
+ css = sb.toString();
+
+
+ // strings are safe, now wrestle the comments
+ for (i = 0, max = comments.size(); i < max; i += 1) {
+
+ token = comments.get(i).toString();
+ placeholder = "___YUICSSMIN_PRESERVE_CANDIDATE_COMMENT_" + i + "___";
+
+ // ! in the first position of the comment means preserve
+ // so push to the preserved tokens while stripping the !
+ if (token.startsWith("!")) {
+ preservedTokens.add(token);
+ css = css.replace(placeholder, "___YUICSSMIN_PRESERVED_TOKEN_" + (preservedTokens.size() - 1) + "___");
+ continue;
+ }
+
+ // \ in the last position looks like hack for Mac/IE5
+ // shorten that to /*\*/ and the next one to /**/
+ if (token.endsWith("\\")) {
+ preservedTokens.add("\\");
+ css = css.replace(placeholder, "___YUICSSMIN_PRESERVED_TOKEN_" + (preservedTokens.size() - 1) + "___");
+ i = i + 1; // attn: advancing the loop
+ preservedTokens.add("");
+ css = css.replace("___YUICSSMIN_PRESERVE_CANDIDATE_COMMENT_" + i + "___", "___YUICSSMIN_PRESERVED_TOKEN_" + (preservedTokens.size() - 1) + "___");
+ continue;
+ }
+
+ // keep empty comments after child selectors (IE7 hack)
+ // e.g. html >/**/ body
+ if (token.length() == 0) {
+ startIndex = css.indexOf(placeholder);
+ if (startIndex > 2) {
+ if (css.charAt(startIndex - 3) == '>') {
+ preservedTokens.add("");
+ css = css.replace(placeholder, "___YUICSSMIN_PRESERVED_TOKEN_" + (preservedTokens.size() - 1) + "___");
+ }
+ }
+ }
+
+ // in all other cases kill the comment
+ css = css.replace("/*" + placeholder + "*/", "");
+ }
+
+
+ // Normalize all whitespace strings to single spaces. Easier to work with that way.
+ css = css.replaceAll("\\s+", " ");
+
+ // Remove the spaces before the things that should not have spaces before them.
+ // But, be careful not to turn "p :link {...}" into "p:link{...}"
+ // Swap out any pseudo-class colons with the token, and then swap back.
+ sb = new StringBuffer();
+ p = Pattern.compile("(^|\\})(([^\\{:])+:)+([^\\{]*\\{)");
+ m = p.matcher(css);
+ while (m.find()) {
+ String s = m.group();
+ s = s.replaceAll(":", "___YUICSSMIN_PSEUDOCLASSCOLON___");
+ s = s.replaceAll( "\\\\", "\\\\\\\\" ).replaceAll( "\\$", "\\\\\\$" );
+ m.appendReplacement(sb, s);
+ }
+ m.appendTail(sb);
+ css = sb.toString();
+ // Remove spaces before the things that should not have spaces before them.
+ css = css.replaceAll("\\s+([!{};:>+\\(\\)\\],])", "$1");
+ // bring back the colon
+ css = css.replaceAll("___YUICSSMIN_PSEUDOCLASSCOLON___", ":");
+
+ // retain space for special IE6 cases
+ css = css.replaceAll(":first\\-(line|letter)(\\{|,)", ":first-$1 $2");
+
+ // no space after the end of a preserved comment
+ css = css.replaceAll("\\*/ ", "*/");
+
+ // If there is a @charset, then only allow one, and push to the top of the file.
+ css = css.replaceAll("^(.*)(@charset \"[^\"]*\";)", "$2$1");
+ css = css.replaceAll("^(\\s*@charset [^;]+;\\s*)+", "$1");
+
+ // Put the space back in some cases, to support stuff like
+ // @media screen and (-webkit-min-device-pixel-ratio:0){
+ css = css.replaceAll("\\band\\(", "and (");
+
+ // Remove the spaces after the things that should not have spaces after them.
+ css = css.replaceAll("([!{}:;>+\\(\\[,])\\s+", "$1");
+
+ // remove unnecessary semicolons
+ css = css.replaceAll(";+}", "}");
+
+ // Replace 0(px,em,%) with 0.
+ css = css.replaceAll("([\\s:])(0)(px|em|%|in|cm|mm|pc|pt|ex)", "$1$2");
+
+ // Replace 0 0 0 0; with 0.
+ css = css.replaceAll(":0 0 0 0(;|})", ":0$1");
+ css = css.replaceAll(":0 0 0(;|})", ":0$1");
+ css = css.replaceAll(":0 0(;|})", ":0$1");
+
+
+ // Replace background-position:0; with background-position:0 0;
+ // same for transform-origin
+ sb = new StringBuffer();
+ p = Pattern.compile("(?i)(background-position|transform-origin|webkit-transform-origin|moz-transform-origin|o-transform-origin|ms-transform-origin):0(;|})");
+ m = p.matcher(css);
+ while (m.find()) {
+ m.appendReplacement(sb, m.group(1).toLowerCase() + ":0 0" + m.group(2));
+ }
+ m.appendTail(sb);
+ css = sb.toString();
+
+ // Replace 0.6 to .6, but only when preceded by : or a white-space
+ css = css.replaceAll("(:|\\s)0+\\.(\\d+)", "$1.$2");
+
+ // Shorten colors from rgb(51,102,153) to #336699
+ // This makes it more likely that it'll get further compressed in the next step.
+ p = Pattern.compile("rgb\\s*\\(\\s*([0-9,\\s]+)\\s*\\)");
+ m = p.matcher(css);
+ sb = new StringBuffer();
+ while (m.find()) {
+ String[] rgbcolors = m.group(1).split(",");
+ StringBuffer hexcolor = new StringBuffer("#");
+ for (i = 0; i < rgbcolors.length; i++) {
+ int val = Integer.parseInt(rgbcolors[i]);
+ if (val < 16) {
+ hexcolor.append("0");
+ }
+ hexcolor.append(Integer.toHexString(val));
+ }
+ m.appendReplacement(sb, hexcolor.toString());
+ }
+ m.appendTail(sb);
+ css = sb.toString();
+
+ // Shorten colors from #AABBCC to #ABC. Note that we want to make sure
+ // the color is not preceded by either ", " or =. Indeed, the property
+ // filter: chroma(color="#FFFFFF");
+ // would become
+ // filter: chroma(color="#FFF");
+ // which makes the filter break in IE.
+ // We also want to make sure we're only compressing #AABBCC patterns inside { }, not id selectors ( #FAABAC {} )
+ // We also want to avoid compressing invalid values (e.g. #AABBCCD to #ABCD)
+ p = Pattern.compile("(\\=\\s*?[\"']?)?" + "#([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])" + "(:?\\}|[^0-9a-fA-F{][^{]*?\\})");
+
+ m = p.matcher(css);
+ sb = new StringBuffer();
+ int index = 0;
+
+ while (m.find(index)) {
+
+ sb.append(css.substring(index, m.start()));
+
+ boolean isFilter = (m.group(1) != null && !"".equals(m.group(1)));
+
+ if (isFilter) {
+ // Restore, as is. Compression will break filters
+ sb.append(m.group(1) + "#" + m.group(2) + m.group(3) + m.group(4) + m.group(5) + m.group(6) + m.group(7));
+ } else {
+ if( m.group(2).equalsIgnoreCase(m.group(3)) &&
+ m.group(4).equalsIgnoreCase(m.group(5)) &&
+ m.group(6).equalsIgnoreCase(m.group(7))) {
+
+ // #AABBCC pattern
+ sb.append("#" + (m.group(3) + m.group(5) + m.group(7)).toLowerCase());
+
+ } else {
+
+ // Non-compressible color, restore, but lower case.
+ sb.append("#" + (m.group(2) + m.group(3) + m.group(4) + m.group(5) + m.group(6) + m.group(7)).toLowerCase());
+ }
+ }
+
+ index = m.end(7);
+ }
+
+ sb.append(css.substring(index));
+ css = sb.toString();
+
+ // border: none -> border:0
+ sb = new StringBuffer();
+ p = Pattern.compile("(?i)(border|border-top|border-right|border-bottom|border-right|outline|background):none(;|})");
+ m = p.matcher(css);
+ while (m.find()) {
+ m.appendReplacement(sb, m.group(1).toLowerCase() + ":0" + m.group(2));
+ }
+ m.appendTail(sb);
+ css = sb.toString();
+
+ // shorter opacity IE filter
+ css = css.replaceAll("(?i)progid:DXImageTransform.Microsoft.Alpha\\(Opacity=", "alpha(opacity=");
+
+ // Remove empty rules.
+ css = css.replaceAll("[^\\}\\{/;]+\\{\\}", "");
+
+ // TODO: Should this be after we re-insert tokens. These could alter the break points. However then
+ // we'd need to make sure we don't break in the middle of a string etc.
+ if (linebreakpos >= 0) {
+ // Some source control tools don't like it when files containing lines longer
+ // than, say 8000 characters, are checked in. The linebreak option is used in
+ // that case to split long lines after a specific column.
+ i = 0;
+ int linestartpos = 0;
+ sb = new StringBuffer(css);
+ while (i < sb.length()) {
+ char c = sb.charAt(i++);
+ if (c == '}' && i - linestartpos > linebreakpos) {
+ sb.insert(i, '\n');
+ linestartpos = i;
+ }
+ }
+
+ css = sb.toString();
+ }
+
+ // Replace multiple semi-colons in a row by a single one
+ // See SF bug #1980989
+ css = css.replaceAll(";;+", ";");
+
+ // restore preserved comments and strings
+ for(i = 0, max = preservedTokens.size(); i < max; i++) {
+ css = css.replace("___YUICSSMIN_PRESERVED_TOKEN_" + i + "___", preservedTokens.get(i).toString());
+ }
+
+ // Trim the final string (for any leading or trailing white spaces)
+ css = css.trim();
+
+ // Write the output...
+ out.write(css);
+ }
+}
diff --git a/inc/contrib/minify/Minify/YUI/CssCompressor.php b/inc/contrib/minify/Minify/YUI/CssCompressor.php
new file mode 100755
index 00000000..ae3443d4
--- /dev/null
+++ b/inc/contrib/minify/Minify/YUI/CssCompressor.php
@@ -0,0 +1,171 @@
+<?php
+/**
+ * Class Minify_YUI_CssCompressor
+ * @package Minify
+ *
+ * YUI Compressor
+ * Author: Julien Lecomte - http://www.julienlecomte.net/
+ * Author: Isaac Schlueter - http://foohack.com/
+ * Author: Stoyan Stefanov - http://phpied.com/
+ * Author: Steve Clay - http://www.mrclay.org/ (PHP port)
+ * Copyright (c) 2009 Yahoo! Inc. All rights reserved.
+ * The copyrights embodied in the content of this file are licensed
+ * by Yahoo! Inc. under the BSD (revised) open source license.
+ */
+
+/**
+ * Compress CSS (incomplete DO NOT USE)
+ *
+ * @see https://github.com/yui/yuicompressor/blob/master/src/com/yahoo/platform/yui/compressor/CssCompressor.java
+ *
+ * @package Minify
+ */
+class Minify_YUI_CssCompressor {
+
+ /**
+ * Minify a CSS string
+ *
+ * @param string $css
+ *
+ * @return string
+ */
+ public function compress($css, $linebreakpos = 0)
+ {
+ $css = str_replace("\r\n", "\n", $css);
+
+ /**
+ * @todo comment removal
+ * @todo re-port from newer Java version
+ */
+
+ // Normalize all whitespace strings to single spaces. Easier to work with that way.
+ $css = preg_replace('@\s+@', ' ', $css);
+
+ // Make a pseudo class for the Box Model Hack
+ $css = preg_replace("@\"\\\\\"}\\\\\"\"@", "___PSEUDOCLASSBMH___", $css);
+
+ // Remove the spaces before the things that should not have spaces before them.
+ // But, be careful not to turn "p :link {...}" into "p:link{...}"
+ // Swap out any pseudo-class colons with the token, and then swap back.
+ $css = preg_replace_callback("@(^|\\})(([^\\{:])+:)+([^\\{]*\\{)@", array($this, '_removeSpacesCB'), $css);
+
+ $css = preg_replace("@\\s+([!{};:>+\\(\\)\\],])@", "$1", $css);
+ $css = str_replace("___PSEUDOCLASSCOLON___", ":", $css);
+
+ // Remove the spaces after the things that should not have spaces after them.
+ $css = preg_replace("@([!{}:;>+\\(\\[,])\\s+@", "$1", $css);
+
+ // Add the semicolon where it's missing.
+ $css = preg_replace("@([^;\\}])}@", "$1;}", $css);
+
+ // Replace 0(px,em,%) with 0.
+ $css = preg_replace("@([\\s:])(0)(px|em|%|in|cm|mm|pc|pt|ex)@", "$1$2", $css);
+
+ // Replace 0 0 0 0; with 0.
+ $css = str_replace(":0 0 0 0;", ":0;", $css);
+ $css = str_replace(":0 0 0;", ":0;", $css);
+ $css = str_replace(":0 0;", ":0;", $css);
+
+ // Replace background-position:0; with background-position:0 0;
+ $css = str_replace("background-position:0;", "background-position:0 0;", $css);
+
+ // Replace 0.6 to .6, but only when preceded by : or a white-space
+ $css = preg_replace("@(:|\\s)0+\\.(\\d+)@", "$1.$2", $css);
+
+ // Shorten colors from rgb(51,102,153) to #336699
+ // This makes it more likely that it'll get further compressed in the next step.
+ $css = preg_replace_callback("@rgb\\s*\\(\\s*([0-9,\\s]+)\\s*\\)@", array($this, '_shortenRgbCB'), $css);
+
+ // Shorten colors from #AABBCC to #ABC. Note that we want to make sure
+ // the color is not preceded by either ", " or =. Indeed, the property
+ // filter: chroma(color="#FFFFFF");
+ // would become
+ // filter: chroma(color="#FFF");
+ // which makes the filter break in IE.
+ $css = preg_replace_callback("@([^\"'=\\s])(\\s*)#([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])@", array($this, '_shortenHexCB'), $css);
+
+ // Remove empty rules.
+ $css = preg_replace("@[^\\}]+\\{;\\}@", "", $css);
+
+ $linebreakpos = isset($this->_options['linebreakpos'])
+ ? $this->_options['linebreakpos']
+ : 0;
+
+ if ($linebreakpos > 0) {
+ // Some source control tools don't like it when files containing lines longer
+ // than, say 8000 characters, are checked in. The linebreak option is used in
+ // that case to split long lines after a specific column.
+ $i = 0;
+ $linestartpos = 0;
+ $sb = $css;
+
+ // make sure strlen returns byte count
+ $mbIntEnc = null;
+ if (function_exists('mb_strlen') && ((int)ini_get('mbstring.func_overload') & 2)) {
+ $mbIntEnc = mb_internal_encoding();
+ mb_internal_encoding('8bit');
+ }
+ $sbLength = strlen($css);
+ while ($i < $sbLength) {
+ $c = $sb[$i++];
+ if ($c === '}' && $i - $linestartpos > $linebreakpos) {
+ $sb = substr_replace($sb, "\n", $i, 0);
+ $sbLength++;
+ $linestartpos = $i;
+ }
+ }
+ $css = $sb;
+
+ // undo potential mb_encoding change
+ if ($mbIntEnc !== null) {
+ mb_internal_encoding($mbIntEnc);
+ }
+ }
+
+ // Replace the pseudo class for the Box Model Hack
+ $css = str_replace("___PSEUDOCLASSBMH___", "\"\\\\\"}\\\\\"\"", $css);
+
+ // Replace multiple semi-colons in a row by a single one
+ // See SF bug #1980989
+ $css = preg_replace("@;;+@", ";", $css);
+
+ // prevent triggering IE6 bug: http://www.crankygeek.com/ie6pebug/
+ $css = preg_replace('/:first-l(etter|ine)\\{/', ':first-l$1 {', $css);
+
+ // Trim the final string (for any leading or trailing white spaces)
+ $css = trim($css);
+
+ return $css;
+ }
+
+ protected function _removeSpacesCB($m)
+ {
+ return str_replace(':', '___PSEUDOCLASSCOLON___', $m[0]);
+ }
+
+ protected function _shortenRgbCB($m)
+ {
+ $rgbcolors = explode(',', $m[1]);
+ $hexcolor = '#';
+ for ($i = 0; $i < count($rgbcolors); $i++) {
+ $val = round($rgbcolors[$i]);
+ if ($val < 16) {
+ $hexcolor .= '0';
+ }
+ $hexcolor .= dechex($val);
+ }
+ return $hexcolor;
+ }
+
+ protected function _shortenHexCB($m)
+ {
+ // Test for AABBCC pattern
+ if ((strtolower($m[3])===strtolower($m[4])) &&
+ (strtolower($m[5])===strtolower($m[6])) &&
+ (strtolower($m[7])===strtolower($m[8]))) {
+ return $m[1] . $m[2] . "#" . $m[3] . $m[5] . $m[7];
+ } else {
+ return $m[0];
+ }
+ }
+} \ No newline at end of file
diff --git a/inc/contrib/minify/Minify/YUICompressor.php b/inc/contrib/minify/Minify/YUICompressor.php
new file mode 100755
index 00000000..c5bd8a1e
--- /dev/null
+++ b/inc/contrib/minify/Minify/YUICompressor.php
@@ -0,0 +1,148 @@
+<?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, $result_code);
+ unlink($tmpFile);
+ if ($result_code != 0) {
+ throw new Exception('Minify_YUICompressor : YUI compressor execution failed.');
+ }
+ 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('/^[\\da-zA-Z0-9\\-]+$/', $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)) {
+ throw new Exception('Minify_YUICompressor : $jarFile('.self::$jarFile.') is not a valid file.');
+ }
+ if (! is_executable(self::$jarFile)) {
+ throw new Exception('Minify_YUICompressor : $jarFile('.self::$jarFile.') is not executable.');
+ }
+ if (! is_dir(self::$tempDir)) {
+ throw new Exception('Minify_YUICompressor : $tempDir('.self::$tempDir.') is not a valid direcotry.');
+ }
+ if (! is_writable(self::$tempDir)) {
+ throw new Exception('Minify_YUICompressor : $tempDir('.self::$tempDir.') is not writable.');
+ }
+ }
+}
+