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