]> git.ipfire.org Git - thirdparty/bugzilla.git/commitdiff
Bug 117760 - Templatise showvotes.cgi and incorporate doeditvotes.cgi. Patch by gerv...
authorgerv%gerv.net <>
Fri, 5 Apr 2002 15:13:46 +0000 (15:13 +0000)
committergerv%gerv.net <>
Fri, 5 Apr 2002 15:13:46 +0000 (15:13 +0000)
CGI.pl
template/default/show/show_bug.html.tmpl
template/default/sidebar/xul.tmpl
template/default/voting/delete-all-votes.html.tmpl [new file with mode: 0644]
template/default/voting/show-bug-votes.html.tmpl [new file with mode: 0644]
template/default/voting/show-user-votes.html.tmpl [new file with mode: 0644]
votehelp.html
votes.cgi [new file with mode: 0755]

diff --git a/CGI.pl b/CGI.pl
index 578c73a94b732f949319c784493eebd2e2522b8b..2971d0899abeba6036f8964427ec1afaa7b7cbb9 100644 (file)
--- a/CGI.pl
+++ b/CGI.pl
@@ -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) {
index 62e43f2225be482125d8347fb14d8c5d50ab0754..c6f7deb2bc350d6b4cf7044a338c35cc674b4589 100644 (file)
       </th>
       <td>
         [% bug.votes %]&nbsp;&nbsp;&nbsp;
-        <a href="showvotes.cgi?bug_id=[% bug.bug_id %]">Show votes for this 
-          bug</a>&nbsp;&nbsp;&nbsp;
-        <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>&nbsp;&nbsp;&nbsp;
+        <a href="votes.cgi?action=show_user&bug_id=[% bug.bug_id %]">Vote 
+        for this bug</a>
       </td>
     </tr>
   </table>
index d8af5d3f2052a086e6ce8c19ccfb10b1972e15fd..6bae21631f88a4a2f7fb753e0df70dc242d86713 100644 (file)
@@ -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 (file)
index 0000000..9a63f75
--- /dev/null
@@ -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 (file)
index 0000000..a9bcaf5
--- /dev/null
@@ -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 (file)
index 0000000..96d9ad8
--- /dev/null
@@ -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 %]
index 93ca5d2236b95a00b2efe0261db067957697c089..b4c2c8cc088625f2db9c367cceee70e779b1e782 100644 (file)
@@ -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
 
      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 (executable)
index 0000000..a6a86f4
--- /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;
+}