PluginProbe
Autoptimize / trunk
Autoptimize vtrunk
3.1.16 2.2.2 2.3.0 2.3.1 2.3.2 2.3.3 2.3.4 2.4.0 2.4.1 2.4.2 2.4.3 2.4.4 2.5.0 2.5.1 2.6.0 2.6.1 2.6.2 2.7.0 2.7.1 2.7.2 2.7.3 2.7.4 2.7.5 2.7.6 2.7.7 All 108 releases
← All changes | classes/autoptimizeStyles.php +420 -197 2.6.0 → trunk 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'];
@@ -128,19 +248,22 @@
128 248 // Determine whether excluded files should be minified if not yet so.
129 249 if ( ! $options['minify_excluded'] && $options['aggregate'] ) {
130 250 $this->minify_excluded = false;
131 251 }
132 - $this->minify_excluded = apply_filters( 'autoptimize_filter_css_minify_excluded', $this->minify_excluded );
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 );
235 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 + }
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,11 @@
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,
643 + $replacement_url,
644 + $url_src_matches[1][ $count ]
488 645 );
489 646 }
490 647 }
491 648 }
@@ -499,9 +656,9 @@
499 656 * "Hides" @font-face declarations by replacing them with `%%FONTFACE%%` markers.
500 657 * Also does CDN replacement of any font-urls within those declarations if the `autoptimize_filter_css_fonts_cdn`
501 658 * filter is used.
502 659 *
503 - * @param string $code
660 + * @param string $code HTML being processed to hide fonts.
504 661 * @return string
505 662 */
506 663 public function hide_fontface_and_maybe_cdn( $code )
507 664 {
@@ -521,9 +678,9 @@
521 678 }
522 679
523 680 // Replace declaration with its base64 encoded string.
524 681 $replacement = self::build_marker( 'FONTFACE', $full_match );
525 - $code = str_replace( $match_search, $replacement, $code );
682 + $code = str_replace( $match_search, $replacement, $code );
526 683 }
527 684 }
528 685
529 686 return $code;
@@ -532,9 +689,9 @@
532 689 /**
533 690 * Restores original @font-face declarations that have been "hidden"
534 691 * using `hide_fontface_and_maybe_cdn()`.
535 692 *
536 - * @param string $code
693 + * @param string $code HTML being processed to unhide fonts.
537 694 * @return string
538 695 */
539 696 public function restore_fontface( $code )
540 697 {
@@ -540,9 +697,14 @@
540 697 {
541 698 return $this->restore_marked_content( 'FONTFACE', $code );
542 699 }
543 700
544 - // Re-write (and/or inline) referenced assets.
701 + /**
702 + * Re-write (and/or inline) referenced assets.
703 + *
704 + * @param string $code HTML being processed rewrite assets.
705 + * @return string
706 + */
545 707 public function rewrite_assets( $code )
546 708 {
547 709 // Handle @font-face rules by hiding and processing them separately.
548 710 $code = $this->hide_fontface_and_maybe_cdn( $code );
@@ -558,9 +720,10 @@
558 720 */
559 721
560 722 // Re-write (and/or inline) URLs to point them to the CDN host.
561 723 $url_src_matches = array();
562 - $imgreplace = array();
724 + $imgreplace = array();
725 +
563 726 // Matches and captures anything specified within the literal `url()` and excludes those containing data: URIs.
564 727 preg_match_all( self::ASSETS_REGEX, $code, $url_src_matches );
565 728 if ( is_array( $url_src_matches ) && ! empty( $url_src_matches ) ) {
566 729 foreach ( $url_src_matches[1] as $count => $original_url ) {
@@ -580,17 +743,17 @@
580 743 $excluded = $this->check_datauri_exclude_list( $ipath );
581 744 if ( ! $excluded ) {
582 745 $is_datauri_candidate = $this->is_datauri_candidate( $ipath );
583 746 if ( $is_datauri_candidate ) {
584 - $datauri = $this->build_or_get_datauri_image( $ipath );
585 - $base64data = $datauri['base64data'];
747 + $datauri = $this->build_or_get_datauri_image( $ipath );
748 + $base64data = $datauri['base64data'];
586 749 // Add it to the list for replacement.
587 750 $imgreplace[ $url_src_matches[1][ $count ] ] = str_replace(
588 - $original_url,
589 - $datauri['full'],
590 - $url_src_matches[1][$count]
751 + $original_url,
752 + $datauri['full'],
753 + $url_src_matches[1][ $count ]
591 754 );
592 - $inlined = true;
755 + $inlined = true;
593 756 }
594 757 }
595 758 }
596 759
@@ -601,11 +764,13 @@
601 764 * being inlined for whatever reason above.
602 765 */
603 766 if ( ! $inlined && ( ! empty( $this->cdn_url ) || has_filter( 'autoptimize_filter_base_replace_cdn' ) ) ) {
604 767 // Just do the "simple" CDN replacement.
605 - $replacement_url = $this->url_replace_cdn( $url );
768 + $replacement_url = $this->url_replace_cdn( $url );
606 769 $imgreplace[ $url_src_matches[1][ $count ] ] = str_replace(
607 - $original_url, $replacement_url, $url_src_matches[1][$count]
770 + $original_url,
771 + $replacement_url,
772 + $url_src_matches[1][ $count ]
608 773 );
609 774 }
610 775 }
611 776 }
@@ -617,9 +782,11 @@
617 782
618 783 return $code;
619 784 }
620 785
621 - // Joins and optimizes CSS.
786 + /**
787 + * Joins and optimizes CSS.
788 + */
622 789 public function minify()
623 790 {
624 791 foreach ( $this->css as $group ) {
625 792 list( $media, $css ) = $group;
@@ -624,27 +791,27 @@
624 791 foreach ( $this->css as $group ) {
625 792 list( $media, $css ) = $group;
626 793 if ( preg_match( '#^INLINE;#', $css ) ) {
627 794 // <style>.
628 - $css = preg_replace( '#^INLINE;#', '', $css );
629 - $css = self::fixurls( ABSPATH . 'index.php', $css ); // ABSPATH already contains a trailing slash.
795 + $css = preg_replace( '#^INLINE;#', '', $css );
796 + $css = self::fixurls( ABSPATH . 'index.php', $css ); // ABSPATH already contains a trailing slash.
630 797 $tmpstyle = apply_filters( 'autoptimize_css_individual_style', $css, '' );
631 798 if ( has_filter( 'autoptimize_css_individual_style' ) && ! empty( $tmpstyle ) ) {
632 - $css = $tmpstyle;
799 + $css = $tmpstyle;
633 800 $this->alreadyminified = true;
634 801 }
635 802 } else {
636 803 // <link>
637 804 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 );
805 + $css_path = $css;
806 + $css = self::fixurls( $css_path, file_get_contents( $css_path ) );
807 + $css = preg_replace( '/\x{EF}\x{BB}\x{BF}/', '', $css );
808 + $tmpstyle = apply_filters( 'autoptimize_css_individual_style', $css, $css_path );
642 809 if ( has_filter( 'autoptimize_css_individual_style' ) && ! empty( $tmpstyle ) ) {
643 - $css = $tmpstyle;
810 + $css = $tmpstyle;
644 811 $this->alreadyminified = true;
645 - } elseif ( $this->can_inject_late( $cssPath, $css ) ) {
646 - $css = self::build_injectlater_marker( $cssPath, md5( $css ) );
812 + } elseif ( $this->can_inject_late( $css_path, $css ) ) {
813 + $css = self::build_injectlater_marker( $css_path, md5( $css ) );
647 814 }
648 815 } else {
649 816 // Couldn't read CSS. Maybe getpath isn't working?
650 817 $css = '';
@@ -652,12 +819,12 @@
652 819 }
653 820
654 821 foreach ( $media as $elem ) {
655 822 if ( ! empty( $css ) ) {
656 - if ( ! isset( $this->csscode[$elem] ) ) {
657 - $this->csscode[$elem] = '';
823 + if ( ! isset( $this->csscode[ $elem ] ) ) {
824 + $this->csscode[ $elem ] = '';
658 825 }
659 - $this->csscode[$elem] .= "\n/*FILESTART*/" . $css;
826 + $this->csscode[ $elem ] .= "\n/*FILESTART*/" . $css;
660 827 }
661 828 }
662 829 }
663 830
@@ -670,15 +837,15 @@
670 837 foreach ( $md5list as $med => $sum ) {
671 838 // If same code.
672 839 if ( $sum === $md5sum ) {
673 840 // 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] );
841 + $medianame = $med . ', ' . $media;
842 + $this->csscode[ $medianame ] = $code;
843 + $md5list[ $medianame ] = $md5list[ $med ];
844 + unset( $this->csscode[ $med ], $this->csscode[ $media ], $md5list[ $med ] );
678 845 }
679 846 }
680 - $md5list[$medianame] = $md5sum;
847 + $md5list[ $medianame ] = $md5sum;
681 848 }
682 849 unset( $tmpcss );
683 850
684 851 // Manage @imports, while is for recursive import management.
@@ -691,20 +858,20 @@
691 858 $thiscss_nocomments = preg_replace( '#/\*.*\*/#Us', '', $thiscss );
692 859 while ( preg_match_all( '#@import +(?:url)?(?:(?:\((["\']?)(?:[^"\')]+)\1\)|(["\'])(?:[^"\']+)\2)(?:[^,;"\']+(?:,[^,;"\']+)*)?)(?:;)#mi', $thiscss_nocomments, $matches ) ) {
693 860 foreach ( $matches[0] as $import ) {
694 861 if ( $this->isremovable( $import, $this->cssremovables ) ) {
695 - $thiscss = str_replace( $import, '', $thiscss );
862 + $thiscss = str_replace( $import, '', $thiscss );
696 863 $import_ok = true;
697 864 } else {
698 - $url = trim( preg_replace( '#^.*((?:https?:|ftp:)?//.*\.css).*$#', '$1', trim( $import ) ), " \t\n\r\0\x0B\"'" );
699 - $path = $this->getpath( $url );
865 + $url = trim( preg_replace( '#^.*((?:https?:|ftp:)?//.*\.css).*$#', '$1', trim( $import ) ), " \t\n\r\0\x0B\"'" );
866 + $path = $this->getpath( $url );
700 867 $import_ok = false;
701 868 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 );
869 + $code = addcslashes( self::fixurls( $path, file_get_contents( $path ) ), '\\' );
870 + $code = preg_replace( '/\x{EF}\x{BB}\x{BF}/', '', $code );
704 871 $tmpstyle = apply_filters( 'autoptimize_css_individual_style', $code, '' );
705 872 if ( has_filter( 'autoptimize_css_individual_style' ) && ! empty( $tmpstyle ) ) {
706 - $code = $tmpstyle;
873 + $code = $tmpstyle;
707 874 $this->alreadyminified = true;
708 875 } elseif ( $this->can_inject_late( $path, $code ) ) {
709 876 $code = self::build_injectlater_marker( $path, md5( $code ) );
710 877 }
@@ -709,11 +876,11 @@
709 876 $code = self::build_injectlater_marker( $path, md5( $code ) );
710 877 }
711 878
712 879 if ( ! empty( $code ) ) {
713 - $tmp_thiscss = preg_replace( '#(/\*FILESTART\*/.*)' . preg_quote( $import, '#' ) . '#Us', '/*FILESTART2*/' . $code . '$1', $thiscss );
880 + $tmp_thiscss = str_replace( $import, stripcslashes( $code ), $thiscss );
714 881 if ( ! empty( $tmp_thiscss ) ) {
715 - $thiscss = $tmp_thiscss;
882 + $thiscss = $tmp_thiscss;
716 883 $import_ok = true;
717 884 unset( $tmp_thiscss );
718 885 }
719 886 }
@@ -749,10 +916,10 @@
749 916 $hash = md5( $code );
750 917 do_action( 'autoptimize_action_css_hash', $hash );
751 918 $ccheck = new autoptimizeCache( $hash, 'css' );
752 919 if ( $ccheck->check() ) {
753 - $code = $ccheck->retrieve();
754 - $this->hashmap[md5( $code )] = $hash;
920 + $code = $ccheck->retrieve();
921 + $this->hashmap[ md5( $code ) ] = $hash;
755 922 continue;
756 923 }
757 924 unset( $ccheck );
758 925
@@ -771,9 +938,9 @@
771 938 $code = $tmp_code;
772 939 unset( $tmp_code );
773 940 }
774 941
775 - $this->hashmap[md5( $code )] = $hash;
942 + $this->hashmap[ md5( $code ) ] = $hash;
776 943 }
777 944
778 945 unset( $code );
779 946 return true;
@@ -797,29 +964,37 @@
797 964
798 965 return $code;
799 966 }
800 967
801 - // Caches the CSS in uncompressed, deflated and gzipped form.
968 + /**
969 + * Caches the CSS in uncompressed, deflated and gzipped form.
970 + */
802 971 public function cache()
803 972 {
804 973 // CSS cache.
805 974 foreach ( $this->csscode as $media => $code ) {
806 - $md5 = $this->hashmap[md5( $code )];
975 + if ( empty( $code ) ) {
976 + continue;
977 + }
978 +
979 + $md5 = $this->hashmap[ md5( $code ) ];
807 980 $cache = new autoptimizeCache( $md5, 'css' );
808 981 if ( ! $cache->check() ) {
809 982 // Cache our code.
810 983 $cache->cache( $code, 'text/css' );
811 984 }
812 - $this->url[$media] = AUTOPTIMIZE_CACHE_URL . $cache->getname();
985 + $this->url[ $media ] = AUTOPTIMIZE_CACHE_URL . $cache->getname();
813 986 }
814 987 }
815 988
816 - // Returns the content.
989 + /**
990 + * Returns the content.
991 + */
817 992 public function getcontent()
818 993 {
819 994 // Restore the full content (only applies when "autoptimize_filter_css_justhead" filter is true).
820 995 if ( ! empty( $this->restofcontent ) ) {
821 - $this->content .= $this->restofcontent;
996 + $this->content .= $this->restofcontent;
822 997 $this->restofcontent = '';
823 998 }
824 999
825 1000 // type is not added by default.
@@ -827,30 +1002,40 @@
827 1002 if ( apply_filters( 'autoptimize_filter_cssjs_addtype', false ) ) {
828 1003 $type_css = 'type="text/css" ';
829 1004 }
830 1005
831 - // Inject the new stylesheets.
832 - $replaceTag = array( '<title', 'before' );
833 - $replaceTag = apply_filters( 'autoptimize_filter_css_replacetag', $replaceTag, $this->content );
1006 + // Inject the new stylesheets, if possible after SEO stuff, but we need to
1007 + // already restore script to be able to inject before ld+json instead of title
1008 + // this should be safe here as all has been extracted already but behind a filter anyway.
1009 + if ( $this->inline && true === apply_filters( 'autoptimize_filter_css_restore_js_early', true ) ) {
1010 + $this->content = $this->restore_marked_content( 'SCRIPT', $this->content );
1011 + }
1012 + $_strpos_ldjson = strpos( $this->content, '<script type="application/ld+json"' );
1013 + if ( false !== $_strpos_ldjson && $_strpos_ldjson < strpos( $this->content, '</head' ) ) {
1014 + $replace_tag = array( '<script type="application/ld+json"', 'before' );
1015 + } else {
1016 + $replace_tag = array( '<title', 'before' );
1017 + }
1018 + $replace_tag = apply_filters( 'autoptimize_filter_css_replacetag', $replace_tag, $this->content );
834 1019
835 1020 if ( $this->inline ) {
836 1021 foreach ( $this->csscode as $media => $code ) {
837 - $this->inject_in_html( '<style ' . $type_css . 'media="' . $media . '">' . $code . '</style>', $replaceTag );
1022 + $this->inject_in_html( apply_filters( 'autoptimize_filter_css_bodyreplacementpayload', '<style ' . $type_css . 'media="' . $media . '">' . $code . '</style>' ), $replace_tag );
838 1023 }
839 1024 } else {
840 1025 if ( $this->defer ) {
841 - $preloadCssBlock = '';
1026 + $preload_css_block = '';
842 1027 $inlined_ccss_block = '';
843 - $noScriptCssBlock = "<noscript id=\"aonoscrcss\">";
1028 + $noscript_css_block = '<noscript id="aonoscrcss">';
844 1029
845 1030 $defer_inline_code = $this->defer_inline;
846 1031 if ( ! empty( $defer_inline_code ) ) {
847 1032 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() ) {
1033 + $icss_hash = md5( $defer_inline_code );
1034 + $icss_cache = new autoptimizeCache( $icss_hash, 'css' );
1035 + if ( $icss_cache->check() ) {
851 1036 // we have the optimized inline CSS in cache.
852 - $defer_inline_code = $iCssCache->retrieve();
1037 + $defer_inline_code = $icss_cache->retrieve();
853 1038 } else {
854 1039 $cssmin = new autoptimizeCSSmin();
855 1040 $tmp_code = trim( $cssmin->run( $defer_inline_code ) );
856 1041
@@ -855,9 +1040,9 @@
855 1040 $tmp_code = trim( $cssmin->run( $defer_inline_code ) );
856 1041
857 1042 if ( ! empty( $tmp_code ) ) {
858 1043 $defer_inline_code = $tmp_code;
859 - $iCssCache->cache( $defer_inline_code, 'text/css' );
1044 + $icss_cache->cache( $defer_inline_code, 'text/css' );
860 1045 unset( $tmp_code );
861 1046 }
862 1047 }
863 1048 }
@@ -870,33 +1055,29 @@
870 1055 foreach ( $this->url as $media => $url ) {
871 1056 $url = $this->url_replace_cdn( $url );
872 1057
873 1058 // 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();
1059 + if ( $this->defer && 'print' !== $media ) {
1060 + $preload_onload = autoptimizeConfig::get_ao_css_preload_onload( $media );
876 1061
877 - $preloadCssBlock .= '<link rel="preload" as="style" media="' . $media . '" href="' . $url . '" onload="' . $preloadOnLoad . '" />';
878 - $noScriptCssBlock .= '<link ' . $type_css . 'media="' . $media . '" href="' . $url . '" rel="stylesheet" />';
1062 + $preload_css_block .= apply_filters( 'autoptimize_filter_css_single_deferred_link', '<link rel="stylesheet" media="print" href="' . $url . '" onload="' . $preload_onload . '">' );
1063 + if ( apply_filters( 'autoptimize_filter_css_preload_and_print', false ) ) {
1064 + $preload_css_block = '<link rel="preload" as="stylesheet" href="' . $url . '"/>' . $preload_css_block;
1065 + }
1066 + $noscript_css_block .= '<link ' . $type_css . 'media="' . $media . '" href="' . $url . '" rel="stylesheet">';
879 1067 } 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 );
1068 + if ( strlen( $this->csscode[ $media ] ) > $this->cssinlinesize ) {
1069 + $this->inject_in_html( apply_filters( 'autoptimize_filter_css_bodyreplacementpayload', '<link ' . $type_css . 'media="' . $media . '" href="' . $url . '" rel="stylesheet">' ), $replace_tag );
1070 + } elseif ( strlen( $this->csscode[ $media ] ) > 0 ) {
1071 + $this->inject_in_html( apply_filters( 'autoptimize_filter_css_bodyreplacementpayload', '<style ' . $type_css . 'media="' . $media . '">' . $this->csscode[ $media ] . '</style>' ), $replace_tag );
884 1072 }
885 1073 }
886 1074 }
887 1075
888 1076 if ( $this->defer ) {
889 - $preload_polyfill = autoptimizeConfig::get_ao_css_preload_polyfill();
890 - $noScriptCssBlock .= '</noscript>';
1077 + $noscript_css_block .= '</noscript>';
891 1078 // 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 - );
1079 + $this->inject_in_html( apply_filters( 'autoptimize_filter_css_bodyreplacementpayload', $inlined_ccss_block . $preload_css_block . $noscript_css_block ), $replace_tag );
899 1080 }
900 1081 }
901 1082
902 1083 // restore comments.
@@ -914,8 +1095,14 @@
914 1095 // Return the modified stylesheet.
915 1096 return $this->content;
916 1097 }
917 1098
1099 + /**
1100 + * Make sure URL's are absolute iso relative to original CSS location.
1101 + *
1102 + * @param string $file filename of optimized CSS-file.
1103 + * @param string $code CSS-code in which to fix URL's.
1104 + */
918 1105 static function fixurls( $file, $code )
919 1106 {
920 1107 // Switch all imports to the url() syntax.
921 1108 $code = preg_replace( '#@import ("|\')(.+?)\.css.*?("|\')#', '@import url("${2}.css")', $code );
@@ -922,9 +1109,9 @@
922 1109
923 1110 if ( preg_match_all( self::ASSETS_REGEX, $code, $matches ) ) {
924 1111 $file = str_replace( WP_ROOT_DIR, '/', $file );
925 1112 /**
926 - * rollback as per https://github.com/futtta/autoptimize/issues/94
1113 + * Rollback as per https://github.com/futtta/autoptimize/issues/94
927 1114 * $file = str_replace( AUTOPTIMIZE_WP_CONTENT_NAME, '', $file );
928 1115 */
929 1116 $dir = dirname( $file ); // Like /themes/expound/css.
930 1117
@@ -938,27 +1125,27 @@
938 1125
939 1126 $replace = array();
940 1127 foreach ( $matches[1] as $k => $url ) {
941 1128 // Remove quotes.
942 - $url = trim( $url, " \t\n\r\0\x0B\"'" );
943 - $noQurl = trim( $url, "\"'" );
944 - if ( $url !== $noQurl ) {
945 - $removedQuotes = true;
1129 + $url = trim( $url, " \t\n\r\0\x0B\"'" );
1130 + $no_q_url = trim( $url, "\"'" );
1131 + if ( $url !== $no_q_url ) {
1132 + $removed_quotes = true;
946 1133 } else {
947 - $removedQuotes = false;
1134 + $removed_quotes = false;
948 1135 }
949 1136
950 - if ( '' === $noQurl ) {
1137 + if ( '' === $no_q_url ) {
951 1138 continue;
952 1139 }
953 1140
954 - $url = $noQurl;
1141 + $url = $no_q_url;
955 1142 if ( '/' === $url[0] || preg_match( '#^(https?://|ftp://|data:)#i', $url ) ) {
956 1143 // URL is protocol-relative, host-relative or something we don't touch.
957 1144 continue;
958 - } else {
959 - // Relative URL.
960 - /**
1145 + } else { // Relative URL.
1146 +
1147 + /*
961 1148 * rollback as per https://github.com/futtta/autoptimize/issues/94
962 1149 * $newurl = preg_replace( '/https?:/', '', str_replace( ' ', '%20', AUTOPTIMIZE_WP_CONTENT_URL . str_replace( '//', '/', $dir . '/' . $url ) ) );
963 1150 */
964 1151 $newurl = preg_replace( '/https?:/', '', str_replace( ' ', '%20', AUTOPTIMIZE_WP_ROOT_URL . str_replace( '//', '/', $dir . '/' . $url ) ) );
@@ -968,15 +1155,15 @@
968 1155 * Hash the url + whatever was behind potentially for replacement
969 1156 * We must do this, or different css classes referencing the same bg image (but
970 1157 * different parts of it, say, in sprites and such) loose their stuff...
971 1158 */
972 - $hash = md5( $url . $matches[2][$k] );
973 - $code = str_replace( $matches[0][$k], $hash, $code );
1159 + $hash = md5( $url . $matches[2][ $k ] );
1160 + $code = str_replace( $matches[0][ $k ], $hash, $code );
974 1161
975 - if ( $removedQuotes ) {
976 - $replace[$hash] = "url('" . $newurl . "')" . $matches[2][$k];
1162 + if ( $removed_quotes ) {
1163 + $replace[ $hash ] = "url('" . $newurl . "')" . $matches[2][ $k ];
977 1164 } else {
978 - $replace[$hash] = 'url(' . $newurl . ')' . $matches[2][$k];
1165 + $replace[ $hash ] = 'url(' . $newurl . ')' . $matches[2][ $k ];
979 1166 }
980 1167 }
981 1168 }
982 1169
@@ -991,15 +1178,15 @@
991 1178 if ( ! $this->aggregate ) {
992 1179 return false;
993 1180 }
994 1181
995 - if ( ! empty( $this->whitelist ) ) {
996 - foreach ( $this->whitelist as $match ) {
1182 + if ( ! empty( $this->allowlist ) ) {
1183 + foreach ( $this->allowlist as $match ) {
997 1184 if ( false !== strpos( $tag, $match ) ) {
998 1185 return true;
999 1186 }
1000 1187 }
1001 - // no match with whitelist.
1188 + // no match with allowlist.
1002 1189 return false;
1003 1190 } else {
1004 1191 if ( is_array( $this->dontmove ) && ! empty( $this->dontmove ) ) {
1005 1192 foreach ( $this->dontmove as $match ) {
@@ -1014,15 +1201,15 @@
1014 1201 return true;
1015 1202 }
1016 1203 }
1017 1204
1018 - private function can_inject_late( $cssPath, $css )
1205 + private function can_inject_late( $css_path, $css )
1019 1206 {
1020 - $consider_minified_array = apply_filters( 'autoptimize_filter_css_consider_minified', false, $cssPath );
1207 + $consider_minified_array = apply_filters( 'autoptimize_filter_css_consider_minified', false, $css_path );
1021 1208 if ( true !== $this->inject_min_late ) {
1022 1209 // late-inject turned off.
1023 1210 return false;
1024 - } elseif ( ( false === strpos( $cssPath, 'min.css' ) ) && ( str_replace( $consider_minified_array, '', $cssPath ) === $cssPath ) ) {
1211 + } elseif ( ( false === strpos( $css_path, 'min.css' ) ) && ( str_replace( $consider_minified_array, '', $css_path ) === $css_path ) ) {
1025 1212 // file not minified based on filename & filter.
1026 1213 return false;
1027 1214 } elseif ( false !== strpos( $css, '@import' ) ) {
1028 1215 // can't late-inject files with imports as those need to be aggregated.
@@ -1029,9 +1216,9 @@
1029 1216 return false;
1030 1217 } elseif ( ( false !== strpos( $css, '@font-face' ) ) && ( apply_filters( 'autoptimize_filter_css_fonts_cdn', false ) === true ) && ( ! empty( $this->cdn_url ) ) ) {
1031 1218 // don't late-inject CSS with font-src's if fonts are set to be CDN'ed.
1032 1219 return false;
1033 - } elseif ( ( ( $this->datauris == true ) || ( ! empty( $this->cdn_url ) ) ) && preg_match( '#background[^;}]*url\(#Ui', $css ) ) {
1220 + } elseif ( ( ( true == $this->datauris ) || ( ! empty( $this->cdn_url ) ) ) && preg_match( '#background[^;}]*url\(#Ui', $css ) ) {
1034 1221 // don't late-inject CSS with images if CDN is set OR if image inlining is on.
1035 1222 return false;
1036 1223 } else {
1037 1224 // phew, all is safe, we can late-inject.
@@ -1052,8 +1239,15 @@
1052 1239 {
1053 1240 $contents = $this->prepare_minify_single( $filepath );
1054 1241
1055 1242 if ( empty( $contents ) ) {
1243 + // if aggregate is off and CCSS is used but all files are minified already, then we
1244 + // must make sure the autoptimize_action_css_hash action still fires for CCSS's sake.
1245 + $ao_ccss_key = get_option( 'autoptimize_ccss_key', '' );
1246 + if ( false === $this->aggregate && isset( $ao_ccss_key ) && ! empty( $ao_ccss_key ) ) {
1247 + $hash = 'single_' . md5( file_get_contents( $filepath ) );
1248 + do_action( 'autoptimize_action_css_hash', $hash );
1249 + }
1056 1250 return false;
1057 1251 }
1058 1252
1059 1253 // Check cache.
@@ -1071,8 +1265,17 @@
1071 1265 $contents = $this->restore_fontface( $contents );
1072 1266 // Now minify...
1073 1267 $cssmin = new autoptimizeCSSmin();
1074 1268 $contents = trim( $cssmin->run( $contents ) );
1269 +
1270 + // Check if minified cache content is empty.
1271 + if ( empty( $contents ) ) {
1272 + return false;
1273 + }
1274 +
1275 + // Filter contents of excluded minified CSS.
1276 + $contents = apply_filters( 'autoptimize_filter_css_single_after_minify', $contents );
1277 +
1075 1278 // Store in cache.
1076 1279 $cache->cache( $contents, 'text/css' );
1077 1280 }
1078 1281
@@ -1102,13 +1305,33 @@
1102 1305 }
1103 1306
1104 1307 public function setOption( $name, $value )
1105 1308 {
1106 - $this->options[$name] = $value;
1107 - $this->$name = $value;
1309 + $this->options[ $name ] = $value;
1310 + $this->$name = $value;
1108 1311 }
1109 1312
1110 1313 public function getOption( $name )
1111 1314 {
1112 - return $this->options[$name];
1315 + return $this->options[ $name ];
1316 + }
1317 +
1318 + /**
1319 + * Sanitize user-provided CSS.
1320 + *
1321 + * 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:
1322 + * 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
1323 + * https://github.com/twigphp/Twig/blob/3.x/src/Extension/EscaperExtension.php#L300-L319
1324 + * https://github.com/laminas/laminas-escaper/blob/2.8.x/src/Escaper.php#L205-L221
1325 + *
1326 + * @param string $css the to be sanitized CSS.
1327 + * @return string sanitized CSS.
1328 + */
1329 + public static function sanitize_css( $css )
1330 + {
1331 + $css = wp_strip_all_tags( $css );
1332 + if ( strpos( $css, '<' ) !== false ) {
1333 + $css = preg_replace( '#<(\/?\w+)#', '\00003C$1', $css );
1334 + }
1335 + return $css;
1113 1336 }
1114 1337 }