532 lines
29 KiB
PHP
532 lines
29 KiB
PHP
<?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));
|
||
}
|
||
}
|
||
|
||
$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 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'; ?>
|