initial commit
[namibia] / public / min / lib / Minify / HTML / Helper.php
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  */
13 class 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 }