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