PluginProbe
Autoptimize / 2.2.2
Autoptimize v2.2.2
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.2.2, at classes/autoptimizeStyles.php

699 lines 32.5 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 if ($options['nogooglefont'] == true) {
35 $removableCSS = "fonts.googleapis.com";
36 } else {
37 $removableCSS = "";
38 }
39 $removableCSS = apply_filters( 'autoptimize_filter_css_removables', $removableCSS);
40 if (!empty($removableCSS)) {
41 $this->cssremovables = array_filter(array_map('trim',explode(",",$removableCSS)));
42 }
43
44 $this->cssinlinesize = apply_filters('autoptimize_filter_css_inlinesize',256);
45
46 // filter to "late inject minified CSS", default to true for now (it is faster)
47 $this->inject_min_late = apply_filters('autoptimize_filter_css_inject_min_late',true);
48
49 // Remove everything that's not the header
50 if ( apply_filters('autoptimize_filter_css_justhead',$options['justhead']) == true ) {
51 $content = explode('</head>',$this->content,2);
52 $this->content = $content[0].'</head>';
53 $this->restofcontent = $content[1];
54 }
55
56 // include inline?
57 if( apply_filters('autoptimize_css_include_inline',$options['include_inline']) == true ) {
58 $this->include_inline = true;
59 }
60
61 // what CSS shouldn't be autoptimized
62 $excludeCSS = $options['css_exclude'];
63 $excludeCSS = apply_filters( 'autoptimize_filter_css_exclude', $excludeCSS, $this->content );
64 if ($excludeCSS!=="") {
65 $this->dontmove = array_filter(array_map('trim',explode(",",$excludeCSS)));
66 } else {
67 $this->dontmove = array();
68 }
69
70 // forcefully exclude CSS with data-noptimize attrib
71 $this->dontmove[]="data-noptimize";
72
73 // should we defer css?
74 // value: true/ false
75 $this->defer = $options['defer'];
76 $this->defer = apply_filters( 'autoptimize_filter_css_defer', $this->defer, $this->content );
77
78 // should we inline while deferring?
79 // value: inlined CSS
80 $this->defer_inline = $options['defer_inline'];
81 $this->defer_inline = apply_filters( 'autoptimize_filter_css_defer_inline', $this->defer_inline, $this->content );
82
83 // should we inline?
84 // value: true/ false
85 $this->inline = $options['inline'];
86 $this->inline = apply_filters( 'autoptimize_filter_css_inline', $this->inline, $this->content );
87
88 // get cdn url
89 $this->cdn_url = $options['cdn_url'];
90
91 // Store data: URIs setting for later use
92 $this->datauris = $options['datauris'];
93
94 // noptimize me
95 $this->content = $this->hide_noptimize($this->content);
96
97 // exclude (no)script, as those may contain CSS which should be left as is
98 if ( strpos( $this->content, '<script' ) !== false ) {
99 $this->content = preg_replace_callback(
100 '#<(?:no)?script.*?<\/(?:no)?script>#is',
101 create_function(
102 '$matches',
103 'return "%%SCRIPT".AUTOPTIMIZE_HASH."%%".base64_encode($matches[0])."%%SCRIPT%%";'
104 ),
105 $this->content
106 );
107 }
108
109 // Save IE hacks
110 $this->content = $this->hide_iehacks($this->content);
111
112 // hide comments
113 $this->content = $this->hide_comments($this->content);
114
115 // Get <style> and <link>
116 if(preg_match_all('#(<style[^>]*>.*</style>)|(<link[^>]*stylesheet[^>]*>)#Usmi',$this->content,$matches)) {
117 foreach($matches[0] as $tag) {
118 if ($this->isremovable($tag,$this->cssremovables)) {
119 $this->content = str_replace($tag,'',$this->content);
120 } else if ($this->ismovable($tag)) {
121 // Get the media
122 if(strpos($tag,'media=')!==false) {
123 preg_match('#media=(?:"|\')([^>]*)(?:"|\')#Ui',$tag,$medias);
124 $medias = explode(',',$medias[1]);
125 $media = array();
126 foreach($medias as $elem) {
127 if (empty($elem)) { $elem="all"; }
128 $media[] = $elem;
129 }
130 } else {
131 // No media specified - applies to all
132 $media = array('all');
133 }
134 $media = apply_filters( 'autoptimize_filter_css_tagmedia',$media,$tag );
135
136 if(preg_match('#<link.*href=("|\')(.*)("|\')#Usmi',$tag,$source)) {
137 // <link>
138 $explUrl = explode('?',$source[2],2);
139 $url = $explUrl[0];
140 $path = $this->getpath($url);
141
142 if($path!==false && preg_match('#\.css$#',$path)) {
143 // Good link
144 $this->css[] = array($media,$path);
145 }else{
146 // Link is dynamic (.php etc)
147 $tag = '';
148 }
149 } else {
150 // inline css in style tags can be wrapped in comment tags, so restore comments
151 $tag = $this->restore_comments($tag);
152 preg_match('#<style.*>(.*)</style>#Usmi',$tag,$code);
153
154 // and re-hide them to be able to to the removal based on tag
155 $tag = $this->hide_comments($tag);
156
157 if ( $this->include_inline ) {
158 $code = preg_replace('#^.*<!\[CDATA\[(?:\s*\*/)?(.*)(?://|/\*)\s*?\]\]>.*$#sm','$1',$code[1]);
159 $this->css[] = array($media,'INLINE;'.$code);
160 } else {
161 $tag = '';
162 }
163 }
164
165 // Remove the original style tag
166 $this->content = str_replace($tag,'',$this->content);
167 } else {
168 // excluded CSS, minify if getpath
169 if (preg_match('#<link.*href=("|\')(.*)("|\')#Usmi',$tag,$source)) {
170 $explUrl = explode('?',$source[2],2);
171 $url = $explUrl[0];
172 $path = $this->getpath($url);
173
174 if ($path && apply_filters('autoptimize_filter_css_minify_excluded',false)) {
175 $_CachedMinifiedUrl = $this->minify_single($path);
176
177 if (!empty($_CachedMinifiedUrl)) {
178 // replace orig URL with URL to cache
179 $newTag = str_replace($url, $_CachedMinifiedUrl, $tag);
180 } else {
181 $newTag = $tag;
182 }
183
184 // remove querystring from URL
185 if ( !empty($explUrl[1]) ) {
186 $newTag = str_replace("?".$explUrl[1],"",$newTag);
187 }
188
189 // and replace
190 $this->content = str_replace($tag,$newTag,$this->content);
191 }
192 }
193 }
194 }
195 return true;
196 }
197 // Really, no styles?
198 return false;
199 }
200
201 // Joins and optimizes CSS
202 public function minify() {
203 foreach($this->css as $group) {
204 list($media,$css) = $group;
205 if(preg_match('#^INLINE;#',$css)) {
206 // <style>
207 $css = preg_replace('#^INLINE;#','',$css);
208 $css = $this->fixurls(ABSPATH.'/index.php',$css);
209 $tmpstyle = apply_filters( 'autoptimize_css_individual_style', $css, "" );
210 if ( has_filter('autoptimize_css_individual_style') && !empty($tmpstyle) ) {
211 $css=$tmpstyle;
212 $this->alreadyminified=true;
213 }
214 } else {
215 //<link>
216 if($css !== false && file_exists($css) && is_readable($css)) {
217 $cssPath = $css;
218 $cssContents = file_get_contents($cssPath);
219 $cssHash = md5($cssContents);
220 $css = $this->fixurls($cssPath,$cssContents);
221 $css = preg_replace('/\x{EF}\x{BB}\x{BF}/','',$css);
222 $tmpstyle = apply_filters( 'autoptimize_css_individual_style', $css, $cssPath );
223 if (has_filter('autoptimize_css_individual_style') && !empty($tmpstyle)) {
224 $css=$tmpstyle;
225 $this->alreadyminified=true;
226 } else if ($this->can_inject_late($cssPath,$css)) {
227 $css="/*!%%INJECTLATER%%".base64_encode($cssPath)."|".$cssHash."%%INJECTLATER%%*/";
228 }
229 } else {
230 // Couldn't read CSS. Maybe getpath isn't working?
231 $css = '';
232 }
233 }
234
235 foreach($media as $elem) {
236 if(!isset($this->csscode[$elem]))
237 $this->csscode[$elem] = '';
238 $this->csscode[$elem] .= "\n/*FILESTART*/".$css;
239 }
240 }
241
242 // Check for duplicate code
243 $md5list = array();
244 $tmpcss = $this->csscode;
245 foreach($tmpcss as $media => $code) {
246 $md5sum = md5($code);
247 $medianame = $media;
248 foreach($md5list as $med => $sum) {
249 // If same code
250 if($sum === $md5sum) {
251 //Add the merged code
252 $medianame = $med.', '.$media;
253 $this->csscode[$medianame] = $code;
254 $md5list[$medianame] = $md5list[$med];
255 unset($this->csscode[$med], $this->csscode[$media]);
256 unset($md5list[$med]);
257 }
258 }
259 $md5list[$medianame] = $md5sum;
260 }
261 unset($tmpcss);
262
263 // Manage @imports, while is for recursive import management
264 foreach ($this->csscode as &$thiscss) {
265 // Flag to trigger import reconstitution and var to hold external imports
266 $fiximports = false;
267 $external_imports = "";
268
269 // remove comments to avoid importing commented-out imports
270 $thiscss_nocomments = preg_replace('#/\*.*\*/#Us','',$thiscss);
271
272 while(preg_match_all('#@import.*(?:;|$)#Um',$thiscss_nocomments,$matches)) {
273 foreach($matches[0] as $import) {
274 if ($this->isremovable($import,$this->cssremovables)) {
275 $thiscss = str_replace($import,'',$thiscss);
276 $import_ok = true;
277 } else {
278 $url = trim(preg_replace('#^.*((?:https?:|ftp:)?//.*\.css).*$#','$1',trim($import))," \t\n\r\0\x0B\"'");
279 $path = $this->getpath($url);
280 $import_ok = false;
281 if (file_exists($path) && is_readable($path)) {
282 $code = addcslashes($this->fixurls($path,file_get_contents($path)),"\\");
283 $code = preg_replace('/\x{EF}\x{BB}\x{BF}/','',$code);
284 $tmpstyle = apply_filters( 'autoptimize_css_individual_style', $code, "" );
285 if ( has_filter('autoptimize_css_individual_style') && !empty($tmpstyle)) {
286 $code=$tmpstyle;
287 $this->alreadyminified=true;
288 } else if ($this->can_inject_late($path,$code)) {
289 $code="/*!%%INJECTLATER".AUTOPTIMIZE_HASH."%%".base64_encode($path)."|".md5($code)."%%INJECTLATER%%*/";
290 }
291
292 if(!empty($code)) {
293 $tmp_thiscss = preg_replace('#(/\*FILESTART\*/.*)'.preg_quote($import,'#').'#Us','/*FILESTART2*/'.$code.'$1',$thiscss);
294 if (!empty($tmp_thiscss)) {
295 $thiscss = $tmp_thiscss;
296 $import_ok = true;
297 unset($tmp_thiscss);
298 }
299 unset($code);
300 }
301 }
302 }
303
304 if (!$import_ok) {
305 // external imports and general fall-back
306 $external_imports .= $import;
307 $thiscss = str_replace($import,'',$thiscss);
308 $fiximports = true;
309 }
310 }
311 $thiscss = preg_replace('#/\*FILESTART\*/#','',$thiscss);
312 $thiscss = preg_replace('#/\*FILESTART2\*/#','/*FILESTART*/',$thiscss);
313
314 // and update $thiscss_nocomments before going into next iteration in while loop
315 $thiscss_nocomments=preg_replace('#/\*.*\*/#Us','',$thiscss);
316 }
317 unset($thiscss_nocomments);
318
319 // add external imports to top of aggregated CSS
320 if($fiximports) {
321 $thiscss=$external_imports.$thiscss;
322 }
323 }
324 unset($thiscss);
325
326 // $this->csscode has all the uncompressed code now.
327 foreach($this->csscode as &$code) {
328 // Check for already-minified code
329 $hash = md5($code);
330 $ccheck = new autoptimizeCache($hash,'css');
331 if($ccheck->check()) {
332 $code = $ccheck->retrieve();
333 $this->hashmap[md5($code)] = $hash;
334 continue;
335 }
336 unset($ccheck);
337
338 // Do the imaging!
339 $imgreplace = array();
340 preg_match_all( self::ASSETS_REGEX, $code, $matches );
341
342 if ( ($this->datauris == true) && (function_exists('base64_encode')) && (is_array($matches)) ) {
343 foreach($matches[1] as $count => $quotedurl) {
344 $iurl = trim($quotedurl," \t\n\r\0\x0B\"'");
345
346 // if querystring, remove it from url
347 if (strpos($iurl,'?') !== false) { $iurl = strtok($iurl,'?'); }
348
349 $ipath = $this->getpath($iurl);
350
351 $datauri_max_size = 4096;
352 $datauri_max_size = (int) apply_filters( 'autoptimize_filter_css_datauri_maxsize', $datauri_max_size );
353 $datauri_exclude = apply_filters( 'autoptimize_filter_css_datauri_exclude', "");
354 if (!empty($datauri_exclude)) {
355 $no_datauris=array_filter(array_map('trim',explode(",",$datauri_exclude)));
356 foreach ($no_datauris as $no_datauri) {
357 if (strpos($iurl,$no_datauri)!==false) {
358 $ipath=false;
359 break;
360 }
361 }
362 }
363
364 if($ipath != false && preg_match('#\.(jpe?g|png|gif|bmp)$#i',$ipath) && file_exists($ipath) && is_readable($ipath) && filesize($ipath) <= $datauri_max_size) {
365 $ihash=md5($ipath);
366 $icheck = new autoptimizeCache($ihash,'img');
367 if($icheck->check()) {
368 // we have the base64 image in cache
369 $headAndData=$icheck->retrieve();
370 $_base64data=explode(";base64,",$headAndData);
371 $base64data=$_base64data[1];
372 } else {
373 // It's an image and we don't have it in cache, get the type
374 $explA=explode('.',$ipath);
375 $type=end($explA);
376
377 switch($type) {
378 case 'jpeg':
379 $dataurihead = 'data:image/jpeg;base64,';
380 break;
381 case 'jpg':
382 $dataurihead = 'data:image/jpeg;base64,';
383 break;
384 case 'gif':
385 $dataurihead = 'data:image/gif;base64,';
386 break;
387 case 'png':
388 $dataurihead = 'data:image/png;base64,';
389 break;
390 case 'bmp':
391 $dataurihead = 'data:image/bmp;base64,';
392 break;
393 default:
394 $dataurihead = 'data:application/octet-stream;base64,';
395 }
396
397 // Encode the data
398 $base64data = base64_encode(file_get_contents($ipath));
399 $headAndData=$dataurihead.$base64data;
400
401 // Save in cache
402 $icheck->cache($headAndData,"text/plain");
403 }
404 unset($icheck);
405
406 // Add it to the list for replacement
407 $imgreplace[$matches[0][$count]] = str_replace($quotedurl,$headAndData,$matches[0][$count]);
408 } else {
409 // just cdn the URL if applicable
410 if (!empty($this->cdn_url)) {
411 $imgreplace[$matches[0][$count]] = str_replace($quotedurl,$this->maybe_cdn_urls($quotedurl),$matches[0][$count]);
412 }
413 }
414 }
415 } else if ((is_array($matches)) && (!empty($this->cdn_url))) {
416 // change urls to cdn-url
417 foreach($matches[1] as $count => $quotedurl) {
418 $imgreplace[$matches[0][$count]] = str_replace($quotedurl,$this->maybe_cdn_urls($quotedurl),$matches[0][$count]);
419 }
420 }
421
422 if(!empty($imgreplace)) {
423 $code = str_replace(array_keys($imgreplace),array_values($imgreplace),$code);
424 }
425
426 // Minify
427 if (($this->alreadyminified!==true) && (apply_filters( "autoptimize_css_do_minify", true))) {
428 if (class_exists('Minify_CSS_Compressor')) {
429 $tmp_code = trim(Minify_CSS_Compressor::process($code));
430 } else if(class_exists('CSSmin')) {
431 $cssmin = new CSSmin();
432 if (method_exists($cssmin,"run")) {
433 $tmp_code = trim($cssmin->run($code));
434 } elseif (@is_callable(array($cssmin,"minify"))) {
435 $tmp_code = trim(CssMin::minify($code));
436 }
437 }
438 if (!empty($tmp_code)) {
439 $code = $tmp_code;
440 unset($tmp_code);
441 }
442 }
443
444 $code = $this->inject_minified($code);
445
446 $tmp_code = apply_filters( 'autoptimize_css_after_minify', $code );
447 if (!empty($tmp_code)) {
448 $code = $tmp_code;
449 unset($tmp_code);
450 }
451
452 $this->hashmap[md5($code)] = $hash;
453 }
454 unset($code);
455 return true;
456 }
457
458 //Caches the CSS in uncompressed, deflated and gzipped form.
459 public function cache() {
460 // CSS cache
461 foreach($this->csscode as $media => $code) {
462 $md5 = $this->hashmap[md5($code)];
463
464 $cache = new autoptimizeCache($md5,'css');
465 if(!$cache->check()) {
466 // Cache our code
467 $cache->cache($code,'text/css');
468 }
469 $this->url[$media] = AUTOPTIMIZE_CACHE_URL.$cache->getname();
470 }
471 }
472
473 //Returns the content
474 public function getcontent() {
475 // restore IE hacks
476 $this->content = $this->restore_iehacks($this->content);
477
478 // restore comments
479 $this->content = $this->restore_comments($this->content);
480
481 // restore (no)script
482 if ( strpos( $this->content, '%%SCRIPT%%' ) !== false ) {
483 $this->content = preg_replace_callback(
484 '#%%SCRIPT'.AUTOPTIMIZE_HASH.'%%(.*?)%%SCRIPT%%#is',
485 create_function(
486 '$matches',
487 'return base64_decode($matches[1]);'
488 ),
489 $this->content
490 );
491 }
492
493 // restore noptimize
494 $this->content = $this->restore_noptimize($this->content);
495
496 //Restore the full content
497 if(!empty($this->restofcontent)) {
498 $this->content .= $this->restofcontent;
499 $this->restofcontent = '';
500 }
501
502 // Inject the new stylesheets
503 $replaceTag = array("<title","before");
504 $replaceTag = apply_filters( 'autoptimize_filter_css_replacetag', $replaceTag, $this->content );
505
506 if ($this->inline == true) {
507 foreach($this->csscode as $media => $code) {
508 $this->inject_in_html('<style type="text/css" media="'.$media.'">'.$code.'</style>',$replaceTag);
509 }
510 } else {
511 if ($this->defer == true) {
512 $preloadCssBlock = "";
513 $noScriptCssBlock = "<noscript id=\"aonoscrcss\">";
514 $defer_inline_code=$this->defer_inline;
515 if(!empty($defer_inline_code)){
516 if ( apply_filters( 'autoptimize_filter_css_critcss_minify', true ) ) {
517 $iCssHash = md5($defer_inline_code);
518 $iCssCache = new autoptimizeCache($iCssHash,'css');
519 if($iCssCache->check()) {
520 // we have the optimized inline CSS in cache
521 $defer_inline_code=$iCssCache->retrieve();
522 } else {
523 if (class_exists('Minify_CSS_Compressor')) {
524 $tmp_code = trim(Minify_CSS_Compressor::process($defer_inline_code));
525 } else if(class_exists('CSSmin')) {
526 $cssmin = new CSSmin();
527 $tmp_code = trim($cssmin->run($defer_inline_code));
528 }
529 if (!empty($tmp_code)) {
530 $defer_inline_code = $tmp_code;
531 $iCssCache->cache($defer_inline_code,"text/css");
532 unset($tmp_code);
533 }
534 }
535 }
536 $code_out='<style type="text/css" id="aoatfcss" media="all">'.$defer_inline_code.'</style>';
537 $this->inject_in_html($code_out,$replaceTag);
538 }
539 }
540
541 foreach($this->url as $media => $url) {
542 $url = $this->url_replace_cdn($url);
543
544 //Add the stylesheet either deferred (import at bottom) or normal links in head
545 if($this->defer == true) {
546 $preloadCssBlock .= '<link rel="preload" as="style" media="'.$media.'" href="'.$url.'" onload="this.rel=\'stylesheet\'" />';
547 $noScriptCssBlock .= '<link type="text/css" media="'.$media.'" href="'.$url.'" rel="stylesheet" />';
548 } else {
549 if (strlen($this->csscode[$media]) > $this->cssinlinesize) {
550 $this->inject_in_html('<link type="text/css" media="'.$media.'" href="'.$url.'" rel="stylesheet" />',$replaceTag);
551 } else if (strlen($this->csscode[$media])>0) {
552 $this->inject_in_html('<style type="text/css" media="'.$media.'">'.$this->csscode[$media].'</style>',$replaceTag);
553 }
554 }
555 }
556
557 if($this->defer == true) {
558 $preloadPolyfill = '<script data-cfasync=\'false\'>/*! loadCSS. [c]2017 Filament Group, Inc. MIT License */
559 !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);
560 /*! loadCSS rel=preload polyfill. [c]2017 Filament Group, Inc. MIT License */
561 !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>';
562 $noScriptCssBlock .= "</noscript>";
563 $this->inject_in_html($preloadCssBlock.$noScriptCssBlock,$replaceTag);
564 $this->inject_in_html($preloadPolyfill,array('</body>','before'));
565 }
566 }
567
568 //Return the modified stylesheet
569 return $this->content;
570 }
571
572 static function fixurls($file, $code) {
573 // Switch all imports to the url() syntax
574 $code = preg_replace( '#@import ("|\')(.+?)\.css.*("|\')#', '@import url("${2}.css")', $code );
575
576 if ( preg_match_all( self::ASSETS_REGEX, $code, $matches ) ) {
577 $file = str_replace( WP_ROOT_DIR, '/', $file );
578 $dir = dirname( $file ); // Like /themes/expound/css
579
580 // $dir should not contain backslashes, since it's used to replace
581 // urls, but it can contain them when running on Windows because
582 // fixurls() is sometimes called with `ABSPATH . 'index.php'`
583 $dir = str_replace( '\\', '/', $dir );
584 unset( $file ); // not used below at all
585
586 $replace = array();
587 foreach ( $matches[1] as $k => $url ) {
588 // Remove quotes
589 $url = trim( $url," \t\n\r\0\x0B\"'" );
590 $noQurl = trim( $url, "\"'" );
591 if ( $url !== $noQurl ) {
592 $removedQuotes = true;
593 } else {
594 $removedQuotes = false;
595 }
596
597 if ( '' === $noQurl ) {
598 continue;
599 }
600
601 $url = $noQurl;
602 if ( '/' === $url{0} || preg_match( '#^(https?://|ftp://|data:)#i', $url ) ) {
603 // URL is protocol-relative, host-relative or something we don't touch
604 continue;
605 } else {
606 // Relative URL
607 $newurl = preg_replace( '/https?:/', '', str_replace( ' ', '%20', AUTOPTIMIZE_WP_ROOT_URL . str_replace( '//', '/', $dir . '/' . $url ) ) );
608
609 // Hash the url + whatever was behind potentially for replacement
610 // We must do this, or different css classes referencing the same bg image (but
611 // different parts of it, say, in sprites and such) loose their stuff...
612 $hash = md5( $url . $matches[2][$k] );
613 $code = str_replace( $matches[0][$k], $hash, $code );
614
615 if ( $removedQuotes ) {
616 $replace[$hash] = "url('" . $newurl . "')" . $matches[2][$k];
617 } else {
618 $replace[$hash] = 'url(' . $newurl . ')' . $matches[2][$k];
619 }
620 }
621 }
622
623 if ( ! empty( $replace ) ) {
624 // Sort the replacements array by key length in desc order (so that the longest strings are replaced first)
625 $keys = array_map( 'strlen', array_keys( $replace ) );
626 array_multisort( $keys, SORT_DESC, $replace );
627
628 // Replace URLs found within $code
629 $code = str_replace( array_keys( $replace ), array_values( $replace ), $code );
630 }
631 }
632
633 return $code;
634 }
635
636 private function ismovable($tag) {
637 if ( apply_filters('autoptimize_filter_css_dontaggregate', false) ) {
638 return false;
639 } else if (!empty($this->whitelist)) {
640 foreach ($this->whitelist as $match) {
641 if(strpos($tag,$match)!==false) {
642 return true;
643 }
644 }
645 // no match with whitelist
646 return false;
647 } else {
648 if (is_array($this->dontmove)) {
649 foreach($this->dontmove as $match) {
650 if(strpos($tag,$match)!==false) {
651 //Matched something
652 return false;
653 }
654 }
655 }
656
657 //If we're here it's safe to move
658 return true;
659 }
660 }
661
662 private function can_inject_late($cssPath,$css) {
663 $consider_minified_array = apply_filters('autoptimize_filter_css_consider_minified', false, $cssPath);
664 if ( $this->inject_min_late !== true ) {
665 // late-inject turned off
666 return false;
667 } else if ( (strpos($cssPath,"min.css") === false) && ( str_replace($consider_minified_array, '', $cssPath) === $cssPath ) ) {
668 // file not minified based on filename & filter
669 return false;
670 } else if ( strpos($css,"@import") !== false ) {
671 // can't late-inject files with imports as those need to be aggregated
672 return false;
673 } else if ( (strpos($css,"@font-face")!==false ) && ( apply_filters("autoptimize_filter_css_fonts_cdn",false)===true) && (!empty($this->cdn_url)) ) {
674 // don't late-inject CSS with font-src's if fonts are set to be CDN'ed
675 return false;
676 } else if ( (($this->datauris == true) || (!empty($this->cdn_url))) && preg_match("#background[^;}]*url\(#Ui",$css) ) {
677 // don't late-inject CSS with images if CDN is set OR is image inlining is on
678 return false;
679 } else {
680 // phew, all is safe, we can late-inject
681 return true;
682 }
683 }
684
685 private function maybe_cdn_urls($inUrl) {
686 $url = trim($inUrl," \t\n\r\0\x0B\"'");
687 $urlPath = parse_url($url,PHP_URL_PATH);
688
689 // exclude fonts from CDN except if filter returns true
690 if ( !preg_match('#\.(woff2?|eot|ttf|otf)$#i',$urlPath) || apply_filters('autoptimize_filter_css_fonts_cdn',false) ) {
691 $cdn_url = $this->url_replace_cdn($url);
692 } else {
693 $cdn_url = $url;
694 }
695
696 return $cdn_url;
697 }
698 }
699