initial commit
[namibia] / public / min / lib / Minify / Controller / Base.php
1 <?php
2 /**
3  * Class Minify_Controller_Base  
4  * @package Minify
5  */
6
7 /**
8  * Base class for Minify controller
9  * 
10  * The controller class validates a request and uses it to create sources
11  * for minification and set options like contentType. It's also responsible
12  * for loading minifier code upon request.
13  * 
14  * @package Minify
15  * @author Stephen Clay <steve@mrclay.org>
16  */
17 abstract class Minify_Controller_Base {
18     
19     /**
20      * Setup controller sources and set an needed options for Minify::source
21      * 
22      * You must override this method in your subclass controller to set 
23      * $this->sources. If the request is NOT valid, make sure $this->sources 
24      * is left an empty array. Then strip any controller-specific options from 
25      * $options and return it. To serve files, $this->sources must be an array of
26      * Minify_Source objects.
27      * 
28      * @param array $options controller and Minify options
29      * 
30      * @return array $options Minify::serve options
31      */
32     abstract public function setupSources($options);
33     
34     /**
35      * Get default Minify options for this controller.
36      * 
37      * Override in subclass to change defaults
38      *
39      * @return array options for Minify
40      */
41     public function getDefaultMinifyOptions() {
42         return array(
43             'isPublic' => true
44             ,'encodeOutput' => function_exists('gzdeflate')
45             ,'encodeMethod' => null // determine later
46             ,'encodeLevel' => 9
47             ,'minifierOptions' => array() // no minifier options
48             ,'contentTypeCharset' => 'utf-8'
49             ,'maxAge' => 1800 // 30 minutes
50             ,'rewriteCssUris' => true
51             ,'bubbleCssImports' => false
52             ,'quiet' => false // serve() will send headers and output
53             ,'debug' => false
54             
55             // if you override these, the response codes MUST be directly after
56             // the first space.
57             ,'badRequestHeader' => 'HTTP/1.0 400 Bad Request'
58             ,'errorHeader'      => 'HTTP/1.0 500 Internal Server Error'
59             
60             // callback function to see/modify content of all sources
61             ,'postprocessor' => null
62             // file to require to load preprocessor
63             ,'postprocessorRequire' => null
64         );
65     }  
66
67     /**
68      * Get default minifiers for this controller.
69      * 
70      * Override in subclass to change defaults
71      *
72      * @return array minifier callbacks for common types
73      */
74     public function getDefaultMinifers() {
75         $ret[Minify::TYPE_JS] = array('JSMin', 'minify');
76         $ret[Minify::TYPE_CSS] = array('Minify_CSS', 'minify');
77         $ret[Minify::TYPE_HTML] = array('Minify_HTML', 'minify');
78         return $ret;
79     }
80     
81     /**
82      * Is a user-given file within an allowable directory, existing,
83      * and having an extension js/css/html/txt ?
84      * 
85      * This is a convenience function for controllers that have to accept
86      * user-given paths
87      *
88      * @param string $file full file path (already processed by realpath())
89      * 
90      * @param array $safeDirs directories where files are safe to serve. Files can also
91      * be in subdirectories of these directories.
92      * 
93      * @return bool file is safe
94      *
95      * @deprecated use checkAllowDirs, checkNotHidden instead
96      */
97     public static function _fileIsSafe($file, $safeDirs)
98     {
99         $pathOk = false;
100         foreach ((array)$safeDirs as $safeDir) {
101             if (strpos($file, $safeDir) === 0) {
102                 $pathOk = true;
103                 break;
104             }
105         }
106         $base = basename($file);
107         if (! $pathOk || ! is_file($file) || $base[0] === '.') {
108             return false;
109         }
110         list($revExt) = explode('.', strrev($base));
111         return in_array(strrev($revExt), array('js', 'css', 'html', 'txt'));
112     }
113
114     /**
115      * @param string $file
116      * @param array $allowDirs
117      * @param string $uri
118      * @return bool
119      * @throws Exception
120      */
121     public static function checkAllowDirs($file, $allowDirs, $uri)
122     {
123         foreach ((array)$allowDirs as $allowDir) {
124             if (strpos($file, $allowDir) === 0) {
125                 return true;
126             }
127         }
128         throw new Exception("File '$file' is outside \$allowDirs. If the path is"
129             . " resolved via an alias/symlink, look into the \$min_symlinks option."
130             . " E.g. \$min_symlinks['/" . dirname($uri) . "'] = '" . dirname($file) . "';");
131     }
132
133     /**
134      * @param string $file
135      * @throws Exception
136      */
137     public static function checkNotHidden($file)
138     {
139         $b = basename($file);
140         if (0 === strpos($b, '.')) {
141             throw new Exception("Filename '$b' starts with period (may be hidden)");
142         }
143     }
144
145     /**
146      * instances of Minify_Source, which provide content and any individual minification needs.
147      *
148      * @var array
149      * 
150      * @see Minify_Source
151      */
152     public $sources = array();
153     
154     /**
155      * Short name to place inside cache id
156      *
157      * The setupSources() method may choose to set this, making it easier to
158      * recognize a particular set of sources/settings in the cache folder. It
159      * will be filtered and truncated to make the final cache id <= 250 bytes.
160      * 
161      * @var string
162      */
163     public $selectionId = '';
164
165     /**
166      * Mix in default controller options with user-given options
167      * 
168      * @param array $options user options
169      * 
170      * @return array mixed options
171      */
172     public final function mixInDefaultOptions($options)
173     {
174         $ret = array_merge(
175             $this->getDefaultMinifyOptions(), $options
176         );
177         if (! isset($options['minifiers'])) {
178             $options['minifiers'] = array();
179         }
180         $ret['minifiers'] = array_merge(
181             $this->getDefaultMinifers(), $options['minifiers']
182         );
183         return $ret;
184     }
185     
186     /**
187      * Analyze sources (if there are any) and set $options 'contentType' 
188      * and 'lastModifiedTime' if they already aren't.
189      * 
190      * @param array $options options for Minify
191      * 
192      * @return array options for Minify
193      */
194     public final function analyzeSources($options = array()) 
195     {
196         if ($this->sources) {
197             if (! isset($options['contentType'])) {
198                 $options['contentType'] = Minify_Source::getContentType($this->sources);
199             }
200             // last modified is needed for caching, even if setExpires is set
201             if (! isset($options['lastModifiedTime'])) {
202                 $max = 0;
203                 foreach ($this->sources as $source) {
204                     $max = max($source->lastModified, $max);
205                 }
206                 $options['lastModifiedTime'] = $max;
207             }    
208         }
209         return $options;
210     }
211
212     /**
213      * Send message to the Minify logger
214      *
215      * @param string $msg
216      *
217      * @return null
218      */
219     public function log($msg) {
220         Minify_Logger::log($msg);
221     }
222 }