- Ajout système complet de gestion des secteurs avec contours géographiques - Import des contours départementaux depuis GeoJSON - API REST pour la gestion des secteurs (/api/sectors) - Service de géolocalisation pour déterminer les secteurs - Migration base de données avec tables x_departements_contours et sectors_adresses - Interface Flutter pour visualisation et gestion des secteurs - Ajout thème sombre dans l'application - Corrections diverses et optimisations 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
1067 lines
36 KiB
PHP
Executable File
1067 lines
36 KiB
PHP
Executable File
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Controllers;
|
|
|
|
require_once __DIR__ . '/../Services/LogService.php';
|
|
require_once __DIR__ . '/../Services/ApiService.php';
|
|
|
|
use PDO;
|
|
use PDOException;
|
|
use Database;
|
|
use AppConfig;
|
|
use Request;
|
|
use Response;
|
|
use Session;
|
|
use LogService;
|
|
use ApiService;
|
|
use Exception;
|
|
|
|
class FileController {
|
|
private PDO $db;
|
|
private AppConfig $appConfig;
|
|
|
|
// Catégories autorisées par type de support
|
|
private const FILE_CATEGORIES = [
|
|
'entite' => ['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);
|
|
}
|
|
}
|
|
}
|