Merge branch 'MDL-48165-28-dimgrades' of git://github.com/mudrd8mz/moodle into MOODLE...
[moodle.git] / enrol / category / locallib.php
blob243995539fe96ce5c1ec16a7308cbce9b826e01c
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 category enrolment plugin.
20 * @package enrol_category
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();
27 /**
28 * Sync all category enrolments in one course
29 * @param stdClass $course
30 * @return void
32 function enrol_category_sync_course($course) {
33 global $DB;
35 if (!enrol_is_enabled('category')) {
36 return;
39 $plugin = enrol_get_plugin('category');
41 $syscontext = context_system::instance();
42 $roles = get_roles_with_capability('enrol/category:synchronised', CAP_ALLOW, $syscontext);
44 if (!$roles) {
45 // Nothing to sync, so remove the instance completely if exists.
46 if ($instances = $DB->get_records('enrol', array('courseid'=>$course->id, 'enrol'=>'category'))) {
47 foreach ($instances as $instance) {
48 $plugin->delete_instance($instance);
51 return;
54 // First find out if any parent category context contains interesting role assignments.
55 $coursecontext = context_course::instance($course->id);
56 $contextids = $coursecontext->get_parent_context_ids();
57 array_pop($contextids); // Remove system context, we are interested in categories only.
59 list($roleids, $params) = $DB->get_in_or_equal(array_keys($roles), SQL_PARAMS_NAMED, 'r');
60 list($contextids, $contextparams) = $DB->get_in_or_equal($contextids, SQL_PARAMS_NAMED, 'c');
61 $params = array_merge($params, $contextparams);
62 $params['courseid'] = $course->id;
64 $sql = "SELECT 'x'
65 FROM {role_assignments}
66 WHERE roleid $roleids AND contextid $contextids";
67 if (!$DB->record_exists_sql($sql, $params)) {
68 if ($instances = $DB->get_records('enrol', array('courseid'=>$course->id, 'enrol'=>'category'))) {
69 // Should be max one instance, but anyway.
70 foreach ($instances as $instance) {
71 $plugin->delete_instance($instance);
74 return;
77 // Make sure the enrol instance exists - there should be always only one instance.
78 $delinstances = array();
79 if ($instances = $DB->get_records('enrol', array('courseid'=>$course->id, 'enrol'=>'category'))) {
80 $instance = array_shift($instances);
81 $delinstances = $instances;
82 } else {
83 $i = $plugin->add_instance($course);
84 $instance = $DB->get_record('enrol', array('id'=>$i));
87 // Add new enrolments.
88 $sql = "SELECT ra.userid, ra.estart
89 FROM (SELECT xra.userid, MIN(xra.timemodified) AS estart
90 FROM {role_assignments} xra
91 JOIN {user} xu ON (xu.id = xra.userid AND xu.deleted = 0)
92 WHERE xra.roleid $roleids AND xra.contextid $contextids
93 GROUP BY xra.userid
94 ) ra
95 LEFT JOIN {user_enrolments} ue ON (ue.enrolid = :instanceid AND ue.userid = ra.userid)
96 WHERE ue.id IS NULL";
97 $params['instanceid'] = $instance->id;
98 $rs = $DB->get_recordset_sql($sql, $params);
99 foreach ($rs as $ra) {
100 $plugin->enrol_user($instance, $ra->userid, null, $ra->estart);
102 $rs->close();
104 // Remove unwanted enrolments.
105 $sql = "SELECT DISTINCT ue.userid
106 FROM {user_enrolments} ue
107 LEFT JOIN {role_assignments} ra ON (ra.roleid $roleids AND ra.contextid $contextids AND ra.userid = ue.userid)
108 WHERE ue.enrolid = :instanceid AND ra.id IS NULL";
109 $rs = $DB->get_recordset_sql($sql, $params);
110 foreach ($rs as $ra) {
111 $plugin->unenrol_user($instance, $ra->userid);
113 $rs->close();
115 if ($delinstances) {
116 // We have to do this as the last step in order to prevent temporary unenrolment.
117 foreach ($delinstances as $delinstance) {
118 $plugin->delete_instance($delinstance);
124 * Synchronise courses in all categories.
126 * It gets out-of-sync if:
127 * - you move course to different category
128 * - reorder categories
129 * - disable enrol_category and enable it again
131 * @param progress_trace $trace
132 * @return int exit code - 0 is ok, 1 means error, 2 if plugin disabled
134 function enrol_category_sync_full(progress_trace $trace) {
135 global $DB;
138 if (!enrol_is_enabled('category')) {
139 $trace->finished();
140 return 2;
143 // We may need a lot of time here.
144 core_php_time_limit::raise();
146 $plugin = enrol_get_plugin('category');
148 $syscontext = context_system::instance();
150 // Any interesting roles worth synchronising?
151 if (!$roles = get_roles_with_capability('enrol/category:synchronised', CAP_ALLOW, $syscontext)) {
152 // yay, nothing to do, so let's remove all leftovers
153 $trace->output("No roles with 'enrol/category:synchronised' capability found.");
154 if ($instances = $DB->get_records('enrol', array('enrol'=>'category'))) {
155 $trace->output("Deleting all category enrol instances...");
156 foreach ($instances as $instance) {
157 $trace->output("deleting category enrol instance from course {$instance->courseid}", 1);
158 $plugin->delete_instance($instance);
160 $trace->output("...all instances deleted.");
162 $trace->finished();
163 return 0;
165 $rolenames = role_fix_names($roles, null, ROLENAME_SHORT, true);
166 $trace->output('Synchronising category enrolments for roles: '.implode(', ', $rolenames).'...');
168 list($roleids, $params) = $DB->get_in_or_equal(array_keys($roles), SQL_PARAMS_NAMED, 'r');
169 $params['courselevel'] = CONTEXT_COURSE;
170 $params['catlevel'] = CONTEXT_COURSECAT;
172 // First of all add necessary enrol instances to all courses.
173 $parentcat = $DB->sql_concat("cat.path", "'/%'");
174 $parentcctx = $DB->sql_concat("cctx.path", "'/%'");
175 // Need whole course records to be used by add_instance(), use inner view (ci) to
176 // get distinct records only.
177 // TODO: Moodle 2.1. Improve enrol API to accept courseid / courserec
178 $sql = "SELECT c.*
179 FROM {course} c
180 JOIN (
181 SELECT DISTINCT c.id
182 FROM {course} c
183 JOIN {context} ctx ON (ctx.instanceid = c.id AND ctx.contextlevel = :courselevel)
184 JOIN (SELECT DISTINCT cctx.path
185 FROM {course_categories} cc
186 JOIN {context} cctx ON (cctx.instanceid = cc.id AND cctx.contextlevel = :catlevel)
187 JOIN {role_assignments} ra ON (ra.contextid = cctx.id AND ra.roleid $roleids)
188 ) cat ON (ctx.path LIKE $parentcat)
189 LEFT JOIN {enrol} e ON (e.courseid = c.id AND e.enrol = 'category')
190 WHERE e.id IS NULL) ci ON (c.id = ci.id)";
192 $rs = $DB->get_recordset_sql($sql, $params);
193 foreach($rs as $course) {
194 $plugin->add_instance($course);
196 $rs->close();
198 // Now look for courses that do not have any interesting roles in parent contexts,
199 // but still have the instance and delete them.
200 $sql = "SELECT e.*
201 FROM {enrol} e
202 JOIN {context} ctx ON (ctx.instanceid = e.courseid AND ctx.contextlevel = :courselevel)
203 LEFT JOIN ({course_categories} cc
204 JOIN {context} cctx ON (cctx.instanceid = cc.id AND cctx.contextlevel = :catlevel)
205 JOIN {role_assignments} ra ON (ra.contextid = cctx.id AND ra.roleid $roleids)
206 ) ON (ctx.path LIKE $parentcctx)
207 WHERE e.enrol = 'category' AND cc.id IS NULL";
209 $rs = $DB->get_recordset_sql($sql, $params);
210 foreach($rs as $instance) {
211 $plugin->delete_instance($instance);
213 $rs->close();
215 // Add missing enrolments.
216 $sql = "SELECT e.*, cat.userid, cat.estart
217 FROM {enrol} e
218 JOIN {context} ctx ON (ctx.instanceid = e.courseid AND ctx.contextlevel = :courselevel)
219 JOIN (SELECT cctx.path, ra.userid, MIN(ra.timemodified) AS estart
220 FROM {course_categories} cc
221 JOIN {context} cctx ON (cctx.instanceid = cc.id AND cctx.contextlevel = :catlevel)
222 JOIN {role_assignments} ra ON (ra.contextid = cctx.id AND ra.roleid $roleids)
223 GROUP BY cctx.path, ra.userid
224 ) cat ON (ctx.path LIKE $parentcat)
225 LEFT JOIN {user_enrolments} ue ON (ue.enrolid = e.id AND ue.userid = cat.userid)
226 WHERE e.enrol = 'category' AND ue.id IS NULL";
227 $rs = $DB->get_recordset_sql($sql, $params);
228 foreach($rs as $instance) {
229 $userid = $instance->userid;
230 $estart = $instance->estart;
231 unset($instance->userid);
232 unset($instance->estart);
233 $plugin->enrol_user($instance, $userid, null, $estart);
234 $trace->output("enrolling: user $userid ==> course $instance->courseid", 1);
236 $rs->close();
238 // Remove stale enrolments.
239 $sql = "SELECT e.*, ue.userid
240 FROM {enrol} e
241 JOIN {context} ctx ON (ctx.instanceid = e.courseid AND ctx.contextlevel = :courselevel)
242 JOIN {user_enrolments} ue ON (ue.enrolid = e.id)
243 LEFT JOIN ({course_categories} cc
244 JOIN {context} cctx ON (cctx.instanceid = cc.id AND cctx.contextlevel = :catlevel)
245 JOIN {role_assignments} ra ON (ra.contextid = cctx.id AND ra.roleid $roleids)
246 ) ON (ctx.path LIKE $parentcctx AND ra.userid = ue.userid)
247 WHERE e.enrol = 'category' AND cc.id IS NULL";
248 $rs = $DB->get_recordset_sql($sql, $params);
249 foreach($rs as $instance) {
250 $userid = $instance->userid;
251 unset($instance->userid);
252 $plugin->unenrol_user($instance, $userid);
253 $trace->output("unenrolling: user $userid ==> course $instance->courseid", 1);
255 $rs->close();
257 $trace->output('...user enrolment synchronisation finished.');
258 $trace->finished();
260 return 0;