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 -701 2.3.31.2 View file →
@@ -1,701 +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 - $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.onload=null;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\'>!function(t){"use strict";t.loadCSS||(t.loadCSS=function(){});var e=loadCSS.relpreload={};if(e.support=function(){var e;try{e=t.document.createElement("link").relList.supports("preload")}catch(t){e=!1}return function(){return e}}(),e.bindMediaToggle=function(t){function e(){t.media=a}var a=t.media||"all";t.addEventListener?t.addEventListener("load",e):t.attachEvent&&t.attachEvent("onload",e),setTimeout(function(){t.rel="stylesheet",t.media="only x"}),setTimeout(e,3e3)},e.poly=function(){if(!e.support())for(var a=t.document.getElementsByTagName("link"),n=0;n<a.length;n++){var o=a[n];"preload"!==o.rel||"style"!==o.getAttribute("as")||o.getAttribute("data-loadcss")||(o.setAttribute("data-loadcss",!0),e.bindMediaToggle(o))}},!e.support()){e.poly();var a=t.setInterval(e.poly,500);t.addEventListener?t.addEventListener("load",function(){e.poly(),t.clearInterval(a)}):t.attachEvent&&t.attachEvent("onload",function(){e.poly(),t.clearInterval(a)})}"undefined"!=typeof exports?exports.loadCSS=loadCSS:t.loadCSS=loadCSS}("undefined"!=typeof global?global:this);</script>';
560 - $noScriptCssBlock .= "</noscript>";
561 - $this->inject_in_html($preloadCssBlock.$noScriptCssBlock,$replaceTag);
562 -
563 - // Adds preload polyfill at end of body tag
564 - $this->inject_in_html(
565 - apply_filters('autoptimize_css_preload_polyfill', $preloadPolyfill),
566 - array('</body>','before')
567 - );
568 - }
569 - }
570 -
571 - //Return the modified stylesheet
572 - return $this->content;
573 - }
574 -
575 - static function fixurls($file, $code) {
576 - // Switch all imports to the url() syntax
577 - $code = preg_replace( '#@import ("|\')(.+?)\.css.*?("|\')#', '@import url("${2}.css")', $code );
578 -
579 - if ( preg_match_all( self::ASSETS_REGEX, $code, $matches ) ) {
580 - $file = str_replace( WP_ROOT_DIR, '/', $file );
581 - $dir = dirname( $file ); // Like /themes/expound/css
582 -
583 - // $dir should not contain backslashes, since it's used to replace
584 - // urls, but it can contain them when running on Windows because
585 - // fixurls() is sometimes called with `ABSPATH . 'index.php'`
586 - $dir = str_replace( '\\', '/', $dir );
587 - unset( $file ); // not used below at all
588 -
589 - $replace = array();
590 - foreach ( $matches[1] as $k => $url ) {
591 - // Remove quotes
592 - $url = trim( $url," \t\n\r\0\x0B\"'" );
593 - $noQurl = trim( $url, "\"'" );
594 - if ( $url !== $noQurl ) {
595 - $removedQuotes = true;
596 - } else {
597 - $removedQuotes = false;
598 - }
599 -
600 - if ( '' === $noQurl ) {
601 - continue;
602 - }
603 -
604 - $url = $noQurl;
605 - if ( '/' === $url{0} || preg_match( '#^(https?://|ftp://|data:)#i', $url ) ) {
606 - // URL is protocol-relative, host-relative or something we don't touch
607 - continue;
608 - } else {
609 - // Relative URL
610 - $newurl = preg_replace( '/https?:/', '', str_replace( ' ', '%20', AUTOPTIMIZE_WP_ROOT_URL . str_replace( '//', '/', $dir . '/' . $url ) ) );
611 -
612 - // Hash the url + whatever was behind potentially for replacement
613 - // We must do this, or different css classes referencing the same bg image (but
614 - // different parts of it, say, in sprites and such) loose their stuff...
615 - $hash = md5( $url . $matches[2][$k] );
616 - $code = str_replace( $matches[0][$k], $hash, $code );
617 -
618 - if ( $removedQuotes ) {
619 - $replace[$hash] = "url('" . $newurl . "')" . $matches[2][$k];
620 - } else {
621 - $replace[$hash] = 'url(' . $newurl . ')' . $matches[2][$k];
622 - }
623 - }
624 - }
625 -
626 - if ( ! empty( $replace ) ) {
627 - // Sort the replacements array by key length in desc order (so that the longest strings are replaced first)
628 - $keys = array_map( 'strlen', array_keys( $replace ) );
629 - array_multisort( $keys, SORT_DESC, $replace );
630 -
631 - // Replace URLs found within $code
632 - $code = str_replace( array_keys( $replace ), array_values( $replace ), $code );
633 - }
634 - }
635 -
636 - return $code;
637 - }
638 -
639 - private function ismovable($tag) {
640 - if ( apply_filters('autoptimize_filter_css_dontaggregate', false) ) {
641 - return false;
642 - } else if (!empty($this->whitelist)) {
643 - foreach ($this->whitelist as $match) {
644 - if(strpos($tag,$match)!==false) {
645 - return true;
646 - }
647 - }
648 - // no match with whitelist
649 - return false;
650 - } else {
651 - if (is_array($this->dontmove)) {
652 - foreach($this->dontmove as $match) {
653 - if(strpos($tag,$match)!==false) {
654 - //Matched something
655 - return false;
656 - }
657 - }
658 - }
659 -
660 - //If we're here it's safe to move
661 - return true;
662 - }
663 - }
664 -
665 - private function can_inject_late($cssPath,$css) {
666 - $consider_minified_array = apply_filters('autoptimize_filter_css_consider_minified', false, $cssPath);
667 - if ( $this->inject_min_late !== true ) {
668 - // late-inject turned off
669 - return false;
670 - } else if ( (strpos($cssPath,"min.css") === false) && ( str_replace($consider_minified_array, '', $cssPath) === $cssPath ) ) {
671 - // file not minified based on filename & filter
672 - return false;
673 - } else if ( strpos($css,"@import") !== false ) {
674 - // can't late-inject files with imports as those need to be aggregated
675 - return false;
676 - } else if ( (strpos($css,"@font-face")!==false ) && ( apply_filters("autoptimize_filter_css_fonts_cdn",false)===true) && (!empty($this->cdn_url)) ) {
677 - // don't late-inject CSS with font-src's if fonts are set to be CDN'ed
678 - return false;
679 - } else if ( (($this->datauris == true) || (!empty($this->cdn_url))) && preg_match("#background[^;}]*url\(#Ui",$css) ) {
680 - // don't late-inject CSS with images if CDN is set OR is image inlining is on
681 - return false;
682 - } else {
683 - // phew, all is safe, we can late-inject
684 - return true;
685 - }
686 - }
687 -
688 - private function maybe_cdn_urls($inUrl) {
689 - $url = trim($inUrl," \t\n\r\0\x0B\"'");
690 - $urlPath = parse_url($url,PHP_URL_PATH);
691 -
692 - // exclude fonts from CDN except if filter returns true
693 - if ( !preg_match('#\.(woff2?|eot|ttf|otf)$#i',$urlPath) || apply_filters('autoptimize_filter_css_fonts_cdn',false) ) {
694 - $cdn_url = $this->url_replace_cdn($url);
695 - } else {
696 - $cdn_url = $url;
697 - }
698 -
699 - return $cdn_url;
700 - }
701 -}
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 +}