diff --git a/wp-content/plugins/wp-debug/assets/css/debug-panel.css b/wp-content/plugins/wp-debug/assets/css/debug-panel.css
new file mode 100644
index 00000000..d9dcc8dc
--- /dev/null
+++ b/wp-content/plugins/wp-debug/assets/css/debug-panel.css
@@ -0,0 +1,457 @@
+/**
+ * WP Debug Frontend Panel Styles
+ *
+ * @package WP_Debug
+ * @since 1.0.0
+ */
+
+/* Toggle Button */
+.wp-debug-toggle {
+ position: fixed;
+ bottom: 20px;
+ right: 20px;
+ z-index: 99998;
+ background: #2271b1;
+ color: #fff;
+ border: none;
+ border-radius: 4px;
+ padding: 10px 15px;
+ cursor: pointer;
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ font-size: 14px;
+ font-weight: 500;
+ transition: all 0.3s ease;
+}
+
+.wp-debug-toggle:hover {
+ background: #135e96;
+ transform: translateY(-2px);
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
+}
+
+.wp-debug-toggle .dashicons {
+ font-size: 18px;
+ width: 18px;
+ height: 18px;
+}
+
+.wp-debug-toggle .label {
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;
+}
+
+/* Main Panel */
+.wp-debug-panel {
+ position: fixed;
+ bottom: 80px;
+ right: 20px;
+ width: 800px;
+ max-width: calc(100vw - 40px);
+ max-height: calc(100vh - 120px);
+ background: #fff;
+ border-radius: 8px;
+ box-shadow: 0 4px 24px rgba(0, 0, 0, 0.2);
+ z-index: 99999;
+ overflow: hidden;
+ display: flex;
+ flex-direction: column;
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;
+}
+
+/* Panel Header */
+.wp-debug-panel-header {
+ background: #2271b1;
+ color: #fff;
+ padding: 15px 20px;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ flex-shrink: 0;
+ border-bottom: 3px solid #135e96;
+}
+
+.wp-debug-panel-header h3 {
+ margin: 0;
+ font-size: 18px;
+ font-weight: 600;
+ color: #fff;
+}
+
+/* Tabs */
+.wp-debug-panel-tabs {
+ display: flex;
+ gap: 5px;
+ margin: 0 auto;
+}
+
+.wp-debug-tab {
+ background: rgba(255, 255, 255, 0.1);
+ color: rgba(255, 255, 255, 0.8);
+ border: none;
+ border-radius: 4px;
+ padding: 6px 12px;
+ cursor: pointer;
+ font-size: 13px;
+ transition: all 0.2s ease;
+}
+
+.wp-debug-tab:hover {
+ background: rgba(255, 255, 255, 0.2);
+ color: #fff;
+}
+
+.wp-debug-tab.active {
+ background: #fff;
+ color: #2271b1;
+ font-weight: 600;
+}
+
+/* Close Button */
+.wp-debug-panel-close {
+ background: rgba(255, 255, 255, 0.1);
+ color: #fff;
+ border: none;
+ width: 32px;
+ height: 32px;
+ border-radius: 4px;
+ cursor: pointer;
+ font-size: 24px;
+ line-height: 1;
+ transition: all 0.2s ease;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+.wp-debug-panel-close:hover {
+ background: rgba(255, 255, 255, 0.2);
+}
+
+/* Panel Content */
+.wp-debug-panel-content {
+ padding: 20px;
+ overflow-y: auto;
+ flex: 1;
+ background: #f9f9f9;
+}
+
+.wp-debug-tab-content {
+ display: none;
+}
+
+.wp-debug-tab-content.active {
+ display: block;
+}
+
+/* Typography */
+.wp-debug-panel-content h4 {
+ margin: 0 0 15px 0;
+ font-size: 16px;
+ font-weight: 600;
+ color: #1d2327;
+ border-bottom: 2px solid #2271b1;
+ padding-bottom: 8px;
+}
+
+.wp-debug-panel-content h5 {
+ margin: 20px 0 10px 0;
+ font-size: 14px;
+ font-weight: 600;
+ color: #1d2327;
+}
+
+.wp-debug-panel-content p {
+ margin: 10px 0;
+ color: #50575e;
+ line-height: 1.6;
+}
+
+/* Tables */
+.wp-debug-table {
+ width: 100%;
+ border-collapse: collapse;
+ background: #fff;
+ border-radius: 4px;
+ overflow: hidden;
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
+ margin: 10px 0 20px 0;
+}
+
+.wp-debug-table thead {
+ background: #f0f0f1;
+}
+
+.wp-debug-table th {
+ padding: 12px;
+ text-align: left;
+ font-weight: 600;
+ font-size: 13px;
+ color: #1d2327;
+ border-bottom: 2px solid #dcdcde;
+}
+
+.wp-debug-table td {
+ padding: 10px 12px;
+ border-bottom: 1px solid #f0f0f1;
+ font-size: 13px;
+ color: #50575e;
+}
+
+.wp-debug-table tr:last-child td {
+ border-bottom: none;
+}
+
+.wp-debug-table tr:hover {
+ background: #f9f9f9;
+}
+
+.wp-debug-table code {
+ background: #f0f0f1;
+ padding: 2px 6px;
+ border-radius: 3px;
+ font-size: 12px;
+ color: #d63638;
+ font-family: "Courier New", Courier, monospace;
+}
+
+/* Lists */
+.wp-debug-list {
+ list-style: none;
+ padding: 0;
+ margin: 10px 0;
+}
+
+.wp-debug-list li {
+ background: #fff;
+ padding: 10px 12px;
+ margin-bottom: 5px;
+ border-radius: 4px;
+ border-left: 3px solid #2271b1;
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ font-size: 13px;
+ box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
+}
+
+.wp-debug-list li.missing {
+ border-left-color: #d63638;
+}
+
+.wp-debug-list li.duplicate {
+ border-left-color: #dba617;
+}
+
+.wp-debug-list code {
+ background: #f0f0f1;
+ padding: 2px 6px;
+ border-radius: 3px;
+ font-size: 12px;
+ font-family: "Courier New", Courier, monospace;
+}
+
+/* Module List */
+.wp-debug-module-list {
+ list-style: none;
+ padding: 0;
+ margin: 0;
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
+ gap: 10px;
+}
+
+.wp-debug-module-list li {
+ background: #fff;
+ padding: 10px;
+ border-radius: 4px;
+ font-size: 13px;
+ box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
+}
+
+/* Status Badges */
+.status-active {
+ display: inline-block;
+ padding: 2px 8px;
+ background: #00a32a;
+ color: #fff;
+ border-radius: 3px;
+ font-size: 11px;
+ font-weight: 600;
+ text-transform: uppercase;
+}
+
+.status-inactive {
+ display: inline-block;
+ padding: 2px 8px;
+ background: #dcdcde;
+ color: #50575e;
+ border-radius: 3px;
+ font-size: 11px;
+ font-weight: 600;
+ text-transform: uppercase;
+}
+
+.status-error {
+ display: inline-block;
+ padding: 2px 8px;
+ background: #d63638;
+ color: #fff;
+ border-radius: 3px;
+ font-size: 11px;
+ font-weight: 600;
+ text-transform: uppercase;
+}
+
+.template-type {
+ display: inline-block;
+ padding: 2px 8px;
+ background: #f0f0f1;
+ color: #2271b1;
+ border-radius: 3px;
+ font-size: 11px;
+ font-weight: 600;
+}
+
+/* Query Items */
+.wp-debug-queries {
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
+ margin: 10px 0;
+}
+
+.query-item {
+ background: #fff;
+ padding: 12px;
+ border-radius: 4px;
+ border-left: 3px solid #2271b1;
+ box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
+}
+
+.query-item.n-plus-one {
+ border-left-color: #d63638;
+}
+
+.query-time {
+ display: inline-block;
+ padding: 2px 6px;
+ background: #f0f0f1;
+ color: #d63638;
+ border-radius: 3px;
+ font-size: 11px;
+ font-weight: 600;
+ margin-bottom: 8px;
+}
+
+.query-count {
+ display: inline-block;
+ padding: 2px 6px;
+ background: #d63638;
+ color: #fff;
+ border-radius: 3px;
+ font-size: 11px;
+ font-weight: 600;
+ margin-bottom: 8px;
+ margin-right: 5px;
+}
+
+.query-sql {
+ display: block;
+ background: #f9f9f9;
+ padding: 8px;
+ border-radius: 3px;
+ font-size: 12px;
+ color: #1d2327;
+ font-family: "Courier New", Courier, monospace;
+ word-break: break-all;
+}
+
+.count {
+ background: #dba617;
+ color: #fff;
+ padding: 2px 6px;
+ border-radius: 3px;
+ font-size: 11px;
+ font-weight: 600;
+}
+
+/* Scrollbar */
+.wp-debug-panel-content::-webkit-scrollbar {
+ width: 8px;
+}
+
+.wp-debug-panel-content::-webkit-scrollbar-track {
+ background: #f0f0f1;
+}
+
+.wp-debug-panel-content::-webkit-scrollbar-thumb {
+ background: #c3c4c7;
+ border-radius: 4px;
+}
+
+.wp-debug-panel-content::-webkit-scrollbar-thumb:hover {
+ background: #a7aaad;
+}
+
+/* Responsive */
+@media (max-width: 768px) {
+ .wp-debug-panel {
+ width: calc(100vw - 40px);
+ bottom: 80px;
+ right: 20px;
+ max-height: calc(100vh - 120px);
+ }
+
+ .wp-debug-panel-header {
+ flex-direction: column;
+ gap: 10px;
+ align-items: flex-start;
+ }
+
+ .wp-debug-panel-tabs {
+ width: 100%;
+ overflow-x: auto;
+ -webkit-overflow-scrolling: touch;
+ }
+
+ .wp-debug-tab {
+ white-space: nowrap;
+ }
+
+ .wp-debug-panel-close {
+ position: absolute;
+ top: 15px;
+ right: 15px;
+ }
+
+ .wp-debug-module-list {
+ grid-template-columns: 1fr;
+ }
+
+ .wp-debug-table {
+ font-size: 12px;
+ }
+
+ .wp-debug-table th,
+ .wp-debug-table td {
+ padding: 8px;
+ }
+}
+
+/* Animation */
+@keyframes slideUp {
+ from {
+ opacity: 0;
+ transform: translateY(20px);
+ }
+ to {
+ opacity: 1;
+ transform: translateY(0);
+ }
+}
+
+.wp-debug-panel[data-visible="true"] {
+ animation: slideUp 0.3s ease;
+}
diff --git a/wp-content/plugins/wp-debug/assets/js/debug-panel.js b/wp-content/plugins/wp-debug/assets/js/debug-panel.js
new file mode 100644
index 00000000..e96fc96b
--- /dev/null
+++ b/wp-content/plugins/wp-debug/assets/js/debug-panel.js
@@ -0,0 +1,229 @@
+/**
+ * WP Debug Frontend Panel JavaScript
+ *
+ * @package WP_Debug
+ * @since 1.0.0
+ */
+
+(function($) {
+ 'use strict';
+
+ /**
+ * WP Debug Panel
+ */
+ const WPDebugPanel = {
+
+ /**
+ * Initialize
+ */
+ init: function() {
+ this.toggleButton = $('#wp-debug-toggle');
+ this.panel = $('#wp-debug-panel');
+ this.closeButton = $('.wp-debug-panel-close');
+ this.tabs = $('.wp-debug-tab');
+
+ // Restore panel state from localStorage
+ this.restoreState();
+
+ // Bind events
+ this.bindEvents();
+
+ // Log initialization
+ if (window.console && console.log) {
+ console.log('[WP Debug] Panel initialized');
+ }
+ },
+
+ /**
+ * Bind events
+ */
+ bindEvents: function() {
+ const self = this;
+
+ // Toggle button click
+ this.toggleButton.on('click', function(e) {
+ e.preventDefault();
+ self.togglePanel();
+ });
+
+ // Close button click
+ this.closeButton.on('click', function(e) {
+ e.preventDefault();
+ self.hidePanel();
+ });
+
+ // Tab clicks
+ this.tabs.on('click', function(e) {
+ e.preventDefault();
+ const tabName = $(this).data('tab');
+ self.switchTab(tabName);
+ });
+
+ // Press Escape to close
+ $(document).on('keydown', function(e) {
+ if (e.key === 'Escape' && self.panel.is(':visible')) {
+ self.hidePanel();
+ }
+ });
+
+ // Click outside to close
+ $(document).on('click', function(e) {
+ if (self.panel.is(':visible') &&
+ !self.panel.is(e.target) &&
+ self.panel.has(e.target).length === 0 &&
+ !self.toggleButton.is(e.target) &&
+ self.toggleButton.has(e.target).length === 0) {
+ self.hidePanel();
+ }
+ });
+ },
+
+ /**
+ * Toggle panel visibility
+ */
+ togglePanel: function() {
+ if (this.panel.is(':visible')) {
+ this.hidePanel();
+ } else {
+ this.showPanel();
+ }
+ },
+
+ /**
+ * Show panel
+ */
+ showPanel: function() {
+ this.panel.attr('data-visible', 'true').fadeIn(300);
+ this.saveState('visible', true);
+
+ // Log
+ if (window.console && console.log) {
+ console.log('[WP Debug] Panel opened');
+ }
+ },
+
+ /**
+ * Hide panel
+ */
+ hidePanel: function() {
+ this.panel.fadeOut(300, function() {
+ $(this).attr('data-visible', 'false');
+ });
+ this.saveState('visible', false);
+
+ // Log
+ if (window.console && console.log) {
+ console.log('[WP Debug] Panel closed');
+ }
+ },
+
+ /**
+ * Switch tab
+ */
+ switchTab: function(tabName) {
+ // Update tab buttons
+ this.tabs.removeClass('active');
+ this.tabs.filter('[data-tab="' + tabName + '"]').addClass('active');
+
+ // Update tab content
+ $('.wp-debug-tab-content').removeClass('active');
+ $('.wp-debug-tab-content[data-tab="' + tabName + '"]').addClass('active');
+
+ // Save active tab
+ this.saveState('activeTab', tabName);
+
+ // Log
+ if (window.console && console.log) {
+ console.log('[WP Debug] Switched to tab: ' + tabName);
+ }
+ },
+
+ /**
+ * Save state to localStorage
+ */
+ saveState: function(key, value) {
+ if (typeof Storage !== 'undefined') {
+ try {
+ localStorage.setItem('wpDebugPanel_' + key, JSON.stringify(value));
+ } catch (e) {
+ // Ignore storage errors
+ }
+ }
+ },
+
+ /**
+ * Get state from localStorage
+ */
+ getState: function(key, defaultValue) {
+ if (typeof Storage !== 'undefined') {
+ try {
+ const value = localStorage.getItem('wpDebugPanel_' + key);
+ return value !== null ? JSON.parse(value) : defaultValue;
+ } catch (e) {
+ return defaultValue;
+ }
+ }
+ return defaultValue;
+ },
+
+ /**
+ * Restore panel state
+ */
+ restoreState: function() {
+ // Restore visibility
+ const isVisible = this.getState('visible', false);
+ if (isVisible) {
+ this.panel.show().attr('data-visible', 'true');
+ }
+
+ // Restore active tab
+ const activeTab = this.getState('activeTab', 'overview');
+ this.switchTab(activeTab);
+ },
+
+ /**
+ * Refresh panel data (for future AJAX updates)
+ */
+ refresh: function() {
+ if (window.console && console.log) {
+ console.log('[WP Debug] Refreshing panel data...');
+ }
+
+ // This could be extended to fetch fresh data via AJAX
+ // For now, it just logs
+ if (typeof wpDebugData !== 'undefined' && wpDebugData.ajaxUrl) {
+ $.ajax({
+ url: wpDebugData.ajaxUrl,
+ type: 'POST',
+ data: {
+ action: 'wp_debug_refresh',
+ nonce: wpDebugData.nonce
+ },
+ success: function(response) {
+ if (window.console && console.log) {
+ console.log('[WP Debug] Panel data refreshed', response);
+ }
+ },
+ error: function(xhr, status, error) {
+ if (window.console && console.error) {
+ console.error('[WP Debug] Refresh failed:', error);
+ }
+ }
+ });
+ }
+ }
+ };
+
+ /**
+ * Initialize on document ready
+ */
+ $(document).ready(function() {
+ WPDebugPanel.init();
+ });
+
+ /**
+ * Expose to window for external access
+ */
+ window.WPDebugPanel = WPDebugPanel;
+
+})(jQuery);
diff --git a/wp-content/plugins/wp-debug/inc/activation.php b/wp-content/plugins/wp-debug/inc/activation.php
new file mode 100644
index 00000000..3b96e5ba
--- /dev/null
+++ b/wp-content/plugins/wp-debug/inc/activation.php
@@ -0,0 +1,145 @@
+1000ms
+
+ // Frontend panel settings
+ add_option('wp_debug_panel_enabled', true);
+ add_option('wp_debug_panel_position', 'bottom-right'); // bottom-right, bottom-left, top-right, top-left
+
+ // Query analyzer settings
+ add_option('wp_debug_query_threshold', 100); // Log queries taking >100ms
+ add_option('wp_debug_detect_n_plus_one', true);
+ }
+
+ /**
+ * Create logs directory
+ */
+ private static function create_logs_directory() {
+ $logs_dir = WP_CONTENT_DIR . '/logs/wp-debug';
+
+ if (!file_exists($logs_dir)) {
+ wp_mkdir_p($logs_dir);
+
+ // Create .htaccess to protect logs
+ $htaccess_file = $logs_dir . '/.htaccess';
+ $htaccess_content = "Order deny,allow\nDeny from all";
+ file_put_contents($htaccess_file, $htaccess_content);
+
+ // Create index.php to prevent directory listing
+ $index_file = $logs_dir . '/index.php';
+ $index_content = "get_charset_collate();
+
+ // Logs table
+ $table_logs = $wpdb->prefix . 'wp_debug_logs';
+ $sql_logs = "CREATE TABLE IF NOT EXISTS $table_logs (
+ id bigint(20) NOT NULL AUTO_INCREMENT,
+ timestamp datetime DEFAULT CURRENT_TIMESTAMP NOT NULL,
+ level varchar(20) DEFAULT 'INFO' NOT NULL,
+ message text NOT NULL,
+ context text,
+ user_id bigint(20),
+ url varchar(255),
+ ip_address varchar(45),
+ PRIMARY KEY (id),
+ KEY level (level),
+ KEY timestamp (timestamp),
+ KEY user_id (user_id)
+ ) $charset_collate;";
+
+ // Hooks table
+ $table_hooks = $wpdb->prefix . 'wp_debug_hooks';
+ $sql_hooks = "CREATE TABLE IF NOT EXISTS $table_hooks (
+ id bigint(20) NOT NULL AUTO_INCREMENT,
+ timestamp datetime DEFAULT CURRENT_TIMESTAMP NOT NULL,
+ hook_name varchar(255) NOT NULL,
+ hook_type varchar(20) NOT NULL,
+ execution_time float,
+ args_count int,
+ PRIMARY KEY (id),
+ KEY hook_name (hook_name),
+ KEY timestamp (timestamp),
+ KEY hook_type (hook_type)
+ ) $charset_collate;";
+
+ require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
+ dbDelta($sql_logs);
+ dbDelta($sql_hooks);
+ }
+}
diff --git a/wp-content/plugins/wp-debug/inc/admin-menu.php b/wp-content/plugins/wp-debug/inc/admin-menu.php
new file mode 100644
index 00000000..47d931f7
--- /dev/null
+++ b/wp-content/plugins/wp-debug/inc/admin-menu.php
@@ -0,0 +1,224 @@
+
+
+
+
+
+
+
+
+
+
+
+
+ ✓ Activo' : '✗ Inactivo'; ?>
+
+
+
+
+
+
+
+
+
+
+
+ get('Name')); ?>
+
+
+
+
+
+
+ $module_data) {
+ $enabled = get_option('wp_debug_module_' . $module_name, $module_data['enabled']);
+ $status_class = $enabled ? 'status-active' : 'status-inactive';
+ $status_text = $enabled ? '✓ Activo' : '✗ Inactivo';
+ echo '
' . esc_html(ucwords(str_replace('_', ' ', $module_name))) . ': ' . esc_html($status_text) . '
';
+ }
+ ?>
+
+
+
+
+
+
+
+
+
wp debug status
+
wp debug hooks
+
wp debug templates
+
wp debug export
+
wp debug clear
+
+
+
+
+
+
+
+
+ - ✓
+ - ✓
+ - ✓
+ - ✓
+ - ✓
+ - ✓
+ - ✓
+
+
+
+
+
+ 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']),
+ ));
+ }
+ }
+}
diff --git a/wp-content/plugins/wp-debug/inc/autoloader.php b/wp-content/plugins/wp-debug/inc/autoloader.php
new file mode 100644
index 00000000..0e7b024e
--- /dev/null
+++ b/wp-content/plugins/wp-debug/inc/autoloader.php
@@ -0,0 +1,154 @@
+ array(
+ 'file' => 'logger.php',
+ 'class' => 'WP_Debug_Logger',
+ 'enabled' => true,
+ ),
+ 'hook_monitor' => array(
+ 'file' => 'hook-monitor.php',
+ 'class' => 'WP_Debug_Hook_Monitor',
+ 'enabled' => true,
+ ),
+ 'template_tracer' => array(
+ 'file' => 'template-tracer.php',
+ 'class' => 'WP_Debug_Template_Tracer',
+ 'enabled' => true,
+ ),
+ 'asset_tracker' => array(
+ 'file' => 'asset-tracker.php',
+ 'class' => 'WP_Debug_Asset_Tracker',
+ 'enabled' => true,
+ ),
+ 'profiler' => array(
+ 'file' => 'profiler.php',
+ 'class' => 'WP_Debug_Profiler',
+ 'enabled' => true,
+ ),
+ 'query_analyzer' => array(
+ 'file' => 'query-analyzer.php',
+ 'class' => 'WP_Debug_Query_Analyzer',
+ 'enabled' => true,
+ ),
+ 'frontend_panel' => array(
+ 'file' => 'frontend-panel.php',
+ 'class' => 'WP_Debug_Frontend_Panel',
+ 'enabled' => true,
+ ),
+ 'cli_commands' => array(
+ 'file' => 'cli-commands.php',
+ 'class' => 'WP_Debug_CLI_Commands',
+ 'enabled' => true,
+ ),
+ );
+
+ /**
+ * Initialize autoloader
+ */
+ public static function init() {
+ // Check if debug is enabled globally
+ $debug_enabled = get_option('wp_debug_enabled', true);
+
+ if (!$debug_enabled) {
+ return;
+ }
+
+ // Load each module
+ foreach (self::$modules as $module_name => $module_data) {
+ self::load_module($module_name, $module_data);
+ }
+ }
+
+ /**
+ * Load a single module
+ *
+ * @param string $module_name Module name
+ * @param array $module_data Module data
+ */
+ private static function load_module($module_name, $module_data) {
+ // Check if module is enabled
+ $module_enabled = get_option('wp_debug_module_' . $module_name, $module_data['enabled']);
+
+ if (!$module_enabled) {
+ return;
+ }
+
+ // Build file path
+ $file_path = WP_DEBUG_PLUGIN_DIR . 'inc/' . $module_data['file'];
+
+ // Check if file exists
+ if (!file_exists($file_path)) {
+ if (WP_DEBUG) {
+ error_log(sprintf('WP Debug: Module file not found: %s', $file_path));
+ }
+ return;
+ }
+
+ // Load the file
+ require_once $file_path;
+
+ // Initialize the class if it has an init method
+ if (class_exists($module_data['class']) && method_exists($module_data['class'], 'init')) {
+ call_user_func(array($module_data['class'], 'init'));
+ }
+ }
+
+ /**
+ * Get list of available modules
+ *
+ * @return array List of modules
+ */
+ public static function get_modules() {
+ return self::$modules;
+ }
+
+ /**
+ * Enable a module
+ *
+ * @param string $module_name Module name
+ * @return bool True on success
+ */
+ public static function enable_module($module_name) {
+ if (!isset(self::$modules[$module_name])) {
+ return false;
+ }
+
+ return update_option('wp_debug_module_' . $module_name, true);
+ }
+
+ /**
+ * Disable a module
+ *
+ * @param string $module_name Module name
+ * @return bool True on success
+ */
+ public static function disable_module($module_name) {
+ if (!isset(self::$modules[$module_name])) {
+ return false;
+ }
+
+ return update_option('wp_debug_module_' . $module_name, false);
+ }
+}
diff --git a/wp-content/plugins/wp-debug/inc/cli-commands.php b/wp-content/plugins/wp-debug/inc/cli-commands.php
new file mode 100644
index 00000000..9e06e155
--- /dev/null
+++ b/wp-content/plugins/wp-debug/inc/cli-commands.php
@@ -0,0 +1,543 @@
+ $module_data) {
+ $module_enabled = get_option('wp_debug_module_' . $module_name, $module_data['enabled']);
+ $class_exists = class_exists($module_data['class']);
+
+ $items[] = array(
+ 'module' => $module_name,
+ 'status' => $module_enabled ? 'enabled' : 'disabled',
+ 'loaded' => $class_exists ? 'yes' : 'no',
+ 'class' => $module_data['class'],
+ );
+ }
+
+ WP_CLI\Utils\format_items('table', $items, array('module', 'status', 'loaded', 'class'));
+
+ // Database info
+ global $wpdb;
+ $table = $wpdb->prefix . 'wp_debug_logs';
+ $count = $wpdb->get_var("SELECT COUNT(*) FROM {$table}");
+
+ WP_CLI::log('');
+ WP_CLI::log('Database Logs: ' . number_format($count) . ' entries');
+
+ // File logs
+ $logs_dir = WP_CONTENT_DIR . '/logs/wp-debug';
+ if (file_exists($logs_dir)) {
+ $files = glob($logs_dir . '/debug-*.log');
+ WP_CLI::log('Log Files: ' . count($files));
+ }
+
+ WP_CLI::success('Status check complete');
+ }
+
+ /**
+ * List hooks executed
+ *
+ * ## OPTIONS
+ *
+ * [--search=]
+ * : Filter hooks by pattern
+ *
+ * [--limit=]
+ * : Limit number of results
+ * ---
+ * default: 50
+ * ---
+ *
+ * [--format=]
+ * : Output format
+ * ---
+ * default: table
+ * options:
+ * - table
+ * - json
+ * - csv
+ * ---
+ *
+ * ## EXAMPLES
+ *
+ * wp debug hooks
+ * wp debug hooks --search=init
+ * wp debug hooks --limit=100 --format=json
+ *
+ * @when after_wp_load
+ */
+ public function hooks($args, $assoc_args) {
+ if (!class_exists('WP_Debug_Hook_Monitor')) {
+ WP_CLI::error('Hook Monitor module is not loaded');
+ return;
+ }
+
+ $search = isset($assoc_args['search']) ? $assoc_args['search'] : '';
+ $limit = isset($assoc_args['limit']) ? intval($assoc_args['limit']) : 50;
+ $format = isset($assoc_args['format']) ? $assoc_args['format'] : 'table';
+
+ // Get hooks from transient or current execution
+ $transient_data = get_transient('wp_debug_hooks_data');
+
+ if (!$transient_data) {
+ WP_CLI::warning('No hook data available. Run a page request first.');
+ return;
+ }
+
+ $hooks = $transient_data['hooks'];
+
+ // Filter by search
+ if ($search) {
+ $hooks = array_filter($hooks, function($hook) use ($search) {
+ return strpos($hook['hook'], $search) !== false;
+ });
+ WP_CLI::log('Filtered by: ' . $search);
+ }
+
+ // Limit results
+ if ($limit > 0) {
+ $hooks = array_slice($hooks, 0, $limit);
+ }
+
+ if (empty($hooks)) {
+ WP_CLI::warning('No hooks found');
+ return;
+ }
+
+ // Prepare items for display
+ $items = array();
+ foreach ($hooks as $hook) {
+ $items[] = array(
+ 'hook' => $hook['hook'],
+ 'type' => $hook['type'],
+ 'caller' => isset($hook['caller']) ? $hook['caller'] : 'unknown',
+ 'args' => isset($hook['args_count']) ? $hook['args_count'] : 0,
+ );
+ }
+
+ WP_CLI\Utils\format_items($format, $items, array('hook', 'type', 'caller', 'args'));
+
+ WP_CLI::log('');
+ WP_CLI::log('Total hooks: ' . $transient_data['total']);
+ WP_CLI::log('Unique hooks: ' . $transient_data['unique']);
+ WP_CLI::success('Retrieved ' . count($items) . ' hooks');
+ }
+
+ /**
+ * List templates loaded
+ *
+ * ## OPTIONS
+ *
+ * [--format=]
+ * : Output format
+ * ---
+ * default: table
+ * options:
+ * - table
+ * - json
+ * - csv
+ * ---
+ *
+ * [--parts]
+ * : Show template parts instead of main templates
+ *
+ * [--missing]
+ * : Show only missing template parts
+ *
+ * ## EXAMPLES
+ *
+ * wp debug templates
+ * wp debug templates --parts
+ * wp debug templates --missing
+ *
+ * @when after_wp_load
+ */
+ public function templates($args, $assoc_args) {
+ if (!class_exists('WP_Debug_Template_Tracer')) {
+ WP_CLI::error('Template Tracer module is not loaded');
+ return;
+ }
+
+ $format = isset($assoc_args['format']) ? $assoc_args['format'] : 'table';
+ $show_parts = isset($assoc_args['parts']);
+ $show_missing = isset($assoc_args['missing']);
+
+ // Get templates from transient
+ $transient_data = get_transient('wp_debug_templates_data');
+
+ if (!$transient_data) {
+ WP_CLI::warning('No template data available. Run a page request first.');
+ return;
+ }
+
+ if ($show_missing) {
+ // Show missing template parts
+ $items = array();
+ foreach ($transient_data['missing'] as $part) {
+ $items[] = array(
+ 'slug' => $part['slug'],
+ 'name' => $part['name'] ? $part['name'] : 'N/A',
+ 'file' => $part['file'],
+ );
+ }
+
+ if (empty($items)) {
+ WP_CLI::success('No missing templates found');
+ return;
+ }
+
+ WP_CLI\Utils\format_items($format, $items, array('slug', 'name', 'file'));
+ WP_CLI::warning('Found ' . count($items) . ' missing templates');
+
+ } elseif ($show_parts) {
+ // Show template parts
+ $items = array();
+ foreach ($transient_data['parts'] as $part) {
+ $items[] = array(
+ 'slug' => $part['slug'],
+ 'name' => $part['name'] ? $part['name'] : 'N/A',
+ 'file' => $part['file'],
+ 'found' => $part['found'] ? 'yes' : 'no',
+ );
+ }
+
+ if (empty($items)) {
+ WP_CLI::warning('No template parts found');
+ return;
+ }
+
+ WP_CLI\Utils\format_items($format, $items, array('slug', 'name', 'file', 'found'));
+ WP_CLI::success('Retrieved ' . count($items) . ' template parts');
+
+ } else {
+ // Show main templates
+ $items = array();
+ foreach ($transient_data['templates'] as $template) {
+ $items[] = array(
+ 'name' => $template['name'],
+ 'type' => $template['type'],
+ 'path' => $template['path'],
+ );
+ }
+
+ if (empty($items)) {
+ WP_CLI::warning('No templates found');
+ return;
+ }
+
+ WP_CLI\Utils\format_items($format, $items, array('name', 'type', 'path'));
+ WP_CLI::success('Retrieved ' . count($items) . ' templates');
+ }
+ }
+
+ /**
+ * Show performance metrics
+ *
+ * ## OPTIONS
+ *
+ * [--format=]
+ * : Output format
+ * ---
+ * default: table
+ * options:
+ * - table
+ * - json
+ * ---
+ *
+ * ## EXAMPLES
+ *
+ * wp debug performance
+ * wp debug performance --format=json
+ *
+ * @when after_wp_load
+ */
+ public function performance($args, $assoc_args) {
+ if (!class_exists('WP_Debug_Profiler')) {
+ WP_CLI::error('Profiler module is not loaded');
+ return;
+ }
+
+ $format = isset($assoc_args['format']) ? $assoc_args['format'] : 'table';
+
+ // Get performance report
+ $report = WP_Debug_Profiler::get_performance_report();
+
+ if (isset($report['error'])) {
+ WP_CLI::warning($report['error']);
+ return;
+ }
+
+ if ($format === 'json') {
+ WP_CLI::log(json_encode($report, JSON_PRETTY_PRINT));
+ return;
+ }
+
+ WP_CLI::log('Performance Metrics');
+ WP_CLI::log('===================');
+ WP_CLI::log('');
+
+ // Display formatted metrics
+ WP_CLI::log('Execution Time: ' . $report['formatted']['execution_time']);
+ WP_CLI::log('Memory Current: ' . $report['formatted']['memory_current']);
+ WP_CLI::log('Memory Peak: ' . $report['formatted']['memory_peak']);
+ if ($report['formatted']['server_load']) {
+ WP_CLI::log('Server Load: ' . $report['formatted']['server_load']);
+ }
+ WP_CLI::log('');
+
+ // Display timers if available
+ if (!empty($report['metrics']['timers'])) {
+ WP_CLI::log('Function Timers:');
+ WP_CLI::log('----------------');
+
+ $items = array();
+ foreach ($report['metrics']['timers'] as $name => $timer) {
+ $items[] = array(
+ 'name' => $name,
+ 'time' => round($timer['elapsed'] * 1000, 2) . 'ms',
+ 'memory' => WP_Debug_Profiler::format_bytes($timer['memory_used']),
+ );
+ }
+
+ WP_CLI\Utils\format_items('table', $items, array('name', 'time', 'memory'));
+ WP_CLI::log('');
+ }
+
+ // Display suggestions
+ if (!empty($report['suggestions'])) {
+ WP_CLI::log('Suggestions:');
+ WP_CLI::log('------------');
+ foreach ($report['suggestions'] as $suggestion) {
+ WP_CLI::warning($suggestion);
+ }
+ } else {
+ WP_CLI::success('Performance looks good!');
+ }
+ }
+
+ /**
+ * Export complete debug report in JSON
+ *
+ * ## OPTIONS
+ *
+ * []
+ * : Output file path (optional, defaults to stdout)
+ *
+ * ## EXAMPLES
+ *
+ * wp debug export
+ * wp debug export report.json
+ *
+ * @when after_wp_load
+ */
+ public function export($args, $assoc_args) {
+ WP_CLI::log('Generating debug report...');
+
+ // Collect all data
+ $report = array(
+ 'timestamp' => current_time('mysql'),
+ 'site_url' => get_site_url(),
+ 'wp_version' => get_bloginfo('version'),
+ 'plugin_version' => WP_DEBUG_VERSION,
+ );
+
+ // Add hooks data
+ if (class_exists('WP_Debug_Hook_Monitor')) {
+ $hooks_data = get_transient('wp_debug_hooks_data');
+ if ($hooks_data) {
+ $report['hooks'] = array(
+ 'total' => $hooks_data['total'],
+ 'unique' => $hooks_data['unique'],
+ 'top_10' => array_slice($hooks_data['counts'], 0, 10, true),
+ );
+ }
+ }
+
+ // Add templates data
+ if (class_exists('WP_Debug_Template_Tracer')) {
+ $templates_data = get_transient('wp_debug_templates_data');
+ if ($templates_data) {
+ $report['templates'] = array(
+ 'templates' => $templates_data['templates'],
+ 'parts_count' => count($templates_data['parts']),
+ 'missing_count' => count($templates_data['missing']),
+ );
+ }
+ }
+
+ // Add performance data
+ if (class_exists('WP_Debug_Profiler')) {
+ $perf_report = WP_Debug_Profiler::get_performance_report();
+ if (!isset($perf_report['error'])) {
+ $report['performance'] = $perf_report;
+ }
+ }
+
+ // Add recent logs
+ if (class_exists('WP_Debug_Logger')) {
+ $logs = WP_Debug_Logger::get_logs(array('limit' => 50));
+ $report['recent_logs'] = $logs;
+ }
+
+ // Add module status
+ $modules = WP_Debug_Autoloader::get_modules();
+ $modules_status = array();
+ foreach ($modules as $module_name => $module_data) {
+ $modules_status[$module_name] = array(
+ 'enabled' => get_option('wp_debug_module_' . $module_name, $module_data['enabled']),
+ 'loaded' => class_exists($module_data['class']),
+ );
+ }
+ $report['modules'] = $modules_status;
+
+ // Convert to JSON
+ $json = json_encode($report, JSON_PRETTY_PRINT);
+
+ // Output to file or stdout
+ if (isset($args[0])) {
+ $file = $args[0];
+ $result = file_put_contents($file, $json);
+
+ if ($result === false) {
+ WP_CLI::error('Failed to write to file: ' . $file);
+ } else {
+ WP_CLI::success('Report exported to: ' . $file);
+ }
+ } else {
+ WP_CLI::log($json);
+ WP_CLI::success('Report generated successfully');
+ }
+ }
+
+ /**
+ * Clear old logs
+ *
+ * ## OPTIONS
+ *
+ * [--days=]
+ * : Number of days to keep logs
+ * ---
+ * default: 7
+ * ---
+ *
+ * [--yes]
+ * : Skip confirmation
+ *
+ * ## EXAMPLES
+ *
+ * wp debug clear
+ * wp debug clear --days=30
+ * wp debug clear --yes
+ *
+ * @when after_wp_load
+ */
+ public function clear($args, $assoc_args) {
+ if (!class_exists('WP_Debug_Logger')) {
+ WP_CLI::error('Logger module is not loaded');
+ return;
+ }
+
+ $days = isset($assoc_args['days']) ? intval($assoc_args['days']) : 7;
+ $yes = isset($assoc_args['yes']);
+
+ // Count logs to be deleted
+ global $wpdb;
+ $table = $wpdb->prefix . 'wp_debug_logs';
+ $date = date('Y-m-d H:i:s', strtotime("-{$days} days"));
+ $count = $wpdb->get_var($wpdb->prepare("SELECT COUNT(*) FROM {$table} WHERE timestamp < %s", $date));
+
+ // Count log files to be deleted
+ $logs_dir = WP_CONTENT_DIR . '/logs/wp-debug';
+ $files_count = 0;
+ if (file_exists($logs_dir)) {
+ $files = glob($logs_dir . '/debug-*.log');
+ $cutoff = strtotime("-{$days} days");
+ foreach ($files as $file) {
+ if (filemtime($file) < $cutoff) {
+ $files_count++;
+ }
+ }
+ }
+
+ WP_CLI::log("Logs older than {$days} days:");
+ WP_CLI::log(" Database entries: {$count}");
+ WP_CLI::log(" Log files: {$files_count}");
+
+ if ($count == 0 && $files_count == 0) {
+ WP_CLI::success('No old logs to clear');
+ return;
+ }
+
+ // Ask for confirmation
+ if (!$yes) {
+ WP_CLI::confirm('Are you sure you want to delete these logs?');
+ }
+
+ // Clear logs
+ WP_CLI::log('Clearing logs...');
+ WP_Debug_Logger::clear_old_logs($days);
+
+ WP_CLI::success("Cleared {$count} database entries and {$files_count} log files");
+ }
+}
diff --git a/wp-content/plugins/wp-debug/inc/deactivation.php b/wp-content/plugins/wp-debug/inc/deactivation.php
new file mode 100644
index 00000000..26c0b37b
--- /dev/null
+++ b/wp-content/plugins/wp-debug/inc/deactivation.php
@@ -0,0 +1,47 @@
+ WP Debug if desired
+ }
+
+ /**
+ * Clean up temporary transients
+ */
+ private static function cleanup_transients() {
+ // Delete temporary transients only
+ delete_transient('wp_debug_temp_data');
+ delete_transient('wp_debug_cache');
+
+ // Clean up expired transients
+ global $wpdb;
+ $wpdb->query("DELETE FROM {$wpdb->options} WHERE option_name LIKE '_transient_timeout_wp_debug_%' AND option_value < UNIX_TIMESTAMP()");
+ $wpdb->query("DELETE FROM {$wpdb->options} WHERE option_name LIKE '_transient_wp_debug_%' AND option_name NOT IN (SELECT CONCAT('_transient_', SUBSTRING(option_name, 19)) FROM {$wpdb->options} WHERE option_name LIKE '_transient_timeout_wp_debug_%')");
+ }
+}
diff --git a/wp-content/plugins/wp-debug/inc/frontend-panel.php b/wp-content/plugins/wp-debug/inc/frontend-panel.php
new file mode 100644
index 00000000..fb492753
--- /dev/null
+++ b/wp-content/plugins/wp-debug/inc/frontend-panel.php
@@ -0,0 +1,373 @@
+ admin_url('admin-ajax.php'),
+ 'nonce' => wp_create_nonce('wp_debug_panel'),
+ ));
+ }
+
+ /**
+ * Render panel
+ */
+ public static function render_panel() {
+ // Load data from transients
+ $metrics = get_transient('wp_debug_last_metrics');
+ $hooks_data = get_transient('wp_debug_hooks_data');
+ $templates_data = get_transient('wp_debug_templates_data');
+ $assets_data = get_transient('wp_debug_assets_data');
+ $queries_data = get_transient('wp_debug_queries_data');
+
+ ?>
+
+
+
+
+
+
+
System Overview
+
+
+ | WordPress Version: |
+ |
+
+
+ | PHP Version: |
+ |
+
+
+ | Active Theme: |
+ get('Name'); ?> |
+
+
+ | Memory Usage: |
+ / peak |
+
+
+ | Execution Time: |
+ s |
+
+
+
+
Module Status
+
+ 'Logger',
+ 'profiler' => 'Profiler',
+ 'hook_monitor' => 'Hook Monitor',
+ 'template_tracer' => 'Template Tracer',
+ 'asset_tracker' => 'Asset Tracker',
+ 'query_analyzer' => 'Query Analyzer',
+ );
+
+ foreach ($modules as $module_key => $module_name) {
+ $enabled = get_option('wp_debug_module_' . $module_key, true);
+ $status = $enabled ? 'Active' : 'Inactive';
+ echo "- {$module_name}: {$status}
";
+ }
+ ?>
+
+
+
+
+
+
Performance Metrics
+
+
+
+ | Execution Time: |
+ s |
+
+
+ | Memory Peak: |
+ |
+
+
+ | Memory Current: |
+ |
+
+
+
+ | Server Load: |
+ |
+
+
+
+
+
+
Custom Timers
+
+
+
+ | Timer |
+ Elapsed |
+ Memory Used |
+
+
+
+ $timer): ?>
+
+ |
+ s |
+ |
+
+
+
+
+
+
+
No performance data available.
+
+
+
+
+
+
WordPress Hooks
+
+
Total Hooks: | Unique:
+
+
Most Frequent Hooks
+
+
+
+ | Hook Name |
+ Count |
+
+
+
+ $count):
+ ?>
+
+ |
+ |
+
+
+
+
+
+
No hooks data available.
+
+
+
+
+
+
Loaded Templates
+
+
+
Main Templates
+
+
+
+
+
Template Parts ()
+
+
+ -
+
+
+ Not Found
+
+
+
+
+
+
+
+
Missing Templates
+
+
+
+
No template data available.
+
+
+
+
+
+
Enqueued Assets
+
+
+
+
+ | Total Assets: |
+ |
+
+
+ | Styles: |
+ |
+
+
+ | Scripts: |
+ |
+
+
+ | Total Size: |
+ |
+
+
+ | Duplicates: |
+ |
+
+
+
+
+
+
Duplicate Assets
+
+
+
+
No assets data available.
+
+
+
+
+
+
Database Queries
+
+
+
+ | Total Queries: |
+ |
+
+
+ | Total Time: |
+ s |
+
+
+ | Slow Queries: |
+ |
+
+
+ | N+1 Patterns: |
+ |
+
+
+
+
+
Slow Queries
+
+
+
+
+
N+1 Query Patterns
+
+
+
+
No query data available. Make sure SAVEQUERIES is enabled.
+
+
+
+
+
+
+
+ $hook,
+ 'type' => current_filter() === $hook ? 'action' : 'filter',
+ 'timestamp' => microtime(true),
+ 'caller' => $caller,
+ 'args_count' => func_num_args() - 1,
+ );
+
+ self::$hooks_executed[] = $hook_info;
+
+ // Count executions
+ if (!isset(self::$hook_counts[$hook])) {
+ self::$hook_counts[$hook] = 0;
+ }
+ self::$hook_counts[$hook]++;
+ }
+
+ /**
+ * Get executed hooks
+ *
+ * @param array $args Query arguments
+ * @return array Hooks
+ */
+ public static function get_hooks($args = array()) {
+ $defaults = array(
+ 'search' => '',
+ 'limit' => 100,
+ );
+
+ $args = wp_parse_args($args, $defaults);
+
+ $hooks = self::$hooks_executed;
+
+ // Filter by search
+ if ($args['search']) {
+ $hooks = array_filter($hooks, function($hook) use ($args) {
+ return strpos($hook['hook'], $args['search']) !== false;
+ });
+ }
+
+ // Limit results
+ if ($args['limit'] > 0) {
+ $hooks = array_slice($hooks, 0, $args['limit']);
+ }
+
+ return $hooks;
+ }
+
+ /**
+ * Get hook counts
+ *
+ * @return array Hook counts
+ */
+ public static function get_hook_counts() {
+ // Sort by count descending
+ arsort(self::$hook_counts);
+ return self::$hook_counts;
+ }
+
+ /**
+ * Get hooks called multiple times
+ *
+ * @param int $threshold Minimum count
+ * @return array Hooks
+ */
+ public static function get_frequent_hooks($threshold = 10) {
+ return array_filter(self::$hook_counts, function($count) use ($threshold) {
+ return $count >= $threshold;
+ });
+ }
+
+ /**
+ * Record hooks on shutdown
+ */
+ public static function record_hooks() {
+ $total_hooks = count(self::$hooks_executed);
+ $unique_hooks = count(self::$hook_counts);
+
+ // Log summary
+ if (class_exists('WP_Debug_Logger')) {
+ WP_Debug_Logger::debug("Hooks executed", array(
+ 'total' => $total_hooks,
+ 'unique' => $unique_hooks,
+ 'top_5' => array_slice(self::$hook_counts, 0, 5, true),
+ ));
+ }
+
+ // Store for frontend panel
+ set_transient('wp_debug_hooks_data', array(
+ 'total' => $total_hooks,
+ 'unique' => $unique_hooks,
+ 'hooks' => self::$hooks_executed,
+ 'counts' => self::$hook_counts,
+ ), 3600);
+ }
+
+ /**
+ * Check if hook was executed
+ *
+ * @param string $hook Hook name
+ * @return bool True if executed
+ */
+ public static function was_hook_executed($hook) {
+ return isset(self::$hook_counts[$hook]);
+ }
+
+ /**
+ * Get hook execution time
+ *
+ * @param string $hook Hook name
+ * @return float|null Execution time in seconds
+ */
+ public static function get_hook_execution_time($hook) {
+ foreach (self::$hooks_executed as $hook_info) {
+ if ($hook_info['hook'] === $hook && isset($hook_info['execution_time'])) {
+ return $hook_info['execution_time'];
+ }
+ }
+ return null;
+ }
+}
diff --git a/wp-content/plugins/wp-debug/inc/logger.php b/wp-content/plugins/wp-debug/inc/logger.php
new file mode 100644
index 00000000..5c26c0e4
--- /dev/null
+++ b/wp-content/plugins/wp-debug/inc/logger.php
@@ -0,0 +1,335 @@
+ 0,
+ self::LEVEL_INFO => 1,
+ self::LEVEL_WARNING => 2,
+ self::LEVEL_ERROR => 3,
+ );
+
+ return $levels[$level] >= $levels[$min_level];
+ }
+
+ /**
+ * Prepare log entry with all context
+ *
+ * @param string $message Message
+ * @param string $level Level
+ * @param array $context Context
+ * @return array Log entry
+ */
+ private static function prepare_log_entry($message, $level, $context) {
+ $user = wp_get_current_user();
+
+ return array(
+ 'timestamp' => current_time('mysql'),
+ 'level' => $level,
+ 'message' => $message,
+ 'context' => !empty($context) ? json_encode($context) : null,
+ 'user_id' => $user->ID,
+ 'url' => isset($_SERVER['REQUEST_URI']) ? esc_url_raw($_SERVER['REQUEST_URI']) : '',
+ 'ip_address' => self::get_client_ip(),
+ 'backtrace' => self::get_backtrace(),
+ );
+ }
+
+ /**
+ * Log to file
+ *
+ * @param array $log_entry Log entry
+ * @return bool True on success
+ */
+ private static function log_to_file($log_entry) {
+ $logs_dir = WP_CONTENT_DIR . '/logs/wp-debug';
+ $log_file = $logs_dir . '/debug-' . date('Y-m-d') . '.log';
+
+ // Ensure directory exists
+ if (!file_exists($logs_dir)) {
+ wp_mkdir_p($logs_dir);
+ }
+
+ // Prepare log line
+ $log_line = sprintf(
+ "[%s] [%s] %s | User: %s | URL: %s | IP: %s\n",
+ $log_entry['timestamp'],
+ $log_entry['level'],
+ $log_entry['message'],
+ $log_entry['user_id'],
+ $log_entry['url'],
+ $log_entry['ip_address']
+ );
+
+ // Add context if exists
+ if ($log_entry['context']) {
+ $log_line .= sprintf(" Context: %s\n", $log_entry['context']);
+ }
+
+ // Add backtrace if exists
+ if ($log_entry['backtrace']) {
+ $log_line .= sprintf(" Backtrace: %s\n", $log_entry['backtrace']);
+ }
+
+ // Write to file
+ return file_put_contents($log_file, $log_line, FILE_APPEND | LOCK_EX) !== false;
+ }
+
+ /**
+ * Log to database
+ *
+ * @param array $log_entry Log entry
+ * @return bool True on success
+ */
+ private static function log_to_database($log_entry) {
+ global $wpdb;
+
+ $table = $wpdb->prefix . 'wp_debug_logs';
+
+ return $wpdb->insert(
+ $table,
+ array(
+ 'timestamp' => $log_entry['timestamp'],
+ 'level' => $log_entry['level'],
+ 'message' => $log_entry['message'],
+ 'context' => $log_entry['context'],
+ 'user_id' => $log_entry['user_id'],
+ 'url' => $log_entry['url'],
+ 'ip_address' => $log_entry['ip_address'],
+ ),
+ array('%s', '%s', '%s', '%s', '%d', '%s', '%s')
+ ) !== false;
+ }
+
+ /**
+ * Get client IP address
+ *
+ * @return string IP address
+ */
+ private static function get_client_ip() {
+ $ip = '';
+
+ if (!empty($_SERVER['HTTP_CLIENT_IP'])) {
+ $ip = $_SERVER['HTTP_CLIENT_IP'];
+ } elseif (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
+ $ip = $_SERVER['HTTP_X_FORWARDED_FOR'];
+ } elseif (!empty($_SERVER['REMOTE_ADDR'])) {
+ $ip = $_SERVER['REMOTE_ADDR'];
+ }
+
+ return sanitize_text_field($ip);
+ }
+
+ /**
+ * Get backtrace
+ *
+ * @return string Backtrace string
+ */
+ private static function get_backtrace() {
+ $backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 5);
+ $trace_string = '';
+
+ foreach ($backtrace as $index => $trace) {
+ if (isset($trace['file']) && isset($trace['line'])) {
+ $trace_string .= sprintf(
+ "%s:%s",
+ basename($trace['file']),
+ $trace['line']
+ );
+ if ($index < count($backtrace) - 1) {
+ $trace_string .= ' -> ';
+ }
+ }
+ }
+
+ return $trace_string;
+ }
+
+ /**
+ * Get logs from database
+ *
+ * @param array $args Query arguments
+ * @return array Logs
+ */
+ public static function get_logs($args = array()) {
+ global $wpdb;
+
+ $defaults = array(
+ 'level' => null,
+ 'limit' => 100,
+ 'offset' => 0,
+ 'orderby' => 'timestamp',
+ 'order' => 'DESC',
+ );
+
+ $args = wp_parse_args($args, $defaults);
+
+ $table = $wpdb->prefix . 'wp_debug_logs';
+ $where = '1=1';
+
+ if ($args['level']) {
+ $where .= $wpdb->prepare(' AND level = %s', $args['level']);
+ }
+
+ $query = $wpdb->prepare(
+ "SELECT * FROM {$table} WHERE {$where} ORDER BY {$args['orderby']} {$args['order']} LIMIT %d OFFSET %d",
+ $args['limit'],
+ $args['offset']
+ );
+
+ return $wpdb->get_results($query);
+ }
+
+ /**
+ * Clear old logs
+ *
+ * @param int $days Days to keep
+ * @return bool True on success
+ */
+ public static function clear_old_logs($days = null) {
+ if ($days === null) {
+ $days = get_option('wp_debug_log_retention_days', 7);
+ }
+
+ // Clear database logs
+ global $wpdb;
+ $table = $wpdb->prefix . 'wp_debug_logs';
+ $date = date('Y-m-d H:i:s', strtotime("-{$days} days"));
+
+ $wpdb->query($wpdb->prepare("DELETE FROM {$table} WHERE timestamp < %s", $date));
+
+ // Clear file logs
+ $logs_dir = WP_CONTENT_DIR . '/logs/wp-debug';
+ if (file_exists($logs_dir)) {
+ $files = glob($logs_dir . '/debug-*.log');
+ $cutoff = strtotime("-{$days} days");
+
+ foreach ($files as $file) {
+ if (filemtime($file) < $cutoff) {
+ unlink($file);
+ }
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Close file handle on shutdown
+ */
+ public static function close_file_handle() {
+ if (self::$file_handle) {
+ fclose(self::$file_handle);
+ self::$file_handle = null;
+ }
+ }
+
+ /**
+ * Convenience methods for different log levels
+ */
+ public static function info($message, $context = array()) {
+ return self::log($message, self::LEVEL_INFO, $context);
+ }
+
+ public static function warning($message, $context = array()) {
+ return self::log($message, self::LEVEL_WARNING, $context);
+ }
+
+ public static function error($message, $context = array()) {
+ return self::log($message, self::LEVEL_ERROR, $context);
+ }
+
+ public static function debug($message, $context = array()) {
+ return self::log($message, self::LEVEL_DEBUG, $context);
+ }
+}
diff --git a/wp-content/plugins/wp-debug/inc/profiler.php b/wp-content/plugins/wp-debug/inc/profiler.php
new file mode 100644
index 00000000..2fab20ab
--- /dev/null
+++ b/wp-content/plugins/wp-debug/inc/profiler.php
@@ -0,0 +1,266 @@
+ isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : '',
+ 'memory' => self::format_bytes(memory_get_usage()),
+ ));
+ }
+ }
+
+ /**
+ * Start a timer
+ *
+ * @param string $name Timer name
+ * @return bool True on success
+ */
+ public static function start_timer($name) {
+ self::$timers[$name] = array(
+ 'start' => microtime(true),
+ 'memory_start' => memory_get_usage(),
+ );
+
+ return true;
+ }
+
+ /**
+ * End a timer
+ *
+ * @param string $name Timer name
+ * @return float|bool Elapsed time in seconds, or false
+ */
+ public static function end_timer($name) {
+ if (!isset(self::$timers[$name])) {
+ return false;
+ }
+
+ $end_time = microtime(true);
+ $memory_end = memory_get_usage();
+
+ $elapsed = $end_time - self::$timers[$name]['start'];
+ $memory_used = $memory_end - self::$timers[$name]['memory_start'];
+
+ self::$completed_timers[$name] = array(
+ 'elapsed' => $elapsed,
+ 'memory_used' => $memory_used,
+ 'memory_peak' => memory_get_peak_usage(),
+ );
+
+ // Log if exceeds threshold
+ $threshold = get_option('wp_debug_profiler_threshold', 1000); // milliseconds
+ if ($elapsed * 1000 > $threshold) {
+ if (class_exists('WP_Debug_Logger')) {
+ WP_Debug_Logger::warning("Slow execution: {$name}", array(
+ 'elapsed_ms' => round($elapsed * 1000, 2),
+ 'memory' => self::format_bytes($memory_used),
+ ));
+ }
+ }
+
+ unset(self::$timers[$name]);
+
+ return $elapsed;
+ }
+
+ /**
+ * Get completed timers
+ *
+ * @return array Timers
+ */
+ public static function get_timers() {
+ return self::$completed_timers;
+ }
+
+ /**
+ * Track query execution
+ *
+ * @param string $query SQL query
+ * @return string Query
+ */
+ public static function track_query($query) {
+ // This will be used by query analyzer
+ return $query;
+ }
+
+ /**
+ * Record metrics on shutdown
+ */
+ public static function record_metrics() {
+ // Calculate total execution time
+ $total_time = timer_stop(0, 3);
+
+ // Get memory stats
+ $memory_current = memory_get_usage();
+ $memory_peak = memory_get_peak_usage();
+
+ // Get server load if available
+ $server_load = self::get_server_load();
+
+ // Prepare metrics
+ $metrics = array(
+ 'execution_time' => $total_time,
+ 'memory_current' => $memory_current,
+ 'memory_peak' => $memory_peak,
+ 'server_load' => $server_load,
+ 'timers' => self::$completed_timers,
+ 'url' => isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : '',
+ 'timestamp' => current_time('mysql'),
+ );
+
+ // Log metrics
+ if (class_exists('WP_Debug_Logger')) {
+ WP_Debug_Logger::info('Page load completed', array(
+ 'time' => round($total_time, 3) . 's',
+ 'memory_peak' => self::format_bytes($memory_peak),
+ 'timers_count' => count(self::$completed_timers),
+ ));
+ }
+
+ // Store metrics in transient for frontend panel
+ set_transient('wp_debug_last_metrics', $metrics, 3600);
+ }
+
+ /**
+ * Get server load
+ *
+ * @return string|null Server load
+ */
+ private static function get_server_load() {
+ if (function_exists('sys_getloadavg')) {
+ $load = sys_getloadavg();
+ return implode(', ', array_map(function($val) {
+ return round($val, 2);
+ }, $load));
+ }
+
+ return null;
+ }
+
+ /**
+ * Format bytes to human readable
+ *
+ * @param int $bytes Bytes
+ * @return string Formatted string
+ */
+ public static function format_bytes($bytes) {
+ $units = array('B', 'KB', 'MB', 'GB');
+ $bytes = max($bytes, 0);
+ $pow = floor(($bytes ? log($bytes) : 0) / log(1024));
+ $pow = min($pow, count($units) - 1);
+
+ $bytes /= (1 << (10 * $pow));
+
+ return round($bytes, 2) . ' ' . $units[$pow];
+ }
+
+ /**
+ * Get performance report
+ *
+ * @return array Performance report
+ */
+ public static function get_performance_report() {
+ $metrics = get_transient('wp_debug_last_metrics');
+
+ if (!$metrics) {
+ return array(
+ 'error' => 'No metrics available',
+ );
+ }
+
+ // Analyze and provide suggestions
+ $suggestions = array();
+
+ // Check execution time
+ if ($metrics['execution_time'] > 3) {
+ $suggestions[] = 'Page load time exceeds 3 seconds. Consider caching or optimizing queries.';
+ }
+
+ // Check memory
+ if ($metrics['memory_peak'] > 128 * 1024 * 1024) { // 128MB
+ $suggestions[] = 'Memory usage is high (' . self::format_bytes($metrics['memory_peak']) . '). Check for memory leaks.';
+ }
+
+ // Check slow timers
+ $slow_timers = array();
+ foreach ($metrics['timers'] as $name => $timer) {
+ if ($timer['elapsed'] > 1) {
+ $slow_timers[$name] = round($timer['elapsed'], 3) . 's';
+ }
+ }
+
+ if (!empty($slow_timers)) {
+ $suggestions[] = 'Slow functions detected: ' . implode(', ', array_keys($slow_timers));
+ }
+
+ return array(
+ 'metrics' => $metrics,
+ 'suggestions' => $suggestions,
+ 'formatted' => array(
+ 'execution_time' => round($metrics['execution_time'], 3) . 's',
+ 'memory_current' => self::format_bytes($metrics['memory_current']),
+ 'memory_peak' => self::format_bytes($metrics['memory_peak']),
+ 'server_load' => $metrics['server_load'],
+ ),
+ );
+ }
+}
diff --git a/wp-content/plugins/wp-debug/inc/query-analyzer.php b/wp-content/plugins/wp-debug/inc/query-analyzer.php
new file mode 100644
index 00000000..f03d8bf8
--- /dev/null
+++ b/wp-content/plugins/wp-debug/inc/query-analyzer.php
@@ -0,0 +1,266 @@
+queries)) {
+ return;
+ }
+
+ // Process each query
+ foreach ($wpdb->queries as $query) {
+ list($sql, $elapsed, $caller) = $query;
+
+ $query_info = array(
+ 'sql' => $sql,
+ 'elapsed' => $elapsed,
+ 'caller' => $caller,
+ 'timestamp' => microtime(true),
+ );
+
+ self::$queries_executed[] = $query_info;
+
+ // Group by pattern
+ $normalized = self::normalize_query($sql);
+ if (!isset(self::$query_patterns[$normalized])) {
+ self::$query_patterns[$normalized] = array(
+ 'count' => 0,
+ 'total_time' => 0,
+ 'queries' => array(),
+ );
+ }
+
+ self::$query_patterns[$normalized]['count']++;
+ self::$query_patterns[$normalized]['total_time'] += floatval($elapsed);
+ self::$query_patterns[$normalized]['queries'][] = $query_info;
+ }
+
+ // Log statistics
+ $total_queries = count($wpdb->queries);
+ $total_time = array_sum(array_column($wpdb->queries, 1));
+ $slow_queries = self::get_slow_queries();
+ $n_plus_one = self::detect_n_plus_one();
+
+ if (class_exists('WP_Debug_Logger')) {
+ WP_Debug_Logger::info('Query Analysis Complete', array(
+ 'total_queries' => $total_queries,
+ 'total_time' => round($total_time, 4) . 's',
+ 'slow_queries' => count($slow_queries),
+ 'n_plus_one_patterns' => count($n_plus_one),
+ ));
+
+ // Log slow queries
+ foreach ($slow_queries as $query) {
+ WP_Debug_Logger::warning('Slow Query Detected', array(
+ 'elapsed' => round($query['elapsed'], 4) . 's',
+ 'sql' => substr($query['sql'], 0, 200),
+ 'caller' => $query['caller'],
+ ));
+ }
+
+ // Log N+1 patterns
+ foreach ($n_plus_one as $pattern) {
+ WP_Debug_Logger::warning('N+1 Query Pattern Detected', array(
+ 'count' => $pattern['count'],
+ 'total_time' => round($pattern['total_time'], 4) . 's',
+ 'example' => substr($pattern['example'], 0, 200),
+ ));
+ }
+ }
+
+ // Store for frontend panel
+ set_transient('wp_debug_queries_data', array(
+ 'total_queries' => $total_queries,
+ 'total_time' => $total_time,
+ 'queries' => self::$queries_executed,
+ 'slow_queries' => $slow_queries,
+ 'n_plus_one' => $n_plus_one,
+ 'patterns' => self::$query_patterns,
+ ), 3600);
+ }
+
+ /**
+ * Normalize query for pattern detection
+ *
+ * @param string $query SQL query
+ * @return string Normalized query
+ */
+ private static function normalize_query($query) {
+ // Remove numeric literals
+ $query = preg_replace('/\b\d+\b/', '?', $query);
+
+ // Remove string literals
+ $query = preg_replace("/'[^']*'/", '?', $query);
+
+ // Remove extra whitespace
+ $query = preg_replace('/\s+/', ' ', $query);
+
+ return trim($query);
+ }
+
+ /**
+ * Detect N+1 query patterns
+ *
+ * @return array N+1 patterns
+ */
+ public static function detect_n_plus_one() {
+ $patterns = array();
+
+ // Find patterns executed 5+ times
+ foreach (self::$query_patterns as $normalized => $pattern) {
+ if ($pattern['count'] >= 5) {
+ $patterns[] = array(
+ 'count' => $pattern['count'],
+ 'total_time' => $pattern['total_time'],
+ 'avg_time' => $pattern['total_time'] / $pattern['count'],
+ 'example' => $pattern['queries'][0]['sql'],
+ 'normalized' => $normalized,
+ );
+ }
+ }
+
+ // Sort by count descending
+ usort($patterns, function($a, $b) {
+ return $b['count'] - $a['count'];
+ });
+
+ return $patterns;
+ }
+
+ /**
+ * Get slow queries
+ *
+ * @param float $threshold Threshold in seconds
+ * @return array Slow queries
+ */
+ public static function get_slow_queries($threshold = 0.05) {
+ return array_filter(self::$queries_executed, function($query) use ($threshold) {
+ return floatval($query['elapsed']) > $threshold;
+ });
+ }
+
+ /**
+ * Get queries by caller
+ *
+ * @param string $caller Caller filter
+ * @return array Queries
+ */
+ public static function get_queries_by_caller($caller) {
+ return array_filter(self::$queries_executed, function($query) use ($caller) {
+ return strpos($query['caller'], $caller) !== false;
+ });
+ }
+
+ /**
+ * Get query statistics
+ *
+ * @return array Statistics
+ */
+ public static function get_statistics() {
+ global $wpdb;
+
+ if (empty($wpdb->queries)) {
+ return array(
+ 'error' => 'SAVEQUERIES not enabled or no queries executed',
+ );
+ }
+
+ $total_queries = count($wpdb->queries);
+ $total_time = array_sum(array_column($wpdb->queries, 1));
+ $avg_time = $total_time / $total_queries;
+
+ return array(
+ 'total_queries' => $total_queries,
+ 'total_time' => $total_time,
+ 'avg_time' => $avg_time,
+ 'slow_queries' => count(self::get_slow_queries()),
+ 'n_plus_one_patterns' => count(self::detect_n_plus_one()),
+ 'unique_patterns' => count(self::$query_patterns),
+ );
+ }
+
+ /**
+ * Get query report
+ *
+ * @return array Query report
+ */
+ public static function get_query_report() {
+ $stats = self::get_statistics();
+ $slow_queries = self::get_slow_queries();
+ $n_plus_one = self::detect_n_plus_one();
+
+ $suggestions = array();
+
+ // Suggestions based on analysis
+ if (isset($stats['total_queries']) && $stats['total_queries'] > 100) {
+ $suggestions[] = 'High number of queries detected (' . $stats['total_queries'] . '). Consider using object caching.';
+ }
+
+ if (isset($stats['total_time']) && $stats['total_time'] > 1) {
+ $suggestions[] = 'Total query time exceeds 1 second. Optimize slow queries.';
+ }
+
+ if (!empty($n_plus_one)) {
+ $suggestions[] = 'N+1 query patterns detected. Consider using WP_Query with proper parameters or caching.';
+ }
+
+ if (!empty($slow_queries)) {
+ $suggestions[] = count($slow_queries) . ' slow queries detected. Add indexes or optimize WHERE clauses.';
+ }
+
+ return array(
+ 'statistics' => $stats,
+ 'slow_queries' => array_slice($slow_queries, 0, 10),
+ 'n_plus_one_patterns' => array_slice($n_plus_one, 0, 5),
+ 'suggestions' => $suggestions,
+ );
+ }
+}
diff --git a/wp-content/plugins/wp-debug/inc/template-tracer.php b/wp-content/plugins/wp-debug/inc/template-tracer.php
new file mode 100644
index 00000000..c4829b5a
--- /dev/null
+++ b/wp-content/plugins/wp-debug/inc/template-tracer.php
@@ -0,0 +1,253 @@
+ $template,
+ 'name' => basename($template),
+ 'type' => self::get_template_type(),
+ 'timestamp' => microtime(true),
+ 'backtrace' => self::get_caller(),
+ );
+
+ self::$templates_loaded[] = $template_info;
+
+ // Log template load
+ if (class_exists('WP_Debug_Logger')) {
+ WP_Debug_Logger::debug("Template loaded: " . basename($template), array(
+ 'type' => $template_info['type'],
+ 'path' => $template,
+ ));
+ }
+
+ return $template;
+ }
+
+ /**
+ * Track template part
+ *
+ * @param string $slug Template slug
+ * @param string $name Template name
+ * @param array $args Template args
+ */
+ public static function track_template_part($slug, $name = null, $args = array()) {
+ $template_file = $slug;
+ if ($name) {
+ $template_file .= '-' . $name;
+ }
+ $template_file .= '.php';
+
+ $template_path = locate_template($template_file);
+
+ $part_info = array(
+ 'slug' => $slug,
+ 'name' => $name,
+ 'file' => $template_file,
+ 'path' => $template_path,
+ 'found' => !empty($template_path),
+ 'args' => $args,
+ 'timestamp' => microtime(true),
+ );
+
+ self::$template_parts[] = $part_info;
+
+ // Log if not found
+ if (!$part_info['found']) {
+ if (class_exists('WP_Debug_Logger')) {
+ WP_Debug_Logger::warning("Template part not found: {$template_file}");
+ }
+ }
+ }
+
+ /**
+ * Track template hierarchy
+ *
+ * @param array $templates Template hierarchy
+ * @return array Template hierarchy
+ */
+ public static function track_template_hierarchy($templates) {
+ if (class_exists('WP_Debug_Logger')) {
+ WP_Debug_Logger::debug("Template hierarchy", array(
+ 'templates' => $templates,
+ ));
+ }
+
+ return $templates;
+ }
+
+ /**
+ * Get template type
+ *
+ * @return string Template type
+ */
+ private static function get_template_type() {
+ if (is_404()) return '404';
+ if (is_search()) return 'search';
+ if (is_front_page()) return 'front-page';
+ if (is_home()) return 'home';
+ if (is_post_type_archive()) return 'archive-' . get_post_type();
+ if (is_tax()) return 'taxonomy';
+ if (is_attachment()) return 'attachment';
+ if (is_single()) return 'single-' . get_post_type();
+ if (is_page()) return 'page';
+ if (is_category()) return 'category';
+ if (is_tag()) return 'tag';
+ if (is_author()) return 'author';
+ if (is_date()) return 'date';
+ if (is_archive()) return 'archive';
+
+ return 'index';
+ }
+
+ /**
+ * Get caller information
+ *
+ * @return string Caller info
+ */
+ private static function get_caller() {
+ $backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 5);
+ $callers = array();
+
+ foreach ($backtrace as $trace) {
+ if (isset($trace['file']) && isset($trace['line'])) {
+ $callers[] = basename($trace['file']) . ':' . $trace['line'];
+ }
+ }
+
+ return implode(' -> ', $callers);
+ }
+
+ /**
+ * Get loaded templates
+ *
+ * @return array Templates
+ */
+ public static function get_templates() {
+ return self::$templates_loaded;
+ }
+
+ /**
+ * Get template parts
+ *
+ * @return array Template parts
+ */
+ public static function get_template_parts() {
+ return self::$template_parts;
+ }
+
+ /**
+ * Get missing template parts
+ *
+ * @return array Missing template parts
+ */
+ public static function get_missing_parts() {
+ return array_filter(self::$template_parts, function($part) {
+ return !$part['found'];
+ });
+ }
+
+ /**
+ * Record templates on shutdown
+ */
+ public static function record_templates() {
+ $templates_count = count(self::$templates_loaded);
+ $parts_count = count(self::$template_parts);
+ $missing_count = count(self::get_missing_parts());
+
+ // Log summary
+ if (class_exists('WP_Debug_Logger')) {
+ WP_Debug_Logger::info("Templates loaded", array(
+ 'templates' => $templates_count,
+ 'parts' => $parts_count,
+ 'missing' => $missing_count,
+ ));
+ }
+
+ // Store for frontend panel
+ set_transient('wp_debug_templates_data', array(
+ 'templates' => self::$templates_loaded,
+ 'parts' => self::$template_parts,
+ 'missing' => self::get_missing_parts(),
+ ), 3600);
+ }
+
+ /**
+ * Get template load order
+ *
+ * @return array Template load order
+ */
+ public static function get_load_order() {
+ $order = array();
+
+ // Combine templates and parts
+ $all = array_merge(
+ array_map(function($t) {
+ return array('type' => 'template', 'data' => $t);
+ }, self::$templates_loaded),
+ array_map(function($p) {
+ return array('type' => 'part', 'data' => $p);
+ }, self::$template_parts)
+ );
+
+ // Sort by timestamp
+ usort($all, function($a, $b) {
+ return $a['data']['timestamp'] - $b['data']['timestamp'];
+ });
+
+ return $all;
+ }
+}
diff --git a/wp-content/plugins/wp-debug/readme.txt b/wp-content/plugins/wp-debug/readme.txt
new file mode 100644
index 00000000..eb213ead
--- /dev/null
+++ b/wp-content/plugins/wp-debug/readme.txt
@@ -0,0 +1,106 @@
+=== WP Debug ===
+Contributors: Developer Team
+Tags: debug, diagnostics, performance, developer tools, wp-cli
+Requires at least: 6.0
+Tested up to: 6.8
+Requires PHP: 7.4
+Stable tag: 1.0.0
+License: GPLv2 or later
+License URI: https://www.gnu.org/licenses/gpl-2.0.html
+
+Sistema avanzado de debug y diagnóstico para WordPress. Monitorea hooks, templates, assets, queries y performance en tiempo real.
+
+== Description ==
+
+WP Debug es un sistema de diagnóstico avanzado diseñado para desarrolladores de WordPress que necesitan visibilidad completa del ciclo de rendering, hooks, queries y performance.
+
+= Características =
+
+* **Hook Monitor:** Rastrea todos los hooks de WordPress ejecutados con timestamps
+* **Template Tracer:** Identifica qué templates se cargan y en qué orden
+* **Asset Tracker:** Monitorea CSS/JS encolados con dependencias y duplicados
+* **Performance Profiler:** Mide tiempos de ejecución y detecta bottlenecks
+* **Query Analyzer:** Analiza queries SQL y detecta problemas N+1
+* **Frontend Panel:** Panel visual flotante en frontend (solo admins)
+* **WP-CLI Commands:** Comandos desde terminal para diagnóstico avanzado
+
+= Casos de Uso =
+
+* Diagnosticar por qué un sidebar no es sticky
+* Identificar hooks que no se ejecutan
+* Detectar CSS/JS que se cargan en orden incorrecto
+* Encontrar queries lentas o duplicadas
+* Medir performance de funciones críticas
+* Debugging de templates y template-parts
+
+= Comandos WP-CLI =
+
+* `wp debug status` - Ver estado del sistema
+* `wp debug hooks` - Listar hooks ejecutados
+* `wp debug templates` - Listar templates cargados
+* `wp debug export` - Exportar reporte completo
+* `wp debug clear` - Limpiar logs
+
+= API Programática =
+
+```php
+// Logging
+wp_debug_log('Mensaje de debug', 'INFO');
+
+// Performance profiling
+wp_debug_start_timer('mi_funcion');
+// ... tu código ...
+wp_debug_end_timer('mi_funcion');
+
+// Obtener datos
+$hooks = wp_debug_get_hooks();
+$templates = wp_debug_get_templates();
+```
+
+== Installation ==
+
+1. Sube la carpeta `wp-debug` a `/wp-content/plugins/`
+2. Activa el plugin desde el menú 'Plugins' de WordPress
+3. Ve a Tools → WP Debug para ver el dashboard
+4. (Opcional) Usa comandos WP-CLI para diagnóstico avanzado
+
+== Frequently Asked Questions ==
+
+= ¿Afecta el performance del sitio? =
+
+El plugin está optimizado para mínimo overhead (<5%). Se recomienda usar solo en staging/desarrollo.
+
+= ¿Puedo usarlo en producción? =
+
+Sí, pero se recomienda desactivar módulos innecesarios y usar solo en modo debug.
+
+= ¿Dónde se guardan los logs? =
+
+En `/wp-content/logs/wp-debug/` y en tablas de base de datos.
+
+= ¿Cómo lo uso en mi plugin? =
+
+Simplemente llama a las funciones API: `wp_debug_log()`, `wp_debug_start_timer()`, etc.
+
+== Screenshots ==
+
+1. Dashboard principal en Tools → WP Debug
+2. Frontend panel flotante (solo admins)
+3. Hook monitor en tiempo real
+4. Template tracer con backtraces
+5. Performance profiler con métricas
+
+== Changelog ==
+
+= 1.0.0 =
+* Release inicial
+* 8 módulos de diagnóstico
+* WP-CLI commands
+* API programática
+* Frontend panel
+* Admin dashboard
+
+== Upgrade Notice ==
+
+= 1.0.0 =
+Primera versión del plugin.
diff --git a/wp-content/plugins/wp-debug/wp-debug.php b/wp-content/plugins/wp-debug/wp-debug.php
new file mode 100644
index 00000000..ff54f1da
--- /dev/null
+++ b/wp-content/plugins/wp-debug/wp-debug.php
@@ -0,0 +1,145 @@
+