117 lines
3.3 KiB
PHP
117 lines
3.3 KiB
PHP
|
|
<?php
|
||
|
|
|
||
|
|
namespace App\Console\Commands;
|
||
|
|
|
||
|
|
use Illuminate\Console\Command;
|
||
|
|
use App\Models\Media;
|
||
|
|
use App\Models\Page;
|
||
|
|
use Illuminate\Support\Facades\File;
|
||
|
|
use Illuminate\Support\Facades\Storage;
|
||
|
|
|
||
|
|
class OrphanedMediaWatcher extends Command
|
||
|
|
{
|
||
|
|
/**
|
||
|
|
* The name and signature of the console command.
|
||
|
|
*
|
||
|
|
* @var string
|
||
|
|
*/
|
||
|
|
protected $signature = 'sw:media:cleanup {--dry-run}';
|
||
|
|
|
||
|
|
/**
|
||
|
|
* The console command description.
|
||
|
|
*
|
||
|
|
* @var string
|
||
|
|
*/
|
||
|
|
protected $description = 'Cleanup unreferenced media files and JIT caches.';
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Execute the console command.
|
||
|
|
*/
|
||
|
|
public function handle()
|
||
|
|
{
|
||
|
|
$this->info('Starting Orphaned Media Watcher...');
|
||
|
|
$dryRun = $this->option('dry-run');
|
||
|
|
|
||
|
|
// 1. Cleanup JIT cache (safe to delete anytime)
|
||
|
|
$cachePath = storage_path('app/public/media/cache');
|
||
|
|
if (is_dir($cachePath)) {
|
||
|
|
$this->info('Cleaning up JIT cache...');
|
||
|
|
if ($dryRun) {
|
||
|
|
$this->info('Dry-run: Would delete directory ' . $cachePath);
|
||
|
|
} else {
|
||
|
|
File::deleteDirectory($cachePath);
|
||
|
|
mkdir($cachePath, 0755, true);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// 2. Identification of orphaned originals
|
||
|
|
$this->info('Scanning for orphaned media files in database...');
|
||
|
|
|
||
|
|
$mediaRecords = Media::all();
|
||
|
|
$referencedMediaIds = $this->getReferencedMediaIds();
|
||
|
|
|
||
|
|
$orphansCount = 0;
|
||
|
|
|
||
|
|
foreach ($mediaRecords as $media) {
|
||
|
|
if (!in_array($media->id, $referencedMediaIds)) {
|
||
|
|
$orphansCount++;
|
||
|
|
$this->warn("Orphaned media found: ID {$media->id}, Filename: {$media->filename}");
|
||
|
|
|
||
|
|
if ($dryRun) {
|
||
|
|
$this->info("Dry-run: Would delete media ID {$media->id} and file {$media->path}");
|
||
|
|
} else {
|
||
|
|
Storage::disk('public')->delete($media->path);
|
||
|
|
$media->delete();
|
||
|
|
$this->info("Deleted media ID {$media->id}");
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
if ($orphansCount === 0) {
|
||
|
|
$this->info("\nNo orphaned media files found.");
|
||
|
|
} else {
|
||
|
|
$this->info("\nProcessed {$orphansCount} orphaned media files.");
|
||
|
|
}
|
||
|
|
|
||
|
|
$this->info('Media cleanup completed.');
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Scans all pages for media IDs.
|
||
|
|
*/
|
||
|
|
protected function getReferencedMediaIds(): array
|
||
|
|
{
|
||
|
|
$ids = [];
|
||
|
|
$pages = Page::all();
|
||
|
|
|
||
|
|
foreach ($pages as $page) {
|
||
|
|
$content = $page->content; // Array due to cast
|
||
|
|
if (is_array($content)) {
|
||
|
|
$ids = array_merge($ids, $this->scanBlocksForMediaIds($content));
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return array_unique($ids);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Recursively scan blocks for media IDs.
|
||
|
|
*/
|
||
|
|
protected function scanBlocksForMediaIds(array $blocks): array
|
||
|
|
{
|
||
|
|
$ids = [];
|
||
|
|
foreach ($blocks as $block) {
|
||
|
|
if (isset($block['type']) && $block['type'] === 'media' && isset($block['media_id'])) {
|
||
|
|
$ids[] = (int) $block['media_id'];
|
||
|
|
}
|
||
|
|
|
||
|
|
// In case of nested blocks in the future
|
||
|
|
if (isset($block['children']) && is_array($block['children'])) {
|
||
|
|
$ids = array_merge($ids, $this->scanBlocksForMediaIds($block['children']));
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return $ids;
|
||
|
|
}
|
||
|
|
}
|