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>
1413 lines
No EOL
49 KiB
PHP
1413 lines
No EOL
49 KiB
PHP
<?php
|
|
/**
|
|
* HVAC Backup Manager
|
|
*
|
|
* Provides automated backup, disaster recovery, and data protection
|
|
* for critical HVAC plugin data and configurations
|
|
*
|
|
* @package HVAC_Community_Events
|
|
* @since 1.0.8
|
|
*/
|
|
|
|
if (!defined('ABSPATH')) {
|
|
exit;
|
|
}
|
|
|
|
/**
|
|
* HVAC_Backup_Manager class
|
|
*/
|
|
class HVAC_Backup_Manager {
|
|
|
|
/**
|
|
* Backup types
|
|
*/
|
|
const BACKUP_FULL = 'full';
|
|
const BACKUP_INCREMENTAL = 'incremental';
|
|
const BACKUP_DIFFERENTIAL = 'differential';
|
|
const BACKUP_CRITICAL_DATA = 'critical_data';
|
|
|
|
/**
|
|
* Backup status
|
|
*/
|
|
const STATUS_PENDING = 'pending';
|
|
const STATUS_RUNNING = 'running';
|
|
const STATUS_COMPLETED = 'completed';
|
|
const STATUS_FAILED = 'failed';
|
|
|
|
/**
|
|
* Backup settings
|
|
*/
|
|
private static $settings = [
|
|
'auto_backup_enabled' => true,
|
|
'backup_frequency' => 'daily',
|
|
'retention_days' => 30,
|
|
'backup_location' => 'local', // local, s3, ftp
|
|
'compression_enabled' => true,
|
|
'encryption_enabled' => true,
|
|
'max_backup_size_mb' => 500,
|
|
'backup_critical_only' => false
|
|
];
|
|
|
|
/**
|
|
* Critical data tables and options
|
|
*/
|
|
private static $critical_data = [
|
|
'tables' => [
|
|
'posts',
|
|
'postmeta',
|
|
'users',
|
|
'usermeta',
|
|
'options'
|
|
],
|
|
'options' => [
|
|
'hvac_*',
|
|
'tribe_events_*',
|
|
'astra_*'
|
|
],
|
|
'files' => [
|
|
'uploads/hvac-*',
|
|
'plugins/hvac-community-events'
|
|
]
|
|
];
|
|
|
|
/**
|
|
* Initialize backup manager
|
|
*/
|
|
public static function init() {
|
|
// Load settings
|
|
self::$settings = array_merge(
|
|
self::$settings,
|
|
get_option('hvac_backup_settings', [])
|
|
);
|
|
|
|
// Schedule automatic backups
|
|
if (self::$settings['auto_backup_enabled']) {
|
|
self::schedule_automatic_backups();
|
|
}
|
|
|
|
// Hook backup actions
|
|
add_action('hvac_run_backup', [__CLASS__, 'run_scheduled_backup']);
|
|
add_action('hvac_cleanup_old_backups', [__CLASS__, 'cleanup_old_backups']);
|
|
|
|
// Emergency backup triggers
|
|
add_action('hvac_before_critical_operation', [__CLASS__, 'create_emergency_backup']);
|
|
add_action('wp_upgrade', [__CLASS__, 'create_pre_update_backup']);
|
|
add_action('upgrader_process_complete', [__CLASS__, 'create_post_update_backup'], 10, 2);
|
|
|
|
// Admin interface
|
|
if (is_admin()) {
|
|
add_action('admin_menu', [__CLASS__, 'add_admin_menu']);
|
|
add_action('wp_ajax_hvac_backup_action', [__CLASS__, 'handle_backup_action']);
|
|
add_action('admin_notices', [__CLASS__, 'show_backup_notices']);
|
|
}
|
|
|
|
// REST API endpoints
|
|
add_action('rest_api_init', [__CLASS__, 'register_rest_endpoints']);
|
|
|
|
// WP-CLI integration
|
|
if (defined('WP_CLI') && WP_CLI) {
|
|
WP_CLI::add_command('hvac backup', [__CLASS__, 'wp_cli_backup']);
|
|
}
|
|
|
|
// Disaster recovery detection
|
|
add_action('init', [__CLASS__, 'check_disaster_recovery_mode']);
|
|
}
|
|
|
|
/**
|
|
* Schedule automatic backups
|
|
*/
|
|
private static function schedule_automatic_backups() {
|
|
$frequency = self::$settings['backup_frequency'];
|
|
|
|
if (!wp_next_scheduled('hvac_run_backup')) {
|
|
wp_schedule_event(time(), $frequency, 'hvac_run_backup');
|
|
}
|
|
|
|
// Schedule cleanup
|
|
if (!wp_next_scheduled('hvac_cleanup_old_backups')) {
|
|
wp_schedule_event(time(), 'daily', 'hvac_cleanup_old_backups');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Create backup
|
|
*
|
|
* @param string $type Backup type
|
|
* @param array $options Backup options
|
|
* @return array Backup result
|
|
*/
|
|
public static function create_backup($type = self::BACKUP_FULL, $options = []) {
|
|
$backup_id = uniqid('hvac_backup_');
|
|
$timestamp = time();
|
|
|
|
// Default options
|
|
$options = array_merge([
|
|
'compress' => self::$settings['compression_enabled'],
|
|
'encrypt' => self::$settings['encryption_enabled'],
|
|
'description' => '',
|
|
'triggered_by' => 'manual'
|
|
], $options);
|
|
|
|
// Create backup record
|
|
$backup_record = [
|
|
'id' => $backup_id,
|
|
'type' => $type,
|
|
'status' => self::STATUS_RUNNING,
|
|
'created_at' => $timestamp,
|
|
'size' => 0,
|
|
'file_path' => '',
|
|
'options' => $options,
|
|
'progress' => 0
|
|
];
|
|
|
|
self::update_backup_record($backup_id, $backup_record);
|
|
|
|
try {
|
|
// Create backup directory
|
|
$backup_dir = self::get_backup_directory();
|
|
if (!wp_mkdir_p($backup_dir)) {
|
|
throw new Exception('Failed to create backup directory');
|
|
}
|
|
|
|
$backup_file = $backup_dir . "/{$backup_id}.sql";
|
|
|
|
// Perform backup based on type
|
|
switch ($type) {
|
|
case self::BACKUP_FULL:
|
|
$result = self::create_full_backup($backup_file, $backup_id);
|
|
break;
|
|
|
|
case self::BACKUP_CRITICAL_DATA:
|
|
$result = self::create_critical_data_backup($backup_file, $backup_id);
|
|
break;
|
|
|
|
case self::BACKUP_INCREMENTAL:
|
|
$result = self::create_incremental_backup($backup_file, $backup_id);
|
|
break;
|
|
|
|
default:
|
|
throw new Exception('Invalid backup type');
|
|
}
|
|
|
|
// Post-processing
|
|
if ($result['success']) {
|
|
$final_file = $result['file_path'];
|
|
|
|
// Compress if enabled
|
|
if ($options['compress']) {
|
|
$final_file = self::compress_backup($final_file);
|
|
}
|
|
|
|
// Encrypt if enabled
|
|
if ($options['encrypt']) {
|
|
$final_file = self::encrypt_backup($final_file);
|
|
}
|
|
|
|
$file_size = file_exists($final_file) ? filesize($final_file) : 0;
|
|
|
|
// Update backup record
|
|
$backup_record['status'] = self::STATUS_COMPLETED;
|
|
$backup_record['file_path'] = $final_file;
|
|
$backup_record['size'] = $file_size;
|
|
$backup_record['progress'] = 100;
|
|
$backup_record['completed_at'] = time();
|
|
|
|
self::update_backup_record($backup_id, $backup_record);
|
|
|
|
// Log success
|
|
HVAC_Logger::info(
|
|
"Backup completed successfully: $backup_id ($type)",
|
|
'Backup Manager'
|
|
);
|
|
|
|
return [
|
|
'success' => true,
|
|
'backup_id' => $backup_id,
|
|
'file_path' => $final_file,
|
|
'size' => $file_size
|
|
];
|
|
} else {
|
|
throw new Exception($result['error']);
|
|
}
|
|
|
|
} catch (Exception $e) {
|
|
// Update backup record with failure
|
|
$backup_record['status'] = self::STATUS_FAILED;
|
|
$backup_record['error'] = $e->getMessage();
|
|
self::update_backup_record($backup_id, $backup_record);
|
|
|
|
HVAC_Logger::error(
|
|
"Backup failed: $backup_id - " . $e->getMessage(),
|
|
'Backup Manager'
|
|
);
|
|
|
|
return [
|
|
'success' => false,
|
|
'error' => $e->getMessage(),
|
|
'backup_id' => $backup_id
|
|
];
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Create full backup
|
|
*/
|
|
private static function create_full_backup($backup_file, $backup_id) {
|
|
global $wpdb;
|
|
|
|
try {
|
|
// Get all tables
|
|
$tables = $wpdb->get_results('SHOW TABLES', ARRAY_N);
|
|
$total_tables = count($tables);
|
|
$current_table = 0;
|
|
|
|
$sql_content = "-- HVAC Plugin Full Backup\n";
|
|
$sql_content .= "-- Created: " . date('Y-m-d H:i:s') . "\n";
|
|
$sql_content .= "-- Backup ID: $backup_id\n\n";
|
|
|
|
foreach ($tables as $table) {
|
|
$table_name = $table[0];
|
|
$current_table++;
|
|
|
|
// Update progress
|
|
$progress = ($current_table / $total_tables) * 90; // 90% for database, 10% for files
|
|
self::update_backup_progress($backup_id, $progress);
|
|
|
|
// Export table structure
|
|
$create_table = $wpdb->get_row("SHOW CREATE TABLE `$table_name`", ARRAY_N);
|
|
$sql_content .= "\n-- Table structure for `$table_name`\n";
|
|
$sql_content .= "DROP TABLE IF EXISTS `$table_name`;\n";
|
|
$sql_content .= $create_table[1] . ";\n\n";
|
|
|
|
// Export table data
|
|
$rows = $wpdb->get_results("SELECT * FROM `$table_name`", ARRAY_A);
|
|
if (!empty($rows)) {
|
|
$sql_content .= "-- Data for table `$table_name`\n";
|
|
|
|
foreach ($rows as $row) {
|
|
$values = array_map(function($value) {
|
|
return is_null($value) ? 'NULL' : "'" . esc_sql($value) . "'";
|
|
}, array_values($row));
|
|
|
|
$sql_content .= "INSERT INTO `$table_name` VALUES (" .
|
|
implode(', ', $values) . ");\n";
|
|
}
|
|
$sql_content .= "\n";
|
|
}
|
|
}
|
|
|
|
// Write SQL file
|
|
$bytes_written = file_put_contents($backup_file, $sql_content);
|
|
if ($bytes_written === false) {
|
|
throw new Exception('Failed to write backup file');
|
|
}
|
|
|
|
// Update progress to 100%
|
|
self::update_backup_progress($backup_id, 100);
|
|
|
|
return [
|
|
'success' => true,
|
|
'file_path' => $backup_file,
|
|
'tables_exported' => $total_tables,
|
|
'size' => $bytes_written
|
|
];
|
|
|
|
} catch (Exception $e) {
|
|
return [
|
|
'success' => false,
|
|
'error' => $e->getMessage()
|
|
];
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Create critical data backup
|
|
*/
|
|
private static function create_critical_data_backup($backup_file, $backup_id) {
|
|
global $wpdb;
|
|
|
|
try {
|
|
$sql_content = "-- HVAC Plugin Critical Data Backup\n";
|
|
$sql_content .= "-- Created: " . date('Y-m-d H:i:s') . "\n";
|
|
$sql_content .= "-- Backup ID: $backup_id\n\n";
|
|
|
|
$critical_tables = self::$critical_data['tables'];
|
|
$total_tables = count($critical_tables);
|
|
$current_table = 0;
|
|
|
|
foreach ($critical_tables as $table_suffix) {
|
|
$table_name = $wpdb->prefix . $table_suffix;
|
|
$current_table++;
|
|
|
|
// Update progress
|
|
$progress = ($current_table / $total_tables) * 100;
|
|
self::update_backup_progress($backup_id, $progress);
|
|
|
|
// Check if table exists
|
|
$table_exists = $wpdb->get_var($wpdb->prepare(
|
|
"SHOW TABLES LIKE %s",
|
|
$table_name
|
|
));
|
|
|
|
if (!$table_exists) {
|
|
continue;
|
|
}
|
|
|
|
// Export critical data from this table
|
|
if ($table_suffix === 'options') {
|
|
// Export critical options only
|
|
$sql_content .= self::export_critical_options($table_name);
|
|
} elseif ($table_suffix === 'posts') {
|
|
// Export HVAC-related posts only
|
|
$sql_content .= self::export_hvac_posts($table_name);
|
|
} else {
|
|
// Export full table for other critical tables
|
|
$sql_content .= self::export_full_table($table_name);
|
|
}
|
|
}
|
|
|
|
// Write SQL file
|
|
$bytes_written = file_put_contents($backup_file, $sql_content);
|
|
if ($bytes_written === false) {
|
|
throw new Exception('Failed to write critical data backup file');
|
|
}
|
|
|
|
return [
|
|
'success' => true,
|
|
'file_path' => $backup_file,
|
|
'tables_exported' => count($critical_tables),
|
|
'size' => $bytes_written
|
|
];
|
|
|
|
} catch (Exception $e) {
|
|
return [
|
|
'success' => false,
|
|
'error' => $e->getMessage()
|
|
];
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Export critical options
|
|
*/
|
|
private static function export_critical_options($table_name) {
|
|
global $wpdb;
|
|
|
|
$sql = "\n-- Critical Options from $table_name\n";
|
|
|
|
foreach (self::$critical_data['options'] as $option_pattern) {
|
|
$like_pattern = str_replace('*', '%', $option_pattern);
|
|
|
|
$options = $wpdb->get_results($wpdb->prepare(
|
|
"SELECT * FROM `$table_name` WHERE option_name LIKE %s",
|
|
$like_pattern
|
|
), ARRAY_A);
|
|
|
|
foreach ($options as $option) {
|
|
$name = esc_sql($option['option_name']);
|
|
$value = esc_sql($option['option_value']);
|
|
$autoload = esc_sql($option['autoload']);
|
|
|
|
$sql .= "INSERT INTO `$table_name` (option_name, option_value, autoload) VALUES ";
|
|
$sql .= "('$name', '$value', '$autoload') ON DUPLICATE KEY UPDATE ";
|
|
$sql .= "option_value = VALUES(option_value), autoload = VALUES(autoload);\n";
|
|
}
|
|
}
|
|
|
|
return $sql . "\n";
|
|
}
|
|
|
|
/**
|
|
* Export HVAC-related posts
|
|
*/
|
|
private static function export_hvac_posts($table_name) {
|
|
global $wpdb;
|
|
|
|
$sql = "\n-- HVAC-related Posts from $table_name\n";
|
|
|
|
// Export tribe_events and HVAC pages
|
|
$post_types = ['tribe_events', 'page'];
|
|
|
|
foreach ($post_types as $post_type) {
|
|
$posts = $wpdb->get_results($wpdb->prepare(
|
|
"SELECT * FROM `$table_name` WHERE post_type = %s AND (post_title LIKE %s OR post_name LIKE %s)",
|
|
$post_type,
|
|
'%trainer%',
|
|
'%hvac%'
|
|
), ARRAY_A);
|
|
|
|
foreach ($posts as $post) {
|
|
$values = array_map(function($value) {
|
|
return is_null($value) ? 'NULL' : "'" . esc_sql($value) . "'";
|
|
}, array_values($post));
|
|
|
|
$sql .= "INSERT INTO `$table_name` VALUES (" . implode(', ', $values) . ");\n";
|
|
}
|
|
}
|
|
|
|
return $sql . "\n";
|
|
}
|
|
|
|
/**
|
|
* Export full table
|
|
*/
|
|
private static function export_full_table($table_name) {
|
|
global $wpdb;
|
|
|
|
$sql = "\n-- Full export of $table_name\n";
|
|
|
|
// Get table structure
|
|
$create_table = $wpdb->get_row("SHOW CREATE TABLE `$table_name`", ARRAY_N);
|
|
$sql .= "DROP TABLE IF EXISTS `$table_name`;\n";
|
|
$sql .= $create_table[1] . ";\n\n";
|
|
|
|
// Get table data
|
|
$rows = $wpdb->get_results("SELECT * FROM `$table_name`", ARRAY_A);
|
|
|
|
foreach ($rows as $row) {
|
|
$values = array_map(function($value) {
|
|
return is_null($value) ? 'NULL' : "'" . esc_sql($value) . "'";
|
|
}, array_values($row));
|
|
|
|
$sql .= "INSERT INTO `$table_name` VALUES (" . implode(', ', $values) . ");\n";
|
|
}
|
|
|
|
return $sql . "\n";
|
|
}
|
|
|
|
/**
|
|
* Create incremental backup
|
|
*/
|
|
private static function create_incremental_backup($backup_file, $backup_id) {
|
|
// Get last backup timestamp
|
|
$last_backup_time = get_option('hvac_last_backup_timestamp', 0);
|
|
$current_time = time();
|
|
|
|
// Find changes since last backup
|
|
global $wpdb;
|
|
|
|
$sql_content = "-- HVAC Plugin Incremental Backup\n";
|
|
$sql_content .= "-- Created: " . date('Y-m-d H:i:s') . "\n";
|
|
$sql_content .= "-- Since: " . date('Y-m-d H:i:s', $last_backup_time) . "\n";
|
|
$sql_content .= "-- Backup ID: $backup_id\n\n";
|
|
|
|
// Get modified posts
|
|
$modified_posts = $wpdb->get_results($wpdb->prepare(
|
|
"SELECT * FROM {$wpdb->posts} WHERE post_modified_gmt > %s",
|
|
date('Y-m-d H:i:s', $last_backup_time)
|
|
), ARRAY_A);
|
|
|
|
if (!empty($modified_posts)) {
|
|
$sql_content .= "-- Modified Posts\n";
|
|
foreach ($modified_posts as $post) {
|
|
$values = array_map(function($value) {
|
|
return is_null($value) ? 'NULL' : "'" . esc_sql($value) . "'";
|
|
}, array_values($post));
|
|
|
|
$sql_content .= "REPLACE INTO {$wpdb->posts} VALUES (" .
|
|
implode(', ', $values) . ");\n";
|
|
}
|
|
}
|
|
|
|
// Get modified options
|
|
$modified_options = $wpdb->get_results($wpdb->prepare(
|
|
"SELECT * FROM {$wpdb->options} WHERE option_name LIKE %s",
|
|
'hvac_%'
|
|
), ARRAY_A);
|
|
|
|
if (!empty($modified_options)) {
|
|
$sql_content .= "\n-- HVAC Options\n";
|
|
foreach ($modified_options as $option) {
|
|
$name = esc_sql($option['option_name']);
|
|
$value = esc_sql($option['option_value']);
|
|
$autoload = esc_sql($option['autoload']);
|
|
|
|
$sql_content .= "REPLACE INTO {$wpdb->options} (option_name, option_value, autoload) ";
|
|
$sql_content .= "VALUES ('$name', '$value', '$autoload');\n";
|
|
}
|
|
}
|
|
|
|
// Write incremental backup
|
|
$bytes_written = file_put_contents($backup_file, $sql_content);
|
|
if ($bytes_written === false) {
|
|
throw new Exception('Failed to write incremental backup file');
|
|
}
|
|
|
|
// Update last backup timestamp
|
|
update_option('hvac_last_backup_timestamp', $current_time);
|
|
|
|
return [
|
|
'success' => true,
|
|
'file_path' => $backup_file,
|
|
'modified_posts' => count($modified_posts),
|
|
'modified_options' => count($modified_options),
|
|
'size' => $bytes_written
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Compress backup file
|
|
*/
|
|
private static function compress_backup($file_path) {
|
|
if (!function_exists('gzopen')) {
|
|
HVAC_Logger::warning('GZ compression not available', 'Backup Manager');
|
|
return $file_path;
|
|
}
|
|
|
|
$compressed_file = $file_path . '.gz';
|
|
|
|
$file_handle = fopen($file_path, 'rb');
|
|
$gz_handle = gzopen($compressed_file, 'wb9');
|
|
|
|
if ($file_handle && $gz_handle) {
|
|
while (!feof($file_handle)) {
|
|
gzwrite($gz_handle, fread($file_handle, 4096));
|
|
}
|
|
|
|
fclose($file_handle);
|
|
gzclose($gz_handle);
|
|
|
|
// Remove original file
|
|
unlink($file_path);
|
|
|
|
return $compressed_file;
|
|
}
|
|
|
|
return $file_path;
|
|
}
|
|
|
|
/**
|
|
* Encrypt backup file
|
|
*/
|
|
private static function encrypt_backup($file_path) {
|
|
if (!function_exists('openssl_encrypt')) {
|
|
HVAC_Logger::warning('OpenSSL encryption not available', 'Backup Manager');
|
|
return $file_path;
|
|
}
|
|
|
|
$encryption_key = self::get_encryption_key();
|
|
$encrypted_file = $file_path . '.enc';
|
|
|
|
$data = file_get_contents($file_path);
|
|
$iv = openssl_random_pseudo_bytes(16);
|
|
|
|
$encrypted_data = openssl_encrypt(
|
|
$data,
|
|
'AES-256-CBC',
|
|
$encryption_key,
|
|
0,
|
|
$iv
|
|
);
|
|
|
|
if ($encrypted_data !== false) {
|
|
// Store IV + encrypted data
|
|
file_put_contents($encrypted_file, base64_encode($iv . $encrypted_data));
|
|
|
|
// Remove original file
|
|
unlink($file_path);
|
|
|
|
return $encrypted_file;
|
|
}
|
|
|
|
return $file_path;
|
|
}
|
|
|
|
/**
|
|
* Get or generate encryption key
|
|
*/
|
|
private static function get_encryption_key() {
|
|
$key = get_option('hvac_backup_encryption_key');
|
|
|
|
if (!$key) {
|
|
$key = bin2hex(openssl_random_pseudo_bytes(32));
|
|
update_option('hvac_backup_encryption_key', $key);
|
|
}
|
|
|
|
return $key;
|
|
}
|
|
|
|
/**
|
|
* Restore from backup
|
|
*
|
|
* @param string $backup_id Backup ID to restore
|
|
* @param array $options Restore options
|
|
* @return array Restore result
|
|
*/
|
|
public static function restore_from_backup($backup_id, $options = []) {
|
|
$backup_record = self::get_backup_record($backup_id);
|
|
|
|
if (!$backup_record || $backup_record['status'] !== self::STATUS_COMPLETED) {
|
|
return [
|
|
'success' => false,
|
|
'error' => 'Backup not found or incomplete'
|
|
];
|
|
}
|
|
|
|
$backup_file = $backup_record['file_path'];
|
|
|
|
if (!file_exists($backup_file)) {
|
|
return [
|
|
'success' => false,
|
|
'error' => 'Backup file not found'
|
|
];
|
|
}
|
|
|
|
try {
|
|
// Create restoration backup first
|
|
self::create_backup(self::BACKUP_CRITICAL_DATA, [
|
|
'description' => 'Pre-restoration backup',
|
|
'triggered_by' => 'restore_operation'
|
|
]);
|
|
|
|
// Decrypt if needed
|
|
if (str_ends_with($backup_file, '.enc')) {
|
|
$backup_file = self::decrypt_backup($backup_file);
|
|
}
|
|
|
|
// Decompress if needed
|
|
if (str_ends_with($backup_file, '.gz')) {
|
|
$backup_file = self::decompress_backup($backup_file);
|
|
}
|
|
|
|
// Execute SQL restoration
|
|
$sql_content = file_get_contents($backup_file);
|
|
|
|
if ($sql_content === false) {
|
|
throw new Exception('Failed to read backup file');
|
|
}
|
|
|
|
// Split SQL into individual statements
|
|
$statements = array_filter(
|
|
explode(";\n", $sql_content),
|
|
function($statement) {
|
|
return trim($statement) && !str_starts_with(trim($statement), '--');
|
|
}
|
|
);
|
|
|
|
global $wpdb;
|
|
$executed_statements = 0;
|
|
$failed_statements = 0;
|
|
|
|
foreach ($statements as $statement) {
|
|
$statement = trim($statement);
|
|
if (empty($statement)) continue;
|
|
|
|
$result = $wpdb->query($statement);
|
|
|
|
if ($result === false) {
|
|
$failed_statements++;
|
|
HVAC_Logger::warning(
|
|
"Failed to execute SQL: " . $wpdb->last_error,
|
|
'Backup Manager'
|
|
);
|
|
} else {
|
|
$executed_statements++;
|
|
}
|
|
}
|
|
|
|
// Clear all caches
|
|
wp_cache_flush();
|
|
|
|
// Log successful restoration
|
|
HVAC_Logger::info(
|
|
"Backup restored successfully: $backup_id ($executed_statements statements)",
|
|
'Backup Manager'
|
|
);
|
|
|
|
return [
|
|
'success' => true,
|
|
'executed_statements' => $executed_statements,
|
|
'failed_statements' => $failed_statements,
|
|
'backup_id' => $backup_id
|
|
];
|
|
|
|
} catch (Exception $e) {
|
|
HVAC_Logger::error(
|
|
"Backup restoration failed: $backup_id - " . $e->getMessage(),
|
|
'Backup Manager'
|
|
);
|
|
|
|
return [
|
|
'success' => false,
|
|
'error' => $e->getMessage(),
|
|
'backup_id' => $backup_id
|
|
];
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Decrypt backup file
|
|
*/
|
|
private static function decrypt_backup($encrypted_file) {
|
|
$encryption_key = self::get_encryption_key();
|
|
$decrypted_file = str_replace('.enc', '', $encrypted_file);
|
|
|
|
$encrypted_data = base64_decode(file_get_contents($encrypted_file));
|
|
$iv = substr($encrypted_data, 0, 16);
|
|
$data = substr($encrypted_data, 16);
|
|
|
|
$decrypted_data = openssl_decrypt(
|
|
$data,
|
|
'AES-256-CBC',
|
|
$encryption_key,
|
|
0,
|
|
$iv
|
|
);
|
|
|
|
if ($decrypted_data !== false) {
|
|
file_put_contents($decrypted_file, $decrypted_data);
|
|
return $decrypted_file;
|
|
}
|
|
|
|
throw new Exception('Failed to decrypt backup file');
|
|
}
|
|
|
|
/**
|
|
* Decompress backup file
|
|
*/
|
|
private static function decompress_backup($compressed_file) {
|
|
$decompressed_file = str_replace('.gz', '', $compressed_file);
|
|
|
|
$gz_handle = gzopen($compressed_file, 'rb');
|
|
$file_handle = fopen($decompressed_file, 'wb');
|
|
|
|
if ($gz_handle && $file_handle) {
|
|
while (!gzeof($gz_handle)) {
|
|
fwrite($file_handle, gzread($gz_handle, 4096));
|
|
}
|
|
|
|
gzclose($gz_handle);
|
|
fclose($file_handle);
|
|
|
|
return $decompressed_file;
|
|
}
|
|
|
|
throw new Exception('Failed to decompress backup file');
|
|
}
|
|
|
|
/**
|
|
* Run scheduled backup
|
|
*/
|
|
public static function run_scheduled_backup() {
|
|
$backup_type = self::$settings['backup_critical_only'] ?
|
|
self::BACKUP_CRITICAL_DATA :
|
|
self::BACKUP_FULL;
|
|
|
|
$result = self::create_backup($backup_type, [
|
|
'description' => 'Scheduled automatic backup',
|
|
'triggered_by' => 'scheduler'
|
|
]);
|
|
|
|
if ($result['success']) {
|
|
HVAC_Logger::info(
|
|
'Scheduled backup completed: ' . $result['backup_id'],
|
|
'Backup Manager'
|
|
);
|
|
} else {
|
|
HVAC_Logger::error(
|
|
'Scheduled backup failed: ' . $result['error'],
|
|
'Backup Manager'
|
|
);
|
|
|
|
// Send alert email
|
|
self::send_backup_alert('Scheduled Backup Failed', [
|
|
'error' => $result['error'],
|
|
'timestamp' => time()
|
|
]);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Create emergency backup
|
|
*/
|
|
public static function create_emergency_backup() {
|
|
$result = self::create_backup(self::BACKUP_CRITICAL_DATA, [
|
|
'description' => 'Emergency backup before critical operation',
|
|
'triggered_by' => 'emergency'
|
|
]);
|
|
|
|
if (!$result['success']) {
|
|
HVAC_Logger::error(
|
|
'Emergency backup failed: ' . $result['error'],
|
|
'Backup Manager'
|
|
);
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* Create pre-update backup
|
|
*/
|
|
public static function create_pre_update_backup() {
|
|
self::create_backup(self::BACKUP_FULL, [
|
|
'description' => 'Pre-WordPress update backup',
|
|
'triggered_by' => 'wp_update'
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* Create post-update backup
|
|
*/
|
|
public static function create_post_update_backup($upgrader, $options) {
|
|
if ($options['type'] === 'plugin' &&
|
|
isset($options['plugins']) &&
|
|
in_array('hvac-community-events/hvac-community-events.php', $options['plugins'])) {
|
|
|
|
self::create_backup(self::BACKUP_CRITICAL_DATA, [
|
|
'description' => 'Post-plugin update backup',
|
|
'triggered_by' => 'plugin_update'
|
|
]);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Cleanup old backups
|
|
*/
|
|
public static function cleanup_old_backups() {
|
|
$retention_days = self::$settings['retention_days'];
|
|
$cutoff_timestamp = time() - ($retention_days * 86400);
|
|
|
|
$backups = self::get_all_backups();
|
|
$cleaned_count = 0;
|
|
|
|
foreach ($backups as $backup_id => $backup) {
|
|
if ($backup['created_at'] < $cutoff_timestamp) {
|
|
if (self::delete_backup($backup_id)) {
|
|
$cleaned_count++;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ($cleaned_count > 0) {
|
|
HVAC_Logger::info(
|
|
"Cleaned up $cleaned_count old backups",
|
|
'Backup Manager'
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Delete backup
|
|
*/
|
|
public static function delete_backup($backup_id) {
|
|
$backup_record = self::get_backup_record($backup_id);
|
|
|
|
if ($backup_record && !empty($backup_record['file_path'])) {
|
|
if (file_exists($backup_record['file_path'])) {
|
|
unlink($backup_record['file_path']);
|
|
}
|
|
}
|
|
|
|
// Remove from records
|
|
$backups = get_option('hvac_backups', []);
|
|
unset($backups[$backup_id]);
|
|
update_option('hvac_backups', $backups);
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Get backup directory
|
|
*/
|
|
private static function get_backup_directory() {
|
|
$upload_dir = wp_upload_dir();
|
|
return $upload_dir['basedir'] . '/hvac-backups';
|
|
}
|
|
|
|
/**
|
|
* Update backup record
|
|
*/
|
|
private static function update_backup_record($backup_id, $record) {
|
|
$backups = get_option('hvac_backups', []);
|
|
$backups[$backup_id] = $record;
|
|
update_option('hvac_backups', $backups);
|
|
}
|
|
|
|
/**
|
|
* Get backup record
|
|
*/
|
|
private static function get_backup_record($backup_id) {
|
|
$backups = get_option('hvac_backups', []);
|
|
return $backups[$backup_id] ?? null;
|
|
}
|
|
|
|
/**
|
|
* Get all backups
|
|
*/
|
|
public static function get_all_backups() {
|
|
return get_option('hvac_backups', []);
|
|
}
|
|
|
|
/**
|
|
* Update backup progress
|
|
*/
|
|
private static function update_backup_progress($backup_id, $progress) {
|
|
$backup_record = self::get_backup_record($backup_id);
|
|
if ($backup_record) {
|
|
$backup_record['progress'] = $progress;
|
|
self::update_backup_record($backup_id, $backup_record);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check disaster recovery mode
|
|
*/
|
|
public static function check_disaster_recovery_mode() {
|
|
$recovery_mode = get_option('hvac_disaster_recovery_mode', false);
|
|
|
|
if ($recovery_mode) {
|
|
// Display disaster recovery notice
|
|
add_action('admin_notices', function() use ($recovery_mode) {
|
|
echo '<div class="notice notice-error">';
|
|
echo '<p><strong>Disaster Recovery Mode Active</strong></p>';
|
|
echo '<p>Last backup: ' . date('Y-m-d H:i:s', $recovery_mode['timestamp']) . '</p>';
|
|
echo '<p>Issue: ' . esc_html($recovery_mode['issue']) . '</p>';
|
|
echo '</div>';
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Send backup alert
|
|
*/
|
|
private static function send_backup_alert($subject, $data) {
|
|
$admin_email = get_option('admin_email');
|
|
$site_name = get_bloginfo('name');
|
|
|
|
$message = "Backup Alert from $site_name\n\n";
|
|
$message .= "Subject: $subject\n\n";
|
|
|
|
foreach ($data as $key => $value) {
|
|
$message .= ucfirst(str_replace('_', ' ', $key)) . ": $value\n";
|
|
}
|
|
|
|
$message .= "\nTime: " . date('Y-m-d H:i:s') . "\n";
|
|
|
|
wp_mail($admin_email, "[$site_name] $subject", $message);
|
|
}
|
|
|
|
/**
|
|
* Get backup statistics
|
|
*/
|
|
public static function get_backup_stats() {
|
|
$backups = self::get_all_backups();
|
|
|
|
$stats = [
|
|
'total_backups' => count($backups),
|
|
'completed_backups' => 0,
|
|
'failed_backups' => 0,
|
|
'total_size' => 0,
|
|
'last_backup' => null,
|
|
'next_scheduled' => wp_next_scheduled('hvac_run_backup')
|
|
];
|
|
|
|
foreach ($backups as $backup) {
|
|
if ($backup['status'] === self::STATUS_COMPLETED) {
|
|
$stats['completed_backups']++;
|
|
$stats['total_size'] += $backup['size'];
|
|
|
|
if (!$stats['last_backup'] || $backup['created_at'] > $stats['last_backup']['created_at']) {
|
|
$stats['last_backup'] = $backup;
|
|
}
|
|
} elseif ($backup['status'] === self::STATUS_FAILED) {
|
|
$stats['failed_backups']++;
|
|
}
|
|
}
|
|
|
|
return $stats;
|
|
}
|
|
|
|
/**
|
|
* Add admin menu
|
|
*/
|
|
public static function add_admin_menu() {
|
|
if (current_user_can('manage_options')) {
|
|
add_management_page(
|
|
'HVAC Backup Manager',
|
|
'HVAC Backups',
|
|
'manage_options',
|
|
'hvac-backup-manager',
|
|
[__CLASS__, 'admin_page']
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Admin page
|
|
*/
|
|
public static function admin_page() {
|
|
$stats = self::get_backup_stats();
|
|
$backups = array_slice(self::get_all_backups(), -10, 10, true);
|
|
|
|
?>
|
|
<div class="wrap">
|
|
<h1>HVAC Backup Manager</h1>
|
|
|
|
<div class="backup-stats">
|
|
<div class="card">
|
|
<h2>Backup Overview</h2>
|
|
<p><strong>Total Backups:</strong> <?php echo $stats['total_backups']; ?></p>
|
|
<p><strong>Completed:</strong> <?php echo $stats['completed_backups']; ?></p>
|
|
<p><strong>Failed:</strong> <?php echo $stats['failed_backups']; ?></p>
|
|
<p><strong>Total Size:</strong> <?php echo size_format($stats['total_size']); ?></p>
|
|
<?php if ($stats['last_backup']): ?>
|
|
<p><strong>Last Backup:</strong> <?php echo date('Y-m-d H:i:s', $stats['last_backup']['created_at']); ?></p>
|
|
<?php endif; ?>
|
|
<?php if ($stats['next_scheduled']): ?>
|
|
<p><strong>Next Scheduled:</strong> <?php echo date('Y-m-d H:i:s', $stats['next_scheduled']); ?></p>
|
|
<?php endif; ?>
|
|
</div>
|
|
|
|
<div class="card">
|
|
<h2>Create New Backup</h2>
|
|
<p>
|
|
<button type="button" id="create-full-backup" class="button button-primary">Create Full Backup</button>
|
|
<button type="button" id="create-critical-backup" class="button">Create Critical Data Backup</button>
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card">
|
|
<h2>Recent Backups</h2>
|
|
<table class="wp-list-table widefat fixed striped">
|
|
<thead>
|
|
<tr>
|
|
<th>Backup ID</th>
|
|
<th>Type</th>
|
|
<th>Status</th>
|
|
<th>Created</th>
|
|
<th>Size</th>
|
|
<th>Actions</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<?php if (empty($backups)): ?>
|
|
<tr>
|
|
<td colspan="6">No backups found</td>
|
|
</tr>
|
|
<?php else: ?>
|
|
<?php foreach (array_reverse($backups, true) as $backup_id => $backup): ?>
|
|
<tr class="backup-status-<?php echo esc_attr($backup['status']); ?>">
|
|
<td><?php echo esc_html($backup_id); ?></td>
|
|
<td><?php echo esc_html($backup['type']); ?></td>
|
|
<td><?php echo esc_html(strtoupper($backup['status'])); ?></td>
|
|
<td><?php echo date('Y-m-d H:i:s', $backup['created_at']); ?></td>
|
|
<td><?php echo size_format($backup['size'] ?? 0); ?></td>
|
|
<td>
|
|
<?php if ($backup['status'] === self::STATUS_COMPLETED): ?>
|
|
<button type="button" class="button button-small restore-backup"
|
|
data-backup-id="<?php echo esc_attr($backup_id); ?>">
|
|
Restore
|
|
</button>
|
|
<?php endif; ?>
|
|
<button type="button" class="button button-small delete-backup"
|
|
data-backup-id="<?php echo esc_attr($backup_id); ?>">
|
|
Delete
|
|
</button>
|
|
</td>
|
|
</tr>
|
|
<?php endforeach; ?>
|
|
<?php endif; ?>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
<style>
|
|
.backup-stats { display: flex; gap: 20px; margin-bottom: 20px; }
|
|
.backup-stats .card { flex: 1; }
|
|
.backup-status-completed { background-color: #d4edda; }
|
|
.backup-status-failed { background-color: #f8d7da; }
|
|
.backup-status-running { background-color: #fff3cd; }
|
|
</style>
|
|
|
|
<script>
|
|
document.getElementById('create-full-backup')?.addEventListener('click', function() {
|
|
this.disabled = true;
|
|
this.textContent = 'Creating Full Backup...';
|
|
|
|
createBackup('full', this);
|
|
});
|
|
|
|
document.getElementById('create-critical-backup')?.addEventListener('click', function() {
|
|
this.disabled = true;
|
|
this.textContent = 'Creating Critical Backup...';
|
|
|
|
createBackup('critical_data', this);
|
|
});
|
|
|
|
function createBackup(type, button) {
|
|
fetch(ajaxurl, {
|
|
method: 'POST',
|
|
body: new URLSearchParams({
|
|
action: 'hvac_backup_action',
|
|
backup_action: 'create',
|
|
backup_type: type,
|
|
nonce: '<?php echo wp_create_nonce('hvac_backup_action'); ?>'
|
|
})
|
|
})
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.success) {
|
|
location.reload();
|
|
} else {
|
|
alert('Backup failed: ' + data.data);
|
|
button.disabled = false;
|
|
button.textContent = button.textContent.replace('Creating', 'Create');
|
|
}
|
|
});
|
|
}
|
|
|
|
document.querySelectorAll('.restore-backup').forEach(button => {
|
|
button.addEventListener('click', function() {
|
|
const backupId = this.dataset.backupId;
|
|
if (confirm(`Restore from backup ${backupId}? This will overwrite current data!`)) {
|
|
this.disabled = true;
|
|
this.textContent = 'Restoring...';
|
|
|
|
fetch(ajaxurl, {
|
|
method: 'POST',
|
|
body: new URLSearchParams({
|
|
action: 'hvac_backup_action',
|
|
backup_action: 'restore',
|
|
backup_id: backupId,
|
|
nonce: '<?php echo wp_create_nonce('hvac_backup_action'); ?>'
|
|
})
|
|
})
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.success) {
|
|
alert('Backup restored successfully!');
|
|
location.reload();
|
|
} else {
|
|
alert('Restore failed: ' + data.data);
|
|
this.disabled = false;
|
|
this.textContent = 'Restore';
|
|
}
|
|
});
|
|
}
|
|
});
|
|
});
|
|
|
|
document.querySelectorAll('.delete-backup').forEach(button => {
|
|
button.addEventListener('click', function() {
|
|
const backupId = this.dataset.backupId;
|
|
if (confirm(`Delete backup ${backupId}? This action cannot be undone!`)) {
|
|
fetch(ajaxurl, {
|
|
method: 'POST',
|
|
body: new URLSearchParams({
|
|
action: 'hvac_backup_action',
|
|
backup_action: 'delete',
|
|
backup_id: backupId,
|
|
nonce: '<?php echo wp_create_nonce('hvac_backup_action'); ?>'
|
|
})
|
|
})
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.success) {
|
|
location.reload();
|
|
} else {
|
|
alert('Delete failed: ' + data.data);
|
|
}
|
|
});
|
|
}
|
|
});
|
|
});
|
|
</script>
|
|
</div>
|
|
<?php
|
|
}
|
|
|
|
/**
|
|
* Handle backup actions
|
|
*/
|
|
public static function handle_backup_action() {
|
|
check_ajax_referer('hvac_backup_action', 'nonce');
|
|
|
|
if (!current_user_can('manage_options')) {
|
|
wp_send_json_error('Insufficient permissions');
|
|
}
|
|
|
|
$action = sanitize_text_field($_POST['backup_action']);
|
|
|
|
switch ($action) {
|
|
case 'create':
|
|
$type = sanitize_text_field($_POST['backup_type']);
|
|
$result = self::create_backup($type, ['triggered_by' => 'manual']);
|
|
|
|
if ($result['success']) {
|
|
wp_send_json_success('Backup created successfully: ' . $result['backup_id']);
|
|
} else {
|
|
wp_send_json_error($result['error']);
|
|
}
|
|
break;
|
|
|
|
case 'restore':
|
|
$backup_id = sanitize_text_field($_POST['backup_id']);
|
|
$result = self::restore_from_backup($backup_id);
|
|
|
|
if ($result['success']) {
|
|
wp_send_json_success('Backup restored successfully');
|
|
} else {
|
|
wp_send_json_error($result['error']);
|
|
}
|
|
break;
|
|
|
|
case 'delete':
|
|
$backup_id = sanitize_text_field($_POST['backup_id']);
|
|
if (self::delete_backup($backup_id)) {
|
|
wp_send_json_success('Backup deleted successfully');
|
|
} else {
|
|
wp_send_json_error('Failed to delete backup');
|
|
}
|
|
break;
|
|
|
|
default:
|
|
wp_send_json_error('Unknown action');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Show backup notices
|
|
*/
|
|
public static function show_backup_notices() {
|
|
if (!current_user_can('manage_options')) {
|
|
return;
|
|
}
|
|
|
|
$stats = self::get_backup_stats();
|
|
|
|
// Show warning if no recent backups
|
|
if (!$stats['last_backup'] ||
|
|
$stats['last_backup']['created_at'] < (time() - 7 * 86400)) {
|
|
|
|
echo '<div class="notice notice-warning">';
|
|
echo '<p><strong>HVAC Backup Warning:</strong> No recent backups found. ';
|
|
echo '<a href="' . admin_url('tools.php?page=hvac-backup-manager') . '">Create a backup now</a></p>';
|
|
echo '</div>';
|
|
}
|
|
|
|
// Show error if recent backup failures
|
|
if ($stats['failed_backups'] > 0) {
|
|
echo '<div class="notice notice-error">';
|
|
echo '<p><strong>HVAC Backup Error:</strong> Recent backup failures detected. ';
|
|
echo '<a href="' . admin_url('tools.php?page=hvac-backup-manager') . '">Check backup status</a></p>';
|
|
echo '</div>';
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Register REST endpoints
|
|
*/
|
|
public static function register_rest_endpoints() {
|
|
register_rest_route('hvac/v1', '/backup/stats', [
|
|
'methods' => 'GET',
|
|
'callback' => [__CLASS__, 'rest_backup_stats'],
|
|
'permission_callback' => function() {
|
|
return current_user_can('manage_options');
|
|
}
|
|
]);
|
|
|
|
register_rest_route('hvac/v1', '/backup/create', [
|
|
'methods' => 'POST',
|
|
'callback' => [__CLASS__, 'rest_create_backup'],
|
|
'permission_callback' => function() {
|
|
return current_user_can('manage_options');
|
|
}
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* REST API backup stats
|
|
*/
|
|
public static function rest_backup_stats() {
|
|
$stats = self::get_backup_stats();
|
|
|
|
return new WP_REST_Response([
|
|
'stats' => $stats,
|
|
'timestamp' => time()
|
|
], 200);
|
|
}
|
|
|
|
/**
|
|
* REST API create backup
|
|
*/
|
|
public static function rest_create_backup($request) {
|
|
$type = $request->get_param('type') ?: self::BACKUP_CRITICAL_DATA;
|
|
|
|
$result = self::create_backup($type, [
|
|
'triggered_by' => 'api'
|
|
]);
|
|
|
|
if ($result['success']) {
|
|
return new WP_REST_Response($result, 200);
|
|
} else {
|
|
return new WP_REST_Response($result, 500);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* WP-CLI backup command
|
|
*/
|
|
public static function wp_cli_backup($args, $assoc_args) {
|
|
$subcommand = $args[0] ?? 'create';
|
|
|
|
switch ($subcommand) {
|
|
case 'create':
|
|
$type = $assoc_args['type'] ?? self::BACKUP_CRITICAL_DATA;
|
|
WP_CLI::line("Creating $type backup...");
|
|
|
|
$result = self::create_backup($type, ['triggered_by' => 'cli']);
|
|
|
|
if ($result['success']) {
|
|
WP_CLI::success("Backup created: {$result['backup_id']}");
|
|
WP_CLI::line("Size: " . size_format($result['size']));
|
|
} else {
|
|
WP_CLI::error($result['error']);
|
|
}
|
|
break;
|
|
|
|
case 'list':
|
|
$backups = self::get_all_backups();
|
|
WP_CLI::line('Recent backups:');
|
|
foreach (array_slice($backups, -10, 10, true) as $id => $backup) {
|
|
WP_CLI::line(sprintf(
|
|
'%s - %s (%s) - %s',
|
|
$id,
|
|
$backup['type'],
|
|
$backup['status'],
|
|
date('Y-m-d H:i:s', $backup['created_at'])
|
|
));
|
|
}
|
|
break;
|
|
|
|
case 'restore':
|
|
$backup_id = $assoc_args['id'] ?? '';
|
|
if (empty($backup_id)) {
|
|
WP_CLI::error('Backup ID required. Use --id=<backup_id>');
|
|
}
|
|
|
|
WP_CLI::line("Restoring from backup: $backup_id");
|
|
$result = self::restore_from_backup($backup_id);
|
|
|
|
if ($result['success']) {
|
|
WP_CLI::success('Backup restored successfully');
|
|
} else {
|
|
WP_CLI::error($result['error']);
|
|
}
|
|
break;
|
|
|
|
case 'stats':
|
|
$stats = self::get_backup_stats();
|
|
WP_CLI::line('Backup Statistics:');
|
|
WP_CLI::line('Total Backups: ' . $stats['total_backups']);
|
|
WP_CLI::line('Completed: ' . $stats['completed_backups']);
|
|
WP_CLI::line('Failed: ' . $stats['failed_backups']);
|
|
WP_CLI::line('Total Size: ' . size_format($stats['total_size']));
|
|
break;
|
|
|
|
default:
|
|
WP_CLI::error('Unknown subcommand. Use: create, list, restore, stats');
|
|
}
|
|
}
|
|
}
|