130 lines
4.6 KiB
PHP
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]);
|