Search moodle.org's
Developer Documentation

See Release Notes

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

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

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