diff options
author | Robin H. Johnson <robbat2@gentoo.org> | 2019-11-30 23:12:11 -0800 |
---|---|---|
committer | Robin H. Johnson <robbat2@gentoo.org> | 2019-12-01 14:53:51 -0800 |
commit | 70780e40e5586c6882e33dd65a3dc3f31031a321 (patch) | |
tree | 51fc3608bd44e7b92d07a976ca3112fd5d87d843 /extensions | |
parent | Merge commit '3395d78cc8b0bd660e56f73a2689d495f2a22628' into bugstest (diff) | |
download | bugzilla-70780e40e5586c6882e33dd65a3dc3f31031a321.tar.gz bugzilla-70780e40e5586c6882e33dd65a3dc3f31031a321.tar.bz2 bugzilla-70780e40e5586c6882e33dd65a3dc3f31031a321.zip |
Gentoo-local version of 7f3a749d7bd78a3e4aee163f562d7e95b0954b44 w/ Perl-Tidy-20180220
Reformat all code using Perl-Tidy v20180220 and .perltidyrc from
matching upstream 7f3a749d7bd78a3e4aee163f562d7e95b0954b44 commit.
Signed-off-by: Robin H. Johnson <robbat2@gentoo.org>
Diffstat (limited to 'extensions')
31 files changed, 2593 insertions, 2520 deletions
diff --git a/extensions/BmpConvert/Config.pm b/extensions/BmpConvert/Config.pm index 4984f19a9..ed2df6197 100644 --- a/extensions/BmpConvert/Config.pm +++ b/extensions/BmpConvert/Config.pm @@ -12,12 +12,7 @@ use strict; use warnings; use constant NAME => 'BmpConvert'; -use constant REQUIRED_MODULES => [ - { - package => 'PerlMagick', - module => 'Image::Magick', - version => 0, - }, -]; +use constant REQUIRED_MODULES => + [{package => 'PerlMagick', module => 'Image::Magick', version => 0,},]; __PACKAGE__->NAME; diff --git a/extensions/BmpConvert/Extension.pm b/extensions/BmpConvert/Extension.pm index b8201f106..d9e3d0ef4 100644 --- a/extensions/BmpConvert/Extension.pm +++ b/extensions/BmpConvert/Extension.pm @@ -18,29 +18,30 @@ use Image::Magick; our $VERSION = '1.0'; sub attachment_process_data { - my ($self, $args) = @_; - return unless $args->{attributes}->{mimetype} eq 'image/bmp'; - - my $data = ${$args->{data}}; - my $img = Image::Magick->new(magick => 'bmp'); - - # $data is a filehandle. - if (ref $data) { - $img->Read(file => \*$data); - $img->set(magick => 'png'); - $img->Write(file => \*$data); - } - # $data is a blob. - else { - $img->BlobToImage($data); - $img->set(magick => 'png'); - $data = $img->ImageToBlob(); - } - undef $img; - - ${$args->{data}} = $data; - $args->{attributes}->{mimetype} = 'image/png'; - $args->{attributes}->{filename} =~ s/^(.+)\.bmp$/$1.png/i; + my ($self, $args) = @_; + return unless $args->{attributes}->{mimetype} eq 'image/bmp'; + + my $data = ${$args->{data}}; + my $img = Image::Magick->new(magick => 'bmp'); + + # $data is a filehandle. + if (ref $data) { + $img->Read(file => \*$data); + $img->set(magick => 'png'); + $img->Write(file => \*$data); + } + + # $data is a blob. + else { + $img->BlobToImage($data); + $img->set(magick => 'png'); + $data = $img->ImageToBlob(); + } + undef $img; + + ${$args->{data}} = $data; + $args->{attributes}->{mimetype} = 'image/png'; + $args->{attributes}->{filename} =~ s/^(.+)\.bmp$/$1.png/i; } - __PACKAGE__->NAME; +__PACKAGE__->NAME; diff --git a/extensions/Example/Config.pm b/extensions/Example/Config.pm index e7782ef6c..696da2de9 100644 --- a/extensions/Example/Config.pm +++ b/extensions/Example/Config.pm @@ -12,21 +12,16 @@ use strict; use warnings; use constant NAME => 'Example'; -use constant REQUIRED_MODULES => [ - { - package => 'Data-Dumper', - module => 'Data::Dumper', - version => 0, - }, -]; +use constant REQUIRED_MODULES => + [{package => 'Data-Dumper', module => 'Data::Dumper', version => 0,},]; use constant OPTIONAL_MODULES => [ - { - package => 'Acme', - module => 'Acme', - version => 1.11, - feature => ['example_acme'], - }, + { + package => 'Acme', + module => 'Acme', + version => 1.11, + feature => ['example_acme'], + }, ]; __PACKAGE__->NAME; diff --git a/extensions/Example/Extension.pm b/extensions/Example/Extension.pm index c4fabe656..d76c71c06 100644 --- a/extensions/Example/Extension.pm +++ b/extensions/Example/Extension.pm @@ -35,357 +35,374 @@ use constant REL_EXAMPLE => -127; our $VERSION = '1.0'; sub user_can_administer { - my ($self, $args) = @_; - my $can_administer = $args->{can_administer}; + my ($self, $args) = @_; + my $can_administer = $args->{can_administer}; - # If you add an option to the admin pages (e.g. by using the Hooks in - # template/en/default/admin/admin.html.tmpl), you may want to allow - # users in another group view admin.cgi - #if (Bugzilla->user->in_group('other_group')) { - # $$can_administer = 1; - #} + # If you add an option to the admin pages (e.g. by using the Hooks in + # template/en/default/admin/admin.html.tmpl), you may want to allow + # users in another group view admin.cgi + #if (Bugzilla->user->in_group('other_group')) { + # $$can_administer = 1; + #} } sub admin_editusers_action { - my ($self, $args) = @_; - my ($vars, $action, $user) = @$args{qw(vars action user)}; - my $template = Bugzilla->template; - - if ($action eq 'my_action') { - # Allow to restrict the search to any group the user is allowed to bless. - $vars->{'restrictablegroups'} = $user->bless_groups(); - $template->process('admin/users/search.html.tmpl', $vars) - || ThrowTemplateError($template->error()); - exit; - } + my ($self, $args) = @_; + my ($vars, $action, $user) = @$args{qw(vars action user)}; + my $template = Bugzilla->template; + + if ($action eq 'my_action') { + + # Allow to restrict the search to any group the user is allowed to bless. + $vars->{'restrictablegroups'} = $user->bless_groups(); + $template->process('admin/users/search.html.tmpl', $vars) + || ThrowTemplateError($template->error()); + exit; + } } sub attachment_process_data { - my ($self, $args) = @_; - my $type = $args->{attributes}->{mimetype}; - my $filename = $args->{attributes}->{filename}; - - # Make sure images have the correct extension. - # Uncomment the two lines below to make this check effective. - if ($type =~ /^image\/(\w+)$/) { - my $format = $1; - if ($filename =~ /^(.+)(:?\.[^\.]+)$/) { - my $name = $1; - #$args->{attributes}->{filename} = "${name}.$format"; - } - else { - # The file has no extension. We append it. - #$args->{attributes}->{filename} .= ".$format"; - } + my ($self, $args) = @_; + my $type = $args->{attributes}->{mimetype}; + my $filename = $args->{attributes}->{filename}; + + # Make sure images have the correct extension. + # Uncomment the two lines below to make this check effective. + if ($type =~ /^image\/(\w+)$/) { + my $format = $1; + if ($filename =~ /^(.+)(:?\.[^\.]+)$/) { + my $name = $1; + + #$args->{attributes}->{filename} = "${name}.$format"; + } + else { + # The file has no extension. We append it. + #$args->{attributes}->{filename} .= ".$format"; } + } } sub auth_login_methods { - my ($self, $args) = @_; - my $modules = $args->{modules}; - if (exists $modules->{Example}) { - $modules->{Example} = 'Bugzilla/Extension/Example/Auth/Login.pm'; - } + my ($self, $args) = @_; + my $modules = $args->{modules}; + if (exists $modules->{Example}) { + $modules->{Example} = 'Bugzilla/Extension/Example/Auth/Login.pm'; + } } sub auth_verify_methods { - my ($self, $args) = @_; - my $modules = $args->{modules}; - if (exists $modules->{Example}) { - $modules->{Example} = 'Bugzilla/Extension/Example/Auth/Verify.pm'; - } + my ($self, $args) = @_; + my $modules = $args->{modules}; + if (exists $modules->{Example}) { + $modules->{Example} = 'Bugzilla/Extension/Example/Auth/Verify.pm'; + } } sub bug_check_can_change_field { - my ($self, $args) = @_; - - my ($bug, $field, $new_value, $old_value, $priv_results) - = @$args{qw(bug field new_value old_value priv_results)}; - - my $user = Bugzilla->user; - - # Disallow a bug from being reopened if currently closed unless user - # is in 'admin' group - if ($field eq 'bug_status' && $bug->product_obj->name eq 'Example') { - if (!is_open_state($old_value) && is_open_state($new_value) - && !$user->in_group('admin')) - { - push(@$priv_results, PRIVILEGES_REQUIRED_EMPOWERED); - return; - } - } + my ($self, $args) = @_; - # Disallow a bug's keywords from being edited unless user is the - # reporter of the bug - if ($field eq 'keywords' && $bug->product_obj->name eq 'Example' - && $user->login ne $bug->reporter->login) - { - push(@$priv_results, PRIVILEGES_REQUIRED_REPORTER); - return; - } + my ($bug, $field, $new_value, $old_value, $priv_results) + = @$args{qw(bug field new_value old_value priv_results)}; + + my $user = Bugzilla->user; - # Allow updating of priority even if user cannot normally edit the bug - # and they are in group 'engineering' - if ($field eq 'priority' && $bug->product_obj->name eq 'Example' - && $user->in_group('engineering')) + # Disallow a bug from being reopened if currently closed unless user + # is in 'admin' group + if ($field eq 'bug_status' && $bug->product_obj->name eq 'Example') { + if (!is_open_state($old_value) + && is_open_state($new_value) + && !$user->in_group('admin')) { - push(@$priv_results, PRIVILEGES_REQUIRED_NONE); - return; + push(@$priv_results, PRIVILEGES_REQUIRED_EMPOWERED); + return; } + } + + # Disallow a bug's keywords from being edited unless user is the + # reporter of the bug + if ( $field eq 'keywords' + && $bug->product_obj->name eq 'Example' + && $user->login ne $bug->reporter->login) + { + push(@$priv_results, PRIVILEGES_REQUIRED_REPORTER); + return; + } + + # Allow updating of priority even if user cannot normally edit the bug + # and they are in group 'engineering' + if ( $field eq 'priority' + && $bug->product_obj->name eq 'Example' + && $user->in_group('engineering')) + { + push(@$priv_results, PRIVILEGES_REQUIRED_NONE); + return; + } } sub bug_columns { - my ($self, $args) = @_; - my $columns = $args->{'columns'}; - push (@$columns, "delta_ts AS example") + my ($self, $args) = @_; + my $columns = $args->{'columns'}; + push(@$columns, "delta_ts AS example"); } sub bug_end_of_create { - my ($self, $args) = @_; + my ($self, $args) = @_; + + # This code doesn't actually *do* anything, it's just here to show you + # how to use this hook. + my $bug = $args->{'bug'}; + my $timestamp = $args->{'timestamp'}; + + my $bug_id = $bug->id; - # This code doesn't actually *do* anything, it's just here to show you - # how to use this hook. - my $bug = $args->{'bug'}; - my $timestamp = $args->{'timestamp'}; - - my $bug_id = $bug->id; - # Uncomment this line to see a line in your webserver's error log whenever - # you file a bug. - # warn "Bug $bug_id has been filed!"; + # Uncomment this line to see a line in your webserver's error log whenever + # you file a bug. + # warn "Bug $bug_id has been filed!"; } sub bug_end_of_create_validators { - my ($self, $args) = @_; - - # This code doesn't actually *do* anything, it's just here to show you - # how to use this hook. - my $bug_params = $args->{'params'}; - - # Uncomment this line below to see a line in your webserver's error log - # containing all validated bug field values every time you file a bug. - # warn Dumper($bug_params); - - # This would remove all ccs from the bug, preventing ANY ccs from being - # added on bug creation. - # $bug_params->{cc} = []; -} + my ($self, $args) = @_; -sub bug_start_of_update { - my ($self, $args) = @_; + # This code doesn't actually *do* anything, it's just here to show you + # how to use this hook. + my $bug_params = $args->{'params'}; - # This code doesn't actually *do* anything, it's just here to show you - # how to use this hook. - my ($bug, $old_bug, $timestamp, $changes) = - @$args{qw(bug old_bug timestamp changes)}; + # Uncomment this line below to see a line in your webserver's error log + # containing all validated bug field values every time you file a bug. + # warn Dumper($bug_params); - foreach my $field (keys %$changes) { - my $used_to_be = $changes->{$field}->[0]; - my $now_it_is = $changes->{$field}->[1]; - } + # This would remove all ccs from the bug, preventing ANY ccs from being + # added on bug creation. + # $bug_params->{cc} = []; +} - my $old_summary = $old_bug->short_desc; - - my $status_message; - if (my $status_change = $changes->{'bug_status'}) { - my $old_status = new Bugzilla::Status({ name => $status_change->[0] }); - my $new_status = new Bugzilla::Status({ name => $status_change->[1] }); - if ($new_status->is_open && !$old_status->is_open) { - $status_message = "Bug re-opened!"; - } - if (!$new_status->is_open && $old_status->is_open) { - $status_message = "Bug closed!"; - } +sub bug_start_of_update { + my ($self, $args) = @_; + + # This code doesn't actually *do* anything, it's just here to show you + # how to use this hook. + my ($bug, $old_bug, $timestamp, $changes) + = @$args{qw(bug old_bug timestamp changes)}; + + foreach my $field (keys %$changes) { + my $used_to_be = $changes->{$field}->[0]; + my $now_it_is = $changes->{$field}->[1]; + } + + my $old_summary = $old_bug->short_desc; + + my $status_message; + if (my $status_change = $changes->{'bug_status'}) { + my $old_status = new Bugzilla::Status({name => $status_change->[0]}); + my $new_status = new Bugzilla::Status({name => $status_change->[1]}); + if ($new_status->is_open && !$old_status->is_open) { + $status_message = "Bug re-opened!"; + } + if (!$new_status->is_open && $old_status->is_open) { + $status_message = "Bug closed!"; } + } + + my $bug_id = $bug->id; + my $num_changes = scalar keys %$changes; + my $result = "There were $num_changes changes to fields on bug $bug_id" + . " at $timestamp."; - my $bug_id = $bug->id; - my $num_changes = scalar keys %$changes; - my $result = "There were $num_changes changes to fields on bug $bug_id" - . " at $timestamp."; - # Uncomment this line to see $result in your webserver's error log whenever - # you update a bug. - # warn $result; + # Uncomment this line to see $result in your webserver's error log whenever + # you update a bug. + # warn $result; } sub bug_end_of_update { - my ($self, $args) = @_; - - # This code doesn't actually *do* anything, it's just here to show you - # how to use this hook. - my ($bug, $old_bug, $timestamp, $changes) = - @$args{qw(bug old_bug timestamp changes)}; - - foreach my $field (keys %$changes) { - my $used_to_be = $changes->{$field}->[0]; - my $now_it_is = $changes->{$field}->[1]; + my ($self, $args) = @_; + + # This code doesn't actually *do* anything, it's just here to show you + # how to use this hook. + my ($bug, $old_bug, $timestamp, $changes) + = @$args{qw(bug old_bug timestamp changes)}; + + foreach my $field (keys %$changes) { + my $used_to_be = $changes->{$field}->[0]; + my $now_it_is = $changes->{$field}->[1]; + } + + my $old_summary = $old_bug->short_desc; + + my $status_message; + if (my $status_change = $changes->{'bug_status'}) { + my $old_status = new Bugzilla::Status({name => $status_change->[0]}); + my $new_status = new Bugzilla::Status({name => $status_change->[1]}); + if ($new_status->is_open && !$old_status->is_open) { + $status_message = "Bug re-opened!"; } - - my $old_summary = $old_bug->short_desc; - - my $status_message; - if (my $status_change = $changes->{'bug_status'}) { - my $old_status = new Bugzilla::Status({ name => $status_change->[0] }); - my $new_status = new Bugzilla::Status({ name => $status_change->[1] }); - if ($new_status->is_open && !$old_status->is_open) { - $status_message = "Bug re-opened!"; - } - if (!$new_status->is_open && $old_status->is_open) { - $status_message = "Bug closed!"; - } + if (!$new_status->is_open && $old_status->is_open) { + $status_message = "Bug closed!"; } - - my $bug_id = $bug->id; - my $num_changes = scalar keys %$changes; - my $result = "There were $num_changes changes to fields on bug $bug_id" - . " at $timestamp."; - # Uncomment this line to see $result in your webserver's error log whenever - # you update a bug. - # warn $result; + } + + my $bug_id = $bug->id; + my $num_changes = scalar keys %$changes; + my $result = "There were $num_changes changes to fields on bug $bug_id" + . " at $timestamp."; + + # Uncomment this line to see $result in your webserver's error log whenever + # you update a bug. + # warn $result; } sub bug_fields { - my ($self, $args) = @_; + my ($self, $args) = @_; - my $fields = $args->{'fields'}; - push (@$fields, "example") + my $fields = $args->{'fields'}; + push(@$fields, "example"); } sub bug_format_comment { - my ($self, $args) = @_; - - # This replaces every occurrence of the word "foo" with the word - # "bar" - - my $regexes = $args->{'regexes'}; - push(@$regexes, { match => qr/\bfoo\b/, replace => 'bar' }); - - # And this links every occurrence of the word "bar" to example.com, - # but it won't affect "foo"s that have already been turned into "bar" - # above (because each regex is run in order, and later regexes don't modify - # earlier matches, due to some cleverness in Bugzilla's internals). - # - # For example, the phrase "foo bar" would become: - # bar <a href="http://example.com/bar">bar</a> - my $bar_match = qr/\b(bar)\b/; - push(@$regexes, { match => $bar_match, replace => \&_replace_bar }); + my ($self, $args) = @_; + + # This replaces every occurrence of the word "foo" with the word + # "bar" + + my $regexes = $args->{'regexes'}; + push(@$regexes, {match => qr/\bfoo\b/, replace => 'bar'}); + + # And this links every occurrence of the word "bar" to example.com, + # but it won't affect "foo"s that have already been turned into "bar" + # above (because each regex is run in order, and later regexes don't modify + # earlier matches, due to some cleverness in Bugzilla's internals). + # + # For example, the phrase "foo bar" would become: + # bar <a href="http://example.com/bar">bar</a> + my $bar_match = qr/\b(bar)\b/; + push(@$regexes, {match => $bar_match, replace => \&_replace_bar}); } # Used by bug_format_comment--see its code for an explanation. sub _replace_bar { - my $args = shift; - # $match is the first parentheses match in the $bar_match regex - # in bug-format_comment.pl. We get up to 10 regex matches as - # arguments to this function. - my $match = $args->{matches}->[0]; - # Remember, you have to HTML-escape any data that you are returning! - $match = html_quote($match); - return qq{<a href="http://example.com/">$match</a>}; -}; + my $args = shift; + + # $match is the first parentheses match in the $bar_match regex + # in bug-format_comment.pl. We get up to 10 regex matches as + # arguments to this function. + my $match = $args->{matches}->[0]; + + # Remember, you have to HTML-escape any data that you are returning! + $match = html_quote($match); + return qq{<a href="http://example.com/">$match</a>}; +} sub buglist_columns { - my ($self, $args) = @_; - - my $columns = $args->{'columns'}; - $columns->{'example'} = { 'name' => 'bugs.delta_ts' , 'title' => 'Example' }; - $columns->{'product_desc'} = { 'name' => 'prod_desc.description', - 'title' => 'Product Description' }; + my ($self, $args) = @_; + + my $columns = $args->{'columns'}; + $columns->{'example'} = {'name' => 'bugs.delta_ts', 'title' => 'Example'}; + $columns->{'product_desc'} + = {'name' => 'prod_desc.description', 'title' => 'Product Description'}; } sub buglist_column_joins { - my ($self, $args) = @_; - my $joins = $args->{'column_joins'}; + my ($self, $args) = @_; + my $joins = $args->{'column_joins'}; - # This column is added using the "buglist_columns" hook - $joins->{'product_desc'} = { - from => 'product_id', - to => 'id', - table => 'products', - as => 'prod_desc', - join => 'INNER', - }; + # This column is added using the "buglist_columns" hook + $joins->{'product_desc'} = { + from => 'product_id', + to => 'id', + table => 'products', + as => 'prod_desc', + join => 'INNER', + }; } sub search_operator_field_override { - my ($self, $args) = @_; - - my $operators = $args->{'operators'}; + my ($self, $args) = @_; - my $original = $operators->{component}->{_non_changed}; - $operators->{component} = { - _non_changed => sub { _component_nonchanged($original, @_) } - }; + my $operators = $args->{'operators'}; + + my $original = $operators->{component}->{_non_changed}; + $operators->{component} = { + _non_changed => sub { _component_nonchanged($original, @_) } + }; } sub _component_nonchanged { - my $original = shift; - my ($invocant, $args) = @_; + my $original = shift; + my ($invocant, $args) = @_; + + $invocant->$original($args); - $invocant->$original($args); - # Actually, it does not change anything in the result, - # just an example. - $args->{term} = $args->{term} . " OR 1=2"; + # Actually, it does not change anything in the result, + # just an example. + $args->{term} = $args->{term} . " OR 1=2"; } sub bugmail_recipients { - my ($self, $args) = @_; - my $recipients = $args->{recipients}; - my $bug = $args->{bug}; - - my $user = - new Bugzilla::User({ name => Bugzilla->params->{'maintainer'} }); - - if ($bug->id == 1) { - # Uncomment the line below to add the maintainer to the recipients - # list of every bugmail from bug 1 as though that the maintainer - # were on the CC list. - #$recipients->{$user->id}->{+REL_CC} = 1; - - # And this line adds the maintainer as though they had the - # "REL_EXAMPLE" relationship from the bugmail_relationships hook below. - #$recipients->{$user->id}->{+REL_EXAMPLE} = 1; - } + my ($self, $args) = @_; + my $recipients = $args->{recipients}; + my $bug = $args->{bug}; + + my $user = new Bugzilla::User({name => Bugzilla->params->{'maintainer'}}); + + if ($bug->id == 1) { + + # Uncomment the line below to add the maintainer to the recipients + # list of every bugmail from bug 1 as though that the maintainer + # were on the CC list. + #$recipients->{$user->id}->{+REL_CC} = 1; + + # And this line adds the maintainer as though they had the + # "REL_EXAMPLE" relationship from the bugmail_relationships hook below. + #$recipients->{$user->id}->{+REL_EXAMPLE} = 1; + } } sub bugmail_relationships { - my ($self, $args) = @_; - my $relationships = $args->{relationships}; - $relationships->{+REL_EXAMPLE} = 'Example'; + my ($self, $args) = @_; + my $relationships = $args->{relationships}; + $relationships->{+REL_EXAMPLE} = 'Example'; } sub cgi_headers { - my ($self, $args) = @_; - my $headers = $args->{'headers'}; + my ($self, $args) = @_; + my $headers = $args->{'headers'}; - $headers->{'-x_test_header'} = "Test header from Example extension"; + $headers->{'-x_test_header'} = "Test header from Example extension"; } sub config_add_panels { - my ($self, $args) = @_; - - my $modules = $args->{panel_modules}; - $modules->{Example} = "Bugzilla::Extension::Example::Config"; + my ($self, $args) = @_; + + my $modules = $args->{panel_modules}; + $modules->{Example} = "Bugzilla::Extension::Example::Config"; } sub config_modify_panels { - my ($self, $args) = @_; - - my $panels = $args->{panels}; - - # Add the "Example" auth methods. - my $auth_params = $panels->{'auth'}->{params}; - my ($info_class) = grep($_->{name} eq 'user_info_class', @$auth_params); - my ($verify_class) = grep($_->{name} eq 'user_verify_class', @$auth_params); + my ($self, $args) = @_; + + my $panels = $args->{panels}; - push(@{ $info_class->{choices} }, 'CGI,Example'); - push(@{ $verify_class->{choices} }, 'Example'); + # Add the "Example" auth methods. + my $auth_params = $panels->{'auth'}->{params}; + my ($info_class) = grep($_->{name} eq 'user_info_class', @$auth_params); + my ($verify_class) = grep($_->{name} eq 'user_verify_class', @$auth_params); - push(@$auth_params, { name => 'param_example', - type => 't', - default => 0, - checker => \&check_numeric }); + push(@{$info_class->{choices}}, 'CGI,Example'); + push(@{$verify_class->{choices}}, 'Example'); + + push( + @$auth_params, + { + name => 'param_example', + type => 't', + default => 0, + checker => \&check_numeric + } + ); } sub db_schema_abstract_schema { - my ($self, $args) = @_; + my ($self, $args) = @_; + # $args->{'schema'}->{'example_table'} = { # FIELDS => [ # id => {TYPE => 'SMALLSERIAL', NOTNULL => 1, @@ -404,672 +421,695 @@ sub db_schema_abstract_schema { } sub email_in_before_parse { - my ($self, $args) = @_; + my ($self, $args) = @_; - my $subject = $args->{mail}->header('Subject'); - # Correctly extract the bug ID from email subjects of the form [Bug comp/NNN]. - if ($subject =~ /\[.*(\d+)\].*/) { - $args->{fields}->{bug_id} = $1; - } + my $subject = $args->{mail}->header('Subject'); + + # Correctly extract the bug ID from email subjects of the form [Bug comp/NNN]. + if ($subject =~ /\[.*(\d+)\].*/) { + $args->{fields}->{bug_id} = $1; + } } sub email_in_after_parse { - my ($self, $args) = @_; - my $reporter = $args->{fields}->{reporter}; - my $dbh = Bugzilla->dbh; - - # No other check needed if this is a valid regular user. - return if login_to_id($reporter); - - # The reporter is not a regular user. We create an account for them, - # but they can only comment on existing bugs. - # This is useful for people who reply by email to bugmails received - # in mailing-lists. - if ($args->{fields}->{bug_id}) { - # WARNING: we return now to skip the remaining code below. - # You must understand that removing this line would make the code - # below effective! Do it only if you are OK with the behavior - # described here. - return; - - Bugzilla::User->create({ login_name => $reporter, cryptpassword => '*' }); - - # For security reasons, delete all fields unrelated to comments. - foreach my $field (keys %{$args->{fields}}) { - next if $field =~ /^(?:bug_id|comment|reporter)$/; - delete $args->{fields}->{$field}; - } - } - else { - ThrowUserError('invalid_username', { name => $reporter }); + my ($self, $args) = @_; + my $reporter = $args->{fields}->{reporter}; + my $dbh = Bugzilla->dbh; + + # No other check needed if this is a valid regular user. + return if login_to_id($reporter); + + # The reporter is not a regular user. We create an account for them, + # but they can only comment on existing bugs. + # This is useful for people who reply by email to bugmails received + # in mailing-lists. + if ($args->{fields}->{bug_id}) { + + # WARNING: we return now to skip the remaining code below. + # You must understand that removing this line would make the code + # below effective! Do it only if you are OK with the behavior + # described here. + return; + + Bugzilla::User->create({login_name => $reporter, cryptpassword => '*'}); + + # For security reasons, delete all fields unrelated to comments. + foreach my $field (keys %{$args->{fields}}) { + next if $field =~ /^(?:bug_id|comment|reporter)$/; + delete $args->{fields}->{$field}; } + } + else { + ThrowUserError('invalid_username', {name => $reporter}); + } } sub enter_bug_entrydefaultvars { - my ($self, $args) = @_; - - my $vars = $args->{vars}; - $vars->{'example'} = 1; + my ($self, $args) = @_; + + my $vars = $args->{vars}; + $vars->{'example'} = 1; } sub error_catch { - my ($self, $args) = @_; - # Customize the error message displayed when someone tries to access - # page.cgi with an invalid page ID, and keep track of this attempt - # in the web server log. - return unless Bugzilla->error_mode == ERROR_MODE_WEBPAGE; - return unless $args->{error} eq 'bad_page_cgi_id'; - - my $page_id = $args->{vars}->{page_id}; - my $login = Bugzilla->user->identity || "Someone"; - warn "$login attempted to access page.cgi with id = $page_id"; - - my $page = $args->{message}; - my $new_error_msg = "Ah ah, you tried to access $page_id? Good try!"; - $new_error_msg = html_quote($new_error_msg); - # There are better tools to parse an HTML page, but it's just an example. - # Since Perl 5.16, we can no longer write "class" inside look-behind - # assertions, because "ss" is also seen as the german ß character, which - # makes Perl 5.16 complain. The right fix is to use the /aa modifier, - # but it's only understood since Perl 5.14. So the workaround is to write - # "clas[s]" instead of "class". Stupid and ugly hack, but it works with - # all Perl versions. - $$page =~ s/(?<=<td id="error_msg" clas[s]="throw_error">).*(?=<\/td>)/$new_error_msg/si; + my ($self, $args) = @_; + + # Customize the error message displayed when someone tries to access + # page.cgi with an invalid page ID, and keep track of this attempt + # in the web server log. + return unless Bugzilla->error_mode == ERROR_MODE_WEBPAGE; + return unless $args->{error} eq 'bad_page_cgi_id'; + + my $page_id = $args->{vars}->{page_id}; + my $login = Bugzilla->user->identity || "Someone"; + warn "$login attempted to access page.cgi with id = $page_id"; + + my $page = $args->{message}; + my $new_error_msg = "Ah ah, you tried to access $page_id? Good try!"; + $new_error_msg = html_quote($new_error_msg); + + # There are better tools to parse an HTML page, but it's just an example. + # Since Perl 5.16, we can no longer write "class" inside look-behind + # assertions, because "ss" is also seen as the german ß character, which + # makes Perl 5.16 complain. The right fix is to use the /aa modifier, + # but it's only understood since Perl 5.14. So the workaround is to write + # "clas[s]" instead of "class". Stupid and ugly hack, but it works with + # all Perl versions. + $$page + =~ s/(?<=<td id="error_msg" clas[s]="throw_error">).*(?=<\/td>)/$new_error_msg/si; } sub flag_end_of_update { - my ($self, $args) = @_; - - # This code doesn't actually *do* anything, it's just here to show you - # how to use this hook. - my $flag_params = $args; - my ($object, $timestamp, $old_flags, $new_flags) = - @$flag_params{qw(object timestamp old_flags new_flags)}; - my ($removed, $added) = diff_arrays($old_flags, $new_flags); - my ($granted, $denied) = (0, 0); - foreach my $new_flag (@$added) { - $granted++ if $new_flag =~ /\+$/; - $denied++ if $new_flag =~ /-$/; - } - my $bug_id = $object->isa('Bugzilla::Bug') ? $object->id - : $object->bug_id; - my $result = "$granted flags were granted and $denied flags were denied" - . " on bug $bug_id at $timestamp."; - # Uncomment this line to see $result in your webserver's error log whenever - # you update flags. - # warn $result; + my ($self, $args) = @_; + + # This code doesn't actually *do* anything, it's just here to show you + # how to use this hook. + my $flag_params = $args; + my ($object, $timestamp, $old_flags, $new_flags) + = @$flag_params{qw(object timestamp old_flags new_flags)}; + my ($removed, $added) = diff_arrays($old_flags, $new_flags); + my ($granted, $denied) = (0, 0); + foreach my $new_flag (@$added) { + $granted++ if $new_flag =~ /\+$/; + $denied++ if $new_flag =~ /-$/; + } + my $bug_id = $object->isa('Bugzilla::Bug') ? $object->id : $object->bug_id; + my $result = "$granted flags were granted and $denied flags were denied" + . " on bug $bug_id at $timestamp."; + + # Uncomment this line to see $result in your webserver's error log whenever + # you update flags. + # warn $result; } sub group_before_delete { - my ($self, $args) = @_; - # This code doesn't actually *do* anything, it's just here to show you - # how to use this hook. + my ($self, $args) = @_; + + # This code doesn't actually *do* anything, it's just here to show you + # how to use this hook. + + my $group = $args->{'group'}; + my $group_id = $group->id; - my $group = $args->{'group'}; - my $group_id = $group->id; - # Uncomment this line to see a line in your webserver's error log whenever - # you file a bug. - # warn "Group $group_id is about to be deleted!"; + # Uncomment this line to see a line in your webserver's error log whenever + # you file a bug. + # warn "Group $group_id is about to be deleted!"; } sub group_end_of_create { - my ($self, $args) = @_; - # This code doesn't actually *do* anything, it's just here to show you - # how to use this hook. - my $group = $args->{'group'}; + my ($self, $args) = @_; - my $group_id = $group->id; - # Uncomment this line to see a line in your webserver's error log whenever - # you create a new group. - #warn "Group $group_id has been created!"; + # This code doesn't actually *do* anything, it's just here to show you + # how to use this hook. + my $group = $args->{'group'}; + + my $group_id = $group->id; + + # Uncomment this line to see a line in your webserver's error log whenever + # you create a new group. + #warn "Group $group_id has been created!"; } sub group_end_of_update { - my ($self, $args) = @_; - # This code doesn't actually *do* anything, it's just here to show you - # how to use this hook. + my ($self, $args) = @_; - my ($group, $changes) = @$args{qw(group changes)}; + # This code doesn't actually *do* anything, it's just here to show you + # how to use this hook. - foreach my $field (keys %$changes) { - my $used_to_be = $changes->{$field}->[0]; - my $now_it_is = $changes->{$field}->[1]; - } + my ($group, $changes) = @$args{qw(group changes)}; + + foreach my $field (keys %$changes) { + my $used_to_be = $changes->{$field}->[0]; + my $now_it_is = $changes->{$field}->[1]; + } - my $group_id = $group->id; - my $num_changes = scalar keys %$changes; - my $result = - "There were $num_changes changes to fields on group $group_id."; - # Uncomment this line to see $result in your webserver's error log whenever - # you update a group. - #warn $result; + my $group_id = $group->id; + my $num_changes = scalar keys %$changes; + my $result = "There were $num_changes changes to fields on group $group_id."; + + # Uncomment this line to see $result in your webserver's error log whenever + # you update a group. + #warn $result; } sub install_before_final_checks { - my ($self, $args) = @_; - print "Install-before_final_checks hook\n" unless $args->{silent}; - - # Add a new user setting like this: - # - # add_setting('product_chooser', # setting name - # ['pretty', 'full', 'small'], # options - # 'pretty'); # default - # - # To add descriptions for the setting and choices, add extra values to - # the hash defined in global/setting-descs.none.tmpl. Do this in a hook: - # hook/global/setting-descs-settings.none.tmpl . + my ($self, $args) = @_; + print "Install-before_final_checks hook\n" unless $args->{silent}; + + # Add a new user setting like this: + # + # add_setting('product_chooser', # setting name + # ['pretty', 'full', 'small'], # options + # 'pretty'); # default + # + # To add descriptions for the setting and choices, add extra values to + # the hash defined in global/setting-descs.none.tmpl. Do this in a hook: + # hook/global/setting-descs-settings.none.tmpl . } sub install_filesystem { - my ($self, $args) = @_; - my $create_dirs = $args->{'create_dirs'}; - my $recurse_dirs = $args->{'recurse_dirs'}; - my $htaccess = $args->{'htaccess'}; - - # Create a new directory in datadir specifically for this extension. - # The directory will need to allow files to be created by the extension - # code as well as allow the webserver to server content from it. - # my $data_path = bz_locations->{'datadir'} . "/" . __PACKAGE__->NAME; - # $create_dirs->{$data_path} = Bugzilla::Install::Filesystem::DIR_CGI_WRITE; - - # Update the permissions of any files and directories that currently reside - # in the extension's directory. - # $recurse_dirs->{$data_path} = { - # files => Bugzilla::Install::Filesystem::CGI_READ, - # dirs => Bugzilla::Install::Filesystem::DIR_CGI_WRITE - # }; - - # Create a htaccess file that allows specific content to be served from the - # extension's directory. - # $htaccess->{"$data_path/.htaccess"} = { - # perms => Bugzilla::Install::Filesystem::WS_SERVE, - # contents => Bugzilla::Install::Filesystem::HT_DEFAULT_DENY - # }; + my ($self, $args) = @_; + my $create_dirs = $args->{'create_dirs'}; + my $recurse_dirs = $args->{'recurse_dirs'}; + my $htaccess = $args->{'htaccess'}; + + # Create a new directory in datadir specifically for this extension. + # The directory will need to allow files to be created by the extension + # code as well as allow the webserver to server content from it. + # my $data_path = bz_locations->{'datadir'} . "/" . __PACKAGE__->NAME; + # $create_dirs->{$data_path} = Bugzilla::Install::Filesystem::DIR_CGI_WRITE; + + # Update the permissions of any files and directories that currently reside + # in the extension's directory. + # $recurse_dirs->{$data_path} = { + # files => Bugzilla::Install::Filesystem::CGI_READ, + # dirs => Bugzilla::Install::Filesystem::DIR_CGI_WRITE + # }; + + # Create a htaccess file that allows specific content to be served from the + # extension's directory. + # $htaccess->{"$data_path/.htaccess"} = { + # perms => Bugzilla::Install::Filesystem::WS_SERVE, + # contents => Bugzilla::Install::Filesystem::HT_DEFAULT_DENY + # }; } sub install_update_db { - my $dbh = Bugzilla->dbh; + my $dbh = Bugzilla->dbh; + # $dbh->bz_add_column('example', 'new_column', # {TYPE => 'INT2', NOTNULL => 1, DEFAULT => 0}); # $dbh->bz_add_index('example', 'example_new_column_idx', [qw(value)]); } sub install_update_db_fielddefs { - my $dbh = Bugzilla->dbh; -# $dbh->bz_add_column('fielddefs', 'example_column', + my $dbh = Bugzilla->dbh; + +# $dbh->bz_add_column('fielddefs', 'example_column', # {TYPE => 'MEDIUMTEXT', NOTNULL => 1, DEFAULT => ''}); } sub job_map { - my ($self, $args) = @_; - - my $job_map = $args->{job_map}; - - # This adds the named class (an instance of TheSchwartz::Worker) as a - # handler for when a job is added with the name "some_task". - $job_map->{'some_task'} = 'Bugzilla::Extension::Example::Job::SomeClass'; - - # Schedule a job like this: - # my $queue = Bugzilla->job_queue(); - # $queue->insert('some_task', { some_parameter => $some_variable }); + my ($self, $args) = @_; + + my $job_map = $args->{job_map}; + + # This adds the named class (an instance of TheSchwartz::Worker) as a + # handler for when a job is added with the name "some_task". + $job_map->{'some_task'} = 'Bugzilla::Extension::Example::Job::SomeClass'; + + # Schedule a job like this: + # my $queue = Bugzilla->job_queue(); + # $queue->insert('some_task', { some_parameter => $some_variable }); } sub mailer_before_send { - my ($self, $args) = @_; - - my $email = $args->{email}; - # If you add a header to an email, it's best to start it with - # 'X-Bugzilla-<Extension>' so that you don't conflict with - # other extensions. - $email->header_set('X-Bugzilla-Example-Header', 'Example'); + my ($self, $args) = @_; + + my $email = $args->{email}; + + # If you add a header to an email, it's best to start it with + # 'X-Bugzilla-<Extension>' so that you don't conflict with + # other extensions. + $email->header_set('X-Bugzilla-Example-Header', 'Example'); } sub object_before_create { - my ($self, $args) = @_; - - my $class = $args->{'class'}; - my $object_params = $args->{'params'}; - - # Note that this is a made-up class, for this example. - if ($class->isa('Bugzilla::ExampleObject')) { - warn "About to create an ExampleObject!"; - warn "Got the following parameters: " - . join(', ', keys(%$object_params)); - } + my ($self, $args) = @_; + + my $class = $args->{'class'}; + my $object_params = $args->{'params'}; + + # Note that this is a made-up class, for this example. + if ($class->isa('Bugzilla::ExampleObject')) { + warn "About to create an ExampleObject!"; + warn "Got the following parameters: " . join(', ', keys(%$object_params)); + } } sub object_before_delete { - my ($self, $args) = @_; + my ($self, $args) = @_; - my $object = $args->{'object'}; + my $object = $args->{'object'}; - # Note that this is a made-up class, for this example. - if ($object->isa('Bugzilla::ExampleObject')) { - my $id = $object->id; - warn "An object with id $id is about to be deleted!"; - } + # Note that this is a made-up class, for this example. + if ($object->isa('Bugzilla::ExampleObject')) { + my $id = $object->id; + warn "An object with id $id is about to be deleted!"; + } } sub object_before_set { - my ($self, $args) = @_; - - my ($object, $field, $value) = @$args{qw(object field value)}; - - # Note that this is a made-up class, for this example. - if ($object->isa('Bugzilla::ExampleObject')) { - warn "The field $field is changing from " . $object->{$field} - . " to $value!"; - } + my ($self, $args) = @_; + + my ($object, $field, $value) = @$args{qw(object field value)}; + + # Note that this is a made-up class, for this example. + if ($object->isa('Bugzilla::ExampleObject')) { + warn "The field $field is changing from " . $object->{$field} . " to $value!"; + } } sub object_columns { - my ($self, $args) = @_; - my ($class, $columns) = @$args{qw(class columns)}; + my ($self, $args) = @_; + my ($class, $columns) = @$args{qw(class columns)}; - if ($class->isa('Bugzilla::ExampleObject')) { - push(@$columns, 'example'); - } + if ($class->isa('Bugzilla::ExampleObject')) { + push(@$columns, 'example'); + } } sub object_end_of_create { - my ($self, $args) = @_; - - my $class = $args->{'class'}; - my $object = $args->{'object'}; + my ($self, $args) = @_; + + my $class = $args->{'class'}; + my $object = $args->{'object'}; - warn "Created a new $class object!"; + warn "Created a new $class object!"; } sub object_end_of_create_validators { - my ($self, $args) = @_; - - my $class = $args->{'class'}; - my $object_params = $args->{'params'}; - - # Note that this is a made-up class, for this example. - if ($class->isa('Bugzilla::ExampleObject')) { - # Always set example_field to 1, even if the validators said otherwise. - $object_params->{example_field} = 1; - } - + my ($self, $args) = @_; + + my $class = $args->{'class'}; + my $object_params = $args->{'params'}; + + # Note that this is a made-up class, for this example. + if ($class->isa('Bugzilla::ExampleObject')) { + + # Always set example_field to 1, even if the validators said otherwise. + $object_params->{example_field} = 1; + } + } sub object_end_of_set { - my ($self, $args) = @_; + my ($self, $args) = @_; - my ($object, $field) = @$args{qw(object field)}; + my ($object, $field) = @$args{qw(object field)}; - # Note that this is a made-up class, for this example. - if ($object->isa('Bugzilla::ExampleObject')) { - warn "The field $field has changed to " . $object->{$field}; - } + # Note that this is a made-up class, for this example. + if ($object->isa('Bugzilla::ExampleObject')) { + warn "The field $field has changed to " . $object->{$field}; + } } sub object_end_of_set_all { - my ($self, $args) = @_; - - my $object = $args->{'object'}; - my $object_params = $args->{'params'}; - - # Note that this is a made-up class, for this example. - if ($object->isa('Bugzilla::ExampleObject')) { - if ($object_params->{example_field} == 1) { - $object->{example_field} = 1; - } + my ($self, $args) = @_; + + my $object = $args->{'object'}; + my $object_params = $args->{'params'}; + + # Note that this is a made-up class, for this example. + if ($object->isa('Bugzilla::ExampleObject')) { + if ($object_params->{example_field} == 1) { + $object->{example_field} = 1; } - + } + } sub object_end_of_update { - my ($self, $args) = @_; - - my ($object, $old_object, $changes) = - @$args{qw(object old_object changes)}; - - # Note that this is a made-up class, for this example. - if ($object->isa('Bugzilla::ExampleObject')) { - if (defined $changes->{'name'}) { - my ($old, $new) = @{ $changes->{'name'} }; - print "The name field changed from $old to $new!"; - } + my ($self, $args) = @_; + + my ($object, $old_object, $changes) = @$args{qw(object old_object changes)}; + + # Note that this is a made-up class, for this example. + if ($object->isa('Bugzilla::ExampleObject')) { + if (defined $changes->{'name'}) { + my ($old, $new) = @{$changes->{'name'}}; + print "The name field changed from $old to $new!"; } + } } sub object_update_columns { - my ($self, $args) = @_; - my ($object, $columns) = @$args{qw(object columns)}; + my ($self, $args) = @_; + my ($object, $columns) = @$args{qw(object columns)}; - if ($object->isa('Bugzilla::ExampleObject')) { - push(@$columns, 'example'); - } + if ($object->isa('Bugzilla::ExampleObject')) { + push(@$columns, 'example'); + } } sub object_validators { - my ($self, $args) = @_; - my ($class, $validators) = @$args{qw(class validators)}; - - if ($class->isa('Bugzilla::Bug')) { - # This is an example of adding a new validator. - # See the _check_example subroutine below. - $validators->{example} = \&_check_example; - - # This is an example of overriding an existing validator. - # See the check_short_desc validator below. - my $original = $validators->{short_desc}; - $validators->{short_desc} = sub { _check_short_desc($original, @_) }; - } + my ($self, $args) = @_; + my ($class, $validators) = @$args{qw(class validators)}; + + if ($class->isa('Bugzilla::Bug')) { + + # This is an example of adding a new validator. + # See the _check_example subroutine below. + $validators->{example} = \&_check_example; + + # This is an example of overriding an existing validator. + # See the check_short_desc validator below. + my $original = $validators->{short_desc}; + $validators->{short_desc} = sub { _check_short_desc($original, @_) }; + } } sub _check_example { - my ($invocant, $value, $field) = @_; - warn "I was called to validate the value of $field."; - warn "The value of $field that I was passed in is: $value"; + my ($invocant, $value, $field) = @_; + warn "I was called to validate the value of $field."; + warn "The value of $field that I was passed in is: $value"; - # Make the value always be 1. - my $fixed_value = 1; - return $fixed_value; + # Make the value always be 1. + my $fixed_value = 1; + return $fixed_value; } sub _check_short_desc { - my $original = shift; - my $invocant = shift; - my $value = $invocant->$original(@_); - if ($value !~ /example/i) { - # Use this line to make Bugzilla throw an error every time - # you try to file a bug or update a bug without the word "example" - # in the summary. - if (0) { - ThrowUserError('example_short_desc_invalid'); - } + my $original = shift; + my $invocant = shift; + my $value = $invocant->$original(@_); + if ($value !~ /example/i) { + + # Use this line to make Bugzilla throw an error every time + # you try to file a bug or update a bug without the word "example" + # in the summary. + if (0) { + ThrowUserError('example_short_desc_invalid'); } - return $value; + } + return $value; } sub page_before_template { - my ($self, $args) = @_; - - my ($vars, $page) = @$args{qw(vars page_id)}; - - # You can see this hook in action by loading page.cgi?id=example.html - if ($page eq 'example.html') { - $vars->{cgi_variables} = { Bugzilla->cgi->Vars }; - } + my ($self, $args) = @_; + + my ($vars, $page) = @$args{qw(vars page_id)}; + + # You can see this hook in action by loading page.cgi?id=example.html + if ($page eq 'example.html') { + $vars->{cgi_variables} = {Bugzilla->cgi->Vars}; + } } sub path_info_whitelist { - my ($self, $args) = @_; - my $whitelist = $args->{whitelist}; - push(@$whitelist, "page.cgi"); + my ($self, $args) = @_; + my $whitelist = $args->{whitelist}; + push(@$whitelist, "page.cgi"); } sub post_bug_after_creation { - my ($self, $args) = @_; - - my $vars = $args->{vars}; - $vars->{'example'} = 1; + my ($self, $args) = @_; + + my $vars = $args->{vars}; + $vars->{'example'} = 1; } sub product_confirm_delete { - my ($self, $args) = @_; - - my $vars = $args->{vars}; - $vars->{'example'} = 1; + my ($self, $args) = @_; + + my $vars = $args->{vars}; + $vars->{'example'} = 1; } sub product_end_of_create { - my ($self, $args) = @_; + my ($self, $args) = @_; + + my $product = $args->{product}; - my $product = $args->{product}; + # For this example, any lines of code that actually make changes to your + # database have been commented out. - # For this example, any lines of code that actually make changes to your - # database have been commented out. + # This section will take a group that exists in your installation + # (possible called test_group) and automatically makes the new + # product hidden to only members of the group. Just remove + # the restriction if you want the new product to be public. - # This section will take a group that exists in your installation - # (possible called test_group) and automatically makes the new - # product hidden to only members of the group. Just remove - # the restriction if you want the new product to be public. + my $example_group = new Bugzilla::Group({name => 'example_group'}); - my $example_group = new Bugzilla::Group({ name => 'example_group' }); + if ($example_group) { + $product->set_group_controls( + $example_group, + { + entry => 1, + membercontrol => CONTROLMAPMANDATORY, + othercontrol => CONTROLMAPMANDATORY + } + ); - if ($example_group) { - $product->set_group_controls($example_group, - { entry => 1, - membercontrol => CONTROLMAPMANDATORY, - othercontrol => CONTROLMAPMANDATORY }); # $product->update(); - } + } - # This section will automatically add a default component - # to the new product called 'No Component'. + # This section will automatically add a default component + # to the new product called 'No Component'. - my $default_assignee = new Bugzilla::User( - { name => Bugzilla->params->{maintainer} }); + my $default_assignee + = new Bugzilla::User({name => Bugzilla->params->{maintainer}}); + + if ($default_assignee) { - if ($default_assignee) { # Bugzilla::Component->create( # { name => 'No Component', # product => $product, -# description => 'Select this component if one does not ' . +# description => 'Select this component if one does not ' . # 'exist in the current list of components', # initialowner => $default_assignee }); - } + } } sub quicksearch_map { - my ($self, $args) = @_; - my $map = $args->{'map'}; + my ($self, $args) = @_; + my $map = $args->{'map'}; - # This demonstrates adding a shorter alias for a long custom field name. - $map->{'impact'} = $map->{'cf_long_field_name_for_impact_field'}; + # This demonstrates adding a shorter alias for a long custom field name. + $map->{'impact'} = $map->{'cf_long_field_name_for_impact_field'}; } sub sanitycheck_check { - my ($self, $args) = @_; - - my $dbh = Bugzilla->dbh; - my $sth; - - my $status = $args->{'status'}; - - # Check that all users are Australian - $status->('example_check_au_user'); - - $sth = $dbh->prepare("SELECT userid, login_name + my ($self, $args) = @_; + + my $dbh = Bugzilla->dbh; + my $sth; + + my $status = $args->{'status'}; + + # Check that all users are Australian + $status->('example_check_au_user'); + + $sth = $dbh->prepare( + "SELECT userid, login_name FROM profiles - WHERE login_name NOT LIKE '%.au'"); - $sth->execute; - - my $seen_nonau = 0; - while (my ($userid, $login, $numgroups) = $sth->fetchrow_array) { - $status->('example_check_au_user_alert', - { userid => $userid, login => $login }, - 'alert'); - $seen_nonau = 1; - } - - $status->('example_check_au_user_prompt') if $seen_nonau; + WHERE login_name NOT LIKE '%.au'" + ); + $sth->execute; + + my $seen_nonau = 0; + while (my ($userid, $login, $numgroups) = $sth->fetchrow_array) { + $status->( + 'example_check_au_user_alert', {userid => $userid, login => $login}, 'alert' + ); + $seen_nonau = 1; + } + + $status->('example_check_au_user_prompt') if $seen_nonau; } sub sanitycheck_repair { - my ($self, $args) = @_; - - my $cgi = Bugzilla->cgi; - my $dbh = Bugzilla->dbh; - - my $status = $args->{'status'}; - - if ($cgi->param('example_repair_au_user')) { - $status->('example_repair_au_user_start'); - - #$dbh->do("UPDATE profiles - # SET login_name = CONCAT(login_name, '.au') - # WHERE login_name NOT LIKE '%.au'"); - - $status->('example_repair_au_user_end'); - } + my ($self, $args) = @_; + + my $cgi = Bugzilla->cgi; + my $dbh = Bugzilla->dbh; + + my $status = $args->{'status'}; + + if ($cgi->param('example_repair_au_user')) { + $status->('example_repair_au_user_start'); + + #$dbh->do("UPDATE profiles + # SET login_name = CONCAT(login_name, '.au') + # WHERE login_name NOT LIKE '%.au'"); + + $status->('example_repair_au_user_end'); + } } sub template_before_create { - my ($self, $args) = @_; - - my $config = $args->{'config'}; - # This will be accessible as "example_global_variable" in every - # template in Bugzilla. See Bugzilla/Template.pm's create() function - # for more things that you can set. - $config->{VARIABLES}->{example_global_variable} = sub { return 'value' }; + my ($self, $args) = @_; + + my $config = $args->{'config'}; + + # This will be accessible as "example_global_variable" in every + # template in Bugzilla. See Bugzilla/Template.pm's create() function + # for more things that you can set. + $config->{VARIABLES}->{example_global_variable} = sub { return 'value' }; } sub template_after_create { - my ( $self, $args ) = @_; - my $context = $args->{template}->context; - - # define a pluck method on template toolkit lists. - $context->define_vmethod( - list => pluck => sub { - my ( $list, $field ) = @_; - return [ map { $_->$field } @$list ]; - } - ); + my ($self, $args) = @_; + my $context = $args->{template}->context; + + # define a pluck method on template toolkit lists. + $context->define_vmethod( + list => pluck => sub { + my ($list, $field) = @_; + return [map { $_->$field } @$list]; + } + ); } sub template_before_process { - my ($self, $args) = @_; - - my ($vars, $file, $context) = @$args{qw(vars file context)}; + my ($self, $args) = @_; - if ($file eq 'bug/edit.html.tmpl') { - $vars->{'viewing_the_bug_form'} = 1; - } + my ($vars, $file, $context) = @$args{qw(vars file context)}; + + if ($file eq 'bug/edit.html.tmpl') { + $vars->{'viewing_the_bug_form'} = 1; + } } sub user_check_account_creation { - my ($self, $args) = @_; + my ($self, $args) = @_; - my $login = $args->{login}; - my $ip = remote_ip(); + my $login = $args->{login}; + my $ip = remote_ip(); - # Log all requests. - warn "USER ACCOUNT CREATION REQUEST FOR $login ($ip)"; + # Log all requests. + warn "USER ACCOUNT CREATION REQUEST FOR $login ($ip)"; - # Reject requests based on their email address. - if ($login =~ /\@evil\.com$/) { - ThrowUserError('account_creation_restricted'); - } + # Reject requests based on their email address. + if ($login =~ /\@evil\.com$/) { + ThrowUserError('account_creation_restricted'); + } - # Reject requests based on their IP address. - if ($ip =~ /^192\.168\./) { - ThrowUserError('account_creation_restricted'); - } + # Reject requests based on their IP address. + if ($ip =~ /^192\.168\./) { + ThrowUserError('account_creation_restricted'); + } } sub user_preferences { - my ($self, $args) = @_; - my $tab = $args->{current_tab}; - my $save = $args->{save_changes}; - my $handled = $args->{handled}; + my ($self, $args) = @_; + my $tab = $args->{current_tab}; + my $save = $args->{save_changes}; + my $handled = $args->{handled}; - return unless $tab eq 'my_tab'; + return unless $tab eq 'my_tab'; - my $value = Bugzilla->input_params->{'example_pref'}; - if ($save) { - # Validate your data and update the DB accordingly. - $value =~ s/\s+/:/g; - } - $args->{'vars'}->{example_pref} = $value; + my $value = Bugzilla->input_params->{'example_pref'}; + if ($save) { + + # Validate your data and update the DB accordingly. + $value =~ s/\s+/:/g; + } + $args->{'vars'}->{example_pref} = $value; - # Set the 'handled' scalar reference to true so that the caller - # knows the panel name is valid and that an extension took care of it. - $$handled = 1; + # Set the 'handled' scalar reference to true so that the caller + # knows the panel name is valid and that an extension took care of it. + $$handled = 1; } sub webservice { - my ($self, $args) = @_; + my ($self, $args) = @_; - my $dispatch = $args->{dispatch}; - $dispatch->{Example} = "Bugzilla::Extension::Example::WebService"; + my $dispatch = $args->{dispatch}; + $dispatch->{Example} = "Bugzilla::Extension::Example::WebService"; } sub webservice_error_codes { - my ($self, $args) = @_; + my ($self, $args) = @_; - my $error_map = $args->{error_map}; - $error_map->{'example_my_error'} = 10001; + my $error_map = $args->{error_map}; + $error_map->{'example_my_error'} = 10001; } sub webservice_status_code_map { - my ($self, $args) = @_; + my ($self, $args) = @_; - my $status_code_map = $args->{status_code_map}; - # Uncomment this line to override the status code for the - # error 'object_does_not_exist' to STATUS_BAD_REQUEST - #$status_code_map->{51} = STATUS_BAD_REQUEST; + my $status_code_map = $args->{status_code_map}; + + # Uncomment this line to override the status code for the + # error 'object_does_not_exist' to STATUS_BAD_REQUEST + #$status_code_map->{51} = STATUS_BAD_REQUEST; } sub webservice_before_call { - my ($self, $args) = @_; + my ($self, $args) = @_; - # This code doesn't actually *do* anything, it's just here to show you - # how to use this hook. - my $method = $args->{method}; - my $full_method = $args->{full_method}; + # This code doesn't actually *do* anything, it's just here to show you + # how to use this hook. + my $method = $args->{method}; + my $full_method = $args->{full_method}; - # Uncomment this line to see a line in your webserver's error log whenever - # a webservice call is made - #warn "RPC call $full_method made by ", - # Bugzilla->user->login || 'an anonymous user', "\n"; + # Uncomment this line to see a line in your webserver's error log whenever + # a webservice call is made + #warn "RPC call $full_method made by ", + # Bugzilla->user->login || 'an anonymous user', "\n"; } sub webservice_fix_credentials { - my ($self, $args) = @_; - my $rpc = $args->{'rpc'}; - my $params = $args->{'params'}; - # Allow user to pass in username=foo&password=bar - if (exists $params->{'username'} && exists $params->{'password'}) { - $params->{'Bugzilla_login'} = $params->{'username'}; - $params->{'Bugzilla_password'} = $params->{'password'}; - } + my ($self, $args) = @_; + my $rpc = $args->{'rpc'}; + my $params = $args->{'params'}; + + # Allow user to pass in username=foo&password=bar + if (exists $params->{'username'} && exists $params->{'password'}) { + $params->{'Bugzilla_login'} = $params->{'username'}; + $params->{'Bugzilla_password'} = $params->{'password'}; + } } sub webservice_rest_request { - my ($self, $args) = @_; - my $rpc = $args->{'rpc'}; - my $params = $args->{'params'}; - # Internally we may have a field called 'cf_test_field' but we allow users - # to use the shorter 'test_field' name. - if (exists $params->{'test_field'}) { - $params->{'test_field'} = delete $params->{'cf_test_field'}; - } + my ($self, $args) = @_; + my $rpc = $args->{'rpc'}; + my $params = $args->{'params'}; + + # Internally we may have a field called 'cf_test_field' but we allow users + # to use the shorter 'test_field' name. + if (exists $params->{'test_field'}) { + $params->{'test_field'} = delete $params->{'cf_test_field'}; + } } sub webservice_rest_resources { - my ($self, $args) = @_; - my $rpc = $args->{'rpc'}; - my $resources = $args->{'resources'}; - # Add a new resource that allows for /rest/example/hello - # to call Example.hello - $resources->{'Bugzilla::Extension::Example::WebService'} = [ - qr{^/example/hello$}, { - GET => { - method => 'hello', - } - } - ]; + my ($self, $args) = @_; + my $rpc = $args->{'rpc'}; + my $resources = $args->{'resources'}; + + # Add a new resource that allows for /rest/example/hello + # to call Example.hello + $resources->{'Bugzilla::Extension::Example::WebService'} + = [qr{^/example/hello$}, {GET => {method => 'hello',}}]; } sub webservice_rest_response { - my ($self, $args) = @_; - my $rpc = $args->{'rpc'}; - my $result = $args->{'result'}; - my $response = $args->{'response'}; - # Convert a list of bug hashes to a single bug hash if only one is - # being returned. - if (ref $$result eq 'HASH' - && exists $$result->{'bugs'} - && scalar @{ $$result->{'bugs'} } == 1) - { - $$result = $$result->{'bugs'}->[0]; - } + my ($self, $args) = @_; + my $rpc = $args->{'rpc'}; + my $result = $args->{'result'}; + my $response = $args->{'response'}; + + # Convert a list of bug hashes to a single bug hash if only one is + # being returned. + if ( ref $$result eq 'HASH' + && exists $$result->{'bugs'} + && scalar @{$$result->{'bugs'}} == 1) + { + $$result = $$result->{'bugs'}->[0]; + } } # This must be the last line of your extension. diff --git a/extensions/Example/lib/Auth/Login.pm b/extensions/Example/lib/Auth/Login.pm index 15c58a881..49af42c22 100644 --- a/extensions/Example/lib/Auth/Login.pm +++ b/extensions/Example/lib/Auth/Login.pm @@ -17,7 +17,7 @@ use Bugzilla::Constants; # Always returns no data. sub get_login_info { - return { failure => AUTH_NODATA }; + return {failure => AUTH_NODATA}; } 1; diff --git a/extensions/Example/lib/Auth/Verify.pm b/extensions/Example/lib/Auth/Verify.pm index 49fd9fbb7..591c1d4f8 100644 --- a/extensions/Example/lib/Auth/Verify.pm +++ b/extensions/Example/lib/Auth/Verify.pm @@ -16,7 +16,7 @@ use Bugzilla::Constants; # A verifier that always fails. sub check_credentials { - return { failure => AUTH_NO_SUCH_USER }; + return {failure => AUTH_NO_SUCH_USER}; } 1; diff --git a/extensions/Example/lib/Config.pm b/extensions/Example/lib/Config.pm index fac0046af..360a57510 100644 --- a/extensions/Example/lib/Config.pm +++ b/extensions/Example/lib/Config.pm @@ -16,16 +16,11 @@ use Bugzilla::Config::Common; our $sortkey = 5000; sub get_param_list { - my ($class) = @_; + my ($class) = @_; - my @param_list = ( - { - name => 'example_string', - type => 't', - default => 'EXAMPLE', - }, - ); - return @param_list; + my @param_list + = ({name => 'example_string', type => 't', default => 'EXAMPLE',},); + return @param_list; } 1; diff --git a/extensions/Example/lib/WebService.pm b/extensions/Example/lib/WebService.pm index d8a96b5f5..f461fee6f 100644 --- a/extensions/Example/lib/WebService.pm +++ b/extensions/Example/lib/WebService.pm @@ -14,8 +14,8 @@ use parent qw(Bugzilla::WebService); use Bugzilla::Error; use constant PUBLIC_METHODS => qw( - hello - throw_an_error + hello + throw_an_error ); # This can be called as Example.hello() from the WebService. diff --git a/extensions/Example/template/en/default/setup/strings.txt.pl b/extensions/Example/template/en/default/setup/strings.txt.pl index 98550547e..e2fc52452 100644 --- a/extensions/Example/template/en/default/setup/strings.txt.pl +++ b/extensions/Example/template/en/default/setup/strings.txt.pl @@ -5,8 +5,6 @@ # This Source Code Form is "Incompatible With Secondary Licenses", as # defined by the Mozilla Public License, v. 2.0. -%strings = ( - feature_example_acme => 'Example Extension: Acme Feature', -); +%strings = (feature_example_acme => 'Example Extension: Acme Feature',); 1; diff --git a/extensions/Gentoo/Extension.pm b/extensions/Gentoo/Extension.pm index 936026678..97402e88d 100644 --- a/extensions/Gentoo/Extension.pm +++ b/extensions/Gentoo/Extension.pm @@ -11,62 +11,58 @@ use POSIX qw(uname); our $VERSION = '1.0'; sub install_filesystem { - my ($self, $args) = @_; + my ($self, $args) = @_; - my $dirs = $args->{'create_dirs'}; - my $files = $args->{'files'}; - my $recurse_dirs = $args->{'recurse_dirs'}; - my $htaccess = $args->{'htaccess'}; + my $dirs = $args->{'create_dirs'}; + my $files = $args->{'files'}; + my $recurse_dirs = $args->{'recurse_dirs'}; + my $htaccess = $args->{'htaccess'}; - my $datadir = bz_locations()->{'datadir'}; + my $datadir = bz_locations()->{'datadir'}; - $dirs->{"${datadir}/cached"} = { perms => 0750 }; + $dirs->{"${datadir}/cached"} = {perms => 0750}; - $files->{"zzz.txt"} = { perms => 0644 }; - $files->{"robots-ssl.txt"} = { perms => 0644 }; - $files->{"bots.html"} = { perms => 0644 }; - $files->{"favicon.ico"} = { perms => 0644 }; - $files->{"runstats.sh"} = { perms => 0700 }; - $files->{"recompile.sh"} = { perms => 0700 }; + $files->{"zzz.txt"} = {perms => 0644}; + $files->{"robots-ssl.txt"} = {perms => 0644}; + $files->{"bots.html"} = {perms => 0644}; + $files->{"favicon.ico"} = {perms => 0644}; + $files->{"runstats.sh"} = {perms => 0700}; + $files->{"recompile.sh"} = {perms => 0700}; - $recurse_dirs->{"$datadir/cached"} = { - files => 0640, dirs => 0750 - }; + $recurse_dirs->{"$datadir/cached"} = {files => 0640, dirs => 0750}; - $recurse_dirs->{"images/ranks"} = { - files => 0640, dirs => 0750 - }; + $recurse_dirs->{"images/ranks"} = {files => 0640, dirs => 0750}; - $htaccess->{"$datadir/cached/.htaccess"} = { - perms => 0640, contents => <<EOT + $htaccess->{"$datadir/cached/.htaccess"} = { + perms => 0640, + contents => <<EOT # Allow access to the cached stuff Allow from all EOT - }; + }; } sub template_before_create { - my ($self, $args) = @_; + my ($self, $args) = @_; - my $config = $args->{config}; - my $constants = $config->{CONSTANTS}; + my $config = $args->{config}; + my $constants = $config->{CONSTANTS}; - my %nodemap = ( - 'yellowbishop' => 'bugs-web1', - 'yellowleg' => 'bugs-web2' - ); + my %nodemap = ('yellowbishop' => 'bugs-web1', 'yellowleg' => 'bugs-web2'); - my $hostname = (uname())[1]; - $constants->{GENTOO_NODE} = $nodemap{$hostname} ? $nodemap{$hostname} : "[$hostname]"; - $constants->{GENTOO_APPEND_VERSION} = "+"; + my $hostname = (uname())[1]; + $constants->{GENTOO_NODE} + = $nodemap{$hostname} ? $nodemap{$hostname} : "[$hostname]"; + $constants->{GENTOO_APPEND_VERSION} = "+"; } sub user_check_account_creation { - my ($self, $args) = @_; + my ($self, $args) = @_; - my $login = $args->{login}; + my $login = $args->{login}; - ThrowUserError('restricted_email_address', {addr => $login}) if $login =~ m/.+\@gentoo\.org$/; + ThrowUserError('restricted_email_address', {addr => $login}) + if $login =~ m/.+\@gentoo\.org$/; } __PACKAGE__->NAME; diff --git a/extensions/InlineHistory/Extension.pm b/extensions/InlineHistory/Extension.pm index c88ff74ed..87d0674ca 100644 --- a/extensions/InlineHistory/Extension.pm +++ b/extensions/InlineHistory/Extension.pm @@ -19,200 +19,207 @@ our $VERSION = '1.6'; use constant MAXIMUM_ACTIVITY_COUNT => 500; # don't show really long values -use constant MAXIMUM_VALUE_LENGTH => 256; +use constant MAXIMUM_VALUE_LENGTH => 256; sub template_before_create { - my ($self, $args) = @_; - $args->{config}->{FILTERS}->{ih_short_value} = sub { - my ($str) = @_; - return length($str) <= MAXIMUM_VALUE_LENGTH - ? $str - : substr($str, 0, MAXIMUM_VALUE_LENGTH - 3) . '...'; - }; + my ($self, $args) = @_; + $args->{config}->{FILTERS}->{ih_short_value} = sub { + my ($str) = @_; + return + length($str) <= MAXIMUM_VALUE_LENGTH + ? $str + : substr($str, 0, MAXIMUM_VALUE_LENGTH - 3) . '...'; + }; } sub template_before_process { - my ($self, $args) = @_; - my $file = $args->{'file'}; - my $vars = $args->{'vars'}; - - return if $file ne 'bug/edit.html.tmpl'; - - my $user = Bugzilla->user; - my $dbh = Bugzilla->dbh; - return unless $user->id && $user->settings->{'inline_history'}->{'value'} eq 'on'; - - # note: bug/edit.html.tmpl doesn't support multiple bugs - my $bug = exists $vars->{'bugs'} ? $vars->{'bugs'}[0] : $vars->{'bug'}; - my $bug_id = $bug->id; - - # build bug activity - my ($activity) = $bug->can('get_activity') - ? $bug->get_activity() - : Bugzilla::Bug::GetBugActivity($bug_id); - $activity = _add_duplicates($bug_id, $activity); - - if (scalar @$activity > MAXIMUM_ACTIVITY_COUNT) { - $activity = []; - $vars->{'ih_activity'} = 0; - $vars->{'ih_activity_max'} = 1; - return; - } - - # prime caches with objects already loaded - my %user_cache; - foreach my $comment (@{$bug->comments}) { - $user_cache{$comment->{author}->login} = $comment->{author}; - } - - my %attachment_cache; - foreach my $attachment (@{$bug->attachments}) { - $attachment_cache{$attachment->id} = $attachment; - } - - # build a list of bugs we need to check visibility of, so we can check with a single query - my %visible_bug_ids; - - # augment and tweak - foreach my $operation (@$activity) { - # make operation.who an object - $user_cache{$operation->{who}} ||= Bugzilla::User->new({ name => $operation->{who} }); - $operation->{who} = $user_cache{$operation->{who}}; - - for (my $i = 0; $i < scalar(@{$operation->{changes}}); $i++) { - my $change = $operation->{changes}->[$i]; - - # make an attachment object - if ($change->{attachid}) { - $change->{attach} = $attachment_cache{$change->{attachid}}; - } - - # empty resolutions are displayed as --- by default - # make it explicit here to enable correct display of the change - if ($change->{fieldname} eq 'resolution') { - $change->{removed} = '---' if $change->{removed} eq ''; - $change->{added} = '---' if $change->{added} eq ''; - } - - # make boolean fields true/false instead of 1/0 - my ($table, $field) = ('bugs', $change->{fieldname}); - if ($field =~ /^([^\.]+)\.(.+)$/) { - ($table, $field) = ($1, $2); - } - my $column = $dbh->bz_column_info($table, $field); - if ($column && $column->{TYPE} eq 'BOOLEAN') { - $change->{removed} = ''; - $change->{added} = $change->{added} ? 'true' : 'false'; - } - - my $field_obj; - if ($change->{fieldname} =~ /^cf_/) { - $field_obj = Bugzilla::Field->new({ name => $change->{fieldname}, cache => 1 }); - } - - # identify buglist changes - if ($change->{fieldname} eq 'blocked' || - $change->{fieldname} eq 'dependson' || - $change->{fieldname} eq 'dupe' || - ($field_obj && $field_obj->type == FIELD_TYPE_BUG_ID) - ) { - $change->{buglist} = 1; - foreach my $what (qw(removed added)) { - my @buglist = split(/[\s,]+/, $change->{$what}); - foreach my $id (@buglist) { - if ($id && $id =~ /^\d+$/) { - $visible_bug_ids{$id} = 1; - } - } - } + my ($self, $args) = @_; + my $file = $args->{'file'}; + my $vars = $args->{'vars'}; + + return if $file ne 'bug/edit.html.tmpl'; + + my $user = Bugzilla->user; + my $dbh = Bugzilla->dbh; + return + unless $user->id && $user->settings->{'inline_history'}->{'value'} eq 'on'; + + # note: bug/edit.html.tmpl doesn't support multiple bugs + my $bug = exists $vars->{'bugs'} ? $vars->{'bugs'}[0] : $vars->{'bug'}; + my $bug_id = $bug->id; + + # build bug activity + my ($activity) + = $bug->can('get_activity') + ? $bug->get_activity() + : Bugzilla::Bug::GetBugActivity($bug_id); + $activity = _add_duplicates($bug_id, $activity); + + if (scalar @$activity > MAXIMUM_ACTIVITY_COUNT) { + $activity = []; + $vars->{'ih_activity'} = 0; + $vars->{'ih_activity_max'} = 1; + return; + } + + # prime caches with objects already loaded + my %user_cache; + foreach my $comment (@{$bug->comments}) { + $user_cache{$comment->{author}->login} = $comment->{author}; + } + + my %attachment_cache; + foreach my $attachment (@{$bug->attachments}) { + $attachment_cache{$attachment->id} = $attachment; + } + +# build a list of bugs we need to check visibility of, so we can check with a single query + my %visible_bug_ids; + + # augment and tweak + foreach my $operation (@$activity) { + + # make operation.who an object + $user_cache{$operation->{who}} + ||= Bugzilla::User->new({name => $operation->{who}}); + $operation->{who} = $user_cache{$operation->{who}}; + + for (my $i = 0; $i < scalar(@{$operation->{changes}}); $i++) { + my $change = $operation->{changes}->[$i]; + + # make an attachment object + if ($change->{attachid}) { + $change->{attach} = $attachment_cache{$change->{attachid}}; + } + + # empty resolutions are displayed as --- by default + # make it explicit here to enable correct display of the change + if ($change->{fieldname} eq 'resolution') { + $change->{removed} = '---' if $change->{removed} eq ''; + $change->{added} = '---' if $change->{added} eq ''; + } + + # make boolean fields true/false instead of 1/0 + my ($table, $field) = ('bugs', $change->{fieldname}); + if ($field =~ /^([^\.]+)\.(.+)$/) { + ($table, $field) = ($1, $2); + } + my $column = $dbh->bz_column_info($table, $field); + if ($column && $column->{TYPE} eq 'BOOLEAN') { + $change->{removed} = ''; + $change->{added} = $change->{added} ? 'true' : 'false'; + } + + my $field_obj; + if ($change->{fieldname} =~ /^cf_/) { + $field_obj = Bugzilla::Field->new({name => $change->{fieldname}, cache => 1}); + } + + # identify buglist changes + if ( $change->{fieldname} eq 'blocked' + || $change->{fieldname} eq 'dependson' + || $change->{fieldname} eq 'dupe' + || ($field_obj && $field_obj->type == FIELD_TYPE_BUG_ID)) + { + $change->{buglist} = 1; + foreach my $what (qw(removed added)) { + my @buglist = split(/[\s,]+/, $change->{$what}); + foreach my $id (@buglist) { + if ($id && $id =~ /^\d+$/) { + $visible_bug_ids{$id} = 1; } + } + } + } + + # split multiple flag changes (must be processed last) + if ($change->{fieldname} eq 'flagtypes.name') { + my @added = split(/, /, $change->{added}); + my @removed = split(/, /, $change->{removed}); + next if scalar(@added) <= 1 && scalar(@removed) <= 1; + + # remove current change + splice(@{$operation->{changes}}, $i, 1); + + # restructure into added/removed for each flag + my %flags; + foreach my $added (@added) { + my ($value, $name) = $added =~ /^((.+).)$/; + $flags{$name}{added} = $value; + $flags{$name}{removed} |= ''; + } + foreach my $removed (@removed) { + my ($value, $name) = $removed =~ /^((.+).)$/; + $flags{$name}{added} |= ''; + $flags{$name}{removed} = $value; + } - # split multiple flag changes (must be processed last) - if ($change->{fieldname} eq 'flagtypes.name') { - my @added = split(/, /, $change->{added}); - my @removed = split(/, /, $change->{removed}); - next if scalar(@added) <= 1 && scalar(@removed) <= 1; - # remove current change - splice(@{$operation->{changes}}, $i, 1); - # restructure into added/removed for each flag - my %flags; - foreach my $added (@added) { - my ($value, $name) = $added =~ /^((.+).)$/; - $flags{$name}{added} = $value; - $flags{$name}{removed} |= ''; - } - foreach my $removed (@removed) { - my ($value, $name) = $removed =~ /^((.+).)$/; - $flags{$name}{added} |= ''; - $flags{$name}{removed} = $value; - } - # clone current change, modify and insert - foreach my $flag (sort keys %flags) { - my $flag_change = {}; - foreach my $key (keys %$change) { - $flag_change->{$key} = $change->{$key}; - } - $flag_change->{removed} = $flags{$flag}{removed}; - $flag_change->{added} = $flags{$flag}{added}; - splice(@{$operation->{changes}}, $i, 0, $flag_change); - } - $i--; - } + # clone current change, modify and insert + foreach my $flag (sort keys %flags) { + my $flag_change = {}; + foreach my $key (keys %$change) { + $flag_change->{$key} = $change->{$key}; + } + $flag_change->{removed} = $flags{$flag}{removed}; + $flag_change->{added} = $flags{$flag}{added}; + splice(@{$operation->{changes}}, $i, 0, $flag_change); } + $i--; + } } + } - $user->visible_bugs([keys %visible_bug_ids]); + $user->visible_bugs([keys %visible_bug_ids]); - $vars->{'ih_activity'} = $activity; + $vars->{'ih_activity'} = $activity; } sub _add_duplicates { - # insert 'is a dupe of this bug' comment to allow js to display - # as activity - my ($bug_id, $activity) = @_; + # insert 'is a dupe of this bug' comment to allow js to display + # as activity - # we're ignoring pre-bugzilla 3.0 ".. has been marked as a duplicate .." - # comments because searching each comment's text is expensive. these - # legacy comments will not be visible at all in the bug's comment/activity - # stream. bug 928786 deals with migrating those comments to be stored as - # CMT_HAS_DUPE instead. + my ($bug_id, $activity) = @_; - my $dbh = Bugzilla->dbh; - my $sth = $dbh->prepare(" - SELECT profiles.login_name, " . - $dbh->sql_date_format('bug_when', '%Y.%m.%d %H:%i:%s') . ", + # we're ignoring pre-bugzilla 3.0 ".. has been marked as a duplicate .." + # comments because searching each comment's text is expensive. these + # legacy comments will not be visible at all in the bug's comment/activity + # stream. bug 928786 deals with migrating those comments to be stored as + # CMT_HAS_DUPE instead. + + my $dbh = Bugzilla->dbh; + my $sth = $dbh->prepare(" + SELECT profiles.login_name, " + . $dbh->sql_date_format('bug_when', '%Y.%m.%d %H:%i:%s') . ", extra_data FROM longdescs INNER JOIN profiles ON profiles.userid = longdescs.who WHERE bug_id = ? AND type = ? ORDER BY bug_when "); - $sth->execute($bug_id, CMT_HAS_DUPE); - - while (my($who, $when, $dupe_id) = $sth->fetchrow_array) { - my $entry = { - 'when' => $when, - 'who' => $who, - 'changes' => [ - { - 'removed' => '', - 'added' => $dupe_id, - 'attachid' => undef, - 'fieldname' => 'dupe', - 'dupe' => 1, - } - ], - }; - push @$activity, $entry; - } + $sth->execute($bug_id, CMT_HAS_DUPE); + + while (my ($who, $when, $dupe_id) = $sth->fetchrow_array) { + my $entry = { + 'when' => $when, + 'who' => $who, + 'changes' => [{ + 'removed' => '', + 'added' => $dupe_id, + 'attachid' => undef, + 'fieldname' => 'dupe', + 'dupe' => 1, + }], + }; + push @$activity, $entry; + } - return [ sort { $a->{when} cmp $b->{when} } @$activity ]; + return [sort { $a->{when} cmp $b->{when} } @$activity]; } sub install_before_final_checks { - my ($self, $args) = @_; - add_setting('inline_history', ['on', 'off'], 'off'); + my ($self, $args) = @_; + add_setting('inline_history', ['on', 'off'], 'off'); } __PACKAGE__->NAME; diff --git a/extensions/MoreBugUrl/Config.pm b/extensions/MoreBugUrl/Config.pm index e0eac5f8a..f8175bb4c 100644 --- a/extensions/MoreBugUrl/Config.pm +++ b/extensions/MoreBugUrl/Config.pm @@ -13,10 +13,8 @@ use warnings; use constant NAME => 'MoreBugUrl'; -use constant REQUIRED_MODULES => [ -]; +use constant REQUIRED_MODULES => []; -use constant OPTIONAL_MODULES => [ -]; +use constant OPTIONAL_MODULES => []; __PACKAGE__->NAME; diff --git a/extensions/MoreBugUrl/Extension.pm b/extensions/MoreBugUrl/Extension.pm index 0a4223e19..0fc29b2aa 100644 --- a/extensions/MoreBugUrl/Extension.pm +++ b/extensions/MoreBugUrl/Extension.pm @@ -14,41 +14,44 @@ use warnings; use parent qw(Bugzilla::Extension); use constant MORE_SUB_CLASSES => qw( - Bugzilla::Extension::MoreBugUrl::BitBucket - Bugzilla::Extension::MoreBugUrl::ReviewBoard - Bugzilla::Extension::MoreBugUrl::Rietveld - Bugzilla::Extension::MoreBugUrl::RT - Bugzilla::Extension::MoreBugUrl::GetSatisfaction - Bugzilla::Extension::MoreBugUrl::PHP - Bugzilla::Extension::MoreBugUrl::Redmine - Bugzilla::Extension::MoreBugUrl::Savane - Bugzilla::Extension::MoreBugUrl::Phabricator + Bugzilla::Extension::MoreBugUrl::BitBucket + Bugzilla::Extension::MoreBugUrl::ReviewBoard + Bugzilla::Extension::MoreBugUrl::Rietveld + Bugzilla::Extension::MoreBugUrl::RT + Bugzilla::Extension::MoreBugUrl::GetSatisfaction + Bugzilla::Extension::MoreBugUrl::PHP + Bugzilla::Extension::MoreBugUrl::Redmine + Bugzilla::Extension::MoreBugUrl::Savane + Bugzilla::Extension::MoreBugUrl::Phabricator ); # We need to update bug_see_also table because both # Rietveld and ReviewBoard were originally under Bugzilla/BugUrl/. sub install_update_db { - my $dbh = Bugzilla->dbh; + my $dbh = Bugzilla->dbh; - my $should_rename = $dbh->selectrow_array( - q{SELECT 1 FROM bug_see_also + my $should_rename = $dbh->selectrow_array( + q{SELECT 1 FROM bug_see_also WHERE class IN ('Bugzilla::BugUrl::Rietveld', - 'Bugzilla::BugUrl::ReviewBoard')}); - - if ($should_rename) { - my $sth = $dbh->prepare('UPDATE bug_see_also SET class = ? - WHERE class = ?'); - $sth->execute('Bugzilla::Extension::MoreBugUrl::ReviewBoard', - 'Bugzilla::BugUrl::ReviewBoard'); - - $sth->execute('Bugzilla::Extension::MoreBugUrl::Rietveld', - 'Bugzilla::BugUrl::Rietveld'); - } + 'Bugzilla::BugUrl::ReviewBoard')} + ); + + if ($should_rename) { + my $sth = $dbh->prepare( + 'UPDATE bug_see_also SET class = ? + WHERE class = ?' + ); + $sth->execute('Bugzilla::Extension::MoreBugUrl::ReviewBoard', + 'Bugzilla::BugUrl::ReviewBoard'); + + $sth->execute('Bugzilla::Extension::MoreBugUrl::Rietveld', + 'Bugzilla::BugUrl::Rietveld'); + } } sub bug_url_sub_classes { - my ($self, $args) = @_; - push @{ $args->{sub_classes} }, MORE_SUB_CLASSES; + my ($self, $args) = @_; + push @{$args->{sub_classes}}, MORE_SUB_CLASSES; } __PACKAGE__->NAME; diff --git a/extensions/MoreBugUrl/lib/BitBucket.pm b/extensions/MoreBugUrl/lib/BitBucket.pm index dcc85992d..683f9aeb3 100644 --- a/extensions/MoreBugUrl/lib/BitBucket.pm +++ b/extensions/MoreBugUrl/lib/BitBucket.pm @@ -18,23 +18,23 @@ use parent qw(Bugzilla::BugUrl); ############################### sub should_handle { - my ($class, $uri) = @_; + my ($class, $uri) = @_; - # BitBucket issues have the form of - # bitbucket.org/user/project/issue/1234 - return (lc($uri->authority) eq "bitbucket.org" - && $uri->path =~ m|[^/]+/[^/]+/issue/\d+|i) ? 1 : 0; + # BitBucket issues have the form of + # bitbucket.org/user/project/issue/1234 + return (lc($uri->authority) eq "bitbucket.org" + && $uri->path =~ m|[^/]+/[^/]+/issue/\d+|i) ? 1 : 0; } sub _check_value { - my $class = shift; + my $class = shift; - my $uri = $class->SUPER::_check_value(@_); + my $uri = $class->SUPER::_check_value(@_); - my ($path) = $uri->path =~ m|([^/]+/[^/]+/issue/\d+)|i; - $uri = new URI("https://bitbucket.org/$path"); + my ($path) = $uri->path =~ m|([^/]+/[^/]+/issue/\d+)|i; + $uri = new URI("https://bitbucket.org/$path"); - return $uri; + return $uri; } 1; diff --git a/extensions/MoreBugUrl/lib/GetSatisfaction.pm b/extensions/MoreBugUrl/lib/GetSatisfaction.pm index 74951735b..34c86b65f 100644 --- a/extensions/MoreBugUrl/lib/GetSatisfaction.pm +++ b/extensions/MoreBugUrl/lib/GetSatisfaction.pm @@ -18,24 +18,24 @@ use parent qw(Bugzilla::BugUrl); ############################### sub should_handle { - my ($class, $uri) = @_; + my ($class, $uri) = @_; - # GetSatisfaction URLs only have one form: - # http(s)://getsatisfaction.com/PROJECT_NAME/topics/TOPIC_NAME - return (lc($uri->authority) eq 'getsatisfaction.com' - and $uri->path =~ m|^/[^/]+/topics/[^/]+$|) ? 1 : 0; + # GetSatisfaction URLs only have one form: + # http(s)://getsatisfaction.com/PROJECT_NAME/topics/TOPIC_NAME + return (lc($uri->authority) eq 'getsatisfaction.com' + and $uri->path =~ m|^/[^/]+/topics/[^/]+$|) ? 1 : 0; } sub _check_value { - my ($class, $uri) = @_; + my ($class, $uri) = @_; - $uri = $class->SUPER::_check_value($uri); + $uri = $class->SUPER::_check_value($uri); - # GetSatisfaction HTTP URLs redirect to HTTPS, so just use the HTTPS - # scheme. - $uri->scheme('https'); + # GetSatisfaction HTTP URLs redirect to HTTPS, so just use the HTTPS + # scheme. + $uri->scheme('https'); - return $uri; + return $uri; } 1; diff --git a/extensions/MoreBugUrl/lib/PHP.pm b/extensions/MoreBugUrl/lib/PHP.pm index 6f201d7b1..9419bcd14 100644 --- a/extensions/MoreBugUrl/lib/PHP.pm +++ b/extensions/MoreBugUrl/lib/PHP.pm @@ -18,27 +18,27 @@ use parent qw(Bugzilla::BugUrl); ############################### sub should_handle { - my ($class, $uri) = @_; + my ($class, $uri) = @_; - # PHP Bug URLs have only one form: - # https://bugs.php.net/bug.php?id=1234 - return (lc($uri->authority) eq 'bugs.php.net' - and $uri->path =~ m|/bug\.php$| - and $uri->query_param('id') =~ /^\d+$/) ? 1 : 0; + # PHP Bug URLs have only one form: + # https://bugs.php.net/bug.php?id=1234 + return (lc($uri->authority) eq 'bugs.php.net' + and $uri->path =~ m|/bug\.php$| + and $uri->query_param('id') =~ /^\d+$/) ? 1 : 0; } sub _check_value { - my $class = shift; + my $class = shift; - my $uri = $class->SUPER::_check_value(@_); + my $uri = $class->SUPER::_check_value(@_); - # PHP Bug URLs redirect to HTTPS, so just use the HTTPS scheme. - $uri->scheme('https'); + # PHP Bug URLs redirect to HTTPS, so just use the HTTPS scheme. + $uri->scheme('https'); - # And remove any # part if there is one. - $uri->fragment(undef); + # And remove any # part if there is one. + $uri->fragment(undef); - return $uri; + return $uri; } 1; diff --git a/extensions/MoreBugUrl/lib/Phabricator.pm b/extensions/MoreBugUrl/lib/Phabricator.pm index 818d9af8f..0822663ad 100644 --- a/extensions/MoreBugUrl/lib/Phabricator.pm +++ b/extensions/MoreBugUrl/lib/Phabricator.pm @@ -18,24 +18,25 @@ use parent qw(Bugzilla::BugUrl); ############################### sub should_handle { - my ($class, $uri) = @_; - return ($uri->path =~ m|^/T\d+$|) ? 1 : 0; + my ($class, $uri) = @_; + return ($uri->path =~ m|^/T\d+$|) ? 1 : 0; } sub _check_value { - my $class = shift; + my $class = shift; - my $uri = $class->SUPER::_check_value(@_); + my $uri = $class->SUPER::_check_value(@_); - # Phabricator URLs have only one form: - # http://example.com/T111 + # Phabricator URLs have only one form: + # http://example.com/T111 - # Make sure there are no query parameters. - $uri->query(undef); - # And remove any # part if there is one. - $uri->fragment(undef); + # Make sure there are no query parameters. + $uri->query(undef); - return $uri; + # And remove any # part if there is one. + $uri->fragment(undef); + + return $uri; } 1; diff --git a/extensions/MoreBugUrl/lib/RT.pm b/extensions/MoreBugUrl/lib/RT.pm index acb90cf39..54dc2dd4c 100644 --- a/extensions/MoreBugUrl/lib/RT.pm +++ b/extensions/MoreBugUrl/lib/RT.pm @@ -18,24 +18,25 @@ use parent qw(Bugzilla::BugUrl); ############################### sub should_handle { - my ($class, $uri) = @_; - - # RT URLs can look like various things: - # http://example.com/rt/Ticket/Display.html?id=1234 - # https://example.com/Public/Bug/Display.html?id=1234 - return ($uri->path =~ m|/Display\.html$| - and $uri->query_param('id') =~ /^\d+$/) ? 1 : 0; + my ($class, $uri) = @_; + + # RT URLs can look like various things: + # http://example.com/rt/Ticket/Display.html?id=1234 + # https://example.com/Public/Bug/Display.html?id=1234 + return ($uri->path =~ m|/Display\.html$| and $uri->query_param('id') =~ /^\d+$/) + ? 1 + : 0; } sub _check_value { - my $class = shift; + my $class = shift; - my $uri = $class->SUPER::_check_value(@_); + my $uri = $class->SUPER::_check_value(@_); - # And remove any # part if there is one. - $uri->fragment(undef); + # And remove any # part if there is one. + $uri->fragment(undef); - return $uri; + return $uri; } 1; diff --git a/extensions/MoreBugUrl/lib/Redmine.pm b/extensions/MoreBugUrl/lib/Redmine.pm index 57a071239..712dab197 100644 --- a/extensions/MoreBugUrl/lib/Redmine.pm +++ b/extensions/MoreBugUrl/lib/Redmine.pm @@ -18,24 +18,25 @@ use parent qw(Bugzilla::BugUrl); ############################### sub should_handle { - my ($class, $uri) = @_; - return ($uri->path =~ m|/issues/\d+$|) ? 1 : 0; + my ($class, $uri) = @_; + return ($uri->path =~ m|/issues/\d+$|) ? 1 : 0; } sub _check_value { - my $class = shift; + my $class = shift; - my $uri = $class->SUPER::_check_value(@_); + my $uri = $class->SUPER::_check_value(@_); - # Redmine URLs have only one form: - # http://demo.redmine.com/issues/111 + # Redmine URLs have only one form: + # http://demo.redmine.com/issues/111 - # Make sure there are no query parameters. - $uri->query(undef); - # And remove any # part if there is one. - $uri->fragment(undef); + # Make sure there are no query parameters. + $uri->query(undef); - return $uri; + # And remove any # part if there is one. + $uri->fragment(undef); + + return $uri; } 1; diff --git a/extensions/MoreBugUrl/lib/ReviewBoard.pm b/extensions/MoreBugUrl/lib/ReviewBoard.pm index af5ff0684..48cbecf11 100644 --- a/extensions/MoreBugUrl/lib/ReviewBoard.pm +++ b/extensions/MoreBugUrl/lib/ReviewBoard.pm @@ -18,29 +18,30 @@ use parent qw(Bugzilla::BugUrl); ############################### sub should_handle { - my ($class, $uri) = @_; - return ($uri->path =~ m|/r/\d+/?$|) ? 1 : 0; + my ($class, $uri) = @_; + return ($uri->path =~ m|/r/\d+/?$|) ? 1 : 0; } sub _check_value { - my $class = shift; + my $class = shift; - my $uri = $class->SUPER::_check_value(@_); + my $uri = $class->SUPER::_check_value(@_); - # Review Board URLs have only one form (the trailing slash is optional): - # http://reviews.reviewboard.org/r/111/ + # Review Board URLs have only one form (the trailing slash is optional): + # http://reviews.reviewboard.org/r/111/ - # Make sure there are no query parameters. - $uri->query(undef); - # And remove any # part if there is one. - $uri->fragment(undef); + # Make sure there are no query parameters. + $uri->query(undef); - # make sure the trailing slash is present - if ($uri->path !~ m|/$|) { - $uri->path($uri->path . '/'); - } + # And remove any # part if there is one. + $uri->fragment(undef); - return $uri; + # make sure the trailing slash is present + if ($uri->path !~ m|/$|) { + $uri->path($uri->path . '/'); + } + + return $uri; } 1; diff --git a/extensions/MoreBugUrl/lib/Rietveld.pm b/extensions/MoreBugUrl/lib/Rietveld.pm index a4bf08492..55a18b0b2 100644 --- a/extensions/MoreBugUrl/lib/Rietveld.pm +++ b/extensions/MoreBugUrl/lib/Rietveld.pm @@ -18,32 +18,34 @@ use parent qw(Bugzilla::BugUrl); ############################### sub should_handle { - my ($class, $uri) = @_; - return ($uri->authority =~ /\.appspot\.com$/i - and $uri->path =~ m#^/\d+(?:/|/show)?$#) ? 1 : 0; + my ($class, $uri) = @_; + return ($uri->authority =~ /\.appspot\.com$/i + and $uri->path =~ m#^/\d+(?:/|/show)?$#) ? 1 : 0; } sub _check_value { - my ($class, $uri) = @_; - - $uri = $class->SUPER::_check_value($uri); - - # Rietveld URLs have three forms: - # http(s)://example.appspot.com/1234 - # http(s)://example.appspot.com/1234/ - # http(s)://example.appspot.com/1234/show - if ($uri->path =~ m#^/(\d+)(?:/|/show)$#) { - # This is the shortest standard URL form for Rietveld issues, - # and so we reduce all URLs to this. - $uri->path('/' . $1); - } - - # Make sure there are no query parameters. - $uri->query(undef); - # And remove any # part if there is one. - $uri->fragment(undef); - - return $uri; + my ($class, $uri) = @_; + + $uri = $class->SUPER::_check_value($uri); + + # Rietveld URLs have three forms: + # http(s)://example.appspot.com/1234 + # http(s)://example.appspot.com/1234/ + # http(s)://example.appspot.com/1234/show + if ($uri->path =~ m#^/(\d+)(?:/|/show)$#) { + + # This is the shortest standard URL form for Rietveld issues, + # and so we reduce all URLs to this. + $uri->path('/' . $1); + } + + # Make sure there are no query parameters. + $uri->query(undef); + + # And remove any # part if there is one. + $uri->fragment(undef); + + return $uri; } 1; diff --git a/extensions/MoreBugUrl/lib/Savane.pm b/extensions/MoreBugUrl/lib/Savane.pm index 5b35bbf7d..717878b33 100644 --- a/extensions/MoreBugUrl/lib/Savane.pm +++ b/extensions/MoreBugUrl/lib/Savane.pm @@ -18,24 +18,27 @@ use parent qw(Bugzilla::BugUrl); ############################### sub should_handle { - my ($class, $uri) = @_; - # Savane URLs look like the following (the index.php is optional): - # https://savannah.gnu.org/bugs/index.php?107657 - # https://savannah.gnu.org/patch/index.php?107657 - # https://savannah.gnu.org/support/index.php?107657 - # https://savannah.gnu.org/task/index.php?107657 - return ($uri->as_string =~ m|/(bugs\|patch\|support\|task)/(index\.php)?\?\d+$|) ? 1 : 0; + my ($class, $uri) = @_; + + # Savane URLs look like the following (the index.php is optional): + # https://savannah.gnu.org/bugs/index.php?107657 + # https://savannah.gnu.org/patch/index.php?107657 + # https://savannah.gnu.org/support/index.php?107657 + # https://savannah.gnu.org/task/index.php?107657 + return ($uri->as_string =~ m|/(bugs\|patch\|support\|task)/(index\.php)?\?\d+$|) + ? 1 + : 0; } sub _check_value { - my $class = shift; + my $class = shift; - my $uri = $class->SUPER::_check_value(@_); + my $uri = $class->SUPER::_check_value(@_); - # And remove any # part if there is one. - $uri->fragment(undef); + # And remove any # part if there is one. + $uri->fragment(undef); - return $uri; + return $uri; } 1; diff --git a/extensions/OldBugMove/Extension.pm b/extensions/OldBugMove/Extension.pm index 375707fd2..53c26fc8d 100644 --- a/extensions/OldBugMove/Extension.pm +++ b/extensions/OldBugMove/Extension.pm @@ -29,172 +29,172 @@ use constant VERSION => BUGZILLA_VERSION; use constant CMT_MOVED_TO => 4; sub install_update_db { - my $reso_type = Bugzilla::Field::Choice->type('resolution'); - my $moved_reso = $reso_type->new({ name => 'MOVED' }); - # We make the MOVED resolution inactive, so that it doesn't show up - # as a valid drop-down option. - if ($moved_reso) { - $moved_reso->set_is_active(0); - $moved_reso->update(); - } - else { - print "Creating the MOVED resolution...\n"; - $reso_type->create( - { value => 'MOVED', sortkey => '30000', isactive => 0 }); - } + my $reso_type = Bugzilla::Field::Choice->type('resolution'); + my $moved_reso = $reso_type->new({name => 'MOVED'}); + + # We make the MOVED resolution inactive, so that it doesn't show up + # as a valid drop-down option. + if ($moved_reso) { + $moved_reso->set_is_active(0); + $moved_reso->update(); + } + else { + print "Creating the MOVED resolution...\n"; + $reso_type->create({value => 'MOVED', sortkey => '30000', isactive => 0}); + } } sub config_add_panels { - my ($self, $args) = @_; - my $modules = $args->{'panel_modules'}; - $modules->{'OldBugMove'} = 'Bugzilla::Extension::OldBugMove::Params'; + my ($self, $args) = @_; + my $modules = $args->{'panel_modules'}; + $modules->{'OldBugMove'} = 'Bugzilla::Extension::OldBugMove::Params'; } sub template_before_create { - my ($self, $args) = @_; - my $config = $args->{config}; + my ($self, $args) = @_; + my $config = $args->{config}; - my $constants = $config->{CONSTANTS}; - $constants->{CMT_MOVED_TO} = CMT_MOVED_TO; + my $constants = $config->{CONSTANTS}; + $constants->{CMT_MOVED_TO} = CMT_MOVED_TO; - my $vars = $config->{VARIABLES}; - $vars->{oldbugmove_user_is_mover} = \&_user_is_mover; + my $vars = $config->{VARIABLES}; + $vars->{oldbugmove_user_is_mover} = \&_user_is_mover; } sub object_before_delete { - my ($self, $args) = @_; - my $object = $args->{'object'}; - if ($object->isa('Bugzilla::Field::Choice::resolution')) { - if ($object->name eq 'MOVED') { - ThrowUserError('oldbugmove_no_delete_moved'); - } + my ($self, $args) = @_; + my $object = $args->{'object'}; + if ($object->isa('Bugzilla::Field::Choice::resolution')) { + if ($object->name eq 'MOVED') { + ThrowUserError('oldbugmove_no_delete_moved'); } + } } sub object_before_set { - my ($self, $args) = @_; - my ($object, $field) = @$args{qw(object field)}; - if ($field eq 'resolution' and $object->isa('Bugzilla::Bug')) { - # Store the old value so that end_of_set can check it. - $object->{'_oldbugmove_old_resolution'} = $object->resolution; - } + my ($self, $args) = @_; + my ($object, $field) = @$args{qw(object field)}; + if ($field eq 'resolution' and $object->isa('Bugzilla::Bug')) { + + # Store the old value so that end_of_set can check it. + $object->{'_oldbugmove_old_resolution'} = $object->resolution; + } } sub object_end_of_set { - my ($self, $args) = @_; - my ($object, $field) = @$args{qw(object field)}; - if ($field eq 'resolution' and $object->isa('Bugzilla::Bug')) { - my $old_value = delete $object->{'_oldbugmove_old_resolution'}; - return if $old_value eq $object->resolution; - if ($object->resolution eq 'MOVED') { - $object->add_comment('', { type => CMT_MOVED_TO, - extra_data => Bugzilla->user->login }); - } + my ($self, $args) = @_; + my ($object, $field) = @$args{qw(object field)}; + if ($field eq 'resolution' and $object->isa('Bugzilla::Bug')) { + my $old_value = delete $object->{'_oldbugmove_old_resolution'}; + return if $old_value eq $object->resolution; + if ($object->resolution eq 'MOVED') { + $object->add_comment('', + {type => CMT_MOVED_TO, extra_data => Bugzilla->user->login}); } + } } sub object_end_of_set_all { - my ($self, $args) = @_; - my $object = $args->{'object'}; + my ($self, $args) = @_; + my $object = $args->{'object'}; - if ($object->isa('Bugzilla::Bug') and Bugzilla->input_params->{'oldbugmove'}) { - my $new_status = Bugzilla->params->{'duplicate_or_move_bug_status'}; - $object->set_bug_status($new_status, { resolution => 'MOVED' }); - } + if ($object->isa('Bugzilla::Bug') and Bugzilla->input_params->{'oldbugmove'}) { + my $new_status = Bugzilla->params->{'duplicate_or_move_bug_status'}; + $object->set_bug_status($new_status, {resolution => 'MOVED'}); + } } sub object_validators { - my ($self, $args) = @_; - my ($class, $validators) = @$args{qw(class validators)}; - if ($class->isa('Bugzilla::Comment')) { - my $extra_data_validator = $validators->{extra_data}; - $validators->{extra_data} = - sub { _check_comment_extra_data($extra_data_validator, @_) }; - } - elsif ($class->isa('Bugzilla::Bug')) { - my $reso_validator = $validators->{resolution}; - $validators->{resolution} = - sub { _check_bug_resolution($reso_validator, @_) }; - } + my ($self, $args) = @_; + my ($class, $validators) = @$args{qw(class validators)}; + if ($class->isa('Bugzilla::Comment')) { + my $extra_data_validator = $validators->{extra_data}; + $validators->{extra_data} + = sub { _check_comment_extra_data($extra_data_validator, @_) }; + } + elsif ($class->isa('Bugzilla::Bug')) { + my $reso_validator = $validators->{resolution}; + $validators->{resolution} = sub { _check_bug_resolution($reso_validator, @_) }; + } } sub _check_bug_resolution { - my $original_validator = shift; - my ($invocant, $resolution) = @_; - - if ($resolution eq 'MOVED' && $invocant->resolution ne 'MOVED' - && !Bugzilla->input_params->{'oldbugmove'}) - { - # MOVED has a special meaning and can only be used when - # really moving bugs to another installation. - ThrowUserError('oldbugmove_no_manual_move'); - } - - return $original_validator->(@_); + my $original_validator = shift; + my ($invocant, $resolution) = @_; + + if ( $resolution eq 'MOVED' + && $invocant->resolution ne 'MOVED' + && !Bugzilla->input_params->{'oldbugmove'}) + { + # MOVED has a special meaning and can only be used when + # really moving bugs to another installation. + ThrowUserError('oldbugmove_no_manual_move'); + } + + return $original_validator->(@_); } sub _check_comment_extra_data { - my $original_validator = shift; - my ($invocant, $extra_data, undef, $params) = @_; - my $type = blessed($invocant) ? $invocant->type : $params->{type}; - - if ($type == CMT_MOVED_TO) { - return Bugzilla::User->check($extra_data)->login; - } - return $original_validator->(@_); + my $original_validator = shift; + my ($invocant, $extra_data, undef, $params) = @_; + my $type = blessed($invocant) ? $invocant->type : $params->{type}; + + if ($type == CMT_MOVED_TO) { + return Bugzilla::User->check($extra_data)->login; + } + return $original_validator->(@_); } sub bug_end_of_update { - my ($self, $args) = @_; - my ($bug, $old_bug, $changes) = @$args{qw(bug old_bug changes)}; - if (defined $changes->{'resolution'} - and $changes->{'resolution'}->[1] eq 'MOVED') - { - $self->_move_bug($bug, $old_bug); - } + my ($self, $args) = @_; + my ($bug, $old_bug, $changes) = @$args{qw(bug old_bug changes)}; + if (defined $changes->{'resolution'} + and $changes->{'resolution'}->[1] eq 'MOVED') + { + $self->_move_bug($bug, $old_bug); + } } sub _move_bug { - my ($self, $bug, $old_bug) = @_; - - my $dbh = Bugzilla->dbh; - my $template = Bugzilla->template; - - _user_is_mover(Bugzilla->user) - or ThrowUserError("auth_failure", { action => 'move', - object => 'bugs' }); - - # Don't export the new status and resolution. We want the current - # ones. - local $Storable::forgive_me = 1; - my $export_me = dclone($bug); - $export_me->{bug_status} = $old_bug->bug_status; - delete $export_me->{status}; - $export_me->{resolution} = $old_bug->resolution; - - # Prepare and send all data about these bugs to the new database - my $to = Bugzilla->params->{'move-to-address'}; - $to =~ s/@/\@/; - my $from = Bugzilla->params->{'mailfrom'}; - $from =~ s/@/\@/; - my $msg = "To: $to\n"; - $msg .= "From: Bugzilla <" . $from . ">\n"; - $msg .= "Subject: Moving bug " . $bug->id . "\n\n"; - my @fieldlist = (Bugzilla::Bug->fields, 'group', 'long_desc', - 'attachment', 'attachmentdata'); - my %displayfields = map { $_ => 1 } @fieldlist; - my $vars = { bugs => [$export_me], displayfields => \%displayfields }; - $template->process("bug/show.xml.tmpl", $vars, \$msg) - || ThrowTemplateError($template->error()); - $msg .= "\n"; - MessageToMTA($msg); + my ($self, $bug, $old_bug) = @_; + + my $dbh = Bugzilla->dbh; + my $template = Bugzilla->template; + + _user_is_mover(Bugzilla->user) + or ThrowUserError("auth_failure", {action => 'move', object => 'bugs'}); + + # Don't export the new status and resolution. We want the current + # ones. + local $Storable::forgive_me = 1; + my $export_me = dclone($bug); + $export_me->{bug_status} = $old_bug->bug_status; + delete $export_me->{status}; + $export_me->{resolution} = $old_bug->resolution; + + # Prepare and send all data about these bugs to the new database + my $to = Bugzilla->params->{'move-to-address'}; + $to =~ s/@/\@/; + my $from = Bugzilla->params->{'mailfrom'}; + $from =~ s/@/\@/; + my $msg = "To: $to\n"; + $msg .= "From: Bugzilla <" . $from . ">\n"; + $msg .= "Subject: Moving bug " . $bug->id . "\n\n"; + my @fieldlist = (Bugzilla::Bug->fields, 'group', 'long_desc', 'attachment', + 'attachmentdata'); + my %displayfields = map { $_ => 1 } @fieldlist; + my $vars = {bugs => [$export_me], displayfields => \%displayfields}; + $template->process("bug/show.xml.tmpl", $vars, \$msg) + || ThrowTemplateError($template->error()); + $msg .= "\n"; + MessageToMTA($msg); } sub _user_is_mover { - my $user = shift; + my $user = shift; - my @movers = map { trim($_) } split(',', Bugzilla->params->{'movers'}); - return ($user->id and grep($_ eq $user->login, @movers)) ? 1 : 0; + my @movers = map { trim($_) } split(',', Bugzilla->params->{'movers'}); + return ($user->id and grep($_ eq $user->login, @movers)) ? 1 : 0; } __PACKAGE__->NAME; diff --git a/extensions/OldBugMove/lib/Params.pm b/extensions/OldBugMove/lib/Params.pm index 05e3ed277..e1bab048b 100644 --- a/extensions/OldBugMove/lib/Params.pm +++ b/extensions/OldBugMove/lib/Params.pm @@ -16,23 +16,11 @@ use Bugzilla::Config::Common; our $sortkey = 700; use constant get_param_list => ( - { - name => 'move-to-url', - type => 't', - default => '' - }, - - { - name => 'move-to-address', - type => 't', - default => 'bugzilla-import' - }, - - { - name => 'movers', - type => 't', - default => '' - }, + {name => 'move-to-url', type => 't', default => ''}, + + {name => 'move-to-address', type => 't', default => 'bugzilla-import'}, + + {name => 'movers', type => 't', default => ''}, ); 1; diff --git a/extensions/SecureMail/Config.pm b/extensions/SecureMail/Config.pm index f1975c1c1..69074573f 100644 --- a/extensions/SecureMail/Config.pm +++ b/extensions/SecureMail/Config.pm @@ -25,25 +25,19 @@ use strict; use constant NAME => 'SecureMail'; use constant REQUIRED_MODULES => [ - { - package => 'Crypt-OpenPGP', - module => 'Crypt::OpenPGP', - # 1.02 added the ability for new() to take KeyRing objects for the - # PubRing argument. - version => '1.02', - # 1.04 hangs - https://rt.cpan.org/Public/Bug/Display.html?id=68018 - # blacklist => [ '1.04' ], - }, - { - package => 'Crypt-SMIME', - module => 'Crypt::SMIME', - version => 0, - }, - { - package => 'HTML-Tree', - module => 'HTML::Tree', - version => 0, - } + { + package => 'Crypt-OpenPGP', + module => 'Crypt::OpenPGP', + + # 1.02 added the ability for new() to take KeyRing objects for the + # PubRing argument. + version => '1.02', + + # 1.04 hangs - https://rt.cpan.org/Public/Bug/Display.html?id=68018 + # blacklist => [ '1.04' ], + }, + {package => 'Crypt-SMIME', module => 'Crypt::SMIME', version => 0,}, + {package => 'HTML-Tree', module => 'HTML::Tree', version => 0,} ]; __PACKAGE__->NAME; diff --git a/extensions/SecureMail/Extension.pm b/extensions/SecureMail/Extension.pm index 492ce0cb6..e1d7468f4 100644 --- a/extensions/SecureMail/Extension.pm +++ b/extensions/SecureMail/Extension.pm @@ -53,12 +53,12 @@ use constant SECURE_ALL => 2; # public_key text in the 'profiles' table - stores public key ############################################################################## sub install_update_db { - my ($self, $args) = @_; + my ($self, $args) = @_; - my $dbh = Bugzilla->dbh; - $dbh->bz_add_column('groups', 'secure_mail', - {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 0}); - $dbh->bz_add_column('profiles', 'public_key', { TYPE => 'LONGTEXT' }); + my $dbh = Bugzilla->dbh; + $dbh->bz_add_column('groups', 'secure_mail', + {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 0}); + $dbh->bz_add_column('profiles', 'public_key', {TYPE => 'LONGTEXT'}); } ############################################################################## @@ -66,516 +66,530 @@ sub install_update_db { ############################################################################## BEGIN { - *Bugzilla::Group::secure_mail = \&_group_secure_mail; - *Bugzilla::User::public_key = \&_user_public_key; + *Bugzilla::Group::secure_mail = \&_group_secure_mail; + *Bugzilla::User::public_key = \&_user_public_key; } sub _group_secure_mail { return $_[0]->{'secure_mail'}; } # We want to lazy-load the public_key. sub _user_public_key { - my $self = shift; - if (!exists $self->{public_key}) { - ($self->{public_key}) = Bugzilla->dbh->selectrow_array( - "SELECT public_key FROM profiles WHERE userid = ?", - undef, - $self->id - ); - } - return $self->{public_key}; + my $self = shift; + if (!exists $self->{public_key}) { + ($self->{public_key}) + = Bugzilla->dbh->selectrow_array( + "SELECT public_key FROM profiles WHERE userid = ?", + undef, $self->id); + } + return $self->{public_key}; } # Make sure generic functions know about the additional fields in the user # and group objects. sub object_columns { - my ($self, $args) = @_; - my $class = $args->{'class'}; - my $columns = $args->{'columns'}; + my ($self, $args) = @_; + my $class = $args->{'class'}; + my $columns = $args->{'columns'}; - if ($class->isa('Bugzilla::Group')) { - push(@$columns, 'secure_mail'); - } + if ($class->isa('Bugzilla::Group')) { + push(@$columns, 'secure_mail'); + } } # Plug appropriate validators so we can check the validity of the two # fields created by this extension, when new values are submitted. sub object_validators { - my ($self, $args) = @_; - my %args = %{ $args }; - my ($invocant, $validators) = @args{qw(class validators)}; + my ($self, $args) = @_; + my %args = %{$args}; + my ($invocant, $validators) = @args{qw(class validators)}; + + if ($invocant->isa('Bugzilla::Group')) { + $validators->{'secure_mail'} = \&Bugzilla::Object::check_boolean; + } + elsif ($invocant->isa('Bugzilla::User')) { + $validators->{'public_key'} = sub { + my ($self, $value) = @_; + $value = trim($value) || ''; + + return $value if $value eq ''; + + if ($value =~ /PUBLIC KEY/) { + + # PGP keys must be ASCII-armoured. + if (!Crypt::OpenPGP::Armour->unarmour($value)) { + ThrowUserError('securemail_invalid_key', + {errstr => Crypt::OpenPGP::Armour->errstr}); + } + } + elsif ($value =~ /BEGIN CERTIFICATE/) { - if ($invocant->isa('Bugzilla::Group')) { - $validators->{'secure_mail'} = \&Bugzilla::Object::check_boolean; - } - elsif ($invocant->isa('Bugzilla::User')) { - $validators->{'public_key'} = sub { - my ($self, $value) = @_; - $value = trim($value) || ''; - - return $value if $value eq ''; - - if ($value =~ /PUBLIC KEY/) { - # PGP keys must be ASCII-armoured. - if (!Crypt::OpenPGP::Armour->unarmour($value)) { - ThrowUserError('securemail_invalid_key', - { errstr => Crypt::OpenPGP::Armour->errstr }); - } - } - elsif ($value =~ /BEGIN CERTIFICATE/) { - # S/MIME Keys must be in PEM format (Base64-encoded X.509) - # - # Crypt::SMIME seems not to like tainted values - it claims - # they aren't scalars! - trick_taint($value); - - my $smime = Crypt::SMIME->new(); - eval { - $smime->setPublicKey([$value]); - }; - if ($@) { - ThrowUserError('securemail_invalid_key', - { errstr => $@ }); - } - } - else { - ThrowUserError('securemail_invalid_key'); - } - - return $value; - }; - } + # S/MIME Keys must be in PEM format (Base64-encoded X.509) + # + # Crypt::SMIME seems not to like tainted values - it claims + # they aren't scalars! + trick_taint($value); + + my $smime = Crypt::SMIME->new(); + eval { $smime->setPublicKey([$value]); }; + if ($@) { + ThrowUserError('securemail_invalid_key', {errstr => $@}); + } + } + else { + ThrowUserError('securemail_invalid_key'); + } + + return $value; + }; + } } # When creating a 'group' object, set up the secure_mail field appropriately. sub object_before_create { - my ($self, $args) = @_; - my $class = $args->{'class'}; - my $params = $args->{'params'}; + my ($self, $args) = @_; + my $class = $args->{'class'}; + my $params = $args->{'params'}; - if ($class->isa('Bugzilla::Group')) { - $params->{secure_mail} = Bugzilla->cgi->param('secure_mail'); - } + if ($class->isa('Bugzilla::Group')) { + $params->{secure_mail} = Bugzilla->cgi->param('secure_mail'); + } } # On update, make sure the updating process knows about our new columns. sub object_update_columns { - my ($self, $args) = @_; - my $object = $args->{'object'}; - my $columns = $args->{'columns'}; + my ($self, $args) = @_; + my $object = $args->{'object'}; + my $columns = $args->{'columns'}; - if ($object->isa('Bugzilla::Group')) { - # This seems like a convenient moment to extract this value... - $object->set('secure_mail', Bugzilla->cgi->param('secure_mail')); + if ($object->isa('Bugzilla::Group')) { - push(@$columns, 'secure_mail'); - } - elsif ($object->isa('Bugzilla::User')) { - push(@$columns, 'public_key'); - } + # This seems like a convenient moment to extract this value... + $object->set('secure_mail', Bugzilla->cgi->param('secure_mail')); + + push(@$columns, 'secure_mail'); + } + elsif ($object->isa('Bugzilla::User')) { + push(@$columns, 'public_key'); + } } # Handle the setting and changing of the public key. sub user_preferences { - my ($self, $args) = @_; - my $tab = $args->{'current_tab'}; - my $save = $args->{'save_changes'}; - my $handled = $args->{'handled'}; - my $vars = $args->{'vars'}; - my $params = Bugzilla->input_params; - - return unless $tab eq 'securemail'; - - # Create a new user object so we don't mess with the main one, as we - # don't know where it's been... - my $user = new Bugzilla::User(Bugzilla->user->id); - - if ($save) { - $user->set('public_key', $params->{'public_key'}); - $user->update(); - - # Send user a test email - if ($user->public_key) { - _send_test_email($user); - $vars->{'test_email_sent'} = 1; - } + my ($self, $args) = @_; + my $tab = $args->{'current_tab'}; + my $save = $args->{'save_changes'}; + my $handled = $args->{'handled'}; + my $vars = $args->{'vars'}; + my $params = Bugzilla->input_params; + + return unless $tab eq 'securemail'; + + # Create a new user object so we don't mess with the main one, as we + # don't know where it's been... + my $user = new Bugzilla::User(Bugzilla->user->id); + + if ($save) { + $user->set('public_key', $params->{'public_key'}); + $user->update(); + + # Send user a test email + if ($user->public_key) { + _send_test_email($user); + $vars->{'test_email_sent'} = 1; } + } - $vars->{'public_key'} = $user->public_key; + $vars->{'public_key'} = $user->public_key; - # Set the 'handled' scalar reference to true so that the caller - # knows the panel name is valid and that an extension took care of it. - $$handled = 1; + # Set the 'handled' scalar reference to true so that the caller + # knows the panel name is valid and that an extension took care of it. + $$handled = 1; } sub template_before_process { - my ($self, $args) = @_; - my $file = $args->{'file'}; - my $vars = $args->{'vars'}; - - # Bug dependency emails contain the subject of the dependent bug - # right before the diffs when a status has gone from open/closed - # or closed/open. We need to sanitize the subject of change.blocker - # similar to how we do referenced bugs - return unless - $file eq 'email/bugmail.html.tmpl' - || $file eq 'email/bugmail.txt.tmpl'; - - if (defined $vars->{diffs}) { - foreach my $change (@{ $vars->{diffs} }) { - next if !defined $change->{blocker}; - if (grep($_->secure_mail, @{ $change->{blocker}->groups_in })) { - $change->{blocker}->{short_desc} = "(Secure bug)"; - } - } + my ($self, $args) = @_; + my $file = $args->{'file'}; + my $vars = $args->{'vars'}; + + # Bug dependency emails contain the subject of the dependent bug + # right before the diffs when a status has gone from open/closed + # or closed/open. We need to sanitize the subject of change.blocker + # similar to how we do referenced bugs + return + unless $file eq 'email/bugmail.html.tmpl' + || $file eq 'email/bugmail.txt.tmpl'; + + if (defined $vars->{diffs}) { + foreach my $change (@{$vars->{diffs}}) { + next if !defined $change->{blocker}; + if (grep($_->secure_mail, @{$change->{blocker}->groups_in})) { + $change->{blocker}->{short_desc} = "(Secure bug)"; + } } + } } sub _send_test_email { - my ($user) = @_; - my $template = Bugzilla->template_inner($user->settings->{'lang'}->{'value'}); + my ($user) = @_; + my $template = Bugzilla->template_inner($user->settings->{'lang'}->{'value'}); - my $vars = { - to_user => $user->email, - }; + my $vars = {to_user => $user->email,}; - my $msg = ""; - $template->process("account/email/securemail-test.txt.tmpl", $vars, \$msg) - || ThrowTemplateError($template->error()); + my $msg = ""; + $template->process("account/email/securemail-test.txt.tmpl", $vars, \$msg) + || ThrowTemplateError($template->error()); - MessageToMTA($msg); + MessageToMTA($msg); } ############################################################################## # Encrypting the email ############################################################################## sub mailer_before_send { - my ($self, $args) = @_; - - my $email = $args->{'email'}; - my $body = $email->body; - - # Decide whether to make secure. - # This is a bit of a hack; it would be nice if it were more clear - # what sort a particular email is. - my $is_bugmail = $email->header('X-Bugzilla-Status') || - $email->header('X-Bugzilla-Type') eq 'request'; - my $is_passwordmail = !$is_bugmail && ($body =~ /cfmpw.*cxlpw/s); - my $is_test_email = $email->header('X-Bugzilla-Type') =~ /securemail-test/ ? 1 : 0; - my $is_whine_email = $email->header('X-Bugzilla-Type') eq 'whine' ? 1 : 0; - my $encrypt_header = $email->header('X-Bugzilla-Encrypt') ? 1 : 0; - - if ($is_bugmail - || $is_passwordmail - || $is_test_email - || $is_whine_email - || $encrypt_header - ) { - # Convert the email's To address into a User object - my $login = $email->header('To'); - my $emailsuffix = Bugzilla->params->{'emailsuffix'}; - $login =~ s/$emailsuffix$//; - my $user = new Bugzilla::User({ name => $login }); - - # Default to secure. (Of course, this means if this extension has a - # bug, lots of people are going to get bugmail falsely claiming their - # bugs are secure and they need to add a key...) - my $make_secure = SECURE_ALL; - - if ($is_bugmail) { - # This is also a bit of a hack, but there's no header with the - # bug ID in. So we take the first number in the subject. - my ($bug_id) = ($email->header('Subject') =~ /\[\D+(\d+)\]/); - my $bug = new Bugzilla::Bug($bug_id); - if (!_should_secure_bug($bug)) { - $make_secure = SECURE_NONE; - } - # If the insider group has securemail enabled.. - my $insider_group = Bugzilla::Group->new({ name => Bugzilla->params->{'insidergroup'} }); - if ($insider_group - && $insider_group->secure_mail - && $make_secure == SECURE_NONE) - { - my $comment_is_private = Bugzilla->dbh->selectcol_arrayref( - "SELECT isprivate FROM longdescs WHERE bug_id=? ORDER BY bug_when", - undef, $bug_id); - # Encrypt if there are private comments on an otherwise public bug - while ($body =~ /[\r\n]--- Comment #(\d+)/g) { - my $comment_number = $1; - if ($comment_number && $comment_is_private->[$comment_number]) { - $make_secure = SECURE_BODY; - last; - } - } - # Encrypt if updating a private attachment without a comment - if ($email->header('X-Bugzilla-Changed-Fields') - && $email->header('X-Bugzilla-Changed-Fields') =~ /Attachment #(\d+)/) - { - my $attachment = Bugzilla::Attachment->new($1); - if ($attachment && $attachment->isprivate) { - $make_secure = SECURE_BODY; - } - } - } - } - elsif ($is_passwordmail) { - # Mail is made unsecure only if the user does not have a public - # key and is not in any security groups. So specifying a public - # key OR being in a security group means the mail is kept secure - # (but, as noted above, the check is the other way around because - # we default to secure). - if ($user && - !$user->public_key && - !grep($_->secure_mail, @{ $user->groups })) - { - $make_secure = SECURE_NONE; - } - } - elsif ($is_whine_email) { - # When a whine email has one or more secure bugs in the body, then - # encrypt the entire email body. Subject can be left alone as it - # comes from the whine settings. - $make_secure = _should_secure_whine($email) ? SECURE_BODY : SECURE_NONE; - } - elsif ($encrypt_header) { - # Templates or code may set the X-Bugzilla-Encrypt header to - # trigger encryption of emails. Remove that header from the email. - $email->header_set('X-Bugzilla-Encrypt'); + my ($self, $args) = @_; + + my $email = $args->{'email'}; + my $body = $email->body; + + # Decide whether to make secure. + # This is a bit of a hack; it would be nice if it were more clear + # what sort a particular email is. + my $is_bugmail = $email->header('X-Bugzilla-Status') + || $email->header('X-Bugzilla-Type') eq 'request'; + my $is_passwordmail = !$is_bugmail && ($body =~ /cfmpw.*cxlpw/s); + my $is_test_email + = $email->header('X-Bugzilla-Type') =~ /securemail-test/ ? 1 : 0; + my $is_whine_email = $email->header('X-Bugzilla-Type') eq 'whine' ? 1 : 0; + my $encrypt_header = $email->header('X-Bugzilla-Encrypt') ? 1 : 0; + + if ( $is_bugmail + || $is_passwordmail + || $is_test_email + || $is_whine_email + || $encrypt_header) + { + # Convert the email's To address into a User object + my $login = $email->header('To'); + my $emailsuffix = Bugzilla->params->{'emailsuffix'}; + $login =~ s/$emailsuffix$//; + my $user = new Bugzilla::User({name => $login}); + + # Default to secure. (Of course, this means if this extension has a + # bug, lots of people are going to get bugmail falsely claiming their + # bugs are secure and they need to add a key...) + my $make_secure = SECURE_ALL; + + if ($is_bugmail) { + + # This is also a bit of a hack, but there's no header with the + # bug ID in. So we take the first number in the subject. + my ($bug_id) = ($email->header('Subject') =~ /\[\D+(\d+)\]/); + my $bug = new Bugzilla::Bug($bug_id); + if (!_should_secure_bug($bug)) { + $make_secure = SECURE_NONE; + } + + # If the insider group has securemail enabled.. + my $insider_group + = Bugzilla::Group->new({name => Bugzilla->params->{'insidergroup'}}); + if ( $insider_group + && $insider_group->secure_mail + && $make_secure == SECURE_NONE) + { + my $comment_is_private + = Bugzilla->dbh->selectcol_arrayref( + "SELECT isprivate FROM longdescs WHERE bug_id=? ORDER BY bug_when", + undef, $bug_id); + + # Encrypt if there are private comments on an otherwise public bug + while ($body =~ /[\r\n]--- Comment #(\d+)/g) { + my $comment_number = $1; + if ($comment_number && $comment_is_private->[$comment_number]) { + $make_secure = SECURE_BODY; + last; + } } - # If finding the user fails for some reason, but we determine we - # should be encrypting, we want to make the mail safe. An empty key - # does that. - my $public_key = $user ? $user->public_key : ''; - - # Check if the new bugmail prefix should be added to the subject. - my $add_new = ($email->header('X-Bugzilla-Type') eq 'new' && - $user && - $user->settings->{'bugmail_new_prefix'}->{'value'} eq 'on') ? 1 : 0; - - if ($make_secure == SECURE_NONE) { - # Filter the bug_links in HTML email in case the bugs the links - # point are "secured" bugs and the user may not be able to see - # the summaries. - _filter_bug_links($email); - } - else { - _make_secure($email, $public_key, $is_bugmail && $make_secure == SECURE_ALL, $add_new); + # Encrypt if updating a private attachment without a comment + if ( $email->header('X-Bugzilla-Changed-Fields') + && $email->header('X-Bugzilla-Changed-Fields') =~ /Attachment #(\d+)/) + { + my $attachment = Bugzilla::Attachment->new($1); + if ($attachment && $attachment->isprivate) { + $make_secure = SECURE_BODY; + } } + } + } + elsif ($is_passwordmail) { + + # Mail is made unsecure only if the user does not have a public + # key and is not in any security groups. So specifying a public + # key OR being in a security group means the mail is kept secure + # (but, as noted above, the check is the other way around because + # we default to secure). + if ($user && !$user->public_key && !grep($_->secure_mail, @{$user->groups})) { + $make_secure = SECURE_NONE; + } + } + elsif ($is_whine_email) { + + # When a whine email has one or more secure bugs in the body, then + # encrypt the entire email body. Subject can be left alone as it + # comes from the whine settings. + $make_secure = _should_secure_whine($email) ? SECURE_BODY : SECURE_NONE; + } + elsif ($encrypt_header) { + + # Templates or code may set the X-Bugzilla-Encrypt header to + # trigger encryption of emails. Remove that header from the email. + $email->header_set('X-Bugzilla-Encrypt'); + } + + # If finding the user fails for some reason, but we determine we + # should be encrypting, we want to make the mail safe. An empty key + # does that. + my $public_key = $user ? $user->public_key : ''; + + # Check if the new bugmail prefix should be added to the subject. + my $add_new + = ( $email->header('X-Bugzilla-Type') eq 'new' + && $user + && $user->settings->{'bugmail_new_prefix'}->{'value'} eq 'on') ? 1 : 0; + + if ($make_secure == SECURE_NONE) { + + # Filter the bug_links in HTML email in case the bugs the links + # point are "secured" bugs and the user may not be able to see + # the summaries. + _filter_bug_links($email); } + else { + _make_secure($email, $public_key, $is_bugmail && $make_secure == SECURE_ALL, + $add_new); + } + } } # Custom hook for bugzilla.mozilla.org (see bug 752400) sub bugmail_referenced_bugs { - my ($self, $args) = @_; - # Sanitise subjects of referenced bugs. - my $referenced_bugs = $args->{'referenced_bugs'}; - # No need to sanitise subjects if the entire email will be secured. - return if _should_secure_bug($args->{'updated_bug'}); - # Replace the subject if required - foreach my $ref (@$referenced_bugs) { - if (grep($_->secure_mail, @{ $ref->{'bug'}->groups_in })) { - $ref->{'short_desc'} = "(Secure bug)"; - } + my ($self, $args) = @_; + + # Sanitise subjects of referenced bugs. + my $referenced_bugs = $args->{'referenced_bugs'}; + + # No need to sanitise subjects if the entire email will be secured. + return if _should_secure_bug($args->{'updated_bug'}); + + # Replace the subject if required + foreach my $ref (@$referenced_bugs) { + if (grep($_->secure_mail, @{$ref->{'bug'}->groups_in})) { + $ref->{'short_desc'} = "(Secure bug)"; } + } } sub _should_secure_bug { - my ($bug) = @_; - # If there's a problem with the bug, err on the side of caution and mark it - # as secure. - return - !$bug - || $bug->{'error'} - || grep($_->secure_mail, @{ $bug->groups_in }); + my ($bug) = @_; + + # If there's a problem with the bug, err on the side of caution and mark it + # as secure. + return !$bug || $bug->{'error'} || grep($_->secure_mail, @{$bug->groups_in}); } sub _should_secure_whine { - my ($email) = @_; - my $should_secure = 0; - $email->walk_parts(sub { - my $part = shift; - my $content_type = $part->content_type; - return if !$content_type || $content_type !~ /^text\/plain/; - my $body = $part->body; - my @bugids = $body =~ /Bug (\d+):/g; - foreach my $id (@bugids) { - $id = trim($id); - next if !$id; - my $bug = new Bugzilla::Bug($id); - if ($bug && _should_secure_bug($bug)) { - $should_secure = 1; - last; - } - } - }); - return $should_secure ? 1 : 0; + my ($email) = @_; + my $should_secure = 0; + $email->walk_parts(sub { + my $part = shift; + my $content_type = $part->content_type; + return if !$content_type || $content_type !~ /^text\/plain/; + my $body = $part->body; + my @bugids = $body =~ /Bug (\d+):/g; + foreach my $id (@bugids) { + $id = trim($id); + next if !$id; + my $bug = new Bugzilla::Bug($id); + if ($bug && _should_secure_bug($bug)) { + $should_secure = 1; + last; + } + } + }); + return $should_secure ? 1 : 0; } sub _make_secure { - my ($email, $key, $sanitise_subject, $add_new) = @_; - - # Add header showing this email has been secured - $email->header_set('X-Bugzilla-Secure-Email', 'Yes'); - - my $subject = $email->header('Subject'); - my ($bug_id) = $subject =~ /\[\D+(\d+)\]/; - - my $key_type = 0; - if ($key && $key =~ /PUBLIC KEY/) { - $key_type = 'PGP'; + my ($email, $key, $sanitise_subject, $add_new) = @_; + + # Add header showing this email has been secured + $email->header_set('X-Bugzilla-Secure-Email', 'Yes'); + + my $subject = $email->header('Subject'); + my ($bug_id) = $subject =~ /\[\D+(\d+)\]/; + + my $key_type = 0; + if ($key && $key =~ /PUBLIC KEY/) { + $key_type = 'PGP'; + } + elsif ($key && $key =~ /BEGIN CERTIFICATE/) { + $key_type = 'S/MIME'; + } + + if ($key_type eq 'PGP') { + ################## + # PGP Encryption # + ################## + + my $pubring = new Crypt::OpenPGP::KeyRing(Data => $key); + my $pgp = new Crypt::OpenPGP(PubRing => $pubring); + + if (scalar $email->parts > 1) { + my $old_boundary = $email->{ct}{attributes}{boundary}; + my $to_encrypt = "Content-Type: " . $email->content_type . "\n\n"; + + # We need to do some fix up of each part for proper encoding and then + # stringify all parts for encrypting. We have to retain the old + # boundaries as well so that the email client can reconstruct the + # original message properly. + $email->walk_parts(\&_fix_encoding); + + $email->walk_parts(sub { + my ($part) = @_; + if ($sanitise_subject) { + _insert_subject($part, $subject); + } + return if $part->parts > 1; # Top-level + $to_encrypt .= "--$old_boundary\n" . $part->as_string . "\n"; + }); + $to_encrypt .= "--$old_boundary--"; + + # Now create the new properly formatted PGP parts containing the + # encrypted original message + my @new_parts = ( + Email::MIME->create( + attributes => + {content_type => 'application/pgp-encrypted', encoding => '7bit',}, + body => "Version: 1\n", + ), + Email::MIME->create( + attributes => { + content_type => 'application/octet-stream', + filename => 'encrypted.asc', + disposition => 'inline', + encoding => '7bit', + }, + body => _pgp_encrypt($pgp, $to_encrypt) + ), + ); + $email->parts_set(\@new_parts); + my $new_boundary = $email->{ct}{attributes}{boundary}; + + # Redo the old content type header with the new boundaries + # and other information needed for PGP + $email->header_set("Content-Type", + "multipart/encrypted; " + . "protocol=\"application/pgp-encrypted\"; " + . "boundary=\"$new_boundary\""); } - elsif ($key && $key =~ /BEGIN CERTIFICATE/) { - $key_type = 'S/MIME'; + else { + _fix_encoding($email); + if ($sanitise_subject) { + _insert_subject($email, $subject); + } + $email->body_set(_pgp_encrypt($pgp, $email->body)); } + } - if ($key_type eq 'PGP') { - ################## - # PGP Encryption # - ################## - - my $pubring = new Crypt::OpenPGP::KeyRing(Data => $key); - my $pgp = new Crypt::OpenPGP(PubRing => $pubring); - - if (scalar $email->parts > 1) { - my $old_boundary = $email->{ct}{attributes}{boundary}; - my $to_encrypt = "Content-Type: " . $email->content_type . "\n\n"; - - # We need to do some fix up of each part for proper encoding and then - # stringify all parts for encrypting. We have to retain the old - # boundaries as well so that the email client can reconstruct the - # original message properly. - $email->walk_parts(\&_fix_encoding); - - $email->walk_parts(sub { - my ($part) = @_; - if ($sanitise_subject) { - _insert_subject($part, $subject); - } - return if $part->parts > 1; # Top-level - $to_encrypt .= "--$old_boundary\n" . $part->as_string . "\n"; - }); - $to_encrypt .= "--$old_boundary--"; - - # Now create the new properly formatted PGP parts containing the - # encrypted original message - my @new_parts = ( - Email::MIME->create( - attributes => { - content_type => 'application/pgp-encrypted', - encoding => '7bit', - }, - body => "Version: 1\n", - ), - Email::MIME->create( - attributes => { - content_type => 'application/octet-stream', - filename => 'encrypted.asc', - disposition => 'inline', - encoding => '7bit', - }, - body => _pgp_encrypt($pgp, $to_encrypt) - ), - ); - $email->parts_set(\@new_parts); - my $new_boundary = $email->{ct}{attributes}{boundary}; - # Redo the old content type header with the new boundaries - # and other information needed for PGP - $email->header_set("Content-Type", - "multipart/encrypted; " . - "protocol=\"application/pgp-encrypted\"; " . - "boundary=\"$new_boundary\""); - } - else { - _fix_encoding($email); - if ($sanitise_subject) { - _insert_subject($email, $subject); - } - $email->body_set(_pgp_encrypt($pgp, $email->body)); - } + elsif ($key_type eq 'S/MIME') { + ##################### + # S/MIME Encryption # + ##################### + + $email->walk_parts(\&_fix_encoding); + + if ($sanitise_subject) { + $email->walk_parts(sub { _insert_subject($_[0], $subject) }); } - elsif ($key_type eq 'S/MIME') { - ##################### - # S/MIME Encryption # - ##################### + my $smime = Crypt::SMIME->new(); + my $encrypted; - $email->walk_parts(\&_fix_encoding); + eval { + $smime->setPublicKey([$key]); + $encrypted = $smime->encrypt($email->as_string()); + }; - if ($sanitise_subject) { - $email->walk_parts(sub { _insert_subject($_[0], $subject) }); - } + if (!$@) { - my $smime = Crypt::SMIME->new(); - my $encrypted; - - eval { - $smime->setPublicKey([$key]); - $encrypted = $smime->encrypt($email->as_string()); - }; - - if (!$@) { - # We can't replace the Email::MIME object, so we have to swap - # out its component parts. - my $enc_obj = new Email::MIME($encrypted); - $email->header_obj_set($enc_obj->header_obj()); - $email->parts_set([]); - $email->body_set($enc_obj->body()); - $email->content_type_set('application/pkcs7-mime'); - $email->charset_set('UTF-8') if Bugzilla->params->{'utf8'}; - } - else { - $email->body_set('Error during Encryption: ' . $@); - } + # We can't replace the Email::MIME object, so we have to swap + # out its component parts. + my $enc_obj = new Email::MIME($encrypted); + $email->header_obj_set($enc_obj->header_obj()); + $email->parts_set([]); + $email->body_set($enc_obj->body()); + $email->content_type_set('application/pkcs7-mime'); + $email->charset_set('UTF-8') if Bugzilla->params->{'utf8'}; } else { - # No encryption key provided; send a generic, safe email. - my $template = Bugzilla->template; - my $message; - my $vars = { - 'urlbase' => correct_urlbase(), - 'bug_id' => $bug_id, - 'maintainer' => Bugzilla->params->{'maintainer'} - }; - - $template->process('account/email/encryption-required.txt.tmpl', - $vars, \$message) - || ThrowTemplateError($template->error()); - - $email->parts_set([]); - $email->content_type_set('text/plain'); - $email->body_set($message); + $email->body_set('Error during Encryption: ' . $@); } + } + else { + # No encryption key provided; send a generic, safe email. + my $template = Bugzilla->template; + my $message; + my $vars = { + 'urlbase' => correct_urlbase(), + 'bug_id' => $bug_id, + 'maintainer' => Bugzilla->params->{'maintainer'} + }; - if ($sanitise_subject) { - # This is designed to still work if the admin changes the word - # 'bug' to something else. However, it could break if they change - # the format of the subject line in another way. - my $new = $add_new ? ' New:' : ''; - my $product = $email->header('X-Bugzilla-Product'); - my $component = $email->header('X-Bugzilla-Component'); - # Note: the $bug_id is required within the parentheses in order to keep - # gmail's threading algorithm happy. - $subject =~ s/($bug_id\])\s+(.*)$/$1$new (Secure bug $bug_id in $product :: $component)/; - $email->header_set('Subject', $subject); - } + $template->process('account/email/encryption-required.txt.tmpl', + $vars, \$message) + || ThrowTemplateError($template->error()); + + $email->parts_set([]); + $email->content_type_set('text/plain'); + $email->body_set($message); + } + + if ($sanitise_subject) { + + # This is designed to still work if the admin changes the word + # 'bug' to something else. However, it could break if they change + # the format of the subject line in another way. + my $new = $add_new ? ' New:' : ''; + my $product = $email->header('X-Bugzilla-Product'); + my $component = $email->header('X-Bugzilla-Component'); + + # Note: the $bug_id is required within the parentheses in order to keep + # gmail's threading algorithm happy. + $subject + =~ s/($bug_id\])\s+(.*)$/$1$new (Secure bug $bug_id in $product :: $component)/; + $email->header_set('Subject', $subject); + } } sub _pgp_encrypt { - my ($pgp, $text) = @_; - # "@" matches every key in the public key ring, which is fine, - # because there's only one key in our keyring. - # - # We use the CAST5 cipher because the Rijndael (AES) module doesn't - # like us for some reason I don't have time to debug fully. - # ("key must be an untainted string scalar") - my $encrypted = $pgp->encrypt(Data => $text, - Recipients => "@", - Cipher => 'CAST5', - Armour => 1); - if (!defined $encrypted) { - return 'Error during Encryption: ' . $pgp->errstr; - } - return $encrypted; + my ($pgp, $text) = @_; + + # "@" matches every key in the public key ring, which is fine, + # because there's only one key in our keyring. + # + # We use the CAST5 cipher because the Rijndael (AES) module doesn't + # like us for some reason I don't have time to debug fully. + # ("key must be an untainted string scalar") + my $encrypted = $pgp->encrypt( + Data => $text, + Recipients => "@", + Cipher => 'CAST5', + Armour => 1 + ); + if (!defined $encrypted) { + return 'Error during Encryption: ' . $pgp->errstr; + } + return $encrypted; } # Insert the subject into the part's body, as the subject of the message will @@ -583,77 +597,75 @@ sub _pgp_encrypt { # XXX this incorrectly assumes all parts of the message are the body # we should only alter parts who's parent is multipart/alternative sub _insert_subject { - my ($part, $subject) = @_; - my $content_type = $part->content_type or return; - if ($content_type =~ /^text\/plain/) { - if (!is_7bit_clean($subject)) { - $part->encoding_set('quoted-printable'); - } - $part->body_str_set("Subject: $subject\015\012\015\012" . $part->body_str); - } - elsif ($content_type =~ /^text\/html/) { - my $tree = HTML::Tree->new->parse_content($part->body_str); - my $body = $tree->look_down(qw(_tag body)); - $body->unshift_content(['div', "Subject: $subject"], ['br']); - _set_body_from_tree($part, $tree); + my ($part, $subject) = @_; + my $content_type = $part->content_type or return; + if ($content_type =~ /^text\/plain/) { + if (!is_7bit_clean($subject)) { + $part->encoding_set('quoted-printable'); } + $part->body_str_set("Subject: $subject\015\012\015\012" . $part->body_str); + } + elsif ($content_type =~ /^text\/html/) { + my $tree = HTML::Tree->new->parse_content($part->body_str); + my $body = $tree->look_down(qw(_tag body)); + $body->unshift_content(['div', "Subject: $subject"], ['br']); + _set_body_from_tree($part, $tree); + } } sub _fix_encoding { - my $part = shift; - - # don't touch the top-level part of multi-part mail - return if $part->parts > 1; - - # nothing to do if the part already has a charset - my $ct = parse_content_type($part->content_type); - my $charset = $ct->{attributes}{charset} - ? $ct->{attributes}{charset} - : ''; - return unless !$charset || $charset eq 'us-ascii'; - - if (Bugzilla->params->{utf8}) { - $part->charset_set('UTF-8'); - my $raw = $part->body_raw; - if (utf8::is_utf8($raw)) { - utf8::encode($raw); - $part->body_set($raw); - } + my $part = shift; + + # don't touch the top-level part of multi-part mail + return if $part->parts > 1; + + # nothing to do if the part already has a charset + my $ct = parse_content_type($part->content_type); + my $charset = $ct->{attributes}{charset} ? $ct->{attributes}{charset} : ''; + return unless !$charset || $charset eq 'us-ascii'; + + if (Bugzilla->params->{utf8}) { + $part->charset_set('UTF-8'); + my $raw = $part->body_raw; + if (utf8::is_utf8($raw)) { + utf8::encode($raw); + $part->body_set($raw); } - $part->encoding_set('quoted-printable'); + } + $part->encoding_set('quoted-printable'); } sub _filter_bug_links { - my ($email) = @_; - $email->walk_parts(sub { - my $part = shift; - _fix_encoding($part); - my $content_type = $part->content_type; - return if !$content_type || $content_type !~ /text\/html/; - my $tree = HTML::Tree->new->parse_content($part->body_str); - my @links = $tree->look_down( _tag => q{a}, class => qr/bz_bug_link/ ); - my $updated = 0; - foreach my $link (@links) { - my $href = $link->attr('href'); - my ($bug_id) = $href =~ /\Qshow_bug.cgi?id=\E(\d+)/; - my $bug = new Bugzilla::Bug($bug_id); - if ($bug && _should_secure_bug($bug)) { - $link->attr('title', '(secure bug)'); - $link->attr('class', 'bz_bug_link'); - $updated = 1; - } - } - if ($updated) { - _set_body_from_tree($part, $tree); - } - }); + my ($email) = @_; + $email->walk_parts(sub { + my $part = shift; + _fix_encoding($part); + my $content_type = $part->content_type; + return if !$content_type || $content_type !~ /text\/html/; + my $tree = HTML::Tree->new->parse_content($part->body_str); + my @links = $tree->look_down(_tag => q{a}, class => qr/bz_bug_link/); + my $updated = 0; + foreach my $link (@links) { + my $href = $link->attr('href'); + my ($bug_id) = $href =~ /\Qshow_bug.cgi?id=\E(\d+)/; + my $bug = new Bugzilla::Bug($bug_id); + if ($bug && _should_secure_bug($bug)) { + $link->attr('title', '(secure bug)'); + $link->attr('class', 'bz_bug_link'); + $updated = 1; + } + } + if ($updated) { + _set_body_from_tree($part, $tree); + } + }); } sub _set_body_from_tree { - my ($part, $tree) = @_; - $part->body_set($tree->as_HTML); - $part->charset_set('UTF-8') if Bugzilla->params->{'utf8'}; - $part->encoding_set('quoted-printable'); + my ($part, $tree) = @_; + $part->body_set($tree->as_HTML); + $part->charset_set('UTF-8') if Bugzilla->params->{'utf8'}; + $part->encoding_set('quoted-printable'); } __PACKAGE__->NAME; diff --git a/extensions/TypeSniffer/Config.pm b/extensions/TypeSniffer/Config.pm index 6ad03b362..72ae2bfce 100644 --- a/extensions/TypeSniffer/Config.pm +++ b/extensions/TypeSniffer/Config.pm @@ -25,16 +25,8 @@ use strict; use constant NAME => 'TypeSniffer'; use constant REQUIRED_MODULES => [ - { - package => 'File-MimeInfo', - module => 'File::MimeInfo::Magic', - version => '0' - }, - { - package => 'IO-stringy', - module => 'IO::Scalar', - version => '0' - }, + {package => 'File-MimeInfo', module => 'File::MimeInfo::Magic', version => '0'}, + {package => 'IO-stringy', module => 'IO::Scalar', version => '0'}, ]; -__PACKAGE__->NAME;
\ No newline at end of file +__PACKAGE__->NAME; diff --git a/extensions/TypeSniffer/Extension.pm b/extensions/TypeSniffer/Extension.pm index b788b5426..ed8eadca1 100644 --- a/extensions/TypeSniffer/Extension.pm +++ b/extensions/TypeSniffer/Extension.pm @@ -29,47 +29,48 @@ use IO::Scalar; our $VERSION = '0.02'; ################################################################################ # This extension uses magic to guess MIME types for data where the browser has -# told us it's application/octet-stream (probably because there's no file +# told us it's application/octet-stream (probably because there's no file # extension, or it's a text type with a non-.txt file extension). ################################################################################ sub attachment_process_data { - my ($self, $args) = @_; - my $attributes = $args->{'attributes'}; - my $params = Bugzilla->input_params; - - # If we have autodetected application/octet-stream from the Content-Type - # header, let's have a better go using a sniffer. - if ($params->{'contenttypemethod'} eq 'autodetect' && - $attributes->{'mimetype'} eq 'application/octet-stream') - { - # data attribute can be either scalar data or filehandle - # bugzilla.org/docs/3.6/en/html/api/Bugzilla/Attachment.html#create - my $fh = $attributes->{'data'}; - if (!ref($fh)) { - my $data = $attributes->{'data'}; - $fh = new IO::Scalar \$data; - } - else { - # CGI.pm sends us an Fh that isn't actually an IO::Handle, but - # has a method for getting an actual handle out of it. - if (!$fh->isa('IO::Handle')) { - $fh = $fh->handle; - # ->handle returns an literal IO::Handle, even though the - # underlying object is a file. So we rebless it to be a proper - # IO::File object so that we can call ->seek on it and so on. - # Just in case CGI.pm fixes this some day, we check ->isa first. - if (!$fh->isa('IO::File')) { - bless $fh, 'IO::File'; - } - } - } - - my $mimetype = mimetype($fh); - $fh->seek(0, 0); - if ($mimetype) { - $attributes->{'mimetype'} = $mimetype; + my ($self, $args) = @_; + my $attributes = $args->{'attributes'}; + my $params = Bugzilla->input_params; + + # If we have autodetected application/octet-stream from the Content-Type + # header, let's have a better go using a sniffer. + if ( $params->{'contenttypemethod'} eq 'autodetect' + && $attributes->{'mimetype'} eq 'application/octet-stream') + { + # data attribute can be either scalar data or filehandle + # bugzilla.org/docs/3.6/en/html/api/Bugzilla/Attachment.html#create + my $fh = $attributes->{'data'}; + if (!ref($fh)) { + my $data = $attributes->{'data'}; + $fh = new IO::Scalar \$data; + } + else { + # CGI.pm sends us an Fh that isn't actually an IO::Handle, but + # has a method for getting an actual handle out of it. + if (!$fh->isa('IO::Handle')) { + $fh = $fh->handle; + + # ->handle returns an literal IO::Handle, even though the + # underlying object is a file. So we rebless it to be a proper + # IO::File object so that we can call ->seek on it and so on. + # Just in case CGI.pm fixes this some day, we check ->isa first. + if (!$fh->isa('IO::File')) { + bless $fh, 'IO::File'; } + } + } + + my $mimetype = mimetype($fh); + $fh->seek(0, 0); + if ($mimetype) { + $attributes->{'mimetype'} = $mimetype; } + } } __PACKAGE__->NAME; diff --git a/extensions/Voting/Config.pm b/extensions/Voting/Config.pm index 97c44933e..d72caa3cc 100644 --- a/extensions/Voting/Config.pm +++ b/extensions/Voting/Config.pm @@ -13,10 +13,8 @@ use warnings; use constant NAME => 'Voting'; -use constant REQUIRED_MODULES => [ -]; +use constant REQUIRED_MODULES => []; -use constant OPTIONAL_MODULES => [ -]; +use constant OPTIONAL_MODULES => []; __PACKAGE__->NAME; diff --git a/extensions/Voting/Extension.pm b/extensions/Voting/Extension.pm index b125933ce..2fd7d2e22 100644 --- a/extensions/Voting/Extension.pm +++ b/extensions/Voting/Extension.pm @@ -25,82 +25,84 @@ 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; ################ # Installation # ################ 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}); + } } ########### @@ -108,93 +110,98 @@ sub install_update_db { ########### 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); } + } } ############# @@ -202,27 +209,28 @@ sub bug_end_of_update { ############# 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; + 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; - } - } + 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; + } + } } ########### @@ -230,19 +238,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'; } ############### @@ -250,59 +258,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'); } @@ -311,35 +319,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]}); + } + else { + $votes = $default; } - return $votes; + } + return $votes; } ######### @@ -347,290 +356,321 @@ 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. - - my $bug_id = $input->{bug_id}; - my $bug = Bugzilla::Bug->check({ id => $bug_id, cache => 1 }) if $bug_id; - my $who_id = $input->{user_id} || $user->id; + # If a bug_id is given, and we're editing, we'll add it to the votes list. - # Logged-out users must specify a user_id. - Bugzilla->login(LOGIN_REQUIRED) if !$who_id; + my $bug_id = $input->{bug_id}; + my $bug = Bugzilla::Bug->check({id => $bug_id, cache => 1}) if $bug_id; + my $who_id = $input->{user_id} || $user->id; - my $who = Bugzilla::User->check({ id => $who_id }); + # Logged-out users must specify a user_id. + Bugzilla->login(LOGIN_REQUIRED) if !$who_id; - my $canedit = $user->id == $who->id; + my $who = Bugzilla::User->check({id => $who_id}); - $dbh->bz_start_transaction(); + my $canedit = $user->id == $who->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)); - } + $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 (@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->{'message'} = 'votes_recorded'; + 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->{'message'} = 'votes_recorded'; } ###################### @@ -638,233 +678,245 @@ 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})); - - 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 = Bugzilla::User->new($who)->login; - - push(@toomanyvotes_list, {id => $id, name => $name}); - } + 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 = Bugzilla::User->new($who)->login; + + 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); - - my $name = Bugzilla::User->new($who)->login; - 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')); - - 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); - } - # 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 + ); + + my $name = Bugzilla::User->new($who)->login; + 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')); + + 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); + } + + # 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 = shift; - my $bug = ref $id ? $id : new Bugzilla::Bug({ id => $id, cache => 1 }); - - 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('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 = shift; + my $bug = ref $id ? $id : new Bugzilla::Bug({id => $id, cache => 1}); + + 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; } - $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; } diff --git a/extensions/create.pl b/extensions/create.pl index 7c8693e28..f2aa3f933 100755 --- a/extensions/create.pl +++ b/extensions/create.pl @@ -27,42 +27,41 @@ my $base_dir = bz_locations()->{'extensionsdir'}; my $name = $ARGV[0] or ThrowUserError('extension_create_no_name'); $name = ucfirst($name); if ($name !~ /^[A-Z]/) { - ThrowUserError('extension_first_letter_caps', { name => $name }); + ThrowUserError('extension_first_letter_caps', {name => $name}); } -my $extension_dir = "$base_dir/$name"; -mkpath($extension_dir) +my $extension_dir = "$base_dir/$name"; +mkpath($extension_dir) || die "$extension_dir already exists or cannot be created.\n"; my $lcname = lc($name); foreach my $path (qw(lib docs/en/rst web template/en/default/hook), - "template/en/default/$lcname") + "template/en/default/$lcname") { - mkpath("$extension_dir/$path") || die "$extension_dir/$path: $!"; + mkpath("$extension_dir/$path") || die "$extension_dir/$path: $!"; } -my $template = Bugzilla->template; -my $vars = { name => $name, path => $extension_dir }; +my $template = Bugzilla->template; +my $vars = {name => $name, path => $extension_dir}; my %create_files = ( - 'config.pm.tmpl' => 'Config.pm', - 'extension.pm.tmpl' => 'Extension.pm', - 'util.pm.tmpl' => 'lib/Util.pm', - 'web-readme.txt.tmpl' => 'web/README', - 'hook-readme.txt.tmpl' => 'template/en/default/hook/README', - 'name-readme.txt.tmpl' => "template/en/default/$lcname/README", - 'index-admin.rst.tmpl' => "docs/en/rst/index-admin.rst", - 'index-user.rst.tmpl' => "docs/en/rst/index-user.rst", + 'config.pm.tmpl' => 'Config.pm', + 'extension.pm.tmpl' => 'Extension.pm', + 'util.pm.tmpl' => 'lib/Util.pm', + 'web-readme.txt.tmpl' => 'web/README', + 'hook-readme.txt.tmpl' => 'template/en/default/hook/README', + 'name-readme.txt.tmpl' => "template/en/default/$lcname/README", + 'index-admin.rst.tmpl' => "docs/en/rst/index-admin.rst", + 'index-user.rst.tmpl' => "docs/en/rst/index-user.rst", ); foreach my $template_file (keys %create_files) { - my $target = $create_files{$template_file}; - my $output; - $template->process("extensions/$template_file", $vars, \$output) - or ThrowTemplateError($template->error()); - open(my $fh, '>', "$extension_dir/$target") - or die "extension_dir/$target: $!"; - print $fh $output; - close($fh); + my $target = $create_files{$template_file}; + my $output; + $template->process("extensions/$template_file", $vars, \$output) + or ThrowTemplateError($template->error()); + open(my $fh, '>', "$extension_dir/$target") or die "extension_dir/$target: $!"; + print $fh $output; + close($fh); } say get_text('extension_created', $vars); |