refactored export functionality
This commit is contained in:
155
lib/questionnaire_reachability.php
Normal file
155
lib/questionnaire_reachability.php
Normal file
@ -0,0 +1,155 @@
|
||||
<?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 ?? '';
|
||||
}
|
||||
Reference in New Issue
Block a user