Helper.php 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225
  1. <?php
  2. /**
  3. * Class Minify_HTML_Helper
  4. * @package Minify
  5. */
  6. /**
  7. * Helpers for writing Minfy URIs into HTML
  8. *
  9. * @package Minify
  10. * @author Stephen Clay <steve@mrclay.org>
  11. */
  12. class Minify_HTML_Helper {
  13. public $rewriteWorks = true;
  14. public $minAppUri = '/min';
  15. public $groupsConfigFile = '';
  16. /**
  17. * Get an HTML-escaped Minify URI for a group or set of files
  18. *
  19. * @param string|array $keyOrFiles a group key or array of filepaths/URIs
  20. * @param array $opts options:
  21. * 'farExpires' : (default true) append a modified timestamp for cache revving
  22. * 'debug' : (default false) append debug flag
  23. * 'charset' : (default 'UTF-8') for htmlspecialchars
  24. * 'minAppUri' : (default '/min') URI of min directory
  25. * 'rewriteWorks' : (default true) does mod_rewrite work in min app?
  26. * 'groupsConfigFile' : specify if different
  27. * @return string
  28. */
  29. public static function getUri($keyOrFiles, $opts = array())
  30. {
  31. $opts = array_merge(array( // default options
  32. 'farExpires' => true
  33. ,'debug' => false
  34. ,'charset' => 'UTF-8'
  35. ,'minAppUri' => '/min'
  36. ,'rewriteWorks' => true
  37. ,'groupsConfigFile' => ''
  38. ), $opts);
  39. $h = new self;
  40. $h->minAppUri = $opts['minAppUri'];
  41. $h->rewriteWorks = $opts['rewriteWorks'];
  42. $h->groupsConfigFile = $opts['groupsConfigFile'];
  43. if (is_array($keyOrFiles)) {
  44. $h->setFiles($keyOrFiles, $opts['farExpires']);
  45. } else {
  46. $h->setGroup($keyOrFiles, $opts['farExpires']);
  47. }
  48. $uri = $h->getRawUri($opts['farExpires'], $opts['debug']);
  49. return htmlspecialchars($uri, ENT_QUOTES, $opts['charset']);
  50. }
  51. /**
  52. * Get non-HTML-escaped URI to minify the specified files
  53. *
  54. * @param bool $farExpires
  55. * @param bool $debug
  56. * @return string
  57. */
  58. public function getRawUri($farExpires = true, $debug = false)
  59. {
  60. $path = rtrim($this->minAppUri, '/') . '/';
  61. if (! $this->rewriteWorks) {
  62. $path .= '?';
  63. }
  64. if (null === $this->_groupKey) {
  65. // @todo: implement shortest uri
  66. $path = self::_getShortestUri($this->_filePaths, $path);
  67. } else {
  68. $path .= "g=" . $this->_groupKey;
  69. }
  70. if ($debug) {
  71. $path .= "&debug";
  72. } elseif ($farExpires && $this->_lastModified) {
  73. $path .= "&" . $this->_lastModified;
  74. }
  75. return $path;
  76. }
  77. /**
  78. * Set the files that will comprise the URI we're building
  79. *
  80. * @param array $files
  81. * @param bool $checkLastModified
  82. */
  83. public function setFiles($files, $checkLastModified = true)
  84. {
  85. $this->_groupKey = null;
  86. if ($checkLastModified) {
  87. $this->_lastModified = self::getLastModified($files);
  88. }
  89. // normalize paths like in /min/f=<paths>
  90. foreach ($files as $k => $file) {
  91. if (0 === strpos($file, '//')) {
  92. $file = substr($file, 2);
  93. } elseif (0 === strpos($file, '/')
  94. || 1 === strpos($file, ':\\')) {
  95. $file = substr($file, strlen($_SERVER['DOCUMENT_ROOT']) + 1);
  96. }
  97. $file = strtr($file, '\\', '/');
  98. $files[$k] = $file;
  99. }
  100. $this->_filePaths = $files;
  101. }
  102. /**
  103. * Set the group of files that will comprise the URI we're building
  104. *
  105. * @param string $key
  106. * @param bool $checkLastModified
  107. */
  108. public function setGroup($key, $checkLastModified = true)
  109. {
  110. $this->_groupKey = $key;
  111. if ($checkLastModified) {
  112. if (! $this->groupsConfigFile) {
  113. $this->groupsConfigFile = dirname(dirname(dirname(dirname(__FILE__)))) . '/groupsConfig.php';
  114. }
  115. if (is_file($this->groupsConfigFile)) {
  116. $gc = (require $this->groupsConfigFile);
  117. $keys = explode(',', $key);
  118. foreach ($keys as $key) {
  119. if (isset($gc[$key])) {
  120. $this->_lastModified = self::getLastModified($gc[$key], $this->_lastModified);
  121. }
  122. }
  123. }
  124. }
  125. }
  126. /**
  127. * Get the max(lastModified) of all files
  128. *
  129. * @param array|string $sources
  130. * @param int $lastModified
  131. * @return int
  132. */
  133. public static function getLastModified($sources, $lastModified = 0)
  134. {
  135. $max = $lastModified;
  136. foreach ((array)$sources as $source) {
  137. if (is_object($source) && isset($source->lastModified)) {
  138. $max = max($max, $source->lastModified);
  139. } elseif (is_string($source)) {
  140. if (0 === strpos($source, '//')) {
  141. $source = $_SERVER['DOCUMENT_ROOT'] . substr($source, 1);
  142. }
  143. if (is_file($source)) {
  144. $max = max($max, filemtime($source));
  145. }
  146. }
  147. }
  148. return $max;
  149. }
  150. protected $_groupKey = null; // if present, URI will be like g=...
  151. protected $_filePaths = array();
  152. protected $_lastModified = null;
  153. /**
  154. * In a given array of strings, find the character they all have at
  155. * a particular index
  156. *
  157. * @param array $arr array of strings
  158. * @param int $pos index to check
  159. * @return mixed a common char or '' if any do not match
  160. */
  161. protected static function _getCommonCharAtPos($arr, $pos) {
  162. if (!isset($arr[0][$pos])) {
  163. return '';
  164. }
  165. $c = $arr[0][$pos];
  166. $l = count($arr);
  167. if ($l === 1) {
  168. return $c;
  169. }
  170. for ($i = 1; $i < $l; ++$i) {
  171. if ($arr[$i][$pos] !== $c) {
  172. return '';
  173. }
  174. }
  175. return $c;
  176. }
  177. /**
  178. * Get the shortest URI to minify the set of source files
  179. *
  180. * @param array $paths root-relative URIs of files
  181. * @param string $minRoot root-relative URI of the "min" application
  182. * @return string
  183. */
  184. protected static function _getShortestUri($paths, $minRoot = '/min/') {
  185. $pos = 0;
  186. $base = '';
  187. while (true) {
  188. $c = self::_getCommonCharAtPos($paths, $pos);
  189. if ($c === '') {
  190. break;
  191. } else {
  192. $base .= $c;
  193. }
  194. ++$pos;
  195. }
  196. $base = preg_replace('@[^/]+$@', '', $base);
  197. $uri = $minRoot . 'f=' . implode(',', $paths);
  198. if (substr($base, -1) === '/') {
  199. // we have a base dir!
  200. $basedPaths = $paths;
  201. $l = count($paths);
  202. for ($i = 0; $i < $l; ++$i) {
  203. $basedPaths[$i] = substr($paths[$i], strlen($base));
  204. }
  205. $base = substr($base, 0, strlen($base) - 1);
  206. $bUri = $minRoot . 'b=' . $base . '&f=' . implode(',', $basedPaths);
  207. $uri = strlen($uri) < strlen($bUri)
  208. ? $uri
  209. : $bUri;
  210. }
  211. return $uri;
  212. }
  213. }