Globinours/scripts/import-asm3-photos.php

165 lines
6.4 KiB
PHP

#!/usr/bin/env php
<?php
declare(strict_types=1);
$root = dirname(__DIR__);
require $root . '/app/Services/DB.php';
require $root . '/app/Services/BackupService.php';
require $root . '/app/Services/ImageService.php';
require $root . '/app/Services/Asm3Analyzer.php';
$apply = in_array('--apply', $argv, true);
$positional = array_values(
array_filter(array_slice($argv, 1), static fn($arg) => !str_starts_with((string) $arg, '--')),
);
$dump = $positional[0] ?? '';
$sourceDir = $positional[1] ?? '';
if ($dump === '' || $sourceDir === '') {
fwrite(STDERR, "Usage : php scripts/import-asm3-photos.php <dump.sql> <dossier-photos> [--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";