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 +123 -55 2.7.2 → trunk 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' );
@@ -342,10 +356,11 @@
342 356 $minified_url = $this->minify_single( $path );
343 357 if ( ! empty( $minified_url ) ) {
344 358 // Replace orig URL with cached minified URL.
345 359 $new_tag = str_replace( $url, $minified_url, $tag );
346 - } else {
347 - // Remove the original style tag, because cache content is empty.
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.
348 363 $new_tag = '';
349 364 }
350 365 }
351 366 }
@@ -352,8 +367,13 @@
352 367
353 368 if ( '' !== $new_tag ) {
354 369 // Optionally defer (preload) non-aggregated CSS.
355 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 + }
356 376 }
357 377
358 378 // And replace!
359 379 if ( ( '' !== $new_tag && $new_tag !== $tag ) || ( '' === $new_tag && apply_filters( 'autoptimize_filter_css_remove_empty_files', false ) ) ) {
@@ -381,25 +401,33 @@
381 401 private function optionally_defer_excluded( $tag, $url = '' )
382 402 {
383 403 // Defer single CSS if "inline & defer" is ON and there is inline CSS.
384 404 if ( ! empty( $tag ) && false === strpos( $tag, ' onload=' ) && $this->defer && ! empty( $this->defer_inline ) && apply_filters( 'autoptimize_filter_css_defer_excluded', true, $tag ) ) {
385 - // Get/ set (via filter) the JS to be triggers onload of the preloaded CSS.
386 - $_preload_onload = apply_filters(
387 - 'autoptimize_filter_css_preload_onload',
388 - "this.onload=null;this.rel='stylesheet'",
389 - $url
390 - );
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 + }
391 409
392 - // Adapt original <link> element for CSS to be preloaded and add <noscript>-version for fallback.
393 - $new_tag = '<noscript>' . autoptimizeUtils::remove_id_from_node( $tag ) . '</noscript>' . str_replace(
394 - array(
395 - "rel='stylesheet'",
396 - 'rel="stylesheet"',
397 - ),
398 - "rel='preload' as='style' onload=\"" . $_preload_onload . '"',
399 - $tag
400 - );
410 + preg_match( '#media=(?:"|\')([^>]*)(?:"|\')#Ui', $tag, $_medias );
411 + $_media = $_medias[1];
412 + $_preload_onload = autoptimizeConfig::get_ao_css_preload_onload( $_media );
401 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 +
402 430 return $new_tag;
403 431 }
404 432
405 433 // Return unchanged $tag.
@@ -462,9 +490,9 @@
462 490
463 491 private function check_datauri_exclude_list( $url )
464 492 {
465 493 static $exclude_list = null;
466 - $no_datauris = array();
494 + static $no_datauris = array();
467 495
468 496 // Again, skip doing certain stuff repeatedly when loop-called.
469 497 if ( null === $exclude_list ) {
470 498 $exclude_list = apply_filters( 'autoptimize_filter_css_datauri_exclude', '' );
@@ -610,9 +638,11 @@
610 638 if ( ! empty( $this->cdn_url ) ) {
611 639 $replacement_url = $this->url_replace_cdn( $url );
612 640 // Prepare replacements array.
613 641 $replacements[ $url_src_matches[1][ $count ] ] = str_replace(
614 - $original_url, $replacement_url, $url_src_matches[1][ $count ]
642 + $original_url,
643 + $replacement_url,
644 + $url_src_matches[1][ $count ]
615 645 );
616 646 }
617 647 }
618 648 }
@@ -736,9 +766,11 @@
736 766 if ( ! $inlined && ( ! empty( $this->cdn_url ) || has_filter( 'autoptimize_filter_base_replace_cdn' ) ) ) {
737 767 // Just do the "simple" CDN replacement.
738 768 $replacement_url = $this->url_replace_cdn( $url );
739 769 $imgreplace[ $url_src_matches[1][ $count ] ] = str_replace(
740 - $original_url, $replacement_url, $url_src_matches[1][ $count ]
770 + $original_url,
771 + $replacement_url,
772 + $url_src_matches[1][ $count ]
741 773 );
742 774 }
743 775 }
744 776 }
@@ -844,9 +876,9 @@
844 876 $code = self::build_injectlater_marker( $path, md5( $code ) );
845 877 }
846 878
847 879 if ( ! empty( $code ) ) {
848 - $tmp_thiscss = preg_replace( '#(/\*FILESTART\*/.*)' . preg_quote( $import, '#' ) . '#Us', '/*FILESTART2*/' . $code . '$1', $thiscss );
880 + $tmp_thiscss = str_replace( $import, stripcslashes( $code ), $thiscss );
849 881 if ( ! empty( $tmp_thiscss ) ) {
850 882 $thiscss = $tmp_thiscss;
851 883 $import_ok = true;
852 884 unset( $tmp_thiscss );
@@ -970,15 +1002,25 @@
970 1002 if ( apply_filters( 'autoptimize_filter_cssjs_addtype', false ) ) {
971 1003 $type_css = 'type="text/css" ';
972 1004 }
973 1005
974 - // Inject the new stylesheets.
975 - $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 + }
976 1018 $replace_tag = apply_filters( 'autoptimize_filter_css_replacetag', $replace_tag, $this->content );
977 1019
978 1020 if ( $this->inline ) {
979 1021 foreach ( $this->csscode as $media => $code ) {
980 - $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 );
981 1023 }
982 1024 } else {
983 1025 if ( $this->defer ) {
984 1026 $preload_css_block = '';
@@ -1013,33 +1055,29 @@
1013 1055 foreach ( $this->url as $media => $url ) {
1014 1056 $url = $this->url_replace_cdn( $url );
1015 1057
1016 1058 // Add the stylesheet either deferred (import at bottom) or normal links in head.
1017 - if ( $this->defer ) {
1018 - $preload_onload = autoptimizeConfig::get_ao_css_preload_onload();
1059 + if ( $this->defer && 'print' !== $media ) {
1060 + $preload_onload = autoptimizeConfig::get_ao_css_preload_onload( $media );
1019 1061
1020 - $preload_css_block .= '<link rel="preload" as="style" media="' . $media . '" href="' . $url . '" onload="' . $preload_onload . '" />';
1021 - $noscript_css_block .= '<link ' . $type_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">';
1022 1067 } else {
1023 1068 if ( strlen( $this->csscode[ $media ] ) > $this->cssinlinesize ) {
1024 - $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 );
1025 1070 } elseif ( strlen( $this->csscode[ $media ] ) > 0 ) {
1026 - $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 );
1027 1072 }
1028 1073 }
1029 1074 }
1030 1075
1031 1076 if ( $this->defer ) {
1032 - $preload_polyfill = autoptimizeConfig::get_ao_css_preload_polyfill();
1033 1077 $noscript_css_block .= '</noscript>';
1034 1078 // Inject inline critical CSS, the preloaded full CSS and the noscript-CSS.
1035 - $this->inject_in_html( $inlined_ccss_block . $preload_css_block . $noscript_css_block, $replace_tag );
1036 -
1037 - // Adds preload polyfill at end of body tag.
1038 - $this->inject_in_html(
1039 - apply_filters( 'autoptimize_css_preload_polyfill', $preload_polyfill ),
1040 - apply_filters( 'autoptimize_css_preload_polyfill_injectat', array( '</body>', 'before' ) )
1041 - );
1079 + $this->inject_in_html( apply_filters( 'autoptimize_filter_css_bodyreplacementpayload', $inlined_ccss_block . $preload_css_block . $noscript_css_block ), $replace_tag );
1042 1080 }
1043 1081 }
1044 1082
1045 1083 // restore comments.
@@ -1140,15 +1178,15 @@
1140 1178 if ( ! $this->aggregate ) {
1141 1179 return false;
1142 1180 }
1143 1181
1144 - if ( ! empty( $this->whitelist ) ) {
1145 - foreach ( $this->whitelist as $match ) {
1182 + if ( ! empty( $this->allowlist ) ) {
1183 + foreach ( $this->allowlist as $match ) {
1146 1184 if ( false !== strpos( $tag, $match ) ) {
1147 1185 return true;
1148 1186 }
1149 1187 }
1150 - // no match with whitelist.
1188 + // no match with allowlist.
1151 1189 return false;
1152 1190 } else {
1153 1191 if ( is_array( $this->dontmove ) && ! empty( $this->dontmove ) ) {
1154 1192 foreach ( $this->dontmove as $match ) {
@@ -1201,8 +1239,15 @@
1201 1239 {
1202 1240 $contents = $this->prepare_minify_single( $filepath );
1203 1241
1204 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 + }
1205 1250 return false;
1206 1251 }
1207 1252
1208 1253 // Check cache.
@@ -1226,8 +1271,11 @@
1226 1271 if ( empty( $contents ) ) {
1227 1272 return false;
1228 1273 }
1229 1274
1275 + // Filter contents of excluded minified CSS.
1276 + $contents = apply_filters( 'autoptimize_filter_css_single_after_minify', $contents );
1277 +
1230 1278 // Store in cache.
1231 1279 $cache->cache( $contents, 'text/css' );
1232 1280 }
1233 1281
@@ -1264,6 +1312,26 @@
1264 1312
1265 1313 public function getOption( $name )
1266 1314 {
1267 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;
1268 1336 }
1269 1337 }