compression_manager = TigerStyleSEO_Compression_Manager::instance(); $this->storage_manager = TigerStyleSEO_Storage_Manager::instance(); $this->logger = TigerStyleSEO_Backup_Logger::instance(); $this->validator = TigerStyleSEO_Backup_Validator::instance(); $this->settings = get_option('tigerstyle_backup_settings', array()); } /** * Restore from backup * * @param array $options Restore options * @return string Restore ID */ public function restore_backup($options = array()) { $defaults = array( 'backup_id' => '', 'restore_files' => true, 'restore_database' => true, 'create_rollback' => true, 'force_restore' => false, 'validate_before_restore' => true ); $options = wp_parse_args($options, $defaults); if (empty($options['backup_id'])) { throw new Exception(__('Backup ID is required', 'tigerstyle-heat')); } // Generate unique restore ID $this->current_restore_id = 'restore_' . time() . '_' . wp_generate_password(8, false); try { // Initialize restore process $this->init_restore_process($options); // Download and extract backup $backup_dir = $this->prepare_backup_for_restore($options['backup_id']); // Load and validate manifest $this->load_backup_manifest($backup_dir); // Validate backup integrity if ($options['validate_before_restore']) { $this->validate_backup_integrity($backup_dir); } // Create rollback backup if requested if ($options['create_rollback']) { $this->create_rollback_backup(); } // Perform restoration if ($options['restore_database']) { $this->restore_database($backup_dir); } if ($options['restore_files']) { $this->restore_files($backup_dir); } // Finalize restoration $this->finalize_restoration($backup_dir); // Log success $this->logger->info('Restore completed successfully', array( 'restore_id' => $this->current_restore_id, 'backup_id' => $options['backup_id'], 'options' => $options )); // Update progress to 100% $this->update_restore_progress(100, __('Restore completed successfully', 'tigerstyle-heat')); return $this->current_restore_id; } catch (Exception $e) { $this->logger->error('Restore failed: ' . $e->getMessage(), array( 'restore_id' => $this->current_restore_id, 'backup_id' => $options['backup_id'], 'options' => $options )); // Cleanup on failure $this->cleanup_failed_restore(); throw $e; } } /** * Initialize restore process */ private function init_restore_process($options) { // Set progress to 0% $this->update_restore_progress(0, __('Initializing restore...', 'tigerstyle-heat')); // Check system requirements $this->check_restore_requirements($options); // Set time limit @set_time_limit(0); // Increase memory limit if possible @ini_set('memory_limit', '512M'); // Check permissions $this->check_restore_permissions(); } /** * Check restore requirements */ private function check_restore_requirements($options) { // Check if backup exists if (!$this->storage_manager->backup_exists($options['backup_id'])) { throw new Exception(__('Backup not found', 'tigerstyle-heat')); } // Check disk space $backup_info = $this->storage_manager->get_backup_info($options['backup_id']); $required_space = $backup_info['size'] * 3; // Backup size + extraction + current files $available_space = disk_free_space(ABSPATH); if ($available_space !== false && $available_space < $required_space) { throw new Exception(__('Insufficient disk space for restore', 'tigerstyle-heat')); } // Check if WordPress is currently being used heavily if (!$options['force_restore'] && $this->is_site_busy()) { throw new Exception(__('Site appears to be busy. Use force_restore option to override.', 'tigerstyle-heat')); } } /** * Check restore permissions */ private function check_restore_permissions() { // Check file system permissions if (!is_writable(ABSPATH)) { throw new Exception(__('WordPress root directory is not writable', 'tigerstyle-heat')); } if (!is_writable(WP_CONTENT_DIR)) { throw new Exception(__('WordPress content directory is not writable', 'tigerstyle-heat')); } // Check database permissions global $wpdb; $test_result = $wpdb->query("CREATE TEMPORARY TABLE tigerstyle_test (id int)"); if ($test_result === false) { throw new Exception(__('Insufficient database permissions for restore', 'tigerstyle-heat')); } } /** * Prepare backup for restore */ private function prepare_backup_for_restore($backup_id) { $this->update_restore_progress(5, __('Downloading backup...', 'tigerstyle-heat')); // Download backup from storage $backup_file = $this->storage_manager->download_backup($backup_id); $this->update_restore_progress(15, __('Extracting backup...', 'tigerstyle-heat')); // Extract backup $backup_dir = $this->extract_backup($backup_file); return $backup_dir; } /** * Extract backup */ private function extract_backup($backup_file) { $upload_dir = wp_upload_dir(); $extract_dir = $upload_dir['basedir'] . '/tigerstyle-restores/' . $this->current_restore_id; if (!wp_mkdir_p($extract_dir)) { throw new Exception(__('Failed to create extraction directory', 'tigerstyle-heat')); } // Detect compression method from file extension $compression_method = $this->detect_compression_method($backup_file); // Extract using appropriate method $this->compression_manager->extract_archive($backup_file, $extract_dir, $compression_method); return $extract_dir; } /** * Load backup manifest */ private function load_backup_manifest($backup_dir) { $manifest_file = $backup_dir . '/manifest.json'; if (!file_exists($manifest_file)) { throw new Exception(__('Backup manifest not found', 'tigerstyle-heat')); } $manifest_content = file_get_contents($manifest_file); $this->backup_manifest = json_decode($manifest_content, true); if (json_last_error() !== JSON_ERROR_NONE) { throw new Exception(__('Invalid backup manifest format', 'tigerstyle-heat')); } $this->logger->info('Backup manifest loaded', array( 'restore_id' => $this->current_restore_id, 'backup_id' => $this->backup_manifest['backup_id'], 'created_at' => $this->backup_manifest['created_at'] )); } /** * Validate backup integrity */ private function validate_backup_integrity($backup_dir) { $this->update_restore_progress(20, __('Validating backup integrity...', 'tigerstyle-heat')); if (!$this->validator->validate_backup_directory($backup_dir, $this->backup_manifest)) { throw new Exception(__('Backup integrity validation failed', 'tigerstyle-heat')); } $this->logger->info('Backup integrity validated successfully', array( 'restore_id' => $this->current_restore_id, 'backup_id' => $this->backup_manifest['backup_id'] )); } /** * Create rollback backup */ private function create_rollback_backup() { $this->update_restore_progress(25, __('Creating rollback backup...', 'tigerstyle-heat')); try { $backup_engine = new TigerStyleSEO_Backup_Engine(); $rollback_id = $backup_engine->create_backup(array( 'type' => 'rollback', 'description' => 'Rollback backup before restore - ' . current_time('mysql'), 'compression' => 'zip', 'storage_location' => 'local', 'include_files' => true, 'include_database' => true )); // Store rollback ID for potential use update_option('tigerstyle_last_rollback_backup', $rollback_id); $this->logger->info('Rollback backup created', array( 'restore_id' => $this->current_restore_id, 'rollback_backup_id' => $rollback_id )); } catch (Exception $e) { $this->logger->warning('Failed to create rollback backup: ' . $e->getMessage()); // Don't fail the restore if rollback backup fails } } /** * Restore database */ private function restore_database($backup_dir) { $this->update_restore_progress(30, __('Restoring database...', 'tigerstyle-heat')); $sql_file = $backup_dir . '/database.sql'; if (!file_exists($sql_file)) { throw new Exception(__('Database backup file not found', 'tigerstyle-heat')); } global $wpdb; // Read and execute SQL file in chunks $handle = fopen($sql_file, 'r'); if (!$handle) { throw new Exception(__('Failed to open database backup file', 'tigerstyle-heat')); } try { $sql_buffer = ''; $line_count = 0; $total_lines = $this->count_file_lines($sql_file); while (($line = fgets($handle)) !== false) { $line_count++; // Skip comments and empty lines $line = trim($line); if (empty($line) || strpos($line, '--') === 0) { continue; } $sql_buffer .= $line; // Execute complete statements if (substr($line, -1) === ';') { $result = $wpdb->query($sql_buffer); if ($result === false && !empty($wpdb->last_error)) { $this->logger->warning('Database restore warning: ' . $wpdb->last_error, array( 'sql' => substr($sql_buffer, 0, 200) . '...' )); } $sql_buffer = ''; // Update progress if ($line_count % 100 === 0) { $progress = 30 + (($line_count / $total_lines) * 40); // 30-70% $this->update_restore_progress($progress, sprintf(__('Restoring database: %d%%', 'tigerstyle-heat'), ($line_count / $total_lines) * 100)); } } } // Execute any remaining SQL if (!empty(trim($sql_buffer))) { $wpdb->query($sql_buffer); } } finally { fclose($handle); } // Clear WordPress caches wp_cache_flush(); $this->logger->info('Database restore completed', array( 'restore_id' => $this->current_restore_id, 'lines_processed' => $line_count )); } /** * Restore files */ private function restore_files($backup_dir) { $this->update_restore_progress(70, __('Restoring files...', 'tigerstyle-heat')); $files_dir = $backup_dir . '/files'; if (!is_dir($files_dir)) { throw new Exception(__('Files backup directory not found', 'tigerstyle-heat')); } // Count total files for progress tracking $total_files = $this->count_directory_files($files_dir); $processed_files = 0; // Restore files $this->restore_directory_recursive($files_dir, ABSPATH, $total_files, $processed_files); $this->logger->info('File restore completed', array( 'restore_id' => $this->current_restore_id, 'files_restored' => $processed_files )); } /** * Restore directory recursively */ private function restore_directory_recursive($source_dir, $dest_dir, $total_files, &$processed_files) { $iterator = new RecursiveIteratorIterator( new RecursiveDirectoryIterator($source_dir, RecursiveDirectoryIterator::SKIP_DOTS), RecursiveIteratorIterator::SELF_FIRST ); foreach ($iterator as $item) { $source_path = $item->getPathname(); $relative_path = substr($source_path, strlen($source_dir) + 1); $dest_path = $dest_dir . $relative_path; if ($item->isDir()) { if (!is_dir($dest_path)) { wp_mkdir_p($dest_path); } } elseif ($item->isFile()) { // Create destination directory if it doesn't exist $dest_parent = dirname($dest_path); if (!is_dir($dest_parent)) { wp_mkdir_p($dest_parent); } // Copy file with error handling if (!copy($source_path, $dest_path)) { $this->logger->warning('Failed to restore file: ' . $relative_path); } else { // Preserve file permissions $source_perms = fileperms($source_path); chmod($dest_path, $source_perms); } $processed_files++; // Update progress if ($processed_files % 50 === 0) { $progress = 70 + (($processed_files / $total_files) * 25); // 70-95% $this->update_restore_progress($progress, sprintf(__('Restored %d of %d files', 'tigerstyle-heat'), $processed_files, $total_files)); } } } } /** * Finalize restoration */ private function finalize_restoration($backup_dir) { $this->update_restore_progress(95, __('Finalizing restoration...', 'tigerstyle-heat')); // Update site URL if necessary $this->update_site_urls(); // Clear all caches $this->clear_all_caches(); // Flush rewrite rules flush_rewrite_rules(); // Run any post-restore hooks do_action('tigerstyle_backup_restore_completed', $this->current_restore_id, $this->backup_manifest); // Cleanup extraction directory $this->cleanup_extraction_directory($backup_dir); $this->logger->info('Restoration finalized', array( 'restore_id' => $this->current_restore_id )); } /** * Update site URLs if necessary */ private function update_site_urls() { $current_home_url = home_url(); $current_site_url = site_url(); $backup_site_url = $this->backup_manifest['site_url'] ?? ''; // If URLs are different, ask user what to do if (!empty($backup_site_url) && $backup_site_url !== $current_home_url) { $this->logger->info('Site URL mismatch detected', array( 'current_url' => $current_home_url, 'backup_url' => $backup_site_url )); // For now, keep current URLs // In a full implementation, you might want to prompt the user } } /** * Clear all caches */ private function clear_all_caches() { // WordPress object cache wp_cache_flush(); // Opcache if (function_exists('opcache_reset')) { opcache_reset(); } // Clear transients global $wpdb; $wpdb->query("DELETE FROM {$wpdb->options} WHERE option_name LIKE '_transient_%'"); // Clear common caching plugins $this->clear_plugin_caches(); } /** * Clear plugin caches */ private function clear_plugin_caches() { // WP Rocket if (function_exists('rocket_clean_domain')) { rocket_clean_domain(); } // W3 Total Cache if (function_exists('w3tc_flush_all')) { w3tc_flush_all(); } // WP Super Cache if (function_exists('wp_cache_clear_cache')) { wp_cache_clear_cache(); } // LiteSpeed Cache if (class_exists('LiteSpeed_Cache_API')) { LiteSpeed_Cache_API::purge_all(); } } /** * Update restore progress */ private function update_restore_progress($percentage, $message) { $progress = array( 'percentage' => $percentage, 'message' => $message, 'timestamp' => time(), 'restore_id' => $this->current_restore_id ); set_transient('tigerstyle_restore_progress_' . $this->current_restore_id, $progress, 3600); } /** * Detect compression method from filename */ private function detect_compression_method($filename) { $extension = strtolower(pathinfo($filename, PATHINFO_EXTENSION)); switch ($extension) { case 'zip': return 'zip'; case 'gz': return pathinfo(pathinfo($filename, PATHINFO_FILENAME), PATHINFO_EXTENSION) === 'tar' ? 'tar.gz' : 'gz'; case 'bz2': return pathinfo(pathinfo($filename, PATHINFO_FILENAME), PATHINFO_EXTENSION) === 'tar' ? 'tar.bz2' : 'bz2'; default: return 'none'; } } /** * Count lines in file */ private function count_file_lines($filename) { $handle = fopen($filename, 'r'); if (!$handle) { return 0; } $line_count = 0; while (fgets($handle) !== false) { $line_count++; } fclose($handle); return $line_count; } /** * Count files in directory */ private function count_directory_files($directory) { $count = 0; $iterator = new RecursiveIteratorIterator( new RecursiveDirectoryIterator($directory, RecursiveDirectoryIterator::SKIP_DOTS) ); foreach ($iterator as $file) { if ($file->isFile()) { $count++; } } return $count; } /** * Check if site is busy */ private function is_site_busy() { // Simple check: if there are many active sessions or high CPU usage // This is a basic implementation - in production you might want more sophisticated checks $load_average = sys_getloadavg(); if ($load_average && $load_average[0] > 2.0) { return true; } return false; } /** * Cleanup extraction directory */ private function cleanup_extraction_directory($backup_dir) { if (is_dir($backup_dir)) { $this->remove_directory($backup_dir); } } /** * Remove directory recursively */ private function remove_directory($dir) { if (!is_dir($dir)) { return; } $iterator = new RecursiveIteratorIterator( new RecursiveDirectoryIterator($dir, RecursiveDirectoryIterator::SKIP_DOTS), RecursiveIteratorIterator::CHILD_FIRST ); foreach ($iterator as $file) { if ($file->isDir()) { rmdir($file->getPathname()); } else { unlink($file->getPathname()); } } rmdir($dir); } /** * Cleanup failed restore */ private function cleanup_failed_restore() { // Update progress to indicate failure $this->update_restore_progress(0, __('Restore failed', 'tigerstyle-heat')); // Add to failed restores list $failed_restores = get_option('tigerstyle_failed_restores', array()); $failed_restores[] = array( 'restore_id' => $this->current_restore_id, 'timestamp' => time(), 'error' => 'Restore process failed' ); update_option('tigerstyle_failed_restores', array_slice($failed_restores, -10)); // Keep last 10 failures } /** * Rollback to previous state */ public function rollback_restore() { $rollback_backup_id = get_option('tigerstyle_last_rollback_backup'); if (empty($rollback_backup_id)) { throw new Exception(__('No rollback backup available', 'tigerstyle-heat')); } // Restore from rollback backup return $this->restore_backup(array( 'backup_id' => $rollback_backup_id, 'restore_files' => true, 'restore_database' => true, 'create_rollback' => false, // Don't create another rollback 'validate_before_restore' => false // Skip validation for rollback )); } }