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