Fixing drift between CVS and git
[moodle.git] / lib / enrollib.php
CommitLineData
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 * This library includes the basic parts of enrol api.
20 * It is available on each page.
21 *
78bfb562
PS
22 * @package core
23 * @subpackage enrol
24 * @copyright 2010 Petr Skoda {@link http://skodak.org}
25 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
df997f84
PS
26 */
27
78bfb562 28defined('MOODLE_INTERNAL') || die();
df997f84
PS
29
30/** Course enrol instance enabled. (used in enrol->status) */
31define('ENROL_INSTANCE_ENABLED', 0);
32
33/** Course enrol instance disabled, user may enter course if other enrol instance enabled. (used in enrol->status)*/
34define('ENROL_INSTANCE_DISABLED', 1);
35
36/** User is active participant (used in user_enrolments->status)*/
37define('ENROL_USER_ACTIVE', 0);
38
39/** User participation in course is suspended (used in user_enrolments->status) */
40define('ENROL_USER_SUSPENDED', 1);
41
42/** Enrol info is cached for this number of seconds in require_login() */
43define('ENROL_REQUIRE_LOGIN_CACHE_PERIOD', 1800);
44
34121765
PS
45/** When user disappears from external source, the enrolment is completely removed */
46define('ENROL_EXT_REMOVED_UNENROL', 0);
47
48/** When user disappears from external source, the enrolment is kept as is - one way sync */
49define('ENROL_EXT_REMOVED_KEEP', 1);
50
51/**
52 * When user disappears from external source, user enrolment is suspended, roles are kept as is.
53 * In some cases user needs a role with some capability to be visible in UI - suc has in gradebook,
54 * assignments, etc.
55 */
56define('ENROL_EXT_REMOVED_SUSPEND', 2);
57
58/**
59 * When user disappears from external source, the enrolment is suspended and roles assigned
60 * by enrol instance are removed. Please note that user may "disappear" from gradebook and other areas.
61 * */
62define('ENROL_EXT_REMOVED_SUSPENDNOROLES', 3);
63
df997f84
PS
64/**
65 * Returns instances of enrol plugins
66 * @param bool $enable return enabled only
67 * @return array of enrol plugins name=>instance
68 */
69function enrol_get_plugins($enabled) {
70 global $CFG;
71
72 $result = array();
73
74 if ($enabled) {
75 // sorted by enabled plugin order
76 $enabled = explode(',', $CFG->enrol_plugins_enabled);
77 $plugins = array();
78 foreach ($enabled as $plugin) {
79 $plugins[$plugin] = "$CFG->dirroot/enrol/$plugin";
80 }
81 } else {
82 // sorted alphabetically
83 $plugins = get_plugin_list('enrol');
84 ksort($plugins);
85 }
86
87 foreach ($plugins as $plugin=>$location) {
88 if (!file_exists("$location/lib.php")) {
89 continue;
90 }
91 include_once("$location/lib.php");
92 $class = "enrol_{$plugin}_plugin";
93 if (!class_exists($class)) {
94 continue;
95 }
96
97 $result[$plugin] = new $class();
98 }
99
100 return $result;
101}
102
103/**
104 * Returns instance of enrol plugin
105 * @param string $name name of enrol plugin ('manual', 'guest', ...)
106 * @return enrol_plugin
107 */
108function enrol_get_plugin($name) {
109 global $CFG;
110
111 if ($name !== clean_param($name, PARAM_SAFEDIR)) {
112 // ignore malformed plugin names completely
113 return null;
114 }
115
116 $location = "$CFG->dirroot/enrol/$name";
117
118 if (!file_exists("$location/lib.php")) {
119 return null;
120 }
121 include_once("$location/lib.php");
122 $class = "enrol_{$name}_plugin";
123 if (!class_exists($class)) {
124 return null;
125 }
126
127 return new $class();
128}
129
130/**
131 * Returns enrolment instances in given course.
132 * @param int $courseid
133 * @param bool $enabled
134 * @return array of enrol instances
135 */
136function enrol_get_instances($courseid, $enabled) {
137 global $DB, $CFG;
138
139 if (!$enabled) {
140 return $DB->get_records('enrol', array('courseid'=>$courseid), 'sortorder,id');
141 }
142
143 $result = $DB->get_records('enrol', array('courseid'=>$courseid, 'status'=>ENROL_INSTANCE_ENABLED), 'sortorder,id');
144
79721bd3 145 $enabled = explode(',', $CFG->enrol_plugins_enabled);
df997f84
PS
146 foreach ($result as $key=>$instance) {
147 if (!in_array($instance->enrol, $enabled)) {
148 unset($result[$key]);
149 continue;
150 }
151 if (!file_exists("$CFG->dirroot/enrol/$instance->enrol/lib.php")) {
152 // broken plugin
153 unset($result[$key]);
154 continue;
155 }
156 }
157
158 return $result;
159}
160
161/**
162 * Checks if a given plugin is in the list of enabled enrolment plugins.
163 *
164 * @param string $enrol Enrolment plugin name
165 * @return boolean Whether the plugin is enabled
166 */
167function enrol_is_enabled($enrol) {
168 global $CFG;
169
170 if (empty($CFG->enrol_plugins_enabled)) {
171 return false;
172 }
173 return in_array($enrol, explode(',', $CFG->enrol_plugins_enabled));
174}
175
176/**
177 * Check all the login enrolment information for the given user object
178 * by querying the enrolment plugins
179 *
180 * @param object $user
181 * @return void
182 */
183function enrol_check_plugins($user) {
184 global $CFG;
185
186 if (empty($user->id) or isguestuser($user)) {
187 // shortcut - there is no enrolment work for guests and not-logged-in users
188 return;
189 }
190
e384d2dc
PS
191 if (is_siteadmin()) {
192 // no sync for admin user, please use admin accounts only for admin tasks like the unix root user!
193 // if plugin fails on sync admins need to be able to log in
194 return;
195 }
196
df997f84
PS
197 static $inprogress = array(); // To prevent this function being called more than once in an invocation
198
199 if (!empty($inprogress[$user->id])) {
200 return;
201 }
202
203 $inprogress[$user->id] = true; // Set the flag
204
205 $enabled = enrol_get_plugins(true);
206
207 foreach($enabled as $enrol) {
208 $enrol->sync_user_enrolments($user);
209 }
210
211 unset($inprogress[$user->id]); // Unset the flag
212}
213
214/**
215 * This function adds necessary enrol plugins UI into the course edit form.
216 *
217 * @param MoodleQuickForm $mform
218 * @param object $data course edit form data
219 * @param object $context context of existing course or parent category if course does not exist
220 * @return void
221 */
222function enrol_course_edit_form(MoodleQuickForm $mform, $data, $context) {
223 $plugins = enrol_get_plugins(true);
224 if (!empty($data->id)) {
225 $instances = enrol_get_instances($data->id, false);
226 foreach ($instances as $instance) {
227 if (!isset($plugins[$instance->enrol])) {
228 continue;
229 }
230 $plugin = $plugins[$instance->enrol];
231 $plugin->course_edit_form($instance, $mform, $data, $context);
232 }
233 } else {
234 foreach ($plugins as $plugin) {
235 $plugin->course_edit_form(NULL, $mform, $data, $context);
236 }
237 }
238}
239
240/**
241 * Validate course edit form data
242 *
243 * @param array $data raw form data
244 * @param object $context context of existing course or parent category if course does not exist
245 * @return array errors array
246 */
247function enrol_course_edit_validation(array $data, $context) {
248 $errors = array();
249 $plugins = enrol_get_plugins(true);
250
251 if (!empty($data['id'])) {
252 $instances = enrol_get_instances($data['id'], false);
253 foreach ($instances as $instance) {
254 if (!isset($plugins[$instance->enrol])) {
255 continue;
256 }
257 $plugin = $plugins[$instance->enrol];
258 $errors = array_merge($errors, $plugin->course_edit_validation($instance, $data, $context));
259 }
260 } else {
261 foreach ($plugins as $plugin) {
262 $errors = array_merge($errors, $plugin->course_edit_validation(NULL, $data, $context));
263 }
264 }
265
266 return $errors;
267}
268
269/**
270 * Update enrol instances after course edit form submission
271 * @param bool $inserted true means new course added, false course already existed
272 * @param object $course
273 * @param object $data form data
274 * @return void
275 */
276function enrol_course_updated($inserted, $course, $data) {
277 global $DB, $CFG;
278
279 $plugins = enrol_get_plugins(true);
280
281 foreach ($plugins as $plugin) {
282 $plugin->course_updated($inserted, $course, $data);
283 }
284}
285
286/**
287 * Add navigation nodes
288 * @param navigation_node $coursenode
289 * @param object $course
290 * @return void
291 */
292function enrol_add_course_navigation(navigation_node $coursenode, $course) {
6e4c374d 293 global $CFG;
df997f84
PS
294
295 $coursecontext = get_context_instance(CONTEXT_COURSE, $course->id);
296
297 $instances = enrol_get_instances($course->id, true);
298 $plugins = enrol_get_plugins(true);
299
300 // we do not want to break all course pages if there is some borked enrol plugin, right?
301 foreach ($instances as $k=>$instance) {
302 if (!isset($plugins[$instance->enrol])) {
303 unset($instances[$k]);
304 }
305 }
306
f5ce6b71 307 $usersnode = $coursenode->add(get_string('users'), null, navigation_node::TYPE_CONTAINER, null, 'users');
df997f84
PS
308
309 if ($course->id != SITEID) {
f5ce6b71 310 // list all participants - allows assigning roles, groups, etc.
df997f84
PS
311 if (has_capability('moodle/course:enrolreview', $coursecontext)) {
312 $url = new moodle_url('/enrol/users.php', array('id'=>$course->id));
f5ce6b71 313 $usersnode->add(get_string('enrolledusers', 'enrol'), $url, navigation_node::TYPE_SETTING, null, 'review', new pix_icon('i/users', ''));
df997f84
PS
314 }
315
316 // manage enrol plugin instances
317 if (has_capability('moodle/course:enrolconfig', $coursecontext) or has_capability('moodle/course:enrolreview', $coursecontext)) {
318 $url = new moodle_url('/enrol/instances.php', array('id'=>$course->id));
319 } else {
320 $url = NULL;
321 }
f5ce6b71 322 $instancesnode = $usersnode->add(get_string('enrolmentinstances', 'enrol'), $url, navigation_node::TYPE_SETTING, null, 'manageinstances');
df997f84
PS
323
324 // each instance decides how to configure itself or how many other nav items are exposed
325 foreach ($instances as $instance) {
326 if (!isset($plugins[$instance->enrol])) {
327 continue;
328 }
329 $plugins[$instance->enrol]->add_course_navigation($instancesnode, $instance);
330 }
331
332 if (!$url) {
333 $instancesnode->trim_if_empty();
334 }
335 }
336
337 // Manage groups in this course or even frontpage
338 if (($course->groupmode || !$course->groupmodeforce) && has_capability('moodle/course:managegroups', $coursecontext)) {
339 $url = new moodle_url('/group/index.php', array('id'=>$course->id));
340 $usersnode->add(get_string('groups'), $url, navigation_node::TYPE_SETTING, null, 'groups', new pix_icon('i/group', ''));
341 }
342
343 if (has_any_capability(array( 'moodle/role:assign', 'moodle/role:safeoverride','moodle/role:override', 'moodle/role:review'), $coursecontext)) {
344 // Override roles
345 if (has_capability('moodle/role:review', $coursecontext)) {
346 $url = new moodle_url('/admin/roles/permissions.php', array('contextid'=>$coursecontext->id));
347 } else {
348 $url = NULL;
349 }
f5ce6b71 350 $permissionsnode = $usersnode->add(get_string('permissions', 'role'), $url, navigation_node::TYPE_SETTING, null, 'override');
df997f84
PS
351
352 // Add assign or override roles if allowed
353 if ($course->id == SITEID or (!empty($CFG->adminsassignrolesincourse) and is_siteadmin())) {
354 if (has_capability('moodle/role:assign', $coursecontext)) {
355 $url = new moodle_url('/admin/roles/assign.php', array('contextid'=>$coursecontext->id));
f5ce6b71 356 $permissionsnode->add(get_string('assignedroles', 'role'), $url, navigation_node::TYPE_SETTING, null, 'roles', new pix_icon('i/roles', ''));
df997f84
PS
357 }
358 }
359 // Check role permissions
360 if (has_any_capability(array('moodle/role:assign', 'moodle/role:safeoverride','moodle/role:override', 'moodle/role:assign'), $coursecontext)) {
361 $url = new moodle_url('/admin/roles/check.php', array('contextid'=>$coursecontext->id));
f5ce6b71 362 $permissionsnode->add(get_string('checkpermissions', 'role'), $url, navigation_node::TYPE_SETTING, null, 'permissions', new pix_icon('i/checkpermissions', ''));
df997f84
PS
363 }
364 }
365
366 // Deal somehow with users that are not enrolled but still got a role somehow
367 if ($course->id != SITEID) {
368 //TODO, create some new UI for role assignments at course level
369 if (has_capability('moodle/role:assign', $coursecontext)) {
370 $url = new moodle_url('/enrol/otherusers.php', array('id'=>$course->id));
f5ce6b71 371 $usersnode->add(get_string('notenrolledusers', 'enrol'), $url, navigation_node::TYPE_SETTING, null, 'otherusers', new pix_icon('i/roles', ''));
df997f84
PS
372 }
373 }
374
375 // just in case nothing was actually added
376 $usersnode->trim_if_empty();
377
378 if ($course->id != SITEID) {
379 // Unenrol link
217d0397
PS
380 if (is_enrolled($coursecontext)) {
381 foreach ($instances as $instance) {
382 if (!isset($plugins[$instance->enrol])) {
383 continue;
384 }
385 $plugin = $plugins[$instance->enrol];
386 if ($unenrollink = $plugin->get_unenrolself_link($instance)) {
387 $coursenode->add(get_string('unenrolme', 'core_enrol', format_string($course->shortname)), $unenrollink, navigation_node::TYPE_SETTING, null, 'unenrolself', new pix_icon('i/user', ''));
388 break;
389 //TODO. deal with multiple unenrol links - not likely case, but still...
390 }
df997f84 391 }
217d0397
PS
392 } else {
393 if (is_viewing($coursecontext)) {
394 // better not show any enrol link, this is intended for managers and inspectors
395 } else {
396 foreach ($instances as $instance) {
397 if (!isset($plugins[$instance->enrol])) {
398 continue;
399 }
400 $plugin = $plugins[$instance->enrol];
401 if ($plugin->show_enrolme_link($instance)) {
402 $url = new moodle_url('/enrol/index.php', array('id'=>$course->id));
403 $coursenode->add(get_string('enrolme', 'core_enrol', format_string($course->shortname)), $url, navigation_node::TYPE_SETTING, null, 'enrolself', new pix_icon('i/user', ''));
404 break;
405 }
406 }
df997f84
PS
407 }
408 }
df997f84
PS
409 }
410}
411
412/**
413 * Returns list of courses current $USER is enrolled in and can access
414 *
415 * - $fields is an array of field names to ADD
416 * so name the fields you really need, which will
417 * be added and uniq'd
418 *
419 * @param strin|array $fields
420 * @param string $sort
421 * @param int $limit max number of courses
422 * @return array
423 */
424function enrol_get_my_courses($fields = NULL, $sort = 'visible DESC,sortorder ASC', $limit = 0) {
425 global $DB, $USER;
426
427 // Guest account does not have any courses
428 if (isguestuser() or !isloggedin()) {
429 return(array());
430 }
431
432 $basefields = array('id', 'category', 'sortorder',
433 'shortname', 'fullname', 'idnumber',
434 'startdate', 'visible',
435 'groupmode', 'groupmodeforce');
436
437 if (empty($fields)) {
438 $fields = $basefields;
439 } else if (is_string($fields)) {
440 // turn the fields from a string to an array
441 $fields = explode(',', $fields);
442 $fields = array_map('trim', $fields);
443 $fields = array_unique(array_merge($basefields, $fields));
444 } else if (is_array($fields)) {
445 $fields = array_unique(array_merge($basefields, $fields));
446 } else {
447 throw new coding_exception('Invalid $fileds parameter in enrol_get_my_courses()');
448 }
449 if (in_array('*', $fields)) {
450 $fields = array('*');
451 }
452
453 $orderby = "";
454 $sort = trim($sort);
455 if (!empty($sort)) {
456 $rawsorts = explode(',', $sort);
457 $sorts = array();
458 foreach ($rawsorts as $rawsort) {
459 $rawsort = trim($rawsort);
460 if (strpos($rawsort, 'c.') === 0) {
461 $rawsort = substr($rawsort, 2);
462 }
463 $sorts[] = trim($rawsort);
464 }
465 $sort = 'c.'.implode(',c.', $sorts);
466 $orderby = "ORDER BY $sort";
467 }
468
469 $wheres = array("c.id <> :siteid");
470 $params = array('siteid'=>SITEID);
471
472 if (isset($USER->loginascontext) and $USER->loginascontext->contextlevel == CONTEXT_COURSE) {
473 // list _only_ this course - anything else is asking for trouble...
474 $wheres[] = "courseid = :loginas";
475 $params['loginas'] = $USER->loginascontext->instanceid;
476 }
477
478 $coursefields = 'c.' .join(',c.', $fields);
479 list($ccselect, $ccjoin) = context_instance_preload_sql('c.id', CONTEXT_COURSE, 'ctx');
4129338c 480 $wheres = implode(" AND ", $wheres);
df997f84 481
4129338c
PS
482 //note: we can not use DISTINCT + text fields due to Oracle and MS limitations, that is why we have the subselect there
483 $sql = "SELECT $coursefields $ccselect
df997f84 484 FROM {course} c
4129338c
PS
485 JOIN (SELECT DISTINCT e.courseid
486 FROM {enrol} e
487 JOIN {user_enrolments} ue ON (ue.enrolid = e.id AND ue.userid = :userid)
488 WHERE ue.status = :active AND e.status = :enabled AND ue.timestart < :now1 AND (ue.timeend = 0 OR ue.timeend > :now2)
489 ) en ON (en.courseid = c.id)
df997f84 490 $ccjoin
4129338c 491 WHERE $wheres
df997f84
PS
492 $orderby";
493 $params['userid'] = $USER->id;
494 $params['active'] = ENROL_USER_ACTIVE;
495 $params['enabled'] = ENROL_INSTANCE_ENABLED;
496 $params['now1'] = round(time(), -2); // improves db caching
497 $params['now2'] = $params['now1'];
498
499 $courses = $DB->get_records_sql($sql, $params, 0, $limit);
500
501 // preload contexts and check visibility
502 foreach ($courses as $id=>$course) {
503 context_instance_preload($course);
504 if (!$course->visible) {
505 if (!$context = get_context_instance(CONTEXT_COURSE, $id)) {
55880bdd 506 unset($courses[$id]);
df997f84
PS
507 continue;
508 }
509 if (!has_capability('moodle/course:viewhiddencourses', $context)) {
55880bdd 510 unset($courses[$id]);
df997f84
PS
511 continue;
512 }
513 }
514 $courses[$id] = $course;
515 }
516
517 //wow! Is that really all? :-D
518
519 return $courses;
520}
521
bf423bb1
PS
522/**
523 * Returns course enrolment information icons.
524 *
525 * @param object $course
526 * @param array $instances enrol instances of this course, improves performance
527 * @return array of pix_icon
528 */
529function enrol_get_course_info_icons($course, array $instances = NULL) {
530 $icons = array();
531 if (is_null($instances)) {
532 $instances = enrol_get_instances($course->id, true);
533 }
534 $plugins = enrol_get_plugins(true);
535 foreach ($plugins as $name => $plugin) {
536 $pis = array();
537 foreach ($instances as $instance) {
538 if ($instance->status != ENROL_INSTANCE_ENABLED or $instance->courseid != $course->id) {
539 debugging('Invalid instances parameter submitted in enrol_get_info_icons()');
540 continue;
541 }
542 if ($instance->enrol == $name) {
543 $pis[$instance->id] = $instance;
544 }
545 }
546 if ($pis) {
547 $icons = array_merge($icons, $plugin->get_info_icons($pis));
548 }
549 }
550 return $icons;
551}
552
553/**
554 * Returns course enrolment detailed information.
555 *
556 * @param object $course
557 * @return array of html fragments - can be used to construct lists
558 */
559function enrol_get_course_description_texts($course) {
560 $lines = array();
561 $instances = enrol_get_instances($course->id, true);
562 $plugins = enrol_get_plugins(true);
563 foreach ($instances as $instance) {
64942a9d 564 if (!isset($plugins[$instance->enrol])) {
bf423bb1
PS
565 //weird
566 continue;
567 }
64942a9d 568 $plugin = $plugins[$instance->enrol];
bf423bb1
PS
569 $text = $plugin->get_description_text($instance);
570 if ($text !== NULL) {
571 $lines[] = $text;
572 }
573 }
574 return $lines;
575}
576
df997f84
PS
577/**
578 * Returns list of courses user is enrolled into.
579 *
580 * - $fields is an array of fieldnames to ADD
581 * so name the fields you really need, which will
582 * be added and uniq'd
583 *
584 * @param int $userid
585 * @param bool $onlyactive return only active enrolments in courses user may see
586 * @param strin|array $fields
587 * @param string $sort
588 * @return array
589 */
590function enrol_get_users_courses($userid, $onlyactive = false, $fields = NULL, $sort = 'visible DESC,sortorder ASC') {
591 global $DB;
592
593 // Guest account does not have any courses
87163782 594 if (isguestuser($userid) or empty($userid)) {
df997f84
PS
595 return(array());
596 }
597
598 $basefields = array('id', 'category', 'sortorder',
599 'shortname', 'fullname', 'idnumber',
600 'startdate', 'visible',
601 'groupmode', 'groupmodeforce');
602
603 if (empty($fields)) {
604 $fields = $basefields;
605 } else if (is_string($fields)) {
606 // turn the fields from a string to an array
607 $fields = explode(',', $fields);
608 $fields = array_map('trim', $fields);
609 $fields = array_unique(array_merge($basefields, $fields));
610 } else if (is_array($fields)) {
611 $fields = array_unique(array_merge($basefields, $fields));
612 } else {
613 throw new coding_exception('Invalid $fileds parameter in enrol_get_my_courses()');
614 }
615 if (in_array('*', $fields)) {
616 $fields = array('*');
617 }
618
619 $orderby = "";
620 $sort = trim($sort);
621 if (!empty($sort)) {
622 $rawsorts = explode(',', $sort);
623 $sorts = array();
624 foreach ($rawsorts as $rawsort) {
625 $rawsort = trim($rawsort);
626 if (strpos($rawsort, 'c.') === 0) {
627 $rawsort = substr($rawsort, 2);
628 }
629 $sorts[] = trim($rawsort);
630 }
631 $sort = 'c.'.implode(',c.', $sorts);
632 $orderby = "ORDER BY $sort";
633 }
634
df997f84
PS
635 $params = array('siteid'=>SITEID);
636
637 if ($onlyactive) {
4129338c 638 $subwhere = "WHERE ue.status = :active AND e.status = :enabled AND ue.timestart < :now1 AND (ue.timeend = 0 OR ue.timeend > :now2)";
df997f84
PS
639 $params['now1'] = round(time(), -2); // improves db caching
640 $params['now2'] = $params['now1'];
641 $params['active'] = ENROL_USER_ACTIVE;
642 $params['enabled'] = ENROL_INSTANCE_ENABLED;
4129338c
PS
643 } else {
644 $subwhere = "";
df997f84
PS
645 }
646
647 $coursefields = 'c.' .join(',c.', $fields);
648 list($ccselect, $ccjoin) = context_instance_preload_sql('c.id', CONTEXT_COURSE, 'ctx');
df997f84 649
4129338c
PS
650 //note: we can not use DISTINCT + text fields due to Oracle and MS limitations, that is why we have the subselect there
651 $sql = "SELECT $coursefields $ccselect
df997f84 652 FROM {course} c
4129338c
PS
653 JOIN (SELECT DISTINCT e.courseid
654 FROM {enrol} e
655 JOIN {user_enrolments} ue ON (ue.enrolid = e.id AND ue.userid = :userid)
656 $subwhere
657 ) en ON (en.courseid = c.id)
df997f84 658 $ccjoin
4129338c 659 WHERE c.id <> :siteid
df997f84 660 $orderby";
87163782 661 $params['userid'] = $userid;
df997f84
PS
662
663 $courses = $DB->get_records_sql($sql, $params);
664
665 // preload contexts and check visibility
666 foreach ($courses as $id=>$course) {
667 context_instance_preload($course);
668 if ($onlyactive) {
669 if (!$course->visible) {
670 if (!$context = get_context_instance(CONTEXT_COURSE, $id)) {
4a5aba7c 671 unset($courses[$id]);
df997f84
PS
672 continue;
673 }
674 if (!has_capability('moodle/course:viewhiddencourses', $context, $userid)) {
4a5aba7c 675 unset($courses[$id]);
df997f84
PS
676 continue;
677 }
678 }
679 }
680 $courses[$id] = $course;
681 }
682
683 //wow! Is that really all? :-D
684
685 return $courses;
686
687}
688
689/**
690 * Called when user is about to be deleted.
691 * @param object $user
692 * @return void
693 */
694function enrol_user_delete($user) {
695 global $DB;
696
697 $plugins = enrol_get_plugins(true);
698 foreach ($plugins as $plugin) {
699 $plugin->user_delete($user);
700 }
701
702 // force cleanup of all broken enrolments
703 $DB->delete_records('user_enrolments', array('userid'=>$user->id));
704}
705
706/**
707 * Try to enrol user via default internal auth plugin.
708 *
709 * For now this is always using the manual enrol plugin...
710 *
711 * @param $courseid
712 * @param $userid
713 * @param $roleid
714 * @param $timestart
715 * @param $timeend
716 * @return bool success
717 */
718function enrol_try_internal_enrol($courseid, $userid, $roleid = null, $timestart = 0, $timeend = 0) {
719 global $DB;
720
721 //note: this is hardcoded to manual plugin for now
722
723 if (!enrol_is_enabled('manual')) {
724 return false;
725 }
726
727 if (!$enrol = enrol_get_plugin('manual')) {
728 return false;
729 }
730 if (!$instances = $DB->get_records('enrol', array('enrol'=>'manual', 'courseid'=>$courseid, 'status'=>ENROL_INSTANCE_ENABLED), 'sortorder,id ASC')) {
731 return false;
732 }
733 $instance = reset($instances);
734
735 $enrol->enrol_user($instance, $userid, $roleid, $timestart, $timeend);
736
737 return true;
738}
739
740/**
741 * All enrol plugins should be based on this class,
742 * this is also the main source of documentation.
743 */
744abstract class enrol_plugin {
745 protected $config = null;
746
747 /**
748 * Returns name of this enrol plugin
749 * @return string
750 */
751 public function get_name() {
bf423bb1 752 // second word in class is always enrol name, sorry, no fancy plugin names with _
df997f84
PS
753 $words = explode('_', get_class($this));
754 return $words[1];
755 }
756
757 /**
758 * Returns localised name of enrol instance
759 *
760 * @param object $instance (null is accepted too)
761 * @return string
762 */
763 public function get_instance_name($instance) {
764 if (empty($instance->name)) {
765 $enrol = $this->get_name();
766 return get_string('pluginname', 'enrol_'.$enrol);
767 } else {
54475ccb
PS
768 $context = get_context_instance(CONTEXT_COURSE, $instance->courseid);
769 return format_string($instance->name, true, array('context'=>$context));
df997f84
PS
770 }
771 }
772
bf423bb1
PS
773 /**
774 * Returns optional enrolment information icons.
775 *
776 * This is used in course list for quick overview of enrolment options.
777 *
778 * We are not using single instance parameter because sometimes
779 * we might want to prevent icon repetition when multiple instances
780 * of one type exist. One instance may also produce several icons.
781 *
782 * @param array $instances all enrol instances of this type in one course
783 * @return array of pix_icon
784 */
785 public function get_info_icons(array $instances) {
786 return array();
787 }
788
789 /**
790 * Returns optional enrolment instance description text.
791 *
792 * This is used in detailed course information.
793 *
794 *
795 * @param object $instance
796 * @return string short html text
797 */
798 public function get_description_text($instance) {
799 return null;
800 }
801
df997f84
PS
802 /**
803 * Makes sure config is loaded and cached.
804 * @return void
805 */
806 protected function load_config() {
807 if (!isset($this->config)) {
808 $name = $this->get_name();
809 if (!$config = get_config("enrol_$name")) {
810 $config = new object();
811 }
812 $this->config = $config;
813 }
814 }
815
816 /**
817 * Returns plugin config value
818 * @param string $name
819 * @param string $default value if config does not exist yet
820 * @return string value or default
821 */
822 public function get_config($name, $default = NULL) {
823 $this->load_config();
824 return isset($this->config->$name) ? $this->config->$name : $default;
825 }
826
827 /**
828 * Sets plugin config value
829 * @param string $name name of config
830 * @param string $value string config value, null means delete
831 * @return string value
832 */
833 public function set_config($name, $value) {
47811589 834 $pluginname = $this->get_name();
df997f84
PS
835 $this->load_config();
836 if ($value === NULL) {
837 unset($this->config->$name);
838 } else {
839 $this->config->$name = $value;
840 }
47811589 841 set_config($name, $value, "enrol_$pluginname");
df997f84
PS
842 }
843
844 /**
845 * Does this plugin assign protected roles are can they be manually removed?
846 * @return bool - false means anybody may tweak roles, it does not use itemid and component when assigning roles
847 */
848 public function roles_protected() {
849 return true;
850 }
851
91b99e80
PS
852 /**
853 * Does this plugin allow manual enrolments?
854 *
855 * @param stdClass $instance course enrol instance
856 * All plugins allowing this must implement 'enrol/xxx:enrol' capability
857 *
858 * @return bool - true means user with 'enrol/xxx:enrol' may enrol others freely, trues means nobody may add more enrolments manually
859 */
860 public function allow_enrol(stdClass $instance) {
861 return false;
862 }
863
df997f84
PS
864 /**
865 * Does this plugin allow manual unenrolments?
866 *
867 * @param stdClass $instance course enrol instance
91b99e80 868 * All plugins allowing this must implement 'enrol/xxx:unenrol' capability
df997f84 869 *
91b99e80 870 * @return bool - true means user with 'enrol/xxx:unenrol' may unenrol others freely, trues means nobody may touch user_enrolments
df997f84
PS
871 */
872 public function allow_unenrol(stdClass $instance) {
873 return false;
874 }
875
876 /**
877 * Does this plugin allow manual changes in user_enrolments table?
878 *
91b99e80 879 * All plugins allowing this must implement 'enrol/xxx:manage' capability
df997f84
PS
880 *
881 * @param stdClass $instance course enrol instance
882 * @return bool - true means it is possible to change enrol period and status in user_enrolments table
883 */
884 public function allow_manage(stdClass $instance) {
885 return false;
886 }
887
217d0397
PS
888 /**
889 * Does this plugin support some way to user to self enrol?
890 *
891 * @param stdClass $instance course enrol instance
892 *
893 * @return bool - true means show "Enrol me in this course" link in course UI
894 */
895 public function show_enrolme_link(stdClass $instance) {
896 return false;
897 }
898
df997f84
PS
899 /**
900 * Attempt to automatically enrol current user in course without any interaction,
901 * calling code has to make sure the plugin and instance are active.
902 *
903 * @param stdClass $instance course enrol instance
904 * @param stdClass $user record
905 * @return bool|int false means not enrolled, integer means timeend
906 */
907 public function try_autoenrol(stdClass $instance) {
908 global $USER;
909
910 return false;
911 }
912
913 /**
914 * Attempt to automatically gain temporary guest access to course,
915 * calling code has to make sure the plugin and instance are active.
916 *
917 * @param stdClass $instance course enrol instance
918 * @param stdClass $user record
919 * @return bool|int false means no guest access, integer means timeend
920 */
921 public function try_guestaccess(stdClass $instance) {
922 global $USER;
923
924 return false;
925 }
926
927 /**
928 * Enrol user into course via enrol instance.
929 *
930 * @param stdClass $instance
931 * @param int $userid
932 * @param int $roleid optional role id
2a6dcb72
PS
933 * @param int $timestart 0 means unknown
934 * @param int $timeend 0 means forever
df997f84
PS
935 * @return void
936 */
937 public function enrol_user(stdClass $instance, $userid, $roleid = null, $timestart = 0, $timeend = 0) {
938 global $DB, $USER, $CFG; // CFG necessary!!!
939
940 if ($instance->courseid == SITEID) {
941 throw new coding_exception('invalid attempt to enrol into frontpage course!');
942 }
943
944 $name = $this->get_name();
945 $courseid = $instance->courseid;
946
947 if ($instance->enrol !== $name) {
948 throw new coding_exception('invalid enrol instance!');
949 }
950 $context = get_context_instance(CONTEXT_COURSE, $instance->courseid, MUST_EXIST);
951
952 $inserted = false;
953 if ($ue = $DB->get_record('user_enrolments', array('enrolid'=>$instance->id, 'userid'=>$userid))) {
954 if ($ue->timestart != $timestart or $ue->timeend != $timeend) {
955 $ue->timestart = $timestart;
956 $ue->timeend = $timeend;
957 $ue->modifier = $USER->id;
958 $ue->timemodified = time();
959 $DB->update_record('user_enrolments', $ue);
960 }
961 } else {
962 $ue = new object();
963 $ue->enrolid = $instance->id;
964 $ue->status = ENROL_USER_ACTIVE;
965 $ue->userid = $userid;
966 $ue->timestart = $timestart;
967 $ue->timeend = $timeend;
968 $ue->modifier = $USER->id;
2a6dcb72
PS
969 $ue->timecreated = time();
970 $ue->timemodified = $ue->timecreated;
df997f84
PS
971 $ue->id = $DB->insert_record('user_enrolments', $ue);
972
973 $inserted = true;
974 }
975
976 if ($roleid) {
977 if ($this->roles_protected()) {
978 role_assign($roleid, $userid, $context->id, 'enrol_'.$name, $instance->id);
979 } else {
980 role_assign($roleid, $userid, $context->id);
981 }
982 }
983
984 if ($inserted) {
985 // add extra info and trigger event
986 $ue->courseid = $courseid;
987 $ue->enrol = $name;
988 events_trigger('user_enrolled', $ue);
989 }
990
991 // reset primitive require_login() caching
992 if ($userid == $USER->id) {
993 if (isset($USER->enrol['enrolled'][$courseid])) {
994 unset($USER->enrol['enrolled'][$courseid]);
995 }
996 if (isset($USER->enrol['tempguest'][$courseid])) {
997 unset($USER->enrol['tempguest'][$courseid]);
998 $USER->access = remove_temp_roles($context, $USER->access);
999 }
1000 }
1001 }
1002
1003 /**
1004 * Store user_enrolments changes and trigger event.
1005 *
1006 * @param object $ue
1007 * @param int $user id
1008 * @param int $status
1009 * @param int $timestart
1010 * @param int $timeend
1011 * @return void
1012 */
1013 public function update_user_enrol(stdClass $instance, $userid, $status = NULL, $timestart = NULL, $timeend = NULL) {
1014 global $DB, $USER;
1015
1016 $name = $this->get_name();
1017
1018 if ($instance->enrol !== $name) {
1019 throw new coding_exception('invalid enrol instance!');
1020 }
1021
1022 if (!$ue = $DB->get_record('user_enrolments', array('enrolid'=>$instance->id, 'userid'=>$userid))) {
1023 // weird, user not enrolled
1024 return;
1025 }
1026
1027 $modified = false;
1028 if (isset($status) and $ue->status != $status) {
1029 $ue->status = $status;
1030 $modified = true;
1031 }
1032 if (isset($timestart) and $ue->timestart != $timestart) {
1033 $ue->timestart = $timestart;
1034 $modified = true;
1035 }
1036 if (isset($timeend) and $ue->timeend != $timeend) {
1037 $ue->timeend = $timeend;
1038 $modified = true;
1039 }
1040
1041 if (!$modified) {
1042 // no change
1043 return;
1044 }
1045
1046 $ue->modifierid = $USER->id;
1047 $DB->update_record('user_enrolments', $ue);
1048
1049 // trigger event
1050 $ue->courseid = $instance->courseid;
1051 $ue->enrol = $instance->name;
1052 events_trigger('user_unenrol_modified', $ue);
1053 }
1054
1055 /**
1056 * Unenrol user from course,
1057 * the last unenrolment removes all remaining roles.
1058 *
1059 * @param stdClass $instance
1060 * @param int $userid
1061 * @return void
1062 */
1063 public function unenrol_user(stdClass $instance, $userid) {
1064 global $CFG, $USER, $DB;
1065
1066 $name = $this->get_name();
1067 $courseid = $instance->courseid;
1068
1069 if ($instance->enrol !== $name) {
1070 throw new coding_exception('invalid enrol instance!');
1071 }
1072 $context = get_context_instance(CONTEXT_COURSE, $instance->courseid, MUST_EXIST);
1073
1074 if (!$ue = $DB->get_record('user_enrolments', array('enrolid'=>$instance->id, 'userid'=>$userid))) {
1075 // weird, user not enrolled
1076 return;
1077 }
1078
1079 role_unassign_all(array('userid'=>$userid, 'contextid'=>$context->id, 'component'=>'enrol_'.$name, 'itemid'=>$instance->id));
1080 $DB->delete_records('user_enrolments', array('id'=>$ue->id));
1081
1082 // add extra info and trigger event
1083 $ue->courseid = $courseid;
1084 $ue->enrol = $name;
1085
1086 $sql = "SELECT 'x'
1087 FROM {user_enrolments} ue
1088 JOIN {enrol} e ON (e.id = ue.enrolid)
1089 WHERE ue.userid = :userid AND e.courseid = :courseid";
1090 if ($DB->record_exists_sql($sql, array('userid'=>$userid, 'courseid'=>$courseid))) {
1091 $ue->lastenrol = false;
1092 events_trigger('user_unenrolled', $ue);
1093 // user still has some enrolments, no big cleanup yet
1094 } else {
1095 // the big cleanup IS necessary!
1096
1097 require_once("$CFG->dirroot/group/lib.php");
1098 require_once("$CFG->libdir/gradelib.php");
1099
1100 // remove all remaining roles
1101 role_unassign_all(array('userid'=>$userid, 'contextid'=>$context->id), true, false);
1102
1103 //clean up ALL invisible user data from course if this is the last enrolment - groups, grades, etc.
1104 groups_delete_group_members($courseid, $userid);
1105
1106 grade_user_unenrol($courseid, $userid);
1107
1108 $DB->delete_records('user_lastaccess', array('userid'=>$userid, 'courseid'=>$courseid));
1109
7d2800fb 1110 $ue->lastenrol = true; // means user not enrolled any more
df997f84
PS
1111 events_trigger('user_unenrolled', $ue);
1112 }
1113 // reset primitive require_login() caching
1114 if ($userid == $USER->id) {
1115 if (isset($USER->enrol['enrolled'][$courseid])) {
1116 unset($USER->enrol['enrolled'][$courseid]);
1117 }
1118 if (isset($USER->enrol['tempguest'][$courseid])) {
1119 unset($USER->enrol['tempguest'][$courseid]);
1120 $USER->access = remove_temp_roles($context, $USER->access);
1121 }
1122 }
1123 }
1124
1125 /**
1126 * Forces synchronisation of user enrolments.
1127 *
1128 * This is important especially for external enrol plugins,
1129 * this function is called for all enabled enrol plugins
1130 * right after every user login.
1131 *
1132 * @param object $user user record
1133 * @return void
1134 */
1135 public function sync_user_enrolments($user) {
1136 // override if necessary
1137 }
1138
1139 /**
1140 * Returns link to page which may be used to add new instance of enrolment plugin in course.
1141 * @param int $courseid
1142 * @return moodle_url page url
1143 */
e25f2466 1144 public function get_newinstance_link($courseid) {
df997f84
PS
1145 // override for most plugins, check if instance already exists in cases only one instance is supported
1146 return NULL;
1147 }
1148
1149 /**
1150 * Is it possible to delete enrol instance via standard UI?
1151 *
1152 * @param object $instance
1153 * @return bool
1154 */
1155 public function instance_deleteable($instance) {
1156 return true;
1157 }
1158
1159 /**
1160 * Returns link to manual enrol UI if exists.
1161 * Does the access control tests automatically.
1162 *
1163 * @param object $instance
1164 * @return moodle_url
1165 */
1166 public function get_manual_enrol_link($instance) {
1167 return NULL;
1168 }
1169
1170 /**
1171 * Returns list of unenrol links for all enrol instances in course.
1172 *
217d0397 1173 * @param int $instance
bf423bb1 1174 * @return moodle_url or NULL if self unenrolment not supported
df997f84
PS
1175 */
1176 public function get_unenrolself_link($instance) {
1177 global $USER, $CFG, $DB;
1178
1179 $name = $this->get_name();
1180 if ($instance->enrol !== $name) {
1181 throw new coding_exception('invalid enrol instance!');
1182 }
1183
1184 if ($instance->courseid == SITEID) {
1185 return NULL;
1186 }
1187
1188 if (!enrol_is_enabled($name)) {
1189 return NULL;
1190 }
1191
1192 if ($instance->status != ENROL_INSTANCE_ENABLED) {
1193 return NULL;
1194 }
1195
df997f84
PS
1196 if (!file_exists("$CFG->dirroot/enrol/$name/unenrolself.php")) {
1197 return NULL;
1198 }
1199
217d0397
PS
1200 $context = get_context_instance(CONTEXT_COURSE, $instance->courseid, MUST_EXIST);
1201
df997f84
PS
1202 if (!has_capability("enrol/$name:unenrolself", $context)) {
1203 return NULL;
1204 }
1205
1206 if (!$DB->record_exists('user_enrolments', array('enrolid'=>$instance->id, 'userid'=>$USER->id, 'status'=>ENROL_USER_ACTIVE))) {
1207 return NULL;
1208 }
1209
1210 return new moodle_url("/enrol/$name/unenrolself.php", array('enrolid'=>$instance->id));;
1211 }
1212
1213 /**
1214 * Adds enrol instance UI to course edit form
1215 *
1216 * @param object $instance enrol instance or null if does not exist yet
1217 * @param MoodleQuickForm $mform
1218 * @param object $data
1219 * @param object $context context of existing course or parent category if course does not exist
1220 * @return void
1221 */
1222 public function course_edit_form($instance, MoodleQuickForm $mform, $data, $context) {
1223 // override - usually at least enable/disable switch, has to add own form header
1224 }
1225
1226 /**
1227 * Validates course edit form data
1228 *
1229 * @param object $instance enrol instance or null if does not exist yet
1230 * @param array $data
1231 * @param object $context context of existing course or parent category if course does not exist
1232 * @return array errors array
1233 */
1234 public function course_edit_validation($instance, array $data, $context) {
1235 return array();
1236 }
1237
1238 /**
1239 * Called after updating/inserting course.
1240 *
1241 * @param bool $inserted true if course just inserted
1242 * @param object $course
1243 * @param object $data form data
1244 * @return void
1245 */
1246 public function course_updated($inserted, $course, $data) {
eafb7a72
PS
1247 if ($inserted) {
1248 if ($this->get_config('defaultenrol')) {
1249 $this->add_default_instance($course);
1250 }
1251 }
df997f84
PS
1252 }
1253
1254 /**
eafb7a72 1255 * Add new instance of enrol plugin.
df997f84
PS
1256 * @param object $course
1257 * @param array instance fields
0848a196 1258 * @return int id of new instance, null if can not be created
df997f84
PS
1259 */
1260 public function add_instance($course, array $fields = NULL) {
1261 global $DB;
1262
1263 if ($course->id == SITEID) {
1264 throw new coding_exception('Invalid request to add enrol instance to frontpage.');
1265 }
1266
1267 $instance = new object();
1268 $instance->enrol = $this->get_name();
1269 $instance->status = ENROL_INSTANCE_ENABLED;
1270 $instance->courseid = $course->id;
1271 $instance->enrolstartdate = 0;
1272 $instance->enrolenddate = 0;
1273 $instance->timemodified = time();
1274 $instance->timecreated = $instance->timemodified;
1275 $instance->sortorder = $DB->get_field('enrol', 'COALESCE(MAX(sortorder), -1) + 1', array('courseid'=>$course->id));
1276
1277 $fields = (array)$fields;
1278 unset($fields['enrol']);
1279 unset($fields['courseid']);
1280 unset($fields['sortorder']);
1281 foreach($fields as $field=>$value) {
1282 $instance->$field = $value;
1283 }
1284
1285 return $DB->insert_record('enrol', $instance);
1286 }
1287
1288 /**
1289 * Add new instance of enrol plugin with default settings,
1290 * called when adding new instance manually or when adding new course.
1291 *
1292 * Not all plugins support this.
1293 *
1294 * @param object $course
1295 * @return int id of new instance or null if no default supported
1296 */
1297 public function add_default_instance($course) {
1298 return null;
1299 }
1300
1301 /**
1302 * Delete course enrol plugin instance, unenrol all users.
1303 * @param object $instance
1304 * @return void
1305 */
1306 public function delete_instance($instance) {
1307 global $DB;
1308
1309 $name = $this->get_name();
1310 if ($instance->enrol !== $name) {
1311 throw new coding_exception('invalid enrol instance!');
1312 }
1313
1314 //first unenrol all users
1315 $participants = $DB->get_recordset('user_enrolments', array('enrolid'=>$instance->id));
1316 foreach ($participants as $participant) {
1317 $this->unenrol_user($instance, $participant->userid);
1318 }
1319 $participants->close();
1320
1321 // now clean up all remainders that were not removed correctly
1322 $DB->delete_records('role_assignments', array('itemid'=>$instance->id, 'component'=>$name));
1323 $DB->delete_records('user_enrolments', array('enrolid'=>$instance->id));
1324
1325 // finally drop the enrol row
1326 $DB->delete_records('enrol', array('id'=>$instance->id));
1327 }
1328
1329 /**
1330 * Creates course enrol form, checks if form submitted
1331 * and enrols user if necessary. It can also redirect.
1332 *
1333 * @param stdClass $instance
1334 * @return string html text, usually a form in a text box
1335 */
1336 public function enrol_page_hook(stdClass $instance) {
1337 return null;
1338 }
1339
1340 /**
1341 * Adds navigation links into course admin block.
1342 *
1343 * By defaults looks for manage links only.
1344 *
1345 * @param navigation_node $instancesnode
1346 * @param object $instance
2d4b1f3e 1347 * @return void
df997f84
PS
1348 */
1349 public function add_course_navigation($instancesnode, stdClass $instance) {
2d4b1f3e 1350 // usually adds manage users
df997f84
PS
1351 }
1352
1353 /**
2d4b1f3e
PS
1354 * Returns edit icons for the page with list of instances
1355 * @param stdClass $instance
1356 * @return array
df997f84 1357 */
2d4b1f3e
PS
1358 public function get_action_icons(stdClass $instance) {
1359 return array();
df997f84
PS
1360 }
1361
1362 /**
1363 * Reads version.php and determines if it is necessary
1364 * to execute the cron job now.
1365 * @return bool
1366 */
1367 public function is_cron_required() {
1368 global $CFG;
1369
1370 $name = $this->get_name();
1371 $versionfile = "$CFG->dirroot/enrol/$name/version.php";
1372 $plugin = new object();
1373 include($versionfile);
1374 if (empty($plugin->cron)) {
1375 return false;
1376 }
1377 $lastexecuted = $this->get_config('lastcron', 0);
1378 if ($lastexecuted + $plugin->cron < time()) {
1379 return true;
1380 } else {
1381 return false;
1382 }
1383 }
1384
1385 /**
1386 * Called for all enabled enrol plugins that returned true from is_cron_required().
1387 * @return void
1388 */
1389 public function cron() {
1390 }
1391
1392 /**
1393 * Called when user is about to be deleted
1394 * @param object $user
1395 * @return void
1396 */
1397 public function user_delete($user) {
1398 global $DB;
1399
1400 $sql = "SELECT e.*
1401 FROM {enrol} e
1402 JOIN {user_enrolments} ue ON (ue.courseid = e.courseid)
1403 WHERE e.enrol = :meta AND ue.userid = :userid";
1404 $params = array('name'=>$this->get_name(), 'userid'=>$user->id);
1405
1406 $rs = $DB->get_records_recordset($sql, $params);
1407 foreach($rs as $instance) {
1408 $this->unenrol_user($instance, $user->id);
1409 }
1410 $rs->close();
1411 }
1412}
1413