tigerstyle-life9/tigerstyle-life9.php
Ryan Malloy e92b7f8700 Initial commit: TigerStyle Life9 v1.0.0
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.
2026-05-27 14:32:00 -06:00

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">&times;</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();