Merge branch 'MDL-30175' of git://github.com/stronk7/moodle
[moodle.git] / enrol / category / locallib.php
blobbb53ecb0f07c4ad65a65501053eddd7058cf6e72
1 <?php
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.
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/>.
18 /**
19 * Local stuff for category enrolment plugin.
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
27 defined('MOODLE_INTERNAL') || die();
29 /**
30 * Event handler for category enrolment plugin.
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.
35 class enrol_category_handler {
36 public function role_assigned($ra) {
37 global $DB;
39 if (!enrol_is_enabled('category')) {
40 return true;
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;
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;
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);
68 $rs->close();
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) {
80 $plugin->enrol_user($instance, $ra->userid, null, $ra->timemodified);
82 $rs->close();
84 return true;
87 public function role_unassigned($ra) {
88 global $DB;
90 if (!enrol_is_enabled('category')) {
91 return true;
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;
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);
102 $roles = get_roles_with_capability('enrol/category:synchronised', CAP_ALLOW, $syscontext);
104 $plugin = enrol_get_plugin('category');
106 $sql = "SELECT e.*
107 FROM {course} c
108 JOIN {context} ctx ON (ctx.instanceid = c.id AND ctx.contextlevel = :courselevel AND ctx.path LIKE :match)
109 JOIN {enrol} e ON (e.courseid = c.id AND e.enrol = 'category')
110 JOIN {user_enrolments} ue ON (ue.enrolid = e.id AND ue.userid = :userid)";
111 $params = array('courselevel'=>CONTEXT_COURSE, 'match'=>$parentcontext->path.'/%', 'userid'=>$ra->userid);
112 $rs = $DB->get_recordset_sql($sql, $params);
114 list($roleids, $params) = $DB->get_in_or_equal(array_keys($roles), SQL_PARAMS_NAMED, 'r');
115 $params['userid'] = $ra->userid;
117 foreach ($rs as $instance) {
118 $coursecontext = get_context_instance(CONTEXT_COURSE, $instance->courseid);
119 $contextids = get_parent_contexts($coursecontext);
120 array_pop($contextids); // remove system context, we are interested in categories only
122 list($contextids, $contextparams) = $DB->get_in_or_equal($contextids, SQL_PARAMS_NAMED, 'c');
123 $params = array_merge($params, $contextparams);
125 $sql = "SELECT ra.id
126 FROM {role_assignments} ra
127 WHERE ra.userid = :userid AND ra.contextid $contextids AND ra.roleid $roleids";
128 if (!$DB->record_exists_sql($sql, $params)) {
129 // user does not have any interesting role in any parent context, let's unenrol
130 $plugin->unenrol_user($instance, $ra->userid);
133 $rs->close();
135 return true;
140 * Sync all category enrolments in one course
141 * @param int $courseid course id
142 * @return void
144 function enrol_category_sync_course($course) {
145 global $DB;
147 if (!enrol_is_enabled('category')) {
148 return;
151 $plugin = enrol_get_plugin('category');
153 $syscontext = get_context_instance(CONTEXT_SYSTEM);
154 $roles = get_roles_with_capability('enrol/category:synchronised', CAP_ALLOW, $syscontext);
156 if (!$roles) {
157 //nothing to sync, so remove the instance completely if exists
158 if ($instances = $DB->get_records('enrol', array('courseid'=>$course->id, 'enrol'=>'category'))) {
159 foreach ($instances as $instance) {
160 $plugin->delete_instance($instance);
163 return;
166 // first find out if any parent category context contains interesting role assignments
167 $coursecontext = get_context_instance(CONTEXT_COURSE, $course->id);
168 $contextids = get_parent_contexts($coursecontext);
169 array_pop($contextids); // remove system context, we are interested in categories only
171 list($roleids, $params) = $DB->get_in_or_equal(array_keys($roles), SQL_PARAMS_NAMED, 'r');
172 list($contextids, $contextparams) = $DB->get_in_or_equal($contextids, SQL_PARAMS_NAMED, 'c');
173 $params = array_merge($params, $contextparams);
174 $params['courseid'] = $course->id;
176 $sql = "SELECT 'x'
177 FROM {role_assignments}
178 WHERE roleid $roleids AND contextid $contextids";
179 if (!$DB->record_exists_sql($sql, $params)) {
180 if ($instances = $DB->get_records('enrol', array('courseid'=>$course->id, 'enrol'=>'category'))) {
181 // should be max one instance, but anyway
182 foreach ($instances as $instance) {
183 $plugin->delete_instance($instance);
186 return;
189 // make sure the enrol instance exists - there should be always only one instance
190 $delinstances = array();
191 if ($instances = $DB->get_records('enrol', array('courseid'=>$course->id, 'enrol'=>'category'))) {
192 $instance = array_shift($instances);
193 $delinstances = $instances;
194 } else {
195 $i = $plugin->add_instance($course);
196 $instance = $DB->get_record('enrol', array('id'=>$i));
199 // add new enrolments
200 $sql = "SELECT ra.userid, ra.estart
201 FROM (SELECT xra.userid, MIN(xra.timemodified) AS estart
202 FROM {role_assignments} xra
203 WHERE xra.roleid $roleids AND xra.contextid $contextids
204 GROUP BY xra.userid
205 ) ra
206 LEFT JOIN {user_enrolments} ue ON (ue.enrolid = :instanceid AND ue.userid = ra.userid)
207 WHERE ue.id IS NULL";
208 $params['instanceid'] = $instance->id;
209 $rs = $DB->get_recordset_sql($sql, $params);
210 foreach ($rs as $ra) {
211 $plugin->enrol_user($instance, $ra->userid, null, $ra->estart);
213 $rs->close();
215 // remove unwanted enrolments
216 $sql = "SELECT DISTINCT ue.userid
217 FROM {user_enrolments} ue
218 LEFT JOIN {role_assignments} ra ON (ra.roleid $roleids AND ra.contextid $contextids AND ra.userid = ue.userid)
219 WHERE ue.enrolid = :instanceid AND ra.id IS NULL";
220 $rs = $DB->get_recordset_sql($sql, $params);
221 foreach ($rs as $ra) {
222 $plugin->unenrol_user($instance, $ra->userid);
224 $rs->close();
226 if ($delinstances) {
227 // we have to do this as the last step in order to prevent temporary unenrolment
228 foreach ($delinstances as $delinstance) {
229 $plugin->delete_instance($delinstance);
234 function enrol_category_sync_full() {
235 global $DB;
238 if (!enrol_is_enabled('category')) {
239 return;
242 // we may need a lot of time here
243 @set_time_limit(0);
245 $plugin = enrol_get_plugin('category');
247 $syscontext = get_context_instance(CONTEXT_SYSTEM);
249 // any interesting roles worth synchronising?
250 if (!$roles = get_roles_with_capability('enrol/category:synchronised', CAP_ALLOW, $syscontext)) {
251 // yay, nothing to do, so let's remove all leftovers
252 if ($instances = $DB->get_records('enrol', array('enrol'=>'category'))) {
253 foreach ($instances as $instance) {
254 $plugin->delete_instance($instance);
257 return;
260 list($roleids, $params) = $DB->get_in_or_equal(array_keys($roles), SQL_PARAMS_NAMED, 'r');
261 $params['courselevel'] = CONTEXT_COURSE;
262 $params['catlevel'] = CONTEXT_COURSECAT;
264 // first of all add necessary enrol instances to all courses
265 $parentcat = $DB->sql_concat("cat.path", "'/%'");
266 // need whole course records to be used by add_instance(), use inner view (ci) to
267 // get distinct records only.
268 // TODO: Moodle 2.1. Improve enrol API to accept courseid / courserec
269 $sql = "SELECT c.*
270 FROM {course} c
271 JOIN (
272 SELECT DISTINCT c.id
273 FROM {course} c
274 JOIN {context} ctx ON (ctx.instanceid = c.id AND ctx.contextlevel = :courselevel)
275 JOIN (SELECT DISTINCT cctx.path
276 FROM {course_categories} cc
277 JOIN {context} cctx ON (cctx.instanceid = cc.id AND cctx.contextlevel = :catlevel)
278 JOIN {role_assignments} ra ON (ra.contextid = cctx.id AND ra.roleid $roleids)
279 ) cat ON (ctx.path LIKE $parentcat)
280 LEFT JOIN {enrol} e ON (e.courseid = c.id AND e.enrol = 'category')
281 WHERE e.id IS NULL) ci ON (c.id = ci.id)";
283 $rs = $DB->get_recordset_sql($sql, $params);
284 foreach($rs as $course) {
285 $plugin->add_instance($course);
287 $rs->close();
289 // now look for courses that do not have any interesting roles in parent contexts,
290 // but still have the instance and delete them
291 $sql = "SELECT e.*
292 FROM {enrol} e
293 JOIN {context} ctx ON (ctx.instanceid = e.courseid AND ctx.contextlevel = :courselevel)
294 LEFT JOIN (SELECT DISTINCT cctx.path
295 FROM {course_categories} cc
296 JOIN {context} cctx ON (cctx.instanceid = cc.id AND cctx.contextlevel = :catlevel)
297 JOIN {role_assignments} ra ON (ra.contextid = cctx.id AND ra.roleid $roleids)
298 ) cat ON (ctx.path LIKE $parentcat)
299 WHERE e.enrol = 'category' AND cat.path IS NULL";
301 $rs = $DB->get_recordset_sql($sql, $params);
302 foreach($rs as $instance) {
303 $plugin->delete_instance($instance);
305 $rs->close();
307 // add missing enrolments
308 $sql = "SELECT e.*, cat.userid, cat.estart
309 FROM {enrol} e
310 JOIN {context} ctx ON (ctx.instanceid = e.courseid AND ctx.contextlevel = :courselevel)
311 JOIN (SELECT cctx.path, ra.userid, MIN(ra.timemodified) AS estart
312 FROM {course_categories} cc
313 JOIN {context} cctx ON (cctx.instanceid = cc.id AND cctx.contextlevel = :catlevel)
314 JOIN {role_assignments} ra ON (ra.contextid = cctx.id AND ra.roleid $roleids)
315 GROUP BY cctx.path, ra.userid
316 ) cat ON (ctx.path LIKE $parentcat)
317 LEFT JOIN {user_enrolments} ue ON (ue.enrolid = e.id AND ue.userid = cat.userid)
318 WHERE e.enrol = 'category' AND ue.id IS NULL";
319 $rs = $DB->get_recordset_sql($sql, $params);
320 foreach($rs as $instance) {
321 $userid = $instance->userid;
322 $estart = $instance->estart;
323 unset($instance->userid);
324 unset($instance->estart);
325 $plugin->enrol_user($instance, $userid, null, $estart);
327 $rs->close();
329 // remove stale enrolments
330 $sql = "SELECT e.*, ue.userid
331 FROM {enrol} e
332 JOIN {context} ctx ON (ctx.instanceid = e.courseid AND ctx.contextlevel = :courselevel)
333 JOIN {user_enrolments} ue ON (ue.enrolid = e.id)
334 LEFT JOIN (SELECT DISTINCT cctx.path, ra.userid
335 FROM {course_categories} cc
336 JOIN {context} cctx ON (cctx.instanceid = cc.id AND cctx.contextlevel = :catlevel)
337 JOIN {role_assignments} ra ON (ra.contextid = cctx.id AND ra.roleid $roleids)
338 ) cat ON (ctx.path LIKE $parentcat AND cat.userid = ue.userid)
339 WHERE e.enrol = 'category' AND cat.userid IS NULL";
340 $rs = $DB->get_recordset_sql($sql, $params);
341 foreach($rs as $instance) {
342 $userid = $instance->userid;
343 unset($instance->userid);
344 $plugin->unenrol_user($instance, $userid);
346 $rs->close();