PageRenderTime 52ms CodeModel.GetById 30ms RepoModel.GetById 0ms app.codeStats 0ms

/classes/casset/cssurirewriter.php

https://github.com/studiofrenetic/fuel-casset
PHP | 285 lines | 138 code | 29 blank | 118 comment | 12 complexity | 4ee01785b410a20c08a797a2e0261a12 MD5 | raw file
Possible License(s): BSD-3-Clause
  1. <?php
  2. /**
  3. * Rewrite file-relative URIs as root-relative in CSS files
  4. *
  5. * @package Minify
  6. * @author Stephen Clay <steve@mrclay.org>
  7. */
  8. /**
  9. * This library is used as part of Casset.
  10. *
  11. * @package Casset
  12. * @version v1.19
  13. * @author Antony Male
  14. * @license MIT License
  15. * @link http://github.com/canton7/fuelphp-casset
  16. */
  17. namespace Casset;
  18. class Casset_Cssurirewriter {
  19. /**
  20. * rewrite() and rewriteRelative() append debugging information here
  21. * @var string
  22. */
  23. public static $debugText = '';
  24. /**
  25. * In CSS content, rewrite file relative URIs as root relative
  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. * @param array $symlinks (default = array()) If the CSS file is stored in
  35. * a symlink-ed directory, provide an array of link paths to
  36. * target paths, where the link paths are within the document root. Because
  37. * paths need to be normalized for this to work, use "//" to substitute
  38. * the doc root in the link paths (the array keys). E.g.:
  39. * <code>
  40. * array('//symlink' => '/real/target/path') // unix
  41. * array('//static' => 'D:\\staticStorage') // Windows
  42. * </code>
  43. *
  44. * @return string
  45. */
  46. public static function rewrite($css, $currentDir, $docRoot = null, $symlinks = array()) {
  47. self::$_docRoot = self::_realpath(
  48. $docRoot ? $docRoot : $_SERVER['DOCUMENT_ROOT']
  49. );
  50. self::$_currentDir = self::_realpath($currentDir);
  51. self::$_symlinks = array();
  52. // normalize symlinks
  53. foreach ($symlinks as $link => $target) {
  54. $link = ($link === '//') ? self::$_docRoot : str_replace('//', self::$_docRoot . '/', $link);
  55. $link = strtr($link, '/', DIRECTORY_SEPARATOR);
  56. self::$_symlinks[$link] = self::_realpath($target);
  57. }
  58. self::$debugText .= "docRoot : " . self::$_docRoot . "\n"
  59. . "currentDir : " . self::$_currentDir . "\n";
  60. if (self::$_symlinks) {
  61. self::$debugText .= "symlinks : " . var_export(self::$_symlinks, 1) . "\n";
  62. }
  63. self::$debugText .= "\n";
  64. $css = self::_trimUrls($css);
  65. // rewrite
  66. $css = preg_replace_callback('/@import\\s+([\'"])(.*?)[\'"]/'
  67. , array(self::$className, '_processUriCB'), $css);
  68. $css = preg_replace_callback('/url\\(\\s*([^\\)\\s]+)\\s*\\)/'
  69. , array(self::$className, '_processUriCB'), $css);
  70. return $css;
  71. }
  72. /**
  73. * In CSS content, prepend a path to relative URIs
  74. *
  75. * @param string $css
  76. *
  77. * @param string $path The path to prepend.
  78. *
  79. * @return string
  80. */
  81. public static function prepend($css, $path) {
  82. self::$_prependPath = $path;
  83. $css = self::_trimUrls($css);
  84. // append
  85. $css = preg_replace_callback('/@import\\s+([\'"])(.*?)[\'"]/'
  86. , array(self::$className, '_processUriCB'), $css);
  87. $css = preg_replace_callback('/url\\(\\s*([^\\)\\s]+)\\s*\\)/'
  88. , array(self::$className, '_processUriCB'), $css);
  89. self::$_prependPath = null;
  90. return $css;
  91. }
  92. /**
  93. * Get a root relative URI from a file relative URI
  94. *
  95. * <code>
  96. * Minify_CSS_UriRewriter::rewriteRelative(
  97. * '../img/hello.gif'
  98. * , '/home/user/www/css' // path of CSS file
  99. * , '/home/user/www' // doc root
  100. * );
  101. * // returns '/img/hello.gif'
  102. *
  103. * // example where static files are stored in a symlinked directory
  104. * Minify_CSS_UriRewriter::rewriteRelative(
  105. * 'hello.gif'
  106. * , '/var/staticFiles/theme'
  107. * , '/home/user/www'
  108. * , array('/home/user/www/static' => '/var/staticFiles')
  109. * );
  110. * // returns '/static/theme/hello.gif'
  111. * </code>
  112. *
  113. * @param string $uri file relative URI
  114. *
  115. * @param string $realCurrentDir realpath of the current file's directory.
  116. *
  117. * @param string $realDocRoot realpath of the site document root.
  118. *
  119. * @param array $symlinks (default = array()) If the file is stored in
  120. * a symlink-ed directory, provide an array of link paths to
  121. * real target paths, where the link paths "appear" to be within the document
  122. * root. E.g.:
  123. * <code>
  124. * array('/home/foo/www/not/real/path' => '/real/target/path') // unix
  125. * array('C:\\htdocs\\not\\real' => 'D:\\real\\target\\path') // Windows
  126. * </code>
  127. *
  128. * @return string
  129. */
  130. public static function rewriteRelative($uri, $realCurrentDir, $realDocRoot, $symlinks = array()) {
  131. // prepend path with current dir separator (OS-independent)
  132. $path = strtr($realCurrentDir, '/', DIRECTORY_SEPARATOR)
  133. . DIRECTORY_SEPARATOR . strtr($uri, '/', DIRECTORY_SEPARATOR);
  134. self::$debugText .= "file-relative URI : {$uri}\n"
  135. . "path prepended : {$path}\n";
  136. // "unresolve" a symlink back to doc root
  137. foreach ($symlinks as $link => $target) {
  138. if (0 === strpos($path, $target)) {
  139. // replace $target with $link
  140. $path = $link . substr($path, strlen($target));
  141. self::$debugText .= "symlink unresolved : {$path}\n";
  142. break;
  143. }
  144. }
  145. // strip doc root
  146. $path = substr($path, strlen($realDocRoot));
  147. self::$debugText .= "docroot stripped : {$path}\n";
  148. // fix to root-relative URI
  149. $uri = strtr($path, '/\\', '//');
  150. $uri = self::removeDots($uri);
  151. self::$debugText .= "traversals removed : {$uri}\n\n";
  152. return $uri;
  153. }
  154. /**
  155. * Remove instances of "./" and "../" where possible from a root-relative URI
  156. * @param string $uri
  157. * @return string
  158. */
  159. public static function removeDots($uri) {
  160. $uri = str_replace('/./', '/', $uri);
  161. // inspired by patch from Oleg Cherniy
  162. do {
  163. $uri = preg_replace('@/[^/]+/\\.\\./@', '/', $uri, 1, $changed);
  164. } while ($changed);
  165. return $uri;
  166. }
  167. /**
  168. * Defines which class to call as part of callbacks, change this
  169. * if you extend Minify_CSS_UriRewriter
  170. * @var string
  171. */
  172. protected static $className = 'Casset_Cssurirewriter';
  173. /**
  174. * Get realpath with any trailing slash removed. If realpath() fails,
  175. * just remove the trailing slash.
  176. *
  177. * @param string $path
  178. *
  179. * @return mixed path with no trailing slash
  180. */
  181. protected static function _realpath($path) {
  182. $realPath = realpath($path);
  183. if ($realPath !== false) {
  184. $path = $realPath;
  185. }
  186. return rtrim($path, '/\\');
  187. }
  188. /**
  189. * @var string directory of this stylesheet
  190. */
  191. private static $_currentDir = '';
  192. /**
  193. * @var string DOC_ROOT
  194. */
  195. private static $_docRoot = '';
  196. /**
  197. * @var array directory replacements to map symlink targets back to their
  198. * source (within the document root) E.g. '/var/www/symlink' => '/var/realpath'
  199. */
  200. private static $_symlinks = array();
  201. /**
  202. * @var string path to prepend
  203. */
  204. private static $_prependPath = null;
  205. private static function _trimUrls($css) {
  206. return preg_replace('/
  207. url\\( # url(
  208. \\s*
  209. ([^\\)]+?) # 1 = URI (assuming does not contain ")")
  210. \\s*
  211. \\) # )
  212. /x', 'url($1)', $css);
  213. }
  214. private static function _processUriCB($m) {
  215. // $m matched either '/@import\\s+([\'"])(.*?)[\'"]/' or '/url\\(\\s*([^\\)\\s]+)\\s*\\)/'
  216. $isImport = ($m[0][0] === '@');
  217. // determine URI and the quote character (if any)
  218. if ($isImport) {
  219. $quoteChar = $m[1];
  220. $uri = $m[2];
  221. } else {
  222. // $m[1] is either quoted or not
  223. $quoteChar = ($m[1][0] === "'" || $m[1][0] === '"') ? $m[1][0] : '';
  224. $uri = ($quoteChar === '') ? $m[1] : substr($m[1], 1, strlen($m[1]) - 2);
  225. }
  226. // analyze URI
  227. if ('/' !== $uri[0] // root-relative
  228. && false === strpos($uri, '//') // protocol (non-data)
  229. && 0 !== strpos($uri, 'data:') // data protocol
  230. ) {
  231. // URI is file-relative: rewrite depending on options
  232. if (self::$_prependPath === null) {
  233. $uri = self::rewriteRelative($uri, self::$_currentDir, self::$_docRoot, self::$_symlinks);
  234. } else {
  235. $uri = self::$_prependPath . $uri;
  236. if ($uri[0] === '/') {
  237. $root = '';
  238. $rootRelative = $uri;
  239. $uri = $root . self::removeDots($rootRelative);
  240. } elseif (preg_match('@^((https?\:)?//([^/]+))/@', $uri, $m) && (false !== strpos($m[3], '.'))) {
  241. $root = $m[1];
  242. $rootRelative = substr($uri, strlen($root));
  243. $uri = $root . self::removeDots($rootRelative);
  244. }
  245. }
  246. }
  247. return $isImport ? "@import {$quoteChar}{$uri}{$quoteChar}" : "url({$quoteChar}{$uri}{$quoteChar})";
  248. }
  249. }
  250. /* End of file casset/cssurirewriter.php */