Search moodle.org's
Developer Documentation

  • Bug fixes for general core bugs in 3.11.x will end 14 Nov 2022 (12 months plus 6 months extension).
  • Bug fixes for security issues in 3.11.x will end 13 Nov 2023 (18 months plus 12 months extension).
  • PHP version: minimum PHP 7.3.0 Note: minimum PHP version has increased since Moodle 3.10. PHP 7.4.x is supported too.
  • Differences Between: [Versions 310 and 311] [Versions 311 and 400] [Versions 37 and 311] [Versions 38 and 311] [Versions 39 and 311]

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