MDL-28980 add basic expiry notification support to manual enrolments

Thanks Helen Foster for the lang string improvements.
This commit is contained in:
Petr Škoda 2012-10-07 18:24:54 +02:00
parent ccd90e765e
commit 129e6d36a4
9 changed files with 426 additions and 25 deletions

View File

@ -42,7 +42,7 @@ if ($unrecognized) {
if ($options['help']) {
$help =
"Execute manual enrolments expiration sync.
"Execute manual enrolments expiration sync and send notifications.
Options:
-v, --verbose Print verbose progress information
@ -62,4 +62,6 @@ $plugin = enrol_get_plugin('manual');
$result = $plugin->sync(null, $verbose);
$plugin->send_notifications($verbose);
exit($result);

View File

@ -0,0 +1,29 @@
<?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/>.
/**
* Defines message providers for manual enrolments.
*
* @package enrol_manual
* @copyright 2012 Petr Skoda {@link http://skodak.org}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
$messageproviders = array (
'expiry_notification' => array(),
);

View File

@ -0,0 +1,44 @@
<?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/>.
/**
* This file keeps track of upgrades to the manual enrolment plugin
*
* @package enrol_manual
* @copyright 2012 Petr Skoda {@link http://skodak.org
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
function xmldb_enrol_manual_upgrade($oldversion) {
global $CFG, $DB, $OUTPUT;
$dbman = $DB->get_manager();
// Moodle v2.3.0 release upgrade line
// Put any upgrade step following this
if ($oldversion < 2012100702) {
// Set default expiry threshold to 1 day.
$DB->execute("UPDATE {enrol} SET expirythreshold = 86400 WHERE enrol = 'manual' AND expirythreshold = 0");
upgrade_plugin_savepoint(true, 2012100702, 'enrol', 'manual');
}
return true;
}

View File

@ -52,36 +52,64 @@ if ($instances = $DB->get_records('enrol', array('courseid'=>$course->id, 'enrol
$plugin->delete_instance($del);
}
}
// Merge these two settings to one value for the single selection element.
if ($instance->notifyall and $instance->expirynotify) {
$instance->expirynotify = 2;
}
unset($instance->notifyall);
} else {
require_capability('moodle/course:enrolconfig', $context);
// No instance yet, we have to add new instance.
navigation_node::override_active_url(new moodle_url('/enrol/instances.php', array('id'=>$course->id)));
$instance = new stdClass();
$instance->id = null;
$instance->courseid = $course->id;
$instance->id = null;
$instance->courseid = $course->id;
$instance->expirynotify = $plugin->get_config('expirynotify');
$instance->expirythreshold = $plugin->get_config('expirythreshold');
}
$mform = new enrol_manual_edit_form(NULL, array($instance, $plugin, $context));
$mform = new enrol_manual_edit_form(null, array($instance, $plugin, $context));
if ($mform->is_cancelled()) {
redirect($return);
} else if ($data = $mform->get_data()) {
if ($data->expirynotify == 2) {
$data->expirynotify = 1;
$data->notifyall = 1;
} else {
$data->notifyall = 0;
}
if (!$data->expirynotify) {
// Keep previous/default value of disabled expirythreshold option.
$data->expirythreshold = $instance->expirythreshold;
}
if ($instance->id) {
$reset = ($instance->status != $data->status);
$instance->roleid = $data->roleid;
$instance->enrolperiod = $data->enrolperiod;
$instance->expirynotify = $data->expirynotify;
$instance->notifyall = $data->notifyall;
$instance->expirythreshold = $data->expirythreshold;
$instance->timemodified = time();
$instance->status = $data->status;
$instance->enrolperiod = $data->enrolperiod;
$instance->roleid = $data->roleid;
$instance->timemodified = time();
$DB->update_record('enrol', $instance);
if ($reset) {
// Use standard API to update instance status.
if ($instance->status != $data->status) {
$instance = $DB->get_record('enrol', array('id'=>$instance->id));
$plugin->update_status($instance, $data->status);
$context->mark_dirty();
}
} else {
$fields = array('status'=>$data->status, 'enrolperiod'=>$data->enrolperiod, 'roleid'=>$data->roleid);
$fields = array(
'status' => $data->status,
'roleid' => $data->roleid,
'enrolperiod' => $data->enrolperiod,
'expirynotify' => $data->expirynotify,
'notifyall' => $data->notifyall,
'expirythreshold' => $data->expirythreshold);
$plugin->add_instance($course, $fields);
}

View File

@ -42,10 +42,6 @@ class enrol_manual_edit_form extends moodleform {
$mform->addHelpButton('status', 'status', 'enrol_manual');
$mform->setDefault('status', $plugin->get_config('status'));
$mform->addElement('duration', 'enrolperiod', get_string('defaultperiod', 'enrol_manual'), array('optional' => true, 'defaultunit' => 86400));
$mform->setDefault('enrolperiod', $plugin->get_config('enrolperiod'));
$mform->addHelpButton('enrolperiod', 'defaultperiod', 'enrol_manual');
if ($instance->id) {
$roles = get_default_enrol_roles($context, $instance->roleid);
} else {
@ -54,10 +50,34 @@ class enrol_manual_edit_form extends moodleform {
$mform->addElement('select', 'roleid', get_string('defaultrole', 'role'), $roles);
$mform->setDefault('roleid', $plugin->get_config('roleid'));
$mform->addElement('duration', 'enrolperiod', get_string('defaultperiod', 'enrol_manual'), array('optional' => true, 'defaultunit' => 86400));
$mform->setDefault('enrolperiod', $plugin->get_config('enrolperiod'));
$mform->addHelpButton('enrolperiod', 'defaultperiod', 'enrol_manual');
$options = array(0 => get_string('no'), 1 => get_string('expirynotifyteacher', 'enrol_manual'), 2 => get_string('expirynotifyall', 'enrol_manual'));
$mform->addElement('select', 'expirynotify', get_string('expirynotify', 'enrol_manual'), $options);
$mform->addHelpButton('expirynotify', 'expirynotify', 'enrol_manual');
$mform->addElement('duration', 'expirythreshold', get_string('expirythreshold', 'enrol_manual'), array('optional' => false, 'defaultunit' => 86400));
$mform->addHelpButton('expirythreshold', 'expirythreshold', 'enrol_manual');
$mform->disabledIf('expirythreshold', 'expirynotify', 'eq', 0);
$mform->addElement('hidden', 'courseid');
$this->add_action_buttons(true, ($instance->id ? null : get_string('addinstance', 'enrol')));
$this->set_data($instance);
}
function validation($data, $files) {
global $DB;
$errors = parent::validation($data, $files);
if ($data['expirynotify'] > 0 and $data['expirythreshold'] < 86400) {
$errors['expirythreshold'] = get_string('errorthresholdlow', 'enrol_manual');
}
return $errors;
}
}

View File

@ -1,5 +1,4 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
@ -16,10 +15,9 @@
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Strings for component 'enrol_manual', language 'en', branch 'MOODLE_20_STABLE'
* Strings for component 'enrol_manual', language 'en'.
*
* @package enrol
* @subpackage manual
* @package enrol_manual
* @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
@ -37,13 +35,35 @@ $string['editenrolment'] = 'Edit enrolment';
$string['editselectedusers'] = 'Edit selected user enrolments';
$string['enrolledincourserole'] = 'Enrolled in "{$a->course}" as "{$a->role}"';
$string['enrolusers'] = 'Enrol users';
$string['errorthresholdlow'] = 'Notification threshold must be at least 1 day.';
$string['expiredaction'] = 'Enrolment expiration action';
$string['expiredaction_help'] = 'Select action to carry out when user enrolment expires. Please note that some user data and settings are purged from course during course unenrolment.';
$string['expirymessageenrollersubject'] = 'Enrolment expiry notification';
$string['expirymessageenrollerbody'] = 'Enrolment in the course \'{$a->course}\' will expire within the next {$a->threshold} for the following users:
{$a->users}
To extend their enrolment, go to {$a->extendurl}';
$string['expirymessageenrolledsubject'] = 'Enrolment expiry notification';
$string['expirymessageenrolledbody'] = 'Dear {$a->user},
This is a notification that your enrolment in the course \'{$a->course}\' is due to expire on {$a->timeend}.
If you need help, please contact {$a->enroller}.';
$string['expirynotify'] = 'Notify before enrolment expires';
$string['expirynotify_help'] = 'This setting determines whether enrolment expiry notification messages are sent.';
$string['expirynotifyall'] = 'Enroller and enrolled user';
$string['expirynotifyteacher'] = 'Enroller only';
$string['expirythreshold'] = 'Notification threshold';
$string['expirythreshold_help'] = 'This setting specifies the number of days before enrolment expiry that a notification message is sent.';
$string['expirythreshold_help'] = 'How long before expiration should be users notified?';
$string['manual:config'] = 'Configure manual enrol instances';
$string['manual:enrol'] = 'Enrol users';
$string['manual:manage'] = 'Manage user enrolments';
$string['manual:unenrol'] = 'Unenrol users from the course';
$string['manual:unenrolself'] = 'Unenrol self from the course';
$string['messageprovider:expiry_notification'] = 'Manual enrolment expiry notifications';
$string['notifyhour'] = 'Hour to send enrolment expiry notifications';
$string['pluginname'] = 'Manual enrolments';
$string['pluginname_desc'] = 'The manual enrolments plugin allows users to be enrolled manually via a link in the course administration settings, by a user with appropriate permissions such as a teacher. The plugin should normally be enabled, since certain other enrolment plugins, such as self enrolment, require it.';
$string['status'] = 'Enable manual enrolments';

View File

@ -26,6 +26,9 @@ defined('MOODLE_INTERNAL') || die();
class enrol_manual_plugin extends enrol_plugin {
protected $lasternoller = null;
protected $lasternollercourseid = 0;
public function roles_protected() {
// Users may tweak the roles later.
return false;
@ -147,7 +150,14 @@ class enrol_manual_plugin extends enrol_plugin {
* @return int id of new instance, null if can not be created
*/
public function add_default_instance($course) {
$fields = array('status'=>$this->get_config('status'), 'enrolperiod'=>$this->get_config('enrolperiod', 0), 'roleid'=>$this->get_config('roleid', 0));
$fields = array(
'status' => $this->get_config('status'),
'roleid' => $this->get_config('roleid', 0),
'enrolperiod' => $this->get_config('enrolperiod', 0),
'expirynotify' => $this->get_config('expirynotify', 0),
'notifyall' => $this->get_config('notifyall', 0),
'expirythreshold' => $this->get_config('expirythreshold', 86400),
);
return $this->add_instance($course, $fields);
}
@ -263,6 +273,7 @@ class enrol_manual_plugin extends enrol_plugin {
*/
public function cron() {
$this->sync(null, true);
$this->send_notifications(true);
}
/**
@ -357,6 +368,239 @@ class enrol_manual_plugin extends enrol_plugin {
return 0;
}
/**
* Send notifications.
*
* @param bool $verbose verbose CLI output
*/
public function send_notifications($verbose = false) {
global $DB, $CFG;
// Unfortunately this may take a long time, it should not be interrupted,
// otherwise users get duplicate notification.
@set_time_limit(0);
raise_memory_limit(MEMORY_HUGE);
$notifylast = $this->get_config('notifylast', 0);
$notifyhour = $this->get_config('notifyhour', 6);
$timenow = time();
$notifytime = usergetmidnight($timenow, $CFG->timezone) + ($notifyhour * 3600);
if ($notifylast > $notifytime) {
if ($verbose) {
mtrace('Manual enrolment notifications were already sent today at '.userdate($notifylast, '', $CFG->timezone).'.');
}
return;
} else if ($timenow < $notifytime) {
if ($verbose) {
mtrace('Manual enrolment notifications will be sent at '.userdate($notifytime, '', $CFG->timezone).'.');
}
return;
}
if ($verbose) {
mtrace('Processing manual enrolment notifications...');
}
// Notify users responsible for enrolment once every day.
$sql = "SELECT ue.*, e.expirynotify, e.notifyall, e.expirythreshold, e.courseid, c.fullname
FROM {user_enrolments} ue
JOIN {enrol} e ON (e.id = ue.enrolid AND e.enrol = 'manual' AND e.expirynotify > 0 AND e.status = :enabled)
JOIN {course} c ON (c.id = e.courseid)
JOIN {user} u ON (u.id = ue.userid AND u.deleted = 0 AND u.suspended = 0)
WHERE ue.status = :active AND ue.timeend > 0 AND ue.timeend > :now1 AND ue.timeend < (e.expirythreshold + :now2)
ORDER BY ue.enrolid ASC, u.lastname ASC, u.firstname ASC, u.id ASC";
$params = array('enabled'=>ENROL_INSTANCE_ENABLED, 'active'=>ENROL_USER_ACTIVE, 'now1'=>$timenow, 'now2'=>$timenow);
$rs = $DB->get_recordset_sql($sql, $params);
$lastenrollid = 0;
$users = array();
foreach($rs as $ue) {
if ($lastenrollid and $lastenrollid != $ue->enrolid) {
$this->notify_expiry_enroller($ue->enrolid, $users, $verbose);
}
$lastenrollid = $ue->enrolid;
$enroller = $this->get_enroller($ue->courseid);
$context = context_course::instance($ue->courseid);
$user = $DB->get_record('user', array('id'=>$ue->userid));
$users[] = array('fullname'=>fullname($user, has_capability('moodle/site:viewfullnames', $context, $enroller)), 'timeend'=>$ue->timeend);
if (!$ue->notifyall) {
continue;
}
if ($ue->timeend - $ue->expirythreshold + 86400 < $timenow) {
// Notify enrolled users only once at the start of the threshold.
continue;
}
$this->notify_expiry_enrolled($user, $ue, $verbose);
}
$rs->close();
if ($lastenrollid and $users) {
$this->notify_expiry_enroller($lastenrollid, $users, $verbose);
}
if ($verbose) {
mtrace('...notification processing finished.');
}
$this->set_config('notifylast', $timenow);
}
/**
* Returns the user who is resposible for manual enrolments in given course
* @param int $courseid
* @return stdClass user record
*/
protected function get_enroller($courseid) {
if ($this->lasternollercourseid == $courseid and $this->lasternoller) {
return $this->lasternoller;
}
$context = context_course::instance($courseid);
if ($users = get_enrolled_users($context, 'enrol/manual:manage')) {
$users = sort_by_roleassignment_authority($users, $context);
$this->lasternoller = reset($users);
unset($users);
} else {
$this->lasternoller = get_admin();
}
$this->lasternollercourseid = $courseid;
return $this->lasternoller;
}
/**
* Notify user about incoming expiration of their enrolment.
* @param stdClass $user
* @param stdClass $ue
* @param bool $verbose
*/
protected function notify_expiry_enrolled($user, $ue, $verbose) {
global $CFG, $SESSION;
// Some nasty hackery to get strings and dates localised for target user.
$sessionlang = isset($SESSION->lang) ? $SESSION->lang : null;
if (get_string_manager()->translation_exists($user->lang, false)) {
$SESSION->lang = $user->lang;
moodle_setlocale();
}
$enroller = $this->get_enroller($ue->courseid);
$context = context_course::instance($ue->courseid);
$subject = get_string('expirymessageenrolledsubject', 'enrol_manual', null);
$a = new stdClass();
$a->course = format_string($ue->fullname, true, array('context'=>$context));
$a->user = fullname($user, true);
$a->timeend = userdate($ue->timeend, '', $user->timezone);
$a->enroller = fullname($enroller, has_capability('moodle/site:viewfullnames', $context, $user));
$body = get_string('expirymessageenrolledbody', 'enrol_manual', $a);
$message = new stdClass();
$message->notification = 1;
$message->component = 'enrol_manual';
$message->name = 'expiry_notification';
$message->userfrom = $enroller;
$message->userto = $user;
$message->subject = $subject;
$message->fullmessage = $body;
$message->fullmessageformat = FORMAT_MARKDOWN;
$message->fullmessagehtml = markdown_to_html($body);
$message->smallmessage = $subject;
$message->contexturlname = $a->course;
$message->contexturl = (string)new moodle_url('/course/view.php', array('id'=>$ue->courseid));
if (message_send($message)) {
if ($verbose) {
mtrace(" notifying user $ue->userid that enrolment in course $ue->courseid expires on ".userdate($ue->timeend, '', $CFG->timezone));
}
} else {
if ($verbose) {
mtrace(" error notifying user $ue->userid that enrolment in course $ue->courseid expires on ".userdate($ue->timeend, '', $CFG->timezone));
}
}
if ($SESSION->lang !== $sessionlang) {
$SESSION->lang = $sessionlang;
moodle_setlocale();
}
}
/**
* Notify person responsible for enrolments that some user enrolments will be expired soon.
* @param int $eid
* @param array $users
* @param bool $verbose
*/
protected function notify_expiry_enroller($eid, $users, $verbose) {
global $DB, $SESSION;
$instance = $DB->get_record('enrol', array('id'=>$eid, 'enrol'=>'manual'));
$context = context_course::instance($instance->courseid);
$course = $DB->get_record('course', array('id'=>$instance->courseid));
$enroller = $this->get_enroller($instance->courseid);
$admin = get_admin();
// Some nasty hackery to get strings and dates localised for target user.
$sessionlang = isset($SESSION->lang) ? $SESSION->lang : null;
if (get_string_manager()->translation_exists($enroller->lang, false)) {
$SESSION->lang = $enroller->lang;
moodle_setlocale();
}
foreach($users as $key=>$info) {
$users[$key] = '* '.$info['fullname'].' - '.userdate($info['timeend'], '', $enroller->timezone);
}
$subject = get_string('expirymessageenrollersubject', 'enrol_manual', null);
$a = new stdClass();
$a->course = format_string($course->fullname, true, array('context'=>$context));
$a->threshold = get_string('numdays', '', $instance->expirythreshold / (60*60*24));
$a->users = implode("\n", $users);
$a->extendurl = (string)new moodle_url('/enrol/users.php', array('id'=>$instance->courseid));
$body = get_string('expirymessageenrollerbody', 'enrol_manual', $a);
$message = new stdClass();
$message->notification = 1;
$message->component = 'enrol_manual';
$message->name = 'expiry_notification';
$message->userfrom = $admin;
$message->userto = $enroller;
$message->subject = $subject;
$message->fullmessage = $body;
$message->fullmessageformat = FORMAT_MARKDOWN;
$message->fullmessagehtml = markdown_to_html($body);
$message->smallmessage = $subject;
$message->contexturlname = $a->course;
$message->contexturl = $a->extendurl;
if (message_send($message)) {
if ($verbose) {
mtrace(" notifying user $enroller->id about all expiring manual enrolments in course $instance->courseid");
}
} else {
if ($verbose) {
mtrace(" error notifying user $enroller->id about all expiring manual enrolments in course $instance->courseid");
}
}
if ($SESSION->lang !== $sessionlang) {
$SESSION->lang = $sessionlang;
moodle_setlocale();
}
}
/**
* Gets an array of the user enrolment actions.
*

View File

@ -38,6 +38,12 @@ if ($ADMIN->fulltree) {
);
$settings->add(new admin_setting_configselect('enrol_manual/expiredaction', get_string('expiredaction', 'enrol_manual'), get_string('expiredaction_help', 'enrol_manual'), ENROL_EXT_REMOVED_KEEP, $options));
$options = array();
for ($i=0; $i<24; $i++) {
$options[$i] = $i;
}
$settings->add(new admin_setting_configselect('enrol_manual/notifyhour', get_string('notifyhour', 'enrol_manual'), '', 6, $options));
//--- enrol instance defaults ----------------------------------------------------------------------------
$settings->add(new admin_setting_heading('enrol_manual_defaults',
@ -51,9 +57,6 @@ if ($ADMIN->fulltree) {
$settings->add(new admin_setting_configselect('enrol_manual/status',
get_string('status', 'enrol_manual'), get_string('status_desc', 'enrol_manual'), ENROL_INSTANCE_ENABLED, $options));
$settings->add(new admin_setting_configduration('enrol_manual/enrolperiod',
get_string('defaultperiod', 'enrol_manual'), get_string('defaultperiod_desc', 'enrol_manual'), 0));
if (!during_initial_install()) {
$options = get_default_enrol_roles(context_system::instance());
$student = get_archetype_roles('student');
@ -61,4 +64,15 @@ if ($ADMIN->fulltree) {
$settings->add(new admin_setting_configselect('enrol_manual/roleid',
get_string('defaultrole', 'role'), '', $student->id, $options));
}
$settings->add(new admin_setting_configduration('enrol_manual/enrolperiod',
get_string('defaultperiod', 'enrol_manual'), get_string('defaultperiod_desc', 'enrol_manual'), 0));
$options = array(0 => get_string('no'), 1 => get_string('expirynotifyteacher', 'enrol_manual'), 2 => get_string('expirynotifyall', 'enrol_manual'));
$settings->add(new admin_setting_configselect('enrol_manual/expirynotify',
get_string('expirynotify', 'enrol_manual'), get_string('expirynotify_help', 'enrol_manual'), 0, $options));
$settings->add(new admin_setting_configduration('enrol_manual/expirythreshold',
get_string('expirythreshold', 'enrol_manual'), get_string('expirythreshold_help', 'enrol_manual'), 86400, 86400));
}

View File

@ -24,7 +24,7 @@
defined('MOODLE_INTERNAL') || die();
$plugin->version = 2012091500; // The current plugin version (Date: YYYYMMDDXX)
$plugin->requires = 2012091400; // Requires this Moodle version
$plugin->version = 2012100705; // The current plugin version (Date: YYYYMMDDXX)
$plugin->requires = 2012100500; // Requires this Moodle version
$plugin->component = 'enrol_manual'; // Full name of the plugin (used for diagnostics)
$plugin->cron = 600;