| @@ -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 ) ) ) { |
| @@ -472,9 +490,9 @@ | ||
| 472 | 490 | |
| 473 | 491 | private function check_datauri_exclude_list( $url ) |
| 474 | 492 | { |
| 475 | 493 | static $exclude_list = null; |
| 476 | - $no_datauris = array(); | |
| 494 | + static $no_datauris = array(); | |
| 477 | 495 | |
| 478 | 496 | // Again, skip doing certain stuff repeatedly when loop-called. |
| 479 | 497 | if ( null === $exclude_list ) { |
| 480 | 498 | $exclude_list = apply_filters( 'autoptimize_filter_css_datauri_exclude', '' ); |
| @@ -620,9 +638,11 @@ | ||
| 620 | 638 | if ( ! empty( $this->cdn_url ) ) { |
| 621 | 639 | $replacement_url = $this->url_replace_cdn( $url ); |
| 622 | 640 | // Prepare replacements array. |
| 623 | 641 | $replacements[ $url_src_matches[1][ $count ] ] = str_replace( |
| 624 | - $original_url, $replacement_url, $url_src_matches[1][ $count ] | |
| 642 | + $original_url, | |
| 643 | + $replacement_url, | |
| 644 | + $url_src_matches[1][ $count ] | |
| 625 | 645 | ); |
| 626 | 646 | } |
| 627 | 647 | } |
| 628 | 648 | } |
| @@ -746,9 +766,11 @@ | ||
| 746 | 766 | if ( ! $inlined && ( ! empty( $this->cdn_url ) || has_filter( 'autoptimize_filter_base_replace_cdn' ) ) ) { |
| 747 | 767 | // Just do the "simple" CDN replacement. |
| 748 | 768 | $replacement_url = $this->url_replace_cdn( $url ); |
| 749 | 769 | $imgreplace[ $url_src_matches[1][ $count ] ] = str_replace( |
| 750 | - $original_url, $replacement_url, $url_src_matches[1][ $count ] | |
| 770 | + $original_url, | |
| 771 | + $replacement_url, | |
| 772 | + $url_src_matches[1][ $count ] | |
| 751 | 773 | ); |
| 752 | 774 | } |
| 753 | 775 | } |
| 754 | 776 | } |
| @@ -854,9 +876,9 @@ | ||
| 854 | 876 | $code = self::build_injectlater_marker( $path, md5( $code ) ); |
| 855 | 877 | } |
| 856 | 878 | |
| 857 | 879 | if ( ! empty( $code ) ) { |
| 858 | - $tmp_thiscss = preg_replace( '#(/\*FILESTART\*/.*)' . preg_quote( $import, '#' ) . '#Us', '/*FILESTART2*/' . $code . '$1', $thiscss ); | |
| 880 | + $tmp_thiscss = str_replace( $import, stripcslashes( $code ), $thiscss ); | |
| 859 | 881 | if ( ! empty( $tmp_thiscss ) ) { |
| 860 | 882 | $thiscss = $tmp_thiscss; |
| 861 | 883 | $import_ok = true; |
| 862 | 884 | unset( $tmp_thiscss ); |
| @@ -980,10 +1002,20 @@ | ||
| 980 | 1002 | if ( apply_filters( 'autoptimize_filter_cssjs_addtype', false ) ) { |
| 981 | 1003 | $type_css = 'type="text/css" '; |
| 982 | 1004 | } |
| 983 | 1005 | |
| 984 | - // Inject the new stylesheets. | |
| 985 | - $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 | + } | |
| 986 | 1018 | $replace_tag = apply_filters( 'autoptimize_filter_css_replacetag', $replace_tag, $this->content ); |
| 987 | 1019 | |
| 988 | 1020 | if ( $this->inline ) { |
| 989 | 1021 | foreach ( $this->csscode as $media => $code ) { |
| @@ -1026,16 +1058,16 @@ | ||
| 1026 | 1058 | // Add the stylesheet either deferred (import at bottom) or normal links in head. |
| 1027 | 1059 | if ( $this->defer && 'print' !== $media ) { |
| 1028 | 1060 | $preload_onload = autoptimizeConfig::get_ao_css_preload_onload( $media ); |
| 1029 | 1061 | |
| 1030 | - $preload_css_block .= '<link rel="stylesheet" media="print" href="' . $url . '" onload="' . $preload_onload . '" />'; | |
| 1031 | - 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 ) ) { | |
| 1032 | 1064 | $preload_css_block = '<link rel="preload" as="stylesheet" href="' . $url . '"/>' . $preload_css_block; |
| 1033 | 1065 | } |
| 1034 | - $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">'; | |
| 1035 | 1067 | } else { |
| 1036 | 1068 | if ( strlen( $this->csscode[ $media ] ) > $this->cssinlinesize ) { |
| 1037 | - $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 ); | |
| 1038 | 1070 | } elseif ( strlen( $this->csscode[ $media ] ) > 0 ) { |
| 1039 | 1071 | $this->inject_in_html( apply_filters( 'autoptimize_filter_css_bodyreplacementpayload', '<style ' . $type_css . 'media="' . $media . '">' . $this->csscode[ $media ] . '</style>' ), $replace_tag ); |
| 1040 | 1072 | } |
| 1041 | 1073 | } |
| @@ -1207,8 +1239,15 @@ | ||
| 1207 | 1239 | { |
| 1208 | 1240 | $contents = $this->prepare_minify_single( $filepath ); |
| 1209 | 1241 | |
| 1210 | 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 | + } | |
| 1211 | 1250 | return false; |
| 1212 | 1251 | } |
| 1213 | 1252 | |
| 1214 | 1253 | // Check cache. |
| @@ -1273,6 +1312,26 @@ | ||
| 1273 | 1312 | |
| 1274 | 1313 | public function getOption( $name ) |
| 1275 | 1314 | { |
| 1276 | 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; | |
| 1277 | 1336 | } |
| 1278 | 1337 | } |