weekly on-sync release 5.0dev
[moodle.git] / enrol / meta / locallib.php
blob5ee305f2200a253c77c7a4388750ef617b6b5cdf
1 <?php
2 // This file is part of Moodle - http://moodle.org/
3 //
4 // Moodle is free software: you can redistribute it and/or modify
5 // it under the terms of the GNU General Public License as published by
6 // the Free Software Foundation, either version 3 of the License, or
7 // (at your option) any later version.
8 //
9 // Moodle is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 // GNU General Public License for more details.
14 // You should have received a copy of the GNU General Public License
15 // along with Moodle. If not, see <http://www.gnu.org/licenses/>.
17 /**
18 * Local stuff for meta course enrolment plugin.
20 * @package enrol_meta
21 * @copyright 2010 Petr Skoda {@link http://skodak.org}
22 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
25 defined('MOODLE_INTERNAL') || die();
28 /**
29 * Event handler for meta enrolment plugin.
31 * We try to keep everything in sync via listening to events,
32 * it may fail sometimes, so we always do a full sync in cron too.
34 class enrol_meta_handler {
36 /**
37 * Synchronise meta enrolments of this user in this course
38 * @static
39 * @param int $courseid
40 * @param int $userid
41 * @return void
43 protected static function sync_course_instances($courseid, $userid) {
44 global $DB;
46 static $preventrecursion = false;
48 // does anything want to sync with this parent?
49 if (!$enrols = $DB->get_records('enrol', array('customint1'=>$courseid, 'enrol'=>'meta'), 'id ASC')) {
50 return;
53 if ($preventrecursion) {
54 return;
57 $preventrecursion = true;
59 try {
60 foreach ($enrols as $enrol) {
61 self::sync_with_parent_course($enrol, $userid);
63 } catch (Exception $e) {
64 $preventrecursion = false;
65 throw $e;
68 $preventrecursion = false;
71 /**
72 * Synchronise user enrolments in given instance as fast as possible.
74 * All roles are removed if the meta plugin disabled.
76 * @static
77 * @param stdClass $instance
78 * @param int $userid
79 * @return void
81 protected static function sync_with_parent_course(stdClass $instance, $userid) {
82 global $DB, $CFG;
83 require_once($CFG->dirroot . '/group/lib.php');
85 $plugin = enrol_get_plugin('meta');
87 if ($instance->customint1 == $instance->courseid) {
88 // can not sync with self!!!
89 return;
92 $context = context_course::instance($instance->courseid);
94 // list of enrolments in parent course (we ignore meta enrols in parents completely)
95 list($enabled, $params) = $DB->get_in_or_equal(explode(',', $CFG->enrol_plugins_enabled), SQL_PARAMS_NAMED, 'e');
96 $params['userid'] = $userid;
97 $params['parentcourse'] = $instance->customint1;
98 $sql = "SELECT ue.*, e.status AS enrolstatus
99 FROM {user_enrolments} ue
100 JOIN {enrol} e ON (e.id = ue.enrolid AND e.enrol <> 'meta' AND e.courseid = :parentcourse AND e.enrol $enabled)
101 WHERE ue.userid = :userid";
102 $parentues = $DB->get_records_sql($sql, $params);
103 // current enrolments for this instance
104 $ue = $DB->get_record('user_enrolments', array('enrolid'=>$instance->id, 'userid'=>$userid));
106 // first deal with users that are not enrolled in parent
107 if (empty($parentues)) {
108 self::user_not_supposed_to_be_here($instance, $ue, $context, $plugin);
109 return;
112 if (!$parentcontext = context_course::instance($instance->customint1, IGNORE_MISSING)) {
113 // Weird, we should not get here.
114 return;
117 $skiproles = $plugin->get_config('nosyncroleids', '');
118 $skiproles = empty($skiproles) ? array() : explode(',', $skiproles);
119 $syncall = $plugin->get_config('syncall', 1);
121 // roles in parent course (meta enrols must be ignored!)
122 $parentroles = array();
123 list($ignoreroles, $params) = $DB->get_in_or_equal($skiproles, SQL_PARAMS_NAMED, 'ri', false, -1);
124 $params['contextid'] = $parentcontext->id;
125 $params['userid'] = $userid;
126 $select = "contextid = :contextid AND userid = :userid AND component <> 'enrol_meta' AND roleid $ignoreroles";
127 foreach($DB->get_records_select('role_assignments', $select, $params) as $ra) {
128 $parentroles[$ra->roleid] = $ra->roleid;
131 // roles from this instance
132 $roles = array();
133 $ras = $DB->get_records('role_assignments', array('contextid'=>$context->id, 'userid'=>$userid, 'component'=>'enrol_meta', 'itemid'=>$instance->id));
134 foreach($ras as $ra) {
135 $roles[$ra->roleid] = $ra->roleid;
137 unset($ras);
139 // do we want users without roles?
140 if (!$syncall and empty($parentroles)) {
141 self::user_not_supposed_to_be_here($instance, $ue, $context, $plugin);
142 return;
145 // Is parent enrol active? Find minimum timestart and maximum timeend of all active enrolments.
146 $parentstatus = ENROL_USER_SUSPENDED;
147 $parenttimeend = null;
148 $parenttimestart = null;
149 foreach ($parentues as $pue) {
150 if ($pue->status == ENROL_USER_ACTIVE && $pue->enrolstatus == ENROL_INSTANCE_ENABLED) {
151 $parentstatus = ENROL_USER_ACTIVE;
152 if ($parenttimeend === null || $pue->timeend == 0 || ($parenttimeend && $parenttimeend < $pue->timeend)) {
153 $parenttimeend = $pue->timeend;
155 if ($parenttimestart === null || $parenttimestart > $pue->timestart) {
156 $parenttimestart = $pue->timestart;
161 // Enrol user if not enrolled yet or fix status/timestart/timeend. Use the minimum timestart and maximum timeend found above.
162 if ($ue) {
163 if ($parentstatus != $ue->status ||
164 ($parentstatus == ENROL_USER_ACTIVE && ($parenttimestart != $ue->timestart || $parenttimeend != $ue->timeend))) {
165 $plugin->update_user_enrol($instance, $userid, $parentstatus, $parenttimestart, $parenttimeend);
166 $ue->status = $parentstatus;
167 $ue->timestart = $parenttimestart;
168 $ue->timeend = $parenttimeend;
170 } else {
171 $plugin->enrol_user($instance, $userid, NULL, (int)$parenttimestart, (int)$parenttimeend, $parentstatus);
172 $ue = new stdClass();
173 $ue->userid = $userid;
174 $ue->enrolid = $instance->id;
175 $ue->status = $parentstatus;
176 if ($instance->customint2 && $group = $DB->get_record('groups', ['id' => $instance->customint2])) {
177 groups_add_member($group, $userid, 'enrol_meta', $instance->id);
181 $unenrolaction = $plugin->get_config('unenrolaction', ENROL_EXT_REMOVED_SUSPENDNOROLES);
183 // Only active users in enabled instances are supposed to have roles (we can reassign the roles any time later).
184 if ($ue->status != ENROL_USER_ACTIVE or $instance->status != ENROL_INSTANCE_ENABLED or
185 ($parenttimeend and $parenttimeend < time()) or ($parenttimestart > time())) {
186 if ($unenrolaction == ENROL_EXT_REMOVED_SUSPEND) {
187 // Always keep the roles.
188 } else if ($roles) {
189 // This will only unassign roles that were assigned in this enrolment method, leaving all manual role assignments intact.
190 role_unassign_all(array('userid'=>$userid, 'contextid'=>$context->id, 'component'=>'enrol_meta', 'itemid'=>$instance->id));
192 return;
195 // add new roles
196 foreach ($parentroles as $rid) {
197 if (!isset($roles[$rid])) {
198 role_assign($rid, $userid, $context->id, 'enrol_meta', $instance->id);
202 if ($unenrolaction == ENROL_EXT_REMOVED_SUSPEND) {
203 // Always keep the roles.
204 return;
207 // remove roles
208 foreach ($roles as $rid) {
209 if (!isset($parentroles[$rid])) {
210 role_unassign($rid, $userid, $context->id, 'enrol_meta', $instance->id);
216 * Deal with users that are not supposed to be enrolled via this instance
217 * @static
218 * @param stdClass $instance
219 * @param stdClass $ue
220 * @param context_course $context
221 * @param enrol_meta $plugin
222 * @return void
224 protected static function user_not_supposed_to_be_here($instance, $ue, context_course $context, $plugin) {
225 if (!$ue) {
226 // Not enrolled yet - simple!
227 return;
230 $userid = $ue->userid;
231 $unenrolaction = $plugin->get_config('unenrolaction', ENROL_EXT_REMOVED_SUSPENDNOROLES);
233 if ($unenrolaction == ENROL_EXT_REMOVED_UNENROL) {
234 // Purges grades, group membership, preferences, etc. - admins were warned!
235 $plugin->unenrol_user($instance, $userid);
237 } else if ($unenrolaction == ENROL_EXT_REMOVED_SUSPEND) {
238 if ($ue->status != ENROL_USER_SUSPENDED) {
239 $plugin->update_user_enrol($instance, $userid, ENROL_USER_SUSPENDED);
242 } else if ($unenrolaction == ENROL_EXT_REMOVED_SUSPENDNOROLES) {
243 if ($ue->status != ENROL_USER_SUSPENDED) {
244 $plugin->update_user_enrol($instance, $userid, ENROL_USER_SUSPENDED);
246 role_unassign_all(array('userid'=>$userid, 'contextid'=>$context->id, 'component'=>'enrol_meta', 'itemid'=>$instance->id));
248 } else {
249 debugging('Unknown unenrol action '.$unenrolaction);
255 * Sync all meta course links.
257 * @param int $courseid one course, empty mean all
258 * @param bool $verbose verbose CLI output
259 * @return int 0 means ok, 1 means error, 2 means plugin disabled
261 function enrol_meta_sync($courseid = NULL, $verbose = false) {
262 global $CFG, $DB;
263 require_once("{$CFG->dirroot}/group/lib.php");
265 // purge all roles if meta sync disabled, those can be recreated later here in cron
266 if (!enrol_is_enabled('meta')) {
267 if ($verbose) {
268 mtrace('Meta sync plugin is disabled, unassigning all plugin roles and stopping.');
270 role_unassign_all(array('component'=>'enrol_meta'));
271 return 2;
274 // unfortunately this may take a long time, execution can be interrupted safely
275 core_php_time_limit::raise();
276 raise_memory_limit(MEMORY_HUGE);
278 if ($verbose) {
279 mtrace('Starting user enrolment synchronisation...');
282 $instances = array(); // cache instances
284 $meta = enrol_get_plugin('meta');
286 $unenrolaction = $meta->get_config('unenrolaction', ENROL_EXT_REMOVED_SUSPENDNOROLES);
287 $skiproles = $meta->get_config('nosyncroleids', '');
288 $skiproles = empty($skiproles) ? array() : explode(',', $skiproles);
289 $syncall = $meta->get_config('syncall', 1);
291 $allroles = get_all_roles();
294 // Iterate through all not enrolled yet users. For each active enrolment of each user find the minimum
295 // enrolment startdate and maximum enrolment enddate.
296 // This SQL relies on the fact that ENROL_USER_ACTIVE < ENROL_USER_SUSPENDED
297 // and ENROL_INSTANCE_ENABLED < ENROL_INSTANCE_DISABLED. Condition "pue.status + pe.status = 0" means
298 // that enrolment is active. When MIN(pue.status + pe.status)=0 it means there exists an active
299 // enrolment.
300 $onecourse = $courseid ? "AND e.courseid = :courseid" : "";
301 list($enabled, $params) = $DB->get_in_or_equal(explode(',', $CFG->enrol_plugins_enabled), SQL_PARAMS_NAMED, 'e');
302 $params['courseid'] = $courseid;
303 $sql = "SELECT pue.userid, e.id AS enrolid, MIN(pue.status + pe.status) AS status,
304 MIN(CASE WHEN (pue.status + pe.status = 0) THEN pue.timestart ELSE " . SQL_INT_MAX . " END) AS timestart,
305 MAX(CASE WHEN (pue.status + pe.status = 0) THEN
306 (CASE WHEN pue.timeend = 0 THEN " . SQL_INT_MAX . " ELSE pue.timeend END)
307 ELSE 0 END) AS timeend
308 FROM {user_enrolments} pue
309 JOIN {enrol} pe ON (pe.id = pue.enrolid AND pe.enrol <> 'meta' AND pe.enrol $enabled)
310 JOIN {enrol} e ON (e.customint1 = pe.courseid AND e.enrol = 'meta' AND e.status = :enrolstatus $onecourse)
311 JOIN {user} u ON (u.id = pue.userid AND u.deleted = 0)
312 LEFT JOIN {user_enrolments} ue ON (ue.enrolid = e.id AND ue.userid = pue.userid)
313 WHERE ue.id IS NULL
314 GROUP BY pue.userid, e.id";
315 $params['enrolstatus'] = ENROL_INSTANCE_ENABLED;
317 $rs = $DB->get_recordset_sql($sql, $params);
318 foreach($rs as $ue) {
319 if (!isset($instances[$ue->enrolid])) {
320 $instances[$ue->enrolid] = $DB->get_record('enrol', array('id'=>$ue->enrolid));
322 $instance = $instances[$ue->enrolid];
324 if (!$syncall) {
325 // this may be slow if very many users are ignored in sync
326 $parentcontext = context_course::instance($instance->customint1);
327 list($ignoreroles, $params) = $DB->get_in_or_equal($skiproles, SQL_PARAMS_NAMED, 'ri', false, -1);
328 $params['contextid'] = $parentcontext->id;
329 $params['userid'] = $ue->userid;
330 $select = "contextid = :contextid AND userid = :userid AND component <> 'enrol_meta' AND roleid $ignoreroles";
331 if (!$DB->record_exists_select('role_assignments', $select, $params)) {
332 // bad luck, this user does not have any role we want in parent course
333 if ($verbose) {
334 mtrace(" skipping enrolling: $ue->userid ==> $instance->courseid (user without role)");
336 continue;
340 // So now we have aggregated values that we will use for the meta enrolment status, timeend and timestart.
341 // Again, we use the fact that active=0 and disabled/suspended=1. Only when MIN(pue.status + pe.status)=0 the enrolment is active:
342 $ue->status = ($ue->status == ENROL_USER_ACTIVE + ENROL_INSTANCE_ENABLED) ? ENROL_USER_ACTIVE : ENROL_USER_SUSPENDED;
343 // Timeend 9999999999 was used instead of 0 in the "MAX()" function:
344 $ue->timeend = ($ue->timeend == SQL_INT_MAX) ? 0 : (int)$ue->timeend;
345 // Timestart 9999999999 is only possible when there are no active enrolments:
346 $ue->timestart = ($ue->timestart == SQL_INT_MAX) ? 0 : (int)$ue->timestart;
348 $meta->enrol_user($instance, $ue->userid, null, $ue->timestart, $ue->timeend, $ue->status);
349 if ($instance->customint2 && $group = $DB->get_record('groups', ['id' => $instance->customint2])) {
350 groups_add_member($group, $ue->userid, 'enrol_meta', $instance->id);
352 if ($verbose) {
353 mtrace(" enrolling: $ue->userid ==> $instance->courseid");
356 $rs->close();
359 // unenrol as necessary - ignore enabled flag, we want to get rid of existing enrols in any case
360 $onecourse = $courseid ? "AND e.courseid = :courseid" : "";
361 list($enabled, $params) = $DB->get_in_or_equal(explode(',', $CFG->enrol_plugins_enabled), SQL_PARAMS_NAMED, 'e');
362 $params['courseid'] = $courseid;
363 $sql = "SELECT ue.*
364 FROM {user_enrolments} ue
365 JOIN {enrol} e ON (e.id = ue.enrolid AND e.enrol = 'meta' $onecourse)
366 LEFT JOIN ({user_enrolments} xpue
367 JOIN {enrol} xpe ON (xpe.id = xpue.enrolid AND xpe.enrol <> 'meta' AND xpe.enrol $enabled)
368 ) ON (xpe.courseid = e.customint1 AND xpue.userid = ue.userid)
369 WHERE xpue.userid IS NULL";
370 $rs = $DB->get_recordset_sql($sql, $params);
371 foreach($rs as $ue) {
372 if (!isset($instances[$ue->enrolid])) {
373 $instances[$ue->enrolid] = $DB->get_record('enrol', array('id'=>$ue->enrolid));
375 $instance = $instances[$ue->enrolid];
377 if ($unenrolaction == ENROL_EXT_REMOVED_UNENROL) {
378 $meta->unenrol_user($instance, $ue->userid);
379 if ($verbose) {
380 mtrace(" unenrolling: $ue->userid ==> $instance->courseid");
383 } else if ($unenrolaction == ENROL_EXT_REMOVED_SUSPEND) {
384 if ($ue->status != ENROL_USER_SUSPENDED) {
385 $meta->update_user_enrol($instance, $ue->userid, ENROL_USER_SUSPENDED);
386 if ($verbose) {
387 mtrace(" suspending: $ue->userid ==> $instance->courseid");
391 } else if ($unenrolaction == ENROL_EXT_REMOVED_SUSPENDNOROLES) {
392 if ($ue->status != ENROL_USER_SUSPENDED) {
393 $meta->update_user_enrol($instance, $ue->userid, ENROL_USER_SUSPENDED);
394 $context = context_course::instance($instance->courseid);
395 role_unassign_all(array('userid'=>$ue->userid, 'contextid'=>$context->id, 'component'=>'enrol_meta', 'itemid'=>$instance->id));
396 if ($verbose) {
397 mtrace(" suspending and removing all roles: $ue->userid ==> $instance->courseid");
402 $rs->close();
405 // Update status - meta enrols are ignored to avoid recursion.
406 // Note the trick here is that the active enrolment and instance constants have value 0.
407 $onecourse = $courseid ? "AND e.courseid = :courseid" : "";
408 list($enabled, $params) = $DB->get_in_or_equal(explode(',', $CFG->enrol_plugins_enabled), SQL_PARAMS_NAMED, 'e');
409 $params['courseid'] = $courseid;
410 // The query builds a a list of all the non-meta enrolments that are on courses (the children) that are linked to by a meta
411 // enrolment, it then groups them by the course that linked to them (the parents).
413 // It will only return results where the there is a difference between the status of the parent and the lowest status
414 // of the children (remember that 0 is active, any other status is some form of inactive), or the time the earliest non-zero
415 // start time of a child is different to the parent, or the longest effective end date has changed.
417 // The last two case statements in the HAVING clause are designed to ignore any inactive child records when calculating
418 // the start and end time.
419 $sql = "SELECT ue.userid, ue.enrolid,
420 MIN(xpue.status + xpe.status) AS pstatus,
421 MIN(CASE WHEN (xpue.status + xpe.status = 0) THEN xpue.timestart ELSE " . SQL_INT_MAX . " END) AS ptimestart,
422 MAX(CASE WHEN (xpue.status + xpe.status = 0) THEN
423 (CASE WHEN xpue.timeend = 0 THEN " . SQL_INT_MAX . " ELSE xpue.timeend END)
424 ELSE 0 END) AS ptimeend
425 FROM {user_enrolments} ue
426 JOIN {enrol} e ON (e.id = ue.enrolid AND e.enrol = 'meta' $onecourse)
427 JOIN {user_enrolments} xpue ON (xpue.userid = ue.userid)
428 JOIN {enrol} xpe ON (xpe.id = xpue.enrolid AND xpe.enrol <> 'meta'
429 AND xpe.enrol $enabled AND xpe.courseid = e.customint1)
430 GROUP BY ue.userid, ue.enrolid
431 HAVING (MIN(xpue.status + xpe.status) = 0 AND MIN(ue.status) > 0)
432 OR (MIN(xpue.status + xpe.status) > 0 AND MIN(ue.status) = 0)
433 OR ((CASE WHEN
434 MIN(CASE WHEN (xpue.status + xpe.status = 0) THEN xpue.timestart ELSE " . SQL_INT_MAX . " END) = " .
435 SQL_INT_MAX . "
436 THEN 0
437 ELSE
438 MIN(CASE WHEN (xpue.status + xpe.status = 0) THEN xpue.timestart ELSE " . SQL_INT_MAX . " END)
439 END) <> MIN(ue.timestart))
440 OR ((CASE
441 WHEN MAX(CASE WHEN (xpue.status + xpe.status = 0)
442 THEN (CASE WHEN xpue.timeend = 0 THEN " . SQL_INT_MAX . " ELSE xpue.timeend END)
443 ELSE 0 END) = " . SQL_INT_MAX . "
444 THEN 0 ELSE MAX(CASE WHEN (xpue.status + xpe.status = 0)
445 THEN (CASE WHEN xpue.timeend = 0 THEN " . SQL_INT_MAX . " ELSE xpue.timeend END)
446 ELSE 0 END)
447 END) <> MAX(ue.timeend))";
448 $rs = $DB->get_recordset_sql($sql, $params);
449 foreach($rs as $ue) {
450 if (!isset($instances[$ue->enrolid])) {
451 $instances[$ue->enrolid] = $DB->get_record('enrol', array('id'=>$ue->enrolid));
453 $instance = $instances[$ue->enrolid];
454 $ue->pstatus = ($ue->pstatus == ENROL_USER_ACTIVE + ENROL_INSTANCE_ENABLED) ? ENROL_USER_ACTIVE : ENROL_USER_SUSPENDED;
455 $ue->ptimeend = ($ue->ptimeend == SQL_INT_MAX) ? 0 : (int)$ue->ptimeend;
456 $ue->ptimestart = ($ue->ptimestart == SQL_INT_MAX) ? 0 : (int)$ue->ptimestart;
458 if ($ue->pstatus == ENROL_USER_ACTIVE and (!$ue->ptimeend || $ue->ptimeend > time())
459 and !$syncall and $unenrolaction != ENROL_EXT_REMOVED_UNENROL) {
460 // this may be slow if very many users are ignored in sync
461 $parentcontext = context_course::instance($instance->customint1);
462 list($ignoreroles, $params) = $DB->get_in_or_equal($skiproles, SQL_PARAMS_NAMED, 'ri', false, -1);
463 $params['contextid'] = $parentcontext->id;
464 $params['userid'] = $ue->userid;
465 $select = "contextid = :contextid AND userid = :userid AND component <> 'enrol_meta' AND roleid $ignoreroles";
466 if (!$DB->record_exists_select('role_assignments', $select, $params)) {
467 // bad luck, this user does not have any role we want in parent course
468 if ($verbose) {
469 mtrace(" skipping unsuspending: $ue->userid ==> $instance->courseid (user without role)");
471 continue;
475 $meta->update_user_enrol($instance, $ue->userid, $ue->pstatus, $ue->ptimestart, $ue->ptimeend);
476 if ($verbose) {
477 if ($ue->pstatus == ENROL_USER_ACTIVE) {
478 mtrace(" unsuspending: $ue->userid ==> $instance->courseid");
479 } else {
480 mtrace(" suspending: $ue->userid ==> $instance->courseid");
484 $rs->close();
487 // now assign all necessary roles
488 $enabled = explode(',', $CFG->enrol_plugins_enabled);
489 foreach($enabled as $k=>$v) {
490 if ($v === 'meta') {
491 continue; // no meta sync of meta roles
493 $enabled[$k] = 'enrol_'.$v;
495 $enabled[] = ''; // manual assignments are replicated too
497 $onecourse = $courseid ? "AND e.courseid = :courseid" : "";
498 list($enabled, $params) = $DB->get_in_or_equal($enabled, SQL_PARAMS_NAMED, 'e');
499 $params['coursecontext'] = CONTEXT_COURSE;
500 $params['courseid'] = $courseid;
501 $params['activeuser'] = ENROL_USER_ACTIVE;
502 $params['enabledinstance'] = ENROL_INSTANCE_ENABLED;
503 $sql = "SELECT DISTINCT pra.roleid, pra.userid, c.id AS contextid, e.id AS enrolid, e.courseid
504 FROM {role_assignments} pra
505 JOIN {user} u ON (u.id = pra.userid AND u.deleted = 0)
506 JOIN {context} pc ON (pc.id = pra.contextid AND pc.contextlevel = :coursecontext AND pra.component $enabled)
507 JOIN {enrol} e ON (e.customint1 = pc.instanceid AND e.enrol = 'meta' $onecourse AND e.status = :enabledinstance)
508 JOIN {user_enrolments} ue ON (ue.enrolid = e.id AND ue.userid = u.id AND ue.status = :activeuser)
509 JOIN {context} c ON (c.contextlevel = pc.contextlevel AND c.instanceid = e.courseid)
510 LEFT JOIN {role_assignments} ra ON (ra.contextid = c.id AND ra.userid = pra.userid AND ra.roleid = pra.roleid AND ra.itemid = e.id AND ra.component = 'enrol_meta')
511 WHERE ra.id IS NULL";
513 if ($ignored = $meta->get_config('nosyncroleids')) {
514 list($notignored, $xparams) = $DB->get_in_or_equal(explode(',', $ignored), SQL_PARAMS_NAMED, 'ig', false);
515 $params = array_merge($params, $xparams);
516 $sql = "$sql AND pra.roleid $notignored";
519 $rs = $DB->get_recordset_sql($sql, $params);
520 foreach($rs as $ra) {
521 role_assign($ra->roleid, $ra->userid, $ra->contextid, 'enrol_meta', $ra->enrolid);
522 if ($verbose) {
523 mtrace(" assigning role: $ra->userid ==> $ra->courseid as ".$allroles[$ra->roleid]->shortname);
526 $rs->close();
529 // remove unwanted roles - include ignored roles and disabled plugins too
530 $onecourse = $courseid ? "AND e.courseid = :courseid" : "";
531 $params = array();
532 $params['coursecontext'] = CONTEXT_COURSE;
533 $params['courseid'] = $courseid;
534 $params['activeuser'] = ENROL_USER_ACTIVE;
535 $params['enabledinstance'] = ENROL_INSTANCE_ENABLED;
536 if ($ignored = $meta->get_config('nosyncroleids')) {
537 list($notignored, $xparams) = $DB->get_in_or_equal(explode(',', $ignored), SQL_PARAMS_NAMED, 'ig', false);
538 $params = array_merge($params, $xparams);
539 $notignored = "AND pra.roleid $notignored";
540 } else {
541 $notignored = "";
544 $sql = "SELECT ra.roleid, ra.userid, ra.contextid, ra.itemid, e.courseid
545 FROM {role_assignments} ra
546 JOIN {enrol} e ON (e.id = ra.itemid AND ra.component = 'enrol_meta' AND e.enrol = 'meta' $onecourse)
547 JOIN {context} pc ON (pc.instanceid = e.customint1 AND pc.contextlevel = :coursecontext)
548 LEFT JOIN {role_assignments} pra ON (pra.contextid = pc.id AND pra.userid = ra.userid AND pra.roleid = ra.roleid AND pra.component <> 'enrol_meta' $notignored)
549 LEFT JOIN {user_enrolments} ue ON (ue.enrolid = e.id AND ue.userid = ra.userid AND ue.status = :activeuser)
550 WHERE pra.id IS NULL OR ue.id IS NULL OR e.status <> :enabledinstance";
552 if ($unenrolaction != ENROL_EXT_REMOVED_SUSPEND) {
553 $rs = $DB->get_recordset_sql($sql, $params);
554 foreach($rs as $ra) {
555 role_unassign($ra->roleid, $ra->userid, $ra->contextid, 'enrol_meta', $ra->itemid);
556 if ($verbose) {
557 mtrace(" unassigning role: $ra->userid ==> $ra->courseid as ".$allroles[$ra->roleid]->shortname);
560 $rs->close();
564 // kick out or suspend users without synced roles if syncall disabled
565 if (!$syncall) {
566 if ($unenrolaction == ENROL_EXT_REMOVED_UNENROL) {
567 $onecourse = $courseid ? "AND e.courseid = :courseid" : "";
568 $params = array();
569 $params['coursecontext'] = CONTEXT_COURSE;
570 $params['courseid'] = $courseid;
571 $sql = "SELECT ue.userid, ue.enrolid
572 FROM {user_enrolments} ue
573 JOIN {enrol} e ON (e.id = ue.enrolid AND e.enrol = 'meta' $onecourse)
574 JOIN {context} c ON (e.courseid = c.instanceid AND c.contextlevel = :coursecontext)
575 LEFT JOIN {role_assignments} ra ON (ra.contextid = c.id AND ra.itemid = e.id AND ra.userid = ue.userid)
576 WHERE ra.id IS NULL";
577 $ues = $DB->get_recordset_sql($sql, $params);
578 foreach($ues as $ue) {
579 if (!isset($instances[$ue->enrolid])) {
580 $instances[$ue->enrolid] = $DB->get_record('enrol', array('id'=>$ue->enrolid));
582 $instance = $instances[$ue->enrolid];
583 $meta->unenrol_user($instance, $ue->userid);
584 if ($verbose) {
585 mtrace(" unenrolling: $ue->userid ==> $instance->courseid (user without role)");
588 $ues->close();
590 } else {
591 // just suspend the users
592 $onecourse = $courseid ? "AND e.courseid = :courseid" : "";
593 $params = array();
594 $params['coursecontext'] = CONTEXT_COURSE;
595 $params['courseid'] = $courseid;
596 $params['active'] = ENROL_USER_ACTIVE;
597 $sql = "SELECT ue.userid, ue.enrolid
598 FROM {user_enrolments} ue
599 JOIN {enrol} e ON (e.id = ue.enrolid AND e.enrol = 'meta' $onecourse)
600 JOIN {context} c ON (e.courseid = c.instanceid AND c.contextlevel = :coursecontext)
601 LEFT JOIN {role_assignments} ra ON (ra.contextid = c.id AND ra.itemid = e.id AND ra.userid = ue.userid)
602 WHERE ra.id IS NULL AND ue.status = :active";
603 $ues = $DB->get_recordset_sql($sql, $params);
604 foreach($ues as $ue) {
605 if (!isset($instances[$ue->enrolid])) {
606 $instances[$ue->enrolid] = $DB->get_record('enrol', array('id'=>$ue->enrolid));
608 $instance = $instances[$ue->enrolid];
609 $meta->update_user_enrol($instance, $ue->userid, ENROL_USER_SUSPENDED);
610 if ($verbose) {
611 mtrace(" suspending: $ue->userid ==> $instance->courseid (user without role)");
614 $ues->close();
618 // Finally sync groups.
619 $affectedusers = groups_sync_with_enrolment('meta', $courseid);
620 if ($verbose) {
621 foreach ($affectedusers['removed'] as $gm) {
622 mtrace("removing user from group: $gm->userid ==> $gm->courseid - $gm->groupname", 1);
624 foreach ($affectedusers['added'] as $ue) {
625 mtrace("adding user to group: $ue->userid ==> $ue->courseid - $ue->groupname", 1);
629 if ($verbose) {
630 mtrace('...user enrolment synchronisation finished.');
633 return 0;
637 * Create a new group with the course's name.
639 * @param int $courseid
640 * @param int $linkedcourseid
641 * @return int $groupid Group ID for this cohort.
643 function enrol_meta_create_new_group($courseid, $linkedcourseid) {
644 global $DB, $CFG;
646 require_once($CFG->dirroot.'/group/lib.php');
648 $coursename = $DB->get_field('course', 'fullname', array('id' => $linkedcourseid), MUST_EXIST);
649 $a = new stdClass();
650 $a->name = $coursename;
651 $a->increment = '';
652 $inc = 1;
653 $groupname = trim(get_string('defaultgroupnametext', 'enrol_meta', $a));
654 // Check to see if the group name already exists in this course. Add an incremented number if it does.
655 while ($DB->record_exists('groups', array('name' => $groupname, 'courseid' => $courseid))) {
656 $a->increment = '(' . (++$inc) . ')';
657 $groupname = trim(get_string('defaultgroupnametext', 'enrol_meta', $a));
659 // Create a new group for the course meta sync.
660 $groupdata = new stdClass();
661 $groupdata->courseid = $courseid;
662 $groupdata->name = $groupname;
663 $groupid = groups_create_group($groupdata);
665 return $groupid;