';
}
// ───────────────────────────── Ticket anlegen ─────────────────────────────
/** Plätze im Formular, die DeskHub für eigene Felder kennt (alles andere kommt als „Zusätzliche Angaben“ ans Ende). */
const SLOTS = ['contact_top', 'contact', 'topic_top', 'topic', 'issue_top', 'issue_mid', 'issue'];
/**
* @param array|string $atts
*/
public static function render_ticket_form($atts = []): string
{
$atts = shortcode_atts([
'department' => '',
'topic' => '',
'priority' => '',
'topics' => '',
'title' => '',
], is_array($atts) ? $atts : [], 'deskhub_ticket');
self::enqueue();
$client = Deskhub_Tickets_Client::from_settings();
if (!$client) {
return current_user_can('manage_options')
? '
' . esc_html__('DeskHub Tickets ist noch nicht eingerichtet: Bitte unter Einstellungen → DeskHub Tickets Server-Adresse und API-Schlüssel eintragen. (Diese Meldung sehen nur Administratoren.)', 'deskhub-tickets') . '
' . esc_html__('Der DeskHub-Server kennt die Formular-Schnittstelle noch nicht (bitte DeskHub aktualisieren). Bis dahin wird das einfache Formular ohne eigene Felder gezeigt. (Diese Meldung sehen nur Administratoren.)', 'deskhub-tickets') . '
';
switch ($f['type']) {
case 'textarea':
$o .= ''
. '';
break;
case 'select':
$o .= '';
break;
case 'radio':
$o .= '';
break;
case 'checkbox':
$o .= '';
$help = '';
break;
case 'number':
case 'date':
$o .= '';
break;
default:
$o .= '';
}
if ($help !== '') {
$o .= '
' . $help . '
';
}
return $o . '
';
}
/**
* Prüft die Angaben zu den Feldern des Formulars wie DeskHub selbst (DeskHub prüft beim Anlegen noch einmal).
*
* @param array $fields
* @param array $input Feld-ID => abgeschickter Wert
* @return array{0:string[],1:array} Fehlermeldungen und bereinigte Werte (Feld-ID => Wert)
*/
public static function validate_fields(array $fields, array $input): array
{
$errors = [];
$values = [];
foreach ($fields as $f) {
$id = (int) $f['id'];
$label = (string) $f['label'];
$type = (string) $f['type'];
$raw = isset($input[$id]) ? (string) $input[$id] : '';
if ($type === 'checkbox') {
$val = in_array(strtolower(trim($raw)), ['1', 'on', 'yes', 'ja', 'true'], true) ? '1' : '0';
} else {
$val = $type === 'textarea' ? sanitize_textarea_field($raw) : sanitize_text_field($raw);
$val = mb_substr($val, 0, 10000);
}
if (!empty($f['required']) && $type !== 'checkbox' && $val === '') {
$errors[] = sprintf(__('Bitte füllen Sie das Feld „%s“ aus.', 'deskhub-tickets'), $label);
} elseif (!empty($f['required']) && $type === 'checkbox' && $val !== '1') {
$errors[] = sprintf(__('Bitte bestätigen Sie „%s“.', 'deskhub-tickets'), $label);
} elseif (in_array($type, ['select', 'radio'], true) && $val !== '' && !in_array($val, $f['options'], true)) {
$errors[] = sprintf(__('Bitte wählen Sie bei „%s“ eine der angebotenen Möglichkeiten.', 'deskhub-tickets'), $label);
} elseif ($type === 'number' && $val !== '' && !is_numeric($val)) {
$errors[] = sprintf(__('Bitte geben Sie bei „%s“ eine Zahl an.', 'deskhub-tickets'), $label);
} elseif ($type === 'date' && $val !== '' && !(preg_match('/^(\d{4})-(\d{2})-(\d{2})$/', $val, $m) && checkdate((int) $m[2], (int) $m[3], (int) $m[1]))) {
$errors[] = sprintf(__('Bitte geben Sie bei „%s“ ein gültiges Datum an.', 'deskhub-tickets'), $label);
}
$values[$id] = $val;
}
return [$errors, $values];
}
/** Ticket-Text aus den Feldern, wenn das Formular kein Beschreibungsfeld hat (wie im Kundenportal von DeskHub). */
public static function compose_message(array $fields, array $values): string
{
$lines = [];
foreach ($fields as $f) {
$v = $values[(int) $f['id']] ?? '';
if ($f['type'] === 'checkbox') {
$v = $v === '1' ? __('Ja', 'deskhub-tickets') : __('Nein', 'deskhub-tickets');
}
if ($v !== '') {
$lines[] = $f['label'] . ': ' . $v;
}
}
return $lines ? implode("\n", $lines) : __('Anfrage ohne weitere Angaben', 'deskhub-tickets');
}
/** Verarbeitet das abgeschickte Formular (admin-post.php) und leitet auf die Seite zurück. */
public static function handle_submit(): void
{
$back = self::safe_back(isset($_POST['dht_back']) ? (string) wp_unslash($_POST['dht_back']) : '');
$opts = deskhub_tickets_options();
// 1) Unterschriebene Angaben prüfen (Abteilung, Thema, Priorität)
$cfgB64 = isset($_POST['dht_cfg']) ? (string) wp_unslash($_POST['dht_cfg']) : '';
$sig = isset($_POST['dht_sig']) ? (string) wp_unslash($_POST['dht_sig']) : '';
$cfg = null;
if (deskhub_tickets_verify_signature('cfg|' . $cfgB64, $sig)) {
$decoded = json_decode((string) base64_decode($cfgB64, true), true);
if (is_array($decoded)) {
$cfg = $decoded;
}
}
if ($cfg === null) {
self::fail($back, [__('Das Formular ist abgelaufen. Bitte laden Sie die Seite neu und versuchen Sie es noch einmal.', 'deskhub-tickets')], []);
}
// 2) Eingaben bereinigen
$postedFields = [];
if (isset($_POST['dht_f']) && is_array($_POST['dht_f'])) {
foreach ((array) wp_unslash($_POST['dht_f']) as $fid => $v) {
if (is_scalar($v)) {
$postedFields[absint($fid)] = (string) $v;
}
}
}
$values = [
'name' => mb_substr(sanitize_text_field(isset($_POST['dht_name']) ? (string) wp_unslash($_POST['dht_name']) : ''), 0, 120),
'email' => sanitize_email(isset($_POST['dht_email']) ? (string) wp_unslash($_POST['dht_email']) : ''),
'subject' => mb_substr(sanitize_text_field(isset($_POST['dht_subject']) ? (string) wp_unslash($_POST['dht_subject']) : ''), 0, 255),
'message' => mb_substr(sanitize_textarea_field(isset($_POST['dht_message']) ? (string) wp_unslash($_POST['dht_message']) : ''), 0, self::MAX_MESSAGE),
'topic' => isset($_POST['dht_topic']) ? absint(wp_unslash($_POST['dht_topic'])) : 0,
'fields' => $postedFields,
];
// 3) Falle-Feld: Wer es ausfüllt, ist ein Roboter. Er bekommt eine Erfolgsmeldung, es wird aber nichts angelegt.
if (isset($_POST['dht_website']) && trim((string) wp_unslash($_POST['dht_website'])) !== '') {
self::succeed($back, '');
}
// 4) Zeit seit dem Anzeigen des Formulars
$age = deskhub_tickets_timestamp_age(isset($_POST['dht_ts']) ? (string) wp_unslash($_POST['dht_ts']) : '');
$minSeconds = (int) $opts['min_seconds'];
if ($age === null || $age < $minSeconds) {
self::fail($back, [__('Das ging etwas zu schnell. Bitte prüfen Sie Ihre Angaben und senden Sie noch einmal ab.', 'deskhub-tickets')], $values);
}
// 5) Thema und Formular (aus DeskHub; ohne Antwort das einfache Formular)
$errors = [];
$allowedTopics = isset($cfg['o']) && is_array($cfg['o']) ? array_map('intval', $cfg['o']) : [];
$topicId = (int) ($cfg['t'] ?? 0);
if ($allowedTopics) {
if (!in_array($values['topic'], $allowedTopics, true)) {
$errors[] = __('Bitte wählen Sie ein Thema aus.', 'deskhub-tickets');
} else {
$topicId = $values['topic'];
}
}
$client = Deskhub_Tickets_Client::from_settings();
if (!$client) {
self::fail($back, [__('Der Support ist gerade nicht erreichbar. Bitte versuchen Sie es später noch einmal.', 'deskhub-tickets')], $values);
}
$form = null;
if (!$errors) {
$remote = $client->get_form($topicId ?: null, (int) ($cfg['d'] ?? 0) ?: null);
if (!is_wp_error($remote)) {
$form = self::normalize_form($remote);
}
}
$showSubject = $form === null || $form['subject']['show'];
$showMessage = $form === null || $form['message']['show'];
// 6) Pflichtfelder
if ($values['name'] === '') {
$errors[] = __('Bitte geben Sie Ihren Namen an.', 'deskhub-tickets');
}
if ($values['email'] === '' || !is_email($values['email'])) {
$errors[] = __('Bitte geben Sie eine gültige E-Mail-Adresse an.', 'deskhub-tickets');
}
if ($showSubject && $values['subject'] === '') {
$errors[] = __('Bitte geben Sie einen Betreff an.', 'deskhub-tickets');
}
if ($showMessage && trim($values['message']) === '') {
$errors[] = __('Bitte beschreiben Sie Ihr Anliegen.', 'deskhub-tickets');
}
$fieldValues = [];
if ($form !== null) {
[$fieldErrors, $fieldValues] = self::validate_fields($form['fields'], $postedFields);
$errors = array_merge($errors, $fieldErrors);
}
// Dateianhänge (nur wenn das Formular sie anbietet)
$attachments = [];
if (!empty($_FILES['dht_files']) && is_array($_FILES['dht_files'])) {
$policy = $form !== null ? $form['attachments'] : self::normalize_attachments(null);
[$attErrors, $attachments] = self::collect_attachments($_FILES['dht_files'], $policy);
$errors = array_merge($errors, $attErrors);
}
if (trim((string) $opts['consent_text']) !== '' && empty($_POST['dht_consent'])) {
$errors[] = __('Bitte stimmen Sie der Verarbeitung Ihrer Angaben zu.', 'deskhub-tickets');
}
if ($errors) {
self::fail($back, $errors, $values);
}
// 7) Captcha und Mengenbegrenzung
$captcha = self::verify_turnstile();
if ($captcha !== true) {
self::fail($back, [$captcha], $values);
}
if (!deskhub_tickets_rate_allow('submit', (int) $opts['rate_limit_per_hour'], HOUR_IN_SECONDS)) {
self::fail($back, [__('Sie haben in der letzten Stunde schon mehrere Tickets gesendet. Bitte versuchen Sie es später noch einmal.', 'deskhub-tickets')], $values);
}
// 8) Ticket anlegen
$text = $showMessage ? $values['message'] : self::compose_message($form['fields'], $fieldValues);
$message = $text . "\n\n— " . sprintf(
/* translators: %s: Adresse der Seite mit dem Formular */
__('Gesendet über das Formular auf %s', 'deskhub-tickets'),
$back
);
$payload = [
'name' => $values['name'],
'email' => $values['email'],
'subject' => $showSubject ? $values['subject'] : '', // ohne Betrefffeld setzt DeskHub „Anfrage “
'message' => $message,
'helpTopicId' => $topicId,
'departmentId' => (int) ($cfg['d'] ?? 0),
'priorityId' => (int) ($cfg['p'] ?? 0),
];
if ($form !== null) {
$payload['fields'] = $fieldValues;
}
if ($attachments) {
$payload['attachments'] = $attachments;
}
$payload = apply_filters('deskhub_tickets_payload', $payload, $values);
$result = $client->create_ticket($payload);
if (is_wp_error($result)) {
self::fail($back, [Deskhub_Tickets_Client::user_message($result)], $values);
}
do_action('deskhub_tickets_created', $result['number'], $payload);
self::succeed($back, $result['number']);
}
// ───────────────────────────── Stand abfragen ─────────────────────────────
/**
* @param array|string $atts
*/
public static function render_status_form($atts = []): string
{
self::enqueue();
if (!Deskhub_Tickets_Client::from_settings()) {
return current_user_can('manage_options')
? '
' . esc_html__('DeskHub Tickets ist noch nicht eingerichtet (Einstellungen → DeskHub Tickets). Diese Meldung sehen nur Administratoren.', 'deskhub-tickets') . '
'
: '';
}
$number = '';
$email = '';
$error = '';
$ticket = null;
if (isset($_SERVER['REQUEST_METHOD']) && $_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['dht_status_submit'])) {
$number = trim(sanitize_text_field((string) wp_unslash($_POST['dht_number'] ?? '')));
$email = sanitize_email((string) wp_unslash($_POST['dht_status_email'] ?? ''));
if (isset($_POST['dht_status_website']) && trim((string) wp_unslash($_POST['dht_status_website'])) !== '') {
$error = __('Zu dieser Kombination aus Ticketnummer und E-Mail-Adresse wurde nichts gefunden.', 'deskhub-tickets');
} elseif (!preg_match('/^[A-Za-z0-9._-]{1,40}$/', $number) || !is_email($email)) {
$error = __('Bitte geben Sie Ihre Ticketnummer und die E-Mail-Adresse an, mit der Sie das Ticket gesendet haben.', 'deskhub-tickets');
} elseif (!deskhub_tickets_rate_allow('status', 10, 10 * MINUTE_IN_SECONDS)) {
// Bremst das Durchprobieren von Ticketnummern
$error = __('Zu viele Abfragen. Bitte versuchen Sie es in einigen Minuten noch einmal.', 'deskhub-tickets');
} else {
$client = Deskhub_Tickets_Client::from_settings();
$res = $client ? $client->get_ticket($number, $email) : new WP_Error('deskhub_config', 'x');
if (is_wp_error($res)) {
$error = Deskhub_Tickets_Client::user_message($res);
} else {
$ticket = $res;
}
}
}
ob_start();
echo self::wrap_open('deskhub-status');
if ($error !== '') {
echo '
' . esc_html($error) . '
';
}
if ($ticket) {
echo self::render_ticket_status($ticket);
}
?>
';
return (string) ob_get_clean();
}
/** Zeigt Status und öffentlichen Verlauf eines Tickets. Alles wird maskiert; die Texte kommen vom Server als Klartext. */
public static function render_ticket_status(array $t): string
{
$labels = [
'open' => __('Offen', 'deskhub-tickets'),
'pending' => __('Wartet auf Ihre Antwort', 'deskhub-tickets'),
'answered' => __('Beantwortet', 'deskhub-tickets'),
'on_hold' => __('In Bearbeitung', 'deskhub-tickets'),
'resolved' => __('Gelöst', 'deskhub-tickets'),
'closed' => __('Geschlossen', 'deskhub-tickets'),
];
$status = (string) ($t['status'] ?? '');
$fmt = function ($s): string {
$ts = strtotime((string) $s);
return $ts ? date_i18n(get_option('date_format') . ' ' . get_option('time_format'), $ts) : '';
};
$out = '