Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 3.9.x will end* 10 May 2021 (12 months).
  • Bug fixes for security issues in 3.9.x will end* 8 May 2023 (36 months).
  • PHP version: minimum PHP 7.2.0 Note: minimum PHP version has increased since Moodle 3.8. PHP 7.3.x and 7.4.x are supported too.

Differences Between: [Versions 39 and 311] [Versions 39 and 400] [Versions 39 and 401] [Versions 39 and 402] [Versions 39 and 403]

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