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" />
+
+
+
+