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