Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.3.x will end 7 October 2024 (12 months).
  • Bug fixes for security issues in 4.3.x will end 21 April 2025 (18 months).
  • PHP version: minimum PHP 8.0.0 Note: minimum PHP version has increased since Moodle 4.1. PHP 8.2.x is supported too.

Differences Between: [Versions 310 and 403] [Versions 311 and 403] [Versions 39 and 403] [Versions 400 and 403] [Versions 401 and 403] [Versions 402 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   * Guest access plugin.
  19   *
  20   * This plugin does not add any entries into the user_enrolments table,
  21   * the access control is granted on the fly via the tricks in require_login().
  22   *
  23   * @package    enrol_guest
  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  /**
  31   * Class enrol_guest_plugin
  32   *
  33   * @copyright  2010 Petr Skoda  {@link http://skodak.org}
  34   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  35   */
  36  class enrol_guest_plugin extends enrol_plugin {
  37  
  38      /**
  39       * Returns optional enrolment information icons.
  40       *
  41       * This is used in course list for quick overview of enrolment options.
  42       *
  43       * We are not using single instance parameter because sometimes
  44       * we might want to prevent icon repetition when multiple instances
  45       * of one type exist. One instance may also produce several icons.
  46       *
  47       * @param array $instances all enrol instances of this type in one course
  48       * @return array of pix_icon
  49       */
  50      public function get_info_icons(array $instances) {
  51          foreach ($instances as $instance) {
  52              if ($instance->password !== '') {
  53                  return array(new pix_icon('withpassword', get_string('guestaccess_withpassword', 'enrol_guest'), 'enrol_guest'));
  54              } else {
  55                  return array(new pix_icon('withoutpassword', get_string('guestaccess_withoutpassword', 'enrol_guest'), 'enrol_guest'));
  56              }
  57          }
  58      }
  59  
  60      /**
  61       * Enrol a user using a given enrolment instance.
  62       *
  63       * @param stdClass $instance
  64       * @param int $userid
  65       * @param null $roleid
  66       * @param int $timestart
  67       * @param int $timeend
  68       * @param null $status
  69       * @param null $recovergrades
  70       */
  71      public function enrol_user(stdClass $instance, $userid, $roleid = null, $timestart = 0, $timeend = 0, $status = null, $recovergrades = null) {
  72          // no real enrolments here!
  73          return;
  74      }
  75  
  76      /**
  77       * Enrol a user from a given enrolment instance.
  78       *
  79       * @param stdClass $instance
  80       * @param int $userid
  81       */
  82      public function unenrol_user(stdClass $instance, $userid) {
  83          // nothing to do, we never enrol here!
  84          return;
  85      }
  86  
  87      /**
  88       * Attempt to automatically gain temporary guest access to course,
  89       * calling code has to make sure the plugin and instance are active.
  90       *
  91       * @param stdClass $instance course enrol instance
  92       * @return bool|int false means no guest access, integer means end of cached time
  93       */
  94      public function try_guestaccess(stdClass $instance) {
  95          global $USER, $CFG;
  96  
  97          $allow = false;
  98  
  99          if ($instance->password === '') {
 100              $allow = true;
 101  
 102          } else if (isset($USER->enrol_guest_passwords[$instance->id])) { // this is a hack, ideally we should not add stuff to $USER...
 103              if ($USER->enrol_guest_passwords[$instance->id] === $instance->password) {
 104                  $allow = true;
 105              }
 106          } else if (WS_SERVER) { // Mobile app mostly.
 107              $storedpass = get_user_preferences('enrol_guest_ws_password_'. $instance->id);
 108              // We check first if there is a supplied password.
 109              if (!is_null($storedpass)) {
 110                  $allow = $storedpass === $instance->password;
 111  
 112                  if (!$allow) {
 113                      // Reset, probably the course password was changed.
 114                      unset_user_preference('enrol_guest_ws_password_' . $instance->id);
 115                  }
 116              }
 117          }
 118  
 119          if ($allow) {
 120              // Temporarily assign them some guest role for this context
 121              $context = context_course::instance($instance->courseid);
 122              load_temp_course_role($context, $CFG->guestroleid);
 123              return ENROL_MAX_TIMESTAMP;
 124          }
 125  
 126          return false;
 127      }
 128  
 129      /**
 130       * Returns true if the current user can add a new instance of enrolment plugin in course.
 131       * @param int $courseid
 132       * @return boolean
 133       */
 134      public function can_add_instance($courseid) {
 135          global $DB;
 136  
 137          $context = context_course::instance($courseid, MUST_EXIST);
 138  
 139          if (!has_capability('moodle/course:enrolconfig', $context) or !has_capability('enrol/guest:config', $context)) {
 140              return false;
 141          }
 142  
 143          if ($DB->record_exists('enrol', array('courseid'=>$courseid, 'enrol'=>'guest'))) {
 144              return false;
 145          }
 146  
 147          return true;
 148      }
 149  
 150      /**
 151       * Creates course enrol form, checks if form submitted
 152       * and enrols user if necessary. It can also redirect.
 153       *
 154       * @param stdClass $instance
 155       * @return string html text, usually a form in a text box
 156       */
 157      public function enrol_page_hook(stdClass $instance) {
 158          global $CFG, $OUTPUT, $SESSION, $USER;
 159  
 160          if ($instance->password === '') {
 161              return null;
 162          }
 163  
 164          if (isset($USER->enrol['tempguest'][$instance->courseid]) and $USER->enrol['tempguest'][$instance->courseid] > time()) {
 165              // no need to show the guest access when user can already enter course as guest
 166              return null;
 167          }
 168  
 169          require_once("$CFG->dirroot/enrol/guest/locallib.php");
 170          $form = new enrol_guest_enrol_form(NULL, $instance);
 171          $instanceid = optional_param('instance', 0, PARAM_INT);
 172  
 173          if ($instance->id == $instanceid) {
 174              if ($data = $form->get_data()) {
 175                  // add guest role
 176                  $context = context_course::instance($instance->courseid);
 177                  $USER->enrol_guest_passwords[$instance->id] = $data->guestpassword; // this is a hack, ideally we should not add stuff to $USER...
 178                  if (isset($USER->enrol['tempguest'][$instance->courseid])) {
 179                      remove_temp_course_roles($context);
 180                  }
 181                  load_temp_course_role($context, $CFG->guestroleid);
 182                  $USER->enrol['tempguest'][$instance->courseid] = ENROL_MAX_TIMESTAMP;
 183  
 184                  // go to the originally requested page
 185                  if (!empty($SESSION->wantsurl)) {
 186                      $destination = $SESSION->wantsurl;
 187                      unset($SESSION->wantsurl);
 188                  } else {
 189                      $destination = "$CFG->wwwroot/course/view.php?id=$instance->courseid";
 190                  }
 191                  redirect($destination);
 192              }
 193          }
 194  
 195          ob_start();
 196          $form->display();
 197          $output = ob_get_clean();
 198  
 199          return $OUTPUT->box($output, 'generalbox');
 200      }
 201  
 202      /**
 203       * Called after updating/inserting course.
 204       *
 205       * @param bool $inserted true if course just inserted
 206       * @param object $course
 207       * @param object $data form data
 208       * @return void
 209       */
 210      public function course_updated($inserted, $course, $data) {
 211          global $DB;
 212  
 213          if ($inserted) {
 214              if (isset($data->enrol_guest_status_0)) {
 215                  $fields = array('status'=>$data->enrol_guest_status_0);
 216                  if ($fields['status'] == ENROL_INSTANCE_ENABLED) {
 217                      $fields['password'] = $data->enrol_guest_password_0;
 218                  } else {
 219                      if ($this->get_config('requirepassword')) {
 220                          $fields['password'] = generate_password(20);
 221                      }
 222                  }
 223                  $this->add_instance($course, $fields);
 224              } else {
 225                  if ($this->get_config('defaultenrol')) {
 226                      $this->add_default_instance($course);
 227                  }
 228              }
 229  
 230          } else {
 231              $instances = $DB->get_records('enrol', array('courseid'=>$course->id, 'enrol'=>'guest'));
 232              foreach ($instances as $instance) {
 233                  $i = $instance->id;
 234  
 235                  if (isset($data->{'enrol_guest_status_'.$i})) {
 236                      $reset = ($instance->status != $data->{'enrol_guest_status_'.$i});
 237  
 238                      $instance->status       = $data->{'enrol_guest_status_'.$i};
 239                      $instance->timemodified = time();
 240                      if ($instance->status == ENROL_INSTANCE_ENABLED) {
 241                          if ($instance->password !== $data->{'enrol_guest_password_'.$i}) {
 242                              $reset = true;
 243                          }
 244                          $instance->password = $data->{'enrol_guest_password_'.$i};
 245                      }
 246                      $DB->update_record('enrol', $instance);
 247                      \core\event\enrol_instance_updated::create_from_record($instance)->trigger();
 248  
 249                      if ($reset) {
 250                          $context = context_course::instance($course->id);
 251                          $context->mark_dirty();
 252                      }
 253                  }
 254              }
 255          }
 256      }
 257  
 258      /**
 259       * Add new instance of enrol plugin.
 260       * @param object $course
 261       * @param array instance fields
 262       * @return int id of new instance, null if can not be created
 263       */
 264      public function add_instance($course, array $fields = NULL) {
 265          $fields = (array)$fields;
 266  
 267          if (!isset($fields['password'])) {
 268              $fields['password'] = '';
 269          }
 270  
 271          return parent::add_instance($course, $fields);
 272      }
 273  
 274      /**
 275       * Add new instance of enrol plugin with default settings.
 276       * @param object $course
 277       * @return int id of new instance
 278       */
 279      public function add_default_instance($course) {
 280          $fields = array('status'=>$this->get_config('status'));
 281  
 282          if ($this->get_config('requirepassword')) {
 283              $fields['password'] = generate_password(20);
 284          }
 285  
 286          return $this->add_instance($course, $fields);
 287      }
 288  
 289      /**
 290       * Restore instance and map settings.
 291       *
 292       * @param restore_enrolments_structure_step $step
 293       * @param stdClass $data
 294       * @param stdClass $course
 295       * @param int $oldid
 296       */
 297      public function restore_instance(restore_enrolments_structure_step $step, stdClass $data, $course, $oldid) {
 298          global $DB;
 299  
 300          if (!$DB->record_exists('enrol', array('courseid' => $data->courseid, 'enrol' => $this->get_name()))) {
 301              $this->add_instance($course, (array)$data);
 302          }
 303  
 304          // No need to set mapping, we do not restore users or roles here.
 305          $step->set_mapping('enrol', $oldid, 0);
 306      }
 307  
 308      /**
 309       * Is it possible to delete enrol instance via standard UI?
 310       *
 311       * @param object $instance
 312       * @return bool
 313       */
 314      public function can_delete_instance($instance) {
 315          $context = context_course::instance($instance->courseid);
 316          return has_capability('enrol/guest:config', $context);
 317      }
 318  
 319      /**
 320       * Is it possible to hide/show enrol instance via standard UI?
 321       *
 322       * @param stdClass $instance
 323       * @return bool
 324       */
 325      public function can_hide_show_instance($instance) {
 326          $context = context_course::instance($instance->courseid);
 327          if (!has_capability('enrol/guest:config', $context)) {
 328              return false;
 329          }
 330  
 331          // If the instance is currently disabled, before it can be enabled, we must check whether the password meets the
 332          // password policies.
 333          if ($instance->status == ENROL_INSTANCE_DISABLED) {
 334              if ($this->get_config('requirepassword')) {
 335                  if (empty($instance->password)) {
 336                      return false;
 337                  }
 338              }
 339  
 340              // Only check the password if it is set.
 341              if (!empty($instance->password) && $this->get_config('usepasswordpolicy')) {
 342                  if (!check_password_policy($instance->password, $errmsg)) {
 343                      return false;
 344                  }
 345              }
 346          }
 347  
 348          return true;
 349      }
 350  
 351      /**
 352       * Get default settings for enrol_guest.
 353       *
 354       * @return array
 355       */
 356      public function get_instance_defaults() {
 357          $fields = array();
 358          $fields['status']          = $this->get_config('status');
 359          return $fields;
 360      }
 361  
 362      /**
 363       * Return information for enrolment instance containing list of parameters required
 364       * for enrolment, name of enrolment plugin etc.
 365       *
 366       * @param stdClass $instance enrolment instance
 367       * @return stdClass instance info.
 368       * @since Moodle 3.1
 369       */
 370      public function get_enrol_info(stdClass $instance) {
 371  
 372          $instanceinfo = new stdClass();
 373          $instanceinfo->id = $instance->id;
 374          $instanceinfo->courseid = $instance->courseid;
 375          $instanceinfo->type = $this->get_name();
 376          $instanceinfo->name = $this->get_instance_name($instance);
 377          $instanceinfo->status = $instance->status == ENROL_INSTANCE_ENABLED;
 378  
 379          // Specifics enrolment method parameters.
 380          $instanceinfo->requiredparam = new stdClass();
 381          $instanceinfo->requiredparam->passwordrequired = !empty($instance->password);
 382  
 383          // If the plugin is enabled, return the URL for obtaining more information.
 384          if ($instanceinfo->status) {
 385              $instanceinfo->wsfunction = 'enrol_guest_get_instance_info';
 386          }
 387          return $instanceinfo;
 388      }
 389  
 390      /**
 391       * Return an array of valid options for the status.
 392       *
 393       * @return array
 394       */
 395      protected function get_status_options() {
 396          $options = array(ENROL_INSTANCE_ENABLED  => get_string('yes'),
 397                           ENROL_INSTANCE_DISABLED => get_string('no'));
 398          return $options;
 399      }
 400  
 401      /**
 402       * Add elements to the edit instance form.
 403       *
 404       * @param stdClass $instance
 405       * @param MoodleQuickForm $mform
 406       * @param context $context
 407       * @return bool
 408       */
 409      public function edit_instance_form($instance, MoodleQuickForm $mform, $context) {
 410          global $CFG;
 411  
 412          $options = $this->get_status_options();
 413          $mform->addElement('select', 'status', get_string('status', 'enrol_guest'), $options);
 414          $mform->addHelpButton('status', 'status', 'enrol_guest');
 415          $mform->setDefault('status', $this->get_config('status'));
 416          $mform->setAdvanced('status', $this->get_config('status_adv'));
 417  
 418          $mform->addElement('passwordunmask', 'password', get_string('password', 'enrol_guest'));
 419          $mform->addHelpButton('password', 'password', 'enrol_guest');
 420  
 421          // If we have a new instance and the password is required - make sure it is set. For existing
 422          // instances we do not force the password to be required as it may have been set to empty before
 423          // the password was required. We check in the validation function whether this check is required
 424          // for existing instances.
 425          if (empty($instance->id) && $this->get_config('requirepassword')) {
 426              $mform->addRule('password', get_string('required'), 'required', null);
 427          }
 428      }
 429  
 430      /**
 431       * We are a good plugin and don't invent our own UI/validation code path.
 432       *
 433       * @return boolean
 434       */
 435      public function use_standard_editing_ui() {
 436          return true;
 437      }
 438  
 439      /**
 440       * Perform custom validation of the data used to edit the instance.
 441       *
 442       * @param array $data array of ("fieldname"=>value) of submitted data
 443       * @param array $files array of uploaded files "element_name"=>tmp_file_path
 444       * @param object $instance The instance loaded from the DB
 445       * @param context $context The context of the instance we are editing
 446       * @return array of "element_name"=>"error_description" if there are errors,
 447       *         or an empty array if everything is OK.
 448       * @return void
 449       */
 450      public function edit_instance_validation($data, $files, $instance, $context) {
 451          $errors = array();
 452  
 453          $checkpassword = false;
 454  
 455          if ($data['id']) {
 456              // Check the password if we are enabling the plugin again.
 457              if (($instance->status == ENROL_INSTANCE_DISABLED) && ($data['status'] == ENROL_INSTANCE_ENABLED)) {
 458                  $checkpassword = true;
 459              }
 460  
 461              // Check the password if the instance is enabled and the password has changed.
 462              if (($data['status'] == ENROL_INSTANCE_ENABLED) && ($instance->password !== $data['password'])) {
 463                  $checkpassword = true;
 464              }
 465          } else {
 466              $checkpassword = true;
 467          }
 468  
 469          if ($checkpassword) {
 470              $require = $this->get_config('requirepassword');
 471              $policy  = $this->get_config('usepasswordpolicy');
 472              if ($require && trim($data['password']) === '') {
 473                  $errors['password'] = get_string('required');
 474              } else if (!empty($data['password']) && $policy) {
 475                  $errmsg = '';
 476                  if (!check_password_policy($data['password'], $errmsg)) {
 477                      $errors['password'] = $errmsg;
 478                  }
 479              }
 480          }
 481  
 482          $validstatus = array_keys($this->get_status_options());
 483          $tovalidate = array(
 484              'status' => $validstatus
 485          );
 486          $typeerrors = $this->validate_param_types($data, $tovalidate);
 487          $errors = array_merge($errors, $typeerrors);
 488  
 489          return $errors;
 490      }
 491  
 492      /**
 493       * Check if enrolment plugin is supported in csv course upload.
 494       *
 495       * @return bool
 496       */
 497      public function is_csv_upload_supported(): bool {
 498          return true;
 499      }
 500  }
 501  
 502  /**
 503   * Get icon mapping for font-awesome.
 504   */
 505  function enrol_guest_get_fontawesome_icon_map() {
 506      return [
 507          'enrol_guest:withpassword' => 'fa-key',
 508          'enrol_guest:withoutpassword' => 'fa-unlock-alt',
 509      ];
 510  }