Asset.php
[Pman.Core] / Asset.php
1 <?php
2  
3  /**
4   * Generic cached assset server... -- No security om this.. should only return compressed CSS/JS
5   *
6   * Does a few tricks with headers to improve caching...
7   *
8   *
9   * Also includes code to generate assets...
10   *
11   * methods outputJavascriptDir / outputCssDir generate links to 
12   *    BASEURL/Asset/css/xxxx.yyy.zzz
13   *    BASEURL/Asset/js/xxxx.yyy.zzz
14   *
15   *   then
16   *   we deliver the file from
17   *       SESSION-DIR/{$www-user}-{$ff->project}-$ff->version}-{js|css}-compile/{filename}/PATH';
18   *
19   *   
20   */
21  
22 require_once 'Pman.php';
23
24 class Pman_Core_Asset extends Pman {
25      
26     var $types = array(
27         'css' => 'text/css',
28         'js' => 'text/javascript',
29     );
30     
31     function getAuth()
32     {
33         return true;
34     }
35     
36     
37     function get($s='')
38     {
39        
40         $bits = explode('/', $s);
41         
42         if (empty($bits[0]) || empty($bits[1])  || !isset($this->types[$bits[0]])) {
43             $this->jerr("invalid url");
44         }
45        
46         $s = str_replace('/', '-', $bits[1]);
47         
48         $ui = posix_getpwuid(posix_geteuid());
49         $ff = HTML_FlexyFramework::get();
50         
51         $compile = session_save_path() . '/' .
52                 $ui['name'] . '-' . $ff->project . '-' . $ff->version .  '-'. $bits[0] . 'compile';
53      
54         $fn = $compile . '/'. $s .'.'. $bits[0];
55         
56         
57         
58         
59         if (!file_exists($fn)) {
60             header('Content-Type: '. $this->types[$bits[0]]);
61         
62             echo "// compiled file not found = $fn";
63             exit;
64         }
65         
66         $last_modified_time = filemtime($fn);
67         
68         
69         if (
70             (
71                 isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) &&
72                 strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']) == $last_modified_time
73             )
74             ||
75             (
76                  isset($_SERVER['HTTP_IF_NONE_MATCH']) &&
77                 trim($_SERVER['HTTP_IF_NONE_MATCH']) == md5($fn)
78             )) { 
79             header("HTTP/1.1 304 Not Modified");
80             exit;
81         }
82         
83         header('Content-Type: '. $this->types[$bits[0]]);
84         
85         
86         header("Pragma: public");
87         header('Content-Length: '. filesize($fn));
88         header('Cache-Control: max-age=2592000, public');
89         header('Expires: '.gmdate('D, d M Y H:i:s \G\M\T', time() + 2592000));
90         header('Last-Modified: '.gmdate('D, d M Y H:i:s \G\M\T', $last_modified_time));
91         header('Etag: '. md5($fn)); 
92         
93         $fh = fopen($fn,'r');
94         fpassthru($fh);
95         fclose($fh);
96         exit;
97         
98     }
99     function post($s='') {
100         die('invalid');
101     }
102      
103     
104 }
105
106 // a little experimental... we are going to use the same name as the class. for these..
107
108 trait Pman_Core_Asset_Trait {
109     
110     
111     
112      /**
113      * usage in template
114      * {outputJavascriptDir(#Hydra#,#Hydra.js",#.......#)}
115      *
116      * call_user
117      * 
118      */
119     
120     function outputJavascriptDir($path)
121     {
122         
123         $relpath = $this->rootURL . '/' . $path .'/';
124         $ff = HTML_FlexyFramework::get();
125         $dir =   $this->rootDir.'/' . $path;
126         
127         $args = func_get_args();
128         $ar = array();
129         if (count($args) < 2) {
130             $ar = glob($dir . '/*.js');
131         } else {
132             array_shift($args);
133             foreach($args as $f) {
134                 if (strpos($f,'*') > -1) {
135  
136                     $ar = array_merge($ar ,  glob($dir . '/'. $f));
137                     continue;
138                 }
139                 
140                 $ar[] = $dir .'/'. $f;
141             }
142           
143         }
144          // cached version?? - how do we decide if it's expired?
145         // while scanning the directory is slow... - it's faster than serving every file...
146         
147         
148         //$path = $this->rootURL ."/Pman/$mod/";
149         
150         
151         
152         $files = array();
153         $arfiles = array();
154         $maxtime = 0;
155         $mtime = 0;
156         foreach($ar as $fn) {
157             $f = basename($fn);
158             // got the 'module file..'
159             $mtime = filemtime($dir . '/'. $f);
160             $maxtime = max($mtime, $maxtime);
161             $arfiles[$fn] = $mtime;
162             $files[] = $relpath  . $f . '?ts='.$mtime;
163         }
164         
165         ksort($arfiles); // just sort by name so it's consistant for serialize..
166         
167         $ui = posix_getpwuid(posix_geteuid());
168        
169         
170         $compiledir = session_save_path() . '/' .
171                 $ui['name'] . '-' . $ff->project . '-' . $ff->version . '-jscompile';
172         
173         if (!file_exists($compiledir)) {
174             mkdir($compiledir,0700,true);
175         }
176         
177          
178         
179         $lsort = create_function('$a,$b','return strlen($a) > strlen($b) ? 1 : -1;');
180         usort($files, $lsort);
181         
182         
183         if (!empty($this->bootLoader->isDev) && !empty($_REQUEST['isDev'])) {
184             echo "<!-- Javascript compile turned off (isDev on) -->\n";
185             $this->assetArrayToHtml($files,'js');
186             return;
187         }
188         
189         
190         $smod = str_replace('/','.',$path);
191         
192         $output = date('Y-m-d-H-i-s-', $maxtime). $smod .'-'.md5(serialize($arfiles)) .'.js';
193          
194          
195         
196         // where are we going to write all of this..
197         // This has to be done via a 
198         if (!file_exists($compiledir.'/'.$output) || !filesize($compiledir.'/'.$output)) {
199             require_once 'Pman/Core/JsCompile.php';
200             $x = new Pman_Core_JsCompile();
201             
202             $x->pack($arfiles,$compiledir.'/'.$output, false);
203             clearstatcache();
204             if (!file_exists($compiledir.'/'.$output) ||
205                 !filesize($compiledir.'/'.$output)) {
206                 echo "<!-- compile did not generate files : ". basename($compiledir)  ."/{$output} -->\n";
207                 $this->assetArrayToHtml($files,'js');
208                 return;
209             } 
210             
211         } else {
212          //   echo "<!-- file already exists: {$basedir}/{$output} -->\n";
213         }
214         //$this->arrayToJsInclude(  $files);
215         $this->assetArrayToHtml(  array(
216             $this->baseURL.'/Asset/js/'. $output,
217           
218         ), 'js');
219         
220     }
221     
222     function assetArrayToHtml($ar, $type)
223     {
224         foreach($ar as $f) {
225             switch( $type) {
226                 case 'js':
227                     echo '<script type="text/javascript" src="'. $f. '"></script>'."\n";
228                     break;
229                 case 'css':
230                     echo '<link rel="stylesheet" href="'. $f. '"/>'."\n";
231                     break;
232        
233             }
234             
235         }
236     }
237     
238     
239     /**
240      * usage in template
241      * {outputCssDir(#{Hydra/templates/images/css/#,#Hydra.js",#.......#)}
242      */
243     
244     function outputCssDir($path)
245     {
246           
247         $relpath = $this->rootURL . '/' . $path .'/';
248         $ff = HTML_FlexyFramework::get();
249         $dir =   $this->rootDir.'/' . $path;
250         
251         $args = func_get_args();
252         $ar = array();
253         if (count($args) < 2) {
254             $ar = glob($dir . '/*.css');
255         } else {
256             array_shift($args);
257             foreach($args as $f) {
258                 if (strpos($f,'*') > -1) {
259  
260                     $ar = array_merge($ar ,  glob($dir . '/'. $f));
261                     continue;
262                 }
263                 // what if the fiel does not exist???
264                 $ar[] = $dir .'/'. $f;
265             }
266           
267         }
268         
269         
270          // cached version?? - how do we decide if it's expired?
271         // while scanning the directory is slow... - it's faster than serving every file...
272         
273         
274         //$path = $this->rootURL ."/Pman/$mod/";
275         
276         //print_R($ar);exit;
277         $missing_files  = false;
278         $files = array();
279         $arfiles = array();
280         $relfiles = array(); // array of files without the path part...
281         $maxtime = 0;
282         $mtime = 0;
283         foreach($ar as $fn) {
284             $relfiles[] = substr($fn, strlen($dir)+1);
285             $f = basename($fn);
286             // got the 'module file..'
287             
288             if (!file_exists($dir . '/'. $f)) {
289                 echo "<!-- missing {$relpath}{$f} -->\n";
290                 $files[] = $relpath  . $f . '?ts=0';
291                 $missing_files = true;
292                 continue;
293             }
294             
295             $mtime = filemtime($dir . '/'. $f);
296             $maxtime = max($mtime, $maxtime);
297             $arfiles[$fn] = $mtime;
298             $files[] = $relpath  . $f . '?ts='.$mtime;
299             
300             
301             
302         }
303         if ($missing_files) {
304             $this->assetArrayToHtml($files, 'css');
305             return;
306             
307         }
308         
309          
310         //print_r($relfiles);
311         
312         $ui = posix_getpwuid(posix_geteuid());
313        
314         
315         $compiledir = session_save_path() . '/' .
316                 $ui['name'] . '-' . $ff->project . '-'. $ff->version . '-csscompile';
317         
318         if (!file_exists($compiledir)) {
319             mkdir($compiledir,0700,true);
320         }
321         
322          
323         // no sorting???
324         //$lsort = create_function('$a,$b','return strlen($a) > strlen($b) ? 1 : -1;');
325         //usort($files, $lsort);
326         
327         
328         if (!empty($this->bootLoader->isDev) && !empty($_REQUEST['isDev'])) {
329             echo "<!-- CSS compile turned off (isDev on) -->\n";
330             $this->assetArrayToHtml($files,'css');
331             return;
332         }
333         
334         
335         $smod = str_replace('/','.',$path);
336         
337         $output = date('Y-m-d-H-i-s-', $maxtime). $smod .'-'.md5(serialize($arfiles)) .'.css';
338          
339          
340         
341         // where are we going to write all of this..
342         // This has to be done via a 
343         if (true || !file_exists($compiledir.'/'.$output) || !filesize($compiledir.'/'.$output)) {
344             
345             
346             
347             require_once 'HTML/CSS/Minify.php';
348             $x = new HTML_CSS_Minify(substr($relpath,0,-1), $dir, $relfiles);
349             
350             file_put_contents($compiledir.'/'.$output , $x->minify( $this->baseURL.'/Asset/css'));
351             clearstatcache();
352             if (!file_exists($compiledir.'/'.$output) ||
353                 !filesize($compiledir.'/'.$routput)) {
354                 echo "<!-- compile did not generate files : " . basename($compiledir) . "/{$output} -->\n";
355                 $this->assetArrayToHtml($files,'css');
356                 return;
357             } 
358             
359         } else {
360          //   echo "<!-- file already exists: {$basedir}/{$output} -->\n";
361         }
362         //$this->arrayToJsInclude(  $files);
363         $this->assetArrayToHtml(  array(
364             $this->baseURL.'/Asset/css/'. $output,
365           
366         ),'css');
367         
368     }
369     
370     
371     
372 }