PluginProbe
Autoptimize / 3.0.3
Autoptimize v3.0.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 +404 -195 2.6.13.0.3 View file →
@@ -12,61 +12,171 @@
12 12 const ASSETS_REGEX = '/url\s*\(\s*(?!["\']?data:)(?![\'|\"]?[\#|\%|])([^)]+)\s*\)([^;},\s]*)/i';
13 13
14 14 /**
15 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 16 */
29 17 const FONT_FACE_REGEX = '~@font-face\s*(\{(?:[^{}]+|(?1))*\})~xsi'; // added `i` flag for case-insensitivity.
30 18
31 - private $css = array();
32 - private $csscode = array();
33 - private $url = array();
34 - private $restofcontent = '';
35 - private $datauris = false;
36 - private $hashmap = array();
19 + /**
20 + * Store CSS.
21 + *
22 + * @var array
23 + */
24 + private $css = array();
25 +
26 + /**
27 + * To store CSS code
28 + *
29 + * @var array
30 + */
31 + private $csscode = array();
32 +
33 + /**
34 + * To store urls
35 + *
36 + * @var array
37 + */
38 + private $url = array();
39 +
40 + /**
41 + * String to store rest of content (when old setting "only in head" is used)
42 + *
43 + * @var string
44 + */
45 + private $restofcontent = '';
46 +
47 + /**
48 + * Setting to change small images to inline CSS
49 + *
50 + * @var bool
51 + */
52 + private $datauris = false;
53 +
54 + /**
55 + * Array to store hashmap
56 + *
57 + * @var array
58 + */
59 + private $hashmap = array();
60 +
61 + /**
62 + * Flag to indicate if CSS is already minified
63 + *
64 + * @var bool
65 + */
37 66 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();
67 +
68 + /**
69 + * Setting if CSS should be aggregated
70 + *
71 + * @var bool
72 + */
73 + private $aggregate = true;
74 +
75 + /**
76 + * Setting if all CSS should be inlined
77 + *
78 + * @var bool
79 + */
80 + private $inline = false;
81 +
82 + /**
83 + * Setting if CSS should be deferred
84 + *
85 + * @var bool
86 + */
87 + private $defer = false;
88 +
89 + /**
90 + * Setting for to be inlined CSS.
91 + *
92 + * @var string
93 + */
94 + private $defer_inline = '';
95 +
96 + /**
97 + * Setting for allowlist of what should be aggregated.
98 + *
99 + * @var string
100 + */
101 + private $allowlist = '';
102 +
103 + /**
104 + * Setting (only filter) for size under which CSS should be inlined instead of linked.
105 + *
106 + * @var string
107 + */
108 + private $cssinlinesize = '';
109 +
110 + /**
111 + * Setting (only filter) of CSS that can be removed.
112 + *
113 + * @var array
114 + */
115 + private $cssremovables = array();
116 +
117 + /**
118 + * Setting: should inline CSS be aggregated.
119 + *
120 + * @var bool
121 + */
122 + private $include_inline = false;
123 +
124 + /**
125 + * Setting (only filter) if minified CSS can be injected after minificatoin of aggregated CSS.
126 + *
127 + * @var bool
128 + */
129 + private $inject_min_late = true;
130 +
131 + /**
132 + * Holds all exclusions.
133 + *
134 + * @var array
135 + */
136 + private $dontmove = array();
137 +
138 + /**
139 + * Holds all options.
140 + *
141 + * @var array
142 + */
143 + private $options = array();
144 +
145 + /**
146 + * Setting; should excluded CSS-files be minified.
147 + *
148 + * @var bool
149 + */
49 150 private $minify_excluded = true;
50 151
51 - // public $cdn_url; // Used all over the place implicitly, so will have to be either public or protected :/ .
152 + /**
153 + * Setting (filter only); should all media-attributes be forced to "all".
154 + *
155 + * @var bool
156 + */
157 + private $media_force_all = false;
52 158
53 - // Reads the page and collects style tags.
159 + /**
160 + * Reads the page and collects style tags.
161 + *
162 + * @param array $options all options.
163 + */
54 164 public function read( $options )
55 165 {
56 - $noptimizeCSS = apply_filters( 'autoptimize_filter_css_noptimize', false, $this->content );
57 - if ( $noptimizeCSS ) {
166 + $noptimize_css = apply_filters( 'autoptimize_filter_css_noptimize', false, $this->content );
167 + if ( $noptimize_css || false === autoptimizeConfig::get_post_meta_ao_settings( 'ao_post_css_optimize' )) {
58 168 return false;
59 169 }
60 170
61 - $whitelistCSS = apply_filters( 'autoptimize_filter_css_whitelist', '', $this->content );
62 - if ( ! empty( $whitelistCSS ) ) {
63 - $this->whitelist = array_filter( array_map( 'trim', explode( ',', $whitelistCSS ) ) );
171 + $allowlist_css = apply_filters( 'autoptimize_filter_css_allowlist', '', $this->content );
172 + if ( ! empty( $allowlist_css ) ) {
173 + $this->allowlist = array_filter( array_map( 'trim', explode( ',', $allowlist_css ) ) );
64 174 }
65 175
66 - $removableCSS = apply_filters( 'autoptimize_filter_css_removables', '' );
67 - if ( ! empty( $removableCSS ) ) {
68 - $this->cssremovables = array_filter( array_map( 'trim', explode( ',', $removableCSS ) ) );
176 + $removable_css = apply_filters( 'autoptimize_filter_css_removables', '' );
177 + if ( ! empty( $removable_css ) ) {
178 + $this->cssremovables = array_filter( array_map( 'trim', explode( ',', $removable_css ) ) );
69 179 }
70 180
71 181 $this->cssinlinesize = apply_filters( 'autoptimize_filter_css_inlinesize', 256 );
72 182
@@ -87,8 +197,10 @@
87 197 // Returning true for "dontaggregate" turns off aggregation.
88 198 if ( $this->aggregate && apply_filters( 'autoptimize_filter_css_dontaggregate', false ) ) {
89 199 $this->aggregate = false;
90 200 }
201 + // and the filter that should have been there to begin with.
202 + $this->aggregate = apply_filters( 'autoptimize_filter_css_aggregate', $this->aggregate );
91 203
92 204 // include inline?
93 205 if ( apply_filters( 'autoptimize_css_include_inline', $options['include_inline'] ) ) {
94 206 $this->include_inline = true;
@@ -94,11 +206,11 @@
94 206 $this->include_inline = true;
95 207 }
96 208
97 209 // 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 ) ) );
210 + $exclude_css = apply_filters( 'autoptimize_filter_css_exclude', $options['css_exclude'], $this->content );
211 + if ( '' !== $exclude_css ) {
212 + $this->dontmove = array_filter( array_map( 'trim', explode( ',', $exclude_css ) ) );
101 213 } else {
102 214 $this->dontmove = array();
103 215 }
104 216
@@ -104,16 +216,24 @@
104 216
105 217 // forcefully exclude CSS with data-noptimize attrib.
106 218 $this->dontmove[] = 'data-noptimize';
107 219
220 + // forcefully exclude inline CSS with ".wp-container-" which due to the random-ish nature busts AO's cache continuously.
221 + $this->dontmove[] = '.wp-container-';
222 +
108 223 // Should we defer css?
109 224 // value: true / false.
110 225 $this->defer = $options['defer'];
111 226 $this->defer = apply_filters( 'autoptimize_filter_css_defer', $this->defer, $this->content );
112 227
228 + // If page/ post check post_meta to see if optimize is off.
229 + if ( $this->defer && false === autoptimizeConfig::get_post_meta_ao_settings( 'ao_post_ccss' ) ) {
230 + $this->defer = false;
231 + }
232 +
113 233 // Should we inline while deferring?
114 234 // value: inlined CSS.
115 - $this->defer_inline = apply_filters( 'autoptimize_filter_css_defer_inline', $options['defer_inline'], $this->content );
235 + $this->defer_inline = apply_filters( 'autoptimize_filter_css_defer_inline', $this->sanitize_css( $options['defer_inline'] ), $this->content );
116 236
117 237 // Should we inline?
118 238 // value: true / false.
119 239 $this->inline = $options['inline'];
@@ -130,17 +250,20 @@
130 250 $this->minify_excluded = false;
131 251 }
132 252 $this->minify_excluded = apply_filters( 'autoptimize_filter_css_minify_excluded', $this->minify_excluded, '' );
133 253
254 + // should we force all media-attributes to all?
255 + $this->media_force_all = apply_filters( 'autoptimize_filter_css_tagmedia_forceall', false );
256 +
134 257 // noptimize me.
135 258 $this->content = $this->hide_noptimize( $this->content );
136 259
137 260 // Exclude (no)script, as those may contain CSS which should be left as is.
138 261 $this->content = $this->replace_contents_with_marker_if_exists(
139 - 'SCRIPT',
140 - '<script',
141 - '#<(?:no)?script.*?<\/(?:no)?script>#is',
142 - $this->content
262 + 'SCRIPT',
263 + '<script',
264 + '#<(?:no)?script.*?<\/(?:no)?script>#is',
265 + $this->content
143 266 );
144 267
145 268 // Save IE hacks.
146 269 $this->content = $this->hide_iehacks( $this->content );
@@ -157,17 +280,20 @@
157 280 } elseif ( $this->ismovable( $tag ) ) {
158 281 // Get the media.
159 282 if ( false !== strpos( $tag, 'media=' ) ) {
160 283 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';
284 + if ( !empty( $medias ) ) {
285 + $medias = explode( ',', $medias[1] );
286 + $media = array();
287 + foreach ( $medias as $elem ) {
288 + if ( empty( $elem ) ) {
289 + $elem = 'all';
290 + }
291 +
292 + $media[] = $elem;
167 293 }
168 -
169 - $media[] = $elem;
294 + } else {
295 + $media = array( 'all' );
170 296 }
171 297 } else {
172 298 // No media specified - applies to all.
173 299 $media = array( 'all' );
@@ -172,8 +298,13 @@
172 298 // No media specified - applies to all.
173 299 $media = array( 'all' );
174 300 }
175 301
302 + // forcing media attribute to all to merge all in one file.
303 + if ( $this->media_force_all ) {
304 + $media = array( 'all' );
305 + }
306 +
176 307 $media = apply_filters( 'autoptimize_filter_css_tagmedia', $media, $tag );
177 308
178 309 if ( preg_match( '#<link.*href=("|\')(.*)("|\')#Usmi', $tag, $source ) ) {
179 310 // <link>.
@@ -185,9 +316,9 @@
185 316 $this->css[] = array( $media, $path );
186 317 } else {
187 318 // Link is dynamic (.php etc).
188 319 $new_tag = $this->optionally_defer_excluded( $tag, 'none' );
189 - if ( $new_tag !== '' && $new_tag !== $tag ) {
320 + if ( '' !== $new_tag && $new_tag !== $tag ) {
190 321 $this->content = str_replace( $tag, $new_tag, $this->content );
191 322 }
192 323 $tag = '';
193 324 }
@@ -199,9 +330,9 @@
199 330 // And re-hide them to be able to to the removal based on tag.
200 331 $tag = $this->hide_comments( $tag );
201 332
202 333 if ( $this->include_inline ) {
203 - $code = preg_replace( '#^.*<!\[CDATA\[(?:\s*\*/)?(.*)(?://|/\*)\s*?\]\]>.*$#sm', '$1', $code[1] );
334 + $code = preg_replace( '#^.*<!\[CDATA\[(?:\s*\*/)?(.*)(?://|/\*)\s*?\]\]>.*$#sm', '$1', $code[1] );
204 335 $this->css[] = array( $media, 'INLINE;' . $code );
205 336 } else {
206 337 $tag = '';
207 338 }
@@ -225,17 +356,28 @@
225 356 $minified_url = $this->minify_single( $path );
226 357 if ( ! empty( $minified_url ) ) {
227 358 // Replace orig URL with cached minified URL.
228 359 $new_tag = str_replace( $url, $minified_url, $tag );
360 + } elseif ( apply_filters( 'autoptimize_filter_ccsjs_remove_empty_minified_url', false ) ) {
361 + // Remove the original style tag, because cache content is empty but only if
362 + // filter is true-ed because $minified_url is also false if file is minified already.
363 + $new_tag = '';
229 364 }
230 365 }
231 366 }
232 367
233 - // Optionally defer (preload) non-aggregated CSS.
234 - $new_tag = $this->optionally_defer_excluded( $new_tag, $url );
368 + if ( '' !== $new_tag ) {
369 + // Optionally defer (preload) non-aggregated CSS.
370 + $new_tag = $this->optionally_defer_excluded( $new_tag, $url );
371 +
372 + // Check if we still need to CDN (esp. for already minified resources).
373 + if ( ! empty( $this->cdn_url ) || has_filter( 'autoptimize_filter_base_replace_cdn' ) ) {
374 + $new_tag = str_replace( $url, $this->url_replace_cdn( $url ), $new_tag );
375 + }
376 + }
235 377
236 378 // And replace!
237 - if ( $new_tag !== '' && $new_tag !== $tag ) {
379 + if ( ( '' !== $new_tag && $new_tag !== $tag ) || ( '' === $new_tag && apply_filters( 'autoptimize_filter_css_remove_empty_files', false ) ) ) {
238 380 $this->content = str_replace( $tag, $new_tag, $this->content );
239 381 }
240 382 }
241 383 }
@@ -258,29 +400,39 @@
258 400 */
259 401 private function optionally_defer_excluded( $tag, $url = '' )
260 402 {
261 403 // 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;
404 + if ( ! empty( $tag ) && false === strpos( $tag, ' onload=' ) && $this->defer && ! empty( $this->defer_inline ) && apply_filters( 'autoptimize_filter_css_defer_excluded', true, $tag ) ) {
405 + // get media attribute and based on that create onload JS attribute value.
406 + if ( false === strpos( $tag, 'media=' ) ) {
407 + $tag = str_replace( '<link', "<link media='all'", $tag );
408 + }
409 +
410 + preg_match( '#media=(?:"|\')([^>]*)(?:"|\')#Ui', $tag, $_medias );
411 + $_media = $_medias[1];
412 + $_preload_onload = autoptimizeConfig::get_ao_css_preload_onload( $_media );
413 +
414 + if ( 'print' !== $_media ) {
415 + // If not media=print, adapt original <link> element for CSS to be preloaded and add <noscript>-version for fallback.
416 + $new_tag = '<noscript>' . autoptimizeUtils::remove_id_from_node( $tag ) . '</noscript>' . str_replace(
417 + $_medias[0],
418 + "media='print' onload=\"" . $_preload_onload . '"',
419 + $tag
420 + );
421 +
422 + // Optionally (but default false) preload the (excluded) CSS-file.
423 + if ( apply_filters( 'autoptimize_fitler_css_preload_and_print', false ) && 'none' !== $url ) {
424 + $new_tag = '<link rel="preload" as="stylesheet" href="' . $url . '"/>' . $new_tag;
425 + }
426 + } else {
427 + $new_tag = $tag;
428 + }
429 +
430 + return $new_tag;
280 431 }
281 432
282 - return $new_tag;
433 + // Return unchanged $tag.
434 + return $tag;
283 435 }
284 436
285 437 /**
286 438 * Checks if the local file referenced by $path is a valid
@@ -285,9 +437,9 @@
285 437 /**
286 438 * Checks if the local file referenced by $path is a valid
287 439 * candidate for being inlined into a data: URI
288 440 *
289 - * @param string $path
441 + * @param string $path image path.
290 442 * @return boolean
291 443 */
292 444 private function is_datauri_candidate( $path )
293 445 {
@@ -338,14 +490,14 @@
338 490
339 491 private function check_datauri_exclude_list( $url )
340 492 {
341 493 static $exclude_list = null;
342 - $no_datauris = array();
494 + static $no_datauris = array();
343 495
344 496 // Again, skip doing certain stuff repeatedly when loop-called.
345 497 if ( null === $exclude_list ) {
346 498 $exclude_list = apply_filters( 'autoptimize_filter_css_datauri_exclude', '' );
347 - $no_datauris = array_filter( array_map( 'trim', explode( ',', $exclude_list ) ) );
499 + $no_datauris = array_filter( array_map( 'trim', explode( ',', $exclude_list ) ) );
348 500 }
349 501
350 502 $matched = false;
351 503
@@ -376,20 +528,20 @@
376 528 return $result;
377 529 }
378 530 }
379 531
380 - $hash = md5( $path );
532 + $hash = md5( $path );
381 533 $check = new autoptimizeCache( $hash, 'img' );
382 534 if ( $check->check() ) {
383 535 // we have the base64 image in cache.
384 - $headAndData = $check->retrieve();
385 - $_base64data = explode( ';base64,', $headAndData );
386 - $base64data = $_base64data[1];
536 + $head_and_data = $check->retrieve();
537 + $_base64data = explode( ';base64,', $head_and_data );
538 + $base64data = $_base64data[1];
387 539 unset( $_base64data );
388 540 } else {
389 541 // It's an image and we don't have it in cache, get the type by extension.
390 542 $exploded_path = explode( '.', $path );
391 - $type = end( $exploded_path );
543 + $type = end( $exploded_path );
392 544
393 545 switch ( $type ) {
394 546 case 'jpg':
395 547 case 'jpeg':
@@ -411,17 +563,20 @@
411 563 $dataurihead = 'data:application/octet-stream;base64,';
412 564 }
413 565
414 566 // Encode the data.
415 - $base64data = base64_encode( file_get_contents( $path ) );
416 - $headAndData = $dataurihead . $base64data;
567 + $base64data = base64_encode( file_get_contents( $path ) );
568 + $head_and_data = $dataurihead . $base64data;
417 569
418 570 // Save in cache.
419 - $check->cache( $headAndData, 'text/plain' );
571 + $check->cache( $head_and_data, 'text/plain' );
420 572 }
421 573 unset( $check );
422 574
423 - return array( 'full' => $headAndData, 'base64data' => $base64data );
575 + return array(
576 + 'full' => $head_and_data,
577 + 'base64data' => $base64data,
578 + );
424 579 }
425 580
426 581 /**
427 582 * Given an array of key/value pairs to replace in $string,
@@ -426,10 +581,10 @@
426 581 /**
427 582 * Given an array of key/value pairs to replace in $string,
428 583 * it does so by replacing the longest-matching strings first.
429 584 *
430 - * @param string $string
431 - * @param array $replacements
585 + * @param string $string string in which to replace.
586 + * @param array $replacements to be replaced strings and replacement.
432 587 *
433 588 * @return string
434 589 */
435 590 protected static function replace_longest_matches_first( $string, $replacements = array() )
@@ -483,9 +638,9 @@
483 638 if ( ! empty( $this->cdn_url ) ) {
484 639 $replacement_url = $this->url_replace_cdn( $url );
485 640 // Prepare replacements array.
486 641 $replacements[ $url_src_matches[1][ $count ] ] = str_replace(
487 - $original_url, $replacement_url, $url_src_matches[1][$count]
642 + $original_url, $replacement_url, $url_src_matches[1][ $count ]
488 643 );
489 644 }
490 645 }
491 646 }
@@ -499,9 +654,9 @@
499 654 * "Hides" @font-face declarations by replacing them with `%%FONTFACE%%` markers.
500 655 * Also does CDN replacement of any font-urls within those declarations if the `autoptimize_filter_css_fonts_cdn`
501 656 * filter is used.
502 657 *
503 - * @param string $code
658 + * @param string $code HTML being processed to hide fonts.
504 659 * @return string
505 660 */
506 661 public function hide_fontface_and_maybe_cdn( $code )
507 662 {
@@ -521,9 +676,9 @@
521 676 }
522 677
523 678 // Replace declaration with its base64 encoded string.
524 679 $replacement = self::build_marker( 'FONTFACE', $full_match );
525 - $code = str_replace( $match_search, $replacement, $code );
680 + $code = str_replace( $match_search, $replacement, $code );
526 681 }
527 682 }
528 683
529 684 return $code;
@@ -532,9 +687,9 @@
532 687 /**
533 688 * Restores original @font-face declarations that have been "hidden"
534 689 * using `hide_fontface_and_maybe_cdn()`.
535 690 *
536 - * @param string $code
691 + * @param string $code HTML being processed to unhide fonts.
537 692 * @return string
538 693 */
539 694 public function restore_fontface( $code )
540 695 {
@@ -540,9 +695,14 @@
540 695 {
541 696 return $this->restore_marked_content( 'FONTFACE', $code );
542 697 }
543 698
544 - // Re-write (and/or inline) referenced assets.
699 + /**
700 + * Re-write (and/or inline) referenced assets.
701 + *
702 + * @param string $code HTML being processed rewrite assets.
703 + * @return string
704 + */
545 705 public function rewrite_assets( $code )
546 706 {
547 707 // Handle @font-face rules by hiding and processing them separately.
548 708 $code = $this->hide_fontface_and_maybe_cdn( $code );
@@ -558,9 +718,10 @@
558 718 */
559 719
560 720 // Re-write (and/or inline) URLs to point them to the CDN host.
561 721 $url_src_matches = array();
562 - $imgreplace = array();
722 + $imgreplace = array();
723 +
563 724 // Matches and captures anything specified within the literal `url()` and excludes those containing data: URIs.
564 725 preg_match_all( self::ASSETS_REGEX, $code, $url_src_matches );
565 726 if ( is_array( $url_src_matches ) && ! empty( $url_src_matches ) ) {
566 727 foreach ( $url_src_matches[1] as $count => $original_url ) {
@@ -580,17 +741,17 @@
580 741 $excluded = $this->check_datauri_exclude_list( $ipath );
581 742 if ( ! $excluded ) {
582 743 $is_datauri_candidate = $this->is_datauri_candidate( $ipath );
583 744 if ( $is_datauri_candidate ) {
584 - $datauri = $this->build_or_get_datauri_image( $ipath );
585 - $base64data = $datauri['base64data'];
745 + $datauri = $this->build_or_get_datauri_image( $ipath );
746 + $base64data = $datauri['base64data'];
586 747 // Add it to the list for replacement.
587 748 $imgreplace[ $url_src_matches[1][ $count ] ] = str_replace(
588 - $original_url,
589 - $datauri['full'],
590 - $url_src_matches[1][$count]
749 + $original_url,
750 + $datauri['full'],
751 + $url_src_matches[1][ $count ]
591 752 );
592 - $inlined = true;
753 + $inlined = true;
593 754 }
594 755 }
595 756 }
596 757
@@ -601,11 +762,11 @@
601 762 * being inlined for whatever reason above.
602 763 */
603 764 if ( ! $inlined && ( ! empty( $this->cdn_url ) || has_filter( 'autoptimize_filter_base_replace_cdn' ) ) ) {
604 765 // Just do the "simple" CDN replacement.
605 - $replacement_url = $this->url_replace_cdn( $url );
766 + $replacement_url = $this->url_replace_cdn( $url );
606 767 $imgreplace[ $url_src_matches[1][ $count ] ] = str_replace(
607 - $original_url, $replacement_url, $url_src_matches[1][$count]
768 + $original_url, $replacement_url, $url_src_matches[1][ $count ]
608 769 );
609 770 }
610 771 }
611 772 }
@@ -617,9 +778,11 @@
617 778
618 779 return $code;
619 780 }
620 781
621 - // Joins and optimizes CSS.
782 + /**
783 + * Joins and optimizes CSS.
784 + */
622 785 public function minify()
623 786 {
624 787 foreach ( $this->css as $group ) {
625 788 list( $media, $css ) = $group;
@@ -624,27 +787,27 @@
624 787 foreach ( $this->css as $group ) {
625 788 list( $media, $css ) = $group;
626 789 if ( preg_match( '#^INLINE;#', $css ) ) {
627 790 // <style>.
628 - $css = preg_replace( '#^INLINE;#', '', $css );
629 - $css = self::fixurls( ABSPATH . 'index.php', $css ); // ABSPATH already contains a trailing slash.
791 + $css = preg_replace( '#^INLINE;#', '', $css );
792 + $css = self::fixurls( ABSPATH . 'index.php', $css ); // ABSPATH already contains a trailing slash.
630 793 $tmpstyle = apply_filters( 'autoptimize_css_individual_style', $css, '' );
631 794 if ( has_filter( 'autoptimize_css_individual_style' ) && ! empty( $tmpstyle ) ) {
632 - $css = $tmpstyle;
795 + $css = $tmpstyle;
633 796 $this->alreadyminified = true;
634 797 }
635 798 } else {
636 799 // <link>
637 800 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 );
801 + $css_path = $css;
802 + $css = self::fixurls( $css_path, file_get_contents( $css_path ) );
803 + $css = preg_replace( '/\x{EF}\x{BB}\x{BF}/', '', $css );
804 + $tmpstyle = apply_filters( 'autoptimize_css_individual_style', $css, $css_path );
642 805 if ( has_filter( 'autoptimize_css_individual_style' ) && ! empty( $tmpstyle ) ) {
643 - $css = $tmpstyle;
806 + $css = $tmpstyle;
644 807 $this->alreadyminified = true;
645 - } elseif ( $this->can_inject_late( $cssPath, $css ) ) {
646 - $css = self::build_injectlater_marker( $cssPath, md5( $css ) );
808 + } elseif ( $this->can_inject_late( $css_path, $css ) ) {
809 + $css = self::build_injectlater_marker( $css_path, md5( $css ) );
647 810 }
648 811 } else {
649 812 // Couldn't read CSS. Maybe getpath isn't working?
650 813 $css = '';
@@ -652,12 +815,12 @@
652 815 }
653 816
654 817 foreach ( $media as $elem ) {
655 818 if ( ! empty( $css ) ) {
656 - if ( ! isset( $this->csscode[$elem] ) ) {
657 - $this->csscode[$elem] = '';
819 + if ( ! isset( $this->csscode[ $elem ] ) ) {
820 + $this->csscode[ $elem ] = '';
658 821 }
659 - $this->csscode[$elem] .= "\n/*FILESTART*/" . $css;
822 + $this->csscode[ $elem ] .= "\n/*FILESTART*/" . $css;
660 823 }
661 824 }
662 825 }
663 826
@@ -670,15 +833,15 @@
670 833 foreach ( $md5list as $med => $sum ) {
671 834 // If same code.
672 835 if ( $sum === $md5sum ) {
673 836 // 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] );
837 + $medianame = $med . ', ' . $media;
838 + $this->csscode[ $medianame ] = $code;
839 + $md5list[ $medianame ] = $md5list[ $med ];
840 + unset( $this->csscode[ $med ], $this->csscode[ $media ], $md5list[ $med ] );
678 841 }
679 842 }
680 - $md5list[$medianame] = $md5sum;
843 + $md5list[ $medianame ] = $md5sum;
681 844 }
682 845 unset( $tmpcss );
683 846
684 847 // Manage @imports, while is for recursive import management.
@@ -691,20 +854,20 @@
691 854 $thiscss_nocomments = preg_replace( '#/\*.*\*/#Us', '', $thiscss );
692 855 while ( preg_match_all( '#@import +(?:url)?(?:(?:\((["\']?)(?:[^"\')]+)\1\)|(["\'])(?:[^"\']+)\2)(?:[^,;"\']+(?:,[^,;"\']+)*)?)(?:;)#mi', $thiscss_nocomments, $matches ) ) {
693 856 foreach ( $matches[0] as $import ) {
694 857 if ( $this->isremovable( $import, $this->cssremovables ) ) {
695 - $thiscss = str_replace( $import, '', $thiscss );
858 + $thiscss = str_replace( $import, '', $thiscss );
696 859 $import_ok = true;
697 860 } else {
698 - $url = trim( preg_replace( '#^.*((?:https?:|ftp:)?//.*\.css).*$#', '$1', trim( $import ) ), " \t\n\r\0\x0B\"'" );
699 - $path = $this->getpath( $url );
861 + $url = trim( preg_replace( '#^.*((?:https?:|ftp:)?//.*\.css).*$#', '$1', trim( $import ) ), " \t\n\r\0\x0B\"'" );
862 + $path = $this->getpath( $url );
700 863 $import_ok = false;
701 864 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 );
865 + $code = addcslashes( self::fixurls( $path, file_get_contents( $path ) ), '\\' );
866 + $code = preg_replace( '/\x{EF}\x{BB}\x{BF}/', '', $code );
704 867 $tmpstyle = apply_filters( 'autoptimize_css_individual_style', $code, '' );
705 868 if ( has_filter( 'autoptimize_css_individual_style' ) && ! empty( $tmpstyle ) ) {
706 - $code = $tmpstyle;
869 + $code = $tmpstyle;
707 870 $this->alreadyminified = true;
708 871 } elseif ( $this->can_inject_late( $path, $code ) ) {
709 872 $code = self::build_injectlater_marker( $path, md5( $code ) );
710 873 }
@@ -709,11 +872,11 @@
709 872 $code = self::build_injectlater_marker( $path, md5( $code ) );
710 873 }
711 874
712 875 if ( ! empty( $code ) ) {
713 - $tmp_thiscss = preg_replace( '#(/\*FILESTART\*/.*)' . preg_quote( $import, '#' ) . '#Us', '/*FILESTART2*/' . $code . '$1', $thiscss );
876 + $tmp_thiscss = str_replace( $import, stripcslashes( $code ), $thiscss );
714 877 if ( ! empty( $tmp_thiscss ) ) {
715 - $thiscss = $tmp_thiscss;
878 + $thiscss = $tmp_thiscss;
716 879 $import_ok = true;
717 880 unset( $tmp_thiscss );
718 881 }
719 882 }
@@ -749,10 +912,10 @@
749 912 $hash = md5( $code );
750 913 do_action( 'autoptimize_action_css_hash', $hash );
751 914 $ccheck = new autoptimizeCache( $hash, 'css' );
752 915 if ( $ccheck->check() ) {
753 - $code = $ccheck->retrieve();
754 - $this->hashmap[md5( $code )] = $hash;
916 + $code = $ccheck->retrieve();
917 + $this->hashmap[ md5( $code ) ] = $hash;
755 918 continue;
756 919 }
757 920 unset( $ccheck );
758 921
@@ -771,9 +934,9 @@
771 934 $code = $tmp_code;
772 935 unset( $tmp_code );
773 936 }
774 937
775 - $this->hashmap[md5( $code )] = $hash;
938 + $this->hashmap[ md5( $code ) ] = $hash;
776 939 }
777 940
778 941 unset( $code );
779 942 return true;
@@ -797,29 +960,37 @@
797 960
798 961 return $code;
799 962 }
800 963
801 - // Caches the CSS in uncompressed, deflated and gzipped form.
964 + /**
965 + * Caches the CSS in uncompressed, deflated and gzipped form.
966 + */
802 967 public function cache()
803 968 {
804 969 // CSS cache.
805 970 foreach ( $this->csscode as $media => $code ) {
806 - $md5 = $this->hashmap[md5( $code )];
971 + if ( empty( $code ) ) {
972 + continue;
973 + }
974 +
975 + $md5 = $this->hashmap[ md5( $code ) ];
807 976 $cache = new autoptimizeCache( $md5, 'css' );
808 977 if ( ! $cache->check() ) {
809 978 // Cache our code.
810 979 $cache->cache( $code, 'text/css' );
811 980 }
812 - $this->url[$media] = AUTOPTIMIZE_CACHE_URL . $cache->getname();
981 + $this->url[ $media ] = AUTOPTIMIZE_CACHE_URL . $cache->getname();
813 982 }
814 983 }
815 984
816 - // Returns the content.
985 + /**
986 + * Returns the content.
987 + */
817 988 public function getcontent()
818 989 {
819 990 // Restore the full content (only applies when "autoptimize_filter_css_justhead" filter is true).
820 991 if ( ! empty( $this->restofcontent ) ) {
821 - $this->content .= $this->restofcontent;
992 + $this->content .= $this->restofcontent;
822 993 $this->restofcontent = '';
823 994 }
824 995
825 996 // type is not added by default.
@@ -828,29 +999,29 @@
828 999 $type_css = 'type="text/css" ';
829 1000 }
830 1001
831 1002 // Inject the new stylesheets.
832 - $replaceTag = array( '<title', 'before' );
833 - $replaceTag = apply_filters( 'autoptimize_filter_css_replacetag', $replaceTag, $this->content );
1003 + $replace_tag = array( '<title', 'before' );
1004 + $replace_tag = apply_filters( 'autoptimize_filter_css_replacetag', $replace_tag, $this->content );
834 1005
835 1006 if ( $this->inline ) {
836 1007 foreach ( $this->csscode as $media => $code ) {
837 - $this->inject_in_html( '<style ' . $type_css . 'media="' . $media . '">' . $code . '</style>', $replaceTag );
1008 + $this->inject_in_html( apply_filters( 'autoptimize_filter_css_bodyreplacementpayload', '<style ' . $type_css . 'media="' . $media . '">' . $code . '</style>' ), $replace_tag );
838 1009 }
839 1010 } else {
840 1011 if ( $this->defer ) {
841 - $preloadCssBlock = '';
1012 + $preload_css_block = '';
842 1013 $inlined_ccss_block = '';
843 - $noScriptCssBlock = "<noscript id=\"aonoscrcss\">";
1014 + $noscript_css_block = '<noscript id="aonoscrcss">';
844 1015
845 1016 $defer_inline_code = $this->defer_inline;
846 1017 if ( ! empty( $defer_inline_code ) ) {
847 1018 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() ) {
1019 + $icss_hash = md5( $defer_inline_code );
1020 + $icss_cache = new autoptimizeCache( $icss_hash, 'css' );
1021 + if ( $icss_cache->check() ) {
851 1022 // we have the optimized inline CSS in cache.
852 - $defer_inline_code = $iCssCache->retrieve();
1023 + $defer_inline_code = $icss_cache->retrieve();
853 1024 } else {
854 1025 $cssmin = new autoptimizeCSSmin();
855 1026 $tmp_code = trim( $cssmin->run( $defer_inline_code ) );
856 1027
@@ -855,9 +1026,9 @@
855 1026 $tmp_code = trim( $cssmin->run( $defer_inline_code ) );
856 1027
857 1028 if ( ! empty( $tmp_code ) ) {
858 1029 $defer_inline_code = $tmp_code;
859 - $iCssCache->cache( $defer_inline_code, 'text/css' );
1030 + $icss_cache->cache( $defer_inline_code, 'text/css' );
860 1031 unset( $tmp_code );
861 1032 }
862 1033 }
863 1034 }
@@ -870,33 +1041,29 @@
870 1041 foreach ( $this->url as $media => $url ) {
871 1042 $url = $this->url_replace_cdn( $url );
872 1043
873 1044 // 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();
1045 + if ( $this->defer && 'print' !== $media ) {
1046 + $preload_onload = autoptimizeConfig::get_ao_css_preload_onload( $media );
876 1047
877 - $preloadCssBlock .= '<link rel="preload" as="style" media="' . $media . '" href="' . $url . '" onload="' . $preloadOnLoad . '" />';
878 - $noScriptCssBlock .= '<link ' . $type_css . 'media="' . $media . '" href="' . $url . '" rel="stylesheet" />';
1048 + $preload_css_block .= apply_filters( 'autoptimize_filter_css_single_deferred_link', '<link rel="stylesheet" media="print" href="' . $url . '" onload="' . $preload_onload . '" />' );
1049 + if ( apply_filters( 'autoptimize_fitler_css_preload_and_print', false ) ) {
1050 + $preload_css_block = '<link rel="preload" as="stylesheet" href="' . $url . '"/>' . $preload_css_block;
1051 + }
1052 + $noscript_css_block .= '<link ' . $type_css . 'media="' . $media . '" href="' . $url . '" rel="stylesheet" />';
879 1053 } 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 );
1054 + if ( strlen( $this->csscode[ $media ] ) > $this->cssinlinesize ) {
1055 + $this->inject_in_html( apply_filters( 'autoptimize_filter_css_bodyreplacementpayload', '<link ' . $type_css . 'media="' . $media . '" href="' . $url . '" rel="stylesheet" />' ), $replace_tag );
1056 + } elseif ( strlen( $this->csscode[ $media ] ) > 0 ) {
1057 + $this->inject_in_html( apply_filters( 'autoptimize_filter_css_bodyreplacementpayload', '<style ' . $type_css . 'media="' . $media . '">' . $this->csscode[ $media ] . '</style>' ), $replace_tag );
884 1058 }
885 1059 }
886 1060 }
887 1061
888 1062 if ( $this->defer ) {
889 - $preload_polyfill = autoptimizeConfig::get_ao_css_preload_polyfill();
890 - $noScriptCssBlock .= '</noscript>';
1063 + $noscript_css_block .= '</noscript>';
891 1064 // 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 - );
1065 + $this->inject_in_html( apply_filters( 'autoptimize_filter_css_bodyreplacementpayload', $inlined_ccss_block . $preload_css_block . $noscript_css_block ), $replace_tag );
899 1066 }
900 1067 }
901 1068
902 1069 // restore comments.
@@ -914,8 +1081,14 @@
914 1081 // Return the modified stylesheet.
915 1082 return $this->content;
916 1083 }
917 1084
1085 + /**
1086 + * Make sure URL's are absolute iso relative to original CSS location.
1087 + *
1088 + * @param string $file filename of optimized CSS-file.
1089 + * @param string $code CSS-code in which to fix URL's.
1090 + */
918 1091 static function fixurls( $file, $code )
919 1092 {
920 1093 // Switch all imports to the url() syntax.
921 1094 $code = preg_replace( '#@import ("|\')(.+?)\.css.*?("|\')#', '@import url("${2}.css")', $code );
@@ -922,9 +1095,9 @@
922 1095
923 1096 if ( preg_match_all( self::ASSETS_REGEX, $code, $matches ) ) {
924 1097 $file = str_replace( WP_ROOT_DIR, '/', $file );
925 1098 /**
926 - * rollback as per https://github.com/futtta/autoptimize/issues/94
1099 + * Rollback as per https://github.com/futtta/autoptimize/issues/94
927 1100 * $file = str_replace( AUTOPTIMIZE_WP_CONTENT_NAME, '', $file );
928 1101 */
929 1102 $dir = dirname( $file ); // Like /themes/expound/css.
930 1103
@@ -938,27 +1111,27 @@
938 1111
939 1112 $replace = array();
940 1113 foreach ( $matches[1] as $k => $url ) {
941 1114 // Remove quotes.
942 - $url = trim( $url, " \t\n\r\0\x0B\"'" );
943 - $noQurl = trim( $url, "\"'" );
944 - if ( $url !== $noQurl ) {
945 - $removedQuotes = true;
1115 + $url = trim( $url, " \t\n\r\0\x0B\"'" );
1116 + $no_q_url = trim( $url, "\"'" );
1117 + if ( $url !== $no_q_url ) {
1118 + $removed_quotes = true;
946 1119 } else {
947 - $removedQuotes = false;
1120 + $removed_quotes = false;
948 1121 }
949 1122
950 - if ( '' === $noQurl ) {
1123 + if ( '' === $no_q_url ) {
951 1124 continue;
952 1125 }
953 1126
954 - $url = $noQurl;
1127 + $url = $no_q_url;
955 1128 if ( '/' === $url[0] || preg_match( '#^(https?://|ftp://|data:)#i', $url ) ) {
956 1129 // URL is protocol-relative, host-relative or something we don't touch.
957 1130 continue;
958 - } else {
959 - // Relative URL.
960 - /**
1131 + } else { // Relative URL.
1132 +
1133 + /*
961 1134 * rollback as per https://github.com/futtta/autoptimize/issues/94
962 1135 * $newurl = preg_replace( '/https?:/', '', str_replace( ' ', '%20', AUTOPTIMIZE_WP_CONTENT_URL . str_replace( '//', '/', $dir . '/' . $url ) ) );
963 1136 */
964 1137 $newurl = preg_replace( '/https?:/', '', str_replace( ' ', '%20', AUTOPTIMIZE_WP_ROOT_URL . str_replace( '//', '/', $dir . '/' . $url ) ) );
@@ -968,15 +1141,15 @@
968 1141 * Hash the url + whatever was behind potentially for replacement
969 1142 * We must do this, or different css classes referencing the same bg image (but
970 1143 * different parts of it, say, in sprites and such) loose their stuff...
971 1144 */
972 - $hash = md5( $url . $matches[2][$k] );
973 - $code = str_replace( $matches[0][$k], $hash, $code );
1145 + $hash = md5( $url . $matches[2][ $k ] );
1146 + $code = str_replace( $matches[0][ $k ], $hash, $code );
974 1147
975 - if ( $removedQuotes ) {
976 - $replace[$hash] = "url('" . $newurl . "')" . $matches[2][$k];
1148 + if ( $removed_quotes ) {
1149 + $replace[ $hash ] = "url('" . $newurl . "')" . $matches[2][ $k ];
977 1150 } else {
978 - $replace[$hash] = 'url(' . $newurl . ')' . $matches[2][$k];
1151 + $replace[ $hash ] = 'url(' . $newurl . ')' . $matches[2][ $k ];
979 1152 }
980 1153 }
981 1154 }
982 1155
@@ -991,15 +1164,15 @@
991 1164 if ( ! $this->aggregate ) {
992 1165 return false;
993 1166 }
994 1167
995 - if ( ! empty( $this->whitelist ) ) {
996 - foreach ( $this->whitelist as $match ) {
1168 + if ( ! empty( $this->allowlist ) ) {
1169 + foreach ( $this->allowlist as $match ) {
997 1170 if ( false !== strpos( $tag, $match ) ) {
998 1171 return true;
999 1172 }
1000 1173 }
1001 - // no match with whitelist.
1174 + // no match with allowlist.
1002 1175 return false;
1003 1176 } else {
1004 1177 if ( is_array( $this->dontmove ) && ! empty( $this->dontmove ) ) {
1005 1178 foreach ( $this->dontmove as $match ) {
@@ -1014,15 +1187,15 @@
1014 1187 return true;
1015 1188 }
1016 1189 }
1017 1190
1018 - private function can_inject_late( $cssPath, $css )
1191 + private function can_inject_late( $css_path, $css )
1019 1192 {
1020 - $consider_minified_array = apply_filters( 'autoptimize_filter_css_consider_minified', false, $cssPath );
1193 + $consider_minified_array = apply_filters( 'autoptimize_filter_css_consider_minified', false, $css_path );
1021 1194 if ( true !== $this->inject_min_late ) {
1022 1195 // late-inject turned off.
1023 1196 return false;
1024 - } elseif ( ( false === strpos( $cssPath, 'min.css' ) ) && ( str_replace( $consider_minified_array, '', $cssPath ) === $cssPath ) ) {
1197 + } elseif ( ( false === strpos( $css_path, 'min.css' ) ) && ( str_replace( $consider_minified_array, '', $css_path ) === $css_path ) ) {
1025 1198 // file not minified based on filename & filter.
1026 1199 return false;
1027 1200 } elseif ( false !== strpos( $css, '@import' ) ) {
1028 1201 // can't late-inject files with imports as those need to be aggregated.
@@ -1029,9 +1202,9 @@
1029 1202 return false;
1030 1203 } elseif ( ( false !== strpos( $css, '@font-face' ) ) && ( apply_filters( 'autoptimize_filter_css_fonts_cdn', false ) === true ) && ( ! empty( $this->cdn_url ) ) ) {
1031 1204 // don't late-inject CSS with font-src's if fonts are set to be CDN'ed.
1032 1205 return false;
1033 - } elseif ( ( ( $this->datauris == true ) || ( ! empty( $this->cdn_url ) ) ) && preg_match( '#background[^;}]*url\(#Ui', $css ) ) {
1206 + } elseif ( ( ( true == $this->datauris ) || ( ! empty( $this->cdn_url ) ) ) && preg_match( '#background[^;}]*url\(#Ui', $css ) ) {
1034 1207 // don't late-inject CSS with images if CDN is set OR if image inlining is on.
1035 1208 return false;
1036 1209 } else {
1037 1210 // phew, all is safe, we can late-inject.
@@ -1052,8 +1225,15 @@
1052 1225 {
1053 1226 $contents = $this->prepare_minify_single( $filepath );
1054 1227
1055 1228 if ( empty( $contents ) ) {
1229 + // if aggregate is off and CCSS is used but all files are minified already, then we
1230 + // must make sure the autoptimize_action_css_hash action still fires for CCSS's sake.
1231 + $ao_ccss_key = get_option( 'autoptimize_ccss_key', '' );
1232 + if ( false === $this->aggregate && isset( $ao_ccss_key ) && ! empty( $ao_ccss_key ) ) {
1233 + $hash = 'single_' . md5( file_get_contents( $filepath ) );
1234 + do_action( 'autoptimize_action_css_hash', $hash );
1235 + }
1056 1236 return false;
1057 1237 }
1058 1238
1059 1239 // Check cache.
@@ -1071,8 +1251,17 @@
1071 1251 $contents = $this->restore_fontface( $contents );
1072 1252 // Now minify...
1073 1253 $cssmin = new autoptimizeCSSmin();
1074 1254 $contents = trim( $cssmin->run( $contents ) );
1255 +
1256 + // Check if minified cache content is empty.
1257 + if ( empty( $contents ) ) {
1258 + return false;
1259 + }
1260 +
1261 + // Filter contents of excluded minified CSS.
1262 + $contents = apply_filters( 'autoptimize_filter_css_single_after_minify', $contents );
1263 +
1075 1264 // Store in cache.
1076 1265 $cache->cache( $contents, 'text/css' );
1077 1266 }
1078 1267
@@ -1102,13 +1291,33 @@
1102 1291 }
1103 1292
1104 1293 public function setOption( $name, $value )
1105 1294 {
1106 - $this->options[$name] = $value;
1107 - $this->$name = $value;
1295 + $this->options[ $name ] = $value;
1296 + $this->$name = $value;
1108 1297 }
1109 1298
1110 1299 public function getOption( $name )
1111 1300 {
1112 - return $this->options[$name];
1301 + return $this->options[ $name ];
1302 + }
1303 +
1304 + /**
1305 + * Sanitize user-provided CSS.
1306 + *
1307 + * For now just strip_tags (the WordPress way) and preg_replace to escape < in certain cases but might do full CSS escaping in the future, see:
1308 + * https://cheatsheetseries.owasp.org/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.html#rule-4-css-encode-and-strictly-validate-before-inserting-untrusted-data-into-html-style-property-values
1309 + * https://github.com/twigphp/Twig/blob/3.x/src/Extension/EscaperExtension.php#L300-L319
1310 + * https://github.com/laminas/laminas-escaper/blob/2.8.x/src/Escaper.php#L205-L221
1311 + *
1312 + * @param string $css the to be sanitized CSS
1313 + * @return string sanitized CSS.
1314 + */
1315 + public static function sanitize_css( $css )
1316 + {
1317 + $css = wp_strip_all_tags( $css );
1318 + if ( strpos( $css, '<' ) !== false ) {
1319 + $css = preg_replace( '#<(\/?\w+)#', '\00003C$1', $css );
1320 + }
1321 + return $css;
1113 1322 }
1114 1323 }