Files
2026-11-01 11:15:37 +00:00

140 lines
5.9 KiB
PHP

<?php
/**
* Discord- und Telegram-Benachrichtigungen für Mitarbeiter.
*
* Konfiguration erfolgt unter "Einstellungen" im Mitarbeiterbereich (nicht in
* config/config.php), da es sich um Werte handelt, die Admins ohne Dateizugriff
* ändern können sollen. Gespeichert in der settings-Tabelle.
*
* Datenschutz-Hinweis: Bei aktivierten Benachrichtigungen werden Ticket-
* Ausschnitte (Betreff, Kundenname/-E-Mail, Nachrichtenanfang) an Discord
* bzw. Telegram als Drittanbieter (außerhalb der EU) übermittelt. Bitte vor
* der Aktivierung die eigenen Datenschutzanforderungen prüfen.
*
* Voraussetzung: PHP-Einstellung allow_url_fopen muss aktiviert sein
* (Standard auf den meisten Hosting-Paketen).
*/
function http_post_json(string $url, array $data, int $timeoutSeconds = 5): bool
{
$opts = ['http' => [
'method' => 'POST',
'header' => "Content-Type: application/json\r\n",
'content' => json_encode($data),
'timeout' => $timeoutSeconds,
'ignore_errors' => true,
]];
$result = @file_get_contents($url, false, stream_context_create($opts));
return $result !== false;
}
function http_post_form(string $url, array $data, int $timeoutSeconds = 5): bool
{
$opts = ['http' => [
'method' => 'POST',
'header' => "Content-Type: application/x-www-form-urlencoded\r\n",
'content' => http_build_query($data),
'timeout' => $timeoutSeconds,
'ignore_errors' => true,
]];
$result = @file_get_contents($url, false, stream_context_create($opts));
return $result !== false;
}
function discord_notify(string $message): bool
{
$url = decrypt_secret(get_setting('discord_webhook_url') ?? '');
if (!$url) {
return false;
}
return http_post_json($url, ['content' => $message]);
}
function telegram_notify(string $message): bool
{
$token = decrypt_secret(get_setting('telegram_bot_token') ?? '');
$chatId = get_setting('telegram_chat_id');
if (!$token || !$chatId) {
return false;
}
$url = "https://api.telegram.org/bot{$token}/sendMessage";
return http_post_form($url, ['chat_id' => $chatId, 'text' => $message]);
}
/** Benachrichtigt E-Mail (Abteilung) + Discord + Telegram über ein neues Ticket */
function notify_new_ticket_channels(array $ticket, ?string $departmentEmail): void
{
fcm_notify_new_ticket($ticket); // Push auf die Android-App (läuft nach dem Senden der Seite)
if ($departmentEmail) {
notify_department_new_ticket($ticket, $departmentEmail);
}
$text = "🆕 Neues Ticket #{$ticket['ticket_number']}\n"
. "Betreff: {$ticket['subject']}\n"
. "Von: {$ticket['requester_name']} ({$ticket['requester_email']})";
if (!empty($ticket['department_name'])) {
$text .= "\nAbteilung: {$ticket['department_name']}";
}
$text .= "\n" . base_url() . '/staff/ticket_view.php?n=' . urlencode($ticket['ticket_number']);
discord_notify($text);
telegram_notify($text);
}
/** Benachrichtigt Discord + Telegram über eine neue Kundenantwort (kein Mail-Duplikat nötig) */
function notify_new_reply_channels(array $ticket, string $excerpt): void
{
fcm_notify_new_reply($ticket); // Push auf die Android-App (läuft nach dem Senden der Seite)
try {
// Antwort des Kunden holt das Ticket aus der Wiedervorlage und setzt die Erinnerung zurück; Beobachter erfahren davon
$pdo = get_db();
ticket_snooze_clear($pdo, (int) $ticket['id'], null, 'Wiedervorlage beendet (Kunde hat geantwortet)');
$pdo->prepare('UPDATE tickets SET reminder_sent_at = NULL, updated_at = updated_at WHERE id = ?')->execute([$ticket['id']]);
ticket_followers_notify($ticket, 'Neue Kundenantwort');
} catch (Throwable $e) {
// Datenbank noch nicht aktualisiert: nicht schlimm
}
$short = mb_strlen($excerpt) > 200 ? mb_substr($excerpt, 0, 200) . '…' : $excerpt;
$text = "💬 Neue Kundenantwort zu Ticket #{$ticket['ticket_number']}\n"
. "Betreff: {$ticket['subject']}\n"
. "„{$short}“\n"
. base_url() . '/staff/ticket_view.php?n=' . urlencode($ticket['ticket_number']);
discord_notify($text);
telegram_notify($text);
}
/**
* Benachrichtigt die Mitglieder eines Teams per E-Mail, wenn ihm ein Ticket zugewiesen wurde.
* Entfällt bei deaktivierten Teams und wenn im Team "Benachrichtigung bei Ticketzuweisung
* deaktivieren" gesetzt ist. Der auslösende Mitarbeiter ($actorId) bekommt keine Mail.
*
* @return int Anzahl verschickter Benachrichtigungen
*/
function notify_team_assignment(PDO $pdo, array $ticket, int $teamId, ?int $actorId = null): int
{
$stmt = $pdo->prepare('SELECT name, is_active, no_alerts FROM teams WHERE id = ?');
$stmt->execute([$teamId]);
$team = $stmt->fetch();
if (!$team || !$team['is_active'] || $team['no_alerts']) return 0;
$members = $pdo->prepare('SELECT s.id FROM team_members tm JOIN staff s ON s.id = tm.staff_id WHERE tm.team_id = ? AND s.is_active = 1');
$members->execute([$teamId]);
// Mitglieder im Urlaub: stattdessen ihre Vertretung
$memberRows = [];
foreach (vacation_expand_ids($pdo, $members->fetchAll(PDO::FETCH_COLUMN)) as $mid) {
$em = staff_emails_by_ids($pdo, [$mid]);
if ($em) $memberRows[] = ['id' => $mid, 'email' => $em[0]];
}
$sent = 0;
$subject = 'Ticket #' . $ticket['ticket_number'] . ' wurde Team „' . $team['name'] . '“ zugewiesen';
$body = '<p>Das Ticket <strong>#' . e($ticket['ticket_number']) . '</strong> („' . e($ticket['subject']) . '“) wurde dem Team <strong>'
. e($team['name']) . '</strong> zugewiesen.</p>';
foreach ($memberRows as $m) {
if ($actorId !== null && (int) $m['id'] === $actorId) continue;
if (send_mail($m['email'], $subject, $body)) $sent++;
}
return $sent;
}