PageRenderTime 43ms CodeModel.GetById 15ms RepoModel.GetById 0ms app.codeStats 0ms

/lib/minify/lib/Minify/ImportProcessor.php

https://github.com/ndenmeade/moodle
PHP | 216 lines | 142 code | 20 blank | 54 comment | 21 complexity | 075c561afa4825021fa44c3cac68ab94 MD5 | raw file
Possible License(s): Apache-2.0, GPL-3.0, LGPL-2.1, BSD-3-Clause
  1. <?php
  2. /**
  3. * Class Minify_ImportProcessor
  4. * @package Minify
  5. */
  6. /**
  7. * Linearize a CSS/JS file by including content specified by CSS import
  8. * declarations. In CSS files, relative URIs are fixed.
  9. *
  10. * @imports will be processed regardless of where they appear in the source
  11. * files; i.e. @imports commented out or in string content will still be
  12. * processed!
  13. *
  14. * This has a unit test but should be considered "experimental".
  15. *
  16. * @package Minify
  17. * @author Stephen Clay <steve@mrclay.org>
  18. * @author Simon Schick <simonsimcity@gmail.com>
  19. */
  20. class Minify_ImportProcessor {
  21. public static $filesIncluded = array();
  22. public static function process($file)
  23. {
  24. self::$filesIncluded = array();
  25. self::$_isCss = (strtolower(substr($file, -4)) === '.css');
  26. $obj = new Minify_ImportProcessor(dirname($file));
  27. return $obj->_getContent($file);
  28. }
  29. // allows callback funcs to know the current directory
  30. private $_currentDir = null;
  31. // allows callback funcs to know the directory of the file that inherits this one
  32. private $_previewsDir = null;
  33. // allows _importCB to write the fetched content back to the obj
  34. private $_importedContent = '';
  35. private static $_isCss = null;
  36. /**
  37. * @param String $currentDir
  38. * @param String $previewsDir Is only used internally
  39. */
  40. private function __construct($currentDir, $previewsDir = "")
  41. {
  42. $this->_currentDir = $currentDir;
  43. $this->_previewsDir = $previewsDir;
  44. }
  45. private function _getContent($file, $is_imported = false)
  46. {
  47. $file = realpath($file);
  48. if (! $file
  49. || in_array($file, self::$filesIncluded)
  50. || false === ($content = @file_get_contents($file))
  51. ) {
  52. // file missing, already included, or failed read
  53. return '';
  54. }
  55. self::$filesIncluded[] = realpath($file);
  56. $this->_currentDir = dirname($file);
  57. // remove UTF-8 BOM if present
  58. if (pack("CCC",0xef,0xbb,0xbf) === substr($content, 0, 3)) {
  59. $content = substr($content, 3);
  60. }
  61. // ensure uniform EOLs
  62. $content = str_replace("\r\n", "\n", $content);
  63. // process @imports
  64. $content = preg_replace_callback(
  65. '/
  66. @import\\s+
  67. (?:url\\(\\s*)? # maybe url(
  68. [\'"]? # maybe quote
  69. (.*?) # 1 = URI
  70. [\'"]? # maybe end quote
  71. (?:\\s*\\))? # maybe )
  72. ([a-zA-Z,\\s]*)? # 2 = media list
  73. ; # end token
  74. /x'
  75. ,array($this, '_importCB')
  76. ,$content
  77. );
  78. // You only need to rework the import-path if the script is imported
  79. if (self::$_isCss && $is_imported) {
  80. // rewrite remaining relative URIs
  81. $content = preg_replace_callback(
  82. '/url\\(\\s*([^\\)\\s]+)\\s*\\)/'
  83. ,array($this, '_urlCB')
  84. ,$content
  85. );
  86. }
  87. return $this->_importedContent . $content;
  88. }
  89. private function _importCB($m)
  90. {
  91. $url = $m[1];
  92. $mediaList = preg_replace('/\\s+/', '', $m[2]);
  93. if (strpos($url, '://') > 0) {
  94. // protocol, leave in place for CSS, comment for JS
  95. return self::$_isCss
  96. ? $m[0]
  97. : "/* Minify_ImportProcessor will not include remote content */";
  98. }
  99. if ('/' === $url[0]) {
  100. // protocol-relative or root path
  101. $url = ltrim($url, '/');
  102. $file = realpath($_SERVER['DOCUMENT_ROOT']) . DIRECTORY_SEPARATOR
  103. . strtr($url, '/', DIRECTORY_SEPARATOR);
  104. } else {
  105. // relative to current path
  106. $file = $this->_currentDir . DIRECTORY_SEPARATOR
  107. . strtr($url, '/', DIRECTORY_SEPARATOR);
  108. }
  109. $obj = new Minify_ImportProcessor(dirname($file), $this->_currentDir);
  110. $content = $obj->_getContent($file, true);
  111. if ('' === $content) {
  112. // failed. leave in place for CSS, comment for JS
  113. return self::$_isCss
  114. ? $m[0]
  115. : "/* Minify_ImportProcessor could not fetch '{$file}' */";
  116. }
  117. return (!self::$_isCss || preg_match('@(?:^$|\\ball\\b)@', $mediaList))
  118. ? $content
  119. : "@media {$mediaList} {\n{$content}\n}\n";
  120. }
  121. private function _urlCB($m)
  122. {
  123. // $m[1] is either quoted or not
  124. $quote = ($m[1][0] === "'" || $m[1][0] === '"')
  125. ? $m[1][0]
  126. : '';
  127. $url = ($quote === '')
  128. ? $m[1]
  129. : substr($m[1], 1, strlen($m[1]) - 2);
  130. if ('/' !== $url[0]) {
  131. if (strpos($url, '//') > 0) {
  132. // probably starts with protocol, do not alter
  133. } else {
  134. // prepend path with current dir separator (OS-independent)
  135. $path = $this->_currentDir
  136. . DIRECTORY_SEPARATOR . strtr($url, '/', DIRECTORY_SEPARATOR);
  137. // update the relative path by the directory of the file that imported this one
  138. $url = self::getPathDiff(realpath($this->_previewsDir), $path);
  139. }
  140. }
  141. return "url({$quote}{$url}{$quote})";
  142. }
  143. /**
  144. * @param string $from
  145. * @param string $to
  146. * @param string $ps
  147. * @return string
  148. */
  149. private function getPathDiff($from, $to, $ps = DIRECTORY_SEPARATOR)
  150. {
  151. $realFrom = $this->truepath($from);
  152. $realTo = $this->truepath($to);
  153. $arFrom = explode($ps, rtrim($realFrom, $ps));
  154. $arTo = explode($ps, rtrim($realTo, $ps));
  155. while (count($arFrom) && count($arTo) && ($arFrom[0] == $arTo[0]))
  156. {
  157. array_shift($arFrom);
  158. array_shift($arTo);
  159. }
  160. return str_pad("", count($arFrom) * 3, '..' . $ps) . implode($ps, $arTo);
  161. }
  162. /**
  163. * This function is to replace PHP's extremely buggy realpath().
  164. * @param string $path The original path, can be relative etc.
  165. * @return string The resolved path, it might not exist.
  166. * @see http://stackoverflow.com/questions/4049856/replace-phps-realpath
  167. */
  168. function truepath($path)
  169. {
  170. // whether $path is unix or not
  171. $unipath = strlen($path) == 0 || $path{0} != '/';
  172. // attempts to detect if path is relative in which case, add cwd
  173. if (strpos($path, ':') === false && $unipath)
  174. $path = $this->_currentDir . DIRECTORY_SEPARATOR . $path;
  175. // resolve path parts (single dot, double dot and double delimiters)
  176. $path = str_replace(array('/', '\\'), DIRECTORY_SEPARATOR, $path);
  177. $parts = array_filter(explode(DIRECTORY_SEPARATOR, $path), 'strlen');
  178. $absolutes = array();
  179. foreach ($parts as $part) {
  180. if ('.' == $part)
  181. continue;
  182. if ('..' == $part) {
  183. array_pop($absolutes);
  184. } else {
  185. $absolutes[] = $part;
  186. }
  187. }
  188. $path = implode(DIRECTORY_SEPARATOR, $absolutes);
  189. // resolve any symlinks
  190. if (file_exists($path) && linkinfo($path) > 0)
  191. $path = readlink($path);
  192. // put initial separator that could have been lost
  193. $path = !$unipath ? '/' . $path : $path;
  194. return $path;
  195. }
  196. }