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

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']);
}
}