diff --git a/api/notifications.php b/api/notifications.php index ff22d70..b3d5dbd 100644 --- a/api/notifications.php +++ b/api/notifications.php @@ -15,8 +15,10 @@ require_once __DIR__ . '/bootstrap.php'; require_once dirname(__DIR__) . '/models/UserPreferencesModel.php'; +require_once dirname(__DIR__) . '/models/TicketModel.php'; $prefsModel = new UserPreferencesModel($conn); +$ticketModel = new TicketModel($conn); // ── POST: mark all read (update last_seen timestamp) ────────────── if ($_SERVER['REQUEST_METHOD'] === 'POST') { @@ -204,7 +206,44 @@ foreach (array_merge($assignRows, $commentRows, $statusRows, $mentionRows) as $r $all[] = $row; } usort($all, fn($a, $b) => strcmp($b['created_at'], $a['created_at'])); -$all = array_slice($all, 0, 30); + +// Re-check current ticket visibility before surfacing anything: a +// notification's audit_log entry reflects historical activity, but the +// ticket's visibility (or the user's group/watcher standing) may have +// tightened since. Without this, a notification still discloses the +// ticket's title and that activity occurred to someone who currently +// shouldn't see it, even though the ticket view's own access check would +// correctly reject them from opening it. +$candidateTicketIds = []; +foreach ($all as $row) { + $details = json_decode($row['details'] ?? '{}', true) ?? []; + $actionType = ($row['action_type'] === 'create' && $row['entity_type'] === 'comment') + ? 'comment' + : $row['action_type']; + $tid = ($actionType === 'comment' || $actionType === 'mention') + ? ($details['ticket_id'] ?? 0) + : $row['entity_id']; + if ($tid) { + $candidateTicketIds[(string)$tid] = true; + } +} +$ticketsById = !empty($candidateTicketIds) + ? $ticketModel->getTicketsByIds(array_keys($candidateTicketIds)) + : []; + +$all = array_filter($all, function ($row) use ($ticketsById, $currentUser, $ticketModel) { + $details = json_decode($row['details'] ?? '{}', true) ?? []; + $actionType = ($row['action_type'] === 'create' && $row['entity_type'] === 'comment') + ? 'comment' + : $row['action_type']; + $tid = (string)(($actionType === 'comment' || $actionType === 'mention') + ? ($details['ticket_id'] ?? 0) + : $row['entity_id']); + $ticket = $ticketsById[$tid] ?? null; + return $ticket && $ticketModel->canUserAccessTicket($ticket, $currentUser); +}); + +$all = array_slice(array_values($all), 0, 30); // Format for response $notifications = [];