diff --git a/question/engine/questionattempt.php b/question/engine/questionattempt.php index 150174482db..bf6eb62780a 100644 --- a/question/engine/questionattempt.php +++ b/question/engine/questionattempt.php @@ -1369,8 +1369,13 @@ class question_attempt { } else { // This is the normal case. Replay the next step of the attempt. - $this->process_action($step->get_submitted_data(), - $step->get_timecreated(), $step->get_user_id(), $step->get_id()); + if ($step === $oldqa->autosavedstep) { + $this->process_autosave($step->get_submitted_data(), + $step->get_timecreated(), $step->get_user_id()); + } else { + $this->process_action($step->get_submitted_data(), + $step->get_timecreated(), $step->get_user_id(), $step->get_id()); + } } } diff --git a/question/engine/tests/questionusage_autosave_test.php b/question/engine/tests/questionusage_autosave_test.php index aa3b03fcd03..6ca87f43ad8 100644 --- a/question/engine/tests/questionusage_autosave_test.php +++ b/question/engine/tests/questionusage_autosave_test.php @@ -686,6 +686,67 @@ class question_usage_autosave_test extends qbehaviour_walkthrough_test_base { $this->delete_quba(); } + /** + * Test that regrading doesn't convert autosave steps to finished steps. + * This can result in students loosing data (due to question_out_of_sequence_exception) if a teacher + * regrades an attempt while it is in progress. + */ + public function test_autosave_and_regrade_then_display() { + $this->resetAfterTest(); + $generator = $this->getDataGenerator()->get_plugin_generator('core_question'); + $cat = $generator->create_question_category(); + $question = $generator->create_question('shortanswer', null, + array('category' => $cat->id)); + + // Start attempt at a shortanswer question. + $q = question_bank::load_question($question->id); + $this->start_attempt_at_question($q, 'deferredfeedback', 1); + + $this->check_current_state(question_state::$todo); + $this->check_current_mark(null); + $this->check_step_count(1); + + // First see if the starting sequence is right. + $this->render(); + $this->check_output_contains_hidden_input(':sequencecheck', 1); + + // Add a submission. + $this->process_submission(array('answer' => 'first response')); + $this->save_quba(); + + // Check the submission and that the sequence went up. + $this->render(); + $this->check_output_contains_text_input('answer', 'first response'); + $this->check_output_contains_hidden_input(':sequencecheck', 2); + $this->assertFalse($this->get_question_attempt()->has_autosaved_step()); + + // Add a autosave response. + $this->load_quba(); + $this->process_autosave(array('answer' => 'second response')); + $this->save_quba(); + + // Confirm that the autosave value shows up, but that the sequence hasn't increased. + $this->render(); + $this->check_output_contains_text_input('answer', 'second response'); + $this->check_output_contains_hidden_input(':sequencecheck', 2); + $this->assertTrue($this->get_question_attempt()->has_autosaved_step()); + + // Call regrade. + $this->load_quba(); + $this->quba->regrade_all_questions(); + $this->save_quba(); + + // Check and see if the autosave response is still there, that the sequence didn't increase, + // and that there is an autosave step. + $this->load_quba(); + $this->render(); + $this->check_output_contains_text_input('answer', 'second response'); + $this->check_output_contains_hidden_input(':sequencecheck', 2); + $this->assertTrue($this->get_question_attempt()->has_autosaved_step()); + + $this->delete_quba(); + } + protected function tearDown() { // This test relies on the destructor for the second DB connection being called before running the next test. // Without this change - there will be unit test failures on "some" DBs (MySQL).