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 +485 -218 2.4.1 → 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,48 +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 - // Removes querystring from URL.
216 - if ( ! empty( $exploded_url[1] ) ) {
217 - $new_tag = str_replace( '?' . $exploded_url[1], '', $new_tag );
218 - }
368 + if ( '' !== $new_tag ) {
369 + // Optionally defer (preload) non-aggregated CSS.
370 + $new_tag = $this->optionally_defer_excluded( $new_tag, $url );
219 371
220 - // Defer single CSS if "inline & defer" is ON and there is inline CSS.
221 - if ( $this->defer && ! empty( $this->defer_inline ) ) {
222 - // Get/ set (via filter) the JS to be triggers onload of the preloaded CSS.
223 - $_preload_onload = apply_filters(
224 - 'autoptimize_filter_css_preload_onload',
225 - "this.onload=null;this.rel='stylesheet'",
226 - $url
227 - );
228 - // Adapt original <link> element for CSS to be preloaded and add <noscript>-version for fallback.
229 - $new_tag = '<noscript>' . $new_tag . '</noscript>' . str_replace(
230 - array(
231 - "rel='stylesheet'",
232 - 'rel="stylesheet"',
233 - ),
234 - "rel='preload' as='style' onload=\"" . $_preload_onload . "\"",
235 - $new_tag
236 - );
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 );
237 375 }
376 + }
238 377
239 - // And replace!
378 + // And replace!
379 + if ( ( '' !== $new_tag && $new_tag !== $tag ) || ( '' === $new_tag && apply_filters( 'autoptimize_filter_css_remove_empty_files', false ) ) ) {
240 380 $this->content = str_replace( $tag, $new_tag, $this->content );
241 381 }
242 382 }
243 383 }
@@ -249,12 +389,57 @@
249 389 return false;
250 390 }
251 391
252 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 + /**
253 438 * Checks if the local file referenced by $path is a valid
254 439 * candidate for being inlined into a data: URI
255 440 *
256 - * @param string $path
441 + * @param string $path image path.
257 442 * @return boolean
258 443 */
259 444 private function is_datauri_candidate( $path )
260 445 {
@@ -305,14 +490,14 @@
305 490
306 491 private function check_datauri_exclude_list( $url )
307 492 {
308 493 static $exclude_list = null;
309 - $no_datauris = array();
494 + static $no_datauris = array();
310 495
311 496 // Again, skip doing certain stuff repeatedly when loop-called.
312 497 if ( null === $exclude_list ) {
313 498 $exclude_list = apply_filters( 'autoptimize_filter_css_datauri_exclude', '' );
314 - $no_datauris = array_filter( array_map( 'trim', explode( ',', $exclude_list ) ) );
499 + $no_datauris = array_filter( array_map( 'trim', explode( ',', $exclude_list ) ) );
315 500 }
316 501
317 502 $matched = false;
318 503
@@ -343,20 +528,20 @@
343 528 return $result;
344 529 }
345 530 }
346 531
347 - $hash = md5( $path );
532 + $hash = md5( $path );
348 533 $check = new autoptimizeCache( $hash, 'img' );
349 534 if ( $check->check() ) {
350 535 // we have the base64 image in cache.
351 - $headAndData = $check->retrieve();
352 - $_base64data = explode( ';base64,', $headAndData );
353 - $base64data = $_base64data[1];
536 + $head_and_data = $check->retrieve();
537 + $_base64data = explode( ';base64,', $head_and_data );
538 + $base64data = $_base64data[1];
354 539 unset( $_base64data );
355 540 } else {
356 541 // It's an image and we don't have it in cache, get the type by extension.
357 542 $exploded_path = explode( '.', $path );
358 - $type = end( $exploded_path );
543 + $type = end( $exploded_path );
359 544
360 545 switch ( $type ) {
361 546 case 'jpg':
362 547 case 'jpeg':
@@ -378,17 +563,20 @@
378 563 $dataurihead = 'data:application/octet-stream;base64,';
379 564 }
380 565
381 566 // Encode the data.
382 - $base64data = base64_encode( file_get_contents( $path ) );
383 - $headAndData = $dataurihead . $base64data;
567 + $base64data = base64_encode( file_get_contents( $path ) );
568 + $head_and_data = $dataurihead . $base64data;
384 569
385 570 // Save in cache.
386 - $check->cache( $headAndData, 'text/plain' );
571 + $check->cache( $head_and_data, 'text/plain' );
387 572 }
388 573 unset( $check );
389 574
390 - return array( 'full' => $headAndData, 'base64data' => $base64data );
575 + return array(
576 + 'full' => $head_and_data,
577 + 'base64data' => $base64data,
578 + );
391 579 }
392 580
393 581 /**
394 582 * Given an array of key/value pairs to replace in $string,
@@ -393,10 +581,10 @@
393 581 /**
394 582 * Given an array of key/value pairs to replace in $string,
395 583 * it does so by replacing the longest-matching strings first.
396 584 *
397 - * @param string $string
398 - * @param array $replacements
585 + * @param string $string string in which to replace.
586 + * @param array $replacements to be replaced strings and replacement.
399 587 *
400 588 * @return string
401 589 */
402 590 protected static function replace_longest_matches_first( $string, $replacements = array() )
@@ -450,9 +638,11 @@
450 638 if ( ! empty( $this->cdn_url ) ) {
451 639 $replacement_url = $this->url_replace_cdn( $url );
452 640 // Prepare replacements array.
453 641 $replacements[ $url_src_matches[1][ $count ] ] = str_replace(
454 - $original_url, $replacement_url, $url_src_matches[1][$count]
642 + $original_url,
643 + $replacement_url,
644 + $url_src_matches[1][ $count ]
455 645 );
456 646 }
457 647 }
458 648 }
@@ -466,9 +656,9 @@
466 656 * "Hides" @font-face declarations by replacing them with `%%FONTFACE%%` markers.
467 657 * Also does CDN replacement of any font-urls within those declarations if the `autoptimize_filter_css_fonts_cdn`
468 658 * filter is used.
469 659 *
470 - * @param string $code
660 + * @param string $code HTML being processed to hide fonts.
471 661 * @return string
472 662 */
473 663 public function hide_fontface_and_maybe_cdn( $code )
474 664 {
@@ -488,9 +678,9 @@
488 678 }
489 679
490 680 // Replace declaration with its base64 encoded string.
491 681 $replacement = self::build_marker( 'FONTFACE', $full_match );
492 - $code = str_replace( $match_search, $replacement, $code );
682 + $code = str_replace( $match_search, $replacement, $code );
493 683 }
494 684 }
495 685
496 686 return $code;
@@ -499,9 +689,9 @@
499 689 /**
500 690 * Restores original @font-face declarations that have been "hidden"
501 691 * using `hide_fontface_and_maybe_cdn()`.
502 692 *
503 - * @param string $code
693 + * @param string $code HTML being processed to unhide fonts.
504 694 * @return string
505 695 */
506 696 public function restore_fontface( $code )
507 697 {
@@ -507,9 +697,14 @@
507 697 {
508 698 return $this->restore_marked_content( 'FONTFACE', $code );
509 699 }
510 700
511 - // 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 + */
512 707 public function rewrite_assets( $code )
513 708 {
514 709 // Handle @font-face rules by hiding and processing them separately.
515 710 $code = $this->hide_fontface_and_maybe_cdn( $code );
@@ -525,9 +720,10 @@
525 720 */
526 721
527 722 // Re-write (and/or inline) URLs to point them to the CDN host.
528 723 $url_src_matches = array();
529 - $imgreplace = array();
724 + $imgreplace = array();
725 +
530 726 // Matches and captures anything specified within the literal `url()` and excludes those containing data: URIs.
531 727 preg_match_all( self::ASSETS_REGEX, $code, $url_src_matches );
532 728 if ( is_array( $url_src_matches ) && ! empty( $url_src_matches ) ) {
533 729 foreach ( $url_src_matches[1] as $count => $original_url ) {
@@ -547,17 +743,17 @@
547 743 $excluded = $this->check_datauri_exclude_list( $ipath );
548 744 if ( ! $excluded ) {
549 745 $is_datauri_candidate = $this->is_datauri_candidate( $ipath );
550 746 if ( $is_datauri_candidate ) {
551 - $datauri = $this->build_or_get_datauri_image( $ipath );
552 - $base64data = $datauri['base64data'];
747 + $datauri = $this->build_or_get_datauri_image( $ipath );
748 + $base64data = $datauri['base64data'];
553 749 // Add it to the list for replacement.
554 750 $imgreplace[ $url_src_matches[1][ $count ] ] = str_replace(
555 - $original_url,
556 - $datauri['full'],
557 - $url_src_matches[1][$count]
751 + $original_url,
752 + $datauri['full'],
753 + $url_src_matches[1][ $count ]
558 754 );
559 - $inlined = true;
755 + $inlined = true;
560 756 }
561 757 }
562 758 }
563 759
@@ -568,11 +764,13 @@
568 764 * being inlined for whatever reason above.
569 765 */
570 766 if ( ! $inlined && ( ! empty( $this->cdn_url ) || has_filter( 'autoptimize_filter_base_replace_cdn' ) ) ) {
571 767 // Just do the "simple" CDN replacement.
572 - $replacement_url = $this->url_replace_cdn( $url );
768 + $replacement_url = $this->url_replace_cdn( $url );
573 769 $imgreplace[ $url_src_matches[1][ $count ] ] = str_replace(
574 - $original_url, $replacement_url, $url_src_matches[1][$count]
770 + $original_url,
771 + $replacement_url,
772 + $url_src_matches[1][ $count ]
575 773 );
576 774 }
577 775 }
578 776 }
@@ -584,9 +782,11 @@
584 782
585 783 return $code;
586 784 }
587 785
588 - // Joins and optimizes CSS.
786 + /**
787 + * Joins and optimizes CSS.
788 + */
589 789 public function minify()
590 790 {
591 791 foreach ( $this->css as $group ) {
592 792 list( $media, $css ) = $group;
@@ -591,27 +791,27 @@
591 791 foreach ( $this->css as $group ) {
592 792 list( $media, $css ) = $group;
593 793 if ( preg_match( '#^INLINE;#', $css ) ) {
594 794 // <style>.
595 - $css = preg_replace( '#^INLINE;#', '', $css );
596 - $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.
597 797 $tmpstyle = apply_filters( 'autoptimize_css_individual_style', $css, '' );
598 798 if ( has_filter( 'autoptimize_css_individual_style' ) && ! empty( $tmpstyle ) ) {
599 - $css = $tmpstyle;
799 + $css = $tmpstyle;
600 800 $this->alreadyminified = true;
601 801 }
602 802 } else {
603 803 // <link>
604 804 if ( false !== $css && file_exists( $css ) && is_readable( $css ) ) {
605 - $cssPath = $css;
606 - $css = self::fixurls( $cssPath, file_get_contents( $cssPath ) );
607 - $css = preg_replace( '/\x{EF}\x{BB}\x{BF}/', '', $css );
608 - $tmpstyle = apply_filters( 'autoptimize_css_individual_style', $css, $cssPath );
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 );
609 809 if ( has_filter( 'autoptimize_css_individual_style' ) && ! empty( $tmpstyle ) ) {
610 - $css = $tmpstyle;
810 + $css = $tmpstyle;
611 811 $this->alreadyminified = true;
612 - } elseif ( $this->can_inject_late( $cssPath, $css ) ) {
613 - $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 ) );
614 814 }
615 815 } else {
616 816 // Couldn't read CSS. Maybe getpath isn't working?
617 817 $css = '';
@@ -619,12 +819,12 @@
619 819 }
620 820
621 821 foreach ( $media as $elem ) {
622 822 if ( ! empty( $css ) ) {
623 - if ( ! isset( $this->csscode[$elem] ) ) {
624 - $this->csscode[$elem] = '';
823 + if ( ! isset( $this->csscode[ $elem ] ) ) {
824 + $this->csscode[ $elem ] = '';
625 825 }
626 - $this->csscode[$elem] .= "\n/*FILESTART*/" . $css;
826 + $this->csscode[ $elem ] .= "\n/*FILESTART*/" . $css;
627 827 }
628 828 }
629 829 }
630 830
@@ -637,15 +837,15 @@
637 837 foreach ( $md5list as $med => $sum ) {
638 838 // If same code.
639 839 if ( $sum === $md5sum ) {
640 840 // Add the merged code.
641 - $medianame = $med . ', ' . $media;
642 - $this->csscode[$medianame] = $code;
643 - $md5list[$medianame] = $md5list[$med];
644 - unset( $this->csscode[$med], $this->csscode[$media], $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 ] );
645 845 }
646 846 }
647 - $md5list[$medianame] = $md5sum;
847 + $md5list[ $medianame ] = $md5sum;
648 848 }
649 849 unset( $tmpcss );
650 850
651 851 // Manage @imports, while is for recursive import management.
@@ -658,20 +858,20 @@
658 858 $thiscss_nocomments = preg_replace( '#/\*.*\*/#Us', '', $thiscss );
659 859 while ( preg_match_all( '#@import +(?:url)?(?:(?:\((["\']?)(?:[^"\')]+)\1\)|(["\'])(?:[^"\']+)\2)(?:[^,;"\']+(?:,[^,;"\']+)*)?)(?:;)#mi', $thiscss_nocomments, $matches ) ) {
660 860 foreach ( $matches[0] as $import ) {
661 861 if ( $this->isremovable( $import, $this->cssremovables ) ) {
662 - $thiscss = str_replace( $import, '', $thiscss );
862 + $thiscss = str_replace( $import, '', $thiscss );
663 863 $import_ok = true;
664 864 } else {
665 - $url = trim( preg_replace( '#^.*((?:https?:|ftp:)?//.*\.css).*$#', '$1', trim( $import ) ), " \t\n\r\0\x0B\"'" );
666 - $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 );
667 867 $import_ok = false;
668 868 if ( file_exists( $path ) && is_readable( $path ) ) {
669 - $code = addcslashes( self::fixurls( $path, file_get_contents( $path ) ), "\\" );
670 - $code = preg_replace( '/\x{EF}\x{BB}\x{BF}/', '', $code );
869 + $code = addcslashes( self::fixurls( $path, file_get_contents( $path ) ), '\\' );
870 + $code = preg_replace( '/\x{EF}\x{BB}\x{BF}/', '', $code );
671 871 $tmpstyle = apply_filters( 'autoptimize_css_individual_style', $code, '' );
672 872 if ( has_filter( 'autoptimize_css_individual_style' ) && ! empty( $tmpstyle ) ) {
673 - $code = $tmpstyle;
873 + $code = $tmpstyle;
674 874 $this->alreadyminified = true;
675 875 } elseif ( $this->can_inject_late( $path, $code ) ) {
676 876 $code = self::build_injectlater_marker( $path, md5( $code ) );
677 877 }
@@ -676,11 +876,11 @@
676 876 $code = self::build_injectlater_marker( $path, md5( $code ) );
677 877 }
678 878
679 879 if ( ! empty( $code ) ) {
680 - $tmp_thiscss = preg_replace( '#(/\*FILESTART\*/.*)' . preg_quote( $import, '#' ) . '#Us', '/*FILESTART2*/' . $code . '$1', $thiscss );
880 + $tmp_thiscss = str_replace( $import, stripcslashes( $code ), $thiscss );
681 881 if ( ! empty( $tmp_thiscss ) ) {
682 - $thiscss = $tmp_thiscss;
882 + $thiscss = $tmp_thiscss;
683 883 $import_ok = true;
684 884 unset( $tmp_thiscss );
685 885 }
686 886 }
@@ -716,10 +916,10 @@
716 916 $hash = md5( $code );
717 917 do_action( 'autoptimize_action_css_hash', $hash );
718 918 $ccheck = new autoptimizeCache( $hash, 'css' );
719 919 if ( $ccheck->check() ) {
720 - $code = $ccheck->retrieve();
721 - $this->hashmap[md5( $code )] = $hash;
920 + $code = $ccheck->retrieve();
921 + $this->hashmap[ md5( $code ) ] = $hash;
722 922 continue;
723 923 }
724 924 unset( $ccheck );
725 925
@@ -738,9 +938,9 @@
738 938 $code = $tmp_code;
739 939 unset( $tmp_code );
740 940 }
741 941
742 - $this->hashmap[md5( $code )] = $hash;
942 + $this->hashmap[ md5( $code ) ] = $hash;
743 943 }
744 944
745 945 unset( $code );
746 946 return true;
@@ -764,65 +964,78 @@
764 964
765 965 return $code;
766 966 }
767 967
768 - // Caches the CSS in uncompressed, deflated and gzipped form.
968 + /**
969 + * Caches the CSS in uncompressed, deflated and gzipped form.
970 + */
769 971 public function cache()
770 972 {
771 973 // CSS cache.
772 974 foreach ( $this->csscode as $media => $code ) {
773 - $md5 = $this->hashmap[md5( $code )];
975 + if ( empty( $code ) ) {
976 + continue;
977 + }
978 +
979 + $md5 = $this->hashmap[ md5( $code ) ];
774 980 $cache = new autoptimizeCache( $md5, 'css' );
775 981 if ( ! $cache->check() ) {
776 982 // Cache our code.
777 983 $cache->cache( $code, 'text/css' );
778 984 }
779 - $this->url[$media] = AUTOPTIMIZE_CACHE_URL . $cache->getname();
985 + $this->url[ $media ] = AUTOPTIMIZE_CACHE_URL . $cache->getname();
780 986 }
781 987 }
782 988
783 - // Returns the content.
989 + /**
990 + * Returns the content.
991 + */
784 992 public function getcontent()
785 993 {
786 - // restore comments.
787 - $this->content = $this->restore_comments( $this->content );
788 -
789 - // restore IE hacks.
790 - $this->content = $this->restore_iehacks( $this->content );
791 -
792 - // restore (no)script.
793 - $this->content = $this->restore_marked_content( 'SCRIPT', $this->content );
794 -
795 - // Restore noptimize.
796 - $this->content = $this->restore_noptimize( $this->content );
797 -
798 - // Restore the full content.
994 + // Restore the full content (only applies when "autoptimize_filter_css_justhead" filter is true).
799 995 if ( ! empty( $this->restofcontent ) ) {
800 - $this->content .= $this->restofcontent;
996 + $this->content .= $this->restofcontent;
801 997 $this->restofcontent = '';
802 998 }
803 999
804 - // Inject the new stylesheets.
805 - $replaceTag = array( '<title', 'before' );
806 - $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 + }
807 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 +
808 1020 if ( $this->inline ) {
809 1021 foreach ( $this->csscode as $media => $code ) {
810 - $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 );
811 1023 }
812 1024 } else {
813 1025 if ( $this->defer ) {
814 - $preloadCssBlock = '';
815 - $noScriptCssBlock = "<noscript id=\"aonoscrcss\">";
1026 + $preload_css_block = '';
1027 + $inlined_ccss_block = '';
1028 + $noscript_css_block = '<noscript id="aonoscrcss">';
816 1029
817 1030 $defer_inline_code = $this->defer_inline;
818 1031 if ( ! empty( $defer_inline_code ) ) {
819 1032 if ( apply_filters( 'autoptimize_filter_css_critcss_minify', true ) ) {
820 - $iCssHash = md5( $defer_inline_code );
821 - $iCssCache = new autoptimizeCache( $iCssHash, 'css' );
822 - if ( $iCssCache->check() ) {
1033 + $icss_hash = md5( $defer_inline_code );
1034 + $icss_cache = new autoptimizeCache( $icss_hash, 'css' );
1035 + if ( $icss_cache->check() ) {
823 1036 // we have the optimized inline CSS in cache.
824 - $defer_inline_code = $iCssCache->retrieve();
1037 + $defer_inline_code = $icss_cache->retrieve();
825 1038 } else {
826 1039 $cssmin = new autoptimizeCSSmin();
827 1040 $tmp_code = trim( $cssmin->run( $defer_inline_code ) );
828 1041
@@ -827,15 +1040,16 @@
827 1040 $tmp_code = trim( $cssmin->run( $defer_inline_code ) );
828 1041
829 1042 if ( ! empty( $tmp_code ) ) {
830 1043 $defer_inline_code = $tmp_code;
831 - $iCssCache->cache( $defer_inline_code, 'text/css' );
1044 + $icss_cache->cache( $defer_inline_code, 'text/css' );
832 1045 unset( $tmp_code );
833 1046 }
834 1047 }
835 1048 }
836 - $code_out = '<style type="text/css" id="aoatfcss" media="all">' . $defer_inline_code . '</style>';
837 - $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>';
838 1052 }
839 1053 }
840 1054
841 1055 foreach ( $this->url as $media => $url ) {
@@ -841,40 +1055,54 @@
841 1055 foreach ( $this->url as $media => $url ) {
842 1056 $url = $this->url_replace_cdn( $url );
843 1057
844 1058 // Add the stylesheet either deferred (import at bottom) or normal links in head.
845 - if ( $this->defer ) {
846 - $preloadOnLoad = autoptimizeConfig::get_ao_css_preload_onload();
1059 + if ( $this->defer && 'print' !== $media ) {
1060 + $preload_onload = autoptimizeConfig::get_ao_css_preload_onload( $media );
847 1061
848 - $preloadCssBlock .= '<link rel="preload" as="style" media="' . $media . '" href="' . $url . '" onload="' . $preloadOnLoad . '" />';
849 - $noScriptCssBlock .= '<link type="text/css" media="' . $media . '" href="' . $url . '" rel="stylesheet" />';
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">';
850 1067 } else {
851 - // $this->inject_in_html('<link type="text/css" media="' . $media . '" href="' . $url . '" rel="stylesheet" />', $replaceTag);
852 - if ( strlen( $this->csscode[$media] ) > $this->cssinlinesize ) {
853 - $this->inject_in_html( '<link type="text/css" media="' . $media . '" href="' . $url . '" rel="stylesheet" />', $replaceTag );
854 - } elseif ( strlen( $this->csscode[$media] ) > 0 ) {
855 - $this->inject_in_html( '<style type="text/css" media="' . $media . '">' . $this->csscode[$media] . '</style>', $replaceTag );
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 );
856 1072 }
857 1073 }
858 1074 }
859 1075
860 1076 if ( $this->defer ) {
861 - $preload_polyfill = autoptimizeConfig::get_ao_css_preload_polyfill();
862 - $noScriptCssBlock .= '</noscript>';
863 - $this->inject_in_html( $preloadCssBlock . $noScriptCssBlock, $replaceTag );
864 -
865 - // Adds preload polyfill at end of body tag.
866 - $this->inject_in_html(
867 - apply_filters( 'autoptimize_css_preload_polyfill', $preload_polyfill ),
868 - array( '</body>', 'before' )
869 - );
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 );
870 1080 }
871 1081 }
872 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 +
873 1095 // Return the modified stylesheet.
874 1096 return $this->content;
875 1097 }
876 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 + */
877 1105 static function fixurls( $file, $code )
878 1106 {
879 1107 // Switch all imports to the url() syntax.
880 1108 $code = preg_replace( '#@import ("|\')(.+?)\.css.*?("|\')#', '@import url("${2}.css")', $code );
@@ -881,9 +1109,9 @@
881 1109
882 1110 if ( preg_match_all( self::ASSETS_REGEX, $code, $matches ) ) {
883 1111 $file = str_replace( WP_ROOT_DIR, '/', $file );
884 1112 /**
885 - * rollback as per https://github.com/futtta/autoptimize/issues/94
1113 + * Rollback as per https://github.com/futtta/autoptimize/issues/94
886 1114 * $file = str_replace( AUTOPTIMIZE_WP_CONTENT_NAME, '', $file );
887 1115 */
888 1116 $dir = dirname( $file ); // Like /themes/expound/css.
889 1117
@@ -897,27 +1125,27 @@
897 1125
898 1126 $replace = array();
899 1127 foreach ( $matches[1] as $k => $url ) {
900 1128 // Remove quotes.
901 - $url = trim( $url, " \t\n\r\0\x0B\"'" );
902 - $noQurl = trim( $url, "\"'" );
903 - if ( $url !== $noQurl ) {
904 - $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;
905 1133 } else {
906 - $removedQuotes = false;
1134 + $removed_quotes = false;
907 1135 }
908 1136
909 - if ( '' === $noQurl ) {
1137 + if ( '' === $no_q_url ) {
910 1138 continue;
911 1139 }
912 1140
913 - $url = $noQurl;
914 - 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 ) ) {
915 1143 // URL is protocol-relative, host-relative or something we don't touch.
916 1144 continue;
917 - } else {
918 - // Relative URL.
919 - /**
1145 + } else { // Relative URL.
1146 +
1147 + /*
920 1148 * rollback as per https://github.com/futtta/autoptimize/issues/94
921 1149 * $newurl = preg_replace( '/https?:/', '', str_replace( ' ', '%20', AUTOPTIMIZE_WP_CONTENT_URL . str_replace( '//', '/', $dir . '/' . $url ) ) );
922 1150 */
923 1151 $newurl = preg_replace( '/https?:/', '', str_replace( ' ', '%20', AUTOPTIMIZE_WP_ROOT_URL . str_replace( '//', '/', $dir . '/' . $url ) ) );
@@ -927,15 +1155,15 @@
927 1155 * Hash the url + whatever was behind potentially for replacement
928 1156 * We must do this, or different css classes referencing the same bg image (but
929 1157 * different parts of it, say, in sprites and such) loose their stuff...
930 1158 */
931 - $hash = md5( $url . $matches[2][$k] );
932 - $code = str_replace( $matches[0][$k], $hash, $code );
1159 + $hash = md5( $url . $matches[2][ $k ] );
1160 + $code = str_replace( $matches[0][ $k ], $hash, $code );
933 1161
934 - if ( $removedQuotes ) {
935 - $replace[$hash] = "url('" . $newurl . "')" . $matches[2][$k];
1162 + if ( $removed_quotes ) {
1163 + $replace[ $hash ] = "url('" . $newurl . "')" . $matches[2][ $k ];
936 1164 } else {
937 - $replace[$hash] = 'url(' . $newurl . ')' . $matches[2][$k];
1165 + $replace[ $hash ] = 'url(' . $newurl . ')' . $matches[2][ $k ];
938 1166 }
939 1167 }
940 1168 }
941 1169
@@ -950,15 +1178,15 @@
950 1178 if ( ! $this->aggregate ) {
951 1179 return false;
952 1180 }
953 1181
954 - if ( ! empty( $this->whitelist ) ) {
955 - foreach ( $this->whitelist as $match ) {
1182 + if ( ! empty( $this->allowlist ) ) {
1183 + foreach ( $this->allowlist as $match ) {
956 1184 if ( false !== strpos( $tag, $match ) ) {
957 1185 return true;
958 1186 }
959 1187 }
960 - // no match with whitelist.
1188 + // no match with allowlist.
961 1189 return false;
962 1190 } else {
963 1191 if ( is_array( $this->dontmove ) && ! empty( $this->dontmove ) ) {
964 1192 foreach ( $this->dontmove as $match ) {
@@ -973,15 +1201,15 @@
973 1201 return true;
974 1202 }
975 1203 }
976 1204
977 - private function can_inject_late( $cssPath, $css )
1205 + private function can_inject_late( $css_path, $css )
978 1206 {
979 - $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 );
980 1208 if ( true !== $this->inject_min_late ) {
981 1209 // late-inject turned off.
982 1210 return false;
983 - } 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 ) ) {
984 1212 // file not minified based on filename & filter.
985 1213 return false;
986 1214 } elseif ( false !== strpos( $css, '@import' ) ) {
987 1215 // can't late-inject files with imports as those need to be aggregated.
@@ -988,9 +1216,9 @@
988 1216 return false;
989 1217 } elseif ( ( false !== strpos( $css, '@font-face' ) ) && ( apply_filters( 'autoptimize_filter_css_fonts_cdn', false ) === true ) && ( ! empty( $this->cdn_url ) ) ) {
990 1218 // don't late-inject CSS with font-src's if fonts are set to be CDN'ed.
991 1219 return false;
992 - } 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 ) ) {
993 1221 // don't late-inject CSS with images if CDN is set OR if image inlining is on.
994 1222 return false;
995 1223 } else {
996 1224 // phew, all is safe, we can late-inject.
@@ -1011,8 +1239,15 @@
1011 1239 {
1012 1240 $contents = $this->prepare_minify_single( $filepath );
1013 1241
1014 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 + }
1015 1250 return false;
1016 1251 }
1017 1252
1018 1253 // Check cache.
@@ -1017,8 +1252,9 @@
1017 1252
1018 1253 // Check cache.
1019 1254 $hash = 'single_' . md5( $contents );
1020 1255 $cache = new autoptimizeCache( $hash, 'css' );
1256 + do_action( 'autoptimize_action_css_hash', $hash );
1021 1257
1022 1258 // If not in cache already, minify...
1023 1259 if ( ! $cache->check() || $cache_miss ) {
1024 1260 // Fixurls...
@@ -1023,12 +1259,23 @@
1023 1259 if ( ! $cache->check() || $cache_miss ) {
1024 1260 // Fixurls...
1025 1261 $contents = self::fixurls( $filepath, $contents );
1026 1262 // CDN-replace any referenced assets if needed...
1263 + $contents = $this->hide_fontface_and_maybe_cdn( $contents );
1027 1264 $contents = $this->replace_urls( $contents );
1265 + $contents = $this->restore_fontface( $contents );
1028 1266 // Now minify...
1029 1267 $cssmin = new autoptimizeCSSmin();
1030 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 +
1031 1278 // Store in cache.
1032 1279 $cache->cache( $contents, 'text/css' );
1033 1280 }
1034 1281
@@ -1058,13 +1305,33 @@
1058 1305 }
1059 1306
1060 1307 public function setOption( $name, $value )
1061 1308 {
1062 - $this->options[$name] = $value;
1063 - $this->$name = $value;
1309 + $this->options[ $name ] = $value;
1310 + $this->$name = $value;
1064 1311 }
1065 1312
1066 1313 public function getOption( $name )
1067 1314 {
1068 - 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;
1069 1336 }
1070 1337 }