Upload via GUI (364 Dateien)
This commit is contained in:
@@ -49,8 +49,138 @@ function api_key_verify(PDO $pdo, string $key, string $ip): ?array
|
||||
return $row;
|
||||
}
|
||||
|
||||
// ── Ticket über die öffentliche API anlegen ─────────────────────────────────
|
||||
// ── Kundenformular für Fremdsysteme (eigene Felder, Formular der Abteilung) ──────────────────
|
||||
|
||||
/**
|
||||
* Das Kundenformular für ein Hilfethema oder eine Abteilung, wie es ticket_new.php zeigt: Hat die Abteilung ein eigenes Formular,
|
||||
* gelten dessen Texte und Felder; sonst die allgemeinen Felder und die des Hilfethemas.
|
||||
*
|
||||
* @return array{topicId:?int,departmentId:?int,own:?array,fields:array}
|
||||
* @throws AdminModelException wenn das Hilfethema nicht existiert
|
||||
*/
|
||||
function public_form_definition(PDO $pdo, ?int $topicId, ?int $deptId): array
|
||||
{
|
||||
if ($topicId !== null) {
|
||||
$t = $pdo->prepare('SELECT * FROM help_topics WHERE id = ? AND is_active = 1');
|
||||
$t->execute([$topicId]);
|
||||
$topic = $t->fetch();
|
||||
if (!$topic) throw new AdminModelException('Das Hilfethema existiert nicht oder ist deaktiviert.', 'INVALID_TOPIC');
|
||||
$deptId = $deptId ?? (int) $topic['department_id'];
|
||||
}
|
||||
$own = $deptId ? department_form($pdo, $deptId) : null;
|
||||
if ($own) {
|
||||
$fields = $own['fields'];
|
||||
} else {
|
||||
$all = $pdo->query('SELECT * FROM custom_fields WHERE is_active = 1 AND department_id IS NULL ORDER BY sort_order, label')->fetchAll();
|
||||
$fields = array_values(array_filter($all, fn(array $f) => $f['help_topic_id'] === null || ($topicId !== null && (int) $f['help_topic_id'] === $topicId)));
|
||||
}
|
||||
return ['topicId' => $topicId, 'departmentId' => $deptId, 'own' => $own, 'fields' => $fields];
|
||||
}
|
||||
|
||||
/** Die Felder eines Formulars für die API-Antwort. Hinweistexte kommen auch als sicheres HTML (Links mit absoluter Adresse). */
|
||||
function public_form_fields_payload(array $fields): array
|
||||
{
|
||||
$base = base_url();
|
||||
$out = [];
|
||||
foreach ($fields as $f) {
|
||||
$type = in_array($f['field_type'], ['text', 'textarea', 'select', 'radio', 'checkbox', 'number', 'date'], true) ? $f['field_type'] : 'text';
|
||||
$options = [];
|
||||
if (in_array($type, ['select', 'radio'], true)) {
|
||||
foreach (preg_split('/\r?\n/', trim((string) ($f['options'] ?? ''))) ?: [] as $o) {
|
||||
$o = trim($o);
|
||||
if ($o !== '') $options[] = $o;
|
||||
}
|
||||
}
|
||||
$help = (string) ($f['help_text'] ?? '');
|
||||
$html = $help !== '' ? str_replace('href="/', 'href="' . $base . '/', form_help_html($help, (string) $f['label'])) : '';
|
||||
$out[] = [
|
||||
'id' => (int) $f['id'],
|
||||
'label' => (string) $f['label'],
|
||||
'type' => $type,
|
||||
'required' => !empty($f['is_required']),
|
||||
'options' => $options,
|
||||
'help' => $help,
|
||||
'helpHtml' => $html,
|
||||
'width' => in_array($f['form_width'] ?? '', ['half', 'third', 'twothirds'], true) ? $f['form_width'] : 'full',
|
||||
'section' => (string) ($f['_section'] ?? 'extra'),
|
||||
'sectionTitle' => (string) ($f['_section_title'] ?? ''),
|
||||
'radioInline' => !empty($f['radio_inline']),
|
||||
];
|
||||
}
|
||||
return $out;
|
||||
}
|
||||
|
||||
/** Das ganze Formular für die API-Antwort (Texte und Felder). */
|
||||
function public_form_payload(array $def): array
|
||||
{
|
||||
$o = $def['own'];
|
||||
return [
|
||||
'helpTopicId' => $def['topicId'],
|
||||
'departmentId' => $def['departmentId'],
|
||||
'ownForm' => $o !== null,
|
||||
'title' => (string) ($o['title'] ?? ''),
|
||||
'intro' => (string) ($o['intro'] ?? ''),
|
||||
'subject' => ['show' => $o ? (bool) $o['subject_show'] : true, 'label' => (string) ($o['subject_label'] ?? 'Betreff')],
|
||||
'message' => ['show' => $o ? (bool) ($o['message_show'] ?? true) : true, 'label' => (string) ($o['message_label'] ?? 'Beschreibung'),
|
||||
'hint' => (string) ($o['message_hint'] ?? 'Beschreiben Sie Ihr Anliegen so detailliert wie möglich…')],
|
||||
'submitLabel' => (string) ($o['submit_label'] ?? 'Ticket erstellen'),
|
||||
'fields' => public_form_fields_payload($def['fields']),
|
||||
];
|
||||
}
|
||||
|
||||
/** Wert eines Feldes aus der Anfrage, in der Form, wie ticket_new.php ihn speichert (Checkbox: "1" oder "0"). */
|
||||
function public_form_field_value(array $f, array $input): string
|
||||
{
|
||||
$raw = $input[(int) $f['id']] ?? $input[(string) $f['id']] ?? null;
|
||||
if ($f['field_type'] === 'checkbox') {
|
||||
$norm = is_string($raw) ? strtolower(trim($raw)) : $raw;
|
||||
return in_array($norm, [true, 1, '1', 'on', 'yes', 'ja', 'true'], true) ? '1' : '0';
|
||||
}
|
||||
return is_scalar($raw) ? trim((string) $raw) : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Prüft die gelieferten Feldwerte gegen das Formular (Pflichtfelder, Auswahlwerte, Zahl, Datum) wie ticket_new.php.
|
||||
*
|
||||
* @return array<int,string> Feld-ID => Wert
|
||||
* @throws AdminModelException mit Code INVALID_FIELD (die Meldung ist für den Besucher gedacht)
|
||||
*/
|
||||
function public_form_validate_fields(array $fields, array $input): array
|
||||
{
|
||||
$values = [];
|
||||
foreach ($fields as $f) {
|
||||
$val = public_form_field_value($f, $input);
|
||||
$label = (string) $f['label'];
|
||||
$type = $f['field_type'];
|
||||
if (mb_strlen($val) > 10000) throw new AdminModelException('Die Angabe bei „' . $label . '“ ist zu lang.', 'INVALID_FIELD');
|
||||
if (!empty($f['is_required']) && $type !== 'checkbox' && $val === '') throw new AdminModelException('Bitte füllen Sie das Feld „' . $label . '“ aus.', 'INVALID_FIELD');
|
||||
if (!empty($f['is_required']) && $type === 'checkbox' && $val !== '1') throw new AdminModelException('Bitte bestätigen Sie „' . $label . '“.', 'INVALID_FIELD');
|
||||
if (in_array($type, ['select', 'radio'], true) && $val !== '' && !in_array($val, array_map('trim', preg_split('/\r?\n/', (string) $f['options']) ?: []), true)) {
|
||||
throw new AdminModelException('Bitte wählen Sie bei „' . $label . '“ eine der angebotenen Möglichkeiten.', 'INVALID_FIELD');
|
||||
}
|
||||
if ($type === 'number' && $val !== '' && !is_numeric($val)) throw new AdminModelException('Bitte geben Sie bei „' . $label . '“ eine Zahl an.', 'INVALID_FIELD');
|
||||
if ($type === 'date' && $val !== '' && !(preg_match('/^(\d{4})-(\d{2})-(\d{2})$/', $val, $d) && checkdate((int) $d[2], (int) $d[3], (int) $d[1]))) {
|
||||
throw new AdminModelException('Bitte geben Sie bei „' . $label . '“ ein gültiges Datum an (JJJJ-MM-TT).', 'INVALID_FIELD');
|
||||
}
|
||||
$values[(int) $f['id']] = $val;
|
||||
}
|
||||
return $values;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ist der Schlüssel gültig, wird aber nur wegen der IP-Beschränkung abgelehnt? So kann die API dem Aufrufer sagen, welche Adresse
|
||||
* DeskHub sieht (hinter einem Proxy oder Cloudflare ist das oft nicht die erwartete). Verrät nichts, was der Aufrufer mit dem Schlüssel nicht ohnehin weiß.
|
||||
*/
|
||||
function api_key_ip_blocked(PDO $pdo, string $key, string $ip): bool
|
||||
{
|
||||
if (!preg_match('/^dh_[a-f0-9]{48}$/', $key)) return false;
|
||||
$stmt = $pdo->prepare('SELECT ip_whitelist FROM api_keys WHERE key_hash = ? AND is_active = 1');
|
||||
$stmt->execute([hash('sha256', $key)]);
|
||||
$row = $stmt->fetch();
|
||||
return $row && !empty($row['ip_whitelist']) && !in_array($ip, explode(',', $row['ip_whitelist']), true);
|
||||
}
|
||||
|
||||
// ── Ticket über die öffentliche API anlegen ─────────────────────────────────
|
||||
/**
|
||||
* Legt ein Ticket für einen Fremdsystem-Aufruf an (Quelle "api"), wendet Ticket-Regeln und SLA-Pläne an,
|
||||
* benachrichtigt Mitarbeiter und Kunde und löst den Webhook "ticket.created" aus.
|
||||
@@ -64,11 +194,33 @@ function public_ticket_create(PDO $pdo, array $in, string $clientIp, array $opts
|
||||
$email = trim((string) ($in['email'] ?? ''));
|
||||
$subject = trim((string) ($in['subject'] ?? ''));
|
||||
$message = trim((string) ($in['message'] ?? ''));
|
||||
// Mit "fields" liefert der Aufrufer die Angaben eines Kundenformulars (eigene Felder, Formular der Abteilung). Ohne "fields" bleibt alles wie bisher.
|
||||
$formDef = null;
|
||||
$fieldInput = [];
|
||||
if (isset($in['fields']) && is_array($in['fields'])) {
|
||||
$fieldInput = $in['fields'];
|
||||
$preTopic = am_int_or_null($in, 'helpTopicId');
|
||||
$preDept = am_int_or_null($in, 'departmentId');
|
||||
$formDef = public_form_definition($pdo, $preTopic, $preTopic === null ? ($preDept ?: (int) get_setting('default_department_id', '1')) : $preDept);
|
||||
$own = $formDef['own'];
|
||||
if ($own && !$own['subject_show'] && $subject === '') $subject = 'Anfrage ' . $own['department_name'];
|
||||
if ($own && empty($own['message_show']) && $message === '') {
|
||||
// Ohne Beschreibungsfeld besteht der Ticket-Text aus den Angaben der eigenen Felder
|
||||
$lines = [];
|
||||
foreach ($formDef['fields'] as $ff) {
|
||||
$v = public_form_field_value($ff, $fieldInput);
|
||||
if ($ff['field_type'] === 'checkbox') $v = $v === '1' ? 'Ja' : 'Nein';
|
||||
if ($v !== '') $lines[] = $ff['label'] . ': ' . $v;
|
||||
}
|
||||
$message = $lines ? implode("\n", $lines) : 'Anfrage ohne weitere Angaben';
|
||||
}
|
||||
}
|
||||
if ($name === '' || mb_strlen($name) > 120) throw new AdminModelException('Bitte einen Namen angeben (höchstens 120 Zeichen).', 'INVALID_NAME');
|
||||
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) throw new AdminModelException('Bitte eine gültige E-Mail-Adresse angeben.', 'INVALID_EMAIL');
|
||||
if ($subject === '' || mb_strlen($subject) > 255) throw new AdminModelException('Bitte einen Betreff angeben (höchstens 255 Zeichen).', 'INVALID_SUBJECT');
|
||||
if ($message === '' || mb_strlen($message) > 50000) throw new AdminModelException('Bitte eine Nachricht angeben (höchstens 50.000 Zeichen).', 'INVALID_MESSAGE');
|
||||
if (is_banned($email)) throw new AdminModelException('Diese Absenderadresse ist gesperrt.', 'BANNED', 403);
|
||||
$fieldValues = $formDef ? public_form_validate_fields($formDef['fields'], $fieldInput) : [];
|
||||
|
||||
$topicId = am_int_or_null($in, 'helpTopicId');
|
||||
$deptId = am_int_or_null($in, 'departmentId');
|
||||
@@ -110,6 +262,12 @@ function public_ticket_create(PDO $pdo, array $in, string $clientIp, array $opts
|
||||
$ticketId = (int) $pdo->lastInsertId();
|
||||
$pdo->prepare('INSERT INTO ticket_threads (ticket_id, author_type, message, is_internal_note, created_at) VALUES (?,?,?,0,?)')
|
||||
->execute([$ticketId, 'customer', $message, $now]);
|
||||
if ($fieldValues) {
|
||||
$cfStmt = $pdo->prepare('INSERT INTO ticket_field_values (ticket_id, custom_field_id, value) VALUES (?,?,?)');
|
||||
foreach ($fieldValues as $fieldId => $val) {
|
||||
if ($val !== '') $cfStmt->execute([$ticketId, $fieldId, $val]);
|
||||
}
|
||||
}
|
||||
log_activity($pdo, $ticketId, null, (string) ($opts['activity'] ?? 'Ticket über die API erstellt'));
|
||||
if (!empty($opts['tag'])) apply_tag_to_ticket($pdo, $ticketId, (string) $opts['tag']);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user