From b8de1b0849f8b91acfdde45dd1caab50af7e30e5 Mon Sep 17 00:00:00 2001 From: Andy Scherzinger Date: Sat, 7 Nov 2026 15:50:38 +0100 Subject: [PATCH] feat(conditional): allow branches on file questions A file question opens a branch depending on whether a file was uploaded, so a branch can also stand for "no file uploaded". Uploads of the question are checked and stored like those of any file question. The code comes from the original pull request #3123 by Micke Nordin and TimedIn, with the review fixes applied. Assisted-by: Claude Code:claude-opus-5-5 Signed-off-by: Andy Scherzinger --- docs/DataStructure.md | 9 ++++++ lib/Constants.php | 2 ++ lib/Service/SubmissionService.php | 12 ++++++-- .../Conditional/BranchConditionEditor.vue | 30 +++++++++++++++++++ src/components/Questions/QuestionFile.vue | 1 + src/utils/Branches.ts | 24 +++++++++++++-- tests/Unit/Service/FormsServiceTest.php | 5 ++++ tests/Unit/Service/SubmissionServiceTest.php | 29 ++++++++++++++++++ 8 files changed, 108 insertions(+), 4 deletions(-) diff --git a/docs/DataStructure.md b/docs/DataStructure.md index 28946a8a9..dc3e59d9f 100644 --- a/docs/DataStructure.md +++ b/docs/DataStructure.md @@ -307,6 +307,7 @@ Follow-up questions (subquestions) are questions of their own with additional pr | `date` | `date_range` | Date - range including its bounds (`YYYY-MM-DD`) | | `datetime` | `date_range` | Deprecated date and time (`YYYY-MM-DD HH:mm`) | | `time` | `date_range` | Time - range including its bounds (`HH:mm`) | +| `file` | `file_uploaded` | File - whether a file was uploaded | A branch is shown when any of its conditions matches. Several branches can be shown at once. @@ -368,6 +369,14 @@ All options in `optionIds` must be selected for the condition to match. `min` and `max` use the answer format of the question type and are optional. +#### file_uploaded (for file) + +```json +{ "type": "file_uploaded", "fileUploaded": true } +``` + +`"fileUploaded": false` matches when no file was uploaded. + ### Example A dropdown question with a branch of follow-up questions: diff --git a/lib/Constants.php b/lib/Constants.php index e46b83143..d98aa875e 100644 --- a/lib/Constants.php +++ b/lib/Constants.php @@ -245,6 +245,7 @@ class Constants { public const CONDITION_TYPE_OPTION_SELECTED = 'option_selected'; public const CONDITION_TYPE_OPTIONS_COMBINATION = 'options_combination'; public const CONDITION_TYPE_DATE_RANGE = 'date_range'; + public const CONDITION_TYPE_FILE_UPLOADED = 'file_uploaded'; /** * Question types that can have branches with follow-up questions in `extraSettings.branches`, @@ -259,6 +260,7 @@ class Constants { self::ANSWER_TYPE_DATE => [self::CONDITION_TYPE_DATE_RANGE], self::ANSWER_TYPE_DATETIME => [self::CONDITION_TYPE_DATE_RANGE], self::ANSWER_TYPE_TIME => [self::CONDITION_TYPE_DATE_RANGE], + self::ANSWER_TYPE_FILE => [self::CONDITION_TYPE_FILE_UPLOADED], ]; /** diff --git a/lib/Service/SubmissionService.php b/lib/Service/SubmissionService.php index 86568ec4d..4e8f8599f 100644 --- a/lib/Service/SubmissionService.php +++ b/lib/Service/SubmissionService.php @@ -1049,8 +1049,8 @@ private function evaluateBranchConditions(string $triggerType, array $triggerAns // Option ids arrive as numbers or strings depending on the trigger component $selectedOptionIds = array_map('strval', array_filter($triggerAnswer, 'is_scalar')); - // An unanswered trigger activates no branch - if ($triggerAnswer === [] || ($triggerAnswer[0] ?? '') === '') { + // An unanswered trigger activates no branch, except "no file uploaded" for file triggers + if ($triggerType !== Constants::ANSWER_TYPE_FILE && ($triggerAnswer === [] || ($triggerAnswer[0] ?? '') === '')) { return false; } @@ -1077,6 +1077,14 @@ private function evaluateBranchConditions(string $triggerType, array $triggerAns } } return false; + case Constants::ANSWER_TYPE_FILE: + $hasFile = !empty($triggerAnswer); + foreach ($conditions as $condition) { + if (($condition['fileUploaded'] ?? true) === $hasFile) { + return true; + } + } + return false; case Constants::ANSWER_TYPE_DATE: case Constants::ANSWER_TYPE_DATETIME: case Constants::ANSWER_TYPE_TIME: diff --git a/src/components/Conditional/BranchConditionEditor.vue b/src/components/Conditional/BranchConditionEditor.vue index 9b3b938a7..26c5959c5 100644 --- a/src/components/Conditional/BranchConditionEditor.vue +++ b/src/components/Conditional/BranchConditionEditor.vue @@ -50,12 +50,24 @@ class="condition-date-input" /> + + +