758 lines
28 KiB
PHP
758 lines
28 KiB
PHP
<?php
|
|
/**
|
|
* Dev-only fixture import: users, clients, completed questionnaires.
|
|
* All entity usernames / client codes must use the fixture prefix (default dev_).
|
|
*/
|
|
|
|
const QDB_DEV_GLASS_LABELS = ['never_glass', 'little_glass', 'moderate_glass', 'much_glass', 'extreme_glass'];
|
|
|
|
/**
|
|
* @return array{imported: array, skipped: array, errors: string[]}
|
|
*/
|
|
function qdb_import_dev_fixture(PDO $pdo, array $fixture): array
|
|
{
|
|
$prefix = qdb_dev_validate_fixture($fixture);
|
|
$password = (string)($fixture['defaultPassword'] ?? 'socialvrlab');
|
|
if (strlen($password) < 6) {
|
|
json_error('INVALID_FIELD', 'defaultPassword must be at least 6 characters', 400);
|
|
}
|
|
|
|
$imported = [
|
|
'admins' => 0,
|
|
'supervisors' => 0,
|
|
'coaches' => 0,
|
|
'clients' => 0,
|
|
'completions' => 0,
|
|
'answers' => 0,
|
|
];
|
|
$skipped = [
|
|
'admins' => 0,
|
|
'supervisors' => 0,
|
|
'coaches' => 0,
|
|
'clients' => 0,
|
|
'completions' => 0,
|
|
];
|
|
$errors = [];
|
|
|
|
$coachIdByUsername = [];
|
|
$supervisorIdByUsername = [];
|
|
|
|
$hash = password_hash($password, PASSWORD_DEFAULT);
|
|
$now = time();
|
|
|
|
$pdo->beginTransaction();
|
|
try {
|
|
foreach ($fixture['admins'] ?? [] as $row) {
|
|
$username = qdb_dev_norm_username($row, $prefix);
|
|
if (qdb_dev_user_exists($pdo, $username)) {
|
|
$skipped['admins']++;
|
|
continue;
|
|
}
|
|
$entityID = qdb_dev_entity_id('admin', $username);
|
|
$userID = qdb_dev_user_id($username);
|
|
$location = trim($row['location'] ?? 'Dev');
|
|
$pdo->prepare('INSERT INTO admin (adminID, username, location) VALUES (:id, :u, :loc)')
|
|
->execute([':id' => $entityID, ':u' => $username, ':loc' => $location]);
|
|
qdb_dev_insert_user($pdo, $userID, $username, $hash, 'admin', $entityID, $now);
|
|
$imported['admins']++;
|
|
}
|
|
|
|
foreach ($fixture['supervisors'] ?? [] as $row) {
|
|
$username = qdb_dev_norm_username($row, $prefix);
|
|
if (qdb_dev_user_exists($pdo, $username)) {
|
|
$skipped['supervisors']++;
|
|
$supervisorIdByUsername[$username] = qdb_dev_lookup_entity_id($pdo, $username);
|
|
continue;
|
|
}
|
|
$entityID = qdb_dev_entity_id('supervisor', $username);
|
|
$userID = qdb_dev_user_id($username);
|
|
$location = trim($row['location'] ?? 'Dev');
|
|
$pdo->prepare('INSERT INTO supervisor (supervisorID, username, location) VALUES (:id, :u, :loc)')
|
|
->execute([':id' => $entityID, ':u' => $username, ':loc' => $location]);
|
|
qdb_dev_insert_user($pdo, $userID, $username, $hash, 'supervisor', $entityID, $now);
|
|
$supervisorIdByUsername[$username] = $entityID;
|
|
$imported['supervisors']++;
|
|
}
|
|
|
|
foreach ($fixture['coaches'] ?? [] as $row) {
|
|
$username = qdb_dev_norm_username($row, $prefix);
|
|
$svUser = trim($row['supervisor'] ?? $row['supervisorUsername'] ?? '');
|
|
if ($svUser === '' || !qdb_dev_has_prefix($svUser, $prefix)) {
|
|
$errors[] = "Coach $username: missing or invalid supervisor";
|
|
continue;
|
|
}
|
|
$supervisorID = $supervisorIdByUsername[$svUser]
|
|
?? qdb_dev_lookup_entity_id($pdo, $svUser);
|
|
if ($supervisorID === '') {
|
|
$errors[] = "Coach $username: supervisor $svUser not found";
|
|
continue;
|
|
}
|
|
if (qdb_dev_user_exists($pdo, $username)) {
|
|
$skipped['coaches']++;
|
|
$coachIdByUsername[$username] = qdb_dev_lookup_entity_id($pdo, $username);
|
|
continue;
|
|
}
|
|
$entityID = qdb_dev_entity_id('coach', $username);
|
|
$userID = qdb_dev_user_id($username);
|
|
$pdo->prepare('INSERT INTO coach (coachID, supervisorID, username) VALUES (:id, :sid, :u)')
|
|
->execute([':id' => $entityID, ':sid' => $supervisorID, ':u' => $username]);
|
|
qdb_dev_insert_user($pdo, $userID, $username, $hash, 'coach', $entityID, $now);
|
|
$coachIdByUsername[$username] = $entityID;
|
|
$imported['coaches']++;
|
|
}
|
|
|
|
foreach ($fixture['clients'] ?? [] as $row) {
|
|
$clientCode = qdb_dev_norm_client_code($row, $prefix);
|
|
$coachUser = trim($row['coach'] ?? $row['coachUsername'] ?? '');
|
|
if ($coachUser === '' || !qdb_dev_has_prefix($coachUser, $prefix)) {
|
|
$errors[] = "Client $clientCode: missing or invalid coach";
|
|
continue;
|
|
}
|
|
$coachID = $coachIdByUsername[$coachUser]
|
|
?? qdb_dev_lookup_entity_id($pdo, $coachUser);
|
|
if ($coachID === '') {
|
|
$errors[] = "Client $clientCode: coach $coachUser not found";
|
|
continue;
|
|
}
|
|
$chk = $pdo->prepare('SELECT 1 FROM client WHERE clientCode = :cc');
|
|
$chk->execute([':cc' => $clientCode]);
|
|
if ($chk->fetch()) {
|
|
$skipped['clients']++;
|
|
continue;
|
|
}
|
|
$pdo->prepare('INSERT INTO client (clientCode, coachID) VALUES (:cc, :cid)')
|
|
->execute([':cc' => $clientCode, ':cid' => $coachID]);
|
|
$imported['clients']++;
|
|
}
|
|
|
|
$variant = 0;
|
|
foreach ($fixture['completions'] ?? [] as $row) {
|
|
$clientCode = qdb_dev_norm_client_code($row, $prefix);
|
|
$qnID = trim($row['questionnaireID'] ?? '');
|
|
if ($qnID === '') {
|
|
$errors[] = 'Completion missing questionnaireID';
|
|
continue;
|
|
}
|
|
$qnChk = $pdo->prepare('SELECT 1 FROM questionnaire WHERE questionnaireID = :id');
|
|
$qnChk->execute([':id' => $qnID]);
|
|
if (!$qnChk->fetch()) {
|
|
$errors[] = "Questionnaire not in DB: $qnID";
|
|
continue;
|
|
}
|
|
$clChk = $pdo->prepare('SELECT coachID FROM client WHERE clientCode = :cc');
|
|
$clChk->execute([':cc' => $clientCode]);
|
|
$coachID = $clChk->fetchColumn();
|
|
if ($coachID === false) {
|
|
$errors[] = "Client not found: $clientCode";
|
|
continue;
|
|
}
|
|
$doneChk = $pdo->prepare(
|
|
'SELECT 1 FROM completed_questionnaire WHERE clientCode = :cc AND questionnaireID = :qn'
|
|
);
|
|
$doneChk->execute([':cc' => $clientCode, ':qn' => $qnID]);
|
|
if ($doneChk->fetch()) {
|
|
$skipped['completions']++;
|
|
continue;
|
|
}
|
|
|
|
$completedAt = isset($row['completedAt']) ? (int)$row['completedAt'] : ($now - ($variant % 90) * 86400);
|
|
$startedAt = isset($row['startedAt']) ? (int)$row['startedAt'] : ($completedAt - 600);
|
|
|
|
$answers = qdb_dev_build_answers_for_questionnaire($pdo, $qnID, $variant);
|
|
$answerCount = qdb_dev_persist_submission(
|
|
$pdo,
|
|
$qnID,
|
|
$clientCode,
|
|
(string)$coachID,
|
|
$answers,
|
|
$startedAt,
|
|
$completedAt
|
|
);
|
|
$imported['completions']++;
|
|
$imported['answers'] += $answerCount;
|
|
$variant++;
|
|
}
|
|
|
|
$pdo->commit();
|
|
} catch (Throwable $e) {
|
|
if ($pdo->inTransaction()) {
|
|
$pdo->rollBack();
|
|
}
|
|
throw $e;
|
|
}
|
|
|
|
return ['imported' => $imported, 'skipped' => $skipped, 'errors' => $errors];
|
|
}
|
|
|
|
function qdb_dev_validate_fixture(array $fixture): string
|
|
{
|
|
$prefix = trim((string)($fixture['prefix'] ?? 'dev_'));
|
|
if ($prefix === '' || !preg_match('/^dev[a-z0-9_]*$/i', $prefix)) {
|
|
json_error('INVALID_FIELD', 'fixture.prefix must start with "dev" (e.g. dev_)', 400);
|
|
}
|
|
if ((int)($fixture['fixtureVersion'] ?? 0) !== 1) {
|
|
json_error('INVALID_FIELD', 'fixtureVersion must be 1', 400);
|
|
}
|
|
return $prefix;
|
|
}
|
|
|
|
function qdb_dev_has_prefix(string $value, string $prefix): bool
|
|
{
|
|
return str_starts_with(strtolower($value), strtolower($prefix))
|
|
|| str_starts_with(strtoupper($value), strtoupper(rtrim($prefix, '_')));
|
|
}
|
|
|
|
function qdb_dev_norm_username(array $row, string $prefix): string
|
|
{
|
|
$username = trim($row['username'] ?? '');
|
|
if ($username === '' || !qdb_dev_has_prefix($username, $prefix)) {
|
|
json_error('INVALID_FIELD', "Username must use prefix $prefix", 400);
|
|
}
|
|
return $username;
|
|
}
|
|
|
|
function qdb_dev_norm_client_code(array $row, string $prefix): string
|
|
{
|
|
$code = trim($row['clientCode'] ?? '');
|
|
$upperPrefix = strtoupper(rtrim($prefix, '_'));
|
|
if ($code === '' || !str_starts_with($code, $upperPrefix)) {
|
|
json_error('INVALID_FIELD', "clientCode must start with $upperPrefix", 400);
|
|
}
|
|
return $code;
|
|
}
|
|
|
|
function qdb_dev_entity_id(string $role, string $username): string
|
|
{
|
|
return 'dev_ent_' . $role . '_' . substr(hash('sha256', $username), 0, 24);
|
|
}
|
|
|
|
function qdb_dev_user_id(string $username): string
|
|
{
|
|
return 'dev_uid_' . substr(hash('sha256', 'user:' . $username), 0, 28);
|
|
}
|
|
|
|
function qdb_dev_user_exists(PDO $pdo, string $username): bool
|
|
{
|
|
$stmt = $pdo->prepare('SELECT 1 FROM users WHERE username = :u');
|
|
$stmt->execute([':u' => $username]);
|
|
return (bool)$stmt->fetch();
|
|
}
|
|
|
|
function qdb_dev_lookup_entity_id(PDO $pdo, string $username): string
|
|
{
|
|
$stmt = $pdo->prepare('SELECT entityID FROM users WHERE username = :u');
|
|
$stmt->execute([':u' => $username]);
|
|
$id = $stmt->fetchColumn();
|
|
return $id !== false ? (string)$id : '';
|
|
}
|
|
|
|
function qdb_dev_insert_user(
|
|
PDO $pdo,
|
|
string $userID,
|
|
string $username,
|
|
string $hash,
|
|
string $role,
|
|
string $entityID,
|
|
int $now
|
|
): void {
|
|
$mcp = 0;
|
|
$pdo->prepare(
|
|
'INSERT INTO users (userID, username, passwordHash, role, entityID, mustChangePassword, createdAt)
|
|
VALUES (:uid, :u, :hash, :role, :eid, :mcp, :now)'
|
|
)->execute([
|
|
':uid' => $userID,
|
|
':u' => $username,
|
|
':hash' => $hash,
|
|
':role' => $role,
|
|
':eid' => $entityID,
|
|
':mcp' => $mcp,
|
|
':now' => $now,
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* @return list<array{questionID: string, answerOptionKey?: string, freeTextValue?: string, numericValue?: float}>
|
|
*/
|
|
function qdb_dev_build_answers_for_questionnaire(PDO $pdo, string $qnID, int $variant): array
|
|
{
|
|
$stmt = $pdo->prepare(
|
|
'SELECT questionID, type, configJson, orderIndex
|
|
FROM question WHERE questionnaireID = :qn ORDER BY orderIndex'
|
|
);
|
|
$stmt->execute([':qn' => $qnID]);
|
|
$questions = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
|
|
|
$aoStmt = $pdo->prepare(
|
|
'SELECT ao.answerOptionID, ao.questionID, ao.defaultText, ao.points, ao.orderIndex
|
|
FROM answer_option ao
|
|
JOIN question q ON q.questionID = ao.questionID
|
|
WHERE q.questionnaireID = :qn
|
|
ORDER BY ao.orderIndex'
|
|
);
|
|
$aoStmt->execute([':qn' => $qnID]);
|
|
$optionsByQuestion = [];
|
|
foreach ($aoStmt->fetchAll(PDO::FETCH_ASSOC) as $ao) {
|
|
$optionsByQuestion[$ao['questionID']][] = $ao;
|
|
}
|
|
|
|
$answers = [];
|
|
$freeTextSamples = ['Testantwort', 'Dev-Eingabe', 'Beispieltext', 'N/A'];
|
|
|
|
foreach ($questions as $q) {
|
|
$type = $q['type'] ?? '';
|
|
$localId = qdb_question_local_id($q['questionID'], $qnID);
|
|
$config = json_decode($q['configJson'] ?? '{}', true) ?: [];
|
|
$v = $variant + (int)($q['orderIndex'] ?? 0);
|
|
|
|
switch ($type) {
|
|
case 'last_page':
|
|
break;
|
|
|
|
case 'glass_scale_question':
|
|
$symData = [];
|
|
foreach ($config['symptoms'] ?? [] as $si => $symptomKey) {
|
|
$sk = trim((string)$symptomKey);
|
|
if ($sk === '') {
|
|
continue;
|
|
}
|
|
$symData[$sk] = QDB_DEV_GLASS_LABELS[($v + $si) % count(QDB_DEV_GLASS_LABELS)];
|
|
}
|
|
if ($symData !== []) {
|
|
$answers[] = [
|
|
'questionID' => $localId,
|
|
'freeTextValue' => json_encode($symData, JSON_UNESCAPED_UNICODE),
|
|
];
|
|
}
|
|
break;
|
|
|
|
case 'radio_question':
|
|
case 'client_not_signed':
|
|
$opts = $optionsByQuestion[$q['questionID']] ?? [];
|
|
if ($opts) {
|
|
$pick = $opts[$v % count($opts)];
|
|
$key = qdb_option_key($pick) ?: $pick['defaultText'];
|
|
$answers[] = ['questionID' => $localId, 'answerOptionKey' => $key];
|
|
}
|
|
break;
|
|
|
|
case 'multi_check_box_question':
|
|
$opts = $optionsByQuestion[$q['questionID']] ?? [];
|
|
if ($opts) {
|
|
$count = min(2, count($opts));
|
|
for ($i = 0; $i < $count; $i++) {
|
|
$pick = $opts[($v + $i) % count($opts)];
|
|
$key = qdb_option_key($pick) ?: $pick['defaultText'];
|
|
$answers[] = ['questionID' => $localId, 'answerOptionKey' => $key];
|
|
}
|
|
}
|
|
break;
|
|
|
|
case 'string_spinner':
|
|
$opts = $config['options'] ?? [];
|
|
if (is_array($opts) && $opts !== []) {
|
|
$answers[] = [
|
|
'questionID' => $localId,
|
|
'freeTextValue' => (string)$opts[$v % count($opts)],
|
|
];
|
|
}
|
|
break;
|
|
|
|
case 'value_spinner':
|
|
case 'slider_question':
|
|
$range = $config['range'] ?? ['min' => 0, 'max' => 10];
|
|
$min = (int)($range['min'] ?? 0);
|
|
$max = (int)($range['max'] ?? 10);
|
|
if ($max < $min) {
|
|
$max = $min;
|
|
}
|
|
$span = max(1, $max - $min + 1);
|
|
$answers[] = [
|
|
'questionID' => $localId,
|
|
'numericValue' => (float)($min + ($v % $span)),
|
|
];
|
|
break;
|
|
|
|
case 'date_spinner':
|
|
$year = 2018 + ($v % 6);
|
|
$month = 1 + ($v % 12);
|
|
$day = 1 + ($v % 28);
|
|
$answers[] = [
|
|
'questionID' => $localId,
|
|
'freeTextValue' => sprintf('%04d-%02d-%02d', $year, $month, $day),
|
|
];
|
|
break;
|
|
|
|
case 'free_text':
|
|
$answers[] = [
|
|
'questionID' => $localId,
|
|
'freeTextValue' => $freeTextSamples[$v % count($freeTextSamples)],
|
|
];
|
|
break;
|
|
|
|
case 'client_coach_code_question':
|
|
$answers[] = [
|
|
'questionID' => $localId,
|
|
'freeTextValue' => 'DEV-IMPORT',
|
|
];
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
return $answers;
|
|
}
|
|
|
|
/**
|
|
* @param list<array{questionID: string, answerOptionKey?: string, freeTextValue?: string, numericValue?: float}> $answers
|
|
*/
|
|
function qdb_dev_persist_submission(
|
|
PDO $pdo,
|
|
string $qnID,
|
|
string $clientCode,
|
|
string $assignedByCoach,
|
|
array $answers,
|
|
int $startedAt,
|
|
int $completedAt
|
|
): int {
|
|
$qRows = $pdo->prepare('SELECT questionID, type, configJson FROM question WHERE questionnaireID = :qn');
|
|
$qRows->execute([':qn' => $qnID]);
|
|
$shortIdMap = [];
|
|
$freeTextMaxLen = [];
|
|
foreach ($qRows->fetchAll(PDO::FETCH_ASSOC) as $qRow) {
|
|
$fullId = $qRow['questionID'];
|
|
$parts = explode('__', $fullId);
|
|
$shortIdMap[end($parts)] = $fullId;
|
|
if (($qRow['type'] ?? '') === 'free_text') {
|
|
$cfg = json_decode($qRow['configJson'] ?? '{}', true) ?: [];
|
|
$freeTextMaxLen[$fullId] = max(1, min((int)($cfg['maxLength'] ?? 500), 10000));
|
|
}
|
|
}
|
|
|
|
$aoRows = $pdo->prepare(
|
|
'SELECT ao.answerOptionID, ao.questionID, ao.defaultText, ao.points
|
|
FROM answer_option ao
|
|
JOIN question q ON q.questionID = ao.questionID
|
|
WHERE q.questionnaireID = :qn'
|
|
);
|
|
$aoRows->execute([':qn' => $qnID]);
|
|
$optionMap = [];
|
|
foreach ($aoRows->fetchAll(PDO::FETCH_ASSOC) as $ao) {
|
|
$optionMap[$ao['questionID']][$ao['defaultText']] = [
|
|
'answerOptionID' => $ao['answerOptionID'],
|
|
'points' => (int)$ao['points'],
|
|
];
|
|
}
|
|
|
|
$sumPoints = 0;
|
|
$written = 0;
|
|
$answeredAt = $completedAt;
|
|
$answerInsert = $pdo->prepare(
|
|
'INSERT INTO client_answer (clientCode, questionID, answerOptionID, freeTextValue, numericValue, answeredAt)
|
|
VALUES (:cc, :qid, :aoid, :ftv, :nv, :at)
|
|
ON CONFLICT(clientCode, questionID) DO UPDATE SET
|
|
answerOptionID = excluded.answerOptionID,
|
|
freeTextValue = excluded.freeTextValue,
|
|
numericValue = excluded.numericValue,
|
|
answeredAt = excluded.answeredAt'
|
|
);
|
|
|
|
foreach ($answers as $a) {
|
|
$shortId = trim($a['questionID'] ?? '');
|
|
if ($shortId === '') {
|
|
continue;
|
|
}
|
|
|
|
$fullQID = $shortIdMap[$shortId] ?? null;
|
|
if ($fullQID === null) {
|
|
continue;
|
|
}
|
|
|
|
$freeTextValue = isset($a['freeTextValue']) ? (string)$a['freeTextValue'] : null;
|
|
$numericValue = isset($a['numericValue']) ? (float)$a['numericValue'] : null;
|
|
if ($freeTextValue !== null && $freeTextValue !== '') {
|
|
$maxLen = $freeTextMaxLen[$fullQID] ?? 2000;
|
|
$freeTextValue = sanitize_free_text($freeTextValue, $maxLen);
|
|
if ($freeTextValue === '') {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
$answerOptionID = null;
|
|
$optionKey = $a['answerOptionKey'] ?? null;
|
|
if ($optionKey !== null && isset($optionMap[$fullQID][(string)$optionKey])) {
|
|
$opt = $optionMap[$fullQID][(string)$optionKey];
|
|
$answerOptionID = $opt['answerOptionID'];
|
|
$sumPoints += $opt['points'];
|
|
}
|
|
|
|
$answerInsert->execute([
|
|
':cc' => $clientCode,
|
|
':qid' => $fullQID,
|
|
':aoid' => $answerOptionID,
|
|
':ftv' => $freeTextValue,
|
|
':nv' => $numericValue,
|
|
':at' => $answeredAt,
|
|
]);
|
|
$written++;
|
|
}
|
|
|
|
$pdo->prepare(
|
|
'INSERT INTO completed_questionnaire (clientCode, questionnaireID, assignedByCoach, status, startedAt, completedAt, sumPoints)
|
|
VALUES (:cc, :qn, :abc, \'completed\', :sa, :ca, :sp)
|
|
ON CONFLICT(clientCode, questionnaireID) DO UPDATE SET
|
|
assignedByCoach = excluded.assignedByCoach,
|
|
status = \'completed\',
|
|
startedAt = COALESCE(excluded.startedAt, startedAt),
|
|
completedAt = excluded.completedAt,
|
|
sumPoints = excluded.sumPoints'
|
|
)->execute([
|
|
':cc' => $clientCode,
|
|
':qn' => $qnID,
|
|
':abc' => $assignedByCoach !== '' ? $assignedByCoach : null,
|
|
':sa' => $startedAt,
|
|
':ca' => $completedAt,
|
|
':sp' => $sumPoints,
|
|
]);
|
|
|
|
return $written;
|
|
}
|
|
|
|
/** @param list<string> $ids */
|
|
function qdb_dev_delete_by_ids(PDO $pdo, string $table, string $column, array $ids): int
|
|
{
|
|
$ids = array_values(array_unique(array_filter($ids, static fn($id) => $id !== '' && $id !== null)));
|
|
if ($ids === []) {
|
|
return 0;
|
|
}
|
|
$total = 0;
|
|
foreach (array_chunk($ids, 200) as $chunk) {
|
|
$ph = implode(',', array_fill(0, count($chunk), '?'));
|
|
$stmt = $pdo->prepare("DELETE FROM $table WHERE $column IN ($ph)");
|
|
$stmt->execute($chunk);
|
|
$total += $stmt->rowCount();
|
|
}
|
|
return $total;
|
|
}
|
|
|
|
/**
|
|
* Remove dev-prefixed test users, clients, and their answers (does not touch other accounts).
|
|
*
|
|
* @return array{deleted: array<string, int>}
|
|
*/
|
|
function qdb_remove_dev_test_data(PDO $pdo, array $options = []): array
|
|
{
|
|
$usernamePrefix = trim((string)($options['usernamePrefix'] ?? 'dev_'));
|
|
$clientPrefix = trim((string)($options['clientCodePrefix'] ?? 'DEV-CL-'));
|
|
if ($usernamePrefix === '' || stripos($usernamePrefix, 'dev') !== 0) {
|
|
json_error('INVALID_FIELD', 'usernamePrefix must start with "dev"', 400);
|
|
}
|
|
if ($clientPrefix === '' || stripos($clientPrefix, 'DEV') !== 0) {
|
|
json_error('INVALID_FIELD', 'clientCodePrefix must start with "DEV"', 400);
|
|
}
|
|
|
|
$userLike = $usernamePrefix . '%';
|
|
$clientLike = $clientPrefix . '%';
|
|
|
|
$deleted = [
|
|
'client_answers' => 0,
|
|
'completed_questionnaires' => 0,
|
|
'clients' => 0,
|
|
'sessions' => 0,
|
|
'users' => 0,
|
|
'coaches' => 0,
|
|
'supervisors' => 0,
|
|
'admins' => 0,
|
|
];
|
|
|
|
$coachIds = [];
|
|
$stmt = $pdo->prepare('SELECT coachID FROM coach WHERE username LIKE :pfx');
|
|
$stmt->execute([':pfx' => $userLike]);
|
|
foreach ($stmt->fetchAll(PDO::FETCH_COLUMN) as $id) {
|
|
$coachIds[$id] = true;
|
|
}
|
|
|
|
$supervisorIds = [];
|
|
$stmt = $pdo->prepare('SELECT supervisorID FROM supervisor WHERE username LIKE :pfx');
|
|
$stmt->execute([':pfx' => $userLike]);
|
|
foreach ($stmt->fetchAll(PDO::FETCH_COLUMN) as $id) {
|
|
$supervisorIds[$id] = true;
|
|
}
|
|
|
|
$adminIds = [];
|
|
$stmt = $pdo->prepare('SELECT adminID FROM admin WHERE username LIKE :pfx');
|
|
$stmt->execute([':pfx' => $userLike]);
|
|
foreach ($stmt->fetchAll(PDO::FETCH_COLUMN) as $id) {
|
|
$adminIds[$id] = true;
|
|
}
|
|
|
|
$userIds = [];
|
|
$userRows = $pdo->prepare('SELECT userID, role, entityID FROM users WHERE username LIKE :pfx');
|
|
$userRows->execute([':pfx' => $userLike]);
|
|
foreach ($userRows->fetchAll(PDO::FETCH_ASSOC) as $u) {
|
|
$userIds[$u['userID']] = true;
|
|
switch ($u['role']) {
|
|
case 'coach':
|
|
$coachIds[$u['entityID']] = true;
|
|
break;
|
|
case 'supervisor':
|
|
$supervisorIds[$u['entityID']] = true;
|
|
break;
|
|
case 'admin':
|
|
$adminIds[$u['entityID']] = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
$coachIdList = array_keys($coachIds);
|
|
$clientCodes = [];
|
|
$ccStmt = $pdo->prepare('SELECT clientCode FROM client WHERE clientCode LIKE :pfx');
|
|
$ccStmt->execute([':pfx' => $clientLike]);
|
|
foreach ($ccStmt->fetchAll(PDO::FETCH_COLUMN) as $code) {
|
|
$clientCodes[$code] = true;
|
|
}
|
|
if ($coachIdList !== []) {
|
|
foreach (array_chunk($coachIdList, 200) as $chunk) {
|
|
$ph = implode(',', array_fill(0, count($chunk), '?'));
|
|
$byCoach = $pdo->prepare("SELECT clientCode FROM client WHERE coachID IN ($ph)");
|
|
$byCoach->execute($chunk);
|
|
foreach ($byCoach->fetchAll(PDO::FETCH_COLUMN) as $code) {
|
|
$clientCodes[$code] = true;
|
|
}
|
|
}
|
|
}
|
|
$clientCodeList = array_keys($clientCodes);
|
|
|
|
$pdo->beginTransaction();
|
|
try {
|
|
$deleted['client_answers'] = qdb_dev_delete_by_ids($pdo, 'client_answer', 'clientCode', $clientCodeList);
|
|
|
|
if ($clientCodeList !== []) {
|
|
foreach (array_chunk($clientCodeList, 200) as $chunk) {
|
|
$ph = implode(',', array_fill(0, count($chunk), '?'));
|
|
$delCq = $pdo->prepare("DELETE FROM completed_questionnaire WHERE clientCode IN ($ph)");
|
|
$delCq->execute($chunk);
|
|
$deleted['completed_questionnaires'] += $delCq->rowCount();
|
|
}
|
|
}
|
|
if ($coachIdList !== []) {
|
|
$deleted['completed_questionnaires'] += qdb_dev_delete_by_ids(
|
|
$pdo,
|
|
'completed_questionnaire',
|
|
'assignedByCoach',
|
|
$coachIdList
|
|
);
|
|
}
|
|
|
|
if ($clientCodeList !== []) {
|
|
$deleted['clients'] += qdb_dev_delete_by_ids($pdo, 'client', 'clientCode', $clientCodeList);
|
|
}
|
|
if ($coachIdList !== []) {
|
|
foreach (array_chunk($coachIdList, 200) as $chunk) {
|
|
$ph = implode(',', array_fill(0, count($chunk), '?'));
|
|
$delCl = $pdo->prepare("DELETE FROM client WHERE coachID IN ($ph)");
|
|
$delCl->execute($chunk);
|
|
$deleted['clients'] += $delCl->rowCount();
|
|
}
|
|
}
|
|
$delClPrefix = $pdo->prepare('DELETE FROM client WHERE clientCode LIKE :pfx');
|
|
$delClPrefix->execute([':pfx' => $clientLike]);
|
|
$deleted['clients'] += $delClPrefix->rowCount();
|
|
|
|
$deleted['sessions'] = qdb_dev_delete_by_ids($pdo, 'session', 'userID', array_keys($userIds));
|
|
|
|
$deleted['users'] = qdb_dev_delete_by_ids($pdo, 'users', 'userID', array_keys($userIds));
|
|
$delUsersLike = $pdo->prepare('DELETE FROM users WHERE username LIKE :pfx');
|
|
$delUsersLike->execute([':pfx' => $userLike]);
|
|
$deleted['users'] += $delUsersLike->rowCount();
|
|
|
|
$deleted['coaches'] = qdb_dev_delete_by_ids($pdo, 'coach', 'coachID', $coachIdList);
|
|
$delCoachLike = $pdo->prepare('DELETE FROM coach WHERE username LIKE :pfx');
|
|
$delCoachLike->execute([':pfx' => $userLike]);
|
|
$deleted['coaches'] += $delCoachLike->rowCount();
|
|
|
|
$deleted['supervisors'] = qdb_dev_delete_by_ids($pdo, 'supervisor', 'supervisorID', array_keys($supervisorIds));
|
|
$delSvLike = $pdo->prepare('DELETE FROM supervisor WHERE username LIKE :pfx');
|
|
$delSvLike->execute([':pfx' => $userLike]);
|
|
$deleted['supervisors'] += $delSvLike->rowCount();
|
|
|
|
$deleted['admins'] = qdb_dev_delete_by_ids($pdo, 'admin', 'adminID', array_keys($adminIds));
|
|
$delAdLike = $pdo->prepare('DELETE FROM admin WHERE username LIKE :pfx');
|
|
$delAdLike->execute([':pfx' => $userLike]);
|
|
$deleted['admins'] += $delAdLike->rowCount();
|
|
|
|
$pdo->commit();
|
|
} catch (Throwable $e) {
|
|
if ($pdo->inTransaction()) {
|
|
$pdo->rollBack();
|
|
}
|
|
throw $e;
|
|
}
|
|
|
|
return ['deleted' => $deleted];
|
|
}
|
|
|
|
/**
|
|
* Remove all clients, completions, coaches, and supervisors. Keeps admin accounts
|
|
* and questionnaire definitions (questions, translations, etc.).
|
|
*
|
|
* @return array{deleted: array<string, int>, kept: array<string, int>}
|
|
*/
|
|
function qdb_wipe_all_data_except_admins(PDO $pdo): array
|
|
{
|
|
$deleted = [
|
|
'client_answers' => 0,
|
|
'completed_questionnaires' => 0,
|
|
'clients' => 0,
|
|
'sessions' => 0,
|
|
'users' => 0,
|
|
'coaches' => 0,
|
|
'supervisors' => 0,
|
|
];
|
|
|
|
$keptStmt = $pdo->query("SELECT COUNT(*) FROM users WHERE role = 'admin'");
|
|
$keptAdmins = (int)$keptStmt->fetchColumn();
|
|
|
|
$pdo->beginTransaction();
|
|
try {
|
|
// Interview data references clients/coaches; coach references supervisor.
|
|
// Disable FK checks for this bulk wipe (same pattern as questionnaire delete).
|
|
$pdo->exec('PRAGMA foreign_keys = OFF');
|
|
|
|
$deleted['client_answers'] = (int)$pdo->exec('DELETE FROM client_answer');
|
|
$deleted['completed_questionnaires'] = (int)$pdo->exec('DELETE FROM completed_questionnaire');
|
|
$deleted['clients'] = (int)$pdo->exec('DELETE FROM client');
|
|
|
|
$deleted['coaches'] = (int)$pdo->exec('DELETE FROM coach');
|
|
$deleted['supervisors'] = (int)$pdo->exec('DELETE FROM supervisor');
|
|
|
|
$sess = $pdo->prepare(
|
|
"DELETE FROM session WHERE userID IN (SELECT userID FROM users WHERE role != 'admin')"
|
|
);
|
|
$sess->execute();
|
|
$deleted['sessions'] = $sess->rowCount();
|
|
|
|
$users = $pdo->prepare("DELETE FROM users WHERE role != 'admin'");
|
|
$users->execute();
|
|
$deleted['users'] = $users->rowCount();
|
|
|
|
$pdo->exec('PRAGMA foreign_keys = ON');
|
|
$pdo->commit();
|
|
} catch (Throwable $e) {
|
|
if ($pdo->inTransaction()) {
|
|
$pdo->rollBack();
|
|
}
|
|
try {
|
|
$pdo->exec('PRAGMA foreign_keys = ON');
|
|
} catch (Throwable $ignored) {
|
|
}
|
|
throw $e;
|
|
}
|
|
|
|
return [
|
|
'deleted' => $deleted,
|
|
'kept' => ['admins' => $keptAdmins],
|
|
];
|
|
}
|