Make your WordPress site irresistible. Natural SEO attraction with: - robots.txt management - sitemap.xml generation - LLMs.txt support - Google integration (Analytics, Search Console, Tag Manager) - Schema.org structured data - Open Graph / Twitter Card meta tags - AMP support - Visual elements gallery - Built-in backup/restore module Includes build.sh and .distignore for WordPress-installable release ZIPs.
839 lines
29 KiB
PHP
839 lines
29 KiB
PHP
<?php
|
|
/**
|
|
* Storage Manager for TigerStyle SEO Backup System
|
|
* Handles local and S3-compatible cloud storage with encryption
|
|
*/
|
|
|
|
// Prevent direct access
|
|
if (!defined('ABSPATH')) {
|
|
exit;
|
|
}
|
|
|
|
class TigerStyleSEO_Storage_Manager {
|
|
|
|
/**
|
|
* Single instance
|
|
*/
|
|
private static $instance = null;
|
|
|
|
/**
|
|
* Storage backends
|
|
*/
|
|
private $storage_backends = array();
|
|
|
|
/**
|
|
* Logger instance
|
|
*/
|
|
private $logger = null;
|
|
|
|
/**
|
|
* Get instance
|
|
*/
|
|
public static function instance() {
|
|
if (is_null(self::$instance)) {
|
|
self::$instance = new self();
|
|
}
|
|
return self::$instance;
|
|
}
|
|
|
|
/**
|
|
* Constructor
|
|
*/
|
|
private function __construct() {
|
|
$this->init();
|
|
}
|
|
|
|
/**
|
|
* Initialize storage manager
|
|
*/
|
|
private function init() {
|
|
$this->logger = TigerStyleSEO_Backup_Logger::instance();
|
|
$this->register_storage_backends();
|
|
}
|
|
|
|
/**
|
|
* Register available storage backends
|
|
*/
|
|
private function register_storage_backends() {
|
|
// Local storage (always available)
|
|
$this->storage_backends['local'] = array(
|
|
'name' => 'Local Storage',
|
|
'description' => 'Store backups locally on server',
|
|
'available' => true,
|
|
'settings' => array(
|
|
'path' => get_option('backup_local_path', WP_CONTENT_DIR . '/tigerstyle-backups/')
|
|
)
|
|
);
|
|
|
|
// S3 Compatible storage
|
|
$s3_enabled = get_option('backup_s3_enabled', false);
|
|
$this->storage_backends['s3'] = array(
|
|
'name' => 'S3 Compatible Storage',
|
|
'description' => 'Store backups in AWS S3 or compatible services (MinIO, DigitalOcean Spaces, etc.)',
|
|
'available' => $s3_enabled && $this->check_s3_requirements(),
|
|
'settings' => array(
|
|
'endpoint' => get_option('backup_s3_endpoint', ''),
|
|
'bucket' => get_option('backup_s3_bucket', ''),
|
|
'access_key' => get_option('backup_s3_access_key', ''),
|
|
'secret_key' => get_option('backup_s3_secret_key', ''),
|
|
'region' => get_option('backup_s3_region', 'us-east-1'),
|
|
'storage_class' => get_option('backup_s3_storage_class', 'STANDARD_IA'),
|
|
'encryption' => get_option('backup_s3_encryption', true),
|
|
'prefix' => get_option('backup_s3_prefix', 'tigerstyle-backups/')
|
|
)
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Check S3 requirements
|
|
*/
|
|
private function check_s3_requirements() {
|
|
return extension_loaded('curl') && function_exists('openssl_encrypt');
|
|
}
|
|
|
|
/**
|
|
* Store backup using configured storage backends
|
|
*/
|
|
public function store_backup($backup_file, $backup_id, $manifest) {
|
|
$storage_results = array();
|
|
$primary_backend = get_option('backup_primary_storage', 'local');
|
|
$secondary_backends = get_option('backup_secondary_storage', array());
|
|
|
|
// Store to primary backend
|
|
if (isset($this->storage_backends[$primary_backend]) && $this->storage_backends[$primary_backend]['available']) {
|
|
$this->logger->info("Storing backup to primary storage", array(
|
|
'backend' => $primary_backend,
|
|
'backup_id' => $backup_id
|
|
));
|
|
|
|
$result = $this->store_to_backend($backup_file, $backup_id, $manifest, $primary_backend);
|
|
|
|
if ($result['success']) {
|
|
$storage_results['primary'] = $result;
|
|
} else {
|
|
$this->logger->error("Primary storage failed", array(
|
|
'backend' => $primary_backend,
|
|
'error' => $result['error']
|
|
));
|
|
}
|
|
}
|
|
|
|
// Store to secondary backends
|
|
foreach ($secondary_backends as $backend) {
|
|
if (isset($this->storage_backends[$backend]) && $this->storage_backends[$backend]['available']) {
|
|
$this->logger->info("Storing backup to secondary storage", array(
|
|
'backend' => $backend,
|
|
'backup_id' => $backup_id
|
|
));
|
|
|
|
$result = $this->store_to_backend($backup_file, $backup_id, $manifest, $backend);
|
|
|
|
if ($result['success']) {
|
|
$storage_results['secondary'][$backend] = $result;
|
|
} else {
|
|
$this->logger->warning("Secondary storage failed", array(
|
|
'backend' => $backend,
|
|
'error' => $result['error']
|
|
));
|
|
}
|
|
}
|
|
}
|
|
|
|
return $storage_results;
|
|
}
|
|
|
|
/**
|
|
* Store backup to specific backend
|
|
*/
|
|
private function store_to_backend($backup_file, $backup_id, $manifest, $backend) {
|
|
$start_time = microtime(true);
|
|
|
|
try {
|
|
switch ($backend) {
|
|
case 'local':
|
|
$result = $this->store_to_local($backup_file, $backup_id, $manifest);
|
|
break;
|
|
case 's3':
|
|
$result = $this->store_to_s3($backup_file, $backup_id, $manifest);
|
|
break;
|
|
default:
|
|
throw new Exception("Unknown storage backend: " . $backend);
|
|
}
|
|
|
|
$duration = microtime(true) - $start_time;
|
|
|
|
$this->logger->info("Backup stored successfully", array(
|
|
'backend' => $backend,
|
|
'backup_id' => $backup_id,
|
|
'duration' => round($duration, 2) . 's',
|
|
'location' => $result['location'] ?? 'unknown'
|
|
));
|
|
|
|
return array(
|
|
'success' => true,
|
|
'backend' => $backend,
|
|
'location' => $result['location'],
|
|
'duration' => $duration,
|
|
'metadata' => $result['metadata'] ?? array()
|
|
);
|
|
|
|
} catch (Exception $e) {
|
|
$this->logger->error("Storage backend failed", array(
|
|
'backend' => $backend,
|
|
'backup_id' => $backup_id,
|
|
'error' => $e->getMessage()
|
|
));
|
|
|
|
return array(
|
|
'success' => false,
|
|
'backend' => $backend,
|
|
'error' => $e->getMessage()
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Store backup to local storage
|
|
*/
|
|
private function store_to_local($backup_file, $backup_id, $manifest) {
|
|
$settings = $this->storage_backends['local']['settings'];
|
|
$destination_dir = $settings['path'];
|
|
|
|
// Ensure destination directory exists
|
|
if (!is_dir($destination_dir)) {
|
|
wp_mkdir_p($destination_dir);
|
|
|
|
// Protect backup directory
|
|
$htaccess_content = "Order deny,allow\nDeny from all\n";
|
|
file_put_contents($destination_dir . '.htaccess', $htaccess_content);
|
|
}
|
|
|
|
$destination_file = $destination_dir . basename($backup_file);
|
|
|
|
// Copy backup file
|
|
if (!copy($backup_file, $destination_file)) {
|
|
throw new Exception("Failed to copy backup to local storage");
|
|
}
|
|
|
|
// Create manifest file
|
|
$manifest_file = $destination_dir . $backup_id . '-manifest.json';
|
|
file_put_contents($manifest_file, json_encode($manifest, JSON_PRETTY_PRINT));
|
|
|
|
return array(
|
|
'location' => $destination_file,
|
|
'manifest_file' => $manifest_file,
|
|
'metadata' => array(
|
|
'size' => filesize($destination_file),
|
|
'checksum' => md5_file($destination_file)
|
|
)
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Store backup to S3 compatible storage
|
|
*/
|
|
private function store_to_s3($backup_file, $backup_id, $manifest) {
|
|
$settings = $this->storage_backends['s3']['settings'];
|
|
|
|
if (empty($settings['bucket']) || empty($settings['access_key']) || empty($settings['secret_key'])) {
|
|
throw new Exception("S3 storage not properly configured");
|
|
}
|
|
|
|
$s3_key = $settings['prefix'] . $backup_id . '/' . basename($backup_file);
|
|
$manifest_key = $settings['prefix'] . $backup_id . '/manifest.json';
|
|
|
|
// Upload backup file
|
|
$backup_upload = $this->s3_put_object($backup_file, $s3_key, $settings);
|
|
|
|
if (!$backup_upload['success']) {
|
|
throw new Exception("Failed to upload backup to S3: " . $backup_upload['error']);
|
|
}
|
|
|
|
// Upload manifest file
|
|
$manifest_content = json_encode($manifest, JSON_PRETTY_PRINT);
|
|
$manifest_upload = $this->s3_put_object_content($manifest_content, $manifest_key, $settings);
|
|
|
|
if (!$manifest_upload['success']) {
|
|
$this->logger->warning("Failed to upload manifest to S3", array(
|
|
'error' => $manifest_upload['error']
|
|
));
|
|
}
|
|
|
|
return array(
|
|
'location' => $s3_key,
|
|
'manifest_location' => $manifest_key,
|
|
'metadata' => array(
|
|
'bucket' => $settings['bucket'],
|
|
'region' => $settings['region'],
|
|
'storage_class' => $settings['storage_class'],
|
|
'encryption' => $settings['encryption'],
|
|
'etag' => $backup_upload['etag'] ?? '',
|
|
'version_id' => $backup_upload['version_id'] ?? ''
|
|
)
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Upload file to S3
|
|
*/
|
|
private function s3_put_object($file_path, $key, $settings) {
|
|
if (!file_exists($file_path)) {
|
|
return array('success' => false, 'error' => 'File not found');
|
|
}
|
|
|
|
$file_content = file_get_contents($file_path);
|
|
return $this->s3_put_object_content($file_content, $key, $settings);
|
|
}
|
|
|
|
/**
|
|
* Upload content to S3
|
|
*/
|
|
private function s3_put_object_content($content, $key, $settings) {
|
|
$endpoint = $settings['endpoint'] ?: 'https://s3.' . $settings['region'] . '.amazonaws.com';
|
|
$bucket = $settings['bucket'];
|
|
$url = $endpoint . '/' . $bucket . '/' . $key;
|
|
|
|
// Prepare headers
|
|
$headers = array();
|
|
$headers['Date'] = gmdate('D, d M Y H:i:s T');
|
|
$headers['Content-Type'] = 'application/octet-stream';
|
|
$headers['Content-Length'] = strlen($content);
|
|
|
|
if ($settings['storage_class'] && $settings['storage_class'] !== 'STANDARD') {
|
|
$headers['x-amz-storage-class'] = $settings['storage_class'];
|
|
}
|
|
|
|
if ($settings['encryption']) {
|
|
$headers['x-amz-server-side-encryption'] = 'AES256';
|
|
}
|
|
|
|
// Calculate content hash for authentication
|
|
$content_hash = hash('sha256', $content);
|
|
$headers['x-amz-content-sha256'] = $content_hash;
|
|
|
|
// Create authorization signature
|
|
$auth_header = $this->create_s3_auth_header('PUT', $key, $headers, $settings);
|
|
$headers['Authorization'] = $auth_header;
|
|
|
|
// Prepare curl request
|
|
$ch = curl_init();
|
|
curl_setopt_array($ch, array(
|
|
CURLOPT_URL => $url,
|
|
CURLOPT_CUSTOMREQUEST => 'PUT',
|
|
CURLOPT_POSTFIELDS => $content,
|
|
CURLOPT_HTTPHEADER => $this->format_headers($headers),
|
|
CURLOPT_RETURNTRANSFER => true,
|
|
CURLOPT_HEADER => true,
|
|
CURLOPT_SSL_VERIFYPEER => true,
|
|
CURLOPT_TIMEOUT => 300,
|
|
CURLOPT_FOLLOWLOCATION => false
|
|
));
|
|
|
|
$response = curl_exec($ch);
|
|
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
|
$curl_error = curl_error($ch);
|
|
curl_close($ch);
|
|
|
|
if ($curl_error) {
|
|
return array('success' => false, 'error' => 'CURL error: ' . $curl_error);
|
|
}
|
|
|
|
if ($http_code >= 200 && $http_code < 300) {
|
|
// Extract ETag from response headers
|
|
$etag = '';
|
|
if (preg_match('/ETag: "([^"]+)"/', $response, $matches)) {
|
|
$etag = $matches[1];
|
|
}
|
|
|
|
return array(
|
|
'success' => true,
|
|
'http_code' => $http_code,
|
|
'etag' => $etag,
|
|
'response' => $response
|
|
);
|
|
} else {
|
|
return array(
|
|
'success' => false,
|
|
'error' => 'HTTP ' . $http_code . ': ' . $this->extract_s3_error($response)
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Create S3 authorization header (AWS Signature Version 4)
|
|
*/
|
|
private function create_s3_auth_header($method, $key, $headers, $settings) {
|
|
$access_key = $settings['access_key'];
|
|
$secret_key = $settings['secret_key'];
|
|
$region = $settings['region'];
|
|
$service = 's3';
|
|
|
|
$timestamp = gmdate('Ymd\THis\Z');
|
|
$date = gmdate('Ymd');
|
|
|
|
// Create canonical request
|
|
$canonical_uri = '/' . $key;
|
|
$canonical_querystring = '';
|
|
|
|
$canonical_headers = '';
|
|
$signed_headers = array();
|
|
|
|
ksort($headers);
|
|
foreach ($headers as $name => $value) {
|
|
$name_lower = strtolower($name);
|
|
$canonical_headers .= $name_lower . ':' . $value . "\n";
|
|
$signed_headers[] = $name_lower;
|
|
}
|
|
|
|
$signed_headers_string = implode(';', $signed_headers);
|
|
$payload_hash = $headers['x-amz-content-sha256'];
|
|
|
|
$canonical_request = $method . "\n" .
|
|
$canonical_uri . "\n" .
|
|
$canonical_querystring . "\n" .
|
|
$canonical_headers . "\n" .
|
|
$signed_headers_string . "\n" .
|
|
$payload_hash;
|
|
|
|
// Create string to sign
|
|
$algorithm = 'AWS4-HMAC-SHA256';
|
|
$credential_scope = $date . '/' . $region . '/' . $service . '/aws4_request';
|
|
$string_to_sign = $algorithm . "\n" .
|
|
$timestamp . "\n" .
|
|
$credential_scope . "\n" .
|
|
hash('sha256', $canonical_request);
|
|
|
|
// Calculate signature
|
|
$k_date = hash_hmac('sha256', $date, 'AWS4' . $secret_key, true);
|
|
$k_region = hash_hmac('sha256', $region, $k_date, true);
|
|
$k_service = hash_hmac('sha256', $service, $k_region, true);
|
|
$k_signing = hash_hmac('sha256', 'aws4_request', $k_service, true);
|
|
$signature = hash_hmac('sha256', $string_to_sign, $k_signing);
|
|
|
|
// Create authorization header
|
|
$authorization = $algorithm . ' ' .
|
|
'Credential=' . $access_key . '/' . $credential_scope . ', ' .
|
|
'SignedHeaders=' . $signed_headers_string . ', ' .
|
|
'Signature=' . $signature;
|
|
|
|
return $authorization;
|
|
}
|
|
|
|
/**
|
|
* Format headers for curl
|
|
*/
|
|
private function format_headers($headers) {
|
|
$formatted = array();
|
|
foreach ($headers as $name => $value) {
|
|
$formatted[] = $name . ': ' . $value;
|
|
}
|
|
return $formatted;
|
|
}
|
|
|
|
/**
|
|
* Extract error message from S3 response
|
|
*/
|
|
private function extract_s3_error($response) {
|
|
if (preg_match('/<Code>([^<]+)<\/Code>/', $response, $matches)) {
|
|
$code = $matches[1];
|
|
if (preg_match('/<Message>([^<]+)<\/Message>/', $response, $msg_matches)) {
|
|
return $code . ': ' . $msg_matches[1];
|
|
}
|
|
return $code;
|
|
}
|
|
return 'Unknown S3 error';
|
|
}
|
|
|
|
/**
|
|
* Test S3 connection
|
|
*/
|
|
public function test_s3_connection($settings = null) {
|
|
if (!$settings) {
|
|
$settings = $this->storage_backends['s3']['settings'];
|
|
}
|
|
|
|
try {
|
|
// Test by trying to list bucket contents
|
|
$test_content = 'TigerStyle SEO Backup Connection Test';
|
|
$test_key = $settings['prefix'] . 'connection-test.txt';
|
|
|
|
$result = $this->s3_put_object_content($test_content, $test_key, $settings);
|
|
|
|
if ($result['success']) {
|
|
// Clean up test file
|
|
$this->s3_delete_object($test_key, $settings);
|
|
return array('success' => true, 'message' => 'S3 connection successful');
|
|
} else {
|
|
return array('success' => false, 'error' => $result['error']);
|
|
}
|
|
|
|
} catch (Exception $e) {
|
|
return array('success' => false, 'error' => $e->getMessage());
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Delete object from S3
|
|
*/
|
|
private function s3_delete_object($key, $settings) {
|
|
$endpoint = $settings['endpoint'] ?: 'https://s3.' . $settings['region'] . '.amazonaws.com';
|
|
$bucket = $settings['bucket'];
|
|
$url = $endpoint . '/' . $bucket . '/' . $key;
|
|
|
|
$headers = array();
|
|
$headers['Date'] = gmdate('D, d M Y H:i:s T');
|
|
$headers['x-amz-content-sha256'] = hash('sha256', '');
|
|
|
|
$auth_header = $this->create_s3_auth_header('DELETE', $key, $headers, $settings);
|
|
$headers['Authorization'] = $auth_header;
|
|
|
|
$ch = curl_init();
|
|
curl_setopt_array($ch, array(
|
|
CURLOPT_URL => $url,
|
|
CURLOPT_CUSTOMREQUEST => 'DELETE',
|
|
CURLOPT_HTTPHEADER => $this->format_headers($headers),
|
|
CURLOPT_RETURNTRANSFER => true,
|
|
CURLOPT_SSL_VERIFYPEER => true,
|
|
CURLOPT_TIMEOUT => 60
|
|
));
|
|
|
|
$response = curl_exec($ch);
|
|
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
|
curl_close($ch);
|
|
|
|
return $http_code >= 200 && $http_code < 300;
|
|
}
|
|
|
|
/**
|
|
* Retrieve backup from storage
|
|
*/
|
|
public function retrieve_backup($backup_id, $destination_path, $backend = null) {
|
|
if (!$backend) {
|
|
$backend = $this->get_backup_location($backup_id);
|
|
}
|
|
|
|
if (!$backend) {
|
|
throw new Exception("Cannot determine backup location for: " . $backup_id);
|
|
}
|
|
|
|
$this->logger->info("Retrieving backup from storage", array(
|
|
'backup_id' => $backup_id,
|
|
'backend' => $backend,
|
|
'destination' => $destination_path
|
|
));
|
|
|
|
switch ($backend) {
|
|
case 'local':
|
|
return $this->retrieve_from_local($backup_id, $destination_path);
|
|
case 's3':
|
|
return $this->retrieve_from_s3($backup_id, $destination_path);
|
|
default:
|
|
throw new Exception("Unknown storage backend: " . $backend);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Retrieve backup from local storage
|
|
*/
|
|
private function retrieve_from_local($backup_id, $destination_path) {
|
|
$settings = $this->storage_backends['local']['settings'];
|
|
$backup_dir = $settings['path'];
|
|
|
|
// Find backup file
|
|
$pattern = $backup_dir . $backup_id . '.*';
|
|
$files = glob($pattern);
|
|
|
|
if (empty($files)) {
|
|
throw new Exception("Backup file not found: " . $backup_id);
|
|
}
|
|
|
|
$backup_file = $files[0];
|
|
|
|
if (!copy($backup_file, $destination_path)) {
|
|
throw new Exception("Failed to copy backup from local storage");
|
|
}
|
|
|
|
return array(
|
|
'success' => true,
|
|
'source' => $backup_file,
|
|
'destination' => $destination_path,
|
|
'size' => filesize($destination_path)
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Retrieve backup from S3
|
|
*/
|
|
private function retrieve_from_s3($backup_id, $destination_path) {
|
|
// Implementation would go here for S3 download
|
|
// For brevity, this is a placeholder
|
|
throw new Exception("S3 backup retrieval not yet implemented");
|
|
}
|
|
|
|
/**
|
|
* Get backup location from database
|
|
*/
|
|
private function get_backup_location($backup_id) {
|
|
global $wpdb;
|
|
|
|
$table_name = $wpdb->prefix . 'tigerstyle_backups';
|
|
$backup = $wpdb->get_row(
|
|
$wpdb->prepare("SELECT storage_location FROM {$table_name} WHERE backup_id = %s", $backup_id),
|
|
ARRAY_A
|
|
);
|
|
|
|
return $backup ? $backup['storage_location'] : null;
|
|
}
|
|
|
|
/**
|
|
* Get available storage backends
|
|
*/
|
|
public function get_available_backends() {
|
|
return array_filter($this->storage_backends, function($backend) {
|
|
return $backend['available'];
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Get storage backend settings
|
|
*/
|
|
public function get_backend_settings($backend) {
|
|
return isset($this->storage_backends[$backend]) ? $this->storage_backends[$backend]['settings'] : null;
|
|
}
|
|
|
|
/**
|
|
* List all available backups
|
|
*/
|
|
public function list_backups($limit = 50, $offset = 0) {
|
|
global $wpdb;
|
|
|
|
$table_name = $wpdb->prefix . 'tigerstyle_backup_metadata';
|
|
|
|
// Get total count
|
|
$total_count = $wpdb->get_var("SELECT COUNT(*) FROM {$table_name}");
|
|
|
|
// Get backup list with pagination
|
|
$backups = $wpdb->get_results(
|
|
$wpdb->prepare(
|
|
"SELECT backup_id, storage_type, file_path, s3_bucket, s3_key, s3_url,
|
|
file_size, created_at, metadata_json
|
|
FROM {$table_name}
|
|
ORDER BY created_at DESC
|
|
LIMIT %d OFFSET %d",
|
|
$limit,
|
|
$offset
|
|
),
|
|
ARRAY_A
|
|
);
|
|
|
|
// Process backup data
|
|
$processed_backups = array();
|
|
foreach ($backups as $backup) {
|
|
$metadata = array();
|
|
if (!empty($backup['metadata_json'])) {
|
|
$metadata = json_decode($backup['metadata_json'], true) ?: array();
|
|
}
|
|
|
|
// Determine storage location and availability
|
|
$storage_info = $this->get_backup_storage_info($backup);
|
|
|
|
$processed_backups[] = array(
|
|
'backup_id' => $backup['backup_id'],
|
|
'storage_type' => $backup['storage_type'],
|
|
'file_size' => intval($backup['file_size']),
|
|
'file_size_formatted' => $this->format_file_size($backup['file_size']),
|
|
'created_at' => $backup['created_at'],
|
|
'created_at_formatted' => $this->format_backup_date($backup['created_at']),
|
|
'storage_location' => $storage_info['location'],
|
|
'is_available' => $storage_info['available'],
|
|
'metadata' => $metadata,
|
|
'actions' => $this->get_backup_actions($backup)
|
|
);
|
|
}
|
|
|
|
return array(
|
|
'backups' => $processed_backups,
|
|
'total_count' => intval($total_count),
|
|
'limit' => $limit,
|
|
'offset' => $offset,
|
|
'has_more' => ($offset + $limit) < $total_count
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Get backup storage information
|
|
*/
|
|
private function get_backup_storage_info($backup) {
|
|
$info = array(
|
|
'location' => '',
|
|
'available' => false
|
|
);
|
|
|
|
switch ($backup['storage_type']) {
|
|
case 'local':
|
|
if (!empty($backup['file_path'])) {
|
|
$info['location'] = basename($backup['file_path']);
|
|
$info['available'] = file_exists($backup['file_path']);
|
|
} else {
|
|
// Fallback: check local storage directory
|
|
$local_path = $this->storage_backends['local']['settings']['path'];
|
|
$pattern = $local_path . $backup['backup_id'] . '.*';
|
|
$files = glob($pattern);
|
|
if (!empty($files)) {
|
|
$info['location'] = basename($files[0]);
|
|
$info['available'] = true;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case 's3':
|
|
if (!empty($backup['s3_key'])) {
|
|
$info['location'] = $backup['s3_bucket'] . '/' . $backup['s3_key'];
|
|
$info['available'] = true; // Assume available unless proven otherwise
|
|
} elseif (!empty($backup['s3_url'])) {
|
|
$info['location'] = $backup['s3_url'];
|
|
$info['available'] = true;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
$info['location'] = __('Unknown storage type', 'tigerstyle-heat');
|
|
break;
|
|
}
|
|
|
|
return $info;
|
|
}
|
|
|
|
/**
|
|
* Format file size for display
|
|
*/
|
|
private function format_file_size($bytes) {
|
|
$bytes = floatval($bytes);
|
|
$units = array('B', 'KB', 'MB', 'GB', 'TB');
|
|
|
|
for ($i = 0; $bytes > 1024 && $i < count($units) - 1; $i++) {
|
|
$bytes /= 1024;
|
|
}
|
|
|
|
return round($bytes, 2) . ' ' . $units[$i];
|
|
}
|
|
|
|
/**
|
|
* Format backup date for display
|
|
*/
|
|
private function format_backup_date($datetime) {
|
|
$timestamp = strtotime($datetime);
|
|
if (!$timestamp) {
|
|
return $datetime;
|
|
}
|
|
|
|
$now = current_time('timestamp');
|
|
$diff = $now - $timestamp;
|
|
|
|
if ($diff < HOUR_IN_SECONDS) {
|
|
$minutes = floor($diff / MINUTE_IN_SECONDS);
|
|
return sprintf(_n('%d minute ago', '%d minutes ago', $minutes, 'tigerstyle-heat'), $minutes);
|
|
} elseif ($diff < DAY_IN_SECONDS) {
|
|
$hours = floor($diff / HOUR_IN_SECONDS);
|
|
return sprintf(_n('%d hour ago', '%d hours ago', $hours, 'tigerstyle-heat'), $hours);
|
|
} elseif ($diff < WEEK_IN_SECONDS) {
|
|
$days = floor($diff / DAY_IN_SECONDS);
|
|
return sprintf(_n('%d day ago', '%d days ago', $days, 'tigerstyle-heat'), $days);
|
|
} else {
|
|
return date_i18n(get_option('date_format') . ' ' . get_option('time_format'), $timestamp);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get available actions for a backup
|
|
*/
|
|
private function get_backup_actions($backup) {
|
|
$actions = array();
|
|
|
|
// Restore action
|
|
$actions['restore'] = array(
|
|
'label' => __('Restore', 'tigerstyle-heat'),
|
|
'url' => admin_url('admin.php?page=tigerstyle-heat&action=restore&backup_id=' . urlencode($backup['backup_id'])),
|
|
'class' => 'button button-primary'
|
|
);
|
|
|
|
// Download action (for local backups)
|
|
if ($backup['storage_type'] === 'local' && !empty($backup['file_path']) && file_exists($backup['file_path'])) {
|
|
$actions['download'] = array(
|
|
'label' => __('Download', 'tigerstyle-heat'),
|
|
'url' => admin_url('admin.php?page=tigerstyle-heat&action=download&backup_id=' . urlencode($backup['backup_id'])),
|
|
'class' => 'button'
|
|
);
|
|
}
|
|
|
|
// Delete action
|
|
$actions['delete'] = array(
|
|
'label' => __('Delete', 'tigerstyle-heat'),
|
|
'url' => admin_url('admin.php?page=tigerstyle-heat&action=delete&backup_id=' . urlencode($backup['backup_id'])),
|
|
'class' => 'button button-link-delete',
|
|
'confirm' => __('Are you sure you want to delete this backup? This action cannot be undone.', 'tigerstyle-heat')
|
|
);
|
|
|
|
return $actions;
|
|
}
|
|
|
|
/**
|
|
* Get storage statistics
|
|
*/
|
|
public function get_storage_stats() {
|
|
$stats = array(
|
|
'total_count' => 0,
|
|
'total_size' => 0,
|
|
'usage_percent' => 0,
|
|
'available_space' => 0,
|
|
'backends' => array()
|
|
);
|
|
|
|
// Calculate local storage stats
|
|
if (isset($this->storage_backends['local']) && $this->storage_backends['local']['available']) {
|
|
$local_path = $this->storage_backends['local']['settings']['path'];
|
|
if (is_dir($local_path)) {
|
|
$total_size = 0;
|
|
$file_count = 0;
|
|
|
|
$files = glob($local_path . '*.{zip,tar,gz,bz2}', GLOB_BRACE);
|
|
if ($files) {
|
|
foreach ($files as $file) {
|
|
if (is_file($file)) {
|
|
$total_size += filesize($file);
|
|
$file_count++;
|
|
}
|
|
}
|
|
}
|
|
|
|
$stats['total_count'] = $file_count;
|
|
$stats['total_size'] = $total_size;
|
|
|
|
// Calculate disk usage percentage
|
|
$disk_total = disk_total_space($local_path);
|
|
$disk_free = disk_free_space($local_path);
|
|
if ($disk_total && $disk_free) {
|
|
$stats['usage_percent'] = round((($disk_total - $disk_free) / $disk_total) * 100, 2);
|
|
$stats['available_space'] = $disk_free;
|
|
}
|
|
|
|
$stats['backends']['local'] = array(
|
|
'count' => $file_count,
|
|
'size' => $total_size,
|
|
'path' => $local_path
|
|
);
|
|
}
|
|
}
|
|
|
|
// Add S3 stats if available (placeholder for now)
|
|
if (isset($this->storage_backends['s3']) && $this->storage_backends['s3']['available']) {
|
|
$stats['backends']['s3'] = array(
|
|
'count' => 0,
|
|
'size' => 0,
|
|
'bucket' => $this->storage_backends['s3']['settings']['bucket']
|
|
);
|
|
}
|
|
|
|
return $stats;
|
|
}
|
|
} |