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