t('admin.exports_title'), 'datasets' => ExportService::catalog()]); } public static function downloadExport(): void { self::adminOnly(); $type = (string) ($_GET['type'] ?? ''); try { AuditService::log( 'data_export', '/admin/exports/download', $type === 'all' ? 'Export complet des données' : 'Export CSV : ' . $type, ); if ($type === 'all') { ExportService::downloadZip(); } else { ExportService::downloadCsv($type); } } catch (InvalidArgumentException) { http_response_code(404); echo h(t('admin.unknown_export')); } catch (Throwable $e) { error_log('Échec export ' . $type . ' : ' . $e->getMessage()); http_response_code(500); echo h(t('admin.export_failed')); } } public static function register(): void { [$rows, $year, $q, $species, $sort, $dir] = self::registerData(); render( 'admin_register.php', compact('rows', 'year', 'q', 'species', 'sort', 'dir') + ['title' => t('admin.title')], ); } public static function registerPdf(): void { [$rows, $year, $q, $species, $sort, $dir] = self::registerData(); $generatedAt = new DateTimeImmutable('now', new DateTimeZone('Europe/Paris')); header('Content-Type: text/html; charset=UTF-8'); require __DIR__ . '/../Views/admin_register_print.php'; } private static function registerData(): array { $pdo = DB::pdo(); $year = isset($_GET['year']) ? (int) $_GET['year'] : (int) date('Y'); $q = trim((string) ($_GET['q'] ?? '')); $species = trim((string) ($_GET['species'] ?? '')); $allowedSort = [ 'date' => 'm.created_at', 'name' => 'a.name', 'species' => 'a.species', 'sex' => 'a.sex', 'type' => 'm.kind', ]; $sort = (string) ($_GET['sort'] ?? 'date'); if (!isset($allowedSort[$sort])) { $sort = 'date'; } $dir = strtolower((string) ($_GET['dir'] ?? 'asc')) === 'desc' ? 'desc' : 'asc'; $sql = "SELECT m.*, a.id AS animal_id, a.internal_code, a.name, a.sex, a.species, a.birth_date, a.chip_id, d.cause_code death_cause_code,d.cause_details death_cause_details,d.euthanized death_euthanized,d.body_disposition death_disposition,d.cremation_date, vet.name death_veterinarian,crem.name death_crematorium FROM animal_movements m JOIN animals a ON a.id=m.animal_id LEFT JOIN animal_deaths d ON d.animal_id=m.animal_id AND m.kind='exit' AND lower(m.lieu) IN ('décès','deces','décès de l''animal','deces de l''animal') LEFT JOIN directory_contacts vet ON vet.id=d.veterinarian_contact_id LEFT JOIN directory_contacts crem ON crem.id=d.crematorium_contact_id WHERE a.deleted_at IS NULL AND a.archived_at IS NULL AND m.created_at BETWEEN :ys AND :ye"; $params = [':ys' => sprintf('%04d-01-01 00:00:00', $year), ':ye' => sprintf('%04d-12-31 23:59:59', $year)]; if ($q !== '') { $sql .= ' AND (a.name LIKE :q OR a.internal_code LIKE :q OR a.chip_id LIKE :q)'; $params[':q'] = "%$q%"; } if ($species !== '') { $sql .= ' AND lower(a.species)=:species'; $params[':species'] = strtolower($species); } $sql .= ' ORDER BY ' . $allowedSort[$sort] . ' ' . strtoupper($dir); $statement = $pdo->prepare($sql); $statement->execute($params); return [$statement->fetchAll(PDO::FETCH_ASSOC), $year, $q, $species, $sort, $dir]; } private static function adminOnly(): void { if (!PermissionService::can('administrative', 'view')) { http_response_code(403); echo h(t('admin.forbidden')); exit(); } } }