/** * Run synchronization process * * @param progress_trace $trace * @param int|null $courseid or null for all courses * @return void */ function local_metagroups_sync(progress_trace $trace, $courseid = null) { global $DB; if ($courseid !== null) { $courseids = array($courseid); } else { $courseids = local_metagroups_parent_courses(); } foreach (array_unique($courseids) as $courseid) { $parent = get_course($courseid); // If parent course doesn't use groups, we can skip synchronization. if (groups_get_course_groupmode($parent) == NOGROUPS) { continue; } $trace->output($parent->fullname, 1); $children = local_metagroups_child_courses($parent->id); foreach ($children as $childid) { $child = get_course($childid); $trace->output($child->fullname, 2); $groups = groups_get_all_groups($child->id); foreach ($groups as $group) { if (!($metagroup = $DB->get_record('groups', array('courseid' => $parent->id, 'idnumber' => $group->id)))) { $metagroup = new stdClass(); $metagroup->courseid = $parent->id; $metagroup->idnumber = $group->id; $metagroup->name = $group->name; $metagroup->id = groups_create_group($metagroup, false, false); } $trace->output($metagroup->name, 3); $users = groups_get_members($group->id); foreach ($users as $user) { groups_add_member($metagroup->id, $user->id, 'local_metagroups', $group->id); } } } } }
/** * Sync all meta 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 */ public function sync(progress_trace $trace, $courseid = null) { global $DB; if (!enrol_is_enabled('self')) { $trace->finished(); return 2; } // Unfortunately this may take a long time, execution can be interrupted safely here. core_php_time_limit::raise(); raise_memory_limit(MEMORY_HUGE); $trace->output('Verifying self-enrolments...'); $params = array('now' => time(), 'useractive' => ENROL_USER_ACTIVE, 'courselevel' => CONTEXT_COURSE); $coursesql = ""; if ($courseid) { $coursesql = "AND e.courseid = :courseid"; $params['courseid'] = $courseid; } // Note: the logic of self enrolment guarantees that user logged in at least once (=== u.lastaccess set) // and that user accessed course at least once too (=== user_lastaccess record exists). // First deal with users that did not log in for a really long time - they do not have user_lastaccess records. $sql = "SELECT e.*, ue.userid\n FROM {user_enrolments} ue\n JOIN {enrol} e ON (e.id = ue.enrolid AND e.enrol = 'self' AND e.customint2 > 0)\n JOIN {user} u ON u.id = ue.userid\n WHERE :now - u.lastaccess > e.customint2\n {$coursesql}"; $rs = $DB->get_recordset_sql($sql, $params); foreach ($rs as $instance) { $userid = $instance->userid; unset($instance->userid); $this->unenrol_user($instance, $userid); $days = $instance->customint2 / 60 * 60 * 24; $trace->output("unenrolling user {$userid} from course {$instance->courseid} as they have did not log in for at least {$days} days", 1); } $rs->close(); // Now unenrol from course user did not visit for a long time. $sql = "SELECT e.*, ue.userid\n FROM {user_enrolments} ue\n JOIN {enrol} e ON (e.id = ue.enrolid AND e.enrol = 'self' AND e.customint2 > 0)\n JOIN {user_lastaccess} ul ON (ul.userid = ue.userid AND ul.courseid = e.courseid)\n WHERE :now - ul.timeaccess > e.customint2\n {$coursesql}"; $rs = $DB->get_recordset_sql($sql, $params); foreach ($rs as $instance) { $userid = $instance->userid; unset($instance->userid); $this->unenrol_user($instance, $userid); $days = $instance->customint2 / 60 * 60 * 24; $trace->output("unenrolling user {$userid} from course {$instance->courseid} as they have did not access course for at least {$days} days", 1); } $rs->close(); $trace->output('...user self-enrolment updates finished.'); $trace->finished(); $this->process_expirations($trace, $courseid); return 0; }
/** * Sync all meta 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 */ public function sync(progress_trace $trace, $courseid = null) { global $DB; if (!enrol_is_enabled('manual')) { $trace->finished(); return 2; } // Unfortunately this may take a long time, execution can be interrupted safely here. @set_time_limit(0); raise_memory_limit(MEMORY_HUGE); $trace->output('Verifying manual enrolment expiration...'); $params = array('now' => time(), 'useractive' => ENROL_USER_ACTIVE, 'courselevel' => CONTEXT_COURSE); $coursesql = ""; if ($courseid) { $coursesql = "AND e.courseid = :courseid"; $params['courseid'] = $courseid; } // Deal with expired accounts. $action = $this->get_config('expiredaction', ENROL_EXT_REMOVED_KEEP); if ($action == ENROL_EXT_REMOVED_UNENROL) { $instances = array(); $sql = "SELECT ue.*, e.courseid, c.id AS contextid\n FROM {user_enrolments} ue\n JOIN {enrol} e ON (e.id = ue.enrolid AND e.enrol = 'manual')\n JOIN {context} c ON (c.instanceid = e.courseid AND c.contextlevel = :courselevel)\n WHERE ue.timeend > 0 AND ue.timeend < :now\n {$coursesql}"; $rs = $DB->get_recordset_sql($sql, $params); foreach ($rs as $ue) { if (empty($instances[$ue->enrolid])) { $instances[$ue->enrolid] = $DB->get_record('enrol', array('id' => $ue->enrolid)); } $instance = $instances[$ue->enrolid]; // Always remove all manually assigned roles here, this may break enrol_self roles but we do not want hardcoded hacks here. role_unassign_all(array('userid' => $ue->userid, 'contextid' => $ue->contextid, 'component' => '', 'itemid' => 0), true); $this->unenrol_user($instance, $ue->userid); $trace->output("unenrolling expired user {$ue->userid} from course {$instance->courseid}", 1); } $rs->close(); unset($instances); } else { if ($action == ENROL_EXT_REMOVED_SUSPENDNOROLES) { $instances = array(); $sql = "SELECT ue.*, e.courseid, c.id AS contextid\n FROM {user_enrolments} ue\n JOIN {enrol} e ON (e.id = ue.enrolid AND e.enrol = 'manual')\n JOIN {context} c ON (c.instanceid = e.courseid AND c.contextlevel = :courselevel)\n WHERE ue.timeend > 0 AND ue.timeend < :now\n AND ue.status = :useractive\n {$coursesql}"; $rs = $DB->get_recordset_sql($sql, $params); foreach ($rs as $ue) { if (empty($instances[$ue->enrolid])) { $instances[$ue->enrolid] = $DB->get_record('enrol', array('id' => $ue->enrolid)); } $instance = $instances[$ue->enrolid]; // Always remove all manually assigned roles here, this may break enrol_self roles but we do not want hardcoded hacks here. role_unassign_all(array('userid' => $ue->userid, 'contextid' => $ue->contextid, 'component' => '', 'itemid' => 0), true); $this->update_user_enrol($instance, $ue->userid, ENROL_USER_SUSPENDED); $trace->output("suspending expired user {$ue->userid} in course {$instance->courseid}", 1); } $rs->close(); unset($instances); } else { // ENROL_EXT_REMOVED_KEEP means no changes. } } $trace->output('...manual enrolment updates finished.'); $trace->finished(); return 0; }
/** * Will update a moodle course with new values from LDAP * A field will be updated only if it is marked to be updated * on sync in plugin settings * * @param object $course * @param array $externalcourse * @param progress_trace $trace * @return bool */ protected function update_course($course, $externalcourse, progress_trace $trace) { global $CFG, $DB; $coursefields = array('shortname', 'fullname', 'summary'); static $shouldupdate; // Initialize $shouldupdate variable. Set to true if one or more fields are marked for update. if (!isset($shouldupdate)) { $shouldupdate = false; foreach ($coursefields as $field) { $shouldupdate = $shouldupdate || $this->get_config('course_' . $field . '_updateonsync'); } } // If we should not update return immediately. if (!$shouldupdate) { return false; } require_once "{$CFG->dirroot}/course/lib.php"; $courseupdated = false; $updatedcourse = new stdClass(); $updatedcourse->id = $course->id; // Update course fields if necessary. foreach ($coursefields as $field) { // If field is marked to be updated on sync && field data was changed update it. if ($this->get_config('course_' . $field . '_updateonsync') && isset($externalcourse[$this->get_config('course_' . $field)][0]) && $course->{$field} != $externalcourse[$this->get_config('course_' . $field)][0]) { $updatedcourse->{$field} = $externalcourse[$this->get_config('course_' . $field)][0]; $courseupdated = true; } } if (!$courseupdated) { $trace->output(get_string('courseupdateskipped', 'enrol_ldap', $course)); return false; } // Do not allow empty fullname or shortname. if (isset($updatedcourse->fullname) && empty($updatedcourse->fullname) || isset($updatedcourse->shortname) && empty($updatedcourse->shortname)) { // We are in trouble! $trace->output(get_string('cannotupdatecourse', 'enrol_ldap', $course)); return false; } // Check if the shortname already exists if it does - skip course updating. if (isset($updatedcourse->shortname) && $DB->record_exists('course', array('shortname' => $updatedcourse->shortname))) { $trace->output(get_string('cannotupdatecourse_duplicateshortname', 'enrol_ldap', $course)); return false; } // Finally - update course in DB. update_course($updatedcourse); $trace->output(get_string('courseupdated', 'enrol_ldap', $course)); return true; }
/** * Very hacky function for rebuilding of log actions in target database. * @param moodle_database $target * @param progress_trace $feedback * @return void * @throws Exception on conversion error */ function tool_dbtransfer_rebuild_target_log_actions(moodle_database $target, progress_trace $feedback = null) { global $DB, $CFG; require_once "{$CFG->libdir}/upgradelib.php"; $feedback->output(get_string('convertinglogdisplay', 'tool_dbtransfer')); $olddb = $DB; $DB = $target; try { $DB->delete_records('log_display', array('component' => 'moodle')); log_update_descriptions('moodle'); $plugintypes = get_plugin_types(); foreach ($plugintypes as $type => $location) { $plugs = get_plugin_list($type); foreach ($plugs as $plug => $fullplug) { $component = $type . '_' . $plug; $DB->delete_records('log_display', array('component' => $component)); log_update_descriptions($component); } } } catch (Exception $e) { $DB = $olddb; throw $e; } $DB = $olddb; $feedback->output(get_string('done', 'core_dbtransfer', null), 1); }
/** * Synchronizes user from external db to moodle user table. * * Sync should be done by using idnumber attribute, not username. * You need to pass firstsync parameter to function to fill in * idnumbers if they don't exists in moodle user table. * * Syncing users removes (disables) users that don't exists anymore in external db. * Creates new users and updates coursecreator status of users. * * This implementation is simpler but less scalable than the one found in the LDAP module. * * @param progress_trace $trace * @param bool $do_updates Optional: set to true to force an update of existing accounts * @return int 0 means success, 1 means failure */ function sync_users(progress_trace $trace, $do_updates = false) { global $CFG, $DB; require_once $CFG->dirroot . '/user/lib.php'; // List external users. $userlist = $this->get_userlist(); // Delete obsolete internal users. if (!empty($this->config->removeuser)) { $suspendselect = ""; if ($this->config->removeuser == AUTH_REMOVEUSER_SUSPEND) { $suspendselect = "AND u.suspended = 0"; } // Find obsolete users. if (count($userlist)) { list($notin_sql, $params) = $DB->get_in_or_equal($userlist, SQL_PARAMS_NAMED, 'u', false); $params['authtype'] = $this->authtype; $sql = "SELECT u.*\n FROM {user} u\n WHERE u.auth=:authtype AND u.deleted=0 AND u.mnethostid=:mnethostid {$suspendselect} AND u.username {$notin_sql}"; } else { $sql = "SELECT u.*\n FROM {user} u\n WHERE u.auth=:authtype AND u.deleted=0 AND u.mnethostid=:mnethostid {$suspendselect}"; $params = array(); $params['authtype'] = $this->authtype; } $params['mnethostid'] = $CFG->mnet_localhost_id; $remove_users = $DB->get_records_sql($sql, $params); if (!empty($remove_users)) { $trace->output(get_string('auth_dbuserstoremove', 'auth_db', count($remove_users))); foreach ($remove_users as $user) { if ($this->config->removeuser == AUTH_REMOVEUSER_FULLDELETE) { delete_user($user); $trace->output(get_string('auth_dbdeleteuser', 'auth_db', array('name' => $user->username, 'id' => $user->id)), 1); } else { if ($this->config->removeuser == AUTH_REMOVEUSER_SUSPEND) { $updateuser = new stdClass(); $updateuser->id = $user->id; $updateuser->suspended = 1; user_update_user($updateuser, false); $trace->output(get_string('auth_dbsuspenduser', 'auth_db', array('name' => $user->username, 'id' => $user->id)), 1); } } } } unset($remove_users); } if (!count($userlist)) { // Exit right here, nothing else to do. $trace->finished(); return 0; } // Update existing accounts. if ($do_updates) { // Narrow down what fields we need to update. $all_keys = array_keys(get_object_vars($this->config)); $updatekeys = array(); foreach ($all_keys as $key) { if (preg_match('/^field_updatelocal_(.+)$/', $key, $match)) { if ($this->config->{$key} === 'onlogin') { array_push($updatekeys, $match[1]); // The actual key name. } } } unset($all_keys); unset($key); // Only go ahead if we actually have fields to update locally. if (!empty($updatekeys)) { list($in_sql, $params) = $DB->get_in_or_equal($userlist, SQL_PARAMS_NAMED, 'u', true); $params['authtype'] = $this->authtype; $sql = "SELECT u.id, u.username\n FROM {user} u\n WHERE u.auth=:authtype AND u.deleted=0 AND u.username {$in_sql}"; if ($update_users = $DB->get_records_sql($sql, $params)) { $trace->output("User entries to update: " . count($update_users)); foreach ($update_users as $user) { if ($this->update_user_record($user->username, $updatekeys)) { $trace->output(get_string('auth_dbupdatinguser', 'auth_db', array('name' => $user->username, 'id' => $user->id)), 1); } else { $trace->output(get_string('auth_dbupdatinguser', 'auth_db', array('name' => $user->username, 'id' => $user->id)) . " - " . get_string('skipped'), 1); } } unset($update_users); } } } // Create missing accounts. // NOTE: this is very memory intensive and generally inefficient. $suspendselect = ""; if ($this->config->removeuser == AUTH_REMOVEUSER_SUSPEND) { $suspendselect = "AND u.suspended = 0"; } $sql = "SELECT u.id, u.username\n FROM {user} u\n WHERE u.auth=:authtype AND u.deleted='0' AND mnethostid=:mnethostid {$suspendselect}"; $users = $DB->get_records_sql($sql, array('authtype' => $this->authtype, 'mnethostid' => $CFG->mnet_localhost_id)); // Simplify down to usernames. $usernames = array(); if (!empty($users)) { foreach ($users as $user) { array_push($usernames, $user->username); } unset($users); } $add_users = array_diff($userlist, $usernames); unset($usernames); if (!empty($add_users)) { $trace->output(get_string('auth_dbuserstoadd', 'auth_db', count($add_users))); // Do not use transactions around this foreach, we want to skip problematic users, not revert everything. foreach ($add_users as $user) { $username = $user; if ($this->config->removeuser == AUTH_REMOVEUSER_SUSPEND) { if ($olduser = $DB->get_record('user', array('username' => $username, 'deleted' => 0, 'suspended' => 1, 'mnethostid' => $CFG->mnet_localhost_id, 'auth' => $this->authtype))) { $updateuser = new stdClass(); $updateuser->id = $olduser->id; $updateuser->suspended = 0; user_update_user($updateuser); $trace->output(get_string('auth_dbreviveduser', 'auth_db', array('name' => $username, 'id' => $olduser->id)), 1); continue; } } // Do not try to undelete users here, instead select suspending if you ever expect users will reappear. // Prep a few params. $user = $this->get_userinfo_asobj($user); $user->username = $username; $user->confirmed = 1; $user->auth = $this->authtype; $user->mnethostid = $CFG->mnet_localhost_id; if (empty($user->lang)) { $user->lang = $CFG->lang; } if ($collision = $DB->get_record_select('user', "username = :username AND mnethostid = :mnethostid AND auth <> :auth", array('username' => $user->username, 'mnethostid' => $CFG->mnet_localhost_id, 'auth' => $this->authtype), 'id,username,auth')) { $trace->output(get_string('auth_dbinsertuserduplicate', 'auth_db', array('username' => $user->username, 'auth' => $collision->auth)), 1); continue; } try { $id = user_create_user($user, false); // It is truly a new user. $trace->output(get_string('auth_dbinsertuser', 'auth_db', array('name' => $user->username, 'id' => $id)), 1); } catch (moodle_exception $e) { $trace->output(get_string('auth_dbinsertusererror', 'auth_db', $user->username), 1); continue; } // If relevant, tag for password generation. if ($this->is_internal()) { set_user_preference('auth_forcepasswordchange', 1, $id); set_user_preference('create_password', 1, $id); } // Make sure user context is present. context_user::instance($id); } unset($add_users); } $trace->finished(); return 0; }
/** * Synchronise courses in all categories. * * It gets out-of-sync if: * - you move course to different category * - reorder categories * - disable enrol_category and enable it again * * @param progress_trace $trace * @return int exit code - 0 is ok, 1 means error, 2 if plugin disabled */ function enrol_category_sync_full(progress_trace $trace) { global $DB; if (!enrol_is_enabled('category')) { $trace->finished(); return 2; } // We may need a lot of time here. core_php_time_limit::raise(); $plugin = enrol_get_plugin('category'); $syscontext = context_system::instance(); // Any interesting roles worth synchronising? if (!($roles = get_roles_with_capability('enrol/category:synchronised', CAP_ALLOW, $syscontext))) { // yay, nothing to do, so let's remove all leftovers $trace->output("No roles with 'enrol/category:synchronised' capability found."); if ($instances = $DB->get_records('enrol', array('enrol' => 'category'))) { $trace->output("Deleting all category enrol instances..."); foreach ($instances as $instance) { $trace->output("deleting category enrol instance from course {$instance->courseid}", 1); $plugin->delete_instance($instance); } $trace->output("...all instances deleted."); } $trace->finished(); return 0; } $rolenames = role_fix_names($roles, null, ROLENAME_SHORT, true); $trace->output('Synchronising category enrolments for roles: ' . implode(', ', $rolenames) . '...'); list($roleids, $params) = $DB->get_in_or_equal(array_keys($roles), SQL_PARAMS_NAMED, 'r'); $params['courselevel'] = CONTEXT_COURSE; $params['catlevel'] = CONTEXT_COURSECAT; // First of all add necessary enrol instances to all courses. $parentcat = $DB->sql_concat("cat.path", "'/%'"); $parentcctx = $DB->sql_concat("cctx.path", "'/%'"); // Need whole course records to be used by add_instance(), use inner view (ci) to // get distinct records only. // TODO: Moodle 2.1. Improve enrol API to accept courseid / courserec $sql = "SELECT c.*\n FROM {course} c\n JOIN (\n SELECT DISTINCT c.id\n FROM {course} c\n JOIN {context} ctx ON (ctx.instanceid = c.id AND ctx.contextlevel = :courselevel)\n JOIN (SELECT DISTINCT cctx.path\n FROM {course_categories} cc\n JOIN {context} cctx ON (cctx.instanceid = cc.id AND cctx.contextlevel = :catlevel)\n JOIN {role_assignments} ra ON (ra.contextid = cctx.id AND ra.roleid {$roleids})\n ) cat ON (ctx.path LIKE {$parentcat})\n LEFT JOIN {enrol} e ON (e.courseid = c.id AND e.enrol = 'category')\n WHERE e.id IS NULL) ci ON (c.id = ci.id)"; $rs = $DB->get_recordset_sql($sql, $params); foreach ($rs as $course) { $plugin->add_instance($course); } $rs->close(); // Now look for courses that do not have any interesting roles in parent contexts, // but still have the instance and delete them. $sql = "SELECT e.*\n FROM {enrol} e\n JOIN {context} ctx ON (ctx.instanceid = e.courseid AND ctx.contextlevel = :courselevel)\n LEFT JOIN ({course_categories} cc\n JOIN {context} cctx ON (cctx.instanceid = cc.id AND cctx.contextlevel = :catlevel)\n JOIN {role_assignments} ra ON (ra.contextid = cctx.id AND ra.roleid {$roleids})\n ) ON (ctx.path LIKE {$parentcctx})\n WHERE e.enrol = 'category' AND cc.id IS NULL"; $rs = $DB->get_recordset_sql($sql, $params); foreach ($rs as $instance) { $plugin->delete_instance($instance); } $rs->close(); // Add missing enrolments. $sql = "SELECT e.*, cat.userid, cat.estart\n FROM {enrol} e\n JOIN {context} ctx ON (ctx.instanceid = e.courseid AND ctx.contextlevel = :courselevel)\n JOIN (SELECT cctx.path, ra.userid, MIN(ra.timemodified) AS estart\n FROM {course_categories} cc\n JOIN {context} cctx ON (cctx.instanceid = cc.id AND cctx.contextlevel = :catlevel)\n JOIN {role_assignments} ra ON (ra.contextid = cctx.id AND ra.roleid {$roleids})\n GROUP BY cctx.path, ra.userid\n ) cat ON (ctx.path LIKE {$parentcat})\n LEFT JOIN {user_enrolments} ue ON (ue.enrolid = e.id AND ue.userid = cat.userid)\n WHERE e.enrol = 'category' AND ue.id IS NULL"; $rs = $DB->get_recordset_sql($sql, $params); foreach ($rs as $instance) { $userid = $instance->userid; $estart = $instance->estart; unset($instance->userid); unset($instance->estart); $plugin->enrol_user($instance, $userid, null, $estart); $trace->output("enrolling: user {$userid} ==> course {$instance->courseid}", 1); } $rs->close(); // Remove stale enrolments. $sql = "SELECT e.*, ue.userid\n FROM {enrol} e\n JOIN {context} ctx ON (ctx.instanceid = e.courseid AND ctx.contextlevel = :courselevel)\n JOIN {user_enrolments} ue ON (ue.enrolid = e.id)\n LEFT JOIN ({course_categories} cc\n JOIN {context} cctx ON (cctx.instanceid = cc.id AND cctx.contextlevel = :catlevel)\n JOIN {role_assignments} ra ON (ra.contextid = cctx.id AND ra.roleid {$roleids})\n ) ON (ctx.path LIKE {$parentcctx} AND ra.userid = ue.userid)\n WHERE e.enrol = 'category' AND cc.id IS NULL"; $rs = $DB->get_recordset_sql($sql, $params); foreach ($rs as $instance) { $userid = $instance->userid; unset($instance->userid); $plugin->unenrol_user($instance, $userid); $trace->output("unenrolling: user {$userid} ==> course {$instance->courseid}", 1); } $rs->close(); $trace->output('...user enrolment synchronisation finished.'); $trace->finished(); return 0; }
/** * Returns a mapping of ims roles to role ids. * * @param progress_trace $trace * @return array imsrolename=>roleid */ protected function get_role_map(progress_trace $trace) { global $DB; // Get all roles. $rolemap = array(); $roles = $DB->get_records('role', null, '', 'id, name, shortname'); foreach ($roles as $id => $role) { $alias = $this->get_config('map_' . $id, $role->shortname, ''); $alias = trim(core_text::strtolower($alias)); if ($alias === '') { // Either not configured yet or somebody wants to skip these intentionally. continue; } if (isset($rolemap[$alias])) { $trace->output("Duplicate role alias {$alias} detected!"); } else { $rolemap[$alias] = $id; } } return $rolemap; }
/** * 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; }
/** * Notify person responsible for enrolments that some user enrolments will be expired soon, * it is called only if notification of enrollers (aka teachers) is enabled in course. * * This is called repeatedly every day for each course if there are any pending expiration * in the expiration threshold. * * @param int $eid * @param array $users * @param progress_trace $trace */ protected function notify_expiry_enroller($eid, $users, progress_trace $trace) { global $DB, $SESSION; $name = $this->get_name(); $instance = $DB->get_record('enrol', array('id' => $eid, 'enrol' => $name)); $context = context_course::instance($instance->courseid); $course = $DB->get_record('course', array('id' => $instance->courseid)); $enroller = $this->get_enroller($instance->id); $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); } $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)); $subject = get_string('expirymessageenrollersubject', 'enrol_' . $name, $a); $body = get_string('expirymessageenrollerbody', 'enrol_' . $name, $a); $message = new stdClass(); $message->notification = 1; $message->component = 'enrol_' . $name; $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)) { $trace->output("notifying user {$enroller->id} about all expiring {$name} enrolments in course {$instance->courseid}", 1); } else { $trace->output("error notifying user {$enroller->id} about all expiring {$name} enrolments in course {$instance->courseid}", 1); } if ($SESSION->lang !== $sessionlang) { $SESSION->lang = $sessionlang; moodle_setlocale(); } }
/** * Notify person responsible for enrolments that some user enrolments will be expired soon, * it is called only if notification of enrollers (aka teachers) is enabled in course. * * This is called repeatedly every day for each course if there are any pending expiration * in the expiration threshold. * * @param int $eid * @param array $users * @param progress_trace $trace */ protected function notify_expiry_enroller($eid, $users, progress_trace $trace) { global $DB; $name = $this->get_name(); $instance = $DB->get_record('enrol', array('id'=>$eid, 'enrol'=>$name)); $context = context_course::instance($instance->courseid); $course = $DB->get_record('course', array('id'=>$instance->courseid)); $enroller = $this->get_enroller($instance->id); $admin = get_admin(); $oldforcelang = force_current_language($enroller->lang); foreach($users as $key=>$info) { $users[$key] = '* '.$info['fullname'].' - '.userdate($info['timeend'], '', $enroller->timezone); } $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)); $subject = get_string('expirymessageenrollersubject', 'enrol_'.$name, $a); $body = get_string('expirymessageenrollerbody', 'enrol_'.$name, $a); $message = new stdClass(); $message->notification = 1; $message->component = 'enrol_'.$name; $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)) { $trace->output("notifying user $enroller->id about all expiring $name enrolments in course $instance->courseid", 1); } else { $trace->output("error notifying user $enroller->id about all expiring $name enrolments in course $instance->courseid", 1); } force_current_language($oldforcelang); }
/** * Will create the moodle course from the template * course_ext is an array as obtained from ldap -- flattened somewhat * * @param array $course_ext * @param progress_trace $trace * @return mixed false on error, id for the newly created course otherwise. */ function create_course($course_ext, progress_trace $trace) { global $CFG, $DB; require_once "{$CFG->dirroot}/course/lib.php"; // Override defaults with template course $template = false; if ($this->get_config('template')) { if ($template = $DB->get_record('course', array('shortname' => $this->get_config('template')))) { $template = fullclone(course_get_format($template)->get_course()); unset($template->id); // So we are clear to reinsert the record unset($template->fullname); unset($template->shortname); unset($template->idnumber); } } if (!$template) { $courseconfig = get_config('moodlecourse'); $template = new stdClass(); $template->summary = ''; $template->summaryformat = FORMAT_HTML; $template->format = $courseconfig->format; $template->newsitems = $courseconfig->newsitems; $template->showgrades = $courseconfig->showgrades; $template->showreports = $courseconfig->showreports; $template->maxbytes = $courseconfig->maxbytes; $template->groupmode = $courseconfig->groupmode; $template->groupmodeforce = $courseconfig->groupmodeforce; $template->visible = $courseconfig->visible; $template->lang = $courseconfig->lang; $template->groupmodeforce = $courseconfig->groupmodeforce; } $course = $template; $course->category = $this->get_config('category'); if (!$DB->record_exists('course_categories', array('id' => $this->get_config('category')))) { $categories = $DB->get_records('course_categories', array(), 'sortorder', 'id', 0, 1); $first = reset($categories); $course->category = $first->id; } // Override with required ext data $course->idnumber = $course_ext[$this->get_config('course_idnumber')][0]; $course->fullname = $course_ext[$this->get_config('course_fullname')][0]; $course->shortname = $course_ext[$this->get_config('course_shortname')][0]; if (empty($course->idnumber) || empty($course->fullname) || empty($course->shortname)) { // We are in trouble! $trace->output(get_string('cannotcreatecourse', 'enrol_ldap') . ' ' . var_export($course, true)); return false; } $summary = $this->get_config('course_summary'); if (!isset($summary) || empty($course_ext[$summary][0])) { $course->summary = ''; } else { $course->summary = $course_ext[$this->get_config('course_summary')][0]; } $newcourse = create_course($course); return $newcourse->id; }
/** * Performs a full sync with external database. * * First it creates new courses if necessary, then * enrols and unenrols users. * * @param progress_trace $trace * @return int 0 means success, 1 db connect failure, 4 db read failure */ public function sync_courses(progress_trace $trace) { global $CFG, $DB; // Make sure we sync either enrolments or courses. if (!$this->get_config('dbtype') or !$this->get_config('newcoursetable') or !$this->get_config('newcoursefullname') or !$this->get_config('newcourseshortname')) { $trace->output('Course synchronisation skipped.'); $trace->finished(); return 0; } $trace->output('Starting course synchronisation...'); // We may need a lot of memory here. core_php_time_limit::raise(); raise_memory_limit(MEMORY_HUGE); if (!($extdb = $this->db_init())) { $trace->output('Error while communicating with external enrolment database'); $trace->finished(); return 1; } $table = $this->get_config('newcoursetable'); $fullname = trim($this->get_config('newcoursefullname')); $shortname = trim($this->get_config('newcourseshortname')); $idnumber = trim($this->get_config('newcourseidnumber')); $category = trim($this->get_config('newcoursecategory')); // Lowercased versions - necessary because we normalise the resultset with array_change_key_case(). $fullname_l = strtolower($fullname); $shortname_l = strtolower($shortname); $idnumber_l = strtolower($idnumber); $category_l = strtolower($category); $localcategoryfield = $this->get_config('localcategoryfield', 'id'); $defaultcategory = $this->get_config('defaultcategory'); if (!$DB->record_exists('course_categories', array('id' => $defaultcategory))) { $trace->output("default course category does not exist!", 1); $categories = $DB->get_records('course_categories', array(), 'sortorder', 'id', 0, 1); $first = reset($categories); $defaultcategory = $first->id; } $sqlfields = array($fullname, $shortname); if ($category) { $sqlfields[] = $category; } if ($idnumber) { $sqlfields[] = $idnumber; } $sql = $this->db_get_sql($table, array(), $sqlfields, true); $createcourses = array(); if ($rs = $extdb->Execute($sql)) { if (!$rs->EOF) { while ($fields = $rs->FetchRow()) { $fields = array_change_key_case($fields, CASE_LOWER); $fields = $this->db_decode($fields); if (empty($fields[$shortname_l]) or empty($fields[$fullname_l])) { $trace->output('error: invalid external course record, shortname and fullname are mandatory: ' . json_encode($fields), 1); // Hopefully every geek can read JS, right? continue; } if ($DB->record_exists('course', array('shortname' => $fields[$shortname_l]))) { // Already exists, skip. continue; } // Allow empty idnumber but not duplicates. if ($idnumber and $fields[$idnumber_l] !== '' and $fields[$idnumber_l] !== null and $DB->record_exists('course', array('idnumber' => $fields[$idnumber_l]))) { $trace->output('error: duplicate idnumber, can not create course: ' . $fields[$shortname_l] . ' [' . $fields[$idnumber_l] . ']', 1); continue; } $course = new stdClass(); $course->fullname = $fields[$fullname_l]; $course->shortname = $fields[$shortname_l]; $course->idnumber = $idnumber ? $fields[$idnumber_l] : ''; if ($category) { if (empty($fields[$category_l])) { // Empty category means use default. $course->category = $defaultcategory; } else { if ($coursecategory = $DB->get_record('course_categories', array($localcategoryfield => $fields[$category_l]), 'id')) { // Yay, correctly specified category! $course->category = $coursecategory->id; unset($coursecategory); } else { // Bad luck, better not continue because unwanted ppl might get access to course in different category. $trace->output('error: invalid category ' . $localcategoryfield . ', can not create course: ' . $fields[$shortname_l], 1); continue; } } } else { $course->category = $defaultcategory; } $createcourses[] = $course; } } $rs->Close(); } else { $extdb->Close(); $trace->output('Error reading data from the external course table'); $trace->finished(); return 4; } if ($createcourses) { require_once "{$CFG->dirroot}/course/lib.php"; $templatecourse = $this->get_config('templatecourse'); $template = false; if ($templatecourse) { if ($template = $DB->get_record('course', array('shortname' => $templatecourse))) { $template = fullclone(course_get_format($template)->get_course()); unset($template->id); unset($template->fullname); unset($template->shortname); unset($template->idnumber); } else { $trace->output("can not find template for new course!", 1); } } if (!$template) { $courseconfig = get_config('moodlecourse'); $template = new stdClass(); $template->summary = ''; $template->summaryformat = FORMAT_HTML; $template->format = $courseconfig->format; $template->newsitems = $courseconfig->newsitems; $template->showgrades = $courseconfig->showgrades; $template->showreports = $courseconfig->showreports; $template->maxbytes = $courseconfig->maxbytes; $template->groupmode = $courseconfig->groupmode; $template->groupmodeforce = $courseconfig->groupmodeforce; $template->visible = $courseconfig->visible; $template->lang = $courseconfig->lang; $template->groupmodeforce = $courseconfig->groupmodeforce; } foreach ($createcourses as $fields) { $newcourse = clone $template; $newcourse->fullname = $fields->fullname; $newcourse->shortname = $fields->shortname; $newcourse->idnumber = $fields->idnumber; $newcourse->category = $fields->category; // Detect duplicate data once again, above we can not find duplicates // in external data using DB collation rules... if ($DB->record_exists('course', array('shortname' => $newcourse->shortname))) { $trace->output("can not insert new course, duplicate shortname detected: " . $newcourse->shortname, 1); continue; } else { if (!empty($newcourse->idnumber) and $DB->record_exists('course', array('idnumber' => $newcourse->idnumber))) { $trace->output("can not insert new course, duplicate idnumber detected: " . $newcourse->idnumber, 1); continue; } } $c = create_course($newcourse); $trace->output("creating course: {$c->id}, {$c->fullname}, {$c->shortname}, {$c->idnumber}, {$c->category}", 1); } unset($createcourses); unset($template); } // Close db connection. $extdb->Close(); $trace->output('...course synchronisation finished.'); $trace->finished(); return 0; }
/** * Sync all ilios 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_ilios_sync(progress_trace $trace, $courseid = NULL) { global $CFG, $DB; require_once "{$CFG->dirroot}/group/lib.php"; // Purge all roles if ilios sync disabled, those can be recreated later here by cron or CLI. if (!enrol_is_enabled('ilios')) { $trace->output('Ilios enrolment sync plugin is disabled, unassigning all plugin roles and stopping.'); role_unassign_all(array('component' => 'enrol_ilios')); return 2; } // Unfortunately this may take a long time, this script can be interrupted without problems. @set_time_limit(0); raise_memory_limit(MEMORY_HUGE); $trace->output('Starting user enrolment synchronisation...'); $allroles = get_all_roles(); $iliosusers = array(); // cache $plugin = enrol_get_plugin('ilios'); $http = $plugin->get_http_client(); $unenrolaction = $plugin->get_config('unenrolaction', ENROL_EXT_REMOVED_UNENROL); $moodleusersyncfield = 'idnumber'; $iliosusersyncfield = 'campusId'; // Iterate through all not enrolled yet users. $onecourse = $courseid ? "AND e.courseid = :courseid" : ""; $sql = "SELECT *\n FROM {enrol} e\n WHERE e.enrol = 'ilios' {$onecourse}"; $params = array(); $params['courseid'] = $courseid; $params['suspended'] = ENROL_USER_SUSPENDED; $instances = $DB->get_recordset_sql($sql, $params); foreach ($instances as $instance) { $synctype = $instance->customchar1; $syncid = $instance->customint1; $groups = $http->get($synctype . 's', array("id" => $syncid)); if (!empty($groups) && is_array($groups)) { $group = $groups[0]; if (!empty($group->users)) { $users = $http->getbyids('users', $group->users); $userids = array(); foreach ($users as $user) { if (!isset($iliosusers[$user->id])) { $iliosusers[$user->id] = null; if (!empty($user->{$iliosusersyncfield})) { $urec = $DB->get_record('user', array("{$moodleusersyncfield}" => $user->{$iliosusersyncfield})); if (!empty($urec)) { $iliosusers[$user->id] = array('id' => $urec->id, 'syncfield' => $urec->{$moodleusersyncfield}); } } } if ($iliosusers[$user->id] === null) { if (!empty($user->{$iliosusersyncfield})) { $trace->output("skipping: Cannot find {$iliosusersyncfield} " . $user->{$iliosusersyncfield} . " that matches Moodle user field {$moodleusersyncfield}.", 1); } else { $trace->output("skipping: Ilios user " . $user->id . " does not have a {$iliosusersyncfield} field.", 1); } } else { $userids[] = $userid = $iliosusers[$user->id]['id']; $ue = $DB->get_record('user_enrolments', array('enrolid' => $instance->id, 'userid' => $userid)); if (!empty($ue) && isset($ue->status)) { if ($ue->status == ENROL_USER_SUSPENDED) { $plugin->update_user_enrol($instance, $userid, ENROL_USER_ACTIVE); $trace->output("unsuspending: userid {$userid} ==> courseid " . $instance->courseid . " via Ilios {$synctype} {$syncid}", 1); } } else { $plugin->enrol_user($instance, $userid); $trace->output("enrolling: userid {$userid} ==> courseid " . $instance->courseid . " via Ilios {$synctype} {$syncid}", 1); } } } // Unenrol as necessary. if (!empty($userids)) { $sql = "SELECT ue.*\n FROM {user_enrolments} ue\n WHERE ue.enrolid = {$instance->id}\n AND ue.userid NOT IN ( " . implode(",", $userids) . " )"; $rs = $DB->get_recordset_sql($sql); foreach ($rs as $ue) { 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 Ilios {$synctype} {$syncid}", 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_ilios', 'itemid' => $instance->id)); $trace->output("suspending and unsassigning all roles: userid " . $ue->userid . " ==> courseid " . $instance->courseid, 1); } } } $rs->close(); } } } } $instances->close(); unset($iliosusers); // 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 = 'ilios' 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_ilios' 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_ilios', $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 = 'ilios' {$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_ilios' 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_ilios', $ra->itemid); $trace->output("unassigning role: {$ra->userid} ==> {$ra->courseid} as " . $allroles[$ra->roleid]->shortname, 1); } $rs->close(); // Finally sync groups. $onecourse = $courseid ? "AND e.courseid = :courseid" : ""; // Remove invalid. $sql = "SELECT gm.*, e.courseid, g.name AS groupname\n FROM {groups_members} gm\n JOIN {groups} g ON (g.id = gm.groupid)\n JOIN {enrol} e ON (e.enrol = 'ilios' AND e.courseid = g.courseid {$onecourse})\n JOIN {user_enrolments} ue ON (ue.userid = gm.userid AND ue.enrolid = e.id)\n WHERE gm.component='enrol_ilios' AND gm.itemid = e.id AND g.id <> e.customint6"; $params = array(); $params['courseid'] = $courseid; $rs = $DB->get_recordset_sql($sql, $params); foreach ($rs as $gm) { groups_remove_member($gm->groupid, $gm->userid); $trace->output("removing user from group: {$gm->userid} ==> {$gm->courseid} - {$gm->groupname}", 1); } $rs->close(); // Add missing. $sql = "SELECT ue.*, g.id AS groupid, e.courseid, g.name AS groupname\n FROM {user_enrolments} ue\n JOIN {enrol} e ON (e.id = ue.enrolid AND e.enrol = 'ilios' {$onecourse})\n JOIN {groups} g ON (g.courseid = e.courseid AND g.id = e.customint6)\n JOIN {user} u ON (u.id = ue.userid AND u.deleted = 0)\n LEFT JOIN {groups_members} gm ON (gm.groupid = g.id AND gm.userid = ue.userid)\n WHERE gm.id IS NULL"; $params = array(); $params['courseid'] = $courseid; $rs = $DB->get_recordset_sql($sql, $params); foreach ($rs as $ue) { groups_add_member($ue->groupid, $ue->userid, 'enrol_ilios', $ue->enrolid); $trace->output("adding user to group: {$ue->userid} ==> {$ue->courseid} - {$ue->groupname}", 1); } $rs->close(); $trace->output('...user enrolment synchronisation finished.'); return 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_delayedcohort_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('delayedcohort')) { $trace->output('Cohort sync plugin is disabled, unassigning all plugin roles and stopping.'); role_unassign_all(array('component' => 'enrol_delayedcohort')); return 2; } // Unfortunately this may take a long time, this script can be interrupted without problems. @set_time_limit(0); raise_memory_limit(MEMORY_HUGE); $trace->output('Starting user enrolment synchronisation...'); $allroles = get_all_roles(); $instances = array(); //cache $plugin = enrol_get_plugin('delayedcohort'); $unenrolaction = $plugin->get_config('unenrolaction', ENROL_EXT_REMOVED_UNENROL); // Iterate through all not enrolled yet users. $onecourse = $courseid ? "AND e.courseid = :courseid" : ""; $now = time(); $sql = "\n SELECT\n cm.userid,\n e.id AS enrolid,\n ue.status\n FROM\n {cohort_members} cm\n JOIN\n {enrol} e \n ON\n (e.customint1 = cm.cohortid AND \n e.enrol = 'delayedcohort' {$onecourse})\n JOIN\n {user} u \n ON\n (u.id = cm.userid AND u.deleted = 0)\n LEFT JOIN \n {user_enrolments} ue \n ON \n (ue.enrolid = e.id AND \n ue.userid = cm.userid)\n WHERE \n ue.id IS NULL OR\n ue.status = :suspended AND\n e.customint3 <= {$now}\n "; $params = array(); $params['courseid'] = $courseid; $params['suspended'] = ENROL_USER_SUSPENDED; $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 delayed cohort {$instance->customint1}", 1); } else { $plugin->enrol_user($instance, $ue->userid); $trace->output("enrolling: {$ue->userid} ==> {$instance->courseid} via delayed cohort {$instance->customint1}", 1); } } $rs->close(); // Unenrol as necessary (not anymore in cohort). $sql = "\n SELECT\n ue.*,\n e.courseid\n FROM\n {user_enrolments} ue\n JOIN\n {enrol} e\n ON\n (e.id = ue.enrolid AND\n e.enrol = 'delayedcohort' {$onecourse}) AND\n e.customint3 <= {$now}\n LEFT JOIN \n {cohort_members} cm\n ON\n (cm.cohortid = e.customint1 AND\n cm.userid = ue.userid)\n WHERE\n cm.id IS NULL\n "; $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 delayed 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_delayedcohort', 'itemid' => $instance->id)); $trace->output("suspending and unsassigning all roles: {$ue->userid} ==> {$instance->courseid}", 1); } } } $rs->close(); unset($instances); // Unenrol enrolled users on modified triggerdate cohorts (not yet enrolled !). $sql = "\n SELECT\n ue.*,\n e.courseid\n FROM\n {user_enrolments} ue\n JOIN\n {enrol} e\n ON\n (e.id = ue.enrolid AND\n e.enrol = 'delayedcohort' {$onecourse}) AND\n e.customint3 > {$now}\n LEFT JOIN \n {cohort_members} cm\n ON\n (cm.cohortid = e.customint1 AND\n cm.userid = ue.userid)\n "; $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 delayed 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_delayedcohort', '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" : ""; $now = time(); $sql = "\n SELECT\n e.roleid,\n ue.userid,\n c.id AS contextid,\n e.id AS itemid,\n e.courseid\n FROM \n {user_enrolments} ue\n JOIN \n {enrol} e\n ON\n (e.id = ue.enrolid AND \n e.enrol = 'delayedcohort' AND \n e.status = :statusenabled {$onecourse}) AND\n e.customint3 <= {$now}\n JOIN \n {role} r\n ON\n (r.id = e.roleid)\n JOIN\n {context} c\n ON\n (c.instanceid = e.courseid AND\n c.contextlevel = :coursecontext)\n JOIN\n {user} u\n ON\n (u.id = ue.userid AND\n u.deleted = 0)\n LEFT JOIN\n {role_assignments} ra\n ON\n (ra.contextid = c.id AND\n ra.userid = ue.userid AND\n ra.itemid = e.id AND\n ra.component = 'enrol_delayedcohort'\n AND e.roleid = ra.roleid)\n WHERE\n ue.status = :useractive AND\n ra.id IS NULL\n "; $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_delayedcohort', $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 = "\n SELECT\n ra.roleid,\n ra.userid,\n ra.contextid,\n ra.itemid,\n e.courseid\n FROM\n {role_assignments} ra\n JOIN\n {context} c\n ON\n (c.id = ra.contextid AND\n c.contextlevel = :coursecontext)\n JOIN\n {enrol} e\n ON\n (e.id = ra.itemid AND\n e.enrol = 'delayedcohort' {$onecourse}) AND\n e.customint3 <= {$now}\n LEFT JOIN\n {user_enrolments} ue\n ON\n (ue.enrolid = e.id AND\n ue.userid = ra.userid AND ue.status = :useractive)\n WHERE\n ra.component = 'enrol_delayedcohort' AND\n (ue.id IS NULL OR e.status <> :statusenabled)\n "; $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_delayedcohort', $ra->itemid); $trace->output("unassigning role: {$ra->userid} ==> {$ra->courseid} as " . $allroles[$ra->roleid]->shortname, 1); } $rs->close(); // Finally sync groups. $onecourse = $courseid ? "AND e.courseid = :courseid" : ""; // Remove invalid. $sql = "\n SELECT\n gm.*,\n e.courseid,\n g.name AS groupname\n FROM\n {groups_members} gm\n JOIN\n {groups} g\n ON\n (g.id = gm.groupid)\n JOIN\n {enrol} e\n ON\n (e.enrol = 'delayedcohort' AND\n e.courseid = g.courseid {$onecourse}) AND\n e.customint3 <= {$now}\n JOIN\n {user_enrolments} ue\n ON\n (ue.userid = gm.userid AND ue.enrolid = e.id)\n WHERE\n gm.component='enrol_delayedcohort' AND\n gm.itemid = e.id AND\n g.id <> e.customint2"; $params = array(); $params['courseid'] = $courseid; $rs = $DB->get_recordset_sql($sql, $params); foreach ($rs as $gm) { groups_remove_member($gm->groupid, $gm->userid); $trace->output("removing user from group: {$gm->userid} ==> {$gm->courseid} - {$gm->groupname}", 1); } $rs->close(); // Add missing. $sql = "\n SELECT\n ue.*,\n g.id AS groupid,\n e.courseid,\n g.name AS groupname\n FROM\n {user_enrolments} ue\n JOIN\n {enrol} e\n ON\n (e.id = ue.enrolid AND\n e.enrol = 'delayedcohort' {$onecourse}) AND\n e.customint3 <= {$now}\n JOIN\n {groups} g\n ON\n (g.courseid = e.courseid AND\n g.id = e.customint2)\n JOIN\n {user} u\n ON\n (u.id = ue.userid AND\n u.deleted = 0)\n LEFT JOIN\n {groups_members} gm\n ON\n (gm.groupid = g.id AND\n gm.userid = ue.userid)\n WHERE\n gm.id IS NULL\n "; $params = array(); $params['courseid'] = $courseid; $rs = $DB->get_recordset_sql($sql, $params); foreach ($rs as $ue) { groups_add_member($ue->groupid, $ue->userid, 'enrol_delayedcohort', $ue->enrolid); $trace->output("adding user to group: {$ue->userid} ==> {$ue->courseid} - {$ue->groupname}", 1); } $rs->close(); $trace->output('...user enrolment synchronisation finished.'); return 0; }