PluginProbe
Autoptimize / 2.3.1
Autoptimize v2.3.1
2.2.2 2.3.0 2.3.1 2.3.2 2.3.3 2.3.4 2.4.0 2.4.1 2.4.2 2.4.3 2.4.4 2.5.0 2.5.1 2.6.0 2.6.1 2.6.2 2.7.0 2.7.1 2.7.2 2.7.3 2.7.4 2.7.5 2.7.6 2.7.7 2.7.8 All 107 releases
autoptimize / classes / autoptimizeStyles.php

autoptimizeStyles.php in Autoptimize 2.3.1, at classes/autoptimizeStyles.php

705 lines 33.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
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 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 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 if ( strpos( $this->content, '<script' ) !== false ) {
94 $this->content = preg_replace_callback(
95 '#<(?:no)?script.*?<\/(?:no)?script>#is',
96 create_function(
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 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 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 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 if ( strpos( $this->content, '%%SCRIPT%%' ) !== false ) {
479 $this->content = preg_replace_callback(
480 '#%%SCRIPT'.AUTOPTIMIZE_HASH.'%%(.*?)%%SCRIPT%%#is',
481 create_function(
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