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