hasPermission('r-task-cert-admin')) { TaskCertResponse::error('Keine Berechtigung', 403, false); } $csrfToken = TaskCertRequest::queryString('csrf_token', ''); if (!TaskCertCsrf::validate($csrfToken)) { TaskCertResponse::error('CSRF validation failed', 419, false); } $taskId = TaskCertRequest::queryInt('id', 0); if ($taskId <= 0) { TaskCertResponse::error('Ungültige Aufgaben-ID.', 400, false); } $queryMode = TaskCertRequest::queryString('mode', ''); $queryCycleKey = TaskCertRequest::queryString('cycle_key', ''); try { $services = task_cert_services(); $taskService = $services['taskDefinitionService'] ?? null; $monitoringService = $services['taskMonitoringService'] ?? null; if (!$taskService instanceof TaskDefinitionService) { throw new RuntimeException('TaskDefinitionService konnte nicht geladen werden.'); } if (!$monitoringService instanceof TaskMonitoringService) { throw new RuntimeException('TaskMonitoringService konnte nicht geladen werden.'); } $task = $taskService->getTaskDetail($taskId); if ($task === null) { TaskCertResponse::error('Aufgabe nicht gefunden.', 404, false); } $export = $monitoringService->buildAnswersExport($task, [ 'mode' => $queryMode, 'cycle_key' => $queryCycleKey, ]); $mode = trim((string) ($export['mode'] ?? 'cycle')); if (!in_array($mode, ['cycle', 'snapshot'], true)) { $mode = 'cycle'; } $selectedCycleKey = trim((string) ($export['cycle_key'] ?? '')); $rows = is_array($export['rows'] ?? null) ? $export['rows'] : []; $taskTitle = trim((string) ($task['title'] ?? 'Aufgabe')); $filenameCyclePart = $mode === 'snapshot' ? 'snapshot' : ('cycle-' . task_cert_csv_safe_slug($selectedCycleKey !== '' ? $selectedCycleKey : 'none')); $filename = sprintf( 'task-answers-%d-%s-%s.csv', $taskId, $filenameCyclePart, date('Ymd_His') ); while (ob_get_level() > 0) { ob_end_clean(); } http_response_code(200); header('Content-Type: text/csv; charset=utf-8'); header('Content-Disposition: attachment; filename="' . $filename . '"'); header('Cache-Control: no-store, no-cache, must-revalidate'); header('Pragma: no-cache'); header('Expires: 0'); header('X-Content-Type-Options: nosniff'); $output = fopen('php://output', 'wb'); if (!is_resource($output)) { throw new RuntimeException('CSV-Ausgabe konnte nicht initialisiert werden.'); } // UTF-8 BOM for Excel on German locale systems. fwrite($output, "\xEF\xBB\xBF"); $header = [ 'task_id', 'task_title', 'export_mode', 'selected_cycle_key', 'row_cycle_key', 'submission_id', 'assignment_id', 'contact_id', 'contact_name', 'contact_email', 'method_type', 'method_label', 'submission_status', 'assignment_status', 'submitted_at', 'due_at', 'overdue_at', 'completed_at', 'escalation_level', 'score', 'max_score', 'review_note', 'answer_summary', 'answer_detail_json', ]; fputcsv($output, $header, ';'); $toCsvCell = static function ($value): string { if ($value === null) { return ''; } if (is_bool($value)) { return $value ? '1' : '0'; } if (is_int($value) || is_float($value)) { return (string) $value; } if (is_array($value)) { $value = json_encode($value, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); if ($value === false) { $value = ''; } } $stringValue = (string) $value; $trimmed = ltrim($stringValue); if ($trimmed !== '') { $firstChar = substr($trimmed, 0, 1); if (in_array($firstChar, ['=', '+', '-', '@'], true)) { $stringValue = "'" . $stringValue; } } return $stringValue; }; foreach ($rows as $row) { $row = is_array($row) ? $row : []; $line = [ $taskId, $taskTitle, $mode, $selectedCycleKey, (string) ($row['cycle_key'] ?? ''), (int) ($row['submission_id'] ?? 0), (int) ($row['assignment_id'] ?? 0), (int) ($row['contact_id'] ?? 0), (string) ($row['contact_name'] ?? ''), (string) ($row['contact_email'] ?? ''), (string) ($row['method_type'] ?? ''), (string) ($row['method_label'] ?? ''), (string) ($row['submission_status'] ?? ''), (string) ($row['assignment_status'] ?? ''), (string) ($row['submitted_at'] ?? ''), (string) ($row['due_at'] ?? ''), (string) ($row['overdue_at'] ?? ''), (string) ($row['completed_at'] ?? ''), (int) ($row['escalation_level'] ?? 0), $row['score'] !== null ? (int) $row['score'] : '', $row['max_score'] !== null ? (int) $row['max_score'] : '', (string) ($row['review_note'] ?? ''), (string) ($row['answer_summary'] ?? ''), (string) ($row['answer_detail_json'] ?? '{}'), ]; $csvLine = array_map($toCsvCell, $line); fputcsv($output, $csvLine, ';'); } fclose($output); exit; } catch (Throwable $throwable) { $statusCode = $throwable instanceof InvalidArgumentException ? 400 : 500; TaskCertResponse::error($throwable->getMessage(), $statusCode, false); }