Notifications. const ALERT_TYPES = [ 'paid' => 'New paid booking', 'bank' => 'New offline payment to confirm', 'capacity' => 'Event reaches capacity threshold', 'payment' => 'Payment problem or mismatch', 'mailfail' => 'Email failed to send', ]; function notify_staff(string $type, string $title, string $body = '', string $link = ''): void { $link = safe_next($link, ''); $title = mb_substr($title, 0, 190); $body = mb_substr($body, 0, 500); if (setting_on("alert_{$type}_app")) { foreach (staff_users() as $uid) q('INSERT INTO {notifications} (user_id, title, body, link, created_at) VALUES (?,?,?,?,?)', [$uid, $title, $body, $link ?: null, now()]); } if (setting_on("alert_{$type}_email") && $type !== 'mailfail') { foreach (alert_recipients() as $to) { queue_template_email($to, '', 'staff_alert', ['alert_title' => $title, 'alert_body' => $body], url($link ?: '/admin')); } } } function alert_recipients(): array { $extra = array_filter(array_map('trim', preg_split('/[\s,;]+/', setting('alert_emails')) ?: []), fn($e) => filter_var($e, FILTER_VALIDATE_EMAIL)); if (setting('alert_recipients') === 'list') return array_values(array_unique($extra)); $staff = array_map(fn($id) => strtolower((string)get_userdata($id)->user_email), staff_users()); return array_values(array_unique(array_merge($staff, $extra))); } function unread_notifications(int $userId): int { return (int)qv('SELECT COUNT(*) FROM {notifications} WHERE user_id = ? AND read_at IS NULL', [$userId]); } /** Alert once per event when sold + held tickets pass the threshold. */ function check_capacity_alert(int $eventId): void { $ev = q1('SELECT * FROM {events} WHERE id = ?', [$eventId]); if (!$ev || $ev['capacity_alerted_at']) return; $cap = event_cap($ev) ?? (int)qv('SELECT COALESCE(SUM(capacity),0) FROM {ticket_categories} WHERE event_id = ? AND is_active = 1', [$eventId]); if ($cap <= 0) return; $sold = held_counts($eventId)['total']; $pct = (int)floor($sold * 100 / $cap); if ($pct < setting_int('capacity_threshold', 1, 100)) return; if (q('UPDATE {events} SET capacity_alerted_at = ? WHERE id = ? AND capacity_alerted_at IS NULL', [now(), $eventId])->rowCount() !== 1) return; notify_staff('capacity', $ev['title'] . " is $pct% sold", "$sold of $cap tickets sold or held.", '/admin/events/' . $eventId); }