MDL-61804 admin: Add setting for course visibility sorting
[moodle.git] / lib / enrollib.php
CommitLineData
df997f84
PS
1<?php
2
3// This file is part of Moodle - http://moodle.org/
4//
5// Moodle is free software: you can redistribute it and/or modify
6// it under the terms of the GNU General Public License as published by
7// the Free Software Foundation, either version 3 of the License, or
8// (at your option) any later version.
9//
10// Moodle is distributed in the hope that it will be useful,
11// but WITHOUT ANY WARRANTY; without even the implied warranty of
12// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13// GNU General Public License for more details.
14//
15// You should have received a copy of the GNU General Public License
16// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
17
18/**
19 * This library includes the basic parts of enrol api.
20 * It is available on each page.
21 *
78bfb562
PS
22 * @package core
23 * @subpackage enrol
24 * @copyright 2010 Petr Skoda {@link http://skodak.org}
25 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
df997f84
PS
26 */
27
78bfb562 28defined('MOODLE_INTERNAL') || die();
df997f84
PS
29
30/** Course enrol instance enabled. (used in enrol->status) */
31define('ENROL_INSTANCE_ENABLED', 0);
32
33/** Course enrol instance disabled, user may enter course if other enrol instance enabled. (used in enrol->status)*/
34define('ENROL_INSTANCE_DISABLED', 1);
35
36/** User is active participant (used in user_enrolments->status)*/
37define('ENROL_USER_ACTIVE', 0);
38
39/** User participation in course is suspended (used in user_enrolments->status) */
40define('ENROL_USER_SUSPENDED', 1);
41
bbfdff34 42/** @deprecated - enrol caching was reworked, use ENROL_MAX_TIMESTAMP instead */
df997f84
PS
43define('ENROL_REQUIRE_LOGIN_CACHE_PERIOD', 1800);
44
bbfdff34
PS
45/** The timestamp indicating forever */
46define('ENROL_MAX_TIMESTAMP', 2147483647);
47
34121765
PS
48/** When user disappears from external source, the enrolment is completely removed */
49define('ENROL_EXT_REMOVED_UNENROL', 0);
50
51/** When user disappears from external source, the enrolment is kept as is - one way sync */
52define('ENROL_EXT_REMOVED_KEEP', 1);
53
7a7b8a1f 54/** @deprecated since 2.4 not used any more, migrate plugin to new restore methods */
f2a9be5f 55define('ENROL_RESTORE_TYPE', 'enrolrestore');
f2a9be5f 56
34121765
PS
57/**
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,
60 * assignments, etc.
61 */
62define('ENROL_EXT_REMOVED_SUSPEND', 2);
63
64/**
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.
67 * */
68define('ENROL_EXT_REMOVED_SUSPENDNOROLES', 3);
69
0ab8b337
SL
70/**
71 * Do not send email.
72 */
73define('ENROL_DO_NOT_SEND_EMAIL', 0);
74
75/**
76 * Send email from course contact.
77 */
78define('ENROL_SEND_EMAIL_FROM_COURSE_CONTACT', 1);
79
80/**
81 * Send email from enrolment key holder.
82 */
83define('ENROL_SEND_EMAIL_FROM_KEY_HOLDER', 2);
84
85/**
86 * Send email from no reply address.
87 */
88define('ENROL_SEND_EMAIL_FROM_NOREPLY', 3);
89
fd0a43be
JP
90/** Edit enrolment action. */
91define('ENROL_ACTION_EDIT', 'editenrolment');
92
93/** Unenrol action. */
94define('ENROL_ACTION_UNENROL', 'unenrol');
95
df997f84
PS
96/**
97 * Returns instances of enrol plugins
bbfdff34 98 * @param bool $enabled return enabled only
df997f84
PS
99 * @return array of enrol plugins name=>instance
100 */
101function enrol_get_plugins($enabled) {
102 global $CFG;
103
104 $result = array();
105
106 if ($enabled) {
107 // sorted by enabled plugin order
108 $enabled = explode(',', $CFG->enrol_plugins_enabled);
109 $plugins = array();
110 foreach ($enabled as $plugin) {
111 $plugins[$plugin] = "$CFG->dirroot/enrol/$plugin";
112 }
113 } else {
114 // sorted alphabetically
bd3b3bba 115 $plugins = core_component::get_plugin_list('enrol');
df997f84
PS
116 ksort($plugins);
117 }
118
119 foreach ($plugins as $plugin=>$location) {
df997f84
PS
120 $class = "enrol_{$plugin}_plugin";
121 if (!class_exists($class)) {
d432e0f7
MA
122 if (!file_exists("$location/lib.php")) {
123 continue;
124 }
125 include_once("$location/lib.php");
126 if (!class_exists($class)) {
127 continue;
128 }
df997f84
PS
129 }
130
131 $result[$plugin] = new $class();
132 }
133
134 return $result;
135}
136
137/**
138 * Returns instance of enrol plugin
139 * @param string $name name of enrol plugin ('manual', 'guest', ...)
140 * @return enrol_plugin
141 */
142function enrol_get_plugin($name) {
143 global $CFG;
144
aff24313
PS
145 $name = clean_param($name, PARAM_PLUGIN);
146
147 if (empty($name)) {
148 // ignore malformed or missing plugin names completely
df997f84
PS
149 return null;
150 }
151
152 $location = "$CFG->dirroot/enrol/$name";
153
df997f84
PS
154 $class = "enrol_{$name}_plugin";
155 if (!class_exists($class)) {
ef97f1a2
MA
156 if (!file_exists("$location/lib.php")) {
157 return null;
158 }
159 include_once("$location/lib.php");
160 if (!class_exists($class)) {
161 return null;
162 }
df997f84
PS
163 }
164
165 return new $class();
166}
167
168/**
169 * Returns enrolment instances in given course.
170 * @param int $courseid
171 * @param bool $enabled
172 * @return array of enrol instances
173 */
174function enrol_get_instances($courseid, $enabled) {
175 global $DB, $CFG;
176
177 if (!$enabled) {
178 return $DB->get_records('enrol', array('courseid'=>$courseid), 'sortorder,id');
179 }
180
181 $result = $DB->get_records('enrol', array('courseid'=>$courseid, 'status'=>ENROL_INSTANCE_ENABLED), 'sortorder,id');
182
79721bd3 183 $enabled = explode(',', $CFG->enrol_plugins_enabled);
df997f84
PS
184 foreach ($result as $key=>$instance) {
185 if (!in_array($instance->enrol, $enabled)) {
186 unset($result[$key]);
187 continue;
188 }
189 if (!file_exists("$CFG->dirroot/enrol/$instance->enrol/lib.php")) {
190 // broken plugin
191 unset($result[$key]);
192 continue;
193 }
194 }
195
196 return $result;
197}
198
199/**
200 * Checks if a given plugin is in the list of enabled enrolment plugins.
201 *
202 * @param string $enrol Enrolment plugin name
203 * @return boolean Whether the plugin is enabled
204 */
205function enrol_is_enabled($enrol) {
206 global $CFG;
207
208 if (empty($CFG->enrol_plugins_enabled)) {
209 return false;
210 }
211 return in_array($enrol, explode(',', $CFG->enrol_plugins_enabled));
212}
213
214/**
215 * Check all the login enrolment information for the given user object
216 * by querying the enrolment plugins
217 *
e922fe23
PS
218 * This function may be very slow, use only once after log-in or login-as.
219 *
220 * @param stdClass $user
df997f84
PS
221 * @return void
222 */
223function enrol_check_plugins($user) {
224 global $CFG;
225
226 if (empty($user->id) or isguestuser($user)) {
227 // shortcut - there is no enrolment work for guests and not-logged-in users
228 return;
229 }
230
bcb368d9
PS
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.
e384d2dc 233
df997f84
PS
234 static $inprogress = array(); // To prevent this function being called more than once in an invocation
235
236 if (!empty($inprogress[$user->id])) {
237 return;
238 }
239
240 $inprogress[$user->id] = true; // Set the flag
241
242 $enabled = enrol_get_plugins(true);
243
244 foreach($enabled as $enrol) {
245 $enrol->sync_user_enrolments($user);
246 }
247
248 unset($inprogress[$user->id]); // Unset the flag
249}
250
181991e7
PS
251/**
252 * Do these two students share any course?
253 *
254 * The courses has to be visible and enrolments has to be active,
255 * timestart and timeend restrictions are ignored.
256 *
61ab8f07
SH
257 * This function calls {@see enrol_get_shared_courses()} setting checkexistsonly
258 * to true.
259 *
181991e7
PS
260 * @param stdClass|int $user1
261 * @param stdClass|int $user2
262 * @return bool
263 */
264function enrol_sharing_course($user1, $user2) {
61ab8f07 265 return enrol_get_shared_courses($user1, $user2, false, true);
181991e7
PS
266}
267
4b715423
SH
268/**
269 * Returns any courses shared by the two users
270 *
271 * The courses has to be visible and enrolments has to be active,
272 * timestart and timeend restrictions are ignored.
273 *
61ab8f07 274 * @global moodle_database $DB
4b715423
SH
275 * @param stdClass|int $user1
276 * @param stdClass|int $user2
61ab8f07
SH
277 * @param bool $preloadcontexts If set to true contexts for the returned courses
278 * will be preloaded.
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
283 * and false if not.
4b715423 284 */
61ab8f07 285function enrol_get_shared_courses($user1, $user2, $preloadcontexts = false, $checkexistsonly = false) {
4b715423
SH
286 global $DB, $CFG;
287
5b244e2b
HD
288 $user1 = isset($user1->id) ? $user1->id : $user1;
289 $user2 = isset($user2->id) ? $user2->id : $user2;
4b715423
SH
290
291 if (empty($user1) or empty($user2)) {
292 return false;
293 }
294
295 if (!$plugins = explode(',', $CFG->enrol_plugins_enabled)) {
296 return false;
297 }
298
499d3775 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;
4b715423
SH
304 $params['active1'] = ENROL_USER_ACTIVE;
305 $params['active2'] = ENROL_USER_ACTIVE;
306 $params['user1'] = $user1;
307 $params['user2'] = $user2;
308
309 $ctxselect = '';
310 $ctxjoin = '';
311 if ($preloadcontexts) {
2e4c0c91
FM
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;
4b715423
SH
315 }
316
317 $sql = "SELECT c.* $ctxselect
318 FROM {course} c
319 JOIN (
320 SELECT DISTINCT c.id
499d3775 321 FROM {course} c
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)
326 WHERE c.visible = 1
4b715423
SH
327 ) ec ON ec.id = c.id
328 $ctxjoin";
4b715423 329
61ab8f07
SH
330 if ($checkexistsonly) {
331 return $DB->record_exists_sql($sql, $params);
332 } else {
333 $courses = $DB->get_records_sql($sql, $params);
334 if ($preloadcontexts) {
a55ad8f8 335 array_map('context_helper::preload_from_record', $courses);
61ab8f07
SH
336 }
337 return $courses;
338 }
4b715423
SH
339}
340
df997f84
PS
341/**
342 * This function adds necessary enrol plugins UI into the course edit form.
343 *
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
347 * @return void
348 */
349function 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])) {
355 continue;
356 }
357 $plugin = $plugins[$instance->enrol];
358 $plugin->course_edit_form($instance, $mform, $data, $context);
359 }
360 } else {
361 foreach ($plugins as $plugin) {
362 $plugin->course_edit_form(NULL, $mform, $data, $context);
363 }
364 }
365}
366
367/**
368 * Validate course edit form data
369 *
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
373 */
374function enrol_course_edit_validation(array $data, $context) {
375 $errors = array();
376 $plugins = enrol_get_plugins(true);
377
378 if (!empty($data['id'])) {
379 $instances = enrol_get_instances($data['id'], false);
380 foreach ($instances as $instance) {
381 if (!isset($plugins[$instance->enrol])) {
382 continue;
383 }
384 $plugin = $plugins[$instance->enrol];
385 $errors = array_merge($errors, $plugin->course_edit_validation($instance, $data, $context));
386 }
387 } else {
388 foreach ($plugins as $plugin) {
389 $errors = array_merge($errors, $plugin->course_edit_validation(NULL, $data, $context));
390 }
391 }
392
393 return $errors;
394}
395
396/**
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
401 * @return void
402 */
403function enrol_course_updated($inserted, $course, $data) {
404 global $DB, $CFG;
405
406 $plugins = enrol_get_plugins(true);
407
408 foreach ($plugins as $plugin) {
409 $plugin->course_updated($inserted, $course, $data);
410 }
411}
412
413/**
414 * Add navigation nodes
415 * @param navigation_node $coursenode
416 * @param object $course
417 * @return void
418 */
419function enrol_add_course_navigation(navigation_node $coursenode, $course) {
6e4c374d 420 global $CFG;
df997f84 421
b0c6dc1c 422 $coursecontext = context_course::instance($course->id);
df997f84
PS
423
424 $instances = enrol_get_instances($course->id, true);
425 $plugins = enrol_get_plugins(true);
426
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]);
431 }
432 }
433
f5ce6b71 434 $usersnode = $coursenode->add(get_string('users'), null, navigation_node::TYPE_CONTAINER, null, 'users');
df997f84
PS
435
436 if ($course->id != SITEID) {
f5ce6b71 437 // list all participants - allows assigning roles, groups, etc.
df997f84 438 if (has_capability('moodle/course:enrolreview', $coursecontext)) {
4b50765b 439 $url = new moodle_url('/user/index.php', array('id'=>$course->id));
c42651d6 440 $usersnode->add(get_string('enrolledusers', 'enrol'), $url, navigation_node::TYPE_SETTING, null, 'review', new pix_icon('i/enrolusers', ''));
df997f84
PS
441 }
442
443 // manage enrol plugin instances
444 if (has_capability('moodle/course:enrolconfig', $coursecontext) or has_capability('moodle/course:enrolreview', $coursecontext)) {
445 $url = new moodle_url('/enrol/instances.php', array('id'=>$course->id));
446 } else {
447 $url = NULL;
448 }
f5ce6b71 449 $instancesnode = $usersnode->add(get_string('enrolmentinstances', 'enrol'), $url, navigation_node::TYPE_SETTING, null, 'manageinstances');
df997f84
PS
450
451 // each instance decides how to configure itself or how many other nav items are exposed
452 foreach ($instances as $instance) {
453 if (!isset($plugins[$instance->enrol])) {
454 continue;
455 }
456 $plugins[$instance->enrol]->add_course_navigation($instancesnode, $instance);
457 }
458
459 if (!$url) {
460 $instancesnode->trim_if_empty();
461 }
462 }
463
464 // Manage groups in this course or even frontpage
465 if (($course->groupmode || !$course->groupmodeforce) && has_capability('moodle/course:managegroups', $coursecontext)) {
466 $url = new moodle_url('/group/index.php', array('id'=>$course->id));
467 $usersnode->add(get_string('groups'), $url, navigation_node::TYPE_SETTING, null, 'groups', new pix_icon('i/group', ''));
468 }
469
470 if (has_any_capability(array( 'moodle/role:assign', 'moodle/role:safeoverride','moodle/role:override', 'moodle/role:review'), $coursecontext)) {
471 // Override roles
472 if (has_capability('moodle/role:review', $coursecontext)) {
473 $url = new moodle_url('/admin/roles/permissions.php', array('contextid'=>$coursecontext->id));
474 } else {
475 $url = NULL;
476 }
f5ce6b71 477 $permissionsnode = $usersnode->add(get_string('permissions', 'role'), $url, navigation_node::TYPE_SETTING, null, 'override');
df997f84
PS
478
479 // Add assign or override roles if allowed
480 if ($course->id == SITEID or (!empty($CFG->adminsassignrolesincourse) and is_siteadmin())) {
481 if (has_capability('moodle/role:assign', $coursecontext)) {
482 $url = new moodle_url('/admin/roles/assign.php', array('contextid'=>$coursecontext->id));
fbb207c5 483 $permissionsnode->add(get_string('assignedroles', 'role'), $url, navigation_node::TYPE_SETTING, null, 'roles', new pix_icon('i/assignroles', ''));
df997f84
PS
484 }
485 }
486 // Check role permissions
77690b69 487 if (has_any_capability(array('moodle/role:assign', 'moodle/role:safeoverride', 'moodle/role:override'), $coursecontext)) {
df997f84 488 $url = new moodle_url('/admin/roles/check.php', array('contextid'=>$coursecontext->id));
f5ce6b71 489 $permissionsnode->add(get_string('checkpermissions', 'role'), $url, navigation_node::TYPE_SETTING, null, 'permissions', new pix_icon('i/checkpermissions', ''));
df997f84
PS
490 }
491 }
492
493 // Deal somehow with users that are not enrolled but still got a role somehow
494 if ($course->id != SITEID) {
495 //TODO, create some new UI for role assignments at course level
288e7b09 496 if (has_capability('moodle/course:reviewotherusers', $coursecontext)) {
df997f84 497 $url = new moodle_url('/enrol/otherusers.php', array('id'=>$course->id));
fbb207c5 498 $usersnode->add(get_string('notenrolledusers', 'enrol'), $url, navigation_node::TYPE_SETTING, null, 'otherusers', new pix_icon('i/assignroles', ''));
df997f84
PS
499 }
500 }
501
502 // just in case nothing was actually added
503 $usersnode->trim_if_empty();
504
505 if ($course->id != SITEID) {
6813cdf7 506 if (isguestuser() or !isloggedin()) {
39ec18db 507 // guest account can not be enrolled - no links for them
6813cdf7 508 } else if (is_enrolled($coursecontext)) {
39ec18db 509 // unenrol link if possible
217d0397
PS
510 foreach ($instances as $instance) {
511 if (!isset($plugins[$instance->enrol])) {
512 continue;
513 }
514 $plugin = $plugins[$instance->enrol];
515 if ($unenrollink = $plugin->get_unenrolself_link($instance)) {
8ebbb06a
SH
516 $shortname = format_string($course->shortname, true, array('context' => $coursecontext));
517 $coursenode->add(get_string('unenrolme', 'core_enrol', $shortname), $unenrollink, navigation_node::TYPE_SETTING, null, 'unenrolself', new pix_icon('i/user', ''));
217d0397
PS
518 break;
519 //TODO. deal with multiple unenrol links - not likely case, but still...
520 }
df997f84 521 }
217d0397 522 } else {
39ec18db 523 // enrol link if possible
217d0397
PS
524 if (is_viewing($coursecontext)) {
525 // better not show any enrol link, this is intended for managers and inspectors
526 } else {
527 foreach ($instances as $instance) {
528 if (!isset($plugins[$instance->enrol])) {
529 continue;
530 }
531 $plugin = $plugins[$instance->enrol];
532 if ($plugin->show_enrolme_link($instance)) {
533 $url = new moodle_url('/enrol/index.php', array('id'=>$course->id));
8ebbb06a
SH
534 $shortname = format_string($course->shortname, true, array('context' => $coursecontext));
535 $coursenode->add(get_string('enrolme', 'core_enrol', $shortname), $url, navigation_node::TYPE_SETTING, null, 'enrolself', new pix_icon('i/user', ''));
217d0397
PS
536 break;
537 }
538 }
df997f84
PS
539 }
540 }
df997f84
PS
541 }
542}
543
544/**
545 * Returns list of courses current $USER is enrolled in and can access
546 *
a3d00360
DM
547 * The $fields param is a list of field names to ADD so name just the fields you really need,
548 * which will be added and uniq'd.
df997f84 549 *
22ef1550 550 * If $allaccessible is true, this will additionally return courses that the current user is not
551 * enrolled in, but can access because they are open to the user for other reasons (course view
552 * permission, currently viewing course as a guest, or course allows guest access without
553 * password).
554 *
a3d00360
DM
555 * @param string|array $fields Extra fields to be returned (array or comma-separated list).
556 * @param string|null $sort Comma separated list of fields to sort by, defaults to respecting navsortmycoursessort.
6481a21f
BB
557 * Allowed prefixes for sort fields are: "ul" for the user_lastaccess table, "c" for the courses table,
558 * "ue" for the user_enrolments table.
df997f84 559 * @param int $limit max number of courses
1ef06b43 560 * @param array $courseids the list of course ids to filter by
22ef1550 561 * @param bool $allaccessible Include courses user is not enrolled in, but can access
2c1d19fd 562 * @param int $offset Offset the result set by this number
7dcb274c 563 * @param array $excludecourses IDs of hidden courses to exclude from search
df997f84
PS
564 * @return array
565 */
7dcb274c
P
566function enrol_get_my_courses($fields = null, $sort = null, $limit = 0, $courseids = [], $allaccessible = false,
567 $offset = 0, $excludecourses = []) {
a3d00360 568 global $DB, $USER, $CFG;
ee218727 569
8471a0db
AB
570 // Re-Arrange the course sorting according to the admin settings.
571 $sort = enrol_get_courses_sortingsql($sort);
df997f84 572
22ef1550 573 // Guest account does not have any enrolled courses.
574 if (!$allaccessible && (isguestuser() or !isloggedin())) {
575 return array();
df997f84
PS
576 }
577
578 $basefields = array('id', 'category', 'sortorder',
579 'shortname', 'fullname', 'idnumber',
580 'startdate', 'visible',
4a3fb71c 581 'groupmode', 'groupmodeforce', 'cacherev');
df997f84
PS
582
583 if (empty($fields)) {
584 $fields = $basefields;
585 } else if (is_string($fields)) {
586 // turn the fields from a string to an array
587 $fields = explode(',', $fields);
588 $fields = array_map('trim', $fields);
589 $fields = array_unique(array_merge($basefields, $fields));
590 } else if (is_array($fields)) {
591 $fields = array_unique(array_merge($basefields, $fields));
592 } else {
9cefe2f2 593 throw new coding_exception('Invalid $fields parameter in enrol_get_my_courses()');
df997f84
PS
594 }
595 if (in_array('*', $fields)) {
596 $fields = array('*');
597 }
598
599 $orderby = "";
600 $sort = trim($sort);
6481a21f
BB
601 $sorttimeaccess = false;
602 $allowedsortprefixes = array('c', 'ul', 'ue');
df997f84
PS
603 if (!empty($sort)) {
604 $rawsorts = explode(',', $sort);
605 $sorts = array();
606 foreach ($rawsorts as $rawsort) {
607 $rawsort = trim($rawsort);
6481a21f
BB
608 if (preg_match('/^ul\.(\S*)\s(asc|desc)/i', $rawsort, $matches)) {
609 if (strcasecmp($matches[2], 'asc') == 0) {
610 $sorts[] = 'COALESCE(ul.' . $matches[1] . ', 0) ASC';
611 } else {
612 $sorts[] = 'COALESCE(ul.' . $matches[1] . ', 0) DESC';
613 }
614 $sorttimeaccess = true;
615 } else if (strpos($rawsort, '.') !== false) {
616 $prefix = explode('.', $rawsort);
617 if (in_array($prefix[0], $allowedsortprefixes)) {
618 $sorts[] = trim($rawsort);
619 } else {
620 throw new coding_exception('Invalid $sort parameter in enrol_get_my_courses()');
621 }
622 } else {
623 $sorts[] = 'c.'.trim($rawsort);
df997f84 624 }
df997f84 625 }
6481a21f 626 $sort = implode(',', $sorts);
df997f84
PS
627 $orderby = "ORDER BY $sort";
628 }
629
630 $wheres = array("c.id <> :siteid");
631 $params = array('siteid'=>SITEID);
632
633 if (isset($USER->loginascontext) and $USER->loginascontext->contextlevel == CONTEXT_COURSE) {
634 // list _only_ this course - anything else is asking for trouble...
635 $wheres[] = "courseid = :loginas";
636 $params['loginas'] = $USER->loginascontext->instanceid;
637 }
638
639 $coursefields = 'c.' .join(',c.', $fields);
2e4c0c91
FM
640 $ccselect = ', ' . context_helper::get_preload_record_columns_sql('ctx');
641 $ccjoin = "LEFT JOIN {context} ctx ON (ctx.instanceid = c.id AND ctx.contextlevel = :contextlevel)";
642 $params['contextlevel'] = CONTEXT_COURSE;
4129338c 643 $wheres = implode(" AND ", $wheres);
df997f84 644
6481a21f
BB
645 $timeaccessselect = "";
646 $timeaccessjoin = "";
647
1ef06b43
RW
648 if (!empty($courseids)) {
649 list($courseidssql, $courseidsparams) = $DB->get_in_or_equal($courseids, SQL_PARAMS_NAMED);
650 $wheres = sprintf("%s AND c.id %s", $wheres, $courseidssql);
7dcb274c
P
651 $params = array_merge($params, $courseidsparams);
652 }
653
654 if (!empty($excludecourses)) {
655 list($courseidssql, $courseidsparams) = $DB->get_in_or_equal($excludecourses, SQL_PARAMS_NAMED, 'param', false);
656 $wheres = sprintf("%s AND c.id %s", $wheres, $courseidssql);
1ef06b43
RW
657 $params = array_merge($params, $courseidsparams);
658 }
659
22ef1550 660 $courseidsql = "";
661 // Logged-in, non-guest users get their enrolled courses.
662 if (!isguestuser() && isloggedin()) {
663 $courseidsql .= "
664 SELECT DISTINCT e.courseid
665 FROM {enrol} e
6481a21f 666 JOIN {user_enrolments} ue ON (ue.enrolid = e.id AND ue.userid = :userid1)
22ef1550 667 WHERE ue.status = :active AND e.status = :enabled AND ue.timestart < :now1
668 AND (ue.timeend = 0 OR ue.timeend > :now2)";
6481a21f 669 $params['userid1'] = $USER->id;
22ef1550 670 $params['active'] = ENROL_USER_ACTIVE;
671 $params['enabled'] = ENROL_INSTANCE_ENABLED;
672 $params['now1'] = round(time(), -2); // Improves db caching.
673 $params['now2'] = $params['now1'];
6481a21f
BB
674
675 if ($sorttimeaccess) {
676 $params['userid2'] = $USER->id;
677 $timeaccessselect = ', ul.timeaccess as lastaccessed';
678 $timeaccessjoin = "LEFT JOIN {user_lastaccess} ul ON (ul.courseid = c.id AND ul.userid = :userid2)";
679 }
22ef1550 680 }
681
682 // When including non-enrolled but accessible courses...
683 if ($allaccessible) {
684 if (is_siteadmin()) {
685 // Site admins can access all courses.
686 $courseidsql = "SELECT DISTINCT c2.id AS courseid FROM {course} c2";
687 } else {
688 // If we used the enrolment as well, then this will be UNIONed.
689 if ($courseidsql) {
690 $courseidsql .= " UNION ";
691 }
692
693 // Include courses with guest access and no password.
694 $courseidsql .= "
695 SELECT DISTINCT e.courseid
696 FROM {enrol} e
7ab09932
EL
697 WHERE e.enrol = 'guest' AND e.password = :emptypass AND e.status = :enabled2";
698 $params['emptypass'] = '';
22ef1550 699 $params['enabled2'] = ENROL_INSTANCE_ENABLED;
700
701 // Include courses where the current user is currently using guest access (may include
702 // those which require a password).
703 $courseids = [];
704 $accessdata = get_user_accessdata($USER->id);
705 foreach ($accessdata['ra'] as $contextpath => $roles) {
706 if (array_key_exists($CFG->guestroleid, $roles)) {
707 // Work out the course id from context path.
708 $context = context::instance_by_id(preg_replace('~^.*/~', '', $contextpath));
709 if ($context instanceof context_course) {
710 $courseids[$context->instanceid] = true;
711 }
712 }
713 }
714
715 // Include courses where the current user has moodle/course:view capability.
716 $courses = get_user_capability_course('moodle/course:view', null, false);
717 if (!$courses) {
718 $courses = [];
719 }
720 foreach ($courses as $course) {
721 $courseids[$course->id] = true;
722 }
723
724 // If there are any in either category, list them individually.
725 if ($courseids) {
726 list ($allowedsql, $allowedparams) = $DB->get_in_or_equal(
727 array_keys($courseids), SQL_PARAMS_NAMED);
728 $courseidsql .= "
729 UNION
730 SELECT DISTINCT c3.id AS courseid
731 FROM {course} c3
732 WHERE c3.id $allowedsql";
733 $params = array_merge($params, $allowedparams);
734 }
735 }
736 }
737
738 // Note: we can not use DISTINCT + text fields due to Oracle and MS limitations, that is why
739 // we have the subselect there.
6481a21f 740 $sql = "SELECT $coursefields $ccselect $timeaccessselect
df997f84 741 FROM {course} c
22ef1550 742 JOIN ($courseidsql) en ON (en.courseid = c.id)
6481a21f 743 $timeaccessjoin
df997f84 744 $ccjoin
4129338c 745 WHERE $wheres
df997f84 746 $orderby";
df997f84 747
2c1d19fd 748 $courses = $DB->get_records_sql($sql, $params, $offset, $limit);
df997f84
PS
749
750 // preload contexts and check visibility
751 foreach ($courses as $id=>$course) {
db314f34 752 context_helper::preload_from_record($course);
df997f84 753 if (!$course->visible) {
b0c6dc1c 754 if (!$context = context_course::instance($id, IGNORE_MISSING)) {
55880bdd 755 unset($courses[$id]);
df997f84
PS
756 continue;
757 }
758 if (!has_capability('moodle/course:viewhiddencourses', $context)) {
55880bdd 759 unset($courses[$id]);
df997f84
PS
760 continue;
761 }
762 }
763 $courses[$id] = $course;
764 }
765
766 //wow! Is that really all? :-D
767
768 return $courses;
769}
770
bf423bb1
PS
771/**
772 * Returns course enrolment information icons.
773 *
774 * @param object $course
775 * @param array $instances enrol instances of this course, improves performance
776 * @return array of pix_icon
777 */
778function enrol_get_course_info_icons($course, array $instances = NULL) {
779 $icons = array();
780 if (is_null($instances)) {
781 $instances = enrol_get_instances($course->id, true);
782 }
783 $plugins = enrol_get_plugins(true);
784 foreach ($plugins as $name => $plugin) {
785 $pis = array();
786 foreach ($instances as $instance) {
787 if ($instance->status != ENROL_INSTANCE_ENABLED or $instance->courseid != $course->id) {
788 debugging('Invalid instances parameter submitted in enrol_get_info_icons()');
789 continue;
790 }
791 if ($instance->enrol == $name) {
792 $pis[$instance->id] = $instance;
793 }
794 }
795 if ($pis) {
796 $icons = array_merge($icons, $plugin->get_info_icons($pis));
797 }
798 }
799 return $icons;
800}
801
8471a0db
AB
802/**
803 * Returns SQL ORDER arguments which reflect the admin settings to sort my courses.
804 *
805 * @param string|null $sort SQL ORDER arguments which were originally requested (optionally).
806 * @return string SQL ORDER arguments.
807 */
808function enrol_get_courses_sortingsql($sort = null) {
809 global $CFG;
810
811 // Prepare the visible SQL fragment as empty.
812 $visible = '';
813 // Only create a visible SQL fragment if the caller didn't already pass a sort order which contains the visible field.
814 if ($sort === null || strpos($sort, 'visible') === false) {
815 // If the admin did not explicitly want to have shown and hidden courses sorted as one list, we will sort hidden
816 // courses to the end of the course list.
817 if (!isset($CFG->navsortmycourseshiddenlast) || $CFG->navsortmycourseshiddenlast == true) {
818 $visible = 'visible DESC, ';
819 }
820 }
821
822 // Only create a sortorder SQL fragment if the caller didn't already pass one.
823 if ($sort === null) {
824 // If the admin has configured a course sort order, we will use this.
825 if (!empty($CFG->navsortmycoursessort)) {
826 $sort = $CFG->navsortmycoursessort . ' ASC';
827
828 // Otherwise we will fall back to the sortorder sorting.
829 } else {
830 $sort = 'sortorder ASC';
831 }
832 }
833
834 return $visible . $sort;
835}
836
bf423bb1
PS
837/**
838 * Returns course enrolment detailed information.
839 *
840 * @param object $course
841 * @return array of html fragments - can be used to construct lists
842 */
843function enrol_get_course_description_texts($course) {
844 $lines = array();
845 $instances = enrol_get_instances($course->id, true);
846 $plugins = enrol_get_plugins(true);
847 foreach ($instances as $instance) {
64942a9d 848 if (!isset($plugins[$instance->enrol])) {
bf423bb1
PS
849 //weird
850 continue;
851 }
64942a9d 852 $plugin = $plugins[$instance->enrol];
bf423bb1
PS
853 $text = $plugin->get_description_text($instance);
854 if ($text !== NULL) {
855 $lines[] = $text;
856 }
857 }
858 return $lines;
859}
860
df997f84
PS
861/**
862 * Returns list of courses user is enrolled into.
863 *
a3d00360 864 * Note: Use {@link enrol_get_all_users_courses()} if you need the list without any capability checks.
df997f84 865 *
a3d00360
DM
866 * The $fields param is a list of field names to ADD so name just the fields you really need,
867 * which will be added and uniq'd.
868 *
869 * @param int $userid User whose courses are returned, defaults to the current user.
870 * @param bool $onlyactive Return only active enrolments in courses user may see.
871 * @param string|array $fields Extra fields to be returned (array or comma-separated list).
872 * @param string|null $sort Comma separated list of fields to sort by, defaults to respecting navsortmycoursessort.
df997f84
PS
873 * @return array
874 */
a3d00360 875function enrol_get_users_courses($userid, $onlyactive = false, $fields = null, $sort = null) {
df997f84
PS
876 global $DB;
877
9ffd29ce
AA
878 $courses = enrol_get_all_users_courses($userid, $onlyactive, $fields, $sort);
879
880 // preload contexts and check visibility
881 if ($onlyactive) {
882 foreach ($courses as $id=>$course) {
db314f34 883 context_helper::preload_from_record($course);
9ffd29ce
AA
884 if (!$course->visible) {
885 if (!$context = context_course::instance($id)) {
886 unset($courses[$id]);
887 continue;
888 }
889 if (!has_capability('moodle/course:viewhiddencourses', $context, $userid)) {
890 unset($courses[$id]);
891 continue;
892 }
893 }
894 }
895 }
896
897 return $courses;
b1d1369d
JB
898}
899
900/**
901 * Returns list of roles per users into course.
902 *
903 * @param int $courseid Course id.
904 * @return array Array[$userid][$roleid] = role_assignment.
905 */
906function enrol_get_course_users_roles(int $courseid) : array {
907 global $DB;
908
909 $context = context_course::instance($courseid);
910
911 $roles = array();
912
913 $records = $DB->get_recordset('role_assignments', array('contextid' => $context->id));
914 foreach ($records as $record) {
915 if (isset($roles[$record->userid]) === false) {
916 $roles[$record->userid] = array();
917 }
918 $roles[$record->userid][$record->roleid] = $record;
919 }
920 $records->close();
9ffd29ce 921
b1d1369d 922 return $roles;
9ffd29ce
AA
923}
924
05f6da14
925/**
926 * Can user access at least one enrolled course?
927 *
928 * Cheat if necessary, but find out as fast as possible!
929 *
930 * @param int|stdClass $user null means use current user
931 * @return bool
932 */
933function enrol_user_sees_own_courses($user = null) {
934 global $USER;
935
936 if ($user === null) {
937 $user = $USER;
938 }
939 $userid = is_object($user) ? $user->id : $user;
940
941 // Guest account does not have any courses
942 if (isguestuser($userid) or empty($userid)) {
943 return false;
944 }
945
946 // Let's cheat here if this is the current user,
947 // if user accessed any course recently, then most probably
948 // we do not need to query the database at all.
949 if ($USER->id == $userid) {
950 if (!empty($USER->enrol['enrolled'])) {
951 foreach ($USER->enrol['enrolled'] as $until) {
952 if ($until > time()) {
953 return true;
954 }
955 }
956 }
957 }
958
959 // Now the slow way.
960 $courses = enrol_get_all_users_courses($userid, true);
961 foreach($courses as $course) {
962 if ($course->visible) {
963 return true;
964 }
965 context_helper::preload_from_record($course);
966 $context = context_course::instance($course->id);
967 if (has_capability('moodle/course:viewhiddencourses', $context, $user)) {
968 return true;
969 }
970 }
971
972 return false;
973}
974
9ffd29ce 975/**
a3d00360
DM
976 * Returns list of courses user is enrolled into without performing any capability checks.
977 *
978 * The $fields param is a list of field names to ADD so name just the fields you really need,
979 * which will be added and uniq'd.
9ffd29ce 980 *
a3d00360
DM
981 * @param int $userid User whose courses are returned, defaults to the current user.
982 * @param bool $onlyactive Return only active enrolments in courses user may see.
983 * @param string|array $fields Extra fields to be returned (array or comma-separated list).
984 * @param string|null $sort Comma separated list of fields to sort by, defaults to respecting navsortmycoursessort.
9ffd29ce
AA
985 * @return array
986 */
a3d00360 987function enrol_get_all_users_courses($userid, $onlyactive = false, $fields = null, $sort = null) {
8471a0db 988 global $DB;
a3d00360 989
8471a0db
AB
990 // Re-Arrange the course sorting according to the admin settings.
991 $sort = enrol_get_courses_sortingsql($sort);
9ffd29ce 992
df997f84 993 // Guest account does not have any courses
87163782 994 if (isguestuser($userid) or empty($userid)) {
df997f84
PS
995 return(array());
996 }
997
998 $basefields = array('id', 'category', 'sortorder',
9ffd29ce
AA
999 'shortname', 'fullname', 'idnumber',
1000 'startdate', 'visible',
8eb15a38 1001 'defaultgroupingid',
9ffd29ce 1002 'groupmode', 'groupmodeforce');
df997f84
PS
1003
1004 if (empty($fields)) {
1005 $fields = $basefields;
1006 } else if (is_string($fields)) {
1007 // turn the fields from a string to an array
1008 $fields = explode(',', $fields);
1009 $fields = array_map('trim', $fields);
1010 $fields = array_unique(array_merge($basefields, $fields));
1011 } else if (is_array($fields)) {
1012 $fields = array_unique(array_merge($basefields, $fields));
1013 } else {
9cefe2f2 1014 throw new coding_exception('Invalid $fields parameter in enrol_get_all_users_courses()');
df997f84
PS
1015 }
1016 if (in_array('*', $fields)) {
1017 $fields = array('*');
1018 }
1019
1020 $orderby = "";
1021 $sort = trim($sort);
1022 if (!empty($sort)) {
1023 $rawsorts = explode(',', $sort);
1024 $sorts = array();
1025 foreach ($rawsorts as $rawsort) {
1026 $rawsort = trim($rawsort);
1027 if (strpos($rawsort, 'c.') === 0) {
1028 $rawsort = substr($rawsort, 2);
1029 }
1030 $sorts[] = trim($rawsort);
1031 }
1032 $sort = 'c.'.implode(',c.', $sorts);
1033 $orderby = "ORDER BY $sort";
1034 }
1035
df997f84
PS
1036 $params = array('siteid'=>SITEID);
1037
1038 if ($onlyactive) {
4129338c 1039 $subwhere = "WHERE ue.status = :active AND e.status = :enabled AND ue.timestart < :now1 AND (ue.timeend = 0 OR ue.timeend > :now2)";
df997f84
PS
1040 $params['now1'] = round(time(), -2); // improves db caching
1041 $params['now2'] = $params['now1'];
1042 $params['active'] = ENROL_USER_ACTIVE;
1043 $params['enabled'] = ENROL_INSTANCE_ENABLED;
4129338c
PS
1044 } else {
1045 $subwhere = "";
df997f84
PS
1046 }
1047
1048 $coursefields = 'c.' .join(',c.', $fields);
2e4c0c91
FM
1049 $ccselect = ', ' . context_helper::get_preload_record_columns_sql('ctx');
1050 $ccjoin = "LEFT JOIN {context} ctx ON (ctx.instanceid = c.id AND ctx.contextlevel = :contextlevel)";
1051 $params['contextlevel'] = CONTEXT_COURSE;
df997f84 1052
4129338c
PS
1053 //note: we can not use DISTINCT + text fields due to Oracle and MS limitations, that is why we have the subselect there
1054 $sql = "SELECT $coursefields $ccselect
df997f84 1055 FROM {course} c
4129338c
PS
1056 JOIN (SELECT DISTINCT e.courseid
1057 FROM {enrol} e
1058 JOIN {user_enrolments} ue ON (ue.enrolid = e.id AND ue.userid = :userid)
1059 $subwhere
1060 ) en ON (en.courseid = c.id)
df997f84 1061 $ccjoin
4129338c 1062 WHERE c.id <> :siteid
df997f84 1063 $orderby";
87163782 1064 $params['userid'] = $userid;
df997f84
PS
1065
1066 $courses = $DB->get_records_sql($sql, $params);
1067
df997f84 1068 return $courses;
df997f84
PS
1069}
1070
9ffd29ce
AA
1071
1072
df997f84
PS
1073/**
1074 * Called when user is about to be deleted.
1075 * @param object $user
1076 * @return void
1077 */
1078function enrol_user_delete($user) {
1079 global $DB;
1080
1081 $plugins = enrol_get_plugins(true);
1082 foreach ($plugins as $plugin) {
1083 $plugin->user_delete($user);
1084 }
1085
1086 // force cleanup of all broken enrolments
1087 $DB->delete_records('user_enrolments', array('userid'=>$user->id));
1088}
1089
582bae08
PS
1090/**
1091 * Called when course is about to be deleted.
bbfdff34 1092 * @param stdClass $course
582bae08
PS
1093 * @return void
1094 */
1095function enrol_course_delete($course) {
1096 global $DB;
1097
1098 $instances = enrol_get_instances($course->id, false);
1099 $plugins = enrol_get_plugins(true);
1100 foreach ($instances as $instance) {
1101 if (isset($plugins[$instance->enrol])) {
1102 $plugins[$instance->enrol]->delete_instance($instance);
1103 }
1104 // low level delete in case plugin did not do it
1105 $DB->delete_records('user_enrolments', array('enrolid'=>$instance->id));
1106 $DB->delete_records('role_assignments', array('itemid'=>$instance->id, 'component'=>'enrol_'.$instance->enrol));
1107 $DB->delete_records('user_enrolments', array('enrolid'=>$instance->id));
1108 $DB->delete_records('enrol', array('id'=>$instance->id));
1109 }
1110}
1111
df997f84
PS
1112/**
1113 * Try to enrol user via default internal auth plugin.
1114 *
1115 * For now this is always using the manual enrol plugin...
1116 *
1117 * @param $courseid
1118 * @param $userid
1119 * @param $roleid
1120 * @param $timestart
1121 * @param $timeend
1122 * @return bool success
1123 */
1124function enrol_try_internal_enrol($courseid, $userid, $roleid = null, $timestart = 0, $timeend = 0) {
1125 global $DB;
1126
1127 //note: this is hardcoded to manual plugin for now
1128
1129 if (!enrol_is_enabled('manual')) {
1130 return false;
1131 }
1132
1133 if (!$enrol = enrol_get_plugin('manual')) {
1134 return false;
1135 }
1136 if (!$instances = $DB->get_records('enrol', array('enrol'=>'manual', 'courseid'=>$courseid, 'status'=>ENROL_INSTANCE_ENABLED), 'sortorder,id ASC')) {
1137 return false;
1138 }
1139 $instance = reset($instances);
1140
1141 $enrol->enrol_user($instance, $userid, $roleid, $timestart, $timeend);
1142
1143 return true;
1144}
1145
45ff8a80
PS
1146/**
1147 * Is there a chance users might self enrol
1148 * @param int $courseid
1149 * @return bool
1150 */
1151function enrol_selfenrol_available($courseid) {
1152 $result = false;
1153
1154 $plugins = enrol_get_plugins(true);
1155 $enrolinstances = enrol_get_instances($courseid, true);
1156 foreach($enrolinstances as $instance) {
1157 if (!isset($plugins[$instance->enrol])) {
1158 continue;
1159 }
1160 if ($instance->enrol === 'guest') {
1161 // blacklist known temporary guest plugins
1162 continue;
1163 }
1164 if ($plugins[$instance->enrol]->show_enrolme_link($instance)) {
1165 $result = true;
1166 break;
1167 }
1168 }
1169
1170 return $result;
1171}
1172
bbfdff34
PS
1173/**
1174 * This function returns the end of current active user enrolment.
1175 *
1176 * It deals correctly with multiple overlapping user enrolments.
1177 *
1178 * @param int $courseid
1179 * @param int $userid
1180 * @return int|bool timestamp when active enrolment ends, false means no active enrolment now, 0 means never
1181 */
1182function enrol_get_enrolment_end($courseid, $userid) {
1183 global $DB;
1184
1185 $sql = "SELECT ue.*
1186 FROM {user_enrolments} ue
1187 JOIN {enrol} e ON (e.id = ue.enrolid AND e.courseid = :courseid)
1188 JOIN {user} u ON u.id = ue.userid
1189 WHERE ue.userid = :userid AND ue.status = :active AND e.status = :enabled AND u.deleted = 0";
1190 $params = array('enabled'=>ENROL_INSTANCE_ENABLED, 'active'=>ENROL_USER_ACTIVE, 'userid'=>$userid, 'courseid'=>$courseid);
1191
1192 if (!$enrolments = $DB->get_records_sql($sql, $params)) {
1193 return false;
1194 }
1195
1196 $changes = array();
1197
1198 foreach ($enrolments as $ue) {
1199 $start = (int)$ue->timestart;
1200 $end = (int)$ue->timeend;
1201 if ($end != 0 and $end < $start) {
1202 debugging('Invalid enrolment start or end in user_enrolment id:'.$ue->id);
1203 continue;
1204 }
1205 if (isset($changes[$start])) {
1206 $changes[$start] = $changes[$start] + 1;
1207 } else {
1208 $changes[$start] = 1;
1209 }
1210 if ($end === 0) {
1211 // no end
1212 } else if (isset($changes[$end])) {
1213 $changes[$end] = $changes[$end] - 1;
1214 } else {
1215 $changes[$end] = -1;
1216 }
1217 }
1218
1219 // let's sort then enrolment starts&ends and go through them chronologically,
1220 // looking for current status and the next future end of enrolment
1221 ksort($changes);
1222
1223 $now = time();
1224 $current = 0;
1225 $present = null;
1226
1227 foreach ($changes as $time => $change) {
1228 if ($time > $now) {
1229 if ($present === null) {
1230 // we have just went past current time
1231 $present = $current;
1232 if ($present < 1) {
1233 // no enrolment active
1234 return false;
1235 }
1236 }
1237 if ($present !== null) {
1238 // we are already in the future - look for possible end
1239 if ($current + $change < 1) {
1240 return $time;
1241 }
1242 }
1243 }
1244 $current += $change;
1245 }
1246
1247 if ($current > 0) {
1248 return 0;
1249 } else {
1250 return false;
1251 }
1252}
1253
12c92bca
PS
1254/**
1255 * Is current user accessing course via this enrolment method?
1256 *
1257 * This is intended for operations that are going to affect enrol instances.
1258 *
1259 * @param stdClass $instance enrol instance
1260 * @return bool
1261 */
1262function enrol_accessing_via_instance(stdClass $instance) {
1263 global $DB, $USER;
1264
1265 if (empty($instance->id)) {
1266 return false;
1267 }
1268
1269 if (is_siteadmin()) {
1270 // Admins may go anywhere.
1271 return false;
1272 }
1273
1274 return $DB->record_exists('user_enrolments', array('userid'=>$USER->id, 'enrolid'=>$instance->id));
1275}
1276
9f5170e9
JB
1277/**
1278 * Returns true if user is enrolled (is participating) in course
1279 * this is intended for students and teachers.
1280 *
1281 * Since 2.2 the result for active enrolments and current user are cached.
1282 *
9f5170e9
JB
1283 * @param context $context
1284 * @param int|stdClass $user if null $USER is used, otherwise user object or id expected
1285 * @param string $withcapability extra capability name
1286 * @param bool $onlyactive consider only active enrolments in enabled plugins and time restrictions
1287 * @return bool
1288 */
1289function is_enrolled(context $context, $user = null, $withcapability = '', $onlyactive = false) {
1290 global $USER, $DB;
1291
9121bb2d 1292 // First find the course context.
9f5170e9
JB
1293 $coursecontext = $context->get_course_context();
1294
9121bb2d 1295 // Make sure there is a real user specified.
9f5170e9
JB
1296 if ($user === null) {
1297 $userid = isset($USER->id) ? $USER->id : 0;
1298 } else {
1299 $userid = is_object($user) ? $user->id : $user;
1300 }
1301
1302 if (empty($userid)) {
9121bb2d 1303 // Not-logged-in!
9f5170e9
JB
1304 return false;
1305 } else if (isguestuser($userid)) {
9121bb2d 1306 // Guest account can not be enrolled anywhere.
9f5170e9
JB
1307 return false;
1308 }
1309
9121bb2d
JB
1310 // Note everybody participates on frontpage, so for other contexts...
1311 if ($coursecontext->instanceid != SITEID) {
1312 // Try cached info first - the enrolled flag is set only when active enrolment present.
9f5170e9
JB
1313 if ($USER->id == $userid) {
1314 $coursecontext->reload_if_dirty();
1315 if (isset($USER->enrol['enrolled'][$coursecontext->instanceid])) {
1316 if ($USER->enrol['enrolled'][$coursecontext->instanceid] > time()) {
1317 if ($withcapability and !has_capability($withcapability, $context, $userid)) {
1318 return false;
1319 }
1320 return true;
1321 }
1322 }
1323 }
1324
1325 if ($onlyactive) {
9121bb2d 1326 // Look for active enrolments only.
9f5170e9
JB
1327 $until = enrol_get_enrolment_end($coursecontext->instanceid, $userid);
1328
1329 if ($until === false) {
1330 return false;
1331 }
1332
1333 if ($USER->id == $userid) {
1334 if ($until == 0) {
1335 $until = ENROL_MAX_TIMESTAMP;
1336 }
1337 $USER->enrol['enrolled'][$coursecontext->instanceid] = $until;
1338 if (isset($USER->enrol['tempguest'][$coursecontext->instanceid])) {
1339 unset($USER->enrol['tempguest'][$coursecontext->instanceid]);
1340 remove_temp_course_roles($coursecontext);
1341 }
1342 }
1343
1344 } else {
9121bb2d 1345 // Any enrolment is good for us here, even outdated, disabled or inactive.
9f5170e9
JB
1346 $sql = "SELECT 'x'
1347 FROM {user_enrolments} ue
1348 JOIN {enrol} e ON (e.id = ue.enrolid AND e.courseid = :courseid)
1349 JOIN {user} u ON u.id = ue.userid
1350 WHERE ue.userid = :userid AND u.deleted = 0";
9121bb2d 1351 $params = array('userid' => $userid, 'courseid' => $coursecontext->instanceid);
9f5170e9
JB
1352 if (!$DB->record_exists_sql($sql, $params)) {
1353 return false;
1354 }
1355 }
1356 }
1357
1358 if ($withcapability and !has_capability($withcapability, $context, $userid)) {
1359 return false;
1360 }
1361
1362 return true;
1363}
1364
9121bb2d
JB
1365/**
1366 * Returns an array of joins, wheres and params that will limit the group of
1367 * users to only those enrolled and with given capability (if specified).
1368 *
8b0d254f
JB
1369 * Note this join will return duplicate rows for users who have been enrolled
1370 * several times (e.g. as manual enrolment, and as self enrolment). You may
1371 * need to use a SELECT DISTINCT in your query (see get_enrolled_sql for example).
1372 *
9121bb2d
JB
1373 * @param context $context
1374 * @param string $prefix optional, a prefix to the user id column
10c4fce5
JB
1375 * @param string|array $capability optional, may include a capability name, or array of names.
1376 * If an array is provided then this is the equivalent of a logical 'OR',
1377 * i.e. the user needs to have one of these capabilities.
5290d060 1378 * @param int $group optional, 0 indicates no current group and USERSWITHOUTGROUP users without any group; otherwise the group id
9121bb2d
JB
1379 * @param bool $onlyactive consider only active enrolments in enabled plugins and time restrictions
1380 * @param bool $onlysuspended inverse of onlyactive, consider only suspended enrolments
bb1c6f85 1381 * @param int $enrolid The enrolment ID. If not 0, only users enrolled using this enrolment method will be returned.
9121bb2d
JB
1382 * @return \core\dml\sql_join Contains joins, wheres, params
1383 */
1384function get_enrolled_with_capabilities_join(context $context, $prefix = '', $capability = '', $group = 0,
bb1c6f85 1385 $onlyactive = false, $onlysuspended = false, $enrolid = 0) {
9121bb2d
JB
1386 $uid = $prefix . 'u.id';
1387 $joins = array();
1388 $wheres = array();
1389
bb1c6f85 1390 $enrolledjoin = get_enrolled_join($context, $uid, $onlyactive, $onlysuspended, $enrolid);
9121bb2d
JB
1391 $joins[] = $enrolledjoin->joins;
1392 $wheres[] = $enrolledjoin->wheres;
1393 $params = $enrolledjoin->params;
1394
1395 if (!empty($capability)) {
1396 $capjoin = get_with_capability_join($context, $capability, $uid);
1397 $joins[] = $capjoin->joins;
1398 $wheres[] = $capjoin->wheres;
1399 $params = array_merge($params, $capjoin->params);
1400 }
1401
1402 if ($group) {
5290d060 1403 $groupjoin = groups_get_members_join($group, $uid, $context);
9121bb2d
JB
1404 $joins[] = $groupjoin->joins;
1405 $params = array_merge($params, $groupjoin->params);
5290d060
SA
1406 if (!empty($groupjoin->wheres)) {
1407 $wheres[] = $groupjoin->wheres;
1408 }
9121bb2d
JB
1409 }
1410
1411 $joins = implode("\n", $joins);
1412 $wheres[] = "{$prefix}u.deleted = 0";
1413 $wheres = implode(" AND ", $wheres);
1414
1415 return new \core\dml\sql_join($joins, $wheres, $params);
1416}
1417
9f5170e9
JB
1418/**
1419 * Returns array with sql code and parameters returning all ids
1420 * of users enrolled into course.
1421 *
1422 * This function is using 'eu[0-9]+_' prefix for table names and parameters.
1423 *
9f5170e9
JB
1424 * @param context $context
1425 * @param string $withcapability
5290d060 1426 * @param int $groupid 0 means ignore groups, USERSWITHOUTGROUP without any group and any other value limits the result by group id
9f5170e9
JB
1427 * @param bool $onlyactive consider only active enrolments in enabled plugins and time restrictions
1428 * @param bool $onlysuspended inverse of onlyactive, consider only suspended enrolments
bb1c6f85 1429 * @param int $enrolid The enrolment ID. If not 0, only users enrolled using this enrolment method will be returned.
9f5170e9
JB
1430 * @return array list($sql, $params)
1431 */
bb1c6f85
JP
1432function get_enrolled_sql(context $context, $withcapability = '', $groupid = 0, $onlyactive = false, $onlysuspended = false,
1433 $enrolid = 0) {
9f5170e9 1434
9121bb2d 1435 // Use unique prefix just in case somebody makes some SQL magic with the result.
9f5170e9
JB
1436 static $i = 0;
1437 $i++;
9121bb2d 1438 $prefix = 'eu' . $i . '_';
9f5170e9 1439
9121bb2d 1440 $capjoin = get_enrolled_with_capabilities_join(
bb1c6f85 1441 $context, $prefix, $withcapability, $groupid, $onlyactive, $onlysuspended, $enrolid);
9121bb2d
JB
1442
1443 $sql = "SELECT DISTINCT {$prefix}u.id
1444 FROM {user} {$prefix}u
1445 $capjoin->joins
1446 WHERE $capjoin->wheres";
1447
1448 return array($sql, $capjoin->params);
1449}
1450
1451/**
1452 * Returns array with sql joins and parameters returning all ids
1453 * of users enrolled into course.
1454 *
1455 * This function is using 'ej[0-9]+_' prefix for table names and parameters.
1456 *
1457 * @throws coding_exception
1458 *
1459 * @param context $context
1460 * @param string $useridcolumn User id column used the calling query, e.g. u.id
1461 * @param bool $onlyactive consider only active enrolments in enabled plugins and time restrictions
1462 * @param bool $onlysuspended inverse of onlyactive, consider only suspended enrolments
bb1c6f85 1463 * @param int $enrolid The enrolment ID. If not 0, only users enrolled using this enrolment method will be returned.
9121bb2d
JB
1464 * @return \core\dml\sql_join Contains joins, wheres, params
1465 */
bb1c6f85 1466function get_enrolled_join(context $context, $useridcolumn, $onlyactive = false, $onlysuspended = false, $enrolid = 0) {
9121bb2d
JB
1467 // Use unique prefix just in case somebody makes some SQL magic with the result.
1468 static $i = 0;
1469 $i++;
1470 $prefix = 'ej' . $i . '_';
1471
1472 // First find the course context.
9f5170e9
JB
1473 $coursecontext = $context->get_course_context();
1474
1475 $isfrontpage = ($coursecontext->instanceid == SITEID);
1476
1477 if ($onlyactive && $onlysuspended) {
1478 throw new coding_exception("Both onlyactive and onlysuspended are set, this is probably not what you want!");
1479 }
1480 if ($isfrontpage && $onlysuspended) {
1481 throw new coding_exception("onlysuspended is not supported on frontpage; please add your own early-exit!");
1482 }
1483
1484 $joins = array();
1485 $wheres = array();
1486 $params = array();
1487
9121bb2d 1488 $wheres[] = "1 = 1"; // Prevent broken where clauses later on.
9f5170e9 1489
9121bb2d
JB
1490 // Note all users are "enrolled" on the frontpage, but for others...
1491 if (!$isfrontpage) {
9f5170e9
JB
1492 $where1 = "{$prefix}ue.status = :{$prefix}active AND {$prefix}e.status = :{$prefix}enabled";
1493 $where2 = "{$prefix}ue.timestart < :{$prefix}now1 AND ({$prefix}ue.timeend = 0 OR {$prefix}ue.timeend > :{$prefix}now2)";
bb1c6f85
JP
1494
1495 $enrolconditions = array(
1496 "{$prefix}e.id = {$prefix}ue.enrolid",
1497 "{$prefix}e.courseid = :{$prefix}courseid",
1498 );
1499 if ($enrolid) {
1500 $enrolconditions[] = "{$prefix}e.id = :{$prefix}enrolid";
1501 $params[$prefix . 'enrolid'] = $enrolid;
1502 }
1503 $enrolconditionssql = implode(" AND ", $enrolconditions);
1504 $ejoin = "JOIN {enrol} {$prefix}e ON ($enrolconditionssql)";
1505
9f5170e9
JB
1506 $params[$prefix.'courseid'] = $coursecontext->instanceid;
1507
1508 if (!$onlysuspended) {
9121bb2d 1509 $joins[] = "JOIN {user_enrolments} {$prefix}ue ON {$prefix}ue.userid = $useridcolumn";
9f5170e9
JB
1510 $joins[] = $ejoin;
1511 if ($onlyactive) {
1512 $wheres[] = "$where1 AND $where2";
1513 }
1514 } else {
1515 // Suspended only where there is enrolment but ALL are suspended.
1516 // Consider multiple enrols where one is not suspended or plain role_assign.
1517 $enrolselect = "SELECT DISTINCT {$prefix}ue.userid FROM {user_enrolments} {$prefix}ue $ejoin WHERE $where1 AND $where2";
9121bb2d 1518 $joins[] = "JOIN {user_enrolments} {$prefix}ue1 ON {$prefix}ue1.userid = $useridcolumn";
bb1c6f85
JP
1519 $enrolconditions = array(
1520 "{$prefix}e1.id = {$prefix}ue1.enrolid",
1521 "{$prefix}e1.courseid = :{$prefix}_e1_courseid",
1522 );
1523 if ($enrolid) {
d1b55a33
JP
1524 $enrolconditions[] = "{$prefix}e1.id = :{$prefix}e1_enrolid";
1525 $params[$prefix . 'e1_enrolid'] = $enrolid;
bb1c6f85
JP
1526 }
1527 $enrolconditionssql = implode(" AND ", $enrolconditions);
1528 $joins[] = "JOIN {enrol} {$prefix}e1 ON ($enrolconditionssql)";
9f5170e9 1529 $params["{$prefix}_e1_courseid"] = $coursecontext->instanceid;
9121bb2d 1530 $wheres[] = "$useridcolumn NOT IN ($enrolselect)";
9f5170e9
JB
1531 }
1532
1533 if ($onlyactive || $onlysuspended) {
9121bb2d
JB
1534 $now = round(time(), -2); // Rounding helps caching in DB.
1535 $params = array_merge($params, array($prefix . 'enabled' => ENROL_INSTANCE_ENABLED,
1536 $prefix . 'active' => ENROL_USER_ACTIVE,
1537 $prefix . 'now1' => $now, $prefix . 'now2' => $now));
9f5170e9
JB
1538 }
1539 }
1540
1541 $joins = implode("\n", $joins);
9121bb2d 1542 $wheres = implode(" AND ", $wheres);
9f5170e9 1543
9121bb2d 1544 return new \core\dml\sql_join($joins, $wheres, $params);
9f5170e9
JB
1545}
1546
1547/**
1548 * Returns list of users enrolled into course.
1549 *
9f5170e9
JB
1550 * @param context $context
1551 * @param string $withcapability
5290d060 1552 * @param int $groupid 0 means ignore groups, USERSWITHOUTGROUP without any group and any other value limits the result by group id
9f5170e9
JB
1553 * @param string $userfields requested user record fields
1554 * @param string $orderby
1555 * @param int $limitfrom return a subset of records, starting at this point (optional, required if $limitnum is set).
1556 * @param int $limitnum return a subset comprising this many records (optional, required if $limitfrom is set).
1557 * @param bool $onlyactive consider only active enrolments in enabled plugins and time restrictions
1558 * @return array of user records
1559 */
1560function get_enrolled_users(context $context, $withcapability = '', $groupid = 0, $userfields = 'u.*', $orderby = null,
1561 $limitfrom = 0, $limitnum = 0, $onlyactive = false) {
1562 global $DB;
1563
1564 list($esql, $params) = get_enrolled_sql($context, $withcapability, $groupid, $onlyactive);
1565 $sql = "SELECT $userfields
1566 FROM {user} u
1567 JOIN ($esql) je ON je.id = u.id
1568 WHERE u.deleted = 0";
1569
1570 if ($orderby) {
1571 $sql = "$sql ORDER BY $orderby";
1572 } else {
1573 list($sort, $sortparams) = users_order_by_sql('u');
1574 $sql = "$sql ORDER BY $sort";
1575 $params = array_merge($params, $sortparams);
1576 }
1577
1578 return $DB->get_records_sql($sql, $params, $limitfrom, $limitnum);
1579}
1580
1581/**
1582 * Counts list of users enrolled into course (as per above function)
1583 *
9f5170e9
JB
1584 * @param context $context
1585 * @param string $withcapability
1586 * @param int $groupid 0 means ignore groups, any other value limits the result by group id
1587 * @param bool $onlyactive consider only active enrolments in enabled plugins and time restrictions
1588 * @return array of user records
1589 */
1590function count_enrolled_users(context $context, $withcapability = '', $groupid = 0, $onlyactive = false) {
1591 global $DB;
1592
9121bb2d
JB
1593 $capjoin = get_enrolled_with_capabilities_join(
1594 $context, '', $withcapability, $groupid, $onlyactive);
1595
f56e6958 1596 $sql = "SELECT COUNT(DISTINCT u.id)
9f5170e9 1597 FROM {user} u
9121bb2d
JB
1598 $capjoin->joins
1599 WHERE $capjoin->wheres AND u.deleted = 0";
9f5170e9 1600
9121bb2d 1601 return $DB->count_records_sql($sql, $capjoin->params);
9f5170e9 1602}
bbfdff34 1603
0ab8b337
SL
1604/**
1605 * Send welcome email "from" options.
1606 *
1607 * @return array list of from options
1608 */
1609function enrol_send_welcome_email_options() {
1610 return [
1611 ENROL_DO_NOT_SEND_EMAIL => get_string('no'),
1612 ENROL_SEND_EMAIL_FROM_COURSE_CONTACT => get_string('sendfromcoursecontact', 'enrol'),
1613 ENROL_SEND_EMAIL_FROM_KEY_HOLDER => get_string('sendfromkeyholder', 'enrol'),
1614 ENROL_SEND_EMAIL_FROM_NOREPLY => get_string('sendfromnoreply', 'enrol')
1615 ];
1616}
1617
5719f2ca
JP
1618/**
1619 * Serve the user enrolment form as a fragment.
1620 *
1621 * @param array $args List of named arguments for the fragment loader.
1622 * @return string
1623 */
1624function enrol_output_fragment_user_enrolment_form($args) {
1625 global $CFG, $DB;
1626
1627 $args = (object) $args;
1628 $context = $args->context;
1629 require_capability('moodle/course:enrolreview', $context);
1630
1631 $ueid = $args->ueid;
1632 $userenrolment = $DB->get_record('user_enrolments', ['id' => $ueid], '*', MUST_EXIST);
af835c24
MN
1633 $instance = $DB->get_record('enrol', ['id' => $userenrolment->enrolid], '*', MUST_EXIST);
1634 $plugin = enrol_get_plugin($instance->enrol);
5719f2ca
JP
1635 $customdata = [
1636 'ue' => $userenrolment,
1637 'modal' => true,
af835c24 1638 'enrolinstancename' => $plugin->get_instance_name($instance)
5719f2ca
JP
1639 ];
1640
1641 // Set the data if applicable.
1642 $data = [];
1643 if (isset($args->formdata)) {
1644 $serialiseddata = json_decode($args->formdata);
1645 parse_str($serialiseddata, $data);
1646 }
1647
1648 require_once("$CFG->dirroot/enrol/editenrolment_form.php");
1649 $mform = new \enrol_user_enrolment_form(null, $customdata, 'post', '', null, true, $data);
1650
1651 if (!empty($data)) {
1652 $mform->set_data($data);
1653 $mform->is_validated();
1654 }
1655
1656 return $mform->render();
1657}
1658
fba40526
DM
1659/**
1660 * Returns the course where a user enrolment belong to.
1661 *
1662 * @param int $ueid user_enrolments id
1663 * @return stdClass
1664 */
1665function enrol_get_course_by_user_enrolment_id($ueid) {
1666 global $DB;
1667 $sql = "SELECT c.* FROM {user_enrolments} ue
1668 JOIN {enrol} e ON e.id = ue.enrolid
1669 JOIN {course} c ON c.id = e.courseid
1670 WHERE ue.id = :ueid";
1671 return $DB->get_record_sql($sql, array('ueid' => $ueid));
1672}
1673
8970ff91
DM
1674/**
1675 * Return all users enrolled in a course.
1676 *
58f86c4c 1677 * @param int $courseid Course id or false if using $uefilter (user enrolment ids may belong to different courses)
8970ff91 1678 * @param bool $onlyactive consider only active enrolments in enabled plugins and time restrictions
413f19bc
DM
1679 * @param array $usersfilter Limit the results obtained to this list of user ids. $uefilter compatibility not guaranteed.
1680 * @param array $uefilter Limit the results obtained to this list of user enrolment ids. $usersfilter compatibility not guaranteed.
1681 * @return stdClass[]
8970ff91 1682 */
58f86c4c 1683function enrol_get_course_users($courseid = false, $onlyactive = false, $usersfilter = array(), $uefilter = array()) {
8970ff91
DM
1684 global $DB;
1685
58f86c4c
DM
1686 if (!$courseid && !$usersfilter && !$uefilter) {
1687 throw new \coding_exception('You should specify at least 1 filter: courseid, users or user enrolments');
1688 }
1689
1690 $sql = "SELECT ue.id AS ueid, ue.status AS uestatus, ue.enrolid AS ueenrolid, ue.timestart AS uetimestart,
1691 ue.timeend AS uetimeend, ue.modifierid AS uemodifierid, ue.timecreated AS uetimecreated,
00a66396 1692 ue.timemodified AS uetimemodified, e.status AS estatus,
58f86c4c 1693 u.* FROM {user_enrolments} ue
8970ff91
DM
1694 JOIN {enrol} e ON e.id = ue.enrolid
1695 JOIN {user} u ON ue.userid = u.id
58f86c4c
DM
1696 WHERE ";
1697 $params = array();
1698
1699 if ($courseid) {
1700 $conditions[] = "e.courseid = :courseid";
1701 $params['courseid'] = $courseid;
1702 }
8970ff91
DM
1703
1704 if ($onlyactive) {
413f19bc
DM
1705 $conditions[] = "ue.status = :active AND e.status = :enabled AND ue.timestart < :now1 AND " .
1706 "(ue.timeend = 0 OR ue.timeend > :now2)";
1707 // Improves db caching.
1708 $params['now1'] = round(time(), -2);
8970ff91
DM
1709 $params['now2'] = $params['now1'];
1710 $params['active'] = ENROL_USER_ACTIVE;
1711 $params['enabled'] = ENROL_INSTANCE_ENABLED;
8970ff91 1712 }
58f86c4c
DM
1713
1714 if ($usersfilter) {
1715 list($usersql, $userparams) = $DB->get_in_or_equal($usersfilter, SQL_PARAMS_NAMED);
1716 $conditions[] = "ue.userid $usersql";
1717 $params = $params + $userparams;
1718 }
1719
1720 if ($uefilter) {
b3d68794 1721 list($uesql, $ueparams) = $DB->get_in_or_equal($uefilter, SQL_PARAMS_NAMED);
58f86c4c
DM
1722 $conditions[] = "ue.id $uesql";
1723 $params = $params + $ueparams;
1724 }
1725
1726 return $DB->get_records_sql($sql . ' ' . implode(' AND ', $conditions), $params);
8970ff91
DM
1727}
1728
0fc30827
HN
1729/**
1730 * Get the list of options for the enrolment period dropdown
1731 *
1732 * @return array List of options for the enrolment period dropdown
1733 */
1734function enrol_get_period_list() {
1735 $periodmenu = [];
1736 $periodmenu[''] = get_string('unlimited');
1737 for ($i = 1; $i <= 365; $i++) {
1738 $seconds = $i * DAYSECS;
1739 $periodmenu[$seconds] = get_string('numdays', '', $i);
1740 }
1741 return $periodmenu;
1742}
1743
1744/**
1745 * Calculate duration base on start time and end time
1746 *
1747 * @param int $timestart Time start
1748 * @param int $timeend Time end
1749 * @return float|int Calculated duration
1750 */
1751function enrol_calculate_duration($timestart, $timeend) {
1752 $duration = floor(($timeend - $timestart) / DAYSECS) * DAYSECS;
1753 return $duration;
1754}
1755
df997f84 1756/**
413f19bc
DM
1757 * Enrolment plugins abstract class.
1758 *
df997f84
PS
1759 * All enrol plugins should be based on this class,
1760 * this is also the main source of documentation.
413f19bc
DM
1761 *
1762 * @copyright 2010 Petr Skoda {@link http://skodak.org}
1763 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
df997f84
PS
1764 */
1765abstract class enrol_plugin {
1766 protected $config = null;
1767
1768 /**
1769 * Returns name of this enrol plugin
1770 * @return string
1771 */
1772 public function get_name() {
bf423bb1 1773 // second word in class is always enrol name, sorry, no fancy plugin names with _
df997f84
PS
1774 $words = explode('_', get_class($this));
1775 return $words[1];
1776 }
1777
1778 /**
1779 * Returns localised name of enrol instance
1780 *
1781 * @param object $instance (null is accepted too)
1782 * @return string
1783 */
1784 public function get_instance_name($instance) {
1785 if (empty($instance->name)) {
1786 $enrol = $this->get_name();
1787 return get_string('pluginname', 'enrol_'.$enrol);
1788 } else {
b0c6dc1c 1789 $context = context_course::instance($instance->courseid);
54475ccb 1790 return format_string($instance->name, true, array('context'=>$context));
df997f84
PS
1791 }
1792 }
1793
bf423bb1
PS
1794 /**
1795 * Returns optional enrolment information icons.
1796 *
1797 * This is used in course list for quick overview of enrolment options.
1798 *
1799 * We are not using single instance parameter because sometimes
1800 * we might want to prevent icon repetition when multiple instances
1801 * of one type exist. One instance may also produce several icons.
1802 *
1803 * @param array $instances all enrol instances of this type in one course
1804 * @return array of pix_icon
1805 */
1806 public function get_info_icons(array $instances) {
1807 return array();
1808 }
1809
1810 /**
1811 * Returns optional enrolment instance description text.
1812 *
1813 * This is used in detailed course information.
1814 *
1815 *
1816 * @param object $instance
1817 * @return string short html text
1818 */
1819 public function get_description_text($instance) {
1820 return null;
1821 }
1822
df997f84
PS
1823 /**
1824 * Makes sure config is loaded and cached.
1825 * @return void
1826 */
1827 protected function load_config() {
1828 if (!isset($this->config)) {
1829 $name = $this->get_name();
820a8188 1830 $this->config = get_config("enrol_$name");
df997f84
PS
1831 }
1832 }
1833
1834 /**
1835 * Returns plugin config value
1836 * @param string $name
1837 * @param string $default value if config does not exist yet
1838 * @return string value or default
1839 */
1840 public function get_config($name, $default = NULL) {
1841 $this->load_config();
1842 return isset($this->config->$name) ? $this->config->$name : $default;
1843 }
1844
1845 /**
1846 * Sets plugin config value
1847 * @param string $name name of config
1848 * @param string $value string config value, null means delete
1849 * @return string value
1850 */
1851 public function set_config($name, $value) {
47811589 1852 $pluginname = $this->get_name();
df997f84
PS
1853 $this->load_config();
1854 if ($value === NULL) {
1855 unset($this->config->$name);
1856 } else {
1857 $this->config->$name = $value;
1858 }
47811589 1859 set_config($name, $value, "enrol_$pluginname");
df997f84
PS
1860 }
1861
1862 /**
1863 * Does this plugin assign protected roles are can they be manually removed?
1864 * @return bool - false means anybody may tweak roles, it does not use itemid and component when assigning roles
1865 */
1866 public function roles_protected() {
1867 return true;
1868 }
1869
91b99e80
PS
1870 /**
1871 * Does this plugin allow manual enrolments?
1872 *
1873 * @param stdClass $instance course enrol instance
1874 * All plugins allowing this must implement 'enrol/xxx:enrol' capability
1875 *
282b5cc7 1876 * @return bool - true means user with 'enrol/xxx:enrol' may enrol others freely, false means nobody may add more enrolments manually
91b99e80
PS
1877 */
1878 public function allow_enrol(stdClass $instance) {
1879 return false;
1880 }
1881
df997f84 1882 /**
282b5cc7 1883 * Does this plugin allow manual unenrolment of all users?
91b99e80 1884 * All plugins allowing this must implement 'enrol/xxx:unenrol' capability
df997f84 1885 *
282b5cc7
PS
1886 * @param stdClass $instance course enrol instance
1887 * @return bool - true means user with 'enrol/xxx:unenrol' may unenrol others freely, false means nobody may touch user_enrolments
df997f84
PS
1888 */
1889 public function allow_unenrol(stdClass $instance) {
1890 return false;
1891 }
1892
282b5cc7
PS
1893 /**
1894 * Does this plugin allow manual unenrolment of a specific user?
1895 * All plugins allowing this must implement 'enrol/xxx:unenrol' capability
1896 *
1897 * This is useful especially for synchronisation plugins that
1898 * do suspend instead of full unenrolment.
1899 *
1900 * @param stdClass $instance course enrol instance
1901 * @param stdClass $ue record from user_enrolments table, specifies user
1902 *
1903 * @return bool - true means user with 'enrol/xxx:unenrol' may unenrol this user, false means nobody may touch this user enrolment
1904 */
1905 public function allow_unenrol_user(stdClass $instance, stdClass $ue) {
1906 return $this->allow_unenrol($instance);
1907 }
1908
df997f84
PS
1909 /**
1910 * Does this plugin allow manual changes in user_enrolments table?
1911 *
91b99e80 1912 * All plugins allowing this must implement 'enrol/xxx:manage' capability
df997f84
PS
1913 *
1914 * @param stdClass $instance course enrol instance
1915 * @return bool - true means it is possible to change enrol period and status in user_enrolments table
1916 */
1917 public function allow_manage(stdClass $instance) {
1918 return false;
1919 }
1920
217d0397
PS
1921 /**
1922 * Does this plugin support some way to user to self enrol?
1923 *
1924 * @param stdClass $instance course enrol instance
1925 *
1926 * @return bool - true means show "Enrol me in this course" link in course UI
1927 */
1928 public function show_enrolme_link(stdClass $instance) {
1929 return false;
1930 }
1931
df997f84
PS
1932 /**
1933 * Attempt to automatically enrol current user in course without any interaction,
1934 * calling code has to make sure the plugin and instance are active.
1935 *
ed1d72ea
SH
1936 * This should return either a timestamp in the future or false.
1937 *
df997f84 1938 * @param stdClass $instance course enrol instance
df997f84
PS
1939 * @return bool|int false means not enrolled, integer means timeend
1940 */
1941 public function try_autoenrol(stdClass $instance) {
1942 global $USER;
1943
1944 return false;
1945 }
1946
1947 /**
1948 * Attempt to automatically gain temporary guest access to course,
1949 * calling code has to make sure the plugin and instance are active.
1950 *
ed1d72ea
SH
1951 * This should return either a timestamp in the future or false.
1952 *
df997f84 1953 * @param stdClass $instance course enrol instance
df997f84
PS
1954 * @return bool|int false means no guest access, integer means timeend
1955 */
1956 public function try_guestaccess(stdClass $instance) {
1957 global $USER;
1958
1959 return false;
1960 }
1961
1962 /**
1963 * Enrol user into course via enrol instance.
1964 *
1965 * @param stdClass $instance
1966 * @param int $userid
1967 * @param int $roleid optional role id
2a6dcb72
PS
1968 * @param int $timestart 0 means unknown
1969 * @param int $timeend 0 means forever
f2a9be5f 1970 * @param int $status default to ENROL_USER_ACTIVE for new enrolments, no change by default in updates
ef8a733a 1971 * @param bool $recovergrades restore grade history
df997f84
PS
1972 * @return void
1973 */
ef8a733a 1974 public function enrol_user(stdClass $instance, $userid, $roleid = null, $timestart = 0, $timeend = 0, $status = null, $recovergrades = null) {
df997f84
PS
1975 global $DB, $USER, $CFG; // CFG necessary!!!
1976
1977 if ($instance->courseid == SITEID) {
1978 throw new coding_exception('invalid attempt to enrol into frontpage course!');
1979 }
1980
1981 $name = $this->get_name();
1982 $courseid = $instance->courseid;
1983
1984 if ($instance->enrol !== $name) {
1985 throw new coding_exception('invalid enrol instance!');
1986 }
b0c6dc1c 1987 $context = context_course::instance($instance->courseid, MUST_EXIST);
ef8a733a
CF
1988 if (!isset($recovergrades)) {
1989 $recovergrades = $CFG->recovergradesdefault;
1990 }
df997f84
PS
1991
1992 $inserted = false;
358fb4dc 1993 $updated = false;
df997f84 1994 if ($ue = $DB->get_record('user_enrolments', array('enrolid'=>$instance->id, 'userid'=>$userid))) {
d587f077 1995 //only update if timestart or timeend or status are different.
ae8c1f3d 1996 if ($ue->timestart != $timestart or $ue->timeend != $timeend or (!is_null($status) and $ue->status != $status)) {
bb78e249 1997 $this->update_user_enrol($instance, $userid, $status, $timestart, $timeend);
df997f84
PS
1998 }
1999 } else {
365a5941 2000 $ue = new stdClass();
df997f84 2001 $ue->enrolid = $instance->id;
ae8c1f3d 2002 $ue->status = is_null($status) ? ENROL_USER_ACTIVE : $status;
df997f84
PS
2003 $ue->userid = $userid;
2004 $ue->timestart = $timestart;
2005 $ue->timeend = $timeend;
6006774c 2006 $ue->modifierid = $USER->id;
2a6dcb72
PS
2007 $ue->timecreated = time();
2008 $ue->timemodified = $ue->timecreated;
df997f84
PS
2009 $ue->id = $DB->insert_record('user_enrolments', $ue);
2010
2011 $inserted = true;
2012 }
2013
df997f84 2014 if ($inserted) {
bb78e249
RT
2015 // Trigger event.
2016 $event = \core\event\user_enrolment_created::create(
2017 array(
2018 'objectid' => $ue->id,
2019 'courseid' => $courseid,
2020 'context' => $context,
2021 'relateduserid' => $ue->userid,
2022 'other' => array('enrol' => $name)
2023 )
2024 );
2025 $event->trigger();
5667e602 2026 // Check if course contacts cache needs to be cleared.
442f12f8 2027 core_course_category::user_enrolment_changed($courseid, $ue->userid,
5667e602 2028 $ue->status, $ue->timestart, $ue->timeend);
df997f84
PS
2029 }
2030
0c2701fd 2031 if ($roleid) {
bbfdff34 2032 // this must be done after the enrolment event so that the role_assigned event is triggered afterwards
0c2701fd
PS
2033 if ($this->roles_protected()) {
2034 role_assign($roleid, $userid, $context->id, 'enrol_'.$name, $instance->id);
2035 } else {
2036 role_assign($roleid, $userid, $context->id);
2037 }
2038 }
2039
ef8a733a
CF
2040 // Recover old grades if present.
2041 if ($recovergrades) {
2042 require_once("$CFG->libdir/gradelib.php");
2043 grade_recover_history_grades($userid, $courseid);
2044 }
2045
bbfdff34 2046 // reset current user enrolment caching
df997f84
PS
2047 if ($userid == $USER->id) {
2048 if (isset($USER->enrol['enrolled'][$courseid])) {
2049 unset($USER->enrol['enrolled'][$courseid]);
2050 }
2051 if (isset($USER->enrol['tempguest'][$courseid])) {
2052 unset($USER->enrol['tempguest'][$courseid]);
e922fe23 2053 remove_temp_course_roles($context);
df997f84
PS
2054 }
2055 }
2056 }
2057
2058 /**
2059 * Store user_enrolments changes and trigger event.
2060 *
358fb4dc
PS
2061 * @param stdClass $instance
2062 * @param int $userid
df997f84
PS
2063 * @param int $status
2064 * @param int $timestart
2065 * @param int $timeend
2066 * @return void
2067 */
2068 public function update_user_enrol(stdClass $instance, $userid, $status = NULL, $timestart = NULL, $timeend = NULL) {
5667e602 2069 global $DB, $USER, $CFG;
df997f84
PS
2070
2071 $name = $this->get_name();
2072
2073 if ($instance->enrol !== $name) {
2074 throw new coding_exception('invalid enrol instance!');
2075 }
2076
2077 if (!$ue = $DB->get_record('user_enrolments', array('enrolid'=>$instance->id, 'userid'=>$userid))) {
2078 // weird, user not enrolled
2079 return;
2080 }
2081
2082 $modified = false;
2083 if (isset($status) and $ue->status != $status) {
2084 $ue->status = $status;
2085 $modified = true;
2086 }
2087 if (isset($timestart) and $ue->timestart != $timestart) {
2088 $ue->timestart = $timestart;
2089 $modified = true;
2090 }
2091 if (isset($timeend) and $ue->timeend != $timeend) {
2092 $ue->timeend = $timeend;
2093 $modified = true;
2094 }
2095
2096 if (!$modified) {
2097 // no change
2098 return;
2099 }
2100
2101 $ue->modifierid = $USER->id;
ed63ffc7 2102 $ue->timemodified = time();
df997f84 2103 $DB->update_record('user_enrolments', $ue);
1d049e08
JC
2104
2105 // User enrolments have changed, so mark user as dirty.
2106 mark_user_dirty($userid);
df997f84 2107
eeb9ee8e 2108 // Invalidate core_access cache for get_suspended_userids.
03ecddb4 2109 cache_helper::invalidate_by_definition('core', 'suspended_userids', array(), array($instance->courseid));
eeb9ee8e 2110
bb78e249
RT
2111 // Trigger event.
2112 $event = \core\event\user_enrolment_updated::create(
2113 array(
2114 'objectid' => $ue->id,
2115 'courseid' => $instance->courseid,
2116 'context' => context_course::instance($instance->courseid),
2117 'relateduserid' => $ue->userid,
2118 'other' => array('enrol' => $name)
2119 )
2120 );
2121 $event->trigger();
5667e602 2122
442f12f8 2123 core_course_category::user_enrolment_changed($instance->courseid, $ue->userid,
5667e602 2124 $ue->status, $ue->timestart, $ue->timeend);
df997f84
PS
2125 }
2126
2127 /**
2128 * Unenrol user from course,
2129 * the last unenrolment removes all remaining roles.
2130 *
2131 * @param stdClass $instance
2132 * @param int $userid
2133 * @return void
2134 */
2135 public function unenrol_user(stdClass $instance, $userid) {
2136 global $CFG, $USER, $DB;
7881024e 2137 require_once("$CFG->dirroot/group/lib.php");
df997f84
PS
2138
2139 $name = $this->get_name();
2140 $courseid = $instance->courseid;
2141
2142 if ($instance->enrol !== $name) {
2143 throw new coding_exception('invalid enrol instance!');
2144 }
b0c6dc1c 2145 $context = context_course::instance($instance->courseid, MUST_EXIST);
df997f84
PS
2146
2147 if (!$ue = $DB->get_record('user_enrolments', array('enrolid'=>$instance->id, 'userid'=>$userid))) {
2148 // weird, user not enrolled
2149 return;
2150 }
2151
7881024e
PS
2152 // Remove all users groups linked to this enrolment instance.
2153 if ($gms = $DB->get_records('groups_members', array('userid'=>$userid, 'component'=>'enrol_'.$name, 'itemid'=>$instance->id))) {
2154 foreach ($gms as $gm) {
2155 groups_remove_member($gm->groupid, $gm->userid);
2156 }
2157 }
2158
df997f84
PS
2159 role_unassign_all(array('userid'=>$userid, 'contextid'=>$context->id, 'component'=>'enrol_'.$name, 'itemid'=>$instance->id));
2160 $DB->delete_records('user_enrolments', array('id'=>$ue->id));
2161
2162 // add extra info and trigger event
2163 $ue->courseid = $courseid;
2164 $ue->enrol = $name;
2165
2166 $sql = "SELECT 'x'
2167 FROM {user_enrolments} ue
2168 JOIN {enrol} e ON (e.id = ue.enrolid)
7881024e 2169 WHERE ue.userid = :userid AND e.courseid = :courseid";
df997f84
PS
2170 if ($DB->record_exists_sql($sql, array('userid'=>$userid, 'courseid'=>$courseid))) {
2171 $ue->lastenrol = false;
7881024e 2172
df997f84
PS
2173 } else {
2174 // the big cleanup IS necessary!
df997f84
PS
2175 require_once("$CFG->libdir/gradelib.php");
2176
2177 // remove all remaining roles
2178 role_unassign_all(array('userid'=>$userid, 'contextid'=>$context->id), true, false);
2179
2180 //clean up ALL invisible user data from course if this is the last enrolment - groups, grades, etc.
2181 groups_delete_group_members($courseid, $userid);
2182
2183 grade_user_unenrol($courseid, $userid);
2184
2185 $DB->delete_records('user_lastaccess', array('userid'=>$userid, 'courseid'=>$courseid));
2186
7d2800fb 2187 $ue->lastenrol = true; // means user not enrolled any more
df997f84 2188 }
bb78e249
RT
2189 // Trigger event.
2190 $event = \core\event\user_enrolment_deleted::create(
2191 array(
2192 'courseid' => $courseid,
2193 'context' => $context,
2194 'relateduserid' => $ue->userid,
4fb6600e 2195 'objectid' => $ue->id,
bb78e249
RT
2196 'other' => array(
2197 'userenrolment' => (array)$ue,
2198 'enrol' => $name
2199 )
2200 )
2201 );
2202 $event->trigger();
1d049e08
JC
2203
2204 // User enrolments have changed, so mark user as dirty.
2205 mark_user_dirty($userid);
bbfdff34 2206
5667e602 2207 // Check if courrse contacts cache needs to be cleared.
442f12f8 2208 core_course_category::user_enrolment_changed($courseid, $ue->userid, ENROL_USER_SUSPENDED);
5667e602 2209
bbfdff34 2210 // reset current user enrolment caching
df997f84
PS
2211 if ($userid == $USER->id) {
2212 if (isset($USER->enrol['enrolled'][$courseid])) {
2213 unset($USER->enrol['enrolled'][$courseid]);
2214 }
2215 if (isset($USER->enrol['tempguest'][$courseid])) {
2216 unset($USER->enrol['tempguest'][$courseid]);
e922fe23 2217 remove_temp_course_roles($context);
df997f84
PS
2218 }
2219 }
2220 }
2221
2222 /**
2223 * Forces synchronisation of user enrolments.
2224 *
2225 * This is important especially for external enrol plugins,
2226 * this function is called for all enabled enrol plugins
2227 * right after every user login.
2228 *
2229 * @param object $user user record
2230 * @return void
2231 */
2232 public function sync_user_enrolments($user) {
2233 // override if necessary
2234 }
2235
60010fd6
DW
2236 /**
2237 * This returns false for backwards compatibility, but it is really recommended.
2238 *
2239 * @since Moodle 3.1
2240 * @return boolean
2241 */
2242 public function use_standard_editing_ui() {
2243 return false;
2244 }
2245
2246 /**
2247 * Return whether or not, given the current state, it is possible to add a new instance
2248 * of this enrolment plugin to the course.
2249 *
2250 * Default implementation is just for backwards compatibility.
2251 *
2252 * @param int $courseid
2253 * @return boolean
2254 */
2255 public function can_add_instance($courseid) {
2256 $link = $this->get_newinstance_link($courseid);
2257 return !empty($link);
2258 }
2259
51c736f0
DW
2260 /**
2261 * Return whether or not, given the current state, it is possible to edit an instance
2262 * of this enrolment plugin in the course. Used by the standard editing UI
2263 * to generate a link to the edit instance form if editing is allowed.
2264 *
2265 * @param stdClass $instance
2266 * @return boolean
2267 */
2268 public function can_edit_instance($instance) {
2269 $context = context_course::instance($instance->courseid);
2270
2271 return has_capability('enrol/' . $instance->enrol . ':config', $context);
2272 }
2273
df997f84
PS
2274 /**
2275 * Returns link to page which may be used to add new instance of enrolment plugin in course.
2276 * @param int $courseid
2277 * @return moodle_url page url
2278 */
e25f2466 2279 public function get_newinstance_link($courseid) {
df997f84
PS
2280 // override for most plugins, check if instance already exists in cases only one instance is supported
2281 return NULL;
2282 }
2283
2284 /**
ee9e079d 2285 * @deprecated since Moodle 2.8 MDL-35864 - please use can_delete_instance() instead.
df997f84
PS
2286 */
2287 public function instance_deleteable($instance) {
648cddcb
AA
2288 throw new coding_exception('Function enrol_plugin::instance_deleteable() is deprecated, use
2289 enrol_plugin::can_delete_instance() instead');
ee9e079d
DN
2290 }
2291
2292 /**
2293 * Is it possible to delete enrol instance via standard UI?
2294 *
b5a289c4 2295 * @param stdClass $instance
ee9e079d
DN
2296 * @return bool
2297 */
2298 public function can_delete_instance($instance) {
2299 return false;
df997f84
PS
2300 }
2301
b5a289c4
DNA
2302 /**
2303 * Is it possible to hide/show enrol instance via standard UI?
2304 *
2305 * @param stdClass $instance
2306 * @return bool
2307 */
2308 public function can_hide_show_instance($instance) {
2309 debugging("The enrolment plugin '".$this->get_name()."' should override the function can_hide_show_instance().", DEBUG_DEVELOPER);
2310 return true;
2311 }
2312
df997f84
PS
2313 /**
2314 * Returns link to manual enrol UI if exists.
2315 * Does the access control tests automatically.
2316 *
2317 * @param object $instance
2318 * @return moodle_url
2319 */
2320 public function get_manual_enrol_link($instance) {
2321 return NULL;
2322 }
2323
2324 /**
2325 * Returns list of unenrol links for all enrol instances in course.
2326 *
217d0397 2327 * @param int $instance
bf423bb1 2328 * @return moodle_url or NULL if self unenrolment not supported
df997f84
PS
2329 */
2330 public function get_unenrolself_link($instance) {
2331 global $USER, $CFG, $DB;
2332
2333 $name = $this->get_name();
2334 if ($instance->enrol !== $name) {
2335 throw new coding_exception('invalid enrol instance!');
2336 }
2337
2338 if ($instance->courseid == SITEID) {
2339 return NULL;
2340 }
2341
2342 if (!enrol_is_enabled($name)) {
2343 return NULL;
2344 }
2345
2346 if ($instance->status != ENROL_INSTANCE_ENABLED) {
2347 return NULL;
2348 }
2349
df997f84
PS
2350 if (!file_exists("$CFG->dirroot/enrol/$name/unenrolself.php")) {
2351 return NULL;
2352 }
2353
b0c6dc1c 2354 $context = context_course::instance($instance->courseid, MUST_EXIST);
217d0397 2355
df997f84
PS
2356 if (!has_capability("enrol/$name:unenrolself", $context)) {
2357 return NULL;
2358 }
2359
2360 if (!$DB->record_exists('user_enrolments', array('enrolid'=>$instance->id, 'userid'=>$USER->id, 'status'=>ENROL_USER_ACTIVE))) {
2361 return NULL;
2362 }
2363
0e35ba6f 2364 return new moodle_url("/enrol/$name/unenrolself.php", array('enrolid'=>$instance->id));
df997f84
PS
2365 }
2366
2367 /**
2368 * Adds enrol instance UI to course edit form
2369 *
2370 * @param object $instance enrol instance or null if does not exist yet
2371 * @param MoodleQuickForm $mform
2372 * @param object $data
2373 * @param object $context context of existing course or parent category if course does not exist
2374 * @return void
2375 */
2376 public function course_edit_form($instance, MoodleQuickForm $mform, $data, $context) {
2377 // override - usually at least enable/disable switch, has to add own form header
2378 }
2379
60010fd6
DW
2380 /**
2381 * Adds form elements to add/edit instance form.
2382 *
2383 * @since Moodle 3.1
2384 * @param object $instance enrol instance or null if does not exist yet
2385 * @param MoodleQuickForm $mform
2386 * @param context $context
2387 * @return void
2388 */
2389 public function edit_instance_form($instance, MoodleQuickForm $mform, $context) {
2390 // Do nothing by default.
2391 }
2392
2393 /**
2394 * Perform custom validation of the data used to edit the instance.
2395 *
2396 * @since Moodle 3.1
2397 * @param array $data array of ("fieldname"=>value) of submitted data
2398 * @param array $files array of uploaded files "element_name"=>tmp_file_path
2399 * @param object $instance The instance data loaded from the DB.
2400 * @param context $context The context of the instance we are editing
2401 * @return array of "element_name"=>"error_description" if there are errors,
2402 * or an empty array if everything is OK.
2403 */
2404 public function edit_instance_validation($data, $files, $instance, $context) {
2405 // No errors by default.
2406 debugging('enrol_plugin::edit_instance_validation() is missing. This plugin has no validation!', DEBUG_DEVELOPER);
2407 return array();
2408 }
2409
df997f84
PS
2410 /**
2411 * Validates course edit form data
2412 *
2413 * @param object $instance enrol instance or null if does not exist yet
2414 * @param array $data
2415 * @param object $context context of existing course or parent category if course does not exist
2416 * @return array errors array
2417 */
2418 public function course_edit_validation($instance, array $data, $context) {
2419 return array();
2420 }
2421
2422 /**
2423 * Called after updating/inserting course.
2424 *
2425 * @param bool $inserted true if course just inserted
2426 * @param object $course
2427 * @param object $data form data
2428 * @return void
2429 */
2430 public function course_updated($inserted, $course, $data) {
eafb7a72
PS
2431 if ($inserted) {
2432 if ($this->get_config('defaultenrol')) {
2433 $this->add_default_instance($course);
2434 }
2435 }
df997f84
PS
2436 }
2437
2438 /**
eafb7a72 2439 * Add new instance of enrol plugin.
df997f84
PS
2440 * @param object $course
2441 * @param array instance fields
0848a196 2442 * @return int id of new instance, null if can not be created
df997f84
PS
2443 */
2444 public function add_instance($course, array $fields = NULL) {
2445 global $DB;
2446
2447 if ($course->id == SITEID) {
2448 throw new coding_exception('Invalid request to add enrol instance to frontpage.');
2449 }
2450
365a5941 2451 $instance = new stdClass();
df997f84
PS
2452 $instance->enrol = $this->get_name();
2453 $instance->status = ENROL_INSTANCE_ENABLED;
2454 $instance->courseid = $course->id;
2455 $instance->enrolstartdate = 0;
2456 $instance->enrolenddate = 0;
2457 $instance->timemodified = time();
2458 $instance->timecreated = $instance->timemodified;
2459 $instance->sortorder = $DB->get_field('enrol', 'COALESCE(MAX(sortorder), -1) + 1', array('courseid'=>$course->id));
2460
2461 $fields = (array)$fields;
2462 unset($fields['enrol']);
2463 unset($fields['courseid']);
2464 unset($fields['sortorder']);
2465 foreach($fields as $field=>$value) {
2466 $instance->$field = $value;
2467 }
2468
080c7d47
MG
2469 $instance->id = $DB->insert_record('enrol', $instance);
2470
2471 \core\event\enrol_instance_created::create_from_record($instance)->trigger();
2472
2473 return $instance->id;
df997f84
PS
2474 }
2475
60010fd6
DW
2476 /**
2477 * Update instance of enrol plugin.
2478 *
2479 * @since Moodle 3.1
2480 * @param stdClass $instance
2481 * @param stdClass $data modified instance fields
2482 * @return boolean
2483 */
2484 public function update_instance($instance, $data) {
2485 global $DB;
2486 $properties = array('status', 'name', 'password', 'customint1', 'customint2', 'customint3',
2487 'customint4', 'customint5', 'customint6', 'customint7', 'customint8',
2488 'customchar1', 'customchar2', 'customchar3', 'customdec1', 'customdec2',
2489 'customtext1', 'customtext2', 'customtext3', 'customtext4', 'roleid',
2490 'enrolperiod', 'expirynotify', 'notifyall', 'expirythreshold',
2491 'enrolstartdate', 'enrolenddate', 'cost', 'currency');
2492
2493 foreach ($properties as $key) {
2494 if (isset($data->$key)) {
2495 $instance->$key = $data->$key;
2496 }
2497 }
2498 $instance->timemodified = time();
2499
2500 $update = $DB->update_record('enrol', $instance);
2501 if ($update) {
2502 \core\event\enrol_instance_updated::create_from_record($instance)->trigger();
2503 }
2504 return $update;
2505 }
2506
df997f84
PS
2507 /**
2508 * Add new instance of enrol plugin with default settings,
2509 * called when adding new instance manually or when adding new course.
2510 *
2511 * Not all plugins support this.
2512 *
2513 * @param object $course
2514 * @return int id of new instance or null if no default supported
2515 */
2516 public function add_default_instance($course) {
2517 return null;
2518 }
2519
af7177db
PS
2520 /**
2521 * Update instance status
2522 *
2523 * Override when plugin needs to do some action when enabled or disabled.
2524 *
2525 * @param stdClass $instance
2526 * @param int $newstatus ENROL_INSTANCE_ENABLED, ENROL_INSTANCE_DISABLED
2527 * @return void
2528 */
2529 public function update_status($instance, $newstatus) {
2530 global $DB;
2531
2532 $instance->status = $newstatus;
2533 $DB->update_record('enrol', $instance);
2534
af7177db 2535 $context = context_course::instance($instance->courseid);
080c7d47
MG
2536 \core\event\enrol_instance_updated::create_from_record($instance)->trigger();
2537
2538 // Invalidate all enrol caches.
af7177db
PS
2539 $context->mark_dirty();
2540 }
2541
df997f84
PS
2542 /**
2543 * Delete course enrol plugin instance, unenrol all users.
2544 * @param object $instance
2545 * @return void
2546 */
2547 public function delete_instance($instance) {
2548 global $DB;
2549
2550 $name = $this->get_name();
2551 if ($instance->enrol !== $name) {
2552 throw new coding_exception('invalid enrol instance!');
2553 }
2554
2555 //first unenrol all users
2556 $participants = $DB->get_recordset('user_enrolments', array('enrolid'=>$instance->id));
2557 foreach ($participants as $participant) {
2558 $this->unenrol_user($instance, $participant->userid);
2559 }
2560 $participants->close();
2561
2562 // now clean up all remainders that were not removed correctly
0897d658
SR
2563 if ($gms = $DB->get_records('groups_members', array('itemid' => $instance->id, 'component' => 'enrol_' . $name))) {
2564 foreach ($gms as $gm) {
2565 groups_remove_member($gm->groupid, $gm->userid);
2566 }
2567 }
a1cedcc9 2568 $DB->delete_records('role_assignments', array('itemid'=>$instance->id, 'component'=>'enrol_'.$name));
df997f84
PS
2569 $DB->delete_records('user_enrolments', array('enrolid'=>$instance->id));
2570
2571 // finally drop the enrol row
2572 $DB->delete_records('enrol', array('id'=>$instance->id));
af7177db 2573
af7177db 2574 $context = context_course::instance($instance->courseid);
080c7d47
MG
2575 \core\event\enrol_instance_deleted::create_from_record($instance)->trigger();
2576
2577 // Invalidate all enrol caches.
af7177db 2578 $context->mark_dirty();
df997f84
PS
2579 }
2580
2581 /**
2582 * Creates course enrol form, checks if form submitted
2583 * and enrols user if necessary. It can also redirect.
2584 *
2585 * @param stdClass $instance
2586 * @return string html text, usually a form in a text box
2587 */
2588 public function enrol_page_hook(stdClass $instance) {
2589 return null;
2590 }
2591
85d1c53a
RT
2592 /**
2593 * Checks if user can self enrol.
2594 *
2595 * @param stdClass $instance enrolment instance
cc1b5015
RT
2596 * @param bool $checkuserenrolment if true will check if user enrolment is inactive.
2597 * used by navigation to improve performance.
2598 * @return bool|string true if successful, else error message or false
85d1c53a 2599 */
cc1b5015 2600 public function can_self_enrol(stdClass $instance, $checkuserenrolment = true) {
85d1c53a
RT
2601 return false;
2602 }
2603
2604 /**
2605 * Return information for enrolment instance containing list of parameters required
2606 * for enrolment, name of enrolment plugin etc.
2607 *
2608 * @param stdClass $instance enrolment instance
2609 * @return array instance info.
2610 */
2611 public function get_enrol_info(stdClass $instance) {
2612 return null;
2613 }
2614
df997f84
PS
2615 /**
2616 * Adds navigation links into course admin block.
2617 *
2618 * By defaults looks for manage links only.
2619 *
2620 * @param navigation_node $instancesnode
bbfdff34 2621 * @param stdClass $instance
2d4b1f3e 2622 * @return void
df997f84
PS
2623 */
2624 public function add_course_navigation($instancesnode, stdClass $instance) {
f7589515
DW
2625 if ($this->use_standard_editing_ui()) {
2626 $context = context_course::instance($instance->courseid);
2627 $cap = 'enrol/' . $instance->enrol . ':config';
2628 if (has_capability($cap, $context)) {
2629 $linkparams = array('courseid' => $instance->courseid, 'id' => $instance->id, 'type' => $instance->enrol);
2630 $managelink = new moodle_url('/enrol/editinstance.php', $linkparams);
2631 $instancesnode->add($this->get_instance_name($instance), $managelink, navigation_node::TYPE_SETTING);
2632 }
2633 }
df997f84
PS
2634 }
2635
2636 /**
2d4b1f3e
PS
2637 * Returns edit icons for the page with list of instances
2638 * @param stdClass $instance
2639 * @return array
df997f84 2640 */
2d4b1f3e 2641 public function get_action_icons(stdClass $instance) {
51c736f0
DW
2642 global $OUTPUT;
2643
2644 $icons = array();
2645 if ($this->use_standard_editing_ui()) {
2646 $linkparams = array('courseid' => $instance->courseid, 'id' => $instance->id, 'type' => $instance->enrol);
2647 $editlink = new moodle_url("/enrol/editinstance.php", $linkparams);
2648 $icons[] = $OUTPUT->action_icon($editlink, new pix_icon('t/edit', get_string('edit'), 'core',
2649 array('class' => 'iconsmall')));
2650 }
2651 return $icons;
df997f84
PS
2652 }
2653
2654 /**
2655 * Reads version.php and determines if it is necessary
2656 * to execute the cron job now.
2657 * @return bool
2658 */
2659 public function is_cron_required() {
2660 global $CFG;
2661
2662 $name = $this->get_name();
2663 $versionfile = "$CFG->dirroot/enrol/$name/version.php";
365a5941 2664 $plugin = new stdClass();
df997f84
PS
2665 include($versionfile);
2666 if (empty($plugin->cron)) {
2667 return false;
2668 }
2669 $lastexecuted = $this->get_config('lastcron', 0);
2670 if ($lastexecuted + $plugin->cron < time()) {
2671 return true;
2672 } else {
2673 return false;
2674 }
2675 }
2676
2677 /**
2678 * Called for all enabled enrol plugins that returned true from is_cron_required().
2679 * @return void
2680 */
2681 public function cron() {
2682 }
2683
2684 /**
2685 * Called when user is about to be deleted
2686 * @param object $user
2687 * @return void
2688 */
2689 public function user_delete($user) {
2690 global $DB;
2691
2692 $sql = "SELECT e.*
2693 FROM {enrol} e
45fb2cf8
PS
2694 JOIN {user_enrolments} ue ON (ue.enrolid = e.id)
2695 WHERE e.enrol = :name AND ue.userid = :userid";
df997f84
PS
2696 $params = array('name'=>$this->get_name(), 'userid'=>$user->id);
2697
45fb2cf8 2698 $rs = $DB->get_recordset_sql($sql, $params);
df997f84
PS
2699 foreach($rs as $instance) {
2700 $this->unenrol_user($instance, $user->id);
2701 }
2702 $rs->close();
2703 }
df997f84 2704
b69ca6be
SH
2705 /**
2706 * Returns an enrol_user_button that takes the user to a page where they are able to
2707 * enrol users into the managers course through this plugin.
2708 *
2709 * Optional: If the plugin supports manual enrolments it can choose to override this
2710 * otherwise it shouldn't
2711 *
2712 * @param course_enrolment_manager $manager
2713 * @return enrol_user_button|false
2714 */
2715 public function get_manual_enrol_button(course_enrolment_manager $manager) {
2716 return false;
2717 }
291215f4
SH
2718
2719 /**
2720 * Gets an array of the user enrolment actions
2721 *
2722 * @param course_enrolment_manager $manager
2723 * @param stdClass $ue
2724 * @return array An array of user_enrolment_actions
2725 */
2726 public function get_user_enrolment_actions(course_enrolment_manager $manager, $ue) {
4047fccb
JP
2727 $actions = [];
2728 $context = $manager->get_context();
2729 $instance = $ue->enrolmentinstance;
2730 $params = $manager->get_moodlepage()->url->params();
2731 $params['ue'] = $ue->id;
2732
2733 // Edit enrolment action.
2734 if ($this->allow_manage($instance) && has_capability("enrol/{$instance->enrol}:manage", $context)) {
2735 $title = get_string('editenrolment', 'enrol');
2736 $icon = new pix_icon('t/edit', $title);
2737 $url = new moodle_url('/enrol/editenrolment.php', $params);
2738 $actionparams = [
2739 'class' => 'editenrollink',
2740 'rel' => $ue->id,
2741 'data-action' => ENROL_ACTION_EDIT
2742 ];
2743 $actions[] = new user_enrolment_action($icon, $title, $url, $actionparams);
2744 }
2745
2746 // Unenrol action.
2747 if ($this->allow_unenrol_user($instance, $ue) && has_capability("enrol/{$instance->enrol}:unenrol", $context)) {
2748 $title = get_string('unenrol', 'enrol');
2749 $icon = new pix_icon('t/delete', $title);
2750 $url = new moodle_url('/enrol/unenroluser.php', $params);
2751 $actionparams = [
2752 'class' => 'unenrollink',
2753 'rel' => $ue->id,
2754 'data-action' => ENROL_ACTION_UNENROL
2755 ];
2756 $actions[] = new user_enrolment_action($icon, $title, $url, $actionparams);
2757 }
2758 return $actions;
291215f4 2759 }
75ee207b
SH
2760
2761 /**
2762 * Returns true if the plugin has one or more bulk operations that can be performed on
2763 * user enrolments.
2764 *
f20edd52 2765 * @param course_enrolment_manager $manager
75ee207b
SH
2766 * @return bool
2767 */
f20edd52 2768 public function has_bulk_operations(course_enrolment_manager $manager) {
75ee207b
SH
2769 return false;
2770 }
2771
2772 /**
2773 * Return an array of enrol_bulk_enrolment_operation objects that define
2774 * the bulk actions that can be performed on user enrolments by the plugin.
2775 *
f20edd52 2776 * @param course_enrolment_manager $manager
75ee207b
SH
2777 * @return array
2778 */
f20edd52 2779 public function get_bulk_operations(course_enrolment_manager $manager) {
75ee207b
SH
2780 return array();
2781 }
7a7b8a1f 2782
d8f22c49
PS
2783 /**
2784 * Do any enrolments need expiration processing.
2785 *
2786 * Plugins that want to call this functionality must implement 'expiredaction' config setting.
2787 *
2788 * @param progress_trace $trace
2789 * @param int $courseid one course, empty mean all
2790 * @return bool true if any data processed, false if not
2791 */
2792 public function process_expirations(progress_trace $trace, $courseid = null) {
2793 global $DB;
2794
2795 $name = $this->get_name();
2796 if (!enrol_is_enabled($name)) {
2797 $trace->finished();
2798 return false;
2799 }
2800
2801 $processed = false;
2802 $params = array();
2803 $coursesql = "";
2804 if ($courseid) {
2805 $coursesql = "AND e.courseid = :courseid";
2806 }
2807
2808 // Deal with expired accounts.
2809 $action = $this->get_config('expiredaction', ENROL_EXT_REMOVED_KEEP);
2810
2811 if ($action == ENROL_EXT_REMOVED_UNENROL) {
2812 $instances = array();
2813 $sql = "SELECT ue.*, e.courseid, c.id AS contextid
2814 FROM {user_enrolments} ue
2815 JOIN {enrol} e ON (e.id = ue.enrolid AND e.enrol = :enrol)
2816 JOIN {context} c ON (c.instanceid = e.courseid AND c.contextlevel = :courselevel)
2817 WHERE ue.timeend > 0 AND ue.timeend < :now $coursesql";
2818 $params = array('now'=>time(), 'courselevel'=>CONTEXT_COURSE, 'enrol'=>$name, 'courseid'=>$courseid);
2819
2820 $rs = $DB->get_recordset_sql($sql, $params);
2821 foreach ($rs as $ue) {
2822 if (!$processed) {
2823 $trace->output("Starting processing of enrol_$name expirations...");
2824 $processed = true;
2825 }
2826 if (empty($instances[$ue->enrolid])) {
2827 $instances[$ue->enrolid] = $DB->get_record('enrol', array('id'=>$ue->enrolid));
2828 }
2829 $instance = $instances[$ue->enrolid];
2830 if (!$this->roles_protected()) {
2831 // Let's just guess what extra roles are supposed to be removed.
2832 if ($instance->roleid) {
2833 role_unassign($instance->roleid, $ue->userid, $ue->contextid);
2834 }
2835 }
2836 // The unenrol cleans up all subcontexts if this is the only course enrolment for this user.
2837 $this->unenrol_user($instance, $ue->userid);
2838 $trace->output("Unenrolling expired user $ue->userid from course $instance->courseid", 1);
2839 }
2840 $rs->close();
2841 unset($instances);
2842
2843 } else if ($action == ENROL_EXT_REMOVED_SUSPENDNOROLES or $action == ENROL_EXT_REMOVED_SUSPEND) {
2844 $instances = array();
2845 $sql = "SELECT ue.*, e.courseid, c.id AS contextid
2846 FROM {user_enrolments} ue
2847 JOIN {enrol} e ON (e.id = ue.enrolid AND e.enrol = :enrol)
2848 JOIN {context} c ON (c.instanceid = e.courseid AND c.contextlevel = :courselevel)
2849 WHERE ue.timeend > 0 AND ue.timeend < :now
2850 AND ue.status = :useractive $coursesql";
2851 $params = array('now'=>time(), 'courselevel'=>CONTEXT_COURSE, 'useractive'=>ENROL_USER_ACTIVE, 'enrol'=>$name, 'courseid'=>$courseid);
2852 $rs = $DB->get_recordset_sql($sql, $params);
2853 foreach ($rs as $ue) {
2854 if (!$processed) {
2855 $trace->output("Starting processing of enrol_$name expirations...");
2856 $processed = true;
2857 }
2858 if (empty($instances[$ue->enrolid])) {
2859 $instances[$ue->enrolid] = $DB->get_record('enrol', array('id'=>$ue->enrolid));
2860 }
2861 $instance = $instances[$ue->enrolid];
2862
2863 if ($action == ENROL_EXT_REMOVED_SUSPENDNOROLES) {
2864 if (!$this->roles_protected()) {
2865 // Let's just guess what roles should be removed.
2866 $count = $DB->count_records('role_assignments', array('userid'=>$ue->userid, 'contextid'=>$ue->contextid));
2867 if ($count == 1) {
2868 role_unassign_all(array('userid'=>$ue->userid, 'contextid'=>$ue->contextid, 'component'=>'', 'itemid'=>0));
2869
2870 } else if ($count > 1 and $instance->roleid) {
2871 role_unassign($instance->roleid, $ue->userid, $ue->contextid, '', 0);
2872 }
2873 }
2874 // In any case remove all roles that belong to this instance and user.
2875 role_unassign_all(array('userid'=>$ue->userid, 'contextid'=>$ue->contextid, 'component'=>'enrol_'.$name, 'itemid'=>$instance->id), true);
2876 // Final cleanup of subcontexts if there are no more course roles.
2877 if (0 == $DB->count_records('role_assignments', array('userid'=>$ue->userid, 'contextid'=>$ue->contextid))) {
2878 role_unassign_all(array('userid'=>$ue->userid, 'contextid'=>$ue->contextid, 'component'=>'', 'itemid'=>0), true);
2879 }
2880 }
2881
2882 $this->update_user_enrol($instance, $ue->userid, ENROL_USER_SUSPENDED);
2883 $trace->output("Suspending expired user $ue->userid in course $instance->courseid", 1);
2884 }
2885 $rs->close();
2886 unset($instances);
2887
2888 } else {
2889 // ENROL_EXT_REMOVED_KEEP means no changes.
2890 }
2891
2892 if ($processed) {
2893 $trace->output("...finished processing of enrol_$name expirations");
2894 } else {
2895 $trace->output("No expired enrol_$name enrolments detected");
2896 }
2897 $trace->finished();
2898
2899 return $processed;
2900 }
2901
8c04252c
PS
2902 /**
2903 * Send expiry notifications.
2904 *
2905 * Plugin that wants to have expiry notification MUST implement following:
2906 * - expirynotifyhour plugin setting,
2907 * - configuration options in instance edit form (expirynotify, notifyall and expirythreshold),
2908 * - notification strings (expirymessageenrollersubject, expirymessageenrollerbody,
2909 * expirymessageenrolledsubject and expirymessageenrolledbody),
2910 * - expiry_notification provider in db/messages.php,
2911 * - upgrade code that sets default thresholds for existing courses (should be 1 day),
2912 * - something that calls this method, such as cron.
2913 *
5d549ffc 2914 * @param progress_trace $trace (accepts bool for backwards compatibility only)
8c04252c 2915 */
5d549ffc 2916 public function send_expiry_notifications($trace) {
8c04252c
PS
2917 global $DB, $CFG;
2918
d8f22c49
PS
2919 $name = $this->get_name();
2920 if (!enrol_is_enabled($name)) {
2921 $trace->finished();
2922 return;
2923 }
2924
8c04252c
PS
2925 // Unfortunately this may take a long time, it should not be interrupted,
2926 // otherwise users get duplicate notification.
2927
3ef7279f 2928 core_php_time_limit::raise();
8c04252c
PS
2929 raise_memory_limit(MEMORY_HUGE);
2930
8c04252c
PS
2931
2932 $expirynotifylast = $this->get_config('expirynotifylast', 0);
2933 $expirynotifyhour = $this->get_config('expirynotifyhour');
2934 if (is_null($expirynotifyhour)) {
2935 debugging("send_expiry_notifications() in $name enrolment plugin needs expirynotifyhour setting");
5d549ffc 2936 $trace->finished();
8c04252c
PS
2937 return;
2938 }
2939
5d549ffc
PS
2940 if (!($trace instanceof progress_trace)) {
2941 $trace = $trace ? new text_progress_trace() : new null_progress_trace();
2942 debugging('enrol_plugin::send_expiry_notifications() now expects progress_trace instance as parameter!', DEBUG_DEVELOPER);
2943 }
2944
8c04252c
PS
2945 $timenow = time();
2946 $notifytime = usergetmidnight($timenow, $CFG->timezone) + ($expirynotifyhour * 3600);
2947
2948 if ($expirynotifylast > $notifytime) {
5d549ffc
PS
2949 $trace->output($name.' enrolment expiry notifications were already sent today at '.userdate($expirynotifylast, '', $CFG->timezone).'.');
2950 $trace->finished();
8c04252c 2951 return;
5d549ffc 2952
8c04252c 2953 } else if ($timenow < $notifytime) {
5d549ffc
PS
2954 $trace->output($name.' enrolment expiry notifications will be sent at '.userdate($notifytime, '', $CFG->timezone).'.');
2955 $trace->finished();
8c04252c
PS
2956 return;
2957 }
2958
5d549ffc 2959 $trace->output('Processing '.$name.' enrolment expiration notifications...');
8c04252c
PS
2960
2961 // Notify users responsible for enrolment once every day.
2962 $sql = "SELECT ue.*, e.expirynotify, e.notifyall, e.expirythreshold, e.courseid, c.fullname
2963 FROM {user_enrolments} ue
2964 JOIN {enrol} e ON (e.id = ue.enrolid AND e.enrol = :name AND e.expirynotify > 0 AND e.status = :enabled)
2965 JOIN {course} c ON (c.id = e.courseid)
2966 JOIN {user} u ON (u.id = ue.userid AND u.deleted = 0 AND u.suspended = 0)
2967 WHERE ue.status = :active AND ue.timeend > 0 AND ue.timeend > :now1 AND ue.timeend < (e.expirythreshold + :now2)
2968 ORDER BY ue.enrolid ASC, u.lastname ASC, u.firstname ASC, u.id ASC";
2969 $params = array('enabled'=>ENROL_INSTANCE_ENABLED, 'active'=>ENROL_USER_ACTIVE, 'now1'=>$timenow, 'now2'=>$timenow, 'name'=>$name);
2970
2971 $rs = $DB->get_recordset_sql($sql, $params);
2972
2973 $lastenrollid = 0;
2974 $users = array();
2975
2976 foreach($rs as $ue) {
2977 if ($lastenrollid and $lastenrollid != $ue->enrolid) {
5d549ffc 2978 $this->notify_expiry_enroller($lastenrollid, $users, $trace);
8c04252c
PS
2979 $users = array();
2980 }
2981 $lastenrollid = $ue->enrolid;
2982
2983 $enroller = $this->get_enroller($ue->enrolid);
2984 $context = context_course::instance($ue->courseid);
2985
2986 $user = $DB->get_record('user', array('id'=>$ue->userid));
2987
2988 $users[] = array('fullname'=>fullname($user, has_capability('moodle/site:viewfullnames', $context, $enroller)), 'timeend'=>$ue->timeend);
2989
2990 if (!$ue->notifyall) {
2991 continue;
2992 }
2993
2994 if ($ue->timeend - $ue->expirythreshold + 86400 < $timenow) {
2995 // Notify enrolled users only once at the start of the threshold.
5d549ffc 2996 $trace->output("user $ue->userid was already notified that enrolment in course $ue->courseid expires on ".userdate($ue->timeend, '', $CFG->timezone), 1);
8c04252c
PS
2997 continue;
2998 }
2999
5d549ffc 3000 $this->notify_expiry_enrolled($user, $ue, $trace);
8c04252c
PS
3001 }
3002 $rs->close();
3003
3004 if ($lastenrollid and $users) {
5d549ffc 3005 $this->notify_expiry_enroller($lastenrollid, $users, $trace);
8c04252c
PS
3006 }
3007
5d549ffc
PS
3008 $trace->output('...notification processing finished.');
3009 $trace->finished();
3010
8c04252c
PS
3011 $this->set_config('expirynotifylast', $timenow);
3012 }
3013
3014 /**
3015 * Returns the user who is responsible for enrolments for given instance.
3016 *
3017 * Override if plugin knows anybody better than admin.
3018 *
3019 * @param int $instanceid enrolment instance id
3020 * @return stdClass user record
3021 */
3022 protected function get_enroller($instanceid) {
3023 return get_admin();
3024 }
3025
3026 /**
3027 * Notify user about incoming expiration of their enrolment,
3028 * it is called only if notification of enrolled users (aka students) is enabled in course.
3029 *
3030 * This is executed only once for each expiring enrolment right
3031 * at the start of the expiration threshold.
3032 *
3033 * @param stdClass $user
3034 * @param stdClass $ue
5d549ffc 3035 * @param progress_trace $trace
8c04252c 3036 */
5d549ffc 3037 protected function notify_expiry_enrolled($user, $ue, progress_trace $trace) {
c484af5a 3038 global $CFG;
8c04252c
PS
3039
3040 $name = $this->get_name();
3041
c484af5a 3042 $oldforcelang = force_current_language($user->lang);
8c04252c
PS
3043
3044 $enroller = $this->get_enroller($ue->enrolid);
3045 $context = context_course::instance($ue->courseid);
3046
3047 $a = new stdClass();
3048 $a->course = format_string($ue->fullname, true, array('context'=>$context));
3049 $a->user = fullname($user, true);
3050 $a->timeend = userdate($ue->timeend, '', $user->timezone);
3051 $a->enroller = fullname($enroller, has_capability('moodle/site:viewfullnames', $context, $user));
3052
3053 $subject = get_string('expirymessageenrolledsubject', 'enrol_'.$name, $a);
3054 $body = get_string('expirymessageenrolledbody', 'enrol_'.$name, $a);
3055
cc350fd9 3056 $message = new \core\message\message();
880fc15b 3057 $message->courseid = $ue->courseid;
8c04252c
PS
3058 $message->notification = 1;
3059 $message->component = 'enrol_'.$name;
3060 $message->name = 'expiry_notification';
3061 $message->userfrom = $enroller;
3062 $message->userto = $user;
3063 $message->subject = $subject;
3064 $message->fullmessage = $body;
3065 $message->fullmessageformat = FORMAT_MARKDOWN;
3066 $message->fullmessagehtml = markdown_to_html($body);
3067 $message->smallmessage = $subject;
3068 $message->contexturlname = $a->course;
3069 $message->contexturl = (string)new moodle_url('/course/view.php', array('id'=>$ue->courseid));
3070
3071 if (message_send($message)) {
5d549ffc 3072 $trace->output("notifying user $ue->userid that enrolment in course $ue->courseid expires on ".userdate($ue->timeend, '', $CFG->timezone), 1);
8c04252c 3073 } else {
5d549ffc 3074 $trace->output("error notifying user $ue->userid that enrolment in course $ue->courseid expires on ".userdate($ue->timeend, '', $CFG->timezone), 1);
8c04252c
PS
3075 }
3076
c484af5a 3077 force_current_language($oldforcelang);
8c04252c
PS
3078 }
3079
3080 /**
3081 * Notify person responsible for enrolments that some user enrolments will be expired soon,
3082 * it is called only if notification of enrollers (aka teachers) is enabled in course.
3083 *
3084 * This is called repeatedly every day for each course if there are any pending expiration
3085 * in the expiration threshold.
3086 *
3087 * @param int $eid
3088 * @param array $users
5d549ffc 3089 * @param progress_trace $trace
8c04252c 3090 */
5d549ffc 3091 protected function notify_expiry_enroller($eid, $users, progress_trace $trace) {
c484af5a 3092 global $DB;
8c04252c
PS
3093
3094 $name = $this->get_name();
3095
3096 $instance = $DB->get_record('enrol', array('id'=>$eid, 'enrol'=>$name));
3097 $context = context_course::instance($instance->courseid);
3098 $course = $DB->get_record('course', array('id'=>$instance->courseid));
3099
3100 $enroller = $this->get_enroller($instance->id);
3101 $admin = get_admin();
3102
c484af5a 3103 $oldforcelang = force_current_language($enroller->lang);
8c04252c
PS
3104
3105 foreach($users as $key=>$info) {
3106 $users[$key] = '* '.$info['fullname'].' - '.userdate($info['timeend'], '', $enroller->timezone);
3107 }
3108
3109 $a = new stdClass();
3110 $a->course = format_string($course->fullname, true, array('context'=>$context));
3111 $a->threshold = get_string('numdays', '', $instance->expirythreshold / (60*60*24));
3112 $a->users = implode("\n", $users);
4b50765b 3113 $a->extendurl = (string)new moodle_url('/user/index.php', array('id'=>$instance->courseid));
8c04252c
PS
3114
3115 $subject = get_string('expirymessageenrollersubject', 'enrol_'.$name, $a);
3116 $body = get_string('expirymessageenrollerbody', 'enrol_'.$name, $a);
3117
cc350fd9 3118 $message = new \core\message\message();
880fc15b 3119 $message->courseid = $course->id;
8c04252c
PS
3120 $message->notification = 1;
3121 $message->component = 'enrol_'.$name;
3122 $message->name = 'expiry_notification';
3123 $message->userfrom = $admin;
3124 $message->userto = $enroller;
3125 $message->subject = $subject;
3126 $message->fullmessage = $body;
3127 $message->fullmessageformat = FORMAT_MARKDOWN;
3128 $message->fullmessagehtml = markdown_to_html($body);
3129 $message->smallmessage = $subject;
3130 $message->contexturlname = $a->course;
3131 $message->contexturl = $a->extendurl;
3132
3133 if (message_send($message)) {
5d549ffc 3134 $trace->output("notifying user $enroller->id about all expiring $name enrolments in course $instance->courseid", 1);
8c04252c 3135 } else {
5d549ffc 3136 $trace->output("error notifying user $enroller->id about all expiring $name enrolments in course $instance->courseid", 1);
8c04252c
PS
3137 }
3138
c484af5a 3139 force_current_language($oldforcelang);
8c04252c
PS
3140 }
3141
f6199295
MP
3142 /**
3143 * Backup execution step hook to annotate custom fields.
3144 *
3145 * @param backup_enrolments_execution_step $step
3146 * @param stdClass $enrol
3147 */
3148 public function backup_annotate_custom_fields(backup_enrolments_execution_step $step, stdClass $enrol) {
3149 // Override as necessary to annotate custom fields in the enrol table.
3150 }
3151
7a7b8a1f
PS
3152 /**
3153 * Automatic enrol sync executed during restore.
3154 * Useful for automatic sync by course->idnumber or course category.
3155 * @param stdClass $course course record
3156 */
3157 public function restore_sync_course($course) {
3158 // Override if necessary.
3159 }
3160
3161 /**
3162 * Restore instance and map settings.
3163 *
3164 * @param restore_enrolments_structure_step $step
3165 * @param stdClass $data
3166 * @param stdClass $course
3167 * @param int $oldid
3168 */
3169 public function restore_instance(restore_enrolments_structure_step $step, stdClass $data, $course, $oldid) {
3170 // Do not call this from overridden methods, restore and set new id there.
3171 $step->set_mapping('enrol', $oldid, 0);
3172 }
3173
3174 /**
3175 * Restore user enrolment.
3176 *
3177 * @param restore_enrolments_structure_step $step
3178 * @param stdClass $data
3179 * @param stdClass $instance
3180 * @param int $oldinstancestatus
3181 * @param int $userid
3182 */
3183 public function restore_user_enrolment(restore_enrolments_structure_step $step, $data, $instance, $userid, $oldinstancestatus) {
3184 // Override as necessary if plugin supports restore of enrolments.
3185 }
3186
3187 /**
3188 * Restore role assignment.
3189 *
3190 * @param stdClass $instance
3191 * @param int $roleid
3192 * @param int $userid
3193 * @param int $contextid
3194 */
3195 public function restore_role_assignment($instance, $roleid, $userid, $contextid) {
3196 // No role assignment by default, override if necessary.
3197 }
7881024e
PS
3198
3199 /**
3200 * Restore user group membership.
3201 * @param stdClass $instance
3202 * @param int $groupid
3203 * @param int $userid
3204 */
3205 public function restore_group_member($instance, $groupid, $userid) {
3206 // Implement if you want to restore protected group memberships,
3207 // usually this is not necessary because plugins should be able to recreate the memberships automatically.
3208 }
60010fd6
DW
3209
3210 /**
3211 * Returns defaults for new instances.
3212 * @since Moodle 3.1
3213 * @return array
3214 */
3215 public function get_instance_defaults() {
3216 return array();
3217 }
3218
3219 /**
3220 * Validate a list of parameter names and types.
3221 * @since Moodle 3.1
3222 *
3223 * @param array $data array of ("fieldname"=>value) of submitted data
3224 * @param array $rules array of ("fieldname"=>PARAM_X types - or "fieldname"=>array( list of valid options )
3225 * @return array of "element_name"=>"error_description" if there are errors,
3226 * or an empty array if everything is OK.
3227 */
3228 public function validate_param_types($data, $rules) {
3229 $errors = array();
3230 $invalidstr = get_string('invaliddata', 'error');
3231 foreach ($rules as $fieldname => $rule) {
3232 if (is_array($rule)) {
3233 if (!in_array($data[$fieldname], $rule)) {
3234 $errors[$fieldname] = $invalidstr;
3235 }
3236 } else {
3237 if ($data[$fieldname] != clean_param($data[$fieldname], $rule)) {
3238 $errors[$fieldname] = $invalidstr;
3239 }
3240 }
3241 }
3242 return $errors;
3243 }
4b715423 3244}