prepare($sql); $stmt->execute($rbacParams); $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); foreach ($rows as &$r) { $r['orderIndex'] = (int)$r['orderIndex']; $r['showPoints'] = (int)$r['showPoints']; $r['questionCount'] = (int)$r['questionCount']; $r['completedCount'] = (int)$r['completedCount']; $r['conditionJson'] = $r['conditionJson'] ?: '{}'; } unset($r); $categoryKeys = qdb_list_category_keys_for_dashboard($pdo); qdb_discard($tmpDb, $lockFp); json_success(['questionnaires' => $rows, 'categoryKeys' => $categoryKeys]); } catch (Throwable $e) { qdb_handler_fail($e, 'Load questionnaires', null, $tmpDb ?? null, $lockFp ?? null); } break; case 'POST': require_role(['admin', 'supervisor'], $tokenRec); $body = read_json_body(); if (($body['action'] ?? '') === 'updateConditionMessageLabel') { $messageKey = trim((string)($body['messageKey'] ?? '')); $germanLabel = (string)($body['germanLabel'] ?? ''); [$pdo, $tmpDb, $lockFp] = qdb_open_write_or_fail(); try { qdb_set_app_string_german_label($pdo, $messageKey, $germanLabel); qdb_save($tmpDb, $lockFp); json_success([ 'messageKey' => qdb_validate_stable_key($messageKey, 'messageKey'), 'germanLabel' => trim($germanLabel), ]); } catch (Throwable $e) { qdb_handler_fail($e, 'Update condition message label', null, $tmpDb, $lockFp); } break; } if (($body['action'] ?? '') === 'updateCategoryLabel') { $categoryKey = trim((string)($body['categoryKey'] ?? '')); $germanLabel = (string)($body['germanLabel'] ?? ''); [$pdo, $tmpDb, $lockFp] = qdb_open_write_or_fail(); try { $stringKey = qdb_set_category_german_label($pdo, $categoryKey, $germanLabel); qdb_save($tmpDb, $lockFp); json_success([ 'categoryKey' => $categoryKey, 'stringKey' => $stringKey, 'germanLabel' => trim($germanLabel), ]); } catch (Throwable $e) { qdb_handler_fail($e, 'Update category label', null, $tmpDb, $lockFp); } break; } if (($body['action'] ?? '') === 'deleteCategoryKey') { $categoryKey = trim((string)($body['categoryKey'] ?? '')); if ($categoryKey === '') { json_error('INVALID_FIELD', 'categoryKey is required', 400); } [$pdo, $tmpDb, $lockFp] = qdb_open_write_or_fail(); try { $cleared = qdb_delete_category_key($pdo, $categoryKey); qdb_save($tmpDb, $lockFp); json_success(['deleted' => $categoryKey, 'questionnairesCleared' => $cleared]); } catch (Throwable $e) { qdb_handler_fail($e, 'Delete category key', null, $tmpDb, $lockFp); } break; } if (($body['action'] ?? '') === 'importBundle') { $bundle = $body['bundle'] ?? null; if (!is_array($bundle)) { json_error('MISSING_FIELDS', 'bundle object is required', 400); } $replaceIfExists = !empty($body['replaceIfExists']); [$pdo, $tmpDb, $lockFp] = qdb_open_write_or_fail(); try { qdb_set_foreign_keys($pdo, false); $result = qdb_import_questionnaires_bundle($pdo, $bundle, $replaceIfExists); qdb_set_foreign_keys($pdo, true); qdb_save($tmpDb, $lockFp); json_success($result); } catch (Throwable $e) { qdb_handler_fail($e, 'Import questionnaires', null, $tmpDb, $lockFp); } break; } if (empty($body['name'])) { json_error('NAME_REQUIRED', 'name is required', 400); } $id = bin2hex(random_bytes(16)); $name = trim($body['name']); $version = trim($body['version'] ?? ''); $state = trim($body['state'] ?? 'draft'); $order = (int)($body['orderIndex'] ?? 0); $showPts = (int)($body['showPoints'] ?? 0); $condJson = $body['conditionJson'] ?? '{}'; [$pdo, $tmpDb, $lockFp] = qdb_open_write_or_fail(); try { if ($order === 0) { $order = (int)$pdo->query("SELECT COALESCE(MAX(orderIndex),0)+1 FROM questionnaire")->fetchColumn(); } $catKey = trim($body['categoryKey'] ?? ''); if ($catKey !== '') { $catKey = qdb_normalize_stable_key($catKey); } $pdo->prepare("INSERT INTO questionnaire (questionnaireID, name, version, state, orderIndex, showPoints, conditionJson, categoryKey) VALUES (:id, :n, :v, :s, :o, :sp, :cj, :ck)") ->execute([':id' => $id, ':n' => $name, ':v' => $version, ':s' => $state, ':o' => $order, ':sp' => $showPts, ':cj' => $condJson, ':ck' => $catKey]); qdb_save($tmpDb, $lockFp); json_success(['questionnaire' => [ 'questionnaireID' => $id, 'name' => $name, 'version' => $version, 'state' => $state, 'orderIndex' => $order, 'showPoints' => $showPts, 'conditionJson' => $condJson, 'questionCount' => 0, ]]); } catch (Throwable $e) { qdb_handler_fail($e, 'Create questionnaire', null, $tmpDb, $lockFp); } break; case 'PUT': require_role(['admin', 'supervisor'], $tokenRec); $body = read_json_body(); if (empty($body['questionnaireID'])) { json_error('QUESTIONNAIRE_ID_REQUIRED', 'questionnaireID is required', 400); } $id = $body['questionnaireID']; [$pdo, $tmpDb, $lockFp] = qdb_open_write_or_fail(); try { $existing = $pdo->prepare('SELECT * FROM questionnaire WHERE questionnaireID = :id'); $existing->execute([':id' => $id]); $row = $existing->fetch(PDO::FETCH_ASSOC); if (!$row) { qdb_discard($tmpDb, $lockFp); json_error('NOT_FOUND', 'Questionnaire not found', 404); } $name = trim($body['name'] ?? $row['name']); $version = trim($body['version'] ?? $row['version']); $state = trim($body['state'] ?? $row['state']); $order = (int)($body['orderIndex'] ?? $row['orderIndex']); $showPts = (int)($body['showPoints'] ?? $row['showPoints']); $condJson = $body['conditionJson'] ?? $row['conditionJson']; $catKey = array_key_exists('categoryKey', $body) ? trim((string)$body['categoryKey']) : trim($row['categoryKey'] ?? ''); if ($catKey !== '') { $catKey = qdb_normalize_stable_key($catKey); } $pdo->prepare("UPDATE questionnaire SET name = :n, version = :v, state = :s, orderIndex = :o, showPoints = :sp, conditionJson = :cj, categoryKey = :ck WHERE questionnaireID = :id") ->execute([':n' => $name, ':v' => $version, ':s' => $state, ':o' => $order, ':sp' => $showPts, ':cj' => $condJson, ':ck' => $catKey, ':id' => $id]); qdb_save($tmpDb, $lockFp); json_success(['questionnaire' => [ 'questionnaireID' => $id, 'name' => $name, 'version' => $version, 'state' => $state, 'orderIndex' => $order, 'showPoints' => $showPts, 'conditionJson' => $condJson, ]]); } catch (Throwable $e) { qdb_handler_fail($e, 'Update questionnaire', null, $tmpDb, $lockFp); } break; case 'DELETE': require_role(['admin'], $tokenRec); $body = read_json_body(); if (empty($body['questionnaireID'])) { json_error('QUESTIONNAIRE_ID_REQUIRED', 'questionnaireID is required', 400); } $id = $body['questionnaireID']; [$pdo, $tmpDb, $lockFp] = qdb_open_write_or_fail(); try { qdb_set_foreign_keys($pdo, false); $pdo->beginTransaction(); $qIds = $pdo->prepare('SELECT questionID FROM question WHERE questionnaireID = :id'); $qIds->execute([':id' => $id]); $questionIDs = $qIds->fetchAll(PDO::FETCH_COLUMN); foreach ($questionIDs as $qid) { $aoIds = $pdo->prepare('SELECT answerOptionID FROM answer_option WHERE questionID = :qid'); $aoIds->execute([':qid' => $qid]); $optionIDs = $aoIds->fetchAll(PDO::FETCH_COLUMN); foreach ($optionIDs as $aoid) { $pdo->prepare('DELETE FROM answer_option_translation WHERE answerOptionID = :id')->execute([':id' => $aoid]); } $pdo->prepare('DELETE FROM answer_option WHERE questionID = :qid')->execute([':qid' => $qid]); $pdo->prepare('DELETE FROM question_translation WHERE questionID = :qid')->execute([':qid' => $qid]); $pdo->prepare('DELETE FROM client_answer WHERE questionID = :qid')->execute([':qid' => $qid]); } $pdo->prepare('DELETE FROM question WHERE questionnaireID = :id')->execute([':id' => $id]); $pdo->prepare('DELETE FROM completed_questionnaire WHERE questionnaireID = :id')->execute([':id' => $id]); $pdo->prepare('DELETE FROM questionnaire WHERE questionnaireID = :id')->execute([':id' => $id]); $pdo->commit(); qdb_set_foreign_keys($pdo, true); qdb_save($tmpDb, $lockFp); json_success([]); } catch (Throwable $e) { qdb_handler_fail($e, 'Delete questionnaire', $pdo, $tmpDb, $lockFp); } break; default: json_error('METHOD_NOT_ALLOWED', 'Method not allowed', 405); }