PluginProbe
Autoptimize / 1.2
Autoptimize v1.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
← All changes | classes/autoptimizeStyles.php +351 -698 2.2.21.2 View file →
@@ -1,698 +1,351 @@
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 -}
1 +<?php
2 +class autoptimizeStyles extends autoptimizeBase
3 +{
4 + private $css = array();
5 + private $csscode = array();
6 + private $url = array();
7 + private $restofcontent = '';
8 + private $mhtml = '';
9 + private $datauris = false;
10 + private $yui = false;
11 + private $hashmap = array();
12 +
13 + //Reads the page and collects style tags
14 + public function read($options)
15 + {
16 + //Remove everything that's not the header
17 + if($options['justhead'] == true)
18 + {
19 + $content = explode('</head>',$this->content,2);
20 + $this->content = $content[0].'</head>';
21 + $this->restofcontent = $content[1];
22 + }
23 +
24 + //Store data: URIs setting for later use
25 + $this->datauris = $options['datauris'];
26 +
27 + //Do we use yui?
28 + $this->yui = $options['yui'];
29 +
30 + //Save IE hacks
31 + $this->content = preg_replace('#(<\!--\[if.*\]>.*<\!\[endif\]-->)#Usie',
32 + '\'%%IEHACK%%\'.base64_encode("$1").\'%%IEHACK%%\'',$this->content);
33 +
34 + //Get <style> and <link>
35 + if(preg_match_all('#(<style[^>]*>.*</style>)|(<link[^>]*text/css[^>]*>)#Usmi',$this->content,$matches))
36 + {
37 + foreach($matches[0] as $tag)
38 + {
39 + if(preg_match('#<link.*href=("|\')(.*)("|\')#Usmi',$tag,$source))
40 + {
41 + //<link>
42 + $url = current(explode('?',$source[2],2));
43 + $path = $this->getpath($url);
44 + $media = array('all');
45 +
46 + if($path !==false && preg_match('#\.css$#',$path))
47 + {
48 + //Good link
49 + //Get the media
50 + if(strpos($tag,'media=')!==false)
51 + {
52 + $medias = preg_replace('#^.*media=(?:"|\')(.*)(?:"|\').*$#U','$1',$tag);
53 + $medias = explode(',',$medias);
54 + $media = array();
55 + foreach($medias as $elem)
56 + {
57 + $media[] = current(explode(' ',trim($elem),2));
58 + }
59 + }else{
60 + //No media specified - applies to all
61 + $media = array('all');
62 + }
63 +
64 + $this->css[] = array($media,$path);
65 + }else{
66 + //Link is dynamic (.php etc)
67 + $tag = '';
68 + }
69 + }else{
70 + //<style>
71 + preg_match('#<style.*>(.*)</style>#Usmi',$tag,$code);
72 + $code = preg_replace('#^.*<!\[CDATA\[(?:\s*\*/)?(.*)(?://|/\*)\s*?\]\]>.*$#sm','$1',$code[1]);
73 + $this->css[] = array('all','INLINE;'.$code);
74 + }
75 +
76 + //Remove the original style tag
77 + $this->content = str_replace($tag,'',$this->content);
78 + }
79 +
80 + return true;
81 + }
82 +
83 + //No styles :(
84 + return false;
85 + }
86 +
87 + //Joins and optimizes CSS
88 + public function minify()
89 + {
90 + foreach($this->css as $group)
91 + {
92 + list($media,$css) = $group;
93 + if(preg_match('#^INLINE;#',$css))
94 + {
95 + //<style>
96 + $css = preg_replace('#^INLINE;#','',$css);
97 + $css = $this->fixurls(ABSPATH.'/index.php',$css);
98 + if(!isset($this->csscode['all']))
99 + $this->csscode['all'] = '';
100 + $this->csscode['all'] .= "\n/*FILESTART*/".$css;
101 + }else{
102 + //<link>
103 + if($css !== false && file_exists($css) && is_readable($css))
104 + {
105 + $css = $this->fixurls($css,file_get_contents($css));
106 + foreach($media as $elem)
107 + {
108 + if(!isset($this->csscode[$elem]))
109 + $this->csscode[$elem] = '';
110 + $this->csscode[$elem] .= "\n/*FILESTART*/".$css;
111 + }
112 + }/*else{
113 + //Couldn't read CSS. Maybe getpath isn't working?
114 + }*/
115 + }
116 + }
117 +
118 + //Check for duplicate code
119 + $md5list = array();
120 + $tmpcss = $this->csscode;
121 + foreach($tmpcss as $media => $code)
122 + {
123 + $md5sum = md5($code);
124 + $medianame = $media;
125 + foreach($md5list as $med => $sum)
126 + {
127 + //If same code
128 + if($sum === $md5sum)
129 + {
130 + //Add the merged code
131 + $medianame = $med.', '.$media;
132 + $this->csscode[$medianame] = $code;
133 + $md5list[$medianame] = $md5list[$med];
134 + unset($this->csscode[$med], $this->csscode[$media]);
135 + unset($md5list[$med]);
136 + }
137 + }
138 + $md5list[$medianame] = $md5sum;
139 + }
140 + unset($tmpcss);
141 +
142 + //Manage @imports, while is for recursive import management
143 + foreach($this->csscode as &$thiscss)
144 + {
145 + //Flag to trigger import reconstitution
146 + $fiximports = false;
147 + while(preg_match_all('#@import.*(?:;|$)#Um',$thiscss,$matches))
148 + {
149 + foreach($matches[0] as $import)
150 + {
151 + $url = trim(preg_replace('#^.*((?:https?|ftp)://.*\.css).*$#','$1',$import)," \t\n\r\0\x0B\"'");
152 + $path = $this->getpath($url);
153 + if(file_exists($path) && is_readable($path))
154 + {
155 + $code = $this->fixurls($path,file_get_contents($path));
156 + /*$media = preg_replace('#^.*(?:\)|"|\')(.*)(?:\s|;).*$#','$1',$import);
157 + $media = array_map('trim',explode(' ',$media));
158 + if(empty($media))
159 + {
160 + $thiscss = [...] (Line under)
161 + }else{
162 + //media in @import - how should I handle these?
163 + //TODO: Infinite recursion!
164 + }*/
165 + $thiscss = preg_replace('#(/\*FILESTART\*/.*)'.preg_quote($import,'#').'#Us','/*FILESTART2*/'.$code.'$1',$thiscss);
166 + }else{
167 + //getpath is not working?
168 + //Encode so preg_match doesn't see it
169 + $thiscss = str_replace($import,'/*IMPORT*/'.base64_encode($import).'/*IMPORT*/',$thiscss);
170 + $fiximports = true;
171 + }
172 + }
173 + $thiscss = preg_replace('#/\*FILESTART\*/#','',$thiscss);
174 + $thiscss = preg_replace('#/\*FILESTART2\*/#','/*FILESTART*/',$thiscss);
175 + }
176 +
177 + //Recover imports
178 + if($fiximports)
179 + {
180 + $thiscss = preg_replace('#/\*IMPORT\*/(.*)/\*IMPORT\*/#Use','base64_decode("$1")',$thiscss);
181 + }
182 + }
183 + unset($thiscss);
184 +
185 + //$this->csscode has all the uncompressed code now.
186 + $mhtmlcount = 0;
187 + foreach($this->csscode as &$code)
188 + {
189 + //Check for already-minified code
190 + $hash = md5($code);
191 + $ccheck = new autoptimizeCache($hash,'css');
192 + if($ccheck->check())
193 + {
194 + $code = $ccheck->retrieve();
195 + $this->hashmap[md5($code)] = $hash;
196 + continue;
197 + }
198 + unset($ccheck);
199 +
200 + $imgreplace = array();
201 + //Do the imaging!
202 + if($this->datauris == true && function_exists('base64_encode') && preg_match_all('#(background[^;}]*url\((.*)\)[^;}]*)(?:;|$|})#Usm',$code,$matches))
203 + {
204 + foreach($matches[2] as $count => $quotedurl)
205 + {
206 + $url = trim($quotedurl," \t\n\r\0\x0B\"'");
207 + $path = $this->getpath($url);
208 + if($path != false && preg_match('#\.(jpe?j|png|gif|bmp)$#',$path) && file_exists($path) && is_readable($path) && filesize($path) <= 5120)
209 + {
210 + //It's an image
211 + //Get type
212 + switch(end(explode('.',$path)))
213 + {
214 + case 'jpej':
215 + case 'jpg':
216 + $dataurihead = 'data:image/jpeg;base64,';
217 + break;
218 + case 'gif':
219 + $dataurihead = 'data:image/gif;base64,';
220 + break;
221 + case 'png':
222 + $dataurihead = 'data:image/png;base64,';
223 + break;
224 + case 'bmp':
225 + $dataurihead = 'data:image/bmp;base64,';
226 + break;
227 + default:
228 + $dataurihead = 'data:application/octet-stream;base64,';
229 + }
230 +
231 + //Encode the data
232 + $base64data = base64_encode(file_get_contents($path));
233 +
234 + //Add it to the list for replacement
235 + $imgreplace[$matches[1][$count]] = str_replace($quotedurl,$dataurihead.$base64data,$matches[1][$count]).";\n*".str_replace($quotedurl,'mhtml:%%MHTML%%!'.$mhtmlcount,$matches[1][$count]).";\n_".$matches[1][$count].';';
236 +
237 + //Store image on the mhtml document
238 + $this->mhtml .= "--_\r\nContent-Location:{$mhtmlcount}\r\nContent-Transfer-Encoding:base64\r\n\r\n{$base64data}\r\n";
239 + $mhtmlcount++;
240 + }
241 + }
242 + //Replace the images
243 + $code = str_replace(array_keys($imgreplace),array_values($imgreplace),$code);
244 + }
245 +
246 + //Minify
247 + if($this->yui == false && class_exists('Minify_CSS_Compressor'))
248 + {
249 + $code = trim(Minify_CSS_Compressor::process($code));
250 + }elseif($this->yui == true && autoptimizeYUI::available()){
251 + $code = autoptimizeYUI::compress('css',$code);
252 + }
253 +
254 + $this->hashmap[md5($code)] = $hash;
255 + }
256 + unset($code);
257 + return true;
258 + }
259 +
260 + //Caches the CSS in uncompressed, deflated and gzipped form.
261 + public function cache()
262 + {
263 + if($this->datauris)
264 + {
265 + //MHTML Preparation
266 + $this->mhtml = "/*\r\nContent-Type: multipart/related; boundary=\"_\"\r\n\r\n".$this->mhtml."*/\r\n";
267 + $md5 = md5($this->mhtml);
268 + $cache = new autoptimizeCache($md5,'txt');
269 + if(!$cache->check())
270 + {
271 + //Cache our images for IE
272 + $cache->cache($this->mhtml,'text/plain');
273 + }
274 + $mhtml = AUTOPTIMIZE_CACHE_URL.$cache->getname();
275 + }
276 +
277 + //CSS cache
278 + foreach($this->csscode as $media => $code)
279 + {
280 + if($this->datauris)
281 + {
282 + //Images for ie! Get the right url
283 + $code = str_replace('%%MHTML%%',$mhtml,$code);
284 + }
285 +
286 + $md5 = $this->hashmap[md5($code)];
287 + $cache = new autoptimizeCache($md5,'css');
288 + if(!$cache->check())
289 + {
290 + //Cache our code
291 + $cache->cache($code,'text/css');
292 + }
293 + $this->url[$media] = AUTOPTIMIZE_CACHE_URL.$cache->getname();
294 + }
295 + }
296 +
297 + //Returns the content
298 + public function getcontent()
299 + {
300 + //Restore IE hacks
301 + $this->content = preg_replace('#%%IEHACK%%(.*)%%IEHACK%%#Usie','base64_decode("$1")',$this->content);
302 +
303 + //Restore the full content
304 + if(!empty($this->restofcontent))
305 + {
306 + $this->content .= $this->restofcontent;
307 + $this->restofcontent = '';
308 + }
309 +
310 + //Add the new stylesheets
311 + foreach($this->url as $media => $url)
312 + {
313 + $this->content = str_replace('</head>','<link type="text/css" media="'.$media.'" href="'.$url.'" rel="stylesheet" /></head>',$this->content);
314 + }
315 +
316 + //Return the modified stylesheet
317 + return $this->content;
318 + }
319 +
320 + private function fixurls($file,$code)
321 + {
322 + $file = str_replace(ABSPATH,'/',$file); //Sth like /wp-content/file.css
323 + $dir = dirname($file); //Like /wp-content
324 +
325 + if(preg_match_all('#url\((.*)\)#Usi',$code,$matches))
326 + {
327 + $replace = array();
328 + foreach($matches[1] as $k => $url)
329 + {
330 + //Remove quotes
331 + $url = trim($url," \t\n\r\0\x0B\"'");
332 + if(substr($url,0,1)=='/' || preg_match('#^(https?|ftp)://#i',$url))
333 + {
334 + //URL is absolute
335 + continue;
336 + }else{
337 + //relative URL. Let's fix it!
338 + $newurl = get_settings('home').str_replace('//','/',$dir.'/'.$url); //http://yourblog.com/wp-content/../image.png
339 + $hash = md5($url);
340 + $code = str_replace($matches[0][$k],$hash,$code);
341 + $replace[$hash] = 'url('.$newurl.')';
342 + }
343 + }
344 +
345 + //Do the replacing here to avoid breaking URLs
346 + $code = str_replace(array_keys($replace),array_values($replace),$code);
347 + }
348 +
349 + return $code;
350 + }
351 +}