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