From 52b8962dbd9329e4ae26f43b7458fbecc0096cd6 Mon Sep 17 00:00:00 2001 From: Tim Hunt Date: Thu, 21 Apr 2022 18:23:36 +0100 Subject: [PATCH 1/2] MDL-74543 quiz: correctly record time when overdue attempt submitted --- mod/quiz/attemptlib.php | 10 +++- mod/quiz/tests/attempt_walkthrough_test.php | 59 +++++++++++++++++++++ 2 files changed, 68 insertions(+), 1 deletion(-) diff --git a/mod/quiz/attemptlib.php b/mod/quiz/attemptlib.php index 2e0b791cf3c..a8f5ddbb39e 100644 --- a/mod/quiz/attemptlib.php +++ b/mod/quiz/attemptlib.php @@ -2469,7 +2469,15 @@ class quiz_attempt { if ($becomingabandoned) { $this->process_abandon($timenow, true); } else { - $this->process_finish($timenow, !$toolate, $toolate ? $timeclose : $timenow, true); + if (!$toolate || $this->get_quiz()->overduehandling == 'graceperiod') { + // Normally, we record the accurate finish time when the student is online. + $finishtime = $timenow; + } else { + // But, if there is no grade period, and the final responses were too + // late to be processed, record the close time, to reduce confusion. + $finishtime = $timeclose; + } + $this->process_finish($timenow, !$toolate, $finishtime, true); } } catch (question_out_of_sequence_exception $e) { diff --git a/mod/quiz/tests/attempt_walkthrough_test.php b/mod/quiz/tests/attempt_walkthrough_test.php index f8d5ddcf50d..053fb076c93 100644 --- a/mod/quiz/tests/attempt_walkthrough_test.php +++ b/mod/quiz/tests/attempt_walkthrough_test.php @@ -119,6 +119,65 @@ class mod_quiz_attempt_walkthrough_testcase extends advanced_testcase { $this->assertEquals(100, $gradebookgrade->grade); } + public function test_quiz_attempt_walkthrough_submit_time_recorded_correctly_when_overdue() { + global $SITE; + + $this->resetAfterTest(); + + // Make a quiz. + $timeclose = time() + HOURSECS; + $quizgenerator = $this->getDataGenerator()->get_plugin_generator('mod_quiz'); + + $quiz = $quizgenerator->create_instance( + ['course' => $SITE->id, 'timeclose' => $timeclose, + 'overduehandling' => 'graceperiod', 'graceperiod' => HOURSECS]); + + // Create a question. + $questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question'); + $cat = $questiongenerator->create_question_category(); + $saq = $questiongenerator->create_question('shortanswer', null, array('category' => $cat->id)); + + // Add them to the quiz. + quiz_add_quiz_question($saq->id, $quiz, 0, 1); + quiz_update_sumgrades($quiz); + + // Make a user to do the quiz. + $user = $this->getDataGenerator()->create_user(); + $this->setUser($user); + $quizobj = quiz::create($quiz->id, $user->id); + + // Start the attempt. + $attempt = quiz_prepare_and_start_new_attempt($quizobj, 1, null); + + // Process some responses from the student. + $attemptobj = quiz_attempt::create($attempt->id); + $attemptobj->process_submitted_actions($timeclose - 30 * MINSECS, false, [1 => ['answer' => 'frog']]); + + // Attempt goes overdue (e.g. if cron ran). + $attemptobj = quiz_attempt::create($attempt->id); + $attemptobj->process_going_overdue($timeclose + 2 * get_config('quiz', 'graceperiodmin'), false); + + // Verify the attempt state. + $attemptobj = quiz_attempt::create($attempt->id); + $this->assertEquals(1, $attemptobj->get_attempt_number()); + $this->assertEquals(false, $attemptobj->is_finished()); + $this->assertEquals(0, $attemptobj->get_submitted_date()); + $this->assertEquals($user->id, $attemptobj->get_userid()); + $this->assertTrue($attemptobj->has_response_to_at_least_one_graded_question()); + + // Student submits the attempt during the grace period. + $attemptobj = quiz_attempt::create($attempt->id); + $attemptobj->process_attempt($timeclose + 30 * MINSECS, true, false, 1); + + // Verify the attempt state. + $attemptobj = quiz_attempt::create($attempt->id); + $this->assertEquals(1, $attemptobj->get_attempt_number()); + $this->assertEquals(true, $attemptobj->is_finished()); + $this->assertEquals($timeclose + 30 * MINSECS, $attemptobj->get_submitted_date()); + $this->assertEquals($user->id, $attemptobj->get_userid()); + $this->assertTrue($attemptobj->has_response_to_at_least_one_graded_question()); + } + /** * Create a quiz with a random as well as other questions and walk through quiz attempts. */ From 7d3eaf892d941113120ecc6c0622347ed8593006 Mon Sep 17 00:00:00 2001 From: Tim Hunt Date: Tue, 3 May 2022 13:45:39 +0100 Subject: [PATCH 2/2] MDL-74543 quiz attemptlib: covert string comparison to === Where obviously safe. --- mod/quiz/attemptlib.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/mod/quiz/attemptlib.php b/mod/quiz/attemptlib.php index a8f5ddbb39e..ff88369dfdd 100644 --- a/mod/quiz/attemptlib.php +++ b/mod/quiz/attemptlib.php @@ -543,7 +543,7 @@ class quiz { $qcategories = array(); foreach ($this->get_questions() as $questiondata) { - if ($questiondata->qtype == 'random' and $includepotential) { + if ($questiondata->qtype === 'random' && $includepotential) { if (!isset($qcategories[$questiondata->category])) { $qcategories[$questiondata->category] = false; } @@ -1648,7 +1648,7 @@ class quiz_attempt { * @return bool whether show all on one page should be on by default. */ public function get_default_show_all($script) { - return $script == 'review' && count($this->questionpages) < self::MAX_SLOTS_FOR_DEFAULT_REVIEW_SHOW_ALL; + return $script === 'review' && count($this->questionpages) < self::MAX_SLOTS_FOR_DEFAULT_REVIEW_SHOW_ALL; } // Bits of content ========================================================= @@ -2409,7 +2409,7 @@ class quiz_attempt { $becomingoverdue = false; $becomingabandoned = false; if ($timeup) { - if ($this->get_quiz()->overduehandling == 'graceperiod') { + if ($this->get_quiz()->overduehandling === 'graceperiod') { if (is_null($graceperiodmin)) { $graceperiodmin = get_config('quiz', 'graceperiodmin'); } @@ -2469,7 +2469,7 @@ class quiz_attempt { if ($becomingabandoned) { $this->process_abandon($timenow, true); } else { - if (!$toolate || $this->get_quiz()->overduehandling == 'graceperiod') { + if (!$toolate || $this->get_quiz()->overduehandling === 'graceperiod') { // Normally, we record the accurate finish time when the student is online. $finishtime = $timenow; } else { @@ -2803,7 +2803,7 @@ abstract class quiz_nav_panel_base { $button->number = $this->attemptobj->get_question_number($slot); $button->stateclass = $qa->get_state_class($showcorrectness); $button->navmethod = $this->attemptobj->get_navigation_method(); - if (!$showcorrectness && $button->stateclass == 'notanswered') { + if (!$showcorrectness && $button->stateclass === 'notanswered') { $button->stateclass = 'complete'; } $button->statestring = $this->get_state_string($qa, $showcorrectness);