Upload via GUI (30 Dateien)

This commit is contained in:
2026-11-08 20:09:19 +00:00
parent 4ad8fe8349
commit fe3c8fa7c5
24 changed files with 1137 additions and 650 deletions
@@ -157,7 +157,7 @@ class Deskhub_Tickets_Form
$texts = [];
foreach ($forms as $tid => $fm) {
$texts[$tid] = ['title' => $fm['title'], 'intro' => $fm['intro'], 'subjectShow' => $fm['subject']['show'], 'subjectLabel' => $fm['subject']['label'],
'messageShow' => $fm['message']['show'], 'messageLabel' => $fm['message']['label'], 'messageHint' => $fm['message']['hint'], 'submitLabel' => $fm['submitLabel']];
'messageShow' => $fm['message']['show'], 'messageLabel' => $fm['message']['label'], 'messageHint' => $fm['message']['hint'], 'submitLabel' => $fm['submitLabel'], 'attachments' => $fm['attachments']['enabled']];
}
if ($unsupported && current_user_can('manage_options')) {
echo '<p class="dht-notice dht-error">' . 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') . '</p>';
@@ -175,7 +175,7 @@ class Deskhub_Tickets_Form
$showSubject = (bool) $active['subject']['show'];
$showMessage = (bool) $active['message']['show'];
?>
<form method="post" action="<?php echo esc_url(admin_url('admin-post.php')); ?>" class="dht-form" data-dht-forms="<?php echo esc_attr((string) wp_json_encode($texts)); ?>">
<form method="post" enctype="multipart/form-data" action="<?php echo esc_url(admin_url('admin-post.php')); ?>" class="dht-form" data-dht-forms="<?php echo esc_attr((string) wp_json_encode($texts)); ?>">
<input type="hidden" name="action" value="deskhub_ticket_submit">
<input type="hidden" name="dht_cfg" value="<?php echo esc_attr($cfgB64); ?>">
<input type="hidden" name="dht_sig" value="<?php echo esc_attr(deskhub_tickets_sign('cfg|' . $cfgB64)); ?>">
@@ -219,6 +219,31 @@ class Deskhub_Tickets_Form
</p>
<?php echo self::render_slot($forms, ['issue'], $multi, $selected, $values['fields']); ?>
<?php echo self::render_slot($forms, [], $multi, $selected, $values['fields'], true); ?>
<?php
// Dateien anhängen: ein Feld für das ganze Formular; form.js blendet es aus, wenn das gewählte Thema keine Anhänge erlaubt
$attPolicy = null;
foreach ($forms as $fm) {
if (!empty($fm['attachments']['enabled'])) {
$attPolicy = $fm['attachments'];
break;
}
}
if ($attPolicy !== null):
$attOn = !empty($active['attachments']['enabled']);
?>
<p class="dht-attach"<?php echo $attOn ? '' : ' hidden'; ?>>
<label for="dht-files"><?php echo esc_html__('Dateien anhängen (optional)', 'deskhub-tickets'); ?></label>
<input type="file" id="dht-files" name="dht_files[]" multiple accept="<?php echo esc_attr('.' . implode(',.', $attPolicy['allowed'])); ?>" <?php echo $attOn ? '' : 'disabled'; ?>>
<span class="dht-help"><?php echo esc_html(sprintf(
/* translators: 1: Anzahl Dateien, 2: Größe je Datei, 3: Größe insgesamt, 4: Dateitypen */
__('Bis zu %1$d Dateien, je höchstens %2$s, zusammen höchstens %3$s. Erlaubt: %4$s', 'deskhub-tickets'),
$attPolicy['maxFiles'],
self::format_bytes($attPolicy['maxFileBytes']),
self::format_bytes($attPolicy['maxTotalBytes']),
implode(', ', $attPolicy['allowed'])
)); ?></span>
</p>
<?php endif; ?>
<?php if (trim((string) $opts['consent_text']) !== ''): ?>
<p class="dht-consent">
<label><input type="checkbox" name="dht_consent" value="1" required> <span><?php echo wp_kses((string) $opts['consent_text'], ['a' => ['href' => [], 'target' => [], 'rel' => []], 'strong' => [], 'em' => []]); ?></span></label>
@@ -247,6 +272,104 @@ class Deskhub_Tickets_Form
];
}
/**
* Angebot für Dateianhänge aus dem Formular von DeskHub. Ohne Angabe (ältere DeskHub-Version) oder wenn die Option des Plugins
* ausgeschaltet ist, gibt es keine Anhänge.
*
* @param mixed $a
* @return array{enabled:bool,maxFiles:int,maxFileBytes:int,maxTotalBytes:int,allowed:string[]}
*/
public static function normalize_attachments($a): array
{
$off = ['enabled' => false, 'maxFiles' => 0, 'maxFileBytes' => 0, 'maxTotalBytes' => 0, 'allowed' => []];
$opts = deskhub_tickets_options();
if (!is_array($a) || empty($a['enabled']) || empty($opts['attachments'])) {
return $off;
}
$allowed = [];
foreach ((array) ($a['allowedExtensions'] ?? []) as $e) {
$e = strtolower(ltrim(trim((string) $e), '.'));
if ($e !== '' && preg_match('/^[a-z0-9]{1,8}$/', $e)) {
$allowed[] = $e;
}
}
$files = max(0, min(10, (int) ($a['maxFiles'] ?? 0)));
$perFile = max(0, (int) ($a['maxFileBytes'] ?? 0));
$total = max(0, (int) ($a['maxTotalBytes'] ?? 0));
if ($files < 1 || $perFile < 1 || $total < 1 || !$allowed) {
return $off;
}
return ['enabled' => true, 'maxFiles' => $files, 'maxFileBytes' => $perFile, 'maxTotalBytes' => $total, 'allowed' => $allowed];
}
/** Menschenlesbare Größe ("5 MB"). */
private static function format_bytes(int $b): string
{
return $b >= 1048576 ? rtrim(rtrim(number_format($b / 1048576, 1, ',', ''), '0'), ',') . ' MB' : max(1, (int) round($b / 1024)) . ' KB';
}
/**
* Prüft die hochgeladenen Dateien gegen das Angebot und gibt sie fertig für die Schnittstelle zurück.
*
* @param array<string,mixed> $files $_FILES['dht_files']
* @return array{0:string[],1:array<int,array{name:string,data:string}>} [Fehler, Anhänge]
*/
public static function collect_attachments(array $files, array $policy): array
{
$names = isset($files['name']) ? (array) $files['name'] : [];
$errors = [];
$out = [];
$total = 0;
$dangerous = ['php', 'php3', 'php4', 'php5', 'php7', 'php8', 'phtml', 'phar', 'exe', 'bat', 'cmd', 'sh', 'js', 'html', 'htm', 'svg'];
foreach ($names as $i => $rawName) {
$err = (int) ($files['error'][$i] ?? UPLOAD_ERR_NO_FILE);
if ($err === UPLOAD_ERR_NO_FILE) {
continue; // nichts ausgewählt
}
$name = sanitize_file_name(wp_unslash((string) $rawName));
if (!$policy['enabled']) {
return [[__('Bei diesem Formular können keine Dateien angehängt werden.', 'deskhub-tickets')], []];
}
if (count($out) >= $policy['maxFiles']) {
$errors[] = sprintf(__('Bitte hängen Sie höchstens %d Dateien an.', 'deskhub-tickets'), $policy['maxFiles']);
break;
}
if ($err !== UPLOAD_ERR_OK) {
$errors[] = ($err === UPLOAD_ERR_INI_SIZE || $err === UPLOAD_ERR_FORM_SIZE)
? sprintf(__('Die Datei „%s“ ist zu groß (höchstens %s).', 'deskhub-tickets'), $name, self::format_bytes($policy['maxFileBytes']))
: sprintf(__('Die Datei „%s“ konnte nicht hochgeladen werden.', 'deskhub-tickets'), $name);
continue;
}
$ext = strtolower(pathinfo($name, PATHINFO_EXTENSION));
if ($name === '' || $ext === '' || in_array($ext, $dangerous, true) || !in_array($ext, $policy['allowed'], true)) {
$errors[] = sprintf(__('Der Dateityp von „%1$s“ ist nicht erlaubt. Erlaubt: %2$s', 'deskhub-tickets'), $name, implode(', ', $policy['allowed']));
continue;
}
$size = (int) ($files['size'][$i] ?? 0);
$tmp = (string) ($files['tmp_name'][$i] ?? '');
if ($size < 1 || $tmp === '' || !(is_uploaded_file($tmp) || (defined('DESKHUB_TICKETS_TESTS') && is_file($tmp)))) {
$errors[] = sprintf(__('Die Datei „%s“ konnte nicht gelesen werden.', 'deskhub-tickets'), $name);
continue;
}
if ($size > $policy['maxFileBytes']) {
$errors[] = sprintf(__('Die Datei „%1$s“ ist zu groß (höchstens %2$s).', 'deskhub-tickets'), $name, self::format_bytes($policy['maxFileBytes']));
continue;
}
$total += $size;
if ($total > $policy['maxTotalBytes']) {
$errors[] = sprintf(__('Die Dateien sind zusammen zu groß (höchstens %s).', 'deskhub-tickets'), self::format_bytes($policy['maxTotalBytes']));
break;
}
$content = file_get_contents($tmp);
if ($content === false || $content === '') {
$errors[] = sprintf(__('Die Datei „%s“ konnte nicht gelesen werden.', 'deskhub-tickets'), $name);
continue;
}
$out[] = ['name' => $name, 'data' => base64_encode($content)];
}
return [$errors, $out];
}
/** Bringt ein Formular von DeskHub (oder null) in eine feste Form mit Standardwerten, Feldern nach Plätzen sortiert. */
public static function normalize_form(?array $f): array
{
@@ -268,6 +391,7 @@ class Deskhub_Tickets_Form
],
'submitLabel' => $own ? $str($f['submitLabel'] ?? '', __('Ticket absenden', 'deskhub-tickets')) : __('Ticket absenden', 'deskhub-tickets'),
'fields' => [],
'attachments' => self::normalize_attachments($f['attachments'] ?? null),
];
$allowed = ['text', 'textarea', 'select', 'radio', 'checkbox', 'number', 'date'];
foreach ((array) ($f['fields'] ?? []) as $fd) {
@@ -528,6 +652,13 @@ class Deskhub_Tickets_Form
[$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');
}
@@ -563,6 +694,9 @@ class Deskhub_Tickets_Form
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)) {