PluginProbe
Autoptimize / 3.1.16
Autoptimize v3.1.16
3.1.16 2.2.2 2.3.0 2.3.1 2.3.2 2.3.3 2.3.4 2.4.0 2.4.1 2.4.2 2.4.3 2.4.4 2.5.0 2.5.1 2.6.0 2.6.1 2.6.2 2.7.0 2.7.1 2.7.2 2.7.3 2.7.4 2.7.5 2.7.6 2.7.7 All 108 releases
← All changes | classes/autoptimizeStyles.php +99 -36 2.7.5 → 3.1.16 View file →
@@ -163,14 +163,13 @@
163 163 */
164 164 public function read( $options )
165 165 {
166 166 $noptimize_css = apply_filters( 'autoptimize_filter_css_noptimize', false, $this->content );
167 - if ( $noptimize_css ) {
167 + if ( $noptimize_css || false === autoptimizeConfig::get_post_meta_ao_settings( 'ao_post_css_optimize' ) ) {
168 168 return false;
169 169 }
170 170
171 171 $allowlist_css = apply_filters( 'autoptimize_filter_css_allowlist', '', $this->content );
172 - $allowlist_css = apply_filters( 'autoptimize_filter_css_whitelist', $allowlist_css, $this->content ); // fixme: to be removed in next version.
173 172 if ( ! empty( $allowlist_css ) ) {
174 173 $this->allowlist = array_filter( array_map( 'trim', explode( ',', $allowlist_css ) ) );
175 174 }
176 175
@@ -198,8 +197,10 @@
198 197 // Returning true for "dontaggregate" turns off aggregation.
199 198 if ( $this->aggregate && apply_filters( 'autoptimize_filter_css_dontaggregate', false ) ) {
200 199 $this->aggregate = false;
201 200 }
201 + // and the filter that should have been there to begin with.
202 + $this->aggregate = apply_filters( 'autoptimize_filter_css_aggregate', $this->aggregate );
202 203
203 204 // include inline?
204 205 if ( apply_filters( 'autoptimize_css_include_inline', $options['include_inline'] ) ) {
205 206 $this->include_inline = true;
@@ -215,16 +216,24 @@
215 216
216 217 // forcefully exclude CSS with data-noptimize attrib.
217 218 $this->dontmove[] = 'data-noptimize';
218 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 +
219 223 // Should we defer css?
220 224 // value: true / false.
221 225 $this->defer = $options['defer'];
222 226 $this->defer = apply_filters( 'autoptimize_filter_css_defer', $this->defer, $this->content );
223 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 +
224 233 // Should we inline while deferring?
225 234 // value: inlined CSS.
226 - $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 );
227 236
228 237 // Should we inline?
229 238 // value: true / false.
230 239 $this->inline = $options['inline'];
@@ -271,16 +280,20 @@
271 280 } elseif ( $this->ismovable( $tag ) ) {
272 281 // Get the media.
273 282 if ( false !== strpos( $tag, 'media=' ) ) {
274 283 preg_match( '#media=(?:"|\')([^>]*)(?:"|\')#Ui', $tag, $medias );
275 - $medias = explode( ',', $medias[1] );
276 - $media = array();
277 - foreach ( $medias as $elem ) {
278 - if ( empty( $elem ) ) {
279 - $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;
280 293 }
281 -
282 - $media[] = $elem;
294 + } else {
295 + $media = array( 'all' );
283 296 }
284 297 } else {
285 298 // No media specified - applies to all.
286 299 $media = array( 'all' );
@@ -354,8 +367,13 @@
354 367
355 368 if ( '' !== $new_tag ) {
356 369 // Optionally defer (preload) non-aggregated CSS.
357 370 $new_tag = $this->optionally_defer_excluded( $new_tag, $url );
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 + }
358 376 }
359 377
360 378 // And replace!
361 379 if ( ( '' !== $new_tag && $new_tag !== $tag ) || ( '' === $new_tag && apply_filters( 'autoptimize_filter_css_remove_empty_files', false ) ) ) {
@@ -384,26 +402,30 @@
384 402 {
385 403 // Defer single CSS if "inline & defer" is ON and there is inline CSS.
386 404 if ( ! empty( $tag ) && false === strpos( $tag, ' onload=' ) && $this->defer && ! empty( $this->defer_inline ) && apply_filters( 'autoptimize_filter_css_defer_excluded', true, $tag ) ) {
387 405 // get media attribute and based on that create onload JS attribute value.
388 - if ( false !== strpos( $tag, 'media=' ) ) {
389 - preg_match( '#media=(?:"|\')([^>]*)(?:"|\')#Ui', $tag, $_medias );
390 - $_media = $_medias[1];
391 - } else {
392 - $_media = 'all';
406 + if ( false === strpos( $tag, 'media=' ) ) {
407 + $tag = str_replace( '<link', "<link media='all'", $tag );
393 408 }
409 +
410 + preg_match( '#media=(?:"|\')([^>]*)(?:"|\')#Ui', $tag, $_medias );
411 + $_media = $_medias[1];
394 412 $_preload_onload = autoptimizeConfig::get_ao_css_preload_onload( $_media );
395 413
396 - // Adapt original <link> element for CSS to be preloaded and add <noscript>-version for fallback.
397 - $new_tag = '<noscript>' . autoptimizeUtils::remove_id_from_node( $tag ) . '</noscript>' . str_replace(
398 - $_medias[0],
399 - "media='print' onload=\"" . $_preload_onload . '"',
400 - $tag
401 - );
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 + );
402 421
403 - // Optionally (but default false) preload the (excluded) CSS-file.
404 - if ( apply_filters( 'autoptimize_fitler_css_preload_and_print', false ) && 'none' !== $url ) {
405 - $new_tag = '<link rel="preload" as="stylesheet" href="' . $url . '"/>' . $new_tag;
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;
406 428 }
407 429
408 430 return $new_tag;
409 431 }
@@ -468,9 +490,9 @@
468 490
469 491 private function check_datauri_exclude_list( $url )
470 492 {
471 493 static $exclude_list = null;
472 - $no_datauris = array();
494 + static $no_datauris = array();
473 495
474 496 // Again, skip doing certain stuff repeatedly when loop-called.
475 497 if ( null === $exclude_list ) {
476 498 $exclude_list = apply_filters( 'autoptimize_filter_css_datauri_exclude', '' );
@@ -616,9 +638,11 @@
616 638 if ( ! empty( $this->cdn_url ) ) {
617 639 $replacement_url = $this->url_replace_cdn( $url );
618 640 // Prepare replacements array.
619 641 $replacements[ $url_src_matches[1][ $count ] ] = str_replace(
620 - $original_url, $replacement_url, $url_src_matches[1][ $count ]
642 + $original_url,
643 + $replacement_url,
644 + $url_src_matches[1][ $count ]
621 645 );
622 646 }
623 647 }
624 648 }
@@ -742,9 +766,11 @@
742 766 if ( ! $inlined && ( ! empty( $this->cdn_url ) || has_filter( 'autoptimize_filter_base_replace_cdn' ) ) ) {
743 767 // Just do the "simple" CDN replacement.
744 768 $replacement_url = $this->url_replace_cdn( $url );
745 769 $imgreplace[ $url_src_matches[1][ $count ] ] = str_replace(
746 - $original_url, $replacement_url, $url_src_matches[1][ $count ]
770 + $original_url,
771 + $replacement_url,
772 + $url_src_matches[1][ $count ]
747 773 );
748 774 }
749 775 }
750 776 }
@@ -850,9 +876,9 @@
850 876 $code = self::build_injectlater_marker( $path, md5( $code ) );
851 877 }
852 878
853 879 if ( ! empty( $code ) ) {
854 - $tmp_thiscss = preg_replace( '#(/\*FILESTART\*/.*)' . preg_quote( $import, '#' ) . '#Us', '/*FILESTART2*/' . $code . '$1', $thiscss );
880 + $tmp_thiscss = str_replace( $import, stripcslashes( $code ), $thiscss );
855 881 if ( ! empty( $tmp_thiscss ) ) {
856 882 $thiscss = $tmp_thiscss;
857 883 $import_ok = true;
858 884 unset( $tmp_thiscss );
@@ -976,10 +1002,20 @@
976 1002 if ( apply_filters( 'autoptimize_filter_cssjs_addtype', false ) ) {
977 1003 $type_css = 'type="text/css" ';
978 1004 }
979 1005
980 - // Inject the new stylesheets.
981 - $replace_tag = array( '<title', 'before' );
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 + }
982 1018 $replace_tag = apply_filters( 'autoptimize_filter_css_replacetag', $replace_tag, $this->content );
983 1019
984 1020 if ( $this->inline ) {
985 1021 foreach ( $this->csscode as $media => $code ) {
@@ -1019,19 +1055,19 @@
1019 1055 foreach ( $this->url as $media => $url ) {
1020 1056 $url = $this->url_replace_cdn( $url );
1021 1057
1022 1058 // Add the stylesheet either deferred (import at bottom) or normal links in head.
1023 - if ( $this->defer ) {
1024 - $preload_onload = autoptimizeConfig::get_ao_css_preload_onload();
1059 + if ( $this->defer && 'print' !== $media ) {
1060 + $preload_onload = autoptimizeConfig::get_ao_css_preload_onload( $media );
1025 1061
1026 - $preload_css_block .= '<link rel="stylesheet" media="print" href="' . $url . '" onload="' . $preload_onload . '" />';
1027 - if ( apply_filters( 'autoptimize_fitler_css_preload_and_print', false ) ) {
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 ) ) {
1028 1064 $preload_css_block = '<link rel="preload" as="stylesheet" href="' . $url . '"/>' . $preload_css_block;
1029 1065 }
1030 - $noscript_css_block .= '<link ' . $type_css . 'media="' . $media . '" href="' . $url . '" rel="stylesheet" />';
1066 + $noscript_css_block .= '<link ' . $type_css . 'media="' . $media . '" href="' . $url . '" rel="stylesheet">';
1031 1067 } else {
1032 1068 if ( strlen( $this->csscode[ $media ] ) > $this->cssinlinesize ) {
1033 - $this->inject_in_html( apply_filters( 'autoptimize_filter_css_bodyreplacementpayload', '<link ' . $type_css . 'media="' . $media . '" href="' . $url . '" rel="stylesheet" />' ), $replace_tag );
1069 + $this->inject_in_html( apply_filters( 'autoptimize_filter_css_bodyreplacementpayload', '<link ' . $type_css . 'media="' . $media . '" href="' . $url . '" rel="stylesheet">' ), $replace_tag );
1034 1070 } elseif ( strlen( $this->csscode[ $media ] ) > 0 ) {
1035 1071 $this->inject_in_html( apply_filters( 'autoptimize_filter_css_bodyreplacementpayload', '<style ' . $type_css . 'media="' . $media . '">' . $this->csscode[ $media ] . '</style>' ), $replace_tag );
1036 1072 }
1037 1073 }
@@ -1203,8 +1239,15 @@
1203 1239 {
1204 1240 $contents = $this->prepare_minify_single( $filepath );
1205 1241
1206 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 + }
1207 1250 return false;
1208 1251 }
1209 1252
1210 1253 // Check cache.
@@ -1269,6 +1312,26 @@
1269 1312
1270 1313 public function getOption( $name )
1271 1314 {
1272 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;
1273 1336 }
1274 1337 }