{active, name, greeting, helpTopics, user} * ask {message} -> {type: "answer"|"none"|"ticket", ...} * ticket {name, email, subject, message, helpTopicId?} -> {ok, number} * Jeder Aufruf braucht den CSRF-Token der Sitzung (csrf_token). Fragen und Tickets sind je Sitzung bzw. Adresse begrenzt. */ require_once __DIR__ . '/includes/bootstrap.php'; require_once __DIR__ . '/includes/assistant.php'; header('Content-Type: application/json; charset=utf-8'); header('Cache-Control: no-store'); function assistant_json(array $data, int $status = 200): never { http_response_code($status); echo json_encode($data, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); exit; } $pdo = get_db(); $action = (string) ($_POST['action'] ?? $_GET['action'] ?? ''); if (!in_array($action, ['status', 'ask', 'ticket', 'feedback'], true)) assistant_json(['error' => 'Unbekannte Aktion.'], 400); // Anfragen müssen vom eigenen Formular der Seite kommen (Token der Sitzung) $token = (string) ($_POST['csrf_token'] ?? $_GET['csrf_token'] ?? ''); if ($token === '' || !hash_equals((string) ($_SESSION['csrf_token'] ?? ''), $token)) { assistant_json(['error' => 'Die Sitzung ist abgelaufen. Bitte laden Sie die Seite neu.', 'csrfExpired' => true], 403); } $active = assistant_active($pdo); $texts = assistant_texts(); if ($action === 'status') { $user = current_user(); assistant_json([ 'active' => $active, 'name' => $texts['name'], 'greeting' => $texts['greeting'], 'noAnswer' => $texts['no_answer'], 'helpTopics' => $active ? assistant_help_topics($pdo) : [], 'user' => $user ? ['name' => (string) ($user['name'] ?? ''), 'email' => (string) ($user['email'] ?? '')] : null, ]); } if (!$active) assistant_json(['error' => 'Der Assistent ist gerade nicht verfügbar.', 'active' => false], 409); if ($_SERVER['REQUEST_METHOD'] !== 'POST') assistant_json(['error' => 'Nur POST.'], 405); if ($action === 'feedback') { // Rückmeldung zu einer Antwort ("Das hat geholfen" oder Ticket statt Antwort); nur für Antworten dieser Sitzung $id = (int) ($_POST['logId'] ?? 0); if ($id > 0 && in_array($id, array_map('intval', (array) ($_SESSION['assistant_log_ids'] ?? [])), true)) { try { $pdo->prepare("UPDATE assistant_log SET helpful = ? WHERE id = ? AND result IN ('custom','kb')")->execute([!empty($_POST['helpful']) ? 1 : 0, $id]); } catch (Throwable $e) { // Datenbank noch nicht aktualisiert } } assistant_json(['ok' => true]); } if ($action === 'ask') { // höchstens 30 Fragen je 10 Minuten und Sitzung $now = time(); $log = array_values(array_filter((array) ($_SESSION['assistant_asks'] ?? []), fn($t) => $t > $now - 600)); if (count($log) >= 30) assistant_json(['error' => 'Das waren viele Fragen in kurzer Zeit. Bitte warten Sie einen Moment.'], 429); $log[] = $now; $_SESSION['assistant_asks'] = $log; $message = trim(mb_substr((string) ($_POST['message'] ?? ''), 0, 300)); if ($message === '') assistant_json(['error' => 'Bitte eine Frage eingeben.'], 422); // Wunsch nach einem Ticket oder einem Menschen: nicht erst suchen $norm = assistant_normalize($message); if (preg_match('/\b(ticket|mitarbeiter|support|beschwerde|reklamation|kundenservice|ansprechpartner)\b/', $norm) && preg_match('/\b(erstell|eroeffn|anleg|oeffn|schreib|melden|sprechen|kontakt|will|moechte|brauche|bitte)/', $norm)) { assistant_log_write($pdo, $message, 'ticket_request'); assistant_json(['type' => 'ticket', 'text' => 'Gern, ich erstelle ein Ticket für Sie.']); } // Begrüßung und Dank if (preg_match('/^(hallo|hi|hey|guten (tag|morgen|abend)|moin|servus)\b/', $norm) && count(assistant_tokens($message)) === 0) { assistant_json(['type' => 'smalltalk', 'text' => 'Hallo! Was möchten Sie wissen?']); } if (preg_match('/^(danke|vielen dank|dankeschoen|super danke|ok danke)\b/', $norm)) { assistant_json(['type' => 'smalltalk', 'text' => 'Gern geschehen! Kann ich noch etwas für Sie tun?']); } $found = assistant_search($pdo, $message); if (!$found) { assistant_log_write($pdo, $message, 'none'); assistant_json(['type' => 'none', 'text' => $texts['no_answer']]); } $logId = assistant_log_write($pdo, $message, $found['source'] === 'custom' ? 'custom' : 'kb', ['source' => $found['source'], 'id' => $found['id'], 'title' => $found['title']]); if ($logId) $_SESSION['assistant_log_ids'] = array_slice(array_merge((array) ($_SESSION['assistant_log_ids'] ?? []), [$logId]), -50); assistant_json(['logId' => $logId, 'type' => 'answer', 'source' => $found['source'], 'title' => $found['title'], 'text' => $found['text'], 'link' => $found['link'], 'others' => $found['others']]); } // ── Ticket erstellen ───────────────────────────────────────────────────────── if (!empty($_POST['hp_website'])) assistant_json(['error' => 'Anfrage abgelehnt.'], 400); // Spam-Falle: das versteckte Feld bleibt bei Menschen leer $ip = client_ip(); // höchstens 3 Tickets je Stunde und Adresse über den Assistenten $recent = $pdo->prepare("SELECT COUNT(*) FROM tickets WHERE requester_ip = ? AND created_at >= ? AND id IN (SELECT ticket_id FROM activity_log WHERE action LIKE 'Ticket über den digitalen Assistenten%')"); $recent->execute([$ip, date('Y-m-d H:i:s', time() - 3600)]); if ((int) $recent->fetchColumn() >= 3) assistant_json(['error' => 'Über den Assistenten wurden bereits mehrere Tickets erstellt. Bitte versuchen Sie es später noch einmal oder antworten Sie auf die E-Mail zu Ihrem Ticket.'], 429); $in = [ 'name' => trim((string) ($_POST['name'] ?? '')), 'email' => trim((string) ($_POST['email'] ?? '')), 'subject' => trim((string) ($_POST['subject'] ?? '')), 'message' => trim((string) ($_POST['message'] ?? '')), ]; $topicId = (int) ($_POST['helpTopicId'] ?? 0); if ($topicId > 0) { $ok = false; foreach (assistant_help_topics($pdo) as $t) if ($t['id'] === $topicId) $ok = true; if (!$ok) assistant_json(['error' => 'Das gewählte Thema gibt es nicht.'], 422); $in['helpTopicId'] = $topicId; } // Abteilung und Priorität für Assistenten-Tickets (Einstellung); ein gewähltes Hilfethema bestimmt sie selbst if ($topicId <= 0) { $dept = (int) get_setting('assistant_department_id', '0'); $prio = (int) get_setting('assistant_priority_id', '0'); if ($dept > 0) $in['departmentId'] = $dept; if ($prio > 0) $in['priorityId'] = $prio; } try { $created = public_ticket_create($pdo, $in, $ip, ['activity' => 'Ticket über den digitalen Assistenten erstellt', 'tag' => 'Assistent']); } catch (AdminModelException $e) { assistant_json(['error' => $e->getMessage()], $e->httpStatus()); } assistant_log_write($pdo, mb_substr($in['subject'], 0, 300), 'ticket_created', null, $created['number']); assistant_json(['ok' => true, 'number' => $created['number']], 201);