Files
michaelschiemer/scripts/maintenance/populate_images_from_filesystem.php

218 lines
6.4 KiB
PHP
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
declare(strict_types=1);
/**
* Script to populate the database with images found in the filesystem
*
* This script scans storage/uploads/ for image files and creates database records
* to match the existing files on disk.
*/
require_once 'vendor/autoload.php';
use App\Framework\DateTime\SystemClock;
use App\Framework\Id\Ulid\UlidGenerator;
class ImageMigrationScript
{
private PDO $db;
private SystemClock $clock;
private string $uploadsPath;
public function __construct()
{
$this->clock = new SystemClock();
$this->uploadsPath = __DIR__ . '/storage/uploads';
// Initialize database
$this->initializeDatabase();
}
private function initializeDatabase(): void
{
// Simple SQLite connection for this script
$pdo = new PDO('sqlite:' . __DIR__ . '/database.sqlite');
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$this->db = $pdo;
}
public function run(): void
{
echo "🔍 Scanning for images in: {$this->uploadsPath}\n";
if (!is_dir($this->uploadsPath)) {
echo "❌ Uploads directory not found: {$this->uploadsPath}\n";
return;
}
$imageFiles = $this->findImageFiles();
echo "📁 Found " . count($imageFiles) . " image files\n";
if (empty($imageFiles)) {
echo " No images to migrate\n";
return;
}
$this->migrateImages($imageFiles);
echo "✅ Migration completed!\n";
}
private function findImageFiles(): array
{
$iterator = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($this->uploadsPath)
);
$imageFiles = [];
$allowedExtensions = ['jpg', 'jpeg', 'png', 'gif', 'webp'];
foreach ($iterator as $file) {
if (!$file->isFile()) {
continue;
}
$extension = strtolower($file->getExtension());
if (!in_array($extension, $allowedExtensions)) {
continue;
}
$imageFiles[] = [
'path' => $file->getPathname(),
'filename' => $file->getFilename(),
'extension' => $extension,
'size' => $file->getSize(),
'mtime' => $file->getMTime()
];
}
return $imageFiles;
}
private function migrateImages(array $imageFiles): void
{
// Check current database schema
$this->checkDatabaseSchema();
$migrated = 0;
$errors = 0;
foreach ($imageFiles as $fileInfo) {
try {
$this->migrateImageFile($fileInfo);
$migrated++;
echo "✓ Migrated: {$fileInfo['filename']}\n";
} catch (Exception $e) {
$errors++;
echo "❌ Error migrating {$fileInfo['filename']}: " . $e->getMessage() . "\n";
}
}
echo "\n📊 Summary:\n";
echo " Migrated: $migrated\n";
echo " Errors: $errors\n";
}
private function checkDatabaseSchema(): void
{
// Check what columns exist in the images table
$stmt = $this->db->query("PRAGMA table_info(images)");
$columns = $stmt->fetchAll(PDO::FETCH_ASSOC);
echo "📋 Database schema (images table):\n";
foreach ($columns as $column) {
echo " - {$column['name']} ({$column['type']})\n";
}
echo "\n";
}
private function migrateImageFile(array $fileInfo): void
{
$fullPath = $fileInfo['path'];
// Extract image dimensions if possible
$imageInfo = @getimagesize($fullPath);
$width = $imageInfo[0] ?? 0;
$height = $imageInfo[1] ?? 0;
// Generate ULID
$ulidGenerator = new UlidGenerator();
$ulidString = $ulidGenerator->generate($this->clock);
// Calculate hash
$hashValue = hash_file('sha256', $fullPath);
// Determine MIME type
$mimeTypeString = match (strtolower($fileInfo['extension'])) {
'jpg', 'jpeg' => 'image/jpeg',
'png' => 'image/png',
'gif' => 'image/gif',
'webp' => 'image/webp',
default => 'image/jpeg'
};
// Extract original filename from the complex filename structure
$originalFilename = $this->extractOriginalFilename($fileInfo['filename']);
// Get relative path from storage root
$relativePath = str_replace($this->uploadsPath . '/', '', $fullPath);
$pathOnly = dirname($relativePath);
// Insert into database using the correct table structure
$sql = "INSERT INTO images (
ulid, filename, original_filename, mime_type, file_size,
width, height, hash, path, created_at, updated_at
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";
$stmt = $this->db->prepare($sql);
$now = date('Y-m-d H:i:s');
$stmt->execute([
$ulidString,
$fileInfo['filename'],
$originalFilename,
$mimeTypeString,
$fileInfo['size'],
$width,
$height,
$hashValue,
$pathOnly,
$now,
$now
]);
}
private function extractOriginalFilename(string $filename): string
{
// Pattern for files like: BFWCAKKEHTKF5SYR_6626fc6b...cd1_original.png
if (preg_match('/^[A-Z0-9]{16}_[a-f0-9]{64}_original\.(.+)$/', $filename, $matches)) {
// This is an original file, try to find the pattern in other files
$basePattern = substr($filename, 0, strpos($filename, '_original.'));
// For now, just return a cleaned version
return "original." . $matches[1];
}
// Pattern for simple files like: 00MF9VW9R36NJN3VCFSTS2CK6R.jpg
if (preg_match('/^[A-Z0-9]{26}\.(.+)$/', $filename, $matches)) {
return "image." . $matches[1];
}
// Fallback: return as-is
return $filename;
}
}
// Run the migration
echo "🚀 Starting image migration from filesystem to database...\n\n";
try {
$migration = new ImageMigrationScript();
$migration->run();
} catch (Exception $e) {
echo "💥 Migration failed: " . $e->getMessage() . "\n";
echo "Stack trace:\n" . $e->getTraceAsString() . "\n";
exit(1);
}
echo "\n🎉 Migration script completed!\n";