prepare( 'SELECT t.*, p.name AS priority_name, p.color AS priority_color, d.name AS department_name FROM tickets t JOIN priorities p ON p.id = t.priority_id JOIN departments d ON d.id = t.department_id WHERE t.ticket_number = ?' ); $stmt->execute([$number]); $ticket = $stmt->fetch(); if (!$ticket) { flash('error', 'Dieses Ticket wurde nicht gefunden.'); redirect('/home.php'); } $user = current_user(); if (verify_ticket_access_token((int) $ticket['id'], (string) ($_GET['token'] ?? ''))) { if ($user) { claim_ticket_for_customer($pdo, (int) $ticket['id'], $user); } $_SESSION['guest_ticket_access'][$ticket['ticket_number']] = true; session_write_close(); redirect('/ticket_view.php?n=' . urlencode($ticket['ticket_number'])); } $allowed = ($user && $ticket['user_id'] == $user['id']) || !empty($_SESSION['guest_ticket_access'][$ticket['ticket_number']]); if (!$allowed) { flash('error', 'Bitte melden Sie sich an oder bestätigen Sie Ihre E-Mail-Adresse, um dieses Ticket zu sehen.'); redirect('/ticket_lookup.php?n=' . urlencode($number)); } $errors = []; if ($_SERVER['REQUEST_METHOD'] === 'POST') { // Ticket-Bewertung (auch bei geschlossenem Ticket erlaubt) if (($_POST['action'] ?? '') === 'rate_ticket') { verify_csrf(); $rating = (int)($_POST['rating'] ?? 0); $comment = trim($_POST['rating_comment'] ?? ''); if ($rating >= 1 && $rating <= 5 && get_setting('ticket_ratings_enabled', '1') === '1') { $ratingSql = $pdo->getAttribute(PDO::ATTR_DRIVER_NAME) === 'sqlite' ? 'INSERT INTO ticket_ratings (ticket_id, user_id, rating, comment, created_at) VALUES (?,?,?,?,?) ON CONFLICT(ticket_id) DO UPDATE SET user_id=excluded.user_id, rating=excluded.rating, comment=excluded.comment, created_at=excluded.created_at' : 'INSERT INTO ticket_ratings (ticket_id, user_id, rating, comment, created_at) VALUES (?,?,?,?,?) ON DUPLICATE KEY UPDATE user_id=VALUES(user_id), rating=VALUES(rating), comment=VALUES(comment), created_at=VALUES(created_at)'; $pdo->prepare($ratingSql) ->execute([$ticket['id'], $user['id'] ?? null, $rating, $comment ?: null, db_now()]); flash('success', 'Vielen Dank für Ihre Bewertung!'); } redirect('/ticket_view.php?n=' . urlencode($ticket['ticket_number'])); } } if ($_SERVER['REQUEST_METHOD'] === 'POST' && !in_array($ticket['status'], ['closed'])) { verify_csrf(); $message = trim($_POST['message'] ?? ''); if ($message === '') { $errors[] = 'Bitte geben Sie eine Nachricht ein.'; } else { $now = db_now(); $stmt = $pdo->prepare( 'INSERT INTO ticket_threads (ticket_id, author_type, user_id, message, is_internal_note, created_at) VALUES (?,?,?,?,0,?)' ); $stmt->execute([$ticket['id'], 'customer', $user['id'] ?? null, $message, $now]); $threadId = (int) $pdo->lastInsertId(); if (!empty($_FILES['attachments']['name'][0])) { $uploadResults = handle_ticket_uploads_multi($_FILES['attachments']); foreach ($uploadResults as $up) { if (isset($up['error'])) { $errors[] = $up['error']; } else { $pdo->prepare('INSERT INTO ticket_attachments (thread_id, original_name, stored_name, filesize, created_at) VALUES (?,?,?,?,?)') ->execute([$threadId, $up['original_name'], $up['stored_name'], $up['filesize'], $now]); } } } // Ein Kunden-Reply auf ein beantwortetes Ticket setzt den Status zurück auf "Offen" $wasReopened = in_array($ticket['status'], ['answered', 'resolved', 'pending']); $newStatus = $wasReopened ? 'open' : $ticket['status']; $stmt = $pdo->prepare('UPDATE tickets SET status = ?, updated_at = ? WHERE id = ?'); $stmt->execute([$newStatus, $now, $ticket['id']]); ticket_sla_status_transition($pdo, (int) $ticket['id'], (string) $ticket['status'], $newStatus); log_activity($pdo, $ticket['id'], null, 'Kunde hat geantwortet'); if ($wasReopened) { log_activity($pdo, $ticket['id'], null, 'Ticket wieder geöffnet'); } notify_new_reply_channels($ticket, $message); webhook_dispatch($pdo, 'ticket.replied', (int) $ticket['id'], ['author' => 'customer']); if ($newStatus !== $ticket['status']) webhook_dispatch($pdo, 'ticket.status_changed', (int) $ticket['id'], ['previousStatus' => $ticket['status']]); flash('success', 'Ihre Antwort wurde übermittelt.'); redirect('/ticket_view.php?n=' . urlencode($number)); } } // Thread laden $stmt = $pdo->prepare( "SELECT tt.*, s.name AS staff_name, u.name AS user_name FROM ticket_threads tt LEFT JOIN staff s ON s.id = tt.staff_id LEFT JOIN users u ON u.id = tt.user_id WHERE tt.ticket_id = ? AND tt.is_internal_note = 0 ORDER BY tt.created_at ASC" ); $stmt->execute([$ticket['id']]); $thread = $stmt->fetchAll(); // Anhänge je Thread-Eintrag $attStmt = $pdo->prepare('SELECT * FROM ticket_attachments WHERE thread_id = ?'); // Individuelle Felder, die bei der Ticket-Erstellung ausgefüllt wurden $fieldStmt = $pdo->prepare( "SELECT cf.label, cf.field_type, v.value FROM ticket_field_values v JOIN custom_fields cf ON cf.id = v.custom_field_id WHERE v.ticket_id = ? ORDER BY cf.sort_order, cf.label" ); $fieldStmt->execute([$ticket['id']]); $customFieldValues = $fieldStmt->fetchAll(); $pageTitle = "Ticket #{$ticket['ticket_number']}"; $activePage = 'dashboard'; require __DIR__ . '/includes/header_customer.php'; [$statusLabel, $statusColor] = status_label($ticket['status']); ?>
= e($ticket['department_name']) ?> · erstellt am = format_dt($ticket['created_at']) ?>
Melden Sie sich an oder erstellen Sie ein Konto mit derselben E-Mail-Adresse. Dieses Ticket bleibt in diesem Browser freigeschaltet und kann danach in „Meine Tickets“ hinzugefügt werden.
Anmelden · Konto erstellenDieses Ticket ist geschlossen. Bei weiteren Fragen erstellen Sie bitte ein neues Ticket.
prepare('SELECT * FROM ticket_ratings WHERE ticket_id=?'); $rs->execute([$ticket['id']]); $existingRating = $rs->fetch(); } if ($ratingsEnabled): ?>= e($existingRating['comment']) ?>