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
 
18
/**
19
 * Extra library for groups and groupings.
20
 *
21
 * @copyright 2006 The Open University, J.White AT open.ac.uk, Petr Skoda (skodak)
22
 * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
23
 * @package   core_group
24
 */
25
 
26
/*
27
 * INTERNAL FUNCTIONS - to be used by moodle core only
28
 * require_once $CFG->dirroot.'/group/lib.php' must be used
29
 */
30
 
31
/**
32
 * Adds a specified user to a group
33
 *
34
 * @param mixed $grouporid  The group id or group object
35
 * @param mixed $userorid   The user id or user object
36
 * @param string $component Optional component name e.g. 'enrol_imsenterprise'
37
 * @param int $itemid Optional itemid associated with component
38
 * @return bool True if user added successfully or the user is already a
39
 * member of the group, false otherwise.
40
 */
41
function groups_add_member($grouporid, $userorid, $component=null, $itemid=0) {
42
    global $DB;
43
 
44
    if (is_object($userorid)) {
45
        $userid = $userorid->id;
46
        $user   = $userorid;
47
        if (!isset($user->deleted)) {
48
            $user = $DB->get_record('user', array('id'=>$userid), '*', MUST_EXIST);
49
        }
50
    } else {
51
        $userid = $userorid;
52
        $user = $DB->get_record('user', array('id'=>$userid), '*', MUST_EXIST);
53
    }
54
 
55
    if ($user->deleted) {
56
        return false;
57
    }
58
 
59
    if (is_object($grouporid)) {
60
        $groupid = $grouporid->id;
61
        $group   = $grouporid;
62
    } else {
63
        $groupid = $grouporid;
64
        $group = $DB->get_record('groups', array('id'=>$groupid), '*', MUST_EXIST);
65
    }
66
 
67
    // Check if the user a participant of the group course.
68
    $context = context_course::instance($group->courseid);
69
    if (!is_enrolled($context, $userid)) {
70
        return false;
71
    }
72
 
73
    if (groups_is_member($groupid, $userid)) {
74
        return true;
75
    }
76
 
77
    $member = new stdClass();
78
    $member->groupid   = $groupid;
79
    $member->userid    = $userid;
80
    $member->timeadded = time();
81
    $member->component = '';
82
    $member->itemid = 0;
83
 
84
    // Check the component exists if specified
85
    if (!empty($component)) {
86
        $dir = core_component::get_component_directory($component);
87
        if ($dir && is_dir($dir)) {
88
            // Component exists and can be used
89
            $member->component = $component;
90
            $member->itemid = $itemid;
91
        } else {
92
            throw new coding_exception('Invalid call to groups_add_member(). An invalid component was specified');
93
        }
94
    }
95
 
96
    if ($itemid !== 0 && empty($member->component)) {
97
        // An itemid can only be specified if a valid component was found
98
        throw new coding_exception('Invalid call to groups_add_member(). A component must be specified if an itemid is given');
99
    }
100
 
101
    $DB->insert_record('groups_members', $member);
102
 
103
    // Update group info, and group object.
104
    $DB->set_field('groups', 'timemodified', $member->timeadded, array('id'=>$groupid));
105
    $group->timemodified = $member->timeadded;
106
 
107
    // Invalidate the group and grouping cache for users.
108
    cache_helper::invalidate_by_definition('core', 'user_group_groupings', array(), array($userid));
109
 
110
    // Group conversation messaging.
111
    if ($conversation = \core_message\api::get_conversation_by_area('core_group', 'groups', $groupid, $context->id)) {
112
        \core_message\api::add_members_to_conversation([$userid], $conversation->id);
113
    }
114
 
115
    // Trigger group event.
116
    $params = array(
117
        'context' => $context,
118
        'objectid' => $groupid,
119
        'relateduserid' => $userid,
120
        'other' => array(
121
            'component' => $member->component,
122
            'itemid' => $member->itemid
123
        )
124
    );
125
    $event = \core\event\group_member_added::create($params);
126
    $event->add_record_snapshot('groups', $group);
127
    $event->trigger();
128
 
129
    // Dispatch the hook for a user added to the group.
130
    $hook = new \core_group\hook\after_group_membership_added(
131
        groupinstance: $group,
132
        userids: [$userid],
133
    );
134
    \core\di::get(\core\hook\manager::class)->dispatch($hook);
135
 
136
    return true;
137
}
138
 
139
/**
140
 * Checks whether the current user is permitted (using the normal UI) to
141
 * remove a specific group member, assuming that they have access to remove
142
 * group members in general.
143
 *
144
 * For automatically-created group member entries, this checks with the
145
 * relevant plugin to see whether it is permitted. The default, if the plugin
146
 * doesn't provide a function, is true.
147
 *
148
 * For other entries (and any which have already been deleted/don't exist) it
149
 * just returns true.
150
 *
151
 * @param mixed $grouporid The group id or group object
152
 * @param mixed $userorid The user id or user object
153
 * @return bool True if permitted, false otherwise
154
 */
155
function groups_remove_member_allowed($grouporid, $userorid) {
156
    global $DB;
157
 
158
    if (is_object($userorid)) {
159
        $userid = $userorid->id;
160
    } else {
161
        $userid = $userorid;
162
    }
163
    if (is_object($grouporid)) {
164
        $groupid = $grouporid->id;
165
    } else {
166
        $groupid = $grouporid;
167
    }
168
 
169
    // Get entry
170
    if (!($entry = $DB->get_record('groups_members',
171
            array('groupid' => $groupid, 'userid' => $userid), '*', IGNORE_MISSING))) {
172
        // If the entry does not exist, they are allowed to remove it (this
173
        // is consistent with groups_remove_member below).
174
        return true;
175
    }
176
 
177
    // If the entry does not have a component value, they can remove it
178
    if (empty($entry->component)) {
179
        return true;
180
    }
181
 
182
    // It has a component value, so we need to call a plugin function (if it
183
    // exists); the default is to allow removal
184
    return component_callback($entry->component, 'allow_group_member_remove',
185
            array($entry->itemid, $entry->groupid, $entry->userid), true);
186
}
187
 
188
/**
189
 * Deletes the link between the specified user and group.
190
 *
191
 * @param mixed $grouporid  The group id or group object
192
 * @param mixed $userorid   The user id or user object
193
 * @return bool True if deletion was successful, false otherwise
194
 */
195
function groups_remove_member($grouporid, $userorid) {
196
    global $DB;
197
 
198
    if (is_object($userorid)) {
199
        $userid = $userorid->id;
200
    } else {
201
        $userid = $userorid;
202
    }
203
 
204
    if (is_object($grouporid)) {
205
        $groupid = $grouporid->id;
206
        $group   = $grouporid;
207
    } else {
208
        $groupid = $grouporid;
209
        $group = $DB->get_record('groups', array('id'=>$groupid), '*', MUST_EXIST);
210
    }
211
 
212
    if (!groups_is_member($groupid, $userid)) {
213
        return true;
214
    }
215
 
216
    $DB->delete_records('groups_members', array('groupid'=>$groupid, 'userid'=>$userid));
217
 
218
    // Update group info.
219
    $time = time();
220
    $DB->set_field('groups', 'timemodified', $time, array('id' => $groupid));
221
    $group->timemodified = $time;
222
 
223
    // Invalidate the group and grouping cache for users.
224
    cache_helper::invalidate_by_definition('core', 'user_group_groupings', array(), array($userid));
225
 
226
    // Group conversation messaging.
227
    $context = context_course::instance($group->courseid);
228
    if ($conversation = \core_message\api::get_conversation_by_area('core_group', 'groups', $groupid, $context->id)) {
229
        \core_message\api::remove_members_from_conversation([$userid], $conversation->id);
230
    }
231
 
232
    // Trigger group event.
233
    $params = array(
234
        'context' => context_course::instance($group->courseid),
235
        'objectid' => $groupid,
236
        'relateduserid' => $userid
237
    );
238
    $event = \core\event\group_member_removed::create($params);
239
    $event->add_record_snapshot('groups', $group);
240
    $event->trigger();
241
 
242
    // Dispatch the hook for a user removed from the group.
243
    $hook = new \core_group\hook\after_group_membership_removed(
244
        groupinstance: $group,
245
        userids: [$userid],
246
    );
247
    \core\di::get(\core\hook\manager::class)->dispatch($hook);
248
 
249
    return true;
250
}
251
 
252
/**
253
 * Add a new group
254
 *
255
 * @param stdClass $data group properties
256
 * @param stdClass $editform
257
 * @param array $editoroptions
258
 * @return int id of group or throws an exception on error
259
 * @throws moodle_exception
260
 */
261
function groups_create_group($data, $editform = false, $editoroptions = false) {
262
    global $CFG, $DB, $USER;
263
 
264
    //check that courseid exists
265
    $course = $DB->get_record('course', array('id' => $data->courseid), '*', MUST_EXIST);
266
    $context = context_course::instance($course->id);
267
 
268
    $data->timecreated  = time();
269
    $data->timemodified = $data->timecreated;
270
    $data->name         = trim($data->name);
271
    if (isset($data->idnumber)) {
272
        $data->idnumber = trim($data->idnumber);
273
        if (groups_get_group_by_idnumber($course->id, $data->idnumber)) {
274
            throw new moodle_exception('idnumbertaken');
275
        }
276
    }
277
 
278
    $data->visibility ??= GROUPS_VISIBILITY_ALL;
279
 
280
    if (!in_array($data->visibility, [GROUPS_VISIBILITY_ALL, GROUPS_VISIBILITY_MEMBERS])) {
281
        $data->participation = false;
282
        $data->enablemessaging = false;
283
    }
284
 
285
    if ($editform and $editoroptions) {
286
        $data->description = $data->description_editor['text'];
287
        $data->descriptionformat = $data->description_editor['format'];
288
    }
289
 
290
    $data->id = $DB->insert_record('groups', $data);
291
 
292
    $handler = \core_group\customfield\group_handler::create();
293
    $handler->instance_form_save($data, true);
294
 
295
    if ($editform and $editoroptions) {
296
        // Update description from editor with fixed files
297
        $data = file_postupdate_standard_editor($data, 'description', $editoroptions, $context, 'group', 'description', $data->id);
298
        $upd = new stdClass();
299
        $upd->id                = $data->id;
300
        $upd->description       = $data->description;
301
        $upd->descriptionformat = $data->descriptionformat;
302
        $DB->update_record('groups', $upd);
303
    }
304
 
305
    $group = $DB->get_record('groups', array('id'=>$data->id));
306
 
307
    if ($editform) {
308
        groups_update_group_icon($group, $data, $editform);
309
    }
310
 
311
    // Invalidate the grouping cache for the course
312
    cache_helper::invalidate_by_definition('core', 'groupdata', array(), array($course->id));
313
    // Rebuild the coursehiddengroups cache for the course.
314
    \core_group\visibility::update_hiddengroups_cache($course->id);
315
 
316
    // Group conversation messaging.
317
    if (\core_message\api::can_create_group_conversation($USER->id, $context)) {
318
        if (!empty($data->enablemessaging)) {
319
            \core_message\api::create_conversation(
320
                \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP,
321
                [],
322
                $group->name,
323
                \core_message\api::MESSAGE_CONVERSATION_ENABLED,
324
                'core_group',
325
                'groups',
326
                $group->id,
327
                $context->id);
328
        }
329
    }
330
 
331
    // Trigger group event.
332
    $params = array(
333
        'context' => $context,
334
        'objectid' => $group->id
335
    );
336
    $event = \core\event\group_created::create($params);
337
    $event->add_record_snapshot('groups', $group);
338
    $event->trigger();
339
 
340
    // Dispatch the hook for post group creation actions.
341
    $hook = new \core_group\hook\after_group_created(
342
        groupinstance: $group,
343
    );
344
    \core\di::get(\core\hook\manager::class)->dispatch($hook);
345
 
346
    return $group->id;
347
}
348
 
349
/**
350
 * Add a new grouping
351
 *
352
 * @param stdClass $data grouping properties
353
 * @param array $editoroptions
354
 * @return int id of grouping or throws an exception on error
355
 * @throws moodle_exception
356
 */
357
function groups_create_grouping($data, $editoroptions=null) {
358
    global $DB;
359
 
360
    $data->timecreated  = time();
361
    $data->timemodified = $data->timecreated;
362
    $data->name         = trim($data->name);
363
    if (isset($data->idnumber)) {
364
        $data->idnumber = trim($data->idnumber);
365
        if (groups_get_grouping_by_idnumber($data->courseid, $data->idnumber)) {
366
            throw new moodle_exception('idnumbertaken');
367
        }
368
    }
369
 
370
    if ($editoroptions !== null) {
371
        $data->description = $data->description_editor['text'];
372
        $data->descriptionformat = $data->description_editor['format'];
373
    }
374
 
375
    $id = $DB->insert_record('groupings', $data);
376
    $data->id = $id;
377
 
378
    $handler = \core_group\customfield\grouping_handler::create();
379
    $handler->instance_form_save($data, true);
380
 
381
    if ($editoroptions !== null) {
382
        $description = new stdClass;
383
        $description->id = $data->id;
384
        $description->description_editor = $data->description_editor;
385
        $description = file_postupdate_standard_editor($description, 'description', $editoroptions, $editoroptions['context'], 'grouping', 'description', $description->id);
386
        $DB->update_record('groupings', $description);
387
    }
388
 
389
    // Invalidate the grouping cache for the course
390
    cache_helper::invalidate_by_definition('core', 'groupdata', array(), array($data->courseid));
391
 
392
    // Trigger group event.
393
    $params = array(
394
        'context' => context_course::instance($data->courseid),
395
        'objectid' => $id
396
    );
397
    $event = \core\event\grouping_created::create($params);
398
    $event->trigger();
399
 
400
    return $id;
401
}
402
 
403
/**
404
 * Update the group icon from form data
405
 *
406
 * @param stdClass $group group information
407
 * @param stdClass $data
408
 * @param stdClass $editform
409
 */
410
function groups_update_group_icon($group, $data, $editform) {
411
    global $CFG, $DB;
412
    require_once("$CFG->libdir/gdlib.php");
413
 
414
    $fs = get_file_storage();
415
    $context = context_course::instance($group->courseid, MUST_EXIST);
416
    $newpicture = $group->picture;
417
 
418
    if (!empty($data->deletepicture)) {
419
        $fs->delete_area_files($context->id, 'group', 'icon', $group->id);
420
        $newpicture = 0;
421
    } else if ($iconfile = $editform->save_temp_file('imagefile')) {
422
        if ($rev = process_new_icon($context, 'group', 'icon', $group->id, $iconfile)) {
423
            $newpicture = $rev;
424
        } else {
425
            $fs->delete_area_files($context->id, 'group', 'icon', $group->id);
426
            $newpicture = 0;
427
        }
428
        @unlink($iconfile);
429
    }
430
 
431
    if ($newpicture != $group->picture) {
432
        $DB->set_field('groups', 'picture', $newpicture, array('id' => $group->id));
433
        $group->picture = $newpicture;
434
 
435
        // Invalidate the group data as we've updated the group record.
436
        cache_helper::invalidate_by_definition('core', 'groupdata', array(), array($group->courseid));
437
    }
438
}
439
 
440
/**
441
 * Update group
442
 *
443
 * @param stdClass $data group properties (with magic quotes)
444
 * @param stdClass $editform
445
 * @param array $editoroptions
446
 * @return bool true or exception
447
 */
448
function groups_update_group($data, $editform = false, $editoroptions = false) {
449
    global $CFG, $DB, $USER;
450
 
451
    $context = context_course::instance($data->courseid);
452
 
453
    $data->timemodified = time();
454
    if (isset($data->name)) {
455
        $data->name = trim($data->name);
456
    }
457
    if (isset($data->idnumber)) {
458
        $data->idnumber = trim($data->idnumber);
459
        if (($existing = groups_get_group_by_idnumber($data->courseid, $data->idnumber)) && $existing->id != $data->id) {
460
            throw new moodle_exception('idnumbertaken');
461
        }
462
    }
463
    if (isset($data->visibility) && !in_array($data->visibility, [GROUPS_VISIBILITY_ALL, GROUPS_VISIBILITY_MEMBERS])) {
464
        $data->participation = false;
465
        $data->enablemessaging = false;
466
    }
467
 
468
    if ($editform and $editoroptions) {
469
        $data = file_postupdate_standard_editor($data, 'description', $editoroptions, $context, 'group', 'description', $data->id);
470
    }
471
 
472
    $DB->update_record('groups', $data);
473
 
474
    $handler = \core_group\customfield\group_handler::create();
475
    $handler->instance_form_save($data);
476
 
477
    // Invalidate the group data.
478
    cache_helper::invalidate_by_definition('core', 'groupdata', array(), array($data->courseid));
479
    // Rebuild the coursehiddengroups cache for the course.
480
    \core_group\visibility::update_hiddengroups_cache($data->courseid);
481
 
482
    $group = $DB->get_record('groups', array('id'=>$data->id));
483
 
484
    if ($editform) {
485
        groups_update_group_icon($group, $data, $editform);
486
    }
487
 
488
    // Group conversation messaging.
489
    if (\core_message\api::can_create_group_conversation($USER->id, $context)) {
490
        if ($conversation = \core_message\api::get_conversation_by_area('core_group', 'groups', $group->id, $context->id)) {
491
            if ($data->enablemessaging && $data->enablemessaging != $conversation->enabled) {
492
                \core_message\api::enable_conversation($conversation->id);
493
            }
494
            if (!$data->enablemessaging && $data->enablemessaging != $conversation->enabled) {
495
                \core_message\api::disable_conversation($conversation->id);
496
            }
497
            \core_message\api::update_conversation_name($conversation->id, $group->name);
498
        } else {
499
            if (!empty($data->enablemessaging)) {
500
                $conversation = \core_message\api::create_conversation(
501
                    \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP,
502
                    [],
503
                    $group->name,
504
                    \core_message\api::MESSAGE_CONVERSATION_ENABLED,
505
                    'core_group',
506
                    'groups',
507
                    $group->id,
508
                    $context->id
509
                );
510
 
511
                // Add members to conversation if they exists in the group.
512
                if ($groupmemberroles = groups_get_members_by_role($group->id, $group->courseid, 'u.id')) {
513
                    $users = [];
514
                    foreach ($groupmemberroles as $roleid => $roledata) {
515
                        foreach ($roledata->users as $member) {
516
                            $users[] = $member->id;
517
                        }
518
                    }
519
                    \core_message\api::add_members_to_conversation($users, $conversation->id);
520
                }
521
            }
522
        }
523
    }
524
 
525
    // Trigger group event.
526
    $params = array(
527
        'context' => $context,
528
        'objectid' => $group->id
529
    );
530
    $event = \core\event\group_updated::create($params);
531
    $event->add_record_snapshot('groups', $group);
532
    $event->trigger();
533
 
534
    // Dispatch the hook for post group update actions.
535
    $hook = new \core_group\hook\after_group_updated(
536
        groupinstance: $group,
537
    );
538
    \core\di::get(\core\hook\manager::class)->dispatch($hook);
539
 
540
    return true;
541
}
542
 
543
/**
544
 * Update grouping
545
 *
546
 * @param stdClass $data grouping properties (with magic quotes)
547
 * @param array $editoroptions
548
 * @return bool true or exception
549
 */
550
function groups_update_grouping($data, $editoroptions=null) {
551
    global $DB;
552
    $data->timemodified = time();
553
    if (isset($data->name)) {
554
        $data->name = trim($data->name);
555
    }
556
    if (isset($data->idnumber)) {
557
        $data->idnumber = trim($data->idnumber);
558
        if (($existing = groups_get_grouping_by_idnumber($data->courseid, $data->idnumber)) && $existing->id != $data->id) {
559
            throw new moodle_exception('idnumbertaken');
560
        }
561
    }
562
    if ($editoroptions !== null) {
563
        $data = file_postupdate_standard_editor($data, 'description', $editoroptions, $editoroptions['context'], 'grouping', 'description', $data->id);
564
    }
565
    $DB->update_record('groupings', $data);
566
 
567
    $handler = \core_group\customfield\grouping_handler::create();
568
    $handler->instance_form_save($data);
569
 
570
    // Invalidate the group data.
571
    cache_helper::invalidate_by_definition('core', 'groupdata', array(), array($data->courseid));
572
 
573
    // Trigger group event.
574
    $params = array(
575
        'context' => context_course::instance($data->courseid),
576
        'objectid' => $data->id
577
    );
578
    $event = \core\event\grouping_updated::create($params);
579
    $event->trigger();
580
 
581
    return true;
582
}
583
 
584
/**
585
 * Delete a group best effort, first removing members and links with courses and groupings.
586
 * Removes group avatar too.
587
 *
588
 * @param mixed $grouporid The id of group to delete or full group object
589
 * @return bool True if deletion was successful, false otherwise
590
 */
591
function groups_delete_group($grouporid) {
592
    global $CFG, $DB;
593
    require_once("$CFG->libdir/gdlib.php");
594
 
595
    if (is_object($grouporid)) {
596
        $groupid = $grouporid->id;
597
        $group   = $grouporid;
598
    } else {
599
        $groupid = $grouporid;
600
        if (!$group = $DB->get_record('groups', array('id'=>$groupid))) {
601
            //silently ignore attempts to delete missing already deleted groups ;-)
602
            return true;
603
        }
604
    }
605
 
606
    $context = context_course::instance($group->courseid);
607
 
608
    // delete group calendar events
609
    $DB->delete_records('event', array('groupid'=>$groupid));
610
    //first delete usage in groupings_groups
611
    $DB->delete_records('groupings_groups', array('groupid'=>$groupid));
612
    //delete members
613
    $DB->delete_records('groups_members', array('groupid'=>$groupid));
614
 
615
    // Delete any members in a conversation related to this group.
616
    if ($conversation = \core_message\api::get_conversation_by_area('core_group', 'groups', $groupid, $context->id)) {
617
        \core_message\api::delete_all_conversation_data($conversation->id);
618
    }
619
 
620
    //group itself last
621
    $DB->delete_records('groups', array('id'=>$groupid));
622
 
623
    // Delete all files associated with this group
624
    $context = context_course::instance($group->courseid);
625
    $fs = get_file_storage();
626
    $fs->delete_area_files($context->id, 'group', 'description', $groupid);
627
    $fs->delete_area_files($context->id, 'group', 'icon', $groupid);
628
 
629
    // Invalidate the grouping cache for the course
630
    cache_helper::invalidate_by_definition('core', 'groupdata', array(), array($group->courseid));
631
    // Purge the group and grouping cache for users.
632
    cache_helper::purge_by_definition('core', 'user_group_groupings');
633
    // Rebuild the coursehiddengroups cache for the course.
634
    \core_group\visibility::update_hiddengroups_cache($group->courseid);
635
 
636
    // Trigger group event.
637
    $params = array(
638
        'context' => $context,
639
        'objectid' => $groupid
640
    );
641
    $event = \core\event\group_deleted::create($params);
642
    $event->add_record_snapshot('groups', $group);
643
    $event->trigger();
644
 
645
    // Dispatch the hook for post group delete actions.
646
    $hook = new \core_group\hook\after_group_deleted(
647
        groupinstance: $group,
648
    );
649
    \core\di::get(\core\hook\manager::class)->dispatch($hook);
650
 
651
    return true;
652
}
653
 
654
/**
655
 * Delete grouping
656
 *
657
 * @param int $groupingorid
658
 * @return bool success
659
 */
660
function groups_delete_grouping($groupingorid) {
661
    global $DB;
662
 
663
    if (is_object($groupingorid)) {
664
        $groupingid = $groupingorid->id;
665
        $grouping   = $groupingorid;
666
    } else {
667
        $groupingid = $groupingorid;
668
        if (!$grouping = $DB->get_record('groupings', array('id'=>$groupingorid))) {
669
            //silently ignore attempts to delete missing already deleted groupings ;-)
670
            return true;
671
        }
672
    }
673
 
674
    //first delete usage in groupings_groups
675
    $DB->delete_records('groupings_groups', array('groupingid'=>$groupingid));
676
    // remove the default groupingid from course
677
    $DB->set_field('course', 'defaultgroupingid', 0, array('defaultgroupingid'=>$groupingid));
678
    // remove the groupingid from all course modules
679
    $DB->set_field('course_modules', 'groupingid', 0, array('groupingid'=>$groupingid));
680
    //group itself last
681
    $DB->delete_records('groupings', array('id'=>$groupingid));
682
 
683
    $context = context_course::instance($grouping->courseid);
684
    $fs = get_file_storage();
685
    $files = $fs->get_area_files($context->id, 'grouping', 'description', $groupingid);
686
    foreach ($files as $file) {
687
        $file->delete();
688
    }
689
 
690
    // Invalidate the grouping cache for the course
691
    cache_helper::invalidate_by_definition('core', 'groupdata', array(), array($grouping->courseid));
692
    // Purge the group and grouping cache for users.
693
    cache_helper::purge_by_definition('core', 'user_group_groupings');
694
 
695
    // Trigger group event.
696
    $params = array(
697
        'context' => $context,
698
        'objectid' => $groupingid
699
    );
700
    $event = \core\event\grouping_deleted::create($params);
701
    $event->add_record_snapshot('groupings', $grouping);
702
    $event->trigger();
703
 
704
    return true;
705
}
706
 
707
/**
708
 * Remove all users (or one user) from all groups in course
709
 *
710
 * @param int $courseid
711
 * @param int $userid 0 means all users
712
 * @param bool $unused - formerly $showfeedback, is no longer used.
713
 * @return bool success
714
 */
715
function groups_delete_group_members($courseid, $userid=0, $unused=false) {
716
    global $DB, $OUTPUT;
717
 
718
    // Get the users in the course which are in a group.
719
    $sql = "SELECT gm.id as gmid, gm.userid, g.*
720
              FROM {groups_members} gm
721
        INNER JOIN {groups} g
722
                ON gm.groupid = g.id
723
             WHERE g.courseid = :courseid";
724
    $params = array();
725
    $params['courseid'] = $courseid;
726
    // Check if we want to delete a specific user.
727
    if ($userid) {
728
        $sql .= " AND gm.userid = :userid";
729
        $params['userid'] = $userid;
730
    }
731
    $rs = $DB->get_recordset_sql($sql, $params);
732
    foreach ($rs as $usergroup) {
733
        groups_remove_member($usergroup, $usergroup->userid);
734
    }
735
    $rs->close();
736
 
737
    return true;
738
}
739
 
740
/**
741
 * Remove all groups from all groupings in course
742
 *
743
 * @param int $courseid
744
 * @param bool $showfeedback
745
 * @return bool success
746
 */
747
function groups_delete_groupings_groups($courseid, $showfeedback=false) {
748
    global $DB, $OUTPUT;
749
 
750
    $groupssql = "SELECT id FROM {groups} g WHERE g.courseid = ?";
751
    $results = $DB->get_recordset_select('groupings_groups', "groupid IN ($groupssql)",
752
        array($courseid), '', 'groupid, groupingid');
753
 
754
    foreach ($results as $result) {
755
        groups_unassign_grouping($result->groupingid, $result->groupid, false);
756
    }
757
    $results->close();
758
 
759
    // Invalidate the grouping cache for the course
760
    cache_helper::invalidate_by_definition('core', 'groupdata', array(), array($courseid));
761
    // Purge the group and grouping cache for users.
762
    cache_helper::purge_by_definition('core', 'user_group_groupings');
763
 
764
    // no need to show any feedback here - we delete usually first groupings and then groups
765
 
766
    return true;
767
}
768
 
769
/**
770
 * Delete all groups from course
771
 *
772
 * @param int $courseid
773
 * @param bool $showfeedback
774
 * @return bool success
775
 */
776
function groups_delete_groups($courseid, $showfeedback=false) {
777
    global $CFG, $DB, $OUTPUT;
778
 
779
    $groups = $DB->get_recordset('groups', array('courseid' => $courseid));
780
    foreach ($groups as $group) {
781
        groups_delete_group($group);
782
    }
783
    $groups->close();
784
 
785
    // Invalidate the grouping cache for the course
786
    cache_helper::invalidate_by_definition('core', 'groupdata', array(), array($courseid));
787
    // Purge the group and grouping cache for users.
788
    cache_helper::purge_by_definition('core', 'user_group_groupings');
789
    // Rebuild the coursehiddengroups cache for the course.
790
    \core_group\visibility::update_hiddengroups_cache($courseid);
791
 
792
    if ($showfeedback) {
793
        echo $OUTPUT->notification(get_string('deleted').' - '.get_string('groups', 'group'), 'notifysuccess');
794
    }
795
 
796
    return true;
797
}
798
 
799
/**
800
 * Delete all groupings from course
801
 *
802
 * @param int $courseid
803
 * @param bool $showfeedback
804
 * @return bool success
805
 */
806
function groups_delete_groupings($courseid, $showfeedback=false) {
807
    global $DB, $OUTPUT;
808
 
809
    $groupings = $DB->get_recordset_select('groupings', 'courseid = ?', array($courseid));
810
    foreach ($groupings as $grouping) {
811
        groups_delete_grouping($grouping);
812
    }
813
    $groupings->close();
814
 
815
    // Invalidate the grouping cache for the course.
816
    cache_helper::invalidate_by_definition('core', 'groupdata', array(), array($courseid));
817
    // Purge the group and grouping cache for users.
818
    cache_helper::purge_by_definition('core', 'user_group_groupings');
819
 
820
    if ($showfeedback) {
821
        echo $OUTPUT->notification(get_string('deleted').' - '.get_string('groupings', 'group'), 'notifysuccess');
822
    }
823
 
824
    return true;
825
}
826
 
827
/* =================================== */
828
/* various functions used by groups UI */
829
/* =================================== */
830
 
831
/**
832
 * Obtains a list of the possible roles that group members might come from,
833
 * on a course. Generally this includes only profile roles.
834
 *
835
 * @param context $context Context of course
836
 * @return Array of role ID integers, or false if error/none.
837
 */
838
function groups_get_possible_roles($context) {
839
    $roles = get_profile_roles($context);
840
    return array_keys($roles);
841
}
842
 
843
 
844
/**
845
 * Gets potential group members for grouping
846
 *
847
 * @param int $courseid The id of the course
848
 * @param int $roleid The role to select users from
849
 * @param mixed $source restrict to cohort, grouping or group id
850
 * @param string $orderby The column to sort users by
851
 * @param int $notingroup restrict to users not in existing groups
852
 * @param bool $onlyactiveenrolments restrict to users who have an active enrolment in the course
853
 * @param array $extrafields Extra user fields to return
854
 * @return array An array of the users
855
 */
856
function groups_get_potential_members($courseid, $roleid = null, $source = null,
857
                                      $orderby = 'lastname ASC, firstname ASC',
858
                                      $notingroup = null, $onlyactiveenrolments = false, $extrafields = []) {
859
    global $DB;
860
 
861
    $context = context_course::instance($courseid);
862
 
863
    list($esql, $params) = get_enrolled_sql($context, '', 0, $onlyactiveenrolments);
864
 
865
    $notingroupsql = "";
866
    if ($notingroup) {
867
        // We want to eliminate users that are already associated with a course group.
868
        $notingroupsql = "u.id NOT IN (SELECT userid
869
                                         FROM {groups_members}
870
                                        WHERE groupid IN (SELECT id
871
                                                            FROM {groups}
872
                                                           WHERE courseid = :courseid))";
873
        $params['courseid'] = $courseid;
874
    }
875
 
876
    if ($roleid) {
877
        // We are looking for all users with this role assigned in this context or higher.
878
        list($relatedctxsql, $relatedctxparams) = $DB->get_in_or_equal($context->get_parent_context_ids(true),
879
                                                                       SQL_PARAMS_NAMED,
880
                                                                       'relatedctx');
881
 
882
        $params = array_merge($params, $relatedctxparams, array('roleid' => $roleid));
883
        $where = "WHERE u.id IN (SELECT userid
884
                                   FROM {role_assignments}
885
                                  WHERE roleid = :roleid AND contextid $relatedctxsql)";
886
        $where .= $notingroup ? "AND $notingroupsql" : "";
887
    } else if ($notingroup) {
888
        $where = "WHERE $notingroupsql";
889
    } else {
890
        $where = "";
891
    }
892
 
893
    $sourcejoin = "";
894
    if (is_int($source)) {
895
        $sourcejoin .= "JOIN {cohort_members} cm ON (cm.userid = u.id AND cm.cohortid = :cohortid) ";
896
        $params['cohortid'] = $source;
897
    } else {
898
        // Auto-create groups from an existing cohort membership.
899
        if (isset($source['cohortid'])) {
900
            $sourcejoin .= "JOIN {cohort_members} cm ON (cm.userid = u.id AND cm.cohortid = :cohortid) ";
901
            $params['cohortid'] = $source['cohortid'];
902
        }
903
        // Auto-create groups from an existing group membership.
904
        if (isset($source['groupid'])) {
905
            $sourcejoin .= "JOIN {groups_members} gp ON (gp.userid = u.id AND gp.groupid = :groupid) ";
906
            $params['groupid'] = $source['groupid'];
907
        }
908
        // Auto-create groups from an existing grouping membership.
909
        if (isset($source['groupingid'])) {
910
            $sourcejoin .= "JOIN {groupings_groups} gg ON gg.groupingid = :groupingid ";
911
            $sourcejoin .= "JOIN {groups_members} gm ON (gm.userid = u.id AND gm.groupid = gg.groupid) ";
912
            $params['groupingid'] = $source['groupingid'];
913
        }
914
    }
915
 
916
    $userfieldsapi = \core_user\fields::for_userpic()->including(...$extrafields);
917
    $allusernamefields = $userfieldsapi->get_sql('u', false, '', '', false)->selects;
918
    $sql = "SELECT DISTINCT u.id, u.username, $allusernamefields, u.idnumber
919
              FROM {user} u
920
              JOIN ($esql) e ON e.id = u.id
921
       $sourcejoin
922
            $where
923
          ORDER BY $orderby";
924
 
925
    return $DB->get_records_sql($sql, $params);
926
 
927
}
928
 
929
/**
930
 * Parse a group name for characters to replace
931
 *
932
 * @param string $format The format a group name will follow
933
 * @param int $groupnumber The number of the group to be used in the parsed format string
934
 * @return string the parsed format string
935
 */
936
function groups_parse_name($format, $groupnumber) {
937
    if (strstr($format, '@') !== false) { // Convert $groupnumber to a character series
938
        $letter = 'A';
939
        for($i=0; $i<$groupnumber; $i++) {
940
            $letter++;
941
        }
942
        $str = str_replace('@', $letter, $format);
943
    } else {
944
        $str = str_replace('#', $groupnumber+1, $format);
945
    }
946
    return($str);
947
}
948
 
949
/**
950
 * Assigns group into grouping
951
 *
952
 * @param int groupingid
953
 * @param int groupid
954
 * @param int $timeadded  The time the group was added to the grouping.
955
 * @param bool $invalidatecache If set to true the course group cache and the user group cache will be invalidated as well.
956
 * @return bool true or exception
957
 */
958
function groups_assign_grouping($groupingid, $groupid, $timeadded = null, $invalidatecache = true) {
959
    global $DB;
960
 
961
    if ($DB->record_exists('groupings_groups', array('groupingid'=>$groupingid, 'groupid'=>$groupid))) {
962
        return true;
963
    }
964
    $assign = new stdClass();
965
    $assign->groupingid = $groupingid;
966
    $assign->groupid    = $groupid;
967
    if ($timeadded != null) {
968
        $assign->timeadded = (integer)$timeadded;
969
    } else {
970
        $assign->timeadded = time();
971
    }
972
    $DB->insert_record('groupings_groups', $assign);
973
 
974
    $courseid = $DB->get_field('groupings', 'courseid', array('id' => $groupingid));
975
    if ($invalidatecache) {
976
        // Invalidate the grouping cache for the course
977
        cache_helper::invalidate_by_definition('core', 'groupdata', array(), array($courseid));
978
        // Purge the group and grouping cache for users.
979
        cache_helper::purge_by_definition('core', 'user_group_groupings');
980
    }
981
 
982
    // Trigger event.
983
    $params = array(
984
        'context' => context_course::instance($courseid),
985
        'objectid' => $groupingid,
986
        'other' => array('groupid' => $groupid)
987
    );
988
    $event = \core\event\grouping_group_assigned::create($params);
989
    $event->trigger();
990
 
991
    return true;
992
}
993
 
994
/**
995
 * Unassigns group from grouping
996
 *
997
 * @param int groupingid
998
 * @param int groupid
999
 * @param bool $invalidatecache If set to true the course group cache and the user group cache will be invalidated as well.
1000
 * @return bool success
1001
 */
1002
function groups_unassign_grouping($groupingid, $groupid, $invalidatecache = true) {
1003
    global $DB;
1004
    $DB->delete_records('groupings_groups', array('groupingid'=>$groupingid, 'groupid'=>$groupid));
1005
 
1006
    $courseid = $DB->get_field('groupings', 'courseid', array('id' => $groupingid));
1007
    if ($invalidatecache) {
1008
        // Invalidate the grouping cache for the course
1009
        cache_helper::invalidate_by_definition('core', 'groupdata', array(), array($courseid));
1010
        // Purge the group and grouping cache for users.
1011
        cache_helper::purge_by_definition('core', 'user_group_groupings');
1012
    }
1013
 
1014
    // Trigger event.
1015
    $params = array(
1016
        'context' => context_course::instance($courseid),
1017
        'objectid' => $groupingid,
1018
        'other' => array('groupid' => $groupid)
1019
    );
1020
    $event = \core\event\grouping_group_unassigned::create($params);
1021
    $event->trigger();
1022
 
1023
    return true;
1024
}
1025
 
1026
/**
1027
 * Lists users in a group based on their role on the course.
1028
 * Returns false if there's an error or there are no users in the group.
1029
 * Otherwise returns an array of role ID => role data, where role data includes:
1030
 * (role) $id, $shortname, $name
1031
 * $users: array of objects for each user which include the specified fields
1032
 * Users who do not have a role are stored in the returned array with key '-'
1033
 * and pseudo-role details (including a name, 'No role'). Users with multiple
1034
 * roles, same deal with key '*' and name 'Multiple roles'. You can find out
1035
 * which roles each has by looking in the $roles array of the user object.
1036
 *
1037
 * @param int $groupid
1038
 * @param int $courseid Course ID (should match the group's course)
1039
 * @param string $fields List of fields from user table (prefixed with u) and joined tables, default 'u.*'
1040
 * @param string|null $sort SQL ORDER BY clause, default (when null passed) is what comes from users_order_by_sql.
1041
 * @param string $extrawheretest extra SQL conditions ANDed with the existing where clause.
1042
 * @param array $whereorsortparams any parameters required by $extrawheretest or $joins (named parameters).
1043
 * @param string $joins any joins required to get the specified fields.
1044
 * @return array Complex array as described above
1045
 */
1046
function groups_get_members_by_role(int $groupid, int $courseid, string $fields = 'u.*',
1047
        ?string $sort = null, string $extrawheretest = '', array $whereorsortparams = [], string $joins = '') {
1048
    global $DB;
1049
 
1050
    // Retrieve information about all users and their roles on the course or
1051
    // parent ('related') contexts
1052
    $context = context_course::instance($courseid);
1053
 
1054
    // We are looking for all users with this role assigned in this context or higher.
1055
    list($relatedctxsql, $relatedctxparams) = $DB->get_in_or_equal($context->get_parent_context_ids(true), SQL_PARAMS_NAMED, 'relatedctx');
1056
 
1057
    if ($extrawheretest) {
1058
        $extrawheretest = ' AND ' . $extrawheretest;
1059
    }
1060
 
1061
    if (is_null($sort)) {
1062
        list($sort, $sortparams) = users_order_by_sql('u');
1063
        $whereorsortparams = array_merge($whereorsortparams, $sortparams);
1064
    }
1065
 
1066
    $sql = "SELECT r.id AS roleid, u.id AS userid, $fields
1067
              FROM {groups_members} gm
1068
              JOIN {user} u ON u.id = gm.userid
1069
         LEFT JOIN {role_assignments} ra ON (ra.userid = u.id AND ra.contextid $relatedctxsql)
1070
         LEFT JOIN {role} r ON r.id = ra.roleid
1071
                   $joins
1072
             WHERE gm.groupid=:mgroupid
1073
                   ".$extrawheretest."
1074
          ORDER BY r.sortorder, $sort";
1075
    $whereorsortparams = array_merge($whereorsortparams, $relatedctxparams, array('mgroupid' => $groupid));
1076
    $rs = $DB->get_recordset_sql($sql, $whereorsortparams);
1077
 
1078
    return groups_calculate_role_people($rs, $context);
1079
}
1080
 
1081
/**
1082
 * Internal function used by groups_get_members_by_role to handle the
1083
 * results of a database query that includes a list of users and possible
1084
 * roles on a course.
1085
 *
1086
 * @param moodle_recordset $rs The record set (may be false)
1087
 * @param int $context ID of course context
1088
 * @return array As described in groups_get_members_by_role
1089
 */
1090
function groups_calculate_role_people($rs, $context) {
1091
    global $CFG, $DB;
1092
 
1093
    if (!$rs) {
1094
        return array();
1095
    }
1096
 
1097
    $allroles = role_fix_names(get_all_roles($context), $context);
1098
    $visibleroles = get_viewable_roles($context);
1099
 
1100
    // Array of all involved roles
1101
    $roles = array();
1102
    // Array of all retrieved users
1103
    $users = array();
1104
    // Fill arrays
1105
    foreach ($rs as $rec) {
1106
        // Create information about user if this is a new one
1107
        if (!array_key_exists($rec->userid, $users)) {
1108
            // User data includes all the optional fields, but not any of the
1109
            // stuff we added to get the role details
1110
            $userdata = clone($rec);
1111
            unset($userdata->roleid);
1112
            unset($userdata->roleshortname);
1113
            unset($userdata->rolename);
1114
            unset($userdata->userid);
1115
            $userdata->id = $rec->userid;
1116
 
1117
            // Make an array to hold the list of roles for this user
1118
            $userdata->roles = array();
1119
            $users[$rec->userid] = $userdata;
1120
        }
1121
        // If user has a role...
1122
        if (!is_null($rec->roleid)) {
1123
            // Create information about role if this is a new one
1124
            if (!array_key_exists($rec->roleid, $roles)) {
1125
                $role = $allroles[$rec->roleid];
1126
                $roledata = new stdClass();
1127
                $roledata->id        = $role->id;
1128
                $roledata->shortname = $role->shortname;
1129
                $roledata->name      = $role->localname;
1130
                $roledata->users = array();
1131
                $roles[$roledata->id] = $roledata;
1132
            }
1133
            // Record that user has role
1134
            $users[$rec->userid]->roles[$rec->roleid] = $roles[$rec->roleid];
1135
        }
1136
    }
1137
    $rs->close();
1138
 
1139
    // Return false if there weren't any users
1140
    if (count($users) == 0) {
1141
        return false;
1142
    }
1143
 
1144
    // Add pseudo-role for multiple roles
1145
    $roledata = new stdClass();
1146
    $roledata->name = get_string('multipleroles','role');
1147
    $roledata->users = array();
1148
    $roles['*'] = $roledata;
1149
 
1150
    $roledata = new stdClass();
1151
    $roledata->name = get_string('noroles','role');
1152
    $roledata->users = array();
1153
    $roles[0] = $roledata;
1154
 
1155
    // Now we rearrange the data to store users by role
1156
    foreach ($users as $userid=>$userdata) {
1157
        $visibleuserroles = array_intersect_key($userdata->roles, $visibleroles);
1158
        $rolecount = count($visibleuserroles);
1159
        if ($rolecount == 0) {
1160
            // does not have any roles
1161
            $roleid = 0;
1162
        } else if($rolecount > 1) {
1163
            $roleid = '*';
1164
        } else {
1165
            $userrole = reset($visibleuserroles);
1166
            $roleid = $userrole->id;
1167
        }
1168
        $roles[$roleid]->users[$userid] = $userdata;
1169
    }
1170
 
1171
    // Delete roles not used
1172
    foreach ($roles as $key=>$roledata) {
1173
        if (count($roledata->users)===0) {
1174
            unset($roles[$key]);
1175
        }
1176
    }
1177
 
1178
    // Return list of roles containing their users
1179
    return $roles;
1180
}
1181
 
1182
/**
1183
 * Synchronises enrolments with the group membership
1184
 *
1185
 * Designed for enrolment methods provide automatic synchronisation between enrolled users
1186
 * and group membership, such as enrol_cohort and enrol_meta .
1187
 *
1188
 * @param string $enrolname name of enrolment method without prefix
1189
 * @param int $courseid course id where sync needs to be performed (0 for all courses)
1190
 * @param string $gidfield name of the field in 'enrol' table that stores group id
1191
 * @return array Returns the list of removed and added users. Each record contains fields:
1192
 *                  userid, enrolid, courseid, groupid, groupname
1193
 */
1194
function groups_sync_with_enrolment($enrolname, $courseid = 0, $gidfield = 'customint2') {
1195
    global $DB;
1196
    $onecourse = $courseid ? "AND e.courseid = :courseid" : "";
1197
    $params = array(
1198
        'enrolname' => $enrolname,
1199
        'component' => 'enrol_'.$enrolname,
1200
        'courseid' => $courseid
1201
    );
1202
 
1203
    $affectedusers = array(
1204
        'removed' => array(),
1205
        'added' => array()
1206
    );
1207
 
1208
    // Remove invalid.
1209
    $sql = "SELECT ue.userid, ue.enrolid, e.courseid, g.id AS groupid, g.name AS groupname
1210
              FROM {groups_members} gm
1211
              JOIN {groups} g ON (g.id = gm.groupid)
1212
              JOIN {enrol} e ON (e.enrol = :enrolname AND e.courseid = g.courseid $onecourse)
1213
              JOIN {user_enrolments} ue ON (ue.userid = gm.userid AND ue.enrolid = e.id)
1214
             WHERE gm.component=:component AND gm.itemid = e.id AND g.id <> e.{$gidfield}";
1215
 
1216
    $rs = $DB->get_recordset_sql($sql, $params);
1217
    foreach ($rs as $gm) {
1218
        groups_remove_member($gm->groupid, $gm->userid);
1219
        $affectedusers['removed'][] = $gm;
1220
    }
1221
    $rs->close();
1222
 
1223
    // Add missing.
1224
    $sql = "SELECT ue.userid, ue.enrolid, e.courseid, g.id AS groupid, g.name AS groupname
1225
              FROM {user_enrolments} ue
1226
              JOIN {enrol} e ON (e.id = ue.enrolid AND e.enrol = :enrolname $onecourse)
1227
              JOIN {groups} g ON (g.courseid = e.courseid AND g.id = e.{$gidfield})
1228
              JOIN {user} u ON (u.id = ue.userid AND u.deleted = 0)
1229
         LEFT JOIN {groups_members} gm ON (gm.groupid = g.id AND gm.userid = ue.userid)
1230
             WHERE gm.id IS NULL";
1231
 
1232
    $rs = $DB->get_recordset_sql($sql, $params);
1233
    foreach ($rs as $ue) {
1234
        groups_add_member($ue->groupid, $ue->userid, 'enrol_'.$enrolname, $ue->enrolid);
1235
        $affectedusers['added'][] = $ue;
1236
    }
1237
    $rs->close();
1238
 
1239
    return $affectedusers;
1240
}
1241
 
1242
/**
1243
 * Callback for inplace editable API.
1244
 *
1245
 * @param string $itemtype - Only user_groups is supported.
1246
 * @param string $itemid - Userid and groupid separated by a :
1247
 * @param string $newvalue - json encoded list of groupids.
1248
 * @return \core\output\inplace_editable
1249
 */
1250
function core_group_inplace_editable($itemtype, $itemid, $newvalue) {
1251
    if ($itemtype === 'user_groups') {
1252
        return \core_group\output\user_groups_editable::update($itemid, $newvalue);
1253
    }
1254
}
1255
 
1256
/**
1257
 * Updates group messaging to enable/disable in bulk.
1258
 *
1259
 * @param array $groupids array of group id numbers.
1260
 * @param bool $enabled if true, enables messaging else disables messaging
1261
 */
1262
function set_groups_messaging(array $groupids, bool $enabled): void {
1263
    foreach ($groupids as $groupid) {
1264
        $data = groups_get_group($groupid, '*', MUST_EXIST);
1265
        $data->enablemessaging = $enabled;
1266
        groups_update_group($data);
1267
    }
1268
}
1269
 
1270
/**
1271
 * Returns custom fields data for provided groups.
1272
 *
1273
 * @param array $groupids a list of group IDs to provide data for.
1274
 * @return \core_customfield\data_controller[]
1275
 */
1276
function get_group_custom_fields_data(array $groupids): array {
1277
    $result = [];
1278
 
1279
    if (!empty($groupids)) {
1280
        $handler = \core_group\customfield\group_handler::create();
1281
        $customfieldsdata = $handler->get_instances_data($groupids, true);
1282
 
1283
        foreach ($customfieldsdata as $groupid => $fieldcontrollers) {
1284
            foreach ($fieldcontrollers as $fieldcontroller) {
1285
                $result[$groupid][] = [
1286
                    'type' => $fieldcontroller->get_field()->get('type'),
1287
                    'value' => $fieldcontroller->export_value(),
1288
                    'valueraw' => $fieldcontroller->get_value(),
1289
                    'name' => $fieldcontroller->get_field()->get('name'),
1290
                    'shortname' => $fieldcontroller->get_field()->get('shortname'),
1291
                ];
1292
            }
1293
        }
1294
    }
1295
 
1296
    return $result;
1297
}
1298
 
1299
/**
1300
 * Returns custom fields data for provided groupings.
1301
 *
1302
 * @param array $groupingids a list of group IDs to provide data for.
1303
 * @return \core_customfield\data_controller[]
1304
 */
1305
function get_grouping_custom_fields_data(array $groupingids): array {
1306
    $result = [];
1307
 
1308
    if (!empty($groupingids)) {
1309
        $handler = \core_group\customfield\grouping_handler::create();
1310
        $customfieldsdata = $handler->get_instances_data($groupingids, true);
1311
 
1312
        foreach ($customfieldsdata as $groupingid => $fieldcontrollers) {
1313
            foreach ($fieldcontrollers as $fieldcontroller) {
1314
                $result[$groupingid][] = [
1315
                    'type' => $fieldcontroller->get_field()->get('type'),
1316
                    'value' => $fieldcontroller->export_value(),
1317
                    'valueraw' => $fieldcontroller->get_value(),
1318
                    'name' => $fieldcontroller->get_field()->get('name'),
1319
                    'shortname' => $fieldcontroller->get_field()->get('shortname'),
1320
                ];
1321
            }
1322
        }
1323
    }
1324
 
1325
    return $result;
1326
}