diff options
author | bugreport%peshkin.net <> | 2002-11-25 04:56:17 +0100 |
---|---|---|
committer | bugreport%peshkin.net <> | 2002-11-25 04:56:17 +0100 |
commit | c64d51111a5ae02d6fc45163a847d0b7e2004548 (patch) | |
tree | fd7a0cb912e4411573faa5305df4d4971a3b6dda | |
parent | e7720dcdd4e332c096a310c53412d3acaacd381e (diff) | |
download | bugzilla-c64d51111a5ae02d6fc45163a847d0b7e2004548.tar.gz bugzilla-c64d51111a5ae02d6fc45163a847d0b7e2004548.tar.xz |
Bug 147275 Rearchitect product groups
Patch by joel
r=bbaetz,justdave
a=justdave
-rw-r--r-- | Bugzilla/Config.pm | 10 | ||||
-rw-r--r-- | Bugzilla/Constants.pm | 76 | ||||
-rw-r--r-- | bug_form.pl | 56 | ||||
-rwxr-xr-x | buglist.cgi | 1 | ||||
-rwxr-xr-x | checksetup.pl | 65 | ||||
-rw-r--r-- | defparams.pl | 6 | ||||
-rwxr-xr-x | describecomponents.cgi | 9 | ||||
-rwxr-xr-x | editgroups.cgi | 57 | ||||
-rwxr-xr-x | editproducts.cgi | 539 | ||||
-rwxr-xr-x | enter_bug.cgi | 60 | ||||
-rw-r--r-- | globals.pl | 113 | ||||
-rwxr-xr-x | post_bug.cgi | 35 | ||||
-rwxr-xr-x | process_bug.cgi | 213 | ||||
-rwxr-xr-x | query.cgi | 11 | ||||
-rwxr-xr-x | queryhelp.cgi | 2 | ||||
-rwxr-xr-x | reports.cgi | 26 | ||||
-rwxr-xr-x | sanitycheck.cgi | 50 | ||||
-rw-r--r-- | template/en/default/admin/products/groupcontrol/confirm-edit.html.tmpl | 55 | ||||
-rw-r--r-- | template/en/default/admin/products/groupcontrol/edit.html.tmpl | 284 | ||||
-rw-r--r-- | template/en/default/bug/edit.html.tmpl | 22 | ||||
-rw-r--r-- | template/en/default/bug/process/verify-new-product.html.tmpl | 4 | ||||
-rw-r--r-- | template/en/default/global/user-error.html.tmpl | 10 |
22 files changed, 1507 insertions, 197 deletions
diff --git a/Bugzilla/Config.pm b/Bugzilla/Config.pm index 25792d476..f72004e3b 100644 --- a/Bugzilla/Config.pm +++ b/Bugzilla/Config.pm @@ -167,6 +167,16 @@ sub UpdateParams { delete $param{'usequip'}; } + # Change from old product groups to controls for group_control_map + # 2002-10-14 bug 147275 bugreport@peshkin.net + if (exists $param{'usebuggroups'} && !exists $param{'makeproductgroups'}) { + $param{'makeproductgroups'} = $param{'usebuggroups'}; + } + if (exists $param{'usebuggroupsentry'} + && !exists $param{'useentrygroupdefault'}) { + $param{'useentrygroupdefault'} = $param{'usebuggroupsentry'}; + } + # --- DEFAULTS FOR NEW PARAMS --- foreach my $item (@param_list) { diff --git a/Bugzilla/Constants.pm b/Bugzilla/Constants.pm new file mode 100644 index 000000000..70773e036 --- /dev/null +++ b/Bugzilla/Constants.pm @@ -0,0 +1,76 @@ +# -*- Mode: perl; indent-tabs-mode: nil -*- +# +# The contents of this file are subject to the Mozilla Public +# License Version 1.1 (the "License"); you may not use this file +# except in compliance with the License. You may obtain a copy of +# the License at http://www.mozilla.org/MPL/ +# +# Software distributed under the License is distributed on an "AS +# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or +# implied. See the License for the specific language governing +# rights and limitations under the License. +# +# The Original Code is the Bugzilla Bug Tracking System. +# +# The Initial Developer of the Original Code is Netscape Communications +# Corporation. Portions created by Netscape are +# Copyright (C) 1998 Netscape Communications Corporation. All +# Rights Reserved. +# +# Contributor(s): Terry Weissman <terry@mozilla.org> +# Dawn Endico <endico@mozilla.org> +# Dan Mosedale <dmose@mozilla.org> +# Joe Robins <jmrobins@tgix.com> +# Jake <jake@bugzilla.org> +# J. Paul Reed <preed@sigkill.com> +# Bradley Baetz <bbaetz@student.usyd.edu.au> +# Christopher Aillon <christopher@aillon.com> + + +package Bugzilla::Constants; +use strict; +use base qw(Exporter); + +@Bugzilla::Constants::EXPORT = qw( + CONTROLMAPNA + CONTROLMAPSHOWN + CONTROLMAPDEFAULT + CONTROLMAPMANDATORY + ); + + +# CONSTANTS +# +# ControlMap constants for group_control_map. +# membercontol:othercontrol => meaning +# Na:Na => Bugs in this product may not be restricted to this +# group. +# Shown:Na => Members of the group may restrict bugs +# in this product to this group. +# Shown:Shown => Members of the group may restrict bugs +# in this product to this group. +# Anyone who can enter bugs in this product may initially +# restrict bugs in this product to this group. +# Shown:Mandatory => Members of the group may restrict bugs +# in this product to this group. +# Non-members who can enter bug in this product +# will be forced to restrict it. +# Default:Na => Members of the group may restrict bugs in this +# product to this group and do so by default. +# Default:Default => Members of the group may restrict bugs in this +# product to this group and do so by default and +# nonmembers have this option on entry. +# Default:Mandatory => Members of the group may restrict bugs in this +# product to this group and do so by default. +# Non-members who can enter bug in this product +# will be forced to restrict it. +# Mandatory:Mandatory => Bug will be forced into this group regardless. +# All other combinations are illegal. + +use constant CONTROLMAPNA => 0; +use constant CONTROLMAPSHOWN => 1; +use constant CONTROLMAPDEFAULT => 2; +use constant CONTROLMAPMANDATORY => 3; + +1; + diff --git a/bug_form.pl b/bug_form.pl index c620f03fd..e390ad51e 100644 --- a/bug_form.pl +++ b/bug_form.pl @@ -24,7 +24,7 @@ use strict; use RelationSet; - +use Bugzilla::Constants; # Use the Attachment module to display attachments for the bug. use Attachment; @@ -144,12 +144,9 @@ sub show_bug { next; } - if (Param("usebuggroupsentry") - && GroupExists($product) - && !UserInGroup($product)) - { + if (!CanEnterProduct($product)) { # If we're using bug groups to restrict entry on products, and - # this product has a bug group, and the user is not in that + # this product has an entry group, and the user is not in that # group, we don't want to include that product in this list. next; } @@ -275,7 +272,7 @@ sub show_bug { SendSQL("SELECT DISTINCT groups.id, name, description," . " bug_group_map.group_id IS NOT NULL," . " user_group_map.group_id IS NOT NULL," . - " isactive" . + " isactive, membercontrol, othercontrol" . " FROM groups" . " LEFT JOIN bug_group_map" . " ON bug_group_map.group_id = groups.id" . @@ -284,33 +281,48 @@ sub show_bug { " ON user_group_map.group_id = groups.id" . " AND user_id = $::userid" . " AND NOT isbless" . + " LEFT JOIN group_control_map" . + " ON group_control_map.group_id = groups.id" . + " AND group_control_map.product_id = " . $bug{'product_id'} . " WHERE isbuggroup"); $user{'inallgroups'} = 1; while (MoreSQLData()) { - my ($groupid, $name, $description, $ison, $ingroup, $isactive) - = FetchSQLData(); + my ($groupid, $name, $description, $ison, $ingroup, $isactive, + $membercontrol, $othercontrol) = FetchSQLData(); $bug{'inagroup'} = 1 if ($ison); + $membercontrol ||= 0; + if ($isactive && ($membercontrol == CONTROLMAPMANDATORY)) { + $bug{'inagroup'} = 1; + } # For product groups, we only want to display the checkbox if either - # (1) The bit is already set, or - # (2) The user is in the group, but either: - # (a) The group is a product group for the current product, or - # (b) The group name isn't a product name - # This means that all product groups will be skipped, but - # non-product bug groups will still be displayed. - if($ison || - ($isactive && ($ingroup && (!Param("usebuggroups") || ($name eq $bug{'product'}) || - (!defined $::proddesc{$name}))))) + # (1) The bit is set and not required, or + # (2) The group is Shown or Default for members and + # the user is a member of the group. + if ($ison || + ($isactive && $ingroup + && (($membercontrol == CONTROLMAPDEFAULT) + || ($membercontrol == CONTROLMAPSHOWN)) + )) { $user{'inallgroups'} &= $ingroup; - push (@groups, { "bit" => $groupid, - "ison" => $ison, - "ingroup" => $ingroup, - "description" => $description }); + my $mandatory; + if ($isactive && ($membercontrol == CONTROLMAPMANDATORY)) { + $mandatory = 1; + } else { + $mandatory = 0; + } + if (($ison) || ($ingroup)) { + push (@groups, { "bit" => $groupid, + "ison" => $ison, + "ingroup" => $ingroup, + "mandatory" => $mandatory, + "description" => $description }); + } } } diff --git a/buglist.cgi b/buglist.cgi index fdab9eb83..18d5b1b55 100755 --- a/buglist.cgi +++ b/buglist.cgi @@ -728,7 +728,6 @@ $vars->{'order'} = $order; my $login = $::COOKIE{'Bugzilla_login'}; $vars->{'caneditbugs'} = UserInGroup('editbugs'); -$vars->{'usebuggroups'} = Param('usebuggroups'); # Whether or not this user is authorized to move bugs to another installation. $vars->{'ismover'} = 1 diff --git a/checksetup.pl b/checksetup.pl index f4a70c284..5f947dabe 100755 --- a/checksetup.pl +++ b/checksetup.pl @@ -112,6 +112,7 @@ use strict; use vars qw( $db_name %answer ); +use Bugzilla::Constants; ########################################################################### # Non-interactive override @@ -1716,6 +1717,17 @@ $table{quips} = userid mediumint not null default 0, quip text not null'; +$table{group_control_map} = + 'group_id mediumint not null, + product_id mediumint not null, + entry tinyint not null, + membercontrol tinyint not null, + othercontrol tinyint not null, + canedit tinyint not null, + + unique(product_id, group_id), + index(group_id)'; + ########################################################################### # Create tables ########################################################################### @@ -2954,7 +2966,7 @@ if (GetFieldDef("logincookies", "hostname")) { AddField("logincookies", "ipaddr", "varchar(40) NOT NULL"); } -# 2002-05-10 - enhanchment bug 143826 +# 2002-08-19 - bugreport@peshkin.net bug 143826 # Add private comments and private attachments on less-private bugs AddField('longdescs', 'isprivate', 'tinyint not null default 0'); AddField('attachments', 'isprivate', 'tinyint not null default 0'); @@ -3121,7 +3133,7 @@ if (($fielddef = GetFieldDef("attachments", "creation_ts")) && ChangeFieldType("attachments", "creation_ts", "datetime NOT NULL"); } -# 2002-08-XX - bugreport@peshkin.net - bug 157756 +# 2002-09-22 - bugreport@peshkin.net - bug 157756 # # If the whole groups system is new, but the installation isn't, # convert all the old groupset groups, etc... @@ -3464,6 +3476,54 @@ if (TableExists("attachstatuses") && TableExists("attachstatusdefs")) { print "done.\n"; } +# 2002-11-24 - bugreport@peshkin.net - bug 147275 +# +if (Param('makeproductgroups')) { + # If makeproductgroups is enabled and group_control_map is empty, + # backward-compatbility usebuggroups-equivalent records should + # be created. + my $entry = Param('useentrygroupdefault'); + $sth = $dbh->prepare("SELECT COUNT(*) FROM group_control_map"); + $sth->execute(); + my ($mapcnt) = $sth->fetchrow_array(); + if ($mapcnt == 0) { + # Initially populate group_control_map. + # First, get all the existing products and their groups. + $sth = $dbh->prepare("SELECT groups.id, products.id, groups.name, " . + "products.name FROM groups, products " . + "WHERE isbuggroup != 0 AND isactive != 0"); + $sth->execute(); + while (my ($groupid, $productid, $groupname, $productname) + = $sth->fetchrow_array()) { + if ($groupname eq $productname) { + # Product and group have same name. + $dbh->do("INSERT INTO group_control_map " . + "(group_id, product_id, entry, membercontrol, " . + "othercontrol, canedit) " . + "VALUES ($groupid, $productid, $entry, " . + CONTROLMAPDEFAULT . ", " . + CONTROLMAPNA . ", 0)"); + } else { + # See if this group is a product group at all. + my $sth2 = $dbh->prepare("SELECT id FROM products WHERE name = " . + $dbh->quote($groupname)); + $sth2->execute(); + my ($id) = $sth2->fetchrow_array(); + if (!$id) { + # If there is no product with the same name as this + # group, then it is permitted for all products. + $dbh->do("INSERT INTO group_control_map " . + "(group_id, product_id, entry, membercontrol, " . + "othercontrol, canedit) " . + "VALUES ($groupid, $productid, 0, " . + CONTROLMAPSHOWN . ", " . + CONTROLMAPNA . ", 0)"); + } + } + } + } +} + # If you had to change the --TABLE-- definition in any way, then add your # differential change code *** A B O V E *** this comment. # @@ -3793,3 +3853,4 @@ $dbh->do("UPDATE components SET initialowner = $adminuid WHERE initialowner = 0" unlink "data/versioncache"; print "Reminder: Bugzilla now requires version 8.7 or later of sendmail.\n" unless $silent; + diff --git a/defparams.pl b/defparams.pl index f518a2de1..b0567c783 100644 --- a/defparams.pl +++ b/defparams.pl @@ -244,7 +244,7 @@ sub check_netmask { }, { - name => 'usebuggroups', + name => 'makeproductgroups', desc => 'If this is on, Bugzilla will associate a bug group with each ' . 'product in the database, and use it for querying bugs.', type => 'b', @@ -252,9 +252,9 @@ sub check_netmask { }, { - name => 'usebuggroupsentry', + name => 'useentrygroupdefault', desc => 'If this is on, Bugzilla will use product bug groups to restrict ' . - 'who can enter bugs. Requires usebuggroups to be on as well.', + 'who can enter bugs. Requires makeproductgroups to be on as well.', type => 'b', default => 0 }, diff --git a/describecomponents.cgi b/describecomponents.cgi index b4953ddc6..a1a6f0049 100755 --- a/describecomponents.cgi +++ b/describecomponents.cgi @@ -42,11 +42,11 @@ if (!defined $::FORM{'product'}) { # Reference to a subset of %::proddesc, which the user is allowed to see my %products; - if (Param("usebuggroups")) { + if (AnyDefaultGroups()) { # OK, now only add products the user can see confirm_login() unless $::userid; foreach my $p (@::legal_product) { - if (!GroupExists($p) || UserInGroup($p)) { + if (CanEnterProduct($p)) { $products{$p} = $::proddesc{$p}; } } @@ -88,11 +88,8 @@ if (!$product_id) { } # Make sure the user is authorized to access this product. -if (Param("usebuggroups") && GroupExists($product)) { - confirm_login() unless $::userid; - UserInGroup($product) +CanEnterProduct($product) || ThrowUserError("product_access_denied"); -} ###################################################################### # End Data/Security Validation diff --git a/editgroups.cgi b/editgroups.cgi index 5dd2395af..031a23c9e 100755 --- a/editgroups.cgi +++ b/editgroups.cgi @@ -27,6 +27,7 @@ use strict; use lib "."; +use Bugzilla::Constants; require "CGI.pl"; ConnectToDatabase(); @@ -117,9 +118,9 @@ unless ($action) { while (MoreSQLData()) { my ($groupid, $name, $desc, $regexp, $isactive, $isbuggroup) = FetchSQLData(); print "<tr>\n"; - print "<td>$name</td>\n"; - print "<td>$desc</td>\n"; - print "<td>$regexp </td>\n"; + print "<td>" . html_quote($name) . "</td>\n"; + print "<td>" . html_quote($desc) . "</td>\n"; + print "<td>" . html_quote($regexp) . " </td>\n"; print "<td align=center>"; print "X" if (($isactive != 0) && ($isbuggroup != 0)); print " </td>\n"; @@ -185,22 +186,27 @@ if ($action eq 'changeform') { print "<TABLE BORDER=1 CELLPADDING=4>"; print "<TR><TH>Group:</TH><TD>"; if ($isbuggroup == 0) { - print "$name"; + print html_quote($name); } else { - print "<INPUT TYPE=HIDDEN NAME=\"oldname\" VALUE=$name> - <INPUT SIZE=60 NAME=\"name\" VALUE=\"$name\">"; + print "<INPUT TYPE=HIDDEN NAME=\"oldname\" VALUE=" . + html_quote($name) . "> + <INPUT SIZE=60 NAME=\"name\" VALUE=\"" . html_quote($name) . "\">"; } print "</TD></TR><TR><TH>Description:</TH><TD>"; if ($isbuggroup == 0) { - print "$description"; + print html_quote($description); } else { - print "<INPUT TYPE=HIDDEN NAME=\"olddesc\" VALUE=\"$description\"> - <INPUT SIZE=70 NAME=\"desc\" VALUE=\"$description\">"; + print "<INPUT TYPE=HIDDEN NAME=\"olddesc\" VALUE=\"" . + html_quote($description) . "\"> + <INPUT SIZE=70 NAME=\"desc\" VALUE=\"" . + html_quote($description) . "\">"; } print "</TD></TR><TR> <TH>User Regexp:</TH><TD>"; - print "<INPUT TYPE=HIDDEN NAME=\"oldrexp\" VALUE=\"$rexp\"> - <INPUT SIZE=40 NAME=\"rexp\" VALUE=\"$rexp\"></TD></TR>"; + print "<INPUT TYPE=HIDDEN NAME=\"oldrexp\" VALUE=\"" . + html_quote($rexp) . "\"> + <INPUT SIZE=40 NAME=\"rexp\" VALUE=\"" . + html_quote($rexp) . "\"></TD></TR>"; if ($isbuggroup == 1) { print "<TR><TH>Use For Bugs:</TH><TD> <INPUT TYPE=checkbox NAME =\"isactive\" VALUE=1 " . (($isactive == 1) ? "CHECKED" : "") . "> @@ -252,8 +258,8 @@ if ($action eq 'changeform') { print "<INPUT TYPE=HIDDEN NAME=\"oldbless-$grpid\" VALUE=$blessmember></TD>"; print "<TD><INPUT TYPE=checkbox NAME=\"grp-$grpid\" $grpchecked VALUE=1>"; print "<INPUT TYPE=HIDDEN NAME=\"oldgrp-$grpid\" VALUE=$grpmember></TD>"; - print "<TD><B>$grpnam</B></TD>"; - print "<TD>$grpdesc</TD>"; + print "<TD><B>" . html_quote($grpnam) . "</B></TD>"; + print "<TD>" . html_quote($grpdesc) . "</TD>"; print "</TR>\n"; } @@ -290,6 +296,10 @@ if ($action eq 'add') { print "<td><input size=30 name=\"regexp\"></td>\n"; print "<td><input type=\"checkbox\" name=\"isactive\" value=\"1\" checked></td>\n"; print "</TR></TABLE>\n<HR>\n"; + print "<input type=\"checkbox\" name=\"insertnew\" value=\"1\""; + print " checked" if Param("makeproductgroups"); + print ">\n"; + print "Insert new group into all existing products.<P>\n"; print "<INPUT TYPE=SUBMIT VALUE=\"Add\">\n"; print "<INPUT TYPE=HIDDEN NAME=\"action\" VALUE=\"new\">\n"; print "</FORM>"; @@ -308,9 +318,13 @@ to this group, although bugs already in the group will remain in the group. Doing so is a much less drastic way to stop a group from growing than deleting the group would be. <b>Note: If you are creating a group, you probably want it to be usable for bugs, in which case you should leave this checked.</b><p>"; - print "<b>User RegExp</b> is optional, and if filled in, will automatically -grant membership to this group to anyone creating a new account with an -email address that matches this regular expression.<p>"; + print "<b>User RegExp</b> is optional, and if filled in, will "; + print "automatically grant membership to this group to anyone with an "; + print "email address that matches this regular expression.<p>\n"; + print "By default, the new group will be associated with existing "; + print "products. Unchecking the \"Insert new group into all existing "; + print "products\" option will prevent this and make the group become "; + print "visible only when its controls have been added to a product.<P>\n"; PutTrailer("<a href=editgroups.cgi>Back to the group list</a>"); exit; @@ -384,6 +398,16 @@ if ($action eq 'new') { VALUES ($admin, $gid, 0)"); SendSQL("INSERT INTO group_group_map (member_id, grantor_id, isbless) VALUES ($admin, $gid, 1)"); + # Permit all existing products to use the new group if makeproductgroups. + if ($::FORM{insertnew}) { + SendSQL("INSERT INTO group_control_map " . + "(group_id, product_id, entry, membercontrol, " . + "othercontrol, canedit) " . + "SELECT $gid, products.id, 0, " . + CONTROLMAPSHOWN . ", " . + CONTROLMAPNA . ", 0 " . + "FROM products"); + } print "OK, done.<p>\n"; PutTrailer("<a href=\"editgroups.cgi?action=add\">Add another group</a>", "<a href=\"editgroups.cgi\">Back to the group list</a>"); @@ -543,6 +567,7 @@ if ($action eq 'delete') { SendSQL("DELETE FROM user_group_map WHERE group_id = $gid"); SendSQL("DELETE FROM group_group_map WHERE grantor_id = $gid"); SendSQL("DELETE FROM bug_group_map WHERE group_id = $gid"); + SendSQL("DELETE FROM group_control_map WHERE group_id = $gid"); SendSQL("DELETE FROM groups WHERE id = $gid"); print "<B>Group $gid has been deleted.</B><BR>"; diff --git a/editproducts.cgi b/editproducts.cgi index 5649c4440..675a70d9e 100755 --- a/editproducts.cgi +++ b/editproducts.cgi @@ -29,7 +29,8 @@ use strict; use lib "."; - +use vars qw ($template $vars); +use Bugzilla::Constants; require "CGI.pl"; require "globals.pl"; @@ -38,9 +39,16 @@ require "globals.pl"; sub sillyness { my $zz; + $zz = %::MFORM; $zz = $::unconfirmedstate; } +my %ctl = ( + &::CONTROLMAPNA => 'NA', + &::CONTROLMAPSHOWN => 'Shown', + &::CONTROLMAPDEFAULT => 'Default', + &::CONTROLMAPMANDATORY => 'Mandatory' +); # TestProduct: just returns if the specified product does exists # CheckProduct: same check, optionally emit an error text @@ -187,10 +195,9 @@ unless (UserInGroup("editcomponents")) { # my $product = trim($::FORM{product} || ''); my $action = trim($::FORM{action} || ''); +my $headerdone = 0; my $localtrailer = "<A HREF=\"editproducts.cgi\">edit</A> more products"; - - # # action='' -> Show nice list of products # @@ -342,7 +349,7 @@ if ($action eq 'new') { # If we're using bug groups, then we need to create a group for this # product as well. -JMR, 2/16/00 - if(Param("usebuggroups")) { + if(Param("makeproductgroups")) { # Next we insert into the groups table SendSQL("INSERT INTO groups " . "(name, description, isbuggroup, last_changed) " . @@ -356,6 +363,35 @@ if ($action eq 'new') { VALUES ($admin, $gid, 0)"); SendSQL("INSERT INTO group_group_map (member_id, grantor_id, isbless) VALUES ($admin, $gid, 1)"); + + # Associate the new group and new product. + SendSQL("INSERT INTO group_control_map " . + "(group_id, product_id, entry, " . + "membercontrol, othercontrol, canedit) VALUES " . + "($gid, $product_id, " . Param("useentrygroupdefault") . + ", " . CONTROLMAPDEFAULT . ", " . + CONTROLMAPNA . ", 0)"); + + # Permit the new product to use any non-product bug groups. + SendSQL("SELECT groups.id, products.id IS NULL FROM groups " . + "LEFT JOIN products " . + "ON groups.name = products.name " . + "WHERE isactive != 0 AND isbuggroup != 0 "); + while (MoreSQLData()) { + my ($grpid, $nonproductgroup) = FetchSQLData(); + if ($nonproductgroup) { + PushGlobalSQLState(); + SendSQL("INSERT INTO group_control_map " . + "(group_id, product_id, entry, " . + "membercontrol, othercontrol, canedit) VALUES " . + "entry, control, canedit) VALUES " . + "($grpid, $product_id, 0, " . + CONTROLMAPSHOWN . ", " . + CONTROLMAPNA . ", 0)"); + PopGlobalSQLState(); + } + } + } @@ -510,7 +546,7 @@ one."; print "<INPUT TYPE=SUBMIT VALUE=\"Yes, delete\">\n"; print "<INPUT TYPE=HIDDEN NAME=\"action\" VALUE=\"delete\">\n"; print "<INPUT TYPE=HIDDEN NAME=\"product\" VALUE=\"" . - value_quote($product) . "\">\n"; + html_quote($product) . "\">\n"; print "</FORM>"; PutTrailer($localtrailer); @@ -538,6 +574,7 @@ if ($action eq 'delete') { versions WRITE, products WRITE, groups WRITE, + group_control_map WRITE, profiles WRITE, milestones WRITE, flaginclusions WRITE, @@ -583,6 +620,10 @@ if ($action eq 'delete') { WHERE product_id=$product_id"); print "Milestones deleted.<BR>\n"; + SendSQL("DELETE FROM group_control_map + WHERE product_id=$product_id"); + print "Group controls deleted.<BR>\n"; + SendSQL("DELETE FROM flaginclusions WHERE product_id=$product_id"); SendSQL("DELETE FROM flagexclusions @@ -593,7 +634,6 @@ if ($action eq 'delete') { WHERE id=$product_id"); print "Product '$product' deleted.<BR>\n"; - SendSQL("UNLOCK TABLES"); unlink "data/versioncache"; @@ -693,6 +733,27 @@ if ($action eq 'edit') { } print "</TD>\n</TR><TR>\n"; + print " <TH ALIGN=\"right\" VALIGN=\"top\"><A HREF=\"editproducts.cgi?action=editgroupcontrols&product=", url_quote($product), "\">Edit Group Access Controls</A></TH>\n"; + print "<TD>\n"; + SendSQL("SELECT id, name, isactive, entry, membercontrol, othercontrol, canedit " . + "FROM groups, " . + "group_control_map " . + "WHERE group_control_map.group_id = id AND product_id = $product_id " . + "AND isbuggroup != 0 ORDER BY name"); + while (MoreSQLData()) { + my ($id, $name, $isactive, $entry, $membercontrol, $othercontrol, $canedit) + = FetchSQLData(); + print "<B>" . html_quote($name) . ":</B> "; + if ($isactive) { + print $ctl{$membercontrol} . "/" . $ctl{$othercontrol}; + print ", ENTRY" if $entry; + print ", CANEDIT" if $canedit; + } else { + print "DISABLED"; + } + print "<BR>\n"; + } + print "</TD>\n</TR><TR>\n"; print " <TH ALIGN=\"right\">Bugs:</TH>\n"; print " <TD>"; SendSQL("SELECT count(bug_id),product_id @@ -706,11 +767,11 @@ if ($action eq 'edit') { print "</TD>\n</TR></TABLE>\n"; print "<INPUT TYPE=HIDDEN NAME=\"productold\" VALUE=\"" . - value_quote($product) . "\">\n"; + html_quote($product) . "\">\n"; print "<INPUT TYPE=HIDDEN NAME=\"descriptionold\" VALUE=\"" . - value_quote($description) . "\">\n"; + html_quote($description) . "\">\n"; print "<INPUT TYPE=HIDDEN NAME=\"milestoneurlold\" VALUE=\"" . - value_quote($milestoneurl) . "\">\n"; + html_quote($milestoneurl) . "\">\n"; print "<INPUT TYPE=HIDDEN NAME=\"disallownewold\" VALUE=\"$disallownew\">\n"; print "<INPUT TYPE=HIDDEN NAME=\"votesperuserold\" VALUE=\"$votesperuser\">\n"; print "<INPUT TYPE=HIDDEN NAME=\"maxvotesperbugold\" VALUE=\"$maxvotesperbug\">\n"; @@ -729,6 +790,230 @@ if ($action eq 'edit') { } +# +# action='updategroupcontrols' -> update the product +# + +if ($action eq 'updategroupcontrols') { + my $product_id = get_product_id($product); + my @now_na = (); + my @now_mandatory = (); + foreach my $f (keys %::FORM) { + if ($f =~ /^membercontrol_(\d+)$/) { + my $id = $1; + if ($::FORM{$f} == CONTROLMAPNA) { + push @now_na,$id; + } elsif ($::FORM{$f} == CONTROLMAPMANDATORY) { + push @now_mandatory,$id; + } + } + } + if (!($::FORM{'confirmed'})) { + $vars->{'form'} = \%::FORM; + $vars->{'mform'} = \%::MFORM; + my @na_groups = (); + if (@now_na) { + SendSQL("SELECT groups.name, COUNT(bugs.bug_id) + FROM bugs, bug_group_map, groups + WHERE groups.id IN(" . join(',',@now_na) . ") + AND bug_group_map.group_id = groups.id + AND bug_group_map.bug_id = bugs.bug_id + AND bugs.product_id = $product_id + GROUP BY groups.name"); + while (MoreSQLData()) { + my ($groupname, $bugcount) = FetchSQLData(); + my %g = (); + $g{'name'} = $groupname; + $g{'count'} = $bugcount; + push @na_groups,\%g; + } + } + + my @mandatory_groups = (); + if (@now_mandatory) { + SendSQL("SELECT groups.name, COUNT(bugs.bug_id) + FROM bugs, groups + LEFT JOIN bug_group_map + ON bug_group_map.group_id = groups.id + AND bug_group_map.bug_id = bugs.bug_id + WHERE groups.id IN(" . join(',',@now_mandatory) . ") + AND bugs.product_id = $product_id + AND bug_group_map.bug_id IS NULL + GROUP BY groups.name"); + while (MoreSQLData()) { + my ($groupname, $bugcount) = FetchSQLData(); + my %g = (); + $g{'name'} = $groupname; + $g{'count'} = $bugcount; + push @mandatory_groups,\%g; + } + } + if ((@na_groups) || (@mandatory_groups)) { + $vars->{'product'} = $product; + $vars->{'na_groups'} = \@na_groups; + $vars->{'mandatory_groups'} = \@mandatory_groups; + $template->process("admin/products/groupcontrol/confirm-edit.html.tmpl", $vars) + || ThrowTemplateError($template->error()); + exit; + } + } + PutHeader("Update group access controls for product \"$product\""); + $headerdone = 1; + SendSQL("SELECT id, name FROM groups " . + "WHERE isbuggroup != 0 AND isactive != 0"); + while (MoreSQLData()){ + my ($groupid, $groupname) = FetchSQLData(); + my $newmembercontrol = $::FORM{"membercontrol_$groupid"} || 0; + my $newothercontrol = $::FORM{"othercontrol_$groupid"} || 0; + # Legality of control combination is a function of + # membercontrol\othercontrol + # NA SH DE MA + # NA + - - - + # SH + + + + + # DE + - + + + # MA - - - + + unless (($newmembercontrol == $newothercontrol) + || ($newmembercontrol == CONTROLMAPSHOWN) + || (($newmembercontrol == CONTROLMAPDEFAULT) + && ($newothercontrol != CONTROLMAPSHOWN))) { + ThrowUserError('illegal_group_control_combination', + {groupname => $groupname, + header_done => 1}); + } + } + SendSQL("LOCK TABLES groups READ, + group_control_map WRITE, + bugs WRITE, + bugs_activity WRITE, + bug_group_map WRITE, + fielddefs READ"); + SendSQL("SELECT id, name, entry, membercontrol, othercontrol, canedit " . + "FROM groups " . + "LEFT JOIN group_control_map " . + "ON group_control_map.group_id = id AND product_id = $product_id " . + "WHERE isbuggroup != 0 AND isactive != 0"); + while (MoreSQLData()) { + my ($groupid, $groupname, $entry, $membercontrol, + $othercontrol, $canedit) = FetchSQLData(); + my $newentry = $::FORM{"entry_$groupid"} || 0; + my $newmembercontrol = $::FORM{"membercontrol_$groupid"} || 0; + my $newothercontrol = $::FORM{"othercontrol_$groupid"} || 0; + my $newcanedit = $::FORM{"canedit_$groupid"} || 0; + my $oldentry = $entry; + $entry = $entry || 0; + $membercontrol = $membercontrol || 0; + $othercontrol = $othercontrol || 0; + $canedit = $canedit || 0; + detaint_natural($newentry); + detaint_natural($newothercontrol); + detaint_natural($newmembercontrol); + detaint_natural($newcanedit); + if ((!defined($oldentry)) && + (($newentry) || ($newmembercontrol) || ($newcanedit))) { + PushGlobalSQLState(); + SendSQL("INSERT INTO group_control_map " . + "(group_id, product_id, entry, " . + "membercontrol, othercontrol, canedit) " . + "VALUES " . + "($groupid, $product_id, $newentry, " . + "$newmembercontrol, $newothercontrol, $newcanedit)"); + PopGlobalSQLState(); + } elsif (($newentry != $entry) + || ($newmembercontrol != $membercontrol) + || ($newothercontrol != $othercontrol) + || ($newcanedit != $canedit)) { + PushGlobalSQLState(); + SendSQL("UPDATE group_control_map " . + "SET entry = $newentry, " . + "membercontrol = $newmembercontrol, " . + "othercontrol = $newothercontrol, " . + "canedit = $newcanedit " . + "WHERE group_id = $groupid " . + "AND product_id = $product_id"); + PopGlobalSQLState(); + } + + if (($newentry == 0) && ($newmembercontrol == 0) + && ($newothercontrol == 0) && ($newcanedit == 0)) { + PushGlobalSQLState(); + SendSQL("DELETE FROM group_control_map " . + "WHERE group_id = $groupid " . + "AND product_id = $product_id"); + PopGlobalSQLState(); + } + } + + foreach my $groupid (@now_na) { + print "Removing bugs from NA group " + . html_quote(GroupIdToName($groupid)) . "<P>\n"; + my $count = 0; + SendSQL("SELECT bugs.bug_id, + (lastdiffed >= delta_ts) + FROM bugs, bug_group_map + WHERE group_id = $groupid + AND bug_group_map.bug_id = bugs.bug_id + AND bugs.product_id = $product_id + ORDER BY bugs.bug_id"); + while (MoreSQLData()) { + my ($bugid, $mailiscurrent) = FetchSQLData(); + PushGlobalSQLState(); + SendSQL("DELETE FROM bug_group_map WHERE + bug_id = $bugid AND group_id = $groupid"); + SendSQL("SELECT name, NOW() FROM groups WHERE id = $groupid"); + my ($removed, $timestamp) = FetchSQLData(); + LogActivityEntry($bugid, "bug_group", $removed, "", + $::userid, $timestamp); + if ($mailiscurrent != 0) { + SendSQL("UPDATE bugs SET lastdiffed = " . SqlQuote($timestamp) + . " WHERE bug_id = $bugid"); + } + SendSQL("UPDATE bugs SET delta_ts = " . SqlQuote($timestamp) + . " WHERE bug_id = $bugid"); + PopGlobalSQLState(); + $count++; + } + print "dropped $count bugs<p>\n"; + } + + foreach my $groupid (@now_mandatory) { + print "Adding bugs to Mandatory group " + . html_quote(GroupIdToName($groupid)) . "<P>\n"; + my $count = 0; + SendSQL("SELECT bugs.bug_id, + (lastdiffed >= delta_ts) + FROM bugs + LEFT JOIN bug_group_map + ON bug_group_map.bug_id = bugs.bug_id + AND group_id = $groupid + WHERE bugs.product_id = $product_id + AND bug_group_map.bug_id IS NULL + ORDER BY bugs.bug_id"); + while (MoreSQLData()) { + my ($bugid, $mailiscurrent) = FetchSQLData(); + PushGlobalSQLState(); + SendSQL("INSERT INTO bug_group_map (bug_id, group_id) + VALUES ($bugid, $groupid)"); + SendSQL("SELECT name, NOW() FROM groups WHERE id = $groupid"); + my ($added, $timestamp) = FetchSQLData(); + LogActivityEntry($bugid, "bug_group", "", $added, + $::userid, $timestamp); + if ($mailiscurrent != 0) { + SendSQL("UPDATE bugs SET lastdiffed = " . SqlQuote($timestamp) + . " WHERE bug_id = $bugid"); + } + SendSQL("UPDATE bugs SET delta_ts = " . SqlQuote($timestamp) + . " WHERE bug_id = $bugid"); + PopGlobalSQLState(); + $count++; + } + print "added $count bugs<p>\n"; + } + SendSQL("UNLOCK TABLES"); + print "Group control updates done<P>\n"; + + PutTrailer($localtrailer); + exit; +} # # action='update' -> update the product @@ -770,6 +1055,7 @@ if ($action eq 'update') { SendSQL("LOCK TABLES products WRITE, versions READ, groups WRITE, + group_control_map WRITE, profiles WRITE, milestones READ"); @@ -863,9 +1149,9 @@ if ($action eq 'update') { } SendSQL("UPDATE products SET name=$qp WHERE id=$product_id"); - # Need to do an update to groups as well. If there is a corresponding - # bug group, whether usebuggroups is currently set or not, we want to - # update it so it will match in the future. If there is no group, this + # Need to do an update to groups as well. If there is + # a corresponding bug group, we want to update it so it will + # match in the future. If there is no group, this # update statement will do nothing, so no harm done. -JMR, 3/8/00 SendSQL("UPDATE groups " . "SET name = $qp, " . @@ -945,6 +1231,235 @@ if ($action eq 'update') { exit; } +# +# action='editgroupcontrols' -> update product group controls +# + +if ($action eq 'editgroupcontrols') { + my $product_id = get_product_id($product); + # Display a group if it is either enabled or has bugs for this product. + SendSQL("SELECT id, name, entry, membercontrol, othercontrol, canedit, " . + "isactive, COUNT(bugs.bug_id) " . + "FROM groups " . + "LEFT JOIN group_control_map " . + "ON group_control_map.group_id = id " . + "AND group_control_map.product_id = $product_id " . + "LEFT JOIN bug_group_map " . + "ON bug_group_map.group_id = groups.id " . + "LEFT JOIN bugs " . + "ON bugs.bug_id = bug_group_map.bug_id " . + "AND bugs.product_id = $product_id " . + "WHERE isbuggroup != 0 " . + "AND (isactive != 0 OR entry IS NOT NULL " . + "OR bugs.bug_id IS NOT NULL) " . + "GROUP BY name"); + my @groups = (); + while (MoreSQLData()) { + my %group = (); + my ($groupid, $groupname, $entry, $membercontrol, $othercontrol, + $canedit, $isactive, $bugcount) = FetchSQLData(); + $group{'id'} = $groupid; + $group{'name'} = $groupname; + $group{'entry'} = $entry; + $group{'membercontrol'} = $membercontrol; + $group{'othercontrol'} = $othercontrol; + $group{'canedit'} = $canedit; + $group{'isactive'} = $isactive; + $group{'bugcount'} = $bugcount; + push @groups,\%group; + } + $vars->{'header_done'} = $headerdone; + $vars->{'product'} = $product; + $vars->{'groups'} = \@groups; + $vars->{'const'} = { + 'CONTROLMAPNA' => CONTROLMAPNA, + 'CONTROLMAPSHOWN' => CONTROLMAPSHOWN, + 'CONTROLMAPDEFAULT' => CONTROLMAPDEFAULT, + 'CONTROLMAPMANDATORY' => CONTROLMAPMANDATORY, + }; + + $template->process("admin/products/groupcontrol/edit.html.tmpl", $vars) + || ThrowTemplateError($template->error()); + exit; + + print "<!-- \n"; + print "<script type=\"text/javascript\">\n"; + print "function hide(id) {\n"; + print " id.visibility = 0\n"; + print " alert(id)\n"; + print "}\n"; + print "</script>"; + print " -->\n"; + print "<STYLE type=\"text/css\">\n"; + print " .hstyle { visibility: visible; color: red; }\n"; + print "</STYLE>\n"; + print "<FORM METHOD=POST ACTION=editproducts.cgi>\n"; + print "<TABLE BORDER=1 CELLPADDING=4 CELLSPACING=0><TR BGCOLOR=\"#6666FF\">\n"; + print " <TH ALIGN=\"left\">Group</TH>\n"; + print " <TH ALIGN=\"left\">Entry</TH>\n"; + print " <TH ALIGN=\"left\">MemberControl</TH>\n"; + print " <TH ALIGN=\"left\">OtherControl</TH>\n"; + print " <TH ALIGN=\"left\">Canedit</TH>\n"; + while (MoreSQLData()) { + print "</TR>\n"; + my ($groupid, $groupname, $entry, $membercontrol, $othercontrol, + $canedit) = FetchSQLData(); + print "<TR id=\"row_$groupname\" class=\"hstyle\" "; + print "onload=\"document.row.row_$groupname.color=green\">\n"; + print " <TD>\n"; + print " $groupname\n"; + print " </TD><TD>\n"; + $entry |= 0; + print " <INPUT TYPE=CHECKBOX NAME=\"entry_$groupid\" VALUE=1"; + print " CHECKED " if $entry; + print ">\n"; + print " </TD><TD>\n"; + $membercontrol |= 0; + $othercontrol |= 0; + print " <SELECT NAME=\"membercontrol_$groupid\">\n"; + print " <OPTION VALUE=" . CONTROLMAPNA; + print " selected=\"selected\"" if ($membercontrol == CONTROLMAPNA); + print ">NA</OPTION>\n"; + print " <OPTION VALUE=" . CONTROLMAPSHOWN; + print " selected=\"selected\"" if ($membercontrol == CONTROLMAPSHOWN); + print ">Shown</OPTION>\n"; + print " <OPTION VALUE=" . CONTROLMAPDEFAULT; + print " selected=\"selected\"" if ($membercontrol == CONTROLMAPDEFAULT); + print ">Default</OPTION>\n"; + print " <OPTION VALUE=" . CONTROLMAPMANDATORY; + print " selected=\"selected\"" if ($membercontrol == CONTROLMAPMANDATORY); + print ">Mandatory</OPTION>\n"; + print "</SELECT>\n"; + print " </TD><TD>\n"; + print " <SELECT NAME=\"othercontrol_$groupid\">\n"; + print " <OPTION VALUE=" . CONTROLMAPNA; + print " selected=\"selected\"" if ($othercontrol == CONTROLMAPNA); + print ">NA</OPTION>\n"; + print " <OPTION VALUE=" . CONTROLMAPSHOWN; + print " selected=\"selected\"" if ($othercontrol == CONTROLMAPSHOWN); + print ">Shown</OPTION>\n"; + print " <OPTION VALUE=" . CONTROLMAPDEFAULT; + print " selected=\"selected\"" if ($othercontrol == CONTROLMAPDEFAULT); + print ">Default</OPTION>\n"; + print " <OPTION VALUE=" . CONTROLMAPMANDATORY; + print " selected=\"selected\"" if ($othercontrol == CONTROLMAPMANDATORY); + print ">Mandatory</OPTION>\n"; + print "</SELECT>\n"; + print " </TD><TD>\n"; + $canedit |= 0; + print " <INPUT TYPE=CHECKBOX NAME=\"canedit_$groupid\" VALUE=1"; + print " CHECKED " if $canedit; + print ">\n"; + + } + + print "</TR>\n"; + print "</TABLE><BR>"; + print "Add controls to the panel above:<BR>\n"; + print "<SELECT NAME=\"newgroups\" SIZE=\"10\" MULTIPLE=\"MULTIPLE\">\n"; + SendSQL("SELECT id, name " . + "FROM groups " . + "LEFT JOIN group_control_map " . + "ON group_control_map.group_id = id AND product_id = $product_id " . + "WHERE canedit IS NULL AND isbuggroup != 0 AND isactive != 0 " . + "ORDER BY name"); + while (MoreSQLData()) { + my ($groupid, $groupname) = FetchSQLData(); + print "<OPTION VALUE=\"$groupid\">$groupname</OPTION>\n"; + } + print "</SELECT><BR><BR>\n"; + + print "<INPUT TYPE=SUBMIT VALUE=\"Update\">\n"; + print "<INPUT TYPE=RESET>\n"; + print "<INPUT TYPE=HIDDEN NAME=\"action\" VALUE=\"updategroupcontrols\">\n"; + print "<INPUT TYPE=HIDDEN NAME=\"product\" VALUE=\"$product\">\n"; + print "</FORM>\n"; + print "<P>note: Any group controls Set to NA/NA with no other checkboxes "; + print "will automatically be removed from the panel the next time "; + print "update is clicked.\n"; + print "<P>These settings control the relationship of the groups to this "; + print "product.\n"; + print "<P>If any group has <B>Entry</B> selected, then this product will "; + print "restrict bug entry to only those users who are members of all the "; + print "groups with entry selected.\n"; + print "<P>If any group has <B>Canedit</B> selected, then this product "; + print "will be read-only for any users who are not members of all of "; + print "the groups with Canedit selected. ONLY users who are members of "; + print "all the canedit groups will be able to edit. This is an additional "; + print "restriction that further restricts what can be edited by a user.\n"; + print "<P>The <B>MemberControl</B> and <B>OtherControl</B> fields "; + print "indicate which bugs will be placed in "; + print "this group according to the following definitions.\n"; + print "<BR><TABLE BORDER=1>"; + print "<TR>"; + print "<TH>MemberControl</TH><TH>OtherControl</TH><TH>Interpretation</TH>"; + print "</TR><TR>"; + print "<TD>NA</TD>\n"; + print "<TD>NA</TD>\n"; + print "<TD>Bugs in this product are never associated with this group.</TD>\n"; + print "</TR><TR>"; + print "<TD>Shown</TD>\n"; + print "<TD>NA</TD>\n"; + print "<TD>Bugs in this product are permitted to be restricted to this "; + print "group. Users who are a member of this group will be able "; + print "to place bugs in this group.</TD>\n"; + print "</TR><TR>"; + print "<TD>Shown</TD>\n"; + print "<TD>Shown</TD>\n"; + print "<TD>Bugs in this product can be placed in this group by anyone "; + print "with permission to edit the bug even if they are not a member "; + print "of this group.</TD>\n"; + print "</TR><TR>"; + print "<TD>Shown</TD>\n"; + print "<TD>Default</TD>\n"; + print "<TD>Bugs in this product can be placed in this group by anyone "; + print "with permission to edit the bug even if they are not a member "; + print "of this group. Non-members place bugs in this group by default."; + print "</TD>\n"; + print "</TR><TR>"; + print "<TD>Shown</TD>\n"; + print "<TD>Mandatory</TD>\n"; + print "<TD>Bugs in this product are permitted to be restricted to this "; + print "group. Users who are a member of this group will be able "; + print "to place bugs in this group."; + print "Non-members will be forced to restrict bugs to this group "; + print "when they initially enter a bug in this product."; + print "</TD>\n"; + print "</TR><TR>"; + print "<TD>Default</TD>\n"; + print "<TD>NA</TD>\n"; + print "<TD>Bugs in this product are permitted to be restricted to this "; + print "group and are placed in this group by default."; + print "Users who are a member of this group will be able "; + print "to place bugs in this group.</TD>\n"; + print "</TR><TR>"; + print "<TD>Default</TD>\n"; + print "<TD>Default</TD>\n"; + print "<TD>Bugs in this product are permitted to be restricted to this "; + print "group and are placed in this group by default."; + print "Users who are a member of this group will be able "; + print "to place bugs in this group. Non-members will be able to "; + print "restrict bugs to this group on entry and will do so by default "; + print "</TD>\n"; + print "</TR><TR>"; + print "<TD>Default</TD>\n"; + print "<TD>Mandatory</TD>\n"; + print "<TD>Bugs in this product are permitted to be restricted to this "; + print "group and are placed in this group by default."; + print "Users who are a member of this group will be able "; + print "to place bugs in this group. Non-members will be forced "; + print "to place bugs in this group on entry."; + print "</TR><TR>"; + print "<TD>Mandatory</TD>\n"; + print "<TD>Mandatory</TD>\n"; + print "<TD>Bugs in this product are required to be restricted to this "; + print "group. Users are not given any option.</TD>\n"; + print "</TABLE>"; + + + PutTrailer($localtrailer); + exit; +} # diff --git a/enter_bug.cgi b/enter_bug.cgi index 5492bb14a..162431ecd 100755 --- a/enter_bug.cgi +++ b/enter_bug.cgi @@ -36,6 +36,7 @@ use strict; use lib qw(.); +use Bugzilla::Constants; require "CGI.pl"; use vars qw( @@ -51,6 +52,7 @@ use vars qw( $userid %MFORM %versions + $proddesc ); # We have to connect to the database, even though we don't use it in this code, @@ -60,7 +62,7 @@ ConnectToDatabase(); # If we're using bug groups to restrict bug entry, we need to know who the # user is right from the start. -confirm_login() if (Param("usebuggroupsentry")); +confirm_login() if AnyEntryGroups(); if (!defined $::FORM{'product'}) { GetVersionTable(); @@ -69,9 +71,7 @@ if (!defined $::FORM{'product'}) { my %products; foreach my $p (@enterable_products) { - if (!(Param("usebuggroupsentry") - && GroupExists($p) - && !UserInGroup($p))) + if (CanEnterProduct($p)) { $products{$p} = $::proddesc{$p}; } @@ -215,13 +215,11 @@ sub pickos { # End of subroutines ############################################################################## -confirm_login() if (!(Param("usebuggroupsentry"))); +confirm_login() if (!(AnyEntryGroups())); -# If the usebuggroupsentry parameter is set, we need to check and make sure +# We need to check and make sure # that the user has permission to enter a bug against this product. -if(Param("usebuggroupsentry") - && GroupExists($product) - && !UserInGroup($product)) +if(!CanEnterProduct($product)) { ThrowUserError("entry_access_denied", { product => $product}); } @@ -309,30 +307,25 @@ if (UserInGroup("editbugs") || UserInGroup("canconfirm")) { $vars->{'bug_status'} = \@status; $default{'bug_status'} = $status[0]; -# Select whether to restrict this bug to the product's bug group or not, -# if the usebuggroups parameter is set, and if this product has a bug group. -# First we get the bit and description for the group. -my $group_id = '0'; - -if(Param("usebuggroups")) { - ($group_id) = GroupExists($product); -} - -SendSQL("SELECT DISTINCT groups.id, groups.name, groups.description " . - "FROM groups, user_group_map " . - "WHERE user_group_map.group_id = groups.id " . - "AND user_group_map.user_id = $::userid " . - "AND isbless = 0 " . - "AND isbuggroup = 1 AND isactive = 1 ORDER BY description"); +SendSQL("SELECT DISTINCT groups.id, groups.name, groups.description, " . + "membercontrol, othercontrol " . + "FROM groups LEFT JOIN group_control_map " . + "ON group_id = id AND product_id = $product_id " . + "WHERE isbuggroup != 0 AND isactive != 0 ORDER BY description"); my @groups; while (MoreSQLData()) { - my ($id, $prodname, $description) = FetchSQLData(); - # Don't want to include product groups other than this product. - next unless(!Param("usebuggroups") || $prodname eq $product || - !defined($::proddesc{$prodname})); - + my ($id, $groupname, $description, $membercontrol, $othercontrol) + = FetchSQLData(); + # Only include groups if the entering user will have an option. + next if ((!$membercontrol) + || ($membercontrol == CONTROLMAPNA) + || ($membercontrol == CONTROLMAPMANDATORY) + || (($othercontrol != CONTROLMAPSHOWN) + && ($othercontrol != CONTROLMAPDEFAULT) + && (!UserInGroup($groupname))) + ); my $check; # If this is the group for this product, make it checked. @@ -343,11 +336,10 @@ while (MoreSQLData()) { $check = formvalue("bit-$id", 0); } else { - # $group_bit will only have a non-zero value if we're using - # bug groups and have one for this product. - # If $group_bit is 0, it won't match the current group, so compare - # it to the current bit instead of checking for non-zero. - $check = ($group_id == $id); + # Checkbox is checked by default if $control is a default state. + $check = (($membercontrol == CONTROLMAPDEFAULT) + || (($othercontrol == CONTROLMAPDEFAULT) + && (!UserInGroup($groupname)))); } my $group = diff --git a/globals.pl b/globals.pl index 7eef23115..9d4372d00 100644 --- a/globals.pl +++ b/globals.pl @@ -28,6 +28,7 @@ use strict; +use Bugzilla::Constants; use Bugzilla::Util; # Bring ChmodDataFile in until this is all moved to the module use Bugzilla::Config qw(:DEFAULT ChmodDataFile); @@ -684,6 +685,116 @@ sub GenerateRandomPassword { return $password; } +# +# This function checks if there are any entry groups defined. +# If called with no arguments, it identifies +# entry groups for all products. If called with a product +# id argument, it checks for entry groups associated with +# one particular product. +sub AnyEntryGroups { + my $product_id = shift; + $product_id = 0 unless ($product_id); + return $::CachedAnyEntryGroups{$product_id} + if defined($::CachedAnyEntryGroups{$product_id}); + PushGlobalSQLState(); + my $query = "SELECT 1 FROM group_control_map WHERE entry != 0"; + $query .= " AND product_id = $product_id" if ($product_id); + $query .= " LIMIT 1"; + SendSQL($query); + $::CachedAnyEntryGroups{$product_id} = MoreSQLData(); + FetchSQLData(); + PopGlobalSQLState(); + return $::CachedAnyEntryGroups{$product_id}; +} + +# +# This function checks if there are any default groups defined. +# If so, then groups may have to be changed when bugs move from +# one bug to another. +sub AnyDefaultGroups { + return $::CachedAnyDefaultGroups if defined($::CachedAnyDefaultGroups); + PushGlobalSQLState(); + SendSQL("SELECT 1 FROM group_control_map, groups WHERE " . + "groups.id = group_control_map.group_id " . + "AND isactive != 0 AND " . + "(membercontrol = " . CONTROLMAPDEFAULT . + " OR othercontrol = " . CONTROLMAPDEFAULT . + ") LIMIT 1"); + $::CachedAnyDefaultGroups = MoreSQLData(); + FetchSQLData(); + PopGlobalSQLState(); + return $::CachedAnyDefaultGroups; +} + +# +# This function checks if, given a product id, the user can edit +# bugs in this product at all. +sub CanEditProductId { + my ($productid) = @_; + my $query = "SELECT group_id FROM group_control_map " . + "WHERE product_id = $productid " . + "AND canedit != 0 "; + if ((defined @{$::vars->{user}{groupids}}) + && (@{$::vars->{user}{groupids}} > 0)) { + $query .= "AND group_id NOT IN(" . + join(',',@{$::vars->{user}{groupids}}) . ") "; + } + $query .= "LIMIT 1"; + PushGlobalSQLState(); + SendSQL($query); + my ($result) = FetchSQLData(); + PopGlobalSQLState(); + return (!defined($result)); +} + +# +# This function determines if a user can enter bugs in the named +# product. +sub CanEnterProduct { + my ($productname) = @_; + my $query = "SELECT group_id IS NULL " . + "FROM products " . + "LEFT JOIN group_control_map " . + "ON group_control_map.product_id = products.id " . + "AND group_control_map.entry != 0 "; + if ((defined @{$::vars->{user}{groupids}}) + && (@{$::vars->{user}{groupids}} > 0)) { + $query .= "AND group_id NOT IN(" . + join(',',@{$::vars->{user}{groupids}}) . ") "; + } + $query .= "WHERE products.name = " . SqlQuote($productname) . " LIMIT 1"; + PushGlobalSQLState(); + SendSQL($query); + my ($ret) = FetchSQLData(); + PopGlobalSQLState(); + return ($ret); +} + +# +# This function returns an alphabetical list of product names to which +# the user can enter bugs. +sub GetEnterableProducts { + my $query = "SELECT name " . + "FROM products " . + "LEFT JOIN group_control_map " . + "ON group_control_map.product_id = products.id " . + "AND group_control_map.entry != 0 "; + if ((defined @{$::vars->{user}{groupids}}) + && (@{$::vars->{user}{groupids}} > 0)) { + $query .= "AND group_id NOT IN(" . + join(',',@{$::vars->{user}{groupids}}) . ") "; + } + $query .= "WHERE group_id IS NULL ORDER BY name"; + PushGlobalSQLState(); + SendSQL($query); + my @products = (); + while (MoreSQLData()) { + push @products,FetchOneColumn(); + } + PopGlobalSQLState(); + return (@products); +} + sub CanSeeBug { my ($id, $userid) = @_; @@ -1749,5 +1860,5 @@ $::vars = 'VERSION' => $Bugzilla::Config::VERSION, }; - 1; + diff --git a/post_bug.cgi b/post_bug.cgi index 4cacb271c..3ddfbe689 100755 --- a/post_bug.cgi +++ b/post_bug.cgi @@ -26,6 +26,7 @@ use strict; use lib qw(.); +use Bugzilla::Constants; require "CGI.pl"; require "bug_form.pl"; @@ -101,9 +102,8 @@ if (defined $::FORM{'maketemplate'}) { umask 0; # Some sanity checking -if(Param("usebuggroupsentry") && GroupExists($product)) { - UserInGroup($product) || - ThrowUserError("entry_access_denied", {product => $product}); +if (!CanEnterProduct($product)) { + ThrowUserError("entry_access_denied", {product => $product}); } my $component_id = get_component_id($product_id, $::FORM{component}); @@ -363,13 +363,38 @@ foreach my $b (grep(/^bit-\d*$/, keys %::FORM)) { WHERE user_id = $::userid AND group_id = $v AND isbless = 0"); - my ($member) = FetchSQLData(); - if ($member) { + my ($permit) = FetchSQLData(); + if (!$permit) { + SendSQL("SELECT othercontrol FROM group_control_map + WHERE group_id = $v AND product_id = $product_id"); + my ($othercontrol) = FetchSQLData(); + $permit = (($othercontrol == CONTROLMAPSHOWN) + || ($othercontrol == CONTROLMAPDEFAULT)); + } + if ($permit) { push(@groupstoadd, $v) } } } +SendSQL("SELECT DISTINCT groups.id, groups.name, " . + "membercontrol, othercontrol " . + "FROM groups LEFT JOIN group_control_map " . + "ON group_id = id AND product_id = $product_id " . + " WHERE isbuggroup != 0 AND isactive != 0 ORDER BY description"); +while (MoreSQLData()) { + my ($id, $groupname, $membercontrol, $othercontrol ) = FetchSQLData(); + $membercontrol ||= 0; + $othercontrol ||= 0; + # Add groups required + if (($membercontrol == CONTROLMAPMANDATORY) + || (($othercontrol == CONTROLMAPMANDATORY) + && (!UserInGroup($groupname)))) { + # User had no option, bug needs to be in this group. + push(@groupstoadd, $id) + } +} + # Lock tables before inserting records for the new bug into the database # if we are using a shadow database to prevent shadow database corruption diff --git a/process_bug.cgi b/process_bug.cgi index 31638b343..a59e439dc 100755 --- a/process_bug.cgi +++ b/process_bug.cgi @@ -31,6 +31,7 @@ my $UserInCanConfirmGroupSet = -1; use lib qw(.); +use Bugzilla::Constants; require "CGI.pl"; require "bug_form.pl"; @@ -236,7 +237,7 @@ if ((($::FORM{'id'} && $::FORM{'product'} ne $::oldproduct) # If the product-specific fields need to be verified, or we need to verify # whether or not to add the bugs to their new product's group, display # a verification form. - if (!$vok || !$cok || !$mok || (Param('usebuggroups') && !defined($::FORM{'addtonewgroup'}))) { + if (!$vok || !$cok || !$mok || (AnyDefaultGroups() && !defined($::FORM{'addtonewgroup'}))) { $vars->{'form'} = \%::FORM; $vars->{'mform'} = \%::MFORM; @@ -276,7 +277,7 @@ if ((($::FORM{'id'} && $::FORM{'product'} ne $::oldproduct) $vars->{"verify_fields"} = 0; } - $vars->{'verify_bug_group'} = (Param('usebuggroups') + $vars->{'verify_bug_group'} = (AnyDefaultGroups() && !defined($::FORM{'addtonewgroup'})); $template->process("bug/process/verify-new-product.html.tmpl", $vars) @@ -594,11 +595,9 @@ sub ChangeResolution { # select lists. This means that instead of looking for the bit-X values in # the form, we need to loop through all the bug groups this user has access # to, and for each one, see if it's selected. -# In order to make mass changes work correctly, keep a sum of bits for groups -# added, and another one for groups removed, and then let mysql do the bit -# operations # If the form element isn't present, or the user isn't in the group, leave # it as-is + my @groupAdd = (); my @groupDel = (); @@ -1070,7 +1069,10 @@ foreach my $id (@idlist) { "bug_group_map $write, flags $write, duplicates $write," . "user_group_map READ, flagtypes READ, " . "flaginclusions AS i READ, flagexclusions AS e READ, " . - "keyworddefs READ, groups READ, attachments READ"); + "keyworddefs READ, groups READ, attachments READ, " . + "group_control_map AS oldcontrolmap READ, " . + "group_control_map AS newcontrolmap READ, " . + "group_control_map READ"); my @oldvalues = SnapShotBug($id); my %oldhash; # Fun hack. @::log_columns only contains the component_id, @@ -1104,6 +1106,18 @@ foreach my $id (@idlist) { $i++; } $oldhash{'product'} = get_product_name($oldhash{'product_id'}); + if (!CanEditProductId($oldhash{'product_id'})) { + $vars->{'product'} = $oldhash{'product'}; + ThrowUserError("product_edit_denied"); + } + + if (defined $::FORM{'product'} + && $::FORM{'product'} ne $::FORM{'dontchange'} + && $::FORM{'product'} ne $oldhash{'product'} + && !CanEnterProduct($::FORM{'product'})) { + $vars->{'product'} = $::FORM{'product'}; + ThrowUserError("entry_access_denied"); + } if ($requiremilestone) { my $value = $::FORM{'target_milestone'}; if (!defined $value || $value eq $::FORM{'dontchange'}) { @@ -1268,6 +1282,7 @@ foreach my $id (@idlist) { if ($::comma ne "") { SendSQL($query); } + # Check for duplicates if the bug is [re]open SendSQL("SELECT resolution FROM bugs WHERE bug_id = $id"); my $resolution = FetchOneColumn(); @@ -1275,8 +1290,34 @@ foreach my $id (@idlist) { SendSQL("DELETE FROM duplicates WHERE dupe = $id"); } + my $newproduct_id = $oldhash{'product_id'}; + if ((defined $::FORM{'product'}) + && ($::FORM{'product'} ne $::FORM{'dontchange'})) { + my $newproduct_id = get_product_id($::FORM{'product'}); + } + + my %groupsrequired = (); + my %groupsforbidden = (); + SendSQL("SELECT id, membercontrol + FROM groups LEFT JOIN group_control_map + ON id = group_id + AND product_id = $newproduct_id WHERE isactive != 0"); + while (MoreSQLData()) { + my ($group, $control) = FetchSQLData(); + $control ||= 0; + unless ($control > &::CONTROLMAPNA) { + $groupsforbidden{$group} = 1; + } + if ($control == &::CONTROLMAPMANDATORY) { + $groupsrequired{$group} = 1; + } + } + my @groupAddNames = (); - foreach my $grouptoadd (@groupAdd) { + my @groupAddNamesAll = (); + foreach my $grouptoadd (@groupAdd, keys %groupsrequired) { + next if $groupsforbidden{$grouptoadd}; + push(@groupAddNamesAll, GroupIdToName($grouptoadd)); if (!BugInGroupId($id, $grouptoadd)) { push(@groupAddNames, GroupIdToName($grouptoadd)); SendSQL("INSERT INTO bug_group_map (bug_id, group_id) @@ -1284,7 +1325,10 @@ foreach my $id (@idlist) { } } my @groupDelNames = (); - foreach my $grouptodel (@groupDel) { + my @groupDelNamesAll = (); + foreach my $grouptodel (@groupDel, keys %groupsforbidden) { + push(@groupDelNamesAll, GroupIdToName($grouptodel)); + next if $groupsrequired{$grouptodel}; if (BugInGroupId($id, $grouptodel)) { push(@groupDelNames, GroupIdToName($grouptodel)); } @@ -1399,63 +1443,117 @@ foreach my $id (@idlist) { # group or add it to the new one. There are a very specific series of # conditions under which these activities take place, more information # about which can be found in comments within the conditionals below. + # Check if the user has changed the product to which the bug belongs; if ( - # the "usebuggroups" parameter is on, indicating that products - # are associated with groups of the same name; - Param('usebuggroups') - - # the user has changed the product to which the bug belongs; - && defined $::FORM{'product'} + defined $::FORM{'product'} && $::FORM{'product'} ne $::FORM{'dontchange'} && $::FORM{'product'} ne $oldhash{'product'} ) { - if ( - # the user wants to add the bug to the new product's group; - ($::FORM{'addtonewgroup'} eq 'yes' - || ($::FORM{'addtonewgroup'} eq 'yesifinold' - && BugInGroup($id, $oldhash{'product'}))) - - # the new product is associated with a group; - && GroupExists($::FORM{'product'}) - - # the bug is not already in the group; (This can happen when the user - # goes to the "edit multiple bugs" form with a list of bugs at least - # one of which is in the new group. In this situation, the user can - # simultaneously change the bugs to a new product and move the bugs - # into that product's group, which happens earlier in this script - # and thus is already done. If we didn't check for this, then this - # situation would cause us to add the bug to the group twice, which - # would result in the bug being added to a totally different group.) - && !BugInGroup($id, $::FORM{'product'}) - - # the user is a member of the associated group, indicating they - # are authorized to add bugs to that group, *or* the "usebuggroupsentry" - # parameter is off, indicating that users can add bugs to a product - # regardless of whether or not they belong to its associated group; - && (UserInGroup($::FORM{'product'}) || !Param('usebuggroupsentry')) - - # the associated group is active, indicating it can accept new bugs; - && GroupIsActive(GroupNameToId($::FORM{'product'})) - ) { - # Add the bug to the group associated with its new product. - my $groupid = GroupNameToId($::FORM{'product'}); - if (!BugInGroupId($id, $groupid)) { - SendSQL("INSERT INTO bug_group_map (bug_id, group_id) VALUES ($id, $groupid)"); + my $newproduct_id = get_product_id($::FORM{'product'}); + # Depending on the "addtonewgroup" variable, groups with + # defaults will change. + # + # For each group, determine + # - The group id and if it is active + # - The control map value for the old product and this group + # - The control map value for the new product and this group + # - Is the user in this group? + # - Is the bug in this group? + SendSQL("SELECT DISTINCT groups.id, isactive, " . + "oldcontrolmap.membercontrol, newcontrolmap.membercontrol, " . + "user_group_map.user_id IS NOT NULL, " . + "bug_group_map.group_id IS NOT NULL " . + "FROM groups " . + "LEFT JOIN group_control_map AS oldcontrolmap " . + "ON oldcontrolmap.group_id = groups.id " . + "AND oldcontrolmap.product_id = " . $oldhash{'product_id'} . + " LEFT JOIN group_control_map AS newcontrolmap " . + "ON newcontrolmap.group_id = groups.id " . + "AND newcontrolmap.product_id = $newproduct_id " . + "LEFT JOIN user_group_map " . + "ON user_group_map.group_id = groups.id " . + "AND user_group_map.user_id = $::userid " . + "AND user_group_map.isbless = 0 " . + "LEFT JOIN bug_group_map " . + "ON bug_group_map.group_id = groups.id " . + "AND bug_group_map.bug_id = $id " + ); + my @groupstoremove = (); + my @groupstoadd = (); + my @defaultstoremove = (); + my @defaultstoadd = (); + my @allgroups = (); + my $buginanydefault = 0; + my $buginanychangingdefault = 0; + while (MoreSQLData()) { + my ($groupid, $isactive, $oldcontrol, $newcontrol, + $useringroup, $bugingroup) = FetchSQLData(); + # An undefined newcontrol is none. + $newcontrol = CONTROLMAPNA unless $newcontrol; + $oldcontrol = CONTROLMAPNA unless $oldcontrol; + push(@allgroups, $groupid); + if (($bugingroup) && ($isactive) + && ($oldcontrol == CONTROLMAPDEFAULT)) { + # Bug was in a default group. + $buginanydefault = 1; + if ($newcontrol != CONTROLMAPDEFAULT) { + # Bug was in a default group that no longer is. + $buginanychangingdefault = 1; + push (@defaultstoremove, $groupid); + } + } + if (($isactive) && (!$bugingroup) + && ($newcontrol == CONTROLMAPDEFAULT) + && ($useringroup)) { + push (@defaultstoadd, $groupid); + } + if (($bugingroup) && ($isactive) && ($newcontrol == CONTROLMAPNA)) { + # Group is no longer permitted. + push(@groupstoremove, $groupid); + } + if ((!$bugingroup) && ($isactive) + && ($newcontrol == CONTROLMAPMANDATORY)) { + # Group is now required. + push(@groupstoadd, $groupid); } } - - if ( - # the old product is associated with a group; - GroupExists($oldhash{'product'}) - - # the bug is a member of that group; - && BugInGroup($id, $oldhash{'product'}) - ) { - # Remove the bug from the group associated with its old product. - my $groupid = GroupNameToId($oldhash{'product'}); - SendSQL("DELETE FROM bug_group_map WHERE bug_id = $id AND group_id = $groupid"); + # If addtonewgroups = "yes", old default groups will be removed + # and new default groups will be added. + # If addtonewgroups = "yesifinold", old default groups will be removed + # and new default groups will be added only if the bug was in ANY + # of the old default groups. + # If addtonewgroups = "no", old default groups will be removed and not + # replaced. + push(@groupstoremove, @defaultstoremove); + if (AnyDefaultGroups() + && (($::FORM{'addtonewgroup'} eq 'yes') + || (($::FORM{'addtonewgroup'} eq 'yesifinold') + && ($buginanydefault)))) { + push(@groupstoadd, @defaultstoadd); } + # Now actually update the bug_group_map. + my @DefGroupsAdded = (); + my @DefGroupsRemoved = (); + foreach my $groupid (@allgroups) { + my $thisadd = grep( ($_ == $groupid), @groupstoadd); + my $thisdel = grep( ($_ == $groupid), @groupstoremove); + if ($thisadd) { + push(@DefGroupsAdded, GroupIdToName($groupid)); + SendSQL("INSERT INTO bug_group_map (bug_id, group_id) VALUES " . + "($id, $groupid)"); + } elsif ($thisdel) { + push(@DefGroupsRemoved, GroupIdToName($groupid)); + SendSQL("DELETE FROM bug_group_map WHERE bug_id = $id " . + "AND group_id = $groupid"); + } + } + if ((@DefGroupsAdded) || (@DefGroupsRemoved)) { + LogActivityEntry($id, "bug_group", + join(', ', @DefGroupsRemoved), + join(', ', @DefGroupsAdded), + $whoid, $timestamp); + } } # get a snapshot of the newly set values out of the database, @@ -1471,7 +1569,6 @@ foreach my $id (@idlist) { $newhash{$col} = $newvalues[$i]; $i++; } - # for passing to processmail to ensure that when someone is removed # from one of these fields, they get notified of that fact (if desired) # @@ -191,19 +191,14 @@ if ($default{'chfieldto'}->[0] eq "") { GetVersionTable(); -# if using usebuggroups, then we don't want people to see products they don't -# have access to. Remove them from the list. +# if using groups for entry, then we don't want people to see products they +# don't have access to. Remove them from the list. my @products = (); my %component_set; my %version_set; my %milestone_set; -foreach my $p (@::legal_product) { - # If we're using bug groups to restrict entry on products, and - # this product has a bug group, and the user is not in that - # group, we don't want to include that product in this list. - next if (Param("usebuggroups") && GroupExists($p) && !UserInGroup($p)); - +foreach my $p (GetEnterableProducts()) { # We build up boolean hashes in the "-set" hashes for each of these things # before making a list because there may be duplicates names across products. push @products, $p; diff --git a/queryhelp.cgi b/queryhelp.cgi index bfd7c0f69..eb9893bd8 100755 --- a/queryhelp.cgi +++ b/queryhelp.cgi @@ -663,7 +663,7 @@ SendSQL("SELECT name, description FROM products ORDER BY name"); while (MoreSQLData()) { my ($product, $productdesc) = FetchSQLData(); - next if (Param("usebuggroups") && GroupExists($product) && !UserInGroup($product)); + next if (!CanEnterProduct($product)); push (@products, $product); $line_count++; diff --git a/reports.cgi b/reports.cgi index f3a18f557..67b175a5e 100755 --- a/reports.cgi +++ b/reports.cgi @@ -23,8 +23,6 @@ # Dawn Endico <endico@mozilla.org> # Bryce Nesbitt <bryce@nextbus.COM>, # Joe Robins <jmrobins@tgix.com>, -# If using the usebuggroups parameter, users shouldn't be able to see -# reports for products they don't have access to. # Gervase Markham <gerv@gerv.net> and Adam Spiers <adam@spiers.net> # Added ability to chart any combination of resolutions/statuses. # Derive the choice of resolutions/statuses from the -All- data file @@ -60,20 +58,11 @@ quietly_check_login(); GetVersionTable(); -# If the usebuggroups parameter is set, we don't want to list all products. # We only want those products that the user has permissions for. my @myproducts; -if(Param("usebuggroups")) { - push( @myproducts, "-All-"); - foreach my $this_product (@legal_product) { - if(GroupExists($this_product) && !UserInGroup($this_product)) { - next; - } else { - push( @myproducts, $this_product ) - } - } -} else { - push( @myproducts, "-All-", @legal_product ); +push( @myproducts, "-All-"); +foreach my $this_product (@legal_product) { + push(@myproducts, $this_product) if CanEnterProduct($this_product); } if (! defined $FORM{'product'}) { @@ -91,12 +80,11 @@ if (! defined $FORM{'product'}) { grep($_ eq $FORM{'product'}, @myproducts) || ThrowUserError("invalid_product_name", {product => $FORM{'product'}}); - # If usebuggroups is on, we don't want people to be able to view + # We don't want people to be able to view # reports for products they don't have permissions for... - Param("usebuggroups") - && GroupExists($FORM{'product'}) - && !UserInGroup($FORM{'product'}) - && ThrowUserError("report_access_denied"); + if (!CanEnterProduct($FORM{'product'})) { + ThrowUserError("report_access_denied"); + } # We've checked that the product exists, and that the user can see it # This means that is OK to detaint diff --git a/sanitycheck.cgi b/sanitycheck.cgi index 8977ce3b5..49c007f7f 100755 --- a/sanitycheck.cgi +++ b/sanitycheck.cgi @@ -26,6 +26,7 @@ use strict; use lib qw(.); require "CGI.pl"; +use Bugzilla::Constants; use vars qw(%FORM $unconfirmedstate); @@ -263,6 +264,7 @@ CrossCheck("groups", "id", ["bug_group_map", "group_id"], ["group_group_map", "grantor_id"], ["group_group_map", "member_id"], + ["group_control_map", "group_id"], ["user_group_map", "group_id"]); CrossCheck("profiles", "userid", @@ -288,6 +290,7 @@ CrossCheck("products", "id", ["components", "product_id", "name"], ["milestones", "product_id", "value"], ["versions", "product_id", "value"], + ["group_control_map", "product_id"], ["flaginclusions", "product_id", "type_id"], ["flagexclusions", "product_id", "type_id"]); @@ -613,6 +616,53 @@ DateCheck("groups", "last_changed"); DateCheck("profiles", "refreshed_when"); ########################################################################### +# Control Values +########################################################################### + +# Checks for values that are invalid OR +# not among the 9 valid combinations +Status("Checking for bad values in group_control_map"); +SendSQL("SELECT COUNT(product_id) FROM group_control_map WHERE " . + "membercontrol NOT IN(" . CONTROLMAPNA . "," . CONTROLMAPSHOWN . + "," . CONTROLMAPDEFAULT . "," . CONTROLMAPMANDATORY . ")" . + " OR " . + "othercontrol NOT IN(" . CONTROLMAPNA . "," . CONTROLMAPSHOWN . + "," . CONTROLMAPDEFAULT . "," . CONTROLMAPMANDATORY . ")" . + " OR " . + "( (membercontrol != othercontrol) " . + "AND (membercontrol != " . CONTROLMAPSHOWN . ") " . + "AND ((membercontrol != " . CONTROLMAPDEFAULT . ") " . + "OR (othercontrol = " . CONTROLMAPSHOWN . ")))"); +my $c = FetchOneColumn(); +if ($c) { + Alert("Found $c bad group_control_map entries"); +} + +Status("Checking for bugs with groups violating their product's group controls"); +BugCheck("bugs, groups, bug_group_map + LEFT JOIN group_control_map + ON group_control_map.product_id = bugs.product_id + AND group_control_map.group_id = bug_group_map.group_id + WHERE bugs.bug_id = bug_group_map.bug_id + AND bug_group_map.group_id = groups.id + AND groups.isactive != 0 + AND ((group_control_map.membercontrol = " . CONTROLMAPNA . ") + OR (group_control_map.membercontrol IS NULL))", + "Have groups not permitted for their products"); + +BugCheck("bugs, groups, group_control_map + LEFT JOIN bug_group_map + ON group_control_map.group_id = bug_group_map.group_id + AND bugs.bug_id = bug_group_map.bug_id + WHERE group_control_map.product_id = bugs.product_id + AND bug_group_map.group_id = groups.id + AND groups.isactive != 0 + AND group_control_map.membercontrol = " . CONTROLMAPMANDATORY . " + AND bug_group_map.group_id IS NULL", + "Are missing groups required for their products"); + + +########################################################################### # Unsent mail ########################################################################### diff --git a/template/en/default/admin/products/groupcontrol/confirm-edit.html.tmpl b/template/en/default/admin/products/groupcontrol/confirm-edit.html.tmpl new file mode 100644 index 000000000..85f89e6e8 --- /dev/null +++ b/template/en/default/admin/products/groupcontrol/confirm-edit.html.tmpl @@ -0,0 +1,55 @@ +<!-- 1.0@bugzilla.org --> +[%# The contents of this file are subject to the Mozilla Public + # License Version 1.1 (the "License"); you may not use this file + # except in compliance with the License. You may obtain a copy of + # the License at http://www.mozilla.org/MPL/ + # + # Software distributed under the License is distributed on an "AS + # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or + # implied. See the License for the specific language governing + # rights and limitations under the License. + # + # The Original Code is the Bugzilla Bug Tracking System. + # + # The Initial Developer of the Original Code is Netscape Communications + # Corporation. Portions created by Netscape are + # Copyright (C) 1998 Netscape Communications Corporation. All + # Rights Reserved. + # + # Contributor(s): Joel Peshkin <bugreport@peshkin.net> + # + #%] + + + +[% PROCESS global/header.html.tmpl title="Confirm Group Control Change for product \'$product\'" %] + +[% FOREACH group = mandatory_groups %] +<P> +group '[% group.name FILTER html %]' impacts [% group.count %] bugs for which the group is +newly mandatory and will be added. +[% END %] + +[% FOREACH group = na_groups %] +<P> +group '[% group.name FILTER html %]' impacts [% group.count %] bugs for which the group is no longer applicable and will be removed. +[% END %] +<form method="post" > + + [% PROCESS "global/hidden-fields.html.tmpl" exclude="^(Bugzilla|LDAP)_(login|password)$" %] + + <br> + Click "Continue" to proceed with the change including the changes + indicated above. If you do not want these changes, use "back" to + return to the previous page. + <p> + <input type="hidden" name="confirmed" value="confirmed"> + <input type="submit" value="Continue"> + </p> + +</form> + + +[% PROCESS global/footer.html.tmpl %] + + diff --git a/template/en/default/admin/products/groupcontrol/edit.html.tmpl b/template/en/default/admin/products/groupcontrol/edit.html.tmpl new file mode 100644 index 000000000..11bb99de5 --- /dev/null +++ b/template/en/default/admin/products/groupcontrol/edit.html.tmpl @@ -0,0 +1,284 @@ +<!-- 1.0@bugzilla.org --> +[%# The contents of this file are subject to the Mozilla Public + # License Version 1.1 (the "License"); you may not use this file + # except in compliance with the License. You may obtain a copy of + # the License at http://www.mozilla.org/MPL/ + # + # Software distributed under the License is distributed on an "AS + # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or + # implied. See the License for the specific language governing + # rights and limitations under the License. + # + # The Original Code is the Bugzilla Bug Tracking System. + # + # The Initial Developer of the Original Code is Netscape Communications + # Corporation. Portions created by Netscape are + # Copyright (C) 1998 Netscape Communications Corporation. All + # Rights Reserved. + # + # Contributor(s): Joel Peshkin <bugreport@peshkin.net> + #%] + + +[% filt_product = product FILTER html %] +[% PROCESS global/header.html.tmpl + title = "Edit Group Controls for '$filt_product'" +%] + +<form method="post" action="editproducts.cgi"> + <input type="hidden" name="action" value="updategroupcontrols"> + <input type="hidden" name="product" value="[% filt_product %]"> + + <table id="form" cellspacing="0" cellpadding="4" border="1"> + <tr bgcolor="#6666ff"> + <th>Group</th> + <th>Entry</th> + <th>MemberControl</th> + <th>OtherControl</th> + <th>Canedit</th> + <th>Bugs</th> + </tr> + [% FOREACH group = groups %] + [% IF group.isactive == 0 AND group.bugcount > 0 %] + <tr bgcolor="#bbbbbb"> + <td> + [% group.name FILTER html %] + </td> + <td align="center" colspan=4> + Disabled + </td> + <td> + [% group.bugcount %] + </td> + <tr> + [% ELSIF group.isactive != 0 %] + <tr> + <td> + [% group.name FILTER html %] + </td> + <td> + <input type=checkbox value=1 name=entry_[% group.id %] + [% " checked=\"checked\"" IF group.entry %]> + </td> + <td> + <select name="membercontrol_[% group.id %]"> + <option value=[% const.CONTROLMAPNA %] + [% " selected=\"selected\"" + IF group.membercontrol == const.CONTROLMAPNA %] + >NA + </option> + <option value=[% const.CONTROLMAPSHOWN %] + [% " selected=\"selected\"" + IF group.membercontrol == const.CONTROLMAPSHOWN %] + >Shown + </option> + <option value=[% const.CONTROLMAPDEFAULT %] + [% " selected=\"selected\"" + IF group.membercontrol == const.CONTROLMAPDEFAULT %] + >Default + </option> + <option value=[% const.CONTROLMAPMANDATORY %] + [% " selected=\"selected\"" + IF group.membercontrol == const.CONTROLMAPMANDATORY %] + >Mandatory + </option> + </select> + </td> + <td> + <select name="othercontrol_[% group.id %]"> + <option value=[% const.CONTROLMAPNA %] + [% " selected=\"selected\"" + IF group.othercontrol == const.CONTROLMAPNA %] + >NA + </option> + <option value=[% const.CONTROLMAPSHOWN %] + [% " selected=\"selected\"" + IF group.othercontrol == const.CONTROLMAPSHOWN %] + >Shown + </option> + <option value=[% const.CONTROLMAPDEFAULT %] + [% " selected=\"selected\"" + IF group.othercontrol == const.CONTROLMAPDEFAULT %] + >Default + </option> + <option value=[% const.CONTROLMAPMANDATORY %] + [% " selected=\"selected\"" + IF group.othercontrol == const.CONTROLMAPMANDATORY %] + >Mandatory + </option> + </select> + </td> + <td> + <input type=checkbox value=1 name=canedit_[% group.id %] + [% " checked=\"checked\"" IF group.canedit %]> + </td> + <td> + [% group.bugcount %] + </td> + </tr> + [% END %] + [% END %] + + </table> + <br> + <input type=submit name="submit" value="submit"> + <br> +</form> + + +<p> +These settings control the relationship of the groups to this +product. +<p> +If any group has <b>Entry</b> selected, then this product will +restrict bug entry to only those users who are members of all the +groups with entry selected. +<p> +If any group has <b>Canedit</b> selected, then this product +will be read-only for any users who are not members of all of +the groups with Canedit selected. ONLY users who are members of +all the canedit groups will be able to edit. This is an additional +restriction that further restricts what can be edited by a user. +<p> +The <b>MemberControl</b> and <b>OtherControl</b> fields +indicate which bugs will be placed in +this group according to the following definitions. +<br> +<table border=1> + <tr> + <th> + MemberControl + </th> + <th> + OtherControl + </th> + <th> + Interpretation + </th> + </tr> + <tr> + <td> + NA + </td> + <td> + NA + </td> + <td> + Bugs in this product are never associated with this group. + </td> + </tr> + <tr> + <td> + Shown + </td> + <td> + NA + </td> + <td> + Bugs in this product are permitted to be restricted to this + group. Users who are a member of this group will be able + to place bugs in this group. + </td> + </tr> + <tr> + <td> + Shown + </td> + <td> + Shown + </td> + <td> + Bugs in this product can be placed in this group by anyone + with permission to edit the bug even if they are not a member + of this group. + </td> + </tr> + <tr> + <td> + Shown + </td> + <td> + Default + </td> + <td> + Bugs in this product can be placed in this group by anyone + with permission to edit the bug even if they are not a member + of this group. Non-members place bugs in this group by default. + </td> + </tr> + <tr> + <td> + Shown + </td> + <td> + Mandatory + </td> + <td> + Bugs in this product are permitted to be restricted to this + group. Users who are a member of this group will be able + to place bugs in this group. + Non-members will be forced to restrict bugs to this group + when they initially enter a bug in this product. + </td> + </tr> + <tr> + <td> + Default + </td> + <td> + NA + </td> + <td> + Bugs in this product are permitted to be restricted to this + group and are placed in this group by default. + Users who are a member of this group will be able + to place bugs in this group. + </td> + </tr> + <tr> + <td> + Default + </td> + <td> + Default + </td> + <td> + Bugs in this product are permitted to be restricted to this + group and are placed in this group by default. + Users who are a member of this group will be able + to place bugs in this group. Non-members will be able to + restrict bugs to this group on entry and will do so by default + </td> + </tr> + <tr> + <td> + Default + </td> + <td> + Mandatory + </td> + <td> + Bugs in this product are permitted to be restricted to this + group and are placed in this group by default. + Users who are a member of this group will be able + to place bugs in this group. Non-members will be forced + to place bugs in this group on entry. + </td> + </tr> + <tr> + <td> + Mandatory + </td> + <td> + Mandatory + </td> + <td> + Bugs in this product are required to be restricted to this + group. Users are not given any option. + </td> + </tr> +</table> + + +[% PROCESS global/footer.html.tmpl %] + diff --git a/template/en/default/bug/edit.html.tmpl b/template/en/default/bug/edit.html.tmpl index 575c0ea07..4af65af1c 100644 --- a/template/en/default/bug/edit.html.tmpl +++ b/template/en/default/bug/edit.html.tmpl @@ -405,20 +405,28 @@ <br> [% IF groups.size > 0 %] - <br> - <b>Only users in all of the selected groups can view this bug:</b> - <br> - <font size="-1">(Unchecking all boxes makes this a public bug.)</font> - <br> - <br> [% FOREACH group = groups %] + [% IF NOT group.mandatory %] + [% IF NOT emitted_description %] + [% emitted_description = 1 %] + <br> + <b>Only users in all of the selected groups can view this bug:</b> + <br> + <font size="-1"> + (Unchecking all boxes makes this a more public bug.) + </font> + <br> + <br> + [% END %] + <input type="checkbox" name="bit-[% group.bit %]" value="1" [% " checked=\"checked\"" IF group.ison %] [% " disabled=\"disabled\"" IF NOT group.ingroup %]> [% group.description %] <br> + [% END %] [% END %] [% IF NOT user.inallgroups %] @@ -431,7 +439,7 @@ [% IF bug.inagroup %] <p> - <b>But users in the roles selected below can always view this bug:</b> + <b>Users in the roles selected below can always view this bug:</b> <br> <small> (The assignee diff --git a/template/en/default/bug/process/verify-new-product.html.tmpl b/template/en/default/bug/process/verify-new-product.html.tmpl index 77a2ab762..bba85d637 100644 --- a/template/en/default/bug/process/verify-new-product.html.tmpl +++ b/template/en/default/bug/process/verify-new-product.html.tmpl @@ -81,14 +81,14 @@ <h3>Verify Bug Group</h3> <p> - Do you want to add the bug to its new product's group (if any)? + Do you want to add the bug to its new product's default groups (if any)? </p> <p> <input type="radio" name="addtonewgroup" value="no"><b>no</b><br> <input type="radio" name="addtonewgroup" value="yes"><b>yes</b><br> <input type="radio" name="addtonewgroup" value="yesifinold" checked="checked"> - <b>yes, but only if the bug was in its old product's group</b><br> + <b>yes, but only if the bug was in any of its old product's default groups</b><br> </p> [% END %] diff --git a/template/en/default/global/user-error.html.tmpl b/template/en/default/global/user-error.html.tmpl index b3b50f68a..885cf24cd 100644 --- a/template/en/default/global/user-error.html.tmpl +++ b/template/en/default/global/user-error.html.tmpl @@ -230,6 +230,12 @@ It must also not contain any of these special characters: <tt>\ ( ) & < > , ; : " [ ]</tt>, or any whitespace. + [% ELSIF error == "illegal_group_control_combination" %] + [% title = "Your Group Control Combination Is Illegal" %] + Your group control combination for group " + [% groupname FILTER html %] + " is illegal. + [% ELSIF error == "illegal_is_obsolete" %] [% title = "Your Query Makes No Sense" %] The only legal values for the <em>Attachment is obsolete</em> field are @@ -462,6 +468,10 @@ [% title = "Access Denied" %] You do not have the permissions necessary to access that product. + [% ELSIF error == "product_edit_denied" %] + [% title = "Product Edit Access Denied" %] + You are not permitted to edit bugs in product [% product %]. + [% ELSIF error == "query_name_missing" %] [% title = "No Query Name Specified" %] You must enter a name for your query. |