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.
603 lines
22 KiB
PHP
603 lines
22 KiB
PHP
<?php
|
|
/**
|
|
* API Manager
|
|
*
|
|
* Manages REST API endpoints and AJAX handlers for TigerStyle Life9
|
|
*
|
|
* @package TigerStyleLife9
|
|
* @since 1.0.0
|
|
*/
|
|
|
|
// Exit if accessed directly
|
|
if (!defined('ABSPATH')) {
|
|
exit;
|
|
}
|
|
|
|
/**
|
|
* API management class
|
|
*
|
|
* @since 1.0.0
|
|
*/
|
|
class TigerStyle_Life9_API {
|
|
|
|
/**
|
|
* REST endpoints instance
|
|
*
|
|
* @var TigerStyle_Life9_REST_Endpoints
|
|
*/
|
|
private $rest_endpoints;
|
|
|
|
/**
|
|
* Security instance
|
|
*
|
|
* @var TigerStyle_Life9_Security
|
|
*/
|
|
private $security;
|
|
|
|
/**
|
|
* Constructor
|
|
*/
|
|
public function __construct() {
|
|
$this->security = tigerstyle_life9()->get_security();
|
|
$this->init_hooks();
|
|
}
|
|
|
|
/**
|
|
* Initialize WordPress hooks
|
|
*/
|
|
private function init_hooks() {
|
|
// REST API
|
|
add_action('rest_api_init', [$this, 'register_rest_routes']);
|
|
|
|
// AJAX handlers
|
|
add_action('wp_ajax_tigerstyle_life9_start_backup', [$this, 'ajax_start_backup']);
|
|
add_action('wp_ajax_tigerstyle_life9_get_backup_status', [$this, 'ajax_get_backup_status']);
|
|
add_action('wp_ajax_tigerstyle_life9_cancel_backup', [$this, 'ajax_cancel_backup']);
|
|
add_action('wp_ajax_tigerstyle_life9_download_backup', [$this, 'ajax_download_backup']);
|
|
add_action('wp_ajax_tigerstyle_life9_delete_backup', [$this, 'ajax_delete_backup']);
|
|
add_action('wp_ajax_tigerstyle_life9_restore_backup', [$this, 'ajax_restore_backup']);
|
|
add_action('wp_ajax_tigerstyle_life9_browse_files', [$this, 'ajax_browse_files']);
|
|
add_action('wp_ajax_tigerstyle_life9_preview_file', [$this, 'ajax_preview_file']);
|
|
add_action('wp_ajax_tigerstyle_life9_save_settings', [$this, 'ajax_save_settings']);
|
|
add_action('wp_ajax_tigerstyle_life9_get_dashboard_stats', [$this, 'ajax_get_dashboard_stats']);
|
|
add_action('wp_ajax_tigerstyle_life9_get_system_status', [$this, 'ajax_get_system_status']);
|
|
|
|
// Rate limiting
|
|
add_action('wp_ajax_tigerstyle_life9_rate_limit_check', [$this, 'check_rate_limits']);
|
|
}
|
|
|
|
/**
|
|
* Register REST API routes
|
|
*/
|
|
public function register_rest_routes() {
|
|
$this->rest_endpoints = new TigerStyle_Life9_REST_Endpoints();
|
|
$this->rest_endpoints->register_routes();
|
|
}
|
|
|
|
/**
|
|
* Check user permissions for API access
|
|
*
|
|
* @param string $capability Required capability
|
|
* @return bool
|
|
*/
|
|
private function check_permissions($capability = 'manage_options') {
|
|
return current_user_can($capability);
|
|
}
|
|
|
|
/**
|
|
* Validate and sanitize AJAX request
|
|
*
|
|
* @param string $action Action name for nonce verification
|
|
* @return array Sanitized request data
|
|
*/
|
|
private function validate_ajax_request($action) {
|
|
// Check permissions
|
|
if (!$this->check_permissions()) {
|
|
wp_send_json_error(['message' => __('Insufficient permissions', 'tigerstyle-life9')]);
|
|
}
|
|
|
|
// Verify nonce
|
|
if (!$this->security->verify_nonce($_POST['nonce'] ?? '', $action)) {
|
|
wp_send_json_error(['message' => __('Security check failed', 'tigerstyle-life9')]);
|
|
}
|
|
|
|
// Sanitize request data
|
|
$sanitizer = new TigerStyle_Life9_Sanitizer();
|
|
return $sanitizer->sanitize_array($_POST);
|
|
}
|
|
|
|
/**
|
|
* AJAX: Start backup process
|
|
*/
|
|
public function ajax_start_backup() {
|
|
$request = $this->validate_ajax_request('start_backup');
|
|
|
|
try {
|
|
// Validate backup configuration
|
|
$backup_config = $request['backup_config'] ?? [];
|
|
if (is_string($backup_config)) {
|
|
$backup_config = json_decode($backup_config, true);
|
|
}
|
|
|
|
$sanitizer = new TigerStyle_Life9_Sanitizer();
|
|
$validator = new TigerStyle_Life9_Validator();
|
|
|
|
$clean_config = $sanitizer->sanitize_backup_config($backup_config);
|
|
|
|
if (!$validator->validate_backup_config($clean_config)) {
|
|
$errors = $validator->get_errors();
|
|
wp_send_json_error([
|
|
'message' => __('Invalid backup configuration', 'tigerstyle-life9'),
|
|
'errors' => $errors
|
|
]);
|
|
}
|
|
|
|
// Start backup process
|
|
$backup_engine = new TigerStyle_Life9_Backup_Engine();
|
|
$backup_id = $backup_engine->start_backup($clean_config);
|
|
|
|
if ($backup_id) {
|
|
wp_send_json_success([
|
|
'backup_id' => $backup_id,
|
|
'message' => __('Backup started successfully', 'tigerstyle-life9'),
|
|
'status_url' => rest_url('tigerstyle-life9/v1/backups/' . $backup_id . '/status')
|
|
]);
|
|
} else {
|
|
wp_send_json_error(['message' => __('Failed to start backup', 'tigerstyle-life9')]);
|
|
}
|
|
|
|
} catch (Exception $e) {
|
|
error_log('TigerStyle Life9: Backup start error - ' . $e->getMessage());
|
|
wp_send_json_error(['message' => __('An error occurred while starting backup', 'tigerstyle-life9')]);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* AJAX: Get backup status
|
|
*/
|
|
public function ajax_get_backup_status() {
|
|
$request = $this->validate_ajax_request('get_backup_status');
|
|
|
|
$backup_id = intval($request['backup_id'] ?? 0);
|
|
if (!$backup_id) {
|
|
wp_send_json_error(['message' => __('Invalid backup ID', 'tigerstyle-life9')]);
|
|
}
|
|
|
|
try {
|
|
global $wpdb;
|
|
|
|
$backup = $wpdb->get_row($wpdb->prepare(
|
|
"SELECT * FROM {$wpdb->prefix}tigerstyle_life9_backups WHERE id = %d",
|
|
$backup_id
|
|
));
|
|
|
|
if (!$backup) {
|
|
wp_send_json_error(['message' => __('Backup not found', 'tigerstyle-life9')]);
|
|
}
|
|
|
|
// Get recent log entries
|
|
$logs = $wpdb->get_results($wpdb->prepare(
|
|
"SELECT * FROM {$wpdb->prefix}tigerstyle_life9_logs
|
|
WHERE backup_id = %d
|
|
ORDER BY created_at DESC
|
|
LIMIT 10",
|
|
$backup_id
|
|
));
|
|
|
|
$response = [
|
|
'id' => $backup->id,
|
|
'status' => $backup->status,
|
|
'created_at' => $backup->created_at,
|
|
'completed_at' => $backup->completed_at,
|
|
'file_size' => $backup->file_size,
|
|
'progress' => $this->calculate_backup_progress($backup),
|
|
'logs' => array_map(function($log) {
|
|
return [
|
|
'level' => $log->level,
|
|
'message' => $log->message,
|
|
'created_at' => $log->created_at
|
|
];
|
|
}, $logs)
|
|
];
|
|
|
|
wp_send_json_success($response);
|
|
|
|
} catch (Exception $e) {
|
|
error_log('TigerStyle Life9: Status check error - ' . $e->getMessage());
|
|
wp_send_json_error(['message' => __('Failed to get backup status', 'tigerstyle-life9')]);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* AJAX: Cancel backup process
|
|
*/
|
|
public function ajax_cancel_backup() {
|
|
$request = $this->validate_ajax_request('cancel_backup');
|
|
|
|
$backup_id = intval($request['backup_id'] ?? 0);
|
|
if (!$backup_id) {
|
|
wp_send_json_error(['message' => __('Invalid backup ID', 'tigerstyle-life9')]);
|
|
}
|
|
|
|
try {
|
|
$backup_engine = new TigerStyle_Life9_Backup_Engine();
|
|
$success = $backup_engine->cancel_backup($backup_id);
|
|
|
|
if ($success) {
|
|
wp_send_json_success(['message' => __('Backup cancelled', 'tigerstyle-life9')]);
|
|
} else {
|
|
wp_send_json_error(['message' => __('Failed to cancel backup', 'tigerstyle-life9')]);
|
|
}
|
|
|
|
} catch (Exception $e) {
|
|
error_log('TigerStyle Life9: Cancel backup error - ' . $e->getMessage());
|
|
wp_send_json_error(['message' => __('An error occurred while cancelling backup', 'tigerstyle-life9')]);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* AJAX: Download backup file
|
|
*/
|
|
public function ajax_download_backup() {
|
|
$request = $this->validate_ajax_request('download_backup');
|
|
|
|
$backup_id = intval($request['backup_id'] ?? 0);
|
|
if (!$backup_id) {
|
|
wp_send_json_error(['message' => __('Invalid backup ID', 'tigerstyle-life9')]);
|
|
}
|
|
|
|
try {
|
|
global $wpdb;
|
|
|
|
$backup = $wpdb->get_row($wpdb->prepare(
|
|
"SELECT * FROM {$wpdb->prefix}tigerstyle_life9_backups WHERE id = %d AND status = 'completed'",
|
|
$backup_id
|
|
));
|
|
|
|
if (!$backup || !$backup->file_path) {
|
|
wp_send_json_error(['message' => __('Backup file not found', 'tigerstyle-life9')]);
|
|
}
|
|
|
|
// Validate file path
|
|
if (!$this->security->validate_path($backup->file_path)) {
|
|
wp_send_json_error(['message' => __('Invalid file path', 'tigerstyle-life9')]);
|
|
}
|
|
|
|
if (!file_exists($backup->file_path)) {
|
|
wp_send_json_error(['message' => __('Backup file does not exist', 'tigerstyle-life9')]);
|
|
}
|
|
|
|
// Generate secure download token
|
|
$token = $this->security->generate_token();
|
|
set_transient('tigerstyle_life9_download_' . $token, $backup_id, 300); // 5 minutes
|
|
|
|
$download_url = add_query_arg([
|
|
'tigerstyle_life9_download' => $token,
|
|
'backup_id' => $backup_id
|
|
], admin_url('admin.php'));
|
|
|
|
wp_send_json_success(['download_url' => $download_url]);
|
|
|
|
} catch (Exception $e) {
|
|
error_log('TigerStyle Life9: Download error - ' . $e->getMessage());
|
|
wp_send_json_error(['message' => __('Failed to generate download link', 'tigerstyle-life9')]);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* AJAX: Delete backup
|
|
*/
|
|
public function ajax_delete_backup() {
|
|
$request = $this->validate_ajax_request('delete_backup');
|
|
|
|
$backup_id = intval($request['backup_id'] ?? 0);
|
|
if (!$backup_id) {
|
|
wp_send_json_error(['message' => __('Invalid backup ID', 'tigerstyle-life9')]);
|
|
}
|
|
|
|
try {
|
|
global $wpdb;
|
|
|
|
$backup = $wpdb->get_row($wpdb->prepare(
|
|
"SELECT * FROM {$wpdb->prefix}tigerstyle_life9_backups WHERE id = %d",
|
|
$backup_id
|
|
));
|
|
|
|
if (!$backup) {
|
|
wp_send_json_error(['message' => __('Backup not found', 'tigerstyle-life9')]);
|
|
}
|
|
|
|
// Delete file if exists
|
|
if ($backup->file_path && file_exists($backup->file_path)) {
|
|
$encryption = new TigerStyle_Life9_Encryption();
|
|
$encryption->secure_delete($backup->file_path);
|
|
}
|
|
|
|
// Delete from database
|
|
$wpdb->delete(
|
|
$wpdb->prefix . 'tigerstyle_life9_backups',
|
|
['id' => $backup_id],
|
|
['%d']
|
|
);
|
|
|
|
// Log the deletion
|
|
$this->security->log_security_event('backup_deleted', [
|
|
'backup_id' => $backup_id,
|
|
'backup_name' => $backup->name
|
|
]);
|
|
|
|
wp_send_json_success(['message' => __('Backup deleted successfully', 'tigerstyle-life9')]);
|
|
|
|
} catch (Exception $e) {
|
|
error_log('TigerStyle Life9: Delete backup error - ' . $e->getMessage());
|
|
wp_send_json_error(['message' => __('Failed to delete backup', 'tigerstyle-life9')]);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* AJAX: Browse files
|
|
*/
|
|
public function ajax_browse_files() {
|
|
$request = $this->validate_ajax_request('browse_files');
|
|
|
|
$path = $request['path'] ?? ABSPATH;
|
|
$show_hidden = (bool) ($request['show_hidden'] ?? false);
|
|
|
|
// Validate path
|
|
if (!$this->security->validate_path($path, ABSPATH)) {
|
|
wp_send_json_error(['message' => __('Invalid path', 'tigerstyle-life9')]);
|
|
}
|
|
|
|
try {
|
|
$scanner = new TigerStyle_Life9_File_Scanner();
|
|
$files = $scanner->scan_directory($path, [
|
|
'show_hidden' => $show_hidden,
|
|
'max_depth' => 1
|
|
]);
|
|
|
|
wp_send_json_success([
|
|
'files' => $files,
|
|
'current_path' => $path
|
|
]);
|
|
|
|
} catch (Exception $e) {
|
|
error_log('TigerStyle Life9: File browse error - ' . $e->getMessage());
|
|
wp_send_json_error(['message' => __('Failed to browse files', 'tigerstyle-life9')]);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* AJAX: Preview file
|
|
*/
|
|
public function ajax_preview_file() {
|
|
$request = $this->validate_ajax_request('preview_file');
|
|
|
|
$file_path = $request['path'] ?? '';
|
|
|
|
// Validate file path
|
|
if (!$this->security->validate_path($file_path, ABSPATH)) {
|
|
wp_send_json_error(['message' => __('Invalid file path', 'tigerstyle-life9')]);
|
|
}
|
|
|
|
if (!file_exists($file_path) || !is_readable($file_path)) {
|
|
wp_send_json_error(['message' => __('File not found or not readable', 'tigerstyle-life9')]);
|
|
}
|
|
|
|
try {
|
|
$file_size = filesize($file_path);
|
|
$max_preview_size = 1024 * 1024; // 1MB
|
|
|
|
if ($file_size > $max_preview_size) {
|
|
wp_send_json_error(['message' => __('File too large for preview', 'tigerstyle-life9')]);
|
|
}
|
|
|
|
$content = file_get_contents($file_path);
|
|
|
|
// Basic security check for content
|
|
if (strpos($content, '<?php') !== false) {
|
|
// For PHP files, sanitize content
|
|
$content = htmlspecialchars($content);
|
|
}
|
|
|
|
wp_send_json_success(['content' => $content]);
|
|
|
|
} catch (Exception $e) {
|
|
error_log('TigerStyle Life9: File preview error - ' . $e->getMessage());
|
|
wp_send_json_error(['message' => __('Failed to preview file', 'tigerstyle-life9')]);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* AJAX: Save settings
|
|
*/
|
|
public function ajax_save_settings() {
|
|
$request = $this->validate_ajax_request('save_settings');
|
|
|
|
$settings = $request['settings'] ?? [];
|
|
if (is_string($settings)) {
|
|
$settings = json_decode($settings, true);
|
|
}
|
|
|
|
if (!is_array($settings)) {
|
|
wp_send_json_error(['message' => __('Invalid settings format', 'tigerstyle-life9')]);
|
|
}
|
|
|
|
try {
|
|
$sanitizer = new TigerStyle_Life9_Sanitizer();
|
|
$validator = new TigerStyle_Life9_Validator();
|
|
|
|
// Define allowed settings with their types
|
|
$allowed_settings = [
|
|
'backup_retention_days' => 'int',
|
|
'max_backup_size_mb' => 'int',
|
|
'enable_compression' => 'bool',
|
|
'compression_method' => 'string',
|
|
'backup_database' => 'bool',
|
|
'backup_files' => 'bool',
|
|
'exclude_patterns' => 'array',
|
|
'storage_locations' => 'array',
|
|
'api_rate_limit' => 'int',
|
|
'enable_logging' => 'bool',
|
|
'log_level' => 'string'
|
|
];
|
|
|
|
$updated_settings = [];
|
|
|
|
foreach ($allowed_settings as $setting_name => $type) {
|
|
if (isset($settings[$setting_name])) {
|
|
$value = $sanitizer->sanitize_sql_param($settings[$setting_name], $type);
|
|
|
|
// Additional validation
|
|
switch ($setting_name) {
|
|
case 'backup_retention_days':
|
|
if ($value < 1 || $value > 365) {
|
|
wp_send_json_error(['message' => __('Retention days must be between 1 and 365', 'tigerstyle-life9')]);
|
|
}
|
|
break;
|
|
|
|
case 'max_backup_size_mb':
|
|
if ($value < 1 || $value > 10000) {
|
|
wp_send_json_error(['message' => __('Max backup size must be between 1MB and 10GB', 'tigerstyle-life9')]);
|
|
}
|
|
break;
|
|
|
|
case 'compression_method':
|
|
$valid_methods = ['zip', 'tar', 'gzip', 'none'];
|
|
if (!in_array($value, $valid_methods)) {
|
|
wp_send_json_error(['message' => __('Invalid compression method', 'tigerstyle-life9')]);
|
|
}
|
|
break;
|
|
}
|
|
|
|
update_option('tigerstyle_life9_' . $setting_name, $value);
|
|
$updated_settings[$setting_name] = $value;
|
|
}
|
|
}
|
|
|
|
wp_send_json_success([
|
|
'message' => __('Settings saved successfully', 'tigerstyle-life9'),
|
|
'updated_settings' => $updated_settings
|
|
]);
|
|
|
|
} catch (Exception $e) {
|
|
error_log('TigerStyle Life9: Save settings error - ' . $e->getMessage());
|
|
wp_send_json_error(['message' => __('Failed to save settings', 'tigerstyle-life9')]);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* AJAX: Get dashboard statistics
|
|
*/
|
|
public function ajax_get_dashboard_stats() {
|
|
$this->validate_ajax_request('get_dashboard_stats');
|
|
|
|
try {
|
|
global $wpdb;
|
|
|
|
$stats = [
|
|
'total_backups' => 0,
|
|
'total_size' => 0,
|
|
'successful_backups' => 0,
|
|
'failed_backups' => 0,
|
|
'last_backup' => null
|
|
];
|
|
|
|
// Get backup counts and stats
|
|
$backup_stats = $wpdb->get_row(
|
|
"SELECT
|
|
COUNT(*) as total_backups,
|
|
SUM(CASE WHEN status = 'completed' THEN 1 ELSE 0 END) as successful_backups,
|
|
SUM(CASE WHEN status = 'failed' THEN 1 ELSE 0 END) as failed_backups,
|
|
SUM(CASE WHEN status = 'completed' THEN file_size ELSE 0 END) as total_size,
|
|
MAX(CASE WHEN status = 'completed' THEN created_at ELSE NULL END) as last_backup
|
|
FROM {$wpdb->prefix}tigerstyle_life9_backups"
|
|
);
|
|
|
|
if ($backup_stats) {
|
|
$stats = [
|
|
'total_backups' => intval($backup_stats->total_backups),
|
|
'total_size' => intval($backup_stats->total_size),
|
|
'successful_backups' => intval($backup_stats->successful_backups),
|
|
'failed_backups' => intval($backup_stats->failed_backups),
|
|
'last_backup' => $backup_stats->last_backup
|
|
];
|
|
}
|
|
|
|
wp_send_json_success($stats);
|
|
|
|
} catch (Exception $e) {
|
|
error_log('TigerStyle Life9: Dashboard stats error - ' . $e->getMessage());
|
|
wp_send_json_error(['message' => __('Failed to load dashboard stats', 'tigerstyle-life9')]);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* AJAX: Get system status
|
|
*/
|
|
public function ajax_get_system_status() {
|
|
$this->validate_ajax_request('get_system_status');
|
|
|
|
try {
|
|
$upload_dir = wp_upload_dir();
|
|
$backup_dir = $upload_dir['basedir'] . '/tigerstyle-life9';
|
|
|
|
$status = [
|
|
'php_version' => PHP_VERSION,
|
|
'php_version_ok' => version_compare(PHP_VERSION, '8.0', '>='),
|
|
'wp_version' => get_bloginfo('version'),
|
|
'wp_version_ok' => version_compare(get_bloginfo('version'), '6.0', '>='),
|
|
'available_space' => disk_free_space($backup_dir),
|
|
'disk_space_ok' => disk_free_space($backup_dir) > (1024 * 1024 * 1024), // 1GB
|
|
'permissions_ok' => is_writable($backup_dir),
|
|
'extensions' => [
|
|
'openssl' => extension_loaded('openssl'),
|
|
'zip' => extension_loaded('zip'),
|
|
'curl' => extension_loaded('curl'),
|
|
'json' => extension_loaded('json')
|
|
]
|
|
];
|
|
|
|
wp_send_json_success($status);
|
|
|
|
} catch (Exception $e) {
|
|
error_log('TigerStyle Life9: System status error - ' . $e->getMessage());
|
|
wp_send_json_error(['message' => __('Failed to get system status', 'tigerstyle-life9')]);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Calculate backup progress percentage
|
|
*
|
|
* @param object $backup Backup database record
|
|
* @return int Progress percentage (0-100)
|
|
*/
|
|
private function calculate_backup_progress($backup) {
|
|
switch ($backup->status) {
|
|
case 'completed':
|
|
return 100;
|
|
case 'failed':
|
|
case 'cancelled':
|
|
return 0;
|
|
case 'running':
|
|
// This would be determined by the backup engine
|
|
// For now, return a placeholder
|
|
return 50;
|
|
default:
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check rate limits for API requests
|
|
*/
|
|
public function check_rate_limits() {
|
|
$action = $_POST['action'] ?? '';
|
|
$limit = intval(get_option('tigerstyle_life9_api_rate_limit', 100));
|
|
|
|
if (!$this->security->check_rate_limit($action, $limit)) {
|
|
wp_send_json_error(['message' => __('Rate limit exceeded', 'tigerstyle-life9')]);
|
|
}
|
|
|
|
wp_send_json_success(['message' => 'Rate limit OK']);
|
|
}
|
|
} |