votes.cgi 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357
  1. #!/usr/bin/env perl -wT
  2. # -*- Mode: perl; indent-tabs-mode: nil -*-
  3. #
  4. # The contents of this file are subject to the Mozilla Public
  5. # License Version 1.1 (the "License"); you may not use this file
  6. # except in compliance with the License. You may obtain a copy of
  7. # the License at http://www.mozilla.org/MPL/
  8. #
  9. # Software distributed under the License is distributed on an "AS
  10. # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
  11. # implied. See the License for the specific language governing
  12. # rights and limitations under the License.
  13. #
  14. # The Original Code is the Bugzilla Bug Tracking System.
  15. #
  16. # The Initial Developer of the Original Code is Netscape Communications
  17. # Corporation. Portions created by Netscape are
  18. # Copyright (C) 1998 Netscape Communications Corporation. All
  19. # Rights Reserved.
  20. #
  21. # Contributor(s): Terry Weissman <terry@mozilla.org>
  22. # Stephan Niemz <st.n@gmx.net>
  23. # Christopher Aillon <christopher@aillon.com>
  24. # Gervase Markham <gerv@gerv.net>
  25. # Frédéric Buclin <LpSolit@gmail.com>
  26. use strict;
  27. use lib qw(. lib);
  28. use Bugzilla;
  29. use Bugzilla::Constants;
  30. use Bugzilla::Util;
  31. use Bugzilla::Error;
  32. use Bugzilla::Bug;
  33. use Bugzilla::User;
  34. use Bugzilla::Product;
  35. use List::Util qw(min);
  36. my $cgi = Bugzilla->cgi;
  37. local our $vars = {};
  38. # If the action is show_bug, you need a bug_id.
  39. # If the action is show_user, you can supply a userid to show the votes for
  40. # another user, otherwise you see your own.
  41. # If the action is vote, your votes are set to those encoded in the URL as
  42. # <bug_id>=<votes>.
  43. #
  44. # If no action is defined, we default to show_bug if a bug_id is given,
  45. # otherwise to show_user.
  46. my $bug_id = $cgi->param('bug_id');
  47. my $action = $cgi->param('action') || ($bug_id ? "show_bug" : "show_user");
  48. if ($action eq "show_bug" ||
  49. ($action eq "show_user" && defined $cgi->param('user')))
  50. {
  51. Bugzilla->login();
  52. }
  53. else {
  54. Bugzilla->login(LOGIN_REQUIRED);
  55. }
  56. ################################################################################
  57. # Begin Data/Security Validation
  58. ################################################################################
  59. # Make sure the bug ID is a positive integer representing an existing
  60. # bug that the user is authorized to access.
  61. ValidateBugID($bug_id) if defined $bug_id;
  62. ################################################################################
  63. # End Data/Security Validation
  64. ################################################################################
  65. if ($action eq "show_bug") {
  66. show_bug($bug_id);
  67. }
  68. elsif ($action eq "show_user") {
  69. show_user($bug_id);
  70. }
  71. elsif ($action eq "vote") {
  72. record_votes() if Bugzilla->params->{'usevotes'};
  73. show_user($bug_id);
  74. }
  75. else {
  76. ThrowCodeError("unknown_action", {action => $action});
  77. }
  78. exit;
  79. # Display the names of all the people voting for this one bug.
  80. sub show_bug {
  81. my ($bug_id) = @_;
  82. my $cgi = Bugzilla->cgi;
  83. my $dbh = Bugzilla->dbh;
  84. my $template = Bugzilla->template;
  85. ThrowCodeError("missing_bug_id") unless defined $bug_id;
  86. $vars->{'bug_id'} = $bug_id;
  87. $vars->{'users'} =
  88. $dbh->selectall_arrayref('SELECT profiles.login_name, votes.vote_count
  89. FROM votes
  90. INNER JOIN profiles
  91. ON profiles.userid = votes.who
  92. WHERE votes.bug_id = ?',
  93. {'Slice' => {}}, $bug_id);
  94. print $cgi->header();
  95. $template->process("bug/votes/list-for-bug.html.tmpl", $vars)
  96. || ThrowTemplateError($template->error());
  97. }
  98. # Display all the votes for a particular user. If it's the user
  99. # doing the viewing, give them the option to edit them too.
  100. sub show_user {
  101. my ($bug_id) = @_;
  102. my $cgi = Bugzilla->cgi;
  103. my $dbh = Bugzilla->dbh;
  104. my $user = Bugzilla->user;
  105. my $template = Bugzilla->template;
  106. # If a bug_id is given, and we're editing, we'll add it to the votes list.
  107. $bug_id ||= "";
  108. my $name = $cgi->param('user') || $user->login;
  109. my $who = login_to_id($name, THROW_ERROR);
  110. my $userid = $user->id;
  111. my $canedit = (Bugzilla->params->{'usevotes'} && $userid == $who) ? 1 : 0;
  112. $dbh->bz_start_transaction();
  113. if ($canedit && $bug_id) {
  114. # Make sure there is an entry for this bug
  115. # in the vote table, just so that things display right.
  116. my $has_votes = $dbh->selectrow_array('SELECT vote_count FROM votes
  117. WHERE bug_id = ? AND who = ?',
  118. undef, ($bug_id, $who));
  119. if (!$has_votes) {
  120. $dbh->do('INSERT INTO votes (who, bug_id, vote_count)
  121. VALUES (?, ?, 0)', undef, ($who, $bug_id));
  122. }
  123. }
  124. my @all_bug_ids;
  125. my @products;
  126. my $products = $user->get_selectable_products;
  127. # Read the votes data for this user for each product.
  128. foreach my $product (@$products) {
  129. next unless ($product->votes_per_user > 0);
  130. my @bugs;
  131. my @bug_ids;
  132. my $total = 0;
  133. my $onevoteonly = 0;
  134. my $vote_list =
  135. $dbh->selectall_arrayref('SELECT votes.bug_id, votes.vote_count,
  136. bugs.short_desc
  137. FROM votes
  138. INNER JOIN bugs
  139. ON votes.bug_id = bugs.bug_id
  140. WHERE votes.who = ?
  141. AND bugs.product_id = ?
  142. ORDER BY votes.bug_id',
  143. undef, ($who, $product->id));
  144. foreach (@$vote_list) {
  145. my ($id, $count, $summary) = @$_;
  146. $total += $count;
  147. # Next if user can't see this bug. So, the totals will be correct
  148. # and they can see there are votes 'missing', but not on what bug
  149. # they are. This seems a reasonable compromise; the alternative is
  150. # to lie in the totals.
  151. next if !$user->can_see_bug($id);
  152. push (@bugs, { id => $id,
  153. summary => $summary,
  154. count => $count });
  155. push (@bug_ids, $id);
  156. push (@all_bug_ids, $id);
  157. }
  158. $onevoteonly = 1 if (min($product->votes_per_user,
  159. $product->max_votes_per_bug) == 1);
  160. # Only add the product for display if there are any bugs in it.
  161. if ($#bugs > -1) {
  162. push (@products, { name => $product->name,
  163. bugs => \@bugs,
  164. bug_ids => \@bug_ids,
  165. onevoteonly => $onevoteonly,
  166. total => $total,
  167. maxvotes => $product->votes_per_user,
  168. maxperbug => $product->max_votes_per_bug });
  169. }
  170. }
  171. $dbh->do('DELETE FROM votes WHERE vote_count <= 0');
  172. $dbh->bz_commit_transaction();
  173. $vars->{'canedit'} = $canedit;
  174. $vars->{'voting_user'} = { "login" => $name };
  175. $vars->{'products'} = \@products;
  176. $vars->{'bug_id'} = $bug_id;
  177. $vars->{'all_bug_ids'} = \@all_bug_ids;
  178. print $cgi->header();
  179. $template->process("bug/votes/list-for-user.html.tmpl", $vars)
  180. || ThrowTemplateError($template->error());
  181. }
  182. # Update the user's votes in the database.
  183. sub record_votes {
  184. ############################################################################
  185. # Begin Data/Security Validation
  186. ############################################################################
  187. my $cgi = Bugzilla->cgi;
  188. my $dbh = Bugzilla->dbh;
  189. my $template = Bugzilla->template;
  190. # Build a list of bug IDs for which votes have been submitted. Votes
  191. # are submitted in form fields in which the field names are the bug
  192. # IDs and the field values are the number of votes.
  193. my @buglist = grep {/^[1-9][0-9]*$/} $cgi->param();
  194. # If no bugs are in the buglist, let's make sure the user gets notified
  195. # that their votes will get nuked if they continue.
  196. if (scalar(@buglist) == 0) {
  197. if (!defined $cgi->param('delete_all_votes')) {
  198. print $cgi->header();
  199. $template->process("bug/votes/delete-all.html.tmpl", $vars)
  200. || ThrowTemplateError($template->error());
  201. exit();
  202. }
  203. elsif ($cgi->param('delete_all_votes') == 0) {
  204. print $cgi->redirect("votes.cgi");
  205. exit();
  206. }
  207. }
  208. # Call ValidateBugID on each bug ID to make sure it is a positive
  209. # integer representing an existing bug that the user is authorized
  210. # to access, and make sure the number of votes submitted is also
  211. # a non-negative integer (a series of digits not preceded by a
  212. # minus sign).
  213. my %votes;
  214. foreach my $id (@buglist) {
  215. ValidateBugID($id);
  216. $votes{$id} = $cgi->param($id);
  217. detaint_natural($votes{$id})
  218. || ThrowUserError("votes_must_be_nonnegative");
  219. }
  220. ############################################################################
  221. # End Data/Security Validation
  222. ############################################################################
  223. my $who = Bugzilla->user->id;
  224. # If the user is voting for bugs, make sure they aren't overstuffing
  225. # the ballot box.
  226. if (scalar(@buglist)) {
  227. my %prodcount;
  228. my %products;
  229. # XXX - We really need a $bug->product() method.
  230. foreach my $bug_id (@buglist) {
  231. my $bug = new Bugzilla::Bug($bug_id);
  232. my $prod = $bug->product;
  233. $products{$prod} ||= new Bugzilla::Product({name => $prod});
  234. $prodcount{$prod} ||= 0;
  235. $prodcount{$prod} += $votes{$bug_id};
  236. # Make sure we haven't broken the votes-per-bug limit
  237. ($votes{$bug_id} <= $products{$prod}->max_votes_per_bug)
  238. || ThrowUserError("too_many_votes_for_bug",
  239. {max => $products{$prod}->max_votes_per_bug,
  240. product => $prod,
  241. votes => $votes{$bug_id}});
  242. }
  243. # Make sure we haven't broken the votes-per-product limit
  244. foreach my $prod (keys(%prodcount)) {
  245. ($prodcount{$prod} <= $products{$prod}->votes_per_user)
  246. || ThrowUserError("too_many_votes_for_product",
  247. {max => $products{$prod}->votes_per_user,
  248. product => $prod,
  249. votes => $prodcount{$prod}});
  250. }
  251. }
  252. # Update the user's votes in the database. If the user did not submit
  253. # any votes, they may be using a form with checkboxes to remove all their
  254. # votes (checkboxes are not submitted along with other form data when
  255. # they are not checked, and Bugzilla uses them to represent single votes
  256. # for products that only allow one vote per bug). In that case, we still
  257. # need to clear the user's votes from the database.
  258. my %affected;
  259. $dbh->bz_start_transaction();
  260. # Take note of, and delete the user's old votes from the database.
  261. my $bug_list = $dbh->selectcol_arrayref('SELECT bug_id FROM votes
  262. WHERE who = ?', undef, $who);
  263. foreach my $id (@$bug_list) {
  264. $affected{$id} = 1;
  265. }
  266. $dbh->do('DELETE FROM votes WHERE who = ?', undef, $who);
  267. my $sth_insertVotes = $dbh->prepare('INSERT INTO votes (who, bug_id, vote_count)
  268. VALUES (?, ?, ?)');
  269. # Insert the new values in their place
  270. foreach my $id (@buglist) {
  271. if ($votes{$id} > 0) {
  272. $sth_insertVotes->execute($who, $id, $votes{$id});
  273. }
  274. $affected{$id} = 1;
  275. }
  276. # Update the cached values in the bugs table
  277. print $cgi->header();
  278. my @updated_bugs = ();
  279. my $sth_getVotes = $dbh->prepare("SELECT SUM(vote_count) FROM votes
  280. WHERE bug_id = ?");
  281. my $sth_updateVotes = $dbh->prepare("UPDATE bugs SET votes = ?
  282. WHERE bug_id = ?");
  283. foreach my $id (keys %affected) {
  284. $sth_getVotes->execute($id);
  285. my $v = $sth_getVotes->fetchrow_array || 0;
  286. $sth_updateVotes->execute($v, $id);
  287. my $confirmed = CheckIfVotedConfirmed($id, $who);
  288. push (@updated_bugs, $id) if $confirmed;
  289. }
  290. $dbh->bz_commit_transaction();
  291. $vars->{'type'} = "votes";
  292. $vars->{'mailrecipients'} = { 'changer' => Bugzilla->user->login };
  293. $vars->{'title_tag'} = 'change_votes';
  294. foreach my $bug_id (@updated_bugs) {
  295. $vars->{'id'} = $bug_id;
  296. $template->process("bug/process/results.html.tmpl", $vars)
  297. || ThrowTemplateError($template->error());
  298. # Set header_done to 1 only after the first bug.
  299. $vars->{'header_done'} = 1;
  300. }
  301. $vars->{'votes_recorded'} = 1;
  302. }