diff options
author | Max Kanat-Alexander <mkanat@bugzilla.org> | 2010-02-16 00:22:55 +0100 |
---|---|---|
committer | Max Kanat-Alexander <mkanat@bugzilla.org> | 2010-02-16 00:22:55 +0100 |
commit | 120b63d507a3316666b25494bc890a024948aef8 (patch) | |
tree | 0a96e60d6316cc8471b066def8b1e1273f38e4ab | |
parent | 7802dbcf7bedcc09e5f1052ceb1ba82347a124b7 (diff) | |
download | bugzilla-120b63d507a3316666b25494bc890a024948aef8.tar.gz bugzilla-120b63d507a3316666b25494bc890a024948aef8.tar.xz |
Bug 372979: Make voting into an extension
r=mkanat, a=mkanat, a=LpSolit
72 files changed, 1577 insertions, 1284 deletions
diff --git a/.bzrignore b/.bzrignore index 8415e356b..5ee4b214d 100644 --- a/.bzrignore +++ b/.bzrignore @@ -27,6 +27,5 @@ /skins/contrib/Dusk/show_bug.css /skins/contrib/Dusk/show_multiple.css /skins/contrib/Dusk/summarize-time.css -/skins/contrib/Dusk/voting.css /skins/contrib/Dusk/yui .DS_Store diff --git a/Bugzilla/Bug.pm b/Bugzilla/Bug.pm index 336b9cfe1..b3f0fe58e 100644 --- a/Bugzilla/Bug.pm +++ b/Bugzilla/Bug.pm @@ -57,7 +57,6 @@ use URI::QueryParam; use base qw(Bugzilla::Object Exporter); @Bugzilla::Bug::EXPORT = qw( bug_alias_to_id - RemoveVotes CheckIfVotedConfirmed LogActivityEntry editable_bug_fields ); @@ -631,7 +630,6 @@ sub run_create_validators { # You can't set these fields on bug creation (or sometimes ever). delete $params->{resolution}; - delete $params->{votes}; delete $params->{lastdiffed}; delete $params->{bug_id}; @@ -967,7 +965,6 @@ sub remove_from_db { # - flags # - keywords # - longdescs - # - votes # Also, the attach_data table uses attachments.attach_id as a foreign # key, and so indirectly depends on a bug deletion too. @@ -983,7 +980,6 @@ sub remove_from_db { undef, ($bug_id, $bug_id)); $dbh->do("DELETE FROM flags WHERE bug_id = ?", undef, $bug_id); $dbh->do("DELETE FROM keywords WHERE bug_id = ?", undef, $bug_id); - $dbh->do("DELETE FROM votes WHERE bug_id = ?", undef, $bug_id); # The attach_data table doesn't depend on bugs.bug_id directly. my $attach_ids = @@ -1819,7 +1815,7 @@ sub fields { bug_status resolution dup_id see_also bug_file_loc status_whiteboard keywords priority bug_severity target_milestone - dependson blocked votes everconfirmed + dependson blocked everconfirmed reporter assigned_to cc estimated_time remaining_time actual_time deadline), @@ -2870,14 +2866,6 @@ sub show_attachment_flags { return $self->{'show_attachment_flags'}; } -sub use_votes { - my ($self) = @_; - return 0 if $self->{'error'}; - - return Bugzilla->params->{'usevotes'} - && $self->product_obj->votes_per_user > 0; -} - sub groups { my $self = shift; return $self->{'groups'} if exists $self->{'groups'}; @@ -3019,20 +3007,6 @@ sub choices { return $self->{'choices'}; } -sub votes { - my ($self) = @_; - return 0 if $self->{error}; - return $self->{votes} if defined $self->{votes}; - - my $dbh = Bugzilla->dbh; - $self->{votes} = $dbh->selectrow_array( - 'SELECT SUM(vote_count) FROM votes - WHERE bug_id = ? ' . $dbh->sql_group_by('bug_id'), - undef, $self->bug_id); - $self->{votes} ||= 0; - return $self->{votes}; -} - # Convenience Function. If you need speed, use this. If you need # other Bug fields in addition to this, just create a new Bug with # the alias. @@ -3312,136 +3286,6 @@ sub CountOpenDependencies { return @dependencies; } -# If a bug is moved to a product which allows less votes per bug -# compared to the previous product, extra votes need to be removed. -sub RemoveVotes { - my ($id, $who, $reason) = (@_); - my $dbh = Bugzilla->dbh; - - my $whopart = ($who) ? " AND votes.who = $who" : ""; - - my $sth = $dbh->prepare("SELECT profiles.login_name, " . - "profiles.userid, votes.vote_count, " . - "products.votesperuser, products.maxvotesperbug " . - "FROM profiles " . - "LEFT JOIN votes ON profiles.userid = votes.who " . - "LEFT JOIN bugs ON votes.bug_id = bugs.bug_id " . - "LEFT JOIN products ON products.id = bugs.product_id " . - "WHERE votes.bug_id = ? " . $whopart); - $sth->execute($id); - my @list; - while (my ($name, $userid, $oldvotes, $votesperuser, $maxvotesperbug) = $sth->fetchrow_array()) { - push(@list, [$name, $userid, $oldvotes, $votesperuser, $maxvotesperbug]); - } - - # @messages stores all emails which have to be sent, if any. - # This array is passed to the caller which will send these emails itself. - my @messages = (); - - if (scalar(@list)) { - foreach my $ref (@list) { - my ($name, $userid, $oldvotes, $votesperuser, $maxvotesperbug) = (@$ref); - - $maxvotesperbug = min($votesperuser, $maxvotesperbug); - - # If this product allows voting and the user's votes are in - # the acceptable range, then don't do anything. - next if $votesperuser && $oldvotes <= $maxvotesperbug; - - # If the user has more votes on this bug than this product - # allows, then reduce the number of votes so it fits - my $newvotes = $maxvotesperbug; - - my $removedvotes = $oldvotes - $newvotes; - - if ($newvotes) { - $dbh->do("UPDATE votes SET vote_count = ? " . - "WHERE bug_id = ? AND who = ?", - undef, ($newvotes, $id, $userid)); - } else { - $dbh->do("DELETE FROM votes WHERE bug_id = ? AND who = ?", - undef, ($id, $userid)); - } - - # Notice that we did not make sure that the user fit within the $votesperuser - # range. This is considered to be an acceptable alternative to losing votes - # during product moves. Then next time the user attempts to change their votes, - # they will be forced to fit within the $votesperuser limit. - - # Now lets send the e-mail to alert the user to the fact that their votes have - # been reduced or removed. - my $vars = { - 'to' => $name . Bugzilla->params->{'emailsuffix'}, - 'bugid' => $id, - 'reason' => $reason, - - 'votesremoved' => $removedvotes, - 'votesold' => $oldvotes, - 'votesnew' => $newvotes, - }; - - my $voter = new Bugzilla::User($userid); - my $template = Bugzilla->template_inner($voter->settings->{'lang'}->{'value'}); - - my $msg; - $template->process("email/votes-removed.txt.tmpl", $vars, \$msg); - push(@messages, $msg); - } - Bugzilla->template_inner(""); - - my $votes = $dbh->selectrow_array("SELECT SUM(vote_count) " . - "FROM votes WHERE bug_id = ?", - undef, $id) || 0; - $dbh->do("UPDATE bugs SET votes = ? WHERE bug_id = ?", - undef, ($votes, $id)); - } - # Now return the array containing emails to be sent. - return @messages; -} - -# If a user votes for a bug, or the number of votes required to -# confirm a bug has been reduced, check if the bug is now confirmed. -sub CheckIfVotedConfirmed { - my $id = shift; - my $bug = new Bugzilla::Bug($id); - - my $ret = 0; - if (!$bug->everconfirmed - and $bug->product_obj->votes_to_confirm - and $bug->votes >= $bug->product_obj->votes_to_confirm) - { - $bug->add_comment('', { type => CMT_POPULAR_VOTES }); - - if ($bug->bug_status eq 'UNCONFIRMED') { - # Get a valid open state. - my $new_status; - foreach my $state (@{$bug->status->can_change_to}) { - if ($state->is_open && $state->name ne 'UNCONFIRMED') { - $new_status = $state->name; - last; - } - } - ThrowCodeError('no_open_bug_status') unless $new_status; - - # We cannot call $bug->set_status() here, because a user without - # canconfirm privs should still be able to confirm a bug by - # popular vote. We already know the new status is valid, so it's safe. - $bug->{bug_status} = $new_status; - $bug->{everconfirmed} = 1; - delete $bug->{'status'}; # Contains the status object. - } - else { - # If the bug is in a closed state, only set everconfirmed to 1. - # Do not call $bug->_set_everconfirmed(), for the same reason as above. - $bug->{everconfirmed} = 1; - } - $bug->update(); - - $ret = 1; - } - return $ret; -} - ################################################################################ # check_can_change_field() defines what users are allowed to change. You # can add code here for site-specific policy changes, according to the diff --git a/Bugzilla/BugMail.pm b/Bugzilla/BugMail.pm index 204c4ba9a..e7694c32e 100644 --- a/Bugzilla/BugMail.pm +++ b/Bugzilla/BugMail.pm @@ -377,12 +377,6 @@ sub Send { # the relationships in a hash. The keys are userids, the values are an # array of role constants. - # Voters - my $voters = $dbh->selectcol_arrayref( - "SELECT who FROM votes WHERE bug_id = ?", undef, ($id)); - - $recipients{$_}->{+REL_VOTER} = BIT_DIRECT foreach (@$voters); - # CCs $recipients{$_}->{+REL_CC} = BIT_DIRECT foreach (@ccs); @@ -405,8 +399,8 @@ sub Send { foreach my $ref (@$diffs) { my ($who, $whoname, $what, $when, $old, $new) = (@$ref); if ($old) { - # You can't stop being the reporter, and mail isn't sent if you - # remove your vote. + # You can't stop being the reporter, so we don't check that + # relationship here. # Ignore people whose user account has been deleted or renamed. if ($what eq "CC") { foreach my $cc_user (split(/[\s,]+/, $old)) { @@ -462,7 +456,6 @@ sub Send { foreach my $user_id (keys %recipients) { my %rels_which_want; my $sent_mail = 0; - my $user = new Bugzilla::User($user_id); # Deleted users must be excluded. next unless $user; diff --git a/Bugzilla/Comment.pm b/Bugzilla/Comment.pm index 300357313..60d26012f 100644 --- a/Bugzilla/Comment.pm +++ b/Bugzilla/Comment.pm @@ -148,7 +148,7 @@ sub set_type { sub _check_extra_data { my ($invocant, $extra_data, $type) = @_; $type = $invocant->type if ref $invocant; - if ($type == CMT_NORMAL or $type == CMT_POPULAR_VOTES) { + if ($type == CMT_NORMAL) { if (defined $extra_data) { ThrowCodeError('comment_extra_data_not_allowed', { type => $type, extra_data => $extra_data }); diff --git a/Bugzilla/Config/BugFields.pm b/Bugzilla/Config/BugFields.pm index 9f3ddc9ab..d0de9dac6 100644 --- a/Bugzilla/Config/BugFields.pm +++ b/Bugzilla/Config/BugFields.pm @@ -72,12 +72,6 @@ sub get_param_list { }, { - name => 'usevotes', - type => 'b', - default => 0 - }, - - { name => 'usebugaliases', type => 'b', default => 0 diff --git a/Bugzilla/Constants.pm b/Bugzilla/Constants.pm index 8ab7455ff..d626c9749 100644 --- a/Bugzilla/Constants.pm +++ b/Bugzilla/Constants.pm @@ -90,7 +90,6 @@ use File::Basename; CMT_NORMAL CMT_DUPE_OF CMT_HAS_DUPE - CMT_POPULAR_VOTES CMT_MOVED_TO CMT_ATTACHMENT_CREATED CMT_ATTACHMENT_UPDATED @@ -98,7 +97,7 @@ use File::Basename; THROW_ERROR RELATIONSHIPS - REL_ASSIGNEE REL_QA REL_REPORTER REL_CC REL_VOTER REL_GLOBAL_WATCHER + REL_ASSIGNEE REL_QA REL_REPORTER REL_CC REL_GLOBAL_WATCHER REL_ANY POS_EVENTS @@ -282,7 +281,7 @@ use constant MAX_COMMENT_LENGTH => 65535; use constant CMT_NORMAL => 0; use constant CMT_DUPE_OF => 1; use constant CMT_HAS_DUPE => 2; -use constant CMT_POPULAR_VOTES => 3; +# Type 3 was CMT_POPULAR_VOTES, which moved to the Voting extension. use constant CMT_MOVED_TO => 4; use constant CMT_ATTACHMENT_CREATED => 5; use constant CMT_ATTACHMENT_UPDATED => 6; @@ -295,7 +294,7 @@ use constant REL_ASSIGNEE => 0; use constant REL_QA => 1; use constant REL_REPORTER => 2; use constant REL_CC => 3; -use constant REL_VOTER => 4; +# REL 4 was REL_VOTER, before it was moved ino an extension. use constant REL_GLOBAL_WATCHER => 5; # We need these strings for the X-Bugzilla-Reasons header @@ -307,7 +306,6 @@ use constant RELATIONSHIPS => { REL_REPORTER , "Reporter", REL_QA , "QAcontact", REL_CC , "CC", - REL_VOTER , "Voter", REL_GLOBAL_WATCHER, "GlobalWatcher" }; diff --git a/Bugzilla/DB/Schema.pm b/Bugzilla/DB/Schema.pm index 27ae3be8a..21c0e7970 100644 --- a/Bugzilla/DB/Schema.pm +++ b/Bugzilla/DB/Schema.pm @@ -273,8 +273,6 @@ use constant ABSTRACT_SCHEMA => { COLUMN => 'userid'}}, status_whiteboard => {TYPE => 'MEDIUMTEXT', NOTNULL => 1, DEFAULT => "''"}, - votes => {TYPE => 'INT3', NOTNULL => 1, - DEFAULT => '0'}, # Note: keywords field is only a cache; the real data # comes from the keywords table keywords => {TYPE => 'MEDIUMTEXT', NOTNULL => 1, @@ -309,7 +307,6 @@ use constant ABSTRACT_SCHEMA => { bugs_resolution_idx => ['resolution'], bugs_target_milestone_idx => ['target_milestone'], bugs_qa_contact_idx => ['qa_contact'], - bugs_votes_idx => ['votes'], ], }, @@ -434,24 +431,6 @@ use constant ABSTRACT_SCHEMA => { ], }, - votes => { - FIELDS => [ - who => {TYPE => 'INT3', NOTNULL => 1, - REFERENCES => {TABLE => 'profiles', - COLUMN => 'userid', - DELETE => 'CASCADE'}}, - bug_id => {TYPE => 'INT3', NOTNULL => 1, - REFERENCES => {TABLE => 'bugs', - COLUMN => 'bug_id', - DELETE => 'CASCADE'}}, - vote_count => {TYPE => 'INT2', NOTNULL => 1}, - ], - INDEXES => [ - votes_who_idx => ['who'], - votes_bug_id_idx => ['bug_id'], - ], - }, - attachments => { FIELDS => [ attach_id => {TYPE => 'MEDIUMSERIAL', NOTNULL => 1, @@ -1223,12 +1202,6 @@ use constant ABSTRACT_SCHEMA => { description => {TYPE => 'MEDIUMTEXT'}, isactive => {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 1}, - votesperuser => {TYPE => 'INT2', NOTNULL => 1, - DEFAULT => 0}, - maxvotesperbug => {TYPE => 'INT2', NOTNULL => 1, - DEFAULT => '10000'}, - votestoconfirm => {TYPE => 'INT2', NOTNULL => 1, - DEFAULT => 0}, defaultmilestone => {TYPE => 'varchar(20)', NOTNULL => 1, DEFAULT => "'---'"}, allows_unconfirmed => {TYPE => 'BOOLEAN', NOTNULL => 1, diff --git a/Bugzilla/Field.pm b/Bugzilla/Field.pm index 17e4194c2..c2ab6e11b 100644 --- a/Bugzilla/Field.pm +++ b/Bugzilla/Field.pm @@ -188,7 +188,6 @@ use constant DEFAULT_FIELDS => ( buglist => 1}, {name => 'reporter', desc => 'ReportedBy', in_new_bugmail => 1, buglist => 1}, - {name => 'votes', desc => 'Votes', buglist => 1}, {name => 'qa_contact', desc => 'QAContact', in_new_bugmail => 1, buglist => 1}, {name => 'cc', desc => 'CC', in_new_bugmail => 1}, diff --git a/Bugzilla/Install/DB.pm b/Bugzilla/Install/DB.pm index 65137e593..66461bf45 100644 --- a/Bugzilla/Install/DB.pm +++ b/Bugzilla/Install/DB.pm @@ -168,11 +168,6 @@ sub update_table_definitions { $dbh->bz_add_column('bugs', 'everconfirmed', {TYPE => 'BOOLEAN', NOTNULL => 1}, 1); - $dbh->bz_add_column('products', 'maxvotesperbug', - {TYPE => 'INT2', NOTNULL => 1, DEFAULT => '10000'}); - $dbh->bz_add_column('products', 'votestoconfirm', - {TYPE => 'INT2', NOTNULL => 1}, 0); - _populate_milestones_table(); # 2000-03-22 Changed the default value for target_milestone to be "---" @@ -363,8 +358,10 @@ sub update_table_definitions { {TYPE => 'MEDIUMTEXT', NOTNULL => 1, DEFAULT => "''"}); $dbh->bz_alter_column('bugs', 'keywords', {TYPE => 'MEDIUMTEXT', NOTNULL => 1, DEFAULT => "''"}); - $dbh->bz_alter_column('bugs', 'votes', - {TYPE => 'INT3', NOTNULL => 1, DEFAULT => '0'}); + if ($dbh->bz_column_info('bugs', 'votes')) { + $dbh->bz_alter_column('bugs', 'votes', + {TYPE => 'INT3', NOTNULL => 1, DEFAULT => '0'}); + } $dbh->bz_alter_column('bugs', 'lastdiffed', {TYPE => 'DATETIME'}); @@ -469,11 +466,14 @@ sub update_table_definitions { if ($dbh->bz_column_info('products', 'disallownew')){ $dbh->bz_alter_column('products', 'disallownew', {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 0}); + + if ($dbh->bz_column_info('products', 'votesperuser')) { + $dbh->bz_alter_column('products', 'votesperuser', + {TYPE => 'INT2', NOTNULL => 1, DEFAULT => 0}); + $dbh->bz_alter_column('products', 'votestoconfirm', + {TYPE => 'INT2', NOTNULL => 1, DEFAULT => 0}); + } } - $dbh->bz_alter_column('products', 'votesperuser', - {TYPE => 'INT2', NOTNULL => 1, DEFAULT => 0}); - $dbh->bz_alter_column('products', 'votestoconfirm', - {TYPE => 'INT2', NOTNULL => 1, DEFAULT => 0}); # 2006-08-04 LpSolit@gmail.com - Bug 305941 $dbh->bz_drop_column('profiles', 'refreshed_when'); @@ -654,14 +654,14 @@ sub _add_bug_vote_cache { # (P.S. All is not lost; it appears that the latest betas of MySQL # support a new table format which will allow 32 indices.) - $dbh->bz_drop_column('bugs', 'area'); - if (!$dbh->bz_column_info('bugs', 'votes')) { + if ($dbh->bz_column_info('bugs', 'area')) { + $dbh->bz_drop_column('bugs', 'area'); $dbh->bz_add_column('bugs', 'votes', {TYPE => 'INT3', NOTNULL => 1, DEFAULT => 0}); $dbh->bz_add_index('bugs', 'bugs_votes_idx', [qw(votes)]); + $dbh->bz_add_column('products', 'votesperuser', + {TYPE => 'INT2', NOTNULL => 1}, 0); } - $dbh->bz_add_column('products', 'votesperuser', - {TYPE => 'INT2', NOTNULL => 1}, 0); } sub _update_product_name_definition { @@ -896,9 +896,11 @@ sub _add_unique_login_name_index_to_profiles { ["votes", "who"], ["longdescs", "who"]) { my ($table, $field) = (@$i); - print " Updating $table.$field...\n"; - $dbh->do("UPDATE $table SET $field = $u1 " . - "WHERE $field = $u2"); + if ($dbh->bz_table_info($table)) { + print " Updating $table.$field...\n"; + $dbh->do("UPDATE $table SET $field = $u1 " . + "WHERE $field = $u2"); + } } $dbh->do("DELETE FROM profiles WHERE userid = $u2"); } @@ -2206,9 +2208,9 @@ sub _rename_votes_count_and_force_group_refresh { # # Renaming the 'count' column in the votes table because Sybase doesn't # like it - if ($dbh->bz_column_info('votes', 'count')) { - $dbh->bz_rename_column('votes', 'count', 'vote_count'); - } + return if !$dbh->bz_table_info('votes'); + return if $dbh->bz_column_info('votes', 'count'); + $dbh->bz_rename_column('votes', 'count', 'vote_count'); } sub _fix_group_with_empty_name { @@ -2266,7 +2268,9 @@ sub _migrate_email_prefs_to_new_table { "Reporter" => REL_REPORTER, "QAcontact" => REL_QA, "CClist" => REL_CC, - "Voter" => REL_VOTER); + # REL_VOTER was "4" before it was moved to an + # extension. + "Voter" => 4); my %events = ("Removeme" => EVT_ADDED_REMOVED, "Comments" => EVT_COMMENT, @@ -3343,8 +3347,10 @@ sub _add_allows_unconfirmed_to_product_table { if (!$dbh->bz_column_info('products', 'allows_unconfirmed')) { $dbh->bz_add_column('products', 'allows_unconfirmed', { TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'FALSE' }); - $dbh->do('UPDATE products SET allows_unconfirmed = 1 - WHERE votestoconfirm > 0'); + if ($dbh->bz_column_info('products', 'votestoconfirm')) { + $dbh->do('UPDATE products SET allows_unconfirmed = 1 + WHERE votestoconfirm > 0'); + } } } diff --git a/Bugzilla/Object.pm b/Bugzilla/Object.pm index dac8962ff..11db7567b 100644 --- a/Bugzilla/Object.pm +++ b/Bugzilla/Object.pm @@ -278,7 +278,8 @@ sub set { my ($self, $field, $value) = @_; # This method is protected. It's used to help implement set_ functions. - caller->isa('Bugzilla::Object') + my $caller = caller; + $caller->isa('Bugzilla::Object') || $caller->isa('Bugzilla::Extension') || ThrowCodeError('protection_violation', { caller => caller, superclass => __PACKAGE__, diff --git a/Bugzilla/Product.pm b/Bugzilla/Product.pm index 6b00fcbf6..975af7d5d 100644 --- a/Bugzilla/Product.pm +++ b/Bugzilla/Product.pm @@ -48,9 +48,6 @@ use constant DB_COLUMNS => qw( classification_id description isactive - votesperuser - maxvotesperbug - votestoconfirm defaultmilestone allows_unconfirmed ); @@ -66,9 +63,6 @@ use constant UPDATE_COLUMNS => qw( description defaultmilestone isactive - votesperuser - maxvotesperbug - votestoconfirm allows_unconfirmed ); @@ -80,9 +74,6 @@ use constant VALIDATORS => { version => \&_check_version, defaultmilestone => \&_check_default_milestone, isactive => \&Bugzilla::Object::check_boolean, - votesperuser => \&_check_votes_per_user, - maxvotesperbug => \&_check_votes_per_bug, - votestoconfirm => \&_check_votes_to_confirm, create_series => \&Bugzilla::Object::check_boolean }; @@ -155,99 +146,6 @@ sub update { $dbh->bz_start_transaction(); my ($changes, $old_self) = $self->SUPER::update(@_); - # We also have to fix votes. - my @msgs; # Will store emails to send to voters. - if ($changes->{maxvotesperbug} || $changes->{votesperuser} || $changes->{votestoconfirm}) { - # We cannot |use| these modules, due to dependency loops. - require Bugzilla::Bug; - import Bugzilla::Bug qw(RemoveVotes CheckIfVotedConfirmed); - require Bugzilla::User; - import Bugzilla::User qw(user_id_to_login); - - # 1. too many votes for a single user on a single bug. - my @toomanyvotes_list = (); - if ($self->max_votes_per_bug < $self->votes_per_user) { - my $votes = $dbh->selectall_arrayref( - 'SELECT votes.who, votes.bug_id - FROM votes - INNER JOIN bugs - ON bugs.bug_id = votes.bug_id - WHERE bugs.product_id = ? - AND votes.vote_count > ?', - undef, ($self->id, $self->max_votes_per_bug)); - - foreach my $vote (@$votes) { - my ($who, $id) = (@$vote); - # If some votes are removed, RemoveVotes() returns a list - # of messages to send to voters. - push(@msgs, RemoveVotes($id, $who, 'votes_too_many_per_bug')); - my $name = user_id_to_login($who); - - push(@toomanyvotes_list, {id => $id, name => $name}); - } - } - $changes->{'too_many_votes'} = \@toomanyvotes_list; - - # 2. too many total votes for a single user. - # This part doesn't work in the general case because RemoveVotes - # doesn't enforce votesperuser (except per-bug when it's less - # than maxvotesperbug). See Bugzilla::Bug::RemoveVotes(). - - my $votes = $dbh->selectall_arrayref( - 'SELECT votes.who, votes.vote_count - FROM votes - INNER JOIN bugs - ON bugs.bug_id = votes.bug_id - WHERE bugs.product_id = ?', - undef, $self->id); - - my %counts; - foreach my $vote (@$votes) { - my ($who, $count) = @$vote; - if (!defined $counts{$who}) { - $counts{$who} = $count; - } else { - $counts{$who} += $count; - } - } - my @toomanytotalvotes_list = (); - foreach my $who (keys(%counts)) { - if ($counts{$who} > $self->votes_per_user) { - my $bug_ids = $dbh->selectcol_arrayref( - 'SELECT votes.bug_id - FROM votes - INNER JOIN bugs - ON bugs.bug_id = votes.bug_id - WHERE bugs.product_id = ? - AND votes.who = ?', - undef, ($self->id, $who)); - - foreach my $bug_id (@$bug_ids) { - # RemoveVotes() returns a list of messages to send - # in case some voters had too many votes. - push(@msgs, RemoveVotes($bug_id, $who, 'votes_too_many_per_user')); - my $name = user_id_to_login($who); - - push(@toomanytotalvotes_list, {id => $bug_id, name => $name}); - } - } - } - $changes->{'too_many_total_votes'} = \@toomanytotalvotes_list; - - # 3. enough votes to confirm - my $bug_list = - $dbh->selectcol_arrayref('SELECT bug_id FROM bugs WHERE product_id = ? - AND bug_status = ? AND votes >= ?', - undef, ($self->id, 'UNCONFIRMED', $self->votes_to_confirm)); - - my @updated_bugs = (); - foreach my $bug_id (@$bug_list) { - my $confirmed = CheckIfVotedConfirmed($bug_id); - push (@updated_bugs, $bug_id) if $confirmed; - } - $changes->{'confirmed_bugs'} = \@updated_bugs; - } - # Also update group settings. if ($self->{check_group_controls}) { require Bugzilla::Bug; @@ -364,11 +262,6 @@ sub update { delete $self->{check_group_controls}; Bugzilla->user->clear_product_cache(); - # Now that changes have been committed, we can send emails to voters. - foreach my $msg (@msgs) { - MessageToMTA($msg); - } - return $changes; } @@ -524,37 +417,6 @@ sub _check_milestone_url { return $url; } -sub _check_votes_per_user { - return _check_votes(@_, 0); -} - -sub _check_votes_per_bug { - return _check_votes(@_, 10000); -} - -sub _check_votes_to_confirm { - return _check_votes(@_, 0); -} - -# This subroutine is only used internally by other _check_votes_* validators. -sub _check_votes { - my ($invocant, $votes, $field, $default) = @_; - - detaint_natural($votes); - # On product creation, if the number of votes is not a valid integer, - # we silently fall back to the given default value. - # If the product already exists and the change is illegal, we complain. - if (!defined $votes) { - if (ref $invocant) { - ThrowUserError('product_illegal_votes', {field => $field, votes => $_[1]}); - } - else { - $votes = $default; - } - } - return $votes; -} - ##################################### # Implement Bugzilla::Field::Choice # ##################################### @@ -618,9 +480,6 @@ sub set_name { $_[0]->set('name', $_[1]); } sub set_description { $_[0]->set('description', $_[1]); } sub set_default_milestone { $_[0]->set('defaultmilestone', $_[1]); } sub set_is_active { $_[0]->set('isactive', $_[1]); } -sub set_votes_per_user { $_[0]->set('votesperuser', $_[1]); } -sub set_votes_per_bug { $_[0]->set('maxvotesperbug', $_[1]); } -sub set_votes_to_confirm { $_[0]->set('votestoconfirm', $_[1]); } sub set_allows_unconfirmed { $_[0]->set('allows_unconfirmed', $_[1]); } sub set_group_controls { @@ -876,9 +735,6 @@ sub flag_types { sub allows_unconfirmed { return $_[0]->{'allows_unconfirmed'}; } sub description { return $_[0]->{'description'}; } sub is_active { return $_[0]->{'isactive'}; } -sub votes_per_user { return $_[0]->{'votesperuser'}; } -sub max_votes_per_bug { return $_[0]->{'maxvotesperbug'}; } -sub votes_to_confirm { return $_[0]->{'votestoconfirm'}; } sub default_milestone { return $_[0]->{'defaultmilestone'}; } sub classification_id { return $_[0]->{'classification_id'}; } @@ -939,9 +795,6 @@ Bugzilla::Product - Bugzilla product class. my $name = $product->name; my $description = $product->description; my isactive = $product->is_active; - my votesperuser = $product->votes_per_user; - my maxvotesperbug = $product->max_votes_per_bug; - my votestoconfirm = $product->votes_to_confirm; my $defaultmilestone = $product->default_milestone; my $classificationid = $product->classification_id; my $allows_unconfirmed = $product->allows_unconfirmed; diff --git a/Bugzilla/Search.pm b/Bugzilla/Search.pm index d85da01a4..52c99903d 100644 --- a/Bugzilla/Search.pm +++ b/Bugzilla/Search.pm @@ -86,10 +86,8 @@ use constant SPECIAL_ORDER_JOIN => { # 3. title: The title of the column as displayed to users. # # Note: There are a few hacks in the code that deviate from these definitions. -# In particular, when the list is sorted by the "votes" field the word -# "DESC" is added to the end of the field to sort in descending order, -# and the redundant short_desc column is removed when the client -# requests "all" columns. +# In particular, the redundant short_desc column is removed when the +# client requests "all" columns. # # This is really a constant--that is, once it's been called once, the value # will always be the same unless somebody adds a new custom field. But @@ -281,18 +279,6 @@ sub init { push(@supptables, "LEFT JOIN flagtypes ON flagtypes.id = flags.type_id"); } - my $minvotes; - if (defined $params->param('votes')) { - my $c = trim($params->param('votes')); - if ($c ne "") { - if ($c !~ /^[0-9]*$/) { - ThrowUserError("illegal_at_least_x_votes", - { value => $c }); - } - push(@specialchart, ["votes", "greaterthan", $c - 1]); - } - } - # If the user has selected all of either status or resolution, change to # selecting none. This is functionally equivalent, but quite a lot faster. # Also, if the status is __open__ or __closed__, translate those diff --git a/Bugzilla/Search/Quicksearch.pm b/Bugzilla/Search/Quicksearch.pm index 2f9e0734f..1e0bdc437 100644 --- a/Bugzilla/Search/Quicksearch.pm +++ b/Bugzilla/Search/Quicksearch.pm @@ -339,12 +339,6 @@ sub _handle_special_first_chars { sub _handle_field_names { my ($or_operand, $negate, $unknownFields, $ambiguous_fields) = @_; - # votes:xx ("at least xx votes") - if ($or_operand =~ /^votes:([0-9]+)$/) { - addChart('votes', 'greaterthan', $1 - 1, $negate); - return 1; - } - # Flag and requestee shortcut if ($or_operand =~ /^(?:flag:)?([^\?]+\?)([^\?]*)$/) { addChart('flagtypes.name', 'substring', $1, $negate); @@ -454,18 +448,6 @@ sub _special_field_syntax { return 1; } - # Votes (votes>xx) - if ($word =~ m/^votes>([0-9]+)$/) { - addChart('votes', 'greaterthan', $1, $negate); - return 1; - } - - # Votes (votes>=xx, votes=>xx) - if ($word =~ m/^votes(>=|=>)([0-9]+)$/) { - addChart('votes', 'greaterthan', $2-1, $negate); - return 1; - } - return 0; } diff --git a/Bugzilla/WebService/Bug.pm b/Bugzilla/WebService/Bug.pm index 711a45f44..1d7b9f7d9 100644 --- a/Bugzilla/WebService/Bug.pm +++ b/Bugzilla/WebService/Bug.pm @@ -386,9 +386,6 @@ sub search { if (my $when = delete $params->{creation_ts}) { $params->{WHERE}->{'creation_ts >= ?'} = $when; } - if (my $votes = delete $params->{votes}) { - $params->{WHERE}->{'votes >= ?'} = $votes; - } if (my $summary = delete $params->{short_desc}) { my @strings = ref $summary ? @$summary : ($summary); my @likes = ("short_desc LIKE ?") x @strings; @@ -1687,11 +1684,6 @@ C<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 diff --git a/buglist.cgi b/buglist.cgi index b8cfa6336..48fe2d873 100755 --- a/buglist.cgi +++ b/buglist.cgi @@ -653,18 +653,6 @@ else { # and are hard-coded into the display templates. @displaycolumns = grep($_ ne 'bug_id', @displaycolumns); -# Add the votes column to the list of columns to be displayed -# in the bug list if the user is searching for bugs with a certain -# number of votes and the votes column is not already on the list. - -# Some versions of perl will taint 'votes' if this is done as a single -# statement, because the votes param is tainted at this point -my $votes = $params->param('votes'); -$votes ||= ""; -if (trim($votes) && !grep($_ eq 'votes', @displaycolumns)) { - push(@displaycolumns, 'votes'); -} - # Remove the timetracking columns if they are not a part of the group # (happens if a user had access to time tracking and it was revoked/disabled) if (!Bugzilla->user->is_timetracker) { @@ -806,12 +794,6 @@ if ($order) { # Special handlings for certain columns next if $column_name eq 'relevance' && !$fulltext; - # If we are sorting by votes, sort in descending order if - # no explicit sort order was given. - if ($column_name eq 'votes' && !$direction) { - $direction = "DESC"; - } - if (exists $columns->{$column_name}) { $direction = " $direction" if $direction; push(@order, "$column_name$direction"); diff --git a/bugzilla.dtd b/bugzilla.dtd index 64f575b62..b449d6ba4 100644 --- a/bugzilla.dtd +++ b/bugzilla.dtd @@ -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)> diff --git a/colchange.cgi b/colchange.cgi index 409f15e5a..15bdac599 100755 --- a/colchange.cgi +++ b/colchange.cgi @@ -55,9 +55,6 @@ if (Bugzilla->params->{"useclassification"}) { push(@masterlist, ("product", "component", "version", "op_sys")); -if (Bugzilla->params->{"usevotes"}) { - push (@masterlist, "votes"); -} if (Bugzilla->params->{"usebugaliases"}) { unshift(@masterlist, "alias"); } diff --git a/contrib/merge-users.pl b/contrib/merge-users.pl index 80c516e04..6c1ed1377 100755 --- a/contrib/merge-users.pl +++ b/contrib/merge-users.pl @@ -129,7 +129,6 @@ my $changes = { flags => ['setter_id', 'requestee_id'], cc => ['who bug_id'], longdescs => ['who'], - votes => ['who'], # Tables affecting global behavior / other users. components => ['initialowner', 'initialqacontact'], component_cc => ['user_id component_id'], diff --git a/docs/en/images/bzLifecycle.xml b/docs/en/images/bzLifecycle.xml index 1adc6b70c..dda8ac48a 100644 --- a/docs/en/images/bzLifecycle.xml +++ b/docs/en/images/bzLifecycle.xml @@ -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"/> diff --git a/editproducts.cgi b/editproducts.cgi index 8433ed16b..4a302aa6c 100755 --- a/editproducts.cgi +++ b/editproducts.cgi @@ -186,11 +186,6 @@ if ($action eq 'new') { create_series => scalar $cgi->param('createseries'), allows_unconfirmed => scalar $cgi->param('allows_unconfirmed'), ); - if (Bugzilla->params->{'usevotes'}) { - $create_params{votesperuser} = $cgi->param('votesperuser'); - $create_params{maxvotesperbug} = $cgi->param('maxvotesperbug'); - $create_params{votestoconfirm} = $cgi->param('votestoconfirm'); - } my $product = Bugzilla::Product->create(\%create_params); delete_token($token); @@ -295,16 +290,13 @@ if ($action eq 'update') { my $product_old_name = trim($cgi->param('product_old_name') || ''); my $product = $user->check_can_admin_product($product_old_name); - $product->set_name($product_name); - $product->set_description(scalar $cgi->param('description')); - $product->set_default_milestone(scalar $cgi->param('defaultmilestone')); - $product->set_is_active(scalar $cgi->param('is_active')); - if (Bugzilla->params->{'usevotes'}) { - $product->set_votes_per_user(scalar $cgi->param('votesperuser')); - $product->set_votes_per_bug(scalar $cgi->param('maxvotesperbug')); - $product->set_votes_to_confirm(scalar $cgi->param('votestoconfirm')); - } - $product->set_allows_unconfirmed(scalar $cgi->param('allows_unconfirmed')); + $product->set_all({ + name => $product_name, + description => scalar $cgi->param('description'), + is_active => scalar $cgi->param('is_active'), + allows_unconfirmed => scalar $cgi->param('allows_unconfirmed'), + default_milestone => scalar $cgi->param('defaultmilestone'), + }); my $changes = $product->update(); diff --git a/editusers.cgi b/editusers.cgi index e63f29fc5..7ce6bb9d2 100755 --- a/editusers.cgi +++ b/editusers.cgi @@ -424,9 +424,6 @@ if ($action eq 'search') { $vars->{'series'} = $dbh->selectrow_array( 'SELECT COUNT(*) FROM series WHERE creator = ?', undef, $otherUserID); - $vars->{'votes'} = $dbh->selectrow_array( - 'SELECT COUNT(*) FROM votes WHERE who = ?', - undef, $otherUserID); $vars->{'watch'}{'watched'} = $dbh->selectrow_array( 'SELECT COUNT(*) FROM watch WHERE watched = ?', undef, $otherUserID); @@ -537,7 +534,6 @@ if ($action eq 'search') { $dbh->do('DELETE FROM tokens WHERE userid = ?', undef, $otherUserID); $dbh->do('DELETE FROM user_group_map WHERE user_id = ?', undef, $otherUserID); - $dbh->do('DELETE FROM votes WHERE who = ?', undef, $otherUserID); $dbh->do('DELETE FROM watch WHERE watcher = ? OR watched = ?', undef, ($otherUserID, $otherUserID)); diff --git a/extensions/Voting/Extension.pm b/extensions/Voting/Extension.pm new file mode 100644 index 000000000..e111ac785 --- /dev/null +++ b/extensions/Voting/Extension.pm @@ -0,0 +1,861 @@ +# -*- Mode: perl; indent-tabs-mode: nil -*- +# +# The contents of this file are subject to the Mozilla Public +# License Version 1.1 (the "License"); you may not use this file +# except in compliance with the License. You may obtain a copy of +# the License at http://www.mozilla.org/MPL/ +# +# Software distributed under the License is distributed on an "AS +# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or +# implied. See the License for the specific language governing +# rights and limitations under the License. +# +# The Original Code is the Bugzilla Bug Tracking System. +# +# The Initial Developer of the Original Code is Netscape Communications +# Corporation. Portions created by Netscape are +# Copyright (C) 1998 Netscape Communications Corporation. All +# Rights Reserved. +# +# Contributor(s): Terry Weissman <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; diff --git a/extensions/Voting/template/en/default/hook/account/prefs/email-relationships.html.tmpl b/extensions/Voting/template/en/default/hook/account/prefs/email-relationships.html.tmpl new file mode 100644 index 000000000..0bd81eae1 --- /dev/null +++ b/extensions/Voting/template/en/default/hook/account/prefs/email-relationships.html.tmpl @@ -0,0 +1,22 @@ +[%# The contents of this file are subject to the Mozilla Public + # License Version 1.1 (the "License"); you may not use this file + # except in compliance with the License. You may obtain a copy of + # the License at http://www.mozilla.org/MPL/ + # + # Software distributed under the License is distributed on an "AS + # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or + # implied. See the License for the specific language governing + # rights and limitations under the License. + # + # The Original Code is the Bugzilla Bug Tracking System. + # + # The Initial Developer of the Original Code is Everything Solved, Inc. + # Portions created by the Initial Developer are Copyright (C) 2010 + # the Initial Developer. All Rights Reserved. + # + # Contributor(s): + # Max Kanat-Alexander <mkanat@bugzilla.org> + #%] + +[% relationships.push({ id = constants.REL_VOTER, description = "Voter" }) %] +[% no_added_removed.push(constants.REL_VOTER) %] diff --git a/extensions/Voting/template/en/default/hook/admin/products/edit-common-rows.html.tmpl b/extensions/Voting/template/en/default/hook/admin/products/edit-common-rows.html.tmpl new file mode 100644 index 000000000..fbbda3ea0 --- /dev/null +++ b/extensions/Voting/template/en/default/hook/admin/products/edit-common-rows.html.tmpl @@ -0,0 +1,60 @@ +[%# The contents of this file are subject to the Mozilla Public + # License Version 1.1 (the "License"); you may not use this file + # except in compliance with the License. You may obtain a copy of + # the License at http://www.mozilla.org/MPL/ + # + # Software distributed under the License is distributed on an "AS + # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or + # implied. See the License for the specific language governing + # rights and limitations under the License. + # + # The Original Code is the Bugzilla Bug Tracking System. + # + # The Initial Developer of the Original Code is Everything Solved, Inc. + # Portions created by the Initial Developer are Copyright (C) 2010 + # the Initial Developer. All Rights Reserved. + # + # Contributor(s): + # Max Kanat-Alexander <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> + diff --git a/extensions/Voting/template/en/default/hook/admin/products/updated-changes.html.tmpl b/extensions/Voting/template/en/default/hook/admin/products/updated-changes.html.tmpl new file mode 100644 index 000000000..876c51187 --- /dev/null +++ b/extensions/Voting/template/en/default/hook/admin/products/updated-changes.html.tmpl @@ -0,0 +1,102 @@ +[%# The contents of this file are subject to the Mozilla Public + # License Version 1.1 (the "License"); you may not use this file + # except in compliance with the License. You may obtain a copy of + # the License at http://www.mozilla.org/MPL/ + # + # Software distributed under the License is distributed on an "AS + # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or + # implied. See the License for the specific language governing + # rights and limitations under the License. + # + # The Original Code is the Bugzilla Bug Tracking System. + # + # The Initial Developer of the Original Code is Everything Solved, Inc. + # Portions created by the Initial Developer are Copyright (C) 2010 + # the Initial Developer. All Rights Reserved. + # + # Contributor(s): + # Max Kanat-Alexander <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 %] diff --git a/extensions/Voting/template/en/default/hook/admin/sanitycheck/messages-statuses.html.tmpl b/extensions/Voting/template/en/default/hook/admin/sanitycheck/messages-statuses.html.tmpl new file mode 100644 index 000000000..afb81d34c --- /dev/null +++ b/extensions/Voting/template/en/default/hook/admin/sanitycheck/messages-statuses.html.tmpl @@ -0,0 +1,40 @@ +[%# The contents of this file are subject to the Mozilla Public + # License Version 1.1 (the "License"); you may not use this file + # except in compliance with the License. You may obtain a copy of + # the License at http://www.mozilla.org/MPL/ + # + # Software distributed under the License is distributed on an "AS + # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or + # implied. See the License for the specific language governing + # rights and limitations under the License. + # + # The Original Code is the Bugzilla Bug Tracking System. + # + # The Initial Developer of the Original Code is Everything Solved, Inc. + # Portions created by the Initial Developer are Copyright (C) 2010 + # the Initial Developer. All Rights Reserved. + # + # Contributor(s): + # Max Kanat-Alexander <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 %] diff --git a/extensions/Voting/template/en/default/hook/admin/users/confirm-delete-warn_safe.html.tmpl b/extensions/Voting/template/en/default/hook/admin/users/confirm-delete-warn_safe.html.tmpl new file mode 100644 index 000000000..f799f1254 --- /dev/null +++ b/extensions/Voting/template/en/default/hook/admin/users/confirm-delete-warn_safe.html.tmpl @@ -0,0 +1,38 @@ +[%# The contents of this file are subject to the Mozilla Public + # License Version 1.1 (the "License"); you may not use this file + # except in compliance with the License. You may obtain a copy of + # the License at http://www.mozilla.org/MPL/ + # + # Software distributed under the License is distributed on an "AS + # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or + # implied. See the License for the specific language governing + # rights and limitations under the License. + # + # The Original Code is the Bugzilla Bug Tracking System. + # + # The Initial Developer of the Original Code is Everything Solved, Inc. + # Portions created by the Initial Developer are Copyright (C) 2010 + # the Initial Developer. All Rights Reserved. + # + # Contributor(s): + # Max Kanat-Alexander <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 %] diff --git a/extensions/Voting/template/en/default/hook/bug/edit-after_importance.html.tmpl b/extensions/Voting/template/en/default/hook/bug/edit-after_importance.html.tmpl new file mode 100644 index 000000000..7952442da --- /dev/null +++ b/extensions/Voting/template/en/default/hook/bug/edit-after_importance.html.tmpl @@ -0,0 +1,41 @@ +[%# The contents of this file are subject to the Mozilla Public + # License Version 1.1 (the "License"); you may not use this file + # except in compliance with the License. You may obtain a copy of + # the License at http://www.mozilla.org/MPL/ + # + # Software distributed under the License is distributed on an "AS + # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or + # implied. See the License for the specific language governing + # rights and limitations under the License. + # + # The Original Code is the Bugzilla Bug Tracking System. + # + # The Initial Developer of the Original Code is Everything Solved, Inc. + # Portions created by the Initial Developer are Copyright (C) 2010 + # the Initial Developer. All Rights Reserved. + # + # Contributor(s): + # Max Kanat-Alexander <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 %] diff --git a/extensions/Voting/template/en/default/hook/bug/format_comment-type.txt.tmpl b/extensions/Voting/template/en/default/hook/bug/format_comment-type.txt.tmpl new file mode 100644 index 000000000..ebba6fcab --- /dev/null +++ b/extensions/Voting/template/en/default/hook/bug/format_comment-type.txt.tmpl @@ -0,0 +1,23 @@ +[%# The contents of this file are subject to the Mozilla Public + # License Version 1.1 (the "License"); you may not use this file + # except in compliance with the License. You may obtain a copy of + # the License at http://www.mozilla.org/MPL/ + # + # Software distributed under the License is distributed on an "AS + # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or + # implied. See the License for the specific language governing + # rights and limitations under the License. + # + # The Original Code is the Bugzilla Bug Tracking System. + # + # The Initial Developer of the Original Code is Everything Solved, Inc. + # Portions created by the Initial Developer are Copyright (C) 2010 + # the Initial Developer. All Rights Reserved. + # + # Contributor(s): + # Max Kanat-Alexander <mkanat@bugzilla.org> + #%] + +[% IF comment.type == constants.CMT_POPULAR_VOTES %] +*** This [% terms.bug %] has been confirmed by popular vote. *** +[% END %] diff --git a/extensions/Voting/template/en/default/hook/bug/process/header-title.html.tmpl b/extensions/Voting/template/en/default/hook/bug/process/header-title.html.tmpl new file mode 100644 index 000000000..a4530653b --- /dev/null +++ b/extensions/Voting/template/en/default/hook/bug/process/header-title.html.tmpl @@ -0,0 +1,24 @@ +[%# The contents of this file are subject to the Mozilla Public + # License Version 1.1 (the "License"); you may not use this file + # except in compliance with the License. You may obtain a copy of + # the License at http://www.mozilla.org/MPL/ + # + # Software distributed under the License is distributed on an "AS + # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or + # implied. See the License for the specific language governing + # rights and limitations under the License. + # + # The Original Code is the Bugzilla Bug Tracking System. + # + # The Initial Developer of the Original Code is Everything Solved, Inc. + # Portions created by the Initial Developer are Copyright (C) 2010 + # the Initial Developer. All Rights Reserved. + # + # Contributor(s): + # Max Kanat-Alexander <mkanat@bugzilla.org> + #%] + +[% IF title_tag == "change_votes" %] + [% title = "Change Votes" %] +[% END %] + diff --git a/extensions/Voting/template/en/default/hook/bug/process/results-title.html.tmpl b/extensions/Voting/template/en/default/hook/bug/process/results-title.html.tmpl new file mode 100644 index 000000000..ae0d465dc --- /dev/null +++ b/extensions/Voting/template/en/default/hook/bug/process/results-title.html.tmpl @@ -0,0 +1,21 @@ +[%# The contents of this file are subject to the Mozilla Public + # License Version 1.1 (the "License"); you may not use this file + # except in compliance with the License. You may obtain a copy of + # the License at http://www.mozilla.org/MPL/ + # + # Software distributed under the License is distributed on an "AS + # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or + # implied. See the License for the specific language governing + # rights and limitations under the License. + # + # The Original Code is the Bugzilla Bug Tracking System. + # + # The Initial Developer of the Original Code is Everything Solved, Inc. + # Portions created by the Initial Developer are Copyright (C) 2010 + # the Initial Developer. All Rights Reserved. + # + # Contributor(s): + # Max Kanat-Alexander <mkanat@bugzilla.org> + #%] + +[% title.votes = "$Link confirmed by number of votes" %] diff --git a/extensions/Voting/template/en/default/hook/global/field-descs-end.none.tmpl b/extensions/Voting/template/en/default/hook/global/field-descs-end.none.tmpl new file mode 100644 index 000000000..2fd798084 --- /dev/null +++ b/extensions/Voting/template/en/default/hook/global/field-descs-end.none.tmpl @@ -0,0 +1,22 @@ +[%# The contents of this file are subject to the Mozilla Public + # License Version 1.1 (the "License"); you may not use this file + # except in compliance with the License. You may obtain a copy of + # the License at http://www.mozilla.org/MPL/ + # + # Software distributed under the License is distributed on an "AS + # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or + # implied. See the License for the specific language governing + # rights and limitations under the License. + # + # The Original Code is the Bugzilla Bug Tracking System. + # + # The Initial Developer of the Original Code is Everything Solved, Inc. + # Portions created by the Initial Developer are Copyright (C) 2010 + # the Initial Developer. All Rights Reserved. + # + # Contributor(s): + # Max Kanat-Alexander <mkanat@bugzilla.org> + #%] + +[% field_descs.votes = "Votes" %] + diff --git a/extensions/Voting/template/en/default/hook/global/reason-descs-end.none.tmpl b/extensions/Voting/template/en/default/hook/global/reason-descs-end.none.tmpl new file mode 100644 index 000000000..3a1f5a189 --- /dev/null +++ b/extensions/Voting/template/en/default/hook/global/reason-descs-end.none.tmpl @@ -0,0 +1,23 @@ +[%# The contents of this file are subject to the Mozilla Public + # License Version 1.1 (the "License"); you may not use this file + # except in compliance with the License. You may obtain a copy of + # the License at http://www.mozilla.org/MPL/ + # + # Software distributed under the License is distributed on an "AS + # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or + # implied. See the License for the specific language governing + # rights and limitations under the License. + # + # The Original Code is the Bugzilla Bug Tracking System. + # + # The Initial Developer of the Original Code is Everything Solved, Inc. + # Portions created by the Initial Developer are Copyright (C) 2010 + # the Initial Developer. All Rights Reserved. + # + # Contributor(s): + # Max Kanat-Alexander <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}." %] diff --git a/extensions/Voting/template/en/default/hook/global/user-error-errors.html.tmpl b/extensions/Voting/template/en/default/hook/global/user-error-errors.html.tmpl new file mode 100644 index 000000000..c2ff70728 --- /dev/null +++ b/extensions/Voting/template/en/default/hook/global/user-error-errors.html.tmpl @@ -0,0 +1,55 @@ +[%# The contents of this file are subject to the Mozilla Public + # License Version 1.1 (the "License"); you may not use this file + # except in compliance with the License. You may obtain a copy of + # the License at http://www.mozilla.org/MPL/ + # + # Software distributed under the License is distributed on an "AS + # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or + # implied. See the License for the specific language governing + # rights and limitations under the License. + # + # The Original Code is the Bugzilla Bug Tracking System. + # + # The Initial Developer of the Original Code is Everything Solved, Inc. + # Portions created by the Initial Developer are Copyright (C) 2010 + # the Initial Developer. All Rights Reserved. + # + # Contributor(s): + # Max Kanat-Alexander <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 %] diff --git a/extensions/Voting/template/en/default/hook/search/form-email_numbering_end.html.tmpl b/extensions/Voting/template/en/default/hook/search/form-email_numbering_end.html.tmpl new file mode 100644 index 000000000..5acfff14c --- /dev/null +++ b/extensions/Voting/template/en/default/hook/search/form-email_numbering_end.html.tmpl @@ -0,0 +1,31 @@ +[%# The contents of this file are subject to the Mozilla Public + # License Version 1.1 (the "License"); you may not use this file + # except in compliance with the License. You may obtain a copy of + # the License at http://www.mozilla.org/MPL/ + # + # Software distributed under the License is distributed on an "AS + # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or + # implied. See the License for the specific language governing + # rights and limitations under the License. + # + # The Original Code is the Bugzilla Bug Tracking System. + # + # The Initial Developer of the Original Code is Everything Solved, Inc. + # Portions created by the Initial Developer are Copyright (C) 2010 + # the Initial Developer. All Rights Reserved. + # + # Contributor(s): + # Max Kanat-Alexander <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> + diff --git a/extensions/Voting/template/en/default/hook/search/search-report-select-rep_fields.html.tmpl b/extensions/Voting/template/en/default/hook/search/search-report-select-rep_fields.html.tmpl new file mode 100644 index 000000000..ca74f6d2d --- /dev/null +++ b/extensions/Voting/template/en/default/hook/search/search-report-select-rep_fields.html.tmpl @@ -0,0 +1,21 @@ +[%# The contents of this file are subject to the Mozilla Public + # License Version 1.1 (the "License"); you may not use this file + # except in compliance with the License. You may obtain a copy of + # the License at http://www.mozilla.org/MPL/ + # + # Software distributed under the License is distributed on an "AS + # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or + # implied. See the License for the specific language governing + # rights and limitations under the License. + # + # The Original Code is the Bugzilla Bug Tracking System. + # + # The Initial Developer of the Original Code is Everything Solved, Inc. + # Portions created by the Initial Developer are Copyright (C) 2010 + # the Initial Developer. All Rights Reserved. + # + # Contributor(s): + # Max Kanat-Alexander <mkanat@bugzilla.org> + #%] + +[% rep_fields.push('votes') %] diff --git a/template/en/default/pages/voting.html.tmpl b/extensions/Voting/template/en/default/pages/voting.html.tmpl index 4e6fb473d..99026c0d5 100644 --- a/template/en/default/pages/voting.html.tmpl +++ b/extensions/Voting/template/en/default/pages/voting.html.tmpl @@ -64,6 +64,6 @@ a few [% terms.bugs %] indicating your strong support for them. on [% terms.bugs %] you vote for.</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 %] diff --git a/template/en/default/bug/votes/list-for-bug.html.tmpl b/extensions/Voting/template/en/default/pages/voting/bug.html.tmpl index a599dc0fb..03434a505 100644 --- a/template/en/default/bug/votes/list-for-bug.html.tmpl +++ b/extensions/Voting/template/en/default/pages/voting/bug.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 %] diff --git a/template/en/default/bug/votes/list-for-user.html.tmpl b/extensions/Voting/template/en/default/pages/voting/user.html.tmpl index 2f97616ed..800079224 100644 --- a/template/en/default/bug/votes/list-for-user.html.tmpl +++ b/extensions/Voting/template/en/default/pages/voting/user.html.tmpl @@ -31,7 +31,7 @@ # maxvotes: max votes allowed for a user in this product # maxperbug: max votes per bug allowed for a user in this product # - # bug_id: number; if the user is voting for a bug, this is the bug id + # this_bug: Bugzilla::Bug; if the user is voting for a bug, this is the bug # # canedit: boolean; Should the votes be presented in a form, or readonly? # @@ -44,18 +44,18 @@ [% subheader = voting_user.login FILTER html %] [% IF canedit %] [% title = "Change Votes" %] - [% IF bug_id %] + [% IF this_bug %] [%# We .select and .focus the input so it works for textbox and checkbox %] - [% onload = "document.forms['voting_form'].bug_" _ bug_id _ - ".select();document.forms['voting_form'].bug_" _ bug_id _ + [% onload = "document.forms['voting_form'].bug_" _ this_bug.id _ + ".select();document.forms['voting_form'].bug_" _ this_bug.id _ ".focus()" %] [% END %] [% ELSE %] [% title = "Show Votes" %] [% END %] [% PROCESS global/header.html.tmpl - style_urls = [ "skins/standard/voting.css" ] + style_urls = [ "extensions/Voting/web/style.css" ] %] [% ELSE %] <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 %] diff --git a/template/en/default/bug/votes/delete-all.html.tmpl b/extensions/Voting/template/en/default/voting/delete-all.html.tmpl index 41b75123d..82ddc3596 100644 --- a/template/en/default/bug/votes/delete-all.html.tmpl +++ b/extensions/Voting/template/en/default/voting/delete-all.html.tmpl @@ -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"> diff --git a/template/en/default/email/votes-removed.txt.tmpl b/extensions/Voting/template/en/default/voting/votes-removed.txt.tmpl index bfb37c90d..bfb37c90d 100644 --- a/template/en/default/email/votes-removed.txt.tmpl +++ b/extensions/Voting/template/en/default/voting/votes-removed.txt.tmpl diff --git a/skins/standard/voting.css b/extensions/Voting/web/style.css index 5d9c9afe6..5d9c9afe6 100644 --- a/skins/standard/voting.css +++ b/extensions/Voting/web/style.css diff --git a/importxml.pl b/importxml.pl index 1a61c5ead..aff475b70 100755 --- a/importxml.pl +++ b/importxml.pl @@ -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; } } diff --git a/process_bug.cgi b/process_bug.cgi index 237588ebd..d16298df6 100755 --- a/process_bug.cgi +++ b/process_bug.cgi @@ -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", diff --git a/report.cgi b/report.cgi index 6612ded2a..100c76e90 100755 --- a/report.cgi +++ b/report.cgi @@ -113,7 +113,6 @@ my @columns = qw( qa_contact classification version - votes keywords target_milestone ); diff --git a/sanitycheck.cgi b/sanitycheck.cgi index 036286454..4b6e524c2 100755 --- a/sanitycheck.cgi +++ b/sanitycheck.cgi @@ -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) @@ -111,27 +111,6 @@ unless ($user->in_group('editcomponents')) { } ########################################################################### -# 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,75 +658,19 @@ 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'; - +sub check_keywords { 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; - } + my $cgi = Bugzilla->cgi; - Status('vote_cache_rebuild_fix') if $offer_votecache_rebuild; -} + my %keyword = @{ $dbh->selectcol_arrayref( + q{SELECT bug_id, keywords FROM bugs WHERE keywords != ''}, + {Columns=>[1,2]}) }; -sub _check_keywords { - my $keyword = shift; Status('keyword_check_start'); - my $dbh = Bugzilla->dbh; - my $cgi = Bugzilla->cgi; my %keywordids; my $keywords = $dbh->selectall_arrayref(q{SELECT id, name @@ -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 ########################################################################### diff --git a/skins/standard/show_bug.css b/skins/standard/show_bug.css index 3e330a169..ec981d51c 100644 --- a/skins/standard/show_bug.css +++ b/skins/standard/show_bug.css @@ -54,9 +54,8 @@ table#flags { height: 1em; } -#duplicate_settings, #votes_container { +#duplicate_settings { white-space: nowrap; - } #bz_big_form_parts td { diff --git a/template/en/default/account/prefs/email.html.tmpl b/template/en/default/account/prefs/email.html.tmpl index a4d22db73..4b76f734d 100644 --- a/template/en/default/account/prefs/email.html.tmpl +++ b/template/en/default/account/prefs/email.html.tmpl @@ -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 %] diff --git a/template/en/default/admin/params/bugfields.html.tmpl b/template/en/default/admin/params/bugfields.html.tmpl index 794f925b7..58b08f615 100644 --- a/template/en/default/admin/params/bugfields.html.tmpl +++ b/template/en/default/admin/params/bugfields.html.tmpl @@ -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?", diff --git a/template/en/default/admin/products/create.html.tmpl b/template/en/default/admin/products/create.html.tmpl index f4a2161aa..045d3a34d 100644 --- a/template/en/default/admin/products/create.html.tmpl +++ b/template/en/default/admin/products/create.html.tmpl @@ -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 diff --git a/template/en/default/admin/products/edit-common.html.tmpl b/template/en/default/admin/products/edit-common.html.tmpl index 2c94402d6..4812707cd 100644 --- a/template/en/default/admin/products/edit-common.html.tmpl +++ b/template/en/default/admin/products/edit-common.html.tmpl @@ -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') %] diff --git a/template/en/default/admin/products/list.html.tmpl b/template/en/default/admin/products/list.html.tmpl index 6fd5240af..fb026aaa4 100644 --- a/template/en/default/admin/products/list.html.tmpl +++ b/template/en/default/admin/products/list.html.tmpl @@ -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 %] diff --git a/template/en/default/admin/products/updated.html.tmpl b/template/en/default/admin/products/updated.html.tmpl index 6e484ff34..4140bab62 100644 --- a/template/en/default/admin/products/updated.html.tmpl +++ b/template/en/default/admin/products/updated.html.tmpl @@ -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 %] diff --git a/template/en/default/admin/sanitycheck/messages.html.tmpl b/template/en/default/admin/sanitycheck/messages.html.tmpl index c3d5daacd..39e2258d0 100644 --- a/template/en/default/admin/sanitycheck/messages.html.tmpl +++ b/template/en/default/admin/sanitycheck/messages.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. diff --git a/template/en/default/admin/users/confirm-delete.html.tmpl b/template/en/default/admin/users/confirm-delete.html.tmpl index b61a99541..4711376b0 100644 --- a/template/en/default/admin/users/confirm-delete.html.tmpl +++ b/template/en/default/admin/users/confirm-delete.html.tmpl @@ -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> diff --git a/template/en/default/bug/edit.html.tmpl b/template/en/default/bug/edit.html.tmpl index 95376bb7d..b84aa8238 100644 --- a/template/en/default/bug/edit.html.tmpl +++ b/template/en/default/bug/edit.html.tmpl @@ -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> diff --git a/template/en/default/bug/format_comment.txt.tmpl b/template/en/default/bug/format_comment.txt.tmpl index 27b72a918..2d4a20303 100644 --- a/template/en/default/bug/format_comment.txt.tmpl +++ b/template/en/default/bug/format_comment.txt.tmpl @@ -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 %] diff --git a/template/en/default/bug/process/header.html.tmpl b/template/en/default/bug/process/header.html.tmpl index 79f0126d4..6b608b9ed 100644 --- a/template/en/default/bug/process/header.html.tmpl +++ b/template/en/default/bug/process/header.html.tmpl @@ -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 %] diff --git a/template/en/default/bug/process/results.html.tmpl b/template/en/default/bug/process/results.html.tmpl index d2adca8b8..7c1af42af 100644 --- a/template/en/default/bug/process/results.html.tmpl +++ b/template/en/default/bug/process/results.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> diff --git a/template/en/default/email/newchangedmail.txt.tmpl b/template/en/default/email/newchangedmail.txt.tmpl index 1bcc2e40d..7d30b890d 100644 --- a/template/en/default/email/newchangedmail.txt.tmpl +++ b/template/en/default/email/newchangedmail.txt.tmpl @@ -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") %] diff --git a/template/en/default/filterexceptions.pl b/template/en/default/filterexceptions.pl index a488f50ca..94604dc17 100644 --- a/template/en/default/filterexceptions.pl +++ b/template/en/default/filterexceptions.pl @@ -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', diff --git a/template/en/default/global/field-descs.none.tmpl b/template/en/default/global/field-descs.none.tmpl index 5012769ca..2c93c3d8a 100644 --- a/template/en/default/global/field-descs.none.tmpl +++ b/template/en/default/global/field-descs.none.tmpl @@ -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 diff --git a/template/en/default/global/reason-descs.none.tmpl b/template/en/default/global/reason-descs.none.tmpl new file mode 100644 index 000000000..4a39497b5 --- /dev/null +++ b/template/en/default/global/reason-descs.none.tmpl @@ -0,0 +1,40 @@ +[%# The contents of this file are subject to the Mozilla Public + # License Version 1.1 (the "License"); you may not use this file + # except in compliance with the License. You may obtain a copy of + # the License at http://www.mozilla.org/MPL/ + # + # Software distributed under the License is distributed on an "AS + # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or + # implied. See the License for the specific language governing + # rights and limitations under the License. + # + # The Original Code is the Bugzilla Bug Tracking System. + # + # The Initial Developer of the Original Code is Everything Solved, Inc. + # Portions created by the Initial Developer are Copyright (C) 2010 + # the Initial Developer. All Rights Reserved. + # + # Contributor(s): + # Max Kanat-Alexander <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') %] diff --git a/template/en/default/global/site-navigation.html.tmpl b/template/en/default/global/site-navigation.html.tmpl index bbf4f6862..60a8ddf96 100644 --- a/template/en/default/global/site-navigation.html.tmpl +++ b/template/en/default/global/site-navigation.html.tmpl @@ -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" diff --git a/template/en/default/global/user-error.html.tmpl b/template/en/default/global/user-error.html.tmpl index bdedc4172..d9872f1b0 100644 --- a/template/en/default/global/user-error.html.tmpl +++ b/template/en/default/global/user-error.html.tmpl @@ -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. diff --git a/template/en/default/list/list.rdf.tmpl b/template/en/default/list/list.rdf.tmpl index 99c06c1ee..d7879a694 100644 --- a/template/en/default/list/list.rdf.tmpl +++ b/template/en/default/list/list.rdf.tmpl @@ -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> diff --git a/template/en/default/search/form.html.tmpl b/template/en/default/search/form.html.tmpl index 2e2ae73d6..63ca03565 100644 --- a/template/en/default/search/form.html.tmpl +++ b/template/en/default/search/form.html.tmpl @@ -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> diff --git a/template/en/default/search/search-help.html.tmpl b/template/en/default/search/search-help.html.tmpl index 12e82ba5e..4dbf6652a 100644 --- a/template/en/default/search/search-help.html.tmpl +++ b/template/en/default/search/search-help.html.tmpl @@ -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." }, diff --git a/template/en/default/search/search-report-select.html.tmpl b/template/en/default/search/search-report-select.html.tmpl index de6478716..2ad779248 100644 --- a/template/en/default/search/search-report-select.html.tmpl +++ b/template/en/default/search/search-report-select.html.tmpl @@ -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> diff --git a/template/en/default/sidebar.xul.tmpl b/template/en/default/sidebar.xul.tmpl index 3df943e5c..b2fa092ea 100644 --- a/template/en/default/sidebar.xul.tmpl +++ b/template/en/default/sidebar.xul.tmpl @@ -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; -} |