diff --git a/helpers/NotificationHelper.php b/helpers/NotificationHelper.php index 548c105..532d8aa 100644 --- a/helpers/NotificationHelper.php +++ b/helpers/NotificationHelper.php @@ -189,11 +189,12 @@ class NotificationHelper * @param array $extraData Merged into the payload (old_status/new_status, author, etc.) * @param int|null $excludeUserId Don't notify the actor themselves * @param string $visibility Ticket visibility: 'public', 'internal', or - * 'confidential'. notify_users includes the - * shared list, which may contain users without - * access to non-public tickets, so any comment - * body preview in $extraData is redacted for - * non-public tickets. + * 'confidential'. The shared notify list may + * contain users without access to non-public + * tickets, so for those tickets it's excluded + * entirely (only actual watchers are notified) + * and both the title and any comment/body + * preview in $extraData are redacted. */ public static function notifyWatchers(\mysqli $conn, $ticketId, string $ticketTitle, string $event, array $extraData = [], ?int $excludeUserId = null, string $visibility = 'public'): void { @@ -253,13 +254,17 @@ class NotificationHelper return; } - // Remove the global notify list duplicates and build payload - $allNotify = array_unique(array_merge($matrixIds, self::notifyUsers())); + // The shared notify list may include users without access to + // non-public tickets, so only mix it in for public tickets — for + // internal/confidential tickets, notify actual watchers only. + $allNotify = $visibility === 'public' + ? array_unique(array_merge($matrixIds, self::notifyUsers())) + : $matrixIds; $payload = array_merge($extraData, [ 'event' => $event, 'ticket_id' => $ticketId, - 'title' => $ticketTitle, + 'title' => self::redactedTitle($ticketTitle, $visibility), 'url' => UrlHelper::ticketUrl($ticketId), 'notify_users' => array_values($allNotify), ]);