summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorbugreport%peshkin.net <>2002-11-25 04:56:17 +0100
committerbugreport%peshkin.net <>2002-11-25 04:56:17 +0100
commitc64d51111a5ae02d6fc45163a847d0b7e2004548 (patch)
treefd7a0cb912e4411573faa5305df4d4971a3b6dda
parente7720dcdd4e332c096a310c53412d3acaacd381e (diff)
downloadbugzilla-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.pm10
-rw-r--r--Bugzilla/Constants.pm76
-rw-r--r--bug_form.pl56
-rwxr-xr-xbuglist.cgi1
-rwxr-xr-xchecksetup.pl65
-rw-r--r--defparams.pl6
-rwxr-xr-xdescribecomponents.cgi9
-rwxr-xr-xeditgroups.cgi57
-rwxr-xr-xeditproducts.cgi539
-rwxr-xr-xenter_bug.cgi60
-rw-r--r--globals.pl113
-rwxr-xr-xpost_bug.cgi35
-rwxr-xr-xprocess_bug.cgi213
-rwxr-xr-xquery.cgi11
-rwxr-xr-xqueryhelp.cgi2
-rwxr-xr-xreports.cgi26
-rwxr-xr-xsanitycheck.cgi50
-rw-r--r--template/en/default/admin/products/groupcontrol/confirm-edit.html.tmpl55
-rw-r--r--template/en/default/admin/products/groupcontrol/edit.html.tmpl284
-rw-r--r--template/en/default/bug/edit.html.tmpl22
-rw-r--r--template/en/default/bug/process/verify-new-product.html.tmpl4
-rw-r--r--template/en/default/global/user-error.html.tmpl10
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&nbsp</td>\n";
+ print "<td>" . html_quote($name) . "</td>\n";
+ print "<td>" . html_quote($desc) . "</td>\n";
+ print "<td>" . html_quote($regexp) . "&nbsp</td>\n";
print "<td align=center>";
print "X" if (($isactive != 0) && ($isbuggroup != 0));
print "&nbsp</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)
#
diff --git a/query.cgi b/query.cgi
index 0d9c37235..680ed0557 100755
--- a/query.cgi
+++ b/query.cgi
@@ -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 %]
+
&nbsp;&nbsp;&nbsp;&nbsp;
<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>\ ( ) &amp; &lt; &gt; , ; : &quot; [ ]</tt>, or any whitespace.
+ [% ELSIF error == "illegal_group_control_combination" %]
+ [% title = "Your Group Control Combination Is Illegal" %]
+ Your group control combination for group &quot;
+ [% groupname FILTER html %]
+ &quot; 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.