feat: Implement comprehensive enterprise monitoring and optimization infrastructure
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 <noreply@anthropic.com>
This commit is contained in:
parent
4d04ad79fe
commit
afc221a98a
8 changed files with 6244 additions and 0 deletions
412
docs/MONITORING-SYSTEMS.md
Normal file
412
docs/MONITORING-SYSTEMS.md
Normal file
|
|
@ -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.
|
||||
1413
includes/class-hvac-backup-manager.php
Normal file
1413
includes/class-hvac-backup-manager.php
Normal file
File diff suppressed because it is too large
Load diff
1171
includes/class-hvac-cache-optimizer.php
Normal file
1171
includes/class-hvac-cache-optimizer.php
Normal file
File diff suppressed because it is too large
Load diff
589
includes/class-hvac-error-recovery.php
Normal file
589
includes/class-hvac-error-recovery.php
Normal file
|
|
@ -0,0 +1,589 @@
|
|||
<?php
|
||||
/**
|
||||
* HVAC Error Recovery System
|
||||
*
|
||||
* Provides automatic error recovery and graceful degradation for plugin functionality
|
||||
*
|
||||
* @package HVAC_Community_Events
|
||||
* @since 1.0.8
|
||||
*/
|
||||
|
||||
if (!defined('ABSPATH')) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* HVAC_Error_Recovery class
|
||||
*/
|
||||
class HVAC_Error_Recovery {
|
||||
|
||||
/**
|
||||
* Recovery strategies
|
||||
*/
|
||||
const STRATEGY_RETRY = 'retry';
|
||||
const STRATEGY_FALLBACK = 'fallback';
|
||||
const STRATEGY_GRACEFUL_FAIL = 'graceful_fail';
|
||||
const STRATEGY_CIRCUIT_BREAKER = 'circuit_breaker';
|
||||
|
||||
/**
|
||||
* Error tracking
|
||||
*/
|
||||
private static $error_counts = [];
|
||||
private static $circuit_breakers = [];
|
||||
|
||||
/**
|
||||
* Recovery configuration
|
||||
*/
|
||||
private static $recovery_config = [
|
||||
'database_query' => [
|
||||
'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();
|
||||
|
||||
?>
|
||||
<div class="wrap">
|
||||
<h1>HVAC Error Recovery System</h1>
|
||||
|
||||
<?php if ($emergency_mode): ?>
|
||||
<div class="notice notice-error">
|
||||
<p><strong>Emergency Mode Active</strong> - Some plugin functionality may be disabled due to critical errors.</p>
|
||||
<p>
|
||||
<button type="button" class="button button-primary" onclick="disableEmergencyMode()">
|
||||
Disable Emergency Mode
|
||||
</button>
|
||||
</p>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="card">
|
||||
<h2>Error Statistics</h2>
|
||||
<table class="wp-list-table widefat fixed striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Operation Type</th>
|
||||
<th>Error Count</th>
|
||||
<th>Circuit Breaker</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php if (empty($stats['error_counts'])): ?>
|
||||
<tr>
|
||||
<td colspan="3">No errors recorded</td>
|
||||
</tr>
|
||||
<?php else: ?>
|
||||
<?php foreach ($stats['error_counts'] as $operation => $count): ?>
|
||||
<tr>
|
||||
<td><?php echo esc_html($operation); ?></td>
|
||||
<td><?php echo esc_html($count); ?></td>
|
||||
<td>
|
||||
<?php
|
||||
$breaker_expiry = $stats['circuit_breakers'][$operation] ?? 0;
|
||||
if ($breaker_expiry > time()) {
|
||||
echo 'OPEN (expires: ' . date('H:i:s', $breaker_expiry) . ')';
|
||||
} else {
|
||||
echo 'CLOSED';
|
||||
}
|
||||
?>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
<?php endif; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h2>Recovery Configuration</h2>
|
||||
<p>The error recovery system is configured with the following strategies:</p>
|
||||
<ul>
|
||||
<li><strong>Database Queries:</strong> Retry with exponential backoff (3 attempts)</li>
|
||||
<li><strong>Cache Operations:</strong> Skip and continue without caching</li>
|
||||
<li><strong>External APIs:</strong> Circuit breaker with 5-minute timeout</li>
|
||||
<li><strong>File Operations:</strong> Graceful failure with safe defaults</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function disableEmergencyMode() {
|
||||
if (confirm('Are you sure you want to disable emergency mode?')) {
|
||||
fetch(ajaxurl, {
|
||||
method: 'POST',
|
||||
body: new URLSearchParams({
|
||||
action: 'hvac_disable_emergency_mode',
|
||||
nonce: '<?php echo wp_create_nonce('hvac_emergency_mode'); ?>'
|
||||
})
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
location.reload();
|
||||
} else {
|
||||
alert('Failed to disable emergency mode');
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<?php
|
||||
}
|
||||
}
|
||||
761
includes/class-hvac-health-monitor.php
Normal file
761
includes/class-hvac-health-monitor.php
Normal file
|
|
@ -0,0 +1,761 @@
|
|||
<?php
|
||||
/**
|
||||
* HVAC Health Monitor
|
||||
*
|
||||
* Provides automated testing and health monitoring for critical plugin functionality
|
||||
*
|
||||
* @package HVAC_Community_Events
|
||||
* @since 1.0.8
|
||||
*/
|
||||
|
||||
if (!defined('ABSPATH')) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* HVAC_Health_Monitor class
|
||||
*/
|
||||
class HVAC_Health_Monitor {
|
||||
|
||||
/**
|
||||
* Health check types
|
||||
*/
|
||||
const CHECK_TYPES = [
|
||||
'database' => '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();
|
||||
|
||||
?>
|
||||
<div class="wrap">
|
||||
<h1>HVAC Health Monitor</h1>
|
||||
|
||||
<div class="card">
|
||||
<h2>Overall Status:
|
||||
<span class="status-<?php echo esc_attr($results['overall_status']); ?>">
|
||||
<?php echo esc_html(strtoupper($results['overall_status'])); ?>
|
||||
</span>
|
||||
</h2>
|
||||
<p>Last checked: <?php echo date('Y-m-d H:i:s', $results['timestamp']); ?></p>
|
||||
<p>
|
||||
<button type="button" id="run-health-check" class="button button-primary">Run Health Check</button>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="health-checks">
|
||||
<?php foreach ($results['checks'] as $type => $check): ?>
|
||||
<div class="card health-check status-<?php echo esc_attr($check['status']); ?>">
|
||||
<h3><?php echo esc_html($check['name']); ?></h3>
|
||||
<p class="status">Status: <?php echo esc_html(strtoupper($check['status'])); ?></p>
|
||||
<p class="message"><?php echo esc_html($check['message']); ?></p>
|
||||
<p class="execution-time">Checked in: <?php echo $check['execution_time']; ?>s</p>
|
||||
|
||||
<?php if (!empty($check['details'])): ?>
|
||||
<details>
|
||||
<summary>Details</summary>
|
||||
<pre><?php echo esc_html(json_encode($check['details'], JSON_PRETTY_PRINT)); ?></pre>
|
||||
</details>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.status-healthy { color: #46b450; }
|
||||
.status-warning { color: #ffb900; }
|
||||
.status-critical { color: #dc3232; }
|
||||
.health-check { margin: 10px 0; }
|
||||
.health-check.status-critical { border-left: 4px solid #dc3232; }
|
||||
.health-check.status-warning { border-left: 4px solid #ffb900; }
|
||||
.health-check.status-healthy { border-left: 4px solid #46b450; }
|
||||
</style>
|
||||
|
||||
<script>
|
||||
document.getElementById('run-health-check').addEventListener('click', function() {
|
||||
this.disabled = true;
|
||||
this.textContent = 'Running...';
|
||||
|
||||
fetch(ajaxurl, {
|
||||
method: 'POST',
|
||||
body: new URLSearchParams({
|
||||
action: 'hvac_run_health_check',
|
||||
nonce: '<?php echo wp_create_nonce('hvac_health_check'); ?>'
|
||||
})
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
location.reload();
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Health check failed:', error);
|
||||
this.disabled = false;
|
||||
this.textContent = 'Run Health Check';
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
* AJAX health check
|
||||
*/
|
||||
public static function ajax_run_health_check() {
|
||||
check_ajax_referer('hvac_health_check', 'nonce');
|
||||
|
||||
if (!current_user_can('manage_options')) {
|
||||
wp_send_json_error('Insufficient permissions');
|
||||
}
|
||||
|
||||
$results = self::run_all_checks(true);
|
||||
wp_send_json_success($results);
|
||||
}
|
||||
|
||||
/**
|
||||
* Show admin warnings
|
||||
*/
|
||||
public static function show_health_warnings() {
|
||||
if (!current_user_can('manage_options')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$results = self::run_all_checks();
|
||||
|
||||
if ($results['overall_status'] === self::STATUS_CRITICAL) {
|
||||
echo '<div class="notice notice-error"><p>';
|
||||
echo '<strong>HVAC Plugin Health Alert:</strong> Critical issues detected. ';
|
||||
echo '<a href="' . admin_url('tools.php?page=hvac-health-monitor') . '">View Details</a>';
|
||||
echo '</p></div>';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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');
|
||||
}
|
||||
}
|
||||
}
|
||||
951
includes/class-hvac-performance-monitor.php
Normal file
951
includes/class-hvac-performance-monitor.php
Normal file
|
|
@ -0,0 +1,951 @@
|
|||
<?php
|
||||
/**
|
||||
* HVAC Performance Monitor
|
||||
*
|
||||
* Provides real-time performance monitoring, benchmarking, and automated
|
||||
* alerts for slow queries, memory leaks, and performance degradation
|
||||
*
|
||||
* @package HVAC_Community_Events
|
||||
* @since 1.0.8
|
||||
*/
|
||||
|
||||
if (!defined('ABSPATH')) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* HVAC_Performance_Monitor class
|
||||
*/
|
||||
class HVAC_Performance_Monitor {
|
||||
|
||||
/**
|
||||
* Performance thresholds
|
||||
*/
|
||||
const THRESHOLDS = [
|
||||
'slow_query_time' => 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;
|
||||
|
||||
?>
|
||||
<div class="wrap">
|
||||
<h1>HVAC Performance Monitor</h1>
|
||||
|
||||
<div class="performance-overview">
|
||||
<div class="card">
|
||||
<h2>Performance Overview</h2>
|
||||
<p><strong>Average Page Load Time:</strong> <?php echo $stats['avg_page_load_time']; ?>s</p>
|
||||
<p><strong>Average Memory Usage:</strong> <?php echo $stats['avg_memory_usage']; ?>MB</p>
|
||||
<p><strong>Average Query Count:</strong> <?php echo $stats['avg_query_count']; ?></p>
|
||||
<p><strong>Total Requests Monitored:</strong> <?php echo $stats['total_requests']; ?></p>
|
||||
<p><strong>Recent Alerts (24h):</strong> <?php echo $stats['recent_alerts']; ?></p>
|
||||
</div>
|
||||
|
||||
<?php if (!empty($current_metrics)): ?>
|
||||
<div class="card">
|
||||
<h2>Current Request Stats</h2>
|
||||
<p><strong>Load Time:</strong> <?php echo isset($current_metrics['total_time']) ? round($current_metrics['total_time'], 3) . 's' : 'In progress'; ?></p>
|
||||
<p><strong>Peak Memory:</strong> <?php echo isset($current_metrics['peak_memory_mb']) ? round($current_metrics['peak_memory_mb'], 2) . 'MB' : round(memory_get_peak_usage(true) / 1024 / 1024, 2) . 'MB'; ?></p>
|
||||
<p><strong>Queries:</strong> <?php echo $current_metrics['query_count'] ?? get_num_queries(); ?></p>
|
||||
<p><strong>Slow Queries:</strong> <?php echo count($current_metrics['slow_queries'] ?? []); ?></p>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($stats['benchmark']): ?>
|
||||
<div class="card">
|
||||
<h2>Latest Benchmark</h2>
|
||||
<p><strong>Date:</strong> <?php echo date('Y-m-d H:i:s', $stats['benchmark']['timestamp']); ?></p>
|
||||
<p><strong>Database (simple):</strong> <?php echo $stats['benchmark']['database_benchmark']['simple_query_time']; ?>s</p>
|
||||
<p><strong>Database (complex):</strong> <?php echo $stats['benchmark']['database_benchmark']['complex_query_time']; ?>s</p>
|
||||
<p><strong>Cache Reads/sec:</strong> <?php echo $stats['benchmark']['cache_benchmark']['reads_per_second']; ?></p>
|
||||
<p>
|
||||
<button type="button" id="run-benchmark" class="button button-primary">Run New Benchmark</button>
|
||||
</p>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h2>Recent Performance Alerts</h2>
|
||||
<table class="wp-list-table widefat fixed striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Time</th>
|
||||
<th>Type</th>
|
||||
<th>Message</th>
|
||||
<th>Value</th>
|
||||
<th>Threshold</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php if (empty($recent_alerts)): ?>
|
||||
<tr>
|
||||
<td colspan="5">No recent performance alerts</td>
|
||||
</tr>
|
||||
<?php else: ?>
|
||||
<?php foreach (array_reverse($recent_alerts) as $alert): ?>
|
||||
<tr>
|
||||
<td><?php echo date('Y-m-d H:i:s', $alert['timestamp']); ?></td>
|
||||
<td><?php echo esc_html($alert['type']); ?></td>
|
||||
<td><?php echo esc_html($alert['message']); ?></td>
|
||||
<td><?php echo esc_html($alert['value']); ?></td>
|
||||
<td><?php echo esc_html($alert['threshold']); ?></td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
<?php endif; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h2>Performance Thresholds</h2>
|
||||
<p>The following thresholds trigger performance alerts:</p>
|
||||
<ul>
|
||||
<li><strong>Slow Query Time:</strong> <?php echo self::THRESHOLDS['slow_query_time']; ?>s</li>
|
||||
<li><strong>Memory Usage:</strong> <?php echo self::THRESHOLDS['memory_usage_mb']; ?>MB</li>
|
||||
<li><strong>Page Load Time:</strong> <?php echo self::THRESHOLDS['page_load_time']; ?>s</li>
|
||||
<li><strong>Database Query Count:</strong> <?php echo self::THRESHOLDS['db_query_count']; ?> queries</li>
|
||||
<li><strong>Cache Hit Rate:</strong> <?php echo self::THRESHOLDS['cache_hit_rate']; ?>%</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.performance-overview { display: flex; gap: 20px; margin-bottom: 20px; }
|
||||
.performance-overview .card { flex: 1; }
|
||||
</style>
|
||||
|
||||
<script>
|
||||
document.getElementById('run-benchmark')?.addEventListener('click', function() {
|
||||
this.disabled = true;
|
||||
this.textContent = 'Running Benchmark...';
|
||||
|
||||
fetch(ajaxurl, {
|
||||
method: 'POST',
|
||||
body: new URLSearchParams({
|
||||
action: 'hvac_performance_action',
|
||||
performance_action: 'run_benchmark',
|
||||
nonce: '<?php echo wp_create_nonce('hvac_performance_action'); ?>'
|
||||
})
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
location.reload();
|
||||
} else {
|
||||
alert('Benchmark failed');
|
||||
this.disabled = false;
|
||||
this.textContent = 'Run New Benchmark';
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle performance actions
|
||||
*/
|
||||
public static function handle_performance_action() {
|
||||
check_ajax_referer('hvac_performance_action', 'nonce');
|
||||
|
||||
if (!current_user_can('manage_options')) {
|
||||
wp_send_json_error('Insufficient permissions');
|
||||
}
|
||||
|
||||
$action = sanitize_text_field($_POST['performance_action']);
|
||||
|
||||
switch ($action) {
|
||||
case 'run_benchmark':
|
||||
self::run_performance_benchmark();
|
||||
wp_send_json_success('Benchmark completed');
|
||||
break;
|
||||
|
||||
default:
|
||||
wp_send_json_error('Unknown action');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Register REST endpoints
|
||||
*/
|
||||
public static function register_rest_endpoints() {
|
||||
register_rest_route('hvac/v1', '/performance/stats', [
|
||||
'methods' => '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');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
|
||||
|
|
|
|||
923
includes/class-hvac-security-monitor.php
Normal file
923
includes/class-hvac-security-monitor.php
Normal file
|
|
@ -0,0 +1,923 @@
|
|||
<?php
|
||||
/**
|
||||
* HVAC Security Monitor
|
||||
*
|
||||
* Provides real-time security monitoring, threat detection, and automated
|
||||
* security response for the HVAC Community Events plugin
|
||||
*
|
||||
* @package HVAC_Community_Events
|
||||
* @since 1.0.8
|
||||
*/
|
||||
|
||||
if (!defined('ABSPATH')) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* HVAC_Security_Monitor class
|
||||
*/
|
||||
class HVAC_Security_Monitor {
|
||||
|
||||
/**
|
||||
* Security event types
|
||||
*/
|
||||
const EVENT_TYPES = [
|
||||
'failed_login' => '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.*?>.*?<\/script>/i',
|
||||
'/javascript:/i',
|
||||
'/onload\s*=/i',
|
||||
'/onerror\s*=/i',
|
||||
'/<iframe.*?>/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', []);
|
||||
|
||||
?>
|
||||
<div class="wrap">
|
||||
<h1>HVAC Security Monitor</h1>
|
||||
|
||||
<div class="security-stats">
|
||||
<div class="card">
|
||||
<h2>Security Overview</h2>
|
||||
<p><strong>Total Events:</strong> <?php echo $stats['total_events']; ?></p>
|
||||
<p><strong>Recent Events (24h):</strong> <?php echo $stats['recent_events']; ?></p>
|
||||
<p><strong>Blocked IPs:</strong> <?php echo $stats['blocked_ips']; ?></p>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h2>Threat Distribution</h2>
|
||||
<?php foreach ($stats['events_by_threat'] as $threat => $count): ?>
|
||||
<p><strong><?php echo ucfirst($threat); ?>:</strong> <?php echo $count; ?></p>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h2>Recent Security Events</h2>
|
||||
<table class="wp-list-table widefat fixed striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Time</th>
|
||||
<th>Type</th>
|
||||
<th>Threat Level</th>
|
||||
<th>IP Address</th>
|
||||
<th>Details</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php if (empty($recent_events)): ?>
|
||||
<tr>
|
||||
<td colspan="5">No recent security events</td>
|
||||
</tr>
|
||||
<?php else: ?>
|
||||
<?php foreach (array_reverse($recent_events) as $event): ?>
|
||||
<tr class="threat-<?php echo esc_attr($event['threat_level']); ?>">
|
||||
<td><?php echo date('Y-m-d H:i:s', $event['timestamp']); ?></td>
|
||||
<td><?php echo esc_html(self::EVENT_TYPES[$event['type']] ?? $event['type']); ?></td>
|
||||
<td><?php echo esc_html(strtoupper($event['threat_level'])); ?></td>
|
||||
<td><?php echo esc_html($event['ip_address']); ?></td>
|
||||
<td>
|
||||
<details>
|
||||
<summary>View Details</summary>
|
||||
<pre><?php echo esc_html(json_encode(json_decode($event['data']), JSON_PRETTY_PRINT)); ?></pre>
|
||||
</details>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
<?php endif; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h2>Blocked IP Addresses</h2>
|
||||
<table class="wp-list-table widefat fixed striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>IP Address</th>
|
||||
<th>Reason</th>
|
||||
<th>Blocked At</th>
|
||||
<th>Expires</th>
|
||||
<th>Action</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php if (empty($blocked_ips)): ?>
|
||||
<tr>
|
||||
<td colspan="5">No blocked IPs</td>
|
||||
</tr>
|
||||
<?php else: ?>
|
||||
<?php foreach ($blocked_ips as $ip => $block_info): ?>
|
||||
<tr>
|
||||
<td><?php echo esc_html($ip); ?></td>
|
||||
<td><?php echo esc_html($block_info['reason']); ?></td>
|
||||
<td><?php echo date('Y-m-d H:i:s', $block_info['timestamp']); ?></td>
|
||||
<td><?php echo date('Y-m-d H:i:s', $block_info['expires']); ?></td>
|
||||
<td>
|
||||
<button type="button" class="button button-small unblock-ip"
|
||||
data-ip="<?php echo esc_attr($ip); ?>">
|
||||
Unblock
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
<?php endif; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.threat-low { background-color: #d4edda; }
|
||||
.threat-medium { background-color: #fff3cd; }
|
||||
.threat-high { background-color: #f8d7da; }
|
||||
.threat-critical { background-color: #d1ecf1; }
|
||||
.security-stats { display: flex; gap: 20px; }
|
||||
.security-stats .card { flex: 1; }
|
||||
</style>
|
||||
|
||||
<script>
|
||||
document.querySelectorAll('.unblock-ip').forEach(button => {
|
||||
button.addEventListener('click', function() {
|
||||
const ip = this.dataset.ip;
|
||||
if (confirm(`Unblock IP address ${ip}?`)) {
|
||||
fetch(ajaxurl, {
|
||||
method: 'POST',
|
||||
body: new URLSearchParams({
|
||||
action: 'hvac_security_action',
|
||||
security_action: 'unblock_ip',
|
||||
ip_address: ip,
|
||||
nonce: '<?php echo wp_create_nonce('hvac_security_action'); ?>'
|
||||
})
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
location.reload();
|
||||
} else {
|
||||
alert('Failed to unblock IP');
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle security actions
|
||||
*/
|
||||
public static function handle_security_action() {
|
||||
check_ajax_referer('hvac_security_action', 'nonce');
|
||||
|
||||
if (!current_user_can('manage_options')) {
|
||||
wp_send_json_error('Insufficient permissions');
|
||||
}
|
||||
|
||||
$action = sanitize_text_field($_POST['security_action']);
|
||||
|
||||
switch ($action) {
|
||||
case 'unblock_ip':
|
||||
$ip = sanitize_text_field($_POST['ip_address']);
|
||||
$blocked_ips = get_option('hvac_blocked_ips', []);
|
||||
unset($blocked_ips[$ip]);
|
||||
update_option('hvac_blocked_ips', $blocked_ips);
|
||||
wp_send_json_success('IP unblocked');
|
||||
break;
|
||||
|
||||
default:
|
||||
wp_send_json_error('Unknown action');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Register REST endpoints
|
||||
*/
|
||||
public static function register_rest_endpoints() {
|
||||
register_rest_route('hvac/v1', '/security/stats', [
|
||||
'methods' => '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');
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue