3 * Class Minify_ImportProcessor
8 * Linearize a CSS/JS file by including content specified by CSS import
9 * declarations. In CSS files, relative URIs are fixed.
11 * @imports will be processed regardless of where they appear in the source
12 * files; i.e. @imports commented out or in string content will still be
15 * This has a unit test but should be considered "experimental".
18 * @author Stephen Clay <steve@mrclay.org>
20 class Minify_ImportProcessor {
22 public static $filesIncluded = array();
24 public static function process($file)
26 self::$filesIncluded = array();
27 self::$_isCss = (strtolower(substr($file, -4)) === '.css');
28 $obj = new Minify_ImportProcessor(dirname($file));
29 return $obj->_getContent($file);
32 // allows callback funcs to know the current directory
33 private $_currentDir = null;
35 // allows _importCB to write the fetched content back to the obj
36 private $_importedContent = '';
38 private static $_isCss = null;
40 private function __construct($currentDir)
42 $this->_currentDir = $currentDir;
45 private function _getContent($file)
47 $file = realpath($file);
49 || in_array($file, self::$filesIncluded)
50 || false === ($content = @file_get_contents($file))
52 // file missing, already included, or failed read
55 self::$filesIncluded[] = realpath($file);
56 $this->_currentDir = dirname($file);
58 // remove UTF-8 BOM if present
59 if (pack("CCC",0xef,0xbb,0xbf) === substr($content, 0, 3)) {
60 $content = substr($content, 3);
62 // ensure uniform EOLs
63 $content = str_replace("\r\n", "\n", $content);
66 $content = preg_replace_callback(
69 (?:url\\(\\s*)? # maybe url(
72 [\'"]? # maybe end quote
73 (?:\\s*\\))? # maybe )
74 ([a-zA-Z,\\s]*)? # 2 = media list
77 ,array($this, '_importCB')
82 // rewrite remaining relative URIs
83 $content = preg_replace_callback(
84 '/url\\(\\s*([^\\)\\s]+)\\s*\\)/'
85 ,array($this, '_urlCB')
90 return $this->_importedContent . $content;
93 private function _importCB($m)
96 $mediaList = preg_replace('/\\s+/', '', $m[2]);
98 if (strpos($url, '://') > 0) {
99 // protocol, leave in place for CSS, comment for JS
102 : "/* Minify_ImportProcessor will not include remote content */";
104 if ('/' === $url[0]) {
105 // protocol-relative or root path
106 $url = ltrim($url, '/');
107 $file = realpath($_SERVER['DOCUMENT_ROOT']) . DIRECTORY_SEPARATOR
108 . strtr($url, '/', DIRECTORY_SEPARATOR);
110 // relative to current path
111 $file = $this->_currentDir . DIRECTORY_SEPARATOR
112 . strtr($url, '/', DIRECTORY_SEPARATOR);
114 $obj = new Minify_ImportProcessor(dirname($file));
115 $content = $obj->_getContent($file);
116 if ('' === $content) {
117 // failed. leave in place for CSS, comment for JS
120 : "/* Minify_ImportProcessor could not fetch '{$file}' */";;
122 return (!self::$_isCss || preg_match('@(?:^$|\\ball\\b)@', $mediaList))
124 : "@media {$mediaList} {\n{$content}\n}\n";
127 private function _urlCB($m)
129 // $m[1] is either quoted or not
130 $quote = ($m[1][0] === "'" || $m[1][0] === '"')
133 $url = ($quote === '')
135 : substr($m[1], 1, strlen($m[1]) - 2);
136 if ('/' !== $url[0]) {
137 if (strpos($url, '//') > 0) {
138 // probably starts with protocol, do not alter
140 // prepend path with current dir separator (OS-independent)
141 $path = $this->_currentDir
142 . DIRECTORY_SEPARATOR . strtr($url, '/', DIRECTORY_SEPARATOR);
144 $path = substr($path, strlen(realpath($_SERVER['DOCUMENT_ROOT'])));
145 // fix to absolute URL
146 $url = strtr($path, '/\\', '//');
147 // remove /./ and /../ where possible
148 $url = str_replace('/./', '/', $url);
149 // inspired by patch from Oleg Cherniy
151 $url = preg_replace('@/[^/]+/\\.\\./@', '/', $url, 1, $changed);
155 return "url({$quote}{$url}{$quote})";