Search moodle.org's
Developer Documentation

See Release Notes

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

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

   1  <?php
   2  // This file is part of Moodle - http://moodle.org/
   3  //
   4  // Moodle is free software: you can redistribute it and/or modify
   5  // it under the terms of the GNU General Public License as published by
   6  // the Free Software Foundation, either version 3 of the License, or
   7  // (at your option) any later version.
   8  //
   9  // Moodle is distributed in the hope that it will be useful,
  10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  12  // GNU General Public License for more details.
  13  //
  14  // You should have received a copy of the GNU General Public License
  15  // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
  16  
  17  use core_external\external_api;
  18  use core_external\external_format_value;
  19  use core_external\external_function_parameters;
  20  use core_external\external_multiple_structure;
  21  use core_external\external_single_structure;
  22  use core_external\external_value;
  23  use core_external\external_warnings;
  24  use core_external\util;
  25  use core_group\visibility;
  26  
  27  defined('MOODLE_INTERNAL') || die();
  28  
  29  require_once($CFG->dirroot . '/group/lib.php');
  30  
  31  /**
  32   * Group external functions
  33   *
  34   * @package    core_group
  35   * @category   external
  36   * @copyright  2011 Jerome Mouneyrac
  37   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  38   * @since Moodle 2.2
  39   */
  40  class core_group_external extends external_api {
  41  
  42  
  43      /**
  44       * Validate visibility.
  45       *
  46       * @param int $visibility Visibility string, must one of the visibility class constants.
  47       * @throws invalid_parameter_exception if visibility is not an allowed value.
  48       */
  49      protected static function validate_visibility(int $visibility): void {
  50          $allowed = [
  51              GROUPS_VISIBILITY_ALL,
  52              GROUPS_VISIBILITY_MEMBERS,
  53              GROUPS_VISIBILITY_OWN,
  54              GROUPS_VISIBILITY_NONE,
  55          ];
  56          if (!array_key_exists($visibility, $allowed)) {
  57              throw new invalid_parameter_exception('Invalid group visibility provided. Must be one of '
  58                      . join(',', $allowed));
  59          }
  60      }
  61  
  62      /**
  63       * Returns description of method parameters
  64       *
  65       * @return external_function_parameters
  66       * @since Moodle 2.2
  67       */
  68      public static function create_groups_parameters() {
  69          return new external_function_parameters(
  70              array(
  71                  'groups' => new external_multiple_structure(
  72                      new external_single_structure(
  73                          array(
  74                              'courseid' => new external_value(PARAM_INT, 'id of course'),
  75                              'name' => new external_value(PARAM_TEXT, 'multilang compatible name, course unique'),
  76                              'description' => new external_value(PARAM_RAW, 'group description text'),
  77                              'descriptionformat' => new external_format_value('description', VALUE_DEFAULT),
  78                              'enrolmentkey' => new external_value(PARAM_RAW, 'group enrol secret phrase', VALUE_OPTIONAL),
  79                              'idnumber' => new external_value(PARAM_RAW, 'id number', VALUE_OPTIONAL),
  80                              'visibility' => new external_value(PARAM_INT,
  81                                      'group visibility mode. 0 = Visible to all. 1 = Visible to members. '
  82                                      . '2 = See own membership. 3 = Membership is hidden. default: 0',
  83                                      VALUE_DEFAULT, 0),
  84                              'participation' => new external_value(PARAM_BOOL,
  85                                      'activity participation enabled? Only for "all" and "members" visibility. Default true.',
  86                                      VALUE_DEFAULT, true),
  87                              'customfields' => self::build_custom_fields_parameters_structure(),
  88                          )
  89                      ), 'List of group object. A group has a courseid, a name, a description and an enrolment key.'
  90                  )
  91              )
  92          );
  93      }
  94  
  95      /**
  96       * Create groups
  97       *
  98       * @param array $groups array of group description arrays (with keys groupname and courseid)
  99       * @return array of newly created groups
 100       * @since Moodle 2.2
 101       */
 102      public static function create_groups($groups) {
 103          global $CFG, $DB;
 104          require_once("$CFG->dirroot/group/lib.php");
 105  
 106          $params = self::validate_parameters(self::create_groups_parameters(), array('groups'=>$groups));
 107  
 108          $transaction = $DB->start_delegated_transaction();
 109  
 110          $groups = array();
 111  
 112          foreach ($params['groups'] as $group) {
 113              $group = (object)$group;
 114  
 115              if (trim($group->name) == '') {
 116                  throw new invalid_parameter_exception('Invalid group name');
 117              }
 118              if ($DB->get_record('groups', array('courseid'=>$group->courseid, 'name'=>$group->name))) {
 119                  throw new invalid_parameter_exception('Group with the same name already exists in the course');
 120              }
 121  
 122              // now security checks
 123              $context = context_course::instance($group->courseid, IGNORE_MISSING);
 124              try {
 125                  self::validate_context($context);
 126              } catch (Exception $e) {
 127                  $exceptionparam = new stdClass();
 128                  $exceptionparam->message = $e->getMessage();
 129                  $exceptionparam->courseid = $group->courseid;
 130                  throw new moodle_exception('errorcoursecontextnotvalid' , 'webservice', '', $exceptionparam);
 131              }
 132              require_capability('moodle/course:managegroups', $context);
 133  
 134              // Validate format.
 135              $group->descriptionformat = util::validate_format($group->descriptionformat);
 136  
 137              // Validate visibility.
 138              self::validate_visibility($group->visibility);
 139  
 140              // Custom fields.
 141              if (!empty($group->customfields)) {
 142                  foreach ($group->customfields as $field) {
 143                      $fieldname = self::build_custom_field_name($field['shortname']);
 144                      $group->{$fieldname} = $field['value'];
 145                  }
 146              }
 147  
 148              // finally create the group
 149              $group->id = groups_create_group($group, false);
 150              if (!isset($group->enrolmentkey)) {
 151                  $group->enrolmentkey = '';
 152              }
 153              if (!isset($group->idnumber)) {
 154                  $group->idnumber = '';
 155              }
 156  
 157              $groups[] = (array)$group;
 158          }
 159  
 160          $transaction->allow_commit();
 161  
 162          return $groups;
 163      }
 164  
 165      /**
 166       * Returns description of method result value
 167       *
 168       * @return \core_external\external_description
 169       * @since Moodle 2.2
 170       */
 171      public static function create_groups_returns() {
 172          return new external_multiple_structure(
 173              new external_single_structure(
 174                  array(
 175                      'id' => new external_value(PARAM_INT, 'group record id'),
 176                      'courseid' => new external_value(PARAM_INT, 'id of course'),
 177                      'name' => new external_value(PARAM_TEXT, 'multilang compatible name, course unique'),
 178                      'description' => new external_value(PARAM_RAW, 'group description text'),
 179                      'descriptionformat' => new external_format_value('description'),
 180                      'enrolmentkey' => new external_value(PARAM_RAW, 'group enrol secret phrase'),
 181                      'idnumber' => new external_value(PARAM_RAW, 'id number'),
 182                      'visibility' => new external_value(PARAM_INT,
 183                              'group visibility mode. 0 = Visible to all. 1 = Visible to members. 2 = See own membership. '
 184                              . '3 = Membership is hidden.'),
 185                      'participation' => new external_value(PARAM_BOOL, 'participation mode'),
 186                      'customfields' => self::build_custom_fields_parameters_structure(),
 187                  )
 188              ), 'List of group object. A group has an id, a courseid, a name, a description and an enrolment key.'
 189          );
 190      }
 191  
 192      /**
 193       * Returns description of method parameters
 194       *
 195       * @return external_function_parameters
 196       * @since Moodle 2.2
 197       */
 198      public static function get_groups_parameters() {
 199          return new external_function_parameters(
 200              array(
 201                  'groupids' => new external_multiple_structure(new external_value(PARAM_INT, 'Group ID')
 202                          ,'List of group id. A group id is an integer.'),
 203              )
 204          );
 205      }
 206  
 207      /**
 208       * Get groups definition specified by ids
 209       *
 210       * @param array $groupids arrays of group ids
 211       * @return array of group objects (id, courseid, name, enrolmentkey)
 212       * @since Moodle 2.2
 213       */
 214      public static function get_groups($groupids) {
 215          $params = self::validate_parameters(self::get_groups_parameters(), array('groupids'=>$groupids));
 216  
 217          $groups = array();
 218          $customfieldsdata = get_group_custom_fields_data($groupids);
 219          foreach ($params['groupids'] as $groupid) {
 220              // validate params
 221              $group = groups_get_group($groupid, 'id, courseid, name, idnumber, description, descriptionformat, enrolmentkey, '
 222                      . 'visibility, participation', MUST_EXIST);
 223  
 224              // now security checks
 225              $context = context_course::instance($group->courseid, IGNORE_MISSING);
 226              try {
 227                  self::validate_context($context);
 228              } catch (Exception $e) {
 229                  $exceptionparam = new stdClass();
 230                  $exceptionparam->message = $e->getMessage();
 231                  $exceptionparam->courseid = $group->courseid;
 232                  throw new moodle_exception('errorcoursecontextnotvalid' , 'webservice', '', $exceptionparam);
 233              }
 234              require_capability('moodle/course:managegroups', $context);
 235  
 236              $group->name = \core_external\util::format_string($group->name, $context);
 237              [$group->description, $group->descriptionformat] =
 238                  \core_external\util::format_text($group->description, $group->descriptionformat,
 239                          $context, 'group', 'description', $group->id);
 240  
 241              $group->customfields = $customfieldsdata[$group->id] ?? [];
 242              $groups[] = (array)$group;
 243          }
 244  
 245          return $groups;
 246      }
 247  
 248      /**
 249       * Returns description of method result value
 250       *
 251       * @return \core_external\external_description
 252       * @since Moodle 2.2
 253       */
 254      public static function get_groups_returns() {
 255          return new external_multiple_structure(
 256              new external_single_structure(
 257                  array(
 258                      'id' => new external_value(PARAM_INT, 'group record id'),
 259                      'courseid' => new external_value(PARAM_INT, 'id of course'),
 260                      'name' => new external_value(PARAM_TEXT, 'group name'),
 261                      'description' => new external_value(PARAM_RAW, 'group description text'),
 262                      'descriptionformat' => new external_format_value('description'),
 263                      'enrolmentkey' => new external_value(PARAM_RAW, 'group enrol secret phrase'),
 264                      'idnumber' => new external_value(PARAM_RAW, 'id number'),
 265                      'visibility' => new external_value(PARAM_INT,
 266                              'group visibility mode. 0 = Visible to all. 1 = Visible to members. 2 = See own membership. '
 267                              . '3 = Membership is hidden.'),
 268                      'participation' => new external_value(PARAM_BOOL, 'participation mode'),
 269                      'customfields' => self::build_custom_fields_returns_structure(),
 270                  )
 271              )
 272          );
 273      }
 274  
 275      /**
 276       * Returns description of method parameters
 277       *
 278       * @return external_function_parameters
 279       * @since Moodle 2.2
 280       */
 281      public static function get_course_groups_parameters() {
 282          return new external_function_parameters(
 283              array(
 284                  'courseid' => new external_value(PARAM_INT, 'id of course'),
 285              )
 286          );
 287      }
 288  
 289      /**
 290       * Get all groups in the specified course
 291       *
 292       * @param int $courseid id of course
 293       * @return array of group objects (id, courseid, name, enrolmentkey)
 294       * @since Moodle 2.2
 295       */
 296      public static function get_course_groups($courseid) {
 297          $params = self::validate_parameters(self::get_course_groups_parameters(), array('courseid'=>$courseid));
 298  
 299          // now security checks
 300          $context = context_course::instance($params['courseid'], IGNORE_MISSING);
 301          try {
 302              self::validate_context($context);
 303          } catch (Exception $e) {
 304                  $exceptionparam = new stdClass();
 305                  $exceptionparam->message = $e->getMessage();
 306                  $exceptionparam->courseid = $params['courseid'];
 307                  throw new moodle_exception('errorcoursecontextnotvalid' , 'webservice', '', $exceptionparam);
 308          }
 309          require_capability('moodle/course:managegroups', $context);
 310  
 311          $gs = groups_get_all_groups($params['courseid'], 0, 0,
 312              'g.id, g.courseid, g.name, g.idnumber, g.description, g.descriptionformat, g.enrolmentkey, '
 313              . 'g.visibility, g.participation');
 314  
 315          $groups = array();
 316          foreach ($gs as $group) {
 317              $group->name = \core_external\util::format_string($group->name, $context);
 318              [$group->description, $group->descriptionformat] =
 319                  \core_external\util::format_text($group->description, $group->descriptionformat,
 320                          $context, 'group', 'description', $group->id);
 321              $groups[] = (array)$group;
 322          }
 323  
 324          return $groups;
 325      }
 326  
 327      /**
 328       * Returns description of method result value
 329       *
 330       * @return \core_external\external_description
 331       * @since Moodle 2.2
 332       */
 333      public static function get_course_groups_returns() {
 334          return new external_multiple_structure(
 335              new external_single_structure(
 336                  array(
 337                      'id' => new external_value(PARAM_INT, 'group record id'),
 338                      'courseid' => new external_value(PARAM_INT, 'id of course'),
 339                      'name' => new external_value(PARAM_TEXT, 'group name'),
 340                      'description' => new external_value(PARAM_RAW, 'group description text'),
 341                      'descriptionformat' => new external_format_value('description'),
 342                      'enrolmentkey' => new external_value(PARAM_RAW, 'group enrol secret phrase'),
 343                      'idnumber' => new external_value(PARAM_RAW, 'id number'),
 344                      'visibility' => new external_value(PARAM_INT,
 345                              'group visibility mode. 0 = Visible to all. 1 = Visible to members. 2 = See own membership. '
 346                              . '3 = Membership is hidden.'),
 347                      'participation' => new external_value(PARAM_BOOL, 'participation mode'),
 348                  )
 349              )
 350          );
 351      }
 352  
 353      /**
 354       * Returns description of method parameters
 355       *
 356       * @return external_function_parameters
 357       * @since Moodle 2.2
 358       */
 359      public static function delete_groups_parameters() {
 360          return new external_function_parameters(
 361              array(
 362                  'groupids' => new external_multiple_structure(new external_value(PARAM_INT, 'Group ID')),
 363              )
 364          );
 365      }
 366  
 367      /**
 368       * Delete groups
 369       *
 370       * @param array $groupids array of group ids
 371       * @since Moodle 2.2
 372       */
 373      public static function delete_groups($groupids) {
 374          global $CFG, $DB;
 375          require_once("$CFG->dirroot/group/lib.php");
 376  
 377          $params = self::validate_parameters(self::delete_groups_parameters(), array('groupids'=>$groupids));
 378  
 379          $transaction = $DB->start_delegated_transaction();
 380  
 381          foreach ($params['groupids'] as $groupid) {
 382              // validate params
 383              $groupid = validate_param($groupid, PARAM_INT);
 384              if (!$group = groups_get_group($groupid, '*', IGNORE_MISSING)) {
 385                  // silently ignore attempts to delete nonexisting groups
 386                  continue;
 387              }
 388  
 389              // now security checks
 390              $context = context_course::instance($group->courseid, IGNORE_MISSING);
 391              try {
 392                  self::validate_context($context);
 393              } catch (Exception $e) {
 394                  $exceptionparam = new stdClass();
 395                  $exceptionparam->message = $e->getMessage();
 396                  $exceptionparam->courseid = $group->courseid;
 397                  throw new moodle_exception('errorcoursecontextnotvalid' , 'webservice', '', $exceptionparam);
 398              }
 399              require_capability('moodle/course:managegroups', $context);
 400  
 401              groups_delete_group($group);
 402          }
 403  
 404          $transaction->allow_commit();
 405      }
 406  
 407      /**
 408       * Returns description of method result value
 409       *
 410       * @return null
 411       * @since Moodle 2.2
 412       */
 413      public static function delete_groups_returns() {
 414          return null;
 415      }
 416  
 417  
 418      /**
 419       * Returns description of method parameters
 420       *
 421       * @return external_function_parameters
 422       * @since Moodle 2.2
 423       */
 424      public static function get_group_members_parameters() {
 425          return new external_function_parameters(
 426              array(
 427                  'groupids' => new external_multiple_structure(new external_value(PARAM_INT, 'Group ID')),
 428              )
 429          );
 430      }
 431  
 432      /**
 433       * Return all members for a group
 434       *
 435       * @param array $groupids array of group ids
 436       * @return array with  group id keys containing arrays of user ids
 437       * @since Moodle 2.2
 438       */
 439      public static function get_group_members($groupids) {
 440          $members = array();
 441  
 442          $params = self::validate_parameters(self::get_group_members_parameters(), array('groupids'=>$groupids));
 443  
 444          foreach ($params['groupids'] as $groupid) {
 445              // validate params
 446              $group = groups_get_group($groupid, 'id, courseid, name, enrolmentkey', MUST_EXIST);
 447              // now security checks
 448              $context = context_course::instance($group->courseid, IGNORE_MISSING);
 449              try {
 450                  self::validate_context($context);
 451              } catch (Exception $e) {
 452                  $exceptionparam = new stdClass();
 453                  $exceptionparam->message = $e->getMessage();
 454                  $exceptionparam->courseid = $group->courseid;
 455                  throw new moodle_exception('errorcoursecontextnotvalid' , 'webservice', '', $exceptionparam);
 456              }
 457              require_capability('moodle/course:managegroups', $context);
 458  
 459              $groupmembers = groups_get_members($group->id, 'u.id', 'lastname ASC, firstname ASC');
 460  
 461              $members[] = array('groupid'=>$groupid, 'userids'=>array_keys($groupmembers));
 462          }
 463  
 464          return $members;
 465      }
 466  
 467      /**
 468       * Returns description of method result value
 469       *
 470       * @return \core_external\external_description
 471       * @since Moodle 2.2
 472       */
 473      public static function get_group_members_returns() {
 474          return new external_multiple_structure(
 475              new external_single_structure(
 476                  array(
 477                      'groupid' => new external_value(PARAM_INT, 'group record id'),
 478                      'userids' => new external_multiple_structure(new external_value(PARAM_INT, 'user id')),
 479                  )
 480              )
 481          );
 482      }
 483  
 484  
 485      /**
 486       * Returns description of method parameters
 487       *
 488       * @return external_function_parameters
 489       * @since Moodle 2.2
 490       */
 491      public static function add_group_members_parameters() {
 492          return new external_function_parameters(
 493              array(
 494                  'members'=> new external_multiple_structure(
 495                      new external_single_structure(
 496                          array(
 497                              'groupid' => new external_value(PARAM_INT, 'group record id'),
 498                              'userid' => new external_value(PARAM_INT, 'user id'),
 499                          )
 500                      )
 501                  )
 502              )
 503          );
 504      }
 505  
 506      /**
 507       * Add group members
 508       *
 509       * @param array $members of arrays with keys userid, groupid
 510       * @since Moodle 2.2
 511       */
 512      public static function add_group_members($members) {
 513          global $CFG, $DB;
 514          require_once("$CFG->dirroot/group/lib.php");
 515  
 516          $params = self::validate_parameters(self::add_group_members_parameters(), array('members'=>$members));
 517  
 518          $transaction = $DB->start_delegated_transaction();
 519          foreach ($params['members'] as $member) {
 520              // validate params
 521              $groupid = $member['groupid'];
 522              $userid = $member['userid'];
 523  
 524              $group = groups_get_group($groupid, '*', MUST_EXIST);
 525              $user = $DB->get_record('user', array('id'=>$userid, 'deleted'=>0, 'mnethostid'=>$CFG->mnet_localhost_id), '*', MUST_EXIST);
 526  
 527              // now security checks
 528              $context = context_course::instance($group->courseid, IGNORE_MISSING);
 529              try {
 530                  self::validate_context($context);
 531              } catch (Exception $e) {
 532                  $exceptionparam = new stdClass();
 533                  $exceptionparam->message = $e->getMessage();
 534                  $exceptionparam->courseid = $group->courseid;
 535                  throw new moodle_exception('errorcoursecontextnotvalid' , 'webservice', '', $exceptionparam);
 536              }
 537              require_capability('moodle/course:managegroups', $context);
 538  
 539              // now make sure user is enrolled in course - this is mandatory requirement,
 540              // unfortunately this is slow
 541              if (!is_enrolled($context, $userid)) {
 542                  throw new invalid_parameter_exception('Only enrolled users may be members of groups');
 543              }
 544  
 545              groups_add_member($group, $user);
 546          }
 547  
 548          $transaction->allow_commit();
 549      }
 550  
 551      /**
 552       * Returns description of method result value
 553       *
 554       * @return null
 555       * @since Moodle 2.2
 556       */
 557      public static function add_group_members_returns() {
 558          return null;
 559      }
 560  
 561  
 562      /**
 563       * Returns description of method parameters
 564       *
 565       * @return external_function_parameters
 566       * @since Moodle 2.2
 567       */
 568      public static function delete_group_members_parameters() {
 569          return new external_function_parameters(
 570              array(
 571                  'members'=> new external_multiple_structure(
 572                      new external_single_structure(
 573                          array(
 574                              'groupid' => new external_value(PARAM_INT, 'group record id'),
 575                              'userid' => new external_value(PARAM_INT, 'user id'),
 576                          )
 577                      )
 578                  )
 579              )
 580          );
 581      }
 582  
 583      /**
 584       * Delete group members
 585       *
 586       * @param array $members of arrays with keys userid, groupid
 587       * @since Moodle 2.2
 588       */
 589      public static function delete_group_members($members) {
 590          global $CFG, $DB;
 591          require_once("$CFG->dirroot/group/lib.php");
 592  
 593          $params = self::validate_parameters(self::delete_group_members_parameters(), array('members'=>$members));
 594  
 595          $transaction = $DB->start_delegated_transaction();
 596  
 597          foreach ($params['members'] as $member) {
 598              // validate params
 599              $groupid = $member['groupid'];
 600              $userid = $member['userid'];
 601  
 602              $group = groups_get_group($groupid, '*', MUST_EXIST);
 603              $user = $DB->get_record('user', array('id'=>$userid, 'deleted'=>0, 'mnethostid'=>$CFG->mnet_localhost_id), '*', MUST_EXIST);
 604  
 605              // now security checks
 606              $context = context_course::instance($group->courseid, IGNORE_MISSING);
 607              try {
 608                  self::validate_context($context);
 609              } catch (Exception $e) {
 610                  $exceptionparam = new stdClass();
 611                  $exceptionparam->message = $e->getMessage();
 612                  $exceptionparam->courseid = $group->courseid;
 613                  throw new moodle_exception('errorcoursecontextnotvalid' , 'webservice', '', $exceptionparam);
 614              }
 615              require_capability('moodle/course:managegroups', $context);
 616  
 617              if (!groups_remove_member_allowed($group, $user)) {
 618                  $fullname = fullname($user, has_capability('moodle/site:viewfullnames', $context));
 619                  throw new moodle_exception('errorremovenotpermitted', 'group', '', $fullname);
 620              }
 621              groups_remove_member($group, $user);
 622          }
 623  
 624          $transaction->allow_commit();
 625      }
 626  
 627      /**
 628       * Returns description of method result value
 629       *
 630       * @return null
 631       * @since Moodle 2.2
 632       */
 633      public static function delete_group_members_returns() {
 634          return null;
 635      }
 636  
 637      /**
 638       * Returns description of method parameters
 639       *
 640       * @return external_function_parameters
 641       * @since Moodle 2.3
 642       */
 643      public static function create_groupings_parameters() {
 644          return new external_function_parameters(
 645              array(
 646                  'groupings' => new external_multiple_structure(
 647                      new external_single_structure(
 648                          array(
 649                              'courseid' => new external_value(PARAM_INT, 'id of course'),
 650                              'name' => new external_value(PARAM_TEXT, 'multilang compatible name, course unique'),
 651                              'description' => new external_value(PARAM_RAW, 'grouping description text'),
 652                              'descriptionformat' => new external_format_value('description', VALUE_DEFAULT),
 653                              'idnumber' => new external_value(PARAM_RAW, 'id number', VALUE_OPTIONAL),
 654                              'customfields' => self::build_custom_fields_parameters_structure(),
 655                          )
 656                      ), 'List of grouping object. A grouping has a courseid, a name and a description.'
 657                  )
 658              )
 659          );
 660      }
 661  
 662      /**
 663       * Create groupings
 664       *
 665       * @param array $groupings array of grouping description arrays (with keys groupname and courseid)
 666       * @return array of newly created groupings
 667       * @since Moodle 2.3
 668       */
 669      public static function create_groupings($groupings) {
 670          global $CFG, $DB;
 671          require_once("$CFG->dirroot/group/lib.php");
 672  
 673          $params = self::validate_parameters(self::create_groupings_parameters(), array('groupings'=>$groupings));
 674  
 675          $transaction = $DB->start_delegated_transaction();
 676  
 677          $groupings = array();
 678  
 679          foreach ($params['groupings'] as $grouping) {
 680              $grouping = (object)$grouping;
 681  
 682              if (trim($grouping->name) == '') {
 683                  throw new invalid_parameter_exception('Invalid grouping name');
 684              }
 685              if ($DB->count_records('groupings', array('courseid'=>$grouping->courseid, 'name'=>$grouping->name))) {
 686                  throw new invalid_parameter_exception('Grouping with the same name already exists in the course');
 687              }
 688  
 689              // Now security checks            .
 690              $context = context_course::instance($grouping->courseid);
 691              try {
 692                  self::validate_context($context);
 693              } catch (Exception $e) {
 694                  $exceptionparam = new stdClass();
 695                  $exceptionparam->message = $e->getMessage();
 696                  $exceptionparam->courseid = $grouping->courseid;
 697                  throw new moodle_exception('errorcoursecontextnotvalid' , 'webservice', '', $exceptionparam);
 698              }
 699              require_capability('moodle/course:managegroups', $context);
 700  
 701              $grouping->descriptionformat = util::validate_format($grouping->descriptionformat);
 702  
 703              // Custom fields.
 704              if (!empty($grouping->customfields)) {
 705                  foreach ($grouping->customfields as $field) {
 706                      $fieldname = self::build_custom_field_name($field['shortname']);
 707                      $grouping->{$fieldname} = $field['value'];
 708                  }
 709              }
 710  
 711              // Finally create the grouping.
 712              $grouping->id = groups_create_grouping($grouping);
 713              $groupings[] = (array)$grouping;
 714          }
 715  
 716          $transaction->allow_commit();
 717  
 718          return $groupings;
 719      }
 720  
 721      /**
 722       * Returns description of method result value
 723       *
 724       * @return \core_external\external_description
 725       * @since Moodle 2.3
 726       */
 727      public static function create_groupings_returns() {
 728          return new external_multiple_structure(
 729              new external_single_structure(
 730                  array(
 731                      'id' => new external_value(PARAM_INT, 'grouping record id'),
 732                      'courseid' => new external_value(PARAM_INT, 'id of course'),
 733                      'name' => new external_value(PARAM_TEXT, 'multilang compatible name, course unique'),
 734                      'description' => new external_value(PARAM_RAW, 'grouping description text'),
 735                      'descriptionformat' => new external_format_value('description'),
 736                      'idnumber' => new external_value(PARAM_RAW, 'id number'),
 737                      'customfields' => self::build_custom_fields_parameters_structure(),
 738                  )
 739              ), 'List of grouping object. A grouping has an id, a courseid, a name and a description.'
 740          );
 741      }
 742  
 743      /**
 744       * Returns description of method parameters
 745       *
 746       * @return external_function_parameters
 747       * @since Moodle 2.3
 748       */
 749      public static function update_groupings_parameters() {
 750          return new external_function_parameters(
 751              array(
 752                  'groupings' => new external_multiple_structure(
 753                      new external_single_structure(
 754                          array(
 755                              'id' => new external_value(PARAM_INT, 'id of grouping'),
 756                              'name' => new external_value(PARAM_TEXT, 'multilang compatible name, course unique'),
 757                              'description' => new external_value(PARAM_RAW, 'grouping description text'),
 758                              'descriptionformat' => new external_format_value('description', VALUE_DEFAULT),
 759                              'idnumber' => new external_value(PARAM_RAW, 'id number', VALUE_OPTIONAL),
 760                              'customfields' => self::build_custom_fields_parameters_structure(),
 761                          )
 762                      ), 'List of grouping object. A grouping has a courseid, a name and a description.'
 763                  )
 764              )
 765          );
 766      }
 767  
 768      /**
 769       * Update groupings
 770       *
 771       * @param array $groupings array of grouping description arrays (with keys groupname and courseid)
 772       * @return array of newly updated groupings
 773       * @since Moodle 2.3
 774       */
 775      public static function update_groupings($groupings) {
 776          global $CFG, $DB;
 777          require_once("$CFG->dirroot/group/lib.php");
 778  
 779          $params = self::validate_parameters(self::update_groupings_parameters(), array('groupings'=>$groupings));
 780  
 781          $transaction = $DB->start_delegated_transaction();
 782  
 783          foreach ($params['groupings'] as $grouping) {
 784              $grouping = (object)$grouping;
 785  
 786              if (trim($grouping->name) == '') {
 787                  throw new invalid_parameter_exception('Invalid grouping name');
 788              }
 789  
 790              if (! $currentgrouping = $DB->get_record('groupings', array('id'=>$grouping->id))) {
 791                  throw new invalid_parameter_exception("Grouping $grouping->id does not exist in the course");
 792              }
 793  
 794              // Check if the new modified grouping name already exists in the course.
 795              if ($grouping->name != $currentgrouping->name and
 796                      $DB->count_records('groupings', array('courseid'=>$currentgrouping->courseid, 'name'=>$grouping->name))) {
 797                  throw new invalid_parameter_exception('A different grouping with the same name already exists in the course');
 798              }
 799  
 800              $grouping->courseid = $currentgrouping->courseid;
 801  
 802              // Now security checks.
 803              $context = context_course::instance($grouping->courseid);
 804              try {
 805                  self::validate_context($context);
 806              } catch (Exception $e) {
 807                  $exceptionparam = new stdClass();
 808                  $exceptionparam->message = $e->getMessage();
 809                  $exceptionparam->courseid = $grouping->courseid;
 810                  throw new moodle_exception('errorcoursecontextnotvalid' , 'webservice', '', $exceptionparam);
 811              }
 812              require_capability('moodle/course:managegroups', $context);
 813  
 814              // We must force allways FORMAT_HTML.
 815              $grouping->descriptionformat = util::validate_format($grouping->descriptionformat);
 816  
 817              // Custom fields.
 818              if (!empty($grouping->customfields)) {
 819                  foreach ($grouping->customfields as $field) {
 820                      $fieldname = self::build_custom_field_name($field['shortname']);
 821                      $grouping->{$fieldname} = $field['value'];
 822                  }
 823              }
 824  
 825              // Finally update the grouping.
 826              groups_update_grouping($grouping);
 827          }
 828  
 829          $transaction->allow_commit();
 830  
 831          return null;
 832      }
 833  
 834      /**
 835       * Returns description of method result value
 836       *
 837       * @return \core_external\external_description
 838       * @since Moodle 2.3
 839       */
 840      public static function update_groupings_returns() {
 841          return null;
 842      }
 843  
 844      /**
 845       * Returns description of method parameters
 846       *
 847       * @return external_function_parameters
 848       * @since Moodle 2.3
 849       */
 850      public static function get_groupings_parameters() {
 851          return new external_function_parameters(
 852              array(
 853                  'groupingids' => new external_multiple_structure(new external_value(PARAM_INT, 'grouping ID')
 854                          , 'List of grouping id. A grouping id is an integer.'),
 855                  'returngroups' => new external_value(PARAM_BOOL, 'return associated groups', VALUE_DEFAULT, 0)
 856              )
 857          );
 858      }
 859  
 860      /**
 861       * Get groupings definition specified by ids
 862       *
 863       * @param array $groupingids arrays of grouping ids
 864       * @param boolean $returngroups return the associated groups if true. The default is false.
 865       * @return array of grouping objects (id, courseid, name)
 866       * @since Moodle 2.3
 867       */
 868      public static function get_groupings($groupingids, $returngroups = false) {
 869          global $CFG, $DB;
 870          require_once("$CFG->dirroot/group/lib.php");
 871          require_once("$CFG->libdir/filelib.php");
 872  
 873          $params = self::validate_parameters(self::get_groupings_parameters(),
 874                                              array('groupingids' => $groupingids,
 875                                                    'returngroups' => $returngroups));
 876  
 877          $groupings = array();
 878          $groupingcustomfieldsdata = get_grouping_custom_fields_data($groupingids);
 879          foreach ($params['groupingids'] as $groupingid) {
 880              // Validate params.
 881              $grouping = groups_get_grouping($groupingid, '*', MUST_EXIST);
 882  
 883              // Now security checks.
 884              $context = context_course::instance($grouping->courseid);
 885              try {
 886                  self::validate_context($context);
 887              } catch (Exception $e) {
 888                  $exceptionparam = new stdClass();
 889                  $exceptionparam->message = $e->getMessage();
 890                  $exceptionparam->courseid = $grouping->courseid;
 891                  throw new moodle_exception('errorcoursecontextnotvalid' , 'webservice', '', $exceptionparam);
 892              }
 893              require_capability('moodle/course:managegroups', $context);
 894  
 895              list($grouping->description, $grouping->descriptionformat) =
 896                  \core_external\util::format_text($grouping->description, $grouping->descriptionformat,
 897                          $context, 'grouping', 'description', $grouping->id);
 898  
 899              $grouping->customfields = $groupingcustomfieldsdata[$grouping->id] ?? [];
 900              $groupingarray = (array)$grouping;
 901  
 902              if ($params['returngroups']) {
 903                  $grouprecords = $DB->get_records_sql("SELECT * FROM {groups} g INNER JOIN {groupings_groups} gg ".
 904                                                 "ON g.id = gg.groupid WHERE gg.groupingid = ? ".
 905                                                 "ORDER BY groupid", array($groupingid));
 906                  if ($grouprecords) {
 907                      $groups = array();
 908                      $groupids = [];
 909                      foreach ($grouprecords as $grouprecord) {
 910                          list($grouprecord->description, $grouprecord->descriptionformat) =
 911                          \core_external\util::format_text($grouprecord->description, $grouprecord->descriptionformat,
 912                          $context, 'group', 'description', $grouprecord->groupid);
 913                          $groups[] = array('id' => $grouprecord->groupid,
 914                                            'name' => $grouprecord->name,
 915                                            'idnumber' => $grouprecord->idnumber,
 916                                            'description' => $grouprecord->description,
 917                                            'descriptionformat' => $grouprecord->descriptionformat,
 918                                            'enrolmentkey' => $grouprecord->enrolmentkey,
 919                                            'courseid' => $grouprecord->courseid
 920                                            );
 921                          $groupids[] = $grouprecord->groupid;
 922                      }
 923                      $groupcustomfieldsdata = get_group_custom_fields_data($groupids);
 924                      foreach ($groups as $i => $group) {
 925                          $groups[$i]['customfields'] = $groupcustomfieldsdata[$group['id']] ?? [];
 926                      }
 927                      $groupingarray['groups'] = $groups;
 928                  }
 929              }
 930              $groupings[] = $groupingarray;
 931          }
 932  
 933          return $groupings;
 934      }
 935  
 936      /**
 937       * Returns description of method result value
 938       *
 939       * @return \core_external\external_description
 940       * @since Moodle 2.3
 941       */
 942      public static function get_groupings_returns() {
 943          return new external_multiple_structure(
 944              new external_single_structure(
 945                  array(
 946                      'id' => new external_value(PARAM_INT, 'grouping record id'),
 947                      'courseid' => new external_value(PARAM_INT, 'id of course'),
 948                      'name' => new external_value(PARAM_TEXT, 'multilang compatible name, course unique'),
 949                      'description' => new external_value(PARAM_RAW, 'grouping description text'),
 950                      'descriptionformat' => new external_format_value('description'),
 951                      'idnumber' => new external_value(PARAM_RAW, 'id number'),
 952                      'customfields' => self::build_custom_fields_returns_structure(),
 953                      'groups' => new external_multiple_structure(
 954                          new external_single_structure(
 955                              array(
 956                                  'id' => new external_value(PARAM_INT, 'group record id'),
 957                                  'courseid' => new external_value(PARAM_INT, 'id of course'),
 958                                  'name' => new external_value(PARAM_TEXT, 'multilang compatible name, course unique'),
 959                                  'description' => new external_value(PARAM_RAW, 'group description text'),
 960                                  'descriptionformat' => new external_format_value('description'),
 961                                  'enrolmentkey' => new external_value(PARAM_RAW, 'group enrol secret phrase'),
 962                                  'idnumber' => new external_value(PARAM_RAW, 'id number'),
 963                                  'customfields' => self::build_custom_fields_returns_structure(),
 964                              )
 965                          ),
 966                      'optional groups', VALUE_OPTIONAL)
 967                  )
 968              )
 969          );
 970      }
 971  
 972      /**
 973       * Returns description of method parameters
 974       *
 975       * @return external_function_parameters
 976       * @since Moodle 2.3
 977       */
 978      public static function get_course_groupings_parameters() {
 979          return new external_function_parameters(
 980              array(
 981                  'courseid' => new external_value(PARAM_INT, 'id of course'),
 982              )
 983          );
 984      }
 985  
 986      /**
 987       * Get all groupings in the specified course
 988       *
 989       * @param int $courseid id of course
 990       * @return array of grouping objects (id, courseid, name, enrolmentkey)
 991       * @since Moodle 2.3
 992       */
 993      public static function get_course_groupings($courseid) {
 994          global $CFG;
 995          require_once("$CFG->dirroot/group/lib.php");
 996          require_once("$CFG->libdir/filelib.php");
 997  
 998          $params = self::validate_parameters(self::get_course_groupings_parameters(), array('courseid'=>$courseid));
 999  
1000          // Now security checks.
1001          $context = context_course::instance($params['courseid']);
1002  
1003          try {
1004              self::validate_context($context);
1005          } catch (Exception $e) {
1006                  $exceptionparam = new stdClass();
1007                  $exceptionparam->message = $e->getMessage();
1008                  $exceptionparam->courseid = $params['courseid'];
1009                  throw new moodle_exception('errorcoursecontextnotvalid' , 'webservice', '', $exceptionparam);
1010          }
1011          require_capability('moodle/course:managegroups', $context);
1012  
1013          $gs = groups_get_all_groupings($params['courseid']);
1014  
1015          $groupings = array();
1016          foreach ($gs as $grouping) {
1017              list($grouping->description, $grouping->descriptionformat) =
1018                  \core_external\util::format_text($grouping->description, $grouping->descriptionformat,
1019                          $context, 'grouping', 'description', $grouping->id);
1020              $groupings[] = (array)$grouping;
1021          }
1022  
1023          return $groupings;
1024      }
1025  
1026      /**
1027       * Returns description of method result value
1028       *
1029       * @return \core_external\external_description
1030       * @since Moodle 2.3
1031       */
1032      public static function get_course_groupings_returns() {
1033          return new external_multiple_structure(
1034              new external_single_structure(
1035                  array(
1036                      'id' => new external_value(PARAM_INT, 'grouping record id'),
1037                      'courseid' => new external_value(PARAM_INT, 'id of course'),
1038                      'name' => new external_value(PARAM_TEXT, 'multilang compatible name, course unique'),
1039                      'description' => new external_value(PARAM_RAW, 'grouping description text'),
1040                      'descriptionformat' => new external_format_value('description'),
1041                      'idnumber' => new external_value(PARAM_RAW, 'id number')
1042                  )
1043              )
1044          );
1045      }
1046  
1047      /**
1048       * Returns description of method parameters
1049       *
1050       * @return external_function_parameters
1051       * @since Moodle 2.3
1052       */
1053      public static function delete_groupings_parameters() {
1054          return new external_function_parameters(
1055              array(
1056                  'groupingids' => new external_multiple_structure(new external_value(PARAM_INT, 'grouping ID')),
1057              )
1058          );
1059      }
1060  
1061      /**
1062       * Delete groupings
1063       *
1064       * @param array $groupingids array of grouping ids
1065       * @return void
1066       * @since Moodle 2.3
1067       */
1068      public static function delete_groupings($groupingids) {
1069          global $CFG, $DB;
1070          require_once("$CFG->dirroot/group/lib.php");
1071  
1072          $params = self::validate_parameters(self::delete_groupings_parameters(), array('groupingids'=>$groupingids));
1073  
1074          $transaction = $DB->start_delegated_transaction();
1075  
1076          foreach ($params['groupingids'] as $groupingid) {
1077  
1078              if (!$grouping = groups_get_grouping($groupingid)) {
1079                  // Silently ignore attempts to delete nonexisting groupings.
1080                  continue;
1081              }
1082  
1083              // Now security checks.
1084              $context = context_course::instance($grouping->courseid);
1085              try {
1086                  self::validate_context($context);
1087              } catch (Exception $e) {
1088                  $exceptionparam = new stdClass();
1089                  $exceptionparam->message = $e->getMessage();
1090                  $exceptionparam->courseid = $grouping->courseid;
1091                  throw new moodle_exception('errorcoursecontextnotvalid' , 'webservice', '', $exceptionparam);
1092              }
1093              require_capability('moodle/course:managegroups', $context);
1094  
1095              groups_delete_grouping($grouping);
1096          }
1097  
1098          $transaction->allow_commit();
1099      }
1100  
1101      /**
1102       * Returns description of method result value
1103       *
1104       * @return \core_external\external_description
1105       * @since Moodle 2.3
1106       */
1107      public static function delete_groupings_returns() {
1108          return null;
1109      }
1110  
1111      /**
1112       * Returns description of method parameters
1113       *
1114       * @return external_function_parameters
1115       * @since Moodle 2.3
1116       */
1117      public static function assign_grouping_parameters() {
1118          return new external_function_parameters(
1119              array(
1120                  'assignments'=> new external_multiple_structure(
1121                      new external_single_structure(
1122                          array(
1123                              'groupingid' => new external_value(PARAM_INT, 'grouping record id'),
1124                              'groupid' => new external_value(PARAM_INT, 'group record id'),
1125                          )
1126                      )
1127                  )
1128              )
1129          );
1130      }
1131  
1132      /**
1133       * Assign a group to a grouping
1134       *
1135       * @param array $assignments of arrays with keys groupid, groupingid
1136       * @return void
1137       * @since Moodle 2.3
1138       */
1139      public static function assign_grouping($assignments) {
1140          global $CFG, $DB;
1141          require_once("$CFG->dirroot/group/lib.php");
1142  
1143          $params = self::validate_parameters(self::assign_grouping_parameters(), array('assignments'=>$assignments));
1144  
1145          $transaction = $DB->start_delegated_transaction();
1146          foreach ($params['assignments'] as $assignment) {
1147              // Validate params.
1148              $groupingid = $assignment['groupingid'];
1149              $groupid = $assignment['groupid'];
1150  
1151              $grouping = groups_get_grouping($groupingid, 'id, courseid', MUST_EXIST);
1152              $group = groups_get_group($groupid, 'id, courseid', MUST_EXIST);
1153  
1154              if ($DB->record_exists('groupings_groups', array('groupingid'=>$groupingid, 'groupid'=>$groupid))) {
1155                  // Continue silently if the group is yet assigned to the grouping.
1156                  continue;
1157              }
1158  
1159              // Now security checks.
1160              $context = context_course::instance($grouping->courseid);
1161              try {
1162                  self::validate_context($context);
1163              } catch (Exception $e) {
1164                  $exceptionparam = new stdClass();
1165                  $exceptionparam->message = $e->getMessage();
1166                  $exceptionparam->courseid = $group->courseid;
1167                  throw new moodle_exception('errorcoursecontextnotvalid' , 'webservice', '', $exceptionparam);
1168              }
1169              require_capability('moodle/course:managegroups', $context);
1170  
1171              groups_assign_grouping($groupingid, $groupid);
1172          }
1173  
1174          $transaction->allow_commit();
1175      }
1176  
1177      /**
1178       * Returns description of method result value
1179       *
1180       * @return null
1181       * @since Moodle 2.3
1182       */
1183      public static function assign_grouping_returns() {
1184          return null;
1185      }
1186  
1187      /**
1188       * Returns description of method parameters
1189       *
1190       * @return external_function_parameters
1191       * @since Moodle 2.3
1192       */
1193      public static function unassign_grouping_parameters() {
1194          return new external_function_parameters(
1195              array(
1196                  'unassignments'=> new external_multiple_structure(
1197                      new external_single_structure(
1198                          array(
1199                              'groupingid' => new external_value(PARAM_INT, 'grouping record id'),
1200                              'groupid' => new external_value(PARAM_INT, 'group record id'),
1201                          )
1202                      )
1203                  )
1204              )
1205          );
1206      }
1207  
1208      /**
1209       * Unassign a group from a grouping
1210       *
1211       * @param array $unassignments of arrays with keys groupid, groupingid
1212       * @return void
1213       * @since Moodle 2.3
1214       */
1215      public static function unassign_grouping($unassignments) {
1216          global $CFG, $DB;
1217          require_once("$CFG->dirroot/group/lib.php");
1218  
1219          $params = self::validate_parameters(self::unassign_grouping_parameters(), array('unassignments'=>$unassignments));
1220  
1221          $transaction = $DB->start_delegated_transaction();
1222          foreach ($params['unassignments'] as $unassignment) {
1223              // Validate params.
1224              $groupingid = $unassignment['groupingid'];
1225              $groupid = $unassignment['groupid'];
1226  
1227              $grouping = groups_get_grouping($groupingid, 'id, courseid', MUST_EXIST);
1228              $group = groups_get_group($groupid, 'id, courseid', MUST_EXIST);
1229  
1230              if (!$DB->record_exists('groupings_groups', array('groupingid'=>$groupingid, 'groupid'=>$groupid))) {
1231                  // Continue silently if the group is not assigned to the grouping.
1232                  continue;
1233              }
1234  
1235              // Now security checks.
1236              $context = context_course::instance($grouping->courseid);
1237              try {
1238                  self::validate_context($context);
1239              } catch (Exception $e) {
1240                  $exceptionparam = new stdClass();
1241                  $exceptionparam->message = $e->getMessage();
1242                  $exceptionparam->courseid = $group->courseid;
1243                  throw new moodle_exception('errorcoursecontextnotvalid' , 'webservice', '', $exceptionparam);
1244              }
1245              require_capability('moodle/course:managegroups', $context);
1246  
1247              groups_unassign_grouping($groupingid, $groupid);
1248          }
1249  
1250          $transaction->allow_commit();
1251      }
1252  
1253      /**
1254       * Returns description of method result value
1255       *
1256       * @return null
1257       * @since Moodle 2.3
1258       */
1259      public static function unassign_grouping_returns() {
1260          return null;
1261      }
1262  
1263      /**
1264       * Returns description of method parameters
1265       *
1266       * @return external_function_parameters
1267       * @since Moodle 2.9
1268       */
1269      public static function get_course_user_groups_parameters() {
1270          return new external_function_parameters(
1271              array(
1272                  'courseid' => new external_value(PARAM_INT,
1273                      'Id of course (empty or 0 for all the courses where the user is enrolled).', VALUE_DEFAULT, 0),
1274                  'userid' => new external_value(PARAM_INT, 'Id of user (empty or 0 for current user).', VALUE_DEFAULT, 0),
1275                  'groupingid' => new external_value(PARAM_INT, 'returns only groups in the specified grouping', VALUE_DEFAULT, 0)
1276              )
1277          );
1278      }
1279  
1280      /**
1281       * Get all groups in the specified course for the specified user.
1282       *
1283       * @throws moodle_exception
1284       * @param int $courseid id of course.
1285       * @param int $userid id of user.
1286       * @param int $groupingid optional returns only groups in the specified grouping.
1287       * @return array of group objects (id, name, description, format) and possible warnings.
1288       * @since Moodle 2.9
1289       */
1290      public static function get_course_user_groups($courseid = 0, $userid = 0, $groupingid = 0) {
1291          global $USER;
1292  
1293          // Warnings array, it can be empty at the end but is mandatory.
1294          $warnings = array();
1295  
1296          $params = array(
1297              'courseid' => $courseid,
1298              'userid' => $userid,
1299              'groupingid' => $groupingid
1300          );
1301          $params = self::validate_parameters(self::get_course_user_groups_parameters(), $params);
1302  
1303          $courseid = $params['courseid'];
1304          $userid = $params['userid'];
1305          $groupingid = $params['groupingid'];
1306  
1307          // Validate user.
1308          if (empty($userid)) {
1309              $userid = $USER->id;
1310          } else {
1311              $user = core_user::get_user($userid, '*', MUST_EXIST);
1312              core_user::require_active_user($user);
1313          }
1314  
1315          // Get courses.
1316          if (empty($courseid)) {
1317              $courses = enrol_get_users_courses($userid, true);
1318              $checkenrolments = false;   // No need to check enrolments here since they are my courses.
1319          } else {
1320              $courses = array($courseid => get_course($courseid));
1321              $checkenrolments = true;
1322          }
1323  
1324          // Security checks.
1325          list($courses, $warnings) = util::validate_courses(array_keys($courses), $courses, true);
1326  
1327          $usergroups = array();
1328          foreach ($courses as $course) {
1329               // Check if we have permissions for retrieve the information.
1330              if ($userid != $USER->id && !has_capability('moodle/course:managegroups', $course->context)) {
1331                  $warnings[] = array(
1332                      'item' => 'course',
1333                      'itemid' => $course->id,
1334                      'warningcode' => 'cannotmanagegroups',
1335                      'message' => "User $USER->id cannot manage groups in course $course->id",
1336                  );
1337                  continue;
1338              }
1339  
1340              // Check if the user being check is enrolled in the given course.
1341              if ($checkenrolments && !is_enrolled($course->context, $userid)) {
1342                  // We return a warning because the function does not fail for not enrolled users.
1343                  $warnings[] = array(
1344                      'item' => 'course',
1345                      'itemid' => $course->id,
1346                      'warningcode' => 'notenrolled',
1347                      'message' => "User $userid is not enrolled in course $course->id",
1348                  );
1349              }
1350  
1351              $groups = groups_get_all_groups($course->id, $userid, $groupingid,
1352                  'g.id, g.name, g.description, g.descriptionformat, g.idnumber');
1353  
1354              foreach ($groups as $group) {
1355                  $group->name = \core_external\util::format_string($group->name, $course->context);
1356                  [$group->description, $group->descriptionformat] =
1357                      \core_external\util::format_text($group->description, $group->descriptionformat,
1358                              $course->context, 'group', 'description', $group->id);
1359                  $group->courseid = $course->id;
1360                  $usergroups[] = $group;
1361              }
1362          }
1363  
1364          $results = array(
1365              'groups' => $usergroups,
1366              'warnings' => $warnings
1367          );
1368          return $results;
1369      }
1370  
1371      /**
1372       * Returns description of method result value.
1373       *
1374       * @return \core_external\external_description A single structure containing groups and possible warnings.
1375       * @since Moodle 2.9
1376       */
1377      public static function get_course_user_groups_returns() {
1378          return new external_single_structure(
1379              array(
1380                  'groups' => new external_multiple_structure(self::group_description()),
1381                  'warnings' => new external_warnings(),
1382              )
1383          );
1384      }
1385  
1386      /**
1387       * Create group return value description.
1388       *
1389       * @return external_single_structure The group description
1390       */
1391      public static function group_description() {
1392          return new external_single_structure(
1393              array(
1394                  'id' => new external_value(PARAM_INT, 'group record id'),
1395                  'name' => new external_value(PARAM_TEXT, 'group name'),
1396                  'description' => new external_value(PARAM_RAW, 'group description text'),
1397                  'descriptionformat' => new external_format_value('description'),
1398                  'idnumber' => new external_value(PARAM_RAW, 'id number'),
1399                  'courseid' => new external_value(PARAM_INT, 'course id', VALUE_OPTIONAL),
1400              )
1401          );
1402      }
1403  
1404      /**
1405       * Returns description of method parameters
1406       *
1407       * @return external_function_parameters
1408       * @since Moodle 3.0
1409       */
1410      public static function get_activity_allowed_groups_parameters() {
1411          return new external_function_parameters(
1412              array(
1413                  'cmid' => new external_value(PARAM_INT, 'course module id'),
1414                  'userid' => new external_value(PARAM_INT, 'id of user, empty for current user', VALUE_DEFAULT, 0)
1415              )
1416          );
1417      }
1418  
1419      /**
1420       * Gets a list of groups that the user is allowed to access within the specified activity.
1421       *
1422       * @throws moodle_exception
1423       * @param int $cmid course module id
1424       * @param int $userid id of user.
1425       * @return array of group objects (id, name, description, format) and possible warnings.
1426       * @since Moodle 3.0
1427       */
1428      public static function get_activity_allowed_groups($cmid, $userid = 0) {
1429          global $USER;
1430  
1431          // Warnings array, it can be empty at the end but is mandatory.
1432          $warnings = array();
1433  
1434          $params = array(
1435              'cmid' => $cmid,
1436              'userid' => $userid
1437          );
1438          $params = self::validate_parameters(self::get_activity_allowed_groups_parameters(), $params);
1439          $cmid = $params['cmid'];
1440          $userid = $params['userid'];
1441  
1442          $cm = get_coursemodule_from_id(null, $cmid, 0, false, MUST_EXIST);
1443  
1444          // Security checks.
1445          $context = context_module::instance($cm->id);
1446          $coursecontext = context_course::instance($cm->course);
1447          self::validate_context($context);
1448  
1449          if (empty($userid)) {
1450              $userid = $USER->id;
1451          }
1452  
1453          $user = core_user::get_user($userid, '*', MUST_EXIST);
1454          core_user::require_active_user($user);
1455  
1456           // Check if we have permissions for retrieve the information.
1457          if ($user->id != $USER->id) {
1458              if (!has_capability('moodle/course:managegroups', $context)) {
1459                  throw new moodle_exception('accessdenied', 'admin');
1460              }
1461  
1462              // Validate if the user is enrolled in the course.
1463              $course = get_course($cm->course);
1464              if (!can_access_course($course, $user, '', true)) {
1465                  // We return a warning because the function does not fail for not enrolled users.
1466                  $warning = array();
1467                  $warning['item'] = 'course';
1468                  $warning['itemid'] = $cm->course;
1469                  $warning['warningcode'] = '1';
1470                  $warning['message'] = "User $user->id cannot access course $cm->course";
1471                  $warnings[] = $warning;
1472              }
1473          }
1474  
1475          $usergroups = array();
1476          if (empty($warnings)) {
1477              $groups = groups_get_activity_allowed_groups($cm, $user->id);
1478  
1479              foreach ($groups as $group) {
1480                  $group->name = \core_external\util::format_string($group->name, $coursecontext);
1481                  [$group->description, $group->descriptionformat] =
1482                      \core_external\util::format_text($group->description, $group->descriptionformat,
1483                              $coursecontext, 'group', 'description', $group->id);
1484                  $group->courseid = $cm->course;
1485                  $usergroups[] = $group;
1486              }
1487          }
1488  
1489          $results = array(
1490              'groups' => $usergroups,
1491              'canaccessallgroups' => has_capability('moodle/site:accessallgroups', $context, $user),
1492              'warnings' => $warnings
1493          );
1494          return $results;
1495      }
1496  
1497      /**
1498       * Returns description of method result value.
1499       *
1500       * @return \core_external\external_description A single structure containing groups and possible warnings.
1501       * @since Moodle 3.0
1502       */
1503      public static function get_activity_allowed_groups_returns() {
1504          return new external_single_structure(
1505              array(
1506                  'groups' => new external_multiple_structure(self::group_description()),
1507                  'canaccessallgroups' => new external_value(PARAM_BOOL,
1508                      'Whether the user will be able to access all the activity groups.', VALUE_OPTIONAL),
1509                  'warnings' => new external_warnings(),
1510              )
1511          );
1512      }
1513  
1514      /**
1515       * Returns description of method parameters
1516       *
1517       * @return external_function_parameters
1518       * @since Moodle 3.0
1519       */
1520      public static function get_activity_groupmode_parameters() {
1521          return new external_function_parameters(
1522              array(
1523                  'cmid' => new external_value(PARAM_INT, 'course module id')
1524              )
1525          );
1526      }
1527  
1528      /**
1529       * Returns effective groupmode used in a given activity.
1530       *
1531       * @throws moodle_exception
1532       * @param int $cmid course module id.
1533       * @return array containing the group mode and possible warnings.
1534       * @since Moodle 3.0
1535       * @throws moodle_exception
1536       */
1537      public static function get_activity_groupmode($cmid) {
1538          global $USER;
1539  
1540          // Warnings array, it can be empty at the end but is mandatory.
1541          $warnings = array();
1542  
1543          $params = array(
1544              'cmid' => $cmid
1545          );
1546          $params = self::validate_parameters(self::get_activity_groupmode_parameters(), $params);
1547          $cmid = $params['cmid'];
1548  
1549          $cm = get_coursemodule_from_id(null, $cmid, 0, false, MUST_EXIST);
1550  
1551          // Security checks.
1552          $context = context_module::instance($cm->id);
1553          self::validate_context($context);
1554  
1555          $groupmode = groups_get_activity_groupmode($cm);
1556  
1557          $results = array(
1558              'groupmode' => $groupmode,
1559              'warnings' => $warnings
1560          );
1561          return $results;
1562      }
1563  
1564      /**
1565       * Returns description of method result value.
1566       *
1567       * @return \core_external\external_description
1568       * @since Moodle 3.0
1569       */
1570      public static function get_activity_groupmode_returns() {
1571          return new external_single_structure(
1572              array(
1573                  'groupmode' => new external_value(PARAM_INT, 'group mode:
1574                                                      0 for no groups, 1 for separate groups, 2 for visible groups'),
1575                  'warnings' => new external_warnings(),
1576              )
1577          );
1578      }
1579  
1580      /**
1581       * Returns description of method parameters
1582       *
1583       * @return external_function_parameters
1584       * @since Moodle 3.6
1585       */
1586      public static function update_groups_parameters() {
1587          return new external_function_parameters(
1588              array(
1589                  'groups' => new external_multiple_structure(
1590                      new external_single_structure(
1591                          array(
1592                              'id' => new external_value(PARAM_INT, 'ID of the group'),
1593                              'name' => new external_value(PARAM_TEXT, 'multilang compatible name, course unique'),
1594                              'description' => new external_value(PARAM_RAW, 'group description text', VALUE_OPTIONAL),
1595                              'descriptionformat' => new external_format_value('description', VALUE_DEFAULT),
1596                              'enrolmentkey' => new external_value(PARAM_RAW, 'group enrol secret phrase', VALUE_OPTIONAL),
1597                              'idnumber' => new external_value(PARAM_RAW, 'id number', VALUE_OPTIONAL),
1598                              'visibility' => new external_value(PARAM_TEXT,
1599                                      'group visibility mode. 0 = Visible to all. 1 = Visible to members. '
1600                                      . '2 = See own membership. 3 = Membership is hidden.', VALUE_OPTIONAL),
1601                              'participation' => new external_value(PARAM_BOOL,
1602                                      'activity participation enabled? Only for "all" and "members" visibility', VALUE_OPTIONAL),
1603                              'customfields' => self::build_custom_fields_parameters_structure(),
1604                          )
1605                      ), 'List of group objects. A group is found by the id, then all other details provided will be updated.'
1606                  )
1607              )
1608          );
1609      }
1610  
1611      /**
1612       * Update groups
1613       *
1614       * @param array $groups
1615       * @return null
1616       * @since Moodle 3.6
1617       */
1618      public static function update_groups($groups) {
1619          global $CFG, $DB;
1620          require_once("$CFG->dirroot/group/lib.php");
1621  
1622          $params = self::validate_parameters(self::update_groups_parameters(), array('groups' => $groups));
1623  
1624          $transaction = $DB->start_delegated_transaction();
1625  
1626          foreach ($params['groups'] as $group) {
1627              $group = (object) $group;
1628  
1629              if (trim($group->name) == '') {
1630                  throw new invalid_parameter_exception('Invalid group name');
1631              }
1632  
1633              if (!$currentgroup = $DB->get_record('groups', array('id' => $group->id))) {
1634                  throw new invalid_parameter_exception("Group $group->id does not exist");
1635              }
1636  
1637              // Check if the modified group name already exists in the course.
1638              if ($group->name != $currentgroup->name and
1639                      $DB->get_record('groups', array('courseid' => $currentgroup->courseid, 'name' => $group->name))) {
1640                  throw new invalid_parameter_exception('A different group with the same name already exists in the course');
1641              }
1642  
1643              if (isset($group->visibility) || isset($group->participation)) {
1644                  $hasmembers = $DB->record_exists('groups_members', ['groupid' => $group->id]);
1645                  if (isset($group->visibility)) {
1646                      // Validate visibility.
1647                      self::validate_visibility($group->visibility);
1648                      if ($hasmembers && $group->visibility != $currentgroup->visibility) {
1649                          throw new invalid_parameter_exception(
1650                                  'The visibility of this group cannot be changed as it currently has members.');
1651                      }
1652                  } else {
1653                      $group->visibility = $currentgroup->visibility;
1654                  }
1655                  if (isset($group->participation) && $hasmembers && $group->participation != $currentgroup->participation) {
1656                      throw new invalid_parameter_exception(
1657                              'The participation mode of this group cannot be changed as it currently has members.');
1658                  }
1659              }
1660  
1661              $group->courseid = $currentgroup->courseid;
1662  
1663              // Now security checks.
1664              $context = context_course::instance($group->courseid);
1665              try {
1666                  self::validate_context($context);
1667              } catch (Exception $e) {
1668                  $exceptionparam = new stdClass();
1669                  $exceptionparam->message = $e->getMessage();
1670                  $exceptionparam->courseid = $group->courseid;
1671                  throw new moodle_exception('errorcoursecontextnotvalid', 'webservice', '', $exceptionparam);
1672              }
1673              require_capability('moodle/course:managegroups', $context);
1674  
1675              if (!empty($group->description)) {
1676                  $group->descriptionformat = util::validate_format($group->descriptionformat);
1677              }
1678  
1679              // Custom fields.
1680              if (!empty($group->customfields)) {
1681                  foreach ($group->customfields as $field) {
1682                      $fieldname = self::build_custom_field_name($field['shortname']);
1683                      $group->{$fieldname} = $field['value'];
1684                  }
1685              }
1686  
1687              groups_update_group($group);
1688          }
1689  
1690          $transaction->allow_commit();
1691  
1692          return null;
1693      }
1694  
1695      /**
1696       * Returns description of method result value
1697       *
1698       * @return null
1699       * @since Moodle 3.6
1700       */
1701      public static function update_groups_returns() {
1702          return null;
1703      }
1704  
1705      /**
1706       * Builds a structure for custom fields parameters.
1707       *
1708       * @return \core_external\external_multiple_structure
1709       */
1710      protected static function build_custom_fields_parameters_structure(): external_multiple_structure {
1711          return new external_multiple_structure(
1712              new external_single_structure([
1713                  'shortname' => new external_value(PARAM_ALPHANUMEXT, 'The shortname of the custom field'),
1714                  'value' => new external_value(PARAM_RAW, 'The value of the custom field'),
1715              ]), 'Custom fields', VALUE_OPTIONAL
1716          );
1717      }
1718  
1719      /**
1720       * Builds a structure for custom fields returns.
1721       *
1722       * @return \core_external\external_multiple_structure
1723       */
1724      protected static function build_custom_fields_returns_structure(): external_multiple_structure {
1725          return new external_multiple_structure(
1726              new external_single_structure([
1727                  'name' => new external_value(PARAM_RAW, 'The name of the custom field'),
1728                  'shortname' => new external_value(PARAM_RAW,
1729                      'The shortname of the custom field - to be able to build the field class in the code'),
1730                  'type' => new external_value(PARAM_ALPHANUMEXT,
1731                      'The type of the custom field - text field, checkbox...'),
1732                  'valueraw' => new external_value(PARAM_RAW, 'The raw value of the custom field'),
1733                  'value' => new external_value(PARAM_RAW, 'The value of the custom field'),
1734              ]), 'Custom fields', VALUE_OPTIONAL
1735          );
1736      }
1737  
1738      /**
1739       * Builds a suitable name of a custom field for a custom field handler based on provided shortname.
1740       *
1741       * @param string $shortname shortname to use.
1742       * @return string
1743       */
1744      protected static function build_custom_field_name(string $shortname): string {
1745          return 'customfield_' . $shortname;
1746      }
1747  }