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 '
Disaster Recovery Mode Active
'; echo 'Last backup: ' . date('Y-m-d H:i:s', $recovery_mode['timestamp']) . '
'; echo 'Issue: ' . esc_html($recovery_mode['issue']) . '
'; echo 'Total Backups:
Completed:
Failed:
Total Size:
Last Backup:
Next Scheduled:
| Backup ID | Type | Status | Created | Size | Actions | 
|---|---|---|---|---|---|
| No backups found | |||||
HVAC Backup Warning: No recent backups found. '; echo 'Create a backup now
'; echo 'HVAC Backup Error: Recent backup failures detected. '; echo 'Check backup status
'; echo '