diff options
-rw-r--r-- | CGI.pl | 52 | ||||
-rw-r--r-- | bug_form.pl | 100 | ||||
-rwxr-xr-x | bug_status.html | 16 | ||||
-rwxr-xr-x | buglist.cgi | 19 | ||||
-rwxr-xr-x | checksetup.pl | 82 | ||||
-rw-r--r-- | confirmhelp.html | 154 | ||||
-rw-r--r-- | defparams.pl | 9 | ||||
-rwxr-xr-x | doeditvotes.cgi | 22 | ||||
-rwxr-xr-x | editproducts.cgi | 178 | ||||
-rwxr-xr-x | editusers.cgi | 222 | ||||
-rwxr-xr-x | enter_bug.cgi | 21 | ||||
-rw-r--r-- | globals.pl | 45 | ||||
-rwxr-xr-x | post_bug.cgi | 20 | ||||
-rwxr-xr-x | process_bug.cgi | 139 | ||||
-rwxr-xr-x | showvotes.cgi | 3 | ||||
-rwxr-xr-x | userprefs.cgi | 46 |
16 files changed, 942 insertions, 186 deletions
@@ -490,6 +490,9 @@ sub BuildPulldown { if ($tag eq $default) { $selectpart = " SELECTED"; } + if (!defined $desc) { + $desc = $tag; + } $entry .= qq{<OPTION$selectpart VALUE="$tag">$desc\n}; } $entry .= qq{</SELECT>}; @@ -515,28 +518,31 @@ sub quietly_check_login() { $::usergroupset = '0'; my $loginok = 0; $::disabledreason = ''; + $::userid = 0; if (defined $::COOKIE{"Bugzilla_login"} && defined $::COOKIE{"Bugzilla_logincookie"}) { ConnectToDatabase(); if (!defined $ENV{'REMOTE_HOST'}) { $ENV{'REMOTE_HOST'} = $ENV{'REMOTE_ADDR'}; } - SendSQL("select profiles.groupset, profiles.login_name, " . + SendSQL("SELECT profiles.userid, profiles.groupset, " . + "profiles.login_name, " . "profiles.login_name = " . SqlQuote($::COOKIE{"Bugzilla_login"}) . - " and profiles.cryptpassword = logincookies.cryptpassword " . - "and logincookies.hostname = " . + " AND profiles.cryptpassword = logincookies.cryptpassword " . + "AND logincookies.hostname = " . SqlQuote($ENV{"REMOTE_HOST"}) . ", profiles.disabledtext " . - " from profiles,logincookies where logincookies.cookie = " . + " FROM profiles, logincookies WHERE logincookies.cookie = " . SqlQuote($::COOKIE{"Bugzilla_logincookie"}) . - " and profiles.userid = logincookies.userid"); + " AND profiles.userid = logincookies.userid"); my @row; if (@row = FetchSQLData()) { - my ($groupset, $loginname, $ok, $disabledtext) = (@row); + my ($userid, $groupset, $loginname, $ok, $disabledtext) = (@row); if ($ok) { if ($disabledtext eq '') { $loginok = 1; + $::userid = $userid; $::usergroupset = $groupset; $::COOKIE{"Bugzilla_login"} = $loginname; # Makes sure case # is in @@ -730,6 +736,7 @@ name=PleaseMailAPassword> # Update the timestamp on our logincookie, so it'll keep on working. SendSQL("update logincookies set lastused = null where cookie = $::COOKIE{'Bugzilla_logincookie'}"); + return $::userid; } @@ -785,6 +792,37 @@ sub PutFooter { } +sub CheckIfVotedConfirmed { + my ($id, $who) = (@_); + SendSQL("SELECT bugs.votes, bugs.bug_status, products.votestoconfirm, " . + " bugs.everconfirmed " . + "FROM bugs, products " . + "WHERE bugs.bug_id = $id AND products.product = bugs.product"); + my ($votes, $status, $votestoconfirm, $everconfirmed) = (FetchSQLData()); + if ($votes >= $votestoconfirm && $status eq $::unconfirmedstate) { + SendSQL("UPDATE bugs SET bug_status = 'NEW', everconfirmed = 1 " . + "WHERE bug_id = $id"); + my $fieldid = GetFieldID("bug_status"); + SendSQL("INSERT INTO bugs_activity " . + "(bug_id,who,bug_when,fieldid,oldvalue,newvalue) VALUES " . + "($id,$who,now(),$fieldid,'$::unconfirmedstate','NEW')"); + if (!$everconfirmed) { + $fieldid = GetFieldID("everconfirmed"); + SendSQL("INSERT INTO bugs_activity " . + "(bug_id,who,bug_when,fieldid,oldvalue,newvalue) VALUES " . + "($id,$who,now(),$fieldid,'0','1')"); + } + AppendComment($id, DBID_to_name($who), + "*** This bug has been confirmed by popular vote. ***"); + print "<TABLE BORDER=1><TD><H2>Bug $id has been confirmed by votes.</H2>\n"; + system("./processmail", $id); + print "<TD><A HREF=\"show_bug.cgi?id=$id\">Go To BUG# $id</A></TABLE>\n"; + } + +} + + + sub DumpBugActivity { my ($id, $starttime) = (@_); my $datepart = ""; @@ -885,7 +923,7 @@ sub GetCommandMenu { $html .= ", <a href=editparams.cgi>parameters</a>"; $html .= ", <a href=sanitycheck.cgi><NOBR>sanity check</NOBR></a>"; } - if (UserInGroup("editusers")) { + if (UserInGroup("editusers") || UserInGroup("editgroupmembers")) { $html .= ", <a href=editusers.cgi>users</a>"; } if (UserInGroup("editcomponents")) { diff --git a/bug_form.pl b/bug_form.pl index c5cfd96e2..520949a36 100644 --- a/bug_form.pl +++ b/bug_form.pl @@ -31,13 +31,13 @@ sub bug_form_pl_sillyness { $zz = %::components; $zz = %::prodmaxvotes; $zz = %::versions; + $zz = @::legal_keywords; $zz = @::legal_opsys; $zz = @::legal_platform; $zz = @::legal_product; $zz = @::legal_priority; $zz = @::legal_resolution_no_dup; $zz = @::legal_severity; - $zz = @::keywordsbyname; } my %knownattachments; @@ -194,14 +194,27 @@ if (@row = FetchSQLData()) { exit; } +my $assignedtoid = $bug{'assigned_to'}; +my $reporterid = $bug{'reporter'}; +my $qacontactid = $bug{'qa_contact'}; + $bug{'assigned_to'} = DBID_to_name($bug{'assigned_to'}); $bug{'reporter'} = DBID_to_name($bug{'reporter'}); + +print qq{<FORM NAME="changeform" METHOD="POST" ACTION="process_bug.cgi">\n}; + +# foreach my $i (sort(keys(%bug))) { +# my $q = value_quote($bug{$i}); +# print qq{<INPUT TYPE="HIDDEN" NAME="orig-$i" VALUE="$q">\n}; +# } + $bug{'long_desc'} = GetLongDescription($id); my $longdesclength = length($bug{'long_desc'}); - GetVersionTable(); + + # # These should be read from the database ... # @@ -229,11 +242,9 @@ if (defined $URL && $URL ne "none" && $URL ne "NULL" && $URL ne "") { } print " -<FORM NAME=changeform METHOD=POST ACTION=\"process_bug.cgi\"> <INPUT TYPE=HIDDEN NAME=\"delta_ts\" VALUE=\"$bug{'delta_ts'}\"> <INPUT TYPE=HIDDEN NAME=\"longdesclength\" VALUE=\"$longdesclength\"> <INPUT TYPE=HIDDEN NAME=\"id\" VALUE=$id> -<INPUT TYPE=HIDDEN NAME=\"was_assigned_to\" VALUE=\"$bug{'assigned_to'}\"> <TABLE CELLSPACING=0 CELLPADDING=0 BORDER=0><TR> <TD ALIGN=RIGHT><B>Bug#:</B></TD><TD><A HREF=\"show_bug.cgi?id=$bug{'bug_id'}\">$bug{'bug_id'}</A></TD> <TD ALIGN=RIGHT><B><A HREF=\"bug_status.html#rep_platform\">Platform:</A></B></TD> @@ -449,50 +460,73 @@ my $knum = 1; my $status = $bug{'bug_status'}; -if ($status eq "NEW" || $status eq "ASSIGNED" || $status eq "REOPENED") { - if ($status ne "ASSIGNED") { - print "<INPUT TYPE=radio NAME=knob VALUE=accept>"; - print "Accept bug (change status to <b>ASSIGNED</b>)<br>"; - $knum++; - } - if ($bug{'resolution'} ne "") { - print "<INPUT TYPE=radio NAME=knob VALUE=clearresolution>\n"; - print "Clear the resolution (remove the current resolution of\n"; - print "<b>$bug{'resolution'}</b>)<br>\n"; +my $canedit = UserInGroup("editbugs"); +my $canconfirm; + +if ($status eq $::unconfirmedstate) { + $canconfirm = UserInGroup("canconfirm"); + if ($canedit || $canconfirm) { + print "<INPUT TYPE=radio NAME=knob VALUE=confirm>"; + print "Confirm bug (change status to <b>NEW</b>)<br>"; $knum++; } - print "<INPUT TYPE=radio NAME=knob VALUE=resolve> +} + + +if ($::userid && ($canedit || $::userid == $assignedtoid || + $::userid == $reporterid || $::userid == $qacontactid)) { + if (IsOpenedState($status)) { + if ($status ne "ASSIGNED") { + print "<INPUT TYPE=radio NAME=knob VALUE=accept>"; + my $extra = ""; + if ($status eq $::unconfirmedstate && ($canconfirm || $canedit)) { + $extra = "confirm bug, "; + } + print "Accept bug (${extra}change status to <b>ASSIGNED</b>)<br>"; + $knum++; + } + if ($bug{'resolution'} ne "") { + print "<INPUT TYPE=radio NAME=knob VALUE=clearresolution>\n"; + print "Clear the resolution (remove the current resolution of\n"; + print "<b>$bug{'resolution'}</b>)<br>\n"; + $knum++; + } + print "<INPUT TYPE=radio NAME=knob VALUE=resolve> Resolve bug, changing <A HREF=\"bug_status.html\">resolution</A> to <SELECT NAME=resolution ONCHANGE=\"document.changeform.knob\[$knum\].checked=true\"> $resolution_popup</SELECT><br>\n"; - $knum++; - print "<INPUT TYPE=radio NAME=knob VALUE=duplicate> + $knum++; + print "<INPUT TYPE=radio NAME=knob VALUE=duplicate> Resolve bug, mark it as duplicate of bug # <INPUT NAME=dup_id SIZE=6 ONCHANGE=\"document.changeform.knob\[$knum\].checked=true\"><br>\n"; - $knum++; - my $assign_element = "<INPUT NAME=\"assigned_to\" SIZE=32 ONCHANGE=\"document.changeform.knob\[$knum\].checked=true\" VALUE=\"$bug{'assigned_to'}\">"; + $knum++; + my $assign_element = "<INPUT NAME=\"assigned_to\" SIZE=32 ONCHANGE=\"document.changeform.knob\[$knum\].checked=true\" VALUE=\"$bug{'assigned_to'}\">"; - print "<INPUT TYPE=radio NAME=knob VALUE=reassign> + print "<INPUT TYPE=radio NAME=knob VALUE=reassign> <A HREF=\"bug_status.html#assigned_to\">Reassign</A> bug to $assign_element <br>\n"; - $knum++; - print "<INPUT TYPE=radio NAME=knob VALUE=reassignbycomponent> + if ($status eq $::unconfirmedstate && ($canconfirm || $canedit)) { + print " <INPUT TYPE=checkbox NAME=andconfirm> and confirm bug (change status to <b>NEW</b>)<BR>"; + } + $knum++; + print "<INPUT TYPE=radio NAME=knob VALUE=reassignbycomponent> Reassign bug to owner of selected component<br>\n"; - $knum++; -} else { - print "<INPUT TYPE=radio NAME=knob VALUE=reopen> Reopen bug<br>\n"; - $knum++; - if ($status eq "RESOLVED") { - print "<INPUT TYPE=radio NAME=knob VALUE=verify> - Mark bug as <b>VERIFIED</b><br>\n"; $knum++; - } - if ($status ne "CLOSED") { - print "<INPUT TYPE=radio NAME=knob VALUE=close> - Mark bug as <b>CLOSED</b><br>\n"; + } else { + print "<INPUT TYPE=radio NAME=knob VALUE=reopen> Reopen bug<br>\n"; $knum++; + if ($status eq "RESOLVED") { + print "<INPUT TYPE=radio NAME=knob VALUE=verify> + Mark bug as <b>VERIFIED</b><br>\n"; + $knum++; + } + if ($status ne "CLOSED") { + print "<INPUT TYPE=radio NAME=knob VALUE=close> + Mark bug as <b>CLOSED</b><br>\n"; + $knum++; + } } } diff --git a/bug_status.html b/bug_status.html index c9629ff38..6e9f044e1 100755 --- a/bug_status.html +++ b/bug_status.html @@ -30,7 +30,9 @@ The <B>status</B> and <B>resolution</B> field define and track the life cycle of a bug. +<a name="status"> <p> +</a> <TABLE BORDER=1 CELLPADDING=4> <TR ALIGN=CENTER VALIGN=TOP> @@ -42,7 +44,12 @@ certain status transitions are allowed. <TD>The <b>resolution</b> field indicates what happened to this bug. <TR VALIGN=TOP><TD> -<DL><DT><B>NEW</B> +<DL><DT><B>UNCONFIRMED</B> +<DD> This bug has recently been added to the database. Nobody has + validated that this bug is true. Users who have the "canconfirm" + permission set may confirm this bug, changing its state to NEW. + Or, it may be directly resolved and marked RESOLVED. +<DT><B>NEW</B> <DD> This bug has recently been added to the assignee's list of bugs and must be processed. Bugs in this state may be accepted, and become <B>ASSIGNED</B>, passed on to someone else, and remain @@ -60,8 +67,8 @@ certain status transitions are allowed. </DL> <TD> <DL> -<DD> No resolution yet. All bugs which are <B>NEW</B> or - <B>ASSIGNED</B> have the resolution set to blank. All other bugs +<DD> No resolution yet. All bugs which are in one of these "open" states + have the resolution set to blank. All other bugs will be marked with one of the following resolutions. </DL> @@ -188,8 +195,7 @@ searching for bugs that have been resolved or verified, remember to set the status field appropriately. <hr> -<address><a href="http://home.netscape.com/people/terry/">Terry Weissman <terry@netscape.com></a></address> <!-- hhmts start --> -Last modified: Thu Jan 13 16:22:39 2000 +Last modified: Wed Feb 16 20:41:24 2000 <!-- hhmts end --> </body> </html> diff --git a/buglist.cgi b/buglist.cgi index 8af1e9433..096508211 100755 --- a/buglist.cgi +++ b/buglist.cgi @@ -33,6 +33,7 @@ use Date::Parse; sub sillyness { my $zz; $zz = $::defaultqueryname; + $zz = $::unconfirmedstate; $zz = @::components; $zz = @::default_column_list; $zz = @::keywordsbyname; @@ -824,6 +825,14 @@ my $dotweak = defined $::FORM{'tweak'}; if ($dotweak) { confirm_login(); + if (!UserInGroup("canedit")) { + print qq{ +Sorry; you do not have sufficient priviledges to edit a bunch of bugs +at once. +}; + PutFooter(); + exit(); + } } else { quietly_check_login(); } @@ -979,7 +988,7 @@ if ($splitheader) { $tablestart .= "</TR>\n<TR><TD></TD>"; } for (my $i=1-$pass ; $i<@th ; $i += 2) { - my $h = @th[$i]; + my $h = $th[$i]; $h =~ s/TH/TH COLSPAN="2" ALIGN="left"/; $tablestart .= $h; } @@ -1287,6 +1296,12 @@ if ($::usergroupset ne '0' && $buggroupset =~ /^\d+$/) { <INPUT TYPE=radio NAME=knob VALUE=none CHECKED> Do nothing else<br>"; $knum++; + if ($statushash{$::unconfirmedstate} && 1 == scaler(keys(%statushash))) { + print " +<INPUT TYPE=radio NAME=knob VALUE=confirm> + Confirm bugs (change status to <b>NEW</b>)<br>"; + } + $knum++; print " <INPUT TYPE=radio NAME=knob VALUE=accept> Accept bugs (change status to <b>ASSIGNED</b>)<br>"; @@ -1363,7 +1378,7 @@ if ($count > 0) { <NOBR><A HREF=\"enter_bug.cgi\">Enter New Bug</A></NOBR> <NOBR><A HREF=\"colchange.cgi?$::buffer\">Change columns</A></NOBR>"; - if (!$dotweak && $count > 1) { + if (!$dotweak && $count > 1 && UserInGroup("canedit")) { print " \n"; print "<NOBR><A HREF=\"buglist.cgi?$fields$orderpart&tweak=1\">"; print "Change several bugs at once</A></NOBR>\n"; diff --git a/checksetup.pl b/checksetup.pl index 05bcf31ac..16f2a9c19 100755 --- a/checksetup.pl +++ b/checksetup.pl @@ -547,7 +547,7 @@ $table{bugs} = assigned_to mediumint not null, # This is a comment. bug_file_loc text, bug_severity enum($severities) not null, - bug_status enum("NEW", "ASSIGNED", "REOPENED", "RESOLVED", "VERIFIED", "CLOSED") not null, + bug_status enum("UNCONFIRMED", "NEW", "ASSIGNED", "REOPENED", "RESOLVED", "VERIFIED", "CLOSED") not null, creation_ts datetime not null, delta_ts timestamp, short_desc mediumtext, @@ -567,6 +567,7 @@ $table{bugs} = # the real data comes from the keywords table. . ' lastdiffed datetime not null, + everconfirmed tinyint not null, index (assigned_to), index (creation_ts), @@ -656,7 +657,10 @@ $table{products} = description mediumtext, milestoneurl tinytext not null, disallownew tinyint not null, - votesperuser smallint not null'; + votesperuser smallint not null, + maxvotesperbug smallint not null default 10000, + votestoconfirm smallint not null +'; $table{profiles} = @@ -670,10 +674,25 @@ $table{profiles} = disabledtext mediumtext not null, newemailtech tinyint not null, mybugslink tinyint not null default 1, + blessgroupset bigint not null, + unique(login_name)'; +$table{profiles_activity} = + 'userid mediumint not null, + who mediumint not null, + profiles_when datetime not null, + fieldid mediumint not null, + oldvalue tinytext, + newvalue tinytext, + + index (userid), + index (profiles_when), + index (fieldid)'; + + $table{namedqueries} = 'userid mediumint not null, name varchar(64) not null, @@ -775,22 +794,31 @@ while (my ($tabname, $fielddef) = each %table) { # Populate groups table ########################################################################### +sub GroupExists ($) +{ + my ($name) = @_; + my $sth = $dbh->prepare("SELECT name FROM groups WHERE name='$name'"); + $sth->execute; + if ($sth->rows) { + return 1; + } + return 0; +} + + # # This subroutine checks if a group exist. If not, it will be automatically # created with the next available bit set # -sub AddGroup ($$) -{ - my ($name, $desc) = @_; +sub AddGroup { + my ($name, $desc, $userregexp) = @_; + $userregexp ||= ""; - # does the group exist? - my $sth = $dbh->prepare("SELECT name FROM groups WHERE name='$name'"); - $sth->execute; - return if $sth->rows; + return if GroupExists($name); # get highest bit number - $sth = $dbh->prepare("SELECT bit FROM groups ORDER BY bit DESC"); + my $sth = $dbh->prepare("SELECT bit FROM groups ORDER BY bit DESC"); $sth->execute; my @row = $sth->fetchrow_array; @@ -807,21 +835,31 @@ sub AddGroup ($$) $sth = $dbh->prepare('INSERT INTO groups (bit, name, description, userregexp) VALUES (?, ?, ?, ?)'); - $sth->execute($bit, $name, $desc, ""); + $sth->execute($bit, $name, $desc, $userregexp); + return $bit; } # -# BugZilla uses --GROUPS-- to assign various rights to it's users. +# BugZilla uses --GROUPS-- to assign various rights to its users. # AddGroup 'tweakparams', 'Can tweak operating parameters'; AddGroup 'editusers', 'Can edit or disable users'; -AddGroup 'editgroupmembers', 'Can put people in and out of groups that they are members of.'; AddGroup 'creategroups', 'Can create and destroy groups.'; AddGroup 'editcomponents', 'Can create, destroy, and edit components.'; AddGroup 'editkeywords', 'Can create, destroy, and edit keywords.'; +if (!GroupExists("editbugs")) { + my $id = AddGroup('editbugs', 'Can edit all aspects of any bug.', ".*"); + $dbh->do("UPDATE profiles SET groupset = groupset | $id"); +} + +if (!GroupExists("canconfirm")) { + my $id = AddGroup('canconfirm', 'Can confirm a bug.', ".*"); + $dbh->do("UPDATE profiles SET groupset = groupset | $id"); +} + @@ -1427,6 +1465,24 @@ AddField('profiles', 'mybugslink', 'tinyint not null default 1'); AddField('namedqueries', 'linkinfooter', 'tinyint not null'); +# 2000-02-12 Added a new state to bugs, UNCONFIRMED. Added ability to confirm +# a vote via bugs. Added user bits to control which users can confirm bugs +# by themselves, and which users can edit bugs without their names on them. +# Added a user field which controls which groups a user can put other users +# into. + +my @states = ("UNCONFIRMED", "NEW", "ASSIGNED", "REOPENED", "RESOLVED", + "VERIFIED", "CLOSED"); +CheckEnumField('bugs', 'bug_status', @states); +if (!GetFieldDef('bugs', 'everconfirmed')) { + AddField('bugs', 'everconfirmed', 'tinyint not null'); + $dbh->do("UPDATE bugs SET everconfirmed = 1"); +} +AddField('products', 'maxvotesperbug', 'smallint not null default 10000'); +AddField('products', 'votestoconfirm', 'smallint not null'); +AddField('profiles', 'blessgroupset', 'bigint not null'); + + # # If you had to change the --TABLE-- definition in any way, then add your # differential change code *** A B O V E *** this comment. diff --git a/confirmhelp.html b/confirmhelp.html new file mode 100644 index 000000000..5c8e64434 --- /dev/null +++ b/confirmhelp.html @@ -0,0 +1,154 @@ +<HTML><head> + +<!-- + 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) 2000 Netscape Communications Corporation. All + Rights Reserved. + + Contributor(s): Terry Weissman <terry@mozilla.org> +--> + +<title>Understanding the UNCONFIRMED state, and other recent changes</title> +</head> + +<body> +<h1>Understanding the UNCONFIRMED state, and other recent changes</h1> + +[This document is aimed primarily at people who have used Bugzilla +before the UNCONFIRMED state was implemented. It might be helpful for +newer users as well.] + +<p> + +New bugs in some products will now show up in a new state, +UNCONFIRMED. This means that we have nobody has confirmed that the +bug is real. Very busy engineers will probably generally ignore +UNCONFIRMED that have been assigned to them, until they have been +confirmed in one way or another. (Engineers with more time will +hopefully glance over their UNCONFIRMED bugs regularly.) + +<p> + +The <a href="bug_status.html">page describing bug fields</a> has been +updated to include UNCONFIRMED. +<p> + +There are two basic ways that a bug can become confirmed (and enter +the NEW) state. + +<ul> + <li> A user with the appropriate permissions (see below for more on + permissions) decides that the bug is a valid one, and confirms + it. We hope to gather a small army of responsible volunteers + to regularly go through bugs for us. + <li> The bug gathers a certain number of votes. <b>Any</b> valid + Bugzilla user may vote for bugs (each user gets a certain + number of bugs); any UNCONFIRMED bug which enough bugs becomes + automatically confirmed, and enters the NEW state. +</ul> + +One implication of this is that it is worth your time to search the +bug system for duplicates of your bug to vote on them, before +submitting your own bug. If we can spread around knowledge of this +fact, it ought to help cut down the number of duplicate bugs in the +system. + +<h2>Permissions.</h2> + +Users now have a certain set of permissions. To see your permissions, +check out the +<a href="userprefs.cgi?bank=permissions">user preferences</a> page. + +<p> + +If you have the "Can confirm a bug" permission, then you will be able +to move UNCONFIRMED bugs into the NEW state. + +<p> + +If you have the "Can edit all aspects of any bug" permission, then you +can tweak anything about any bug. If not, you may only edit those +bugs that you have submitted, or that you have assigned to you (or +qa-assigned to you). However, anyone may add a comment to any bug. + +<p> + +Some people (initially, the initial owners and initial qa-contacts for +components in the system) have the ability to give the above two +permissions to other people. So, if you really feel that you ought to +have one of these permissions, a good person to ask (via private +email, please!) is the person who is assigned a relevant bug. + +<h2>Other details.</h2> + +An initial stab was taken to decide who would be given which of the +above permissions. This was determined by some simple heurstics of +who was assigned bugs, and who the default owners of bugs were, and a +look at people who seem to have submitted several bugs that appear to +have been interesting and valid. Inevitably, we have failed to give +someone the permissions they deserve. Please don't take it +personally; just bear with us as we shake out the new system. + +<p> + + +People with one of the two bits above can easily confirm their own +bugs, so bugs they submit will actually start out in the NEW state. +They can override this when submitting a bug. + +<p> + +People can ACCEPT or RESOLVE a bug assigned to them, even if they +aren't allowed to confirm it. However, the system remembers, and if +the bug gets REOPENED or reassigned to someone else, it will revert +back to the UNCONFIRMED state. If the bug has ever been confirmed, +then REOPENing or reassigning will cause it to go to the NEW or +REOPENED state. + +<p> + +Note that only some products support the UNCONFIRMED state. In other +products, all new bugs will automatically start in the NEW state. + +<h2>Things still to be done.</h2> + +There probably ought to be a way to get a bug back into the +UNCONFIRMED state, but there isn't yet. + +<p> + +If a person has submitted several bugs that get confirmed, then this +is probably a person who understands the system well, and deserves the +"Can confirm a bug" permission. This kind of person should be +detected and promoted automatically. + +<p> + +There should also be a way to automatically promote people to get the +"Can edit all aspects of any bug" permission. + +<p> + +The "enter a new bug" page needs to be revamped with easy ways for new +people to educate themselves on the benefit of searching for a bug +like the one they're about to submit and voting on it, rather than +adding a new useless duplicate. + +<hr> +<!-- hhmts start --> +Last modified: Wed Feb 16 21:04:56 2000 +<!-- hhmts end --> +</body> </html> diff --git a/defparams.pl b/defparams.pl index 742ca3746..c5cf400e0 100644 --- a/defparams.pl +++ b/defparams.pl @@ -428,7 +428,7 @@ DefParam("expectbigqueries", 0); DefParam("emailregexp", - 'This defines the regexp to use for legal email addresses. The default tries to match fully qualified email addresses. Another popular value to put here is <tt>^[^@, ]$</tt>, which means "local usernames, no @ allowed.', + 'This defines the regexp to use for legal email addresses. The default tries to match fully qualified email addresses. Another popular value to put here is <tt>^[^@, ]*$</tt>, which means "local usernames, no @ allowed.', "t", q:^[^@, ]*@[^@, ]*\\.[^@, ]*$:); @@ -444,7 +444,7 @@ DefParam("emailsuffix", DefParam("voteremovedmail", -q{This is a mail message to send to anyone who gets a vote removed from a bug for any reason. %to% gets replaced by a comma-separated list of people who used to be voting for this bug. %bugid% gets replaced by the bug number. %reason% gets replaced by a short reason describing why the vote was removed. %<i>anythingelse</i>% gets replaced by the definition of thatparameter (as defined on this page).}, +q{This is a mail message to send to anyone who gets a vote removed from a bug for any reason. %to% gets replaced by a comma-separated list of people who used to be voting for this bug. %bugid% gets replaced by the bug number. %reason% gets replaced by a short reason describing why the vote was removed. %count% is how many votes got removed.%<i>anythingelse</i>% gets replaced by the definition of that parameter (as defined on this page).}, "l", "From: bugzilla-daemon To: %to% @@ -454,6 +454,8 @@ You used to have a vote on bug %bugid%, but it has been removed. Reason: %reason% +Votes removed: %count% + %urlbase%show_bug.cgi?id=%bugid% "); @@ -488,6 +490,9 @@ DefParam("commentonaccept", DefParam("commentonclearresolution", "If this option is on, the user needs to enter a short comment if the bugs resolution is cleared", "b", 0 ); +DefParam("commentonconfirm", + "If this option is on, the user needs to enter a short comment when confirming a bug", + "b", 0 ); DefParam("commentonresolve", "If this option is on, the user needs to enter a short comment if the bug is resolved", "b", 0 ); diff --git a/doeditvotes.cgi b/doeditvotes.cgi index eef6381d8..3902f9118 100755 --- a/doeditvotes.cgi +++ b/doeditvotes.cgi @@ -63,17 +63,27 @@ foreach my $id (@buglist) { } } -SendSQL("select bug_id, product from bugs where bug_id = " . - join(" or bug_id = ", @buglist)); +SendSQL("SELECT bugs.bug_id, bugs.product, products.maxvotesperbug " . + "FROM bugs, products " . + "WHERE products.product = bugs.product ". + " AND bugs.bug_id IN (" . join(", ", @buglist) . ")"); my %prodcount; while (MoreSQLData()) { - my ($id, $prod) = (FetchSQLData()); + my ($id, $prod, $max) = (FetchSQLData()); if (!defined $prodcount{$prod}) { $prodcount{$prod} = 0; } $prodcount{$prod} += $::FORM{$id}; + if ($::FORM{$id} > $max) { + PutHeader("Don't overstuff!", "Illegal vote"); + print "You may only use at most $max votes for a single bug in the\n"; + print "<tt>$prod</tt> product, but you are using $::FORM{$id}.\n"; + print "<P>Please click <b>Back</b> and try again.<hr>\n"; + PutFooter(); + exit(); + } } foreach my $prod (keys(%prodcount)) { @@ -81,7 +91,7 @@ foreach my $prod (keys(%prodcount)) { PutHeader("Don't overstuff!", "Illegal vote"); print "You may only use $::prodmaxvotes{$prod} votes for bugs in the\n"; print "<tt>$prod</tt> product, but you are using $prodcount{$prod}.\n"; - print "Please click <b>Back</b> and try again.<hr>\n"; + print "<P>Please click <b>Back</b> and try again.<hr>\n"; PutFooter(); exit(); } @@ -110,10 +120,12 @@ foreach my $id (keys %affected) { SendSQL("unlock tables"); - PutHeader("Voting tabulated", "Voting tabulated", $::COOKIE{'Bugzilla_login'}); print "Your votes have been recorded.\n"; print qq{<p><a href="showvotes.cgi?user=$who">Review your votes</a><hr>\n}; +foreach my $id (keys %affected) { + CheckIfVotedConfirmed($id, $who); +} PutFooter(); exit(); diff --git a/editproducts.cgi b/editproducts.cgi index e9c5f9f35..d224077b3 100755 --- a/editproducts.cgi +++ b/editproducts.cgi @@ -32,7 +32,13 @@ use strict; require "CGI.pl"; require "globals.pl"; +# Shut up misguided -w warnings about "used only once". "use vars" just +# doesn't work for me. +sub sillyness { + my $zz; + $zz = $::unconfirmedstate; +} # TestProduct: just returns if the specified product does exists @@ -72,10 +78,10 @@ sub CheckProduct ($) # Displays the form to edit a products parameters # -sub EmitFormElements ($$$$$) +sub EmitFormElements ($$$$$$$) { my ($product, $description, $milestoneurl, $disallownew, - $votesperuser) = @_; + $votesperuser, $maxvotesperbug, $votestoconfirm) = @_; $product = value_quote($product); $description = value_quote($description); @@ -102,6 +108,14 @@ sub EmitFormElements ($$$$$) print "</TR><TR>\n"; print " <TH ALIGN=\"right\">Maximum votes per person:</TH>\n"; print " <TD><INPUT SIZE=5 MAXLENGTH=5 NAME=\"votesperuser\" VALUE=\"$votesperuser\"></TD>\n"; + + print "</TR><TR>\n"; + print " <TH ALIGN=\"right\">Maximum votes a person can put on a single bug:</TH>\n"; + print " <TD><INPUT SIZE=5 MAXLENGTH=5 NAME=\"maxvotesperbug\" VALUE=\"$maxvotesperbug\"></TD>\n"; + + print "</TR><TR>\n"; + print " <TH ALIGN=\"right\">Number of votes a bug in this product needs to automatically get out of the <A HREF=\"bug_status.html#status\">UNCONFIRMED</A> state:</TH>\n"; + print " <TD><INPUT SIZE=5 MAXLENGTH=5 NAME=\"votestoconfirm\" VALUE=\"$votestoconfirm\"></TD>\n"; } @@ -173,7 +187,7 @@ unless ($action) { PutHeader("Select product"); SendSQL("SELECT products.product,description,disallownew, - votesperuser,COUNT(bug_id) + votesperuser,maxvotesperbug,votestoconfirm,COUNT(bug_id) FROM products LEFT JOIN bugs ON products.product=bugs.product GROUP BY products.product @@ -183,12 +197,14 @@ unless ($action) { print " <TH ALIGN=\"left\">Description</TH>\n"; print " <TH ALIGN=\"left\">Status</TH>\n"; print " <TH ALIGN=\"left\">Votes<br>per<br>user</TH>\n"; + print " <TH ALIGN=\"left\">Max<br>Votes<br>per<br>bug</TH>\n"; + print " <TH ALIGN=\"left\">Votes<br>to<br>confirm</TH>\n"; print " <TH ALIGN=\"left\">Bugs</TH>\n"; print " <TH ALIGN=\"left\">Action</TH>\n"; print "</TR>"; while ( MoreSQLData() ) { my ($product, $description, $disallownew, $votesperuser, - $bugs) = FetchSQLData(); + $maxvotesperbug, $votestoconfirm, $bugs) = FetchSQLData(); $description ||= "<FONT COLOR=\"red\">missing</FONT>"; $disallownew = $disallownew ? 'closed' : 'open'; $bugs ||= 'none'; @@ -197,6 +213,8 @@ unless ($action) { print " <TD VALIGN=\"top\">$description</TD>\n"; print " <TD VALIGN=\"top\">$disallownew</TD>\n"; print " <TD VALIGN=\"top\" ALIGN=\"right\">$votesperuser</TD>\n"; + print " <TD VALIGN=\"top\" ALIGN=\"right\">$maxvotesperbug</TD>\n"; + print " <TD VALIGN=\"top\" ALIGN=\"right\">$votestoconfirm</TD>\n"; print " <TD VALIGN=\"top\" ALIGN=\"right\">$bugs</TD>\n"; print " <TD VALIGN=\"top\"><A HREF=\"editproducts.cgi?action=del&product=", url_quote($product), "\">Delete</A></TD>\n"; print "</TR>"; @@ -227,7 +245,7 @@ if ($action eq 'add') { print "<FORM METHOD=POST ACTION=editproducts.cgi>\n"; print "<TABLE BORDER=0 CELLPADDING=4 CELLSPACING=0><TR>\n"; - EmitFormElements('', '', '', 0, 0); + EmitFormElements('', '', '', 0, 0, 10000, 0); print "</TR><TR>\n"; print " <TH ALIGN=\"right\">Version:</TH>\n"; @@ -283,16 +301,21 @@ if ($action eq 'new') { $disallownew = 1 if $::FORM{disallownew}; my $votesperuser = $::FORM{votesperuser}; $votesperuser ||= 0; + my $maxvotesperbug = $::FORM{maxvotesperbug}; + $maxvotesperbug = 10000 if !defined $maxvotesperbug; + my $votestoconfirm = $::FORM{votestoconfirm}; + $votestoconfirm ||= 0; # Add the new product. SendSQL("INSERT INTO products ( " . - "product, description, milestoneurl, disallownew, votesperuser" . + "product, description, milestoneurl, disallownew, votesperuser, " . + "maxvotesperbug, votestoconfirm" . " ) VALUES ( " . SqlQuote($product) . "," . SqlQuote($description) . "," . SqlQuote($milestoneurl) . "," . $disallownew . "," . - SqlQuote($votesperuser) . ")" ); + "$votesperuser, $maxvotesperbug, $votestoconfirm)"); SendSQL("INSERT INTO versions ( " . "value, program" . " ) VALUES ( " . @@ -513,17 +536,19 @@ if ($action eq 'edit') { CheckProduct($product); # get data of product - SendSQL("SELECT description,milestoneurl,disallownew,votesperuser + SendSQL("SELECT description,milestoneurl,disallownew, + votesperuser,maxvotesperbug,votestoconfirm FROM products WHERE product=" . SqlQuote($product)); - my ($description, $milestoneurl, $disallownew, $votesperuser) = + my ($description, $milestoneurl, $disallownew, + $votesperuser, $maxvotesperbug, $votestoconfirm) = FetchSQLData(); print "<FORM METHOD=POST ACTION=editproducts.cgi>\n"; print "<TABLE BORDER=0 CELLPADDING=4 CELLSPACING=0><TR>\n"; EmitFormElements($product, $description, $milestoneurl, $disallownew, - $votesperuser); + $votesperuser, $maxvotesperbug, $votestoconfirm); print "</TR><TR VALIGN=top>\n"; print " <TH ALIGN=\"right\"><A HREF=\"editcomponents.cgi?product=", url_quote($product), "\">Edit components:</A></TH>\n"; @@ -586,6 +611,8 @@ if ($action eq 'edit') { value_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"; + print "<INPUT TYPE=HIDDEN NAME=\"votestoconfirmold\" VALUE=\"$votestoconfirm\">\n"; print "<INPUT TYPE=HIDDEN NAME=\"action\" VALUE=\"update\">\n"; print "<INPUT TYPE=SUBMIT VALUE=\"Update\">\n"; @@ -606,18 +633,30 @@ if ($action eq 'edit') { if ($action eq 'update') { PutHeader("Update product"); - my $productold = trim($::FORM{productold} || ''); - my $description = trim($::FORM{description} || ''); - my $descriptionold = trim($::FORM{descriptionold} || ''); - my $disallownew = trim($::FORM{disallownew} || ''); - my $disallownewold = trim($::FORM{disallownewold} || ''); - my $milestoneurl = trim($::FORM{milestoneurl} || ''); - my $milestoneurlold = trim($::FORM{milestoneurlold} || ''); - my $votesperuser = trim($::FORM{votesperuser} || 0); - my $votesperuserold = trim($::FORM{votesperuserold} || ''); + my $productold = trim($::FORM{productold} || ''); + my $description = trim($::FORM{description} || ''); + my $descriptionold = trim($::FORM{descriptionold} || ''); + my $disallownew = trim($::FORM{disallownew} || ''); + my $disallownewold = trim($::FORM{disallownewold} || ''); + my $milestoneurl = trim($::FORM{milestoneurl} || ''); + my $milestoneurlold = trim($::FORM{milestoneurlold} || ''); + my $votesperuser = trim($::FORM{votesperuser} || 0); + my $votesperuserold = trim($::FORM{votesperuserold} || ''); + my $maxvotesperbug = trim($::FORM{maxvotesperbug} || 0); + my $maxvotesperbugold = trim($::FORM{maxvotesperbugold} || ''); + my $votestoconfirm = trim($::FORM{votestoconfirm} || 0); + my $votestoconfirmold = trim($::FORM{votestoconfirmold} || ''); + + my $checkvotes = 0; CheckProduct($productold); + if ($maxvotesperbug !~ /^\d+$/ || $maxvotesperbug <= 0) { + print "Sorry, the max votes per bug must be a positive integer."; + PutTrailer($localtrailer); + exit; + } + # Note that the order of this tests is important. If you change # them, be sure to test for WHERE='$product' or WHERE='$productold' @@ -659,9 +698,31 @@ if ($action eq 'update') { SET votesperuser=$votesperuser WHERE product=" . SqlQuote($productold)); print "Update votes per user.<BR>\n"; + $checkvotes = 1; } + if ($maxvotesperbug ne $maxvotesperbugold) { + SendSQL("UPDATE products + SET maxvotesperbug=$maxvotesperbug + WHERE product=" . SqlQuote($productold)); + print "Update max votes per bug.<BR>\n"; + $checkvotes = 1; + } + + + if ($votestoconfirm ne $votestoconfirmold) { + SendSQL("UPDATE products + SET votestoconfirm=$votestoconfirm + WHERE product=" . SqlQuote($productold)); + print "Update votes to confirm.<BR>\n"; + $checkvotes = 1; + } + + + my $qp = SqlQuote($product); + my $qpold = SqlQuote($productold); + if ($product ne $productold) { unless ($product) { print "Sorry, I can't delete the product name."; @@ -676,24 +737,79 @@ if ($action eq 'update') { exit; } - SendSQL("UPDATE bugs - SET product=" . SqlQuote($product) . " - WHERE product=" . SqlQuote($productold)); - SendSQL("UPDATE components - SET program=" . SqlQuote($product) . " - WHERE program=" . SqlQuote($productold)); - SendSQL("UPDATE products - SET product=" . SqlQuote($product) . " - WHERE product=" . SqlQuote($productold)); - SendSQL("UPDATE versions - SET program='$product' - WHERE program=" . SqlQuote($productold)); + SendSQL("UPDATE bugs SET product=$qp WHERE product=$qpold"); + SendSQL("UPDATE components SET program=$qp WHERE program=$qpold"); + SendSQL("UPDATE products SET product=$qp WHERE product=$qpold"); + SendSQL("UPDATE versions SET program=$qp WHERE program=$qpold"); print "Updated product name.<BR>\n"; } unlink "data/versioncache"; SendSQL("UNLOCK TABLES"); + if ($checkvotes) { + print "Checking existing votes in this product for anybody who now has too many votes."; + if ($maxvotesperbug < $votesperuser) { + SendSQL("SELECT votes.who, votes.bug_id " . + "FROM votes, bugs " . + "WHERE bugs.bug_id = votes.bug_id " . + " AND bugs.product = $qp " . + " AND votes.count > $maxvotesperbug"); + my @list; + while (MoreSQLData()) { + my ($who, $id) = (FetchSQLData()); + push(@list, [$who, $id]); + } + foreach my $ref (@list) { + my ($who, $id) = (@$ref); + RemoveVotes($id, $who, "The rules for voting on this product has changed;\nyou had too many votes for a single bug."); + my $name = DBID_to_name($who); + print qq{<br>Removed votes for bug <A HREF="show_bug.cgi?id=$id">$id</A> from $name\n}; + } + } + SendSQL("SELECT votes.who, votes.count FROM votes, bugs " . + "WHERE bugs.bug_id = votes.bug_id " . + " AND bugs.product = $qp"); + my %counts; + while (MoreSQLData()) { + my ($who, $count) = (FetchSQLData()); + if (!defined $counts{$who}) { + $counts{$who} = $count; + } else { + $counts{$who} += $count; + } + } + foreach my $who (keys(%counts)) { + if ($counts{$who} > $votesperuser) { + SendSQL("SELECT votes.bug_id FROM votes, bugs " . + "WHERE bugs.bug_id = votes.bug_id " . + " AND bugs.product = $qp " . + " AND votes.who = $who"); + while (MoreSQLData()) { + my $id = FetchSQLData(); + RemoveVotes($id, $who, + "The rules for voting on this product has changed; you had too many\ntotal votes, so all votes have been removed."); + my $name = DBID_to_name($who); + print qq{<br>Removed votes for bug <A HREF="show_bug.cgi?id=$id">$id</A> from $name\n}; + } + } + } + SendSQL("SELECT bug_id FROM bugs " . + "WHERE product = $qp " . + " AND bug_status = '$::unconfirmedstate' " . + " AND votes >= $votestoconfirm"); + my @list; + while (MoreSQLData()) { + push(@list, FetchOneColumn()); + } + foreach my $id (@list) { + SendSQL("SELECT who FROM votes WHERE bug_id = $id"); + my $who = FetchOneColumn(); + CheckIfVotedConfirmed($id, $who); + } + + } + PutTrailer($localtrailer); exit; } diff --git a/editusers.cgi b/editusers.cgi index f4a6c4dfb..03819ec35 100755 --- a/editusers.cgi +++ b/editusers.cgi @@ -31,7 +31,16 @@ use strict; require "CGI.pl"; require "globals.pl"; +# Shut up misguided -w warnings about "used only once". "use vars" just +# doesn't work for me. +sub sillyness { + my $zz; + $zz = $::userid; +} + +my $editall; +my $opblessgroupset = '9223372036854775807'; # This is all 64 bits. @@ -69,59 +78,81 @@ sub CheckUser ($) +sub EmitElement ($$) +{ + my ($name, $value) = (@_); + $value = value_quote($value); + if ($editall) { + print qq{<TD><INPUT SIZE=64 MAXLENGTH=255 NAME="$name" VALUE="$value"></TD>\n}; + } else { + print qq{<TD>$value</TD>\n}; + } +} + + # # Displays the form to edit a user parameters # -sub EmitFormElements ($$$$$$) +sub EmitFormElements ($$$$$$$) { - my ($user, $password, $realname, $groupset, $emailnotification, - $disabledtext) = @_; + my ($user, $password, $realname, $groupset, $blessgroupset, + $emailnotification, $disabledtext) = @_; print " <TH ALIGN=\"right\">Login name:</TH>\n"; - print " <TD><INPUT SIZE=64 MAXLENGTH=255 NAME=\"user\" VALUE=\"$user\"></TD>\n"; + EmitElement("user", $user); print "</TR><TR>\n"; print " <TH ALIGN=\"right\">Real name:</TH>\n"; - print " <TD><INPUT SIZE=64 MAXLENGTH=255 NAME=\"realname\" VALUE=\"$realname\"></TD>\n"; - - print "</TR><TR>\n"; - print " <TH ALIGN=\"right\">Password:</TH>\n"; - print " <TD><INPUT SIZE=16 MAXLENGTH=16 NAME=\"password\" VALUE=\"$password\"></TD>\n"; - - print "</TR><TR>\n"; - print " <TH ALIGN=\"right\">Email notification:</TH>\n"; - print qq{<TD><SELECT NAME="emailnotification">}; - foreach my $i (["ExcludeSelfChanges", "All qualifying bugs except those which I change"], - ["CConly", "Only those bugs which I am listed on the CC line"], - ["All", "All qualifying bugs"]) { - my ($tag, $desc) = (@$i); - my $selectpart = ""; - if ($tag eq $emailnotification) { - $selectpart = " SELECTED"; + EmitElement("realname", $realname); + + if ($editall) { + print "</TR><TR>\n"; + print " <TH ALIGN=\"right\">Password:</TH>\n"; + print " <TD><INPUT SIZE=16 MAXLENGTH=16 NAME=\"password\" VALUE=\"$password\"></TD>\n"; + + print "</TR><TR>\n"; + print " <TH ALIGN=\"right\">Email notification:</TH>\n"; + print qq{<TD><SELECT NAME="emailnotification">}; + foreach my $i (["ExcludeSelfChanges", "All qualifying bugs except those which I change"], + ["CConly", "Only those bugs which I am listed on the CC line"], + ["All", "All qualifying bugs"]) { + my ($tag, $desc) = (@$i); + my $selectpart = ""; + if ($tag eq $emailnotification) { + $selectpart = " SELECTED"; + } + print qq{<OPTION$selectpart VALUE="$tag">$desc\n}; } - print qq{<OPTION$selectpart VALUE="$tag">$desc\n}; + print "</SELECT></TD>\n"; + print "</TR><TR>\n"; + print " <TH ALIGN=\"right\">Disable text:</TH>\n"; + print " <TD ROWSPAN=2><TEXTAREA NAME=\"disabledtext\" ROWS=10 COLS=60>" . + value_quote($disabledtext) . "</TEXTAREA>\n"; + print " </TD>\n"; + print "</TR><TR>\n"; + print " <TD VALIGN=\"top\">If non-empty, then the account will\n"; + print "be disabled, and this text should explain why.</TD>\n"; } - print "</SELECT></TD>\n"; - print "</TR><TR>\n"; - print " <TH ALIGN=\"right\">Disable text:</TH>\n"; - print " <TD ROWSPAN=2><TEXTAREA NAME=\"disabledtext\" ROWS=10 COLS=60>" . - value_quote($disabledtext) . "</TEXTAREA>\n"; - print " </TD>\n"; - print "</TR><TR>\n"; - print " <TD VALIGN=\"top\">If non-empty, then the account will\n"; - print "be disabled, and this text should explain why.</TD>\n"; - - - SendSQL("SELECT bit,name,description,bit & $groupset != 0 - FROM groups - ORDER BY name"); + + + SendSQL("SELECT bit,name,description,bit & $groupset != 0, " . + " bit & $blessgroupset " . + "FROM groups " . + "WHERE bit & $opblessgroupset != 0 " . + "ORDER BY name"); while (MoreSQLData()) { - my ($bit,$name,$description,$checked) = FetchSQLData(); + my ($bit,$name,$description,$checked,$blchecked) = FetchSQLData(); print "</TR><TR>\n"; print " <TH ALIGN=\"right\">", ucfirst($name), ":</TH>\n"; $checked = ($checked) ? "CHECKED" : ""; print " <TD><INPUT TYPE=CHECKBOX NAME=\"bit_$name\" $checked VALUE=\"$bit\"> $description</TD>\n"; + if ($editall) { + print "</TR><TR>\n"; + print "<TH></TH>"; + $blchecked = ($blchecked) ? "CHECKED" : ""; + print "<TD><INPUT TYPE=CHECKBOX NAME=\"blbit_$name\" $blchecked VALUE=\"$bit\"> Can turn this bit on for other users</TD>\n"; + } } } @@ -165,12 +196,19 @@ confirm_login(); print "Content-type: text/html\n\n"; -unless (UserInGroup("editusers")) { - PutHeader("Not allowed"); - print "Sorry, you aren't a member of the 'editusers' group.\n"; - print "And so, you aren't allowed to add, modify or delete users.\n"; - PutTrailer(); - exit; +$editall = UserInGroup("editusers"); + +if (!$editall) { + SendSQL("SELECT blessgroupset FROM profiles WHERE userid = $::userid"); + $opblessgroupset = FetchOneColumn(); + if (!$opblessgroupset) { + PutHeader("Not allowed"); + print "Sorry, you aren't a member of the 'editusers' group, and you\n"; + print "don't have permissions to put people in or out of any group.\n"; + print "And so, you aren't allowed to add, modify or delete users.\n"; + PutTrailer(); + exit; + } } @@ -198,8 +236,8 @@ List users with login name matching: <INPUT SIZE=32 NAME="matchstr"> <SELECT NAME="matchtype"> <OPTION VALUE="substr" SELECTED>case-insensitive substring -<OPTION VALUE="regexp" SELECTED>case-sensitive regexp -<OPTION VALUE="notregexp" SELECTED>not (case-sensitive regexp) +<OPTION VALUE="regexp">case-sensitive regexp +<OPTION VALUE="notregexp">not (case-sensitive regexp) </SELECT> <BR> <INPUT TYPE=SUBMIT VALUE="Submit"> @@ -261,14 +299,17 @@ if ($action eq 'list') { } print "</TR>"; } - print "<TR>\n"; - my $span = $candelete ? 3 : 2; - print qq{ + if ($editall) { + print "<TR>\n"; + my $span = $candelete ? 3 : 2; + print qq{ <TD VALIGN="top" COLSPAN=$span ALIGN="right"> <A HREF=\"editusers.cgi?action=add\">Add a new user</A> </TD> }; - print "</TR></TABLE>\n"; + print "</TR>"; + } + print "</TABLE>\n"; print "$count users found.\n"; PutTrailer($localtrailer); @@ -286,11 +327,16 @@ if ($action eq 'list') { if ($action eq 'add') { PutHeader("Add user"); + if (!$editall) { + print "Sorry, you don't have permissions to add new users."; + PutTrailer(); + exit; + } print "<FORM METHOD=POST ACTION=editusers.cgi>\n"; print "<TABLE BORDER=0 CELLPADDING=4 CELLSPACING=0><TR>\n"; - EmitFormElements('', '', '', 0, 'ExcludeSelfChanges', ''); + EmitFormElements('', '', '', 0, 0, 'ExcludeSelfChanges', ''); print "</TR></TABLE>\n<HR>\n"; print "<INPUT TYPE=SUBMIT VALUE=\"Add\">\n"; @@ -312,6 +358,12 @@ if ($action eq 'add') { if ($action eq 'new') { PutHeader("Adding new user"); + if (!$editall) { + print "Sorry, you don't have permissions to add new users."; + PutTrailer(); + exit; + } + # Cleanups and valididy checks my $realname = trim($::FORM{realname} || ''); my $password = trim($::FORM{password} || ''); @@ -386,6 +438,11 @@ if ($action eq 'del') { print "Sorry, deleting users isn't allowed."; PutTrailer(); } + if (!$editall) { + print "Sorry, you don't have permissions to delete users."; + PutTrailer(); + exit; + } CheckUser($user); # display some data about the user @@ -515,6 +572,11 @@ if ($action eq 'delete') { print "Sorry, deleting users isn't allowed."; PutTrailer(); } + if (!$editall) { + print "Sorry, you don't have permissions to delete users."; + PutTrailer(); + exit; + } CheckUser($user); SendSQL("SELECT userid @@ -545,25 +607,28 @@ if ($action eq 'edit') { CheckUser($user); # get data of user - SendSQL("SELECT password, realname, groupset, emailnotification, - disabledtext + SendSQL("SELECT password, realname, groupset, blessgroupset, + emailnotification, disabledtext FROM profiles WHERE login_name=" . SqlQuote($user)); - my ($password, $realname, $groupset, $emailnotification, + my ($password, $realname, $groupset, $blessgroupset, $emailnotification, $disabledtext) = FetchSQLData(); print "<FORM METHOD=POST ACTION=editusers.cgi>\n"; print "<TABLE BORDER=0 CELLPADDING=4 CELLSPACING=0><TR>\n"; - EmitFormElements($user, $password, $realname, $groupset, + EmitFormElements($user, $password, $realname, $groupset, $blessgroupset, $emailnotification, $disabledtext); print "</TR></TABLE>\n"; print "<INPUT TYPE=HIDDEN NAME=\"userold\" VALUE=\"$user\">\n"; - print "<INPUT TYPE=HIDDEN NAME=\"passwordold\" VALUE=\"$password\">\n"; + if ($editall) { + print "<INPUT TYPE=HIDDEN NAME=\"passwordold\" VALUE=\"$password\">\n"; + } print "<INPUT TYPE=HIDDEN NAME=\"realnameold\" VALUE=\"$realname\">\n"; print "<INPUT TYPE=HIDDEN NAME=\"groupsetold\" VALUE=\"$groupset\">\n"; + print "<INPUT TYPE=HIDDEN NAME=\"blessgroupsetold\" VALUE=\"$blessgroupset\">\n"; print "<INPUT TYPE=HIDDEN NAME=\"emailnotificationold\" VALUE=\"$emailnotification\">\n"; print "<INPUT TYPE=HIDDEN NAME=\"disabledtextold\" VALUE=\"" . value_quote($disabledtext) . "\">\n"; @@ -583,7 +648,7 @@ if ($action eq 'edit') { # if ($action eq 'update') { - PutHeader("Update User"); + PutHeader("Updated user"); my $userold = trim($::FORM{userold} || ''); my $realname = trim($::FORM{realname} || ''); @@ -595,12 +660,19 @@ if ($action eq 'update') { my $disabledtext = trim($::FORM{disabledtext} || ''); my $disabledtextold = trim($::FORM{disabledtextold} || ''); my $groupsetold = trim($::FORM{groupsetold} || ''); + my $blessgroupsetold = trim($::FORM{blessgroupsetold} || ''); my $groupset = "0"; foreach (keys %::FORM) { next unless /^bit_/; #print "$_=$::FORM{$_}<br>\n"; - $groupset .= "+ $::FORM{$_}"; + $groupset .= " + $::FORM{$_}"; + } + my $blessgroupset = "0"; + foreach (keys %::FORM) { + next unless /^blbit_/; + #print "$_=$::FORM{$_}<br>\n"; + $blessgroupset .= " + $::FORM{$_}"; } CheckUser($userold); @@ -608,34 +680,58 @@ if ($action eq 'update') { # Note that the order of this tests is important. If you change # them, be sure to test for WHERE='$product' or WHERE='$productold' - if ($groupset != $groupsetold) { + if ($groupset ne $groupsetold) { SendSQL("UPDATE profiles - SET groupset=" . $groupset . " + SET groupset = + groupset - (groupset & $opblessgroupset) + $groupset WHERE login_name=" . SqlQuote($userold)); + + # I'm paranoid that someone who I give the ability to bless people + # will start misusing it. Let's log who blesses who (even though + # nothing actually uses this log right now). + my $fieldid = GetFieldID("groupset"); + SendSQL("SELECT userid, groupset FROM profiles WHERE login_name=" . + SqlQuote($userold)); + my $u; + ($u, $groupset) = (FetchSQLData()); + if ($groupset ne $groupsetold) { + SendSQL("INSERT INTO profiles_activity " . + "(userid,who,profiles_when,fieldid,oldvalue,newvalue)" . + "VALUES " . + "($u, $::userid, now(), $fieldid, " . + " $groupsetold, $groupset)"); + } print "Updated permissions.\n"; } - if ($emailnotification ne $emailnotificationold) { + if ($editall && $blessgroupset ne $blessgroupsetold) { + SendSQL("UPDATE profiles + SET blessgroupset=" . $blessgroupset . " + WHERE login_name=" . SqlQuote($userold)); + print "Updated ability to tweak permissions of other users.\n"; + } + + if ($editall && $emailnotification ne $emailnotificationold) { SendSQL("UPDATE profiles SET emailnotification=" . SqlQuote($emailnotification) . " WHERE login_name=" . SqlQuote($userold)); print "Updated email notification.<BR>\n"; } - if ($password ne $passwordold) { + if ($editall && $password ne $passwordold) { my $q = SqlQuote($password); SendSQL("UPDATE profiles SET password= $q, cryptpassword = ENCRYPT($q) WHERE login_name=" . SqlQuote($userold)); print "Updated password.<BR>\n"; } - if ($realname ne $realnameold) { + if ($editall && $realname ne $realnameold) { SendSQL("UPDATE profiles SET realname=" . SqlQuote($realname) . " WHERE login_name=" . SqlQuote($userold)); print "Updated real name.<BR>\n"; } - if ($disabledtext ne $disabledtextold) { + if ($editall && $disabledtext ne $disabledtextold) { SendSQL("UPDATE profiles SET disabledtext=" . SqlQuote($disabledtext) . " WHERE login_name=" . SqlQuote($userold)); @@ -647,7 +743,7 @@ if ($action eq 'update') { WHERE userid=" . $userid); print "Updated disabled text.<BR>\n"; } - if ($user ne $userold) { + if ($editall && $user ne $userold) { unless ($user) { print "Sorry, I can't delete the user's name."; PutTrailer($localtrailer); diff --git a/enter_bug.cgi b/enter_bug.cgi index 57d9d0de3..45ca19de5 100755 --- a/enter_bug.cgi +++ b/enter_bug.cgi @@ -204,7 +204,6 @@ PutHeader ("Enter Bug","Enter Bug","This page lets you enter a new bug into Bugz print " <FORM METHOD=POST ACTION=\"post_bug.cgi\"> -<INPUT TYPE=HIDDEN NAME=bug_status VALUE=NEW> <INPUT TYPE=HIDDEN NAME=reporter VALUE=\"$::COOKIE{'Bugzilla_login'}\"> <INPUT TYPE=HIDDEN NAME=product VALUE=\"" . value_quote($product) . "\"> <TABLE CELLSPACING=2 CELLPADDING=0 BORDER=0>"; @@ -260,6 +259,26 @@ print " <td></td> </TR> <tr><td> <td> <td> <td> <td> <td> </tr> +"; + +if (UserInGroup("canedit") || UserInGroup("canconfirm")) { + SendSQL("SELECT votestoconfirm FROM products WHERE product = " . + SqlQuote($product)); + if (FetchOneColumn()) { + print qq{ + <TR> + <TD ALIGN="right"><B><A HREF="bug_status.html#status">Initial state:</B></A></TD> + <TD COLSPAN="5"> +}; + print BuildPulldown("bug_status", + [[$::unconfirmedstate], ["NEW"]], + "NEW"); + print "</TD></TR>"; + } +} + + +print " <tr> <TD ALIGN=RIGHT><B><A HREF=\"bug_status.html#assigned_to\">Assigned To:</A></B></TD> <TD colspan=5>$assign_element diff --git a/globals.pl b/globals.pl index 2f88d41a8..cbfc87357 100644 --- a/globals.pl +++ b/globals.pl @@ -73,6 +73,7 @@ $::param{'version'} = '2.9'; $::dontchange = "--do_not_change--"; $::chooseone = "--Choose_one:--"; $::defaultqueryname = "(Default query)"; +$::unconfirmedstate = "UNCONFIRMED"; sub ConnectToDatabase { if (!defined $::db) { @@ -644,7 +645,7 @@ sub UserInGroup { sub IsOpenedState { my ($state) = (@_); - if ($state =~ /^(NEW|REOPENED|ASSIGNED)$/) { + if ($state =~ /^(NEW|REOPENED|ASSIGNED)$/ || $state eq $::unconfirmedstate) { return 1; } return 0; @@ -652,24 +653,42 @@ sub IsOpenedState { sub RemoveVotes { - my ($id, $reason) = (@_); + my ($id, $who, $reason) = (@_); ConnectToDatabase(); - SendSQL("select profiles.login_name from votes, profiles where votes.bug_id = $id and profiles.userid = votes.who"); + my $whopart = ""; + if ($who) { + $whopart = " AND votes.who = $who"; + } + SendSQL("SELECT profiles.login_name, votes.count " . + "FROM votes, profiles " . + "WHERE votes.bug_id = $id " . + "AND profiles.userid = votes.who" . + $whopart); my @list; while (MoreSQLData()) { - push(@list, FetchOneColumn()); + my ($name, $count) = (@_); + push(@list, [$name, $count]); } if (0 < @list) { - if (open(SENDMAIL, "|/usr/lib/sendmail -t")) { - my %substs; - $substs{"to"} = join(',', @list); - $substs{"bugid"} = $id; - $substs{"reason"} = $reason; - print SENDMAIL PerformSubsts(Param("voteremovedmail"), \%substs); - close SENDMAIL; + foreach my $ref (@list) { + my ($name, $count) = (@$ref); + if (open(SENDMAIL, "|/usr/lib/sendmail -t")) { + my %substs; + $substs{"to"} = $name; + $substs{"bugid"} = $id; + $substs{"reason"} = $reason; + $substs{"count"} = $count; + print SENDMAIL PerformSubsts(Param("voteremovedmail"), + \%substs); + close SENDMAIL; + } } - SendSQL("delete from votes where bug_id = $id"); - SendSQL("update bugs set votes = 0, delta_ts=delta_ts where bug_id = $id"); + SendSQL("DELETE FROM votes WHERE bug_id = $id" . $whopart); + SendSQL("SELECT SUM(count) FROM votes WHERE bug_id = $id"); + my $v = FetchOneColumn(); + $v ||= 0; + SendSQL("UPDATE bugs SET votes = $v, delta_ts = delta_ts " . + "WHERE bug_id = $id"); } } diff --git a/post_bug.cgi b/post_bug.cgi index c3be5c67b..18b579119 100755 --- a/post_bug.cgi +++ b/post_bug.cgi @@ -120,12 +120,32 @@ if (Param("useqacontact")) { +if (exists $::FORM{'bug_status'}) { + if (!UserInGroup("canedit") && !UserInGroup("canconfirm")) { + delete $::FORM{'bug_status'}; + } +} + +if (!exists $::FORM{'bug_status'}) { + $::FORM{'bug_status'} = $::unconfirmedstate; + SendSQL("SELECT votestoconfirm FROM products WHERE product = " . + SqlQuote($::FORM{'product'})); + if (!FetchOneColumn()) { + $::FORM{'bug_status'} = "NEW"; + } +} + + my @used_fields; foreach my $f (@bug_fields) { if (exists $::FORM{$f}) { push (@used_fields, $f); } } +if (exists $::FORM{'bug_status'} && $::FORM{'bug_status'} ne $::unconfirmedstate) { + push(@used_fields, "everconfirmed"); + $::FORM{'everconfirmed'} = 1; +} my $query = "insert into bugs (\n" . join(",\n", @used_fields) . ", creation_ts ) diff --git a/process_bug.cgi b/process_bug.cgi index 9eb32e129..52fc5b423 100755 --- a/process_bug.cgi +++ b/process_bug.cgi @@ -24,6 +24,9 @@ use diagnostics; use strict; +my $UserInEditGroupSet = -1; +my $UserInCanConfirmGroupSet = -1; + require "CGI.pl"; # Shut up misguided -w warnings about "used only once": @@ -38,7 +41,7 @@ use vars %::versions, %::legal_priority, %::legal_severity; -confirm_login(); +my $whoid = confirm_login(); print "Content-type: text/html\n\n"; @@ -103,6 +106,97 @@ if ($::FORM{'product'} ne $::dontchange) { } +# Checks that the user is allowed to change the given field. Actually, right +# now, the rules are pretty simple, and don't look at the field itself very +# much, but that could be enhanced. + +my $lastbugid = 0; +my $ownerid; +my $reporterid; +my $qacontactid; + +sub CheckCanChangeField { + my ($f, $bugid, $oldvalue, $newvalue) = (@_); + if ($f eq "assigned_to" || $f eq "reporter" || $f eq "qa_contact") { + if ($oldvalue =~ /^\d+$/) { + if ($oldvalue == 0) { + $oldvalue = ""; + } else { + $oldvalue = DBID_to_name($oldvalue); + } + } + } + if ($oldvalue eq $newvalue) { + return 1; + } + if ($f =~ /^longdesc/) { + return 1; + } + if ($UserInEditGroupSet < 0) { + $UserInEditGroupSet = UserInGroup("editbugs"); + } + if ($UserInEditGroupSet) { + return 1; + } + if ($lastbugid != $bugid) { + SendSQL("SELECT reporter, assigned_to, qa_contact FROM bugs " . + "WHERE bug_id = $bugid"); + ($reporterid, $ownerid, $qacontactid) = (FetchSQLData()); + } + if ($reporterid eq $whoid || $ownerid eq $whoid || $qacontactid eq $whoid) { + if ($f ne "bug_status") { + return 1; + } + if ($newvalue eq $::unconfirmedstate || !IsOpenedState($newvalue)) { + return 1; + } + + # Hmm. They are trying to set this bug to some opened state + # that isn't the UNCONFIRMED state. Are they in the right + # group? Or, has it ever been confirmed? If not, then this + # isn't legal. + + if ($UserInCanConfirmGroupSet < 0) { + $UserInCanConfirmGroupSet = UserInGroup("canconfirm"); + } + if ($UserInCanConfirmGroupSet) { + return 1; + } + my $fieldid = GetFieldID("bug_status"); + SendSQL("SELECT newvalue FROM bugs_activity " . + "WHERE fieldid = $fieldid " . + " AND oldvalue = '$::unconfirmedstate'"); + while (MoreSQLData()) { + my $n = FetchOneColumn(); + if (IsOpenedState($n) && $n ne $::unconfirmedstate) { + return 1; + } + } + } + SendSQL("UNLOCK TABLES"); + $oldvalue = value_quote($oldvalue); + $newvalue = value_quote($newvalue); + print qq{ +<H1>Sorry, not allowed.</H1> +Only the owner or submitter of the bug, or a sufficiently +empowered user, may make that change to the $f field. +<TABLE> +<TR><TH ALIGN="right">Old value:</TH><TD>$oldvalue</TD></TR> +<TR><TH ALIGN="right">New value:</TH><TD>$newvalue</TD></TR> +</TABLE> +<pre>($reporterid eq $whoid || $ownerid eq $whoid || $qacontactid eq $whoid)</PRE> + +<P>Click <B>Back</B> and try again. +}; + PutFooter(); + exit(); +} + + + + + + my @idlist; if (defined $::FORM{'id'}) { @@ -160,11 +254,29 @@ sub DoComma { $::comma = ","; } +sub DoConfirm { + if ($UserInEditGroupSet < 0) { + $UserInEditGroupSet = UserInGroup("editbugs"); + } + if ($UserInCanConfirmGroupSet < 0) { + $UserInCanConfirmGroupSet = UserInGroup("canconfirm"); + } + if ($UserInEditGroupSet || $UserInCanConfirmGroupSet) { + DoComma(); + $::query .= "everconfirmed = 1"; + } +} + + sub ChangeStatus { my ($str) = (@_); if ($str ne $::dontchange) { DoComma(); - $::query .= "bug_status = '$str'"; + if (IsOpenedState($str)) { + $::query .= "bug_status = IF(everconfirmed = 1, '$str', '$::unconfirmedstate')"; + } else { + $::query .= "bug_status = '$str'"; + } } } @@ -192,7 +304,7 @@ sub CheckonComment( $ ) { if( $ret ) { if (!defined $::FORM{'comment'} || $::FORM{'comment'} =~ /^\s*$/) { - # No commet - sorry, action not allowed ! + # No comment - sorry, action not allowed ! warnBanner("You have to specify a <b>comment</b> on this change." . "<p>" . "Please press <b>Back</b> and give some words " . @@ -275,11 +387,17 @@ SWITCH: for ($::FORM{'knob'}) { /^none$/ && do { last SWITCH; }; + /^confirm$/ && CheckonComment( "confirm" ) && do { + DoConfirm(); + ChangeStatus('NEW'); + last SWITCH; + }; /^accept$/ && CheckonComment( "accept" ) && do { + DoConfirm(); ChangeStatus('ASSIGNED'); last SWITCH; }; - /^clearresolution$/ && CheckonComment( "clearresolution" ) &&do { + /^clearresolution$/ && CheckonComment( "clearresolution" ) && do { ChangeResolution(''); last SWITCH; }; @@ -289,6 +407,9 @@ SWITCH: for ($::FORM{'knob'}) { last SWITCH; }; /^reassign$/ && CheckonComment( "reassign" ) && do { + if ($::FORM{'andconfirm'}) { + DoConfirm(); + } ChangeStatus('NEW'); DoComma(); if ( Param("strictvaluechecks") ) { @@ -460,7 +581,6 @@ sub SnapShotDeps { } -my $whoid = DBNameToIdAndCheck($::FORM{'who'}); my $timestamp; sub LogDependencyActivity { @@ -489,6 +609,13 @@ foreach my $id (@idlist) { "keywords $write, longdescs $write, fielddefs $write, " . "keyworddefs READ, groups READ"); my @oldvalues = SnapShotBug($id); + my $i = 0; + foreach my $col (@::log_columns) { + if (exists $::FORM{$col}) { + CheckCanChangeField($col, $id, $oldvalues[$i], $::FORM{$col}); + } + $i++; + } if (defined $::FORM{'delta_ts'} && $::FORM{'delta_ts'} ne $delta_ts) { print " @@ -730,7 +857,7 @@ The changes made were: # updates about this bug. } if ($col eq 'product') { - RemoveVotes($id, + RemoveVotes($id, 0, "This bug has been moved to a different product"); } $col = GetFieldID($col); diff --git a/showvotes.cgi b/showvotes.cgi index 429c71545..894baafbb 100755 --- a/showvotes.cgi +++ b/showvotes.cgi @@ -84,8 +84,7 @@ if (defined $::FORM{'bug_id'}) { if (!defined $status) { next; } - my $opened = ($status eq "NEW" || $status eq "ASSIGNED" || - $status eq "REOPENED"); + my $opened = IsOpenedState($status); my $strike = $opened ? "" : "<strike>"; my $endstrike = $opened ? "" : "</strike>"; $summary = html_quote($summary); diff --git a/userprefs.cgi b/userprefs.cgi index 0ee7d576f..88f032192 100755 --- a/userprefs.cgi +++ b/userprefs.cgi @@ -215,6 +215,42 @@ sub SaveFooter { +sub ShowPermissions { + print "You have the following permission bits set on your account:\n"; + print "<P><UL>\n"; + my $found = 0; + SendSQL("SELECT description FROM groups " . + "WHERE bit & $::usergroupset != 0 " . + "ORDER BY bit"); + while (MoreSQLData()) { + my ($description) = (FetchSQLData()); + print "<LI>$description\n"; + $found = 1; + } + if ($found == 0) { + print "<LI>(No extra permission bits have been set).\n"; + } + print "</UL>\n"; + SendSQL("SELECT blessgroupset FROM profiles WHERE userid = $userid"); + my $blessgroupset = FetchOneColumn(); + if ($blessgroupset) { + print "And you can turn on or off the following bits for\n"; + print qq{<A HREF="editusers.cgi">other users</A>:\n}; + print "<P><UL>\n"; + SendSQL("SELECT description FROM groups " . + "WHERE bit & $blessgroupset != 0 " . + "ORDER BY bit"); + while (MoreSQLData()) { + my ($description) = (FetchSQLData()); + print "<LI>$description\n"; + } + print "</UL>\n"; + } +} + + + + ###################################################################### ################# Live code (not sub defs) starts here ############### @@ -239,7 +275,9 @@ my @banklist = ( ["diffs", "Email settings", \&ShowDiffs, \&SaveDiffs], ["footer", "Page footer", - \&ShowFooter, \&SaveFooter] + \&ShowFooter, \&SaveFooter], + ["permissions", "Permissions", + \&ShowPermissions, undef] ); @@ -300,9 +338,11 @@ if (defined $bankdescription) { </TABLE> <INPUT TYPE="hidden" NAME="dosave" VALUE="1"> <INPUT TYPE="hidden" NAME="bank" VALUE="$bank"> -<INPUT TYPE="submit" VALUE="Submit"> -</FORM> }; + if ($savefunc) { + print qq{<INPUT TYPE="submit" VALUE="Submit">\n}; + } + print qq{</FORM>\n}; } else { print "<P>Please choose from the above links which settings you wish to change.</P>"; } |