#!/usr/bin/env php !str_starts_with((string) $arg, '--')), ); $dump = $positional[0] ?? ''; $sourceDir = $positional[1] ?? ''; if ($dump === '' || $sourceDir === '') { fwrite(STDERR, "Usage : php scripts/import-asm3-photos.php [--apply]\n"); exit(2); } if (!is_file($dump) || !is_dir($sourceDir)) { throw new RuntimeException('Dump ASM3 ou dossier des photos introuvable.'); } $hash = hash_file('sha256', $dump); $source = Asm3Analyzer::rows($dump, ['media', 'dbfs']); $db = DB::pdo(); $links = []; $q = $db->prepare("SELECT source_id,target_id FROM asm3_import_links WHERE source_sha256=? AND entity_type='animal'"); $q->execute([$hash]); foreach ($q as $row) { $links[(int) $row['source_id']] = (int) $row['target_id']; } $dbfs = []; foreach ($source['dbfs'] ?? [] as $row) { $dbfs[(int) $row['ID']] = $row; } $items = []; $errors = []; foreach ($source['media'] ?? [] as $media) { if ((int) $media['LINKTYPEID'] !== 0 || !str_starts_with((string) $media['MEDIAMIMETYPE'], 'image/')) { continue; } $sourceAnimal = (int) $media['LINKID']; $targetAnimal = $links[$sourceAnimal] ?? 0; $physical = $dbfs[(int) $media['DBFSID']] ?? null; $filename = $physical ? basename(str_replace('file:', '', (string) $physical['URL'])) : ''; $path = $sourceDir . '/' . $filename; if (!$targetAnimal) { $errors[] = 'Animal ASM3 non relié : ' . $sourceAnimal; } elseif ($filename === '' || !is_file($path)) { $errors[] = 'Fichier absent pour le média ASM3 ' . $media['ID']; } else { $inspection = ImageService::inspect($path); $items[] = [ 'media' => $media, 'animal' => $targetAnimal, 'path' => $path, 'physical' => $filename, 'inspection' => $inspection, ]; } } if ($errors) { throw new RuntimeException(implode("\n", $errors)); } $targets = array_values(array_unique(array_column($items, 'animal'))); $primaryCount = count(array_filter($items, static fn(array $i): bool => (int) $i['media']['WEBSITEPHOTO'] === 1)); $summary = [ 'mode' => $apply ? 'APPLIQUE' : 'SIMULATION', 'photos_found' => count($items), 'animals' => count($targets), 'primary_photos' => $primaryCount, 'public_photos' => count($items), 'already_imported' => 0, 'created' => 0, 'optimized' => 0, 'backup' => null, ]; foreach ($items as $item) { $original = 'ASM3-' . (int) $item['media']['ID'] . '-' . (string) $item['media']['MEDIANAME']; $s = $db->prepare('SELECT 1 FROM animal_photos WHERE animal_id=? AND original_name=?'); $s->execute([$item['animal'], $original]); if ($s->fetchColumn()) { $summary['already_imported']++; } } if (!$apply) { echo json_encode($summary, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), "\n"; exit(); } $backup = BackupService::create('avant-import-photos-asm3'); $summary['backup'] = $backup['name'] ?? null; $written = []; $db->beginTransaction(); try { $db->prepare( "INSERT INTO asm3_import_runs(source_name,source_sha256,status,summary_json,backup_name) VALUES(?,?,'completed','{}',?)", )->execute([basename($dump) . ' · photos', $hash, $summary['backup']]); $run = (int) $db->lastInsertId(); $reset = $db->prepare('UPDATE animal_photos SET is_primary=0 WHERE animal_id=?'); foreach ($targets as $animalId) { $reset->execute([$animalId]); } $insert = $db->prepare( 'INSERT INTO animal_photos(animal_id,filename,original_name,mime,size_bytes,is_primary,is_public,caption,created_at) VALUES(?,?,?,?,?,?,1,?,COALESCE(?,datetime(\'now\')))', ); $link = $db->prepare( "INSERT OR IGNORE INTO asm3_import_links(source_sha256,entity_type,source_id,target_id,import_run_id) VALUES(?,'media',?,?,?)", ); foreach ($items as $item) { $media = $item['media']; $original = 'ASM3-' . (int) $media['ID'] . '-' . (string) $media['MEDIANAME']; $existing = $db->prepare('SELECT id FROM animal_photos WHERE animal_id=? AND original_name=?'); $existing->execute([$item['animal'], $original]); $photoId = (int) $existing->fetchColumn(); if (!$photoId) { $stored = ImageService::storeLocal( $item['path'], $root . '/public/media/animals/' . $item['animal'], 'asm3_' . (int) $media['ID'], 'photo', ); $written[] = $stored['path']; $caption = trim((string) ($media['MEDIANOTES'] ?? '')); $caption = $caption !== '' ? 'Archive ASM3 · ' . $caption : 'Archive ASM3'; $insert->execute([ $item['animal'], $stored['filename'], $original, $stored['mime'], $stored['size'], (int) $media['WEBSITEPHOTO'] === 1 ? 1 : 0, $caption, substr((string) $media['CREATEDDATE'], 0, 19) ?: null, ]); $photoId = (int) $db->lastInsertId(); $summary['created']++; if ($stored['optimized']) { $summary['optimized']++; } } elseif ((int) $media['WEBSITEPHOTO'] === 1) { $db->prepare('UPDATE animal_photos SET is_primary=1,is_public=1 WHERE id=?')->execute([$photoId]); } else { $db->prepare('UPDATE animal_photos SET is_public=1 WHERE id=?')->execute([$photoId]); } $link->execute([$hash, (int) $media['ID'], $photoId, $run]); } $db->prepare('UPDATE asm3_import_runs SET summary_json=? WHERE id=?')->execute([ json_encode($summary, JSON_UNESCAPED_UNICODE), $run, ]); if ($db->query('PRAGMA foreign_key_check')->fetchAll()) { throw new RuntimeException('Violation de clé étrangère après import.'); } $db->commit(); } catch (Throwable $e) { if ($db->inTransaction()) { $db->rollBack(); } foreach ($written as $path) { @unlink($path); } throw $e; } echo json_encode($summary, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), "\n";