156 lines
4.8 KiB
PHP
156 lines
4.8 KiB
PHP
<?php
|
|
/**
|
|
* Branching walk for questionnaire exports/results (matches app all-in-one logic).
|
|
* Only questions on the active path from current answers are "reachable".
|
|
*/
|
|
|
|
function qrb_load_path_questions(PDO $pdo, string $questionnaireId): array {
|
|
$stmt = $pdo->prepare("
|
|
SELECT questionID, defaultText, type, orderIndex, configJson
|
|
FROM question
|
|
WHERE questionnaireID = :qn
|
|
ORDER BY orderIndex
|
|
");
|
|
$stmt->execute([':qn' => $questionnaireId]);
|
|
$path = [];
|
|
|
|
foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) {
|
|
$parts = explode('__', $row['questionID']);
|
|
$shortId = end($parts);
|
|
$config = json_decode($row['configJson'] ?? '{}', true) ?: [];
|
|
|
|
$options = [];
|
|
$ao = $pdo->prepare("
|
|
SELECT defaultText, nextQuestionId
|
|
FROM answer_option
|
|
WHERE questionID = :qid
|
|
ORDER BY orderIndex
|
|
");
|
|
$ao->execute([':qid' => $row['questionID']]);
|
|
foreach ($ao->fetchAll(PDO::FETCH_ASSOC) as $opt) {
|
|
$options[] = [
|
|
'text' => $opt['defaultText'],
|
|
'nextQuestionId' => $opt['nextQuestionId'] ?? '',
|
|
];
|
|
}
|
|
|
|
$symptoms = [];
|
|
if (!empty($config['symptoms']) && is_array($config['symptoms'])) {
|
|
$symptoms = $config['symptoms'];
|
|
}
|
|
|
|
$path[] = [
|
|
'questionID' => $row['questionID'],
|
|
'shortId' => $shortId,
|
|
'type' => $row['type'] ?? '',
|
|
'storageKey' => $row['defaultText'] !== '' ? $row['defaultText'] : $shortId,
|
|
'options' => $options,
|
|
'symptoms' => $symptoms,
|
|
'isLastPage' => ($row['type'] ?? '') === 'last_page',
|
|
];
|
|
}
|
|
|
|
return $path;
|
|
}
|
|
|
|
/**
|
|
* @param array<string, array> $answerMap keyed by full questionID
|
|
* @return array<string, bool> reachable full questionIDs
|
|
*/
|
|
function qrb_reachable_question_ids(PDO $pdo, string $questionnaireId, array $answerMap, array $optionTextMap): array {
|
|
$path = qrb_load_path_questions($pdo, $questionnaireId);
|
|
$reachable = [];
|
|
$shortToIndex = [];
|
|
foreach ($path as $idx => $q) {
|
|
$shortToIndex[$q['shortId']] = $idx;
|
|
}
|
|
|
|
$i = 0;
|
|
$count = count($path);
|
|
while ($i < $count) {
|
|
$q = $path[$i];
|
|
if ($q['isLastPage']) {
|
|
$i++;
|
|
continue;
|
|
}
|
|
|
|
$reachable[$q['questionID']] = true;
|
|
|
|
$nextTarget = qrb_resolve_next_target($q, $answerMap, $optionTextMap);
|
|
if ($nextTarget !== null) {
|
|
foreach ($path as $pq) {
|
|
if ($pq['isLastPage'] && $pq['shortId'] === $nextTarget) {
|
|
break 2;
|
|
}
|
|
}
|
|
if (isset($shortToIndex[$nextTarget])) {
|
|
$i = $shortToIndex[$nextTarget];
|
|
} else {
|
|
$i++;
|
|
}
|
|
} elseif (qrb_has_branching_options($q) && !qrb_has_branch_answer($q, $answerMap, $optionTextMap)) {
|
|
break;
|
|
} else {
|
|
$i++;
|
|
}
|
|
}
|
|
|
|
return $reachable;
|
|
}
|
|
|
|
function qrb_resolve_next_target(array $q, array $answerMap, array $optionTextMap): ?string {
|
|
$answer = $answerMap[$q['questionID']] ?? null;
|
|
if ($answer === null) {
|
|
return null;
|
|
}
|
|
|
|
$selectedText = qrb_answer_display_value($answer, $optionTextMap);
|
|
if ($selectedText === null || $selectedText === '') {
|
|
return null;
|
|
}
|
|
|
|
foreach ($q['options'] as $opt) {
|
|
if ($opt['text'] === $selectedText && $opt['nextQuestionId'] !== '') {
|
|
return $opt['nextQuestionId'];
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
function qrb_has_branching_options(array $q): bool {
|
|
foreach ($q['options'] as $opt) {
|
|
if ($opt['nextQuestionId'] !== '') {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function qrb_has_branch_answer(array $q, array $answerMap, array $optionTextMap): bool {
|
|
$answer = $answerMap[$q['questionID']] ?? null;
|
|
if ($answer === null) {
|
|
return false;
|
|
}
|
|
$v = qrb_answer_display_value($answer, $optionTextMap);
|
|
return $v !== null && $v !== '';
|
|
}
|
|
|
|
function qrb_answer_display_value(array $answer, array $optionTextMap): ?string {
|
|
if (!empty($answer['answerOptionID']) && isset($optionTextMap[$answer['answerOptionID']])) {
|
|
return $optionTextMap[$answer['answerOptionID']];
|
|
}
|
|
if (isset($answer['freeTextValue']) && $answer['freeTextValue'] !== '') {
|
|
return (string)$answer['freeTextValue'];
|
|
}
|
|
if (isset($answer['numericValue']) && $answer['numericValue'] !== null && $answer['numericValue'] !== '') {
|
|
return (string)$answer['numericValue'];
|
|
}
|
|
return null;
|
|
}
|
|
|
|
function qrb_format_cell(array $answer, array $optionTextMap): string {
|
|
$v = qrb_answer_display_value($answer, $optionTextMap);
|
|
return $v ?? '';
|
|
}
|