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