diff --git a/mod/quiz/attemptlib.php b/mod/quiz/attemptlib.php index adf1182387591..aedd15c973d43 100644 --- a/mod/quiz/attemptlib.php +++ b/mod/quiz/attemptlib.php @@ -995,14 +995,6 @@ public function check_file_access($slot, $reviewing, $contextid, $component, $component, $filearea, $args, $forcedownload); } - /** - * Triggers the sending of the notification emails at the end of this attempt. - */ - public function quiz_send_notification_emails() { - quiz_send_notification_emails($this->get_course(), $this->get_quiz(), $this->attempt, - $this->quizobj->get_context(), $this->get_cm()); - } - /** * Get the navigation panel object for this attempt. * @@ -1080,7 +1072,7 @@ public function save_question_flags() { } public function finish_attempt($timestamp) { - global $DB; + global $DB, $USER; $this->quba->process_all_actions($timestamp); $this->quba->finish_all_questions($timestamp); @@ -1093,7 +1085,21 @@ public function finish_attempt($timestamp) { if (!$this->is_preview()) { quiz_save_best_grade($this->get_quiz()); - $this->quiz_send_notification_emails(); + + // Trigger event + $eventdata = new stdClass(); + $eventdata->component = 'mod_quiz'; + $eventdata->attemptid = $this->attempt->id; + $eventdata->timefinish = $this->attempt->timefinish; + $eventdata->userid = $this->attempt->userid; + $eventdata->submitterid = $USER->id; + $eventdata->quizid = $this->get_quizid(); + $eventdata->cmid = $this->get_cmid(); + $eventdata->courseid = $this->get_courseid(); + events_trigger('quiz_attempt_submitted', $eventdata); + + // Clear the password check flag in the session. + $this->get_access_manager($timestamp)->clear_password_access(); } } diff --git a/mod/quiz/db/access.php b/mod/quiz/db/access.php index a792b6ac0aa8d..485cc246a44e3 100644 --- a/mod/quiz/db/access.php +++ b/mod/quiz/db/access.php @@ -149,14 +149,14 @@ 'archetypes' => array() ), - // Receive email confirmation of own quiz submission + // Receive a confirmation message of own quiz submission. 'mod/quiz:emailconfirmsubmission' => array( 'captype' => 'read', 'contextlevel' => CONTEXT_MODULE, 'archetypes' => array() ), - // Receive email notification of other peoples quiz submissions + // Receive a notification message of other peoples' quiz submissions. 'mod/quiz:emailnotifysubmission' => array( 'captype' => 'read', 'contextlevel' => CONTEXT_MODULE, diff --git a/mod/quiz/db/events.php b/mod/quiz/db/events.php new file mode 100755 index 0000000000000..bfb7b5c3527b1 --- /dev/null +++ b/mod/quiz/db/events.php @@ -0,0 +1,61 @@ +. + +/** + * Post-install code for the quiz module. + * + * @package mod + * @subpackage quiz + * @copyright 2011 The Open University + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + + +defined('MOODLE_INTERNAL') || die(); + + +$handlers = array( + // Handle our own quiz_attempt_submitted event, as a way to send confirmation + // messages asynchronously. + 'quiz_attempt_submitted' => array ( + 'handlerfile' => '/mod/quiz/locallib.php', + 'handlerfunction' => 'quiz_attempt_submitted_handler', + 'schedule' => 'cron', + ), +); + +/* List of events generated by the quiz module, with the fields on the event object. + +quiz_attempt_started + ->component = 'mod_quiz'; + ->attemptid = // The id of the new quiz attempt. + ->timestart = // The timestamp of when the attempt was started. + ->userid = // The user id that the attempt belongs to. + ->quizid = // The quiz id of the quiz the attempt belongs to. + ->cmid = // The course_module id of the quiz the attempt belongs to. + ->courseid = // The course id of the course the quiz belongs to. + +quiz_attempt_submitted + ->component = 'mod_quiz'; + ->attemptid = // The id of the quiz attempt that was submitted. + ->timefinish = // The timestamp of when the attempt was submitted. + ->userid = // The user id that the attempt belongs to. + ->submitterid = // The user id of the user who sumitted the attempt. + ->quizid = // The quiz id of the quiz the attempt belongs to. + ->cmid = // The course_module id of the quiz the attempt belongs to. + ->courseid = // The course id of the course the quiz belongs to. + +*/ diff --git a/mod/quiz/lang/en/quiz.php b/mod/quiz/lang/en/quiz.php index f788ef5daeb94..deaf6a7ac0331 100644 --- a/mod/quiz/lang/en/quiz.php +++ b/mod/quiz/lang/en/quiz.php @@ -256,7 +256,7 @@ in course \'{$a->coursename}\' at {$a->submissiontime}. -This email confirms that we have safely received your answers. +This message confirms that we have safely received your answers. You can access this quiz at {$a->quizurl}.'; $string['emailconfirmsmall'] = 'Thank you for submitting your answers to \'{$a->quizname}\''; @@ -550,8 +550,8 @@ $string['quizcloses'] = 'Quiz closes'; $string['quizcloseson'] = 'This quiz will close at {$a}'; $string['quiz:deleteattempts'] = 'Delete quiz attempts'; -$string['quiz:emailconfirmsubmission'] = 'Get email confirmation when submitting'; -$string['quiz:emailnotifysubmission'] = 'Get email notification of submissions'; +$string['quiz:emailconfirmsubmission'] = 'Get a confirmation message when submitting'; +$string['quiz:emailnotifysubmission'] = 'Get a notification message when an attempt is submitted'; $string['quiz:grade'] = 'Grade quizzes manually'; $string['quiz:ignoretimelimits'] = 'Ignores time limit on quizzes'; $string['quizisclosed'] = 'This quiz is closed'; diff --git a/mod/quiz/locallib.php b/mod/quiz/locallib.php index 0e5718215db21..8cad96ad9cda9 100644 --- a/mod/quiz/locallib.php +++ b/mod/quiz/locallib.php @@ -1092,38 +1092,33 @@ function quiz_get_slot_for_question($quiz, $questionid) { return null; } -/// FUNCTIONS FOR SENDING NOTIFICATION EMAILS /////////////////////////////// +/// FUNCTIONS FOR SENDING NOTIFICATION MESSAGES /////////////////////////////// /** - * Sends confirmation email to the student taking the course + * Sends a confirmation message to the student confirming that the attempt was processed. * - * @param object $a associative array of replaceable fields for the templates + * @param object $a lots of useful information that can be used in the message + * subject and body. * - * @return bool + * @return int|false as for {@link message_send()}. */ -function quiz_send_confirmation($a) { - - global $USER; - - // recipient is self - $a->useridnumber = $USER->idnumber; - $a->username = fullname($USER); - $a->userusername = $USER->username; +function quiz_send_confirmation($recipient, $a) { - // fetch the subject and body from strings - $subject = get_string('emailconfirmsubject', 'quiz', $a); - $body = get_string('emailconfirmbody', 'quiz', $a); + // Add information about the recipient to $a + // Don't do idnumber. we want idnumber to be the submitter's idnumber. + $a->username = fullname($recipient); + $a->userusername = $recipient->username; - // send email and analyse result + // Prepare message $eventdata = new stdClass(); - $eventdata->component = 'mod_quiz'; - $eventdata->name = 'confirmation'; + $eventdata->component = 'mod_quiz'; + $eventdata->name = 'confirmation'; $eventdata->notification = 1; $eventdata->userfrom = get_admin(); - $eventdata->userto = $USER; - $eventdata->subject = $subject; - $eventdata->fullmessage = $body; + $eventdata->userto = $recipient; + $eventdata->subject = get_string('emailconfirmsubject', 'quiz', $a); + $eventdata->fullmessage = get_string('emailconfirmbody', 'quiz', $a); $eventdata->fullmessageformat = FORMAT_PLAIN; $eventdata->fullmessagehtml = ''; @@ -1131,7 +1126,8 @@ function quiz_send_confirmation($a) { $eventdata->contexturl = $a->quizurl; $eventdata->contexturlname = $a->quizname; - return (bool)message_send($eventdata); // returns message id or false + // ... and send it. + return message_send($eventdata); } /** @@ -1140,30 +1136,27 @@ function quiz_send_confirmation($a) { * @param object $recipient user object of the intended recipient * @param object $a associative array of replaceable fields for the templates * - * @return bool + * @return int|false as for {@link message_send()}. */ -function quiz_send_notification($recipient, $a) { +function quiz_send_notification($recipient, $submitter, $a) { global $USER; - // recipient info for template - $a->username = fullname($recipient); + // Recipient info for template + $a->useridnumber = $recipient->idnumber; + $a->username = fullname($recipient); $a->userusername = $recipient->username; - // fetch the subject and body from strings - $subject = get_string('emailnotifysubject', 'quiz', $a); - $body = get_string('emailnotifybody', 'quiz', $a); - - // send email and analyse result + // Prepare message $eventdata = new stdClass(); - $eventdata->component = 'mod_quiz'; - $eventdata->name = 'submission'; + $eventdata->component = 'mod_quiz'; + $eventdata->name = 'submission'; $eventdata->notification = 1; - $eventdata->userfrom = $USER; + $eventdata->userfrom = $submitter; $eventdata->userto = $recipient; - $eventdata->subject = $subject; - $eventdata->fullmessage = $body; + $eventdata->subject = get_string('emailnotifysubject', 'quiz', $a); + $eventdata->fullmessage = get_string('emailnotifybody', 'quiz', $a); $eventdata->fullmessageformat = FORMAT_PLAIN; $eventdata->fullmessagehtml = ''; @@ -1171,12 +1164,12 @@ function quiz_send_notification($recipient, $a) { $eventdata->contexturl = $a->quizreviewurl; $eventdata->contexturlname = $a->quizname; - return (bool)message_send($eventdata); + // ... and send it. + return message_send($eventdata); } /** - * Takes a bunch of information to format into an email and send - * to the specified recipient. + * Send all the requried messages when a quiz attempt is submitted. * * @param object $course the course * @param object $quiz the quiz @@ -1184,39 +1177,35 @@ function quiz_send_notification($recipient, $a) { * @param object $context the quiz context * @param object $cm the coursemodule for this quiz * - * @return int number of emails sent + * @return bool true if all necessary messages were sent successfully, else false. */ -function quiz_send_notification_emails($course, $quiz, $attempt, $context, $cm) { - global $CFG, $USER; - // we will count goods and bads for error logging - $emailresult = array('good' => 0, 'fail' => 0); +function quiz_send_notification_messages($course, $quiz, $attempt, $context, $cm) { + global $CFG, $DB; - // do nothing if required objects not present + // Do nothing if required objects not present if (empty($course) or empty($quiz) or empty($attempt) or empty($context)) { - debugging('quiz_send_notification_emails: Email(s) not sent due to program error.', - DEBUG_DEVELOPER); - return $emailresult['fail']; + throw new coding_exception('$course, $quiz, $attempt, $context and $cm must all be set.'); } - // check for confirmation required + $submitter = $DB->get_record('user', array('id' => $attempt->userid), '*', MUST_EXIST); + + // Check for confirmation required $sendconfirm = false; $notifyexcludeusers = ''; - if (has_capability('mod/quiz:emailconfirmsubmission', $context, null, false)) { - // exclude from notify emails later - $notifyexcludeusers = $USER->id; - // send the email + if (has_capability('mod/quiz:emailconfirmsubmission', $context, $submitter, false)) { + $notifyexcludeusers = $submitter->id; $sendconfirm = true; } // check for notifications required - $notifyfields = 'u.id, u.username, u.firstname, u.lastname, u.email, u.lang, ' . - 'u.timezone, u.mailformat, u.maildisplay'; - $groups = groups_get_all_groups($course->id, $USER->id); + $notifyfields = 'u.id, u.username, u.firstname, u.lastname, u.idnumber, u.email, ' . + 'u.lang, u.timezone, u.mailformat, u.maildisplay'; + $groups = groups_get_all_groups($course->id, $submitter->id); if (is_array($groups) && count($groups) > 0) { $groups = array_keys($groups); } else if (groups_get_activity_groupmode($cm, $course) != NOGROUPS) { // If the user is not in a group, and the quiz is set to group mode, - // then set $gropus to a non-existant id so that only users with + // then set $groups to a non-existant id so that only users with // 'moodle/site:accessallgroups' get notified. $groups = -1; } else { @@ -1225,67 +1214,75 @@ function quiz_send_notification_emails($course, $quiz, $attempt, $context, $cm) $userstonotify = get_users_by_capability($context, 'mod/quiz:emailnotifysubmission', $notifyfields, '', '', '', $groups, $notifyexcludeusers, false, false, true); - // if something to send, then build $a - if (! empty($userstonotify) or $sendconfirm) { - $a = new stdClass(); - // course info - $a->coursename = $course->fullname; - $a->courseshortname = $course->shortname; - // quiz info - $a->quizname = $quiz->name; - $a->quizreporturl = $CFG->wwwroot . '/mod/quiz/report.php?id=' . $cm->id; - $a->quizreportlink = '' . - format_string($quiz->name) . ' report'; - $a->quizreviewurl = $CFG->wwwroot . '/mod/quiz/review.php?attempt=' . $attempt->id; - $a->quizreviewlink = '' . - format_string($quiz->name) . ' review'; - $a->quizurl = $CFG->wwwroot . '/mod/quiz/view.php?id=' . $cm->id; - $a->quizlink = '' . format_string($quiz->name) . ''; - // attempt info - $a->submissiontime = userdate($attempt->timefinish); - $a->timetaken = format_time($attempt->timefinish - $attempt->timestart); - // student who sat the quiz info - $a->studentidnumber = $USER->idnumber; - $a->studentname = fullname($USER); - $a->studentusername = $USER->username; + if (empty($userstonotify) && !$sendconfirm) { + return true; // Nothing to do. } - // send confirmation if required - if ($sendconfirm) { - // send the email and update stats - switch (quiz_send_confirmation($a)) { - case true: - $emailresult['good']++; - break; - case false: - $emailresult['fail']++; - break; - } - } - - // send notifications if required + $a = new stdClass(); + // Course info + $a->coursename = $course->fullname; + $a->courseshortname = $course->shortname; + // Quiz info + $a->quizname = $quiz->name; + $a->quizreporturl = $CFG->wwwroot . '/mod/quiz/report.php?id=' . $cm->id; + $a->quizreportlink = '' . + format_string($quiz->name) . ' report'; + $a->quizreviewurl = $CFG->wwwroot . '/mod/quiz/review.php?attempt=' . $attempt->id; + $a->quizreviewlink = '' . + format_string($quiz->name) . ' review'; + $a->quizurl = $CFG->wwwroot . '/mod/quiz/view.php?id=' . $cm->id; + $a->quizlink = '' . format_string($quiz->name) . ''; + // Attempt info + $a->submissiontime = userdate($attempt->timefinish); + $a->timetaken = format_time($attempt->timefinish - $attempt->timestart); + // Student who sat the quiz info + $a->studentidnumber = $submitter->idnumber; + $a->studentname = fullname($submitter); + $a->studentusername = $submitter->username; + + $allok = true; + + // Send notifications if required if (!empty($userstonotify)) { - // loop through recipients and send an email to each and update stats foreach ($userstonotify as $recipient) { - switch (quiz_send_notification($recipient, $a)) { - case true: - $emailresult['good']++; - break; - case false: - $emailresult['fail']++; - break; - } + $allok = $allok && quiz_send_notification($recipient, $submitter, $a); } } - // log errors sending emails if any - if (! empty($emailresult['fail'])) { - debugging('quiz_send_notification_emails:: ' . $emailresult['fail'] . - ' email(s) failed to be sent.', DEBUG_DEVELOPER); + // Send confirmation if required. We send the student confirmation last, so + // that if message sending is being intermittently buggy, which means we send + // some but not all messages, and then try again later, then teachers may get + // duplicate messages, but the student will always get exactly one. + if ($sendconfirm) { + $allok = $allok && quiz_send_confirmation($submitter, $a); + } + + return $allok; +} + +/** + * Handle the quiz_attempt_submitted event. + * + * This sends the confirmation and notification messages, if required. + * + * @param object $event the event object. + */ +function quiz_attempt_submitted_handler($event) { + global $DB; + + $course = $DB->get_record('course', array('id' => $event->courseid)); + $quiz = $DB->get_record('quiz', array('id' => $event->quizid)); + $cm = get_coursemodule_from_id('quiz', $event->cmid, $event->courseid); + $attempt = $DB->get_record('quiz_attempts', array('id' => $event->attemptid)); + + if (!($course && $quiz && $cm && $attempt)) { + // Something has been deleted since the event was raised. Therefore, the + // event is no longer relevant. + return true; } - // return the number of successfully sent emails - return $emailresult['good']; + return quiz_send_notification_messages($course, $quiz, $attempt, + get_context_instance(CONTEXT_MODULE, $cm->id), $cm); } /** diff --git a/mod/quiz/processattempt.php b/mod/quiz/processattempt.php index 7d74e19432ca8..79da301051279 100644 --- a/mod/quiz/processattempt.php +++ b/mod/quiz/processattempt.php @@ -112,20 +112,6 @@ // Update the quiz attempt record. $attemptobj->finish_attempt($timenow); -// Trigger event -$eventdata = new stdClass(); -$eventdata->component = 'mod_quiz'; -$eventdata->course = $attemptobj->get_courseid(); -$eventdata->quiz = $attemptobj->get_quizid(); -$eventdata->cm = $attemptobj->get_cmid(); -$eventdata->user = $USER; -$eventdata->attempt = $attemptobj->get_attemptid(); -events_trigger('quiz_attempt_processed', $eventdata); - -// Clear the password check flag in the session. -$accessmanager = $attemptobj->get_access_manager($timenow); -$accessmanager->clear_password_access(); - // Send the user to the review page. $transaction->allow_commit(); redirect($attemptobj->review_url()); diff --git a/mod/quiz/startattempt.php b/mod/quiz/startattempt.php index f44ecc54443de..4f85d373ca311 100644 --- a/mod/quiz/startattempt.php +++ b/mod/quiz/startattempt.php @@ -210,12 +210,13 @@ // Trigger event $eventdata = new stdClass(); -$eventdata->component = 'mod_quiz'; -$eventdata->course = $quizobj->get_courseid(); -$eventdata->quiz = $quizobj->get_quizid(); -$eventdata->cm = $quizobj->get_cmid(); -$eventdata->user = $USER; -$eventdata->attempt = $attempt->id; +$eventdata->component = 'mod_quiz'; +$eventdata->attemptid = $attempt->id; +$eventdata->timestart = $attempt->timestart; +$eventdata->userid = $attempt->userid; +$eventdata->quizid = $quizobj->get_quizid(); +$eventdata->cmid = $quizobj->get_cmid(); +$eventdata->courseid = $quizobj->get_courseid(); events_trigger('quiz_attempt_started', $eventdata); $transaction->allow_commit(); diff --git a/mod/quiz/version.php b/mod/quiz/version.php index 9b7fd7edc314a..1381c23225b34 100644 --- a/mod/quiz/version.php +++ b/mod/quiz/version.php @@ -25,6 +25,6 @@ defined('MOODLE_INTERNAL') || die(); -$module->version = 2011051250; +$module->version = 2011070100; $module->requires = 2011060313; $module->cron = 0;