diff options
Diffstat (limited to 'votes.cgi')
-rwxr-xr-x | votes.cgi | 354 |
1 files changed, 354 insertions, 0 deletions
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; +} |