Created
October 19, 2025 13:01
-
-
Save youknowriad/e5854d6c602c73dec260d56a2783408a to your computer and use it in GitHub Desktop.
Audit WordPress Admin Page Scripts and Styles
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <?php | |
| /** | |
| * Plugin Name: Script Dependency Auditor | |
| * Plugin URI: https://github.com/yourusername/script-dependency-auditor | |
| * Description: Audit and visualize the tree of JS script dependencies in the WordPress post editor. Add ?debug_scripts=true to any admin URL to see the dependency tree. | |
| * Version: 1.0.0 | |
| * Author: Your Name | |
| * Author URI: https://yourwebsite.com | |
| * License: GPL v2 or later | |
| * License URI: https://www.gnu.org/licenses/gpl-2.0.html | |
| * Text Domain: script-dependency-auditor | |
| */ | |
| // Exit if accessed directly | |
| if ( ! defined( 'ABSPATH' ) ) { | |
| exit; | |
| } | |
| class Script_Dependency_Auditor { | |
| /** | |
| * Initialize the plugin | |
| */ | |
| public function __construct() { | |
| // Check early if we should intercept | |
| add_action( 'admin_init', array( $this, 'maybe_start_buffer' ) ); | |
| add_action( 'admin_footer', array( $this, 'maybe_dump_dependencies' ), PHP_INT_MAX ); | |
| } | |
| /** | |
| * Start output buffering early if debug mode is on | |
| */ | |
| public function maybe_start_buffer() { | |
| if ( isset( $_GET['debug_scripts'] ) && $_GET['debug_scripts'] === 'true' ) { | |
| ob_start(); | |
| } | |
| } | |
| /** | |
| * Check if we should dump dependencies and do it | |
| */ | |
| public function maybe_dump_dependencies() { | |
| if ( ! isset( $_GET['debug_scripts'] ) || $_GET['debug_scripts'] !== 'true' ) { | |
| return; | |
| } | |
| // Clear the buffered output | |
| while ( ob_get_level() > 0 ) { | |
| ob_end_clean(); | |
| } | |
| global $wp_scripts, $wp_styles, $wp_script_modules; | |
| ?> | |
| <!DOCTYPE html> | |
| <html> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <title>Script & Style Dependencies</title> | |
| <style> | |
| * { | |
| box-sizing: border-box; | |
| } | |
| body { | |
| font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Helvetica Neue', Arial, sans-serif; | |
| padding: 20px; | |
| background: #ffffff; | |
| color: #24292f; | |
| line-height: 1.6; | |
| max-width: 1400px; | |
| margin: 0 auto; | |
| } | |
| h1 { | |
| color: #1f2937; | |
| border-bottom: 3px solid #2563eb; | |
| padding-bottom: 15px; | |
| font-size: 28px; | |
| margin-bottom: 20px; | |
| } | |
| h2 { | |
| color: #374151; | |
| border-bottom: 2px solid #60a5fa; | |
| padding-bottom: 10px; | |
| font-size: 22px; | |
| margin-top: 40px; | |
| } | |
| h3 { | |
| color: #4b5563; | |
| font-size: 18px; | |
| margin-top: 30px; | |
| } | |
| .handle { | |
| color: #1f2937; | |
| font-weight: 600; | |
| font-size: 15px; | |
| font-family: 'SF Mono', Monaco, 'Courier New', monospace; | |
| } | |
| .badge { | |
| display: inline-block; | |
| padding: 3px 10px; | |
| margin-left: 8px; | |
| border-radius: 4px; | |
| font-size: 11px; | |
| font-weight: 600; | |
| text-transform: uppercase; | |
| letter-spacing: 0.5px; | |
| } | |
| .badge-enqueued { | |
| background: #dbeafe; | |
| color: #1e40af; | |
| } | |
| .badge-done { | |
| background: #d1fae5; | |
| color: #065f46; | |
| } | |
| .badge-footer { | |
| background: #e0e7ff; | |
| color: #4338ca; | |
| } | |
| .badge-missing { | |
| background: #fee2e2; | |
| color: #991b1b; | |
| } | |
| .badge-circular { | |
| background: #fed7aa; | |
| color: #92400e; | |
| } | |
| .meta { | |
| color: #9ca3af; | |
| font-size: 12px; | |
| margin-left: 8px; | |
| } | |
| .stats { | |
| background: #f3f4f6; | |
| padding: 20px; | |
| border-radius: 8px; | |
| margin: 20px 0; | |
| border-left: 4px solid #2563eb; | |
| } | |
| .stats-item { | |
| margin: 8px 0; | |
| color: #374151; | |
| font-size: 14px; | |
| } | |
| .stats-item strong { | |
| color: #1f2937; | |
| } | |
| .section { | |
| margin: 40px 0; | |
| } | |
| /* List view styles */ | |
| .list-view { | |
| margin: 20px 0; | |
| } | |
| .list-item { | |
| background: #ffffff; | |
| border: 1px solid #e5e7eb; | |
| border-radius: 4px; | |
| padding: 12px 16px; | |
| margin: 6px 0; | |
| display: flex; | |
| align-items: center; | |
| gap: 12px; | |
| flex-wrap: wrap; | |
| } | |
| .list-item-main { | |
| flex: 1; | |
| min-width: 300px; | |
| } | |
| .list-item-meta { | |
| color: #6b7280; | |
| font-size: 13px; | |
| display: flex; | |
| gap: 16px; | |
| flex-wrap: wrap; | |
| } | |
| .meta-item { | |
| display: flex; | |
| gap: 6px; | |
| align-items: center; | |
| } | |
| .meta-label { | |
| font-weight: 600; | |
| color: #4b5563; | |
| } | |
| .meta-value { | |
| color: #6b7280; | |
| } | |
| .script-list { | |
| display: inline; | |
| color: #2563eb; | |
| font-family: 'SF Mono', Monaco, monospace; | |
| font-size: 12px; | |
| } | |
| .size-badge { | |
| display: inline-block; | |
| background: #f3f4f6; | |
| color: #4b5563; | |
| padding: 2px 8px; | |
| border-radius: 3px; | |
| font-size: 11px; | |
| font-weight: 600; | |
| margin-left: 8px; | |
| font-family: 'SF Mono', Monaco, monospace; | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <h1>WordPress Script & Style Dependencies Audit</h1> | |
| <div class="stats"> | |
| <div class="stats-item"><strong>Page:</strong> <?php echo esc_html( $_SERVER['REQUEST_URI'] ); ?></div> | |
| <div class="stats-item"><strong>Time:</strong> <?php echo esc_html( current_time( 'Y-m-d H:i:s' ) ); ?></div> | |
| </div> | |
| <?php if ( $wp_scripts ) : ?> | |
| <div class="section"> | |
| <h2>JavaScript Dependencies</h2> | |
| <?php | |
| // Calculate needed scripts for stats | |
| $needed_scripts = array(); | |
| foreach ( $wp_scripts->queue as $handle ) { | |
| $this->collect_needed_scripts( $handle, $wp_scripts, $needed_scripts ); | |
| } | |
| // Calculate total size | |
| $total_size = 0; | |
| foreach ( array_keys( $needed_scripts ) as $handle ) { | |
| if ( isset( $wp_scripts->registered[ $handle ] ) ) { | |
| $script = $wp_scripts->registered[ $handle ]; | |
| $size = $this->get_file_size( $script->src ); | |
| if ( $size !== null ) { | |
| $total_size += $size; | |
| } | |
| } | |
| } | |
| $this->render_script_stats( $wp_scripts, count( $needed_scripts ), $total_size ); | |
| $this->render_script_list( $wp_scripts ); | |
| ?> | |
| </div> | |
| <?php endif; ?> | |
| <?php if ( $wp_styles ) : ?> | |
| <div class="section"> | |
| <h2>CSS Dependencies</h2> | |
| <?php | |
| // Calculate needed styles for stats | |
| $needed_styles = array(); | |
| foreach ( $wp_styles->queue as $handle ) { | |
| $this->collect_needed_styles( $handle, $wp_styles, $needed_styles ); | |
| } | |
| // Calculate total size | |
| $total_size = 0; | |
| foreach ( array_keys( $needed_styles ) as $handle ) { | |
| if ( isset( $wp_styles->registered[ $handle ] ) ) { | |
| $style = $wp_styles->registered[ $handle ]; | |
| $size = $this->get_file_size( $style->src ); | |
| if ( $size !== null ) { | |
| $total_size += $size; | |
| } | |
| } | |
| } | |
| $this->render_style_stats( $wp_styles, count( $needed_styles ), $total_size ); | |
| $this->render_style_list( $wp_styles ); | |
| ?> | |
| </div> | |
| <?php endif; ?> | |
| <?php if ( $wp_script_modules && method_exists( $wp_script_modules, 'get_enqueued' ) ) : ?> | |
| <div class="section"> | |
| <h2>Script Module Dependencies</h2> | |
| <?php $this->render_script_modules( $wp_script_modules ); ?> | |
| </div> | |
| <?php endif; ?> | |
| </body> | |
| </html> | |
| <?php | |
| die(); | |
| } | |
| /** | |
| * Render script statistics | |
| */ | |
| private function render_script_stats( $wp_scripts, $needed_count = null, $total_size = 0 ) { | |
| $registered = count( $wp_scripts->registered ); | |
| $enqueued = count( $wp_scripts->queue ); | |
| echo '<div class="stats">'; | |
| echo '<div class="stats-item"><strong>Total Registered:</strong> ' . $registered . ' <span style="color: #6b7280; font-size: 12px;">(all scripts WordPress knows about)</span></div>'; | |
| echo '<div class="stats-item"><strong>Directly Enqueued:</strong> ' . $enqueued . ' <span style="color: #6b7280; font-size: 12px;">(explicitly requested by plugins/themes)</span></div>'; | |
| if ( $needed_count !== null ) { | |
| echo '<div class="stats-item"><strong>Total Scripts Needed:</strong> ' . $needed_count . ' <span style="color: #6b7280; font-size: 12px;">(enqueued + all their dependencies)</span></div>'; | |
| } | |
| if ( $total_size > 0 ) { | |
| echo '<div class="stats-item"><strong>Total Size:</strong> ' . $this->format_bytes( $total_size ) . ' <span style="color: #6b7280; font-size: 12px;">(uncompressed file size)</span></div>'; | |
| } | |
| echo '</div>'; | |
| } | |
| /** | |
| * Render style statistics | |
| */ | |
| private function render_style_stats( $wp_styles, $needed_count = null, $total_size = 0 ) { | |
| $registered = count( $wp_styles->registered ); | |
| $enqueued = count( $wp_styles->queue ); | |
| echo '<div class="stats">'; | |
| echo '<div class="stats-item"><strong>Total Registered:</strong> ' . $registered . ' <span style="color: #6b7280; font-size: 12px;">(all styles WordPress knows about)</span></div>'; | |
| echo '<div class="stats-item"><strong>Directly Enqueued:</strong> ' . $enqueued . ' <span style="color: #6b7280; font-size: 12px;">(explicitly requested by plugins/themes)</span></div>'; | |
| if ( $needed_count !== null ) { | |
| echo '<div class="stats-item"><strong>Total Styles Needed:</strong> ' . $needed_count . ' <span style="color: #6b7280; font-size: 12px;">(enqueued + all their dependencies)</span></div>'; | |
| } | |
| if ( $total_size > 0 ) { | |
| echo '<div class="stats-item"><strong>Total Size:</strong> ' . $this->format_bytes( $total_size ) . ' <span style="color: #6b7280; font-size: 12px;">(uncompressed file size)</span></div>'; | |
| } | |
| echo '</div>'; | |
| } | |
| /** | |
| * Render script dependency tree | |
| */ | |
| private function render_script_tree( $wp_scripts ) { | |
| echo '<h3>Enqueued Scripts (with dependencies):</h3>'; | |
| echo '<div class="tree">'; | |
| if ( empty( $wp_scripts->queue ) ) { | |
| echo '<p>No scripts enqueued yet.</p>'; | |
| } else { | |
| foreach ( $wp_scripts->queue as $handle ) { | |
| $this->render_script_node( $handle, $wp_scripts, 0, array() ); | |
| } | |
| } | |
| echo '</div>'; | |
| } | |
| /** | |
| * Render a single script node recursively | |
| */ | |
| private function render_script_node( $handle, $wp_scripts, $depth, $visited ) { | |
| $indent_class = 'indent-' . min( $depth, 5 ); | |
| // Check for circular dependencies | |
| if ( in_array( $handle, $visited ) ) { | |
| echo '<div class="node ' . $indent_class . '">'; | |
| echo '<span class="handle">' . esc_html( $handle ) . '</span>'; | |
| echo '<span class="badge badge-circular">CIRCULAR</span>'; | |
| echo '</div>'; | |
| return; | |
| } | |
| // Check if script exists | |
| if ( ! isset( $wp_scripts->registered[ $handle ] ) ) { | |
| echo '<div class="node ' . $indent_class . '">'; | |
| echo '<span class="handle">' . esc_html( $handle ) . '</span>'; | |
| echo '<span class="badge badge-missing">MISSING</span>'; | |
| echo '</div>'; | |
| return; | |
| } | |
| $script = $wp_scripts->registered[ $handle ]; | |
| $visited[] = $handle; | |
| echo '<div class="node ' . $indent_class . '">'; | |
| echo '<span class="handle">' . esc_html( $handle ) . '</span>'; | |
| // Badges | |
| if ( in_array( $handle, $wp_scripts->queue ) ) { | |
| echo '<span class="badge badge-enqueued">ENQUEUED</span>'; | |
| } | |
| if ( ! empty( $script->extra['group'] ) ) { | |
| echo '<span class="badge badge-footer">FOOTER</span>'; | |
| } | |
| // Source and version | |
| if ( $script->src ) { | |
| echo '<div class="src">↳ ' . esc_html( $script->src ); | |
| if ( $script->ver ) { | |
| echo '<span class="meta">(v' . esc_html( $script->ver ) . ')</span>'; | |
| } | |
| echo '</div>'; | |
| } | |
| echo '</div>'; | |
| // Render dependencies | |
| if ( ! empty( $script->deps ) ) { | |
| foreach ( $script->deps as $dep ) { | |
| $this->render_script_node( $dep, $wp_scripts, $depth + 1, $visited ); | |
| } | |
| } | |
| } | |
| /** | |
| * Render style dependency tree | |
| */ | |
| private function render_style_tree( $wp_styles ) { | |
| echo '<h3>Enqueued Styles (with dependencies):</h3>'; | |
| echo '<div class="tree">'; | |
| if ( empty( $wp_styles->queue ) ) { | |
| echo '<p>No styles enqueued yet.</p>'; | |
| } else { | |
| foreach ( $wp_styles->queue as $handle ) { | |
| $this->render_style_node( $handle, $wp_styles, 0, array() ); | |
| } | |
| } | |
| echo '</div>'; | |
| } | |
| /** | |
| * Render a single style node recursively | |
| */ | |
| private function render_style_node( $handle, $wp_styles, $depth, $visited ) { | |
| $indent_class = 'indent-' . min( $depth, 5 ); | |
| // Check for circular dependencies | |
| if ( in_array( $handle, $visited ) ) { | |
| echo '<div class="node ' . $indent_class . '">'; | |
| echo '<span class="handle">' . esc_html( $handle ) . '</span>'; | |
| echo '<span class="badge badge-circular">CIRCULAR</span>'; | |
| echo '</div>'; | |
| return; | |
| } | |
| // Check if style exists | |
| if ( ! isset( $wp_styles->registered[ $handle ] ) ) { | |
| echo '<div class="node ' . $indent_class . '">'; | |
| echo '<span class="handle">' . esc_html( $handle ) . '</span>'; | |
| echo '<span class="badge badge-missing">MISSING</span>'; | |
| echo '</div>'; | |
| return; | |
| } | |
| $style = $wp_styles->registered[ $handle ]; | |
| $visited[] = $handle; | |
| echo '<div class="node ' . $indent_class . '">'; | |
| echo '<span class="handle">' . esc_html( $handle ) . '</span>'; | |
| // Badges | |
| if ( in_array( $handle, $wp_styles->queue ) ) { | |
| echo '<span class="badge badge-enqueued">ENQUEUED</span>'; | |
| } | |
| // Source and version | |
| if ( $style->src ) { | |
| echo '<div class="src">↳ ' . esc_html( $style->src ); | |
| if ( $style->ver ) { | |
| echo '<span class="meta">(v' . esc_html( $style->ver ) . ')</span>'; | |
| } | |
| echo '</div>'; | |
| } | |
| echo '</div>'; | |
| // Render dependencies | |
| if ( ! empty( $style->deps ) ) { | |
| foreach ( $style->deps as $dep ) { | |
| $this->render_style_node( $dep, $wp_styles, $depth + 1, $visited ); | |
| } | |
| } | |
| } | |
| /** | |
| * Render script list view with reasons | |
| */ | |
| private function render_script_list( $wp_scripts ) { | |
| echo '<div class="list-view">'; | |
| // Build dependency map (who depends on what) | |
| $dependents = array(); | |
| foreach ( $wp_scripts->registered as $handle => $script ) { | |
| if ( ! empty( $script->deps ) ) { | |
| foreach ( $script->deps as $dep ) { | |
| if ( ! isset( $dependents[ $dep ] ) ) { | |
| $dependents[ $dep ] = array(); | |
| } | |
| $dependents[ $dep ][] = $handle; | |
| } | |
| } | |
| } | |
| // Get all needed scripts (recursive function to find all dependencies) | |
| $needed_scripts = array(); | |
| foreach ( $wp_scripts->queue as $handle ) { | |
| $this->collect_needed_scripts( $handle, $wp_scripts, $needed_scripts ); | |
| } | |
| // Get all scripts (only the ones that are needed) | |
| $all_handles = array_keys( $needed_scripts ); | |
| sort( $all_handles ); | |
| foreach ( $all_handles as $handle ) { | |
| if ( ! isset( $wp_scripts->registered[ $handle ] ) ) { | |
| continue; | |
| } | |
| $script = $wp_scripts->registered[ $handle ]; | |
| $is_enqueued = in_array( $handle, $wp_scripts->queue ); | |
| $is_done = in_array( $handle, $wp_scripts->done ); | |
| echo '<div class="list-item">'; | |
| // Main section with handle and badges | |
| echo '<div class="list-item-main">'; | |
| echo '<span class="handle">' . esc_html( $handle ) . '</span>'; | |
| // Show file size | |
| $file_size = $this->get_file_size( $script->src ); | |
| if ( $file_size !== null ) { | |
| echo '<span class="size-badge">' . esc_html( $this->format_bytes( $file_size ) ) . '</span>'; | |
| } | |
| if ( $is_enqueued ) { | |
| echo '<span class="badge badge-enqueued">ENQUEUED</span>'; | |
| } | |
| if ( ! empty( $script->extra['group'] ) ) { | |
| echo '<span class="badge badge-footer">FOOTER</span>'; | |
| } | |
| echo '</div>'; | |
| // Meta section with relationships | |
| echo '<div class="list-item-meta">'; | |
| // Enqueued by | |
| if ( ! $is_enqueued ) { | |
| $enqueued_by = $this->find_enqueued_parents( $handle, $wp_scripts, $dependents ); | |
| if ( ! empty( $enqueued_by ) ) { | |
| echo '<div class="meta-item">'; | |
| echo '<span class="meta-label">Enqueued by:</span>'; | |
| echo '<span class="script-list">' . esc_html( implode( ', ', $enqueued_by ) ) . '</span>'; | |
| echo '</div>'; | |
| } | |
| } | |
| // Required by | |
| if ( isset( $dependents[ $handle ] ) ) { | |
| $needed_dependents = array_intersect( $dependents[ $handle ], $all_handles ); | |
| if ( ! empty( $needed_dependents ) ) { | |
| echo '<div class="meta-item">'; | |
| echo '<span class="meta-label">Required by:</span>'; | |
| echo '<span class="script-list">' . esc_html( implode( ', ', $needed_dependents ) ) . '</span>'; | |
| echo '</div>'; | |
| } | |
| } | |
| echo '</div>'; | |
| echo '</div>'; | |
| } | |
| echo '</div>'; | |
| } | |
| /** | |
| * Find top-level enqueued scripts that caused this script to load | |
| */ | |
| private function find_enqueued_parents( $handle, $wp_scripts, $dependents ) { | |
| $enqueued_parents = array(); | |
| $visited = array(); | |
| // Recursive function to traverse up the dependency tree | |
| $find_parents = function( $current_handle ) use ( &$find_parents, $dependents, $wp_scripts, &$visited, &$enqueued_parents ) { | |
| if ( in_array( $current_handle, $visited ) ) { | |
| return; | |
| } | |
| $visited[] = $current_handle; | |
| // If this is enqueued, add it to the list | |
| if ( in_array( $current_handle, $wp_scripts->queue ) ) { | |
| $enqueued_parents[] = $current_handle; | |
| return; // Stop here, we found a top-level enqueued script | |
| } | |
| // Otherwise, continue traversing up | |
| if ( isset( $dependents[ $current_handle ] ) ) { | |
| foreach ( $dependents[ $current_handle ] as $parent ) { | |
| $find_parents( $parent ); | |
| } | |
| } | |
| }; | |
| $find_parents( $handle ); | |
| return array_unique( $enqueued_parents ); | |
| } | |
| /** | |
| * Recursively collect all needed scripts | |
| */ | |
| private function collect_needed_scripts( $handle, $wp_scripts, &$needed ) { | |
| if ( isset( $needed[ $handle ] ) ) { | |
| return; | |
| } | |
| $needed[ $handle ] = true; | |
| if ( ! isset( $wp_scripts->registered[ $handle ] ) ) { | |
| return; | |
| } | |
| $script = $wp_scripts->registered[ $handle ]; | |
| if ( ! empty( $script->deps ) ) { | |
| foreach ( $script->deps as $dep ) { | |
| $this->collect_needed_scripts( $dep, $wp_scripts, $needed ); | |
| } | |
| } | |
| } | |
| /** | |
| * Render style list view with reasons | |
| */ | |
| private function render_style_list( $wp_styles ) { | |
| echo '<div class="list-view">'; | |
| // Build dependency map (who depends on what) | |
| $dependents = array(); | |
| foreach ( $wp_styles->registered as $handle => $style ) { | |
| if ( ! empty( $style->deps ) ) { | |
| foreach ( $style->deps as $dep ) { | |
| if ( ! isset( $dependents[ $dep ] ) ) { | |
| $dependents[ $dep ] = array(); | |
| } | |
| $dependents[ $dep ][] = $handle; | |
| } | |
| } | |
| } | |
| // Get all needed styles (recursive function to find all dependencies) | |
| $needed_styles = array(); | |
| foreach ( $wp_styles->queue as $handle ) { | |
| $this->collect_needed_styles( $handle, $wp_styles, $needed_styles ); | |
| } | |
| // Get all styles (only the ones that are needed) | |
| $all_handles = array_keys( $needed_styles ); | |
| sort( $all_handles ); | |
| foreach ( $all_handles as $handle ) { | |
| if ( ! isset( $wp_styles->registered[ $handle ] ) ) { | |
| continue; | |
| } | |
| $style = $wp_styles->registered[ $handle ]; | |
| $is_enqueued = in_array( $handle, $wp_styles->queue ); | |
| $is_done = in_array( $handle, $wp_styles->done ); | |
| echo '<div class="list-item">'; | |
| // Main section with handle and badges | |
| echo '<div class="list-item-main">'; | |
| echo '<span class="handle">' . esc_html( $handle ) . '</span>'; | |
| // Show file size | |
| $file_size = $this->get_file_size( $style->src ); | |
| if ( $file_size !== null ) { | |
| echo '<span class="size-badge">' . esc_html( $this->format_bytes( $file_size ) ) . '</span>'; | |
| } | |
| if ( $is_enqueued ) { | |
| echo '<span class="badge badge-enqueued">ENQUEUED</span>'; | |
| } | |
| echo '</div>'; | |
| // Meta section with relationships | |
| echo '<div class="list-item-meta">'; | |
| // Enqueued by | |
| if ( ! $is_enqueued ) { | |
| $enqueued_by = $this->find_enqueued_style_parents( $handle, $wp_styles, $dependents ); | |
| if ( ! empty( $enqueued_by ) ) { | |
| echo '<div class="meta-item">'; | |
| echo '<span class="meta-label">Enqueued by:</span>'; | |
| echo '<span class="script-list">' . esc_html( implode( ', ', $enqueued_by ) ) . '</span>'; | |
| echo '</div>'; | |
| } | |
| } | |
| // Required by | |
| if ( isset( $dependents[ $handle ] ) ) { | |
| $needed_dependents = array_intersect( $dependents[ $handle ], $all_handles ); | |
| if ( ! empty( $needed_dependents ) ) { | |
| echo '<div class="meta-item">'; | |
| echo '<span class="meta-label">Required by:</span>'; | |
| echo '<span class="script-list">' . esc_html( implode( ', ', $needed_dependents ) ) . '</span>'; | |
| echo '</div>'; | |
| } | |
| } | |
| echo '</div>'; | |
| echo '</div>'; | |
| } | |
| echo '</div>'; | |
| } | |
| /** | |
| * Find top-level enqueued styles that caused this style to load | |
| */ | |
| private function find_enqueued_style_parents( $handle, $wp_styles, $dependents ) { | |
| $enqueued_parents = array(); | |
| $visited = array(); | |
| // Recursive function to traverse up the dependency tree | |
| $find_parents = function( $current_handle ) use ( &$find_parents, $dependents, $wp_styles, &$visited, &$enqueued_parents ) { | |
| if ( in_array( $current_handle, $visited ) ) { | |
| return; | |
| } | |
| $visited[] = $current_handle; | |
| // If this is enqueued, add it to the list | |
| if ( in_array( $current_handle, $wp_styles->queue ) ) { | |
| $enqueued_parents[] = $current_handle; | |
| return; // Stop here, we found a top-level enqueued style | |
| } | |
| // Otherwise, continue traversing up | |
| if ( isset( $dependents[ $current_handle ] ) ) { | |
| foreach ( $dependents[ $current_handle ] as $parent ) { | |
| $find_parents( $parent ); | |
| } | |
| } | |
| }; | |
| $find_parents( $handle ); | |
| return array_unique( $enqueued_parents ); | |
| } | |
| /** | |
| * Recursively collect all needed styles | |
| */ | |
| private function collect_needed_styles( $handle, $wp_styles, &$needed ) { | |
| if ( isset( $needed[ $handle ] ) ) { | |
| return; | |
| } | |
| $needed[ $handle ] = true; | |
| if ( ! isset( $wp_styles->registered[ $handle ] ) ) { | |
| return; | |
| } | |
| $style = $wp_styles->registered[ $handle ]; | |
| if ( ! empty( $style->deps ) ) { | |
| foreach ( $style->deps as $dep ) { | |
| $this->collect_needed_styles( $dep, $wp_styles, $needed ); | |
| } | |
| } | |
| } | |
| /** | |
| * Render script modules | |
| */ | |
| private function render_script_modules( $wp_script_modules ) { | |
| // Try to get registered modules using reflection if needed | |
| $registered = array(); | |
| $enqueued = array(); | |
| try { | |
| // WP_Script_Modules may have different internal structure | |
| // Try to access via get_marked_for_enqueue() or similar methods | |
| if ( method_exists( $wp_script_modules, 'get_enqueued' ) ) { | |
| $enqueued = $wp_script_modules->get_enqueued(); | |
| } | |
| // Try to get all registered modules | |
| if ( method_exists( $wp_script_modules, 'get_registered' ) ) { | |
| $registered = $wp_script_modules->get_registered(); | |
| } else { | |
| // Use reflection to access private properties if needed | |
| $reflection = new ReflectionClass( $wp_script_modules ); | |
| if ( $reflection->hasProperty( 'registered' ) ) { | |
| $prop = $reflection->getProperty( 'registered' ); | |
| $prop->setAccessible( true ); | |
| $registered = $prop->getValue( $wp_script_modules ); | |
| } | |
| } | |
| } catch ( Exception $e ) { | |
| echo '<p>Unable to retrieve script module information.</p>'; | |
| return; | |
| } | |
| // Statistics | |
| echo '<div class="stats">'; | |
| echo '<div class="stats-item"><strong>Total Registered:</strong> ' . count( $registered ) . ' <span style="color: #6b7280; font-size: 12px;">(all script modules WordPress knows about)</span></div>'; | |
| echo '<div class="stats-item"><strong>Enqueued:</strong> ' . count( $enqueued ) . ' <span style="color: #6b7280; font-size: 12px;">(modules marked for loading)</span></div>'; | |
| echo '</div>'; | |
| // List view | |
| echo '<div class="list-view">'; | |
| if ( empty( $registered ) ) { | |
| echo '<p>No script modules registered.</p>'; | |
| return; | |
| } | |
| foreach ( $registered as $id => $module_data ) { | |
| $is_enqueued = in_array( $id, $enqueued ); | |
| echo '<div class="list-item">'; | |
| // Main section with handle and badges | |
| echo '<div class="list-item-main">'; | |
| echo '<span class="handle">' . esc_html( $id ) . '</span>'; | |
| // Show file size if we can get the src | |
| $src = null; | |
| if ( is_array( $module_data ) && isset( $module_data['src'] ) ) { | |
| $src = $module_data['src']; | |
| } elseif ( is_object( $module_data ) && isset( $module_data->src ) ) { | |
| $src = $module_data->src; | |
| } | |
| if ( $src ) { | |
| $file_size = $this->get_file_size( $src ); | |
| if ( $file_size !== null ) { | |
| echo '<span class="size-badge">' . esc_html( $this->format_bytes( $file_size ) ) . '</span>'; | |
| } | |
| } | |
| if ( $is_enqueued ) { | |
| echo '<span class="badge badge-enqueued">ENQUEUED</span>'; | |
| } | |
| echo '<span class="badge" style="background: #c084fc; color: #fff;">MODULE</span>'; | |
| echo '</div>'; | |
| // Meta section with dependencies | |
| echo '<div class="list-item-meta">'; | |
| // Show dependencies if available | |
| $deps = array(); | |
| if ( is_array( $module_data ) && isset( $module_data['dependencies'] ) ) { | |
| $deps = $module_data['dependencies']; | |
| } elseif ( is_object( $module_data ) && isset( $module_data->dependencies ) ) { | |
| $deps = $module_data->dependencies; | |
| } | |
| if ( ! empty( $deps ) ) { | |
| echo '<div class="meta-item">'; | |
| echo '<span class="meta-label">Depends on:</span>'; | |
| echo '<span class="script-list">' . esc_html( implode( ', ', $deps ) ) . '</span>'; | |
| echo '</div>'; | |
| } | |
| echo '</div>'; | |
| echo '</div>'; | |
| } | |
| echo '</div>'; | |
| } | |
| /** | |
| * Get file size for a script/style | |
| */ | |
| private function get_file_size( $src ) { | |
| if ( empty( $src ) ) { | |
| return null; | |
| } | |
| // Try to convert URL to file path | |
| $file_path = null; | |
| // Handle relative URLs | |
| if ( strpos( $src, 'http' ) !== 0 ) { | |
| $file_path = ABSPATH . ltrim( $src, '/' ); | |
| } else { | |
| // Try to convert absolute URL to file path | |
| $wp_content_url = content_url(); | |
| $wp_includes_url = includes_url(); | |
| if ( strpos( $src, $wp_content_url ) === 0 ) { | |
| $file_path = WP_CONTENT_DIR . str_replace( $wp_content_url, '', $src ); | |
| } elseif ( strpos( $src, $wp_includes_url ) === 0 ) { | |
| $file_path = ABSPATH . WPINC . str_replace( $wp_includes_url, '', $src ); | |
| } else { | |
| // Try to extract path from full URL | |
| $parsed = parse_url( $src ); | |
| if ( isset( $parsed['path'] ) ) { | |
| $file_path = ABSPATH . ltrim( $parsed['path'], '/' ); | |
| } | |
| } | |
| } | |
| // Remove query strings | |
| if ( $file_path ) { | |
| $file_path = preg_replace( '/\?.*$/', '', $file_path ); | |
| } | |
| if ( $file_path && file_exists( $file_path ) ) { | |
| return filesize( $file_path ); | |
| } | |
| return null; | |
| } | |
| /** | |
| * Format bytes to human readable size | |
| */ | |
| private function format_bytes( $bytes ) { | |
| if ( $bytes === null ) { | |
| return ''; | |
| } | |
| if ( $bytes < 1024 ) { | |
| return $bytes . ' B'; | |
| } elseif ( $bytes < 1024 * 1024 ) { | |
| return round( $bytes / 1024, 1 ) . ' KB'; | |
| } else { | |
| return round( $bytes / ( 1024 * 1024 ), 1 ) . ' MB'; | |
| } | |
| } | |
| } | |
| // Initialize the plugin | |
| new Script_Dependency_Auditor(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment