From 7e9b52d2e1605549d6d3176d1060a2ef4e7dbd4f Mon Sep 17 00:00:00 2001 From: Christian Hartmann Date: Fri, 5 Jun 2026 13:59:14 +0200 Subject: [PATCH] enh: improve error messages for required questions. Signed-off-by: Christian Hartmann --- lib/Service/SubmissionService.php | 32 +++++++++----- src/components/Questions/QuestionColor.vue | 12 ++++++ src/components/Questions/QuestionDate.vue | 42 +++++++++++++++---- src/components/Questions/QuestionDropdown.vue | 12 ++++++ src/components/Questions/QuestionFile.vue | 8 ++++ src/components/Questions/QuestionGrid.vue | 38 +++++------------ .../Questions/QuestionLinearScale.vue | 32 ++++++-------- src/components/Questions/QuestionLong.vue | 15 +++++++ src/components/Questions/QuestionMultiple.vue | 7 ++++ src/components/Questions/QuestionShort.vue | 15 +++++++ 10 files changed, 148 insertions(+), 65 deletions(-) diff --git a/lib/Service/SubmissionService.php b/lib/Service/SubmissionService.php index ca1e325b7..2b3ea7abc 100644 --- a/lib/Service/SubmissionService.php +++ b/lib/Service/SubmissionService.php @@ -485,9 +485,16 @@ public function validateSubmission(array $questions, array $answers, string $for if ($question['isRequired'] && (!$questionAnswered || !array_filter($answers[$questionId], static function (string|array $value): bool { - // file type if (is_array($value)) { - return !empty($value['uploadedFileId']); + // file type + if (isset($value['uploadedFileId'])) { + return !empty($value['uploadedFileId']); + } + + // Grid questions + return !empty(array_filter($value, static function ($subValue): bool { + return is_array($subValue) ? !empty(array_filter($subValue)) : $subValue !== ''; + })); } return $value !== ''; @@ -557,16 +564,19 @@ public function validateSubmission(array $questions, array $answers, string $for } // Search corresponding option, return false if non-existent else { - // Accept numeric strings like "46" from JSON payloads reliably (e.g. with hardening extensions enabled) - $answerId = is_int($answer) ? $answer : (is_string($answer) ? intval(trim($answer)) : null); - - // Reject non-numeric / malformed values early - if ($answerId === null || (string)$answerId !== (string)intval($answerId)) { - throw new \InvalidArgumentException(sprintf('Answer "%s" for question "%s" is not a valid option.', is_scalar($answer) ? (string)$answer : gettype($answer), $question['text'])); - } + $subAnswers = is_array($answer) ? $answer : [$answer]; + foreach ($subAnswers as $subAnswer) { + // Accept numeric strings like "46" from JSON payloads reliably (e.g. with hardening extensions enabled) + $answerId = is_int($subAnswer) ? $subAnswer : (is_string($subAnswer) ? intval(trim($subAnswer)) : null); + + // Reject non-numeric / malformed values early + if ($answerId === null || (string)$answerId !== (string)intval($answerId)) { + throw new \InvalidArgumentException(sprintf('Answer "%s" for question "%s" is not a valid option.', is_scalar($subAnswer) ? (string)$subAnswer : gettype($subAnswer), $question['text'])); + } - if (!in_array($answerId, $optionIds, true)) { - throw new \InvalidArgumentException(sprintf('Answer "%s" for question "%s" is not a valid option.', $answer, $question['text'])); + if (!in_array($answerId, $optionIds, true)) { + throw new \InvalidArgumentException(sprintf('Answer "%s" for question "%s" is not a valid option.', $subAnswer, $question['text'])); + } } } } diff --git a/src/components/Questions/QuestionColor.vue b/src/components/Questions/QuestionColor.vue index f2f2709a0..2655cb1b9 100644 --- a/src/components/Questions/QuestionColor.vue +++ b/src/components/Questions/QuestionColor.vue @@ -8,6 +8,7 @@ v-bind="questionProps" :titlePlaceholder="answerType.titlePlaceholder" :warningInvalid="answerType.warningInvalid" + :errorMessage="errorMessage" v-on="commonListeners">
{{ colorPickerPlaceholder }} @@ -87,6 +89,16 @@ export default { }, methods: { + async validate() { + if (this.isRequired && this.pickedColor === '') { + this.errorMessage = t('forms', 'You must answer this question') + return false + } + + this.errorMessage = null + return true + }, + onUpdatePickedColor(color) { this.$emit('update:values', [color]) }, diff --git a/src/components/Questions/QuestionDate.vue b/src/components/Questions/QuestionDate.vue index 671d0f0b4..e4e5aa1f3 100644 --- a/src/components/Questions/QuestionDate.vue +++ b/src/components/Questions/QuestionDate.vue @@ -8,6 +8,7 @@ v-bind="questionProps" :titlePlaceholder="answerType.titlePlaceholder" :warningInvalid="answerType.warningInvalid" + :errorMessage="errorMessage" v-on="commonListeners">