Files
awo-hamburg-intranet/module/tasks/endpoints/admin/export_answers_csv.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

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);
}