Files
deskhub/DeskHub-Webseite/ticket_new.php
T
2026-10-24 20:14:46 +00:00

562 lines
31 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
require_once __DIR__ . '/includes/bootstrap.php';
$user = current_user();
$pdo = get_db();
$departments = departments_for_select($pdo);
$priorities = $pdo->query('SELECT * FROM priorities ORDER BY sort_order')->fetchAll();
$helpTopics = help_topic_options($pdo, true);
$useHelpTopics = count($helpTopics) > 0;
// Ohne Hilfe-Themen gibt es keine Themenauswahl auf dem Formular, daher nur themenunabhängige Felder anzeigen.
$customFields = $useHelpTopics
? $pdo->query('SELECT * FROM custom_fields WHERE is_active = 1 AND department_id IS NULL ORDER BY sort_order, label')->fetchAll()
: $pdo->query('SELECT * FROM custom_fields WHERE is_active = 1 AND department_id IS NULL AND help_topic_id IS NULL ORDER BY sort_order, label')->fetchAll();
// Abteilungen mit eigenem Formular: Texte und Felder (erscheinen nur, wenn der Kunde diese Abteilung wählt)
$deptForms = [];
foreach ($departments as $d) {
$df = department_form($pdo, (int) $d['id']);
if ($df) {
$deptForms[(int) $d['id']] = $df;
}
}
$topicDept = [];
foreach ($helpTopics as $t) $topicDept[(int) $t['id']] = (int) $t['department_id'];
$errors = [];
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
verify_csrf();
// Honeypot: für Menschen unsichtbares Feld, das leer bleiben muss. Bots füllen
// häufig alle Formularfelder aus, daher dient dies als einfache Spam-Bremse.
if (!empty($_POST['website'])) {
flash('error', 'Es ist ein Fehler aufgetreten. Bitte versuchen Sie es erneut.');
redirect('/ticket_new.php');
}
if (!$user && !rate_limit_check('ticket_create', client_ip(), 8, 60)) {
$errors[] = 'Sie haben in kurzer Zeit zu viele Tickets erstellt. Bitte versuchen Sie es später erneut.';
}
$name = trim($_POST['name'] ?? ($user['name'] ?? ''));
$email = trim($_POST['email'] ?? ($user['email'] ?? ''));
$subject = trim($_POST['subject'] ?? '');
$message = trim($_POST['message'] ?? '');
// Gesperrte Absender: Ticket wird wie beim Honeypot kommentarlos abgewiesen,
// damit nicht erkennbar ist, dass die Adresse gezielt blockiert wurde.
if ($email !== '' && is_banned($email)) {
flash('error', 'Es ist ein Fehler aufgetreten. Bitte versuchen Sie es erneut.');
redirect('/ticket_new.php');
}
$helpTopicId = null;
$deptId = null;
$prioId = null;
if ($useHelpTopics) {
$helpTopicId = (int) ($_POST['help_topic_id'] ?? 0);
$topic = null;
foreach ($helpTopics as $t) {
if ($t['id'] == $helpTopicId) { $topic = $t; break; }
}
if (!$topic) {
$errors[] = 'Bitte wählen Sie ein Anliegen aus.';
} else {
$deptId = (int) $topic['department_id'];
$prioId = (int) $topic['priority_id'];
}
} else {
$deptId = (int) ($_POST['department_id'] ?? 0);
$prioId = (int) ($_POST['priority_id'] ?? 0);
if (!in_array($deptId, array_column($departments, 'id'))) $errors[] = 'Bitte wählen Sie eine Abteilung.';
if (!in_array($prioId, array_column($priorities, 'id'))) $errors[] = 'Bitte wählen Sie eine Priorität.';
}
// Eigenes Formular der gewählten Abteilung: ersetzt Betreff-/Feld-/Anhang-Vorgaben des Standardformulars
$ownForm = $deptId ? ($deptForms[(int) $deptId] ?? null) : null;
if ($ownForm && !$ownForm['subject_show']) $subject = 'Anfrage ' . $ownForm['department_name'];
if ($ownForm && !$ownForm['message_show']) {
// Ohne Beschreibungsfeld besteht der Ticket-Text aus den Angaben der eigenen Felder
$lines = [];
foreach ($ownForm['fields'] as $ff) {
$raw = $ff['field_type'] === 'checkbox' ? (!empty($_POST['custom_' . $ff['id']]) ? 'Ja' : 'Nein') : trim((string) ($_POST['custom_' . $ff['id']] ?? ''));
if ($raw !== '') $lines[] = $ff['label'] . ': ' . $raw;
}
$message = $lines ? implode("\n", $lines) : 'Anfrage ohne weitere Angaben';
}
if ($name === '') $errors[] = 'Bitte geben Sie Ihren Namen an.';
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) $errors[] = 'Bitte geben Sie eine gültige E-Mail-Adresse an.';
if ($subject === '') $errors[] = 'Bitte geben Sie einen Betreff an.';
if ($message === '') $errors[] = 'Bitte beschreiben Sie Ihr Anliegen.';
// Individuelle Felder validieren (nur Felder, die zum gewählten Hilfe-Thema gehören oder themenunabhängig sind)
$customValues = []; // field id => submitted value
foreach ($ownForm ? $ownForm['fields'] : $customFields as $f) {
if (!$ownForm && ($f['department_id'] !== null || ($f['help_topic_id'] !== null && (int) $f['help_topic_id'] !== (int) $helpTopicId))) continue;
$fieldName = 'custom_' . $f['id'];
$val = $f['field_type'] === 'checkbox' ? (!empty($_POST[$fieldName]) ? '1' : '0') : trim($_POST[$fieldName] ?? '');
if ($f['is_required'] && $f['field_type'] !== 'checkbox' && $val === '') {
$errors[] = 'Bitte füllen Sie das Feld „' . $f['label'] . '“ aus.';
}
if ($f['is_required'] && $f['field_type'] === 'checkbox' && $val !== '1') {
$errors[] = 'Bitte bestätigen Sie „' . $f['label'] . '“.';
}
if (in_array($f['field_type'], ['select', 'radio'], true) && $val !== '' && !in_array($val, array_map('trim', preg_split('/\r?\n/', (string) $f['options'])), true)) {
$errors[] = 'Bitte wählen Sie bei „' . $f['label'] . '“ eine der angebotenen Möglichkeiten.';
$val = '';
}
$customValues[$f['id']] = $val;
}
// Datei-Upload (optional, ein Anhang)
$uploadInfo = null;
if (!$errors && (!$ownForm || $ownForm['attachment_show']) && !empty($_FILES['attachment']['name'])) {
$uploadInfo = handle_ticket_upload($_FILES['attachment']);
if (isset($uploadInfo['error'])) {
$errors[] = $uploadInfo['error'];
}
}
if (!$errors) {
// Ticket-Regeln auswerten: koennen Abteilung/Prioritaet/Hilfe-Thema vor dem Anlegen
// ueberschreiben, das Ticket abweisen, oder nach dem Anlegen einen Mitarbeiter zuweisen/Tags setzen.
$ruleResult = evaluate_ticket_rules($pdo, [
'subject' => $subject,
'message' => $message,
'requester_email' => $email,
'requester_name' => $name,
'source' => 'web',
]);
if ($ruleResult['reject']) {
flash('error', 'Es ist ein Fehler aufgetreten. Bitte versuchen Sie es erneut.');
redirect('/ticket_new.php');
}
if ($ruleResult['set_department'] !== null) $deptId = $ruleResult['set_department'];
if ($ruleResult['set_priority'] !== null) $prioId = $ruleResult['set_priority'];
if ($ruleResult['set_help_topic'] !== null) $helpTopicId = $ruleResult['set_help_topic'];
$priority = null;
foreach ($priorities as $p) {
if ($p['id'] == $prioId) { $priority = $p; break; }
}
$sla = calc_sla_dates($priority, null, (int) $deptId, !empty($helpTopicId) ? (int) $helpTopicId : null);
$stmt = $pdo->prepare(
'INSERT INTO tickets (ticket_number, subject, user_id, requester_name, requester_email, department_id,
help_topic_id, priority_id, status, source, requester_ip, first_response_due_at, resolution_due_at, created_at, updated_at)
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)'
);
$ticketNumber = generate_ticket_number($pdo);
$now = db_now();
// IP kürzen wenn Anonymisierung aktiv
$clientIp = client_ip();
if (get_setting('ip_anonymize', '0') === '1') {
$clientIp = preg_replace('/\.(\d+)$/', '.0', $clientIp);
$clientIp = preg_replace('/:[0-9a-f]{1,4}$/', ':0', $clientIp);
}
$stmt->execute([
$ticketNumber, $subject, $user['id'] ?? null, $name, $email, $deptId,
$helpTopicId, $prioId, 'open', 'web', $clientIp, $sla['first_response_due_at'], $sla['resolution_due_at'], $now, $now,
]);
$ticketId = (int) $pdo->lastInsertId();
$stmt = $pdo->prepare(
'INSERT INTO ticket_threads (ticket_id, author_type, user_id, message, is_internal_note, created_at)
VALUES (?,?,?,?,0,?)'
);
$stmt->execute([$ticketId, 'customer', $user['id'] ?? null, $message, $now]);
$threadId = (int) $pdo->lastInsertId();
if ($uploadInfo) {
$stmt = $pdo->prepare(
'INSERT INTO ticket_attachments (thread_id, original_name, stored_name, filesize, created_at) VALUES (?,?,?,?,?)'
);
$stmt->execute([$threadId, $uploadInfo['original_name'], $uploadInfo['stored_name'], $uploadInfo['filesize'], $now]);
}
if ($customValues) {
$cfStmt = $pdo->prepare('INSERT INTO ticket_field_values (ticket_id, custom_field_id, value) VALUES (?,?,?)');
foreach ($customValues as $fieldId => $val) {
if ($val === '' ) continue;
$cfStmt->execute([$ticketId, $fieldId, $val]);
}
}
log_activity($pdo, $ticketId, null, 'Ticket erstellt');
// Verbleibende Regel-Aktionen, die eine vorhandene Ticket-ID benötigen
$assignTo = $ruleResult['assign_staff'];
// Einstellung der Abteilung ("Neue Tickets automatisch zuweisen"): Ticket-Regel hat Vorrang, dann die Abteilung, dann der letzte Bearbeiter
$deptAssignee = $assignTo === null ? department_auto_assignee($pdo, (int) $deptId) : null;
if ($deptAssignee !== null) {
$assignTo = $deptAssignee;
log_activity($pdo, $ticketId, null, 'Automatisch zugewiesen (Einstellung der Abteilung)');
} elseif ($assignTo === null) {
// Auto-Zuweisung: war der Kunde schon mal einem Mitarbeiter zugewiesen?
$prevStmt = $pdo->prepare(
"SELECT assigned_staff_id FROM tickets
WHERE LOWER(requester_email) = LOWER(?)
AND assigned_staff_id IS NOT NULL
AND status NOT IN ('closed','resolved')
AND id != ?
ORDER BY updated_at DESC LIMIT 1"
);
$prevStmt->execute([$email, $ticketId]);
$prevId = $prevStmt->fetchColumn();
if ($prevId) {
$assignTo = (int) $prevId;
log_activity($pdo, $ticketId, null, 'Automatisch zugewiesen (letzter Bearbeiter)');
}
} else {
log_activity($pdo, $ticketId, null, 'Automatisch zugewiesen (Ticket-Regel)');
}
if ($assignTo !== null) {
$pdo->prepare('UPDATE tickets SET assigned_staff_id = ? WHERE id = ?')->execute([$assignTo, $ticketId]);
}
foreach ($ruleResult['add_tags'] as $tagName) {
apply_tag_to_ticket($pdo, $ticketId, $tagName);
}
if ($ruleResult['matched_rules']) {
log_activity($pdo, $ticketId, null, 'Ticket-Regel angewendet: ' . implode(', ', $ruleResult['matched_rules']));
}
apply_auto_reply($pdo, $ticketId, $ruleResult['auto_reply']);
$closedByRule = apply_rule_close($pdo, $ticketId, $ruleResult);
$deptStmt = $pdo->prepare('SELECT name, email FROM departments WHERE id = ?');
$deptStmt->execute([$deptId]);
$dept = $deptStmt->fetch();
$ticketForNotify = [
'ticket_number' => $ticketNumber,
'subject' => $subject,
'requester_name' => $name,
'requester_email' => $email,
'department_id' => $deptId,
'department_name' => $dept['name'] ?? null,
];
if (!$closedByRule) {
notify_new_ticket_channels($ticketForNotify, $dept['email'] ?? null);
notify_staff_new_ticket($ticketForNotify);
webhook_dispatch($pdo, 'ticket.created', $ticketId);
if (file_exists(__DIR__ . '/includes/push.php')) {
require_once __DIR__ . '/includes/push.php';
push_new_ticket($ticketForNotify);
}
notify_customer_ticket_created($ticketForNotify);
}
$_SESSION['guest_ticket_access'][$ticketNumber] = true;
flash('success', "Ihr Ticket #{$ticketNumber} wurde erstellt. Wir melden uns so schnell wie möglich.");
redirect('/ticket_view.php?n=' . urlencode($ticketNumber));
}
}
// Ticket-Vorlagen: ?template=ID füllt das Formular vor (nur Vorlagen, die für Kunden sichtbar sind)
$ticketTemplates = [];
$activeTemplateId = 0;
try {
$ticketTemplates = ticket_templates_list(get_db(), true);
if ($_SERVER['REQUEST_METHOD'] !== 'POST' && (int) ($_GET['template'] ?? 0) > 0) {
$tpl = ticket_template_get(get_db(), (int) $_GET['template'], true);
if ($tpl) {
$activeTemplateId = (int) $tpl['id'];
$_POST['subject'] = (string) $tpl['subject'];
$_POST['message'] = (string) ($tpl['body'] ?? '');
if ($tpl['department_id']) $_POST['department_id'] = (string) $tpl['department_id'];
if ($tpl['help_topic_id']) $_POST['help_topic_id'] = (string) $tpl['help_topic_id'];
if ($tpl['priority_id']) $_POST['priority_id'] = (string) $tpl['priority_id'];
}
}
} catch (Throwable $e) {
$ticketTemplates = []; // Datenbank noch nicht aktualisiert: Formular funktioniert wie bisher
}
$pageTitle = 'Neues Ticket';
$activePage = 'new';
require __DIR__ . '/includes/header_customer.php';
?>
<div class="ticket-create-page">
<div class="topbar ticket-create-header">
<div>
<h1>Neues Ticket erstellen</h1>
<p class="muted" style="margin:4px 0 0">Beschreiben Sie Ihr Anliegen so genau wie möglich – das hilft uns, schneller zu helfen.</p>
</div>
<a href="/dashboard.php" class="btn secondary" style="white-space:nowrap">Meine Tickets</a>
</div>
<?php if ($ticketTemplates): ?>
<div class="ticket-templates" style="margin:0 0 18px">
<div class="muted" style="font-size:.82rem;margin-bottom:6px">Womit können wir helfen? Eine Vorlage füllt das Formular für Sie vor:</div>
<div style="display:flex;gap:8px;flex-wrap:wrap">
<?php foreach ($ticketTemplates as $__tp): ?>
<a href="/ticket_new.php?template=<?= (int) $__tp['id'] ?>" class="btn <?= $activeTemplateId === (int) $__tp['id'] ? '' : 'secondary' ?>" style="font-size:.85rem" <?= $__tp['description'] ? 'title="' . e((string) $__tp['description']) . '"' : '' ?>><?= e($__tp['name']) ?></a>
<?php endforeach; ?>
<?php if ($activeTemplateId): ?><a href="/ticket_new.php" class="muted" style="align-self:center;font-size:.82rem">Ohne Vorlage</a><?php endif; ?>
</div>
</div>
<?php endif; ?>
<?php foreach ($errors as $err): ?>
<div class="flash error"><?= e($err) ?></div>
<?php endforeach; ?>
<?php $renderField = function (array $f): void {
$fieldName = 'custom_' . $f['id'];
$posted = $_POST[$fieldName] ?? '';
?>
<?php $__hint = ($f['field_type'] !== 'checkbox' && !empty($f['help_text'])) ? form_help_html($f['help_text'], (string) $f['label']) : ''; ?>
<div class="custom-field-group fw-<?= e($f['form_width'] ?? '') ?: 'full' ?>" data-topic="<?= $f['help_topic_id'] !== null ? (int) $f['help_topic_id'] : '' ?>" data-dept="<?= $f['department_id'] !== null ? (int) $f['department_id'] : '' ?>">
<label for="<?= $fieldName ?>"><?= e($f['label']) ?><?= $f['is_required'] ? ' <span style="color:var(--danger)">*</span>' : '' ?></label>
<?php if ($f['field_type'] === 'textarea'): ?>
<textarea id="<?= $fieldName ?>" name="<?= $fieldName ?>" <?= $f['is_required'] ? 'required' : '' ?>><?= e($posted) ?></textarea>
<?php elseif ($f['field_type'] === 'select'): ?>
<select id="<?= $fieldName ?>" name="<?= $fieldName ?>" <?= $f['is_required'] ? 'required' : '' ?>>
<option value="">– bitte wählen –</option>
<?php foreach (preg_split('/\r?\n/', trim((string) $f['options'])) as $opt): $opt = trim($opt); if ($opt === '') continue; ?>
<option value="<?= e($opt) ?>" <?= $posted === $opt ? 'selected' : '' ?>><?= e($opt) ?></option>
<?php endforeach; ?>
</select>
<?php elseif ($f['field_type'] === 'radio'): ?>
<div style="margin-top:4px<?= !empty($f['radio_inline']) ? ';display:flex;flex-wrap:wrap;gap:4px 20px' : '' ?>">
<?php foreach (preg_split('/\r?\n/', trim((string) $f['options'])) as $ri => $opt): $opt = trim($opt); if ($opt === '') continue; ?>
<label style="display:flex;align-items:center;gap:8px;font-weight:400;margin:4px 0">
<input type="radio" id="<?= $fieldName ?>_<?= (int) $ri ?>" name="<?= $fieldName ?>" value="<?= e($opt) ?>" style="width:auto" <?= $f['is_required'] ? 'required' : '' ?> <?= $posted === $opt ? 'checked' : '' ?>>
<span><?= e($opt) ?></span>
</label>
<?php endforeach; ?>
</div>
<?php elseif ($f['field_type'] === 'checkbox'): ?>
<label style="display:flex;align-items:flex-start;gap:8px;font-weight:400;margin-top:4px">
<input type="checkbox" id="<?= $fieldName ?>" name="<?= $fieldName ?>" value="1" style="width:auto;margin-top:3px" <?= $f['is_required'] ? 'required' : '' ?> <?= !empty($posted) ? 'checked' : '' ?>>
<span><?= !empty($f['help_text']) ? form_help_html($f['help_text'], (string) $f['label']) : e($f['label']) ?></span>
</label>
<?php elseif ($f['field_type'] === 'number'): ?>
<input type="number" id="<?= $fieldName ?>" name="<?= $fieldName ?>" value="<?= e($posted) ?>" <?= $f['is_required'] ? 'required' : '' ?>>
<?php elseif ($f['field_type'] === 'date'): ?>
<input type="date" id="<?= $fieldName ?>" name="<?= $fieldName ?>" value="<?= e($posted) ?>" <?= $f['is_required'] ? 'required' : '' ?>>
<?php else: ?>
<input type="text" id="<?= $fieldName ?>" name="<?= $fieldName ?>" value="<?= e($posted) ?>" <?= $f['is_required'] ? 'required' : '' ?>>
<?php endif; ?>
<?php if ($__hint !== ''): ?><p class="muted" style="font-size:.8rem;margin:4px 0 0"><?= $__hint ?></p><?php endif; ?>
</div>
<?php }; ?>
<?php $renderSlot = function (string $section, bool $wrap = true) use ($deptForms, $renderField): void {
$list = [];
foreach ($deptForms as $df) foreach ($df['fields'] as $ff) if ($ff['_section'] === $section) $list[] = $ff;
if (!$list) return;
if ($wrap) echo '<div class="dept-fields">';
foreach ($list as $ff) $renderField($ff);
if ($wrap) echo '</div>';
}; ?>
<style>
.dept-fields{display:flex;flex-wrap:wrap;gap:14px 16px;margin-bottom:14px}
.dept-fields>*{flex:1 1 100%;min-width:0}
.dept-fields>.fw-half{flex:0 0 calc(50% - 8px)}
.dept-fields>.fw-third{flex:0 0 calc(33.333% - 11px)}
.dept-fields>.fw-twothirds{flex:0 0 calc(66.666% - 6px)}
.dept-fields input[type=text],.dept-fields input[type=email],.dept-fields input[type=number],.dept-fields input[type=date],.dept-fields select,.dept-fields textarea{width:100%;box-sizing:border-box;margin-bottom:0}
@media (max-width:640px){.dept-fields>.fw-half,.dept-fields>.fw-third,.dept-fields>.fw-twothirds{flex:1 1 100%}}
</style>
<div class="card ticket-create-card">
<form method="post" enctype="multipart/form-data" class="ticket-create-form">
<?= csrf_field() ?>
<!-- Honeypot -->
<div style="position:absolute;left:-9999px;top:-9999px" aria-hidden="true">
<input type="text" name="website" tabindex="-1" autocomplete="off">
</div>
<!-- Schritt 2: Anliegen -->
<div class="ticket-form-section">
<p class="ticket-section-label"><span class="ticket-section-number">2</span><span id="issue-title">Ihr Anliegen</span></p>
<div class="dept-fields">
<?php $renderSlot('topic_top', false); ?>
<div>
<?php if ($useHelpTopics): ?>
<label for="help_topic_id">Kategorie <span style="color:var(--danger)">*</span></label>
<select id="help_topic_id" name="help_topic_id" required onchange="updateTopicFieldVisibility()">
<option value="">– bitte wählen –</option>
<?php foreach ($helpTopics as $t): ?>
<option value="<?= $t['id'] ?>" <?= (($_POST['help_topic_id'] ?? '') == $t['id']) ? 'selected' : '' ?>><?= e($t['path']) ?></option>
<?php endforeach; ?>
</select>
<?php else: ?>
<div class="field-row" style="margin-bottom:14px">
<div>
<label for="department_id">Abteilung <span style="color:var(--danger)">*</span></label>
<select id="department_id" name="department_id" required>
<option value="">– bitte wählen –</option>
<?php foreach ($departments as $d): ?>
<option value="<?= $d['id'] ?>" <?= (($_POST['department_id'] ?? '') == $d['id']) ? 'selected' : '' ?>><?= e($d['name']) ?></option>
<?php endforeach; ?>
</select>
</div>
<div>
<label for="priority_id">Priorität <span style="color:var(--danger)">*</span></label>
<select id="priority_id" name="priority_id" required>
<?php foreach ($priorities as $p): ?>
<option value="<?= $p['id'] ?>" <?= (($_POST['priority_id'] ?? '') == $p['id'] || (!isset($_POST['priority_id']) && $p['name']==='Normal')) ? 'selected' : '' ?>><?= e($p['name']) ?></option>
<?php endforeach; ?>
</select>
</div>
</div>
<?php endif; ?>
</div>
<?php $renderSlot('topic', false); ?>
</div>
</div>
<?php if (!$user): ?>
<!-- Schritt 1: Kontaktdaten -->
<div class="ticket-form-section">
<p class="ticket-section-label"><span class="ticket-section-number">1</span><span class="contact-title">Ihre Kontaktdaten</span></p>
<div class="dept-fields">
<?php $renderSlot('contact_top', false); ?>
<div class="fw-half">
<label for="name">Name <span style="color:var(--danger)">*</span></label>
<input type="text" id="name" name="name" value="<?= e($_POST['name'] ?? '') ?>" placeholder="Max Mustermann" required autofocus>
</div>
<div class="fw-half">
<label for="email">E-Mail-Adresse <span style="color:var(--danger)">*</span></label>
<input type="email" id="email" name="email" value="<?= e($_POST['email'] ?? '') ?>" placeholder="max@beispiel.de" required>
</div>
<?php $renderSlot('contact', false); ?>
</div>
<p class="ticket-form-hint">
<a href="/register.php">Kostenloses Konto erstellen</a> um alle Ihre Tickets im Überblick zu behalten.
</p>
</div>
<?php else: ?>
<div class="ticket-form-section" id="contact-extra" style="display:none">
<p class="ticket-section-label"><span class="ticket-section-number">1</span><span class="contact-title">Ihre Kontaktdaten</span></p>
<?php $renderSlot('contact_top'); ?>
<?php $renderSlot('contact'); ?>
</div>
<?php endif; ?>
<!-- Schritt 3: Details -->
<div class="ticket-form-section" id="details-section">
<p class="ticket-section-label"><span class="ticket-section-number"></span><span id="details-title">Ihr Anliegen</span></p>
<div id="dept-intro" style="display:none;margin-bottom:14px"><strong id="dept-title"></strong><p class="muted" id="dept-intro-text" style="margin:4px 0 0"></p></div>
<div class="dept-fields">
<?php $renderSlot('issue_top', false); ?>
<div id="subject-wrap">
<label for="subject"><span id="subject-label">Betreff</span> <span style="color:var(--danger)">*</span></label>
<input type="text" id="subject" name="subject" value="<?= e($_POST['subject'] ?? '') ?>" placeholder="Kurze Zusammenfassung Ihres Problems" required>
</div>
<?php $renderSlot('issue_mid', false); ?>
<div id="message-wrap">
<label for="message"><span id="message-label">Beschreibung</span> <span style="color:var(--danger)">*</span></label>
<textarea id="message" name="message" required placeholder="Beschreiben Sie Ihr Anliegen so detailliert wie möglich…" style="min-height:140px"><?= e($_POST['message'] ?? '') ?></textarea>
</div>
<?php $renderSlot('issue', false); ?>
</div>
</div>
<?php if ($customFields): ?>
<div class="ticket-form-section" id="custom-section">
<p class="ticket-section-label"><span class="ticket-section-number"></span>Zusätzliche Angaben</p>
<div class="dept-fields"><?php foreach ($customFields as $f) { $renderField($f); } ?></div>
</div>
<?php endif; ?>
<?php // Eigene Abschnitte der Abteilungsformulare (Abschnittsname frei wählbar)
foreach ($deptForms as $did => $df):
$groups = [];
foreach ($df['fields'] as $ff) if (!in_array($ff['_section'], ['contact', 'contact_top', 'issue', 'issue_top', 'issue_mid', 'topic', 'topic_top'], true)) $groups[$ff['_section_title']][] = $ff;
foreach ($groups as $title => $list): ?>
<div class="ticket-form-section dept-section" data-dept="<?= (int) $did ?>" style="display:none">
<p class="ticket-section-label"><span class="ticket-section-number"></span><?= e($title) ?></p>
<div class="dept-fields"><?php foreach ($list as $ff) $renderField($ff); ?></div>
</div>
<?php endforeach; endforeach; ?>
<!-- Anhang -->
<div class="ticket-form-section ticket-attachment-section" id="attachment-section">
<p class="ticket-section-label"><span class="ticket-section-number"></span>Anhang <span class="ticket-section-optional">(optional)</span></p>
<label for="attachment" style="display:inline-flex;align-items:center;gap:8px;padding:8px 16px;border:1.5px dashed var(--border);border-radius:var(--radius);cursor:pointer;font-size:.85rem;color:var(--muted);font-weight:400;transition:border-color .15s" onmouseover="this.style.borderColor='var(--primary)'" onmouseout="this.style.borderColor='var(--border)'">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21.44 11.05l-9.19 9.19a6 6 0 0 1-8.49-8.49l9.19-9.19a4 4 0 0 1 5.66 5.66l-9.2 9.19a2 2 0 0 1-2.83-2.83l8.49-8.48"/></svg>
<span id="att-label">Datei auswählen</span>
</label>
<input type="file" id="attachment" name="attachment" style="display:none" onchange="document.getElementById('att-label').textContent=this.files[0]?this.files[0].name:'Datei auswählen'">
</div>
<div class="ticket-form-actions">
<p class="ticket-form-hint">Pflichtfelder sind mit <span class="ticket-required">*</span> gekennzeichnet.</p>
<button type="submit" class="btn ticket-submit" id="submit-btn">Ticket erstellen</button>
</div>
</form>
</div>
</div>
<script>
// Formular passt sich der gewählten Abteilung an: Abteilungen mit eigenem Formular zeigen nur ihre Felder und Texte.
var DEPT_FORMS = <?= json_encode($deptForms ? array_map(fn($f) => array_diff_key($f, ['fields' => 1]), $deptForms) : new stdClass(), JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_UNESCAPED_UNICODE) ?>;
var TOPIC_DEPT = <?= json_encode($topicDept ?: new stdClass()) ?>;
var DEFAULTS = { subjectLabel: 'Betreff', messageLabel: 'Beschreibung', hint: 'Beschreiben Sie Ihr Anliegen so detailliert wie möglich…', submit: 'Ticket erstellen' };
function setShown(el, on) {
if (!el) return;
el.style.display = on ? '' : 'none';
el.querySelectorAll('input, select, textarea').forEach(function (i) {
if (!on) { if (i.required) { i.dataset.wasRequired = '1'; i.required = false; } if (i.type !== 'hidden') i.disabled = true; }
else { if (i.dataset.wasRequired === '1') i.required = true; i.disabled = false; }
});
}
function applyDeptForm() {
var topicSel = document.getElementById('help_topic_id'), deptSel = document.getElementById('department_id');
var topic = topicSel ? topicSel.value : '';
var dept = topicSel ? (TOPIC_DEPT[topic] || '') : (deptSel ? deptSel.value : '');
var form = DEPT_FORMS[dept] || null;
var visible = 0, contactVisible = 0;
document.querySelectorAll('.custom-field-group').forEach(function (g) {
var gd = g.getAttribute('data-dept'), gt = g.getAttribute('data-topic');
var show = form ? gd === String(dept) : (gd === '' && (gt === '' || gt === topic));
setShown(g, show);
if (show && gd === '') visible++;
if (show && gd !== '' && g.closest('#contact-extra')) contactVisible++;
});
var sec = document.getElementById('custom-section');
if (sec) sec.style.display = visible > 0 ? '' : 'none';
document.querySelectorAll('.dept-fields').forEach(function (w) {
w.style.display = Array.prototype.some.call(w.children, function (c) { return c.style.display !== 'none'; }) ? '' : 'none';
});
var cx = document.getElementById('contact-extra');
if (cx) cx.style.display = contactVisible > 0 ? '' : 'none';
document.querySelectorAll('.dept-section').forEach(function (s) { s.style.display = (form && s.getAttribute('data-dept') === String(dept)) ? '' : 'none'; });
document.querySelectorAll('.contact-title').forEach(function (t) { t.textContent = form ? form.contact_title : 'Ihre Kontaktdaten'; });
document.getElementById('issue-title').textContent = form ? form.issue_title : 'Ihr Anliegen';
document.getElementById('details-title').textContent = form ? form.details_title : 'Ihr Anliegen'; document.getElementById('subject-label').textContent = form ? form.subject_label : DEFAULTS.subjectLabel;
document.getElementById('message-label').textContent = form ? form.message_label : DEFAULTS.messageLabel;
document.getElementById('message').placeholder = form ? form.message_hint : DEFAULTS.hint;
document.getElementById('submit-btn').textContent = form ? form.submit_label : DEFAULTS.submit;
setShown(document.getElementById('subject-wrap'), !form || form.subject_show);
setShown(document.getElementById('message-wrap'), !form || form.message_show);
[['subject-wrap', 'subject_width'], ['message-wrap', 'message_width']].forEach(function (p) {
var el = document.getElementById(p[0]);
['fw-full', 'fw-half', 'fw-third', 'fw-twothirds'].forEach(function (k) { el.classList.remove(k); });
el.classList.add('fw-' + ((form && form[p[1]]) || 'full'));
});
setShown(document.getElementById('attachment-section'), !form || form.attachment_show);
var has = form && (form.title || form.intro);
document.getElementById('dept-intro').style.display = has ? '' : 'none';
document.getElementById('dept-title').textContent = form ? form.title : '';
document.getElementById('dept-intro-text').textContent = form ? form.intro : '';
var num = 0;
document.querySelectorAll('.ticket-form-section').forEach(function (s) {
var n = s.querySelector('.ticket-section-number');
if (n && s.style.display !== 'none') n.textContent = ++num;
});
}
function updateTopicFieldVisibility() { applyDeptForm(); }
document.addEventListener('DOMContentLoaded', function () {
var d = document.getElementById('department_id');
if (d) d.addEventListener('change', applyDeptForm);
applyDeptForm();
});
</script>
<?php require __DIR__ . '/includes/footer_customer.php'; ?>