Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 4.1.x will end 13 November 2023 (12 months).
  • Bug fixes for security issues in 4.1.x will end 10 November 2025 (36 months).
  • PHP version: minimum PHP 7.4.0 Note: minimum PHP version has increased since Moodle 4.0. PHP 8.0.x is supported too.

Differences Between: [Versions 310 and 401] [Versions 311 and 401] [Versions 39 and 401] [Versions 401 and 402] [Versions 401 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  /**
  19   * External course participation api.
  20   *
  21   * This api is mostly read only, the actual enrol and unenrol
  22   * support is in each enrol plugin.
  23   *
  24   * @package    core_enrol
  25   * @category   external
  26   * @copyright  2010 Jerome Mouneyrac
  27   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  28   */
  29  
  30  defined('MOODLE_INTERNAL') || die();
  31  
  32  require_once("$CFG->libdir/externallib.php");
  33  
  34  /**
  35   * Enrol external functions
  36   *
  37   * @package    core_enrol
  38   * @category   external
  39   * @copyright  2011 Jerome Mouneyrac
  40   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  41   * @since Moodle 2.2
  42   */
  43  class core_enrol_external extends external_api {
  44  
  45      /**
  46       * Returns description of method parameters
  47       *
  48       * @return external_function_parameters
  49       * @since Moodle 2.4
  50       */
  51      public static function get_enrolled_users_with_capability_parameters() {
  52          return new external_function_parameters(
  53              array (
  54                  'coursecapabilities' => new external_multiple_structure(
  55                      new external_single_structure(
  56                          array (
  57                              'courseid' => new external_value(PARAM_INT, 'Course ID number in the Moodle course table'),
  58                              'capabilities' => new external_multiple_structure(
  59                                  new external_value(PARAM_CAPABILITY, 'Capability name, such as mod/forum:viewdiscussion')),
  60                          )
  61                      )
  62                  , 'course id and associated capability name'),
  63                   'options'  => new external_multiple_structure(
  64                      new external_single_structure(
  65                          array(
  66                              'name'  => new external_value(PARAM_ALPHANUMEXT, 'option name'),
  67                              'value' => new external_value(PARAM_RAW, 'option value')
  68                          )
  69                      ), 'Option names:
  70                              * groupid (integer) return only users in this group id. Requires \'moodle/site:accessallgroups\' .
  71                              * onlyactive (integer) only users with active enrolments. Requires \'moodle/course:enrolreview\' .
  72                              * userfields (\'string, string, ...\') return only the values of these user fields.
  73                              * limitfrom (integer) sql limit from.
  74                              * limitnumber (integer) max number of users per course and capability.', VALUE_DEFAULT, array())
  75              )
  76          );
  77      }
  78  
  79      /**
  80       * Return users that have the capabilities for each course specified. For each course and capability specified,
  81       * a list of the users that are enrolled in the course and have that capability are returned.
  82       *
  83       * @param array $coursecapabilities array of course ids and associated capability names {courseid, {capabilities}}
  84       * @return array An array of arrays describing users for each associated courseid and capability
  85       * @since  Moodle 2.4
  86       */
  87      public static function get_enrolled_users_with_capability($coursecapabilities, $options) {
  88          global $CFG, $DB;
  89  
  90          require_once($CFG->dirroot . '/course/lib.php');
  91          require_once($CFG->dirroot . "/user/lib.php");
  92  
  93          if (empty($coursecapabilities)) {
  94              throw new invalid_parameter_exception('Parameter can not be empty');
  95          }
  96          $params = self::validate_parameters(self::get_enrolled_users_with_capability_parameters(),
  97              array ('coursecapabilities' => $coursecapabilities,  'options'=>$options));
  98          $result = array();
  99          $userlist = array();
 100          $groupid        = 0;
 101          $onlyactive     = false;
 102          $userfields     = array();
 103          $limitfrom = 0;
 104          $limitnumber = 0;
 105          foreach ($params['options'] as $option) {
 106              switch ($option['name']) {
 107                  case 'groupid':
 108                      $groupid = (int)$option['value'];
 109                      break;
 110                  case 'onlyactive':
 111                      $onlyactive = !empty($option['value']);
 112                      break;
 113                  case 'userfields':
 114                      $thefields = explode(',', $option['value']);
 115                      foreach ($thefields as $f) {
 116                          $userfields[] = clean_param($f, PARAM_ALPHANUMEXT);
 117                      }
 118                      break;
 119                  case 'limitfrom' :
 120                      $limitfrom = clean_param($option['value'], PARAM_INT);
 121                      break;
 122                  case 'limitnumber' :
 123                      $limitnumber = clean_param($option['value'], PARAM_INT);
 124                      break;
 125              }
 126          }
 127  
 128          foreach ($params['coursecapabilities'] as $coursecapability) {
 129              $courseid = $coursecapability['courseid'];
 130              $course = $DB->get_record('course', array('id'=>$courseid), '*', MUST_EXIST);
 131              $coursecontext = context_course::instance($courseid);
 132              if (!$coursecontext) {
 133                  throw new moodle_exception('cannotfindcourse', 'error', '', null,
 134                          'The course id ' . $courseid . ' doesn\'t exist.');
 135              }
 136              if ($courseid == SITEID) {
 137                  $context = context_system::instance();
 138              } else {
 139                  $context = $coursecontext;
 140              }
 141              try {
 142                  self::validate_context($context);
 143              } catch (Exception $e) {
 144                  $exceptionparam = new stdClass();
 145                  $exceptionparam->message = $e->getMessage();
 146                  $exceptionparam->courseid = $params['courseid'];
 147                  throw new moodle_exception(get_string('errorcoursecontextnotvalid' , 'webservice', $exceptionparam));
 148              }
 149  
 150              course_require_view_participants($context);
 151  
 152              // The accessallgroups capability is needed to use this option.
 153              if (!empty($groupid) && groups_is_member($groupid)) {
 154                  require_capability('moodle/site:accessallgroups', $coursecontext);
 155              }
 156              // The course:enrolereview capability is needed to use this option.
 157              if ($onlyactive) {
 158                  require_capability('moodle/course:enrolreview', $coursecontext);
 159              }
 160  
 161              // To see the permissions of others role:review capability is required.
 162              require_capability('moodle/role:review', $coursecontext);
 163              foreach ($coursecapability['capabilities'] as $capability) {
 164                  $courseusers['courseid'] = $courseid;
 165                  $courseusers['capability'] = $capability;
 166  
 167                  list($enrolledsql, $enrolledparams) = get_enrolled_sql($coursecontext, $capability, $groupid, $onlyactive);
 168                  $enrolledparams['courseid'] = $courseid;
 169  
 170                  $sql = "SELECT u.*, COALESCE(ul.timeaccess, 0) AS lastcourseaccess
 171                            FROM {user} u
 172                       LEFT JOIN {user_lastaccess} ul ON (ul.userid = u.id AND ul.courseid = :courseid)
 173                           WHERE u.id IN ($enrolledsql)
 174                        ORDER BY u.id ASC";
 175  
 176                  $enrolledusers = $DB->get_recordset_sql($sql, $enrolledparams, $limitfrom, $limitnumber);
 177                  $users = array();
 178                  foreach ($enrolledusers as $courseuser) {
 179                      if ($userdetails = user_get_user_details($courseuser, $course, $userfields)) {
 180                          $users[] = $userdetails;
 181                      }
 182                  }
 183                  $enrolledusers->close();
 184                  $courseusers['users'] = $users;
 185                  $result[] = $courseusers;
 186              }
 187          }
 188          return $result;
 189      }
 190  
 191      /**
 192       * Returns description of method result value
 193       *
 194       * @return external_multiple_structure
 195       * @since Moodle 2.4
 196       */
 197      public static function get_enrolled_users_with_capability_returns() {
 198          return  new external_multiple_structure( new external_single_structure (
 199                  array (
 200                      'courseid' => new external_value(PARAM_INT, 'Course ID number in the Moodle course table'),
 201                      'capability' => new external_value(PARAM_CAPABILITY, 'Capability name'),
 202                      'users' => new external_multiple_structure(
 203                          new external_single_structure(
 204                  array(
 205                      'id'    => new external_value(PARAM_INT, 'ID of the user'),
 206                      'username'    => new external_value(PARAM_RAW, 'Username', VALUE_OPTIONAL),
 207                      'firstname'   => new external_value(PARAM_NOTAGS, 'The first name(s) of the user', VALUE_OPTIONAL),
 208                      'lastname'    => new external_value(PARAM_NOTAGS, 'The family name of the user', VALUE_OPTIONAL),
 209                      'fullname'    => new external_value(PARAM_NOTAGS, 'The fullname of the user'),
 210                      'email'       => new external_value(PARAM_TEXT, 'Email address', VALUE_OPTIONAL),
 211                      'address'     => new external_value(PARAM_MULTILANG, 'Postal address', VALUE_OPTIONAL),
 212                      'phone1'      => new external_value(PARAM_NOTAGS, 'Phone 1', VALUE_OPTIONAL),
 213                      'phone2'      => new external_value(PARAM_NOTAGS, 'Phone 2', VALUE_OPTIONAL),
 214                      'department'  => new external_value(PARAM_TEXT, 'department', VALUE_OPTIONAL),
 215                      'institution' => new external_value(PARAM_TEXT, 'institution', VALUE_OPTIONAL),
 216                      'interests'   => new external_value(PARAM_TEXT, 'user interests (separated by commas)', VALUE_OPTIONAL),
 217                      'firstaccess' => new external_value(PARAM_INT, 'first access to the site (0 if never)', VALUE_OPTIONAL),
 218                      'lastaccess'  => new external_value(PARAM_INT, 'last access to the site (0 if never)', VALUE_OPTIONAL),
 219                      'lastcourseaccess'  => new external_value(PARAM_INT, 'last access to the course (0 if never)', VALUE_OPTIONAL),
 220                      'description' => new external_value(PARAM_RAW, 'User profile description', VALUE_OPTIONAL),
 221                      'descriptionformat' => new external_value(PARAM_INT, 'User profile description format', VALUE_OPTIONAL),
 222                      'city'        => new external_value(PARAM_NOTAGS, 'Home city of the user', VALUE_OPTIONAL),
 223                      'country'     => new external_value(PARAM_ALPHA, 'Country code of the user, such as AU or CZ', VALUE_OPTIONAL),
 224                      'profileimageurlsmall' => new external_value(PARAM_URL, 'User image profile URL - small', VALUE_OPTIONAL),
 225                      'profileimageurl' => new external_value(PARAM_URL, 'User image profile URL - big', VALUE_OPTIONAL),
 226                      'customfields' => new external_multiple_structure(
 227                          new external_single_structure(
 228                              array(
 229                                  'type'  => new external_value(PARAM_ALPHANUMEXT, 'The type of the custom field'),
 230                                  'value' => new external_value(PARAM_RAW, 'The value of the custom field'),
 231                                  'name' => new external_value(PARAM_RAW, 'The name of the custom field'),
 232                                  'shortname' => new external_value(PARAM_RAW, 'The shortname of the custom field'),
 233                              )
 234                          ), 'User custom fields (also known as user profil fields)', VALUE_OPTIONAL),
 235                      'groups' => new external_multiple_structure(
 236                          new external_single_structure(
 237                              array(
 238                                  'id'  => new external_value(PARAM_INT, 'group id'),
 239                                  'name' => new external_value(PARAM_RAW, 'group name'),
 240                                  'description' => new external_value(PARAM_RAW, 'group description'),
 241                              )
 242                          ), 'user groups', VALUE_OPTIONAL),
 243                      'roles' => new external_multiple_structure(
 244                          new external_single_structure(
 245                              array(
 246                                  'roleid'       => new external_value(PARAM_INT, 'role id'),
 247                                  'name'         => new external_value(PARAM_RAW, 'role name'),
 248                                  'shortname'    => new external_value(PARAM_ALPHANUMEXT, 'role shortname'),
 249                                  'sortorder'    => new external_value(PARAM_INT, 'role sortorder')
 250                              )
 251                          ), 'user roles', VALUE_OPTIONAL),
 252                      'preferences' => new external_multiple_structure(
 253                          new external_single_structure(
 254                              array(
 255                                  'name'  => new external_value(PARAM_RAW, 'The name of the preferences'),
 256                                  'value' => new external_value(PARAM_RAW, 'The value of the custom field'),
 257                              )
 258                      ), 'User preferences', VALUE_OPTIONAL),
 259                      'enrolledcourses' => new external_multiple_structure(
 260                          new external_single_structure(
 261                              array(
 262                                  'id'  => new external_value(PARAM_INT, 'Id of the course'),
 263                                  'fullname' => new external_value(PARAM_RAW, 'Fullname of the course'),
 264                                  'shortname' => new external_value(PARAM_RAW, 'Shortname of the course')
 265                              )
 266                      ), 'Courses where the user is enrolled - limited by which courses the user is able to see', VALUE_OPTIONAL)
 267                  )
 268                          ), 'List of users that are enrolled in the course and have the specified capability'),
 269                      )
 270                  )
 271              );
 272      }
 273  
 274      /**
 275       * Returns description of method parameters
 276       *
 277       * @return external_function_parameters
 278       */
 279      public static function get_users_courses_parameters() {
 280          return new external_function_parameters(
 281              array(
 282                  'userid' => new external_value(PARAM_INT, 'user id'),
 283                  'returnusercount' => new external_value(PARAM_BOOL,
 284                          'Include count of enrolled users for each course? This can add several seconds to the response time'
 285                              . ' if a user is on several large courses, so set this to false if the value will not be used to'
 286                              . ' improve performance.',
 287                          VALUE_DEFAULT, true),
 288              )
 289          );
 290      }
 291  
 292      /**
 293       * Get list of courses user is enrolled in (only active enrolments are returned).
 294       * Please note the current user must be able to access the course, otherwise the course is not included.
 295       *
 296       * @param int $userid
 297       * @param bool $returnusercount
 298       * @return array of courses
 299       */
 300      public static function get_users_courses($userid, $returnusercount = true) {
 301          global $CFG, $USER, $DB;
 302  
 303          require_once($CFG->dirroot . '/course/lib.php');
 304          require_once($CFG->dirroot . '/user/lib.php');
 305          require_once($CFG->libdir . '/completionlib.php');
 306  
 307          // Do basic automatic PARAM checks on incoming data, using params description
 308          // If any problems are found then exceptions are thrown with helpful error messages
 309          $params = self::validate_parameters(self::get_users_courses_parameters(),
 310                  ['userid' => $userid, 'returnusercount' => $returnusercount]);
 311          $userid = $params['userid'];
 312          $returnusercount = $params['returnusercount'];
 313  
 314          $courses = enrol_get_users_courses($userid, true, '*');
 315          $result = array();
 316  
 317          // Get user data including last access to courses.
 318          $user = get_complete_user_data('id', $userid);
 319          $sameuser = $USER->id == $userid;
 320  
 321          // Retrieve favourited courses (starred).
 322          $favouritecourseids = array();
 323          if ($sameuser) {
 324              $ufservice = \core_favourites\service_factory::get_service_for_user_context(\context_user::instance($userid));
 325              $favourites = $ufservice->find_favourites_by_type('core_course', 'courses');
 326  
 327              if ($favourites) {
 328                  $favouritecourseids = array_flip(array_map(
 329                      function($favourite) {
 330                          return $favourite->itemid;
 331                      }, $favourites));
 332              }
 333          }
 334  
 335          foreach ($courses as $course) {
 336              $context = context_course::instance($course->id, IGNORE_MISSING);
 337              try {
 338                  self::validate_context($context);
 339              } catch (Exception $e) {
 340                  // current user can not access this course, sorry we can not disclose who is enrolled in this course!
 341                  continue;
 342              }
 343  
 344              // If viewing details of another user, then we must be able to view participants as well as profile of that user.
 345              if (!$sameuser && (!course_can_view_participants($context) || !user_can_view_profile($user, $course))) {
 346                  continue;
 347              }
 348  
 349              if ($returnusercount) {
 350                  list($enrolledsqlselect, $enrolledparams) = get_enrolled_sql($context);
 351                  $enrolledsql = "SELECT COUNT('x') FROM ($enrolledsqlselect) enrolleduserids";
 352                  $enrolledusercount = $DB->count_records_sql($enrolledsql, $enrolledparams);
 353              }
 354  
 355              $displayname = external_format_string(get_course_display_name_for_list($course), $context->id);
 356              list($course->summary, $course->summaryformat) =
 357                  external_format_text($course->summary, $course->summaryformat, $context->id, 'course', 'summary', null);
 358              $course->fullname = external_format_string($course->fullname, $context->id);
 359              $course->shortname = external_format_string($course->shortname, $context->id);
 360  
 361              $progress = null;
 362              $completed = null;
 363              $completionhascriteria = false;
 364              $completionusertracked = false;
 365  
 366              // Return only private information if the user should be able to see it.
 367              if ($sameuser || completion_can_view_data($userid, $course)) {
 368                  if ($course->enablecompletion) {
 369                      $completion = new completion_info($course);
 370                      $completed = $completion->is_course_complete($userid);
 371                      $completionhascriteria = $completion->has_criteria();
 372                      $completionusertracked = $completion->is_tracked_user($userid);
 373                      $progress = \core_completion\progress::get_course_progress_percentage($course, $userid);
 374                  }
 375              }
 376  
 377              $lastaccess = null;
 378              // Check if last access is a hidden field.
 379              $hiddenfields = array_flip(explode(',', $CFG->hiddenuserfields));
 380              $canviewlastaccess = $sameuser || !isset($hiddenfields['lastaccess']);
 381              if (!$canviewlastaccess) {
 382                  $canviewlastaccess = has_capability('moodle/course:viewhiddenuserfields', $context);
 383              }
 384  
 385              if ($canviewlastaccess && isset($user->lastcourseaccess[$course->id])) {
 386                  $lastaccess = $user->lastcourseaccess[$course->id];
 387              }
 388  
 389              $hidden = false;
 390              if ($sameuser) {
 391                  $hidden = boolval(get_user_preferences('block_myoverview_hidden_course_' . $course->id, 0));
 392              }
 393  
 394              // Retrieve course overview used files.
 395              $courselist = new core_course_list_element($course);
 396              $overviewfiles = array();
 397              foreach ($courselist->get_course_overviewfiles() as $file) {
 398                  $fileurl = moodle_url::make_webservice_pluginfile_url($file->get_contextid(), $file->get_component(),
 399                                                                          $file->get_filearea(), null, $file->get_filepath(),
 400                                                                          $file->get_filename())->out(false);
 401                  $overviewfiles[] = array(
 402                      'filename' => $file->get_filename(),
 403                      'fileurl' => $fileurl,
 404                      'filesize' => $file->get_filesize(),
 405                      'filepath' => $file->get_filepath(),
 406                      'mimetype' => $file->get_mimetype(),
 407                      'timemodified' => $file->get_timemodified(),
 408                  );
 409              }
 410  
 411              $courseresult = [
 412                  'id' => $course->id,
 413                  'shortname' => $course->shortname,
 414                  'fullname' => $course->fullname,
 415                  'displayname' => $displayname,
 416                  'idnumber' => $course->idnumber,
 417                  'visible' => $course->visible,
 418                  'summary' => $course->summary,
 419                  'summaryformat' => $course->summaryformat,
 420                  'format' => $course->format,
 421                  'showgrades' => $course->showgrades,
 422                  'lang' => clean_param($course->lang, PARAM_LANG),
 423                  'enablecompletion' => $course->enablecompletion,
 424                  'completionhascriteria' => $completionhascriteria,
 425                  'completionusertracked' => $completionusertracked,
 426                  'category' => $course->category,
 427                  'progress' => $progress,
 428                  'completed' => $completed,
 429                  'startdate' => $course->startdate,
 430                  'enddate' => $course->enddate,
 431                  'marker' => $course->marker,
 432                  'lastaccess' => $lastaccess,
 433                  'isfavourite' => isset($favouritecourseids[$course->id]),
 434                  'hidden' => $hidden,
 435                  'overviewfiles' => $overviewfiles,
 436                  'showactivitydates' => $course->showactivitydates,
 437                  'showcompletionconditions' => $course->showcompletionconditions,
 438                  'timemodified' => $course->timemodified,
 439              ];
 440              if ($returnusercount) {
 441                  $courseresult['enrolledusercount'] = $enrolledusercount;
 442              }
 443              $result[] = $courseresult;
 444          }
 445  
 446          return $result;
 447      }
 448  
 449      /**
 450       * Returns description of method result value
 451       *
 452       * @return external_description
 453       */
 454      public static function get_users_courses_returns() {
 455          return new external_multiple_structure(
 456              new external_single_structure(
 457                  array(
 458                      'id'        => new external_value(PARAM_INT, 'id of course'),
 459                      'shortname' => new external_value(PARAM_RAW, 'short name of course'),
 460                      'fullname'  => new external_value(PARAM_RAW, 'long name of course'),
 461                      'displayname' => new external_value(PARAM_RAW, 'course display name for lists.', VALUE_OPTIONAL),
 462                      'enrolledusercount' => new external_value(PARAM_INT, 'Number of enrolled users in this course',
 463                              VALUE_OPTIONAL),
 464                      'idnumber'  => new external_value(PARAM_RAW, 'id number of course'),
 465                      'visible'   => new external_value(PARAM_INT, '1 means visible, 0 means not yet visible course'),
 466                      'summary'   => new external_value(PARAM_RAW, 'summary', VALUE_OPTIONAL),
 467                      'summaryformat' => new external_format_value('summary', VALUE_OPTIONAL),
 468                      'format'    => new external_value(PARAM_PLUGIN, 'course format: weeks, topics, social, site', VALUE_OPTIONAL),
 469                      'showgrades' => new external_value(PARAM_BOOL, 'true if grades are shown, otherwise false', VALUE_OPTIONAL),
 470                      'lang'      => new external_value(PARAM_LANG, 'forced course language', VALUE_OPTIONAL),
 471                      'enablecompletion' => new external_value(PARAM_BOOL, 'true if completion is enabled, otherwise false',
 472                                                                  VALUE_OPTIONAL),
 473                      'completionhascriteria' => new external_value(PARAM_BOOL, 'If completion criteria is set.', VALUE_OPTIONAL),
 474                      'completionusertracked' => new external_value(PARAM_BOOL, 'If the user is completion tracked.', VALUE_OPTIONAL),
 475                      'category' => new external_value(PARAM_INT, 'course category id', VALUE_OPTIONAL),
 476                      'progress' => new external_value(PARAM_FLOAT, 'Progress percentage', VALUE_OPTIONAL),
 477                      'completed' => new external_value(PARAM_BOOL, 'Whether the course is completed.', VALUE_OPTIONAL),
 478                      'startdate' => new external_value(PARAM_INT, 'Timestamp when the course start', VALUE_OPTIONAL),
 479                      'enddate' => new external_value(PARAM_INT, 'Timestamp when the course end', VALUE_OPTIONAL),
 480                      'marker' => new external_value(PARAM_INT, 'Course section marker.', VALUE_OPTIONAL),
 481                      'lastaccess' => new external_value(PARAM_INT, 'Last access to the course (timestamp).', VALUE_OPTIONAL),
 482                      'isfavourite' => new external_value(PARAM_BOOL, 'If the user marked this course a favourite.', VALUE_OPTIONAL),
 483                      'hidden' => new external_value(PARAM_BOOL, 'If the user hide the course from the dashboard.', VALUE_OPTIONAL),
 484                      'overviewfiles' => new external_files('Overview files attached to this course.', VALUE_OPTIONAL),
 485                      'showactivitydates' => new external_value(PARAM_BOOL, 'Whether the activity dates are shown or not'),
 486                      'showcompletionconditions' => new external_value(PARAM_BOOL, 'Whether the activity completion conditions are shown or not'),
 487                      'timemodified' => new external_value(PARAM_INT, 'Last time course settings were updated (timestamp).',
 488                          VALUE_OPTIONAL),
 489                  )
 490              )
 491          );
 492      }
 493  
 494      /**
 495       * Returns description of method parameters value
 496       *
 497       * @return external_description
 498       */
 499      public static function get_potential_users_parameters() {
 500          return new external_function_parameters(
 501              array(
 502                  'courseid' => new external_value(PARAM_INT, 'course id'),
 503                  'enrolid' => new external_value(PARAM_INT, 'enrolment id'),
 504                  'search' => new external_value(PARAM_RAW, 'query'),
 505                  'searchanywhere' => new external_value(PARAM_BOOL, 'find a match anywhere, or only at the beginning'),
 506                  'page' => new external_value(PARAM_INT, 'Page number'),
 507                  'perpage' => new external_value(PARAM_INT, 'Number per page'),
 508              )
 509          );
 510      }
 511  
 512      /**
 513       * Get potential users.
 514       *
 515       * @param int $courseid Course id
 516       * @param int $enrolid Enrolment id
 517       * @param string $search The query
 518       * @param boolean $searchanywhere Match anywhere in the string
 519       * @param int $page Page number
 520       * @param int $perpage Max per page
 521       * @return array An array of users
 522       */
 523      public static function get_potential_users($courseid, $enrolid, $search, $searchanywhere, $page, $perpage) {
 524          global $PAGE, $DB, $CFG;
 525  
 526          require_once($CFG->dirroot.'/enrol/locallib.php');
 527          require_once($CFG->dirroot.'/user/lib.php');
 528  
 529          $params = self::validate_parameters(
 530              self::get_potential_users_parameters(),
 531              array(
 532                  'courseid' => $courseid,
 533                  'enrolid' => $enrolid,
 534                  'search' => $search,
 535                  'searchanywhere' => $searchanywhere,
 536                  'page' => $page,
 537                  'perpage' => $perpage
 538              )
 539          );
 540          $context = context_course::instance($params['courseid']);
 541          try {
 542              self::validate_context($context);
 543          } catch (Exception $e) {
 544              $exceptionparam = new stdClass();
 545              $exceptionparam->message = $e->getMessage();
 546              $exceptionparam->courseid = $params['courseid'];
 547              throw new moodle_exception('errorcoursecontextnotvalid' , 'webservice', '', $exceptionparam);
 548          }
 549          require_capability('moodle/course:enrolreview', $context);
 550  
 551          $course = $DB->get_record('course', array('id' => $params['courseid']));
 552          $manager = new course_enrolment_manager($PAGE, $course);
 553  
 554          $users = $manager->get_potential_users($params['enrolid'],
 555                                                 $params['search'],
 556                                                 $params['searchanywhere'],
 557                                                 $params['page'],
 558                                                 $params['perpage']);
 559  
 560          $results = array();
 561          // Add also extra user fields.
 562          $identityfields = \core_user\fields::get_identity_fields($context, true);
 563          $customprofilefields = [];
 564          foreach ($identityfields as $key => $value) {
 565              if ($fieldname = \core_user\fields::match_custom_field($value)) {
 566                  unset($identityfields[$key]);
 567                  $customprofilefields[$fieldname] = true;
 568              }
 569          }
 570          if ($customprofilefields) {
 571              $identityfields[] = 'customfields';
 572          }
 573          $requiredfields = array_merge(
 574              ['id', 'fullname', 'profileimageurl', 'profileimageurlsmall'],
 575              $identityfields
 576          );
 577          foreach ($users['users'] as $id => $user) {
 578              // Note: We pass the course here to validate that the current user can at least view user details in this course.
 579              // The user we are looking at is not in this course yet though - but we only fetch the minimal set of
 580              // user records, and the user has been validated to have course:enrolreview in this course. Otherwise
 581              // there is no way to find users who aren't in the course in order to enrol them.
 582              if ($userdetails = user_get_user_details($user, $course, $requiredfields)) {
 583                  // For custom fields, only return the ones we actually need.
 584                  if ($customprofilefields && array_key_exists('customfields', $userdetails)) {
 585                      foreach ($userdetails['customfields'] as $key => $data) {
 586                          if (!array_key_exists($data['shortname'], $customprofilefields)) {
 587                              unset($userdetails['customfields'][$key]);
 588                          }
 589                      }
 590                      $userdetails['customfields'] = array_values($userdetails['customfields']);
 591                  }
 592                  $results[] = $userdetails;
 593              }
 594          }
 595          return $results;
 596      }
 597  
 598      /**
 599       * Returns description of method result value
 600       *
 601       * @return external_description
 602       */
 603      public static function get_potential_users_returns() {
 604          global $CFG;
 605          require_once($CFG->dirroot . '/user/externallib.php');
 606          return new external_multiple_structure(core_user_external::user_description());
 607      }
 608  
 609      /**
 610       * Returns description of method parameters
 611       *
 612       * @return external_function_parameters
 613       */
 614      public static function search_users_parameters(): external_function_parameters {
 615          return new external_function_parameters(
 616              [
 617                  'courseid' => new external_value(PARAM_INT, 'course id'),
 618                  'search' => new external_value(PARAM_RAW, 'query'),
 619                  'searchanywhere' => new external_value(PARAM_BOOL, 'find a match anywhere, or only at the beginning'),
 620                  'page' => new external_value(PARAM_INT, 'Page number'),
 621                  'perpage' => new external_value(PARAM_INT, 'Number per page'),
 622              ]
 623          );
 624      }
 625  
 626      /**
 627       * Search course participants.
 628       *
 629       * @param int $courseid Course id
 630       * @param string $search The query
 631       * @param bool $searchanywhere Match anywhere in the string
 632       * @param int $page Page number
 633       * @param int $perpage Max per page
 634       * @return array An array of users
 635       * @throws moodle_exception
 636       */
 637      public static function search_users(int $courseid, string $search, bool $searchanywhere, int $page, int $perpage): array {
 638          global $PAGE, $DB, $CFG;
 639  
 640          require_once($CFG->dirroot.'/enrol/locallib.php');
 641          require_once($CFG->dirroot.'/user/lib.php');
 642  
 643          $params = self::validate_parameters(
 644                  self::search_users_parameters(),
 645                  [
 646                      'courseid'       => $courseid,
 647                      'search'         => $search,
 648                      'searchanywhere' => $searchanywhere,
 649                      'page'           => $page,
 650                      'perpage'        => $perpage
 651                  ]
 652          );
 653          $context = context_course::instance($params['courseid']);
 654          try {
 655              self::validate_context($context);
 656          } catch (Exception $e) {
 657              $exceptionparam = new stdClass();
 658              $exceptionparam->message = $e->getMessage();
 659              $exceptionparam->courseid = $params['courseid'];
 660              throw new moodle_exception('errorcoursecontextnotvalid' , 'webservice', '', $exceptionparam);
 661          }
 662          course_require_view_participants($context);
 663  
 664          $course = get_course($params['courseid']);
 665          $manager = new course_enrolment_manager($PAGE, $course);
 666  
 667          $users = $manager->search_users($params['search'],
 668                                          $params['searchanywhere'],
 669                                          $params['page'],
 670                                          $params['perpage']);
 671  
 672          $results = [];
 673          // Add also extra user fields.
 674          $requiredfields = array_merge(
 675                  ['id', 'fullname', 'profileimageurl', 'profileimageurlsmall'],
 676                  // TODO Does not support custom user profile fields (MDL-70456).
 677                  \core_user\fields::get_identity_fields($context, false)
 678          );
 679          foreach ($users['users'] as $user) {
 680              if ($userdetails = user_get_user_details($user, $course, $requiredfields)) {
 681                  $results[] = $userdetails;
 682              }
 683          }
 684          return $results;
 685      }
 686  
 687      /**
 688       * Returns description of method result value
 689       *
 690       * @return external_multiple_structure
 691       */
 692      public static function search_users_returns(): external_multiple_structure {
 693          global $CFG;
 694          require_once($CFG->dirroot . '/user/externallib.php');
 695          return new external_multiple_structure(core_user_external::user_description());
 696      }
 697  
 698      /**
 699       * Returns description of method parameters
 700       *
 701       * @return external_function_parameters
 702       */
 703      public static function get_enrolled_users_parameters() {
 704          return new external_function_parameters(
 705              [
 706                  'courseid' => new external_value(PARAM_INT, 'course id'),
 707                  'options'  => new external_multiple_structure(
 708                      new external_single_structure(
 709                          [
 710                              'name'  => new external_value(PARAM_ALPHANUMEXT, 'option name'),
 711                              'value' => new external_value(PARAM_RAW, 'option value')
 712                          ]
 713                      ), 'Option names:
 714                              * withcapability (string) return only users with this capability. This option requires \'moodle/role:review\' on the course context.
 715                              * groupid (integer) return only users in this group id. If the course has groups enabled and this param
 716                                                  isn\'t defined, returns all the viewable users.
 717                                                  This option requires \'moodle/site:accessallgroups\' on the course context if the
 718                                                  user doesn\'t belong to the group.
 719                              * onlyactive (integer) return only users with active enrolments and matching time restrictions.
 720                                                  This option requires \'moodle/course:enrolreview\' on the course context.
 721                                                  Please note that this option can\'t
 722                                                  be used together with onlysuspended (only one can be active).
 723                              * onlysuspended (integer) return only suspended users. This option requires
 724                                              \'moodle/course:enrolreview\' on the course context. Please note that this option can\'t
 725                                                  be used together with onlyactive (only one can be active).
 726                              * userfields (\'string, string, ...\') return only the values of these user fields.
 727                              * limitfrom (integer) sql limit from.
 728                              * limitnumber (integer) maximum number of returned users.
 729                              * sortby (string) sort by id, firstname or lastname. For ordering like the site does, use siteorder.
 730                              * sortdirection (string) ASC or DESC',
 731                              VALUE_DEFAULT, []),
 732              ]
 733          );
 734      }
 735  
 736      /**
 737       * Get course participants details
 738       *
 739       * @param int $courseid  course id
 740       * @param array $options options {
 741       *                                'name' => option name
 742       *                                'value' => option value
 743       *                               }
 744       * @return array An array of users
 745       */
 746      public static function get_enrolled_users($courseid, $options = []) {
 747          global $CFG, $USER, $DB;
 748  
 749          require_once($CFG->dirroot . '/course/lib.php');
 750          require_once($CFG->dirroot . "/user/lib.php");
 751  
 752          $params = self::validate_parameters(
 753              self::get_enrolled_users_parameters(),
 754              [
 755                  'courseid'=>$courseid,
 756                  'options'=>$options
 757              ]
 758          );
 759          $withcapability = '';
 760          $groupid        = 0;
 761          $onlyactive     = false;
 762          $onlysuspended  = false;
 763          $userfields     = [];
 764          $limitfrom = 0;
 765          $limitnumber = 0;
 766          $sortby = 'us.id';
 767          $sortparams = [];
 768          $sortdirection = 'ASC';
 769          foreach ($options as $option) {
 770              switch ($option['name']) {
 771                  case 'withcapability':
 772                      $withcapability = $option['value'];
 773                      break;
 774                  case 'groupid':
 775                      $groupid = (int)$option['value'];
 776                      break;
 777                  case 'onlyactive':
 778                      $onlyactive = !empty($option['value']);
 779                      break;
 780                  case 'onlysuspended':
 781                      $onlysuspended = !empty($option['value']);
 782                      break;
 783                  case 'userfields':
 784                      $thefields = explode(',', $option['value']);
 785                      foreach ($thefields as $f) {
 786                          $userfields[] = clean_param($f, PARAM_ALPHANUMEXT);
 787                      }
 788                      break;
 789                  case 'limitfrom' :
 790                      $limitfrom = clean_param($option['value'], PARAM_INT);
 791                      break;
 792                  case 'limitnumber' :
 793                      $limitnumber = clean_param($option['value'], PARAM_INT);
 794                      break;
 795                  case 'sortby':
 796                      $sortallowedvalues = ['id', 'firstname', 'lastname', 'siteorder'];
 797                      if (!in_array($option['value'], $sortallowedvalues)) {
 798                          throw new invalid_parameter_exception('Invalid value for sortby parameter (value: ' .
 799                              $option['value'] . '), allowed values are: ' . implode(',', $sortallowedvalues));
 800                      }
 801                      if ($option['value'] == 'siteorder') {
 802                          list($sortby, $sortparams) = users_order_by_sql('us');
 803                      } else {
 804                          $sortby = 'us.' . $option['value'];
 805                      }
 806                      break;
 807                  case 'sortdirection':
 808                      $sortdirection = strtoupper($option['value']);
 809                      $directionallowedvalues = ['ASC', 'DESC'];
 810                      if (!in_array($sortdirection, $directionallowedvalues)) {
 811                          throw new invalid_parameter_exception('Invalid value for sortdirection parameter
 812                          (value: ' . $sortdirection . '),' . 'allowed values are: ' . implode(',', $directionallowedvalues));
 813                      }
 814                      break;
 815              }
 816          }
 817  
 818          $course = $DB->get_record('course', ['id' => $courseid], '*', MUST_EXIST);
 819          $coursecontext = context_course::instance($courseid, IGNORE_MISSING);
 820          if ($courseid == SITEID) {
 821              $context = context_system::instance();
 822          } else {
 823              $context = $coursecontext;
 824          }
 825          try {
 826              self::validate_context($context);
 827          } catch (Exception $e) {
 828              $exceptionparam = new stdClass();
 829              $exceptionparam->message = $e->getMessage();
 830              $exceptionparam->courseid = $params['courseid'];
 831              throw new moodle_exception('errorcoursecontextnotvalid' , 'webservice', '', $exceptionparam);
 832          }
 833  
 834          course_require_view_participants($context);
 835  
 836          // to overwrite this parameter, you need role:review capability
 837          if ($withcapability) {
 838              require_capability('moodle/role:review', $coursecontext);
 839          }
 840          // need accessallgroups capability if you want to overwrite this option
 841          if (!empty($groupid) && !groups_is_member($groupid)) {
 842              require_capability('moodle/site:accessallgroups', $coursecontext);
 843          }
 844          // to overwrite this option, you need course:enrolereview permission
 845          if ($onlyactive || $onlysuspended) {
 846              require_capability('moodle/course:enrolreview', $coursecontext);
 847          }
 848  
 849          list($enrolledsql, $enrolledparams) = get_enrolled_sql($coursecontext, $withcapability, $groupid, $onlyactive,
 850          $onlysuspended);
 851          $ctxselect = ', ' . context_helper::get_preload_record_columns_sql('ctx');
 852          $ctxjoin = "LEFT JOIN {context} ctx ON (ctx.instanceid = u.id AND ctx.contextlevel = :contextlevel)";
 853          $enrolledparams['contextlevel'] = CONTEXT_USER;
 854  
 855          $groupjoin = '';
 856          if (empty($groupid) && groups_get_course_groupmode($course) == SEPARATEGROUPS &&
 857                  !has_capability('moodle/site:accessallgroups', $coursecontext)) {
 858              // Filter by groups the user can view.
 859              $usergroups = groups_get_user_groups($course->id);
 860              if (!empty($usergroups['0'])) {
 861                  list($groupsql, $groupparams) = $DB->get_in_or_equal($usergroups['0'], SQL_PARAMS_NAMED);
 862                  $groupjoin = "JOIN {groups_members} gm ON (u.id = gm.userid AND gm.groupid $groupsql)";
 863                  $enrolledparams = array_merge($enrolledparams, $groupparams);
 864              } else {
 865                  // User doesn't belong to any group, so he can't see any user. Return an empty array.
 866                  return [];
 867              }
 868          }
 869          $sql = "SELECT us.*, COALESCE(ul.timeaccess, 0) AS lastcourseaccess
 870                    FROM {user} us
 871                    JOIN (
 872                        SELECT DISTINCT u.id $ctxselect
 873                          FROM {user} u $ctxjoin $groupjoin
 874                         WHERE u.id IN ($enrolledsql)
 875                    ) q ON q.id = us.id
 876               LEFT JOIN {user_lastaccess} ul ON (ul.userid = us.id AND ul.courseid = :courseid)
 877                  ORDER BY $sortby $sortdirection";
 878          $enrolledparams = array_merge($enrolledparams, $sortparams);
 879          $enrolledparams['courseid'] = $courseid;
 880  
 881          $enrolledusers = $DB->get_recordset_sql($sql, $enrolledparams, $limitfrom, $limitnumber);
 882          $users = [];
 883          foreach ($enrolledusers as $user) {
 884              context_helper::preload_from_record($user);
 885              if ($userdetails = user_get_user_details($user, $course, $userfields)) {
 886                  $users[] = $userdetails;
 887              }
 888          }
 889          $enrolledusers->close();
 890  
 891          return $users;
 892      }
 893  
 894      /**
 895       * Returns description of method result value
 896       *
 897       * @return external_description
 898       */
 899      public static function get_enrolled_users_returns() {
 900          return new external_multiple_structure(
 901              new external_single_structure(
 902                  [
 903                      'id'    => new external_value(PARAM_INT, 'ID of the user'),
 904                      'username'    => new external_value(PARAM_RAW, 'Username policy is defined in Moodle security config', VALUE_OPTIONAL),
 905                      'firstname'   => new external_value(PARAM_NOTAGS, 'The first name(s) of the user', VALUE_OPTIONAL),
 906                      'lastname'    => new external_value(PARAM_NOTAGS, 'The family name of the user', VALUE_OPTIONAL),
 907                      'fullname'    => new external_value(PARAM_NOTAGS, 'The fullname of the user'),
 908                      'email'       => new external_value(PARAM_TEXT, 'An email address - allow email as root@localhost', VALUE_OPTIONAL),
 909                      'address'     => new external_value(PARAM_TEXT, 'Postal address', VALUE_OPTIONAL),
 910                      'phone1'      => new external_value(PARAM_NOTAGS, 'Phone 1', VALUE_OPTIONAL),
 911                      'phone2'      => new external_value(PARAM_NOTAGS, 'Phone 2', VALUE_OPTIONAL),
 912                      'department'  => new external_value(PARAM_TEXT, 'department', VALUE_OPTIONAL),
 913                      'institution' => new external_value(PARAM_TEXT, 'institution', VALUE_OPTIONAL),
 914                      'idnumber'    => new external_value(PARAM_RAW, 'An arbitrary ID code number perhaps from the institution', VALUE_OPTIONAL),
 915                      'interests'   => new external_value(PARAM_TEXT, 'user interests (separated by commas)', VALUE_OPTIONAL),
 916                      'firstaccess' => new external_value(PARAM_INT, 'first access to the site (0 if never)', VALUE_OPTIONAL),
 917                      'lastaccess'  => new external_value(PARAM_INT, 'last access to the site (0 if never)', VALUE_OPTIONAL),
 918                      'lastcourseaccess'  => new external_value(PARAM_INT, 'last access to the course (0 if never)', VALUE_OPTIONAL),
 919                      'description' => new external_value(PARAM_RAW, 'User profile description', VALUE_OPTIONAL),
 920                      'descriptionformat' => new external_format_value('description', VALUE_OPTIONAL),
 921                      'city'        => new external_value(PARAM_NOTAGS, 'Home city of the user', VALUE_OPTIONAL),
 922                      'country'     => new external_value(PARAM_ALPHA, 'Home country code of the user, such as AU or CZ', VALUE_OPTIONAL),
 923                      'profileimageurlsmall' => new external_value(PARAM_URL, 'User image profile URL - small version', VALUE_OPTIONAL),
 924                      'profileimageurl' => new external_value(PARAM_URL, 'User image profile URL - big version', VALUE_OPTIONAL),
 925                      'customfields' => new external_multiple_structure(
 926                          new external_single_structure(
 927                              [
 928                                  'type'  => new external_value(PARAM_ALPHANUMEXT, 'The type of the custom field - text field, checkbox...'),
 929                                  'value' => new external_value(PARAM_RAW, 'The value of the custom field'),
 930                                  'name' => new external_value(PARAM_RAW, 'The name of the custom field'),
 931                                  'shortname' => new external_value(PARAM_RAW, 'The shortname of the custom field - to be able to build the field class in the code'),
 932                              ]
 933                          ), 'User custom fields (also known as user profil fields)', VALUE_OPTIONAL),
 934                      'groups' => new external_multiple_structure(
 935                          new external_single_structure(
 936                              [
 937                                  'id'  => new external_value(PARAM_INT, 'group id'),
 938                                  'name' => new external_value(PARAM_RAW, 'group name'),
 939                                  'description' => new external_value(PARAM_RAW, 'group description'),
 940                                  'descriptionformat' => new external_format_value('description'),
 941                              ]
 942                          ), 'user groups', VALUE_OPTIONAL),
 943                      'roles' => new external_multiple_structure(
 944                          new external_single_structure(
 945                              [
 946                                  'roleid'       => new external_value(PARAM_INT, 'role id'),
 947                                  'name'         => new external_value(PARAM_RAW, 'role name'),
 948                                  'shortname'    => new external_value(PARAM_ALPHANUMEXT, 'role shortname'),
 949                                  'sortorder'    => new external_value(PARAM_INT, 'role sortorder')
 950                              ]
 951                          ), 'user roles', VALUE_OPTIONAL),
 952                      'preferences' => new external_multiple_structure(
 953                          new external_single_structure(
 954                              [
 955                                  'name'  => new external_value(PARAM_RAW, 'The name of the preferences'),
 956                                  'value' => new external_value(PARAM_RAW, 'The value of the custom field'),
 957                              ]
 958                      ), 'User preferences', VALUE_OPTIONAL),
 959                      'enrolledcourses' => new external_multiple_structure(
 960                          new external_single_structure(
 961                              [
 962                                  'id'  => new external_value(PARAM_INT, 'Id of the course'),
 963                                  'fullname' => new external_value(PARAM_RAW, 'Fullname of the course'),
 964                                  'shortname' => new external_value(PARAM_RAW, 'Shortname of the course')
 965                              ]
 966                      ), 'Courses where the user is enrolled - limited by which courses the user is able to see', VALUE_OPTIONAL)
 967                  ]
 968              )
 969          );
 970      }
 971  
 972      /**
 973       * Returns description of get_course_enrolment_methods() parameters
 974       *
 975       * @return external_function_parameters
 976       */
 977      public static function get_course_enrolment_methods_parameters() {
 978          return new external_function_parameters(
 979              array(
 980                  'courseid' => new external_value(PARAM_INT, 'Course id')
 981              )
 982          );
 983      }
 984  
 985      /**
 986       * Get list of active course enrolment methods for current user.
 987       *
 988       * @param int $courseid
 989       * @return array of course enrolment methods
 990       * @throws moodle_exception
 991       */
 992      public static function get_course_enrolment_methods($courseid) {
 993          global $DB;
 994  
 995          $params = self::validate_parameters(self::get_course_enrolment_methods_parameters(), array('courseid' => $courseid));
 996          self::validate_context(context_system::instance());
 997  
 998          $course = $DB->get_record('course', array('id' => $params['courseid']), '*', MUST_EXIST);
 999          if (!core_course_category::can_view_course_info($course) && !can_access_course($course)) {
1000              throw new moodle_exception('coursehidden');
1001          }
1002  
1003          $result = array();
1004          $enrolinstances = enrol_get_instances($params['courseid'], true);
1005          foreach ($enrolinstances as $enrolinstance) {
1006              if ($enrolplugin = enrol_get_plugin($enrolinstance->enrol)) {
1007                  if ($instanceinfo = $enrolplugin->get_enrol_info($enrolinstance)) {
1008                      $result[] = (array) $instanceinfo;
1009                  }
1010              }
1011          }
1012          return $result;
1013      }
1014  
1015      /**
1016       * Returns description of get_course_enrolment_methods() result value
1017       *
1018       * @return external_description
1019       */
1020      public static function get_course_enrolment_methods_returns() {
1021          return new external_multiple_structure(
1022              new external_single_structure(
1023                  array(
1024                      'id' => new external_value(PARAM_INT, 'id of course enrolment instance'),
1025                      'courseid' => new external_value(PARAM_INT, 'id of course'),
1026                      'type' => new external_value(PARAM_PLUGIN, 'type of enrolment plugin'),
1027                      'name' => new external_value(PARAM_RAW, 'name of enrolment plugin'),
1028                      'status' => new external_value(PARAM_RAW, 'status of enrolment plugin'),
1029                      'wsfunction' => new external_value(PARAM_ALPHANUMEXT, 'webservice function to get more information', VALUE_OPTIONAL),
1030                  )
1031              )
1032          );
1033      }
1034  
1035      /**
1036       * Returns description of submit_user_enrolment_form parameters.
1037       *
1038       * @return external_function_parameters.
1039       */
1040      public static function submit_user_enrolment_form_parameters() {
1041          return new external_function_parameters([
1042              'formdata' => new external_value(PARAM_RAW, 'The data from the event form'),
1043          ]);
1044      }
1045  
1046      /**
1047       * External function that handles the user enrolment form submission.
1048       *
1049       * @param string $formdata The user enrolment form data in s URI encoded param string
1050       * @return array An array consisting of the processing result and error flag, if available
1051       */
1052      public static function submit_user_enrolment_form($formdata) {
1053          global $CFG, $DB, $PAGE;
1054  
1055          // Parameter validation.
1056          $params = self::validate_parameters(self::submit_user_enrolment_form_parameters(), ['formdata' => $formdata]);
1057  
1058          $data = [];
1059          parse_str($params['formdata'], $data);
1060  
1061          $userenrolment = $DB->get_record('user_enrolments', ['id' => $data['ue']], '*', MUST_EXIST);
1062          $instance = $DB->get_record('enrol', ['id' => $userenrolment->enrolid], '*', MUST_EXIST);
1063          $plugin = enrol_get_plugin($instance->enrol);
1064          $course = get_course($instance->courseid);
1065          $context = context_course::instance($course->id);
1066          self::validate_context($context);
1067  
1068          require_once("$CFG->dirroot/enrol/editenrolment_form.php");
1069          $customformdata = [
1070              'ue' => $userenrolment,
1071              'modal' => true,
1072              'enrolinstancename' => $plugin->get_instance_name($instance)
1073          ];
1074          $mform = new enrol_user_enrolment_form(null, $customformdata, 'post', '', null, true, $data);
1075  
1076          if ($validateddata = $mform->get_data()) {
1077              if (!empty($validateddata->duration) && $validateddata->timeend == 0) {
1078                  $validateddata->timeend = $validateddata->timestart + $validateddata->duration;
1079              }
1080              require_once($CFG->dirroot . '/enrol/locallib.php');
1081              $manager = new course_enrolment_manager($PAGE, $course);
1082              $result = $manager->edit_enrolment($userenrolment, $validateddata);
1083  
1084              return ['result' => $result];
1085          } else {
1086              return ['result' => false, 'validationerror' => true];
1087          }
1088      }
1089  
1090      /**
1091       * Returns description of submit_user_enrolment_form() result value
1092       *
1093       * @return external_description
1094       */
1095      public static function submit_user_enrolment_form_returns() {
1096          return new external_single_structure([
1097              'result' => new external_value(PARAM_BOOL, 'True if the user\'s enrolment was successfully updated'),
1098              'validationerror' => new external_value(PARAM_BOOL, 'Indicates invalid form data', VALUE_DEFAULT, false),
1099          ]);
1100      }
1101  
1102      /**
1103       * Returns description of unenrol_user_enrolment() parameters
1104       *
1105       * @return external_function_parameters
1106       */
1107      public static function unenrol_user_enrolment_parameters() {
1108          return new external_function_parameters(
1109              array(
1110                  'ueid' => new external_value(PARAM_INT, 'User enrolment ID')
1111              )
1112          );
1113      }
1114  
1115      /**
1116       * External function that unenrols a given user enrolment.
1117       *
1118       * @param int $ueid The user enrolment ID.
1119       * @return array An array consisting of the processing result, errors.
1120       */
1121      public static function unenrol_user_enrolment($ueid) {
1122          global $CFG, $DB, $PAGE;
1123  
1124          $params = self::validate_parameters(self::unenrol_user_enrolment_parameters(), [
1125              'ueid' => $ueid
1126          ]);
1127  
1128          $result = false;
1129          $errors = [];
1130  
1131          $userenrolment = $DB->get_record('user_enrolments', ['id' => $params['ueid']], '*');
1132          if ($userenrolment) {
1133              $userid = $userenrolment->userid;
1134              $enrolid = $userenrolment->enrolid;
1135              $enrol = $DB->get_record('enrol', ['id' => $enrolid], '*', MUST_EXIST);
1136              $courseid = $enrol->courseid;
1137              $course = get_course($courseid);
1138              $context = context_course::instance($course->id);
1139              self::validate_context($context);
1140          } else {
1141              $validationerrors['invalidrequest'] = get_string('invalidrequest', 'enrol');
1142          }
1143  
1144          // If the userenrolment exists, unenrol the user.
1145          if (!isset($validationerrors)) {
1146              require_once($CFG->dirroot . '/enrol/locallib.php');
1147              $manager = new course_enrolment_manager($PAGE, $course);
1148              $result = $manager->unenrol_user($userenrolment);
1149          } else {
1150              foreach ($validationerrors as $key => $errormessage) {
1151                  $errors[] = (object)[
1152                      'key' => $key,
1153                      'message' => $errormessage
1154                  ];
1155              }
1156          }
1157  
1158          return [
1159              'result' => $result,
1160              'errors' => $errors,
1161          ];
1162      }
1163  
1164      /**
1165       * Returns description of unenrol_user_enrolment() result value
1166       *
1167       * @return external_description
1168       */
1169      public static function unenrol_user_enrolment_returns() {
1170          return new external_single_structure(
1171              array(
1172                  'result' => new external_value(PARAM_BOOL, 'True if the user\'s enrolment was successfully updated'),
1173                  'errors' => new external_multiple_structure(
1174                      new external_single_structure(
1175                          array(
1176                              'key' => new external_value(PARAM_TEXT, 'The data that failed the validation'),
1177                              'message' => new external_value(PARAM_TEXT, 'The error message'),
1178                          )
1179                      ), 'List of validation errors'
1180                  ),
1181              )
1182          );
1183      }
1184  }
1185  
1186  /**
1187   * Role external functions
1188   *
1189   * @package    core_role
1190   * @category   external
1191   * @copyright  2011 Jerome Mouneyrac
1192   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1193   * @since Moodle 2.2
1194   */
1195  class core_role_external extends external_api {
1196  
1197      /**
1198       * Returns description of method parameters
1199       *
1200       * @return external_function_parameters
1201       */
1202      public static function assign_roles_parameters() {
1203          return new external_function_parameters(
1204              array(
1205                  'assignments' => new external_multiple_structure(
1206                      new external_single_structure(
1207                          array(
1208                              'roleid'    => new external_value(PARAM_INT, 'Role to assign to the user'),
1209                              'userid'    => new external_value(PARAM_INT, 'The user that is going to be assigned'),
1210                              'contextid' => new external_value(PARAM_INT, 'The context to assign the user role in', VALUE_OPTIONAL),
1211                              'contextlevel' => new external_value(PARAM_ALPHA, 'The context level to assign the user role in
1212                                      (block, course, coursecat, system, user, module)', VALUE_OPTIONAL),
1213                              'instanceid' => new external_value(PARAM_INT, 'The Instance id of item where the role needs to be assigned', VALUE_OPTIONAL),
1214                          )
1215                      )
1216                  )
1217              )
1218          );
1219      }
1220  
1221      /**
1222       * Manual role assignments to users
1223       *
1224       * @param array $assignments An array of manual role assignment
1225       */
1226      public static function assign_roles($assignments) {
1227          global $DB;
1228  
1229          // Do basic automatic PARAM checks on incoming data, using params description
1230          // If any problems are found then exceptions are thrown with helpful error messages
1231          $params = self::validate_parameters(self::assign_roles_parameters(), array('assignments'=>$assignments));
1232  
1233          $transaction = $DB->start_delegated_transaction();
1234  
1235          foreach ($params['assignments'] as $assignment) {
1236              // Ensure correct context level with a instance id or contextid is passed.
1237              $context = self::get_context_from_params($assignment);
1238  
1239              // Ensure the current user is allowed to run this function in the enrolment context.
1240              self::validate_context($context);
1241              require_capability('moodle/role:assign', $context);
1242  
1243              // throw an exception if user is not able to assign the role in this context
1244              $roles = get_assignable_roles($context, ROLENAME_SHORT);
1245  
1246              if (!array_key_exists($assignment['roleid'], $roles)) {
1247                  throw new invalid_parameter_exception('Can not assign roleid='.$assignment['roleid'].' in contextid='.$assignment['contextid']);
1248              }
1249  
1250              role_assign($assignment['roleid'], $assignment['userid'], $context->id);
1251          }
1252  
1253          $transaction->allow_commit();
1254      }
1255  
1256      /**
1257       * Returns description of method result value
1258       *
1259       * @return null
1260       */
1261      public static function assign_roles_returns() {
1262          return null;
1263      }
1264  
1265  
1266      /**
1267       * Returns description of method parameters
1268       *
1269       * @return external_function_parameters
1270       */
1271      public static function unassign_roles_parameters() {
1272          return new external_function_parameters(
1273              array(
1274                  'unassignments' => new external_multiple_structure(
1275                      new external_single_structure(
1276                          array(
1277                              'roleid'    => new external_value(PARAM_INT, 'Role to assign to the user'),
1278                              'userid'    => new external_value(PARAM_INT, 'The user that is going to be assigned'),
1279                              'contextid' => new external_value(PARAM_INT, 'The context to unassign the user role from', VALUE_OPTIONAL),
1280                              'contextlevel' => new external_value(PARAM_ALPHA, 'The context level to unassign the user role in
1281  +                                    (block, course, coursecat, system, user, module)', VALUE_OPTIONAL),
1282                              'instanceid' => new external_value(PARAM_INT, 'The Instance id of item where the role needs to be unassigned', VALUE_OPTIONAL),
1283                          )
1284                      )
1285                  )
1286              )
1287          );
1288      }
1289  
1290       /**
1291       * Unassign roles from users
1292       *
1293       * @param array $unassignments An array of unassignment
1294       */
1295      public static function unassign_roles($unassignments) {
1296           global $DB;
1297  
1298          // Do basic automatic PARAM checks on incoming data, using params description
1299          // If any problems are found then exceptions are thrown with helpful error messages
1300          $params = self::validate_parameters(self::unassign_roles_parameters(), array('unassignments'=>$unassignments));
1301  
1302          $transaction = $DB->start_delegated_transaction();
1303  
1304          foreach ($params['unassignments'] as $unassignment) {
1305              // Ensure the current user is allowed to run this function in the unassignment context
1306              $context = self::get_context_from_params($unassignment);
1307              self::validate_context($context);
1308              require_capability('moodle/role:assign', $context);
1309  
1310              // throw an exception if user is not able to unassign the role in this context
1311              $roles = get_assignable_roles($context, ROLENAME_SHORT);
1312              if (!array_key_exists($unassignment['roleid'], $roles)) {
1313                  throw new invalid_parameter_exception('Can not unassign roleid='.$unassignment['roleid'].' in contextid='.$unassignment['contextid']);
1314              }
1315  
1316              role_unassign($unassignment['roleid'], $unassignment['userid'], $context->id);
1317          }
1318  
1319          $transaction->allow_commit();
1320      }
1321  
1322     /**
1323       * Returns description of method result value
1324       *
1325       * @return null
1326       */
1327      public static function unassign_roles_returns() {
1328          return null;
1329      }
1330  }