Merge branch 'w15_MDL-32293_m23_timeout' of git://github.com/skodak/moodle
[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     if (is_siteadmin()) {
208         // no sync for admin user, please use admin accounts only for admin tasks like the unix root user!
209         // if plugin fails on sync admins need to be able to log in and fix the settings
210         return;
211     }
213     static $inprogress = array();  // To prevent this function being called more than once in an invocation
215     if (!empty($inprogress[$user->id])) {
216         return;
217     }
219     $inprogress[$user->id] = true;  // Set the flag
221     $enabled = enrol_get_plugins(true);
223     foreach($enabled as $enrol) {
224         $enrol->sync_user_enrolments($user);
225     }
227     unset($inprogress[$user->id]);  // Unset the flag
230 /**
231  * Do these two students share any course?
232  *
233  * The courses has to be visible and enrolments has to be active,
234  * timestart and timeend restrictions are ignored.
235  *
236  * This function calls {@see enrol_get_shared_courses()} setting checkexistsonly
237  * to true.
238  *
239  * @param stdClass|int $user1
240  * @param stdClass|int $user2
241  * @return bool
242  */
243 function enrol_sharing_course($user1, $user2) {
244     return enrol_get_shared_courses($user1, $user2, false, true);
247 /**
248  * Returns any courses shared by the two users
249  *
250  * The courses has to be visible and enrolments has to be active,
251  * timestart and timeend restrictions are ignored.
252  *
253  * @global moodle_database $DB
254  * @param stdClass|int $user1
255  * @param stdClass|int $user2
256  * @param bool $preloadcontexts If set to true contexts for the returned courses
257  *              will be preloaded.
258  * @param bool $checkexistsonly If set to true then this function will return true
259  *              if the users share any courses and false if not.
260  * @return array|bool An array of courses that both users are enrolled in OR if
261  *              $checkexistsonly set returns true if the users share any courses
262  *              and false if not.
263  */
264 function enrol_get_shared_courses($user1, $user2, $preloadcontexts = false, $checkexistsonly = false) {
265     global $DB, $CFG;
267     $user1 = isset($user1->id) ? $user1->id : $user1;
268     $user2 = isset($user2->id) ? $user2->id : $user2;
270     if (empty($user1) or empty($user2)) {
271         return false;
272     }
274     if (!$plugins = explode(',', $CFG->enrol_plugins_enabled)) {
275         return false;
276     }
278     list($plugins, $params) = $DB->get_in_or_equal($plugins, SQL_PARAMS_NAMED, 'ee');
279     $params['enabled'] = ENROL_INSTANCE_ENABLED;
280     $params['active1'] = ENROL_USER_ACTIVE;
281     $params['active2'] = ENROL_USER_ACTIVE;
282     $params['user1']   = $user1;
283     $params['user2']   = $user2;
285     $ctxselect = '';
286     $ctxjoin = '';
287     if ($preloadcontexts) {
288         list($ctxselect, $ctxjoin) = context_instance_preload_sql('c.id', CONTEXT_COURSE, 'ctx');
289     }
291     $sql = "SELECT c.* $ctxselect
292               FROM {course} c
293               JOIN (
294                 SELECT DISTINCT c.id
295                   FROM {enrol} e
296                   JOIN {user_enrolments} ue1 ON (ue1.enrolid = e.id AND ue1.status = :active1 AND ue1.userid = :user1)
297                   JOIN {user_enrolments} ue2 ON (ue2.enrolid = e.id AND ue2.status = :active2 AND ue2.userid = :user2)
298                   JOIN {course} c ON (c.id = e.courseid AND c.visible = 1)
299                  WHERE e.status = :enabled AND e.enrol $plugins
300               ) ec ON ec.id = c.id
301               $ctxjoin";
303     if ($checkexistsonly) {
304         return $DB->record_exists_sql($sql, $params);
305     } else {
306         $courses = $DB->get_records_sql($sql, $params);
307         if ($preloadcontexts) {
308             array_map('context_instance_preload', $courses);
309         }
310         return $courses;
311     }
314 /**
315  * This function adds necessary enrol plugins UI into the course edit form.
316  *
317  * @param MoodleQuickForm $mform
318  * @param object $data course edit form data
319  * @param object $context context of existing course or parent category if course does not exist
320  * @return void
321  */
322 function enrol_course_edit_form(MoodleQuickForm $mform, $data, $context) {
323     $plugins = enrol_get_plugins(true);
324     if (!empty($data->id)) {
325         $instances = enrol_get_instances($data->id, false);
326         foreach ($instances as $instance) {
327             if (!isset($plugins[$instance->enrol])) {
328                 continue;
329             }
330             $plugin = $plugins[$instance->enrol];
331             $plugin->course_edit_form($instance, $mform, $data, $context);
332         }
333     } else {
334         foreach ($plugins as $plugin) {
335             $plugin->course_edit_form(NULL, $mform, $data, $context);
336         }
337     }
340 /**
341  * Validate course edit form data
342  *
343  * @param array $data raw form data
344  * @param object $context context of existing course or parent category if course does not exist
345  * @return array errors array
346  */
347 function enrol_course_edit_validation(array $data, $context) {
348     $errors = array();
349     $plugins = enrol_get_plugins(true);
351     if (!empty($data['id'])) {
352         $instances = enrol_get_instances($data['id'], false);
353         foreach ($instances as $instance) {
354             if (!isset($plugins[$instance->enrol])) {
355                 continue;
356             }
357             $plugin = $plugins[$instance->enrol];
358             $errors = array_merge($errors, $plugin->course_edit_validation($instance, $data, $context));
359         }
360     } else {
361         foreach ($plugins as $plugin) {
362             $errors = array_merge($errors, $plugin->course_edit_validation(NULL, $data, $context));
363         }
364     }
366     return $errors;
369 /**
370  * Update enrol instances after course edit form submission
371  * @param bool $inserted true means new course added, false course already existed
372  * @param object $course
373  * @param object $data form data
374  * @return void
375  */
376 function enrol_course_updated($inserted, $course, $data) {
377     global $DB, $CFG;
379     $plugins = enrol_get_plugins(true);
381     foreach ($plugins as $plugin) {
382         $plugin->course_updated($inserted, $course, $data);
383     }
386 /**
387  * Add navigation nodes
388  * @param navigation_node $coursenode
389  * @param object $course
390  * @return void
391  */
392 function enrol_add_course_navigation(navigation_node $coursenode, $course) {
393     global $CFG;
395     $coursecontext = get_context_instance(CONTEXT_COURSE, $course->id);
397     $instances = enrol_get_instances($course->id, true);
398     $plugins   = enrol_get_plugins(true);
400     // we do not want to break all course pages if there is some borked enrol plugin, right?
401     foreach ($instances as $k=>$instance) {
402         if (!isset($plugins[$instance->enrol])) {
403             unset($instances[$k]);
404         }
405     }
407     $usersnode = $coursenode->add(get_string('users'), null, navigation_node::TYPE_CONTAINER, null, 'users');
409     if ($course->id != SITEID) {
410         // list all participants - allows assigning roles, groups, etc.
411         if (has_capability('moodle/course:enrolreview', $coursecontext)) {
412             $url = new moodle_url('/enrol/users.php', array('id'=>$course->id));
413             $usersnode->add(get_string('enrolledusers', 'enrol'), $url, navigation_node::TYPE_SETTING, null, 'review', new pix_icon('i/users', ''));
414         }
416         // manage enrol plugin instances
417         if (has_capability('moodle/course:enrolconfig', $coursecontext) or has_capability('moodle/course:enrolreview', $coursecontext)) {
418             $url = new moodle_url('/enrol/instances.php', array('id'=>$course->id));
419         } else {
420             $url = NULL;
421         }
422         $instancesnode = $usersnode->add(get_string('enrolmentinstances', 'enrol'), $url, navigation_node::TYPE_SETTING, null, 'manageinstances');
424         // each instance decides how to configure itself or how many other nav items are exposed
425         foreach ($instances as $instance) {
426             if (!isset($plugins[$instance->enrol])) {
427                 continue;
428             }
429             $plugins[$instance->enrol]->add_course_navigation($instancesnode, $instance);
430         }
432         if (!$url) {
433             $instancesnode->trim_if_empty();
434         }
435     }
437     // Manage groups in this course or even frontpage
438     if (($course->groupmode || !$course->groupmodeforce) && has_capability('moodle/course:managegroups', $coursecontext)) {
439         $url = new moodle_url('/group/index.php', array('id'=>$course->id));
440         $usersnode->add(get_string('groups'), $url, navigation_node::TYPE_SETTING, null, 'groups', new pix_icon('i/group', ''));
441     }
443      if (has_any_capability(array( 'moodle/role:assign', 'moodle/role:safeoverride','moodle/role:override', 'moodle/role:review'), $coursecontext)) {
444         // Override roles
445         if (has_capability('moodle/role:review', $coursecontext)) {
446             $url = new moodle_url('/admin/roles/permissions.php', array('contextid'=>$coursecontext->id));
447         } else {
448             $url = NULL;
449         }
450         $permissionsnode = $usersnode->add(get_string('permissions', 'role'), $url, navigation_node::TYPE_SETTING, null, 'override');
452         // Add assign or override roles if allowed
453         if ($course->id == SITEID or (!empty($CFG->adminsassignrolesincourse) and is_siteadmin())) {
454             if (has_capability('moodle/role:assign', $coursecontext)) {
455                 $url = new moodle_url('/admin/roles/assign.php', array('contextid'=>$coursecontext->id));
456                 $permissionsnode->add(get_string('assignedroles', 'role'), $url, navigation_node::TYPE_SETTING, null, 'roles', new pix_icon('i/roles', ''));
457             }
458         }
459         // Check role permissions
460         if (has_any_capability(array('moodle/role:assign', 'moodle/role:safeoverride','moodle/role:override', 'moodle/role:assign'), $coursecontext)) {
461             $url = new moodle_url('/admin/roles/check.php', array('contextid'=>$coursecontext->id));
462             $permissionsnode->add(get_string('checkpermissions', 'role'), $url, navigation_node::TYPE_SETTING, null, 'permissions', new pix_icon('i/checkpermissions', ''));
463         }
464      }
466      // Deal somehow with users that are not enrolled but still got a role somehow
467     if ($course->id != SITEID) {
468         //TODO, create some new UI for role assignments at course level
469         if (has_capability('moodle/role:assign', $coursecontext)) {
470             $url = new moodle_url('/enrol/otherusers.php', array('id'=>$course->id));
471             $usersnode->add(get_string('notenrolledusers', 'enrol'), $url, navigation_node::TYPE_SETTING, null, 'otherusers', new pix_icon('i/roles', ''));
472         }
473     }
475     // just in case nothing was actually added
476     $usersnode->trim_if_empty();
478     if ($course->id != SITEID) {
479         if (isguestuser() or !isloggedin()) {
480             // guest account can not be enrolled - no links for them
481         } else if (is_enrolled($coursecontext)) {
482             // unenrol link if possible
483             foreach ($instances as $instance) {
484                 if (!isset($plugins[$instance->enrol])) {
485                     continue;
486                 }
487                 $plugin = $plugins[$instance->enrol];
488                 if ($unenrollink = $plugin->get_unenrolself_link($instance)) {
489                     $shortname = format_string($course->shortname, true, array('context' => $coursecontext));
490                     $coursenode->add(get_string('unenrolme', 'core_enrol', $shortname), $unenrollink, navigation_node::TYPE_SETTING, null, 'unenrolself', new pix_icon('i/user', ''));
491                     break;
492                     //TODO. deal with multiple unenrol links - not likely case, but still...
493                 }
494             }
495         } else {
496             // enrol link if possible
497             if (is_viewing($coursecontext)) {
498                 // better not show any enrol link, this is intended for managers and inspectors
499             } else {
500                 foreach ($instances as $instance) {
501                     if (!isset($plugins[$instance->enrol])) {
502                         continue;
503                     }
504                     $plugin = $plugins[$instance->enrol];
505                     if ($plugin->show_enrolme_link($instance)) {
506                         $url = new moodle_url('/enrol/index.php', array('id'=>$course->id));
507                         $shortname = format_string($course->shortname, true, array('context' => $coursecontext));
508                         $coursenode->add(get_string('enrolme', 'core_enrol', $shortname), $url, navigation_node::TYPE_SETTING, null, 'enrolself', new pix_icon('i/user', ''));
509                         break;
510                     }
511                 }
512             }
513         }
514     }
517 /**
518  * Returns list of courses current $USER is enrolled in and can access
519  *
520  * - $fields is an array of field names to ADD
521  *   so name the fields you really need, which will
522  *   be added and uniq'd
523  *
524  * @param string|array $fields
525  * @param string $sort
526  * @param int $limit max number of courses
527  * @return array
528  */
529 function enrol_get_my_courses($fields = NULL, $sort = 'visible DESC,sortorder ASC', $limit = 0) {
530     global $DB, $USER;
532     // Guest account does not have any courses
533     if (isguestuser() or !isloggedin()) {
534         return(array());
535     }
537     $basefields = array('id', 'category', 'sortorder',
538                         'shortname', 'fullname', 'idnumber',
539                         'startdate', 'visible',
540                         'groupmode', 'groupmodeforce');
542     if (empty($fields)) {
543         $fields = $basefields;
544     } else if (is_string($fields)) {
545         // turn the fields from a string to an array
546         $fields = explode(',', $fields);
547         $fields = array_map('trim', $fields);
548         $fields = array_unique(array_merge($basefields, $fields));
549     } else if (is_array($fields)) {
550         $fields = array_unique(array_merge($basefields, $fields));
551     } else {
552         throw new coding_exception('Invalid $fileds parameter in enrol_get_my_courses()');
553     }
554     if (in_array('*', $fields)) {
555         $fields = array('*');
556     }
558     $orderby = "";
559     $sort    = trim($sort);
560     if (!empty($sort)) {
561         $rawsorts = explode(',', $sort);
562         $sorts = array();
563         foreach ($rawsorts as $rawsort) {
564             $rawsort = trim($rawsort);
565             if (strpos($rawsort, 'c.') === 0) {
566                 $rawsort = substr($rawsort, 2);
567             }
568             $sorts[] = trim($rawsort);
569         }
570         $sort = 'c.'.implode(',c.', $sorts);
571         $orderby = "ORDER BY $sort";
572     }
574     $wheres = array("c.id <> :siteid");
575     $params = array('siteid'=>SITEID);
577     if (isset($USER->loginascontext) and $USER->loginascontext->contextlevel == CONTEXT_COURSE) {
578         // list _only_ this course - anything else is asking for trouble...
579         $wheres[] = "courseid = :loginas";
580         $params['loginas'] = $USER->loginascontext->instanceid;
581     }
583     $coursefields = 'c.' .join(',c.', $fields);
584     list($ccselect, $ccjoin) = context_instance_preload_sql('c.id', CONTEXT_COURSE, 'ctx');
585     $wheres = implode(" AND ", $wheres);
587     //note: we can not use DISTINCT + text fields due to Oracle and MS limitations, that is why we have the subselect there
588     $sql = "SELECT $coursefields $ccselect
589               FROM {course} c
590               JOIN (SELECT DISTINCT e.courseid
591                       FROM {enrol} e
592                       JOIN {user_enrolments} ue ON (ue.enrolid = e.id AND ue.userid = :userid)
593                      WHERE ue.status = :active AND e.status = :enabled AND ue.timestart < :now1 AND (ue.timeend = 0 OR ue.timeend > :now2)
594                    ) en ON (en.courseid = c.id)
595            $ccjoin
596              WHERE $wheres
597           $orderby";
598     $params['userid']  = $USER->id;
599     $params['active']  = ENROL_USER_ACTIVE;
600     $params['enabled'] = ENROL_INSTANCE_ENABLED;
601     $params['now1']    = round(time(), -2); // improves db caching
602     $params['now2']    = $params['now1'];
604     $courses = $DB->get_records_sql($sql, $params, 0, $limit);
606     // preload contexts and check visibility
607     foreach ($courses as $id=>$course) {
608         context_instance_preload($course);
609         if (!$course->visible) {
610             if (!$context = get_context_instance(CONTEXT_COURSE, $id)) {
611                 unset($courses[$id]);
612                 continue;
613             }
614             if (!has_capability('moodle/course:viewhiddencourses', $context)) {
615                 unset($courses[$id]);
616                 continue;
617             }
618         }
619         $courses[$id] = $course;
620     }
622     //wow! Is that really all? :-D
624     return $courses;
627 /**
628  * Returns course enrolment information icons.
629  *
630  * @param object $course
631  * @param array $instances enrol instances of this course, improves performance
632  * @return array of pix_icon
633  */
634 function enrol_get_course_info_icons($course, array $instances = NULL) {
635     $icons = array();
636     if (is_null($instances)) {
637         $instances = enrol_get_instances($course->id, true);
638     }
639     $plugins = enrol_get_plugins(true);
640     foreach ($plugins as $name => $plugin) {
641         $pis = array();
642         foreach ($instances as $instance) {
643             if ($instance->status != ENROL_INSTANCE_ENABLED or $instance->courseid != $course->id) {
644                 debugging('Invalid instances parameter submitted in enrol_get_info_icons()');
645                 continue;
646             }
647             if ($instance->enrol == $name) {
648                 $pis[$instance->id] = $instance;
649             }
650         }
651         if ($pis) {
652             $icons = array_merge($icons, $plugin->get_info_icons($pis));
653         }
654     }
655     return $icons;
658 /**
659  * Returns course enrolment detailed information.
660  *
661  * @param object $course
662  * @return array of html fragments - can be used to construct lists
663  */
664 function enrol_get_course_description_texts($course) {
665     $lines = array();
666     $instances = enrol_get_instances($course->id, true);
667     $plugins = enrol_get_plugins(true);
668     foreach ($instances as $instance) {
669         if (!isset($plugins[$instance->enrol])) {
670             //weird
671             continue;
672         }
673         $plugin = $plugins[$instance->enrol];
674         $text = $plugin->get_description_text($instance);
675         if ($text !== NULL) {
676             $lines[] = $text;
677         }
678     }
679     return $lines;
682 /**
683  * Returns list of courses user is enrolled into.
684  *
685  * - $fields is an array of fieldnames to ADD
686  *   so name the fields you really need, which will
687  *   be added and uniq'd
688  *
689  * @param int $userid
690  * @param bool $onlyactive return only active enrolments in courses user may see
691  * @param string|array $fields
692  * @param string $sort
693  * @return array
694  */
695 function enrol_get_users_courses($userid, $onlyactive = false, $fields = NULL, $sort = 'visible DESC,sortorder ASC') {
696     global $DB;
698     // Guest account does not have any courses
699     if (isguestuser($userid) or empty($userid)) {
700         return(array());
701     }
703     $basefields = array('id', 'category', 'sortorder',
704                         'shortname', 'fullname', 'idnumber',
705                         'startdate', 'visible',
706                         'groupmode', 'groupmodeforce');
708     if (empty($fields)) {
709         $fields = $basefields;
710     } else if (is_string($fields)) {
711         // turn the fields from a string to an array
712         $fields = explode(',', $fields);
713         $fields = array_map('trim', $fields);
714         $fields = array_unique(array_merge($basefields, $fields));
715     } else if (is_array($fields)) {
716         $fields = array_unique(array_merge($basefields, $fields));
717     } else {
718         throw new coding_exception('Invalid $fileds parameter in enrol_get_my_courses()');
719     }
720     if (in_array('*', $fields)) {
721         $fields = array('*');
722     }
724     $orderby = "";
725     $sort    = trim($sort);
726     if (!empty($sort)) {
727         $rawsorts = explode(',', $sort);
728         $sorts = array();
729         foreach ($rawsorts as $rawsort) {
730             $rawsort = trim($rawsort);
731             if (strpos($rawsort, 'c.') === 0) {
732                 $rawsort = substr($rawsort, 2);
733             }
734             $sorts[] = trim($rawsort);
735         }
736         $sort = 'c.'.implode(',c.', $sorts);
737         $orderby = "ORDER BY $sort";
738     }
740     $params = array('siteid'=>SITEID);
742     if ($onlyactive) {
743         $subwhere = "WHERE ue.status = :active AND e.status = :enabled AND ue.timestart < :now1 AND (ue.timeend = 0 OR ue.timeend > :now2)";
744         $params['now1']    = round(time(), -2); // improves db caching
745         $params['now2']    = $params['now1'];
746         $params['active']  = ENROL_USER_ACTIVE;
747         $params['enabled'] = ENROL_INSTANCE_ENABLED;
748     } else {
749         $subwhere = "";
750     }
752     $coursefields = 'c.' .join(',c.', $fields);
753     list($ccselect, $ccjoin) = context_instance_preload_sql('c.id', CONTEXT_COURSE, 'ctx');
755     //note: we can not use DISTINCT + text fields due to Oracle and MS limitations, that is why we have the subselect there
756     $sql = "SELECT $coursefields $ccselect
757               FROM {course} c
758               JOIN (SELECT DISTINCT e.courseid
759                       FROM {enrol} e
760                       JOIN {user_enrolments} ue ON (ue.enrolid = e.id AND ue.userid = :userid)
761                  $subwhere
762                    ) en ON (en.courseid = c.id)
763            $ccjoin
764              WHERE c.id <> :siteid
765           $orderby";
766     $params['userid']  = $userid;
768     $courses = $DB->get_records_sql($sql, $params);
770     // preload contexts and check visibility
771     foreach ($courses as $id=>$course) {
772         context_instance_preload($course);
773         if ($onlyactive) {
774             if (!$course->visible) {
775                 if (!$context = get_context_instance(CONTEXT_COURSE, $id)) {
776                     unset($courses[$id]);
777                     continue;
778                 }
779                 if (!has_capability('moodle/course:viewhiddencourses', $context, $userid)) {
780                     unset($courses[$id]);
781                     continue;
782                 }
783             }
784         }
785         $courses[$id] = $course;
786     }
788     //wow! Is that really all? :-D
790     return $courses;
794 /**
795  * Called when user is about to be deleted.
796  * @param object $user
797  * @return void
798  */
799 function enrol_user_delete($user) {
800     global $DB;
802     $plugins = enrol_get_plugins(true);
803     foreach ($plugins as $plugin) {
804         $plugin->user_delete($user);
805     }
807     // force cleanup of all broken enrolments
808     $DB->delete_records('user_enrolments', array('userid'=>$user->id));
811 /**
812  * Called when course is about to be deleted.
813  * @param stdClass $course
814  * @return void
815  */
816 function enrol_course_delete($course) {
817     global $DB;
819     $instances = enrol_get_instances($course->id, false);
820     $plugins = enrol_get_plugins(true);
821     foreach ($instances as $instance) {
822         if (isset($plugins[$instance->enrol])) {
823             $plugins[$instance->enrol]->delete_instance($instance);
824         }
825         // low level delete in case plugin did not do it
826         $DB->delete_records('user_enrolments', array('enrolid'=>$instance->id));
827         $DB->delete_records('role_assignments', array('itemid'=>$instance->id, 'component'=>'enrol_'.$instance->enrol));
828         $DB->delete_records('user_enrolments', array('enrolid'=>$instance->id));
829         $DB->delete_records('enrol', array('id'=>$instance->id));
830     }
833 /**
834  * Try to enrol user via default internal auth plugin.
835  *
836  * For now this is always using the manual enrol plugin...
837  *
838  * @param $courseid
839  * @param $userid
840  * @param $roleid
841  * @param $timestart
842  * @param $timeend
843  * @return bool success
844  */
845 function enrol_try_internal_enrol($courseid, $userid, $roleid = null, $timestart = 0, $timeend = 0) {
846     global $DB;
848     //note: this is hardcoded to manual plugin for now
850     if (!enrol_is_enabled('manual')) {
851         return false;
852     }
854     if (!$enrol = enrol_get_plugin('manual')) {
855         return false;
856     }
857     if (!$instances = $DB->get_records('enrol', array('enrol'=>'manual', 'courseid'=>$courseid, 'status'=>ENROL_INSTANCE_ENABLED), 'sortorder,id ASC')) {
858         return false;
859     }
860     $instance = reset($instances);
862     $enrol->enrol_user($instance, $userid, $roleid, $timestart, $timeend);
864     return true;
867 /**
868  * Is there a chance users might self enrol
869  * @param int $courseid
870  * @return bool
871  */
872 function enrol_selfenrol_available($courseid) {
873     $result = false;
875     $plugins = enrol_get_plugins(true);
876     $enrolinstances = enrol_get_instances($courseid, true);
877     foreach($enrolinstances as $instance) {
878         if (!isset($plugins[$instance->enrol])) {
879             continue;
880         }
881         if ($instance->enrol === 'guest') {
882             // blacklist known temporary guest plugins
883             continue;
884         }
885         if ($plugins[$instance->enrol]->show_enrolme_link($instance)) {
886             $result = true;
887             break;
888         }
889     }
891     return $result;
894 /**
895  * This function returns the end of current active user enrolment.
896  *
897  * It deals correctly with multiple overlapping user enrolments.
898  *
899  * @param int $courseid
900  * @param int $userid
901  * @return int|bool timestamp when active enrolment ends, false means no active enrolment now, 0 means never
902  */
903 function enrol_get_enrolment_end($courseid, $userid) {
904     global $DB;
906     $sql = "SELECT ue.*
907               FROM {user_enrolments} ue
908               JOIN {enrol} e ON (e.id = ue.enrolid AND e.courseid = :courseid)
909               JOIN {user} u ON u.id = ue.userid
910              WHERE ue.userid = :userid AND ue.status = :active AND e.status = :enabled AND u.deleted = 0";
911     $params = array('enabled'=>ENROL_INSTANCE_ENABLED, 'active'=>ENROL_USER_ACTIVE, 'userid'=>$userid, 'courseid'=>$courseid);
913     if (!$enrolments = $DB->get_records_sql($sql, $params)) {
914         return false;
915     }
917     $changes = array();
919     foreach ($enrolments as $ue) {
920         $start = (int)$ue->timestart;
921         $end = (int)$ue->timeend;
922         if ($end != 0 and $end < $start) {
923             debugging('Invalid enrolment start or end in user_enrolment id:'.$ue->id);
924             continue;
925         }
926         if (isset($changes[$start])) {
927             $changes[$start] = $changes[$start] + 1;
928         } else {
929             $changes[$start] = 1;
930         }
931         if ($end === 0) {
932             // no end
933         } else if (isset($changes[$end])) {
934             $changes[$end] = $changes[$end] - 1;
935         } else {
936             $changes[$end] = -1;
937         }
938     }
940     // let's sort then enrolment starts&ends and go through them chronologically,
941     // looking for current status and the next future end of enrolment
942     ksort($changes);
944     $now = time();
945     $current = 0;
946     $present = null;
948     foreach ($changes as $time => $change) {
949         if ($time > $now) {
950             if ($present === null) {
951                 // we have just went past current time
952                 $present = $current;
953                 if ($present < 1) {
954                     // no enrolment active
955                     return false;
956                 }
957             }
958             if ($present !== null) {
959                 // we are already in the future - look for possible end
960                 if ($current + $change < 1) {
961                     return $time;
962                 }
963             }
964         }
965         $current += $change;
966     }
968     if ($current > 0) {
969         return 0;
970     } else {
971         return false;
972     }
976 /**
977  * All enrol plugins should be based on this class,
978  * this is also the main source of documentation.
979  */
980 abstract class enrol_plugin {
981     protected $config = null;
983     /**
984      * Returns name of this enrol plugin
985      * @return string
986      */
987     public function get_name() {
988         // second word in class is always enrol name, sorry, no fancy plugin names with _
989         $words = explode('_', get_class($this));
990         return $words[1];
991     }
993     /**
994      * Returns localised name of enrol instance
995      *
996      * @param object $instance (null is accepted too)
997      * @return string
998      */
999     public function get_instance_name($instance) {
1000         if (empty($instance->name)) {
1001             $enrol = $this->get_name();
1002             return get_string('pluginname', 'enrol_'.$enrol);
1003         } else {
1004             $context = get_context_instance(CONTEXT_COURSE, $instance->courseid);
1005             return format_string($instance->name, true, array('context'=>$context));
1006         }
1007     }
1009     /**
1010      * Returns optional enrolment information icons.
1011      *
1012      * This is used in course list for quick overview of enrolment options.
1013      *
1014      * We are not using single instance parameter because sometimes
1015      * we might want to prevent icon repetition when multiple instances
1016      * of one type exist. One instance may also produce several icons.
1017      *
1018      * @param array $instances all enrol instances of this type in one course
1019      * @return array of pix_icon
1020      */
1021     public function get_info_icons(array $instances) {
1022         return array();
1023     }
1025     /**
1026      * Returns optional enrolment instance description text.
1027      *
1028      * This is used in detailed course information.
1029      *
1030      *
1031      * @param object $instance
1032      * @return string short html text
1033      */
1034     public function get_description_text($instance) {
1035         return null;
1036     }
1038     /**
1039      * Makes sure config is loaded and cached.
1040      * @return void
1041      */
1042     protected function load_config() {
1043         if (!isset($this->config)) {
1044             $name = $this->get_name();
1045             $this->config = get_config("enrol_$name");
1046         }
1047     }
1049     /**
1050      * Returns plugin config value
1051      * @param  string $name
1052      * @param  string $default value if config does not exist yet
1053      * @return string value or default
1054      */
1055     public function get_config($name, $default = NULL) {
1056         $this->load_config();
1057         return isset($this->config->$name) ? $this->config->$name : $default;
1058     }
1060     /**
1061      * Sets plugin config value
1062      * @param  string $name name of config
1063      * @param  string $value string config value, null means delete
1064      * @return string value
1065      */
1066     public function set_config($name, $value) {
1067         $pluginname = $this->get_name();
1068         $this->load_config();
1069         if ($value === NULL) {
1070             unset($this->config->$name);
1071         } else {
1072             $this->config->$name = $value;
1073         }
1074         set_config($name, $value, "enrol_$pluginname");
1075     }
1077     /**
1078      * Does this plugin assign protected roles are can they be manually removed?
1079      * @return bool - false means anybody may tweak roles, it does not use itemid and component when assigning roles
1080      */
1081     public function roles_protected() {
1082         return true;
1083     }
1085     /**
1086      * Does this plugin allow manual enrolments?
1087      *
1088      * @param stdClass $instance course enrol instance
1089      * All plugins allowing this must implement 'enrol/xxx:enrol' capability
1090      *
1091      * @return bool - true means user with 'enrol/xxx:enrol' may enrol others freely, false means nobody may add more enrolments manually
1092      */
1093     public function allow_enrol(stdClass $instance) {
1094         return false;
1095     }
1097     /**
1098      * Does this plugin allow manual unenrolment of all users?
1099      * All plugins allowing this must implement 'enrol/xxx:unenrol' capability
1100      *
1101      * @param stdClass $instance course enrol instance
1102      * @return bool - true means user with 'enrol/xxx:unenrol' may unenrol others freely, false means nobody may touch user_enrolments
1103      */
1104     public function allow_unenrol(stdClass $instance) {
1105         return false;
1106     }
1108     /**
1109      * Does this plugin allow manual unenrolment of a specific user?
1110      * All plugins allowing this must implement 'enrol/xxx:unenrol' capability
1111      *
1112      * This is useful especially for synchronisation plugins that
1113      * do suspend instead of full unenrolment.
1114      *
1115      * @param stdClass $instance course enrol instance
1116      * @param stdClass $ue record from user_enrolments table, specifies user
1117      *
1118      * @return bool - true means user with 'enrol/xxx:unenrol' may unenrol this user, false means nobody may touch this user enrolment
1119      */
1120     public function allow_unenrol_user(stdClass $instance, stdClass $ue) {
1121         return $this->allow_unenrol($instance);
1122     }
1124     /**
1125      * Does this plugin allow manual changes in user_enrolments table?
1126      *
1127      * All plugins allowing this must implement 'enrol/xxx:manage' capability
1128      *
1129      * @param stdClass $instance course enrol instance
1130      * @return bool - true means it is possible to change enrol period and status in user_enrolments table
1131      */
1132     public function allow_manage(stdClass $instance) {
1133         return false;
1134     }
1136     /**
1137      * Does this plugin support some way to user to self enrol?
1138      *
1139      * @param stdClass $instance course enrol instance
1140      *
1141      * @return bool - true means show "Enrol me in this course" link in course UI
1142      */
1143     public function show_enrolme_link(stdClass $instance) {
1144         return false;
1145     }
1147     /**
1148      * Attempt to automatically enrol current user in course without any interaction,
1149      * calling code has to make sure the plugin and instance are active.
1150      *
1151      * This should return either a timestamp in the future or false.
1152      *
1153      * @param stdClass $instance course enrol instance
1154      * @return bool|int false means not enrolled, integer means timeend
1155      */
1156     public function try_autoenrol(stdClass $instance) {
1157         global $USER;
1159         return false;
1160     }
1162     /**
1163      * Attempt to automatically gain temporary guest access to course,
1164      * calling code has to make sure the plugin and instance are active.
1165      *
1166      * This should return either a timestamp in the future or false.
1167      *
1168      * @param stdClass $instance course enrol instance
1169      * @return bool|int false means no guest access, integer means timeend
1170      */
1171     public function try_guestaccess(stdClass $instance) {
1172         global $USER;
1174         return false;
1175     }
1177     /**
1178      * Enrol user into course via enrol instance.
1179      *
1180      * @param stdClass $instance
1181      * @param int $userid
1182      * @param int $roleid optional role id
1183      * @param int $timestart 0 means unknown
1184      * @param int $timeend 0 means forever
1185      * @param int $status default to ENROL_USER_ACTIVE for new enrolments, no change by default in updates
1186      * @return void
1187      */
1188     public function enrol_user(stdClass $instance, $userid, $roleid = NULL, $timestart = 0, $timeend = 0, $status = NULL) {
1189         global $DB, $USER, $CFG; // CFG necessary!!!
1191         if ($instance->courseid == SITEID) {
1192             throw new coding_exception('invalid attempt to enrol into frontpage course!');
1193         }
1195         $name = $this->get_name();
1196         $courseid = $instance->courseid;
1198         if ($instance->enrol !== $name) {
1199             throw new coding_exception('invalid enrol instance!');
1200         }
1201         $context = get_context_instance(CONTEXT_COURSE, $instance->courseid, MUST_EXIST);
1203         $inserted = false;
1204         $updated  = false;
1205         if ($ue = $DB->get_record('user_enrolments', array('enrolid'=>$instance->id, 'userid'=>$userid))) {
1206             //only update if timestart or timeend or status are different.
1207             if ($ue->timestart != $timestart or $ue->timeend != $timeend or (!is_null($status) and $ue->status != $status)) {
1208                 $ue->timestart    = $timestart;
1209                 $ue->timeend      = $timeend;
1210                 if (!is_null($status)) {
1211                     $ue->status   = $status;
1212                 }
1213                 $ue->modifierid   = $USER->id;
1214                 $ue->timemodified = time();
1215                 $DB->update_record('user_enrolments', $ue);
1217                 $updated = true;
1218             }
1219         } else {
1220             $ue = new stdClass();
1221             $ue->enrolid      = $instance->id;
1222             $ue->status       = is_null($status) ? ENROL_USER_ACTIVE : $status;
1223             $ue->userid       = $userid;
1224             $ue->timestart    = $timestart;
1225             $ue->timeend      = $timeend;
1226             $ue->modifierid   = $USER->id;
1227             $ue->timecreated  = time();
1228             $ue->timemodified = $ue->timecreated;
1229             $ue->id = $DB->insert_record('user_enrolments', $ue);
1231             $inserted = true;
1232         }
1234         if ($inserted) {
1235             // add extra info and trigger event
1236             $ue->courseid  = $courseid;
1237             $ue->enrol     = $name;
1238             events_trigger('user_enrolled', $ue);
1239         } else if ($updated) {
1240             $ue->courseid  = $courseid;
1241             $ue->enrol     = $name;
1242             events_trigger('user_enrol_modified', $ue);
1243             // resets current enrolment caches
1244             $context->mark_dirty();
1245         }
1247         if ($roleid) {
1248             // this must be done after the enrolment event so that the role_assigned event is triggered afterwards
1249             if ($this->roles_protected()) {
1250                 role_assign($roleid, $userid, $context->id, 'enrol_'.$name, $instance->id);
1251             } else {
1252                 role_assign($roleid, $userid, $context->id);
1253             }
1254         }
1256         // reset current user enrolment caching
1257         if ($userid == $USER->id) {
1258             if (isset($USER->enrol['enrolled'][$courseid])) {
1259                 unset($USER->enrol['enrolled'][$courseid]);
1260             }
1261             if (isset($USER->enrol['tempguest'][$courseid])) {
1262                 unset($USER->enrol['tempguest'][$courseid]);
1263                 remove_temp_course_roles($context);
1264             }
1265         }
1266     }
1268     /**
1269      * Store user_enrolments changes and trigger event.
1270      *
1271      * @param stdClass $instance
1272      * @param int $userid
1273      * @param int $status
1274      * @param int $timestart
1275      * @param int $timeend
1276      * @return void
1277      */
1278     public function update_user_enrol(stdClass $instance, $userid, $status = NULL, $timestart = NULL, $timeend = NULL) {
1279         global $DB, $USER;
1281         $name = $this->get_name();
1283         if ($instance->enrol !== $name) {
1284             throw new coding_exception('invalid enrol instance!');
1285         }
1287         if (!$ue = $DB->get_record('user_enrolments', array('enrolid'=>$instance->id, 'userid'=>$userid))) {
1288             // weird, user not enrolled
1289             return;
1290         }
1292         $modified = false;
1293         if (isset($status) and $ue->status != $status) {
1294             $ue->status = $status;
1295             $modified = true;
1296         }
1297         if (isset($timestart) and $ue->timestart != $timestart) {
1298             $ue->timestart = $timestart;
1299             $modified = true;
1300         }
1301         if (isset($timeend) and $ue->timeend != $timeend) {
1302             $ue->timeend = $timeend;
1303             $modified = true;
1304         }
1306         if (!$modified) {
1307             // no change
1308             return;
1309         }
1311         $ue->modifierid = $USER->id;
1312         $DB->update_record('user_enrolments', $ue);
1313         context_course::instance($instance->courseid)->mark_dirty(); // reset enrol caches
1315         // trigger event
1316         $ue->courseid  = $instance->courseid;
1317         $ue->enrol     = $instance->name;
1318         events_trigger('user_enrol_modified', $ue);
1319     }
1321     /**
1322      * Unenrol user from course,
1323      * the last unenrolment removes all remaining roles.
1324      *
1325      * @param stdClass $instance
1326      * @param int $userid
1327      * @return void
1328      */
1329     public function unenrol_user(stdClass $instance, $userid) {
1330         global $CFG, $USER, $DB;
1332         $name = $this->get_name();
1333         $courseid = $instance->courseid;
1335         if ($instance->enrol !== $name) {
1336             throw new coding_exception('invalid enrol instance!');
1337         }
1338         $context = get_context_instance(CONTEXT_COURSE, $instance->courseid, MUST_EXIST);
1340         if (!$ue = $DB->get_record('user_enrolments', array('enrolid'=>$instance->id, 'userid'=>$userid))) {
1341             // weird, user not enrolled
1342             return;
1343         }
1345         role_unassign_all(array('userid'=>$userid, 'contextid'=>$context->id, 'component'=>'enrol_'.$name, 'itemid'=>$instance->id));
1346         $DB->delete_records('user_enrolments', array('id'=>$ue->id));
1348         // add extra info and trigger event
1349         $ue->courseid  = $courseid;
1350         $ue->enrol     = $name;
1352         $sql = "SELECT 'x'
1353                   FROM {user_enrolments} ue
1354                   JOIN {enrol} e ON (e.id = ue.enrolid)
1355                   WHERE ue.userid = :userid AND e.courseid = :courseid";
1356         if ($DB->record_exists_sql($sql, array('userid'=>$userid, 'courseid'=>$courseid))) {
1357             $ue->lastenrol = false;
1358             events_trigger('user_unenrolled', $ue);
1359             // user still has some enrolments, no big cleanup yet
1360         } else {
1361             // the big cleanup IS necessary!
1363             require_once("$CFG->dirroot/group/lib.php");
1364             require_once("$CFG->libdir/gradelib.php");
1366             // remove all remaining roles
1367             role_unassign_all(array('userid'=>$userid, 'contextid'=>$context->id), true, false);
1369             //clean up ALL invisible user data from course if this is the last enrolment - groups, grades, etc.
1370             groups_delete_group_members($courseid, $userid);
1372             grade_user_unenrol($courseid, $userid);
1374             $DB->delete_records('user_lastaccess', array('userid'=>$userid, 'courseid'=>$courseid));
1376             $ue->lastenrol = true; // means user not enrolled any more
1377             events_trigger('user_unenrolled', $ue);
1378         }
1380         // reset all enrol caches
1381         $context->mark_dirty();
1383         // reset current user enrolment caching
1384         if ($userid == $USER->id) {
1385             if (isset($USER->enrol['enrolled'][$courseid])) {
1386                 unset($USER->enrol['enrolled'][$courseid]);
1387             }
1388             if (isset($USER->enrol['tempguest'][$courseid])) {
1389                 unset($USER->enrol['tempguest'][$courseid]);
1390                 remove_temp_course_roles($context);
1391             }
1392         }
1393     }
1395     /**
1396      * Forces synchronisation of user enrolments.
1397      *
1398      * This is important especially for external enrol plugins,
1399      * this function is called for all enabled enrol plugins
1400      * right after every user login.
1401      *
1402      * @param object $user user record
1403      * @return void
1404      */
1405     public function sync_user_enrolments($user) {
1406         // override if necessary
1407     }
1409     /**
1410      * Returns link to page which may be used to add new instance of enrolment plugin in course.
1411      * @param int $courseid
1412      * @return moodle_url page url
1413      */
1414     public function get_newinstance_link($courseid) {
1415         // override for most plugins, check if instance already exists in cases only one instance is supported
1416         return NULL;
1417     }
1419     /**
1420      * Is it possible to delete enrol instance via standard UI?
1421      *
1422      * @param object $instance
1423      * @return bool
1424      */
1425     public function instance_deleteable($instance) {
1426         return true;
1427     }
1429     /**
1430      * Returns link to manual enrol UI if exists.
1431      * Does the access control tests automatically.
1432      *
1433      * @param object $instance
1434      * @return moodle_url
1435      */
1436     public function get_manual_enrol_link($instance) {
1437         return NULL;
1438     }
1440     /**
1441      * Returns list of unenrol links for all enrol instances in course.
1442      *
1443      * @param int $instance
1444      * @return moodle_url or NULL if self unenrolment not supported
1445      */
1446     public function get_unenrolself_link($instance) {
1447         global $USER, $CFG, $DB;
1449         $name = $this->get_name();
1450         if ($instance->enrol !== $name) {
1451             throw new coding_exception('invalid enrol instance!');
1452         }
1454         if ($instance->courseid == SITEID) {
1455             return NULL;
1456         }
1458         if (!enrol_is_enabled($name)) {
1459             return NULL;
1460         }
1462         if ($instance->status != ENROL_INSTANCE_ENABLED) {
1463             return NULL;
1464         }
1466         if (!file_exists("$CFG->dirroot/enrol/$name/unenrolself.php")) {
1467             return NULL;
1468         }
1470         $context = get_context_instance(CONTEXT_COURSE, $instance->courseid, MUST_EXIST);
1472         if (!has_capability("enrol/$name:unenrolself", $context)) {
1473             return NULL;
1474         }
1476         if (!$DB->record_exists('user_enrolments', array('enrolid'=>$instance->id, 'userid'=>$USER->id, 'status'=>ENROL_USER_ACTIVE))) {
1477             return NULL;
1478         }
1480         return new moodle_url("/enrol/$name/unenrolself.php", array('enrolid'=>$instance->id));;
1481     }
1483     /**
1484      * Adds enrol instance UI to course edit form
1485      *
1486      * @param object $instance enrol instance or null if does not exist yet
1487      * @param MoodleQuickForm $mform
1488      * @param object $data
1489      * @param object $context context of existing course or parent category if course does not exist
1490      * @return void
1491      */
1492     public function course_edit_form($instance, MoodleQuickForm $mform, $data, $context) {
1493         // override - usually at least enable/disable switch, has to add own form header
1494     }
1496     /**
1497      * Validates course edit form data
1498      *
1499      * @param object $instance enrol instance or null if does not exist yet
1500      * @param array $data
1501      * @param object $context context of existing course or parent category if course does not exist
1502      * @return array errors array
1503      */
1504     public function course_edit_validation($instance, array $data, $context) {
1505         return array();
1506     }
1508     /**
1509      * Called after updating/inserting course.
1510      *
1511      * @param bool $inserted true if course just inserted
1512      * @param object $course
1513      * @param object $data form data
1514      * @return void
1515      */
1516     public function course_updated($inserted, $course, $data) {
1517         if ($inserted) {
1518             if ($this->get_config('defaultenrol')) {
1519                 $this->add_default_instance($course);
1520             }
1521         }
1522     }
1524     /**
1525      * Add new instance of enrol plugin.
1526      * @param object $course
1527      * @param array instance fields
1528      * @return int id of new instance, null if can not be created
1529      */
1530     public function add_instance($course, array $fields = NULL) {
1531         global $DB;
1533         if ($course->id == SITEID) {
1534             throw new coding_exception('Invalid request to add enrol instance to frontpage.');
1535         }
1537         $instance = new stdClass();
1538         $instance->enrol          = $this->get_name();
1539         $instance->status         = ENROL_INSTANCE_ENABLED;
1540         $instance->courseid       = $course->id;
1541         $instance->enrolstartdate = 0;
1542         $instance->enrolenddate   = 0;
1543         $instance->timemodified   = time();
1544         $instance->timecreated    = $instance->timemodified;
1545         $instance->sortorder      = $DB->get_field('enrol', 'COALESCE(MAX(sortorder), -1) + 1', array('courseid'=>$course->id));
1547         $fields = (array)$fields;
1548         unset($fields['enrol']);
1549         unset($fields['courseid']);
1550         unset($fields['sortorder']);
1551         foreach($fields as $field=>$value) {
1552             $instance->$field = $value;
1553         }
1555         return $DB->insert_record('enrol', $instance);
1556     }
1558     /**
1559      * Add new instance of enrol plugin with default settings,
1560      * called when adding new instance manually or when adding new course.
1561      *
1562      * Not all plugins support this.
1563      *
1564      * @param object $course
1565      * @return int id of new instance or null if no default supported
1566      */
1567     public function add_default_instance($course) {
1568         return null;
1569     }
1571     /**
1572      * Update instance status
1573      *
1574      * Override when plugin needs to do some action when enabled or disabled.
1575      *
1576      * @param stdClass $instance
1577      * @param int $newstatus ENROL_INSTANCE_ENABLED, ENROL_INSTANCE_DISABLED
1578      * @return void
1579      */
1580     public function update_status($instance, $newstatus) {
1581         global $DB;
1583         $instance->status = $newstatus;
1584         $DB->update_record('enrol', $instance);
1586         // invalidate all enrol caches
1587         $context = context_course::instance($instance->courseid);
1588         $context->mark_dirty();
1589     }
1591     /**
1592      * Delete course enrol plugin instance, unenrol all users.
1593      * @param object $instance
1594      * @return void
1595      */
1596     public function delete_instance($instance) {
1597         global $DB;
1599         $name = $this->get_name();
1600         if ($instance->enrol !== $name) {
1601             throw new coding_exception('invalid enrol instance!');
1602         }
1604         //first unenrol all users
1605         $participants = $DB->get_recordset('user_enrolments', array('enrolid'=>$instance->id));
1606         foreach ($participants as $participant) {
1607             $this->unenrol_user($instance, $participant->userid);
1608         }
1609         $participants->close();
1611         // now clean up all remainders that were not removed correctly
1612         $DB->delete_records('role_assignments', array('itemid'=>$instance->id, 'component'=>$name));
1613         $DB->delete_records('user_enrolments', array('enrolid'=>$instance->id));
1615         // finally drop the enrol row
1616         $DB->delete_records('enrol', array('id'=>$instance->id));
1618         // invalidate all enrol caches
1619         $context = context_course::instance($instance->courseid);
1620         $context->mark_dirty();
1621     }
1623     /**
1624      * Creates course enrol form, checks if form submitted
1625      * and enrols user if necessary. It can also redirect.
1626      *
1627      * @param stdClass $instance
1628      * @return string html text, usually a form in a text box
1629      */
1630     public function enrol_page_hook(stdClass $instance) {
1631         return null;
1632     }
1634     /**
1635      * Adds navigation links into course admin block.
1636      *
1637      * By defaults looks for manage links only.
1638      *
1639      * @param navigation_node $instancesnode
1640      * @param stdClass $instance
1641      * @return void
1642      */
1643     public function add_course_navigation($instancesnode, stdClass $instance) {
1644         // usually adds manage users
1645     }
1647     /**
1648      * Returns edit icons for the page with list of instances
1649      * @param stdClass $instance
1650      * @return array
1651      */
1652     public function get_action_icons(stdClass $instance) {
1653         return array();
1654     }
1656     /**
1657      * Reads version.php and determines if it is necessary
1658      * to execute the cron job now.
1659      * @return bool
1660      */
1661     public function is_cron_required() {
1662         global $CFG;
1664         $name = $this->get_name();
1665         $versionfile = "$CFG->dirroot/enrol/$name/version.php";
1666         $plugin = new stdClass();
1667         include($versionfile);
1668         if (empty($plugin->cron)) {
1669             return false;
1670         }
1671         $lastexecuted = $this->get_config('lastcron', 0);
1672         if ($lastexecuted + $plugin->cron < time()) {
1673             return true;
1674         } else {
1675             return false;
1676         }
1677     }
1679     /**
1680      * Called for all enabled enrol plugins that returned true from is_cron_required().
1681      * @return void
1682      */
1683     public function cron() {
1684     }
1686     /**
1687      * Called when user is about to be deleted
1688      * @param object $user
1689      * @return void
1690      */
1691     public function user_delete($user) {
1692         global $DB;
1694         $sql = "SELECT e.*
1695                   FROM {enrol} e
1696                   JOIN {user_enrolments} ue ON (ue.enrolid = e.id)
1697                  WHERE e.enrol = :name AND ue.userid = :userid";
1698         $params = array('name'=>$this->get_name(), 'userid'=>$user->id);
1700         $rs = $DB->get_recordset_sql($sql, $params);
1701         foreach($rs as $instance) {
1702             $this->unenrol_user($instance, $user->id);
1703         }
1704         $rs->close();
1705     }
1707     /**
1708      * Returns an enrol_user_button that takes the user to a page where they are able to
1709      * enrol users into the managers course through this plugin.
1710      *
1711      * Optional: If the plugin supports manual enrolments it can choose to override this
1712      * otherwise it shouldn't
1713      *
1714      * @param course_enrolment_manager $manager
1715      * @return enrol_user_button|false
1716      */
1717     public function get_manual_enrol_button(course_enrolment_manager $manager) {
1718         return false;
1719     }
1721     /**
1722      * Gets an array of the user enrolment actions
1723      *
1724      * @param course_enrolment_manager $manager
1725      * @param stdClass $ue
1726      * @return array An array of user_enrolment_actions
1727      */
1728     public function get_user_enrolment_actions(course_enrolment_manager $manager, $ue) {
1729         return array();
1730     }
1732     /**
1733      * Returns true if the plugin has one or more bulk operations that can be performed on
1734      * user enrolments.
1735      *
1736      * @param course_enrolment_manager $manager
1737      * @return bool
1738      */
1739     public function has_bulk_operations(course_enrolment_manager $manager) {
1740        return false;
1741     }
1743     /**
1744      * Return an array of enrol_bulk_enrolment_operation objects that define
1745      * the bulk actions that can be performed on user enrolments by the plugin.
1746      *
1747      * @param course_enrolment_manager $manager
1748      * @return array
1749      */
1750     public function get_bulk_operations(course_enrolment_manager $manager) {
1751         return array();
1752     }