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