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 +486 -214 2.4.2 → trunk View file →
@@ -12,58 +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 67
48 - // public $cdn_url; // Used all over the place implicitly, so will have to be either public or protected :/ .
68 + /**
69 + * Setting if CSS should be aggregated
70 + *
71 + * @var bool
72 + */
73 + private $aggregate = true;
49 74
50 - // Reads the page and collects style tags.
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 + */
150 + private $minify_excluded = true;
151 +
152 + /**
153 + * Setting (filter only); should all media-attributes be forced to "all".
154 + *
155 + * @var bool
156 + */
157 + private $media_force_all = false;
158 +
159 + /**
160 + * Reads the page and collects style tags.
161 + *
162 + * @param array $options all options.
163 + */
51 164 public function read( $options )
52 165 {
53 - $noptimizeCSS = apply_filters( 'autoptimize_filter_css_noptimize', false, $this->content );
54 - 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' ) ) {
55 168 return false;
56 169 }
57 170
58 - $whitelistCSS = apply_filters( 'autoptimize_filter_css_whitelist', '', $this->content );
59 - if ( ! empty( $whitelistCSS ) ) {
60 - $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 ) ) );
61 174 }
62 175
63 - $removableCSS = apply_filters( 'autoptimize_filter_css_removables', '' );
64 - if ( ! empty( $removableCSS ) ) {
65 - $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 ) ) );
66 179 }
67 180
68 181 $this->cssinlinesize = apply_filters( 'autoptimize_filter_css_inlinesize', 256 );
69 182
@@ -84,8 +197,10 @@
84 197 // Returning true for "dontaggregate" turns off aggregation.
85 198 if ( $this->aggregate && apply_filters( 'autoptimize_filter_css_dontaggregate', false ) ) {
86 199 $this->aggregate = false;
87 200 }
201 + // and the filter that should have been there to begin with.
202 + $this->aggregate = apply_filters( 'autoptimize_filter_css_aggregate', $this->aggregate );
88 203
89 204 // include inline?
90 205 if ( apply_filters( 'autoptimize_css_include_inline', $options['include_inline'] ) ) {
91 206 $this->include_inline = true;
@@ -91,11 +206,11 @@
91 206 $this->include_inline = true;
92 207 }
93 208
94 209 // List of CSS strings which are excluded from autoptimization.
95 - $excludeCSS = apply_filters( 'autoptimize_filter_css_exclude', $options['css_exclude'], $this->content );
96 - if ( '' !== $excludeCSS ) {
97 - $this->dontmove = array_filter( array_map( 'trim', explode( ',', $excludeCSS ) ) );
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 ) ) );
98 213 } else {
99 214 $this->dontmove = array();
100 215 }
101 216
@@ -101,16 +216,24 @@
101 216
102 217 // forcefully exclude CSS with data-noptimize attrib.
103 218 $this->dontmove[] = 'data-noptimize';
104 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 +
105 223 // Should we defer css?
106 224 // value: true / false.
107 225 $this->defer = $options['defer'];
108 226 $this->defer = apply_filters( 'autoptimize_filter_css_defer', $this->defer, $this->content );
109 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 +
110 233 // Should we inline while deferring?
111 234 // value: inlined CSS.
112 - $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 );
113 236
114 237 // Should we inline?
115 238 // value: true / false.
116 239 $this->inline = $options['inline'];
@@ -121,17 +244,26 @@
121 244
122 245 // Store data: URIs setting for later use.
123 246 $this->datauris = $options['datauris'];
124 247
248 + // Determine whether excluded files should be minified if not yet so.
249 + if ( ! $options['minify_excluded'] && $options['aggregate'] ) {
250 + $this->minify_excluded = false;
251 + }
252 + $this->minify_excluded = apply_filters( 'autoptimize_filter_css_minify_excluded', $this->minify_excluded, '' );
253 +
254 + // should we force all media-attributes to all?
255 + $this->media_force_all = apply_filters( 'autoptimize_filter_css_tagmedia_forceall', false );
256 +
125 257 // noptimize me.
126 258 $this->content = $this->hide_noptimize( $this->content );
127 259
128 260 // Exclude (no)script, as those may contain CSS which should be left as is.
129 261 $this->content = $this->replace_contents_with_marker_if_exists(
130 - 'SCRIPT',
131 - '<script',
132 - '#<(?:no)?script.*?<\/(?:no)?script>#is',
133 - $this->content
262 + 'SCRIPT',
263 + '<script',
264 + '#<(?:no)?script.*?<\/(?:no)?script>#is',
265 + $this->content
134 266 );
135 267
136 268 // Save IE hacks.
137 269 $this->content = $this->hide_iehacks( $this->content );
@@ -148,17 +280,20 @@
148 280 } elseif ( $this->ismovable( $tag ) ) {
149 281 // Get the media.
150 282 if ( false !== strpos( $tag, 'media=' ) ) {
151 283 preg_match( '#media=(?:"|\')([^>]*)(?:"|\')#Ui', $tag, $medias );
152 - $medias = explode( ',', $medias[1] );
153 - $media = array();
154 - foreach ( $medias as $elem ) {
155 - /* $media[] = current(explode(' ',trim($elem),2)); */
156 - if ( empty( $elem ) ) {
157 - $elem = 'all';
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;
158 293 }
159 -
160 - $media[] = $elem;
294 + } else {
295 + $media = array( 'all' );
161 296 }
162 297 } else {
163 298 // No media specified - applies to all.
164 299 $media = array( 'all' );
@@ -163,8 +298,13 @@
163 298 // No media specified - applies to all.
164 299 $media = array( 'all' );
165 300 }
166 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 +
167 307 $media = apply_filters( 'autoptimize_filter_css_tagmedia', $media, $tag );
168 308
169 309 if ( preg_match( '#<link.*href=("|\')(.*)("|\')#Usmi', $tag, $source ) ) {
170 310 // <link>.
@@ -175,8 +315,12 @@
175 315 // Good link.
176 316 $this->css[] = array( $media, $path );
177 317 } else {
178 318 // Link is dynamic (.php etc).
319 + $new_tag = $this->optionally_defer_excluded( $tag, 'none' );
320 + if ( '' !== $new_tag && $new_tag !== $tag ) {
321 + $this->content = str_replace( $tag, $new_tag, $this->content );
322 + }
179 323 $tag = '';
180 324 }
181 325 } else {
182 326 // Inline css in style tags can be wrapped in comment tags, so restore comments.
@@ -186,9 +330,9 @@
186 330 // And re-hide them to be able to to the removal based on tag.
187 331 $tag = $this->hide_comments( $tag );
188 332
189 333 if ( $this->include_inline ) {
190 - $code = preg_replace( '#^.*<!\[CDATA\[(?:\s*\*/)?(.*)(?://|/\*)\s*?\]\]>.*$#sm', '$1', $code[1] );
334 + $code = preg_replace( '#^.*<!\[CDATA\[(?:\s*\*/)?(.*)(?://|/\*)\s*?\]\]>.*$#sm', '$1', $code[1] );
191 335 $this->css[] = array( $media, 'INLINE;' . $code );
192 336 } else {
193 337 $tag = '';
194 338 }
@@ -196,43 +340,44 @@
196 340
197 341 // Remove the original style tag.
198 342 $this->content = str_replace( $tag, '', $this->content );
199 343 } else {
200 - // Excluded CSS, minify if getpath and filter says so...
201 344 if ( preg_match( '#<link.*href=("|\')(.*)("|\')#Usmi', $tag, $source ) ) {
202 345 $exploded_url = explode( '?', $source[2], 2 );
203 346 $url = $exploded_url[0];
204 347 $path = $this->getpath( $url );
348 + $new_tag = $tag;
205 349
206 - if ( $path && apply_filters( 'autoptimize_filter_css_minify_excluded', true, $url ) ) {
207 - $minified_url = $this->minify_single( $path );
208 - if ( ! empty( $minified_url ) ) {
209 - // Replace orig URL with cached minified URL.
210 - $new_tag = str_replace( $url, $minified_url, $tag );
211 - } else {
212 - $new_tag = $tag;
350 + // Excluded CSS, minify that file:
351 + // -> if aggregate is on and exclude minify is on
352 + // -> if aggregate is off and the file is not in dontmove.
353 + if ( $path && $this->minify_excluded ) {
354 + $consider_minified_array = apply_filters( 'autoptimize_filter_css_consider_minified', false );
355 + if ( ( false === $this->aggregate && str_replace( $this->dontmove, '', $path ) === $path ) || ( true === $this->aggregate && ( false === $consider_minified_array || str_replace( $consider_minified_array, '', $path ) === $path ) ) ) {
356 + $minified_url = $this->minify_single( $path );
357 + if ( ! empty( $minified_url ) ) {
358 + // Replace orig URL with cached minified URL.
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 = '';
364 + }
213 365 }
366 + }
214 367
215 - // Defer single CSS if "inline & defer" is ON and there is inline CSS.
216 - if ( $this->defer && ! empty( $this->defer_inline ) ) {
217 - // Get/ set (via filter) the JS to be triggers onload of the preloaded CSS.
218 - $_preload_onload = apply_filters(
219 - 'autoptimize_filter_css_preload_onload',
220 - "this.onload=null;this.rel='stylesheet'",
221 - $url
222 - );
223 - // Adapt original <link> element for CSS to be preloaded and add <noscript>-version for fallback.
224 - $new_tag = '<noscript>' . $new_tag . '</noscript>' . str_replace(
225 - array(
226 - "rel='stylesheet'",
227 - 'rel="stylesheet"',
228 - ),
229 - "rel='preload' as='style' onload=\"" . $_preload_onload . "\"",
230 - $new_tag
231 - );
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 );
232 375 }
376 + }
233 377
234 - // And replace!
378 + // And replace!
379 + if ( ( '' !== $new_tag && $new_tag !== $tag ) || ( '' === $new_tag && apply_filters( 'autoptimize_filter_css_remove_empty_files', false ) ) ) {
235 380 $this->content = str_replace( $tag, $new_tag, $this->content );
236 381 }
237 382 }
238 383 }
@@ -244,12 +389,57 @@
244 389 return false;
245 390 }
246 391
247 392 /**
393 + * Checks if non-optimized CSS is to be preloaded and if so return
394 + * the tag with preload code.
395 + *
396 + * @param string $tag (required).
397 + * @param string $url (optional).
398 + *
399 + * @return string $new_tag
400 + */
401 + private function optionally_defer_excluded( $tag, $url = '' )
402 + {
403 + // Defer single CSS if "inline & defer" is ON and there is inline CSS.
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;
431 + }
432 +
433 + // Return unchanged $tag.
434 + return $tag;
435 + }
436 +
437 + /**
248 438 * Checks if the local file referenced by $path is a valid
249 439 * candidate for being inlined into a data: URI
250 440 *
251 - * @param string $path
441 + * @param string $path image path.
252 442 * @return boolean
253 443 */
254 444 private function is_datauri_candidate( $path )
255 445 {
@@ -300,14 +490,14 @@
300 490
301 491 private function check_datauri_exclude_list( $url )
302 492 {
303 493 static $exclude_list = null;
304 - $no_datauris = array();
494 + static $no_datauris = array();
305 495
306 496 // Again, skip doing certain stuff repeatedly when loop-called.
307 497 if ( null === $exclude_list ) {
308 498 $exclude_list = apply_filters( 'autoptimize_filter_css_datauri_exclude', '' );
309 - $no_datauris = array_filter( array_map( 'trim', explode( ',', $exclude_list ) ) );
499 + $no_datauris = array_filter( array_map( 'trim', explode( ',', $exclude_list ) ) );
310 500 }
311 501
312 502 $matched = false;
313 503
@@ -338,20 +528,20 @@
338 528 return $result;
339 529 }
340 530 }
341 531
342 - $hash = md5( $path );
532 + $hash = md5( $path );
343 533 $check = new autoptimizeCache( $hash, 'img' );
344 534 if ( $check->check() ) {
345 535 // we have the base64 image in cache.
346 - $headAndData = $check->retrieve();
347 - $_base64data = explode( ';base64,', $headAndData );
348 - $base64data = $_base64data[1];
536 + $head_and_data = $check->retrieve();
537 + $_base64data = explode( ';base64,', $head_and_data );
538 + $base64data = $_base64data[1];
349 539 unset( $_base64data );
350 540 } else {
351 541 // It's an image and we don't have it in cache, get the type by extension.
352 542 $exploded_path = explode( '.', $path );
353 - $type = end( $exploded_path );
543 + $type = end( $exploded_path );
354 544
355 545 switch ( $type ) {
356 546 case 'jpg':
357 547 case 'jpeg':
@@ -373,17 +563,20 @@
373 563 $dataurihead = 'data:application/octet-stream;base64,';
374 564 }
375 565
376 566 // Encode the data.
377 - $base64data = base64_encode( file_get_contents( $path ) );
378 - $headAndData = $dataurihead . $base64data;
567 + $base64data = base64_encode( file_get_contents( $path ) );
568 + $head_and_data = $dataurihead . $base64data;
379 569
380 570 // Save in cache.
381 - $check->cache( $headAndData, 'text/plain' );
571 + $check->cache( $head_and_data, 'text/plain' );
382 572 }
383 573 unset( $check );
384 574
385 - return array( 'full' => $headAndData, 'base64data' => $base64data );
575 + return array(
576 + 'full' => $head_and_data,
577 + 'base64data' => $base64data,
578 + );
386 579 }
387 580
388 581 /**
389 582 * Given an array of key/value pairs to replace in $string,
@@ -388,10 +581,10 @@
388 581 /**
389 582 * Given an array of key/value pairs to replace in $string,
390 583 * it does so by replacing the longest-matching strings first.
391 584 *
392 - * @param string $string
393 - * @param array $replacements
585 + * @param string $string string in which to replace.
586 + * @param array $replacements to be replaced strings and replacement.
394 587 *
395 588 * @return string
396 589 */
397 590 protected static function replace_longest_matches_first( $string, $replacements = array() )
@@ -445,9 +638,11 @@
445 638 if ( ! empty( $this->cdn_url ) ) {
446 639 $replacement_url = $this->url_replace_cdn( $url );
447 640 // Prepare replacements array.
448 641 $replacements[ $url_src_matches[1][ $count ] ] = str_replace(
449 - $original_url, $replacement_url, $url_src_matches[1][$count]
642 + $original_url,
643 + $replacement_url,
644 + $url_src_matches[1][ $count ]
450 645 );
451 646 }
452 647 }
453 648 }
@@ -461,9 +656,9 @@
461 656 * "Hides" @font-face declarations by replacing them with `%%FONTFACE%%` markers.
462 657 * Also does CDN replacement of any font-urls within those declarations if the `autoptimize_filter_css_fonts_cdn`
463 658 * filter is used.
464 659 *
465 - * @param string $code
660 + * @param string $code HTML being processed to hide fonts.
466 661 * @return string
467 662 */
468 663 public function hide_fontface_and_maybe_cdn( $code )
469 664 {
@@ -483,9 +678,9 @@
483 678 }
484 679
485 680 // Replace declaration with its base64 encoded string.
486 681 $replacement = self::build_marker( 'FONTFACE', $full_match );
487 - $code = str_replace( $match_search, $replacement, $code );
682 + $code = str_replace( $match_search, $replacement, $code );
488 683 }
489 684 }
490 685
491 686 return $code;
@@ -494,9 +689,9 @@
494 689 /**
495 690 * Restores original @font-face declarations that have been "hidden"
496 691 * using `hide_fontface_and_maybe_cdn()`.
497 692 *
498 - * @param string $code
693 + * @param string $code HTML being processed to unhide fonts.
499 694 * @return string
500 695 */
501 696 public function restore_fontface( $code )
502 697 {
@@ -502,9 +697,14 @@
502 697 {
503 698 return $this->restore_marked_content( 'FONTFACE', $code );
504 699 }
505 700
506 - // 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 + */
507 707 public function rewrite_assets( $code )
508 708 {
509 709 // Handle @font-face rules by hiding and processing them separately.
510 710 $code = $this->hide_fontface_and_maybe_cdn( $code );
@@ -520,9 +720,10 @@
520 720 */
521 721
522 722 // Re-write (and/or inline) URLs to point them to the CDN host.
523 723 $url_src_matches = array();
524 - $imgreplace = array();
724 + $imgreplace = array();
725 +
525 726 // Matches and captures anything specified within the literal `url()` and excludes those containing data: URIs.
526 727 preg_match_all( self::ASSETS_REGEX, $code, $url_src_matches );
527 728 if ( is_array( $url_src_matches ) && ! empty( $url_src_matches ) ) {
528 729 foreach ( $url_src_matches[1] as $count => $original_url ) {
@@ -542,17 +743,17 @@
542 743 $excluded = $this->check_datauri_exclude_list( $ipath );
543 744 if ( ! $excluded ) {
544 745 $is_datauri_candidate = $this->is_datauri_candidate( $ipath );
545 746 if ( $is_datauri_candidate ) {
546 - $datauri = $this->build_or_get_datauri_image( $ipath );
547 - $base64data = $datauri['base64data'];
747 + $datauri = $this->build_or_get_datauri_image( $ipath );
748 + $base64data = $datauri['base64data'];
548 749 // Add it to the list for replacement.
549 750 $imgreplace[ $url_src_matches[1][ $count ] ] = str_replace(
550 - $original_url,
551 - $datauri['full'],
552 - $url_src_matches[1][$count]
751 + $original_url,
752 + $datauri['full'],
753 + $url_src_matches[1][ $count ]
553 754 );
554 - $inlined = true;
755 + $inlined = true;
555 756 }
556 757 }
557 758 }
558 759
@@ -563,11 +764,13 @@
563 764 * being inlined for whatever reason above.
564 765 */
565 766 if ( ! $inlined && ( ! empty( $this->cdn_url ) || has_filter( 'autoptimize_filter_base_replace_cdn' ) ) ) {
566 767 // Just do the "simple" CDN replacement.
567 - $replacement_url = $this->url_replace_cdn( $url );
768 + $replacement_url = $this->url_replace_cdn( $url );
568 769 $imgreplace[ $url_src_matches[1][ $count ] ] = str_replace(
569 - $original_url, $replacement_url, $url_src_matches[1][$count]
770 + $original_url,
771 + $replacement_url,
772 + $url_src_matches[1][ $count ]
570 773 );
571 774 }
572 775 }
573 776 }
@@ -579,9 +782,11 @@
579 782
580 783 return $code;
581 784 }
582 785
583 - // Joins and optimizes CSS.
786 + /**
787 + * Joins and optimizes CSS.
788 + */
584 789 public function minify()
585 790 {
586 791 foreach ( $this->css as $group ) {
587 792 list( $media, $css ) = $group;
@@ -586,27 +791,27 @@
586 791 foreach ( $this->css as $group ) {
587 792 list( $media, $css ) = $group;
588 793 if ( preg_match( '#^INLINE;#', $css ) ) {
589 794 // <style>.
590 - $css = preg_replace( '#^INLINE;#', '', $css );
591 - $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.
592 797 $tmpstyle = apply_filters( 'autoptimize_css_individual_style', $css, '' );
593 798 if ( has_filter( 'autoptimize_css_individual_style' ) && ! empty( $tmpstyle ) ) {
594 - $css = $tmpstyle;
799 + $css = $tmpstyle;
595 800 $this->alreadyminified = true;
596 801 }
597 802 } else {
598 803 // <link>
599 804 if ( false !== $css && file_exists( $css ) && is_readable( $css ) ) {
600 - $cssPath = $css;
601 - $css = self::fixurls( $cssPath, file_get_contents( $cssPath ) );
602 - $css = preg_replace( '/\x{EF}\x{BB}\x{BF}/', '', $css );
603 - $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 );
604 809 if ( has_filter( 'autoptimize_css_individual_style' ) && ! empty( $tmpstyle ) ) {
605 - $css = $tmpstyle;
810 + $css = $tmpstyle;
606 811 $this->alreadyminified = true;
607 - } elseif ( $this->can_inject_late( $cssPath, $css ) ) {
608 - $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 ) );
609 814 }
610 815 } else {
611 816 // Couldn't read CSS. Maybe getpath isn't working?
612 817 $css = '';
@@ -614,12 +819,12 @@
614 819 }
615 820
616 821 foreach ( $media as $elem ) {
617 822 if ( ! empty( $css ) ) {
618 - if ( ! isset( $this->csscode[$elem] ) ) {
619 - $this->csscode[$elem] = '';
823 + if ( ! isset( $this->csscode[ $elem ] ) ) {
824 + $this->csscode[ $elem ] = '';
620 825 }
621 - $this->csscode[$elem] .= "\n/*FILESTART*/" . $css;
826 + $this->csscode[ $elem ] .= "\n/*FILESTART*/" . $css;
622 827 }
623 828 }
624 829 }
625 830
@@ -632,15 +837,15 @@
632 837 foreach ( $md5list as $med => $sum ) {
633 838 // If same code.
634 839 if ( $sum === $md5sum ) {
635 840 // Add the merged code.
636 - $medianame = $med . ', ' . $media;
637 - $this->csscode[$medianame] = $code;
638 - $md5list[$medianame] = $md5list[$med];
639 - 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 ] );
640 845 }
641 846 }
642 - $md5list[$medianame] = $md5sum;
847 + $md5list[ $medianame ] = $md5sum;
643 848 }
644 849 unset( $tmpcss );
645 850
646 851 // Manage @imports, while is for recursive import management.
@@ -653,20 +858,20 @@
653 858 $thiscss_nocomments = preg_replace( '#/\*.*\*/#Us', '', $thiscss );
654 859 while ( preg_match_all( '#@import +(?:url)?(?:(?:\((["\']?)(?:[^"\')]+)\1\)|(["\'])(?:[^"\']+)\2)(?:[^,;"\']+(?:,[^,;"\']+)*)?)(?:;)#mi', $thiscss_nocomments, $matches ) ) {
655 860 foreach ( $matches[0] as $import ) {
656 861 if ( $this->isremovable( $import, $this->cssremovables ) ) {
657 - $thiscss = str_replace( $import, '', $thiscss );
862 + $thiscss = str_replace( $import, '', $thiscss );
658 863 $import_ok = true;
659 864 } else {
660 - $url = trim( preg_replace( '#^.*((?:https?:|ftp:)?//.*\.css).*$#', '$1', trim( $import ) ), " \t\n\r\0\x0B\"'" );
661 - $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 );
662 867 $import_ok = false;
663 868 if ( file_exists( $path ) && is_readable( $path ) ) {
664 - $code = addcslashes( self::fixurls( $path, file_get_contents( $path ) ), "\\" );
665 - $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 );
666 871 $tmpstyle = apply_filters( 'autoptimize_css_individual_style', $code, '' );
667 872 if ( has_filter( 'autoptimize_css_individual_style' ) && ! empty( $tmpstyle ) ) {
668 - $code = $tmpstyle;
873 + $code = $tmpstyle;
669 874 $this->alreadyminified = true;
670 875 } elseif ( $this->can_inject_late( $path, $code ) ) {
671 876 $code = self::build_injectlater_marker( $path, md5( $code ) );
672 877 }
@@ -671,11 +876,11 @@
671 876 $code = self::build_injectlater_marker( $path, md5( $code ) );
672 877 }
673 878
674 879 if ( ! empty( $code ) ) {
675 - $tmp_thiscss = preg_replace( '#(/\*FILESTART\*/.*)' . preg_quote( $import, '#' ) . '#Us', '/*FILESTART2*/' . $code . '$1', $thiscss );
880 + $tmp_thiscss = str_replace( $import, stripcslashes( $code ), $thiscss );
676 881 if ( ! empty( $tmp_thiscss ) ) {
677 - $thiscss = $tmp_thiscss;
882 + $thiscss = $tmp_thiscss;
678 883 $import_ok = true;
679 884 unset( $tmp_thiscss );
680 885 }
681 886 }
@@ -711,10 +916,10 @@
711 916 $hash = md5( $code );
712 917 do_action( 'autoptimize_action_css_hash', $hash );
713 918 $ccheck = new autoptimizeCache( $hash, 'css' );
714 919 if ( $ccheck->check() ) {
715 - $code = $ccheck->retrieve();
716 - $this->hashmap[md5( $code )] = $hash;
920 + $code = $ccheck->retrieve();
921 + $this->hashmap[ md5( $code ) ] = $hash;
717 922 continue;
718 923 }
719 924 unset( $ccheck );
720 925
@@ -733,9 +938,9 @@
733 938 $code = $tmp_code;
734 939 unset( $tmp_code );
735 940 }
736 941
737 - $this->hashmap[md5( $code )] = $hash;
942 + $this->hashmap[ md5( $code ) ] = $hash;
738 943 }
739 944
740 945 unset( $code );
741 946 return true;
@@ -759,65 +964,78 @@
759 964
760 965 return $code;
761 966 }
762 967
763 - // Caches the CSS in uncompressed, deflated and gzipped form.
968 + /**
969 + * Caches the CSS in uncompressed, deflated and gzipped form.
970 + */
764 971 public function cache()
765 972 {
766 973 // CSS cache.
767 974 foreach ( $this->csscode as $media => $code ) {
768 - $md5 = $this->hashmap[md5( $code )];
975 + if ( empty( $code ) ) {
976 + continue;
977 + }
978 +
979 + $md5 = $this->hashmap[ md5( $code ) ];
769 980 $cache = new autoptimizeCache( $md5, 'css' );
770 981 if ( ! $cache->check() ) {
771 982 // Cache our code.
772 983 $cache->cache( $code, 'text/css' );
773 984 }
774 - $this->url[$media] = AUTOPTIMIZE_CACHE_URL . $cache->getname();
985 + $this->url[ $media ] = AUTOPTIMIZE_CACHE_URL . $cache->getname();
775 986 }
776 987 }
777 988
778 - // Returns the content.
989 + /**
990 + * Returns the content.
991 + */
779 992 public function getcontent()
780 993 {
781 - // restore comments.
782 - $this->content = $this->restore_comments( $this->content );
783 -
784 - // restore IE hacks.
785 - $this->content = $this->restore_iehacks( $this->content );
786 -
787 - // restore (no)script.
788 - $this->content = $this->restore_marked_content( 'SCRIPT', $this->content );
789 -
790 - // Restore noptimize.
791 - $this->content = $this->restore_noptimize( $this->content );
792 -
793 - // Restore the full content.
994 + // Restore the full content (only applies when "autoptimize_filter_css_justhead" filter is true).
794 995 if ( ! empty( $this->restofcontent ) ) {
795 - $this->content .= $this->restofcontent;
996 + $this->content .= $this->restofcontent;
796 997 $this->restofcontent = '';
797 998 }
798 999
799 - // Inject the new stylesheets.
800 - $replaceTag = array( '<title', 'before' );
801 - $replaceTag = apply_filters( 'autoptimize_filter_css_replacetag', $replaceTag, $this->content );
1000 + // type is not added by default.
1001 + $type_css = '';
1002 + if ( apply_filters( 'autoptimize_filter_cssjs_addtype', false ) ) {
1003 + $type_css = 'type="text/css" ';
1004 + }
802 1005
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 );
1019 +
803 1020 if ( $this->inline ) {
804 1021 foreach ( $this->csscode as $media => $code ) {
805 - $this->inject_in_html( '<style type="text/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 );
806 1023 }
807 1024 } else {
808 1025 if ( $this->defer ) {
809 - $preloadCssBlock = '';
810 - $noScriptCssBlock = "<noscript id=\"aonoscrcss\">";
1026 + $preload_css_block = '';
1027 + $inlined_ccss_block = '';
1028 + $noscript_css_block = '<noscript id="aonoscrcss">';
811 1029
812 1030 $defer_inline_code = $this->defer_inline;
813 1031 if ( ! empty( $defer_inline_code ) ) {
814 1032 if ( apply_filters( 'autoptimize_filter_css_critcss_minify', true ) ) {
815 - $iCssHash = md5( $defer_inline_code );
816 - $iCssCache = new autoptimizeCache( $iCssHash, 'css' );
817 - if ( $iCssCache->check() ) {
1033 + $icss_hash = md5( $defer_inline_code );
1034 + $icss_cache = new autoptimizeCache( $icss_hash, 'css' );
1035 + if ( $icss_cache->check() ) {
818 1036 // we have the optimized inline CSS in cache.
819 - $defer_inline_code = $iCssCache->retrieve();
1037 + $defer_inline_code = $icss_cache->retrieve();
820 1038 } else {
821 1039 $cssmin = new autoptimizeCSSmin();
822 1040 $tmp_code = trim( $cssmin->run( $defer_inline_code ) );
823 1041
@@ -822,15 +1040,16 @@
822 1040 $tmp_code = trim( $cssmin->run( $defer_inline_code ) );
823 1041
824 1042 if ( ! empty( $tmp_code ) ) {
825 1043 $defer_inline_code = $tmp_code;
826 - $iCssCache->cache( $defer_inline_code, 'text/css' );
1044 + $icss_cache->cache( $defer_inline_code, 'text/css' );
827 1045 unset( $tmp_code );
828 1046 }
829 1047 }
830 1048 }
831 - $code_out = '<style type="text/css" id="aoatfcss" media="all">' . $defer_inline_code . '</style>';
832 - $this->inject_in_html( $code_out, $replaceTag );
1049 + // inlined critical css set here, but injected when full CSS is injected
1050 + // to avoid CSS containing SVG with <title tag receiving the full CSS link.
1051 + $inlined_ccss_block = '<style ' . $type_css . 'id="aoatfcss" media="all">' . $defer_inline_code . '</style>';
833 1052 }
834 1053 }
835 1054
836 1055 foreach ( $this->url as $media => $url ) {
@@ -836,40 +1055,54 @@
836 1055 foreach ( $this->url as $media => $url ) {
837 1056 $url = $this->url_replace_cdn( $url );
838 1057
839 1058 // Add the stylesheet either deferred (import at bottom) or normal links in head.
840 - if ( $this->defer ) {
841 - $preloadOnLoad = autoptimizeConfig::get_ao_css_preload_onload();
1059 + if ( $this->defer && 'print' !== $media ) {
1060 + $preload_onload = autoptimizeConfig::get_ao_css_preload_onload( $media );
842 1061
843 - $preloadCssBlock .= '<link rel="preload" as="style" media="' . $media . '" href="' . $url . '" onload="' . $preloadOnLoad . '" />';
844 - $noScriptCssBlock .= '<link type="text/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">';
845 1067 } else {
846 - // $this->inject_in_html('<link type="text/css" media="' . $media . '" href="' . $url . '" rel="stylesheet" />', $replaceTag);
847 - if ( strlen( $this->csscode[$media] ) > $this->cssinlinesize ) {
848 - $this->inject_in_html( '<link type="text/css" media="' . $media . '" href="' . $url . '" rel="stylesheet" />', $replaceTag );
849 - } elseif ( strlen( $this->csscode[$media] ) > 0 ) {
850 - $this->inject_in_html( '<style type="text/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 );
851 1072 }
852 1073 }
853 1074 }
854 1075
855 1076 if ( $this->defer ) {
856 - $preload_polyfill = autoptimizeConfig::get_ao_css_preload_polyfill();
857 - $noScriptCssBlock .= '</noscript>';
858 - $this->inject_in_html( $preloadCssBlock . $noScriptCssBlock, $replaceTag );
859 -
860 - // Adds preload polyfill at end of body tag.
861 - $this->inject_in_html(
862 - apply_filters( 'autoptimize_css_preload_polyfill', $preload_polyfill ),
863 - array( '</body>', 'before' )
864 - );
1077 + $noscript_css_block .= '</noscript>';
1078 + // Inject inline critical CSS, the preloaded full CSS and the noscript-CSS.
1079 + $this->inject_in_html( apply_filters( 'autoptimize_filter_css_bodyreplacementpayload', $inlined_ccss_block . $preload_css_block . $noscript_css_block ), $replace_tag );
865 1080 }
866 1081 }
867 1082
1083 + // restore comments.
1084 + $this->content = $this->restore_comments( $this->content );
1085 +
1086 + // restore IE hacks.
1087 + $this->content = $this->restore_iehacks( $this->content );
1088 +
1089 + // restore (no)script.
1090 + $this->content = $this->restore_marked_content( 'SCRIPT', $this->content );
1091 +
1092 + // Restore noptimize.
1093 + $this->content = $this->restore_noptimize( $this->content );
1094 +
868 1095 // Return the modified stylesheet.
869 1096 return $this->content;
870 1097 }
871 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 + */
872 1105 static function fixurls( $file, $code )
873 1106 {
874 1107 // Switch all imports to the url() syntax.
875 1108 $code = preg_replace( '#@import ("|\')(.+?)\.css.*?("|\')#', '@import url("${2}.css")', $code );
@@ -876,9 +1109,9 @@
876 1109
877 1110 if ( preg_match_all( self::ASSETS_REGEX, $code, $matches ) ) {
878 1111 $file = str_replace( WP_ROOT_DIR, '/', $file );
879 1112 /**
880 - * rollback as per https://github.com/futtta/autoptimize/issues/94
1113 + * Rollback as per https://github.com/futtta/autoptimize/issues/94
881 1114 * $file = str_replace( AUTOPTIMIZE_WP_CONTENT_NAME, '', $file );
882 1115 */
883 1116 $dir = dirname( $file ); // Like /themes/expound/css.
884 1117
@@ -892,27 +1125,27 @@
892 1125
893 1126 $replace = array();
894 1127 foreach ( $matches[1] as $k => $url ) {
895 1128 // Remove quotes.
896 - $url = trim( $url, " \t\n\r\0\x0B\"'" );
897 - $noQurl = trim( $url, "\"'" );
898 - if ( $url !== $noQurl ) {
899 - $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;
900 1133 } else {
901 - $removedQuotes = false;
1134 + $removed_quotes = false;
902 1135 }
903 1136
904 - if ( '' === $noQurl ) {
1137 + if ( '' === $no_q_url ) {
905 1138 continue;
906 1139 }
907 1140
908 - $url = $noQurl;
909 - if ( '/' === $url{0} || preg_match( '#^(https?://|ftp://|data:)#i', $url ) ) {
1141 + $url = $no_q_url;
1142 + if ( '/' === $url[0] || preg_match( '#^(https?://|ftp://|data:)#i', $url ) ) {
910 1143 // URL is protocol-relative, host-relative or something we don't touch.
911 1144 continue;
912 - } else {
913 - // Relative URL.
914 - /**
1145 + } else { // Relative URL.
1146 +
1147 + /*
915 1148 * rollback as per https://github.com/futtta/autoptimize/issues/94
916 1149 * $newurl = preg_replace( '/https?:/', '', str_replace( ' ', '%20', AUTOPTIMIZE_WP_CONTENT_URL . str_replace( '//', '/', $dir . '/' . $url ) ) );
917 1150 */
918 1151 $newurl = preg_replace( '/https?:/', '', str_replace( ' ', '%20', AUTOPTIMIZE_WP_ROOT_URL . str_replace( '//', '/', $dir . '/' . $url ) ) );
@@ -922,15 +1155,15 @@
922 1155 * Hash the url + whatever was behind potentially for replacement
923 1156 * We must do this, or different css classes referencing the same bg image (but
924 1157 * different parts of it, say, in sprites and such) loose their stuff...
925 1158 */
926 - $hash = md5( $url . $matches[2][$k] );
927 - $code = str_replace( $matches[0][$k], $hash, $code );
1159 + $hash = md5( $url . $matches[2][ $k ] );
1160 + $code = str_replace( $matches[0][ $k ], $hash, $code );
928 1161
929 - if ( $removedQuotes ) {
930 - $replace[$hash] = "url('" . $newurl . "')" . $matches[2][$k];
1162 + if ( $removed_quotes ) {
1163 + $replace[ $hash ] = "url('" . $newurl . "')" . $matches[2][ $k ];
931 1164 } else {
932 - $replace[$hash] = 'url(' . $newurl . ')' . $matches[2][$k];
1165 + $replace[ $hash ] = 'url(' . $newurl . ')' . $matches[2][ $k ];
933 1166 }
934 1167 }
935 1168 }
936 1169
@@ -945,15 +1178,15 @@
945 1178 if ( ! $this->aggregate ) {
946 1179 return false;
947 1180 }
948 1181
949 - if ( ! empty( $this->whitelist ) ) {
950 - foreach ( $this->whitelist as $match ) {
1182 + if ( ! empty( $this->allowlist ) ) {
1183 + foreach ( $this->allowlist as $match ) {
951 1184 if ( false !== strpos( $tag, $match ) ) {
952 1185 return true;
953 1186 }
954 1187 }
955 - // no match with whitelist.
1188 + // no match with allowlist.
956 1189 return false;
957 1190 } else {
958 1191 if ( is_array( $this->dontmove ) && ! empty( $this->dontmove ) ) {
959 1192 foreach ( $this->dontmove as $match ) {
@@ -968,15 +1201,15 @@
968 1201 return true;
969 1202 }
970 1203 }
971 1204
972 - private function can_inject_late( $cssPath, $css )
1205 + private function can_inject_late( $css_path, $css )
973 1206 {
974 - $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 );
975 1208 if ( true !== $this->inject_min_late ) {
976 1209 // late-inject turned off.
977 1210 return false;
978 - } 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 ) ) {
979 1212 // file not minified based on filename & filter.
980 1213 return false;
981 1214 } elseif ( false !== strpos( $css, '@import' ) ) {
982 1215 // can't late-inject files with imports as those need to be aggregated.
@@ -983,9 +1216,9 @@
983 1216 return false;
984 1217 } elseif ( ( false !== strpos( $css, '@font-face' ) ) && ( apply_filters( 'autoptimize_filter_css_fonts_cdn', false ) === true ) && ( ! empty( $this->cdn_url ) ) ) {
985 1218 // don't late-inject CSS with font-src's if fonts are set to be CDN'ed.
986 1219 return false;
987 - } 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 ) ) {
988 1221 // don't late-inject CSS with images if CDN is set OR if image inlining is on.
989 1222 return false;
990 1223 } else {
991 1224 // phew, all is safe, we can late-inject.
@@ -1006,8 +1239,15 @@
1006 1239 {
1007 1240 $contents = $this->prepare_minify_single( $filepath );
1008 1241
1009 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 + }
1010 1250 return false;
1011 1251 }
1012 1252
1013 1253 // Check cache.
@@ -1012,8 +1252,9 @@
1012 1252
1013 1253 // Check cache.
1014 1254 $hash = 'single_' . md5( $contents );
1015 1255 $cache = new autoptimizeCache( $hash, 'css' );
1256 + do_action( 'autoptimize_action_css_hash', $hash );
1016 1257
1017 1258 // If not in cache already, minify...
1018 1259 if ( ! $cache->check() || $cache_miss ) {
1019 1260 // Fixurls...
@@ -1018,12 +1259,23 @@
1018 1259 if ( ! $cache->check() || $cache_miss ) {
1019 1260 // Fixurls...
1020 1261 $contents = self::fixurls( $filepath, $contents );
1021 1262 // CDN-replace any referenced assets if needed...
1263 + $contents = $this->hide_fontface_and_maybe_cdn( $contents );
1022 1264 $contents = $this->replace_urls( $contents );
1265 + $contents = $this->restore_fontface( $contents );
1023 1266 // Now minify...
1024 1267 $cssmin = new autoptimizeCSSmin();
1025 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 +
1026 1278 // Store in cache.
1027 1279 $cache->cache( $contents, 'text/css' );
1028 1280 }
1029 1281
@@ -1053,13 +1305,33 @@
1053 1305 }
1054 1306
1055 1307 public function setOption( $name, $value )
1056 1308 {
1057 - $this->options[$name] = $value;
1058 - $this->$name = $value;
1309 + $this->options[ $name ] = $value;
1310 + $this->$name = $value;
1059 1311 }
1060 1312
1061 1313 public function getOption( $name )
1062 1314 {
1063 - 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;
1064 1336 }
1065 1337 }