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/autoptimizeScripts.php +117 -24 2.7.3 → trunk View file →
@@ -57,9 +57,14 @@
57 57 '_stq',
58 58 'nonce',
59 59 'post_id',
60 60 'data-noptimize',
61 + 'data-cfasync',
62 + 'data-pagespeed-no-defer',
61 63 'logHuman',
64 + 'amp-mobile-version-switcher',
65 + 'data-rocketlazyloadscript',
66 + 'rocket-browser-checker-js-after',
62 67 );
63 68
64 69 /**
65 70 * List of to be moved JS.
@@ -108,8 +113,22 @@
108 113 */
109 114 private $aggregate = true;
110 115
111 116 /**
117 + * Setting; if not aggregated, should we defer?
118 + *
119 + * @var bool
120 + */
121 + private $defer_not_aggregate = false;
122 +
123 + /**
124 + * Setting; defer inline JS?
125 + *
126 + * @var bool
127 + */
128 + private $defer_inline = false;
129 +
130 + /**
112 131 * Setting; try/catch wrapping or not.
113 132 *
114 133 * @var bool
115 134 */
@@ -164,13 +183,13 @@
164 183 */
165 184 private $md5hash = '';
166 185
167 186 /**
168 - * Setting (filter); whitelist of to be aggregated JS.
187 + * Setting (filter); allowlist of to be aggregated JS.
169 188 *
170 189 * @var string
171 190 */
172 - private $whitelist = '';
191 + private $allowlist = '';
173 192
174 193 /**
175 194 * Setting (filter); holds JS that should be removed.
176 195 *
@@ -199,17 +218,27 @@
199 218 * @param array $options all options.
200 219 */
201 220 public function read( $options )
202 221 {
203 - $noptimize_js = apply_filters( 'autoptimize_filter_js_noptimize', false, $this->content );
222 + $noptimize_js = false;
223 +
224 + // If page/ post check post_meta to see if optimize is off.
225 + if ( false === autoptimizeConfig::get_post_meta_ao_settings( 'ao_post_js_optimize' ) ) {
226 + $noptimize_js = true;
227 + }
228 +
229 + // And a filter to enforce JS noptimize.
230 + $noptimize_js = apply_filters( 'autoptimize_filter_js_noptimize', $noptimize_js, $this->content );
231 +
232 + // And finally bail if noptimize_js is true.
204 233 if ( $noptimize_js ) {
205 234 return false;
206 235 }
207 236
208 237 // only optimize known good JS?
209 - $whitelist_js = apply_filters( 'autoptimize_filter_js_whitelist', '', $this->content );
210 - if ( ! empty( $whitelist_js ) ) {
211 - $this->whitelist = array_filter( array_map( 'trim', explode( ',', $whitelist_js ) ) );
238 + $allowlist_js = apply_filters( 'autoptimize_filter_js_allowlist', '', $this->content );
239 + if ( ! empty( $allowlist_js ) ) {
240 + $this->allowlist = array_filter( array_map( 'trim', explode( ',', $allowlist_js ) ) );
212 241 }
213 242
214 243 // is there JS we should simply remove?
215 244 $removable_js = apply_filters( 'autoptimize_filter_js_removables', '', $this->content );
@@ -231,9 +260,21 @@
231 260 // Returning true for "dontaggregate" turns off aggregation.
232 261 if ( $this->aggregate && apply_filters( 'autoptimize_filter_js_dontaggregate', false ) ) {
233 262 $this->aggregate = false;
234 263 }
264 + // and the filter that should have been there to begin with.
265 + $this->aggregate = apply_filters( 'autoptimize_filter_js_aggregate', $this->aggregate );
235 266
267 + // Defer when not aggregating.
268 + if ( false === $this->aggregate && apply_filters( 'autoptimize_filter_js_defer_not_aggregate', $options['defer_not_aggregate'] ) ) {
269 + $this->defer_not_aggregate = true;
270 + }
271 +
272 + // Defer inline JS?
273 + if ( ( true === $this->defer_not_aggregate && apply_filters( 'autoptimize_js_filter_defer_inline', $options['defer_inline'] ) ) || apply_filters( 'autoptimize_js_filter_force_defer_inline', false ) ) {
274 + $this->defer_inline = true;
275 + }
276 +
236 277 // include inline?
237 278 if ( apply_filters( 'autoptimize_js_include_inline', $options['include_inline'] ) ) {
238 279 $this->include_inline = true;
239 280 }
@@ -300,9 +341,9 @@
300 341
301 342 // Get script files.
302 343 if ( preg_match_all( '#<script.*</script>#Usmi', $this->content, $matches ) ) {
303 344 foreach ( $matches[0] as $tag ) {
304 - // only consider script aggregation for types whitelisted in should_aggregate-function.
345 + // only consider script aggregation for types allowlisted in should_aggregate-function.
305 346 $should_aggregate = $this->should_aggregate( $tag );
306 347 if ( ! $should_aggregate ) {
307 348 $tag = '';
308 349 continue;
@@ -334,8 +375,25 @@
334 375 }
335 376 }
336 377 }
337 378
379 + // not aggregating but deferring?
380 + if ( $this->defer_not_aggregate && false === $this->aggregate && ( str_replace( $this->dontmove, '', $path ) === $path || ( apply_filters( 'autoptimize_filter_js_defer_external', true ) && str_replace( $this->dontmove, '', $orig_tag ) === $orig_tag ) ) && strpos( $new_tag, ' defer' ) === false ) {
381 + if ( false !== strpos( $new_tag, ' async' ) && true === apply_filters( 'autoptimize_filter_js_defer_trumps_async', true ) ) {
382 + // remove async flag to ensure JS is properly deferred, otherwise the asynced JS might fire
383 + // before deferred inlined JS is executed, off course except filter is set to false which
384 + // re-institutes previous behavior.
385 + $new_tag = str_replace( array( " async='async'", ' async="async"', ' async=async', ' async' ), '', $new_tag );
386 + }
387 +
388 + if ( false === strpos( $new_tag, ' async' ) ) {
389 + // either async wasn't there to begin with or it was removed.
390 + // if async is there, the autoptimize_filter_js_defer_trumps_async
391 + // filter was set to false and in that case defer should not be added.
392 + $new_tag = str_replace( '<script ', '<script defer ', $new_tag );
393 + }
394 + }
395 +
338 396 // Should we minify the non-aggregated script?
339 397 // -> if aggregate is on and exclude minify is on
340 398 // -> if aggregate is off and the file is not in dontmove.
341 399 if ( $path && $this->minify_excluded ) {
@@ -352,8 +410,13 @@
352 410 }
353 411 }
354 412 }
355 413
414 + // Check if we still need to CDN (esp. for already minified resources).
415 + if ( ! empty( $this->cdn_url ) || has_filter( 'autoptimize_filter_base_replace_cdn' ) ) {
416 + $new_tag = str_replace( $url, $this->url_replace_cdn( $url ), $new_tag );
417 + }
418 +
356 419 if ( $this->ismovable( $new_tag ) ) {
357 420 // can be moved, flags and all.
358 421 if ( $this->movetolast( $new_tag ) ) {
359 422 $this->move['last'][] = $new_tag;
@@ -384,18 +447,46 @@
384 447 $code = preg_replace( '#.*<!\[CDATA\[(?:\s*\*/)?(.*)(?://|/\*)\s*?\]\]>.*#sm', '$1', $code[1] );
385 448 $code = preg_replace( '/(?:^\\s*<!--\\s*|\\s*(?:\\/\\/)?\\s*-->\\s*$)/', '', $code );
386 449 $this->scripts[] = 'INLINE;' . $code;
387 450 } else {
388 - // Can we move this?
389 - $autoptimize_js_moveable = apply_filters( 'autoptimize_js_moveable', '', $tag );
390 - if ( $this->ismovable( $tag ) || '' !== $autoptimize_js_moveable ) {
391 - if ( $this->movetolast( $tag ) || 'last' === $autoptimize_js_moveable ) {
392 - $this->move['last'][] = $tag;
451 + $_inline_deferable = apply_filters( 'autoptimize_filters_js_inline_deferable', array( 'nonce', 'post_id', 'syntaxhighlighter' ) );
452 + $_inline_dontmove = array_values( array_diff( $this->dontmove, $_inline_deferable ) );
453 + if ( false === $this->defer_inline ) {
454 + // Can we move this?
455 + $autoptimize_js_moveable = apply_filters( 'autoptimize_js_moveable', '', $tag );
456 + if ( $this->ismovable( $tag ) || '' !== $autoptimize_js_moveable ) {
457 + if ( $this->movetolast( $tag ) || 'last' === $autoptimize_js_moveable ) {
458 + $this->move['last'][] = $tag;
459 + } else {
460 + $this->move['first'][] = $tag;
461 + }
393 462 } else {
394 - $this->move['first'][] = $tag;
463 + $tag = '';
395 464 }
465 + } else if ( str_replace( $_inline_dontmove, '', $tag ) === $tag && strlen( $tag ) < apply_filters( 'autoptimize_filter_script_defer_inline_maxsize', 200000 ) ) {
466 + // defer inline JS by base64 encoding it but only if string is not ridiculously huge (to avoid issues with below regex mainly).
467 + preg_match( '#<script(?:[^>](?!id=))*\s*(?:id=(["\'])([^"\']+)\1)*+[^>]*+>(.*?)<\/script>#is', $tag, $match );
468 + if ( $match[2] ) {
469 + $_id = 'id="' . $match[2] . '" ';
470 + } else {
471 + $_id = '';
472 + }
473 +
474 + // if "minify inline" is on and if more then 9 spaces or 4 line breaks are found
475 + // in the inline JS then it is likely not minified, so minify before base64-encoding.
476 + $_script_contents = $match[3];
477 + if ( 'on' === autoptimizeOptionWrapper::get_option( 'autoptimize_html_minify_inline', 'off' ) && substr_count( $_script_contents, ' ' ) > 9 && substr_count( $_script_contents, "\n" ) > 4 && true === apply_filters( 'autoptimize_filter_script_defer_inline_minify', true ) ) {
478 + $_tmp_script_contents = trim( JSMin::minify( $_script_contents ) );
479 + if ( ! empty( $_tmp_script_contents ) ) {
480 + $_script_contents = $_tmp_script_contents;
481 + }
482 + }
483 +
484 + // base64 and defer the lot already.
485 + $new_tag = '<script defer ' . $_id . 'src="data:text/javascript;base64,' . base64_encode( $_script_contents ) . '"></script>';
486 + $this->content = str_replace( $this->hide_comments( $tag ), $new_tag, $this->content );
487 + $tag = '';
396 488 } else {
397 - // We shouldn't touch this.
398 489 $tag = '';
399 490 }
400 491 }
401 492 // Re-hide comments to be able to do the removal based on tag from $this->content.
@@ -427,9 +518,9 @@
427 518 *
428 519 * @param string $tag Script node & child(ren).
429 520 * @return bool
430 521 */
431 - public function should_aggregate( $tag )
522 + public static function should_aggregate( $tag )
432 523 {
433 524 if ( empty( $tag ) ) {
434 525 return false;
435 526 }
@@ -442,9 +533,9 @@
442 533 if ( ! empty( $tag_parts[1] ) ) {
443 534 $tag_without_contents = $tag_parts[1];
444 535 }
445 536
446 - $has_type = ( strpos( $tag_without_contents, 'type' ) !== false );
537 + $has_type = ( strpos( $tag_without_contents, ' type' ) !== false );
447 538
448 539 $type_valid = false;
449 540 if ( $has_type ) {
450 541 $type_valid = (bool) preg_match( '/type\s*=\s*[\'"]?(?:text|application)\/(?:javascript|ecmascript)[\'"]?/i', $tag_without_contents );
@@ -601,9 +692,9 @@
601 692 return $this->content;
602 693 }
603 694
604 695 /**
605 - * Checks against the white- and blacklists.
696 + * Checks against the allow- and blocklists.
606 697 *
607 698 * @param string $tag JS tag.
608 699 */
609 700 private function ismergeable( $tag )
@@ -611,15 +702,15 @@
611 702 if ( empty( $tag ) || ! $this->aggregate ) {
612 703 return false;
613 704 }
614 705
615 - if ( ! empty( $this->whitelist ) ) {
616 - foreach ( $this->whitelist as $match ) {
706 + if ( ! empty( $this->allowlist ) ) {
707 + foreach ( $this->allowlist as $match ) {
617 708 if ( false !== strpos( $tag, $match ) ) {
618 709 return true;
619 710 }
620 711 }
621 - // No match with whitelist.
712 + // No match with allowlist.
622 713 return false;
623 714 } else {
624 715 foreach ( $this->domove as $match ) {
625 716 if ( false !== strpos( $tag, $match ) ) {
@@ -644,11 +735,11 @@
644 735 }
645 736 }
646 737
647 738 /**
648 - * Checks agains the blacklist.
739 + * Checks agains the blocklist.
649 740 *
650 - * @param string $tag tag to check for blacklist (exclusions).
741 + * @param string $tag tag to check for blocklist (exclusions).
651 742 */
652 743 private function ismovable( $tag )
653 744 {
654 745 if ( empty( $tag ) || true !== $this->include_inline || apply_filters( 'autoptimize_filter_js_unmovable', true ) ) {
@@ -697,9 +788,8 @@
697 788 /**
698 789 * Determines wheter a <script> $tag can be excluded from minification (as already minified) based on:
699 790 * - inject_min_late being active
700 791 * - filename ending in `min.js`
701 - * - filename matching `js/jquery/jquery.js` (WordPress core jquery, is minified)
702 792 * - filename matching one passed in the consider minified filter
703 793 *
704 794 * @param string $js_path Path to JS file.
705 795 * @return bool
@@ -708,9 +798,9 @@
708 798 $consider_minified_array = apply_filters( 'autoptimize_filter_js_consider_minified', false );
709 799 if ( true !== $this->inject_min_late ) {
710 800 // late-inject turned off.
711 801 return false;
712 - } elseif ( ( false === strpos( $js_path, 'min.js' ) ) && ( false === strpos( $js_path, 'wp-includes/js/jquery/jquery.js' ) ) && ( str_replace( $consider_minified_array, '', $js_path ) === $js_path ) ) {
802 + } elseif ( ( false === strpos( $js_path, 'min.js' ) ) && ( str_replace( $consider_minified_array, '', $js_path ) === $js_path ) ) {
713 803 // file not minified based on filename & filter.
714 804 return false;
715 805 } else {
716 806 // phew, all is safe, we can late-inject.
@@ -755,8 +845,11 @@
755 845 // Check if minified cache content is empty.
756 846 if ( empty( $contents ) ) {
757 847 return false;
758 848 }
849 +
850 + // Filter contents of excluded minified CSS.
851 + $contents = apply_filters( 'autoptimize_filter_js_single_after_minify', $contents );
759 852
760 853 // Store in cache.
761 854 $cache->cache( $contents, 'text/javascript' );
762 855 }