Example #1
0
/**
 * Sync all cohort course links.
 * @param progress_trace $trace
 * @param int $courseid one course, empty mean all
 * @return int 0 means ok, 1 means error, 2 means plugin disabled
 */
function enrol_cohort_sync(progress_trace $trace, $courseid = NULL)
{
    global $CFG, $DB;
    require_once "{$CFG->dirroot}/group/lib.php";
    // Purge all roles if cohort sync disabled, those can be recreated later here by cron or CLI.
    if (!enrol_is_enabled('cohort')) {
        $trace->output('Cohort sync plugin is disabled, unassigning all plugin roles and stopping.');
        role_unassign_all(array('component' => 'enrol_cohort'));
        return 2;
    }
    // Unfortunately this may take a long time, this script can be interrupted without problems.
    core_php_time_limit::raise();
    raise_memory_limit(MEMORY_HUGE);
    $trace->output('Starting user enrolment synchronisation...');
    $allroles = get_all_roles();
    $instances = array();
    //cache
    $plugin = enrol_get_plugin('cohort');
    $unenrolaction = $plugin->get_config('unenrolaction', ENROL_EXT_REMOVED_UNENROL);
    // Iterate through all not enrolled yet users.
    $onecourse = $courseid ? "AND e.courseid = :courseid" : "";
    $sql = "SELECT cm.userid, e.id AS enrolid, ue.status\n              FROM {cohort_members} cm\n              JOIN {enrol} e ON (e.customint1 = cm.cohortid AND e.enrol = 'cohort' AND e.status = :enrolstatus {$onecourse})\n              JOIN {user} u ON (u.id = cm.userid AND u.deleted = 0)\n         LEFT JOIN {user_enrolments} ue ON (ue.enrolid = e.id AND ue.userid = cm.userid)\n             WHERE ue.id IS NULL OR ue.status = :suspended";
    $params = array();
    $params['courseid'] = $courseid;
    $params['suspended'] = ENROL_USER_SUSPENDED;
    $params['enrolstatus'] = ENROL_INSTANCE_ENABLED;
    $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 ($ue->status == ENROL_USER_SUSPENDED) {
            $plugin->update_user_enrol($instance, $ue->userid, ENROL_USER_ACTIVE);
            $trace->output("unsuspending: {$ue->userid} ==> {$instance->courseid} via cohort {$instance->customint1}", 1);
        } else {
            $plugin->enrol_user($instance, $ue->userid);
            $trace->output("enrolling: {$ue->userid} ==> {$instance->courseid} via cohort {$instance->customint1}", 1);
        }
    }
    $rs->close();
    // Unenrol as necessary.
    $sql = "SELECT ue.*, e.courseid\n              FROM {user_enrolments} ue\n              JOIN {enrol} e ON (e.id = ue.enrolid AND e.enrol = 'cohort' {$onecourse})\n         LEFT JOIN {cohort_members} cm ON (cm.cohortid = e.customint1 AND cm.userid = ue.userid)\n             WHERE cm.id IS NULL";
    $rs = $DB->get_recordset_sql($sql, array('courseid' => $courseid));
    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) {
            // Remove enrolment together with group membership, grades, preferences, etc.
            $plugin->unenrol_user($instance, $ue->userid);
            $trace->output("unenrolling: {$ue->userid} ==> {$instance->courseid} via cohort {$instance->customint1}", 1);
        } else {
            // ENROL_EXT_REMOVED_SUSPENDNOROLES
            // Just disable and ignore any changes.
            if ($ue->status != ENROL_USER_SUSPENDED) {
                $plugin->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_cohort', 'itemid' => $instance->id));
                $trace->output("suspending and unsassigning all roles: {$ue->userid} ==> {$instance->courseid}", 1);
            }
        }
    }
    $rs->close();
    unset($instances);
    // Now assign all necessary roles to enrolled users - skip suspended instances and users.
    $onecourse = $courseid ? "AND e.courseid = :courseid" : "";
    $sql = "SELECT e.roleid, ue.userid, c.id AS contextid, e.id AS itemid, e.courseid\n              FROM {user_enrolments} ue\n              JOIN {enrol} e ON (e.id = ue.enrolid AND e.enrol = 'cohort' AND e.status = :statusenabled {$onecourse})\n              JOIN {role} r ON (r.id = e.roleid)\n              JOIN {context} c ON (c.instanceid = e.courseid AND c.contextlevel = :coursecontext)\n              JOIN {user} u ON (u.id = ue.userid AND u.deleted = 0)\n         LEFT JOIN {role_assignments} ra ON (ra.contextid = c.id AND ra.userid = ue.userid AND ra.itemid = e.id AND ra.component = 'enrol_cohort' AND e.roleid = ra.roleid)\n             WHERE ue.status = :useractive AND ra.id IS NULL";
    $params = array();
    $params['statusenabled'] = ENROL_INSTANCE_ENABLED;
    $params['useractive'] = ENROL_USER_ACTIVE;
    $params['coursecontext'] = CONTEXT_COURSE;
    $params['courseid'] = $courseid;
    $rs = $DB->get_recordset_sql($sql, $params);
    foreach ($rs as $ra) {
        role_assign($ra->roleid, $ra->userid, $ra->contextid, 'enrol_cohort', $ra->itemid);
        $trace->output("assigning role: {$ra->userid} ==> {$ra->courseid} as " . $allroles[$ra->roleid]->shortname, 1);
    }
    $rs->close();
    // Remove unwanted roles - sync role can not be changed, we only remove role when unenrolled.
    $onecourse = $courseid ? "AND e.courseid = :courseid" : "";
    $sql = "SELECT ra.roleid, ra.userid, ra.contextid, ra.itemid, e.courseid\n              FROM {role_assignments} ra\n              JOIN {context} c ON (c.id = ra.contextid AND c.contextlevel = :coursecontext)\n              JOIN {enrol} e ON (e.id = ra.itemid AND e.enrol = 'cohort' {$onecourse})\n         LEFT JOIN {user_enrolments} ue ON (ue.enrolid = e.id AND ue.userid = ra.userid AND ue.status = :useractive)\n             WHERE ra.component = 'enrol_cohort' AND (ue.id IS NULL OR e.status <> :statusenabled)";
    $params = array();
    $params['statusenabled'] = ENROL_INSTANCE_ENABLED;
    $params['useractive'] = ENROL_USER_ACTIVE;
    $params['coursecontext'] = CONTEXT_COURSE;
    $params['courseid'] = $courseid;
    $rs = $DB->get_recordset_sql($sql, $params);
    foreach ($rs as $ra) {
        role_unassign($ra->roleid, $ra->userid, $ra->contextid, 'enrol_cohort', $ra->itemid);
        $trace->output("unassigning role: {$ra->userid} ==> {$ra->courseid} as " . $allroles[$ra->roleid]->shortname, 1);
    }
    $rs->close();
    // Finally sync groups.
    $affectedusers = groups_sync_with_enrolment('cohort', $courseid);
    foreach ($affectedusers['removed'] as $gm) {
        $trace->output("removing user from group: {$gm->userid} ==> {$gm->courseid} - {$gm->groupname}", 1);
    }
    foreach ($affectedusers['added'] as $ue) {
        $trace->output("adding user to group: {$ue->userid} ==> {$ue->courseid} - {$ue->groupname}", 1);
    }
    $trace->output('...user enrolment synchronisation finished.');
    return 0;
}
Example #2
0
/**
 * Sync all meta course links.
 *
 * @param int $courseid one course, empty mean all
 * @param bool $verbose verbose CLI output
 * @return int 0 means ok, 1 means error, 2 means plugin disabled
 */
function enrol_meta_sync($courseid = NULL, $verbose = false)
{
    global $CFG, $DB;
    require_once "{$CFG->dirroot}/group/lib.php";
    // 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;
    }
    // unfortunately this may take a long time, execution can be interrupted safely
    core_php_time_limit::raise();
    raise_memory_limit(MEMORY_HUGE);
    if ($verbose) {
        mtrace('Starting user enrolment synchronisation...');
    }
    $instances = array();
    // cache instances
    $meta = enrol_get_plugin('meta');
    $unenrolaction = $meta->get_config('unenrolaction', ENROL_EXT_REMOVED_SUSPENDNOROLES);
    $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
    $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\n              FROM {user_enrolments} pue\n              JOIN {enrol} pe ON (pe.id = pue.enrolid AND pe.enrol <> 'meta' AND pe.enrol {$enabled})\n              JOIN {enrol} e ON (e.customint1 = pe.courseid AND e.enrol = 'meta' {$onecourse})\n              JOIN {user} u ON (u.id = pue.userid AND u.deleted = 0)\n         LEFT JOIN {user_enrolments} ue ON (ue.enrolid = e.id AND ue.userid = pue.userid)\n             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));
        }
        $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 ($instance->customint2) {
            groups_add_member($instance->customint2, $ue->userid, 'enrol_meta', $instance->id);
        }
        if ($verbose) {
            mtrace("  enrolling: {$ue->userid} ==> {$instance->courseid}");
        }
    }
    $rs->close();
    // 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.*\n              FROM {user_enrolments} ue\n              JOIN {enrol} e ON (e.id = ue.enrolid AND e.enrol = 'meta' {$onecourse})\n         LEFT JOIN ({user_enrolments} xpue\n                      JOIN {enrol} xpe ON (xpe.id = xpue.enrolid AND xpe.enrol <> 'meta' AND xpe.enrol {$enabled})\n                   ) ON (xpe.courseid = e.customint1 AND xpue.userid = ue.userid)\n             WHERE xpue.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}");
            }
        } else {
            if ($unenrolaction == ENROL_EXT_REMOVED_SUSPEND) {
                if ($ue->status != ENROL_USER_SUSPENDED) {
                    $meta->update_user_enrol($instance, $ue->userid, ENROL_USER_SUSPENDED);
                    if ($verbose) {
                        mtrace("  suspending: {$ue->userid} ==> {$instance->courseid}");
                    }
                }
            } else {
                if ($unenrolaction == ENROL_EXT_REMOVED_SUSPENDNOROLES) {
                    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', 'itemid' => $instance->id));
                        if ($verbose) {
                            mtrace("  suspending and removing all roles: {$ue->userid} ==> {$instance->courseid}");
                        }
                    }
                }
            }
        }
    }
    $rs->close();
    // 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\n              FROM {user_enrolments} ue\n              JOIN {enrol} e ON (e.id = ue.enrolid AND e.enrol = 'meta' {$onecourse})\n              JOIN (SELECT xpue.userid, xpe.courseid, MIN(xpue.status + xpe.status) AS pstatus\n                      FROM {user_enrolments} xpue\n                      JOIN {enrol} xpe ON (xpe.id = xpue.enrolid AND xpe.enrol <> 'meta' AND xpe.enrol {$enabled})\n                  GROUP BY xpue.userid, xpe.courseid\n                   ) pue ON (pue.courseid = e.customint1 AND pue.userid = ue.userid)\n             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;
            }
        }
        $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();
    // 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[] = '';
    // 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\n              FROM {role_assignments} pra\n              JOIN {user} u ON (u.id = pra.userid AND u.deleted = 0)\n              JOIN {context} pc ON (pc.id = pra.contextid AND pc.contextlevel = :coursecontext AND pra.component {$enabled})\n              JOIN {enrol} e ON (e.customint1 = pc.instanceid AND e.enrol = 'meta' {$onecourse} AND e.status = :enabledinstance)\n              JOIN {user_enrolments} ue ON (ue.enrolid = e.id AND ue.userid = u.id AND ue.status = :activeuser)\n              JOIN {context} c ON (c.contextlevel = pc.contextlevel AND c.instanceid = e.courseid)\n         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')\n             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
    $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);
        $notignored = "AND pra.roleid {$notignored}";
    } else {
        $notignored = "";
    }
    $sql = "SELECT ra.roleid, ra.userid, ra.contextid, ra.itemid, e.courseid\n              FROM {role_assignments} ra\n              JOIN {enrol} e ON (e.id = ra.itemid AND ra.component = 'enrol_meta' AND e.enrol = 'meta' {$onecourse})\n              JOIN {context} pc ON (pc.instanceid = e.customint1 AND pc.contextlevel = :coursecontext)\n         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})\n         LEFT JOIN {user_enrolments} ue ON (ue.enrolid = e.id AND ue.userid = ra.userid AND ue.status = :activeuser)\n             WHERE pra.id IS NULL OR ue.id IS NULL OR e.status <> :enabledinstance";
    if ($unenrolaction != ENROL_EXT_REMOVED_SUSPEND) {
        $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();
    }
    // 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;
            $sql = "SELECT ue.userid, ue.enrolid\n                      FROM {user_enrolments} ue\n                      JOIN {enrol} e ON (e.id = ue.enrolid AND e.enrol = 'meta' {$onecourse})\n                      JOIN {context} c ON (e.courseid = c.instanceid AND c.contextlevel = :coursecontext)\n                 LEFT JOIN {role_assignments} ra ON (ra.contextid = c.id AND ra.itemid = e.id AND ra.userid = ue.userid)\n                     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)");
                }
            }
            $ues->close();
        } else {
            // just suspend the users
            $onecourse = $courseid ? "AND e.courseid = :courseid" : "";
            $params = array();
            $params['coursecontext'] = CONTEXT_COURSE;
            $params['courseid'] = $courseid;
            $params['active'] = ENROL_USER_ACTIVE;
            $sql = "SELECT ue.userid, ue.enrolid\n                      FROM {user_enrolments} ue\n                      JOIN {enrol} e ON (e.id = ue.enrolid AND e.enrol = 'meta' {$onecourse})\n                      JOIN {context} c ON (e.courseid = c.instanceid AND c.contextlevel = :coursecontext)\n                 LEFT JOIN {role_assignments} ra ON (ra.contextid = c.id AND ra.itemid = e.id AND ra.userid = ue.userid)\n                     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)");
                }
            }
            $ues->close();
        }
    }
    // Finally sync groups.
    $affectedusers = groups_sync_with_enrolment('meta', $courseid);
    if ($verbose) {
        foreach ($affectedusers['removed'] as $gm) {
            mtrace("removing user from group: {$gm->userid} ==> {$gm->courseid} - {$gm->groupname}", 1);
        }
        foreach ($affectedusers['added'] as $ue) {
            mtrace("adding user to group: {$ue->userid} ==> {$ue->courseid} - {$ue->groupname}", 1);
        }
    }
    if ($verbose) {
        mtrace('...user enrolment synchronisation finished.');
    }
    return 0;
}