370 lines
10 KiB
PHP
370 lines
10 KiB
PHP
<?php
|
|
/**
|
|
* Asset Tracker Module
|
|
*
|
|
* Tracks all enqueued CSS and JS assets, detects duplicates,
|
|
* missing versions, and calculates file sizes.
|
|
*
|
|
* @package WP_Debug
|
|
* @since 1.0.0
|
|
*/
|
|
|
|
if (!defined('ABSPATH')) {
|
|
exit;
|
|
}
|
|
|
|
/**
|
|
* Class WP_Debug_Asset_Tracker
|
|
*
|
|
* Tracks and analyzes enqueued CSS and JS assets.
|
|
*/
|
|
class WP_Debug_Asset_Tracker {
|
|
|
|
/**
|
|
* Enqueued assets storage
|
|
*
|
|
* @var array
|
|
*/
|
|
private static $assets_enqueued = array(
|
|
'styles' => 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']),
|
|
));
|
|
}
|
|
}
|
|
}
|