Because cats have 9 lives, but servers don't - so they need backup-restore! Complete backup solution with S3/MinIO support. - Full WordPress backup (files + database) - S3 / MinIO / S3-compatible storage backends - Scheduled automatic backups - Disaster recovery / one-click restore - Backup integrity validation - Cat-themed admin interface Includes build.sh and .distignore for WordPress-installable release ZIPs.
3439 lines
146 KiB
PHP
3439 lines
146 KiB
PHP
<?php
|
|
/**
|
|
* Plugin Name: TigerStyle Life9 Complete
|
|
* Plugin URI: https://tigerstyle.com/plugins/life9
|
|
* Description: Because cats have 9 lives, but servers don't - so they need backup-restore! Complete backup solution with S3/MinIO support.
|
|
* Version: 1.0.0
|
|
* Author: TigerStyle Development
|
|
* Author URI: https://tigerstyle.com
|
|
* License: GPL v2 or later
|
|
* License URI: https://www.gnu.org/licenses/gpl-2.0.html
|
|
* Text Domain: tigerstyle-life9
|
|
* Domain Path: /languages
|
|
* Requires at least: 5.0
|
|
* Tested up to: 6.3
|
|
* Requires PHP: 7.4
|
|
* Network: false
|
|
*
|
|
* @package TigerStyleLife9
|
|
* @version 1.0.0
|
|
*/
|
|
|
|
// Exit if accessed directly
|
|
if (!defined('ABSPATH')) {
|
|
exit;
|
|
}
|
|
|
|
// Define plugin constants
|
|
define('TIGERSTYLE_LIFE9_COMPLETE_VERSION', '1.0.0');
|
|
define('TIGERSTYLE_LIFE9_COMPLETE_PATH', plugin_dir_path(__FILE__));
|
|
define('TIGERSTYLE_LIFE9_COMPLETE_URL', plugin_dir_url(__FILE__));
|
|
define('TIGERSTYLE_LIFE9_COMPLETE_BASENAME', plugin_basename(__FILE__));
|
|
define('TIGERSTYLE_LIFE9_COMPLETE_FILE', __FILE__);
|
|
|
|
/**
|
|
* Main TigerStyle Life9 Complete Plugin Class
|
|
*
|
|
* Combines beautiful cat-themed interface with full backup functionality
|
|
*
|
|
* @since 1.0.0
|
|
*/
|
|
class TigerStyle_Life9_Complete {
|
|
|
|
/**
|
|
* Plugin instance
|
|
*
|
|
* @var TigerStyle_Life9_Complete
|
|
*/
|
|
private static $instance = null;
|
|
|
|
/**
|
|
* Whether full functionality is available
|
|
*
|
|
* @var bool
|
|
*/
|
|
private $full_functionality = false;
|
|
|
|
/**
|
|
* Get plugin instance (Singleton pattern)
|
|
*
|
|
* @return TigerStyle_Life9_Complete
|
|
*/
|
|
public static function instance() {
|
|
if (null === self::$instance) {
|
|
self::$instance = new self();
|
|
}
|
|
return self::$instance;
|
|
}
|
|
|
|
/**
|
|
* Constructor - Initialize the plugin
|
|
*/
|
|
private function __construct() {
|
|
$this->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() {
|
|
?>
|
|
<div class="wrap tigerstyle-life9-complete">
|
|
<h1 class="wp-heading-inline">
|
|
<span class="tigerstyle-icon">🐾</span>
|
|
<?php _e('TigerStyle Life9 Complete Dashboard', 'tigerstyle-life9'); ?>
|
|
</h1>
|
|
|
|
<div class="tigerstyle-cat-message">
|
|
<p><strong>🐱 Welcome to your backup territory!</strong></p>
|
|
<p><?php _e('Because cats have 9 lives, but servers don\'t - so they need backup-restore! This is the COMPLETE version with S3/MinIO support.', 'tigerstyle-life9'); ?></p>
|
|
</div>
|
|
|
|
<?php if (!$this->full_functionality): ?>
|
|
<div class="tigerstyle-cat-error">
|
|
<p><strong>😿 Some components couldn't load:</strong> <?php _e('Running in basic mode. Some advanced features may not be available.', 'tigerstyle-life9'); ?></p>
|
|
</div>
|
|
<?php endif; ?>
|
|
|
|
<div class="card">
|
|
<h2 class="title"><?php _e('🏠 Territory Status', 'tigerstyle-life9'); ?></h2>
|
|
<table class="form-table">
|
|
<tr>
|
|
<th scope="row"><?php _e('🐾 Lives Saved:', 'tigerstyle-life9'); ?></th>
|
|
<td>
|
|
<?php
|
|
$existing_backups = $this->get_existing_backups();
|
|
$backup_count = count($existing_backups);
|
|
|
|
if ($backup_count == 0) {
|
|
echo '<strong>';
|
|
_e('0 backups created (ready to start!)', 'tigerstyle-life9');
|
|
echo '</strong>';
|
|
} else {
|
|
echo '<strong>';
|
|
printf(_n('%d backup created', '%d backups created', $backup_count, 'tigerstyle-life9'), $backup_count);
|
|
echo '</strong> <span style="color: green;">✅</span>';
|
|
}
|
|
?>
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<th scope="row"><?php _e('🔒 Security Level:', 'tigerstyle-life9'); ?></th>
|
|
<td>
|
|
<?php
|
|
$security_status = $this->get_security_status();
|
|
if ($security_status['status']) {
|
|
echo '<span style="color: green;">✅ ' . esc_html($security_status['message']) . '</span>';
|
|
} else {
|
|
echo '<span style="color: red;">❌ ' . esc_html($security_status['message']) . '</span>';
|
|
}
|
|
?>
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<th scope="row"><?php _e('📦 Storage Support:', 'tigerstyle-life9'); ?></th>
|
|
<td><?php _e('Local + S3/MinIO architecture implemented', 'tigerstyle-life9'); ?></td>
|
|
</tr>
|
|
<tr>
|
|
<th scope="row"><?php _e('🛠️ Full Functionality:', 'tigerstyle-life9'); ?></th>
|
|
<td>
|
|
<?php if ($this->full_functionality): ?>
|
|
<span style="color: green;">✅ <?php _e('All systems go!', 'tigerstyle-life9'); ?></span>
|
|
<?php else: ?>
|
|
<span style="color: orange;">⚠️ <?php _e('Basic mode - dependencies loading', 'tigerstyle-life9'); ?></span>
|
|
<?php endif; ?>
|
|
</td>
|
|
</tr>
|
|
</table>
|
|
</div>
|
|
|
|
<div class="card">
|
|
<h2 class="title"><?php _e('🎯 Quick Actions', 'tigerstyle-life9'); ?></h2>
|
|
<p>
|
|
<a href="<?php echo admin_url('admin.php?page=tigerstyle-life9-complete-backup'); ?>" class="button button-primary">
|
|
💾 <?php _e('Save a Life (Create Backup)', 'tigerstyle-life9'); ?>
|
|
</a>
|
|
<a href="<?php echo admin_url('admin.php?page=tigerstyle-life9-complete-restore'); ?>" class="button">
|
|
🔄 <?php _e('Restore a Life', 'tigerstyle-life9'); ?>
|
|
</a>
|
|
<a href="<?php echo admin_url('admin.php?page=tigerstyle-life9-complete-settings'); ?>" class="button">
|
|
⚙️ <?php _e('Territory Settings', 'tigerstyle-life9'); ?>
|
|
</a>
|
|
</p>
|
|
</div>
|
|
|
|
<div class="tigerstyle-cat-success">
|
|
<p><strong>🎉 S3/MinIO Ready:</strong> <?php _e('Complete backup infrastructure with cat-themed S3 storage class implemented! Ready for your MinIO testing.', 'tigerstyle-life9'); ?></p>
|
|
</div>
|
|
</div>
|
|
<?php
|
|
}
|
|
|
|
/**
|
|
* Render backup page
|
|
*/
|
|
public function render_backup_page() {
|
|
error_log('TigerStyle Life9: render_backup_page() called with URL: ' . $_SERVER['REQUEST_URI']);
|
|
|
|
// Download requests are now handled earlier in admin_init hook
|
|
|
|
// Handle form submissions
|
|
if (isset($_POST['create_backup'])) {
|
|
$this->handle_backup_creation();
|
|
}
|
|
|
|
if (isset($_POST['upload_backup'])) {
|
|
$this->handle_backup_upload();
|
|
}
|
|
|
|
// Get existing backups
|
|
$backups = $this->get_existing_backups();
|
|
?>
|
|
<div class="wrap tigerstyle-life9-complete">
|
|
<h1 class="wp-heading-inline">
|
|
<span class="tigerstyle-icon">💾</span>
|
|
<?php _e('Save a Life - Complete Backup System', 'tigerstyle-life9'); ?>
|
|
</h1>
|
|
|
|
<div class="tigerstyle-cat-message">
|
|
<p><strong>🐱 Ready for action!</strong> <?php _e('Create new backups or upload existing backup files for testing. Full S3/MinIO integration active!', 'tigerstyle-life9'); ?></p>
|
|
</div>
|
|
|
|
<!-- Create New Backup Section -->
|
|
<div class="card">
|
|
<h2><span class="tigerstyle-icon">🏗️</span><?php _e('Create New Backup', 'tigerstyle-life9'); ?></h2>
|
|
<form method="post" action="">
|
|
<?php wp_nonce_field('tigerstyle_backup_create', 'backup_nonce'); ?>
|
|
<table class="form-table">
|
|
<tr>
|
|
<th scope="row"><?php _e('🐱 Backup Name:', 'tigerstyle-life9'); ?></th>
|
|
<td>
|
|
<input type="text" name="backup_name" value="<?php echo esc_attr(get_bloginfo('name') . '-' . date('Y-m-d-H-i')); ?>" class="regular-text" />
|
|
<p class="description"><?php _e('Give your backup a memorable cat-like name!', 'tigerstyle-life9'); ?></p>
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<th scope="row"><?php _e('📁 Include Files:', 'tigerstyle-life9'); ?></th>
|
|
<td>
|
|
<label><input type="checkbox" name="include_files" value="1" checked /> <?php _e('WordPress files & uploads', 'tigerstyle-life9'); ?></label>
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<th scope="row"><?php _e('🗄️ Include Database:', 'tigerstyle-life9'); ?></th>
|
|
<td>
|
|
<label><input type="checkbox" name="include_database" value="1" checked /> <?php _e('Complete database export', 'tigerstyle-life9'); ?></label>
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<th scope="row"><?php _e('☁️ Storage Location:', 'tigerstyle-life9'); ?></th>
|
|
<td>
|
|
<select name="storage_type">
|
|
<option value="local"><?php _e('🏠 Local Storage', 'tigerstyle-life9'); ?></option>
|
|
<option value="s3"><?php _e('☁️ S3/MinIO Storage', 'tigerstyle-life9'); ?></option>
|
|
</select>
|
|
</td>
|
|
</tr>
|
|
</table>
|
|
<p class="submit">
|
|
<input type="submit" name="create_backup" class="button button-primary" value="<?php esc_attr_e('🐾 Create Backup Now!', 'tigerstyle-life9'); ?>" />
|
|
</p>
|
|
</form>
|
|
</div>
|
|
|
|
<!-- Upload Backup for Testing Section -->
|
|
<div class="card">
|
|
<h2><span class="tigerstyle-icon">📤</span><?php _e('Upload Backup File (for testing)', 'tigerstyle-life9'); ?></h2>
|
|
<p><?php _e('Upload an existing backup file to test the restore functionality.', 'tigerstyle-life9'); ?></p>
|
|
<form method="post" action="" enctype="multipart/form-data">
|
|
<?php wp_nonce_field('tigerstyle_backup_upload', 'upload_nonce'); ?>
|
|
<table class="form-table">
|
|
<tr>
|
|
<th scope="row"><?php _e('🎯 Backup File:', 'tigerstyle-life9'); ?></th>
|
|
<td>
|
|
<input type="file" name="backup_file" accept=".zip,.tar,.gz,.xml,.sql" />
|
|
<p class="description"><?php _e('Supported formats: .zip, .tar, .gz, .xml, .sql (Max: 512MB)', 'tigerstyle-life9'); ?></p>
|
|
</td>
|
|
</tr>
|
|
</table>
|
|
<p class="submit">
|
|
<input type="submit" name="upload_backup" class="button" value="<?php esc_attr_e('📥 Upload Backup File', 'tigerstyle-life9'); ?>" />
|
|
</p>
|
|
</form>
|
|
</div>
|
|
|
|
<!-- Existing Backups Section -->
|
|
<div class="card">
|
|
<h2><span class="tigerstyle-icon">📋</span><?php _e('Existing Backups', 'tigerstyle-life9'); ?></h2>
|
|
<?php if (!empty($backups)): ?>
|
|
<table class="wp-list-table widefat fixed striped">
|
|
<thead>
|
|
<tr>
|
|
<th><?php _e('🐱 Backup Name', 'tigerstyle-life9'); ?></th>
|
|
<th><?php _e('📅 Created', 'tigerstyle-life9'); ?></th>
|
|
<th><?php _e('📏 Size', 'tigerstyle-life9'); ?></th>
|
|
<th><?php _e('📍 Location', 'tigerstyle-life9'); ?></th>
|
|
<th><?php _e('🎯 Actions', 'tigerstyle-life9'); ?></th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<?php foreach ($backups as $backup): ?>
|
|
<tr>
|
|
<td><strong><?php echo esc_html($backup['name']); ?></strong></td>
|
|
<td><?php echo esc_html($backup['date']); ?></td>
|
|
<td><?php echo esc_html($backup['size']); ?></td>
|
|
<td><?php echo esc_html($backup['location']); ?></td>
|
|
<td>
|
|
<a href="<?php echo wp_nonce_url(admin_url('admin.php?page=tigerstyle-life9-complete-restore&backup=' . urlencode($backup['id'])), 'restore_backup'); ?>" class="button button-small">🔄 Restore</a>
|
|
<a href="<?php echo wp_nonce_url(admin_url('admin.php?page=tigerstyle-life9-complete-backup&download=' . urlencode($backup['id'])), 'download_backup'); ?>" class="button button-small">⬇️ Download</a>
|
|
<button type="button" class="button button-small tigerstyle-generate-link" data-backup-id="<?php echo esc_attr($backup['id']); ?>" data-backup-name="<?php echo esc_attr($backup['name']); ?>">🔗 Share Link</button>
|
|
</td>
|
|
</tr>
|
|
<?php endforeach; ?>
|
|
</tbody>
|
|
</table>
|
|
<?php else: ?>
|
|
<div class="tigerstyle-cat-message">
|
|
<p><strong>😸 No backups yet!</strong> <?php _e('Create your first backup or upload a test file to get started.', 'tigerstyle-life9'); ?></p>
|
|
</div>
|
|
<?php endif; ?>
|
|
</div>
|
|
|
|
<!-- Infrastructure Status -->
|
|
<div class="card">
|
|
<h2><span class="tigerstyle-icon">🔧</span><?php _e('Infrastructure Status', 'tigerstyle-life9'); ?></h2>
|
|
<?php
|
|
$status_checks = $this->get_infrastructure_status();
|
|
$all_good = array_reduce($status_checks, function($carry, $item) {
|
|
return $carry && $item['status'];
|
|
}, true);
|
|
?>
|
|
<div class="<?php echo $all_good ? 'tigerstyle-cat-success' : 'tigerstyle-cat-warning'; ?>">
|
|
<ul>
|
|
<?php foreach ($status_checks as $check): ?>
|
|
<li><?php echo $check['status'] ? '✅' : '❌'; ?> <strong><?php echo esc_html($check['name']); ?>:</strong> <?php echo esc_html($check['message']); ?></li>
|
|
<?php endforeach; ?>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Time-Limited Download Link Modal -->
|
|
<div id="tigerstyle-link-modal" class="tigerstyle-modal" style="display: none;">
|
|
<div class="tigerstyle-modal-content">
|
|
<div class="tigerstyle-modal-header">
|
|
<h3>🔗 Generate Time-Limited Download Link</h3>
|
|
<span class="tigerstyle-modal-close">×</span>
|
|
</div>
|
|
<div class="tigerstyle-modal-body">
|
|
<p><strong>🐱 Share your backup securely!</strong> Generate a time-limited download link with YouTube-style short ID.</p>
|
|
|
|
<table class="form-table">
|
|
<tr>
|
|
<th><label for="tigerstyle-expires-minutes">🕐 Expires in (minutes):</label></th>
|
|
<td>
|
|
<select id="tigerstyle-expires-minutes">
|
|
<option value="5">5 minutes (Quick)</option>
|
|
<option value="10" selected>10 minutes (Default)</option>
|
|
<option value="30">30 minutes (Extended)</option>
|
|
<option value="60">1 hour (Long-term)</option>
|
|
<option value="120">2 hours (Very long)</option>
|
|
<option value="1440">24 hours (Maximum)</option>
|
|
</select>
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<th><label for="tigerstyle-max-downloads">📊 Max downloads:</label></th>
|
|
<td>
|
|
<select id="tigerstyle-max-downloads">
|
|
<option value="1" selected>1 download (Single use)</option>
|
|
<option value="3">3 downloads (Limited)</option>
|
|
<option value="5">5 downloads (Moderate)</option>
|
|
<option value="10">10 downloads (Generous)</option>
|
|
</select>
|
|
</td>
|
|
</tr>
|
|
</table>
|
|
|
|
<div class="tigerstyle-link-result" style="display: none;">
|
|
<h4>🎉 Link Generated Successfully!</h4>
|
|
<div class="tigerstyle-link-box">
|
|
<input type="text" id="tigerstyle-generated-url" readonly style="width: 100%; padding: 8px; margin-bottom: 10px;">
|
|
<div class="tigerstyle-link-actions">
|
|
<button type="button" id="tigerstyle-copy-link" class="button button-primary">📋 Copy Link</button>
|
|
<button type="button" id="tigerstyle-test-link" class="button">🔗 Test Link</button>
|
|
</div>
|
|
</div>
|
|
<div class="tigerstyle-link-details">
|
|
<p><strong>Token:</strong> <code id="tigerstyle-link-token"></code></p>
|
|
<p><strong>Expires:</strong> <span id="tigerstyle-link-expires"></span></p>
|
|
<p><strong>Max Downloads:</strong> <span id="tigerstyle-link-max"></span></p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="tigerstyle-modal-footer">
|
|
<button type="button" id="tigerstyle-generate-btn" class="button button-primary">🚀 Generate Link</button>
|
|
<button type="button" class="button tigerstyle-modal-close">Cancel</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<style>
|
|
/* Improved Backup Table Styling */
|
|
.wp-list-table.widefat.fixed.striped {
|
|
border-radius: 8px;
|
|
overflow: hidden;
|
|
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
|
margin-top: 15px;
|
|
}
|
|
|
|
.wp-list-table th {
|
|
background: linear-gradient(135deg, #2563eb 0%, #0891b2 100%);
|
|
color: white;
|
|
font-weight: 600;
|
|
padding: 12px 15px;
|
|
border: none;
|
|
}
|
|
|
|
.wp-list-table td {
|
|
padding: 15px;
|
|
vertical-align: middle;
|
|
border-bottom: 1px solid #f1f1f1;
|
|
}
|
|
|
|
.wp-list-table tbody tr:hover {
|
|
background-color: #f8f9ff;
|
|
}
|
|
|
|
/* Action Buttons Styling */
|
|
.wp-list-table .button {
|
|
margin-right: 5px;
|
|
margin-bottom: 5px;
|
|
padding: 6px 12px;
|
|
border-radius: 4px;
|
|
font-size: 12px;
|
|
text-decoration: none;
|
|
display: inline-block;
|
|
transition: all 0.2s ease;
|
|
}
|
|
|
|
.wp-list-table .button:hover {
|
|
transform: translateY(-1px);
|
|
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
|
}
|
|
|
|
/* Responsive Actions Column */
|
|
.wp-list-table td:last-child {
|
|
white-space: nowrap;
|
|
min-width: 200px;
|
|
}
|
|
|
|
/* Wider Cards for Better Use of Viewport */
|
|
.wrap .card {
|
|
max-width: none;
|
|
margin-bottom: 20px;
|
|
}
|
|
|
|
/* Better responsive table */
|
|
@media (max-width: 782px) {
|
|
.wp-list-table .button {
|
|
display: block;
|
|
width: 100%;
|
|
text-align: center;
|
|
margin-bottom: 3px;
|
|
}
|
|
|
|
.wp-list-table td:last-child {
|
|
white-space: normal;
|
|
}
|
|
}
|
|
|
|
.tigerstyle-modal {
|
|
position: fixed;
|
|
z-index: 100000;
|
|
left: 0;
|
|
top: 0;
|
|
width: 100%;
|
|
height: 100%;
|
|
background-color: rgba(0,0,0,0.5);
|
|
}
|
|
|
|
.tigerstyle-modal-content {
|
|
background-color: #fefefe;
|
|
margin: 5% auto;
|
|
padding: 0;
|
|
border: 1px solid #888;
|
|
border-radius: 8px;
|
|
width: 90%;
|
|
max-width: 600px;
|
|
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
|
}
|
|
|
|
.tigerstyle-modal-header {
|
|
padding: 20px;
|
|
background: linear-gradient(135deg, #2563eb 0%, #0891b2 100%);
|
|
color: white;
|
|
border-radius: 8px 8px 0 0;
|
|
position: relative;
|
|
}
|
|
|
|
.tigerstyle-modal-header h3 {
|
|
margin: 0;
|
|
font-size: 18px;
|
|
}
|
|
|
|
.tigerstyle-modal-close {
|
|
color: white;
|
|
float: right;
|
|
font-size: 28px;
|
|
font-weight: bold;
|
|
position: absolute;
|
|
right: 15px;
|
|
top: 10px;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.tigerstyle-modal-close:hover {
|
|
opacity: 0.7;
|
|
}
|
|
|
|
.tigerstyle-modal-body {
|
|
padding: 20px;
|
|
}
|
|
|
|
.tigerstyle-modal-footer {
|
|
padding: 20px;
|
|
text-align: right;
|
|
border-top: 1px solid #eee;
|
|
background-color: #f9f9f9;
|
|
border-radius: 0 0 8px 8px;
|
|
}
|
|
|
|
.tigerstyle-link-result {
|
|
margin-top: 20px;
|
|
padding: 15px;
|
|
background-color: #d4edda;
|
|
border: 1px solid #c3e6cb;
|
|
border-radius: 5px;
|
|
}
|
|
|
|
.tigerstyle-link-box {
|
|
background-color: white;
|
|
padding: 15px;
|
|
border-radius: 5px;
|
|
margin: 10px 0;
|
|
}
|
|
|
|
.tigerstyle-link-actions {
|
|
display: flex;
|
|
gap: 10px;
|
|
}
|
|
|
|
.tigerstyle-link-details {
|
|
margin-top: 15px;
|
|
font-size: 12px;
|
|
}
|
|
|
|
.tigerstyle-link-details code {
|
|
background-color: #f1f1f1;
|
|
padding: 2px 4px;
|
|
border-radius: 3px;
|
|
font-family: monospace;
|
|
}
|
|
</style>
|
|
|
|
<script>
|
|
jQuery(document).ready(function($) {
|
|
let currentBackupId = '';
|
|
let currentBackupName = '';
|
|
|
|
// Add some interactivity for better user experience
|
|
$('#storage_type').change(function() {
|
|
if ($(this).val() === 's3') {
|
|
$('.tigerstyle-cat-message').html('<p><strong>☁️ S3/MinIO Mode:</strong> Your backup will be stored in the cloud with cat-themed folder organization!</p>');
|
|
} else {
|
|
$('.tigerstyle-cat-message').html('<p><strong>🏠 Local Mode:</strong> Your backup will be stored locally on the server.</p>');
|
|
}
|
|
});
|
|
|
|
// Handle Generate Link button clicks
|
|
$('.tigerstyle-generate-link').click(function() {
|
|
currentBackupId = $(this).data('backup-id');
|
|
currentBackupName = $(this).data('backup-name');
|
|
|
|
// Reset modal state
|
|
$('.tigerstyle-link-result').hide();
|
|
$('#tigerstyle-generated-url').val('');
|
|
|
|
// Show modal
|
|
$('#tigerstyle-link-modal').show();
|
|
});
|
|
|
|
// Handle modal close
|
|
$('.tigerstyle-modal-close').click(function() {
|
|
$('#tigerstyle-link-modal').hide();
|
|
});
|
|
|
|
// Close modal when clicking outside
|
|
$(window).click(function(e) {
|
|
if (e.target.id === 'tigerstyle-link-modal') {
|
|
$('#tigerstyle-link-modal').hide();
|
|
}
|
|
});
|
|
|
|
// Handle Generate Link button in modal
|
|
$('#tigerstyle-generate-btn').click(function() {
|
|
const $btn = $(this);
|
|
const originalText = $btn.text();
|
|
|
|
$btn.text('🔄 Generating...').prop('disabled', true);
|
|
|
|
$.ajax({
|
|
url: ajaxurl,
|
|
type: 'POST',
|
|
data: {
|
|
action: 'generate_download_link',
|
|
backup_id: currentBackupId,
|
|
expires_minutes: $('#tigerstyle-expires-minutes').val(),
|
|
max_downloads: $('#tigerstyle-max-downloads').val(),
|
|
nonce: '<?php echo wp_create_nonce('tigerstyle_generate_link'); ?>'
|
|
},
|
|
success: function(response) {
|
|
if (response.success) {
|
|
// Display the generated link
|
|
$('#tigerstyle-generated-url').val(response.data.url);
|
|
$('#tigerstyle-link-token').text(response.data.token);
|
|
$('#tigerstyle-link-expires').text(new Date(response.data.expires_at).toLocaleString());
|
|
$('#tigerstyle-link-max').text(response.data.max_downloads);
|
|
|
|
$('.tigerstyle-link-result').show();
|
|
|
|
// Auto-select the URL for easy copying
|
|
$('#tigerstyle-generated-url').select();
|
|
} else {
|
|
alert('❌ Failed to generate link: ' + response.data);
|
|
}
|
|
},
|
|
error: function() {
|
|
alert('❌ Network error occurred while generating link.');
|
|
},
|
|
complete: function() {
|
|
$btn.text(originalText).prop('disabled', false);
|
|
}
|
|
});
|
|
});
|
|
|
|
// Handle Copy Link button
|
|
$('#tigerstyle-copy-link').click(function() {
|
|
const urlField = $('#tigerstyle-generated-url')[0];
|
|
urlField.select();
|
|
urlField.setSelectionRange(0, 99999); // For mobile devices
|
|
|
|
try {
|
|
document.execCommand('copy');
|
|
$(this).text('✅ Copied!').removeClass('button-primary').addClass('button-secondary');
|
|
|
|
setTimeout(() => {
|
|
$(this).text('📋 Copy Link').removeClass('button-secondary').addClass('button-primary');
|
|
}, 2000);
|
|
} catch(err) {
|
|
alert('❌ Failed to copy to clipboard. Please copy manually.');
|
|
}
|
|
});
|
|
|
|
// Handle Test Link button
|
|
$('#tigerstyle-test-link').click(function() {
|
|
const url = $('#tigerstyle-generated-url').val();
|
|
if (url) {
|
|
window.open(url, '_blank');
|
|
}
|
|
});
|
|
});
|
|
</script>
|
|
<?php
|
|
}
|
|
|
|
/**
|
|
* Render restore page
|
|
*/
|
|
public function render_restore_page() {
|
|
// Handle restore action
|
|
if (isset($_POST['restore_backup'])) {
|
|
$this->handle_backup_restore();
|
|
}
|
|
|
|
// Get available backups
|
|
$backups = $this->get_existing_backups();
|
|
$selected_backup = isset($_GET['backup']) ? sanitize_text_field($_GET['backup']) : '';
|
|
?>
|
|
<div class="wrap tigerstyle-life9-complete">
|
|
<h1 class="wp-heading-inline">
|
|
<span class="tigerstyle-icon">🔄</span>
|
|
<?php _e('Restore a Life - Complete Recovery System', 'tigerstyle-life9'); ?>
|
|
</h1>
|
|
|
|
<div class="tigerstyle-cat-message">
|
|
<p><strong>🐱 Nine lives, infinite possibilities!</strong> <?php _e('Restore your WordPress site from any backup with full S3/MinIO support.', 'tigerstyle-life9'); ?></p>
|
|
</div>
|
|
|
|
<?php if (!empty($backups)): ?>
|
|
<!-- Restore Backup Section -->
|
|
<div class="card">
|
|
<h2><span class="tigerstyle-icon">🔄</span><?php _e('Restore from Backup', 'tigerstyle-life9'); ?></h2>
|
|
<form method="post" action="">
|
|
<?php wp_nonce_field('tigerstyle_restore_backup', 'restore_nonce'); ?>
|
|
<table class="form-table">
|
|
<tr>
|
|
<th scope="row"><?php _e('🎯 Select Backup:', 'tigerstyle-life9'); ?></th>
|
|
<td>
|
|
<select name="backup_id" id="backup_select" required>
|
|
<option value=""><?php _e('-- Choose a backup to restore --', 'tigerstyle-life9'); ?></option>
|
|
<?php foreach ($backups as $backup): ?>
|
|
<option value="<?php echo esc_attr($backup['id']); ?>" <?php selected($selected_backup, $backup['id']); ?>>
|
|
<?php echo esc_html($backup['name'] . ' - ' . $backup['date'] . ' (' . $backup['size'] . ')'); ?>
|
|
</option>
|
|
<?php endforeach; ?>
|
|
</select>
|
|
<p class="description"><?php _e('Choose which backup to restore from.', 'tigerstyle-life9'); ?></p>
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<th scope="row"><?php _e('📁 Restore Files:', 'tigerstyle-life9'); ?></th>
|
|
<td>
|
|
<label><input type="checkbox" name="restore_files" value="1" checked /> <?php _e('WordPress files & uploads', 'tigerstyle-life9'); ?></label>
|
|
<p class="description"><?php _e('Restore all WordPress files, themes, plugins, and uploads.', 'tigerstyle-life9'); ?></p>
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<th scope="row"><?php _e('🗄️ Restore Database:', 'tigerstyle-life9'); ?></th>
|
|
<td>
|
|
<label><input type="checkbox" name="restore_database" value="1" checked /> <?php _e('Complete database restoration', 'tigerstyle-life9'); ?></label>
|
|
<p class="description"><?php _e('Restore all posts, pages, settings, and user data.', 'tigerstyle-life9'); ?></p>
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<th scope="row"><?php _e('⚠️ Safety Options:', 'tigerstyle-life9'); ?></th>
|
|
<td>
|
|
<label><input type="checkbox" name="create_backup_before_restore" value="1" checked /> <?php _e('Create backup before restore', 'tigerstyle-life9'); ?></label><br>
|
|
<label><input type="checkbox" name="validate_backup" value="1" checked /> <?php _e('Validate backup integrity', 'tigerstyle-life9'); ?></label>
|
|
<p class="description"><?php _e('Safety first! Always create a backup before restoring.', 'tigerstyle-life9'); ?></p>
|
|
</td>
|
|
</tr>
|
|
</table>
|
|
|
|
<div class="tigerstyle-cat-error" style="display: none;" id="restore-warning">
|
|
<p><strong>⚠️ WARNING:</strong> <?php _e('This will replace your current WordPress installation! Make sure you have a recent backup.', 'tigerstyle-life9'); ?></p>
|
|
</div>
|
|
|
|
<p class="submit">
|
|
<input type="hidden" name="restore_backup" value="1" />
|
|
<input type="submit" class="button button-primary" value="<?php esc_attr_e('🔄 Restore This Life!', 'tigerstyle-life9'); ?>" onclick="return confirm('<?php _e('Are you sure you want to restore this backup? This will replace your current site!', 'tigerstyle-life9'); ?>');" />
|
|
<a href="<?php echo admin_url('admin.php?page=tigerstyle-life9-complete-backup'); ?>" class="button"><?php _e('↩️ Back to Backups', 'tigerstyle-life9'); ?></a>
|
|
</p>
|
|
</form>
|
|
</div>
|
|
|
|
<!-- Backup Details Section -->
|
|
<div class="card" id="backup-details" style="display: none;">
|
|
<h2><span class="tigerstyle-icon">📋</span><?php _e('Backup Details', 'tigerstyle-life9'); ?></h2>
|
|
<div id="backup-info">
|
|
<!-- Will be populated by JavaScript -->
|
|
</div>
|
|
</div>
|
|
|
|
<?php else: ?>
|
|
<!-- No Backups Available -->
|
|
<div class="card">
|
|
<h2><span class="tigerstyle-icon">😿</span><?php _e('No Backups Available', 'tigerstyle-life9'); ?></h2>
|
|
<div class="tigerstyle-cat-message">
|
|
<p><strong>😸 No backups found!</strong> <?php _e('You need to create or upload a backup first before you can restore.', 'tigerstyle-life9'); ?></p>
|
|
<p>
|
|
<a href="<?php echo admin_url('admin.php?page=tigerstyle-life9-complete-backup'); ?>" class="button button-primary">💾 <?php _e('Create Your First Backup', 'tigerstyle-life9'); ?></a>
|
|
</p>
|
|
</div>
|
|
</div>
|
|
<?php endif; ?>
|
|
|
|
<!-- Available Backups List -->
|
|
<?php if (!empty($backups)): ?>
|
|
<div class="card">
|
|
<h2><span class="tigerstyle-icon">📋</span><?php _e('Available Backups', 'tigerstyle-life9'); ?></h2>
|
|
<table class="wp-list-table widefat fixed striped">
|
|
<thead>
|
|
<tr>
|
|
<th><?php _e('🐱 Backup Name', 'tigerstyle-life9'); ?></th>
|
|
<th><?php _e('📅 Created', 'tigerstyle-life9'); ?></th>
|
|
<th><?php _e('📏 Size', 'tigerstyle-life9'); ?></th>
|
|
<th><?php _e('📍 Location', 'tigerstyle-life9'); ?></th>
|
|
<th><?php _e('🎯 Actions', 'tigerstyle-life9'); ?></th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<?php foreach ($backups as $backup): ?>
|
|
<tr>
|
|
<td><strong><?php echo esc_html($backup['name']); ?></strong></td>
|
|
<td><?php echo esc_html($backup['date']); ?></td>
|
|
<td><?php echo esc_html($backup['size']); ?></td>
|
|
<td><?php echo esc_html($backup['location']); ?></td>
|
|
<td>
|
|
<a href="<?php echo admin_url('admin.php?page=tigerstyle-life9-complete-restore&backup=' . urlencode($backup['id'])); ?>" class="button button-small">🎯 <?php _e('Select', 'tigerstyle-life9'); ?></a>
|
|
<a href="<?php echo wp_nonce_url(admin_url('admin.php?page=tigerstyle-life9-complete-backup&download=' . urlencode($backup['id'])), 'download_backup'); ?>" class="button button-small">⬇️ <?php _e('Download', 'tigerstyle-life9'); ?></a>
|
|
</td>
|
|
</tr>
|
|
<?php endforeach; ?>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
<?php endif; ?>
|
|
|
|
<!-- Restore Process Info -->
|
|
<div class="card">
|
|
<h2><span class="tigerstyle-icon">🔧</span><?php _e('Restore Process Information', 'tigerstyle-life9'); ?></h2>
|
|
<div class="tigerstyle-cat-success">
|
|
<p><strong>🐱 How Restore Works:</strong></p>
|
|
<ol>
|
|
<li><strong>🔍 Validation:</strong> <?php _e('Backup file integrity is verified', 'tigerstyle-life9'); ?></li>
|
|
<li><strong>💾 Safety Backup:</strong> <?php _e('Current site is backed up before restore', 'tigerstyle-life9'); ?></li>
|
|
<li><strong>📁 File Extraction:</strong> <?php _e('Backup files are extracted to temporary location', 'tigerstyle-life9'); ?></li>
|
|
<li><strong>🗄️ Database Restore:</strong> <?php _e('Database is imported (if selected)', 'tigerstyle-life9'); ?></li>
|
|
<li><strong>📋 File Replacement:</strong> <?php _e('WordPress files are replaced (if selected)', 'tigerstyle-life9'); ?></li>
|
|
<li><strong>🔄 Cleanup:</strong> <?php _e('Temporary files are cleaned up', 'tigerstyle-life9'); ?></li>
|
|
<li><strong>✅ Verification:</strong> <?php _e('Site functionality is verified', 'tigerstyle-life9'); ?></li>
|
|
</ol>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
jQuery(document).ready(function($) {
|
|
// Show backup details when a backup is selected
|
|
$('#backup_select').change(function() {
|
|
var backupId = $(this).val();
|
|
if (backupId) {
|
|
$('#restore-warning').show();
|
|
$('#backup-details').show();
|
|
// Here you could add AJAX call to get detailed backup info
|
|
$('#backup-info').html('<p><strong>Selected:</strong> ' + $(this).find('option:selected').text() + '</p>');
|
|
} else {
|
|
$('#restore-warning').hide();
|
|
$('#backup-details').hide();
|
|
}
|
|
});
|
|
|
|
// Auto-select backup if provided in URL
|
|
<?php if ($selected_backup): ?>
|
|
$('#backup_select').val('<?php echo esc_js($selected_backup); ?>').trigger('change');
|
|
<?php endif; ?>
|
|
});
|
|
</script>
|
|
<?php
|
|
}
|
|
|
|
/**
|
|
* Render settings page
|
|
*/
|
|
public function render_settings_page() {
|
|
// Handle form submissions
|
|
if (isset($_POST['save_schedule_settings'])) {
|
|
$this->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();
|
|
?>
|
|
<div class="wrap tigerstyle-life9-complete">
|
|
<h1 class="wp-heading-inline">
|
|
<span class="tigerstyle-icon">⚙️</span>
|
|
<?php _e('Territory Settings', 'tigerstyle-life9'); ?>
|
|
</h1>
|
|
|
|
<div class="tigerstyle-cat-message">
|
|
<p><strong>🐱 Configure your automated backup territory!</strong> <?php _e('Set up recurring backups, storage locations, and retention policies for your site\'s nine lives.', 'tigerstyle-life9'); ?></p>
|
|
</div>
|
|
|
|
<!-- Territory Status -->
|
|
<div class="card">
|
|
<h2><span class="tigerstyle-icon">📊</span><?php _e('Territory Status', 'tigerstyle-life9'); ?></h2>
|
|
<table class="form-table">
|
|
<tr>
|
|
<th scope="row"><?php _e('🔄 Automated Backups:', 'tigerstyle-life9'); ?></th>
|
|
<td>
|
|
<?php if ($schedule_settings['enabled']): ?>
|
|
<span style="color: green;">✅ <?php _e('Enabled', 'tigerstyle-life9'); ?></span>
|
|
<span style="margin-left: 1em;">📅 <?php echo esc_html(ucfirst($schedule_settings['frequency'])); ?></span>
|
|
<?php if ($next_backup): ?>
|
|
<br><small style="color: #666;">⏰ <?php _e('Next backup:', 'tigerstyle-life9'); ?> <?php echo esc_html($next_backup); ?></small>
|
|
<?php endif; ?>
|
|
<?php else: ?>
|
|
<span style="color: orange;">⏸️ <?php _e('Disabled', 'tigerstyle-life9'); ?></span>
|
|
<?php endif; ?>
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<th scope="row"><?php _e('☁️ Cloud Storage:', 'tigerstyle-life9'); ?></th>
|
|
<td>
|
|
<?php if ($storage_settings['s3_enabled']): ?>
|
|
<span style="color: green;">✅ <?php _e('Configured', 'tigerstyle-life9'); ?></span>
|
|
<?php if (!empty($storage_settings['s3_bucket'])): ?>
|
|
<span style="margin-left: 1em;">🪣 <?php echo esc_html($storage_settings['s3_bucket']); ?></span>
|
|
<?php endif; ?>
|
|
<?php else: ?>
|
|
<span style="color: gray;">💾 <?php _e('Local storage only', 'tigerstyle-life9'); ?></span>
|
|
<?php endif; ?>
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<th scope="row"><?php _e('🗄️ WordPress Cron:', 'tigerstyle-life9'); ?></th>
|
|
<td>
|
|
<?php if (!defined('DISABLE_WP_CRON') || !DISABLE_WP_CRON): ?>
|
|
<span style="color: green;">✅ <?php _e('Active', 'tigerstyle-life9'); ?></span>
|
|
<?php else: ?>
|
|
<span style="color: red;">❌ <?php _e('Disabled in wp-config.php', 'tigerstyle-life9'); ?></span>
|
|
<?php endif; ?>
|
|
</td>
|
|
</tr>
|
|
</table>
|
|
</div>
|
|
|
|
<!-- Automated Backup Scheduling -->
|
|
<div class="card">
|
|
<h2><span class="tigerstyle-icon">🕐</span><?php _e('Automated Backup Scheduling', 'tigerstyle-life9'); ?></h2>
|
|
<form method="post" action="">
|
|
<?php wp_nonce_field('tigerstyle_schedule_settings', 'schedule_nonce'); ?>
|
|
<table class="form-table">
|
|
<tr>
|
|
<th scope="row"><?php _e('🔄 Enable Automated Backups:', 'tigerstyle-life9'); ?></th>
|
|
<td>
|
|
<label>
|
|
<input type="checkbox" name="schedule_enabled" value="1" <?php checked($schedule_settings['enabled']); ?> />
|
|
<?php _e('Automatically create backups on schedule', 'tigerstyle-life9'); ?>
|
|
</label>
|
|
<p class="description"><?php _e('When enabled, backups will be created automatically based on your schedule below.', 'tigerstyle-life9'); ?></p>
|
|
</td>
|
|
</tr>
|
|
<tr class="schedule-options" <?php echo !$schedule_settings['enabled'] ? 'style="opacity: 0.5;"' : ''; ?>>
|
|
<th scope="row"><?php _e('📅 Backup Frequency:', 'tigerstyle-life9'); ?></th>
|
|
<td>
|
|
<select name="schedule_frequency" id="schedule_frequency">
|
|
<option value="daily" <?php selected($schedule_settings['frequency'], 'daily'); ?>><?php _e('🌅 Daily', 'tigerstyle-life9'); ?></option>
|
|
<option value="weekly" <?php selected($schedule_settings['frequency'], 'weekly'); ?>><?php _e('📅 Weekly', 'tigerstyle-life9'); ?></option>
|
|
<option value="monthly" <?php selected($schedule_settings['frequency'], 'monthly'); ?>><?php _e('📆 Monthly', 'tigerstyle-life9'); ?></option>
|
|
</select>
|
|
<p class="description"><?php _e('How often should backups be created automatically?', 'tigerstyle-life9'); ?></p>
|
|
</td>
|
|
</tr>
|
|
<tr class="schedule-options" <?php echo !$schedule_settings['enabled'] ? 'style="opacity: 0.5;"' : ''; ?>>
|
|
<th scope="row"><?php _e('🕒 Backup Time:', 'tigerstyle-life9'); ?></th>
|
|
<td>
|
|
<select name="schedule_hour">
|
|
<?php for ($h = 0; $h < 24; $h++): ?>
|
|
<option value="<?php echo $h; ?>" <?php selected($schedule_settings['hour'], $h); ?>>
|
|
<?php echo sprintf('%02d:00 (%s)', $h, $h < 12 ? 'AM' : 'PM'); ?>
|
|
</option>
|
|
<?php endfor; ?>
|
|
</select>
|
|
<p class="description"><?php _e('What time should backups be created? (24-hour format)', 'tigerstyle-life9'); ?></p>
|
|
</td>
|
|
</tr>
|
|
<tr class="schedule-options weekly-only" style="<?php echo ($schedule_settings['frequency'] !== 'weekly' || !$schedule_settings['enabled']) ? 'display: none;' : ''; ?>">
|
|
<th scope="row"><?php _e('📅 Day of Week:', 'tigerstyle-life9'); ?></th>
|
|
<td>
|
|
<select name="schedule_day_of_week">
|
|
<option value="0" <?php selected($schedule_settings['day_of_week'], 0); ?>><?php _e('Sunday', 'tigerstyle-life9'); ?></option>
|
|
<option value="1" <?php selected($schedule_settings['day_of_week'], 1); ?>><?php _e('Monday', 'tigerstyle-life9'); ?></option>
|
|
<option value="2" <?php selected($schedule_settings['day_of_week'], 2); ?>><?php _e('Tuesday', 'tigerstyle-life9'); ?></option>
|
|
<option value="3" <?php selected($schedule_settings['day_of_week'], 3); ?>><?php _e('Wednesday', 'tigerstyle-life9'); ?></option>
|
|
<option value="4" <?php selected($schedule_settings['day_of_week'], 4); ?>><?php _e('Thursday', 'tigerstyle-life9'); ?></option>
|
|
<option value="5" <?php selected($schedule_settings['day_of_week'], 5); ?>><?php _e('Friday', 'tigerstyle-life9'); ?></option>
|
|
<option value="6" <?php selected($schedule_settings['day_of_week'], 6); ?>><?php _e('Saturday', 'tigerstyle-life9'); ?></option>
|
|
</select>
|
|
<p class="description"><?php _e('Which day of the week should weekly backups be created?', 'tigerstyle-life9'); ?></p>
|
|
</td>
|
|
</tr>
|
|
<tr class="schedule-options monthly-only" style="<?php echo ($schedule_settings['frequency'] !== 'monthly' || !$schedule_settings['enabled']) ? 'display: none;' : ''; ?>">
|
|
<th scope="row"><?php _e('📆 Day of Month:', 'tigerstyle-life9'); ?></th>
|
|
<td>
|
|
<select name="schedule_day_of_month">
|
|
<?php for ($d = 1; $d <= 28; $d++): ?>
|
|
<option value="<?php echo $d; ?>" <?php selected($schedule_settings['day_of_month'], $d); ?>>
|
|
<?php echo $d . ($d == 1 ? 'st' : ($d == 2 ? 'nd' : ($d == 3 ? 'rd' : 'th'))); ?>
|
|
</option>
|
|
<?php endfor; ?>
|
|
</select>
|
|
<p class="description"><?php _e('Which day of the month should monthly backups be created? (Days 1-28 available for reliability)', 'tigerstyle-life9'); ?></p>
|
|
</td>
|
|
</tr>
|
|
<tr class="schedule-options" <?php echo !$schedule_settings['enabled'] ? 'style="opacity: 0.5;"' : ''; ?>>
|
|
<th scope="row"><?php _e('📦 Backup Content:', 'tigerstyle-life9'); ?></th>
|
|
<td>
|
|
<label><input type="checkbox" name="schedule_include_files" value="1" <?php checked($schedule_settings['include_files']); ?> /> <?php _e('WordPress files & uploads', 'tigerstyle-life9'); ?></label><br>
|
|
<label><input type="checkbox" name="schedule_include_database" value="1" <?php checked($schedule_settings['include_database']); ?> /> <?php _e('Complete database', 'tigerstyle-life9'); ?></label>
|
|
<p class="description"><?php _e('What should be included in automated backups?', 'tigerstyle-life9'); ?></p>
|
|
</td>
|
|
</tr>
|
|
<tr class="schedule-options" <?php echo !$schedule_settings['enabled'] ? 'style="opacity: 0.5;"' : ''; ?>>
|
|
<th scope="row"><?php _e('🗂️ Backup Retention:', 'tigerstyle-life9'); ?></th>
|
|
<td>
|
|
<select name="retention_count">
|
|
<option value="5" <?php selected($schedule_settings['retention_count'], 5); ?>><?php _e('Keep 5 backups', 'tigerstyle-life9'); ?></option>
|
|
<option value="10" <?php selected($schedule_settings['retention_count'], 10); ?>><?php _e('Keep 10 backups', 'tigerstyle-life9'); ?></option>
|
|
<option value="15" <?php selected($schedule_settings['retention_count'], 15); ?>><?php _e('Keep 15 backups', 'tigerstyle-life9'); ?></option>
|
|
<option value="30" <?php selected($schedule_settings['retention_count'], 30); ?>><?php _e('Keep 30 backups', 'tigerstyle-life9'); ?></option>
|
|
<option value="-1" <?php selected($schedule_settings['retention_count'], -1); ?>><?php _e('Keep all backups (unlimited)', 'tigerstyle-life9'); ?></option>
|
|
</select>
|
|
<p class="description"><?php _e('How many automated backups should be kept? Older backups will be automatically deleted.', 'tigerstyle-life9'); ?></p>
|
|
</td>
|
|
</tr>
|
|
</table>
|
|
|
|
<?php if ($next_backup): ?>
|
|
<div class="tigerstyle-cat-success">
|
|
<p><strong>🕐 Next Scheduled Backup:</strong> <?php echo esc_html($next_backup); ?></p>
|
|
</div>
|
|
<?php endif; ?>
|
|
|
|
<p class="submit">
|
|
<input type="submit" name="save_schedule_settings" class="button button-primary" value="<?php esc_attr_e('🐾 Save Schedule Settings', 'tigerstyle-life9'); ?>" />
|
|
</p>
|
|
</form>
|
|
</div>
|
|
|
|
<!-- S3/MinIO Storage Configuration -->
|
|
<div class="card">
|
|
<h2><span class="tigerstyle-icon">☁️</span><?php _e('S3/MinIO Storage Configuration', 'tigerstyle-life9'); ?></h2>
|
|
<form method="post" action="">
|
|
<?php wp_nonce_field('tigerstyle_storage_settings', 'storage_nonce'); ?>
|
|
<table class="form-table">
|
|
<tr>
|
|
<th scope="row"><?php _e('☁️ Enable Cloud Storage:', 'tigerstyle-life9'); ?></th>
|
|
<td>
|
|
<label>
|
|
<input type="checkbox" name="s3_enabled" value="1" <?php checked($storage_settings['s3_enabled']); ?> />
|
|
<?php _e('Store backups in S3/MinIO cloud storage', 'tigerstyle-life9'); ?>
|
|
</label>
|
|
<p class="description"><?php _e('When enabled, backups will also be uploaded to your S3-compatible storage.', 'tigerstyle-life9'); ?></p>
|
|
</td>
|
|
</tr>
|
|
<tr class="s3-options" <?php echo !$storage_settings['s3_enabled'] ? 'style="opacity: 0.5;"' : ''; ?>>
|
|
<th scope="row"><?php _e('🔗 S3 Endpoint:', 'tigerstyle-life9'); ?></th>
|
|
<td>
|
|
<input type="url" name="s3_endpoint" value="<?php echo esc_attr($storage_settings['s3_endpoint']); ?>" class="regular-text" placeholder="https://s3.amazonaws.com" />
|
|
<p class="description"><?php _e('S3 endpoint URL (leave blank for AWS S3, or enter MinIO endpoint)', 'tigerstyle-life9'); ?></p>
|
|
</td>
|
|
</tr>
|
|
<tr class="s3-options" <?php echo !$storage_settings['s3_enabled'] ? 'style="opacity: 0.5;"' : ''; ?>>
|
|
<th scope="row"><?php _e('🪣 Bucket Name:', 'tigerstyle-life9'); ?></th>
|
|
<td>
|
|
<input type="text" name="s3_bucket" value="<?php echo esc_attr($storage_settings['s3_bucket']); ?>" class="regular-text" placeholder="my-backup-bucket" />
|
|
<p class="description"><?php _e('S3 bucket name where backups will be stored', 'tigerstyle-life9'); ?></p>
|
|
</td>
|
|
</tr>
|
|
<tr class="s3-options" <?php echo !$storage_settings['s3_enabled'] ? 'style="opacity: 0.5;"' : ''; ?>>
|
|
<th scope="row"><?php _e('🔑 Access Key ID:', 'tigerstyle-life9'); ?></th>
|
|
<td>
|
|
<input type="text" name="s3_access_key" value="<?php echo esc_attr($storage_settings['s3_access_key']); ?>" class="regular-text" />
|
|
<p class="description"><?php _e('S3 Access Key ID for authentication', 'tigerstyle-life9'); ?></p>
|
|
</td>
|
|
</tr>
|
|
<tr class="s3-options" <?php echo !$storage_settings['s3_enabled'] ? 'style="opacity: 0.5;"' : ''; ?>>
|
|
<th scope="row"><?php _e('🔐 Secret Access Key:', 'tigerstyle-life9'); ?></th>
|
|
<td>
|
|
<input type="password" name="s3_secret_key" value="<?php echo esc_attr($storage_settings['s3_secret_key']); ?>" class="regular-text" />
|
|
<p class="description"><?php _e('S3 Secret Access Key for authentication', 'tigerstyle-life9'); ?></p>
|
|
</td>
|
|
</tr>
|
|
<tr class="s3-options" <?php echo !$storage_settings['s3_enabled'] ? 'style="opacity: 0.5;"' : ''; ?>>
|
|
<th scope="row"><?php _e('🌍 S3 Region:', 'tigerstyle-life9'); ?></th>
|
|
<td>
|
|
<input type="text" name="s3_region" value="<?php echo esc_attr($storage_settings['s3_region']); ?>" class="regular-text" placeholder="us-east-1" />
|
|
<p class="description"><?php _e('S3 region (e.g., us-east-1, eu-west-1)', 'tigerstyle-life9'); ?></p>
|
|
</td>
|
|
</tr>
|
|
</table>
|
|
|
|
<p class="submit">
|
|
<input type="submit" name="save_storage_settings" class="button button-primary" value="<?php esc_attr_e('☁️ Save Storage Settings', 'tigerstyle-life9'); ?>" />
|
|
</p>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
jQuery(document).ready(function($) {
|
|
// Handle schedule enable/disable
|
|
$('input[name="schedule_enabled"]').change(function() {
|
|
if ($(this).is(':checked')) {
|
|
$('.schedule-options').css('opacity', '1');
|
|
} else {
|
|
$('.schedule-options').css('opacity', '0.5');
|
|
}
|
|
});
|
|
|
|
// Handle frequency changes
|
|
$('#schedule_frequency').change(function() {
|
|
var frequency = $(this).val();
|
|
$('.weekly-only, .monthly-only').hide();
|
|
if (frequency === 'weekly') {
|
|
$('.weekly-only').show();
|
|
} else if (frequency === 'monthly') {
|
|
$('.monthly-only').show();
|
|
}
|
|
});
|
|
|
|
// Handle S3 enable/disable
|
|
$('input[name="s3_enabled"]').change(function() {
|
|
if ($(this).is(':checked')) {
|
|
$('.s3-options').css('opacity', '1');
|
|
} else {
|
|
$('.s3-options').css('opacity', '0.5');
|
|
}
|
|
});
|
|
|
|
// Initialize visibility
|
|
$('#schedule_frequency').trigger('change');
|
|
});
|
|
</script>
|
|
<?php
|
|
}
|
|
|
|
/**
|
|
* Handle schedule settings save
|
|
*/
|
|
private function handle_schedule_settings_save() {
|
|
// Verify nonce for security
|
|
if (!isset($_POST['schedule_nonce']) || !wp_verify_nonce($_POST['schedule_nonce'], 'tigerstyle_schedule_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 backup settings.', 'tigerstyle-life9'));
|
|
}
|
|
|
|
try {
|
|
$settings = [
|
|
'enabled' => 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 '<div class="notice notice-success is-dismissible">';
|
|
echo '<p><strong>🎉 ' . __('Schedule settings saved successfully!', 'tigerstyle-life9') . '</strong></p>';
|
|
echo '</div>';
|
|
});
|
|
|
|
} catch (Exception $e) {
|
|
add_action('admin_notices', function() use ($e) {
|
|
echo '<div class="notice notice-error is-dismissible">';
|
|
echo '<p><strong>😿 ' . sprintf(__('Settings save failed: %s', 'tigerstyle-life9'), $e->getMessage()) . '</strong></p>';
|
|
echo '</div>';
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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 '<div class="notice notice-success is-dismissible">';
|
|
echo '<p><strong>🎉 ' . __('Storage settings saved successfully!', 'tigerstyle-life9') . '</strong></p>';
|
|
echo '</div>';
|
|
});
|
|
|
|
} catch (Exception $e) {
|
|
add_action('admin_notices', function() use ($e) {
|
|
echo '<div class="notice notice-error is-dismissible">';
|
|
echo '<p><strong>😿 ' . sprintf(__('Storage settings save failed: %s', 'tigerstyle-life9'), $e->getMessage()) . '</strong></p>';
|
|
echo '</div>';
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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 '<div class="notice notice-success is-dismissible">';
|
|
echo '<p><strong>🎉 ' . sprintf(__('Backup "%s" created successfully!', 'tigerstyle-life9'), $backup_filename) . '</strong></p>';
|
|
echo '</div>';
|
|
});
|
|
|
|
} catch (Exception $e) {
|
|
add_action('admin_notices', function() use ($e) {
|
|
echo '<div class="notice notice-error is-dismissible">';
|
|
echo '<p><strong>😿 ' . sprintf(__('Backup failed: %s', 'tigerstyle-life9'), $e->getMessage()) . '</strong></p>';
|
|
echo '</div>';
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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 '<div class="notice notice-success is-dismissible">';
|
|
echo '<p><strong>📥 ' . sprintf(__('Backup file "%s" uploaded successfully!', 'tigerstyle-life9'), $filename) . '</strong></p>';
|
|
echo '</div>';
|
|
});
|
|
|
|
} catch (Exception $e) {
|
|
add_action('admin_notices', function() use ($e) {
|
|
echo '<div class="notice notice-error is-dismissible">';
|
|
echo '<p><strong>😿 ' . sprintf(__('Upload failed: %s', 'tigerstyle-life9'), $e->getMessage()) . '</strong></p>';
|
|
echo '</div>';
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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 '<div class="notice notice-success is-dismissible">';
|
|
echo '<p><strong>🎉 ' . __('Backup restored successfully! Your site has been brought back to life!', 'tigerstyle-life9') . '</strong></p>';
|
|
echo '</div>';
|
|
});
|
|
|
|
} catch (Exception $e) {
|
|
add_action('admin_notices', function() use ($e) {
|
|
echo '<div class="notice notice-error is-dismissible">';
|
|
echo '<p><strong>😿 ' . sprintf(__('Restore failed: %s', 'tigerstyle-life9'), $e->getMessage()) . '</strong></p>';
|
|
echo '</div>';
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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();
|