tigerstyle-life9/includes/storage/class-storage-s3.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

277 lines
8.4 KiB
PHP

<?php
/**
* S3 Compatible Storage Backend
*
* Handles Amazon S3 and compatible storage (MinIO) for backups
*
* @package TigerStyleLife9
* @since 1.0.0
*/
// Exit if accessed directly
if (!defined('ABSPATH')) {
exit;
}
/**
* S3 storage backend class
*
* @since 1.0.0
*/
class TigerStyle_Life9_Storage_S3 extends TigerStyle_Life9_Storage_Backend {
/**
* Store file to S3 storage
*
* @param string $file_path Local file path
* @param array $config Storage configuration
* @return array Storage result
*/
public function store($file_path, $config = []) {
if (!file_exists($file_path)) {
throw new Exception('🐱 Meow! Source file does not exist - cats can\'t backup invisible files!');
}
// Validate required configuration
$required_fields = ['access_key', 'secret_key', 'bucket', 'region'];
foreach ($required_fields as $field) {
if (empty($config[$field])) {
throw new Exception("🐱 Paws! Missing required S3 configuration: {$field}");
}
}
try {
// Create S3 client
$s3_config = [
'version' => 'latest',
'region' => $config['region'],
'credentials' => [
'key' => $config['access_key'],
'secret' => $config['secret_key'],
]
];
// Add custom endpoint for MinIO
if (!empty($config['endpoint'])) {
$s3_config['endpoint'] = $config['endpoint'];
$s3_config['use_path_style_endpoint'] = true;
}
if (!class_exists('Aws\S3\S3Client')) {
throw new Exception('🐱 Hiss! AWS SDK not found. Install it with: composer require aws/aws-sdk-php');
}
$s3 = new Aws\S3\S3Client($s3_config);
// Generate remote path with cat-themed organization
$remote_path = $this->generate_cat_remote_path($file_path);
// Upload file
$result = $s3->putObject([
'Bucket' => $config['bucket'],
'Key' => $remote_path,
'SourceFile' => $file_path,
'Metadata' => [
'created-by' => 'tigerstyle-life9',
'backup-type' => 'cat-lives',
'created-at' => date('c'),
'purr-factor' => 'maximum'
]
]);
return [
'url' => $result['ObjectURL'] ?? '',
'remote_path' => $remote_path,
'storage_id' => $remote_path,
'metadata' => [
'size' => filesize($file_path),
'created' => date('Y-m-d H:i:s'),
'etag' => $result['ETag'] ?? '',
'cat_rating' => '🐱🐱🐱🐱🐱'
]
];
} catch (Exception $e) {
throw new Exception('🐱 Cat-astrophic S3 upload failure: ' . $e->getMessage());
}
}
/**
* Retrieve file from S3 storage
*
* @param string $remote_path Remote file path or ID
* @param string $local_path Local destination path
* @param array $config Storage configuration
* @return bool Success status
*/
public function retrieve($remote_path, $local_path, $config = []) {
try {
$s3 = $this->create_s3_client($config);
$s3->getObject([
'Bucket' => $config['bucket'],
'Key' => $remote_path,
'SaveAs' => $local_path
]);
return true;
} catch (Exception $e) {
error_log('🐱 S3 download failed: ' . $e->getMessage());
return false;
}
}
/**
* Delete file from S3 storage
*
* @param string $remote_path Remote file path or ID
* @param array $config Storage configuration
* @return bool Success status
*/
public function delete($remote_path, $config = []) {
try {
$s3 = $this->create_s3_client($config);
$s3->deleteObject([
'Bucket' => $config['bucket'],
'Key' => $remote_path
]);
return true;
} catch (Exception $e) {
error_log('🐱 S3 deletion failed: ' . $e->getMessage());
return false;
}
}
/**
* Test S3 storage connection
*
* @param array $config Storage configuration
* @return bool Connection status
*/
public function test_connection($config = []) {
try {
$s3 = $this->create_s3_client($config);
// Test by checking if bucket exists and is accessible
$s3->headBucket(['Bucket' => $config['bucket']]);
return true;
} catch (Exception $e) {
error_log('🐱 S3 connection test failed: ' . $e->getMessage());
return false;
}
}
/**
* List backup files in S3
*
* @param array $config Storage configuration
* @return array List of backup files
*/
public function list_backups($config = []) {
try {
$s3 = $this->create_s3_client($config);
$result = $s3->listObjects([
'Bucket' => $config['bucket'],
'Prefix' => 'tigerstyle-life9/'
]);
$backups = [];
if (isset($result['Contents'])) {
foreach ($result['Contents'] as $object) {
$backups[] = [
'key' => $object['Key'],
'size' => $object['Size'],
'modified' => $object['LastModified']->format('Y-m-d H:i:s'),
'cat_rating' => $this->calculate_cat_rating($object['Size'])
];
}
}
return $backups;
} catch (Exception $e) {
error_log('🐱 Failed to list S3 backups: ' . $e->getMessage());
return [];
}
}
/**
* Create S3 client with proper configuration
*
* @param array $config Storage configuration
* @return Aws\S3\S3Client
*/
private function create_s3_client($config) {
$s3_config = [
'version' => 'latest',
'region' => $config['region'],
'credentials' => [
'key' => $config['access_key'],
'secret' => $config['secret_key'],
]
];
// Add custom endpoint for MinIO
if (!empty($config['endpoint'])) {
$s3_config['endpoint'] = $config['endpoint'];
$s3_config['use_path_style_endpoint'] = true;
}
return new Aws\S3\S3Client($s3_config);
}
/**
* Generate cat-themed remote path
*
* @param string $file_path Local file path
* @return string Remote file path
*/
protected function generate_cat_remote_path($file_path) {
$filename = basename($file_path);
$date_path = date('Y/m/d');
$hour = date('H');
// Add cat-themed hour descriptions
$cat_time = '';
if ($hour >= 0 && $hour < 6) {
$cat_time = 'midnight-prowl';
} elseif ($hour >= 6 && $hour < 12) {
$cat_time = 'morning-stretch';
} elseif ($hour >= 12 && $hour < 18) {
$cat_time = 'afternoon-nap';
} else {
$cat_time = 'evening-hunt';
}
return "tigerstyle-life9/{$date_path}/{$cat_time}/{$filename}";
}
/**
* Calculate cat rating based on file size
*
* @param int $size File size in bytes
* @return string Cat rating
*/
private function calculate_cat_rating($size) {
$size_mb = $size / (1024 * 1024);
if ($size_mb < 10) {
return '🐱';
} elseif ($size_mb < 50) {
return '🐱🐱';
} elseif ($size_mb < 100) {
return '🐱🐱🐱';
} elseif ($size_mb < 500) {
return '🐱🐱🐱🐱';
} else {
return '🐱🐱🐱🐱🐱';
}
}
}