"MDL-23822, fixed return url of private files manager"
[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) {
293
294 $coursecontext = get_context_instance(CONTEXT_COURSE, $course->id);
295
296 $instances = enrol_get_instances($course->id, true);
297 $plugins = enrol_get_plugins(true);
298
299 // we do not want to break all course pages if there is some borked enrol plugin, right?
300 foreach ($instances as $k=>$instance) {
301 if (!isset($plugins[$instance->enrol])) {
302 unset($instances[$k]);
303 }
304 }
305
f5ce6b71 306 $usersnode = $coursenode->add(get_string('users'), null, navigation_node::TYPE_CONTAINER, null, 'users');
df997f84
PS
307
308 if ($course->id != SITEID) {
f5ce6b71 309 // list all participants - allows assigning roles, groups, etc.
df997f84
PS
310 if (has_capability('moodle/course:enrolreview', $coursecontext)) {
311 $url = new moodle_url('/enrol/users.php', array('id'=>$course->id));
f5ce6b71 312 $usersnode->add(get_string('enrolledusers', 'enrol'), $url, navigation_node::TYPE_SETTING, null, 'review', new pix_icon('i/users', ''));
df997f84
PS
313 }
314
315 // manage enrol plugin instances
316 if (has_capability('moodle/course:enrolconfig', $coursecontext) or has_capability('moodle/course:enrolreview', $coursecontext)) {
317 $url = new moodle_url('/enrol/instances.php', array('id'=>$course->id));
318 } else {
319 $url = NULL;
320 }
f5ce6b71 321 $instancesnode = $usersnode->add(get_string('enrolmentinstances', 'enrol'), $url, navigation_node::TYPE_SETTING, null, 'manageinstances');
df997f84
PS
322
323 // each instance decides how to configure itself or how many other nav items are exposed
324 foreach ($instances as $instance) {
325 if (!isset($plugins[$instance->enrol])) {
326 continue;
327 }
328 $plugins[$instance->enrol]->add_course_navigation($instancesnode, $instance);
329 }
330
331 if (!$url) {
332 $instancesnode->trim_if_empty();
333 }
334 }
335
336 // Manage groups in this course or even frontpage
337 if (($course->groupmode || !$course->groupmodeforce) && has_capability('moodle/course:managegroups', $coursecontext)) {
338 $url = new moodle_url('/group/index.php', array('id'=>$course->id));
339 $usersnode->add(get_string('groups'), $url, navigation_node::TYPE_SETTING, null, 'groups', new pix_icon('i/group', ''));
340 }
341
342 if (has_any_capability(array( 'moodle/role:assign', 'moodle/role:safeoverride','moodle/role:override', 'moodle/role:review'), $coursecontext)) {
343 // Override roles
344 if (has_capability('moodle/role:review', $coursecontext)) {
345 $url = new moodle_url('/admin/roles/permissions.php', array('contextid'=>$coursecontext->id));
346 } else {
347 $url = NULL;
348 }
f5ce6b71 349 $permissionsnode = $usersnode->add(get_string('permissions', 'role'), $url, navigation_node::TYPE_SETTING, null, 'override');
df997f84
PS
350
351 // Add assign or override roles if allowed
352 if ($course->id == SITEID or (!empty($CFG->adminsassignrolesincourse) and is_siteadmin())) {
353 if (has_capability('moodle/role:assign', $coursecontext)) {
354 $url = new moodle_url('/admin/roles/assign.php', array('contextid'=>$coursecontext->id));
f5ce6b71 355 $permissionsnode->add(get_string('assignedroles', 'role'), $url, navigation_node::TYPE_SETTING, null, 'roles', new pix_icon('i/roles', ''));
df997f84
PS
356 }
357 }
358 // Check role permissions
359 if (has_any_capability(array('moodle/role:assign', 'moodle/role:safeoverride','moodle/role:override', 'moodle/role:assign'), $coursecontext)) {
360 $url = new moodle_url('/admin/roles/check.php', array('contextid'=>$coursecontext->id));
f5ce6b71 361 $permissionsnode->add(get_string('checkpermissions', 'role'), $url, navigation_node::TYPE_SETTING, null, 'permissions', new pix_icon('i/checkpermissions', ''));
df997f84
PS
362 }
363 }
364
365 // Deal somehow with users that are not enrolled but still got a role somehow
366 if ($course->id != SITEID) {
367 //TODO, create some new UI for role assignments at course level
368 if (has_capability('moodle/role:assign', $coursecontext)) {
369 $url = new moodle_url('/enrol/otherusers.php', array('id'=>$course->id));
f5ce6b71 370 $usersnode->add(get_string('notenrolledusers', 'enrol'), $url, navigation_node::TYPE_SETTING, null, 'otherusers', new pix_icon('i/roles', ''));
df997f84
PS
371 }
372 }
373
374 // just in case nothing was actually added
375 $usersnode->trim_if_empty();
376
377 if ($course->id != SITEID) {
378 // Unenrol link
217d0397
PS
379 if (is_enrolled($coursecontext)) {
380 foreach ($instances as $instance) {
381 if (!isset($plugins[$instance->enrol])) {
382 continue;
383 }
384 $plugin = $plugins[$instance->enrol];
385 if ($unenrollink = $plugin->get_unenrolself_link($instance)) {
386 $coursenode->add(get_string('unenrolme', 'core_enrol', format_string($course->shortname)), $unenrollink, navigation_node::TYPE_SETTING, null, 'unenrolself', new pix_icon('i/user', ''));
387 break;
388 //TODO. deal with multiple unenrol links - not likely case, but still...
389 }
df997f84 390 }
217d0397
PS
391 } else {
392 if (is_viewing($coursecontext)) {
393 // better not show any enrol link, this is intended for managers and inspectors
394 } else {
395 foreach ($instances as $instance) {
396 if (!isset($plugins[$instance->enrol])) {
397 continue;
398 }
399 $plugin = $plugins[$instance->enrol];
400 if ($plugin->show_enrolme_link($instance)) {
401 $url = new moodle_url('/enrol/index.php', array('id'=>$course->id));
402 $coursenode->add(get_string('enrolme', 'core_enrol', format_string($course->shortname)), $url, navigation_node::TYPE_SETTING, null, 'enrolself', new pix_icon('i/user', ''));
403 break;
404 }
405 }
df997f84
PS
406 }
407 }
df997f84
PS
408 }
409}
410
411/**
412 * Returns list of courses current $USER is enrolled in and can access
413 *
414 * - $fields is an array of field names to ADD
415 * so name the fields you really need, which will
416 * be added and uniq'd
417 *
418 * @param strin|array $fields
419 * @param string $sort
420 * @param int $limit max number of courses
421 * @return array
422 */
423function enrol_get_my_courses($fields = NULL, $sort = 'visible DESC,sortorder ASC', $limit = 0) {
424 global $DB, $USER;
425
426 // Guest account does not have any courses
427 if (isguestuser() or !isloggedin()) {
428 return(array());
429 }
430
431 $basefields = array('id', 'category', 'sortorder',
432 'shortname', 'fullname', 'idnumber',
433 'startdate', 'visible',
434 'groupmode', 'groupmodeforce');
435
436 if (empty($fields)) {
437 $fields = $basefields;
438 } else if (is_string($fields)) {
439 // turn the fields from a string to an array
440 $fields = explode(',', $fields);
441 $fields = array_map('trim', $fields);
442 $fields = array_unique(array_merge($basefields, $fields));
443 } else if (is_array($fields)) {
444 $fields = array_unique(array_merge($basefields, $fields));
445 } else {
446 throw new coding_exception('Invalid $fileds parameter in enrol_get_my_courses()');
447 }
448 if (in_array('*', $fields)) {
449 $fields = array('*');
450 }
451
452 $orderby = "";
453 $sort = trim($sort);
454 if (!empty($sort)) {
455 $rawsorts = explode(',', $sort);
456 $sorts = array();
457 foreach ($rawsorts as $rawsort) {
458 $rawsort = trim($rawsort);
459 if (strpos($rawsort, 'c.') === 0) {
460 $rawsort = substr($rawsort, 2);
461 }
462 $sorts[] = trim($rawsort);
463 }
464 $sort = 'c.'.implode(',c.', $sorts);
465 $orderby = "ORDER BY $sort";
466 }
467
468 $wheres = array("c.id <> :siteid");
469 $params = array('siteid'=>SITEID);
470
471 if (isset($USER->loginascontext) and $USER->loginascontext->contextlevel == CONTEXT_COURSE) {
472 // list _only_ this course - anything else is asking for trouble...
473 $wheres[] = "courseid = :loginas";
474 $params['loginas'] = $USER->loginascontext->instanceid;
475 }
476
477 $coursefields = 'c.' .join(',c.', $fields);
478 list($ccselect, $ccjoin) = context_instance_preload_sql('c.id', CONTEXT_COURSE, 'ctx');
4129338c 479 $wheres = implode(" AND ", $wheres);
df997f84 480
4129338c
PS
481 //note: we can not use DISTINCT + text fields due to Oracle and MS limitations, that is why we have the subselect there
482 $sql = "SELECT $coursefields $ccselect
df997f84 483 FROM {course} c
4129338c
PS
484 JOIN (SELECT DISTINCT e.courseid
485 FROM {enrol} e
486 JOIN {user_enrolments} ue ON (ue.enrolid = e.id AND ue.userid = :userid)
487 WHERE ue.status = :active AND e.status = :enabled AND ue.timestart < :now1 AND (ue.timeend = 0 OR ue.timeend > :now2)
488 ) en ON (en.courseid = c.id)
df997f84 489 $ccjoin
4129338c 490 WHERE $wheres
df997f84
PS
491 $orderby";
492 $params['userid'] = $USER->id;
493 $params['active'] = ENROL_USER_ACTIVE;
494 $params['enabled'] = ENROL_INSTANCE_ENABLED;
495 $params['now1'] = round(time(), -2); // improves db caching
496 $params['now2'] = $params['now1'];
497
498 $courses = $DB->get_records_sql($sql, $params, 0, $limit);
499
500 // preload contexts and check visibility
501 foreach ($courses as $id=>$course) {
502 context_instance_preload($course);
503 if (!$course->visible) {
504 if (!$context = get_context_instance(CONTEXT_COURSE, $id)) {
55880bdd 505 unset($courses[$id]);
df997f84
PS
506 continue;
507 }
508 if (!has_capability('moodle/course:viewhiddencourses', $context)) {
55880bdd 509 unset($courses[$id]);
df997f84
PS
510 continue;
511 }
512 }
513 $courses[$id] = $course;
514 }
515
516 //wow! Is that really all? :-D
517
518 return $courses;
519}
520
521/**
522 * Returns list of courses user is enrolled into.
523 *
524 * - $fields is an array of fieldnames to ADD
525 * so name the fields you really need, which will
526 * be added and uniq'd
527 *
528 * @param int $userid
529 * @param bool $onlyactive return only active enrolments in courses user may see
530 * @param strin|array $fields
531 * @param string $sort
532 * @return array
533 */
534function enrol_get_users_courses($userid, $onlyactive = false, $fields = NULL, $sort = 'visible DESC,sortorder ASC') {
535 global $DB;
536
537 // Guest account does not have any courses
87163782 538 if (isguestuser($userid) or empty($userid)) {
df997f84
PS
539 return(array());
540 }
541
542 $basefields = array('id', 'category', 'sortorder',
543 'shortname', 'fullname', 'idnumber',
544 'startdate', 'visible',
545 'groupmode', 'groupmodeforce');
546
547 if (empty($fields)) {
548 $fields = $basefields;
549 } else if (is_string($fields)) {
550 // turn the fields from a string to an array
551 $fields = explode(',', $fields);
552 $fields = array_map('trim', $fields);
553 $fields = array_unique(array_merge($basefields, $fields));
554 } else if (is_array($fields)) {
555 $fields = array_unique(array_merge($basefields, $fields));
556 } else {
557 throw new coding_exception('Invalid $fileds parameter in enrol_get_my_courses()');
558 }
559 if (in_array('*', $fields)) {
560 $fields = array('*');
561 }
562
563 $orderby = "";
564 $sort = trim($sort);
565 if (!empty($sort)) {
566 $rawsorts = explode(',', $sort);
567 $sorts = array();
568 foreach ($rawsorts as $rawsort) {
569 $rawsort = trim($rawsort);
570 if (strpos($rawsort, 'c.') === 0) {
571 $rawsort = substr($rawsort, 2);
572 }
573 $sorts[] = trim($rawsort);
574 }
575 $sort = 'c.'.implode(',c.', $sorts);
576 $orderby = "ORDER BY $sort";
577 }
578
df997f84
PS
579 $params = array('siteid'=>SITEID);
580
581 if ($onlyactive) {
4129338c 582 $subwhere = "WHERE ue.status = :active AND e.status = :enabled AND ue.timestart < :now1 AND (ue.timeend = 0 OR ue.timeend > :now2)";
df997f84
PS
583 $params['now1'] = round(time(), -2); // improves db caching
584 $params['now2'] = $params['now1'];
585 $params['active'] = ENROL_USER_ACTIVE;
586 $params['enabled'] = ENROL_INSTANCE_ENABLED;
4129338c
PS
587 } else {
588 $subwhere = "";
df997f84
PS
589 }
590
591 $coursefields = 'c.' .join(',c.', $fields);
592 list($ccselect, $ccjoin) = context_instance_preload_sql('c.id', CONTEXT_COURSE, 'ctx');
df997f84 593
4129338c
PS
594 //note: we can not use DISTINCT + text fields due to Oracle and MS limitations, that is why we have the subselect there
595 $sql = "SELECT $coursefields $ccselect
df997f84 596 FROM {course} c
4129338c
PS
597 JOIN (SELECT DISTINCT e.courseid
598 FROM {enrol} e
599 JOIN {user_enrolments} ue ON (ue.enrolid = e.id AND ue.userid = :userid)
600 $subwhere
601 ) en ON (en.courseid = c.id)
df997f84 602 $ccjoin
4129338c 603 WHERE c.id <> :siteid
df997f84 604 $orderby";
87163782 605 $params['userid'] = $userid;
df997f84
PS
606
607 $courses = $DB->get_records_sql($sql, $params);
608
609 // preload contexts and check visibility
610 foreach ($courses as $id=>$course) {
611 context_instance_preload($course);
612 if ($onlyactive) {
613 if (!$course->visible) {
614 if (!$context = get_context_instance(CONTEXT_COURSE, $id)) {
4a5aba7c 615 unset($courses[$id]);
df997f84
PS
616 continue;
617 }
618 if (!has_capability('moodle/course:viewhiddencourses', $context, $userid)) {
4a5aba7c 619 unset($courses[$id]);
df997f84
PS
620 continue;
621 }
622 }
623 }
624 $courses[$id] = $course;
625 }
626
627 //wow! Is that really all? :-D
628
629 return $courses;
630
631}
632
633/**
634 * Called when user is about to be deleted.
635 * @param object $user
636 * @return void
637 */
638function enrol_user_delete($user) {
639 global $DB;
640
641 $plugins = enrol_get_plugins(true);
642 foreach ($plugins as $plugin) {
643 $plugin->user_delete($user);
644 }
645
646 // force cleanup of all broken enrolments
647 $DB->delete_records('user_enrolments', array('userid'=>$user->id));
648}
649
650/**
651 * Try to enrol user via default internal auth plugin.
652 *
653 * For now this is always using the manual enrol plugin...
654 *
655 * @param $courseid
656 * @param $userid
657 * @param $roleid
658 * @param $timestart
659 * @param $timeend
660 * @return bool success
661 */
662function enrol_try_internal_enrol($courseid, $userid, $roleid = null, $timestart = 0, $timeend = 0) {
663 global $DB;
664
665 //note: this is hardcoded to manual plugin for now
666
667 if (!enrol_is_enabled('manual')) {
668 return false;
669 }
670
671 if (!$enrol = enrol_get_plugin('manual')) {
672 return false;
673 }
674 if (!$instances = $DB->get_records('enrol', array('enrol'=>'manual', 'courseid'=>$courseid, 'status'=>ENROL_INSTANCE_ENABLED), 'sortorder,id ASC')) {
675 return false;
676 }
677 $instance = reset($instances);
678
679 $enrol->enrol_user($instance, $userid, $roleid, $timestart, $timeend);
680
681 return true;
682}
683
684/**
685 * All enrol plugins should be based on this class,
686 * this is also the main source of documentation.
687 */
688abstract class enrol_plugin {
689 protected $config = null;
690
691 /**
692 * Returns name of this enrol plugin
693 * @return string
694 */
695 public function get_name() {
696 // second word in class is always enrol name
697 $words = explode('_', get_class($this));
698 return $words[1];
699 }
700
701 /**
702 * Returns localised name of enrol instance
703 *
704 * @param object $instance (null is accepted too)
705 * @return string
706 */
707 public function get_instance_name($instance) {
708 if (empty($instance->name)) {
709 $enrol = $this->get_name();
710 return get_string('pluginname', 'enrol_'.$enrol);
711 } else {
54475ccb
PS
712 $context = get_context_instance(CONTEXT_COURSE, $instance->courseid);
713 return format_string($instance->name, true, array('context'=>$context));
df997f84
PS
714 }
715 }
716
717 /**
718 * Makes sure config is loaded and cached.
719 * @return void
720 */
721 protected function load_config() {
722 if (!isset($this->config)) {
723 $name = $this->get_name();
724 if (!$config = get_config("enrol_$name")) {
725 $config = new object();
726 }
727 $this->config = $config;
728 }
729 }
730
731 /**
732 * Returns plugin config value
733 * @param string $name
734 * @param string $default value if config does not exist yet
735 * @return string value or default
736 */
737 public function get_config($name, $default = NULL) {
738 $this->load_config();
739 return isset($this->config->$name) ? $this->config->$name : $default;
740 }
741
742 /**
743 * Sets plugin config value
744 * @param string $name name of config
745 * @param string $value string config value, null means delete
746 * @return string value
747 */
748 public function set_config($name, $value) {
47811589 749 $pluginname = $this->get_name();
df997f84
PS
750 $this->load_config();
751 if ($value === NULL) {
752 unset($this->config->$name);
753 } else {
754 $this->config->$name = $value;
755 }
47811589 756 set_config($name, $value, "enrol_$pluginname");
df997f84
PS
757 }
758
759 /**
760 * Does this plugin assign protected roles are can they be manually removed?
761 * @return bool - false means anybody may tweak roles, it does not use itemid and component when assigning roles
762 */
763 public function roles_protected() {
764 return true;
765 }
766
91b99e80
PS
767 /**
768 * Does this plugin allow manual enrolments?
769 *
770 * @param stdClass $instance course enrol instance
771 * All plugins allowing this must implement 'enrol/xxx:enrol' capability
772 *
773 * @return bool - true means user with 'enrol/xxx:enrol' may enrol others freely, trues means nobody may add more enrolments manually
774 */
775 public function allow_enrol(stdClass $instance) {
776 return false;
777 }
778
df997f84
PS
779 /**
780 * Does this plugin allow manual unenrolments?
781 *
782 * @param stdClass $instance course enrol instance
91b99e80 783 * All plugins allowing this must implement 'enrol/xxx:unenrol' capability
df997f84 784 *
91b99e80 785 * @return bool - true means user with 'enrol/xxx:unenrol' may unenrol others freely, trues means nobody may touch user_enrolments
df997f84
PS
786 */
787 public function allow_unenrol(stdClass $instance) {
788 return false;
789 }
790
791 /**
792 * Does this plugin allow manual changes in user_enrolments table?
793 *
91b99e80 794 * All plugins allowing this must implement 'enrol/xxx:manage' capability
df997f84
PS
795 *
796 * @param stdClass $instance course enrol instance
797 * @return bool - true means it is possible to change enrol period and status in user_enrolments table
798 */
799 public function allow_manage(stdClass $instance) {
800 return false;
801 }
802
217d0397
PS
803 /**
804 * Does this plugin support some way to user to self enrol?
805 *
806 * @param stdClass $instance course enrol instance
807 *
808 * @return bool - true means show "Enrol me in this course" link in course UI
809 */
810 public function show_enrolme_link(stdClass $instance) {
811 return false;
812 }
813
df997f84
PS
814 /**
815 * Attempt to automatically enrol current user in course without any interaction,
816 * calling code has to make sure the plugin and instance are active.
817 *
818 * @param stdClass $instance course enrol instance
819 * @param stdClass $user record
820 * @return bool|int false means not enrolled, integer means timeend
821 */
822 public function try_autoenrol(stdClass $instance) {
823 global $USER;
824
825 return false;
826 }
827
828 /**
829 * Attempt to automatically gain temporary guest access to course,
830 * calling code has to make sure the plugin and instance are active.
831 *
832 * @param stdClass $instance course enrol instance
833 * @param stdClass $user record
834 * @return bool|int false means no guest access, integer means timeend
835 */
836 public function try_guestaccess(stdClass $instance) {
837 global $USER;
838
839 return false;
840 }
841
842 /**
843 * Enrol user into course via enrol instance.
844 *
845 * @param stdClass $instance
846 * @param int $userid
847 * @param int $roleid optional role id
2a6dcb72
PS
848 * @param int $timestart 0 means unknown
849 * @param int $timeend 0 means forever
df997f84
PS
850 * @return void
851 */
852 public function enrol_user(stdClass $instance, $userid, $roleid = null, $timestart = 0, $timeend = 0) {
853 global $DB, $USER, $CFG; // CFG necessary!!!
854
855 if ($instance->courseid == SITEID) {
856 throw new coding_exception('invalid attempt to enrol into frontpage course!');
857 }
858
859 $name = $this->get_name();
860 $courseid = $instance->courseid;
861
862 if ($instance->enrol !== $name) {
863 throw new coding_exception('invalid enrol instance!');
864 }
865 $context = get_context_instance(CONTEXT_COURSE, $instance->courseid, MUST_EXIST);
866
867 $inserted = false;
868 if ($ue = $DB->get_record('user_enrolments', array('enrolid'=>$instance->id, 'userid'=>$userid))) {
869 if ($ue->timestart != $timestart or $ue->timeend != $timeend) {
870 $ue->timestart = $timestart;
871 $ue->timeend = $timeend;
872 $ue->modifier = $USER->id;
873 $ue->timemodified = time();
874 $DB->update_record('user_enrolments', $ue);
875 }
876 } else {
877 $ue = new object();
878 $ue->enrolid = $instance->id;
879 $ue->status = ENROL_USER_ACTIVE;
880 $ue->userid = $userid;
881 $ue->timestart = $timestart;
882 $ue->timeend = $timeend;
883 $ue->modifier = $USER->id;
2a6dcb72
PS
884 $ue->timecreated = time();
885 $ue->timemodified = $ue->timecreated;
df997f84
PS
886 $ue->id = $DB->insert_record('user_enrolments', $ue);
887
888 $inserted = true;
889 }
890
891 if ($roleid) {
892 if ($this->roles_protected()) {
893 role_assign($roleid, $userid, $context->id, 'enrol_'.$name, $instance->id);
894 } else {
895 role_assign($roleid, $userid, $context->id);
896 }
897 }
898
899 if ($inserted) {
900 // add extra info and trigger event
901 $ue->courseid = $courseid;
902 $ue->enrol = $name;
903 events_trigger('user_enrolled', $ue);
904 }
905
906 // reset primitive require_login() caching
907 if ($userid == $USER->id) {
908 if (isset($USER->enrol['enrolled'][$courseid])) {
909 unset($USER->enrol['enrolled'][$courseid]);
910 }
911 if (isset($USER->enrol['tempguest'][$courseid])) {
912 unset($USER->enrol['tempguest'][$courseid]);
913 $USER->access = remove_temp_roles($context, $USER->access);
914 }
915 }
916 }
917
918 /**
919 * Store user_enrolments changes and trigger event.
920 *
921 * @param object $ue
922 * @param int $user id
923 * @param int $status
924 * @param int $timestart
925 * @param int $timeend
926 * @return void
927 */
928 public function update_user_enrol(stdClass $instance, $userid, $status = NULL, $timestart = NULL, $timeend = NULL) {
929 global $DB, $USER;
930
931 $name = $this->get_name();
932
933 if ($instance->enrol !== $name) {
934 throw new coding_exception('invalid enrol instance!');
935 }
936
937 if (!$ue = $DB->get_record('user_enrolments', array('enrolid'=>$instance->id, 'userid'=>$userid))) {
938 // weird, user not enrolled
939 return;
940 }
941
942 $modified = false;
943 if (isset($status) and $ue->status != $status) {
944 $ue->status = $status;
945 $modified = true;
946 }
947 if (isset($timestart) and $ue->timestart != $timestart) {
948 $ue->timestart = $timestart;
949 $modified = true;
950 }
951 if (isset($timeend) and $ue->timeend != $timeend) {
952 $ue->timeend = $timeend;
953 $modified = true;
954 }
955
956 if (!$modified) {
957 // no change
958 return;
959 }
960
961 $ue->modifierid = $USER->id;
962 $DB->update_record('user_enrolments', $ue);
963
964 // trigger event
965 $ue->courseid = $instance->courseid;
966 $ue->enrol = $instance->name;
967 events_trigger('user_unenrol_modified', $ue);
968 }
969
970 /**
971 * Unenrol user from course,
972 * the last unenrolment removes all remaining roles.
973 *
974 * @param stdClass $instance
975 * @param int $userid
976 * @return void
977 */
978 public function unenrol_user(stdClass $instance, $userid) {
979 global $CFG, $USER, $DB;
980
981 $name = $this->get_name();
982 $courseid = $instance->courseid;
983
984 if ($instance->enrol !== $name) {
985 throw new coding_exception('invalid enrol instance!');
986 }
987 $context = get_context_instance(CONTEXT_COURSE, $instance->courseid, MUST_EXIST);
988
989 if (!$ue = $DB->get_record('user_enrolments', array('enrolid'=>$instance->id, 'userid'=>$userid))) {
990 // weird, user not enrolled
991 return;
992 }
993
994 role_unassign_all(array('userid'=>$userid, 'contextid'=>$context->id, 'component'=>'enrol_'.$name, 'itemid'=>$instance->id));
995 $DB->delete_records('user_enrolments', array('id'=>$ue->id));
996
997 // add extra info and trigger event
998 $ue->courseid = $courseid;
999 $ue->enrol = $name;
1000
1001 $sql = "SELECT 'x'
1002 FROM {user_enrolments} ue
1003 JOIN {enrol} e ON (e.id = ue.enrolid)
1004 WHERE ue.userid = :userid AND e.courseid = :courseid";
1005 if ($DB->record_exists_sql($sql, array('userid'=>$userid, 'courseid'=>$courseid))) {
1006 $ue->lastenrol = false;
1007 events_trigger('user_unenrolled', $ue);
1008 // user still has some enrolments, no big cleanup yet
1009 } else {
1010 // the big cleanup IS necessary!
1011
1012 require_once("$CFG->dirroot/group/lib.php");
1013 require_once("$CFG->libdir/gradelib.php");
1014
1015 // remove all remaining roles
1016 role_unassign_all(array('userid'=>$userid, 'contextid'=>$context->id), true, false);
1017
1018 //clean up ALL invisible user data from course if this is the last enrolment - groups, grades, etc.
1019 groups_delete_group_members($courseid, $userid);
1020
1021 grade_user_unenrol($courseid, $userid);
1022
1023 $DB->delete_records('user_lastaccess', array('userid'=>$userid, 'courseid'=>$courseid));
1024
7d2800fb 1025 $ue->lastenrol = true; // means user not enrolled any more
df997f84
PS
1026 events_trigger('user_unenrolled', $ue);
1027 }
1028 // reset primitive require_login() caching
1029 if ($userid == $USER->id) {
1030 if (isset($USER->enrol['enrolled'][$courseid])) {
1031 unset($USER->enrol['enrolled'][$courseid]);
1032 }
1033 if (isset($USER->enrol['tempguest'][$courseid])) {
1034 unset($USER->enrol['tempguest'][$courseid]);
1035 $USER->access = remove_temp_roles($context, $USER->access);
1036 }
1037 }
1038 }
1039
1040 /**
1041 * Forces synchronisation of user enrolments.
1042 *
1043 * This is important especially for external enrol plugins,
1044 * this function is called for all enabled enrol plugins
1045 * right after every user login.
1046 *
1047 * @param object $user user record
1048 * @return void
1049 */
1050 public function sync_user_enrolments($user) {
1051 // override if necessary
1052 }
1053
1054 /**
1055 * Returns link to page which may be used to add new instance of enrolment plugin in course.
1056 * @param int $courseid
1057 * @return moodle_url page url
1058 */
e25f2466 1059 public function get_newinstance_link($courseid) {
df997f84
PS
1060 // override for most plugins, check if instance already exists in cases only one instance is supported
1061 return NULL;
1062 }
1063
1064 /**
1065 * Is it possible to delete enrol instance via standard UI?
1066 *
1067 * @param object $instance
1068 * @return bool
1069 */
1070 public function instance_deleteable($instance) {
1071 return true;
1072 }
1073
1074 /**
1075 * Returns link to manual enrol UI if exists.
1076 * Does the access control tests automatically.
1077 *
1078 * @param object $instance
1079 * @return moodle_url
1080 */
1081 public function get_manual_enrol_link($instance) {
1082 return NULL;
1083 }
1084
1085 /**
1086 * Returns list of unenrol links for all enrol instances in course.
1087 *
217d0397
PS
1088 * @param int $instance
1089 * @return moodle_url or NULL if self unernolmnet not supported
df997f84
PS
1090 */
1091 public function get_unenrolself_link($instance) {
1092 global $USER, $CFG, $DB;
1093
1094 $name = $this->get_name();
1095 if ($instance->enrol !== $name) {
1096 throw new coding_exception('invalid enrol instance!');
1097 }
1098
1099 if ($instance->courseid == SITEID) {
1100 return NULL;
1101 }
1102
1103 if (!enrol_is_enabled($name)) {
1104 return NULL;
1105 }
1106
1107 if ($instance->status != ENROL_INSTANCE_ENABLED) {
1108 return NULL;
1109 }
1110
df997f84
PS
1111 if (!file_exists("$CFG->dirroot/enrol/$name/unenrolself.php")) {
1112 return NULL;
1113 }
1114
217d0397
PS
1115 $context = get_context_instance(CONTEXT_COURSE, $instance->courseid, MUST_EXIST);
1116
df997f84
PS
1117 if (!has_capability("enrol/$name:unenrolself", $context)) {
1118 return NULL;
1119 }
1120
1121 if (!$DB->record_exists('user_enrolments', array('enrolid'=>$instance->id, 'userid'=>$USER->id, 'status'=>ENROL_USER_ACTIVE))) {
1122 return NULL;
1123 }
1124
1125 return new moodle_url("/enrol/$name/unenrolself.php", array('enrolid'=>$instance->id));;
1126 }
1127
1128 /**
1129 * Adds enrol instance UI to course edit form
1130 *
1131 * @param object $instance enrol instance or null if does not exist yet
1132 * @param MoodleQuickForm $mform
1133 * @param object $data
1134 * @param object $context context of existing course or parent category if course does not exist
1135 * @return void
1136 */
1137 public function course_edit_form($instance, MoodleQuickForm $mform, $data, $context) {
1138 // override - usually at least enable/disable switch, has to add own form header
1139 }
1140
1141 /**
1142 * Validates course edit form data
1143 *
1144 * @param object $instance enrol instance or null if does not exist yet
1145 * @param array $data
1146 * @param object $context context of existing course or parent category if course does not exist
1147 * @return array errors array
1148 */
1149 public function course_edit_validation($instance, array $data, $context) {
1150 return array();
1151 }
1152
1153 /**
1154 * Called after updating/inserting course.
1155 *
1156 * @param bool $inserted true if course just inserted
1157 * @param object $course
1158 * @param object $data form data
1159 * @return void
1160 */
1161 public function course_updated($inserted, $course, $data) {
eafb7a72
PS
1162 if ($inserted) {
1163 if ($this->get_config('defaultenrol')) {
1164 $this->add_default_instance($course);
1165 }
1166 }
df997f84
PS
1167 }
1168
1169 /**
eafb7a72 1170 * Add new instance of enrol plugin.
df997f84
PS
1171 * @param object $course
1172 * @param array instance fields
0848a196 1173 * @return int id of new instance, null if can not be created
df997f84
PS
1174 */
1175 public function add_instance($course, array $fields = NULL) {
1176 global $DB;
1177
1178 if ($course->id == SITEID) {
1179 throw new coding_exception('Invalid request to add enrol instance to frontpage.');
1180 }
1181
1182 $instance = new object();
1183 $instance->enrol = $this->get_name();
1184 $instance->status = ENROL_INSTANCE_ENABLED;
1185 $instance->courseid = $course->id;
1186 $instance->enrolstartdate = 0;
1187 $instance->enrolenddate = 0;
1188 $instance->timemodified = time();
1189 $instance->timecreated = $instance->timemodified;
1190 $instance->sortorder = $DB->get_field('enrol', 'COALESCE(MAX(sortorder), -1) + 1', array('courseid'=>$course->id));
1191
1192 $fields = (array)$fields;
1193 unset($fields['enrol']);
1194 unset($fields['courseid']);
1195 unset($fields['sortorder']);
1196 foreach($fields as $field=>$value) {
1197 $instance->$field = $value;
1198 }
1199
1200 return $DB->insert_record('enrol', $instance);
1201 }
1202
1203 /**
1204 * Add new instance of enrol plugin with default settings,
1205 * called when adding new instance manually or when adding new course.
1206 *
1207 * Not all plugins support this.
1208 *
1209 * @param object $course
1210 * @return int id of new instance or null if no default supported
1211 */
1212 public function add_default_instance($course) {
1213 return null;
1214 }
1215
1216 /**
1217 * Delete course enrol plugin instance, unenrol all users.
1218 * @param object $instance
1219 * @return void
1220 */
1221 public function delete_instance($instance) {
1222 global $DB;
1223
1224 $name = $this->get_name();
1225 if ($instance->enrol !== $name) {
1226 throw new coding_exception('invalid enrol instance!');
1227 }
1228
1229 //first unenrol all users
1230 $participants = $DB->get_recordset('user_enrolments', array('enrolid'=>$instance->id));
1231 foreach ($participants as $participant) {
1232 $this->unenrol_user($instance, $participant->userid);
1233 }
1234 $participants->close();
1235
1236 // now clean up all remainders that were not removed correctly
1237 $DB->delete_records('role_assignments', array('itemid'=>$instance->id, 'component'=>$name));
1238 $DB->delete_records('user_enrolments', array('enrolid'=>$instance->id));
1239
1240 // finally drop the enrol row
1241 $DB->delete_records('enrol', array('id'=>$instance->id));
1242 }
1243
1244 /**
1245 * Creates course enrol form, checks if form submitted
1246 * and enrols user if necessary. It can also redirect.
1247 *
1248 * @param stdClass $instance
1249 * @return string html text, usually a form in a text box
1250 */
1251 public function enrol_page_hook(stdClass $instance) {
1252 return null;
1253 }
1254
1255 /**
1256 * Adds navigation links into course admin block.
1257 *
1258 * By defaults looks for manage links only.
1259 *
1260 * @param navigation_node $instancesnode
1261 * @param object $instance
2d4b1f3e 1262 * @return void
df997f84
PS
1263 */
1264 public function add_course_navigation($instancesnode, stdClass $instance) {
2d4b1f3e 1265 // usually adds manage users
df997f84
PS
1266 }
1267
1268 /**
2d4b1f3e
PS
1269 * Returns edit icons for the page with list of instances
1270 * @param stdClass $instance
1271 * @return array
df997f84 1272 */
2d4b1f3e
PS
1273 public function get_action_icons(stdClass $instance) {
1274 return array();
df997f84
PS
1275 }
1276
1277 /**
1278 * Reads version.php and determines if it is necessary
1279 * to execute the cron job now.
1280 * @return bool
1281 */
1282 public function is_cron_required() {
1283 global $CFG;
1284
1285 $name = $this->get_name();
1286 $versionfile = "$CFG->dirroot/enrol/$name/version.php";
1287 $plugin = new object();
1288 include($versionfile);
1289 if (empty($plugin->cron)) {
1290 return false;
1291 }
1292 $lastexecuted = $this->get_config('lastcron', 0);
1293 if ($lastexecuted + $plugin->cron < time()) {
1294 return true;
1295 } else {
1296 return false;
1297 }
1298 }
1299
1300 /**
1301 * Called for all enabled enrol plugins that returned true from is_cron_required().
1302 * @return void
1303 */
1304 public function cron() {
1305 }
1306
1307 /**
1308 * Called when user is about to be deleted
1309 * @param object $user
1310 * @return void
1311 */
1312 public function user_delete($user) {
1313 global $DB;
1314
1315 $sql = "SELECT e.*
1316 FROM {enrol} e
1317 JOIN {user_enrolments} ue ON (ue.courseid = e.courseid)
1318 WHERE e.enrol = :meta AND ue.userid = :userid";
1319 $params = array('name'=>$this->get_name(), 'userid'=>$user->id);
1320
1321 $rs = $DB->get_records_recordset($sql, $params);
1322 foreach($rs as $instance) {
1323 $this->unenrol_user($instance, $user->id);
1324 }
1325 $rs->close();
1326 }
1327}
1328