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

237 lines
7.5 KiB
PHP

<?php
declare(strict_types=1);
require_once __DIR__ . '/../_bootstrap.php';
header('Content-Type: application/json; charset=utf-8');
$role = $GLOBALS['MESSAGES_CTX']['role']; // 'praticien' ou 'client'
$meId = $GLOBALS['MESSAGES_CTX']['me_id']; // id praticien ou client
if (!hash_equals($_SESSION['_csrf'] ?? '', $_POST['_csrf'] ?? '')) {
json_response(['error' => 'csrf'], 419);
}
$convId = (int)($_POST['conversation_id'] ?? 0);
$body = isset($_POST['body']) ? trim((string)$_POST['body']) : '';
if ($convId <= 0) {
json_response(['error' => 'bad_params'], 400);
}
// Autorisation + statut
$conv = assert_conversation_access($pdo, $convId, $role, $meId);
if (($conv['status'] ?? 'open') !== 'open') {
json_response(['error' => 'closed'], 403);
}
// Upload (optionnel)
$attPath = null;
$attName = null;
if (!empty($_FILES['file']['name'] ?? '')) {
$okExt = ['jpg','jpeg','png','pdf','doc','docx'];
$name = (string)$_FILES['file']['name'];
$size = (int)$_FILES['file']['size'];
$tmp = (string)$_FILES['file']['tmp_name'];
$ext = strtolower(pathinfo($name, PATHINFO_EXTENSION));
if (!in_array($ext, $okExt, true)) {
json_response(['error' => 'invalid_file_type'], 400);
}
if ($size > 10 * 1024 * 1024) { // 10 Mo
json_response(['error' => 'file_too_large'], 400);
}
$dir = realpath(__DIR__ . '/..') . '/uploads/';
if (!is_dir($dir)) { @mkdir($dir, 0775, true); }
$safe = $meId . '_' . time() . '_' . bin2hex(random_bytes(4)) . '.' . $ext;
$dest = $dir . $safe;
if (!move_uploaded_file($tmp, $dest)) {
json_response(['error' => 'upload_failed'], 500);
}
// Chemin web relatif (sans leading slash pour constituer une URL /messages/uploads/..)
$attPath = 'messages/uploads/' . $safe;
$attName = $name;
}
// Flag : doit-on envoyer une notification mail ?
$shouldNotify = false;
try {
$pdo->beginTransaction();
// Insérer le message
$ins = $pdo->prepare("
INSERT INTO messages
(conversation_id, sender_role, sender_id, body, attachment_path, attachment_name, created_at)
VALUES
(?, ?, ?, ?, ?, ?, NOW())
");
$ins->execute([
$convId,
$role,
$meId,
$body !== '' ? $body : null,
$attPath,
$attName
]);
$msgId = (int)$pdo->lastInsertId();
// Mettre à jour les compteurs de non-lus (chez l'autre)
if ($role === 'praticien') {
// Le praticien écrit → c'est le client qui a des messages non lus (c_unread)
$stmt = $pdo->prepare("
SELECT c_unread
FROM conversation_counters
WHERE conversation_id = ?
FOR UPDATE
");
$stmt->execute([$convId]);
$prev = $stmt->fetchColumn();
$prevUnread = ($prev === false) ? 0 : (int)$prev;
$pdo->prepare("
UPDATE conversation_counters
SET c_unread = c_unread + 1
WHERE conversation_id = ?
")->execute([$convId]);
if ($prevUnread === 0) {
$shouldNotify = true;
}
} else {
// Le client écrit → c'est le praticien qui a des messages non lus (p_unread)
$stmt = $pdo->prepare("
SELECT p_unread
FROM conversation_counters
WHERE conversation_id = ?
FOR UPDATE
");
$stmt->execute([$convId]);
$prev = $stmt->fetchColumn();
$prevUnread = ($prev === false) ? 0 : (int)$prev;
$pdo->prepare("
UPDATE conversation_counters
SET p_unread = p_unread + 1
WHERE conversation_id = ?
")->execute([$convId]);
if ($prevUnread === 0) {
$shouldNotify = true;
}
}
// Bump updated_at
$pdo->prepare("
UPDATE conversations
SET updated_at = NOW()
WHERE id = ?
")->execute([$convId]);
$pdo->commit();
} catch (Throwable $e) {
if ($pdo->inTransaction()) { $pdo->rollBack(); }
error_log('send_message error: '.$e->getMessage());
json_response(['error' => 'server_error'], 500);
}
// 🔔 Notification par e-mail (ne doit jamais casser la réponse JSON)
if ($shouldNotify) {
try {
require_once __DIR__ . '/../../public/message_par_mail.php';
notifier_nouveau_message_par_email($pdo, $convId, $role);
} catch (Throwable $e) {
error_log('Notif mail error: '.$e->getMessage());
}
try {
// Déterminer le destinataire (l'autre)
$targetRole = ($role === 'praticien') ? 'client' : 'praticien';
// Normalement assert_conversation_access() te renvoie ces champs,
// sinon fallback query (au cas où)
$clientId = (int)($conv['client_id'] ?? 0);
$praticienId = (int)($conv['praticien_id'] ?? 0);
if ($clientId <= 0 || $praticienId <= 0) {
$st = $pdo->prepare("SELECT client_id, praticien_id FROM conversations WHERE id=? LIMIT 1");
$st->execute([$convId]);
$row = $st->fetch(PDO::FETCH_ASSOC) ?: [];
$clientId = (int)($row['client_id'] ?? 0);
$praticienId = (int)($row['praticien_id'] ?? 0);
}
$targetRef = ($targetRole === 'client') ? $clientId : $praticienId;
// ✅ Trouver le compte_id du destinataire (obligatoire pour worker + badge global)
$stC = $pdo->prepare("
SELECT id
FROM compte
WHERE role = :role AND reference_id = :ref
LIMIT 1
");
$stC->execute([
':role' => $targetRole,
':ref' => $targetRef,
]);
$targetCompteId = (int)($stC->fetchColumn() ?: 0);
if ($targetRef > 0) {
// Texte push (simple)
$title = 'Новое сообщение c uslugi.fr';
$text = 'У вас новое сообщение с uslugi.fr.';
if ($body !== '') {
$snippet = mb_substr($body, 0, 120);
$text = $snippet;
}
$url = ($targetRole === 'client')
? '/messages/?conv=' . $convId
: '/messages/?conv=' . $convId;
$data = json_encode([
'message_id' => $msgId,
'sender_role' => $role,
'conversation_id' => $convId,
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
if ($targetCompteId > 0) {
$pdo->prepare("
INSERT INTO push_badge_counters (compte_id, msg_unread, rdv_unread)
VALUES (:cid, 1, 0)
ON DUPLICATE KEY UPDATE
msg_unread = LEAST(msg_unread + 1, 99),
updated_at = NOW()
")->execute([':cid' => $targetCompteId]);
}
$insPush = $pdo->prepare("
INSERT INTO push_notifications
(target_role, target_reference_id, target_compte_id, type, title, body, url, data_json, status, attempts, created_at)
VALUES
(:role, :ref, :cid, 'message_new', :title, :body, :url, :data, 'pending', 0, NOW())
");
$insPush->execute([
':role' => $targetRole,
':ref' => $targetRef,
':cid' => ($targetCompteId > 0 ? $targetCompteId : null),
':title' => $title,
':body' => $text,
':url' => $url,
':data' => $data,
]);
}
} catch (Throwable $e) {
error_log('Notif push error: ' . $e->getMessage());
}
}
json_response(['ok' => true, 'message_id' => $msgId]);