Dump of bugs.webkit.org's Bugzilla instance.
[WebKit-https.git] / BugsSite / editgroups.cgi
1 #!/usr/bin/perl -wT
2 # -*- Mode: perl; indent-tabs-mode: nil -*-
3 #
4 # The contents of this file are subject to the Mozilla Public
5 # License Version 1.1 (the "License"); you may not use this file
6 # except in compliance with the License. You may obtain a copy of
7 # the License at http://www.mozilla.org/MPL/
8 #
9 # Software distributed under the License is distributed on an "AS
10 # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
11 # implied. See the License for the specific language governing
12 # rights and limitations under the License.
13 #
14 # The Original Code is the Bugzilla Bug Tracking System.
15 #
16 # The Initial Developer of the Original Code is Netscape Communications
17 # Corporation. Portions created by Netscape are
18 # Copyright (C) 1998 Netscape Communications Corporation. All
19 # Rights Reserved.
20 #
21 # Contributor(s): Dave Miller <justdave@syndicomm.com>
22 #                 Joel Peshkin <bugreport@peshkin.net>
23 #                 Jacob Steenhagen <jake@bugzilla.org>
24 #                 Vlad Dascalu <jocuri@softhome.net>
25 #                 Frédéric Buclin <LpSolit@gmail.com>
26
27 # Code derived from editowners.cgi and editusers.cgi
28
29 use strict;
30 use lib ".";
31
32 use Bugzilla;
33 use Bugzilla::Constants;
34 use Bugzilla::User;
35 require "CGI.pl";
36
37 my $cgi = Bugzilla->cgi;
38 my $dbh = Bugzilla->dbh;
39
40 use vars qw($template $vars);
41
42 Bugzilla->login(LOGIN_REQUIRED);
43
44 print Bugzilla->cgi->header();
45
46 UserInGroup("creategroups")
47   || ThrowUserError("auth_failure", {group  => "creategroups",
48                                      action => "edit",
49                                      object => "groups"});
50
51 my $action = trim($cgi->param('action') || '');
52
53 # RederiveRegexp: update user_group_map with regexp-based grants
54 sub RederiveRegexp ($$)
55 {
56     my $regexp = shift;
57     my $gid = shift;
58     my $dbh = Bugzilla->dbh;
59     my $sth = $dbh->prepare("SELECT userid, login_name FROM profiles");
60     my $sthqry = $dbh->prepare("SELECT 1 FROM user_group_map
61                                  WHERE user_id = ? AND group_id = ?
62                                  AND grant_type = ? and isbless = 0");
63     my $sthadd = $dbh->prepare("INSERT INTO user_group_map
64                                  (user_id, group_id, grant_type, isbless)
65                                  VALUES (?, ?, ?, 0)");
66     my $sthdel = $dbh->prepare("DELETE FROM user_group_map
67                                  WHERE user_id = ? AND group_id = ?
68                                  AND grant_type = ? and isbless = 0");
69     $sth->execute();
70     while (my ($uid, $login) = $sth->fetchrow_array()) {
71         my $present = $dbh->selectrow_array($sthqry, undef,
72                                             $uid, $gid, GRANT_REGEXP);
73         if (($regexp =~ /\S+/) && ($login =~ m/$regexp/i))
74         {
75             $sthadd->execute($uid, $gid, GRANT_REGEXP) unless $present;
76         } else {
77             $sthdel->execute($uid, $gid, GRANT_REGEXP) if $present;
78         }
79     }
80 }
81
82 # Add missing entries in bug_group_map for bugs created while
83 # a mandatory group was disabled and which is now enabled again.
84 sub fix_bug_permissions {
85     my $gid = shift;
86     my $dbh = Bugzilla->dbh;
87
88     detaint_natural($gid);
89     return unless $gid;
90
91     my $bug_ids =
92       $dbh->selectcol_arrayref('SELECT bugs.bug_id
93                                   FROM bugs
94                             INNER JOIN group_control_map
95                                     ON group_control_map.product_id = bugs.product_id
96                              LEFT JOIN bug_group_map
97                                     ON bug_group_map.bug_id = bugs.bug_id
98                                    AND bug_group_map.group_id = group_control_map.group_id
99                                  WHERE group_control_map.group_id = ?
100                                    AND group_control_map.membercontrol = ?
101                                    AND bug_group_map.group_id IS NULL',
102                                  undef, ($gid, CONTROLMAPMANDATORY));
103
104     my $sth = $dbh->prepare('INSERT INTO bug_group_map (bug_id, group_id) VALUES (?, ?)');
105     foreach my $bug_id (@$bug_ids) {
106         $sth->execute($bug_id, $gid);
107     }
108 }
109
110 # CheckGroupID checks that a positive integer is given and is
111 # actually a valid group ID. If all tests are successful, the
112 # trimmed group ID is returned.
113
114 sub CheckGroupID {
115     my ($group_id) = @_;
116     $group_id = trim($group_id || 0);
117     ThrowUserError("group_not_specified") unless $group_id;
118     (detaint_natural($group_id)
119       && Bugzilla->dbh->selectrow_array("SELECT id FROM groups WHERE id = ?",
120                                         undef, $group_id))
121       || ThrowUserError("invalid_group_ID");
122     return $group_id;
123 }
124
125 # This subroutine is called when:
126 # - a new group is created. CheckGroupName checks that its name
127 #   is not empty and is not already used by any existing group.
128 # - an existing group is edited. CheckGroupName checks that its
129 #   name has not been deleted or renamed to another existing
130 #   group name (whose group ID is different from $group_id).
131 # In both cases, an error message is returned to the user if any
132 # test fails! Else, the trimmed group name is returned.
133
134 sub CheckGroupName {
135     my ($name, $group_id) = @_;
136     $name = trim($name || '');
137     trick_taint($name);
138     ThrowUserError("empty_group_name") unless $name;
139     my $excludeself = (defined $group_id) ? " AND id != $group_id" : "";
140     my $name_exists = Bugzilla->dbh->selectrow_array("SELECT name FROM groups " .
141                                                      "WHERE name = ? $excludeself",
142                                                      undef, $name);
143     if ($name_exists) {
144         ThrowUserError("group_exists", { name => $name });
145     }
146     return $name;
147 }
148
149 # CheckGroupDesc checks that a non empty description is given. The
150 # trimmed description is returned.
151
152 sub CheckGroupDesc {
153     my ($desc) = @_;
154     $desc = trim($desc || '');
155     trick_taint($desc);
156     ThrowUserError("empty_group_description") unless $desc;
157     return $desc;
158 }
159
160 # CheckGroupRegexp checks that the regular expression is valid
161 # (the regular expression being optional, the test is successful
162 # if none is given, as expected). The trimmed regular expression
163 # is returned.
164
165 sub CheckGroupRegexp {
166     my ($regexp) = @_;
167     $regexp = trim($regexp || '');
168     trick_taint($regexp);
169     ThrowUserError("invalid_regexp") unless (eval {qr/$regexp/});
170     return $regexp;
171 }
172
173 # If no action is specified, get a list of all groups available.
174
175 unless ($action) {
176     my @groups;
177
178     SendSQL("SELECT id,name,description,userregexp,isactive,isbuggroup " .
179             "FROM groups " .
180             "ORDER BY isbuggroup, name");
181
182     while (MoreSQLData()) {
183         my ($id, $name, $description, $regexp, $isactive, $isbuggroup)
184             = FetchSQLData();
185         my $group = {};
186         $group->{'id'}          = $id;
187         $group->{'name'}        = $name;
188         $group->{'description'} = $description;
189         $group->{'regexp'}      = $regexp;
190         $group->{'isactive'}    = $isactive;
191         $group->{'isbuggroup'}  = $isbuggroup;
192
193         push(@groups, $group);
194     }
195
196     $vars->{'groups'} = \@groups;
197     
198     print Bugzilla->cgi->header();
199     $template->process("admin/groups/list.html.tmpl", $vars)
200       || ThrowTemplateError($template->error());
201
202     exit;
203 }
204
205 #
206 # action='changeform' -> present form for altering an existing group
207 #
208 # (next action will be 'postchanges')
209 #
210
211 if ($action eq 'changeform') {
212     # Check that an existing group ID is given
213     my $group_id = CheckGroupID($cgi->param('group'));
214     my ($name, $description, $regexp, $isactive, $isbuggroup) =
215         $dbh->selectrow_array("SELECT name, description, userregexp, " .
216                               "isactive, isbuggroup " .
217                               "FROM groups WHERE id = ?", undef, $group_id);
218
219     # For each group, we use left joins to establish the existence of
220     # a record making that group a member of this group
221     # and the existence of a record permitting that group to bless
222     # this one
223
224     my @groups;
225     SendSQL("SELECT groups.id, groups.name, groups.description," .
226              " CASE WHEN group_group_map.member_id IS NOT NULL THEN 1 ELSE 0 END," .
227              " CASE WHEN B.member_id IS NOT NULL THEN 1 ELSE 0 END," .
228              " CASE WHEN C.member_id IS NOT NULL THEN 1 ELSE 0 END" .
229              " FROM groups" .
230              " LEFT JOIN group_group_map" .
231              " ON group_group_map.member_id = groups.id" .
232              " AND group_group_map.grantor_id = $group_id" .
233              " AND group_group_map.grant_type = " . GROUP_MEMBERSHIP .
234              " LEFT JOIN group_group_map as B" .
235              " ON B.member_id = groups.id" .
236              " AND B.grantor_id = $group_id" .
237              " AND B.grant_type = " . GROUP_BLESS .
238              " LEFT JOIN group_group_map as C" .
239              " ON C.member_id = groups.id" .
240              " AND C.grantor_id = $group_id" .
241              " AND C.grant_type = " . GROUP_VISIBLE .
242              " ORDER by name");
243
244     while (MoreSQLData()) {
245         my ($grpid, $grpnam, $grpdesc, $grpmember, $blessmember, $membercansee) 
246             = FetchSQLData();
247
248         my $group = {};
249         $group->{'grpid'}       = $grpid;
250         $group->{'grpnam'}      = $grpnam;
251         $group->{'grpdesc'}     = $grpdesc;
252         $group->{'grpmember'}   = $grpmember;
253         $group->{'blessmember'} = $blessmember;
254         $group->{'membercansee'}= $membercansee;
255         push(@groups, $group);
256     }
257
258     $vars->{'group_id'}    = $group_id;
259     $vars->{'name'}        = $name;
260     $vars->{'description'} = $description;
261     $vars->{'regexp'}      = $regexp;
262     $vars->{'isactive'}    = $isactive;
263     $vars->{'isbuggroup'}  = $isbuggroup;
264     $vars->{'groups'}      = \@groups;
265
266     print Bugzilla->cgi->header();
267     $template->process("admin/groups/edit.html.tmpl", $vars)
268       || ThrowTemplateError($template->error());
269
270     exit;
271 }
272
273 #
274 # action='add' -> present form for parameters for new group
275 #
276 # (next action will be 'new')
277 #
278
279 if ($action eq 'add') {
280     print Bugzilla->cgi->header();
281     $template->process("admin/groups/create.html.tmpl", $vars)
282       || ThrowTemplateError($template->error());
283     
284     exit;
285 }
286
287
288
289 #
290 # action='new' -> add group entered in the 'action=add' screen
291 #
292
293 if ($action eq 'new') {
294     # Check that a not already used group name is given, that
295     # a description is also given and check if the regular
296     # expression is valid (if any).
297     my $name = CheckGroupName($cgi->param('name'));
298     my $desc = CheckGroupDesc($cgi->param('desc'));
299     my $regexp = CheckGroupRegexp($cgi->param('regexp'));
300     my $isactive = $cgi->param('isactive') ? 1 : 0;
301
302     # Add the new group
303     SendSQL("INSERT INTO groups ( " .
304             "name, description, isbuggroup, userregexp, isactive, last_changed " .
305             " ) VALUES ( " .
306             SqlQuote($name) . ", " .
307             SqlQuote($desc) . ", " .
308             "1," .
309             SqlQuote($regexp) . ", " . 
310             $isactive . ", NOW())" );
311     my $gid = $dbh->bz_last_key('groups', 'id');
312     my $admin = GroupNameToId('admin');
313     # Since we created a new group, give the "admin" group all privileges
314     # initially.
315     SendSQL("INSERT INTO group_group_map (member_id, grantor_id, grant_type)
316              VALUES ($admin, $gid, " . GROUP_MEMBERSHIP . ")");
317     SendSQL("INSERT INTO group_group_map (member_id, grantor_id, grant_type)
318              VALUES ($admin, $gid, " . GROUP_BLESS . ")");
319     SendSQL("INSERT INTO group_group_map (member_id, grantor_id, grant_type)
320              VALUES ($admin, $gid, " . GROUP_VISIBLE . ")");
321     # Permit all existing products to use the new group if makeproductgroups.
322     if ($cgi->param('insertnew')) {
323         SendSQL("INSERT INTO group_control_map " .
324                 "(group_id, product_id, entry, membercontrol, " .
325                 "othercontrol, canedit) " .
326                 "SELECT $gid, products.id, 0, " .
327                 CONTROLMAPSHOWN . ", " .
328                 CONTROLMAPNA . ", 0 " .
329                 "FROM products");
330     }
331     RederiveRegexp($regexp, $gid);
332
333     print Bugzilla->cgi->header();
334     $template->process("admin/groups/created.html.tmpl", $vars)
335       || ThrowTemplateError($template->error());
336     exit;
337 }
338
339 #
340 # action='del' -> ask if user really wants to delete
341 #
342 # (next action would be 'delete')
343 #
344
345 if ($action eq 'del') {
346     # Check that an existing group ID is given
347     my $gid = CheckGroupID($cgi->param('group'));
348     my ($name, $desc, $isbuggroup) =
349         $dbh->selectrow_array("SELECT name, description, isbuggroup " .
350                               "FROM groups WHERE id = ?", undef, $gid);
351
352     # System groups cannot be deleted!
353     if (!$isbuggroup) {
354         ThrowUserError("system_group_not_deletable", { name => $name });
355     }
356
357     my $hasusers = 0;
358     SendSQL("SELECT user_id FROM user_group_map 
359              WHERE group_id = $gid AND isbless = 0");
360     if (FetchOneColumn()) {
361         $hasusers = 1;
362     }
363
364     my $hasbugs = 0;
365     my $buglist = "0";
366     SendSQL("SELECT bug_id FROM bug_group_map WHERE group_id = $gid");
367
368     if (MoreSQLData()) {
369         $hasbugs = 1;
370
371         while (MoreSQLData()) {
372             my ($bug) = FetchSQLData();
373             $buglist .= "," . $bug;
374         }
375     }
376
377     my $hasproduct = 0;
378     SendSQL("SELECT name FROM products WHERE name=" . SqlQuote($name));
379     if (MoreSQLData()) {
380         $hasproduct = 1;
381     }
382
383     my $hasflags = 0;
384     SendSQL("SELECT id FROM flagtypes 
385              WHERE grant_group_id = $gid OR request_group_id = $gid");
386     if (FetchOneColumn()) {
387         $hasflags = 1;
388     }
389
390     $vars->{'gid'}         = $gid;
391     $vars->{'name'}        = $name;
392     $vars->{'description'} = $desc;
393     $vars->{'hasusers'}    = $hasusers;
394     $vars->{'hasbugs'}     = $hasbugs;
395     $vars->{'hasproduct'}  = $hasproduct;
396     $vars->{'hasflags'}    = $hasflags;
397     $vars->{'buglist'}     = $buglist;
398
399     print Bugzilla->cgi->header();
400     $template->process("admin/groups/delete.html.tmpl", $vars)
401       || ThrowTemplateError($template->error());
402     
403     exit;
404 }
405
406 #
407 # action='delete' -> really delete the group
408 #
409
410 if ($action eq 'delete') {
411     # Check that an existing group ID is given
412     my $gid = CheckGroupID($cgi->param('group'));
413     my ($name, $isbuggroup) =
414         $dbh->selectrow_array("SELECT name, isbuggroup FROM groups " .
415                               "WHERE id = ?", undef, $gid);
416
417     # System groups cannot be deleted!
418     if (!$isbuggroup) {
419         ThrowUserError("system_group_not_deletable", { name => $name });
420     }
421
422     my $cantdelete = 0;
423
424     SendSQL("SELECT user_id FROM user_group_map 
425              WHERE group_id = $gid AND isbless = 0");
426     if (FetchOneColumn()) {
427         if (!defined $cgi->param('removeusers')) {
428             $cantdelete = 1;
429         }
430     }
431     SendSQL("SELECT bug_id FROM bug_group_map WHERE group_id = $gid");
432     if (FetchOneColumn()) {
433         if (!defined $cgi->param('removebugs')) {
434             $cantdelete = 1;
435         }
436     }
437     SendSQL("SELECT name FROM products WHERE name=" . SqlQuote($name));
438     if (FetchOneColumn()) {
439         if (!defined $cgi->param('unbind')) {
440             $cantdelete = 1;
441         }
442     }
443     SendSQL("SELECT id FROM flagtypes 
444              WHERE grant_group_id = $gid OR request_group_id = $gid");
445     if (FetchOneColumn()) {
446         if (!defined $cgi->param('removeflags')) {
447             $cantdelete = 1;
448         }
449     }
450
451     if (!$cantdelete) {
452         SendSQL("UPDATE flagtypes SET grant_group_id = NULL 
453                  WHERE grant_group_id = $gid");
454         SendSQL("UPDATE flagtypes SET request_group_id = NULL 
455                  WHERE request_group_id = $gid");
456         SendSQL("DELETE FROM user_group_map WHERE group_id = $gid");
457         SendSQL("DELETE FROM group_group_map
458                  WHERE grantor_id = $gid OR member_id = $gid");
459         SendSQL("DELETE FROM bug_group_map WHERE group_id = $gid");
460         SendSQL("DELETE FROM group_control_map WHERE group_id = $gid");
461         SendSQL("DELETE FROM whine_schedules WHERE " .
462                 "mailto_type = " . MAILTO_GROUP . " " .
463                 "AND mailto = $gid");
464         SendSQL("DELETE FROM groups WHERE id = $gid");
465     }
466
467     $vars->{'gid'}        = $gid;
468     $vars->{'name'}       = $name;
469     $vars->{'cantdelete'} = $cantdelete;
470
471     print Bugzilla->cgi->header();
472     $template->process("admin/groups/deleted.html.tmpl", $vars)
473       || ThrowTemplateError($template->error());
474
475     exit;
476 }
477
478 #
479 # action='postchanges' -> update the groups
480 #
481
482 if ($action eq 'postchanges') {
483     # ZLL: Bug 181589: we need to have something to remove explictly listed users from
484     # groups in order for the conversion to 2.18 groups to work
485     my $action;
486
487     if ($cgi->param('remove_explicit_members')) {
488         $action = 1;
489     } elsif ($cgi->param('remove_explicit_members_regexp')) {
490         $action = 2;
491     } else {
492         $action = 3;
493     }
494     
495     my ($gid, $chgs, $name, $regexp) = doGroupChanges();
496     
497     $vars->{'action'}  = $action;
498     $vars->{'changes'} = $chgs;
499     $vars->{'gid'}     = $gid;
500     $vars->{'name'}    = $name;
501     if ($action == 2) {
502         $vars->{'regexp'} = $regexp;
503     }
504     
505     print Bugzilla->cgi->header();
506     $template->process("admin/groups/change.html.tmpl", $vars)
507       || ThrowTemplateError($template->error());
508     exit;
509 }
510
511 if (($action eq 'remove_all_regexp') || ($action eq 'remove_all')) {
512     # remove all explicit users from the group with
513     # gid = $cgi->param('group') that match the regular expression
514     # stored in the DB for that group or all of them period
515
516     my $gid = CheckGroupID($cgi->param('group'));
517
518     my $sth = $dbh->prepare("SELECT name, userregexp FROM groups
519                              WHERE id = ?");
520     $sth->execute($gid);
521     my ($name, $regexp) = $sth->fetchrow_array();
522     $dbh->bz_lock_tables('groups WRITE', 'profiles READ',
523                          'user_group_map WRITE');
524     $sth = $dbh->prepare("SELECT user_group_map.user_id, profiles.login_name
525                             FROM user_group_map
526                       INNER JOIN profiles
527                               ON user_group_map.user_id = profiles.userid
528                            WHERE user_group_map.group_id = ?
529                              AND grant_type = ?
530                              AND isbless = 0");
531     $sth->execute($gid, GRANT_DIRECT);
532
533     my @users;
534     my $sth2 = $dbh->prepare("DELETE FROM user_group_map
535                               WHERE user_id = ?
536                               AND isbless = 0
537                               AND group_id = ?");
538     while ( my ($userid, $userlogin) = $sth->fetchrow_array() ) {
539         if ((($regexp =~ /\S/) && ($userlogin =~ m/$regexp/i))
540             || ($action eq 'remove_all'))
541         {
542             $sth2->execute($userid,$gid);
543
544             my $user = {};
545             $user->{'login'} = $userlogin;
546             push(@users, $user);
547         }
548     }
549
550     $sth = $dbh->prepare("UPDATE groups
551              SET last_changed = NOW()
552              WHERE id = ?");
553     $sth->execute($gid);
554     $dbh->bz_unlock_tables();
555
556     $vars->{'users'}      = \@users;
557     $vars->{'name'}       = $name;
558     $vars->{'regexp'}     = $regexp;
559     $vars->{'remove_all'} = ($action eq 'remove_all');
560     $vars->{'gid'}        = $gid;
561     
562     print Bugzilla->cgi->header();
563     $template->process("admin/groups/remove.html.tmpl", $vars)
564       || ThrowTemplateError($template->error());
565
566     exit;
567 }
568
569
570 #
571 # No valid action found
572 #
573
574 ThrowCodeError("action_unrecognized", $vars);
575
576
577 # Helper sub to handle the making of changes to a group
578 sub doGroupChanges {
579     my $cgi = Bugzilla->cgi;
580     my $dbh = Bugzilla->dbh;
581     my $sth;
582
583     $dbh->bz_lock_tables('groups WRITE', 'group_group_map WRITE',
584                          'bug_group_map WRITE', 'user_group_map WRITE',
585                          'group_control_map READ', 'bugs READ', 'profiles READ',
586                          'namedqueries READ', 'whine_queries READ');
587
588     # Check that the given group ID and regular expression are valid.
589     # If tests are successful, trimmed values are returned by CheckGroup*.
590     my $gid = CheckGroupID($cgi->param('group'));
591     my $regexp = CheckGroupRegexp($cgi->param('regexp'));
592
593     # The name and the description of system groups cannot be edited.
594     # We then need to know if the group being edited is a system group.
595     SendSQL("SELECT isbuggroup FROM groups WHERE id = $gid");
596     my ($isbuggroup) = FetchSQLData();
597     my $name;
598     my $desc;
599     my $isactive;
600     my $chgs = 0;
601
602     # We trust old values given by the template. If they are hacked
603     # in a way that some of the tests below become negative, the
604     # corresponding attributes are not updated in the DB, which does
605     # not hurt.
606     if ($isbuggroup) {
607         # Check that the group name and its description are valid
608         # and return trimmed values if tests are successful.
609         $name = CheckGroupName($cgi->param('name'), $gid);
610         $desc = CheckGroupDesc($cgi->param('desc'));
611         $isactive = $cgi->param('isactive') ? 1 : 0;
612
613         if ($name ne $cgi->param('oldname')) {
614             $chgs = 1;
615             $sth = $dbh->do("UPDATE groups SET name = ? WHERE id = ?",
616                             undef, $name, $gid);
617         }
618         if ($desc ne $cgi->param('olddesc')) {
619             $chgs = 1;
620             $sth = $dbh->do("UPDATE groups SET description = ? WHERE id = ?",
621                             undef, $desc, $gid);
622         }
623         if ($isactive ne $cgi->param('oldisactive')) {
624             $chgs = 1;
625             $sth = $dbh->do("UPDATE groups SET isactive = ? WHERE id = ?",
626                             undef, $isactive, $gid);
627             # If the group was mandatory for some products before
628             # we deactivated it and we now activate this group again,
629             # we have to add all bugs created while this group was
630             # disabled in bug_group_map to correctly protect them.
631             if ($isactive) { fix_bug_permissions($gid); }
632         }
633     }
634     if ($regexp ne $cgi->param('oldregexp')) {
635         $chgs = 1;
636         $sth = $dbh->do("UPDATE groups SET userregexp = ? WHERE id = ?",
637                         undef, $regexp, $gid);
638         RederiveRegexp($regexp, $gid);
639     }
640
641     foreach my $b (grep {/^oldgrp-\d*$/} $cgi->param()) {
642         if (defined($cgi->param($b))) {
643             $b =~ /^oldgrp-(\d+)$/;
644             my $v = $1;
645             my $grp = $cgi->param("grp-$v") || 0;
646             if (($v != $gid) && ($cgi->param("oldgrp-$v") != $grp)) {
647                 $chgs = 1;
648                 if ($grp != 0) {
649                     SendSQL("INSERT INTO group_group_map 
650                              (member_id, grantor_id, grant_type)
651                              VALUES ($v, $gid," . GROUP_MEMBERSHIP . ")");
652                 } else {
653                     SendSQL("DELETE FROM group_group_map
654                              WHERE member_id = $v AND grantor_id = $gid
655                              AND grant_type = " . GROUP_MEMBERSHIP);
656                 }
657             }
658
659             my $bless = $cgi->param("bless-$v") || 0;
660             my $oldbless = $cgi->param("oldbless-$v");
661             if ((defined $oldbless) and ($oldbless != $bless)) {
662                 $chgs = 1;
663                 if ($bless != 0) {
664                     SendSQL("INSERT INTO group_group_map 
665                              (member_id, grantor_id, grant_type)
666                              VALUES ($v, $gid," . GROUP_BLESS . ")");
667                 } else {
668                     SendSQL("DELETE FROM group_group_map
669                              WHERE member_id = $v AND grantor_id = $gid
670                              AND grant_type = " . GROUP_BLESS);
671                 }
672             }
673
674             my $cansee = $cgi->param("cansee-$v") || 0;
675             if (Param("usevisibilitygroups") 
676                && ($cgi->param("oldcansee-$v") != $cansee)) {
677                 $chgs = 1;
678                 if ($cansee != 0) {
679                     SendSQL("INSERT INTO group_group_map 
680                              (member_id, grantor_id, grant_type)
681                              VALUES ($v, $gid," . GROUP_VISIBLE . ")");
682                 } else {
683                     SendSQL("DELETE FROM group_group_map
684                              WHERE member_id = $v AND grantor_id = $gid
685                              AND grant_type = " . GROUP_VISIBLE);
686                 }
687             }
688
689         }
690     }
691     
692     if ($chgs) {
693         # mark the changes
694         SendSQL("UPDATE groups SET last_changed = NOW() WHERE id = $gid");
695     }
696     $dbh->bz_unlock_tables();
697     return $gid, $chgs, $name, $regexp;
698 }