c90139d4418fd2c393ad0f8ea979c20f52966285
[moodle.git] / lib / enrollib.php
1 <?php
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/>.
18 /**
19  * This library includes the basic parts of enrol api.
20  * It is available on each page.
21  *
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
26  */
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 /** @deprecated - enrol caching was reworked, use ENROL_MAX_TIMESTAMP instead */
43 define('ENROL_REQUIRE_LOGIN_CACHE_PERIOD', 1800);
45 /** The timestamp indicating forever */
46 define('ENROL_MAX_TIMESTAMP', 2147483647);
48 /** When user disappears from external source, the enrolment is completely removed */
49 define('ENROL_EXT_REMOVED_UNENROL', 0);
51 /** When user disappears from external source, the enrolment is kept as is - one way sync */
52 define('ENROL_EXT_REMOVED_KEEP', 1);
54 /** enrol plugin feature describing requested restore type */
55 define('ENROL_RESTORE_TYPE', 'enrolrestore');
56 /** User custom backup/restore class  stored in backup/moodle2/ subdirectory */
57 define('ENROL_RESTORE_CLASS', 'class');
58 /** Restore all custom fields from enrol table without any changes and all user_enrolments records */
59 define('ENROL_RESTORE_EXACT', 'exact');
60 /** Restore enrol record like ENROL_RESTORE_EXACT, but no user enrolments */
61 define('ENROL_RESTORE_NOUSERS', 'nousers');
63 /**
64  * When user disappears from external source, user enrolment is suspended, roles are kept as is.
65  * In some cases user needs a role with some capability to be visible in UI - suc has in gradebook,
66  * assignments, etc.
67  */
68 define('ENROL_EXT_REMOVED_SUSPEND', 2);
70 /**
71  * When user disappears from external source, the enrolment is suspended and roles assigned
72  * by enrol instance are removed. Please note that user may "disappear" from gradebook and other areas.
73  * */
74 define('ENROL_EXT_REMOVED_SUSPENDNOROLES', 3);
76 /**
77  * Returns instances of enrol plugins
78  * @param bool $enabled return enabled only
79  * @return array of enrol plugins name=>instance
80  */
81 function enrol_get_plugins($enabled) {
82     global $CFG;
84     $result = array();
86     if ($enabled) {
87         // sorted by enabled plugin order
88         $enabled = explode(',', $CFG->enrol_plugins_enabled);
89         $plugins = array();
90         foreach ($enabled as $plugin) {
91             $plugins[$plugin] = "$CFG->dirroot/enrol/$plugin";
92         }
93     } else {
94         // sorted alphabetically
95         $plugins = get_plugin_list('enrol');
96         ksort($plugins);
97     }
99     foreach ($plugins as $plugin=>$location) {
100         if (!file_exists("$location/lib.php")) {
101             continue;
102         }
103         include_once("$location/lib.php");
104         $class = "enrol_{$plugin}_plugin";
105         if (!class_exists($class)) {
106             continue;
107         }
109         $result[$plugin] = new $class();
110     }
112     return $result;
115 /**
116  * Returns instance of enrol plugin
117  * @param  string $name name of enrol plugin ('manual', 'guest', ...)
118  * @return enrol_plugin
119  */
120 function enrol_get_plugin($name) {
121     global $CFG;
123     $name = clean_param($name, PARAM_PLUGIN);
125     if (empty($name)) {
126         // ignore malformed or missing plugin names completely
127         return null;
128     }
130     $location = "$CFG->dirroot/enrol/$name";
132     if (!file_exists("$location/lib.php")) {
133         return null;
134     }
135     include_once("$location/lib.php");
136     $class = "enrol_{$name}_plugin";
137     if (!class_exists($class)) {
138         return null;
139     }
141     return new $class();
144 /**
145  * Returns enrolment instances in given course.
146  * @param int $courseid
147  * @param bool $enabled
148  * @return array of enrol instances
149  */
150 function enrol_get_instances($courseid, $enabled) {
151     global $DB, $CFG;
153     if (!$enabled) {
154         return $DB->get_records('enrol', array('courseid'=>$courseid), 'sortorder,id');
155     }
157     $result = $DB->get_records('enrol', array('courseid'=>$courseid, 'status'=>ENROL_INSTANCE_ENABLED), 'sortorder,id');
159     $enabled = explode(',', $CFG->enrol_plugins_enabled);
160     foreach ($result as $key=>$instance) {
161         if (!in_array($instance->enrol, $enabled)) {
162             unset($result[$key]);
163             continue;
164         }
165         if (!file_exists("$CFG->dirroot/enrol/$instance->enrol/lib.php")) {
166             // broken plugin
167             unset($result[$key]);
168             continue;
169         }
170     }
172     return $result;
175 /**
176  * Checks if a given plugin is in the list of enabled enrolment plugins.
177  *
178  * @param string $enrol Enrolment plugin name
179  * @return boolean Whether the plugin is enabled
180  */
181 function enrol_is_enabled($enrol) {
182     global $CFG;
184     if (empty($CFG->enrol_plugins_enabled)) {
185         return false;
186     }
187     return in_array($enrol, explode(',', $CFG->enrol_plugins_enabled));
190 /**
191  * Check all the login enrolment information for the given user object
192  * by querying the enrolment plugins
193  *
194  * This function may be very slow, use only once after log-in or login-as.
195  *
196  * @param stdClass $user
197  * @return void
198  */
199 function enrol_check_plugins($user) {
200     global $CFG;
202     if (empty($user->id) or isguestuser($user)) {
203         // shortcut - there is no enrolment work for guests and not-logged-in users
204         return;
205     }
207     // originally there was a broken admin test, but accidentally it was non-functional in 2.2,
208     // which proved it was actually not necessary.
210     static $inprogress = array();  // To prevent this function being called more than once in an invocation
212     if (!empty($inprogress[$user->id])) {
213         return;
214     }
216     $inprogress[$user->id] = true;  // Set the flag
218     $enabled = enrol_get_plugins(true);
220     foreach($enabled as $enrol) {
221         $enrol->sync_user_enrolments($user);
222     }
224     unset($inprogress[$user->id]);  // Unset the flag
227 /**
228  * Do these two students share any course?
229  *
230  * The courses has to be visible and enrolments has to be active,
231  * timestart and timeend restrictions are ignored.
232  *
233  * This function calls {@see enrol_get_shared_courses()} setting checkexistsonly
234  * to true.
235  *
236  * @param stdClass|int $user1
237  * @param stdClass|int $user2
238  * @return bool
239  */
240 function enrol_sharing_course($user1, $user2) {
241     return enrol_get_shared_courses($user1, $user2, false, true);
244 /**
245  * Returns any courses shared by the two users
246  *
247  * The courses has to be visible and enrolments has to be active,
248  * timestart and timeend restrictions are ignored.
249  *
250  * @global moodle_database $DB
251  * @param stdClass|int $user1
252  * @param stdClass|int $user2
253  * @param bool $preloadcontexts If set to true contexts for the returned courses
254  *              will be preloaded.
255  * @param bool $checkexistsonly If set to true then this function will return true
256  *              if the users share any courses and false if not.
257  * @return array|bool An array of courses that both users are enrolled in OR if
258  *              $checkexistsonly set returns true if the users share any courses
259  *              and false if not.
260  */
261 function enrol_get_shared_courses($user1, $user2, $preloadcontexts = false, $checkexistsonly = false) {
262     global $DB, $CFG;
264     $user1 = isset($user1->id) ? $user1->id : $user1;
265     $user2 = isset($user2->id) ? $user2->id : $user2;
267     if (empty($user1) or empty($user2)) {
268         return false;
269     }
271     if (!$plugins = explode(',', $CFG->enrol_plugins_enabled)) {
272         return false;
273     }
275     list($plugins, $params) = $DB->get_in_or_equal($plugins, SQL_PARAMS_NAMED, 'ee');
276     $params['enabled'] = ENROL_INSTANCE_ENABLED;
277     $params['active1'] = ENROL_USER_ACTIVE;
278     $params['active2'] = ENROL_USER_ACTIVE;
279     $params['user1']   = $user1;
280     $params['user2']   = $user2;
282     $ctxselect = '';
283     $ctxjoin = '';
284     if ($preloadcontexts) {
285         list($ctxselect, $ctxjoin) = context_instance_preload_sql('c.id', CONTEXT_COURSE, 'ctx');
286     }
288     $sql = "SELECT c.* $ctxselect
289               FROM {course} c
290               JOIN (
291                 SELECT DISTINCT c.id
292                   FROM {enrol} e
293                   JOIN {user_enrolments} ue1 ON (ue1.enrolid = e.id AND ue1.status = :active1 AND ue1.userid = :user1)
294                   JOIN {user_enrolments} ue2 ON (ue2.enrolid = e.id AND ue2.status = :active2 AND ue2.userid = :user2)
295                   JOIN {course} c ON (c.id = e.courseid AND c.visible = 1)
296                  WHERE e.status = :enabled AND e.enrol $plugins
297               ) ec ON ec.id = c.id
298               $ctxjoin";
300     if ($checkexistsonly) {
301         return $DB->record_exists_sql($sql, $params);
302     } else {
303         $courses = $DB->get_records_sql($sql, $params);
304         if ($preloadcontexts) {
305             array_map('context_instance_preload', $courses);
306         }
307         return $courses;
308     }
311 /**
312  * This function adds necessary enrol plugins UI into the course edit form.
313  *
314  * @param MoodleQuickForm $mform
315  * @param object $data course edit form data
316  * @param object $context context of existing course or parent category if course does not exist
317  * @return void
318  */
319 function enrol_course_edit_form(MoodleQuickForm $mform, $data, $context) {
320     $plugins = enrol_get_plugins(true);
321     if (!empty($data->id)) {
322         $instances = enrol_get_instances($data->id, false);
323         foreach ($instances as $instance) {
324             if (!isset($plugins[$instance->enrol])) {
325                 continue;
326             }
327             $plugin = $plugins[$instance->enrol];
328             $plugin->course_edit_form($instance, $mform, $data, $context);
329         }
330     } else {
331         foreach ($plugins as $plugin) {
332             $plugin->course_edit_form(NULL, $mform, $data, $context);
333         }
334     }
337 /**
338  * Validate course edit form data
339  *
340  * @param array $data raw form data
341  * @param object $context context of existing course or parent category if course does not exist
342  * @return array errors array
343  */
344 function enrol_course_edit_validation(array $data, $context) {
345     $errors = array();
346     $plugins = enrol_get_plugins(true);
348     if (!empty($data['id'])) {
349         $instances = enrol_get_instances($data['id'], false);
350         foreach ($instances as $instance) {
351             if (!isset($plugins[$instance->enrol])) {
352                 continue;
353             }
354             $plugin = $plugins[$instance->enrol];
355             $errors = array_merge($errors, $plugin->course_edit_validation($instance, $data, $context));
356         }
357     } else {
358         foreach ($plugins as $plugin) {
359             $errors = array_merge($errors, $plugin->course_edit_validation(NULL, $data, $context));
360         }
361     }
363     return $errors;
366 /**
367  * Update enrol instances after course edit form submission
368  * @param bool $inserted true means new course added, false course already existed
369  * @param object $course
370  * @param object $data form data
371  * @return void
372  */
373 function enrol_course_updated($inserted, $course, $data) {
374     global $DB, $CFG;
376     $plugins = enrol_get_plugins(true);
378     foreach ($plugins as $plugin) {
379         $plugin->course_updated($inserted, $course, $data);
380     }
383 /**
384  * Add navigation nodes
385  * @param navigation_node $coursenode
386  * @param object $course
387  * @return void
388  */
389 function enrol_add_course_navigation(navigation_node $coursenode, $course) {
390     global $CFG;
392     $coursecontext = get_context_instance(CONTEXT_COURSE, $course->id);
394     $instances = enrol_get_instances($course->id, true);
395     $plugins   = enrol_get_plugins(true);
397     // we do not want to break all course pages if there is some borked enrol plugin, right?
398     foreach ($instances as $k=>$instance) {
399         if (!isset($plugins[$instance->enrol])) {
400             unset($instances[$k]);
401         }
402     }
404     $usersnode = $coursenode->add(get_string('users'), null, navigation_node::TYPE_CONTAINER, null, 'users');
406     if ($course->id != SITEID) {
407         // list all participants - allows assigning roles, groups, etc.
408         if (has_capability('moodle/course:enrolreview', $coursecontext)) {
409             $url = new moodle_url('/enrol/users.php', array('id'=>$course->id));
410             $usersnode->add(get_string('enrolledusers', 'enrol'), $url, navigation_node::TYPE_SETTING, null, 'review', new pix_icon('i/users', ''));
411         }
413         // manage enrol plugin instances
414         if (has_capability('moodle/course:enrolconfig', $coursecontext) or has_capability('moodle/course:enrolreview', $coursecontext)) {
415             $url = new moodle_url('/enrol/instances.php', array('id'=>$course->id));
416         } else {
417             $url = NULL;
418         }
419         $instancesnode = $usersnode->add(get_string('enrolmentinstances', 'enrol'), $url, navigation_node::TYPE_SETTING, null, 'manageinstances');
421         // each instance decides how to configure itself or how many other nav items are exposed
422         foreach ($instances as $instance) {
423             if (!isset($plugins[$instance->enrol])) {
424                 continue;
425             }
426             $plugins[$instance->enrol]->add_course_navigation($instancesnode, $instance);
427         }
429         if (!$url) {
430             $instancesnode->trim_if_empty();
431         }
432     }
434     // Manage groups in this course or even frontpage
435     if (($course->groupmode || !$course->groupmodeforce) && has_capability('moodle/course:managegroups', $coursecontext)) {
436         $url = new moodle_url('/group/index.php', array('id'=>$course->id));
437         $usersnode->add(get_string('groups'), $url, navigation_node::TYPE_SETTING, null, 'groups', new pix_icon('i/group', ''));
438     }
440      if (has_any_capability(array( 'moodle/role:assign', 'moodle/role:safeoverride','moodle/role:override', 'moodle/role:review'), $coursecontext)) {
441         // Override roles
442         if (has_capability('moodle/role:review', $coursecontext)) {
443             $url = new moodle_url('/admin/roles/permissions.php', array('contextid'=>$coursecontext->id));
444         } else {
445             $url = NULL;
446         }
447         $permissionsnode = $usersnode->add(get_string('permissions', 'role'), $url, navigation_node::TYPE_SETTING, null, 'override');
449         // Add assign or override roles if allowed
450         if ($course->id == SITEID or (!empty($CFG->adminsassignrolesincourse) and is_siteadmin())) {
451             if (has_capability('moodle/role:assign', $coursecontext)) {
452                 $url = new moodle_url('/admin/roles/assign.php', array('contextid'=>$coursecontext->id));
453                 $permissionsnode->add(get_string('assignedroles', 'role'), $url, navigation_node::TYPE_SETTING, null, 'roles', new pix_icon('i/roles', ''));
454             }
455         }
456         // Check role permissions
457         if (has_any_capability(array('moodle/role:assign', 'moodle/role:safeoverride','moodle/role:override', 'moodle/role:assign'), $coursecontext)) {
458             $url = new moodle_url('/admin/roles/check.php', array('contextid'=>$coursecontext->id));
459             $permissionsnode->add(get_string('checkpermissions', 'role'), $url, navigation_node::TYPE_SETTING, null, 'permissions', new pix_icon('i/checkpermissions', ''));
460         }
461      }
463      // Deal somehow with users that are not enrolled but still got a role somehow
464     if ($course->id != SITEID) {
465         //TODO, create some new UI for role assignments at course level
466         if (has_capability('moodle/role:assign', $coursecontext)) {
467             $url = new moodle_url('/enrol/otherusers.php', array('id'=>$course->id));
468             $usersnode->add(get_string('notenrolledusers', 'enrol'), $url, navigation_node::TYPE_SETTING, null, 'otherusers', new pix_icon('i/roles', ''));
469         }
470     }
472     // just in case nothing was actually added
473     $usersnode->trim_if_empty();
475     if ($course->id != SITEID) {
476         if (isguestuser() or !isloggedin()) {
477             // guest account can not be enrolled - no links for them
478         } else if (is_enrolled($coursecontext)) {
479             // unenrol link if possible
480             foreach ($instances as $instance) {
481                 if (!isset($plugins[$instance->enrol])) {
482                     continue;
483                 }
484                 $plugin = $plugins[$instance->enrol];
485                 if ($unenrollink = $plugin->get_unenrolself_link($instance)) {
486                     $shortname = format_string($course->shortname, true, array('context' => $coursecontext));
487                     $coursenode->add(get_string('unenrolme', 'core_enrol', $shortname), $unenrollink, navigation_node::TYPE_SETTING, null, 'unenrolself', new pix_icon('i/user', ''));
488                     break;
489                     //TODO. deal with multiple unenrol links - not likely case, but still...
490                 }
491             }
492         } else {
493             // enrol link if possible
494             if (is_viewing($coursecontext)) {
495                 // better not show any enrol link, this is intended for managers and inspectors
496             } else {
497                 foreach ($instances as $instance) {
498                     if (!isset($plugins[$instance->enrol])) {
499                         continue;
500                     }
501                     $plugin = $plugins[$instance->enrol];
502                     if ($plugin->show_enrolme_link($instance)) {
503                         $url = new moodle_url('/enrol/index.php', array('id'=>$course->id));
504                         $shortname = format_string($course->shortname, true, array('context' => $coursecontext));
505                         $coursenode->add(get_string('enrolme', 'core_enrol', $shortname), $url, navigation_node::TYPE_SETTING, null, 'enrolself', new pix_icon('i/user', ''));
506                         break;
507                     }
508                 }
509             }
510         }
511     }
514 /**
515  * Returns list of courses current $USER is enrolled in and can access
516  *
517  * - $fields is an array of field names to ADD
518  *   so name the fields you really need, which will
519  *   be added and uniq'd
520  *
521  * @param string|array $fields
522  * @param string $sort
523  * @param int $limit max number of courses
524  * @return array
525  */
526 function enrol_get_my_courses($fields = NULL, $sort = 'visible DESC,sortorder ASC', $limit = 0) {
527     global $DB, $USER;
529     // Guest account does not have any courses
530     if (isguestuser() or !isloggedin()) {
531         return(array());
532     }
534     $basefields = array('id', 'category', 'sortorder',
535                         'shortname', 'fullname', 'idnumber',
536                         'startdate', 'visible',
537                         'groupmode', 'groupmodeforce');
539     if (empty($fields)) {
540         $fields = $basefields;
541     } else if (is_string($fields)) {
542         // turn the fields from a string to an array
543         $fields = explode(',', $fields);
544         $fields = array_map('trim', $fields);
545         $fields = array_unique(array_merge($basefields, $fields));
546     } else if (is_array($fields)) {
547         $fields = array_unique(array_merge($basefields, $fields));
548     } else {
549         throw new coding_exception('Invalid $fileds parameter in enrol_get_my_courses()');
550     }
551     if (in_array('*', $fields)) {
552         $fields = array('*');
553     }
555     $orderby = "";
556     $sort    = trim($sort);
557     if (!empty($sort)) {
558         $rawsorts = explode(',', $sort);
559         $sorts = array();
560         foreach ($rawsorts as $rawsort) {
561             $rawsort = trim($rawsort);
562             if (strpos($rawsort, 'c.') === 0) {
563                 $rawsort = substr($rawsort, 2);
564             }
565             $sorts[] = trim($rawsort);
566         }
567         $sort = 'c.'.implode(',c.', $sorts);
568         $orderby = "ORDER BY $sort";
569     }
571     $wheres = array("c.id <> :siteid");
572     $params = array('siteid'=>SITEID);
574     if (isset($USER->loginascontext) and $USER->loginascontext->contextlevel == CONTEXT_COURSE) {
575         // list _only_ this course - anything else is asking for trouble...
576         $wheres[] = "courseid = :loginas";
577         $params['loginas'] = $USER->loginascontext->instanceid;
578     }
580     $coursefields = 'c.' .join(',c.', $fields);
581     list($ccselect, $ccjoin) = context_instance_preload_sql('c.id', CONTEXT_COURSE, 'ctx');
582     $wheres = implode(" AND ", $wheres);
584     //note: we can not use DISTINCT + text fields due to Oracle and MS limitations, that is why we have the subselect there
585     $sql = "SELECT $coursefields $ccselect
586               FROM {course} c
587               JOIN (SELECT DISTINCT e.courseid
588                       FROM {enrol} e
589                       JOIN {user_enrolments} ue ON (ue.enrolid = e.id AND ue.userid = :userid)
590                      WHERE ue.status = :active AND e.status = :enabled AND ue.timestart < :now1 AND (ue.timeend = 0 OR ue.timeend > :now2)
591                    ) en ON (en.courseid = c.id)
592            $ccjoin
593              WHERE $wheres
594           $orderby";
595     $params['userid']  = $USER->id;
596     $params['active']  = ENROL_USER_ACTIVE;
597     $params['enabled'] = ENROL_INSTANCE_ENABLED;
598     $params['now1']    = round(time(), -2); // improves db caching
599     $params['now2']    = $params['now1'];
601     $courses = $DB->get_records_sql($sql, $params, 0, $limit);
603     // preload contexts and check visibility
604     foreach ($courses as $id=>$course) {
605         context_instance_preload($course);
606         if (!$course->visible) {
607             if (!$context = get_context_instance(CONTEXT_COURSE, $id)) {
608                 unset($courses[$id]);
609                 continue;
610             }
611             if (!has_capability('moodle/course:viewhiddencourses', $context)) {
612                 unset($courses[$id]);
613                 continue;
614             }
615         }
616         $courses[$id] = $course;
617     }
619     //wow! Is that really all? :-D
621     return $courses;
624 /**
625  * Returns course enrolment information icons.
626  *
627  * @param object $course
628  * @param array $instances enrol instances of this course, improves performance
629  * @return array of pix_icon
630  */
631 function enrol_get_course_info_icons($course, array $instances = NULL) {
632     $icons = array();
633     if (is_null($instances)) {
634         $instances = enrol_get_instances($course->id, true);
635     }
636     $plugins = enrol_get_plugins(true);
637     foreach ($plugins as $name => $plugin) {
638         $pis = array();
639         foreach ($instances as $instance) {
640             if ($instance->status != ENROL_INSTANCE_ENABLED or $instance->courseid != $course->id) {
641                 debugging('Invalid instances parameter submitted in enrol_get_info_icons()');
642                 continue;
643             }
644             if ($instance->enrol == $name) {
645                 $pis[$instance->id] = $instance;
646             }
647         }
648         if ($pis) {
649             $icons = array_merge($icons, $plugin->get_info_icons($pis));
650         }
651     }
652     return $icons;
655 /**
656  * Returns course enrolment detailed information.
657  *
658  * @param object $course
659  * @return array of html fragments - can be used to construct lists
660  */
661 function enrol_get_course_description_texts($course) {
662     $lines = array();
663     $instances = enrol_get_instances($course->id, true);
664     $plugins = enrol_get_plugins(true);
665     foreach ($instances as $instance) {
666         if (!isset($plugins[$instance->enrol])) {
667             //weird
668             continue;
669         }
670         $plugin = $plugins[$instance->enrol];
671         $text = $plugin->get_description_text($instance);
672         if ($text !== NULL) {
673             $lines[] = $text;
674         }
675     }
676     return $lines;
679 /**
680  * Returns list of courses user is enrolled into.
681  *
682  * - $fields is an array of fieldnames to ADD
683  *   so name the fields you really need, which will
684  *   be added and uniq'd
685  *
686  * @param int $userid
687  * @param bool $onlyactive return only active enrolments in courses user may see
688  * @param string|array $fields
689  * @param string $sort
690  * @return array
691  */
692 function enrol_get_users_courses($userid, $onlyactive = false, $fields = NULL, $sort = 'visible DESC,sortorder ASC') {
693     global $DB;
695     // Guest account does not have any courses
696     if (isguestuser($userid) or empty($userid)) {
697         return(array());
698     }
700     $basefields = array('id', 'category', 'sortorder',
701                         'shortname', 'fullname', 'idnumber',
702                         'startdate', 'visible',
703                         'groupmode', 'groupmodeforce');
705     if (empty($fields)) {
706         $fields = $basefields;
707     } else if (is_string($fields)) {
708         // turn the fields from a string to an array
709         $fields = explode(',', $fields);
710         $fields = array_map('trim', $fields);
711         $fields = array_unique(array_merge($basefields, $fields));
712     } else if (is_array($fields)) {
713         $fields = array_unique(array_merge($basefields, $fields));
714     } else {
715         throw new coding_exception('Invalid $fileds parameter in enrol_get_my_courses()');
716     }
717     if (in_array('*', $fields)) {
718         $fields = array('*');
719     }
721     $orderby = "";
722     $sort    = trim($sort);
723     if (!empty($sort)) {
724         $rawsorts = explode(',', $sort);
725         $sorts = array();
726         foreach ($rawsorts as $rawsort) {
727             $rawsort = trim($rawsort);
728             if (strpos($rawsort, 'c.') === 0) {
729                 $rawsort = substr($rawsort, 2);
730             }
731             $sorts[] = trim($rawsort);
732         }
733         $sort = 'c.'.implode(',c.', $sorts);
734         $orderby = "ORDER BY $sort";
735     }
737     $params = array('siteid'=>SITEID);
739     if ($onlyactive) {
740         $subwhere = "WHERE ue.status = :active AND e.status = :enabled AND ue.timestart < :now1 AND (ue.timeend = 0 OR ue.timeend > :now2)";
741         $params['now1']    = round(time(), -2); // improves db caching
742         $params['now2']    = $params['now1'];
743         $params['active']  = ENROL_USER_ACTIVE;
744         $params['enabled'] = ENROL_INSTANCE_ENABLED;
745     } else {
746         $subwhere = "";
747     }
749     $coursefields = 'c.' .join(',c.', $fields);
750     list($ccselect, $ccjoin) = context_instance_preload_sql('c.id', CONTEXT_COURSE, 'ctx');
752     //note: we can not use DISTINCT + text fields due to Oracle and MS limitations, that is why we have the subselect there
753     $sql = "SELECT $coursefields $ccselect
754               FROM {course} c
755               JOIN (SELECT DISTINCT e.courseid
756                       FROM {enrol} e
757                       JOIN {user_enrolments} ue ON (ue.enrolid = e.id AND ue.userid = :userid)
758                  $subwhere
759                    ) en ON (en.courseid = c.id)
760            $ccjoin
761              WHERE c.id <> :siteid
762           $orderby";
763     $params['userid']  = $userid;
765     $courses = $DB->get_records_sql($sql, $params);
767     // preload contexts and check visibility
768     foreach ($courses as $id=>$course) {
769         context_instance_preload($course);
770         if ($onlyactive) {
771             if (!$course->visible) {
772                 if (!$context = get_context_instance(CONTEXT_COURSE, $id)) {
773                     unset($courses[$id]);
774                     continue;
775                 }
776                 if (!has_capability('moodle/course:viewhiddencourses', $context, $userid)) {
777                     unset($courses[$id]);
778                     continue;
779                 }
780             }
781         }
782         $courses[$id] = $course;
783     }
785     //wow! Is that really all? :-D
787     return $courses;
791 /**
792  * Called when user is about to be deleted.
793  * @param object $user
794  * @return void
795  */
796 function enrol_user_delete($user) {
797     global $DB;
799     $plugins = enrol_get_plugins(true);
800     foreach ($plugins as $plugin) {
801         $plugin->user_delete($user);
802     }
804     // force cleanup of all broken enrolments
805     $DB->delete_records('user_enrolments', array('userid'=>$user->id));
808 /**
809  * Called when course is about to be deleted.
810  * @param stdClass $course
811  * @return void
812  */
813 function enrol_course_delete($course) {
814     global $DB;
816     $instances = enrol_get_instances($course->id, false);
817     $plugins = enrol_get_plugins(true);
818     foreach ($instances as $instance) {
819         if (isset($plugins[$instance->enrol])) {
820             $plugins[$instance->enrol]->delete_instance($instance);
821         }
822         // low level delete in case plugin did not do it
823         $DB->delete_records('user_enrolments', array('enrolid'=>$instance->id));
824         $DB->delete_records('role_assignments', array('itemid'=>$instance->id, 'component'=>'enrol_'.$instance->enrol));
825         $DB->delete_records('user_enrolments', array('enrolid'=>$instance->id));
826         $DB->delete_records('enrol', array('id'=>$instance->id));
827     }
830 /**
831  * Try to enrol user via default internal auth plugin.
832  *
833  * For now this is always using the manual enrol plugin...
834  *
835  * @param $courseid
836  * @param $userid
837  * @param $roleid
838  * @param $timestart
839  * @param $timeend
840  * @return bool success
841  */
842 function enrol_try_internal_enrol($courseid, $userid, $roleid = null, $timestart = 0, $timeend = 0) {
843     global $DB;
845     //note: this is hardcoded to manual plugin for now
847     if (!enrol_is_enabled('manual')) {
848         return false;
849     }
851     if (!$enrol = enrol_get_plugin('manual')) {
852         return false;
853     }
854     if (!$instances = $DB->get_records('enrol', array('enrol'=>'manual', 'courseid'=>$courseid, 'status'=>ENROL_INSTANCE_ENABLED), 'sortorder,id ASC')) {
855         return false;
856     }
857     $instance = reset($instances);
859     $enrol->enrol_user($instance, $userid, $roleid, $timestart, $timeend);
861     return true;
864 /**
865  * Is there a chance users might self enrol
866  * @param int $courseid
867  * @return bool
868  */
869 function enrol_selfenrol_available($courseid) {
870     $result = false;
872     $plugins = enrol_get_plugins(true);
873     $enrolinstances = enrol_get_instances($courseid, true);
874     foreach($enrolinstances as $instance) {
875         if (!isset($plugins[$instance->enrol])) {
876             continue;
877         }
878         if ($instance->enrol === 'guest') {
879             // blacklist known temporary guest plugins
880             continue;
881         }
882         if ($plugins[$instance->enrol]->show_enrolme_link($instance)) {
883             $result = true;
884             break;
885         }
886     }
888     return $result;
891 /**
892  * This function returns the end of current active user enrolment.
893  *
894  * It deals correctly with multiple overlapping user enrolments.
895  *
896  * @param int $courseid
897  * @param int $userid
898  * @return int|bool timestamp when active enrolment ends, false means no active enrolment now, 0 means never
899  */
900 function enrol_get_enrolment_end($courseid, $userid) {
901     global $DB;
903     $sql = "SELECT ue.*
904               FROM {user_enrolments} ue
905               JOIN {enrol} e ON (e.id = ue.enrolid AND e.courseid = :courseid)
906               JOIN {user} u ON u.id = ue.userid
907              WHERE ue.userid = :userid AND ue.status = :active AND e.status = :enabled AND u.deleted = 0";
908     $params = array('enabled'=>ENROL_INSTANCE_ENABLED, 'active'=>ENROL_USER_ACTIVE, 'userid'=>$userid, 'courseid'=>$courseid);
910     if (!$enrolments = $DB->get_records_sql($sql, $params)) {
911         return false;
912     }
914     $changes = array();
916     foreach ($enrolments as $ue) {
917         $start = (int)$ue->timestart;
918         $end = (int)$ue->timeend;
919         if ($end != 0 and $end < $start) {
920             debugging('Invalid enrolment start or end in user_enrolment id:'.$ue->id);
921             continue;
922         }
923         if (isset($changes[$start])) {
924             $changes[$start] = $changes[$start] + 1;
925         } else {
926             $changes[$start] = 1;
927         }
928         if ($end === 0) {
929             // no end
930         } else if (isset($changes[$end])) {
931             $changes[$end] = $changes[$end] - 1;
932         } else {
933             $changes[$end] = -1;
934         }
935     }
937     // let's sort then enrolment starts&ends and go through them chronologically,
938     // looking for current status and the next future end of enrolment
939     ksort($changes);
941     $now = time();
942     $current = 0;
943     $present = null;
945     foreach ($changes as $time => $change) {
946         if ($time > $now) {
947             if ($present === null) {
948                 // we have just went past current time
949                 $present = $current;
950                 if ($present < 1) {
951                     // no enrolment active
952                     return false;
953                 }
954             }
955             if ($present !== null) {
956                 // we are already in the future - look for possible end
957                 if ($current + $change < 1) {
958                     return $time;
959                 }
960             }
961         }
962         $current += $change;
963     }
965     if ($current > 0) {
966         return 0;
967     } else {
968         return false;
969     }
973 /**
974  * All enrol plugins should be based on this class,
975  * this is also the main source of documentation.
976  */
977 abstract class enrol_plugin {
978     protected $config = null;
980     /**
981      * Returns name of this enrol plugin
982      * @return string
983      */
984     public function get_name() {
985         // second word in class is always enrol name, sorry, no fancy plugin names with _
986         $words = explode('_', get_class($this));
987         return $words[1];
988     }
990     /**
991      * Returns localised name of enrol instance
992      *
993      * @param object $instance (null is accepted too)
994      * @return string
995      */
996     public function get_instance_name($instance) {
997         if (empty($instance->name)) {
998             $enrol = $this->get_name();
999             return get_string('pluginname', 'enrol_'.$enrol);
1000         } else {
1001             $context = get_context_instance(CONTEXT_COURSE, $instance->courseid);
1002             return format_string($instance->name, true, array('context'=>$context));
1003         }
1004     }
1006     /**
1007      * Returns optional enrolment information icons.
1008      *
1009      * This is used in course list for quick overview of enrolment options.
1010      *
1011      * We are not using single instance parameter because sometimes
1012      * we might want to prevent icon repetition when multiple instances
1013      * of one type exist. One instance may also produce several icons.
1014      *
1015      * @param array $instances all enrol instances of this type in one course
1016      * @return array of pix_icon
1017      */
1018     public function get_info_icons(array $instances) {
1019         return array();
1020     }
1022     /**
1023      * Returns optional enrolment instance description text.
1024      *
1025      * This is used in detailed course information.
1026      *
1027      *
1028      * @param object $instance
1029      * @return string short html text
1030      */
1031     public function get_description_text($instance) {
1032         return null;
1033     }
1035     /**
1036      * Makes sure config is loaded and cached.
1037      * @return void
1038      */
1039     protected function load_config() {
1040         if (!isset($this->config)) {
1041             $name = $this->get_name();
1042             $this->config = get_config("enrol_$name");
1043         }
1044     }
1046     /**
1047      * Returns plugin config value
1048      * @param  string $name
1049      * @param  string $default value if config does not exist yet
1050      * @return string value or default
1051      */
1052     public function get_config($name, $default = NULL) {
1053         $this->load_config();
1054         return isset($this->config->$name) ? $this->config->$name : $default;
1055     }
1057     /**
1058      * Sets plugin config value
1059      * @param  string $name name of config
1060      * @param  string $value string config value, null means delete
1061      * @return string value
1062      */
1063     public function set_config($name, $value) {
1064         $pluginname = $this->get_name();
1065         $this->load_config();
1066         if ($value === NULL) {
1067             unset($this->config->$name);
1068         } else {
1069             $this->config->$name = $value;
1070         }
1071         set_config($name, $value, "enrol_$pluginname");
1072     }
1074     /**
1075      * Does this plugin assign protected roles are can they be manually removed?
1076      * @return bool - false means anybody may tweak roles, it does not use itemid and component when assigning roles
1077      */
1078     public function roles_protected() {
1079         return true;
1080     }
1082     /**
1083      * Does this plugin allow manual enrolments?
1084      *
1085      * @param stdClass $instance course enrol instance
1086      * All plugins allowing this must implement 'enrol/xxx:enrol' capability
1087      *
1088      * @return bool - true means user with 'enrol/xxx:enrol' may enrol others freely, false means nobody may add more enrolments manually
1089      */
1090     public function allow_enrol(stdClass $instance) {
1091         return false;
1092     }
1094     /**
1095      * Does this plugin allow manual unenrolment of all users?
1096      * All plugins allowing this must implement 'enrol/xxx:unenrol' capability
1097      *
1098      * @param stdClass $instance course enrol instance
1099      * @return bool - true means user with 'enrol/xxx:unenrol' may unenrol others freely, false means nobody may touch user_enrolments
1100      */
1101     public function allow_unenrol(stdClass $instance) {
1102         return false;
1103     }
1105     /**
1106      * Does this plugin allow manual unenrolment of a specific user?
1107      * All plugins allowing this must implement 'enrol/xxx:unenrol' capability
1108      *
1109      * This is useful especially for synchronisation plugins that
1110      * do suspend instead of full unenrolment.
1111      *
1112      * @param stdClass $instance course enrol instance
1113      * @param stdClass $ue record from user_enrolments table, specifies user
1114      *
1115      * @return bool - true means user with 'enrol/xxx:unenrol' may unenrol this user, false means nobody may touch this user enrolment
1116      */
1117     public function allow_unenrol_user(stdClass $instance, stdClass $ue) {
1118         return $this->allow_unenrol($instance);
1119     }
1121     /**
1122      * Does this plugin allow manual changes in user_enrolments table?
1123      *
1124      * All plugins allowing this must implement 'enrol/xxx:manage' capability
1125      *
1126      * @param stdClass $instance course enrol instance
1127      * @return bool - true means it is possible to change enrol period and status in user_enrolments table
1128      */
1129     public function allow_manage(stdClass $instance) {
1130         return false;
1131     }
1133     /**
1134      * Does this plugin support some way to user to self enrol?
1135      *
1136      * @param stdClass $instance course enrol instance
1137      *
1138      * @return bool - true means show "Enrol me in this course" link in course UI
1139      */
1140     public function show_enrolme_link(stdClass $instance) {
1141         return false;
1142     }
1144     /**
1145      * Attempt to automatically enrol current user in course without any interaction,
1146      * calling code has to make sure the plugin and instance are active.
1147      *
1148      * This should return either a timestamp in the future or false.
1149      *
1150      * @param stdClass $instance course enrol instance
1151      * @return bool|int false means not enrolled, integer means timeend
1152      */
1153     public function try_autoenrol(stdClass $instance) {
1154         global $USER;
1156         return false;
1157     }
1159     /**
1160      * Attempt to automatically gain temporary guest access to course,
1161      * calling code has to make sure the plugin and instance are active.
1162      *
1163      * This should return either a timestamp in the future or false.
1164      *
1165      * @param stdClass $instance course enrol instance
1166      * @return bool|int false means no guest access, integer means timeend
1167      */
1168     public function try_guestaccess(stdClass $instance) {
1169         global $USER;
1171         return false;
1172     }
1174     /**
1175      * Enrol user into course via enrol instance.
1176      *
1177      * @param stdClass $instance
1178      * @param int $userid
1179      * @param int $roleid optional role id
1180      * @param int $timestart 0 means unknown
1181      * @param int $timeend 0 means forever
1182      * @param int $status default to ENROL_USER_ACTIVE for new enrolments, no change by default in updates
1183      * @return void
1184      */
1185     public function enrol_user(stdClass $instance, $userid, $roleid = NULL, $timestart = 0, $timeend = 0, $status = NULL) {
1186         global $DB, $USER, $CFG; // CFG necessary!!!
1188         if ($instance->courseid == SITEID) {
1189             throw new coding_exception('invalid attempt to enrol into frontpage course!');
1190         }
1192         $name = $this->get_name();
1193         $courseid = $instance->courseid;
1195         if ($instance->enrol !== $name) {
1196             throw new coding_exception('invalid enrol instance!');
1197         }
1198         $context = get_context_instance(CONTEXT_COURSE, $instance->courseid, MUST_EXIST);
1200         $inserted = false;
1201         $updated  = false;
1202         if ($ue = $DB->get_record('user_enrolments', array('enrolid'=>$instance->id, 'userid'=>$userid))) {
1203             //only update if timestart or timeend or status are different.
1204             if ($ue->timestart != $timestart or $ue->timeend != $timeend or (!is_null($status) and $ue->status != $status)) {
1205                 $ue->timestart    = $timestart;
1206                 $ue->timeend      = $timeend;
1207                 if (!is_null($status)) {
1208                     $ue->status   = $status;
1209                 }
1210                 $ue->modifierid   = $USER->id;
1211                 $ue->timemodified = time();
1212                 $DB->update_record('user_enrolments', $ue);
1214                 $updated = true;
1215             }
1216         } else {
1217             $ue = new stdClass();
1218             $ue->enrolid      = $instance->id;
1219             $ue->status       = is_null($status) ? ENROL_USER_ACTIVE : $status;
1220             $ue->userid       = $userid;
1221             $ue->timestart    = $timestart;
1222             $ue->timeend      = $timeend;
1223             $ue->modifierid   = $USER->id;
1224             $ue->timecreated  = time();
1225             $ue->timemodified = $ue->timecreated;
1226             $ue->id = $DB->insert_record('user_enrolments', $ue);
1228             $inserted = true;
1229         }
1231         if ($inserted) {
1232             // add extra info and trigger event
1233             $ue->courseid  = $courseid;
1234             $ue->enrol     = $name;
1235             events_trigger('user_enrolled', $ue);
1236         } else if ($updated) {
1237             $ue->courseid  = $courseid;
1238             $ue->enrol     = $name;
1239             events_trigger('user_enrol_modified', $ue);
1240             // resets current enrolment caches
1241             $context->mark_dirty();
1242         }
1244         if ($roleid) {
1245             // this must be done after the enrolment event so that the role_assigned event is triggered afterwards
1246             if ($this->roles_protected()) {
1247                 role_assign($roleid, $userid, $context->id, 'enrol_'.$name, $instance->id);
1248             } else {
1249                 role_assign($roleid, $userid, $context->id);
1250             }
1251         }
1253         // reset current user enrolment caching
1254         if ($userid == $USER->id) {
1255             if (isset($USER->enrol['enrolled'][$courseid])) {
1256                 unset($USER->enrol['enrolled'][$courseid]);
1257             }
1258             if (isset($USER->enrol['tempguest'][$courseid])) {
1259                 unset($USER->enrol['tempguest'][$courseid]);
1260                 remove_temp_course_roles($context);
1261             }
1262         }
1263     }
1265     /**
1266      * Store user_enrolments changes and trigger event.
1267      *
1268      * @param stdClass $instance
1269      * @param int $userid
1270      * @param int $status
1271      * @param int $timestart
1272      * @param int $timeend
1273      * @return void
1274      */
1275     public function update_user_enrol(stdClass $instance, $userid, $status = NULL, $timestart = NULL, $timeend = NULL) {
1276         global $DB, $USER;
1278         $name = $this->get_name();
1280         if ($instance->enrol !== $name) {
1281             throw new coding_exception('invalid enrol instance!');
1282         }
1284         if (!$ue = $DB->get_record('user_enrolments', array('enrolid'=>$instance->id, 'userid'=>$userid))) {
1285             // weird, user not enrolled
1286             return;
1287         }
1289         $modified = false;
1290         if (isset($status) and $ue->status != $status) {
1291             $ue->status = $status;
1292             $modified = true;
1293         }
1294         if (isset($timestart) and $ue->timestart != $timestart) {
1295             $ue->timestart = $timestart;
1296             $modified = true;
1297         }
1298         if (isset($timeend) and $ue->timeend != $timeend) {
1299             $ue->timeend = $timeend;
1300             $modified = true;
1301         }
1303         if (!$modified) {
1304             // no change
1305             return;
1306         }
1308         $ue->modifierid = $USER->id;
1309         $DB->update_record('user_enrolments', $ue);
1310         context_course::instance($instance->courseid)->mark_dirty(); // reset enrol caches
1312         // trigger event
1313         $ue->courseid  = $instance->courseid;
1314         $ue->enrol     = $instance->name;
1315         events_trigger('user_enrol_modified', $ue);
1316     }
1318     /**
1319      * Unenrol user from course,
1320      * the last unenrolment removes all remaining roles.
1321      *
1322      * @param stdClass $instance
1323      * @param int $userid
1324      * @return void
1325      */
1326     public function unenrol_user(stdClass $instance, $userid) {
1327         global $CFG, $USER, $DB;
1329         $name = $this->get_name();
1330         $courseid = $instance->courseid;
1332         if ($instance->enrol !== $name) {
1333             throw new coding_exception('invalid enrol instance!');
1334         }
1335         $context = get_context_instance(CONTEXT_COURSE, $instance->courseid, MUST_EXIST);
1337         if (!$ue = $DB->get_record('user_enrolments', array('enrolid'=>$instance->id, 'userid'=>$userid))) {
1338             // weird, user not enrolled
1339             return;
1340         }
1342         role_unassign_all(array('userid'=>$userid, 'contextid'=>$context->id, 'component'=>'enrol_'.$name, 'itemid'=>$instance->id));
1343         $DB->delete_records('user_enrolments', array('id'=>$ue->id));
1345         // add extra info and trigger event
1346         $ue->courseid  = $courseid;
1347         $ue->enrol     = $name;
1349         $sql = "SELECT 'x'
1350                   FROM {user_enrolments} ue
1351                   JOIN {enrol} e ON (e.id = ue.enrolid)
1352                   WHERE ue.userid = :userid AND e.courseid = :courseid";
1353         if ($DB->record_exists_sql($sql, array('userid'=>$userid, 'courseid'=>$courseid))) {
1354             $ue->lastenrol = false;
1355             events_trigger('user_unenrolled', $ue);
1356             // user still has some enrolments, no big cleanup yet
1357         } else {
1358             // the big cleanup IS necessary!
1360             require_once("$CFG->dirroot/group/lib.php");
1361             require_once("$CFG->libdir/gradelib.php");
1363             // remove all remaining roles
1364             role_unassign_all(array('userid'=>$userid, 'contextid'=>$context->id), true, false);
1366             //clean up ALL invisible user data from course if this is the last enrolment - groups, grades, etc.
1367             groups_delete_group_members($courseid, $userid);
1369             grade_user_unenrol($courseid, $userid);
1371             $DB->delete_records('user_lastaccess', array('userid'=>$userid, 'courseid'=>$courseid));
1373             $ue->lastenrol = true; // means user not enrolled any more
1374             events_trigger('user_unenrolled', $ue);
1375         }
1377         // reset all enrol caches
1378         $context->mark_dirty();
1380         // reset current user enrolment caching
1381         if ($userid == $USER->id) {
1382             if (isset($USER->enrol['enrolled'][$courseid])) {
1383                 unset($USER->enrol['enrolled'][$courseid]);
1384             }
1385             if (isset($USER->enrol['tempguest'][$courseid])) {
1386                 unset($USER->enrol['tempguest'][$courseid]);
1387                 remove_temp_course_roles($context);
1388             }
1389         }
1390     }
1392     /**
1393      * Forces synchronisation of user enrolments.
1394      *
1395      * This is important especially for external enrol plugins,
1396      * this function is called for all enabled enrol plugins
1397      * right after every user login.
1398      *
1399      * @param object $user user record
1400      * @return void
1401      */
1402     public function sync_user_enrolments($user) {
1403         // override if necessary
1404     }
1406     /**
1407      * Returns link to page which may be used to add new instance of enrolment plugin in course.
1408      * @param int $courseid
1409      * @return moodle_url page url
1410      */
1411     public function get_newinstance_link($courseid) {
1412         // override for most plugins, check if instance already exists in cases only one instance is supported
1413         return NULL;
1414     }
1416     /**
1417      * Is it possible to delete enrol instance via standard UI?
1418      *
1419      * @param object $instance
1420      * @return bool
1421      */
1422     public function instance_deleteable($instance) {
1423         return true;
1424     }
1426     /**
1427      * Returns link to manual enrol UI if exists.
1428      * Does the access control tests automatically.
1429      *
1430      * @param object $instance
1431      * @return moodle_url
1432      */
1433     public function get_manual_enrol_link($instance) {
1434         return NULL;
1435     }
1437     /**
1438      * Returns list of unenrol links for all enrol instances in course.
1439      *
1440      * @param int $instance
1441      * @return moodle_url or NULL if self unenrolment not supported
1442      */
1443     public function get_unenrolself_link($instance) {
1444         global $USER, $CFG, $DB;
1446         $name = $this->get_name();
1447         if ($instance->enrol !== $name) {
1448             throw new coding_exception('invalid enrol instance!');
1449         }
1451         if ($instance->courseid == SITEID) {
1452             return NULL;
1453         }
1455         if (!enrol_is_enabled($name)) {
1456             return NULL;
1457         }
1459         if ($instance->status != ENROL_INSTANCE_ENABLED) {
1460             return NULL;
1461         }
1463         if (!file_exists("$CFG->dirroot/enrol/$name/unenrolself.php")) {
1464             return NULL;
1465         }
1467         $context = get_context_instance(CONTEXT_COURSE, $instance->courseid, MUST_EXIST);
1469         if (!has_capability("enrol/$name:unenrolself", $context)) {
1470             return NULL;
1471         }
1473         if (!$DB->record_exists('user_enrolments', array('enrolid'=>$instance->id, 'userid'=>$USER->id, 'status'=>ENROL_USER_ACTIVE))) {
1474             return NULL;
1475         }
1477         return new moodle_url("/enrol/$name/unenrolself.php", array('enrolid'=>$instance->id));;
1478     }
1480     /**
1481      * Adds enrol instance UI to course edit form
1482      *
1483      * @param object $instance enrol instance or null if does not exist yet
1484      * @param MoodleQuickForm $mform
1485      * @param object $data
1486      * @param object $context context of existing course or parent category if course does not exist
1487      * @return void
1488      */
1489     public function course_edit_form($instance, MoodleQuickForm $mform, $data, $context) {
1490         // override - usually at least enable/disable switch, has to add own form header
1491     }
1493     /**
1494      * Validates course edit form data
1495      *
1496      * @param object $instance enrol instance or null if does not exist yet
1497      * @param array $data
1498      * @param object $context context of existing course or parent category if course does not exist
1499      * @return array errors array
1500      */
1501     public function course_edit_validation($instance, array $data, $context) {
1502         return array();
1503     }
1505     /**
1506      * Called after updating/inserting course.
1507      *
1508      * @param bool $inserted true if course just inserted
1509      * @param object $course
1510      * @param object $data form data
1511      * @return void
1512      */
1513     public function course_updated($inserted, $course, $data) {
1514         if ($inserted) {
1515             if ($this->get_config('defaultenrol')) {
1516                 $this->add_default_instance($course);
1517             }
1518         }
1519     }
1521     /**
1522      * Add new instance of enrol plugin.
1523      * @param object $course
1524      * @param array instance fields
1525      * @return int id of new instance, null if can not be created
1526      */
1527     public function add_instance($course, array $fields = NULL) {
1528         global $DB;
1530         if ($course->id == SITEID) {
1531             throw new coding_exception('Invalid request to add enrol instance to frontpage.');
1532         }
1534         $instance = new stdClass();
1535         $instance->enrol          = $this->get_name();
1536         $instance->status         = ENROL_INSTANCE_ENABLED;
1537         $instance->courseid       = $course->id;
1538         $instance->enrolstartdate = 0;
1539         $instance->enrolenddate   = 0;
1540         $instance->timemodified   = time();
1541         $instance->timecreated    = $instance->timemodified;
1542         $instance->sortorder      = $DB->get_field('enrol', 'COALESCE(MAX(sortorder), -1) + 1', array('courseid'=>$course->id));
1544         $fields = (array)$fields;
1545         unset($fields['enrol']);
1546         unset($fields['courseid']);
1547         unset($fields['sortorder']);
1548         foreach($fields as $field=>$value) {
1549             $instance->$field = $value;
1550         }
1552         return $DB->insert_record('enrol', $instance);
1553     }
1555     /**
1556      * Add new instance of enrol plugin with default settings,
1557      * called when adding new instance manually or when adding new course.
1558      *
1559      * Not all plugins support this.
1560      *
1561      * @param object $course
1562      * @return int id of new instance or null if no default supported
1563      */
1564     public function add_default_instance($course) {
1565         return null;
1566     }
1568     /**
1569      * Update instance status
1570      *
1571      * Override when plugin needs to do some action when enabled or disabled.
1572      *
1573      * @param stdClass $instance
1574      * @param int $newstatus ENROL_INSTANCE_ENABLED, ENROL_INSTANCE_DISABLED
1575      * @return void
1576      */
1577     public function update_status($instance, $newstatus) {
1578         global $DB;
1580         $instance->status = $newstatus;
1581         $DB->update_record('enrol', $instance);
1583         // invalidate all enrol caches
1584         $context = context_course::instance($instance->courseid);
1585         $context->mark_dirty();
1586     }
1588     /**
1589      * Delete course enrol plugin instance, unenrol all users.
1590      * @param object $instance
1591      * @return void
1592      */
1593     public function delete_instance($instance) {
1594         global $DB;
1596         $name = $this->get_name();
1597         if ($instance->enrol !== $name) {
1598             throw new coding_exception('invalid enrol instance!');
1599         }
1601         //first unenrol all users
1602         $participants = $DB->get_recordset('user_enrolments', array('enrolid'=>$instance->id));
1603         foreach ($participants as $participant) {
1604             $this->unenrol_user($instance, $participant->userid);
1605         }
1606         $participants->close();
1608         // now clean up all remainders that were not removed correctly
1609         $DB->delete_records('role_assignments', array('itemid'=>$instance->id, 'component'=>$name));
1610         $DB->delete_records('user_enrolments', array('enrolid'=>$instance->id));
1612         // finally drop the enrol row
1613         $DB->delete_records('enrol', array('id'=>$instance->id));
1615         // invalidate all enrol caches
1616         $context = context_course::instance($instance->courseid);
1617         $context->mark_dirty();
1618     }
1620     /**
1621      * Creates course enrol form, checks if form submitted
1622      * and enrols user if necessary. It can also redirect.
1623      *
1624      * @param stdClass $instance
1625      * @return string html text, usually a form in a text box
1626      */
1627     public function enrol_page_hook(stdClass $instance) {
1628         return null;
1629     }
1631     /**
1632      * Adds navigation links into course admin block.
1633      *
1634      * By defaults looks for manage links only.
1635      *
1636      * @param navigation_node $instancesnode
1637      * @param stdClass $instance
1638      * @return void
1639      */
1640     public function add_course_navigation($instancesnode, stdClass $instance) {
1641         // usually adds manage users
1642     }
1644     /**
1645      * Returns edit icons for the page with list of instances
1646      * @param stdClass $instance
1647      * @return array
1648      */
1649     public function get_action_icons(stdClass $instance) {
1650         return array();
1651     }
1653     /**
1654      * Reads version.php and determines if it is necessary
1655      * to execute the cron job now.
1656      * @return bool
1657      */
1658     public function is_cron_required() {
1659         global $CFG;
1661         $name = $this->get_name();
1662         $versionfile = "$CFG->dirroot/enrol/$name/version.php";
1663         $plugin = new stdClass();
1664         include($versionfile);
1665         if (empty($plugin->cron)) {
1666             return false;
1667         }
1668         $lastexecuted = $this->get_config('lastcron', 0);
1669         if ($lastexecuted + $plugin->cron < time()) {
1670             return true;
1671         } else {
1672             return false;
1673         }
1674     }
1676     /**
1677      * Called for all enabled enrol plugins that returned true from is_cron_required().
1678      * @return void
1679      */
1680     public function cron() {
1681     }
1683     /**
1684      * Called when user is about to be deleted
1685      * @param object $user
1686      * @return void
1687      */
1688     public function user_delete($user) {
1689         global $DB;
1691         $sql = "SELECT e.*
1692                   FROM {enrol} e
1693                   JOIN {user_enrolments} ue ON (ue.enrolid = e.id)
1694                  WHERE e.enrol = :name AND ue.userid = :userid";
1695         $params = array('name'=>$this->get_name(), 'userid'=>$user->id);
1697         $rs = $DB->get_recordset_sql($sql, $params);
1698         foreach($rs as $instance) {
1699             $this->unenrol_user($instance, $user->id);
1700         }
1701         $rs->close();
1702     }
1704     /**
1705      * Returns an enrol_user_button that takes the user to a page where they are able to
1706      * enrol users into the managers course through this plugin.
1707      *
1708      * Optional: If the plugin supports manual enrolments it can choose to override this
1709      * otherwise it shouldn't
1710      *
1711      * @param course_enrolment_manager $manager
1712      * @return enrol_user_button|false
1713      */
1714     public function get_manual_enrol_button(course_enrolment_manager $manager) {
1715         return false;
1716     }
1718     /**
1719      * Gets an array of the user enrolment actions
1720      *
1721      * @param course_enrolment_manager $manager
1722      * @param stdClass $ue
1723      * @return array An array of user_enrolment_actions
1724      */
1725     public function get_user_enrolment_actions(course_enrolment_manager $manager, $ue) {
1726         return array();
1727     }
1729     /**
1730      * Returns true if the plugin has one or more bulk operations that can be performed on
1731      * user enrolments.
1732      *
1733      * @param course_enrolment_manager $manager
1734      * @return bool
1735      */
1736     public function has_bulk_operations(course_enrolment_manager $manager) {
1737        return false;
1738     }
1740     /**
1741      * Return an array of enrol_bulk_enrolment_operation objects that define
1742      * the bulk actions that can be performed on user enrolments by the plugin.
1743      *
1744      * @param course_enrolment_manager $manager
1745      * @return array
1746      */
1747     public function get_bulk_operations(course_enrolment_manager $manager) {
1748         return array();
1749     }