Init Git Prod Mars 2026
This commit is contained in:
commit
d81efe93b1
362 files changed
+86627
No files matched your search
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
// /messages/api/archive_conversation.php
|
||||
declare(strict_types=1);
|
||||
require_once __DIR__ . '/../_bootstrap.php';
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
|
||||
if (!hash_equals($_SESSION['_csrf'] ?? '', $_POST['_csrf'] ?? '')) {
|
||||
json_response(['error' => 'csrf'], 419);
|
||||
}
|
||||
|
||||
$role = $GLOBALS['MESSAGES_CTX']['role'];
|
||||
$meId = $GLOBALS['MESSAGES_CTX']['me_id'];
|
||||
$convId = (int)($_POST['conversation_id'] ?? 0);
|
||||
$archive= (int)($_POST['archive'] ?? 1);
|
||||
|
||||
if ($convId <= 0) json_response(['error' => 'bad_params'], 400);
|
||||
|
||||
assert_conversation_access($pdo, $convId, $role, $meId);
|
||||
|
||||
$col = ($role === 'praticien') ? 'archived_by_praticien' : 'archived_by_client';
|
||||
$pdo->prepare("UPDATE conversations SET $col = ? WHERE id = ?")->execute([$archive ? 1 : 0, $convId]);
|
||||
|
||||
json_response(['ok' => true, 'archived' => (bool)$archive]);
|
||||
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
// /messages/api/close_conversation.php
|
||||
declare(strict_types=1);
|
||||
require_once __DIR__ . '/../_bootstrap.php';
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
|
||||
if (!hash_equals($_SESSION['_csrf'] ?? '', $_POST['_csrf'] ?? '')) {
|
||||
json_response(['error' => 'csrf'], 419);
|
||||
}
|
||||
|
||||
$role = $GLOBALS['MESSAGES_CTX']['role'];
|
||||
$meId = $GLOBALS['MESSAGES_CTX']['me_id'];
|
||||
$convId = (int)($_POST['conversation_id'] ?? 0);
|
||||
$close = (int)($_POST['close'] ?? 1);
|
||||
|
||||
if ($convId <= 0) json_response(['error' => 'bad_params'], 400);
|
||||
|
||||
// Vérifie l'accès (participant à la conversation)
|
||||
assert_conversation_access($pdo, $convId, $role, $meId);
|
||||
|
||||
// Met à jour le statut
|
||||
if ($close) {
|
||||
$pdo->prepare("UPDATE conversations SET status='closed', closed_at=NOW() WHERE id=?")->execute([$convId]);
|
||||
} else {
|
||||
$pdo->prepare("UPDATE conversations SET status='open', closed_at=NULL WHERE id=?")->execute([$convId]);
|
||||
}
|
||||
|
||||
json_response(['ok' => true, 'status' => $close ? 'closed' : 'open']);
|
||||
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
// /messages/api/delete_conversation.php
|
||||
declare(strict_types=1);
|
||||
|
||||
require_once __DIR__ . '/../_bootstrap.php';
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
|
||||
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'];
|
||||
$convId = (int)($_POST['conversation_id'] ?? 0);
|
||||
|
||||
if ($convId <= 0) {
|
||||
json_response(['error' => 'bad_params'], 400);
|
||||
}
|
||||
|
||||
// Vérifie l'accès (participant)
|
||||
$conv = assert_conversation_access($pdo, $convId, $role, $meId);
|
||||
|
||||
// Colonnes dépendant du rôle
|
||||
$colDeleted = ($role === 'praticien') ? 'deleted_for_praticien' : 'deleted_for_client';
|
||||
$colUnread = ($role === 'praticien') ? 'p_unread' : 'c_unread';
|
||||
|
||||
try {
|
||||
$pdo->beginTransaction();
|
||||
|
||||
// 1) Marquer la conversation supprimée "pour moi"
|
||||
$pdo->prepare("UPDATE conversations SET $colDeleted = 1 WHERE id = ?")
|
||||
->execute([$convId]);
|
||||
|
||||
// 2) Masquer tous ses messages "pour moi"
|
||||
$pdo->prepare("UPDATE messages SET $colDeleted = 1 WHERE conversation_id = ?")
|
||||
->execute([$convId]);
|
||||
|
||||
// 3) Réinitialiser mes non-lus sur cette conversation
|
||||
$pdo->prepare("UPDATE conversation_counters SET $colUnread = 0 WHERE conversation_id = ?")
|
||||
->execute([$convId]);
|
||||
|
||||
$pdo->commit();
|
||||
} catch (Throwable $e) {
|
||||
if ($pdo->inTransaction()) { $pdo->rollBack(); }
|
||||
json_response(['error' => 'server_error'], 500);
|
||||
}
|
||||
|
||||
json_response(['ok' => true]);
|
||||
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
// /messages/api/delete_message.php
|
||||
declare(strict_types=1);
|
||||
require_once __DIR__ . '/../_bootstrap.php';
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
|
||||
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 message + conversation et vérifie l'accès
|
||||
$sql = "SELECT m.id, m.conversation_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]);
|
||||
$row = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if (!$row) json_response(['error' => 'not_found'], 404);
|
||||
|
||||
// Autorisation (est-ce que je participe à cette conversation ?)
|
||||
assert_conversation_access($pdo, (int)$row['conversation_id'], $role, $meId);
|
||||
|
||||
// Marque “supprimé pour moi” selon le rôle
|
||||
$col = ($role === 'praticien') ? 'deleted_for_praticien' : 'deleted_for_client';
|
||||
$upd = $pdo->prepare("UPDATE messages SET $col = 1 WHERE id = ?");
|
||||
$upd->execute([$msgId]);
|
||||
|
||||
json_response(['ok' => true]);
|
||||
@@ -0,0 +1,113 @@
|
||||
<?php
|
||||
// /messages/api/download_attachment.php
|
||||
declare(strict_types=1);
|
||||
|
||||
require_once __DIR__ . '/../_bootstrap.php';
|
||||
|
||||
// Important: aucune sortie HTML avant les headers !
|
||||
/**
|
||||
* Règles d’accès:
|
||||
* - On reçoit ?message_id=ID (GET).
|
||||
* - Vérifie que l’utilisateur courant participe à la conversation du message.
|
||||
* - Vérifie que le message n’est pas rétracté et pas "supprimé pour moi".
|
||||
* - Sert le fichier avec le bon Content-Type et Content-Disposition.
|
||||
*/
|
||||
|
||||
$messageId = (int)($_GET['message_id'] ?? 0);
|
||||
if ($messageId <= 0) {
|
||||
http_response_code(400);
|
||||
echo "Bad params";
|
||||
exit;
|
||||
}
|
||||
|
||||
$role = $GLOBALS['MESSAGES_CTX']['role']; // 'praticien'|'client' (en CLI, inutilisé)
|
||||
$meId = $GLOBALS['MESSAGES_CTX']['me_id'];
|
||||
|
||||
$hideCol = ($role === 'praticien') ? 'deleted_for_praticien' : 'deleted_for_client';
|
||||
|
||||
// Récupère message + conversation
|
||||
$sql = "SELECT m.id, m.conversation_id, m.attachment_path, m.attachment_name,
|
||||
m.retracted_for_all, m.$hideCol AS hidden_for_me,
|
||||
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([$messageId]);
|
||||
$row = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if (!$row) {
|
||||
http_response_code(404);
|
||||
echo "Message not found";
|
||||
exit;
|
||||
}
|
||||
|
||||
// Accès: dois être participant de la conversation
|
||||
assert_conversation_access($pdo, (int)$row['conversation_id'], $role, (int)$meId);
|
||||
|
||||
// Visibilité: pas rétracté, pas supprimé pour moi, et PJ existante
|
||||
if (!empty($row['retracted_for_all']) || !empty($row['hidden_for_me'])) {
|
||||
http_response_code(403);
|
||||
echo "Forbidden";
|
||||
exit;
|
||||
}
|
||||
$webRel = (string)($row['attachment_path'] ?? '');
|
||||
if ($webRel === '') {
|
||||
http_response_code(404);
|
||||
echo "No attachment";
|
||||
exit;
|
||||
}
|
||||
|
||||
// Sécurité chemin: doit commencer par messages/uploads/
|
||||
if (strpos($webRel, 'messages/uploads/') !== 0) {
|
||||
http_response_code(400);
|
||||
echo "Invalid path";
|
||||
exit;
|
||||
}
|
||||
|
||||
// Résolution chemin disque
|
||||
$siteRoot = realpath(__DIR__ . '/../../'); // racine site
|
||||
$uploadsAbs = $siteRoot . DIRECTORY_SEPARATOR . str_replace(['/', '\\'], DIRECTORY_SEPARATOR, $webRel);
|
||||
$real = realpath($uploadsAbs);
|
||||
$uploadsRoot = realpath(__DIR__ . '/../uploads/');
|
||||
|
||||
if (!$real || !$uploadsRoot || strpos($real, $uploadsRoot) !== 0 || !is_file($real)) {
|
||||
http_response_code(404);
|
||||
echo "File not found";
|
||||
exit;
|
||||
}
|
||||
|
||||
// Détection MIME + nom fichier
|
||||
$finfo = function_exists('finfo_open') ? finfo_open(FILEINFO_MIME_TYPE) : false;
|
||||
$mime = ($finfo && is_file($real)) ? finfo_file($finfo, $real) : null;
|
||||
if ($finfo) { @finfo_close($finfo); }
|
||||
if (!$mime) $mime = 'application/octet-stream';
|
||||
|
||||
$name = (string)($row['attachment_name'] ?? basename($real));
|
||||
$size = filesize($real);
|
||||
|
||||
// En-têtes (évite le sniffing, force téléchargement)
|
||||
header('Content-Type: ' . $mime);
|
||||
header('Content-Length: ' . (string)$size);
|
||||
header('X-Content-Type-Options: nosniff');
|
||||
header('Content-Security-Policy: default-src \'none\'; frame-ancestors \'none\'; sandbox');
|
||||
header('Content-Disposition: attachment; filename*=UTF-8\'\'' . rawurlencode($name));
|
||||
|
||||
// Lecture par chunks (robuste)
|
||||
$fp = @fopen($real, 'rb');
|
||||
if (!$fp) {
|
||||
http_response_code(500);
|
||||
echo "Cannot open file";
|
||||
exit;
|
||||
}
|
||||
|
||||
while (!feof($fp)) {
|
||||
$buf = fread($fp, 8192);
|
||||
if ($buf === false) break;
|
||||
echo $buf;
|
||||
// facultatif: flush pour gros fichiers
|
||||
if (function_exists('fastcgi_finish_request')) { /* noop here */ }
|
||||
}
|
||||
fclose($fp);
|
||||
exit;
|
||||
@@ -0,0 +1,92 @@
|
||||
<?php
|
||||
// /messages/api/fetch_messages.php
|
||||
declare(strict_types=1);
|
||||
require_once __DIR__ . '/../_bootstrap.php';
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
|
||||
$role = $GLOBALS['MESSAGES_CTX']['role'];
|
||||
$meId = $GLOBALS['MESSAGES_CTX']['me_id'];
|
||||
|
||||
$convId = (int)($_GET['conversation_id'] ?? 0);
|
||||
$sinceId = (int)($_GET['since_id'] ?? 0); // pour récupérer les NOUVEAUX messages (id > since_id)
|
||||
$beforeId = (int)($_GET['before_id'] ?? 0); // pour paginer vers le HAUT (id < before_id)
|
||||
$limit = (int)($_GET['limit'] ?? 50); // taille de page (max 100)
|
||||
|
||||
if ($convId <= 0) json_response(['error' => 'bad_params'], 400);
|
||||
if ($limit <= 0) $limit = 50;
|
||||
if ($limit > 100) $limit = 100;
|
||||
|
||||
// Vérifie droits et récupère la conversation
|
||||
$conv = assert_conversation_access($pdo, $convId, $role, $meId);
|
||||
|
||||
// Filtre de visibilité selon le rôle (suppression “pour moi”)
|
||||
$hideCol = ($role === 'praticien') ? 'deleted_for_praticien' : 'deleted_for_client';
|
||||
|
||||
// Stratégie de tri:
|
||||
// - On interroge en DESC (plus récents en premier) pour limiter efficacement,
|
||||
// - puis on reverse en PHP pour renvoyer ASC (du plus ancien au plus récent) côté client.
|
||||
$params = [$convId];
|
||||
$where = "conversation_id = ? AND $hideCol = 0";
|
||||
$order = "ORDER BY id DESC";
|
||||
$limitSql = "LIMIT $limit";
|
||||
|
||||
if ($sinceId > 0 && $beforeId > 0) {
|
||||
// Cas ambigu — on privilégie since_id (nouveaux messages)
|
||||
$beforeId = 0;
|
||||
}
|
||||
|
||||
if ($sinceId > 0) {
|
||||
$where .= " AND id > ?";
|
||||
$params[] = $sinceId;
|
||||
} elseif ($beforeId > 0) {
|
||||
$where .= " AND id < ?";
|
||||
$params[] = $beforeId;
|
||||
} else {
|
||||
// premier chargement (pas de since/before) → rien à ajouter
|
||||
}
|
||||
|
||||
$sql = "SELECT id, conversation_id, sender_role, sender_id, body,
|
||||
attachment_path, attachment_name, retracted_for_all, created_at
|
||||
FROM messages
|
||||
WHERE $where
|
||||
$order
|
||||
$limitSql";
|
||||
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$stmt->execute($params);
|
||||
$rowsDesc = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
// Transformations + placeholder “(Message supprimé)”
|
||||
$rows = array_reverse($rowsDesc); // renvoyer ASC
|
||||
foreach ($rows as &$m) {
|
||||
$m['id'] = (int)$m['id'];
|
||||
$m['conversation_id'] = (int)$m['conversation_id'];
|
||||
$m['sender_id'] = (int)$m['sender_id'];
|
||||
if (!empty($m['retracted_for_all'])) {
|
||||
$m['body'] = '(' . ($translations['msg_message_supprime'] ?? 'Message supprimé') . ')';
|
||||
$m['attachment_path'] = null;
|
||||
$m['attachment_name'] = null;
|
||||
}
|
||||
unset($m['retracted_for_all']);
|
||||
}
|
||||
unset($m);
|
||||
|
||||
// Calcul du “has_more” pour la pagination vers le haut (before_id)
|
||||
$hasMore = false;
|
||||
$nextBeforeId = null;
|
||||
if (!empty($rows)) {
|
||||
// Si on a utilisé beforeId (ou premier load), vérifier s'il reste des messages plus anciens
|
||||
// On prend le plus ancien renvoyé (premier en ASC) et teste s'il existe plus ancien
|
||||
$oldestId = $rows[0]['id'];
|
||||
$stmt2 = $pdo->prepare("SELECT 1 FROM messages WHERE conversation_id = ? AND $hideCol = 0 AND id < ? LIMIT 1");
|
||||
$stmt2->execute([$convId, $oldestId]);
|
||||
$hasMore = (bool)$stmt2->fetchColumn();
|
||||
$nextBeforeId = $oldestId; // pour l'appel suivant: before_id = oldestId
|
||||
}
|
||||
|
||||
json_response([
|
||||
'items' => $rows,
|
||||
'has_more' => $hasMore,
|
||||
'next_before_id'=> $hasMore ? $nextBeforeId : null,
|
||||
]);
|
||||
@@ -0,0 +1,60 @@
|
||||
<?php
|
||||
// /messages/api/get_conversation.php
|
||||
declare(strict_types=1);
|
||||
|
||||
require_once __DIR__ . '/../_bootstrap.php';
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
|
||||
$role = $GLOBALS['MESSAGES_CTX']['role']; // 'praticien' | 'client'
|
||||
$meId = (int)$GLOBALS['MESSAGES_CTX']['me_id'];
|
||||
|
||||
$convId = (int)($_GET['conversation_id'] ?? 0);
|
||||
if ($convId <= 0) {
|
||||
json_response(['ok'=>false, 'error'=>'bad_params'], 400);
|
||||
}
|
||||
|
||||
// Vérifie l'accès et récupère la conversation de base
|
||||
$conv = assert_conversation_access($pdo, $convId, $role, $meId);
|
||||
|
||||
// Récupère détails + noms/prénoms + états d’archivage
|
||||
$sql = "
|
||||
SELECT
|
||||
c.id, c.status, c.praticien_id, c.client_id,
|
||||
c.archived_by_praticien, c.archived_by_client,
|
||||
p.nom AS p_nom, p.prenom AS p_prenom,
|
||||
cl.nom AS c_nom, cl.prenom AS c_prenom
|
||||
FROM conversations c
|
||||
JOIN praticien p ON p.id = c.praticien_id
|
||||
JOIN client cl ON cl.id = c.client_id
|
||||
WHERE c.id = ?
|
||||
LIMIT 1";
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$stmt->execute([$convId]);
|
||||
$row = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if (!$row) {
|
||||
json_response(['ok'=>false, 'error'=>'conversation_not_found'], 404);
|
||||
}
|
||||
|
||||
// Nom de l’interlocuteur (peer)
|
||||
if ($role === 'praticien') {
|
||||
$peerName = trim(sprintf('%s %s', $row['c_prenom'] ?? '', $row['c_nom'] ?? ''));
|
||||
$archForMe = (int)$row['archived_by_praticien'];
|
||||
} else { // client
|
||||
$peerName = trim(sprintf('%s %s', $row['p_prenom'] ?? '', $row['p_nom'] ?? ''));
|
||||
$archForMe = (int)$row['archived_by_client'];
|
||||
}
|
||||
if ($peerName === '') $peerName = 'Conversation #' . $convId;
|
||||
|
||||
// Réponse
|
||||
json_response([
|
||||
'ok' => true,
|
||||
'id' => (int)$row['id'],
|
||||
'status' => (string)$row['status'], // 'open' | 'closed'
|
||||
'archived_for_me' => $archForMe, // 0|1
|
||||
'peer_name' => $peerName,
|
||||
// (optionnel utile côté UI)
|
||||
'praticien_id' => (int)$row['praticien_id'],
|
||||
'client_id' => (int)$row['client_id'],
|
||||
]);
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
<?php
|
||||
// /messages/api/list_conversations.php
|
||||
declare(strict_types=1);
|
||||
|
||||
require_once __DIR__ . '/../_bootstrap.php';
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
|
||||
$role = $GLOBALS['MESSAGES_CTX']['role'] ?? '';
|
||||
$meId = (int)($GLOBALS['MESSAGES_CTX']['me_id'] ?? 0);
|
||||
|
||||
// Sécurité: on ne gère ici que praticien/client
|
||||
if (!in_array($role, ['praticien','client'], true)) {
|
||||
json_response(['items' => []]);
|
||||
}
|
||||
if ($meId <= 0) {
|
||||
json_response(['items' => []]);
|
||||
}
|
||||
|
||||
// colonnes de visibilité/suppression selon le rôle
|
||||
$hideCol = ($role === 'praticien') ? 'deleted_for_praticien' : 'deleted_for_client';
|
||||
$archCol = ($role === 'praticien') ? 'archived_by_praticien' : 'archived_by_client'; // (pas utilisé ici, mais OK)
|
||||
|
||||
// Condition WHERE selon le rôle (évite :role répété => HY093)
|
||||
$where = ($role === 'praticien')
|
||||
? 'c.praticien_id = :me'
|
||||
: 'c.client_id = :me';
|
||||
|
||||
// Récupère les conversations qui me concernent (non supprimées pour moi)
|
||||
$sql = "
|
||||
SELECT
|
||||
c.id,
|
||||
c.praticien_id,
|
||||
c.client_id,
|
||||
c.status,
|
||||
cc.p_unread,
|
||||
cc.c_unread,
|
||||
-- dernier message (pour l’aperçu)
|
||||
(SELECT m.body FROM messages m
|
||||
WHERE m.conversation_id = c.id
|
||||
AND m.$hideCol = 0
|
||||
ORDER BY m.id DESC LIMIT 1) AS last_body,
|
||||
|
||||
-- jointures pour noms
|
||||
p.nom AS p_nom, p.prenom AS p_prenom,
|
||||
cl.nom AS c_nom, cl.prenom AS c_prenom
|
||||
|
||||
FROM conversations c
|
||||
JOIN conversation_counters cc ON cc.conversation_id = c.id
|
||||
JOIN praticien p ON p.id = c.praticien_id
|
||||
JOIN client cl ON cl.id = c.client_id
|
||||
|
||||
WHERE
|
||||
$where
|
||||
AND c.$hideCol = 0
|
||||
ORDER BY c.id DESC
|
||||
";
|
||||
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$stmt->execute([':me' => $meId]);
|
||||
|
||||
$items = [];
|
||||
while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
|
||||
$id = (int)$row['id'];
|
||||
|
||||
// nom affiché = l’autre participant
|
||||
if ($role === 'praticien') {
|
||||
$peer = trim(($row['c_prenom'] ?? '').' '.($row['c_nom'] ?? ''));
|
||||
} else {
|
||||
$peer = trim(($row['p_prenom'] ?? '').' '.($row['p_nom'] ?? ''));
|
||||
}
|
||||
if ($peer === '') $peer = "Conversation #$id"; // fallback
|
||||
|
||||
// compteur non lus côté moi
|
||||
$unread = ($role === 'praticien') ? (int)$row['p_unread'] : (int)$row['c_unread'];
|
||||
|
||||
$items[] = [
|
||||
'id' => $id,
|
||||
'peer_name' => $peer,
|
||||
'last_body' => (string)($row['last_body'] ?? ''),
|
||||
'unread' => $unread,
|
||||
'status' => (string)$row['status'],
|
||||
];
|
||||
}
|
||||
|
||||
json_response(['items' => $items]);
|
||||
@@ -0,0 +1,122 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
require_once __DIR__ . '/../_bootstrap.php';
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
|
||||
$role = $GLOBALS['MESSAGES_CTX']['role']; // 'client' | 'praticien'
|
||||
$meId = (int)$GLOBALS['MESSAGES_CTX']['me_id'];
|
||||
$convId = (int)($_POST['conversation_id'] ?? 0);
|
||||
|
||||
if ($convId <= 0) {
|
||||
json_response(['error' => 'bad_params'], 400);
|
||||
}
|
||||
|
||||
// Autorisation d’accès à la conversation
|
||||
assert_conversation_access($pdo, $convId, $role, $meId);
|
||||
|
||||
try {
|
||||
$pdo->beginTransaction();
|
||||
|
||||
// ==================================================
|
||||
// 1) Marquer les messages comme lus
|
||||
// ==================================================
|
||||
$sql = "
|
||||
INSERT IGNORE INTO message_reads (message_id, user_role, user_id, read_at)
|
||||
SELECT m.id, ?, ?, NOW()
|
||||
FROM messages m
|
||||
WHERE m.conversation_id = ?
|
||||
";
|
||||
$pdo->prepare($sql)->execute([$role, $meId, $convId]);
|
||||
|
||||
// ==================================================
|
||||
// 2) Reset compteur de la conversation
|
||||
// ==================================================
|
||||
if ($role === 'praticien') {
|
||||
$pdo->prepare("
|
||||
UPDATE conversation_counters
|
||||
SET p_unread = 0
|
||||
WHERE conversation_id = ?
|
||||
")->execute([$convId]);
|
||||
} else {
|
||||
$pdo->prepare("
|
||||
UPDATE conversation_counters
|
||||
SET c_unread = 0
|
||||
WHERE conversation_id = ?
|
||||
")->execute([$convId]);
|
||||
}
|
||||
|
||||
// ==================================================
|
||||
// 3) Recalcul msg_unread GLOBAL pour le compte
|
||||
// (source de vérité = conversation_counters)
|
||||
// ==================================================
|
||||
$compteId = (int)($_SESSION['compte_id'] ?? 0);
|
||||
$msgUnread = 0;
|
||||
$rdvUnread = 0;
|
||||
|
||||
if ($compteId > 0) {
|
||||
|
||||
if ($role === 'praticien') {
|
||||
$stmt = $pdo->prepare("
|
||||
SELECT COALESCE(SUM(cc.p_unread), 0)
|
||||
FROM conversations c
|
||||
JOIN conversation_counters cc ON cc.conversation_id = c.id
|
||||
WHERE c.praticien_id = ?
|
||||
");
|
||||
$stmt->execute([$meId]);
|
||||
} else {
|
||||
$stmt = $pdo->prepare("
|
||||
SELECT COALESCE(SUM(cc.c_unread), 0)
|
||||
FROM conversations c
|
||||
JOIN conversation_counters cc ON cc.conversation_id = c.id
|
||||
WHERE c.client_id = ?
|
||||
");
|
||||
$stmt->execute([$meId]);
|
||||
}
|
||||
|
||||
$msgUnread = (int)$stmt->fetchColumn();
|
||||
|
||||
// ==================================================
|
||||
// 4) Mise à jour push_badge_counters
|
||||
// ⚠️ msg_unread uniquement (rdv_unread préservé)
|
||||
// ==================================================
|
||||
$pdo->prepare("
|
||||
INSERT INTO push_badge_counters (compte_id, msg_unread, rdv_unread, updated_at)
|
||||
VALUES (:cid, :msg, 0, NOW())
|
||||
ON DUPLICATE KEY UPDATE
|
||||
msg_unread = VALUES(msg_unread),
|
||||
updated_at = NOW()
|
||||
")->execute([
|
||||
':cid' => $compteId,
|
||||
':msg' => $msgUnread,
|
||||
]);
|
||||
|
||||
// Relire rdv_unread sans le modifier
|
||||
$stmt = $pdo->prepare("
|
||||
SELECT rdv_unread
|
||||
FROM push_badge_counters
|
||||
WHERE compte_id = :cid
|
||||
");
|
||||
$stmt->execute([':cid' => $compteId]);
|
||||
$rdvUnread = (int)($stmt->fetchColumn() ?? 0);
|
||||
}
|
||||
|
||||
$pdo->commit();
|
||||
|
||||
// ==================================================
|
||||
// 5) Réponse JSON pour synchro UI + badge PWA
|
||||
// ==================================================
|
||||
json_response([
|
||||
'ok' => true,
|
||||
'msg_unread' => $msgUnread,
|
||||
'rdv_unread' => $rdvUnread,
|
||||
'app_badge' => $msgUnread + $rdvUnread,
|
||||
]);
|
||||
|
||||
} catch (Throwable $e) {
|
||||
if ($pdo->inTransaction()) {
|
||||
$pdo->rollBack();
|
||||
}
|
||||
json_response(['error' => 'server_error'], 500);
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
<?php
|
||||
// /messages/api/open_or_create_conversation.php
|
||||
declare(strict_types=1);
|
||||
require_once __DIR__ . '/../_bootstrap.php';
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
|
||||
$role = $GLOBALS['MESSAGES_CTX']['role']; // 'praticien' | 'client'
|
||||
$meId = (int)$GLOBALS['MESSAGES_CTX']['me_id'];
|
||||
|
||||
$peerRole = $_POST['peer_role'] ?? $_GET['peer_role'] ?? '';
|
||||
$peerId = (int)($_POST['peer_id'] ?? $_GET['peer_id'] ?? 0);
|
||||
|
||||
// sécurité minimale + cohérence rôles
|
||||
if (!in_array($peerRole, ['praticien','client'], true) || $peerId<=0) {
|
||||
json_response(['ok'=>false,'error'=>'bad_params'],400);
|
||||
}
|
||||
if ($role === $peerRole) { // on attend l’autre rôle
|
||||
json_response(['ok'=>false,'error'=>'target_must_be_other_role'],400);
|
||||
}
|
||||
|
||||
// Déterminer les 2 ids
|
||||
if ($role === 'client') {
|
||||
$clientId = $meId;
|
||||
$praticienId = $peerId;
|
||||
} else { // role=praticien
|
||||
$praticienId = $meId;
|
||||
$clientId = $peerId;
|
||||
}
|
||||
|
||||
// Chercher une conversation existante entre ces 2
|
||||
$stmt = $pdo->prepare("SELECT * FROM conversations WHERE praticien_id=? AND client_id=? LIMIT 1");
|
||||
$stmt->execute([$praticienId,$clientId]);
|
||||
$conv = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if ($conv) {
|
||||
// Restaurer pour moi si supprimée/archivée et rouvrir si besoin
|
||||
if ($role === 'client') {
|
||||
$pdo->prepare("UPDATE conversations
|
||||
SET deleted_for_client=0, archived_by_client=0, status='open', updated_at=NOW()
|
||||
WHERE id=?")->execute([(int)$conv['id']]);
|
||||
} else {
|
||||
$pdo->prepare("UPDATE conversations
|
||||
SET deleted_for_praticien=0, archived_by_praticien=0, status='open', updated_at=NOW()
|
||||
WHERE id=?")->execute([(int)$conv['id']]);
|
||||
}
|
||||
json_response(['ok'=>true,'conversation_id'=>(int)$conv['id']]);
|
||||
}
|
||||
|
||||
// Sinon, créer la conversation + counters
|
||||
$pdo->beginTransaction();
|
||||
try{
|
||||
$pdo->prepare("INSERT INTO conversations
|
||||
(praticien_id, client_id, status,
|
||||
archived_by_praticien, archived_by_client,
|
||||
deleted_for_praticien, deleted_for_client,
|
||||
created_at, updated_at)
|
||||
VALUES (?, ?, 'open', 0, 0, 0, 0, NOW(), NOW())
|
||||
")->execute([$praticienId,$clientId]);
|
||||
|
||||
$newId = (int)$pdo->lastInsertId();
|
||||
|
||||
// counters (si table présente)
|
||||
if ($pdo->query("SHOW TABLES LIKE 'conversation_counters'")->rowCount()){
|
||||
$pdo->prepare("INSERT INTO conversation_counters (conversation_id, p_unread, c_unread) VALUES (?,0,0)")
|
||||
->execute([$newId]);
|
||||
}
|
||||
|
||||
$pdo->commit();
|
||||
json_response(['ok'=>true,'conversation_id'=>$newId]);
|
||||
}catch(Throwable $e){
|
||||
$pdo->rollBack();
|
||||
json_response(['ok'=>false,'error'=>'create_failed'],500);
|
||||
}
|
||||
@@ -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]);
|
||||
@@ -0,0 +1,236 @@
|
||||
<?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]);
|
||||
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
require_once __DIR__ . '/../_bootstrap.php';
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
|
||||
$role = $GLOBALS['MESSAGES_CTX']['role'];
|
||||
$meId = $GLOBALS['MESSAGES_CTX']['me_id'];
|
||||
|
||||
if ($role === 'praticien') {
|
||||
$sql = "SELECT COALESCE(SUM(cc.p_unread),0)
|
||||
FROM conversations c
|
||||
LEFT JOIN conversation_counters cc ON cc.conversation_id = c.id
|
||||
WHERE c.praticien_id = ?
|
||||
AND c.deleted_for_praticien = 0";
|
||||
} else {
|
||||
$sql = "SELECT COALESCE(SUM(cc.c_unread),0)
|
||||
FROM conversations c
|
||||
LEFT JOIN conversation_counters cc ON cc.conversation_id = c.id
|
||||
WHERE c.client_id = ?
|
||||
AND c.deleted_for_client = 0";
|
||||
}
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$stmt->execute([$meId]);
|
||||
$total = (int)$stmt->fetchColumn();
|
||||
|
||||
json_response(['total' => $total]);
|
||||
Reference in new issue
Block a user