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 +99 -36 2.7.4 → trunk 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 }