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