Commit | Line | Data |
---|---|---|
df997f84 PS |
1 | <?php |
2 | ||
3 | // This file is part of Moodle - http://moodle.org/ | |
4 | // | |
5 | // Moodle is free software: you can redistribute it and/or modify | |
6 | // it under the terms of the GNU General Public License as published by | |
7 | // the Free Software Foundation, either version 3 of the License, or | |
8 | // (at your option) any later version. | |
9 | // | |
10 | // Moodle is distributed in the hope that it will be useful, | |
11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of | |
12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
13 | // GNU General Public License for more details. | |
14 | // | |
15 | // You should have received a copy of the GNU General Public License | |
16 | // along with Moodle. If not, see <http://www.gnu.org/licenses/>. | |
17 | ||
18 | /** | |
19 | * Local stuff for category enrolment plugin. | |
20 | * | |
fe441933 PS |
21 | * @package enrol |
22 | * @subpackage category | |
23 | * @copyright 2010 Petr Skoda {@link http://skodak.org} | |
24 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later | |
df997f84 PS |
25 | */ |
26 | ||
97795859 | 27 | defined('MOODLE_INTERNAL') || die(); |
df997f84 PS |
28 | |
29 | /** | |
30 | * Event handler for category enrolment plugin. | |
31 | * | |
32 | * We try to keep everything in sync via listening to events, | |
33 | * it may fail sometimes, so we always do a full sync in cron too. | |
34 | */ | |
35 | class enrol_category_handler { | |
e807c0be | 36 | public static function role_assigned($ra) { |
df997f84 PS |
37 | global $DB; |
38 | ||
39 | if (!enrol_is_enabled('category')) { | |
40 | return true; | |
41 | } | |
42 | ||
43 | //only category level roles are interesting | |
44 | $parentcontext = get_context_instance_by_id($ra->contextid); | |
45 | if ($parentcontext->contextlevel != CONTEXT_COURSECAT) { | |
46 | return true; | |
47 | } | |
48 | ||
49 | // make sure the role is to be actually synchronised | |
50 | // please note we are ignoring overrides of the synchronised capability (for performance reasons in full sync) | |
51 | $syscontext = get_context_instance(CONTEXT_SYSTEM); | |
52 | if (!$DB->record_exists('role_capabilities', array('contextid'=>$syscontext->id, 'roleid'=>$ra->roleid, 'capability'=>'enrol/category:synchronised', 'permission'=>CAP_ALLOW))) { | |
53 | return true; | |
54 | } | |
55 | ||
56 | // add necessary enrol instances | |
57 | $plugin = enrol_get_plugin('category'); | |
58 | $sql = "SELECT c.* | |
59 | FROM {course} c | |
60 | JOIN {context} ctx ON (ctx.instanceid = c.id AND ctx.contextlevel = :courselevel AND ctx.path LIKE :match) | |
61 | LEFT JOIN {enrol} e ON (e.courseid = c.id AND e.enrol = 'category') | |
62 | WHERE e.id IS NULL"; | |
63 | $params = array('courselevel'=>CONTEXT_COURSE, 'match'=>$parentcontext->path.'/%'); | |
64 | $rs = $DB->get_recordset_sql($sql, $params); | |
65 | foreach ($rs as $course) { | |
66 | $plugin->add_instance($course); | |
67 | } | |
68 | $rs->close(); | |
69 | ||
70 | // now look for missing enrols | |
71 | $sql = "SELECT e.* | |
72 | FROM {course} c | |
73 | JOIN {context} ctx ON (ctx.instanceid = c.id AND ctx.contextlevel = :courselevel AND ctx.path LIKE :match) | |
74 | JOIN {enrol} e ON (e.courseid = c.id AND e.enrol = 'category') | |
75 | LEFT JOIN {user_enrolments} ue ON (ue.enrolid = e.id AND ue.userid = :userid) | |
76 | WHERE ue.id IS NULL"; | |
77 | $params = array('courselevel'=>CONTEXT_COURSE, 'match'=>$parentcontext->path.'/%', 'userid'=>$ra->userid); | |
78 | $rs = $DB->get_recordset_sql($sql, $params); | |
79 | foreach ($rs as $instance) { | |
2a6dcb72 | 80 | $plugin->enrol_user($instance, $ra->userid, null, $ra->timemodified); |
df997f84 PS |
81 | } |
82 | $rs->close(); | |
83 | ||
84 | return true; | |
85 | } | |
86 | ||
e807c0be | 87 | public static function role_unassigned($ra) { |
df997f84 PS |
88 | global $DB; |
89 | ||
90 | if (!enrol_is_enabled('category')) { | |
91 | return true; | |
92 | } | |
93 | ||
94 | // only category level roles are interesting | |
95 | $parentcontext = get_context_instance_by_id($ra->contextid); | |
96 | if ($parentcontext->contextlevel != CONTEXT_COURSECAT) { | |
97 | return true; | |
98 | } | |
99 | ||
100 | // now this is going to be a bit slow, take all enrolments in child courses and verify each separately | |
101 | $syscontext = get_context_instance(CONTEXT_SYSTEM); | |
c2d6b4f9 PS |
102 | if (!$roles = get_roles_with_capability('enrol/category:synchronised', CAP_ALLOW, $syscontext)) { |
103 | return true; | |
104 | } | |
df997f84 PS |
105 | |
106 | $plugin = enrol_get_plugin('category'); | |
107 | ||
108 | $sql = "SELECT e.* | |
109 | FROM {course} c | |
110 | JOIN {context} ctx ON (ctx.instanceid = c.id AND ctx.contextlevel = :courselevel AND ctx.path LIKE :match) | |
111 | JOIN {enrol} e ON (e.courseid = c.id AND e.enrol = 'category') | |
112 | JOIN {user_enrolments} ue ON (ue.enrolid = e.id AND ue.userid = :userid)"; | |
113 | $params = array('courselevel'=>CONTEXT_COURSE, 'match'=>$parentcontext->path.'/%', 'userid'=>$ra->userid); | |
114 | $rs = $DB->get_recordset_sql($sql, $params); | |
115 | ||
cf717dc2 | 116 | list($roleids, $params) = $DB->get_in_or_equal(array_keys($roles), SQL_PARAMS_NAMED, 'r'); |
df997f84 PS |
117 | $params['userid'] = $ra->userid; |
118 | ||
119 | foreach ($rs as $instance) { | |
120 | $coursecontext = get_context_instance(CONTEXT_COURSE, $instance->courseid); | |
121 | $contextids = get_parent_contexts($coursecontext); | |
122 | array_pop($contextids); // remove system context, we are interested in categories only | |
123 | ||
cf717dc2 | 124 | list($contextids, $contextparams) = $DB->get_in_or_equal($contextids, SQL_PARAMS_NAMED, 'c'); |
df997f84 PS |
125 | $params = array_merge($params, $contextparams); |
126 | ||
127 | $sql = "SELECT ra.id | |
128 | FROM {role_assignments} ra | |
129 | WHERE ra.userid = :userid AND ra.contextid $contextids AND ra.roleid $roleids"; | |
130 | if (!$DB->record_exists_sql($sql, $params)) { | |
131 | // user does not have any interesting role in any parent context, let's unenrol | |
132 | $plugin->unenrol_user($instance, $ra->userid); | |
133 | } | |
134 | } | |
135 | $rs->close(); | |
136 | ||
137 | return true; | |
138 | } | |
139 | } | |
140 | ||
141 | /** | |
142 | * Sync all category enrolments in one course | |
143 | * @param int $courseid course id | |
144 | * @return void | |
145 | */ | |
146 | function enrol_category_sync_course($course) { | |
147 | global $DB; | |
148 | ||
149 | if (!enrol_is_enabled('category')) { | |
150 | return; | |
151 | } | |
152 | ||
153 | $plugin = enrol_get_plugin('category'); | |
154 | ||
155 | $syscontext = get_context_instance(CONTEXT_SYSTEM); | |
156 | $roles = get_roles_with_capability('enrol/category:synchronised', CAP_ALLOW, $syscontext); | |
157 | ||
ff3af0a5 PS |
158 | if (!$roles) { |
159 | //nothing to sync, so remove the instance completely if exists | |
160 | if ($instances = $DB->get_records('enrol', array('courseid'=>$course->id, 'enrol'=>'category'))) { | |
161 | foreach ($instances as $instance) { | |
162 | $plugin->delete_instance($instance); | |
163 | } | |
164 | } | |
165 | return; | |
166 | } | |
167 | ||
df997f84 PS |
168 | // first find out if any parent category context contains interesting role assignments |
169 | $coursecontext = get_context_instance(CONTEXT_COURSE, $course->id); | |
170 | $contextids = get_parent_contexts($coursecontext); | |
171 | array_pop($contextids); // remove system context, we are interested in categories only | |
172 | ||
cf717dc2 PS |
173 | list($roleids, $params) = $DB->get_in_or_equal(array_keys($roles), SQL_PARAMS_NAMED, 'r'); |
174 | list($contextids, $contextparams) = $DB->get_in_or_equal($contextids, SQL_PARAMS_NAMED, 'c'); | |
df997f84 PS |
175 | $params = array_merge($params, $contextparams); |
176 | $params['courseid'] = $course->id; | |
177 | ||
178 | $sql = "SELECT 'x' | |
179 | FROM {role_assignments} | |
180 | WHERE roleid $roleids AND contextid $contextids"; | |
181 | if (!$DB->record_exists_sql($sql, $params)) { | |
182 | if ($instances = $DB->get_records('enrol', array('courseid'=>$course->id, 'enrol'=>'category'))) { | |
183 | // should be max one instance, but anyway | |
184 | foreach ($instances as $instance) { | |
185 | $plugin->delete_instance($instance); | |
186 | } | |
187 | } | |
188 | return; | |
189 | } | |
190 | ||
191 | // make sure the enrol instance exists - there should be always only one instance | |
192 | $delinstances = array(); | |
193 | if ($instances = $DB->get_records('enrol', array('courseid'=>$course->id, 'enrol'=>'category'))) { | |
194 | $instance = array_shift($instances); | |
195 | $delinstances = $instances; | |
196 | } else { | |
197 | $i = $plugin->add_instance($course); | |
198 | $instance = $DB->get_record('enrol', array('id'=>$i)); | |
199 | } | |
200 | ||
201 | // add new enrolments | |
2a6dcb72 PS |
202 | $sql = "SELECT ra.userid, ra.estart |
203 | FROM (SELECT xra.userid, MIN(xra.timemodified) AS estart | |
df997f84 PS |
204 | FROM {role_assignments} xra |
205 | WHERE xra.roleid $roleids AND xra.contextid $contextids | |
2a6dcb72 | 206 | GROUP BY xra.userid |
df997f84 PS |
207 | ) ra |
208 | LEFT JOIN {user_enrolments} ue ON (ue.enrolid = :instanceid AND ue.userid = ra.userid) | |
209 | WHERE ue.id IS NULL"; | |
210 | $params['instanceid'] = $instance->id; | |
211 | $rs = $DB->get_recordset_sql($sql, $params); | |
212 | foreach ($rs as $ra) { | |
2a6dcb72 | 213 | $plugin->enrol_user($instance, $ra->userid, null, $ra->estart); |
df997f84 PS |
214 | } |
215 | $rs->close(); | |
216 | ||
217 | // remove unwanted enrolments | |
218 | $sql = "SELECT DISTINCT ue.userid | |
219 | FROM {user_enrolments} ue | |
220 | LEFT JOIN {role_assignments} ra ON (ra.roleid $roleids AND ra.contextid $contextids AND ra.userid = ue.userid) | |
221 | WHERE ue.enrolid = :instanceid AND ra.id IS NULL"; | |
222 | $rs = $DB->get_recordset_sql($sql, $params); | |
223 | foreach ($rs as $ra) { | |
224 | $plugin->unenrol_user($instance, $ra->userid); | |
225 | } | |
226 | $rs->close(); | |
227 | ||
228 | if ($delinstances) { | |
229 | // we have to do this as the last step in order to prevent temporary unenrolment | |
230 | foreach ($delinstances as $delinstance) { | |
231 | $plugin->delete_instance($delinstance); | |
232 | } | |
233 | } | |
234 | } | |
235 | ||
a2043a31 | 236 | function enrol_category_sync_full($verbose = false) { |
df997f84 PS |
237 | global $DB; |
238 | ||
239 | ||
240 | if (!enrol_is_enabled('category')) { | |
a2043a31 | 241 | return 2; |
df997f84 PS |
242 | } |
243 | ||
244 | // we may need a lot of time here | |
245 | @set_time_limit(0); | |
246 | ||
247 | $plugin = enrol_get_plugin('category'); | |
248 | ||
249 | $syscontext = get_context_instance(CONTEXT_SYSTEM); | |
250 | ||
251 | // any interesting roles worth synchronising? | |
252 | if (!$roles = get_roles_with_capability('enrol/category:synchronised', CAP_ALLOW, $syscontext)) { | |
253 | // yay, nothing to do, so let's remove all leftovers | |
a2043a31 PS |
254 | if ($verbose) { |
255 | mtrace("No roles with 'enrol/category:synchronised' capability found."); | |
256 | } | |
df997f84 | 257 | if ($instances = $DB->get_records('enrol', array('enrol'=>'category'))) { |
ac86edd0 | 258 | foreach ($instances as $instance) { |
a2043a31 PS |
259 | if ($verbose) { |
260 | mtrace(" deleting category enrol instance from course {$instance->courseid}"); | |
261 | } | |
ac86edd0 PS |
262 | $plugin->delete_instance($instance); |
263 | } | |
df997f84 | 264 | } |
a2043a31 PS |
265 | return 0; |
266 | } | |
267 | $rolenames = role_fix_names($roles, null, ROLENAME_SHORT, true); | |
268 | if ($verbose) { | |
269 | mtrace('Synchronising category enrolments for roles: '.implode(', ', $rolenames).'...'); | |
df997f84 PS |
270 | } |
271 | ||
cf717dc2 | 272 | list($roleids, $params) = $DB->get_in_or_equal(array_keys($roles), SQL_PARAMS_NAMED, 'r'); |
df997f84 PS |
273 | $params['courselevel'] = CONTEXT_COURSE; |
274 | $params['catlevel'] = CONTEXT_COURSECAT; | |
275 | ||
034ef761 | 276 | // first of all add necessary enrol instances to all courses |
df997f84 | 277 | $parentcat = $DB->sql_concat("cat.path", "'/%'"); |
3d7f23c5 | 278 | $parentcctx = $DB->sql_concat("cctx.path", "'/%'"); |
f320cb76 EL |
279 | // need whole course records to be used by add_instance(), use inner view (ci) to |
280 | // get distinct records only. | |
281 | // TODO: Moodle 2.1. Improve enrol API to accept courseid / courserec | |
282 | $sql = "SELECT c.* | |
df997f84 | 283 | FROM {course} c |
f320cb76 EL |
284 | JOIN ( |
285 | SELECT DISTINCT c.id | |
286 | FROM {course} c | |
287 | JOIN {context} ctx ON (ctx.instanceid = c.id AND ctx.contextlevel = :courselevel) | |
288 | JOIN (SELECT DISTINCT cctx.path | |
289 | FROM {course_categories} cc | |
290 | JOIN {context} cctx ON (cctx.instanceid = cc.id AND cctx.contextlevel = :catlevel) | |
291 | JOIN {role_assignments} ra ON (ra.contextid = cctx.id AND ra.roleid $roleids) | |
292 | ) cat ON (ctx.path LIKE $parentcat) | |
293 | LEFT JOIN {enrol} e ON (e.courseid = c.id AND e.enrol = 'category') | |
294 | WHERE e.id IS NULL) ci ON (c.id = ci.id)"; | |
df997f84 PS |
295 | |
296 | $rs = $DB->get_recordset_sql($sql, $params); | |
297 | foreach($rs as $course) { | |
298 | $plugin->add_instance($course); | |
299 | } | |
300 | $rs->close(); | |
301 | ||
302 | // now look for courses that do not have any interesting roles in parent contexts, | |
303 | // but still have the instance and delete them | |
304 | $sql = "SELECT e.* | |
305 | FROM {enrol} e | |
306 | JOIN {context} ctx ON (ctx.instanceid = e.courseid AND ctx.contextlevel = :courselevel) | |
3d7f23c5 | 307 | LEFT JOIN ({course_categories} cc |
df997f84 PS |
308 | JOIN {context} cctx ON (cctx.instanceid = cc.id AND cctx.contextlevel = :catlevel) |
309 | JOIN {role_assignments} ra ON (ra.contextid = cctx.id AND ra.roleid $roleids) | |
3d7f23c5 PS |
310 | ) ON (ctx.path LIKE $parentcctx) |
311 | WHERE e.enrol = 'category' AND cc.id IS NULL"; | |
df997f84 PS |
312 | |
313 | $rs = $DB->get_recordset_sql($sql, $params); | |
314 | foreach($rs as $instance) { | |
315 | $plugin->delete_instance($instance); | |
316 | } | |
317 | $rs->close(); | |
318 | ||
319 | // add missing enrolments | |
2a6dcb72 | 320 | $sql = "SELECT e.*, cat.userid, cat.estart |
df997f84 PS |
321 | FROM {enrol} e |
322 | JOIN {context} ctx ON (ctx.instanceid = e.courseid AND ctx.contextlevel = :courselevel) | |
2a6dcb72 | 323 | JOIN (SELECT cctx.path, ra.userid, MIN(ra.timemodified) AS estart |
df997f84 PS |
324 | FROM {course_categories} cc |
325 | JOIN {context} cctx ON (cctx.instanceid = cc.id AND cctx.contextlevel = :catlevel) | |
326 | JOIN {role_assignments} ra ON (ra.contextid = cctx.id AND ra.roleid $roleids) | |
2a6dcb72 | 327 | GROUP BY cctx.path, ra.userid |
df997f84 PS |
328 | ) cat ON (ctx.path LIKE $parentcat) |
329 | LEFT JOIN {user_enrolments} ue ON (ue.enrolid = e.id AND ue.userid = cat.userid) | |
330 | WHERE e.enrol = 'category' AND ue.id IS NULL"; | |
331 | $rs = $DB->get_recordset_sql($sql, $params); | |
332 | foreach($rs as $instance) { | |
333 | $userid = $instance->userid; | |
2a6dcb72 | 334 | $estart = $instance->estart; |
df997f84 | 335 | unset($instance->userid); |
2a6dcb72 PS |
336 | unset($instance->estart); |
337 | $plugin->enrol_user($instance, $userid, null, $estart); | |
a2043a31 PS |
338 | if ($verbose) { |
339 | mtrace(" enrolling: user $userid ==> course $instance->courseid"); | |
340 | } | |
df997f84 PS |
341 | } |
342 | $rs->close(); | |
343 | ||
344 | // remove stale enrolments | |
345 | $sql = "SELECT e.*, ue.userid | |
346 | FROM {enrol} e | |
347 | JOIN {context} ctx ON (ctx.instanceid = e.courseid AND ctx.contextlevel = :courselevel) | |
348 | JOIN {user_enrolments} ue ON (ue.enrolid = e.id) | |
3d7f23c5 | 349 | LEFT JOIN ({course_categories} cc |
df997f84 PS |
350 | JOIN {context} cctx ON (cctx.instanceid = cc.id AND cctx.contextlevel = :catlevel) |
351 | JOIN {role_assignments} ra ON (ra.contextid = cctx.id AND ra.roleid $roleids) | |
3d7f23c5 PS |
352 | ) ON (ctx.path LIKE $parentcctx AND ra.userid = ue.userid) |
353 | WHERE e.enrol = 'category' AND cc.id IS NULL"; | |
df997f84 PS |
354 | $rs = $DB->get_recordset_sql($sql, $params); |
355 | foreach($rs as $instance) { | |
356 | $userid = $instance->userid; | |
357 | unset($instance->userid); | |
358 | $plugin->unenrol_user($instance, $userid); | |
a2043a31 PS |
359 | if ($verbose) { |
360 | mtrace(" unenrolling: user $userid ==> course $instance->courseid"); | |
361 | } | |
df997f84 PS |
362 | } |
363 | $rs->close(); | |
a2043a31 PS |
364 | |
365 | if ($verbose) { | |
366 | mtrace('...user enrolment synchronisation finished.'); | |
367 | } | |
368 | ||
369 | return 0; | |
df997f84 | 370 | } |