179 lines
6.1 KiB
PHP
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'] : '',
|
|
];
|
|
}
|