tigerstyle-heat/includes/backup/class-backup-engine.php
Ryan Malloy 0028738e33 Initial commit: TigerStyle Heat v2.0.0
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.
2026-05-27 13:41:35 -06:00

500 lines
16 KiB
PHP

<?php
/**
* Core Backup Engine for TigerStyle SEO
* Handles filesystem and database backup operations with chunked processing
*/
// Prevent direct access
if (!defined('ABSPATH')) {
exit;
}
class TigerStyleSEO_Backup_Engine {
/**
* Single instance
*/
private static $instance = null;
/**
* Backup settings
*/
private $settings = array();
/**
* Logger instance
*/
private $logger = null;
/**
* Storage manager instance
*/
private $storage = null;
/**
* Compression manager instance
*/
private $compression = null;
/**
* Get instance
*/
public static function instance() {
if (is_null(self::$instance)) {
self::$instance = new self();
}
return self::$instance;
}
/**
* Constructor
*/
private function __construct() {
$this->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);
}
}