Upload via GUI (360 Dateien)
This commit is contained in:
@@ -147,7 +147,7 @@ function run_check_escalations(PDO $pdo, bool $verbose = false): array
|
||||
}
|
||||
|
||||
$sql = 'SELECT t.*, d.name AS department_name, d.email AS department_email,
|
||||
p.name AS priority_name, s.name AS staff_name, s.email AS staff_email
|
||||
p.name AS priority_name, s.name AS staff_name, s.email AS staff_email, s.is_on_vacation AS staff_on_vacation
|
||||
FROM tickets t
|
||||
JOIN departments d ON d.id = t.department_id
|
||||
JOIN priorities p ON p.id = t.priority_id
|
||||
@@ -202,6 +202,11 @@ function run_check_escalations(PDO $pdo, bool $verbose = false): array
|
||||
}
|
||||
|
||||
// Zugewiesenen Mitarbeiter benachrichtigen
|
||||
if ($rule['action_notify_staff'] && !empty($ticket['staff_email'])) {
|
||||
// Bearbeiter im Urlaub: die Vertretung bekommt die Mail
|
||||
$escTo = vacation_expand_ids($pdo, [(int) $ticket['assigned_staff_id']]);
|
||||
$ticket['staff_email'] = $escTo ? (staff_emails_by_ids($pdo, [$escTo[0]])[0] ?? '') : '';
|
||||
}
|
||||
if ($rule['action_notify_staff'] && !empty($ticket['staff_email'])) {
|
||||
$subj = "Eskalation: Ticket #{$ticket['ticket_number']} – {$ticket['subject']}";
|
||||
$inner = email_p('Die Eskalationsregel <strong>' . e($rule['name']) . '</strong> wurde für folgendes Ticket ausgelöst:')
|
||||
|
||||
@@ -48,15 +48,8 @@ function run_check_sla(PDO $pdo, bool $verbose = false): array
|
||||
ticket_sla_log_overdue($pdo, (int) $t['id'], 'resolution', (string) $t['resolution_due_at']);
|
||||
log_activity($pdo, $t['id'], null, 'SLA überschritten – Ticket als überfällig markiert');
|
||||
|
||||
if ($t['department_email']) {
|
||||
$subject = "SLA überschritten: Ticket #{$t['ticket_number']}";
|
||||
$inner = email_p('Das folgende Ticket hat die SLA-Lösungsfrist überschritten und benötigt Aufmerksamkeit:')
|
||||
. email_badge('Ticket', '#' . $t['ticket_number'], '#b91c1c', '#fee2e2')
|
||||
. email_p('<strong>Betreff:</strong> ' . e($t['subject']), '24px')
|
||||
. email_button('Ticket ansehen', e(base_url() . '/staff/ticket_view.php?n=' . urlencode($t['ticket_number'])), '#dc2626');
|
||||
email_send_and_log($t['department_email'], $subject, email_wrap($inner), null, 'SLA-überschritten-Mail (Abteilung)', $t['ticket_number']);
|
||||
}
|
||||
notify_admins_overdue_ticket($t);
|
||||
// Nur die Betroffenen bekommen eine Mail (Bearbeiter bzw. Abteilung), nicht die Postfach-Adresse der Abteilung und nicht alle Admins
|
||||
notify_overdue_ticket_staff($t);
|
||||
fcm_notify_overdue($t); // Push auf die Android-App
|
||||
$newlyOverdue++;
|
||||
if ($verbose) {
|
||||
|
||||
@@ -175,6 +175,14 @@ function fetch_emails_for_account(PDO $pdo, array $account, bool $verbose): arra
|
||||
// Bei "delete" bleibt die Zuordnung zwar erhalten, die Originalmail ist danach jedoch nicht mehr verschiebbar.
|
||||
$sourceFolderAfterFetch = $afterFetch === 'archive' ? $archiveFolder : $account['folder'];
|
||||
|
||||
// Mails, die DeskHub selbst verschickt hat (zum Beispiel SLA-Meldungen an eine Postfach-Adresse), dürfen keine Tickets erzeugen
|
||||
if (mail_is_own_system_mail($pdo, (string) $fromEmail, (string) @imap_fetchheader($mbox, $emailNum))) {
|
||||
$log[] = "[$label] Mail von {$fromEmail} ignoriert (eigene Systemmail, würde sonst ein Ticket erzeugen): {$subject}";
|
||||
handle_after_fetch($mbox, $emailNum, $afterFetch, $archiveFolder);
|
||||
$processed++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Nachrichtentext: Text-Teil bevorzugt, bei reinen HTML-Mails (Newsletter) nur der sichtbare Text ohne CSS und Skripte
|
||||
$structure = @imap_fetchstructure($mbox, $emailNum);
|
||||
$body = $structure ? mail_best_text($structure, fn(string $no) => (string) imap_fetchbody($mbox, $emailNum, $no)) : '';
|
||||
|
||||
@@ -1850,6 +1850,101 @@ function run_migrations(PDO $pdo, bool $verbose = false): array
|
||||
}
|
||||
}
|
||||
|
||||
// Anmelde-Wächter: Protokoll der Anmeldungen sowie gemerkte Geräte und Orte je Mitarbeiter
|
||||
if (!isset($done['php_0065_login_guard'])) {
|
||||
try {
|
||||
$drv = $pdo->getAttribute(PDO::ATTR_DRIVER_NAME);
|
||||
$ai = $drv === 'sqlite' ? 'INTEGER PRIMARY KEY AUTOINCREMENT' : 'INT AUTO_INCREMENT PRIMARY KEY';
|
||||
$eng = $drv === 'sqlite' ? '' : ' ENGINE=InnoDB DEFAULT CHARSET=utf8mb4';
|
||||
$pdo->exec("CREATE TABLE IF NOT EXISTS login_events (
|
||||
id $ai,
|
||||
staff_id INT NULL,
|
||||
email VARCHAR(255) NOT NULL DEFAULT '',
|
||||
event VARCHAR(20) NOT NULL,
|
||||
channel VARCHAR(10) NOT NULL DEFAULT 'web',
|
||||
ip VARCHAR(64) NOT NULL DEFAULT '',
|
||||
place VARCHAR(100) NOT NULL DEFAULT '',
|
||||
device VARCHAR(160) NOT NULL DEFAULT '',
|
||||
detail VARCHAR(255) NOT NULL DEFAULT '',
|
||||
token_hash VARCHAR(64) NULL,
|
||||
token_used TINYINT NOT NULL DEFAULT 0,
|
||||
created_at DATETIME NOT NULL
|
||||
)$eng");
|
||||
$pdo->exec('CREATE INDEX idx_login_events_created ON login_events (created_at)');
|
||||
$pdo->exec('CREATE INDEX idx_login_events_staff ON login_events (staff_id, created_at)');
|
||||
$pdo->exec('CREATE INDEX idx_login_events_token ON login_events (token_hash)');
|
||||
$pdo->exec("CREATE TABLE IF NOT EXISTS login_devices (
|
||||
id $ai,
|
||||
staff_id INT NOT NULL,
|
||||
device_hash VARCHAR(64) NOT NULL,
|
||||
label VARCHAR(160) NOT NULL DEFAULT '',
|
||||
channel VARCHAR(10) NOT NULL DEFAULT 'web',
|
||||
first_ip VARCHAR(64) NOT NULL DEFAULT '',
|
||||
last_ip VARCHAR(64) NOT NULL DEFAULT '',
|
||||
place VARCHAR(100) NOT NULL DEFAULT '',
|
||||
first_seen DATETIME NOT NULL,
|
||||
last_seen DATETIME NOT NULL,
|
||||
UNIQUE (staff_id, device_hash)
|
||||
)$eng");
|
||||
$pdo->exec("CREATE TABLE IF NOT EXISTS login_places (
|
||||
id $ai,
|
||||
staff_id INT NOT NULL,
|
||||
place_key VARCHAR(80) NOT NULL,
|
||||
label VARCHAR(100) NOT NULL DEFAULT '',
|
||||
first_seen DATETIME NOT NULL,
|
||||
last_seen DATETIME NOT NULL,
|
||||
UNIQUE (staff_id, place_key)
|
||||
)$eng");
|
||||
$pdo->prepare('INSERT INTO schema_migrations (migration) VALUES (?)')->execute(['php_0065_login_guard']);
|
||||
$result['applied']++;
|
||||
if ($verbose) $result['log'][] = ' [OK] php_0065_login_guard';
|
||||
} catch (Throwable $e) {
|
||||
$result['errors'][] = 'php_0065_login_guard: ' . $e->getMessage();
|
||||
if ($verbose) $result['log'][] = ' [FAIL] php_0065_login_guard - ' . $e->getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
// Abwesenheit im Profil: Welche Tickets an die Vertretung weitergegeben wurden (für die Rückgabe nach dem Urlaub)
|
||||
if (!isset($done['php_0066_vacation_moves'])) {
|
||||
try {
|
||||
$drv = $pdo->getAttribute(PDO::ATTR_DRIVER_NAME);
|
||||
$ai = $drv === 'sqlite' ? 'INTEGER PRIMARY KEY AUTOINCREMENT' : 'INT AUTO_INCREMENT PRIMARY KEY';
|
||||
$eng = $drv === 'sqlite' ? '' : ' ENGINE=InnoDB DEFAULT CHARSET=utf8mb4';
|
||||
$pdo->exec("CREATE TABLE IF NOT EXISTS vacation_moves (
|
||||
id $ai,
|
||||
ticket_id INT NOT NULL,
|
||||
from_staff_id INT NOT NULL,
|
||||
to_staff_id INT NOT NULL,
|
||||
created_at DATETIME NOT NULL,
|
||||
returned_at DATETIME NULL
|
||||
)$eng");
|
||||
$pdo->exec('CREATE INDEX idx_vacation_moves_from ON vacation_moves (from_staff_id, returned_at)');
|
||||
$pdo->prepare('INSERT INTO schema_migrations (migration) VALUES (?)')->execute(['php_0066_vacation_moves']);
|
||||
$result['applied']++;
|
||||
if ($verbose) $result['log'][] = ' [OK] php_0066_vacation_moves';
|
||||
} catch (Throwable $e) {
|
||||
$result['errors'][] = 'php_0066_vacation_moves: ' . $e->getMessage();
|
||||
if ($verbose) $result['log'][] = ' [FAIL] php_0066_vacation_moves - ' . $e->getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
// Reparatur: Entwarnungs-Notizen der öffentlichen API waren doppelt maskiert (" statt ")
|
||||
if (!isset($done['php_0067_fix_api_resolve_notes'])) {
|
||||
try {
|
||||
$rows = $pdo->query("SELECT t.id, t.message FROM ticket_threads t JOIN tickets k ON k.id = t.ticket_id
|
||||
WHERE k.source = 'api' AND t.author_type = 'system' AND t.is_internal_note = 1
|
||||
AND (t.message LIKE '%"%' OR t.message LIKE '%&%' OR t.message LIKE '%'%')")->fetchAll();
|
||||
$upd = $pdo->prepare('UPDATE ticket_threads SET message = ? WHERE id = ?');
|
||||
foreach ($rows as $r) $upd->execute([html_entity_decode((string) $r['message'], ENT_QUOTES | ENT_HTML5, 'UTF-8'), $r['id']]);
|
||||
$pdo->prepare('INSERT INTO schema_migrations (migration) VALUES (?)')->execute(['php_0067_fix_api_resolve_notes']);
|
||||
$result['applied']++;
|
||||
if ($verbose) $result['log'][] = ' [OK] php_0067_fix_api_resolve_notes (' . count($rows) . ' Notizen repariert)';
|
||||
} catch (Throwable $e) {
|
||||
$result['errors'][] = 'php_0067_fix_api_resolve_notes: ' . $e->getMessage();
|
||||
if ($verbose) $result['log'][] = ' [FAIL] php_0067_fix_api_resolve_notes - ' . $e->getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user