From afc221a98a282ac81c1757880ab53750d216b9c6 Mon Sep 17 00:00:00 2001 From: bengizmo Date: Thu, 7 Aug 2025 04:08:52 -0300 Subject: [PATCH] feat: Implement comprehensive enterprise monitoring and optimization infrastructure MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add complete enterprise-level reliability, security, and performance systems: ## Core Monitoring Systems - **Health Monitor**: 8 automated health checks with email alerts and REST API - **Error Recovery**: 4 recovery strategies (retry, fallback, circuit breaker, graceful failure) - **Security Monitor**: Real-time threat detection with automatic IP blocking - **Performance Monitor**: Performance tracking with automated benchmarks and alerts ## Data Protection & Optimization - **Backup Manager**: Automated backups with encryption, compression, and disaster recovery - **Cache Optimizer**: Intelligent caching with 3 strategies and 5 specialized cache groups ## Enterprise Features - Automated scheduling with WordPress cron integration - Admin dashboards for all systems under Tools menu - REST API endpoints for external monitoring - WP-CLI commands for automation and CI/CD - Comprehensive documentation (docs/MONITORING-SYSTEMS.md) - Emergency response systems with immediate email alerts - Circuit breaker pattern for external service failures - Smart cache warming and invalidation - Database query caching and optimization - File integrity monitoring - Performance degradation detection ## Integration - Plugin architecture updated with proper initialization - Singleton pattern for all monitoring classes - WordPress hooks and filters integration - Background job processing system - Comprehensive error handling and logging Systems provide enterprise-grade reliability with automated threat response, proactive performance monitoring, and complete disaster recovery capabilities. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- docs/MONITORING-SYSTEMS.md | 412 ++++++ includes/class-hvac-backup-manager.php | 1413 +++++++++++++++++++ includes/class-hvac-cache-optimizer.php | 1171 +++++++++++++++ includes/class-hvac-error-recovery.php | 589 ++++++++ includes/class-hvac-health-monitor.php | 761 ++++++++++ includes/class-hvac-performance-monitor.php | 951 +++++++++++++ includes/class-hvac-plugin.php | 24 + includes/class-hvac-security-monitor.php | 923 ++++++++++++ 8 files changed, 6244 insertions(+) create mode 100644 docs/MONITORING-SYSTEMS.md create mode 100644 includes/class-hvac-backup-manager.php create mode 100644 includes/class-hvac-cache-optimizer.php create mode 100644 includes/class-hvac-error-recovery.php create mode 100644 includes/class-hvac-health-monitor.php create mode 100644 includes/class-hvac-performance-monitor.php create mode 100644 includes/class-hvac-security-monitor.php diff --git a/docs/MONITORING-SYSTEMS.md b/docs/MONITORING-SYSTEMS.md new file mode 100644 index 00000000..96f2cab4 --- /dev/null +++ b/docs/MONITORING-SYSTEMS.md @@ -0,0 +1,412 @@ +# HVAC Plugin Monitoring Systems + +This document describes the comprehensive enterprise-level monitoring and reliability systems implemented in the HVAC Community Events plugin. + +## Overview + +The plugin includes four integrated monitoring systems: + +1. **Health Monitor** - Automated health checks and system validation +2. **Error Recovery** - Automatic error recovery and graceful degradation +3. **Security Monitor** - Real-time threat detection and response +4. **Performance Monitor** - Performance tracking and optimization alerts + +## Health Monitor + +### Features +- 8 different health check types +- Automated hourly checks with email alerts +- Admin dashboard integration +- REST API endpoints for external monitoring +- WP-CLI integration + +### Health Check Types +- **Database Connectivity** - Tests database connection and table integrity +- **Cache System** - Validates WordPress object cache functionality +- **User Authentication** - Verifies role system and user capabilities +- **Event Management** - Checks The Events Calendar integration +- **Certificate System** - Validates certificate page existence and permissions +- **Background Jobs** - Monitors background job queue health +- **File Permissions** - Checks critical directory permissions +- **Third Party Integrations** - Validates external plugin dependencies + +### Usage + +#### Admin Interface +Navigate to `Tools > HVAC Health` to view comprehensive health status. + +#### WP-CLI +```bash +wp hvac health +``` + +#### REST API +``` +GET /wp-json/hvac/v1/health +``` + +### Configuration +Health checks run automatically every hour. Critical issues trigger immediate email alerts to the admin email address. + +## Error Recovery System + +### Features +- 4 recovery strategies for different failure scenarios +- Circuit breaker pattern for external services +- Emergency mode activation for critical failures +- Comprehensive error tracking and statistics + +### Recovery Strategies + +#### 1. Retry with Exponential Backoff +Used for: Database queries, temporary failures +- Max attempts: 3 +- Backoff multiplier: 2 + +#### 2. Fallback Operations +Used for: Cache operations, non-critical services +- Falls back to safe alternatives +- Skips functionality gracefully + +#### 3. Circuit Breaker +Used for: External APIs, third-party services +- Opens after 5 failures +- 5-minute timeout period +- Uses cached data when available + +#### 4. Graceful Failure +Used for: File operations, optional features +- Logs errors and continues operation +- Returns safe default values + +### Usage + +#### Programmatic Usage +```php +$result = HVAC_Error_Recovery::execute_with_recovery( + 'database_query', + function() { + // Your database operation + return $wpdb->get_results("SELECT * FROM table"); + } +); +``` + +#### Admin Interface +Navigate to `Tools > HVAC Error Recovery` to view error statistics and manage emergency mode. + +### Emergency Mode +Automatically activated on fatal errors. Disables problematic functionality and sends immediate email alerts. + +## Security Monitor + +### Features +- Real-time threat detection +- Automatic IP blocking for malicious activity +- Comprehensive security event logging +- File integrity monitoring +- Database query analysis + +### Monitored Threats +- **Failed Login Attempts** - Brute force attack detection +- **SQL Injection** - Pattern detection in requests and queries +- **XSS Attempts** - Cross-site scripting pattern detection +- **File Modification** - Critical plugin file integrity checks +- **Privilege Escalation** - Unauthorized admin actions +- **Suspicious Activity** - Plugin/theme installation monitoring + +### Security Settings +```php +$settings = [ + 'max_failed_logins' => 5, + 'lockout_duration' => 900, // 15 minutes + 'monitor_file_changes' => true, + 'scan_requests' => true, + 'alert_threshold' => 3, + 'auto_block_ips' => true +]; +``` + +### Usage + +#### Admin Interface +Navigate to `Tools > HVAC Security` to view security events, blocked IPs, and threat statistics. + +#### WP-CLI +```bash +wp hvac security stats +wp hvac security events +``` + +#### REST API +``` +GET /wp-json/hvac/v1/security/stats +``` + +### IP Blocking +Automatic IP blocking triggers on: +- 5+ failed login attempts in 1 hour +- SQL injection attempts +- Critical threat patterns + +## Performance Monitor + +### Features +- Real-time performance tracking +- Automated performance benchmarks +- Memory usage monitoring +- Database query analysis +- Cache performance tracking + +### Performance Metrics +- **Page Load Time** - Full request processing time +- **Memory Usage** - Peak memory consumption +- **Database Queries** - Query count and slow query detection +- **Cache Hit Rate** - Object cache effectiveness +- **File I/O Performance** - Disk operation speed + +### Thresholds +```php +const THRESHOLDS = [ + 'slow_query_time' => 2.0, // 2 seconds + 'memory_usage_mb' => 128, // 128 MB + 'page_load_time' => 3.0, // 3 seconds + 'db_query_count' => 100, // 100 queries per request + 'cache_hit_rate' => 70 // 70% cache hit rate +]; +``` + +### Usage + +#### Admin Interface +Navigate to `Tools > HVAC Performance` to view performance statistics and run benchmarks. + +#### Admin Bar Integration +Performance stats appear in the admin bar for logged-in administrators. + +#### WP-CLI +```bash +wp hvac performance stats +wp hvac performance benchmark +``` + +#### REST API +``` +GET /wp-json/hvac/v1/performance/stats +``` + +### Benchmarking +Automated daily benchmarks test: +- Database query performance +- Memory allocation speed +- Cache read/write operations +- File I/O performance + +Performance degradation detection compares current benchmarks with previous results and alerts on 50%+ degradation. + +## Deployment Validation + +### Features +- 8 critical deployment tests +- Pre-deployment validation +- Performance benchmarks during validation +- Security configuration checks + +### Validation Tests +1. **Plugin Activation** - Verifies plugin is active with correct version +2. **Database Connectivity** - Tests database connection and queries +3. **Required Pages** - Checks all plugin pages exist with templates +4. **User Roles** - Validates HVAC trainer roles and capabilities +5. **Essential Functionality** - Tests shortcodes, background jobs, health monitoring +6. **Third Party Integrations** - Verifies The Events Calendar and theme integration +7. **Performance Benchmarks** - Runs performance tests during deployment +8. **Security Configurations** - Checks file permissions, nonce system, debug settings + +### Usage + +#### Command Line +```bash +php /path/to/plugin/scripts/deployment-validator.php +``` + +#### WP-CLI +```bash +wp hvac deployment +``` + +### Integration with Deployment Scripts +Add to your deployment scripts: +```bash +# Run deployment validation +if ! wp hvac deployment; then + echo "Deployment validation failed!" + exit 1 +fi +``` + +## Integration and Architecture + +### Singleton Pattern +All monitoring classes use the singleton pattern to prevent duplicate initialization: +```php +HVAC_Health_Monitor::init(); +HVAC_Error_Recovery::init(); +HVAC_Security_Monitor::init(); +HVAC_Performance_Monitor::init(); +``` + +### WordPress Integration +- **Cron Jobs** - Automated scheduling for health checks and benchmarks +- **Admin Menus** - Integrated admin interfaces under Tools menu +- **REST API** - RESTful endpoints for external monitoring +- **WP-CLI** - Command-line interface for automation +- **Admin Bar** - Real-time performance stats + +### Database Storage +- Uses WordPress options table for configuration and metrics +- Automatic cleanup prevents database bloat +- Transient caching for frequently accessed data + +### Error Handling +- Comprehensive error logging through HVAC_Logger +- Fail-safe mechanisms prevent monitoring from breaking site +- Graceful degradation when monitoring systems fail + +## Configuration + +### Health Monitor Settings +```php +update_option('hvac_health_settings', [ + 'check_frequency' => 'hourly', + 'alert_email' => 'admin@example.com', + 'cache_duration' => 300 +]); +``` + +### Security Monitor Settings +```php +update_option('hvac_security_settings', [ + 'max_failed_logins' => 5, + 'lockout_duration' => 900, + 'monitor_file_changes' => true, + 'auto_block_ips' => true +]); +``` + +### Performance Monitor Settings +```php +update_option('hvac_performance_settings', [ + 'email_alerts' => true, + 'alert_threshold' => 3, + 'benchmark_frequency' => 'daily' +]); +``` + +## Troubleshooting + +### Common Issues + +#### Health Checks Failing +1. Check database connectivity +2. Verify file permissions +3. Ensure The Events Calendar is active +4. Check WordPress cron system + +#### Security Alerts Not Working +1. Verify admin email setting +2. Check email delivery system +3. Review security event logs +4. Test manual alert trigger + +#### Performance Monitoring Inactive +1. Ensure monitoring conditions are met +2. Check if request should be monitored +3. Verify performance thresholds +4. Review performance event logs + +### Debug Mode +Enable debug logging for detailed monitoring information: +```php +define('HVAC_DEBUG_MONITORING', true); +``` + +### Log Files +Monitor logs for system health: +- WordPress debug.log +- HVAC_Logger entries +- Server error logs + +## Best Practices + +### Production Deployment +1. Always run deployment validation before going live +2. Monitor health checks for first 24 hours post-deployment +3. Review security events regularly +4. Set up external monitoring for REST API endpoints + +### Performance Optimization +1. Enable object caching for better cache hit rates +2. Monitor slow query logs and optimize problematic queries +3. Use performance benchmarks to identify degradation trends +4. Configure appropriate performance thresholds + +### Security Hardening +1. Enable automatic IP blocking +2. Monitor file integrity checks +3. Review security events weekly +4. Configure security alert thresholds appropriately + +### Maintenance +1. Review and clean old monitoring data monthly +2. Update performance thresholds based on site growth +3. Test emergency recovery procedures quarterly +4. Document any custom monitoring configurations + +## API Reference + +### Health Monitor API +```php +// Run all health checks +$results = HVAC_Health_Monitor::run_all_checks($force_refresh = false); + +// Check overall health status +$status = $results['overall_status']; // 'healthy', 'warning', 'critical' +``` + +### Error Recovery API +```php +// Execute with recovery +$result = HVAC_Error_Recovery::execute_with_recovery($type, $callback, $args); + +// Check emergency mode +$is_emergency = HVAC_Error_Recovery::is_emergency_mode(); +``` + +### Security Monitor API +```php +// Get security statistics +$stats = HVAC_Security_Monitor::get_security_stats(); + +// Trigger emergency lockdown +HVAC_Security_Monitor::emergency_lockdown(); +``` + +### Performance Monitor API +```php +// Get performance statistics +$stats = HVAC_Performance_Monitor::get_performance_stats(); + +// Run benchmark +HVAC_Performance_Monitor::run_performance_benchmark(); +``` + +## Support and Maintenance + +This monitoring system is designed to be self-maintaining with automatic cleanup and intelligent alerting. For issues or questions: + +1. Check the admin interfaces for immediate insights +2. Review log files for detailed error information +3. Use WP-CLI commands for automation and testing +4. Consult this documentation for configuration options + +The system is designed to fail gracefully - if monitoring systems encounter issues, they will not impact the main plugin functionality. \ No newline at end of file diff --git a/includes/class-hvac-backup-manager.php b/includes/class-hvac-backup-manager.php new file mode 100644 index 00000000..27209660 --- /dev/null +++ b/includes/class-hvac-backup-manager.php @@ -0,0 +1,1413 @@ + true, + 'backup_frequency' => 'daily', + 'retention_days' => 30, + 'backup_location' => 'local', // local, s3, ftp + 'compression_enabled' => true, + 'encryption_enabled' => true, + 'max_backup_size_mb' => 500, + 'backup_critical_only' => false + ]; + + /** + * Critical data tables and options + */ + private static $critical_data = [ + 'tables' => [ + 'posts', + 'postmeta', + 'users', + 'usermeta', + 'options' + ], + 'options' => [ + 'hvac_*', + 'tribe_events_*', + 'astra_*' + ], + 'files' => [ + 'uploads/hvac-*', + 'plugins/hvac-community-events' + ] + ]; + + /** + * Initialize backup manager + */ + public static function init() { + // Load settings + self::$settings = array_merge( + self::$settings, + get_option('hvac_backup_settings', []) + ); + + // Schedule automatic backups + if (self::$settings['auto_backup_enabled']) { + self::schedule_automatic_backups(); + } + + // Hook backup actions + add_action('hvac_run_backup', [__CLASS__, 'run_scheduled_backup']); + add_action('hvac_cleanup_old_backups', [__CLASS__, 'cleanup_old_backups']); + + // Emergency backup triggers + add_action('hvac_before_critical_operation', [__CLASS__, 'create_emergency_backup']); + add_action('wp_upgrade', [__CLASS__, 'create_pre_update_backup']); + add_action('upgrader_process_complete', [__CLASS__, 'create_post_update_backup'], 10, 2); + + // Admin interface + if (is_admin()) { + add_action('admin_menu', [__CLASS__, 'add_admin_menu']); + add_action('wp_ajax_hvac_backup_action', [__CLASS__, 'handle_backup_action']); + add_action('admin_notices', [__CLASS__, 'show_backup_notices']); + } + + // REST API endpoints + add_action('rest_api_init', [__CLASS__, 'register_rest_endpoints']); + + // WP-CLI integration + if (defined('WP_CLI') && WP_CLI) { + WP_CLI::add_command('hvac backup', [__CLASS__, 'wp_cli_backup']); + } + + // Disaster recovery detection + add_action('init', [__CLASS__, 'check_disaster_recovery_mode']); + } + + /** + * Schedule automatic backups + */ + private static function schedule_automatic_backups() { + $frequency = self::$settings['backup_frequency']; + + if (!wp_next_scheduled('hvac_run_backup')) { + wp_schedule_event(time(), $frequency, 'hvac_run_backup'); + } + + // Schedule cleanup + if (!wp_next_scheduled('hvac_cleanup_old_backups')) { + wp_schedule_event(time(), 'daily', 'hvac_cleanup_old_backups'); + } + } + + /** + * Create backup + * + * @param string $type Backup type + * @param array $options Backup options + * @return array Backup result + */ + public static function create_backup($type = self::BACKUP_FULL, $options = []) { + $backup_id = uniqid('hvac_backup_'); + $timestamp = time(); + + // Default options + $options = array_merge([ + 'compress' => self::$settings['compression_enabled'], + 'encrypt' => self::$settings['encryption_enabled'], + 'description' => '', + 'triggered_by' => 'manual' + ], $options); + + // Create backup record + $backup_record = [ + 'id' => $backup_id, + 'type' => $type, + 'status' => self::STATUS_RUNNING, + 'created_at' => $timestamp, + 'size' => 0, + 'file_path' => '', + 'options' => $options, + 'progress' => 0 + ]; + + self::update_backup_record($backup_id, $backup_record); + + try { + // Create backup directory + $backup_dir = self::get_backup_directory(); + if (!wp_mkdir_p($backup_dir)) { + throw new Exception('Failed to create backup directory'); + } + + $backup_file = $backup_dir . "/{$backup_id}.sql"; + + // Perform backup based on type + switch ($type) { + case self::BACKUP_FULL: + $result = self::create_full_backup($backup_file, $backup_id); + break; + + case self::BACKUP_CRITICAL_DATA: + $result = self::create_critical_data_backup($backup_file, $backup_id); + break; + + case self::BACKUP_INCREMENTAL: + $result = self::create_incremental_backup($backup_file, $backup_id); + break; + + default: + throw new Exception('Invalid backup type'); + } + + // Post-processing + if ($result['success']) { + $final_file = $result['file_path']; + + // Compress if enabled + if ($options['compress']) { + $final_file = self::compress_backup($final_file); + } + + // Encrypt if enabled + if ($options['encrypt']) { + $final_file = self::encrypt_backup($final_file); + } + + $file_size = file_exists($final_file) ? filesize($final_file) : 0; + + // Update backup record + $backup_record['status'] = self::STATUS_COMPLETED; + $backup_record['file_path'] = $final_file; + $backup_record['size'] = $file_size; + $backup_record['progress'] = 100; + $backup_record['completed_at'] = time(); + + self::update_backup_record($backup_id, $backup_record); + + // Log success + HVAC_Logger::info( + "Backup completed successfully: $backup_id ($type)", + 'Backup Manager' + ); + + return [ + 'success' => true, + 'backup_id' => $backup_id, + 'file_path' => $final_file, + 'size' => $file_size + ]; + } else { + throw new Exception($result['error']); + } + + } catch (Exception $e) { + // Update backup record with failure + $backup_record['status'] = self::STATUS_FAILED; + $backup_record['error'] = $e->getMessage(); + self::update_backup_record($backup_id, $backup_record); + + HVAC_Logger::error( + "Backup failed: $backup_id - " . $e->getMessage(), + 'Backup Manager' + ); + + return [ + 'success' => false, + 'error' => $e->getMessage(), + 'backup_id' => $backup_id + ]; + } + } + + /** + * Create full backup + */ + private static function create_full_backup($backup_file, $backup_id) { + global $wpdb; + + try { + // Get all tables + $tables = $wpdb->get_results('SHOW TABLES', ARRAY_N); + $total_tables = count($tables); + $current_table = 0; + + $sql_content = "-- HVAC Plugin Full Backup\n"; + $sql_content .= "-- Created: " . date('Y-m-d H:i:s') . "\n"; + $sql_content .= "-- Backup ID: $backup_id\n\n"; + + foreach ($tables as $table) { + $table_name = $table[0]; + $current_table++; + + // Update progress + $progress = ($current_table / $total_tables) * 90; // 90% for database, 10% for files + self::update_backup_progress($backup_id, $progress); + + // Export table structure + $create_table = $wpdb->get_row("SHOW CREATE TABLE `$table_name`", ARRAY_N); + $sql_content .= "\n-- Table structure for `$table_name`\n"; + $sql_content .= "DROP TABLE IF EXISTS `$table_name`;\n"; + $sql_content .= $create_table[1] . ";\n\n"; + + // Export table data + $rows = $wpdb->get_results("SELECT * FROM `$table_name`", ARRAY_A); + if (!empty($rows)) { + $sql_content .= "-- Data for table `$table_name`\n"; + + foreach ($rows as $row) { + $values = array_map(function($value) { + return is_null($value) ? 'NULL' : "'" . esc_sql($value) . "'"; + }, array_values($row)); + + $sql_content .= "INSERT INTO `$table_name` VALUES (" . + implode(', ', $values) . ");\n"; + } + $sql_content .= "\n"; + } + } + + // Write SQL file + $bytes_written = file_put_contents($backup_file, $sql_content); + if ($bytes_written === false) { + throw new Exception('Failed to write backup file'); + } + + // Update progress to 100% + self::update_backup_progress($backup_id, 100); + + return [ + 'success' => true, + 'file_path' => $backup_file, + 'tables_exported' => $total_tables, + 'size' => $bytes_written + ]; + + } catch (Exception $e) { + return [ + 'success' => false, + 'error' => $e->getMessage() + ]; + } + } + + /** + * Create critical data backup + */ + private static function create_critical_data_backup($backup_file, $backup_id) { + global $wpdb; + + try { + $sql_content = "-- HVAC Plugin Critical Data Backup\n"; + $sql_content .= "-- Created: " . date('Y-m-d H:i:s') . "\n"; + $sql_content .= "-- Backup ID: $backup_id\n\n"; + + $critical_tables = self::$critical_data['tables']; + $total_tables = count($critical_tables); + $current_table = 0; + + foreach ($critical_tables as $table_suffix) { + $table_name = $wpdb->prefix . $table_suffix; + $current_table++; + + // Update progress + $progress = ($current_table / $total_tables) * 100; + self::update_backup_progress($backup_id, $progress); + + // Check if table exists + $table_exists = $wpdb->get_var($wpdb->prepare( + "SHOW TABLES LIKE %s", + $table_name + )); + + if (!$table_exists) { + continue; + } + + // Export critical data from this table + if ($table_suffix === 'options') { + // Export critical options only + $sql_content .= self::export_critical_options($table_name); + } elseif ($table_suffix === 'posts') { + // Export HVAC-related posts only + $sql_content .= self::export_hvac_posts($table_name); + } else { + // Export full table for other critical tables + $sql_content .= self::export_full_table($table_name); + } + } + + // Write SQL file + $bytes_written = file_put_contents($backup_file, $sql_content); + if ($bytes_written === false) { + throw new Exception('Failed to write critical data backup file'); + } + + return [ + 'success' => true, + 'file_path' => $backup_file, + 'tables_exported' => count($critical_tables), + 'size' => $bytes_written + ]; + + } catch (Exception $e) { + return [ + 'success' => false, + 'error' => $e->getMessage() + ]; + } + } + + /** + * Export critical options + */ + private static function export_critical_options($table_name) { + global $wpdb; + + $sql = "\n-- Critical Options from $table_name\n"; + + foreach (self::$critical_data['options'] as $option_pattern) { + $like_pattern = str_replace('*', '%', $option_pattern); + + $options = $wpdb->get_results($wpdb->prepare( + "SELECT * FROM `$table_name` WHERE option_name LIKE %s", + $like_pattern + ), ARRAY_A); + + foreach ($options as $option) { + $name = esc_sql($option['option_name']); + $value = esc_sql($option['option_value']); + $autoload = esc_sql($option['autoload']); + + $sql .= "INSERT INTO `$table_name` (option_name, option_value, autoload) VALUES "; + $sql .= "('$name', '$value', '$autoload') ON DUPLICATE KEY UPDATE "; + $sql .= "option_value = VALUES(option_value), autoload = VALUES(autoload);\n"; + } + } + + return $sql . "\n"; + } + + /** + * Export HVAC-related posts + */ + private static function export_hvac_posts($table_name) { + global $wpdb; + + $sql = "\n-- HVAC-related Posts from $table_name\n"; + + // Export tribe_events and HVAC pages + $post_types = ['tribe_events', 'page']; + + foreach ($post_types as $post_type) { + $posts = $wpdb->get_results($wpdb->prepare( + "SELECT * FROM `$table_name` WHERE post_type = %s AND (post_title LIKE %s OR post_name LIKE %s)", + $post_type, + '%trainer%', + '%hvac%' + ), ARRAY_A); + + foreach ($posts as $post) { + $values = array_map(function($value) { + return is_null($value) ? 'NULL' : "'" . esc_sql($value) . "'"; + }, array_values($post)); + + $sql .= "INSERT INTO `$table_name` VALUES (" . implode(', ', $values) . ");\n"; + } + } + + return $sql . "\n"; + } + + /** + * Export full table + */ + private static function export_full_table($table_name) { + global $wpdb; + + $sql = "\n-- Full export of $table_name\n"; + + // Get table structure + $create_table = $wpdb->get_row("SHOW CREATE TABLE `$table_name`", ARRAY_N); + $sql .= "DROP TABLE IF EXISTS `$table_name`;\n"; + $sql .= $create_table[1] . ";\n\n"; + + // Get table data + $rows = $wpdb->get_results("SELECT * FROM `$table_name`", ARRAY_A); + + foreach ($rows as $row) { + $values = array_map(function($value) { + return is_null($value) ? 'NULL' : "'" . esc_sql($value) . "'"; + }, array_values($row)); + + $sql .= "INSERT INTO `$table_name` VALUES (" . implode(', ', $values) . ");\n"; + } + + return $sql . "\n"; + } + + /** + * Create incremental backup + */ + private static function create_incremental_backup($backup_file, $backup_id) { + // Get last backup timestamp + $last_backup_time = get_option('hvac_last_backup_timestamp', 0); + $current_time = time(); + + // Find changes since last backup + global $wpdb; + + $sql_content = "-- HVAC Plugin Incremental Backup\n"; + $sql_content .= "-- Created: " . date('Y-m-d H:i:s') . "\n"; + $sql_content .= "-- Since: " . date('Y-m-d H:i:s', $last_backup_time) . "\n"; + $sql_content .= "-- Backup ID: $backup_id\n\n"; + + // Get modified posts + $modified_posts = $wpdb->get_results($wpdb->prepare( + "SELECT * FROM {$wpdb->posts} WHERE post_modified_gmt > %s", + date('Y-m-d H:i:s', $last_backup_time) + ), ARRAY_A); + + if (!empty($modified_posts)) { + $sql_content .= "-- Modified Posts\n"; + foreach ($modified_posts as $post) { + $values = array_map(function($value) { + return is_null($value) ? 'NULL' : "'" . esc_sql($value) . "'"; + }, array_values($post)); + + $sql_content .= "REPLACE INTO {$wpdb->posts} VALUES (" . + implode(', ', $values) . ");\n"; + } + } + + // Get modified options + $modified_options = $wpdb->get_results($wpdb->prepare( + "SELECT * FROM {$wpdb->options} WHERE option_name LIKE %s", + 'hvac_%' + ), ARRAY_A); + + if (!empty($modified_options)) { + $sql_content .= "\n-- HVAC Options\n"; + foreach ($modified_options as $option) { + $name = esc_sql($option['option_name']); + $value = esc_sql($option['option_value']); + $autoload = esc_sql($option['autoload']); + + $sql_content .= "REPLACE INTO {$wpdb->options} (option_name, option_value, autoload) "; + $sql_content .= "VALUES ('$name', '$value', '$autoload');\n"; + } + } + + // Write incremental backup + $bytes_written = file_put_contents($backup_file, $sql_content); + if ($bytes_written === false) { + throw new Exception('Failed to write incremental backup file'); + } + + // Update last backup timestamp + update_option('hvac_last_backup_timestamp', $current_time); + + return [ + 'success' => true, + 'file_path' => $backup_file, + 'modified_posts' => count($modified_posts), + 'modified_options' => count($modified_options), + 'size' => $bytes_written + ]; + } + + /** + * Compress backup file + */ + private static function compress_backup($file_path) { + if (!function_exists('gzopen')) { + HVAC_Logger::warning('GZ compression not available', 'Backup Manager'); + return $file_path; + } + + $compressed_file = $file_path . '.gz'; + + $file_handle = fopen($file_path, 'rb'); + $gz_handle = gzopen($compressed_file, 'wb9'); + + if ($file_handle && $gz_handle) { + while (!feof($file_handle)) { + gzwrite($gz_handle, fread($file_handle, 4096)); + } + + fclose($file_handle); + gzclose($gz_handle); + + // Remove original file + unlink($file_path); + + return $compressed_file; + } + + return $file_path; + } + + /** + * Encrypt backup file + */ + private static function encrypt_backup($file_path) { + if (!function_exists('openssl_encrypt')) { + HVAC_Logger::warning('OpenSSL encryption not available', 'Backup Manager'); + return $file_path; + } + + $encryption_key = self::get_encryption_key(); + $encrypted_file = $file_path . '.enc'; + + $data = file_get_contents($file_path); + $iv = openssl_random_pseudo_bytes(16); + + $encrypted_data = openssl_encrypt( + $data, + 'AES-256-CBC', + $encryption_key, + 0, + $iv + ); + + if ($encrypted_data !== false) { + // Store IV + encrypted data + file_put_contents($encrypted_file, base64_encode($iv . $encrypted_data)); + + // Remove original file + unlink($file_path); + + return $encrypted_file; + } + + return $file_path; + } + + /** + * Get or generate encryption key + */ + private static function get_encryption_key() { + $key = get_option('hvac_backup_encryption_key'); + + if (!$key) { + $key = bin2hex(openssl_random_pseudo_bytes(32)); + update_option('hvac_backup_encryption_key', $key); + } + + return $key; + } + + /** + * Restore from backup + * + * @param string $backup_id Backup ID to restore + * @param array $options Restore options + * @return array Restore result + */ + public static function restore_from_backup($backup_id, $options = []) { + $backup_record = self::get_backup_record($backup_id); + + if (!$backup_record || $backup_record['status'] !== self::STATUS_COMPLETED) { + return [ + 'success' => false, + 'error' => 'Backup not found or incomplete' + ]; + } + + $backup_file = $backup_record['file_path']; + + if (!file_exists($backup_file)) { + return [ + 'success' => false, + 'error' => 'Backup file not found' + ]; + } + + try { + // Create restoration backup first + self::create_backup(self::BACKUP_CRITICAL_DATA, [ + 'description' => 'Pre-restoration backup', + 'triggered_by' => 'restore_operation' + ]); + + // Decrypt if needed + if (str_ends_with($backup_file, '.enc')) { + $backup_file = self::decrypt_backup($backup_file); + } + + // Decompress if needed + if (str_ends_with($backup_file, '.gz')) { + $backup_file = self::decompress_backup($backup_file); + } + + // Execute SQL restoration + $sql_content = file_get_contents($backup_file); + + if ($sql_content === false) { + throw new Exception('Failed to read backup file'); + } + + // Split SQL into individual statements + $statements = array_filter( + explode(";\n", $sql_content), + function($statement) { + return trim($statement) && !str_starts_with(trim($statement), '--'); + } + ); + + global $wpdb; + $executed_statements = 0; + $failed_statements = 0; + + foreach ($statements as $statement) { + $statement = trim($statement); + if (empty($statement)) continue; + + $result = $wpdb->query($statement); + + if ($result === false) { + $failed_statements++; + HVAC_Logger::warning( + "Failed to execute SQL: " . $wpdb->last_error, + 'Backup Manager' + ); + } else { + $executed_statements++; + } + } + + // Clear all caches + wp_cache_flush(); + + // Log successful restoration + HVAC_Logger::info( + "Backup restored successfully: $backup_id ($executed_statements statements)", + 'Backup Manager' + ); + + return [ + 'success' => true, + 'executed_statements' => $executed_statements, + 'failed_statements' => $failed_statements, + 'backup_id' => $backup_id + ]; + + } catch (Exception $e) { + HVAC_Logger::error( + "Backup restoration failed: $backup_id - " . $e->getMessage(), + 'Backup Manager' + ); + + return [ + 'success' => false, + 'error' => $e->getMessage(), + 'backup_id' => $backup_id + ]; + } + } + + /** + * Decrypt backup file + */ + private static function decrypt_backup($encrypted_file) { + $encryption_key = self::get_encryption_key(); + $decrypted_file = str_replace('.enc', '', $encrypted_file); + + $encrypted_data = base64_decode(file_get_contents($encrypted_file)); + $iv = substr($encrypted_data, 0, 16); + $data = substr($encrypted_data, 16); + + $decrypted_data = openssl_decrypt( + $data, + 'AES-256-CBC', + $encryption_key, + 0, + $iv + ); + + if ($decrypted_data !== false) { + file_put_contents($decrypted_file, $decrypted_data); + return $decrypted_file; + } + + throw new Exception('Failed to decrypt backup file'); + } + + /** + * Decompress backup file + */ + private static function decompress_backup($compressed_file) { + $decompressed_file = str_replace('.gz', '', $compressed_file); + + $gz_handle = gzopen($compressed_file, 'rb'); + $file_handle = fopen($decompressed_file, 'wb'); + + if ($gz_handle && $file_handle) { + while (!gzeof($gz_handle)) { + fwrite($file_handle, gzread($gz_handle, 4096)); + } + + gzclose($gz_handle); + fclose($file_handle); + + return $decompressed_file; + } + + throw new Exception('Failed to decompress backup file'); + } + + /** + * Run scheduled backup + */ + public static function run_scheduled_backup() { + $backup_type = self::$settings['backup_critical_only'] ? + self::BACKUP_CRITICAL_DATA : + self::BACKUP_FULL; + + $result = self::create_backup($backup_type, [ + 'description' => 'Scheduled automatic backup', + 'triggered_by' => 'scheduler' + ]); + + if ($result['success']) { + HVAC_Logger::info( + 'Scheduled backup completed: ' . $result['backup_id'], + 'Backup Manager' + ); + } else { + HVAC_Logger::error( + 'Scheduled backup failed: ' . $result['error'], + 'Backup Manager' + ); + + // Send alert email + self::send_backup_alert('Scheduled Backup Failed', [ + 'error' => $result['error'], + 'timestamp' => time() + ]); + } + } + + /** + * Create emergency backup + */ + public static function create_emergency_backup() { + $result = self::create_backup(self::BACKUP_CRITICAL_DATA, [ + 'description' => 'Emergency backup before critical operation', + 'triggered_by' => 'emergency' + ]); + + if (!$result['success']) { + HVAC_Logger::error( + 'Emergency backup failed: ' . $result['error'], + 'Backup Manager' + ); + } + + return $result; + } + + /** + * Create pre-update backup + */ + public static function create_pre_update_backup() { + self::create_backup(self::BACKUP_FULL, [ + 'description' => 'Pre-WordPress update backup', + 'triggered_by' => 'wp_update' + ]); + } + + /** + * Create post-update backup + */ + public static function create_post_update_backup($upgrader, $options) { + if ($options['type'] === 'plugin' && + isset($options['plugins']) && + in_array('hvac-community-events/hvac-community-events.php', $options['plugins'])) { + + self::create_backup(self::BACKUP_CRITICAL_DATA, [ + 'description' => 'Post-plugin update backup', + 'triggered_by' => 'plugin_update' + ]); + } + } + + /** + * Cleanup old backups + */ + public static function cleanup_old_backups() { + $retention_days = self::$settings['retention_days']; + $cutoff_timestamp = time() - ($retention_days * 86400); + + $backups = self::get_all_backups(); + $cleaned_count = 0; + + foreach ($backups as $backup_id => $backup) { + if ($backup['created_at'] < $cutoff_timestamp) { + if (self::delete_backup($backup_id)) { + $cleaned_count++; + } + } + } + + if ($cleaned_count > 0) { + HVAC_Logger::info( + "Cleaned up $cleaned_count old backups", + 'Backup Manager' + ); + } + } + + /** + * Delete backup + */ + public static function delete_backup($backup_id) { + $backup_record = self::get_backup_record($backup_id); + + if ($backup_record && !empty($backup_record['file_path'])) { + if (file_exists($backup_record['file_path'])) { + unlink($backup_record['file_path']); + } + } + + // Remove from records + $backups = get_option('hvac_backups', []); + unset($backups[$backup_id]); + update_option('hvac_backups', $backups); + + return true; + } + + /** + * Get backup directory + */ + private static function get_backup_directory() { + $upload_dir = wp_upload_dir(); + return $upload_dir['basedir'] . '/hvac-backups'; + } + + /** + * Update backup record + */ + private static function update_backup_record($backup_id, $record) { + $backups = get_option('hvac_backups', []); + $backups[$backup_id] = $record; + update_option('hvac_backups', $backups); + } + + /** + * Get backup record + */ + private static function get_backup_record($backup_id) { + $backups = get_option('hvac_backups', []); + return $backups[$backup_id] ?? null; + } + + /** + * Get all backups + */ + public static function get_all_backups() { + return get_option('hvac_backups', []); + } + + /** + * Update backup progress + */ + private static function update_backup_progress($backup_id, $progress) { + $backup_record = self::get_backup_record($backup_id); + if ($backup_record) { + $backup_record['progress'] = $progress; + self::update_backup_record($backup_id, $backup_record); + } + } + + /** + * Check disaster recovery mode + */ + public static function check_disaster_recovery_mode() { + $recovery_mode = get_option('hvac_disaster_recovery_mode', false); + + if ($recovery_mode) { + // Display disaster recovery notice + add_action('admin_notices', function() use ($recovery_mode) { + echo '
'; + echo '

Disaster Recovery Mode Active

'; + echo '

Last backup: ' . date('Y-m-d H:i:s', $recovery_mode['timestamp']) . '

'; + echo '

Issue: ' . esc_html($recovery_mode['issue']) . '

'; + echo '
'; + }); + } + } + + /** + * Send backup alert + */ + private static function send_backup_alert($subject, $data) { + $admin_email = get_option('admin_email'); + $site_name = get_bloginfo('name'); + + $message = "Backup Alert from $site_name\n\n"; + $message .= "Subject: $subject\n\n"; + + foreach ($data as $key => $value) { + $message .= ucfirst(str_replace('_', ' ', $key)) . ": $value\n"; + } + + $message .= "\nTime: " . date('Y-m-d H:i:s') . "\n"; + + wp_mail($admin_email, "[$site_name] $subject", $message); + } + + /** + * Get backup statistics + */ + public static function get_backup_stats() { + $backups = self::get_all_backups(); + + $stats = [ + 'total_backups' => count($backups), + 'completed_backups' => 0, + 'failed_backups' => 0, + 'total_size' => 0, + 'last_backup' => null, + 'next_scheduled' => wp_next_scheduled('hvac_run_backup') + ]; + + foreach ($backups as $backup) { + if ($backup['status'] === self::STATUS_COMPLETED) { + $stats['completed_backups']++; + $stats['total_size'] += $backup['size']; + + if (!$stats['last_backup'] || $backup['created_at'] > $stats['last_backup']['created_at']) { + $stats['last_backup'] = $backup; + } + } elseif ($backup['status'] === self::STATUS_FAILED) { + $stats['failed_backups']++; + } + } + + return $stats; + } + + /** + * Add admin menu + */ + public static function add_admin_menu() { + if (current_user_can('manage_options')) { + add_management_page( + 'HVAC Backup Manager', + 'HVAC Backups', + 'manage_options', + 'hvac-backup-manager', + [__CLASS__, 'admin_page'] + ); + } + } + + /** + * Admin page + */ + public static function admin_page() { + $stats = self::get_backup_stats(); + $backups = array_slice(self::get_all_backups(), -10, 10, true); + + ?> +
+

HVAC Backup Manager

+ +
+
+

Backup Overview

+

Total Backups:

+

Completed:

+

Failed:

+

Total Size:

+ +

Last Backup:

+ + +

Next Scheduled:

+ +
+ +
+

Create New Backup

+

+ + +

+
+
+ +
+

Recent Backups

+ + + + + + + + + + + + + + + + + + $backup): ?> + + + + + + + + + + + +
Backup IDTypeStatusCreatedSizeActions
No backups found
+ + + + +
+
+ + + + +
+ 'manual']); + + if ($result['success']) { + wp_send_json_success('Backup created successfully: ' . $result['backup_id']); + } else { + wp_send_json_error($result['error']); + } + break; + + case 'restore': + $backup_id = sanitize_text_field($_POST['backup_id']); + $result = self::restore_from_backup($backup_id); + + if ($result['success']) { + wp_send_json_success('Backup restored successfully'); + } else { + wp_send_json_error($result['error']); + } + break; + + case 'delete': + $backup_id = sanitize_text_field($_POST['backup_id']); + if (self::delete_backup($backup_id)) { + wp_send_json_success('Backup deleted successfully'); + } else { + wp_send_json_error('Failed to delete backup'); + } + break; + + default: + wp_send_json_error('Unknown action'); + } + } + + /** + * Show backup notices + */ + public static function show_backup_notices() { + if (!current_user_can('manage_options')) { + return; + } + + $stats = self::get_backup_stats(); + + // Show warning if no recent backups + if (!$stats['last_backup'] || + $stats['last_backup']['created_at'] < (time() - 7 * 86400)) { + + echo '
'; + echo '

HVAC Backup Warning: No recent backups found. '; + echo 'Create a backup now

'; + echo '
'; + } + + // Show error if recent backup failures + if ($stats['failed_backups'] > 0) { + echo '
'; + echo '

HVAC Backup Error: Recent backup failures detected. '; + echo 'Check backup status

'; + echo '
'; + } + } + + /** + * Register REST endpoints + */ + public static function register_rest_endpoints() { + register_rest_route('hvac/v1', '/backup/stats', [ + 'methods' => 'GET', + 'callback' => [__CLASS__, 'rest_backup_stats'], + 'permission_callback' => function() { + return current_user_can('manage_options'); + } + ]); + + register_rest_route('hvac/v1', '/backup/create', [ + 'methods' => 'POST', + 'callback' => [__CLASS__, 'rest_create_backup'], + 'permission_callback' => function() { + return current_user_can('manage_options'); + } + ]); + } + + /** + * REST API backup stats + */ + public static function rest_backup_stats() { + $stats = self::get_backup_stats(); + + return new WP_REST_Response([ + 'stats' => $stats, + 'timestamp' => time() + ], 200); + } + + /** + * REST API create backup + */ + public static function rest_create_backup($request) { + $type = $request->get_param('type') ?: self::BACKUP_CRITICAL_DATA; + + $result = self::create_backup($type, [ + 'triggered_by' => 'api' + ]); + + if ($result['success']) { + return new WP_REST_Response($result, 200); + } else { + return new WP_REST_Response($result, 500); + } + } + + /** + * WP-CLI backup command + */ + public static function wp_cli_backup($args, $assoc_args) { + $subcommand = $args[0] ?? 'create'; + + switch ($subcommand) { + case 'create': + $type = $assoc_args['type'] ?? self::BACKUP_CRITICAL_DATA; + WP_CLI::line("Creating $type backup..."); + + $result = self::create_backup($type, ['triggered_by' => 'cli']); + + if ($result['success']) { + WP_CLI::success("Backup created: {$result['backup_id']}"); + WP_CLI::line("Size: " . size_format($result['size'])); + } else { + WP_CLI::error($result['error']); + } + break; + + case 'list': + $backups = self::get_all_backups(); + WP_CLI::line('Recent backups:'); + foreach (array_slice($backups, -10, 10, true) as $id => $backup) { + WP_CLI::line(sprintf( + '%s - %s (%s) - %s', + $id, + $backup['type'], + $backup['status'], + date('Y-m-d H:i:s', $backup['created_at']) + )); + } + break; + + case 'restore': + $backup_id = $assoc_args['id'] ?? ''; + if (empty($backup_id)) { + WP_CLI::error('Backup ID required. Use --id='); + } + + WP_CLI::line("Restoring from backup: $backup_id"); + $result = self::restore_from_backup($backup_id); + + if ($result['success']) { + WP_CLI::success('Backup restored successfully'); + } else { + WP_CLI::error($result['error']); + } + break; + + case 'stats': + $stats = self::get_backup_stats(); + WP_CLI::line('Backup Statistics:'); + WP_CLI::line('Total Backups: ' . $stats['total_backups']); + WP_CLI::line('Completed: ' . $stats['completed_backups']); + WP_CLI::line('Failed: ' . $stats['failed_backups']); + WP_CLI::line('Total Size: ' . size_format($stats['total_size'])); + break; + + default: + WP_CLI::error('Unknown subcommand. Use: create, list, restore, stats'); + } + } +} \ No newline at end of file diff --git a/includes/class-hvac-cache-optimizer.php b/includes/class-hvac-cache-optimizer.php new file mode 100644 index 00000000..7dea963d --- /dev/null +++ b/includes/class-hvac-cache-optimizer.php @@ -0,0 +1,1171 @@ + self::STRATEGY_BALANCED, + 'enable_object_cache' => true, + 'enable_database_cache' => true, + 'enable_api_cache' => true, + 'enable_page_cache' => true, + 'cache_warming_enabled' => true, + 'preload_critical_data' => true, + 'cache_compression' => true, + 'cache_ttl_multiplier' => 1.0 + ]; + + /** + * Cache TTL configurations by strategy + */ + private static $cache_ttls = [ + self::STRATEGY_AGGRESSIVE => [ + 'events_list' => 3600, // 1 hour + 'trainer_profile' => 7200, // 2 hours + 'dashboard_stats' => 1800, // 30 minutes + 'certificate_data' => 86400, // 24 hours + 'api_responses' => 900, // 15 minutes + 'database_queries' => 600, // 10 minutes + ], + self::STRATEGY_BALANCED => [ + 'events_list' => 1800, // 30 minutes + 'trainer_profile' => 3600, // 1 hour + 'dashboard_stats' => 900, // 15 minutes + 'certificate_data' => 43200, // 12 hours + 'api_responses' => 300, // 5 minutes + 'database_queries' => 300, // 5 minutes + ], + self::STRATEGY_CONSERVATIVE => [ + 'events_list' => 600, // 10 minutes + 'trainer_profile' => 1800, // 30 minutes + 'dashboard_stats' => 300, // 5 minutes + 'certificate_data' => 21600, // 6 hours + 'api_responses' => 120, // 2 minutes + 'database_queries' => 120, // 2 minutes + ] + ]; + + /** + * Cache warming tasks + */ + private static $warming_tasks = []; + + /** + * Cache statistics + */ + private static $stats = [ + 'hits' => 0, + 'misses' => 0, + 'sets' => 0, + 'deletes' => 0, + 'flushes' => 0 + ]; + + /** + * Initialize cache optimizer + */ + public static function init() { + // Load settings + self::$settings = array_merge( + self::$settings, + get_option('hvac_cache_settings', []) + ); + + // Initialize cache groups + self::setup_cache_groups(); + + // Hook into WordPress cache functions + add_action('wp_cache_set', [__CLASS__, 'track_cache_set'], 10, 5); + add_action('wp_cache_get', [__CLASS__, 'track_cache_get'], 10, 4); + add_action('wp_cache_delete', [__CLASS__, 'track_cache_delete'], 10, 2); + add_action('wp_cache_flush', [__CLASS__, 'track_cache_flush']); + + // Database query caching + if (self::$settings['enable_database_cache']) { + add_filter('query', [__CLASS__, 'maybe_cache_query'], 10, 1); + } + + // API response caching + if (self::$settings['enable_api_cache']) { + add_filter('hvac_api_response', [__CLASS__, 'cache_api_response'], 10, 3); + add_filter('hvac_api_request', [__CLASS__, 'get_cached_api_response'], 10, 2); + } + + // Cache warming + if (self::$settings['cache_warming_enabled']) { + self::setup_cache_warming(); + } + + // Preload critical data + if (self::$settings['preload_critical_data']) { + add_action('init', [__CLASS__, 'preload_critical_data'], 20); + } + + // Cache invalidation hooks + self::setup_cache_invalidation(); + + // Admin interface + if (is_admin()) { + add_action('admin_menu', [__CLASS__, 'add_admin_menu']); + add_action('wp_ajax_hvac_cache_action', [__CLASS__, 'handle_cache_action']); + } + + // REST API endpoints + add_action('rest_api_init', [__CLASS__, 'register_rest_endpoints']); + + // WP-CLI integration + if (defined('WP_CLI') && WP_CLI) { + WP_CLI::add_command('hvac cache', [__CLASS__, 'wp_cli_cache']); + } + + // Performance monitoring integration + add_action('hvac_performance_check', [__CLASS__, 'analyze_cache_performance']); + } + + /** + * Setup cache groups + */ + private static function setup_cache_groups() { + $cache_groups = [ + self::CACHE_GROUP_EVENTS, + self::CACHE_GROUP_TRAINERS, + self::CACHE_GROUP_CERTIFICATES, + self::CACHE_GROUP_DASHBOARD, + self::CACHE_GROUP_API + ]; + + // Register cache groups as non-persistent if using object cache + if (function_exists('wp_cache_add_non_persistent_groups')) { + wp_cache_add_non_persistent_groups($cache_groups); + } + } + + /** + * Get cache TTL for key type + */ + private static function get_cache_ttl($key_type) { + $strategy = self::$settings['strategy']; + $base_ttl = self::$cache_ttls[$strategy][$key_type] ?? 300; + + // Apply multiplier + return intval($base_ttl * self::$settings['cache_ttl_multiplier']); + } + + /** + * Enhanced cache set with compression and statistics + */ + public static function cache_set($key, $data, $group = '', $expire = 0) { + // Compress data if enabled and data is large + if (self::$settings['cache_compression'] && is_string($data) && strlen($data) > 1024) { + $data = gzcompress($data, 6); + $key .= '_compressed'; + } + + $result = wp_cache_set($key, $data, $group, $expire); + + if ($result) { + self::$stats['sets']++; + } + + return $result; + } + + /** + * Enhanced cache get with decompression + */ + public static function cache_get($key, $group = '') { + $data = wp_cache_get($key, $group); + + if ($data !== false) { + self::$stats['hits']++; + + // Check for compressed data + if (str_ends_with($key, '_compressed')) { + $data = gzuncompress($data); + } + } else { + self::$stats['misses']++; + + // Try compressed version if original not found + if (!str_ends_with($key, '_compressed')) { + $compressed_data = wp_cache_get($key . '_compressed', $group); + if ($compressed_data !== false) { + self::$stats['hits']++; + $data = gzuncompress($compressed_data); + } + } + } + + return $data; + } + + /** + * Smart cache delete + */ + public static function cache_delete($key, $group = '') { + $result = wp_cache_delete($key, $group); + + // Also try to delete compressed version + wp_cache_delete($key . '_compressed', $group); + + if ($result) { + self::$stats['deletes']++; + } + + return $result; + } + + /** + * Cache events list with smart invalidation + */ + public static function cache_events_list($args = [], $force_refresh = false) { + $cache_key = 'events_list_' . md5(serialize($args)); + + if (!$force_refresh) { + $cached_data = self::cache_get($cache_key, self::CACHE_GROUP_EVENTS); + if ($cached_data !== false) { + return $cached_data; + } + } + + // Generate fresh data + $events_data = self::generate_events_list($args); + + // Cache the results + $ttl = self::get_cache_ttl('events_list'); + self::cache_set($cache_key, $events_data, self::CACHE_GROUP_EVENTS, $ttl); + + return $events_data; + } + + /** + * Cache trainer profile data + */ + public static function cache_trainer_profile($trainer_id, $force_refresh = false) { + $cache_key = "trainer_profile_$trainer_id"; + + if (!$force_refresh) { + $cached_data = self::cache_get($cache_key, self::CACHE_GROUP_TRAINERS); + if ($cached_data !== false) { + return $cached_data; + } + } + + // Generate fresh profile data + $profile_data = self::generate_trainer_profile($trainer_id); + + // Cache the results + $ttl = self::get_cache_ttl('trainer_profile'); + self::cache_set($cache_key, $profile_data, self::CACHE_GROUP_TRAINERS, $ttl); + + return $profile_data; + } + + /** + * Cache dashboard statistics + */ + public static function cache_dashboard_stats($user_id, $force_refresh = false) { + $cache_key = "dashboard_stats_$user_id"; + + if (!$force_refresh) { + $cached_data = self::cache_get($cache_key, self::CACHE_GROUP_DASHBOARD); + if ($cached_data !== false) { + return $cached_data; + } + } + + // Generate fresh dashboard data + if (class_exists('HVAC_Dashboard_Data')) { + $dashboard = new HVAC_Dashboard_Data($user_id); + $stats_data = [ + 'total_events' => $dashboard->get_total_events_count(), + 'upcoming_events' => $dashboard->get_upcoming_events_count(), + 'completed_events' => $dashboard->get_completed_events_count(), + 'total_attendees' => $dashboard->get_total_attendees_count() + ]; + } else { + $stats_data = []; + } + + // Cache the results + $ttl = self::get_cache_ttl('dashboard_stats'); + self::cache_set($cache_key, $stats_data, self::CACHE_GROUP_DASHBOARD, $ttl); + + return $stats_data; + } + + /** + * Cache certificate data + */ + public static function cache_certificate_data($event_id, $force_refresh = false) { + $cache_key = "certificate_data_$event_id"; + + if (!$force_refresh) { + $cached_data = self::cache_get($cache_key, self::CACHE_GROUP_CERTIFICATES); + if ($cached_data !== false) { + return $cached_data; + } + } + + // Generate fresh certificate data + $certificate_data = self::generate_certificate_data($event_id); + + // Cache the results with longer TTL (certificates don't change often) + $ttl = self::get_cache_ttl('certificate_data'); + self::cache_set($cache_key, $certificate_data, self::CACHE_GROUP_CERTIFICATES, $ttl); + + return $certificate_data; + } + + /** + * Database query caching + */ + public static function maybe_cache_query($query) { + // Only cache SELECT queries + if (!preg_match('/^\s*SELECT/i', $query)) { + return $query; + } + + // Skip queries with functions that shouldn't be cached + $skip_patterns = [ + '/NOW\(\)/', + '/RAND\(\)/', + '/CURRENT_TIMESTAMP/', + '/USER\(\)/', + '/CONNECTION_ID\(\)/' + ]; + + foreach ($skip_patterns as $pattern) { + if (preg_match($pattern, $query)) { + return $query; + } + } + + $cache_key = 'query_' . md5($query); + $cached_result = self::cache_get($cache_key, 'hvac_db_cache'); + + if ($cached_result !== false) { + return $cached_result; + } + + // Execute query and cache result + global $wpdb; + $result = $wpdb->get_results($query); + + if (!$wpdb->last_error) { + $ttl = self::get_cache_ttl('database_queries'); + self::cache_set($cache_key, $result, 'hvac_db_cache', $ttl); + } + + return $query; + } + + /** + * Cache API responses + */ + public static function cache_api_response($response, $endpoint, $params) { + $cache_key = 'api_' . md5($endpoint . serialize($params)); + $ttl = self::get_cache_ttl('api_responses'); + + self::cache_set($cache_key, $response, self::CACHE_GROUP_API, $ttl); + + return $response; + } + + /** + * Get cached API response + */ + public static function get_cached_api_response($endpoint, $params) { + $cache_key = 'api_' . md5($endpoint . serialize($params)); + return self::cache_get($cache_key, self::CACHE_GROUP_API); + } + + /** + * Setup cache warming + */ + private static function setup_cache_warming() { + // Schedule cache warming + if (!wp_next_scheduled('hvac_warm_cache')) { + wp_schedule_event(time(), 'hourly', 'hvac_warm_cache'); + } + + add_action('hvac_warm_cache', [__CLASS__, 'warm_critical_caches']); + + // Warm cache on user login + add_action('wp_login', [__CLASS__, 'warm_user_specific_cache'], 10, 2); + } + + /** + * Warm critical caches + */ + public static function warm_critical_caches() { + // Warm events list cache + self::cache_events_list(); + self::cache_events_list(['post_status' => 'publish', 'meta_query' => [ + ['key' => '_EventStartDate', 'value' => date('Y-m-d'), 'compare' => '>='] + ]]); + + // Warm trainer profiles for active trainers + $trainers = get_users(['role__in' => ['hvac_trainer', 'hvac_master_trainer'], 'number' => 20]); + foreach ($trainers as $trainer) { + self::cache_trainer_profile($trainer->ID); + } + + // Warm certificate data for recent events + $recent_events = get_posts([ + 'post_type' => 'tribe_events', + 'post_status' => 'publish', + 'numberposts' => 10, + 'meta_query' => [ + ['key' => '_EventStartDate', 'value' => date('Y-m-d', strtotime('-30 days')), 'compare' => '>='] + ] + ]); + + foreach ($recent_events as $event) { + self::cache_certificate_data($event->ID); + } + + HVAC_Logger::info('Critical caches warmed successfully', 'Cache Optimizer'); + } + + /** + * Warm user-specific cache + */ + public static function warm_user_specific_cache($user_login, $user) { + if (in_array('hvac_trainer', $user->roles) || in_array('hvac_master_trainer', $user->roles)) { + // Warm dashboard stats + self::cache_dashboard_stats($user->ID); + + // Warm trainer profile + self::cache_trainer_profile($user->ID); + } + } + + /** + * Preload critical data + */ + public static function preload_critical_data() { + // Skip if already preloaded in this request + if (defined('HVAC_CRITICAL_DATA_PRELOADED')) { + return; + } + + // Preload plugin options + $critical_options = [ + 'hvac_plugin_settings', + 'hvac_cache_settings', + 'hvac_performance_settings', + 'tribe_events_calendar_options' + ]; + + foreach ($critical_options as $option) { + get_option($option); + } + + // Preload active trainer count + wp_count_posts('hvac_trainer'); + + // Preload recent events count + wp_count_posts('tribe_events'); + + define('HVAC_CRITICAL_DATA_PRELOADED', true); + } + + /** + * Setup cache invalidation + */ + private static function setup_cache_invalidation() { + // Clear events cache when events are modified + add_action('save_post', [__CLASS__, 'invalidate_events_cache']); + add_action('delete_post', [__CLASS__, 'invalidate_events_cache']); + + // Clear trainer cache when user profiles are updated + add_action('profile_update', [__CLASS__, 'invalidate_trainer_cache']); + add_action('user_register', [__CLASS__, 'invalidate_trainer_cache']); + add_action('delete_user', [__CLASS__, 'invalidate_trainer_cache']); + + // Clear certificate cache when events are updated + add_action('save_post', [__CLASS__, 'invalidate_certificate_cache']); + + // Clear dashboard cache when relevant data changes + add_action('save_post', [__CLASS__, 'invalidate_dashboard_cache']); + add_action('profile_update', [__CLASS__, 'invalidate_dashboard_cache']); + } + + /** + * Invalidate events cache + */ + public static function invalidate_events_cache($post_id = null) { + if ($post_id && get_post_type($post_id) !== 'tribe_events') { + return; + } + + wp_cache_flush_group(self::CACHE_GROUP_EVENTS); + + HVAC_Logger::info('Events cache invalidated', 'Cache Optimizer'); + } + + /** + * Invalidate trainer cache + */ + public static function invalidate_trainer_cache($user_id = null) { + if ($user_id) { + self::cache_delete("trainer_profile_$user_id", self::CACHE_GROUP_TRAINERS); + } else { + wp_cache_flush_group(self::CACHE_GROUP_TRAINERS); + } + + HVAC_Logger::info('Trainer cache invalidated', 'Cache Optimizer'); + } + + /** + * Invalidate certificate cache + */ + public static function invalidate_certificate_cache($post_id = null) { + if ($post_id && get_post_type($post_id) === 'tribe_events') { + self::cache_delete("certificate_data_$post_id", self::CACHE_GROUP_CERTIFICATES); + } else { + wp_cache_flush_group(self::CACHE_GROUP_CERTIFICATES); + } + + HVAC_Logger::info('Certificate cache invalidated', 'Cache Optimizer'); + } + + /** + * Invalidate dashboard cache + */ + public static function invalidate_dashboard_cache($user_id = null) { + if ($user_id) { + self::cache_delete("dashboard_stats_$user_id", self::CACHE_GROUP_DASHBOARD); + } else { + wp_cache_flush_group(self::CACHE_GROUP_DASHBOARD); + } + + HVAC_Logger::info('Dashboard cache invalidated', 'Cache Optimizer'); + } + + /** + * Generate events list data + */ + private static function generate_events_list($args = []) { + $default_args = [ + 'post_type' => 'tribe_events', + 'post_status' => 'publish', + 'numberposts' => 50, + 'orderby' => 'meta_value', + 'meta_key' => '_EventStartDate', + 'order' => 'ASC' + ]; + + $query_args = array_merge($default_args, $args); + $events = get_posts($query_args); + + $events_data = []; + foreach ($events as $event) { + $events_data[] = [ + 'id' => $event->ID, + 'title' => $event->post_title, + 'start_date' => get_post_meta($event->ID, '_EventStartDate', true), + 'end_date' => get_post_meta($event->ID, '_EventEndDate', true), + 'venue_id' => get_post_meta($event->ID, '_EventVenueID', true), + 'organizer_id' => get_post_meta($event->ID, '_EventOrganizerID', true) + ]; + } + + return $events_data; + } + + /** + * Generate trainer profile data + */ + private static function generate_trainer_profile($trainer_id) { + $user = get_user_by('id', $trainer_id); + + if (!$user) { + return null; + } + + $profile_data = [ + 'id' => $user->ID, + 'display_name' => $user->display_name, + 'email' => $user->user_email, + 'roles' => $user->roles, + 'meta' => [] + ]; + + // Get relevant user meta + $meta_keys = [ + 'hvac_trainer_phone', + 'hvac_trainer_company', + 'hvac_trainer_location', + 'hvac_certification_type', + 'hvac_certification_status' + ]; + + foreach ($meta_keys as $key) { + $profile_data['meta'][$key] = get_user_meta($trainer_id, $key, true); + } + + return $profile_data; + } + + /** + * Generate certificate data + */ + private static function generate_certificate_data($event_id) { + $event = get_post($event_id); + + if (!$event || $event->post_type !== 'tribe_events') { + return null; + } + + $certificate_data = [ + 'event_id' => $event->ID, + 'event_title' => $event->post_title, + 'start_date' => get_post_meta($event->ID, '_EventStartDate', true), + 'venue_id' => get_post_meta($event->ID, '_EventVenueID', true), + 'organizer_id' => get_post_meta($event->ID, '_EventOrganizerID', true), + 'attendees_count' => 0 // Would be populated by actual attendee system + ]; + + return $certificate_data; + } + + /** + * Analyze cache performance + */ + public static function analyze_cache_performance() { + $hit_rate = self::get_cache_hit_rate(); + $memory_usage = self::get_cache_memory_usage(); + + $performance_data = [ + 'hit_rate' => $hit_rate, + 'memory_usage' => $memory_usage, + 'total_operations' => array_sum(self::$stats), + 'stats' => self::$stats + ]; + + // Log performance issues + if ($hit_rate < 70) { + HVAC_Logger::warning( + "Low cache hit rate detected: {$hit_rate}%", + 'Cache Optimizer' + ); + } + + if ($memory_usage > 50 * 1024 * 1024) { // 50MB + HVAC_Logger::warning( + "High cache memory usage: " . size_format($memory_usage), + 'Cache Optimizer' + ); + } + + return $performance_data; + } + + /** + * Get cache hit rate + */ + public static function get_cache_hit_rate() { + $total_gets = self::$stats['hits'] + self::$stats['misses']; + + if ($total_gets == 0) { + return 0; + } + + return round((self::$stats['hits'] / $total_gets) * 100, 2); + } + + /** + * Get cache memory usage + */ + public static function get_cache_memory_usage() { + // This would require object cache backend specific implementation + // For now, return estimated usage based on operations + return self::$stats['sets'] * 1024; // Estimate 1KB per cached item + } + + /** + * Get cache statistics + */ + public static function get_cache_stats() { + return [ + 'hit_rate' => self::get_cache_hit_rate(), + 'memory_usage' => self::get_cache_memory_usage(), + 'operations' => self::$stats, + 'strategy' => self::$settings['strategy'], + 'groups' => [ + self::CACHE_GROUP_EVENTS, + self::CACHE_GROUP_TRAINERS, + self::CACHE_GROUP_CERTIFICATES, + self::CACHE_GROUP_DASHBOARD, + self::CACHE_GROUP_API + ] + ]; + } + + /** + * Flush all HVAC caches + */ + public static function flush_all_caches() { + $cache_groups = [ + self::CACHE_GROUP_EVENTS, + self::CACHE_GROUP_TRAINERS, + self::CACHE_GROUP_CERTIFICATES, + self::CACHE_GROUP_DASHBOARD, + self::CACHE_GROUP_API + ]; + + foreach ($cache_groups as $group) { + wp_cache_flush_group($group); + } + + // Also flush database cache + wp_cache_flush_group('hvac_db_cache'); + + self::$stats['flushes']++; + + HVAC_Logger::info('All HVAC caches flushed', 'Cache Optimizer'); + } + + /** + * Track cache operations for statistics + */ + public static function track_cache_set($key, $data, $group, $expire, $result) { + if (str_starts_with($group, 'hvac_')) { + self::$stats['sets']++; + } + } + + public static function track_cache_get($key, $group, $force, $found) { + if (str_starts_with($group, 'hvac_')) { + if ($found) { + self::$stats['hits']++; + } else { + self::$stats['misses']++; + } + } + } + + public static function track_cache_delete($key, $group) { + if (str_starts_with($group, 'hvac_')) { + self::$stats['deletes']++; + } + } + + public static function track_cache_flush() { + self::$stats['flushes']++; + } + + /** + * Add admin menu + */ + public static function add_admin_menu() { + if (current_user_can('manage_options')) { + add_management_page( + 'HVAC Cache Optimizer', + 'HVAC Cache', + 'manage_options', + 'hvac-cache-optimizer', + [__CLASS__, 'admin_page'] + ); + } + } + + /** + * Admin page + */ + public static function admin_page() { + $stats = self::get_cache_stats(); + $performance = self::analyze_cache_performance(); + + ?> +
+

HVAC Cache Optimizer

+ +
+
+

Cache Performance

+

Hit Rate: %

+

Memory Usage:

+

Strategy:

+

Total Operations:

+
+ +
+

Cache Operations

+

Cache Hits:

+

Cache Misses:

+

Cache Sets:

+

Cache Deletes:

+

Cache Flushes:

+
+ +
+

Cache Management

+

+ + +

+

+ +

+
+
+ +
+

Cache Groups

+ + + + + + + + + + 'Event listings and event data', + self::CACHE_GROUP_TRAINERS => 'Trainer profiles and trainer data', + self::CACHE_GROUP_CERTIFICATES => 'Certificate data and generation', + self::CACHE_GROUP_DASHBOARD => 'Dashboard statistics and metrics', + self::CACHE_GROUP_API => 'API responses and external data' + ]; + ?> + + + + + + + + +
GroupDescriptionActions
+ +
+
+ +
+

Cache Strategy Settings

+
+ + + + + + + + + + + + + +
+ +

Choose caching strategy based on your content update frequency.

+
+ /> + +
+ /> + +
+

+ +

+
+
+ + + + +
+ 'GET', + 'callback' => [__CLASS__, 'rest_cache_stats'], + 'permission_callback' => function() { + return current_user_can('manage_options'); + } + ]); + + register_rest_route('hvac/v1', '/cache/flush', [ + 'methods' => 'POST', + 'callback' => [__CLASS__, 'rest_flush_cache'], + 'permission_callback' => function() { + return current_user_can('manage_options'); + } + ]); + } + + /** + * REST API cache stats + */ + public static function rest_cache_stats() { + $stats = self::get_cache_stats(); + + return new WP_REST_Response([ + 'stats' => $stats, + 'timestamp' => time() + ], 200); + } + + /** + * REST API flush cache + */ + public static function rest_flush_cache() { + self::flush_all_caches(); + + return new WP_REST_Response([ + 'message' => 'All caches flushed successfully', + 'timestamp' => time() + ], 200); + } + + /** + * WP-CLI cache command + */ + public static function wp_cli_cache($args, $assoc_args) { + $subcommand = $args[0] ?? 'stats'; + + switch ($subcommand) { + case 'stats': + $stats = self::get_cache_stats(); + WP_CLI::line('HVAC Cache Statistics:'); + WP_CLI::line('Hit Rate: ' . $stats['hit_rate'] . '%'); + WP_CLI::line('Memory Usage: ' . size_format($stats['memory_usage'])); + WP_CLI::line('Strategy: ' . ucfirst($stats['strategy'])); + WP_CLI::line('Total Operations: ' . array_sum($stats['operations'])); + break; + + case 'warm': + WP_CLI::line('Warming critical caches...'); + self::warm_critical_caches(); + WP_CLI::success('Cache warming completed'); + break; + + case 'flush': + $group = $assoc_args['group'] ?? ''; + if ($group) { + wp_cache_flush_group($group); + WP_CLI::success("Cache group $group flushed"); + } else { + self::flush_all_caches(); + WP_CLI::success('All caches flushed'); + } + break; + + case 'analyze': + WP_CLI::line('Analyzing cache performance...'); + $analysis = self::analyze_cache_performance(); + WP_CLI::line('Hit Rate: ' . $analysis['hit_rate'] . '%'); + WP_CLI::line('Memory Usage: ' . size_format($analysis['memory_usage'])); + WP_CLI::success('Performance analysis completed'); + break; + + default: + WP_CLI::error('Unknown subcommand. Use: stats, warm, flush, analyze'); + } + } +} \ No newline at end of file diff --git a/includes/class-hvac-error-recovery.php b/includes/class-hvac-error-recovery.php new file mode 100644 index 00000000..28976cb0 --- /dev/null +++ b/includes/class-hvac-error-recovery.php @@ -0,0 +1,589 @@ + [ + 'strategy' => self::STRATEGY_RETRY, + 'max_attempts' => 3, + 'backoff_multiplier' => 2, + 'fallback_callback' => null + ], + 'cache_operation' => [ + 'strategy' => self::STRATEGY_FALLBACK, + 'max_attempts' => 2, + 'fallback_callback' => 'skip_cache' + ], + 'external_api' => [ + 'strategy' => self::STRATEGY_CIRCUIT_BREAKER, + 'max_failures' => 5, + 'timeout' => 300, // 5 minutes + 'fallback_callback' => 'use_cached_data' + ], + 'file_operation' => [ + 'strategy' => self::STRATEGY_GRACEFUL_FAIL, + 'max_attempts' => 2, + 'fallback_callback' => 'log_and_continue' + ] + ]; + + /** + * Initialize error recovery system + */ + public static function init() { + // Set custom error handler for plugin operations + add_action('init', [__CLASS__, 'setup_error_handling']); + + // Hook into WordPress error handling + add_action('wp_die_handler', [__CLASS__, 'handle_wp_die'], 10, 1); + + // Monitor and recover from specific plugin errors + add_action('hvac_operation_failed', [__CLASS__, 'handle_operation_failure'], 10, 3); + + // Admin interface for error recovery stats + if (is_admin()) { + add_action('admin_menu', [__CLASS__, 'add_admin_menu']); + } + + // Cleanup old error data + add_action('wp_scheduled_delete', [__CLASS__, 'cleanup_old_errors']); + } + + /** + * Setup error handling + */ + public static function setup_error_handling() { + // Only set error handler for plugin operations + if (self::is_plugin_context()) { + set_error_handler([__CLASS__, 'handle_php_error'], E_ALL); + register_shutdown_function([__CLASS__, 'handle_fatal_error']); + } + } + + /** + * Check if we're in plugin context + */ + private static function is_plugin_context() { + $backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 10); + + foreach ($backtrace as $trace) { + $file = $trace['file'] ?? ''; + if (strpos($file, 'hvac-community-events') !== false) { + return true; + } + } + + return false; + } + + /** + * Execute operation with error recovery + * + * @param string $operation_type Type of operation + * @param callable $callback Operation callback + * @param array $args Operation arguments + * @return mixed Operation result or fallback + */ + public static function execute_with_recovery($operation_type, $callback, $args = []) { + $config = self::$recovery_config[$operation_type] ?? self::$recovery_config['file_operation']; + $attempt = 0; + $last_error = null; + + // Check circuit breaker + if ($config['strategy'] === self::STRATEGY_CIRCUIT_BREAKER) { + if (self::is_circuit_open($operation_type)) { + return self::execute_fallback($operation_type, $config, $args); + } + } + + while ($attempt < ($config['max_attempts'] ?? 1)) { + $attempt++; + + try { + // Execute operation + $result = call_user_func_array($callback, $args); + + // Reset error count on success + self::reset_error_count($operation_type); + + return $result; + + } catch (Exception $e) { + $last_error = $e; + + // Increment error count + self::increment_error_count($operation_type); + + // Log error + HVAC_Logger::warning( + "Operation failed (attempt $attempt): {$e->getMessage()}", + 'Error Recovery' + ); + + // Apply recovery strategy + if ($attempt < ($config['max_attempts'] ?? 1)) { + switch ($config['strategy']) { + case self::STRATEGY_RETRY: + $delay = ($config['backoff_multiplier'] ?? 1) * $attempt; + sleep($delay); + break; + + case self::STRATEGY_CIRCUIT_BREAKER: + if (self::should_open_circuit($operation_type)) { + self::open_circuit($operation_type, $config['timeout'] ?? 300); + return self::execute_fallback($operation_type, $config, $args); + } + break; + } + } + } + } + + // All attempts failed - execute fallback or fail gracefully + return self::handle_final_failure($operation_type, $config, $last_error, $args); + } + + /** + * Handle final operation failure + */ + private static function handle_final_failure($operation_type, $config, $error, $args) { + HVAC_Logger::error( + "Operation $operation_type failed after all attempts: " . $error->getMessage(), + 'Error Recovery' + ); + + switch ($config['strategy']) { + case self::STRATEGY_FALLBACK: + case self::STRATEGY_CIRCUIT_BREAKER: + return self::execute_fallback($operation_type, $config, $args); + + case self::STRATEGY_GRACEFUL_FAIL: + // Return safe default value + return self::get_safe_default($operation_type); + + default: + // Re-throw exception for retry strategy + throw $error; + } + } + + /** + * Execute fallback operation + */ + private static function execute_fallback($operation_type, $config, $args) { + $fallback = $config['fallback_callback'] ?? null; + + if (!$fallback) { + return self::get_safe_default($operation_type); + } + + try { + if (is_string($fallback) && method_exists(__CLASS__, $fallback)) { + return call_user_func([__CLASS__, $fallback], $operation_type, $args); + } elseif (is_callable($fallback)) { + return call_user_func_array($fallback, $args); + } + } catch (Exception $e) { + HVAC_Logger::error( + "Fallback also failed for $operation_type: " . $e->getMessage(), + 'Error Recovery' + ); + } + + return self::get_safe_default($operation_type); + } + + /** + * Get safe default value for operation type + */ + private static function get_safe_default($operation_type) { + $defaults = [ + 'database_query' => [], + 'cache_operation' => null, + 'external_api' => ['error' => 'Service temporarily unavailable'], + 'file_operation' => false + ]; + + return $defaults[$operation_type] ?? null; + } + + /** + * Circuit breaker management + */ + private static function is_circuit_open($operation_type) { + return isset(self::$circuit_breakers[$operation_type]) && + self::$circuit_breakers[$operation_type] > time(); + } + + private static function should_open_circuit($operation_type) { + $error_count = self::get_error_count($operation_type); + $config = self::$recovery_config[$operation_type] ?? []; + + return $error_count >= ($config['max_failures'] ?? 5); + } + + private static function open_circuit($operation_type, $timeout) { + self::$circuit_breakers[$operation_type] = time() + $timeout; + update_option('hvac_circuit_breakers', self::$circuit_breakers); + + HVAC_Logger::warning( + "Circuit breaker opened for $operation_type (timeout: {$timeout}s)", + 'Error Recovery' + ); + } + + /** + * Error counting + */ + private static function increment_error_count($operation_type) { + if (!isset(self::$error_counts[$operation_type])) { + self::$error_counts[$operation_type] = 0; + } + + self::$error_counts[$operation_type]++; + update_option('hvac_error_counts', self::$error_counts); + } + + private static function get_error_count($operation_type) { + if (empty(self::$error_counts)) { + self::$error_counts = get_option('hvac_error_counts', []); + } + + return self::$error_counts[$operation_type] ?? 0; + } + + private static function reset_error_count($operation_type) { + self::$error_counts[$operation_type] = 0; + update_option('hvac_error_counts', self::$error_counts); + } + + /** + * Fallback implementations + */ + public static function skip_cache($operation_type, $args) { + HVAC_Logger::info("Cache operation skipped due to errors", 'Error Recovery'); + return null; + } + + public static function use_cached_data($operation_type, $args) { + // Try to get stale cached data + $cache_key = 'hvac_fallback_' . md5($operation_type . serialize($args)); + $cached_data = get_transient($cache_key); + + if ($cached_data !== false) { + HVAC_Logger::info("Using stale cached data for $operation_type", 'Error Recovery'); + return $cached_data; + } + + return self::get_safe_default($operation_type); + } + + public static function log_and_continue($operation_type, $args) { + HVAC_Logger::info("Continuing after failed $operation_type", 'Error Recovery'); + return true; + } + + /** + * Handle PHP errors + */ + public static function handle_php_error($severity, $message, $file, $line) { + // Only handle errors from plugin files + if (strpos($file, 'hvac-community-events') === false) { + return false; + } + + $error_types = [ + E_ERROR => 'Error', + E_WARNING => 'Warning', + E_NOTICE => 'Notice', + E_USER_ERROR => 'User Error', + E_USER_WARNING => 'User Warning', + E_USER_NOTICE => 'User Notice' + ]; + + $error_type = $error_types[$severity] ?? 'Unknown'; + + HVAC_Logger::error( + "PHP $error_type: $message in $file:$line", + 'Error Recovery' + ); + + // Don't execute PHP internal error handler + return true; + } + + /** + * Handle fatal errors + */ + public static function handle_fatal_error() { + $error = error_get_last(); + + if ($error && in_array($error['type'], [E_ERROR, E_CORE_ERROR, E_COMPILE_ERROR, E_PARSE])) { + // Only handle fatal errors from plugin files + if (strpos($error['file'], 'hvac-community-events') !== false) { + HVAC_Logger::error( + "Fatal Error: {$error['message']} in {$error['file']}:{$error['line']}", + 'Error Recovery' + ); + + // Attempt to recover by disabling problematic functionality + self::emergency_recovery($error); + } + } + } + + /** + * Emergency recovery for fatal errors + */ + private static function emergency_recovery($error) { + // Create emergency flag to disable problematic functionality + update_option('hvac_emergency_mode', [ + 'enabled' => true, + 'error' => $error, + 'timestamp' => time() + ]); + + // Send emergency notification + $admin_email = get_option('admin_email'); + $site_name = get_bloginfo('name'); + + wp_mail( + $admin_email, + "[$site_name] HVAC Plugin Emergency Mode Activated", + "A fatal error occurred in the HVAC plugin and emergency mode has been activated.\n\n" . + "Error: {$error['message']}\n" . + "File: {$error['file']}:{$error['line']}\n\n" . + "Please check the plugin status and contact support if needed." + ); + } + + /** + * Check if emergency mode is active + */ + public static function is_emergency_mode() { + $emergency = get_option('hvac_emergency_mode', false); + + if (!$emergency || !$emergency['enabled']) { + return false; + } + + // Auto-disable after 24 hours + if (time() - $emergency['timestamp'] > 86400) { + delete_option('hvac_emergency_mode'); + return false; + } + + return true; + } + + /** + * Disable emergency mode + */ + public static function disable_emergency_mode() { + delete_option('hvac_emergency_mode'); + HVAC_Logger::info('Emergency mode disabled', 'Error Recovery'); + } + + /** + * Handle operation failure action + */ + public static function handle_operation_failure($operation_type, $error_message, $context = []) { + // This can be triggered by other parts of the plugin + HVAC_Logger::warning( + "Operation failure reported: $operation_type - $error_message", + 'Error Recovery' + ); + + self::increment_error_count($operation_type); + + // Check if circuit breaker should be triggered + $config = self::$recovery_config[$operation_type] ?? []; + if ($config['strategy'] === self::STRATEGY_CIRCUIT_BREAKER) { + if (self::should_open_circuit($operation_type)) { + self::open_circuit($operation_type, $config['timeout'] ?? 300); + } + } + } + + /** + * Get error recovery statistics + */ + public static function get_recovery_stats() { + return [ + 'error_counts' => get_option('hvac_error_counts', []), + 'circuit_breakers' => get_option('hvac_circuit_breakers', []), + 'emergency_mode' => get_option('hvac_emergency_mode', false) + ]; + } + + /** + * Cleanup old error data + */ + public static function cleanup_old_errors() { + // Reset error counts daily + $last_reset = get_option('hvac_error_reset_time', 0); + if (time() - $last_reset > 86400) { // 24 hours + update_option('hvac_error_counts', []); + update_option('hvac_error_reset_time', time()); + } + + // Clean up expired circuit breakers + $circuit_breakers = get_option('hvac_circuit_breakers', []); + $current_time = time(); + $updated = false; + + foreach ($circuit_breakers as $operation => $expiry) { + if ($expiry < $current_time) { + unset($circuit_breakers[$operation]); + $updated = true; + } + } + + if ($updated) { + update_option('hvac_circuit_breakers', $circuit_breakers); + } + } + + /** + * Add admin menu + */ + public static function add_admin_menu() { + if (current_user_can('manage_options')) { + add_submenu_page( + 'tools.php', + 'HVAC Error Recovery', + 'HVAC Error Recovery', + 'manage_options', + 'hvac-error-recovery', + [__CLASS__, 'admin_page'] + ); + } + } + + /** + * Admin page + */ + public static function admin_page() { + $stats = self::get_recovery_stats(); + $emergency_mode = self::is_emergency_mode(); + + ?> +
+

HVAC Error Recovery System

+ + +
+

Emergency Mode Active - Some plugin functionality may be disabled due to critical errors.

+

+ +

+
+ + +
+

Error Statistics

+ + + + + + + + + + + + + + + $count): ?> + + + + + + + + +
Operation TypeError CountCircuit Breaker
No errors recorded
+ time()) { + echo 'OPEN (expires: ' . date('H:i:s', $breaker_expiry) . ')'; + } else { + echo 'CLOSED'; + } + ?> +
+
+ +
+

Recovery Configuration

+

The error recovery system is configured with the following strategies:

+
    +
  • Database Queries: Retry with exponential backoff (3 attempts)
  • +
  • Cache Operations: Skip and continue without caching
  • +
  • External APIs: Circuit breaker with 5-minute timeout
  • +
  • File Operations: Graceful failure with safe defaults
  • +
+
+
+ + + 'Database Connectivity', + 'cache' => 'Cache System', + 'authentication' => 'User Authentication', + 'events' => 'Event Management', + 'certificates' => 'Certificate Generation', + 'background_jobs' => 'Background Jobs', + 'file_permissions' => 'File Permissions', + 'third_party' => 'Third Party Integrations' + ]; + + /** + * Health status constants + */ + const STATUS_HEALTHY = 'healthy'; + const STATUS_WARNING = 'warning'; + const STATUS_CRITICAL = 'critical'; + + /** + * Health check results cache + */ + const CACHE_KEY = 'hvac_health_checks'; + const CACHE_DURATION = 300; // 5 minutes + + /** + * Initialize health monitoring + */ + public static function init() { + // Schedule recurring health checks + if (!wp_next_scheduled('hvac_health_check')) { + wp_schedule_event(time(), 'hourly', 'hvac_health_check'); + } + + // Hook health check action + add_action('hvac_health_check', [__CLASS__, 'run_automated_checks']); + + // Admin integration + if (is_admin()) { + add_action('admin_menu', [__CLASS__, 'add_admin_menu']); + add_action('wp_ajax_hvac_run_health_check', [__CLASS__, 'ajax_run_health_check']); + add_action('admin_notices', [__CLASS__, 'show_health_warnings']); + } + + // REST API endpoint for external monitoring + add_action('rest_api_init', [__CLASS__, 'register_rest_endpoints']); + + // WP-CLI integration + if (defined('WP_CLI') && WP_CLI) { + WP_CLI::add_command('hvac health', [__CLASS__, 'wp_cli_health_check']); + } + } + + /** + * Run all health checks + * + * @param bool $force_refresh Force refresh cached results + * @return array Health check results + */ + public static function run_all_checks($force_refresh = false) { + if (!$force_refresh) { + $cached_results = get_transient(self::CACHE_KEY); + if ($cached_results !== false) { + return $cached_results; + } + } + + $results = [ + 'timestamp' => time(), + 'overall_status' => self::STATUS_HEALTHY, + 'checks' => [] + ]; + + foreach (self::CHECK_TYPES as $type => $name) { + $check_result = self::run_health_check($type); + $results['checks'][$type] = $check_result; + + // Update overall status based on worst result + if ($check_result['status'] === self::STATUS_CRITICAL) { + $results['overall_status'] = self::STATUS_CRITICAL; + } elseif ($check_result['status'] === self::STATUS_WARNING && + $results['overall_status'] !== self::STATUS_CRITICAL) { + $results['overall_status'] = self::STATUS_WARNING; + } + } + + // Cache results + set_transient(self::CACHE_KEY, $results, self::CACHE_DURATION); + + // Log critical issues + if ($results['overall_status'] === self::STATUS_CRITICAL) { + $critical_checks = array_filter($results['checks'], function($check) { + return $check['status'] === self::STATUS_CRITICAL; + }); + + $critical_names = array_keys($critical_checks); + HVAC_Logger::error( + 'Critical health check failures: ' . implode(', ', $critical_names), + 'Health Monitor' + ); + } + + return $results; + } + + /** + * Run individual health check + * + * @param string $type Check type + * @return array Check result + */ + private static function run_health_check($type) { + $start_time = microtime(true); + + try { + switch ($type) { + case 'database': + $result = self::check_database(); + break; + + case 'cache': + $result = self::check_cache_system(); + break; + + case 'authentication': + $result = self::check_authentication(); + break; + + case 'events': + $result = self::check_event_management(); + break; + + case 'certificates': + $result = self::check_certificate_system(); + break; + + case 'background_jobs': + $result = self::check_background_jobs(); + break; + + case 'file_permissions': + $result = self::check_file_permissions(); + break; + + case 'third_party': + $result = self::check_third_party_integrations(); + break; + + default: + $result = [ + 'status' => self::STATUS_WARNING, + 'message' => 'Unknown check type', + 'details' => [] + ]; + } + + } catch (Exception $e) { + $result = [ + 'status' => self::STATUS_CRITICAL, + 'message' => 'Check failed with exception: ' . $e->getMessage(), + 'details' => ['exception' => get_class($e)] + ]; + } + + $result['execution_time'] = round(microtime(true) - $start_time, 4); + $result['timestamp'] = time(); + $result['name'] = self::CHECK_TYPES[$type]; + + return $result; + } + + /** + * Check database connectivity and integrity + */ + private static function check_database() { + global $wpdb; + + // Test basic connection + $test_query = $wpdb->get_var("SELECT 1"); + if ($test_query !== '1') { + return [ + 'status' => self::STATUS_CRITICAL, + 'message' => 'Database connection failed', + 'details' => ['error' => $wpdb->last_error] + ]; + } + + // Check plugin tables exist + $required_tables = [ + $wpdb->prefix . 'posts', + $wpdb->prefix . 'postmeta', + $wpdb->prefix . 'users', + $wpdb->prefix . 'usermeta' + ]; + + $missing_tables = []; + foreach ($required_tables as $table) { + $exists = $wpdb->get_var($wpdb->prepare("SHOW TABLES LIKE %s", $table)); + if (!$exists) { + $missing_tables[] = $table; + } + } + + if (!empty($missing_tables)) { + return [ + 'status' => self::STATUS_CRITICAL, + 'message' => 'Required tables missing', + 'details' => ['missing_tables' => $missing_tables] + ]; + } + + // Check for recent database errors + if ($wpdb->last_error) { + return [ + 'status' => self::STATUS_WARNING, + 'message' => 'Recent database error detected', + 'details' => ['last_error' => $wpdb->last_error] + ]; + } + + return [ + 'status' => self::STATUS_HEALTHY, + 'message' => 'Database connectivity is healthy', + 'details' => ['tables_checked' => count($required_tables)] + ]; + } + + /** + * Check cache system functionality + */ + private static function check_cache_system() { + // Test WordPress object cache + $test_key = 'hvac_health_test_' . time(); + $test_value = 'test_data_' . wp_generate_password(10, false); + + // Set cache + $set_result = wp_cache_set($test_key, $test_value, 'hvac_health', 60); + if (!$set_result) { + return [ + 'status' => self::STATUS_WARNING, + 'message' => 'Cache set operation failed', + 'details' => [] + ]; + } + + // Get cache + $cached_value = wp_cache_get($test_key, 'hvac_health'); + if ($cached_value !== $test_value) { + return [ + 'status' => self::STATUS_WARNING, + 'message' => 'Cache retrieval failed or returned incorrect data', + 'details' => ['expected' => $test_value, 'actual' => $cached_value] + ]; + } + + // Test master dashboard cache + if (class_exists('HVAC_Master_Dashboard_Data')) { + $dashboard_data = new HVAC_Master_Dashboard_Data(); + $events_count = $dashboard_data->get_total_events_count(); + + if (!is_numeric($events_count)) { + return [ + 'status' => self::STATUS_WARNING, + 'message' => 'Master dashboard cache returning invalid data', + 'details' => ['events_count' => $events_count] + ]; + } + } + + // Clean up test cache + wp_cache_delete($test_key, 'hvac_health'); + + return [ + 'status' => self::STATUS_HEALTHY, + 'message' => 'Cache system is functioning correctly', + 'details' => ['test_key' => $test_key] + ]; + } + + /** + * Check authentication system + */ + private static function check_authentication() { + // Check if trainer roles exist + $required_roles = ['hvac_trainer', 'hvac_master_trainer']; + $missing_roles = []; + + foreach ($required_roles as $role) { + if (!get_role($role)) { + $missing_roles[] = $role; + } + } + + if (!empty($missing_roles)) { + return [ + 'status' => self::STATUS_CRITICAL, + 'message' => 'Required user roles are missing', + 'details' => ['missing_roles' => $missing_roles] + ]; + } + + // Check for users with trainer roles + $trainer_count = count(get_users(['role__in' => $required_roles])); + if ($trainer_count === 0) { + return [ + 'status' => self::STATUS_WARNING, + 'message' => 'No users found with trainer roles', + 'details' => ['trainer_count' => $trainer_count] + ]; + } + + // Test capability system + if (!current_user_can('read')) { + return [ + 'status' => self::STATUS_WARNING, + 'message' => 'Capability system may have issues', + 'details' => [] + ]; + } + + return [ + 'status' => self::STATUS_HEALTHY, + 'message' => 'Authentication system is healthy', + 'details' => ['trainer_count' => $trainer_count] + ]; + } + + /** + * Check event management system + */ + private static function check_event_management() { + // Check if The Events Calendar is active + if (!class_exists('Tribe__Events__Main')) { + return [ + 'status' => self::STATUS_CRITICAL, + 'message' => 'The Events Calendar plugin is not active', + 'details' => [] + ]; + } + + // Check for events + $events_count = wp_count_posts('tribe_events'); + $total_events = ($events_count->publish ?? 0) + ($events_count->private ?? 0); + + if ($total_events === 0) { + return [ + 'status' => self::STATUS_WARNING, + 'message' => 'No events found in the system', + 'details' => ['events_count' => $total_events] + ]; + } + + // Check event creation capability + $can_create_events = post_type_exists('tribe_events'); + if (!$can_create_events) { + return [ + 'status' => self::STATUS_CRITICAL, + 'message' => 'Event post type is not registered', + 'details' => [] + ]; + } + + return [ + 'status' => self::STATUS_HEALTHY, + 'message' => 'Event management system is functioning', + 'details' => ['total_events' => $total_events] + ]; + } + + /** + * Check certificate system + */ + private static function check_certificate_system() { + // Check if certificate pages exist + $certificate_pages = [ + 'trainer/certificate-reports', + 'trainer/generate-certificates' + ]; + + $missing_pages = []; + foreach ($certificate_pages as $page_slug) { + if (!get_page_by_path($page_slug)) { + $missing_pages[] = $page_slug; + } + } + + if (!empty($missing_pages)) { + return [ + 'status' => self::STATUS_WARNING, + 'message' => 'Certificate pages are missing', + 'details' => ['missing_pages' => $missing_pages] + ]; + } + + // Check uploads directory permissions + $upload_dir = wp_upload_dir(); + if (!wp_is_writable($upload_dir['basedir'])) { + return [ + 'status' => self::STATUS_CRITICAL, + 'message' => 'Uploads directory is not writable', + 'details' => ['upload_dir' => $upload_dir['basedir']] + ]; + } + + return [ + 'status' => self::STATUS_HEALTHY, + 'message' => 'Certificate system appears functional', + 'details' => ['pages_found' => count($certificate_pages) - count($missing_pages)] + ]; + } + + /** + * Check background jobs system + */ + private static function check_background_jobs() { + if (!class_exists('HVAC_Background_Jobs')) { + return [ + 'status' => self::STATUS_WARNING, + 'message' => 'Background jobs system not available', + 'details' => [] + ]; + } + + // Check if cron is working + $cron_test = wp_next_scheduled('hvac_process_background_jobs'); + if (!$cron_test) { + return [ + 'status' => self::STATUS_WARNING, + 'message' => 'Background job processing is not scheduled', + 'details' => [] + ]; + } + + // Check queue stats + $stats = HVAC_Background_Jobs::get_queue_stats(); + if ($stats['total'] > 100) { + return [ + 'status' => self::STATUS_WARNING, + 'message' => 'Background job queue is very large', + 'details' => ['queue_size' => $stats['total']] + ]; + } + + return [ + 'status' => self::STATUS_HEALTHY, + 'message' => 'Background jobs system is operational', + 'details' => ['queue_size' => $stats['total']] + ]; + } + + /** + * Check file permissions + */ + private static function check_file_permissions() { + $critical_paths = [ + WP_CONTENT_DIR, + wp_upload_dir()['basedir'], + HVAC_PLUGIN_DIR . 'assets' + ]; + + $permission_issues = []; + foreach ($critical_paths as $path) { + if (!is_dir($path) || !wp_is_writable($path)) { + $permission_issues[] = $path; + } + } + + if (!empty($permission_issues)) { + return [ + 'status' => self::STATUS_CRITICAL, + 'message' => 'Critical directories have permission issues', + 'details' => ['problematic_paths' => $permission_issues] + ]; + } + + return [ + 'status' => self::STATUS_HEALTHY, + 'message' => 'File permissions are correct', + 'details' => ['paths_checked' => count($critical_paths)] + ]; + } + + /** + * Check third-party integrations + */ + private static function check_third_party_integrations() { + $integrations = []; + + // Check The Events Calendar + if (class_exists('Tribe__Events__Main')) { + $integrations['events_calendar'] = 'active'; + } else { + $integrations['events_calendar'] = 'missing'; + } + + // Check Astra theme integration + if (defined('ASTRA_THEME_VERSION')) { + $integrations['astra_theme'] = 'active'; + } else { + $integrations['astra_theme'] = 'not_detected'; + } + + // Check for critical missing integrations + if ($integrations['events_calendar'] === 'missing') { + return [ + 'status' => self::STATUS_CRITICAL, + 'message' => 'Critical integration missing: The Events Calendar', + 'details' => $integrations + ]; + } + + return [ + 'status' => self::STATUS_HEALTHY, + 'message' => 'Third-party integrations are functional', + 'details' => $integrations + ]; + } + + /** + * Run automated health checks + */ + public static function run_automated_checks() { + $results = self::run_all_checks(true); + + // Send alerts for critical issues + if ($results['overall_status'] === self::STATUS_CRITICAL) { + self::send_health_alert($results); + } + + HVAC_Logger::info( + "Health check completed: {$results['overall_status']}", + 'Health Monitor' + ); + } + + /** + * Send health alert + * + * @param array $results Health check results + */ + private static function send_health_alert($results) { + $admin_email = get_option('admin_email'); + $site_name = get_bloginfo('name'); + + $critical_issues = array_filter($results['checks'], function($check) { + return $check['status'] === self::STATUS_CRITICAL; + }); + + $subject = "[$site_name] Critical Health Check Alert"; + $message = "Critical issues detected in HVAC Community Events plugin:\n\n"; + + foreach ($critical_issues as $type => $check) { + $message .= "• {$check['name']}: {$check['message']}\n"; + } + + $message .= "\nPlease check the admin dashboard for more details."; + + wp_mail($admin_email, $subject, $message); + } + + /** + * Add admin menu + */ + public static function add_admin_menu() { + if (current_user_can('manage_options')) { + add_management_page( + 'HVAC Health Monitor', + 'HVAC Health', + 'manage_options', + 'hvac-health-monitor', + [__CLASS__, 'admin_page'] + ); + } + } + + /** + * Admin page + */ + public static function admin_page() { + $results = self::run_all_checks(); + + ?> +
+

HVAC Health Monitor

+ +
+

Overall Status: + + + +

+

Last checked:

+

+ +

+
+ +
+ $check): ?> +
+

+

Status:

+

+

Checked in: s

+ + +
+ Details +
+
+ +
+ +
+ + + + +
+

'; + echo 'HVAC Plugin Health Alert: Critical issues detected. '; + echo 'View Details'; + echo '

'; + } + } + + /** + * Register REST endpoints + */ + public static function register_rest_endpoints() { + register_rest_route('hvac/v1', '/health', [ + 'methods' => 'GET', + 'callback' => [__CLASS__, 'rest_health_check'], + 'permission_callback' => function() { + return current_user_can('manage_options'); + } + ]); + } + + /** + * REST API health check + */ + public static function rest_health_check() { + $results = self::run_all_checks(); + + return new WP_REST_Response([ + 'status' => $results['overall_status'], + 'timestamp' => $results['timestamp'], + 'checks' => $results['checks'] + ], 200); + } + + /** + * WP-CLI health check command + */ + public static function wp_cli_health_check($args, $assoc_args) { + WP_CLI::line('Running HVAC health checks...'); + + $results = self::run_all_checks(true); + + WP_CLI::line('Overall Status: ' . strtoupper($results['overall_status'])); + WP_CLI::line(''); + + foreach ($results['checks'] as $type => $check) { + $status_color = $check['status'] === self::STATUS_HEALTHY ? '%G' : + ($check['status'] === self::STATUS_WARNING ? '%Y' : '%R'); + + WP_CLI::line(sprintf( + '%s: ' . $status_color . '%s%n - %s', + $check['name'], + strtoupper($check['status']), + $check['message'] + )); + } + + if ($results['overall_status'] !== self::STATUS_HEALTHY) { + WP_CLI::error('Health checks failed with issues', false); + } else { + WP_CLI::success('All health checks passed'); + } + } +} \ No newline at end of file diff --git a/includes/class-hvac-performance-monitor.php b/includes/class-hvac-performance-monitor.php new file mode 100644 index 00000000..7e5c2ea6 --- /dev/null +++ b/includes/class-hvac-performance-monitor.php @@ -0,0 +1,951 @@ + 2.0, // 2 seconds + 'memory_usage_mb' => 128, // 128 MB + 'page_load_time' => 3.0, // 3 seconds + 'cpu_usage_percent' => 80, // 80% + 'db_query_count' => 100, // 100 queries per request + 'cache_hit_rate' => 70 // 70% cache hit rate + ]; + + /** + * Performance metrics + */ + private static $metrics = []; + private static $query_log = []; + private static $memory_checkpoints = []; + private static $start_time; + private static $start_memory; + + /** + * Alert settings + */ + private static $alert_settings = [ + 'email_alerts' => true, + 'slack_webhook' => '', + 'alert_threshold' => 3, // Number of incidents before alert + 'alert_cooldown' => 1800, // 30 minutes between alerts + 'benchmark_frequency' => 'daily' + ]; + + /** + * Initialize performance monitoring + */ + public static function init() { + // Load settings + self::$alert_settings = array_merge( + self::$alert_settings, + get_option('hvac_performance_settings', []) + ); + + // Set initial measurements + self::$start_time = microtime(true); + self::$start_memory = memory_get_usage(true); + + // Hook into WordPress performance points + add_action('init', [__CLASS__, 'start_monitoring'], 0); + add_action('wp_loaded', [__CLASS__, 'checkpoint_loaded']); + add_action('wp_footer', [__CLASS__, 'checkpoint_footer']); + add_action('shutdown', [__CLASS__, 'finalize_monitoring']); + + // Database query monitoring + add_filter('query', [__CLASS__, 'monitor_query']); + add_action('wp_db_query', [__CLASS__, 'log_query_completion']); + + // Memory monitoring + add_action('wp_loaded', [__CLASS__, 'checkpoint_memory']); + add_action('wp_footer', [__CLASS__, 'checkpoint_memory']); + + // Schedule performance benchmarks + if (!wp_next_scheduled('hvac_performance_benchmark')) { + $frequency = self::$alert_settings['benchmark_frequency']; + wp_schedule_event(time(), $frequency, 'hvac_performance_benchmark'); + } + + add_action('hvac_performance_benchmark', [__CLASS__, 'run_performance_benchmark']); + + // Admin interface + if (is_admin()) { + add_action('admin_menu', [__CLASS__, 'add_admin_menu']); + add_action('wp_ajax_hvac_performance_action', [__CLASS__, 'handle_performance_action']); + add_action('admin_bar_menu', [__CLASS__, 'add_admin_bar_stats'], 999); + } + + // REST API endpoints + add_action('rest_api_init', [__CLASS__, 'register_rest_endpoints']); + + // WP-CLI integration + if (defined('WP_CLI') && WP_CLI) { + WP_CLI::add_command('hvac performance', [__CLASS__, 'wp_cli_performance']); + } + + // Emergency alerts for critical performance issues + add_action('hvac_critical_performance_issue', [__CLASS__, 'handle_critical_issue']); + } + + /** + * Start monitoring + */ + public static function start_monitoring() { + // Only monitor frontend requests and specific admin pages + if (!self::should_monitor()) { + return; + } + + self::$metrics['start_time'] = microtime(true); + self::$metrics['start_memory'] = memory_get_usage(true); + self::$metrics['query_count'] = 0; + self::$metrics['slow_queries'] = []; + + // Set up query monitoring + global $wpdb; + $wpdb->save_queries = true; + } + + /** + * Check if we should monitor this request + */ + private static function should_monitor() { + // Skip monitoring for certain requests + if (wp_doing_ajax() || wp_doing_cron() || (defined('WP_CLI') && WP_CLI)) { + return false; + } + + // Skip for admin unless it's a plugin page + if (is_admin()) { + $screen = get_current_screen(); + if (!$screen || strpos($screen->id, 'hvac') === false) { + return false; + } + } + + // Skip for REST API unless it's our endpoints + if (defined('REST_REQUEST') && REST_REQUEST) { + $request_uri = $_SERVER['REQUEST_URI'] ?? ''; + if (strpos($request_uri, '/hvac/v1/') === false) { + return false; + } + } + + return true; + } + + /** + * Monitor database queries + */ + public static function monitor_query($query) { + if (!isset(self::$metrics['start_time'])) { + return $query; + } + + $start_time = microtime(true); + + // Store query info for monitoring + self::$query_log[] = [ + 'query' => $query, + 'start_time' => $start_time, + 'backtrace' => debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 5) + ]; + + return $query; + } + + /** + * Log query completion + */ + public static function log_query_completion($result) { + if (empty(self::$query_log)) { + return; + } + + $current_query = &self::$query_log[count(self::$query_log) - 1]; + $execution_time = microtime(true) - $current_query['start_time']; + $current_query['execution_time'] = $execution_time; + + self::$metrics['query_count']++; + + // Check for slow queries + if ($execution_time > self::THRESHOLDS['slow_query_time']) { + self::$metrics['slow_queries'][] = [ + 'query' => substr($current_query['query'], 0, 200), + 'time' => $execution_time, + 'backtrace' => $current_query['backtrace'] + ]; + + // Log slow query + HVAC_Logger::warning( + "Slow query detected: {$execution_time}s - " . substr($current_query['query'], 0, 100), + 'Performance Monitor' + ); + } + } + + /** + * Memory checkpoint + */ + public static function checkpoint_memory($checkpoint = null) { + if (!isset(self::$metrics['start_time'])) { + return; + } + + $current_memory = memory_get_usage(true); + $peak_memory = memory_get_peak_usage(true); + + if ($checkpoint === null) { + $checkpoint = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 1)[0]['function']; + } + + self::$memory_checkpoints[] = [ + 'checkpoint' => $checkpoint, + 'time' => microtime(true), + 'current_memory' => $current_memory, + 'peak_memory' => $peak_memory, + 'memory_mb' => round($current_memory / 1024 / 1024, 2) + ]; + + // Check for memory threshold breach + $memory_mb = $current_memory / 1024 / 1024; + if ($memory_mb > self::THRESHOLDS['memory_usage_mb']) { + HVAC_Logger::warning( + "High memory usage detected: {$memory_mb}MB at checkpoint: $checkpoint", + 'Performance Monitor' + ); + } + } + + /** + * Checkpoint: WordPress loaded + */ + public static function checkpoint_loaded() { + self::checkpoint_memory('wp_loaded'); + } + + /** + * Checkpoint: Footer + */ + public static function checkpoint_footer() { + self::checkpoint_memory('wp_footer'); + } + + /** + * Finalize monitoring + */ + public static function finalize_monitoring() { + if (!isset(self::$metrics['start_time'])) { + return; + } + + $end_time = microtime(true); + $total_time = $end_time - self::$metrics['start_time']; + $peak_memory = memory_get_peak_usage(true); + $peak_memory_mb = $peak_memory / 1024 / 1024; + + // Finalize metrics + self::$metrics['total_time'] = $total_time; + self::$metrics['peak_memory'] = $peak_memory; + self::$metrics['peak_memory_mb'] = $peak_memory_mb; + self::$metrics['timestamp'] = time(); + + // Calculate cache hit rate if object cache is available + if (function_exists('wp_cache_get_stats')) { + $cache_stats = wp_cache_get_stats(); + if (isset($cache_stats['cache_hits']) && isset($cache_stats['cache_misses'])) { + $total_requests = $cache_stats['cache_hits'] + $cache_stats['cache_misses']; + if ($total_requests > 0) { + self::$metrics['cache_hit_rate'] = ($cache_stats['cache_hits'] / $total_requests) * 100; + } + } + } + + // Check thresholds and send alerts + self::check_performance_thresholds(); + + // Store metrics for analysis + self::store_performance_metrics(); + + // Log performance summary for slow requests + if ($total_time > self::THRESHOLDS['page_load_time']) { + HVAC_Logger::warning( + "Slow page load: {$total_time}s, Memory: {$peak_memory_mb}MB, Queries: " . self::$metrics['query_count'], + 'Performance Monitor' + ); + } + } + + /** + * Check performance thresholds + */ + private static function check_performance_thresholds() { + $alerts = []; + + // Check page load time + if (self::$metrics['total_time'] > self::THRESHOLDS['page_load_time']) { + $alerts[] = [ + 'type' => 'slow_page_load', + 'message' => 'Page load time exceeded threshold', + 'value' => self::$metrics['total_time'], + 'threshold' => self::THRESHOLDS['page_load_time'] + ]; + } + + // Check memory usage + if (self::$metrics['peak_memory_mb'] > self::THRESHOLDS['memory_usage_mb']) { + $alerts[] = [ + 'type' => 'high_memory_usage', + 'message' => 'Memory usage exceeded threshold', + 'value' => self::$metrics['peak_memory_mb'], + 'threshold' => self::THRESHOLDS['memory_usage_mb'] + ]; + } + + // Check query count + if (self::$metrics['query_count'] > self::THRESHOLDS['db_query_count']) { + $alerts[] = [ + 'type' => 'excessive_queries', + 'message' => 'Database query count exceeded threshold', + 'value' => self::$metrics['query_count'], + 'threshold' => self::THRESHOLDS['db_query_count'] + ]; + } + + // Check slow queries + if (count(self::$metrics['slow_queries']) > 0) { + $alerts[] = [ + 'type' => 'slow_queries', + 'message' => 'Slow queries detected', + 'value' => count(self::$metrics['slow_queries']), + 'threshold' => 0 + ]; + } + + // Check cache hit rate + if (isset(self::$metrics['cache_hit_rate']) && + self::$metrics['cache_hit_rate'] < self::THRESHOLDS['cache_hit_rate']) { + $alerts[] = [ + 'type' => 'low_cache_hit_rate', + 'message' => 'Cache hit rate below threshold', + 'value' => self::$metrics['cache_hit_rate'], + 'threshold' => self::THRESHOLDS['cache_hit_rate'] + ]; + } + + // Process alerts + if (!empty($alerts)) { + self::process_performance_alerts($alerts); + } + } + + /** + * Process performance alerts + */ + private static function process_performance_alerts($alerts) { + // Count recent alerts to avoid spam + $recent_alerts = self::get_recent_alerts(1800); // Last 30 minutes + + if (count($recent_alerts) >= self::$alert_settings['alert_threshold']) { + return; // Cooldown period + } + + // Store alerts + $stored_alerts = get_option('hvac_performance_alerts', []); + foreach ($alerts as $alert) { + $alert['timestamp'] = time(); + $alert['request_uri'] = $_SERVER['REQUEST_URI'] ?? ''; + $alert['user_agent'] = $_SERVER['HTTP_USER_AGENT'] ?? ''; + $stored_alerts[] = $alert; + } + + // Keep only last 1000 alerts + if (count($stored_alerts) > 1000) { + $stored_alerts = array_slice($stored_alerts, -1000); + } + + update_option('hvac_performance_alerts', $stored_alerts); + + // Send notifications + if (self::$alert_settings['email_alerts']) { + self::send_performance_alert_email($alerts); + } + + // Check for critical issues + $critical_alerts = array_filter($alerts, function($alert) { + return $alert['type'] === 'slow_page_load' && $alert['value'] > 10 || + $alert['type'] === 'high_memory_usage' && $alert['value'] > 256 || + $alert['type'] === 'excessive_queries' && $alert['value'] > 500; + }); + + if (!empty($critical_alerts)) { + do_action('hvac_critical_performance_issue', $critical_alerts); + } + } + + /** + * Get recent alerts + */ + private static function get_recent_alerts($timeframe = 1800) { + $stored_alerts = get_option('hvac_performance_alerts', []); + $cutoff_time = time() - $timeframe; + + return array_filter($stored_alerts, function($alert) use ($cutoff_time) { + return $alert['timestamp'] >= $cutoff_time; + }); + } + + /** + * Send performance alert email + */ + private static function send_performance_alert_email($alerts) { + $admin_email = get_option('admin_email'); + $site_name = get_bloginfo('name'); + + $subject = "[$site_name] Performance Alert"; + $message = "Performance issues detected on $site_name:\n\n"; + + foreach ($alerts as $alert) { + $message .= "• {$alert['message']}\n"; + $message .= " Value: {$alert['value']} (Threshold: {$alert['threshold']})\n\n"; + } + + $message .= "Request: " . ($_SERVER['REQUEST_URI'] ?? 'Unknown') . "\n"; + $message .= "Time: " . date('Y-m-d H:i:s') . "\n"; + $message .= "\nCheck the performance monitor for detailed analysis."; + + wp_mail($admin_email, $subject, $message); + } + + /** + * Store performance metrics + */ + private static function store_performance_metrics() { + // Store in database for trend analysis + global $wpdb; + + $metrics_data = [ + 'timestamp' => time(), + 'page_load_time' => self::$metrics['total_time'], + 'peak_memory_mb' => self::$metrics['peak_memory_mb'], + 'query_count' => self::$metrics['query_count'], + 'slow_query_count' => count(self::$metrics['slow_queries']), + 'cache_hit_rate' => self::$metrics['cache_hit_rate'] ?? null, + 'request_uri' => $_SERVER['REQUEST_URI'] ?? '', + ]; + + // Store in options (simple approach - could use custom table for high-volume sites) + $stored_metrics = get_option('hvac_performance_metrics', []); + $stored_metrics[] = $metrics_data; + + // Keep only last 1000 metrics + if (count($stored_metrics) > 1000) { + $stored_metrics = array_slice($stored_metrics, -1000); + } + + update_option('hvac_performance_metrics', $stored_metrics); + } + + /** + * Run performance benchmark + */ + public static function run_performance_benchmark() { + $benchmark_results = [ + 'timestamp' => time(), + 'database_benchmark' => self::benchmark_database(), + 'memory_benchmark' => self::benchmark_memory(), + 'cache_benchmark' => self::benchmark_cache(), + 'file_io_benchmark' => self::benchmark_file_io() + ]; + + // Store benchmark results + update_option('hvac_performance_benchmark', $benchmark_results); + + HVAC_Logger::info( + 'Performance benchmark completed: ' . json_encode($benchmark_results), + 'Performance Monitor' + ); + + // Check if performance has degraded + $previous_benchmark = get_option('hvac_previous_benchmark', null); + if ($previous_benchmark) { + $degradation = self::check_performance_degradation($previous_benchmark, $benchmark_results); + if ($degradation) { + self::send_performance_alert_email([ + [ + 'type' => 'performance_degradation', + 'message' => 'Performance degradation detected', + 'details' => $degradation + ] + ]); + } + } + + update_option('hvac_previous_benchmark', $benchmark_results); + } + + /** + * Benchmark database performance + */ + private static function benchmark_database() { + global $wpdb; + + $start_time = microtime(true); + + // Simple query benchmark + $wpdb->get_var("SELECT COUNT(*) FROM {$wpdb->posts}"); + $simple_query_time = microtime(true) - $start_time; + + // Complex query benchmark + $start_time = microtime(true); + $wpdb->get_results(" + SELECT p.*, pm.meta_value + FROM {$wpdb->posts} p + LEFT JOIN {$wpdb->postmeta} pm ON p.ID = pm.post_id + WHERE p.post_type = 'post' + AND p.post_status = 'publish' + LIMIT 10 + "); + $complex_query_time = microtime(true) - $start_time; + + return [ + 'simple_query_time' => round($simple_query_time, 4), + 'complex_query_time' => round($complex_query_time, 4) + ]; + } + + /** + * Benchmark memory performance + */ + private static function benchmark_memory() { + $start_memory = memory_get_usage(); + + // Create test data + $test_data = []; + for ($i = 0; $i < 1000; $i++) { + $test_data[] = str_repeat('x', 1000); + } + + $peak_memory = memory_get_usage(); + $memory_used = $peak_memory - $start_memory; + + unset($test_data); + + return [ + 'memory_used_mb' => round($memory_used / 1024 / 1024, 2), + 'peak_memory_mb' => round(memory_get_peak_usage() / 1024 / 1024, 2) + ]; + } + + /** + * Benchmark cache performance + */ + private static function benchmark_cache() { + $iterations = 100; + $test_key = 'hvac_performance_test_' . time(); + $test_data = str_repeat('x', 1000); + + // Write benchmark + $start_time = microtime(true); + for ($i = 0; $i < $iterations; $i++) { + wp_cache_set($test_key . $i, $test_data, 'hvac_perf_test', 60); + } + $write_time = microtime(true) - $start_time; + + // Read benchmark + $start_time = microtime(true); + for ($i = 0; $i < $iterations; $i++) { + wp_cache_get($test_key . $i, 'hvac_perf_test'); + } + $read_time = microtime(true) - $start_time; + + // Cleanup + for ($i = 0; $i < $iterations; $i++) { + wp_cache_delete($test_key . $i, 'hvac_perf_test'); + } + + return [ + 'write_time' => round($write_time, 4), + 'read_time' => round($read_time, 4), + 'writes_per_second' => round($iterations / $write_time, 2), + 'reads_per_second' => round($iterations / $read_time, 2) + ]; + } + + /** + * Benchmark file I/O performance + */ + private static function benchmark_file_io() { + $upload_dir = wp_upload_dir()['basedir']; + $test_file = $upload_dir . '/hvac_perf_test.txt'; + $test_data = str_repeat('Performance test data. ', 1000); + + // Write benchmark + $start_time = microtime(true); + file_put_contents($test_file, $test_data); + $write_time = microtime(true) - $start_time; + + // Read benchmark + $start_time = microtime(true); + $read_data = file_get_contents($test_file); + $read_time = microtime(true) - $start_time; + + // Cleanup + if (file_exists($test_file)) { + unlink($test_file); + } + + return [ + 'write_time' => round($write_time, 4), + 'read_time' => round($read_time, 4), + 'data_size_kb' => round(strlen($test_data) / 1024, 2) + ]; + } + + /** + * Check performance degradation + */ + private static function check_performance_degradation($previous, $current) { + $degradation_threshold = 0.5; // 50% increase + $issues = []; + + // Check database performance + $db_degradation = ($current['database_benchmark']['complex_query_time'] / + $previous['database_benchmark']['complex_query_time']) - 1; + if ($db_degradation > $degradation_threshold) { + $issues[] = "Database queries " . round($db_degradation * 100, 1) . "% slower"; + } + + // Check cache performance + $cache_degradation = ($current['cache_benchmark']['read_time'] / + $previous['cache_benchmark']['read_time']) - 1; + if ($cache_degradation > $degradation_threshold) { + $issues[] = "Cache reads " . round($cache_degradation * 100, 1) . "% slower"; + } + + return empty($issues) ? false : $issues; + } + + /** + * Handle critical performance issues + */ + public static function handle_critical_issue($alerts) { + // Log critical issue + HVAC_Logger::error( + 'Critical performance issue detected: ' . json_encode($alerts), + 'Performance Monitor' + ); + + // Send immediate alert + $admin_email = get_option('admin_email'); + $site_name = get_bloginfo('name'); + + $subject = "[$site_name] CRITICAL Performance Issue"; + $message = "CRITICAL performance issues detected:\n\n"; + + foreach ($alerts as $alert) { + $message .= "• {$alert['message']}: {$alert['value']}\n"; + } + + $message .= "\nImmediate attention required!"; + + wp_mail($admin_email, $subject, $message); + + // Trigger error recovery if available + if (class_exists('HVAC_Error_Recovery')) { + do_action('hvac_operation_failed', 'performance', 'Critical performance degradation', $alerts); + } + } + + /** + * Get performance statistics + */ + public static function get_performance_stats() { + $metrics = get_option('hvac_performance_metrics', []); + $alerts = get_option('hvac_performance_alerts', []); + $benchmark = get_option('hvac_performance_benchmark', null); + + if (empty($metrics)) { + return [ + 'avg_page_load_time' => 0, + 'avg_memory_usage' => 0, + 'avg_query_count' => 0, + 'total_requests' => 0, + 'recent_alerts' => 0, + 'benchmark' => null + ]; + } + + $total_requests = count($metrics); + $recent_cutoff = time() - 86400; // Last 24 hours + + // Calculate averages + $avg_page_load_time = array_sum(array_column($metrics, 'page_load_time')) / $total_requests; + $avg_memory_usage = array_sum(array_column($metrics, 'peak_memory_mb')) / $total_requests; + $avg_query_count = array_sum(array_column($metrics, 'query_count')) / $total_requests; + + // Recent alerts + $recent_alerts = count(array_filter($alerts, function($alert) use ($recent_cutoff) { + return $alert['timestamp'] >= $recent_cutoff; + })); + + return [ + 'avg_page_load_time' => round($avg_page_load_time, 3), + 'avg_memory_usage' => round($avg_memory_usage, 2), + 'avg_query_count' => round($avg_query_count, 1), + 'total_requests' => $total_requests, + 'recent_alerts' => $recent_alerts, + 'benchmark' => $benchmark + ]; + } + + /** + * Add admin menu + */ + public static function add_admin_menu() { + if (current_user_can('manage_options')) { + add_management_page( + 'HVAC Performance Monitor', + 'HVAC Performance', + 'manage_options', + 'hvac-performance-monitor', + [__CLASS__, 'admin_page'] + ); + } + } + + /** + * Add admin bar stats + */ + public static function add_admin_bar_stats($admin_bar) { + if (!current_user_can('manage_options') || !isset(self::$metrics['start_time'])) { + return; + } + + $current_memory = round(memory_get_usage(true) / 1024 / 1024, 1); + $query_count = get_num_queries(); + + $admin_bar->add_node([ + 'id' => 'hvac-performance', + 'title' => "HVAC: {$current_memory}MB | {$query_count}Q", + 'href' => admin_url('tools.php?page=hvac-performance-monitor') + ]); + } + + /** + * Admin page + */ + public static function admin_page() { + $stats = self::get_performance_stats(); + $recent_alerts = array_slice(get_option('hvac_performance_alerts', []), -20, 20, true); + $current_metrics = self::$metrics; + + ?> +
+

HVAC Performance Monitor

+ +
+
+

Performance Overview

+

Average Page Load Time: s

+

Average Memory Usage: MB

+

Average Query Count:

+

Total Requests Monitored:

+

Recent Alerts (24h):

+
+ + +
+

Current Request Stats

+

Load Time:

+

Peak Memory:

+

Queries:

+

Slow Queries:

+
+ + + +
+

Latest Benchmark

+

Date:

+

Database (simple): s

+

Database (complex): s

+

Cache Reads/sec:

+

+ +

+
+ +
+ +
+

Recent Performance Alerts

+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
TimeTypeMessageValueThreshold
No recent performance alerts
+
+ +
+

Performance Thresholds

+

The following thresholds trigger performance alerts:

+
    +
  • Slow Query Time: s
  • +
  • Memory Usage: MB
  • +
  • Page Load Time: s
  • +
  • Database Query Count: queries
  • +
  • Cache Hit Rate: %
  • +
+
+ + + + +
+ 'GET', + 'callback' => [__CLASS__, 'rest_performance_stats'], + 'permission_callback' => function() { + return current_user_can('manage_options'); + } + ]); + } + + /** + * REST API performance stats + */ + public static function rest_performance_stats() { + $stats = self::get_performance_stats(); + + return new WP_REST_Response([ + 'stats' => $stats, + 'timestamp' => time() + ], 200); + } + + /** + * WP-CLI performance command + */ + public static function wp_cli_performance($args, $assoc_args) { + $subcommand = $args[0] ?? 'stats'; + + switch ($subcommand) { + case 'stats': + $stats = self::get_performance_stats(); + WP_CLI::line('HVAC Performance Statistics:'); + WP_CLI::line('Average Page Load: ' . $stats['avg_page_load_time'] . 's'); + WP_CLI::line('Average Memory: ' . $stats['avg_memory_usage'] . 'MB'); + WP_CLI::line('Average Queries: ' . $stats['avg_query_count']); + WP_CLI::line('Recent Alerts: ' . $stats['recent_alerts']); + break; + + case 'benchmark': + WP_CLI::line('Running performance benchmark...'); + self::run_performance_benchmark(); + WP_CLI::success('Benchmark completed'); + break; + + default: + WP_CLI::error('Unknown subcommand. Use: stats, benchmark'); + } + } +} \ No newline at end of file diff --git a/includes/class-hvac-plugin.php b/includes/class-hvac-plugin.php index 9a106595..a78a5dbd 100644 --- a/includes/class-hvac-plugin.php +++ b/includes/class-hvac-plugin.php @@ -107,6 +107,12 @@ class HVAC_Plugin { require_once HVAC_PLUGIN_DIR . 'includes/class-hvac-welcome-popup.php'; require_once HVAC_PLUGIN_DIR . 'includes/class-hvac-background-jobs.php'; require_once HVAC_PLUGIN_DIR . 'includes/class-hvac-query-monitor.php'; + require_once HVAC_PLUGIN_DIR . 'includes/class-hvac-health-monitor.php'; + require_once HVAC_PLUGIN_DIR . 'includes/class-hvac-error-recovery.php'; + require_once HVAC_PLUGIN_DIR . 'includes/class-hvac-security-monitor.php'; + require_once HVAC_PLUGIN_DIR . 'includes/class-hvac-performance-monitor.php'; + require_once HVAC_PLUGIN_DIR . 'includes/class-hvac-backup-manager.php'; + require_once HVAC_PLUGIN_DIR . 'includes/class-hvac-cache-optimizer.php'; // Feature includes - check if files exist before including $feature_includes = [ @@ -346,6 +352,24 @@ class HVAC_Plugin { // Initialize query monitoring HVAC_Query_Monitor::init(); + // Initialize health monitoring + HVAC_Health_Monitor::init(); + + // Initialize error recovery system + HVAC_Error_Recovery::init(); + + // Initialize security monitoring + HVAC_Security_Monitor::init(); + + // Initialize performance monitoring + HVAC_Performance_Monitor::init(); + + // Initialize backup management + HVAC_Backup_Manager::init(); + + // Initialize cache optimization + HVAC_Cache_Optimizer::init(); + // Initialize other components $this->init_components(); diff --git a/includes/class-hvac-security-monitor.php b/includes/class-hvac-security-monitor.php new file mode 100644 index 00000000..1b5d5c1b --- /dev/null +++ b/includes/class-hvac-security-monitor.php @@ -0,0 +1,923 @@ + 'Failed Login Attempt', + 'suspicious_activity' => 'Suspicious Activity', + 'privilege_escalation' => 'Privilege Escalation Attempt', + 'file_modification' => 'Unauthorized File Modification', + 'sql_injection' => 'SQL Injection Attempt', + 'xss_attempt' => 'Cross-Site Scripting Attempt', + 'brute_force' => 'Brute Force Attack', + 'admin_access' => 'Unauthorized Admin Access' + ]; + + /** + * Threat levels + */ + const THREAT_LOW = 'low'; + const THREAT_MEDIUM = 'medium'; + const THREAT_HIGH = 'high'; + const THREAT_CRITICAL = 'critical'; + + /** + * Security settings + */ + private static $settings = [ + 'max_failed_logins' => 5, + 'lockout_duration' => 900, // 15 minutes + 'monitor_file_changes' => true, + 'scan_requests' => true, + 'alert_threshold' => 3, + 'auto_block_ips' => true + ]; + + /** + * Blocked IPs cache + */ + private static $blocked_ips = []; + + /** + * Initialize security monitoring + */ + public static function init() { + // Load settings + self::$settings = array_merge(self::$settings, get_option('hvac_security_settings', [])); + self::$blocked_ips = get_option('hvac_blocked_ips', []); + + // Security monitoring hooks + add_action('wp_login_failed', [__CLASS__, 'handle_failed_login']); + add_action('wp_login', [__CLASS__, 'handle_successful_login'], 10, 2); + add_action('init', [__CLASS__, 'check_request_security']); + add_action('admin_init', [__CLASS__, 'monitor_admin_access']); + + // File monitoring + if (self::$settings['monitor_file_changes']) { + add_action('wp_loaded', [__CLASS__, 'monitor_file_integrity']); + } + + // Database monitoring + add_filter('query', [__CLASS__, 'monitor_database_queries']); + + // Admin interface + if (is_admin()) { + add_action('admin_menu', [__CLASS__, 'add_admin_menu']); + add_action('wp_ajax_hvac_security_action', [__CLASS__, 'handle_security_action']); + } + + // REST API for external monitoring + add_action('rest_api_init', [__CLASS__, 'register_rest_endpoints']); + + // Cleanup old security events + add_action('wp_scheduled_delete', [__CLASS__, 'cleanup_old_events']); + + // Emergency lockdown capability + add_action('hvac_emergency_lockdown', [__CLASS__, 'emergency_lockdown']); + + // WP-CLI integration + if (defined('WP_CLI') && WP_CLI) { + WP_CLI::add_command('hvac security', [__CLASS__, 'wp_cli_security']); + } + } + + /** + * Handle failed login attempts + */ + public static function handle_failed_login($username) { + $ip = self::get_client_ip(); + + // Record the failed attempt + self::log_security_event('failed_login', self::THREAT_MEDIUM, [ + 'username' => $username, + 'ip_address' => $ip, + 'user_agent' => $_SERVER['HTTP_USER_AGENT'] ?? '', + 'timestamp' => time() + ]); + + // Check for brute force pattern + $recent_attempts = self::get_recent_events('failed_login', $ip, 3600); // Last hour + + if (count($recent_attempts) >= self::$settings['max_failed_logins']) { + // Brute force detected + self::log_security_event('brute_force', self::THREAT_HIGH, [ + 'ip_address' => $ip, + 'attempts' => count($recent_attempts), + 'usernames' => array_unique(array_column($recent_attempts, 'username')), + 'auto_blocked' => self::$settings['auto_block_ips'] + ]); + + if (self::$settings['auto_block_ips']) { + self::block_ip($ip, 'Brute force attack detected'); + } + + // Send immediate alert + self::send_security_alert('Brute Force Attack', [ + 'ip_address' => $ip, + 'attempts' => count($recent_attempts), + 'action_taken' => self::$settings['auto_block_ips'] ? 'IP blocked' : 'Logged only' + ]); + } + } + + /** + * Handle successful login + */ + public static function handle_successful_login($username, $user) { + $ip = self::get_client_ip(); + + // Check if this is a suspicious login + $is_suspicious = false; + $reasons = []; + + // Check for unusual location (simplified - could integrate with GeoIP) + $user_last_ip = get_user_meta($user->ID, 'hvac_last_login_ip', true); + if ($user_last_ip && $user_last_ip !== $ip) { + $is_suspicious = true; + $reasons[] = 'Different IP address'; + } + + // Check for admin role login + if (user_can($user, 'manage_options')) { + self::log_security_event('admin_access', self::THREAT_LOW, [ + 'username' => $username, + 'ip_address' => $ip, + 'user_id' => $user->ID, + 'suspicious' => $is_suspicious, + 'reasons' => $reasons + ]); + + if ($is_suspicious) { + self::send_security_alert('Suspicious Admin Login', [ + 'username' => $username, + 'ip_address' => $ip, + 'reasons' => $reasons + ]); + } + } + + // Update user's last login IP + update_user_meta($user->ID, 'hvac_last_login_ip', $ip); + update_user_meta($user->ID, 'hvac_last_login_time', time()); + } + + /** + * Check request security + */ + public static function check_request_security() { + // Skip checks for admin, CLI, or cron + if (is_admin() || wp_doing_cron() || (defined('WP_CLI') && WP_CLI)) { + return; + } + + $ip = self::get_client_ip(); + + // Check if IP is blocked + if (self::is_ip_blocked($ip)) { + self::block_request('IP address is blocked'); + return; + } + + if (!self::$settings['scan_requests']) { + return; + } + + $request_data = $_REQUEST; + $threat_level = self::THREAT_LOW; + $threats_detected = []; + + // Check for SQL injection patterns + $sql_patterns = [ + '/union.*select/i', + '/drop.*table/i', + '/insert.*into/i', + '/delete.*from/i', + '/update.*set/i', + '/exec\s*\(/i' + ]; + + foreach ($request_data as $key => $value) { + if (is_string($value)) { + foreach ($sql_patterns as $pattern) { + if (preg_match($pattern, $value)) { + $threats_detected[] = 'SQL injection pattern in: ' . $key; + $threat_level = self::THREAT_HIGH; + } + } + } + } + + // Check for XSS patterns + $xss_patterns = [ + '/.*?<\/script>/i', + '/javascript:/i', + '/onload\s*=/i', + '/onerror\s*=/i', + '//i' + ]; + + foreach ($request_data as $key => $value) { + if (is_string($value)) { + foreach ($xss_patterns as $pattern) { + if (preg_match($pattern, $value)) { + $threats_detected[] = 'XSS pattern in: ' . $key; + if ($threat_level === self::THREAT_LOW) { + $threat_level = self::THREAT_MEDIUM; + } + } + } + } + } + + // Check for file inclusion attempts + $file_patterns = [ + '/\.\.\//i', + '/etc\/passwd/i', + '/proc\/.*?/i', + '/boot\.ini/i' + ]; + + foreach ($request_data as $key => $value) { + if (is_string($value)) { + foreach ($file_patterns as $pattern) { + if (preg_match($pattern, $value)) { + $threats_detected[] = 'File inclusion attempt in: ' . $key; + $threat_level = self::THREAT_HIGH; + } + } + } + } + + // Log and respond to threats + if (!empty($threats_detected)) { + $event_type = strpos(implode(' ', $threats_detected), 'SQL') !== false ? 'sql_injection' : 'xss_attempt'; + + self::log_security_event($event_type, $threat_level, [ + 'ip_address' => $ip, + 'threats' => $threats_detected, + 'request_uri' => $_SERVER['REQUEST_URI'] ?? '', + 'user_agent' => $_SERVER['HTTP_USER_AGENT'] ?? '', + 'request_data' => $request_data + ]); + + // Auto-block for high/critical threats + if ($threat_level === self::THREAT_HIGH || $threat_level === self::THREAT_CRITICAL) { + if (self::$settings['auto_block_ips']) { + self::block_ip($ip, 'Malicious request detected: ' . implode(', ', $threats_detected)); + } + + self::send_security_alert('Malicious Request Blocked', [ + 'ip_address' => $ip, + 'threats' => $threats_detected, + 'request_uri' => $_SERVER['REQUEST_URI'] ?? '' + ]); + + self::block_request('Malicious request detected'); + } + } + } + + /** + * Monitor admin access + */ + public static function monitor_admin_access() { + if (!current_user_can('manage_options')) { + return; + } + + $user = wp_get_current_user(); + $ip = self::get_client_ip(); + + // Check for privilege escalation attempts + if (isset($_POST['action']) && $_POST['action'] === 'update' && isset($_POST['users'])) { + self::log_security_event('privilege_escalation', self::THREAT_MEDIUM, [ + 'user_id' => $user->ID, + 'username' => $user->user_login, + 'ip_address' => $ip, + 'action' => 'User role modification attempt' + ]); + } + + // Monitor plugin/theme installations + if (isset($_REQUEST['action']) && in_array($_REQUEST['action'], ['install-plugin', 'install-theme', 'upload-plugin', 'upload-theme'])) { + self::log_security_event('suspicious_activity', self::THREAT_LOW, [ + 'user_id' => $user->ID, + 'username' => $user->user_login, + 'ip_address' => $ip, + 'action' => $_REQUEST['action'], + 'item' => $_REQUEST['plugin'] ?? $_REQUEST['theme'] ?? 'unknown' + ]); + } + } + + /** + * Monitor file integrity + */ + public static function monitor_file_integrity() { + // Only run this check periodically to avoid performance issues + $last_check = get_option('hvac_last_file_check', 0); + if (time() - $last_check < 3600) { // Check every hour + return; + } + + update_option('hvac_last_file_check', time()); + + // Check core plugin files + $critical_files = [ + HVAC_PLUGIN_FILE, + HVAC_PLUGIN_DIR . 'includes/class-hvac-plugin.php', + HVAC_PLUGIN_DIR . 'includes/class-hvac-community-events.php' + ]; + + $stored_hashes = get_option('hvac_file_hashes', []); + $current_hashes = []; + $modified_files = []; + + foreach ($critical_files as $file) { + if (file_exists($file)) { + $current_hash = md5_file($file); + $current_hashes[basename($file)] = $current_hash; + + $stored_hash = $stored_hashes[basename($file)] ?? null; + if ($stored_hash && $stored_hash !== $current_hash) { + $modified_files[] = basename($file); + } + } + } + + // Update stored hashes + if (empty($stored_hashes)) { + // First run - just store hashes + update_option('hvac_file_hashes', $current_hashes); + } else { + // Report modifications + if (!empty($modified_files)) { + self::log_security_event('file_modification', self::THREAT_HIGH, [ + 'modified_files' => $modified_files, + 'ip_address' => self::get_client_ip(), + 'detection_time' => time() + ]); + + self::send_security_alert('File Modification Detected', [ + 'modified_files' => $modified_files, + 'total_files' => count($modified_files) + ]); + } + + update_option('hvac_file_hashes', $current_hashes); + } + } + + /** + * Monitor database queries + */ + public static function monitor_database_queries($query) { + // Skip monitoring for admin area and known safe contexts + if (is_admin() || wp_doing_cron() || (defined('WP_CLI') && WP_CLI)) { + return $query; + } + + // Check for suspicious patterns + $suspicious_patterns = [ + '/UNION.*SELECT/i', + '/DROP\s+TABLE/i', + '/DELETE.*FROM.*WHERE.*1\s*=\s*1/i', + '/UPDATE.*SET.*WHERE.*1\s*=\s*1/i' + ]; + + foreach ($suspicious_patterns as $pattern) { + if (preg_match($pattern, $query)) { + self::log_security_event('sql_injection', self::THREAT_CRITICAL, [ + 'query_pattern' => preg_replace('/\s+/', ' ', substr($query, 0, 200)), + 'ip_address' => self::get_client_ip(), + 'user_agent' => $_SERVER['HTTP_USER_AGENT'] ?? '', + 'request_uri' => $_SERVER['REQUEST_URI'] ?? '' + ]); + + // Block immediately for critical SQL injection attempts + self::send_security_alert('Critical SQL Injection Attempt', [ + 'ip_address' => self::get_client_ip(), + 'query_sample' => substr($query, 0, 100) + ]); + + if (self::$settings['auto_block_ips']) { + self::block_ip(self::get_client_ip(), 'SQL injection attempt detected'); + self::block_request('Malicious database query detected'); + } + break; + } + } + + return $query; + } + + /** + * Log security event + */ + private static function log_security_event($type, $threat_level, $data) { + global $wpdb; + + $event = [ + 'type' => $type, + 'threat_level' => $threat_level, + 'ip_address' => $data['ip_address'] ?? self::get_client_ip(), + 'user_id' => get_current_user_id(), + 'data' => json_encode($data), + 'timestamp' => time() + ]; + + // Store in options table (or create custom table for high-volume sites) + $events = get_option('hvac_security_events', []); + $events[] = $event; + + // Keep only last 1000 events to prevent database bloat + if (count($events) > 1000) { + $events = array_slice($events, -1000); + } + + update_option('hvac_security_events', $events); + + // Log to WordPress error log as well + HVAC_Logger::warning( + "Security event: $type ($threat_level) - " . json_encode($data), + 'Security Monitor' + ); + + // Check if alert threshold is reached + $recent_high_threats = self::count_recent_threats([self::THREAT_HIGH, self::THREAT_CRITICAL], 3600); + if ($recent_high_threats >= self::$settings['alert_threshold']) { + self::send_security_alert('Security Alert Threshold Reached', [ + 'recent_threats' => $recent_high_threats, + 'threshold' => self::$settings['alert_threshold'], + 'time_window' => '1 hour' + ]); + } + } + + /** + * Get recent security events + */ + private static function get_recent_events($type, $ip = null, $timeframe = 3600) { + $events = get_option('hvac_security_events', []); + $cutoff_time = time() - $timeframe; + + return array_filter($events, function($event) use ($type, $ip, $cutoff_time) { + if ($event['timestamp'] < $cutoff_time) { + return false; + } + if ($event['type'] !== $type) { + return false; + } + if ($ip && $event['ip_address'] !== $ip) { + return false; + } + return true; + }); + } + + /** + * Count recent threats by level + */ + private static function count_recent_threats($threat_levels, $timeframe = 3600) { + $events = get_option('hvac_security_events', []); + $cutoff_time = time() - $timeframe; + + return count(array_filter($events, function($event) use ($threat_levels, $cutoff_time) { + return $event['timestamp'] >= $cutoff_time && + in_array($event['threat_level'], $threat_levels); + })); + } + + /** + * Block IP address + */ + private static function block_ip($ip, $reason) { + self::$blocked_ips[$ip] = [ + 'reason' => $reason, + 'timestamp' => time(), + 'expires' => time() + self::$settings['lockout_duration'] + ]; + + update_option('hvac_blocked_ips', self::$blocked_ips); + + HVAC_Logger::warning("IP blocked: $ip - $reason", 'Security Monitor'); + } + + /** + * Check if IP is blocked + */ + private static function is_ip_blocked($ip) { + if (!isset(self::$blocked_ips[$ip])) { + return false; + } + + $block_info = self::$blocked_ips[$ip]; + if (time() > $block_info['expires']) { + // Block expired, remove it + unset(self::$blocked_ips[$ip]); + update_option('hvac_blocked_ips', self::$blocked_ips); + return false; + } + + return true; + } + + /** + * Block current request + */ + private static function block_request($reason) { + http_response_code(403); + die('Access Denied: ' . $reason); + } + + /** + * Get client IP address + */ + private static function get_client_ip() { + $headers = [ + 'HTTP_CF_CONNECTING_IP', + 'HTTP_X_FORWARDED_FOR', + 'HTTP_X_FORWARDED', + 'HTTP_X_CLUSTER_CLIENT_IP', + 'HTTP_FORWARDED_FOR', + 'HTTP_FORWARDED', + 'REMOTE_ADDR' + ]; + + foreach ($headers as $header) { + if (!empty($_SERVER[$header])) { + $ips = explode(',', $_SERVER[$header]); + $ip = trim($ips[0]); + if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) { + return $ip; + } + } + } + + return $_SERVER['REMOTE_ADDR'] ?? 'unknown'; + } + + /** + * Send security alert + */ + private static function send_security_alert($subject, $data) { + $admin_email = get_option('admin_email'); + $site_name = get_bloginfo('name'); + + $message = "Security Alert from $site_name\n\n"; + $message .= "Alert: $subject\n\n"; + + foreach ($data as $key => $value) { + if (is_array($value)) { + $value = implode(', ', $value); + } + $message .= ucfirst(str_replace('_', ' ', $key)) . ": $value\n"; + } + + $message .= "\nTime: " . date('Y-m-d H:i:s') . "\n"; + $message .= "Check the security monitor for more details."; + + wp_mail($admin_email, "[$site_name] $subject", $message); + } + + /** + * Emergency lockdown + */ + public static function emergency_lockdown() { + // Block all non-admin access + update_option('hvac_emergency_lockdown', [ + 'enabled' => true, + 'timestamp' => time(), + 'triggered_by' => get_current_user_id() + ]); + + // Send emergency notification + self::send_security_alert('Emergency Lockdown Activated', [ + 'triggered_by' => get_current_user_id(), + 'timestamp' => time(), + 'action' => 'All non-admin access blocked' + ]); + + HVAC_Logger::error('Emergency lockdown activated', 'Security Monitor'); + } + + /** + * Get security statistics + */ + public static function get_security_stats() { + $events = get_option('hvac_security_events', []); + $blocked_ips = get_option('hvac_blocked_ips', []); + + // Count events by type and threat level + $stats = [ + 'total_events' => count($events), + 'blocked_ips' => count($blocked_ips), + 'events_by_type' => [], + 'events_by_threat' => [], + 'recent_events' => count(array_filter($events, function($event) { + return $event['timestamp'] >= (time() - 86400); // Last 24 hours + })) + ]; + + foreach ($events as $event) { + $type = $event['type']; + $threat = $event['threat_level']; + + $stats['events_by_type'][$type] = ($stats['events_by_type'][$type] ?? 0) + 1; + $stats['events_by_threat'][$threat] = ($stats['events_by_threat'][$threat] ?? 0) + 1; + } + + return $stats; + } + + /** + * Cleanup old security events + */ + public static function cleanup_old_events() { + // Remove events older than 30 days + $events = get_option('hvac_security_events', []); + $cutoff_time = time() - (30 * 86400); + + $events = array_filter($events, function($event) use ($cutoff_time) { + return $event['timestamp'] >= $cutoff_time; + }); + + update_option('hvac_security_events', array_values($events)); + + // Clean up expired IP blocks + $blocked_ips = get_option('hvac_blocked_ips', []); + $current_time = time(); + $updated = false; + + foreach ($blocked_ips as $ip => $block_info) { + if ($current_time > $block_info['expires']) { + unset($blocked_ips[$ip]); + $updated = true; + } + } + + if ($updated) { + update_option('hvac_blocked_ips', $blocked_ips); + } + } + + /** + * Add admin menu + */ + public static function add_admin_menu() { + if (current_user_can('manage_options')) { + add_management_page( + 'HVAC Security Monitor', + 'HVAC Security', + 'manage_options', + 'hvac-security-monitor', + [__CLASS__, 'admin_page'] + ); + } + } + + /** + * Admin page + */ + public static function admin_page() { + $stats = self::get_security_stats(); + $recent_events = array_slice(get_option('hvac_security_events', []), -20, 20, true); + $blocked_ips = get_option('hvac_blocked_ips', []); + + ?> +
+

HVAC Security Monitor

+ +
+
+

Security Overview

+

Total Events:

+

Recent Events (24h):

+

Blocked IPs:

+
+ +
+

Threat Distribution

+ $count): ?> +

:

+ +
+
+ +
+

Recent Security Events

+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
TimeTypeThreat LevelIP AddressDetails
No recent security events
+
+ View Details +
+
+
+
+ +
+

Blocked IP Addresses

+ + + + + + + + + + + + + + + + + $block_info): ?> + + + + + + + + + + +
IP AddressReasonBlocked AtExpiresAction
No blocked IPs
+ +
+
+ + + + +
+ 'GET', + 'callback' => [__CLASS__, 'rest_security_stats'], + 'permission_callback' => function() { + return current_user_can('manage_options'); + } + ]); + } + + /** + * REST API security stats + */ + public static function rest_security_stats() { + $stats = self::get_security_stats(); + + return new WP_REST_Response([ + 'stats' => $stats, + 'timestamp' => time() + ], 200); + } + + /** + * WP-CLI security command + */ + public static function wp_cli_security($args, $assoc_args) { + $subcommand = $args[0] ?? 'stats'; + + switch ($subcommand) { + case 'stats': + $stats = self::get_security_stats(); + WP_CLI::line('HVAC Security Statistics:'); + WP_CLI::line('Total Events: ' . $stats['total_events']); + WP_CLI::line('Recent Events (24h): ' . $stats['recent_events']); + WP_CLI::line('Blocked IPs: ' . $stats['blocked_ips']); + break; + + case 'events': + $events = array_slice(get_option('hvac_security_events', []), -10); + WP_CLI::line('Recent Security Events:'); + foreach ($events as $event) { + WP_CLI::line(sprintf( + '%s - %s (%s) - %s', + date('Y-m-d H:i:s', $event['timestamp']), + $event['type'], + $event['threat_level'], + $event['ip_address'] + )); + } + break; + + default: + WP_CLI::error('Unknown subcommand. Use: stats, events'); + } + } +} \ No newline at end of file