✨ Nouvelles fonctionnalités: - Ajout du mode terrain pour utilisation mobile hors connexion - Génération automatique de reçus PDF avec template personnalisé - Révision complète du système de cartes avec amélioration des performances 🔧 Améliorations techniques: - Refactoring du module chat avec architecture simplifiée - Optimisation du système de sécurité NIST SP 800-63B - Amélioration de la gestion des secteurs géographiques - Support UTF-8 étendu pour les noms d'utilisateurs 📱 Application mobile: - Nouveau mode terrain dans user_field_mode_page - Interface utilisateur adaptative pour conditions difficiles - Synchronisation offline améliorée 🗺️ Cartographie: - Optimisation des performances MapBox - Meilleure gestion des tuiles hors ligne - Amélioration de l'affichage des secteurs 📄 Documentation: - Ajout guide Android (ANDROID-GUIDE.md) - Documentation sécurité API (API-SECURITY.md) - Guide module chat (CHAT_MODULE.md) 🐛 Corrections: - Résolution des erreurs 400 lors de la création d'utilisateurs - Correction de la validation des noms d'utilisateurs - Fix des problèmes de synchronisation chat 🤖 Generated with Claude Code (https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
875 lines
31 KiB
PHP
875 lines
31 KiB
PHP
<?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 Request;
|
|
use Response;
|
|
use Session;
|
|
use LogService;
|
|
use ApiService;
|
|
|
|
class ChatController {
|
|
private PDO $db;
|
|
|
|
public function __construct() {
|
|
$this->db = Database::getInstance();
|
|
}
|
|
|
|
/**
|
|
* GET /api/chat/rooms
|
|
* Liste des conversations filtrées par rôle et entité
|
|
*/
|
|
public function getRooms(): void {
|
|
Session::requireAuth();
|
|
|
|
try {
|
|
$userId = Session::getUserId();
|
|
$entityId = Session::getEntityId();
|
|
|
|
// Récupérer le rôle de l'utilisateur
|
|
$userRole = $this->getUserRole($userId);
|
|
|
|
// Construction de la requête selon le rôle
|
|
$sql = '
|
|
SELECT DISTINCT
|
|
r.id,
|
|
r.title,
|
|
r.type,
|
|
r.created_at,
|
|
r.created_by,
|
|
r.updated_at,
|
|
-- Dernier message
|
|
(SELECT m.content
|
|
FROM chat_messages m
|
|
WHERE m.room_id = r.id
|
|
AND m.is_deleted = 0
|
|
ORDER BY m.sent_at DESC
|
|
LIMIT 1) as last_message,
|
|
(SELECT m.sent_at
|
|
FROM chat_messages m
|
|
WHERE m.room_id = r.id
|
|
AND m.is_deleted = 0
|
|
ORDER BY m.sent_at DESC
|
|
LIMIT 1) as last_message_at,
|
|
-- Nombre de messages non lus
|
|
(SELECT COUNT(*)
|
|
FROM chat_messages m
|
|
WHERE m.room_id = r.id
|
|
AND m.sent_at > COALESCE(p.last_read_at, p.joined_at)
|
|
AND m.sender_id != :user_id_count) as unread_count,
|
|
-- Participants
|
|
(SELECT COUNT(*)
|
|
FROM chat_participants cp
|
|
WHERE cp.room_id = r.id
|
|
AND cp.left_at IS NULL) as participant_count
|
|
FROM chat_rooms r
|
|
INNER JOIN chat_participants p ON r.id = p.room_id
|
|
WHERE r.is_active = 1
|
|
AND p.user_id = :user_id
|
|
AND p.left_at IS NULL
|
|
';
|
|
|
|
// Filtrage supplémentaire selon le rôle
|
|
if ($userRole == 1) {
|
|
// Utilisateur simple : seulement ses conversations privées et de groupe
|
|
$sql .= ' AND r.type IN ("private", "group")';
|
|
} elseif ($userRole == 2) {
|
|
// Admin d'entité : toutes les conversations de son entité
|
|
$sql .= ' AND (p.entite_id = :entity_id OR r.type = "broadcast")';
|
|
}
|
|
// Rôle > 2 : accès à toutes les conversations
|
|
|
|
$sql .= ' ORDER BY COALESCE(last_message_at, r.created_at) DESC';
|
|
|
|
$stmt = $this->db->prepare($sql);
|
|
$params = [
|
|
'user_id' => $userId,
|
|
'user_id_count' => $userId
|
|
];
|
|
|
|
if ($userRole == 2) {
|
|
$params['entity_id'] = $entityId;
|
|
}
|
|
|
|
$stmt->execute($params);
|
|
$rooms = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
|
|
|
// Pour chaque room, récupérer les participants
|
|
foreach ($rooms as &$room) {
|
|
$room['participants'] = $this->getRoomParticipants($room['id']);
|
|
}
|
|
|
|
LogService::log('Récupération des conversations', [
|
|
'level' => 'debug',
|
|
'user_id' => $userId,
|
|
'room_count' => count($rooms)
|
|
]);
|
|
|
|
Response::json([
|
|
'status' => 'success',
|
|
'rooms' => $rooms
|
|
]);
|
|
|
|
} catch (PDOException $e) {
|
|
LogService::log('Erreur lors de la récupération des conversations', [
|
|
'level' => 'error',
|
|
'error' => $e->getMessage()
|
|
]);
|
|
Response::json([
|
|
'status' => 'error',
|
|
'message' => 'Erreur serveur'
|
|
], 500);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* POST /api/chat/rooms
|
|
* Créer une nouvelle conversation
|
|
*/
|
|
public function createRoom(): void {
|
|
Session::requireAuth();
|
|
|
|
try {
|
|
$data = Request::getJson();
|
|
$userId = Session::getUserId();
|
|
$entityId = Session::getEntityId();
|
|
$userRole = $this->getUserRole($userId);
|
|
|
|
// Validation des données
|
|
if (!isset($data['type']) || !in_array($data['type'], ['private', 'group', 'broadcast'])) {
|
|
Response::json([
|
|
'status' => 'error',
|
|
'message' => 'Type de conversation invalide'
|
|
], 400);
|
|
return;
|
|
}
|
|
|
|
// Vérification des permissions pour broadcast
|
|
if ($data['type'] === 'broadcast' && $userRole < 2) {
|
|
Response::json([
|
|
'status' => 'error',
|
|
'message' => 'Permissions insuffisantes pour créer une diffusion'
|
|
], 403);
|
|
return;
|
|
}
|
|
|
|
// Validation des participants
|
|
if (!isset($data['participants']) || !is_array($data['participants']) || empty($data['participants'])) {
|
|
Response::json([
|
|
'status' => 'error',
|
|
'message' => 'Au moins un participant requis'
|
|
], 400);
|
|
return;
|
|
}
|
|
|
|
// Pour une conversation privée, limiter à 2 participants (incluant le créateur)
|
|
if ($data['type'] === 'private' && count($data['participants']) > 1) {
|
|
Response::json([
|
|
'status' => 'error',
|
|
'message' => 'Une conversation privée ne peut avoir que 2 participants'
|
|
], 400);
|
|
return;
|
|
}
|
|
|
|
// Vérifier que tous les participants existent et sont accessibles
|
|
$participantIds = array_map('intval', $data['participants']);
|
|
if (!in_array($userId, $participantIds)) {
|
|
$participantIds[] = $userId; // Ajouter le créateur
|
|
}
|
|
|
|
// Vérifier l'existence d'une conversation privée existante
|
|
if ($data['type'] === 'private' && count($participantIds) === 2) {
|
|
$existingRoom = $this->findExistingPrivateRoom($participantIds[0], $participantIds[1]);
|
|
if ($existingRoom) {
|
|
Response::json([
|
|
'status' => 'success',
|
|
'room' => $existingRoom,
|
|
'existing' => true
|
|
]);
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Générer un UUID pour la room
|
|
$roomId = $this->generateUUID();
|
|
|
|
// Titre de la conversation
|
|
$title = $data['title'] ?? null;
|
|
if (!$title && $data['type'] === 'private') {
|
|
// Pour une conversation privée, pas de titre par défaut
|
|
$title = null;
|
|
}
|
|
|
|
$this->db->beginTransaction();
|
|
|
|
try {
|
|
// Créer la room
|
|
$stmt = $this->db->prepare('
|
|
INSERT INTO chat_rooms (id, title, type, created_by, created_at)
|
|
VALUES (:id, :title, :type, :created_by, NOW())
|
|
');
|
|
$stmt->execute([
|
|
'id' => $roomId,
|
|
'title' => $title,
|
|
'type' => $data['type'],
|
|
'created_by' => $userId
|
|
]);
|
|
|
|
// Ajouter les participants
|
|
$participantStmt = $this->db->prepare('
|
|
INSERT INTO chat_participants (room_id, user_id, role, entite_id, is_admin)
|
|
VALUES (:room_id, :user_id, :role, :entite_id, :is_admin)
|
|
');
|
|
|
|
foreach ($participantIds as $participantId) {
|
|
$participantData = $this->getUserData($participantId);
|
|
if (!$participantData) {
|
|
throw new \Exception("Participant invalide: $participantId");
|
|
}
|
|
|
|
$participantStmt->execute([
|
|
'room_id' => $roomId,
|
|
'user_id' => $participantId,
|
|
'role' => $participantData['fk_role'],
|
|
'entite_id' => $participantData['fk_entite'],
|
|
'is_admin' => ($participantId === $userId) ? 1 : 0
|
|
]);
|
|
}
|
|
|
|
// Si un message initial est fourni, l'envoyer
|
|
if (isset($data['initial_message']) && !empty($data['initial_message'])) {
|
|
$messageId = $this->generateUUID();
|
|
$msgStmt = $this->db->prepare('
|
|
INSERT INTO chat_messages (id, room_id, content, sender_id, sent_at)
|
|
VALUES (:id, :room_id, :content, :sender_id, NOW())
|
|
');
|
|
$msgStmt->execute([
|
|
'id' => $messageId,
|
|
'room_id' => $roomId,
|
|
'content' => $data['initial_message'],
|
|
'sender_id' => $userId
|
|
]);
|
|
}
|
|
|
|
$this->db->commit();
|
|
|
|
LogService::log('Conversation créée', [
|
|
'level' => 'info',
|
|
'room_id' => $roomId,
|
|
'type' => $data['type'],
|
|
'created_by' => $userId,
|
|
'participant_count' => count($participantIds)
|
|
]);
|
|
|
|
// Récupérer la room créée avec ses détails
|
|
$room = $this->getRoomDetails($roomId);
|
|
|
|
Response::json([
|
|
'status' => 'success',
|
|
'room' => $room
|
|
], 201);
|
|
|
|
} catch (\Exception $e) {
|
|
$this->db->rollBack();
|
|
throw $e;
|
|
}
|
|
|
|
} catch (PDOException $e) {
|
|
LogService::log('Erreur lors de la création de la conversation', [
|
|
'level' => 'error',
|
|
'error' => $e->getMessage()
|
|
]);
|
|
Response::json([
|
|
'status' => 'error',
|
|
'message' => 'Erreur serveur'
|
|
], 500);
|
|
} catch (\Exception $e) {
|
|
LogService::log('Erreur lors de la création de la conversation', [
|
|
'level' => 'error',
|
|
'error' => $e->getMessage()
|
|
]);
|
|
Response::json([
|
|
'status' => 'error',
|
|
'message' => $e->getMessage()
|
|
], 400);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* GET /api/chat/rooms/{id}/messages
|
|
* Récupérer les messages d'une conversation
|
|
*/
|
|
public function getRoomMessages(string $roomId): void {
|
|
Session::requireAuth();
|
|
|
|
try {
|
|
$userId = Session::getUserId();
|
|
|
|
// Vérifier que l'utilisateur est participant
|
|
if (!$this->isUserInRoom($userId, $roomId)) {
|
|
Response::json([
|
|
'status' => 'error',
|
|
'message' => 'Accès non autorisé à cette conversation'
|
|
], 403);
|
|
return;
|
|
}
|
|
|
|
// Paramètres de pagination
|
|
$limit = isset($_GET['limit']) ? min(100, max(1, (int)$_GET['limit'])) : 50;
|
|
$before = $_GET['before'] ?? null; // Message ID pour pagination
|
|
|
|
$sql = '
|
|
SELECT
|
|
m.id,
|
|
m.content,
|
|
m.sender_id,
|
|
m.sent_at,
|
|
m.edited_at,
|
|
m.is_deleted,
|
|
u.encrypted_name as sender_name,
|
|
u.first_name as sender_first_name,
|
|
-- Statut de lecture
|
|
(SELECT COUNT(*)
|
|
FROM chat_read_receipts r
|
|
WHERE r.message_id = m.id) as read_count,
|
|
(SELECT COUNT(*)
|
|
FROM chat_read_receipts r
|
|
WHERE r.message_id = m.id
|
|
AND r.user_id = :user_id) as is_read
|
|
FROM chat_messages m
|
|
INNER JOIN users u ON m.sender_id = u.id
|
|
WHERE m.room_id = :room_id
|
|
';
|
|
|
|
$params = [
|
|
'room_id' => $roomId,
|
|
'user_id' => $userId
|
|
];
|
|
|
|
if ($before) {
|
|
$sql .= ' AND m.sent_at < (SELECT sent_at FROM chat_messages WHERE id = :before)';
|
|
$params['before'] = $before;
|
|
}
|
|
|
|
$sql .= ' ORDER BY m.sent_at DESC LIMIT :limit';
|
|
|
|
$stmt = $this->db->prepare($sql);
|
|
foreach ($params as $key => $value) {
|
|
if ($key === 'limit') {
|
|
$stmt->bindValue($key, $value, PDO::PARAM_INT);
|
|
} else {
|
|
$stmt->bindValue($key, $value);
|
|
}
|
|
}
|
|
$stmt->bindValue('limit', $limit, PDO::PARAM_INT);
|
|
$stmt->execute();
|
|
|
|
$messages = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
|
|
|
// Déchiffrer les noms
|
|
foreach ($messages as &$message) {
|
|
$message['sender_name'] = ApiService::decryptData($message['sender_name']);
|
|
$message['is_read'] = (bool)$message['is_read'];
|
|
$message['is_mine'] = ($message['sender_id'] == $userId);
|
|
}
|
|
|
|
// Inverser pour avoir l'ordre chronologique
|
|
$messages = array_reverse($messages);
|
|
|
|
// Mettre à jour last_read_at pour ce participant
|
|
$this->updateLastRead($roomId, $userId);
|
|
|
|
Response::json([
|
|
'status' => 'success',
|
|
'messages' => $messages,
|
|
'has_more' => count($messages) === $limit
|
|
]);
|
|
|
|
} catch (PDOException $e) {
|
|
LogService::log('Erreur lors de la récupération des messages', [
|
|
'level' => 'error',
|
|
'room_id' => $roomId,
|
|
'error' => $e->getMessage()
|
|
]);
|
|
Response::json([
|
|
'status' => 'error',
|
|
'message' => 'Erreur serveur'
|
|
], 500);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* POST /api/chat/rooms/{id}/messages
|
|
* Envoyer un message dans une conversation
|
|
*/
|
|
public function sendMessage(string $roomId): void {
|
|
Session::requireAuth();
|
|
|
|
try {
|
|
$data = Request::getJson();
|
|
$userId = Session::getUserId();
|
|
|
|
// Vérifier que l'utilisateur est participant
|
|
if (!$this->isUserInRoom($userId, $roomId)) {
|
|
Response::json([
|
|
'status' => 'error',
|
|
'message' => 'Accès non autorisé à cette conversation'
|
|
], 403);
|
|
return;
|
|
}
|
|
|
|
// Validation du contenu
|
|
if (!isset($data['content']) || empty(trim($data['content']))) {
|
|
Response::json([
|
|
'status' => 'error',
|
|
'message' => 'Le message ne peut pas être vide'
|
|
], 400);
|
|
return;
|
|
}
|
|
|
|
$content = trim($data['content']);
|
|
|
|
// Limiter la longueur du message
|
|
if (mb_strlen($content, 'UTF-8') > 5000) {
|
|
Response::json([
|
|
'status' => 'error',
|
|
'message' => 'Message trop long (max 5000 caractères)'
|
|
], 400);
|
|
return;
|
|
}
|
|
|
|
$messageId = $this->generateUUID();
|
|
|
|
// Insérer le message
|
|
$stmt = $this->db->prepare('
|
|
INSERT INTO chat_messages (id, room_id, content, sender_id, sent_at)
|
|
VALUES (:id, :room_id, :content, :sender_id, NOW())
|
|
');
|
|
$stmt->execute([
|
|
'id' => $messageId,
|
|
'room_id' => $roomId,
|
|
'content' => $content,
|
|
'sender_id' => $userId
|
|
]);
|
|
|
|
// Mettre à jour la date de dernière modification de la room
|
|
$updateStmt = $this->db->prepare('
|
|
UPDATE chat_rooms
|
|
SET updated_at = NOW()
|
|
WHERE id = :room_id
|
|
');
|
|
$updateStmt->execute(['room_id' => $roomId]);
|
|
|
|
// Récupérer le message créé avec les infos du sender
|
|
$msgStmt = $this->db->prepare('
|
|
SELECT
|
|
m.id,
|
|
m.content,
|
|
m.sender_id,
|
|
m.sent_at,
|
|
u.encrypted_name as sender_name,
|
|
u.first_name as sender_first_name
|
|
FROM chat_messages m
|
|
INNER JOIN users u ON m.sender_id = u.id
|
|
WHERE m.id = :id
|
|
');
|
|
$msgStmt->execute(['id' => $messageId]);
|
|
$message = $msgStmt->fetch(PDO::FETCH_ASSOC);
|
|
|
|
$message['sender_name'] = ApiService::decryptData($message['sender_name']);
|
|
$message['is_mine'] = true;
|
|
$message['is_read'] = false;
|
|
$message['read_count'] = 0;
|
|
|
|
LogService::log('Message envoyé', [
|
|
'level' => 'debug',
|
|
'room_id' => $roomId,
|
|
'message_id' => $messageId,
|
|
'sender_id' => $userId
|
|
]);
|
|
|
|
Response::json([
|
|
'status' => 'success',
|
|
'message' => $message
|
|
], 201);
|
|
|
|
} catch (PDOException $e) {
|
|
LogService::log('Erreur lors de l\'envoi du message', [
|
|
'level' => 'error',
|
|
'room_id' => $roomId,
|
|
'error' => $e->getMessage()
|
|
]);
|
|
Response::json([
|
|
'status' => 'error',
|
|
'message' => 'Erreur serveur'
|
|
], 500);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* POST /api/chat/rooms/{id}/read
|
|
* Marquer les messages comme lus
|
|
*/
|
|
public function markAsRead(string $roomId): void {
|
|
Session::requireAuth();
|
|
|
|
try {
|
|
$data = Request::getJson();
|
|
$userId = Session::getUserId();
|
|
|
|
// Vérifier que l'utilisateur est participant
|
|
if (!$this->isUserInRoom($userId, $roomId)) {
|
|
Response::json([
|
|
'status' => 'error',
|
|
'message' => 'Accès non autorisé à cette conversation'
|
|
], 403);
|
|
return;
|
|
}
|
|
|
|
// Si des message_ids spécifiques sont fournis
|
|
if (isset($data['message_ids']) && is_array($data['message_ids'])) {
|
|
$messageIds = $data['message_ids'];
|
|
|
|
// Marquer ces messages spécifiques comme lus
|
|
$stmt = $this->db->prepare('
|
|
INSERT IGNORE INTO chat_read_receipts (message_id, user_id, read_at)
|
|
VALUES (:message_id, :user_id, NOW())
|
|
');
|
|
|
|
foreach ($messageIds as $messageId) {
|
|
$stmt->execute([
|
|
'message_id' => $messageId,
|
|
'user_id' => $userId
|
|
]);
|
|
}
|
|
} else {
|
|
// Marquer tous les messages non lus de la room comme lus
|
|
$stmt = $this->db->prepare('
|
|
INSERT IGNORE INTO chat_read_receipts (message_id, user_id, read_at)
|
|
SELECT m.id, :user_id, NOW()
|
|
FROM chat_messages m
|
|
WHERE m.room_id = :room_id
|
|
AND m.id NOT IN (
|
|
SELECT message_id
|
|
FROM chat_read_receipts
|
|
WHERE user_id = :user_id_check
|
|
)
|
|
');
|
|
$stmt->execute([
|
|
'user_id' => $userId,
|
|
'user_id_check' => $userId,
|
|
'room_id' => $roomId
|
|
]);
|
|
}
|
|
|
|
// Mettre à jour last_read_at
|
|
$this->updateLastRead($roomId, $userId);
|
|
|
|
// Compter les messages non lus restants
|
|
$countStmt = $this->db->prepare('
|
|
SELECT COUNT(*) as unread_count
|
|
FROM chat_messages m
|
|
WHERE m.room_id = :room_id
|
|
AND m.sender_id != :user_id
|
|
AND m.id NOT IN (
|
|
SELECT message_id
|
|
FROM chat_read_receipts
|
|
WHERE user_id = :user_id_check
|
|
)
|
|
');
|
|
$countStmt->execute([
|
|
'room_id' => $roomId,
|
|
'user_id' => $userId,
|
|
'user_id_check' => $userId
|
|
]);
|
|
$result = $countStmt->fetch(PDO::FETCH_ASSOC);
|
|
|
|
Response::json([
|
|
'status' => 'success',
|
|
'unread_count' => (int)$result['unread_count']
|
|
]);
|
|
|
|
} catch (PDOException $e) {
|
|
LogService::log('Erreur lors du marquage comme lu', [
|
|
'level' => 'error',
|
|
'room_id' => $roomId,
|
|
'error' => $e->getMessage()
|
|
]);
|
|
Response::json([
|
|
'status' => 'error',
|
|
'message' => 'Erreur serveur'
|
|
], 500);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* GET /api/chat/recipients
|
|
* Liste des destinataires possibles selon le rôle
|
|
*/
|
|
public function getRecipients(): void {
|
|
Session::requireAuth();
|
|
|
|
try {
|
|
$userId = Session::getUserId();
|
|
$entityId = Session::getEntityId();
|
|
$userRole = $this->getUserRole($userId);
|
|
|
|
$sql = '
|
|
SELECT
|
|
u.id,
|
|
u.encrypted_name as name,
|
|
u.first_name,
|
|
u.sect_name,
|
|
u.fk_role as role,
|
|
u.fk_entite as entite_id,
|
|
e.encrypted_name as entite_name
|
|
FROM users u
|
|
LEFT JOIN entites e ON u.fk_entite = e.id
|
|
WHERE u.chk_active = 1
|
|
AND u.id != :user_id
|
|
';
|
|
|
|
$params = ['user_id' => $userId];
|
|
|
|
// Filtrage selon le rôle
|
|
if ($userRole == 1) {
|
|
// Utilisateur simple : seulement les utilisateurs de son entité
|
|
$sql .= ' AND u.fk_entite = :entity_id';
|
|
$params['entity_id'] = $entityId;
|
|
} elseif ($userRole == 2) {
|
|
// Admin d'entité :
|
|
// - Tous les membres actifs de son amicale (même entité)
|
|
// - Les super-admins (fk_role=9) de l'entité 1
|
|
$sql .= ' AND (
|
|
u.fk_entite = :entity_id
|
|
OR (u.fk_role = 9 AND u.fk_entite = 1)
|
|
)';
|
|
$params['entity_id'] = $entityId;
|
|
} elseif ($userRole == 9) {
|
|
// Super-administrateur :
|
|
// - Seulement les administrateurs actifs des amicales (fk_role=2)
|
|
// - Et les autres super-admins (fk_role=9)
|
|
$sql .= ' AND (u.fk_role = 2 OR u.fk_role = 9)';
|
|
}
|
|
// Autres rôles (3-8) : pas de filtrage spécifique pour le moment
|
|
|
|
$sql .= ' ORDER BY u.fk_entite, u.encrypted_name';
|
|
|
|
$stmt = $this->db->prepare($sql);
|
|
$stmt->execute($params);
|
|
$recipients = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
|
|
|
// Déchiffrer les données et organiser par entité
|
|
$recipientsByEntity = [];
|
|
$recipientsDecrypted = [];
|
|
|
|
foreach ($recipients as &$recipient) {
|
|
// Déchiffrer le nom
|
|
$recipient['name'] = ApiService::decryptData($recipient['name']);
|
|
|
|
// Déchiffrer le nom de l'entité
|
|
$entiteName = $recipient['entite_name'] ?
|
|
ApiService::decryptData($recipient['entite_name']) :
|
|
'Sans entité';
|
|
|
|
// Créer une copie pour recipients_by_entity
|
|
$recipientCopy = $recipient;
|
|
unset($recipientCopy['entite_name']);
|
|
|
|
// Organiser par entité
|
|
if (!isset($recipientsByEntity[$entiteName])) {
|
|
$recipientsByEntity[$entiteName] = [];
|
|
}
|
|
$recipientsByEntity[$entiteName][] = $recipientCopy;
|
|
|
|
// Remplacer entite_name chiffré par la version déchiffrée
|
|
$recipient['entite_name'] = $entiteName;
|
|
|
|
// Ajouter à la liste déchiffrée
|
|
$recipientsDecrypted[] = $recipient;
|
|
}
|
|
|
|
Response::json([
|
|
'status' => 'success',
|
|
'recipients' => $recipientsDecrypted,
|
|
'recipients_by_entity' => $recipientsByEntity
|
|
]);
|
|
|
|
} catch (PDOException $e) {
|
|
LogService::log('Erreur lors de la récupération des destinataires', [
|
|
'level' => 'error',
|
|
'error' => $e->getMessage()
|
|
]);
|
|
Response::json([
|
|
'status' => 'error',
|
|
'message' => 'Erreur serveur'
|
|
], 500);
|
|
}
|
|
}
|
|
|
|
// ===== Méthodes utilitaires privées =====
|
|
|
|
/**
|
|
* Récupérer le rôle d'un utilisateur
|
|
*/
|
|
private function getUserRole(int $userId): int {
|
|
$stmt = $this->db->prepare('SELECT fk_role FROM users WHERE id = ?');
|
|
$stmt->execute([$userId]);
|
|
$result = $stmt->fetch(PDO::FETCH_ASSOC);
|
|
return $result ? (int)$result['fk_role'] : 1;
|
|
}
|
|
|
|
/**
|
|
* Récupérer les données d'un utilisateur
|
|
*/
|
|
private function getUserData(int $userId): ?array {
|
|
$stmt = $this->db->prepare('
|
|
SELECT id, fk_role, fk_entite, encrypted_name, first_name
|
|
FROM users
|
|
WHERE id = ? AND chk_active = 1
|
|
');
|
|
$stmt->execute([$userId]);
|
|
$result = $stmt->fetch(PDO::FETCH_ASSOC);
|
|
return $result ?: null;
|
|
}
|
|
|
|
/**
|
|
* Vérifier si un utilisateur est dans une room
|
|
*/
|
|
private function isUserInRoom(int $userId, string $roomId): bool {
|
|
$stmt = $this->db->prepare('
|
|
SELECT COUNT(*) as count
|
|
FROM chat_participants
|
|
WHERE room_id = ?
|
|
AND user_id = ?
|
|
AND left_at IS NULL
|
|
');
|
|
$stmt->execute([$roomId, $userId]);
|
|
$result = $stmt->fetch(PDO::FETCH_ASSOC);
|
|
return $result && $result['count'] > 0;
|
|
}
|
|
|
|
/**
|
|
* Récupérer les participants d'une room
|
|
*/
|
|
private function getRoomParticipants(string $roomId): array {
|
|
$stmt = $this->db->prepare('
|
|
SELECT
|
|
p.user_id,
|
|
p.is_admin,
|
|
u.encrypted_name as name,
|
|
u.first_name
|
|
FROM chat_participants p
|
|
INNER JOIN users u ON p.user_id = u.id
|
|
WHERE p.room_id = ?
|
|
AND p.left_at IS NULL
|
|
');
|
|
$stmt->execute([$roomId]);
|
|
$participants = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
|
|
|
foreach ($participants as &$participant) {
|
|
$participant['name'] = ApiService::decryptData($participant['name']);
|
|
}
|
|
|
|
return $participants;
|
|
}
|
|
|
|
/**
|
|
* Récupérer les détails d'une room
|
|
*/
|
|
private function getRoomDetails(string $roomId): array {
|
|
$stmt = $this->db->prepare('
|
|
SELECT
|
|
r.id,
|
|
r.title,
|
|
r.type,
|
|
r.created_at,
|
|
r.created_by,
|
|
r.updated_at
|
|
FROM chat_rooms r
|
|
WHERE r.id = ?
|
|
');
|
|
$stmt->execute([$roomId]);
|
|
$room = $stmt->fetch(PDO::FETCH_ASSOC);
|
|
|
|
if ($room) {
|
|
$room['participants'] = $this->getRoomParticipants($roomId);
|
|
}
|
|
|
|
return $room;
|
|
}
|
|
|
|
/**
|
|
* Trouver une conversation privée existante entre deux utilisateurs
|
|
*/
|
|
private function findExistingPrivateRoom(int $user1, int $user2): ?array {
|
|
$stmt = $this->db->prepare('
|
|
SELECT r.*
|
|
FROM chat_rooms r
|
|
WHERE r.type = "private"
|
|
AND r.is_active = 1
|
|
AND EXISTS (
|
|
SELECT 1 FROM chat_participants p1
|
|
WHERE p1.room_id = r.id
|
|
AND p1.user_id = ?
|
|
AND p1.left_at IS NULL
|
|
)
|
|
AND EXISTS (
|
|
SELECT 1 FROM chat_participants p2
|
|
WHERE p2.room_id = r.id
|
|
AND p2.user_id = ?
|
|
AND p2.left_at IS NULL
|
|
)
|
|
AND (
|
|
SELECT COUNT(*)
|
|
FROM chat_participants p
|
|
WHERE p.room_id = r.id
|
|
AND p.left_at IS NULL
|
|
) = 2
|
|
');
|
|
$stmt->execute([$user1, $user2]);
|
|
$room = $stmt->fetch(PDO::FETCH_ASSOC);
|
|
|
|
if ($room) {
|
|
$room['participants'] = $this->getRoomParticipants($room['id']);
|
|
return $room;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Mettre à jour la date de dernière lecture
|
|
*/
|
|
private function updateLastRead(string $roomId, int $userId): void {
|
|
$stmt = $this->db->prepare('
|
|
UPDATE chat_participants
|
|
SET last_read_at = NOW()
|
|
WHERE room_id = ?
|
|
AND user_id = ?
|
|
');
|
|
$stmt->execute([$roomId, $userId]);
|
|
}
|
|
|
|
/**
|
|
* Générer un UUID v4
|
|
*/
|
|
private function generateUUID(): string {
|
|
return sprintf(
|
|
'%04x%04x-%04x-%04x-%04x-%04x%04x%04x',
|
|
mt_rand(0, 0xffff), mt_rand(0, 0xffff),
|
|
mt_rand(0, 0xffff),
|
|
mt_rand(0, 0x0fff) | 0x4000,
|
|
mt_rand(0, 0x3fff) | 0x8000,
|
|
mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0xffff)
|
|
);
|
|
}
|
|
} |