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 +346 -1065 2.4.01.2 View file →
@@ -1,1070 +1,351 @@
1 1 <?php
2 -/**
3 - * Class for CSS optimization.
4 - */
5 -
6 -if ( ! defined( 'ABSPATH' ) ) {
7 - exit;
8 -}
9 -
10 2 class autoptimizeStyles extends autoptimizeBase
11 3 {
12 - const ASSETS_REGEX = '/url\s*\(\s*(?!["\']?data:)(?![\'|\"]?[\#|\%|])([^)]+)\s*\)([^;},\s]*)/i';
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 + }
13 63
14 - /**
15 - * Font-face regex-fu from HamZa at: https://stackoverflow.com/a/21395083
16 - * ~
17 - * @font-face\s* # Match @font-face and some spaces
18 - * ( # Start group 1
19 - * \{ # Match {
20 - * (?: # A non-capturing group
21 - * [^{}]+ # Match anything except {} one or more times
22 - * | # Or
23 - * (?1) # Recurse/rerun the expression of group 1
24 - * )* # Repeat 0 or more times
25 - * \} # Match }
26 - * ) # End group 1
27 - * ~xs';
28 - */
29 - const FONT_FACE_REGEX = '~@font-face\s*(\{(?:[^{}]+|(?1))*\})~xsi'; // added `i` flag for case-insensitivity.
30 -
31 - private $css = array();
32 - private $csscode = array();
33 - private $url = array();
34 - private $restofcontent = '';
35 - private $datauris = false;
36 - private $hashmap = array();
37 - private $alreadyminified = false;
38 - private $aggregate = true;
39 - private $inline = false;
40 - private $defer = false;
41 - private $defer_inline = false;
42 - private $whitelist = '';
43 - private $cssinlinesize = '';
44 - private $cssremovables = array();
45 - private $include_inline = false;
46 - private $inject_min_late = '';
47 -
48 - // public $cdn_url; // Used all over the place implicitly, so will have to be either public or protected :/ .
49 -
50 - // Reads the page and collects style tags.
51 - public function read( $options )
52 - {
53 - $noptimizeCSS = apply_filters( 'autoptimize_filter_css_noptimize', false, $this->content );
54 - if ( $noptimizeCSS ) {
55 - return false;
56 - }
57 -
58 - $whitelistCSS = apply_filters( 'autoptimize_filter_css_whitelist', '', $this->content );
59 - if ( ! empty( $whitelistCSS ) ) {
60 - $this->whitelist = array_filter( array_map( 'trim', explode( ',', $whitelistCSS ) ) );
61 - }
62 -
63 - $removableCSS = apply_filters( 'autoptimize_filter_css_removables', '' );
64 - if ( ! empty( $removableCSS ) ) {
65 - $this->cssremovables = array_filter( array_map( 'trim', explode( ',', $removableCSS ) ) );
66 - }
67 -
68 - $this->cssinlinesize = apply_filters( 'autoptimize_filter_css_inlinesize', 256 );
69 -
70 - // filter to "late inject minified CSS", default to true for now (it is faster).
71 - $this->inject_min_late = apply_filters( 'autoptimize_filter_css_inject_min_late', true );
72 -
73 - // Remove everything that's not the header.
74 - if ( apply_filters( 'autoptimize_filter_css_justhead', $options['justhead'] ) ) {
75 - $content = explode( '</head>', $this->content, 2 );
76 - $this->content = $content[0] . '</head>';
77 - $this->restofcontent = $content[1];
78 - }
79 -
80 - // Determine whether we're doing CSS-files aggregation or not.
81 - if ( isset( $options['aggregate'] ) && ! $options['aggregate'] ) {
82 - $this->aggregate = false;
83 - }
84 - // Returning true for "dontaggregate" turns off aggregation.
85 - if ( $this->aggregate && apply_filters( 'autoptimize_filter_css_dontaggregate', false ) ) {
86 - $this->aggregate = false;
87 - }
88 -
89 - // include inline?
90 - if ( apply_filters( 'autoptimize_css_include_inline', $options['include_inline'] ) ) {
91 - $this->include_inline = true;
92 - }
93 -
94 - // List of CSS strings which are excluded from autoptimization.
95 - $excludeCSS = apply_filters( 'autoptimize_filter_css_exclude', $options['css_exclude'], $this->content );
96 - if ( '' !== $excludeCSS ) {
97 - $this->dontmove = array_filter( array_map( 'trim', explode( ',', $excludeCSS ) ) );
98 - } else {
99 - $this->dontmove = array();
100 - }
101 -
102 - // forcefully exclude CSS with data-noptimize attrib.
103 - $this->dontmove[] = 'data-noptimize';
104 -
105 - // Should we defer css?
106 - // value: true / false.
107 - $this->defer = $options['defer'];
108 - $this->defer = apply_filters( 'autoptimize_filter_css_defer', $this->defer, $this->content );
109 -
110 - // Should we inline while deferring?
111 - // value: inlined CSS.
112 - $this->defer_inline = apply_filters( 'autoptimize_filter_css_defer_inline', $options['defer_inline'], $this->content );
113 -
114 - // Should we inline?
115 - // value: true / false.
116 - $this->inline = $options['inline'];
117 - $this->inline = apply_filters( 'autoptimize_filter_css_inline', $this->inline, $this->content );
118 -
119 - // Store cdn url.
120 - $this->cdn_url = $options['cdn_url'];
121 -
122 - // Store data: URIs setting for later use.
123 - $this->datauris = $options['datauris'];
124 -
125 - // noptimize me.
126 - $this->content = $this->hide_noptimize( $this->content );
127 -
128 - // Exclude (no)script, as those may contain CSS which should be left as is.
129 - $this->content = $this->replace_contents_with_marker_if_exists(
130 - 'SCRIPT',
131 - '<script',
132 - '#<(?:no)?script.*?<\/(?:no)?script>#is',
133 - $this->content
134 - );
135 -
136 - // Save IE hacks.
137 - $this->content = $this->hide_iehacks( $this->content );
138 -
139 - // Hide HTML comments.
140 - $this->content = $this->hide_comments( $this->content );
141 -
142 - // Get <style> and <link>.
143 - if ( preg_match_all( '#(<style[^>]*>.*</style>)|(<link[^>]*stylesheet[^>]*>)#Usmi', $this->content, $matches ) ) {
144 -
145 - foreach ( $matches[0] as $tag ) {
146 - if ( $this->isremovable( $tag, $this->cssremovables ) ) {
147 - $this->content = str_replace( $tag, '', $this->content );
148 - } elseif ( $this->ismovable( $tag ) ) {
149 - // Get the media.
150 - if ( false !== strpos( $tag, 'media=' ) ) {
151 - preg_match( '#media=(?:"|\')([^>]*)(?:"|\')#Ui', $tag, $medias );
152 - $medias = explode( ',', $medias[1] );
153 - $media = array();
154 - foreach ( $medias as $elem ) {
155 - /* $media[] = current(explode(' ',trim($elem),2)); */
156 - if ( empty( $elem ) ) {
157 - $elem = 'all';
158 - }
159 -
160 - $media[] = $elem;
161 - }
162 - } else {
163 - // No media specified - applies to all.
164 - $media = array( 'all' );
165 - }
166 -
167 - $media = apply_filters( 'autoptimize_filter_css_tagmedia', $media, $tag );
168 -
169 - if ( preg_match( '#<link.*href=("|\')(.*)("|\')#Usmi', $tag, $source ) ) {
170 - // <link>.
171 - $url = current( explode( '?', $source[2], 2 ) );
172 - $path = $this->getpath( $url );
173 -
174 - if ( false !== $path && preg_match( '#\.css$#', $path ) ) {
175 - // Good link.
176 - $this->css[] = array( $media, $path );
177 - } else {
178 - // Link is dynamic (.php etc).
179 - $tag = '';
180 - }
181 - } else {
182 - // Inline css in style tags can be wrapped in comment tags, so restore comments.
183 - $tag = $this->restore_comments( $tag );
184 - preg_match( '#<style.*>(.*)</style>#Usmi', $tag, $code );
185 -
186 - // And re-hide them to be able to to the removal based on tag.
187 - $tag = $this->hide_comments( $tag );
188 -
189 - if ( $this->include_inline ) {
190 - $code = preg_replace( '#^.*<!\[CDATA\[(?:\s*\*/)?(.*)(?://|/\*)\s*?\]\]>.*$#sm', '$1', $code[1] );
191 - $this->css[] = array( $media, 'INLINE;' . $code );
192 - } else {
193 - $tag = '';
194 - }
195 - }
196 -
197 - // Remove the original style tag.
198 - $this->content = str_replace( $tag, '', $this->content );
199 - } else {
200 - // Excluded CSS, minify if getpath and filter says so...
201 - if ( preg_match( '#<link.*href=("|\')(.*)("|\')#Usmi', $tag, $source ) ) {
202 - $exploded_url = explode( '?', $source[2], 2 );
203 - $url = $exploded_url[0];
204 - $path = $this->getpath( $url );
205 -
206 - if ( $path && apply_filters( 'autoptimize_filter_css_minify_excluded', true, $url ) ) {
207 - $minified_url = $this->minify_single( $path );
208 - if ( ! empty( $minified_url ) ) {
209 - // Replace orig URL with cached minified URL.
210 - $new_tag = str_replace( $url, $minified_url, $tag );
211 - } else {
212 - $new_tag = $tag;
213 - }
214 -
215 - // Removes querystring from URL.
216 - if ( ! empty( $exploded_url[1] ) ) {
217 - $new_tag = str_replace( '?' . $exploded_url[1], '', $new_tag );
218 - }
219 -
220 - // Defer single CSS if "inline & defer" is ON and there is inline CSS.
221 - if ( $this->defer && ! empty( $this->defer_inline ) ) {
222 - // Get/ set (via filter) the JS to be triggers onload of the preloaded CSS.
223 - $_preload_onload = apply_filters(
224 - 'autoptimize_filter_css_preload_onload',
225 - "this.onload=null;this.rel='stylesheet'",
226 - $url
227 - );
228 - // Adapt original <link> element for CSS to be preloaded and add <noscript>-version for fallback.
229 - $new_tag = '<noscript>' . $new_tag . '</noscript>' . str_replace(
230 - array(
231 - "rel='stylesheet'",
232 - 'rel="stylesheet"',
233 - ),
234 - "rel='preload' as='style' onload=\"" . $_preload_onload . "\"",
235 - $new_tag
236 - );
237 - }
238 -
239 - // And replace!
240 - $this->content = str_replace( $tag, $new_tag, $this->content );
241 - }
242 - }
243 - }
244 - }
245 - return true;
246 - }
247 -
248 - // Really, no styles?
249 - return false;
250 - }
251 -
252 - /**
253 - * Checks if the local file referenced by $path is a valid
254 - * candidate for being inlined into a data: URI
255 - *
256 - * @param string $path
257 - * @return boolean
258 - */
259 - private function is_datauri_candidate( $path )
260 - {
261 - // Call only once since it's called from a loop.
262 - static $max_size = null;
263 - if ( null === $max_size ) {
264 - $max_size = $this->get_datauri_maxsize();
265 - }
266 -
267 - if ( $path && preg_match( '#\.(jpe?g|png|gif|webp|bmp)$#i', $path ) &&
268 - file_exists( $path ) && is_readable( $path ) && filesize( $path ) <= $max_size ) {
269 -
270 - // Seems we have a candidate.
271 - $is_candidate = true;
272 - } else {
273 - // Filter allows overriding default decision (which checks for local file existence).
274 - $is_candidate = apply_filters( 'autoptimize_filter_css_is_datauri_candidate', false, $path );
275 - }
276 -
277 - return $is_candidate;
278 - }
279 -
280 - /**
281 - * Returns the amount of bytes that shouldn't be exceeded if a file is to
282 - * be inlined into a data: URI. Defaults to 4096, passed through
283 - * `autoptimize_filter_css_datauri_maxsize` filter.
284 - *
285 - * @return mixed
286 - */
287 - private function get_datauri_maxsize()
288 - {
289 - static $max_size = null;
290 -
291 - /**
292 - * No need to apply the filter multiple times in case the
293 - * method itself is invoked multiple times during a single request.
294 - * This prevents some wild stuff like having different maxsizes
295 - * for different files/site-sections etc. But if you're into that sort
296 - * of thing you're probably better of building assets completely
297 - * outside of WordPress anyway.
298 - */
299 - if ( null === $max_size ) {
300 - $max_size = (int) apply_filters( 'autoptimize_filter_css_datauri_maxsize', 4096 );
301 - }
302 -
303 - return $max_size;
304 - }
305 -
306 - private function check_datauri_exclude_list( $url )
307 - {
308 - static $exclude_list = null;
309 - $no_datauris = array();
310 -
311 - // Again, skip doing certain stuff repeatedly when loop-called.
312 - if ( null === $exclude_list ) {
313 - $exclude_list = apply_filters( 'autoptimize_filter_css_datauri_exclude', '' );
314 - $no_datauris = array_filter( array_map( 'trim', explode( ',', $exclude_list ) ) );
315 - }
316 -
317 - $matched = false;
318 -
319 - if ( ! empty( $exclude_list ) ) {
320 - foreach ( $no_datauris as $no_datauri ) {
321 - if ( false !== strpos( $url, $no_datauri ) ) {
322 - $matched = true;
323 - break;
324 - }
325 - }
326 - }
327 -
328 - return $matched;
329 - }
330 -
331 - private function build_or_get_datauri_image( $path )
332 - {
333 - /**
334 - * TODO/FIXME: document the required return array format, or better yet,
335 - * use a string, since we don't really need an array for this. That would, however,
336 - * require changing even more code, which is not happening right now...
337 - */
338 -
339 - // Allows short-circuiting datauri generation for an image.
340 - $result = apply_filters( 'autoptimize_filter_css_datauri_image', array(), $path );
341 - if ( ! empty( $result ) ) {
342 - if ( is_array( $result ) && isset( $result['full'] ) && isset( $result['base64data'] ) ) {
343 - return $result;
344 - }
345 - }
346 -
347 - $hash = md5( $path );
348 - $check = new autoptimizeCache( $hash, 'img' );
349 - if ( $check->check() ) {
350 - // we have the base64 image in cache.
351 - $headAndData = $check->retrieve();
352 - $_base64data = explode( ';base64,', $headAndData );
353 - $base64data = $_base64data[1];
354 - unset( $_base64data );
355 - } else {
356 - // It's an image and we don't have it in cache, get the type by extension.
357 - $exploded_path = explode( '.', $path );
358 - $type = end( $exploded_path );
359 -
360 - switch ( $type ) {
361 - case 'jpg':
362 - case 'jpeg':
363 - $dataurihead = 'data:image/jpeg;base64,';
364 - break;
365 - case 'gif':
366 - $dataurihead = 'data:image/gif;base64,';
367 - break;
368 - case 'png':
369 - $dataurihead = 'data:image/png;base64,';
370 - break;
371 - case 'bmp':
372 - $dataurihead = 'data:image/bmp;base64,';
373 - break;
374 - case 'webp':
375 - $dataurihead = 'data:image/webp;base64,';
376 - break;
377 - default:
378 - $dataurihead = 'data:application/octet-stream;base64,';
379 - }
380 -
381 - // Encode the data.
382 - $base64data = base64_encode( file_get_contents( $path ) );
383 - $headAndData = $dataurihead . $base64data;
384 -
385 - // Save in cache.
386 - $check->cache( $headAndData, 'text/plain' );
387 - }
388 - unset( $check );
389 -
390 - return array( 'full' => $headAndData, 'base64data' => $base64data );
391 - }
392 -
393 - /**
394 - * Given an array of key/value pairs to replace in $string,
395 - * it does so by replacing the longest-matching strings first.
396 - *
397 - * @param string $string
398 - * @param array $replacements
399 - *
400 - * @return string
401 - */
402 - protected static function replace_longest_matches_first( $string, $replacements = array() )
403 - {
404 - if ( ! empty( $replacements ) ) {
405 - // Sort the replacements array by key length in desc order (so that the longest strings are replaced first).
406 - $keys = array_map( 'strlen', array_keys( $replacements ) );
407 - array_multisort( $keys, SORT_DESC, $replacements );
408 - $string = str_replace( array_keys( $replacements ), array_values( $replacements ), $string );
409 - }
410 -
411 - return $string;
412 - }
413 -
414 - /**
415 - * Rewrites/Replaces any ASSETS_REGEX-matching urls in a string.
416 - * Removes quotes/cruft around each one and passes it through to
417 - * `autoptimizeBase::url_replace_cdn()`.
418 - * Replacements are performed in a `longest-match-replaced-first` way.
419 - *
420 - * @param string $code CSS code.
421 - *
422 - * @return string
423 - */
424 - public function replace_urls( $code = '' )
425 - {
426 - $replacements = array();
427 -
428 - preg_match_all( self::ASSETS_REGEX, $code, $url_src_matches );
429 - if ( is_array( $url_src_matches ) && ! empty( $url_src_matches ) ) {
430 - foreach ( $url_src_matches[1] as $count => $original_url ) {
431 - // Removes quotes and other cruft.
432 - $url = trim( $original_url, " \t\n\r\0\x0B\"'" );
433 -
434 - /**
435 - * TODO/FIXME: Add a way for other code / callable to be called here
436 - * and provide it's own results for the $replacements array
437 - * for the "current" key.
438 - * If such a result is returned/provided, we sholud then avoid
439 - * calling url_replace_cdn() here for the current iteration.
440 - *
441 - * This would maybe allow the inlining logic currently present
442 - * in `autoptimizeStyles::rewrite_assets()` to be "pulled out"
443 - * and given as a callable to this method or something... and
444 - * then we could "just" call `replace_urls()` from within
445 - * `autoptimizeStyles::rewrite_assets()` and avoid some
446 - * (currently present) code/logic duplication.
447 - */
448 -
449 - // Do CDN replacement if needed.
450 - if ( ! empty( $this->cdn_url ) ) {
451 - $replacement_url = $this->url_replace_cdn( $url );
452 - // Prepare replacements array.
453 - $replacements[ $url_src_matches[1][ $count ] ] = str_replace(
454 - $original_url, $replacement_url, $url_src_matches[1][$count]
455 - );
456 - }
457 - }
458 - }
459 -
460 - $code = self::replace_longest_matches_first( $code, $replacements );
461 -
462 - return $code;
463 - }
464 -
465 - /**
466 - * "Hides" @font-face declarations by replacing them with `%%FONTFACE%%` markers.
467 - * Also does CDN replacement of any font-urls within those declarations if the `autoptimize_filter_css_fonts_cdn`
468 - * filter is used.
469 - *
470 - * @param string $code
471 - * @return string
472 - */
473 - public function hide_fontface_and_maybe_cdn( $code )
474 - {
475 - // Proceed only if @font-face declarations exist within $code.
476 - preg_match_all( self::FONT_FACE_REGEX, $code, $fontfaces );
477 - if ( isset( $fontfaces[0] ) ) {
478 - // Check if we need to cdn fonts or not.
479 - $do_font_cdn = apply_filters( 'autoptimize_filter_css_fonts_cdn', false );
480 -
481 - foreach ( $fontfaces[0] as $full_match ) {
482 - // Keep original match so we can search/replace it.
483 - $match_search = $full_match;
484 -
485 - // Do font cdn if needed.
486 - if ( $do_font_cdn ) {
487 - $full_match = $this->replace_urls( $full_match );
488 - }
489 -
490 - // Replace declaration with its base64 encoded string.
491 - $replacement = self::build_marker( 'FONTFACE', $full_match );
492 - $code = str_replace( $match_search, $replacement, $code );
493 - }
494 - }
495 -
496 - return $code;
497 - }
498 -
499 - /**
500 - * Restores original @font-face declarations that have been "hidden"
501 - * using `hide_fontface_and_maybe_cdn()`.
502 - *
503 - * @param string $code
504 - * @return string
505 - */
506 - public function restore_fontface( $code )
507 - {
508 - return $this->restore_marked_content( 'FONTFACE', $code );
509 - }
510 -
511 - // Re-write (and/or inline) referenced assets.
512 - public function rewrite_assets( $code )
513 - {
514 - // Handle @font-face rules by hiding and processing them separately.
515 - $code = $this->hide_fontface_and_maybe_cdn( $code );
516 -
517 - /**
518 - * TODO/FIXME:
519 - * Certain code parts below are kind-of repeated now in `replace_urls()`, which is not ideal.
520 - * There is maybe a way to separate/refactor things and then be able to keep
521 - * the ASSETS_REGEX rewriting/handling logic in a single place (along with removing quotes/cruft from matched urls).
522 - * See comments in `replace_urls()` regarding this. The idea is to extract the inlining
523 - * logic out (which is the only real difference between replace_urls() and the code below), but still
524 - * achieve identical results as before.
525 - */
526 -
527 - // Re-write (and/or inline) URLs to point them to the CDN host.
528 - $url_src_matches = array();
529 - $imgreplace = array();
530 - // Matches and captures anything specified within the literal `url()` and excludes those containing data: URIs.
531 - preg_match_all( self::ASSETS_REGEX, $code, $url_src_matches );
532 - if ( is_array( $url_src_matches ) && ! empty( $url_src_matches ) ) {
533 - foreach ( $url_src_matches[1] as $count => $original_url ) {
534 - // Removes quotes and other cruft.
535 - $url = trim( $original_url, " \t\n\r\0\x0B\"'" );
536 -
537 - // If datauri inlining is turned on, do it.
538 - $inlined = false;
539 - if ( $this->datauris ) {
540 - $iurl = $url;
541 - if ( false !== strpos( $iurl, '?' ) ) {
542 - $iurl = strtok( $iurl, '?' );
543 - }
544 -
545 - $ipath = $this->getpath( $iurl );
546 -
547 - $excluded = $this->check_datauri_exclude_list( $ipath );
548 - if ( ! $excluded ) {
549 - $is_datauri_candidate = $this->is_datauri_candidate( $ipath );
550 - if ( $is_datauri_candidate ) {
551 - $datauri = $this->build_or_get_datauri_image( $ipath );
552 - $base64data = $datauri['base64data'];
553 - // Add it to the list for replacement.
554 - $imgreplace[ $url_src_matches[1][ $count ] ] = str_replace(
555 - $original_url,
556 - $datauri['full'],
557 - $url_src_matches[1][$count]
558 - );
559 - $inlined = true;
560 - }
561 - }
562 - }
563 -
564 - /**
565 - * Doing CDN URL replacement for every found match (if CDN is
566 - * specified). This way we make sure to do it even if
567 - * inlining isn't turned on, or if a resource is skipped from
568 - * being inlined for whatever reason above.
569 - */
570 - if ( ! $inlined && ( ! empty( $this->cdn_url ) || has_filter( 'autoptimize_filter_base_replace_cdn' ) ) ) {
571 - // Just do the "simple" CDN replacement.
572 - $replacement_url = $this->url_replace_cdn( $url );
573 - $imgreplace[ $url_src_matches[1][ $count ] ] = str_replace(
574 - $original_url, $replacement_url, $url_src_matches[1][$count]
575 - );
576 - }
577 - }
578 - }
579 -
580 - $code = self::replace_longest_matches_first( $code, $imgreplace );
581 -
582 - // Replace back font-face markers with actual font-face declarations.
583 - $code = $this->restore_fontface( $code );
584 -
585 - return $code;
586 - }
587 -
588 - // Joins and optimizes CSS.
589 - public function minify()
590 - {
591 - foreach ( $this->css as $group ) {
592 - list( $media, $css ) = $group;
593 - if ( preg_match( '#^INLINE;#', $css ) ) {
594 - // <style>.
595 - $css = preg_replace( '#^INLINE;#', '', $css );
596 - $css = self::fixurls( ABSPATH . 'index.php', $css ); // ABSPATH already contains a trailing slash.
597 - $tmpstyle = apply_filters( 'autoptimize_css_individual_style', $css, '' );
598 - if ( has_filter( 'autoptimize_css_individual_style' ) && ! empty( $tmpstyle ) ) {
599 - $css = $tmpstyle;
600 - $this->alreadyminified = true;
601 - }
602 - } else {
603 - // <link>
604 - if ( false !== $css && file_exists( $css ) && is_readable( $css ) ) {
605 - $cssPath = $css;
606 - $css = self::fixurls( $cssPath, file_get_contents( $cssPath ) );
607 - $css = preg_replace( '/\x{EF}\x{BB}\x{BF}/', '', $css );
608 - $tmpstyle = apply_filters( 'autoptimize_css_individual_style', $css, $cssPath );
609 - if ( has_filter( 'autoptimize_css_individual_style' ) && ! empty( $tmpstyle ) ) {
610 - $css = $tmpstyle;
611 - $this->alreadyminified = true;
612 - } elseif ( $this->can_inject_late( $cssPath, $css ) ) {
613 - $css = self::build_injectlater_marker( $cssPath, md5( $css ) );
614 - }
615 - } else {
616 - // Couldn't read CSS. Maybe getpath isn't working?
617 - $css = '';
618 - }
619 - }
620 -
621 - foreach ( $media as $elem ) {
622 - if ( ! empty( $css ) ) {
623 - if ( ! isset( $this->csscode[$elem] ) ) {
624 - $this->csscode[$elem] = '';
625 - }
626 - $this->csscode[$elem] .= "\n/*FILESTART*/" . $css;
627 - }
628 - }
629 - }
630 -
631 - // Check for duplicate code.
632 - $md5list = array();
633 - $tmpcss = $this->csscode;
634 - foreach ( $tmpcss as $media => $code ) {
635 - $md5sum = md5( $code );
636 - $medianame = $media;
637 - foreach ( $md5list as $med => $sum ) {
638 - // If same code.
639 - if ( $sum === $md5sum ) {
640 - // Add the merged code.
641 - $medianame = $med . ', ' . $media;
642 - $this->csscode[$medianame] = $code;
643 - $md5list[$medianame] = $md5list[$med];
644 - unset( $this->csscode[$med], $this->csscode[$media], $med5list[$med] );
645 - }
646 - }
647 - $md5list[$medianame] = $md5sum;
648 - }
649 - unset( $tmpcss );
650 -
651 - // Manage @imports, while is for recursive import management.
652 - foreach ( $this->csscode as &$thiscss ) {
653 - // Flag to trigger import reconstitution and var to hold external imports.
654 - $fiximports = false;
655 - $external_imports = '';
656 -
657 - // remove comments to avoid importing commented-out imports.
658 - $thiscss_nocomments = preg_replace( '#/\*.*\*/#Us', '', $thiscss );
659 - while ( preg_match_all( '#@import +(?:url)?(?:(?:\((["\']?)(?:[^"\')]+)\1\)|(["\'])(?:[^"\']+)\2)(?:[^,;"\']+(?:,[^,;"\']+)*)?)(?:;)#mi', $thiscss_nocomments, $matches ) ) {
660 - foreach ( $matches[0] as $import ) {
661 - if ( $this->isremovable( $import, $this->cssremovables ) ) {
662 - $thiscss = str_replace( $import, '', $thiscss );
663 - $import_ok = true;
664 - } else {
665 - $url = trim( preg_replace( '#^.*((?:https?:|ftp:)?//.*\.css).*$#', '$1', trim( $import ) ), " \t\n\r\0\x0B\"'" );
666 - $path = $this->getpath( $url );
667 - $import_ok = false;
668 - if ( file_exists( $path ) && is_readable( $path ) ) {
669 - $code = addcslashes( self::fixurls( $path, file_get_contents( $path ) ), "\\" );
670 - $code = preg_replace( '/\x{EF}\x{BB}\x{BF}/', '', $code );
671 - $tmpstyle = apply_filters( 'autoptimize_css_individual_style', $code, '' );
672 - if ( has_filter( 'autoptimize_css_individual_style' ) && ! empty( $tmpstyle ) ) {
673 - $code = $tmpstyle;
674 - $this->alreadyminified = true;
675 - } elseif ( $this->can_inject_late( $path, $code ) ) {
676 - $code = self::build_injectlater_marker( $path, md5( $code ) );
677 - }
678 -
679 - if ( ! empty( $code ) ) {
680 - $tmp_thiscss = preg_replace( '#(/\*FILESTART\*/.*)' . preg_quote( $import, '#' ) . '#Us', '/*FILESTART2*/' . $code . '$1', $thiscss );
681 - if ( ! empty( $tmp_thiscss ) ) {
682 - $thiscss = $tmp_thiscss;
683 - $import_ok = true;
684 - unset( $tmp_thiscss );
685 - }
686 - }
687 - unset( $code );
688 - }
689 - }
690 - if ( ! $import_ok ) {
691 - // External imports and general fall-back.
692 - $external_imports .= $import;
693 -
694 - $thiscss = str_replace( $import, '', $thiscss );
695 - $fiximports = true;
696 - }
697 - }
698 - $thiscss = preg_replace( '#/\*FILESTART\*/#', '', $thiscss );
699 - $thiscss = preg_replace( '#/\*FILESTART2\*/#', '/*FILESTART*/', $thiscss );
700 -
701 - // and update $thiscss_nocomments before going into next iteration in while loop.
702 - $thiscss_nocomments = preg_replace( '#/\*.*\*/#Us', '', $thiscss );
703 - }
704 - unset( $thiscss_nocomments );
705 -
706 - // Add external imports to top of aggregated CSS.
707 - if ( $fiximports ) {
708 - $thiscss = $external_imports . $thiscss;
709 - }
710 - }
711 - unset( $thiscss );
712 -
713 - // $this->csscode has all the uncompressed code now.
714 - foreach ( $this->csscode as &$code ) {
715 - // Check for already-minified code.
716 - $hash = md5( $code );
717 - do_action( 'autoptimize_action_css_hash', $hash );
718 - $ccheck = new autoptimizeCache( $hash, 'css' );
719 - if ( $ccheck->check() ) {
720 - $code = $ccheck->retrieve();
721 - $this->hashmap[md5( $code )] = $hash;
722 - continue;
723 - }
724 - unset( $ccheck );
725 -
726 - // Rewrite and/or inline referenced assets.
727 - $code = $this->rewrite_assets( $code );
728 -
729 - // Minify.
730 - $code = $this->run_minifier_on( $code );
731 -
732 - // Bring back INJECTLATER stuff.
733 - $code = $this->inject_minified( $code );
734 -
735 - // Filter results.
736 - $tmp_code = apply_filters( 'autoptimize_css_after_minify', $code );
737 - if ( ! empty( $tmp_code ) ) {
738 - $code = $tmp_code;
739 - unset( $tmp_code );
740 - }
741 -
742 - $this->hashmap[md5( $code )] = $hash;
743 - }
744 -
745 - unset( $code );
746 - return true;
747 - }
748 -
749 - public function run_minifier_on( $code )
750 - {
751 - if ( ! $this->alreadyminified ) {
752 - $do_minify = apply_filters( 'autoptimize_css_do_minify', true );
753 -
754 - if ( $do_minify ) {
755 - $cssmin = new autoptimizeCSSmin();
756 - $tmp_code = trim( $cssmin->run( $code ) );
757 -
758 - if ( ! empty( $tmp_code ) ) {
759 - $code = $tmp_code;
760 - unset( $tmp_code );
761 - }
762 - }
763 - }
764 -
765 - return $code;
766 - }
767 -
768 - // Caches the CSS in uncompressed, deflated and gzipped form.
769 - public function cache()
770 - {
771 - // CSS cache.
772 - foreach ( $this->csscode as $media => $code ) {
773 - $md5 = $this->hashmap[md5( $code )];
774 - $cache = new autoptimizeCache( $md5, 'css' );
775 - if ( ! $cache->check() ) {
776 - // Cache our code.
777 - $cache->cache( $code, 'text/css' );
778 - }
779 - $this->url[$media] = AUTOPTIMIZE_CACHE_URL . $cache->getname();
780 - }
781 - }
782 -
783 - // Returns the content.
784 - public function getcontent()
785 - {
786 - // restore comments.
787 - $this->content = $this->restore_comments( $this->content );
788 -
789 - // restore IE hacks.
790 - $this->content = $this->restore_iehacks( $this->content );
791 -
792 - // restore (no)script.
793 - $this->content = $this->restore_marked_content( 'SCRIPT', $this->content );
794 -
795 - // Restore noptimize.
796 - $this->content = $this->restore_noptimize( $this->content );
797 -
798 - // Restore the full content.
799 - if ( ! empty( $this->restofcontent ) ) {
800 - $this->content .= $this->restofcontent;
801 - $this->restofcontent = '';
802 - }
803 -
804 - // Inject the new stylesheets.
805 - $replaceTag = array( '<title', 'before' );
806 - $replaceTag = apply_filters( 'autoptimize_filter_css_replacetag', $replaceTag, $this->content );
807 -
808 - if ( $this->inline ) {
809 - foreach ( $this->csscode as $media => $code ) {
810 - $this->inject_in_html( '<style type="text/css" media="' . $media . '">' . $code . '</style>', $replaceTag );
811 - }
812 - } else {
813 - if ( $this->defer ) {
814 - $preloadCssBlock = '';
815 - $noScriptCssBlock = "<noscript id=\"aonoscrcss\">";
816 -
817 - $defer_inline_code = $this->defer_inline;
818 - if ( ! empty( $defer_inline_code ) ) {
819 - if ( apply_filters( 'autoptimize_filter_css_critcss_minify', true ) ) {
820 - $iCssHash = md5( $defer_inline_code );
821 - $iCssCache = new autoptimizeCache( $iCssHash, 'css' );
822 - if ( $iCssCache->check() ) {
823 - // we have the optimized inline CSS in cache.
824 - $defer_inline_code = $iCssCache->retrieve();
825 - } else {
826 - $cssmin = new autoptimizeCSSmin();
827 - $tmp_code = trim( $cssmin->run( $defer_inline_code ) );
828 -
829 - if ( ! empty( $tmp_code ) ) {
830 - $defer_inline_code = $tmp_code;
831 - $iCssCache->cache( $defer_inline_code, 'text/css' );
832 - unset( $tmp_code );
833 - }
834 - }
835 - }
836 - $code_out = '<style type="text/css" id="aoatfcss" media="all">' . $defer_inline_code . '</style>';
837 - $this->inject_in_html( $code_out, $replaceTag );
838 - }
839 - }
840 -
841 - foreach ( $this->url as $media => $url ) {
842 - $url = $this->url_replace_cdn( $url );
843 -
844 - // Add the stylesheet either deferred (import at bottom) or normal links in head.
845 - if ( $this->defer ) {
846 - $preloadOnLoad = autoptimizeConfig::get_ao_css_preload_onload();
847 -
848 - $preloadCssBlock .= '<link rel="preload" as="style" media="' . $media . '" href="' . $url . '" onload="' . $preloadOnLoad . '" />';
849 - $noScriptCssBlock .= '<link type="text/css" media="' . $media . '" href="' . $url . '" rel="stylesheet" />';
850 - } else {
851 - // $this->inject_in_html('<link type="text/css" media="' . $media . '" href="' . $url . '" rel="stylesheet" />', $replaceTag);
852 - if ( strlen( $this->csscode[$media] ) > $this->cssinlinesize ) {
853 - $this->inject_in_html( '<link type="text/css" media="' . $media . '" href="' . $url . '" rel="stylesheet" />', $replaceTag );
854 - } elseif ( strlen( $this->csscode[$media] ) > 0 ) {
855 - $this->inject_in_html( '<style type="text/css" media="' . $media . '">' . $this->csscode[$media] . '</style>', $replaceTag );
856 - }
857 - }
858 - }
859 -
860 - if ( $this->defer ) {
861 - $preload_polyfill = autoptimizeConfig::get_ao_css_preload_polyfill();
862 - $noScriptCssBlock .= '</noscript>';
863 - $this->inject_in_html( $preloadCssBlock . $noScriptCssBlock, $replaceTag );
864 -
865 - // Adds preload polyfill at end of body tag.
866 - $this->inject_in_html(
867 - apply_filters( 'autoptimize_css_preload_polyfill', $preload_polyfill ),
868 - array( '</body>', 'before' )
869 - );
870 - }
871 - }
872 -
873 - // Return the modified stylesheet.
874 - return $this->content;
875 - }
876 -
877 - static function fixurls( $file, $code )
878 - {
879 - // Switch all imports to the url() syntax.
880 - $code = preg_replace( '#@import ("|\')(.+?)\.css.*?("|\')#', '@import url("${2}.css")', $code );
881 -
882 - if ( preg_match_all( self::ASSETS_REGEX, $code, $matches ) ) {
883 - $file = str_replace( WP_ROOT_DIR, '/', $file );
884 - /**
885 - * rollback as per https://github.com/futtta/autoptimize/issues/94
886 - * $file = str_replace( AUTOPTIMIZE_WP_CONTENT_NAME, '', $file );
887 - */
888 - $dir = dirname( $file ); // Like /themes/expound/css.
889 -
890 - /**
891 - * $dir should not contain backslashes, since it's used to replace
892 - * urls, but it can contain them when running on Windows because
893 - * fixurls() is sometimes called with `ABSPATH . 'index.php'`
894 - */
895 - $dir = str_replace( '\\', '/', $dir );
896 - unset( $file ); // not used below at all.
897 -
898 - $replace = array();
899 - foreach ( $matches[1] as $k => $url ) {
900 - // Remove quotes.
901 - $url = trim( $url, " \t\n\r\0\x0B\"'" );
902 - $noQurl = trim( $url, "\"'" );
903 - if ( $url !== $noQurl ) {
904 - $removedQuotes = true;
905 - } else {
906 - $removedQuotes = false;
907 - }
908 -
909 - if ( '' === $noQurl ) {
910 - continue;
911 - }
912 -
913 - $url = $noQurl;
914 - if ( '/' === $url{0} || preg_match( '#^(https?://|ftp://|data:)#i', $url ) ) {
915 - // URL is protocol-relative, host-relative or something we don't touch.
916 - continue;
917 - } else {
918 - // Relative URL.
919 - /**
920 - * rollback as per https://github.com/futtta/autoptimize/issues/94
921 - * $newurl = preg_replace( '/https?:/', '', str_replace( ' ', '%20', AUTOPTIMIZE_WP_CONTENT_URL . str_replace( '//', '/', $dir . '/' . $url ) ) );
922 - */
923 - $newurl = preg_replace( '/https?:/', '', str_replace( ' ', '%20', AUTOPTIMIZE_WP_ROOT_URL . str_replace( '//', '/', $dir . '/' . $url ) ) );
924 - $newurl = apply_filters( 'autoptimize_filter_css_fixurl_newurl', $newurl );
925 -
926 - /**
927 - * Hash the url + whatever was behind potentially for replacement
928 - * We must do this, or different css classes referencing the same bg image (but
929 - * different parts of it, say, in sprites and such) loose their stuff...
930 - */
931 - $hash = md5( $url . $matches[2][$k] );
932 - $code = str_replace( $matches[0][$k], $hash, $code );
933 -
934 - if ( $removedQuotes ) {
935 - $replace[$hash] = "url('" . $newurl . "')" . $matches[2][$k];
936 - } else {
937 - $replace[$hash] = 'url(' . $newurl . ')' . $matches[2][$k];
938 - }
939 - }
940 - }
941 -
942 - $code = self::replace_longest_matches_first( $code, $replace );
943 - }
944 -
945 - return $code;
946 - }
947 -
948 - private function ismovable( $tag )
949 - {
950 - if ( ! $this->aggregate ) {
951 - return false;
952 - }
953 -
954 - if ( ! empty( $this->whitelist ) ) {
955 - foreach ( $this->whitelist as $match ) {
956 - if ( false !== strpos( $tag, $match ) ) {
957 - return true;
958 - }
959 - }
960 - // no match with whitelist.
961 - return false;
962 - } else {
963 - if ( is_array( $this->dontmove ) && ! empty( $this->dontmove ) ) {
964 - foreach ( $this->dontmove as $match ) {
965 - if ( false !== strpos( $tag, $match ) ) {
966 - // Matched something.
967 - return false;
968 - }
969 - }
970 - }
971 -
972 - // If we're here it's safe to move.
973 - return true;
974 - }
975 - }
976 -
977 - private function can_inject_late( $cssPath, $css )
978 - {
979 - $consider_minified_array = apply_filters( 'autoptimize_filter_css_consider_minified', false, $cssPath );
980 - if ( true !== $this->inject_min_late ) {
981 - // late-inject turned off.
982 - return false;
983 - } elseif ( ( false === strpos( $cssPath, 'min.css' ) ) && ( str_replace( $consider_minified_array, '', $cssPath ) === $cssPath ) ) {
984 - // file not minified based on filename & filter.
985 - return false;
986 - } elseif ( false !== strpos( $css, '@import' ) ) {
987 - // can't late-inject files with imports as those need to be aggregated.
988 - return false;
989 - } elseif ( ( false !== strpos( $css, '@font-face' ) ) && ( apply_filters( 'autoptimize_filter_css_fonts_cdn', false ) === true ) && ( ! empty( $this->cdn_url ) ) ) {
990 - // don't late-inject CSS with font-src's if fonts are set to be CDN'ed.
991 - return false;
992 - } elseif ( ( ( $this->datauris == true ) || ( ! empty( $this->cdn_url ) ) ) && preg_match( '#background[^;}]*url\(#Ui', $css ) ) {
993 - // don't late-inject CSS with images if CDN is set OR if image inlining is on.
994 - return false;
995 - } else {
996 - // phew, all is safe, we can late-inject.
997 - return true;
998 - }
999 - }
1000 -
1001 - /**
1002 - * Minifies (and cdn-replaces) a single local css file
1003 - * and returns its (cached) url.
1004 - *
1005 - * @param string $filepath Filepath.
1006 - * @param bool $cache_miss Optional. Force a cache miss. Default false.
1007 - *
1008 - * @return bool|string Url pointing to the minified css file or false.
1009 - */
1010 - public function minify_single( $filepath, $cache_miss = false )
1011 - {
1012 - $contents = $this->prepare_minify_single( $filepath );
1013 -
1014 - if ( empty( $contents ) ) {
1015 - return false;
1016 - }
1017 -
1018 - // Check cache.
1019 - $hash = 'single_' . md5( $contents );
1020 - $cache = new autoptimizeCache( $hash, 'css' );
1021 -
1022 - // If not in cache already, minify...
1023 - if ( ! $cache->check() || $cache_miss ) {
1024 - // Fixurls...
1025 - $contents = self::fixurls( $filepath, $contents );
1026 - // CDN-replace any referenced assets if needed...
1027 - $contents = $this->replace_urls( $contents );
1028 - // Now minify...
1029 - $cssmin = new autoptimizeCSSmin();
1030 - $contents = trim( $cssmin->run( $contents ) );
1031 - // Store in cache.
1032 - $cache->cache( $contents, 'text/css' );
1033 - }
1034 -
1035 - $url = $this->build_minify_single_url( $cache );
1036 -
1037 - return $url;
1038 - }
1039 -
1040 - /**
1041 - * Returns whether we're doing aggregation or not.
1042 - *
1043 - * @return bool
1044 - */
1045 - public function aggregating()
1046 - {
1047 - return $this->aggregate;
1048 - }
1049 -
1050 - public function getOptions()
1051 - {
1052 - return $this->options;
1053 - }
1054 -
1055 - public function replaceOptions( $options )
1056 - {
1057 - $this->options = $options;
1058 - }
1059 -
1060 - public function setOption( $name, $value )
1061 - {
1062 - $this->options[$name] = $value;
1063 - $this->$name = $value;
1064 - }
1065 -
1066 - public function getOption( $name )
1067 - {
1068 - return $this->options[$name];
1069 - }
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 + }
1070 351 }