Make your WordPress site irresistible. Natural SEO attraction with: - robots.txt management - sitemap.xml generation - LLMs.txt support - Google integration (Analytics, Search Console, Tag Manager) - Schema.org structured data - Open Graph / Twitter Card meta tags - AMP support - Visual elements gallery - Built-in backup/restore module Includes build.sh and .distignore for WordPress-installable release ZIPs.
723 lines
23 KiB
PHP
723 lines
23 KiB
PHP
<?php
|
|
/**
|
|
* TigerStyle SEO Restore Engine
|
|
*
|
|
* Core restore processing engine with safety checks,
|
|
* validation, and rollback capabilities.
|
|
*
|
|
* @package TigerStyleSEO
|
|
* @subpackage RestoreEngine
|
|
*/
|
|
|
|
// Prevent direct access
|
|
if (!defined('ABSPATH')) {
|
|
exit;
|
|
}
|
|
|
|
class TigerStyleSEO_Restore_Engine {
|
|
|
|
/**
|
|
* Single instance
|
|
*/
|
|
private static $instance = null;
|
|
|
|
/**
|
|
* Get instance
|
|
*/
|
|
public static function instance() {
|
|
if (is_null(self::$instance)) {
|
|
self::$instance = new self();
|
|
}
|
|
return self::$instance;
|
|
}
|
|
|
|
/**
|
|
* Compression manager
|
|
*/
|
|
private $compression_manager;
|
|
|
|
/**
|
|
* Storage manager
|
|
*/
|
|
private $storage_manager;
|
|
|
|
/**
|
|
* Logger
|
|
*/
|
|
private $logger;
|
|
|
|
/**
|
|
* Validator
|
|
*/
|
|
private $validator;
|
|
|
|
/**
|
|
* Current restore ID
|
|
*/
|
|
private $current_restore_id;
|
|
|
|
/**
|
|
* Backup manifest
|
|
*/
|
|
private $backup_manifest;
|
|
|
|
/**
|
|
* Restore settings
|
|
*/
|
|
private $settings;
|
|
|
|
/**
|
|
* Constructor
|
|
*/
|
|
private function __construct() {
|
|
$this->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
|
|
));
|
|
}
|
|
} |