Upload via GUI (10 Dateien)
This commit is contained in:
@@ -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;
|
||||||
@@ -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
|
||||||
|
);
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
<?php
|
||||||
|
// Seite des Beispiel-Plugins. Verfügbar: $staff, $pdo.
|
||||||
|
$rows = $pdo->query('SELECT ticket_id, event, created_at FROM plugin_beispiel_log ORDER BY id DESC LIMIT 50')->fetchAll();
|
||||||
|
?>
|
||||||
|
<h1>Ticket-Protokoll</h1>
|
||||||
|
<p class="muted">Letzter Plugin-Cron: <?= e(plugin_setting('beispiel', 'last_cron', 'noch nie')) ?></p>
|
||||||
|
<div class="card"><table>
|
||||||
|
<thead><tr><th>Ticket</th><th>Ereignis</th><th>Zeit</th></tr></thead>
|
||||||
|
<tbody>
|
||||||
|
<?php foreach ($rows as $r): ?><tr><td>#<?= (int) $r['ticket_id'] ?></td><td><?= e($r['event']) ?></td><td><?= e($r['created_at']) ?></td></tr><?php endforeach; ?>
|
||||||
|
<?php if (!$rows): ?><tr><td colspan="3" class="muted">Noch keine Ereignisse.</td></tr><?php endif; ?>
|
||||||
|
</tbody>
|
||||||
|
</table></div>
|
||||||
@@ -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"]
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
<?php
|
||||||
|
// Beispiel-Plugin: protokolliert Ticket-Ereignisse in einer eigenen Tabelle.
|
||||||
|
// Wird beim Aktivieren geladen; hier werden nur Erweiterungen registriert.
|
||||||
|
|
||||||
|
plugin_add_hook('ticket.created', function (array $e) {
|
||||||
|
$pdo = get_db();
|
||||||
|
$pdo->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 . '<div class="card" style="margin-top:16px;padding:14px 16px"><strong>Protokoll-Plugin</strong><br>'
|
||||||
|
. (int) $stmt->fetchColumn() . ' Ereignis(se) zu diesem Ticket</div>';
|
||||||
|
});
|
||||||
|
|
||||||
|
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');
|
||||||
|
});
|
||||||
Binary file not shown.
@@ -0,0 +1,106 @@
|
|||||||
|
<?php
|
||||||
|
/** Logik des Tagesbericht-Plugins (ohne Seiten-/Hook-Code, damit sie einzeln testbar ist). */
|
||||||
|
|
||||||
|
const TAGESBERICHT = 'tagesbericht';
|
||||||
|
|
||||||
|
function tagesbericht_config(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'enabled' => 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 '<td style="padding:6px"><div style="background:#f5f7f9;border-radius:8px;padding:12px;text-align:center">'
|
||||||
|
. '<div style="font-size:24px;font-weight:700;color:' . $color . '">' . $value . '</div>'
|
||||||
|
. '<div style="font-size:12px;color:#64748b">' . e($label) . '</div></div></td>';
|
||||||
|
};
|
||||||
|
$rows = '';
|
||||||
|
foreach ($d['oldest'] as $t) {
|
||||||
|
$late = $t['resolution_due_at'] && $t['resolution_due_at'] < db_now();
|
||||||
|
$rows .= '<tr><td style="padding:6px 0;border-top:1px solid #e2e8f0;font-size:13px"><strong>' . e($t['ticket_number']) . '</strong> '
|
||||||
|
. e(mb_strimwidth((string) $t['subject'], 0, 60, '…')) . '<br><span style="color:#64748b;font-size:12px">'
|
||||||
|
. e((string) $t['department']) . ' · seit ' . e(date('d.m.Y', strtotime($t['created_at'])))
|
||||||
|
. ($late ? ' · <span style="color:#dc2626">überfällig</span>' : '') . '</span></td></tr>';
|
||||||
|
}
|
||||||
|
$inner = email_p('Guten Morgen ' . e(explode(' ', trim((string) $staff['name']))[0]) . ', hier ist deine Ticket-Übersicht:')
|
||||||
|
. '<table role="presentation" width="100%" cellpadding="0" cellspacing="0"><tr>'
|
||||||
|
. $card('Offen', $d['open'], '#0f6f6a') . $card('Unzugewiesen', $d['unassigned'], '#d97706') . $card('Überfällig', $d['overdue'], '#dc2626')
|
||||||
|
. '</tr><tr>'
|
||||||
|
. $card('Mir zugewiesen', $d['mine'], '#2563eb') . $card('Neu (24 h)', $d['created'], '#475569') . $card('Erledigt (24 h)', $d['resolved'], '#15803d')
|
||||||
|
. '</tr></table>'
|
||||||
|
. ($rows !== '' ? '<p style="margin:20px 0 6px;font-weight:700;font-size:14px">Älteste offene Tickets</p><table role="presentation" width="100%" cellpadding="0" cellspacing="0">' . $rows . '</table>' : '')
|
||||||
|
. 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);
|
||||||
|
}
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
<?php
|
||||||
|
// Einstellungsseite des Tagesbericht-Plugins. Verfügbar: $staff, $pdo.
|
||||||
|
$message = null;
|
||||||
|
$error = null;
|
||||||
|
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||||
|
verify_csrf();
|
||||||
|
$action = $_POST['action'] ?? '';
|
||||||
|
if ($action === 'save') {
|
||||||
|
plugin_set_setting(TAGESBERICHT, 'enabled', !empty($_POST['enabled']) ? '1' : '0');
|
||||||
|
plugin_set_setting(TAGESBERICHT, 'hour', (string) max(0, min(23, (int) ($_POST['hour'] ?? 7))));
|
||||||
|
plugin_set_setting(TAGESBERICHT, 'workdays', !empty($_POST['workdays']) ? '1' : '0');
|
||||||
|
$ids = array_map('intval', (array) ($_POST['recipients'] ?? []));
|
||||||
|
plugin_set_setting(TAGESBERICHT, 'recipients', implode(',', array_filter($ids)));
|
||||||
|
$message = 'Einstellungen gespeichert.';
|
||||||
|
} elseif ($action === 'test') {
|
||||||
|
$n = tagesbericht_send($pdo, [$staff]);
|
||||||
|
if ($n > 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();
|
||||||
|
?>
|
||||||
|
<h1>Tagesbericht</h1>
|
||||||
|
<?php if ($message): ?><div class="flash success"><?= e($message) ?></div><?php endif; ?>
|
||||||
|
<?php if ($error): ?><div class="flash error"><?= e($error) ?></div><?php endif; ?>
|
||||||
|
|
||||||
|
<form method="post" class="card" style="max-width:640px">
|
||||||
|
<?= csrf_field() ?>
|
||||||
|
<input type="hidden" name="action" value="save">
|
||||||
|
<label><input type="checkbox" name="enabled" value="1" <?= $cfg['enabled'] ? 'checked' : '' ?>> Täglichen Bericht per E-Mail senden</label>
|
||||||
|
<label for="hour">Ab Uhrzeit (Serverzeit)</label>
|
||||||
|
<select id="hour" name="hour" style="max-width:140px">
|
||||||
|
<?php for ($h = 0; $h < 24; $h++): ?><option value="<?= $h ?>" <?= $cfg['hour'] === $h ? 'selected' : '' ?>><?= sprintf('%02d:00', $h) ?></option><?php endfor; ?>
|
||||||
|
</select>
|
||||||
|
<label style="margin-top:10px"><input type="checkbox" name="workdays" value="1" <?= $cfg['workdays'] ? 'checked' : '' ?>> Nur an Werktagen (Mo–Fr)</label>
|
||||||
|
|
||||||
|
<h3>Empfänger</h3>
|
||||||
|
<p class="muted" style="font-size:.85rem">Ohne Auswahl erhalten alle aktiven Mitarbeiter den Bericht. Jeder sieht nur Tickets seiner Abteilungen.</p>
|
||||||
|
<?php foreach ($allStaff as $s): ?>
|
||||||
|
<label style="display:block"><input type="checkbox" name="recipients[]" value="<?= (int) $s['id'] ?>" <?= in_array((int) $s['id'], $cfg['recipients'], true) ? 'checked' : '' ?>> <?= e($s['name']) ?> <span class="muted">(<?= e($s['email']) ?>)</span></label>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
|
||||||
|
<p class="muted" style="font-size:.82rem;margin-top:14px">Zuletzt gesendet: <?= e($cfg['last_sent'] !== '' ? $cfg['last_sent'] : 'noch nie') ?></p>
|
||||||
|
<div class="btn-row"><button type="submit" class="btn">Speichern</button></div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<form method="post" class="card" style="max-width:640px;margin-top:16px">
|
||||||
|
<?= csrf_field() ?>
|
||||||
|
<input type="hidden" name="action" value="test">
|
||||||
|
<p style="margin-top:0">Schickt dir den Bericht sofort zum Ausprobieren (an <?= e($staff['email']) ?>).</p>
|
||||||
|
<button type="submit" class="btn secondary">Testbericht an mich senden</button>
|
||||||
|
</form>
|
||||||
@@ -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"]
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
<?php
|
||||||
|
// Tagesbericht-Plugin: sendet Mitarbeitern täglich eine Ticket-Zusammenfassung per E-Mail.
|
||||||
|
require_once __DIR__ . '/lib.php';
|
||||||
|
|
||||||
|
plugin_add_hook('cron.run', function () {
|
||||||
|
tagesbericht_tick(get_db());
|
||||||
|
});
|
||||||
|
|
||||||
|
// Einstellungen: nur für Admins sichtbar (siehe pages/einstellungen.php)
|
||||||
|
plugin_register_menu('Tagesbericht', 'einstellungen', 'manage_email_addresses');
|
||||||
Reference in New Issue
Block a user