Completed
Push — master ( 9a01d9...c990d6 )
by frank
08:32
created

classes/autoptimizeStyles.php (2 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
3
4
class autoptimizeStyles extends autoptimizeBase {
5
6
    const ASSETS_REGEX = '/url\s*\(\s*(?!["\']?data:)(?![\'|\"]?[\#|\%|])([^)]+)\s*\)([^;},\s]*)/i';
7
8
    private $css = array();
9
    private $csscode = array();
10
    private $url = array();
11
    private $restofcontent = '';
12
    private $datauris = false;
13
    private $hashmap = array();
14
    private $alreadyminified = false;
15
    private $inline = false;
16
    private $defer = false;
17
    private $defer_inline = false;
18
    private $whitelist = '';
19
    private $cssinlinesize = '';
20
    private $cssremovables = array();
21
    private $include_inline = false;
22
    private $inject_min_late = '';
23
24
    //Reads the page and collects style tags
25
    public function read($options) {
26
        $noptimizeCSS = apply_filters( 'autoptimize_filter_css_noptimize', false, $this->content );
27
        if ($noptimizeCSS) return false;
28
29
        $whitelistCSS = apply_filters( 'autoptimize_filter_css_whitelist', '', $this->content );
30
        if (!empty($whitelistCSS)) {
31
            $this->whitelist = array_filter(array_map('trim',explode(",",$whitelistCSS)));
32
        }
33
        
34
        $removableCSS = apply_filters( 'autoptimize_filter_css_removables','' );
35 View Code Duplication
        if (!empty($removableCSS)) {
36
            $this->cssremovables = array_filter(array_map('trim',explode(",",$removableCSS)));
37
        }
38
39
        $this->cssinlinesize = apply_filters('autoptimize_filter_css_inlinesize',256);
40
41
        // filter to "late inject minified CSS", default to true for now (it is faster)
42
        $this->inject_min_late = apply_filters('autoptimize_filter_css_inject_min_late',true);
43
44
        // Remove everything that's not the header
45 View Code Duplication
        if ( apply_filters('autoptimize_filter_css_justhead',$options['justhead']) == true ) {
46
            $content = explode('</head>',$this->content,2);
47
            $this->content = $content[0].'</head>';
48
            $this->restofcontent = $content[1];
49
        }
50
51
        // include inline?
52
        if( apply_filters('autoptimize_css_include_inline',$options['include_inline']) == true ) {
53
            $this->include_inline = true;
54
        }
55
        
56
        // what CSS shouldn't be autoptimized
57
        $excludeCSS = $options['css_exclude'];
58
        $excludeCSS = apply_filters( 'autoptimize_filter_css_exclude', $excludeCSS, $this->content );
59
        if ($excludeCSS!=="") {
60
            $this->dontmove = array_filter(array_map('trim',explode(",",$excludeCSS)));
61
        } else {
62
            $this->dontmove = array();
63
        }
64
        
65
        // forcefully exclude CSS with data-noptimize attrib
66
        $this->dontmove[]="data-noptimize";
67
68
        // should we defer css?
69
        // value: true/ false
70
        $this->defer = $options['defer'];
71
        $this->defer = apply_filters( 'autoptimize_filter_css_defer', $this->defer, $this->content );
72
73
        // should we inline while deferring?
74
        // value: inlined CSS
75
        $this->defer_inline = $options['defer_inline'];
76
        $this->defer_inline = apply_filters( 'autoptimize_filter_css_defer_inline', $this->defer_inline, $this->content );
77
78
        // should we inline?
79
        // value: true/ false
80
        $this->inline = $options['inline'];
81
        $this->inline = apply_filters( 'autoptimize_filter_css_inline', $this->inline, $this->content );
82
        
83
        // get cdn url
84
        $this->cdn_url = $options['cdn_url'];
85
        
86
        // Store data: URIs setting for later use
87
        $this->datauris = $options['datauris'];
88
        
89
        // noptimize me
90
        $this->content = $this->hide_noptimize($this->content);
91
        
92
        // exclude (no)script, as those may contain CSS which should be left as is
93 View Code Duplication
        if ( strpos( $this->content, '<script' ) !== false ) { 
94
            $this->content = preg_replace_callback(
95
                '#<(?:no)?script.*?<\/(?:no)?script>#is',
96
                create_function(
0 ignored issues
show
Security Best Practice introduced by
The use of create_function is highly discouraged, better use a closure.

create_function can pose a great security vulnerability as it is similar to eval, and could be used for arbitrary code execution. We highly recommend to use a closure instead.

// Instead of
$function = create_function('$a, $b', 'return $a + $b');

// Better use
$function = function($a, $b) { return $a + $b; }
Loading history...
97
                    '$matches',
98
                    'return "%%SCRIPT".AUTOPTIMIZE_HASH."%%".base64_encode($matches[0])."%%SCRIPT%%";'
99
                ),
100
                $this->content
101
            );
102
        }
103
104
        // Save IE hacks
105
        $this->content = $this->hide_iehacks($this->content);
106
107
        // hide comments
108
        $this->content = $this->hide_comments($this->content);
109
        
110
        // Get <style> and <link>
111
        if(preg_match_all('#(<style[^>]*>.*</style>)|(<link[^>]*stylesheet[^>]*>)#Usmi',$this->content,$matches)) {
112
            foreach($matches[0] as $tag) {
113
                if ($this->isremovable($tag,$this->cssremovables)) {
114
                    $this->content = str_replace($tag,'',$this->content);
115
                } else if ($this->ismovable($tag)) {
116
                    // Get the media
117
                    if(strpos($tag,'media=')!==false) {
118
                        preg_match('#media=(?:"|\')([^>]*)(?:"|\')#Ui',$tag,$medias);
119
                        $medias = explode(',',$medias[1]);
120
                        $media = array();
121
                        foreach($medias as $elem) {
122
                            if (empty($elem)) { $elem="all"; }
123
                            $media[] = $elem;
124
                        }
125
                    } else {
126
                        // No media specified - applies to all
127
                        $media = array('all');
128
                    }
129
                    $media = apply_filters( 'autoptimize_filter_css_tagmedia',$media,$tag );
130
                
131
                    if(preg_match('#<link.*href=("|\')(.*)("|\')#Usmi',$tag,$source)) {
132
                        // <link>
133
                        $explUrl = explode('?',$source[2],2);
134
                        $url = $explUrl[0];
135
                        $path = $this->getpath($url);
136
                        
137
                        if($path!==false && preg_match('#\.css$#',$path)) {
138
                            // Good link
139
                            $this->css[] = array($media,$path);
140
                        }else{
141
                            // Link is dynamic (.php etc)
142
                            $tag = '';
143
                        }
144
                    } else {
145
                        // inline css in style tags can be wrapped in comment tags, so restore comments
146
                        $tag = $this->restore_comments($tag);
147
                        preg_match('#<style.*>(.*)</style>#Usmi',$tag,$code);
148
149
                        // and re-hide them to be able to to the removal based on tag
150
                        $tag = $this->hide_comments($tag);
151
152
                        if ( $this->include_inline ) {
153
                            $code = preg_replace('#^.*<!\[CDATA\[(?:\s*\*/)?(.*)(?://|/\*)\s*?\]\]>.*$#sm','$1',$code[1]);
154
                            $this->css[] = array($media,'INLINE;'.$code);
155
                        } else {
156
                            $tag = '';
157
                        }
158
                    }
159
                    
160
                    // Remove the original style tag
161
                    $this->content = str_replace($tag,'',$this->content);
162
                } else {
163
					// excluded CSS, minify if getpath 
164
					if (preg_match('#<link.*href=("|\')(.*)("|\')#Usmi',$tag,$source)) {
165
						$explUrl = explode('?',$source[2],2);
166
                        $url = $explUrl[0];
167
                        $path = $this->getpath($url);
168
 					
169
						if ($path && apply_filters('autoptimize_filter_css_minify_excluded',false)) {
170
							$_CachedMinifiedUrl = $this->minify_single($path);
171
172
							if (!empty($_CachedMinifiedUrl)) {
173
								// replace orig URL with URL to cache
174
								$newTag = str_replace($url, $_CachedMinifiedUrl, $tag);
175
							} else {
176
								$newTag = $tag;
177
							}
178
							
179
							// remove querystring from URL
180 View Code Duplication
							if ( !empty($explUrl[1]) ) {
181
								$newTag = str_replace("?".$explUrl[1],"",$newTag);
182
							}
183
184
							// and replace
185
							$this->content = str_replace($tag,$newTag,$this->content);
186
						}
187
					}					
188
				}
189
            }
190
            return true;
191
        }
192
        // Really, no styles?
193
        return false;
194
    }
195
    
196
    // Joins and optimizes CSS
197
    public function minify() {
198
        foreach($this->css as $group) {
199
            list($media,$css) = $group;
200
            if(preg_match('#^INLINE;#',$css)) {
201
                // <style>
202
                $css = preg_replace('#^INLINE;#','',$css);
203
                $css = $this->fixurls(ABSPATH.'/index.php',$css);
204
                $tmpstyle = apply_filters( 'autoptimize_css_individual_style', $css, "" );
205
                if ( has_filter('autoptimize_css_individual_style') && !empty($tmpstyle) ) {
206
                    $css=$tmpstyle;
207
                    $this->alreadyminified=true;
208
                }
209
            } else {
210
                //<link>
211
                if($css !== false && file_exists($css) && is_readable($css)) {
212
                    $cssPath = $css;
213
                    $cssContents = file_get_contents($cssPath);
214
                    $cssHash = md5($cssContents);
215
                    $css = $this->fixurls($cssPath,$cssContents);
216
                    $css = preg_replace('/\x{EF}\x{BB}\x{BF}/','',$css);
217
                    $tmpstyle = apply_filters( 'autoptimize_css_individual_style', $css, $cssPath );
218 View Code Duplication
                    if (has_filter('autoptimize_css_individual_style') && !empty($tmpstyle)) {
219
                        $css=$tmpstyle;
220
                        $this->alreadyminified=true;
221
                    } else if ($this->can_inject_late($cssPath,$css)) {
222
                        $css="/*!%%INJECTLATER%%".base64_encode($cssPath)."|".$cssHash."%%INJECTLATER%%*/";
223
                    }
224
                } else {
225
                    // Couldn't read CSS. Maybe getpath isn't working?
226
                    $css = '';
227
                }
228
            }
229
            
230
            foreach($media as $elem) {
231
                if(!isset($this->csscode[$elem]))
232
                    $this->csscode[$elem] = '';
233
                $this->csscode[$elem] .= "\n/*FILESTART*/".$css;
234
            }
235
        }
236
        
237
        // Check for duplicate code
238
        $md5list = array();
239
        $tmpcss = $this->csscode;
240
        foreach($tmpcss as $media => $code) {
241
            $md5sum = md5($code);
242
            $medianame = $media;
243
            foreach($md5list as $med => $sum) {
244
                // If same code
245
                if($sum === $md5sum) {
246
                    //Add the merged code
247
                    $medianame = $med.', '.$media;
248
                    $this->csscode[$medianame] = $code;
249
                    $md5list[$medianame] = $md5list[$med];
250
                    unset($this->csscode[$med], $this->csscode[$media]);
251
                    unset($md5list[$med]);
252
                }
253
            }
254
            $md5list[$medianame] = $md5sum;
255
        }
256
        unset($tmpcss);
257
        
258
        // Manage @imports, while is for recursive import management
259
        foreach ($this->csscode as &$thiscss) {
260
            // Flag to trigger import reconstitution and var to hold external imports
261
            $fiximports = false;
262
            $external_imports = "";
263
264
            // remove comments to avoid importing commented-out imports
265
            $thiscss_nocomments = preg_replace('#/\*.*\*/#Us','',$thiscss);
266
267
            while(preg_match_all('#@import +(?:url)?(?:(?:\\(([\"\']?)(?:[^\"\')]+)\\1\\)|([\"\'])(?:[^\"\']+)\\2)(?:[^,;\"\']+(?:,[^,;\"\']+)*)?)(?:;)#m',$thiscss_nocomments,$matches)) {
268
                foreach($matches[0] as $import)    {
269
                    if ($this->isremovable($import,$this->cssremovables)) {
270
                        $thiscss = str_replace($import,'',$thiscss);
271
                        $import_ok = true;
272
                    } else {
273
                        $url = trim(preg_replace('#^.*((?:https?:|ftp:)?//.*\.css).*$#','$1',trim($import))," \t\n\r\0\x0B\"'");
274
                        $path = $this->getpath($url);
275
                        $import_ok = false;
276
                        if (file_exists($path) && is_readable($path)) {
277
                            $code = addcslashes($this->fixurls($path,file_get_contents($path)),"\\");
278
                            $code = preg_replace('/\x{EF}\x{BB}\x{BF}/','',$code);
279
                            $tmpstyle = apply_filters( 'autoptimize_css_individual_style', $code, "" );
280
                            if ( has_filter('autoptimize_css_individual_style') && !empty($tmpstyle)) {
281
                                $code=$tmpstyle;
282
                                $this->alreadyminified=true;
283
                            } else if ($this->can_inject_late($path,$code)) {
284
                                $code="/*!%%INJECTLATER".AUTOPTIMIZE_HASH."%%".base64_encode($path)."|".md5($code)."%%INJECTLATER%%*/";
285
                            }
286
                            
287
                            if(!empty($code)) {
288
                                $tmp_thiscss = preg_replace('#(/\*FILESTART\*/.*)'.preg_quote($import,'#').'#Us','/*FILESTART2*/'.$code.'$1',$thiscss);
289
                                if (!empty($tmp_thiscss)) {
290
                                    $thiscss = $tmp_thiscss;
291
                                    $import_ok = true;
292
                                    unset($tmp_thiscss);
293
                                }
294
                                unset($code);
295
                            }
296
                        }
297
                    }
298
299
                    if (!$import_ok) {
300
                        // external imports and general fall-back
301
                        $external_imports .= $import;
302
                        $thiscss = str_replace($import,'',$thiscss);
303
                        $fiximports = true;
304
                    }
305
                }
306
                $thiscss = preg_replace('#/\*FILESTART\*/#','',$thiscss);
307
                $thiscss = preg_replace('#/\*FILESTART2\*/#','/*FILESTART*/',$thiscss);
308
                
309
                // and update $thiscss_nocomments before going into next iteration in while loop
310
                $thiscss_nocomments=preg_replace('#/\*.*\*/#Us','',$thiscss);
311
            }
312
            unset($thiscss_nocomments);
313
            
314
            // add external imports to top of aggregated CSS
315
            if($fiximports) {
316
                $thiscss=$external_imports.$thiscss;
317
            }
318
        }
319
        unset($thiscss);
320
        
321
        // $this->csscode has all the uncompressed code now. 
322
        foreach($this->csscode as &$code) {
323
            // Check for already-minified code
324
            $hash = md5($code);
325
            do_action( 'autoptimize_action_css_hash', $hash );
326
            $ccheck = new autoptimizeCache($hash,'css');
327
            if($ccheck->check()) {
328
                $code = $ccheck->retrieve();
329
                $this->hashmap[md5($code)] = $hash;
330
                continue;
331
            }
332
            unset($ccheck);            
333
334
            // Do the imaging!
335
            $imgreplace = array();
336
            preg_match_all( self::ASSETS_REGEX, $code, $matches );
337
338
            if ( ($this->datauris == true) && (function_exists('base64_encode')) && (is_array($matches)) ) {
339
                foreach($matches[1] as $count => $quotedurl) {
340
                    $iurl = trim($quotedurl," \t\n\r\0\x0B\"'");
341
342
                    // if querystring, remove it from url
343
                    if (strpos($iurl,'?') !== false) { $iurl = strtok($iurl,'?'); }
344
                    
345
                    $ipath = $this->getpath($iurl);
346
347
                    $datauri_max_size = 4096;
348
                    $datauri_max_size = (int) apply_filters( 'autoptimize_filter_css_datauri_maxsize', $datauri_max_size );
349
                    $datauri_exclude = apply_filters( 'autoptimize_filter_css_datauri_exclude', "");
350
                    if (!empty($datauri_exclude)) {
351
                        $no_datauris=array_filter(array_map('trim',explode(",",$datauri_exclude)));
352
                        foreach ($no_datauris as $no_datauri) {
353
                            if (strpos($iurl,$no_datauri)!==false) {
354
                                $ipath=false;
355
                                break;
356
                            }
357
                        }
358
                    }
359
360
                    if($ipath != false && preg_match('#\.(jpe?g|png|gif|bmp)$#i',$ipath) && file_exists($ipath) && is_readable($ipath) && filesize($ipath) <= $datauri_max_size) {
361
                        $ihash=md5($ipath);
362
                        $icheck = new autoptimizeCache($ihash,'img');
363
                        if($icheck->check()) {
364
                            // we have the base64 image in cache
365
                            $headAndData=$icheck->retrieve();
366
                            $_base64data=explode(";base64,",$headAndData);
367
                            $base64data=$_base64data[1];
368
                        } else {
369
                            // It's an image and we don't have it in cache, get the type
370
                            $explA=explode('.',$ipath);
371
                            $type=end($explA);
372
373
                            switch($type) {
374
                                case 'jpeg':
375
                                    $dataurihead = 'data:image/jpeg;base64,';
376
                                    break;
377
                                case 'jpg':
378
                                    $dataurihead = 'data:image/jpeg;base64,';
379
                                    break;
380
                                case 'gif':
381
                                    $dataurihead = 'data:image/gif;base64,';
382
                                    break;
383
                                case 'png':
384
                                    $dataurihead = 'data:image/png;base64,';
385
                                    break;
386
                                case 'bmp':
387
                                    $dataurihead = 'data:image/bmp;base64,';
388
                                    break;
389
                                default:
390
                                    $dataurihead = 'data:application/octet-stream;base64,';
391
                            }
392
                        
393
                            // Encode the data
394
                            $base64data = base64_encode(file_get_contents($ipath));
395
                            $headAndData=$dataurihead.$base64data;
396
397
                            // Save in cache
398
                            $icheck->cache($headAndData,"text/plain");
399
                        }
400
                        unset($icheck);
401
402
                        // Add it to the list for replacement
403
                        $imgreplace[$matches[0][$count]] = str_replace($quotedurl,$headAndData,$matches[0][$count]);
404
                    } else {
405
                        // just cdn the URL if applicable
406
                        if (!empty($this->cdn_url)) {
407
                            $imgreplace[$matches[0][$count]] = str_replace($quotedurl,$this->maybe_cdn_urls($quotedurl),$matches[0][$count]);
408
						}
409
                    }
410
                }
411
            } else if ((is_array($matches)) && (!empty($this->cdn_url))) {
412
                // change urls to cdn-url
413
                foreach($matches[1] as $count => $quotedurl) {
414
                    $imgreplace[$matches[0][$count]] = str_replace($quotedurl,$this->maybe_cdn_urls($quotedurl),$matches[0][$count]);
415
                }
416
            }
417
            
418
            if(!empty($imgreplace)) {
419
                $code = str_replace(array_keys($imgreplace),array_values($imgreplace),$code);
420
            }
421
            
422
            // Minify
423
            if (($this->alreadyminified!==true) && (apply_filters( "autoptimize_css_do_minify", true))) {
424 View Code Duplication
                if (class_exists('Minify_CSS_Compressor')) {
425
                    $tmp_code = trim(Minify_CSS_Compressor::process($code));
426
                } else if(class_exists('CSSmin')) {
427
                    $cssmin = new CSSmin();
428
                    if (method_exists($cssmin,"run")) {
429
                        $tmp_code = trim($cssmin->run($code));
430
                    } elseif (@is_callable(array($cssmin,"minify"))) {
431
                        $tmp_code = trim(CssMin::minify($code));
432
                    }
433
                }
434
                if (!empty($tmp_code)) {
435
                    $code = $tmp_code;
436
                    unset($tmp_code);
437
                }
438
            }
439
            
440
            $code = $this->inject_minified($code);
441
            
442
            $tmp_code = apply_filters( 'autoptimize_css_after_minify', $code );
443
            if (!empty($tmp_code)) {
444
                $code = $tmp_code;
445
                unset($tmp_code);
446
            }
447
            
448
            $this->hashmap[md5($code)] = $hash;
449
        }
450
        unset($code);
451
        return true;
452
    }
453
    
454
    //Caches the CSS in uncompressed, deflated and gzipped form.
455
    public function cache() {
456
        // CSS cache
457
        foreach($this->csscode as $media => $code) {
458
            $md5 = $this->hashmap[md5($code)];
459
                
460
            $cache = new autoptimizeCache($md5,'css');
461
            if(!$cache->check()) {
462
                // Cache our code
463
                $cache->cache($code,'text/css');
464
            }
465
            $this->url[$media] = AUTOPTIMIZE_CACHE_URL.$cache->getname();
466
        }
467
    }
468
    
469
    //Returns the content
470
    public function getcontent() {
471
        // restore IE hacks
472
        $this->content = $this->restore_iehacks($this->content);
473
474
        // restore comments
475
        $this->content = $this->restore_comments($this->content);
476
        
477
        // restore (no)script
478 View Code Duplication
        if ( strpos( $this->content, '%%SCRIPT%%' ) !== false ) { 
479
            $this->content = preg_replace_callback(
480
                '#%%SCRIPT'.AUTOPTIMIZE_HASH.'%%(.*?)%%SCRIPT%%#is',
481
                create_function(
0 ignored issues
show
Security Best Practice introduced by
The use of create_function is highly discouraged, better use a closure.

create_function can pose a great security vulnerability as it is similar to eval, and could be used for arbitrary code execution. We highly recommend to use a closure instead.

// Instead of
$function = create_function('$a, $b', 'return $a + $b');

// Better use
$function = function($a, $b) { return $a + $b; }
Loading history...
482
                    '$matches',
483
                    'return base64_decode($matches[1]);'
484
                ),
485
                $this->content
486
            );
487
        }
488
489
        // restore noptimize
490
        $this->content = $this->restore_noptimize($this->content);
491
        
492
        //Restore the full content
493
        if(!empty($this->restofcontent)) {
494
            $this->content .= $this->restofcontent;
495
            $this->restofcontent = '';
496
        }
497
        
498
        // Inject the new stylesheets
499
        $replaceTag = array("<title","before");
500
        $replaceTag = apply_filters( 'autoptimize_filter_css_replacetag', $replaceTag, $this->content );
501
502
        if ($this->inline == true) {
503
            foreach($this->csscode as $media => $code) {
504
                $this->inject_in_html('<style type="text/css" media="'.$media.'">'.$code.'</style>',$replaceTag);
505
            }
506
        } else {
507
            if ($this->defer == true) {
508
                $preloadCssBlock = "";
509
                $noScriptCssBlock = "<noscript id=\"aonoscrcss\">";
510
                $defer_inline_code=$this->defer_inline;
511
                if(!empty($defer_inline_code)){
512
                    if ( apply_filters( 'autoptimize_filter_css_critcss_minify', true ) ) {
513
                        $iCssHash = md5($defer_inline_code);
514
                        $iCssCache = new autoptimizeCache($iCssHash,'css');
515
                        if($iCssCache->check()) { 
516
                            // we have the optimized inline CSS in cache
517
                            $defer_inline_code=$iCssCache->retrieve();
518
                        } else {
519
                            if (class_exists('Minify_CSS_Compressor')) {
520
                                $tmp_code = trim(Minify_CSS_Compressor::process($defer_inline_code));
521
                            } else if(class_exists('CSSmin')) {
522
                                $cssmin = new CSSmin();
523
                                $tmp_code = trim($cssmin->run($defer_inline_code));
524
                            }
525
                            if (!empty($tmp_code)) {
526
                                $defer_inline_code = $tmp_code;
527
                                $iCssCache->cache($defer_inline_code,"text/css");
528
                                unset($tmp_code);
529
                            }
530
                        }
531
                    }
532
                    $code_out='<style type="text/css" id="aoatfcss" media="all">'.$defer_inline_code.'</style>';
533
                    $this->inject_in_html($code_out,$replaceTag);
534
                }
535
            }
536
537
            foreach($this->url as $media => $url) {
538
                $url = $this->url_replace_cdn($url);
539
                
540
                //Add the stylesheet either deferred (import at bottom) or normal links in head
541
                if($this->defer == true) {
542
                    
543
                    // Filter to modify the onload attribute - passes value and the stylesheet url
544
                    $preloadOnLoad = apply_filters('autoptimize_filter_css_preload_onload', "this.rel='stylesheet'", $url);
545
                    
546
                    $preloadCssBlock .= '<link rel="preload" as="style" media="'.$media.'" href="'.$url.'" onload="'.$preloadOnLoad.'" />';
547
                    $noScriptCssBlock .= '<link type="text/css" media="'.$media.'" href="'.$url.'" rel="stylesheet" />';
548
                    
549
                } else {
550
                    if (strlen($this->csscode[$media]) > $this->cssinlinesize) {
551
                        $this->inject_in_html('<link type="text/css" media="'.$media.'" href="'.$url.'" rel="stylesheet" />',$replaceTag);
552
                    } else if (strlen($this->csscode[$media])>0) {
553
                        $this->inject_in_html('<style type="text/css" media="'.$media.'">'.$this->csscode[$media].'</style>',$replaceTag);
554
                    }
555
                }
556
            }
557
            
558
            if($this->defer == true) {
559
                $preloadPolyfill = '<script data-cfasync=\'false\'>/*! loadCSS. [c]2017 Filament Group, Inc. MIT License */
560
!function(a){"use strict";var b=function(b,c,d){function e(a){return h.body?a():void setTimeout(function(){e(a)})}function f(){i.addEventListener&&i.removeEventListener("load",f),i.media=d||"all"}var g,h=a.document,i=h.createElement("link");if(c)g=c;else{var j=(h.body||h.getElementsByTagName("head")[0]).childNodes;g=j[j.length-1]}var k=h.styleSheets;i.rel="stylesheet",i.href=b,i.media="only x",e(function(){g.parentNode.insertBefore(i,c?g:g.nextSibling)});var l=function(a){for(var b=i.href,c=k.length;c--;)if(k[c].href===b)return a();setTimeout(function(){l(a)})};return i.addEventListener&&i.addEventListener("load",f),i.onloadcssdefined=l,l(f),i};"undefined"!=typeof exports?exports.loadCSS=b:a.loadCSS=b}("undefined"!=typeof global?global:this);
561
/*! loadCSS rel=preload polyfill. [c]2017 Filament Group, Inc. MIT License */
562
!function(a){if(a.loadCSS){var b=loadCSS.relpreload={};if(b.support=function(){try{return a.document.createElement("link").relList.supports("preload")}catch(b){return!1}},b.poly=function(){for(var b=a.document.getElementsByTagName("link"),c=0;c<b.length;c++){var d=b[c];"preload"===d.rel&&"style"===d.getAttribute("as")&&(a.loadCSS(d.href,d,d.getAttribute("media")),d.rel=null)}},!b.support()){b.poly();var c=a.setInterval(b.poly,300);a.addEventListener&&a.addEventListener("load",function(){b.poly(),a.clearInterval(c)}),a.attachEvent&&a.attachEvent("onload",function(){a.clearInterval(c)})}}}(this);</script>';
563
                $noScriptCssBlock .= "</noscript>";
564
                $this->inject_in_html($preloadCssBlock.$noScriptCssBlock,$replaceTag);
565
                
566
                // Adds preload polyfill at end of body tag
567
                $this->inject_in_html(
568
                    apply_filters('autoptimize_css_preload_polyfill', $preloadPolyfill),
569
                    array('</body>','before')
570
                );
571
            }
572
        }
573
574
        //Return the modified stylesheet
575
        return $this->content;
576
    }
577
    
578
    static function fixurls($file, $code) {
579
        // Switch all imports to the url() syntax
580
        $code = preg_replace( '#@import ("|\')(.+?)\.css.*?("|\')#', '@import url("${2}.css")', $code );
581
582
        if ( preg_match_all( self::ASSETS_REGEX, $code, $matches ) ) {
583
            $file = str_replace( WP_ROOT_DIR, '/', $file );
584
            $dir = dirname( $file ); // Like /themes/expound/css
585
586
            // $dir should not contain backslashes, since it's used to replace
587
            // urls, but it can contain them when running on Windows because
588
            // fixurls() is sometimes called with `ABSPATH . 'index.php'`
589
            $dir = str_replace( '\\', '/', $dir );
590
            unset( $file ); // not used below at all
591
592
            $replace = array();
593
            foreach ( $matches[1] as $k => $url ) {
594
                // Remove quotes
595
                $url    = trim( $url," \t\n\r\0\x0B\"'" );
596
                $noQurl = trim( $url, "\"'" );
597
                if ( $url !== $noQurl ) {
598
                    $removedQuotes = true;
599
                } else {
600
                    $removedQuotes = false;
601
                }
602
603
                if ( '' === $noQurl ) {
604
                    continue;
605
                }
606
607
                $url = $noQurl;
608
                if ( '/' === $url{0} || preg_match( '#^(https?://|ftp://|data:)#i', $url ) ) {
609
                    // URL is protocol-relative, host-relative or something we don't touch
610
                    continue;
611
                } else {
612
                    // Relative URL
613
                    $newurl = preg_replace( '/https?:/', '', str_replace( ' ', '%20', AUTOPTIMIZE_WP_ROOT_URL . str_replace( '//', '/', $dir . '/' . $url ) ) );
614
615
                    // Hash the url + whatever was behind potentially for replacement
616
                    // We must do this, or different css classes referencing the same bg image (but
617
                    // different parts of it, say, in sprites and such) loose their stuff...
618
                    $hash = md5( $url . $matches[2][$k] );
619
                    $code = str_replace( $matches[0][$k], $hash, $code );
620
621
                    if ( $removedQuotes ) {
622
                        $replace[$hash] = "url('" . $newurl . "')" . $matches[2][$k];
623
                    } else {
624
                        $replace[$hash] = 'url(' . $newurl . ')' . $matches[2][$k];
625
                    }
626
                }
627
            }
628
629
            if ( ! empty( $replace ) ) {
630
                // Sort the replacements array by key length in desc order (so that the longest strings are replaced first)
631
                $keys = array_map( 'strlen', array_keys( $replace ) );
632
                array_multisort( $keys, SORT_DESC, $replace );
633
634
                // Replace URLs found within $code
635
                $code = str_replace( array_keys( $replace ), array_values( $replace ), $code );
636
            }
637
        }
638
639
        return $code;
640
    }
641
    
642
    private function ismovable($tag) {
643
		if ( apply_filters('autoptimize_filter_css_dontaggregate', false) ) {
644
			return false;
645
        } else if (!empty($this->whitelist)) {
646
            foreach ($this->whitelist as $match) {
647
                if(strpos($tag,$match)!==false) {
648
                    return true;
649
                }
650
            }
651
            // no match with whitelist
652
            return false;
653
        } else {
654
            if (is_array($this->dontmove)) {
655
                foreach($this->dontmove as $match) {
656
                    if(strpos($tag,$match)!==false) {
657
                        //Matched something
658
                        return false;
659
                    }
660
                }
661
            }
662
            
663
            //If we're here it's safe to move
664
            return true;
665
        }
666
    }
667
    
668
    private function can_inject_late($cssPath,$css) {
669
		$consider_minified_array = apply_filters('autoptimize_filter_css_consider_minified', false, $cssPath);
670
        if ( $this->inject_min_late !== true ) {
671
            // late-inject turned off
672
            return false;
673
        } else if ( (strpos($cssPath,"min.css") === false) && ( str_replace($consider_minified_array, '', $cssPath) === $cssPath ) ) {
674
			// file not minified based on filename & filter
675
			return false;
676
        } else if ( strpos($css,"@import") !== false ) {
677
            // can't late-inject files with imports as those need to be aggregated 
678
            return false;
679
        } else if ( (strpos($css,"@font-face")!==false ) && ( apply_filters("autoptimize_filter_css_fonts_cdn",false)===true) && (!empty($this->cdn_url)) ) {
680
            // don't late-inject CSS with font-src's if fonts are set to be CDN'ed
681
            return false;
682
        } else if ( (($this->datauris == true) || (!empty($this->cdn_url))) && preg_match("#background[^;}]*url\(#Ui",$css) ) {
683
            // don't late-inject CSS with images if CDN is set OR is image inlining is on
684
            return false;
685
        } else {
686
            // phew, all is safe, we can late-inject
687
            return true;
688
        }
689
    }
690
    
691
    private function maybe_cdn_urls($inUrl) {
692
        $url = trim($inUrl," \t\n\r\0\x0B\"'");
693
        $urlPath = parse_url($url,PHP_URL_PATH);
694
695
        // exclude fonts from CDN except if filter returns true
696
        if ( !preg_match('#\.(woff2?|eot|ttf|otf)$#i',$urlPath) || apply_filters('autoptimize_filter_css_fonts_cdn',false) ) {
697
            $cdn_url = $this->url_replace_cdn($url);
698
        } else {
699
            $cdn_url = $url;
700
        }
701
                
702
        return $cdn_url;
703
    }
704
}
705