3 // This file is part of Moodle - http://moodle.org/
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.
10 // Moodle is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 // GNU General Public License for more details.
15 // You should have received a copy of the GNU General Public License
16 // along with Moodle. If not, see <http://www.gnu.org/licenses/>.
19 * This library includes the basic parts of enrol api.
20 * It is available on each page.
24 * @copyright 2010 Petr Skoda {@link http://skodak.org}
25 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
28 defined('MOODLE_INTERNAL') || die();
30 /** Course enrol instance enabled. (used in enrol->status) */
31 define('ENROL_INSTANCE_ENABLED', 0);
33 /** Course enrol instance disabled, user may enter course if other enrol instance enabled. (used in enrol->status)*/
34 define('ENROL_INSTANCE_DISABLED', 1);
36 /** User is active participant (used in user_enrolments->status)*/
37 define('ENROL_USER_ACTIVE', 0);
39 /** User participation in course is suspended (used in user_enrolments->status) */
40 define('ENROL_USER_SUSPENDED', 1);
42 /** Enrol info is cached for this number of seconds in require_login() */
43 define('ENROL_REQUIRE_LOGIN_CACHE_PERIOD', 1800);
45 /** When user disappears from external source, the enrolment is completely removed */
46 define('ENROL_EXT_REMOVED_UNENROL', 0);
48 /** When user disappears from external source, the enrolment is kept as is - one way sync */
49 define('ENROL_EXT_REMOVED_KEEP', 1);
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,
56 define('ENROL_EXT_REMOVED_SUSPEND', 2);
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.
62 define('ENROL_EXT_REMOVED_SUSPENDNOROLES', 3);
65 * Returns instances of enrol plugins
66 * @param bool $enable return enabled only
67 * @return array of enrol plugins name=>instance
69 function enrol_get_plugins($enabled) {
75 // sorted by enabled plugin order
76 $enabled = explode(',', $CFG->enrol_plugins_enabled);
78 foreach ($enabled as $plugin) {
79 $plugins[$plugin] = "$CFG->dirroot/enrol/$plugin";
82 // sorted alphabetically
83 $plugins = get_plugin_list('enrol');
87 foreach ($plugins as $plugin=>$location) {
88 if (!file_exists("$location/lib.php")) {
91 include_once("$location/lib.php");
92 $class = "enrol_{$plugin}_plugin";
93 if (!class_exists($class)) {
97 $result[$plugin] = new $class();
104 * Returns instance of enrol plugin
105 * @param string $name name of enrol plugin ('manual', 'guest', ...)
106 * @return enrol_plugin
108 function enrol_get_plugin($name) {
111 if ($name !== clean_param($name, PARAM_SAFEDIR)) {
112 // ignore malformed plugin names completely
116 $location = "$CFG->dirroot/enrol/$name";
118 if (!file_exists("$location/lib.php")) {
121 include_once("$location/lib.php");
122 $class = "enrol_{$name}_plugin";
123 if (!class_exists($class)) {
131 * Returns enrolment instances in given course.
132 * @param int $courseid
133 * @param bool $enabled
134 * @return array of enrol instances
136 function enrol_get_instances($courseid, $enabled) {
140 return $DB->get_records('enrol', array('courseid'=>$courseid), 'sortorder,id');
143 $result = $DB->get_records('enrol', array('courseid'=>$courseid, 'status'=>ENROL_INSTANCE_ENABLED), 'sortorder,id');
145 $enabled = explode(',', $CFG->enrol_plugins_enabled);
146 foreach ($result as $key=>$instance) {
147 if (!in_array($instance->enrol, $enabled)) {
148 unset($result[$key]);
151 if (!file_exists("$CFG->dirroot/enrol/$instance->enrol/lib.php")) {
153 unset($result[$key]);
162 * Checks if a given plugin is in the list of enabled enrolment plugins.
164 * @param string $enrol Enrolment plugin name
165 * @return boolean Whether the plugin is enabled
167 function enrol_is_enabled($enrol) {
170 if (empty($CFG->enrol_plugins_enabled)) {
173 return in_array($enrol, explode(',', $CFG->enrol_plugins_enabled));
177 * Check all the login enrolment information for the given user object
178 * by querying the enrolment plugins
180 * @param object $user
183 function enrol_check_plugins($user) {
186 if (empty($user->id) or isguestuser($user)) {
187 // shortcut - there is no enrolment work for guests and not-logged-in users
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
197 static $inprogress = array(); // To prevent this function being called more than once in an invocation
199 if (!empty($inprogress[$user->id])) {
203 $inprogress[$user->id] = true; // Set the flag
205 $enabled = enrol_get_plugins(true);
207 foreach($enabled as $enrol) {
208 $enrol->sync_user_enrolments($user);
211 unset($inprogress[$user->id]); // Unset the flag
215 * This function adds necessary enrol plugins UI into the course edit form.
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
222 function 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])) {
230 $plugin = $plugins[$instance->enrol];
231 $plugin->course_edit_form($instance, $mform, $data, $context);
234 foreach ($plugins as $plugin) {
235 $plugin->course_edit_form(NULL, $mform, $data, $context);
241 * Validate course edit form data
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
247 function enrol_course_edit_validation(array $data, $context) {
249 $plugins = enrol_get_plugins(true);
251 if (!empty($data['id'])) {
252 $instances = enrol_get_instances($data['id'], false);
253 foreach ($instances as $instance) {
254 if (!isset($plugins[$instance->enrol])) {
257 $plugin = $plugins[$instance->enrol];
258 $errors = array_merge($errors, $plugin->course_edit_validation($instance, $data, $context));
261 foreach ($plugins as $plugin) {
262 $errors = array_merge($errors, $plugin->course_edit_validation(NULL, $data, $context));
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
276 function enrol_course_updated($inserted, $course, $data) {
279 $plugins = enrol_get_plugins(true);
281 foreach ($plugins as $plugin) {
282 $plugin->course_updated($inserted, $course, $data);
287 * Add navigation nodes
288 * @param navigation_node $coursenode
289 * @param object $course
292 function enrol_add_course_navigation(navigation_node $coursenode, $course) {
294 $coursecontext = get_context_instance(CONTEXT_COURSE, $course->id);
296 $instances = enrol_get_instances($course->id, true);
297 $plugins = enrol_get_plugins(true);
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]);
306 $usersnode = $coursenode->add(get_string('users'), null, navigation_node::TYPE_CONTAINER, null, 'users');
308 if ($course->id != SITEID) {
309 // list all participants - allows assigning roles, groups, etc.
310 if (has_capability('moodle/course:enrolreview', $coursecontext)) {
311 $url = new moodle_url('/enrol/users.php', array('id'=>$course->id));
312 $usersnode->add(get_string('enrolledusers', 'enrol'), $url, navigation_node::TYPE_SETTING, null, 'review', new pix_icon('i/users', ''));
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));
321 $instancesnode = $usersnode->add(get_string('enrolmentinstances', 'enrol'), $url, navigation_node::TYPE_SETTING, null, 'manageinstances');
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])) {
328 $plugins[$instance->enrol]->add_course_navigation($instancesnode, $instance);
332 $instancesnode->trim_if_empty();
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', ''));
342 if (has_any_capability(array( 'moodle/role:assign', 'moodle/role:safeoverride','moodle/role:override', 'moodle/role:review'), $coursecontext)) {
344 if (has_capability('moodle/role:review', $coursecontext)) {
345 $url = new moodle_url('/admin/roles/permissions.php', array('contextid'=>$coursecontext->id));
349 $permissionsnode = $usersnode->add(get_string('permissions', 'role'), $url, navigation_node::TYPE_SETTING, null, 'override');
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));
355 $permissionsnode->add(get_string('assignedroles', 'role'), $url, navigation_node::TYPE_SETTING, null, 'roles', new pix_icon('i/roles', ''));
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));
361 $permissionsnode->add(get_string('checkpermissions', 'role'), $url, navigation_node::TYPE_SETTING, null, 'permissions', new pix_icon('i/checkpermissions', ''));
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));
370 $usersnode->add(get_string('notenrolledusers', 'enrol'), $url, navigation_node::TYPE_SETTING, null, 'otherusers', new pix_icon('i/roles', ''));
374 // just in case nothing was actually added
375 $usersnode->trim_if_empty();
377 if ($course->id != SITEID) {
379 if (is_enrolled($coursecontext)) {
380 foreach ($instances as $instance) {
381 if (!isset($plugins[$instance->enrol])) {
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', ''));
388 //TODO. deal with multiple unenrol links - not likely case, but still...
392 if (is_viewing($coursecontext)) {
393 // better not show any enrol link, this is intended for managers and inspectors
395 foreach ($instances as $instance) {
396 if (!isset($plugins[$instance->enrol])) {
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', ''));
412 * Returns list of courses current $USER is enrolled in and can access
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
418 * @param strin|array $fields
419 * @param string $sort
420 * @param int $limit max number of courses
423 function enrol_get_my_courses($fields = NULL, $sort = 'visible DESC,sortorder ASC', $limit = 0) {
426 // Guest account does not have any courses
427 if (isguestuser() or !isloggedin()) {
431 $basefields = array('id', 'category', 'sortorder',
432 'shortname', 'fullname', 'idnumber',
433 'startdate', 'visible',
434 'groupmode', 'groupmodeforce');
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));
446 throw new coding_exception('Invalid $fileds parameter in enrol_get_my_courses()');
448 if (in_array('*', $fields)) {
449 $fields = array('*');
455 $rawsorts = explode(',', $sort);
457 foreach ($rawsorts as $rawsort) {
458 $rawsort = trim($rawsort);
459 if (strpos($rawsort, 'c.') === 0) {
460 $rawsort = substr($rawsort, 2);
462 $sorts[] = trim($rawsort);
464 $sort = 'c.'.implode(',c.', $sorts);
465 $orderby = "ORDER BY $sort";
468 $wheres = array("c.id <> :siteid");
469 $params = array('siteid'=>SITEID);
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;
477 $coursefields = 'c.' .join(',c.', $fields);
478 list($ccselect, $ccjoin) = context_instance_preload_sql('c.id', CONTEXT_COURSE, 'ctx');
479 $wheres = implode(" AND ", $wheres);
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
484 JOIN (SELECT DISTINCT e.courseid
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)
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'];
498 $courses = $DB->get_records_sql($sql, $params, 0, $limit);
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)) {
505 unset($courses[$id]);
508 if (!has_capability('moodle/course:viewhiddencourses', $context)) {
509 unset($courses[$id]);
513 $courses[$id] = $course;
516 //wow! Is that really all? :-D
522 * Returns list of courses user is enrolled into.
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
529 * @param bool $onlyactive return only active enrolments in courses user may see
530 * @param strin|array $fields
531 * @param string $sort
534 function enrol_get_users_courses($userid, $onlyactive = false, $fields = NULL, $sort = 'visible DESC,sortorder ASC') {
537 // Guest account does not have any courses
538 if (isguestuser($userid) or empty($userid)) {
542 $basefields = array('id', 'category', 'sortorder',
543 'shortname', 'fullname', 'idnumber',
544 'startdate', 'visible',
545 'groupmode', 'groupmodeforce');
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));
557 throw new coding_exception('Invalid $fileds parameter in enrol_get_my_courses()');
559 if (in_array('*', $fields)) {
560 $fields = array('*');
566 $rawsorts = explode(',', $sort);
568 foreach ($rawsorts as $rawsort) {
569 $rawsort = trim($rawsort);
570 if (strpos($rawsort, 'c.') === 0) {
571 $rawsort = substr($rawsort, 2);
573 $sorts[] = trim($rawsort);
575 $sort = 'c.'.implode(',c.', $sorts);
576 $orderby = "ORDER BY $sort";
579 $params = array('siteid'=>SITEID);
582 $subwhere = "WHERE ue.status = :active AND e.status = :enabled AND ue.timestart < :now1 AND (ue.timeend = 0 OR ue.timeend > :now2)";
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;
591 $coursefields = 'c.' .join(',c.', $fields);
592 list($ccselect, $ccjoin) = context_instance_preload_sql('c.id', CONTEXT_COURSE, 'ctx');
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
597 JOIN (SELECT DISTINCT e.courseid
599 JOIN {user_enrolments} ue ON (ue.enrolid = e.id AND ue.userid = :userid)
601 ) en ON (en.courseid = c.id)
603 WHERE c.id <> :siteid
605 $params['userid'] = $userid;
607 $courses = $DB->get_records_sql($sql, $params);
609 // preload contexts and check visibility
610 foreach ($courses as $id=>$course) {
611 context_instance_preload($course);
613 if (!$course->visible) {
614 if (!$context = get_context_instance(CONTEXT_COURSE, $id)) {
618 if (!has_capability('moodle/course:viewhiddencourses', $context, $userid)) {
624 $courses[$id] = $course;
627 //wow! Is that really all? :-D
634 * Called when user is about to be deleted.
635 * @param object $user
638 function enrol_user_delete($user) {
641 $plugins = enrol_get_plugins(true);
642 foreach ($plugins as $plugin) {
643 $plugin->user_delete($user);
646 // force cleanup of all broken enrolments
647 $DB->delete_records('user_enrolments', array('userid'=>$user->id));
651 * Try to enrol user via default internal auth plugin.
653 * For now this is always using the manual enrol plugin...
660 * @return bool success
662 function enrol_try_internal_enrol($courseid, $userid, $roleid = null, $timestart = 0, $timeend = 0) {
665 //note: this is hardcoded to manual plugin for now
667 if (!enrol_is_enabled('manual')) {
671 if (!$enrol = enrol_get_plugin('manual')) {
674 if (!$instances = $DB->get_records('enrol', array('enrol'=>'manual', 'courseid'=>$courseid, 'status'=>ENROL_INSTANCE_ENABLED), 'sortorder,id ASC')) {
677 $instance = reset($instances);
679 $enrol->enrol_user($instance, $userid, $roleid, $timestart, $timeend);
685 * All enrol plugins should be based on this class,
686 * this is also the main source of documentation.
688 abstract class enrol_plugin {
689 protected $config = null;
692 * Returns name of this enrol plugin
695 public function get_name() {
696 // second word in class is always enrol name
697 $words = explode('_', get_class($this));
702 * Returns localised name of enrol instance
704 * @param object $instance (null is accepted too)
707 public function get_instance_name($instance) {
708 if (empty($instance->name)) {
709 $enrol = $this->get_name();
710 return get_string('pluginname', 'enrol_'.$enrol);
712 return format_string($instance->name);
717 * Makes sure config is loaded and cached.
720 protected function load_config() {
721 if (!isset($this->config)) {
722 $name = $this->get_name();
723 if (!$config = get_config("enrol_$name")) {
724 $config = new object();
726 $this->config = $config;
731 * Returns plugin config value
732 * @param string $name
733 * @param string $default value if config does not exist yet
734 * @return string value or default
736 public function get_config($name, $default = NULL) {
737 $this->load_config();
738 return isset($this->config->$name) ? $this->config->$name : $default;
742 * Sets plugin config value
743 * @param string $name name of config
744 * @param string $value string config value, null means delete
745 * @return string value
747 public function set_config($name, $value) {
748 $pluginname = $this->get_name();
749 $this->load_config();
750 if ($value === NULL) {
751 unset($this->config->$name);
753 $this->config->$name = $value;
755 set_config($name, $value, "enrol_$pluginname");
759 * Does this plugin assign protected roles are can they be manually removed?
760 * @return bool - false means anybody may tweak roles, it does not use itemid and component when assigning roles
762 public function roles_protected() {
767 * Does this plugin allow manual enrolments?
769 * @param stdClass $instance course enrol instance
770 * All plugins allowing this must implement 'enrol/xxx:enrol' capability
772 * @return bool - true means user with 'enrol/xxx:enrol' may enrol others freely, trues means nobody may add more enrolments manually
774 public function allow_enrol(stdClass $instance) {
779 * Does this plugin allow manual unenrolments?
781 * @param stdClass $instance course enrol instance
782 * All plugins allowing this must implement 'enrol/xxx:unenrol' capability
784 * @return bool - true means user with 'enrol/xxx:unenrol' may unenrol others freely, trues means nobody may touch user_enrolments
786 public function allow_unenrol(stdClass $instance) {
791 * Does this plugin allow manual changes in user_enrolments table?
793 * All plugins allowing this must implement 'enrol/xxx:manage' capability
795 * @param stdClass $instance course enrol instance
796 * @return bool - true means it is possible to change enrol period and status in user_enrolments table
798 public function allow_manage(stdClass $instance) {
803 * Does this plugin support some way to user to self enrol?
805 * @param stdClass $instance course enrol instance
807 * @return bool - true means show "Enrol me in this course" link in course UI
809 public function show_enrolme_link(stdClass $instance) {
814 * Attempt to automatically enrol current user in course without any interaction,
815 * calling code has to make sure the plugin and instance are active.
817 * @param stdClass $instance course enrol instance
818 * @param stdClass $user record
819 * @return bool|int false means not enrolled, integer means timeend
821 public function try_autoenrol(stdClass $instance) {
828 * Attempt to automatically gain temporary guest access to course,
829 * calling code has to make sure the plugin and instance are active.
831 * @param stdClass $instance course enrol instance
832 * @param stdClass $user record
833 * @return bool|int false means no guest access, integer means timeend
835 public function try_guestaccess(stdClass $instance) {
842 * Enrol user into course via enrol instance.
844 * @param stdClass $instance
846 * @param int $roleid optional role id
847 * @param int $timestart 0 means unknown
848 * @param int $timeend 0 means forever
851 public function enrol_user(stdClass $instance, $userid, $roleid = null, $timestart = 0, $timeend = 0) {
852 global $DB, $USER, $CFG; // CFG necessary!!!
854 if ($instance->courseid == SITEID) {
855 throw new coding_exception('invalid attempt to enrol into frontpage course!');
858 $name = $this->get_name();
859 $courseid = $instance->courseid;
861 if ($instance->enrol !== $name) {
862 throw new coding_exception('invalid enrol instance!');
864 $context = get_context_instance(CONTEXT_COURSE, $instance->courseid, MUST_EXIST);
867 if ($ue = $DB->get_record('user_enrolments', array('enrolid'=>$instance->id, 'userid'=>$userid))) {
868 if ($ue->timestart != $timestart or $ue->timeend != $timeend) {
869 $ue->timestart = $timestart;
870 $ue->timeend = $timeend;
871 $ue->modifier = $USER->id;
872 $ue->timemodified = time();
873 $DB->update_record('user_enrolments', $ue);
877 $ue->enrolid = $instance->id;
878 $ue->status = ENROL_USER_ACTIVE;
879 $ue->userid = $userid;
880 $ue->timestart = $timestart;
881 $ue->timeend = $timeend;
882 $ue->modifier = $USER->id;
883 $ue->timecreated = time();
884 $ue->timemodified = $ue->timecreated;
885 $ue->id = $DB->insert_record('user_enrolments', $ue);
891 if ($this->roles_protected()) {
892 role_assign($roleid, $userid, $context->id, 'enrol_'.$name, $instance->id);
894 role_assign($roleid, $userid, $context->id);
899 // add extra info and trigger event
900 $ue->courseid = $courseid;
902 events_trigger('user_enrolled', $ue);
905 // reset primitive require_login() caching
906 if ($userid == $USER->id) {
907 if (isset($USER->enrol['enrolled'][$courseid])) {
908 unset($USER->enrol['enrolled'][$courseid]);
910 if (isset($USER->enrol['tempguest'][$courseid])) {
911 unset($USER->enrol['tempguest'][$courseid]);
912 $USER->access = remove_temp_roles($context, $USER->access);
918 * Store user_enrolments changes and trigger event.
921 * @param int $user id
923 * @param int $timestart
924 * @param int $timeend
927 public function update_user_enrol(stdClass $instance, $userid, $status = NULL, $timestart = NULL, $timeend = NULL) {
930 $name = $this->get_name();
932 if ($instance->enrol !== $name) {
933 throw new coding_exception('invalid enrol instance!');
936 if (!$ue = $DB->get_record('user_enrolments', array('enrolid'=>$instance->id, 'userid'=>$userid))) {
937 // weird, user not enrolled
942 if (isset($status) and $ue->status != $status) {
943 $ue->status = $status;
946 if (isset($timestart) and $ue->timestart != $timestart) {
947 $ue->timestart = $timestart;
950 if (isset($timeend) and $ue->timeend != $timeend) {
951 $ue->timeend = $timeend;
960 $ue->modifierid = $USER->id;
961 $DB->update_record('user_enrolments', $ue);
964 $ue->courseid = $instance->courseid;
965 $ue->enrol = $instance->name;
966 events_trigger('user_unenrol_modified', $ue);
970 * Unenrol user from course,
971 * the last unenrolment removes all remaining roles.
973 * @param stdClass $instance
977 public function unenrol_user(stdClass $instance, $userid) {
978 global $CFG, $USER, $DB;
980 $name = $this->get_name();
981 $courseid = $instance->courseid;
983 if ($instance->enrol !== $name) {
984 throw new coding_exception('invalid enrol instance!');
986 $context = get_context_instance(CONTEXT_COURSE, $instance->courseid, MUST_EXIST);
988 if (!$ue = $DB->get_record('user_enrolments', array('enrolid'=>$instance->id, 'userid'=>$userid))) {
989 // weird, user not enrolled
993 role_unassign_all(array('userid'=>$userid, 'contextid'=>$context->id, 'component'=>'enrol_'.$name, 'itemid'=>$instance->id));
994 $DB->delete_records('user_enrolments', array('id'=>$ue->id));
996 // add extra info and trigger event
997 $ue->courseid = $courseid;
1001 FROM {user_enrolments} ue
1002 JOIN {enrol} e ON (e.id = ue.enrolid)
1003 WHERE ue.userid = :userid AND e.courseid = :courseid";
1004 if ($DB->record_exists_sql($sql, array('userid'=>$userid, 'courseid'=>$courseid))) {
1005 $ue->lastenrol = false;
1006 events_trigger('user_unenrolled', $ue);
1007 // user still has some enrolments, no big cleanup yet
1009 // the big cleanup IS necessary!
1011 require_once("$CFG->dirroot/group/lib.php");
1012 require_once("$CFG->libdir/gradelib.php");
1014 // remove all remaining roles
1015 role_unassign_all(array('userid'=>$userid, 'contextid'=>$context->id), true, false);
1017 //clean up ALL invisible user data from course if this is the last enrolment - groups, grades, etc.
1018 groups_delete_group_members($courseid, $userid);
1020 grade_user_unenrol($courseid, $userid);
1022 $DB->delete_records('user_lastaccess', array('userid'=>$userid, 'courseid'=>$courseid));
1024 $ue->lastenrol = true; // means user not enrolled any more
1025 events_trigger('user_unenrolled', $ue);
1027 // reset primitive require_login() caching
1028 if ($userid == $USER->id) {
1029 if (isset($USER->enrol['enrolled'][$courseid])) {
1030 unset($USER->enrol['enrolled'][$courseid]);
1032 if (isset($USER->enrol['tempguest'][$courseid])) {
1033 unset($USER->enrol['tempguest'][$courseid]);
1034 $USER->access = remove_temp_roles($context, $USER->access);
1040 * Forces synchronisation of user enrolments.
1042 * This is important especially for external enrol plugins,
1043 * this function is called for all enabled enrol plugins
1044 * right after every user login.
1046 * @param object $user user record
1049 public function sync_user_enrolments($user) {
1050 // override if necessary
1054 * Returns link to page which may be used to add new instance of enrolment plugin in course.
1055 * @param int $courseid
1056 * @return moodle_url page url
1058 public function get_candidate_link($courseid) {
1059 // override for most plugins, check if instance already exists in cases only one instance is supported
1064 * Is it possible to delete enrol instance via standard UI?
1066 * @param object $instance
1069 public function instance_deleteable($instance) {
1074 * Returns link to manual enrol UI if exists.
1075 * Does the access control tests automatically.
1077 * @param object $instance
1078 * @return moodle_url
1080 public function get_manual_enrol_link($instance) {
1085 * Returns list of unenrol links for all enrol instances in course.
1087 * @param int $instance
1088 * @return moodle_url or NULL if self unernolmnet not supported
1090 public function get_unenrolself_link($instance) {
1091 global $USER, $CFG, $DB;
1093 $name = $this->get_name();
1094 if ($instance->enrol !== $name) {
1095 throw new coding_exception('invalid enrol instance!');
1098 if ($instance->courseid == SITEID) {
1102 if (!enrol_is_enabled($name)) {
1106 if ($instance->status != ENROL_INSTANCE_ENABLED) {
1110 if (!file_exists("$CFG->dirroot/enrol/$name/unenrolself.php")) {
1114 $context = get_context_instance(CONTEXT_COURSE, $instance->courseid, MUST_EXIST);
1116 if (!has_capability("enrol/$name:unenrolself", $context)) {
1120 if (!$DB->record_exists('user_enrolments', array('enrolid'=>$instance->id, 'userid'=>$USER->id, 'status'=>ENROL_USER_ACTIVE))) {
1124 return new moodle_url("/enrol/$name/unenrolself.php", array('enrolid'=>$instance->id));;
1128 * Adds enrol instance UI to course edit form
1130 * @param object $instance enrol instance or null if does not exist yet
1131 * @param MoodleQuickForm $mform
1132 * @param object $data
1133 * @param object $context context of existing course or parent category if course does not exist
1136 public function course_edit_form($instance, MoodleQuickForm $mform, $data, $context) {
1137 // override - usually at least enable/disable switch, has to add own form header
1141 * Validates course edit form data
1143 * @param object $instance enrol instance or null if does not exist yet
1144 * @param array $data
1145 * @param object $context context of existing course or parent category if course does not exist
1146 * @return array errors array
1148 public function course_edit_validation($instance, array $data, $context) {
1153 * Called after updating/inserting course.
1155 * @param bool $inserted true if course just inserted
1156 * @param object $course
1157 * @param object $data form data
1160 public function course_updated($inserted, $course, $data) {
1161 // override if settings on course edit page or some automatic sync needed
1165 * Add new instance of enrol plugin settings.
1166 * @param object $course
1167 * @param array instance fields
1168 * @return int id of new instance
1170 public function add_instance($course, array $fields = NULL) {
1173 if ($course->id == SITEID) {
1174 throw new coding_exception('Invalid request to add enrol instance to frontpage.');
1177 $instance = new object();
1178 $instance->enrol = $this->get_name();
1179 $instance->status = ENROL_INSTANCE_ENABLED;
1180 $instance->courseid = $course->id;
1181 $instance->enrolstartdate = 0;
1182 $instance->enrolenddate = 0;
1183 $instance->timemodified = time();
1184 $instance->timecreated = $instance->timemodified;
1185 $instance->sortorder = $DB->get_field('enrol', 'COALESCE(MAX(sortorder), -1) + 1', array('courseid'=>$course->id));
1187 $fields = (array)$fields;
1188 unset($fields['enrol']);
1189 unset($fields['courseid']);
1190 unset($fields['sortorder']);
1191 foreach($fields as $field=>$value) {
1192 $instance->$field = $value;
1195 return $DB->insert_record('enrol', $instance);
1199 * Add new instance of enrol plugin with default settings,
1200 * called when adding new instance manually or when adding new course.
1202 * Not all plugins support this.
1204 * @param object $course
1205 * @return int id of new instance or null if no default supported
1207 public function add_default_instance($course) {
1212 * Delete course enrol plugin instance, unenrol all users.
1213 * @param object $instance
1216 public function delete_instance($instance) {
1219 $name = $this->get_name();
1220 if ($instance->enrol !== $name) {
1221 throw new coding_exception('invalid enrol instance!');
1224 //first unenrol all users
1225 $participants = $DB->get_recordset('user_enrolments', array('enrolid'=>$instance->id));
1226 foreach ($participants as $participant) {
1227 $this->unenrol_user($instance, $participant->userid);
1229 $participants->close();
1231 // now clean up all remainders that were not removed correctly
1232 $DB->delete_records('role_assignments', array('itemid'=>$instance->id, 'component'=>$name));
1233 $DB->delete_records('user_enrolments', array('enrolid'=>$instance->id));
1235 // finally drop the enrol row
1236 $DB->delete_records('enrol', array('id'=>$instance->id));
1240 * Creates course enrol form, checks if form submitted
1241 * and enrols user if necessary. It can also redirect.
1243 * @param stdClass $instance
1244 * @return string html text, usually a form in a text box
1246 public function enrol_page_hook(stdClass $instance) {
1251 * Adds navigation links into course admin block.
1253 * By defaults looks for manage links only.
1255 * @param navigation_node $instancesnode
1256 * @param object $instance
1257 * @return moodle_url;
1259 public function add_course_navigation($instancesnode, stdClass $instance) {
1260 if ($managelink = $this->get_manage_link($instance)) {
1261 $instancesnode->add($this->get_instance_name($instance), $managelink, navigation_node::TYPE_SETTING);
1266 * Returns enrolment instance manage link.
1268 * By defaults looks for manage.php file and tests for manage capability.
1270 * @param object $instance
1271 * @return moodle_url;
1273 public function get_manage_link($instance) {
1276 $name = $this->get_name();
1278 if ($instance->enrol !== $name) {
1279 throw new coding_exception('Invalid enrol instance type!');
1282 if (!file_exists("$CFG->dirroot/enrol/$name/manage.php")) {
1286 if ($instance->courseid == SITEID) {
1287 // no enrolments on the frontpage, only roles there allowed
1291 $context = get_context_instance(CONTEXT_COURSE, $instance->courseid);
1292 if (!has_capability('enrol/'.$name.':manage', $context)) {
1296 return new moodle_url("/enrol/$name/manage.php", array('enrolid'=>$instance->id));
1300 * Reads version.php and determines if it is necessary
1301 * to execute the cron job now.
1304 public function is_cron_required() {
1307 $name = $this->get_name();
1308 $versionfile = "$CFG->dirroot/enrol/$name/version.php";
1309 $plugin = new object();
1310 include($versionfile);
1311 if (empty($plugin->cron)) {
1314 $lastexecuted = $this->get_config('lastcron', 0);
1315 if ($lastexecuted + $plugin->cron < time()) {
1323 * Called for all enabled enrol plugins that returned true from is_cron_required().
1326 public function cron() {
1330 * Called when user is about to be deleted
1331 * @param object $user
1334 public function user_delete($user) {
1339 JOIN {user_enrolments} ue ON (ue.courseid = e.courseid)
1340 WHERE e.enrol = :meta AND ue.userid = :userid";
1341 $params = array('name'=>$this->get_name(), 'userid'=>$user->id);
1343 $rs = $DB->get_records_recordset($sql, $params);
1344 foreach($rs as $instance) {
1345 $this->unenrol_user($instance, $user->id);