cms/app/Console/Commands/SiteRestore.php

139 lines
4.7 KiB
PHP
Raw Normal View History

<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Cache;
class SiteRestore extends Command
{
/**
* Cache key for restoration progress.
*/
const PROGRESS_KEY = 'site_restore_progress';
/**
* Update progress in Cache.
*
* @param string $message Progress message.
* @param int $percentage Progress percentage.
* @return void
*/
protected function updateProgress(string $message, int $percentage): void
{
Cache::put(self::PROGRESS_KEY, [
'status' => $message,
'percent' => $percentage,
'timestamp' => now()->timestamp,
], 300); // 5-minute TTL
$this->info("[$percentage%] $message");
}
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'sw:site:restore {filename} {--force}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Restore the site from a backup archive.';
/**
* Execute the console command.
*/
public function handle()
{
$filename = $this->argument('filename');
$backupPath = storage_path("app/backups/{$filename}");
if (!File::exists($backupPath)) {
$this->error("Backup file not found: {$filename}");
return 1;
}
if ($this->option('force') || $this->confirm("Are you sure you want to restore from {$filename}? This will overwrite your current database and files.")) {
$this->updateProgress("Starting restoration...", 5);
$tempPath = storage_path('app/temp_restore');
if (File::exists($tempPath)) {
File::deleteDirectory($tempPath);
}
mkdir($tempPath, 0755, true);
// Extract archive
$this->updateProgress("Extracting backup archive...", 20);
$command = "tar -xzf " . escapeshellarg($backupPath) . " -C " . escapeshellarg($tempPath);
exec($command, $output, $returnVar);
if ($returnVar !== 0) {
$this->updateProgress("Failed to extract backup.", 0);
$this->error("Failed to extract backup.");
File::deleteDirectory($tempPath);
return 1;
}
// Database restoration
$this->updateProgress("Restoring database...", 40);
$dbPath = config('database.connections.sqlite.database');
$dbFilename = basename($dbPath);
// Skip restoration if database is in memory (tests)
if ($dbPath === ':memory:') {
$this->warn('Skipping database restoration because it is in-memory.');
// We'll still progress
} else {
// Look for database file in temp path. It should now be in the root of the temp path.
$extractedDbFile = $tempPath . '/' . $dbFilename;
if (File::exists($extractedDbFile)) {
File::copy($extractedDbFile, $dbPath);
} else {
// Compatibility for old absolute path backups (tar stripping leading slash)
$oldPathDb = $tempPath . ltrim($dbPath, '/');
if (File::exists($oldPathDb)) {
File::copy($oldPathDb, $dbPath);
} else {
$this->warn("Database file ($dbFilename) not found in backup.");
}
}
}
// Media restoration
$this->updateProgress("Restoring media files...", 60);
$extractedMedia = $tempPath . '/media';
if (File::exists($extractedMedia)) {
$targetMedia = storage_path('app/public/media');
if (File::exists($targetMedia)) {
File::deleteDirectory($targetMedia);
}
File::moveDirectory($extractedMedia, $targetMedia);
}
// Themes restoration
$this->updateProgress("Restoring themes...", 80);
$extractedThemes = $tempPath . '/themes';
if (File::exists($extractedThemes)) {
$targetThemes = base_path('themes');
if (File::exists($targetThemes)) {
File::deleteDirectory($targetThemes);
}
File::moveDirectory($extractedThemes, $targetThemes);
}
$this->updateProgress("Cleaning up...", 95);
File::deleteDirectory($tempPath);
$this->updateProgress("Restoration completed successfully.", 100);
return 0;
}
$this->info("Restoration cancelled.");
return 0;
}
}