PluginProbe
Autoptimize / 3.1.4
Autoptimize v3.1.4
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 2.7.8 All 107 releases
← All changes | classes/autoptimizeStyles.php +119 -52 2.7.33.1.4 View file →
@@ -93,13 +93,13 @@
93 93 */
94 94 private $defer_inline = '';
95 95
96 96 /**
97 - * Setting for whitelist of what should be aggregated.
97 + * Setting for allowlist of what should be aggregated.
98 98 *
99 99 * @var string
100 100 */
101 - private $whitelist = '';
101 + private $allowlist = '';
102 102
103 103 /**
104 104 * Setting (only filter) for size under which CSS should be inlined instead of linked.
105 105 *
@@ -163,15 +163,15 @@
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 - $whitelist_css = apply_filters( 'autoptimize_filter_css_whitelist', '', $this->content );
172 - if ( ! empty( $whitelist_css ) ) {
173 - $this->whitelist = array_filter( array_map( 'trim', explode( ',', $whitelist_css ) ) );
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 ) ) );
174 174 }
175 175
176 176 $removable_css = apply_filters( 'autoptimize_filter_css_removables', '' );
177 177 if ( ! empty( $removable_css ) ) {
@@ -197,8 +197,10 @@
197 197 // Returning true for "dontaggregate" turns off aggregation.
198 198 if ( $this->aggregate && apply_filters( 'autoptimize_filter_css_dontaggregate', false ) ) {
199 199 $this->aggregate = false;
200 200 }
201 + // and the filter that should have been there to begin with.
202 + $this->aggregate = apply_filters( 'autoptimize_filter_css_aggregate', $this->aggregate );
201 203
202 204 // include inline?
203 205 if ( apply_filters( 'autoptimize_css_include_inline', $options['include_inline'] ) ) {
204 206 $this->include_inline = true;
@@ -214,16 +216,24 @@
214 216
215 217 // forcefully exclude CSS with data-noptimize attrib.
216 218 $this->dontmove[] = 'data-noptimize';
217 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 +
218 223 // Should we defer css?
219 224 // value: true / false.
220 225 $this->defer = $options['defer'];
221 226 $this->defer = apply_filters( 'autoptimize_filter_css_defer', $this->defer, $this->content );
222 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 +
223 233 // Should we inline while deferring?
224 234 // value: inlined CSS.
225 - $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 );
226 236
227 237 // Should we inline?
228 238 // value: true / false.
229 239 $this->inline = $options['inline'];
@@ -270,16 +280,20 @@
270 280 } elseif ( $this->ismovable( $tag ) ) {
271 281 // Get the media.
272 282 if ( false !== strpos( $tag, 'media=' ) ) {
273 283 preg_match( '#media=(?:"|\')([^>]*)(?:"|\')#Ui', $tag, $medias );
274 - $medias = explode( ',', $medias[1] );
275 - $media = array();
276 - foreach ( $medias as $elem ) {
277 - if ( empty( $elem ) ) {
278 - $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;
279 293 }
280 -
281 - $media[] = $elem;
294 + } else {
295 + $media = array( 'all' );
282 296 }
283 297 } else {
284 298 // No media specified - applies to all.
285 299 $media = array( 'all' );
@@ -353,8 +367,13 @@
353 367
354 368 if ( '' !== $new_tag ) {
355 369 // Optionally defer (preload) non-aggregated CSS.
356 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 + }
357 376 }
358 377
359 378 // And replace!
360 379 if ( ( '' !== $new_tag && $new_tag !== $tag ) || ( '' === $new_tag && apply_filters( 'autoptimize_filter_css_remove_empty_files', false ) ) ) {
@@ -382,25 +401,33 @@
382 401 private function optionally_defer_excluded( $tag, $url = '' )
383 402 {
384 403 // Defer single CSS if "inline & defer" is ON and there is inline CSS.
385 404 if ( ! empty( $tag ) && false === strpos( $tag, ' onload=' ) && $this->defer && ! empty( $this->defer_inline ) && apply_filters( 'autoptimize_filter_css_defer_excluded', true, $tag ) ) {
386 - // Get/ set (via filter) the JS to be triggers onload of the preloaded CSS.
387 - $_preload_onload = apply_filters(
388 - 'autoptimize_filter_css_preload_onload',
389 - "this.onload=null;this.rel='stylesheet'",
390 - $url
391 - );
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 + }
392 409
393 - // Adapt original <link> element for CSS to be preloaded and add <noscript>-version for fallback.
394 - $new_tag = '<noscript>' . autoptimizeUtils::remove_id_from_node( $tag ) . '</noscript>' . str_replace(
395 - array(
396 - "rel='stylesheet'",
397 - 'rel="stylesheet"',
398 - ),
399 - "rel='preload' as='style' onload=\"" . $_preload_onload . '"',
400 - $tag
401 - );
410 + preg_match( '#media=(?:"|\')([^>]*)(?:"|\')#Ui', $tag, $_medias );
411 + $_media = $_medias[1];
412 + $_preload_onload = autoptimizeConfig::get_ao_css_preload_onload( $_media );
402 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 +
403 430 return $new_tag;
404 431 }
405 432
406 433 // Return unchanged $tag.
@@ -463,9 +490,9 @@
463 490
464 491 private function check_datauri_exclude_list( $url )
465 492 {
466 493 static $exclude_list = null;
467 - $no_datauris = array();
494 + static $no_datauris = array();
468 495
469 496 // Again, skip doing certain stuff repeatedly when loop-called.
470 497 if ( null === $exclude_list ) {
471 498 $exclude_list = apply_filters( 'autoptimize_filter_css_datauri_exclude', '' );
@@ -611,9 +638,11 @@
611 638 if ( ! empty( $this->cdn_url ) ) {
612 639 $replacement_url = $this->url_replace_cdn( $url );
613 640 // Prepare replacements array.
614 641 $replacements[ $url_src_matches[1][ $count ] ] = str_replace(
615 - $original_url, $replacement_url, $url_src_matches[1][ $count ]
642 + $original_url,
643 + $replacement_url,
644 + $url_src_matches[1][ $count ]
616 645 );
617 646 }
618 647 }
619 648 }
@@ -737,9 +766,11 @@
737 766 if ( ! $inlined && ( ! empty( $this->cdn_url ) || has_filter( 'autoptimize_filter_base_replace_cdn' ) ) ) {
738 767 // Just do the "simple" CDN replacement.
739 768 $replacement_url = $this->url_replace_cdn( $url );
740 769 $imgreplace[ $url_src_matches[1][ $count ] ] = str_replace(
741 - $original_url, $replacement_url, $url_src_matches[1][ $count ]
770 + $original_url,
771 + $replacement_url,
772 + $url_src_matches[1][ $count ]
742 773 );
743 774 }
744 775 }
745 776 }
@@ -845,9 +876,9 @@
845 876 $code = self::build_injectlater_marker( $path, md5( $code ) );
846 877 }
847 878
848 879 if ( ! empty( $code ) ) {
849 - $tmp_thiscss = preg_replace( '#(/\*FILESTART\*/.*)' . preg_quote( $import, '#' ) . '#Us', '/*FILESTART2*/' . $code . '$1', $thiscss );
880 + $tmp_thiscss = str_replace( $import, stripcslashes( $code ), $thiscss );
850 881 if ( ! empty( $tmp_thiscss ) ) {
851 882 $thiscss = $tmp_thiscss;
852 883 $import_ok = true;
853 884 unset( $tmp_thiscss );
@@ -971,15 +1002,25 @@
971 1002 if ( apply_filters( 'autoptimize_filter_cssjs_addtype', false ) ) {
972 1003 $type_css = 'type="text/css" ';
973 1004 }
974 1005
975 - // Inject the new stylesheets.
976 - $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 + }
977 1018 $replace_tag = apply_filters( 'autoptimize_filter_css_replacetag', $replace_tag, $this->content );
978 1019
979 1020 if ( $this->inline ) {
980 1021 foreach ( $this->csscode as $media => $code ) {
981 - $this->inject_in_html( '<style ' . $type_css . 'media="' . $media . '">' . $code . '</style>', $replace_tag );
1022 + $this->inject_in_html( apply_filters( 'autoptimize_filter_css_bodyreplacementpayload', '<style ' . $type_css . 'media="' . $media . '">' . $code . '</style>' ), $replace_tag );
982 1023 }
983 1024 } else {
984 1025 if ( $this->defer ) {
985 1026 $preload_css_block = '';
@@ -1014,33 +1055,29 @@
1014 1055 foreach ( $this->url as $media => $url ) {
1015 1056 $url = $this->url_replace_cdn( $url );
1016 1057
1017 1058 // Add the stylesheet either deferred (import at bottom) or normal links in head.
1018 - if ( $this->defer ) {
1019 - $preload_onload = autoptimizeConfig::get_ao_css_preload_onload();
1059 + if ( $this->defer && 'print' !== $media ) {
1060 + $preload_onload = autoptimizeConfig::get_ao_css_preload_onload( $media );
1020 1061
1021 - $preload_css_block .= '<link rel="preload" as="style" media="' . $media . '" href="' . $url . '" onload="' . $preload_onload . '" />';
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_fitler_css_preload_and_print', false ) ) {
1064 + $preload_css_block = '<link rel="preload" as="stylesheet" href="' . $url . '"/>' . $preload_css_block;
1065 + }
1022 1066 $noscript_css_block .= '<link ' . $type_css . 'media="' . $media . '" href="' . $url . '" rel="stylesheet" />';
1023 1067 } else {
1024 1068 if ( strlen( $this->csscode[ $media ] ) > $this->cssinlinesize ) {
1025 - $this->inject_in_html( '<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 );
1026 1070 } elseif ( strlen( $this->csscode[ $media ] ) > 0 ) {
1027 - $this->inject_in_html( '<style ' . $type_css . 'media="' . $media . '">' . $this->csscode[ $media ] . '</style>', $replace_tag );
1071 + $this->inject_in_html( apply_filters( 'autoptimize_filter_css_bodyreplacementpayload', '<style ' . $type_css . 'media="' . $media . '">' . $this->csscode[ $media ] . '</style>' ), $replace_tag );
1028 1072 }
1029 1073 }
1030 1074 }
1031 1075
1032 1076 if ( $this->defer ) {
1033 - $preload_polyfill = autoptimizeConfig::get_ao_css_preload_polyfill();
1034 1077 $noscript_css_block .= '</noscript>';
1035 1078 // Inject inline critical CSS, the preloaded full CSS and the noscript-CSS.
1036 - $this->inject_in_html( $inlined_ccss_block . $preload_css_block . $noscript_css_block, $replace_tag );
1037 -
1038 - // Adds preload polyfill at end of body tag.
1039 - $this->inject_in_html(
1040 - apply_filters( 'autoptimize_css_preload_polyfill', $preload_polyfill ),
1041 - apply_filters( 'autoptimize_css_preload_polyfill_injectat', array( '</body>', 'before' ) )
1042 - );
1079 + $this->inject_in_html( apply_filters( 'autoptimize_filter_css_bodyreplacementpayload', $inlined_ccss_block . $preload_css_block . $noscript_css_block ), $replace_tag );
1043 1080 }
1044 1081 }
1045 1082
1046 1083 // restore comments.
@@ -1141,15 +1178,15 @@
1141 1178 if ( ! $this->aggregate ) {
1142 1179 return false;
1143 1180 }
1144 1181
1145 - if ( ! empty( $this->whitelist ) ) {
1146 - foreach ( $this->whitelist as $match ) {
1182 + if ( ! empty( $this->allowlist ) ) {
1183 + foreach ( $this->allowlist as $match ) {
1147 1184 if ( false !== strpos( $tag, $match ) ) {
1148 1185 return true;
1149 1186 }
1150 1187 }
1151 - // no match with whitelist.
1188 + // no match with allowlist.
1152 1189 return false;
1153 1190 } else {
1154 1191 if ( is_array( $this->dontmove ) && ! empty( $this->dontmove ) ) {
1155 1192 foreach ( $this->dontmove as $match ) {
@@ -1202,8 +1239,15 @@
1202 1239 {
1203 1240 $contents = $this->prepare_minify_single( $filepath );
1204 1241
1205 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 + }
1206 1250 return false;
1207 1251 }
1208 1252
1209 1253 // Check cache.
@@ -1227,8 +1271,11 @@
1227 1271 if ( empty( $contents ) ) {
1228 1272 return false;
1229 1273 }
1230 1274
1275 + // Filter contents of excluded minified CSS.
1276 + $contents = apply_filters( 'autoptimize_filter_css_single_after_minify', $contents );
1277 +
1231 1278 // Store in cache.
1232 1279 $cache->cache( $contents, 'text/css' );
1233 1280 }
1234 1281
@@ -1265,6 +1312,26 @@
1265 1312
1266 1313 public function getOption( $name )
1267 1314 {
1268 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;
1269 1336 }
1270 1337 }