Subversion-Projekte lars-tiefland.laravel_shop

Revision

Zur aktuellen Revision | Details | Letzte Änderung | Log anzeigen | RSS feed

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