Skip to content
Open
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
7 changes: 7 additions & 0 deletions lib/Constants.php
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ class Constants {
public const ANSWER_TYPE_LONG = 'long';
public const ANSWER_TYPE_MULTIPLE = 'multiple';
public const ANSWER_TYPE_MULTIPLEUNIQUE = 'multiple_unique';
public const ANSWER_TYPE_RANKING = 'ranking';
public const ANSWER_TYPE_SHORT = 'short';
public const ANSWER_TYPE_TIME = 'time';

Expand All @@ -95,6 +96,7 @@ class Constants {
self::ANSWER_TYPE_LONG,
self::ANSWER_TYPE_MULTIPLE,
self::ANSWER_TYPE_MULTIPLEUNIQUE,
self::ANSWER_TYPE_RANKING,
self::ANSWER_TYPE_SHORT,
self::ANSWER_TYPE_TIME,
];
Expand All @@ -105,6 +107,7 @@ class Constants {
self::ANSWER_TYPE_LINEARSCALE,
self::ANSWER_TYPE_MULTIPLE,
self::ANSWER_TYPE_MULTIPLEUNIQUE,
self::ANSWER_TYPE_RANKING,
];

// AnswerTypes for date/time questions
Expand Down Expand Up @@ -191,6 +194,10 @@ class Constants {
'rows' => ['array'],
];

public const EXTRA_SETTINGS_RANKING = [
'shuffleOptions' => ['boolean'],
];

public const EXTRA_SETTINGS_GRID_QUESTION_TYPE = [
self::ANSWER_GRID_TYPE_CHECKBOX,
self::ANSWER_GRID_TYPE_NUMBER,
Expand Down
16 changes: 16 additions & 0 deletions lib/Controller/ApiController.php
Original file line number Diff line number Diff line change
Expand Up @@ -1736,6 +1736,22 @@ private function storeAnswersForQuestion(Form $form, $submissionId, array $quest
return;
}

if ($question['type'] === Constants::ANSWER_TYPE_RANKING) {
if (!$answerArray) {
return;
}

$answerEntity = new Answer();
$answerEntity->setSubmissionId($submissionId);
$answerEntity->setQuestionId($question['id']);

$answerText = json_encode($answerArray);
$answerEntity->setText($answerText);
$this->answerMapper->insert($answerEntity);

return;
}

Comment on lines +1739 to +1754
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This if-branch is exactly the same as for grid questions. Please remove it and adjust the condition above.

foreach ($answerArray as $answer) {
$answerEntity = new Answer();
$answerEntity->setSubmissionId($submissionId);
Expand Down
3 changes: 3 additions & 0 deletions lib/Service/FormsService.php
Original file line number Diff line number Diff line change
Expand Up @@ -810,6 +810,9 @@ public function areExtraSettingsValid(array $extraSettings, string $questionType
case Constants::ANSWER_TYPE_GRID:
$allowed = Constants::EXTRA_SETTINGS_GRID;
break;
case Constants::ANSWER_TYPE_RANKING:
$allowed = Constants::EXTRA_SETTINGS_RANKING;
break;
case Constants::ANSWER_TYPE_TIME:
$allowed = Constants::EXTRA_SETTINGS_TIME;
break;
Expand Down
35 changes: 34 additions & 1 deletion lib/Service/SubmissionService.php
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,8 @@ public function getSubmissionsData(Form $form, string $fileFormat, ?File $file =
$gridRowsPerQuestionId = [];
/** @var array<int, array<int, string>> $gridColumnsPerQuestionId */
$gridColumnsPerQuestionId = [];
/** @var array<int, list<int>> $rankingOptionsPerQuestionId */
$rankingOptionsPerQuestionId = [];

$optionPerOptionId = [];
foreach ($questions as $question) {
Expand Down Expand Up @@ -280,6 +282,15 @@ public function getSubmissionsData(Form $form, string $fileFormat, ?File $file =
}
}
}
} elseif ($question->getType() === Constants::ANSWER_TYPE_RANKING) {
$options = $this->optionMapper->findByQuestion($question->getId());
foreach ($options as $option) {
$optionPerOptionId[$option->getId()] = $option;
$rankingOptionsPerQuestionId[$question->getId()][] = $option->getId();
}
foreach ($rankingOptionsPerQuestionId[$question->getId()] as $optionId) {
$header[] = $question->getText() . ' (' . $optionPerOptionId[$optionId]->getText() . ')';
}
} else {
$header[] = $question->getText();
}
Expand Down Expand Up @@ -311,7 +322,7 @@ public function getSubmissionsData(Form $form, string $fileFormat, ?File $file =

// Answers, make sure we keep the question order
$answers = array_reduce($this->answerMapper->findBySubmission($submission->getId()),
function (array $carry, Answer $answer) use ($questionPerQuestionId, $gridRowsPerQuestionId, $gridColumnsPerQuestionId, $optionPerOptionId) {
function (array $carry, Answer $answer) use ($questionPerQuestionId, $gridRowsPerQuestionId, $gridColumnsPerQuestionId, $rankingOptionsPerQuestionId, $optionPerOptionId) {
$questionId = $answer->getQuestionId();
$questionType = isset($questionPerQuestionId[$questionId]) ? $questionPerQuestionId[$questionId]->getType() : null;

Expand Down Expand Up @@ -354,6 +365,14 @@ function (array $carry, Answer $answer) use ($questionPerQuestionId, $gridRowsPe
}
}
$carry[$questionId] = ['columns' => $columns];
} elseif ($questionType === Constants::ANSWER_TYPE_RANKING) {
$rankedIds = json_decode($answer->getText(), true);
$columns = [];
foreach ($rankingOptionsPerQuestionId[$questionId] as $optionId) {
$position = array_search($optionId, $rankedIds);
$columns[] = $position !== false ? $position + 1 : '';
}
$carry[$questionId] = ['columns' => $columns];
} else {
if (array_key_exists($questionId, $carry)) {
$carry[$questionId] .= '; ' . $answer->getText();
Expand Down Expand Up @@ -510,6 +529,7 @@ public function validateSubmission(array $questions, array $answers, string $for
} elseif ($answersCount > 1
&& $question['type'] !== Constants::ANSWER_TYPE_FILE
&& $question['type'] !== Constants::ANSWER_TYPE_GRID
&& $question['type'] !== Constants::ANSWER_TYPE_RANKING
&& !($question['type'] === Constants::ANSWER_TYPE_DATE && isset($question['extraSettings']['dateRange'])
|| $question['type'] === Constants::ANSWER_TYPE_TIME && isset($question['extraSettings']['timeRange']))) {
// Check if non-multiple questions have not more than one answer
Expand Down Expand Up @@ -561,6 +581,19 @@ public function validateSubmission(array $questions, array $answers, string $for
throw new \InvalidArgumentException(sprintf('Invalid input for question "%s".', $question['text']));
}

// Handle ranking questions: answers must be a permutation of all option IDs
if ($question['type'] === Constants::ANSWER_TYPE_RANKING) {
$optionIds = array_map('intval', array_column($question['options'] ?? [], 'id'));
$rankedIds = array_map('intval', $answers[$questionId]);
$sortedRanked = $rankedIds;
$sortedOptions = $optionIds;
sort($sortedRanked);
sort($sortedOptions);
if ($sortedRanked !== $sortedOptions) {
Comment on lines +588 to +592
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
$sortedRanked = $rankedIds;
$sortedOptions = $optionIds;
sort($sortedRanked);
sort($sortedOptions);
if ($sortedRanked !== $sortedOptions) {
if (sort($rankedIds) !== sort($optionIds)) {

throw new \InvalidArgumentException(sprintf('Ranking for question "%s" must include all options exactly once.', $question['text']));
}
}

// Handle color questions
if (
$question['type'] === Constants::ANSWER_TYPE_COLOR
Expand Down
Loading
Loading