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 +470 -217 2.4.4 → 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>.
@@ -176,9 +316,9 @@
176 316 $this->css[] = array( $media, $path );
177 317 } else {
178 318 // Link is dynamic (.php etc).
179 319 $new_tag = $this->optionally_defer_excluded( $tag, 'none' );
180 - if ( $new_tag !== $tag ) {
320 + if ( '' !== $new_tag && $new_tag !== $tag ) {
181 321 $this->content = str_replace( $tag, $new_tag, $this->content );
182 322 }
183 323 $tag = '';
184 324 }
@@ -190,9 +330,9 @@
190 330 // And re-hide them to be able to to the removal based on tag.
191 331 $tag = $this->hide_comments( $tag );
192 332
193 333 if ( $this->include_inline ) {
194 - $code = preg_replace( '#^.*<!\[CDATA\[(?:\s*\*/)?(.*)(?://|/\*)\s*?\]\]>.*$#sm', '$1', $code[1] );
334 + $code = preg_replace( '#^.*<!\[CDATA\[(?:\s*\*/)?(.*)(?://|/\*)\s*?\]\]>.*$#sm', '$1', $code[1] );
195 335 $this->css[] = array( $media, 'INLINE;' . $code );
196 336 } else {
197 337 $tag = '';
198 338 }
@@ -200,26 +340,44 @@
200 340
201 341 // Remove the original style tag.
202 342 $this->content = str_replace( $tag, '', $this->content );
203 343 } else {
204 - // Excluded CSS, minify if getpath and filter says so...
205 344 if ( preg_match( '#<link.*href=("|\')(.*)("|\')#Usmi', $tag, $source ) ) {
206 345 $exploded_url = explode( '?', $source[2], 2 );
207 346 $url = $exploded_url[0];
208 347 $path = $this->getpath( $url );
348 + $new_tag = $tag;
209 349
210 - if ( $path && apply_filters( 'autoptimize_filter_css_minify_excluded', true, $url ) ) {
211 - $minified_url = $this->minify_single( $path );
212 - if ( ! empty( $minified_url ) ) {
213 - // Replace orig URL with cached minified URL.
214 - $new_tag = str_replace( $url, $minified_url, $tag );
215 - } else {
216 - $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 + }
217 365 }
366 + }
218 367
368 + if ( '' !== $new_tag ) {
369 + // Optionally defer (preload) non-aggregated CSS.
219 370 $new_tag = $this->optionally_defer_excluded( $new_tag, $url );
220 371
221 - // And replace!
372 + // Check if we still need to CDN (esp. for already minified resources).
373 + if ( ! empty( $this->cdn_url ) || has_filter( 'autoptimize_filter_base_replace_cdn' ) ) {
374 + $new_tag = str_replace( $url, $this->url_replace_cdn( $url ), $new_tag );
375 + }
376 + }
377 +
378 + // And replace!
379 + if ( ( '' !== $new_tag && $new_tag !== $tag ) || ( '' === $new_tag && apply_filters( 'autoptimize_filter_css_remove_empty_files', false ) ) ) {
222 380 $this->content = str_replace( $tag, $new_tag, $this->content );
223 381 }
224 382 }
225 383 }
@@ -234,34 +392,47 @@
234 392 /**
235 393 * Checks if non-optimized CSS is to be preloaded and if so return
236 394 * the tag with preload code.
237 395 *
238 - * @param string $new_tag (required).
396 + * @param string $tag (required).
239 397 * @param string $url (optional).
240 398 *
241 399 * @return string $new_tag
242 400 */
243 - private function optionally_defer_excluded( $new_tag, $url = '' )
401 + private function optionally_defer_excluded( $tag, $url = '' )
244 402 {
245 403 // Defer single CSS if "inline & defer" is ON and there is inline CSS.
246 - if ( $this->defer && ! empty( $this->defer_inline ) ) {
247 - // Get/ set (via filter) the JS to be triggers onload of the preloaded CSS.
248 - $_preload_onload = apply_filters(
249 - 'autoptimize_filter_css_preload_onload',
250 - "this.onload=null;this.rel='stylesheet'",
251 - $url
252 - );
253 - // Adapt original <link> element for CSS to be preloaded and add <noscript>-version for fallback.
254 - $new_tag = '<noscript>' . $new_tag . '</noscript>' . str_replace(
255 - array(
256 - "rel='stylesheet'",
257 - 'rel="stylesheet"',
258 - ),
259 - "rel='preload' as='style' onload=\"" . $_preload_onload . "\"",
260 - $new_tag
261 - );
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;
262 431 }
263 - return $new_tag;
432 +
433 + // Return unchanged $tag.
434 + return $tag;
264 435 }
265 436
266 437 /**
267 438 * Checks if the local file referenced by $path is a valid
@@ -266,9 +437,9 @@
266 437 /**
267 438 * Checks if the local file referenced by $path is a valid
268 439 * candidate for being inlined into a data: URI
269 440 *
270 - * @param string $path
441 + * @param string $path image path.
271 442 * @return boolean
272 443 */
273 444 private function is_datauri_candidate( $path )
274 445 {
@@ -319,14 +490,14 @@
319 490
320 491 private function check_datauri_exclude_list( $url )
321 492 {
322 493 static $exclude_list = null;
323 - $no_datauris = array();
494 + static $no_datauris = array();
324 495
325 496 // Again, skip doing certain stuff repeatedly when loop-called.
326 497 if ( null === $exclude_list ) {
327 498 $exclude_list = apply_filters( 'autoptimize_filter_css_datauri_exclude', '' );
328 - $no_datauris = array_filter( array_map( 'trim', explode( ',', $exclude_list ) ) );
499 + $no_datauris = array_filter( array_map( 'trim', explode( ',', $exclude_list ) ) );
329 500 }
330 501
331 502 $matched = false;
332 503
@@ -357,20 +528,20 @@
357 528 return $result;
358 529 }
359 530 }
360 531
361 - $hash = md5( $path );
532 + $hash = md5( $path );
362 533 $check = new autoptimizeCache( $hash, 'img' );
363 534 if ( $check->check() ) {
364 535 // we have the base64 image in cache.
365 - $headAndData = $check->retrieve();
366 - $_base64data = explode( ';base64,', $headAndData );
367 - $base64data = $_base64data[1];
536 + $head_and_data = $check->retrieve();
537 + $_base64data = explode( ';base64,', $head_and_data );
538 + $base64data = $_base64data[1];
368 539 unset( $_base64data );
369 540 } else {
370 541 // It's an image and we don't have it in cache, get the type by extension.
371 542 $exploded_path = explode( '.', $path );
372 - $type = end( $exploded_path );
543 + $type = end( $exploded_path );
373 544
374 545 switch ( $type ) {
375 546 case 'jpg':
376 547 case 'jpeg':
@@ -392,17 +563,20 @@
392 563 $dataurihead = 'data:application/octet-stream;base64,';
393 564 }
394 565
395 566 // Encode the data.
396 - $base64data = base64_encode( file_get_contents( $path ) );
397 - $headAndData = $dataurihead . $base64data;
567 + $base64data = base64_encode( file_get_contents( $path ) );
568 + $head_and_data = $dataurihead . $base64data;
398 569
399 570 // Save in cache.
400 - $check->cache( $headAndData, 'text/plain' );
571 + $check->cache( $head_and_data, 'text/plain' );
401 572 }
402 573 unset( $check );
403 574
404 - return array( 'full' => $headAndData, 'base64data' => $base64data );
575 + return array(
576 + 'full' => $head_and_data,
577 + 'base64data' => $base64data,
578 + );
405 579 }
406 580
407 581 /**
408 582 * Given an array of key/value pairs to replace in $string,
@@ -407,10 +581,10 @@
407 581 /**
408 582 * Given an array of key/value pairs to replace in $string,
409 583 * it does so by replacing the longest-matching strings first.
410 584 *
411 - * @param string $string
412 - * @param array $replacements
585 + * @param string $string string in which to replace.
586 + * @param array $replacements to be replaced strings and replacement.
413 587 *
414 588 * @return string
415 589 */
416 590 protected static function replace_longest_matches_first( $string, $replacements = array() )
@@ -464,9 +638,11 @@
464 638 if ( ! empty( $this->cdn_url ) ) {
465 639 $replacement_url = $this->url_replace_cdn( $url );
466 640 // Prepare replacements array.
467 641 $replacements[ $url_src_matches[1][ $count ] ] = str_replace(
468 - $original_url, $replacement_url, $url_src_matches[1][$count]
642 + $original_url,
643 + $replacement_url,
644 + $url_src_matches[1][ $count ]
469 645 );
470 646 }
471 647 }
472 648 }
@@ -480,9 +656,9 @@
480 656 * "Hides" @font-face declarations by replacing them with `%%FONTFACE%%` markers.
481 657 * Also does CDN replacement of any font-urls within those declarations if the `autoptimize_filter_css_fonts_cdn`
482 658 * filter is used.
483 659 *
484 - * @param string $code
660 + * @param string $code HTML being processed to hide fonts.
485 661 * @return string
486 662 */
487 663 public function hide_fontface_and_maybe_cdn( $code )
488 664 {
@@ -502,9 +678,9 @@
502 678 }
503 679
504 680 // Replace declaration with its base64 encoded string.
505 681 $replacement = self::build_marker( 'FONTFACE', $full_match );
506 - $code = str_replace( $match_search, $replacement, $code );
682 + $code = str_replace( $match_search, $replacement, $code );
507 683 }
508 684 }
509 685
510 686 return $code;
@@ -513,9 +689,9 @@
513 689 /**
514 690 * Restores original @font-face declarations that have been "hidden"
515 691 * using `hide_fontface_and_maybe_cdn()`.
516 692 *
517 - * @param string $code
693 + * @param string $code HTML being processed to unhide fonts.
518 694 * @return string
519 695 */
520 696 public function restore_fontface( $code )
521 697 {
@@ -521,9 +697,14 @@
521 697 {
522 698 return $this->restore_marked_content( 'FONTFACE', $code );
523 699 }
524 700
525 - // 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 + */
526 707 public function rewrite_assets( $code )
527 708 {
528 709 // Handle @font-face rules by hiding and processing them separately.
529 710 $code = $this->hide_fontface_and_maybe_cdn( $code );
@@ -539,9 +720,10 @@
539 720 */
540 721
541 722 // Re-write (and/or inline) URLs to point them to the CDN host.
542 723 $url_src_matches = array();
543 - $imgreplace = array();
724 + $imgreplace = array();
725 +
544 726 // Matches and captures anything specified within the literal `url()` and excludes those containing data: URIs.
545 727 preg_match_all( self::ASSETS_REGEX, $code, $url_src_matches );
546 728 if ( is_array( $url_src_matches ) && ! empty( $url_src_matches ) ) {
547 729 foreach ( $url_src_matches[1] as $count => $original_url ) {
@@ -561,17 +743,17 @@
561 743 $excluded = $this->check_datauri_exclude_list( $ipath );
562 744 if ( ! $excluded ) {
563 745 $is_datauri_candidate = $this->is_datauri_candidate( $ipath );
564 746 if ( $is_datauri_candidate ) {
565 - $datauri = $this->build_or_get_datauri_image( $ipath );
566 - $base64data = $datauri['base64data'];
747 + $datauri = $this->build_or_get_datauri_image( $ipath );
748 + $base64data = $datauri['base64data'];
567 749 // Add it to the list for replacement.
568 750 $imgreplace[ $url_src_matches[1][ $count ] ] = str_replace(
569 - $original_url,
570 - $datauri['full'],
571 - $url_src_matches[1][$count]
751 + $original_url,
752 + $datauri['full'],
753 + $url_src_matches[1][ $count ]
572 754 );
573 - $inlined = true;
755 + $inlined = true;
574 756 }
575 757 }
576 758 }
577 759
@@ -582,11 +764,13 @@
582 764 * being inlined for whatever reason above.
583 765 */
584 766 if ( ! $inlined && ( ! empty( $this->cdn_url ) || has_filter( 'autoptimize_filter_base_replace_cdn' ) ) ) {
585 767 // Just do the "simple" CDN replacement.
586 - $replacement_url = $this->url_replace_cdn( $url );
768 + $replacement_url = $this->url_replace_cdn( $url );
587 769 $imgreplace[ $url_src_matches[1][ $count ] ] = str_replace(
588 - $original_url, $replacement_url, $url_src_matches[1][$count]
770 + $original_url,
771 + $replacement_url,
772 + $url_src_matches[1][ $count ]
589 773 );
590 774 }
591 775 }
592 776 }
@@ -598,9 +782,11 @@
598 782
599 783 return $code;
600 784 }
601 785
602 - // Joins and optimizes CSS.
786 + /**
787 + * Joins and optimizes CSS.
788 + */
603 789 public function minify()
604 790 {
605 791 foreach ( $this->css as $group ) {
606 792 list( $media, $css ) = $group;
@@ -605,27 +791,27 @@
605 791 foreach ( $this->css as $group ) {
606 792 list( $media, $css ) = $group;
607 793 if ( preg_match( '#^INLINE;#', $css ) ) {
608 794 // <style>.
609 - $css = preg_replace( '#^INLINE;#', '', $css );
610 - $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.
611 797 $tmpstyle = apply_filters( 'autoptimize_css_individual_style', $css, '' );
612 798 if ( has_filter( 'autoptimize_css_individual_style' ) && ! empty( $tmpstyle ) ) {
613 - $css = $tmpstyle;
799 + $css = $tmpstyle;
614 800 $this->alreadyminified = true;
615 801 }
616 802 } else {
617 803 // <link>
618 804 if ( false !== $css && file_exists( $css ) && is_readable( $css ) ) {
619 - $cssPath = $css;
620 - $css = self::fixurls( $cssPath, file_get_contents( $cssPath ) );
621 - $css = preg_replace( '/\x{EF}\x{BB}\x{BF}/', '', $css );
622 - $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 );
623 809 if ( has_filter( 'autoptimize_css_individual_style' ) && ! empty( $tmpstyle ) ) {
624 - $css = $tmpstyle;
810 + $css = $tmpstyle;
625 811 $this->alreadyminified = true;
626 - } elseif ( $this->can_inject_late( $cssPath, $css ) ) {
627 - $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 ) );
628 814 }
629 815 } else {
630 816 // Couldn't read CSS. Maybe getpath isn't working?
631 817 $css = '';
@@ -633,12 +819,12 @@
633 819 }
634 820
635 821 foreach ( $media as $elem ) {
636 822 if ( ! empty( $css ) ) {
637 - if ( ! isset( $this->csscode[$elem] ) ) {
638 - $this->csscode[$elem] = '';
823 + if ( ! isset( $this->csscode[ $elem ] ) ) {
824 + $this->csscode[ $elem ] = '';
639 825 }
640 - $this->csscode[$elem] .= "\n/*FILESTART*/" . $css;
826 + $this->csscode[ $elem ] .= "\n/*FILESTART*/" . $css;
641 827 }
642 828 }
643 829 }
644 830
@@ -651,15 +837,15 @@
651 837 foreach ( $md5list as $med => $sum ) {
652 838 // If same code.
653 839 if ( $sum === $md5sum ) {
654 840 // Add the merged code.
655 - $medianame = $med . ', ' . $media;
656 - $this->csscode[$medianame] = $code;
657 - $md5list[$medianame] = $md5list[$med];
658 - 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 ] );
659 845 }
660 846 }
661 - $md5list[$medianame] = $md5sum;
847 + $md5list[ $medianame ] = $md5sum;
662 848 }
663 849 unset( $tmpcss );
664 850
665 851 // Manage @imports, while is for recursive import management.
@@ -672,20 +858,20 @@
672 858 $thiscss_nocomments = preg_replace( '#/\*.*\*/#Us', '', $thiscss );
673 859 while ( preg_match_all( '#@import +(?:url)?(?:(?:\((["\']?)(?:[^"\')]+)\1\)|(["\'])(?:[^"\']+)\2)(?:[^,;"\']+(?:,[^,;"\']+)*)?)(?:;)#mi', $thiscss_nocomments, $matches ) ) {
674 860 foreach ( $matches[0] as $import ) {
675 861 if ( $this->isremovable( $import, $this->cssremovables ) ) {
676 - $thiscss = str_replace( $import, '', $thiscss );
862 + $thiscss = str_replace( $import, '', $thiscss );
677 863 $import_ok = true;
678 864 } else {
679 - $url = trim( preg_replace( '#^.*((?:https?:|ftp:)?//.*\.css).*$#', '$1', trim( $import ) ), " \t\n\r\0\x0B\"'" );
680 - $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 );
681 867 $import_ok = false;
682 868 if ( file_exists( $path ) && is_readable( $path ) ) {
683 - $code = addcslashes( self::fixurls( $path, file_get_contents( $path ) ), "\\" );
684 - $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 );
685 871 $tmpstyle = apply_filters( 'autoptimize_css_individual_style', $code, '' );
686 872 if ( has_filter( 'autoptimize_css_individual_style' ) && ! empty( $tmpstyle ) ) {
687 - $code = $tmpstyle;
873 + $code = $tmpstyle;
688 874 $this->alreadyminified = true;
689 875 } elseif ( $this->can_inject_late( $path, $code ) ) {
690 876 $code = self::build_injectlater_marker( $path, md5( $code ) );
691 877 }
@@ -690,11 +876,11 @@
690 876 $code = self::build_injectlater_marker( $path, md5( $code ) );
691 877 }
692 878
693 879 if ( ! empty( $code ) ) {
694 - $tmp_thiscss = preg_replace( '#(/\*FILESTART\*/.*)' . preg_quote( $import, '#' ) . '#Us', '/*FILESTART2*/' . $code . '$1', $thiscss );
880 + $tmp_thiscss = str_replace( $import, stripcslashes( $code ), $thiscss );
695 881 if ( ! empty( $tmp_thiscss ) ) {
696 - $thiscss = $tmp_thiscss;
882 + $thiscss = $tmp_thiscss;
697 883 $import_ok = true;
698 884 unset( $tmp_thiscss );
699 885 }
700 886 }
@@ -730,10 +916,10 @@
730 916 $hash = md5( $code );
731 917 do_action( 'autoptimize_action_css_hash', $hash );
732 918 $ccheck = new autoptimizeCache( $hash, 'css' );
733 919 if ( $ccheck->check() ) {
734 - $code = $ccheck->retrieve();
735 - $this->hashmap[md5( $code )] = $hash;
920 + $code = $ccheck->retrieve();
921 + $this->hashmap[ md5( $code ) ] = $hash;
736 922 continue;
737 923 }
738 924 unset( $ccheck );
739 925
@@ -752,9 +938,9 @@
752 938 $code = $tmp_code;
753 939 unset( $tmp_code );
754 940 }
755 941
756 - $this->hashmap[md5( $code )] = $hash;
942 + $this->hashmap[ md5( $code ) ] = $hash;
757 943 }
758 944
759 945 unset( $code );
760 946 return true;
@@ -778,65 +964,78 @@
778 964
779 965 return $code;
780 966 }
781 967
782 - // Caches the CSS in uncompressed, deflated and gzipped form.
968 + /**
969 + * Caches the CSS in uncompressed, deflated and gzipped form.
970 + */
783 971 public function cache()
784 972 {
785 973 // CSS cache.
786 974 foreach ( $this->csscode as $media => $code ) {
787 - $md5 = $this->hashmap[md5( $code )];
975 + if ( empty( $code ) ) {
976 + continue;
977 + }
978 +
979 + $md5 = $this->hashmap[ md5( $code ) ];
788 980 $cache = new autoptimizeCache( $md5, 'css' );
789 981 if ( ! $cache->check() ) {
790 982 // Cache our code.
791 983 $cache->cache( $code, 'text/css' );
792 984 }
793 - $this->url[$media] = AUTOPTIMIZE_CACHE_URL . $cache->getname();
985 + $this->url[ $media ] = AUTOPTIMIZE_CACHE_URL . $cache->getname();
794 986 }
795 987 }
796 988
797 - // Returns the content.
989 + /**
990 + * Returns the content.
991 + */
798 992 public function getcontent()
799 993 {
800 - // restore comments.
801 - $this->content = $this->restore_comments( $this->content );
802 -
803 - // restore IE hacks.
804 - $this->content = $this->restore_iehacks( $this->content );
805 -
806 - // restore (no)script.
807 - $this->content = $this->restore_marked_content( 'SCRIPT', $this->content );
808 -
809 - // Restore noptimize.
810 - $this->content = $this->restore_noptimize( $this->content );
811 -
812 - // Restore the full content.
994 + // Restore the full content (only applies when "autoptimize_filter_css_justhead" filter is true).
813 995 if ( ! empty( $this->restofcontent ) ) {
814 - $this->content .= $this->restofcontent;
996 + $this->content .= $this->restofcontent;
815 997 $this->restofcontent = '';
816 998 }
817 999
818 - // Inject the new stylesheets.
819 - $replaceTag = array( '<title', 'before' );
820 - $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 + }
821 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 +
822 1020 if ( $this->inline ) {
823 1021 foreach ( $this->csscode as $media => $code ) {
824 - $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 );
825 1023 }
826 1024 } else {
827 1025 if ( $this->defer ) {
828 - $preloadCssBlock = '';
829 - $noScriptCssBlock = "<noscript id=\"aonoscrcss\">";
1026 + $preload_css_block = '';
1027 + $inlined_ccss_block = '';
1028 + $noscript_css_block = '<noscript id="aonoscrcss">';
830 1029
831 1030 $defer_inline_code = $this->defer_inline;
832 1031 if ( ! empty( $defer_inline_code ) ) {
833 1032 if ( apply_filters( 'autoptimize_filter_css_critcss_minify', true ) ) {
834 - $iCssHash = md5( $defer_inline_code );
835 - $iCssCache = new autoptimizeCache( $iCssHash, 'css' );
836 - if ( $iCssCache->check() ) {
1033 + $icss_hash = md5( $defer_inline_code );
1034 + $icss_cache = new autoptimizeCache( $icss_hash, 'css' );
1035 + if ( $icss_cache->check() ) {
837 1036 // we have the optimized inline CSS in cache.
838 - $defer_inline_code = $iCssCache->retrieve();
1037 + $defer_inline_code = $icss_cache->retrieve();
839 1038 } else {
840 1039 $cssmin = new autoptimizeCSSmin();
841 1040 $tmp_code = trim( $cssmin->run( $defer_inline_code ) );
842 1041
@@ -841,15 +1040,16 @@
841 1040 $tmp_code = trim( $cssmin->run( $defer_inline_code ) );
842 1041
843 1042 if ( ! empty( $tmp_code ) ) {
844 1043 $defer_inline_code = $tmp_code;
845 - $iCssCache->cache( $defer_inline_code, 'text/css' );
1044 + $icss_cache->cache( $defer_inline_code, 'text/css' );
846 1045 unset( $tmp_code );
847 1046 }
848 1047 }
849 1048 }
850 - $code_out = '<style type="text/css" id="aoatfcss" media="all">' . $defer_inline_code . '</style>';
851 - $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>';
852 1052 }
853 1053 }
854 1054
855 1055 foreach ( $this->url as $media => $url ) {
@@ -855,40 +1055,54 @@
855 1055 foreach ( $this->url as $media => $url ) {
856 1056 $url = $this->url_replace_cdn( $url );
857 1057
858 1058 // Add the stylesheet either deferred (import at bottom) or normal links in head.
859 - if ( $this->defer ) {
860 - $preloadOnLoad = autoptimizeConfig::get_ao_css_preload_onload();
1059 + if ( $this->defer && 'print' !== $media ) {
1060 + $preload_onload = autoptimizeConfig::get_ao_css_preload_onload( $media );
861 1061
862 - $preloadCssBlock .= '<link rel="preload" as="style" media="' . $media . '" href="' . $url . '" onload="' . $preloadOnLoad . '" />';
863 - $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">';
864 1067 } else {
865 - // $this->inject_in_html('<link type="text/css" media="' . $media . '" href="' . $url . '" rel="stylesheet" />', $replaceTag);
866 - if ( strlen( $this->csscode[$media] ) > $this->cssinlinesize ) {
867 - $this->inject_in_html( '<link type="text/css" media="' . $media . '" href="' . $url . '" rel="stylesheet" />', $replaceTag );
868 - } elseif ( strlen( $this->csscode[$media] ) > 0 ) {
869 - $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 );
870 1072 }
871 1073 }
872 1074 }
873 1075
874 1076 if ( $this->defer ) {
875 - $preload_polyfill = autoptimizeConfig::get_ao_css_preload_polyfill();
876 - $noScriptCssBlock .= '</noscript>';
877 - $this->inject_in_html( $preloadCssBlock . $noScriptCssBlock, $replaceTag );
878 -
879 - // Adds preload polyfill at end of body tag.
880 - $this->inject_in_html(
881 - apply_filters( 'autoptimize_css_preload_polyfill', $preload_polyfill ),
882 - array( '</body>', 'before' )
883 - );
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 );
884 1080 }
885 1081 }
886 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 +
887 1095 // Return the modified stylesheet.
888 1096 return $this->content;
889 1097 }
890 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 + */
891 1105 static function fixurls( $file, $code )
892 1106 {
893 1107 // Switch all imports to the url() syntax.
894 1108 $code = preg_replace( '#@import ("|\')(.+?)\.css.*?("|\')#', '@import url("${2}.css")', $code );
@@ -895,9 +1109,9 @@
895 1109
896 1110 if ( preg_match_all( self::ASSETS_REGEX, $code, $matches ) ) {
897 1111 $file = str_replace( WP_ROOT_DIR, '/', $file );
898 1112 /**
899 - * rollback as per https://github.com/futtta/autoptimize/issues/94
1113 + * Rollback as per https://github.com/futtta/autoptimize/issues/94
900 1114 * $file = str_replace( AUTOPTIMIZE_WP_CONTENT_NAME, '', $file );
901 1115 */
902 1116 $dir = dirname( $file ); // Like /themes/expound/css.
903 1117
@@ -911,27 +1125,27 @@
911 1125
912 1126 $replace = array();
913 1127 foreach ( $matches[1] as $k => $url ) {
914 1128 // Remove quotes.
915 - $url = trim( $url, " \t\n\r\0\x0B\"'" );
916 - $noQurl = trim( $url, "\"'" );
917 - if ( $url !== $noQurl ) {
918 - $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;
919 1133 } else {
920 - $removedQuotes = false;
1134 + $removed_quotes = false;
921 1135 }
922 1136
923 - if ( '' === $noQurl ) {
1137 + if ( '' === $no_q_url ) {
924 1138 continue;
925 1139 }
926 1140
927 - $url = $noQurl;
928 - 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 ) ) {
929 1143 // URL is protocol-relative, host-relative or something we don't touch.
930 1144 continue;
931 - } else {
932 - // Relative URL.
933 - /**
1145 + } else { // Relative URL.
1146 +
1147 + /*
934 1148 * rollback as per https://github.com/futtta/autoptimize/issues/94
935 1149 * $newurl = preg_replace( '/https?:/', '', str_replace( ' ', '%20', AUTOPTIMIZE_WP_CONTENT_URL . str_replace( '//', '/', $dir . '/' . $url ) ) );
936 1150 */
937 1151 $newurl = preg_replace( '/https?:/', '', str_replace( ' ', '%20', AUTOPTIMIZE_WP_ROOT_URL . str_replace( '//', '/', $dir . '/' . $url ) ) );
@@ -941,15 +1155,15 @@
941 1155 * Hash the url + whatever was behind potentially for replacement
942 1156 * We must do this, or different css classes referencing the same bg image (but
943 1157 * different parts of it, say, in sprites and such) loose their stuff...
944 1158 */
945 - $hash = md5( $url . $matches[2][$k] );
946 - $code = str_replace( $matches[0][$k], $hash, $code );
1159 + $hash = md5( $url . $matches[2][ $k ] );
1160 + $code = str_replace( $matches[0][ $k ], $hash, $code );
947 1161
948 - if ( $removedQuotes ) {
949 - $replace[$hash] = "url('" . $newurl . "')" . $matches[2][$k];
1162 + if ( $removed_quotes ) {
1163 + $replace[ $hash ] = "url('" . $newurl . "')" . $matches[2][ $k ];
950 1164 } else {
951 - $replace[$hash] = 'url(' . $newurl . ')' . $matches[2][$k];
1165 + $replace[ $hash ] = 'url(' . $newurl . ')' . $matches[2][ $k ];
952 1166 }
953 1167 }
954 1168 }
955 1169
@@ -964,15 +1178,15 @@
964 1178 if ( ! $this->aggregate ) {
965 1179 return false;
966 1180 }
967 1181
968 - if ( ! empty( $this->whitelist ) ) {
969 - foreach ( $this->whitelist as $match ) {
1182 + if ( ! empty( $this->allowlist ) ) {
1183 + foreach ( $this->allowlist as $match ) {
970 1184 if ( false !== strpos( $tag, $match ) ) {
971 1185 return true;
972 1186 }
973 1187 }
974 - // no match with whitelist.
1188 + // no match with allowlist.
975 1189 return false;
976 1190 } else {
977 1191 if ( is_array( $this->dontmove ) && ! empty( $this->dontmove ) ) {
978 1192 foreach ( $this->dontmove as $match ) {
@@ -987,15 +1201,15 @@
987 1201 return true;
988 1202 }
989 1203 }
990 1204
991 - private function can_inject_late( $cssPath, $css )
1205 + private function can_inject_late( $css_path, $css )
992 1206 {
993 - $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 );
994 1208 if ( true !== $this->inject_min_late ) {
995 1209 // late-inject turned off.
996 1210 return false;
997 - } 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 ) ) {
998 1212 // file not minified based on filename & filter.
999 1213 return false;
1000 1214 } elseif ( false !== strpos( $css, '@import' ) ) {
1001 1215 // can't late-inject files with imports as those need to be aggregated.
@@ -1002,9 +1216,9 @@
1002 1216 return false;
1003 1217 } elseif ( ( false !== strpos( $css, '@font-face' ) ) && ( apply_filters( 'autoptimize_filter_css_fonts_cdn', false ) === true ) && ( ! empty( $this->cdn_url ) ) ) {
1004 1218 // don't late-inject CSS with font-src's if fonts are set to be CDN'ed.
1005 1219 return false;
1006 - } 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 ) ) {
1007 1221 // don't late-inject CSS with images if CDN is set OR if image inlining is on.
1008 1222 return false;
1009 1223 } else {
1010 1224 // phew, all is safe, we can late-inject.
@@ -1025,8 +1239,15 @@
1025 1239 {
1026 1240 $contents = $this->prepare_minify_single( $filepath );
1027 1241
1028 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 + }
1029 1250 return false;
1030 1251 }
1031 1252
1032 1253 // Check cache.
@@ -1031,8 +1252,9 @@
1031 1252
1032 1253 // Check cache.
1033 1254 $hash = 'single_' . md5( $contents );
1034 1255 $cache = new autoptimizeCache( $hash, 'css' );
1256 + do_action( 'autoptimize_action_css_hash', $hash );
1035 1257
1036 1258 // If not in cache already, minify...
1037 1259 if ( ! $cache->check() || $cache_miss ) {
1038 1260 // Fixurls...
@@ -1037,12 +1259,23 @@
1037 1259 if ( ! $cache->check() || $cache_miss ) {
1038 1260 // Fixurls...
1039 1261 $contents = self::fixurls( $filepath, $contents );
1040 1262 // CDN-replace any referenced assets if needed...
1263 + $contents = $this->hide_fontface_and_maybe_cdn( $contents );
1041 1264 $contents = $this->replace_urls( $contents );
1265 + $contents = $this->restore_fontface( $contents );
1042 1266 // Now minify...
1043 1267 $cssmin = new autoptimizeCSSmin();
1044 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 +
1045 1278 // Store in cache.
1046 1279 $cache->cache( $contents, 'text/css' );
1047 1280 }
1048 1281
@@ -1072,13 +1305,33 @@
1072 1305 }
1073 1306
1074 1307 public function setOption( $name, $value )
1075 1308 {
1076 - $this->options[$name] = $value;
1077 - $this->$name = $value;
1309 + $this->options[ $name ] = $value;
1310 + $this->$name = $value;
1078 1311 }
1079 1312
1080 1313 public function getOption( $name )
1081 1314 {
1082 - 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;
1083 1336 }
1084 1337 }