diff options
Diffstat (limited to 'extensions/Voting/Extension.pm')
-rw-r--r-- | extensions/Voting/Extension.pm | 1367 |
1 files changed, 710 insertions, 657 deletions
diff --git a/extensions/Voting/Extension.pm b/extensions/Voting/Extension.pm index 198b065fa..523ee653d 100644 --- a/extensions/Voting/Extension.pm +++ b/extensions/Voting/Extension.pm @@ -23,15 +23,16 @@ use Bugzilla::Token; use List::Util qw(min sum); -use constant VERSION => BUGZILLA_VERSION; +use constant VERSION => BUGZILLA_VERSION; 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; +use constant REL_VOTER => 4; BEGIN { - *Bugzilla::Bug::user_votes = \&_bug_user_votes; + *Bugzilla::Bug::user_votes = \&_bug_user_votes; } ################ @@ -39,70 +40,71 @@ BEGIN { ################ BEGIN { - *Bugzilla::Bug::votes = \&votes; + *Bugzilla::Bug::votes = \&votes; } sub votes { - my $self = shift; - my $dbh = Bugzilla->dbh; + my $self = shift; + my $dbh = Bugzilla->dbh; - return $self->{votes} if exists $self->{votes}; + return $self->{votes} if exists $self->{votes}; - $self->{votes} = $dbh->selectrow_array('SELECT votes FROM bugs WHERE bug_id = ?', - undef, $self->id); - return $self->{votes}; + $self->{votes} + = $dbh->selectrow_array('SELECT votes FROM bugs WHERE bug_id = ?', + undef, $self->id); + return $self->{votes}; } 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'], - ], - }; + 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 4.0, 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}); - } + my $dbh = Bugzilla->dbh; + + # Note that before Bugzilla 4.0, 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}); + } } ########### @@ -110,102 +112,108 @@ sub install_update_db { ########### sub _bug_user_votes { - my ($self) = @_; - return $self->{'user_votes'} if exists $self->{'user_votes'}; - $self->{'user_votes'} = Bugzilla->dbh->selectrow_array( - "SELECT vote_count FROM votes WHERE bug_id = ? AND who = ?", - undef, $self->id, Bugzilla->user->id); - return $self->{'user_votes'}; + my ($self) = @_; + return $self->{'user_votes'} if exists $self->{'user_votes'}; + $self->{'user_votes'} + = Bugzilla->dbh->selectrow_array( + "SELECT vote_count FROM votes WHERE bug_id = ? AND who = ?", + undef, $self->id, Bugzilla->user->id); + return $self->{'user_votes'}; } 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)); - } + 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'); + 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)); - } + 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; - } + 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'}; - } + 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'}); - } + 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); - } + 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); - - foreach my $msg (@msgs) { - MessageToMTA($msg); - } + 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); + + foreach my $msg (@msgs) { + MessageToMTA($msg); } + } } ############# @@ -213,27 +221,28 @@ sub bug_end_of_update { ############# sub template_before_create { - my ($self, $args) = @_; - my $config = $args->{config}; - my $constants = $config->{VARIABLES}{constants}; - $constants->{REL_VOTER} = REL_VOTER; - $constants->{CMT_POPULAR_VOTES} = CMT_POPULAR_VOTES; - $constants->{DEFAULT_VOTES_PER_BUG} = DEFAULT_VOTES_PER_BUG; + my ($self, $args) = @_; + my $config = $args->{config}; + my $constants = $config->{VARIABLES}{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; - } - } + 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; + } + } } ########### @@ -241,19 +250,19 @@ sub template_before_process { ########### sub bugmail_recipients { - my ($self, $args) = @_; - my ($bug, $recipients) = @$args{qw(bug recipients)}; - my $dbh = Bugzilla->dbh; + 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); + 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'; + my ($self, $args) = @_; + my $relationships = $args->{relationships}; + $relationships->{+REL_VOTER} = 'Voter'; } ############### @@ -261,59 +270,59 @@ sub bugmail_relationships { ############### 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; + 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'); + 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'); } @@ -322,35 +331,36 @@ sub sanitycheck_repair { ############## sub _check_votesperuser { - return _check_votes(0, @_); + return _check_votes(0, @_); } sub _check_maxvotesperbug { - return _check_votes(DEFAULT_VOTES_PER_BUG, @_); + return _check_votes(DEFAULT_VOTES_PER_BUG, @_); } sub _check_votestoconfirm { - return _check_votes(0, @_); + 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) if defined $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; - } + my ($default, $invocant, $votes, $field) = @_; + + detaint_natural($votes) if defined $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]}); } - return $votes; + else { + $votes = $default; + } + } + return $votes; } ######### @@ -358,291 +368,322 @@ sub _check_votes { ######### 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); - } + 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 $input = Bugzilla->input_params; + my ($vars) = @_; + my $dbh = Bugzilla->dbh; + my $input = Bugzilla->input_params; - my $bug_id = $input->{bug_id}; - my $bug = Bugzilla::Bug->check($bug_id); + 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, + $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); + WHERE votes.bug_id = ?', {Slice => {}}, $bug->id + ); } sub _page_user { - my ($vars) = @_; - my $dbh = Bugzilla->dbh; - my $user = Bugzilla->user; - my $input = Bugzilla->input_params; + my ($vars) = @_; + my $dbh = Bugzilla->dbh; + my $user = Bugzilla->user; + my $input = Bugzilla->input_params; - my $action = $input->{action}; - if ($action and $action eq 'vote') { - _update_votes($vars); - } + 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. + # If a bug_id is given, and we're editing, we'll add it to the votes list. - my $bug_id = $input->{bug_id}; - $bug_id = $bug_id->[0] if ref($bug_id) eq 'ARRAY'; - my $bug = Bugzilla::Bug->check({ id => $bug_id, cache => 1 }) if $bug_id; - my $who_id = $input->{user_id} || $user->id; + my $bug_id = $input->{bug_id}; + $bug_id = $bug_id->[0] if ref($bug_id) eq 'ARRAY'; + my $bug = Bugzilla::Bug->check({id => $bug_id, cache => 1}) 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; + # Logged-out users must specify a user_id. + Bugzilla->login(LOGIN_REQUIRED) if !$who_id; - my $who = Bugzilla::User->check({ id => $who_id }); + my $who = Bugzilla::User->check({id => $who_id}); - my $canedit = $user->id == $who->id; + my $canedit = $user->id == $who->id; - $dbh->bz_start_transaction(); + $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)); - } + 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 (@products, @all_bug_ids); - my $vote_list = - $dbh->selectall_arrayref('SELECT votes.bug_id, votes.vote_count + # Read the votes data for this user for each product. + foreach my $product (@{$user->get_selectable_products}) { + next unless ($product->{votesperuser} > 0); + + my $vote_list = $dbh->selectall_arrayref( + 'SELECT votes.bug_id, votes.vote_count FROM votes INNER JOIN bugs ON votes.bug_id = bugs.bug_id WHERE votes.who = ? - AND bugs.product_id = ?', - undef, ($who->id, $product->id)); - - my %votes = map { $_->[0] => $_->[1] } @$vote_list; - my @bug_ids = sort keys %votes; - # Exclude bugs that the user can no longer see. - @bug_ids = @{ $user->visible_bugs(\@bug_ids) }; - next unless scalar @bug_ids; - - push(@all_bug_ids, @bug_ids); - my @bugs = @{ Bugzilla::Bug->new_from_list(\@bug_ids) }; - $_->{count} = $votes{$_->id} foreach @bugs; - # We include votes from bugs that the user can no longer see. - my $total = sum(values %votes) || 0; - - my $onevoteonly = 0; - $onevoteonly = 1 if (min($product->{votesperuser}, - $product->{maxvotesperbug}) == 1); - - push(@products, { name => $product->name, - bugs => \@bugs, - bug_ids => \@bug_ids, - onevoteonly => $onevoteonly, - total => $total, - maxvotes => $product->{votesperuser}, - maxperbug => $product->{maxvotesperbug} }); - } - - if ($canedit && $bug) { - $dbh->do('DELETE FROM votes WHERE vote_count = 0 AND who = ?', - undef, $who->id); - } - $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; + AND bugs.product_id = ?', undef, + ($who->id, $product->id) + ); + + my %votes = map { $_->[0] => $_->[1] } @$vote_list; + my @bug_ids = sort keys %votes; + + # Exclude bugs that the user can no longer see. + @bug_ids = @{$user->visible_bugs(\@bug_ids)}; + next unless scalar @bug_ids; + + push(@all_bug_ids, @bug_ids); + my @bugs = @{Bugzilla::Bug->new_from_list(\@bug_ids)}; + $_->{count} = $votes{$_->id} foreach @bugs; + + # We include votes from bugs that the user can no longer see. + my $total = sum(values %votes) || 0; + + my $onevoteonly = 0; + $onevoteonly = 1 + if (min($product->{votesperuser}, $product->{maxvotesperbug}) == 1); + + push( + @products, + { + name => $product->name, + bugs => \@bugs, + bug_ids => \@bug_ids, + onevoteonly => $onevoteonly, + total => $total, + maxvotes => $product->{votesperuser}, + maxperbug => $product->{maxvotesperbug} + } + ); + } + + if ($canedit && $bug) { + $dbh->do('DELETE FROM votes WHERE vote_count = 0 AND who = ?', undef, $who->id); + } + $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; - my (%bugs, %votes); - - # 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; + 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; + my (%bugs, %votes); + + # 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; + } + } + else { + $user->visible_bugs(\@buglist); + my $bugs_obj = Bugzilla::Bug->new_from_list(\@buglist); + $bugs{$_->id} = $_ foreach @$bugs_obj; + } + + # Call check_is_visible() on each bug to make sure it is an existing bug + # that the user is authorized to access, and make sure the number of votes + # submitted is also an integer. + foreach my $id (@buglist) { + my $bug = $bugs{$id} + or ThrowUserError('bug_id_does_not_exist', {bug_id => $id}); + $bug->check_is_visible; + $id = $bug->id; + $votes{$id} = $input->{$id}; + detaint_natural($votes{$id}) || ThrowUserError("voting_must_be_nonnegative"); + } + + my $token = $cgi->param('token'); + check_hash_token($token, ['vote']); + + ############################################################################ + # 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 @buglist) { + my (%prodcount, %products); + foreach my $bug_id (keys %bugs) { + my $bug = $bugs{$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} } - } - else { - $user->visible_bugs(\@buglist); - my $bugs_obj = Bugzilla::Bug->new_from_list(\@buglist); - $bugs{$_->id} = $_ foreach @$bugs_obj; + ); } - # Call check_is_visible() on each bug to make sure it is an existing bug - # that the user is authorized to access, and make sure the number of votes - # submitted is also an integer. - foreach my $id (@buglist) { - my $bug = $bugs{$id} - or ThrowUserError('bug_id_does_not_exist', { bug_id => $id }); - $bug->check_is_visible; - $id = $bug->id; - $votes{$id} = $input->{$id}; - detaint_natural($votes{$id}) - || ThrowUserError("voting_must_be_nonnegative"); - } - - my $token = $cgi->param('token'); - check_hash_token($token, ['vote']); - - ############################################################################ - # 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 @buglist) { - my (%prodcount, %products); - foreach my $bug_id (keys %bugs) { - my $bug = $bugs{$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}}); + # 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. - $dbh->bz_start_transaction(); + # Update the user's votes in the database. + $dbh->bz_start_transaction(); - my $old_list = $dbh->selectall_arrayref('SELECT bug_id, vote_count FROM votes - WHERE who = ?', undef, $who); + my $old_list = $dbh->selectall_arrayref( + 'SELECT bug_id, vote_count FROM votes + WHERE who = ?', undef, $who + ); - my %old_votes = map { $_->[0] => $_->[1] } @$old_list; + my %old_votes = map { $_->[0] => $_->[1] } @$old_list; - my $sth_insertVotes = $dbh->prepare('INSERT INTO votes (who, bug_id, vote_count) - VALUES (?, ?, ?)'); - my $sth_updateVotes = $dbh->prepare('UPDATE votes SET vote_count = ? - WHERE bug_id = ? AND who = ?'); + my $sth_insertVotes = $dbh->prepare( + 'INSERT INTO votes (who, bug_id, vote_count) + VALUES (?, ?, ?)' + ); + my $sth_updateVotes = $dbh->prepare( + 'UPDATE votes SET vote_count = ? + WHERE bug_id = ? AND who = ?' + ); - my %affected = map { $_ => 1 } (@buglist, keys %old_votes); - my @deleted_votes; - - foreach my $id (keys %affected) { - if (!$votes{$id}) { - push(@deleted_votes, $id); - next; - } - if ($votes{$id} == ($old_votes{$id} || 0)) { - delete $affected{$id}; - next; - } - # We use 'defined' in case 0 was accidentally stored in the DB. - if (defined $old_votes{$id}) { - $sth_updateVotes->execute($votes{$id}, $id, $who); - } - else { - $sth_insertVotes->execute($who, $id, $votes{$id}); - } - } + my %affected = map { $_ => 1 } (@buglist, keys %old_votes); + my @deleted_votes; - if (@deleted_votes) { - $dbh->do('DELETE FROM votes WHERE who = ? AND ' . - $dbh->sql_in('bug_id', \@deleted_votes), undef, $who); + foreach my $id (keys %affected) { + if (!$votes{$id}) { + push(@deleted_votes, $id); + next; } - - # Update the cached values in the bugs table - my @updated_bugs = (); - - my $sth_getVotes = $dbh->prepare("SELECT SUM(vote_count) FROM votes - WHERE bug_id = ?"); - - $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); - $bugs{$id}->{votes} = $v if $bugs{$id}; - my $confirmed = _confirm_if_vote_confirmed($bugs{$id} || $id); - push (@updated_bugs, $id) if $confirmed; + if ($votes{$id} == ($old_votes{$id} || 0)) { + delete $affected{$id}; + next; } - $dbh->bz_commit_transaction(); - - print $cgi->header() if scalar @updated_bugs; - $vars->{'type'} = "votes"; - $vars->{'title_tag'} = 'change_votes'; - foreach my $bug_id (@updated_bugs) { - $vars->{'id'} = $bug_id; - $vars->{'sent_bugmail'} = - Bugzilla::BugMail::Send($bug_id, { 'changer' => $user }); - - $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; + # We use 'defined' in case 0 was accidentally stored in the DB. + if (defined $old_votes{$id}) { + $sth_updateVotes->execute($votes{$id}, $id, $who); } - $vars->{'votes_recorded'} = 1; + else { + $sth_insertVotes->execute($who, $id, $votes{$id}); + } + } + + if (@deleted_votes) { + $dbh->do( + 'DELETE FROM votes WHERE who = ? AND ' + . $dbh->sql_in('bug_id', \@deleted_votes), + undef, $who + ); + } + + # Update the cached values in the bugs table + my @updated_bugs = (); + + my $sth_getVotes = $dbh->prepare( + "SELECT SUM(vote_count) FROM votes + WHERE bug_id = ?" + ); + + $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); + $bugs{$id}->{votes} = $v if $bugs{$id}; + my $confirmed = _confirm_if_vote_confirmed($bugs{$id} || $id); + push(@updated_bugs, $id) if $confirmed; + } + + $dbh->bz_commit_transaction(); + + print $cgi->header() if scalar @updated_bugs; + $vars->{'type'} = "votes"; + $vars->{'title_tag'} = 'change_votes'; + foreach my $bug_id (@updated_bugs) { + $vars->{'id'} = $bug_id; + $vars->{'sent_bugmail'} + = Bugzilla::BugMail::Send($bug_id, {'changer' => $user}); + + $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; } ###################### @@ -650,234 +691,246 @@ sub _update_votes { ###################### sub _modify_bug_votes { - my ($product, $changes) = @_; - my $dbh = Bugzilla->dbh; - my @msgs; + 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 + # 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})); + AND votes.vote_count > ?', undef, + ($product->id, $product->{maxvotesperbug}) + ); + + foreach my $vote (@$votes) { + my ($who, $id) = (@$vote); - 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); + # 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}); - } + push(@toomanyvotes_list, {id => $id, name => $name}); } + } - $changes->{'_too_many_votes'} = \@toomanyvotes_list; + $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(). + # 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 + 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); + 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 %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 + 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, $product); - 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); - } - # And send out emails about changed bugs - foreach my $bug_id (@updated_bugs) { - my $sent_bugmail = Bugzilla::BugMail::Send( - $bug_id, { changer => Bugzilla->user }); - $changes->{'_confirmed_bugs_sent_bugmail'}->{$bug_id} = $sent_bugmail; - } + 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, $product); + 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); + } + + # And send out emails about changed bugs + foreach my $bug_id (@updated_bugs) { + my $sent_bugmail + = Bugzilla::BugMail::Send($bug_id, {changer => Bugzilla->user}); + $changes->{'_confirmed_bugs_sent_bugmail'}->{$bug_id} = $sent_bugmail; + } } # 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->setting('lang')); - - my $msg; - $template->process("voting/votes-removed.txt.tmpl", $vars, \$msg); - push(@messages, $msg); - } - - 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; + 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->setting('lang')); + + my $msg; + $template->process("voting/votes-removed.txt.tmpl", $vars, \$msg); + push(@messages, $msg); + } + + 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, $product) = @_; - my $bug = ref $id ? $id : new Bugzilla::Bug({ id => $id, cache => 1 }); - $product ||= $bug->product_obj; - - my $ret = 0; - if (!$bug->everconfirmed - and $product->{votestoconfirm} - and $bug->votes >= $product->{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('voting_no_open_bug_status') unless $new_status; - - # We cannot call $bug->set_bug_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; + my ($id, $product) = @_; + my $bug = ref $id ? $id : new Bugzilla::Bug({id => $id, cache => 1}); + $product ||= $bug->product_obj; + + my $ret = 0; + if ( !$bug->everconfirmed + and $product->{votestoconfirm} + and $bug->votes >= $product->{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; } - $bug->update(); + } + ThrowCodeError('voting_no_open_bug_status') unless $new_status; - $ret = 1; + # We cannot call $bug->set_bug_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. } - return $ret; + 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; } |