MDL-29684 improve enrol_meta plugin

List of changes:
* configurable unenrol action
* new setting for synchronisation of all enrolled users or users with at least one role
* cron period increased to 1 hour in order to lower server load, courses should not get out of sync often
* CLI sync script for debugging or manual sync
* phpdocs fixes
* when plugin is disabled all roles are removed, enrollments are kept
* uninstall script
* other bugfixing
This commit is contained in:
Petr Skoda
2012-02-03 11:29:47 +13:00
committed by Dan Marsden
parent 4d2f89f85b
commit 0007843de0
9 changed files with 711 additions and 263 deletions
+1 -2
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
@@ -74,7 +73,7 @@ class enrol_meta_addinstance_form extends moodleform {
function validation($data, $files) {
global $DB, $CFG;
// TODO: this is duplicated here because it may be necessary one we implement ajax course selection element
// TODO: this is duplicated here because it may be necessary once we implement ajax course selection element
$errors = parent::validation($data, $files);
if (!$c = $DB->get_record('course', array('id'=>$data['link']))) {
+66
View File
@@ -0,0 +1,66 @@
<?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/>.
/**
* CLI sync for meta course enrolments, use for debugging or immediate sync
* of all courses.
*
* Notes:
* - it is required to use the web server account when executing PHP CLI scripts
* - you need to change the "www-data" to match the apache user account
* - use "su" if "sudo" not available
*
* @package enrol
* @subpackage meta
* @copyright 2011 Petr Skoda {@link http://skodak.org}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define('CLI_SCRIPT', true);
require(dirname(dirname(dirname(dirname(__FILE__)))).'/config.php');
require_once($CFG->libdir.'/clilib.php');
require_once("$CFG->dirroot/enrol/meta/locallib.php");
// now get cli options
list($options, $unrecognized) = cli_get_params(array('verbose'=>false, 'help'=>false), array('v'=>'verbose', 'h'=>'help'));
if ($unrecognized) {
$unrecognized = implode("\n ", $unrecognized);
cli_error(get_string('cliunknowoption', 'admin', $unrecognized));
}
if ($options['help']) {
$help =
"Execute meta course enrol sync.
Options:
-v, --verbose Print verbose progess information
-h, --help Print out this help
Example:
\$sudo -u www-data /usr/bin/php enrol/meta/cli/sync.php
";
echo $help;
die;
}
$verbose = !empty($options['verbose']);
$result = enrol_meta_sync(null, $verbose);
exit($result);
+10
View File
@@ -44,6 +44,16 @@ $capabilities = array(
'manager' => CAP_ALLOW,
)
),
/* This is used only when sync suspends users instead of full unenrolment */
'enrol/meta:unenrol' => array(
'captype' => 'write',
'contextlevel' => CONTEXT_COURSE,
'archetypes' => array(
'manager' => CAP_ALLOW,
)
),
);
+41
View File
@@ -0,0 +1,41 @@
<?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/>.
/**
* Meta link enrolment plugin uninstallation.
*
* @package enrol
* @subpackage meta
* @copyright 2011 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_meta_uninstall() {
global $CFG, $DB;
$meta = enrol_get_plugin('meta');
$rs = $DB->get_recordset('enrol', array('enrol'=>'meta'));
foreach ($rs as $instance) {
$meta->delete_instance($instance);
}
$rs->close();
role_unassign_all(array('component'=>'enrol_meta'));
return true;
}
+3
View File
@@ -27,7 +27,10 @@
$string['linkedcourse'] = 'Link course';
$string['meta:config'] = 'Configure meta enrol instances';
$string['meta:selectaslinked'] = 'Select course as meta linked';
$string['meta:unenrol'] = 'Unenrol suspended users';
$string['nosyncroleids'] = 'Roles that are not synchronised';
$string['nosyncroleids_desc'] = 'By default all course level role assignments are synchronised from parent to child courses. Roles that are selected here will not be included in the synchronisation process. The roles available for synchronisation will be updated in the next cron execution.';
$string['pluginname'] = 'Course meta link';
$string['pluginname_desc'] = 'Course meta link enrolment plugin synchronises enrolments and roles in two different courses.';
$string['syncall'] = 'Synchronise all enrolled users';
$string['syncall_desc'] = 'If enabled all enrolled users are synchronised even if they have no role in parent course, if disabled only users that have at least one synchronised role are enrolled in child course.';
+54 -10
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
@@ -36,7 +35,7 @@ class enrol_meta_plugin extends enrol_plugin {
/**
* Returns localised name of enrol instance
*
* @param object $instance (null is accepted too)
* @param stdClass $instance (null is accepted too)
* @return string
*/
public function get_instance_name($instance) {
@@ -67,13 +66,49 @@ class enrol_meta_plugin extends enrol_plugin {
return new moodle_url('/enrol/meta/addinstance.php', array('id'=>$courseid));
}
/**
* Does this plugin allow manual unenrolment of a specific user?
* Yes, but only if user suspended...
*
* @param stdClass $instance course enrol instance
* @param stdClass $ue record from user_enrolments table
*
* @return bool - true means user with 'enrol/xxx:unenrol' may unenrol this user, false means nobody may touch this user enrolment
*/
public function allow_unenrol_user(stdClass $instance, stdClass $ue) {
if ($ue->status == ENROL_USER_SUSPENDED) {
return true;
}
return false;
}
/**
* Gets an array of the user enrolment actions
*
* @param course_enrolment_manager $manager
* @param stdClass $ue A user enrolment object
* @return array An array of user_enrolment_actions
*/
public function get_user_enrolment_actions(course_enrolment_manager $manager, $ue) {
$actions = array();
$context = $manager->get_context();
$instance = $ue->enrolmentinstance;
$params = $manager->get_moodlepage()->url->params();
$params['ue'] = $ue->id;
if ($this->allow_unenrol_user($instance, $ue) && has_capability('enrol/meta:unenrol', $context)) {
$url = new moodle_url('/enrol/unenroluser.php', $params);
$actions[] = new user_enrolment_action(new pix_icon('t/delete', ''), get_string('unenrol', 'enrol'), $url, array('class'=>'unenrollink', 'rel'=>$ue->id));
}
return $actions;
}
/**
* Called after updating/inserting course.
*
* @param bool $inserted true if course just inserted
* @param object $course
* @param object $data form data
* @param stdClass $course
* @param stdClass $data form data
* @return void
*/
public function course_updated($inserted, $course, $data) {
@@ -86,7 +121,22 @@ class enrol_meta_plugin extends enrol_plugin {
} else {
// cohorts are never inserted automatically
}
}
/**
* Update instance status
*
* @param stdClass $instance
* @param int $newstatus ENROL_INSTANCE_ENABLED, ENROL_INSTANCE_DISABLED
* @return void
*/
public function update_status($instance, $newstatus) {
global $CFG;
parent::update_status($instance, $newstatus);
require_once("$CFG->dirroot/enrol/meta/locallib.php");
enrol_meta_sync($instance->courseid);
}
/**
@@ -96,12 +146,6 @@ class enrol_meta_plugin extends enrol_plugin {
public function cron() {
global $CFG;
// purge all roles if meta sync disabled, those can be recreated later here in cron
if (!enrol_is_enabled('meta')) {
role_unassign_all(array('component'=>'meta_enrol'));
return;
}
require_once("$CFG->dirroot/enrol/meta/locallib.php");
enrol_meta_sync();
}
+527 -248
View File
@@ -25,6 +25,7 @@
defined('MOODLE_INTERNAL') || die();
/**
* Event handler for meta enrolment plugin.
*
@@ -32,9 +33,209 @@ defined('MOODLE_INTERNAL') || die();
* it may fail sometimes, so we always do a full sync in cron too.
*/
class enrol_meta_handler {
public function role_assigned($ra) {
/**
* Synchronise meta enrolments of this user in this course
* @static
* @param int $courseid
* @param int $userid
* @return void
*/
protected static function sync_course_instances($courseid, $userid) {
global $DB;
static $preventrecursion = false;
// does anything want to sync with this parent?
if (!$enrols = $DB->get_records('enrol', array('customint1'=>$courseid, 'enrol'=>'meta'), 'id ASC')) {
return;
}
if ($preventrecursion) {
return;
}
$preventrecursion = true;
try {
foreach ($enrols as $enrol) {
self::sync_with_parent_course($enrol, $userid);
}
} catch (Exception $e) {
$preventrecursion = false;
throw $e;
}
$preventrecursion = false;
}
/**
* Synchronise user enrolments in given instance as fast as possible.
*
* All roles are removed if the meta plugin disabled.
*
* @static
* @param stdClass $instance
* @param int $userid
* @return void
*/
protected static function sync_with_parent_course(stdClass $instance, $userid) {
global $DB, $CFG;
$plugin = enrol_get_plugin('meta');
if ($instance->customint1 == $instance->courseid) {
// can not sync with self!!!
return;
}
$context = context_course::instance($instance->courseid);
if (!$parentcontext = context_course::instance($instance->customint1, IGNORE_MISSING)) {
// linking to missing course is not possible
role_unassign_all(array('userid'=>$userid, 'contextid'=>$context->id, 'component'=>'enrol_meta'));
return;
}
// list of enrolments in parent course (we ignore meta enrols in parents completely)
list($enabled, $params) = $DB->get_in_or_equal(explode(',', $CFG->enrol_plugins_enabled), SQL_PARAMS_NAMED, 'e');
$params['userid'] = $userid;
$params['parentcourse'] = $instance->customint1;
$sql = "SELECT ue.*
FROM {user_enrolments} ue
JOIN {enrol} e ON (e.id = ue.enrolid AND e.enrol <> 'meta' AND e.courseid = :parentcourse AND e.enrol $enabled)
WHERE ue.userid = :userid";
$parentues = $DB->get_records_sql($sql, $params);
// current enrolments for this instance
$ue = $DB->get_record('user_enrolments', array('enrolid'=>$instance->id, 'userid'=>$userid));
// first deal with users that are not enrolled in parent
if (empty($parentues)) {
self::user_not_supposed_to_be_here($instance, $ue, $context, $plugin);
return;
}
if (!enrol_is_enabled('meta')) {
if ($ue) {
role_unassign_all(array('userid'=>$userid, 'contextid'=>$context->id, 'component'=>'enrol_meta'));
}
return;
}
$skiproles = $plugin->get_config('nosyncroleids', '');
$skiproles = empty($skiproles) ? array() : explode(',', $skiproles);
$syncall = $plugin->get_config('syncall', 1);
// roles in parent course (meta enrols must be ignored!)
$parentroles = array();
list($ignoreroles, $params) = $DB->get_in_or_equal($skiproles, SQL_PARAMS_NAMED, 'ri', false, -1);
$params['contextid'] = $parentcontext->id;
$params['userid'] = $userid;
$select = "contextid = :contextid AND userid = :userid AND component <> 'enrol_meta' AND roleid $ignoreroles";
foreach($DB->get_records_select('role_assignments', $select, $params) as $ra) {
$parentroles[$ra->roleid] = $ra->roleid;
}
// roles from this instance
$roles = array();
$ras = $DB->get_records('role_assignments', array('contextid'=>$context->id, 'userid'=>$userid, 'component'=>'enrol_meta', 'itemid'=>$instance->id));
foreach($ras as $ra) {
$roles[$ra->roleid] = $ra->roleid;
}
unset($ras);
// do we want users without roles?
if (!$syncall and empty($parentroles)) {
self::user_not_supposed_to_be_here($instance, $ue, $context, $plugin);
return;
}
// is parent enrol active? (we ignore enrol starts and ends, sorry it would be too complex)
$parentstatus = ENROL_USER_SUSPENDED;
foreach ($parentues as $pue) {
if ($pue->status == ENROL_USER_ACTIVE) {
$parentstatus = ENROL_USER_ACTIVE;
break;
}
}
// enrol user if not enrolled yet or fix status
if ($ue) {
if ($parentstatus != $ue->status) {
$plugin->update_user_enrol($instance, $userid, $parentstatus);
$ue->status = $parentstatus;
}
} else {
$plugin->enrol_user($instance, $userid, NULL, 0, 0, $parentstatus);
$ue = new stdClass();
$ue->userid = $userid;
$ue->enrolid = $instance->id;
$ue->status = $parentstatus;
}
// only active users in enabled instances are supposed to have roles (we can reassign the roles any time later)
if ($ue->status != ENROL_USER_ACTIVE or $instance->status != ENROL_INSTANCE_ENABLED) {
if ($roles) {
role_unassign_all(array('userid'=>$userid, 'contextid'=>$context->id, 'component'=>'enrol_meta', 'itemid'=>$instance->id));
}
return;
}
// add new roles
foreach ($parentroles as $rid) {
if (!isset($roles[$rid])) {
role_assign($rid, $userid, $context->id, 'enrol_meta', $instance->id);
}
}
// remove roles
foreach ($roles as $rid) {
if (!isset($parentroles[$rid])) {
role_unassign($rid, $userid, $context->id, 'enrol_meta', $instance->id);
}
}
}
/**
* Deal with users that are not supposed to be enrolled via this instance
* @static
* @param stdClass $instance
* @param stdClass $ue
* @param context_course $context
* @param enrol_meta $plugin
* @return void
*/
protected static function user_not_supposed_to_be_here($instance, $ue, context_course $context, $plugin) {
if (!$ue) {
// not enrolled yet - simple!
return;
}
$userid = $ue->userid;
$unenrolaction = $plugin->get_config('unenrolaction', ENROL_EXT_REMOVED_UNENROL);
if ($unenrolaction == ENROL_EXT_REMOVED_UNENROL) {
// purges grades, group membership, preferences, etc. - admins were warned!
$plugin->unenrol_user($instance, $userid);
return;
} else { // ENROL_EXT_REMOVED_SUSPENDNOROLES
// just suspend users and remove all roles (we can reassign the roles any time later)
if ($ue->status != ENROL_USER_SUSPENDED) {
$plugin->update_user_enrol($instance, $userid, ENROL_USER_SUSPENDED);
role_unassign_all(array('userid'=>$userid, 'contextid'=>$context->id, 'component'=>'enrol_meta', 'itemid'=>$instance->id));
}
return;
}
}
/**
* Triggered via role assigned event.
* @static
* @param stdClass $ra
* @return bool success
*/
public static function role_assigned($ra) {
if (!enrol_is_enabled('meta')) {
return true;
}
@@ -45,42 +246,29 @@ class enrol_meta_handler {
}
// only course level roles are interesting
$parentcontext = get_context_instance_by_id($ra->contextid);
if (!$parentcontext = context::instance_by_id($ra->contextid, IGNORE_MISSING)) {
return true;
}
if ($parentcontext->contextlevel != CONTEXT_COURSE) {
return true;
}
// does anything want to sync with this parent?
if (!$enrols = $DB->get_records('enrol', array('customint1'=>$parentcontext->instanceid, 'enrol'=>'meta'), 'id ASC')) {
return true;
}
// make sure the role sync is not prevented
$plugin = enrol_get_plugin('meta');
if ($disabled = $plugin->get_config('nosyncroleids')) {
if (in_array($ra->roleid, explode(',', $disabled))) {
return true;
}
}
foreach ($enrols as $enrol) {
// Is the user enrolled? We want to sync only really enrolled users
if (!$DB->record_exists('user_enrolments', array('userid'=>$ra->userid, 'enrolid'=>$enrol->id))) {
continue;
}
$context = get_context_instance(CONTEXT_COURSE, $enrol->courseid);
// just try to assign role, no problem if role assignment already exists
role_assign($ra->roleid, $ra->userid, $context->id, 'enrol_meta', $enrol->id);
}
self::sync_course_instances($parentcontext->instanceid, $ra->userid);
return true;
}
public function role_unassigned($ra) {
global $DB;
// note: do not test if plugin enabled, we want to keep removing previous roles
/**
* Triggered via role unassigned event.
* @static
* @param stdClass $ra
* @return bool success
*/
public static function role_unassigned($ra) {
if (!enrol_is_enabled('meta')) {
// all roles are removed via cron automatically
return true;
}
// prevent circular dependencies - we can not sync meta roles recursively
if ($ra->component === 'enrol_meta') {
@@ -88,42 +276,27 @@ class enrol_meta_handler {
}
// only course level roles are interesting
$parentcontext = get_context_instance_by_id($ra->contextid);
if (!$parentcontext = context::instance_by_id($ra->contextid, IGNORE_MISSING)) {
return true;
}
if ($parentcontext->contextlevel != CONTEXT_COURSE) {
return true;
}
// does anything want to sync with this parent?
if (!$enrols = $DB->get_records('enrol', array('customint1'=>$parentcontext->instanceid, 'enrol'=>'meta'), 'id ASC')) {
return true;
}
// note: do not check 'nosyncroleids', somebody might have just enabled it, we want to get rid of nosync roles gradually
foreach ($enrols as $enrol) {
// Is the user enrolled? We want to sync only really enrolled users
if (!$DB->record_exists('user_enrolments', array('userid'=>$ra->userid, 'enrolid'=>$enrol->id))) {
continue;
}
$context = get_context_instance(CONTEXT_COURSE, $enrol->courseid);
// now make sure the user does not have the role through some other enrol plugin
$params = array('contextid'=>$ra->contextid, 'roleid'=>$ra->roleid, 'userid'=>$ra->userid);
if ($DB->record_exists_select('role_assignments', "contextid = :contextid AND roleid = :roleid AND userid = :userid AND component <> 'enrol_meta'", $params)) {
continue;
}
// unassign role, there is no other role assignment in parent course
role_unassign($ra->roleid, $ra->userid, $context->id, 'enrol_meta', $enrol->id);
}
self::sync_course_instances($parentcontext->instanceid, $ra->userid);
return true;
}
public function user_enrolled($ue) {
global $DB;
/**
* Triggered via user enrolled event.
* @static
* @param stdClass $ue
* @return bool success
*/
public static function user_enrolled($ue) {
if (!enrol_is_enabled('meta')) {
// no more enrolments for disabled plugins
return true;
}
@@ -132,63 +305,40 @@ class enrol_meta_handler {
return true;
}
// does anything want to sync with this parent?
if (!$enrols = $DB->get_records('enrol', array('customint1'=>$ue->courseid, 'enrol'=>'meta'), 'id ASC')) {
self::sync_course_instances($ue->courseid, $ue->userid);
return true;
}
/**
* Triggered via user unenrolled event.
* @static
* @param stdClass $ue
* @return bool success
*/
public static function user_unenrolled($ue) {
// keep unenrolling even if plugin disabled
if ($ue->enrol === 'meta') {
// prevent circular dependencies - we can not sync meta enrolments recursively
return true;
}
$plugin = enrol_get_plugin('meta');
foreach ($enrols as $enrol) {
if ($ue->status == ENROL_USER_ACTIVE) {
$status = ENROL_USER_ACTIVE;
} else {
$context = get_context_instance(CONTEXT_COURSE, $enrol->courseid);
if (is_enrolled($context, $ue->userid)) {
// user already has active enrolment, do not change it
$status = ENROL_USER_ACTIVE;
} else {
$status = $ue->status;
}
}
// no problem if already enrolled
$plugin->enrol_user($enrol, $ue->userid, $status);
}
self::sync_course_instances($ue->courseid, $ue->userid);
return true;
}
public function user_unenrolled($ue) {
global $DB;
//note: do not test if plugin enabled, we want to keep removing previously linked courses
// look for unenrolment candidates - it may be possible that user has multiple enrolments...
$sql = "SELECT e.*
FROM {enrol} e
JOIN {user_enrolments} ue ON (ue.enrolid = e.id AND ue.userid = :userid)
JOIN {enrol} pe ON (pe.courseid = e.customint1 AND pe.enrol <> 'meta' AND pe.courseid = :courseid)
LEFT JOIN {user_enrolments} pue ON (pue.enrolid = pe.id AND pue.userid = ue.userid)
WHERE pue.id IS NULL AND e.enrol = 'meta'";
$params = array('courseid'=>$ue->courseid, 'userid'=>$ue->userid);
$rs = $DB->get_recordset_sql($sql, $params);
$plugin = enrol_get_plugin('meta');
foreach ($rs as $enrol) {
$plugin->unenrol_user($enrol, $ue->userid);
}
$rs->close();
return true;
}
public function user_enrol_modified($ue) {
global $DB;
// update enrolment status if necessary
/**
* Triggered via user enrolment modification event.
* @static
* @param stdClass $ue
* @return bool success
*/
public static function user_enrol_modified($ue) {
if (!enrol_is_enabled('meta')) {
// no modifications if plugin disabled
return true;
}
@@ -197,37 +347,41 @@ class enrol_meta_handler {
return true;
}
// does anything want to sync with this parent?
if (!$enrols = $DB->get_records('enrol', array('customint1'=>$ue->courseid, 'enrol'=>'meta'), 'id ASC')) {
return true;
}
$plugin = enrol_get_plugin('meta');
foreach ($enrols as $enrol) {
$plugin->update_user_enrol($enrol, $ue->userid, $ue->status);
}
self::sync_course_instances($ue->courseid, $ue->userid);
return true;
}
public function course_deleted($course) {
/**
* Triggered via course_deleted event.
* @static
* @param stdClass $course
* @return bool success
*/
public static function course_deleted($course) {
global $DB;
// note: do not test if plugin enabled, we want to keep removing previously linked courses
// NOTE: do not test if plugin enabled, we want to keep disabling instances with invalid course links
// does anything want to sync with this parent?
if (!$enrols = $DB->get_records('enrol', array('customint1'=>$course->id, 'enrol'=>'meta'), 'id ASC')) {
if (!$enrols = $DB->get_records('enrol', array('customint1'=>$course->id, 'enrol'=>'meta'), 'courseid ASC, id ASC')) {
return true;
}
$plugin = enrol_get_plugin('meta');
// hack the DB info for all courses first
foreach ($enrols as $enrol) {
// unenrol all users
$ues = $DB->get_recordset('user_enrolments', array('enrolid'=>$enrol->id));
foreach ($ues as $ue) {
$plugin->unenrol_user($enrol, $ue->userid);
}
$ues->close();
$enrol->customint1 = 0;
$enrol->status = ENROL_INSTANCE_DISABLED;
$DB->update_record('enrol', $enrol);
$context = context_course::instance($enrol->courseid);
role_unassign_all(array('contextid'=>$context->id, 'component'=>'enrol_meta', 'itemid'=>$enrol->id));
}
// now trigger sync for each instance and purge caches
foreach ($enrols as $enrol) {
$plugin->update_status($enrol, ENROL_INSTANCE_DISABLED);
}
return true;
@@ -237,104 +391,226 @@ class enrol_meta_handler {
/**
* Sync all meta course links.
*
* @param int $courseid one course, empty mean all
* @return void
* @param bool $verbose verbose CLI output
* @return int 0 means ok, 1 means error, 2 means plugin disabled
*/
function enrol_meta_sync($courseid = NULL) {
function enrol_meta_sync($courseid = NULL, $verbose = false) {
global $CFG, $DB;
// unfortunately this may take a loooong time
@set_time_limit(0); //if this fails during upgrade we can continue from cron, no big deal
// purge all roles if meta sync disabled, those can be recreated later here in cron
if (!enrol_is_enabled('meta')) {
if ($verbose) {
mtrace('Meta sync plugin is disabled, unassigning all plugin roles and stopping.');
}
role_unassign_all(array('component'=>'enrol_meta'));
return 2;
}
$instances = array(); //cache
// unfortunately this may take a long time, execution can be interrupted safely
@set_time_limit(0);
raise_memory_limit(MEMORY_HUGE);
if ($verbose) {
mtrace('Starting user enrolment synchronisation...');
}
$instances = array(); // cache instances
$meta = enrol_get_plugin('meta');
$onecourse = $courseid ? "AND e.courseid = :courseid" : "";
$unenrolaction = $meta->get_config('unenrolaction', ENROL_EXT_REMOVED_UNENROL);
$skiproles = $meta->get_config('nosyncroleids', '');
$skiproles = empty($skiproles) ? array() : explode(',', $skiproles);
$syncall = $meta->get_config('syncall', 1);
$allroles = get_all_roles();
// iterate through all not enrolled yet users
if (enrol_is_enabled('meta')) {
list($enabled, $params) = $DB->get_in_or_equal(explode(',', $CFG->enrol_plugins_enabled), SQL_PARAMS_NAMED, 'e');
if ($courseid) {
$params['courseid'] = $courseid;
}
$sql = "SELECT pue.userid, e.id AS enrolid
FROM {user_enrolments} pue
JOIN {enrol} pe ON (pe.id = pue.enrolid AND pe.enrol <> 'meta' AND pe.enrol $enabled )
JOIN {enrol} e ON (e.customint1 = pe.courseid AND e.enrol = 'meta' AND e.status = :statusenabled $onecourse)
LEFT JOIN {user_enrolments} ue ON (ue.enrolid = e.id AND ue.userid = pue.userid)
WHERE ue.id IS NULL";
$params['statusenabled'] = ENROL_INSTANCE_ENABLED;
$params['courseid'] = $courseid;
$onecourse = $courseid ? "AND e.courseid = :courseid" : "";
list($enabled, $params) = $DB->get_in_or_equal(explode(',', $CFG->enrol_plugins_enabled), SQL_PARAMS_NAMED, 'e');
$params['courseid'] = $courseid;
$sql = "SELECT pue.userid, e.id AS enrolid, pue.status
FROM {user_enrolments} pue
JOIN {enrol} pe ON (pe.id = pue.enrolid AND pe.enrol <> 'meta' AND pe.enrol $enabled)
JOIN {enrol} e ON (e.customint1 = pe.courseid AND e.enrol = 'meta' $onecourse)
LEFT JOIN {user_enrolments} ue ON (ue.enrolid = e.id AND ue.userid = pue.userid)
WHERE ue.id IS NULL";
$rs = $DB->get_recordset_sql($sql, $params);
foreach($rs as $ue) {
if (!isset($instances[$ue->enrolid])) {
$instances[$ue->enrolid] = $DB->get_record('enrol', array('id'=>$ue->enrolid));
}
$meta->enrol_user($instances[$ue->enrolid], $ue->userid);
}
$rs->close();
}
// unenrol as necessary - ignore enabled flag, we want to get rid of all
$sql = "SELECT ue.userid, e.id AS enrolid
FROM {user_enrolments} ue
JOIN {enrol} e ON (e.id = ue.enrolid AND e.enrol = 'meta' $onecourse)
LEFT JOIN (SELECT xue.userid, xe.courseid
FROM {enrol} xe
JOIN {user_enrolments} xue ON (xue.enrolid = xe.id)
) pue ON (pue.courseid = e.customint1 AND pue.userid = ue.userid)
WHERE pue.courseid IS NULL";
//TODO: this may use a bit of SQL optimisation
$rs = $DB->get_recordset_sql($sql, array('courseid'=>$courseid));
$rs = $DB->get_recordset_sql($sql, $params);
foreach($rs as $ue) {
if (!isset($instances[$ue->enrolid])) {
$instances[$ue->enrolid] = $DB->get_record('enrol', array('id'=>$ue->enrolid));
}
$meta->unenrol_user($instances[$ue->enrolid], $ue->userid);
$instance = $instances[$ue->enrolid];
if (!$syncall) {
// this may be slow if very many users are ignored in sync
$parentcontext = context_course::instance($instance->customint1);
list($ignoreroles, $params) = $DB->get_in_or_equal($skiproles, SQL_PARAMS_NAMED, 'ri', false, -1);
$params['contextid'] = $parentcontext->id;
$params['userid'] = $ue->userid;
$select = "contextid = :contextid AND userid = :userid AND component <> 'enrol_meta' AND roleid $ignoreroles";
if (!$DB->record_exists_select('role_assignments', $select, $params)) {
// bad luck, this user does not have any role we want in parent course
if ($verbose) {
mtrace(" skipping enrolling: $ue->userid ==> $instance->courseid (user without role)");
}
continue;
}
}
$meta->enrol_user($instance, $ue->userid, $ue->status);
if ($verbose) {
mtrace(" enrolling: $ue->userid ==> $instance->courseid");
}
}
$rs->close();
// now assign all necessary roles
if (enrol_is_enabled('meta')) {
$enabled = explode(',', $CFG->enrol_plugins_enabled);
foreach($enabled as $k=>$v) {
if ($v === 'meta') {
continue; // no meta sync of meta roles
// unenrol as necessary - ignore enabled flag, we want to get rid of existing enrols in any case
$onecourse = $courseid ? "AND e.courseid = :courseid" : "";
list($enabled, $params) = $DB->get_in_or_equal(explode(',', $CFG->enrol_plugins_enabled), SQL_PARAMS_NAMED, 'e');
$params['courseid'] = $courseid;
$sql = "SELECT ue.*
FROM {user_enrolments} ue
JOIN {enrol} e ON (e.id = ue.enrolid AND e.enrol = 'meta' $onecourse)
LEFT JOIN (SELECT xpue.userid, xpe.courseid
FROM {user_enrolments} xpue
JOIN {enrol} xpe ON (xpe.id = xpue.enrolid AND xpe.enrol <> 'meta' AND xpe.enrol $enabled)
) pue ON (pue.courseid = e.customint1 AND pue.userid = ue.userid)
WHERE pue.userid IS NULL";
$rs = $DB->get_recordset_sql($sql, $params);
foreach($rs as $ue) {
if (!isset($instances[$ue->enrolid])) {
$instances[$ue->enrolid] = $DB->get_record('enrol', array('id'=>$ue->enrolid));
}
$instance = $instances[$ue->enrolid];
if ($unenrolaction == ENROL_EXT_REMOVED_UNENROL) {
$meta->unenrol_user($instance, $ue->userid);
if ($verbose) {
mtrace(" unenrolling: $ue->userid ==> $instance->courseid");
}
$enabled[$k] = 'enrol_'.$v;
continue;
} else { // ENROL_EXT_REMOVED_SUSPENDNOROLES
// just disable and ignore any changes
if ($ue->status != ENROL_USER_SUSPENDED) {
$meta->update_user_enrol($instance, $ue->userid, ENROL_USER_SUSPENDED);
$context = context_course::instance($instance->courseid);
role_unassign_all(array('userid'=>$ue->userid, 'contextid'=>$context->id, 'component'=>'enrol_meta'));
if ($verbose) {
mtrace(" suspending and removing all roles: $ue->userid ==> $instance->courseid");
}
}
continue;
}
$enabled[] = $DB->sql_empty(); // manual assignments are replicated too
}
$rs->close();
list($enabled, $params) = $DB->get_in_or_equal($enabled, SQL_PARAMS_NAMED, 'e');
$sql = "SELECT DISTINCT pra.roleid, pra.userid, c.id AS contextid, e.id AS enrolid
FROM {role_assignments} pra
JOIN {user} u ON (u.id = pra.userid AND u.deleted = 0)
JOIN {context} pc ON (pc.id = pra.contextid AND pc.contextlevel = :coursecontext AND pra.component $enabled)
JOIN {enrol} e ON (e.customint1 = pc.instanceid AND e.enrol = 'meta' AND e.status = :statusenabled $onecourse)
JOIN {context} c ON (c.contextlevel = pc.contextlevel AND c.instanceid = e.courseid)
LEFT JOIN {role_assignments} ra ON (ra.contextid = c.id AND ra.userid = pra.userid AND ra.roleid = pra.itemid AND ra.itemid = e.id AND ra.component = 'enrol_meta')
WHERE ra.id IS NULL";
$params['statusenabled'] = ENROL_INSTANCE_ENABLED;
$params['coursecontext'] = CONTEXT_COURSE;
$params['courseid'] = $courseid;
if ($ignored = $meta->get_config('nosyncroleids')) {
list($notignored, $xparams) = $DB->get_in_or_equal(explode(',', $ignored), SQL_PARAMS_NAMED, 'ig', false);
$params = array_merge($params, $xparams);
$sql = "$sql AND pra.roleid $notignored";
// update status - meta enrols + start and end dates are ignored, sorry
// note the trick here is that the active enrolment and instance constants have value 0
$onecourse = $courseid ? "AND e.courseid = :courseid" : "";
list($enabled, $params) = $DB->get_in_or_equal(explode(',', $CFG->enrol_plugins_enabled), SQL_PARAMS_NAMED, 'e');
$params['courseid'] = $courseid;
$sql = "SELECT ue.userid, ue.enrolid, pue.pstatus
FROM {user_enrolments} ue
JOIN {enrol} e ON (e.id = ue.enrolid AND e.enrol = 'meta' $onecourse)
JOIN (SELECT xpue.userid, xpe.courseid, MIN(xpue.status + xpe.status) AS pstatus
FROM {user_enrolments} xpue
JOIN {enrol} xpe ON (xpe.id = xpue.enrolid AND xpe.enrol <> 'meta' AND xpe.enrol $enabled)
GROUP BY xpue.userid, xpe.courseid
) pue ON (pue.courseid = e.customint1 AND pue.userid = ue.userid)
WHERE (pue.pstatus = 0 AND ue.status > 0) OR (pue.pstatus > 0 and ue.status = 0)";
$rs = $DB->get_recordset_sql($sql, $params);
foreach($rs as $ue) {
if (!isset($instances[$ue->enrolid])) {
$instances[$ue->enrolid] = $DB->get_record('enrol', array('id'=>$ue->enrolid));
}
$instance = $instances[$ue->enrolid];
$ue->pstatus = ($ue->pstatus == ENROL_USER_ACTIVE) ? ENROL_USER_ACTIVE : ENROL_USER_SUSPENDED;
if ($ue->pstatus == ENROL_USER_ACTIVE and !$syncall and $unenrolaction != ENROL_EXT_REMOVED_UNENROL) {
// this may be slow if very many users are ignored in sync
$parentcontext = context_course::instance($instance->customint1);
list($ignoreroles, $params) = $DB->get_in_or_equal($skiproles, SQL_PARAMS_NAMED, 'ri', false, -1);
$params['contextid'] = $parentcontext->id;
$params['userid'] = $ue->userid;
$select = "contextid = :contextid AND userid = :userid AND component <> 'enrol_meta' AND roleid $ignoreroles";
if (!$DB->record_exists_select('role_assignments', $select, $params)) {
// bad luck, this user does not have any role we want in parent course
if ($verbose) {
mtrace(" skipping unsuspending: $ue->userid ==> $instance->courseid (user without role)");
}
continue;
}
}
$rs = $DB->get_recordset_sql($sql, $params);
foreach($rs as $ra) {
role_assign($ra->roleid, $ra->userid, $ra->contextid, 'enrol_meta', $ra->enrolid);
$meta->update_user_enrol($instance, $ue->userid, $ue->pstatus);
if ($verbose) {
if ($ue->pstatus == ENROL_USER_ACTIVE) {
mtrace(" unsuspending: $ue->userid ==> $instance->courseid");
} else {
mtrace(" suspending: $ue->userid ==> $instance->courseid");
}
}
$rs->close();
}
$rs->close();
// now assign all necessary roles
$enabled = explode(',', $CFG->enrol_plugins_enabled);
foreach($enabled as $k=>$v) {
if ($v === 'meta') {
continue; // no meta sync of meta roles
}
$enabled[$k] = 'enrol_'.$v;
}
$enabled[] = $DB->sql_empty(); // manual assignments are replicated too
$onecourse = $courseid ? "AND e.courseid = :courseid" : "";
list($enabled, $params) = $DB->get_in_or_equal($enabled, SQL_PARAMS_NAMED, 'e');
$params['coursecontext'] = CONTEXT_COURSE;
$params['courseid'] = $courseid;
$params['activeuser'] = ENROL_USER_ACTIVE;
$params['enabledinstance'] = ENROL_INSTANCE_ENABLED;
$sql = "SELECT DISTINCT pra.roleid, pra.userid, c.id AS contextid, e.id AS enrolid, e.courseid
FROM {role_assignments} pra
JOIN {user} u ON (u.id = pra.userid AND u.deleted = 0)
JOIN {context} pc ON (pc.id = pra.contextid AND pc.contextlevel = :coursecontext AND pra.component $enabled)
JOIN {enrol} e ON (e.customint1 = pc.instanceid AND e.enrol = 'meta' $onecourse AND e.status = :enabledinstance)
JOIN {user_enrolments} ue ON (ue.enrolid = e.id AND ue.userid = u.id AND ue.status = :activeuser)
JOIN {context} c ON (c.contextlevel = pc.contextlevel AND c.instanceid = e.courseid)
LEFT JOIN {role_assignments} ra ON (ra.contextid = c.id AND ra.userid = pra.userid AND ra.roleid = pra.roleid AND ra.itemid = e.id AND ra.component = 'enrol_meta')
WHERE ra.id IS NULL";
if ($ignored = $meta->get_config('nosyncroleids')) {
list($notignored, $xparams) = $DB->get_in_or_equal(explode(',', $ignored), SQL_PARAMS_NAMED, 'ig', false);
$params = array_merge($params, $xparams);
$sql = "$sql AND pra.roleid $notignored";
}
$rs = $DB->get_recordset_sql($sql, $params);
foreach($rs as $ra) {
role_assign($ra->roleid, $ra->userid, $ra->contextid, 'enrol_meta', $ra->enrolid);
if ($verbose) {
mtrace(" assigning role: $ra->userid ==> $ra->courseid as ".$allroles[$ra->roleid]->shortname);
}
}
$rs->close();
// remove unwanted roles - include ignored roles and disabled plugins too
$params = array('coursecontext' => CONTEXT_COURSE, 'courseid' => $courseid);
$onecourse = $courseid ? "AND e.courseid = :courseid" : "";
$params = array();
$params['coursecontext'] = CONTEXT_COURSE;
$params['courseid'] = $courseid;
$params['activeuser'] = ENROL_USER_ACTIVE;
$params['enabledinstance'] = ENROL_INSTANCE_ENABLED;
if ($ignored = $meta->get_config('nosyncroleids')) {
list($notignored, $xparams) = $DB->get_in_or_equal(explode(',', $ignored), SQL_PARAMS_NAMED, 'ig', false);
$params = array_merge($params, $xparams);
@@ -342,78 +618,81 @@ function enrol_meta_sync($courseid = NULL) {
} else {
$notignored = "";
}
$sql = "SELECT ra.roleid, ra.userid, ra.contextid, ra.itemid
$sql = "SELECT ra.roleid, ra.userid, ra.contextid, ra.itemid, e.courseid
FROM {role_assignments} ra
JOIN {enrol} e ON (e.id = ra.itemid AND ra.component = 'enrol_meta' AND e.enrol = 'meta' $onecourse)
JOIN {context} pc ON (pc.instanceid = e.customint1 AND pc.contextlevel = :coursecontext)
LEFT JOIN {role_assignments} pra ON (pra.contextid = pc.id AND pra.userid = ra.userid AND pra.roleid = ra.roleid AND pra.component <> 'enrol_meta' $notignored)
WHERE pra.id IS NULL";
LEFT JOIN {user_enrolments} ue ON (ue.enrolid = e.id AND ue.userid = ra.userid AND ue.status = :activeuser)
WHERE pra.id IS NULL OR ue.id IS NULL OR e.status <> :enabledinstance";
$rs = $DB->get_recordset_sql($sql, $params);
foreach($rs as $ra) {
role_unassign($ra->roleid, $ra->userid, $ra->contextid, 'enrol_meta', $ra->itemid);
if ($verbose) {
mtrace(" unassigning role: $ra->userid ==> $ra->courseid as ".$allroles[$ra->roleid]->shortname);
}
}
$rs->close();
// sync enrolment status
if (enrol_is_enabled('meta')) {
$enabled = explode(',', $CFG->enrol_plugins_enabled);
foreach($enabled as $k=>$v) {
if ($v === 'meta') {
unset($enabled[$k]);
}
}
list($enabled, $params) = $DB->get_in_or_equal($enabled, SQL_PARAMS_NAMED, 'e');
if ($courseid) {
// kick out or suspend users without synced roles if syncall disabled
if (!$syncall) {
if ($unenrolaction == ENROL_EXT_REMOVED_UNENROL) {
$onecourse = $courseid ? "AND e.courseid = :courseid" : "";
$params = array();
$params['coursecontext'] = CONTEXT_COURSE;
$params['courseid'] = $courseid;
}
//note: this will probably take a long time on mysql...
$sql = "SELECT ue.userid, e.id AS enrolid
FROM {user_enrolments} ue
JOIN {enrol} e ON (e.id = ue.enrolid AND e.enrol = 'meta' AND e.status = :statusenabled $onecourse)
LEFT JOIN (SELECT pue.id, pue.userid, pe.courseid
FROM {user_enrolments} pue
JOIN {enrol} pe ON (pe.enrol $enabled)
WHERE pue.enrolid = pe.id AND pue.status = :activestatus2) xx ON (xx.userid = ue.userid AND xx.courseid = e.customint1)
WHERE ue.status = :activestatus1 AND xx.id IS NULL";
$params['statusenabled'] = ENROL_INSTANCE_ENABLED;
$params['activestatus1'] = ENROL_USER_ACTIVE;
$params['activestatus2'] = ENROL_USER_ACTIVE;
$params['courseid'] = $courseid;
$rs = $DB->get_recordset_sql($sql, $params);
foreach($rs as $ue) {
if (!isset($instances[$ue->enrolid])) {
$instances[$ue->enrolid] = $DB->get_record('enrol', array('id'=>$ue->enrolid));
$sql = "SELECT ue.userid, ue.enrolid
FROM {user_enrolments} ue
JOIN {enrol} e ON (e.id = ue.enrolid AND e.enrol = 'meta' $onecourse)
JOIN {context} c ON (e.courseid = c.instanceid AND c.contextlevel = :coursecontext)
LEFT JOIN {role_assignments} ra ON (ra.contextid = c.id AND ra.itemid = e.id AND ra.userid = ue.userid)
WHERE ra.id IS NULL";
$ues = $DB->get_recordset_sql($sql, $params);
foreach($ues as $ue) {
if (!isset($instances[$ue->enrolid])) {
$instances[$ue->enrolid] = $DB->get_record('enrol', array('id'=>$ue->enrolid));
}
$instance = $instances[$ue->enrolid];
$meta->unenrol_user($instance, $ue->userid);
if ($verbose) {
mtrace(" unenrolling: $ue->userid ==> $instance->courseid (user without role)");
}
}
$meta->update_user_enrol($instances[$ue->enrolid], $ue->userid, ENROL_USER_SUSPENDED);
}
$rs->close();
$ues->close();
list($enabled, $params) = $DB->get_in_or_equal(explode(',', $CFG->enrol_plugins_enabled), SQL_PARAMS_NAMED, 'e');
if ($courseid) {
} else {
// just suspend the users
$onecourse = $courseid ? "AND e.courseid = :courseid" : "";
$params = array();
$params['coursecontext'] = CONTEXT_COURSE;
$params['courseid'] = $courseid;
}
// enable if at least one enrolment active in linked course
$sql = "SELECT DISTINCT ue.userid, e.id AS enrolid
FROM {user_enrolments} ue
JOIN {enrol} e ON (e.id = ue.enrolid AND e.enrol = 'meta' AND e.status = :statusenabled $onecourse)
JOIN {enrol} pe ON (pe.courseid = e.customint1 AND pe.enrol $enabled)
JOIN {user_enrolments} pue ON (pue.enrolid = pe.id AND pue.userid = ue.userid AND pue.status = :activestatus)
WHERE ue.status = :suspendedstatus";
$params['statusenabled'] = ENROL_INSTANCE_ENABLED;
$params['suspendedstatus'] = ENROL_USER_SUSPENDED;
$params['activestatus'] = ENROL_USER_ACTIVE;
$params['courseid'] = $courseid;
$rs = $DB->get_recordset_sql($sql, $params);
foreach($rs as $ue) {
if (!isset($instances[$ue->enrolid])) {
$instances[$ue->enrolid] = $DB->get_record('enrol', array('id'=>$ue->enrolid));
$params['active'] = ENROL_USER_ACTIVE;
$sql = "SELECT ue.userid, ue.enrolid
FROM {user_enrolments} ue
JOIN {enrol} e ON (e.id = ue.enrolid AND e.enrol = 'meta' $onecourse)
JOIN {context} c ON (e.courseid = c.instanceid AND c.contextlevel = :coursecontext)
LEFT JOIN {role_assignments} ra ON (ra.contextid = c.id AND ra.itemid = e.id AND ra.userid = ue.userid)
WHERE ra.id IS NULL AND ue.status = :active";
$ues = $DB->get_recordset_sql($sql, $params);
foreach($ues as $ue) {
if (!isset($instances[$ue->enrolid])) {
$instances[$ue->enrolid] = $DB->get_record('enrol', array('id'=>$ue->enrolid));
}
$instance = $instances[$ue->enrolid];
$meta->update_user_enrol($instance, $ue->userid, ENROL_USER_SUSPENDED);
if ($verbose) {
mtrace(" suspending: $ue->userid ==> $instance->courseid (user without role)");
}
}
$meta->update_user_enrol($instances[$ue->enrolid], $ue->userid, ENROL_USER_ACTIVE);
$ues->close();
}
$rs->close();
}
if ($verbose) {
mtrace('...user enrolment synchronisation finished.');
}
return 0;
}
+7 -1
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
@@ -38,5 +37,12 @@ if ($ADMIN->fulltree) {
$allroles[$role->id] = $rolename;
}
$settings->add(new admin_setting_configmultiselect('enrol_meta/nosyncroleids', get_string('nosyncroleids', 'enrol_meta'), get_string('nosyncroleids_desc', 'enrol_meta'), array(), $allroles));
$settings->add(new admin_setting_configcheckbox('enrol_meta/syncall', get_string('syncall', 'enrol_meta'), get_string('syncall_desc', 'enrol_meta'), 1));
$options = array(
ENROL_EXT_REMOVED_UNENROL => get_string('extremovedunenrol', 'enrol'),
ENROL_EXT_REMOVED_SUSPENDNOROLES => get_string('extremovedsuspendnoroles', 'enrol'));
$settings->add(new admin_setting_configselect('enrol_meta/unenrolaction', get_string('extremovedaction', 'enrol'), get_string('extremovedaction_help', 'enrol'), ENROL_EXT_REMOVED_UNENROL, $options));
}
}
+2 -2
View File
@@ -25,7 +25,7 @@
defined('MOODLE_INTERNAL') || die();
$plugin->version = 2011112900; // The current plugin version (Date: YYYYMMDDXX)
$plugin->version = 2011112901; // The current plugin version (Date: YYYYMMDDXX)
$plugin->requires = 2011112900; // Requires this Moodle version
$plugin->component = 'enrol_meta'; // Full name of the plugin (used for diagnostics)
$plugin->cron = 60;
$plugin->cron = 60*60; // run cron every hour by default, it is not out-of-sync often