diff --git a/Plugins/plugin-beispiel/migrations/001_log.mysql.sql b/Plugins/plugin-beispiel/migrations/001_log.mysql.sql new file mode 100644 index 00000000..b08f93ef --- /dev/null +++ b/Plugins/plugin-beispiel/migrations/001_log.mysql.sql @@ -0,0 +1,6 @@ +CREATE TABLE IF NOT EXISTS plugin_beispiel_log ( + id INT AUTO_INCREMENT PRIMARY KEY, + ticket_id INT NOT NULL, + event VARCHAR(40) NOT NULL, + created_at DATETIME NOT NULL +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; diff --git a/Plugins/plugin-beispiel/migrations/001_log.sqlite.sql b/Plugins/plugin-beispiel/migrations/001_log.sqlite.sql new file mode 100644 index 00000000..a3782fe8 --- /dev/null +++ b/Plugins/plugin-beispiel/migrations/001_log.sqlite.sql @@ -0,0 +1,6 @@ +CREATE TABLE IF NOT EXISTS plugin_beispiel_log ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + ticket_id INT NOT NULL, + event VARCHAR(40) NOT NULL, + created_at DATETIME NOT NULL +); diff --git a/Plugins/plugin-beispiel/pages/log.php b/Plugins/plugin-beispiel/pages/log.php new file mode 100644 index 00000000..040ae514 --- /dev/null +++ b/Plugins/plugin-beispiel/pages/log.php @@ -0,0 +1,13 @@ +query('SELECT ticket_id, event, created_at FROM plugin_beispiel_log ORDER BY id DESC LIMIT 50')->fetchAll(); +?> +

Ticket-Protokoll

+

Letzter Plugin-Cron:

+
+ + + + + +
TicketEreignisZeit
#
Noch keine Ereignisse.
diff --git a/Plugins/plugin-beispiel/plugin.json b/Plugins/plugin-beispiel/plugin.json new file mode 100644 index 00000000..c4b46e0c --- /dev/null +++ b/Plugins/plugin-beispiel/plugin.json @@ -0,0 +1,9 @@ +{ + "slug": "beispiel", + "name": "Beispiel: Ticket-Protokoll", + "version": "1.0.0", + "description": "Demonstriert Hooks, Menüpunkt, Seite, API-Endpunkt, Ticket-Seitenleiste, Einstellungen und Datenbank-Migration.", + "author": "DeskHub", + "requires": "1.0.0", + "permissions": ["hooks.tickets", "hooks.cron", "ui.menu", "ui.sidebar", "api.endpoints", "db.migrations"] +} diff --git a/Plugins/plugin-beispiel/plugin.php b/Plugins/plugin-beispiel/plugin.php new file mode 100644 index 00000000..a1700f95 --- /dev/null +++ b/Plugins/plugin-beispiel/plugin.php @@ -0,0 +1,35 @@ +prepare('INSERT INTO plugin_beispiel_log (ticket_id, event, created_at) VALUES (?,?,?)')->execute([$e['ticketId'], 'created', db_now()]); +}); +plugin_add_hook('ticket.status_changed', function (array $e) { + $pdo = get_db(); + $pdo->prepare('INSERT INTO plugin_beispiel_log (ticket_id, event, created_at) VALUES (?,?,?)')->execute([$e['ticketId'], 'status_changed', db_now()]); +}); + +// Baustein in der Ticketansicht (Plugins liefern fertiges HTML und müssen Ausgaben selbst escapen) +plugin_add_filter('ticket.view.sidebar', function (string $html, array $ctx) { + $stmt = get_db()->prepare('SELECT COUNT(*) FROM plugin_beispiel_log WHERE ticket_id = ?'); + $stmt->execute([$ctx['ticketId']]); + return $html . '
Protokoll-Plugin
' + . (int) $stmt->fetchColumn() . ' Ereignis(se) zu diesem Ticket
'; +}); + +plugin_register_menu('Ticket-Protokoll', 'log'); + +plugin_register_api('count', function (array $staff, array $request): array { + return ['count' => (int) get_db()->query('SELECT COUNT(*) FROM plugin_beispiel_log')->fetchColumn()]; +}); + +plugin_add_hook('cron.run', function () { + plugin_set_setting('beispiel', 'last_cron', db_now()); +}); + +// Aufräumen beim Entfernen mit "Daten löschen" +plugin_add_hook('plugin.uninstall.beispiel', function () { + get_db()->exec('DROP TABLE IF EXISTS plugin_beispiel_log'); +}); diff --git a/Plugins/plugins/tagesbericht-1.0.0.zip b/Plugins/plugins/tagesbericht-1.0.0.zip new file mode 100644 index 00000000..a7645a8d Binary files /dev/null and b/Plugins/plugins/tagesbericht-1.0.0.zip differ diff --git a/Plugins/plugins/tagesbericht/lib.php b/Plugins/plugins/tagesbericht/lib.php new file mode 100644 index 00000000..71a5f105 --- /dev/null +++ b/Plugins/plugins/tagesbericht/lib.php @@ -0,0 +1,106 @@ + plugin_setting(TAGESBERICHT, 'enabled', '0') === '1', + 'hour' => max(0, min(23, (int) plugin_setting(TAGESBERICHT, 'hour', '7'))), + 'workdays' => plugin_setting(TAGESBERICHT, 'workdays', '1') === '1', + // Leer = alle aktiven Mitarbeiter, sonst Liste von Mitarbeiter-IDs + 'recipients' => array_values(array_filter(array_map('intval', explode(',', (string) plugin_setting(TAGESBERICHT, 'recipients', ''))))), + 'last_sent' => plugin_setting(TAGESBERICHT, 'last_sent', ''), + ]; +} + +/** Empfänger laut Einstellung (nur aktive Mitarbeiter mit E-Mail-Adresse). */ +function tagesbericht_recipients(PDO $pdo, array $ids): array +{ + $rows = $pdo->query("SELECT * FROM staff WHERE is_active = 1 AND email <> ''")->fetchAll(); + return array_values(array_filter($rows, fn($s) => !$ids || in_array((int) $s['id'], $ids, true))); +} + +/** Zahlen und Listen für einen Mitarbeiter; berücksichtigt dessen Abteilungen. */ +function tagesbericht_data(PDO $pdo, array $staff, ?string $now = null): array +{ + $now = $now ?? db_now(); + $since = date('Y-m-d H:i:s', strtotime($now) - 86400); + [$dept, $deptParams] = dept_filter_sql(staff_department_ids($staff), 't.department_id'); + $open = "t.status NOT IN ('resolved','closed') AND t.is_spam = 0"; + + $count = function (string $where, array $params = []) use ($pdo, $dept, $deptParams): int { + $stmt = $pdo->prepare("SELECT COUNT(*) FROM tickets t WHERE {$where}{$dept}"); + $stmt->execute(array_merge($params, $deptParams)); + return (int) $stmt->fetchColumn(); + }; + $oldest = $pdo->prepare("SELECT t.ticket_number, t.subject, t.created_at, t.resolution_due_at, d.name AS department + FROM tickets t LEFT JOIN departments d ON d.id = t.department_id + WHERE {$open}{$dept} ORDER BY t.created_at ASC LIMIT 5"); + $oldest->execute($deptParams); + + return [ + 'open' => $count($open), + 'unassigned' => $count($open . ' AND t.assigned_staff_id IS NULL'), + 'mine' => $count($open . ' AND t.assigned_staff_id = ?', [(int) $staff['id']]), + 'overdue' => $count($open . ' AND t.resolution_due_at IS NOT NULL AND t.resolution_due_at < ?', [$now]), + 'created' => $count('t.is_spam = 0 AND t.created_at >= ?', [$since]), + 'resolved' => $count('t.is_spam = 0 AND t.resolved_at IS NOT NULL AND t.resolved_at >= ?', [$since]), + 'oldest' => $oldest->fetchAll(), + ]; +} + +function tagesbericht_html(array $staff, array $d): string +{ + $card = function (string $label, int $value, string $color) { + return '
' + . '
' . $value . '
' + . '
' . e($label) . '
'; + }; + $rows = ''; + foreach ($d['oldest'] as $t) { + $late = $t['resolution_due_at'] && $t['resolution_due_at'] < db_now(); + $rows .= '' . e($t['ticket_number']) . ' ' + . e(mb_strimwidth((string) $t['subject'], 0, 60, '…')) . '
' + . e((string) $t['department']) . ' · seit ' . e(date('d.m.Y', strtotime($t['created_at']))) + . ($late ? ' · überfällig' : '') . ''; + } + $inner = email_p('Guten Morgen ' . e(explode(' ', trim((string) $staff['name']))[0]) . ', hier ist deine Ticket-Übersicht:') + . '' + . $card('Offen', $d['open'], '#0f6f6a') . $card('Unzugewiesen', $d['unassigned'], '#d97706') . $card('Überfällig', $d['overdue'], '#dc2626') + . '' + . $card('Mir zugewiesen', $d['mine'], '#2563eb') . $card('Neu (24 h)', $d['created'], '#475569') . $card('Erledigt (24 h)', $d['resolved'], '#15803d') + . '
' + . ($rows !== '' ? '

Älteste offene Tickets

' . $rows . '
' : '') + . email_button('Zum Ticketsystem', base_url() . '/staff/tickets.php'); + return email_wrap($inner); +} + +/** Sendet den Bericht an alle Empfänger; liefert Anzahl erfolgreich versendeter Mails. */ +function tagesbericht_send(PDO $pdo, ?array $only = null): int +{ + $cfg = tagesbericht_config(); + $sent = 0; + foreach ($only ?? tagesbericht_recipients($pdo, $cfg['recipients']) as $staff) { + $d = tagesbericht_data($pdo, $staff); + if ($d['open'] === 0 && $d['created'] === 0 && $d['resolved'] === 0) continue; // nichts zu berichten + $subject = 'Tagesbericht: ' . $d['open'] . ' offen, ' . $d['overdue'] . ' überfällig'; + if (send_mail($staff['email'], $subject, tagesbericht_html($staff, $d))) $sent++; + } + return $sent; +} + +/** Wird alle paar Minuten aufgerufen: sendet einmal pro Tag ab der eingestellten Uhrzeit. */ +function tagesbericht_tick(PDO $pdo, ?int $time = null): int +{ + $cfg = tagesbericht_config(); + $time = $time ?? time(); + if (!$cfg['enabled']) return 0; + if ((int) date('G', $time) < $cfg['hour']) return 0; + if ($cfg['workdays'] && (int) date('N', $time) > 5) return 0; + $today = date('Y-m-d', $time); + if ($cfg['last_sent'] === $today) return 0; + plugin_set_setting(TAGESBERICHT, 'last_sent', $today); // zuerst merken, damit parallele Aufrufe nicht doppelt senden + return tagesbericht_send($pdo); +} diff --git a/Plugins/plugins/tagesbericht/pages/einstellungen.php b/Plugins/plugins/tagesbericht/pages/einstellungen.php new file mode 100644 index 00000000..b1572217 --- /dev/null +++ b/Plugins/plugins/tagesbericht/pages/einstellungen.php @@ -0,0 +1,55 @@ + 0) $message = 'Testbericht an ' . $staff['email'] . ' gesendet.'; + else $error = 'Kein Bericht gesendet: Entweder gibt es nichts zu berichten oder der Mailversand ist nicht eingerichtet.'; + } +} + +$cfg = tagesbericht_config(); +$allStaff = $pdo->query("SELECT id, name, email FROM staff WHERE is_active = 1 ORDER BY name")->fetchAll(); +?> +

Tagesbericht

+
+
+ +
+ + + + + + + +

Empfänger

+

Ohne Auswahl erhalten alle aktiven Mitarbeiter den Bericht. Jeder sieht nur Tickets seiner Abteilungen.

+ + + + +

Zuletzt gesendet:

+
+
+ +
+ + +

Schickt dir den Bericht sofort zum Ausprobieren (an ).

+ +
diff --git a/Plugins/plugins/tagesbericht/plugin.json b/Plugins/plugins/tagesbericht/plugin.json new file mode 100644 index 00000000..bc3f3044 --- /dev/null +++ b/Plugins/plugins/tagesbericht/plugin.json @@ -0,0 +1,9 @@ +{ + "slug": "tagesbericht", + "name": "Tagesbericht per E-Mail", + "version": "1.0.0", + "description": "Sendet Mitarbeitern täglich eine Zusammenfassung: offene, unzugewiesene und überfällige Tickets, Zugänge und Erledigungen der letzten 24 Stunden sowie die ältesten offenen Tickets. Jeder sieht nur seine Abteilungen.", + "author": "DeskHub", + "requires": "1.0.0", + "permissions": ["hooks.cron", "ui.menu"] +} diff --git a/Plugins/plugins/tagesbericht/plugin.php b/Plugins/plugins/tagesbericht/plugin.php new file mode 100644 index 00000000..ed5cebb3 --- /dev/null +++ b/Plugins/plugins/tagesbericht/plugin.php @@ -0,0 +1,10 @@ +