예제 #1
0
/**
 * 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);
                }
            }
        }
    }
}
예제 #2
0
파일: lib.php 프로젝트: evltuma/moodle
 /**
  * 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;
 }
예제 #3
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;
 }
예제 #4
0
파일: lib.php 프로젝트: abhilash1994/moodle
 /**
  * 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;
 }
예제 #5
0
파일: locallib.php 프로젝트: JP-Git/moodle
/**
 * 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);
}
예제 #6
0
 /**
  * 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;
 }
예제 #7
0
파일: lib.php 프로젝트: evltuma/moodle
 /**
  * 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;
 }
예제 #8
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();
     }
 }
예제 #9
0
파일: locallib.php 프로젝트: evltuma/moodle
/**
 * 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;
}
예제 #10
0
 /**
  * Calculate the stats.
  *
  * @param \qubaid_condition $qubaids Which question usages to calculate the stats for?
  * @return all_calculated_for_qubaid_condition The calculated stats.
  */
 public function calculate($qubaids)
 {
     $this->progress->start_progress('', 6);
     list($lateststeps, $summarks) = $this->get_latest_steps($qubaids);
     if ($lateststeps) {
         $this->progress->start_progress('', count($lateststeps), 1);
         // Compute the statistics of position, and for random questions, work
         // out which questions appear in which positions.
         foreach ($lateststeps as $step) {
             $this->progress->increment_progress();
             $israndomquestion = $step->questionid != $this->stats->for_slot($step->slot)->questionid;
             $breakdownvariants = !$israndomquestion && $this->stats->for_slot($step->slot)->break_down_by_variant();
             // If this is a variant we have not seen before create a place to store stats calculations for this variant.
             if ($breakdownvariants && is_null($this->stats->for_slot($step->slot, $step->variant))) {
                 $question = $this->stats->for_slot($step->slot)->question;
                 $this->stats->initialise_for_slot($step->slot, $question, $step->variant);
                 $this->stats->for_slot($step->slot, $step->variant)->randomguessscore = $this->get_random_guess_score($question);
             }
             // Step data walker for main question.
             $this->initial_steps_walker($step, $this->stats->for_slot($step->slot), $summarks, true, $breakdownvariants);
             // If this is a random question do the calculations for sub question stats.
             if ($israndomquestion) {
                 if (is_null($this->stats->for_subq($step->questionid))) {
                     $this->stats->initialise_for_subq($step);
                 } else {
                     if ($this->stats->for_subq($step->questionid)->maxmark != $step->maxmark) {
                         $this->stats->for_subq($step->questionid)->differentweights = true;
                     }
                 }
                 // If this is a variant of this subq we have not seen before create a place to store stats calculations for it.
                 if (is_null($this->stats->for_subq($step->questionid, $step->variant))) {
                     $this->stats->initialise_for_subq($step, $step->variant);
                 }
                 $this->initial_steps_walker($step, $this->stats->for_subq($step->questionid), $summarks, false);
                 // Extra stuff we need to do in this loop for subqs to keep track of where they need to be displayed later.
                 $number = $this->stats->for_slot($step->slot)->question->number;
                 $this->stats->for_subq($step->questionid)->usedin[$number] = $number;
                 // Keep track of which random questions are actually selected from each pool of questions that random
                 // questions are pulled from.
                 $randomselectorstring = $this->stats->for_slot($step->slot)->random_selector_string();
                 if (!isset($this->randomselectors[$randomselectorstring])) {
                     $this->randomselectors[$randomselectorstring] = array();
                 }
                 $this->randomselectors[$randomselectorstring][$step->questionid] = $step->questionid;
             }
         }
         $this->progress->end_progress();
         foreach ($this->randomselectors as $key => $notused) {
             ksort($this->randomselectors[$key]);
             $this->randomselectors[$key] = implode(',', $this->randomselectors[$key]);
         }
         $this->stats->subquestions = question_load_questions($this->stats->get_all_subq_ids());
         // Compute the statistics for sub questions, if there are any.
         $this->progress->start_progress('', count($this->stats->subquestions), 1);
         foreach ($this->stats->subquestions as $qid => $subquestion) {
             $this->progress->increment_progress();
             $subquestion->maxmark = $this->stats->for_subq($qid)->maxmark;
             $this->stats->for_subq($qid)->question = $subquestion;
             $this->stats->for_subq($qid)->randomguessscore = $this->get_random_guess_score($subquestion);
             if ($variants = $this->stats->for_subq($qid)->get_variants()) {
                 foreach ($variants as $variant) {
                     $this->stats->for_subq($qid, $variant)->question = $subquestion;
                     $this->stats->for_subq($qid, $variant)->randomguessscore = $this->get_random_guess_score($subquestion);
                 }
                 $this->stats->for_subq($qid)->sort_variants();
             }
             $this->initial_question_walker($this->stats->for_subq($qid));
             if ($this->stats->for_subq($qid)->usedin) {
                 sort($this->stats->for_subq($qid)->usedin, SORT_NUMERIC);
                 $this->stats->for_subq($qid)->positions = implode(',', $this->stats->for_subq($qid)->usedin);
             } else {
                 $this->stats->for_subq($qid)->positions = '';
             }
         }
         $this->progress->end_progress();
         // Finish computing the averages, and put the sub-question data into the
         // corresponding questions.
         // This cannot be a foreach loop because we need to have both
         // $question and $nextquestion available, but apart from that it is
         // foreach ($this->questions as $qid => $question).
         $slots = $this->stats->get_all_slots();
         $this->progress->start_progress('', count($slots), 1);
         while (list(, $slot) = each($slots)) {
             $this->stats->for_slot($slot)->sort_variants();
             $this->progress->increment_progress();
             $nextslot = current($slots);
             $this->initial_question_walker($this->stats->for_slot($slot));
             // The rest of this loop is to finish working out where randomly selected question stats should be displayed.
             if ($this->stats->for_slot($slot)->question->qtype == 'random') {
                 $randomselectorstring = $this->stats->for_slot($slot)->random_selector_string();
                 if ($nextslot && $randomselectorstring == $this->stats->for_slot($nextslot)->random_selector_string()) {
                     continue;
                     // Next loop iteration.
                 }
                 if (isset($this->randomselectors[$randomselectorstring])) {
                     $this->stats->for_slot($slot)->subquestions = $this->randomselectors[$randomselectorstring];
                 }
             }
         }
         $this->progress->end_progress();
         // Go through the records one more time.
         $this->progress->start_progress('', count($lateststeps), 1);
         foreach ($lateststeps as $step) {
             $this->progress->increment_progress();
             $israndomquestion = $this->stats->for_slot($step->slot)->question->qtype == 'random';
             $this->secondary_steps_walker($step, $this->stats->for_slot($step->slot), $summarks);
             if ($israndomquestion) {
                 $this->secondary_steps_walker($step, $this->stats->for_subq($step->questionid), $summarks);
             }
         }
         $this->progress->end_progress();
         $slots = $this->stats->get_all_slots();
         $this->progress->start_progress('', count($slots), 1);
         $sumofcovariancewithoverallmark = 0;
         foreach ($this->stats->get_all_slots() as $slot) {
             $this->progress->increment_progress();
             $this->secondary_question_walker($this->stats->for_slot($slot));
             $this->sumofmarkvariance += $this->stats->for_slot($slot)->markvariance;
             if ($this->stats->for_slot($slot)->covariancewithoverallmark >= 0) {
                 $sumofcovariancewithoverallmark += sqrt($this->stats->for_slot($slot)->covariancewithoverallmark);
             }
         }
         $this->progress->end_progress();
         $subqids = $this->stats->get_all_subq_ids();
         $this->progress->start_progress('', count($subqids), 1);
         foreach ($subqids as $subqid) {
             $this->progress->increment_progress();
             $this->secondary_question_walker($this->stats->for_subq($subqid));
         }
         $this->progress->end_progress();
         foreach ($this->stats->get_all_slots() as $slot) {
             if ($sumofcovariancewithoverallmark) {
                 if ($this->stats->for_slot($slot)->negcovar) {
                     $this->stats->for_slot($slot)->effectiveweight = null;
                 } else {
                     $this->stats->for_slot($slot)->effectiveweight = 100 * sqrt($this->stats->for_slot($slot)->covariancewithoverallmark) / $sumofcovariancewithoverallmark;
                 }
             } else {
                 $this->stats->for_slot($slot)->effectiveweight = null;
             }
         }
         $this->stats->cache($qubaids);
         // All finished.
         $this->progress->end_progress();
     }
     return $this->stats;
 }
예제 #11
0
파일: locallib.php 프로젝트: stronk7/moodle
/**
 * 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;
}
예제 #12
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;

        $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);
    }
예제 #13
0
파일: lib.php 프로젝트: Jtgadbois/Pedadida
 /**
  * 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;
 }
예제 #14
0
파일: lib.php 프로젝트: alanaipe2015/moodle
 /**
  * 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;
 }
예제 #15
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;
}