group.php 44.3 KB
Newer Older
1
2
<?php
/**
Francois Marier's avatar
Francois Marier committed
3
 * Mahara: Electronic portfolio, weblog, resume builder and social networking
4
5
 * Copyright (C) 2006-2009 Catalyst IT Ltd and others; see:
 *                         http://wiki.mahara.org/Contributors
6
 *
Francois Marier's avatar
Francois Marier committed
7
8
9
10
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
11
 *
Francois Marier's avatar
Francois Marier committed
12
13
14
15
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
16
 *
Francois Marier's avatar
Francois Marier committed
17
18
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
19
20
21
 *
 * @package    mahara
 * @subpackage core
22
 * @author     Catalyst IT Ltd
23
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL
24
 * @copyright  (C) 2006-2009 Catalyst IT Ltd http://catalyst.net.nz
25
26
27
28
29
 *
 */

defined('INTERNAL') || die();

30
// Role related functions
31

32
33
34
35
36
37
38
39
40
41
42
/**
 * Establishes what role a user has in a given group.
 *
 * If the user is not in the group, this returns false.
 *
 * @param mixed $groupid  ID of the group to check
 * @param mixed $userid   ID of the user to check. Defaults to the logged in 
 *                        user.
 * @return mixed          The role the user has in the group, or false if they 
 *                        have no role in the group
 */
43
function group_user_access($groupid, $userid=null, $refresh=null) {
44
    static $result;
45

46
47
48
49
    if (!is_logged_in()) {
        return false;
    }

50
51
    $groupid = group_param_groupid($groupid);
    $userid  = group_param_userid($userid);
52

53
    if (isset($result[$groupid][$userid]) && !isset($refresh)) {
54
55
56
57
        return $result[$groupid][$userid];
    }

    return $result[$groupid][$userid] = get_field('group_member', 'role', 'group', $groupid, 'member', $userid);
58
59
}

60
61
62
63
64
65
/**
 * Returns whether the given user is the only administrator in the given group.
 *
 * If the user isn't in the group, or they're not an admin, or there is another admin, false 
 * is returned.
 *
66
67
 * @param int $groupid The ID of the group to check
 * @param int $userid  The ID of the user to check
68
69
 * @returns boolean
 */
70
function group_is_only_admin($groupid, $userid=null) {
71
    static $result;
72

73
74
    $groupid = group_param_groupid($groupid);
    $userid  = group_param_userid($userid);
75

76
77
78
79
80
81
    if (isset($result[$groupid][$userid])) {
        return $result[$groupid][$userid];
    }

    return $result[$groupid][$userid] = (group_user_access($groupid, $userid) == 'admin'
        && count_records('group_member', 'group', $groupid, 'role', 'admin') == 1);
82
83
84
85
86
87
88
89
90
}

/**
 * Returns whether the given user is allowed to change their role to the 
 * requested role in the given group.
 *
 * This function is checking whether _role changes_ are allowed, not if a user 
 * is allowed to be added to a group.
 *
91
92
 * @param int $groupid The ID of the group to check
 * @param int $userid  The ID of the user to check
93
94
95
 * @param string $role The role the user wishes to switch to
 * @returns boolean
 */
96
function group_can_change_role($groupid, $userid, $role) {
97
98
    $groupid = group_param_groupid($groupid);
    $userid  = group_param_userid($userid);
99
100

    if (!group_user_access($groupid, $userid)) {
101
102
103
104
        return false;
    }

    // Sole remaining admins can never change their role
105
    if (group_is_only_admin($groupid, $userid)) {
106
107
108
        return false;
    }

109
110
111
112
113
114
115
116
    // admin role permissions check
    if ($role == 'admin') {
        $group = group_current_group();
        $user = new User();
        $user->find_by_id($userid);
        safe_require('grouptype', $group->grouptype);
        return in_array($group->jointype, call_static_method('GroupType' . $group->grouptype, 'user_allowed_join_types', $user));
    }
117
118
119
120
121
122
123

    return true;
}

/**
 * Changes a user role in a group, if this is allowed.
 *
124
125
 * @param int $groupid The ID of the group
 * @param int $userid  The ID of the user whose role needs changing
126
127
128
129
130
 * @param string $role The role the user wishes to switch to
 * @throws AccessDeniedException If the specified role change is not allowed. 
 *                               Check with group_can_change_role first if you 
 *                               need to.
 */
131
132
133
function group_change_role($groupid, $userid, $role) {
    // group_can_change_role checks whether the group and user parameters are valid
    if (!group_can_change_role($groupid, $userid, $role)) {
134
        throw new AccessDeniedException(get_string('usercannotchangetothisrole', 'group'));
135
136
    }

137
    set_field('group_member', 'role', $role, 'group', $groupid, 'member', $userid);
138
139
}

140
141
142
143
144
145
146
/**
 * Returns whether a user is allowed to edit views in a given group
 *
 * @param int $groupid The ID of the group
 * @param int $userid The ID of the user
 * @returns boolean
 */
147
function group_user_can_edit_views($groupid, $userid=null) {
148
149
150
151
    if (!is_logged_in()) {
        return false;
    }

152
153
    $groupid = group_param_groupid($groupid);
    $userid  = group_param_userid($userid);
154
155
156
157
158
159
160
161
162
163
164
165
166

    return get_field_sql('
        SELECT
            r.edit_views
        FROM
            {group_member} m
            INNER JOIN {group} g ON (m.group = g.id AND g.deleted = 0)
            INNER JOIN {grouptype_roles} r ON (g.grouptype = r.grouptype AND m.role = r.role)
        WHERE
            m.group = ?
            AND m.member = ?', array($groupid, $userid));
}

167
168
169
170
171
172
173
174
175
/**
 * Returns whether a user is allowed to assess views that have been submitted 
 * to the given group.
 *
 * @param int $groupid ID of group
 * @param int $userid  ID of user
 * @return boolean
 */
function group_user_can_assess_submitted_views($groupid, $userid) {
176
177
    $groupid = group_param_groupid($groupid);
    $userid  = group_param_userid($userid);
178
179
180
181
182
183
184
185
186
187
188
189
190

    return get_field_sql('
        SELECT
            r.see_submitted_views
        FROM
            {group_member} m
            INNER JOIN {group} g ON (m.group = g.id AND g.deleted = 0)
            INNER JOIN {grouptype_roles} r ON (g.grouptype = r.grouptype AND r.role = m.role)
        WHERE
            m.member = ?
            AND m.group = ?', array($userid, $groupid));
}

191
192
// Functions for creation/deletion of groups, and adding/removing users to groups

193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
/**
 * Creates a group.
 *
 * All group creation should be done through this function, as the 
 * implementation of group creation may change over time.
 *
 * @param array $data Data required to create the group. The following 
 * key/value pairs can be specified:
 *
 * - name: The group name [required, must be unique]
 * - description: The group description [optional, defaults to empty string]
 * - grouptype: The grouptype for the new group. Must be an installed grouptype.
 * - jointype: The jointype for the new group. One of 'open', 'invite', 
 *             'request' or 'controlled'
 * - ctime: The unix timestamp of the time the group will be recorded as having 
 *          been created. Defaults to the current time.
 * - members: Array of users who should be in the group, structured like this:
 *            array(
 *                userid => role,
 *                userid => role,
 *                ...
 *            )
215
 * @return int The ID of the created group
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
 */
function group_create($data) {
    if (!is_array($data)) {
        throw new InvalidArgumentException("group_create: data must be an array, see the doc comment for this "
            . "function for details on its format");
    }

    if (!isset($data['name'])) {
        throw new InvalidArgumentException("group_create: must specify a name for the group");
    }

    if (!isset($data['grouptype']) || !in_array($data['grouptype'], group_get_grouptypes())) {
        throw new InvalidArgumentException("group_create: grouptype specified must be an installed grouptype");
    }

231
232
    safe_require('grouptype', $data['grouptype']);

233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
    if (isset($data['jointype'])) {
        if (!in_array($data['jointype'], call_static_method('GroupType' . $data['grouptype'], 'allowed_join_types'))) {
            throw new InvalidArgumentException("group_create: jointype specified is not allowed by the grouptype specified");
        }
    }
    else {
        throw new InvalidArgumentException("group_create: jointype specified must be one of the valid join types");
    }

    if (!isset($data['ctime'])) {
        $data['ctime'] = time();
    }
    $data['ctime'] = db_format_timestamp($data['ctime']);

    if (!is_array($data['members']) || count($data['members']) == 0) {
        throw new InvalidArgumentException("group_create: at least one member must be specified for adding to the group");
    }

251
    $data['public'] = (isset($data['public'])) ? intval($data['public']) : 0;
252
    $data['usersautoadded'] = (isset($data['usersautoadded'])) ? intval($data['usersautoadded']) : 0;
253

254
255
256
257
258
    db_begin();

    $id = insert_record(
        'group',
        (object) array(
259
260
261
262
263
264
265
266
            'name'           => $data['name'],
            'description'    => $data['description'],
            'grouptype'      => $data['grouptype'],
            'jointype'       => $data['jointype'],
            'ctime'          => $data['ctime'],
            'mtime'          => $data['ctime'],
            'public'         => $data['public'],
            'usersautoadded' => $data['usersautoadded'],
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
        ),
        'id',
        true
    );

    foreach ($data['members'] as $userid => $role) {
        insert_record(
            'group_member',
            (object) array(
                'group'  => $id,
                'member' => $userid,
                'role'   => $role,
                'ctime'  => $data['ctime'],
            )
        );
    }

Richard Mansfield's avatar
Richard Mansfield committed
284
285
    // Copy views for the new group
    $templates = get_column('view_autocreate_grouptype', 'view', 'grouptype', $data['grouptype']);
286
287
288
289
290
    $templates = get_records_sql_array("
        SELECT v.id, v.title, v.description 
        FROM {view} v
        INNER JOIN {view_autocreate_grouptype} vag ON vag.view = v.id
        WHERE vag.grouptype = 'standard'", array());
Richard Mansfield's avatar
Richard Mansfield committed
291
292
    if ($templates) {
        require_once(get_config('libroot') . 'view.php');
293
294
        foreach ($templates as $template) {
            list($view) = View::create_from_template(array(
Richard Mansfield's avatar
Richard Mansfield committed
295
                'group'       => $id,
296
297
298
299
                'title'       => $template->title,
                'description' => $template->description,
            ), $template->id);
            $view->set_access(array(array(
Richard Mansfield's avatar
Richard Mansfield committed
300
301
302
303
304
305
306
307
308
                'type'      => 'group',
                'id'        => $id,
                'startdate' => null,
                'stopdate'  => null,
                'role'      => null
            )));
        }
    }

Richard Mansfield's avatar
Richard Mansfield committed
309
310
    $data['id'] = $id;
    handle_event('creategroup', $data);
311
    db_commit();
312
313

    return $id;
314
315
}

316
317
318
319
320
321
322
323
324
325
326
327
/**
 * Deletes a group.
 *
 * All group deleting should be done through this function, even though it is 
 * simple. What is required to perform group deletion may change over time.
 *
 * @param int $groupid The group to delete
 *
 * {{@internal Maybe later we can have a group_can_be_deleted function if 
 * necessary}}
 */
function group_delete($groupid) {
328
    $groupid = group_param_groupid($groupid);
329
330
331
332
333
334
335
336
337
    update_record('group',
        array(
            'deleted' => 1,
            'name' => get_field('group', 'name', 'id', $groupid) . '.deleted.' . time(),
        ),
        array(
            'id' => $groupid,
        )
    );
338
339
}

340
/**
341
342
343
344
345
 * Adds a member to a group.
 *
 * Doesn't do any jointype checking, that should be handled by the caller.
 *
 * TODO: it should though. We should probably have group_user_can_be_added
346
 *
347
 * @param int $groupid
348
 * @param int $userid
Richard Mansfield's avatar
Richard Mansfield committed
349
 * @param string $role
350
 */
351
function group_add_user($groupid, $userid, $role=null) {
352
353
354
    $groupid = group_param_groupid($groupid);
    $userid  = group_param_userid($userid);

355
356
357
358
    $gm = new StdClass;
    $gm->member = $userid;
    $gm->group = $groupid;
    $gm->ctime =  db_format_timestamp(time());
Richard Mansfield's avatar
Richard Mansfield committed
359
360
361
    if (!$role) {
        $role = get_field_sql('SELECT gt.defaultrole FROM {grouptype} gt, {group} g WHERE g.id = ? AND g.grouptype = gt.name', array($groupid));
    }
362
    $gm->role = $role;
363
364

    db_begin();
365
    insert_record('group_member', $gm);
366
    delete_records('group_member_request', 'group', $groupid, 'member', $userid);
367
    handle_event('userjoinsgroup', $gm);
368
    db_commit();
369
370
}

371
372
373
374
375
376
377
378
379
/**
 * Checks whether a user is allowed to leave a group.
 *
 * This checks things like if they're the owner and the group membership type
 *
 * @param mixed $group  DB record or ID of group to check
 * @param int   $userid (optional, will default to logged in user)
 */
function group_user_can_leave($group, $userid=null) {
380
    global $USER;
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
    static $result;

    $userid = optional_userid($userid);

    if (is_numeric($group)) {
        if (!$group = get_record('group', 'id', $group, 'deleted', 0)) {
            return false;
        }
    }

    // Return cached value if we have it
    if (isset($result[$group->id][$userid])) {
        return $result[$group->id][$userid];
    }

396
    if ($group->jointype == 'controlled' && group_user_access($group->id, $USER->get('id')) != 'admin') {
397
398
399
400
401
402
403
404
405
406
407
        return ($result[$group->id][$userid] = false);
    }

    if (group_is_only_admin($group->id, $userid)) {
        return ($result[$group->id][$userid] = false);
    }

    return ($result[$group->id][$userid] = true);
}

/**
408
 * Removes a user from a group.
409
 *
410
411
412
413
 * Also removes view access given by the user to the group
 *
 * @param int $groupid ID of group
 * @param int $userid  ID of user to remove
414
 */
415
function group_remove_user($groupid, $userid=null, $force=false) {
416
    // group_user_can_leave checks the validity of groupid and userid
417
    if (!$force && !group_user_can_leave($groupid, $userid)) {
418
419
420
        throw new AccessDeniedException(get_string('usercantleavegroup', 'group'));
    }
    db_begin();
421
    delete_records('group_member', 'group', $groupid, 'member', $userid);
422
423
424
425
426
427
428
429
    delete_records_sql(
        'DELETE FROM {view_access_group}
        WHERE "group" = ?
        AND view IN (
            SELECT v.id
            FROM {view} v
            WHERE v.owner = ?
        )',
430
        array($groupid, $userid)
431
432
433
434
    );
    db_commit();

    require_once(get_config('docroot') . 'interaction/lib.php');
435
    $interactions = get_column('interaction_instance', 'id', 'group', $groupid);
436
437
438
439
440
    foreach ($interactions as $interaction) {
        interaction_instance_from_id($interaction)->interaction_remove_user($userid);
    }
}

441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
/**
 * Invite a user to a group.
 *
 * @param object $group group
 * @param object $userid  User to invite
 * @param object $userfrom  User sending the invitation
 */
function group_invite_user($group, $userid, $userfrom, $role='member') {
    $user = optional_userobj($userid);

    $data = new StdClass;
    $data->group = $group->id;
    $data->member= $user->id;
    $data->ctime = db_format_timestamp(time());
    $data->role = $role;
    ensure_record_exists('group_member_invite', $data, $data);
    $lang = get_user_language($user->id);
    require_once('activity.php');
    activity_occurred('maharamessage',
        array('users'   => array($user->id),
              'subject' => get_string_from_language($lang, 'invitetogroupsubject', 'group'),
              'message' => get_string_from_language($lang, 'invitetogroupmessage', 'group', display_name($userfrom, $user), $group->name),
              'url'     => get_config('wwwroot')
              . 'group/view.php?id=' . $group->id));
}

467
468
// Pieforms for various operations on groups

469
/**
470
 * Form for users to join a given group
471
 */
472
function group_get_join_form($name, $groupid, $returnto='view') {
473
474
475
    return pieform(array(
        'name' => $name,
        'successcallback' => 'joingroup_submit',
476
        'autofocus' => false,
477
478
479
480
481
482
483
484
        'elements' => array(
            'join' => array(
                'type' => 'submit',
                'value' => get_string('joingroup', 'group')
            ),
            'group' => array(
                'type' => 'hidden',
                'value' => $groupid
485
486
487
488
489
            ),
            'returnto' => array(
                'type' => 'hidden',
                'value' => $returnto
            ),
490
491
492
493
        )
    ));
}

494
495
496
/**
 * Form for accepting/declining a group invite
 */
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
function group_get_accept_form($name, $groupid, $returnto) {
    return pieform(array(
       'name'     => $name,
       'renderer' => 'oneline',
       'successcallback' => 'group_invite_submit',
       'elements' => array(
            'accept' => array(
                'type'  => 'submit',
                'value' => get_string('acceptinvitegroup', 'group')
            ),
            'decline' => array(
                'type'  => 'submit',
                'value' => get_string('declineinvitegroup', 'group')
            ),
            'group' => array(
                'type' => 'hidden',
                'value' => $groupid
            ),
            'returnto' => array(
                'type' => 'hidden',
                'value' => $returnto
            )
        )
    ));
}

523
524
525
/**
 * Form for adding a user to a group
 */
526
function group_get_adduser_form($userid, $groupid) {
527
    return pieform(array(
528
        'name'                => 'adduser' . $userid,
529
        'successcallback'     => 'group_adduser_submit',
530
531
532
533
534
535
536
537
538
539
540
541
        'renderer'            => 'div',
        'elements'            => array(
            'group' => array(
                'type'    => 'hidden',
                'value' => $groupid,
            ),
            'member' => array(
                'type'  => 'hidden',
                'value' => $userid,
            ),
            'submit' => array(
                'type'  => 'submit',
542
                'value' => get_string('add') . ' ' . display_name($userid),
543
544
545
546
547
            ),
        ),
    ));
}

548
549
550
/**
 * Form for removing a user from a group
 */
551
function group_get_removeuser_form($userid, $groupid) {
552
    require_once('pieforms/pieform.php');
553
554
555
556
557
558
559
560
561
562
563
564
565
566
    return pieform(array(
        'name'                => 'removeuser' . $userid,
        'validatecallback'    => 'group_removeuser_validate',
        'successcallback'     => 'group_removeuser_submit',
        'renderer'            => 'oneline',
        'elements'            => array(
            'group' => array(
                'type'    => 'hidden',
                'value' => $groupid,
            ),
            'member' => array(
                'type'  => 'hidden',
                'value' => $userid,
            ),
567
            'removeuser' => array(
568
569
570
571
572
573
574
                'type'  => 'submit',
                'value' => get_string('removefromgroup', 'group'),
            ),
        ),
    ));
}

575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
/**
 * Form for denying request (request jointype group)
 */
function group_get_denyuser_form($userid, $groupid) {
    require_once('pieforms/pieform.php');
    return pieform(array(
        'name'                => 'denyuser' . $userid,
        'successcallback'     => 'group_denyuser_submit',
        'renderer'            => 'oneline',
        'elements'            => array(
            'group' => array(
                'type'    => 'hidden',
                'value' => $groupid,
            ),
            'member' => array(
                'type'  => 'hidden',
                'value' => $userid,
            ),
            'denyuser' => array(
                'type'  => 'submit',
                'value' => get_string('declinerequest', 'group'),
            ),
        ),
    ));
}

601
// Functions for handling submission of group related forms
602
603
604

function joingroup_submit(Pieform $form, $values) {
    global $SESSION, $USER;
605
    group_add_user($values['group'], $USER->get('id'));
Clare Lenihan's avatar
Clare Lenihan committed
606
    $SESSION->add_ok_msg(get_string('joinedgroup', 'group'));
607
608
609
610
611
612
613
    if (substr($values['returnto'], 0, 1) == '/') {
        $next = $values['returnto'];
    }
    else {
        $next = '/group/view.php?id=' . $values['group'];
    }
    redirect($next);
614
615
616
617
}

function group_invite_submit(Pieform $form, $values) {
    global $SESSION, $USER;
618
619
    $inviterecord = get_record('group_member_invite', 'member', $USER->get('id'), 'group', $values['group']);
    if ($inviterecord) {
620
621
        delete_records('group_member_invite', 'group', $values['group'], 'member', $USER->get('id'));
        if (isset($values['accept'])) {
622
            group_add_user($values['group'], $USER->get('id'), $inviterecord->role);
Clare Lenihan's avatar
Clare Lenihan committed
623
            $SESSION->add_ok_msg(get_string('groupinviteaccepted', 'group'));
624
625
626
627
628
629
630
            if (substr($values['returnto'], 0, 1) == '/') {
                $next = $values['returnto'];
            }
            else {
                $next = '/group/view.php?id=' . $values['group'];
            }
            redirect($next);
631
632
        }
        else {
Clare Lenihan's avatar
Clare Lenihan committed
633
            $SESSION->add_ok_msg(get_string('groupinvitedeclined', 'group'));
634
635
636
637
638
            redirect($values['returnto'] == 'find' ? '/group/find.php' : '/group/mygroups.php');
        }
    }
}

639
function group_adduser_submit(Pieform $form, $values) {
640
641
642
643
644
645
    global $SESSION;
    $group = (int)$values['group'];
    if (group_user_access($group) != 'admin') {
        $SESSION->add_error_msg(get_string('accessdenied', 'error'));
        redirect('/group/members.php?id=' . $group . '&membershiptype=request');
    }
646
    group_add_user($group, $values['member']);
647
648
649
650
651
652
653
    $SESSION->add_ok_msg(get_string('useradded', 'group'));
    if (count_records('group_member_request', 'group', $group)) {
        redirect('/group/members.php?id=' . $group . '&membershiptype=request');
    }
    redirect('/group/members.php?id=' . $group);
}

654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
/**
 * Denying request (request jointype group)
 */
function group_denyuser_submit(Pieform $form, $values) {
    global $SESSION;
    $group = (int)$values['group'];
    if (group_user_access($group) != 'admin') {
        $SESSION->add_error_msg(get_string('accessdenied', 'error'));
        redirect('/group/members.php?id=' . $group . '&membershiptype=request');
    }
    delete_records('group_member_request', 'group', $values['group'], 'member', $values['member']);
    $SESSION->add_ok_msg(get_string('declinerequestsuccess', 'group'));
    if (count_records('group_member_request', 'group', $group)) {
        redirect('/group/members.php?id=' . $group . '&membershiptype=request');
    }
    redirect('/group/members.php?id=' . $group);
}

672
673
674
675
676
677
678
function group_removeuser_validate(Pieform $form, $values) {
    global $user, $group, $SESSION;
    if (!group_user_can_leave($values['group'], $values['member'])) {
        $form->set_error('submit', get_string('usercantleavegroup', 'group'));
    }
}

679
680
681
682
683
684
685
686
687
688
689
690
function group_removeuser_submit(Pieform $form, $values) {
    global $SESSION;
    $group = (int)$values['group'];
    if (group_user_access($group) != 'admin') {
        $SESSION->add_error_msg(get_string('accessdenied', 'error'));
        redirect('/group/members.php?id=' . $group);
    }
    group_remove_user($group, $values['member']);
    $SESSION->add_ok_msg(get_string('userremoved', 'group'));
    redirect('/group/members.php?id=' . $group);
}

691
692
693
694
695
696
/**
 * Form for submitting views to a group
 */
function group_view_submission_form($groupid, $viewdata) {
    $options = array();
    foreach ($viewdata as $view) {
697
698
699
700
701
702
        if (empty($view->submittedgroup) && empty($view->submittedhost)) {
            $options[$view->id] = $view->title;
        }
    }
    if (empty($options)) {
        return;
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
    }
    return pieform(array(
        'name' => 'group_view_submission_form_' . $groupid,
        'method' => 'post',
        'renderer' => 'oneline',
        'autofocus' => false,
        'successcallback' => 'group_view_submission_form_submit',
        'elements' => array(
            'text1' => array(
                'type' => 'html', 'value' => get_string('submit', 'group') . ' ',
            ),
            'options' => array(
                'type' => 'select',
                'collapseifoneoption' => false,
                'options' => $options,
            ),
            'text2' => array(
                'type' => 'html',
                'value' => get_string('forassessment', 'view'),
            ),
            'submit' => array(
                'type' => 'submit',
                'value' => get_string('submit')
            ),
            'group' => array(
                'type' => 'hidden',
                'value' => $groupid
730
731
732
733
734
            ),
            'returnto' => array(
                'type' => 'hidden',
                'value' => get_config('wwwroot') . 'group/view.php?id=' . $groupid,
            ),
735
736
737
738
739
        ),
    ));
}

function group_view_submission_form_submit(Pieform $form, $values) {
740
    redirect('/view/submit.php?id=' . $values['options'] . '&group=' . $values['group'] . '&returnto=group');
741
742
}

743
// Miscellaneous group related functions
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763

/**
 * Returns a list of user IDs who are admins for a group
 *
 * @param int ID of group
 * @return array
 */
function group_get_admin_ids($groupid) {
    return (array)get_column_sql("SELECT member
        FROM {group_member}
        WHERE \"group\" = ?
        AND role = 'admin'", $groupid);
}

/**
 * Gets information about what the roles in a given group are able to do
 *
 * @param int $groupid ID of group to get role information for
 * @return array
 */
764
765
766
767
768
769
function group_get_role_info($groupid) {
    $roles = get_records_sql_assoc('SELECT role, edit_views, see_submitted_views, gr.grouptype FROM {grouptype_roles} gr
        INNER JOIN {group} g ON g.grouptype = gr.grouptype
        WHERE g.id = ?', array($groupid));
    foreach ($roles as $role) {
        $role->display = get_string($role->role, 'grouptype.'.$role->grouptype);
770
        $role->name = $role->role;
771
772
773
774
    }
    return $roles;
}

775
776
777
778
779
780
function group_get_default_artefact_permissions($groupid) {
    $type = get_field('group', 'grouptype', 'id', $groupid);
    safe_require('grouptype', $type);
    return call_static_method('GroupType' . $type, 'default_artefact_rolepermissions');
}

781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
/**
 * Sets up groups for display in mygroups.php and find.php
 *
 * @param array $groups    Initial group data, including the current user's 
 *                         membership type in each group. See mygroups.php for
 *                         the query to build this information.
 * @param string $returnto Where forms generated for display should be told to return to
 */
function group_prepare_usergroups_for_display($groups, $returnto='mygroups') {
    if (!$groups) {
        return;
    }

    // Retrieve a list of all the group admins, for placing in each $group object
    $groupadmins = array();
    $groupids = array_map(create_function('$a', 'return $a->id;'), $groups);
    if ($groupids) {
798
        $groupadmins = get_records_sql_array('SELECT "group", member
799
800
801
            FROM {group_member}
            WHERE "group" IN (' . implode(',', db_array_to_ph($groupids)) . ")
            AND role = 'admin'", $groupids);
802
803
804
        if (!$groupadmins) {
            $groupadmins = array();
        }
805
806
807
808
809
810
811
812
813
814
    }

    $i = 0;
    foreach ($groups as $group) {
        $group->admins = array();
        foreach ($groupadmins as $admin) {
            if ($admin->group == $group->id) {
                $group->admins[] = $admin->member;
            }
        }
815
        $group->description = str_shorten_html($group->description, 100, true);
816
817
818
819
820
821
822
823
824
        if ($group->membershiptype == 'member') {
            $group->canleave = group_user_can_leave($group->id);
        }
        else if ($group->jointype == 'open') {
            $group->groupjoin = group_get_join_form('joingroup' . $i++, $group->id);
        }
        else if ($group->membershiptype == 'invite') {
            $group->invite = group_get_accept_form('invite' . $i++, $group->id, $returnto);
        }
825
        $group->grouptypedescription = get_string('grouptypedescription', 'group', get_string('name', 'grouptype.' . $group->grouptype), get_string('membershiptype.'.$group->jointype, 'group'));
826
827
828
829
    }
}


830
function group_get_membersearch_data($group, $query, $offset, $limit, $membershiptype) {
831
    global $USER;
832
    $results = get_group_user_search_results($group, $query, $offset, $limit, $membershiptype);
833
834
835
836
837
838

    $params = array();
    if (!empty($query)) {
        $params[] = 'query=' . $query;
    }
    $params[] = 'limit=' . $limit;
839
840
841
842
843
844
845
    if (!empty($membershiptype)) {
        $params[] = 'membershiptype=' . $membershiptype;
    }
    $searchurl = get_config('wwwroot') . 'group/members.php?id=' . $group . '&amp;' . join('&amp;', $params);

    $smarty = smarty_core();

846
847
    $role = group_user_access($group);
    $userid = $USER->get('id');
848
    foreach ($results['data'] as &$r) {
849
        if ($role == 'admin' && ($r['id'] != $userid || group_user_can_leave($group, $r['id']))) {
850
851
            $r['removeform'] = group_get_removeuser_form($r['id'], $group);
        }
852
853
854
855
856
857
858
859
        // NOTE: this is a quick approximation. We should really check whether, 
        // for each role in the group, that the user can change to it (using 
        // group_can_change_role).  This only controls whether the 'change 
        // role' link appears though, so it doesn't matter too much. If the 
        // user clicks on this link, changerole.php does the full check and 
        // sends them back here saying that the user has no roles they can 
        // change to anyway.
        $r['canchangerole'] = !group_is_only_admin($group, $r['id']);
860
861
    }

862
863
864
    if (!empty($membershiptype)) {
        if ($membershiptype == 'request') {
            foreach ($results['data'] as &$r) {
865
                $r['addform'] = group_get_adduser_form($r['id'], $group);
866
                $r['denyform'] = group_get_denyuser_form($r['id'], $group);
867
868
869
                // TODO: this will suck when there's quite a few on the page, 
                // would be better to grab all the reasons in one go
                $r['reason']  = get_field('group_member_request', 'reason', 'group', $group, 'member', $r['id']);
870
871
872
873
874
            }
        }
        $smarty->assign('membershiptype', $membershiptype);
    }

875
    $results['cdata'] = array_chunk($results['data'], 2);
876
    $results['roles'] = group_get_role_info($group);
877
878
879
    $smarty->assign_by_ref('results', $results);
    $smarty->assign('searchurl', $searchurl);
    $smarty->assign('pagebaseurl', $searchurl);
880
881
    $smarty->assign('caneditroles', group_user_access($group) == 'admin');
    $smarty->assign('group', $group);
882
883
884
885
886
    $html = $smarty->fetch('group/membersearchresults.tpl');

    $pagination = build_pagination(array(
        'id' => 'member_pagination',
        'class' => 'center',
887
        'url' => $searchurl,
888
889
890
891
        'count' => $results['count'],
        'limit' => $limit,
        'offset' => $offset,
        'datatable' => 'membersearchresults',
892
        'jsonscript' => 'group/membersearchresults.json.php',
893
894
895
896
897
        'firsttext' => '',
        'previoustext' => '',
        'nexttext' => '',
        'lasttext' => '',
        'numbersincludefirstlast' => false,
898
899
        'resultcounttextsingular' => get_string('member', 'group'),
        'resultcounttextplural' => get_string('members', 'group'),
900
901
    ));

902
    return array($html, $pagination, $results['count'], $offset, $membershiptype);
903
904
}

905
906
907

/**
 * Returns a list of available grouptypes
908
909
 *
 * @return array
910
911
912
913
914
 */
function group_get_grouptypes() {
    static $grouptypes = null;

    if (is_null($grouptypes)) {
915
        $grouptypes = get_column('grouptype', 'name');
916
917
918
919
920
    }

    return $grouptypes;
}

921

922
923
924
925
926
927
928
/**
 * Returns a list of grouptype & jointype options to be used in create
 * group/edit group drop-downs.
 * 
 * If there is more than one group type with the same join type,
 * prefix the join types with the group type for display.
 */
929
function group_get_grouptype_options($currentgrouptype=null) {
930
931
    $groupoptions = array();
    $jointypecount = array('open' => 0, 'invite' => 0, 'request' => 0, 'controlled' => 0);
932
    $grouptypes = group_get_grouptypes();
933
    $enabled = array_map(create_function('$a', 'return $a->name;'), plugins_installed('grouptype'));
934
935
936
937
    if (is_null($currentgrouptype) || in_array($currentgrouptype, $enabled)) {
        $grouptypes = array_intersect($enabled, $grouptypes);
    }
    foreach ($grouptypes as $grouptype) {
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
        safe_require('grouptype', $grouptype);
        if (call_static_method('GroupType' . $grouptype, 'can_be_created_by_user')) {
            $grouptypename = get_string('name', 'grouptype.' . $grouptype);
            foreach (call_static_method('GroupType' . $grouptype, 'allowed_join_types') as $jointype) {
                $jointypecount[$jointype]++;
                $groupoptions['jointype']["$grouptype.$jointype"] = get_string('membershiptype.'.$jointype, 'group');
                $groupoptions['grouptype']["$grouptype.$jointype"] = $grouptypename . ': ' . get_string('membershiptype.'.$jointype, 'group');
            }
        }
    }
    $duplicates = array_reduce($jointypecount, create_function('$a, $b', 'return $a || $b > 1;'));
    if ($duplicates) {
        return $groupoptions['grouptype'];
    }
    return $groupoptions['jointype'];
}

955
956
957
958
959
960
/**
 * Returns a datastructure describing the tabs that appear on a group page
 *
 * @param object $group Database record of group to get tabs for
 * @return array
 */
961
function group_get_menu_tabs() {
Richard Mansfield's avatar
Richard Mansfield committed
962
963
    static $menu;

964
    $group = group_current_group();
965
966
967
    if (!$group) {
        return null;
    }
968
    $menu = array(
969
        'info' => array(
970
            'path' => 'groups/info',
971
972
            'url' => 'group/view.php?id='.$group->id,
            'title' => get_string('About', 'group'),
973
            'weight' => 20
974
        ),
975
        'members' => array(
976
            'path' => 'groups/members',
977
978
            'url' => 'group/members.php?id='.$group->id,
            'title' => get_string('Members', 'group'),
979
            'weight' => 30
980
        ),
981
982
983
    );
    if ($group->public || group_user_access($group->id)) {
        $menu['forums'] = array(  // @todo: get this from a function in the interaction plugin (or better, make forums an artefact plugin)
984
985
986
            'path' => 'groups/forums',
            'url' => 'interaction/forum/index.php?group='.$group->id,
            'title' => get_string('nameplural', 'interaction.forum'),
987
988
989
990
991
992
993
994
            'weight' => 40,
        );
    }
    $menu['views'] = array(
        'path' => 'groups/views',
        'url' => 'view/groupviews.php?group='.$group->id,
        'title' => get_string('Views', 'group'),
        'weight' => 50,
995
    );
996

997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
    if (group_user_access($group->id)) {
        safe_require('grouptype', $group->grouptype);
        $artefactplugins = call_static_method('GroupType' . $group->grouptype, 'get_group_artefact_plugins');
        if ($plugins = get_records_array('artefact_installed', 'active', 1)) {
            foreach ($plugins as &$plugin) {
                if (!in_array($plugin->name, $artefactplugins)) {
                    continue;
                }
                safe_require('artefact', $plugin->name);
                $plugin_menu = call_static_method(generate_class_name('artefact',$plugin->name), 'group_tabs', $group->id);
                $menu = array_merge($menu, $plugin_menu);
            }
        }
    }

1012
1013
1014
1015
1016
1017
1018
    if (defined('MENUITEM')) {
        $key = substr(MENUITEM, strlen('groups/'));
        if ($key && isset($menu[$key])) {
            $menu[$key]['selected'] = true;
        }
    }

1019
1020
1021
    return $menu;
}

1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
/**
 * Used by this file to perform validation of group ID function arguments
 *
 * @param int $groupid
 * @return int
 * @throws InvalidArgumentException
 */
function group_param_groupid($groupid) {
    $groupid = (int)$groupid;

    if ($groupid == 0) {
        throw new InvalidArgumentException("group_user_access: group argument should be an integer");
    }

    return $groupid;
}

/**
 * Used by this file to perform validation of user ID function arguments
 *
 * @param int $userid
 * @return int
 * @throws InvalidArgumentException
 */
function group_param_userid($userid) {
    if (is_null($userid)) {
        global $USER;
        $userid = (int)$USER->get('id');
    }
    else {
        $userid = (int)$userid;
    }

    if ($userid == 0) {
        throw new InvalidArgumentException("group_user_access: user argument should be an integer");
    }

    return $userid;
}

1062
1063
1064
1065

function group_current_group() {
    static $group;

1066
1067
1068
1069
1070
1071
1072
    // This function sometimes gets called by the smarty function
    // during the execution of a GroupNotFound exception.  This
    // variable prevents a 2nd exception from being thrown.  Perhaps
    // better achieved with a global in the exception handler?
    static $dying;

    if (defined('GROUP') && !$dying) {
1073
        $group = get_record_select('group', 'id = ? AND deleted = 0', array(GROUP), '*, ' . db_format_tsfield('ctime'));
1074
        if (!$group) {
1075
            $dying = 1;
1076
            throw new GroupNotFoundException(get_string('groupnotfound', 'group', GROUP));
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
        }
    }
    else {
        $group = null;
    }

    return $group;
}


Richard Mansfield's avatar
Richard Mansfield committed
1087
1088
1089
1090
1091
1092
/**
 * creates the group sideblock
 */
function group_sideblock() {
    require_once('group.php');
    $data['group'] = group_current_group();
1093
1094
1095
    if (!$data['group']) {
        return null;
    }
Richard Mansfield's avatar
Richard Mansfield committed
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
    $data['menu'] = group_get_menu_tabs();
    // @todo either: remove this if interactions become group
    // artefacts, or: do this in interaction/lib.php if we leave them
    // as interactions
    $data['forums'] = get_records_select_array(
        'interaction_instance',
        '"group" = ? AND deleted = ? AND plugin = ?',
        array(GROUP, 0, 'forum'),
        'ctime',
        'id, plugin, title'
    );
    if (!$data['forums']) {
        $data['forums'] = array();
    }
    else {
        safe_require('interaction', 'forum');
        $data['forums'] = PluginInteractionForum::sideblock_sort($data['forums']);
    }
    return $data;
}


Richard Mansfield's avatar
Richard Mansfield committed
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
function group_get_associated_groups($userid, $filter='all', $limit=20, $offset=0) {

    // Strangely, casting is only needed for invite, request and admin and only in 
    // postgres
    if (is_mysql()) {
        $invitesql  = "'invite'";
        $requestsql = "'request'";
        $adminsql   = "'admin'";
        $empty      = "''";
    }
    else {
        $invitesql  = "CAST('invite' AS TEXT)";
        $requestsql = "CAST('request' AS TEXT)";
        $adminsql   = "CAST('admin' AS TEXT)";
        $empty      = "CAST('' AS TEXT)";
    }

    // Different filters join on the different kinds of association
    if ($filter == 'admin') {
        $sql = "
            INNER JOIN (
1139
                SELECT g.id, $adminsql AS membershiptype, $empty AS reason, $adminsql AS role
Richard Mansfield's avatar
Richard Mansfield committed
1140
1141
1142
1143
1144
1145
1146
1147
                FROM {group} g
                INNER JOIN {group_member} gm ON (gm.group = g.id AND gm.member = ? AND gm.role = 'admin')
            ) t ON t.id = g.id";
        $values = array($userid);
    }
    else if ($filter == 'member') {
        $sql = "
            INNER JOIN (
1148
                SELECT g.id, 'admin' AS membershiptype, $empty AS reason, $adminsql AS role
Richard Mansfield's avatar
Richard Mansfield committed
1149
1150
1151
                FROM {group} g
                INNER JOIN {group_member} gm ON (gm.group = g.id AND gm.member = ? AND gm.role = 'admin')
                UNION
1152
                SELECT g.id, 'member' AS type, $empty AS reason, gm.role AS role
Richard Mansfield's avatar
Richard Mansfield committed
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
                FROM {group} g
                INNER JOIN {group_member} gm ON (gm.group = g.id AND gm.member = ? AND gm.role != 'admin')
            ) t ON t.id = g.id";
        $values = array($userid, $userid);
    }
    else if ($filter == 'invite') {
        $sql = "
            INNER JOIN (
                SELECT g.id, $invitesql AS membershiptype, gmi.reason, gmi.role
                FROM {group} g
                INNER JOIN {group_member_invite} gmi ON (gmi.group = g.id AND gmi.member = ?)
            ) t ON t.id = g.id";
        $values = array($userid);
    }
    else if ($filter == 'request') {
        $sql = "
            INNER JOIN (
                SELECT g.id, $requestsql AS membershiptype, gmr.reason, $empty AS role
                FROM {group} g
                INNER JOIN {group_member_request} gmr ON (gmr.group = g.id AND gmr.member = ?)
            ) t ON t.id = g.id";
        $values = array($userid);
    }
    else { // all or some other text
        $filter = 'all';
        $sql = "
            INNER JOIN (
1180
                SELECT g.id, 'admin' AS membershiptype, '' AS reason, 'admin' AS role
Richard Mansfield's avatar
Richard Mansfield committed
1181
1182
1183
                FROM {group} g
                INNER JOIN {group_member} gm ON (gm.group = g.id AND gm.member = ? AND gm.role = 'admin')
                UNION
1184
                SELECT g.id, 'member' AS membershiptype, '' AS reason, gm.role AS role
Richard Mansfield's avatar
Richard Mansfield committed
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
                FROM {group} g
                INNER JOIN {group_member} gm ON (g.id = gm.group AND gm.member = ? AND gm.role != 'admin')
                UNION
                SELECT g.id, 'invite' AS membershiptype, gmi.reason, gmi.role
                FROM {group} g
                INNER JOIN {group_member_invite} gmi ON (gmi.group = g.id AND gmi.member = ?)
                UNION SELECT g.id, 'request' AS membershiptype, gmr.reason, '' AS role
                FROM {group} g
                INNER JOIN {group_member_request} gmr ON (gmr.group = g.id AND gmr.member = ?)
            ) t ON t.id = g.id";
        $values = array($userid, $userid, $userid, $userid);
    }
    
    $values[] = 0;
    
    $count = count_records_sql('SELECT COUNT(*) FROM {group} g ' . $sql . ' WHERE g.deleted = ?', $values);
    
    // almost the same as query used in find - common parts should probably be pulled out
    // gets the groups filtered by above
    // and the first three members by id
    
1206
    $sql = 'SELECT g1.id, g1.name, g1.description, g1.public, g1.jointype, g1.grouptype, g1.membershiptype, g1.reason, g1.role, g1.membercount, COUNT(gmr.member) AS requests
1207
        FROM (
1208
        SELECT g.id, g.name, g.description, g.public, g.jointype, g.grouptype, t.membershiptype, t.reason, t.role, COUNT(gm.member) AS membercount
1209
1210
1211
1212
            FROM {group} g
            LEFT JOIN {group_member} gm ON (gm.group = g.id)' .
            $sql . '
            WHERE g.deleted = ?
1213
            GROUP BY g.id, g.name, g.description, g.public, g.jointype, g.grouptype, t.membershiptype, t.reason, t.role
1214
1215
1216
            ORDER BY g.name
        ) g1
        LEFT JOIN {group_member_request} gmr ON (gmr.group = g1.id)
1217
        GROUP BY g1.id, g1.name, g1.description, g1.public, g1.jointype, g1.grouptype, g1.membershiptype, g1.reason, g1.role, g1.membercount';
Richard Mansfield's avatar
Richard Mansfield committed
1218
1219
1220
1221
    
    $groups = get_records_sql_assoc($sql, $values, $offset, $limit);
    
    if ($groups) {
1222
1223
1224
1225
1226
1227
1228
        // Get a few random members from each group. We've tried this with one 
        // query before but it's painfully slow, databases don't do random rows 
        // efficiently.
        foreach (array_keys($groups) as $groupid) {
            $members = get_records_sql_array("
                SELECT u.*
                FROM {group_member} gm
1229
                INNER JOIN {usr} u ON (gm.member = u.id AND u.deleted = 0)
1230
1231
1232
1233
1234
1235
                WHERE gm.group = ?
                ORDER BY " . db_random() . "
                LIMIT 3", array($groupid));
            foreach ($members as $m) {
                $groups[$groupid]->members[] = (object) array('id' => $m->id, 'name' => display_name($m));
            }
Richard Mansfield's avatar
Richard Mansfield committed
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
        }
        $groups = array_values($groups);
    }
    else {
        $groups = array();
    }

    return array('groups' => $groups, 'count' => $count);

}

1247

1248
function group_get_user_groups($userid=null, $roles=null) {
1249
1250
1251
1252
1253
    if (is_null($userid)) {
        global $USER;
        $userid = $USER->get('id');
    }
    if ($groups = get_records_sql_array(
1254
        "SELECT g.id, g.name, gm.role, g.jointype, g.grouptype
1255
1256
1257
        FROM {group} g
        JOIN {group_member} gm ON (gm.group = g.id)
        WHERE gm.member = ?
1258
        AND g.deleted = 0 " . (is_array($roles) ? (' AND gm.role IN (' . join(',', array_map('db_quote', $roles)) . ')') : '') . "
1259
1260
1261
1262
1263
1264
        ORDER BY gm.role = 'admin' DESC, gm.role, g.id", array($userid))) {
        return $groups;
    }
    return array();
}

1265
1266
1267
1268
1269
1270
1271
1272
1273
1274

function group_get_member_ids($group, $roles=null) {
    $rolesql = is_null($roles) ? '' : (' AND gm.role IN (' . join(',', array_map('db_quote', $roles)) . ')');
    return get_column_sql('
        SELECT gm.member
        FROM {group_member} gm INNER JOIN {group} g ON gm.group = g.id
        WHERE g.deleted = 0 AND g.id = ?' . $rolesql,
        array($group)
    );
}
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286

function group_can_create_groups() {
    global $USER;
    $creators = get_config('creategroups');
    if ($creators == 'all') {
        return true;
    }
    if ($USER->get('admin') || $USER->is_institutional_admin()) {
        return true;
    }
    return $creators == 'staff' && ($USER->get('staff') || $USER->is_institutional_staff());
}
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305

function group_get_user_course_groups($userid=null) {
    if (is_null($userid)) {
        global $USER;
        $userid = $USER->get('id');
    }
    if ($groups = get_records_sql_array(
        "SELECT g.id, g.name
        FROM {group_member} u
        INNER JOIN {group} g ON (u.group = g.id AND g.deleted = 0)
        INNER JOIN {grouptype} t ON t.name = g.grouptype
        WHERE u.member = ?
        AND t.submittableto = 1
        ORDER BY g.name
        ", array($userid))) {
        return $groups;
    }
    return array();
}
1306
1307
1308
1309

function group_allows_submission($grouptype) {
    return get_field('grouptype', 'submittableto', 'name', $grouptype);
}
1310
?>