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-welcome-popup.php';
|
||||||
require_once HVAC_PLUGIN_DIR . 'includes/class-hvac-background-jobs.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-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 - check if files exist before including
|
||||||
$feature_includes = [
|
$feature_includes = [
|
||||||
|
|
@ -346,6 +352,24 @@ class HVAC_Plugin {
|
||||||
// Initialize query monitoring
|
// Initialize query monitoring
|
||||||
HVAC_Query_Monitor::init();
|
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
|
// Initialize other components
|
||||||
$this->init_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