Files
nat-as-server/handlers/client_export.php
2026-05-22 13:26:29 +02:00

179 lines
6.1 KiB
PHP

<?php
require_once __DIR__ . '/../lib/activity.php';
$tokenRec = require_valid_token();
require_role(['admin', 'supervisor'], $tokenRec);
if ($method !== 'GET') {
json_error('METHOD_NOT_ALLOWED', 'Method not allowed', 405);
}
$clientCode = trim($_GET['clientCode'] ?? '');
$exportAll = (strpos($route, 'export_all') !== false) || isset($_GET['export_all']);
[$pdo, $tmpDb, $lockFp] = qdb_open(false);
[$rbacClause, $rbacParams] = rbac_client_filter($tokenRec, 'cl');
$clientFilter = '';
$params = $rbacParams;
if (!$exportAll) {
if ($clientCode === '') {
qdb_discard($tmpDb, $lockFp);
json_error('MISSING_PARAM', 'clientCode query param required', 400);
}
$clientFilter = ' AND cl.clientCode = :cc';
$params[':cc'] = $clientCode;
}
$sql = "
SELECT cl.clientCode, cl.coachID,
co.username AS coachUsername,
sv.username AS supervisorUsername
FROM client cl
LEFT JOIN coach co ON co.coachID = cl.coachID
LEFT JOIN supervisor sv ON sv.supervisorID = co.supervisorID
WHERE ($rbacClause) $clientFilter
ORDER BY cl.clientCode
";
$stmt = $pdo->prepare($sql);
$stmt->execute($params);
$clients = $stmt->fetchAll(PDO::FETCH_ASSOC);
if (!$exportAll && empty($clients)) {
qdb_discard($tmpDb, $lockFp);
json_error('NOT_FOUND', 'Client not found or not authorized', 404);
}
$subStmt = $pdo->prepare("
SELECT qs.submissionID, qs.clientCode, qs.questionnaireID, qs.submissionVersion,
qs.status, qs.startedAt, qs.completedAt, qs.uploadedAt, qs.sumPoints,
qs.submittedByRole, qn.name AS questionnaireName
FROM questionnaire_submission qs
JOIN questionnaire qn ON qn.questionnaireID = qs.questionnaireID
WHERE qs.clientCode = :cc
ORDER BY qs.uploadedAt, qs.submissionVersion
");
$ansStmt = $pdo->prepare("
SELECT sa.questionID, sa.answerOptionID, sa.freeTextValue, sa.numericValue,
q.defaultText AS questionText, ao.defaultText AS optionText
FROM submission_answer sa
JOIN question q ON q.questionID = sa.questionID
LEFT JOIN answer_option ao ON ao.answerOptionID = sa.answerOptionID
WHERE sa.submissionID = :sid
ORDER BY q.orderIndex
");
$rows = [];
foreach ($clients as $cl) {
$cc = $cl['clientCode'];
$subStmt->execute([':cc' => $cc]);
foreach ($subStmt->fetchAll(PDO::FETCH_ASSOC) as $sub) {
$ansStmt->execute([':sid' => $sub['submissionID']]);
$answers = $ansStmt->fetchAll(PDO::FETCH_ASSOC);
if (empty($answers)) {
$rows[] = build_export_row($cl, $sub, null);
continue;
}
foreach ($answers as $a) {
$rows[] = build_export_row($cl, $sub, $a);
}
}
}
qdb_discard($tmpDb, $lockFp);
try {
[$pdoW, $tmpDbW, $lockFpW] = qdb_open(true);
log_activity(
$pdoW,
$tokenRec,
'client_data_exported',
'client',
$exportAll ? '*' : $clientCode,
$exportAll ? 'Exported all client data' : "Exported data for $clientCode",
['exportAll' => $exportAll, 'clientCode' => $exportAll ? null : $clientCode, 'rowCount' => count($rows)]
);
qdb_save($tmpDbW, $lockFpW);
} catch (Throwable $e) {
if (isset($tmpDbW, $lockFpW)) qdb_discard($tmpDbW, $lockFpW);
error_log('Activity log failed on export: ' . $e->getMessage());
}
$filename = $exportAll
? 'clients_export_' . date('Y-m-d') . '.csv'
: 'client_' . preg_replace('/[^a-zA-Z0-9_-]/', '_', $clientCode) . '_export.csv';
header('Content-Type: text/csv; charset=UTF-8');
header('Content-Disposition: attachment; filename="' . $filename . '"');
echo "\xEF\xBB\xBF";
$out = fopen('php://output', 'w');
$headers = [
'clientCode', 'coach', 'supervisor', 'questionnaireID', 'questionnaireName',
'submissionVersion', 'submissionID', 'status', 'sumPoints',
'startedAt', 'completedAt', 'uploadedAt', 'submittedByRole',
'questionText', 'answerOption', 'freeTextValue', 'numericValue',
];
fputcsv($out, $headers);
foreach ($rows as $r) {
fputcsv($out, [
$r['clientCode'],
$r['coach'],
$r['supervisor'],
$r['questionnaireID'],
$r['questionnaireName'],
$r['submissionVersion'],
$r['submissionID'],
$r['status'],
$r['sumPoints'],
$r['startedAt'],
$r['completedAt'],
$r['uploadedAt'],
$r['submittedByRole'],
$r['questionText'],
$r['answerOption'],
$r['freeTextValue'],
$r['numericValue'],
]);
}
fclose($out);
exit;
function build_export_row(array $cl, array $sub, ?array $a): array {
$fmt = static function ($ms) {
if ($ms === null || $ms === '') return '';
return date('Y-m-d H:i', (int)($ms / 1000));
};
$answerVal = '';
if ($a) {
if (!empty($a['optionText'])) {
$answerVal = $a['optionText'];
} elseif ($a['freeTextValue'] !== null && $a['freeTextValue'] !== '') {
$answerVal = $a['freeTextValue'];
} elseif ($a['numericValue'] !== null) {
$answerVal = (string)$a['numericValue'];
}
}
return [
'clientCode' => $cl['clientCode'],
'coach' => $cl['coachUsername'] ?? $cl['coachID'] ?? '',
'supervisor' => $cl['supervisorUsername'] ?? '',
'questionnaireID' => $sub['questionnaireID'],
'questionnaireName' => $sub['questionnaireName'] ?? '',
'submissionVersion' => (int)$sub['submissionVersion'],
'submissionID' => $sub['submissionID'],
'status' => $sub['status'],
'sumPoints' => $sub['sumPoints'] !== null ? (int)$sub['sumPoints'] : '',
'startedAt' => $fmt($sub['startedAt']),
'completedAt' => $fmt($sub['completedAt']),
'uploadedAt' => $fmt($sub['uploadedAt']),
'submittedByRole' => $sub['submittedByRole'] ?? '',
'questionText' => $a['questionText'] ?? '',
'answerOption' => $answerVal,
'freeTextValue' => $a['freeTextValue'] ?? '',
'numericValue' => $a['numericValue'] !== null ? $a['numericValue'] : '',
];
}