Files
2026-03-05 10:25:39 +01:00

130 lines
4.6 KiB
PHP

<?php
// /messages/api/retract_message.php
declare(strict_types=1);
require_once __DIR__ . '/../_bootstrap.php';
header('Content-Type: application/json; charset=utf-8');
/**
* Règles:
* - Seul l'expéditeur peut rétracter.
* - Fenêtre max (min) depuis l'envoi (par défaut 15 min).
* - Refus si déjà lu par l'autre partie.
* Effets:
* - messages.retracted_for_all = 1
* - body = NULL, attachment_* = NULL (et suppression fichier si présent)
* - Décrément du compteur non-lus de l'autre si le message était encore non lu.
*/
const RETRACT_WINDOW_MIN = 15;
if (!hash_equals($_SESSION['_csrf'] ?? '', $_POST['_csrf'] ?? '')) {
json_response(['error' => 'csrf'], 419);
}
$role = $GLOBALS['MESSAGES_CTX']['role']; // 'praticien' | 'client'
$meId = $GLOBALS['MESSAGES_CTX']['me_id'];
$msgId = (int)($_POST['message_id'] ?? 0);
if ($msgId <= 0) json_response(['error' => 'bad_params'], 400);
// Récupère le message + conversation
$sql = "SELECT m.id, m.conversation_id, m.sender_role, m.sender_id, m.created_at,
m.attachment_path, m.attachment_name, m.retracted_for_all,
c.praticien_id, c.client_id
FROM messages m
JOIN conversations c ON c.id = m.conversation_id
WHERE m.id = ?
LIMIT 1";
$stmt = $pdo->prepare($sql);
$stmt->execute([$msgId]);
$msg = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$msg) json_response(['error' => 'not_found'], 404);
// Vérifie que je participe bien à la conversation (sécurité)
assert_conversation_access($pdo, (int)$msg['conversation_id'], $role, $meId);
// Vérifie que je suis bien l'expéditeur
if (!($msg['sender_role'] === $role && (int)$msg['sender_id'] === $meId)) {
json_response(['error' => 'not_sender'], 403);
}
// Déjà rétracté ?
if (!empty($msg['retracted_for_all'])) {
json_response(['error' => 'already_retracted'], 400);
}
// Vérifie fenêtre temps
$win = (int)RETRACT_WINDOW_MIN;
$ageSql = $pdo->prepare("SELECT TIMESTAMPDIFF(MINUTE, ?, NOW()) AS age_min");
$ageSql->execute([$msg['created_at']]);
$ageMin = (int)$ageSql->fetchColumn();
if ($ageMin > $win) {
json_response(['error' => 'too_late', 'window_min' => $win], 400);
}
// Vérifie lecture par l'autre (si lu → refus)
$otherRole = ($role === 'praticien') ? 'client' : 'praticien';
$readChk = $pdo->prepare("SELECT 1 FROM message_reads WHERE message_id = ? AND user_role = ? LIMIT 1");
$readChk->execute([$msgId, $otherRole]);
$alreadyRead = (bool)$readChk->fetchColumn();
if ($alreadyRead) {
json_response(['error' => 'already_read'], 400);
}
// Si non lu par l'autre, il compte encore dans ses non-lus → on décrémentera
$counterCol = ($role === 'praticien') ? 'c_unread' : 'p_unread';
// Prépare suppression de l'éventuelle pièce jointe
$attPath = $msg['attachment_path'] ?? null;
$fullPath = null;
if (!empty($attPath) && is_string($attPath)) {
// Par convention: messages/uploads/FILENAME
if (strpos($attPath, 'messages/uploads/') === 0) {
// filesystem: /path/to/site/ + messages/uploads/...
$siteRoot = realpath(__DIR__ . '/../../'); // .../ (racine du site)
if ($siteRoot) {
$fullPath = $siteRoot . DIRECTORY_SEPARATOR . str_replace(['/', '\\'], DIRECTORY_SEPARATOR, $attPath);
}
}
}
try {
$pdo->beginTransaction();
// 1) Marquer rétracté (+ effacer contenu & PJ)
$upd = $pdo->prepare("UPDATE messages
SET retracted_for_all = 1,
body = NULL,
attachment_path = NULL,
attachment_name = NULL
WHERE id = ?");
$upd->execute([$msgId]);
// 2) Corriger le compteur de non-lus de l'autre si nécessaire (message encore non lu)
// On vérifie à nouveau, mais on l'a déjà en mémoire (alreadyRead=false) :
if (!$alreadyRead) {
$pdo->prepare("UPDATE conversation_counters
SET $counterCol = GREATEST($counterCol - 1, 0)
WHERE conversation_id = ?")
->execute([(int)$msg['conversation_id']]);
}
$pdo->commit();
} catch (Throwable $e) {
if ($pdo->inTransaction()) { $pdo->rollBack(); }
json_response(['error' => 'server_error'], 500);
}
// 3) Supprime physiquement la PJ (hors transaction DB)
if ($fullPath && is_file($fullPath)) {
// sécurité simple: s'assure que le chemin reste dans /messages/uploads/
$uploadsRoot = realpath(__DIR__ . '/../uploads/');
$fpReal = realpath($fullPath);
if ($uploadsRoot && $fpReal && strpos($fpReal, $uploadsRoot) === 0) {
@unlink($fpReal);
}
}
json_response(['ok' => true, 'message_id' => $msgId]);