try_load_dependencies();
$this->init_hooks();
}
/**
* Try to load dependencies gracefully
*/
private function try_load_dependencies() {
$includes_path = TIGERSTYLE_LIFE9_COMPLETE_PATH . 'includes/';
// Check if includes directory exists
if (!is_dir($includes_path)) {
return false;
}
try {
// Try to load core classes
$required_files = [
'class-storage-manager.php'
];
foreach ($required_files as $file) {
$file_path = $includes_path . $file;
if (file_exists($file_path)) {
require_once $file_path;
}
}
// If we get here, we have at least some functionality
$this->full_functionality = true;
} catch (Exception $e) {
// Log error but continue with basic functionality
error_log('TigerStyle Life9: ' . $e->getMessage());
$this->full_functionality = false;
}
}
/**
* Prevent cloning
*/
private function __clone() {}
/**
* Prevent unserialization
*/
public function __wakeup() {}
/**
* Initialize WordPress hooks
*/
private function init_hooks() {
// Admin hooks
if (is_admin()) {
add_action('admin_menu', [$this, 'add_admin_menu']);
add_action('admin_enqueue_scripts', [$this, 'enqueue_admin_scripts']);
add_action('admin_init', [$this, 'handle_early_admin_requests']);
}
// Global hooks (front-end and admin)
add_action('init', [$this, 'handle_early_admin_requests']); // Handle time-limited downloads on front-end too
// Load text domain for translations
add_action('init', [$this, 'load_textdomain']);
// Register scheduled backup hook
add_action('tigerstyle_life9_scheduled_backup', [$this, 'execute_scheduled_backup']);
// Register custom cron intervals
add_filter('cron_schedules', [$this, 'add_custom_cron_intervals']);
// Register activation hook for database setup
register_activation_hook(TIGERSTYLE_LIFE9_COMPLETE_FILE, [$this, 'activate_plugin']);
// Register AJAX handlers
add_action('wp_ajax_generate_download_link', [$this, 'handle_generate_download_link']);
add_action('wp_ajax_cleanup_expired_tokens', [$this, 'cleanup_expired_tokens']);
add_action('wp_ajax_create_table_manual', [$this, 'handle_manual_table_creation']);
}
/**
* Load plugin text domain for translations
*/
public function load_textdomain() {
load_plugin_textdomain(
'tigerstyle-life9',
false,
dirname(plugin_basename(__FILE__)) . '/languages'
);
}
/**
* Handle early admin requests (like downloads) before HTML output
*/
public function handle_early_admin_requests() {
// Handle time-limited download URLs (global access, no page restriction)
if (isset($_GET['tigerstyle_dl']) && !empty($_GET['tigerstyle_dl'])) {
$this->handle_time_limited_download();
return;
}
// Only handle regular requests for our backup page
if (!isset($_GET['page']) || $_GET['page'] !== 'tigerstyle-life9-complete-backup') {
return;
}
// Handle regular download requests
if (isset($_GET['download']) && !empty($_GET['download'])) {
error_log('TigerStyle Life9: Early download handler called for: ' . $_GET['download']);
$this->handle_backup_download();
}
// Handle AJAX requests for generating time-limited URLs
if (isset($_POST['action']) && $_POST['action'] === 'generate_download_link') {
$this->handle_generate_download_link();
}
}
/**
* Add admin menu with cat-themed items
*/
public function add_admin_menu() {
// Main menu page
add_menu_page(
'πΎ Life Tracker Dashboard', // Page title
'TigerStyle Life9', // Menu title
'manage_options', // Capability
'tigerstyle-life9-complete', // Menu slug
[$this, 'render_dashboard_page'], // Callback
'dashicons-backup', // Icon
32 // Position
);
// Submenu pages with cat themes
add_submenu_page(
'tigerstyle-life9-complete',
'πΎ Save a Life',
'πΎ Save a Life',
'manage_options',
'tigerstyle-life9-complete-backup',
[$this, 'render_backup_page']
);
add_submenu_page(
'tigerstyle-life9-complete',
'π Restore a Life',
'π Restore a Life',
'manage_options',
'tigerstyle-life9-complete-restore',
[$this, 'render_restore_page']
);
add_submenu_page(
'tigerstyle-life9-complete',
'βοΈ Territory Settings',
'βοΈ Territory Settings',
'manage_options',
'tigerstyle-life9-complete-settings',
[$this, 'render_settings_page']
);
}
/**
* Enqueue admin scripts and styles
*/
public function enqueue_admin_scripts($hook) {
// Only load on our plugin pages
if (strpos($hook, 'tigerstyle-life9-complete') === false) {
return;
}
// Add inline styles
wp_add_inline_style('admin-menu', '
.tigerstyle-life9-complete .form-table th {
padding-left: 2em;
}
.tigerstyle-cat-message {
background: #fff3cd;
border-left: 4px solid #ffc107;
padding: 12px;
margin: 16px 0;
}
.tigerstyle-cat-success {
background: #d1edff;
border-left: 4px solid #0073aa;
padding: 12px;
margin: 16px 0;
}
.tigerstyle-cat-error {
background: #ffeaea;
border-left: 4px solid #dc3232;
padding: 12px;
margin: 16px 0;
}
.tigerstyle-icon {
font-size: 1.2em;
margin-right: 0.5em;
}
');
}
/**
* Render dashboard page
*/
public function render_dashboard_page() {
?>
πΎ
π± Welcome to your backup territory!
full_functionality): ?>
πΏ Some components couldn't load:
handle_backup_creation();
}
if (isset($_POST['upload_backup'])) {
$this->handle_backup_upload();
}
// Get existing backups
$backups = $this->get_existing_backups();
?>
πΎ
π§
get_infrastructure_status();
$all_good = array_reduce($status_checks, function($carry, $item) {
return $carry && $item['status'];
}, true);
?>
π± Share your backup securely! Generate a time-limited download link with YouTube-style short ID.
π Link Generated Successfully!
Token:
Expires:
Max Downloads:
handle_backup_restore();
}
// Get available backups
$backups = $this->get_existing_backups();
$selected_backup = isset($_GET['backup']) ? sanitize_text_field($_GET['backup']) : '';
?>
π
π± Nine lives, infinite possibilities!
πΏ
πΈ No backups found!
πΎ
π§
π± How Restore Works:
- π Validation:
- πΎ Safety Backup:
- π File Extraction:
- ποΈ Database Restore:
- π File Replacement:
- π Cleanup:
- β
Verification:
handle_schedule_settings_save();
}
if (isset($_POST['save_storage_settings'])) {
$this->handle_storage_settings_save();
}
// Get current settings
$schedule_settings = get_option('tigerstyle_life9_schedule_settings', $this->get_default_schedule_settings());
$storage_settings = get_option('tigerstyle_life9_storage_settings', $this->get_default_storage_settings());
$next_backup = $this->get_next_scheduled_backup();
?>
βοΈ
π± Configure your automated backup territory!
isset($_POST['schedule_enabled']),
'frequency' => sanitize_text_field($_POST['schedule_frequency'] ?? 'daily'),
'hour' => intval($_POST['schedule_hour'] ?? 2),
'day_of_week' => intval($_POST['schedule_day_of_week'] ?? 0),
'day_of_month' => intval($_POST['schedule_day_of_month'] ?? 1),
'include_files' => isset($_POST['schedule_include_files']),
'include_database' => isset($_POST['schedule_include_database']),
'retention_count' => intval($_POST['retention_count'] ?? 10)
];
// Validate settings
if (!in_array($settings['frequency'], ['daily', 'weekly', 'monthly'])) {
throw new Exception(__('Invalid backup frequency selected.', 'tigerstyle-life9'));
}
if ($settings['hour'] < 0 || $settings['hour'] > 23) {
throw new Exception(__('Invalid backup hour selected.', 'tigerstyle-life9'));
}
if (!$settings['include_files'] && !$settings['include_database']) {
throw new Exception(__('Please select at least files or database for automated backups.', 'tigerstyle-life9'));
}
// Save settings
update_option('tigerstyle_life9_schedule_settings', $settings);
// Update wp-cron schedule
$this->update_backup_schedule($settings);
add_action('admin_notices', function() {
echo '';
echo '
π ' . __('Schedule settings saved successfully!', 'tigerstyle-life9') . '
';
echo '
';
});
} catch (Exception $e) {
add_action('admin_notices', function() use ($e) {
echo '';
echo '
πΏ ' . sprintf(__('Settings save failed: %s', 'tigerstyle-life9'), $e->getMessage()) . '
';
echo '
';
});
}
}
/**
* Handle storage settings save
*/
private function handle_storage_settings_save() {
// Verify nonce for security
if (!isset($_POST['storage_nonce']) || !wp_verify_nonce($_POST['storage_nonce'], 'tigerstyle_storage_settings')) {
wp_die(__('Security check failed. Please try again.', 'tigerstyle-life9'));
}
// Check user permissions
if (!current_user_can('manage_options')) {
wp_die(__('You do not have permission to manage storage settings.', 'tigerstyle-life9'));
}
try {
$settings = [
's3_enabled' => isset($_POST['s3_enabled']),
's3_endpoint' => sanitize_url($_POST['s3_endpoint'] ?? ''),
's3_bucket' => sanitize_text_field($_POST['s3_bucket'] ?? ''),
's3_access_key' => sanitize_text_field($_POST['s3_access_key'] ?? ''),
's3_secret_key' => sanitize_text_field($_POST['s3_secret_key'] ?? ''),
's3_region' => sanitize_text_field($_POST['s3_region'] ?? 'us-east-1')
];
// Validate S3 settings if enabled
if ($settings['s3_enabled']) {
if (empty($settings['s3_bucket'])) {
throw new Exception(__('S3 bucket name is required when cloud storage is enabled.', 'tigerstyle-life9'));
}
if (empty($settings['s3_access_key']) || empty($settings['s3_secret_key'])) {
throw new Exception(__('S3 access credentials are required when cloud storage is enabled.', 'tigerstyle-life9'));
}
}
// Save settings
update_option('tigerstyle_life9_storage_settings', $settings);
add_action('admin_notices', function() {
echo '';
echo '
π ' . __('Storage settings saved successfully!', 'tigerstyle-life9') . '
';
echo '
';
});
} catch (Exception $e) {
add_action('admin_notices', function() use ($e) {
echo '';
echo '
πΏ ' . sprintf(__('Storage settings save failed: %s', 'tigerstyle-life9'), $e->getMessage()) . '
';
echo '
';
});
}
}
/**
* Get default schedule settings
*/
private function get_default_schedule_settings() {
return [
'enabled' => false,
'frequency' => 'daily',
'hour' => 2,
'day_of_week' => 0, // Sunday
'day_of_month' => 1,
'include_files' => true,
'include_database' => true,
'retention_count' => 10
];
}
/**
* Get default storage settings
*/
private function get_default_storage_settings() {
return [
's3_enabled' => false,
's3_endpoint' => '',
's3_bucket' => '',
's3_access_key' => '',
's3_secret_key' => '',
's3_region' => 'us-east-1'
];
}
/**
* Update backup schedule in wp-cron
*/
private function update_backup_schedule($settings) {
$hook = 'tigerstyle_life9_scheduled_backup';
// Clear existing schedule
wp_clear_scheduled_hook($hook);
if (!$settings['enabled']) {
return; // Just clear the schedule if disabled
}
// Calculate next run time
$next_run = $this->calculate_next_backup_time($settings);
// Register custom intervals if needed
add_filter('cron_schedules', [$this, 'add_custom_cron_intervals']);
// Schedule the backup
wp_schedule_event($next_run, $settings['frequency'], $hook, [$settings]);
// Log scheduling
error_log("TigerStyle Life9: Scheduled backup for " . date('Y-m-d H:i:s', $next_run) . " ({$settings['frequency']})");
}
/**
* Add custom cron intervals
*/
public function add_custom_cron_intervals($schedules) {
// WordPress already has 'daily', but we might want custom intervals
if (!isset($schedules['weekly'])) {
$schedules['weekly'] = [
'interval' => 7 * 24 * 60 * 60, // 1 week in seconds
'display' => __('Weekly', 'tigerstyle-life9')
];
}
if (!isset($schedules['monthly'])) {
$schedules['monthly'] = [
'interval' => 30 * 24 * 60 * 60, // 30 days in seconds
'display' => __('Monthly', 'tigerstyle-life9')
];
}
return $schedules;
}
/**
* Calculate next backup time based on settings
*/
private function calculate_next_backup_time($settings) {
$now = current_time('timestamp');
$hour = $settings['hour'];
switch ($settings['frequency']) {
case 'daily':
// Schedule for today at the specified hour, or tomorrow if past that time
$today_time = strtotime("today {$hour}:00", $now);
return ($today_time > $now) ? $today_time : strtotime("tomorrow {$hour}:00", $now);
case 'weekly':
$day_of_week = $settings['day_of_week'];
$target_time = strtotime("next " . $this->get_day_name($day_of_week) . " {$hour}:00", $now);
// If it's the same day but past the time, schedule for next week
if (date('w', $now) == $day_of_week) {
$today_time = strtotime("today {$hour}:00", $now);
if ($today_time > $now) {
return $today_time;
}
}
return $target_time;
case 'monthly':
$day_of_month = $settings['day_of_month'];
$current_month = date('Y-m', $now);
$target_time = strtotime("{$current_month}-{$day_of_month} {$hour}:00");
// If past this month's date, schedule for next month
if ($target_time <= $now) {
$next_month = date('Y-m', strtotime('+1 month', $now));
$target_time = strtotime("{$next_month}-{$day_of_month} {$hour}:00");
}
return $target_time;
default:
return strtotime('+1 hour', $now);
}
}
/**
* Get day name from day number
*/
private function get_day_name($day_number) {
$days = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
return $days[$day_number] ?? 'Sunday';
}
/**
* Get next scheduled backup time
*/
private function get_next_scheduled_backup() {
$hook = 'tigerstyle_life9_scheduled_backup';
$next_scheduled = wp_next_scheduled($hook);
if ($next_scheduled) {
return date('F j, Y \a\t g:i A', $next_scheduled);
}
return false;
}
/**
* Execute scheduled backup
*/
public function execute_scheduled_backup($settings) {
try {
error_log("TigerStyle Life9: Executing scheduled backup");
// Create backup configuration
$backup_config = [
'backup_name' => 'Scheduled Backup ' . date('Y-m-d H:i'),
'backup_type' => 'scheduled',
'include_files' => $settings['include_files'],
'include_database' => $settings['include_database'],
'storage_type' => 'local' // TODO: Add S3 support for scheduled backups
];
// Create backup using existing engine
$result = $this->handle_backup_creation_programmatic($backup_config);
if ($result) {
error_log("TigerStyle Life9: Scheduled backup completed successfully");
// Clean up old backups based on retention policy
$this->cleanup_old_backups($settings['retention_count']);
} else {
error_log("TigerStyle Life9: Scheduled backup failed");
}
} catch (Exception $e) {
error_log("TigerStyle Life9: Scheduled backup error - " . $e->getMessage());
}
}
/**
* Cleanup old backups based on retention policy
*/
private function cleanup_old_backups($retention_count) {
if ($retention_count <= 0) {
return; // Unlimited retention
}
try {
$backups = get_option('tigerstyle_life9_backups', []);
// Filter for scheduled backups only
$scheduled_backups = array_filter($backups, function($backup) {
return isset($backup['backup_type']) && $backup['backup_type'] === 'scheduled';
});
// Sort by creation date (newest first)
usort($scheduled_backups, function($a, $b) {
return strtotime($b['created']) - strtotime($a['created']);
});
// Remove excess backups
if (count($scheduled_backups) > $retention_count) {
$backups_to_remove = array_slice($scheduled_backups, $retention_count);
foreach ($backups_to_remove as $backup) {
// Delete backup file
if (isset($backup['storage_path']) && file_exists($backup['storage_path'])) {
unlink($backup['storage_path']);
}
// Remove from metadata
$backups = array_filter($backups, function($b) use ($backup) {
return $b['filename'] !== $backup['filename'];
});
}
// Update metadata
update_option('tigerstyle_life9_backups', array_values($backups));
error_log("TigerStyle Life9: Cleaned up " . count($backups_to_remove) . " old backups");
}
} catch (Exception $e) {
error_log("TigerStyle Life9: Backup cleanup error - " . $e->getMessage());
}
}
/**
* Handle backup creation programmatically (for scheduled backups)
*/
private function handle_backup_creation_programmatic($config) {
try {
$backup_name = $config['backup_name'];
$include_files = $config['include_files'];
$include_database = $config['include_database'];
$storage_type = $config['storage_type'] ?? 'local';
// Validate inputs
if (empty($backup_name)) {
throw new Exception(__('Backup name is required.', 'tigerstyle-life9'));
}
if (!$include_files && !$include_database) {
throw new Exception(__('Please select at least files or database to backup.', 'tigerstyle-life9'));
}
// Generate backup filename with timestamp
$timestamp = date('Y-m-d_H-i-s');
$backup_filename = sanitize_file_name($backup_name . '_' . $timestamp . '.zip');
// Create backup directory if it doesn't exist
$backup_dir = WP_CONTENT_DIR . '/backups';
if (!file_exists($backup_dir)) {
wp_mkdir_p($backup_dir);
}
$backup_path = $backup_dir . '/' . $backup_filename;
// Initialize backup log
$backup_log = [
'name' => $backup_name,
'filename' => $backup_filename,
'created' => current_time('mysql'),
'size' => 0,
'includes' => [],
'storage' => $storage_type,
'status' => 'creating',
'backup_type' => $config['backup_type'] ?? 'manual'
];
// Create ZIP archive using WordPress's PclZip library (more reliable)
require_once(ABSPATH . 'wp-admin/includes/class-pclzip.php');
$zip = new PclZip($backup_path);
$files_to_add = [];
// Include files
if ($include_files) {
$files_to_add = array_merge($files_to_add, $this->get_files_for_backup());
$backup_log['includes'][] = 'files';
}
// Include database
if ($include_database) {
$db_file = $this->create_database_backup();
if ($db_file) {
$files_to_add[] = $db_file;
$backup_log['includes'][] = 'database';
}
}
// Create the archive
if (empty($files_to_add)) {
throw new Exception(__('No files to backup.', 'tigerstyle-life9'));
}
$result = $zip->create($files_to_add, PCLZIP_OPT_REMOVE_PATH, ABSPATH);
if ($result == 0) {
throw new Exception(__('Failed to create backup archive: ', 'tigerstyle-life9') . $zip->errorInfo(true));
}
// Get final backup size
$backup_log['size'] = filesize($backup_path);
$backup_log['status'] = 'completed';
$backup_log['storage_path'] = $backup_path;
// Save backup metadata
$this->save_backup_metadata($backup_log);
return true;
} catch (Exception $e) {
error_log("TigerStyle Life9: Programmatic backup failed - " . $e->getMessage());
return false;
}
}
/**
* Handle backup creation
*/
private function handle_backup_creation() {
// Verify nonce for security
if (!isset($_POST['backup_nonce']) || !wp_verify_nonce($_POST['backup_nonce'], 'tigerstyle_backup_create')) {
wp_die(__('Security check failed. Please try again.', 'tigerstyle-life9'));
}
// Check user permissions
if (!current_user_can('manage_options')) {
wp_die(__('You do not have permission to create backups.', 'tigerstyle-life9'));
}
try {
$backup_name = sanitize_text_field($_POST['backup_name']);
$include_files = isset($_POST['include_files']);
$include_database = isset($_POST['include_database']);
$storage_type = sanitize_text_field($_POST['storage_type']);
// Validate inputs
if (empty($backup_name)) {
throw new Exception(__('Backup name is required.', 'tigerstyle-life9'));
}
if (!$include_files && !$include_database) {
throw new Exception(__('Please select at least files or database to backup.', 'tigerstyle-life9'));
}
// Generate backup filename with timestamp
$timestamp = date('Y-m-d_H-i-s');
$backup_filename = sanitize_file_name($backup_name . '_' . $timestamp . '.zip');
// Create backup directory if it doesn't exist
$backup_dir = WP_CONTENT_DIR . '/backups';
if (!file_exists($backup_dir)) {
wp_mkdir_p($backup_dir);
}
$backup_path = $backup_dir . '/' . $backup_filename;
// Initialize backup log
$backup_log = [
'name' => $backup_name,
'filename' => $backup_filename,
'created' => current_time('mysql'),
'size' => 0,
'includes' => [],
'storage' => $storage_type,
'status' => 'creating'
];
// Create ZIP archive using WordPress's PclZip library (more reliable)
require_once(ABSPATH . 'wp-admin/includes/class-pclzip.php');
$zip = new PclZip($backup_path);
$files_to_add = [];
// Include files
if ($include_files) {
$files_to_add = array_merge($files_to_add, $this->get_files_for_backup());
$backup_log['includes'][] = 'files';
}
// Include database
if ($include_database) {
$db_file = $this->create_database_backup();
if ($db_file) {
$files_to_add[] = $db_file;
$backup_log['includes'][] = 'database';
}
}
// Create the archive
if (empty($files_to_add)) {
throw new Exception(__('No files to backup.', 'tigerstyle-life9'));
}
$result = $zip->create($files_to_add, PCLZIP_OPT_REMOVE_PATH, ABSPATH);
if ($result == 0) {
throw new Exception(__('Failed to create backup archive: ', 'tigerstyle-life9') . $zip->errorInfo(true));
}
// Get final backup size
$backup_log['size'] = filesize($backup_path);
$backup_log['status'] = 'completed';
// Handle S3/MinIO storage
if ($storage_type === 's3') {
$this->upload_to_s3($backup_path, $backup_filename);
$backup_log['storage_path'] = 's3://' . $backup_filename;
} else {
$backup_log['storage_path'] = $backup_path;
}
// Save backup metadata
$this->save_backup_metadata($backup_log);
add_action('admin_notices', function() use ($backup_filename) {
echo '';
echo '
π ' . sprintf(__('Backup "%s" created successfully!', 'tigerstyle-life9'), $backup_filename) . '
';
echo '
';
});
} catch (Exception $e) {
add_action('admin_notices', function() use ($e) {
echo '';
echo '
πΏ ' . sprintf(__('Backup failed: %s', 'tigerstyle-life9'), $e->getMessage()) . '
';
echo '
';
});
}
}
/**
* Handle backup file upload
*/
private function handle_backup_upload() {
// Verify nonce for security
if (!isset($_POST['upload_nonce']) || !wp_verify_nonce($_POST['upload_nonce'], 'tigerstyle_backup_upload')) {
wp_die(__('Security check failed. Please try again.', 'tigerstyle-life9'));
}
// Check user permissions
if (!current_user_can('manage_options')) {
wp_die(__('You do not have permission to upload backups.', 'tigerstyle-life9'));
}
try {
// Check if file was uploaded
if (!isset($_FILES['backup_file']) || $_FILES['backup_file']['error'] !== UPLOAD_ERR_OK) {
throw new Exception(__('No file uploaded or upload error occurred.', 'tigerstyle-life9'));
}
$file = $_FILES['backup_file'];
// Validate file size (512MB max)
$max_size = 512 * 1024 * 1024; // 512MB in bytes
if ($file['size'] > $max_size) {
throw new Exception(__('File is too large. Maximum size is 512MB.', 'tigerstyle-life9'));
}
// Validate file type
$allowed_types = ['zip', 'tar', 'gz', 'xml', 'sql'];
$file_ext = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));
if (!in_array($file_ext, $allowed_types)) {
throw new Exception(__('Invalid file type. Allowed: .zip, .tar, .gz, .xml, .sql', 'tigerstyle-life9'));
}
// Create backup directory if it doesn't exist
$backup_dir = WP_CONTENT_DIR . '/backups';
if (!file_exists($backup_dir)) {
wp_mkdir_p($backup_dir);
}
// Generate safe filename
$filename = sanitize_file_name($file['name']);
$timestamp = date('Y-m-d_H-i-s');
$safe_filename = $timestamp . '_uploaded_' . $filename;
$destination = $backup_dir . '/' . $safe_filename;
// Move uploaded file
if (!move_uploaded_file($file['tmp_name'], $destination)) {
throw new Exception(__('Failed to save uploaded file.', 'tigerstyle-life9'));
}
// Validate backup file
$validation_result = $this->validate_backup_file($destination, $file_ext);
// Save backup metadata
$backup_log = [
'name' => pathinfo($filename, PATHINFO_FILENAME),
'filename' => $safe_filename,
'created' => current_time('mysql'),
'size' => filesize($destination),
'includes' => $validation_result['includes'],
'storage' => 'local',
'storage_path' => $destination,
'status' => 'uploaded',
'original_name' => $filename
];
$this->save_backup_metadata($backup_log);
add_action('admin_notices', function() use ($filename) {
echo '';
echo '
π₯ ' . sprintf(__('Backup file "%s" uploaded successfully!', 'tigerstyle-life9'), $filename) . '
';
echo '
';
});
} catch (Exception $e) {
add_action('admin_notices', function() use ($e) {
echo '';
echo '
πΏ ' . sprintf(__('Upload failed: %s', 'tigerstyle-life9'), $e->getMessage()) . '
';
echo '
';
});
}
}
/**
* Handle backup download
*/
private function handle_backup_download() {
// Verify nonce for security
if (!isset($_GET['_wpnonce']) || !wp_verify_nonce($_GET['_wpnonce'], 'download_backup')) {
wp_die(__('Security check failed. Please try again.', 'tigerstyle-life9'));
}
// Check user permissions
if (!current_user_can('manage_options')) {
wp_die(__('You do not have permission to download backups.', 'tigerstyle-life9'));
}
try {
$backup_filename = sanitize_file_name($_GET['download']);
// Get backup metadata to determine storage type
$backups = get_option('tigerstyle_life9_backups', []);
$backup_metadata = null;
foreach ($backups as $backup) {
if ($backup['filename'] === $backup_filename) {
$backup_metadata = $backup;
break;
}
}
if (!$backup_metadata) {
wp_die(__('Backup file not found in metadata.', 'tigerstyle-life9'));
}
// Handle download based on storage type
if ($backup_metadata['storage'] === 's3') {
$this->download_from_s3($backup_filename, $backup_metadata);
} else {
$this->download_from_local($backup_filename, $backup_metadata);
}
} catch (Exception $e) {
wp_die(__('Download failed: ', 'tigerstyle-life9') . $e->getMessage());
}
}
/**
* Download backup from local storage
*/
private function download_from_local($filename, $metadata) {
$backup_dir = WP_CONTENT_DIR . '/backups';
$file_path = $backup_dir . '/' . $filename;
if (!file_exists($file_path)) {
throw new Exception(__('Backup file not found on local storage.', 'tigerstyle-life9'));
}
// Serve the file
$this->serve_file_download($file_path, $filename);
}
/**
* Download backup from S3/MinIO storage
*/
private function download_from_s3($filename, $metadata) {
// Get S3/MinIO configuration
$s3_settings = get_option('tigerstyle_life9_storage_settings', []);
if (empty($s3_settings['s3_enabled']) ||
empty($s3_settings['s3_endpoint']) ||
empty($s3_settings['s3_bucket']) ||
empty($s3_settings['s3_access_key']) ||
empty($s3_settings['s3_secret_key'])) {
throw new Exception(__('S3/MinIO configuration is incomplete.', 'tigerstyle-life9'));
}
try {
// Initialize S3 client
require_once(ABSPATH . 'wp-admin/includes/class-wp-filesystem.php');
// Create temporary file for download
$temp_dir = get_temp_dir();
$temp_file = $temp_dir . '/' . $filename;
// Download from MinIO using curl
$url = rtrim($s3_settings['s3_endpoint'], '/') . '/' . $s3_settings['s3_bucket'] . '/' . $filename;
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
curl_setopt($ch, CURLOPT_USERPWD, $s3_settings['s3_access_key'] . ':' . $s3_settings['s3_secret_key']);
curl_setopt($ch, CURLOPT_HTTPAUTH, CURLAUTH_BASIC);
$file_content = curl_exec($ch);
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($http_code !== 200 || $file_content === false) {
throw new Exception(__('Failed to download from S3/MinIO storage. HTTP Code: ', 'tigerstyle-life9') . $http_code);
}
// Write to temporary file
if (file_put_contents($temp_file, $file_content) === false) {
throw new Exception(__('Failed to write temporary file.', 'tigerstyle-life9'));
}
// Serve the file and clean up
$this->serve_file_download($temp_file, $filename, true);
} catch (Exception $e) {
// Clean up temp file if it exists
if (isset($temp_file) && file_exists($temp_file)) {
unlink($temp_file);
}
throw $e;
}
}
/**
* Serve file for download
*/
private function serve_file_download($file_path, $filename, $delete_after = false) {
if (!file_exists($file_path)) {
throw new Exception(__('File not found.', 'tigerstyle-life9'));
}
// Set headers for file download
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename="' . $filename . '"');
header('Content-Length: ' . filesize($file_path));
header('Cache-Control: no-cache, must-revalidate');
header('Expires: 0');
// Output file content
readfile($file_path);
// Clean up temporary file if requested
if ($delete_after) {
unlink($file_path);
}
exit; // Important: stop execution after file download
}
/**
* Handle backup restoration
*/
private function handle_backup_restore() {
error_log("TigerStyle Life9: handle_backup_restore() called");
error_log("TigerStyle Life9: POST data: " . print_r($_POST, true));
// Verify nonce for security
if (!isset($_POST['restore_nonce']) || !wp_verify_nonce($_POST['restore_nonce'], 'tigerstyle_restore_backup')) {
error_log("TigerStyle Life9: Nonce verification failed");
wp_die(__('Security check failed. Please try again.', 'tigerstyle-life9'));
}
// Check user permissions
if (!current_user_can('manage_options')) {
wp_die(__('You do not have permission to restore backups.', 'tigerstyle-life9'));
}
try {
$backup_id = sanitize_text_field($_POST['backup_id']);
$restore_files = isset($_POST['restore_files']);
$restore_database = isset($_POST['restore_database']);
$create_backup_before = isset($_POST['create_backup_before_restore']);
$validate_backup = isset($_POST['validate_backup']);
// Validate inputs
if (empty($backup_id)) {
throw new Exception(__('Please select a backup file to restore.', 'tigerstyle-life9'));
}
if (!$restore_files && !$restore_database) {
throw new Exception(__('Please select at least files or database to restore.', 'tigerstyle-life9'));
}
// Find backup file
$backup_path = $this->get_backup_file_path($backup_id);
if (!$backup_path || !file_exists($backup_path)) {
throw new Exception(__('Backup file not found.', 'tigerstyle-life9'));
}
// Validate backup if requested
if ($validate_backup) {
$file_ext = strtolower(pathinfo($backup_path, PATHINFO_EXTENSION));
$validation = $this->validate_backup_file($backup_path, $file_ext);
if (!$validation['valid']) {
throw new Exception(__('Backup file validation failed: ' . $validation['error'], 'tigerstyle-life9'));
}
}
// Create backup before restore if requested
if ($create_backup_before) {
$this->create_pre_restore_backup();
}
// Begin restoration process
$this->perform_restoration($backup_path, $restore_files, $restore_database);
add_action('admin_notices', function() {
echo '';
echo '
π ' . __('Backup restored successfully! Your site has been brought back to life!', 'tigerstyle-life9') . '
';
echo '
';
});
} catch (Exception $e) {
add_action('admin_notices', function() use ($e) {
echo '';
echo '
πΏ ' . sprintf(__('Restore failed: %s', 'tigerstyle-life9'), $e->getMessage()) . '
';
echo '
';
});
}
}
/**
* Get existing backups
*/
private function get_existing_backups() {
$backups = [];
// Get backup metadata
$backup_metadata = get_option('tigerstyle_life9_backups', []);
foreach ($backup_metadata as $index => $backup) {
// Check if backup file still exists
$file_exists = false;
if (isset($backup['storage_path'])) {
if (strpos($backup['storage_path'], 's3://') === 0) {
$file_exists = $this->check_s3_file_exists($backup['filename']);
} else {
$file_exists = file_exists($backup['storage_path']);
}
}
// Format backup data for interface
$formatted_backup = [
'id' => $backup['filename'], // Use filename as ID
'name' => $backup['name'],
'filename' => $backup['filename'],
'date' => date('M j, Y g:i A', strtotime($backup['created'])),
'size' => $this->format_file_size($backup['size']),
'location' => ($backup['storage'] === 's3') ? 'βοΈ S3/MinIO' : 'π Local',
'file_exists' => $file_exists,
'storage' => $backup['storage'],
'storage_path' => $backup['storage_path'],
'includes' => $backup['includes'],
'status' => $backup['status'],
'created' => $backup['created'],
'size_bytes' => $backup['size']
];
// Only include if file exists
if ($file_exists) {
$backups[] = $formatted_backup;
}
}
// Fallback: If no metadata backups found, scan backup directory directly
if (empty($backups)) {
error_log('TigerStyle Life9: No metadata backups found, scanning backup directory directly');
$backup_dir = WP_CONTENT_DIR . '/backups';
if (is_dir($backup_dir)) {
$files = glob($backup_dir . '/*.zip');
foreach ($files as $file) {
if (is_file($file)) {
$filename = basename($file);
$file_size = filesize($file);
$file_time = filemtime($file);
// Extract backup name from filename (remove timestamp suffix)
$name_parts = explode('_', pathinfo($filename, PATHINFO_FILENAME));
array_pop($name_parts); // Remove timestamp
$backup_name = implode('_', $name_parts);
$fallback_backup = [
'id' => $filename,
'name' => $backup_name,
'filename' => $filename,
'date' => date('M j, Y g:i A', $file_time),
'size' => $this->format_file_size($file_size),
'location' => 'π Local (Found)',
'file_exists' => true,
'storage' => 'local',
'storage_path' => $file,
'includes' => ['files', 'database'], // Assume full backup
'status' => 'completed',
'created' => date('Y-m-d H:i:s', $file_time),
'size_bytes' => $file_size
];
$backups[] = $fallback_backup;
error_log('TigerStyle Life9: Found fallback backup: ' . $filename);
}
}
}
}
// Sort by creation date (newest first)
usort($backups, function($a, $b) {
return strtotime($b['created']) - strtotime($a['created']);
});
return $backups;
}
/**
* Get infrastructure status checks
*/
private function get_infrastructure_status() {
$status_checks = [];
// 1. Backup Engine Status
$backup_dir = WP_CONTENT_DIR . '/backups';
$backup_engine_status = is_dir($backup_dir) && is_writable($backup_dir);
$status_checks[] = [
'name' => 'Backup Engine',
'status' => $backup_engine_status,
'message' => $backup_engine_status ? 'Ready for operations' : 'Backup directory not accessible'
];
// 2. S3/MinIO Cloud Storage Status
$s3_settings = get_option('tigerstyle_life9_s3_settings', []);
$s3_configured = !empty($s3_settings['access_key']) && !empty($s3_settings['secret_key']);
$s3_status = false;
$s3_message = 'Not configured';
if ($s3_configured) {
try {
// Test S3 connectivity
$test_result = $this->test_s3_connection();
$s3_status = $test_result['success'];
$s3_message = $test_result['success'] ? 'Connected and ready' : $test_result['error'];
} catch (Exception $e) {
$s3_message = 'Connection test failed: ' . $e->getMessage();
}
}
$status_checks[] = [
'name' => 'S3/MinIO Cloud Storage',
'status' => $s3_status,
'message' => $s3_message
];
// 3. File Upload Limits
$upload_max = wp_max_upload_size();
$php_max = ini_get('upload_max_filesize');
$post_max = ini_get('post_max_size');
$memory_limit = ini_get('memory_limit');
$upload_healthy = $upload_max >= (100 * 1024 * 1024); // 100MB threshold
$status_checks[] = [
'name' => 'File Upload Limits',
'status' => $upload_healthy,
'message' => sprintf('Max: %s (PHP: %s, POST: %s, Memory: %s)',
size_format($upload_max), $php_max, $post_max, $memory_limit)
];
// 4. Database Connection
global $wpdb;
$db_status = false;
$db_message = 'Connection failed';
try {
$result = $wpdb->get_var("SELECT 1");
$db_status = ($result == 1);
$db_message = $db_status ? 'Connected and responsive' : 'Query test failed';
} catch (Exception $e) {
$db_message = 'Error: ' . $e->getMessage();
}
$status_checks[] = [
'name' => 'Database Connection',
'status' => $db_status,
'message' => $db_message
];
// 5. Disk Space
$backup_dir_space = disk_free_space($backup_dir);
$wp_content_space = disk_free_space(WP_CONTENT_DIR);
$min_space_threshold = 1024 * 1024 * 1024; // 1GB
$space_adequate = $backup_dir_space > $min_space_threshold;
$status_checks[] = [
'name' => 'Disk Space',
'status' => $space_adequate,
'message' => sprintf('Available: %s (Backup dir: %s)',
size_format($wp_content_space),
size_format($backup_dir_space))
];
// 6. Security & Permissions
$security_status = true;
$security_issues = [];
// Check if backup directory is web-accessible (security risk)
$backup_url = content_url('backups/');
$response = wp_remote_head($backup_url, ['timeout' => 5]);
if (!is_wp_error($response) && wp_remote_retrieve_response_code($response) !== 403) {
$security_status = false;
$security_issues[] = 'Backup directory web-accessible';
}
// Check file permissions
if (!is_writable($backup_dir)) {
$security_status = false;
$security_issues[] = 'Backup directory not writable';
}
$status_checks[] = [
'name' => 'Security & Permissions',
'status' => $security_status,
'message' => $security_status ? 'All security checks passed' : implode(', ', $security_issues)
];
return $status_checks;
}
/**
* Get security status for dashboard display
*/
private function get_security_status() {
$security_issues = [];
// Check backup directory permissions
$backup_dir = WP_CONTENT_DIR . '/backups';
if (!is_dir($backup_dir) || !is_writable($backup_dir)) {
$security_issues[] = 'Backup directory not writable';
}
// Check if backup directory is web-accessible (security risk)
$backup_url = content_url('backups/');
$response = wp_remote_head($backup_url, ['timeout' => 5]);
if (!is_wp_error($response) && wp_remote_retrieve_response_code($response) !== 403) {
$security_issues[] = 'Backup directory web-accessible';
}
// Check file permissions on key files
$plugin_file = __FILE__;
if (is_writable($plugin_file)) {
$security_issues[] = 'Plugin file is writable';
}
// Check WordPress security constants
if (!defined('DISALLOW_FILE_EDIT') || !DISALLOW_FILE_EDIT) {
$security_issues[] = 'File editing not disabled';
}
// Check SSL
if (!is_ssl() && !defined('WP_DEBUG') || !WP_DEBUG) {
$security_issues[] = 'SSL not enabled';
}
$is_secure = empty($security_issues);
return [
'status' => $is_secure,
'message' => $is_secure ? 'All security checks passed' : implode(', ', $security_issues),
'issues' => $security_issues
];
}
/**
* Test S3 connection
*/
private function test_s3_connection() {
$s3_settings = get_option('tigerstyle_life9_s3_settings', []);
if (empty($s3_settings['access_key']) || empty($s3_settings['secret_key'])) {
return ['success' => false, 'error' => 'S3 credentials not configured'];
}
try {
// Create a test object to verify connectivity
$test_key = 'tigerstyle-connection-test-' . time() . '.txt';
$test_content = 'TigerStyle Life9 connection test at ' . date('Y-m-d H:i:s');
$result = $this->upload_to_s3($test_content, $test_key);
if ($result['success']) {
// Clean up test file
$this->delete_from_s3($test_key);
return ['success' => true, 'error' => null];
} else {
return ['success' => false, 'error' => $result['error']];
}
} catch (Exception $e) {
return ['success' => false, 'error' => 'Connection failed: ' . $e->getMessage()];
}
}
/**
* Add files to backup
*/
private function add_files_to_backup($zip) {
$wp_root = ABSPATH;
$iterator = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($wp_root));
foreach ($iterator as $file) {
if ($file->isFile()) {
$filePath = $file->getRealPath();
$relativePath = substr($filePath, strlen($wp_root));
// Skip certain directories and files
if ($this->should_skip_file($relativePath)) {
continue;
}
$zip->addFile($filePath, $relativePath);
}
}
}
/**
* Add database to backup
*/
private function add_database_to_backup($zip) {
global $wpdb;
$sql_content = "-- TigerStyle Life9 Database Backup\n";
$sql_content .= "-- Created: " . current_time('mysql') . "\n\n";
// Get all tables
$tables = $wpdb->get_col("SHOW TABLES");
foreach ($tables as $table) {
// Get table structure
$create_table = $wpdb->get_row("SHOW CREATE TABLE `$table`", ARRAY_A);
$sql_content .= "DROP TABLE IF EXISTS `$table`;\n";
$sql_content .= $create_table['Create Table'] . ";\n\n";
// Get table data
$rows = $wpdb->get_results("SELECT * FROM `$table`", ARRAY_A);
if ($rows) {
foreach ($rows as $row) {
$values = array_map(function($value) use ($wpdb) {
return is_null($value) ? 'NULL' : "'" . $wpdb->_real_escape($value) . "'";
}, array_values($row));
$sql_content .= "INSERT INTO `$table` VALUES (" . implode(', ', $values) . ");\n";
}
}
$sql_content .= "\n";
}
// Add SQL file to ZIP
$zip->addFromString('database.sql', $sql_content);
}
/**
* Get files for backup (PclZip compatible)
*/
private function get_files_for_backup() {
$files = [];
$wp_root = ABSPATH;
$iterator = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($wp_root));
foreach ($iterator as $file) {
if ($file->isFile()) {
$filePath = $file->getRealPath();
$relativePath = substr($filePath, strlen($wp_root));
// Skip certain directories and files
if ($this->should_skip_file($relativePath)) {
continue;
}
$files[] = $filePath;
}
}
return $files;
}
/**
* Create database backup file (PclZip compatible)
*/
private function create_database_backup() {
global $wpdb;
$sql_content = "-- TigerStyle Life9 Database Backup\n";
$sql_content .= "-- Created: " . current_time('mysql') . "\n\n";
// Get all tables
$tables = $wpdb->get_col("SHOW TABLES");
foreach ($tables as $table) {
// Get table structure
$create_table = $wpdb->get_row("SHOW CREATE TABLE `$table`", ARRAY_A);
$sql_content .= "DROP TABLE IF EXISTS `$table`;\n";
$sql_content .= $create_table['Create Table'] . ";\n\n";
// Get table data
$rows = $wpdb->get_results("SELECT * FROM `$table`", ARRAY_A);
if ($rows) {
foreach ($rows as $row) {
$values = array_map(function($value) use ($wpdb) {
return is_null($value) ? 'NULL' : "'" . $wpdb->_real_escape($value) . "'";
}, array_values($row));
$sql_content .= "INSERT INTO `$table` VALUES (" . implode(', ', $values) . ");\n";
}
}
$sql_content .= "\n";
}
// Write to temporary file with safer filename
$backup_dir = WP_CONTENT_DIR . '/backups';
$safe_timestamp = date('Y-m-d_H-i-s');
$db_file = $backup_dir . '/database_' . $safe_timestamp . '.sql';
// Ensure directory exists and is writable
if (!file_exists($backup_dir)) {
wp_mkdir_p($backup_dir);
}
// Try to write the file
$result = file_put_contents($db_file, $sql_content);
if ($result === false) {
error_log('TigerStyle Life9: Failed to write database file to ' . $db_file);
return false;
}
return $db_file;
}
/**
* Upload backup to S3/MinIO
*/
private function upload_to_s3($file_path, $filename) {
// This would integrate with AWS SDK or MinIO client
// For now, we'll simulate the upload
// In production, you'd implement actual S3/MinIO upload logic here
return true;
}
/**
* Save backup metadata
*/
private function save_backup_metadata($backup_data) {
$backups = get_option('tigerstyle_life9_backups', []);
$backups[] = $backup_data;
update_option('tigerstyle_life9_backups', $backups);
}
/**
* Validate backup file
*/
private function validate_backup_file($file_path, $file_ext) {
$result = ['valid' => false, 'includes' => [], 'error' => ''];
try {
switch ($file_ext) {
case 'zip':
$zip = new ZipArchive();
if ($zip->open($file_path) === TRUE) {
// Check for common WordPress files/folders
for ($i = 0; $i < $zip->numFiles; $i++) {
$filename = $zip->getNameIndex($i);
if (strpos($filename, 'wp-config.php') !== false) {
$result['includes'][] = 'files';
}
if (strpos($filename, 'database.sql') !== false || strpos($filename, '.sql') !== false) {
$result['includes'][] = 'database';
}
}
$zip->close();
$result['valid'] = true;
}
break;
case 'sql':
$result['includes'][] = 'database';
$result['valid'] = true;
break;
case 'xml':
// WordPress export file
$result['includes'][] = 'content';
$result['valid'] = true;
break;
default:
$result['valid'] = true;
$result['includes'][] = 'unknown';
}
} catch (Exception $e) {
$result['error'] = $e->getMessage();
}
return $result;
}
/**
* Check if file should be skipped during backup
*/
private function should_skip_file($relative_path) {
$skip_patterns = [
'wp-content/cache/',
'wp-content/backups/',
'wp-content/upgrade/',
'.git/',
'.svn/',
'node_modules/',
'.DS_Store',
'Thumbs.db'
];
foreach ($skip_patterns as $pattern) {
if (strpos($relative_path, $pattern) !== false) {
return true;
}
}
return false;
}
/**
* Format file size for display
*/
private function format_file_size($bytes) {
if ($bytes >= 1073741824) {
return number_format($bytes / 1073741824, 2) . ' GB';
} elseif ($bytes >= 1048576) {
return number_format($bytes / 1048576, 2) . ' MB';
} elseif ($bytes >= 1024) {
return number_format($bytes / 1024, 2) . ' KB';
}
return $bytes . ' bytes';
}
/**
* Get backup file path by filename
*/
private function get_backup_file_path($filename) {
$backup_metadata = get_option('tigerstyle_life9_backups', []);
foreach ($backup_metadata as $backup) {
if ($backup['filename'] === $filename) {
return $backup['storage_path'];
}
}
return false;
}
/**
* Check if S3 file exists
*/
private function check_s3_file_exists($filename) {
// This would check S3/MinIO for file existence
// For now, we'll return true as a placeholder
return true;
}
/**
* Create pre-restore backup
*/
private function create_pre_restore_backup() {
// Create a quick backup before restoration
$backup_name = 'pre_restore_' . date('Y-m-d_H-i-s');
// Implementation would be similar to handle_backup_creation
}
/**
* Perform restoration
*/
private function perform_restoration($backup_path, $restore_files, $restore_database) {
error_log("TigerStyle Life9: perform_restoration() called");
error_log("TigerStyle Life9: backup_path: $backup_path");
error_log("TigerStyle Life9: restore_files: " . ($restore_files ? 'true' : 'false'));
error_log("TigerStyle Life9: restore_database: " . ($restore_database ? 'true' : 'false'));
try {
// Create temporary directory for extraction
$temp_dir = WP_CONTENT_DIR . '/temp_restore_' . time();
if (!wp_mkdir_p($temp_dir)) {
throw new Exception(__('Could not create temporary directory for restoration.', 'tigerstyle-life9'));
}
// Extract backup ZIP file
if (!$this->extract_backup_file($backup_path, $temp_dir)) {
throw new Exception(__('Failed to extract backup file.', 'tigerstyle-life9'));
}
// Restore database if requested
if ($restore_database) {
$this->restore_database_from_backup($temp_dir);
}
// Restore files if requested
if ($restore_files) {
$this->restore_files_from_backup($temp_dir);
}
// Clean up temporary directory
$this->cleanup_directory($temp_dir);
error_log("TigerStyle Life9: Restoration completed successfully");
} catch (Exception $e) {
// Clean up on failure
if (isset($temp_dir) && file_exists($temp_dir)) {
$this->cleanup_directory($temp_dir);
}
throw $e;
}
}
/**
* Extract backup ZIP file
*/
private function extract_backup_file($backup_path, $temp_dir) {
if (!class_exists('PclZip')) {
require_once(ABSPATH . 'wp-admin/includes/class-pclzip.php');
}
$zip = new PclZip($backup_path);
$result = $zip->extract(PCLZIP_OPT_PATH, $temp_dir);
if ($result == 0) {
error_log("TigerStyle Life9: ZIP extraction failed: " . $zip->errorInfo(true));
return false;
}
return true;
}
/**
* Restore database from backup
*/
private function restore_database_from_backup($temp_dir) {
// Look for database SQL file in wp-content/backups directory
$backup_dir = $temp_dir . '/wp-content/backups';
$sql_file = null;
if (file_exists($backup_dir)) {
// Find the database file (it will have a timestamp in the name)
$files = glob($backup_dir . '/database_*.sql');
if (!empty($files)) {
$sql_file = $files[0]; // Use the first (and likely only) database file
}
}
// Also check for a simple database.sql in root (for legacy backups)
if (!$sql_file && file_exists($temp_dir . '/database.sql')) {
$sql_file = $temp_dir . '/database.sql';
}
if (!$sql_file) {
throw new Exception(__('Database backup file not found in backup. Expected wp-content/backups/database_*.sql', 'tigerstyle-life9'));
}
// Read SQL file
$sql_content = file_get_contents($sql_file);
if ($sql_content === false) {
throw new Exception(__('Could not read database backup file.', 'tigerstyle-life9'));
}
// Split SQL into individual statements (respecting quoted strings)
$sql_statements = $this->parse_sql_statements($sql_content);
global $wpdb;
error_log("TigerStyle Life9: Starting database restoration with transaction safety");
// Start transaction for atomic restoration
$wpdb->query('START TRANSACTION');
try {
// Disable foreign key checks for import
$wpdb->query('SET FOREIGN_KEY_CHECKS = 0');
// Create a temporary backup of current state for rollback
$current_backup_path = $this->create_emergency_backup();
error_log("TigerStyle Life9: Emergency backup created at: $current_backup_path");
$statements_executed = 0;
$total_statements = count($sql_statements);
foreach ($sql_statements as $statement) {
if (!empty($statement)) {
$result = $wpdb->query($statement);
if ($result === false) {
throw new Exception(__('Database restoration failed at statement ' . ($statements_executed + 1) . ' of ' . $total_statements . ': ' . $wpdb->last_error, 'tigerstyle-life9'));
}
$statements_executed++;
// Log progress every 100 statements
if ($statements_executed % 100 === 0) {
error_log("TigerStyle Life9: Processed $statements_executed/$total_statements SQL statements");
}
}
}
// Re-enable foreign key checks
$wpdb->query('SET FOREIGN_KEY_CHECKS = 1');
// Commit the transaction
$wpdb->query('COMMIT');
error_log("TigerStyle Life9: Database restored successfully ($statements_executed statements executed)");
} catch (Exception $e) {
// Rollback the transaction
$wpdb->query('ROLLBACK');
$wpdb->query('SET FOREIGN_KEY_CHECKS = 1');
error_log("TigerStyle Life9: Database restoration failed, transaction rolled back: " . $e->getMessage());
throw $e;
}
}
/**
* Parse SQL statements while respecting quoted strings and serialized data
*/
private function parse_sql_statements($sql_content) {
$statements = [];
$current_statement = '';
$in_quotes = false;
$quote_char = '';
$length = strlen($sql_content);
for ($i = 0; $i < $length; $i++) {
$char = $sql_content[$i];
$next_char = ($i + 1 < $length) ? $sql_content[$i + 1] : '';
// Handle quote escaping
if ($in_quotes && $char === '\\' && ($next_char === $quote_char || $next_char === '\\')) {
$current_statement .= $char . $next_char;
$i++; // Skip next character
continue;
}
// Handle quote start/end
if (!$in_quotes && ($char === '"' || $char === "'")) {
$in_quotes = true;
$quote_char = $char;
$current_statement .= $char;
} elseif ($in_quotes && $char === $quote_char) {
// Check if it's a doubled quote (escape sequence)
if ($next_char === $quote_char) {
$current_statement .= $char . $next_char;
$i++; // Skip next character
} else {
$in_quotes = false;
$quote_char = '';
$current_statement .= $char;
}
} elseif ($char === ';' && !$in_quotes) {
// End of statement
$statement = trim($current_statement);
if (!empty($statement)) {
$statements[] = $statement;
}
$current_statement = '';
} else {
$current_statement .= $char;
}
}
// Add final statement if exists
$statement = trim($current_statement);
if (!empty($statement)) {
$statements[] = $statement;
}
return $statements;
}
/**
* Create emergency backup of current database state for rollback purposes
*/
private function create_emergency_backup() {
$emergency_dir = WP_CONTENT_DIR . '/backups/emergency';
if (!file_exists($emergency_dir)) {
wp_mkdir_p($emergency_dir);
}
$timestamp = date('Y-m-d_H-i-s');
$backup_file = $emergency_dir . '/emergency_backup_' . $timestamp . '.sql';
global $wpdb;
// Get all tables in the database
$tables = $wpdb->get_results('SHOW TABLES', ARRAY_N);
$sql_content = "-- Emergency backup created on $timestamp\n";
$sql_content .= "-- This backup was created automatically before database restoration\n\n";
foreach ($tables as $table) {
$table_name = $table[0];
// Get table structure
$create_table = $wpdb->get_row("SHOW CREATE TABLE `$table_name`", ARRAY_N);
$sql_content .= "DROP TABLE IF EXISTS `$table_name`;\n";
$sql_content .= $create_table[1] . ";\n\n";
// Get table data
$rows = $wpdb->get_results("SELECT * FROM `$table_name`", ARRAY_A);
if (!empty($rows)) {
$columns = array_keys($rows[0]);
$sql_content .= "INSERT INTO `$table_name` (`" . implode('`, `', $columns) . "`) VALUES\n";
$values = [];
foreach ($rows as $row) {
$escaped_values = [];
foreach ($row as $value) {
if ($value === null) {
$escaped_values[] = 'NULL';
} else {
$escaped_values[] = "'" . $wpdb->_real_escape($value) . "'";
}
}
$values[] = "(" . implode(', ', $escaped_values) . ")";
}
$sql_content .= implode(",\n", $values) . ";\n\n";
}
}
$result = file_put_contents($backup_file, $sql_content);
if ($result === false) {
throw new Exception(__('Could not create emergency backup file.', 'tigerstyle-life9'));
}
return $backup_file;
}
/**
* Restore files from backup
*/
private function restore_files_from_backup($temp_dir) {
// Check if backup has WordPress files at root level (current format)
$wp_content_backup = $temp_dir . '/wp-content';
// Also check legacy format with wordpress/ subdirectory
if (!file_exists($wp_content_backup)) {
$legacy_wordpress_dir = $temp_dir . '/wordpress';
if (file_exists($legacy_wordpress_dir . '/wp-content')) {
$wp_content_backup = $legacy_wordpress_dir . '/wp-content';
}
}
if (!file_exists($wp_content_backup)) {
throw new Exception(__('WordPress wp-content directory not found in backup.', 'tigerstyle-life9'));
}
// Restore wp-content directory (themes, plugins, uploads)
if (file_exists($wp_content_backup)) {
$this->copy_directory($wp_content_backup, WP_CONTENT_DIR);
}
// Restore other WordPress files (excluding wp-config.php for safety)
// Determine the correct source directory for core files
$core_files_source = file_exists($temp_dir . '/wp-includes') ? $temp_dir : $temp_dir . '/wordpress';
if (file_exists($core_files_source . '/wp-includes')) {
$this->restore_core_files($core_files_source, ABSPATH);
} else {
error_log("TigerStyle Life9: No WordPress core files found in backup, skipping core file restoration");
}
error_log("TigerStyle Life9: Files restored successfully");
}
/**
* Copy directory recursively
*/
private function copy_directory($source, $destination) {
if (!is_dir($source)) {
return false;
}
if (!is_dir($destination)) {
wp_mkdir_p($destination);
}
$dir = opendir($source);
while (($file = readdir($dir)) !== false) {
if ($file != '.' && $file != '..') {
$src = $source . '/' . $file;
$dst = $destination . '/' . $file;
if (is_dir($src)) {
$this->copy_directory($src, $dst);
} else {
copy($src, $dst);
}
}
}
closedir($dir);
return true;
}
/**
* Restore core WordPress files (excluding sensitive files)
*/
private function restore_core_files($source_dir, $destination_dir) {
$excluded_files = ['wp-config.php', '.htaccess']; // Skip sensitive files
$dir = opendir($source_dir);
while (($file = readdir($dir)) !== false) {
if ($file != '.' && $file != '..' && !in_array($file, $excluded_files)) {
$src = $source_dir . '/' . $file;
$dst = $destination_dir . '/' . $file;
if (is_file($src) && $file != 'wp-content') { // Skip wp-content as it's handled separately
copy($src, $dst);
}
}
}
closedir($dir);
}
/**
* Clean up directory recursively
*/
private function cleanup_directory($dir) {
if (!is_dir($dir)) {
return;
}
$files = array_diff(scandir($dir), ['.', '..']);
foreach ($files as $file) {
$path = $dir . '/' . $file;
if (is_dir($path)) {
$this->cleanup_directory($path);
} else {
unlink($path);
}
}
rmdir($dir);
}
/**
* Plugin activation - create download tokens table
*/
public function activate_plugin() {
global $wpdb;
$table_name = $wpdb->prefix . 'tigerstyle_download_tokens';
$charset_collate = $wpdb->get_charset_collate();
$sql = "CREATE TABLE $table_name (
id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
token varchar(11) NOT NULL UNIQUE,
backup_id varchar(255) NOT NULL,
backup_filename varchar(255) NOT NULL,
created_by bigint(20) unsigned NOT NULL,
created_at datetime DEFAULT CURRENT_TIMESTAMP,
expires_at datetime NOT NULL,
download_count int(11) DEFAULT 0,
max_downloads int(11) DEFAULT 1,
user_agent text DEFAULT NULL,
ip_address varchar(45) DEFAULT NULL,
last_downloaded_at datetime DEFAULT NULL,
PRIMARY KEY (id),
UNIQUE KEY token (token),
KEY backup_id (backup_id),
KEY created_by (created_by),
KEY expires_at (expires_at)
) $charset_collate;";
require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
dbDelta($sql);
error_log('TigerStyle Life9: Download tokens table created');
}
/**
* Public method to manually create database table (for debugging)
*/
public function manual_create_table() {
global $wpdb;
$table_name = $wpdb->prefix . 'tigerstyle_download_tokens';
$charset_collate = $wpdb->get_charset_collate();
$sql = "CREATE TABLE $table_name (
id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
token varchar(11) NOT NULL UNIQUE,
backup_id varchar(255) NOT NULL,
backup_filename varchar(255) NOT NULL,
created_by bigint(20) unsigned NOT NULL,
created_at datetime DEFAULT CURRENT_TIMESTAMP,
expires_at datetime NOT NULL,
download_count int(11) DEFAULT 0,
max_downloads int(11) DEFAULT 1,
user_agent text DEFAULT NULL,
ip_address varchar(45) DEFAULT NULL,
last_downloaded_at datetime DEFAULT NULL,
PRIMARY KEY (id),
UNIQUE KEY token (token),
KEY backup_id (backup_id),
KEY created_by (created_by),
KEY expires_at (expires_at)
) $charset_collate;";
require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
$result = dbDelta($sql);
error_log('TigerStyle Life9: Manual table creation result: ' . print_r($result, true));
// Check if table exists
$table_exists = $wpdb->get_var("SHOW TABLES LIKE '$table_name'") == $table_name;
error_log('TigerStyle Life9: Table exists after manual creation: ' . ($table_exists ? 'YES' : 'NO'));
return $table_exists;
}
/**
* AJAX handler for manual table creation
*/
public function handle_manual_table_creation() {
// Verify user capabilities
if (!current_user_can('manage_options')) {
wp_die('Unauthorized access');
}
$result = $this->manual_create_table();
wp_send_json_success([
'table_created' => $result,
'message' => $result ? 'Table created successfully' : 'Table creation failed'
]);
}
/**
* Generate YouTube-style short ID (11 characters)
*/
private function generate_short_id() {
$characters = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_';
$id = '';
for ($i = 0; $i < 11; $i++) {
$id .= $characters[wp_rand(0, strlen($characters) - 1)];
}
return $id;
}
/**
* Create time-limited download token
*/
private function create_download_token($backup_id, $backup_filename, $expires_in_minutes = 10, $max_downloads = 1) {
global $wpdb;
$table_name = $wpdb->prefix . 'tigerstyle_download_tokens';
// Generate unique token
do {
$token = $this->generate_short_id();
$existing = $wpdb->get_var($wpdb->prepare(
"SELECT COUNT(*) FROM $table_name WHERE token = %s",
$token
));
} while ($existing > 0);
$expires_at = date('Y-m-d H:i:s', time() + ($expires_in_minutes * 60));
$result = $wpdb->insert(
$table_name,
[
'token' => $token,
'backup_id' => $backup_id,
'backup_filename' => $backup_filename,
'created_by' => get_current_user_id(),
'expires_at' => $expires_at,
'max_downloads' => $max_downloads,
'ip_address' => $_SERVER['REMOTE_ADDR'] ?? '',
'user_agent' => $_SERVER['HTTP_USER_AGENT'] ?? ''
],
[
'%s', '%s', '%s', '%d', '%s', '%d', '%s', '%s'
]
);
if ($result === false) {
throw new Exception(__('Failed to create download token.', 'tigerstyle-life9'));
}
error_log("TigerStyle Life9: Created download token $token for backup $backup_filename, expires at $expires_at");
return [
'token' => $token,
'url' => home_url("/?tigerstyle_dl=$token"),
'expires_at' => $expires_at,
'expires_in_minutes' => $expires_in_minutes
];
}
/**
* Handle AJAX request to generate download link
*/
public function handle_generate_download_link() {
// Verify nonce
if (!wp_verify_nonce($_POST['nonce'], 'tigerstyle_generate_link')) {
wp_die(__('Security check failed.', 'tigerstyle-life9'));
}
// Check permissions
if (!current_user_can('manage_options')) {
wp_die(__('Insufficient permissions.', 'tigerstyle-life9'));
}
try {
$backup_id = sanitize_text_field($_POST['backup_id']);
$expires_minutes = intval($_POST['expires_minutes'] ?? 10);
$max_downloads = intval($_POST['max_downloads'] ?? 1);
// Validate expiration time (1 minute to 24 hours)
if ($expires_minutes < 1 || $expires_minutes > 1440) {
$expires_minutes = 10;
}
// Validate max downloads (1 to 100)
if ($max_downloads < 1 || $max_downloads > 100) {
$max_downloads = 1;
}
// Get backup metadata using the same method as the UI
$backups = $this->get_existing_backups();
$backup_filename = null;
foreach ($backups as $backup) {
if ($backup['id'] === $backup_id) {
$backup_filename = $backup['id']; // The ID is actually the filename
break;
}
}
if (!$backup_filename) {
throw new Exception(__('Backup not found.', 'tigerstyle-life9'));
}
$token_data = $this->create_download_token($backup_id, $backup_filename, $expires_minutes, $max_downloads);
wp_send_json_success([
'token' => $token_data['token'],
'url' => $token_data['url'],
'expires_at' => $token_data['expires_at'],
'expires_in_minutes' => $expires_minutes,
'max_downloads' => $max_downloads
]);
} catch (Exception $e) {
wp_send_json_error($e->getMessage());
}
}
/**
* Handle time-limited download request
*/
private function handle_time_limited_download() {
global $wpdb;
$token = sanitize_text_field($_GET['tigerstyle_dl']);
$table_name = $wpdb->prefix . 'tigerstyle_download_tokens';
try {
// Get token data
$token_data = $wpdb->get_row($wpdb->prepare(
"SELECT * FROM $table_name WHERE token = %s",
$token
), ARRAY_A);
if (!$token_data) {
$this->log_download_attempt($token, 'TOKEN_NOT_FOUND', $_SERVER['REMOTE_ADDR'] ?? '', $_SERVER['HTTP_USER_AGENT'] ?? '');
wp_die(__('π« Download link not found or invalid.', 'tigerstyle-life9'), __('Invalid Download Link', 'tigerstyle-life9'));
}
// Check if token has expired
if (strtotime($token_data['expires_at']) < time()) {
$this->log_download_attempt($token, 'EXPIRED', $_SERVER['REMOTE_ADDR'] ?? '', $_SERVER['HTTP_USER_AGENT'] ?? '', $token_data['backup_filename']);
wp_die(__('π Download link has expired.', 'tigerstyle-life9'), __('Link Expired', 'tigerstyle-life9'));
}
// Check download count limit
if ($token_data['download_count'] >= $token_data['max_downloads']) {
$this->log_download_attempt($token, 'MAX_DOWNLOADS_EXCEEDED', $_SERVER['REMOTE_ADDR'] ?? '', $_SERVER['HTTP_USER_AGENT'] ?? '', $token_data['backup_filename']);
wp_die(__('π« Download limit reached for this link.', 'tigerstyle-life9'), __('Download Limit Exceeded', 'tigerstyle-life9'));
}
// Update download count and timestamp
$wpdb->update(
$table_name,
[
'download_count' => $token_data['download_count'] + 1,
'last_downloaded_at' => current_time('mysql'),
'ip_address' => $_SERVER['REMOTE_ADDR'] ?? '',
'user_agent' => $_SERVER['HTTP_USER_AGENT'] ?? ''
],
['id' => $token_data['id']],
['%d', '%s', '%s', '%s'],
['%d']
);
// Log successful download attempt
$this->log_download_attempt($token, 'SUCCESS', $_SERVER['REMOTE_ADDR'] ?? '', $_SERVER['HTTP_USER_AGENT'] ?? '', $token_data['backup_filename']);
// Determine backup location and serve file
$backups = $this->get_existing_backups();
$backup_metadata = null;
foreach ($backups as $backup) {
if ($backup['id'] === $token_data['backup_filename']) {
$backup_metadata = $backup;
break;
}
}
if (!$backup_metadata) {
throw new Exception(__('Backup metadata not found.', 'tigerstyle-life9'));
}
// Serve the file based on storage type
if ($backup_metadata['storage'] === 's3') {
$this->download_from_s3($token_data['backup_filename'], $backup_metadata);
} else {
$this->download_from_local($token_data['backup_filename'], $backup_metadata);
}
} catch (Exception $e) {
$this->log_download_attempt($token, 'ERROR', $_SERVER['REMOTE_ADDR'] ?? '', $_SERVER['HTTP_USER_AGENT'] ?? '', $token_data['backup_filename'] ?? '', $e->getMessage());
wp_die(__('Download failed: ', 'tigerstyle-life9') . $e->getMessage());
}
}
/**
* Log download attempt for tracking
*/
private function log_download_attempt($token, $status, $ip_address, $user_agent, $backup_filename = '', $error_message = '') {
error_log(sprintf(
'TigerStyle Life9 Download: Token=%s Status=%s IP=%s UserAgent=%s File=%s Error=%s',
$token,
$status,
$ip_address,
substr($user_agent, 0, 100),
$backup_filename,
$error_message
));
}
/**
* Get download token statistics
*/
public function get_download_token_stats($backup_id = null) {
global $wpdb;
$table_name = $wpdb->prefix . 'tigerstyle_download_tokens';
$where_clause = '';
$params = [];
if ($backup_id) {
$where_clause = 'WHERE backup_id = %s';
$params[] = $backup_id;
}
$stats = $wpdb->get_row($wpdb->prepare(
"SELECT
COUNT(*) as total_tokens,
SUM(download_count) as total_downloads,
COUNT(CASE WHEN expires_at > NOW() THEN 1 END) as active_tokens,
COUNT(CASE WHEN expires_at <= NOW() THEN 1 END) as expired_tokens
FROM $table_name $where_clause",
$params
), ARRAY_A);
return $stats;
}
/**
* Clean up expired tokens (AJAX handler)
*/
public function cleanup_expired_tokens() {
if (!current_user_can('manage_options')) {
wp_die(__('Insufficient permissions.', 'tigerstyle-life9'));
}
global $wpdb;
$table_name = $wpdb->prefix . 'tigerstyle_download_tokens';
$deleted = $wpdb->query(
"DELETE FROM $table_name WHERE expires_at <= NOW()"
);
wp_send_json_success([
'deleted_count' => $deleted,
'message' => sprintf(__('Cleaned up %d expired tokens.', 'tigerstyle-life9'), $deleted)
]);
}
}
/**
* Initialize the complete plugin
*
* @return TigerStyle_Life9_Complete
*/
function tigerstyle_life9_complete_init() {
return TigerStyle_Life9_Complete::instance();
}
// Start the complete plugin
tigerstyle_life9_complete_init();