Fixed Survey bug in handling of logical sections

This commit is contained in:
Patrick Donelan 2009-05-20 10:17:46 +00:00
parent c64a1de9f3
commit 867aa51507
2 changed files with 138 additions and 97 deletions

View file

@ -1616,6 +1616,22 @@ sub www_loadQuestions {
my @questions;
eval { @questions = $self->responseJSON->nextQuestions(); };
# Logical sections cause nextResponse to move when nextQuestions is called, so
# persist and changes, and repeat the surveyEnd check in case we are now at the end
$self->persistResponseJSON();
if ( $self->responseJSON->surveyEnd() ) {
$self->session->log->debug('surveyEnd, probably as a result of a Logical Section');
if ( $self->get('quizModeSummary') ) {
if(! $self->session->form->param('shownsummary')){
my ($summary,$html) = $self->getSummary();
my $json = to_json( { type => 'summary', summary => $summary, html => $html });
$self->session->http->setMimeType('application/json');
return $json;
}
}
return $self->surveyEnd();
}
my $section = $self->responseJSON->nextResponseSection();
#return $self->prepareShowSurveyTemplate($section,$questions);

View file

@ -521,10 +521,7 @@ sub recordResponses {
# We want to record responses against the "next" response section and questions, since these are
# the items that have just been displayed to the user.
my $section = $self->nextResponseSection();
my @questions = $self->nextQuestions();
#GOTO jumps in the Survey. Order of precedence is Answer, Question, then Section.
my ($sectionGoto, $questionGoto, $answerGoto, $sectionExpression, $questionExpression, $answerExpression);
# Handle terminal Section..
@ -543,112 +540,118 @@ sub recordResponses {
$sectionExpression = $section->{gotoExpression};
}
# Handle empty Section..
if ( !@questions and !$section->{logical}) {
# No questions to process, so increment lastResponse and return
$self->lastResponse( $self->nextResponse );
return $sTerminal ? { terminal => $terminalUrl} : {};
}
my $logicalSection = $section->{logical};
# Process Questions in Section..
my $terminal = 0;
my $allRequiredQsAnswered = 1;
for my $question (@questions) {
my $aAnswered = 0;
my @questions;
if (!$logicalSection) {
# N.B. Important that nextQuestions is not called for logicalSetions, since
# logical sections cause this sub to be called from nextQuestions() in the first place!
@questions = $self->nextQuestions();
for my $question (@questions) {
my $aAnswered = 0;
# Handle terminal Questions..
if ( $question->{terminal} ) {
$terminal = 1;
$terminalUrl = $question->{terminalUrl};
}
# ..and also gotos..
elsif ( $question->{goto} =~ /\w/ ) {
$questionGoto = $question->{goto};
}
# .. and also gotoExpressions..
elsif ( $question->{gotoExpression} =~ /\w/ ) {
$questionExpression = $question->{gotoExpression};
}
# Record Question comment
$self->responses->{ $question->{id} }->{comment} = $submittedResponses->{ $question->{id} . 'comment' };
# Process Answers in Question..
for my $answer ( @{ $question->{answers} } ) {
# Pluck the values out of the responses hash that we want to record..
my $submittedAnswerResponse = $submittedResponses->{ $answer->{id} };
my $submittedAnswerComment = $submittedResponses->{ $answer->{id} . 'comment' };
my $submittedAnswerVerbatim = $submittedResponses->{ $answer->{id} . 'verbatim' };
# Server-side Validation and storing of extra data for special q types goes here
if($question->{questionType} eq 'Number'){
if($answer->{max} =~ /\d/ and $submittedAnswerResponse > $answer->{max}){
next;
}elsif($answer->{min} =~ /\d/ and $submittedAnswerResponse < $answer->{min}){
next;
}elsif($answer->{step} =~ /\d/ and $submittedAnswerResponse % $answer->{step} != 0){
next;
}
} elsif ($question->{questionType} eq 'Year Month'){
# store year and month as "YYYY Month"
$submittedAnswerResponse = $submittedResponses->{ $answer->{id} . '-year' } . " " . $submittedResponses->{ $answer->{id} . '-month' };
} else {
if ( !defined $submittedAnswerResponse || $submittedAnswerResponse !~ /\S/ ) {
$self->session->log->debug("Skipping invalid submitted answer response: $submittedAnswerResponse") if $submittedAnswerResponse;
next;
}
}
# If we reach here, answer validated ok
$aAnswered = 1;
# Now, decide what to record. For multi-choice questions, use recordedAnswer.
# Otherwise, we use the (raw) submitted response (e.g. text input, date input etc..)
$self->responses->{ $answer->{id} }->{value}
= $knownTypes{ $question->{questionType} }
? $submittedAnswerResponse
: $answer->{recordedAnswer};
$self->responses->{ $answer->{id} }->{verbatim} = $answer->{verbatim} ? $submittedAnswerVerbatim : undef;
$self->responses->{ $answer->{id} }->{time} = time;
$self->responses->{ $answer->{id} }->{comment} = $submittedAnswerComment;
# Handle terminal Answers..
if ( $answer->{terminal} ) {
# Handle terminal Questions..
if ( $question->{terminal} ) {
$terminal = 1;
$terminalUrl = $answer->{terminalUrl};
$terminalUrl = $question->{terminalUrl};
}
# ..and also gotos..
elsif ( $answer->{goto} =~ /\w/ ) {
$answerGoto = $answer->{goto};
elsif ( $question->{goto} =~ /\w/ ) {
$questionGoto = $question->{goto};
}
# .. and also gotoExpressions..
elsif ( $answer->{gotoExpression} =~ /\w/ ) {
$answerExpression = $answer->{gotoExpression};
elsif ( $question->{gotoExpression} =~ /\w/ ) {
$questionExpression = $question->{gotoExpression};
}
}
# Check if a required Question was skipped
if ( $question->{required} && !$aAnswered ) {
$allRequiredQsAnswered = 0;
}
# Record Question comment
$self->responses->{ $question->{id} }->{comment} = $submittedResponses->{ $question->{id} . 'comment' };
# If question was answered, increment the questionsAnswered count..
if ($aAnswered) {
$self->questionsAnswered(+1);
# Process Answers in Question..
for my $answer ( @{ $question->{answers} } ) {
# Pluck the values out of the responses hash that we want to record..
my $submittedAnswerResponse = $submittedResponses->{ $answer->{id} };
my $submittedAnswerComment = $submittedResponses->{ $answer->{id} . 'comment' };
my $submittedAnswerVerbatim = $submittedResponses->{ $answer->{id} . 'verbatim' };
# Server-side Validation and storing of extra data for special q types goes here
if($question->{questionType} eq 'Number'){
if($answer->{max} =~ /\d/ and $submittedAnswerResponse > $answer->{max}){
next;
}elsif($answer->{min} =~ /\d/ and $submittedAnswerResponse < $answer->{min}){
next;
}elsif($answer->{step} =~ /\d/ and $submittedAnswerResponse % $answer->{step} != 0){
next;
}
} elsif ($question->{questionType} eq 'Year Month'){
# store year and month as "YYYY Month"
$submittedAnswerResponse = $submittedResponses->{ $answer->{id} . '-year' } . " " . $submittedResponses->{ $answer->{id} . '-month' };
} else {
if ( !defined $submittedAnswerResponse || $submittedAnswerResponse !~ /\S/ ) {
$self->session->log->debug("Skipping invalid submitted answer response: $submittedAnswerResponse") if $submittedAnswerResponse;
next;
}
}
# If we reach here, answer validated ok
$aAnswered = 1;
# Now, decide what to record. For multi-choice questions, use recordedAnswer.
# Otherwise, we use the (raw) submitted response (e.g. text input, date input etc..)
$self->responses->{ $answer->{id} }->{value}
= $knownTypes{ $question->{questionType} }
? $submittedAnswerResponse
: $answer->{recordedAnswer};
$self->responses->{ $answer->{id} }->{verbatim} = $answer->{verbatim} ? $submittedAnswerVerbatim : undef;
$self->responses->{ $answer->{id} }->{time} = time;
$self->responses->{ $answer->{id} }->{comment} = $submittedAnswerComment;
# Handle terminal Answers..
if ( $answer->{terminal} ) {
$terminal = 1;
$terminalUrl = $answer->{terminalUrl};
}
# ..and also gotos..
elsif ( $answer->{goto} =~ /\w/ ) {
$answerGoto = $answer->{goto};
}
# .. and also gotoExpressions..
elsif ( $answer->{gotoExpression} =~ /\w/ ) {
$answerExpression = $answer->{gotoExpression};
}
}
# Check if a required Question was skipped
if ( $question->{required} && !$aAnswered ) {
$allRequiredQsAnswered = 0;
}
# If question was answered, increment the questionsAnswered count..
if ($aAnswered) {
$self->questionsAnswered(+1);
}
}
}
# If all required responses were given, proceed onwards!
if ($allRequiredQsAnswered) {
if ($allRequiredQsAnswered && !$logicalSection) {
# Move the lastResponse index to the last question answered
$self->lastResponse( $self->lastResponse + @questions );
}
if ($allRequiredQsAnswered || $logicalSection) {
# Process jumps and jump expressions in precedence order of:
# answer goto, answer expression, question goto, question expression, section..
@ -666,18 +669,20 @@ sub recordResponses {
if ($action && ref $action eq 'HASH') {
return $action;
}
# Handle next logic Section..
my $section = $self->nextResponseSection();
if ( $section and $section->{logical} ) {
return $self->recordResponses( {} );
}
}
else {
if (!$allRequiredQsAnswered) {
# Required responses were missing, so we don't let the Survey terminate
$terminal = 0;
}
# Handle special cases down here, after we've given sections a chance for their jump [expressions] to run
if ( !@questions || $logicalSection ) {
# No questions to be (or should be) displayed, so increment lastResponse and return
$self->lastResponse( $self->nextResponse );
return $sTerminal ? { terminal => $terminalUrl } : {};
}
if ( $sTerminal && $self->nextResponseSectionIndex != $self->lastResponseSectionIndex ) {
$terminal = 1;
}
@ -1070,6 +1075,26 @@ sub nextQuestions {
# Get some information about the Section that the next response belongs to..
my $section = $self->nextResponseSection();
# Logical sections get processed immediately rather than displayed
if ($section->{logical}) {
my $nextResponse = $self->nextResponse;
$self->session->log->debug("Processing logical section");
# Pass off to recordResponses, which will process expressions and increment nextResponse
$self->recordResponses({});
# Explicitly check that nextResponse was incremented, lest we end up with an infinite loop
if ($nextResponse == $self->nextResponse) {
$self->session->log->error("Something bad happened in Survey logic, bailing out to avoid infinite loop");
} else {
$self->session->log->debug("nextResponse has been updated to " . $self->nextResponse);
# ..and then start over
return $self->nextQuestions;
}
}
my $sectionIndex = $self->nextResponseSectionIndex;
my $questionsPerPage = $self->survey->section( [ $self->nextResponseSectionIndex ] )->{questionsPerPage};