PluginProbe
Search Atlas SEO – OTTO AI SEO Automation for WordPress / 2.6.21
Search Atlas SEO – OTTO AI SEO Automation for WordPress v2.6.21
2.6.26 2.6.25 2.6.24 2.6.23 2.6.22 2.6.21 2.6.20 2.6.19 2.6.18 2.6.17 2.6.16 2.6.15 2.6.14 2.6.13 2.6.12 2.6.11 2.6.10 2.6.9 2.6.8 2.6.7 2.6.6 2.6.5 2.6.4 2.6.3 2.5.23 All 138 releases
metasync / code-minification / class-js-defer-delay.php

class-js-defer-delay.php in Search Atlas SEO – OTTO AI SEO Automation for WordPress 2.6.21, at code-minification/class-js-defer-delay.php

209 lines 6.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Metasync_JS_Defer_Delay
4 * Orchestrates JS defer/async/delay for non-essential scripts.
5 *
6 * @package Search Atlas SEO
7 * @copyright Copyright (C) 2021-2025, Search Atlas Group - support@searchatlas.com
8 * @since 2.5.23
9 */
10
11 if (!defined('ABSPATH')) {
12 exit;
13 }
14
15 class Metasync_JS_Defer_Delay {
16
17 /** @var array */
18 private $settings;
19
20 /** @var array Handles that must never be deferred or delayed. */
21 private $defer_excluded_handles = [];
22
23 /** @var array Explicit handles to delay. */
24 private $delay_handles = [];
25
26 /** @var array URL patterns that trigger delay. */
27 private $delay_patterns = [];
28
29 /** @var bool Whether the delay loader has been enqueued. */
30 private $loader_enqueued = false;
31
32 /** @var bool Whether JS delay is active for this request. */
33 private $has_delayed_scripts = false;
34
35 /** @var array Handles never to defer/delay (core + OTTO). */
36 private static $system_excluded = [
37 'jquery',
38 'jquery-core',
39 'jquery-migrate',
40 'wp-i18n',
41 'wp-hooks',
42 'wp-element',
43 'wp-polyfill',
44 'admin-bar',
45 'metasync-tracker',
46 'metasync',
47 ];
48
49 public function __construct(array $settings) {
50 $this->settings = $settings;
51
52 // Parse defer exclusions
53 $user_excludes = array_filter(
54 array_map('trim', explode(',', $settings['js_defer_exclude_handles'] ?? ''))
55 );
56 $this->defer_excluded_handles = array_unique(array_merge(self::$system_excluded, $user_excludes));
57
58 // Parse delay handles
59 $this->delay_handles = array_filter(
60 array_map('trim', explode(',', $settings['js_delay_handles'] ?? ''))
61 );
62
63 // Parse delay URL patterns
64 $this->delay_patterns = array_filter(
65 array_map('trim', explode(',', $settings['js_delay_patterns'] ?? ''))
66 );
67
68 if (!is_admin()) {
69 // Priority 20: after OTTO defer at priority 10
70 add_filter('script_loader_tag', [$this, 'process_script_tag'], 20, 3);
71 add_action('wp_footer', [$this, 'maybe_output_loader'], 999);
72 }
73 }
74
75 /**
76 * Process each script tag for defer or delay.
77 *
78 * @param string $tag The script tag.
79 * @param string $handle The script handle.
80 * @param string $src The script source URL.
81 * @return string Modified script tag.
82 */
83 public function process_script_tag(string $tag, string $handle, string $src): string {
84 // Never modify excluded handles
85 if ($this->is_excluded($handle)) {
86 return $tag;
87 }
88
89 // Check if this script should be delayed
90 if ($this->should_delay($handle, $src)) {
91 return $this->delay_script($tag, $src);
92 }
93
94 // Otherwise, defer if enabled
95 if (!empty($this->settings['enable_js_defer'])) {
96 return $this->defer_script($tag);
97 }
98
99 return $tag;
100 }
101
102 /**
103 * Add defer attribute to a script tag.
104 */
105 private function defer_script(string $tag): string {
106 // Don't add if already has defer or async
107 if (strpos($tag, 'defer') !== false || strpos($tag, 'async') !== false) {
108 return $tag;
109 }
110
111 return str_replace(' src=', ' defer src=', $tag);
112 }
113
114 /**
115 * Convert a script tag to a delayed script.
116 * Replaces type with metasync/delayed and src with data-metasync-src.
117 */
118 private function delay_script(string $tag, string $src): string {
119 $this->has_delayed_scripts = true;
120
121 // Replace type attribute or add one
122 if (preg_match('/type\s*=\s*["\'][^"\']*["\']/', $tag)) {
123 $tag = preg_replace('/type\s*=\s*["\'][^"\']*["\']/', 'type="metasync/delayed"', $tag);
124 } else {
125 $tag = str_replace('<script ', '<script type="metasync/delayed" ', $tag);
126 }
127
128 // Replace src with data-metasync-src
129 $tag = preg_replace(
130 '/\ssrc\s*=\s*(["\'])(' . preg_quote($src, '/') . ')\1/',
131 ' data-metasync-src=$1$2$1',
132 $tag
133 );
134
135 // Fallback: simpler replacement
136 if (strpos($tag, 'data-metasync-src') === false) {
137 $tag = str_replace("src='" . $src . "'", "data-metasync-src='" . $src . "'", $tag);
138 $tag = str_replace('src="' . $src . '"', 'data-metasync-src="' . $src . '"', $tag);
139 }
140
141 return $tag;
142 }
143
144 /**
145 * Output the delay loader script if we have delayed scripts.
146 */
147 public function maybe_output_loader(): void {
148 if (!$this->has_delayed_scripts || $this->loader_enqueued) {
149 return;
150 }
151
152 $this->loader_enqueued = true;
153 ?>
154 <script id="metasync-delay-loader">
155 (function(){
156 var loaded=false;
157 var events=['mousemove','scroll','keydown','touchstart','click'];
158 function load(){
159 if(loaded)return;
160 loaded=true;
161 events.forEach(function(e){document.removeEventListener(e,load);});
162 document.querySelectorAll('script[type="metasync/delayed"]').forEach(function(el){
163 var s=document.createElement('script');
164 s.src=el.getAttribute('data-metasync-src');
165 if(el.id)s.id=el.id;
166 s.defer=true;
167 el.parentNode.replaceChild(s,el);
168 });
169 }
170 events.forEach(function(e){document.addEventListener(e,load,{once:true,passive:true});});
171 setTimeout(load,5000);
172 })();
173 </script>
174 <?php
175 }
176
177 /**
178 * Check if a handle is excluded from defer/delay.
179 */
180 private function is_excluded(string $handle): bool {
181 return in_array($handle, $this->defer_excluded_handles, true);
182 }
183
184 /**
185 * Check if a script should be delayed (not just deferred).
186 */
187 private function should_delay(string $handle, string $src): bool {
188 if (empty($this->settings['enable_js_delay'])) {
189 return false;
190 }
191
192 // Check explicit delay handles
193 if (in_array($handle, $this->delay_handles, true)) {
194 return true;
195 }
196
197 // Check URL pattern matches
198 if (!empty($src)) {
199 foreach ($this->delay_patterns as $pattern) {
200 if (!empty($pattern) && strpos($src, $pattern) !== false) {
201 return true;
202 }
203 }
204 }
205
206 return false;
207 }
208 }
209