Merge branch 'MDL-65584-37' of https://github.com/paulholden/moodle into MOODLE_37_STABLE
[moodle.git] / enrol / meta / locallib.php
blob7dd4a92dd2d5037df0643d048134c9a5934b1350
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) {
177 groups_add_member($instance->customint2, $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 9999999999 END) AS timestart,
305 MAX(CASE WHEN (pue.status + pe.status = 0) THEN
306 (CASE WHEN pue.timeend = 0 THEN 9999999999 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 == 9999999999) ? 0 : (int)$ue->timeend;
345 // Timestart 9999999999 is only possible when there are no active enrolments:
346 $ue->timestart = ($ue->timestart == 9999999999) ? 0 : (int)$ue->timestart;
348 $meta->enrol_user($instance, $ue->userid, null, $ue->timestart, $ue->timeend, $ue->status);
349 if ($instance->customint2) {
350 groups_add_member($instance->customint2, $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 9999999999 END) AS ptimestart,
422 MAX(CASE WHEN (xpue.status + xpe.status = 0) THEN
423 (CASE WHEN xpue.timeend = 0 THEN 9999999999 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 9999999999 END) = 9999999999
435 THEN 0
436 ELSE
437 MIN(CASE WHEN (xpue.status + xpe.status = 0) THEN xpue.timestart ELSE 9999999999 END)
438 END) <> MIN(ue.timestart))
439 OR ((CASE
440 WHEN MAX(CASE WHEN (xpue.status + xpe.status = 0)
441 THEN (CASE WHEN xpue.timeend = 0 THEN 9999999999 ELSE xpue.timeend END)
442 ELSE 0 END) = 9999999999
443 THEN 0 ELSE MAX(CASE WHEN (xpue.status + xpe.status = 0)
444 THEN (CASE WHEN xpue.timeend = 0 THEN 9999999999 ELSE xpue.timeend END)
445 ELSE 0 END)
446 END) <> MAX(ue.timeend))";
447 $rs = $DB->get_recordset_sql($sql, $params);
448 foreach($rs as $ue) {
449 if (!isset($instances[$ue->enrolid])) {
450 $instances[$ue->enrolid] = $DB->get_record('enrol', array('id'=>$ue->enrolid));
452 $instance = $instances[$ue->enrolid];
453 $ue->pstatus = ($ue->pstatus == ENROL_USER_ACTIVE + ENROL_INSTANCE_ENABLED) ? ENROL_USER_ACTIVE : ENROL_USER_SUSPENDED;
454 $ue->ptimeend = ($ue->ptimeend == 9999999999) ? 0 : (int)$ue->ptimeend;
455 $ue->ptimestart = ($ue->ptimestart == 9999999999) ? 0 : (int)$ue->ptimestart;
457 if ($ue->pstatus == ENROL_USER_ACTIVE and (!$ue->ptimeend || $ue->ptimeend > time())
458 and !$syncall and $unenrolaction != ENROL_EXT_REMOVED_UNENROL) {
459 // this may be slow if very many users are ignored in sync
460 $parentcontext = context_course::instance($instance->customint1);
461 list($ignoreroles, $params) = $DB->get_in_or_equal($skiproles, SQL_PARAMS_NAMED, 'ri', false, -1);
462 $params['contextid'] = $parentcontext->id;
463 $params['userid'] = $ue->userid;
464 $select = "contextid = :contextid AND userid = :userid AND component <> 'enrol_meta' AND roleid $ignoreroles";
465 if (!$DB->record_exists_select('role_assignments', $select, $params)) {
466 // bad luck, this user does not have any role we want in parent course
467 if ($verbose) {
468 mtrace(" skipping unsuspending: $ue->userid ==> $instance->courseid (user without role)");
470 continue;
474 $meta->update_user_enrol($instance, $ue->userid, $ue->pstatus, $ue->ptimestart, $ue->ptimeend);
475 if ($verbose) {
476 if ($ue->pstatus == ENROL_USER_ACTIVE) {
477 mtrace(" unsuspending: $ue->userid ==> $instance->courseid");
478 } else {
479 mtrace(" suspending: $ue->userid ==> $instance->courseid");
483 $rs->close();
486 // now assign all necessary roles
487 $enabled = explode(',', $CFG->enrol_plugins_enabled);
488 foreach($enabled as $k=>$v) {
489 if ($v === 'meta') {
490 continue; // no meta sync of meta roles
492 $enabled[$k] = 'enrol_'.$v;
494 $enabled[] = ''; // manual assignments are replicated too
496 $onecourse = $courseid ? "AND e.courseid = :courseid" : "";
497 list($enabled, $params) = $DB->get_in_or_equal($enabled, SQL_PARAMS_NAMED, 'e');
498 $params['coursecontext'] = CONTEXT_COURSE;
499 $params['courseid'] = $courseid;
500 $params['activeuser'] = ENROL_USER_ACTIVE;
501 $params['enabledinstance'] = ENROL_INSTANCE_ENABLED;
502 $sql = "SELECT DISTINCT pra.roleid, pra.userid, c.id AS contextid, e.id AS enrolid, e.courseid
503 FROM {role_assignments} pra
504 JOIN {user} u ON (u.id = pra.userid AND u.deleted = 0)
505 JOIN {context} pc ON (pc.id = pra.contextid AND pc.contextlevel = :coursecontext AND pra.component $enabled)
506 JOIN {enrol} e ON (e.customint1 = pc.instanceid AND e.enrol = 'meta' $onecourse AND e.status = :enabledinstance)
507 JOIN {user_enrolments} ue ON (ue.enrolid = e.id AND ue.userid = u.id AND ue.status = :activeuser)
508 JOIN {context} c ON (c.contextlevel = pc.contextlevel AND c.instanceid = e.courseid)
509 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')
510 WHERE ra.id IS NULL";
512 if ($ignored = $meta->get_config('nosyncroleids')) {
513 list($notignored, $xparams) = $DB->get_in_or_equal(explode(',', $ignored), SQL_PARAMS_NAMED, 'ig', false);
514 $params = array_merge($params, $xparams);
515 $sql = "$sql AND pra.roleid $notignored";
518 $rs = $DB->get_recordset_sql($sql, $params);
519 foreach($rs as $ra) {
520 role_assign($ra->roleid, $ra->userid, $ra->contextid, 'enrol_meta', $ra->enrolid);
521 if ($verbose) {
522 mtrace(" assigning role: $ra->userid ==> $ra->courseid as ".$allroles[$ra->roleid]->shortname);
525 $rs->close();
528 // remove unwanted roles - include ignored roles and disabled plugins too
529 $onecourse = $courseid ? "AND e.courseid = :courseid" : "";
530 $params = array();
531 $params['coursecontext'] = CONTEXT_COURSE;
532 $params['courseid'] = $courseid;
533 $params['activeuser'] = ENROL_USER_ACTIVE;
534 $params['enabledinstance'] = ENROL_INSTANCE_ENABLED;
535 if ($ignored = $meta->get_config('nosyncroleids')) {
536 list($notignored, $xparams) = $DB->get_in_or_equal(explode(',', $ignored), SQL_PARAMS_NAMED, 'ig', false);
537 $params = array_merge($params, $xparams);
538 $notignored = "AND pra.roleid $notignored";
539 } else {
540 $notignored = "";
543 $sql = "SELECT ra.roleid, ra.userid, ra.contextid, ra.itemid, e.courseid
544 FROM {role_assignments} ra
545 JOIN {enrol} e ON (e.id = ra.itemid AND ra.component = 'enrol_meta' AND e.enrol = 'meta' $onecourse)
546 JOIN {context} pc ON (pc.instanceid = e.customint1 AND pc.contextlevel = :coursecontext)
547 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)
548 LEFT JOIN {user_enrolments} ue ON (ue.enrolid = e.id AND ue.userid = ra.userid AND ue.status = :activeuser)
549 WHERE pra.id IS NULL OR ue.id IS NULL OR e.status <> :enabledinstance";
551 if ($unenrolaction != ENROL_EXT_REMOVED_SUSPEND) {
552 $rs = $DB->get_recordset_sql($sql, $params);
553 foreach($rs as $ra) {
554 role_unassign($ra->roleid, $ra->userid, $ra->contextid, 'enrol_meta', $ra->itemid);
555 if ($verbose) {
556 mtrace(" unassigning role: $ra->userid ==> $ra->courseid as ".$allroles[$ra->roleid]->shortname);
559 $rs->close();
563 // kick out or suspend users without synced roles if syncall disabled
564 if (!$syncall) {
565 if ($unenrolaction == ENROL_EXT_REMOVED_UNENROL) {
566 $onecourse = $courseid ? "AND e.courseid = :courseid" : "";
567 $params = array();
568 $params['coursecontext'] = CONTEXT_COURSE;
569 $params['courseid'] = $courseid;
570 $sql = "SELECT ue.userid, ue.enrolid
571 FROM {user_enrolments} ue
572 JOIN {enrol} e ON (e.id = ue.enrolid AND e.enrol = 'meta' $onecourse)
573 JOIN {context} c ON (e.courseid = c.instanceid AND c.contextlevel = :coursecontext)
574 LEFT JOIN {role_assignments} ra ON (ra.contextid = c.id AND ra.itemid = e.id AND ra.userid = ue.userid)
575 WHERE ra.id IS NULL";
576 $ues = $DB->get_recordset_sql($sql, $params);
577 foreach($ues as $ue) {
578 if (!isset($instances[$ue->enrolid])) {
579 $instances[$ue->enrolid] = $DB->get_record('enrol', array('id'=>$ue->enrolid));
581 $instance = $instances[$ue->enrolid];
582 $meta->unenrol_user($instance, $ue->userid);
583 if ($verbose) {
584 mtrace(" unenrolling: $ue->userid ==> $instance->courseid (user without role)");
587 $ues->close();
589 } else {
590 // just suspend the users
591 $onecourse = $courseid ? "AND e.courseid = :courseid" : "";
592 $params = array();
593 $params['coursecontext'] = CONTEXT_COURSE;
594 $params['courseid'] = $courseid;
595 $params['active'] = ENROL_USER_ACTIVE;
596 $sql = "SELECT ue.userid, ue.enrolid
597 FROM {user_enrolments} ue
598 JOIN {enrol} e ON (e.id = ue.enrolid AND e.enrol = 'meta' $onecourse)
599 JOIN {context} c ON (e.courseid = c.instanceid AND c.contextlevel = :coursecontext)
600 LEFT JOIN {role_assignments} ra ON (ra.contextid = c.id AND ra.itemid = e.id AND ra.userid = ue.userid)
601 WHERE ra.id IS NULL AND ue.status = :active";
602 $ues = $DB->get_recordset_sql($sql, $params);
603 foreach($ues as $ue) {
604 if (!isset($instances[$ue->enrolid])) {
605 $instances[$ue->enrolid] = $DB->get_record('enrol', array('id'=>$ue->enrolid));
607 $instance = $instances[$ue->enrolid];
608 $meta->update_user_enrol($instance, $ue->userid, ENROL_USER_SUSPENDED);
609 if ($verbose) {
610 mtrace(" suspending: $ue->userid ==> $instance->courseid (user without role)");
613 $ues->close();
617 // Finally sync groups.
618 $affectedusers = groups_sync_with_enrolment('meta', $courseid);
619 if ($verbose) {
620 foreach ($affectedusers['removed'] as $gm) {
621 mtrace("removing user from group: $gm->userid ==> $gm->courseid - $gm->groupname", 1);
623 foreach ($affectedusers['added'] as $ue) {
624 mtrace("adding user to group: $ue->userid ==> $ue->courseid - $ue->groupname", 1);
628 if ($verbose) {
629 mtrace('...user enrolment synchronisation finished.');
632 return 0;
636 * Create a new group with the course's name.
638 * @param int $courseid
639 * @param int $linkedcourseid
640 * @return int $groupid Group ID for this cohort.
642 function enrol_meta_create_new_group($courseid, $linkedcourseid) {
643 global $DB, $CFG;
645 require_once($CFG->dirroot.'/group/lib.php');
647 $coursename = $DB->get_field('course', 'fullname', array('id' => $linkedcourseid), MUST_EXIST);
648 $a = new stdClass();
649 $a->name = $coursename;
650 $a->increment = '';
651 $inc = 1;
652 $groupname = trim(get_string('defaultgroupnametext', 'enrol_meta', $a));
653 // Check to see if the group name already exists in this course. Add an incremented number if it does.
654 while ($DB->record_exists('groups', array('name' => $groupname, 'courseid' => $courseid))) {
655 $a->increment = '(' . (++$inc) . ')';
656 $groupname = trim(get_string('defaultgroupnametext', 'enrol_meta', $a));
658 // Create a new group for the course meta sync.
659 $groupdata = new stdClass();
660 $groupdata->courseid = $courseid;
661 $groupdata->name = $groupname;
662 $groupid = groups_create_group($groupdata);
664 return $groupid;