PluginProbe
Powered Cache – Caching and Optimization for WordPress – Easily Improve PageSpeed & Web Vitals Score / trunk
Powered Cache – Caching and Optimization for WordPress – Easily Improve PageSpeed & Web Vitals Score vtrunk
trunk 1.0 1.0.1 1.1 1.1.1 1.1.2 1.2 1.2.1 1.2.2 1.2.3 1.2.4 1.2.5 1.2.6 1.2.7 1.2.8 2.0 2.0.1 2.0.2 2.0.3 2.0.4 2.1 2.1.1 2.1.2 2.2 2.2.1 All 69 releases
← All changes | includes/classes/FileOptimizer.php +419 -45 2.2trunk View file →
@@ -6,9 +6,12 @@
6 6 */
7 7
8 8 namespace PoweredCache;
9 9
10 +use PoweredCache\Dependencies\voku\helper\HtmlMin;
10 11 use const PoweredCache\Constants\POST_META_DISABLE_CSS_OPTIMIZATION;
12 +use const PoweredCache\Constants\POST_META_DISABLE_JS_DEFER;
13 +use const PoweredCache\Constants\POST_META_DISABLE_JS_DELAY;
11 14 use const PoweredCache\Constants\POST_META_DISABLE_JS_OPTIMIZATION;
12 15 use PoweredCache\Optimizer\CSS;
13 16 use PoweredCache\Optimizer\Helper;
14 17 use PoweredCache\Optimizer\JS;
@@ -62,8 +65,16 @@
62 65 if ( ! isset( $_SERVER['REQUEST_METHOD'] ) || ! in_array( $_SERVER['REQUEST_METHOD'], [ 'GET', 'HEAD' ], true ) ) {
63 66 return true;
64 67 }
65 68
69 + add_action( 'plugins_loaded', [ $this, 'setup_file_optimizer' ] );
70 +
71 + }
72 +
73 + /**
74 + * Setup file optimizer module
75 + */
76 + public function setup_file_optimizer() {
66 77 /**
67 78 * Filters whether apply or not apply file optimizations on wp-admin.
68 79 *
69 80 * @hook powered_cache_fo_dashboard
@@ -86,8 +97,17 @@
86 97 return;
87 98 }
88 99
89 100 /**
101 + * Don't optimize in customizer preview
102 + */
103 + if ( ! empty( $_GET['customize_changeset_uuid'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
104 + \PoweredCache\Utils\log( 'Do not run file optimizer in customizer preview' );
105 +
106 + return;
107 + }
108 +
109 + /**
90 110 * Filters FileOptimizer integration
91 111 *
92 112 * @hook powered_cache_fo_disable
93 113 *
@@ -105,8 +125,12 @@
105 125 add_action( 'init', [ $this, 'setup_css_combine' ] );
106 126 add_action( 'init', [ $this, 'setup_js_combine' ] );
107 127 add_filter( 'script_loader_tag', [ $this, 'js_minify' ], 10, 3 );
108 128 add_filter( 'style_loader_tag', [ $this, 'css_minify' ], 10, 4 );
129 + add_filter( 'powered_cache_fo_script_loader_tag', [ $this, 'change_js_execute_method' ] );
130 + add_filter( 'script_loader_tag', [ $this, 'change_js_execute_method' ], 99 );
131 + add_action( 'after_setup_theme', [ $this, 'maybe_start_buffer' ], 999 );
132 + add_action( 'template_redirect', [ $this, 'maybe_suppress_optimizations' ] );
109 133
110 134 if ( ! $this->settings['combine_js'] ) {
111 135 add_filter( 'powered_cache_fo_js_do_concat', '__return_false' );
112 136 }
@@ -114,25 +138,89 @@
114 138 if ( ! $this->settings['combine_css'] ) {
115 139 add_filter( 'powered_cache_fo_css_do_concat', '__return_false' );
116 140 }
117 141
118 - add_filter( 'powered_cache_fo_script_loader_tag', [ $this, 'change_js_execute_method' ] );
142 + if ( $this->settings['combine_google_fonts'] ) {
143 + add_action( 'wp_enqueue_scripts', [ $this, 'combine_google_fonts' ], 99 );
144 + }
145 + }
119 146
120 - if ( ! $this->settings['js_execution_optimized_only'] ) {
121 - add_filter( 'script_loader_tag', [ $this, 'change_js_execute_method' ], 99 );
147 + /**
148 + * Process output buffer
149 + *
150 + * @return void
151 + */
152 + public function maybe_start_buffer() {
153 + if ( ! isset( $_SERVER['REQUEST_URI'] ) ) {
154 + return;
122 155 }
156 + $request_uri = sanitize_text_field( wp_unslash( $_SERVER['REQUEST_URI'] ) );
123 157
124 - add_action( 'after_setup_theme', [ $this, 'html_minify' ] );
158 + if ( strpos( $request_uri, 'robots.txt' ) !== false || strpos( $request_uri, '.htaccess' ) !== false ) {
159 + return;
160 + }
125 161
126 - if ( $this->settings['combine_google_fonts'] ) {
127 - add_action( 'wp_enqueue_scripts', [ $this, 'combine_google_fonts' ], 99 );
162 + if ( ! isset( $_SERVER['REQUEST_METHOD'] ) || 'GET' !== $_SERVER['REQUEST_METHOD'] ) {
163 + return;
128 164 }
129 165
130 - add_action( 'template_redirect', [ $this, 'maybe_suppress_optimizations' ] );
166 + $file_extension = preg_replace( '#^(.*?)\?.*$#', '$1', $request_uri );
167 + $file_extension = trim( preg_replace( '#^.*\.(.*)$#', '$1', $file_extension ) );
168 +
169 + if ( ! preg_match( '#index\.php$#i', $request_uri ) && in_array( $file_extension, array( 'php', 'xml', 'xsl' ), true ) ) {
170 + return;
171 + }
172 +
173 + if ( defined( 'REST_REQUEST' ) && REST_REQUEST ) {
174 + return;
175 + }
176 +
177 + ob_start( [ $this, 'process_buffer' ] );
131 178 }
132 179
180 + /**
181 + * Process output buffer
182 + *
183 + * @param string $html Output buffer
184 + *
185 + * @return string|string[]|null
186 + */
187 + public function process_buffer( $html ) {
188 + $html = $this->maybe_defer_inline_scripts( $html );
189 + $html = $this->maybe_delay_scripts( $html );
190 + $html = $this->maybe_replace_google_fonts_with_bunny_fonts( $html );
191 + $html = $this->maybe_minify_html( $html );
133 192
193 + return $html;
194 + }
195 +
134 196 /**
197 + * Replace Google Fonts with Bunny drop-in replacement's
198 + *
199 + * @param string $html Output buffer
200 + *
201 + * @return array|mixed|string|string[]
202 + * @since 3.0
203 + */
204 + public function maybe_replace_google_fonts_with_bunny_fonts( $html ) {
205 + if ( ! $this->settings['use_bunny_fonts'] ) {
206 + return $html;
207 + }
208 +
209 + $html = str_replace(
210 + [
211 + 'https://fonts.googleapis.com',
212 + 'http://fonts.googleapis.com',
213 + '//fonts.googleapis.com',
214 + ],
215 + 'https://fonts.bunny.net',
216 + $html
217 + );
218 +
219 + return $html;
220 + }
221 +
222 + /**
135 223 * Maybe suppress optimizations based on the post meta options
136 224 *
137 225 * @since 2.1
138 226 */
@@ -139,8 +227,10 @@
139 227 public function maybe_suppress_optimizations() {
140 228 if ( is_singular() ) {
141 229 $disable_css_optimization = (bool) get_post_meta( get_the_ID(), POST_META_DISABLE_CSS_OPTIMIZATION, true );
142 230 $disable_js_optimization = (bool) get_post_meta( get_the_ID(), POST_META_DISABLE_JS_OPTIMIZATION, true );
231 + $disable_js_defer = (bool) get_post_meta( get_the_ID(), POST_META_DISABLE_JS_DEFER, true );
232 + $disable_js_delay = (bool) get_post_meta( get_the_ID(), POST_META_DISABLE_JS_DELAY, true );
143 233
144 234 if ( $disable_css_optimization ) {
145 235 add_filter( 'powered_cache_fo_css_do_concat', '__return_false' );
146 236 add_filter( 'powered_cache_fo_disable_css_minify', '__return_true' );
@@ -149,8 +239,17 @@
149 239 if ( $disable_js_optimization ) {
150 240 add_filter( 'powered_cache_fo_js_do_concat', '__return_false' );
151 241 add_filter( 'powered_cache_fo_disable_js_minify', '__return_true' );
152 242 }
243 +
244 + if ( $disable_js_defer ) {
245 + add_filter( 'powered_cache_disable_js_defer', '__return_true' );
246 + add_filter( 'powered_cache_disable_js_defer_inline', '__return_true' );
247 + }
248 +
249 + if ( $disable_js_delay ) {
250 + add_filter( 'powered_cache_delayed_js_skip', '__return_true' );
251 + }
153 252 }
154 253 }
155 254
156 255 /**
@@ -164,15 +263,23 @@
164 263 }
165 264 }
166 265
167 266 /**
168 - * Minify HTML output
267 + * Minify given HTML output
268 + *
269 + * @param string $buffer HTML
270 + *
271 + * @return string|string[]|null
169 272 */
170 - public function html_minify() {
273 + public function maybe_minify_html( $buffer ) {
171 274 if ( ! $this->settings['minify_html'] ) {
172 - return;
275 + return $buffer;
173 276 }
174 277
278 + if ( false === stripos( $buffer, '<html' ) && false === stripos( $buffer, '<!DOCTYPE html' ) ) {
279 + return $buffer;
280 + }
281 +
175 282 /**
176 283 * Filters whether disable or not disable HTML minification.
177 284 *
178 285 * @hook powered_cache_fo_disable_html_minify
@@ -182,32 +289,25 @@
182 289 * @return {boolean} New value.
183 290 * @since 2.0
184 291 */
185 292 if ( apply_filters( 'powered_cache_fo_disable_html_minify', false ) ) {
186 - return;
293 + return $buffer;
187 294 }
188 295
189 - ob_start( [ $this, 'run_html_minify' ] );
190 - }
296 + $html_min = new HtmlMin();
297 + $html_min->doOptimizeViaHtmlDomParser( $this->settings['minify_html_dom_optimization'] );
298 + $html_min->doRemoveOmittedQuotes( false );
299 + $html_min->doRemoveOmittedHtmlTags( false );
300 + $html_min->overwriteSpecialScriptTags( [ 'x-tmpl-mustache', 'text/template' ] );
301 + $buffer = $html_min->minify( $buffer );
191 302
192 - /**
193 - * Minify given HTML output
194 - *
195 - * @param string $buffer HTML
196 - *
197 - * @return string|string[]|null
198 - */
199 - public function run_html_minify( $buffer ) {
200 - $search = array(
201 - '/\>[^\S ]+/s', // strip whitespaces after tags, except space
202 - '/[^\S ]+\</s', // strip whitespaces before tags, except space
203 - '/(\s)+/s', // shorten multiple whitespace sequences
204 - '/^\\s+|\\s+$/', // shorten multiple whitespace sequences
205 - );
303 + if ( ! $this->settings['minify_html_dom_optimization'] ) {
304 + // Remove line breaks and spaces between tags
305 + $buffer = preg_replace( '/>\s+</', '><', $buffer );
306 + // Remove comments
307 + $buffer = preg_replace( '/<!--(.|\s)*?-->/', '', $buffer );
308 + }
206 309
207 - $replace = array( '>', '<', '\\1', ' ' );
208 - $buffer = preg_replace( $search, $replace, $buffer );
209 -
210 310 return $buffer;
211 311 }
212 312
213 313
@@ -218,29 +318,40 @@
218 318 *
219 319 * @return mixed
220 320 */
221 321 public function change_js_execute_method( $tag ) {
222 - $js_execution = $this->settings['js_execution_method'];
223 - if ( ! $js_execution ) {
322 + $is_defer = $this->settings['js_defer'];
323 +
324 + if ( ! $is_defer ) {
224 325 return $tag;
225 326 }
226 327
227 - if ( 'blocking' === $js_execution ) {
328 + if ( preg_match( '/<script\s+[^>]*\b(?:async|defer)\b[^>]*>/i', $tag ) ) {
228 329 return $tag;
229 330 }
230 331
231 - if ( 'async' === $js_execution ) {
232 - $js_attr = 'async="async"';
233 - } elseif ( 'defer' === $js_execution ) {
234 - $js_attr = 'defer="defer"';
332 + if ( Helper::is_defer_excluded( $tag ) ) {
333 + return $tag;
235 334 }
236 335
237 - if ( false === stripos( $tag, $js_attr ) ) {
238 - $search = '<script ';
239 - $replace = sprintf( '<script %s ', $js_attr );
240 - $tag = str_replace( $search, $replace, $tag );
336 + /**
337 + * Filters whether disable or not disable Defer
338 + *
339 + * @hook powered_cache_disable_js_defer
340 + *
341 + * @param {boolean} true to disable defer
342 + *
343 + * @return {boolean} New value.
344 + * @since 3.2
345 + */
346 + if ( apply_filters( 'powered_cache_disable_js_defer', false, $tag ) ) {
347 + return $tag;
241 348 }
242 349
350 + $search = '<script ';
351 + $replace = '<script defer="defer" ';
352 + $tag = str_replace( $search, $replace, $tag );
353 +
243 354 return $tag;
244 355 }
245 356
246 357 /**
@@ -319,13 +430,16 @@
319 430 *
320 431 * @hook powered_cache_fo_disable_js_minify
321 432 *
322 433 * @param {boolean} true to disable JS minify
434 + * @param {string} $tag script tag <script...
435 + * @param {string} $handle JS handle
436 + * @param {string} $src Resource URL
323 437 *
324 438 * @return {boolean} New value.
325 439 * @since 2.0
326 440 */
327 - if ( apply_filters( 'powered_cache_fo_disable_js_minify', false ) ) {
441 + if ( apply_filters( 'powered_cache_fo_disable_js_minify', false, $tag, $handle, $src ) ) {
328 442 return $tag;
329 443 }
330 444
331 445 // only minify static .css
@@ -451,9 +565,9 @@
451 565 }
452 566
453 567 $style_src = $wp_styles->registered[ $handle ]->src;
454 568
455 - if ( false !== strpos( $style_src, 'fonts.googleapis.com/css' ) ) {
569 + if ( false !== strpos( $style_src, 'fonts.googleapis.com/css' ) || false !== strpos( $style_src, 'fonts.bunny.net/css' ) ) {
456 570 $url = wp_parse_url( $style_src );
457 571
458 572 if ( is_string( $url['query'] ) ) {
459 573 parse_str( $url['query'], $parsed_url );
@@ -531,9 +645,9 @@
531 645 * @hook powered_cache_fo_google_font_display
532 646 *
533 647 * @param {string} $font_display font display attribute.
534 648 *
535 - * @return {boolean} New value.
649 + * @return {string} New value.
536 650 * @since 2.0
537 651 */
538 652 $font_display = apply_filters( 'powered_cache_fo_google_font_display', $font_display );
539 653
@@ -540,10 +654,22 @@
540 654 if ( ! empty( $font_display ) ) {
541 655 $font_args['display'] = $font_display;
542 656 }
543 657
544 - $src = esc_url_raw( add_query_arg( $font_args, $google_fonts_domain ) );
658 + /**
659 + * Filters google font's domain
660 + *
661 + * @hook powered_cache_fo_google_fonts_domain
662 + *
663 + * @param {string} $font_display font display attribute.
664 + *
665 + * @return {string} New value.
666 + * @since 3.0
667 + */
668 + $fonts_domain = apply_filters( 'powered_cache_fo_google_fonts_domain', $google_fonts_domain );
545 669
670 + $src = esc_url_raw( add_query_arg( $font_args, $fonts_domain ) );
671 +
546 672 // Enqueue google fonts into one URL request
547 673 wp_enqueue_style( // phpcs:ignore WordPress.WP.EnqueuedResourceParameters.MissingVersion
548 674 'pc-google-fonts-' . md5( $src ),
549 675 $src,
@@ -554,6 +680,254 @@
554 680 }
555 681 }
556 682 }
557 683 }
684 +
685 + /**
686 + * DelayedJS execution
687 + * Similar logic with image lazy-loading but this time for scripts.
688 + *
689 + * @param string $html HTML Buffer
690 + *
691 + * @return string
692 + * @since 3.0
693 + */
694 + public function maybe_delay_scripts( $html ) {
695 + if ( ! $this->settings['js_delay'] ) {
696 + return $html;
697 + }
698 +
699 + $pattern = '/<script([^>]*)>(.*?)<\/script>/si';
700 +
701 + preg_match_all( $pattern, $html, $matches, PREG_SET_ORDER );
702 +
703 + if ( empty( $matches ) ) {
704 + return $html;
705 + }
706 +
707 + $delayed_script_count = 0;
708 +
709 + foreach ( $matches as $match ) {
710 + $script = $match[0];
711 + $attributes = $match[1];
712 + $content = $match[2];
713 +
714 + $is_delay_skipped = function_exists( 'is_user_logged_in' ) && is_user_logged_in();
715 +
716 + /**
717 + * Whether skip or not skip js for delay
718 + *
719 + * @hook powered_cache_delayed_js_skip
720 + *
721 + * @param {bool} Depends on logged-in status by default.
722 + *
723 + * @return {bool} New value.
724 + * @since 3.0
725 + */
726 + if ( apply_filters( 'powered_cache_delayed_js_skip', $is_delay_skipped, $script, $attributes, $content ) ) {
727 + continue;
728 + }
729 +
730 + if ( Helper::is_delay_excluded( $script ) ) {
731 + continue;
732 + }
733 +
734 + if ( ! preg_match( '/type=/', $script ) || preg_match( '/type=[\'"]text\/javascript[\'"]/', $script ) ) {
735 + $new_script = $script;
736 +
737 + // Check if script has a src attribute
738 + if ( strpos( $attributes, 'src=' ) !== false ) {
739 + $new_script = preg_replace( '/<script([^>]*)>/', '<script type="pc-delayed-js"$1>', $new_script );
740 + } else {
741 + $new_script = preg_replace( '/<script([^>]*)>/', '<script type="pc-delayed-js"$1>', $new_script );
742 + }
743 +
744 + $html = str_replace( $script, $new_script, $html );
745 + $delayed_script_count ++;
746 + }
747 + }
748 +
749 + if ( 0 === $delayed_script_count ) {
750 + return $html;
751 + }
752 +
753 + /**
754 + * Filter delay time for JS execution fallback
755 + *
756 + * @hook powered_cache_delayed_js_timeout
757 + *
758 + * @param {int} $delay_in_ms Delay time in milliseconds
759 + *
760 + * @return {int} New value.
761 + * @since 3.0
762 + */
763 + $delay_timeout = apply_filters( 'powered_cache_delayed_js_timeout', $this->settings['js_delay_timeout'] );
764 + $script_path = POWERED_CACHE_PATH . 'dist/js/script-loader.js';
765 + $script_content = file_get_contents( $script_path ); // phpcs:ignore
766 +
767 + if ( ! $script_content ) {
768 + return $html;
769 + }
770 +
771 + $head_pos = strpos( $html, '</head>' );
772 +
773 + if ( false === $head_pos ) { // bail if no head tag
774 + return $html;
775 + }
776 +
777 + $delay_js_script_content = '<script id="powered-cache-delayed-js">' . $script_content . '</script>' . PHP_EOL;
778 +
779 + /**
780 + * Delayed JS script content
781 + *
782 + * @hook powered_cache_delayed_js_script_content
783 + *
784 + * @param {string} $delay_js_script_content Delayed JS script content
785 + * @param {string} $script_path Script path
786 + * @param {string} $html HTML buffer
787 + * @param {int} $delay_timeout Delay time in milliseconds
788 + *
789 + * @return {string} New value.
790 + * @since 3.4
791 + */
792 + $delay_js_script_content = apply_filters( 'powered_cache_delayed_js_script_content', $delay_js_script_content, $script_path, $html, $delay_timeout );
793 +
794 + $html = substr_replace( $html, $delay_js_script_content, $head_pos, 0 );
795 +
796 + $script_loader = PHP_EOL . '<script id="powered-cache-delayed-script-loader">' . PHP_EOL;
797 + $script_loader .= 'console.log("[Powered Cache] - Script(s) will be loaded with delay or interaction");' . PHP_EOL;
798 + $script_loader .= 'window.PCScriptLoaderTimeout=' . absint( $delay_timeout ) . ';' . PHP_EOL;
799 +
800 + $script_loader .= 'Defer.all(\'script[type="pc-delayed-js"]\', window.PCScriptLoaderTimeout, true);' . PHP_EOL;
801 +
802 + // Dispatch DOMContentLoaded event after delayed scripts are loaded
803 + // This ensures scripts that listen for DOMContentLoaded still execute
804 + $script_loader .= '(function(){' . PHP_EOL;
805 + $script_loader .= ' var pcDelayedScripts=document.querySelectorAll(\'script[type="pc-delayed-js"]\');' . PHP_EOL;
806 + $script_loader .= ' if(pcDelayedScripts.length===0)return;' . PHP_EOL;
807 + $script_loader .= ' var pcScriptCount=pcDelayedScripts.length;' . PHP_EOL;
808 + $script_loader .= ' var pcCheckInterval=setInterval(function(){' . PHP_EOL;
809 + $script_loader .= ' var remaining=document.querySelectorAll(\'script[type="pc-delayed-js"]\').length;' . PHP_EOL;
810 + $script_loader .= ' if(remaining===0){' . PHP_EOL;
811 + $script_loader .= ' clearInterval(pcCheckInterval);' . PHP_EOL;
812 + $script_loader .= ' setTimeout(function(){' . PHP_EOL;
813 + $script_loader .= ' if(document.readyState==="complete"||document.readyState==="interactive"){' . PHP_EOL;
814 + $script_loader .= ' var event=document.createEvent?document.createEvent("Event"):new Event("DOMContentLoaded");' . PHP_EOL;
815 + $script_loader .= ' if(document.createEvent){event.initEvent("DOMContentLoaded",true,true);}' . PHP_EOL;
816 + $script_loader .= ' document.dispatchEvent(event);' . PHP_EOL;
817 + $script_loader .= ' console.log("[Powered Cache] - DOMContentLoaded event dispatched for "+pcScriptCount+" delayed script(s)");' . PHP_EOL;
818 + $script_loader .= ' }' . PHP_EOL;
819 + $script_loader .= ' },10);' . PHP_EOL;
820 + $script_loader .= ' }' . PHP_EOL;
821 + $script_loader .= ' },50);' . PHP_EOL;
822 + $script_loader .= '})();' . PHP_EOL;
823 +
824 + $script_loader .= '</script>' . PHP_EOL;
825 +
826 + /**
827 + * Delayed JS script loader content
828 + *
829 + * @hook powered_cache_delayed_js_script_loader
830 + *
831 + * @param {string} $script_loader Delayed JS script loader content
832 + * @param {string} $html HTML buffer
833 + * @param {int} $delay_timeout Delay time in milliseconds
834 + *
835 + * @return {string} New value.
836 + * @since 3.4
837 + */
838 + $script_loader = apply_filters( 'powered_cache_delayed_js_script_loader', $script_loader, $html, $delay_timeout );
839 +
840 + $body_pos = strpos( $html, '</body>' );
841 +
842 + if ( false !== $body_pos ) {
843 + $html = substr_replace( $html, $script_loader, $body_pos, 0 );
844 + } else {
845 + $html_pos = strpos( $html, '</html>' );
846 + if ( false !== $html_pos ) {
847 + $html = substr_replace( $html, $script_loader, $html_pos, 0 );
848 + }
849 + }
850 +
851 + return $html;
852 + }
853 +
854 + /**
855 + * Defer jQuery depended inline scripts
856 + *
857 + * @param string $html Output buffer
858 + *
859 + * @return array|mixed|string|string[]|null
860 + * @since 3.2
861 + */
862 + public function maybe_defer_inline_scripts( $html ) {
863 + if ( ! $this->settings['js_defer'] ) {
864 + return $html;
865 + }
866 +
867 + $html = preg_replace_callback(
868 + '/(<script[^>]*>)(.*?)<\/script>/s',
869 + function ( $matches ) {
870 + $script_open_tag = $matches[1];
871 + $script_content = $matches[2];
872 +
873 + if ( ! $this->should_defer_script( $script_content, $script_open_tag ) ) {
874 + return $matches[0]; // Return the script unchanged
875 + }
876 +
877 + /**
878 + * Filters whether disable or not disable inline defer
879 + *
880 + * @hook powered_cache_disable_js_defer_inline
881 + *
882 + * @param {boolean} true to disable defer
883 + *
884 + * @return {boolean} New value.
885 + * @since 3.2
886 + */
887 + if ( apply_filters( 'powered_cache_disable_js_defer_inline', false, $script_content, $script_open_tag ) ) {
888 + return $matches[0]; // Return the script unchanged
889 + }
890 +
891 + return $script_open_tag . 'window.addEventListener("DOMContentLoaded", function() {(function($) {' . $script_content . '})(jQuery);});</script>';
892 + },
893 + $html
894 + );
895 +
896 + return $html;
897 + }
898 +
899 + /**
900 + * Determine whether defer or not defer inline script
901 + *
902 + * @param string $script_content Script content
903 + * @param string $script_attributes Script attributes
904 + *
905 + * @return bool
906 + * @since 3.2
907 + */
908 + private function should_defer_script( $script_content, $script_attributes ) {
909 + // Ignore scripts with a 'src' attribute (external scripts)
910 + if ( false !== strpos( $script_attributes, 'src=' ) ) {
911 + return false;
912 + }
913 +
914 + // ignore ld+json
915 + if ( false !== strpos( $script_attributes, 'type="application/ld+json"' ) ) {
916 + return false;
917 + }
918 +
919 + // Check if the script contains 'DOMContentLoaded' or 'document.write'
920 + if ( false !== strpos( $script_content, 'DOMContentLoaded' ) || false !== strpos( $script_content, 'document.write' ) ) {
921 + return false;
922 + }
923 +
924 + // Check if the script does NOT contain jQuery-related functions
925 + if ( false === strpos( $script_content, 'jQuery(' ) && false === strpos( $script_content, '$.(' ) && false === strpos( $script_content, '$(' ) ) {
926 + return false;
927 + }
928 +
929 + return true;
930 + }
931 +
558 932
559 933 }