PageRenderTime 42ms CodeModel.GetById 14ms RepoModel.GetById 0ms app.codeStats 0ms

/library/vendors/Minify/lib/Minify/CSS/UriRewriter.php

http://github.com/vanillaforums/Garden
PHP | 305 lines | 158 code | 41 blank | 106 comment | 15 complexity | e32f41b2b93fde1cc4bf5768b492a5a6 MD5 | raw file
Possible License(s): LGPL-2.1, GPL-2.0, AGPL-1.0, BSD-3-Clause, MIT
  1. <?php
  2. /**
  3. * Class Minify_CSS_UriRewriter
  4. * @package Minify
  5. */
  6. /**
  7. * Rewrite file-relative URIs as root-relative in CSS files
  8. *
  9. * @package Minify
  10. * @author Stephen Clay <steve@mrclay.org>
  11. */
  12. class Minify_CSS_UriRewriter {
  13. /**
  14. * Defines which class to call as part of callbacks, change this
  15. * if you extend Minify_CSS_UriRewriter
  16. * @var string
  17. */
  18. protected static $className = 'Minify_CSS_UriRewriter';
  19. /**
  20. * rewrite() and rewriteRelative() append debugging information here
  21. * @var string
  22. */
  23. public static $debugText = '';
  24. /**
  25. * Rewrite file relative URIs as root relative in CSS files
  26. *
  27. * @param string $css
  28. *
  29. * @param string $currentDir The directory of the current CSS file.
  30. *
  31. * @param string $docRoot The document root of the web site in which
  32. * the CSS file resides (default = $_SERVER['DOCUMENT_ROOT']).
  33. *
  34. * @return string
  35. */
  36. public static function rewrite($css, $currentDir, $docRoot = null, $prependPath = null)
  37. {
  38. self::$_docRoot = self::_realpath(
  39. $docRoot ? $docRoot : $_SERVER['DOCUMENT_ROOT']
  40. );
  41. self::$_currentDir = self::_realpath($currentDir);
  42. self::$_prependPath = $prependPath ? $prependPath : '';
  43. self::$debugText .= "docRoot : " . self::$_docRoot . "\n"
  44. . "currentDir : " . self::$_currentDir . "\n";
  45. // if (self::$_symlinks) {
  46. // self::$debugText .= "symlinks : " . var_export(self::$_symlinks, 1) . "\n";
  47. // }
  48. self::$debugText .= "\n";
  49. $css = self::_trimUrls($css);
  50. // rewrite
  51. $css = preg_replace_callback('/@import\s+(?:url)(.+);/'
  52. ,array(self::$className, '_processImportUriCB'), $css);
  53. $css = preg_replace_callback('/url\\(\\s*([^\\)\\s]+)\\s*\\)/'
  54. ,array(self::$className, '_processUriCB'), $css);
  55. return $css;
  56. }
  57. /**
  58. * Prepend a path to relative URIs in CSS files
  59. *
  60. * @param string $css
  61. *
  62. * @param string $path The path to prepend.
  63. *
  64. * @return string
  65. */
  66. public static function prepend($css, $path)
  67. {
  68. self::$_prependPath = $path;
  69. $css = self::_trimUrls($css);
  70. // append
  71. $css = preg_replace_callback('/@import\\s+([\'"])(.*?)[\'"]/'
  72. ,array(self::$className, '_processUriCB'), $css);
  73. $css = preg_replace_callback('/url\\(\\s*([^\\)\\s]+)\\s*\\)/'
  74. ,array(self::$className, '_processUriCB'), $css);
  75. self::$_prependPath = null;
  76. return $css;
  77. }
  78. /**
  79. * @var string directory of this stylesheet
  80. */
  81. private static $_currentDir = '';
  82. /**
  83. * @var string DOC_ROOT
  84. */
  85. private static $_docRoot = '';
  86. /**
  87. * @var array directory replacements to map symlink targets back to their
  88. * source (within the document root) E.g. '/var/www/symlink' => '/var/realpath'
  89. */
  90. // private static $_symlinks = array();
  91. /**
  92. * @var string path to prepend
  93. */
  94. private static $_prependPath = null;
  95. private static function _trimUrls($css)
  96. {
  97. return preg_replace('/
  98. url\\( # url(
  99. \\s*
  100. ([^\\)]+?) # 1 = URI (assuming does not contain ")")
  101. \\s*
  102. \\) # )
  103. /x', 'url($1)', $css);
  104. }
  105. private static function _processImportUriCB($m)
  106. {
  107. $uri = trim($m[1], '()"\' ');
  108. // We want to grab the import.
  109. if (strpos($uri, '//') !== false) {
  110. $path = $uri;
  111. } elseif ($uri[0] == '/') {
  112. $path = self::_realpath(self::$_docRoot, $uri);
  113. } else {
  114. $path = realpath2(self::$_currentDir.'/'.trim($uri, '/\\'));
  115. if (substr_compare(self::$_docRoot, $path, 0, strlen($path)) != 0) {
  116. return "/* Error: $uri isn't in the webroot. */\n";
  117. } elseif (substr_compare($path, '.css', -4, 4, true) != 0) {
  118. return "/* Error: $uri must end in .css. */\n";
  119. }
  120. }
  121. $css = file_get_contents($path);
  122. // Not so fast, we've got to rewrite this file too. What's more, the current dir and path are different.
  123. $bak = array(self::$_currentDir, self::$_prependPath, self::$_docRoot, self::$debugText);
  124. self::$debugText = '';
  125. if (IsUrl($path)) {
  126. $newCurrentDir = $path;
  127. $newDocRoot = $path;
  128. } else {
  129. $newDocRoot = self::$_docRoot;
  130. $newCurrentDir = realpath2($currentDirBak.realpath2(dirname($uri)));
  131. }
  132. $css = self::rewrite($css, $newCurrentDir, $newDocRoot);
  133. list(self::$_currentDir, self::$_prependPath, self::$_docRoot, self::$debugText) = $bak;
  134. return "/* @include url('$uri'); */\n".
  135. $css;
  136. }
  137. private static function _processUriCB($m)
  138. {
  139. // $m matched either '/@import\\s+([\'"])(.*?)[\'"]/' or '/url\\(\\s*([^\\)\\s]+)\\s*\\)/'
  140. $isImport = ($m[0][0] === '@');
  141. // determine URI and the quote character (if any)
  142. if ($isImport) {
  143. $quoteChar = $m[1];
  144. $uri = $m[2];
  145. } else {
  146. // $m[1] is either quoted or not
  147. $quoteChar = ($m[1][0] === "'" || $m[1][0] === '"')
  148. ? $m[1][0]
  149. : '';
  150. $uri = ($quoteChar === '')
  151. ? $m[1]
  152. : substr($m[1], 1, strlen($m[1]) - 2);
  153. }
  154. // analyze URI
  155. if ('/' !== $uri[0] // root-relative
  156. && false === strpos($uri, '//') // protocol (non-data)
  157. && 0 !== strpos($uri, 'data:') // data protocol
  158. ) {
  159. // URI is file-relative: rewrite depending on options
  160. if (self::$_prependPath !== null)
  161. $uri = self::$_prependPath . $uri;
  162. else
  163. $uri = self::rewriteRelative($uri, self::$_currentDir, self::$_docRoot);
  164. }
  165. $result = $isImport
  166. ? "@import {$quoteChar}{$uri}{$quoteChar}"
  167. : "url({$quoteChar}{$uri}{$quoteChar})";
  168. return $result;
  169. }
  170. /**
  171. * Rewrite a file relative URI as root relative
  172. *
  173. * <code>
  174. * Minify_CSS_UriRewriter::rewriteRelative(
  175. * '../img/hello.gif'
  176. * , '/home/user/www/css' // path of CSS file
  177. * , '/home/user/www' // doc root
  178. * );
  179. * // returns '/img/hello.gif'
  180. *
  181. * // example where static files are stored in a symlinked directory
  182. * Minify_CSS_UriRewriter::rewriteRelative(
  183. * 'hello.gif'
  184. * , '/var/staticFiles/theme'
  185. * , '/home/user/www'
  186. * , array('/home/user/www/static' => '/var/staticFiles')
  187. * );
  188. * // returns '/static/theme/hello.gif'
  189. * </code>
  190. *
  191. * @param string $uri file relative URI
  192. *
  193. * @param string $realCurrentDir realpath of the current file's directory.
  194. *
  195. * @param string $realDocRoot realpath of the site document root.
  196. *
  197. * @return string
  198. */
  199. public static function rewriteRelative($uri, $realCurrentDir, $realDocRoot)
  200. {
  201. // prepend path with current dir separator (OS-independent)
  202. $path = strtr($realCurrentDir, '/', DIRECTORY_SEPARATOR)
  203. . DIRECTORY_SEPARATOR . strtr($uri, '/', DIRECTORY_SEPARATOR);
  204. self::$debugText .= "file-relative URI : {$uri}\n"
  205. . "path prepended : {$path}\n";
  206. $path = substr($path, strlen($realDocRoot));
  207. self::$debugText .= "docroot stripped : {$path}\n";
  208. // fix to root-relative URI
  209. $uri = strtr($path, '/\\', '//');
  210. // remove /./ and /../ where possible
  211. $uri = str_replace('/./', '/', $uri);
  212. // inspired by patch from Oleg Cherniy
  213. do {
  214. $uri = preg_replace('@/[^/]+/\\.\\./@', '/', $uri, 1, $changed);
  215. } while ($changed);
  216. self::$debugText .= "traversals removed : {$uri}\n\n";
  217. return $uri;
  218. }
  219. /**
  220. * Get realpath with any trailing slash removed. If realpath() fails,
  221. * just remove the trailing slash.
  222. *
  223. * @param string $path
  224. *
  225. * @return mixed path with no trailing slash
  226. */
  227. protected static function _realpath($path)
  228. {
  229. $realPath = realpath2($path);
  230. if ($realPath !== false) {
  231. $path = $realPath;
  232. }
  233. return rtrim($path, '/\\');
  234. }
  235. }
  236. /**
  237. * A function similar to realpath, but it doesn't follow symlinks.
  238. * @param string $path The path to the file.
  239. * @return string
  240. */
  241. function realpath2($path) {
  242. if (substr($path, 0, 2) == '//' || strpos($path, '://'))
  243. return $path;
  244. $parts = explode('/', str_replace('\\', '/', $path));
  245. $result = array();
  246. foreach ($parts as $part) {
  247. if (!$part || $part == '.')
  248. continue;
  249. if ($part == '..')
  250. array_pop($result);
  251. else
  252. $result[] = $part;
  253. }
  254. $result = '/'.implode('/', $result);
  255. // Do a sanity check.
  256. if (realpath($result) != realpath($path))
  257. $result = realpath($path);
  258. return $result;
  259. }