diff options
Diffstat (limited to 'min/lib/Minify/ImportProcessor.php')
-rw-r--r-- | min/lib/Minify/ImportProcessor.php | 157 |
1 files changed, 157 insertions, 0 deletions
diff --git a/min/lib/Minify/ImportProcessor.php b/min/lib/Minify/ImportProcessor.php new file mode 100644 index 0000000..0d6d90a --- /dev/null +++ b/min/lib/Minify/ImportProcessor.php @@ -0,0 +1,157 @@ +<?php +/** + * Class Minify_ImportProcessor + * @package Minify + */ + +/** + * Linearize a CSS/JS file by including content specified by CSS import + * declarations. In CSS files, relative URIs are fixed. + * + * @imports will be processed regardless of where they appear in the source + * files; i.e. @imports commented out or in string content will still be + * processed! + * + * This has a unit test but should be considered "experimental". + * + * @package Minify + * @author Stephen Clay <steve@mrclay.org> + */ +class Minify_ImportProcessor { + + public static $filesIncluded = array(); + + public static function process($file) + { + self::$filesIncluded = array(); + self::$_isCss = (strtolower(substr($file, -4)) === '.css'); + $obj = new Minify_ImportProcessor(dirname($file)); + return $obj->_getContent($file); + } + + // allows callback funcs to know the current directory + private $_currentDir = null; + + // allows _importCB to write the fetched content back to the obj + private $_importedContent = ''; + + private static $_isCss = null; + + private function __construct($currentDir) + { + $this->_currentDir = $currentDir; + } + + private function _getContent($file) + { + $file = realpath($file); + if (! $file + || in_array($file, self::$filesIncluded) + || false === ($content = @file_get_contents($file)) + ) { + // file missing, already included, or failed read + return ''; + } + self::$filesIncluded[] = realpath($file); + $this->_currentDir = dirname($file); + + // remove UTF-8 BOM if present + if (pack("CCC",0xef,0xbb,0xbf) === substr($content, 0, 3)) { + $content = substr($content, 3); + } + // ensure uniform EOLs + $content = str_replace("\r\n", "\n", $content); + + // process @imports + $content = preg_replace_callback( + '/ + @import\\s+ + (?:url\\(\\s*)? # maybe url( + [\'"]? # maybe quote + (.*?) # 1 = URI + [\'"]? # maybe end quote + (?:\\s*\\))? # maybe ) + ([a-zA-Z,\\s]*)? # 2 = media list + ; # end token + /x' + ,array($this, '_importCB') + ,$content + ); + + if (self::$_isCss) { + // rewrite remaining relative URIs + $content = preg_replace_callback( + '/url\\(\\s*([^\\)\\s]+)\\s*\\)/' + ,array($this, '_urlCB') + ,$content + ); + } + + return $this->_importedContent . $content; + } + + private function _importCB($m) + { + $url = $m[1]; + $mediaList = preg_replace('/\\s+/', '', $m[2]); + + if (strpos($url, '://') > 0) { + // protocol, leave in place for CSS, comment for JS + return self::$_isCss + ? $m[0] + : "/* Minify_ImportProcessor will not include remote content */"; + } + if ('/' === $url[0]) { + // protocol-relative or root path + $url = ltrim($url, '/'); + $file = realpath($_SERVER['DOCUMENT_ROOT']) . DIRECTORY_SEPARATOR + . strtr($url, '/', DIRECTORY_SEPARATOR); + } else { + // relative to current path + $file = $this->_currentDir . DIRECTORY_SEPARATOR + . strtr($url, '/', DIRECTORY_SEPARATOR); + } + $obj = new Minify_ImportProcessor(dirname($file)); + $content = $obj->_getContent($file); + if ('' === $content) { + // failed. leave in place for CSS, comment for JS + return self::$_isCss + ? $m[0] + : "/* Minify_ImportProcessor could not fetch '{$file}' */";; + } + return (!self::$_isCss || preg_match('@(?:^$|\\ball\\b)@', $mediaList)) + ? $content + : "@media {$mediaList} {\n{$content}\n}\n"; + } + + private function _urlCB($m) + { + // $m[1] is either quoted or not + $quote = ($m[1][0] === "'" || $m[1][0] === '"') + ? $m[1][0] + : ''; + $url = ($quote === '') + ? $m[1] + : substr($m[1], 1, strlen($m[1]) - 2); + if ('/' !== $url[0]) { + if (strpos($url, '//') > 0) { + // probably starts with protocol, do not alter + } else { + // prepend path with current dir separator (OS-independent) + $path = $this->_currentDir + . DIRECTORY_SEPARATOR . strtr($url, '/', DIRECTORY_SEPARATOR); + // strip doc root + $path = substr($path, strlen(realpath($_SERVER['DOCUMENT_ROOT']))); + // fix to absolute URL + $url = strtr($path, '/\\', '//'); + // remove /./ and /../ where possible + $url = str_replace('/./', '/', $url); + // inspired by patch from Oleg Cherniy + do { + $url = preg_replace('@/[^/]+/\\.\\./@', '/', $url, 1, $changed); + } while ($changed); + } + } + return "url({$quote}{$url}{$quote})"; + } +} |