Agregar plugin WP Debug para diagnóstico sistemático

This commit is contained in:
FrankZamora
2025-11-06 22:57:52 -06:00
parent 9670659267
commit 7e422d5499
16 changed files with 4105 additions and 0 deletions

View File

@@ -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;
}

View File

@@ -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);

View File

@@ -0,0 +1,145 @@
<?php
/**
* Plugin Activation
*
* @package WP_Debug
* @since 1.0.0
*/
// Exit if accessed directly
if (!defined('ABSPATH')) {
exit;
}
/**
* WP_Debug_Activation class
*/
class WP_Debug_Activation {
/**
* Run activation tasks
*/
public static function activate() {
// Set default options
self::set_default_options();
// Create logs directory
self::create_logs_directory();
// Create database tables if needed
self::create_tables();
// Set activation timestamp
update_option('wp_debug_activated_at', time());
// Flush rewrite rules
flush_rewrite_rules();
}
/**
* Set default plugin options
*/
private static function set_default_options() {
// Main toggle
add_option('wp_debug_enabled', true);
// Module toggles (all enabled by default)
add_option('wp_debug_module_logger', true);
add_option('wp_debug_module_hook_monitor', true);
add_option('wp_debug_module_template_tracer', true);
add_option('wp_debug_module_asset_tracker', true);
add_option('wp_debug_module_profiler', true);
add_option('wp_debug_module_query_analyzer', true);
add_option('wp_debug_module_frontend_panel', true);
add_option('wp_debug_module_cli_commands', true);
// Logger settings
add_option('wp_debug_log_level', 'INFO'); // INFO, WARNING, ERROR, DEBUG
add_option('wp_debug_log_retention_days', 7); // Keep logs for 7 days
add_option('wp_debug_log_to_file', true);
add_option('wp_debug_log_to_db', true);
// Performance settings
add_option('wp_debug_profiler_enabled', true);
add_option('wp_debug_profiler_threshold', 1000); // Log functions taking >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 = "<?php\n// Silence is golden.";
file_put_contents($index_file, $index_content);
}
// Set proper permissions
if (file_exists($logs_dir)) {
chmod($logs_dir, 0755);
}
}
/**
* Create database tables
*/
private static function create_tables() {
global $wpdb;
$charset_collate = $wpdb->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);
}
}

View File

@@ -0,0 +1,224 @@
<?php
/**
* Admin Menu
*
* @package WP_Debug
* @since 1.0.0
*/
// Exit if accessed directly
if (!defined('ABSPATH')) {
exit;
}
/**
* WP_Debug_Admin_Menu class
*/
class WP_Debug_Admin_Menu {
/**
* Initialize admin menu
*/
public static function init() {
// Add menu page under Tools
add_management_page(
__('WP Debug', 'wp-debug'), // Page title
__('WP Debug', 'wp-debug'), // Menu title
'manage_options', // Capability
'wp-debug', // Menu slug
array(__CLASS__, 'render_dashboard'), // Callback
99 // Position
);
// Enqueue admin styles and scripts
add_action('admin_enqueue_scripts', array(__CLASS__, 'enqueue_admin_assets'));
}
/**
* Render dashboard page
*/
public static function render_dashboard() {
// Check user capabilities
if (!current_user_can('manage_options')) {
return;
}
?>
<div class="wrap wp-debug-dashboard">
<h1><?php echo esc_html(get_admin_page_title()); ?></h1>
<div class="wp-debug-header">
<p><?php _e('Sistema avanzado de debug y diagnóstico para WordPress', 'wp-debug'); ?></p>
<p><strong><?php _e('Versión:', 'wp-debug'); ?></strong> <?php echo esc_html(WP_DEBUG_VERSION); ?></p>
</div>
<div class="wp-debug-cards">
<!-- Status Card -->
<div class="wp-debug-card">
<h2><?php _e('Estado del Sistema', 'wp-debug'); ?></h2>
<p>
<strong><?php _e('Debug Activo:', 'wp-debug'); ?></strong>
<?php echo get_option('wp_debug_enabled', true) ? '<span class="status-active">✓ Activo</span>' : '<span class="status-inactive">✗ Inactivo</span>'; ?>
</p>
<p>
<strong><?php _e('WordPress:', 'wp-debug'); ?></strong>
<?php echo esc_html(get_bloginfo('version')); ?>
</p>
<p>
<strong><?php _e('PHP:', 'wp-debug'); ?></strong>
<?php echo esc_html(phpversion()); ?>
</p>
<p>
<strong><?php _e('Tema Activo:', 'wp-debug'); ?></strong>
<?php echo esc_html(wp_get_theme()->get('Name')); ?>
</p>
</div>
<!-- Modules Card -->
<div class="wp-debug-card">
<h2><?php _e('Módulos Instalados', 'wp-debug'); ?></h2>
<?php
$modules = WP_Debug_Autoloader::get_modules();
foreach ($modules as $module_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 '<p><strong>' . esc_html(ucwords(str_replace('_', ' ', $module_name))) . ':</strong> <span class="' . esc_attr($status_class) . '">' . esc_html($status_text) . '</span></p>';
}
?>
</div>
<!-- Quick Actions Card -->
<div class="wp-debug-card">
<h2><?php _e('Acciones Rápidas', 'wp-debug'); ?></h2>
<p><a href="#" class="button button-secondary" onclick="alert('Panel de logs próximamente')">📋 Ver Logs</a></p>
<p><a href="#" class="button button-secondary" onclick="alert('Monitor de hooks próximamente')">🔌 Hooks Monitor</a></p>
<p><a href="#" class="button button-secondary" onclick="alert('Template tracer próximamente')">📄 Templates</a></p>
<p><a href="#" class="button button-secondary" onclick="alert('Asset tracker próximamente')">📦 Assets</a></p>
<p><a href="#" class="button button-secondary" onclick="alert('Performance profiler próximamente')">⚡ Performance</a></p>
<p><a href="#" class="button button-secondary" onclick="alert('Query analyzer próximamente')">🗄️ Queries</a></p>
</div>
<!-- WP-CLI Commands Card -->
<div class="wp-debug-card">
<h2><?php _e('Comandos WP-CLI', 'wp-debug'); ?></h2>
<code>wp debug status</code><br>
<code>wp debug hooks</code><br>
<code>wp debug templates</code><br>
<code>wp debug export</code><br>
<code>wp debug clear</code>
<p><small><?php _e('Ejecuta estos comandos desde la terminal para diagnóstico avanzado', 'wp-debug'); ?></small></p>
</div>
</div>
<div class="wp-debug-info">
<h3><?php _e('Información del Plugin', 'wp-debug'); ?></h3>
<p><?php _e('WP Debug es un sistema de diagnóstico avanzado que te permite:', 'wp-debug'); ?></p>
<ul>
<li>✓ <?php _e('Monitorear hooks de WordPress en tiempo real', 'wp-debug'); ?></li>
<li>✓ <?php _e('Rastrear templates y template-parts cargados', 'wp-debug'); ?></li>
<li>✓ <?php _e('Analizar assets CSS/JS con dependencias', 'wp-debug'); ?></li>
<li>✓ <?php _e('Medir performance y detectar bottlenecks', 'wp-debug'); ?></li>
<li>✓ <?php _e('Analizar queries SQL y detectar problemas N+1', 'wp-debug'); ?></li>
<li>✓ <?php _e('Panel visual flotante en frontend (solo admins)', 'wp-debug'); ?></li>
<li>✓ <?php _e('Comandos WP-CLI para diagnóstico desde terminal', 'wp-debug'); ?></li>
</ul>
</div>
</div>
<style>
.wp-debug-dashboard {
max-width: 1200px;
}
.wp-debug-header {
background: #f0f0f1;
padding: 20px;
margin: 20px 0;
border-left: 4px solid #2271b1;
}
.wp-debug-cards {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 20px;
margin: 20px 0;
}
.wp-debug-card {
background: #fff;
border: 1px solid #c3c4c7;
padding: 20px;
box-shadow: 0 1px 1px rgba(0,0,0,0.04);
}
.wp-debug-card h2 {
margin-top: 0;
font-size: 1.2em;
border-bottom: 2px solid #2271b1;
padding-bottom: 10px;
}
.wp-debug-card p {
margin: 10px 0;
}
.wp-debug-card code {
display: block;
background: #f0f0f1;
padding: 5px 10px;
margin: 5px 0;
font-family: monospace;
}
.status-active {
color: #00a32a;
font-weight: bold;
}
.status-inactive {
color: #d63638;
font-weight: bold;
}
.wp-debug-info {
background: #fff;
border: 1px solid #c3c4c7;
padding: 20px;
margin: 20px 0;
}
.wp-debug-info ul {
list-style: none;
padding-left: 0;
}
.wp-debug-info li {
padding: 5px 0;
}
</style>
<?php
}
/**
* Enqueue admin assets
*
* @param string $hook Current admin page hook
*/
public static function enqueue_admin_assets($hook) {
// Only load on our admin page
if ('tools_page_wp-debug' !== $hook) {
return;
}
// Enqueue admin CSS if exists
if (file_exists(WP_DEBUG_PLUGIN_DIR . 'assets/css/admin.css')) {
wp_enqueue_style(
'wp-debug-admin',
WP_DEBUG_PLUGIN_URL . 'assets/css/admin.css',
array(),
WP_DEBUG_VERSION
);
}
// Enqueue admin JS if exists
if (file_exists(WP_DEBUG_PLUGIN_DIR . 'assets/js/admin.js')) {
wp_enqueue_script(
'wp-debug-admin',
WP_DEBUG_PLUGIN_URL . 'assets/js/admin.js',
array('jquery'),
WP_DEBUG_VERSION,
true
);
}
}
}

View File

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

View File

@@ -0,0 +1,154 @@
<?php
/**
* Autoloader - Loads all plugin modules
*
* @package WP_Debug
* @since 1.0.0
*/
// Exit if accessed directly
if (!defined('ABSPATH')) {
exit;
}
/**
* WP_Debug_Autoloader class
*/
class WP_Debug_Autoloader {
/**
* Modules to load
*
* @var array
*/
private static $modules = array(
'logger' => 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);
}
}

View File

@@ -0,0 +1,543 @@
<?php
/**
* CLI Commands Module
*
* Comandos WP-CLI para debug y diagnóstico
*
* @package WP_Debug
* @since 1.0.0
*/
// Exit if accessed directly
if (!defined('ABSPATH')) {
exit;
}
// Check if WP-CLI is available
if (!class_exists('WP_CLI')) {
return;
}
/**
* WP_Debug_CLI_Commands class
*/
class WP_Debug_CLI_Commands extends WP_CLI_Command {
/**
* Initialize CLI commands
*/
public static function init() {
if (class_exists('WP_CLI')) {
WP_CLI::add_command('debug', 'WP_Debug_CLI_Commands');
}
}
/**
* Show plugin status and modules information
*
* ## EXAMPLES
*
* wp debug status
*
* @when after_wp_load
*/
public function status($args, $assoc_args) {
WP_CLI::log('WP Debug Plugin Status');
WP_CLI::log('======================');
WP_CLI::log('');
// Plugin info
WP_CLI::log('Version: ' . WP_DEBUG_VERSION);
WP_CLI::log('Plugin Directory: ' . WP_DEBUG_PLUGIN_DIR);
WP_CLI::log('');
// Global status
$enabled = get_option('wp_debug_enabled', true);
WP_CLI::log('Plugin Status: ' . ($enabled ? 'Enabled' : 'Disabled'));
WP_CLI::log('');
// Modules status
WP_CLI::log('Modules:');
WP_CLI::log('--------');
$modules = WP_Debug_Autoloader::get_modules();
$items = array();
foreach ($modules as $module_name => $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=<pattern>]
* : Filter hooks by pattern
*
* [--limit=<number>]
* : Limit number of results
* ---
* default: 50
* ---
*
* [--format=<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=<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=<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
*
* [<file>]
* : 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=<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");
}
}

View File

@@ -0,0 +1,47 @@
<?php
/**
* Plugin Deactivation
*
* @package WP_Debug
* @since 1.0.0
*/
// Exit if accessed directly
if (!defined('ABSPATH')) {
exit;
}
/**
* WP_Debug_Deactivation class
*/
class WP_Debug_Deactivation {
/**
* Run deactivation tasks
*/
public static function deactivate() {
// Clean up transients
self::cleanup_transients();
// Flush rewrite rules
flush_rewrite_rules();
// Note: We DO NOT delete logs or database tables
// This preserves data in case user reactivates the plugin
// User can manually delete via Tools > 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_%')");
}
}

View File

@@ -0,0 +1,373 @@
<?php
/**
* Frontend Panel Module
*
* Panel visual flotante en frontend para administradores
*
* @package WP_Debug
* @since 1.0.0
*/
// Exit if accessed directly
if (!defined('ABSPATH')) {
exit;
}
/**
* WP_Debug_Frontend_Panel class
*/
class WP_Debug_Frontend_Panel {
/**
* Initialize frontend panel
*/
public static function init() {
// Only for admins
if (!current_user_can('manage_options')) {
return;
}
// Enqueue assets
add_action('wp_enqueue_scripts', array(__CLASS__, 'enqueue_assets'));
// Render panel
add_action('wp_footer', array(__CLASS__, 'render_panel'), 999);
}
/**
* Enqueue assets
*/
public static function enqueue_assets() {
// Enqueue CSS
wp_enqueue_style(
'wp-debug-panel',
WP_DEBUG_PLUGIN_URL . 'assets/css/debug-panel.css',
array(),
WP_DEBUG_VERSION
);
// Enqueue JS
wp_enqueue_script(
'wp-debug-panel',
WP_DEBUG_PLUGIN_URL . 'assets/js/debug-panel.js',
array('jquery'),
WP_DEBUG_VERSION,
true
);
// Localize script with data
wp_localize_script('wp-debug-panel', 'wpDebugData', array(
'ajaxUrl' => 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');
?>
<div id="wp-debug-panel" class="wp-debug-panel" style="display: none;">
<div class="wp-debug-panel-header">
<h3>WP Debug Panel</h3>
<div class="wp-debug-panel-tabs">
<button class="wp-debug-tab active" data-tab="overview">Overview</button>
<button class="wp-debug-tab" data-tab="performance">Performance</button>
<button class="wp-debug-tab" data-tab="hooks">Hooks</button>
<button class="wp-debug-tab" data-tab="templates">Templates</button>
<button class="wp-debug-tab" data-tab="assets">Assets</button>
<button class="wp-debug-tab" data-tab="queries">Queries</button>
</div>
<button class="wp-debug-panel-close">&times;</button>
</div>
<div class="wp-debug-panel-content">
<!-- Overview Tab -->
<div class="wp-debug-tab-content active" data-tab="overview">
<h4>System Overview</h4>
<table class="wp-debug-table">
<tr>
<td><strong>WordPress Version:</strong></td>
<td><?php echo get_bloginfo('version'); ?></td>
</tr>
<tr>
<td><strong>PHP Version:</strong></td>
<td><?php echo phpversion(); ?></td>
</tr>
<tr>
<td><strong>Active Theme:</strong></td>
<td><?php echo wp_get_theme()->get('Name'); ?></td>
</tr>
<tr>
<td><strong>Memory Usage:</strong></td>
<td><?php echo size_format(memory_get_usage()); ?> / <?php echo size_format(memory_get_peak_usage()); ?> peak</td>
</tr>
<tr>
<td><strong>Execution Time:</strong></td>
<td><?php echo round(timer_stop(0), 3); ?>s</td>
</tr>
</table>
<h4>Module Status</h4>
<ul class="wp-debug-module-list">
<?php
$modules = array(
'logger' => '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 ? '<span class="status-active">Active</span>' : '<span class="status-inactive">Inactive</span>';
echo "<li>{$module_name}: {$status}</li>";
}
?>
</ul>
</div>
<!-- Performance Tab -->
<div class="wp-debug-tab-content" data-tab="performance">
<h4>Performance Metrics</h4>
<?php if ($metrics): ?>
<table class="wp-debug-table">
<tr>
<td><strong>Execution Time:</strong></td>
<td><?php echo round($metrics['execution_time'], 3); ?>s</td>
</tr>
<tr>
<td><strong>Memory Peak:</strong></td>
<td><?php echo size_format($metrics['memory_peak']); ?></td>
</tr>
<tr>
<td><strong>Memory Current:</strong></td>
<td><?php echo size_format($metrics['memory_current']); ?></td>
</tr>
<?php if (isset($metrics['server_load'])): ?>
<tr>
<td><strong>Server Load:</strong></td>
<td><?php echo esc_html($metrics['server_load']); ?></td>
</tr>
<?php endif; ?>
</table>
<?php if (!empty($metrics['timers'])): ?>
<h4>Custom Timers</h4>
<table class="wp-debug-table">
<thead>
<tr>
<th>Timer</th>
<th>Elapsed</th>
<th>Memory Used</th>
</tr>
</thead>
<tbody>
<?php foreach ($metrics['timers'] as $name => $timer): ?>
<tr>
<td><?php echo esc_html($name); ?></td>
<td><?php echo round($timer['elapsed'], 4); ?>s</td>
<td><?php echo size_format($timer['memory_used']); ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<?php endif; ?>
<?php else: ?>
<p>No performance data available.</p>
<?php endif; ?>
</div>
<!-- Hooks Tab -->
<div class="wp-debug-tab-content" data-tab="hooks">
<h4>WordPress Hooks</h4>
<?php if ($hooks_data): ?>
<p><strong>Total Hooks:</strong> <?php echo $hooks_data['total']; ?> | <strong>Unique:</strong> <?php echo $hooks_data['unique']; ?></p>
<h5>Most Frequent Hooks</h5>
<table class="wp-debug-table">
<thead>
<tr>
<th>Hook Name</th>
<th>Count</th>
</tr>
</thead>
<tbody>
<?php
$top_hooks = array_slice($hooks_data['counts'], 0, 20, true);
foreach ($top_hooks as $hook => $count):
?>
<tr>
<td><code><?php echo esc_html($hook); ?></code></td>
<td><?php echo $count; ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<?php else: ?>
<p>No hooks data available.</p>
<?php endif; ?>
</div>
<!-- Templates Tab -->
<div class="wp-debug-tab-content" data-tab="templates">
<h4>Loaded Templates</h4>
<?php if ($templates_data): ?>
<?php if (!empty($templates_data['templates'])): ?>
<h5>Main Templates</h5>
<ul class="wp-debug-list">
<?php foreach ($templates_data['templates'] as $template): ?>
<li>
<strong><?php echo esc_html($template['name']); ?></strong>
<span class="template-type"><?php echo esc_html($template['type']); ?></span>
</li>
<?php endforeach; ?>
</ul>
<?php endif; ?>
<?php if (!empty($templates_data['parts'])): ?>
<h5>Template Parts (<?php echo count($templates_data['parts']); ?>)</h5>
<ul class="wp-debug-list">
<?php foreach (array_slice($templates_data['parts'], 0, 20) as $part): ?>
<li>
<code><?php echo esc_html($part['file']); ?></code>
<?php if (!$part['found']): ?>
<span class="status-error">Not Found</span>
<?php endif; ?>
</li>
<?php endforeach; ?>
</ul>
<?php endif; ?>
<?php if (!empty($templates_data['missing'])): ?>
<h5>Missing Templates</h5>
<ul class="wp-debug-list">
<?php foreach ($templates_data['missing'] as $missing): ?>
<li class="missing">
<code><?php echo esc_html($missing['file']); ?></code>
</li>
<?php endforeach; ?>
</ul>
<?php endif; ?>
<?php else: ?>
<p>No template data available.</p>
<?php endif; ?>
</div>
<!-- Assets Tab -->
<div class="wp-debug-tab-content" data-tab="assets">
<h4>Enqueued Assets</h4>
<?php if ($assets_data): ?>
<?php if (isset($assets_data['statistics'])): ?>
<table class="wp-debug-table">
<tr>
<td><strong>Total Assets:</strong></td>
<td><?php echo $assets_data['statistics']['total_assets']; ?></td>
</tr>
<tr>
<td><strong>Styles:</strong></td>
<td><?php echo $assets_data['statistics']['total_styles']; ?></td>
</tr>
<tr>
<td><strong>Scripts:</strong></td>
<td><?php echo $assets_data['statistics']['total_scripts']; ?></td>
</tr>
<tr>
<td><strong>Total Size:</strong></td>
<td><?php echo $assets_data['statistics']['total_size_formatted']; ?></td>
</tr>
<tr>
<td><strong>Duplicates:</strong></td>
<td><?php echo $assets_data['statistics']['duplicates_count']; ?></td>
</tr>
</table>
<?php endif; ?>
<?php if (!empty($assets_data['duplicates'])): ?>
<h5>Duplicate Assets</h5>
<ul class="wp-debug-list">
<?php foreach ($assets_data['duplicates'] as $dup): ?>
<li class="duplicate">
<strong><?php echo esc_html($dup['type']); ?>:</strong>
<?php echo implode(', ', array_map('esc_html', $dup['handles'])); ?>
<span class="count">(<?php echo $dup['count']; ?>x)</span>
</li>
<?php endforeach; ?>
</ul>
<?php endif; ?>
<?php else: ?>
<p>No assets data available.</p>
<?php endif; ?>
</div>
<!-- Queries Tab -->
<div class="wp-debug-tab-content" data-tab="queries">
<h4>Database Queries</h4>
<?php if ($queries_data): ?>
<table class="wp-debug-table">
<tr>
<td><strong>Total Queries:</strong></td>
<td><?php echo $queries_data['total_queries']; ?></td>
</tr>
<tr>
<td><strong>Total Time:</strong></td>
<td><?php echo round($queries_data['total_time'], 4); ?>s</td>
</tr>
<tr>
<td><strong>Slow Queries:</strong></td>
<td><?php echo count($queries_data['slow_queries']); ?></td>
</tr>
<tr>
<td><strong>N+1 Patterns:</strong></td>
<td><?php echo count($queries_data['n_plus_one']); ?></td>
</tr>
</table>
<?php if (!empty($queries_data['slow_queries'])): ?>
<h5>Slow Queries</h5>
<div class="wp-debug-queries">
<?php foreach (array_slice($queries_data['slow_queries'], 0, 5) as $query): ?>
<div class="query-item">
<div class="query-time"><?php echo round($query['elapsed'], 4); ?>s</div>
<code class="query-sql"><?php echo esc_html(substr($query['sql'], 0, 150)); ?>...</code>
</div>
<?php endforeach; ?>
</div>
<?php endif; ?>
<?php if (!empty($queries_data['n_plus_one'])): ?>
<h5>N+1 Query Patterns</h5>
<div class="wp-debug-queries">
<?php foreach (array_slice($queries_data['n_plus_one'], 0, 3) as $pattern): ?>
<div class="query-item n-plus-one">
<div class="query-count"><?php echo $pattern['count']; ?>x</div>
<div class="query-time"><?php echo round($pattern['total_time'], 4); ?>s total</div>
<code class="query-sql"><?php echo esc_html(substr($pattern['example'], 0, 150)); ?>...</code>
</div>
<?php endforeach; ?>
</div>
<?php endif; ?>
<?php else: ?>
<p>No query data available. Make sure SAVEQUERIES is enabled.</p>
<?php endif; ?>
</div>
</div>
</div>
<!-- Toggle Button -->
<button id="wp-debug-toggle" class="wp-debug-toggle">
<span class="dashicons dashicons-admin-tools"></span>
<span class="label">Debug</span>
</button>
<?php
}
}

View File

@@ -0,0 +1,193 @@
<?php
/**
* Hook Monitor Module
*
* Rastrea todos los hooks de WordPress ejecutados
*
* @package WP_Debug
* @since 1.0.0
*/
// Exit if accessed directly
if (!defined('ABSPATH')) {
exit;
}
/**
* WP_Debug_Hook_Monitor class
*/
class WP_Debug_Hook_Monitor {
/**
* Hooks executed
*
* @var array
*/
private static $hooks_executed = array();
/**
* Hook execution count
*
* @var array
*/
private static $hook_counts = array();
/**
* Initialize hook monitor
*/
public static function init() {
// Monitor all actions
add_action('all', array(__CLASS__, 'track_hook'), 1);
// Record hooks on shutdown
add_action('shutdown', array(__CLASS__, 'record_hooks'), 999);
}
/**
* Track hook execution
*
* @param string $hook Hook name
*/
public static function track_hook($hook) {
// Skip our own hooks to avoid recursion
if (strpos($hook, 'wp_debug_') === 0) {
return;
}
// Skip some noisy hooks
$skip_hooks = array('gettext', 'gettext_with_context', 'ngettext', 'all');
if (in_array($hook, $skip_hooks)) {
return;
}
$start_time = microtime(true);
// Get backtrace
$backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 3);
$caller = 'unknown';
if (isset($backtrace[2]['file'])) {
$caller = basename($backtrace[2]['file']) . ':' . $backtrace[2]['line'];
}
// Store hook info
$hook_info = array(
'hook' => $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;
}
}

View File

@@ -0,0 +1,335 @@
<?php
/**
* Logger Module
*
* Sistema de logging centralizado con soporte para archivos y base de datos
*
* @package WP_Debug
* @since 1.0.0
*/
// Exit if accessed directly
if (!defined('ABSPATH')) {
exit;
}
/**
* WP_Debug_Logger class
*/
class WP_Debug_Logger {
/**
* Log levels
*/
const LEVEL_INFO = 'INFO';
const LEVEL_WARNING = 'WARNING';
const LEVEL_ERROR = 'ERROR';
const LEVEL_DEBUG = 'DEBUG';
/**
* Log file handle
*
* @var resource
*/
private static $file_handle = null;
/**
* Initialize logger
*/
public static function init() {
// Hook into shutdown to close file handle
add_action('shutdown', array(__CLASS__, 'close_file_handle'));
}
/**
* Log a message
*
* @param string $message Message to log
* @param string $level Log level
* @param array $context Additional context
* @return bool True on success
*/
public static function log($message, $level = self::LEVEL_INFO, $context = array()) {
// Check if logging is enabled
if (!get_option('wp_debug_enabled', true)) {
return false;
}
// Validate level
if (!in_array($level, array(self::LEVEL_INFO, self::LEVEL_WARNING, self::LEVEL_ERROR, self::LEVEL_DEBUG))) {
$level = self::LEVEL_INFO;
}
// Get current log level setting
$min_level = get_option('wp_debug_log_level', self::LEVEL_INFO);
if (!self::should_log($level, $min_level)) {
return false;
}
// Prepare log entry
$log_entry = self::prepare_log_entry($message, $level, $context);
// Log to file
$log_to_file = get_option('wp_debug_log_to_file', true);
if ($log_to_file) {
self::log_to_file($log_entry);
}
// Log to database
$log_to_db = get_option('wp_debug_log_to_db', true);
if ($log_to_db) {
self::log_to_database($log_entry);
}
return true;
}
/**
* Check if message should be logged based on level
*
* @param string $level Message level
* @param string $min_level Minimum level to log
* @return bool True if should log
*/
private static function should_log($level, $min_level) {
$levels = array(
self::LEVEL_DEBUG => 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);
}
}

View File

@@ -0,0 +1,266 @@
<?php
/**
* Performance Profiler Module
*
* Mide tiempos de ejecución, memoria y detecta bottlenecks
*
* @package WP_Debug
* @since 1.0.0
*/
// Exit if accessed directly
if (!defined('ABSPATH')) {
exit;
}
/**
* WP_Debug_Profiler class
*/
class WP_Debug_Profiler {
/**
* Active timers
*
* @var array
*/
private static $timers = array();
/**
* Completed timers
*
* @var array
*/
private static $completed_timers = array();
/**
* Memory snapshots
*
* @var array
*/
private static $memory_snapshots = array();
/**
* Initialize profiler
*/
public static function init() {
// Start tracking on init
add_action('init', array(__CLASS__, 'start_tracking'), 1);
// Record metrics on shutdown
add_action('shutdown', array(__CLASS__, 'record_metrics'), 999);
// Track query execution
add_filter('query', array(__CLASS__, 'track_query'));
}
/**
* Start tracking
*/
public static function start_tracking() {
self::$memory_snapshots['init'] = memory_get_usage();
// Log page load start
if (class_exists('WP_Debug_Logger')) {
WP_Debug_Logger::debug('Page load started', array(
'url' => 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'],
),
);
}
}

View File

@@ -0,0 +1,266 @@
<?php
/**
* Query Analyzer Module
*
* Analiza queries SQL, detecta N+1 queries y queries lentas
*
* @package WP_Debug
* @since 1.0.0
*/
// Exit if accessed directly
if (!defined('ABSPATH')) {
exit;
}
/**
* WP_Debug_Query_Analyzer class
*/
class WP_Debug_Query_Analyzer {
/**
* Queries executed
*
* @var array
*/
private static $queries_executed = array();
/**
* Query patterns
*
* @var array
*/
private static $query_patterns = array();
/**
* Initialize query analyzer
*/
public static function init() {
// Enable query saving
if (!defined('SAVEQUERIES')) {
define('SAVEQUERIES', true);
}
// Track queries on shutdown
add_action('shutdown', array(__CLASS__, 'analyze_queries'), 999);
}
/**
* Analyze queries
*/
public static function analyze_queries() {
global $wpdb;
if (empty($wpdb->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,
);
}
}

View File

@@ -0,0 +1,253 @@
<?php
/**
* Template Tracer Module
*
* Rastrea qué templates se cargan y en qué orden
*
* @package WP_Debug
* @since 1.0.0
*/
// Exit if accessed directly
if (!defined('ABSPATH')) {
exit;
}
/**
* WP_Debug_Template_Tracer class
*/
class WP_Debug_Template_Tracer {
/**
* Templates loaded
*
* @var array
*/
private static $templates_loaded = array();
/**
* Template parts loaded
*
* @var array
*/
private static $template_parts = array();
/**
* Initialize template tracer
*/
public static function init() {
// Track template loading
add_filter('template_include', array(__CLASS__, 'track_template'), 999);
// Track template parts
add_filter('get_template_part', array(__CLASS__, 'track_template_part'), 10, 3);
// Track template hierarchy
add_filter('template_hierarchy', array(__CLASS__, 'track_template_hierarchy'));
// Record templates on shutdown
add_action('shutdown', array(__CLASS__, 'record_templates'), 999);
}
/**
* Track main template
*
* @param string $template Template path
* @return string Template path
*/
public static function track_template($template) {
$template_info = array(
'path' => $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;
}
}

View File

@@ -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.

View File

@@ -0,0 +1,145 @@
<?php
/**
* Plugin Name: WP Debug
* Plugin URI: https://github.com/prime-leads-app/wp-debug
* Description: Sistema avanzado de debug y diagnóstico para WordPress. Monitorea hooks, templates, assets, queries y performance en tiempo real.
* Version: 1.0.0
* Author: Developer Team
* Author URI: https://github.com/prime-leads-app
* License: GPL v2 or later
* License URI: https://www.gnu.org/licenses/gpl-2.0.html
* Text Domain: wp-debug
* Domain Path: /languages
* Requires at least: 6.0
* Requires PHP: 7.4
*
* @package WP_Debug
* @since 1.0.0
*/
// Exit if accessed directly
if (!defined('ABSPATH')) {
exit;
}
/**
* Plugin Constants
*/
define('WP_DEBUG_VERSION', '1.0.0');
define('WP_DEBUG_PLUGIN_DIR', plugin_dir_path(__FILE__));
define('WP_DEBUG_PLUGIN_URL', plugin_dir_url(__FILE__));
define('WP_DEBUG_PLUGIN_BASENAME', plugin_basename(__FILE__));
/**
* Activation Hook
*/
function wp_debug_activate() {
require_once WP_DEBUG_PLUGIN_DIR . 'inc/activation.php';
WP_Debug_Activation::activate();
}
register_activation_hook(__FILE__, 'wp_debug_activate');
/**
* Deactivation Hook
*/
function wp_debug_deactivate() {
require_once WP_DEBUG_PLUGIN_DIR . 'inc/deactivation.php';
WP_Debug_Deactivation::deactivate();
}
register_deactivation_hook(__FILE__, 'wp_debug_deactivate');
/**
* Autoloader - Load all modules
*/
require_once WP_DEBUG_PLUGIN_DIR . 'inc/autoloader.php';
/**
* Initialize the plugin
*/
function wp_debug_init() {
// Load text domain for translations
load_plugin_textdomain('wp-debug', false, dirname(WP_DEBUG_PLUGIN_BASENAME) . '/languages');
// Initialize autoloader
WP_Debug_Autoloader::init();
}
add_action('plugins_loaded', 'wp_debug_init');
/**
* Admin Menu
*/
function wp_debug_admin_menu() {
require_once WP_DEBUG_PLUGIN_DIR . 'inc/admin-menu.php';
WP_Debug_Admin_Menu::init();
}
add_action('admin_menu', 'wp_debug_admin_menu');
/**
* Public API Functions
*
* These functions can be used by themes and other plugins
*/
/**
* Log a debug message
*
* @param string $message Message to log
* @param string $level Log level: INFO, WARNING, ERROR, DEBUG
* @return bool True on success, false on failure
*/
function wp_debug_log($message, $level = 'INFO') {
if (class_exists('WP_Debug_Logger')) {
return WP_Debug_Logger::log($message, $level);
}
return false;
}
/**
* Start a performance timer
*
* @param string $name Timer name
* @return bool True on success
*/
function wp_debug_start_timer($name) {
if (class_exists('WP_Debug_Profiler')) {
return WP_Debug_Profiler::start_timer($name);
}
return false;
}
/**
* End a performance timer
*
* @param string $name Timer name
* @return float|bool Elapsed time in seconds, or false on failure
*/
function wp_debug_end_timer($name) {
if (class_exists('WP_Debug_Profiler')) {
return WP_Debug_Profiler::end_timer($name);
}
return false;
}
/**
* Get list of executed hooks
*
* @return array List of hooks
*/
function wp_debug_get_hooks() {
if (class_exists('WP_Debug_Hook_Monitor')) {
return WP_Debug_Hook_Monitor::get_hooks();
}
return array();
}
/**
* Get list of loaded templates
*
* @return array List of templates
*/
function wp_debug_get_templates() {
if (class_exists('WP_Debug_Template_Tracer')) {
return WP_Debug_Template_Tracer::get_templates();
}
return array();
}