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