3 // This file is part of Moodle - http://moodle.org/
5 // Moodle is free software: you can redistribute it and/or modify
6 // it under the terms of the GNU General Public License as published by
7 // the Free Software Foundation, either version 3 of the License, or
8 // (at your option) any later version.
10 // Moodle is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 // GNU General Public License for more details.
15 // You should have received a copy of the GNU General Public License
16 // along with Moodle. If not, see <http://www.gnu.org/licenses/>.
19 * This library includes the basic parts of enrol api.
20 * It is available on each page.
24 * @copyright 2010 Petr Skoda {@link http://skodak.org}
25 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
28 defined('MOODLE_INTERNAL') ||
die();
30 /** Course enrol instance enabled. (used in enrol->status) */
31 define('ENROL_INSTANCE_ENABLED', 0);
33 /** Course enrol instance disabled, user may enter course if other enrol instance enabled. (used in enrol->status)*/
34 define('ENROL_INSTANCE_DISABLED', 1);
36 /** User is active participant (used in user_enrolments->status)*/
37 define('ENROL_USER_ACTIVE', 0);
39 /** User participation in course is suspended (used in user_enrolments->status) */
40 define('ENROL_USER_SUSPENDED', 1);
42 /** @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 /** @deprecated since 2.4 not used any more, migrate plugin to new restore methods */
55 define('ENROL_RESTORE_TYPE', 'enrolrestore');
58 * When user disappears from external source, user enrolment is suspended, roles are kept as is.
59 * In some cases user needs a role with some capability to be visible in UI - suc has in gradebook,
62 define('ENROL_EXT_REMOVED_SUSPEND', 2);
65 * When user disappears from external source, the enrolment is suspended and roles assigned
66 * by enrol instance are removed. Please note that user may "disappear" from gradebook and other areas.
68 define('ENROL_EXT_REMOVED_SUSPENDNOROLES', 3);
73 define('ENROL_DO_NOT_SEND_EMAIL', 0);
76 * Send email from course contact.
78 define('ENROL_SEND_EMAIL_FROM_COURSE_CONTACT', 1);
81 * Send email from enrolment key holder.
83 define('ENROL_SEND_EMAIL_FROM_KEY_HOLDER', 2);
86 * Send email from no reply address.
88 define('ENROL_SEND_EMAIL_FROM_NOREPLY', 3);
90 /** Edit enrolment action. */
91 define('ENROL_ACTION_EDIT', 'editenrolment');
93 /** Unenrol action. */
94 define('ENROL_ACTION_UNENROL', 'unenrol');
97 * Returns instances of enrol plugins
98 * @param bool $enabled return enabled only
99 * @return array of enrol plugins name=>instance
101 function enrol_get_plugins($enabled) {
107 // sorted by enabled plugin order
108 $enabled = explode(',', $CFG->enrol_plugins_enabled
);
110 foreach ($enabled as $plugin) {
111 $plugins[$plugin] = "$CFG->dirroot/enrol/$plugin";
114 // sorted alphabetically
115 $plugins = core_component
::get_plugin_list('enrol');
119 foreach ($plugins as $plugin=>$location) {
120 $class = "enrol_{$plugin}_plugin";
121 if (!class_exists($class)) {
122 if (!file_exists("$location/lib.php")) {
125 include_once("$location/lib.php");
126 if (!class_exists($class)) {
131 $result[$plugin] = new $class();
138 * Returns instance of enrol plugin
139 * @param string $name name of enrol plugin ('manual', 'guest', ...)
140 * @return enrol_plugin
142 function enrol_get_plugin($name) {
145 $name = clean_param($name, PARAM_PLUGIN
);
148 // ignore malformed or missing plugin names completely
152 $location = "$CFG->dirroot/enrol/$name";
154 $class = "enrol_{$name}_plugin";
155 if (!class_exists($class)) {
156 if (!file_exists("$location/lib.php")) {
159 include_once("$location/lib.php");
160 if (!class_exists($class)) {
169 * Returns enrolment instances in given course.
170 * @param int $courseid
171 * @param bool $enabled
172 * @return array of enrol instances
174 function enrol_get_instances($courseid, $enabled) {
178 return $DB->get_records('enrol', array('courseid'=>$courseid), 'sortorder,id');
181 $result = $DB->get_records('enrol', array('courseid'=>$courseid, 'status'=>ENROL_INSTANCE_ENABLED
), 'sortorder,id');
183 $enabled = explode(',', $CFG->enrol_plugins_enabled
);
184 foreach ($result as $key=>$instance) {
185 if (!in_array($instance->enrol
, $enabled)) {
186 unset($result[$key]);
189 if (!file_exists("$CFG->dirroot/enrol/$instance->enrol/lib.php")) {
191 unset($result[$key]);
200 * Checks if a given plugin is in the list of enabled enrolment plugins.
202 * @param string $enrol Enrolment plugin name
203 * @return boolean Whether the plugin is enabled
205 function enrol_is_enabled($enrol) {
208 if (empty($CFG->enrol_plugins_enabled
)) {
211 return in_array($enrol, explode(',', $CFG->enrol_plugins_enabled
));
215 * Check all the login enrolment information for the given user object
216 * by querying the enrolment plugins
218 * This function may be very slow, use only once after log-in or login-as.
220 * @param stdClass $user
223 function enrol_check_plugins($user) {
226 if (empty($user->id
) or isguestuser($user)) {
227 // shortcut - there is no enrolment work for guests and not-logged-in users
231 // originally there was a broken admin test, but accidentally it was non-functional in 2.2,
232 // which proved it was actually not necessary.
234 static $inprogress = array(); // To prevent this function being called more than once in an invocation
236 if (!empty($inprogress[$user->id
])) {
240 $inprogress[$user->id
] = true; // Set the flag
242 $enabled = enrol_get_plugins(true);
244 foreach($enabled as $enrol) {
245 $enrol->sync_user_enrolments($user);
248 unset($inprogress[$user->id
]); // Unset the flag
252 * Do these two students share any course?
254 * The courses has to be visible and enrolments has to be active,
255 * timestart and timeend restrictions are ignored.
257 * This function calls {@see enrol_get_shared_courses()} setting checkexistsonly
260 * @param stdClass|int $user1
261 * @param stdClass|int $user2
264 function enrol_sharing_course($user1, $user2) {
265 return enrol_get_shared_courses($user1, $user2, false, true);
269 * Returns any courses shared by the two users
271 * The courses has to be visible and enrolments has to be active,
272 * timestart and timeend restrictions are ignored.
274 * @global moodle_database $DB
275 * @param stdClass|int $user1
276 * @param stdClass|int $user2
277 * @param bool $preloadcontexts If set to true contexts for the returned courses
279 * @param bool $checkexistsonly If set to true then this function will return true
280 * if the users share any courses and false if not.
281 * @return array|bool An array of courses that both users are enrolled in OR if
282 * $checkexistsonly set returns true if the users share any courses
285 function enrol_get_shared_courses($user1, $user2, $preloadcontexts = false, $checkexistsonly = false) {
288 $user1 = isset($user1->id
) ?
$user1->id
: $user1;
289 $user2 = isset($user2->id
) ?
$user2->id
: $user2;
291 if (empty($user1) or empty($user2)) {
295 if (!$plugins = explode(',', $CFG->enrol_plugins_enabled
)) {
299 list($plugins1, $params1) = $DB->get_in_or_equal($plugins, SQL_PARAMS_NAMED
, 'ee1');
300 list($plugins2, $params2) = $DB->get_in_or_equal($plugins, SQL_PARAMS_NAMED
, 'ee2');
301 $params = array_merge($params1, $params2);
302 $params['enabled1'] = ENROL_INSTANCE_ENABLED
;
303 $params['enabled2'] = ENROL_INSTANCE_ENABLED
;
304 $params['active1'] = ENROL_USER_ACTIVE
;
305 $params['active2'] = ENROL_USER_ACTIVE
;
306 $params['user1'] = $user1;
307 $params['user2'] = $user2;
311 if ($preloadcontexts) {
312 $ctxselect = ', ' . context_helper
::get_preload_record_columns_sql('ctx');
313 $ctxjoin = "LEFT JOIN {context} ctx ON (ctx.instanceid = c.id AND ctx.contextlevel = :contextlevel)";
314 $params['contextlevel'] = CONTEXT_COURSE
;
317 $sql = "SELECT c.* $ctxselect
322 JOIN {enrol} e1 ON (c.id = e1.courseid AND e1.status = :enabled1 AND e1.enrol $plugins1)
323 JOIN {user_enrolments} ue1 ON (ue1.enrolid = e1.id AND ue1.status = :active1 AND ue1.userid = :user1)
324 JOIN {enrol} e2 ON (c.id = e2.courseid AND e2.status = :enabled2 AND e2.enrol $plugins2)
325 JOIN {user_enrolments} ue2 ON (ue2.enrolid = e2.id AND ue2.status = :active2 AND ue2.userid = :user2)
330 if ($checkexistsonly) {
331 return $DB->record_exists_sql($sql, $params);
333 $courses = $DB->get_records_sql($sql, $params);
334 if ($preloadcontexts) {
335 array_map('context_helper::preload_from_record', $courses);
342 * This function adds necessary enrol plugins UI into the course edit form.
344 * @param MoodleQuickForm $mform
345 * @param object $data course edit form data
346 * @param object $context context of existing course or parent category if course does not exist
349 function enrol_course_edit_form(MoodleQuickForm
$mform, $data, $context) {
350 $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
])) {
357 $plugin = $plugins[$instance->enrol
];
358 $plugin->course_edit_form($instance, $mform, $data, $context);
361 foreach ($plugins as $plugin) {
362 $plugin->course_edit_form(NULL, $mform, $data, $context);
368 * Validate course edit form data
370 * @param array $data raw form data
371 * @param object $context context of existing course or parent category if course does not exist
372 * @return array errors array
374 function enrol_course_edit_validation(array $data, $context) {
376 $plugins = enrol_get_plugins(true);
378 if (!empty($data['id'])) {
379 $instances = enrol_get_instances($data['id'], false);
380 foreach ($instances as $instance) {
381 if (!isset($plugins[$instance->enrol
])) {
384 $plugin = $plugins[$instance->enrol
];
385 $errors = array_merge($errors, $plugin->course_edit_validation($instance, $data, $context));
388 foreach ($plugins as $plugin) {
389 $errors = array_merge($errors, $plugin->course_edit_validation(NULL, $data, $context));
397 * Update enrol instances after course edit form submission
398 * @param bool $inserted true means new course added, false course already existed
399 * @param object $course
400 * @param object $data form data
403 function enrol_course_updated($inserted, $course, $data) {
406 $plugins = enrol_get_plugins(true);
408 foreach ($plugins as $plugin) {
409 $plugin->course_updated($inserted, $course, $data);
414 * Add navigation nodes
415 * @param navigation_node $coursenode
416 * @param object $course
419 function enrol_add_course_navigation(navigation_node
$coursenode, $course) {
422 $coursecontext = context_course
::instance($course->id
);
424 $instances = enrol_get_instances($course->id
, true);
425 $plugins = enrol_get_plugins(true);
427 // we do not want to break all course pages if there is some borked enrol plugin, right?
428 foreach ($instances as $k=>$instance) {
429 if (!isset($plugins[$instance->enrol
])) {
430 unset($instances[$k]);
434 $usersnode = $coursenode->add(get_string('users'), null, navigation_node
::TYPE_CONTAINER
, null, 'users');
436 // List all participants - allows assigning roles, groups, etc.
437 // Have this available even in the site context as the page is still accessible from the frontpage.
438 if (has_capability('moodle/course:enrolreview', $coursecontext)) {
439 $url = new moodle_url('/user/index.php', array('id' => $course->id
));
440 $usersnode->add(get_string('enrolledusers', 'enrol'), $url, navigation_node
::TYPE_SETTING
,
441 null, 'review', new pix_icon('i/enrolusers', ''));
444 if ($course->id
!= SITEID
) {
445 // manage enrol plugin instances
446 if (has_capability('moodle/course:enrolconfig', $coursecontext) or has_capability('moodle/course:enrolreview', $coursecontext)) {
447 $url = new moodle_url('/enrol/instances.php', array('id'=>$course->id
));
451 $instancesnode = $usersnode->add(get_string('enrolmentinstances', 'enrol'), $url, navigation_node
::TYPE_SETTING
, null, 'manageinstances');
453 // each instance decides how to configure itself or how many other nav items are exposed
454 foreach ($instances as $instance) {
455 if (!isset($plugins[$instance->enrol
])) {
458 $plugins[$instance->enrol
]->add_course_navigation($instancesnode, $instance);
462 $instancesnode->trim_if_empty();
466 // Manage groups in this course or even frontpage
467 if (($course->groupmode ||
!$course->groupmodeforce
) && has_capability('moodle/course:managegroups', $coursecontext)) {
468 $url = new moodle_url('/group/index.php', array('id'=>$course->id
));
469 $usersnode->add(get_string('groups'), $url, navigation_node
::TYPE_SETTING
, null, 'groups', new pix_icon('i/group', ''));
472 if (has_any_capability(array( 'moodle/role:assign', 'moodle/role:safeoverride','moodle/role:override', 'moodle/role:review'), $coursecontext)) {
474 if (has_capability('moodle/role:review', $coursecontext)) {
475 $url = new moodle_url('/admin/roles/permissions.php', array('contextid'=>$coursecontext->id
));
479 $permissionsnode = $usersnode->add(get_string('permissions', 'role'), $url, navigation_node
::TYPE_SETTING
, null, 'override');
481 // Add assign or override roles if allowed
482 if ($course->id
== SITEID
or (!empty($CFG->adminsassignrolesincourse
) and is_siteadmin())) {
483 if (has_capability('moodle/role:assign', $coursecontext)) {
484 $url = new moodle_url('/admin/roles/assign.php', array('contextid'=>$coursecontext->id
));
485 $permissionsnode->add(get_string('assignedroles', 'role'), $url, navigation_node
::TYPE_SETTING
, null, 'roles', new pix_icon('i/assignroles', ''));
488 // Check role permissions
489 if (has_any_capability(array('moodle/role:assign', 'moodle/role:safeoverride', 'moodle/role:override'), $coursecontext)) {
490 $url = new moodle_url('/admin/roles/check.php', array('contextid'=>$coursecontext->id
));
491 $permissionsnode->add(get_string('checkpermissions', 'role'), $url, navigation_node
::TYPE_SETTING
, null, 'permissions', new pix_icon('i/checkpermissions', ''));
495 // Deal somehow with users that are not enrolled but still got a role somehow
496 if ($course->id
!= SITEID
) {
497 //TODO, create some new UI for role assignments at course level
498 if (has_capability('moodle/course:reviewotherusers', $coursecontext)) {
499 $url = new moodle_url('/enrol/otherusers.php', array('id'=>$course->id
));
500 $usersnode->add(get_string('notenrolledusers', 'enrol'), $url, navigation_node
::TYPE_SETTING
, null, 'otherusers', new pix_icon('i/assignroles', ''));
504 // just in case nothing was actually added
505 $usersnode->trim_if_empty();
507 if ($course->id
!= SITEID
) {
508 if (isguestuser() or !isloggedin()) {
509 // guest account can not be enrolled - no links for them
510 } else if (is_enrolled($coursecontext)) {
511 // unenrol link if possible
512 foreach ($instances as $instance) {
513 if (!isset($plugins[$instance->enrol
])) {
516 $plugin = $plugins[$instance->enrol
];
517 if ($unenrollink = $plugin->get_unenrolself_link($instance)) {
518 $shortname = format_string($course->shortname
, true, array('context' => $coursecontext));
519 $coursenode->add(get_string('unenrolme', 'core_enrol', $shortname), $unenrollink, navigation_node
::TYPE_SETTING
, null, 'unenrolself', new pix_icon('i/user', ''));
520 $coursenode->get('unenrolself')->set_force_into_more_menu(true);
522 //TODO. deal with multiple unenrol links - not likely case, but still...
526 // enrol link if possible
527 if (is_viewing($coursecontext)) {
528 // better not show any enrol link, this is intended for managers and inspectors
530 foreach ($instances as $instance) {
531 if (!isset($plugins[$instance->enrol
])) {
534 $plugin = $plugins[$instance->enrol
];
535 if ($plugin->show_enrolme_link($instance)) {
536 $url = new moodle_url('/enrol/index.php', array('id'=>$course->id
));
537 $shortname = format_string($course->shortname
, true, array('context' => $coursecontext));
538 $coursenode->add(get_string('enrolme', 'core_enrol', $shortname), $url, navigation_node
::TYPE_SETTING
, null, 'enrolself', new pix_icon('i/user', ''));
548 * Returns list of courses current $USER is enrolled in and can access
550 * The $fields param is a list of field names to ADD so name just the fields you really need,
551 * which will be added and uniq'd.
553 * If $allaccessible is true, this will additionally return courses that the current user is not
554 * enrolled in, but can access because they are open to the user for other reasons (course view
555 * permission, currently viewing course as a guest, or course allows guest access without
558 * @param string|array $fields Extra fields to be returned (array or comma-separated list).
559 * @param string|null $sort Comma separated list of fields to sort by, defaults to respecting navsortmycoursessort.
560 * Allowed prefixes for sort fields are: "ul" for the user_lastaccess table, "c" for the courses table,
561 * "ue" for the user_enrolments table.
562 * @param int $limit max number of courses
563 * @param array $courseids the list of course ids to filter by
564 * @param bool $allaccessible Include courses user is not enrolled in, but can access
565 * @param int $offset Offset the result set by this number
566 * @param array $excludecourses IDs of hidden courses to exclude from search
569 function enrol_get_my_courses($fields = null, $sort = null, $limit = 0, $courseids = [], $allaccessible = false,
570 $offset = 0, $excludecourses = []) {
571 global $DB, $USER, $CFG;
573 // Allowed prefixes and field names.
574 $allowedprefixesandfields = ['c' => array_keys($DB->get_columns('course')),
575 'ul' => array_keys($DB->get_columns('user_lastaccess')),
576 'ue' => array_keys($DB->get_columns('user_enrolments'))];
578 // Re-Arrange the course sorting according to the admin settings.
579 $sort = enrol_get_courses_sortingsql($sort);
581 // Guest account does not have any enrolled courses.
582 if (!$allaccessible && (isguestuser() or !isloggedin())) {
587 'id', 'category', 'sortorder',
588 'shortname', 'fullname', 'idnumber',
589 'startdate', 'visible',
590 'groupmode', 'groupmodeforce', 'cacherev',
591 'showactivitydates', 'showcompletionconditions',
594 if (empty($fields)) {
595 $fields = $basefields;
596 } else if (is_string($fields)) {
597 // turn the fields from a string to an array
598 $fields = explode(',', $fields);
599 $fields = array_map('trim', $fields);
600 $fields = array_unique(array_merge($basefields, $fields));
601 } else if (is_array($fields)) {
602 $fields = array_unique(array_merge($basefields, $fields));
604 throw new coding_exception('Invalid $fields parameter in enrol_get_my_courses()');
606 if (in_array('*', $fields)) {
607 $fields = array('*');
612 $sorttimeaccess = false;
614 $rawsorts = explode(',', $sort);
616 foreach ($rawsorts as $rawsort) {
617 $rawsort = trim($rawsort);
618 // Make sure that there are no more white spaces in sortparams after explode.
619 $sortparams = array_values(array_filter(explode(' ', $rawsort)));
620 // If more than 2 values present then throw coding_exception.
621 if (isset($sortparams[2])) {
622 throw new coding_exception('Invalid $sort parameter in enrol_get_my_courses()');
624 // Check the sort ordering if present, at the beginning.
625 if (isset($sortparams[1]) && (preg_match("/^(asc|desc)$/i", $sortparams[1]) === 0)) {
626 throw new coding_exception('Invalid sort direction in $sort parameter in enrol_get_my_courses()');
629 $sortfield = $sortparams[0];
630 $sortdirection = $sortparams[1] ??
'asc';
631 if (strpos($sortfield, '.') !== false) {
632 $sortfieldparams = explode('.', $sortfield);
633 // Check if more than one dots present in the prefix field.
634 if (isset($sortfieldparams[2])) {
635 throw new coding_exception('Invalid $sort parameter in enrol_get_my_courses()');
637 list($prefix, $fieldname) = [$sortfieldparams[0], $sortfieldparams[1]];
638 // Check if the field name matches with the allowed prefix.
639 if (array_key_exists($prefix, $allowedprefixesandfields) &&
640 (in_array($fieldname, $allowedprefixesandfields[$prefix]))) {
641 if ($prefix === 'ul') {
642 $sorts[] = "COALESCE({$prefix}.{$fieldname}, 0) {$sortdirection}";
643 $sorttimeaccess = true;
645 // Check if the field name that matches with the prefix and just append to sorts.
649 throw new coding_exception('Invalid $sort parameter in enrol_get_my_courses()');
652 // Check if the field name matches with $allowedprefixesandfields.
654 foreach (array_keys($allowedprefixesandfields) as $prefix) {
655 if (in_array($sortfield, $allowedprefixesandfields[$prefix])) {
656 if ($prefix === 'ul') {
657 $sorts[] = "COALESCE({$prefix}.{$sortfield}, 0) {$sortdirection}";
658 $sorttimeaccess = true;
660 $sorts[] = "{$prefix}.{$sortfield} {$sortdirection}";
667 // The param is not found in $allowedprefixesandfields.
668 throw new coding_exception('Invalid $sort parameter in enrol_get_my_courses()');
672 $sort = implode(',', $sorts);
673 $orderby = "ORDER BY $sort";
676 $wheres = ['c.id <> ' . SITEID
];
679 if (isset($USER->loginascontext
) and $USER->loginascontext
->contextlevel
== CONTEXT_COURSE
) {
680 // list _only_ this course - anything else is asking for trouble...
681 $wheres[] = "courseid = :loginas";
682 $params['loginas'] = $USER->loginascontext
->instanceid
;
685 $coursefields = 'c.' .join(',c.', $fields);
686 $ccselect = ', ' . context_helper
::get_preload_record_columns_sql('ctx');
687 $ccjoin = "LEFT JOIN {context} ctx ON (ctx.instanceid = c.id AND ctx.contextlevel = :contextlevel)";
688 $params['contextlevel'] = CONTEXT_COURSE
;
689 $wheres = implode(" AND ", $wheres);
691 $timeaccessselect = "";
692 $timeaccessjoin = "";
694 if (!empty($courseids)) {
695 list($courseidssql, $courseidsparams) = $DB->get_in_or_equal($courseids, SQL_PARAMS_NAMED
);
696 $wheres = sprintf("%s AND c.id %s", $wheres, $courseidssql);
697 $params = array_merge($params, $courseidsparams);
700 if (!empty($excludecourses)) {
701 list($courseidssql, $courseidsparams) = $DB->get_in_or_equal($excludecourses, SQL_PARAMS_NAMED
, 'param', false);
702 $wheres = sprintf("%s AND c.id %s", $wheres, $courseidssql);
703 $params = array_merge($params, $courseidsparams);
707 // Logged-in, non-guest users get their enrolled courses.
708 if (!isguestuser() && isloggedin()) {
710 SELECT DISTINCT e.courseid
712 JOIN {user_enrolments} ue ON (ue.enrolid = e.id AND ue.userid = :userid1)
713 WHERE ue.status = :active AND e.status = :enabled AND ue.timestart <= :now1
714 AND (ue.timeend = 0 OR ue.timeend > :now2)";
715 $params['userid1'] = $USER->id
;
716 $params['active'] = ENROL_USER_ACTIVE
;
717 $params['enabled'] = ENROL_INSTANCE_ENABLED
;
718 $params['now1'] = $params['now2'] = time();
720 if ($sorttimeaccess) {
721 $params['userid2'] = $USER->id
;
722 $timeaccessselect = ', ul.timeaccess as lastaccessed';
723 $timeaccessjoin = "LEFT JOIN {user_lastaccess} ul ON (ul.courseid = c.id AND ul.userid = :userid2)";
727 // When including non-enrolled but accessible courses...
728 if ($allaccessible) {
729 if (is_siteadmin()) {
730 // Site admins can access all courses.
731 $courseidsql = "SELECT DISTINCT c2.id AS courseid FROM {course} c2";
733 // If we used the enrolment as well, then this will be UNIONed.
735 $courseidsql .= " UNION ";
738 // Include courses with guest access and no password.
740 SELECT DISTINCT e.courseid
742 WHERE e.enrol = 'guest' AND e.password = :emptypass AND e.status = :enabled2";
743 $params['emptypass'] = '';
744 $params['enabled2'] = ENROL_INSTANCE_ENABLED
;
746 // Include courses where the current user is currently using guest access (may include
747 // those which require a password).
749 $accessdata = get_user_accessdata($USER->id
);
750 foreach ($accessdata['ra'] as $contextpath => $roles) {
751 if (array_key_exists($CFG->guestroleid
, $roles)) {
752 // Work out the course id from context path.
753 $context = context
::instance_by_id(preg_replace('~^.*/~', '', $contextpath));
754 if ($context instanceof context_course
) {
755 $courseids[$context->instanceid
] = true;
760 // Include courses where the current user has moodle/course:view capability.
761 $courses = get_user_capability_course('moodle/course:view', null, false);
765 foreach ($courses as $course) {
766 $courseids[$course->id
] = true;
769 // If there are any in either category, list them individually.
771 list ($allowedsql, $allowedparams) = $DB->get_in_or_equal(
772 array_keys($courseids), SQL_PARAMS_NAMED
);
775 SELECT DISTINCT c3.id AS courseid
777 WHERE c3.id $allowedsql";
778 $params = array_merge($params, $allowedparams);
783 // Note: we can not use DISTINCT + text fields due to Oracle and MS limitations, that is why
784 // we have the subselect there.
785 $sql = "SELECT $coursefields $ccselect $timeaccessselect
787 JOIN ($courseidsql) en ON (en.courseid = c.id)
793 $courses = $DB->get_records_sql($sql, $params, $offset, $limit);
795 // preload contexts and check visibility
796 foreach ($courses as $id=>$course) {
797 context_helper
::preload_from_record($course);
798 if (!$course->visible
) {
799 if (!$context = context_course
::instance($id, IGNORE_MISSING
)) {
800 unset($courses[$id]);
803 if (!has_capability('moodle/course:viewhiddencourses', $context)) {
804 unset($courses[$id]);
808 $courses[$id] = $course;
811 //wow! Is that really all? :-D
817 * Returns course enrolment information icons.
819 * @param object $course
820 * @param array $instances enrol instances of this course, improves performance
821 * @return array of pix_icon
823 function enrol_get_course_info_icons($course, array $instances = NULL) {
825 if (is_null($instances)) {
826 $instances = enrol_get_instances($course->id
, true);
828 $plugins = enrol_get_plugins(true);
829 foreach ($plugins as $name => $plugin) {
831 foreach ($instances as $instance) {
832 if ($instance->status
!= ENROL_INSTANCE_ENABLED
or $instance->courseid
!= $course->id
) {
833 debugging('Invalid instances parameter submitted in enrol_get_info_icons()');
836 if ($instance->enrol
== $name) {
837 $pis[$instance->id
] = $instance;
841 $icons = array_merge($icons, $plugin->get_info_icons($pis));
848 * Returns SQL ORDER arguments which reflect the admin settings to sort my courses.
850 * @param string|null $sort SQL ORDER arguments which were originally requested (optionally).
851 * @return string SQL ORDER arguments.
853 function enrol_get_courses_sortingsql($sort = null) {
856 // Prepare the visible SQL fragment as empty.
858 // Only create a visible SQL fragment if the caller didn't already pass a sort order which contains the visible field.
859 if ($sort === null ||
strpos($sort, 'visible') === false) {
860 // If the admin did not explicitly want to have shown and hidden courses sorted as one list, we will sort hidden
861 // courses to the end of the course list.
862 if (!isset($CFG->navsortmycourseshiddenlast
) ||
$CFG->navsortmycourseshiddenlast
== true) {
863 $visible = 'visible DESC, ';
867 // Only create a sortorder SQL fragment if the caller didn't already pass one.
868 if ($sort === null) {
869 // If the admin has configured a course sort order, we will use this.
870 if (!empty($CFG->navsortmycoursessort
)) {
871 $sort = $CFG->navsortmycoursessort
. ' ASC';
873 // Otherwise we will fall back to the sortorder sorting.
875 $sort = 'sortorder ASC';
879 return $visible . $sort;
883 * Returns course enrolment detailed information.
885 * @param object $course
886 * @return array of html fragments - can be used to construct lists
888 function enrol_get_course_description_texts($course) {
890 $instances = enrol_get_instances($course->id
, true);
891 $plugins = enrol_get_plugins(true);
892 foreach ($instances as $instance) {
893 if (!isset($plugins[$instance->enrol
])) {
897 $plugin = $plugins[$instance->enrol
];
898 $text = $plugin->get_description_text($instance);
899 if ($text !== NULL) {
907 * Returns list of courses user is enrolled into.
909 * Note: Use {@link enrol_get_all_users_courses()} if you need the list without any capability checks.
911 * The $fields param is a list of field names to ADD so name just the fields you really need,
912 * which will be added and uniq'd.
914 * @param int $userid User whose courses are returned, defaults to the current user.
915 * @param bool $onlyactive Return only active enrolments in courses user may see.
916 * @param string|array $fields Extra fields to be returned (array or comma-separated list).
917 * @param string|null $sort Comma separated list of fields to sort by, defaults to respecting navsortmycoursessort.
920 function enrol_get_users_courses($userid, $onlyactive = false, $fields = null, $sort = null) {
923 $courses = enrol_get_all_users_courses($userid, $onlyactive, $fields, $sort);
925 // preload contexts and check visibility
927 foreach ($courses as $id=>$course) {
928 context_helper
::preload_from_record($course);
929 if (!$course->visible
) {
930 if (!$context = context_course
::instance($id)) {
931 unset($courses[$id]);
934 if (!has_capability('moodle/course:viewhiddencourses', $context, $userid)) {
935 unset($courses[$id]);
946 * Returns list of roles per users into course.
948 * @param int $courseid Course id.
949 * @return array Array[$userid][$roleid] = role_assignment.
951 function enrol_get_course_users_roles(int $courseid) : array {
954 $context = context_course
::instance($courseid);
958 $records = $DB->get_recordset('role_assignments', array('contextid' => $context->id
));
959 foreach ($records as $record) {
960 if (isset($roles[$record->userid
]) === false) {
961 $roles[$record->userid
] = array();
963 $roles[$record->userid
][$record->roleid
] = $record;
971 * Can user access at least one enrolled course?
973 * Cheat if necessary, but find out as fast as possible!
975 * @param int|stdClass $user null means use current user
978 function enrol_user_sees_own_courses($user = null) {
981 if ($user === null) {
984 $userid = is_object($user) ?
$user->id
: $user;
986 // Guest account does not have any courses
987 if (isguestuser($userid) or empty($userid)) {
991 // Let's cheat here if this is the current user,
992 // if user accessed any course recently, then most probably
993 // we do not need to query the database at all.
994 if ($USER->id
== $userid) {
995 if (!empty($USER->enrol
['enrolled'])) {
996 foreach ($USER->enrol
['enrolled'] as $until) {
997 if ($until > time()) {
1004 // Now the slow way.
1005 $courses = enrol_get_all_users_courses($userid, true);
1006 foreach($courses as $course) {
1007 if ($course->visible
) {
1010 context_helper
::preload_from_record($course);
1011 $context = context_course
::instance($course->id
);
1012 if (has_capability('moodle/course:viewhiddencourses', $context, $user)) {
1021 * Returns list of courses user is enrolled into without performing any capability checks.
1023 * The $fields param is a list of field names to ADD so name just the fields you really need,
1024 * which will be added and uniq'd.
1026 * @param int $userid User whose courses are returned, defaults to the current user.
1027 * @param bool $onlyactive Return only active enrolments in courses user may see.
1028 * @param string|array $fields Extra fields to be returned (array or comma-separated list).
1029 * @param string|null $sort Comma separated list of fields to sort by, defaults to respecting navsortmycoursessort.
1032 function enrol_get_all_users_courses($userid, $onlyactive = false, $fields = null, $sort = null) {
1035 // Re-Arrange the course sorting according to the admin settings.
1036 $sort = enrol_get_courses_sortingsql($sort);
1038 // Guest account does not have any courses
1039 if (isguestuser($userid) or empty($userid)) {
1043 $basefields = array('id', 'category', 'sortorder',
1044 'shortname', 'fullname', 'idnumber',
1045 'startdate', 'visible',
1046 'defaultgroupingid',
1047 'groupmode', 'groupmodeforce');
1049 if (empty($fields)) {
1050 $fields = $basefields;
1051 } else if (is_string($fields)) {
1052 // turn the fields from a string to an array
1053 $fields = explode(',', $fields);
1054 $fields = array_map('trim', $fields);
1055 $fields = array_unique(array_merge($basefields, $fields));
1056 } else if (is_array($fields)) {
1057 $fields = array_unique(array_merge($basefields, $fields));
1059 throw new coding_exception('Invalid $fields parameter in enrol_get_all_users_courses()');
1061 if (in_array('*', $fields)) {
1062 $fields = array('*');
1066 $sort = trim($sort);
1067 if (!empty($sort)) {
1068 $rawsorts = explode(',', $sort);
1070 foreach ($rawsorts as $rawsort) {
1071 $rawsort = trim($rawsort);
1072 if (strpos($rawsort, 'c.') === 0) {
1073 $rawsort = substr($rawsort, 2);
1075 $sorts[] = trim($rawsort);
1077 $sort = 'c.'.implode(',c.', $sorts);
1078 $orderby = "ORDER BY $sort";
1084 $subwhere = "WHERE ue.status = :active AND e.status = :enabled AND ue.timestart < :now1 AND (ue.timeend = 0 OR ue.timeend > :now2)";
1085 $params['now1'] = round(time(), -2); // improves db caching
1086 $params['now2'] = $params['now1'];
1087 $params['active'] = ENROL_USER_ACTIVE
;
1088 $params['enabled'] = ENROL_INSTANCE_ENABLED
;
1093 $coursefields = 'c.' .join(',c.', $fields);
1094 $ccselect = ', ' . context_helper
::get_preload_record_columns_sql('ctx');
1095 $ccjoin = "LEFT JOIN {context} ctx ON (ctx.instanceid = c.id AND ctx.contextlevel = :contextlevel)";
1096 $params['contextlevel'] = CONTEXT_COURSE
;
1098 //note: we can not use DISTINCT + text fields due to Oracle and MS limitations, that is why we have the subselect there
1099 $sql = "SELECT $coursefields $ccselect
1101 JOIN (SELECT DISTINCT e.courseid
1103 JOIN {user_enrolments} ue ON (ue.enrolid = e.id AND ue.userid = :userid)
1105 ) en ON (en.courseid = c.id)
1107 WHERE c.id <> " . SITEID
. "
1109 $params['userid'] = $userid;
1111 $courses = $DB->get_records_sql($sql, $params);
1119 * Called when user is about to be deleted.
1120 * @param object $user
1123 function enrol_user_delete($user) {
1126 $plugins = enrol_get_plugins(true);
1127 foreach ($plugins as $plugin) {
1128 $plugin->user_delete($user);
1131 // force cleanup of all broken enrolments
1132 $DB->delete_records('user_enrolments', array('userid'=>$user->id
));
1136 * Called when course is about to be deleted.
1137 * If a user id is passed, only enrolments that the user has permission to un-enrol will be removed,
1138 * otherwise all enrolments in the course will be removed.
1140 * @param stdClass $course
1141 * @param int|null $userid
1144 function enrol_course_delete($course, $userid = null) {
1147 $context = context_course
::instance($course->id
);
1148 $instances = enrol_get_instances($course->id
, false);
1149 $plugins = enrol_get_plugins(true);
1152 // If the user id is present, include only course enrolment instances which allow manual unenrolment and
1153 // the given user have a capability to perform unenrolment.
1154 $instances = array_filter($instances, function($instance) use ($userid, $plugins, $context) {
1155 $unenrolcap = "enrol/{$instance->enrol}:unenrol";
1156 return $plugins[$instance->enrol
]->allow_unenrol($instance) &&
1157 has_capability($unenrolcap, $context, $userid);
1161 foreach ($instances as $instance) {
1162 if (isset($plugins[$instance->enrol
])) {
1163 $plugins[$instance->enrol
]->delete_instance($instance);
1165 // low level delete in case plugin did not do it
1166 $DB->delete_records('role_assignments', array('itemid'=>$instance->id
, 'component'=>'enrol_'.$instance->enrol
));
1167 $DB->delete_records('user_enrolments', array('enrolid'=>$instance->id
));
1168 $DB->delete_records('enrol', array('id'=>$instance->id
));
1173 * Try to enrol user via default internal auth plugin.
1175 * For now this is always using the manual enrol plugin...
1182 * @return bool success
1184 function enrol_try_internal_enrol($courseid, $userid, $roleid = null, $timestart = 0, $timeend = 0) {
1187 //note: this is hardcoded to manual plugin for now
1189 if (!enrol_is_enabled('manual')) {
1193 if (!$enrol = enrol_get_plugin('manual')) {
1196 if (!$instances = $DB->get_records('enrol', array('enrol'=>'manual', 'courseid'=>$courseid, 'status'=>ENROL_INSTANCE_ENABLED
), 'sortorder,id ASC')) {
1200 if ($roleid && !$DB->record_exists('role', ['id' => $roleid])) {
1204 $instance = reset($instances);
1205 $enrol->enrol_user($instance, $userid, $roleid, $timestart, $timeend);
1211 * Is there a chance users might self enrol
1212 * @param int $courseid
1215 function enrol_selfenrol_available($courseid) {
1218 $plugins = enrol_get_plugins(true);
1219 $enrolinstances = enrol_get_instances($courseid, true);
1220 foreach($enrolinstances as $instance) {
1221 if (!isset($plugins[$instance->enrol
])) {
1224 if ($instance->enrol
=== 'guest') {
1227 if ((isguestuser() ||
!isloggedin()) &&
1228 ($plugins[$instance->enrol
]->is_self_enrol_available($instance) === true)) {
1232 if ($plugins[$instance->enrol
]->show_enrolme_link($instance) === true) {
1242 * This function returns the end of current active user enrolment.
1244 * It deals correctly with multiple overlapping user enrolments.
1246 * @param int $courseid
1247 * @param int $userid
1248 * @return int|bool timestamp when active enrolment ends, false means no active enrolment now, 0 means never
1250 function enrol_get_enrolment_end($courseid, $userid) {
1254 FROM {user_enrolments} ue
1255 JOIN {enrol} e ON (e.id = ue.enrolid AND e.courseid = :courseid)
1256 JOIN {user} u ON u.id = ue.userid
1257 WHERE ue.userid = :userid AND ue.status = :active AND e.status = :enabled AND u.deleted = 0";
1258 $params = array('enabled'=>ENROL_INSTANCE_ENABLED
, 'active'=>ENROL_USER_ACTIVE
, 'userid'=>$userid, 'courseid'=>$courseid);
1260 if (!$enrolments = $DB->get_records_sql($sql, $params)) {
1266 foreach ($enrolments as $ue) {
1267 $start = (int)$ue->timestart
;
1268 $end = (int)$ue->timeend
;
1269 if ($end != 0 and $end < $start) {
1270 debugging('Invalid enrolment start or end in user_enrolment id:'.$ue->id
);
1273 if (isset($changes[$start])) {
1274 $changes[$start] = $changes[$start] +
1;
1276 $changes[$start] = 1;
1280 } else if (isset($changes[$end])) {
1281 $changes[$end] = $changes[$end] - 1;
1283 $changes[$end] = -1;
1287 // let's sort then enrolment starts&ends and go through them chronologically,
1288 // looking for current status and the next future end of enrolment
1295 foreach ($changes as $time => $change) {
1297 if ($present === null) {
1298 // we have just went past current time
1299 $present = $current;
1301 // no enrolment active
1305 if ($present !== null) {
1306 // we are already in the future - look for possible end
1307 if ($current +
$change < 1) {
1312 $current +
= $change;
1323 * Is current user accessing course via this enrolment method?
1325 * This is intended for operations that are going to affect enrol instances.
1327 * @param stdClass $instance enrol instance
1330 function enrol_accessing_via_instance(stdClass
$instance) {
1333 if (empty($instance->id
)) {
1337 if (is_siteadmin()) {
1338 // Admins may go anywhere.
1342 return $DB->record_exists('user_enrolments', array('userid'=>$USER->id
, 'enrolid'=>$instance->id
));
1346 * Returns true if user is enrolled (is participating) in course
1347 * this is intended for students and teachers.
1349 * Since 2.2 the result for active enrolments and current user are cached.
1351 * @param context $context
1352 * @param int|stdClass $user if null $USER is used, otherwise user object or id expected
1353 * @param string $withcapability extra capability name
1354 * @param bool $onlyactive consider only active enrolments in enabled plugins and time restrictions
1357 function is_enrolled(context
$context, $user = null, $withcapability = '', $onlyactive = false) {
1360 // First find the course context.
1361 $coursecontext = $context->get_course_context();
1363 // Make sure there is a real user specified.
1364 if ($user === null) {
1365 $userid = isset($USER->id
) ?
$USER->id
: 0;
1367 $userid = is_object($user) ?
$user->id
: $user;
1370 if (empty($userid)) {
1373 } else if (isguestuser($userid)) {
1374 // Guest account can not be enrolled anywhere.
1378 // Note everybody participates on frontpage, so for other contexts...
1379 if ($coursecontext->instanceid
!= SITEID
) {
1380 // Try cached info first - the enrolled flag is set only when active enrolment present.
1381 if ($USER->id
== $userid) {
1382 $coursecontext->reload_if_dirty();
1383 if (isset($USER->enrol
['enrolled'][$coursecontext->instanceid
])) {
1384 if ($USER->enrol
['enrolled'][$coursecontext->instanceid
] > time()) {
1385 if ($withcapability and !has_capability($withcapability, $context, $userid)) {
1394 // Look for active enrolments only.
1395 $until = enrol_get_enrolment_end($coursecontext->instanceid
, $userid);
1397 if ($until === false) {
1401 if ($USER->id
== $userid) {
1403 $until = ENROL_MAX_TIMESTAMP
;
1405 $USER->enrol
['enrolled'][$coursecontext->instanceid
] = $until;
1406 if (isset($USER->enrol
['tempguest'][$coursecontext->instanceid
])) {
1407 unset($USER->enrol
['tempguest'][$coursecontext->instanceid
]);
1408 remove_temp_course_roles($coursecontext);
1413 // Any enrolment is good for us here, even outdated, disabled or inactive.
1415 FROM {user_enrolments} ue
1416 JOIN {enrol} e ON (e.id = ue.enrolid AND e.courseid = :courseid)
1417 JOIN {user} u ON u.id = ue.userid
1418 WHERE ue.userid = :userid AND u.deleted = 0";
1419 $params = array('userid' => $userid, 'courseid' => $coursecontext->instanceid
);
1420 if (!$DB->record_exists_sql($sql, $params)) {
1426 if ($withcapability and !has_capability($withcapability, $context, $userid)) {
1434 * Returns an array of joins, wheres and params that will limit the group of
1435 * users to only those enrolled and with given capability (if specified).
1437 * Note this join will return duplicate rows for users who have been enrolled
1438 * several times (e.g. as manual enrolment, and as self enrolment). You may
1439 * need to use a SELECT DISTINCT in your query (see get_enrolled_sql for example).
1441 * In case is guaranteed some of the joins never match any rows, the resulting
1442 * join_sql->cannotmatchanyrows will be true. This happens when the capability
1445 * @param context $context
1446 * @param string $prefix optional, a prefix to the user id column
1447 * @param string|array $capability optional, may include a capability name, or array of names.
1448 * If an array is provided then this is the equivalent of a logical 'OR',
1449 * i.e. the user needs to have one of these capabilities.
1450 * @param int $group optional, 0 indicates no current group and USERSWITHOUTGROUP users without any group; otherwise the group id
1451 * @param bool $onlyactive consider only active enrolments in enabled plugins and time restrictions
1452 * @param bool $onlysuspended inverse of onlyactive, consider only suspended enrolments
1453 * @param int $enrolid The enrolment ID. If not 0, only users enrolled using this enrolment method will be returned.
1454 * @return \core\dml\sql_join Contains joins, wheres, params and cannotmatchanyrows
1456 function get_enrolled_with_capabilities_join(context
$context, $prefix = '', $capability = '', $group = 0,
1457 $onlyactive = false, $onlysuspended = false, $enrolid = 0) {
1458 $uid = $prefix . 'u.id';
1461 $cannotmatchanyrows = false;
1463 $enrolledjoin = get_enrolled_join($context, $uid, $onlyactive, $onlysuspended, $enrolid);
1464 $joins[] = $enrolledjoin->joins
;
1465 $wheres[] = $enrolledjoin->wheres
;
1466 $params = $enrolledjoin->params
;
1467 $cannotmatchanyrows = $cannotmatchanyrows ||
$enrolledjoin->cannotmatchanyrows
;
1469 if (!empty($capability)) {
1470 $capjoin = get_with_capability_join($context, $capability, $uid);
1471 $joins[] = $capjoin->joins
;
1472 $wheres[] = $capjoin->wheres
;
1473 $params = array_merge($params, $capjoin->params
);
1474 $cannotmatchanyrows = $cannotmatchanyrows ||
$capjoin->cannotmatchanyrows
;
1478 $groupjoin = groups_get_members_join($group, $uid, $context);
1479 $joins[] = $groupjoin->joins
;
1480 $params = array_merge($params, $groupjoin->params
);
1481 if (!empty($groupjoin->wheres
)) {
1482 $wheres[] = $groupjoin->wheres
;
1484 $cannotmatchanyrows = $cannotmatchanyrows ||
$groupjoin->cannotmatchanyrows
;
1487 $joins = implode("\n", $joins);
1488 $wheres[] = "{$prefix}u.deleted = 0";
1489 $wheres = implode(" AND ", $wheres);
1491 return new \core\dml\
sql_join($joins, $wheres, $params, $cannotmatchanyrows);
1495 * Returns array with sql code and parameters returning all ids
1496 * of users enrolled into course.
1498 * This function is using 'eu[0-9]+_' prefix for table names and parameters.
1500 * @param context $context
1501 * @param string $withcapability
1502 * @param int $groupid 0 means ignore groups, USERSWITHOUTGROUP without any group and any other value limits the result by group id
1503 * @param bool $onlyactive consider only active enrolments in enabled plugins and time restrictions
1504 * @param bool $onlysuspended inverse of onlyactive, consider only suspended enrolments
1505 * @param int $enrolid The enrolment ID. If not 0, only users enrolled using this enrolment method will be returned.
1506 * @return array list($sql, $params)
1508 function get_enrolled_sql(context
$context, $withcapability = '', $groupid = 0, $onlyactive = false, $onlysuspended = false,
1511 // Use unique prefix just in case somebody makes some SQL magic with the result.
1514 $prefix = 'eu' . $i . '_';
1516 $capjoin = get_enrolled_with_capabilities_join(
1517 $context, $prefix, $withcapability, $groupid, $onlyactive, $onlysuspended, $enrolid);
1519 $sql = "SELECT DISTINCT {$prefix}u.id
1520 FROM {user} {$prefix}u
1522 WHERE $capjoin->wheres";
1524 return array($sql, $capjoin->params
);
1528 * Returns array with sql joins and parameters returning all ids
1529 * of users enrolled into course.
1531 * This function is using 'ej[0-9]+_' prefix for table names and parameters.
1533 * @throws coding_exception
1535 * @param context $context
1536 * @param string $useridcolumn User id column used the calling query, e.g. u.id
1537 * @param bool $onlyactive consider only active enrolments in enabled plugins and time restrictions
1538 * @param bool $onlysuspended inverse of onlyactive, consider only suspended enrolments
1539 * @param int $enrolid The enrolment ID. If not 0, only users enrolled using this enrolment method will be returned.
1540 * @return \core\dml\sql_join Contains joins, wheres, params
1542 function get_enrolled_join(context
$context, $useridcolumn, $onlyactive = false, $onlysuspended = false, $enrolid = 0) {
1543 // Use unique prefix just in case somebody makes some SQL magic with the result.
1546 $prefix = 'ej' . $i . '_';
1548 // First find the course context.
1549 $coursecontext = $context->get_course_context();
1551 $isfrontpage = ($coursecontext->instanceid
== SITEID
);
1553 if ($onlyactive && $onlysuspended) {
1554 throw new coding_exception("Both onlyactive and onlysuspended are set, this is probably not what you want!");
1556 if ($isfrontpage && $onlysuspended) {
1557 throw new coding_exception("onlysuspended is not supported on frontpage; please add your own early-exit!");
1564 $wheres[] = "1 = 1"; // Prevent broken where clauses later on.
1566 // Note all users are "enrolled" on the frontpage, but for others...
1567 if (!$isfrontpage) {
1568 $where1 = "{$prefix}ue.status = :{$prefix}active AND {$prefix}e.status = :{$prefix}enabled";
1569 $where2 = "{$prefix}ue.timestart < :{$prefix}now1 AND ({$prefix}ue.timeend = 0 OR {$prefix}ue.timeend > :{$prefix}now2)";
1571 $enrolconditions = array(
1572 "{$prefix}e.id = {$prefix}ue.enrolid",
1573 "{$prefix}e.courseid = :{$prefix}courseid",
1576 $enrolconditions[] = "{$prefix}e.id = :{$prefix}enrolid";
1577 $params[$prefix . 'enrolid'] = $enrolid;
1579 $enrolconditionssql = implode(" AND ", $enrolconditions);
1580 $ejoin = "JOIN {enrol} {$prefix}e ON ($enrolconditionssql)";
1582 $params[$prefix.'courseid'] = $coursecontext->instanceid
;
1584 if (!$onlysuspended) {
1585 $joins[] = "JOIN {user_enrolments} {$prefix}ue ON {$prefix}ue.userid = $useridcolumn";
1588 $wheres[] = "$where1 AND $where2";
1591 // Suspended only where there is enrolment but ALL are suspended.
1592 // Consider multiple enrols where one is not suspended or plain role_assign.
1593 $enrolselect = "SELECT DISTINCT {$prefix}ue.userid FROM {user_enrolments} {$prefix}ue $ejoin WHERE $where1 AND $where2";
1594 $joins[] = "JOIN {user_enrolments} {$prefix}ue1 ON {$prefix}ue1.userid = $useridcolumn";
1595 $enrolconditions = array(
1596 "{$prefix}e1.id = {$prefix}ue1.enrolid",
1597 "{$prefix}e1.courseid = :{$prefix}_e1_courseid",
1600 $enrolconditions[] = "{$prefix}e1.id = :{$prefix}e1_enrolid";
1601 $params[$prefix . 'e1_enrolid'] = $enrolid;
1603 $enrolconditionssql = implode(" AND ", $enrolconditions);
1604 $joins[] = "JOIN {enrol} {$prefix}e1 ON ($enrolconditionssql)";
1605 $params["{$prefix}_e1_courseid"] = $coursecontext->instanceid
;
1606 $wheres[] = "$useridcolumn NOT IN ($enrolselect)";
1609 if ($onlyactive ||
$onlysuspended) {
1610 $now = round(time(), -2); // Rounding helps caching in DB.
1611 $params = array_merge($params, array($prefix . 'enabled' => ENROL_INSTANCE_ENABLED
,
1612 $prefix . 'active' => ENROL_USER_ACTIVE
,
1613 $prefix . 'now1' => $now, $prefix . 'now2' => $now));
1617 $joins = implode("\n", $joins);
1618 $wheres = implode(" AND ", $wheres);
1620 return new \core\dml\
sql_join($joins, $wheres, $params);
1624 * Returns list of users enrolled into course.
1626 * @param context $context
1627 * @param string $withcapability
1628 * @param int $groupid 0 means ignore groups, USERSWITHOUTGROUP without any group and any other value limits the result by group id
1629 * @param string $userfields requested user record fields
1630 * @param string $orderby
1631 * @param int $limitfrom return a subset of records, starting at this point (optional, required if $limitnum is set).
1632 * @param int $limitnum return a subset comprising this many records (optional, required if $limitfrom is set).
1633 * @param bool $onlyactive consider only active enrolments in enabled plugins and time restrictions
1634 * @return array of user records
1636 function get_enrolled_users(context
$context, $withcapability = '', $groupid = 0, $userfields = 'u.*', $orderby = null,
1637 $limitfrom = 0, $limitnum = 0, $onlyactive = false) {
1640 list($esql, $params) = get_enrolled_sql($context, $withcapability, $groupid, $onlyactive);
1641 $sql = "SELECT $userfields
1643 JOIN ($esql) je ON je.id = u.id
1644 WHERE u.deleted = 0";
1647 $sql = "$sql ORDER BY $orderby";
1649 list($sort, $sortparams) = users_order_by_sql('u');
1650 $sql = "$sql ORDER BY $sort";
1651 $params = array_merge($params, $sortparams);
1654 return $DB->get_records_sql($sql, $params, $limitfrom, $limitnum);
1658 * Counts list of users enrolled into course (as per above function)
1660 * @param context $context
1661 * @param string $withcapability
1662 * @param int $groupid 0 means ignore groups, any other value limits the result by group id
1663 * @param bool $onlyactive consider only active enrolments in enabled plugins and time restrictions
1664 * @return int number of users enrolled into course
1666 function count_enrolled_users(context
$context, $withcapability = '', $groupid = 0, $onlyactive = false) {
1669 $capjoin = get_enrolled_with_capabilities_join(
1670 $context, '', $withcapability, $groupid, $onlyactive);
1672 $sql = "SELECT COUNT(DISTINCT u.id)
1675 WHERE $capjoin->wheres AND u.deleted = 0";
1677 return $DB->count_records_sql($sql, $capjoin->params
);
1681 * Send welcome email "from" options.
1683 * @return array list of from options
1685 function enrol_send_welcome_email_options() {
1687 ENROL_DO_NOT_SEND_EMAIL
=> get_string('no'),
1688 ENROL_SEND_EMAIL_FROM_COURSE_CONTACT
=> get_string('sendfromcoursecontact', 'enrol'),
1689 ENROL_SEND_EMAIL_FROM_KEY_HOLDER
=> get_string('sendfromkeyholder', 'enrol'),
1690 ENROL_SEND_EMAIL_FROM_NOREPLY
=> get_string('sendfromnoreply', 'enrol')
1695 * Serve the user enrolment form as a fragment.
1697 * @param array $args List of named arguments for the fragment loader.
1700 function enrol_output_fragment_user_enrolment_form($args) {
1703 $args = (object) $args;
1704 $context = $args->context
;
1705 require_capability('moodle/course:enrolreview', $context);
1707 $ueid = $args->ueid
;
1708 $userenrolment = $DB->get_record('user_enrolments', ['id' => $ueid], '*', MUST_EXIST
);
1709 $instance = $DB->get_record('enrol', ['id' => $userenrolment->enrolid
], '*', MUST_EXIST
);
1710 $plugin = enrol_get_plugin($instance->enrol
);
1712 'ue' => $userenrolment,
1714 'enrolinstancename' => $plugin->get_instance_name($instance)
1717 // Set the data if applicable.
1719 if (isset($args->formdata
)) {
1720 $serialiseddata = json_decode($args->formdata
);
1721 parse_str($serialiseddata, $data);
1724 require_once("$CFG->dirroot/enrol/editenrolment_form.php");
1725 $mform = new \
enrol_user_enrolment_form(null, $customdata, 'post', '', null, true, $data);
1727 if (!empty($data)) {
1728 $mform->set_data($data);
1729 $mform->is_validated();
1732 return $mform->render();
1736 * Returns the course where a user enrolment belong to.
1738 * @param int $ueid user_enrolments id
1741 function enrol_get_course_by_user_enrolment_id($ueid) {
1743 $sql = "SELECT c.* FROM {user_enrolments} ue
1744 JOIN {enrol} e ON e.id = ue.enrolid
1745 JOIN {course} c ON c.id = e.courseid
1746 WHERE ue.id = :ueid";
1747 return $DB->get_record_sql($sql, array('ueid' => $ueid));
1751 * Return all users enrolled in a course.
1753 * @param int $courseid Course id or false if using $uefilter (user enrolment ids may belong to different courses)
1754 * @param bool $onlyactive consider only active enrolments in enabled plugins and time restrictions
1755 * @param array $usersfilter Limit the results obtained to this list of user ids. $uefilter compatibility not guaranteed.
1756 * @param array $uefilter Limit the results obtained to this list of user enrolment ids. $usersfilter compatibility not guaranteed.
1757 * @return stdClass[]
1759 function enrol_get_course_users($courseid = false, $onlyactive = false, $usersfilter = array(), $uefilter = array()) {
1762 if (!$courseid && !$usersfilter && !$uefilter) {
1763 throw new \
coding_exception('You should specify at least 1 filter: courseid, users or user enrolments');
1766 $sql = "SELECT ue.id AS ueid, ue.status AS uestatus, ue.enrolid AS ueenrolid, ue.timestart AS uetimestart,
1767 ue.timeend AS uetimeend, ue.modifierid AS uemodifierid, ue.timecreated AS uetimecreated,
1768 ue.timemodified AS uetimemodified, e.status AS estatus,
1769 u.* FROM {user_enrolments} ue
1770 JOIN {enrol} e ON e.id = ue.enrolid
1771 JOIN {user} u ON ue.userid = u.id
1776 $conditions[] = "e.courseid = :courseid";
1777 $params['courseid'] = $courseid;
1781 $conditions[] = "ue.status = :active AND e.status = :enabled AND ue.timestart < :now1 AND " .
1782 "(ue.timeend = 0 OR ue.timeend > :now2)";
1783 // Improves db caching.
1784 $params['now1'] = round(time(), -2);
1785 $params['now2'] = $params['now1'];
1786 $params['active'] = ENROL_USER_ACTIVE
;
1787 $params['enabled'] = ENROL_INSTANCE_ENABLED
;
1791 list($usersql, $userparams) = $DB->get_in_or_equal($usersfilter, SQL_PARAMS_NAMED
);
1792 $conditions[] = "ue.userid $usersql";
1793 $params = $params +
$userparams;
1797 list($uesql, $ueparams) = $DB->get_in_or_equal($uefilter, SQL_PARAMS_NAMED
);
1798 $conditions[] = "ue.id $uesql";
1799 $params = $params +
$ueparams;
1802 return $DB->get_records_sql($sql . ' ' . implode(' AND ', $conditions), $params);
1806 * Get the list of options for the enrolment period dropdown
1808 * @return array List of options for the enrolment period dropdown
1810 function enrol_get_period_list() {
1812 $periodmenu[''] = get_string('unlimited');
1813 for ($i = 1; $i <= 365; $i++
) {
1814 $seconds = $i * DAYSECS
;
1815 $periodmenu[$seconds] = get_string('numdays', '', $i);
1821 * Calculate duration base on start time and end time
1823 * @param int $timestart Time start
1824 * @param int $timeend Time end
1825 * @return float|int Calculated duration
1827 function enrol_calculate_duration($timestart, $timeend) {
1828 $duration = floor(($timeend - $timestart) / DAYSECS
) * DAYSECS
;
1833 * Enrolment plugins abstract class.
1835 * All enrol plugins should be based on this class,
1836 * this is also the main source of documentation.
1838 * @copyright 2010 Petr Skoda {@link http://skodak.org}
1839 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1841 abstract class enrol_plugin
{
1842 protected $config = null;
1845 * Returns name of this enrol plugin
1848 public function get_name() {
1849 // second word in class is always enrol name, sorry, no fancy plugin names with _
1850 $words = explode('_', get_class($this));
1855 * Returns localised name of enrol instance
1857 * @param object $instance (null is accepted too)
1860 public function get_instance_name($instance) {
1861 if (empty($instance->name
)) {
1862 $enrol = $this->get_name();
1863 return get_string('pluginname', 'enrol_'.$enrol);
1865 $context = context_course
::instance($instance->courseid
);
1866 return format_string($instance->name
, true, array('context'=>$context));
1871 * Returns optional enrolment information icons.
1873 * This is used in course list for quick overview of enrolment options.
1875 * We are not using single instance parameter because sometimes
1876 * we might want to prevent icon repetition when multiple instances
1877 * of one type exist. One instance may also produce several icons.
1879 * @param array $instances all enrol instances of this type in one course
1880 * @return array of pix_icon
1882 public function get_info_icons(array $instances) {
1887 * Returns optional enrolment instance description text.
1889 * This is used in detailed course information.
1892 * @param object $instance
1893 * @return string short html text
1895 public function get_description_text($instance) {
1900 * Makes sure config is loaded and cached.
1903 protected function load_config() {
1904 if (!isset($this->config
)) {
1905 $name = $this->get_name();
1906 $this->config
= get_config("enrol_$name");
1911 * Returns plugin config value
1912 * @param string $name
1913 * @param string $default value if config does not exist yet
1914 * @return string value or default
1916 public function get_config($name, $default = NULL) {
1917 $this->load_config();
1918 return isset($this->config
->$name) ?
$this->config
->$name : $default;
1922 * Sets plugin config value
1923 * @param string $name name of config
1924 * @param string $value string config value, null means delete
1925 * @return string value
1927 public function set_config($name, $value) {
1928 $pluginname = $this->get_name();
1929 $this->load_config();
1930 if ($value === NULL) {
1931 unset($this->config
->$name);
1933 $this->config
->$name = $value;
1935 set_config($name, $value, "enrol_$pluginname");
1939 * Does this plugin assign protected roles are can they be manually removed?
1940 * @return bool - false means anybody may tweak roles, it does not use itemid and component when assigning roles
1942 public function roles_protected() {
1947 * Does this plugin allow manual enrolments?
1949 * @param stdClass $instance course enrol instance
1950 * All plugins allowing this must implement 'enrol/xxx:enrol' capability
1952 * @return bool - true means user with 'enrol/xxx:enrol' may enrol others freely, false means nobody may add more enrolments manually
1954 public function allow_enrol(stdClass
$instance) {
1959 * Does this plugin allow manual unenrolment of all users?
1960 * All plugins allowing this must implement 'enrol/xxx:unenrol' capability
1962 * @param stdClass $instance course enrol instance
1963 * @return bool - true means user with 'enrol/xxx:unenrol' may unenrol others freely, false means nobody may touch user_enrolments
1965 public function allow_unenrol(stdClass
$instance) {
1970 * Does this plugin allow manual unenrolment of a specific user?
1971 * All plugins allowing this must implement 'enrol/xxx:unenrol' capability
1973 * This is useful especially for synchronisation plugins that
1974 * do suspend instead of full unenrolment.
1976 * @param stdClass $instance course enrol instance
1977 * @param stdClass $ue record from user_enrolments table, specifies user
1979 * @return bool - true means user with 'enrol/xxx:unenrol' may unenrol this user, false means nobody may touch this user enrolment
1981 public function allow_unenrol_user(stdClass
$instance, stdClass
$ue) {
1982 return $this->allow_unenrol($instance);
1986 * Does this plugin allow manual changes in user_enrolments table?
1988 * All plugins allowing this must implement 'enrol/xxx:manage' capability
1990 * @param stdClass $instance course enrol instance
1991 * @return bool - true means it is possible to change enrol period and status in user_enrolments table
1993 public function allow_manage(stdClass
$instance) {
1998 * Does this plugin support some way to user to self enrol?
2000 * @param stdClass $instance course enrol instance
2002 * @return bool - true means show "Enrol me in this course" link in course UI
2004 public function show_enrolme_link(stdClass
$instance) {
2009 * Does this plugin support some way to self enrol?
2010 * This function doesn't check user capabilities. Use can_self_enrol to check capabilities.
2012 * @param stdClass $instance enrolment instance
2013 * @return bool - true means "Enrol me in this course" link could be available.
2015 public function is_self_enrol_available(stdClass
$instance) {
2020 * Attempt to automatically enrol current user in course without any interaction,
2021 * calling code has to make sure the plugin and instance are active.
2023 * This should return either a timestamp in the future or false.
2025 * @param stdClass $instance course enrol instance
2026 * @return bool|int false means not enrolled, integer means timeend
2028 public function try_autoenrol(stdClass
$instance) {
2035 * Attempt to automatically gain temporary guest access to course,
2036 * calling code has to make sure the plugin and instance are active.
2038 * This should return either a timestamp in the future or false.
2040 * @param stdClass $instance course enrol instance
2041 * @return bool|int false means no guest access, integer means timeend
2043 public function try_guestaccess(stdClass
$instance) {
2050 * Enrol user into course via enrol instance.
2052 * @param stdClass $instance
2053 * @param int $userid
2054 * @param int $roleid optional role id
2055 * @param int $timestart 0 means unknown
2056 * @param int $timeend 0 means forever
2057 * @param int $status default to ENROL_USER_ACTIVE for new enrolments, no change by default in updates
2058 * @param bool $recovergrades restore grade history
2061 public function enrol_user(stdClass
$instance, $userid, $roleid = null, $timestart = 0, $timeend = 0, $status = null, $recovergrades = null) {
2062 global $DB, $USER, $CFG; // CFG necessary!!!
2064 if ($instance->courseid
== SITEID
) {
2065 throw new coding_exception('invalid attempt to enrol into frontpage course!');
2068 $name = $this->get_name();
2069 $courseid = $instance->courseid
;
2071 if ($instance->enrol
!== $name) {
2072 throw new coding_exception('invalid enrol instance!');
2074 $context = context_course
::instance($instance->courseid
, MUST_EXIST
);
2075 if (!isset($recovergrades)) {
2076 $recovergrades = $CFG->recovergradesdefault
;
2081 if ($ue = $DB->get_record('user_enrolments', array('enrolid'=>$instance->id
, 'userid'=>$userid))) {
2082 //only update if timestart or timeend or status are different.
2083 if ($ue->timestart
!= $timestart or $ue->timeend
!= $timeend or (!is_null($status) and $ue->status
!= $status)) {
2084 $this->update_user_enrol($instance, $userid, $status, $timestart, $timeend);
2087 $ue = new stdClass();
2088 $ue->enrolid
= $instance->id
;
2089 $ue->status
= is_null($status) ? ENROL_USER_ACTIVE
: $status;
2090 $ue->userid
= $userid;
2091 $ue->timestart
= $timestart;
2092 $ue->timeend
= $timeend;
2093 $ue->modifierid
= $USER->id
;
2094 $ue->timecreated
= time();
2095 $ue->timemodified
= $ue->timecreated
;
2096 $ue->id
= $DB->insert_record('user_enrolments', $ue);
2103 $event = \core\event\user_enrolment_created
::create(
2105 'objectid' => $ue->id
,
2106 'courseid' => $courseid,
2107 'context' => $context,
2108 'relateduserid' => $ue->userid
,
2109 'other' => array('enrol' => $name)
2113 // Check if course contacts cache needs to be cleared.
2114 core_course_category
::user_enrolment_changed($courseid, $ue->userid
,
2115 $ue->status
, $ue->timestart
, $ue->timeend
);
2119 // this must be done after the enrolment event so that the role_assigned event is triggered afterwards
2120 if ($this->roles_protected()) {
2121 role_assign($roleid, $userid, $context->id
, 'enrol_'.$name, $instance->id
);
2123 role_assign($roleid, $userid, $context->id
);
2127 // Recover old grades if present.
2128 if ($recovergrades) {
2129 require_once("$CFG->libdir/gradelib.php");
2130 grade_recover_history_grades($userid, $courseid);
2133 // reset current user enrolment caching
2134 if ($userid == $USER->id
) {
2135 if (isset($USER->enrol
['enrolled'][$courseid])) {
2136 unset($USER->enrol
['enrolled'][$courseid]);
2138 if (isset($USER->enrol
['tempguest'][$courseid])) {
2139 unset($USER->enrol
['tempguest'][$courseid]);
2140 remove_temp_course_roles($context);
2146 * Store user_enrolments changes and trigger event.
2148 * @param stdClass $instance
2149 * @param int $userid
2150 * @param int $status
2151 * @param int $timestart
2152 * @param int $timeend
2155 public function update_user_enrol(stdClass
$instance, $userid, $status = NULL, $timestart = NULL, $timeend = NULL) {
2156 global $DB, $USER, $CFG;
2158 $name = $this->get_name();
2160 if ($instance->enrol
!== $name) {
2161 throw new coding_exception('invalid enrol instance!');
2164 if (!$ue = $DB->get_record('user_enrolments', array('enrolid'=>$instance->id
, 'userid'=>$userid))) {
2165 // weird, user not enrolled
2170 if (isset($status) and $ue->status
!= $status) {
2171 $ue->status
= $status;
2174 if (isset($timestart) and $ue->timestart
!= $timestart) {
2175 $ue->timestart
= $timestart;
2178 if (isset($timeend) and $ue->timeend
!= $timeend) {
2179 $ue->timeend
= $timeend;
2188 $ue->modifierid
= $USER->id
;
2189 $ue->timemodified
= time();
2190 $DB->update_record('user_enrolments', $ue);
2192 // User enrolments have changed, so mark user as dirty.
2193 mark_user_dirty($userid);
2195 // Invalidate core_access cache for get_suspended_userids.
2196 cache_helper
::invalidate_by_definition('core', 'suspended_userids', array(), array($instance->courseid
));
2199 $event = \core\event\user_enrolment_updated
::create(
2201 'objectid' => $ue->id
,
2202 'courseid' => $instance->courseid
,
2203 'context' => context_course
::instance($instance->courseid
),
2204 'relateduserid' => $ue->userid
,
2205 'other' => array('enrol' => $name)
2210 core_course_category
::user_enrolment_changed($instance->courseid
, $ue->userid
,
2211 $ue->status
, $ue->timestart
, $ue->timeend
);
2215 * Unenrol user from course,
2216 * the last unenrolment removes all remaining roles.
2218 * @param stdClass $instance
2219 * @param int $userid
2222 public function unenrol_user(stdClass
$instance, $userid) {
2223 global $CFG, $USER, $DB;
2224 require_once("$CFG->dirroot/group/lib.php");
2226 $name = $this->get_name();
2227 $courseid = $instance->courseid
;
2229 if ($instance->enrol
!== $name) {
2230 throw new coding_exception('invalid enrol instance!');
2232 $context = context_course
::instance($instance->courseid
, MUST_EXIST
);
2234 if (!$ue = $DB->get_record('user_enrolments', array('enrolid'=>$instance->id
, 'userid'=>$userid))) {
2235 // weird, user not enrolled
2239 // Remove all users groups linked to this enrolment instance.
2240 if ($gms = $DB->get_records('groups_members', array('userid'=>$userid, 'component'=>'enrol_'.$name, 'itemid'=>$instance->id
))) {
2241 foreach ($gms as $gm) {
2242 groups_remove_member($gm->groupid
, $gm->userid
);
2246 role_unassign_all(array('userid'=>$userid, 'contextid'=>$context->id
, 'component'=>'enrol_'.$name, 'itemid'=>$instance->id
));
2247 $DB->delete_records('user_enrolments', array('id'=>$ue->id
));
2249 // add extra info and trigger event
2250 $ue->courseid
= $courseid;
2254 FROM {user_enrolments} ue
2255 JOIN {enrol} e ON (e.id = ue.enrolid)
2256 WHERE ue.userid = :userid AND e.courseid = :courseid";
2257 if ($DB->record_exists_sql($sql, array('userid'=>$userid, 'courseid'=>$courseid))) {
2258 $ue->lastenrol
= false;
2261 // the big cleanup IS necessary!
2262 require_once("$CFG->libdir/gradelib.php");
2264 // remove all remaining roles
2265 role_unassign_all(array('userid'=>$userid, 'contextid'=>$context->id
), true, false);
2267 //clean up ALL invisible user data from course if this is the last enrolment - groups, grades, etc.
2268 groups_delete_group_members($courseid, $userid);
2270 grade_user_unenrol($courseid, $userid);
2272 $DB->delete_records('user_lastaccess', array('userid'=>$userid, 'courseid'=>$courseid));
2274 $ue->lastenrol
= true; // means user not enrolled any more
2277 $event = \core\event\user_enrolment_deleted
::create(
2279 'courseid' => $courseid,
2280 'context' => $context,
2281 'relateduserid' => $ue->userid
,
2282 'objectid' => $ue->id
,
2284 'userenrolment' => (array)$ue,
2291 // User enrolments have changed, so mark user as dirty.
2292 mark_user_dirty($userid);
2294 // Check if courrse contacts cache needs to be cleared.
2295 core_course_category
::user_enrolment_changed($courseid, $ue->userid
, ENROL_USER_SUSPENDED
);
2297 // reset current user enrolment caching
2298 if ($userid == $USER->id
) {
2299 if (isset($USER->enrol
['enrolled'][$courseid])) {
2300 unset($USER->enrol
['enrolled'][$courseid]);
2302 if (isset($USER->enrol
['tempguest'][$courseid])) {
2303 unset($USER->enrol
['tempguest'][$courseid]);
2304 remove_temp_course_roles($context);
2310 * Forces synchronisation of user enrolments.
2312 * This is important especially for external enrol plugins,
2313 * this function is called for all enabled enrol plugins
2314 * right after every user login.
2316 * @param object $user user record
2319 public function sync_user_enrolments($user) {
2320 // override if necessary
2324 * This returns false for backwards compatibility, but it is really recommended.
2329 public function use_standard_editing_ui() {
2334 * Return whether or not, given the current state, it is possible to add a new instance
2335 * of this enrolment plugin to the course.
2337 * Default implementation is just for backwards compatibility.
2339 * @param int $courseid
2342 public function can_add_instance($courseid) {
2343 $link = $this->get_newinstance_link($courseid);
2344 return !empty($link);
2348 * Return whether or not, given the current state, it is possible to edit an instance
2349 * of this enrolment plugin in the course. Used by the standard editing UI
2350 * to generate a link to the edit instance form if editing is allowed.
2352 * @param stdClass $instance
2355 public function can_edit_instance($instance) {
2356 $context = context_course
::instance($instance->courseid
);
2358 return has_capability('enrol/' . $instance->enrol
. ':config', $context);
2362 * Returns link to page which may be used to add new instance of enrolment plugin in course.
2363 * @param int $courseid
2364 * @return moodle_url page url
2366 public function get_newinstance_link($courseid) {
2367 // override for most plugins, check if instance already exists in cases only one instance is supported
2372 * @deprecated since Moodle 2.8 MDL-35864 - please use can_delete_instance() instead.
2374 public function instance_deleteable($instance) {
2375 throw new coding_exception('Function enrol_plugin::instance_deleteable() is deprecated, use
2376 enrol_plugin::can_delete_instance() instead');
2380 * Is it possible to delete enrol instance via standard UI?
2382 * @param stdClass $instance
2385 public function can_delete_instance($instance) {
2390 * Is it possible to hide/show enrol instance via standard UI?
2392 * @param stdClass $instance
2395 public function can_hide_show_instance($instance) {
2396 debugging("The enrolment plugin '".$this->get_name()."' should override the function can_hide_show_instance().", DEBUG_DEVELOPER
);
2401 * Returns link to manual enrol UI if exists.
2402 * Does the access control tests automatically.
2404 * @param object $instance
2405 * @return moodle_url
2407 public function get_manual_enrol_link($instance) {
2412 * Returns list of unenrol links for all enrol instances in course.
2414 * @param int $instance
2415 * @return moodle_url or NULL if self unenrolment not supported
2417 public function get_unenrolself_link($instance) {
2418 global $USER, $CFG, $DB;
2420 $name = $this->get_name();
2421 if ($instance->enrol
!== $name) {
2422 throw new coding_exception('invalid enrol instance!');
2425 if ($instance->courseid
== SITEID
) {
2429 if (!enrol_is_enabled($name)) {
2433 if ($instance->status
!= ENROL_INSTANCE_ENABLED
) {
2437 if (!file_exists("$CFG->dirroot/enrol/$name/unenrolself.php")) {
2441 $context = context_course
::instance($instance->courseid
, MUST_EXIST
);
2443 if (!has_capability("enrol/$name:unenrolself", $context)) {
2447 if (!$DB->record_exists('user_enrolments', array('enrolid'=>$instance->id
, 'userid'=>$USER->id
, 'status'=>ENROL_USER_ACTIVE
))) {
2451 return new moodle_url("/enrol/$name/unenrolself.php", array('enrolid'=>$instance->id
));
2455 * Adds enrol instance UI to course edit form
2457 * @param object $instance enrol instance or null if does not exist yet
2458 * @param MoodleQuickForm $mform
2459 * @param object $data
2460 * @param object $context context of existing course or parent category if course does not exist
2463 public function course_edit_form($instance, MoodleQuickForm
$mform, $data, $context) {
2464 // override - usually at least enable/disable switch, has to add own form header
2468 * Adds form elements to add/edit instance form.
2471 * @param object $instance enrol instance or null if does not exist yet
2472 * @param MoodleQuickForm $mform
2473 * @param context $context
2476 public function edit_instance_form($instance, MoodleQuickForm
$mform, $context) {
2477 // Do nothing by default.
2481 * Perform custom validation of the data used to edit the instance.
2484 * @param array $data array of ("fieldname"=>value) of submitted data
2485 * @param array $files array of uploaded files "element_name"=>tmp_file_path
2486 * @param object $instance The instance data loaded from the DB.
2487 * @param context $context The context of the instance we are editing
2488 * @return array of "element_name"=>"error_description" if there are errors,
2489 * or an empty array if everything is OK.
2491 public function edit_instance_validation($data, $files, $instance, $context) {
2492 // No errors by default.
2493 debugging('enrol_plugin::edit_instance_validation() is missing. This plugin has no validation!', DEBUG_DEVELOPER
);
2498 * Validates course edit form data
2500 * @param object $instance enrol instance or null if does not exist yet
2501 * @param array $data
2502 * @param object $context context of existing course or parent category if course does not exist
2503 * @return array errors array
2505 public function course_edit_validation($instance, array $data, $context) {
2510 * Called after updating/inserting course.
2512 * @param bool $inserted true if course just inserted
2513 * @param object $course
2514 * @param object $data form data
2517 public function course_updated($inserted, $course, $data) {
2519 if ($this->get_config('defaultenrol')) {
2520 $this->add_default_instance($course);
2526 * Add new instance of enrol plugin.
2527 * @param object $course
2528 * @param array instance fields
2529 * @return int id of new instance, null if can not be created
2531 public function add_instance($course, array $fields = NULL) {
2534 if ($course->id
== SITEID
) {
2535 throw new coding_exception('Invalid request to add enrol instance to frontpage.');
2538 $instance = new stdClass();
2539 $instance->enrol
= $this->get_name();
2540 $instance->status
= ENROL_INSTANCE_ENABLED
;
2541 $instance->courseid
= $course->id
;
2542 $instance->enrolstartdate
= 0;
2543 $instance->enrolenddate
= 0;
2544 $instance->timemodified
= time();
2545 $instance->timecreated
= $instance->timemodified
;
2546 $instance->sortorder
= $DB->get_field('enrol', 'COALESCE(MAX(sortorder), -1) + 1', array('courseid'=>$course->id
));
2548 $fields = (array)$fields;
2549 unset($fields['enrol']);
2550 unset($fields['courseid']);
2551 unset($fields['sortorder']);
2552 foreach($fields as $field=>$value) {
2553 $instance->$field = $value;
2556 $instance->id
= $DB->insert_record('enrol', $instance);
2558 \core\event\enrol_instance_created
::create_from_record($instance)->trigger();
2560 return $instance->id
;
2564 * Update instance of enrol plugin.
2567 * @param stdClass $instance
2568 * @param stdClass $data modified instance fields
2571 public function update_instance($instance, $data) {
2573 $properties = array('status', 'name', 'password', 'customint1', 'customint2', 'customint3',
2574 'customint4', 'customint5', 'customint6', 'customint7', 'customint8',
2575 'customchar1', 'customchar2', 'customchar3', 'customdec1', 'customdec2',
2576 'customtext1', 'customtext2', 'customtext3', 'customtext4', 'roleid',
2577 'enrolperiod', 'expirynotify', 'notifyall', 'expirythreshold',
2578 'enrolstartdate', 'enrolenddate', 'cost', 'currency');
2580 foreach ($properties as $key) {
2581 if (isset($data->$key)) {
2582 $instance->$key = $data->$key;
2585 $instance->timemodified
= time();
2587 $update = $DB->update_record('enrol', $instance);
2589 \core\event\enrol_instance_updated
::create_from_record($instance)->trigger();
2595 * Add new instance of enrol plugin with default settings,
2596 * called when adding new instance manually or when adding new course.
2598 * Not all plugins support this.
2600 * @param object $course
2601 * @return int id of new instance or null if no default supported
2603 public function add_default_instance($course) {
2608 * Update instance status
2610 * Override when plugin needs to do some action when enabled or disabled.
2612 * @param stdClass $instance
2613 * @param int $newstatus ENROL_INSTANCE_ENABLED, ENROL_INSTANCE_DISABLED
2616 public function update_status($instance, $newstatus) {
2619 $instance->status
= $newstatus;
2620 $DB->update_record('enrol', $instance);
2622 $context = context_course
::instance($instance->courseid
);
2623 \core\event\enrol_instance_updated
::create_from_record($instance)->trigger();
2625 // Invalidate all enrol caches.
2626 $context->mark_dirty();
2630 * Delete course enrol plugin instance, unenrol all users.
2631 * @param object $instance
2634 public function delete_instance($instance) {
2637 $name = $this->get_name();
2638 if ($instance->enrol
!== $name) {
2639 throw new coding_exception('invalid enrol instance!');
2642 //first unenrol all users
2643 $participants = $DB->get_recordset('user_enrolments', array('enrolid'=>$instance->id
));
2644 foreach ($participants as $participant) {
2645 $this->unenrol_user($instance, $participant->userid
);
2647 $participants->close();
2649 // now clean up all remainders that were not removed correctly
2650 if ($gms = $DB->get_records('groups_members', array('itemid' => $instance->id
, 'component' => 'enrol_' . $name))) {
2651 foreach ($gms as $gm) {
2652 groups_remove_member($gm->groupid
, $gm->userid
);
2655 $DB->delete_records('role_assignments', array('itemid'=>$instance->id
, 'component'=>'enrol_'.$name));
2656 $DB->delete_records('user_enrolments', array('enrolid'=>$instance->id
));
2658 // finally drop the enrol row
2659 $DB->delete_records('enrol', array('id'=>$instance->id
));
2661 $context = context_course
::instance($instance->courseid
);
2662 \core\event\enrol_instance_deleted
::create_from_record($instance)->trigger();
2664 // Invalidate all enrol caches.
2665 $context->mark_dirty();
2669 * Creates course enrol form, checks if form submitted
2670 * and enrols user if necessary. It can also redirect.
2672 * @param stdClass $instance
2673 * @return string html text, usually a form in a text box
2675 public function enrol_page_hook(stdClass
$instance) {
2680 * Checks if user can self enrol.
2682 * @param stdClass $instance enrolment instance
2683 * @param bool $checkuserenrolment if true will check if user enrolment is inactive.
2684 * used by navigation to improve performance.
2685 * @return bool|string true if successful, else error message or false
2687 public function can_self_enrol(stdClass
$instance, $checkuserenrolment = true) {
2692 * Return information for enrolment instance containing list of parameters required
2693 * for enrolment, name of enrolment plugin etc.
2695 * @param stdClass $instance enrolment instance
2696 * @return array instance info.
2698 public function get_enrol_info(stdClass
$instance) {
2703 * Adds navigation links into course admin block.
2705 * By defaults looks for manage links only.
2707 * @param navigation_node $instancesnode
2708 * @param stdClass $instance
2711 public function add_course_navigation($instancesnode, stdClass
$instance) {
2712 if ($this->use_standard_editing_ui()) {
2713 $context = context_course
::instance($instance->courseid
);
2714 $cap = 'enrol/' . $instance->enrol
. ':config';
2715 if (has_capability($cap, $context)) {
2716 $linkparams = array('courseid' => $instance->courseid
, 'id' => $instance->id
, 'type' => $instance->enrol
);
2717 $managelink = new moodle_url('/enrol/editinstance.php', $linkparams);
2718 $instancesnode->add($this->get_instance_name($instance), $managelink, navigation_node
::TYPE_SETTING
);
2724 * Returns edit icons for the page with list of instances
2725 * @param stdClass $instance
2728 public function get_action_icons(stdClass
$instance) {
2732 if ($this->use_standard_editing_ui()) {
2733 $context = context_course
::instance($instance->courseid
);
2734 $cap = 'enrol/' . $instance->enrol
. ':config';
2735 if (has_capability($cap, $context)) {
2736 $linkparams = array('courseid' => $instance->courseid
, 'id' => $instance->id
, 'type' => $instance->enrol
);
2737 $editlink = new moodle_url("/enrol/editinstance.php", $linkparams);
2738 $icons[] = $OUTPUT->action_icon($editlink, new pix_icon('t/edit', get_string('edit'), 'core',
2739 array('class' => 'iconsmall')));
2746 * Reads version.php and determines if it is necessary
2747 * to execute the cron job now.
2750 public function is_cron_required() {
2753 $name = $this->get_name();
2754 $versionfile = "$CFG->dirroot/enrol/$name/version.php";
2755 $plugin = new stdClass();
2756 include($versionfile);
2757 if (empty($plugin->cron
)) {
2760 $lastexecuted = $this->get_config('lastcron', 0);
2761 if ($lastexecuted +
$plugin->cron
< time()) {
2769 * Called for all enabled enrol plugins that returned true from is_cron_required().
2772 public function cron() {
2776 * Called when user is about to be deleted
2777 * @param object $user
2780 public function user_delete($user) {
2785 JOIN {user_enrolments} ue ON (ue.enrolid = e.id)
2786 WHERE e.enrol = :name AND ue.userid = :userid";
2787 $params = array('name'=>$this->get_name(), 'userid'=>$user->id
);
2789 $rs = $DB->get_recordset_sql($sql, $params);
2790 foreach($rs as $instance) {
2791 $this->unenrol_user($instance, $user->id
);
2797 * Returns an enrol_user_button that takes the user to a page where they are able to
2798 * enrol users into the managers course through this plugin.
2800 * Optional: If the plugin supports manual enrolments it can choose to override this
2801 * otherwise it shouldn't
2803 * @param course_enrolment_manager $manager
2804 * @return enrol_user_button|false
2806 public function get_manual_enrol_button(course_enrolment_manager
$manager) {
2811 * Gets an array of the user enrolment actions
2813 * @param course_enrolment_manager $manager
2814 * @param stdClass $ue
2815 * @return array An array of user_enrolment_actions
2817 public function get_user_enrolment_actions(course_enrolment_manager
$manager, $ue) {
2819 $context = $manager->get_context();
2820 $instance = $ue->enrolmentinstance
;
2821 $params = $manager->get_moodlepage()->url
->params();
2822 $params['ue'] = $ue->id
;
2824 // Edit enrolment action.
2825 if ($this->allow_manage($instance) && has_capability("enrol/{$instance->enrol}:manage", $context)) {
2826 $title = get_string('editenrolment', 'enrol');
2827 $icon = new pix_icon('t/edit', $title);
2828 $url = new moodle_url('/enrol/editenrolment.php', $params);
2830 'class' => 'editenrollink',
2832 'data-action' => ENROL_ACTION_EDIT
2834 $actions[] = new user_enrolment_action($icon, $title, $url, $actionparams);
2838 if ($this->allow_unenrol_user($instance, $ue) && has_capability("enrol/{$instance->enrol}:unenrol", $context)) {
2839 $title = get_string('unenrol', 'enrol');
2840 $icon = new pix_icon('t/delete', $title);
2841 $url = new moodle_url('/enrol/unenroluser.php', $params);
2843 'class' => 'unenrollink',
2845 'data-action' => ENROL_ACTION_UNENROL
2847 $actions[] = new user_enrolment_action($icon, $title, $url, $actionparams);
2853 * Returns true if the plugin has one or more bulk operations that can be performed on
2856 * @param course_enrolment_manager $manager
2859 public function has_bulk_operations(course_enrolment_manager
$manager) {
2864 * Return an array of enrol_bulk_enrolment_operation objects that define
2865 * the bulk actions that can be performed on user enrolments by the plugin.
2867 * @param course_enrolment_manager $manager
2870 public function get_bulk_operations(course_enrolment_manager
$manager) {
2875 * Do any enrolments need expiration processing.
2877 * Plugins that want to call this functionality must implement 'expiredaction' config setting.
2879 * @param progress_trace $trace
2880 * @param int $courseid one course, empty mean all
2881 * @return bool true if any data processed, false if not
2883 public function process_expirations(progress_trace
$trace, $courseid = null) {
2886 $name = $this->get_name();
2887 if (!enrol_is_enabled($name)) {
2896 $coursesql = "AND e.courseid = :courseid";
2899 // Deal with expired accounts.
2900 $action = $this->get_config('expiredaction', ENROL_EXT_REMOVED_KEEP
);
2902 if ($action == ENROL_EXT_REMOVED_UNENROL
) {
2903 $instances = array();
2904 $sql = "SELECT ue.*, e.courseid, c.id AS contextid
2905 FROM {user_enrolments} ue
2906 JOIN {enrol} e ON (e.id = ue.enrolid AND e.enrol = :enrol)
2907 JOIN {context} c ON (c.instanceid = e.courseid AND c.contextlevel = :courselevel)
2908 WHERE ue.timeend > 0 AND ue.timeend < :now $coursesql";
2909 $params = array('now'=>time(), 'courselevel'=>CONTEXT_COURSE
, 'enrol'=>$name, 'courseid'=>$courseid);
2911 $rs = $DB->get_recordset_sql($sql, $params);
2912 foreach ($rs as $ue) {
2914 $trace->output("Starting processing of enrol_$name expirations...");
2917 if (empty($instances[$ue->enrolid
])) {
2918 $instances[$ue->enrolid
] = $DB->get_record('enrol', array('id'=>$ue->enrolid
));
2920 $instance = $instances[$ue->enrolid
];
2921 if (!$this->roles_protected()) {
2922 // Let's just guess what extra roles are supposed to be removed.
2923 if ($instance->roleid
) {
2924 role_unassign($instance->roleid
, $ue->userid
, $ue->contextid
);
2927 // The unenrol cleans up all subcontexts if this is the only course enrolment for this user.
2928 $this->unenrol_user($instance, $ue->userid
);
2929 $trace->output("Unenrolling expired user $ue->userid from course $instance->courseid", 1);
2934 } else if ($action == ENROL_EXT_REMOVED_SUSPENDNOROLES
or $action == ENROL_EXT_REMOVED_SUSPEND
) {
2935 $instances = array();
2936 $sql = "SELECT ue.*, e.courseid, c.id AS contextid
2937 FROM {user_enrolments} ue
2938 JOIN {enrol} e ON (e.id = ue.enrolid AND e.enrol = :enrol)
2939 JOIN {context} c ON (c.instanceid = e.courseid AND c.contextlevel = :courselevel)
2940 WHERE ue.timeend > 0 AND ue.timeend < :now
2941 AND ue.status = :useractive $coursesql";
2942 $params = array('now'=>time(), 'courselevel'=>CONTEXT_COURSE
, 'useractive'=>ENROL_USER_ACTIVE
, 'enrol'=>$name, 'courseid'=>$courseid);
2943 $rs = $DB->get_recordset_sql($sql, $params);
2944 foreach ($rs as $ue) {
2946 $trace->output("Starting processing of enrol_$name expirations...");
2949 if (empty($instances[$ue->enrolid
])) {
2950 $instances[$ue->enrolid
] = $DB->get_record('enrol', array('id'=>$ue->enrolid
));
2952 $instance = $instances[$ue->enrolid
];
2954 if ($action == ENROL_EXT_REMOVED_SUSPENDNOROLES
) {
2955 if (!$this->roles_protected()) {
2956 // Let's just guess what roles should be removed.
2957 $count = $DB->count_records('role_assignments', array('userid'=>$ue->userid
, 'contextid'=>$ue->contextid
));
2959 role_unassign_all(array('userid'=>$ue->userid
, 'contextid'=>$ue->contextid
, 'component'=>'', 'itemid'=>0));
2961 } else if ($count > 1 and $instance->roleid
) {
2962 role_unassign($instance->roleid
, $ue->userid
, $ue->contextid
, '', 0);
2965 // In any case remove all roles that belong to this instance and user.
2966 role_unassign_all(array('userid'=>$ue->userid
, 'contextid'=>$ue->contextid
, 'component'=>'enrol_'.$name, 'itemid'=>$instance->id
), true);
2967 // Final cleanup of subcontexts if there are no more course roles.
2968 if (0 == $DB->count_records('role_assignments', array('userid'=>$ue->userid
, 'contextid'=>$ue->contextid
))) {
2969 role_unassign_all(array('userid'=>$ue->userid
, 'contextid'=>$ue->contextid
, 'component'=>'', 'itemid'=>0), true);
2973 $this->update_user_enrol($instance, $ue->userid
, ENROL_USER_SUSPENDED
);
2974 $trace->output("Suspending expired user $ue->userid in course $instance->courseid", 1);
2980 // ENROL_EXT_REMOVED_KEEP means no changes.
2984 $trace->output("...finished processing of enrol_$name expirations");
2986 $trace->output("No expired enrol_$name enrolments detected");
2994 * Send expiry notifications.
2996 * Plugin that wants to have expiry notification MUST implement following:
2997 * - expirynotifyhour plugin setting,
2998 * - configuration options in instance edit form (expirynotify, notifyall and expirythreshold),
2999 * - notification strings (expirymessageenrollersubject, expirymessageenrollerbody,
3000 * expirymessageenrolledsubject and expirymessageenrolledbody),
3001 * - expiry_notification provider in db/messages.php,
3002 * - upgrade code that sets default thresholds for existing courses (should be 1 day),
3003 * - something that calls this method, such as cron.
3005 * @param progress_trace $trace (accepts bool for backwards compatibility only)
3007 public function send_expiry_notifications($trace) {
3010 $name = $this->get_name();
3011 if (!enrol_is_enabled($name)) {
3016 // Unfortunately this may take a long time, it should not be interrupted,
3017 // otherwise users get duplicate notification.
3019 core_php_time_limit
::raise();
3020 raise_memory_limit(MEMORY_HUGE
);
3023 $expirynotifylast = $this->get_config('expirynotifylast', 0);
3024 $expirynotifyhour = $this->get_config('expirynotifyhour');
3025 if (is_null($expirynotifyhour)) {
3026 debugging("send_expiry_notifications() in $name enrolment plugin needs expirynotifyhour setting");
3031 if (!($trace instanceof progress_trace
)) {
3032 $trace = $trace ?
new text_progress_trace() : new null_progress_trace();
3033 debugging('enrol_plugin::send_expiry_notifications() now expects progress_trace instance as parameter!', DEBUG_DEVELOPER
);
3037 $notifytime = usergetmidnight($timenow, $CFG->timezone
) +
($expirynotifyhour * 3600);
3039 if ($expirynotifylast > $notifytime) {
3040 $trace->output($name.' enrolment expiry notifications were already sent today at '.userdate($expirynotifylast, '', $CFG->timezone
).'.');
3044 } else if ($timenow < $notifytime) {
3045 $trace->output($name.' enrolment expiry notifications will be sent at '.userdate($notifytime, '', $CFG->timezone
).'.');
3050 $trace->output('Processing '.$name.' enrolment expiration notifications...');
3052 // Notify users responsible for enrolment once every day.
3053 $sql = "SELECT ue.*, e.expirynotify, e.notifyall, e.expirythreshold, e.courseid, c.fullname
3054 FROM {user_enrolments} ue
3055 JOIN {enrol} e ON (e.id = ue.enrolid AND e.enrol = :name AND e.expirynotify > 0 AND e.status = :enabled)
3056 JOIN {course} c ON (c.id = e.courseid)
3057 JOIN {user} u ON (u.id = ue.userid AND u.deleted = 0 AND u.suspended = 0)
3058 WHERE ue.status = :active AND ue.timeend > 0 AND ue.timeend > :now1 AND ue.timeend < (e.expirythreshold + :now2)
3059 ORDER BY ue.enrolid ASC, u.lastname ASC, u.firstname ASC, u.id ASC";
3060 $params = array('enabled'=>ENROL_INSTANCE_ENABLED
, 'active'=>ENROL_USER_ACTIVE
, 'now1'=>$timenow, 'now2'=>$timenow, 'name'=>$name);
3062 $rs = $DB->get_recordset_sql($sql, $params);
3067 foreach($rs as $ue) {
3068 if ($lastenrollid and $lastenrollid != $ue->enrolid
) {
3069 $this->notify_expiry_enroller($lastenrollid, $users, $trace);
3072 $lastenrollid = $ue->enrolid
;
3074 $enroller = $this->get_enroller($ue->enrolid
);
3075 $context = context_course
::instance($ue->courseid
);
3077 $user = $DB->get_record('user', array('id'=>$ue->userid
));
3079 $users[] = array('fullname'=>fullname($user, has_capability('moodle/site:viewfullnames', $context, $enroller)), 'timeend'=>$ue->timeend
);
3081 if (!$ue->notifyall
) {
3085 if ($ue->timeend
- $ue->expirythreshold +
86400 < $timenow) {
3086 // Notify enrolled users only once at the start of the threshold.
3087 $trace->output("user $ue->userid was already notified that enrolment in course $ue->courseid expires on ".userdate($ue->timeend
, '', $CFG->timezone
), 1);
3091 $this->notify_expiry_enrolled($user, $ue, $trace);
3095 if ($lastenrollid and $users) {
3096 $this->notify_expiry_enroller($lastenrollid, $users, $trace);
3099 $trace->output('...notification processing finished.');
3102 $this->set_config('expirynotifylast', $timenow);
3106 * Returns the user who is responsible for enrolments for given instance.
3108 * Override if plugin knows anybody better than admin.
3110 * @param int $instanceid enrolment instance id
3111 * @return stdClass user record
3113 protected function get_enroller($instanceid) {
3118 * Notify user about incoming expiration of their enrolment,
3119 * it is called only if notification of enrolled users (aka students) is enabled in course.
3121 * This is executed only once for each expiring enrolment right
3122 * at the start of the expiration threshold.
3124 * @param stdClass $user
3125 * @param stdClass $ue
3126 * @param progress_trace $trace
3128 protected function notify_expiry_enrolled($user, $ue, progress_trace
$trace) {
3131 $name = $this->get_name();
3133 $oldforcelang = force_current_language($user->lang
);
3135 $enroller = $this->get_enroller($ue->enrolid
);
3136 $context = context_course
::instance($ue->courseid
);
3138 $a = new stdClass();
3139 $a->course
= format_string($ue->fullname
, true, array('context'=>$context));
3140 $a->user
= fullname($user, true);
3141 $a->timeend
= userdate($ue->timeend
, '', $user->timezone
);
3142 $a->enroller
= fullname($enroller, has_capability('moodle/site:viewfullnames', $context, $user));
3144 $subject = get_string('expirymessageenrolledsubject', 'enrol_'.$name, $a);
3145 $body = get_string('expirymessageenrolledbody', 'enrol_'.$name, $a);
3147 $message = new \core\message\
message();
3148 $message->courseid
= $ue->courseid
;
3149 $message->notification
= 1;
3150 $message->component
= 'enrol_'.$name;
3151 $message->name
= 'expiry_notification';
3152 $message->userfrom
= $enroller;
3153 $message->userto
= $user;
3154 $message->subject
= $subject;
3155 $message->fullmessage
= $body;
3156 $message->fullmessageformat
= FORMAT_MARKDOWN
;
3157 $message->fullmessagehtml
= markdown_to_html($body);
3158 $message->smallmessage
= $subject;
3159 $message->contexturlname
= $a->course
;
3160 $message->contexturl
= (string)new moodle_url('/course/view.php', array('id'=>$ue->courseid
));
3162 if (message_send($message)) {
3163 $trace->output("notifying user $ue->userid that enrolment in course $ue->courseid expires on ".userdate($ue->timeend
, '', $CFG->timezone
), 1);
3165 $trace->output("error notifying user $ue->userid that enrolment in course $ue->courseid expires on ".userdate($ue->timeend
, '', $CFG->timezone
), 1);
3168 force_current_language($oldforcelang);
3172 * Notify person responsible for enrolments that some user enrolments will be expired soon,
3173 * it is called only if notification of enrollers (aka teachers) is enabled in course.
3175 * This is called repeatedly every day for each course if there are any pending expiration
3176 * in the expiration threshold.
3179 * @param array $users
3180 * @param progress_trace $trace
3182 protected function notify_expiry_enroller($eid, $users, progress_trace
$trace) {
3185 $name = $this->get_name();
3187 $instance = $DB->get_record('enrol', array('id'=>$eid, 'enrol'=>$name));
3188 $context = context_course
::instance($instance->courseid
);
3189 $course = $DB->get_record('course', array('id'=>$instance->courseid
));
3191 $enroller = $this->get_enroller($instance->id
);
3192 $admin = get_admin();
3194 $oldforcelang = force_current_language($enroller->lang
);
3196 foreach($users as $key=>$info) {
3197 $users[$key] = '* '.$info['fullname'].' - '.userdate($info['timeend'], '', $enroller->timezone
);
3200 $a = new stdClass();
3201 $a->course
= format_string($course->fullname
, true, array('context'=>$context));
3202 $a->threshold
= get_string('numdays', '', $instance->expirythreshold
/ (60*60*24));
3203 $a->users
= implode("\n", $users);
3204 $a->extendurl
= (string)new moodle_url('/user/index.php', array('id'=>$instance->courseid
));
3206 $subject = get_string('expirymessageenrollersubject', 'enrol_'.$name, $a);
3207 $body = get_string('expirymessageenrollerbody', 'enrol_'.$name, $a);
3209 $message = new \core\message\
message();
3210 $message->courseid
= $course->id
;
3211 $message->notification
= 1;
3212 $message->component
= 'enrol_'.$name;
3213 $message->name
= 'expiry_notification';
3214 $message->userfrom
= $admin;
3215 $message->userto
= $enroller;
3216 $message->subject
= $subject;
3217 $message->fullmessage
= $body;
3218 $message->fullmessageformat
= FORMAT_MARKDOWN
;
3219 $message->fullmessagehtml
= markdown_to_html($body);
3220 $message->smallmessage
= $subject;
3221 $message->contexturlname
= $a->course
;
3222 $message->contexturl
= $a->extendurl
;
3224 if (message_send($message)) {
3225 $trace->output("notifying user $enroller->id about all expiring $name enrolments in course $instance->courseid", 1);
3227 $trace->output("error notifying user $enroller->id about all expiring $name enrolments in course $instance->courseid", 1);
3230 force_current_language($oldforcelang);
3234 * Backup execution step hook to annotate custom fields.
3236 * @param backup_enrolments_execution_step $step
3237 * @param stdClass $enrol
3239 public function backup_annotate_custom_fields(backup_enrolments_execution_step
$step, stdClass
$enrol) {
3240 // Override as necessary to annotate custom fields in the enrol table.
3244 * Automatic enrol sync executed during restore.
3245 * Useful for automatic sync by course->idnumber or course category.
3246 * @param stdClass $course course record
3248 public function restore_sync_course($course) {
3249 // Override if necessary.
3253 * Restore instance and map settings.
3255 * @param restore_enrolments_structure_step $step
3256 * @param stdClass $data
3257 * @param stdClass $course
3260 public function restore_instance(restore_enrolments_structure_step
$step, stdClass
$data, $course, $oldid) {
3261 // Do not call this from overridden methods, restore and set new id there.
3262 $step->set_mapping('enrol', $oldid, 0);
3266 * Restore user enrolment.
3268 * @param restore_enrolments_structure_step $step
3269 * @param stdClass $data
3270 * @param stdClass $instance
3271 * @param int $oldinstancestatus
3272 * @param int $userid
3274 public function restore_user_enrolment(restore_enrolments_structure_step
$step, $data, $instance, $userid, $oldinstancestatus) {
3275 // Override as necessary if plugin supports restore of enrolments.
3279 * Restore role assignment.
3281 * @param stdClass $instance
3282 * @param int $roleid
3283 * @param int $userid
3284 * @param int $contextid
3286 public function restore_role_assignment($instance, $roleid, $userid, $contextid) {
3287 // No role assignment by default, override if necessary.
3291 * Restore user group membership.
3292 * @param stdClass $instance
3293 * @param int $groupid
3294 * @param int $userid
3296 public function restore_group_member($instance, $groupid, $userid) {
3297 // Implement if you want to restore protected group memberships,
3298 // usually this is not necessary because plugins should be able to recreate the memberships automatically.
3302 * Returns defaults for new instances.
3306 public function get_instance_defaults() {
3311 * Validate a list of parameter names and types.
3314 * @param array $data array of ("fieldname"=>value) of submitted data
3315 * @param array $rules array of ("fieldname"=>PARAM_X types - or "fieldname"=>array( list of valid options )
3316 * @return array of "element_name"=>"error_description" if there are errors,
3317 * or an empty array if everything is OK.
3319 public function validate_param_types($data, $rules) {
3321 $invalidstr = get_string('invaliddata', 'error');
3322 foreach ($rules as $fieldname => $rule) {
3323 if (is_array($rule)) {
3324 if (!in_array($data[$fieldname], $rule)) {
3325 $errors[$fieldname] = $invalidstr;
3328 if ($data[$fieldname] != clean_param($data[$fieldname], $rule)) {
3329 $errors[$fieldname] = $invalidstr;