enhanced questionnaire handling with stable key validation, improved note management, and added option key support
This commit is contained in:
@ -17,6 +17,8 @@ case 'GET':
|
||||
foreach ($options as &$o) {
|
||||
$o['points'] = (int)$o['points'];
|
||||
$o['orderIndex'] = (int)$o['orderIndex'];
|
||||
$o['optionKey'] = qdb_option_key($o);
|
||||
$o['labelGerman'] = qdb_option_german_label($pdo, $o['answerOptionID'], $o['defaultText']);
|
||||
$tr = $pdo->prepare('SELECT languageCode, text FROM answer_option_translation WHERE answerOptionID = :id');
|
||||
$tr->execute([':id' => $o['answerOptionID']]);
|
||||
$o['translations'] = $tr->fetchAll(PDO::FETCH_ASSOC);
|
||||
@ -29,13 +31,14 @@ case 'GET':
|
||||
case 'POST':
|
||||
require_role(['admin', 'supervisor'], $tokenRec);
|
||||
$body = read_json_body();
|
||||
if (empty($body['questionID']) || !isset($body['defaultText'])) {
|
||||
json_error('MISSING_FIELDS', 'questionID and defaultText required', 400);
|
||||
if (empty($body['questionID']) || empty($body['optionKey']) || !isset($body['defaultText'])) {
|
||||
json_error('MISSING_FIELDS', 'questionID, optionKey, and defaultText (German label) required', 400);
|
||||
}
|
||||
$id = bin2hex(random_bytes(16));
|
||||
$qID = $body['questionID'];
|
||||
$optKey = qdb_validate_stable_key((string)$body['optionKey'], 'Option key');
|
||||
$text = trim($body['defaultText']);
|
||||
qdb_require_non_empty_german($text);
|
||||
qdb_require_non_empty_german($text, 'German label');
|
||||
$points = (int)($body['points'] ?? 0);
|
||||
$order = (int)($body['orderIndex'] ?? 0);
|
||||
$nextQ = trim($body['nextQuestionId'] ?? '');
|
||||
@ -53,14 +56,17 @@ case 'POST':
|
||||
$max->execute([':id' => $qID]);
|
||||
$order = (int)$max->fetchColumn();
|
||||
}
|
||||
qdb_assert_unique_option_key($pdo, $qID, $optKey);
|
||||
$pdo->prepare('INSERT INTO answer_option (answerOptionID, questionID, defaultText, points, orderIndex, nextQuestionId) VALUES (:id, :qid, :t, :p, :o, :nq)')
|
||||
->execute([':id' => $id, ':qid' => $qID, ':t' => $text, ':p' => $points, ':o' => $order, ':nq' => $nextQ]);
|
||||
->execute([':id' => $id, ':qid' => $qID, ':t' => $optKey, ':p' => $points, ':o' => $order, ':nq' => $nextQ]);
|
||||
qdb_upsert_source_translation($pdo, 'answer_option', $id, $text);
|
||||
qdb_save($tmpDb, $lockFp);
|
||||
json_success(['answerOption' => [
|
||||
'answerOptionID' => $id,
|
||||
'questionID' => $qID,
|
||||
'defaultText' => $text,
|
||||
'optionKey' => $optKey,
|
||||
'defaultText' => $optKey,
|
||||
'labelGerman' => $text,
|
||||
'points' => $points,
|
||||
'orderIndex' => $order,
|
||||
'nextQuestionId' => $nextQ,
|
||||
@ -90,20 +96,32 @@ case 'PUT':
|
||||
qdb_discard($tmpDb, $lockFp);
|
||||
json_error('NOT_FOUND', 'Answer option not found', 404);
|
||||
}
|
||||
$text = trim($body['defaultText'] ?? $row['defaultText']);
|
||||
qdb_require_non_empty_german($text);
|
||||
$optKey = isset($body['optionKey'])
|
||||
? qdb_validate_stable_key((string)$body['optionKey'], 'Option key')
|
||||
: qdb_option_key($row);
|
||||
if ($optKey === '') {
|
||||
json_error('MISSING_FIELDS', 'optionKey is required', 400);
|
||||
}
|
||||
$labelGerman = trim($body['defaultText'] ?? $body['labelGerman'] ?? '');
|
||||
if ($labelGerman === '') {
|
||||
$labelGerman = qdb_option_german_label($pdo, $id, $row['defaultText']);
|
||||
}
|
||||
qdb_require_non_empty_german($labelGerman, 'German label');
|
||||
qdb_assert_unique_option_key($pdo, $row['questionID'], $optKey, $id);
|
||||
$points = (int)($body['points'] ?? $row['points']);
|
||||
$order = (int)($body['orderIndex'] ?? $row['orderIndex']);
|
||||
$nextQ = trim($body['nextQuestionId'] ?? $row['nextQuestionId']);
|
||||
|
||||
$pdo->prepare('UPDATE answer_option SET defaultText = :t, points = :p, orderIndex = :o, nextQuestionId = :nq WHERE answerOptionID = :id')
|
||||
->execute([':t' => $text, ':p' => $points, ':o' => $order, ':nq' => $nextQ, ':id' => $id]);
|
||||
qdb_upsert_source_translation($pdo, 'answer_option', $id, $text);
|
||||
->execute([':t' => $optKey, ':p' => $points, ':o' => $order, ':nq' => $nextQ, ':id' => $id]);
|
||||
qdb_upsert_source_translation($pdo, 'answer_option', $id, $labelGerman);
|
||||
qdb_save($tmpDb, $lockFp);
|
||||
json_success(['answerOption' => [
|
||||
'answerOptionID' => $id,
|
||||
'questionID' => $row['questionID'],
|
||||
'defaultText' => $text,
|
||||
'optionKey' => $optKey,
|
||||
'defaultText' => $optKey,
|
||||
'labelGerman' => $labelGerman,
|
||||
'points' => $points,
|
||||
'orderIndex' => $order,
|
||||
'nextQuestionId' => $nextQ,
|
||||
|
||||
@ -250,14 +250,21 @@ if ($qnID) {
|
||||
$shortId = end($parts);
|
||||
|
||||
$layout = $dbQ['type'];
|
||||
$config = json_decode($dbQ['configJson'], true) ?: [];
|
||||
$config = qdb_parse_config_json($dbQ['configJson']);
|
||||
$qKey = qdb_question_key($config, $dbQ['defaultText']);
|
||||
|
||||
$q = [
|
||||
'id' => $shortId,
|
||||
'layout' => $layout,
|
||||
'question' => $dbQ['defaultText'],
|
||||
'question' => $qKey !== '' ? $qKey : $dbQ['defaultText'],
|
||||
];
|
||||
|
||||
if ($qKey !== '' && !empty($config['noteBefore'])) {
|
||||
$q['noteBeforeKey'] = qdb_note_before_key($qKey);
|
||||
}
|
||||
if ($qKey !== '' && !empty($config['noteAfter'])) {
|
||||
$q['noteAfterKey'] = qdb_note_after_key($qKey);
|
||||
}
|
||||
if (isset($config['textKey'])) $q['textKey'] = $config['textKey'];
|
||||
if (isset($config['textKey1'])) $q['textKey1'] = $config['textKey1'];
|
||||
if (isset($config['textKey2'])) $q['textKey2'] = $config['textKey2'];
|
||||
@ -267,18 +274,20 @@ if ($qnID) {
|
||||
if (isset($config['symptoms'])) $q['symptoms'] = $config['symptoms'];
|
||||
if (isset($config['scaleType'])) $q['scaleType'] = $config['scaleType'];
|
||||
if (isset($config['range'])) $q['range'] = $config['range'];
|
||||
if (isset($config['step'])) $q['step'] = (int)$config['step'];
|
||||
if (isset($config['unitLabel'])) $q['unitLabel'] = $config['unitLabel'];
|
||||
if (isset($config['constraints'])) $q['constraints'] = $config['constraints'];
|
||||
if (isset($config['precision'])) $q['precision'] = $config['precision'];
|
||||
if (isset($config['minSelection'])) $q['minSelection'] = $config['minSelection'];
|
||||
if (isset($config['maxLength'])) $q['maxLength'] = (int)$config['maxLength'];
|
||||
if (!empty($config['multiline'])) $q['multiline'] = true;
|
||||
if (isset($config['nextQuestionId'])) $q['nextQuestionId'] = $config['nextQuestionId'];
|
||||
if (isset($config['otherNextQuestionId'])) $q['otherNextQuestionId'] = $config['otherNextQuestionId'];
|
||||
if (isset($config['otherOptionKey'])) $q['otherOptionKey'] = $config['otherOptionKey'];
|
||||
|
||||
if ($layout === 'string_spinner' && isset($config['options'])) {
|
||||
$q['options'] = $config['options'];
|
||||
}
|
||||
if ($layout === 'value_spinner' && isset($config['valueOptions'])) {
|
||||
if (($layout === 'value_spinner' || $layout === 'slider_question') && isset($config['valueOptions'])) {
|
||||
$q['options'] = $config['valueOptions'];
|
||||
}
|
||||
|
||||
|
||||
@ -19,7 +19,10 @@ case 'GET':
|
||||
foreach ($questions as &$q) {
|
||||
$q['isRequired'] = (int)$q['isRequired'];
|
||||
$q['orderIndex'] = (int)$q['orderIndex'];
|
||||
$q['configJson'] = $q['configJson'] ?: '{}';
|
||||
$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
|
||||
@ -29,7 +32,10 @@ case 'GET':
|
||||
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);
|
||||
@ -42,16 +48,24 @@ case 'GET':
|
||||
case 'POST':
|
||||
require_role(['admin', 'supervisor'], $tokenRec);
|
||||
$body = read_json_body();
|
||||
if (empty($body['questionnaireID']) || !isset($body['defaultText'])) {
|
||||
json_error('BAD_REQUEST', 'questionnaireID and defaultText required', 400);
|
||||
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);
|
||||
$config = $body['configJson'] ?? '{}';
|
||||
$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(true);
|
||||
try {
|
||||
$chk = $pdo->prepare("SELECT 1 FROM questionnaire WHERE questionnaireID = :id");
|
||||
@ -79,13 +93,15 @@ case 'POST':
|
||||
$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' => $config]);
|
||||
':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' => $config, 'answerOptions' => [], 'translations' => [],
|
||||
'configJson' => $configJson, 'answerOptions' => [], 'translations' => [],
|
||||
]]);
|
||||
} catch (Throwable $e) {
|
||||
qdb_discard($tmpDb, $lockFp);
|
||||
@ -115,15 +131,31 @@ case 'PUT':
|
||||
$type = trim($body['type'] ?? $row['type']);
|
||||
$order = (int)($body['orderIndex'] ?? $row['orderIndex']);
|
||||
$req = (int)($body['isRequired'] ?? $row['isRequired']);
|
||||
$config = $body['configJson'] ?? $row['configJson'];
|
||||
$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' => $config, ':id' => $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, 'type' => $type, 'orderIndex' => $order, 'isRequired' => $req,
|
||||
'configJson' => $config,
|
||||
'defaultText' => $text, 'questionKey' => $questionKey,
|
||||
'type' => $type, 'orderIndex' => $order, 'isRequired' => $req,
|
||||
'configJson' => $configJson,
|
||||
]]);
|
||||
} catch (Throwable $e) {
|
||||
qdb_discard($tmpDb, $lockFp);
|
||||
|
||||
Reference in New Issue
Block a user