Browse files

MDL-28166 send quiz event notifications asynchronously.

This avoids the problem that you cannot send messages in transactions.

It also means that the quiz submission will not be prevented, and the
message will still be sent eventually, if any part of the messaging
system is giving intermittent errors when the student wants to submit
their quiz.
  • Loading branch information...
1 parent 376d87c commit 54bf1cf7dcfbaaf307ffdb2c1c4dfe296a9f6533 @timhunt timhunt committed with stronk7 Jul 4, 2011
View
26 mod/quiz/attemptlib.php
@@ -996,14 +996,6 @@ public function check_file_access($slot, $reviewing, $contextid, $component,
}
/**
- * 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.
*
* @param $panelclass The type of panel, quiz_attempt_nav_panel or quiz_review_nav_panel
@@ -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();
}
}
View
4 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,
View
61 mod/quiz/db/events.php
@@ -0,0 +1,61 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * 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.
+
+*/
View
6 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';
View
215 mod/quiz/locallib.php
@@ -1092,46 +1092,42 @@ 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 = '';
$eventdata->smallmessage = get_string('emailconfirmsmall', 'quiz', $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,83 +1136,76 @@ 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 = '';
$eventdata->smallmessage = get_string('emailnotifysmall', 'quiz', $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
* @param object $attempt this attempt just finished
* @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 = '<a href="' . $a->quizreporturl . '">' .
- format_string($quiz->name) . ' report</a>';
- $a->quizreviewurl = $CFG->wwwroot . '/mod/quiz/review.php?attempt=' . $attempt->id;
- $a->quizreviewlink = '<a href="' . $a->quizreviewurl . '">' .
- format_string($quiz->name) . ' review</a>';
- $a->quizurl = $CFG->wwwroot . '/mod/quiz/view.php?id=' . $cm->id;
- $a->quizlink = '<a href="' . $a->quizurl . '">' . format_string($quiz->name) . '</a>';
- // 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 = '<a href="' . $a->quizreporturl . '">' .
+ format_string($quiz->name) . ' report</a>';
+ $a->quizreviewurl = $CFG->wwwroot . '/mod/quiz/review.php?attempt=' . $attempt->id;
+ $a->quizreviewlink = '<a href="' . $a->quizreviewurl . '">' .
+ format_string($quiz->name) . ' review</a>';
+ $a->quizurl = $CFG->wwwroot . '/mod/quiz/view.php?id=' . $cm->id;
+ $a->quizlink = '<a href="' . $a->quizurl . '">' . format_string($quiz->name) . '</a>';
+ // 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);
}
/**
View
14 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());
View
13 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();
View
2 mod/quiz/version.php
@@ -25,6 +25,6 @@
defined('MOODLE_INTERNAL') || die();
-$module->version = 2011051250;
+$module->version = 2011070100;
$module->requires = 2011060313;
$module->cron = 0;

0 comments on commit 54bf1cf

Please sign in to comment.