252 lines
11 KiB
PHP
252 lines
11 KiB
PHP
<?php
|
|
|
|
$tokenRec = require_valid_token_web();
|
|
|
|
switch ($method) {
|
|
|
|
case 'GET':
|
|
$qnID = $_GET['questionnaireID'] ?? '';
|
|
if (!$qnID) {
|
|
json_error('BAD_REQUEST', 'questionnaireID query param required', 400);
|
|
}
|
|
try {
|
|
$opened = qdb_open_read_or_fail();
|
|
[$pdo, $tmpDb, $lockFp] = $opened;
|
|
$stmt = $pdo->prepare("
|
|
SELECT questionID, questionnaireID, defaultText, type, orderIndex, isRequired, configJson
|
|
FROM question WHERE questionnaireID = :qid ORDER BY orderIndex
|
|
");
|
|
$stmt->execute([':qid' => $qnID]);
|
|
$questions = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
|
foreach ($questions as &$q) {
|
|
$q['isRequired'] = (int)$q['isRequired'];
|
|
$q['orderIndex'] = (int)$q['orderIndex'];
|
|
$cfg = qdb_parse_config_json($q['configJson']);
|
|
$q['configJson'] = json_encode($cfg, JSON_UNESCAPED_UNICODE);
|
|
$q['questionKey'] = qdb_question_key($cfg, $q['defaultText']);
|
|
$q['localId'] = qdb_question_local_id($q['questionID'], $qnID);
|
|
$ao = $pdo->prepare("
|
|
SELECT answerOptionID, questionID, defaultText, points, orderIndex, nextQuestionId
|
|
FROM answer_option WHERE questionID = :qid ORDER BY orderIndex
|
|
");
|
|
$ao->execute([':qid' => $q['questionID']]);
|
|
$q['answerOptions'] = $ao->fetchAll(PDO::FETCH_ASSOC);
|
|
foreach ($q['answerOptions'] as &$opt) {
|
|
$opt['points'] = (int)$opt['points'];
|
|
$opt['orderIndex'] = (int)$opt['orderIndex'];
|
|
$opt['optionKey'] = qdb_option_key($opt);
|
|
$opt['labelGerman'] = qdb_option_german_label($pdo, $opt['answerOptionID'], $opt['defaultText']);
|
|
}
|
|
unset($opt);
|
|
$tr = $pdo->prepare("SELECT languageCode, text FROM question_translation WHERE questionID = :qid");
|
|
$tr->execute([':qid' => $q['questionID']]);
|
|
$q['translations'] = $tr->fetchAll(PDO::FETCH_ASSOC);
|
|
}
|
|
unset($q);
|
|
qdb_discard($tmpDb, $lockFp);
|
|
json_success(['questions' => $questions]);
|
|
} catch (Throwable $e) {
|
|
qdb_handler_fail($e, 'Load questions', null, $tmpDb ?? null, $lockFp ?? null);
|
|
}
|
|
break;
|
|
|
|
case 'POST':
|
|
require_role(['admin', 'supervisor'], $tokenRec);
|
|
$body = read_json_body();
|
|
if (empty($body['questionnaireID']) || !isset($body['defaultText']) || empty($body['questionKey'])) {
|
|
json_error('BAD_REQUEST', 'questionnaireID, questionKey, and defaultText required', 400);
|
|
}
|
|
$qnID = $body['questionnaireID'];
|
|
$text = trim($body['defaultText']);
|
|
qdb_require_non_empty_german($text);
|
|
$questionKey = qdb_validate_stable_key((string)$body['questionKey'], 'Question key');
|
|
$type = trim($body['type'] ?? '');
|
|
$order = (int)($body['orderIndex'] ?? 0);
|
|
$req = (int)($body['isRequired'] ?? 0);
|
|
$cfg = qdb_parse_config_json($body['configJson'] ?? '{}');
|
|
$config = qdb_normalize_question_config(
|
|
$cfg,
|
|
$questionKey,
|
|
$body['noteBefore'] ?? ($cfg['noteBefore'] ?? null),
|
|
$body['noteAfter'] ?? ($cfg['noteAfter'] ?? null)
|
|
);
|
|
$configJson = json_encode($config, JSON_UNESCAPED_UNICODE);
|
|
[$pdo, $tmpDb, $lockFp] = qdb_open_write_or_fail();
|
|
try {
|
|
$chk = $pdo->prepare("SELECT 1 FROM questionnaire WHERE questionnaireID = :id");
|
|
$chk->execute([':id' => $qnID]);
|
|
if (!$chk->fetch()) {
|
|
qdb_discard($tmpDb, $lockFp);
|
|
json_error('NOT_FOUND', 'Questionnaire not found', 404);
|
|
}
|
|
if ($order === 0) {
|
|
$max = $pdo->prepare("SELECT COALESCE(MAX(orderIndex),0)+1 FROM question WHERE questionnaireID = :id");
|
|
$max->execute([':id' => $qnID]);
|
|
$order = (int)$max->fetchColumn();
|
|
}
|
|
$localId = trim($body['localId'] ?? '');
|
|
if ($localId === '') {
|
|
$localId = qdb_allocate_question_local_id($pdo, $qnID);
|
|
}
|
|
$id = qdb_make_question_id($qnID, $localId);
|
|
$dup = $pdo->prepare('SELECT 1 FROM question WHERE questionID = :id');
|
|
$dup->execute([':id' => $id]);
|
|
if ($dup->fetch()) {
|
|
qdb_discard($tmpDb, $lockFp);
|
|
json_error('CONFLICT', "Question id \"$localId\" already exists in this questionnaire", 409);
|
|
}
|
|
$pdo->prepare("INSERT INTO question (questionID, questionnaireID, defaultText, type, orderIndex, isRequired, configJson)
|
|
VALUES (:id, :qid, :t, :ty, :o, :r, :cj)")
|
|
->execute([':id' => $id, ':qid' => $qnID, ':t' => $text, ':ty' => $type,
|
|
':o' => $order, ':r' => $req, ':cj' => $configJson]);
|
|
qdb_upsert_source_translation($pdo, 'question', $id, $text);
|
|
qdb_sync_question_note_strings($pdo, $questionKey, $config);
|
|
qdb_save($tmpDb, $lockFp);
|
|
json_success(['question' => [
|
|
'questionID' => $id, 'questionnaireID' => $qnID, 'defaultText' => $text,
|
|
'questionKey' => $questionKey, 'localId' => $localId,
|
|
'type' => $type, 'orderIndex' => $order, 'isRequired' => $req,
|
|
'configJson' => $configJson, 'answerOptions' => [], 'translations' => [],
|
|
]]);
|
|
} catch (Throwable $e) {
|
|
qdb_handler_fail($e, 'Questions', null, $tmpDb ?? null, $lockFp ?? null);
|
|
}
|
|
break;
|
|
|
|
case 'PUT':
|
|
require_role(['admin', 'supervisor'], $tokenRec);
|
|
$body = read_json_body();
|
|
if (empty($body['questionID'])) {
|
|
json_error('BAD_REQUEST', 'questionID is required', 400);
|
|
}
|
|
$id = $body['questionID'];
|
|
[$pdo, $tmpDb, $lockFp] = qdb_open_write_or_fail();
|
|
try {
|
|
$existing = $pdo->prepare("SELECT * FROM question WHERE questionID = :id");
|
|
$existing->execute([':id' => $id]);
|
|
$row = $existing->fetch(PDO::FETCH_ASSOC);
|
|
if (!$row) {
|
|
qdb_discard($tmpDb, $lockFp);
|
|
json_error('NOT_FOUND', 'Question not found', 404);
|
|
}
|
|
$text = trim($body['defaultText'] ?? $row['defaultText']);
|
|
qdb_require_non_empty_german($text);
|
|
$type = trim($body['type'] ?? $row['type']);
|
|
$order = (int)($body['orderIndex'] ?? $row['orderIndex']);
|
|
$req = (int)($body['isRequired'] ?? $row['isRequired']);
|
|
$oldCfg = qdb_parse_config_json($row['configJson']);
|
|
$questionKey = isset($body['questionKey'])
|
|
? qdb_validate_stable_key((string)$body['questionKey'], 'Question key')
|
|
: qdb_question_key($oldCfg, $row['defaultText']);
|
|
if ($questionKey === '') {
|
|
json_error('MISSING_FIELDS', 'questionKey is required', 400);
|
|
}
|
|
$cfgIn = isset($body['configJson']) ? qdb_parse_config_json($body['configJson']) : $oldCfg;
|
|
$config = qdb_normalize_question_config(
|
|
$cfgIn,
|
|
$questionKey,
|
|
$body['noteBefore'] ?? ($cfgIn['noteBefore'] ?? null),
|
|
$body['noteAfter'] ?? ($cfgIn['noteAfter'] ?? null)
|
|
);
|
|
$configJson = json_encode($config, JSON_UNESCAPED_UNICODE);
|
|
$pdo->prepare("UPDATE question SET defaultText = :t, type = :ty, orderIndex = :o, isRequired = :r, configJson = :cj WHERE questionID = :id")
|
|
->execute([':t' => $text, ':ty' => $type, ':o' => $order, ':r' => $req, ':cj' => $configJson, ':id' => $id]);
|
|
qdb_upsert_source_translation($pdo, 'question', $id, $text);
|
|
qdb_sync_question_note_strings($pdo, $questionKey, $config);
|
|
qdb_save($tmpDb, $lockFp);
|
|
json_success(['question' => [
|
|
'questionID' => $id, 'questionnaireID' => $row['questionnaireID'],
|
|
'defaultText' => $text, 'questionKey' => $questionKey,
|
|
'type' => $type, 'orderIndex' => $order, 'isRequired' => $req,
|
|
'configJson' => $configJson,
|
|
]]);
|
|
} catch (Throwable $e) {
|
|
qdb_handler_fail($e, 'Questions', null, $tmpDb ?? null, $lockFp ?? null);
|
|
}
|
|
break;
|
|
|
|
case 'DELETE':
|
|
require_role(['admin', 'supervisor'], $tokenRec);
|
|
$body = read_json_body();
|
|
if (empty($body['questionID'])) {
|
|
json_error('BAD_REQUEST', 'questionID is required', 400);
|
|
}
|
|
$id = $body['questionID'];
|
|
[$pdo, $tmpDb, $lockFp] = qdb_open_write_or_fail();
|
|
try {
|
|
$pdo->exec("PRAGMA foreign_keys = OFF;");
|
|
$pdo->beginTransaction();
|
|
$aoIds = $pdo->prepare("SELECT answerOptionID FROM answer_option WHERE questionID = :id");
|
|
$aoIds->execute([':id' => $id]);
|
|
foreach ($aoIds->fetchAll(PDO::FETCH_COLUMN) as $aoid) {
|
|
$pdo->prepare("DELETE FROM answer_option_translation WHERE answerOptionID = :id")->execute([':id' => $aoid]);
|
|
}
|
|
$pdo->prepare("DELETE FROM answer_option WHERE questionID = :id")->execute([':id' => $id]);
|
|
$pdo->prepare("DELETE FROM question_translation WHERE questionID = :id")->execute([':id' => $id]);
|
|
$pdo->prepare("DELETE FROM client_answer WHERE questionID = :id")->execute([':id' => $id]);
|
|
$pdo->prepare("DELETE FROM question WHERE questionID = :id")->execute([':id' => $id]);
|
|
$pdo->commit();
|
|
$pdo->exec("PRAGMA foreign_keys = ON;");
|
|
qdb_save($tmpDb, $lockFp);
|
|
json_success(['deleted' => true]);
|
|
} catch (Throwable $e) {
|
|
qdb_handler_fail($e, 'Delete question', $pdo ?? null, $tmpDb ?? null, $lockFp ?? null);
|
|
}
|
|
break;
|
|
|
|
case 'PATCH':
|
|
require_role(['admin', 'supervisor'], $tokenRec);
|
|
$body = read_json_body();
|
|
if (empty($body['questionnaireID']) || !is_array($body['order'] ?? null)) {
|
|
json_error('BAD_REQUEST', 'questionnaireID and order[] required', 400);
|
|
}
|
|
$qnID = (string)$body['questionnaireID'];
|
|
$orderIds = array_values(array_filter(
|
|
array_map('strval', $body['order']),
|
|
static fn($id) => $id !== ''
|
|
));
|
|
if ($orderIds === []) {
|
|
json_error('BAD_REQUEST', 'order[] must list at least one questionID', 400);
|
|
}
|
|
try {
|
|
$opened = qdb_open_write_or_fail();
|
|
[$pdo, $tmpDb, $lockFp] = $opened;
|
|
$chk = $pdo->prepare('SELECT 1 FROM questionnaire WHERE questionnaireID = :id');
|
|
$chk->execute([':id' => $qnID]);
|
|
if (!$chk->fetch()) {
|
|
qdb_discard($tmpDb, $lockFp);
|
|
json_error('NOT_FOUND', 'Questionnaire not found', 404);
|
|
}
|
|
$ph = implode(',', array_fill(0, count($orderIds), '?'));
|
|
$stmt = $pdo->prepare(
|
|
"SELECT questionID FROM question WHERE questionnaireID = ? AND questionID IN ($ph)"
|
|
);
|
|
$stmt->execute(array_merge([$qnID], $orderIds));
|
|
$found = $stmt->fetchAll(PDO::FETCH_COLUMN);
|
|
if (count($found) !== count($orderIds)) {
|
|
$missing = array_values(array_diff($orderIds, $found));
|
|
qdb_discard($tmpDb, $lockFp);
|
|
json_error(
|
|
'INVALID_FIELD',
|
|
'order[] contains questionIDs not in this questionnaire',
|
|
400,
|
|
['missingQuestionIDs' => $missing]
|
|
);
|
|
}
|
|
$upd = $pdo->prepare(
|
|
'UPDATE question SET orderIndex = :o WHERE questionID = :id AND questionnaireID = :qid'
|
|
);
|
|
foreach ($orderIds as $idx => $qid) {
|
|
$upd->execute([':o' => $idx, ':id' => $qid, ':qid' => $qnID]);
|
|
}
|
|
qdb_save($tmpDb, $lockFp);
|
|
json_success(['reordered' => true]);
|
|
} catch (Throwable $e) {
|
|
qdb_handler_fail($e, 'Reorder questions', null, $tmpDb ?? null, $lockFp ?? null);
|
|
}
|
|
break;
|
|
|
|
default:
|
|
json_error('METHOD_NOT_ALLOWED', 'Method not allowed', 405);
|
|
}
|