123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216 |
- <?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;
- }
- }
|