'd.m.Y', 'time_format' => 'H:i']; $GLOBALS['wp_transients'] = []; $GLOBALS['wp_http_log'] = []; $GLOBALS['wp_http_handler'] = null; $GLOBALS['wp_user'] = ['id' => 0, 'caps' => [], 'name' => '', 'email' => '']; $GLOBALS['wp_settings_errors'] = []; $GLOBALS['wp_enqueued'] = []; $_POST = []; $_GET = []; $_SERVER['REQUEST_METHOD'] = 'GET'; $_SERVER['REMOTE_ADDR'] = '203.0.113.5'; if ($opts !== null) { $GLOBALS['wp_options']['deskhub_tickets_options'] = array_merge([ 'server_url' => 'https://support.example.de', 'api_key' => 'dh_geheimer_schluessel_123', 'min_seconds' => 0, 'rate_limit_per_hour' => 5, ], $opts); } } function run_form_post(array $post): ?string { $_POST = $post; $_SERVER['REQUEST_METHOD'] = 'POST'; try { Deskhub_Tickets_Form::handle_submit(); } catch (WpRedirect $r) { return $r->location; } return null; } /** Holt den Zustand (Fehler/Erfolg), den handle_submit hinterlassen hat, aus der Weiterleitungsadresse. */ function state_from(?string $location): ?array { if (!$location || !preg_match('/[?&]dht=([a-f0-9]{16})/', $location, $m)) return null; return $GLOBALS['wp_transients']['dht_state_' . $m[1]][0] ?? null; } function render_form(array $atts = [], ?string $location = null): string { $_GET = []; if ($location && preg_match('/[?&]dht=([a-f0-9]{16})/', $location, $m)) $_GET['dht'] = $m[1]; return Deskhub_Tickets_Form::render_ticket_form($atts); } /** Ein gültig aussehendes Formular wie vom Plugin erzeugt: unterschriebene Angaben und Zeitstempel aus der Ausgabe lesen. */ function valid_post(string $html, array $over = []): array { preg_match('/name="dht_cfg" value="([^"]*)"/', $html, $c); preg_match('/name="dht_sig" value="([^"]*)"/', $html, $s); preg_match('/name="dht_ts" value="([^"]*)"/', $html, $t); preg_match('/name="dht_back" value="([^"]*)"/', $html, $b); return array_merge([ 'action' => 'deskhub_ticket_submit', 'dht_cfg' => html_entity_decode($c[1] ?? ''), 'dht_sig' => $s[1] ?? '', 'dht_ts' => $t[1] ?? '', 'dht_back' => html_entity_decode($b[1] ?? ''), 'dht_website' => '', 'dht_name' => 'Erika Muster', 'dht_email' => 'erika@kunde.example', 'dht_subject' => 'Rechnung fehlt', 'dht_message' => "Hallo,\nbitte schicken Sie mir die Rechnung.", ], $over); } function ok_handler(array &$seen = null): callable { return function (string $url, array $args) use (&$seen) { if (str_contains($url, 'form=1') || str_contains($url, 'topics=1')) return wp_stub_response(422, ['error' => ['code' => 'MISSING_PARAMS', 'message' => 'x']]); // wie ein älterer Server $seen[] = ['url' => $url, 'args' => $args]; if (($args['method'] ?? 'GET') === 'POST') return wp_stub_response(201, ['number' => '261101-0001', 'status' => 'open']); return wp_stub_response(200, ['ticket' => ['number' => '261101-0001', 'subject' => 'Rechnung', 'status' => 'answered', 'createdAt' => '2026-11-01 10:00:00', 'updatedAt' => '2026-11-01 11:00:00', 'messages' => [['author' => 'customer', 'message' => 'Hallo ', 'createdAt' => '2026-11-01 10:00:00'], ['author' => 'staff', 'message' => "Erledigt.\nViele Grüße", 'createdAt' => '2026-11-01 11:00:00']]]]); }; } // ── Server-Adresse ───────────────────────────────────────────────────────── reset_wp(); foreach ([ ['support.example.de', 'https://support.example.de'], [' https://support.example.de/ ', 'https://support.example.de'], ['https://example.de/helpdesk/', 'https://example.de/helpdesk'], ['http://127.0.0.1:8080', 'http://127.0.0.1:8080'], ['http://localhost', 'http://localhost'], ] as [$in, $want]) { $check(Deskhub_Tickets_Client::normalize_url($in) === $want, "Adresse '$in' wird zu '$want'"); } foreach (['', 'http://support.example.de', 'https://user:pw@example.de', 'https://example.de/?x=1', 'https://example.de/#a', 'ftp://example.de', 'http://localhost.evil.de'] as $bad) { $check(is_wp_error(Deskhub_Tickets_Client::normalize_url($bad)), "Adresse '$bad' wird abgelehnt"); } // ── Nicht eingerichtet ────────────────────────────────────────────────────── reset_wp(['server_url' => '', 'api_key' => '']); $check(Deskhub_Tickets_Form::render_ticket_form() === '', 'Ohne Einrichtung sieht ein Besucher nichts'); $GLOBALS['wp_user']['caps'] = ['manage_options']; $check(str_contains(Deskhub_Tickets_Form::render_ticket_form(), 'noch nicht eingerichtet'), 'Ein Administrator sieht den Einrichtungshinweis'); // ── Formular wird angezeigt ───────────────────────────────────────────────── reset_wp(['consent_text' => 'Ich habe die Datenschutzerklärung gelesen.']); $html = render_form(); foreach (['name="dht_name"', 'name="dht_email"', 'name="dht_subject"', 'name="dht_message"', 'name="dht_website"', 'name="dht_consent"', 'name="dht_ts"', 'name="dht_sig"'] as $f) { $check(str_contains($html, $f), "Formular enthält $f"); } $check(!str_contains($html, 'dh_geheimer_schluessel_123') && !str_contains($html, 'support.example.de'), 'Weder API-Schlüssel noch Server-Adresse stehen im Formular'); $check(str_contains($html, 'href="/datenschutz"') && !str_contains($html, 'onclick') && !str_contains($html, ' 7, 'caps' => [], 'name' => 'Lena', 'email' => 'lena@example.de']; $check(str_contains(render_form(), 'value="lena@example.de"'), 'Angemeldete Besucher: E-Mail-Adresse ist vorausgefüllt'); $GLOBALS['wp_user'] = ['id' => 0, 'caps' => [], 'name' => '', 'email' => '']; // ── Absenden: Erfolg und Inhalt der Anfrage ───────────────────────────────── reset_wp(['department_id' => 2, 'priority_id' => 1]); $seen = []; $GLOBALS['wp_http_handler'] = ok_handler($seen); $html = render_form(); $loc = run_form_post(valid_post($html, ['dht_departmentId' => '99', 'dht_priority' => '9', 'dht_topic' => '77'])); $state = state_from($loc); $check($state && !empty($state['ok']) && $state['number'] === '261101-0001', 'Absenden gelingt, Ticketnummer wird gemerkt'); $check(str_starts_with((string) $loc, 'https://wp.test/kontakt/?dht=') && str_ends_with((string) $loc, '#deskhub-ticket'), 'Danach geht es auf die Seite des Formulars zurück'); $req = $seen[0] ?? null; $sent = json_decode((string) ($req['args']['body'] ?? ''), true); $check($req && ($req['args']['method'] ?? '') === 'POST' && str_ends_with($req['url'], '/api/public/v1/tickets.php'), 'Anfrage geht per POST an die Ticket-API'); $check(($req['args']['headers']['X-API-Key'] ?? '') === 'dh_geheimer_schluessel_123', 'Der API-Schlüssel wird im Header gesendet'); $check(($sent['departmentId'] ?? null) === 2 && ($sent['priorityId'] ?? null) === 1, 'Abteilung und Priorität kommen aus den Einstellungen, nicht aus dem Formular'); $check(!isset($sent['helpTopicId']), 'Ein vom Besucher erfundenes Thema wird nicht übernommen'); $check(str_contains((string) ($sent['message'] ?? ''), "bitte schicken Sie mir die Rechnung.") && str_contains((string) $sent['message'], 'https://wp.test/kontakt/'), 'Nachricht enthält den Text und die Herkunftsseite'); $check(render_form([], $loc) !== '' && str_contains(render_form([], $loc), 'Vielen Dank') === false, 'Der Erfolgszustand wird nur einmal angezeigt (danach wieder das leere Formular)'); $loc2 = run_form_post(valid_post($html)); $check(str_contains(render_form([], $loc2), '261101-0001'), 'Erfolgsseite zeigt die Ticketnummer'); // ── Manipulation und Roboter ──────────────────────────────────────────────── reset_wp(); $seen = []; $GLOBALS['wp_http_handler'] = ok_handler($seen); $html = render_form(); $bad = valid_post($html, ['dht_cfg' => base64_encode('{"d":99,"t":0,"p":9,"o":[]}')]); $st = state_from(run_form_post($bad)); $check($st && !empty($st['errors']) && !$seen, 'Veränderte Abteilung/Priorität (falsche Unterschrift) wird abgelehnt, nichts gesendet'); $st = state_from(run_form_post(valid_post($html, ['dht_website' => 'http://spam.example']))); $check($st && !empty($st['ok']) && $st['number'] === '' && !$seen, 'Falle-Feld ausgefüllt: scheinbar Erfolg, aber nichts angelegt'); $st = state_from(run_form_post(valid_post($html, ['dht_ts' => 'abc']))); $check($st && !empty($st['errors']) && !$seen, 'Gefälschter Zeitstempel wird abgelehnt'); reset_wp(['min_seconds' => 10]); $seen = []; $GLOBALS['wp_http_handler'] = ok_handler($seen); $st = state_from(run_form_post(valid_post(render_form()))); $check($st && !empty($st['errors']) && !$seen, 'Zu schnell abgesendet (unter der Mindestzeit) wird abgelehnt'); // ── Pflichtfelder, Einwilligung, Themenauswahl ────────────────────────────── reset_wp(['consent_text' => 'Ich stimme zu.']); $seen = []; $GLOBALS['wp_http_handler'] = ok_handler($seen); $html = render_form(); $st = state_from(run_form_post(valid_post($html, ['dht_name' => '', 'dht_email' => 'kein-email', 'dht_subject' => '', 'dht_message' => ' ']))); $check($st && count($st['errors']) === 5 && !$seen, 'Fehlende Pflichtfelder und fehlende Einwilligung ergeben fünf Fehler (nichts gesendet)'); $st = state_from(run_form_post(valid_post($html, ['dht_message' => 'Hallo Welt ', 'dht_consent' => '1']))); $sent = json_decode((string) ($seen[0]['args']['body'] ?? ''), true); $check(!str_contains((string) ($sent['message'] ?? ''), '<'), 'HTML in der Nachricht wird entfernt'); $check($st && !empty($st['ok']), 'Mit Einwilligung klappt es'); reset_wp(['consent_text' => '']); $seen = []; $GLOBALS['wp_http_handler'] = ok_handler($seen); $html = render_form(['topics' => 'Rechnung:3,Technik:4', 'department' => '5']); $check(str_contains($html, '