Subversion-Projekte lars-tiefland.laravel_shop

Revision

Revision 115 | Details | Vergleich mit vorheriger | 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'];
1372 lars 71
            $path = array( $_SERVER['DOCUMENT_ROOT'] );
115 lars 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
            {
1372 lars 182
                if (
183
                    !in_array( $key, array(
184
                        'query_strings',
185
                        'write_headers',
186
                        'debug_toggle',
187
                    ) )
188
                )
115 lars 189
                {
190
                    throw new sacy_Exception( "Invalid option: $key" );
191
                }
192
            }
1372 lars 193
            if (
194
                isset( $params['query_strings'] ) && !in_array( $params['query_strings'], array(
115 lars 195
                    'force-handle',
196
                    'ignore',
1372 lars 197
                ) )
198
            )
115 lars 199
            {
200
                throw new sacy_Exception( "Invalid setting for query_strings: " . $params['query_strings'] );
201
            }
1372 lars 202
            if (
203
                isset( $params['write_headers'] ) && !in_array( $params['write_headers'], array(
115 lars 204
                    true,
205
                    false,
1372 lars 206
                ), true )
207
            )
115 lars 208
            {
209
                throw new sacy_Exception( "Invalid setting for write_headers: " . $params['write_headers'] );
210
            }
211
 
212
 
213
            $this->params = array_merge( $this->params, $params );
214
        }
215
 
216
    }
217
 
218
    class sacy_CacheRenderer
219
    {
220
        private $_smarty;
221
        private $_cfg;
222
 
223
        function __construct( sacy_Config $config, $smarty )
224
        {
225
            $this->_smarty = $smarty;
226
            $this->_cfg = $config;
227
        }
228
 
229
        function renderFiles( $tag, $cat, $files )
230
        {
231
            switch ( $tag )
232
            {
233
                case 'link':
234
                    $fn = 'render_css_files';
235
                    break;
236
                case 'script':
237
                    $fn = 'render_js_files';
238
                    break;
239
                default:
240
                    throw new sacy_Exception( "Cannot handle tag: $tag" );
241
            }
242
            return $this->$fn( $files, $cat );
243
        }
244
 
245
 
246
        private function render_css_files( $files, $cat )
247
        {
248
            $ref = sacy_generate_cache( $this->_smarty, $files, new sacy_CssRenderHandler( $this->_cfg, $this->_smarty ) );
249
            if ( !$ref )
250
            {
251
                return false;
252
            }
253
            $cs = $cat ? sprintf( ' media="%s"', htmlspecialchars( $cat, ENT_QUOTES ) ) : '';
254
            return sprintf( '<link rel="stylesheet" type="text/css"%s href="%s" />' . "\n", $cs, htmlspecialchars( $ref, ENT_QUOTES ) );
255
 
256
        }
257
 
258
        private function render_js_files( $files, $cat )
259
        {
260
            $ref = sacy_generate_cache( $this->_smarty, $files, new sacy_JavascriptRenderHandler( $this->_cfg, $this->_smarty ) );
261
            if ( !$ref )
262
            {
263
                return false;
264
            }
265
            return sprintf( '<script type="text/javascript" src="%s"></script>' . "\n", htmlspecialchars( $ref, ENT_QUOTES ) );
266
        }
267
    }
268
 
269
    interface sacy_CacheRenderHandler
270
    {
271
        function __construct( sacy_Config $cfg, $smarty );
272
 
273
        function getFileExtension();
274
 
275
        function writeHeader( $fh, $files );
276
 
277
        function processFile( $fh, $filename );
278
 
279
        function getConfig();
280
    }
281
 
282
    abstract class sacy_ConfiguredRenderHandler implements sacy_CacheRenderHandler
283
    {
284
        private $_smarty;
285
        private $_cfg;
286
 
287
        function __construct( sacy_Config $cfg, $smarty )
288
        {
289
            $this->_smarty = $smarty;
290
            $this->_cfg = $cfg;
291
        }
292
 
293
        protected function getSmarty()
294
        {
295
            return $this->_smarty;
296
        }
297
 
298
        public function getConfig()
299
        {
300
            return $this->_cfg;
301
        }
302
 
303
    }
304
 
305
    class sacy_JavaScriptRenderHandler extends sacy_ConfiguredRenderHandler
306
    {
307
 
308
        function getFileExtension()
309
        {
310
            return '.js';
311
        }
312
 
313
        function writeHeader( $fh, $files )
314
        {
315
            fwrite( $fh, "/*\nsacy javascript cache dump \n\n" );
316
            fwrite( $fh, "This dump has been created from the following files:\n" );
317
            foreach ( $files as $file )
318
            {
319
                fprintf( $fh, "    - %s\n", str_replace( $_SERVER['DOCUMENT_ROOT'], '<root>', $file ) );
320
            }
321
            fwrite( $fh, "*/\n\n" );
322
        }
323
 
324
        function processFile( $fh, $filename )
325
        {
326
            if ( $this->getConfig()->get( 'write_headers' ) )
327
            {
328
                fprintf( $fh, "\n/* %s */\n", str_replace( $_SERVER['DOCUMENT_ROOT'], '<root>', $filename ) );
329
            }
330
            $js = @file_get_contents( $filename );
331
            if ( $js == false )
332
            {
333
                fwrite( $fhc, "/* <Error accessing file> */\n" );
334
                $this->getSmarty()->trigger_error( "Error accessing JavaScript-File: $filename" );
335
                return;
336
            }
337
            fwrite( $fh, JSMin::minify( $js ) );
338
        }
339
 
340
    }
341
 
342
    class sacy_CssRenderHandler extends sacy_ConfiguredRenderHandler
343
    {
344
        function getFileExtension()
345
        {
346
            return '.css';
347
        }
348
 
349
        function writeHeader( $fh, $files )
350
        {
351
            fwrite( $fh, "/*\nsacy css cache dump \n\n" );
352
            fwrite( $fh, "This dump has been created from the following files:\n" );
353
            foreach ( $files as $file )
354
            {
355
                fprintf( $fh, "    - %s\n", str_replace( $_SERVER['DOCUMENT_ROOT'], '<root>', $file ) );
356
            }
357
            fwrite( $fh, "*/\n\n" );
358
        }
359
 
360
        function processFile( $fh, $filename )
361
        {
362
            if ( $this->getConfig()->get( 'write_headers' ) )
363
            {
364
                fprintf( $fh, "\n/* %s */\n", str_replace( $_SERVER['DOCUMENT_ROOT'], '<root>', $filename ) );
365
            }
366
            $css = @file_get_contents( $filename ); //maybe stream this later to save memory?
367
            if ( $css == false )
368
            {
369
                fwrite( $fh, "/* <Error accessing file> */\n" );
370
                $this->getSmarty()->trigger_error( "Error accessing CSS-File: $filename" );
371
                return;
372
            }
373
            fwrite( $fh, Minify_CSS::minify( $css, array(
374
                'currentDir' => dirname( $filename ),
375
            ) ) );
376
        }
377
    }
378
 
379
    class sacy_Exception extends Exception
380
    {
381
    }
382
 
383
    function sacy_extract_attrs( $attstr )
384
    {
385
        $attextract = '#([a-z]+)\s*=\s*(["\'])\s*(.*?)\s*\2#';
386
        $res = array();
387
        if ( !preg_match_all( $attextract, $attstr, $m ) )
388
        {
389
            return false;
390
        }
391
        $res = array();
392
        foreach ( $m[1] as $idx => $name )
393
        {
394
            $res[ strtolower( $name ) ] = $m[3][ $idx ];
395
        }
396
        return $res;
397
 
398
    }
399
 
400
    function sacy_generate_cache( &$smarty, $files, sacy_CacheRenderHandler $rh )
401
    {
402
        $GLOBALS["rh"] = $rh;
403
        if ( !is_dir( ASSET_COMPILE_OUTPUT_DIR ) )
404
        {
405
            mkdir( ASSET_COMPILE_OUTPUT_DIR );
406
        }
407
 
408
 
409
        $f = function ( $f )
410
        {
411
            return basename( $f, $GLOBALS["rh"]->getFileExtension() );
412
        };
413
        $ident = implode( '-', array_map( $f, $files ) );
414
        if ( strlen( $ident ) > 120 )
415
        {
416
            $ident = 'many-files-' . md5( $ident );
417
        }
418
        $max = 0;
419
        foreach ( $files as $f )
420
        {
421
            $max = max( $max, filemtime( $f ) );
422
        }
423
        // not using the actual content for quicker access
1372 lars 424
        $key = md5( $max . serialize( $files ) );
425
        @mkdir( ASSET_COMPILE_OUTPUT_DIR, 0755, true );
115 lars 426
        $cfile = ASSET_COMPILE_OUTPUT_DIR . DIRECTORY_SEPARATOR . "$ident-$key" . $rh->getFileExtension();
427
        $pub = ASSET_COMPILE_URL_ROOT . "/$ident-$key" . $rh->getFileExtension();
428
 
429
        if ( file_exists( $cfile ) && ( $rh->getConfig()->getDebugMode() != 2 ) )
430
        {
431
            return $pub;
432
        }
433
 
434
        if ( !sacy_write_cache( $smarty, $cfile, $files, $rh ) )
435
        {
436
            return false;
437
        }
438
 
439
        return $pub;
440
    }
441
 
442
    function sacy_write_cache( &$smarty, $cfile, $files, sacy_CacheRenderHandler $rh )
443
    {
1372 lars 444
        $lockfile = $cfile . ".lock";
115 lars 445
        $fhl = @fopen( $lockfile, 'w' );
446
        if ( !$fhl )
447
        {
448
            $smarty->trigger_error( "Cannot create cache-lockfile: $lockfile" );
449
            return false;
450
        }
451
        $wb = false;
452
        if ( !@flock( $fhl, LOCK_EX | LOCK_NB, $wb ) )
453
        {
454
            $smarty->trigger_error( "Canot lock cache-lockfile: $lockfile" );
455
            return false;
456
        }
457
        if ( $wb )
458
        {
459
            // another process is writing the cache. Let's just return false
460
            // the caller will leave the CSS unaltered
461
            return false;
462
        }
463
        $fhc = @fopen( $cfile, 'w' );
464
        if ( !$fhc )
465
        {
466
            $smarty->trigger_error( "Cannot open cache file: $cfile" );
467
            fclose( $fhl );
468
            unlink( $lockfile );
469
            return false;
470
        }
471
        if ( $rh->getConfig()->get( 'write_headers' ) )
472
        {
473
            $rh->writeHeader( $fhc, $files );
474
        }
475
 
476
        foreach ( $files as $file )
477
        {
478
            $rh->processFile( $fhc, $file );
479
        }
480
 
481
        fclose( $fhc );
482
        fclose( $fhl );
483
        unlink( $lockfile );
484
        return true;
485
    }
486
 
487
 
488
 
489
 
490