settings = $settings; // Parse defer exclusions $user_excludes = array_filter( array_map('trim', explode(',', $settings['js_defer_exclude_handles'] ?? '')) ); $this->defer_excluded_handles = array_unique(array_merge(self::$system_excluded, $user_excludes)); // Parse delay handles $this->delay_handles = array_filter( array_map('trim', explode(',', $settings['js_delay_handles'] ?? '')) ); // Parse delay URL patterns $this->delay_patterns = array_filter( array_map('trim', explode(',', $settings['js_delay_patterns'] ?? '')) ); if (!is_admin()) { // Priority 20: after OTTO defer at priority 10 add_filter('script_loader_tag', [$this, 'process_script_tag'], 20, 3); add_action('wp_footer', [$this, 'maybe_output_loader'], 999); } } /** * Process each script tag for defer or delay. * * @param string $tag The script tag. * @param string $handle The script handle. * @param string $src The script source URL. * @return string Modified script tag. */ public function process_script_tag(string $tag, string $handle, string $src): string { // Never modify excluded handles if ($this->is_excluded($handle)) { return $tag; } // Check if this script should be delayed if ($this->should_delay($handle, $src)) { return $this->delay_script($tag, $src); } // Otherwise, defer if enabled if (!empty($this->settings['enable_js_defer'])) { return $this->defer_script($tag); } return $tag; } /** * Add defer attribute to a script tag. */ private function defer_script(string $tag): string { // Don't add if already has defer or async if (strpos($tag, 'defer') !== false || strpos($tag, 'async') !== false) { return $tag; } return str_replace(' src=', ' defer src=', $tag); } /** * Convert a script tag to a delayed script. * Replaces type with metasync/delayed and src with data-metasync-src. */ private function delay_script(string $tag, string $src): string { $this->has_delayed_scripts = true; // Replace type attribute or add one if (preg_match('/type\s*=\s*["\'][^"\']*["\']/', $tag)) { $tag = preg_replace('/type\s*=\s*["\'][^"\']*["\']/', 'type="metasync/delayed"', $tag); } else { $tag = str_replace(' defer_excluded_handles, true); } /** * Check if a script should be delayed (not just deferred). */ private function should_delay(string $handle, string $src): bool { if (empty($this->settings['enable_js_delay'])) { return false; } // Check explicit delay handles if (in_array($handle, $this->delay_handles, true)) { return true; } // Check URL pattern matches if (!empty($src)) { foreach ($this->delay_patterns as $pattern) { if (!empty($pattern) && strpos($src, $pattern) !== false) { return true; } } } return false; } }