Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 22 additions & 1 deletion lib/Service/SubmissionService.php
Original file line number Diff line number Diff line change
Expand Up @@ -594,8 +594,28 @@ private function escapeCSV(string $value): string {
return $value;
}

/**
* Whether the submit view shows the question to respondents: it needs a title, choice, dropdown and ranking questions need options, and grid questions need rows and columns
*
* @param array $question the question with its options
*/
private static function isShownToRespondents(array $question): bool {
if (preg_match('/^[\s\p{Z}\x{FEFF}]*$/u', (string)($question['text'] ?? '')) === 1) {
return false;
}

$optionTypes = array_map(static fn (mixed $option): mixed => is_array($option) ? ($option['optionType'] ?? null) : null, $question['options'] ?? []);
return match ($question['type'] ?? null) {
Constants::ANSWER_TYPE_MULTIPLE, Constants::ANSWER_TYPE_MULTIPLEUNIQUE, Constants::ANSWER_TYPE_DROPDOWN, Constants::ANSWER_TYPE_RANKING => $optionTypes !== [],
Constants::ANSWER_TYPE_GRID => in_array(Option::OPTION_TYPE_ROW, $optionTypes, true) && in_array(Option::OPTION_TYPE_COLUMN, $optionTypes, true),
default => true,
};
}

/**
* Validate all answers against the questions
* Required questions the submit view does not show to respondents need no answer
*
* @param array $questions Array of the questions of the form
* @param array $answers Array of the submitted answers
* @param string $formOwnerId Owner of the form
Expand All @@ -610,6 +630,7 @@ public function validateSubmission(array $questions, array $answers, string $for

// Check if all required questions have an answer
if ($question['isRequired']
&& self::isShownToRespondents($question)
&& (!$questionAnswered
|| !array_filter($answers[$questionId], static function (string|array $value): bool {
if (is_array($value)) {
Expand Down Expand Up @@ -683,7 +704,7 @@ public function validateSubmission(array $questions, array $answers, string $for
if ($question['type'] === Constants::ANSWER_TYPE_LINEARSCALE) {
$optionsLowest = $question['extraSettings']['optionsLowest'] ?? 1;
$optionsHighest = $question['extraSettings']['optionsHighest'] ?? 5;
if (!ctype_digit((string)$answer) || intval($answer) < $optionsLowest || intval($answer) > $optionsHighest) {
if (!is_scalar($answer) || is_bool($answer) || !ctype_digit((string)$answer) || intval($answer) < $optionsLowest || intval($answer) > $optionsHighest) {
throw new \InvalidArgumentException(sprintf('The answer for question "%s" must be an integer between %d and %d.', $question['text'], $optionsLowest, $optionsHighest));
}
}
Expand Down
58 changes: 58 additions & 0 deletions tests/Unit/Service/SubmissionServiceTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -925,6 +925,46 @@ private function setUpCsvTest(array $questions, array $submissions, string $csvT
// Data for validation of Submissions
public static function dataValidateSubmission() {
return [
'required-without-title-is-not-shown' => [
[
['id' => 1, 'type' => 'short', 'text' => ' ', 'isRequired' => true]
],
[],
null,
],
'required-with-unicode-space-title-is-not-shown' => [
[
['id' => 1, 'type' => 'short', 'text' => "\u{00A0}\u{3000}\u{FEFF}", 'isRequired' => true]
],
[],
null,
],
'required-choice-without-options-is-not-shown' => [
[
['id' => 1, 'type' => 'multiple_unique', 'text' => 'q1', 'isRequired' => true, 'options' => []]
],
[],
null,
],
'required-grid-without-columns-is-not-shown' => [
[
['id' => 1, 'type' => 'grid', 'text' => 'q1', 'isRequired' => true, 'options' => [
['id' => 3, 'optionType' => 'row']
]]
],
[],
null,
],
'required-grid-with-rows-and-columns' => [
[
['id' => 1, 'type' => 'grid', 'text' => 'q1', 'isRequired' => true, 'options' => [
['id' => 3, 'optionType' => 'row'],
['id' => 4, 'optionType' => 'column']
]]
],
[],
'Question "q1" is required.',
],
'required-not-answered' => [
// Questions
[
Expand Down Expand Up @@ -1297,6 +1337,24 @@ public static function dataValidateSubmission() {
// Expected Result
'The answer for question "q1" must be an integer between 0 and 5.',
],
'boolean-linearscale-answer' => [
[
['id' => 1, 'type' => 'linearscale', 'text' => 'q1', 'isRequired' => false, 'extraSettings' => ['optionsLowest' => 0]]
],
[
'1' => [true]
],
'The answer for question "q1" must be an integer between 0 and 5.',
],
'list-linearscale-answer' => [
[
['id' => 1, 'type' => 'linearscale', 'text' => 'q1', 'isRequired' => false, 'extraSettings' => ['optionsLowest' => 0]]
],
[
'1' => [['3']]
],
'The answer for question "q1" must be an integer between 0 and 5.',
],
'invalid-color-question' => [
// Questions
[
Expand Down
Loading