Files
awo-hamburg-intranet/module/tasks/endpoints/admin/download_submission_file.php
Moritz Weinmann 743476f64a Module tasks + knowledgecenter_update aus Kundenprojekt übernehmen (bereinigt)
Beide Module wurden aus einem anderen Kundenprojekt kopiert und bereinigt:

Knowledgecenter:
- Bild-Cache (1.985 Dateien) geleert, Ordnerstruktur mit .gitkeep behalten
- Files-Gallery-Lizenzschlüssel entfernt (config.php)
- Kundenspezifische Kategorien gelöscht: Teil I/II/III QM-Struktur,
  nummerierte QM-Kapitel, Gremien (Präsidium/Finanzausschuss/Landesausschuss),
  Sitzungsservice-Serie, Protokoll-Abteilungen, Testeinträge
- 56 generische Kategorien bleiben (Downloads, Onboarding, Personalthemen …)

Tasks:
- Alle Betriebsdaten gelöscht (114 Tasks, 914 Submissions, 579 Assignments)
- Task-Definitionen und Verlinkungen geleert
- Kategoriestruktur bleibt (Allgemein, IT, Personal, Buchhaltung …)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-22 09:20:02 +02:00

100 lines
3.7 KiB
PHP

<?php
declare(strict_types=1);
require_once dirname(dirname(__DIR__)) . '/bootstrap.php';
if (TaskCertRequest::method() !== 'GET') {
TaskCertResponse::error('Methode nicht erlaubt', 405, false);
}
$auth = TaskCertAuthContext::fromGlobals();
if (!$auth->isLoggedIn()) {
TaskCertResponse::error('Nicht eingeloggt', 401, false);
}
$csrfToken = TaskCertRequest::queryString('csrf_token', '');
if (!TaskCertCsrf::validate($csrfToken)) {
TaskCertResponse::error('CSRF validation failed', 419, false);
}
$submissionId = TaskCertRequest::queryInt('submission_id', 0);
$fieldKey = TaskCertRequest::queryString('field_key', '');
$index = TaskCertRequest::queryInt('index', -1);
if ($submissionId <= 0 || $fieldKey === '' || $index < 0) {
TaskCertResponse::error('Ungültige Parameter.', 400, false);
}
try {
$services = task_cert_services();
$submission = $services['submissionRepo']->findById($submissionId);
if ($submission === null) {
TaskCertResponse::error('Einreichung nicht gefunden.', 404, false);
}
// Zugriff: Admin ODER Eigentümer der Einreichung.
$isAdmin = $auth->hasPermission('r-task-cert-admin');
$isOwner = (int) ($submission['main_contact_id'] ?? 0) === $auth->contactId();
if (!$isAdmin && !$isOwner) {
TaskCertResponse::error('Keine Berechtigung', 403, false);
}
$payload = is_array($submission['payload'] ?? null) ? $submission['payload'] : [];
$formValues = is_array($payload['form_values'] ?? null) ? $payload['form_values'] : [];
$fieldFiles = is_array($formValues[$fieldKey] ?? null) ? $formValues[$fieldKey] : [];
$fileMeta = is_array($fieldFiles[$index] ?? null) ? $fieldFiles[$index] : null;
if ($fileMeta === null || !isset($fileMeta['file_path'])) {
TaskCertResponse::error('Datei nicht gefunden.', 404, false);
}
$absolutePath = TaskUploadStorage::absolutePath((string) $fileMeta['file_path']);
if ($absolutePath === null || !is_file($absolutePath)) {
TaskCertResponse::error('Datei nicht gefunden.', 404, false);
}
$fileName = (string) ($fileMeta['file_name'] ?? 'datei');
// Content-Type IMMER aus der (erlaubten) Endung ableiten — nie aus dem
// gespeicherten/Client-MIME (schützt auch Alt-Datensätze vor Stored-XSS,
// z. B. als Bild getarntes SVG).
$mime = TaskUploadPolicy::mimeForFilename($fileName);
// RFC-5987: ASCII-Fallback für alte Clients + UTF-8-URL-encoded für moderne.
$asciiFallback = preg_replace('/[^\x20-\x7E]+/', '_', $fileName) ?: 'datei';
// Inline nur für eine feste, sichere MIME-Menge (kein SVG/HTML/XML),
// alles andere als Download.
$disposition = TaskUploadPolicy::isInlineSafeMime($mime) ? 'inline' : 'attachment';
if (!headers_sent()) {
http_response_code(200);
header('Content-Type: ' . $mime);
header(sprintf(
'Content-Disposition: %s; filename="%s"; filename*=UTF-8\'\'%s',
$disposition,
addcslashes($asciiFallback, '"\\'),
rawurlencode($fileName)
));
header('Content-Length: ' . (int) filesize($absolutePath));
header('X-Content-Type-Options: nosniff');
header('X-Frame-Options: SAMEORIGIN');
header('Cache-Control: private, max-age=0, must-revalidate');
}
while (ob_get_level() > 0) {
ob_end_clean();
}
readfile($absolutePath);
exit;
} catch (Throwable $throwable) {
TaskCertLogger::error('Submission-Datei-Download fehlgeschlagen', [
'submission_id' => $submissionId,
'field_key' => $fieldKey,
'index' => $index,
'exception' => $throwable,
]);
TaskCertResponse::error('Download fehlgeschlagen.', 500, false);
}