Init Git Prod Mars 2026
This commit is contained in:
commit
d81efe93b1
362 files changed
+86627
No files matched your search
@@ -0,0 +1,129 @@
|
||||
<?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]);
|
||||
Reference in new issue
Block a user