Subversion-Projekte lars-tiefland.php_share

Revision

Details | Letzte Änderung | Log anzeigen | RSS feed

Revision Autor Zeilennr. Zeile
3 lars 1
<?php
2
 
3
if (!class_exists('JSMin'))
4
    include_once(implode(DIRECTORY_SEPARATOR, array(dirname(__FILE__), 'jsmin.php')));
5
if (!class_exists('Minify_CSS'))
6
    include_once(implode(DIRECTORY_SEPARATOR, array(dirname(__FILE__), 'cssmin.php')));
7
 
8
/*
9
 *   An earlier experiment contained a real framework for tag
10
 *   and parser registration. In the end, this turned out
11
 *   to be much too complex if we just need to support two tags
12
 *   for two types of resources.
13
 */
14
class sacy_FileExtractor{
15
    private $_cfg;
16
 
17
    function __construct(sacy_Config $config){
18
        $this->_cfg = $config;
19
    }
20
 
21
    function extractFile($tag, $attrdata, $content){
22
        switch($tag){
23
            case 'link':
24
                $fn = 'extract_css_file';
25
                break;
26
            case 'script':
27
                $fn = 'extract_js_file';
28
                break;
29
            default: throw new sacy_Exception("Cannot handle tag: $tag");
30
        }
31
        return $this->$fn($attrdata, $content);
32
    }
33
 
34
    private function urlToFile($ref){
35
        $u = parse_url($ref);
36
        if ($u === false) return false;
37
        if (isset($u['host']) || isset($u['scheme']))
38
            return false;
39
 
40
        if ($this->_cfg->get('query_strings') == 'ignore')
41
            if (isset($u['query'])) return false;
42
 
43
        $ref = $u['path'];
44
        $path = array($_SERVER['DOCUMENT_ROOT']);
45
        if ($ref[0] != '/')
46
            $path[] = $_SERVER['PHP_SELF'];
47
        $path[] = $ref;
48
        return realpath(implode(DIRECTORY_SEPARATOR, $path));
49
 
50
    }
51
 
52
 
53
    private function extract_css_file($attrdata, $content){
54
        // if any of these conditions are met, this handler will decline
55
        // handling the tag:
56
        //
57
        //  - the tag contains content (invalid markup)
58
        //  - the tag uses any rel beside 'stylesheet' (valid, but not supported)
59
        //  - the tag uses any type besides text/css (would maybe still work, but I can't be sure)
60
        $attrs = sacy_extract_attrs($attrdata);
61
        if (empty($content) && (strtolower($attrs['rel']) == 'stylesheet') &&
62
            (!isset($attrs['type']) || strtolower($attrs['type']) == 'text/css')){
63
            if (!isset($attrs['media']))
64
                $attrs['media'] = "";
65
 
66
            $path = $this->urlToFile($attrs['href']);
67
            if ($path === false) return false;
68
 
69
            return array($attrs['media'], $path);
70
        }
71
        return false;
72
    }
73
 
74
    private function extract_js_file($attrdata, $content){
75
        // don't handle non-empty tags
76
        if (preg_match('#\S+#', $content)) return false;
77
 
78
        $attrs = sacy_extract_attrs($attrdata);
79
 
80
        if ( ($attrs['type'] == 'text/javascript' ||
81
                $attrs['type'] == 'application/javascript') &&
82
             (isset($attrs['src']) && !empty($attrs['src'])) ){
83
 
84
            $path = $this->urlToFile($attrs['src']);
85
            if ($path === false) return false;
86
            return array('', $path);
87
        }
88
        return false;
89
    }
90
 
91
}
92
 
93
class sacy_Config{
94
    private $params;
95
 
96
    public function get($key){
97
        return $this->params[$key];
98
    }
99
 
100
    public function __construct($params = null){
101
        $this->params['query_strings'] = 'ignore';
102
        $this->params['write_headers'] = true;
103
        $this->params['debug_toggle']  = '_sacy_debug';
104
        if (is_array($params))
105
            $this->setParams($params);
106
    }
107
 
108
    public function getDebugMode(){
109
        if ($this->params['debug_toggle'] === false)
110
            return 0;
111
        if (isset($_GET[$this->params['debug_toggle']]))
112
            return intval($_GET[$this->params['debug_toggle']]);
113
        if (isset($_COOKIE[$this->params['debug_toggle']]))
114
            return intval($_COOKIE[$this->params['debug_toggle']]);
115
        return 0;
116
 
117
    }
118
 
119
    public function setParams($params){
120
        foreach($params as $key => $value){
121
            if (!in_array($key, array('query_strings', 'write_headers', 'debug_toggle')))
122
                throw new sacy_Exception("Invalid option: $key");
123
        }
124
        if (isset($params['query_strings']) && !in_array($params['query_strings'], array('force-handle', 'ignore')))
125
            throw new sacy_Exception("Invalid setting for query_strings: ".$params['query_strings']);
126
        if (isset($params['write_headers']) && !in_array($params['write_headers'], array(true, false), true))
127
            throw new sacy_Exception("Invalid setting for write_headers: ".$params['write_headers']);
128
 
129
 
130
        $this->params = array_merge($this->params, $params);
131
    }
132
 
133
}
134
 
135
class sacy_CacheRenderer {
136
    private $_smarty;
137
    private $_cfg;
138
 
139
    function __construct(sacy_Config $config, $smarty){
140
        $this->_smarty = $smarty;
141
        $this->_cfg = $config;
142
    }
143
 
144
    function renderFiles($tag, $cat, $files){
145
        switch($tag){
146
            case 'link':
147
                $fn = 'render_css_files';
148
                break;
149
            case 'script':
150
                $fn = 'render_js_files';
151
                break;
152
            default: throw new sacy_Exception("Cannot handle tag: $tag");
153
        }
154
        return $this->$fn($files, $cat);
155
    }
156
 
157
 
158
    private function render_css_files($files, $cat){
159
        $ref = sacy_generate_cache($this->_smarty, $files, new sacy_CssRenderHandler($this->_cfg, $this->_smarty));
160
        if (!$ref) return false;
161
        $cs = $cat ? sprintf(' media="%s"', htmlspecialchars($cat, ENT_QUOTES)) : '';
162
        return sprintf('<link rel="stylesheet" type="text/css"%s href="%s" />'."\n",
163
                       $cs, htmlspecialchars($ref, ENT_QUOTES)
164
                      );
165
 
166
    }
167
 
168
    private function render_js_files($files, $cat){
169
        $ref = sacy_generate_cache($this->_smarty, $files, new sacy_JavascriptRenderHandler($this->_cfg, $this->_smarty));
170
        if (!$ref) return false;
171
        return sprintf('<script type="text/javascript" src="%s"></script>'."\n", htmlspecialchars($ref, ENT_QUOTES));
172
    }
173
}
174
 
175
interface sacy_CacheRenderHandler{
176
    function __construct(sacy_Config $cfg, $smarty);
177
    function getFileExtension();
178
    function writeHeader($fh, $files);
179
    function processFile($fh, $filename);
180
    function getConfig();
181
}
182
 
183
abstract class sacy_ConfiguredRenderHandler implements sacy_CacheRenderHandler{
184
    private $_smarty;
185
    private $_cfg;
186
 
187
    function __construct(sacy_Config $cfg, $smarty){
188
        $this->_smarty = $smarty;
189
        $this->_cfg = $cfg;
190
    }
191
 
192
    protected function getSmarty(){
193
        return $this->_smarty;
194
    }
195
 
196
    public function getConfig(){
197
        return $this->_cfg;
198
    }
199
 
200
}
201
 
202
class sacy_JavaScriptRenderHandler extends sacy_ConfiguredRenderHandler{
203
 
204
    function getFileExtension() { return '.js'; }
205
 
206
    function writeHeader($fh, $files){
207
        fwrite($fh, "/*\nsacy javascript cache dump \n\n");
208
        fwrite($fh, "This dump has been created from the following files:\n");
209
        foreach($files as $file){
210
            fprintf($fh, "    - %s\n", str_replace($_SERVER['DOCUMENT_ROOT'], '<root>', $file));
211
        }
212
        fwrite($fh, "*/\n\n");
213
    }
214
 
215
    function processFile($fh, $filename){
216
        if ($this->getConfig()->get('write_headers'))
217
            fprintf($fh, "\n/* %s */\n", str_replace($_SERVER['DOCUMENT_ROOT'], '<root>', $filename));
218
        $js = @file_get_contents($filename);
219
        if ($js == false){
220
            fwrite($fhc, "/* <Error accessing file> */\n");
221
            $this->getSmarty()->trigger_error("Error accessing JavaScript-File: $filename");
222
            return;
223
        }
224
        fwrite($fh, JSMin::minify($js));
225
    }
226
 
227
}
228
 
229
class sacy_CssRenderHandler extends sacy_ConfiguredRenderHandler{
230
    function getFileExtension() { return '.css'; }
231
 
232
    function writeHeader($fh, $files){
233
        fwrite($fh, "/*\nsacy css cache dump \n\n");
234
        fwrite($fh, "This dump has been created from the following files:\n");
235
        foreach($files as $file){
236
            fprintf($fh, "    - %s\n", str_replace($_SERVER['DOCUMENT_ROOT'], '<root>', $file));
237
        }
238
        fwrite($fh, "*/\n\n");
239
    }
240
 
241
    function processFile($fh, $filename){
242
        if ($this->getConfig()->get('write_headers'))
243
           fprintf($fh, "\n/* %s */\n", str_replace($_SERVER['DOCUMENT_ROOT'], '<root>', $filename));
244
        $css = @file_get_contents($filename); //maybe stream this later to save memory?
245
        if ($css == false){
246
            fwrite($fh, "/* <Error accessing file> */\n");
247
            $this->getSmarty()->trigger_error("Error accessing CSS-File: $filename");
248
            return;
249
        }
250
        fwrite($fh, Minify_CSS::minify($css, array(
251
            'currentDir' => dirname($filename)
252
        )));
253
    }
254
}
255
 
256
class sacy_Exception extends Exception {}
257
 
258
function sacy_extract_attrs($attstr){
259
    $attextract = '#([a-z]+)\s*=\s*(["\'])\s*(.*?)\s*\2#';
260
    $res = array();
261
    if (!preg_match_all($attextract, $attstr, $m)) return false;
262
    $res = array();
263
    foreach($m[1] as $idx => $name){
264
        $res[strtolower($name)] = $m[3][$idx];
265
    }
266
    return $res;
267
 
268
}
269
 
270
function sacy_generate_cache(&$smarty, $files, sacy_CacheRenderHandler $rh){
271
    if (!is_dir(ASSET_COMPILE_OUTPUT_DIR))
272
        mkdir(ASSET_COMPILE_OUTPUT_DIR);
273
 
274
    $f = create_function('$f', 'return basename($f, "'.$rh->getFileExtension().'");');
275
    $ident = implode('-', array_map($f, $files));
276
    if (strlen($ident) > 120)
277
        $ident = 'many-files-'.md5($ident);
278
    $max = 0;
279
    foreach($files as $f){
280
        $max = max($max, filemtime($f));
281
    }
282
    // not using the actual content for quicker access
283
    $key = md5($max . serialize($files));
284
    $cfile = ASSET_COMPILE_OUTPUT_DIR . DIRECTORY_SEPARATOR ."$ident-$key".$rh->getFileExtension();
285
    $pub = ASSET_COMPILE_URL_ROOT . "/$ident-$key".$rh->getFileExtension();
286
 
287
    if (file_exists($cfile) && ($rh->getConfig()->getDebugMode() != 2)){
288
        return $pub;
289
    }
290
 
291
    if (!sacy_write_cache($smarty, $cfile, $files, $rh)){
292
        return false;
293
    }
294
 
295
    return $pub;
296
}
297
 
298
function sacy_write_cache(&$smarty, $cfile, $files, sacy_CacheRenderHandler $rh){
299
    $lockfile = $cfile.".lock";
300
    $fhl = @fopen($lockfile, 'w');
301
    if (!$fhl){
302
        $smarty->trigger_error("Cannot create cache-lockfile: $lockfile");
303
        return false;
304
    }
305
    $wb = false;
306
    if (!@flock($fhl, LOCK_EX | LOCK_NB, $wb)){
307
        $smarty->trigger_error("Canot lock cache-lockfile: $lockfile");
308
        return false;
309
    }
310
    if ($wb){
311
        // another process is writing the cache. Let's just return false
312
        // the caller will leave the CSS unaltered
313
        return false;
314
    }
315
    $fhc = @fopen($cfile, 'w');
316
    if (!$fhc){
317
        $smarty->trigger_error("Cannot open cache file: $cfile");
318
        fclose($fhl);
319
        unlink($lockfile);
320
        return false;
321
    }
322
    if ($rh->getConfig()->get('write_headers'))
323
        $rh->writeHeader($fhc, $files);
324
 
325
    foreach($files as $file){
326
        $rh->processFile($fhc, $file);
327
    }
328
 
329
    fclose($fhc);
330
    fclose($fhl);
331
    unlink($lockfile);
332
    return true;
333
}
334
 
335
 
336
 
337
 
338