197 lines
6.1 KiB
PHP
197 lines
6.1 KiB
PHP
|
|
<?php
|
||
|
|
declare(strict_types=1);
|
||
|
|
|
||
|
|
require_once dirname(dirname(__DIR__)) . '/bootstrap.php';
|
||
|
|
|
||
|
|
if (!function_exists('task_cert_csv_safe_slug')) {
|
||
|
|
function task_cert_csv_safe_slug(string $value): string
|
||
|
|
{
|
||
|
|
$value = strtolower(trim($value));
|
||
|
|
$value = preg_replace('/[^a-z0-9_-]+/', '-', $value) ?? '';
|
||
|
|
$value = trim($value, '-_');
|
||
|
|
return $value !== '' ? $value : 'export';
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
if (TaskCertRequest::method() !== 'GET') {
|
||
|
|
TaskCertResponse::error('Methode nicht erlaubt', 405, false);
|
||
|
|
}
|
||
|
|
|
||
|
|
$auth = TaskCertAuthContext::fromGlobals();
|
||
|
|
if (!$auth->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);
|
||
|
|
}
|