PluginProbe
Autoptimize / 3.1.16
Autoptimize v3.1.16
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 → 3.1.16 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 }