Bug 372979: Make voting into an extension
r=mkanat, a=mkanat, a=LpSolit git-svn-id: svn://10.0.0.236/trunk@259709 18797224-902f-48f8-a5cc-f745e15eee43
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -1 +1 @@
|
||||
6990
|
||||
6991
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 });
|
||||
|
||||
@@ -71,12 +71,6 @@ sub get_param_list {
|
||||
default => 0
|
||||
},
|
||||
|
||||
{
|
||||
name => 'usevotes',
|
||||
type => 'b',
|
||||
default => 0
|
||||
},
|
||||
|
||||
{
|
||||
name => 'usebugaliases',
|
||||
type => 'b',
|
||||
|
||||
@@ -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"
|
||||
};
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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},
|
||||
|
||||
@@ -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');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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__,
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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<string> The "URL" field of a bug.
|
||||
|
||||
C<string> The Version field of a bug.
|
||||
|
||||
=item C<votes>
|
||||
|
||||
C<int> Searches for bugs with this many votes or greater. May not
|
||||
be an array.
|
||||
|
||||
=item C<whiteboard>
|
||||
|
||||
C<string> 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<votes> was removed in Bugzilla B<3.8>.
|
||||
|
||||
=back
|
||||
|
||||
=back
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
maintainer CDATA #REQUIRED
|
||||
exporter CDATA #IMPLIED
|
||||
>
|
||||
<!ELEMENT bug (bug_id, (alias?, creation_ts, short_desc, delta_ts, reporter_accessible, cclist_accessible, classification_id, classification, product, component, version, rep_platform, op_sys, bug_status, resolution?, dup_id?, bug_file_loc?, status_whiteboard?, keywords*, priority, bug_severity, target_milestone?, dependson*, blocked*, votes?, everconfirmed, reporter, assigned_to, qa_contact?, cc*, (estimated_time, remaining_time, actual_time, deadline)?, group*, flag*, long_desc*, attachment*)?)>
|
||||
<!ELEMENT bug (bug_id, (alias?, creation_ts, short_desc, delta_ts, reporter_accessible, cclist_accessible, classification_id, classification, product, component, version, rep_platform, op_sys, bug_status, resolution?, dup_id?, bug_file_loc?, status_whiteboard?, keywords*, priority, bug_severity, target_milestone?, dependson*, blocked*, everconfirmed, reporter, assigned_to, qa_contact?, cc*, (estimated_time, remaining_time, actual_time, deadline)?, group*, flag*, long_desc*, attachment*)?)>
|
||||
<!ATTLIST bug
|
||||
error (NotFound | NotPermitted | InvalidBugId) #IMPLIED
|
||||
>
|
||||
@@ -39,7 +39,6 @@
|
||||
<!ELEMENT keywords (#PCDATA)>
|
||||
<!ELEMENT dependson (#PCDATA)>
|
||||
<!ELEMENT blocked (#PCDATA)>
|
||||
<!ELEMENT votes (#PCDATA)>
|
||||
<!ELEMENT everconfirmed (#PCDATA)>
|
||||
<!ELEMENT cc (#PCDATA)>
|
||||
<!ELEMENT group (#PCDATA)>
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
@@ -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'],
|
||||
|
||||
@@ -968,8 +968,7 @@ UNCONFIRMED state#</dia:string>
|
||||
<dia:attribute name="text">
|
||||
<dia:composite type="text">
|
||||
<dia:attribute name="string">
|
||||
<dia:string>#Bug confirmed or
|
||||
receives enough votes#</dia:string>
|
||||
<dia:string>#Bug confirmed#</dia:string>
|
||||
</dia:attribute>
|
||||
<dia:attribute name="font">
|
||||
<dia:font family="sans" style="0" name="Helvetica"/>
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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));
|
||||
|
||||
|
||||
861
mozilla/webtools/bugzilla/extensions/Voting/Extension.pm
Normal file
861
mozilla/webtools/bugzilla/extensions/Voting/Extension.pm
Normal file
@@ -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 <terry@mozilla.org>
|
||||
# Stephan Niemz <st.n@gmx.net>
|
||||
# Christopher Aillon <christopher@aillon.com>
|
||||
# Gervase Markham <gerv@gerv.net>
|
||||
# Frédéric Buclin <LpSolit@gmail.com>
|
||||
# Max Kanat-Alexander <mkanat@bugzilla.org>
|
||||
|
||||
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;
|
||||
@@ -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 <mkanat@bugzilla.org>
|
||||
#%]
|
||||
|
||||
[% relationships.push({ id = constants.REL_VOTER, description = "Voter" }) %]
|
||||
[% no_added_removed.push(constants.REL_VOTER) %]
|
||||
@@ -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 <mkanat@bugzilla.org>
|
||||
#%]
|
||||
|
||||
[% DEFAULT
|
||||
product.maxvotesperbug = constants.DEFAULT_VOTES_PER_BUG
|
||||
product.votesperuser = 0
|
||||
product.votestoconfirm = 0
|
||||
%]
|
||||
|
||||
<tr>
|
||||
<th align="right">Maximum votes per person:</th>
|
||||
<td><input size="5" maxlength="5" name="votesperuser" id="votesperuser"
|
||||
value="[% product.votesperuser FILTER html %]">
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<th align="right">
|
||||
Maximum votes a person can put on a single [% terms.bug %]:
|
||||
</th>
|
||||
<td><input size="5" maxlength="5" name="maxvotesperbug" id="maxvotesperbug"
|
||||
value="[% product.maxvotesperbug FILTER html %]">
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr id="votes_to_confirm_container"
|
||||
[%- ' class="bz_default_hidden"' IF !product.allows_unconfirmed %]>
|
||||
<th align="right">
|
||||
Confirm [% terms.abug %] if it gets this many votes:
|
||||
</th>
|
||||
<td>
|
||||
<input size="3" maxlength="5" name="votestoconfirm" id="votestoconfirm"
|
||||
value="[% product.votestoconfirm FILTER html %]">
|
||||
<br>(Setting this to 0 disables auto-confirming [% terms.bugs %]
|
||||
by vote.)
|
||||
<script type="text/javascript">
|
||||
YAHOO.util.Event.addListener('allows_unconfirmed', 'change',
|
||||
function() { bz_toggleClass('votes_to_confirm_container',
|
||||
'bz_default_hidden'); });
|
||||
</script>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
@@ -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 <mkanat@bugzilla.org>
|
||||
#%]
|
||||
|
||||
[% SET checkvotes = 0 %]
|
||||
|
||||
[% IF changes.votesperuser.defined %]
|
||||
<p>
|
||||
Updated votes per user from
|
||||
[%+ changes.votesperuser.0 FILTER html %] to
|
||||
[%+ product.votesperuser FILTER html %].
|
||||
</p>
|
||||
[% checkvotes = 1 %]
|
||||
[% END %]
|
||||
|
||||
[% IF changes.maxvotesperbug.defined %]
|
||||
<p>
|
||||
Updated maximum votes per [% terms.bug %] from
|
||||
[%+ changes.maxvotesperbug.0 FILTER html %] to
|
||||
[%+ product.maxvotesperbug FILTER html %].
|
||||
</p>
|
||||
[% checkvotes = 1 %]
|
||||
[% END %]
|
||||
|
||||
[% IF changes.votestoconfirm.defined %]
|
||||
<p>
|
||||
Updated number of votes needed to confirm a [% terms.bug %] from
|
||||
[%+ changes.votestoconfirm.0 FILTER html %] to
|
||||
[%+ product.votestoconfirm FILTER html %].
|
||||
</p>
|
||||
[% 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 %]
|
||||
<hr>
|
||||
|
||||
<p>Checking existing votes in this product for anybody who now
|
||||
has too many votes for [% terms.abug %]...<br>
|
||||
[% IF changes.too_many_votes.size %]
|
||||
[% FOREACH detail = changes.too_many_votes %]
|
||||
→removed votes for [% terms.bug %] <a href="show_bug.cgi?id=
|
||||
[%- detail.id FILTER url_quote %]">
|
||||
[%- detail.id FILTER html %]</a> from [% detail.name FILTER html %]<br>
|
||||
[% END %]
|
||||
[% ELSE %]
|
||||
→there were none.
|
||||
[% END %]
|
||||
</p>
|
||||
|
||||
<p>Checking existing votes in this product for anybody
|
||||
who now has too many total votes...<br>
|
||||
[% IF changes.too_many_total_votes.size %]
|
||||
[% FOREACH detail = changes.too_many_total_votes %]
|
||||
→removed votes for [% terms.bug %] <a href="show_bug.cgi?id=
|
||||
[%- detail.id FILTER url_quote %]">
|
||||
[%- detail.id FILTER html %]</a> from [% detail.name FILTER html %]<br>
|
||||
[% END %]
|
||||
[% ELSE %]
|
||||
→there were none.
|
||||
[% END %]
|
||||
</p>
|
||||
|
||||
<p>Checking unconfirmed [% terms.bugs %] in this product for any which now have
|
||||
sufficient votes...<br>
|
||||
[% 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 %]
|
||||
</p>
|
||||
|
||||
[% END %]
|
||||
@@ -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 <mkanat@bugzilla.org>
|
||||
#%]
|
||||
|
||||
[% IF san_tag == "voting_cache_rebuild_fix" %]
|
||||
<a href="sanitycheck.cgi?rebuild_vote_cache=1">Click here to
|
||||
rebuild the vote cache</a>
|
||||
|
||||
[% 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 %]
|
||||
@@ -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 <mkanat@bugzilla.org>
|
||||
#%]
|
||||
|
||||
[% IF votes %]
|
||||
<li>
|
||||
[% 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.
|
||||
</li>
|
||||
[% END %]
|
||||
@@ -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 <mkanat@bugzilla.org>
|
||||
#%]
|
||||
[% IF bug.product_obj.votesperuser %]
|
||||
<style type="text/css">
|
||||
#votes_container { white-space: nowrap; }
|
||||
</style>
|
||||
|
||||
<span id="votes_container">
|
||||
[% IF bug.votes %]
|
||||
with
|
||||
<a href="page.cgi?id=voting/bug.html?bug_id=
|
||||
[%- bug.id FILTER url_quote %]">
|
||||
[%- bug.votes %]
|
||||
[% IF bug.votes == 1 %]
|
||||
vote
|
||||
[% ELSE %]
|
||||
votes
|
||||
[% END %]</a>
|
||||
[% END %]
|
||||
(<a href="page.cgi?id=voting/user.html&bug_id=
|
||||
[%- bug.id FILTER url_quote %]#vote_
|
||||
[%- bug.id FILTER url_quote %]">vote</a>)
|
||||
</span>
|
||||
[% END %]
|
||||
@@ -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 <mkanat@bugzilla.org>
|
||||
#%]
|
||||
|
||||
[% IF comment.type == constants.CMT_POPULAR_VOTES %]
|
||||
*** This [% terms.bug %] has been confirmed by popular vote. ***
|
||||
[% END %]
|
||||
@@ -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 <mkanat@bugzilla.org>
|
||||
#%]
|
||||
|
||||
[% IF title_tag == "change_votes" %]
|
||||
[% title = "Change Votes" %]
|
||||
[% END %]
|
||||
|
||||
@@ -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 <mkanat@bugzilla.org>
|
||||
#%]
|
||||
|
||||
[% title.votes = "$Link confirmed by number of votes" %]
|
||||
@@ -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 <mkanat@bugzilla.org>
|
||||
#%]
|
||||
|
||||
[% field_descs.votes = "Votes" %]
|
||||
|
||||
@@ -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 <mkanat@bugzilla.org>
|
||||
#%]
|
||||
|
||||
[% 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}." %]
|
||||
@@ -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 <mkanat@bugzilla.org>
|
||||
#%]
|
||||
|
||||
[% 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
|
||||
<em>
|
||||
[% IF field == "votesperuser" %]
|
||||
Votes Per User
|
||||
[% ELSIF field == "maxvotesperbug" %]
|
||||
Maximum Votes Per [% terms.Bug %]
|
||||
[% ELSIF field == "votestoconfirm" %]
|
||||
Votes To Confirm
|
||||
[% END %]
|
||||
</em> 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
|
||||
<tt>[% product FILTER html %]</tt> 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
|
||||
<tt>[% product FILTER html %]</tt> product, which exceeds the maximum of
|
||||
[%+ max FILTER html %] votes for this product.
|
||||
|
||||
[% END %]
|
||||
@@ -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 <mkanat@bugzilla.org>
|
||||
#%]
|
||||
|
||||
<tr>
|
||||
<td align="right">
|
||||
<label for="votes">Only [% terms.bugs %] with at least</label>:
|
||||
</td>
|
||||
<td>
|
||||
<input name="votes" id="votes" size="3"
|
||||
value="[% default.votes.0 FILTER html %]"> votes
|
||||
<input type="hidden" name="votes_type" value="greaterthaneq">
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
@@ -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 <mkanat@bugzilla.org>
|
||||
#%]
|
||||
|
||||
[% rep_fields.push('votes') %]
|
||||
@@ -64,6 +64,6 @@ a few [% terms.bugs %] indicating your strong support for them.
|
||||
on [% terms.bugs %] you vote for.</p>
|
||||
|
||||
<p>You may review your votes at any time by clicking on the "<a href=
|
||||
"votes.cgi?action=show_user">My Votes</a>" link in the page footer.</p>
|
||||
"page.cgi?id=voting/user.html">My Votes</a>" link in the page footer.</p>
|
||||
|
||||
[% INCLUDE global/footer.html.tmpl %]
|
||||
@@ -16,10 +16,11 @@
|
||||
# Rights Reserved.
|
||||
#
|
||||
# Contributor(s): Gervase Markham <gerv@gerv.net>
|
||||
# Max Kanat-Alexander <mkanat@bugzilla.org>
|
||||
#%]
|
||||
|
||||
[%# 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 <a href=\"show_bug.cgi?id=$bug_id\">$bug_id</a>"
|
||||
subheader = "$terms.Bug $bug.id" FILTER bug_link(bug)
|
||||
%]
|
||||
|
||||
[% total = 0 %]
|
||||
@@ -43,18 +44,18 @@
|
||||
[% total = total + voter.vote_count %]
|
||||
<tr>
|
||||
<td>
|
||||
<a href="votes.cgi?action=show_user&user_id=
|
||||
<a href="page.cgi?id=voting/user.html&user_id=
|
||||
[%- voter.id FILTER url_quote %]">
|
||||
[% voter.login_name FILTER email FILTER html %]
|
||||
</a>
|
||||
</td>
|
||||
<td align="right">
|
||||
[% voter.vote_count %]
|
||||
[% voter.vote_count FILTER html %]
|
||||
</td>
|
||||
</tr>
|
||||
[% END %]
|
||||
</table>
|
||||
|
||||
<p>Total votes: [% total %]</p>
|
||||
<p>Total votes: [% total FILTER html %]</p>
|
||||
|
||||
[% PROCESS global/footer.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 %]
|
||||
<hr>
|
||||
@@ -72,7 +72,7 @@
|
||||
[% END %]
|
||||
|
||||
[% IF products.size %]
|
||||
<form name="voting_form" method="post" action="votes.cgi">
|
||||
<form name="voting_form" method="post" action="page.cgi?id=voting/user.html">
|
||||
<input type="hidden" name="action" value="vote">
|
||||
<table cellspacing="4">
|
||||
<tr>
|
||||
@@ -108,9 +108,9 @@
|
||||
</tr>
|
||||
|
||||
[% FOREACH bug = product.bugs %]
|
||||
<tr [% IF bug.id == bug_id && canedit %]
|
||||
<tr [% IF bug.id == this_bug.id && canedit %]
|
||||
class="bz_bug_being_voted_on" [% END %]>
|
||||
<td>[% IF bug.id == bug_id && canedit %]Enter New Vote here →
|
||||
<td>[% IF bug.id == this_bug.id && canedit %]Enter New Vote here →
|
||||
[%- END %]</td>
|
||||
<td align="right"><a name="vote_[% bug.id %]">
|
||||
[% IF canedit %]
|
||||
@@ -130,7 +130,7 @@
|
||||
</td>
|
||||
<td>
|
||||
[% bug.summary FILTER html %]
|
||||
(<a href="votes.cgi?action=show_bug&bug_id=[% bug.id %]">Show Votes</a>)
|
||||
(<a href="page.cgi?id=voting/bug.html&bug_id=[% bug.id %]">Show Votes</a>)
|
||||
</td>
|
||||
</tr>
|
||||
[% END %]
|
||||
@@ -33,7 +33,7 @@
|
||||
remove your vote from every [% terms.bug %] you've voted on?
|
||||
</p>
|
||||
|
||||
<form action="votes.cgi" method="post">
|
||||
<form action="page.cgi?id=voting/user.html" method="post">
|
||||
<input type="hidden" name="action" value="vote">
|
||||
<p>
|
||||
<input type="radio" name="delete_all_votes" value="1">
|
||||
@@ -980,7 +980,6 @@ sub process_bug {
|
||||
if($status eq "UNCONFIRMED"){
|
||||
$err .= "Bug Status was UNCONFIRMED but everconfirmed was true\n";
|
||||
$err .= " Setting status to $initial_status\n";
|
||||
$err .= "Resetting votes to 0\n" if ( $bug_fields{'votes'} );
|
||||
$status = $initial_status;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -573,27 +573,12 @@ foreach my $bug (@bug_objects) {
|
||||
# an error later.
|
||||
delete $changed_deps{''};
|
||||
|
||||
# @msgs will store emails which have to be sent to voters, if any.
|
||||
my @msgs;
|
||||
if ($changes->{'product'}) {
|
||||
# If some votes have been removed, RemoveVotes() returns
|
||||
# a list of messages to send to voters.
|
||||
# We delay the sending of these messages till changes are committed.
|
||||
@msgs = RemoveVotes($bug->id, 0, 'votes_bug_moved');
|
||||
CheckIfVotedConfirmed($bug->id);
|
||||
}
|
||||
|
||||
$dbh->bz_commit_transaction();
|
||||
|
||||
###############
|
||||
# Send Emails #
|
||||
###############
|
||||
|
||||
# Now is a good time to send email to voters.
|
||||
foreach my $msg (@msgs) {
|
||||
MessageToMTA($msg);
|
||||
}
|
||||
|
||||
my $old_qa = $changes->{'qa_contact'} ? $changes->{'qa_contact'}->[0] : '';
|
||||
my $old_own = $changes->{'assigned_to'} ? $changes->{'assigned_to'}->[0] : '';
|
||||
my $old_cc = $changes->{cc} ? $changes->{cc}->[0] : '';
|
||||
|
||||
@@ -127,7 +127,7 @@ sub PrefillForm {
|
||||
"email", "emailtype", "emailreporter",
|
||||
"emailassigned_to", "emailcc", "emailqa_contact",
|
||||
"emaillongdesc", "content",
|
||||
"changedin", "votes", "short_desc", "short_desc_type",
|
||||
"changedin", "short_desc", "short_desc_type",
|
||||
"longdesc", "longdesc_type", "bug_file_loc",
|
||||
"bug_file_loc_type", "status_whiteboard",
|
||||
"status_whiteboard_type", "bug_id",
|
||||
|
||||
@@ -113,7 +113,6 @@ my @columns = qw(
|
||||
qa_contact
|
||||
classification
|
||||
version
|
||||
votes
|
||||
keywords
|
||||
target_milestone
|
||||
);
|
||||
|
||||
@@ -102,7 +102,7 @@ unless (Bugzilla->usage_mode == USAGE_MODE_CMDLINE) {
|
||||
# Users with 'editkeywords' privs only can only check keywords.
|
||||
###########################################################################
|
||||
unless ($user->in_group('editcomponents')) {
|
||||
check_votes_or_keywords('keywords');
|
||||
check_keywords();
|
||||
Status('checks_completed');
|
||||
|
||||
$template->process('global/footer.html.tmpl', $vars)
|
||||
@@ -110,27 +110,6 @@ unless ($user->in_group('editcomponents')) {
|
||||
exit;
|
||||
}
|
||||
|
||||
###########################################################################
|
||||
# Fix vote cache
|
||||
###########################################################################
|
||||
|
||||
if ($cgi->param('rebuildvotecache')) {
|
||||
Status('vote_cache_rebuild_start');
|
||||
$dbh->bz_start_transaction();
|
||||
$dbh->do(q{UPDATE bugs SET votes = 0});
|
||||
my $sth_update = $dbh->prepare(q{UPDATE bugs
|
||||
SET votes = ?
|
||||
WHERE bug_id = ?});
|
||||
my $sth = $dbh->prepare(q{SELECT bug_id, SUM(vote_count)
|
||||
FROM votes }. $dbh->sql_group_by('bug_id'));
|
||||
$sth->execute();
|
||||
while (my ($id, $v) = $sth->fetchrow_array) {
|
||||
$sth_update->execute($v, $id);
|
||||
}
|
||||
$dbh->bz_commit_transaction();
|
||||
Status('vote_cache_rebuild_end');
|
||||
}
|
||||
|
||||
###########################################################################
|
||||
# Create missing group_control_map entries
|
||||
###########################################################################
|
||||
@@ -310,7 +289,7 @@ if ($cgi->param('remove_invalid_bug_references')) {
|
||||
'bugs_fulltext/', 'cc/',
|
||||
'dependencies/blocked', 'dependencies/dependson',
|
||||
'duplicates/dupe', 'duplicates/dupe_of',
|
||||
'flags/', 'keywords/', 'longdescs/', 'votes/') {
|
||||
'flags/', 'keywords/', 'longdescs/') {
|
||||
|
||||
my ($table, $field) = split('/', $pair);
|
||||
$field ||= "bug_id";
|
||||
@@ -489,7 +468,6 @@ CrossCheck("bugs", "bug_id",
|
||||
["dependencies", "blocked"],
|
||||
["dependencies", "dependson"],
|
||||
['flags', 'bug_id'],
|
||||
["votes", "bug_id"],
|
||||
["keywords", "bug_id"],
|
||||
["duplicates", "dupe_of", "dupe"],
|
||||
["duplicates", "dupe", "dupe_of"]);
|
||||
@@ -524,7 +502,6 @@ CrossCheck("profiles", "userid",
|
||||
["bugs_activity", "who", "bug_id"],
|
||||
["cc", "who", "bug_id"],
|
||||
['quips', 'userid'],
|
||||
["votes", "who", "bug_id"],
|
||||
["longdescs", "who", "bug_id"],
|
||||
["logincookies", "userid"],
|
||||
["namedqueries", "userid"],
|
||||
@@ -681,76 +658,20 @@ while (my ($id, $email) = $sth->fetchrow_array) {
|
||||
}
|
||||
|
||||
###########################################################################
|
||||
# Perform vote/keyword cache checks
|
||||
# Perform keyword cache checks
|
||||
###########################################################################
|
||||
|
||||
check_votes_or_keywords();
|
||||
|
||||
sub check_votes_or_keywords {
|
||||
my $check = shift || 'all';
|
||||
|
||||
my $dbh = Bugzilla->dbh;
|
||||
my $sth = $dbh->prepare(q{SELECT bug_id, votes, keywords
|
||||
FROM bugs
|
||||
WHERE votes != 0 OR keywords != ''});
|
||||
$sth->execute;
|
||||
|
||||
my %votes;
|
||||
my %keyword;
|
||||
|
||||
while (my ($id, $v, $k) = $sth->fetchrow_array) {
|
||||
if ($v != 0) {
|
||||
$votes{$id} = $v;
|
||||
}
|
||||
if ($k) {
|
||||
$keyword{$id} = $k;
|
||||
}
|
||||
}
|
||||
|
||||
# If we only want to check keywords, skip checks about votes.
|
||||
_check_votes(\%votes) unless ($check eq 'keywords');
|
||||
# If we only want to check votes, skip checks about keywords.
|
||||
_check_keywords(\%keyword) unless ($check eq 'votes');
|
||||
}
|
||||
|
||||
sub _check_votes {
|
||||
my $votes = shift;
|
||||
|
||||
Status('vote_count_start');
|
||||
my $dbh = Bugzilla->dbh;
|
||||
my $sth = $dbh->prepare(q{SELECT bug_id, SUM(vote_count)
|
||||
FROM votes }.
|
||||
$dbh->sql_group_by('bug_id'));
|
||||
$sth->execute;
|
||||
|
||||
my $offer_votecache_rebuild = 0;
|
||||
|
||||
while (my ($id, $v) = $sth->fetchrow_array) {
|
||||
if ($v <= 0) {
|
||||
Status('vote_count_alert', {id => $id}, 'alert');
|
||||
} else {
|
||||
if (!defined $votes->{$id} || $votes->{$id} != $v) {
|
||||
Status('vote_cache_alert', {id => $id}, 'alert');
|
||||
$offer_votecache_rebuild = 1;
|
||||
}
|
||||
delete $votes->{$id};
|
||||
}
|
||||
}
|
||||
foreach my $id (keys %$votes) {
|
||||
Status('vote_cache_alert', {id => $id}, 'alert');
|
||||
$offer_votecache_rebuild = 1;
|
||||
}
|
||||
|
||||
Status('vote_cache_rebuild_fix') if $offer_votecache_rebuild;
|
||||
}
|
||||
|
||||
sub _check_keywords {
|
||||
my $keyword = shift;
|
||||
|
||||
Status('keyword_check_start');
|
||||
sub check_keywords {
|
||||
my $dbh = Bugzilla->dbh;
|
||||
my $cgi = Bugzilla->cgi;
|
||||
|
||||
my %keyword = @{ $dbh->selectcol_arrayref(
|
||||
q{SELECT bug_id, keywords FROM bugs WHERE keywords != ''},
|
||||
{Columns=>[1,2]}) };
|
||||
|
||||
|
||||
Status('keyword_check_start');
|
||||
|
||||
my %keywordids;
|
||||
my $keywords = $dbh->selectall_arrayref(q{SELECT id, name
|
||||
FROM keyworddefs});
|
||||
@@ -819,13 +740,13 @@ sub _check_keywords {
|
||||
|
||||
my @badbugs = ();
|
||||
|
||||
foreach my $b (keys(%$keyword)) {
|
||||
if (!exists $realk{$b} || $realk{$b} ne $keyword->{$b}) {
|
||||
foreach my $b (keys(%keyword)) {
|
||||
if (!exists $realk{$b} || $realk{$b} ne $keyword{$b}) {
|
||||
push(@badbugs, $b);
|
||||
}
|
||||
}
|
||||
foreach my $b (keys(%realk)) {
|
||||
if (!exists $keyword->{$b}) {
|
||||
if (!exists $keyword{$b}) {
|
||||
push(@badbugs, $b);
|
||||
}
|
||||
}
|
||||
@@ -973,13 +894,6 @@ my $confirmed_open_states = join(', ', map {$dbh->quote($_)} @confirmed_open_sta
|
||||
BugCheck("bugs WHERE bug_status IN ($confirmed_open_states) AND everconfirmed = 0",
|
||||
'bug_check_status_everconfirmed_error_text2', 'repair_everconfirmed');
|
||||
|
||||
Status('bug_check_votes_everconfirmed');
|
||||
|
||||
BugCheck("bugs INNER JOIN products ON bugs.product_id = products.id " .
|
||||
"WHERE everconfirmed = 0 AND votestoconfirm > 0
|
||||
AND votestoconfirm <= votes",
|
||||
'bug_check_votes_everconfirmed_error_text');
|
||||
|
||||
###########################################################################
|
||||
# Control Values
|
||||
###########################################################################
|
||||
|
||||
@@ -54,9 +54,8 @@ table#flags {
|
||||
height: 1em;
|
||||
}
|
||||
|
||||
#duplicate_settings, #votes_container {
|
||||
#duplicate_settings {
|
||||
white-space: nowrap;
|
||||
|
||||
}
|
||||
|
||||
#bz_big_form_parts td {
|
||||
|
||||
@@ -35,9 +35,6 @@
|
||||
|
||||
[% PROCESS global/variables.none.tmpl %]
|
||||
|
||||
[% useqacontact = Param('useqacontact') %]
|
||||
[% usevotes = Param('usevotes') %]
|
||||
|
||||
<p>
|
||||
If you don't like getting a notification for "trivial"
|
||||
changes to [% terms.bugs %], you can use the settings below to
|
||||
@@ -150,21 +147,28 @@ document.write('<input type="button" value="Disable All Mail" onclick="SetCheckb
|
||||
[% relationships = [
|
||||
{ id = constants.REL_ASSIGNEE,
|
||||
description = "Assignee" },
|
||||
{ id = constants.REL_QA,
|
||||
description = "QA Contact" },
|
||||
{ id = constants.REL_REPORTER,
|
||||
description = "Reporter" },
|
||||
{ id = constants.REL_CC,
|
||||
description = "CCed" },
|
||||
{ id = constants.REL_VOTER,
|
||||
description = "Voter" },
|
||||
] %]
|
||||
|
||||
[% IF Param('useqacontact') %]
|
||||
[% relationships.push({ id = constants.REL_QA,
|
||||
description = "QA Contact" }) %]
|
||||
[% END %]
|
||||
|
||||
|
||||
[%# This is up here so that the "relationships" hook can modify it. %]
|
||||
[% no_added_removed = [constants.REL_REPORTER] %]
|
||||
|
||||
[% Hook.process('relationships') %]
|
||||
|
||||
[% num_columns = relationships.size %]
|
||||
|
||||
<table class="bz_emailprefs" border="1">
|
||||
<tr>
|
||||
<td colspan="[% (useqacontact AND usevotes) ? '5' :
|
||||
((useqacontact OR usevotes) ? '4' : '3') %]"
|
||||
align="center" width="50%">
|
||||
<td colspan="[% num_columns FILTER html %]" align="center" width="50%">
|
||||
<b>When my relationship to this [% terms.bug %] is:</b>
|
||||
</td>
|
||||
<td rowspan="2" width="40%">
|
||||
@@ -174,8 +178,6 @@ document.write('<input type="button" value="Disable All Mail" onclick="SetCheckb
|
||||
|
||||
<tr>
|
||||
[% FOREACH relationship = relationships %]
|
||||
[% NEXT IF (relationship.id == constants.REL_QA AND NOT useqacontact) OR
|
||||
(relationship.id == constants.REL_VOTER AND NOT usevotes) %]
|
||||
<th align="center" width="9%">
|
||||
[% relationship.description FILTER html %]
|
||||
</th>
|
||||
@@ -186,16 +188,14 @@ document.write('<input type="button" value="Disable All Mail" onclick="SetCheckb
|
||||
[% count = loop.count() %]
|
||||
<tr class="bz_row_[% count % 2 == 1 ? "odd" : "even" %]">
|
||||
[% FOREACH relationship = relationships %]
|
||||
[% NEXT IF (relationship.id == constants.REL_QA AND NOT useqacontact) OR
|
||||
(relationship.id == constants.REL_VOTER AND NOT usevotes) %]
|
||||
<td align="center">
|
||||
<input type="checkbox"
|
||||
name="email-[% relationship.id %]-[% event.id %]"
|
||||
value="1"
|
||||
[%# The combinations don't always make sense; disable a couple %]
|
||||
[% IF event.id == constants.EVT_ADDED_REMOVED AND
|
||||
(relationship.id == constants.REL_REPORTER OR
|
||||
relationship.id == constants.REL_VOTER) %]
|
||||
no_added_removed.contains(relationship.id)
|
||||
%]
|
||||
disabled
|
||||
[% ELSIF mail.${relationship.id}.${event.id} %]
|
||||
checked
|
||||
@@ -209,8 +209,7 @@ document.write('<input type="button" value="Disable All Mail" onclick="SetCheckb
|
||||
[% END %]
|
||||
|
||||
<tr>
|
||||
<td colspan="[% (useqacontact AND usevotes) ? '5' :
|
||||
((useqacontact OR usevotes) ? '4' : '3') %]"
|
||||
<td colspan="[% num_columns FILTER html %]"
|
||||
align="center" width="50%">
|
||||
|
||||
</td>
|
||||
@@ -223,8 +222,6 @@ document.write('<input type="button" value="Disable All Mail" onclick="SetCheckb
|
||||
[% count = loop.count() %]
|
||||
<tr class="bz_row_[% count % 2 == 1 ? "odd" : "even" %]">
|
||||
[% FOREACH relationship = relationships %]
|
||||
[% NEXT IF (relationship.id == constants.REL_QA AND NOT useqacontact) OR
|
||||
(relationship.id == constants.REL_VOTER AND NOT usevotes) %]
|
||||
<td align="center">
|
||||
<input type="checkbox"
|
||||
name="neg-email-[% relationship.id %]-[% event.id %]"
|
||||
@@ -243,23 +240,17 @@ document.write('<input type="button" value="Disable All Mail" onclick="SetCheckb
|
||||
[%# Add hidden form fields for fields not used %]
|
||||
[% FOREACH event = events %]
|
||||
[% FOREACH relationship = relationships %]
|
||||
[% IF (relationship.id == constants.REL_QA AND NOT useqacontact) OR
|
||||
(relationship.id == constants.REL_VOTER AND NOT usevotes) %]
|
||||
<input type="hidden"
|
||||
name="email-[% relationship.id %]-[% event.id %]"
|
||||
value="[% mail.${relationship.id}.${event.id} ? "1" : "0" %]">
|
||||
[% END %]
|
||||
<input type="hidden"
|
||||
name="email-[% relationship.id %]-[% event.id %]"
|
||||
value="[% mail.${relationship.id}.${event.id} ? "1" : "0" %]">
|
||||
[% END %]
|
||||
[% END %]
|
||||
|
||||
[% FOREACH event = neg_events %]
|
||||
[% FOREACH relationship = relationships %]
|
||||
[% IF (relationship.id == constants.REL_QA AND NOT useqacontact) OR
|
||||
(relationship.id == constants.REL_VOTER AND NOT usevotes) %]
|
||||
<input type="hidden"
|
||||
name="neg-email-[% relationship.id %]-[% event.id %]"
|
||||
value="[% mail.${relationship.id}.${event.id} ? "0" : "1" %]">
|
||||
[% END %]
|
||||
<input type="hidden"
|
||||
name="neg-email-[% relationship.id %]-[% event.id %]"
|
||||
value="[% mail.${relationship.id}.${event.id} ? "0" : "1" %]">
|
||||
[% END %]
|
||||
[% END %]
|
||||
|
||||
|
||||
@@ -34,11 +34,6 @@
|
||||
|
||||
usestatuswhiteboard => "Do you wish to use the Status Whiteboard field?",
|
||||
|
||||
usevotes => "Do you wish to allow users to vote for ${terms.bugs}? Note that in order " _
|
||||
"for this to be effective, you will have to change the maximum " _
|
||||
"votes allowed in a product to be non-zero in " _
|
||||
"<a href=\"editproducts.cgi\">the product edit page</a>.",
|
||||
|
||||
usebugaliases => "Do you wish to use $terms.bug aliases, which allow you to assign " _
|
||||
"$terms.bugs an easy-to-remember name by which you can refer to them?",
|
||||
|
||||
|
||||
@@ -29,9 +29,6 @@
|
||||
%]
|
||||
|
||||
[% DEFAULT
|
||||
product.votesperuser = "0",
|
||||
product.maxvotesperbug = "10000",
|
||||
product.votes_to_confirm = "0",
|
||||
product.is_active = 1,
|
||||
version = "unspecified",
|
||||
product.defaultmilestone = constants.DEFAULT_MILESTONE
|
||||
|
||||
@@ -76,37 +76,8 @@
|
||||
in this product:</label>
|
||||
</th>
|
||||
<td><input type="checkbox" id="allows_unconfirmed" name="allows_unconfirmed"
|
||||
[% ' checked="checked"' IF product.allows_unconfirmed %]
|
||||
[% IF Param('usevotes') %]
|
||||
onchange="bz_toggleClass('votes_to_confirm_container',
|
||||
'bz_default_hidden')"
|
||||
[% END %]>
|
||||
[% IF Param('usevotes') %]
|
||||
<span id="votes_to_confirm_container"
|
||||
[% ' class="bz_default_hidden"' IF !product.allows_unconfirmed %]>
|
||||
...and automatically confirm [% terms.bugs %] if they get
|
||||
<input size="3" maxlength="5" name="votestoconfirm" id="votestoconfirm"
|
||||
value="[% product.votes_to_confirm FILTER html %]">
|
||||
votes. (Setting this to 0 disables auto-confirming [% terms.bugs %]
|
||||
by vote.)
|
||||
</span>
|
||||
[% END %]
|
||||
[% ' checked="checked"' IF product.allows_unconfirmed %]>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
[% IF Param('usevotes') %]
|
||||
<tr>
|
||||
<th align="right">Maximum votes per person:</th>
|
||||
<td><input size="5" maxlength="5" name="votesperuser" id="votesperuser"
|
||||
value="[% product.votesperuser FILTER html %]">
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th align="right">
|
||||
Maximum votes a person can put on a single [% terms.bug %]:
|
||||
</th>
|
||||
<td><input size="5" maxlength="5" name="maxvotesperbug" id="maxvotesperbug"
|
||||
value="[% product.maxvotesperbug FILTER html %]">
|
||||
</td>
|
||||
</tr>
|
||||
[% END %]
|
||||
[% Hook.process('rows') %]
|
||||
|
||||
@@ -64,22 +64,7 @@
|
||||
heading => "Open For New $terms.Bugs"
|
||||
yesno_field => 1
|
||||
},
|
||||
{
|
||||
name => "votesperuser"
|
||||
heading => "Votes Per User"
|
||||
align => 'right'
|
||||
},
|
||||
{
|
||||
name => "maxvotesperbug"
|
||||
heading => "Maximum Votes Per $terms.Bug"
|
||||
align => 'right'
|
||||
},
|
||||
{
|
||||
name => "votestoconfirm"
|
||||
heading => "Votes To Confirm"
|
||||
align => 'right'
|
||||
} ]
|
||||
%]
|
||||
] %]
|
||||
|
||||
[% IF showbugcounts %]
|
||||
|
||||
|
||||
@@ -75,33 +75,6 @@
|
||||
'[% product.default_milestone FILTER html %]'.
|
||||
</p>
|
||||
[% END %]
|
||||
|
||||
[% IF changes.votesperuser.defined %]
|
||||
<p>
|
||||
Updated votes per user from
|
||||
[%+ changes.votesperuser.0 FILTER html %] to
|
||||
[%+ product.votes_per_user FILTER html %].
|
||||
</p>
|
||||
[% checkvotes = 1 %]
|
||||
[% END %]
|
||||
|
||||
[% IF changes.maxvotesperbug.defined %]
|
||||
<p>
|
||||
Updated maximum votes per [% terms.bug %] from
|
||||
[%+ changes.maxvotesperbug.0 FILTER html %] to
|
||||
[%+ product.max_votes_per_bug FILTER html %].
|
||||
</p>
|
||||
[% checkvotes = 1 %]
|
||||
[% END %]
|
||||
|
||||
[% IF changes.votestoconfirm.defined %]
|
||||
<p>
|
||||
Updated number of votes needed to confirm a [% terms.bug %] from
|
||||
[%+ changes.votestoconfirm.0 FILTER html %] to
|
||||
[%+ product.votes_to_confirm FILTER html %].
|
||||
</p>
|
||||
[% checkvotes = 1 %]
|
||||
[% END %]
|
||||
|
||||
[% IF changes.allows_unconfirmed.defined %]
|
||||
<p>
|
||||
@@ -121,65 +94,12 @@
|
||||
</p>
|
||||
[% END %]
|
||||
|
||||
[% Hook.process('changes') %]
|
||||
|
||||
[% IF !changes.keys.size %]
|
||||
<p>Nothing changed for product '[% product.name FILTER html %]'.</p>
|
||||
[% 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 %]
|
||||
<hr>
|
||||
|
||||
<p>Checking existing votes in this product for anybody who now
|
||||
has too many votes for [% terms.abug %]...<br>
|
||||
[% IF changes.too_many_votes.size %]
|
||||
[% FOREACH detail = changes.too_many_votes %]
|
||||
→removed votes for [% terms.bug %] <a href="show_bug.cgi?id=
|
||||
[%- detail.id FILTER url_quote %]">
|
||||
[%- detail.id FILTER html %]</a> from [% detail.name FILTER html %]<br>
|
||||
[% END %]
|
||||
[% ELSE %]
|
||||
→there were none.
|
||||
[% END %]
|
||||
</p>
|
||||
|
||||
<p>Checking existing votes in this product for anybody
|
||||
who now has too many total votes...<br>
|
||||
[% IF changes.too_many_total_votes.size %]
|
||||
[% FOREACH detail = changes.too_many_total_votes %]
|
||||
→removed votes for [% terms.bug %] <a href="show_bug.cgi?id=
|
||||
[%- detail.id FILTER url_quote %]">
|
||||
[%- detail.id FILTER html %]</a> from [% detail.name FILTER html %]<br>
|
||||
[% END %]
|
||||
[% ELSE %]
|
||||
→there were none.
|
||||
[% END %]
|
||||
</p>
|
||||
|
||||
<p>Checking unconfirmed [% terms.bugs %] in this product for any which now have
|
||||
sufficient votes...<br>
|
||||
[% 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 %]
|
||||
</p>
|
||||
|
||||
[% END %]
|
||||
|
||||
[% PROCESS admin/products/footer.html.tmpl %]
|
||||
|
||||
[% PROCESS global/footer.html.tmpl %]
|
||||
|
||||
@@ -81,12 +81,6 @@
|
||||
[% ELSIF san_tag == "bug_check_status_everconfirmed_error_text2" %]
|
||||
[% terms.Bugs %] with confirmed status but don't have everconfirmed set
|
||||
|
||||
[% ELSIF san_tag == "bug_check_votes_everconfirmed" %]
|
||||
Checking votes/everconfirmed
|
||||
|
||||
[% ELSIF san_tag == "bug_check_votes_everconfirmed_error_text" %]
|
||||
[% terms.Bugs %] that have enough votes to be confirmed but haven't been
|
||||
|
||||
[% ELSIF san_tag == "bug_check_control_values" %]
|
||||
Checking for bad values in group_control_map
|
||||
|
||||
@@ -275,25 +269,6 @@
|
||||
[% ELSIF san_tag == "unsent_bugmail_fix" %]
|
||||
<a href="sanitycheck.cgi?rescanallBugMail=1">Send these mails</a>.
|
||||
|
||||
[% ELSIF san_tag == "vote_cache_rebuild_start" %]
|
||||
OK, now rebuilding vote cache.
|
||||
|
||||
[% ELSIF san_tag == "vote_cache_rebuild_end" %]
|
||||
Vote cache has been rebuilt.
|
||||
|
||||
[% ELSIF san_tag == "vote_cache_rebuild_fix" %]
|
||||
<a href="sanitycheck.cgi?rebuildvotecache=1">Click here to
|
||||
rebuild the vote cache</a>
|
||||
|
||||
[% ELSIF san_tag == "vote_cache_alert" %]
|
||||
Bad vote cache for [% PROCESS bug_link bug_id = id %]
|
||||
|
||||
[% ELSIF san_tag == "vote_count_start" %]
|
||||
Checking cached vote counts.
|
||||
|
||||
[% ELSIF san_tag == "vote_count_alert" %]
|
||||
Bad vote sum for [% terms.bug %] [%+ id FILTER html %].
|
||||
|
||||
[% ELSIF san_tag == "whines_obsolete_target_deletion_start" %]
|
||||
OK, now removing non-existent users/groups from whines.
|
||||
|
||||
|
||||
@@ -33,7 +33,6 @@
|
||||
# namedquery_group_map: number of named queries the user has shared
|
||||
# profiles_activity: number of changes made to other users' profiles
|
||||
# series: number of series the viewed user has created
|
||||
# votes: number of bugs the viewed user has voted on
|
||||
# watch.watched: number of users the viewed user is being watched
|
||||
# by
|
||||
# watch.watcher: number of users the viewed user is watching
|
||||
@@ -226,8 +225,8 @@
|
||||
[% END %]
|
||||
|
||||
[% IF assignee_or_qa || cc || component_cc || email_setting || flags.requestee ||
|
||||
namedqueries || profile_setting || quips || series || votes || watch.watched ||
|
||||
watch.watcher || whine_events || whine_schedules %]
|
||||
namedqueries || profile_setting || quips || series || watch.watched ||
|
||||
watch.watcher || whine_events || whine_schedules || other_safe %]
|
||||
<div class="warningmessages">
|
||||
<p>The following deletions are <b>safe</b> and will not generate
|
||||
referential integrity inconsistencies.</p>
|
||||
@@ -372,23 +371,6 @@
|
||||
will have no author anymore, but will remain available.
|
||||
</li>
|
||||
[% END %]
|
||||
[% IF votes %]
|
||||
<li>
|
||||
[% 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.
|
||||
</li>
|
||||
[% END %]
|
||||
[% IF watch.watched || watch.watcher %]
|
||||
<li>
|
||||
[% otheruser.login FILTER html %]
|
||||
@@ -445,6 +427,7 @@
|
||||
but the whines themselves will be left unaltered.
|
||||
</li>
|
||||
[% END %]
|
||||
[% Hook.process('warn_safe') %]
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -398,7 +398,7 @@
|
||||
[% BLOCK section_details2 %]
|
||||
|
||||
[%###############################################################%]
|
||||
[%# Importance (priority, severity and votes) #%]
|
||||
[%# Importance (priority and severity) #%]
|
||||
[%###############################################################%]
|
||||
<tr>
|
||||
<td class="field_label">
|
||||
@@ -414,22 +414,7 @@
|
||||
bug = bug, field = bug_fields.bug_severity,
|
||||
no_tds = 1, value = bug.bug_severity
|
||||
editable = bug.check_can_change_field('bug_severity', 0, 1) %]
|
||||
[% IF bug.use_votes %]
|
||||
<span id="votes_container">
|
||||
[% IF bug.votes %]
|
||||
with
|
||||
<a href="votes.cgi?action=show_bug&bug_id=[% bug.bug_id %]">
|
||||
[% bug.votes %]
|
||||
[% IF bug.votes == 1 %]
|
||||
vote
|
||||
[% ELSE %]
|
||||
votes
|
||||
[% END %]</a>
|
||||
[% END %]
|
||||
(<a href="votes.cgi?action=show_user&bug_id=
|
||||
[% bug.bug_id %]#vote_[% bug.bug_id %]">vote</a>)
|
||||
</span>
|
||||
[% END %]
|
||||
[% Hook.process('after_importance', 'bug/edit.html.tmpl') %]
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
|
||||
@@ -39,8 +39,6 @@ X[% comment_body %]
|
||||
*** This [% terms.bug %] has been marked as a duplicate of [% terms.bug %] [%+ comment.extra_data %] ***
|
||||
[% ELSIF comment.type == constants.CMT_HAS_DUPE %]
|
||||
*** [% terms.Bug %] [%+ comment.extra_data %] has been marked as a duplicate of this [% terms.bug %]. ***
|
||||
[% ELSIF comment.type == constants.CMT_POPULAR_VOTES %]
|
||||
*** This [% terms.bug %] has been confirmed by popular vote. ***
|
||||
[% ELSIF comment.type == constants.CMT_MOVED_TO %]
|
||||
X[% comment_body %]
|
||||
|
||||
@@ -65,6 +63,8 @@ Comment on attachment [% comment.extra_data %]
|
||||
[%+ comment.attachment.description %]
|
||||
|
||||
[%+ comment.body %]
|
||||
[% ELSIF comment.type %]
|
||||
[% Hook.process('type') %]
|
||||
[% ELSE %]
|
||||
X[% comment_body %]
|
||||
[% END %]
|
||||
|
||||
@@ -39,8 +39,8 @@
|
||||
[% END %]
|
||||
[% ELSIF title_tag == "mid_air" %]
|
||||
[% title = "Mid-air collision!" %]
|
||||
[% ELSIF title_tag == "change_votes" %]
|
||||
[% title = "Change Votes" %]
|
||||
[% END %]
|
||||
|
||||
[% Hook.process('title') %]
|
||||
|
||||
[% PROCESS global/header.html.tmpl %]
|
||||
|
||||
@@ -44,12 +44,13 @@
|
||||
'bug' => "Changes submitted for $link" ,
|
||||
'dupe' => "Duplicate notation added to $link" ,
|
||||
'dep' => "Checking for dependency changes on $link" ,
|
||||
'votes' => "$Link confirmed by number of votes" ,
|
||||
'created' => "$Link has been added to the database" ,
|
||||
'move' => "$Link has been moved to another database" ,
|
||||
}
|
||||
%]
|
||||
|
||||
[% Hook.process('title') %]
|
||||
|
||||
<dl>
|
||||
<dt>[% title.$type %]</dt>
|
||||
<dd>
|
||||
|
||||
@@ -19,6 +19,8 @@
|
||||
#%]
|
||||
|
||||
[% PROCESS "global/variables.none.tmpl" %]
|
||||
[% PROCESS "global/reason-descs.none.tmpl" %]
|
||||
|
||||
From: [% Param('mailfrom') %]
|
||||
To: [% to_user.email %]
|
||||
Subject: [[% terms.Bug %] [%+ bugid %]] [% 'New: ' IF isnew %][%+ summary %]
|
||||
@@ -56,33 +58,12 @@ X-Bugzilla-Changed-Fields: [% changedfields %]
|
||||
-- [%# Protect the trailing space of the signature marker %]
|
||||
Configure [% terms.bug %]mail: [% urlbase %]userprefs.cgi?tab=email
|
||||
------- You are receiving this mail because: -------
|
||||
[% FOREACH relationship = reasons %]
|
||||
[% SWITCH relationship %]
|
||||
[% CASE constants.REL_ASSIGNEE %]
|
||||
You are the assignee for the [% terms.bug %].
|
||||
[% CASE constants.REL_REPORTER %]
|
||||
You reported the [% terms.bug %].
|
||||
[% CASE constants.REL_QA %]
|
||||
You are the QA contact for the [% terms.bug %].
|
||||
[% CASE constants.REL_CC %]
|
||||
You are on the CC list for the [% terms.bug %].
|
||||
[% CASE constants.REL_VOTER %]
|
||||
You are a voter for the [% terms.bug %].
|
||||
[% CASE constants.REL_GLOBAL_WATCHER %]
|
||||
You are watching all [% terms.bug %] changes.
|
||||
[% END %]
|
||||
[% SET reason_lines = [] %]
|
||||
[% FOREACH reason = reasons %]
|
||||
[% reason_lines.push(reason_descs.$reason) IF reason_descs.$reason %]
|
||||
[% END %]
|
||||
[% FOREACH relationship = reasons_watch %]
|
||||
[% SWITCH relationship %]
|
||||
[% CASE constants.REL_ASSIGNEE %]
|
||||
You are watching the assignee of the [% terms.bug %].
|
||||
[% CASE constants.REL_REPORTER %]
|
||||
You are watching the reporter.
|
||||
[% CASE constants.REL_QA %]
|
||||
You are watching the QA contact of the [% terms.bug %].
|
||||
[% CASE constants.REL_CC %]
|
||||
You are watching someone on the CC list of the [% terms.bug %].
|
||||
[% CASE constants.REL_VOTER %]
|
||||
You are watching a voter for the [% terms.bug %].
|
||||
[% END %]
|
||||
[% FOREACH reason = reasons_watch %]
|
||||
[% reason_lines.push(watch_reason_descs.$reason)
|
||||
IF watch_reason_descs.$reason %]
|
||||
[% END %]
|
||||
[%+ reason_lines.join("\n") %]
|
||||
|
||||
@@ -233,7 +233,6 @@
|
||||
|
||||
'global/site-navigation.html.tmpl' => [
|
||||
'bug.bug_id',
|
||||
'bug.votes',
|
||||
],
|
||||
|
||||
'bug/comments.html.tmpl' => [
|
||||
@@ -264,7 +263,6 @@
|
||||
'bug.remaining_time',
|
||||
'bug.delta_ts',
|
||||
'bug.bug_id',
|
||||
'bug.votes',
|
||||
'group.bit',
|
||||
'dep.title',
|
||||
'dep.fieldname',
|
||||
@@ -312,19 +310,6 @@
|
||||
FILTER format("%d")',
|
||||
],
|
||||
|
||||
'bug/votes/list-for-bug.html.tmpl' => [
|
||||
'voter.vote_count',
|
||||
'total',
|
||||
],
|
||||
|
||||
'bug/votes/list-for-user.html.tmpl' => [
|
||||
'product.maxperbug',
|
||||
'bug.id',
|
||||
'bug.count',
|
||||
'product.total',
|
||||
'product.maxvotes',
|
||||
],
|
||||
|
||||
'bug/process/results.html.tmpl' => [
|
||||
'title.$type',
|
||||
'"$terms.Bug $id" FILTER bug_link(id)',
|
||||
@@ -482,7 +467,6 @@
|
||||
'flags.setter',
|
||||
'longdescs',
|
||||
'quips',
|
||||
'votes',
|
||||
'series',
|
||||
'watch.watched',
|
||||
'watch.watcher',
|
||||
|
||||
@@ -84,7 +84,6 @@
|
||||
"status_whiteboard" => "Whiteboard",
|
||||
"target_milestone" => "Target Milestone",
|
||||
"version" => "Version",
|
||||
"votes" => "Votes",
|
||||
"work_time" => "Hours Worked"} %]
|
||||
|
||||
[%# Also include any custom fields or fields which don't have a
|
||||
|
||||
@@ -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 <mkanat@bugzilla.org>
|
||||
#%]
|
||||
|
||||
[% SET reason_descs = {
|
||||
${constants.REL_ASSIGNEE} => "You are the assignee for the ${terms.bug}.",
|
||||
${constants.REL_REPORTER} => "You reported the ${terms.bug}.",
|
||||
${constants.REL_QA} => "You are the QA Contact for the ${terms.bug}.",
|
||||
${constants.REL_CC} => "You are on the CC list for the ${terms.bug}.",
|
||||
${constants.REL_GLOBAL_WATCHER} => "You are watching all $terms.bug changes.",
|
||||
} %]
|
||||
|
||||
[% SET watch_reason_descs => {
|
||||
${constants.REL_ASSIGNEE} =>
|
||||
"You are the watching assignee of the ${terms.bug}.",
|
||||
${constants.REL_REPORTER} =>
|
||||
"You watching the reporter of the ${terms.bug}.",
|
||||
${constants.REL_QA} =>
|
||||
"You are watching the QA Contact of the ${terms.bug}.",
|
||||
${constants.REL_CC} =>
|
||||
"You are watching someone on the CC list of the ${terms.bug}.",
|
||||
} %]
|
||||
|
||||
[% Hook.process('end') %]
|
||||
@@ -37,7 +37,7 @@
|
||||
[% END %]
|
||||
|
||||
|
||||
[%# *** Dependencies, Votes, Activity, Print-version *** %]
|
||||
[%# *** Dependencies, Activity, Print-version *** %]
|
||||
[% IF bug %]
|
||||
<link rel="Show" title="Dependency Tree"
|
||||
href="showdependencytree.cgi?id=[% bug.bug_id %]&hide_resolved=1">
|
||||
@@ -46,11 +46,6 @@
|
||||
href="showdependencygraph.cgi?id=[% bug.bug_id %]">
|
||||
[% END %]
|
||||
|
||||
[% IF bug.use_votes %]
|
||||
<link rel="Show" title="Votes ([% bug.votes %])"
|
||||
href="votes.cgi?action=show_bug&bug_id=[% bug.bug_id %]">
|
||||
[% END %]
|
||||
|
||||
<link rel="Show" title="[% terms.Bug %] Activity"
|
||||
href="show_activity.cgi?id=[% bug.bug_id %]">
|
||||
<link rel="Show" title="Printer-Friendly Version"
|
||||
|
||||
@@ -740,11 +740,6 @@
|
||||
The group [% name FILTER html %] does not exist. Please specify
|
||||
a valid group name. Create it first if necessary!
|
||||
|
||||
[% ELSIF error == "illegal_at_least_x_votes" %]
|
||||
[% title = "Your Search Makes No Sense" %]
|
||||
The <em>At least ___ votes</em> field must be a simple number.
|
||||
You entered <tt>[% value FILTER html %]</tt>, which isn't.
|
||||
|
||||
[% ELSIF error == "illegal_attachment_edit" %]
|
||||
[% title = "Unauthorized Action" %]
|
||||
You are not authorized to edit attachment [% attach_id FILTER html %].
|
||||
@@ -1318,20 +1313,6 @@
|
||||
[% group.name FILTER html %] is not an active [% terms.bug %] group
|
||||
and so you cannot edit group controls for it.
|
||||
|
||||
[% ELSIF error == "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
|
||||
<em>
|
||||
[% IF field == "votesperuser" %]
|
||||
Votes Per User
|
||||
[% ELSIF field == "maxvotesperbug" %]
|
||||
Maximum Votes Per [% terms.Bug %]
|
||||
[% ELSIF field == "votestoconfirm" %]
|
||||
Votes To Confirm
|
||||
[% END %]
|
||||
</em> field, which should contain a non-negative number.
|
||||
|
||||
[% ELSIF error == "product_name_already_in_use" %]
|
||||
[% title = "Product name already exists" %]
|
||||
[% admindocslinks = {'products.html' => 'Administering products'} %]
|
||||
@@ -1548,21 +1529,6 @@
|
||||
[% title = "User Protected" %]
|
||||
The user [% login FILTER html %] may not be impersonated by sudoers.
|
||||
|
||||
[% ELSIF error == "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
|
||||
<tt>[% product FILTER html %]</tt> product, but you are trying to
|
||||
use [% votes FILTER html %].
|
||||
|
||||
[% ELSIF error == "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
|
||||
<tt>[% product FILTER html %]</tt> product, which exceeds the maximum of
|
||||
[%+ max FILTER html %] votes for this product.
|
||||
|
||||
[% ELSIF error == "token_does_not_exist" %]
|
||||
[% title = "Token Does Not Exist" %]
|
||||
The token you submitted does not exist, has expired, or has
|
||||
@@ -1650,11 +1616,6 @@
|
||||
Sorry, but you are not allowed to (un)mark comments or attachments
|
||||
as private.
|
||||
|
||||
[% ELSIF error == "votes_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 == "wrong_token_for_cancelling_email_change" %]
|
||||
[% title = "Wrong Token" %]
|
||||
That token cannot be used to cancel an email address change.
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
<bz:id nc:parseType="Integer">[% bug.bug_id %]</bz:id>
|
||||
|
||||
[% FOREACH column = displaycolumns %]
|
||||
<bz:[% column %][% ' nc:parseType="Integer"' IF column == "votes" %]>[% bug.$column FILTER html %]</bz:[% column %]>
|
||||
<bz:[% column %]>[% bug.$column FILTER html %]</bz:[% column %]>
|
||||
[% END %]
|
||||
|
||||
</bz:bug>
|
||||
|
||||
@@ -422,20 +422,14 @@ function doOnSelectProduct(selectmode) {
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
[%# *** Email Numbering Votes *** %]
|
||||
[%# *** Email Numbering *** %]
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<td>
|
||||
<fieldset>
|
||||
<legend>
|
||||
<strong>
|
||||
[% IF Param('usevotes') %]
|
||||
Email Addresses, [% terms.Bug %] Numbers, and Votes
|
||||
[% ELSE %]
|
||||
Email Addresses and [% terms.Bug %] Numbers
|
||||
[% END %]
|
||||
</strong>
|
||||
<strong>Email Addresses and [% terms.Bug %] Numbers</strong>
|
||||
</legend>
|
||||
|
||||
|
||||
@@ -550,18 +544,7 @@ function doOnSelectProduct(selectmode) {
|
||||
<td></td>
|
||||
<td>(comma-separated list)</td>
|
||||
</tr>
|
||||
[% IF Param('usevotes') %]
|
||||
<tr>
|
||||
<td align="right">
|
||||
<label for="votes">Only [% terms.bugs %] with at least</label>:
|
||||
</td>
|
||||
<td>
|
||||
<input name="votes" id="votes" size="3"
|
||||
value="[% default.votes.0 FILTER html %]">
|
||||
votes
|
||||
</td>
|
||||
</tr>
|
||||
[% END %]
|
||||
[% Hook.process('email_numbering_end') %]
|
||||
</table>
|
||||
|
||||
|
||||
|
||||
@@ -82,9 +82,6 @@
|
||||
roles.<br>Here, you can search on what people are in what role." },
|
||||
{ id => "bug_id",
|
||||
html => "You can limit your search to a specific set of $terms.bugs ." },
|
||||
{ id => "votes",
|
||||
html => "Some $terms.bugs can be voted for, and you can limit your search to
|
||||
$terms.bugs<br>with more than a certain number of votes." },
|
||||
{ id => "chfield",
|
||||
html => "You can search for specific types of change - this field define <br>
|
||||
which field you are interested in changes for." },
|
||||
|
||||
@@ -29,7 +29,8 @@
|
||||
[% rep_fields = ["classification", "product", "component", "version", "rep_platform",
|
||||
"op_sys", "bug_status", "resolution", "bug_severity",
|
||||
"priority", "target_milestone", "assigned_to",
|
||||
"reporter", "qa_contact", "votes" ] %]
|
||||
"reporter", "qa_contact" ] %]
|
||||
[% Hook.process('rep_fields', 'search/search-report-select.html.tmpl') %]
|
||||
|
||||
<select name="[% name FILTER html %]">
|
||||
<option value=""><none></option>
|
||||
@@ -38,7 +39,6 @@
|
||||
[% NEXT IF field == "classification" AND !Param('useclassification') %]
|
||||
[% NEXT IF field == "target_milestone" AND !Param('usetargetmilestone') %]
|
||||
[% NEXT IF field == "qa_contact" AND !Param('useqacontact') %]
|
||||
[% NEXT IF field == "votes" AND !Param('usevotes') %]
|
||||
<option value="[% field FILTER html %]"
|
||||
[% " selected" IF default.$name.0 == field %]>
|
||||
[% field_descs.$field || field FILTER html %]</option>
|
||||
|
||||
@@ -105,9 +105,6 @@ function normal_keypress_handler( aEvent ) {
|
||||
[% filtered_username = user.login FILTER url_quote %]
|
||||
<text class="text-link" onclick="load_relative_url('[% Param('mybugstemplate').replace('%userid%', filtered_username) FILTER js FILTER html %]')" value="my [% terms.bugs %]"/>
|
||||
[%- END %]
|
||||
[%- IF Param('usevotes') %]
|
||||
<text class="text-link" onclick="load_relative_url('votes.cgi?action=show_user')" value="my votes"/>
|
||||
[%- END %]
|
||||
|
||||
[%- FOREACH q = user.queries %]
|
||||
<text class="text-link" onclick="load_relative_url('buglist.cgi?cmdtype=runnamed&namedcmd=[% q.name FILTER url_quote %]')" value="[% q.name FILTER html %]"/>
|
||||
|
||||
@@ -13,350 +13,36 @@
|
||||
#
|
||||
# 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.
|
||||
# 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): Terry Weissman <terry@mozilla.org>
|
||||
# Stephan Niemz <st.n@gmx.net>
|
||||
# Christopher Aillon <christopher@aillon.com>
|
||||
# Gervase Markham <gerv@gerv.net>
|
||||
# Frédéric Buclin <LpSolit@gmail.com>
|
||||
# Contributor(s):
|
||||
# Max Kanat-Alexander <mkanat@bugzilla.org>
|
||||
|
||||
# This script remains as a backwards-compatibility URL for before
|
||||
# the time that Voting was an extension.
|
||||
|
||||
use strict;
|
||||
use lib qw(. lib);
|
||||
|
||||
use Bugzilla;
|
||||
use Bugzilla::Constants;
|
||||
use Bugzilla::Util;
|
||||
use Bugzilla::Error;
|
||||
use Bugzilla::Bug;
|
||||
use Bugzilla::User;
|
||||
use Bugzilla::Product;
|
||||
|
||||
use List::Util qw(min);
|
||||
|
||||
my $cgi = Bugzilla->cgi;
|
||||
local our $vars = {};
|
||||
|
||||
# If the action is show_bug, you need a bug_id.
|
||||
# If the action is show_user, you can supply a userid to show the votes for
|
||||
# another user, otherwise you see your own.
|
||||
# If the action is vote, your votes are set to those encoded in the URL as
|
||||
# <bug_id>=<votes>.
|
||||
#
|
||||
# If no action is defined, we default to show_bug if a bug_id is given,
|
||||
# otherwise to show_user.
|
||||
my $bug_id = $cgi->param('bug_id');
|
||||
my $action = $cgi->param('action') || ($bug_id ? "show_bug" : "show_user");
|
||||
|
||||
if ($action eq "show_bug" ||
|
||||
($action eq "show_user" && defined $cgi->param('user_id')))
|
||||
{
|
||||
Bugzilla->login();
|
||||
}
|
||||
else {
|
||||
Bugzilla->login(LOGIN_REQUIRED);
|
||||
}
|
||||
|
||||
################################################################################
|
||||
# Begin Data/Security Validation
|
||||
################################################################################
|
||||
|
||||
# Make sure the bug ID is a positive integer representing an existing
|
||||
# bug that the user is authorized to access.
|
||||
|
||||
if (defined $bug_id) {
|
||||
my $bug = Bugzilla::Bug->check($bug_id);
|
||||
$bug_id = $bug->id;
|
||||
}
|
||||
|
||||
################################################################################
|
||||
# End Data/Security Validation
|
||||
################################################################################
|
||||
my $to_url;
|
||||
my $action = $cgi->param('action');
|
||||
|
||||
if ($action eq "show_bug") {
|
||||
show_bug($bug_id);
|
||||
$cgi->delete('action');
|
||||
$cgi->param('id', 'voting/bug.html');
|
||||
}
|
||||
elsif ($action eq "show_user") {
|
||||
show_user($bug_id);
|
||||
}
|
||||
elsif ($action eq "vote") {
|
||||
record_votes() if Bugzilla->params->{'usevotes'};
|
||||
show_user($bug_id);
|
||||
elsif ($action eq "show_user" or $action eq 'vote') {
|
||||
$cgi->delete('action') unless $action eq 'vote';
|
||||
$cgi->param('id', 'voting/user.html');
|
||||
}
|
||||
else {
|
||||
ThrowCodeError("unknown_action", {action => $action});
|
||||
}
|
||||
|
||||
print $cgi->redirect('page.cgi?' . $cgi->query_string);
|
||||
exit;
|
||||
|
||||
# Display the names of all the people voting for this one bug.
|
||||
sub show_bug {
|
||||
my ($bug_id) = @_;
|
||||
my $cgi = Bugzilla->cgi;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
my $template = Bugzilla->template;
|
||||
|
||||
ThrowCodeError("missing_bug_id") unless defined $bug_id;
|
||||
|
||||
$vars->{'bug_id'} = $bug_id;
|
||||
$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);
|
||||
|
||||
print $cgi->header();
|
||||
$template->process("bug/votes/list-for-bug.html.tmpl", $vars)
|
||||
|| ThrowTemplateError($template->error());
|
||||
}
|
||||
|
||||
# Display all the votes for a particular user. If it's the user
|
||||
# doing the viewing, give them the option to edit them too.
|
||||
sub show_user {
|
||||
my ($bug_id) = @_;
|
||||
my $cgi = Bugzilla->cgi;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
my $user = Bugzilla->user;
|
||||
my $template = Bugzilla->template;
|
||||
|
||||
# If a bug_id is given, and we're editing, we'll add it to the votes list.
|
||||
$bug_id ||= "";
|
||||
|
||||
my $who_id = $cgi->param('user_id') || $user->id;
|
||||
my $who = Bugzilla::User->check({ id => $who_id });
|
||||
|
||||
my $canedit = (Bugzilla->params->{'usevotes'} && $user->id == $who->id)
|
||||
? 1 : 0;
|
||||
|
||||
$dbh->bz_start_transaction();
|
||||
|
||||
if ($canedit && $bug_id) {
|
||||
# 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 @all_bug_ids;
|
||||
my @products;
|
||||
my $products = $user->get_selectable_products;
|
||||
# Read the votes data for this user for each product.
|
||||
foreach my $product (@$products) {
|
||||
next unless ($product->votes_per_user > 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->votes_per_user,
|
||||
$product->max_votes_per_bug) == 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->votes_per_user,
|
||||
maxperbug => $product->max_votes_per_bug });
|
||||
}
|
||||
}
|
||||
|
||||
$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->{'bug_id'} = $bug_id;
|
||||
$vars->{'all_bug_ids'} = \@all_bug_ids;
|
||||
|
||||
print $cgi->header();
|
||||
$template->process("bug/votes/list-for-user.html.tmpl", $vars)
|
||||
|| ThrowTemplateError($template->error());
|
||||
}
|
||||
|
||||
# Update the user's votes in the database.
|
||||
sub record_votes {
|
||||
############################################################################
|
||||
# Begin Data/Security Validation
|
||||
############################################################################
|
||||
|
||||
my $cgi = Bugzilla->cgi;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
my $template = Bugzilla->template;
|
||||
|
||||
# Build a list of bug IDs for which votes have been submitted. Votes
|
||||
# are submitted in form fields in which the field names are the bug
|
||||
# IDs and the field values are the number of votes.
|
||||
|
||||
my @buglist = grep {/^[1-9][0-9]*$/} $cgi->param();
|
||||
|
||||
# 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("bug/votes/delete-all.html.tmpl", $vars)
|
||||
|| ThrowTemplateError($template->error());
|
||||
exit();
|
||||
}
|
||||
elsif ($cgi->param('delete_all_votes') == 0) {
|
||||
print $cgi->redirect("votes.cgi");
|
||||
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;
|
||||
foreach my $id (@buglist) {
|
||||
my $bug = Bugzilla::Bug->check($id);
|
||||
$id = $bug->id;
|
||||
$votes{$id} = $cgi->param($id);
|
||||
detaint_natural($votes{$id})
|
||||
|| ThrowUserError("votes_must_be_nonnegative");
|
||||
}
|
||||
|
||||
############################################################################
|
||||
# End Data/Security Validation
|
||||
############################################################################
|
||||
my $who = Bugzilla->user->id;
|
||||
|
||||
# If the user is voting for bugs, make sure they aren't overstuffing
|
||||
# the ballot box.
|
||||
if (scalar(@buglist)) {
|
||||
my %prodcount;
|
||||
my %products;
|
||||
# XXX - We really need a $bug->product() method.
|
||||
foreach my $bug_id (@buglist) {
|
||||
my $bug = new Bugzilla::Bug($bug_id);
|
||||
my $prod = $bug->product;
|
||||
$products{$prod} ||= new Bugzilla::Product({name => $prod});
|
||||
$prodcount{$prod} ||= 0;
|
||||
$prodcount{$prod} += $votes{$bug_id};
|
||||
|
||||
# Make sure we haven't broken the votes-per-bug limit
|
||||
($votes{$bug_id} <= $products{$prod}->max_votes_per_bug)
|
||||
|| ThrowUserError("too_many_votes_for_bug",
|
||||
{max => $products{$prod}->max_votes_per_bug,
|
||||
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}->votes_per_user)
|
||||
|| ThrowUserError("too_many_votes_for_product",
|
||||
{max => $products{$prod}->votes_per_user,
|
||||
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 = CheckIfVotedConfirmed($id);
|
||||
push (@updated_bugs, $id) if $confirmed;
|
||||
}
|
||||
$dbh->bz_commit_transaction();
|
||||
|
||||
$vars->{'type'} = "votes";
|
||||
$vars->{'mailrecipients'} = { 'changer' => Bugzilla->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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user