/** * 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; }
/** * 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; }