array(), 'scripts' => array() ); /** * Initialize the asset tracker * * @return void */ public static function init() { // Hook into wp_enqueue_scripts with high priority to catch all assets add_action('wp_enqueue_scripts', array(__CLASS__, 'track_assets'), PHP_INT_MAX); // Hook into admin_enqueue_scripts for admin assets add_action('admin_enqueue_scripts', array(__CLASS__, 'track_assets'), PHP_INT_MAX); // Record assets on shutdown add_action('shutdown', array(__CLASS__, 'record_assets'), 1); if (class_exists('WP_Debug_Logger')) { WP_Debug_Logger::debug('Asset Tracker initialized'); } } /** * Track enqueued assets * * @return void */ public static function track_assets() { global $wp_styles, $wp_scripts; // Track styles if (isset($wp_styles) && is_object($wp_styles)) { self::track_styles($wp_styles); } // Track scripts if (isset($wp_scripts) && is_object($wp_scripts)) { self::track_scripts($wp_scripts); } } /** * Track stylesheet assets * * @param WP_Styles $wp_styles WordPress styles object * @return void */ private static function track_styles($wp_styles) { if (empty($wp_styles->queue)) { return; } foreach ($wp_styles->queue as $handle) { if (!isset($wp_styles->registered[$handle])) { continue; } $style = $wp_styles->registered[$handle]; $asset_data = array( 'handle' => $handle, 'src' => $style->src, 'dependencies' => $style->deps, 'version' => $style->ver, 'media' => $style->args, 'type' => 'style', 'size' => self::get_file_size($style->src), 'local' => self::is_local_asset($style->src), 'has_version' => !empty($style->ver), 'extra' => isset($style->extra) ? $style->extra : array() ); self::$assets_enqueued['styles'][$handle] = $asset_data; } } /** * Track script assets * * @param WP_Scripts $wp_scripts WordPress scripts object * @return void */ private static function track_scripts($wp_scripts) { if (empty($wp_scripts->queue)) { return; } foreach ($wp_scripts->queue as $handle) { if (!isset($wp_scripts->registered[$handle])) { continue; } $script = $wp_scripts->registered[$handle]; $asset_data = array( 'handle' => $handle, 'src' => $script->src, 'dependencies' => $script->deps, 'version' => $script->ver, 'in_footer' => isset($script->extra['group']) && $script->extra['group'] === 1, 'type' => 'script', 'size' => self::get_file_size($script->src), 'local' => self::is_local_asset($script->src), 'has_version' => !empty($script->ver), 'extra' => isset($script->extra) ? $script->extra : array() ); self::$assets_enqueued['scripts'][$handle] = $asset_data; } } /** * Check if asset is local * * @param string $src Asset source URL * @return bool */ private static function is_local_asset($src) { if (empty($src)) { return false; } $home_url = home_url(); $site_url = site_url(); // Check if it starts with home or site URL if (strpos($src, $home_url) === 0 || strpos($src, $site_url) === 0) { return true; } // Check if it starts with relative path if (strpos($src, '/') === 0 && strpos($src, '//') !== 0) { return true; } return false; } /** * Get file size for local assets * * @param string $src Asset source URL * @return int|null File size in bytes or null if not available */ private static function get_file_size($src) { if (empty($src) || !self::is_local_asset($src)) { return null; } // Convert URL to file path $file_path = self::url_to_path($src); if (!$file_path || !file_exists($file_path)) { return null; } $size = @filesize($file_path); return $size !== false ? $size : null; } /** * Convert asset URL to file system path * * @param string $src Asset source URL * @return string|false File path or false if not convertible */ private static function url_to_path($src) { // Remove query string $src = strtok($src, '?'); // Handle absolute URLs $home_url = home_url(); $site_url = site_url(); if (strpos($src, $home_url) === 0) { $relative_path = str_replace($home_url, '', $src); return ABSPATH . ltrim($relative_path, '/'); } if (strpos($src, $site_url) === 0) { $relative_path = str_replace($site_url, '', $src); return ABSPATH . ltrim($relative_path, '/'); } // Handle relative URLs if (strpos($src, '/') === 0 && strpos($src, '//') !== 0) { return ABSPATH . ltrim($src, '/'); } return false; } /** * Get all tracked assets * * @return array All tracked assets */ public static function get_assets() { return self::$assets_enqueued; } /** * Detect duplicate assets (same src, different handles) * * @return array Detected duplicates */ public static function get_duplicates() { $duplicates = array(); $src_map = array(); // Check styles foreach (self::$assets_enqueued['styles'] as $handle => $asset) { if (empty($asset['src'])) { continue; } // Normalize source (remove query strings for comparison) $normalized_src = strtok($asset['src'], '?'); if (!isset($src_map['styles'][$normalized_src])) { $src_map['styles'][$normalized_src] = array(); } $src_map['styles'][$normalized_src][] = $handle; } // Check scripts foreach (self::$assets_enqueued['scripts'] as $handle => $asset) { if (empty($asset['src'])) { continue; } // Normalize source (remove query strings for comparison) $normalized_src = strtok($asset['src'], '?'); if (!isset($src_map['scripts'][$normalized_src])) { $src_map['scripts'][$normalized_src] = array(); } $src_map['scripts'][$normalized_src][] = $handle; } // Find duplicates foreach ($src_map as $type => $sources) { foreach ($sources as $src => $handles) { if (count($handles) > 1) { $duplicates[] = array( 'type' => $type, 'src' => $src, 'handles' => $handles, 'count' => count($handles) ); } } } return $duplicates; } /** * Get statistics about tracked assets * * @return array Asset statistics */ public static function get_statistics() { $total_styles = count(self::$assets_enqueued['styles']); $total_scripts = count(self::$assets_enqueued['scripts']); $total_size = 0; $local_count = 0; $external_count = 0; // Calculate statistics for styles foreach (self::$assets_enqueued['styles'] as $asset) { if ($asset['size'] !== null) { $total_size += $asset['size']; } if ($asset['local']) { $local_count++; } else { $external_count++; } } // Calculate statistics for scripts foreach (self::$assets_enqueued['scripts'] as $asset) { if ($asset['size'] !== null) { $total_size += $asset['size']; } if ($asset['local']) { $local_count++; } else { $external_count++; } } return array( 'total_assets' => $total_styles + $total_scripts, 'total_styles' => $total_styles, 'total_scripts' => $total_scripts, 'total_size' => $total_size, 'total_size_formatted' => size_format($total_size), 'local_assets' => $local_count, 'external_assets' => $external_count, 'duplicates_count' => count(self::get_duplicates()), ); } /** * Record assets and save to transient * * @return void */ public static function record_assets() { if (empty(self::$assets_enqueued['styles']) && empty(self::$assets_enqueued['scripts'])) { if (class_exists('WP_Debug_Logger')) { WP_Debug_Logger::debug('No assets to record'); } return; } $assets_data = array( 'assets' => self::$assets_enqueued, 'duplicates' => self::get_duplicates(), 'statistics' => self::get_statistics(), 'timestamp' => current_time('timestamp'), ); // Save to transient (expires in 1 hour) set_transient('wp_debug_assets_data', $assets_data, HOUR_IN_SECONDS); if (class_exists('WP_Debug_Logger')) { WP_Debug_Logger::debug('Asset Tracker recorded assets', array( 'total_styles' => count(self::$assets_enqueued['styles']), 'total_scripts' => count(self::$assets_enqueued['scripts']), 'duplicates' => count($assets_data['duplicates']), )); } } }