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