['logo', 'document', 'reglement', 'statut'], 'user' => ['avatar', 'photo'], 'operation' => ['planning', 'liste', 'export', 'backup'], 'passage' => ['recu', 'photo', 'justificatif', 'carte'] ]; // Extensions autorisées private const ALLOWED_EXTENSIONS = [ 'pdf', 'jpg', 'jpeg', 'png', 'gif', 'webp', 'xlsx', 'xls', 'json', 'csv' ]; public function __construct() { $this->db = Database::getInstance(); $this->appConfig = AppConfig::getInstance(); } /** * Récupère les informations utilisateur (rôle et entité) */ private function getUserInfo(int $userId): ?array { try { $stmt = $this->db->prepare('SELECT fk_entite, fk_role FROM users WHERE id = ?'); $stmt->execute([$userId]); return $stmt->fetch(PDO::FETCH_ASSOC) ?: null; } catch (Exception $e) { LogService::log('Erreur lors de la récupération des infos utilisateur', [ 'level' => 'error', 'error' => $e->getMessage(), 'userId' => $userId ]); return null; } } /** * Valide qu'un chemin est autorisé pour l'utilisateur */ private function validatePath(string $path, int $userRole, int $userEntiteId): bool { // Empêcher les traversées de répertoire if (strpos($path, '..') !== false || strpos($path, './') !== false) { return false; } // Normaliser le chemin $path = trim($path, '/'); // Super admin : accès total if ($userRole > 2) { return true; } // Admin entité : limité à son entité if ($userRole == 2) { return strpos($path, "entites/{$userEntiteId}") === 0 || $path === "entites/{$userEntiteId}"; } return false; } /** * Vérifie si l'utilisateur peut accéder à un fichier */ private function canAccessFile(int $fileId, int $userRole, int $userEntiteId): bool { try { $stmt = $this->db->prepare('SELECT fk_entite FROM medias WHERE id = ?'); $stmt->execute([$fileId]); $file = $stmt->fetch(PDO::FETCH_ASSOC); if (!$file) { return false; } // Super admin : accès total if ($userRole > 2) { return true; } // Admin entité : seulement ses fichiers return (int)$file['fk_entite'] === $userEntiteId; } catch (Exception $e) { LogService::log('Erreur lors de la vérification d\'accès au fichier', [ 'level' => 'error', 'error' => $e->getMessage(), 'fileId' => $fileId, 'userId' => Session::getUserId() ]); return false; } } /** * Extrait et valide les paramètres de filtrage */ private function extractFilters(array $params): array { return [ 'page' => max(1, (int)($params['page'] ?? 1)), 'per_page' => min(100, max(1, (int)($params['per_page'] ?? 50))), 'search' => !empty($params['search']) ? trim($params['search']) : null, 'type' => !empty($params['type']) && in_array($params['type'], self::ALLOWED_EXTENSIONS) ? $params['type'] : null, 'category' => !empty($params['category']) ? trim($params['category']) : null, 'sort' => in_array($params['sort'] ?? '', ['name', 'date', 'size', 'type']) ? $params['sort'] : 'date', 'order' => in_array($params['order'] ?? '', ['asc', 'desc']) ? $params['order'] : 'desc' ]; } /** * Construit la clause WHERE pour les requêtes de recherche */ private function buildWhereClause(array $filters, int $userRole, int $userEntiteId, ?string $path = null): array { $conditions = []; $params = []; // Restriction par entité selon le rôle if ($userRole == 2) { $conditions[] = 'm.fk_entite = ?'; $params[] = $userEntiteId; } // Filtrage par chemin si spécifié if ($path !== null) { $conditions[] = 'm.file_path LIKE ?'; $params[] = "uploads/{$path}%"; } // Recherche textuelle if ($filters['search']) { $searchTerm = '%' . $filters['search'] . '%'; $conditions[] = '(m.fichier LIKE ? OR m.original_name LIKE ? OR m.description LIKE ?)'; $params[] = $searchTerm; $params[] = $searchTerm; $params[] = $searchTerm; } // Filtrage par type (extension) if ($filters['type']) { $conditions[] = 'm.file_type = ?'; $params[] = $filters['type']; } // Filtrage par catégorie if ($filters['category']) { $conditions[] = 'm.file_category = ?'; $params[] = $filters['category']; } $whereClause = !empty($conditions) ? 'WHERE ' . implode(' AND ', $conditions) : ''; return [$whereClause, $params]; } /** * Construit la clause ORDER BY */ private function buildOrderClause(array $filters): string { $sortField = match ($filters['sort']) { 'name' => 'm.original_name', 'size' => 'm.file_size', 'type' => 'm.file_type', default => 'm.created_at' }; return "ORDER BY {$sortField} {$filters['order']}"; } /** * Navigation dans l'arborescence avec recherche et pagination */ public function browse(): void { try { $userId = Session::getUserId(); if (!$userId) { Response::json([ 'status' => 'error', 'message' => 'Vous devez être connecté pour effectuer cette action' ], 401); return; } $userInfo = $this->getUserInfo($userId); if (!$userInfo) { Response::json([ 'status' => 'error', 'message' => 'Informations utilisateur non trouvées' ], 404); return; } $userRole = (int)$userInfo['fk_role']; $userEntiteId = (int)$userInfo['fk_entite']; $path = $_GET['path'] ?? ''; $filters = $this->extractFilters($_GET); // Validation du chemin if (!$this->validatePath($path, $userRole, $userEntiteId)) { Response::json([ 'status' => 'error', 'message' => 'Accès refusé à ce répertoire' ], 403); return; } // Construction de la requête [$whereClause, $params] = $this->buildWhereClause($filters, $userRole, $userEntiteId, $path); $orderClause = $this->buildOrderClause($filters); // Requête pour compter le total $countSql = " SELECT COUNT(*) as total FROM medias m {$whereClause} "; $stmt = $this->db->prepare($countSql); $stmt->execute($params); $totalItems = (int)$stmt->fetchColumn(); // Calcul de la pagination $totalPages = ceil($totalItems / $filters['per_page']); $offset = ($filters['page'] - 1) * $filters['per_page']; // Requête principale avec pagination $sql = " SELECT m.id, m.fichier, m.original_name, m.file_type, m.file_category, m.file_size, m.file_path, m.description, m.created_at, m.fk_user_creat, u.encrypted_name as creator_name FROM medias m LEFT JOIN users u ON u.id = m.fk_user_creat {$whereClause} {$orderClause} LIMIT ? OFFSET ? "; $params[] = $filters['per_page']; $params[] = $offset; $stmt = $this->db->prepare($sql); $stmt->execute($params); $files = $stmt->fetchAll(PDO::FETCH_ASSOC); // Déchiffrer les noms des créateurs foreach ($files as &$file) { if ($file['creator_name']) { $file['creator_name'] = ApiService::decryptData($file['creator_name']); } unset($file['encrypted_name']); } // Statistiques rapides $statsSql = " SELECT COUNT(*) as total_files, SUM(m.file_size) as total_size, m.file_category, COUNT(*) as category_count FROM medias m {$whereClause} GROUP BY m.file_category "; $stmt = $this->db->prepare($statsSql); $stmt->execute(array_slice($params, 0, -2)); // Enlever LIMIT et OFFSET $stats = $stmt->fetchAll(PDO::FETCH_ASSOC); $summary = [ 'total_files' => $totalItems, 'total_size' => array_sum(array_column($stats, 'total_size')), 'by_category' => [] ]; foreach ($stats as $stat) { if ($stat['file_category']) { $summary['by_category'][$stat['file_category']] = (int)$stat['category_count']; } } Response::json([ 'status' => 'success', 'current_path' => $path, 'parent_path' => dirname($path) !== '.' ? dirname($path) : null, 'pagination' => [ 'current_page' => $filters['page'], 'per_page' => $filters['per_page'], 'total_items' => $totalItems, 'total_pages' => $totalPages, 'has_next' => $filters['page'] < $totalPages, 'has_prev' => $filters['page'] > 1 ], 'filters' => [ 'search' => $filters['search'], 'type' => $filters['type'], 'category' => $filters['category'], 'sort' => $filters['sort'], 'order' => $filters['order'] ], 'files' => $files, 'summary' => $summary ], 200); } catch (Exception $e) { LogService::log('Erreur lors de la navigation des fichiers', [ 'level' => 'error', 'error' => $e->getMessage(), 'userId' => $userId ?? null, 'path' => $_GET['path'] ?? null ]); Response::json([ 'status' => 'error', 'message' => 'Erreur lors de la navigation des fichiers' ], 500); } } /** * Liste des fichiers par support avec recherche et pagination */ public function listBySupport(string $support, string $supportId): void { try { $userId = Session::getUserId(); if (!$userId) { Response::json([ 'status' => 'error', 'message' => 'Vous devez être connecté pour effectuer cette action' ], 401); return; } $userInfo = $this->getUserInfo($userId); if (!$userInfo) { Response::json([ 'status' => 'error', 'message' => 'Informations utilisateur non trouvées' ], 404); return; } $userRole = (int)$userInfo['fk_role']; $userEntiteId = (int)$userInfo['fk_entite']; $supportIdInt = (int)$supportId; $filters = $this->extractFilters($_GET); // Construction de la requête de base $conditions = ['m.support = ?', 'm.support_id = ?']; $params = [$support, $supportIdInt]; // Restriction par entité selon le rôle if ($userRole == 2) { $conditions[] = 'm.fk_entite = ?'; $params[] = $userEntiteId; } // Recherche textuelle if ($filters['search']) { $searchTerm = '%' . $filters['search'] . '%'; $conditions[] = '(m.fichier LIKE ? OR m.original_name LIKE ? OR m.description LIKE ?)'; $params[] = $searchTerm; $params[] = $searchTerm; $params[] = $searchTerm; } // Filtrage par type if ($filters['type']) { $conditions[] = 'm.file_type = ?'; $params[] = $filters['type']; } // Filtrage par catégorie if ($filters['category']) { $conditions[] = 'm.file_category = ?'; $params[] = $filters['category']; } $whereClause = 'WHERE ' . implode(' AND ', $conditions); $orderClause = $this->buildOrderClause($filters); // Compter le total $countSql = "SELECT COUNT(*) as total FROM medias m {$whereClause}"; $stmt = $this->db->prepare($countSql); $stmt->execute($params); $totalItems = (int)$stmt->fetchColumn(); // Calcul pagination $totalPages = ceil($totalItems / $filters['per_page']); $offset = ($filters['page'] - 1) * $filters['per_page']; // Requête principale $sql = " SELECT m.id, m.fichier, m.original_name, m.file_type, m.file_category, m.file_size, m.file_path, m.description, m.created_at, m.fk_user_creat, u.encrypted_name as creator_name FROM medias m LEFT JOIN users u ON u.id = m.fk_user_creat {$whereClause} {$orderClause} LIMIT ? OFFSET ? "; $params[] = $filters['per_page']; $params[] = $offset; $stmt = $this->db->prepare($sql); $stmt->execute($params); $files = $stmt->fetchAll(PDO::FETCH_ASSOC); // Déchiffrer les noms foreach ($files as &$file) { if ($file['creator_name']) { $file['creator_name'] = ApiService::decryptData($file['creator_name']); } unset($file['encrypted_name']); } Response::json([ 'status' => 'success', 'support' => $support, 'support_id' => $supportIdInt, 'pagination' => [ 'current_page' => $filters['page'], 'per_page' => $filters['per_page'], 'total_items' => $totalItems, 'total_pages' => $totalPages, 'has_next' => $filters['page'] < $totalPages, 'has_prev' => $filters['page'] > 1 ], 'filters' => [ 'search' => $filters['search'], 'type' => $filters['type'], 'category' => $filters['category'] ], 'files' => $files ], 200); } catch (Exception $e) { LogService::log('Erreur lors de la liste des fichiers par support', [ 'level' => 'error', 'error' => $e->getMessage(), 'support' => $support, 'supportId' => $supportId, 'userId' => $userId ?? null ]); Response::json([ 'status' => 'error', 'message' => 'Erreur lors de la récupération des fichiers' ], 500); } } /** * Recherche globale de fichiers */ public function search(): void { try { $userId = Session::getUserId(); if (!$userId) { Response::json([ 'status' => 'error', 'message' => 'Vous devez être connecté pour effectuer cette action' ], 401); return; } $userInfo = $this->getUserInfo($userId); if (!$userInfo) { Response::json([ 'status' => 'error', 'message' => 'Informations utilisateur non trouvées' ], 404); return; } $userRole = (int)$userInfo['fk_role']; $userEntiteId = (int)$userInfo['fk_entite']; $query = $_GET['q'] ?? ''; if (empty(trim($query))) { Response::json([ 'status' => 'error', 'message' => 'Terme de recherche requis' ], 400); return; } $filters = $this->extractFilters($_GET); $filters['search'] = trim($query); [$whereClause, $params] = $this->buildWhereClause($filters, $userRole, $userEntiteId); $orderClause = $this->buildOrderClause($filters); // Compter le total $countSql = "SELECT COUNT(*) as total FROM medias m {$whereClause}"; $stmt = $this->db->prepare($countSql); $stmt->execute($params); $totalItems = (int)$stmt->fetchColumn(); // Pagination $totalPages = ceil($totalItems / $filters['per_page']); $offset = ($filters['page'] - 1) * $filters['per_page']; // Requête principale $sql = " SELECT m.id, m.fichier, m.original_name, m.file_type, m.file_category, m.file_size, m.file_path, m.description, m.created_at, m.support, m.support_id, m.fk_user_creat, u.encrypted_name as creator_name FROM medias m LEFT JOIN users u ON u.id = m.fk_user_creat {$whereClause} {$orderClause} LIMIT ? OFFSET ? "; $params[] = $filters['per_page']; $params[] = $offset; $stmt = $this->db->prepare($sql); $stmt->execute($params); $files = $stmt->fetchAll(PDO::FETCH_ASSOC); // Déchiffrer les noms foreach ($files as &$file) { if ($file['creator_name']) { $file['creator_name'] = ApiService::decryptData($file['creator_name']); } unset($file['encrypted_name']); } Response::json([ 'status' => 'success', 'query' => $query, 'pagination' => [ 'current_page' => $filters['page'], 'per_page' => $filters['per_page'], 'total_items' => $totalItems, 'total_pages' => $totalPages, 'has_next' => $filters['page'] < $totalPages, 'has_prev' => $filters['page'] > 1 ], 'filters' => [ 'type' => $filters['type'], 'category' => $filters['category'] ], 'files' => $files ], 200); } catch (Exception $e) { LogService::log('Erreur lors de la recherche de fichiers', [ 'level' => 'error', 'error' => $e->getMessage(), 'query' => $_GET['q'] ?? null, 'userId' => $userId ?? null ]); Response::json([ 'status' => 'error', 'message' => 'Erreur lors de la recherche' ], 500); } } /** * Statistiques d'utilisation des fichiers */ public function getStats(): void { try { $userId = Session::getUserId(); if (!$userId) { Response::json([ 'status' => 'error', 'message' => 'Vous devez être connecté pour effectuer cette action' ], 401); return; } $userInfo = $this->getUserInfo($userId); if (!$userInfo) { Response::json([ 'status' => 'error', 'message' => 'Informations utilisateur non trouvées' ], 404); return; } $userRole = (int)$userInfo['fk_role']; $userEntiteId = (int)$userInfo['fk_entite']; if ($userRole == 2) { // Stats pour admin d'entité $sql = " SELECT COUNT(*) as total_files, SUM(file_size) as total_size, support, file_category, file_type, COUNT(*) as count FROM medias WHERE fk_entite = ? GROUP BY support, file_category, file_type ORDER BY support, file_category "; $stmt = $this->db->prepare($sql); $stmt->execute([$userEntiteId]); $stats = $stmt->fetchAll(PDO::FETCH_ASSOC); $response = [ 'status' => 'success', 'entite_id' => $userEntiteId, 'storage' => [ 'total_files' => 0, 'total_size' => 0, 'by_support' => [], 'by_category' => [], 'by_type' => [] ] ]; foreach ($stats as $stat) { $response['storage']['total_files'] += (int)$stat['count']; $response['storage']['total_size'] += (int)$stat['total_size']; $support = $stat['support']; $category = $stat['file_category'] ?: 'non_categorise'; $type = $stat['file_type'] ?: 'inconnu'; if (!isset($response['storage']['by_support'][$support])) { $response['storage']['by_support'][$support] = ['count' => 0, 'size' => 0]; } $response['storage']['by_support'][$support]['count'] += (int)$stat['count']; $response['storage']['by_support'][$support]['size'] += (int)$stat['total_size']; if (!isset($response['storage']['by_category'][$category])) { $response['storage']['by_category'][$category] = 0; } $response['storage']['by_category'][$category] += (int)$stat['count']; if (!isset($response['storage']['by_type'][$type])) { $response['storage']['by_type'][$type] = 0; } $response['storage']['by_type'][$type] += (int)$stat['count']; } } else { // Stats globales pour super admin $sql = " SELECT fk_entite, COUNT(*) as files, SUM(file_size) as size FROM medias GROUP BY fk_entite ORDER BY size DESC "; $stmt = $this->db->prepare($sql); $stmt->execute(); $entiteStats = $stmt->fetchAll(PDO::FETCH_ASSOC); $totalSql = " SELECT COUNT(*) as total_files, SUM(file_size) as total_size, COUNT(DISTINCT fk_entite) as entites_count FROM medias "; $stmt = $this->db->prepare($totalSql); $stmt->execute(); $totals = $stmt->fetch(PDO::FETCH_ASSOC); $response = [ 'status' => 'success', 'global_stats' => [ 'total_files' => (int)$totals['total_files'], 'total_size' => (int)$totals['total_size'], 'entites_count' => (int)$totals['entites_count'], 'by_entite' => [] ] ]; foreach ($entiteStats as $stat) { $response['global_stats']['by_entite'][] = [ 'entite_id' => (int)$stat['fk_entite'], 'files' => (int)$stat['files'], 'size' => (int)$stat['size'] ]; } } Response::json($response, 200); } catch (Exception $e) { LogService::log('Erreur lors de la récupération des statistiques', [ 'level' => 'error', 'error' => $e->getMessage(), 'userId' => $userId ?? null ]); Response::json([ 'status' => 'error', 'message' => 'Erreur lors de la récupération des statistiques' ], 500); } } /** * Téléchargement sécurisé d'un fichier */ public function download(string $id): void { try { $userId = Session::getUserId(); if (!$userId) { Response::json([ 'status' => 'error', 'message' => 'Vous devez être connecté pour effectuer cette action' ], 401); return; } $userInfo = $this->getUserInfo($userId); if (!$userInfo) { Response::json([ 'status' => 'error', 'message' => 'Informations utilisateur non trouvées' ], 404); return; } $userRole = (int)$userInfo['fk_role']; $userEntiteId = (int)$userInfo['fk_entite']; $fileId = (int)$id; // Vérifier l'accès au fichier if (!$this->canAccessFile($fileId, $userRole, $userEntiteId)) { Response::json([ 'status' => 'error', 'message' => 'Accès refusé à ce fichier' ], 403); return; } // Récupérer les informations du fichier $stmt = $this->db->prepare(' SELECT fichier, file_path, mime_type, original_name, file_size FROM medias WHERE id = ? '); $stmt->execute([$fileId]); $file = $stmt->fetch(PDO::FETCH_ASSOC); if (!$file) { Response::json([ 'status' => 'error', 'message' => 'Fichier non trouvé' ], 404); return; } $filepath = getcwd() . '/' . $file['file_path']; if (!file_exists($filepath)) { Response::json([ 'status' => 'error', 'message' => 'Fichier physique non trouvé' ], 404); return; } // Log du téléchargement LogService::log('Téléchargement de fichier', [ 'level' => 'info', 'userId' => $userId, 'fileId' => $fileId, 'filename' => $file['original_name'] ]); // Envoyer le fichier header('Content-Type: ' . $file['mime_type']); header('Content-Disposition: attachment; filename="' . $file['original_name'] . '"'); header('Content-Length: ' . filesize($filepath)); header('Cache-Control: no-cache, must-revalidate'); header('Expires: 0'); readfile($filepath); exit; } catch (Exception $e) { LogService::log('Erreur lors du téléchargement', [ 'level' => 'error', 'error' => $e->getMessage(), 'fileId' => $id, 'userId' => $userId ?? null ]); Response::json([ 'status' => 'error', 'message' => 'Erreur lors du téléchargement' ], 500); } } /** * Suppression sécurisée d'un fichier */ public function deleteFile(string $id): void { try { $userId = Session::getUserId(); if (!$userId) { Response::json([ 'status' => 'error', 'message' => 'Vous devez être connecté pour effectuer cette action' ], 401); return; } $userInfo = $this->getUserInfo($userId); if (!$userInfo) { Response::json([ 'status' => 'error', 'message' => 'Informations utilisateur non trouvées' ], 404); return; } $userRole = (int)$userInfo['fk_role']; $userEntiteId = (int)$userInfo['fk_entite']; $fileId = (int)$id; // Vérifier l'accès au fichier if (!$this->canAccessFile($fileId, $userRole, $userEntiteId)) { Response::json([ 'status' => 'error', 'message' => 'Accès refusé à ce fichier' ], 403); return; } // Récupérer les informations du fichier $stmt = $this->db->prepare(' SELECT fichier, file_path, original_name, support, support_id FROM medias WHERE id = ? '); $stmt->execute([$fileId]); $file = $stmt->fetch(PDO::FETCH_ASSOC); if (!$file) { Response::json([ 'status' => 'error', 'message' => 'Fichier non trouvé' ], 404); return; } // Supprimer le fichier physique $filepath = getcwd() . '/' . $file['file_path']; if (file_exists($filepath)) { unlink($filepath); } // Supprimer l'enregistrement en base $stmt = $this->db->prepare('DELETE FROM medias WHERE id = ?'); $stmt->execute([$fileId]); LogService::log('Suppression de fichier', [ 'level' => 'info', 'userId' => $userId, 'fileId' => $fileId, 'filename' => $file['original_name'], 'support' => $file['support'], 'support_id' => $file['support_id'] ]); Response::json([ 'status' => 'success', 'message' => 'Fichier supprimé avec succès' ], 200); } catch (Exception $e) { LogService::log('Erreur lors de la suppression de fichier', [ 'level' => 'error', 'error' => $e->getMessage(), 'fileId' => $id, 'userId' => $userId ?? null ]); Response::json([ 'status' => 'error', 'message' => 'Erreur lors de la suppression' ], 500); } } /** * Informations détaillées d'un fichier */ public function getFileInfo(string $id): void { try { $userId = Session::getUserId(); if (!$userId) { Response::json([ 'status' => 'error', 'message' => 'Vous devez être connecté pour effectuer cette action' ], 401); return; } $userInfo = $this->getUserInfo($userId); if (!$userInfo) { Response::json([ 'status' => 'error', 'message' => 'Informations utilisateur non trouvées' ], 404); return; } $userRole = (int)$userInfo['fk_role']; $userEntiteId = (int)$userInfo['fk_entite']; $fileId = (int)$id; // Vérifier l'accès au fichier if (!$this->canAccessFile($fileId, $userRole, $userEntiteId)) { Response::json([ 'status' => 'error', 'message' => 'Accès refusé à ce fichier' ], 403); return; } // Récupérer toutes les informations du fichier $stmt = $this->db->prepare(' SELECT m.*, u_creat.encrypted_name as creator_name, u_modif.encrypted_name as modifier_name FROM medias m LEFT JOIN users u_creat ON u_creat.id = m.fk_user_creat LEFT JOIN users u_modif ON u_modif.id = m.fk_user_modif WHERE m.id = ? '); $stmt->execute([$fileId]); $file = $stmt->fetch(PDO::FETCH_ASSOC); if (!$file) { Response::json([ 'status' => 'error', 'message' => 'Fichier non trouvé' ], 404); return; } // Déchiffrer les noms d'utilisateurs if ($file['creator_name']) { $file['creator_name'] = ApiService::decryptData($file['creator_name']); } if ($file['modifier_name']) { $file['modifier_name'] = ApiService::decryptData($file['modifier_name']); } // Vérifier si le fichier physique existe $filepath = getcwd() . '/' . $file['file_path']; $file['file_exists'] = file_exists($filepath); Response::json([ 'status' => 'success', 'file' => $file ], 200); } catch (Exception $e) { LogService::log('Erreur lors de la récupération des infos fichier', [ 'level' => 'error', 'error' => $e->getMessage(), 'fileId' => $id, 'userId' => $userId ?? null ]); Response::json([ 'status' => 'error', 'message' => 'Erreur lors de la récupération des informations' ], 500); } } /** * Métadonnées du système de fichiers (catégories, extensions, etc.) */ public function getMetadata(): void { try { $userId = Session::getUserId(); if (!$userId) { Response::json([ 'status' => 'error', 'message' => 'Vous devez être connecté pour effectuer cette action' ], 401); return; } Response::json([ 'status' => 'success', 'categories' => self::FILE_CATEGORIES, 'extensions' => self::ALLOWED_EXTENSIONS, 'mime_types' => [ 'pdf' => 'application/pdf', 'jpg' => 'image/jpeg', 'jpeg' => 'image/jpeg', 'png' => 'image/png', 'gif' => 'image/gif', 'webp' => 'image/webp', 'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', 'xls' => 'application/vnd.ms-excel', 'json' => 'application/json', 'csv' => 'text/csv' ], 'max_file_sizes' => [ 'entite' => 20971520, // 20 MB 'user' => 5242880, // 5 MB 'operation' => 20971520, // 20 MB 'passage' => 10485760 // 10 MB ] ], 200); } catch (Exception $e) { LogService::log('Erreur lors de la récupération des métadonnées', [ 'level' => 'error', 'error' => $e->getMessage(), 'userId' => $userId ?? null ]); Response::json([ 'status' => 'error', 'message' => 'Erreur lors de la récupération des métadonnées' ], 500); } } }