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.
This commit is contained in:
Tim Hunt 2014-10-15 16:31:25 +01:00
parent b49de5d930
commit 1c2e05c060
6 changed files with 57 additions and 61 deletions

View File

@ -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);
}
/**

View File

@ -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(

View File

@ -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);
if (!$attemptobj->has_capability('mod/quiz:emailwarnoverdue', $submitter->id, false)) {
return; // Message not required.
}
$submitter = $DB->get_record('user', array('id' => $attempt->userid), '*', MUST_EXIST);
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 = '<a href="' . $a->quizurl . '">' . $quizname . '</a>';
// 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 = '<a href="' . $a->attemptsummaryurl . '">' . $quizname . ' review</a>';
// 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
*

View File

@ -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);
}
}

View File

@ -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.

View File

@ -15,7 +15,7 @@
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* 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;