implemented scoring system
Some checks failed
PHPUnit / test (push) Has been cancelled

This commit is contained in:
2026-06-08 19:10:23 +02:00
parent 4589be119c
commit 63adef79df
19 changed files with 2873 additions and 36 deletions

View File

@ -1728,6 +1728,7 @@ function qdb_translation_row_label(array $e): string {
/** Export one questionnaire with structure + all translations (portable JSON). */
function qdb_export_questionnaire_bundle(PDO $pdo, string $qnID): ?array {
require_once __DIR__ . '/lib/scoring.php';
$qn = $pdo->prepare('SELECT * FROM questionnaire WHERE questionnaireID = :id');
$qn->execute([':id' => $qnID]);
$qnRow = $qn->fetch(PDO::FETCH_ASSOC);
@ -1779,6 +1780,7 @@ function qdb_export_questionnaire_bundle(PDO $pdo, string $qnID): ?array {
'isRequired' => (int)$dbQ['isRequired'],
'config' => $config,
'answerOptions' => $optionsOut,
'scoreRules' => qdb_get_score_rules_for_question($pdo, $dbQ['questionID']),
'translations' => qdb_fetch_translation_map($pdo, 'question', $dbQ['questionID']),
];
}
@ -1823,14 +1825,21 @@ function qdb_export_all_questionnaires_bundle(PDO $pdo): array {
}
}
return [
'exportVersion' => 1,
'exportVersion' => 2,
'exportedAt' => time(),
'sourceLanguage' => QDB_SOURCE_LANGUAGE,
'questionnaireCount' => count($items),
'questionnaires' => $items,
'scoringProfiles' => qdb_export_scoring_profiles_bundle($pdo),
];
}
/** Export all scoring profiles for bundle portability. */
function qdb_export_scoring_profiles_bundle(PDO $pdo): array {
require_once __DIR__ . '/lib/scoring.php';
return qdb_list_scoring_profiles($pdo);
}
/** Remove questionnaire and related rows (no client_answer cleanup for other qns). */
function qdb_delete_questionnaire_cascade(PDO $pdo, string $qnID): void {
$qIds = $pdo->prepare('SELECT questionID FROM question WHERE questionnaireID = :id');
@ -1957,6 +1966,11 @@ function qdb_import_questionnaire_bundle(PDO $pdo, array $item, bool $replaceIfE
qdb_apply_translation_map($pdo, 'question', $questionID, is_array($qTr) ? $qTr : []);
qdb_sync_question_note_strings($pdo, $questionKey, $config);
if (!empty($q['scoreRules']) && is_array($q['scoreRules'])) {
require_once __DIR__ . '/lib/scoring.php';
qdb_sync_question_score_rules($pdo, $questionID, $q['scoreRules']);
}
foreach ($q['answerOptions'] ?? [] as $opt) {
$optKey = trim((string)($opt['optionKey'] ?? ''));
$optGerman = trim($opt['defaultText'] ?? '');
@ -2213,12 +2227,102 @@ function qdb_import_questionnaires_bundle(PDO $pdo, array $bundle, bool $replace
foreach ($items as $item) {
$results[] = qdb_import_questionnaire_bundle($pdo, $item, $replaceIfExists);
}
if (!empty($bundle['scoringProfiles']) && is_array($bundle['scoringProfiles'])) {
qdb_import_scoring_profiles_bundle($pdo, $bundle['scoringProfiles'], $replaceIfExists);
}
return [
'imported' => count($results),
'results' => $results,
];
}
/** Import scoring profiles from a questionnaire bundle. */
function qdb_import_scoring_profiles_bundle(PDO $pdo, array $profiles, bool $replaceIfExists = false): void {
require_once __DIR__ . '/lib/scoring.php';
foreach ($profiles as $profile) {
if (!is_array($profile)) {
continue;
}
$wantedId = trim($profile['profileID'] ?? '');
$name = trim($profile['name'] ?? '');
if ($name === '') {
continue;
}
$exists = false;
if ($wantedId !== '') {
$chk = $pdo->prepare('SELECT 1 FROM scoring_profile WHERE profileID = :id');
$chk->execute([':id' => $wantedId]);
$exists = (bool)$chk->fetch();
}
if ($exists && $replaceIfExists) {
$pdo->prepare('DELETE FROM scoring_profile WHERE profileID = :id')->execute([':id' => $wantedId]);
$profileID = $wantedId;
} elseif ($exists) {
$profileID = bin2hex(random_bytes(16));
} else {
$profileID = $wantedId !== '' ? $wantedId : bin2hex(random_bytes(16));
}
$now = time();
$pdo->prepare(
'INSERT INTO scoring_profile (profileID, name, description, isActive,
greenMin, greenMax, yellowMin, yellowMax, redMin, createdAt, updatedAt)
VALUES (:id, :name, :desc, :act, :gmin, :gmax, :ymin, :ymax, :rmin, :ca, :ua)'
)->execute([
':id' => $profileID,
':name' => $name,
':desc' => trim($profile['description'] ?? ''),
':act' => !empty($profile['isActive']) ? 1 : 0,
':gmin' => (int)($profile['greenMin'] ?? 0),
':gmax' => (int)($profile['greenMax'] ?? 12),
':ymin' => (int)($profile['yellowMin'] ?? ((int)($profile['greenMax'] ?? 12) + 1)),
':ymax' => (int)($profile['yellowMax'] ?? 36),
':rmin' => (int)($profile['redMin'] ?? ((int)($profile['yellowMax'] ?? 36) + 1)),
':ca' => (int)($profile['createdAt'] ?? $now),
':ua' => (int)($profile['updatedAt'] ?? $now),
]);
$members = [];
foreach ($profile['questionnaires'] ?? [] as $idx => $row) {
if (!is_array($row)) {
continue;
}
$qnID = trim((string)($row['questionnaireID'] ?? ''));
if ($qnID === '') {
continue;
}
$chk = $pdo->prepare('SELECT 1 FROM questionnaire WHERE questionnaireID = :id');
$chk->execute([':id' => $qnID]);
if (!$chk->fetch()) {
continue;
}
$members[] = [
'questionnaireID' => $qnID,
'weight' => (float)($row['weight'] ?? 1.0),
'orderIndex' => (int)($row['orderIndex'] ?? $idx),
];
}
if ($members !== []) {
qdb_sync_profile_questionnaire_members($pdo, $profileID, $members);
}
}
}
function qdb_sync_profile_questionnaire_members(PDO $pdo, string $profileID, array $members): void {
$pdo->prepare('DELETE FROM scoring_profile_questionnaire WHERE profileID = :pid')
->execute([':pid' => $profileID]);
$ins = $pdo->prepare(
'INSERT INTO scoring_profile_questionnaire (profileID, questionnaireID, weight, orderIndex)
VALUES (:pid, :qn, :w, :o)'
);
foreach ($members as $m) {
$ins->execute([
':pid' => $profileID,
':qn' => $m['questionnaireID'],
':w' => $m['weight'],
':o' => $m['orderIndex'],
]);
}
}
// --- AES-256-CBC: IV(16) + CIPHERTEXT ---
function aes256_cbc_encrypt_bytes(string $plain, string $key): string {
$key = str_pad(substr($key, 0, 32), 32, "\0");