3 * Class Minify_CSS_UriRewriter
8 * Rewrite file-relative URIs as root-relative in CSS files
11 * @author Stephen Clay <steve@mrclay.org>
13 class Minify_CSS_UriRewriter {
16 * Defines which class to call as part of callbacks, change this
17 * if you extend Minify_CSS_UriRewriter
20 protected static $className = 'Minify_CSS_UriRewriter';
23 * rewrite() and rewriteRelative() append debugging information here
26 public static $debugText = '';
29 * Rewrite file relative URIs as root relative in CSS files
33 * @param string $currentDir The directory of the current CSS file.
35 * @param string $docRoot The document root of the web site in which
36 * the CSS file resides (default = $_SERVER['DOCUMENT_ROOT']).
38 * @param array $symlinks (default = array()) If the CSS file is stored in
39 * a symlink-ed directory, provide an array of link paths to
40 * target paths, where the link paths are within the document root. Because
41 * paths need to be normalized for this to work, use "//" to substitute
42 * the doc root in the link paths (the array keys). E.g.:
44 * array('//symlink' => '/real/target/path') // unix
45 * array('//static' => 'D:\\staticStorage') // Windows
50 public static function rewrite($css, $currentDir, $docRoot = null, $symlinks = array())
52 self::$_docRoot = self::_realpath(
53 $docRoot ? $docRoot : $_SERVER['DOCUMENT_ROOT']
55 self::$_currentDir = self::_realpath($currentDir);
56 self::$_symlinks = array();
59 foreach ($symlinks as $link => $target) {
60 $link = ($link === '//')
62 : str_replace('//', self::$_docRoot . '/', $link);
63 $link = strtr($link, '/', DIRECTORY_SEPARATOR);
64 self::$_symlinks[$link] = self::_realpath($target);
67 self::$debugText .= "docRoot : " . self::$_docRoot . "\n"
68 . "currentDir : " . self::$_currentDir . "\n";
69 if (self::$_symlinks) {
70 self::$debugText .= "symlinks : " . var_export(self::$_symlinks, 1) . "\n";
72 self::$debugText .= "\n";
74 $css = self::_trimUrls($css);
77 $css = preg_replace_callback('/@import\\s+([\'"])(.*?)[\'"]/'
78 ,array(self::$className, '_processUriCB'), $css);
79 $css = preg_replace_callback('/url\\(\\s*([^\\)\\s]+)\\s*\\)/'
80 ,array(self::$className, '_processUriCB'), $css);
86 * Prepend a path to relative URIs in CSS files
90 * @param string $path The path to prepend.
94 public static function prepend($css, $path)
96 self::$_prependPath = $path;
98 $css = self::_trimUrls($css);
101 $css = preg_replace_callback('/@import\\s+([\'"])(.*?)[\'"]/'
102 ,array(self::$className, '_processUriCB'), $css);
103 $css = preg_replace_callback('/url\\(\\s*([^\\)\\s]+)\\s*\\)/'
104 ,array(self::$className, '_processUriCB'), $css);
106 self::$_prependPath = null;
112 * @var string directory of this stylesheet
114 private static $_currentDir = '';
117 * @var string DOC_ROOT
119 private static $_docRoot = '';
122 * @var array directory replacements to map symlink targets back to their
\r
123 * source (within the document root) E.g. '/var/www/symlink' => '/var/realpath'
\r
125 private static $_symlinks = array();
128 * @var string path to prepend
130 private static $_prependPath = null;
132 private static function _trimUrls($css)
134 return preg_replace('/
137 ([^\\)]+?) # 1 = URI (assuming does not contain ")")
140 /x', 'url($1)', $css);
143 private static function _processUriCB($m)
145 // $m matched either '/@import\\s+([\'"])(.*?)[\'"]/' or '/url\\(\\s*([^\\)\\s]+)\\s*\\)/'
146 $isImport = ($m[0][0] === '@');
147 // determine URI and the quote character (if any)
152 // $m[1] is either quoted or not
153 $quoteChar = ($m[1][0] === "'" || $m[1][0] === '"')
156 $uri = ($quoteChar === '')
158 : substr($m[1], 1, strlen($m[1]) - 2);
161 if ('/' !== $uri[0] // root-relative
162 && false === strpos($uri, '//') // protocol (non-data)
163 && 0 !== strpos($uri, 'data:') // data protocol
165 // URI is file-relative: rewrite depending on options
166 $uri = (self::$_prependPath !== null)
167 ? (self::$_prependPath . $uri)
168 : self::rewriteRelative($uri, self::$_currentDir, self::$_docRoot, self::$_symlinks);
171 ? "@import {$quoteChar}{$uri}{$quoteChar}"
172 : "url({$quoteChar}{$uri}{$quoteChar})";
176 * Rewrite a file relative URI as root relative
179 * Minify_CSS_UriRewriter::rewriteRelative(
181 * , '/home/user/www/css' // path of CSS file
182 * , '/home/user/www' // doc root
184 * // returns '/img/hello.gif'
186 * // example where static files are stored in a symlinked directory
187 * Minify_CSS_UriRewriter::rewriteRelative(
189 * , '/var/staticFiles/theme'
191 * , array('/home/user/www/static' => '/var/staticFiles')
193 * // returns '/static/theme/hello.gif'
196 * @param string $uri file relative URI
198 * @param string $realCurrentDir realpath of the current file's directory.
200 * @param string $realDocRoot realpath of the site document root.
202 * @param array $symlinks (default = array()) If the file is stored in
203 * a symlink-ed directory, provide an array of link paths to
204 * real target paths, where the link paths "appear" to be within the document
207 * array('/home/foo/www/not/real/path' => '/real/target/path') // unix
208 * array('C:\\htdocs\\not\\real' => 'D:\\real\\target\\path') // Windows
213 public static function rewriteRelative($uri, $realCurrentDir, $realDocRoot, $symlinks = array())
215 // prepend path with current dir separator (OS-independent)
216 $path = strtr($realCurrentDir, '/', DIRECTORY_SEPARATOR)
217 . DIRECTORY_SEPARATOR . strtr($uri, '/', DIRECTORY_SEPARATOR);
219 self::$debugText .= "file-relative URI : {$uri}\n"
220 . "path prepended : {$path}\n";
222 // "unresolve" a symlink back to doc root
\r
223 foreach ($symlinks as $link => $target) {
\r
224 if (0 === strpos($path, $target)) {
\r
225 // replace $target with $link
\r
226 $path = $link . substr($path, strlen($target));
228 self::$debugText .= "symlink unresolved : {$path}\n";
\r
234 $path = substr($path, strlen($realDocRoot));
236 self::$debugText .= "docroot stripped : {$path}\n";
238 // fix to root-relative URI
240 $uri = strtr($path, '/\\', '//');
242 // remove /./ and /../ where possible
243 $uri = str_replace('/./', '/', $uri);
244 // inspired by patch from Oleg Cherniy
246 $uri = preg_replace('@/[^/]+/\\.\\./@', '/', $uri, 1, $changed);
249 self::$debugText .= "traversals removed : {$uri}\n\n";
255 * Get realpath with any trailing slash removed. If realpath() fails,
256 * just remove the trailing slash.
258 * @param string $path
260 * @return mixed path with no trailing slash
262 protected static function _realpath($path)
264 $realPath = realpath($path);
265 if ($realPath !== false) {
268 return rtrim($path, '/\\');