165 lines
6.4 KiB
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";
|