diff --git a/lib/questionlib.php b/lib/questionlib.php index 930189554fb..3f2799c48fe 100644 --- a/lib/questionlib.php +++ b/lib/questionlib.php @@ -602,7 +602,7 @@ function get_question_options(&$questions) { * @param object $attempt The attempt for which the question sessions are * to be restored or created. */ -function get_question_states(&$questions, $cmoptions, $attempt) { +function get_question_states(&$questions, $cmoptions, $attempt, $lastattemptid=null) { global $CFG, $QTYPES; // get the question ids @@ -641,8 +641,33 @@ function get_question_states(&$questions, $cmoptions, $attempt) { $states[$i]->last_graded = clone($states[$i]); } } else { - // create a new empty state - $states[$i] = new object; + // If the new attempt is to be based on a previous attempt get it and clean things + // Having lastattemptid filled implies that (should we double check?): + // $attempt->attempt > 1 and $cmoptions->attemptonlast and !$attempt->preview + if ($lastattemptid) { + // find the responses from the previous attempt and save them to the new session + + // Load the last graded state for the question + $statefields = 'n.questionid as question, s.*, n.sumpenalty'; + $sql = "SELECT $statefields". + " FROM {$CFG->prefix}question_states s,". + " {$CFG->prefix}question_sessions n". + " WHERE s.id = n.newgraded". + " AND n.attemptid = '$lastattemptid'". + " AND n.questionid = '$i'"; + if (!$laststate = get_record_sql($sql)) { + // Only restore previous responses that have been graded + continue; + } + // Restore the state so that the responses will be restored + restore_question_state($questions[$i], $laststate); + $states[$i] = clone ($laststate); + } else { + // create a new empty state + $states[$i] = new object; + } + + // now fill/overide initial values $states[$i]->attempt = $attempt->uniqueid; $states[$i]->question = (int) $i; $states[$i]->seq_number = 0; @@ -653,15 +678,36 @@ function get_question_states(&$questions, $cmoptions, $attempt) { $states[$i]->penalty = 0; $states[$i]->sumpenalty = 0; $states[$i]->manualcomment = ''; - $states[$i]->responses = array('' => ''); + + // if building on last attempt we want to preserve responses + if (!$lastattemptid) { + $states[$i]->responses = array('' => ''); + } // Prevent further changes to the session from incrementing the // sequence number $states[$i]->changed = true; - // Create the empty question type specific information - if (!$QTYPES[$questions[$i]->qtype]->create_session_and_responses( - $questions[$i], $states[$i], $cmoptions, $attempt)) { - return false; + if ($lastattemptid) { + // prepare the previous responses for new processing + $action = new stdClass; + $action->responses = $laststate->responses; + $action->timestamp = $laststate->timestamp; + $action->event = QUESTION_EVENTSAVE; //emulate save of questions from all pages MDL-7631 + + // Process these responses ... + question_process_responses($questions[$i], $states[$i], $action, $cmoptions, $attempt); + + // Fix for Bug #5506: When each attempt is built on the last one, + // preserve the options from any previous attempt. + if ( isset($laststate->options) ) { + $states[$i]->options = $laststate->options; + } + } else { + // Create the empty question type specific information + if (!$QTYPES[$questions[$i]->qtype]->create_session_and_responses( + $questions[$i], $states[$i], $cmoptions, $attempt)) { + return false; + } } $states[$i]->last_graded = clone($states[$i]); } @@ -989,8 +1035,10 @@ function question_process_responses(&$question, &$state, $action, $cmoptions, &$ // Check for unchanged responses (exactly unchanged, not equivalent). // We also have to catch questions that the student has not yet attempted - $sameresponses = !$state->last_graded->event == QUESTION_EVENTOPEN && - $QTYPES[$question->qtype]->compare_responses($question, $action, $state); + $sameresponses = $QTYPES[$question->qtype]->compare_responses($question, $action, $state); + if ($state->last_graded->event == QUESTION_EVENTOPEN && question_isgradingevent($action->event)) { + $sameresponses = false; + } // If the response has not been changed then we do not have to process it again // unless the attempt is closing or validation is requested diff --git a/mod/quiz/attempt.php b/mod/quiz/attempt.php index 79149e925e5..87e5ff364a4 100644 --- a/mod/quiz/attempt.php +++ b/mod/quiz/attempt.php @@ -283,9 +283,17 @@ error('Could not load question options'); } + // If the new attempt is to be based on a previous attempt find its id + if ($newattempt and $attempt->attempt > 1 and $quiz->attemptonlast and !$attempt->preview) { + // Find the previous attempt + if (!$lastattemptid = get_field('quiz_attempts', 'uniqueid', 'quiz', $attempt->quiz, 'userid', $attempt->userid, 'attempt', $attempt->attempt-1)) { + error('Could not find previous attempt to build on'); + } + } + // Restore the question sessions to their most recent states // creating new sessions where required - if (!$states = get_question_states($questions, $quiz, $attempt)) { + if (!$states = get_question_states($questions, $quiz, $attempt, $lastattemptid)) { error('Could not restore question sessions'); } @@ -296,49 +304,6 @@ } } - // If the new attempt is to be based on a previous attempt copy responses over - if ($newattempt and $attempt->attempt > 1 and $quiz->attemptonlast and !$attempt->preview) { - // Find the previous attempt - if (!$lastattemptid = get_field('quiz_attempts', 'uniqueid', 'quiz', $attempt->quiz, 'userid', $attempt->userid, 'attempt', $attempt->attempt-1)) { - error('Could not find previous attempt to build on'); - } - // For each question find the responses from the previous attempt and save them to the new session - foreach ($questions as $i => $question) { - // Load the last graded state for the question - $statefields = 'n.questionid as question, s.*, n.sumpenalty'; - $sql = "SELECT $statefields". - " FROM {$CFG->prefix}question_states s,". - " {$CFG->prefix}question_sessions n". - " WHERE s.id = n.newgraded". - " AND n.attemptid = '$lastattemptid'". - " AND n.questionid = '$i'"; - if (!$laststate = get_record_sql($sql)) { - // Only restore previous responses that have been graded - continue; - } - // Restore the state so that the responses will be restored - restore_question_state($questions[$i], $laststate); - // prepare the previous responses for new processing - $action = new stdClass; - $action->responses = $laststate->responses; - $action->timestamp = $laststate->timestamp; - $action->event = QUESTION_EVENTSAVE; //emulate save of questions from all pages MDL-7631 - - // Process these responses ... - question_process_responses($questions[$i], $states[$i], $action, $quiz, $attempt); - - // Fix for Bug #5506: When each attempt is built on the last one, - // preserve the options from any previous attempt. - if ( isset($laststate->options) ) { - $states[$i]->options = $laststate->options; - } - - // ... and save the new states - save_question_session($questions[$i], $states[$i]); - } - } - - /// Process form data ///////////////////////////////////////////////// if ($responses = data_submitted() and empty($_POST['quizpassword'])) {