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