Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 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
 
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
}