diff options
-rw-r--r-- | CGI.pl | 2 | ||||
-rw-r--r-- | template/default/show/show_bug.html.tmpl | 7 | ||||
-rw-r--r-- | template/default/sidebar/xul.tmpl | 2 | ||||
-rw-r--r-- | template/default/voting/delete-all-votes.html.tmpl | 46 | ||||
-rw-r--r-- | template/default/voting/show-bug-votes.html.tmpl | 49 | ||||
-rw-r--r-- | template/default/voting/show-user-votes.html.tmpl | 130 | ||||
-rw-r--r-- | votehelp.html | 27 | ||||
-rwxr-xr-x | votes.cgi | 354 |
8 files changed, 602 insertions, 15 deletions
@@ -1410,7 +1410,7 @@ Actions: }; if ($loggedin) { if ($::anyvotesallowed) { - $html .= " | <A HREF=\"showvotes.cgi\">My votes</A>\n"; + $html .= " | <A HREF=\"votes.cgi?action=show_user\">My votes</A>\n"; } } if ($loggedin) { diff --git a/template/default/show/show_bug.html.tmpl b/template/default/show/show_bug.html.tmpl index 62e43f222..c6f7deb2b 100644 --- a/template/default/show/show_bug.html.tmpl +++ b/template/default/show/show_bug.html.tmpl @@ -287,9 +287,10 @@ </th> <td> [% bug.votes %] - <a href="showvotes.cgi?bug_id=[% bug.bug_id %]">Show votes for this - bug</a> - <a href="showvotes.cgi?voteon=[% bug.bug_id %]">Vote for this bug</a> + <a href="votes.cgi?action=show_bug&bug_id=[% bug.bug_id %]">Show + votes for this bug</a> + <a href="votes.cgi?action=show_user&bug_id=[% bug.bug_id %]">Vote + for this bug</a> </td> </tr> </table> diff --git a/template/default/sidebar/xul.tmpl b/template/default/sidebar/xul.tmpl index d8af5d3f2..6bae21631 100644 --- a/template/default/sidebar/xul.tmpl +++ b/template/default/sidebar/xul.tmpl @@ -97,7 +97,7 @@ function normal_keypress_handler( aEvent ) { <text class="text-link" onclick="load_relative_url('[% mybugsurl FILTER html %]')" value="my bugs"/> [% END %] [% IF anyvotesallowed && username %] - <text class="text-link" onclick="load_relative_url('showvotes.cgi')" value="my votes"/> + <text class="text-link" onclick="load_relative_url('votes.cgi?action=show_user')" value="my votes"/> [% END %] [% FOREACH name = namedqueries %] diff --git a/template/default/voting/delete-all-votes.html.tmpl b/template/default/voting/delete-all-votes.html.tmpl new file mode 100644 index 000000000..9a63f75af --- /dev/null +++ b/template/default/voting/delete-all-votes.html.tmpl @@ -0,0 +1,46 @@ +<!-- 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): Gervase Markham <gerv@gerv.net> + #%] + +[% INCLUDE global/header + title = "Remove your votes?" + %] + +<p> + You are about to remove all of your bug votes. Are you sure you wish to + remove your vote from every bug you've voted on? +</p> + +<form action="votes.cgi" method="post"> + <input type="hidden" name="action" value="vote"> + <p> + <input type="radio" name="delete_all_votes" value="1"> + Yes, delete all my votes + </p> + <p> + <input type="radio" name="delete_all_votes" value="0" checked="checked"> + No, go back and review my votes + </p> + <p> + <input type="submit" value="Submit"> + </p> +</form> + +[% INCLUDE global/footer %] diff --git a/template/default/voting/show-bug-votes.html.tmpl b/template/default/voting/show-bug-votes.html.tmpl new file mode 100644 index 000000000..a9bcaf5c8 --- /dev/null +++ b/template/default/voting/show-bug-votes.html.tmpl @@ -0,0 +1,49 @@ +<!-- 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): Gervase Markham <gerv@gerv.net> + #%] + +[% INCLUDE global/header + title = "Show Votes" + h2 = "Bug <a href='show_bug.cgi?id=$bug_id'>$bug_id</a>" + %] + +<table cellspacing="4"> + <tr> + <th>Who</th> + <th>Number of votes</th> + </tr> + + [% FOREACH user = users %] + <tr> + <td> + <a href="votes.cgi?action=show_user&user=[% user.name %]"> + [% user.name %] + </a> + </td> + <td align="right"> + [% user.count %] + </td> + </tr> + [% END %] +</table> + +<p>Total votes: [% total %]</p> + +[% INCLUDE global/footer %] diff --git a/template/default/voting/show-user-votes.html.tmpl b/template/default/voting/show-user-votes.html.tmpl new file mode 100644 index 000000000..96d9ad84a --- /dev/null +++ b/template/default/voting/show-user-votes.html.tmpl @@ -0,0 +1,130 @@ +<!-- 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): Gervase Markham <gerv@gerv.net> + #%] + +[% INCLUDE global/header + title = "Show Votes" + h2 = user.name + %] + +[% IF votes_recorded %] + <p> + <font color="red"> + The changes to your votes have been saved. + </font> + </p> +[% ELSE %] + <br> +[% END %] + +[% IF products.size %] + <form action="votes.cgi"> + <input type="hidden" name="action" value="vote"> + <table cellspacing="4"> + <tr> + <td></td> + <th>Bug #</th> + <th>Summary</th> + <th>Votes</th> + </tr> + + [% FOREACH product = products %] + <tr> + <th>[% product.name FILTER html %]</th> + <td></td> + [% IF product.maxperbug < product.maxvotes AND + product.maxperbug > 1 %] + <td> + <font size="-1"> + (Note: only [% product.maxperbug %] vote + [% "s" IF product.maxperbug != 1 %] allowed per bug in + this product.) + </font> + </td> + [% END %] + </tr> + + [% FOREACH bug = product.bugs %] + <tr> + <td></td> + <td> + [% "<strike>" IF NOT bug.opened %] + <a href="show_bug.cgi?id=[% bug.id %]"> + [% bug.id %]</a> + [% "</strike>" IF NOT bug.opened %] + </td> + <td> + <a href="votes.cgi?action=show_bug&bug_id=[% bug.id %]"> + [% bug.summary FILTER html %] + </a> + </td> + <td align="right"> + [% IF user.canedit %] + [% IF product.onevoteonly %] + <input type="checkbox" name="[% bug.id %]" value="1" + [% " checked" IF bug.count %]> + [% ELSE %] + <input name="[% bug.id %]" value="[% bug.count %]" + size="2"> + [% END %] + [% ELSE %] + [% bug.count %] + [% END %] + </td> + </tr> + [% END %] + + <tr> + <td></td> + <td colspan="3">[% product.total %] vote + [% "s" IF product.total != 1 %] used out of [% product.maxvotes %] + allowed. + <br> + <br> + </td> + </tr> + [% END %] + </table> + + [% IF user.canedit %] + <input type="submit" value="Change My Votes"> + <br> + <br> + To change your votes, type in new numbers (using zero to + mean no votes) or change the checkbox, and then click + <b>Change My Votes</b>. + [% END %] + </form> +[% ELSE %] + <p> + [% IF user.canedit %] + You are + [% ELSE %] + This user is + [% END %] + currently not voting on any bugs. + </p> +[% END %] + +<p> + <a href="votehelp.html">Help with voting</a>. +</p> + +[% INCLUDE global/footer %] diff --git a/votehelp.html b/votehelp.html index 93ca5d223..b4c2c8cc0 100644 --- a/votehelp.html +++ b/votehelp.html @@ -1,4 +1,4 @@ -<HTML> +<html> <!-- The contents of this file are subject to the Mozilla Public License Version 1.1 (the "License"); you may not use this file @@ -21,25 +21,29 @@ Contributor(s): Terry Weissman <terry@mozilla.org> --> +<head> +<title>Bugzilla Voting</title> +</head> -<TITLE>Bugzilla Voting</TITLE> -<H1>Bugzilla Voting</H1> +<body> +<h1>Bugzilla Voting</h1> +<p> Bugzilla has a "voting" feature. Each product allows users to have a certain number of votes. (Some products may not allow any, which means you can't vote on things in that product at all.) With your vote, you indicate which bugs you think are the most important to be fixed. +</p> <p> - Depending on how the administrator has configured the relevant product, you may be able to vote for the same bug more than one time. But remember, you only have so many votes to use in total! So, you can either vote a little for many bugs, or vote a lot for a few bugs. +</p> <p> - To look at votes: <ul> @@ -47,9 +51,9 @@ To look at votes: "At least ___ votes" field. This will show you items that match your query that have at least one vote. </ul> +</p> <p> - To vote for a bug: <ul> @@ -64,9 +68,12 @@ To vote for a bug: You will automatically get email notifying you of any changes that occur on bugs you vote for. +</p> <p> - -You may review your votes at any time by clicking on the "My Votes" link in -the page footer (which appears on most pages), or by clicking <a -href="showvotes.cgi">here</a>. +You may review your votes at any time by clicking on the "<a +href="votes.cgi?action=show_user">My Votes</a>" link in +the page footer. +</p> +</body> +</html> diff --git a/votes.cgi b/votes.cgi new file mode 100755 index 000000000..a6a86f426 --- /dev/null +++ b/votes.cgi @@ -0,0 +1,354 @@ +#!/usr/bonsaitools/bin/perl -wT +# -*- 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> +# Stephan Niemz <st.n@gmx.net> +# Christopher Aillon <christopher@aillon.com> +# Gervase Markham <gerv@gerv.net> + +use diagnostics; +use strict; +use lib "."; + +require "CGI.pl"; + +use vars qw($usergroupset); + +# Use global template variables +use vars qw($template $vars); + +ConnectToDatabase(); + +# If the action is show_bug, you need a bug_id. +# If the action is show_user, you can supply a userid to show the votes for +# another user, otherwise you see your own. +# If the action is vote, your votes are set to those encoded in the URL as +# <bug_id>=<votes>. +# +# If no action is defined, we default to show_bug if a bug_id is given, +# otherwise to show_user. +my $action = $::FORM{'action'} || + ($::FORM{'bug_id'} ? "show_bug" : "show_user"); + +if ($action eq "show_bug" || + ($action eq "show_user" && defined($::FORM{'user'}))) +{ + quietly_check_login(); +} +else { + confirm_login(); +} + +################################################################################ +# Begin Data/Security Validation +################################################################################ + +# Make sure the bug ID is a positive integer representing an existing +# bug that the user is authorized to access. +if (defined $::FORM{'bug_id'}) { + ValidateBugID($::FORM{'bug_id'}); +} + +################################################################################ +# End Data/Security Validation +################################################################################ + +if ($action eq "show_bug") { + show_bug(); +} +elsif ($action eq "show_user") { + show_user(); +} +elsif ($action eq "vote") { + record_votes(); + show_user(); +} +else { + DisplayError("Unknown action: " . html_quote($action)); +} + +exit; + +# Display the names of all the people voting for this one bug. +sub show_bug { + my $bug_id = $::FORM{'bug_id'} + || DisplayError("Please give a bug ID to show the votes for.") + && exit; + my $total = 0; + my @users; + + SendSQL("SELECT profiles.login_name, votes.who, votes.count + FROM votes, profiles + WHERE votes.bug_id = $bug_id + AND profiles.userid = votes.who"); + + while (MoreSQLData()) { + my ($name, $userid, $count) = (FetchSQLData()); + push (@users, { name => $name, id => $userid, count => $count }); + $total += $count; + } + + $vars->{'bug_id'} = $bug_id; + $vars->{'users'} = \@users; + $vars->{'total'} = $total; + + print "Content-type: text/html\n\n"; + $template->process("voting/show-bug-votes.html.tmpl", $vars) + || DisplayError("Template process failed: " . $template->error()) + && exit; +} + +# Display all the votes for a particular user. If it's the user +# doing the viewing, give them the option to edit them too. +sub show_user { + GetVersionTable(); + + # If a bug_id is given, and we're editing, we'll add it to the votes list. + my $bug_id = $::FORM{'bug_id'} || ""; + + my $name = $::FORM{'user'} || $::COOKIE{'Bugzilla_login'}; + my $who = DBname_to_id($name); + + # After DBNameToIdAndCheck is templatised and prints a Content-Type, + # the above should revert to a call to that function, and this + # special error handling should go away. + if (!$who) { + DisplayError(html_quote($name) . " is not a valid username.\n"); + exit; + } + + my $canedit = 1 if ($name eq $::COOKIE{'Bugzilla_login'}); + + SendSQL("LOCK TABLES bugs READ, products READ, votes WRITE, + cc AS selectVisible_cc READ"); + + if ($canedit && $bug_id) { + # Make sure there is an entry for this bug + # in the vote table, just so that things display right. + SendSQL("SELECT votes.count FROM votes + WHERE votes.bug_id = $bug_id AND votes.who = $who"); + if (!FetchOneColumn()) { + SendSQL("INSERT INTO votes (who, bug_id, count) + VALUES ($who, $bug_id, 0)"); + } + } + + # Calculate the max votes per bug for each product; doing it here means + # we can do it all in one query. + my %maxvotesperbug; + if($canedit) { + SendSQL("SELECT products.product, products.maxvotesperbug + FROM products"); + while (MoreSQLData()) { + my ($prod, $max) = FetchSQLData(); + $maxvotesperbug{$prod} = $max; + } + } + + my @products; + + # Read the votes data for this user for each product + foreach my $product (sort(keys(%::prodmaxvotes))) { + next if $::prodmaxvotes{$product} <= 0; + + my @bugs; + my $total = 0; + my $onevoteonly = 0; + + SendSQL("SELECT votes.bug_id, votes.count, bugs.short_desc, + bugs.bug_status + FROM votes, bugs + WHERE votes.who = $who + AND votes.bug_id = bugs.bug_id + AND bugs.product = " . SqlQuote($product) . + "ORDER BY votes.bug_id"); + + while (MoreSQLData()) { + my ($id, $count, $summary, $status) = FetchSQLData(); + next if !defined($status); + $total += $count; + + # Next if user can't see this bug. So, the totals will be correct + # and they can see there are votes 'missing', but not on what bug + # they are. This seems a reasonable compromise; the alternative is + # to lie in the totals. + next if !CanSeeBug($id, $who, $usergroupset); + + push (@bugs, { id => $id, + summary => $summary, + count => $count, + opened => IsOpenedState($status) }); + } + + $onevoteonly = 1 if (min($::prodmaxvotes{$product}, + $maxvotesperbug{$product}) == 1); + + # Only add the product for display if there are any bugs in it. + if ($#bugs > -1) { + push (@products, { name => $product, + bugs => \@bugs, + onevoteonly => $onevoteonly, + total => $total, + maxvotes => $::prodmaxvotes{$product}, + maxperbug => $maxvotesperbug{$product} }); + } + } + + SendSQL("DELETE FROM votes WHERE count <= 0"); + SendSQL("UNLOCK TABLES"); + + $vars->{'user'} = { canedit => $canedit, name => $name, id => $who }; + $vars->{'products'} = \@products; + + print "Content-type: text/html\n\n"; + $template->process("voting/show-user-votes.html.tmpl", $vars) + || DisplayError("Template process failed: " . $template->error()) + && exit; +} + +# Update the user's votes in the database. +sub record_votes { + ############################################################################ + # Begin Data/Security Validation + ############################################################################ + + # Build a list of bug IDs for which votes have been submitted. Votes + # are submitted in form fields in which the field names are the bug + # IDs and the field values are the number of votes. + my @buglist = grep {/^[1-9][0-9]*$/} keys(%::FORM); + + # If no bugs are in the buglist, let's make sure the user gets notified + # that their votes will get nuked if they continue. + if (scalar(@buglist) == 0) { + if (!defined($::FORM{'delete_all_votes'})) { + print "Content-type: text/html\n\n"; + $template->process("voting/delete-all-votes.html.tmpl", $vars) + || DisplayError("Template process failed: " . $template->error()); + exit(); + } + elsif ($::FORM{'delete_all_votes'} == 0) { + print "Location: votes.cgi\n\n"; + exit(); + } + } + + # Call ValidateBugID on each bug ID to make sure it is a positive + # integer representing an existing bug that the user is authorized + # to access, and make sure the number of votes submitted is also + # a non-negative integer (a series of digits not preceded by a + # minus sign). + foreach my $id (@buglist) { + ValidateBugID($id); + detaint_natural($::FORM{$id}) + || DisplayError("Only use non-negative numbers for your bug votes.") + && exit; + } + + ############################################################################ + # End Data/Security Validation + ############################################################################ + + GetVersionTable(); + + my $who = DBNameToIdAndCheck($::COOKIE{'Bugzilla_login'}); + + # If the user is voting for bugs, make sure they aren't overstuffing + # the ballot box. + if (scalar(@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, $max) = FetchSQLData(); + $prodcount{$prod} ||= 0; + $prodcount{$prod} += $::FORM{$id}; + + # Make sure we haven't broken the votes-per-bug limit + if ($::FORM{$id} > $max) { + $prod = html_quote($prod); + my $votes = html_quote($::FORM{$id}); + + DisplayError("You may only use at most $max votes for a single + bug in the <tt>$prod</tt> product, but you are + trying to use $votes.", "Illegal vote"); + exit(); + } + } + + # Make sure we haven't broken the votes-per-product limit + foreach my $prod (keys(%prodcount)) { + if ($prodcount{$prod} > $::prodmaxvotes{$prod}) { + $prod = html_quote($prod); + + DisplayError("You may only use at most $::prodmaxvotes{$prod} + votes for bugs in the <tt>$prod</tt> product, + but you are trying to use $prodcount{$prod}.", + "Illegal vote"); + exit(); + } + } + } + + # Update the user's votes in the database. If the user did not submit + # any votes, they may be using a form with checkboxes to remove all their + # votes (checkboxes are not submitted along with other form data when + # they are not checked, and Bugzilla uses them to represent single votes + # for products that only allow one vote per bug). In that case, we still + # need to clear the user's votes from the database. + my %affected; + SendSQL("LOCK TABLES bugs write, votes write, products read"); + + # Take note of, and delete the user's old votes from the database. + SendSQL("SELECT bug_id FROM votes WHERE who = $who"); + while (MoreSQLData()) { + my $id = FetchOneColumn(); + $affected{$id} = 1; + } + + SendSQL("DELETE FROM votes WHERE who = $who"); + + # Insert the new values in their place + foreach my $id (@buglist) { + if ($::FORM{$id} > 0) { + SendSQL("INSERT INTO votes (who, bug_id, count) + VALUES ($who, $id, $::FORM{$id})"); + } + + $affected{$id} = 1; + } + + # Update the cached values in the bugs table + foreach my $id (keys %affected) { + 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"); + CheckIfVotedConfirmed($id, $who); + } + + SendSQL("UNLOCK TABLES"); + + $vars->{'votes_recorded'} = 1; +} |