init(); } /** * Initialize the backup engine */ private function init() { $this->settings = $this->get_default_settings(); $this->logger = TigerStyleSEO_Backup_Logger::instance(); $this->storage = TigerStyleSEO_Storage_Manager::instance(); $this->compression = TigerStyleSEO_Compression_Manager::instance(); } /** * Get default backup settings */ private function get_default_settings() { return array( 'backup_location' => WP_CONTENT_DIR . '/tigerstyle-backups/', 'chunk_size' => 5242880, // 5MB chunks 'max_execution_time' => 300, // 5 minutes - reasonable limit for backup operations 'compression_method' => 'zip', 'exclude_patterns' => array( '*.log', 'cache/*', 'logs/*', 'node_modules/*', 'wp-content/tigerstyle-backups/*', '*.tmp', '.git/*', '.svn/*' ), 'include_uploads' => true, 'include_themes' => true, 'include_plugins' => true, 'include_wp_core' => false, 'database_batch_size' => 1000 ); } /** * Create a complete backup */ public function create_backup($options = array()) { $start_time = microtime(true); $backup_id = $this->generate_backup_id(); $this->logger->info("Starting backup creation", array('backup_id' => $backup_id)); try { // Set reasonable execution time limit to prevent runaway processes set_time_limit($this->settings['max_execution_time']); // Log the execution time limit being set $this->logger->info("Setting execution time limit", array( 'time_limit_seconds' => $this->settings['max_execution_time'] )); // Create backup directory $backup_dir = $this->create_backup_directory($backup_id); if (!$backup_dir) { throw new Exception('Failed to create backup directory'); } $manifest = array( 'backup_id' => $backup_id, 'created_at' => current_time('mysql'), 'wordpress_version' => get_bloginfo('version'), 'plugin_version' => TIGERSTYLE_HEAT_VERSION, 'site_url' => site_url(), 'files' => array(), 'database' => array(), 'compression' => $this->settings['compression_method'], 'checksum' => '', 'size' => 0 ); // Backup files if (!isset($options['skip_files']) || !$options['skip_files']) { $this->logger->info("Starting file backup", array('backup_id' => $backup_id)); $file_backup = $this->backup_files($backup_dir, $backup_id); $manifest['files'] = $file_backup; } // Backup database if (!isset($options['skip_database']) || !$options['skip_database']) { $this->logger->info("Starting database backup", array('backup_id' => $backup_id)); $db_backup = $this->backup_database($backup_dir, $backup_id); $manifest['database'] = $db_backup; } // Create manifest file $manifest_file = $backup_dir . '/manifest.json'; file_put_contents($manifest_file, json_encode($manifest, JSON_PRETTY_PRINT)); // Compress backup $this->logger->info("Compressing backup", array('backup_id' => $backup_id)); $compressed_file = $this->compression->compress_directory($backup_dir, $backup_id); if (!$compressed_file) { throw new Exception('Failed to compress backup'); } // Calculate final size and checksum $manifest['size'] = filesize($compressed_file); $manifest['checksum'] = md5_file($compressed_file); // Update manifest in compressed file file_put_contents($manifest_file, json_encode($manifest, JSON_PRETTY_PRINT)); // Store backup using storage manager $stored_backup = $this->storage->store_backup($compressed_file, $backup_id, $manifest); // Cleanup temporary files $this->cleanup_temp_directory($backup_dir); $duration = microtime(true) - $start_time; $this->logger->info("Backup completed successfully", array( 'backup_id' => $backup_id, 'duration' => round($duration, 2) . 's', 'size' => $this->format_bytes($manifest['size']) )); // Save backup record to database $this->save_backup_record($backup_id, $manifest, $stored_backup, $compressed_file); return array( 'success' => true, 'backup_id' => $backup_id, 'manifest' => $manifest, 'storage' => $stored_backup, 'duration' => $duration ); } catch (Exception $e) { $this->logger->error("Backup failed", array( 'backup_id' => $backup_id, 'error' => $e->getMessage(), 'trace' => $e->getTraceAsString() )); // Cleanup on failure if (isset($backup_dir)) { $this->cleanup_temp_directory($backup_dir); } return array( 'success' => false, 'error' => $e->getMessage(), 'backup_id' => $backup_id ); } } /** * Backup files with chunked processing */ private function backup_files($backup_dir, $backup_id) { $files_dir = $backup_dir . '/files/'; wp_mkdir_p($files_dir); $file_list = array(); $total_size = 0; $file_count = 0; // Define directories to backup $backup_paths = array(); if ($this->settings['include_uploads']) { $backup_paths[] = WP_CONTENT_DIR . '/uploads/'; } if ($this->settings['include_themes']) { $backup_paths[] = WP_CONTENT_DIR . '/themes/'; } if ($this->settings['include_plugins']) { $backup_paths[] = WP_CONTENT_DIR . '/plugins/'; } if ($this->settings['include_wp_core']) { $backup_paths[] = ABSPATH; } foreach ($backup_paths as $path) { if (!is_dir($path)) { continue; } $this->logger->debug("Backing up directory", array('path' => $path)); $iterator = new RecursiveIteratorIterator( new RecursiveDirectoryIterator($path, RecursiveDirectoryIterator::SKIP_DOTS), RecursiveIteratorIterator::SELF_FIRST ); foreach ($iterator as $file) { if ($file->isDir()) { continue; } $filepath = $file->getPathname(); $relative_path = str_replace(ABSPATH, '', $filepath); // Check exclusion patterns if ($this->should_exclude_file($relative_path)) { continue; } // Copy file to backup directory $backup_filepath = $files_dir . $relative_path; $backup_file_dir = dirname($backup_filepath); if (!is_dir($backup_file_dir)) { wp_mkdir_p($backup_file_dir); } if (copy($filepath, $backup_filepath)) { $file_size = filesize($filepath); $file_list[] = array( 'path' => $relative_path, 'size' => $file_size, 'modified' => filemtime($filepath), 'checksum' => md5_file($filepath) ); $total_size += $file_size; $file_count++; // Update progress every 100 files if ($file_count % 100 === 0) { $this->update_backup_progress($backup_id, 'files', $file_count); } } else { $this->logger->warning("Failed to copy file", array('file' => $filepath)); } } } return array( 'file_count' => $file_count, 'total_size' => $total_size, 'files' => $file_list ); } /** * Backup database with batch processing */ private function backup_database($backup_dir, $backup_id) { global $wpdb; $db_dir = $backup_dir . '/database/'; wp_mkdir_p($db_dir); $sql_file = $db_dir . 'database.sql'; $tables_info = array(); // Get all WordPress tables $tables = $wpdb->get_col("SHOW TABLES LIKE '{$wpdb->prefix}%'"); $sql_content = "-- TigerStyle SEO Database Backup\n"; $sql_content .= "-- Created: " . current_time('mysql') . "\n"; $sql_content .= "-- WordPress Version: " . get_bloginfo('version') . "\n\n"; $sql_content .= "SET FOREIGN_KEY_CHECKS = 0;\n"; $sql_content .= "SET SQL_MODE = 'NO_AUTO_VALUE_ON_ZERO';\n\n"; foreach ($tables as $table) { $this->logger->debug("Backing up table", array('table' => $table)); // Get table structure $create_table = $wpdb->get_row("SHOW CREATE TABLE `{$table}`", ARRAY_N); $sql_content .= "\n-- Table structure for `{$table}`\n"; $sql_content .= "DROP TABLE IF EXISTS `{$table}`;\n"; $sql_content .= $create_table[1] . ";\n\n"; // Get table data in batches $row_count = $wpdb->get_var("SELECT COUNT(*) FROM `{$table}`"); $offset = 0; $batch_size = $this->settings['database_batch_size']; $sql_content .= "-- Data for table `{$table}`\n"; while ($offset < $row_count) { $rows = $wpdb->get_results("SELECT * FROM `{$table}` LIMIT {$offset}, {$batch_size}", ARRAY_A); if (empty($rows)) { break; } foreach ($rows as $row) { $values = array(); foreach ($row as $value) { $values[] = $wpdb->prepare('%s', $value); } $sql_content .= "INSERT INTO `{$table}` VALUES (" . implode(', ', $values) . ");\n"; } $offset += $batch_size; // Update progress $progress = min(100, ($offset / $row_count) * 100); $this->update_backup_progress($backup_id, 'database', $progress, $table); } $tables_info[] = array( 'name' => $table, 'rows' => $row_count, 'size' => $wpdb->get_var("SELECT ROUND(((data_length + index_length) / 1024 / 1024), 2) AS 'DB Size in MB' FROM information_schema.tables WHERE table_schema='{$wpdb->dbname}' AND table_name='{$table}'") ); } $sql_content .= "\nSET FOREIGN_KEY_CHECKS = 1;\n"; // Write SQL file file_put_contents($sql_file, $sql_content); return array( 'table_count' => count($tables), 'total_rows' => array_sum(array_column($tables_info, 'rows')), 'sql_file' => 'database/database.sql', 'sql_size' => filesize($sql_file), 'tables' => $tables_info ); } /** * Check if file should be excluded */ private function should_exclude_file($filepath) { foreach ($this->settings['exclude_patterns'] as $pattern) { if (fnmatch($pattern, $filepath)) { return true; } } return false; } /** * Generate unique backup ID */ private function generate_backup_id() { return 'backup_' . date('Y-m-d_H-i-s') . '_' . wp_generate_password(8, false); } /** * Create backup directory */ private function create_backup_directory($backup_id) { $backup_dir = $this->settings['backup_location'] . $backup_id . '/'; if (wp_mkdir_p($backup_dir)) { // Create .htaccess for security $htaccess_content = "Order deny,allow\nDeny from all\n"; file_put_contents($backup_dir . '.htaccess', $htaccess_content); return $backup_dir; } return false; } /** * Cleanup temporary directory */ private function cleanup_temp_directory($directory) { if (!is_dir($directory)) { return; } $iterator = new RecursiveIteratorIterator( new RecursiveDirectoryIterator($directory, RecursiveDirectoryIterator::SKIP_DOTS), RecursiveIteratorIterator::CHILD_FIRST ); foreach ($iterator as $file) { if ($file->isDir()) { rmdir($file->getPathname()); } else { unlink($file->getPathname()); } } rmdir($directory); } /** * Update backup progress */ private function update_backup_progress($backup_id, $stage, $progress, $details = '') { update_option('tigerstyle_backup_progress_' . $backup_id, array( 'stage' => $stage, 'progress' => $progress, 'details' => $details, 'updated' => time() )); } /** * Save backup record to database */ private function save_backup_record($backup_id, $manifest, $storage_info, $compressed_file) { global $wpdb; $table_name = $wpdb->prefix . 'tigerstyle_backup_metadata'; $wpdb->insert( $table_name, array( 'backup_id' => $backup_id, 'storage_type' => $storage_info['type'] ?? 'local', 'file_path' => $storage_info['path'] ?? $compressed_file, 'file_size' => $manifest['size'], 'created_at' => current_time('mysql'), 'metadata_json' => json_encode($manifest) ), array('%s', '%s', '%s', '%d', '%s', '%s') ); } /** * Format bytes for display */ private function format_bytes($bytes, $precision = 2) { $units = array('B', 'KB', 'MB', 'GB', 'TB'); for ($i = 0; $bytes > 1024 && $i < count($units) - 1; $i++) { $bytes /= 1024; } return round($bytes, $precision) . ' ' . $units[$i]; } /** * Get backup progress */ public function get_backup_progress($backup_id) { return get_option('tigerstyle_backup_progress_' . $backup_id, array()); } /** * Cleanup backup progress */ public function cleanup_backup_progress($backup_id) { delete_option('tigerstyle_backup_progress_' . $backup_id); } }