See Release Notes
Long Term Support Release
Differences Between: [Versions 39 and 400] [Versions 39 and 401] [Versions 39 and 402] [Versions 39 and 403]
1 <?php 2 // This file is part of Moodle - http://moodle.org/ 3 // 4 // Moodle is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // Moodle is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU General Public License for more details. 13 // 14 // You should have received a copy of the GNU General Public License 15 // along with Moodle. If not, see <http://www.gnu.org/licenses/>. 16 17 /** 18 * Manual enrolment plugin main library file. 19 * 20 * @package enrol_manual 21 * @copyright 2010 Petr Skoda {@link http://skodak.org} 22 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 */ 24 25 defined('MOODLE_INTERNAL') || die(); 26 27 class enrol_manual_plugin extends enrol_plugin { 28 29 protected $lasternoller = null; 30 protected $lasternollerinstanceid = 0; 31 32 public function roles_protected() { 33 // Users may tweak the roles later. 34 return false; 35 } 36 37 public function allow_enrol(stdClass $instance) { 38 // Users with enrol cap may unenrol other users manually manually. 39 return true; 40 } 41 42 public function allow_unenrol(stdClass $instance) { 43 // Users with unenrol cap may unenrol other users manually manually. 44 return true; 45 } 46 47 public function allow_manage(stdClass $instance) { 48 // Users with manage cap may tweak period and status. 49 return true; 50 } 51 52 /** 53 * Returns link to manual enrol UI if exists. 54 * Does the access control tests automatically. 55 * 56 * @param stdClass $instance 57 * @return moodle_url 58 */ 59 public function get_manual_enrol_link($instance) { 60 $name = $this->get_name(); 61 if ($instance->enrol !== $name) { 62 throw new coding_exception('invalid enrol instance!'); 63 } 64 65 if (!enrol_is_enabled($name)) { 66 return NULL; 67 } 68 69 $context = context_course::instance($instance->courseid, MUST_EXIST); 70 71 if (!has_capability('enrol/manual:enrol', $context)) { 72 // Note: manage capability not used here because it is used for editing 73 // of existing enrolments which is not possible here. 74 return NULL; 75 } 76 77 return new moodle_url('/enrol/manual/manage.php', array('enrolid'=>$instance->id, 'id'=>$instance->courseid)); 78 } 79 80 /** 81 * Return true if we can add a new instance to this course. 82 * 83 * @param int $courseid 84 * @return boolean 85 */ 86 public function can_add_instance($courseid) { 87 global $DB; 88 89 $context = context_course::instance($courseid, MUST_EXIST); 90 if (!has_capability('moodle/course:enrolconfig', $context) or !has_capability('enrol/manual:config', $context)) { 91 return false; 92 } 93 94 if ($DB->record_exists('enrol', array('courseid'=>$courseid, 'enrol'=>'manual'))) { 95 // Multiple instances not supported. 96 return false; 97 } 98 99 return true; 100 } 101 102 /** 103 * Returns edit icons for the page with list of instances. 104 * @param stdClass $instance 105 * @return array 106 */ 107 public function get_action_icons(stdClass $instance) { 108 global $OUTPUT; 109 110 $context = context_course::instance($instance->courseid); 111 112 $icons = array(); 113 if (has_capability('enrol/manual:enrol', $context) or has_capability('enrol/manual:unenrol', $context)) { 114 $managelink = new moodle_url("/enrol/manual/manage.php", array('enrolid'=>$instance->id)); 115 $icons[] = $OUTPUT->action_icon($managelink, new pix_icon('t/enrolusers', get_string('enrolusers', 'enrol_manual'), 'core', array('class'=>'iconsmall'))); 116 } 117 $parenticons = parent::get_action_icons($instance); 118 $icons = array_merge($icons, $parenticons); 119 120 return $icons; 121 } 122 123 /** 124 * Add new instance of enrol plugin with default settings. 125 * @param stdClass $course 126 * @return int id of new instance, null if can not be created 127 */ 128 public function add_default_instance($course) { 129 $expirynotify = $this->get_config('expirynotify', 0); 130 if ($expirynotify == 2) { 131 $expirynotify = 1; 132 $notifyall = 1; 133 } else { 134 $notifyall = 0; 135 } 136 $fields = array( 137 'status' => $this->get_config('status'), 138 'roleid' => $this->get_config('roleid', 0), 139 'enrolperiod' => $this->get_config('enrolperiod', 0), 140 'expirynotify' => $expirynotify, 141 'notifyall' => $notifyall, 142 'expirythreshold' => $this->get_config('expirythreshold', 86400), 143 ); 144 return $this->add_instance($course, $fields); 145 } 146 147 /** 148 * Add new instance of enrol plugin. 149 * @param stdClass $course 150 * @param array instance fields 151 * @return int id of new instance, null if can not be created 152 */ 153 public function add_instance($course, array $fields = NULL) { 154 global $DB; 155 156 if ($DB->record_exists('enrol', array('courseid'=>$course->id, 'enrol'=>'manual'))) { 157 // only one instance allowed, sorry 158 return NULL; 159 } 160 161 return parent::add_instance($course, $fields); 162 } 163 164 /** 165 * Update instance of enrol plugin. 166 * @param stdClass $instance 167 * @param stdClass $data modified instance fields 168 * @return boolean 169 */ 170 public function update_instance($instance, $data) { 171 global $DB; 172 173 // Delete all other instances, leaving only one. 174 if ($instances = $DB->get_records('enrol', array('courseid' => $instance->courseid, 'enrol' => 'manual'), 'id ASC')) { 175 foreach ($instances as $anotherinstance) { 176 if ($anotherinstance->id != $instance->id) { 177 $this->delete_instance($anotherinstance); 178 } 179 } 180 } 181 return parent::update_instance($instance, $data); 182 } 183 184 /** 185 * Returns a button to manually enrol users through the manual enrolment plugin. 186 * 187 * By default the first manual enrolment plugin instance available in the course is used. 188 * If no manual enrolment instances exist within the course then false is returned. 189 * 190 * This function also adds a quickenrolment JS ui to the page so that users can be enrolled 191 * via AJAX. 192 * 193 * @param course_enrolment_manager $manager 194 * @return enrol_user_button 195 */ 196 public function get_manual_enrol_button(course_enrolment_manager $manager) { 197 global $CFG, $PAGE; 198 require_once($CFG->dirroot.'/cohort/lib.php'); 199 200 static $called = false; 201 202 $instance = null; 203 foreach ($manager->get_enrolment_instances() as $tempinstance) { 204 if ($tempinstance->enrol == 'manual') { 205 if ($instance === null) { 206 $instance = $tempinstance; 207 } 208 } 209 } 210 if (empty($instance)) { 211 return false; 212 } 213 214 $link = $this->get_manual_enrol_link($instance); 215 if (!$link) { 216 return false; 217 } 218 219 $button = new enrol_user_button($link, get_string('enrolusers', 'enrol_manual'), 'get'); 220 $button->class .= ' enrol_manual_plugin'; 221 222 $context = context_course::instance($instance->courseid); 223 $arguments = array('contextid' => $context->id); 224 225 if (!$called) { 226 $called = true; 227 // Calling the following more than once will cause unexpected results. 228 $PAGE->requires->js_call_amd('enrol_manual/quickenrolment', 'init', array($arguments)); 229 } 230 231 return $button; 232 } 233 234 /** 235 * Sync all meta course links. 236 * 237 * @param progress_trace $trace 238 * @param int $courseid one course, empty mean all 239 * @return int 0 means ok, 1 means error, 2 means plugin disabled 240 */ 241 public function sync(progress_trace $trace, $courseid = null) { 242 global $DB; 243 244 if (!enrol_is_enabled('manual')) { 245 $trace->finished(); 246 return 2; 247 } 248 249 // Unfortunately this may take a long time, execution can be interrupted safely here. 250 core_php_time_limit::raise(); 251 raise_memory_limit(MEMORY_HUGE); 252 253 $trace->output('Verifying manual enrolment expiration...'); 254 255 $params = array('now'=>time(), 'useractive'=>ENROL_USER_ACTIVE, 'courselevel'=>CONTEXT_COURSE); 256 $coursesql = ""; 257 if ($courseid) { 258 $coursesql = "AND e.courseid = :courseid"; 259 $params['courseid'] = $courseid; 260 } 261 262 // Deal with expired accounts. 263 $action = $this->get_config('expiredaction', ENROL_EXT_REMOVED_KEEP); 264 265 if ($action == ENROL_EXT_REMOVED_UNENROL) { 266 $instances = array(); 267 $sql = "SELECT ue.*, e.courseid, c.id AS contextid 268 FROM {user_enrolments} ue 269 JOIN {enrol} e ON (e.id = ue.enrolid AND e.enrol = 'manual') 270 JOIN {context} c ON (c.instanceid = e.courseid AND c.contextlevel = :courselevel) 271 WHERE ue.timeend > 0 AND ue.timeend < :now 272 $coursesql"; 273 $rs = $DB->get_recordset_sql($sql, $params); 274 foreach ($rs as $ue) { 275 if (empty($instances[$ue->enrolid])) { 276 $instances[$ue->enrolid] = $DB->get_record('enrol', array('id'=>$ue->enrolid)); 277 } 278 $instance = $instances[$ue->enrolid]; 279 // Always remove all manually assigned roles here, this may break enrol_self roles but we do not want hardcoded hacks here. 280 role_unassign_all(array('userid'=>$ue->userid, 'contextid'=>$ue->contextid, 'component'=>'', 'itemid'=>0), true); 281 $this->unenrol_user($instance, $ue->userid); 282 $trace->output("unenrolling expired user $ue->userid from course $instance->courseid", 1); 283 } 284 $rs->close(); 285 unset($instances); 286 287 } else if ($action == ENROL_EXT_REMOVED_SUSPENDNOROLES or $action == ENROL_EXT_REMOVED_SUSPEND) { 288 $instances = array(); 289 $sql = "SELECT ue.*, e.courseid, c.id AS contextid 290 FROM {user_enrolments} ue 291 JOIN {enrol} e ON (e.id = ue.enrolid AND e.enrol = 'manual') 292 JOIN {context} c ON (c.instanceid = e.courseid AND c.contextlevel = :courselevel) 293 WHERE ue.timeend > 0 AND ue.timeend < :now 294 AND ue.status = :useractive 295 $coursesql"; 296 $rs = $DB->get_recordset_sql($sql, $params); 297 foreach ($rs as $ue) { 298 if (empty($instances[$ue->enrolid])) { 299 $instances[$ue->enrolid] = $DB->get_record('enrol', array('id'=>$ue->enrolid)); 300 } 301 $instance = $instances[$ue->enrolid]; 302 if ($action == ENROL_EXT_REMOVED_SUSPENDNOROLES) { 303 // Remove all manually assigned roles here, this may break enrol_self roles but we do not want hardcoded hacks here. 304 role_unassign_all(array('userid'=>$ue->userid, 'contextid'=>$ue->contextid, 'component'=>'', 'itemid'=>0), true); 305 $this->update_user_enrol($instance, $ue->userid, ENROL_USER_SUSPENDED); 306 $trace->output("suspending expired user $ue->userid in course $instance->courseid, roles unassigned", 1); 307 } else { 308 $this->update_user_enrol($instance, $ue->userid, ENROL_USER_SUSPENDED); 309 $trace->output("suspending expired user $ue->userid in course $instance->courseid, roles kept", 1); 310 } 311 } 312 $rs->close(); 313 unset($instances); 314 315 } else { 316 // ENROL_EXT_REMOVED_KEEP means no changes. 317 } 318 319 $trace->output('...manual enrolment updates finished.'); 320 $trace->finished(); 321 322 return 0; 323 } 324 325 /** 326 * Returns the user who is responsible for manual enrolments in given instance. 327 * 328 * Usually it is the first editing teacher - the person with "highest authority" 329 * as defined by sort_by_roleassignment_authority() having 'enrol/manual:manage' 330 * capability. 331 * 332 * @param int $instanceid enrolment instance id 333 * @return stdClass user record 334 */ 335 protected function get_enroller($instanceid) { 336 global $DB; 337 338 if ($this->lasternollerinstanceid == $instanceid and $this->lasternoller) { 339 return $this->lasternoller; 340 } 341 342 $instance = $DB->get_record('enrol', array('id'=>$instanceid, 'enrol'=>$this->get_name()), '*', MUST_EXIST); 343 $context = context_course::instance($instance->courseid); 344 345 if ($users = get_enrolled_users($context, 'enrol/manual:manage')) { 346 $users = sort_by_roleassignment_authority($users, $context); 347 $this->lasternoller = reset($users); 348 unset($users); 349 } else { 350 $this->lasternoller = parent::get_enroller($instanceid); 351 } 352 353 $this->lasternollerinstanceid = $instanceid; 354 355 return $this->lasternoller; 356 } 357 358 /** 359 * The manual plugin has several bulk operations that can be performed. 360 * @param course_enrolment_manager $manager 361 * @return array 362 */ 363 public function get_bulk_operations(course_enrolment_manager $manager) { 364 global $CFG; 365 require_once($CFG->dirroot.'/enrol/manual/locallib.php'); 366 $context = $manager->get_context(); 367 $bulkoperations = array(); 368 if (has_capability("enrol/manual:manage", $context)) { 369 $bulkoperations['editselectedusers'] = new enrol_manual_editselectedusers_operation($manager, $this); 370 } 371 if (has_capability("enrol/manual:unenrol", $context)) { 372 $bulkoperations['deleteselectedusers'] = new enrol_manual_deleteselectedusers_operation($manager, $this); 373 } 374 return $bulkoperations; 375 } 376 377 /** 378 * Restore instance and map settings. 379 * 380 * @param restore_enrolments_structure_step $step 381 * @param stdClass $data 382 * @param stdClass $course 383 * @param int $oldid 384 */ 385 public function restore_instance(restore_enrolments_structure_step $step, stdClass $data, $course, $oldid) { 386 global $DB; 387 // There is only I manual enrol instance allowed per course. 388 if ($instances = $DB->get_records('enrol', array('courseid'=>$data->courseid, 'enrol'=>'manual'), 'id')) { 389 $instance = reset($instances); 390 $instanceid = $instance->id; 391 } else { 392 $instanceid = $this->add_instance($course, (array)$data); 393 } 394 $step->set_mapping('enrol', $oldid, $instanceid); 395 } 396 397 /** 398 * Restore user enrolment. 399 * 400 * @param restore_enrolments_structure_step $step 401 * @param stdClass $data 402 * @param stdClass $instance 403 * @param int $oldinstancestatus 404 * @param int $userid 405 */ 406 public function restore_user_enrolment(restore_enrolments_structure_step $step, $data, $instance, $userid, $oldinstancestatus) { 407 global $DB; 408 409 // Note: this is a bit tricky because other types may be converted to manual enrolments, 410 // and manual is restricted to one enrolment per user. 411 412 $ue = $DB->get_record('user_enrolments', array('enrolid'=>$instance->id, 'userid'=>$userid)); 413 $enrol = false; 414 if ($ue and $ue->status == ENROL_USER_ACTIVE) { 415 // We do not want to restrict current active enrolments, let's kind of merge the times only. 416 // This prevents some teacher lockouts too. 417 if ($data->status == ENROL_USER_ACTIVE) { 418 if ($data->timestart > $ue->timestart) { 419 $data->timestart = $ue->timestart; 420 $enrol = true; 421 } 422 423 if ($data->timeend == 0) { 424 if ($ue->timeend != 0) { 425 $enrol = true; 426 } 427 } else if ($ue->timeend == 0) { 428 $data->timeend = 0; 429 } else if ($data->timeend < $ue->timeend) { 430 $data->timeend = $ue->timeend; 431 $enrol = true; 432 } 433 } 434 } else { 435 if ($instance->status == ENROL_INSTANCE_ENABLED and $oldinstancestatus != ENROL_INSTANCE_ENABLED) { 436 // Make sure that user enrolments are not activated accidentally, 437 // we do it only here because it is not expected that enrolments are migrated to other plugins. 438 $data->status = ENROL_USER_SUSPENDED; 439 } 440 $enrol = true; 441 } 442 443 if ($enrol) { 444 $this->enrol_user($instance, $userid, null, $data->timestart, $data->timeend, $data->status); 445 } 446 } 447 448 /** 449 * Restore role assignment. 450 * 451 * @param stdClass $instance 452 * @param int $roleid 453 * @param int $userid 454 * @param int $contextid 455 */ 456 public function restore_role_assignment($instance, $roleid, $userid, $contextid) { 457 // This is necessary only because we may migrate other types to this instance, 458 // we do not use component in manual or self enrol. 459 role_assign($roleid, $userid, $contextid, '', 0); 460 } 461 462 /** 463 * Restore user group membership. 464 * @param stdClass $instance 465 * @param int $groupid 466 * @param int $userid 467 */ 468 public function restore_group_member($instance, $groupid, $userid) { 469 global $CFG; 470 require_once("$CFG->dirroot/group/lib.php"); 471 472 // This might be called when forcing restore as manual enrolments. 473 474 groups_add_member($groupid, $userid); 475 } 476 477 /** 478 * Is it possible to delete enrol instance via standard UI? 479 * 480 * @param object $instance 481 * @return bool 482 */ 483 public function can_delete_instance($instance) { 484 $context = context_course::instance($instance->courseid); 485 return has_capability('enrol/manual:config', $context); 486 } 487 488 /** 489 * Is it possible to hide/show enrol instance via standard UI? 490 * 491 * @param stdClass $instance 492 * @return bool 493 */ 494 public function can_hide_show_instance($instance) { 495 $context = context_course::instance($instance->courseid); 496 return has_capability('enrol/manual:config', $context); 497 } 498 499 /** 500 * Enrol all not enrolled cohort members into course via enrol instance. 501 * 502 * @param stdClass $instance 503 * @param int $cohortid 504 * @param int $roleid optional role id 505 * @param int $timestart 0 means unknown 506 * @param int $timeend 0 means forever 507 * @param int $status default to ENROL_USER_ACTIVE for new enrolments, no change by default in updates 508 * @param bool $recovergrades restore grade history 509 * @return int The number of enrolled cohort users 510 */ 511 public function enrol_cohort(stdClass $instance, $cohortid, $roleid = null, $timestart = 0, $timeend = 0, $status = null, $recovergrades = null) { 512 global $DB; 513 $context = context_course::instance($instance->courseid); 514 list($esql, $params) = get_enrolled_sql($context); 515 $sql = "SELECT cm.userid FROM {cohort_members} cm LEFT JOIN ($esql) u ON u.id = cm.userid ". 516 "WHERE cm.cohortid = :cohortid AND u.id IS NULL"; 517 $params['cohortid'] = $cohortid; 518 $members = $DB->get_fieldset_sql($sql, $params); 519 foreach ($members as $userid) { 520 $this->enrol_user($instance, $userid, $roleid, $timestart, $timeend, $status, $recovergrades); 521 } 522 return count($members); 523 } 524 525 /** 526 * We are a good plugin and don't invent our own UI/validation code path. 527 * 528 * @return boolean 529 */ 530 public function use_standard_editing_ui() { 531 return true; 532 } 533 534 /** 535 * Return an array of valid options for the status. 536 * 537 * @return array 538 */ 539 protected function get_status_options() { 540 $options = array(ENROL_INSTANCE_ENABLED => get_string('yes'), 541 ENROL_INSTANCE_DISABLED => get_string('no')); 542 return $options; 543 } 544 545 /** 546 * Return an array of valid options for the roleid. 547 * 548 * @param stdClass $instance 549 * @param context $context 550 * @return array 551 */ 552 protected function get_roleid_options($instance, $context) { 553 if ($instance->id) { 554 $roles = get_default_enrol_roles($context, $instance->roleid); 555 } else { 556 $roles = get_default_enrol_roles($context, $this->get_config('roleid')); 557 } 558 return $roles; 559 } 560 561 /** 562 * Return an array of valid options for the expirynotify. 563 * 564 * @return array 565 */ 566 protected function get_expirynotify_options() { 567 $options = array( 568 0 => get_string('no'), 569 1 => get_string('expirynotifyenroller', 'core_enrol'), 570 2 => get_string('expirynotifyall', 'core_enrol') 571 ); 572 return $options; 573 } 574 575 /** 576 * Add elements to the edit instance form. 577 * 578 * @param stdClass $instance 579 * @param MoodleQuickForm $mform 580 * @param context $context 581 * @return bool 582 */ 583 public function edit_instance_form($instance, MoodleQuickForm $mform, $context) { 584 585 $options = $this->get_status_options(); 586 $mform->addElement('select', 'status', get_string('status', 'enrol_manual'), $options); 587 $mform->addHelpButton('status', 'status', 'enrol_manual'); 588 $mform->setDefault('status', $this->get_config('status')); 589 590 $roles = $this->get_roleid_options($instance, $context); 591 $mform->addElement('select', 'roleid', get_string('defaultrole', 'role'), $roles); 592 $mform->setDefault('roleid', $this->get_config('roleid')); 593 594 $options = array('optional' => true, 'defaultunit' => 86400); 595 $mform->addElement('duration', 'enrolperiod', get_string('defaultperiod', 'enrol_manual'), $options); 596 $mform->setDefault('enrolperiod', $this->get_config('enrolperiod')); 597 $mform->addHelpButton('enrolperiod', 'defaultperiod', 'enrol_manual'); 598 599 $options = $this->get_expirynotify_options(); 600 $mform->addElement('select', 'expirynotify', get_string('expirynotify', 'core_enrol'), $options); 601 $mform->addHelpButton('expirynotify', 'expirynotify', 'core_enrol'); 602 603 $options = array('optional' => false, 'defaultunit' => 86400); 604 $mform->addElement('duration', 'expirythreshold', get_string('expirythreshold', 'core_enrol'), $options); 605 $mform->addHelpButton('expirythreshold', 'expirythreshold', 'core_enrol'); 606 $mform->disabledIf('expirythreshold', 'expirynotify', 'eq', 0); 607 608 if (enrol_accessing_via_instance($instance)) { 609 $warntext = get_string('instanceeditselfwarningtext', 'core_enrol'); 610 $mform->addElement('static', 'selfwarn', get_string('instanceeditselfwarning', 'core_enrol'), $warntext); 611 } 612 } 613 614 /** 615 * Perform custom validation of the data used to edit the instance. 616 * 617 * @param array $data array of ("fieldname"=>value) of submitted data 618 * @param array $files array of uploaded files "element_name"=>tmp_file_path 619 * @param object $instance The instance loaded from the DB 620 * @param context $context The context of the instance we are editing 621 * @return array of "element_name"=>"error_description" if there are errors, 622 * or an empty array if everything is OK. 623 * @return void 624 */ 625 public function edit_instance_validation($data, $files, $instance, $context) { 626 $errors = array(); 627 628 if ($data['expirynotify'] > 0 and $data['expirythreshold'] < 86400) { 629 $errors['expirythreshold'] = get_string('errorthresholdlow', 'core_enrol'); 630 } 631 632 $validstatus = array_keys($this->get_status_options()); 633 $validroles = array_keys($this->get_roleid_options($instance, $context)); 634 $validexpirynotify = array_keys($this->get_expirynotify_options()); 635 636 $tovalidate = array( 637 'status' => $validstatus, 638 'roleid' => $validroles, 639 'enrolperiod' => PARAM_INT, 640 'expirynotify' => $validexpirynotify, 641 'expirythreshold' => PARAM_INT 642 ); 643 644 $typeerrors = $this->validate_param_types($data, $tovalidate); 645 $errors = array_merge($errors, $typeerrors); 646 647 return $errors; 648 } 649 650 } 651 652 /** 653 * Serve the manual enrol users form as a fragment. 654 * 655 * @param array $args List of named arguments for the fragment loader. 656 * @return string 657 */ 658 function enrol_manual_output_fragment_enrol_users_form($args) { 659 $args = (object) $args; 660 $context = $args->context; 661 $o = ''; 662 663 require_capability('enrol/manual:enrol', $context); 664 $mform = new enrol_manual_enrol_users_form(null, $args); 665 666 ob_start(); 667 $mform->display(); 668 $o .= ob_get_contents(); 669 ob_end_clean(); 670 671 return $o; 672 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body