diff --git a/mozilla/webtools/bugzilla/.bzrignore b/mozilla/webtools/bugzilla/.bzrignore
index 8415e356ba6..5ee4b214df1 100644
--- a/mozilla/webtools/bugzilla/.bzrignore
+++ b/mozilla/webtools/bugzilla/.bzrignore
@@ -27,6 +27,5 @@
/skins/contrib/Dusk/show_bug.css
/skins/contrib/Dusk/show_multiple.css
/skins/contrib/Dusk/summarize-time.css
-/skins/contrib/Dusk/voting.css
/skins/contrib/Dusk/yui
.DS_Store
diff --git a/mozilla/webtools/bugzilla/.bzrrev b/mozilla/webtools/bugzilla/.bzrrev
index 2cc430b9be2..4108896befb 100644
--- a/mozilla/webtools/bugzilla/.bzrrev
+++ b/mozilla/webtools/bugzilla/.bzrrev
@@ -1 +1 @@
-6990
\ No newline at end of file
+6991
\ No newline at end of file
diff --git a/mozilla/webtools/bugzilla/Bugzilla/Bug.pm b/mozilla/webtools/bugzilla/Bugzilla/Bug.pm
index 336b9cfe105..b3f0fe58e20 100644
--- a/mozilla/webtools/bugzilla/Bugzilla/Bug.pm
+++ b/mozilla/webtools/bugzilla/Bugzilla/Bug.pm
@@ -57,7 +57,6 @@ use URI::QueryParam;
use base qw(Bugzilla::Object Exporter);
@Bugzilla::Bug::EXPORT = qw(
bug_alias_to_id
- RemoveVotes CheckIfVotedConfirmed
LogActivityEntry
editable_bug_fields
);
@@ -631,7 +630,6 @@ sub run_create_validators {
# You can't set these fields on bug creation (or sometimes ever).
delete $params->{resolution};
- delete $params->{votes};
delete $params->{lastdiffed};
delete $params->{bug_id};
@@ -967,7 +965,6 @@ sub remove_from_db {
# - flags
# - keywords
# - longdescs
- # - votes
# Also, the attach_data table uses attachments.attach_id as a foreign
# key, and so indirectly depends on a bug deletion too.
@@ -983,7 +980,6 @@ sub remove_from_db {
undef, ($bug_id, $bug_id));
$dbh->do("DELETE FROM flags WHERE bug_id = ?", undef, $bug_id);
$dbh->do("DELETE FROM keywords WHERE bug_id = ?", undef, $bug_id);
- $dbh->do("DELETE FROM votes WHERE bug_id = ?", undef, $bug_id);
# The attach_data table doesn't depend on bugs.bug_id directly.
my $attach_ids =
@@ -1819,7 +1815,7 @@ sub fields {
bug_status resolution dup_id see_also
bug_file_loc status_whiteboard keywords
priority bug_severity target_milestone
- dependson blocked votes everconfirmed
+ dependson blocked everconfirmed
reporter assigned_to cc estimated_time
remaining_time actual_time deadline),
@@ -2870,14 +2866,6 @@ sub show_attachment_flags {
return $self->{'show_attachment_flags'};
}
-sub use_votes {
- my ($self) = @_;
- return 0 if $self->{'error'};
-
- return Bugzilla->params->{'usevotes'}
- && $self->product_obj->votes_per_user > 0;
-}
-
sub groups {
my $self = shift;
return $self->{'groups'} if exists $self->{'groups'};
@@ -3019,20 +3007,6 @@ sub choices {
return $self->{'choices'};
}
-sub votes {
- my ($self) = @_;
- return 0 if $self->{error};
- return $self->{votes} if defined $self->{votes};
-
- my $dbh = Bugzilla->dbh;
- $self->{votes} = $dbh->selectrow_array(
- 'SELECT SUM(vote_count) FROM votes
- WHERE bug_id = ? ' . $dbh->sql_group_by('bug_id'),
- undef, $self->bug_id);
- $self->{votes} ||= 0;
- return $self->{votes};
-}
-
# Convenience Function. If you need speed, use this. If you need
# other Bug fields in addition to this, just create a new Bug with
# the alias.
@@ -3312,136 +3286,6 @@ sub CountOpenDependencies {
return @dependencies;
}
-# If a bug is moved to a product which allows less votes per bug
-# compared to the previous product, extra votes need to be removed.
-sub RemoveVotes {
- my ($id, $who, $reason) = (@_);
- my $dbh = Bugzilla->dbh;
-
- my $whopart = ($who) ? " AND votes.who = $who" : "";
-
- my $sth = $dbh->prepare("SELECT profiles.login_name, " .
- "profiles.userid, votes.vote_count, " .
- "products.votesperuser, products.maxvotesperbug " .
- "FROM profiles " .
- "LEFT JOIN votes ON profiles.userid = votes.who " .
- "LEFT JOIN bugs ON votes.bug_id = bugs.bug_id " .
- "LEFT JOIN products ON products.id = bugs.product_id " .
- "WHERE votes.bug_id = ? " . $whopart);
- $sth->execute($id);
- my @list;
- while (my ($name, $userid, $oldvotes, $votesperuser, $maxvotesperbug) = $sth->fetchrow_array()) {
- push(@list, [$name, $userid, $oldvotes, $votesperuser, $maxvotesperbug]);
- }
-
- # @messages stores all emails which have to be sent, if any.
- # This array is passed to the caller which will send these emails itself.
- my @messages = ();
-
- if (scalar(@list)) {
- foreach my $ref (@list) {
- my ($name, $userid, $oldvotes, $votesperuser, $maxvotesperbug) = (@$ref);
-
- $maxvotesperbug = min($votesperuser, $maxvotesperbug);
-
- # If this product allows voting and the user's votes are in
- # the acceptable range, then don't do anything.
- next if $votesperuser && $oldvotes <= $maxvotesperbug;
-
- # If the user has more votes on this bug than this product
- # allows, then reduce the number of votes so it fits
- my $newvotes = $maxvotesperbug;
-
- my $removedvotes = $oldvotes - $newvotes;
-
- if ($newvotes) {
- $dbh->do("UPDATE votes SET vote_count = ? " .
- "WHERE bug_id = ? AND who = ?",
- undef, ($newvotes, $id, $userid));
- } else {
- $dbh->do("DELETE FROM votes WHERE bug_id = ? AND who = ?",
- undef, ($id, $userid));
- }
-
- # Notice that we did not make sure that the user fit within the $votesperuser
- # range. This is considered to be an acceptable alternative to losing votes
- # during product moves. Then next time the user attempts to change their votes,
- # they will be forced to fit within the $votesperuser limit.
-
- # Now lets send the e-mail to alert the user to the fact that their votes have
- # been reduced or removed.
- my $vars = {
- 'to' => $name . Bugzilla->params->{'emailsuffix'},
- 'bugid' => $id,
- 'reason' => $reason,
-
- 'votesremoved' => $removedvotes,
- 'votesold' => $oldvotes,
- 'votesnew' => $newvotes,
- };
-
- my $voter = new Bugzilla::User($userid);
- my $template = Bugzilla->template_inner($voter->settings->{'lang'}->{'value'});
-
- my $msg;
- $template->process("email/votes-removed.txt.tmpl", $vars, \$msg);
- push(@messages, $msg);
- }
- Bugzilla->template_inner("");
-
- my $votes = $dbh->selectrow_array("SELECT SUM(vote_count) " .
- "FROM votes WHERE bug_id = ?",
- undef, $id) || 0;
- $dbh->do("UPDATE bugs SET votes = ? WHERE bug_id = ?",
- undef, ($votes, $id));
- }
- # Now return the array containing emails to be sent.
- return @messages;
-}
-
-# If a user votes for a bug, or the number of votes required to
-# confirm a bug has been reduced, check if the bug is now confirmed.
-sub CheckIfVotedConfirmed {
- my $id = shift;
- my $bug = new Bugzilla::Bug($id);
-
- my $ret = 0;
- if (!$bug->everconfirmed
- and $bug->product_obj->votes_to_confirm
- and $bug->votes >= $bug->product_obj->votes_to_confirm)
- {
- $bug->add_comment('', { type => CMT_POPULAR_VOTES });
-
- if ($bug->bug_status eq 'UNCONFIRMED') {
- # Get a valid open state.
- my $new_status;
- foreach my $state (@{$bug->status->can_change_to}) {
- if ($state->is_open && $state->name ne 'UNCONFIRMED') {
- $new_status = $state->name;
- last;
- }
- }
- ThrowCodeError('no_open_bug_status') unless $new_status;
-
- # We cannot call $bug->set_status() here, because a user without
- # canconfirm privs should still be able to confirm a bug by
- # popular vote. We already know the new status is valid, so it's safe.
- $bug->{bug_status} = $new_status;
- $bug->{everconfirmed} = 1;
- delete $bug->{'status'}; # Contains the status object.
- }
- else {
- # If the bug is in a closed state, only set everconfirmed to 1.
- # Do not call $bug->_set_everconfirmed(), for the same reason as above.
- $bug->{everconfirmed} = 1;
- }
- $bug->update();
-
- $ret = 1;
- }
- return $ret;
-}
-
################################################################################
# check_can_change_field() defines what users are allowed to change. You
# can add code here for site-specific policy changes, according to the
diff --git a/mozilla/webtools/bugzilla/Bugzilla/BugMail.pm b/mozilla/webtools/bugzilla/Bugzilla/BugMail.pm
index 204c4ba9a05..e7694c32e17 100644
--- a/mozilla/webtools/bugzilla/Bugzilla/BugMail.pm
+++ b/mozilla/webtools/bugzilla/Bugzilla/BugMail.pm
@@ -377,12 +377,6 @@ sub Send {
# the relationships in a hash. The keys are userids, the values are an
# array of role constants.
- # Voters
- my $voters = $dbh->selectcol_arrayref(
- "SELECT who FROM votes WHERE bug_id = ?", undef, ($id));
-
- $recipients{$_}->{+REL_VOTER} = BIT_DIRECT foreach (@$voters);
-
# CCs
$recipients{$_}->{+REL_CC} = BIT_DIRECT foreach (@ccs);
@@ -405,8 +399,8 @@ sub Send {
foreach my $ref (@$diffs) {
my ($who, $whoname, $what, $when, $old, $new) = (@$ref);
if ($old) {
- # You can't stop being the reporter, and mail isn't sent if you
- # remove your vote.
+ # You can't stop being the reporter, so we don't check that
+ # relationship here.
# Ignore people whose user account has been deleted or renamed.
if ($what eq "CC") {
foreach my $cc_user (split(/[\s,]+/, $old)) {
@@ -462,7 +456,6 @@ sub Send {
foreach my $user_id (keys %recipients) {
my %rels_which_want;
my $sent_mail = 0;
-
my $user = new Bugzilla::User($user_id);
# Deleted users must be excluded.
next unless $user;
diff --git a/mozilla/webtools/bugzilla/Bugzilla/Comment.pm b/mozilla/webtools/bugzilla/Bugzilla/Comment.pm
index 300357313e2..60d26012f1d 100644
--- a/mozilla/webtools/bugzilla/Bugzilla/Comment.pm
+++ b/mozilla/webtools/bugzilla/Bugzilla/Comment.pm
@@ -148,7 +148,7 @@ sub set_type {
sub _check_extra_data {
my ($invocant, $extra_data, $type) = @_;
$type = $invocant->type if ref $invocant;
- if ($type == CMT_NORMAL or $type == CMT_POPULAR_VOTES) {
+ if ($type == CMT_NORMAL) {
if (defined $extra_data) {
ThrowCodeError('comment_extra_data_not_allowed',
{ type => $type, extra_data => $extra_data });
diff --git a/mozilla/webtools/bugzilla/Bugzilla/Config/BugFields.pm b/mozilla/webtools/bugzilla/Bugzilla/Config/BugFields.pm
index 9f3ddc9abb1..d0de9dac601 100644
--- a/mozilla/webtools/bugzilla/Bugzilla/Config/BugFields.pm
+++ b/mozilla/webtools/bugzilla/Bugzilla/Config/BugFields.pm
@@ -71,12 +71,6 @@ sub get_param_list {
default => 0
},
- {
- name => 'usevotes',
- type => 'b',
- default => 0
- },
-
{
name => 'usebugaliases',
type => 'b',
diff --git a/mozilla/webtools/bugzilla/Bugzilla/Constants.pm b/mozilla/webtools/bugzilla/Bugzilla/Constants.pm
index 8ab7455ff60..d626c97497a 100644
--- a/mozilla/webtools/bugzilla/Bugzilla/Constants.pm
+++ b/mozilla/webtools/bugzilla/Bugzilla/Constants.pm
@@ -90,7 +90,6 @@ use File::Basename;
CMT_NORMAL
CMT_DUPE_OF
CMT_HAS_DUPE
- CMT_POPULAR_VOTES
CMT_MOVED_TO
CMT_ATTACHMENT_CREATED
CMT_ATTACHMENT_UPDATED
@@ -98,7 +97,7 @@ use File::Basename;
THROW_ERROR
RELATIONSHIPS
- REL_ASSIGNEE REL_QA REL_REPORTER REL_CC REL_VOTER REL_GLOBAL_WATCHER
+ REL_ASSIGNEE REL_QA REL_REPORTER REL_CC REL_GLOBAL_WATCHER
REL_ANY
POS_EVENTS
@@ -282,7 +281,7 @@ use constant MAX_COMMENT_LENGTH => 65535;
use constant CMT_NORMAL => 0;
use constant CMT_DUPE_OF => 1;
use constant CMT_HAS_DUPE => 2;
-use constant CMT_POPULAR_VOTES => 3;
+# Type 3 was CMT_POPULAR_VOTES, which moved to the Voting extension.
use constant CMT_MOVED_TO => 4;
use constant CMT_ATTACHMENT_CREATED => 5;
use constant CMT_ATTACHMENT_UPDATED => 6;
@@ -295,7 +294,7 @@ use constant REL_ASSIGNEE => 0;
use constant REL_QA => 1;
use constant REL_REPORTER => 2;
use constant REL_CC => 3;
-use constant REL_VOTER => 4;
+# REL 4 was REL_VOTER, before it was moved ino an extension.
use constant REL_GLOBAL_WATCHER => 5;
# We need these strings for the X-Bugzilla-Reasons header
@@ -307,7 +306,6 @@ use constant RELATIONSHIPS => {
REL_REPORTER , "Reporter",
REL_QA , "QAcontact",
REL_CC , "CC",
- REL_VOTER , "Voter",
REL_GLOBAL_WATCHER, "GlobalWatcher"
};
diff --git a/mozilla/webtools/bugzilla/Bugzilla/DB/Schema.pm b/mozilla/webtools/bugzilla/Bugzilla/DB/Schema.pm
index 27ae3be8a3a..21c0e7970bd 100644
--- a/mozilla/webtools/bugzilla/Bugzilla/DB/Schema.pm
+++ b/mozilla/webtools/bugzilla/Bugzilla/DB/Schema.pm
@@ -273,8 +273,6 @@ use constant ABSTRACT_SCHEMA => {
COLUMN => 'userid'}},
status_whiteboard => {TYPE => 'MEDIUMTEXT', NOTNULL => 1,
DEFAULT => "''"},
- votes => {TYPE => 'INT3', NOTNULL => 1,
- DEFAULT => '0'},
# Note: keywords field is only a cache; the real data
# comes from the keywords table
keywords => {TYPE => 'MEDIUMTEXT', NOTNULL => 1,
@@ -309,7 +307,6 @@ use constant ABSTRACT_SCHEMA => {
bugs_resolution_idx => ['resolution'],
bugs_target_milestone_idx => ['target_milestone'],
bugs_qa_contact_idx => ['qa_contact'],
- bugs_votes_idx => ['votes'],
],
},
@@ -434,24 +431,6 @@ use constant ABSTRACT_SCHEMA => {
],
},
- votes => {
- FIELDS => [
- who => {TYPE => 'INT3', NOTNULL => 1,
- REFERENCES => {TABLE => 'profiles',
- COLUMN => 'userid',
- DELETE => 'CASCADE'}},
- bug_id => {TYPE => 'INT3', NOTNULL => 1,
- REFERENCES => {TABLE => 'bugs',
- COLUMN => 'bug_id',
- DELETE => 'CASCADE'}},
- vote_count => {TYPE => 'INT2', NOTNULL => 1},
- ],
- INDEXES => [
- votes_who_idx => ['who'],
- votes_bug_id_idx => ['bug_id'],
- ],
- },
-
attachments => {
FIELDS => [
attach_id => {TYPE => 'MEDIUMSERIAL', NOTNULL => 1,
@@ -1223,12 +1202,6 @@ use constant ABSTRACT_SCHEMA => {
description => {TYPE => 'MEDIUMTEXT'},
isactive => {TYPE => 'BOOLEAN', NOTNULL => 1,
DEFAULT => 1},
- votesperuser => {TYPE => 'INT2', NOTNULL => 1,
- DEFAULT => 0},
- maxvotesperbug => {TYPE => 'INT2', NOTNULL => 1,
- DEFAULT => '10000'},
- votestoconfirm => {TYPE => 'INT2', NOTNULL => 1,
- DEFAULT => 0},
defaultmilestone => {TYPE => 'varchar(20)',
NOTNULL => 1, DEFAULT => "'---'"},
allows_unconfirmed => {TYPE => 'BOOLEAN', NOTNULL => 1,
diff --git a/mozilla/webtools/bugzilla/Bugzilla/Field.pm b/mozilla/webtools/bugzilla/Bugzilla/Field.pm
index 17e4194c2a6..c2ab6e11b08 100644
--- a/mozilla/webtools/bugzilla/Bugzilla/Field.pm
+++ b/mozilla/webtools/bugzilla/Bugzilla/Field.pm
@@ -188,7 +188,6 @@ use constant DEFAULT_FIELDS => (
buglist => 1},
{name => 'reporter', desc => 'ReportedBy', in_new_bugmail => 1,
buglist => 1},
- {name => 'votes', desc => 'Votes', buglist => 1},
{name => 'qa_contact', desc => 'QAContact', in_new_bugmail => 1,
buglist => 1},
{name => 'cc', desc => 'CC', in_new_bugmail => 1},
diff --git a/mozilla/webtools/bugzilla/Bugzilla/Install/DB.pm b/mozilla/webtools/bugzilla/Bugzilla/Install/DB.pm
index 65137e593ce..66461bf4546 100644
--- a/mozilla/webtools/bugzilla/Bugzilla/Install/DB.pm
+++ b/mozilla/webtools/bugzilla/Bugzilla/Install/DB.pm
@@ -168,11 +168,6 @@ sub update_table_definitions {
$dbh->bz_add_column('bugs', 'everconfirmed',
{TYPE => 'BOOLEAN', NOTNULL => 1}, 1);
- $dbh->bz_add_column('products', 'maxvotesperbug',
- {TYPE => 'INT2', NOTNULL => 1, DEFAULT => '10000'});
- $dbh->bz_add_column('products', 'votestoconfirm',
- {TYPE => 'INT2', NOTNULL => 1}, 0);
-
_populate_milestones_table();
# 2000-03-22 Changed the default value for target_milestone to be "---"
@@ -363,8 +358,10 @@ sub update_table_definitions {
{TYPE => 'MEDIUMTEXT', NOTNULL => 1, DEFAULT => "''"});
$dbh->bz_alter_column('bugs', 'keywords',
{TYPE => 'MEDIUMTEXT', NOTNULL => 1, DEFAULT => "''"});
- $dbh->bz_alter_column('bugs', 'votes',
- {TYPE => 'INT3', NOTNULL => 1, DEFAULT => '0'});
+ if ($dbh->bz_column_info('bugs', 'votes')) {
+ $dbh->bz_alter_column('bugs', 'votes',
+ {TYPE => 'INT3', NOTNULL => 1, DEFAULT => '0'});
+ }
$dbh->bz_alter_column('bugs', 'lastdiffed', {TYPE => 'DATETIME'});
@@ -469,11 +466,14 @@ sub update_table_definitions {
if ($dbh->bz_column_info('products', 'disallownew')){
$dbh->bz_alter_column('products', 'disallownew',
{TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 0});
+
+ if ($dbh->bz_column_info('products', 'votesperuser')) {
+ $dbh->bz_alter_column('products', 'votesperuser',
+ {TYPE => 'INT2', NOTNULL => 1, DEFAULT => 0});
+ $dbh->bz_alter_column('products', 'votestoconfirm',
+ {TYPE => 'INT2', NOTNULL => 1, DEFAULT => 0});
+ }
}
- $dbh->bz_alter_column('products', 'votesperuser',
- {TYPE => 'INT2', NOTNULL => 1, DEFAULT => 0});
- $dbh->bz_alter_column('products', 'votestoconfirm',
- {TYPE => 'INT2', NOTNULL => 1, DEFAULT => 0});
# 2006-08-04 LpSolit@gmail.com - Bug 305941
$dbh->bz_drop_column('profiles', 'refreshed_when');
@@ -654,14 +654,14 @@ sub _add_bug_vote_cache {
# (P.S. All is not lost; it appears that the latest betas of MySQL
# support a new table format which will allow 32 indices.)
- $dbh->bz_drop_column('bugs', 'area');
- if (!$dbh->bz_column_info('bugs', 'votes')) {
+ if ($dbh->bz_column_info('bugs', 'area')) {
+ $dbh->bz_drop_column('bugs', 'area');
$dbh->bz_add_column('bugs', 'votes', {TYPE => 'INT3', NOTNULL => 1,
DEFAULT => 0});
$dbh->bz_add_index('bugs', 'bugs_votes_idx', [qw(votes)]);
+ $dbh->bz_add_column('products', 'votesperuser',
+ {TYPE => 'INT2', NOTNULL => 1}, 0);
}
- $dbh->bz_add_column('products', 'votesperuser',
- {TYPE => 'INT2', NOTNULL => 1}, 0);
}
sub _update_product_name_definition {
@@ -896,9 +896,11 @@ sub _add_unique_login_name_index_to_profiles {
["votes", "who"],
["longdescs", "who"]) {
my ($table, $field) = (@$i);
- print " Updating $table.$field...\n";
- $dbh->do("UPDATE $table SET $field = $u1 " .
- "WHERE $field = $u2");
+ if ($dbh->bz_table_info($table)) {
+ print " Updating $table.$field...\n";
+ $dbh->do("UPDATE $table SET $field = $u1 " .
+ "WHERE $field = $u2");
+ }
}
$dbh->do("DELETE FROM profiles WHERE userid = $u2");
}
@@ -2206,9 +2208,9 @@ sub _rename_votes_count_and_force_group_refresh {
#
# Renaming the 'count' column in the votes table because Sybase doesn't
# like it
- if ($dbh->bz_column_info('votes', 'count')) {
- $dbh->bz_rename_column('votes', 'count', 'vote_count');
- }
+ return if !$dbh->bz_table_info('votes');
+ return if $dbh->bz_column_info('votes', 'count');
+ $dbh->bz_rename_column('votes', 'count', 'vote_count');
}
sub _fix_group_with_empty_name {
@@ -2266,7 +2268,9 @@ sub _migrate_email_prefs_to_new_table {
"Reporter" => REL_REPORTER,
"QAcontact" => REL_QA,
"CClist" => REL_CC,
- "Voter" => REL_VOTER);
+ # REL_VOTER was "4" before it was moved to an
+ # extension.
+ "Voter" => 4);
my %events = ("Removeme" => EVT_ADDED_REMOVED,
"Comments" => EVT_COMMENT,
@@ -3343,8 +3347,10 @@ sub _add_allows_unconfirmed_to_product_table {
if (!$dbh->bz_column_info('products', 'allows_unconfirmed')) {
$dbh->bz_add_column('products', 'allows_unconfirmed',
{ TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'FALSE' });
- $dbh->do('UPDATE products SET allows_unconfirmed = 1
- WHERE votestoconfirm > 0');
+ if ($dbh->bz_column_info('products', 'votestoconfirm')) {
+ $dbh->do('UPDATE products SET allows_unconfirmed = 1
+ WHERE votestoconfirm > 0');
+ }
}
}
diff --git a/mozilla/webtools/bugzilla/Bugzilla/Object.pm b/mozilla/webtools/bugzilla/Bugzilla/Object.pm
index dac8962ff3f..11db7567bb4 100644
--- a/mozilla/webtools/bugzilla/Bugzilla/Object.pm
+++ b/mozilla/webtools/bugzilla/Bugzilla/Object.pm
@@ -278,7 +278,8 @@ sub set {
my ($self, $field, $value) = @_;
# This method is protected. It's used to help implement set_ functions.
- caller->isa('Bugzilla::Object')
+ my $caller = caller;
+ $caller->isa('Bugzilla::Object') || $caller->isa('Bugzilla::Extension')
|| ThrowCodeError('protection_violation',
{ caller => caller,
superclass => __PACKAGE__,
diff --git a/mozilla/webtools/bugzilla/Bugzilla/Product.pm b/mozilla/webtools/bugzilla/Bugzilla/Product.pm
index 6b00fcbf6a4..975af7d5d6c 100644
--- a/mozilla/webtools/bugzilla/Bugzilla/Product.pm
+++ b/mozilla/webtools/bugzilla/Bugzilla/Product.pm
@@ -48,9 +48,6 @@ use constant DB_COLUMNS => qw(
classification_id
description
isactive
- votesperuser
- maxvotesperbug
- votestoconfirm
defaultmilestone
allows_unconfirmed
);
@@ -66,9 +63,6 @@ use constant UPDATE_COLUMNS => qw(
description
defaultmilestone
isactive
- votesperuser
- maxvotesperbug
- votestoconfirm
allows_unconfirmed
);
@@ -80,9 +74,6 @@ use constant VALIDATORS => {
version => \&_check_version,
defaultmilestone => \&_check_default_milestone,
isactive => \&Bugzilla::Object::check_boolean,
- votesperuser => \&_check_votes_per_user,
- maxvotesperbug => \&_check_votes_per_bug,
- votestoconfirm => \&_check_votes_to_confirm,
create_series => \&Bugzilla::Object::check_boolean
};
@@ -155,99 +146,6 @@ sub update {
$dbh->bz_start_transaction();
my ($changes, $old_self) = $self->SUPER::update(@_);
- # We also have to fix votes.
- my @msgs; # Will store emails to send to voters.
- if ($changes->{maxvotesperbug} || $changes->{votesperuser} || $changes->{votestoconfirm}) {
- # We cannot |use| these modules, due to dependency loops.
- require Bugzilla::Bug;
- import Bugzilla::Bug qw(RemoveVotes CheckIfVotedConfirmed);
- require Bugzilla::User;
- import Bugzilla::User qw(user_id_to_login);
-
- # 1. too many votes for a single user on a single bug.
- my @toomanyvotes_list = ();
- if ($self->max_votes_per_bug < $self->votes_per_user) {
- my $votes = $dbh->selectall_arrayref(
- 'SELECT votes.who, votes.bug_id
- FROM votes
- INNER JOIN bugs
- ON bugs.bug_id = votes.bug_id
- WHERE bugs.product_id = ?
- AND votes.vote_count > ?',
- undef, ($self->id, $self->max_votes_per_bug));
-
- foreach my $vote (@$votes) {
- my ($who, $id) = (@$vote);
- # If some votes are removed, RemoveVotes() returns a list
- # of messages to send to voters.
- push(@msgs, RemoveVotes($id, $who, 'votes_too_many_per_bug'));
- my $name = user_id_to_login($who);
-
- push(@toomanyvotes_list, {id => $id, name => $name});
- }
- }
- $changes->{'too_many_votes'} = \@toomanyvotes_list;
-
- # 2. too many total votes for a single user.
- # This part doesn't work in the general case because RemoveVotes
- # doesn't enforce votesperuser (except per-bug when it's less
- # than maxvotesperbug). See Bugzilla::Bug::RemoveVotes().
-
- my $votes = $dbh->selectall_arrayref(
- 'SELECT votes.who, votes.vote_count
- FROM votes
- INNER JOIN bugs
- ON bugs.bug_id = votes.bug_id
- WHERE bugs.product_id = ?',
- undef, $self->id);
-
- my %counts;
- foreach my $vote (@$votes) {
- my ($who, $count) = @$vote;
- if (!defined $counts{$who}) {
- $counts{$who} = $count;
- } else {
- $counts{$who} += $count;
- }
- }
- my @toomanytotalvotes_list = ();
- foreach my $who (keys(%counts)) {
- if ($counts{$who} > $self->votes_per_user) {
- my $bug_ids = $dbh->selectcol_arrayref(
- 'SELECT votes.bug_id
- FROM votes
- INNER JOIN bugs
- ON bugs.bug_id = votes.bug_id
- WHERE bugs.product_id = ?
- AND votes.who = ?',
- undef, ($self->id, $who));
-
- foreach my $bug_id (@$bug_ids) {
- # RemoveVotes() returns a list of messages to send
- # in case some voters had too many votes.
- push(@msgs, RemoveVotes($bug_id, $who, 'votes_too_many_per_user'));
- my $name = user_id_to_login($who);
-
- push(@toomanytotalvotes_list, {id => $bug_id, name => $name});
- }
- }
- }
- $changes->{'too_many_total_votes'} = \@toomanytotalvotes_list;
-
- # 3. enough votes to confirm
- my $bug_list =
- $dbh->selectcol_arrayref('SELECT bug_id FROM bugs WHERE product_id = ?
- AND bug_status = ? AND votes >= ?',
- undef, ($self->id, 'UNCONFIRMED', $self->votes_to_confirm));
-
- my @updated_bugs = ();
- foreach my $bug_id (@$bug_list) {
- my $confirmed = CheckIfVotedConfirmed($bug_id);
- push (@updated_bugs, $bug_id) if $confirmed;
- }
- $changes->{'confirmed_bugs'} = \@updated_bugs;
- }
-
# Also update group settings.
if ($self->{check_group_controls}) {
require Bugzilla::Bug;
@@ -364,11 +262,6 @@ sub update {
delete $self->{check_group_controls};
Bugzilla->user->clear_product_cache();
- # Now that changes have been committed, we can send emails to voters.
- foreach my $msg (@msgs) {
- MessageToMTA($msg);
- }
-
return $changes;
}
@@ -524,37 +417,6 @@ sub _check_milestone_url {
return $url;
}
-sub _check_votes_per_user {
- return _check_votes(@_, 0);
-}
-
-sub _check_votes_per_bug {
- return _check_votes(@_, 10000);
-}
-
-sub _check_votes_to_confirm {
- return _check_votes(@_, 0);
-}
-
-# This subroutine is only used internally by other _check_votes_* validators.
-sub _check_votes {
- my ($invocant, $votes, $field, $default) = @_;
-
- detaint_natural($votes);
- # On product creation, if the number of votes is not a valid integer,
- # we silently fall back to the given default value.
- # If the product already exists and the change is illegal, we complain.
- if (!defined $votes) {
- if (ref $invocant) {
- ThrowUserError('product_illegal_votes', {field => $field, votes => $_[1]});
- }
- else {
- $votes = $default;
- }
- }
- return $votes;
-}
-
#####################################
# Implement Bugzilla::Field::Choice #
#####################################
@@ -618,9 +480,6 @@ sub set_name { $_[0]->set('name', $_[1]); }
sub set_description { $_[0]->set('description', $_[1]); }
sub set_default_milestone { $_[0]->set('defaultmilestone', $_[1]); }
sub set_is_active { $_[0]->set('isactive', $_[1]); }
-sub set_votes_per_user { $_[0]->set('votesperuser', $_[1]); }
-sub set_votes_per_bug { $_[0]->set('maxvotesperbug', $_[1]); }
-sub set_votes_to_confirm { $_[0]->set('votestoconfirm', $_[1]); }
sub set_allows_unconfirmed { $_[0]->set('allows_unconfirmed', $_[1]); }
sub set_group_controls {
@@ -876,9 +735,6 @@ sub flag_types {
sub allows_unconfirmed { return $_[0]->{'allows_unconfirmed'}; }
sub description { return $_[0]->{'description'}; }
sub is_active { return $_[0]->{'isactive'}; }
-sub votes_per_user { return $_[0]->{'votesperuser'}; }
-sub max_votes_per_bug { return $_[0]->{'maxvotesperbug'}; }
-sub votes_to_confirm { return $_[0]->{'votestoconfirm'}; }
sub default_milestone { return $_[0]->{'defaultmilestone'}; }
sub classification_id { return $_[0]->{'classification_id'}; }
@@ -939,9 +795,6 @@ Bugzilla::Product - Bugzilla product class.
my $name = $product->name;
my $description = $product->description;
my isactive = $product->is_active;
- my votesperuser = $product->votes_per_user;
- my maxvotesperbug = $product->max_votes_per_bug;
- my votestoconfirm = $product->votes_to_confirm;
my $defaultmilestone = $product->default_milestone;
my $classificationid = $product->classification_id;
my $allows_unconfirmed = $product->allows_unconfirmed;
diff --git a/mozilla/webtools/bugzilla/Bugzilla/Search.pm b/mozilla/webtools/bugzilla/Bugzilla/Search.pm
index d85da01a459..52c99903d77 100644
--- a/mozilla/webtools/bugzilla/Bugzilla/Search.pm
+++ b/mozilla/webtools/bugzilla/Bugzilla/Search.pm
@@ -86,10 +86,8 @@ use constant SPECIAL_ORDER_JOIN => {
# 3. title: The title of the column as displayed to users.
#
# Note: There are a few hacks in the code that deviate from these definitions.
-# In particular, when the list is sorted by the "votes" field the word
-# "DESC" is added to the end of the field to sort in descending order,
-# and the redundant short_desc column is removed when the client
-# requests "all" columns.
+# In particular, the redundant short_desc column is removed when the
+# client requests "all" columns.
#
# This is really a constant--that is, once it's been called once, the value
# will always be the same unless somebody adds a new custom field. But
@@ -281,18 +279,6 @@ sub init {
push(@supptables, "LEFT JOIN flagtypes ON flagtypes.id = flags.type_id");
}
- my $minvotes;
- if (defined $params->param('votes')) {
- my $c = trim($params->param('votes'));
- if ($c ne "") {
- if ($c !~ /^[0-9]*$/) {
- ThrowUserError("illegal_at_least_x_votes",
- { value => $c });
- }
- push(@specialchart, ["votes", "greaterthan", $c - 1]);
- }
- }
-
# If the user has selected all of either status or resolution, change to
# selecting none. This is functionally equivalent, but quite a lot faster.
# Also, if the status is __open__ or __closed__, translate those
diff --git a/mozilla/webtools/bugzilla/Bugzilla/Search/Quicksearch.pm b/mozilla/webtools/bugzilla/Bugzilla/Search/Quicksearch.pm
index 2f9e0734fd2..1e0bdc437e0 100644
--- a/mozilla/webtools/bugzilla/Bugzilla/Search/Quicksearch.pm
+++ b/mozilla/webtools/bugzilla/Bugzilla/Search/Quicksearch.pm
@@ -339,12 +339,6 @@ sub _handle_special_first_chars {
sub _handle_field_names {
my ($or_operand, $negate, $unknownFields, $ambiguous_fields) = @_;
- # votes:xx ("at least xx votes")
- if ($or_operand =~ /^votes:([0-9]+)$/) {
- addChart('votes', 'greaterthan', $1 - 1, $negate);
- return 1;
- }
-
# Flag and requestee shortcut
if ($or_operand =~ /^(?:flag:)?([^\?]+\?)([^\?]*)$/) {
addChart('flagtypes.name', 'substring', $1, $negate);
@@ -454,18 +448,6 @@ sub _special_field_syntax {
return 1;
}
- # Votes (votes>xx)
- if ($word =~ m/^votes>([0-9]+)$/) {
- addChart('votes', 'greaterthan', $1, $negate);
- return 1;
- }
-
- # Votes (votes>=xx, votes=>xx)
- if ($word =~ m/^votes(>=|=>)([0-9]+)$/) {
- addChart('votes', 'greaterthan', $2-1, $negate);
- return 1;
- }
-
return 0;
}
diff --git a/mozilla/webtools/bugzilla/Bugzilla/WebService/Bug.pm b/mozilla/webtools/bugzilla/Bugzilla/WebService/Bug.pm
index 711a45f4439..1d7b9f7d9ad 100644
--- a/mozilla/webtools/bugzilla/Bugzilla/WebService/Bug.pm
+++ b/mozilla/webtools/bugzilla/Bugzilla/WebService/Bug.pm
@@ -386,9 +386,6 @@ sub search {
if (my $when = delete $params->{creation_ts}) {
$params->{WHERE}->{'creation_ts >= ?'} = $when;
}
- if (my $votes = delete $params->{votes}) {
- $params->{WHERE}->{'votes >= ?'} = $votes;
- }
if (my $summary = delete $params->{short_desc}) {
my @strings = ref $summary ? @$summary : ($summary);
my @likes = ("short_desc LIKE ?") x @strings;
@@ -1687,11 +1684,6 @@ C The "URL" field of a bug.
C The Version field of a bug.
-=item C
-
-C Searches for bugs with this many votes or greater. May not
-be an array.
-
=item C
C Search the "Status Whiteboard" field on bugs for a substring.
@@ -1722,6 +1714,8 @@ for that value.
=item Added in Bugzilla B<3.4>.
+=item Searching by C was removed in Bugzilla B<3.8>.
+
=back
=back
diff --git a/mozilla/webtools/bugzilla/buglist.cgi b/mozilla/webtools/bugzilla/buglist.cgi
index b8cfa633677..48fe2d87320 100755
--- a/mozilla/webtools/bugzilla/buglist.cgi
+++ b/mozilla/webtools/bugzilla/buglist.cgi
@@ -653,18 +653,6 @@ else {
# and are hard-coded into the display templates.
@displaycolumns = grep($_ ne 'bug_id', @displaycolumns);
-# Add the votes column to the list of columns to be displayed
-# in the bug list if the user is searching for bugs with a certain
-# number of votes and the votes column is not already on the list.
-
-# Some versions of perl will taint 'votes' if this is done as a single
-# statement, because the votes param is tainted at this point
-my $votes = $params->param('votes');
-$votes ||= "";
-if (trim($votes) && !grep($_ eq 'votes', @displaycolumns)) {
- push(@displaycolumns, 'votes');
-}
-
# Remove the timetracking columns if they are not a part of the group
# (happens if a user had access to time tracking and it was revoked/disabled)
if (!Bugzilla->user->is_timetracker) {
@@ -806,12 +794,6 @@ if ($order) {
# Special handlings for certain columns
next if $column_name eq 'relevance' && !$fulltext;
- # If we are sorting by votes, sort in descending order if
- # no explicit sort order was given.
- if ($column_name eq 'votes' && !$direction) {
- $direction = "DESC";
- }
-
if (exists $columns->{$column_name}) {
$direction = " $direction" if $direction;
push(@order, "$column_name$direction");
diff --git a/mozilla/webtools/bugzilla/bugzilla.dtd b/mozilla/webtools/bugzilla/bugzilla.dtd
index 64f575b62cf..b449d6ba42b 100644
--- a/mozilla/webtools/bugzilla/bugzilla.dtd
+++ b/mozilla/webtools/bugzilla/bugzilla.dtd
@@ -5,7 +5,7 @@
maintainer CDATA #REQUIRED
exporter CDATA #IMPLIED
>
-
+
@@ -39,7 +39,6 @@
-
diff --git a/mozilla/webtools/bugzilla/colchange.cgi b/mozilla/webtools/bugzilla/colchange.cgi
index 409f15e5a3a..15bdac599a1 100755
--- a/mozilla/webtools/bugzilla/colchange.cgi
+++ b/mozilla/webtools/bugzilla/colchange.cgi
@@ -55,9 +55,6 @@ if (Bugzilla->params->{"useclassification"}) {
push(@masterlist, ("product", "component", "version", "op_sys"));
-if (Bugzilla->params->{"usevotes"}) {
- push (@masterlist, "votes");
-}
if (Bugzilla->params->{"usebugaliases"}) {
unshift(@masterlist, "alias");
}
diff --git a/mozilla/webtools/bugzilla/contrib/merge-users.pl b/mozilla/webtools/bugzilla/contrib/merge-users.pl
index 80c516e04c2..6c1ed13771f 100755
--- a/mozilla/webtools/bugzilla/contrib/merge-users.pl
+++ b/mozilla/webtools/bugzilla/contrib/merge-users.pl
@@ -129,7 +129,6 @@ my $changes = {
flags => ['setter_id', 'requestee_id'],
cc => ['who bug_id'],
longdescs => ['who'],
- votes => ['who'],
# Tables affecting global behavior / other users.
components => ['initialowner', 'initialqacontact'],
component_cc => ['user_id component_id'],
diff --git a/mozilla/webtools/bugzilla/docs/en/images/bzLifecycle.xml b/mozilla/webtools/bugzilla/docs/en/images/bzLifecycle.xml
index 1adc6b70c5f..dda8ac48a0c 100644
--- a/mozilla/webtools/bugzilla/docs/en/images/bzLifecycle.xml
+++ b/mozilla/webtools/bugzilla/docs/en/images/bzLifecycle.xml
@@ -968,8 +968,7 @@ UNCONFIRMED state#
- #Bug confirmed or
-receives enough votes#
+ #Bug confirmed#
diff --git a/mozilla/webtools/bugzilla/editproducts.cgi b/mozilla/webtools/bugzilla/editproducts.cgi
index 8433ed16bac..4a302aa6cb6 100755
--- a/mozilla/webtools/bugzilla/editproducts.cgi
+++ b/mozilla/webtools/bugzilla/editproducts.cgi
@@ -186,11 +186,6 @@ if ($action eq 'new') {
create_series => scalar $cgi->param('createseries'),
allows_unconfirmed => scalar $cgi->param('allows_unconfirmed'),
);
- if (Bugzilla->params->{'usevotes'}) {
- $create_params{votesperuser} = $cgi->param('votesperuser');
- $create_params{maxvotesperbug} = $cgi->param('maxvotesperbug');
- $create_params{votestoconfirm} = $cgi->param('votestoconfirm');
- }
my $product = Bugzilla::Product->create(\%create_params);
delete_token($token);
@@ -295,16 +290,13 @@ if ($action eq 'update') {
my $product_old_name = trim($cgi->param('product_old_name') || '');
my $product = $user->check_can_admin_product($product_old_name);
- $product->set_name($product_name);
- $product->set_description(scalar $cgi->param('description'));
- $product->set_default_milestone(scalar $cgi->param('defaultmilestone'));
- $product->set_is_active(scalar $cgi->param('is_active'));
- if (Bugzilla->params->{'usevotes'}) {
- $product->set_votes_per_user(scalar $cgi->param('votesperuser'));
- $product->set_votes_per_bug(scalar $cgi->param('maxvotesperbug'));
- $product->set_votes_to_confirm(scalar $cgi->param('votestoconfirm'));
- }
- $product->set_allows_unconfirmed(scalar $cgi->param('allows_unconfirmed'));
+ $product->set_all({
+ name => $product_name,
+ description => scalar $cgi->param('description'),
+ is_active => scalar $cgi->param('is_active'),
+ allows_unconfirmed => scalar $cgi->param('allows_unconfirmed'),
+ default_milestone => scalar $cgi->param('defaultmilestone'),
+ });
my $changes = $product->update();
diff --git a/mozilla/webtools/bugzilla/editusers.cgi b/mozilla/webtools/bugzilla/editusers.cgi
index e63f29fc5c3..7ce6bb9d24d 100755
--- a/mozilla/webtools/bugzilla/editusers.cgi
+++ b/mozilla/webtools/bugzilla/editusers.cgi
@@ -424,9 +424,6 @@ if ($action eq 'search') {
$vars->{'series'} = $dbh->selectrow_array(
'SELECT COUNT(*) FROM series WHERE creator = ?',
undef, $otherUserID);
- $vars->{'votes'} = $dbh->selectrow_array(
- 'SELECT COUNT(*) FROM votes WHERE who = ?',
- undef, $otherUserID);
$vars->{'watch'}{'watched'} = $dbh->selectrow_array(
'SELECT COUNT(*) FROM watch WHERE watched = ?',
undef, $otherUserID);
@@ -537,7 +534,6 @@ if ($action eq 'search') {
$dbh->do('DELETE FROM tokens WHERE userid = ?', undef, $otherUserID);
$dbh->do('DELETE FROM user_group_map WHERE user_id = ?', undef,
$otherUserID);
- $dbh->do('DELETE FROM votes WHERE who = ?', undef, $otherUserID);
$dbh->do('DELETE FROM watch WHERE watcher = ? OR watched = ?', undef,
($otherUserID, $otherUserID));
diff --git a/mozilla/webtools/bugzilla/extensions/Voting/Extension.pm b/mozilla/webtools/bugzilla/extensions/Voting/Extension.pm
new file mode 100644
index 00000000000..e111ac7853d
--- /dev/null
+++ b/mozilla/webtools/bugzilla/extensions/Voting/Extension.pm
@@ -0,0 +1,861 @@
+# -*- 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
+# Stephan Niemz
+# Christopher Aillon
+# Gervase Markham
+# Frédéric Buclin
+# Max Kanat-Alexander
+
+package Bugzilla::Extension::Voting;
+use strict;
+use base qw(Bugzilla::Extension);
+
+use Bugzilla::Bug;
+use Bugzilla::Constants;
+use Bugzilla::Error;
+use Bugzilla::Field;
+use Bugzilla::Mailer;
+use Bugzilla::User;
+use Bugzilla::Util qw(detaint_natural);
+
+use List::Util qw(min);
+
+use constant NAME => 'Voting';
+use constant DEFAULT_VOTES_PER_BUG => 1;
+# These came from Bugzilla itself, so they maintain the old numbers
+# they had before.
+use constant CMT_POPULAR_VOTES => 3;
+use constant REL_VOTER => 4;
+
+################
+# Installation #
+################
+
+our $VERSION = BUGZILLA_VERSION;
+
+sub db_schema_abstract_schema {
+ my ($self, $args) = @_;
+ $args->{'schema'}->{'votes'} = {
+ FIELDS => [
+ who => {TYPE => 'INT3', NOTNULL => 1,
+ REFERENCES => {TABLE => 'profiles',
+ COLUMN => 'userid',
+ DELETE => 'CASCADE'}},
+ bug_id => {TYPE => 'INT3', NOTNULL => 1,
+ REFERENCES => {TABLE => 'bugs',
+ COLUMN => 'bug_id',
+ DELETE => 'CASCADE'}},
+ vote_count => {TYPE => 'INT2', NOTNULL => 1},
+ ],
+ INDEXES => [
+ votes_who_idx => ['who'],
+ votes_bug_id_idx => ['bug_id'],
+ ],
+ };
+}
+
+sub install_update_db {
+ my $dbh = Bugzilla->dbh;
+ # Note that before Bugzilla 3.8, voting was a built-in part of Bugzilla,
+ # so updates to the columns for old versions of Bugzilla happen in
+ # Bugzilla::Install::DB, and can't safely be moved to this extension.
+
+ my $field = new Bugzilla::Field({ name => 'votes' });
+ if (!$field) {
+ Bugzilla::Field->create(
+ { name => 'votes', description => 'Votes', buglist => 1 });
+ }
+
+ $dbh->bz_add_column('products', 'votesperuser',
+ {TYPE => 'INT2', NOTNULL => 1, DEFAULT => 0});
+ $dbh->bz_add_column('products', 'maxvotesperbug',
+ {TYPE => 'INT2', NOTNULL => 1, DEFAULT => DEFAULT_VOTES_PER_BUG});
+ $dbh->bz_add_column('products', 'votestoconfirm',
+ {TYPE => 'INT2', NOTNULL => 1, DEFAULT => 0});
+
+ $dbh->bz_add_column('bugs', 'votes',
+ {TYPE => 'INT3', NOTNULL => 1, DEFAULT => 0});
+ $dbh->bz_add_index('bugs', 'bugs_votes_idx', ['votes']);
+
+ # maxvotesperbug used to default to 10,000, which isn't very sensible.
+ my $per_bug = $dbh->bz_column_info('products', 'maxvotesperbug');
+ if ($per_bug->{DEFAULT} != DEFAULT_VOTES_PER_BUG) {
+ $dbh->bz_alter_column('products', 'maxvotesperbug',
+ {TYPE => 'INT2', NOTNULL => 1, DEFAULT => DEFAULT_VOTES_PER_BUG});
+ }
+}
+
+###########
+# Objects #
+###########
+
+sub object_columns {
+ my ($self, $args) = @_;
+ my ($class, $columns) = @$args{qw(class columns)};
+ if ($class->isa('Bugzilla::Bug')) {
+ push(@$columns, 'votes');
+ }
+ elsif ($class->isa('Bugzilla::Product')) {
+ push(@$columns, qw(votesperuser maxvotesperbug votestoconfirm));
+ }
+}
+
+sub bug_fields {
+ my ($self, $args) = @_;
+ my $fields = $args->{fields};
+ push(@$fields, 'votes');
+}
+
+sub object_update_columns {
+ my ($self, $args) = @_;
+ my ($object, $columns) = @$args{qw(object columns)};
+ if ($object->isa('Bugzilla::Product')) {
+ push(@$columns, qw(votesperuser maxvotesperbug votestoconfirm));
+ }
+}
+
+sub object_validators {
+ my ($self, $args) = @_;
+ my ($class, $validators) = @$args{qw(class validators)};
+ if ($class->isa('Bugzilla::Product')) {
+ $validators->{'votesperuser'} = \&_check_votesperuser;
+ $validators->{'maxvotesperbug'} = \&_check_maxvotesperbug;
+ $validators->{'votestoconfirm'} = \&_check_votestoconfirm;
+ }
+}
+
+sub object_before_create {
+ my ($self, $args) = @_;
+ my ($class, $params) = @$args{qw(class params)};
+ if ($class->isa('Bugzilla::Bug')) {
+ # Don't ever allow people to directly specify "votes" into the bugs
+ # table.
+ delete $params->{votes};
+ }
+ elsif ($class->isa('Bugzilla::Product')) {
+ my $input = Bugzilla->input_params;
+ $params->{votesperuser} = $input->{'votesperuser'};
+ $params->{maxvotesperbug} = $input->{'maxvotesperbug'};
+ $params->{votestoconfirm} = $input->{'votestoconfirm'};
+ }
+}
+
+sub object_end_of_set_all {
+ my ($self, $args) = @_;
+ my ($object) = $args->{object};
+ if ($object->isa('Bugzilla::Product')) {
+ my $input = Bugzilla->input_params;
+ $object->set('votesperuser', $input->{'votesperuser'});
+ $object->set('maxvotesperbug', $input->{'maxvotesperbug'});
+ $object->set('votestoconfirm', $input->{'votestoconfirm'});
+ }
+}
+
+sub object_end_of_update {
+ my ($self, $args) = @_;
+ my ($object, $changes) = @$args{qw(object changes)};
+ if ( $object->isa('Bugzilla::Product')
+ and ($changes->{maxvotesperbug} or $changes->{votesperuser}
+ or $changes->{votestoconfirm}) )
+ {
+ _modify_bug_votes($object, $changes);
+ }
+}
+
+sub bug_end_of_update {
+ my ($self, $args) = @_;
+ my ($bug, $changes) = @$args{qw(bug changes)};
+
+ if ($changes->{'product'}) {
+ my @msgs;
+ # If some votes have been removed, RemoveVotes() returns
+ # a list of messages to send to voters.
+ @msgs = _remove_votes($bug->id, 0, 'votes_bug_moved');
+ _confirm_if_vote_confirmed($bug->id);
+
+ foreach my $msg (@msgs) {
+ MessageToMTA($msg);
+ }
+ }
+}
+
+#############
+# Templates #
+#############
+
+sub template_before_create {
+ my ($self, $args) = @_;
+ my $config = $args->{config};
+ my $constants = $config->{CONSTANTS};
+ $constants->{REL_VOTER} = REL_VOTER;
+ $constants->{CMT_POPULAR_VOTES} = CMT_POPULAR_VOTES;
+ $constants->{DEFAULT_VOTES_PER_BUG} = DEFAULT_VOTES_PER_BUG;
+}
+
+
+sub template_before_process {
+ my ($self, $args) = @_;
+ my ($vars, $file) = @$args{qw(vars file)};
+ if ($file eq 'admin/users/confirm-delete.html.tmpl') {
+ my $who = $vars->{otheruser};
+ my $votes = Bugzilla->dbh->selectrow_array(
+ 'SELECT COUNT(*) FROM votes WHERE who = ?', undef, $who->id);
+ if ($votes) {
+ $vars->{other_safe} = 1;
+ $vars->{votes} = $votes;
+ }
+ }
+}
+
+###########
+# Bugmail #
+###########
+
+sub bugmail_recipients {
+ my ($self, $args) = @_;
+ my ($bug, $recipients) = @$args{qw(bug recipients)};
+ my $dbh = Bugzilla->dbh;
+
+ my $voters = $dbh->selectcol_arrayref(
+ "SELECT who FROM votes WHERE bug_id = ?", undef, $bug->id);
+ $recipients->{$_}->{+REL_VOTER} = 1 foreach (@$voters);
+}
+
+sub bugmail_relationships {
+ my ($self, $args) = @_;
+ my $relationships = $args->{relationships};
+ $relationships->{+REL_VOTER} = 'Voter';
+}
+
+###############
+# Sanitycheck #
+###############
+
+sub sanitycheck_check {
+ my ($self, $args) = @_;
+ my $status = $args->{status};
+
+ # Vote Cache
+ $status->('voting_count_start');
+ my $dbh = Bugzilla->dbh;
+ my %cached_counts = @{ $dbh->selectcol_arrayref(
+ 'SELECT bug_id, votes FROM bugs', {Columns=>[1,2]}) };
+
+ my %real_counts = @{ $dbh->selectcol_arrayref(
+ 'SELECT bug_id, SUM(vote_count) FROM votes '
+ . $dbh->sql_group_by('bug_id'), {Columns=>[1,2]}) };
+
+ my $needs_rebuild;
+ foreach my $id (keys %cached_counts) {
+ my $cached_count = $cached_counts{$id};
+ my $real_count = $real_counts{$id} || 0;
+ if ($cached_count < 0) {
+ $status->('voting_count_alert', { id => $id }, 'alert');
+ }
+ elsif ($cached_count != $real_count) {
+ $status->('voting_cache_alert', { id => $id }, 'alert');
+ $needs_rebuild = 1;
+ }
+ }
+
+ $status->('voting_cache_rebuild_fix') if $needs_rebuild;
+}
+
+sub sanitycheck_repair {
+ my ($self, $args) = @_;
+ my $status = $args->{status};
+ my $input = Bugzilla->input_params;
+ my $dbh = Bugzilla->dbh;
+
+ return if !$input->{rebuild_vote_cache};
+
+ $status->('voting_cache_rebuild_start');
+ $dbh->bz_start_transaction();
+ $dbh->do('UPDATE bugs SET votes = 0');
+
+ my $sth = $dbh->prepare(
+ 'SELECT bug_id, SUM(vote_count) FROM votes '
+ . $dbh->sql_group_by('bug_id'));
+ $sth->execute();
+
+ my $sth_update = $dbh->prepare(
+ 'UPDATE bugs SET votes = ? WHERE bug_id = ?');
+ while (my ($id, $count) = $sth->fetchrow_array) {
+ $sth_update->execute($count, $id);
+ }
+ $dbh->bz_commit_transaction();
+ $status->('voting_cache_rebuild_end');
+}
+
+
+##############
+# Validators #
+##############
+
+sub _check_votesperuser {
+ return _check_votes(0, @_);
+}
+
+sub _check_maxvotesperbug {
+ return _check_votes(DEFAULT_VOTES_PER_BUG, @_);
+}
+
+sub _check_votestoconfirm {
+ return _check_votes(0, @_);
+}
+
+# This subroutine is only used internally by other _check_votes_* validators.
+sub _check_votes {
+ my ($default, $invocant, $votes, $field) = @_;
+
+ detaint_natural($votes);
+ # On product creation, if the number of votes is not a valid integer,
+ # we silently fall back to the given default value.
+ # If the product already exists and the change is illegal, we complain.
+ if (!defined $votes) {
+ if (ref $invocant) {
+ ThrowUserError('voting_product_illegal_votes',
+ { field => $field, votes => $_[2] });
+ }
+ else {
+ $votes = $default;
+ }
+ }
+ return $votes;
+}
+
+#########
+# Pages #
+#########
+
+sub page_before_template {
+ my ($self, $args) = @_;
+ my $page = $args->{page_id};
+ my $vars = $args->{vars};
+
+ if ($page =~ m{^voting/bug\.}) {
+ _page_bug($vars);
+ }
+ elsif ($page =~ m{^voting/user\.}) {
+ _page_user($vars);
+ }
+}
+
+sub _page_bug {
+ my ($vars) = @_;
+ my $dbh = Bugzilla->dbh;
+ my $template = Bugzilla->template;
+ my $input = Bugzilla->input_params;
+
+ my $bug_id = $input->{bug_id};
+ my $bug = Bugzilla::Bug->check($bug_id);
+
+ $vars->{'bug'} = $bug;
+ $vars->{'users'} =
+ $dbh->selectall_arrayref('SELECT profiles.login_name,
+ profiles.userid AS id,
+ votes.vote_count
+ FROM votes
+ INNER JOIN profiles
+ ON profiles.userid = votes.who
+ WHERE votes.bug_id = ?',
+ {Slice=>{}}, $bug->id);
+}
+
+sub _page_user {
+ my ($vars) = @_;
+ my $dbh = Bugzilla->dbh;
+ my $user = Bugzilla->user;
+ my $template = Bugzilla->template;
+ my $input = Bugzilla->input_params;
+
+ my $action = $input->{action};
+ if ($action and $action eq 'vote') {
+ _update_votes($vars);
+ }
+
+ # If a bug_id is given, and we're editing, we'll add it to the votes list.
+
+ my $bug_id = $input->{bug_id};
+ my $bug = Bugzilla::Bug->check($bug_id) if $bug_id;
+ my $who_id = $input->{user_id} || $user->id;
+
+ # Logged-out users must specify a user_id.
+ Bugzilla->login(LOGIN_REQUIRED) if !$who_id;
+
+ my $who = Bugzilla::User->check({ id => $who_id });
+
+ my $canedit = $user->id == $who->id;
+
+ $dbh->bz_start_transaction();
+
+ if ($canedit && $bug) {
+ # Make sure there is an entry for this bug
+ # in the vote table, just so that things display right.
+ my $has_votes = $dbh->selectrow_array('SELECT vote_count FROM votes
+ WHERE bug_id = ? AND who = ?',
+ undef, ($bug->id, $who->id));
+ if (!$has_votes) {
+ $dbh->do('INSERT INTO votes (who, bug_id, vote_count)
+ VALUES (?, ?, 0)', undef, ($who->id, $bug->id));
+ }
+ }
+
+ my (@products, @all_bug_ids);
+ # Read the votes data for this user for each product.
+ foreach my $product (@{ $user->get_selectable_products }) {
+ next unless ($product->{votesperuser} > 0);
+
+ my @bugs;
+ my @bug_ids;
+ my $total = 0;
+ my $onevoteonly = 0;
+
+ my $vote_list =
+ $dbh->selectall_arrayref('SELECT votes.bug_id, votes.vote_count,
+ bugs.short_desc
+ FROM votes
+ INNER JOIN bugs
+ ON votes.bug_id = bugs.bug_id
+ WHERE votes.who = ?
+ AND bugs.product_id = ?
+ ORDER BY votes.bug_id',
+ undef, ($who->id, $product->id));
+
+ foreach (@$vote_list) {
+ my ($id, $count, $summary) = @$_;
+ $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 !$user->can_see_bug($id);
+
+ push (@bugs, { id => $id,
+ summary => $summary,
+ count => $count });
+ push (@bug_ids, $id);
+ push (@all_bug_ids, $id);
+ }
+
+ $onevoteonly = 1 if (min($product->{votesperuser},
+ $product->{maxvotesperbug}) == 1);
+
+ # Only add the product for display if there are any bugs in it.
+ if ($#bugs > -1) {
+ push (@products, { name => $product->name,
+ bugs => \@bugs,
+ bug_ids => \@bug_ids,
+ onevoteonly => $onevoteonly,
+ total => $total,
+ maxvotes => $product->{votesperuser},
+ maxperbug => $product->{maxvotesperbug} });
+ }
+ }
+
+ $dbh->do('DELETE FROM votes WHERE vote_count <= 0');
+ $dbh->bz_commit_transaction();
+
+ $vars->{'canedit'} = $canedit;
+ $vars->{'voting_user'} = { "login" => $who->name };
+ $vars->{'products'} = \@products;
+ $vars->{'this_bug'} = $bug;
+ $vars->{'all_bug_ids'} = \@all_bug_ids;
+}
+
+sub _update_votes {
+ my ($vars) = @_;
+
+ ############################################################################
+ # Begin Data/Security Validation
+ ############################################################################
+
+ my $cgi = Bugzilla->cgi;
+ my $dbh = Bugzilla->dbh;
+ my $template = Bugzilla->template;
+ my $user = Bugzilla->login(LOGIN_REQUIRED);
+ my $input = Bugzilla->input_params;
+
+ # 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 {/^\d+$/} keys %$input;
+
+ # 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 $cgi->param('delete_all_votes')) {
+ print $cgi->header();
+ $template->process("voting/delete-all.html.tmpl", $vars)
+ || ThrowTemplateError($template->error());
+ exit;
+ }
+ elsif ($cgi->param('delete_all_votes') == 0) {
+ print $cgi->redirect("page.cgi?id=voting/user.html");
+ exit;
+ }
+ }
+
+ # Call check() 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).
+ my (%votes, @bugs);
+ foreach my $id (@buglist) {
+ my $bug = Bugzilla::Bug->check($id);
+ push(@bugs, $bug);
+ $id = $bug->id;
+ $votes{$id} = $input->{$id};
+ detaint_natural($votes{$id})
+ || ThrowUserError("voting_must_be_nonnegative");
+ }
+
+ ############################################################################
+ # End Data/Security Validation
+ ############################################################################
+ my $who = $user->id;
+
+ # If the user is voting for bugs, make sure they aren't overstuffing
+ # the ballot box.
+ if (scalar @bugs) {
+ my (%prodcount, %products);
+ foreach my $bug (@bugs) {
+ my $bug_id = $bug->id;
+ my $prod = $bug->product;
+ $products{$prod} ||= $bug->product_obj;
+ $prodcount{$prod} ||= 0;
+ $prodcount{$prod} += $votes{$bug_id};
+
+ # Make sure we haven't broken the votes-per-bug limit
+ ($votes{$bug_id} <= $products{$prod}->{maxvotesperbug})
+ || ThrowUserError("voting_too_many_votes_for_bug",
+ {max => $products{$prod}->{maxvotesperbug},
+ product => $prod,
+ votes => $votes{$bug_id}});
+ }
+
+ # Make sure we haven't broken the votes-per-product limit
+ foreach my $prod (keys(%prodcount)) {
+ ($prodcount{$prod} <= $products{$prod}->{votesperuser})
+ || ThrowUserError("voting_too_many_votes_for_product",
+ {max => $products{$prod}->{votesperuser},
+ product => $prod,
+ votes => $prodcount{$prod}});
+ }
+ }
+
+ # 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;
+ $dbh->bz_start_transaction();
+
+ # Take note of, and delete the user's old votes from the database.
+ my $bug_list = $dbh->selectcol_arrayref('SELECT bug_id FROM votes
+ WHERE who = ?', undef, $who);
+
+ foreach my $id (@$bug_list) {
+ $affected{$id} = 1;
+ }
+ $dbh->do('DELETE FROM votes WHERE who = ?', undef, $who);
+
+ my $sth_insertVotes = $dbh->prepare('INSERT INTO votes (who, bug_id, vote_count)
+ VALUES (?, ?, ?)');
+
+ # Insert the new values in their place
+ foreach my $id (@buglist) {
+ if ($votes{$id} > 0) {
+ $sth_insertVotes->execute($who, $id, $votes{$id});
+ }
+ $affected{$id} = 1;
+ }
+
+ # Update the cached values in the bugs table
+ print $cgi->header();
+ my @updated_bugs = ();
+
+ my $sth_getVotes = $dbh->prepare("SELECT SUM(vote_count) FROM votes
+ WHERE bug_id = ?");
+
+ my $sth_updateVotes = $dbh->prepare("UPDATE bugs SET votes = ?
+ WHERE bug_id = ?");
+
+ foreach my $id (keys %affected) {
+ $sth_getVotes->execute($id);
+ my $v = $sth_getVotes->fetchrow_array || 0;
+ $sth_updateVotes->execute($v, $id);
+
+ my $confirmed = _confirm_if_vote_confirmed($id);
+ push (@updated_bugs, $id) if $confirmed;
+ }
+
+ $dbh->bz_commit_transaction();
+
+ $vars->{'type'} = "votes";
+ $vars->{'mailrecipients'} = { 'changer' => $user->login };
+ $vars->{'title_tag'} = 'change_votes';
+ foreach my $bug_id (@updated_bugs) {
+ $vars->{'id'} = $bug_id;
+ $template->process("bug/process/results.html.tmpl", $vars)
+ || ThrowTemplateError($template->error());
+ # Set header_done to 1 only after the first bug.
+ $vars->{'header_done'} = 1;
+ }
+ $vars->{'votes_recorded'} = 1;
+}
+
+######################
+# Helper Subroutines #
+######################
+
+sub _modify_bug_votes {
+ my ($product, $changes) = @_;
+ my $dbh = Bugzilla->dbh;
+ my @msgs;
+
+ # 1. too many votes for a single user on a single bug.
+ my @toomanyvotes_list;
+ if ($product->{maxvotesperbug} < $product->{votesperuser}) {
+ my $votes = $dbh->selectall_arrayref(
+ 'SELECT votes.who, votes.bug_id
+ FROM votes
+ INNER JOIN bugs ON bugs.bug_id = votes.bug_id
+ WHERE bugs.product_id = ?
+ AND votes.vote_count > ?',
+ undef, ($product->id, $product->{maxvotesperbug}));
+
+ foreach my $vote (@$votes) {
+ my ($who, $id) = (@$vote);
+ # If some votes are removed, _remove_votes() returns a list
+ # of messages to send to voters.
+ push(@msgs, _remove_votes($id, $who, 'votes_too_many_per_bug'));
+ my $name = user_id_to_login($who);
+
+ push(@toomanyvotes_list, {id => $id, name => $name});
+ }
+ }
+
+ $changes->{'too_many_votes'} = \@toomanyvotes_list;
+
+ # 2. too many total votes for a single user.
+ # This part doesn't work in the general case because _remove_votes
+ # doesn't enforce votesperuser (except per-bug when it's less
+ # than maxvotesperbug). See _remove_votes().
+
+ my $votes = $dbh->selectall_arrayref(
+ 'SELECT votes.who, votes.vote_count
+ FROM votes
+ INNER JOIN bugs ON bugs.bug_id = votes.bug_id
+ WHERE bugs.product_id = ?',
+ undef, $product->id);
+
+ my %counts;
+ foreach my $vote (@$votes) {
+ my ($who, $count) = @$vote;
+ if (!defined $counts{$who}) {
+ $counts{$who} = $count;
+ } else {
+ $counts{$who} += $count;
+ }
+ }
+
+ my @toomanytotalvotes_list;
+ foreach my $who (keys(%counts)) {
+ if ($counts{$who} > $product->{votesperuser}) {
+ my $bug_ids = $dbh->selectcol_arrayref(
+ 'SELECT votes.bug_id
+ FROM votes
+ INNER JOIN bugs ON bugs.bug_id = votes.bug_id
+ WHERE bugs.product_id = ?
+ AND votes.who = ?',
+ undef, $product->id, $who);
+
+ foreach my $bug_id (@$bug_ids) {
+ # _remove_votes returns a list of messages to send
+ # in case some voters had too many votes.
+ push(@msgs, _remove_votes($bug_id, $who,
+ 'votes_too_many_per_user'));
+ my $name = user_id_to_login($who);
+
+ push(@toomanytotalvotes_list, {id => $bug_id, name => $name});
+ }
+ }
+ }
+
+ $changes->{'too_many_total_votes'} = \@toomanytotalvotes_list;
+
+ # 3. enough votes to confirm
+ my $bug_list = $dbh->selectcol_arrayref(
+ 'SELECT bug_id FROM bugs
+ WHERE product_id = ? AND bug_status = ? AND votes >= ?',
+ undef, ($product->id, 'UNCONFIRMED', $product->{votestoconfirm}));
+
+ my @updated_bugs;
+ foreach my $bug_id (@$bug_list) {
+ my $confirmed = _confirm_if_vote_confirmed($bug_id);
+ push (@updated_bugs, $bug_id) if $confirmed;
+ }
+ $changes->{'confirmed_bugs'} = \@updated_bugs;
+
+ # Now that changes are done, we can send emails to voters.
+ foreach my $msg (@msgs) {
+ MessageToMTA($msg);
+ }
+}
+
+# If a bug is moved to a product which allows less votes per bug
+# compared to the previous product, extra votes need to be removed.
+sub _remove_votes {
+ my ($id, $who, $reason) = (@_);
+ my $dbh = Bugzilla->dbh;
+
+ my $whopart = ($who) ? " AND votes.who = $who" : "";
+
+ my $sth = $dbh->prepare("SELECT profiles.login_name, " .
+ "profiles.userid, votes.vote_count, " .
+ "products.votesperuser, products.maxvotesperbug " .
+ "FROM profiles " .
+ "LEFT JOIN votes ON profiles.userid = votes.who " .
+ "LEFT JOIN bugs ON votes.bug_id = bugs.bug_id " .
+ "LEFT JOIN products ON products.id = bugs.product_id " .
+ "WHERE votes.bug_id = ? " . $whopart);
+ $sth->execute($id);
+ my @list;
+ while (my ($name, $userid, $oldvotes, $votesperuser, $maxvotesperbug) = $sth->fetchrow_array()) {
+ push(@list, [$name, $userid, $oldvotes, $votesperuser, $maxvotesperbug]);
+ }
+
+ # @messages stores all emails which have to be sent, if any.
+ # This array is passed to the caller which will send these emails itself.
+ my @messages = ();
+
+ if (scalar(@list)) {
+ foreach my $ref (@list) {
+ my ($name, $userid, $oldvotes, $votesperuser, $maxvotesperbug) = (@$ref);
+
+ $maxvotesperbug = min($votesperuser, $maxvotesperbug);
+
+ # If this product allows voting and the user's votes are in
+ # the acceptable range, then don't do anything.
+ next if $votesperuser && $oldvotes <= $maxvotesperbug;
+
+ # If the user has more votes on this bug than this product
+ # allows, then reduce the number of votes so it fits
+ my $newvotes = $maxvotesperbug;
+
+ my $removedvotes = $oldvotes - $newvotes;
+
+ if ($newvotes) {
+ $dbh->do("UPDATE votes SET vote_count = ? " .
+ "WHERE bug_id = ? AND who = ?",
+ undef, ($newvotes, $id, $userid));
+ } else {
+ $dbh->do("DELETE FROM votes WHERE bug_id = ? AND who = ?",
+ undef, ($id, $userid));
+ }
+
+ # Notice that we did not make sure that the user fit within the $votesperuser
+ # range. This is considered to be an acceptable alternative to losing votes
+ # during product moves. Then next time the user attempts to change their votes,
+ # they will be forced to fit within the $votesperuser limit.
+
+ # Now lets send the e-mail to alert the user to the fact that their votes have
+ # been reduced or removed.
+ my $vars = {
+ 'to' => $name . Bugzilla->params->{'emailsuffix'},
+ 'bugid' => $id,
+ 'reason' => $reason,
+
+ 'votesremoved' => $removedvotes,
+ 'votesold' => $oldvotes,
+ 'votesnew' => $newvotes,
+ };
+
+ my $voter = new Bugzilla::User($userid);
+ my $template = Bugzilla->template_inner($voter->settings->{'lang'}->{'value'});
+
+ my $msg;
+ $template->process("voting/votes-removed.txt.tmpl", $vars, \$msg);
+ push(@messages, $msg);
+ }
+ Bugzilla->template_inner("");
+
+ my $votes = $dbh->selectrow_array("SELECT SUM(vote_count) " .
+ "FROM votes WHERE bug_id = ?",
+ undef, $id) || 0;
+ $dbh->do("UPDATE bugs SET votes = ? WHERE bug_id = ?",
+ undef, ($votes, $id));
+ }
+ # Now return the array containing emails to be sent.
+ return @messages;
+}
+
+# If a user votes for a bug, or the number of votes required to
+# confirm a bug has been reduced, check if the bug is now confirmed.
+sub _confirm_if_vote_confirmed {
+ my $id = shift;
+ my $bug = new Bugzilla::Bug($id);
+
+ my $ret = 0;
+ if (!$bug->everconfirmed
+ and $bug->product_obj->{votestoconfirm}
+ and $bug->votes >= $bug->product_obj->{votestoconfirm})
+ {
+ $bug->add_comment('', { type => CMT_POPULAR_VOTES });
+
+ if ($bug->bug_status eq 'UNCONFIRMED') {
+ # Get a valid open state.
+ my $new_status;
+ foreach my $state (@{$bug->status->can_change_to}) {
+ if ($state->is_open && $state->name ne 'UNCONFIRMED') {
+ $new_status = $state->name;
+ last;
+ }
+ }
+ ThrowCodeError('no_open_bug_status') unless $new_status;
+
+ # We cannot call $bug->set_status() here, because a user without
+ # canconfirm privs should still be able to confirm a bug by
+ # popular vote. We already know the new status is valid, so it's safe.
+ $bug->{bug_status} = $new_status;
+ $bug->{everconfirmed} = 1;
+ delete $bug->{'status'}; # Contains the status object.
+ }
+ else {
+ # If the bug is in a closed state, only set everconfirmed to 1.
+ # Do not call $bug->_set_everconfirmed(), for the same reason as above.
+ $bug->{everconfirmed} = 1;
+ }
+ $bug->update();
+
+ $ret = 1;
+ }
+ return $ret;
+}
+
+
+__PACKAGE__->NAME;
diff --git a/mozilla/webtools/bugzilla/extensions/Voting/template/en/default/hook/account/prefs/email-relationships.html.tmpl b/mozilla/webtools/bugzilla/extensions/Voting/template/en/default/hook/account/prefs/email-relationships.html.tmpl
new file mode 100644
index 00000000000..0bd81eae101
--- /dev/null
+++ b/mozilla/webtools/bugzilla/extensions/Voting/template/en/default/hook/account/prefs/email-relationships.html.tmpl
@@ -0,0 +1,22 @@
+[%# 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 Everything Solved, Inc.
+ # Portions created by the Initial Developer are Copyright (C) 2010
+ # the Initial Developer. All Rights Reserved.
+ #
+ # Contributor(s):
+ # Max Kanat-Alexander
+ #%]
+
+[% relationships.push({ id = constants.REL_VOTER, description = "Voter" }) %]
+[% no_added_removed.push(constants.REL_VOTER) %]
diff --git a/mozilla/webtools/bugzilla/extensions/Voting/template/en/default/hook/admin/products/edit-common-rows.html.tmpl b/mozilla/webtools/bugzilla/extensions/Voting/template/en/default/hook/admin/products/edit-common-rows.html.tmpl
new file mode 100644
index 00000000000..fbbda3ea07a
--- /dev/null
+++ b/mozilla/webtools/bugzilla/extensions/Voting/template/en/default/hook/admin/products/edit-common-rows.html.tmpl
@@ -0,0 +1,60 @@
+[%# 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 Everything Solved, Inc.
+ # Portions created by the Initial Developer are Copyright (C) 2010
+ # the Initial Developer. All Rights Reserved.
+ #
+ # Contributor(s):
+ # Max Kanat-Alexander
+ #%]
+
+[% DEFAULT
+ product.maxvotesperbug = constants.DEFAULT_VOTES_PER_BUG
+ product.votesperuser = 0
+ product.votestoconfirm = 0
+%]
+
+
+
Maximum votes per person:
+
+
+
+
+
+
+ Maximum votes a person can put on a single [% terms.bug %]:
+
+
+
+
+
+
+
+ Confirm [% terms.abug %] if it gets this many votes:
+
+
+
+ (Setting this to 0 disables auto-confirming [% terms.bugs %]
+ by vote.)
+
+
+
+
diff --git a/mozilla/webtools/bugzilla/extensions/Voting/template/en/default/hook/admin/products/updated-changes.html.tmpl b/mozilla/webtools/bugzilla/extensions/Voting/template/en/default/hook/admin/products/updated-changes.html.tmpl
new file mode 100644
index 00000000000..876c5118797
--- /dev/null
+++ b/mozilla/webtools/bugzilla/extensions/Voting/template/en/default/hook/admin/products/updated-changes.html.tmpl
@@ -0,0 +1,102 @@
+[%# 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 Everything Solved, Inc.
+ # Portions created by the Initial Developer are Copyright (C) 2010
+ # the Initial Developer. All Rights Reserved.
+ #
+ # Contributor(s):
+ # Max Kanat-Alexander
+ #%]
+
+[% SET checkvotes = 0 %]
+
+[% IF changes.votesperuser.defined %]
+
+ Updated votes per user from
+ [%+ changes.votesperuser.0 FILTER html %] to
+ [%+ product.votesperuser FILTER html %].
+
+ [% checkvotes = 1 %]
+[% END %]
+
+[% IF changes.maxvotesperbug.defined %]
+
+ Updated maximum votes per [% terms.bug %] from
+ [%+ changes.maxvotesperbug.0 FILTER html %] to
+ [%+ product.maxvotesperbug FILTER html %].
+
+ [% checkvotes = 1 %]
+[% END %]
+
+[% IF changes.votestoconfirm.defined %]
+
+ Updated number of votes needed to confirm a [% terms.bug %] from
+ [%+ changes.votestoconfirm.0 FILTER html %] to
+ [%+ product.votestoconfirm FILTER html %].
+
+ [% checkvotes = 1 %]
+[% END %]
+
+[%# Note that this display of changed votes and/or confirmed bugs is
+ not very scalable. We could have a _lot_, and we just list them all.
+ One day we should limit this perhaps, or have a more scalable display %]
+
+[% IF checkvotes %]
+
+
+
Checking existing votes in this product for anybody who now
+ has too many votes for [% terms.abug %]...
+ [% IF changes.too_many_votes.size %]
+ [% FOREACH detail = changes.too_many_votes %]
+ →removed votes for [% terms.bug %]
+ [%- detail.id FILTER html %] from [% detail.name FILTER html %]
+ [% END %]
+ [% ELSE %]
+ →there were none.
+ [% END %]
+
+
+
Checking existing votes in this product for anybody
+ who now has too many total votes...
+ [% IF changes.too_many_total_votes.size %]
+ [% FOREACH detail = changes.too_many_total_votes %]
+ →removed votes for [% terms.bug %]
+ [%- detail.id FILTER html %] from [% detail.name FILTER html %]
+ [% END %]
+ [% ELSE %]
+ →there were none.
+ [% END %]
+
+
+
Checking unconfirmed [% terms.bugs %] in this product for any which now have
+ sufficient votes...
+ [% IF changes.confirmed_bugs.size %]
+ [% FOREACH id = changes.confirmed_bugs %]
+
+ [%# This is INCLUDED instead of PROCESSED to avoid variables getting
+ overwritten, which happens otherwise %]
+ [% INCLUDE bug/process/results.html.tmpl
+ type = 'votes'
+ mailrecipients = { 'changer' => user.login }
+ header_done = 1
+ id = id
+ %]
+ [% END %]
+ [% ELSE %]
+ →there were none.
+ [% END %]
+
+
+[% END %]
diff --git a/mozilla/webtools/bugzilla/extensions/Voting/template/en/default/hook/admin/sanitycheck/messages-statuses.html.tmpl b/mozilla/webtools/bugzilla/extensions/Voting/template/en/default/hook/admin/sanitycheck/messages-statuses.html.tmpl
new file mode 100644
index 00000000000..afb81d34c49
--- /dev/null
+++ b/mozilla/webtools/bugzilla/extensions/Voting/template/en/default/hook/admin/sanitycheck/messages-statuses.html.tmpl
@@ -0,0 +1,40 @@
+[%# 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 Everything Solved, Inc.
+ # Portions created by the Initial Developer are Copyright (C) 2010
+ # the Initial Developer. All Rights Reserved.
+ #
+ # Contributor(s):
+ # Max Kanat-Alexander
+ #%]
+
+[% IF san_tag == "voting_cache_rebuild_fix" %]
+ Click here to
+ rebuild the vote cache
+
+[% ELSIF san_tag == "voting_cache_alert" %]
+ Bad vote cache for [% PROCESS bug_link bug_id = id %]
+
+[% ELSIF san_tag == "voting_count_start" %]
+ Checking cached vote counts.
+
+[% ELSIF san_tag == "voting_count_alert" %]
+ Bad vote sum for [% terms.bug %] [%+ id FILTER html %].
+
+[% ELSIF san_tag == "voting_cache_rebuild_start" %]
+ OK, now rebuilding vote cache.
+
+[% ELSIF san_tag == "voting_cache_rebuild_end" %]
+ Vote cache has been rebuilt
+
+[% END %]
diff --git a/mozilla/webtools/bugzilla/extensions/Voting/template/en/default/hook/admin/users/confirm-delete-warn_safe.html.tmpl b/mozilla/webtools/bugzilla/extensions/Voting/template/en/default/hook/admin/users/confirm-delete-warn_safe.html.tmpl
new file mode 100644
index 00000000000..f799f12544a
--- /dev/null
+++ b/mozilla/webtools/bugzilla/extensions/Voting/template/en/default/hook/admin/users/confirm-delete-warn_safe.html.tmpl
@@ -0,0 +1,38 @@
+[%# 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 Everything Solved, Inc.
+ # Portions created by the Initial Developer are Copyright (C) 2010
+ # the Initial Developer. All Rights Reserved.
+ #
+ # Contributor(s):
+ # Max Kanat-Alexander
+ #%]
+
+[% IF votes %]
+
+ [% otheruser.login FILTER html %] has voted on
+ [% IF votes == 1 %]
+ [%+ terms.abug %]
+ [% ELSE %]
+ [%+ votes %] [%+ terms.bugs %]
+ [% END %].
+
+ If you delete the user account,
+ [% IF votes == 1 %]
+ this vote
+ [% ELSE %]
+ these votes
+ [% END %]
+ will be deleted along with the user account.
+
+[% END %]
diff --git a/mozilla/webtools/bugzilla/extensions/Voting/template/en/default/hook/bug/edit-after_importance.html.tmpl b/mozilla/webtools/bugzilla/extensions/Voting/template/en/default/hook/bug/edit-after_importance.html.tmpl
new file mode 100644
index 00000000000..7952442daf7
--- /dev/null
+++ b/mozilla/webtools/bugzilla/extensions/Voting/template/en/default/hook/bug/edit-after_importance.html.tmpl
@@ -0,0 +1,41 @@
+[%# 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 Everything Solved, Inc.
+ # Portions created by the Initial Developer are Copyright (C) 2010
+ # the Initial Developer. All Rights Reserved.
+ #
+ # Contributor(s):
+ # Max Kanat-Alexander
+ #%]
+[% IF bug.product_obj.votesperuser %]
+
+
+
+ [% IF bug.votes %]
+ with
+
+ [%- bug.votes %]
+ [% IF bug.votes == 1 %]
+ vote
+ [% ELSE %]
+ votes
+ [% END %]
+ [% END %]
+ (vote)
+
+[% END %]
diff --git a/mozilla/webtools/bugzilla/extensions/Voting/template/en/default/hook/bug/format_comment-type.txt.tmpl b/mozilla/webtools/bugzilla/extensions/Voting/template/en/default/hook/bug/format_comment-type.txt.tmpl
new file mode 100644
index 00000000000..ebba6fcabff
--- /dev/null
+++ b/mozilla/webtools/bugzilla/extensions/Voting/template/en/default/hook/bug/format_comment-type.txt.tmpl
@@ -0,0 +1,23 @@
+[%# 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 Everything Solved, Inc.
+ # Portions created by the Initial Developer are Copyright (C) 2010
+ # the Initial Developer. All Rights Reserved.
+ #
+ # Contributor(s):
+ # Max Kanat-Alexander
+ #%]
+
+[% IF comment.type == constants.CMT_POPULAR_VOTES %]
+*** This [% terms.bug %] has been confirmed by popular vote. ***
+[% END %]
diff --git a/mozilla/webtools/bugzilla/extensions/Voting/template/en/default/hook/bug/process/header-title.html.tmpl b/mozilla/webtools/bugzilla/extensions/Voting/template/en/default/hook/bug/process/header-title.html.tmpl
new file mode 100644
index 00000000000..a4530653b83
--- /dev/null
+++ b/mozilla/webtools/bugzilla/extensions/Voting/template/en/default/hook/bug/process/header-title.html.tmpl
@@ -0,0 +1,24 @@
+[%# 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 Everything Solved, Inc.
+ # Portions created by the Initial Developer are Copyright (C) 2010
+ # the Initial Developer. All Rights Reserved.
+ #
+ # Contributor(s):
+ # Max Kanat-Alexander
+ #%]
+
+[% IF title_tag == "change_votes" %]
+ [% title = "Change Votes" %]
+[% END %]
+
diff --git a/mozilla/webtools/bugzilla/extensions/Voting/template/en/default/hook/bug/process/results-title.html.tmpl b/mozilla/webtools/bugzilla/extensions/Voting/template/en/default/hook/bug/process/results-title.html.tmpl
new file mode 100644
index 00000000000..ae0d465dcc1
--- /dev/null
+++ b/mozilla/webtools/bugzilla/extensions/Voting/template/en/default/hook/bug/process/results-title.html.tmpl
@@ -0,0 +1,21 @@
+[%# 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 Everything Solved, Inc.
+ # Portions created by the Initial Developer are Copyright (C) 2010
+ # the Initial Developer. All Rights Reserved.
+ #
+ # Contributor(s):
+ # Max Kanat-Alexander
+ #%]
+
+[% title.votes = "$Link confirmed by number of votes" %]
diff --git a/mozilla/webtools/bugzilla/extensions/Voting/template/en/default/hook/global/field-descs-end.none.tmpl b/mozilla/webtools/bugzilla/extensions/Voting/template/en/default/hook/global/field-descs-end.none.tmpl
new file mode 100644
index 00000000000..2fd79808497
--- /dev/null
+++ b/mozilla/webtools/bugzilla/extensions/Voting/template/en/default/hook/global/field-descs-end.none.tmpl
@@ -0,0 +1,22 @@
+[%# 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 Everything Solved, Inc.
+ # Portions created by the Initial Developer are Copyright (C) 2010
+ # the Initial Developer. All Rights Reserved.
+ #
+ # Contributor(s):
+ # Max Kanat-Alexander
+ #%]
+
+[% field_descs.votes = "Votes" %]
+
diff --git a/mozilla/webtools/bugzilla/extensions/Voting/template/en/default/hook/global/reason-descs-end.none.tmpl b/mozilla/webtools/bugzilla/extensions/Voting/template/en/default/hook/global/reason-descs-end.none.tmpl
new file mode 100644
index 00000000000..3a1f5a189d7
--- /dev/null
+++ b/mozilla/webtools/bugzilla/extensions/Voting/template/en/default/hook/global/reason-descs-end.none.tmpl
@@ -0,0 +1,23 @@
+[%# 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 Everything Solved, Inc.
+ # Portions created by the Initial Developer are Copyright (C) 2010
+ # the Initial Developer. All Rights Reserved.
+ #
+ # Contributor(s):
+ # Max Kanat-Alexander
+ #%]
+
+[% reason_descs.${constants.REL_VOTER} = "You voted for the ${terms.bug}." %]
+[% watch_reason_descs.${constants.REL_VOTER} =
+ "You are watching a voter for the ${terms.bug}." %]
diff --git a/mozilla/webtools/bugzilla/extensions/Voting/template/en/default/hook/global/user-error-errors.html.tmpl b/mozilla/webtools/bugzilla/extensions/Voting/template/en/default/hook/global/user-error-errors.html.tmpl
new file mode 100644
index 00000000000..c2ff70728ea
--- /dev/null
+++ b/mozilla/webtools/bugzilla/extensions/Voting/template/en/default/hook/global/user-error-errors.html.tmpl
@@ -0,0 +1,55 @@
+[%# 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 Everything Solved, Inc.
+ # Portions created by the Initial Developer are Copyright (C) 2010
+ # the Initial Developer. All Rights Reserved.
+ #
+ # Contributor(s):
+ # Max Kanat-Alexander
+ #%]
+
+[% IF error == "voting_must_be_nonnegative" %]
+ [% title = "Votes Must Be Non-negative" %]
+ [% admindocslinks = {'voting.html' => 'Setting up the voting feature'} %]
+ Only use non-negative numbers for your [% terms.bug %] votes.
+
+[% ELSIF error == "voting_product_illegal_votes" %]
+ [% title = "Votes Must Be Non-negative" %]
+ [% admindocslinks = {'voting.html' => 'Setting up the voting feature'} %]
+ '[% votes FILTER html %]' is an invalid value for the
+
+ [% IF field == "votesperuser" %]
+ Votes Per User
+ [% ELSIF field == "maxvotesperbug" %]
+ Maximum Votes Per [% terms.Bug %]
+ [% ELSIF field == "votestoconfirm" %]
+ Votes To Confirm
+ [% END %]
+ field, which should contain a non-negative number.
+
+[% ELSIF error == "voting_too_many_votes_for_bug" %]
+ [% title = "Illegal Vote" %]
+ [% admindocslinks = {'voting.html' => 'Setting up the voting feature'} %]
+ You may only use at most [% max FILTER html %] votes for a single
+ [%+ terms.bug %] in the
+ [% product FILTER html %] product, but you are trying to
+ use [% votes FILTER html %].
+
+[% ELSIF error == "voting_too_many_votes_for_product" %]
+ [% title = "Illegal Vote" %]
+ [% admindocslinks = {'voting.html' => 'Setting up the voting feature'} %]
+ You tried to use [% votes FILTER html %] votes in the
+ [% product FILTER html %] product, which exceeds the maximum of
+ [%+ max FILTER html %] votes for this product.
+
+[% END %]
diff --git a/mozilla/webtools/bugzilla/extensions/Voting/template/en/default/hook/search/form-email_numbering_end.html.tmpl b/mozilla/webtools/bugzilla/extensions/Voting/template/en/default/hook/search/form-email_numbering_end.html.tmpl
new file mode 100644
index 00000000000..5acfff14cec
--- /dev/null
+++ b/mozilla/webtools/bugzilla/extensions/Voting/template/en/default/hook/search/form-email_numbering_end.html.tmpl
@@ -0,0 +1,31 @@
+[%# 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 Everything Solved, Inc.
+ # Portions created by the Initial Developer are Copyright (C) 2010
+ # the Initial Developer. All Rights Reserved.
+ #
+ # Contributor(s):
+ # Max Kanat-Alexander
+ #%]
+
+
+
+ :
+
+
+ votes
+
+
+
+
diff --git a/mozilla/webtools/bugzilla/extensions/Voting/template/en/default/hook/search/search-report-select-rep_fields.html.tmpl b/mozilla/webtools/bugzilla/extensions/Voting/template/en/default/hook/search/search-report-select-rep_fields.html.tmpl
new file mode 100644
index 00000000000..ca74f6d2dc6
--- /dev/null
+++ b/mozilla/webtools/bugzilla/extensions/Voting/template/en/default/hook/search/search-report-select-rep_fields.html.tmpl
@@ -0,0 +1,21 @@
+[%# 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 Everything Solved, Inc.
+ # Portions created by the Initial Developer are Copyright (C) 2010
+ # the Initial Developer. All Rights Reserved.
+ #
+ # Contributor(s):
+ # Max Kanat-Alexander
+ #%]
+
+[% rep_fields.push('votes') %]
diff --git a/mozilla/webtools/bugzilla/template/en/default/pages/voting.html.tmpl b/mozilla/webtools/bugzilla/extensions/Voting/template/en/default/pages/voting.html.tmpl
similarity index 97%
rename from mozilla/webtools/bugzilla/template/en/default/pages/voting.html.tmpl
rename to mozilla/webtools/bugzilla/extensions/Voting/template/en/default/pages/voting.html.tmpl
index 4e6fb473d64..99026c0d555 100644
--- a/mozilla/webtools/bugzilla/template/en/default/pages/voting.html.tmpl
+++ b/mozilla/webtools/bugzilla/extensions/Voting/template/en/default/pages/voting.html.tmpl
@@ -64,6 +64,6 @@ a few [% terms.bugs %] indicating your strong support for them.
on [% terms.bugs %] you vote for.
You may review your votes at any time by clicking on the "My Votes" link in the page footer.
+"page.cgi?id=voting/user.html">My Votes" link in the page footer.
[% INCLUDE global/footer.html.tmpl %]
diff --git a/mozilla/webtools/bugzilla/template/en/default/bug/votes/list-for-bug.html.tmpl b/mozilla/webtools/bugzilla/extensions/Voting/template/en/default/pages/voting/bug.html.tmpl
similarity index 83%
rename from mozilla/webtools/bugzilla/template/en/default/bug/votes/list-for-bug.html.tmpl
rename to mozilla/webtools/bugzilla/extensions/Voting/template/en/default/pages/voting/bug.html.tmpl
index a599dc0fb9e..03434a50530 100644
--- a/mozilla/webtools/bugzilla/template/en/default/bug/votes/list-for-bug.html.tmpl
+++ b/mozilla/webtools/bugzilla/extensions/Voting/template/en/default/pages/voting/bug.html.tmpl
@@ -16,10 +16,11 @@
# Rights Reserved.
#
# Contributor(s): Gervase Markham
+ # Max Kanat-Alexander
#%]
[%# INTERFACE:
- # bug_id: integer. ID of the bug we are listing the votes for.
+ # bug: Bugzilla::Bug that we are listing the votes for.
# users: list of hashes. May be empty. Each hash has two members:
# login_name: string. The login name of the user whose vote is attached
# vote_count: integer. The number of times that user has votes for this bug.
@@ -29,7 +30,7 @@
[% PROCESS global/header.html.tmpl
title = "Show Votes"
- subheader = "$terms.Bug $bug_id"
+ subheader = "$terms.Bug $bug.id" FILTER bug_link(bug)
%]
[% total = 0 %]
@@ -43,18 +44,18 @@
[% total = total + voter.vote_count %]
- [% voter.vote_count %]
+ [% voter.vote_count FILTER html %]
[% END %]
-
Total votes: [% total %]
+
Total votes: [% total FILTER html %]
[% PROCESS global/footer.html.tmpl %]
diff --git a/mozilla/webtools/bugzilla/template/en/default/bug/votes/list-for-user.html.tmpl b/mozilla/webtools/bugzilla/extensions/Voting/template/en/default/pages/voting/user.html.tmpl
similarity index 90%
rename from mozilla/webtools/bugzilla/template/en/default/bug/votes/list-for-user.html.tmpl
rename to mozilla/webtools/bugzilla/extensions/Voting/template/en/default/pages/voting/user.html.tmpl
index 2f97616ed69..80007922433 100644
--- a/mozilla/webtools/bugzilla/template/en/default/bug/votes/list-for-user.html.tmpl
+++ b/mozilla/webtools/bugzilla/extensions/Voting/template/en/default/pages/voting/user.html.tmpl
@@ -31,7 +31,7 @@
# maxvotes: max votes allowed for a user in this product
# maxperbug: max votes per bug allowed for a user in this product
#
- # bug_id: number; if the user is voting for a bug, this is the bug id
+ # this_bug: Bugzilla::Bug; if the user is voting for a bug, this is the bug
#
# canedit: boolean; Should the votes be presented in a form, or readonly?
#
@@ -44,18 +44,18 @@
[% subheader = voting_user.login FILTER html %]
[% IF canedit %]
[% title = "Change Votes" %]
- [% IF bug_id %]
+ [% IF this_bug %]
[%# We .select and .focus the input so it works for textbox and
checkbox %]
- [% onload = "document.forms['voting_form'].bug_" _ bug_id _
- ".select();document.forms['voting_form'].bug_" _ bug_id _
+ [% onload = "document.forms['voting_form'].bug_" _ this_bug.id _
+ ".select();document.forms['voting_form'].bug_" _ this_bug.id _
".focus()" %]
[% END %]
[% ELSE %]
[% title = "Show Votes" %]
[% END %]
[% PROCESS global/header.html.tmpl
- style_urls = [ "skins/standard/voting.css" ]
+ style_urls = [ "extensions/Voting/web/style.css" ]
%]
[% ELSE %]
@@ -72,7 +72,7 @@
[% END %]
[% IF products.size %]
-