From 1c2e05c0606df58e12eb9f2e9bc7e59351f395eb Mon Sep 17 00:00:00 2001 From: Tim Hunt Date: Wed, 15 Oct 2014 16:31:25 +0100 Subject: [PATCH] MDL-47691 quiz: only warn re overdue attempts if a Q has been answered If a quiz is set to have a grace period when time has expired, we only email studnets to warn them about their overdue attempt if they have put in an answer to at least one question that is worth some marks. --- mod/quiz/attemptlib.php | 21 +++++++ mod/quiz/db/events.php | 9 --- mod/quiz/locallib.php | 67 ++++++--------------- mod/quiz/tests/attempt_walkthrough_test.php | 13 +++- mod/quiz/upgrade.txt | 4 ++ mod/quiz/version.php | 4 +- 6 files changed, 57 insertions(+), 61 deletions(-) diff --git a/mod/quiz/attemptlib.php b/mod/quiz/attemptlib.php index 1be2baa3929..fe7044a25e5 100644 --- a/mod/quiz/attemptlib.php +++ b/mod/quiz/attemptlib.php @@ -758,6 +758,24 @@ class quiz_attempt { array_intersect(array_keys($teachersgroups), array_keys($studentsgroups)); } + /** + * Has the student, in this attempt, engaged with the quiz in a non-trivial way? + * That is, is there any question worth a non-zero number of marks, where + * the student has made some response that we have saved? + * @return bool true if we have saved a response for at least one graded question. + */ + public function has_response_to_at_least_one_graded_question() { + foreach ($this->quba->get_attempt_iterator() as $qa) { + if ($qa->get_max_mark() == 0) { + continue; + } + if ($qa->get_num_steps() > 1) { + return true; + } + } + return false; + } + /** * Get extra summary information about this attempt. * @@ -772,6 +790,7 @@ class quiz_attempt { * The values are arrays with two items, title and content. Each of these * will be either a string, or a renderable. * + * @param question_display_options $options the display options for this quiz attempt at this time. * @return array as described above. */ public function get_additional_summary_data(question_display_options $options) { @@ -1523,6 +1542,8 @@ class quiz_attempt { $this->fire_state_transition_event('\mod_quiz\event\attempt_becameoverdue', $timestamp); $transaction->allow_commit(); + + quiz_send_overdue_message($this); } /** diff --git a/mod/quiz/db/events.php b/mod/quiz/db/events.php index 50c12e5be1d..46f0060957a 100644 --- a/mod/quiz/db/events.php +++ b/mod/quiz/db/events.php @@ -50,15 +50,6 @@ $observers = array( 'callback' => '\mod_quiz\group_observers::group_member_removed', ), - // Handle our own \mod_quiz\event\attempt_becameoverdue event, to email - // the student to let them know they forgot to submit, and that they have another chance. - array( - 'eventname' => '\mod_quiz\event\attempt_becameoverdue', - 'includefile' => '/mod/quiz/locallib.php', - 'callback' => 'quiz_attempt_overdue_handler', - 'internal' => false, - ), - // Handle our own \mod_quiz\event\attempt_submitted event, as a way to // send confirmation messages asynchronously. array( diff --git a/mod/quiz/locallib.php b/mod/quiz/locallib.php index 6e8ac7b4e1e..8ea4888f693 100644 --- a/mod/quiz/locallib.php +++ b/mod/quiz/locallib.php @@ -1546,52 +1546,47 @@ function quiz_send_notification_messages($course, $quiz, $attempt, $context, $cm /** * Send the notification message when a quiz attempt becomes overdue. * - * @param object $course the course - * @param object $quiz the quiz - * @param object $attempt this attempt just finished - * @param object $context the quiz context - * @param object $cm the coursemodule for this quiz + * @param quiz_attempt $attemptobj all the data about the quiz attempt. */ -function quiz_send_overdue_message($course, $quiz, $attempt, $context, $cm) { +function quiz_send_overdue_message($attemptobj) { global $CFG, $DB; - // Do nothing if required objects not present. - if (empty($course) or empty($quiz) or empty($attempt) or empty($context)) { - throw new coding_exception('$course, $quiz, $attempt, $context and $cm must all be set.'); - } + $submitter = $DB->get_record('user', array('id' => $attemptobj->get_userid()), '*', MUST_EXIST); - $submitter = $DB->get_record('user', array('id' => $attempt->userid), '*', MUST_EXIST); + if (!$attemptobj->has_capability('mod/quiz:emailwarnoverdue', $submitter->id, false)) { + return; // Message not required. + } - if (!has_capability('mod/quiz:emailwarnoverdue', $context, $submitter, false)) { + if (!$attemptobj->has_response_to_at_least_one_graded_question()) { return; // Message not required. } // Prepare lots of useful information that admins might want to include in // the email message. - $quizname = format_string($quiz->name); + $quizname = format_string($attemptobj->get_quiz_name()); $deadlines = array(); - if ($quiz->timelimit) { - $deadlines[] = $attempt->timestart + $quiz->timelimit; + if ($attemptobj->get_quiz()->timelimit) { + $deadlines[] = $attemptobj->get_attempt()->timestart + $attemptobj->get_quiz()->timelimit; } - if ($quiz->timeclose) { - $deadlines[] = $quiz->timeclose; + if ($attemptobj->get_quiz()->timeclose) { + $deadlines[] = $attemptobj->get_quiz()->timeclose; } $duedate = min($deadlines); - $graceend = $duedate + $quiz->graceperiod; + $graceend = $duedate + $attemptobj->get_quiz()->graceperiod; $a = new stdClass(); // Course info. - $a->coursename = $course->fullname; - $a->courseshortname = $course->shortname; + $a->coursename = format_string($attemptobj->get_course()->fullname); + $a->courseshortname = format_string($attemptobj->get_course()->shortname); // Quiz info. $a->quizname = $quizname; - $a->quizurl = $CFG->wwwroot . '/mod/quiz/view.php?id=' . $cm->id; + $a->quizurl = $attemptobj->view_url(); $a->quizlink = '' . $quizname . ''; // Attempt info. - $a->attemptduedate = userdate($duedate); + $a->attemptduedate = userdate($duedate); $a->attemptgraceend = userdate($graceend); - $a->attemptsummaryurl = $CFG->wwwroot . '/mod/quiz/summary.php?attempt=' . $attempt->id; + $a->attemptsummaryurl = $attemptobj->summary_url()->out(false); $a->attemptsummarylink = '' . $quizname . ' review'; // Student's info. $a->studentidnumber = $submitter->idnumber; @@ -1649,32 +1644,6 @@ function quiz_attempt_submitted_handler($event) { context_module::instance($cm->id), $cm); } -/** - * Handle the quiz_attempt_overdue event. - * - * For quizzes with applicable settings, this sends a message to the user, reminding - * them that they forgot to submit, and that they have another chance to do so. - * - * @param object $event the event object. - */ -function quiz_attempt_overdue_handler($event) { - global $DB; - - $course = $DB->get_record('course', array('id' => $event->courseid)); - $attempt = $event->get_record_snapshot('quiz_attempts', $event->objectid); - $quiz = $event->get_record_snapshot('quiz', $attempt->quiz); - $cm = get_coursemodule_from_id('quiz', $event->get_context()->instanceid, $event->courseid); - - if (!($course && $quiz && $cm && $attempt)) { - // Something has been deleted since the event was raised. Therefore, the - // event is no longer relevant. - return true; - } - - return quiz_send_overdue_message($course, $quiz, $attempt, - context_module::instance($cm->id), $cm); -} - /** * Handle groups_member_added event * diff --git a/mod/quiz/tests/attempt_walkthrough_test.php b/mod/quiz/tests/attempt_walkthrough_test.php index 282e453ded7..1b2ae5158ac 100644 --- a/mod/quiz/tests/attempt_walkthrough_test.php +++ b/mod/quiz/tests/attempt_walkthrough_test.php @@ -84,6 +84,8 @@ class mod_quiz_attempt_walkthrough_testcase extends advanced_testcase { // Process some responses from the student. $attemptobj = quiz_attempt::create($attempt->id); + $this->assertFalse($attemptobj->has_response_to_at_least_one_graded_question()); + $prefix1 = $quba->get_field_prefix(1); $prefix2 = $quba->get_field_prefix(2); @@ -94,6 +96,7 @@ class mod_quiz_attempt_walkthrough_testcase extends advanced_testcase { // Finish the attempt. $attemptobj = quiz_attempt::create($attempt->id); + $this->assertTrue($attemptobj->has_response_to_at_least_one_graded_question()); $attemptobj->process_finish($timenow, false); // Re-load quiz attempt data. @@ -105,6 +108,7 @@ class mod_quiz_attempt_walkthrough_testcase extends advanced_testcase { $this->assertEquals(true, $attemptobj->is_finished()); $this->assertEquals($timenow, $attemptobj->get_submitted_date()); $this->assertEquals($user1->id, $attemptobj->get_userid()); + $this->assertTrue($attemptobj->has_response_to_at_least_one_graded_question()); // Check quiz grades. $grades = quiz_get_user_grades($quiz, $user1->id); @@ -180,6 +184,7 @@ class mod_quiz_attempt_walkthrough_testcase extends advanced_testcase { // Process some responses from the student. $attemptobj = quiz_attempt::create($attempt->id); + $this->assertFalse($attemptobj->has_response_to_at_least_one_graded_question()); $tosubmit = array(); $selectedquestionid = $quba->get_question_attempt(1)->get_question()->id; @@ -195,6 +200,7 @@ class mod_quiz_attempt_walkthrough_testcase extends advanced_testcase { // Finish the attempt. $attemptobj = quiz_attempt::create($attempt->id); + $this->assertTrue($attemptobj->has_response_to_at_least_one_graded_question()); $attemptobj->process_finish($timenow, false); // Re-load quiz attempt data. @@ -206,6 +212,7 @@ class mod_quiz_attempt_walkthrough_testcase extends advanced_testcase { $this->assertEquals(true, $attemptobj->is_finished()); $this->assertEquals($timenow, $attemptobj->get_submitted_date()); $this->assertEquals($user1->id, $attemptobj->get_userid()); + $this->assertTrue($attemptobj->has_response_to_at_least_one_graded_question()); // Check quiz grades. $grades = quiz_get_user_grades($quiz, $user1->id); @@ -275,11 +282,15 @@ class mod_quiz_attempt_walkthrough_testcase extends advanced_testcase { // Process some responses from the student. $attemptobj = quiz_attempt::create($attempt->id); + $this->assertFalse($attemptobj->has_response_to_at_least_one_graded_question()); + $tosubmit = array(1 => array('answer' => $correctresponse)); $attemptobj->process_submitted_actions($timenow, false, $tosubmit); // Finish the attempt. $attemptobj = quiz_attempt::create($attempt->id); + $this->assertTrue($attemptobj->has_response_to_at_least_one_graded_question()); + $attemptobj->process_finish($timenow, false); // Re-load quiz attempt data. @@ -291,6 +302,7 @@ class mod_quiz_attempt_walkthrough_testcase extends advanced_testcase { $this->assertEquals(true, $attemptobj->is_finished()); $this->assertEquals($timenow, $attemptobj->get_submitted_date()); $this->assertEquals($user1->id, $attemptobj->get_userid()); + $this->assertTrue($attemptobj->has_response_to_at_least_one_graded_question()); // Check quiz grades. $grades = quiz_get_user_grades($this->quizwithvariants, $user1->id); @@ -303,5 +315,4 @@ class mod_quiz_attempt_walkthrough_testcase extends advanced_testcase { $gradebookgrade = array_shift($gradebookitem->grades); $this->assertEquals(100, $gradebookgrade->grade); } - } diff --git a/mod/quiz/upgrade.txt b/mod/quiz/upgrade.txt index 0f38061900e..682124c733d 100644 --- a/mod/quiz/upgrade.txt +++ b/mod/quiz/upgrade.txt @@ -6,6 +6,10 @@ This files describes API changes in the quiz code. folder to take advantage of auto-loading. This has involved renaming them. see the list in mod/quiz/db/renamedclasses.php. +* The quiz no longer handles its own \mod_quiz\event\attempt_becameoverdue event, + and so the event handler function quiz_attempt_overdue_handler has been deleted. + Also, the internal function quiz_send_overdue_message has add the arguments + changed. It now takes the $attemptobj object, not separate stdClass objects. * Major changes to the Edit quiz page. diff --git a/mod/quiz/version.php b/mod/quiz/version.php index 808d627f84f..dcc06bbcee9 100644 --- a/mod/quiz/version.php +++ b/mod/quiz/version.php @@ -15,7 +15,7 @@ // along with Moodle. If not, see . /** - * Quiz statistics report version information. + * Quiz activity version information. * * @package mod_quiz * @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com} @@ -24,7 +24,7 @@ defined('MOODLE_INTERNAL') || die(); -$plugin->version = 2014100200; // The current module version (Date: YYYYMMDDXX). +$plugin->version = 2014101500; // The current module version (Date: YYYYMMDDXX). $plugin->requires = 2014050800; // Requires this Moodle version. $plugin->component = 'mod_quiz'; // Full name of the plugin (used for diagnostics). $plugin->cron = 60; -- 2.36.1