PluginProbe
Autoptimize / 1.8.2
Autoptimize v1.8.2
3.1.16 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 All 108 releases
← All changes | classes/autoptimizeStyles.php +468 -701 2.3.3 → 1.8.2 View file →
@@ -1,701 +1,468 @@
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 +if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
3 +
4 +class autoptimizeStyles extends autoptimizeBase {
5 + private $css = array();
6 + private $csscode = array();
7 + private $url = array();
8 + private $restofcontent = '';
9 + private $mhtml = '';
10 + private $datauris = false;
11 + private $hashmap = array();
12 +
13 + //Reads the page and collects style tags
14 + public function read($options) {
15 + //Remove everything that's not the header
16 + if($options['justhead'] == true) {
17 + $content = explode('</head>',$this->content,2);
18 + $this->content = $content[0].'</head>';
19 + $this->restofcontent = $content[1];
20 + }
21 +
22 + // what CSS shouldn't be autoptimized
23 + $excludeCSS = $options['css_exclude'];
24 + $excludeCSS = apply_filters( 'autoptimize_filter_css_exclude', $excludeCSS );
25 + if ($excludeCSS!=="") {
26 + $this->dontmove = array_filter(array_map('trim',explode(",",$excludeCSS)));
27 + }
28 +
29 + // should we defer css?
30 + $this->defer = $options['defer'];
31 +
32 + // should we inline?
33 + $this->inline = $options['inline'];
34 +
35 + // get cdn url
36 + $this->cdn_url = $options['cdn_url'];
37 +
38 + // Store data: URIs setting for later use
39 + $this->datauris = $options['datauris'];
40 +
41 + // noptimize me
42 + $this->content = $this->hide_noptimize($this->content);
43 +
44 + // exclude noscript, as those may contain CSS
45 + if ( strpos( $this->content, '<noscript>' ) !== false ) {
46 + $this->content = preg_replace_callback(
47 + '#<noscript>.*?</noscript>#is',
48 + create_function(
49 + '$matches',
50 + 'return "%%NOSCRIPT%%".base64_encode($matches[0])."%%NOSCRIPT%%";'
51 + ),
52 + $this->content
53 + );
54 + }
55 +
56 + // Save IE hacks
57 + $this->content = $this->hide_iehacks($this->content);
58 +
59 + // hide comments
60 + $this->content = $this->hide_comments($this->content);
61 +
62 + // Get <style> and <link>
63 + if(preg_match_all('#(<style[^>]*>.*</style>)|(<link[^>]*stylesheet[^>]*>)#Usmi',$this->content,$matches)) {
64 + foreach($matches[0] as $tag) {
65 + if ($this->ismovable($tag)) {
66 + // Get the media
67 + if(strpos($tag,'media=')!==false) {
68 + preg_match('#media=(?:"|\')([^>]*)(?:"|\')#Ui',$tag,$medias);
69 + $medias = explode(',',$medias[1]);
70 + $media = array();
71 + foreach($medias as $elem) {
72 + // $media[] = current(explode(' ',trim($elem),2));
73 + $media[] = $elem;
74 + }
75 + } else {
76 + //No media specified - applies to all
77 + $media = array('all');
78 + }
79 +
80 + if(preg_match('#<link.*href=("|\')(.*)("|\')#Usmi',$tag,$source)) {
81 + //<link>
82 + $url = current(explode('?',$source[2],2));
83 + $path = $this->getpath($url);
84 +
85 + if($path !==false && preg_match('#\.css$#',$path)) {
86 + //Good link
87 + $this->css[] = array($media,$path);
88 + }else{
89 + //Link is dynamic (.php etc)
90 + $tag = '';
91 + }
92 + } else {
93 + //<style>
94 + preg_match('#<style.*>(.*)</style>#Usmi',$tag,$code);
95 + $code = preg_replace('#^.*<!\[CDATA\[(?:\s*\*/)?(.*)(?://|/\*)\s*?\]\]>.*$#sm','$1',$code[1]);
96 + $this->css[] = array($media,'INLINE;'.$code);
97 + }
98 +
99 + //Remove the original style tag
100 + $this->content = str_replace($tag,'',$this->content);
101 + }
102 + }
103 + return true;
104 + }
105 + // Really, no styles?
106 + return false;
107 + }
108 +
109 + // Joins and optimizes CSS
110 + public function minify() {
111 + foreach($this->css as $group) {
112 + list($media,$css) = $group;
113 + if(preg_match('#^INLINE;#',$css)) {
114 + //<style>
115 + $css = preg_replace('#^INLINE;#','',$css);
116 + $css = $this->fixurls(ABSPATH.'/index.php',$css);
117 + } else {
118 + //<link>
119 + if($css !== false && file_exists($css) && is_readable($css)) {
120 + $css = $this->fixurls($css,file_get_contents($css));
121 + $css = preg_replace('/\x{EF}\x{BB}\x{BF}/','',$css);
122 + } else {
123 + //Couldn't read CSS. Maybe getpath isn't working?
124 + $css = '';
125 + }
126 + }
127 +
128 + foreach($media as $elem) {
129 + if(!isset($this->csscode[$elem]))
130 + $this->csscode[$elem] = '';
131 + $this->csscode[$elem] .= "\n/*FILESTART*/".$css;
132 + }
133 + }
134 +
135 + // Check for duplicate code
136 + $md5list = array();
137 + $tmpcss = $this->csscode;
138 + foreach($tmpcss as $media => $code) {
139 + $md5sum = md5($code);
140 + $medianame = $media;
141 + foreach($md5list as $med => $sum) {
142 + //If same code
143 + if($sum === $md5sum) {
144 + //Add the merged code
145 + $medianame = $med.', '.$media;
146 + $this->csscode[$medianame] = $code;
147 + $md5list[$medianame] = $md5list[$med];
148 + unset($this->csscode[$med], $this->csscode[$media]);
149 + unset($md5list[$med]);
150 + }
151 + }
152 + $md5list[$medianame] = $md5sum;
153 + }
154 + unset($tmpcss);
155 +
156 + //Manage @imports, while is for recursive import management
157 + foreach($this->csscode as &$thiscss) {
158 + // Flag to trigger import reconstitution and var to hold external imports
159 + $fiximports = false;
160 + $external_imports = "";
161 +
162 + while(preg_match_all('#^(/*\s?)@import.*(?:;|$)#Um',$thiscss,$matches)) {
163 + foreach($matches[0] as $import) {
164 + $url = trim(preg_replace('#^.*((?:https?|ftp)://.*\.css).*$#','$1',trim($import))," \t\n\r\0\x0B\"'");
165 + $path = $this->getpath($url);
166 + $import_ok = false;
167 + if (file_exists($path) && is_readable($path)) {
168 + $code = addcslashes($this->fixurls($path,file_get_contents($path)),"\\");
169 + $code = preg_replace('/\x{EF}\x{BB}\x{BF}/','',$code);
170 + if(!empty($code)) {
171 + $tmp_thiscss = preg_replace('#(/\*FILESTART\*/.*)'.preg_quote($import,'#').'#Us','/*FILESTART2*/'.$code.'$1',$thiscss);
172 + if (!empty($tmp_thiscss)) {
173 + $thiscss = $tmp_thiscss;
174 + $import_ok = true;
175 + unset($tmp_thiscss);
176 + }
177 + unset($code);
178 + }
179 + }
180 +
181 + if (!$import_ok) {
182 + // external imports and general fall-back
183 + $external_imports .= $import;
184 + $thiscss = str_replace($import,'',$thiscss);
185 + $fiximports = true;
186 + }
187 + }
188 + $thiscss = preg_replace('#/\*FILESTART\*/#','',$thiscss);
189 + $thiscss = preg_replace('#/\*FILESTART2\*/#','/*FILESTART*/',$thiscss);
190 + }
191 +
192 + // add external imports to top of aggregated CSS
193 + if($fiximports) {
194 + $thiscss=$external_imports.$thiscss;
195 + }
196 + }
197 + unset($thiscss);
198 +
199 + // $this->csscode has all the uncompressed code now.
200 + $mhtmlcount = 0;
201 + foreach($this->csscode as &$code) {
202 + // Check for already-minified code
203 + $hash = md5($code);
204 + $ccheck = new autoptimizeCache($hash,'css');
205 + if($ccheck->check()) {
206 + $code = $ccheck->retrieve();
207 + $this->hashmap[md5($code)] = $hash;
208 + continue;
209 + }
210 + unset($ccheck);
211 + //Do the imaging!
212 + $imgreplace = array();
213 + preg_match_all('#(background[^;}]*url\((?!data)(.*)\)[^;}]*)(?:;|$|})#Usm',$code,$matches);
214 +
215 + if(($this->datauris == true) && (function_exists('base64_encode')) && (is_array($matches))) {
216 + foreach($matches[2] as $count => $quotedurl) {
217 + $url = trim($quotedurl," \t\n\r\0\x0B\"'");
218 +
219 + // if querystring, remove it from url
220 + if (strpos($url,'?') !== false) { $url = reset(explode('?',$url)); }
221 +
222 + $path = $this->getpath($url);
223 +
224 + $datauri_max_size = 2560;
225 + $datauri_max_size = (int) apply_filters( 'autoptimize_filter_css_datauri_maxsize', $datauri_max_size );
226 +
227 + if($path != false && preg_match('#\.(jpe?g|png|gif|bmp)$#',$path) && file_exists($path) && is_readable($path) && filesize($path) <= $datauri_max_size) {
228 + //It's an image, get the type
229 + $type=end(explode('.',$path));
230 + switch($type) {
231 + case 'jpeg':
232 + $dataurihead = 'data:image/jpeg;base64,';
233 + break;
234 + case 'jpg':
235 + $dataurihead = 'data:image/jpeg;base64,';
236 + break;
237 + case 'gif':
238 + $dataurihead = 'data:image/gif;base64,';
239 + break;
240 + case 'png':
241 + $dataurihead = 'data:image/png;base64,';
242 + break;
243 + case 'bmp':
244 + $dataurihead = 'data:image/bmp;base64,';
245 + break;
246 + default:
247 + $dataurihead = 'data:application/octet-stream;base64,';
248 + }
249 +
250 + //Encode the data
251 + $base64data = base64_encode(file_get_contents($path));
252 +
253 + //Add it to the list for replacement
254 + $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].';';
255 +
256 + //Store image on the mhtml document
257 + $this->mhtml .= "--_\r\nContent-Location:{$mhtmlcount}\r\nContent-Transfer-Encoding:base64\r\n\r\n{$base64data}\r\n";
258 + $mhtmlcount++;
259 + }
260 + }
261 + } else if ((is_array($matches)) && (!empty($this->cdn_url))) {
262 + // change background image urls to cdn-url
263 + foreach($matches[2] as $count => $quotedurl) {
264 + $url = trim($quotedurl," \t\n\r\0\x0B\"'");
265 + $cdn_url=$this->url_replace_cdn($url);
266 + $imgreplace[$matches[1][$count]] = str_replace($quotedurl,$cdn_url,$matches[1][$count]);
267 + }
268 + }
269 +
270 + if(!empty($imgreplace)) {
271 + $code = str_replace(array_keys($imgreplace),array_values($imgreplace),$code);
272 + }
273 +
274 + //Minify
275 + if (class_exists('Minify_CSS_Compressor')) {
276 + $tmp_code = trim(Minify_CSS_Compressor::process($code));
277 + } else if(class_exists('CSSmin')) {
278 + $cssmin = new CSSmin();
279 + if (method_exists($cssmin,"run")) {
280 + $tmp_code = trim($cssmin->run($code));
281 + } elseif (is_callable(array($cssmin,"minify"))) {
282 + $tmp_code = trim(CssMin::minify($code));
283 + }
284 + }
285 +
286 + if (!empty($tmp_code)) {
287 + $code = $tmp_code;
288 + unset($tmp_code);
289 + }
290 +
291 + $this->hashmap[md5($code)] = $hash;
292 + }
293 + unset($code);
294 + return true;
295 + }
296 +
297 + //Caches the CSS in uncompressed, deflated and gzipped form.
298 + public function cache() {
299 + if($this->datauris) {
300 + // MHTML Preparation
301 + $this->mhtml = "/*\r\nContent-Type: multipart/related; boundary=\"_\"\r\n\r\n".$this->mhtml."*/\r\n";
302 + $md5 = md5($this->mhtml);
303 + $cache = new autoptimizeCache($md5,'txt');
304 + if(!$cache->check()) {
305 + //Cache our images for IE
306 + $cache->cache($this->mhtml,'text/plain');
307 + }
308 + $mhtml = AUTOPTIMIZE_CACHE_URL.$cache->getname();
309 + }
310 +
311 + //CSS cache
312 + foreach($this->csscode as $media => $code) {
313 + $md5 = $this->hashmap[md5($code)];
314 +
315 + if($this->datauris) {
316 + // Images for ie! Get the right url
317 + $code = str_replace('%%MHTML%%',$mhtml,$code);
318 + }
319 +
320 + $cache = new autoptimizeCache($md5,'css');
321 + if(!$cache->check()) {
322 + // Cache our code
323 + $cache->cache($code,'text/css');
324 + }
325 + $this->url[$media] = AUTOPTIMIZE_CACHE_URL.$cache->getname();
326 + }
327 + }
328 +
329 + //Returns the content
330 + public function getcontent() {
331 + // restore IE hacks
332 + $this->content = $this->restore_iehacks($this->content);
333 +
334 + // restore comments
335 + $this->content = $this->restore_comments($this->content);
336 +
337 + // restore noscript
338 + if ( strpos( $this->content, '%%NOSCRIPT%%' ) !== false ) {
339 + $this->content = preg_replace_callback(
340 + '#%%NOSCRIPT%%(.*?)%%NOSCRIPT%%#is',
341 + create_function(
342 + '$matches',
343 + 'return stripslashes(base64_decode($matches[1]));'
344 + ),
345 + $this->content
346 + );
347 + }
348 +
349 + // restore noptimize
350 + $this->content = $this->restore_noptimize($this->content);
351 +
352 + //Restore the full content
353 + if(!empty($this->restofcontent)) {
354 + $this->content .= $this->restofcontent;
355 + $this->restofcontent = '';
356 + }
357 +
358 + $warn_html_template=false;
359 +
360 + //Add the new stylesheets
361 + if ($this->inline == true) {
362 + foreach($this->csscode as $media => $code) {
363 + if (strpos($this->content,"<title")!==false) {
364 + $this->content = str_replace('<title','<style type="text/css" media="'.$media.'">'.$code.'</style><title',$this->content);
365 + } else {
366 + $warn_html_template=true;
367 + $this->content .= '<style type="text/css" media="'.$media.'">'.$code.'</style>';
368 + }
369 + }
370 + } else {
371 + if($this->defer == true) {
372 + $deferredCssBlock = "<script>function lCss(url,media) {var d=document;var l=d.createElement('link');l.rel='stylesheet';l.type='text/css';l.href=url;l.media=media; d.getElementsByTagName('head')[0].appendChild(l);}function deferredCSS() {";
373 + $noScriptCssBlock = "<noscript>";
374 + }
375 +
376 + foreach($this->url as $media => $url) {
377 + $url = $this->url_replace_cdn($url);
378 +
379 + //Add the stylesheet either deferred (import at bottom) or normal links in head
380 + if($this->defer == true) {
381 + $deferredCssBlock .= "lCss('".$url."','".$media."');";
382 + $noScriptCssBlock .= '<link type="text/css" media="'.$media.'" href="'.$url.'" rel="stylesheet" />';
383 + } else {
384 + if (strpos($this->content,"<title")!==false) {
385 + $this->content = str_replace('<title','<link type="text/css" media="'.$media.'" href="'.$url.'" rel="stylesheet" /><title',$this->content);
386 + } else {
387 + $warn_html_template=true;
388 + $this->content .= '<link type="text/css" media="'.$media.'" href="'.$url.'" rel="stylesheet" />';
389 + }
390 + }
391 + }
392 +
393 + if($this->defer == true) {
394 + $deferredCssBlock .= "}if(window.addEventListener){window.addEventListener('DOMContentLoaded',deferredCSS,false);}else{window.onload = deferredCSS;}</script>";
395 + $noScriptCssBlock .= "</noscript>";
396 + if (strpos($this->content,"<title")!==false) {
397 + $this->content = str_replace('<title',$noScriptCssBlock.'<title',$this->content);
398 + } else {
399 + $warn_html_template=true;
400 + $this->content .= $noScriptCssBlock;
401 + }
402 + if (strpos($this->content,"</body>")!==false) {
403 + $this->content = str_replace('</body>',$deferredCssBlock.'</body>',$this->content);
404 + } else {
405 + $warn_html_template=true;
406 + $this->content .= $deferredCssBlock;
407 + }
408 + }
409 + }
410 +
411 + if ($warn_html_template) {
412 + $this->warn_html();
413 + }
414 +
415 + //Return the modified stylesheet
416 + return $this->content;
417 + }
418 +
419 + private function fixurls($file,$code) {
420 + // $file = str_replace(ABSPATH,'/',$file); //Sth like /wp-content/file.css
421 + $file = str_replace(WP_ROOT_DIR,'/',$file);
422 + $dir = dirname($file); //Like /wp-content
423 +
424 + // quick fix for import-troubles in e.g. arras theme
425 + $code=preg_replace('#@import ("|\')(.+?)\.css("|\')#','@import url("${2}.css")',$code);
426 +
427 + if(preg_match_all('#url\((?!data)(.*)\)#Usi',$code,$matches))
428 + {
429 + $replace = array();
430 + foreach($matches[1] as $k => $url)
431 + {
432 + //Remove quotes
433 + $url = trim($url," \t\n\r\0\x0B\"'");
434 + if(substr($url,0,1)=='/' || preg_match('#^(https?://|ftp://|data:)#i',$url))
435 + {
436 + //URL is absolute
437 + continue;
438 + }else{
439 + // relative URL
440 + $newurl = AUTOPTIMIZE_WP_ROOT_URL.str_replace('//','/',$dir.'/'.$url);
441 + $hash = md5($url);
442 + $code = str_replace($matches[0][$k],$hash,$code);
443 + $replace[$hash] = 'url('.$newurl.')';
444 + }
445 + }
446 +
447 + //Do the replacing here to avoid breaking URLs
448 + $code = str_replace(array_keys($replace),array_values($replace),$code);
449 + }
450 +
451 + return $code;
452 + }
453 +
454 + private function ismovable($tag) {
455 + if (is_array($this->dontmove)) {
456 + foreach($this->dontmove as $match) {
457 + if(strpos($tag,$match)!==false) {
458 + //Matched something
459 + return false;
460 + }
461 + }
462 + }
463 +
464 + //If we're here it's safe to move
465 + return true;
466 + }
467 +
468 +}