diff options
author | Perl Tidy <perltidy@bugzilla.org> | 2018-12-05 21:38:52 +0100 |
---|---|---|
committer | Dylan William Hardison <dylan@hardison.net> | 2018-12-05 23:49:08 +0100 |
commit | 8ec8da0491ad89604700b3e29a227966f6d84ba1 (patch) | |
tree | 9d270f173330ca19700e0ba9f2ee931300646de1 /extensions/BMO | |
parent | a7bb5a65b71644d9efce5fed783ed545b9336548 (diff) | |
download | bugzilla-8ec8da0491ad89604700b3e29a227966f6d84ba1.tar.gz bugzilla-8ec8da0491ad89604700b3e29a227966f6d84ba1.tar.xz |
no bug - reformat all the code using the new perltidy rules
Diffstat (limited to 'extensions/BMO')
-rw-r--r-- | extensions/BMO/Config.pm | 21 | ||||
-rw-r--r-- | extensions/BMO/Extension.pm | 4648 | ||||
-rwxr-xr-x | extensions/BMO/bin/bug_1022707.pl | 6 | ||||
-rwxr-xr-x | extensions/BMO/bin/bug_1093952.pl | 52 | ||||
-rwxr-xr-x | extensions/BMO/bin/bug_1141452.pl | 110 | ||||
-rwxr-xr-x | extensions/BMO/bin/migrate-github-pull-requests.pl | 67 | ||||
-rw-r--r-- | extensions/BMO/lib/Constants.pm | 6 | ||||
-rw-r--r-- | extensions/BMO/lib/Data.pm | 403 | ||||
-rw-r--r-- | extensions/BMO/lib/FakeBug.pm | 28 | ||||
-rw-r--r-- | extensions/BMO/lib/Reports/Groups.pm | 485 | ||||
-rw-r--r-- | extensions/BMO/lib/Reports/Internship.pm | 135 | ||||
-rw-r--r-- | extensions/BMO/lib/Reports/ProductSecurity.pm | 76 | ||||
-rw-r--r-- | extensions/BMO/lib/Reports/Recruiting.pm | 126 | ||||
-rw-r--r-- | extensions/BMO/lib/Reports/ReleaseTracking.pm | 779 | ||||
-rw-r--r-- | extensions/BMO/lib/Reports/Triage.pm | 516 | ||||
-rw-r--r-- | extensions/BMO/lib/Reports/UserActivity.pm | 382 | ||||
-rw-r--r-- | extensions/BMO/lib/Util.pm | 111 | ||||
-rw-r--r-- | extensions/BMO/lib/WebService.pm | 68 | ||||
-rw-r--r-- | extensions/BMO/t/bounty_attachment.t | 93 |
19 files changed, 4066 insertions, 4046 deletions
diff --git a/extensions/BMO/Config.pm b/extensions/BMO/Config.pm index 153af24cb..e185b0b5d 100644 --- a/extensions/BMO/Config.pm +++ b/extensions/BMO/Config.pm @@ -28,24 +28,11 @@ use warnings; use constant NAME => 'BMO'; use constant REQUIRED_MODULES => [ - { - package => 'Tie-IxHash', - module => 'Tie::IxHash', - version => 0 - }, - { - package => 'Sys-Syslog', - module => 'Sys::Syslog', - version => 0 - }, - { - package => 'File-MimeInfo', - module => 'File::MimeInfo::Magic', - version => '0' - }, + {package => 'Tie-IxHash', module => 'Tie::IxHash', version => 0}, + {package => 'Sys-Syslog', module => 'Sys::Syslog', version => 0}, + {package => 'File-MimeInfo', module => 'File::MimeInfo::Magic', version => '0'}, ]; -use constant OPTIONAL_MODULES => [ -]; +use constant OPTIONAL_MODULES => []; __PACKAGE__->NAME; diff --git a/extensions/BMO/Extension.pm b/extensions/BMO/Extension.pm index 7499f0d1c..f4fb6fa32 100644 --- a/extensions/BMO/Extension.pm +++ b/extensions/BMO/Extension.pm @@ -72,296 +72,304 @@ our $VERSION = '0.1'; # BEGIN { - *Bugzilla::Bug::last_closed_date = \&_last_closed_date; - *Bugzilla::Bug::reporters_hw_os = \&_bug_reporters_hw_os; - *Bugzilla::Bug::is_unassigned = \&_bug_is_unassigned; - *Bugzilla::Bug::has_current_patch = \&_bug_has_current_patch; - *Bugzilla::Bug::missing_sec_approval = \&_bug_missing_sec_approval; - *Bugzilla::Product::default_security_group = \&_default_security_group; - *Bugzilla::Product::default_security_group_obj = \&_default_security_group_obj; - *Bugzilla::Product::group_always_settable = \&_group_always_settable; - *Bugzilla::Product::default_platform_id = \&_product_default_platform_id; - *Bugzilla::Product::default_op_sys_id = \&_product_default_op_sys_id; - *Bugzilla::Product::default_platform = \&_product_default_platform; - *Bugzilla::Product::default_op_sys = \&_product_default_op_sys; - *Bugzilla::check_default_product_security_group = \&_check_default_product_security_group; - *Bugzilla::Attachment::is_bounty_attachment = \&_attachment_is_bounty_attachment; - *Bugzilla::Attachment::bounty_details = \&_attachment_bounty_details; - *Bugzilla::Attachment::external_redirect = \&_attachment_external_redirect; - *Bugzilla::Attachment::can_review = \&_attachment_can_review; - *Bugzilla::Attachment::fetch_github_pr_diff = \&_attachment_fetch_github_pr_diff; + *Bugzilla::Bug::last_closed_date = \&_last_closed_date; + *Bugzilla::Bug::reporters_hw_os = \&_bug_reporters_hw_os; + *Bugzilla::Bug::is_unassigned = \&_bug_is_unassigned; + *Bugzilla::Bug::has_current_patch = \&_bug_has_current_patch; + *Bugzilla::Bug::missing_sec_approval = \&_bug_missing_sec_approval; + *Bugzilla::Product::default_security_group = \&_default_security_group; + *Bugzilla::Product::default_security_group_obj = \&_default_security_group_obj; + *Bugzilla::Product::group_always_settable = \&_group_always_settable; + *Bugzilla::Product::default_platform_id = \&_product_default_platform_id; + *Bugzilla::Product::default_op_sys_id = \&_product_default_op_sys_id; + *Bugzilla::Product::default_platform = \&_product_default_platform; + *Bugzilla::Product::default_op_sys = \&_product_default_op_sys; + *Bugzilla::check_default_product_security_group + = \&_check_default_product_security_group; + *Bugzilla::Attachment::is_bounty_attachment + = \&_attachment_is_bounty_attachment; + *Bugzilla::Attachment::bounty_details = \&_attachment_bounty_details; + *Bugzilla::Attachment::external_redirect = \&_attachment_external_redirect; + *Bugzilla::Attachment::can_review = \&_attachment_can_review; + *Bugzilla::Attachment::fetch_github_pr_diff + = \&_attachment_fetch_github_pr_diff; } sub template_before_process { - my ($self, $args) = @_; - my $file = $args->{'file'}; - my $vars = $args->{'vars'}; - - $vars->{'cf_hidden_in_product'} = \&cf_hidden_in_product; - - if ($file =~ /^list\/list/) { - # Purpose: enable correct sorting of list table - # Matched to changes in list/table.html.tmpl - my %db_order_column_name_map = ( - 'map_components.name' => 'component', - 'map_products.name' => 'product', - 'map_reporter.login_name' => 'reporter', - 'map_assigned_to.login_name' => 'assigned_to', - 'delta_ts' => 'opendate', - 'creation_ts' => 'changeddate', - ); - - my @orderstrings = split(/,\s*/, $vars->{'order'}); - - # contains field names of the columns being used to sort the table. - my @order_columns; - foreach my $o (@orderstrings) { - $o =~ s/bugs.//; - $o = $db_order_column_name_map{$o} if - grep($_ eq $o, keys(%db_order_column_name_map)); - next if (grep($_ eq $o, @order_columns)); - push(@order_columns, $o); - } + my ($self, $args) = @_; + my $file = $args->{'file'}; + my $vars = $args->{'vars'}; + + $vars->{'cf_hidden_in_product'} = \&cf_hidden_in_product; + + if ($file =~ /^list\/list/) { + + # Purpose: enable correct sorting of list table + # Matched to changes in list/table.html.tmpl + my %db_order_column_name_map = ( + 'map_components.name' => 'component', + 'map_products.name' => 'product', + 'map_reporter.login_name' => 'reporter', + 'map_assigned_to.login_name' => 'assigned_to', + 'delta_ts' => 'opendate', + 'creation_ts' => 'changeddate', + ); - $vars->{'order_columns'} = \@order_columns; + my @orderstrings = split(/,\s*/, $vars->{'order'}); - # fields that have a custom sortkey. (So they are correctly sorted - # when using js) - my @sortkey_fields = qw(bug_status resolution bug_severity priority - rep_platform op_sys); + # contains field names of the columns being used to sort the table. + my @order_columns; + foreach my $o (@orderstrings) { + $o =~ s/bugs.//; + $o = $db_order_column_name_map{$o} + if grep($_ eq $o, keys(%db_order_column_name_map)); + next if (grep($_ eq $o, @order_columns)); + push(@order_columns, $o); + } - my %columns_sortkey; - foreach my $field (@sortkey_fields) { - $columns_sortkey{$field} = _get_field_values_sort_key($field); - } - $columns_sortkey{'target_milestone'} = _get_field_values_sort_key('milestones'); + $vars->{'order_columns'} = \@order_columns; - $vars->{'columns_sortkey'} = \%columns_sortkey; + # fields that have a custom sortkey. (So they are correctly sorted + # when using js) + my @sortkey_fields = qw(bug_status resolution bug_severity priority + rep_platform op_sys); + + my %columns_sortkey; + foreach my $field (@sortkey_fields) { + $columns_sortkey{$field} = _get_field_values_sort_key($field); } - elsif ($file =~ /^bug\/create\/create[\.-](.*)/) { - my $format = $1; - if (!$vars->{'cloned_bug_id'}) { - # Allow status whiteboard values to be bookmarked - $vars->{'status_whiteboard'} = - Bugzilla->cgi->param('status_whiteboard') || ""; - } + $columns_sortkey{'target_milestone'} = _get_field_values_sort_key('milestones'); - # Purpose: for pretty product chooser - $vars->{'format'} = Bugzilla->cgi->param('format'); + $vars->{'columns_sortkey'} = \%columns_sortkey; + } + elsif ($file =~ /^bug\/create\/create[\.-](.*)/) { + my $format = $1; + if (!$vars->{'cloned_bug_id'}) { - if ($format eq 'doc.html.tmpl') { - my $versions = Bugzilla::Product->new({ name => 'Core' })->versions; - $vars->{'versions'} = [ reverse @$versions ]; - } - } - elsif ($file eq 'bug/edit.html.tmpl' || $file eq 'bug_modal/edit.html.tmpl') { - $vars->{split_cf_crash_signature} = $self->_split_crash_signature($vars); + # Allow status whiteboard values to be bookmarked + $vars->{'status_whiteboard'} = Bugzilla->cgi->param('status_whiteboard') || ""; } + # Purpose: for pretty product chooser + $vars->{'format'} = Bugzilla->cgi->param('format'); - if ($file =~ /^list\/list/ || $file =~ /^bug\/create\/create[\.-]/) { - # hack to allow the bug entry templates to use check_can_change_field - # to see if various field values should be available to the current user. - $vars->{'default'} = Bugzilla::Extension::BMO::FakeBug->new($vars->{'default'} || {}); + if ($format eq 'doc.html.tmpl') { + my $versions = Bugzilla::Product->new({name => 'Core'})->versions; + $vars->{'versions'} = [reverse @$versions]; } + } + elsif ($file eq 'bug/edit.html.tmpl' || $file eq 'bug_modal/edit.html.tmpl') { + $vars->{split_cf_crash_signature} = $self->_split_crash_signature($vars); + } - if ($file =~ /^attachment\/diff-header\./) { - my $attachid = $vars->{attachid} ? $vars->{attachid} : $vars->{newid}; - $vars->{attachment} = Bugzilla::Attachment->new({ id => $attachid, cache => 1 }) - if $attachid; - } - if ($file =~ /^admin\/products\/(create|edit)\./) { - my $product = $vars->{product}; - my $security_groups = Bugzilla::Group->match({ isbuggroup => 1, isactive => 1 }); - if ($product) { - # If set group is not active currently, we add it into the list - if (!grep($_->name eq $product->default_security_group, @$security_groups)) { - push(@$security_groups, $product->default_security_group_obj); - @$security_groups = sort { $a->name cmp $b->name } @$security_groups; - } - } - $vars->{security_groups} = $security_groups; - } -} + if ($file =~ /^list\/list/ || $file =~ /^bug\/create\/create[\.-]/) { -sub page_before_template { - my ($self, $args) = @_; - my $page = $args->{'page_id'}; - my $vars = $args->{'vars'}; + # hack to allow the bug entry templates to use check_can_change_field + # to see if various field values should be available to the current user. + $vars->{'default'} + = Bugzilla::Extension::BMO::FakeBug->new($vars->{'default'} || {}); + } - if ($page eq 'user_activity.html') { - require Bugzilla::Extension::BMO::Reports::UserActivity; - Bugzilla::Extension::BMO::Reports::UserActivity::report($vars); + if ($file =~ /^attachment\/diff-header\./) { + my $attachid = $vars->{attachid} ? $vars->{attachid} : $vars->{newid}; + $vars->{attachment} = Bugzilla::Attachment->new({id => $attachid, cache => 1}) + if $attachid; + } + if ($file =~ /^admin\/products\/(create|edit)\./) { + my $product = $vars->{product}; + my $security_groups = Bugzilla::Group->match({isbuggroup => 1, isactive => 1}); + if ($product) { + + # If set group is not active currently, we add it into the list + if (!grep($_->name eq $product->default_security_group, @$security_groups)) { + push(@$security_groups, $product->default_security_group_obj); + @$security_groups = sort { $a->name cmp $b->name } @$security_groups; + } } - elsif ($page eq 'triage_reports.html') { - require Bugzilla::Extension::BMO::Reports::Triage; - Bugzilla::Extension::BMO::Reports::Triage::unconfirmed($vars); - } - elsif ($page eq 'triage_owners.html') { - require Bugzilla::Extension::BMO::Reports::Triage; - Bugzilla::Extension::BMO::Reports::Triage::owners($vars); - } - elsif ($page eq 'group_admins.html') { - require Bugzilla::Extension::BMO::Reports::Groups; - Bugzilla::Extension::BMO::Reports::Groups::admins_report($vars); - } - elsif ($page eq 'group_membership.html' or $page eq 'group_membership.txt') { - require Bugzilla::Extension::BMO::Reports::Groups; - Bugzilla::Extension::BMO::Reports::Groups::membership_report($page, $vars); - } - elsif ($page eq 'group_members.html' or $page eq 'group_members.json') { - require Bugzilla::Extension::BMO::Reports::Groups; - Bugzilla::Extension::BMO::Reports::Groups::members_report($page, $vars); - } - elsif ($page eq 'recruiting_dashboard.html') { - require Bugzilla::Extension::BMO::Reports::Recruiting; - Bugzilla::Extension::BMO::Reports::Recruiting::report($vars); - } - elsif ($page eq 'internship_dashboard.html') { - require Bugzilla::Extension::BMO::Reports::Internship; - Bugzilla::Extension::BMO::Reports::Internship::report($vars); - } - elsif ($page eq 'email_queue.html') { - print Bugzilla->cgi->redirect('view_job_queue.cgi'); - } - elsif ($page eq 'release_tracking_report.html') { - require Bugzilla::Extension::BMO::Reports::ReleaseTracking; - Bugzilla::Extension::BMO::Reports::ReleaseTracking::report($vars); - } - elsif ($page eq 'product_security_report.html') { - require Bugzilla::Extension::BMO::Reports::ProductSecurity; - Bugzilla::Extension::BMO::Reports::ProductSecurity::report($vars); - } - elsif ($page eq 'fields.html') { - # Recently global/field-descs.none.tmpl and bug/field-help.none.tmpl - # were changed for better performance and are now only loaded once. - # I have not found an easy way to allow our hook template to check if - # it is called from pages/fields.html.tmpl. So we set a value in request_cache - # that our hook template can see. - Bugzilla->request_cache->{'bmo_fields_page'} = 1; - } - elsif ($page eq 'query_database.html') { - query_database($vars); - } - elsif ($page eq 'attachment_bounty_form.html') { - bounty_attachment($vars); - } - elsif ($page eq 'triage_request.html') { - triage_request($vars); - } + $vars->{security_groups} = $security_groups; + } +} + +sub page_before_template { + my ($self, $args) = @_; + my $page = $args->{'page_id'}; + my $vars = $args->{'vars'}; + + if ($page eq 'user_activity.html') { + require Bugzilla::Extension::BMO::Reports::UserActivity; + Bugzilla::Extension::BMO::Reports::UserActivity::report($vars); + + } + elsif ($page eq 'triage_reports.html') { + require Bugzilla::Extension::BMO::Reports::Triage; + Bugzilla::Extension::BMO::Reports::Triage::unconfirmed($vars); + } + elsif ($page eq 'triage_owners.html') { + require Bugzilla::Extension::BMO::Reports::Triage; + Bugzilla::Extension::BMO::Reports::Triage::owners($vars); + } + elsif ($page eq 'group_admins.html') { + require Bugzilla::Extension::BMO::Reports::Groups; + Bugzilla::Extension::BMO::Reports::Groups::admins_report($vars); + } + elsif ($page eq 'group_membership.html' or $page eq 'group_membership.txt') { + require Bugzilla::Extension::BMO::Reports::Groups; + Bugzilla::Extension::BMO::Reports::Groups::membership_report($page, $vars); + } + elsif ($page eq 'group_members.html' or $page eq 'group_members.json') { + require Bugzilla::Extension::BMO::Reports::Groups; + Bugzilla::Extension::BMO::Reports::Groups::members_report($page, $vars); + } + elsif ($page eq 'recruiting_dashboard.html') { + require Bugzilla::Extension::BMO::Reports::Recruiting; + Bugzilla::Extension::BMO::Reports::Recruiting::report($vars); + } + elsif ($page eq 'internship_dashboard.html') { + require Bugzilla::Extension::BMO::Reports::Internship; + Bugzilla::Extension::BMO::Reports::Internship::report($vars); + } + elsif ($page eq 'email_queue.html') { + print Bugzilla->cgi->redirect('view_job_queue.cgi'); + } + elsif ($page eq 'release_tracking_report.html') { + require Bugzilla::Extension::BMO::Reports::ReleaseTracking; + Bugzilla::Extension::BMO::Reports::ReleaseTracking::report($vars); + } + elsif ($page eq 'product_security_report.html') { + require Bugzilla::Extension::BMO::Reports::ProductSecurity; + Bugzilla::Extension::BMO::Reports::ProductSecurity::report($vars); + } + elsif ($page eq 'fields.html') { + + # Recently global/field-descs.none.tmpl and bug/field-help.none.tmpl + # were changed for better performance and are now only loaded once. + # I have not found an easy way to allow our hook template to check if + # it is called from pages/fields.html.tmpl. So we set a value in request_cache + # that our hook template can see. + Bugzilla->request_cache->{'bmo_fields_page'} = 1; + } + elsif ($page eq 'query_database.html') { + query_database($vars); + } + elsif ($page eq 'attachment_bounty_form.html') { + bounty_attachment($vars); + } + elsif ($page eq 'triage_request.html') { + triage_request($vars); + } } sub bounty_attachment { - my ($vars) = @_; - - my $user = Bugzilla->user; - $user->in_group('bounty-team') - || ThrowUserError("auth_failure", { group => "bounty-team", - action => "add", - object => "bounty_attachments" }); - - my $input = Bugzilla->input_params; - my $dbh = Bugzilla->dbh; - my $bug = Bugzilla::Bug->check({ id => $input->{bug_id}, cache => 1 }); - my $attachment = first { $_ && _attachment_is_bounty_attachment($_) } @{$bug->attachments}; - $vars->{bug} = $bug; - - if ($input->{submit}) { - ThrowUserError('bounty_attachment_missing_reporter') - unless $input->{reporter_email}; - - check_hash_token($input->{token}, ['bounty', $bug->id]); - - my @fields = qw( reporter_email amount_paid reported_date fixed_date awarded_date publish ); - my %form = map { $_ => $input->{$_} } @fields; - $form{credit} = [ grep { defined } map { $input->{"credit_$_"} } 1..3 ]; - - $dbh->bz_start_transaction(); - if ($attachment) { - $attachment->set( - description => format_bounty_attachment_description(\%form) - ); - $attachment->update; - } - else { - my $attachment = Bugzilla::Attachment->create({ - bug => $bug, - isprivate => 1, - mimetype => 'text/plain', - data => 'bounty', - filename => 'bugbounty.data', - description => format_bounty_attachment_description(\%form), - }); - } - $dbh->bz_commit_transaction(); + my ($vars) = @_; - Bugzilla::BugMail::Send($bug->id, { changer => $user }); + my $user = Bugzilla->user; + $user->in_group('bounty-team') + || ThrowUserError("auth_failure", + {group => "bounty-team", action => "add", object => "bounty_attachments"}); - print Bugzilla->cgi->redirect('show_bug.cgi?id=' . $bug->id); - exit; - } + my $input = Bugzilla->input_params; + my $dbh = Bugzilla->dbh; + my $bug = Bugzilla::Bug->check({id => $input->{bug_id}, cache => 1}); + my $attachment + = first { $_ && _attachment_is_bounty_attachment($_) } @{$bug->attachments}; + $vars->{bug} = $bug; + + if ($input->{submit}) { + ThrowUserError('bounty_attachment_missing_reporter') + unless $input->{reporter_email}; + + check_hash_token($input->{token}, ['bounty', $bug->id]); + + my @fields + = qw( reporter_email amount_paid reported_date fixed_date awarded_date publish ); + my %form = map { $_ => $input->{$_} } @fields; + $form{credit} = [grep {defined} map { $input->{"credit_$_"} } 1 .. 3]; + $dbh->bz_start_transaction(); if ($attachment) { - $vars->{form} = $attachment->bounty_details; + $attachment->set(description => format_bounty_attachment_description(\%form)); + $attachment->update; } else { - my $now = $dbh->selectrow_array('SELECT LOCALTIMESTAMP(0)'); - $vars->{form} = { - reporter_email => $bug->reporter->email, - reported_date => format_time($bug->creation_ts, "%Y-%m-%d"), - awarded_date => format_time($now, "%Y-%m-%d"), - publish => 1 - }; - if ($bug->cf_last_resolved) { - $vars->{form}{fixed_date} = format_time($bug->cf_last_resolved, "%Y-%m-%d"), - } + my $attachment = Bugzilla::Attachment->create({ + bug => $bug, + isprivate => 1, + mimetype => 'text/plain', + data => 'bounty', + filename => 'bugbounty.data', + description => format_bounty_attachment_description(\%form), + }); + } + $dbh->bz_commit_transaction(); + + Bugzilla::BugMail::Send($bug->id, {changer => $user}); + + print Bugzilla->cgi->redirect('show_bug.cgi?id=' . $bug->id); + exit; + } + + if ($attachment) { + $vars->{form} = $attachment->bounty_details; + } + else { + my $now = $dbh->selectrow_array('SELECT LOCALTIMESTAMP(0)'); + $vars->{form} = { + reporter_email => $bug->reporter->email, + reported_date => format_time($bug->creation_ts, "%Y-%m-%d"), + awarded_date => format_time($now, "%Y-%m-%d"), + publish => 1 + }; + if ($bug->cf_last_resolved) { + $vars->{form}{fixed_date} = format_time($bug->cf_last_resolved, "%Y-%m-%d"),; } - $vars->{form}{token} = issue_hash_token(['bounty', $bug->id]); + } + $vars->{form}{token} = issue_hash_token(['bounty', $bug->id]); } sub _attachment_is_bounty_attachment { - my ($attachment) = @_; + my ($attachment) = @_; - return 0 unless $attachment->filename eq 'bugbounty.data'; - return 0 unless $attachment->contenttype eq 'text/plain'; - return 0 unless $attachment->isprivate; - return 0 unless $attachment->attacher->in_group('bounty-team'); + return 0 unless $attachment->filename eq 'bugbounty.data'; + return 0 unless $attachment->contenttype eq 'text/plain'; + return 0 unless $attachment->isprivate; + return 0 unless $attachment->attacher->in_group('bounty-team'); - return $attachment->description =~ /^(?:[^,]*,)+[^,]*$/; + return $attachment->description =~ /^(?:[^,]*,)+[^,]*$/; } sub _attachment_bounty_details { - my ($attachment) = @_; - if (!exists $attachment->{bounty_details}) { - if ($attachment->is_bounty_attachment) { - $attachment->{bounty_details} = parse_bounty_attachment_description($attachment->description); - } - else { - $attachment->{bounty_details} = undef; - } + my ($attachment) = @_; + if (!exists $attachment->{bounty_details}) { + if ($attachment->is_bounty_attachment) { + $attachment->{bounty_details} + = parse_bounty_attachment_description($attachment->description); + } + else { + $attachment->{bounty_details} = undef; } - return $attachment->{bounty_details}; + } + return $attachment->{bounty_details}; } sub format_bounty_attachment_description { - my ($form) = @_; - my @fields = ( - @$form{qw( reporter_email amount_paid reported_date fixed_date awarded_date )}, - $form->{publish} ? 'true' : 'false', - @{ $form->{credit} // [] } - ); + my ($form) = @_; + my @fields = ( + @$form{qw( reporter_email amount_paid reported_date fixed_date awarded_date )}, + $form->{publish} ? 'true' : 'false', + @{$form->{credit} // []} + ); - return join(',', map { $_ // '' } @fields); + return join(',', map { $_ // '' } @fields); } sub parse_bounty_attachment_description { - my ($desc) = @_; + my ($desc) = @_; - my %map = ( true => 1, false => 0 ); - my $date = qr/\d{4}-\d{2}-\d{2}/; - $desc =~ m! + my %map = (true => 1, false => 0); + my $date = qr/\d{4}-\d{2}-\d{2}/; + $desc =~ m! ^ (?<reporter_email> [^,]+) \s*,\s* (?<amount_paid> [0-9]+[-+?]?) ? \s*,\s* @@ -373,1799 +381,1830 @@ sub parse_bounty_attachment_description { $ !x; - return { - reporter_email => $+{reporter_email} // '', - amount_paid => $+{amount_paid} // '', - reported_date => $+{reported_date} // '', - fixed_date => $+{fixed_date} // '', - awarded_date => $+{awarded_date} // '', - publish => $map{ $+{publish} // 'false' }, - credit => [grep { $_ } split(/\s*,\s*/, $+{credits}) ] - }; + return { + reporter_email => $+{reporter_email} // '', + amount_paid => $+{amount_paid} // '', + reported_date => $+{reported_date} // '', + fixed_date => $+{fixed_date} // '', + awarded_date => $+{awarded_date} // '', + publish => $map{$+{publish} // 'false'}, + credit => [grep {$_} split(/\s*,\s*/, $+{credits})] + }; } sub triage_request { - my ($vars) = @_; - my $user = Bugzilla->login(LOGIN_REQUIRED); - if (Bugzilla->input_params->{update}) { - Bugzilla->set_user(Bugzilla::User->super_user); - $user->set_groups({ add => [ 'canconfirm' ] }); - Bugzilla->set_user($user); - $user->update(); - $vars->{updated} = 1; - } + my ($vars) = @_; + my $user = Bugzilla->login(LOGIN_REQUIRED); + if (Bugzilla->input_params->{update}) { + Bugzilla->set_user(Bugzilla::User->super_user); + $user->set_groups({add => ['canconfirm']}); + Bugzilla->set_user($user); + $user->update(); + $vars->{updated} = 1; + } } sub _get_field_values_sort_key { - my ($field) = @_; - my $dbh = Bugzilla->dbh; - my $fields = $dbh->selectall_arrayref( - "SELECT value, sortkey FROM $field - ORDER BY sortkey, value"); - - my %field_values; - foreach my $field (@$fields) { - my ($value, $sortkey) = @$field; - $field_values{$value} = $sortkey; - } - return \%field_values; + my ($field) = @_; + my $dbh = Bugzilla->dbh; + my $fields = $dbh->selectall_arrayref( + "SELECT value, sortkey FROM $field + ORDER BY sortkey, value" + ); + + my %field_values; + foreach my $field (@$fields) { + my ($value, $sortkey) = @$field; + $field_values{$value} = $sortkey; + } + return \%field_values; } sub active_custom_fields { - my ($self, $args) = @_; - my $fields = $args->{'fields'}; - my $params = $args->{'params'}; - my $product = $params->{'product'}; - my $component = $params->{'component'}; + my ($self, $args) = @_; + my $fields = $args->{'fields'}; + my $params = $args->{'params'}; + my $product = $params->{'product'}; + my $component = $params->{'component'}; - return if !$product; + return if !$product; - my $product_name = blessed $product ? $product->name : $product; - my $component_name = blessed $component ? $component->name : $component; + my $product_name = blessed $product ? $product->name : $product; + my $component_name = blessed $component ? $component->name : $component; - my @tmp_fields; - foreach my $field (@$$fields) { - next if cf_hidden_in_product($field->name, $product_name, $component_name); - push(@tmp_fields, $field); - } - $$fields = \@tmp_fields; + my @tmp_fields; + foreach my $field (@$$fields) { + next if cf_hidden_in_product($field->name, $product_name, $component_name); + push(@tmp_fields, $field); + } + $$fields = \@tmp_fields; } sub cf_hidden_in_product { - my ($field_name, $product_name, $component_name, $bug) = @_; - - # check bugzilla's built-in visibility controls first - if ($bug) { - my $field = Bugzilla::Field->new({ name => $field_name, cache => 1 }); - return 1 if $field && !$field->is_visible_on_bug($bug); - } - - # If used in buglist.cgi, we pass in one_product which is a Bugzilla::Product - # elsewhere, we just pass the name of the product. - $product_name = blessed($product_name) - ? $product_name->name - : $product_name; - - # Also in buglist.cgi, we pass in a list of components instead - # of a single component name everywhere else. - my $component_list = []; - if ($component_name) { - $component_list = ref $component_name - ? $component_name - : [ $component_name ]; - } - - foreach my $field_re (keys %$cf_visible_in_products) { - if ($field_name =~ $field_re) { - # If no product given, for example more than one product - # in buglist.cgi, then hide field by default - return 1 if !$product_name; - - my $products = $cf_visible_in_products->{$field_re}; - foreach my $product (keys %$products) { - my $components = $products->{$product}; - - my $found_component = 0; - if (@$components) { - foreach my $component (@$components) { - if (ref($component) eq 'Regexp') { - if (grep($_ =~ $component, @$component_list)) { - $found_component = 1; - last; - } - } else { - if (grep($_ eq $component, @$component_list)) { - $found_component = 1; - last; - } - } - } - } - - # If product matches and at at least one component matches - # from component_list (if a matching component was required), - # we allow the field to be seen - if ($product eq $product_name && (!@$components || $found_component)) { - return 0; - } + my ($field_name, $product_name, $component_name, $bug) = @_; + + # check bugzilla's built-in visibility controls first + if ($bug) { + my $field = Bugzilla::Field->new({name => $field_name, cache => 1}); + return 1 if $field && !$field->is_visible_on_bug($bug); + } + + # If used in buglist.cgi, we pass in one_product which is a Bugzilla::Product + # elsewhere, we just pass the name of the product. + $product_name = blessed($product_name) ? $product_name->name : $product_name; + + # Also in buglist.cgi, we pass in a list of components instead + # of a single component name everywhere else. + my $component_list = []; + if ($component_name) { + $component_list = ref $component_name ? $component_name : [$component_name]; + } + + foreach my $field_re (keys %$cf_visible_in_products) { + if ($field_name =~ $field_re) { + + # If no product given, for example more than one product + # in buglist.cgi, then hide field by default + return 1 if !$product_name; + + my $products = $cf_visible_in_products->{$field_re}; + foreach my $product (keys %$products) { + my $components = $products->{$product}; + + my $found_component = 0; + if (@$components) { + foreach my $component (@$components) { + if (ref($component) eq 'Regexp') { + if (grep($_ =~ $component, @$component_list)) { + $found_component = 1; + last; + } } - return 1; + else { + if (grep($_ eq $component, @$component_list)) { + $found_component = 1; + last; + } + } + } } + + # If product matches and at at least one component matches + # from component_list (if a matching component was required), + # we allow the field to be seen + if ($product eq $product_name && (!@$components || $found_component)) { + return 0; + } + } + return 1; } + } - return 0; + return 0; } # Purpose: CC certain email addresses on bugmail when a bug is added or # removed from a particular group. sub bugmail_recipients { - my ($self, $args) = @_; - my $bug = $args->{'bug'}; - my $recipients = $args->{'recipients'}; - my $diffs = $args->{'diffs'}; - - if (@$diffs) { - # Changed bug - foreach my $ref (@$diffs) { - my $old = $ref->{old}; - my $new = $ref->{new}; - my $fieldname = $ref->{field_name}; - - if ($fieldname eq "bug_group") { - _cc_if_special_group($old, $recipients); - _cc_if_special_group($new, $recipients); - } - } - } else { - # Determine if it's a new bug, or a comment without a field change - my $comment_count = scalar @{$bug->comments}; - if ($comment_count == 1) { - # New bug - foreach my $group (@{ $bug->groups_in }) { - _cc_if_special_group($group->{'name'}, $recipients); - } - } + my ($self, $args) = @_; + my $bug = $args->{'bug'}; + my $recipients = $args->{'recipients'}; + my $diffs = $args->{'diffs'}; + + if (@$diffs) { + + # Changed bug + foreach my $ref (@$diffs) { + my $old = $ref->{old}; + my $new = $ref->{new}; + my $fieldname = $ref->{field_name}; + + if ($fieldname eq "bug_group") { + _cc_if_special_group($old, $recipients); + _cc_if_special_group($new, $recipients); + } + } + } + else { + # Determine if it's a new bug, or a comment without a field change + my $comment_count = scalar @{$bug->comments}; + if ($comment_count == 1) { + + # New bug + foreach my $group (@{$bug->groups_in}) { + _cc_if_special_group($group->{'name'}, $recipients); + } } + } } sub _cc_if_special_group { - my ($group, $recipients) = @_; + my ($group, $recipients) = @_; - return if !$group; + return if !$group; - if (exists $group_change_notification{$group}) { - foreach my $login (@{ $group_change_notification{$group} }) { - my $id = login_to_id($login); - $recipients->{$id}->{+REL_CC} = Bugzilla::BugMail::BIT_DIRECT(); - } + if (exists $group_change_notification{$group}) { + foreach my $login (@{$group_change_notification{$group}}) { + my $id = login_to_id($login); + $recipients->{$id}->{+REL_CC} = Bugzilla::BugMail::BIT_DIRECT(); } + } } sub _check_trusted { - my ($field, $trusted, $priv_results) = @_; + my ($field, $trusted, $priv_results) = @_; - my $needed_group = $trusted->{'_default'} || ""; - foreach my $dfield (keys %$trusted) { - if ($field =~ $dfield) { - $needed_group = $trusted->{$dfield}; - } - } - if ($needed_group && !Bugzilla->user->in_group($needed_group)) { - push (@$priv_results, PRIVILEGES_REQUIRED_EMPOWERED); + my $needed_group = $trusted->{'_default'} || ""; + foreach my $dfield (keys %$trusted) { + if ($field =~ $dfield) { + $needed_group = $trusted->{$dfield}; } + } + if ($needed_group && !Bugzilla->user->in_group($needed_group)) { + push(@$priv_results, PRIVILEGES_REQUIRED_EMPOWERED); + } } sub _is_field_set { - my $value = shift; - return $value ne '---' && $value !~ /\?$/; + my $value = shift; + return $value ne '---' && $value !~ /\?$/; } sub bug_check_can_change_field { - my ($self, $args) = @_; - my $bug = $args->{'bug'}; - my $field = $args->{'field'}; - my $new_value = $args->{'new_value'}; - my $old_value = $args->{'old_value'}; - my $priv_results = $args->{'priv_results'}; - my $user = Bugzilla->user; - - if ($field =~ /^cf/ && !@$priv_results && $new_value ne '---') { - # Cannot use the standard %cf_setter mapping as we want anyone - # to be able to set ?, just not the other values. - if ($field eq 'cf_cab_review') { - if ($new_value ne '1' - && $new_value ne '?' - && !$user->in_group('infra', $bug->product_id)) - { - push (@$priv_results, PRIVILEGES_REQUIRED_EMPOWERED); - } - } - # "other" custom field setters restrictions - elsif (exists $cf_setters->{$field}) { - my $in_group = 0; - foreach my $group (@{$cf_setters->{$field}}) { - if ($user->in_group($group, $bug->product_id)) { - $in_group = 1; - last; - } - } - if (!$in_group) { - push (@$priv_results, PRIVILEGES_REQUIRED_EMPOWERED); - } + my ($self, $args) = @_; + my $bug = $args->{'bug'}; + my $field = $args->{'field'}; + my $new_value = $args->{'new_value'}; + my $old_value = $args->{'old_value'}; + my $priv_results = $args->{'priv_results'}; + my $user = Bugzilla->user; + + if ($field =~ /^cf/ && !@$priv_results && $new_value ne '---') { + + # Cannot use the standard %cf_setter mapping as we want anyone + # to be able to set ?, just not the other values. + if ($field eq 'cf_cab_review') { + if ( $new_value ne '1' + && $new_value ne '?' + && !$user->in_group('infra', $bug->product_id)) + { + push(@$priv_results, PRIVILEGES_REQUIRED_EMPOWERED); + } + } + + # "other" custom field setters restrictions + elsif (exists $cf_setters->{$field}) { + my $in_group = 0; + foreach my $group (@{$cf_setters->{$field}}) { + if ($user->in_group($group, $bug->product_id)) { + $in_group = 1; + last; } + } + if (!$in_group) { + push(@$priv_results, PRIVILEGES_REQUIRED_EMPOWERED); + } } - elsif ($field eq 'resolution' && $new_value eq 'EXPIRED') { - # The EXPIRED resolution should only be settable by gerv. - if ($user->login ne 'gerv@mozilla.org') { - push (@$priv_results, PRIVILEGES_REQUIRED_EMPOWERED); - } + } + elsif ($field eq 'resolution' && $new_value eq 'EXPIRED') { - } elsif ($field eq 'resolution' && $new_value eq 'FIXED') { - # You need at least canconfirm to mark a bug as FIXED - if (!$user->in_group('canconfirm', $bug->{'product_id'})) { - push (@$priv_results, PRIVILEGES_REQUIRED_EMPOWERED); - } + # The EXPIRED resolution should only be settable by gerv. + if ($user->login ne 'gerv@mozilla.org') { + push(@$priv_results, PRIVILEGES_REQUIRED_EMPOWERED); + } - } elsif ( - ($field eq 'bug_status' && $old_value eq 'VERIFIED') - || ($field eq 'dup_id' && $bug->status->name eq 'VERIFIED') - || ($field eq 'resolution' && $bug->status->name eq 'VERIFIED') - ) { - # You need at least editbugs to reopen a resolved/verified bug - if (!$user->in_group('editbugs', $bug->{'product_id'})) { - push (@$priv_results, PRIVILEGES_REQUIRED_EMPOWERED); - } + } + elsif ($field eq 'resolution' && $new_value eq 'FIXED') { - } elsif ($user->in_group('canconfirm', $bug->{'product_id'})) { - # Canconfirm is really "cantriage"; users with canconfirm can also mark - # bugs as DUPLICATE, WORKSFORME, and INCOMPLETE. - if ($field eq 'bug_status' - && is_open_state($old_value) - && !is_open_state($new_value)) - { - push (@$priv_results, PRIVILEGES_REQUIRED_NONE); - } - elsif ($field eq 'resolution' && - ($new_value eq 'DUPLICATE' || - $new_value eq 'WORKSFORME' || - $new_value eq 'INCOMPLETE' || - ($old_value eq '' && $new_value eq '1'))) - { - push (@$priv_results, PRIVILEGES_REQUIRED_NONE); - } - elsif ($field eq 'dup_id') { - push (@$priv_results, PRIVILEGES_REQUIRED_NONE); - } + # You need at least canconfirm to mark a bug as FIXED + if (!$user->in_group('canconfirm', $bug->{'product_id'})) { + push(@$priv_results, PRIVILEGES_REQUIRED_EMPOWERED); + } - } elsif ($field eq 'bug_status') { - # Disallow reopening of bugs which have been resolved for > 1 year - if (is_open_state($new_value) - && !is_open_state($old_value) - && $bug->resolution eq 'FIXED') - { - my $days_ago = DateTime->now(time_zone => Bugzilla->local_timezone); - $days_ago->subtract(days => 365); - my $last_closed = datetime_from($bug->last_closed_date); - if ($last_closed lt $days_ago) { - push (@$priv_results, PRIVILEGES_REQUIRED_EMPOWERED); - } - } + } + elsif (($field eq 'bug_status' && $old_value eq 'VERIFIED') + || ($field eq 'dup_id' && $bug->status->name eq 'VERIFIED') + || ($field eq 'resolution' && $bug->status->name eq 'VERIFIED')) + { + # You need at least editbugs to reopen a resolved/verified bug + if (!$user->in_group('editbugs', $bug->{'product_id'})) { + push(@$priv_results, PRIVILEGES_REQUIRED_EMPOWERED); } + + } + elsif ($user->in_group('canconfirm', $bug->{'product_id'})) { + + # Canconfirm is really "cantriage"; users with canconfirm can also mark + # bugs as DUPLICATE, WORKSFORME, and INCOMPLETE. + if ( $field eq 'bug_status' + && is_open_state($old_value) + && !is_open_state($new_value)) + { + push(@$priv_results, PRIVILEGES_REQUIRED_NONE); + } + elsif ( + $field eq 'resolution' + && ( $new_value eq 'DUPLICATE' + || $new_value eq 'WORKSFORME' + || $new_value eq 'INCOMPLETE' + || ($old_value eq '' && $new_value eq '1')) + ) + { + push(@$priv_results, PRIVILEGES_REQUIRED_NONE); + } + elsif ($field eq 'dup_id') { + push(@$priv_results, PRIVILEGES_REQUIRED_NONE); + } + + } + elsif ($field eq 'bug_status') { + + # Disallow reopening of bugs which have been resolved for > 1 year + if ( is_open_state($new_value) + && !is_open_state($old_value) + && $bug->resolution eq 'FIXED') + { + my $days_ago = DateTime->now(time_zone => Bugzilla->local_timezone); + $days_ago->subtract(days => 365); + my $last_closed = datetime_from($bug->last_closed_date); + if ($last_closed lt $days_ago) { + push(@$priv_results, PRIVILEGES_REQUIRED_EMPOWERED); + } + } + } } # link up various Mozilla-specific strings sub bug_format_comment { - my ($self, $args) = @_; - my $regexes = $args->{'regexes'}; + my ($self, $args) = @_; + my $regexes = $args->{'regexes'}; - # link to crash-stats - # Only match if not already in an URL using the negative lookbehind (?<!\/) - push (@$regexes, { - match => qr/(?<!\/)\bbp-([a-f0-9]{8}\-[a-f0-9]{4}\-[a-f0-9]{4}\- + # link to crash-stats + # Only match if not already in an URL using the negative lookbehind (?<!\/) + push( + @$regexes, + { + match => qr/(?<!\/)\bbp-([a-f0-9]{8}\-[a-f0-9]{4}\-[a-f0-9]{4}\- [a-f0-9]{4}\-[a-f0-9]{12})\b/x, - replace => sub { - my $args = shift; - my $match = html_quote($args->{matches}->[0]); - return qq{<a href="https://crash-stats.mozilla.com/report/index/$match">bp-$match</a>}; - } - }); - - # link to CVE/CAN security releases - push (@$regexes, { - match => qr/(?<!\/|=)\b((?:CVE|CAN)-\d{4}-(?:\d{4}|[1-9]\d{4,})(?!\d))\b/, - replace => sub { - my $args = shift; - my $match = html_quote($args->{matches}->[0]); - return qq{<a href="https://cve.mitre.org/cgi-bin/cvename.cgi?name=$match">$match</a>}; - } - }); - - # link to svn.m.o - push (@$regexes, { - match => qr/(^|\s)r(\d{4,})\b/, - replace => sub { - my $args = shift; - my $match = html_quote($args->{matches}->[1]); - return - $args->{matches}->[0] . - qq{<a href="https://viewvc.svn.mozilla.org/vc?view=rev&revision=$match">r$match</a>}; - } - }); - - # link old git.mozilla.org commit messages to github - push (@$regexes, { - match => qr#^(To\s(?:ssh://)?(?:[^\@]+\@)?git\.mozilla\.org[:/](.+?\.git)\n + replace => sub { + my $args = shift; + my $match = html_quote($args->{matches}->[0]); + return + qq{<a href="https://crash-stats.mozilla.com/report/index/$match">bp-$match</a>}; + } + } + ); + + # link to CVE/CAN security releases + push( + @$regexes, + { + match => qr/(?<!\/|=)\b((?:CVE|CAN)-\d{4}-(?:\d{4}|[1-9]\d{4,})(?!\d))\b/, + replace => sub { + my $args = shift; + my $match = html_quote($args->{matches}->[0]); + return + qq{<a href="https://cve.mitre.org/cgi-bin/cvename.cgi?name=$match">$match</a>}; + } + } + ); + + # link to svn.m.o + push( + @$regexes, + { + match => qr/(^|\s)r(\d{4,})\b/, + replace => sub { + my $args = shift; + my $match = html_quote($args->{matches}->[1]); + return $args->{matches}->[0] + . qq{<a href="https://viewvc.svn.mozilla.org/vc?view=rev&revision=$match">r$match</a>}; + } + } + ); + + # link old git.mozilla.org commit messages to github + push( + @$regexes, + { + match => qr#^(To\s(?:ssh://)?(?:[^\@]+\@)?git\.mozilla\.org[:/](.+?\.git)\n \s+)([0-9a-z]+\.\.([0-9a-z]+)\s+\S+\s->\s\S+)#mx, - replace => sub { - my $args = shift; - my $preamble = html_quote($args->{matches}->[0]); - my $repo = html_quote($args->{matches}->[1]); - my $text = html_quote($args->{matches}->[2]); - my $revision = html_quote($args->{matches}->[3]); - $repo = 'mozilla/webtools-bmo-bugzilla' if $repo =~ /^webtools\/bmo\/bugzilla/; - $repo = 'bugzilla/bugzilla' if $repo =~ /^bugzilla\/bugzilla\.git/; - $repo = 'bugzilla/bugzilla.org' if $repo =~ /^www\/bugzilla\.org/; - return qq#$preamble<a href="https://github.com/$repo/commit/$revision">$text</a>#; - } - }); - - # link github commit messages - push (@$regexes, { - match => qr#^(To\s(?:https://|git@)?github\.com[:/](.+?)\.git\n + replace => sub { + my $args = shift; + my $preamble = html_quote($args->{matches}->[0]); + my $repo = html_quote($args->{matches}->[1]); + my $text = html_quote($args->{matches}->[2]); + my $revision = html_quote($args->{matches}->[3]); + $repo = 'mozilla/webtools-bmo-bugzilla' if $repo =~ /^webtools\/bmo\/bugzilla/; + $repo = 'bugzilla/bugzilla' if $repo =~ /^bugzilla\/bugzilla\.git/; + $repo = 'bugzilla/bugzilla.org' if $repo =~ /^www\/bugzilla\.org/; + return + qq#$preamble<a href="https://github.com/$repo/commit/$revision">$text</a>#; + } + } + ); + + # link github commit messages + push( + @$regexes, + { + match => qr#^(To\s(?:https://|git@)?github\.com[:/](.+?)\.git\n \s+)([0-9a-z]+\.\.([0-9a-z]+)\s+\S+\s->\s\S+)#mx, - replace => sub { - my $args = shift; - my $preamble = html_quote($args->{matches}->[0]); - my $repo = html_quote($args->{matches}->[1]); - my $text = html_quote($args->{matches}->[2]); - my $revision = html_quote($args->{matches}->[3]); - return qq#$preamble<a href="https://github.com/$repo/commit/$revision">$text</a>#; - } - }); - - # link github pull requests and issues - push (@$regexes, { - match => qr/(\s)([A-Za-z0-9_\.-]+)\/([A-Za-z0-9_\.-]+)\#([0-9]+)\b/, - replace => sub { - my $args = shift; - my $owner = html_quote($args->{matches}->[1]); - my $repo = html_quote($args->{matches}->[2]); - my $number = html_quote($args->{matches}->[3]); - return qq# <a href="https://github.com/$owner/$repo/issues/$number">$owner/$repo\#$number</a>#; + replace => sub { + my $args = shift; + my $preamble = html_quote($args->{matches}->[0]); + my $repo = html_quote($args->{matches}->[1]); + my $text = html_quote($args->{matches}->[2]); + my $revision = html_quote($args->{matches}->[3]); + return + qq#$preamble<a href="https://github.com/$repo/commit/$revision">$text</a>#; + } + } + ); + + # link github pull requests and issues + push( + @$regexes, + { + match => qr/(\s)([A-Za-z0-9_\.-]+)\/([A-Za-z0-9_\.-]+)\#([0-9]+)\b/, + replace => sub { + my $args = shift; + my $owner = html_quote($args->{matches}->[1]); + my $repo = html_quote($args->{matches}->[2]); + my $number = html_quote($args->{matches}->[3]); + return + qq# <a href="https://github.com/$owner/$repo/issues/$number">$owner/$repo\#$number</a>#; + } + } + ); + +# Update certain links to git.mozilla.org to go to github.com instead +# https://git.mozilla.org/?p=webtools/bmo/bugzilla.git;a=blob;f=Bugzilla/WebService/Bug.pm;h=d7a1d8f9bb5fdee524f2bb342a4573a63d890f2e;hb=HEAD#l657 + push( + @$regexes, + { + match => qr#\b(https?://git\.mozilla\.org\S+)\b#mx, + replace => sub { + my $args = shift; + my $match = $args->{matches}->[0]; + my $uri = URI->new($match); + my $text = html_quote($match); + + # Only work on BMO and Bugzilla repos + my $repo = html_quote($uri->query_param_delete("p")) || ''; + if ($repo !~ /(webtools\/bmo|bugzilla)\//) { + return qq#<a href="$text">$text</a>#; } - }); - - # Update certain links to git.mozilla.org to go to github.com instead - # https://git.mozilla.org/?p=webtools/bmo/bugzilla.git;a=blob;f=Bugzilla/WebService/Bug.pm;h=d7a1d8f9bb5fdee524f2bb342a4573a63d890f2e;hb=HEAD#l657 - push(@$regexes, { - match => qr#\b(https?://git\.mozilla\.org\S+)\b#mx, - replace => sub { - my $args = shift; - my $match = $args->{matches}->[0]; - my $uri = URI->new($match); - my $text = html_quote($match); - - # Only work on BMO and Bugzilla repos - my $repo = html_quote($uri->query_param_delete("p")) || ''; - if ($repo !~ /(webtools\/bmo|bugzilla)\//) { - return qq#<a href="$text">$text</a>#; - } - my $action = html_quote($uri->query_param_delete("a")) || ''; - my $file = html_quote($uri->query_param_delete("f")) || ''; - my $frag = html_quote($uri->fragment) || ''; - my $from_rev = html_quote($uri->query_param_delete("h")) || ''; - my $to_rev = html_quote($uri->query_param_delete("hb")) || ''; + my $action = html_quote($uri->query_param_delete("a")) || ''; + my $file = html_quote($uri->query_param_delete("f")) || ''; + my $frag = html_quote($uri->fragment) || ''; + my $from_rev = html_quote($uri->query_param_delete("h")) || ''; + my $to_rev = html_quote($uri->query_param_delete("hb")) || ''; - if ($frag) { - $frag =~ tr/l/L/; - $frag = "#$frag"; - } + if ($frag) { + $frag =~ tr/l/L/; + $frag = "#$frag"; + } - $to_rev = $from_rev if !$to_rev; - $to_rev = 'master' if $to_rev eq 'HEAD'; - $to_rev =~ s#refs/heads/(.*)$#$1#; + $to_rev = $from_rev if !$to_rev; + $to_rev = 'master' if $to_rev eq 'HEAD'; + $to_rev =~ s#refs/heads/(.*)$#$1#; - $repo = 'mozilla-bteam/bmo' if $repo =~ /^webtools\/bmo\/bugzilla\.git$/; - $repo = 'bugzilla/bugzilla' if $repo =~ /^bugzilla\/bugzilla\.git$/; - $repo = 'bugzilla/bugzilla.org' if $repo =~ /^www\/bugzilla\.org\.git$/; + $repo = 'mozilla-bteam/bmo' if $repo =~ /^webtools\/bmo\/bugzilla\.git$/; + $repo = 'bugzilla/bugzilla' if $repo =~ /^bugzilla\/bugzilla\.git$/; + $repo = 'bugzilla/bugzilla.org' if $repo =~ /^www\/bugzilla\.org\.git$/; - if ($action eq 'tree') { - return $to_rev eq 'HEAD' - ? qq#<a href="https://github.com/$repo">$text [github]</a># - : qq#<a href="https://github.com/$repo/tree/$to_rev">$text [github]</a>#; - } - if ($action eq 'blob') { - return qq#<a href="https://github.com/$repo/blob/$to_rev/$file$frag">$text [github]</a>#; - } - if ($action eq 'shortlog' || $action eq 'log') { - return qq#<a href="https://github.com/$repo/commits/$to_rev">$text [github]</a>#; - } - if ($action eq 'commit' || $action eq 'commitdiff') { - return qq#<a href="https://github.com/$repo/commit/$to_rev">$text [github]</a>#; - } - return qq#<a href="$text">$text</a>#; + if ($action eq 'tree') { + return $to_rev eq 'HEAD' + ? qq#<a href="https://github.com/$repo">$text [github]</a># + : qq#<a href="https://github.com/$repo/tree/$to_rev">$text [github]</a>#; } - }); - - # link to hg.m.o - # Note: for grouping in this regexp, always use non-capturing parentheses. - my $hgrepos = join('|', qw!(?:releases/)?comm-[\w.]+ - (?:releases/)?mozilla-[\w.]+ - (?:releases/)?mobile-[\w.]+ - tracemonkey - tamarin-[\w.]+ - camino!); - - push (@$regexes, { - match => qr/\b(($hgrepos)\s+changeset:?\s+(?:\d+:)?([0-9a-fA-F]{12}))\b/, - replace => sub { - my $args = shift; - my $text = html_quote($args->{matches}->[0]); - my $repo = html_quote($args->{matches}->[1]); - my $id = html_quote($args->{matches}->[2]); - $repo = 'integration/mozilla-inbound' if $repo eq 'mozilla-inbound'; - return qq{<a href="https://hg.mozilla.org/$repo/rev/$id">$text</a>}; + if ($action eq 'blob') { + return + qq#<a href="https://github.com/$repo/blob/$to_rev/$file$frag">$text [github]</a>#; } - }); + if ($action eq 'shortlog' || $action eq 'log') { + return + qq#<a href="https://github.com/$repo/commits/$to_rev">$text [github]</a>#; + } + if ($action eq 'commit' || $action eq 'commitdiff') { + return qq#<a href="https://github.com/$repo/commit/$to_rev">$text [github]</a>#; + } + return qq#<a href="$text">$text</a>#; + } + } + ); + + # link to hg.m.o + # Note: for grouping in this regexp, always use non-capturing parentheses. + my $hgrepos = join( + '|', qw!(?:releases/)?comm-[\w.]+ + (?:releases/)?mozilla-[\w.]+ + (?:releases/)?mobile-[\w.]+ + tracemonkey + tamarin-[\w.]+ + camino! + ); + + push( + @$regexes, + { + match => qr/\b(($hgrepos)\s+changeset:?\s+(?:\d+:)?([0-9a-fA-F]{12}))\b/, + replace => sub { + my $args = shift; + my $text = html_quote($args->{matches}->[0]); + my $repo = html_quote($args->{matches}->[1]); + my $id = html_quote($args->{matches}->[2]); + $repo = 'integration/mozilla-inbound' if $repo eq 'mozilla-inbound'; + return qq{<a href="https://hg.mozilla.org/$repo/rev/$id">$text</a>}; + } + } + ); } sub quicksearch_map { - my ($self, $args) = @_; - my $map = $args->{'map'}; + my ($self, $args) = @_; + my $map = $args->{'map'}; - foreach my $name (keys %$map) { - if ($name =~ /cf_crash_signature$/) { - $map->{'sig'} = $name; - } + foreach my $name (keys %$map) { + if ($name =~ /cf_crash_signature$/) { + $map->{'sig'} = $name; } + } } sub object_columns { - my ($self, $args) = @_; - return unless $args->{class}->isa('Bugzilla::Product'); - push @{ $args->{columns} }, qw( - default_platform_id - default_op_sys_id - security_group_id - ); + my ($self, $args) = @_; + return unless $args->{class}->isa('Bugzilla::Product'); + push @{$args->{columns}}, qw( + default_platform_id + default_op_sys_id + security_group_id + ); } sub object_update_columns { - my ($self, $args) = @_; - return unless $args->{object}->isa('Bugzilla::Product'); - push @{ $args->{columns} }, qw( - default_platform_id - default_op_sys_id - security_group_id - ); + my ($self, $args) = @_; + return unless $args->{object}->isa('Bugzilla::Product'); + push @{$args->{columns}}, qw( + default_platform_id + default_op_sys_id + security_group_id + ); } sub object_before_create { - my ($self, $args) = @_; - return unless $args->{class}->isa('Bugzilla::Product'); + my ($self, $args) = @_; + return unless $args->{class}->isa('Bugzilla::Product'); - my $cgi = Bugzilla->cgi; - my $params = $args->{params}; - foreach my $field (qw( default_platform_id default_op_sys_id security_group_id )) { - $params->{$field} = $cgi->param($field); - } + my $cgi = Bugzilla->cgi; + my $params = $args->{params}; + foreach + my $field (qw( default_platform_id default_op_sys_id security_group_id )) + { + $params->{$field} = $cgi->param($field); + } } sub object_end_of_set_all { - my ($self, $args) = @_; - my $object = $args->{object}; - return unless $object->isa('Bugzilla::Product'); - - my $cgi = Bugzilla->cgi; - my $params = $args->{params}; - foreach my $field (qw( default_platform_id default_op_sys_id security_group_id )) { - my $value = $cgi->param($field); - detaint_natural($value); - $object->set($field, $value); - } + my ($self, $args) = @_; + my $object = $args->{object}; + return unless $object->isa('Bugzilla::Product'); + + my $cgi = Bugzilla->cgi; + my $params = $args->{params}; + foreach + my $field (qw( default_platform_id default_op_sys_id security_group_id )) + { + my $value = $cgi->param($field); + detaint_natural($value); + $object->set($field, $value); + } } sub object_end_of_create { - my ($self, $args) = @_; - my $class = $args->{class}; - - if ($class eq 'Bugzilla::User') { - my $user = $args->{object}; - - # Log real IP addresses for auditing - Bugzilla->audit(sprintf('<%s> created user %s', remote_ip(), $user->login)); - - # Add default searches to new user's footer - my $dbh = Bugzilla->dbh; - - my $sharer = Bugzilla::User->new({ name => Bugzilla->params->{'nobody_user'} }) - or return; - my $group = Bugzilla::Group->new({ name => 'everyone' }) - or return; - - foreach my $definition (@default_named_queries) { - my ($namedquery_id) = _get_named_query($sharer->id, $group->id, $definition); - $dbh->do( - "INSERT INTO namedqueries_link_in_footer(namedquery_id,user_id) VALUES (?,?)", - undef, - $namedquery_id, $user->id - ); - } + my ($self, $args) = @_; + my $class = $args->{class}; + + if ($class eq 'Bugzilla::User') { + my $user = $args->{object}; + + # Log real IP addresses for auditing + Bugzilla->audit(sprintf('<%s> created user %s', remote_ip(), $user->login)); + + # Add default searches to new user's footer + my $dbh = Bugzilla->dbh; - } elsif ($class eq 'Bugzilla::Bug') { - # Log real IP addresses for auditing - Bugzilla->audit(sprintf('%s <%s> created bug %s', Bugzilla->user->login, remote_ip(), $args->{object}->id)); + my $sharer = Bugzilla::User->new({name => Bugzilla->params->{'nobody_user'}}) + or return; + my $group = Bugzilla::Group->new({name => 'everyone'}) or return; + + foreach my $definition (@default_named_queries) { + my ($namedquery_id) = _get_named_query($sharer->id, $group->id, $definition); + $dbh->do( + "INSERT INTO namedqueries_link_in_footer(namedquery_id,user_id) VALUES (?,?)", + undef, $namedquery_id, $user->id); } + + } + elsif ($class eq 'Bugzilla::Bug') { + + # Log real IP addresses for auditing + Bugzilla->audit(sprintf( + '%s <%s> created bug %s', + Bugzilla->user->login, remote_ip(), $args->{object}->id + )); + } } sub _bug_reporters_hw_os { - my ($self) = @_; - return $self->{ua_hw_os} if exists $self->{ua_hw_os}; - my $memcached = Bugzilla->memcached; - my $hw_os = $memcached->get({ key => 'bug.ua.' . $self->id }); - if (!$hw_os) { - (my $ua) = Bugzilla->dbh->selectrow_array( - "SELECT user_agent FROM bug_user_agent WHERE bug_id = ?", - undef, - $self->id); - $hw_os = $ua - ? [ detect_platform($ua), detect_op_sys($ua) ] - : []; - $memcached->set({ key => 'bug.ua.' . $self->id, value => $hw_os }); - } - return $self->{ua_hw_os} = $hw_os; + my ($self) = @_; + return $self->{ua_hw_os} if exists $self->{ua_hw_os}; + my $memcached = Bugzilla->memcached; + my $hw_os = $memcached->get({key => 'bug.ua.' . $self->id}); + if (!$hw_os) { + (my $ua) + = Bugzilla->dbh->selectrow_array( + "SELECT user_agent FROM bug_user_agent WHERE bug_id = ?", + undef, $self->id); + $hw_os = $ua ? [detect_platform($ua), detect_op_sys($ua)] : []; + $memcached->set({key => 'bug.ua.' . $self->id, value => $hw_os}); + } + return $self->{ua_hw_os} = $hw_os; } sub _bug_is_unassigned { - my ($self) = @_; - my $assignee = $self->assigned_to->login; - return $assignee eq Bugzilla->params->{'nobody_user'} || $assignee =~ /\.bugs$/; + my ($self) = @_; + my $assignee = $self->assigned_to->login; + return $assignee eq Bugzilla->params->{'nobody_user'} || $assignee =~ /\.bugs$/; } sub _bug_has_current_patch { - my ($self) = @_; - foreach my $attachment (@{ $self->attachments }) { - next if $attachment->isobsolete; - return 1 if $attachment->can_review; - } - return 0; + my ($self) = @_; + foreach my $attachment (@{$self->attachments}) { + next if $attachment->isobsolete; + return 1 if $attachment->can_review; + } + return 0; } sub _bug_missing_sec_approval { - my ($self) = @_; - # see https://wiki.mozilla.org/Security/Bug_Approval_Process for the rules + my ($self) = @_; - # no need to alert once a bug is closed - return 0 if $self->resolution; + # see https://wiki.mozilla.org/Security/Bug_Approval_Process for the rules - # only bugs with sec-high or sec-critical keywords need sec-approval - return 0 unless $self->has_keyword('sec-high') || $self->has_keyword('sec-critical'); + # no need to alert once a bug is closed + return 0 if $self->resolution; - # look for patches with sec-approval set to any value - foreach my $attachment (@{ $self->attachments }) { - next if $attachment->isobsolete || !$attachment->ispatch; - foreach my $flag (@{ $attachment->flags }) { - # only one patch needs sec-approval - return 0 if $flag->name eq 'sec-approval'; - } - } + # only bugs with sec-high or sec-critical keywords need sec-approval + return 0 + unless $self->has_keyword('sec-high') || $self->has_keyword('sec-critical'); - # tracking flags - require Bugzilla::Extension::TrackingFlags::Flag; - my $flags = Bugzilla::Extension::TrackingFlags::Flag->match({ - product => $self->product, - component => $self->component, - bug_id => $self->id, - is_active => 1, - WHERE => { - 'name like ?' => 'cf_status_firefox%', - }, - }); - # set flags are added after the sql query, filter those out - $flags = [ grep { $_->name =~ /^cf_status_firefox/ } @$flags ]; - return 0 unless @$flags; - - my $nightly = last_value { $_->name !~ /_esr\d+$/ } @$flags; - my $set = 0; - foreach my $flag (@$flags) { - my $value = $flag->bug_flag($self->id)->value; - next if $value eq '---'; - $set++; - # sec-approval is required if any of the current status-firefox - # tracking flags that aren't the latest are set to 'affected' - return 1 if $flag->name ne $nightly->name && $value eq 'affected'; + # look for patches with sec-approval set to any value + foreach my $attachment (@{$self->attachments}) { + next if $attachment->isobsolete || !$attachment->ispatch; + foreach my $flag (@{$attachment->flags}) { + + # only one patch needs sec-approval + return 0 if $flag->name eq 'sec-approval'; } - # sec-approval is required if no tracking flags are set - return $set == 0; + } + + # tracking flags + require Bugzilla::Extension::TrackingFlags::Flag; + my $flags = Bugzilla::Extension::TrackingFlags::Flag->match({ + product => $self->product, + component => $self->component, + bug_id => $self->id, + is_active => 1, + WHERE => {'name like ?' => 'cf_status_firefox%',}, + }); + + # set flags are added after the sql query, filter those out + $flags = [grep { $_->name =~ /^cf_status_firefox/ } @$flags]; + return 0 unless @$flags; + + my $nightly = last_value { $_->name !~ /_esr\d+$/ } @$flags; + my $set = 0; + foreach my $flag (@$flags) { + my $value = $flag->bug_flag($self->id)->value; + next if $value eq '---'; + $set++; + + # sec-approval is required if any of the current status-firefox + # tracking flags that aren't the latest are set to 'affected' + return 1 if $flag->name ne $nightly->name && $value eq 'affected'; + } + + # sec-approval is required if no tracking flags are set + return $set == 0; } sub _product_default_platform_id { $_[0]->{default_platform_id} } -sub _product_default_op_sys_id { $_[0]->{default_op_sys_id} } +sub _product_default_op_sys_id { $_[0]->{default_op_sys_id} } sub _product_default_platform { - my ($self) = @_; - if (!exists $self->{default_platform}) { - $self->{default_platform} = $self->default_platform_id - ? Bugzilla::Field::Choice - ->type('rep_platform') - ->new($_[0]->{default_platform_id}) - ->name - : undef; - } - return $self->{default_platform}; + my ($self) = @_; + if (!exists $self->{default_platform}) { + $self->{default_platform} + = $self->default_platform_id + ? Bugzilla::Field::Choice->type('rep_platform') + ->new($_[0]->{default_platform_id})->name + : undef; + } + return $self->{default_platform}; } + sub _product_default_op_sys { - my ($self) = @_; - if (!exists $self->{default_op_sys}) { - $self->{default_op_sys} = $self->default_op_sys_id - ? Bugzilla::Field::Choice - ->type('op_sys') - ->new($_[0]->{default_op_sys_id}) - ->name - : undef; - } - return $self->{default_op_sys}; + my ($self) = @_; + if (!exists $self->{default_op_sys}) { + $self->{default_op_sys} + = $self->default_op_sys_id + ? Bugzilla::Field::Choice->type('op_sys')->new($_[0]->{default_op_sys_id}) + ->name + : undef; + } + return $self->{default_op_sys}; } sub _get_named_query { - my ($sharer_id, $group_id, $definition) = @_; - my $dbh = Bugzilla->dbh; - # find existing namedquery - my ($namedquery_id) = $dbh->selectrow_array( - "SELECT id FROM namedqueries WHERE userid=? AND name=?", - undef, - $sharer_id, $definition->{name} - ); - return $namedquery_id if $namedquery_id; - # create namedquery - $dbh->do( - "INSERT INTO namedqueries(userid,name,query) VALUES (?,?,?)", - undef, - $sharer_id, $definition->{name}, $definition->{query} - ); - $namedquery_id = $dbh->bz_last_key(); - # and share it - $dbh->do( - "INSERT INTO namedquery_group_map(namedquery_id,group_id) VALUES (?,?)", - undef, - $namedquery_id, $group_id, - ); - return $namedquery_id; + my ($sharer_id, $group_id, $definition) = @_; + my $dbh = Bugzilla->dbh; + + # find existing namedquery + my ($namedquery_id) + = $dbh->selectrow_array( + "SELECT id FROM namedqueries WHERE userid=? AND name=?", + undef, $sharer_id, $definition->{name}); + return $namedquery_id if $namedquery_id; + + # create namedquery + $dbh->do("INSERT INTO namedqueries(userid,name,query) VALUES (?,?,?)", + undef, $sharer_id, $definition->{name}, $definition->{query}); + $namedquery_id = $dbh->bz_last_key(); + + # and share it + $dbh->do( + "INSERT INTO namedquery_group_map(namedquery_id,group_id) VALUES (?,?)", + undef, $namedquery_id, $group_id,); + return $namedquery_id; } sub bug_end_of_create { - my ($self, $args) = @_; - my $bug = $args->{'bug'}; - - # automatically CC users to bugs based on group & product - foreach my $group_name (keys %group_auto_cc) { - my $group_obj = Bugzilla::Group->new({ name => $group_name }); - if ($group_obj && $bug->in_group($group_obj)) { - my $ra_logins = exists $group_auto_cc{$group_name}->{$bug->product} - ? $group_auto_cc{$group_name}->{$bug->product} - : $group_auto_cc{$group_name}->{'_default'}; - foreach my $login (@$ra_logins) { - $bug->add_cc($login); - } - } - } - - # store user-agent - if (my $ua = Bugzilla->cgi->user_agent) { - trick_taint($ua); - Bugzilla->dbh->do( - "INSERT INTO bug_user_agent (bug_id, user_agent) VALUES (?, ?)", - undef, - $bug->id, $ua - ); - } + my ($self, $args) = @_; + my $bug = $args->{'bug'}; + + # automatically CC users to bugs based on group & product + foreach my $group_name (keys %group_auto_cc) { + my $group_obj = Bugzilla::Group->new({name => $group_name}); + if ($group_obj && $bug->in_group($group_obj)) { + my $ra_logins + = exists $group_auto_cc{$group_name}->{$bug->product} + ? $group_auto_cc{$group_name}->{$bug->product} + : $group_auto_cc{$group_name}->{'_default'}; + foreach my $login (@$ra_logins) { + $bug->add_cc($login); + } + } + } + + # store user-agent + if (my $ua = Bugzilla->cgi->user_agent) { + trick_taint($ua); + Bugzilla->dbh->do( + "INSERT INTO bug_user_agent (bug_id, user_agent) VALUES (?, ?)", + undef, $bug->id, $ua); + } } sub sanitycheck_check { - my ($self, $args) = @_; + my ($self, $args) = @_; - my $dbh = Bugzilla->dbh; - my $status = $args->{'status'}; - $status->('bmo_check_cf_visible_in_products'); - - my $products = $dbh->selectcol_arrayref('SELECT name FROM products'); - my %product = map { $_ => 1 } @$products; - my @cf_products = map { keys %$_ } values %$cf_visible_in_products; - foreach my $cf_product (@cf_products) { - $status->('bmo_check_cf_visible_in_products_missing', - { cf_product => $cf_product }, 'alert') unless $product{$cf_product}; - } + my $dbh = Bugzilla->dbh; + my $status = $args->{'status'}; + $status->('bmo_check_cf_visible_in_products'); + + my $products = $dbh->selectcol_arrayref('SELECT name FROM products'); + my %product = map { $_ => 1 } @$products; + my @cf_products = map { keys %$_ } values %$cf_visible_in_products; + foreach my $cf_product (@cf_products) { + $status->( + 'bmo_check_cf_visible_in_products_missing', + {cf_product => $cf_product}, 'alert' + ) unless $product{$cf_product}; + } } sub db_sanitize { - print "deleting reporter's user-agents...\n"; - Bugzilla->dbh->do("TRUNCATE TABLE bug_user_agent"); + print "deleting reporter's user-agents...\n"; + Bugzilla->dbh->do("TRUNCATE TABLE bug_user_agent"); } # bugs in an ASSIGNED state must be assigned to a real person # reset bugs to NEW if the assignee is nobody/.bugs$ sub object_start_of_update { - my ($self, $args) = @_; - my ($new_bug, $old_bug) = @$args{qw( object old_object )}; - return unless $new_bug->isa('Bugzilla::Bug'); - - # if either the assignee or status has changed - return unless - $old_bug->assigned_to->id != $new_bug->assigned_to->id - || $old_bug->bug_status ne $new_bug->bug_status; - - # and the bug is now ASSIGNED - return unless - $new_bug->bug_status eq 'ASSIGNED'; - - # and the assignee isn't a real person - return unless - $new_bug->assigned_to->login eq Bugzilla->params->{'nobody_user'} - || $new_bug->assigned_to->login =~ /\.bugs$/; - - # and the user can set the status to NEW - return unless - $old_bug->check_can_change_field('bug_status', $old_bug->bug_status, 'NEW'); - - # if the user is changing the assignee, silently change the bug's status to new - if ($old_bug->assigned_to->id != $new_bug->assigned_to->id) { - $new_bug->set_bug_status('NEW'); - } + my ($self, $args) = @_; + my ($new_bug, $old_bug) = @$args{qw( object old_object )}; + return unless $new_bug->isa('Bugzilla::Bug'); - # otherwise the user is trying to set the bug's status to ASSIGNED without - # assigning a real person. throw an error. - else { - ThrowUserError('bug_status_unassigned'); - } + # if either the assignee or status has changed + return + unless $old_bug->assigned_to->id != $new_bug->assigned_to->id + || $old_bug->bug_status ne $new_bug->bug_status; + + # and the bug is now ASSIGNED + return unless $new_bug->bug_status eq 'ASSIGNED'; + + # and the assignee isn't a real person + return + unless $new_bug->assigned_to->login eq Bugzilla->params->{'nobody_user'} + || $new_bug->assigned_to->login =~ /\.bugs$/; + + # and the user can set the status to NEW + return + unless $old_bug->check_can_change_field('bug_status', $old_bug->bug_status, + 'NEW'); + + # if the user is changing the assignee, silently change the bug's status to new + if ($old_bug->assigned_to->id != $new_bug->assigned_to->id) { + $new_bug->set_bug_status('NEW'); + } + + # otherwise the user is trying to set the bug's status to ASSIGNED without + # assigning a real person. throw an error. + else { + ThrowUserError('bug_status_unassigned'); + } } # detect github pull requests and reviewboard reviews, set the content-type sub attachment_process_data { - my ($self, $args) = @_; - my $attributes = $args->{attributes}; - - # must be a text attachment - return unless $attributes->{mimetype} eq 'text/plain'; - - # check the attachment size, and get attachment content if it isn't too large - my $data = $attributes->{data}; - my $url; - if (blessed($data) && blessed($data) eq 'Fh') { - # filehandle - my $size = -s $data; - return if $size > 256; - sysread($data, $url, $size); - seek($data, 0, 0); - } else { - # string - $url = $data; - } - - if (my $detected = _detect_attached_url($url)) { - $attributes->{mimetype} = $detected->{content_type}; - $attributes->{ispatch} = 0; - } + my ($self, $args) = @_; + my $attributes = $args->{attributes}; + + # must be a text attachment + return unless $attributes->{mimetype} eq 'text/plain'; + + # check the attachment size, and get attachment content if it isn't too large + my $data = $attributes->{data}; + my $url; + if (blessed($data) && blessed($data) eq 'Fh') { + + # filehandle + my $size = -s $data; + return if $size > 256; + sysread($data, $url, $size); + seek($data, 0, 0); + } + else { + # string + $url = $data; + } + + if (my $detected = _detect_attached_url($url)) { + $attributes->{mimetype} = $detected->{content_type}; + $attributes->{ispatch} = 0; + } } sub _detect_attached_url { - my ($url) = @_; - - # trim and check for the pull request url - return unless defined $url; - return if length($url) > 256; - $url = trim($url); - # ignore urls that contain unescaped characters outside of the range mentioned in RFC 3986 section 2 - return if $url =~ m<[^A-Za-z0-9._~:/?#\[\]@!\$&'()*+,;=`.%-]>; - - foreach my $key (keys %autodetect_attach_urls) { - my $regex = $autodetect_attach_urls{$key}->{regex}; - if (ref($regex) eq 'CODE') { - $regex = $regex->(); - } - if ($url =~ $regex) { - return $autodetect_attach_urls{$key}; - } + my ($url) = @_; + + # trim and check for the pull request url + return unless defined $url; + return if length($url) > 256; + $url = trim($url); + +# ignore urls that contain unescaped characters outside of the range mentioned in RFC 3986 section 2 + return if $url =~ m<[^A-Za-z0-9._~:/?#\[\]@!\$&'()*+,;=`.%-]>; + + foreach my $key (keys %autodetect_attach_urls) { + my $regex = $autodetect_attach_urls{$key}->{regex}; + if (ref($regex) eq 'CODE') { + $regex = $regex->(); + } + if ($url =~ $regex) { + return $autodetect_attach_urls{$key}; } + } - return undef; + return undef; } sub _attachment_external_redirect { - my ($self) = @_; + my ($self) = @_; - # must be our supported content-type - return undef unless - any { $self->contenttype eq $autodetect_attach_urls{$_}->{content_type} } - keys %autodetect_attach_urls; + # must be our supported content-type + return undef + unless + any { $self->contenttype eq $autodetect_attach_urls{$_}->{content_type} } + keys %autodetect_attach_urls; - # must still be a valid url - return _detect_attached_url($self->data) + # must still be a valid url + return _detect_attached_url($self->data); } sub _attachment_can_review { - my ($self) = @_; + my ($self) = @_; - return 1 if $self->ispatch; - my $external = $self->external_redirect // return; - return $external->{can_review}; + return 1 if $self->ispatch; + my $external = $self->external_redirect // return; + return $external->{can_review}; } sub _attachment_fetch_github_pr_diff { - my ($self) = @_; + my ($self) = @_; - # must be our supported content-type - return undef unless - any { $self->contenttype eq $autodetect_attach_urls{$_}->{content_type} } - keys %autodetect_attach_urls; + # must be our supported content-type + return undef + unless + any { $self->contenttype eq $autodetect_attach_urls{$_}->{content_type} } + keys %autodetect_attach_urls; - # must still be a valid url - return undef unless _detect_attached_url($self->data); + # must still be a valid url + return undef unless _detect_attached_url($self->data); - my $ua = LWP::UserAgent->new( timeout => 10 ); - if (Bugzilla->params->{proxy_url}) { - $ua->proxy('https', Bugzilla->params->{proxy_url}); - } + my $ua = LWP::UserAgent->new(timeout => 10); + if (Bugzilla->params->{proxy_url}) { + $ua->proxy('https', Bugzilla->params->{proxy_url}); + } - my $pr_diff = $self->data . ".diff"; - my $response = $ua->get($pr_diff); - if ($response->is_error) { - warn "Github fetch error: $pr_diff, " . $response->status_line; - return "Error retrieving Github pull request diff for " . $self->data; - } - return $response->decoded_content; + my $pr_diff = $self->data . ".diff"; + my $response = $ua->get($pr_diff); + if ($response->is_error) { + warn "Github fetch error: $pr_diff, " . $response->status_line; + return "Error retrieving Github pull request diff for " . $self->data; + } + return $response->decoded_content; } # redirect automatically to github urls sub attachment_view { - my ($self, $args) = @_; - my $attachment = $args->{attachment}; - my $cgi = Bugzilla->cgi; + my ($self, $args) = @_; + my $attachment = $args->{attachment}; + my $cgi = Bugzilla->cgi; - # don't redirect if the content-type is specified explicitly - return if defined $cgi->param('content_type'); + # don't redirect if the content-type is specified explicitly + return if defined $cgi->param('content_type'); - # must be a valid redirection url - return unless defined $attachment->external_redirect; + # must be a valid redirection url + return unless defined $attachment->external_redirect; - # redirect - print $cgi->redirect(trim($attachment->data)); - exit; + # redirect + print $cgi->redirect(trim($attachment->data)); + exit; } sub install_before_final_checks { - my ($self, $args) = @_; - - # Add product chooser setting - add_setting({ - name => 'product_chooser', - options => ['pretty_product_chooser', 'full_product_chooser'], - default => 'pretty_product_chooser', - category => 'User Interface' - }); - - # Add option to inject x-bugzilla headers into the message body to work - # around gmail filtering limitations - add_setting({ - name => 'headers_in_body', - options => ['on', 'off'], - default => 'off', - category => 'Email Notifications' - }); - - # Migrate from 'gmail_threading' setting to 'bugmail_new_prefix' - my $dbh = Bugzilla->dbh; - if ($dbh->selectrow_array("SELECT 1 FROM setting WHERE name='gmail_threading'")) { - $dbh->bz_start_transaction(); - $dbh->do("UPDATE profile_setting + my ($self, $args) = @_; + + # Add product chooser setting + add_setting({ + name => 'product_chooser', + options => ['pretty_product_chooser', 'full_product_chooser'], + default => 'pretty_product_chooser', + category => 'User Interface' + }); + + # Add option to inject x-bugzilla headers into the message body to work + # around gmail filtering limitations + add_setting({ + name => 'headers_in_body', + options => ['on', 'off'], + default => 'off', + category => 'Email Notifications' + }); + + # Migrate from 'gmail_threading' setting to 'bugmail_new_prefix' + my $dbh = Bugzilla->dbh; + if ($dbh->selectrow_array("SELECT 1 FROM setting WHERE name='gmail_threading'")) + { + $dbh->bz_start_transaction(); + $dbh->do( + "UPDATE profile_setting SET setting_value='on-temp' - WHERE setting_name='gmail_threading' AND setting_value='Off'"); - $dbh->do("UPDATE profile_setting + WHERE setting_name='gmail_threading' AND setting_value='Off'" + ); + $dbh->do( + "UPDATE profile_setting SET setting_value='off' - WHERE setting_name='gmail_threading' AND setting_value='On'"); - $dbh->do("UPDATE profile_setting + WHERE setting_name='gmail_threading' AND setting_value='On'" + ); + $dbh->do( + "UPDATE profile_setting SET setting_value='on' - WHERE setting_name='gmail_threading' AND setting_value='on-temp'"); - $dbh->do("UPDATE profile_setting + WHERE setting_name='gmail_threading' AND setting_value='on-temp'" + ); + $dbh->do( + "UPDATE profile_setting SET setting_name='bugmail_new_prefix' - WHERE setting_name='gmail_threading'"); - $dbh->do("DELETE FROM setting WHERE name='gmail_threading'"); - $dbh->bz_commit_transaction(); - } + WHERE setting_name='gmail_threading'" + ); + $dbh->do("DELETE FROM setting WHERE name='gmail_threading'"); + $dbh->bz_commit_transaction(); + } } sub db_schema_abstract_schema { - my ($self, $args) = @_; - $args->{schema}->{bug_user_agent} = { - FIELDS => [ - id => { - TYPE => 'MEDIUMSERIAL', - NOTNULL => 1, - PRIMARYKEY => 1, - }, - bug_id => { - TYPE => 'INT3', - NOTNULL => 1, - REFERENCES => { - TABLE => 'bugs', - COLUMN => 'bug_id', - DELETE => 'CASCADE', - }, - }, - user_agent => { - TYPE => 'MEDIUMTEXT', - NOTNULL => 1, - }, - ], - INDEXES => [ - bug_user_agent_idx => { - FIELDS => [ 'bug_id' ], - TYPE => 'UNIQUE', - }, - ], - }; - $args->{schema}->{job_last_run} = { - FIELDS => [ - id => { - TYPE => 'INTSERIAL', - NOTNULL => 1, - PRIMARYKEY => 1, - }, - name => { - TYPE => 'VARCHAR(100)', - NOTNULL => 1, - }, - last_run => { - TYPE => 'DATETIME', - NOTNULL => 1, - }, - ], - INDEXES => [ - job_last_run_name_idx => { - FIELDS => [ 'name' ], - TYPE => 'UNIQUE', - }, - ], - }; - $args->{schema}->{secbugs_BugHistory} = { - FIELDS => [ - bugid => { TYPE => 'BIGINT', NOTNULL => 1 }, - changetime => { TYPE => 'NATIVE_DATETIME' }, - fieldname => { TYPE => 'VARCHAR(32)', NOTNULL => 1 }, - new => { TYPE => 'VARCHAR(255)' }, - old => { TYPE => 'VARCHAR(255)' }, - ], - }; - - $args->{schema}->{secbugs_Bugs} = { - FIELDS => [ - bugid => { TYPE => 'BIGINT', NOTNULL => 1, PRIMARYKEY => 1 }, - opendate => { TYPE => 'NATIVE_DATETIME' }, - closedate => { TYPE => 'NATIVE_DATETIME', NOTNULL => 1 }, - severity => { TYPE => 'VARCHAR(16)' }, - summary => { TYPE => 'VARCHAR(255)' }, - updated => { TYPE => 'NATIVE_DATETIME' }, - ], - }; - - $args->{schema}->{secbugs_Details} = { - FIELDS => [ - did => { - TYPE => 'INTSERIAL', - NOTNULL => 1, - PRIMARYKEY => 1, - }, - sid => { - TYPE => 'INT4', - }, - product => { - TYPE => 'VARCHAR(255)', - }, - component => { - TYPE => 'VARCHAR(255)', - }, - count => { TYPE => 'INT4' }, - bug_list => { TYPE => 'TEXT' }, - date => { TYPE => 'NATIVE_DATETIME' }, - avg_age_days => { TYPE => 'INT4' }, - med_age_days => { TYPE => 'INT4' }, - ] - }; - - $args->{schema}->{secbugs_Stats} = { - FIELDS => [ - sid => { TYPE => 'INTSERIAL', NOTNULL => 1, PRIMARYKEY => 1 }, - category => { TYPE => 'VARCHAR(32)' }, - count => { TYPE => 'INT4' }, - date => { TYPE => 'NATIVE_DATETIME' }, - ] - }; + my ($self, $args) = @_; + $args->{schema}->{bug_user_agent} = { + FIELDS => [ + id => {TYPE => 'MEDIUMSERIAL', NOTNULL => 1, PRIMARYKEY => 1,}, + bug_id => { + TYPE => 'INT3', + NOTNULL => 1, + REFERENCES => {TABLE => 'bugs', COLUMN => 'bug_id', DELETE => 'CASCADE',}, + }, + user_agent => {TYPE => 'MEDIUMTEXT', NOTNULL => 1,}, + ], + INDEXES => [bug_user_agent_idx => {FIELDS => ['bug_id'], TYPE => 'UNIQUE',},], + }; + $args->{schema}->{job_last_run} = { + FIELDS => [ + id => {TYPE => 'INTSERIAL', NOTNULL => 1, PRIMARYKEY => 1,}, + name => {TYPE => 'VARCHAR(100)', NOTNULL => 1,}, + last_run => {TYPE => 'DATETIME', NOTNULL => 1,}, + ], + INDEXES => [job_last_run_name_idx => {FIELDS => ['name'], TYPE => 'UNIQUE',},], + }; + $args->{schema}->{secbugs_BugHistory} = { + FIELDS => [ + bugid => {TYPE => 'BIGINT', NOTNULL => 1}, + changetime => {TYPE => 'NATIVE_DATETIME'}, + fieldname => {TYPE => 'VARCHAR(32)', NOTNULL => 1}, + new => {TYPE => 'VARCHAR(255)'}, + old => {TYPE => 'VARCHAR(255)'}, + ], + }; + + $args->{schema}->{secbugs_Bugs} = { + FIELDS => [ + bugid => {TYPE => 'BIGINT', NOTNULL => 1, PRIMARYKEY => 1}, + opendate => {TYPE => 'NATIVE_DATETIME'}, + closedate => {TYPE => 'NATIVE_DATETIME', NOTNULL => 1}, + severity => {TYPE => 'VARCHAR(16)'}, + summary => {TYPE => 'VARCHAR(255)'}, + updated => {TYPE => 'NATIVE_DATETIME'}, + ], + }; + + $args->{schema}->{secbugs_Details} = { + FIELDS => [ + did => {TYPE => 'INTSERIAL', NOTNULL => 1, PRIMARYKEY => 1,}, + sid => {TYPE => 'INT4',}, + product => {TYPE => 'VARCHAR(255)',}, + component => {TYPE => 'VARCHAR(255)',}, + count => {TYPE => 'INT4'}, + bug_list => {TYPE => 'TEXT'}, + date => {TYPE => 'NATIVE_DATETIME'}, + avg_age_days => {TYPE => 'INT4'}, + med_age_days => {TYPE => 'INT4'}, + ] + }; + + $args->{schema}->{secbugs_Stats} = { + FIELDS => [ + sid => {TYPE => 'INTSERIAL', NOTNULL => 1, PRIMARYKEY => 1}, + category => {TYPE => 'VARCHAR(32)'}, + count => {TYPE => 'INT4'}, + date => {TYPE => 'NATIVE_DATETIME'}, + ] + }; } sub install_update_db { - my $dbh = Bugzilla->dbh; + my $dbh = Bugzilla->dbh; + + # per-product hw/os defaults + my $op_sys_default = _field_value('op_sys', 'Unspecified', 50); + $dbh->bz_add_column( + 'products', + 'default_op_sys_id' => { + TYPE => 'INT2', + DEFAULT => $op_sys_default->id, + REFERENCES => {TABLE => 'op_sys', COLUMN => 'id', DELETE => 'SET NULL',}, + } + ); + my $platform_default = _field_value('rep_platform', 'Unspecified', 50); + $dbh->bz_add_column( + 'products', + 'default_platform_id' => { + TYPE => 'INT2', + DEFAULT => $platform_default->id, + REFERENCES => {TABLE => 'rep_platform', COLUMN => 'id', DELETE => 'SET NULL',}, + } + ); + + # Migrate old is_active stuff to new patch (is in core in 4.2), The old + # column name was 'is_active', the new one is 'isactive' (no underscore). + if ($dbh->bz_column_info('milestones', 'is_active')) { + $dbh->do("UPDATE milestones SET isactive = 0 WHERE is_active = 0;"); + $dbh->bz_drop_column('milestones', 'is_active'); + $dbh->bz_drop_column('milestones', 'is_searchable'); + } + + # remove tables from the old TryAutoLand extension + $dbh->bz_drop_table('autoland_branches'); + $dbh->bz_drop_table('autoland_attachments'); + + unless (Bugzilla::Field->new({name => 'cf_rank'})) { + Bugzilla::Field->create({ + name => 'cf_rank', + description => 'Rank', + type => FIELD_TYPE_INTEGER, + mailhead => 0, + enter_bug => 0, + obsolete => 0, + custom => 1, + buglist => 1, + }); + } + unless (Bugzilla::Field->new({name => 'cf_crash_signature'})) { + Bugzilla::Field->create({ + name => 'cf_crash_signature', + description => 'Crash Signature', + type => FIELD_TYPE_TEXTAREA, + mailhead => 0, + enter_bug => 1, + obsolete => 0, + custom => 1, + buglist => 0, + }); + } - # per-product hw/os defaults - my $op_sys_default = _field_value('op_sys', 'Unspecified', 50); - $dbh->bz_add_column( - 'products', - 'default_op_sys_id' => { - TYPE => 'INT2', - DEFAULT => $op_sys_default->id, - REFERENCES => { - TABLE => 'op_sys', - COLUMN => 'id', - DELETE => 'SET NULL', - }, - } - ); - my $platform_default = _field_value('rep_platform', 'Unspecified', 50); + # Add default security group id column + if (!$dbh->bz_column_info('products', 'security_group_id')) { $dbh->bz_add_column( - 'products', - 'default_platform_id' => { - TYPE => 'INT2', - DEFAULT => $platform_default->id, - REFERENCES => { - TABLE => 'rep_platform', - COLUMN => 'id', - DELETE => 'SET NULL', - }, - } + 'products', + 'security_group_id' => { + TYPE => 'INT3', + REFERENCES => {TABLE => 'groups', COLUMN => 'id', DELETE => 'SET NULL',}, + } ); - # Migrate old is_active stuff to new patch (is in core in 4.2), The old - # column name was 'is_active', the new one is 'isactive' (no underscore). - if ($dbh->bz_column_info('milestones', 'is_active')) { - $dbh->do("UPDATE milestones SET isactive = 0 WHERE is_active = 0;"); - $dbh->bz_drop_column('milestones', 'is_active'); - $dbh->bz_drop_column('milestones', 'is_searchable'); - } - - # remove tables from the old TryAutoLand extension - $dbh->bz_drop_table('autoland_branches'); - $dbh->bz_drop_table('autoland_attachments'); - - unless (Bugzilla::Field->new({ name => 'cf_rank' })) { - Bugzilla::Field->create({ - name => 'cf_rank', - description => 'Rank', - type => FIELD_TYPE_INTEGER, - mailhead => 0, - enter_bug => 0, - obsolete => 0, - custom => 1, - buglist => 1, - }); - } - unless (Bugzilla::Field->new({ name => 'cf_crash_signature' })) { - Bugzilla::Field->create({ - name => 'cf_crash_signature', - description => 'Crash Signature', - type => FIELD_TYPE_TEXTAREA, - mailhead => 0, - enter_bug => 1, - obsolete => 0, - custom => 1, - buglist => 0, - }); - } - - # Add default security group id column - if (!$dbh->bz_column_info('products', 'security_group_id')) { - $dbh->bz_add_column( - 'products', - 'security_group_id' => { - TYPE => 'INT3', - REFERENCES => { - TABLE => 'groups', - COLUMN => 'id', - DELETE => 'SET NULL', - }, - } - ); - - # if there are no groups, then we're creating a database from scratch - # and there's nothing to migrate - my ($group_count) = $dbh->selectrow_array("SELECT COUNT(*) FROM groups"); - if ($group_count) { - # Migrate old product_sec_group mappings from the time this change was made - my %product_sec_groups = ( - "addons.mozilla.org" => 'client-services-security', - "Air Mozilla" => 'mozilla-employee-confidential', - "Android Background Services" => 'cloud-services-security', - "Audio/Visual Infrastructure" => 'mozilla-employee-confidential', - "AUS" => 'client-services-security', - "Bugzilla" => 'bugzilla-security', - "bugzilla.mozilla.org" => 'bugzilla-security', - "Cloud Services" => 'cloud-services-security', - "Community Tools" => 'websites-security', - "Data & BI Services Team" => 'metrics-private', - "Developer Documentation" => 'websites-security', - "Developer Ecosystem" => 'client-services-security', - "Finance" => 'finance', - "Firefox Friends" => 'mozilla-employee-confidential', - "Firefox Health Report" => 'cloud-services-security', - "Infrastructure & Operations" => 'mozilla-employee-confidential', - "Input" => 'websites-security', - "Intellego" => 'intellego-team', - "Internet Public Policy" => 'mozilla-employee-confidential', - "L20n" => 'l20n-security', - "Legal" => 'legal', - "Marketing" => 'marketing-private', - "Marketplace" => 'client-services-security', - "Mozilla Communities" => 'mozilla-communities-security', - "Mozilla Corporation" => 'mozilla-employee-confidential', - "Mozilla Developer Network" => 'websites-security', - "Mozilla Foundation" => 'mozilla-employee-confidential', - "Mozilla Foundation Operations" => 'mozilla-foundation-operations', - "Mozilla Grants" => 'grants', - "mozillaignite" => 'websites-security', - "Mozilla Messaging" => 'mozilla-messaging-confidential', - "Mozilla Metrics" => 'metrics-private', - "mozilla.org" => 'mozilla-employee-confidential', - "Mozilla PR" => 'pr-private', - "Mozilla QA" => 'mozilla-employee-confidential', - "Mozilla Reps" => 'mozilla-reps', - "Popcorn" => 'websites-security', - "Privacy" => 'privacy', - "quality.mozilla.org" => 'websites-security', - "Recruiting" => 'hr', - "Release Engineering" => 'mozilla-employee-confidential', - "Snippets" => 'websites-security', - "Socorro" => 'client-services-security', - "support.mozillamessaging.com" => 'websites-security', - "support.mozilla.org" => 'websites-security', - "Talkback" => 'talkback-private', - "Tamarin" => 'tamarin-security', - "Taskcluster" => 'taskcluster-security', - "Testopia" => 'bugzilla-security', - "Tree Management" => 'mozilla-employee-confidential', - "Web Apps" => 'client-services-security', - "Webmaker" => 'websites-security', - "Websites" => 'websites-security', - "Webtools" => 'webtools-security', - "www.mozilla.org" => 'websites-security', - ); - # 1. Set all to core-security by default - my $core_sec_group = Bugzilla::Group->new({ name => Bugzilla->params->{insidergroup} }); - $dbh->do("UPDATE products SET security_group_id = ?", undef, $core_sec_group->id); - # 2. Update the ones that have explicit security groups - foreach my $prod_name (keys %product_sec_groups) { - my $group_name = $product_sec_groups{$prod_name}; - next if $group_name eq Bugzilla->params->{insidergroup}; # already done - my $group = Bugzilla::Group->new({ name => $group_name, cache => 1 }); - if (!$group) { - warn "Security group $group_name not found. Using insider group instead.\n"; - next; - } - $dbh->do("UPDATE products SET security_group_id = ? WHERE name = ?", undef, $group->id, $prod_name); - } + # if there are no groups, then we're creating a database from scratch + # and there's nothing to migrate + my ($group_count) = $dbh->selectrow_array("SELECT COUNT(*) FROM groups"); + if ($group_count) { + + # Migrate old product_sec_group mappings from the time this change was made + my %product_sec_groups = ( + "addons.mozilla.org" => 'client-services-security', + "Air Mozilla" => 'mozilla-employee-confidential', + "Android Background Services" => 'cloud-services-security', + "Audio/Visual Infrastructure" => 'mozilla-employee-confidential', + "AUS" => 'client-services-security', + "Bugzilla" => 'bugzilla-security', + "bugzilla.mozilla.org" => 'bugzilla-security', + "Cloud Services" => 'cloud-services-security', + "Community Tools" => 'websites-security', + "Data & BI Services Team" => 'metrics-private', + "Developer Documentation" => 'websites-security', + "Developer Ecosystem" => 'client-services-security', + "Finance" => 'finance', + "Firefox Friends" => 'mozilla-employee-confidential', + "Firefox Health Report" => 'cloud-services-security', + "Infrastructure & Operations" => 'mozilla-employee-confidential', + "Input" => 'websites-security', + "Intellego" => 'intellego-team', + "Internet Public Policy" => 'mozilla-employee-confidential', + "L20n" => 'l20n-security', + "Legal" => 'legal', + "Marketing" => 'marketing-private', + "Marketplace" => 'client-services-security', + "Mozilla Communities" => 'mozilla-communities-security', + "Mozilla Corporation" => 'mozilla-employee-confidential', + "Mozilla Developer Network" => 'websites-security', + "Mozilla Foundation" => 'mozilla-employee-confidential', + "Mozilla Foundation Operations" => 'mozilla-foundation-operations', + "Mozilla Grants" => 'grants', + "mozillaignite" => 'websites-security', + "Mozilla Messaging" => 'mozilla-messaging-confidential', + "Mozilla Metrics" => 'metrics-private', + "mozilla.org" => 'mozilla-employee-confidential', + "Mozilla PR" => 'pr-private', + "Mozilla QA" => 'mozilla-employee-confidential', + "Mozilla Reps" => 'mozilla-reps', + "Popcorn" => 'websites-security', + "Privacy" => 'privacy', + "quality.mozilla.org" => 'websites-security', + "Recruiting" => 'hr', + "Release Engineering" => 'mozilla-employee-confidential', + "Snippets" => 'websites-security', + "Socorro" => 'client-services-security', + "support.mozillamessaging.com" => 'websites-security', + "support.mozilla.org" => 'websites-security', + "Talkback" => 'talkback-private', + "Tamarin" => 'tamarin-security', + "Taskcluster" => 'taskcluster-security', + "Testopia" => 'bugzilla-security', + "Tree Management" => 'mozilla-employee-confidential', + "Web Apps" => 'client-services-security', + "Webmaker" => 'websites-security', + "Websites" => 'websites-security', + "Webtools" => 'webtools-security', + "www.mozilla.org" => 'websites-security', + ); + + # 1. Set all to core-security by default + my $core_sec_group + = Bugzilla::Group->new({name => Bugzilla->params->{insidergroup}}); + $dbh->do("UPDATE products SET security_group_id = ?", + undef, $core_sec_group->id); + + # 2. Update the ones that have explicit security groups + foreach my $prod_name (keys %product_sec_groups) { + my $group_name = $product_sec_groups{$prod_name}; + next if $group_name eq Bugzilla->params->{insidergroup}; # already done + my $group = Bugzilla::Group->new({name => $group_name, cache => 1}); + if (!$group) { + warn "Security group $group_name not found. Using insider group instead.\n"; + next; } + $dbh->do("UPDATE products SET security_group_id = ? WHERE name = ?", + undef, $group->id, $prod_name); + } } + } } # return the Bugzilla::Field::Choice object for the specified field and value. # if the value doesn't exist it will be created. sub _field_value { - my ($field_name, $value_name, $sort_key) = @_; - my $field = Bugzilla::Field->check({ name => $field_name }); - my $existing = Bugzilla::Field::Choice->type($field)->match({ value => $value_name }); - return $existing->[0] if $existing && @$existing; - return Bugzilla::Field::Choice->type($field)->create({ - value => $value_name, - sortkey => $sort_key, - isactive => 1, - }); + my ($field_name, $value_name, $sort_key) = @_; + my $field = Bugzilla::Field->check({name => $field_name}); + my $existing + = Bugzilla::Field::Choice->type($field)->match({value => $value_name}); + return $existing->[0] if $existing && @$existing; + return Bugzilla::Field::Choice->type($field) + ->create({value => $value_name, sortkey => $sort_key, isactive => 1,}); } sub _last_closed_date { - my ($self) = @_; - my $dbh = Bugzilla->dbh; + my ($self) = @_; + my $dbh = Bugzilla->dbh; - return $self->{'last_closed_date'} if defined $self->{'last_closed_date'}; + return $self->{'last_closed_date'} if defined $self->{'last_closed_date'}; - my $closed_statuses = "'" . join("','", map { $_->name } closed_bug_statuses()) . "'"; - my $status_field_id = get_field_id('bug_status'); + my $closed_statuses + = "'" . join("','", map { $_->name } closed_bug_statuses()) . "'"; + my $status_field_id = get_field_id('bug_status'); - $self->{'last_closed_date'} = $dbh->selectrow_array(" + $self->{'last_closed_date'} = $dbh->selectrow_array(" SELECT bugs_activity.bug_when FROM bugs_activity WHERE bugs_activity.fieldid = ? AND bugs_activity.added IN ($closed_statuses) AND bugs_activity.bug_id = ? - ORDER BY bugs_activity.bug_when DESC " . $dbh->sql_limit(1), - undef, $status_field_id, $self->id - ); + ORDER BY bugs_activity.bug_when DESC " . $dbh->sql_limit(1), undef, + $status_field_id, $self->id); - return $self->{'last_closed_date'}; + return $self->{'last_closed_date'}; } sub field_end_of_create { - my ($self, $args) = @_; - my $field = $args->{'field'}; - - # Create an IT bug so Mozilla's DBAs so they can update the grants for metrics - - if (Bugzilla->localconfig->{'urlbase'} ne 'https://bugzilla.mozilla.org/' - && Bugzilla->localconfig->{'urlbase'} ne 'https://bugzilla.allizom.org/') - { - return; - } - - my $name = $field->name; - - if (Bugzilla->usage_mode == USAGE_MODE_CMDLINE) { - Bugzilla->set_user(Bugzilla::User->check({ name => Bugzilla->params->{'nobody_user'} })); - print "Creating IT permission grant bug for new field '$name'..."; - } - - my $bug_data = { - short_desc => "Custom field '$name' added to bugzilla.mozilla.org", - product => 'Data & BI Services Team', - component => 'Database Operations', - bug_severity => 'normal', - op_sys => 'All', - rep_platform => 'All', - version => 'other', - }; - - my $comment = <<COMMENT; + my ($self, $args) = @_; + my $field = $args->{'field'}; + + # Create an IT bug so Mozilla's DBAs so they can update the grants for metrics + + if ( Bugzilla->localconfig->{'urlbase'} ne 'https://bugzilla.mozilla.org/' + && Bugzilla->localconfig->{'urlbase'} ne 'https://bugzilla.allizom.org/') + { + return; + } + + my $name = $field->name; + + if (Bugzilla->usage_mode == USAGE_MODE_CMDLINE) { + Bugzilla->set_user(Bugzilla::User->check( + {name => Bugzilla->params->{'nobody_user'}})); + print "Creating IT permission grant bug for new field '$name'..."; + } + + my $bug_data = { + short_desc => "Custom field '$name' added to bugzilla.mozilla.org", + product => 'Data & BI Services Team', + component => 'Database Operations', + bug_severity => 'normal', + op_sys => 'All', + rep_platform => 'All', + version => 'other', + }; + + my $comment = <<COMMENT; The custom field '$name' has been added to the BMO database. Please run the following on bugzilla1.db.scl3.mozilla.com: COMMENT - if ($field->type == FIELD_TYPE_SINGLE_SELECT - || $field->type == FIELD_TYPE_MULTI_SELECT) { - $comment .= <<COMMENT; + if ( $field->type == FIELD_TYPE_SINGLE_SELECT + || $field->type == FIELD_TYPE_MULTI_SELECT) + { + $comment .= <<COMMENT; GRANT SELECT ON `bugs`.`$name` TO 'metrics'\@'10.22.70.20_'; GRANT SELECT ON `bugs`.`$name` TO 'metrics'\@'10.22.70.21_'; COMMENT - } - if ($field->type == FIELD_TYPE_MULTI_SELECT) { - $comment .= <<COMMENT; + } + if ($field->type == FIELD_TYPE_MULTI_SELECT) { + $comment .= <<COMMENT; GRANT SELECT ON `bugs`.`bug_$name` TO 'metrics'\@'10.22.70.20_'; GRANT SELECT ON `bugs`.`bug_$name` TO 'metrics'\@'10.22.70.21_'; COMMENT - } - if ($field->type != FIELD_TYPE_MULTI_SELECT) { - $comment .= <<COMMENT; + } + if ($field->type != FIELD_TYPE_MULTI_SELECT) { + $comment .= <<COMMENT; GRANT SELECT ($name) ON `bugs`.`bugs` TO 'metrics'\@'10.22.70.20_'; GRANT SELECT ($name) ON `bugs`.`bugs` TO 'metrics'\@'10.22.70.21_'; COMMENT - } + } - $bug_data->{'comment'} = $comment; + $bug_data->{'comment'} = $comment; - my $old_error_mode = Bugzilla->error_mode; - Bugzilla->error_mode(ERROR_MODE_DIE); + my $old_error_mode = Bugzilla->error_mode; + Bugzilla->error_mode(ERROR_MODE_DIE); - my $new_bug = eval { Bugzilla::Bug->create($bug_data) }; + my $new_bug = eval { Bugzilla::Bug->create($bug_data) }; - my $error = $@; - undef $@; - Bugzilla->error_mode($old_error_mode); + my $error = $@; + undef $@; + Bugzilla->error_mode($old_error_mode); - if ($error || !($new_bug && $new_bug->{'bug_id'})) { - warn "Error creating IT bug for new field $name: $error"; - if (Bugzilla->usage_mode == USAGE_MODE_CMDLINE) { - print "\nError: $error\n"; - } + if ($error || !($new_bug && $new_bug->{'bug_id'})) { + warn "Error creating IT bug for new field $name: $error"; + if (Bugzilla->usage_mode == USAGE_MODE_CMDLINE) { + print "\nError: $error\n"; } - else { - Bugzilla::BugMail::Send($new_bug->id, { changer => Bugzilla->user }); - if (Bugzilla->usage_mode == USAGE_MODE_CMDLINE) { - print "bug " . $new_bug->id . " created.\n"; - } + } + else { + Bugzilla::BugMail::Send($new_bug->id, {changer => Bugzilla->user}); + if (Bugzilla->usage_mode == USAGE_MODE_CMDLINE) { + print "bug " . $new_bug->id . " created.\n"; } + } } sub webservice { - my ($self, $args) = @_; + my ($self, $args) = @_; - my $dispatch = $args->{dispatch}; - $dispatch->{BMO} = "Bugzilla::Extension::BMO::WebService"; + my $dispatch = $args->{dispatch}; + $dispatch->{BMO} = "Bugzilla::Extension::BMO::WebService"; } sub psgi_builder { - my ($self, $args) = @_; - my $mount = $args->{mount}; - - my $ses_index = Plack::Builder::builder(sub { - my $auth_user = Bugzilla->localconfig->{ses_username}; - my $auth_pass = Bugzilla->localconfig->{ses_password}; - Plack::Builder::enable("Auth::Basic", authenticator => sub { - my ($username, $password, $env) = @_; - return ( $auth_user - && $auth_pass - && $username - && $password - && $username eq $auth_user - && $password eq $auth_pass ); - }); - compile_cgi("ses/index.cgi"); - }); + my ($self, $args) = @_; + my $mount = $args->{mount}; + + my $ses_index = Plack::Builder::builder(sub { + my $auth_user = Bugzilla->localconfig->{ses_username}; + my $auth_pass = Bugzilla->localconfig->{ses_password}; + Plack::Builder::enable( + "Auth::Basic", + authenticator => sub { + my ($username, $password, $env) = @_; + return ($auth_user + && $auth_pass + && $username + && $password + && $username eq $auth_user + && $password eq $auth_pass); + } + ); + compile_cgi("ses/index.cgi"); + }); - $mount->{'ses/index.cgi'} = $ses_index; + $mount->{'ses/index.cgi'} = $ses_index; } our $search_content_matches; + BEGIN { - $search_content_matches = \&Bugzilla::Search::_content_matches; + $search_content_matches = \&Bugzilla::Search::_content_matches; } sub search_operator_field_override { - my ($self, $args) = @_; - my $search = $args->{'search'}; - my $operators = $args->{'operators'}; - - my $cgi = Bugzilla->cgi; - my @comments = $cgi->param('comments'); - my $exclude_comments = scalar(@comments) && !grep { $_ eq '1' } @comments; - - if ($cgi->param('query_format') - && $cgi->param('query_format') eq 'specific' - && $exclude_comments - ) { - # use the non-comment operator - $operators->{'content'}->{matches} = \&_short_desc_matches; - $operators->{'content'}->{notmatches} = \&_short_desc_matches; - - } else { - # restore default content operator - $operators->{'content'}->{matches} = $search_content_matches; - $operators->{'content'}->{notmatches} = $search_content_matches; - } + my ($self, $args) = @_; + my $search = $args->{'search'}; + my $operators = $args->{'operators'}; + + my $cgi = Bugzilla->cgi; + my @comments = $cgi->param('comments'); + my $exclude_comments = scalar(@comments) && !grep { $_ eq '1' } @comments; + + if ( $cgi->param('query_format') + && $cgi->param('query_format') eq 'specific' + && $exclude_comments) + { + # use the non-comment operator + $operators->{'content'}->{matches} = \&_short_desc_matches; + $operators->{'content'}->{notmatches} = \&_short_desc_matches; + + } + else { + # restore default content operator + $operators->{'content'}->{matches} = $search_content_matches; + $operators->{'content'}->{notmatches} = $search_content_matches; + } } sub _short_desc_matches { - # copy of Bugzilla::Search::_content_matches with comment searching removed - my ($self, $args) = @_; - my ($chart_id, $joins, $fields, $operator, $value) = - @$args{qw(chart_id joins fields operator value)}; - my $dbh = Bugzilla->dbh; + # copy of Bugzilla::Search::_content_matches with comment searching removed - # Add the fulltext table to the query so we can search on it. - my $table = "bugs_fulltext_$chart_id"; - push(@$joins, { table => 'bugs_fulltext', as => $table }); + my ($self, $args) = @_; + my ($chart_id, $joins, $fields, $operator, $value) + = @$args{qw(chart_id joins fields operator value)}; + my $dbh = Bugzilla->dbh; - # Create search terms to add to the SELECT and WHERE clauses. - my ($term, $rterm) = - $dbh->sql_fulltext_search("$table.short_desc", $value, 2); - $rterm = $term if !$rterm; + # Add the fulltext table to the query so we can search on it. + my $table = "bugs_fulltext_$chart_id"; + push(@$joins, {table => 'bugs_fulltext', as => $table}); - # The term to use in the WHERE clause. - if ($operator =~ /not/i) { - $term = "NOT($term)"; - } - $args->{term} = $term; + # Create search terms to add to the SELECT and WHERE clauses. + my ($term, $rterm) = $dbh->sql_fulltext_search("$table.short_desc", $value, 2); + $rterm = $term if !$rterm; + + # The term to use in the WHERE clause. + if ($operator =~ /not/i) { + $term = "NOT($term)"; + } + $args->{term} = $term; + + my $current = $self->COLUMNS->{'relevance'}->{name}; + $current = $current ? "$current + " : ''; - my $current = $self->COLUMNS->{'relevance'}->{name}; - $current = $current ? "$current + " : ''; - # For NOT searches, we just add 0 to the relevance. - my $select_term = $operator =~ /not/ ? 0 : "($current$rterm)"; - $self->COLUMNS->{'relevance'}->{name} = $select_term; + # For NOT searches, we just add 0 to the relevance. + my $select_term = $operator =~ /not/ ? 0 : "($current$rterm)"; + $self->COLUMNS->{'relevance'}->{name} = $select_term; } sub mailer_before_send { - my ($self, $args) = @_; - my $email = $args->{email}; + my ($self, $args) = @_; + my $email = $args->{email}; - _log_sent_email($email); + _log_sent_email($email); - # $bug->mentors is added by the Review extension - if (Bugzilla::Bug->can('mentors')) { - _add_mentors_header($email); - } + # $bug->mentors is added by the Review extension + if (Bugzilla::Bug->can('mentors')) { + _add_mentors_header($email); + } - # insert x-bugzilla headers into the body - _inject_headers_into_body($email); + # insert x-bugzilla headers into the body + _inject_headers_into_body($email); } # Log a summary of bugmail sent to the syslog, for auditing and monitoring sub _log_sent_email { - my $email = shift; + my $email = shift; - my $recipient = $email->header('to'); - return unless $recipient; + my $recipient = $email->header('to'); + return unless $recipient; - my $subject = $email->header('Subject'); + my $subject = $email->header('Subject'); - my $bug_id = $email->header('X-Bugzilla-ID'); - if (!$bug_id && $subject =~ /[\[\(]Bug (\d+)/i) { - $bug_id = $1; - } - $bug_id = $bug_id ? "bug-$bug_id" : '-'; - - my $message_type; - my $type = $email->header('X-Bugzilla-Type'); - my $reason = $email->header('X-Bugzilla-Reason'); - if ($type eq 'whine' || $type eq 'request' || $type eq 'admin') { - $message_type = $type; - } elsif ($reason && $reason ne 'None') { - $message_type = $reason; - } else { - $message_type = $email->header('X-Bugzilla-Watch-Reason'); - } - $message_type ||= $type || '?'; + my $bug_id = $email->header('X-Bugzilla-ID'); + if (!$bug_id && $subject =~ /[\[\(]Bug (\d+)/i) { + $bug_id = $1; + } + $bug_id = $bug_id ? "bug-$bug_id" : '-'; - $subject =~ s/[\[\(]Bug \d+[\]\)]\s*//; + my $message_type; + my $type = $email->header('X-Bugzilla-Type'); + my $reason = $email->header('X-Bugzilla-Reason'); + if ($type eq 'whine' || $type eq 'request' || $type eq 'admin') { + $message_type = $type; + } + elsif ($reason && $reason ne 'None') { + $message_type = $reason; + } + else { + $message_type = $email->header('X-Bugzilla-Watch-Reason'); + } + $message_type ||= $type || '?'; - _syslog("[bugmail] $recipient ($message_type) $bug_id $subject"); + $subject =~ s/[\[\(]Bug \d+[\]\)]\s*//; + + _syslog("[bugmail] $recipient ($message_type) $bug_id $subject"); } # Add X-Bugzilla-Mentors field to bugmail sub _add_mentors_header { - my $email = shift; - return unless my $bug_id = $email->header('X-Bugzilla-ID'); - return unless my $bug = Bugzilla::Bug->new({ id => $bug_id, cache => 1 }); - return unless my $mentors = $bug->mentors; - return unless @$mentors; - $email->header_set('X-Bugzilla-Mentors', join(', ', map { $_->login } @$mentors)); + my $email = shift; + return unless my $bug_id = $email->header('X-Bugzilla-ID'); + return unless my $bug = Bugzilla::Bug->new({id => $bug_id, cache => 1}); + return unless my $mentors = $bug->mentors; + return unless @$mentors; + $email->header_set('X-Bugzilla-Mentors', + join(', ', map { $_->login } @$mentors)); } sub _inject_headers_into_body { - my $email = shift; - my $replacement = ''; - - my $recipient = Bugzilla::User->new({ name => $email->header('To'), cache => 1 }); - if ($recipient - && $recipient->settings->{headers_in_body}->{value} eq 'on') - { - my @headers; - my $it = natatime(2, $email->header_pairs); - while (my ($name, $value) = $it->()) { - next unless $name =~ /^X-Bugzilla-(.+)/; - if ($name eq 'X-Bugzilla-Flags' || $name eq 'X-Bugzilla-Changed-Field-Names') { - # these are multi-value fields, split on space - foreach my $v (split(/\s+/, $value)) { - push @headers, "$name: $v"; - } - } - elsif ($name eq 'X-Bugzilla-Changed-Fields') { - # cannot split on space for this field, because field names contain - # spaces. instead work from a list of field names. - my @fields = - map { $_->description } - @{ Bugzilla->fields }; - # these aren't real fields, but exist in the headers - push @fields, ('Comment Created', 'Attachment Created'); - @fields = - sort { length($b) <=> length($a) } - @fields; - while ($value ne '') { - foreach my $field (@fields) { - if ($value eq $field) { - push @headers, "$name: $field"; - $value = ''; - last; - } - if (substr($value, 0, length($field) + 1) eq $field . ' ') { - push @headers, "$name: $field"; - $value = substr($value, length($field) + 1); - last; - } - } - } + my $email = shift; + my $replacement = ''; + + my $recipient = Bugzilla::User->new({name => $email->header('To'), cache => 1}); + if ($recipient && $recipient->settings->{headers_in_body}->{value} eq 'on') { + my @headers; + my $it = natatime(2, $email->header_pairs); + while (my ($name, $value) = $it->()) { + next unless $name =~ /^X-Bugzilla-(.+)/; + if ($name eq 'X-Bugzilla-Flags' || $name eq 'X-Bugzilla-Changed-Field-Names') { + + # these are multi-value fields, split on space + foreach my $v (split(/\s+/, $value)) { + push @headers, "$name: $v"; + } + } + elsif ($name eq 'X-Bugzilla-Changed-Fields') { + + # cannot split on space for this field, because field names contain + # spaces. instead work from a list of field names. + my @fields = map { $_->description } @{Bugzilla->fields}; + + # these aren't real fields, but exist in the headers + push @fields, ('Comment Created', 'Attachment Created'); + @fields = sort { length($b) <=> length($a) } @fields; + while ($value ne '') { + foreach my $field (@fields) { + if ($value eq $field) { + push @headers, "$name: $field"; + $value = ''; + last; } - else { - push @headers, "$name: $value"; + if (substr($value, 0, length($field) + 1) eq $field . ' ') { + push @headers, "$name: $field"; + $value = substr($value, length($field) + 1); + last; } + } } - $replacement = join("\n", @headers); - } - - # update the message body - if (scalar($email->parts) > 1) { - $email->walk_parts(sub { - my ($part) = @_; - - # skip top-level - return if $part->parts > 1; - - # do not filter attachments such as patches, etc. - return if - $part->header('Content-Disposition') - && $part->header('Content-Disposition') =~ /attachment/; - - # text/plain|html only - return unless $part->content_type =~ /^text\/(?:html|plain)/; - - # hide in html content - if ($replacement && $part->content_type =~ /^text\/html/) { - $replacement = '<pre style="font-size: 0pt; color: #fff">' . $replacement . '</pre>'; - } - - # and inject - _replace_placeholder_in_part($part, $replacement); - }); + } + else { + push @headers, "$name: $value"; + } + } + $replacement = join("\n", @headers); + } + + # update the message body + if (scalar($email->parts) > 1) { + $email->walk_parts(sub { + my ($part) = @_; + + # skip top-level + return if $part->parts > 1; + + # do not filter attachments such as patches, etc. + return + if $part->header('Content-Disposition') + && $part->header('Content-Disposition') =~ /attachment/; + + # text/plain|html only + return unless $part->content_type =~ /^text\/(?:html|plain)/; + + # hide in html content + if ($replacement && $part->content_type =~ /^text\/html/) { + $replacement + = '<pre style="font-size: 0pt; color: #fff">' . $replacement . '</pre>'; + } + + # and inject + _replace_placeholder_in_part($part, $replacement); + }); - # force Email::MIME to re-create all the parts. without this - # as_string() doesn't return the updated body for multi-part sub-parts. - $email->parts_set([ $email->subparts ]); - } - elsif (!$email->content_type - || $email->content_type =~ /^text\/(?:html|plain)/) - { - # text-only email - _replace_placeholder_in_part($email, $replacement); - } + # force Email::MIME to re-create all the parts. without this + # as_string() doesn't return the updated body for multi-part sub-parts. + $email->parts_set([$email->subparts]); + } + elsif (!$email->content_type || $email->content_type =~ /^text\/(?:html|plain)/) + { + # text-only email + _replace_placeholder_in_part($email, $replacement); + } } sub _replace_placeholder_in_part { - my ($part, $replacement) = @_; + my ($part, $replacement) = @_; - _fix_encoding($part); + _fix_encoding($part); - # replace - my $placeholder = quotemeta('@@body-headers@@'); - my $body = $part->body_str; - $body =~ s/$placeholder/$replacement/; - $part->body_str_set($body); + # replace + my $placeholder = quotemeta('@@body-headers@@'); + my $body = $part->body_str; + $body =~ s/$placeholder/$replacement/; + $part->body_str_set($body); } 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 _syslog { - my $message = shift; - openlog('apache', 'cons,pid', 'local4'); - syslog('notice', encode_utf8($message)); - closelog(); + my $message = shift; + openlog('apache', 'cons,pid', 'local4'); + syslog('notice', encode_utf8($message)); + closelog(); } sub post_bug_after_creation { - my ($self, $args) = @_; - return unless my $format = Bugzilla->input_params->{format}; - my $bug = $args->{vars}->{bug}; - - if ($format eq 'employee-incident' - && $bug->component eq 'Server Operations: Desktop Issues') - { - $self->_post_employee_incident_bug($args); - } - elsif ($format eq 'swag') { - $self->_post_gear_bug($args); - } - elsif ($format eq 'mozpr') { - $self->_post_mozpr_bug($args); - } - elsif ($format eq 'dev-engagement-event') { - $self->_post_dev_engagement($args); - } - elsif ($format eq 'shield-studies') { - $self->_post_shield_studies($args); - } + my ($self, $args) = @_; + return unless my $format = Bugzilla->input_params->{format}; + my $bug = $args->{vars}->{bug}; + + if ( $format eq 'employee-incident' + && $bug->component eq 'Server Operations: Desktop Issues') + { + $self->_post_employee_incident_bug($args); + } + elsif ($format eq 'swag') { + $self->_post_gear_bug($args); + } + elsif ($format eq 'mozpr') { + $self->_post_mozpr_bug($args); + } + elsif ($format eq 'dev-engagement-event') { + $self->_post_dev_engagement($args); + } + elsif ($format eq 'shield-studies') { + $self->_post_shield_studies($args); + } } sub _post_employee_incident_bug { - my ($self, $args) = @_; - my $vars = $args->{vars}; - my $bug = $vars->{bug}; - - my $error_mode_cache = Bugzilla->error_mode; - Bugzilla->error_mode(ERROR_MODE_DIE); - - my $template = Bugzilla->template; - my $cgi = Bugzilla->cgi; + my ($self, $args) = @_; + my $vars = $args->{vars}; + my $bug = $vars->{bug}; + + my $error_mode_cache = Bugzilla->error_mode; + Bugzilla->error_mode(ERROR_MODE_DIE); + + my $template = Bugzilla->template; + my $cgi = Bugzilla->cgi; + + my ($investigate_bug, $ssh_key_bug); + my $old_user = Bugzilla->user; + eval { + Bugzilla->set_user(Bugzilla::User->new( + {name => Bugzilla->params->{'nobody_user'}})); + my $new_user = Bugzilla->user; + + # HACK: User needs to be in the editbugs and primary bug's group to allow + # setting of dependencies. + $new_user->{'groups'} = [ + Bugzilla::Group->new({name => 'editbugs'}), + Bugzilla::Group->new({name => 'infra'}), + Bugzilla::Group->new({name => 'infrasec'}) + ]; - my ($investigate_bug, $ssh_key_bug); - my $old_user = Bugzilla->user; - eval { - Bugzilla->set_user(Bugzilla::User->new({ name => Bugzilla->params->{'nobody_user'} })); - my $new_user = Bugzilla->user; - - # HACK: User needs to be in the editbugs and primary bug's group to allow - # setting of dependencies. - $new_user->{'groups'} = [ Bugzilla::Group->new({ name => 'editbugs' }), - Bugzilla::Group->new({ name => 'infra' }), - Bugzilla::Group->new({ name => 'infrasec' }) ]; - - my $recipients = { changer => $new_user }; - $vars->{original_reporter} = $old_user; - - my $comment; - $cgi->param('display_action', ''); - $template->process('bug/create/comment-employee-incident.txt.tmpl', $vars, \$comment) - || ThrowTemplateError($template->error()); - - $investigate_bug = Bugzilla::Bug->create({ - short_desc => 'Investigate Lost Device', - product => 'mozilla.org', - component => 'Security Assurance: Incident', - status_whiteboard => '[infrasec:incident]', - bug_severity => 'critical', - cc => [ 'jstevensen@mozilla.com' ], - groups => [ 'infrasec' ], - comment => $comment, - op_sys => 'All', - rep_platform => 'All', - version => 'other', - dependson => $bug->bug_id, - }); - $bug->set_all({ blocked => { add => [ $investigate_bug->bug_id ] }}); - Bugzilla::BugMail::Send($investigate_bug->id, $recipients); - - Bugzilla->set_user($old_user); - $vars->{original_reporter} = ''; - $comment = ''; - $cgi->param('display_action', 'ssh'); - $template->process('bug/create/comment-employee-incident.txt.tmpl', $vars, \$comment) - || ThrowTemplateError($template->error()); - - $ssh_key_bug = Bugzilla::Bug->create({ - short_desc => 'Disable/Regenerate SSH Key', - product => $bug->product, - component => $bug->component, - bug_severity => 'critical', - cc => $bug->cc, - groups => [ map { $_->{name} } @{ $bug->groups } ], - comment => $comment, - op_sys => 'All', - rep_platform => 'All', - version => 'other', - dependson => $bug->bug_id, - }); - $bug->set_all({ blocked => { add => [ $ssh_key_bug->bug_id ] }}); - Bugzilla::BugMail::Send($ssh_key_bug->id, $recipients); - }; - my $error = $@; + my $recipients = {changer => $new_user}; + $vars->{original_reporter} = $old_user; + + my $comment; + $cgi->param('display_action', ''); + $template->process('bug/create/comment-employee-incident.txt.tmpl', + $vars, \$comment) + || ThrowTemplateError($template->error()); + + $investigate_bug = Bugzilla::Bug->create({ + short_desc => 'Investigate Lost Device', + product => 'mozilla.org', + component => 'Security Assurance: Incident', + status_whiteboard => '[infrasec:incident]', + bug_severity => 'critical', + cc => ['jstevensen@mozilla.com'], + groups => ['infrasec'], + comment => $comment, + op_sys => 'All', + rep_platform => 'All', + version => 'other', + dependson => $bug->bug_id, + }); + $bug->set_all({blocked => {add => [$investigate_bug->bug_id]}}); + Bugzilla::BugMail::Send($investigate_bug->id, $recipients); Bugzilla->set_user($old_user); - Bugzilla->error_mode($error_mode_cache); + $vars->{original_reporter} = ''; + $comment = ''; + $cgi->param('display_action', 'ssh'); + $template->process('bug/create/comment-employee-incident.txt.tmpl', + $vars, \$comment) + || ThrowTemplateError($template->error()); + + $ssh_key_bug = Bugzilla::Bug->create({ + short_desc => 'Disable/Regenerate SSH Key', + product => $bug->product, + component => $bug->component, + bug_severity => 'critical', + cc => $bug->cc, + groups => [map { $_->{name} } @{$bug->groups}], + comment => $comment, + op_sys => 'All', + rep_platform => 'All', + version => 'other', + dependson => $bug->bug_id, + }); + $bug->set_all({blocked => {add => [$ssh_key_bug->bug_id]}}); + Bugzilla::BugMail::Send($ssh_key_bug->id, $recipients); + }; + my $error = $@; - if ($error || !$investigate_bug || !$ssh_key_bug) { - warn "Failed to create additional employee-incident bug: $error" if $error; - $vars->{'message'} = 'employee_incident_creation_failed'; - } + Bugzilla->set_user($old_user); + Bugzilla->error_mode($error_mode_cache); + + if ($error || !$investigate_bug || !$ssh_key_bug) { + warn "Failed to create additional employee-incident bug: $error" if $error; + $vars->{'message'} = 'employee_incident_creation_failed'; + } } sub _post_gear_bug { - my ($self, $args) = @_; - my $vars = $args->{vars}; - my $bug = $vars->{bug}; - my $input = Bugzilla->input_params; - - my ($team, $code) = $input->{teamcode} =~ /^(.+?) \((\d+)\)$/; - my @request = ( - "Date Required: $input->{date_required}", - "$input->{firstname} $input->{lastname}", - $input->{email}, - $input->{mozspace}, - $team, - $code, - $input->{purpose}, - ); - my @recipient = ( - "$input->{shiptofirstname} $input->{shiptolastname}", - $input->{shiptoemail}, - $input->{shiptoaddress1}, - $input->{shiptoaddress2}, - $input->{shiptocity}, - $input->{shiptostate}, - $input->{shiptopostcode}, - $input->{shiptocountry}, - "Phone: $input->{shiptophone}", - $input->{shiptoidrut}, - ); - - # the csv has 14 item fields - my @items = map { trim($_) } split(/\n/, $input->{items}); - my @csv; - while (@items) { - my @batch; - if (scalar(@items) > 14) { - @batch = splice(@items, 0, 14); - } - else { - @batch = @items; - push @batch, '' for scalar(@items)..13; - @items = (); - } - push @csv, [ @request, @batch, @recipient ]; + my ($self, $args) = @_; + my $vars = $args->{vars}; + my $bug = $vars->{bug}; + my $input = Bugzilla->input_params; + + my ($team, $code) = $input->{teamcode} =~ /^(.+?) \((\d+)\)$/; + my @request = ( + "Date Required: $input->{date_required}", + "$input->{firstname} $input->{lastname}", + $input->{email}, $input->{mozspace}, $team, $code, $input->{purpose}, + ); + my @recipient = ( + "$input->{shiptofirstname} $input->{shiptolastname}", + $input->{shiptoemail}, + $input->{shiptoaddress1}, + $input->{shiptoaddress2}, + $input->{shiptocity}, + $input->{shiptostate}, + $input->{shiptopostcode}, + $input->{shiptocountry}, + "Phone: $input->{shiptophone}", + $input->{shiptoidrut}, + ); + + # the csv has 14 item fields + my @items = map { trim($_) } split(/\n/, $input->{items}); + my @csv; + while (@items) { + my @batch; + if (scalar(@items) > 14) { + @batch = splice(@items, 0, 14); + } + else { + @batch = @items; + push @batch, '' for scalar(@items) .. 13; + @items = (); } + push @csv, [@request, @batch, @recipient]; + } - # csv quoting and concat - foreach my $line (@csv) { - foreach my $field (@$line) { - if ($field =~ s/"/""/g || $field =~ /,/) { - $field = qq#"$field"#; - } - } - $line = join(',', @$line); + # csv quoting and concat + foreach my $line (@csv) { + foreach my $field (@$line) { + if ($field =~ s/"/""/g || $field =~ /,/) { + $field = qq#"$field"#; + } } + $line = join(',', @$line); + } - $self->_add_attachment($args, { - data => join("\n", @csv), - description => "Items (CSV)", - filename => "gear_" . $bug->id . ".csv", - mimetype => "text/csv", - }); - $bug->update($bug->creation_ts); + $self->_add_attachment( + $args, + { + data => join("\n", @csv), + description => "Items (CSV)", + filename => "gear_" . $bug->id . ".csv", + mimetype => "text/csv", + } + ); + $bug->update($bug->creation_ts); } sub _post_mozpr_bug { - my ($self, $args) = @_; - my $vars = $args->{vars}; - my $bug = $vars->{bug}; - my $input = Bugzilla->input_params; - - if ($input->{proj_mat_file}) { - $self->_add_attachment($args, { - data => $input->{proj_mat_file_attach}, - description => $input->{proj_mat_file_desc}, - filename => scalar $input->{proj_mat_file_attach}, - }); - } - if ($input->{pr_mat_file}) { - $self->_add_attachment($args, { - data => $input->{pr_mat_file_attach}, - description => $input->{pr_mat_file_desc}, - filename => scalar $input->{pr_mat_file_attach}, - }); - } - $bug->update($bug->creation_ts); + my ($self, $args) = @_; + my $vars = $args->{vars}; + my $bug = $vars->{bug}; + my $input = Bugzilla->input_params; + + if ($input->{proj_mat_file}) { + $self->_add_attachment( + $args, + { + data => $input->{proj_mat_file_attach}, + description => $input->{proj_mat_file_desc}, + filename => scalar $input->{proj_mat_file_attach}, + } + ); + } + if ($input->{pr_mat_file}) { + $self->_add_attachment( + $args, + { + data => $input->{pr_mat_file_attach}, + description => $input->{pr_mat_file_desc}, + filename => scalar $input->{pr_mat_file_attach}, + } + ); + } + $bug->update($bug->creation_ts); } sub _post_dev_engagement { - my ($self, $args) = @_; - my $vars = $args->{vars}; - my $parent_bug = $vars->{bug}; - my $template = Bugzilla->template; - my $cgi = Bugzilla->cgi; - my $params = Bugzilla->input_params; - my $old_user = Bugzilla->user; - - my $error_mode_cache = Bugzilla->error_mode; - Bugzilla->error_mode(ERROR_MODE_DIE); - - eval { - # Add attachment containing tab delimited field values for - # spreadsheet import. - my @columns = qw(event start_date end_date location attendees - audience desc mozilla_attending_list); - my @attach_values; - foreach my $column(@columns) { - my $value = $params->{$column} || ""; - $value =~ s/"/""/g; - push(@attach_values, qq{"$value"}); - } - - my @requested; - foreach my $param (grep(/^request_/, keys %$params)) { - next if !$params->{$param} || $param eq 'request_other_text'; - $param =~ s/^request_//; - push(@requested, ucfirst($param)); - } - push(@attach_values, '"' . join(",", @requested) . '"'); - - # we wrap the data inside a textarea to allow for the delimited data to - # be pasted directly into google docs. - - my $values = html_quote(join("\t", @attach_values)); - my $data = <<EOF; + my ($self, $args) = @_; + my $vars = $args->{vars}; + my $parent_bug = $vars->{bug}; + my $template = Bugzilla->template; + my $cgi = Bugzilla->cgi; + my $params = Bugzilla->input_params; + my $old_user = Bugzilla->user; + + my $error_mode_cache = Bugzilla->error_mode; + Bugzilla->error_mode(ERROR_MODE_DIE); + + eval { + # Add attachment containing tab delimited field values for + # spreadsheet import. + my @columns = qw(event start_date end_date location attendees + audience desc mozilla_attending_list); + my @attach_values; + foreach my $column (@columns) { + my $value = $params->{$column} || ""; + $value =~ s/"/""/g; + push(@attach_values, qq{"$value"}); + } + + my @requested; + foreach my $param (grep(/^request_/, keys %$params)) { + next if !$params->{$param} || $param eq 'request_other_text'; + $param =~ s/^request_//; + push(@requested, ucfirst($param)); + } + push(@attach_values, '"' . join(",", @requested) . '"'); + + # we wrap the data inside a textarea to allow for the delimited data to + # be pasted directly into google docs. + + my $values = html_quote(join("\t", @attach_values)); + my $data = <<EOF; <!doctype html> <html> <head> @@ -2196,651 +2235,728 @@ sub _post_dev_engagement { </html> EOF - $self->_add_attachment($args, { - data => $data, - description => 'Spreadsheet Data', - filename => 'dev_engagement_submission.html', - mimetype => 'text/html', - }); - }; + $self->_add_attachment( + $args, + { + data => $data, + description => 'Spreadsheet Data', + filename => 'dev_engagement_submission.html', + mimetype => 'text/html', + } + ); + }; - $parent_bug->update($parent_bug->creation_ts); + $parent_bug->update($parent_bug->creation_ts); } sub _post_shield_studies { - my ($self, $args) = @_; - my $vars = $args->{vars}; - my $parent_bug = $vars->{bug}; - my $params = Bugzilla->input_params; - my (@dep_comment, @dep_errors, @send_mail); - - # Common parameters always passed to _file_child_bug - # bug_data and template_suffix will be different for each bug - my $child_params = { - parent_bug => $parent_bug, - template_vars => $vars, - dep_comment => \@dep_comment, - dep_errors => \@dep_errors, - send_mail => \@send_mail, - }; - - # Study Validation Review - $child_params->{'bug_data'} = { - short_desc => '[SHIELD] Study Validation Review for ' . $params->{hypothesis}, - product => 'Shield', - component => 'Shield Study', - bug_severity => 'normal', - op_sys => 'All', - rep_platform => 'All', - version => 'unspecified', - blocked => $parent_bug->bug_id, - }; - $child_params->{'template_suffix'} = 'validation-review'; - _file_child_bug($child_params); - - # Shipping Status - $child_params->{'bug_data'} = { - short_desc => '[SHIELD] Shipping Status for ' . $params->{hypothesis}, - product => 'Shield', - component => 'Shield Study', - bug_severity => 'normal', - op_sys => 'All', - rep_platform => 'All', - version => 'unspecified', - blocked => $parent_bug->bug_id, - }; - $child_params->{'template_suffix'} = 'shipping-status'; - - # Data Review - _file_child_bug($child_params); - $child_params->{'bug_data'} = { - short_desc => '[SHIELD] Data Review for ' . $params->{hypothesis}, - product => 'Shield', - component => 'Shield Study', - bug_severity => 'normal', - op_sys => 'All', - rep_platform => 'All', - version => 'unspecified', - blocked => $parent_bug->bug_id, - }; - $child_params->{'template_suffix'} = 'data-review'; - _file_child_bug($child_params); - - # Legal Review - $child_params->{'bug_data'} = { - short_desc => '[SHIELD] Legal Review for ' . $params->{hypothesis}, - product => 'Legal', - component => 'Firefox', - bug_severity => 'normal', - op_sys => 'All', - rep_platform => 'All', - groups => [ 'mozilla-employee-confidential' ], - version => 'unspecified', - blocked => $parent_bug->bug_id, - }; - $child_params->{'template_suffix'} = 'legal'; - _file_child_bug($child_params); - + my ($self, $args) = @_; + my $vars = $args->{vars}; + my $parent_bug = $vars->{bug}; + my $params = Bugzilla->input_params; + my (@dep_comment, @dep_errors, @send_mail); + + # Common parameters always passed to _file_child_bug + # bug_data and template_suffix will be different for each bug + my $child_params = { + parent_bug => $parent_bug, + template_vars => $vars, + dep_comment => \@dep_comment, + dep_errors => \@dep_errors, + send_mail => \@send_mail, + }; + + # Study Validation Review + $child_params->{'bug_data'} = { + short_desc => '[SHIELD] Study Validation Review for ' . $params->{hypothesis}, + product => 'Shield', + component => 'Shield Study', + bug_severity => 'normal', + op_sys => 'All', + rep_platform => 'All', + version => 'unspecified', + blocked => $parent_bug->bug_id, + }; + $child_params->{'template_suffix'} = 'validation-review'; + _file_child_bug($child_params); + + # Shipping Status + $child_params->{'bug_data'} = { + short_desc => '[SHIELD] Shipping Status for ' . $params->{hypothesis}, + product => 'Shield', + component => 'Shield Study', + bug_severity => 'normal', + op_sys => 'All', + rep_platform => 'All', + version => 'unspecified', + blocked => $parent_bug->bug_id, + }; + $child_params->{'template_suffix'} = 'shipping-status'; + + # Data Review + _file_child_bug($child_params); + $child_params->{'bug_data'} = { + short_desc => '[SHIELD] Data Review for ' . $params->{hypothesis}, + product => 'Shield', + component => 'Shield Study', + bug_severity => 'normal', + op_sys => 'All', + rep_platform => 'All', + version => 'unspecified', + blocked => $parent_bug->bug_id, + }; + $child_params->{'template_suffix'} = 'data-review'; + _file_child_bug($child_params); + + # Legal Review + $child_params->{'bug_data'} = { + short_desc => '[SHIELD] Legal Review for ' . $params->{hypothesis}, + product => 'Legal', + component => 'Firefox', + bug_severity => 'normal', + op_sys => 'All', + rep_platform => 'All', + groups => ['mozilla-employee-confidential'], + version => 'unspecified', + blocked => $parent_bug->bug_id, + }; + $child_params->{'template_suffix'} = 'legal'; + _file_child_bug($child_params); + + if (scalar @dep_errors) { + warn "[Bug " + . $parent_bug->id + . "] Failed to create additional moz-project-review bugs:\n" + . join("\n", @dep_errors); + $vars->{'message'} = 'moz_project_review_creation_failed'; + } + + if (scalar @dep_comment) { + my $comment = join("\n", @dep_comment); if (scalar @dep_errors) { - warn "[Bug " . $parent_bug->id . "] Failed to create additional moz-project-review bugs:\n" . - join("\n", @dep_errors); - $vars->{'message'} = 'moz_project_review_creation_failed'; - } - - if (scalar @dep_comment) { - my $comment = join("\n", @dep_comment); - if (scalar @dep_errors) { - $comment .= "\n\nSome errors occurred creating dependent bugs and have been recorded"; - } - $parent_bug->add_comment($comment); - $parent_bug->update($parent_bug->creation_ts); + $comment + .= "\n\nSome errors occurred creating dependent bugs and have been recorded"; } + $parent_bug->add_comment($comment); + $parent_bug->update($parent_bug->creation_ts); + } - foreach my $bug_id (@send_mail) { - Bugzilla::BugMail::Send($bug_id, { changer => Bugzilla->user }); - } + foreach my $bug_id (@send_mail) { + Bugzilla::BugMail::Send($bug_id, {changer => Bugzilla->user}); + } } sub _file_child_bug { - my ($params) = @_; - my ($parent_bug, $template_vars, $template_suffix, $bug_data, $dep_comment, $dep_errors, $send_mail) - = @$params{qw(parent_bug template_vars template_suffix bug_data dep_comment dep_errors send_mail)}; - my $old_error_mode = Bugzilla->error_mode; - Bugzilla->error_mode(ERROR_MODE_DIE); - - my $new_bug; - eval { - my $comment; - my $full_template = "bug/create/comment-shield-studies-$template_suffix.txt.tmpl"; - Bugzilla->template->process($full_template, $template_vars, \$comment) - || ThrowTemplateError(Bugzilla->template->error()); - $bug_data->{'comment'} = $comment; - if ($new_bug = Bugzilla::Bug->create($bug_data)) { - my $set_all = { - dependson => { add => [ $new_bug->bug_id ] } - }; - $parent_bug->set_all($set_all); - $parent_bug->update($parent_bug->creation_ts); - } + my ($params) = @_; + my ($parent_bug, $template_vars, $template_suffix, $bug_data, $dep_comment, + $dep_errors, $send_mail) + = @$params{ + qw(parent_bug template_vars template_suffix bug_data dep_comment dep_errors send_mail) }; - - if ($@ || !($new_bug && $new_bug->{'bug_id'})) { - push(@$dep_comment, "Error creating $template_suffix review bug"); - push(@$dep_errors, "$template_suffix : $@") if $@; - # Since we performed Bugzilla::Bug::create in an eval block, we - # need to manually rollback the commit as this is not done - # in Bugzilla::Error automatically for eval'ed code. - Bugzilla->dbh->bz_rollback_transaction(); - } - else { - push(@$send_mail, $new_bug->id); - push(@$dep_comment, "Bug " . $new_bug->id . " - " . $new_bug->short_desc); + my $old_error_mode = Bugzilla->error_mode; + Bugzilla->error_mode(ERROR_MODE_DIE); + + my $new_bug; + eval { + my $comment; + my $full_template + = "bug/create/comment-shield-studies-$template_suffix.txt.tmpl"; + Bugzilla->template->process($full_template, $template_vars, \$comment) + || ThrowTemplateError(Bugzilla->template->error()); + $bug_data->{'comment'} = $comment; + if ($new_bug = Bugzilla::Bug->create($bug_data)) { + my $set_all = {dependson => {add => [$new_bug->bug_id]}}; + $parent_bug->set_all($set_all); + $parent_bug->update($parent_bug->creation_ts); } + }; + + if ($@ || !($new_bug && $new_bug->{'bug_id'})) { + push(@$dep_comment, "Error creating $template_suffix review bug"); + push(@$dep_errors, "$template_suffix : $@") if $@; + + # Since we performed Bugzilla::Bug::create in an eval block, we + # need to manually rollback the commit as this is not done + # in Bugzilla::Error automatically for eval'ed code. + Bugzilla->dbh->bz_rollback_transaction(); + } + else { + push(@$send_mail, $new_bug->id); + push(@$dep_comment, "Bug " . $new_bug->id . " - " . $new_bug->short_desc); + } - undef $@; - Bugzilla->error_mode($old_error_mode); + undef $@; + Bugzilla->error_mode($old_error_mode); } sub _pre_fxos_feature { - my ($self, $args) = @_; - my $cgi = Bugzilla->cgi; - my $user = Bugzilla->user; - my $params = $args->{params}; + my ($self, $args) = @_; + my $cgi = Bugzilla->cgi; + my $user = Bugzilla->user; + my $params = $args->{params}; - $params->{keywords} = 'foxfood'; - $params->{keywords} .= ',feature' if ($cgi->param('feature_type') // '') eq 'new'; - $params->{bug_status} = $user->in_group('canconfirm') ? 'NEW' : 'UNCONFIRMED'; + $params->{keywords} = 'foxfood'; + $params->{keywords} .= ',feature' + if ($cgi->param('feature_type') // '') eq 'new'; + $params->{bug_status} = $user->in_group('canconfirm') ? 'NEW' : 'UNCONFIRMED'; } sub _add_attachment { - my ($self, $args, $attachment_args) = @_; - - my $bug = $args->{vars}->{bug}; - $attachment_args->{bug} = $bug; - $attachment_args->{creation_ts} = $bug->creation_ts; - $attachment_args->{ispatch} = 0 unless exists $attachment_args->{ispatch}; - $attachment_args->{isprivate} = 0 unless exists $attachment_args->{isprivate}; - $attachment_args->{mimetype} ||= $self->_detect_content_type($attachment_args->{data}); - - # If the attachment cannot be successfully added to the bug, - # we notify the user, but we don't interrupt the bug creation process. - my $old_error_mode = Bugzilla->error_mode; - Bugzilla->error_mode(ERROR_MODE_DIE); - my $attachment; - eval { - $attachment = Bugzilla::Attachment->create($attachment_args); - }; - warn "$@" if $@; - Bugzilla->error_mode($old_error_mode); - - if ($attachment) { - # Insert comment for attachment - $bug->add_comment('', { isprivate => 0, - type => CMT_ATTACHMENT_CREATED, - extra_data => $attachment->id }); - delete $bug->{attachments}; - } - else { - $args->{vars}->{'message'} = 'attachment_creation_failed'; - } + my ($self, $args, $attachment_args) = @_; + + my $bug = $args->{vars}->{bug}; + $attachment_args->{bug} = $bug; + $attachment_args->{creation_ts} = $bug->creation_ts; + $attachment_args->{ispatch} = 0 unless exists $attachment_args->{ispatch}; + $attachment_args->{isprivate} = 0 unless exists $attachment_args->{isprivate}; + $attachment_args->{mimetype} + ||= $self->_detect_content_type($attachment_args->{data}); + + # If the attachment cannot be successfully added to the bug, + # we notify the user, but we don't interrupt the bug creation process. + my $old_error_mode = Bugzilla->error_mode; + Bugzilla->error_mode(ERROR_MODE_DIE); + my $attachment; + eval { $attachment = Bugzilla::Attachment->create($attachment_args); }; + warn "$@" if $@; + Bugzilla->error_mode($old_error_mode); + + if ($attachment) { + + # Insert comment for attachment + $bug->add_comment('', + {isprivate => 0, type => CMT_ATTACHMENT_CREATED, extra_data => $attachment->id} + ); + delete $bug->{attachments}; + } + else { + $args->{vars}->{'message'} = 'attachment_creation_failed'; + } - # Note: you must call $bug->update($bug->creation_ts) after adding all attachments +# Note: you must call $bug->update($bug->creation_ts) after adding all attachments } # bugzilla's content_type detection makes assumptions about form fields, which # means we can't use it here. this code is lifted from # Bugzilla::Attachment::get_content_type and the TypeSniffer extension. sub _detect_content_type { - my ($self, $data) = @_; - my $cgi = Bugzilla->cgi; - - # browser provided content-type - my $content_type = $cgi->uploadInfo($data)->{'Content-Type'}; - $content_type = 'image/png' if $content_type eq 'image/x-png'; - - if ($content_type eq 'application/octet-stream') { - # detect from filename - my $filename = scalar($data); - if (my $from_filename = mimetype($filename)) { - return $from_filename; - } + my ($self, $data) = @_; + my $cgi = Bugzilla->cgi; + + # browser provided content-type + my $content_type = $cgi->uploadInfo($data)->{'Content-Type'}; + $content_type = 'image/png' if $content_type eq 'image/x-png'; + + if ($content_type eq 'application/octet-stream') { + + # detect from filename + my $filename = scalar($data); + if (my $from_filename = mimetype($filename)) { + return $from_filename; } + } - return $content_type || 'application/octet-stream'; + return $content_type || 'application/octet-stream'; } sub buglist_columns { - my ($self, $args) = @_; - my $columns = $args->{columns}; - $columns->{'cc_count'} = { - name => '(SELECT COUNT(*) FROM cc WHERE cc.bug_id = bugs.bug_id)', - title => 'CC Count', - }; - $columns->{'dupe_count'} = { - name => '(SELECT COUNT(*) FROM duplicates WHERE duplicates.dupe_of = bugs.bug_id)', - title => 'Duplicate Count', - }; + my ($self, $args) = @_; + my $columns = $args->{columns}; + $columns->{'cc_count'} = { + name => '(SELECT COUNT(*) FROM cc WHERE cc.bug_id = bugs.bug_id)', + title => 'CC Count', + }; + $columns->{'dupe_count'} = { + name => + '(SELECT COUNT(*) FROM duplicates WHERE duplicates.dupe_of = bugs.bug_id)', + title => 'Duplicate Count', + }; } sub enter_bug_start { - my ($self, $args) = @_; - # if configured with create_bug_formats, force users into a custom bug - # format (can be overridden with a __standard__ format) - my $cgi = Bugzilla->cgi; - if ($cgi->param('format')) { - if ($cgi->param('format') eq '__standard__') { - $cgi->delete('format'); - $cgi->param('format_forced', 1); - } - } elsif (my $format = forced_format($cgi->param('product'))) { - $cgi->param('format', $format); - } - - # If product eq 'mozilla.org' and format eq 'itrequest', then - # switch to the new 'Infrastructure & Operations' product. - if ($cgi->param('product') && $cgi->param('product') eq 'mozilla.org' - && $cgi->param('format') && $cgi->param('format') eq 'itrequest') - { - $cgi->param('product', 'Infrastructure & Operations'); - } - - # map renamed groups - $cgi->param('groups', _map_groups($cgi->param('groups'))); + my ($self, $args) = @_; + + # if configured with create_bug_formats, force users into a custom bug + # format (can be overridden with a __standard__ format) + my $cgi = Bugzilla->cgi; + if ($cgi->param('format')) { + if ($cgi->param('format') eq '__standard__') { + $cgi->delete('format'); + $cgi->param('format_forced', 1); + } + } + elsif (my $format = forced_format($cgi->param('product'))) { + $cgi->param('format', $format); + } + + # If product eq 'mozilla.org' and format eq 'itrequest', then + # switch to the new 'Infrastructure & Operations' product. + if ( $cgi->param('product') + && $cgi->param('product') eq 'mozilla.org' + && $cgi->param('format') + && $cgi->param('format') eq 'itrequest') + { + $cgi->param('product', 'Infrastructure & Operations'); + } + + # map renamed groups + $cgi->param('groups', _map_groups($cgi->param('groups'))); } sub bug_before_create { - my ($self, $args) = @_; - my $params = $args->{params}; - if (exists $params->{groups}) { - # map renamed groups - $params->{groups} = [ _map_groups($params->{groups}) ]; - } - if ((Bugzilla->cgi->param('format') // '') eq 'fxos-feature') { - $self->_pre_fxos_feature($args); - } + my ($self, $args) = @_; + my $params = $args->{params}; + if (exists $params->{groups}) { + + # map renamed groups + $params->{groups} = [_map_groups($params->{groups})]; + } + if ((Bugzilla->cgi->param('format') // '') eq 'fxos-feature') { + $self->_pre_fxos_feature($args); + } } sub _map_groups { - my (@groups) = @_; - return unless @groups; - @groups = @{ $groups[0] } if ref($groups[0]); - return map { - # map mozilla-corporation-confidential => mozilla-employee-confidential - $_ eq 'mozilla-corporation-confidential' - ? 'mozilla-employee-confidential' - : $_ - } @groups; + my (@groups) = @_; + return unless @groups; + @groups = @{$groups[0]} if ref($groups[0]); + return map { + + # map mozilla-corporation-confidential => mozilla-employee-confidential + $_ eq 'mozilla-corporation-confidential' ? 'mozilla-employee-confidential' : $_ + } @groups; } sub forced_format { - # note: this is also called from the guided bug entry extension - my ($product) = @_; - return undef unless defined $product; - - # always work on the correct product name - $product = Bugzilla::Product->new({ name => $product, cache => 1 }) - unless blessed($product); - return undef unless $product; - - # check for a forced-format entry - my $forced = $create_bug_formats{$product->name} - || return; - - # should this user be included? - my $user = Bugzilla->user; - my $include = ref($forced->{include}) ? $forced->{include} : [ $forced->{include} ]; - foreach my $inc (@$include) { - return $forced->{format} if $user->in_group($inc); - } - return undef; + # note: this is also called from the guided bug entry extension + my ($product) = @_; + return undef unless defined $product; + + # always work on the correct product name + $product = Bugzilla::Product->new({name => $product, cache => 1}) + unless blessed($product); + return undef unless $product; + + # check for a forced-format entry + my $forced = $create_bug_formats{$product->name} || return; + + # should this user be included? + my $user = Bugzilla->user; + my $include + = ref($forced->{include}) ? $forced->{include} : [$forced->{include}]; + foreach my $inc (@$include) { + return $forced->{format} if $user->in_group($inc); + } + + return undef; } sub query_database { - my ($vars) = @_; - my $cgi = Bugzilla->cgi; - my $user = Bugzilla->user; - my $template = Bugzilla->template; - - # validate group membership - $user->in_group('query_database') - || ThrowUserError('auth_failure', { group => 'query_database', - action => 'access', - object => 'query_database' }); - - # read query - my $input = Bugzilla->input_params; - my $query = $input->{query}; - $vars->{query} = $query; - - if ($query) { - # Only allow POST requests - if ($cgi->request_method ne 'POST') { - ThrowCodeError('illegal_request_method', - { method => $cgi->request_method, accepted => ['POST'] }); - } + my ($vars) = @_; + my $cgi = Bugzilla->cgi; + my $user = Bugzilla->user; + my $template = Bugzilla->template; - check_hash_token($input->{token}, ['query_database']); - trick_taint($query); - $vars->{executed} = 1; + # validate group membership + $user->in_group('query_database') + || ThrowUserError('auth_failure', + {group => 'query_database', action => 'access', object => 'query_database'}); - # add limit if missing - if ($query !~ /\sLIMIT\s+\d+\s*$/si) { - $query .= ' LIMIT 1000'; - $vars->{query} = $query; - } + # read query + my $input = Bugzilla->input_params; + my $query = $input->{query}; + $vars->{query} = $query; - # log query - _syslog(sprintf("[db_query] %s %s", $user->login, $query)); - - # connect to database and execute - # switching to the shadow db gives us a read-only connection - my $dbh = Bugzilla->switch_to_shadow_db(); - my $sth; - eval { - $sth = $dbh->prepare($query); - $sth->execute(); - }; - if ($@) { - $vars->{sql_error} = $@; - return; - } + if ($query) { - # build result - my $columns = $sth->{NAME}; - my $rows; - while (my @row = $sth->fetchrow_array) { - push @$rows, \@row; - } + # Only allow POST requests + if ($cgi->request_method ne 'POST') { + ThrowCodeError('illegal_request_method', + {method => $cgi->request_method, accepted => ['POST']}); + } - # return results - $vars->{columns} = $columns; - $vars->{rows} = $rows; + check_hash_token($input->{token}, ['query_database']); + trick_taint($query); + $vars->{executed} = 1; - if ($input->{csv}) { - print $cgi->header(-type=> 'text/csv', - -content_disposition=> "attachment; filename=\"query_database.csv\""); - $template->process("pages/query_database.csv.tmpl", $vars) - || ThrowTemplateError($template->error()); - exit; - } + # add limit if missing + if ($query !~ /\sLIMIT\s+\d+\s*$/si) { + $query .= ' LIMIT 1000'; + $vars->{query} = $query; } + + # log query + _syslog(sprintf("[db_query] %s %s", $user->login, $query)); + + # connect to database and execute + # switching to the shadow db gives us a read-only connection + my $dbh = Bugzilla->switch_to_shadow_db(); + my $sth; + eval { + $sth = $dbh->prepare($query); + $sth->execute(); + }; + if ($@) { + $vars->{sql_error} = $@; + return; + } + + # build result + my $columns = $sth->{NAME}; + my $rows; + while (my @row = $sth->fetchrow_array) { + push @$rows, \@row; + } + + # return results + $vars->{columns} = $columns; + $vars->{rows} = $rows; + + if ($input->{csv}) { + print $cgi->header( + -type => 'text/csv', + -content_disposition => "attachment; filename=\"query_database.csv\"" + ); + $template->process("pages/query_database.csv.tmpl", $vars) + || ThrowTemplateError($template->error()); + exit; + } + } } # you can always file bugs into a product's default security group, as well as # into any of the groups in @always_fileable_groups sub _group_always_settable { - my ($self, $group) = @_; - return - $group->name eq $self->default_security_group - || ((grep { $_ eq $group->name } @always_fileable_groups) ? 1 : 0); + my ($self, $group) = @_; + return $group->name eq $self->default_security_group + || ((grep { $_ eq $group->name } @always_fileable_groups) ? 1 : 0); } sub _default_security_group { - return $_[0]->default_security_group_obj->name; + return $_[0]->default_security_group_obj->name; } sub _default_security_group_obj { - my $group_id = $_[0]->{security_group_id}; - if (!$group_id) { - return Bugzilla::Group->new({ name => Bugzilla->params->{insidergroup}, cache => 1 }); - } - return Bugzilla::Group->new({ id => $group_id, cache => 1 }); + my $group_id = $_[0]->{security_group_id}; + if (!$group_id) { + return Bugzilla::Group->new( + {name => Bugzilla->params->{insidergroup}, cache => 1}); + } + return Bugzilla::Group->new({id => $group_id, cache => 1}); } # called from the verify version, component, and group page. # if we're making a group invalid, stuff the default group into the cgi param # to make it checked by default. sub _check_default_product_security_group { - my ($self, $product, $invalid_groups, $optional_group_controls) = @_; - return unless my $group = $product->default_security_group_obj; - if (@$invalid_groups) { - my $cgi = Bugzilla->cgi; - my @groups = $cgi->param('groups'); - push @groups, $group->name unless grep { $_ eq $group->name } @groups; - $cgi->param('groups', @groups); - } + my ($self, $product, $invalid_groups, $optional_group_controls) = @_; + return unless my $group = $product->default_security_group_obj; + if (@$invalid_groups) { + my $cgi = Bugzilla->cgi; + my @groups = $cgi->param('groups'); + push @groups, $group->name unless grep { $_ eq $group->name } @groups; + $cgi->param('groups', @groups); + } } sub install_filesystem { - my ($self, $args) = @_; - my $files = $args->{files}; - my $create_files = $args->{create_files}; - my $extensions_dir = bz_locations()->{extensionsdir}; - $create_files->{__lbheartbeat__} = { - perms => Bugzilla::Install::Filesystem::WS_SERVE, - overwrite => 1, # the original value for this was wrong, overwrite it - contents => 'httpd OK', - }; - - - # version.json needs to have a source attribute pointing to - # our repository. We already have this information in the (static) - # contribute.json file, so parse that in - my $json = JSON::XS->new->pretty->utf8->canonical(); - my $contribute = eval { - $json->decode(scalar read_file(bz_locations()->{cgi_path} . "/contribute.json")); - }; - - if (!$contribute) { - die "Missing or invalid contribute.json file"; - } - - my $version_obj = { - source => $contribute->{repository}{url}, - version => BUGZILLA_VERSION, - commit => $ENV{CIRCLE_SHA1} // 'unknown', - build => $ENV{CIRCLE_BUILD_URL} // 'unknown', - }; - - $create_files->{'version.json'} = { - overwrite => 1, - perms => Bugzilla::Install::Filesystem::WS_SERVE, - contents => $json->encode($version_obj), - }; - - $files->{"$extensions_dir/BMO/bin/migrate-github-pull-requests.pl"} = { - perms => Bugzilla::Install::Filesystem::OWNER_EXECUTE - }; + my ($self, $args) = @_; + my $files = $args->{files}; + my $create_files = $args->{create_files}; + my $extensions_dir = bz_locations()->{extensionsdir}; + $create_files->{__lbheartbeat__} = { + perms => Bugzilla::Install::Filesystem::WS_SERVE, + overwrite => 1, # the original value for this was wrong, overwrite it + contents => 'httpd OK', + }; + + + # version.json needs to have a source attribute pointing to + # our repository. We already have this information in the (static) + # contribute.json file, so parse that in + my $json = JSON::XS->new->pretty->utf8->canonical(); + my $contribute = eval { + $json->decode( + scalar read_file(bz_locations()->{cgi_path} . "/contribute.json")); + }; + + if (!$contribute) { + die "Missing or invalid contribute.json file"; + } + + my $version_obj = { + source => $contribute->{repository}{url}, + version => BUGZILLA_VERSION, + commit => $ENV{CIRCLE_SHA1} // 'unknown', + build => $ENV{CIRCLE_BUILD_URL} // 'unknown', + }; + + $create_files->{'version.json'} = { + overwrite => 1, + perms => Bugzilla::Install::Filesystem::WS_SERVE, + contents => $json->encode($version_obj), + }; + + $files->{"$extensions_dir/BMO/bin/migrate-github-pull-requests.pl"} + = {perms => Bugzilla::Install::Filesystem::OWNER_EXECUTE}; } # "deleted" comment tag sub config_modify_panels { - my ($self, $args) = @_; - push @{ $args->{panels}->{groupsecurity}->{params} }, { - name => 'delete_comments_group', - type => 's', - choices => \&get_all_group_names, - default => 'admin', - checker => \&check_group + my ($self, $args) = @_; + push @{$args->{panels}->{groupsecurity}->{params}}, + { + name => 'delete_comments_group', + type => 's', + choices => \&get_all_group_names, + default => 'admin', + checker => \&check_group }; } sub comment_after_add_tag { - my ($self, $args) = @_; - my $tag = $args->{tag}; - return unless lc($tag) eq 'deleted'; - - my $group_name = Bugzilla->params->{delete_comments_group}; - if (!$group_name || !Bugzilla->user->in_group($group_name)) { - ThrowUserError('auth_failure', { group => $group_name, - action => 'delete', - object => 'comments' }); - } + my ($self, $args) = @_; + my $tag = $args->{tag}; + return unless lc($tag) eq 'deleted'; + + my $group_name = Bugzilla->params->{delete_comments_group}; + if (!$group_name || !Bugzilla->user->in_group($group_name)) { + ThrowUserError('auth_failure', + {group => $group_name, action => 'delete', object => 'comments'}); + } } sub comment_after_remove_tag { - my ($self, $args) = @_; - my $tag = $args->{tag}; - return unless lc($tag) eq 'deleted'; - - my $group_name = Bugzilla->params->{delete_comments_group}; - if (!$group_name || !Bugzilla->user->in_group($group_name)) { - ThrowUserError('auth_failure', { group => $group_name, - action => 'delete', - object => 'comments' }); - } + my ($self, $args) = @_; + my $tag = $args->{tag}; + return unless lc($tag) eq 'deleted'; + + my $group_name = Bugzilla->params->{delete_comments_group}; + if (!$group_name || !Bugzilla->user->in_group($group_name)) { + ThrowUserError('auth_failure', + {group => $group_name, action => 'delete', object => 'comments'}); + } } BEGIN { - *Bugzilla::Comment::has_tag = \&_comment_has_tag; + *Bugzilla::Comment::has_tag = \&_comment_has_tag; } sub _comment_has_tag { - my ($self, $test_tag) = @_; - $test_tag = lc($test_tag); - foreach my $tag (@{ $self->tags }) { - return 1 if lc($tag) eq $test_tag; - } - return 0; + my ($self, $test_tag) = @_; + $test_tag = lc($test_tag); + foreach my $tag (@{$self->tags}) { + return 1 if lc($tag) eq $test_tag; + } + return 0; } sub bug_comments { - my ($self, $args) = @_; - my $can_delete = Bugzilla->user->in_group(Bugzilla->params->{delete_comments_group}); - my $comments = $args->{comments}; - my @deleted = grep { $_->has_tag('deleted') } @$comments; - while (my $comment = pop @deleted) { - for (my $i = scalar(@$comments) - 1; $i >= 0; $i--) { - if ($comment == $comments->[$i]) { - if ($can_delete) { - # don't remove comment from users who can "delete" them - # just collapse it instead - $comment->{collapsed} = 1; - } - else { - # otherwise, remove it from the array - splice(@$comments, $i, 1); - } - last; - } + my ($self, $args) = @_; + my $can_delete + = Bugzilla->user->in_group(Bugzilla->params->{delete_comments_group}); + my $comments = $args->{comments}; + my @deleted = grep { $_->has_tag('deleted') } @$comments; + while (my $comment = pop @deleted) { + for (my $i = scalar(@$comments) - 1; $i >= 0; $i--) { + if ($comment == $comments->[$i]) { + if ($can_delete) { + + # don't remove comment from users who can "delete" them + # just collapse it instead + $comment->{collapsed} = 1; } + else { + # otherwise, remove it from the array + splice(@$comments, $i, 1); + } + last; + } } + } } sub _split_crash_signature { - my ($self, $vars) = @_; - my $bug = $vars->{bug} // return; - my $crash_signature = $bug->cf_crash_signature // return; - return [ - grep { /\S/ } - extract_multiple($crash_signature, [ sub { extract_bracketed($_[0], '[]') } ]) - ]; + my ($self, $vars) = @_; + my $bug = $vars->{bug} // return; + my $crash_signature = $bug->cf_crash_signature // return; + return [grep {/\S/} + extract_multiple($crash_signature, [sub { extract_bracketed($_[0], '[]') }])]; } sub enter_bug_entrydefaultvars { - my ($self, $args) = @_; - my $vars = $args->{vars}; - my $cgi = Bugzilla->cgi; - return unless my $format = $cgi->param('format'); - - if ($format eq 'fxos-feature') { - $vars->{feature_type} = $cgi->param('feature_type'); - $vars->{description} = $cgi->param('description'); - $vars->{discussion} = $cgi->param('discussion'); - } + my ($self, $args) = @_; + my $vars = $args->{vars}; + my $cgi = Bugzilla->cgi; + return unless my $format = $cgi->param('format'); + + if ($format eq 'fxos-feature') { + $vars->{feature_type} = $cgi->param('feature_type'); + $vars->{description} = $cgi->param('description'); + $vars->{discussion} = $cgi->param('discussion'); + } } sub app_startup { - my ($self, $args) = @_; - my $app = $args->{app}; - my $r = $app->routes; - - $r->get( - '/favicon.ico' => sub { - my $c = shift; - $c->reply->file( - $c->app->home->child('extensions/BMO/web/images/favicon.ico') - ); - } + my ($self, $args) = @_; + my $app = $args->{app}; + my $r = $app->routes; + + $r->get( + '/favicon.ico' => sub { + my $c = shift; + $c->reply->file($c->app->home->child('extensions/BMO/web/images/favicon.ico')); + } + ); + + $r->any('/:REWRITE_itrequest' => [REWRITE_itrequest => qr{form[\.:]itrequest}]) + ->to('CGI#enter_bug_cgi' => + {'product' => 'Infrastructure & Operations', 'format' => 'itrequest'}); + $r->any('/:REWRITE_mozlist' => [REWRITE_mozlist => qr{form[\.:]mozlist}]) + ->to( + 'CGI#enter_bug_cgi' => {'product' => 'mozilla.org', 'format' => 'mozlist'}); + $r->any('/:REWRITE_poweredby' => [REWRITE_poweredby => qr{form[\.:]poweredby}]) + ->to( + 'CGI#enter_bug_cgi' => {'product' => 'mozilla.org', 'format' => 'poweredby'}); + $r->any( + '/:REWRITE_presentation' => [REWRITE_presentation => qr{form[\.:]presentation}]) + ->to( + 'cgi#enter_bug_cgi' => {'product' => 'mozilla.org', 'format' => 'presentation'} ); - - $r->any( '/:REWRITE_itrequest' => [ REWRITE_itrequest => qr{form[\.:]itrequest} ] ) - ->to( 'CGI#enter_bug_cgi' => { 'product' => 'Infrastructure & Operations', 'format' => 'itrequest' } ); - $r->any( '/:REWRITE_mozlist' => [ REWRITE_mozlist => qr{form[\.:]mozlist} ] ) - ->to( 'CGI#enter_bug_cgi' => { 'product' => 'mozilla.org', 'format' => 'mozlist' } ); - $r->any( '/:REWRITE_poweredby' => [ REWRITE_poweredby => qr{form[\.:]poweredby} ] ) - ->to( 'CGI#enter_bug_cgi' => { 'product' => 'mozilla.org', 'format' => 'poweredby' } ); - $r->any( '/:REWRITE_presentation' => [ REWRITE_presentation => qr{form[\.:]presentation} ] ) - ->to( 'cgi#enter_bug_cgi' => { 'product' => 'mozilla.org', 'format' => 'presentation' } ); - $r->any( '/:REWRITE_trademark' => [ REWRITE_trademark => qr{form[\.:]trademark} ] ) - ->to( 'cgi#enter_bug_cgi' => { 'product' => 'mozilla.org', 'format' => 'trademark' } ); - $r->any( '/:REWRITE_recoverykey' => [ REWRITE_recoverykey => qr{form[\.:]recoverykey} ] ) - ->to( 'cgi#enter_bug_cgi' => { 'product' => 'mozilla.org', 'format' => 'recoverykey' } ); - $r->any( '/:REWRITE_legal' => [ REWRITE_legal => qr{form[\.:]legal} ] ) - ->to( 'CGI#enter_bug_cgi' => { 'product' => 'Legal', 'format' => 'legal' }, ); - $r->any( '/:REWRITE_recruiting' => [ REWRITE_recruiting => qr{form[\.:]recruiting} ] ) - ->to( 'CGI#enter_bug_cgi' => { 'product' => 'Recruiting', 'format' => 'recruiting' } ); - $r->any( '/:REWRITE_intern' => [ REWRITE_intern => qr{form[\.:]intern} ] ) - ->to( 'CGI#enter_bug_cgi' => { 'product' => 'Recruiting', 'format' => 'intern' } ); - $r->any( '/:REWRITE_mozpr' => [ REWRITE_mozpr => qr{form[\.:]mozpr} ] ) - ->to( 'CGI#enter_bug_cgi' => { 'product' => 'Mozilla PR', 'format' => 'mozpr' }, ); - $r->any( '/:REWRITE_reps_mentorship' => [ REWRITE_reps_mentorship => qr{form[\.:]reps[\.:]mentorship} ] ) - ->to( 'CGI#enter_bug_cgi' => { 'product' => 'Mozilla Reps', 'format' => 'mozreps' }, ); - $r->any( '/:REWRITE_reps_budget' => [ REWRITE_reps_budget => qr{form[\.:]reps[\.:]budget} ] ) - ->to( 'CGI#enter_bug_cgi' => { 'product' => 'Mozilla Reps', 'format' => 'remo-budget' } ); - $r->any( '/:REWRITE_reps_swag' => [ REWRITE_reps_swag => qr{form[\.:]reps[\.:]swag} ] ) - ->to( 'CGI#enter_bug_cgi' => { 'product' => 'Mozilla Reps', 'format' => 'remo-swag' } ); - $r->any( '/:REWRITE_reps_it' => [ REWRITE_reps_it => qr{form[\.:]reps[\.:]it} ] ) - ->to( 'CGI#enter_bug_cgi' => { 'product' => 'Mozilla Reps', 'format' => 'remo-it' } ); - $r->any( '/:REWRITE_reps_payment' => [ REWRITE_reps_payment => qr{form[\.:]reps[\.:]payment} ] ) - ->to( 'CGI#page_cgi' => { 'id' => 'remo-form-payment.html' } ); - $r->any( '/:REWRITE_csa_discourse' => [ REWRITE_csa_discourse => qr{form[\.:]csa[\.:]discourse} ] ) - ->to( 'CGI#enter_bug_cgi' => { 'product' => 'Infrastructure & Operations', 'format' => 'csa-discourse' } ); - $r->any( '/:REWRITE_employee_incident' => [ REWRITE_employee_incident => qr{form[\.:]employee[\.\-:]incident} ] ) - ->to( 'CGI#enter_bug_cgi' => { 'product' => 'mozilla.org', 'format' => 'employee-incident' } ); - $r->any( '/:REWRITE_brownbag' => [ REWRITE_brownbag => qr{form[\.:]brownbag} ] ) - ->to( 'CGI#https_air_mozilla_org_requests' => {} ); - $r->any( '/:REWRITE_finance' => [ REWRITE_finance => qr{form[\.:]finance} ] ) - ->to( 'CGI#enter_bug_cgi' => { 'product' => 'Finance', 'format' => 'finance' } ); - $r->any( - '/:REWRITE_moz_project_review' => [ REWRITE_moz_project_review => qr{form[\.:]moz[\.\-:]project[\.\-:]review} ] - )->to( 'CGI#enter_bug_cgi' => { 'product' => 'mozilla.org', 'format' => 'moz-project-review' } ); - $r->any( '/:REWRITE_docs' => [ REWRITE_docs => qr{form[\.:]docs?} ] ) - ->to( 'CGI#enter_bug_cgi' => { 'product' => 'Developer Documentation', 'format' => 'doc' } ); - $r->any( '/:REWRITE_mdn' => [ REWRITE_mdn => qr{form[\.:]mdn?} ] ) - ->to( 'CGI#enter_bug_cgi' => { 'format' => 'mdn', 'product' => 'developer.mozilla.org' } ); - $r->any( '/:REWRITE_swag_gear' => [ REWRITE_swag_gear => qr{form[\.:](swag|gear)} ] ) - ->to( 'CGI#enter_bug_cgi' => { 'format' => 'swag', 'product' => 'Marketing' } ); - $r->any( '/:REWRITE_costume' => [ REWRITE_costume => qr{form[\.:]costume} ] ) - ->to( 'CGI#enter_bug_cgi' => { 'product' => 'Marketing', 'format' => 'costume' } ); - $r->any( '/:REWRITE_ipp' => [ REWRITE_ipp => qr{form[\.:]ipp} ] ) - ->to( 'CGI#enter_bug_cgi' => { 'product' => 'Internet Public Policy', 'format' => 'ipp' } ); - $r->any( '/:REWRITE_creative' => [ REWRITE_creative => qr{form[\.:]creative} ] ) - ->to( 'CGI#enter_bug_cgi' => { 'format' => 'creative', 'product' => 'Marketing' } ); - $r->any( '/:REWRITE_user_engagement' => [ REWRITE_user_engagement => qr{form[\.:]user[\.\-:]engagement} ] ) - ->to( 'CGI#enter_bug_cgi' => { 'format' => 'user-engagement', 'product' => 'Marketing' } ); - $r->any( '/:REWRITE_dev_engagement_event' => - [ REWRITE_dev_engagement_event => qr{form[\.:]dev[\.\-:]engagement[\.\-\:]event} ] ) - ->to( 'CGI#enter_bug_cgi' => { 'product' => 'Developer Engagement', 'format' => 'dev-engagement-event' } ); - $r->any( '/:REWRITE_mobile_compat' => [ REWRITE_mobile_compat => qr{form[\.:]mobile[\.\-:]compat} ] ) - ->to( 'CGI#enter_bug_cgi' => { 'product' => 'Tech Evangelism', 'format' => 'mobile-compat' } ); - $r->any( '/:REWRITE_web_bounty' => [ REWRITE_web_bounty => qr{form[\.:]web[\.:]bounty} ] ) - ->to( 'CGI#enter_bug_cgi' => { 'format' => 'web-bounty', 'product' => 'mozilla.org' } ); - $r->any( '/:REWRITE_automative' => [ REWRITE_automative => qr{form[\.:]automative} ] ) - ->to( 'CGI#enter_bug_cgi' => { 'product' => 'Testing', 'format' => 'automative' } ); - $r->any( '/:REWRITE_comm_newsletter' => [ REWRITE_comm_newsletter => qr{form[\.:]comm[\.:]newsletter} ] ) - ->to( 'CGI#enter_bug_cgi' => { 'format' => 'comm-newsletter', 'product' => 'Marketing' } ); - $r->any( '/:REWRITE_screen_share_whitelist' => - [ REWRITE_screen_share_whitelist => qr{form[\.:]screen[\.:]share[\.:]whitelist} ] ) - ->to( 'CGI#enter_bug_cgi' => { 'format' => 'screen-share-whitelist', 'product' => 'Firefox' } ); - $r->any( '/:REWRITE_data_compliance' => [ REWRITE_data_compliance => qr{form[\.:]data[\.\-:]compliance} ] ) - ->to( 'CGI#enter_bug_cgi' => { 'product' => 'Data Compliance', 'format' => 'data-compliance' } ); - $r->any( '/:REWRITE_fsa_budget' => [ REWRITE_fsa_budget => qr{form[\.:]fsa[\.:]budget} ] ) - ->to( 'CGI#enter_bug_cgi' => { 'product' => 'FSA', 'format' => 'fsa-budget' } ); - $r->any( '/:REWRITE_triage_request' => [ REWRITE_triage_request => qr{form[\.:]triage[\.\-]request} ] ) - ->to( 'CGI#page_cgi' => { 'id' => 'triage_request.html' } ); - $r->any( '/:REWRITE_crm_CRM' => [ REWRITE_crm_CRM => qr{form[\.:](crm|CRM)} ] ) - ->to( 'CGI#enter_bug_cgi' => { 'format' => 'crm', 'product' => 'Marketing' } ); - $r->any( '/:REWRITE_nda' => [ REWRITE_nda => qr{form[\.:]nda} ] ) - ->to( 'CGI#enter_bug_cgi' => { 'product' => 'Legal', 'format' => 'nda' } ); - $r->any( '/:REWRITE_name_clearance' => [ REWRITE_name_clearance => qr{form[\.:]name[\.:]clearance} ] ) - ->to( 'CGI#enter_bug_cgi' => { 'format' => 'name-clearance', 'product' => 'Legal' } ); - $r->any( '/:REWRITE_shield_studies' => [ REWRITE_shield_studies => qr{form[\.:]shield[\.:]studies} ] ) - ->to( 'CGI#enter_bug_cgi' => { 'product' => 'Shield', 'format' => 'shield-studies' } ); - $r->any( '/:REWRITE_client_bounty' => [ REWRITE_client_bounty => qr{form[\.:]client[\.:]bounty} ] ) - ->to( 'CGI#enter_bug_cgi' => { 'product' => 'Firefox', 'format' => 'client-bounty' } ); + $r->any('/:REWRITE_trademark' => [REWRITE_trademark => qr{form[\.:]trademark}]) + ->to( + 'cgi#enter_bug_cgi' => {'product' => 'mozilla.org', 'format' => 'trademark'}); + $r->any( + '/:REWRITE_recoverykey' => [REWRITE_recoverykey => qr{form[\.:]recoverykey}]) + ->to( + 'cgi#enter_bug_cgi' => {'product' => 'mozilla.org', 'format' => 'recoverykey'}); + $r->any('/:REWRITE_legal' => [REWRITE_legal => qr{form[\.:]legal}]) + ->to('CGI#enter_bug_cgi' => {'product' => 'Legal', 'format' => 'legal'},); + $r->any( + '/:REWRITE_recruiting' => [REWRITE_recruiting => qr{form[\.:]recruiting}]) + ->to( + 'CGI#enter_bug_cgi' => {'product' => 'Recruiting', 'format' => 'recruiting'}); + $r->any('/:REWRITE_intern' => [REWRITE_intern => qr{form[\.:]intern}]) + ->to( + 'CGI#enter_bug_cgi' => {'product' => 'Recruiting', 'format' => 'intern'}); + $r->any('/:REWRITE_mozpr' => [REWRITE_mozpr => qr{form[\.:]mozpr}]) + ->to('CGI#enter_bug_cgi' => {'product' => 'Mozilla PR', 'format' => 'mozpr'}, + ); + $r->any('/:REWRITE_reps_mentorship' => + [REWRITE_reps_mentorship => qr{form[\.:]reps[\.:]mentorship}]) + ->to( + 'CGI#enter_bug_cgi' => {'product' => 'Mozilla Reps', 'format' => 'mozreps'},); + $r->any('/:REWRITE_reps_budget' => + [REWRITE_reps_budget => qr{form[\.:]reps[\.:]budget}]) + ->to( + 'CGI#enter_bug_cgi' => {'product' => 'Mozilla Reps', 'format' => 'remo-budget'} + ); + $r->any( + '/:REWRITE_reps_swag' => [REWRITE_reps_swag => qr{form[\.:]reps[\.:]swag}]) + ->to( + 'CGI#enter_bug_cgi' => {'product' => 'Mozilla Reps', 'format' => 'remo-swag'}); + $r->any('/:REWRITE_reps_it' => [REWRITE_reps_it => qr{form[\.:]reps[\.:]it}]) + ->to( + 'CGI#enter_bug_cgi' => {'product' => 'Mozilla Reps', 'format' => 'remo-it'}); + $r->any('/:REWRITE_reps_payment' => + [REWRITE_reps_payment => qr{form[\.:]reps[\.:]payment}]) + ->to('CGI#page_cgi' => {'id' => 'remo-form-payment.html'}); + $r->any('/:REWRITE_csa_discourse' => + [REWRITE_csa_discourse => qr{form[\.:]csa[\.:]discourse}]) + ->to('CGI#enter_bug_cgi' => + {'product' => 'Infrastructure & Operations', 'format' => 'csa-discourse'}); + $r->any('/:REWRITE_employee_incident' => + [REWRITE_employee_incident => qr{form[\.:]employee[\.\-:]incident}]) + ->to('CGI#enter_bug_cgi' => + {'product' => 'mozilla.org', 'format' => 'employee-incident'}); + $r->any('/:REWRITE_brownbag' => [REWRITE_brownbag => qr{form[\.:]brownbag}]) + ->to('CGI#https_air_mozilla_org_requests' => {}); + $r->any('/:REWRITE_finance' => [REWRITE_finance => qr{form[\.:]finance}]) + ->to('CGI#enter_bug_cgi' => {'product' => 'Finance', 'format' => 'finance'}); + $r->any('/:REWRITE_moz_project_review' => + [REWRITE_moz_project_review => qr{form[\.:]moz[\.\-:]project[\.\-:]review}]) + ->to('CGI#enter_bug_cgi' => + {'product' => 'mozilla.org', 'format' => 'moz-project-review'}); + $r->any('/:REWRITE_docs' => [REWRITE_docs => qr{form[\.:]docs?}]) + ->to('CGI#enter_bug_cgi' => + {'product' => 'Developer Documentation', 'format' => 'doc'}); + $r->any('/:REWRITE_mdn' => [REWRITE_mdn => qr{form[\.:]mdn?}]) + ->to('CGI#enter_bug_cgi' => + {'format' => 'mdn', 'product' => 'developer.mozilla.org'}); + $r->any( + '/:REWRITE_swag_gear' => [REWRITE_swag_gear => qr{form[\.:](swag|gear)}]) + ->to('CGI#enter_bug_cgi' => {'format' => 'swag', 'product' => 'Marketing'}); + $r->any('/:REWRITE_costume' => [REWRITE_costume => qr{form[\.:]costume}]) + ->to( + 'CGI#enter_bug_cgi' => {'product' => 'Marketing', 'format' => 'costume'}); + $r->any('/:REWRITE_ipp' => [REWRITE_ipp => qr{form[\.:]ipp}]) + ->to('CGI#enter_bug_cgi' => + {'product' => 'Internet Public Policy', 'format' => 'ipp'}); + $r->any('/:REWRITE_creative' => [REWRITE_creative => qr{form[\.:]creative}]) + ->to( + 'CGI#enter_bug_cgi' => {'format' => 'creative', 'product' => 'Marketing'}); + $r->any('/:REWRITE_user_engagement' => + [REWRITE_user_engagement => qr{form[\.:]user[\.\-:]engagement}]) + ->to('CGI#enter_bug_cgi' => + {'format' => 'user-engagement', 'product' => 'Marketing'}); + $r->any( + '/:REWRITE_dev_engagement_event' => [ + REWRITE_dev_engagement_event => qr{form[\.:]dev[\.\-:]engagement[\.\-\:]event} + ] + ) + ->to('CGI#enter_bug_cgi' => + {'product' => 'Developer Engagement', 'format' => 'dev-engagement-event'}); + $r->any('/:REWRITE_mobile_compat' => + [REWRITE_mobile_compat => qr{form[\.:]mobile[\.\-:]compat}]) + ->to('CGI#enter_bug_cgi' => + {'product' => 'Tech Evangelism', 'format' => 'mobile-compat'}); + $r->any( + '/:REWRITE_web_bounty' => [REWRITE_web_bounty => qr{form[\.:]web[\.:]bounty}]) + ->to( + 'CGI#enter_bug_cgi' => {'format' => 'web-bounty', 'product' => 'mozilla.org'}); + $r->any( + '/:REWRITE_automative' => [REWRITE_automative => qr{form[\.:]automative}]) + ->to( + 'CGI#enter_bug_cgi' => {'product' => 'Testing', 'format' => 'automative'}); + $r->any('/:REWRITE_comm_newsletter' => + [REWRITE_comm_newsletter => qr{form[\.:]comm[\.:]newsletter}]) + ->to('CGI#enter_bug_cgi' => + {'format' => 'comm-newsletter', 'product' => 'Marketing'}); + $r->any( + '/:REWRITE_screen_share_whitelist' => [ + REWRITE_screen_share_whitelist => qr{form[\.:]screen[\.:]share[\.:]whitelist} + ] + ) + ->to('CGI#enter_bug_cgi' => + {'format' => 'screen-share-whitelist', 'product' => 'Firefox'}); + $r->any('/:REWRITE_data_compliance' => + [REWRITE_data_compliance => qr{form[\.:]data[\.\-:]compliance}]) + ->to('CGI#enter_bug_cgi' => + {'product' => 'Data Compliance', 'format' => 'data-compliance'}); + $r->any( + '/:REWRITE_fsa_budget' => [REWRITE_fsa_budget => qr{form[\.:]fsa[\.:]budget}]) + ->to('CGI#enter_bug_cgi' => {'product' => 'FSA', 'format' => 'fsa-budget'}); + $r->any('/:REWRITE_triage_request' => + [REWRITE_triage_request => qr{form[\.:]triage[\.\-]request}]) + ->to('CGI#page_cgi' => {'id' => 'triage_request.html'}); + $r->any('/:REWRITE_crm_CRM' => [REWRITE_crm_CRM => qr{form[\.:](crm|CRM)}]) + ->to('CGI#enter_bug_cgi' => {'format' => 'crm', 'product' => 'Marketing'}); + $r->any('/:REWRITE_nda' => [REWRITE_nda => qr{form[\.:]nda}]) + ->to('CGI#enter_bug_cgi' => {'product' => 'Legal', 'format' => 'nda'}); + $r->any('/:REWRITE_name_clearance' => + [REWRITE_name_clearance => qr{form[\.:]name[\.:]clearance}]) + ->to( + 'CGI#enter_bug_cgi' => {'format' => 'name-clearance', 'product' => 'Legal'}); + $r->any('/:REWRITE_shield_studies' => + [REWRITE_shield_studies => qr{form[\.:]shield[\.:]studies}]) + ->to( + 'CGI#enter_bug_cgi' => {'product' => 'Shield', 'format' => 'shield-studies'}); + $r->any('/:REWRITE_client_bounty' => + [REWRITE_client_bounty => qr{form[\.:]client[\.:]bounty}]) + ->to( + 'CGI#enter_bug_cgi' => {'product' => 'Firefox', 'format' => 'client-bounty'}); } __PACKAGE__->NAME; diff --git a/extensions/BMO/bin/bug_1022707.pl b/extensions/BMO/bin/bug_1022707.pl index 4d48db01d..31afa7d05 100755 --- a/extensions/BMO/bin/bug_1022707.pl +++ b/extensions/BMO/bin/bug_1022707.pl @@ -37,8 +37,10 @@ print "About to fix $total flags\n"; print "Press <enter> to start, or ^C to cancel...\n"; readline; -my $update_fsa_sql= "UPDATE flag_state_activity SET type_id = 4 WHERE " . $dbh->sql_in('flag_id', $flag_ids); -my $update_flags_sql = "UPDATE flags SET type_id = 4 WHERE " . $dbh->sql_in('id', $flag_ids); +my $update_fsa_sql = "UPDATE flag_state_activity SET type_id = 4 WHERE " + . $dbh->sql_in('flag_id', $flag_ids); +my $update_flags_sql + = "UPDATE flags SET type_id = 4 WHERE " . $dbh->sql_in('id', $flag_ids); $dbh->bz_start_transaction(); $dbh->do($update_fsa_sql); diff --git a/extensions/BMO/bin/bug_1093952.pl b/extensions/BMO/bin/bug_1093952.pl index fd891f4ae..f52427284 100755 --- a/extensions/BMO/bin/bug_1093952.pl +++ b/extensions/BMO/bin/bug_1093952.pl @@ -23,14 +23,17 @@ Bugzilla->usage_mode(USAGE_MODE_CMDLINE); my $dbh = Bugzilla->dbh; -my $infra = Bugzilla::Product->check({ name => 'Infrastructure & Operations' }); -my $relops_id = Bugzilla::Component->check({ product => $infra, name => 'RelOps' })->id; -my $puppet_id = Bugzilla::Component->check({ product => $infra, name => 'RelOps: Puppet' })->id; -my $infra_id = $infra->id; -my $components = $dbh->sql_in('component_id', [ $relops_id, $puppet_id ]); +my $infra = Bugzilla::Product->check({name => 'Infrastructure & Operations'}); +my $relops_id + = Bugzilla::Component->check({product => $infra, name => 'RelOps'})->id; +my $puppet_id + = Bugzilla::Component->check({product => $infra, name => 'RelOps: Puppet'}) + ->id; +my $infra_id = $infra->id; +my $components = $dbh->sql_in('component_id', [$relops_id, $puppet_id]); print "Searching for bugs..\n"; -my $bugs = $dbh->selectall_arrayref(<<EOF, { Slice => {} }); +my $bugs = $dbh->selectall_arrayref(<<EOF, {Slice => {}}); SELECT bug_id, product_id, @@ -52,9 +55,9 @@ printf "About to fix %s bugs\n", scalar(@$bugs); print "Press <Ctrl-C> to stop or <Enter> to continue...\n"; getc(); -my $nobody = Bugzilla::User->check({ name => Bugzilla->params->{'nobody_user'} }); -my $field = Bugzilla::Field->check({ name => 'status_whiteboard' }); -my $when = $dbh->selectrow_array('SELECT LOCALTIMESTAMP(0)'); +my $nobody = Bugzilla::User->check({name => Bugzilla->params->{'nobody_user'}}); +my $field = Bugzilla::Field->check({name => 'status_whiteboard'}); +my $when = $dbh->selectrow_array('SELECT LOCALTIMESTAMP(0)'); my $sth_bugs = $dbh->prepare(" UPDATE bugs @@ -70,22 +73,25 @@ my $sth_activity = $dbh->prepare(" $dbh->bz_start_transaction(); foreach my $bug (@$bugs) { - my $bug_id = $bug->{bug_id}; - my $whiteboard = $bug->{status_whiteboard}; - print "bug $bug_id\n $whiteboard\n"; + my $bug_id = $bug->{bug_id}; + my $whiteboard = $bug->{status_whiteboard}; + print "bug $bug_id\n $whiteboard\n"; - my $updated = $whiteboard; - $updated =~ s#\[kanban:engops:https://kanbanize\.com/ctrl_board/6/[^\]]*\]\s*##g; - if ($bug->{product_id} == $infra->id - && $bug->{component_id} != $relops_id - && $bug->{component_id} != $puppet_id - ) { - $updated =~ s#\[kanban:engops:https://mozilla\.kanbanize\.com/ctrl_board/6/[^\]]*\]\s*##g; - } - print " $updated\n"; + my $updated = $whiteboard; + $updated + =~ s#\[kanban:engops:https://kanbanize\.com/ctrl_board/6/[^\]]*\]\s*##g; + if ( $bug->{product_id} == $infra->id + && $bug->{component_id} != $relops_id + && $bug->{component_id} != $puppet_id) + { + $updated + =~ s#\[kanban:engops:https://mozilla\.kanbanize\.com/ctrl_board/6/[^\]]*\]\s*##g; + } + print " $updated\n"; - $sth_bugs->execute($updated, $when, $when, $bug_id); - $sth_activity->execute($bug_id, $nobody->id, $when, $field->id, $whiteboard, $updated); + $sth_bugs->execute($updated, $when, $when, $bug_id); + $sth_activity->execute($bug_id, $nobody->id, $when, $field->id, $whiteboard, + $updated); } $dbh->bz_commit_transaction(); diff --git a/extensions/BMO/bin/bug_1141452.pl b/extensions/BMO/bin/bug_1141452.pl index 155c4704c..56b63db91 100755 --- a/extensions/BMO/bin/bug_1141452.pl +++ b/extensions/BMO/bin/bug_1141452.pl @@ -13,8 +13,8 @@ use 5.10.1; use lib qw(. lib local/lib/perl5); BEGIN { - use Bugzilla; - Bugzilla->extensions; + use Bugzilla; + Bugzilla->extensions; } use Bugzilla::Constants qw( USAGE_MODE_CMDLINE ); @@ -24,14 +24,17 @@ use Bugzilla::User; Bugzilla->usage_mode(USAGE_MODE_CMDLINE); my $dbh = Bugzilla->dbh; -my $blocking_b2g = Bugzilla::Extension::TrackingFlags::Flag->check({ name => 'cf_blocking_b2g' }); -my $tracking_b2g = Bugzilla::Extension::TrackingFlags::Flag->check({ name => 'cf_tracking_b2g' }); +my $blocking_b2g = Bugzilla::Extension::TrackingFlags::Flag->check( + {name => 'cf_blocking_b2g'}); +my $tracking_b2g = Bugzilla::Extension::TrackingFlags::Flag->check( + {name => 'cf_tracking_b2g'}); die "tracking-b2g does not have a 'backlog' value\n" - unless grep { $_->value eq 'backlog' } @{ $tracking_b2g->values }; + unless grep { $_->value eq 'backlog' } @{$tracking_b2g->values}; print "Searching for bugs..\n"; -my $flags = $dbh->selectall_arrayref(<<EOF, { Slice => {} }, $blocking_b2g->flag_id, $tracking_b2g->flag_id); +my $flags = $dbh->selectall_arrayref( + <<EOF, {Slice => {}}, $blocking_b2g->flag_id, $tracking_b2g->flag_id); SELECT bugs.bug_id, blocking_b2g.id id, @@ -50,54 +53,59 @@ printf "About to fix %s bugs\n", scalar(@$flags); print "Press <Ctrl-C> to stop or <Enter> to continue...\n"; getc(); -my $nobody = Bugzilla::User->check({ name => Bugzilla->params->{'nobody_user'} }); -my $when = $dbh->selectrow_array('SELECT LOCALTIMESTAMP(0)'); +my $nobody = Bugzilla::User->check({name => Bugzilla->params->{'nobody_user'}}); +my $when = $dbh->selectrow_array('SELECT LOCALTIMESTAMP(0)'); $dbh->bz_start_transaction(); foreach my $flag (@$flags) { - if (!$flag->{value}) { - print $flag->{bug_id}, ": changing blocking_b2g:backlog -> tracking_b2g:backlog\n"; - # no tracking_b2g value, change blocking_b2g:backlog -> tracking_b2g:backlog - $dbh->do( - "UPDATE tracking_flags_bugs SET tracking_flag_id = ? WHERE id = ?", - undef, - $tracking_b2g->flag_id, $flag->{id}, - ); - $dbh->do( - "UPDATE bugs SET delta_ts = ?, lastdiffed = ? WHERE bug_id = ?", - undef, - $when, $when, $flag->{bug_id}, - ); - $dbh->do( - "INSERT INTO bugs_activity(bug_id, who, bug_when, fieldid, removed, added) VALUES (?, ?, ?, ?, ?, ?)", - undef, - $flag->{bug_id}, $nobody->id, $when, $blocking_b2g->id, 'backlog', '---', - ); - $dbh->do( - "INSERT INTO bugs_activity(bug_id, who, bug_when, fieldid, removed, added) VALUES (?, ?, ?, ?, ?, ?)", - undef, - $flag->{bug_id}, $nobody->id, $when, $tracking_b2g->id, '---', 'backlog', - ); - } - elsif ($flag->{value}) { - print $flag->{bug_id}, ": deleting blocking_b2g:backlog\n"; - # tracking_b2g already has a value, just delete blocking_b2g:backlog - $dbh->do( - "DELETE FROM tracking_flags_bugs WHERE id = ?", - undef, - $flag->{id}, - ); - $dbh->do( - "UPDATE bugs SET delta_ts = ?, lastdiffed = ? WHERE bug_id = ?", - undef, - $when, $when, $flag->{bug_id}, - ); - $dbh->do( - "INSERT INTO bugs_activity(bug_id, who, bug_when, fieldid, removed, added) VALUES (?, ?, ?, ?, ?, ?)", - undef, - $flag->{bug_id}, $nobody->id, $when, $blocking_b2g->id, 'backlog', '---', - ); - } + if (!$flag->{value}) { + print $flag->{bug_id}, + ": changing blocking_b2g:backlog -> tracking_b2g:backlog\n"; + + # no tracking_b2g value, change blocking_b2g:backlog -> tracking_b2g:backlog + $dbh->do("UPDATE tracking_flags_bugs SET tracking_flag_id = ? WHERE id = ?", + undef, $tracking_b2g->flag_id, $flag->{id},); + $dbh->do("UPDATE bugs SET delta_ts = ?, lastdiffed = ? WHERE bug_id = ?", + undef, $when, $when, $flag->{bug_id},); + $dbh->do( + "INSERT INTO bugs_activity(bug_id, who, bug_when, fieldid, removed, added) VALUES (?, ?, ?, ?, ?, ?)", + undef, + $flag->{bug_id}, + $nobody->id, + $when, + $blocking_b2g->id, + 'backlog', + '---', + ); + $dbh->do( + "INSERT INTO bugs_activity(bug_id, who, bug_when, fieldid, removed, added) VALUES (?, ?, ?, ?, ?, ?)", + undef, + $flag->{bug_id}, + $nobody->id, + $when, + $tracking_b2g->id, + '---', + 'backlog', + ); + } + elsif ($flag->{value}) { + print $flag->{bug_id}, ": deleting blocking_b2g:backlog\n"; + + # tracking_b2g already has a value, just delete blocking_b2g:backlog + $dbh->do("DELETE FROM tracking_flags_bugs WHERE id = ?", undef, $flag->{id},); + $dbh->do("UPDATE bugs SET delta_ts = ?, lastdiffed = ? WHERE bug_id = ?", + undef, $when, $when, $flag->{bug_id},); + $dbh->do( + "INSERT INTO bugs_activity(bug_id, who, bug_when, fieldid, removed, added) VALUES (?, ?, ?, ?, ?, ?)", + undef, + $flag->{bug_id}, + $nobody->id, + $when, + $blocking_b2g->id, + 'backlog', + '---', + ); + } } $dbh->bz_commit_transaction(); diff --git a/extensions/BMO/bin/migrate-github-pull-requests.pl b/extensions/BMO/bin/migrate-github-pull-requests.pl index c39778a4a..c8afdedfb 100755 --- a/extensions/BMO/bin/migrate-github-pull-requests.pl +++ b/extensions/BMO/bin/migrate-github-pull-requests.pl @@ -22,9 +22,9 @@ use Bugzilla::Install::Util qw(indicate_progress); use Bugzilla::User; use Bugzilla::Util qw(trim); -my $dbh = Bugzilla->dbh; -my $nobody = Bugzilla::User->check({ name => Bugzilla->params->{'nobody_user'} }); -my $field = Bugzilla::Field->check({ name => 'attachments.mimetype' }); +my $dbh = Bugzilla->dbh; +my $nobody = Bugzilla::User->check({name => Bugzilla->params->{'nobody_user'}}); +my $field = Bugzilla::Field->check({name => 'attachments.mimetype'}); # grab list of suitable attachments @@ -42,7 +42,7 @@ SELECT attachments.attach_id, AND LENGTH(thedata) <= 256 EOF print "Searching for suitable attachments..\n"; -my $attachments = $dbh->selectall_arrayref($sql, { Slice => {} }); +my $attachments = $dbh->selectall_arrayref($sql, {Slice => {}}); my ($current, $total, $updated) = (1, scalar(@$attachments), 0); die "No suitable attachments found\n" unless $total; @@ -52,39 +52,32 @@ print "Press <enter> to start, or ^C to cancel...\n"; <>; foreach my $attachment (@$attachments) { - indicate_progress({ current => $current++, total => $total, every => 25 }); - - # check payload - my $url = trim($attachment->{thedata}); - next if $url =~ /\s/; - next unless $url =~ m#^https://github\.com/[^/]+/[^/]+/pull/\d+\/?$#i; - - $dbh->bz_start_transaction; - - # set content-type - $dbh->do( - "UPDATE attachments SET mimetype = ? WHERE attach_id = ?", - undef, - 'text/x-github-pull-request', $attachment->{attach_id} - ); - - # insert into bugs_activity - my $timestamp = $dbh->selectrow_array('SELECT LOCALTIMESTAMP(0)'); - $dbh->do( - "INSERT INTO bugs_activity(bug_id, who, bug_when, fieldid, removed, added) - VALUES (?, ?, ?, ?, ?, ?)", - undef, - $attachment->{bug_id}, $nobody->id, $timestamp, $field->id, - $attachment->{mimetype}, 'text/x-github-pull-request' - ); - $dbh->do( - "UPDATE bugs SET delta_ts = ?, lastdiffed = ? WHERE bug_id = ?", - undef, - $timestamp, $timestamp, $attachment->{bug_id} - ); - - $dbh->bz_commit_transaction; - $updated++; + indicate_progress({current => $current++, total => $total, every => 25}); + + # check payload + my $url = trim($attachment->{thedata}); + next if $url =~ /\s/; + next unless $url =~ m#^https://github\.com/[^/]+/[^/]+/pull/\d+\/?$#i; + + $dbh->bz_start_transaction; + + # set content-type + $dbh->do("UPDATE attachments SET mimetype = ? WHERE attach_id = ?", + undef, 'text/x-github-pull-request', $attachment->{attach_id}); + + # insert into bugs_activity + my $timestamp = $dbh->selectrow_array('SELECT LOCALTIMESTAMP(0)'); + $dbh->do( + "INSERT INTO bugs_activity(bug_id, who, bug_when, fieldid, removed, added) + VALUES (?, ?, ?, ?, ?, ?)", undef, $attachment->{bug_id}, + $nobody->id, $timestamp, $field->id, $attachment->{mimetype}, + 'text/x-github-pull-request' + ); + $dbh->do("UPDATE bugs SET delta_ts = ?, lastdiffed = ? WHERE bug_id = ?", + undef, $timestamp, $timestamp, $attachment->{bug_id}); + + $dbh->bz_commit_transaction; + $updated++; } print "Attachments updated: $updated\n"; diff --git a/extensions/BMO/lib/Constants.pm b/extensions/BMO/lib/Constants.pm index 8227208c8..7ec92befb 100644 --- a/extensions/BMO/lib/Constants.pm +++ b/extensions/BMO/lib/Constants.pm @@ -13,8 +13,8 @@ use warnings; use base qw(Exporter); our @EXPORT = qw( - REQUEST_MAX_ATTACH_LINES - DEV_ENGAGE_DISCUSS_NEEDINFO + REQUEST_MAX_ATTACH_LINES + DEV_ENGAGE_DISCUSS_NEEDINFO ); # Maximum attachment size in lines that will be sent with a @@ -24,7 +24,7 @@ use constant REQUEST_MAX_ATTACH_LINES => 1000; # Requestees who need a needinfo flag set for the dev engagement # discussion bug use constant DEV_ENGAGE_DISCUSS_NEEDINFO => qw( - spersing@mozilla.com + spersing@mozilla.com ); 1; diff --git a/extensions/BMO/lib/Data.pm b/extensions/BMO/lib/Data.pm index 349f88093..a1e010346 100644 --- a/extensions/BMO/lib/Data.pm +++ b/extensions/BMO/lib/Data.pm @@ -15,13 +15,13 @@ use base qw(Exporter); use Tie::IxHash; our @EXPORT = qw( $cf_visible_in_products - %group_change_notification - $cf_setters - @always_fileable_groups - %group_auto_cc - %create_bug_formats - @default_named_queries - %autodetect_attach_urls ); + %group_change_notification + $cf_setters + @always_fileable_groups + %group_auto_cc + %create_bug_formats + @default_named_queries + %autodetect_attach_urls ); # Creating an attachment whose contents is a URL matching one of these regexes # will result in the user being redirected to that URL when viewing the @@ -43,35 +43,37 @@ my $mozreview_url_re = qr{ }ix; sub phabricator_url_re { - my $phab_uri = Bugzilla->params->{phabricator_base_uri} || 'https://example.com'; - return qr/^\Q${phab_uri}\ED\d+$/i; + my $phab_uri + = Bugzilla->params->{phabricator_base_uri} || 'https://example.com'; + return qr/^\Q${phab_uri}\ED\d+$/i; } our %autodetect_attach_urls = ( - github_pr => { - title => 'GitHub Pull Request', - regex => qr#^https://github\.com/[^/]+/[^/]+/pull/\d+/?$#i, - content_type => 'text/x-github-pull-request', - can_review => 1, - }, - reviewboard => { - title => 'MozReview', - regex => $mozreview_url_re, - content_type => 'text/x-review-board-request', - can_review => 0, - }, - Phabricator => { - title => 'Phabricator', - regex => \&phabricator_url_re, - content_type => 'text/x-phabricator-request', - can_review => 1, - }, - google_docs => { - title => 'Google Doc', - regex => qr#^https://docs\.google\.com/(?:document|spreadsheets|presentation)/d/#i, - content_type => 'text/x-google-doc', - can_review => 0, - }, + github_pr => { + title => 'GitHub Pull Request', + regex => qr#^https://github\.com/[^/]+/[^/]+/pull/\d+/?$#i, + content_type => 'text/x-github-pull-request', + can_review => 1, + }, + reviewboard => { + title => 'MozReview', + regex => $mozreview_url_re, + content_type => 'text/x-review-board-request', + can_review => 0, + }, + Phabricator => { + title => 'Phabricator', + regex => \&phabricator_url_re, + content_type => 'text/x-phabricator-request', + can_review => 1, + }, + google_docs => { + title => 'Google Doc', + regex => + qr#^https://docs\.google\.com/(?:document|spreadsheets|presentation)/d/#i, + content_type => 'text/x-google-doc', + can_review => 0, + }, ); # Which custom fields are visible in which products and components. @@ -83,209 +85,184 @@ our %autodetect_attach_urls = ( # # IxHash keeps them in insertion order, and so we get regexp priorities right. our $cf_visible_in_products; -tie(%$cf_visible_in_products, "Tie::IxHash", - qr/^cf_colo_site$/ => { - "mozilla.org" => [ - "Server Operations", - "Server Operations: DCOps", - "Server Operations: Projects", - "Server Operations: RelEng", - "Server Operations: Security", - ], - "Infrastructure & Operations" => [ - "RelOps", - "RelOps: Puppet", - "DCOps", - ], - }, - qr/^cf_office$/ => { - "mozilla.org" => ["Server Operations: Desktop Issues"], - }, - qr/^cf_crash_signature$/ => { - "Add-on SDK" => [], - "addons.mozilla.org" => [], - "Android Background Services" => [], - "B2GDroid" => [], - "Calendar" => [], - "Composer" => [], - "Core" => [], - "DevTools" => [], - "Directory" => [], - "External Software Affecting Firefox" => [], - "Firefox" => [], - "Firefox for Android" => [], - "GeckoView" => [], - "JSS" => [], - "MailNews Core" => [], - "Mozilla Labs" => [], - "Mozilla Localizations" => [], - "mozilla.org" => [], - "Cloud Services" => [], - "NSPR" => [], - "NSS" => [], - "Other Applications" => [], - "Penelope" => [], - "Release Engineering" => [], - "Rhino" => [], - "SeaMonkey" => [], - "Tamarin" => [], - "Tech Evangelism" => [], - "Testing" => [], - "Thunderbird" => [], - "Toolkit" => [], - "WebExtensions" => [], - }, - qr/^cf_due_date$/ => { - "bugzilla.mozilla.org" => [], - "Community Building" => [], - "Data & BI Services Team" => [], - "Data Compliance" => [], - "Developer Engagement" => [], - "Firefox" => ["Security: Review Requests"], - "Infrastructure & Operations" => [], - "Marketing" => [], - "mozilla.org" => ["Security Assurance: Review Request"], - "Mozilla Metrics" => [], - "Mozilla PR" => [], - "Mozilla Reps" => [], - }, - qr/^cf_locale$/ => { - "Mozilla Localizations" => ['Other'], - "www.mozilla.org" => [], - }, - qr/^cf_mozilla_project$/ => { - "Data & BI Services Team" => [], - }, - qr/^cf_machine_state$/ => { - "Release Engineering" => ["Buildduty"], - }, - qr/^cf_rank$/ => { - "Core" => [], - "Firefox for Android" => [], - "Firefox for iOS" => [], - "Firefox" => [], - "GeckoView" => [], - "Hello (Loop)" => [], - "Cloud Services" => [], - "Tech Evangelism" => [], - "Toolkit" => [], - }, - qr/^cf_has_regression_range$/ => { - "Core" => [], - "Firefox for Android" => [], - "Firefox for iOS" => [], - "Firefox" => [], - "GeckoView" => [], - "Toolkit" => [], - }, - qr/^cf_has_str$/ => { - "Core" => [], - "Firefox for Android" => [], - "Firefox for iOS" => [], - "Firefox" => [], - "GeckoView" => [], - "Toolkit" => [], - }, - qr/^cf_cab_review$/ => { - "Infrastructure & Operations Graveyard" => [], - "Infrastructure & Operations" => [], - "Data & BI Services Team" => [], - } +tie( + %$cf_visible_in_products, + "Tie::IxHash", + qr/^cf_colo_site$/ => { + "mozilla.org" => [ + "Server Operations", + "Server Operations: DCOps", + "Server Operations: Projects", + "Server Operations: RelEng", + "Server Operations: Security", + ], + "Infrastructure & Operations" => ["RelOps", "RelOps: Puppet", "DCOps",], + }, + qr/^cf_office$/ => {"mozilla.org" => ["Server Operations: Desktop Issues"],}, + qr/^cf_crash_signature$/ => { + "Add-on SDK" => [], + "addons.mozilla.org" => [], + "Android Background Services" => [], + "B2GDroid" => [], + "Calendar" => [], + "Composer" => [], + "Core" => [], + "DevTools" => [], + "Directory" => [], + "External Software Affecting Firefox" => [], + "Firefox" => [], + "Firefox for Android" => [], + "GeckoView" => [], + "JSS" => [], + "MailNews Core" => [], + "Mozilla Labs" => [], + "Mozilla Localizations" => [], + "mozilla.org" => [], + "Cloud Services" => [], + "NSPR" => [], + "NSS" => [], + "Other Applications" => [], + "Penelope" => [], + "Release Engineering" => [], + "Rhino" => [], + "SeaMonkey" => [], + "Tamarin" => [], + "Tech Evangelism" => [], + "Testing" => [], + "Thunderbird" => [], + "Toolkit" => [], + "WebExtensions" => [], + }, + qr/^cf_due_date$/ => { + "bugzilla.mozilla.org" => [], + "Community Building" => [], + "Data & BI Services Team" => [], + "Data Compliance" => [], + "Developer Engagement" => [], + "Firefox" => ["Security: Review Requests"], + "Infrastructure & Operations" => [], + "Marketing" => [], + "mozilla.org" => ["Security Assurance: Review Request"], + "Mozilla Metrics" => [], + "Mozilla PR" => [], + "Mozilla Reps" => [], + }, + qr/^cf_locale$/ => + {"Mozilla Localizations" => ['Other'], "www.mozilla.org" => [],}, + qr/^cf_mozilla_project$/ => {"Data & BI Services Team" => [],}, + qr/^cf_machine_state$/ => {"Release Engineering" => ["Buildduty"],}, + qr/^cf_rank$/ => { + "Core" => [], + "Firefox for Android" => [], + "Firefox for iOS" => [], + "Firefox" => [], + "GeckoView" => [], + "Hello (Loop)" => [], + "Cloud Services" => [], + "Tech Evangelism" => [], + "Toolkit" => [], + }, + qr/^cf_has_regression_range$/ => { + "Core" => [], + "Firefox for Android" => [], + "Firefox for iOS" => [], + "Firefox" => [], + "GeckoView" => [], + "Toolkit" => [], + }, + qr/^cf_has_str$/ => { + "Core" => [], + "Firefox for Android" => [], + "Firefox for iOS" => [], + "Firefox" => [], + "GeckoView" => [], + "Toolkit" => [], + }, + qr/^cf_cab_review$/ => { + "Infrastructure & Operations Graveyard" => [], + "Infrastructure & Operations" => [], + "Data & BI Services Team" => [], + } ); # Who to CC on particular bugmails when certain groups are added or removed. our %group_change_notification = ( - 'addons-security' => ['amo-editors@mozilla.org'], - 'b2g-core-security' => ['security@mozilla.org'], - 'bugzilla-security' => ['security@bugzilla.org'], - 'client-services-security' => ['amo-admins@mozilla.org', 'web-security@mozilla.org'], - 'cloud-services-security' => ['web-security@mozilla.org'], - 'core-security' => ['security@mozilla.org'], - 'crypto-core-security' => ['security@mozilla.org'], - 'dom-core-security' => ['security@mozilla.org'], - 'firefox-core-security' => ['security@mozilla.org'], - 'gfx-core-security' => ['security@mozilla.org'], - 'javascript-core-security' => ['security@mozilla.org'], - 'layout-core-security' => ['security@mozilla.org'], - 'mail-core-security' => ['security@mozilla.org'], - 'media-core-security' => ['security@mozilla.org'], - 'network-core-security' => ['security@mozilla.org'], - 'core-security-release' => ['security@mozilla.org'], - 'tamarin-security' => ['tamarinsecurity@adobe.com'], - 'toolkit-core-security' => ['security@mozilla.org'], - 'websites-security' => ['web-security@mozilla.org'], - 'webtools-security' => ['web-security@mozilla.org'], + 'addons-security' => ['amo-editors@mozilla.org'], + 'b2g-core-security' => ['security@mozilla.org'], + 'bugzilla-security' => ['security@bugzilla.org'], + 'client-services-security' => + ['amo-admins@mozilla.org', 'web-security@mozilla.org'], + 'cloud-services-security' => ['web-security@mozilla.org'], + 'core-security' => ['security@mozilla.org'], + 'crypto-core-security' => ['security@mozilla.org'], + 'dom-core-security' => ['security@mozilla.org'], + 'firefox-core-security' => ['security@mozilla.org'], + 'gfx-core-security' => ['security@mozilla.org'], + 'javascript-core-security' => ['security@mozilla.org'], + 'layout-core-security' => ['security@mozilla.org'], + 'mail-core-security' => ['security@mozilla.org'], + 'media-core-security' => ['security@mozilla.org'], + 'network-core-security' => ['security@mozilla.org'], + 'core-security-release' => ['security@mozilla.org'], + 'tamarin-security' => ['tamarinsecurity@adobe.com'], + 'toolkit-core-security' => ['security@mozilla.org'], + 'websites-security' => ['web-security@mozilla.org'], + 'webtools-security' => ['web-security@mozilla.org'], ); # Who can set custom flags (use full field names only, not regex's) -our $cf_setters = { - 'cf_colo_site' => [ 'infra', 'build' ], - 'cf_rank' => [ 'rank-setters' ], -}; +our $cf_setters + = {'cf_colo_site' => ['infra', 'build'], 'cf_rank' => ['rank-setters'],}; # Groups in which you can always file a bug, regardless of product or user. our @always_fileable_groups = qw( - addons-security - bugzilla-security - client-services-security - consulting - core-security - finance - infra - infrasec - l20n-security - marketing-private - mozilla-confidential - mozilla-employee-confidential - mozilla-foundation-confidential - mozilla-engagement - mozilla-messaging-confidential - partner-confidential - payments-confidential - tamarin-security - websites-security - webtools-security + addons-security + bugzilla-security + client-services-security + consulting + core-security + finance + infra + infrasec + l20n-security + marketing-private + mozilla-confidential + mozilla-employee-confidential + mozilla-foundation-confidential + mozilla-engagement + mozilla-messaging-confidential + partner-confidential + payments-confidential + tamarin-security + websites-security + webtools-security ); # Automatically CC users to bugs filed into configured groups and products our %group_auto_cc = ( - 'partner-confidential' => { - 'Marketing' => ['jbalaco@mozilla.com'], - '_default' => ['mbest@mozilla.com'], - }, + 'partner-confidential' => { + 'Marketing' => ['jbalaco@mozilla.com'], + '_default' => ['mbest@mozilla.com'], + }, ); # Force create-bug template by product # Users in 'include' group will be forced into using the form. our %create_bug_formats = ( - 'Data Compliance' => { - 'format' => 'data-compliance', - 'include' => 'everyone', - }, - 'developer.mozilla.org' => { - 'format' => 'mdn', - 'include' => 'everyone', - }, - 'Legal' => { - 'format' => 'legal', - 'include' => 'everyone', - }, - 'Recruiting' => { - 'format' => 'recruiting', - 'include' => 'everyone', - }, - 'Internet Public Policy' => { - 'format' => 'ipp', - 'include' => 'everyone', - }, + 'Data Compliance' => {'format' => 'data-compliance', 'include' => 'everyone',}, + 'developer.mozilla.org' => {'format' => 'mdn', 'include' => 'everyone',}, + 'Legal' => {'format' => 'legal', 'include' => 'everyone',}, + 'Recruiting' => {'format' => 'recruiting', 'include' => 'everyone',}, + 'Internet Public Policy' => {'format' => 'ipp', 'include' => 'everyone',}, ); # List of named queries which will be added to new users' footer our @default_named_queries = ( - { - name => 'Bugs Filed Today', - query => 'query_format=advanced&chfieldto=Now&chfield=[Bug creation]&chfieldfrom=-24h&order=bug_id', - }, + { + name => 'Bugs Filed Today', + query => + 'query_format=advanced&chfieldto=Now&chfield=[Bug creation]&chfieldfrom=-24h&order=bug_id', + }, ); 1; diff --git a/extensions/BMO/lib/FakeBug.pm b/extensions/BMO/lib/FakeBug.pm index f84835ddd..5b5395619 100644 --- a/extensions/BMO/lib/FakeBug.pm +++ b/extensions/BMO/lib/FakeBug.pm @@ -12,32 +12,32 @@ use Bugzilla::Bug; our $AUTOLOAD; sub new { - my $class = shift; - my $self = shift; - bless $self, $class; - return $self; + my $class = shift; + my $self = shift; + bless $self, $class; + return $self; } sub AUTOLOAD { - my $self = shift; - my $name = $AUTOLOAD; - $name =~ s/.*://; - return exists $self->{$name} ? $self->{$name} : undef; + my $self = shift; + my $name = $AUTOLOAD; + $name =~ s/.*://; + return exists $self->{$name} ? $self->{$name} : undef; } sub check_can_change_field { - my $self = shift; - return Bugzilla::Bug::check_can_change_field($self, @_) + my $self = shift; + return Bugzilla::Bug::check_can_change_field($self, @_); } sub _changes_everconfirmed { - my $self = shift; - return Bugzilla::Bug::_changes_everconfirmed($self, @_) + my $self = shift; + return Bugzilla::Bug::_changes_everconfirmed($self, @_); } sub everconfirmed { - my $self = shift; - return ($self->{'status'} == 'UNCONFIRMED') ? 0 : 1; + my $self = shift; + return ($self->{'status'} == 'UNCONFIRMED') ? 0 : 1; } 1; diff --git a/extensions/BMO/lib/Reports/Groups.pm b/extensions/BMO/lib/Reports/Groups.pm index ce7df767c..7b395aca9 100644 --- a/extensions/BMO/lib/Reports/Groups.pm +++ b/extensions/BMO/lib/Reports/Groups.pm @@ -19,25 +19,24 @@ use Bugzilla::Util qw(trim datetime_from); use JSON qw(encode_json); sub admins_report { - my ($vars) = @_; - my $dbh = Bugzilla->dbh; - my $user = Bugzilla->user; + my ($vars) = @_; + my $dbh = Bugzilla->dbh; + my $user = Bugzilla->user; - ($user->in_group('editbugs')) - || ThrowUserError('auth_failure', { group => 'editbugs', - action => 'run', - object => 'group_admins' }); + ($user->in_group('editbugs')) + || ThrowUserError('auth_failure', + {group => 'editbugs', action => 'run', object => 'group_admins'}); - my @grouplist = - ($user->in_group('editusers') || $user->in_group('infrasec')) - ? map { lc($_->name) } Bugzilla::Group->get_all - : _get_permitted_membership_groups(); + my @grouplist + = ($user->in_group('editusers') || $user->in_group('infrasec')) + ? map { lc($_->name) } Bugzilla::Group->get_all + : _get_permitted_membership_groups(); - my $groups = join(',', map { $dbh->quote($_) } @grouplist); + my $groups = join(',', map { $dbh->quote($_) } @grouplist); - my $query = " - SELECT groups.id, " . - $dbh->sql_group_concat('profiles.userid', "','", 1) . " + my $query = " + SELECT groups.id, " + . $dbh->sql_group_concat('profiles.userid', "','", 1) . " FROM groups LEFT JOIN user_group_map ON user_group_map.group_id = groups.id @@ -49,271 +48,275 @@ sub admins_report { AND groups.name IN ($groups) GROUP BY groups.name"; - my @groups; - foreach my $row (@{ $dbh->selectall_arrayref($query) }) { - my $group = Bugzilla::Group->new({ id => shift @$row, cache => 1}); - my @admins; - if (my $admin_ids = shift @$row) { - foreach my $uid (split(/,/, $admin_ids)) { - push(@admins, Bugzilla::User->new({ id => $uid, cache => 1 })); - } - } - push(@groups, { name => $group->name, - description => $group->description, - owner => $group->owner, - admins => \@admins }); + my @groups; + foreach my $row (@{$dbh->selectall_arrayref($query)}) { + my $group = Bugzilla::Group->new({id => shift @$row, cache => 1}); + my @admins; + if (my $admin_ids = shift @$row) { + foreach my $uid (split(/,/, $admin_ids)) { + push(@admins, Bugzilla::User->new({id => $uid, cache => 1})); + } } + push( + @groups, + { + name => $group->name, + description => $group->description, + owner => $group->owner, + admins => \@admins + } + ); + } - $vars->{'groups'} = \@groups; + $vars->{'groups'} = \@groups; } sub membership_report { - my ($page, $vars) = @_; - my $dbh = Bugzilla->dbh; - my $user = Bugzilla->user; - my $cgi = Bugzilla->cgi; - - ($user->in_group('editusers') || $user->in_group('infrasec')) - || ThrowUserError('auth_failure', { group => 'editusers', - action => 'run', - object => 'group_admins' }); - - my $who = $cgi->param('who'); - if (!defined($who) || $who eq '') { - if ($page eq 'group_membership.txt') { - print $cgi->redirect("page.cgi?id=group_membership.html&output=txt"); - exit; - } - $vars->{'output'} = $cgi->param('output'); - return; + my ($page, $vars) = @_; + my $dbh = Bugzilla->dbh; + my $user = Bugzilla->user; + my $cgi = Bugzilla->cgi; + + ($user->in_group('editusers') || $user->in_group('infrasec')) + || ThrowUserError('auth_failure', + {group => 'editusers', action => 'run', object => 'group_admins'}); + + my $who = $cgi->param('who'); + if (!defined($who) || $who eq '') { + if ($page eq 'group_membership.txt') { + print $cgi->redirect("page.cgi?id=group_membership.html&output=txt"); + exit; } + $vars->{'output'} = $cgi->param('output'); + return; + } - Bugzilla::User::match_field({ 'who' => {'type' => 'multi'} }); - $who = Bugzilla->input_params->{'who'}; - $who = ref($who) ? $who : [ $who ]; + Bugzilla::User::match_field({'who' => {'type' => 'multi'}}); + $who = Bugzilla->input_params->{'who'}; + $who = ref($who) ? $who : [$who]; - my @users; - foreach my $login (@$who) { - my $u = Bugzilla::User->new(login_to_id($login, 1)); + my @users; + foreach my $login (@$who) { + my $u = Bugzilla::User->new(login_to_id($login, 1)); - # this is lifted from $user->groups() - # we need to show which groups are direct and which are inherited + # this is lifted from $user->groups() + # we need to show which groups are direct and which are inherited - my $groups_to_check = $dbh->selectcol_arrayref( - q{SELECT DISTINCT group_id + my $groups_to_check = $dbh->selectcol_arrayref( + q{SELECT DISTINCT group_id FROM user_group_map - WHERE user_id = ? AND isbless = 0}, undef, $u->id); + WHERE user_id = ? AND isbless = 0}, undef, $u->id + ); - my $rows = $dbh->selectall_arrayref( - "SELECT DISTINCT grantor_id, member_id + my $rows = $dbh->selectall_arrayref( + "SELECT DISTINCT grantor_id, member_id FROM group_group_map - WHERE grant_type = " . GROUP_MEMBERSHIP); + WHERE grant_type = " . GROUP_MEMBERSHIP + ); - my %group_membership; - foreach my $row (@$rows) { - my ($grantor_id, $member_id) = @$row; - push (@{ $group_membership{$member_id} }, $grantor_id); - } + my %group_membership; + foreach my $row (@$rows) { + my ($grantor_id, $member_id) = @$row; + push(@{$group_membership{$member_id}}, $grantor_id); + } - my %checked_groups; - my %direct_groups; - my %indirect_groups; - my %groups; + my %checked_groups; + my %direct_groups; + my %indirect_groups; + my %groups; - foreach my $member_id (@$groups_to_check) { - $direct_groups{$member_id} = 1; - } + foreach my $member_id (@$groups_to_check) { + $direct_groups{$member_id} = 1; + } - while (scalar(@$groups_to_check) > 0) { - my $member_id = shift @$groups_to_check; - if (!$checked_groups{$member_id}) { - $checked_groups{$member_id} = 1; - my $members = $group_membership{$member_id}; - my @new_to_check = grep(!$checked_groups{$_}, @$members); - push(@$groups_to_check, @new_to_check); - foreach my $id (@new_to_check) { - $indirect_groups{$id} = $member_id; - } - $groups{$member_id} = 1; - } + while (scalar(@$groups_to_check) > 0) { + my $member_id = shift @$groups_to_check; + if (!$checked_groups{$member_id}) { + $checked_groups{$member_id} = 1; + my $members = $group_membership{$member_id}; + my @new_to_check = grep(!$checked_groups{$_}, @$members); + push(@$groups_to_check, @new_to_check); + foreach my $id (@new_to_check) { + $indirect_groups{$id} = $member_id; } + $groups{$member_id} = 1; + } + } - my @groups; - my $ra_groups = Bugzilla::Group->new_from_list([keys %groups]); - foreach my $group (@$ra_groups) { - my $via; - if ($direct_groups{$group->id}) { - $via = ''; - } else { - foreach my $g (@$ra_groups) { - if ($g->id == $indirect_groups{$group->id}) { - $via = $g->name; - last; - } - } - } - push @groups, { - name => $group->name, - desc => $group->description, - via => $via, - }; + my @groups; + my $ra_groups = Bugzilla::Group->new_from_list([keys %groups]); + foreach my $group (@$ra_groups) { + my $via; + if ($direct_groups{$group->id}) { + $via = ''; + } + else { + foreach my $g (@$ra_groups) { + if ($g->id == $indirect_groups{$group->id}) { + $via = $g->name; + last; + } } - - push @users, { - user => $u, - groups => \@groups, - }; + } + push @groups, {name => $group->name, desc => $group->description, via => $via,}; } - $vars->{'who'} = $who; - $vars->{'users'} = \@users; + push @users, {user => $u, groups => \@groups,}; + } + + $vars->{'who'} = $who; + $vars->{'users'} = \@users; } sub members_report { - my ($page, $vars) = @_; - my $dbh = Bugzilla->dbh; - my $user = Bugzilla->user; - my $cgi = Bugzilla->cgi; - - ($user->in_group('editbugs')) - || ThrowUserError('auth_failure', { group => 'editbugs', - action => 'run', - object => 'group_admins' }); - - my $privileged = $user->in_group('editusers') || $user->in_group('infrasec'); - $vars->{privileged} = $privileged; - - my @grouplist = $privileged - ? map { lc($_->name) } Bugzilla::Group->get_all - : _get_permitted_membership_groups(); - - my $include_disabled = $cgi->param('include_disabled') ? 1 : 0; - $vars->{'include_disabled'} = $include_disabled; - - # don't allow all groups, to avoid putting pain on the servers - my @group_names = - sort - grep { !/^(?:bz_.+|canconfirm|editbugs|editbugs-team|everyone)$/ } - @grouplist; - unshift(@group_names, ''); - $vars->{'groups'} = \@group_names; - - # load selected group - my $group = lc(trim($cgi->param('group') || '')); - $group = '' unless grep { $_ eq $group } @group_names; - return if $group eq ''; - my $group_obj = Bugzilla::Group->new({ name => $group }); - $vars->{'group'} = $group_obj; - - $vars->{'privileged'} = 1 if ($group_obj->owner && $group_obj->owner->id == $user->id); - - my @types; - my $members = $group_obj->members_complete(); - foreach my $name (sort keys %$members) { - push @types, { - name => ($name eq '_direct' ? 'direct' : $name), - members => _filter_userlist($members->{$name}), + my ($page, $vars) = @_; + my $dbh = Bugzilla->dbh; + my $user = Bugzilla->user; + my $cgi = Bugzilla->cgi; + + ($user->in_group('editbugs')) + || ThrowUserError('auth_failure', + {group => 'editbugs', action => 'run', object => 'group_admins'}); + + my $privileged = $user->in_group('editusers') || $user->in_group('infrasec'); + $vars->{privileged} = $privileged; + + my @grouplist + = $privileged + ? map { lc($_->name) } Bugzilla::Group->get_all + : _get_permitted_membership_groups(); + + my $include_disabled = $cgi->param('include_disabled') ? 1 : 0; + $vars->{'include_disabled'} = $include_disabled; + + # don't allow all groups, to avoid putting pain on the servers + my @group_names + = sort grep { !/^(?:bz_.+|canconfirm|editbugs|editbugs-team|everyone)$/ } + @grouplist; + unshift(@group_names, ''); + $vars->{'groups'} = \@group_names; + + # load selected group + my $group = lc(trim($cgi->param('group') || '')); + $group = '' unless grep { $_ eq $group } @group_names; + return if $group eq ''; + my $group_obj = Bugzilla::Group->new({name => $group}); + $vars->{'group'} = $group_obj; + + $vars->{'privileged'} = 1 + if ($group_obj->owner && $group_obj->owner->id == $user->id); + + my @types; + my $members = $group_obj->members_complete(); + foreach my $name (sort keys %$members) { + push @types, + { + name => ($name eq '_direct' ? 'direct' : $name), + members => _filter_userlist($members->{$name}), + }; + } + + # make it easy for the template to detect an empty group + my $has_members = 0; + foreach my $type (@types) { + $has_members += scalar(@{$type->{members}}); + last if $has_members; + } + @types = () unless $has_members; + + if ($page eq 'group_members.json') { + my %users; + foreach my $rh (@types) { + foreach my $member (@{$rh->{members}}) { + my $login = $member->login; + if (exists $users{$login}) { + push @{$users{$login}->{groups}}, $rh->{name} if $privileged; } - } - - # make it easy for the template to detect an empty group - my $has_members = 0; - foreach my $type (@types) { - $has_members += scalar(@{ $type->{members} }); - last if $has_members; - } - @types = () unless $has_members; - - if ($page eq 'group_members.json') { - my %users; - foreach my $rh (@types) { - foreach my $member (@{ $rh->{members} }) { - my $login = $member->login; - if (exists $users{$login}) { - push @{ $users{$login}->{groups} }, $rh->{name} if $privileged; - } - else { - my $rh_user = { - login => $login, - membership => $rh->{name} eq 'direct' ? 'direct' : 'indirect', - rh_name => $rh->{name}, - }; - if ($privileged) { - $rh_user->{group} = $rh->{name}; - $rh_user->{groups} = [ $rh->{name} ]; - $rh_user->{lastseeon} = $member->last_seen_date; - $rh_user->{mfa} = $member->mfa; - $rh_user->{api_key_only} = $member->settings->{api_key_only}->{value} eq 'on' - ? JSON::true : JSON::false; - } - $users{$login} = $rh_user; - } - } + else { + my $rh_user = { + login => $login, + membership => $rh->{name} eq 'direct' ? 'direct' : 'indirect', + rh_name => $rh->{name}, + }; + if ($privileged) { + $rh_user->{group} = $rh->{name}; + $rh_user->{groups} = [$rh->{name}]; + $rh_user->{lastseeon} = $member->last_seen_date; + $rh_user->{mfa} = $member->mfa; + $rh_user->{api_key_only} + = $member->settings->{api_key_only}->{value} eq 'on' + ? JSON::true + : JSON::false; + } + $users{$login} = $rh_user; } - $vars->{types_json} = JSON->new->pretty->canonical->utf8->encode([ values %users ]); + } } - else { - my %users; - foreach my $rh (@types) { - foreach my $member (@{ $rh->{members} }) { - $users{$member->login} = 1 unless exists $users{$member->login}; - } - } - $vars->{types} = \@types; - $vars->{count} = scalar(keys %users); + $vars->{types_json} + = JSON->new->pretty->canonical->utf8->encode([values %users]); + } + else { + my %users; + foreach my $rh (@types) { + foreach my $member (@{$rh->{members}}) { + $users{$member->login} = 1 unless exists $users{$member->login}; + } } + $vars->{types} = \@types; + $vars->{count} = scalar(keys %users); + } } sub _filter_userlist { - my ($list, $include_disabled) = @_; - $list = [ grep { $_->is_enabled } @$list ] unless $include_disabled; - my $now = DateTime->now(); - my $never = DateTime->from_epoch( epoch => 0 ); - foreach my $user (@$list) { - my $last_seen = $user->last_seen_date ? datetime_from($user->last_seen_date) : $never; - $user->{last_seen_days} = sprintf( - '%.0f', - $now->subtract_datetime_absolute($last_seen)->delta_seconds / (28 * 60 * 60)); - } - return [ sort { lc($a->identity) cmp lc($b->identity) } @$list ]; + my ($list, $include_disabled) = @_; + $list = [grep { $_->is_enabled } @$list] unless $include_disabled; + my $now = DateTime->now(); + my $never = DateTime->from_epoch(epoch => 0); + foreach my $user (@$list) { + my $last_seen + = $user->last_seen_date ? datetime_from($user->last_seen_date) : $never; + $user->{last_seen_days} = sprintf('%.0f', + $now->subtract_datetime_absolute($last_seen)->delta_seconds / (28 * 60 * 60)); + } + return [sort { lc($a->identity) cmp lc($b->identity) } @$list]; } # Groups that any user with editbugs can see the membership or admin lists for. # Transparency FTW. sub _get_permitted_membership_groups { - my $user = Bugzilla->user; - - # Default publicly viewable groups - my %default_public_groups = map { $_ => 1 } qw( - bugzilla-approvers - bugzilla-reviewers - can_restrict_comments - community-it-team - mozilla-employee-confidential - mozilla-foundation-confidential - mozilla-reps - qa-approvers - ); - - # We add the group to the permitted list if: - # 1. it is a drivers group - this gives us a little - # future-proofing - # 2. it is a one of the default public groups - # 3. the user is the group's owner - # 4. or the user can bless others into the group - my @permitted_groups; - foreach my $group (Bugzilla::Group->get_all) { - my $name = $group->name; - if ($name =~ /-drivers$/ - || exists $default_public_groups{$name} - || ($group->owner && $group->owner->id == $user->id) - || $user->can_bless($group->id)) - { - push(@permitted_groups, $name); - } + my $user = Bugzilla->user; + + # Default publicly viewable groups + my %default_public_groups = map { $_ => 1 } qw( + bugzilla-approvers + bugzilla-reviewers + can_restrict_comments + community-it-team + mozilla-employee-confidential + mozilla-foundation-confidential + mozilla-reps + qa-approvers + ); + + # We add the group to the permitted list if: + # 1. it is a drivers group - this gives us a little + # future-proofing + # 2. it is a one of the default public groups + # 3. the user is the group's owner + # 4. or the user can bless others into the group + my @permitted_groups; + foreach my $group (Bugzilla::Group->get_all) { + my $name = $group->name; + if ( $name =~ /-drivers$/ + || exists $default_public_groups{$name} + || ($group->owner && $group->owner->id == $user->id) + || $user->can_bless($group->id)) + { + push(@permitted_groups, $name); } + } - return @permitted_groups; + return @permitted_groups; } 1; diff --git a/extensions/BMO/lib/Reports/Internship.pm b/extensions/BMO/lib/Reports/Internship.pm index 2dfa583a6..f9ad1a578 100644 --- a/extensions/BMO/lib/Reports/Internship.pm +++ b/extensions/BMO/lib/Reports/Internship.pm @@ -17,33 +17,30 @@ use Bugzilla::Product; use Bugzilla::Component; sub report { - my ($vars) = @_; - my $user = Bugzilla->user; - - $user->in_group('hr') - || ThrowUserError('auth_failure', { group => 'hr', - action => 'run', - object => 'internship_dashboard' }); - - my $product = Bugzilla::Product->check({ name => 'Recruiting', cache => 1 }); - my $component = Bugzilla::Component->new({ product => $product, name => 'Intern', cache => 1 }); - - # find all open internship bugs - my $bugs = Bugzilla::Bug->match({ - product_id => $product->id, - component_id => $component->id, - resolution => '', - }); - - # filter bugs based on visibility and re-bless - $user->visible_bugs($bugs); - $bugs = [ - map { bless($_, 'InternshipBug') } - grep { $user->can_see_bug($_->id) } - @$bugs - ]; - - $vars->{bugs} = $bugs; + my ($vars) = @_; + my $user = Bugzilla->user; + + $user->in_group('hr') + || ThrowUserError('auth_failure', + {group => 'hr', action => 'run', object => 'internship_dashboard'}); + + my $product = Bugzilla::Product->check({name => 'Recruiting', cache => 1}); + my $component = Bugzilla::Component->new( + {product => $product, name => 'Intern', cache => 1}); + + # find all open internship bugs + my $bugs = Bugzilla::Bug->match({ + product_id => $product->id, + component_id => $component->id, + resolution => '', + }); + + # filter bugs based on visibility and re-bless + $user->visible_bugs($bugs); + $bugs = [map { bless($_, 'InternshipBug') } + grep { $user->can_see_bug($_->id) } @$bugs]; + + $vars->{bugs} = $bugs; } 1; @@ -58,64 +55,62 @@ use Bugzilla::Comment; use Bugzilla::Util qw(trim); sub _extract { - my ($self) = @_; - return if exists $self->{internship_data}; - $self->{internship_data} = {}; - - # we only need the first comment - my $comment = Bugzilla::Comment->match({ - bug_id => $self->id, - LIMIT => 1, - })->[0]->body; - - # extract just what we need - # changing the comment will break this - - if ($comment =~ /Hiring Manager:\s+(.+)\nTeam:\n/s) { - $self->{internship_data}->{hiring_manager} = trim($1); - } - if ($comment =~ /\nVP Authority:\s+(.+)\nProduct Line:\n/s) { - $self->{internship_data}->{scvp} = trim($1); - } - if ($comment =~ /\nProduct Line:\s+(.+)\nLevel 1/s) { - $self->{internship_data}->{product_line} = trim($1); - } - if ($comment =~ /\nBusiness Need:\s+(.+)\nPotential Project:\n/s) { - $self->{internship_data}->{business_need} = trim($1); - } - if ($comment =~ /\nName:\s+(.+)$/s) { - $self->{internship_data}->{intern_name} = trim($1); - } + my ($self) = @_; + return if exists $self->{internship_data}; + $self->{internship_data} = {}; + + # we only need the first comment + my $comment + = Bugzilla::Comment->match({bug_id => $self->id, LIMIT => 1,})->[0]->body; + + # extract just what we need + # changing the comment will break this + + if ($comment =~ /Hiring Manager:\s+(.+)\nTeam:\n/s) { + $self->{internship_data}->{hiring_manager} = trim($1); + } + if ($comment =~ /\nVP Authority:\s+(.+)\nProduct Line:\n/s) { + $self->{internship_data}->{scvp} = trim($1); + } + if ($comment =~ /\nProduct Line:\s+(.+)\nLevel 1/s) { + $self->{internship_data}->{product_line} = trim($1); + } + if ($comment =~ /\nBusiness Need:\s+(.+)\nPotential Project:\n/s) { + $self->{internship_data}->{business_need} = trim($1); + } + if ($comment =~ /\nName:\s+(.+)$/s) { + $self->{internship_data}->{intern_name} = trim($1); + } } sub hiring_manager { - my ($self) = @_; - $self->_extract(); - return $self->{internship_data}->{hiring_manager}; + my ($self) = @_; + $self->_extract(); + return $self->{internship_data}->{hiring_manager}; } sub scvp { - my ($self) = @_; - $self->_extract(); - return $self->{internship_data}->{scvp}; + my ($self) = @_; + $self->_extract(); + return $self->{internship_data}->{scvp}; } sub business_need { - my ($self) = @_; - $self->_extract(); - return $self->{internship_data}->{business_need}; + my ($self) = @_; + $self->_extract(); + return $self->{internship_data}->{business_need}; } sub product_line { - my ($self) = @_; - $self->_extract(); - return $self->{internship_data}->{product_line}; + my ($self) = @_; + $self->_extract(); + return $self->{internship_data}->{product_line}; } sub intern_name { - my ($self) = @_; - $self->_extract(); - return $self->{internship_data}->{intern_name}; + my ($self) = @_; + $self->_extract(); + return $self->{internship_data}->{intern_name}; } 1; diff --git a/extensions/BMO/lib/Reports/ProductSecurity.pm b/extensions/BMO/lib/Reports/ProductSecurity.pm index e7ccda171..fb773cd93 100644 --- a/extensions/BMO/lib/Reports/ProductSecurity.pm +++ b/extensions/BMO/lib/Reports/ProductSecurity.pm @@ -16,54 +16,54 @@ use Bugzilla::Error; use Bugzilla::Product; sub report { - my ($vars) = @_; - my $user = Bugzilla->user; + my ($vars) = @_; + my $user = Bugzilla->user; - ($user->in_group('admin') || $user->in_group('infrasec')) - || ThrowUserError('auth_failure', { group => 'admin', - action => 'run', - object => 'product_security' }); + ($user->in_group('admin') || $user->in_group('infrasec')) + || ThrowUserError('auth_failure', + {group => 'admin', action => 'run', object => 'product_security'}); - my $moco = Bugzilla::Group->new({ name => 'mozilla-employee-confidential' }) - or return; + my $moco = Bugzilla::Group->new({name => 'mozilla-employee-confidential'}) + or return; - my $products = []; - foreach my $product (@{ Bugzilla::Product->match({}) }) { - my $default_group = $product->default_security_group_obj; - my $group_controls = $product->group_controls(); + my $products = []; + foreach my $product (@{Bugzilla::Product->match({})}) { + my $default_group = $product->default_security_group_obj; + my $group_controls = $product->group_controls(); - my $item = { - name => $product->name, - default_security_group => $product->default_security_group, - group_visibility => 'None/None', - moco => exists $group_controls->{$moco->id}, - }; + my $item = { + name => $product->name, + default_security_group => $product->default_security_group, + group_visibility => 'None/None', + moco => exists $group_controls->{$moco->id}, + }; - if ($default_group) { - if (my $control = $group_controls->{$default_group->id}) { - $item->{group_visibility} = control_to_string($control->{membercontrol}) . - '/' . control_to_string($control->{othercontrol}); - } - } + if ($default_group) { + if (my $control = $group_controls->{$default_group->id}) { + $item->{group_visibility} = control_to_string($control->{membercontrol}) . '/' + . control_to_string($control->{othercontrol}); + } + } - $item->{group_problem} = $default_group ? '' : "Invalid group " . $product->default_security_group; - $item->{visibility_problem} = 'Default security group should be Shown/Shown' - if ($item->{group_visibility} ne 'Shown/Shown') - && ($item->{group_visibility} ne 'Mandatory/Mandatory') - && ($item->{group_visibility} ne 'Default/Default'); + $item->{group_problem} + = $default_group ? '' : "Invalid group " . $product->default_security_group; + $item->{visibility_problem} = 'Default security group should be Shown/Shown' + if ($item->{group_visibility} ne 'Shown/Shown') + && ($item->{group_visibility} ne 'Mandatory/Mandatory') + && ($item->{group_visibility} ne 'Default/Default'); - push @$products, $item; - } - $vars->{products} = $products; + push @$products, $item; + } + $vars->{products} = $products; } sub control_to_string { - my ($control) = @_; - return 'NA' if $control == CONTROLMAPNA; - return 'Shown' if $control == CONTROLMAPSHOWN; - return 'Default' if $control == CONTROLMAPDEFAULT; - return 'Mandatory' if $control == CONTROLMAPMANDATORY; - return ''; + my ($control) = @_; + return 'NA' if $control == CONTROLMAPNA; + return 'Shown' if $control == CONTROLMAPSHOWN; + return 'Default' if $control == CONTROLMAPDEFAULT; + return 'Mandatory' if $control == CONTROLMAPMANDATORY; + return ''; } 1; diff --git a/extensions/BMO/lib/Reports/Recruiting.pm b/extensions/BMO/lib/Reports/Recruiting.pm index 39eb8327d..c35b0cbff 100644 --- a/extensions/BMO/lib/Reports/Recruiting.pm +++ b/extensions/BMO/lib/Reports/Recruiting.pm @@ -17,33 +17,30 @@ use Bugzilla::Product; use Bugzilla::Component; sub report { - my ($vars) = @_; - my $user = Bugzilla->user; - - $user->in_group('hr') - || ThrowUserError('auth_failure', { group => 'hr', - action => 'run', - object => 'recruiting_dashboard' }); - - my $product = Bugzilla::Product->check({ name => 'Recruiting', cache => 1 }); - my $component = Bugzilla::Component->new({ product => $product, name => 'General', cache => 1 }); - - # find all open recruiting bugs - my $bugs = Bugzilla::Bug->match({ - product_id => $product->id, - component_id => $component->id, - resolution => '', - }); - - # filter bugs based on visibility and re-bless - $user->visible_bugs($bugs); - $bugs = [ - map { bless($_, 'RecruitingBug') } - grep { $user->can_see_bug($_->id) } - @$bugs - ]; - - $vars->{bugs} = $bugs; + my ($vars) = @_; + my $user = Bugzilla->user; + + $user->in_group('hr') + || ThrowUserError('auth_failure', + {group => 'hr', action => 'run', object => 'recruiting_dashboard'}); + + my $product = Bugzilla::Product->check({name => 'Recruiting', cache => 1}); + my $component = Bugzilla::Component->new( + {product => $product, name => 'General', cache => 1}); + + # find all open recruiting bugs + my $bugs = Bugzilla::Bug->match({ + product_id => $product->id, + component_id => $component->id, + resolution => '', + }); + + # filter bugs based on visibility and re-bless + $user->visible_bugs($bugs); + $bugs = [map { bless($_, 'RecruitingBug') } + grep { $user->can_see_bug($_->id) } @$bugs]; + + $vars->{bugs} = $bugs; } 1; @@ -58,55 +55,56 @@ use Bugzilla::Comment; use Bugzilla::Util qw(trim); sub _extract { - my ($self) = @_; - return if exists $self->{recruitment_data}; - $self->{recruitment_data} = {}; - - # we only need the first comment - my $comment = Bugzilla::Comment->match({ - bug_id => $self->id, - LIMIT => 1, - })->[0]->body; - - # extract just what we need - # changing the comment will break this - - if ($comment =~ /\nHiring Manager:\s+(.+)VP Authority:\n/s) { - $self->{recruitment_data}->{hiring_manager} = trim($1); - } - if ($comment =~ /\nVP Authority:\s+(.+)HRBP:\n/s) { - $self->{recruitment_data}->{scvp} = trim($1); - } - if ($comment =~ /\nWhat part of your strategic plan does this role impact\?\s+(.+)Why is this critical for success\?\n/s) { - $self->{recruitment_data}->{strategic_plan} = trim($1); - } - if ($comment =~ /\nWhy is this critical for success\?\s+(.+)$/s) { - $self->{recruitment_data}->{why_critical} = trim($1); - } + my ($self) = @_; + return if exists $self->{recruitment_data}; + $self->{recruitment_data} = {}; + + # we only need the first comment + my $comment + = Bugzilla::Comment->match({bug_id => $self->id, LIMIT => 1,})->[0]->body; + + # extract just what we need + # changing the comment will break this + + if ($comment =~ /\nHiring Manager:\s+(.+)VP Authority:\n/s) { + $self->{recruitment_data}->{hiring_manager} = trim($1); + } + if ($comment =~ /\nVP Authority:\s+(.+)HRBP:\n/s) { + $self->{recruitment_data}->{scvp} = trim($1); + } + if ($comment + =~ /\nWhat part of your strategic plan does this role impact\?\s+(.+)Why is this critical for success\?\n/s + ) + { + $self->{recruitment_data}->{strategic_plan} = trim($1); + } + if ($comment =~ /\nWhy is this critical for success\?\s+(.+)$/s) { + $self->{recruitment_data}->{why_critical} = trim($1); + } } sub hiring_manager { - my ($self) = @_; - $self->_extract(); - return $self->{recruitment_data}->{hiring_manager}; + my ($self) = @_; + $self->_extract(); + return $self->{recruitment_data}->{hiring_manager}; } sub scvp { - my ($self) = @_; - $self->_extract(); - return $self->{recruitment_data}->{scvp}; + my ($self) = @_; + $self->_extract(); + return $self->{recruitment_data}->{scvp}; } sub strategic_plan { - my ($self) = @_; - $self->_extract(); - return $self->{recruitment_data}->{strategic_plan}; + my ($self) = @_; + $self->_extract(); + return $self->{recruitment_data}->{strategic_plan}; } sub why_critical { - my ($self) = @_; - $self->_extract(); - return $self->{recruitment_data}->{why_critical}; + my ($self) = @_; + $self->_extract(); + return $self->{recruitment_data}->{why_critical}; } 1; diff --git a/extensions/BMO/lib/Reports/ReleaseTracking.pm b/extensions/BMO/lib/Reports/ReleaseTracking.pm index 9fba1e14b..38a07aee7 100644 --- a/extensions/BMO/lib/Reports/ReleaseTracking.pm +++ b/extensions/BMO/lib/Reports/ReleaseTracking.pm @@ -21,496 +21,381 @@ use JSON qw(-convert_blessed_universally); use List::MoreUtils qw(uniq); use constant DATE_RANGES => [ - { - value => '20160126-20160307', - label => '2016-01-26 and 2016-03-07' - }, - { - value => '20151215-20160125', - label => '2015-12-15 and 2016-01-25' - }, - { - value => '20151103-20151214', - label => '2015-11-03 and 2015-12-14' - }, - { - value => '20150922-20151102', - label => '2015-09-22 and 2015-11-02' - }, - { - value => '20150811-20150921', - label => '2015-08-11 and 2015-09-21' - }, - { - value => '20150630-20150810', - label => '2015-06-30 and 2015-08-10' - }, - { - value => '20150512-20150629', - label => '2015-05-12 and 2015-06-29' - }, - { - value => '20150331-20150511', - label => '2015-03-31 and 2015-05-11' - }, - { - value => '20150224-20150330', - label => '2015-02-24 and 2015-03-30' - }, - { - value => '20150113-20150223', - label => '2015-01-13 and 2015-02-23' - }, - { - value => '20141111-20141222', - label => '2014-11-11 and 2014-12-22' - }, - { - value => '20140930-20141110', - label => '2014-09-30 and 2014-11-10' - }, - { - value => '20140819-20140929', - label => '2014-08-19 and 2014-09-29' - }, - { - value => '20140708-20140818', - label => '2014-07-08 and 2014-08-18' - }, - { - value => '20140527-20140707', - label => '2014-05-27 and 2014-07-07' - }, - { - value => '20140415-20140526', - label => '2014-04-15 and 2014-05-26' - }, - { - value => '20140304-20140414', - label => '2014-03-04 and 2014-04-14' - }, - { - value => '20140121-20140303', - label => '2014-01-21 and 2014-03-03' - }, - { - value => '20131210-20140120', - label => '2013-12-10 and 2014-01-20' - }, - { - value => '20131029-20131209', - label => '2013-10-29 and 2013-12-09' - }, - { - value => '20130917-20131028', - label => '2013-09-17 and 2013-10-28' - }, - { - value => '20130806-20130916', - label => '2013-08-06 and 2013-09-16' - }, - { - value => '20130625-20130805', - label => '2013-06-25 and 2013-08-05' - }, - { - value => '20130514-20130624', - label => '2013-05-14 and 2013-06-24' - }, - { - value => '20130402-20130513', - label => '2013-04-02 and 2013-05-13' - }, - { - value => '20130219-20130401', - label => '2013-02-19 and 2013-04-01' - }, - { - value => '20130108-20130218', - label => '2013-01-08 and 2013-02-18' - }, - { - value => '20121120-20130107', - label => '2012-11-20 and 2013-01-07' - }, - { - value => '20121009-20121119', - label => '2012-10-09 and 2012-11-19' - }, - { - value => '20120828-20121008', - label => '2012-08-28 and 2012-10-08' - }, - { - value => '20120717-20120827', - label => '2012-07-17 and 2012-08-27' - }, - { - value => '20120605-20120716', - label => '2012-06-05 and 2012-07-16' - }, - { - value => '20120424-20120604', - label => '2012-04-24 and 2012-06-04' - }, - { - value => '20120313-20120423', - label => '2012-03-13 and 2012-04-23' - }, - { - value => '20120131-20120312', - label => '2012-01-31 and 2012-03-12' - }, - { - value => '20111220-20120130', - label => '2011-12-20 and 2012-01-30' - }, - { - value => '20111108-20111219', - label => '2011-11-08 and 2011-12-19' - }, - { - value => '20110927-20111107', - label => '2011-09-27 and 2011-11-07' - }, - { - value => '20110816-20110926', - label => '2011-08-16 and 2011-09-26' - }, - { - value => '*', - label => 'Anytime' - } + {value => '20160126-20160307', label => '2016-01-26 and 2016-03-07'}, + {value => '20151215-20160125', label => '2015-12-15 and 2016-01-25'}, + {value => '20151103-20151214', label => '2015-11-03 and 2015-12-14'}, + {value => '20150922-20151102', label => '2015-09-22 and 2015-11-02'}, + {value => '20150811-20150921', label => '2015-08-11 and 2015-09-21'}, + {value => '20150630-20150810', label => '2015-06-30 and 2015-08-10'}, + {value => '20150512-20150629', label => '2015-05-12 and 2015-06-29'}, + {value => '20150331-20150511', label => '2015-03-31 and 2015-05-11'}, + {value => '20150224-20150330', label => '2015-02-24 and 2015-03-30'}, + {value => '20150113-20150223', label => '2015-01-13 and 2015-02-23'}, + {value => '20141111-20141222', label => '2014-11-11 and 2014-12-22'}, + {value => '20140930-20141110', label => '2014-09-30 and 2014-11-10'}, + {value => '20140819-20140929', label => '2014-08-19 and 2014-09-29'}, + {value => '20140708-20140818', label => '2014-07-08 and 2014-08-18'}, + {value => '20140527-20140707', label => '2014-05-27 and 2014-07-07'}, + {value => '20140415-20140526', label => '2014-04-15 and 2014-05-26'}, + {value => '20140304-20140414', label => '2014-03-04 and 2014-04-14'}, + {value => '20140121-20140303', label => '2014-01-21 and 2014-03-03'}, + {value => '20131210-20140120', label => '2013-12-10 and 2014-01-20'}, + {value => '20131029-20131209', label => '2013-10-29 and 2013-12-09'}, + {value => '20130917-20131028', label => '2013-09-17 and 2013-10-28'}, + {value => '20130806-20130916', label => '2013-08-06 and 2013-09-16'}, + {value => '20130625-20130805', label => '2013-06-25 and 2013-08-05'}, + {value => '20130514-20130624', label => '2013-05-14 and 2013-06-24'}, + {value => '20130402-20130513', label => '2013-04-02 and 2013-05-13'}, + {value => '20130219-20130401', label => '2013-02-19 and 2013-04-01'}, + {value => '20130108-20130218', label => '2013-01-08 and 2013-02-18'}, + {value => '20121120-20130107', label => '2012-11-20 and 2013-01-07'}, + {value => '20121009-20121119', label => '2012-10-09 and 2012-11-19'}, + {value => '20120828-20121008', label => '2012-08-28 and 2012-10-08'}, + {value => '20120717-20120827', label => '2012-07-17 and 2012-08-27'}, + {value => '20120605-20120716', label => '2012-06-05 and 2012-07-16'}, + {value => '20120424-20120604', label => '2012-04-24 and 2012-06-04'}, + {value => '20120313-20120423', label => '2012-03-13 and 2012-04-23'}, + {value => '20120131-20120312', label => '2012-01-31 and 2012-03-12'}, + {value => '20111220-20120130', label => '2011-12-20 and 2012-01-30'}, + {value => '20111108-20111219', label => '2011-11-08 and 2011-12-19'}, + {value => '20110927-20111107', label => '2011-09-27 and 2011-11-07'}, + {value => '20110816-20110926', label => '2011-08-16 and 2011-09-26'}, + {value => '*', label => 'Anytime'} ]; sub report { - my ($vars) = @_; - my $dbh = Bugzilla->dbh; - my $input = Bugzilla->input_params; - my $user = Bugzilla->user; - - my @flag_names = qw( - approval-mozilla-release - approval-mozilla-beta - approval-mozilla-aurora - approval-mozilla-central - approval-comm-release - approval-comm-beta - approval-comm-aurora - approval-calendar-release - approval-calendar-beta - approval-calendar-aurora - approval-mozilla-esr10 - ); - - my @flags_json; - my @fields_json; - my @products_json; - - # - # tracking flags - # - - my $all_products = $user->get_selectable_products; - my @usable_products; - - # build list of flags and their matching products - - my @invalid_flag_names; - foreach my $flag_name (@flag_names) { - # grab all matching flag_types - my @flag_types = @{Bugzilla::FlagType::match({ name => $flag_name, is_active => 1 })}; - - # remove invalid flags - if (!@flag_types) { - push @invalid_flag_names, $flag_name; - next; - } + my ($vars) = @_; + my $dbh = Bugzilla->dbh; + my $input = Bugzilla->input_params; + my $user = Bugzilla->user; + + my @flag_names = qw( + approval-mozilla-release + approval-mozilla-beta + approval-mozilla-aurora + approval-mozilla-central + approval-comm-release + approval-comm-beta + approval-comm-aurora + approval-calendar-release + approval-calendar-beta + approval-calendar-aurora + approval-mozilla-esr10 + ); + + my @flags_json; + my @fields_json; + my @products_json; + + # + # tracking flags + # + + my $all_products = $user->get_selectable_products; + my @usable_products; + + # build list of flags and their matching products + + my @invalid_flag_names; + foreach my $flag_name (@flag_names) { + + # grab all matching flag_types + my @flag_types + = @{Bugzilla::FlagType::match({name => $flag_name, is_active => 1})}; + + # remove invalid flags + if (!@flag_types) { + push @invalid_flag_names, $flag_name; + next; + } - # we need a list of products, based on inclusions/exclusions - my @products; - my %flag_types; - foreach my $flag_type (@flag_types) { - $flag_types{$flag_type->name} = $flag_type->id; - my $has_all = 0; - my @exclusion_ids; - my @inclusion_ids; - foreach my $flag_type (@flag_types) { - if (scalar keys %{$flag_type->inclusions}) { - my $inclusions = $flag_type->inclusions; - foreach my $key (keys %$inclusions) { - push @inclusion_ids, ($inclusions->{$key} =~ /^(\d+)/); - } - } elsif (scalar keys %{$flag_type->exclusions}) { - my $exclusions = $flag_type->exclusions; - foreach my $key (keys %$exclusions) { - push @exclusion_ids, ($exclusions->{$key} =~ /^(\d+)/); - } - } else { - $has_all = 1; - last; - } - } - - if ($has_all) { - push @products, @$all_products; - } elsif (scalar @exclusion_ids) { - push @products, @$all_products; - foreach my $exclude_id (uniq @exclusion_ids) { - @products = grep { $_->id != $exclude_id } @products; - } - } else { - foreach my $include_id (uniq @inclusion_ids) { - push @products, grep { $_->id == $include_id } @$all_products; - } - } + # we need a list of products, based on inclusions/exclusions + my @products; + my %flag_types; + foreach my $flag_type (@flag_types) { + $flag_types{$flag_type->name} = $flag_type->id; + my $has_all = 0; + my @exclusion_ids; + my @inclusion_ids; + foreach my $flag_type (@flag_types) { + if (scalar keys %{$flag_type->inclusions}) { + my $inclusions = $flag_type->inclusions; + foreach my $key (keys %$inclusions) { + push @inclusion_ids, ($inclusions->{$key} =~ /^(\d+)/); + } } - @products = uniq @products; - push @usable_products, @products; - my @product_ids = map { $_->id } sort { lc($a->name) cmp lc($b->name) } @products; - - push @flags_json, { - name => $flag_name, - id => $flag_types{$flag_name} || 0, - products => \@product_ids, - fields => [], - }; - } - foreach my $flag_name (@invalid_flag_names) { - @flag_names = grep { $_ ne $flag_name } @flag_names; - } - @usable_products = uniq @usable_products; - - # build a list of tracking flags for each product - # also build the list of all fields - - my @unlink_products; - foreach my $product (@usable_products) { - my @fields = - sort { $a->sortkey <=> $b->sortkey } - grep { is_active_status_field($_) } - Bugzilla->active_custom_fields({ product => $product }); - my @field_ids = map { $_->id } @fields; - if (!scalar @fields) { - push @unlink_products, $product; - next; + elsif (scalar keys %{$flag_type->exclusions}) { + my $exclusions = $flag_type->exclusions; + foreach my $key (keys %$exclusions) { + push @exclusion_ids, ($exclusions->{$key} =~ /^(\d+)/); + } } - - # product - push @products_json, { - name => $product->name, - id => $product->id, - fields => \@field_ids, - }; - - # add fields to flags - foreach my $rh (@flags_json) { - if (grep { $_ eq $product->id } @{$rh->{products}}) { - push @{$rh->{fields}}, @field_ids; - } + else { + $has_all = 1; + last; } - - # add fields to fields_json - foreach my $field (@fields) { - my $existing = 0; - foreach my $rh (@fields_json) { - if ($rh->{id} == $field->id) { - $existing = 1; - last; - } - } - if (!$existing) { - push @fields_json, { - name => $field->name, - desc => $field->description, - id => $field->id, - }; - } + } + + if ($has_all) { + push @products, @$all_products; + } + elsif (scalar @exclusion_ids) { + push @products, @$all_products; + foreach my $exclude_id (uniq @exclusion_ids) { + @products = grep { $_->id != $exclude_id } @products; } + } + else { + foreach my $include_id (uniq @inclusion_ids) { + push @products, grep { $_->id == $include_id } @$all_products; + } + } } - foreach my $rh (@flags_json) { - my @fields = uniq @{$rh->{fields}}; - $rh->{fields} = \@fields; + @products = uniq @products; + push @usable_products, @products; + my @product_ids + = map { $_->id } sort { lc($a->name) cmp lc($b->name) } @products; + + push @flags_json, + { + name => $flag_name, + id => $flag_types{$flag_name} || 0, + products => \@product_ids, + fields => [], + }; + } + foreach my $flag_name (@invalid_flag_names) { + @flag_names = grep { $_ ne $flag_name } @flag_names; + } + @usable_products = uniq @usable_products; + + # build a list of tracking flags for each product + # also build the list of all fields + + my @unlink_products; + foreach my $product (@usable_products) { + my @fields + = sort { $a->sortkey <=> $b->sortkey } + grep { is_active_status_field($_) } + Bugzilla->active_custom_fields({product => $product}); + my @field_ids = map { $_->id } @fields; + if (!scalar @fields) { + push @unlink_products, $product; + next; } - # remove products which aren't linked with status fields + # product + push @products_json, + {name => $product->name, id => $product->id, fields => \@field_ids,}; + # add fields to flags foreach my $rh (@flags_json) { - my @product_ids; - foreach my $id (@{$rh->{products}}) { - unless (grep { $_->id == $id } @unlink_products) { - push @product_ids, $id; - } - $rh->{products} = \@product_ids; + if (grep { $_ eq $product->id } @{$rh->{products}}) { + push @{$rh->{fields}}, @field_ids; + } + } + + # add fields to fields_json + foreach my $field (@fields) { + my $existing = 0; + foreach my $rh (@fields_json) { + if ($rh->{id} == $field->id) { + $existing = 1; + last; } + } + if (!$existing) { + push @fields_json, + {name => $field->name, desc => $field->description, id => $field->id,}; + } } + } + foreach my $rh (@flags_json) { + my @fields = uniq @{$rh->{fields}}; + $rh->{fields} = \@fields; + } + + # remove products which aren't linked with status fields + + foreach my $rh (@flags_json) { + my @product_ids; + foreach my $id (@{$rh->{products}}) { + unless (grep { $_->id == $id } @unlink_products) { + push @product_ids, $id; + } + $rh->{products} = \@product_ids; + } + } - # - # run report - # + # + # run report + # - if ($input->{q} && !$input->{edit}) { - my $q = _parse_query($input->{q}); + if ($input->{q} && !$input->{edit}) { + my $q = _parse_query($input->{q}); - my @where; - my @params; - my $query = " + my @where; + my @params; + my $query = " SELECT DISTINCT b.bug_id FROM bugs b INNER JOIN flags f ON f.bug_id = b.bug_id\n"; - if ($q->{start_date}) { - $query .= "INNER JOIN bugs_activity a ON a.bug_id = b.bug_id\n"; - } + if ($q->{start_date}) { + $query .= "INNER JOIN bugs_activity a ON a.bug_id = b.bug_id\n"; + } - $query .= "WHERE "; + $query .= "WHERE "; - if ($q->{start_date}) { - push @where, "(a.fieldid = ?)"; - push @params, $q->{field_id}; + if ($q->{start_date}) { + push @where, "(a.fieldid = ?)"; + push @params, $q->{field_id}; - push @where, "(CONVERT_TZ(a.bug_when, 'UTC', 'America/Los_Angeles') >= ?)"; - push @params, $q->{start_date} . ' 00:00:00'; - push @where, "(CONVERT_TZ(a.bug_when, 'UTC', 'America/Los_Angeles') <= ?)"; - push @params, $q->{end_date} . ' 23:59:59'; + push @where, "(CONVERT_TZ(a.bug_when, 'UTC', 'America/Los_Angeles') >= ?)"; + push @params, $q->{start_date} . ' 00:00:00'; + push @where, "(CONVERT_TZ(a.bug_when, 'UTC', 'America/Los_Angeles') <= ?)"; + push @params, $q->{end_date} . ' 23:59:59'; - push @where, "(a.added LIKE ?)"; - push @params, '%' . $q->{flag_name} . $q->{flag_status} . '%'; - } + push @where, "(a.added LIKE ?)"; + push @params, '%' . $q->{flag_name} . $q->{flag_status} . '%'; + } - my ($type_id) = $dbh->selectrow_array( - "SELECT id FROM flagtypes WHERE name = ?", - undef, - $q->{flag_name} - ); - push @where, "(f.type_id = ?)"; - push @params, $type_id; + my ($type_id) = $dbh->selectrow_array("SELECT id FROM flagtypes WHERE name = ?", + undef, $q->{flag_name}); + push @where, "(f.type_id = ?)"; + push @params, $type_id; - push @where, "(f.status = ?)"; - push @params, $q->{flag_status}; + push @where, "(f.status = ?)"; + push @params, $q->{flag_status}; - if ($q->{product_id}) { - push @where, "(b.product_id = ?)"; - push @params, $q->{product_id}; - } + if ($q->{product_id}) { + push @where, "(b.product_id = ?)"; + push @params, $q->{product_id}; + } - if (scalar @{$q->{fields}}) { - my @fields; - foreach my $field (@{$q->{fields}}) { - my $field_sql = "("; - if ($field->{type} == FIELD_TYPE_EXTENSION) { - $field_sql .= " + if (scalar @{$q->{fields}}) { + my @fields; + foreach my $field (@{$q->{fields}}) { + my $field_sql = "("; + if ($field->{type} == FIELD_TYPE_EXTENSION) { + $field_sql .= " COALESCE( (SELECT tracking_flags_bugs.value FROM tracking_flags_bugs LEFT JOIN tracking_flags ON tracking_flags.id = tracking_flags_bugs.tracking_flag_id WHERE tracking_flags_bugs.bug_id = b.bug_id - AND tracking_flags.name = " . $dbh->quote($field->{name}) . ") + AND tracking_flags.name = " + . $dbh->quote($field->{name}) . ") , '') "; - } - else { - $field_sql .= "b." . $field->{name}; - } - $field_sql .= " " . ($field->{value} eq '+' ? '' : 'NOT ') . "IN ('fixed','verified'))"; - push(@fields, $field_sql); - } - my $join = uc $q->{join}; - push @where, '(' . join(" $join ", @fields) . ')'; } - - $query .= join("\nAND ", @where); - - my $bugs = $dbh->selectcol_arrayref($query, undef, @params); - push @$bugs, 0 unless @$bugs; - - my $urlbase = Bugzilla->localconfig->{urlbase}; - my $cgi = Bugzilla->cgi; - print $cgi->redirect( - -url => "${urlbase}buglist.cgi?bug_id=" . join(',', @$bugs) - ); - exit; + else { + $field_sql .= "b." . $field->{name}; + } + $field_sql + .= " " . ($field->{value} eq '+' ? '' : 'NOT ') . "IN ('fixed','verified'))"; + push(@fields, $field_sql); + } + my $join = uc $q->{join}; + push @where, '(' . join(" $join ", @fields) . ')'; } - # - # set template vars - # - - my $json = JSON->new()->shrink(1); - $vars->{flags_json} = $json->encode(\@flags_json); - $vars->{products_json} = $json->encode(\@products_json); - $vars->{fields_json} = $json->encode(\@fields_json); - $vars->{flag_names} = \@flag_names; - $vars->{ranges} = DATE_RANGES; - $vars->{default_query} = $input->{q}; - $vars->{is_custom} = $input->{is_custom}; - foreach my $field (qw(product flags range)) { - $vars->{$field} = $input->{$field}; - } + $query .= join("\nAND ", @where); + + my $bugs = $dbh->selectcol_arrayref($query, undef, @params); + push @$bugs, 0 unless @$bugs; + + my $urlbase = Bugzilla->localconfig->{urlbase}; + my $cgi = Bugzilla->cgi; + print $cgi->redirect( + -url => "${urlbase}buglist.cgi?bug_id=" . join(',', @$bugs)); + exit; + } + + # + # set template vars + # + + my $json = JSON->new()->shrink(1); + $vars->{flags_json} = $json->encode(\@flags_json); + $vars->{products_json} = $json->encode(\@products_json); + $vars->{fields_json} = $json->encode(\@fields_json); + $vars->{flag_names} = \@flag_names; + $vars->{ranges} = DATE_RANGES; + $vars->{default_query} = $input->{q}; + $vars->{is_custom} = $input->{is_custom}; + foreach my $field (qw(product flags range)) { + $vars->{$field} = $input->{$field}; + } } sub _parse_query { - my $q = shift; - my @query = split(/:/, $q); - my $query; - - # field_id for flag changes - $query->{field_id} = get_field_id('flagtypes.name'); - - # flag_name - my $flag_name = shift @query; - @{Bugzilla::FlagType::match({ name => $flag_name, is_active => 1 })} - or ThrowUserError('report_invalid_parameter', { name => 'flag_name' }); - trick_taint($flag_name); - $query->{flag_name} = $flag_name; - - # flag_status - my $flag_status = shift @query; - $flag_status =~ /^([\?\-\+])$/ - or ThrowUserError('report_invalid_parameter', { name => 'flag_status' }); - $query->{flag_status} = $1; - - # date_range -> from_ymd to_ymd - my $date_range = shift @query; - if ($date_range ne '*') { - $date_range =~ /^(\d\d\d\d)(\d\d)(\d\d)-(\d\d\d\d)(\d\d)(\d\d)$/ - or ThrowUserError('report_invalid_parameter', { name => 'date_range' }); - $query->{start_date} = "$1-$2-$3"; - $query->{end_date} = "$4-$5-$6"; - validate_date($query->{start_date}) - || ThrowUserError('illegal_date', { date => $query->{start_date}, - format => 'YYYY-MM-DD' }); - validate_date($query->{end_date}) - || ThrowUserError('illegal_date', { date => $query->{end_date}, - format => 'YYYY-MM-DD' }); - } - - # product_id - my $product_id = shift @query; - $product_id =~ /^(\d+)$/ - or ThrowUserError('report_invalid_parameter', { name => 'product_id' }); - $query->{product_id} = $1; - - # join - my $join = shift @query; - $join =~ /^(and|or)$/ - or ThrowUserError('report_invalid_parameter', { name => 'join' }); - $query->{join} = $1; - - # fields - my @fields; - foreach my $field (@query) { - $field =~ /^(\d+)([\-\+])$/ - or ThrowUserError('report_invalid_parameter', { name => 'fields' }); - my ($id, $value) = ($1, $2); - my $field_obj = Bugzilla::Field->new($id) - or ThrowUserError('report_invalid_parameter', { name => 'field_id' }); - push @fields, { id => $id, value => $value, - name => $field_obj->name, type => $field_obj->type }; - } - $query->{fields} = \@fields; - - return $query; + my $q = shift; + my @query = split(/:/, $q); + my $query; + + # field_id for flag changes + $query->{field_id} = get_field_id('flagtypes.name'); + + # flag_name + my $flag_name = shift @query; + @{Bugzilla::FlagType::match({name => $flag_name, is_active => 1})} + or ThrowUserError('report_invalid_parameter', {name => 'flag_name'}); + trick_taint($flag_name); + $query->{flag_name} = $flag_name; + + # flag_status + my $flag_status = shift @query; + $flag_status =~ /^([\?\-\+])$/ + or ThrowUserError('report_invalid_parameter', {name => 'flag_status'}); + $query->{flag_status} = $1; + + # date_range -> from_ymd to_ymd + my $date_range = shift @query; + if ($date_range ne '*') { + $date_range =~ /^(\d\d\d\d)(\d\d)(\d\d)-(\d\d\d\d)(\d\d)(\d\d)$/ + or ThrowUserError('report_invalid_parameter', {name => 'date_range'}); + $query->{start_date} = "$1-$2-$3"; + $query->{end_date} = "$4-$5-$6"; + validate_date($query->{start_date}) + || ThrowUserError('illegal_date', + {date => $query->{start_date}, format => 'YYYY-MM-DD'}); + validate_date($query->{end_date}) + || ThrowUserError('illegal_date', + {date => $query->{end_date}, format => 'YYYY-MM-DD'}); + } + + # product_id + my $product_id = shift @query; + $product_id =~ /^(\d+)$/ + or ThrowUserError('report_invalid_parameter', {name => 'product_id'}); + $query->{product_id} = $1; + + # join + my $join = shift @query; + $join =~ /^(and|or)$/ + or ThrowUserError('report_invalid_parameter', {name => 'join'}); + $query->{join} = $1; + + # fields + my @fields; + foreach my $field (@query) { + $field =~ /^(\d+)([\-\+])$/ + or ThrowUserError('report_invalid_parameter', {name => 'fields'}); + my ($id, $value) = ($1, $2); + my $field_obj = Bugzilla::Field->new($id) + or ThrowUserError('report_invalid_parameter', {name => 'field_id'}); + push @fields, + { + id => $id, + value => $value, + name => $field_obj->name, + type => $field_obj->type + }; + } + $query->{fields} = \@fields; + + return $query; } 1; diff --git a/extensions/BMO/lib/Reports/Triage.pm b/extensions/BMO/lib/Reports/Triage.pm index 55eeb17eb..0ccbbee6e 100644 --- a/extensions/BMO/lib/Reports/Triage.pm +++ b/extensions/BMO/lib/Reports/Triage.pm @@ -25,261 +25,279 @@ use List::MoreUtils qw(any); # set an upper limit on the *unfiltered* number of bugs to process use constant MAX_NUMBER_BUGS => 4000; -use constant DEFAULT_OWNER_PRODUCTS => ( - 'Core', - 'Firefox', - 'Firefox for Android', - 'Firefox for iOS', - 'Toolkit', -); +use constant DEFAULT_OWNER_PRODUCTS => + ('Core', 'Firefox', 'Firefox for Android', 'Firefox for iOS', 'Toolkit',); sub unconfirmed { - my ($vars, $filter) = @_; - my $dbh = Bugzilla->dbh; - my $input = Bugzilla->input_params; - my $user = Bugzilla->user; - - if (exists $input->{'action'} && $input->{'action'} eq 'run' && $input->{'product'}) { - - # load product and components from input - - my $product = Bugzilla::Product->new({ name => $input->{'product'} }) - || ThrowUserError('invalid_object', { object => 'Product', value => $input->{'product'} }); - - my @component_ids; - if ($input->{'component'} ne '') { - my $ra_components = ref($input->{'component'}) - ? $input->{'component'} : [ $input->{'component'} ]; - foreach my $component_name (@$ra_components) { - my $component = Bugzilla::Component->new({ name => $component_name, product => $product }) - || ThrowUserError('invalid_object', { object => 'Component', value => $component_name }); - push @component_ids, $component->id; - } - } + my ($vars, $filter) = @_; + my $dbh = Bugzilla->dbh; + my $input = Bugzilla->input_params; + my $user = Bugzilla->user; - # determine which comment filters to run + if ( exists $input->{'action'} + && $input->{'action'} eq 'run' + && $input->{'product'}) + { - my $filter_commenter = $input->{'filter_commenter'}; - my $filter_commenter_on = $input->{'commenter'}; - my $filter_last = $input->{'filter_last'}; - my $filter_last_period = $input->{'last'}; + # load product and components from input - if (!$filter_commenter || $filter_last) { - $filter_commenter = '1'; - $filter_commenter_on = 'reporter'; - } + my $product + = Bugzilla::Product->new({name => $input->{'product'}}) + || ThrowUserError('invalid_object', + {object => 'Product', value => $input->{'product'}}); - my $filter_commenter_id; - if ($filter_commenter && $filter_commenter_on eq 'is') { - Bugzilla::User::match_field({ 'commenter_is' => {'type' => 'single'} }); - my $user = Bugzilla::User->new({ name => $input->{'commenter_is'} }) - || ThrowUserError('invalid_object', { object => 'User', value => $input->{'commenter_is'} }); - $filter_commenter_id = $user ? $user->id : 0; - } + my @component_ids; + if ($input->{'component'} ne '') { + my $ra_components + = ref($input->{'component'}) + ? $input->{'component'} + : [$input->{'component'}]; + foreach my $component_name (@$ra_components) { + my $component + = Bugzilla::Component->new({name => $component_name, product => $product}) + || ThrowUserError('invalid_object', + {object => 'Component', value => $component_name}); + push @component_ids, $component->id; + } + } - my $filter_last_time; - if ($filter_last) { - if ($filter_last_period eq 'is') { - $filter_last_period = -1; - $filter_last_time = str2time($input->{'last_is'} . " 00:00:00") || 0; - } else { - detaint_natural($filter_last_period); - $filter_last_period = 14 if $filter_last_period < 14; - } - } + # determine which comment filters to run + + my $filter_commenter = $input->{'filter_commenter'}; + my $filter_commenter_on = $input->{'commenter'}; + my $filter_last = $input->{'filter_last'}; + my $filter_last_period = $input->{'last'}; + + if (!$filter_commenter || $filter_last) { + $filter_commenter = '1'; + $filter_commenter_on = 'reporter'; + } + + my $filter_commenter_id; + if ($filter_commenter && $filter_commenter_on eq 'is') { + Bugzilla::User::match_field({'commenter_is' => {'type' => 'single'}}); + my $user + = Bugzilla::User->new({name => $input->{'commenter_is'}}) + || ThrowUserError('invalid_object', + {object => 'User', value => $input->{'commenter_is'}}); + $filter_commenter_id = $user ? $user->id : 0; + } - # form sql queries + my $filter_last_time; + if ($filter_last) { + if ($filter_last_period eq 'is') { + $filter_last_period = -1; + $filter_last_time = str2time($input->{'last_is'} . " 00:00:00") || 0; + } + else { + detaint_natural($filter_last_period); + $filter_last_period = 14 if $filter_last_period < 14; + } + } + + # form sql queries - my $now = (time); - my $bugs_sql = " + my $now = (time); + my $bugs_sql = " SELECT bug_id, short_desc, reporter, creation_ts FROM bugs WHERE product_id = ? AND bug_status = 'UNCONFIRMED'"; - if (@component_ids) { - $bugs_sql .= " AND component_id IN (" . join(',', @component_ids) . ")"; - } - $bugs_sql .= " + if (@component_ids) { + $bugs_sql .= " AND component_id IN (" . join(',', @component_ids) . ")"; + } + $bugs_sql .= " ORDER BY creation_ts "; - my $comment_count_sql = " + my $comment_count_sql = " SELECT COUNT(*) FROM longdescs WHERE bug_id = ? "; - my $comment_sql = " + my $comment_sql = " SELECT who, bug_when, type, thetext, extra_data FROM longdescs WHERE bug_id = ? "; - if (!Bugzilla->user->is_insider) { - $comment_sql .= " AND isprivate = 0 "; - } - $comment_sql .= " + if (!Bugzilla->user->is_insider) { + $comment_sql .= " AND isprivate = 0 "; + } + $comment_sql .= " ORDER BY bug_when DESC LIMIT 1 "; - my $attach_sql = " + my $attach_sql = " SELECT description, isprivate FROM attachments WHERE attach_id = ? "; - # work on an initial list of bugs + # work on an initial list of bugs - my $list = $dbh->selectall_arrayref($bugs_sql, undef, $product->id); - my @bugs; + my $list = $dbh->selectall_arrayref($bugs_sql, undef, $product->id); + my @bugs; - # this can be slow to process, resulting in 'service unavailable' errors from zeus - # so if too many bugs are returned, throw an error + # this can be slow to process, resulting in 'service unavailable' errors from zeus + # so if too many bugs are returned, throw an error - if (scalar(@$list) > MAX_NUMBER_BUGS) { - ThrowUserError('report_too_many_bugs'); - } + if (scalar(@$list) > MAX_NUMBER_BUGS) { + ThrowUserError('report_too_many_bugs'); + } - foreach my $entry (@$list) { - my ($bug_id, $summary, $reporter_id, $creation_ts) = @$entry; - - next unless $user->can_see_bug($bug_id); - - # get last comment information - - my ($comment_count) = $dbh->selectrow_array($comment_count_sql, undef, $bug_id); - my ($commenter_id, $comment_ts, $type, $comment, $extra) - = $dbh->selectrow_array($comment_sql, undef, $bug_id); - my $commenter = 0; - - # apply selected filters - - if ($filter_commenter) { - next if $comment_count <= 1; - - if ($filter_commenter_on eq 'reporter') { - next if $commenter_id != $reporter_id; - - } elsif ($filter_commenter_on eq 'noconfirm') { - $commenter = Bugzilla::User->new({ id => $commenter_id, cache => 1 }); - next if $commenter_id != $reporter_id - || $commenter->in_group('canconfirm'); - - } elsif ($filter_commenter_on eq 'is') { - next if $commenter_id != $filter_commenter_id; - } - } else { - $input->{'commenter'} = ''; - $input->{'commenter_is'} = ''; - } - - if ($filter_last) { - my $comment_time = str2time($comment_ts) - or next; - if ($filter_last_period == -1) { - next if $comment_time >= $filter_last_time; - } else { - next if $now - $comment_time <= 60 * 60 * 24 * $filter_last_period; - } - } else { - $input->{'last'} = ''; - $input->{'last_is'} = ''; - } - - # get data for attachment comments - - if ($comment eq '' && $type == CMT_ATTACHMENT_CREATED) { - my ($description, $is_private) = $dbh->selectrow_array($attach_sql, undef, $extra); - next if $is_private && !Bugzilla->user->is_insider; - $comment = "(Attachment) " . $description; - } - - # truncate long comments - - if (length($comment) > 80) { - $comment = substr($comment, 0, 80) . '...'; - } - - # build bug hash for template - - my $bug = {}; - $bug->{id} = $bug_id; - $bug->{summary} = $summary; - $bug->{reporter} = Bugzilla::User->new({ id => $reporter_id, cache => 1 }); - $bug->{creation_ts} = $creation_ts; - $bug->{commenter} = $commenter || Bugzilla::User->new({ id => $commenter_id, cache => 1 }); - $bug->{comment_ts} = $comment_ts; - $bug->{comment} = $comment; - $bug->{comment_count} = $comment_count; - push @bugs, $bug; - } + foreach my $entry (@$list) { + my ($bug_id, $summary, $reporter_id, $creation_ts) = @$entry; - @bugs = sort { $b->{comment_ts} cmp $a->{comment_ts} } @bugs; + next unless $user->can_see_bug($bug_id); - $vars->{bugs} = \@bugs; - } else { - $input->{action} = ''; - } + # get last comment information - if (!$input->{filter_commenter} && !$input->{filter_last}) { - $input->{filter_commenter} = 1; - } + my ($comment_count) = $dbh->selectrow_array($comment_count_sql, undef, $bug_id); + my ($commenter_id, $comment_ts, $type, $comment, $extra) + = $dbh->selectrow_array($comment_sql, undef, $bug_id); + my $commenter = 0; - $vars->{'input'} = $input; -} + # apply selected filters -sub owners { - my ($vars, $filter) = @_; - my $dbh = Bugzilla->dbh; - my $input = Bugzilla->input_params; - my $user = Bugzilla->user; + if ($filter_commenter) { + next if $comment_count <= 1; - Bugzilla::User::match_field({ 'owner' => {'type' => 'multi'} }); + if ($filter_commenter_on eq 'reporter') { + next if $commenter_id != $reporter_id; - my @products; - if (!$input->{product} && $input->{owner}) { - @products = @{ $user->get_selectable_products }; - } - else { - my @product_names = $input->{product} ? ($input->{product}) : DEFAULT_OWNER_PRODUCTS; - foreach my $name (@product_names) { - push(@products, Bugzilla::Product->check({ name => $name })); } - } + elsif ($filter_commenter_on eq 'noconfirm') { + $commenter = Bugzilla::User->new({id => $commenter_id, cache => 1}); + next if $commenter_id != $reporter_id || $commenter->in_group('canconfirm'); - my @component_ids; - if (@products == 1 && $input->{'component'}) { - my $ra_components = ref($input->{'component'}) - ? $input->{'component'} - : [ $input->{'component'} ]; - foreach my $component_name (@$ra_components) { - my $component = Bugzilla::Component->check({ name => $component_name, product => $products[0] }); - push @component_ids, $component->id; } + elsif ($filter_commenter_on eq 'is') { + next if $commenter_id != $filter_commenter_id; + } + } + else { + $input->{'commenter'} = ''; + $input->{'commenter_is'} = ''; + } + + if ($filter_last) { + my $comment_time = str2time($comment_ts) or next; + if ($filter_last_period == -1) { + next if $comment_time >= $filter_last_time; + } + else { + next if $now - $comment_time <= 60 * 60 * 24 * $filter_last_period; + } + } + else { + $input->{'last'} = ''; + $input->{'last_is'} = ''; + } + + # get data for attachment comments + + if ($comment eq '' && $type == CMT_ATTACHMENT_CREATED) { + my ($description, $is_private) + = $dbh->selectrow_array($attach_sql, undef, $extra); + next if $is_private && !Bugzilla->user->is_insider; + $comment = "(Attachment) " . $description; + } + + # truncate long comments + + if (length($comment) > 80) { + $comment = substr($comment, 0, 80) . '...'; + } + + # build bug hash for template + + my $bug = {}; + $bug->{id} = $bug_id; + $bug->{summary} = $summary; + $bug->{reporter} = Bugzilla::User->new({id => $reporter_id, cache => 1}); + $bug->{creation_ts} = $creation_ts; + $bug->{commenter} + = $commenter || Bugzilla::User->new({id => $commenter_id, cache => 1}); + $bug->{comment_ts} = $comment_ts; + $bug->{comment} = $comment; + $bug->{comment_count} = $comment_count; + push @bugs, $bug; } - my @owner_names = split(/[,;]+/, $input->{owner}) if $input->{owner}; - my @owner_ids; - foreach my $name (@owner_names) { - $name = trim($name); - next unless $name; - push(@owner_ids, login_to_id($name, THROW_ERROR)); - } + @bugs = sort { $b->{comment_ts} cmp $a->{comment_ts} } @bugs; - my $sql = "SELECT products.name, components.name, components.id, components.triage_owner_id - FROM components JOIN products ON components.product_id = products.id - WHERE products.id IN (" . join(',', map { $_->id } @products) . ")"; - if (@component_ids) { - $sql .= " AND components.id IN (" . join(',', @component_ids) . ")"; - } - if (@owner_ids) { - $sql .= " AND components.triage_owner_id IN (" . join(',', @owner_ids) . ")"; - } - $sql .= " ORDER BY products.name, components.name"; + $vars->{bugs} = \@bugs; + } + else { + $input->{action} = ''; + } - my $rows = $dbh->selectall_arrayref($sql); + if (!$input->{filter_commenter} && !$input->{filter_last}) { + $input->{filter_commenter} = 1; + } - my $bug_count_sth = $dbh->prepare(" + $vars->{'input'} = $input; +} + +sub owners { + my ($vars, $filter) = @_; + my $dbh = Bugzilla->dbh; + my $input = Bugzilla->input_params; + my $user = Bugzilla->user; + + Bugzilla::User::match_field({'owner' => {'type' => 'multi'}}); + + my @products; + if (!$input->{product} && $input->{owner}) { + @products = @{$user->get_selectable_products}; + } + else { + my @product_names + = $input->{product} ? ($input->{product}) : DEFAULT_OWNER_PRODUCTS; + foreach my $name (@product_names) { + push(@products, Bugzilla::Product->check({name => $name})); + } + } + + my @component_ids; + if (@products == 1 && $input->{'component'}) { + my $ra_components + = ref($input->{'component'}) + ? $input->{'component'} + : [$input->{'component'}]; + foreach my $component_name (@$ra_components) { + my $component = Bugzilla::Component->check( + {name => $component_name, product => $products[0]}); + push @component_ids, $component->id; + } + } + + my @owner_names = split(/[,;]+/, $input->{owner}) if $input->{owner}; + my @owner_ids; + foreach my $name (@owner_names) { + $name = trim($name); + next unless $name; + push(@owner_ids, login_to_id($name, THROW_ERROR)); + } + + my $sql + = "SELECT products.name, components.name, components.id, components.triage_owner_id + FROM components JOIN products ON components.product_id = products.id + WHERE products.id IN (" + . join(',', map { $_->id } @products) . ")"; + if (@component_ids) { + $sql .= " AND components.id IN (" . join(',', @component_ids) . ")"; + } + if (@owner_ids) { + $sql .= " AND components.triage_owner_id IN (" . join(',', @owner_ids) . ")"; + } + $sql .= " ORDER BY products.name, components.name"; + + my $rows = $dbh->selectall_arrayref($sql); + + my $bug_count_sth = $dbh->prepare(" SELECT COUNT(bugs.bug_id) FROM bugs INNER JOIN components AS map_component ON bugs.component_id = map_component.id INNER JOIN bug_status AS map_bug_status ON bugs.bug_status = map_bug_status.value @@ -296,55 +314,57 @@ sub owners { WHERE bugs_1.bug_id = bugs.bug_id AND CONCAT(flagtypes_1.name, flags_1.status) = 'needinfo?'))) AND bugs.component_id = ?"); - my @results; - foreach my $row (@$rows) { - my ($product_name, $component_name, $component_id, $triage_owner_id) = @$row; - my $triage_owner = $triage_owner_id - ? Bugzilla::User->new({ id => $triage_owner_id, cache => 1 }) - : ""; - my $data = { - product => $product_name, - component => $component_name, - owner => $triage_owner, - }; - $data->{buglist_url} = 'priority=--&resolution=---&f1=creation_ts&o1=greaterthaneq&v1=2016-06-01'. - '&f2=flagtypes.name&o2=notequals&v2=needinfo%3F'; - if ($triage_owner) { - $data->{buglist_url} .= '&f3=triage_owner&o3=equals&v3=' . url_quote($triage_owner->login); - } - $bug_count_sth->execute($component_id); - ($data->{bug_count}) = $bug_count_sth->fetchrow_array(); - push @results, $data; + my @results; + foreach my $row (@$rows) { + my ($product_name, $component_name, $component_id, $triage_owner_id) = @$row; + my $triage_owner + = $triage_owner_id + ? Bugzilla::User->new({id => $triage_owner_id, cache => 1}) + : ""; + my $data = { + product => $product_name, + component => $component_name, + owner => $triage_owner, + }; + $data->{buglist_url} + = 'priority=--&resolution=---&f1=creation_ts&o1=greaterthaneq&v1=2016-06-01' + . '&f2=flagtypes.name&o2=notequals&v2=needinfo%3F'; + if ($triage_owner) { + $data->{buglist_url} + .= '&f3=triage_owner&o3=equals&v3=' . url_quote($triage_owner->login); } - $vars->{results} = \@results; - - my $json_data = { products => [] }; - foreach my $product (@{ $user->get_selectable_products }) { - my $prod_data = { - name => $product->name, - components => [], - }; - foreach my $component (@{ $product->components }) { - my $selected = 0; - if ($input->{product} - && $input->{product} eq $product->name - && $input->{component}) - { - $selected = 1 if (ref $input->{component} && any { $_ eq $component->name } @{ $input->{component} }); - $selected = 1 if (!ref $input->{componet} && $input->{component} eq $component->name); - } - my $comp_data = { - name => $component->name, - selected => $selected - }; - push(@{ $prod_data->{components} }, $comp_data); - } - push(@{ $json_data->{products} }, $prod_data); + $bug_count_sth->execute($component_id); + ($data->{bug_count}) = $bug_count_sth->fetchrow_array(); + push @results, $data; + } + $vars->{results} = \@results; + + my $json_data = {products => []}; + foreach my $product (@{$user->get_selectable_products}) { + my $prod_data = {name => $product->name, components => [],}; + foreach my $component (@{$product->components}) { + my $selected = 0; + if ( $input->{product} + && $input->{product} eq $product->name + && $input->{component}) + { + $selected = 1 + if ( + ref $input->{component} && any { $_ eq $component->name } + @{$input->{component}} + ); + $selected = 1 + if (!ref $input->{componet} && $input->{component} eq $component->name); + } + my $comp_data = {name => $component->name, selected => $selected}; + push(@{$prod_data->{components}}, $comp_data); } + push(@{$json_data->{products}}, $prod_data); + } - $vars->{product} = $input->{product}; - $vars->{owner} = $input->{owner}; - $vars->{json_data} = encode_json($json_data); + $vars->{product} = $input->{product}; + $vars->{owner} = $input->{owner}; + $vars->{json_data} = encode_json($json_data); } 1; diff --git a/extensions/BMO/lib/Reports/UserActivity.pm b/extensions/BMO/lib/Reports/UserActivity.pm index 8dfe0c5cd..3be6f74c9 100644 --- a/extensions/BMO/lib/Reports/UserActivity.pm +++ b/extensions/BMO/lib/Reports/UserActivity.pm @@ -18,104 +18,104 @@ use Bugzilla::Util qw(trim); use DateTime; sub report { - my ($vars) = @_; - my $dbh = Bugzilla->dbh; - my $input = Bugzilla->input_params; - - my @who = (); - my $from = trim($input->{'from'} || ''); - my $to = trim($input->{'to'} || ''); - my $action = $input->{'action'} || ''; - - # fix non-breaking hyphens - $from =~ s/\N{U+2011}/-/g; - $to =~ s/\N{U+2011}/-/g; - - if ($from eq '') { - my $dt = DateTime->now()->subtract('weeks' => 1); - $from = $dt->ymd('-'); + my ($vars) = @_; + my $dbh = Bugzilla->dbh; + my $input = Bugzilla->input_params; + + my @who = (); + my $from = trim($input->{'from'} || ''); + my $to = trim($input->{'to'} || ''); + my $action = $input->{'action'} || ''; + + # fix non-breaking hyphens + $from =~ s/\N{U+2011}/-/g; + $to =~ s/\N{U+2011}/-/g; + + if ($from eq '') { + my $dt = DateTime->now()->subtract('weeks' => 1); + $from = $dt->ymd('-'); + } + if ($to eq '') { + my $dt = DateTime->now(); + $to = $dt->ymd('-'); + } + + if ($action eq 'run') { + if (!exists $input->{'who'} || $input->{'who'} eq '') { + ThrowUserError('user_activity_missing_username'); } - if ($to eq '') { - my $dt = DateTime->now(); - $to = $dt->ymd('-'); - } - - if ($action eq 'run') { - if (!exists $input->{'who'} || $input->{'who'} eq '') { - ThrowUserError('user_activity_missing_username'); - } - Bugzilla::User::match_field({ 'who' => {'type' => 'multi'} }); + Bugzilla::User::match_field({'who' => {'type' => 'multi'}}); - my $from_dt = string_to_datetime($from); - $from = $from_dt->ymd(); + my $from_dt = string_to_datetime($from); + $from = $from_dt->ymd(); - my $to_dt = string_to_datetime($to); - $to = $to_dt->ymd(); + my $to_dt = string_to_datetime($to); + $to = $to_dt->ymd(); - my ($activity_joins, $activity_where) = ('', ''); - my ($attachments_joins, $attachments_where) = ('', ''); - my ($tags_activity_joins, $tags_activity_where) = ('', ''); - if (Bugzilla->params->{"insidergroup"} - && !Bugzilla->user->in_group(Bugzilla->params->{'insidergroup'})) - { - $activity_joins = "LEFT JOIN attachments + my ($activity_joins, $activity_where) = ('', ''); + my ($attachments_joins, $attachments_where) = ('', ''); + my ($tags_activity_joins, $tags_activity_where) = ('', ''); + if (Bugzilla->params->{"insidergroup"} + && !Bugzilla->user->in_group(Bugzilla->params->{'insidergroup'})) + { + $activity_joins = "LEFT JOIN attachments ON attachments.attach_id = bugs_activity.attach_id"; - $activity_where = "AND COALESCE(attachments.isprivate, 0) = 0"; - $attachments_where = $activity_where; + $activity_where = "AND COALESCE(attachments.isprivate, 0) = 0"; + $attachments_where = $activity_where; - $tags_activity_joins = 'LEFT JOIN longdescs + $tags_activity_joins = 'LEFT JOIN longdescs ON longdescs_tags_activity.comment_id = longdescs.comment_id'; - $tags_activity_where = 'AND COALESCE(longdescs.isprivate, 0) = 0'; - } + $tags_activity_where = 'AND COALESCE(longdescs.isprivate, 0) = 0'; + } - my @who_bits; - foreach my $who ( - ref $input->{'who'} - ? @{$input->{'who'}} - : $input->{'who'} - ) { - push @who, $who; - push @who_bits, '?'; - } - my $who_bits = join(',', @who_bits); - - if (!@who) { - my $template = Bugzilla->template; - my $cgi = Bugzilla->cgi; - my $vars = {}; - $vars->{'script'} = $cgi->url(-relative => 1); - $vars->{'fields'} = {}; - $vars->{'matches'} = []; - $vars->{'matchsuccess'} = 0; - $vars->{'matchmultiple'} = 1; - print $cgi->header(); - $template->process("global/confirm-user-match.html.tmpl", $vars) - || ThrowTemplateError($template->error()); - exit; - } + my @who_bits; + foreach my $who (ref $input->{'who'} ? @{$input->{'who'}} : $input->{'who'}) { + push @who, $who; + push @who_bits, '?'; + } + my $who_bits = join(',', @who_bits); + + if (!@who) { + my $template = Bugzilla->template; + my $cgi = Bugzilla->cgi; + my $vars = {}; + $vars->{'script'} = $cgi->url(-relative => 1); + $vars->{'fields'} = {}; + $vars->{'matches'} = []; + $vars->{'matchsuccess'} = 0; + $vars->{'matchmultiple'} = 1; + print $cgi->header(); + $template->process("global/confirm-user-match.html.tmpl", $vars) + || ThrowTemplateError($template->error()); + exit; + } - $from_dt = $from_dt->ymd() . ' 00:00:00'; - $to_dt = $to_dt->ymd() . ' 23:59:59'; - my @params; - for (1..5) { - push @params, @who; - push @params, ($from_dt, $to_dt); - } + $from_dt = $from_dt->ymd() . ' 00:00:00'; + $to_dt = $to_dt->ymd() . ' 23:59:59'; + my @params; + for (1 .. 5) { + push @params, @who; + push @params, ($from_dt, $to_dt); + } - my $order = ($input->{'group'} && $input->{'group'} eq 'bug') - ? 'bug_id, bug_when' : 'bug_when'; + my $order + = ($input->{'group'} && $input->{'group'} eq 'bug') + ? 'bug_id, bug_when' + : 'bug_when'; - my $comment_filter = ''; - if (!Bugzilla->user->is_insider) { - $comment_filter = 'AND longdescs.isprivate = 0'; - } + my $comment_filter = ''; + if (!Bugzilla->user->is_insider) { + $comment_filter = 'AND longdescs.isprivate = 0'; + } - my $query = " + my $query = " SELECT fielddefs.name, bugs_activity.bug_id, bugs_activity.attach_id, - ".$dbh->sql_date_format('bugs_activity.bug_when', '%Y.%m.%d %H:%i:%s')." AS ts, + " + . $dbh->sql_date_format('bugs_activity.bug_when', '%Y.%m.%d %H:%i:%s') + . " AS ts, bugs_activity.removed, bugs_activity.added, profiles.login_name, @@ -138,8 +138,10 @@ sub report { 'comment_tag' AS name, longdescs_tags_activity.bug_id, NULL as attach_id, - ".$dbh->sql_date_format('longdescs_tags_activity.bug_when', - '%Y.%m.%d %H:%i:%s') . " AS bug_when, + " + . $dbh->sql_date_format('longdescs_tags_activity.bug_when', + '%Y.%m.%d %H:%i:%s') + . " AS bug_when, longdescs_tags_activity.removed, longdescs_tags_activity.added, profiles.login_name, @@ -160,7 +162,8 @@ sub report { 'bug_id' AS name, bugs.bug_id, NULL AS attach_id, - ".$dbh->sql_date_format('bugs.creation_ts', '%Y.%m.%d %H:%i:%s')." AS ts, + " + . $dbh->sql_date_format('bugs.creation_ts', '%Y.%m.%d %H:%i:%s') . " AS ts, '(new bug)' AS removed, bugs.short_desc AS added, profiles.login_name, @@ -199,7 +202,9 @@ sub report { 'attachments.description' AS name, attachments.bug_id, attachments.attach_id, - ".$dbh->sql_date_format('attachments.creation_ts', '%Y.%m.%d %H:%i:%s')." AS ts, + " + . $dbh->sql_date_format('attachments.creation_ts', '%Y.%m.%d %H:%i:%s') + . " AS ts, '(new attachment)' AS removed, attachments.description AS added, profiles.login_name, @@ -215,119 +220,118 @@ sub report { ORDER BY $order "; - my $list = $dbh->selectall_arrayref($query, undef, @params); + my $list = $dbh->selectall_arrayref($query, undef, @params); - if ($input->{debug}) { - while (my $param = shift @params) { - $query =~ s/\?/$dbh->quote($param)/e; - } - $vars->{debug_sql} = $query; + if ($input->{debug}) { + while (my $param = shift @params) { + $query =~ s/\?/$dbh->quote($param)/e; + } + $vars->{debug_sql} = $query; + } + + my @operations; + my $operation = {}; + my $changes = []; + my $incomplete_data = 0; + my %bug_ids; + + foreach my $entry (@$list) { + my ($fieldname, $bugid, $attachid, $when, $removed, $added, $who, $comment_id) + = @$entry; + my %change; + my $activity_visible = 1; + + next unless Bugzilla->user->can_see_bug($bugid); + + # check if the user should see this field's activity + if ( $fieldname eq 'remaining_time' + || $fieldname eq 'estimated_time' + || $fieldname eq 'work_time' + || $fieldname eq 'deadline') + { + $activity_visible = Bugzilla->user->is_timetracker; + } + elsif ($fieldname eq 'longdescs.isprivate' + && !Bugzilla->user->is_insider + && $added) + { + $activity_visible = 0; + } + else { + $activity_visible = 1; + } + + if ($activity_visible) { + + # Check for the results of an old Bugzilla data corruption bug + if ( ($added eq '?' && $removed eq '?') + || ($added =~ /^\? / || $removed =~ /^\? /)) + { + $incomplete_data = 1; } - my @operations; - my $operation = {}; - my $changes = []; - my $incomplete_data = 0; - my %bug_ids; - - foreach my $entry (@$list) { - my ($fieldname, $bugid, $attachid, $when, $removed, $added, $who, - $comment_id) = @$entry; - my %change; - my $activity_visible = 1; - - next unless Bugzilla->user->can_see_bug($bugid); - - # check if the user should see this field's activity - if ($fieldname eq 'remaining_time' - || $fieldname eq 'estimated_time' - || $fieldname eq 'work_time' - || $fieldname eq 'deadline') - { - $activity_visible = Bugzilla->user->is_timetracker; - } - elsif ($fieldname eq 'longdescs.isprivate' - && !Bugzilla->user->is_insider - && $added) - { - $activity_visible = 0; - } - else { - $activity_visible = 1; - } - - if ($activity_visible) { - # Check for the results of an old Bugzilla data corruption bug - if (($added eq '?' && $removed eq '?') - || ($added =~ /^\? / || $removed =~ /^\? /)) { - $incomplete_data = 1; - } - - # Start a new changeset if required (depends on the grouping type) - my $is_new_changeset; - if ($order eq 'bug_when') { - $is_new_changeset = - $operation->{'who'} && - ( - $who ne $operation->{'who'} - || $when ne $operation->{'when'} - || $bugid != $operation->{'bug'} - ); - } else { - $is_new_changeset = - $operation->{'bug'} && - $bugid != $operation->{'bug'}; - } - if ($is_new_changeset) { - $operation->{'changes'} = $changes; - push (@operations, $operation); - $operation = {}; - $changes = []; - } - - $bug_ids{$bugid} = 1; - - $operation->{'bug'} = $bugid; - $operation->{'who'} = $who; - $operation->{'when'} = $when; - - $change{'fieldname'} = $fieldname; - $change{'attachid'} = $attachid; - $change{'removed'} = $removed; - $change{'added'} = $added; - $change{'when'} = $when; - - if ($comment_id) { - $change{'comment'} = Bugzilla::Comment->new($comment_id); - next if $change{'comment'}->count == 0; - } - - if ($attachid) { - $change{'attach'} = Bugzilla::Attachment->new($attachid); - } - - push (@$changes, \%change); - } + # Start a new changeset if required (depends on the grouping type) + my $is_new_changeset; + if ($order eq 'bug_when') { + $is_new_changeset + = $operation->{'who'} + && ($who ne $operation->{'who'} + || $when ne $operation->{'when'} + || $bugid != $operation->{'bug'}); + } + else { + $is_new_changeset = $operation->{'bug'} && $bugid != $operation->{'bug'}; } + if ($is_new_changeset) { + $operation->{'changes'} = $changes; + push(@operations, $operation); + $operation = {}; + $changes = []; + } + + $bug_ids{$bugid} = 1; + + $operation->{'bug'} = $bugid; + $operation->{'who'} = $who; + $operation->{'when'} = $when; - if ($operation->{'who'}) { - $operation->{'changes'} = $changes; - push (@operations, $operation); + $change{'fieldname'} = $fieldname; + $change{'attachid'} = $attachid; + $change{'removed'} = $removed; + $change{'added'} = $added; + $change{'when'} = $when; + + if ($comment_id) { + $change{'comment'} = Bugzilla::Comment->new($comment_id); + next if $change{'comment'}->count == 0; + } + + if ($attachid) { + $change{'attach'} = Bugzilla::Attachment->new($attachid); } - $vars->{'incomplete_data'} = $incomplete_data; - $vars->{'operations'} = \@operations; + push(@$changes, \%change); + } + } - my @bug_ids = sort { $a <=> $b } keys %bug_ids; - $vars->{'bug_ids'} = \@bug_ids; + if ($operation->{'who'}) { + $operation->{'changes'} = $changes; + push(@operations, $operation); } - $vars->{'action'} = $action; - $vars->{'who'} = join(',', @who); - $vars->{'who_count'} = scalar @who; - $vars->{'from'} = $from; - $vars->{'to'} = $to; - $vars->{'group'} = $input->{'group'}; + $vars->{'incomplete_data'} = $incomplete_data; + $vars->{'operations'} = \@operations; + + my @bug_ids = sort { $a <=> $b } keys %bug_ids; + $vars->{'bug_ids'} = \@bug_ids; + } + + $vars->{'action'} = $action; + $vars->{'who'} = join(',', @who); + $vars->{'who_count'} = scalar @who; + $vars->{'from'} = $from; + $vars->{'to'} = $to; + $vars->{'group'} = $input->{'group'}; } 1; diff --git a/extensions/BMO/lib/Util.pm b/extensions/BMO/lib/Util.pm index dc9c904f9..4d376aecb 100644 --- a/extensions/BMO/lib/Util.pm +++ b/extensions/BMO/lib/Util.pm @@ -19,74 +19,77 @@ use DateTime; use base qw(Exporter); our @EXPORT = qw( string_to_datetime - time_to_datetime - parse_date - is_active_status_field ); + time_to_datetime + parse_date + is_active_status_field ); sub string_to_datetime { - my $input = shift; - my $time = parse_date($input) - or ThrowUserError('report_invalid_date', { date => $input }); - return time_to_datetime($time); + my $input = shift; + my $time = parse_date($input) + or ThrowUserError('report_invalid_date', {date => $input}); + return time_to_datetime($time); } sub time_to_datetime { - my $time = shift; - return DateTime->from_epoch(epoch => $time) - ->set_time_zone('local') - ->truncate(to => 'day'); + my $time = shift; + return DateTime->from_epoch(epoch => $time)->set_time_zone('local') + ->truncate(to => 'day'); } sub parse_date { - my ($str) = @_; - if ($str =~ /^(-|\+)?(\d+)([hHdDwWmMyY])$/) { - # relative date - my ($sign, $amount, $unit, $date) = ($1, $2, lc $3, time); - my ($sec, $min, $hour, $mday, $month, $year, $wday) = localtime($date); - $amount = -$amount if $sign && $sign eq '+'; - if ($unit eq 'w') { - # convert weeks to days - $amount = 7 * $amount + $wday; - $unit = 'd'; - } - if ($unit eq 'd') { - $date -= $sec + 60 * $min + 3600 * $hour + 24 * 3600 * $amount; - return $date; - } - elsif ($unit eq 'y') { - return str2time(sprintf("%4d-01-01 00:00:00", $year + 1900 - $amount)); - } - elsif ($unit eq 'm') { - $month -= $amount; - while ($month < 0) { $year--; $month += 12; } - return str2time(sprintf("%4d-%02d-01 00:00:00", $year + 1900, $month + 1)); - } - elsif ($unit eq 'h') { - # Special case 0h for 'beginning of this hour' - if ($amount == 0) { - $date -= $sec + 60 * $min; - } else { - $date -= 3600 * $amount; - } - return $date; - } - return undef; + my ($str) = @_; + if ($str =~ /^(-|\+)?(\d+)([hHdDwWmMyY])$/) { + + # relative date + my ($sign, $amount, $unit, $date) = ($1, $2, lc $3, time); + my ($sec, $min, $hour, $mday, $month, $year, $wday) = localtime($date); + $amount = -$amount if $sign && $sign eq '+'; + if ($unit eq 'w') { + + # convert weeks to days + $amount = 7 * $amount + $wday; + $unit = 'd'; + } + if ($unit eq 'd') { + $date -= $sec + 60 * $min + 3600 * $hour + 24 * 3600 * $amount; + return $date; + } + elsif ($unit eq 'y') { + return str2time(sprintf("%4d-01-01 00:00:00", $year + 1900 - $amount)); } - return str2time($str); + elsif ($unit eq 'm') { + $month -= $amount; + while ($month < 0) { $year--; $month += 12; } + return str2time(sprintf("%4d-%02d-01 00:00:00", $year + 1900, $month + 1)); + } + elsif ($unit eq 'h') { + + # Special case 0h for 'beginning of this hour' + if ($amount == 0) { + $date -= $sec + 60 * $min; + } + else { + $date -= 3600 * $amount; + } + return $date; + } + return undef; + } + return str2time($str); } sub is_active_status_field { - my ($field) = @_; + my ($field) = @_; - if ($field->type == FIELD_TYPE_EXTENSION - && $field->isa('Bugzilla::Extension::TrackingFlags::Flag') - && $field->flag_type eq 'tracking' - && $field->name =~ /_status_/ - ) { - return $field->is_active; - } + if ( $field->type == FIELD_TYPE_EXTENSION + && $field->isa('Bugzilla::Extension::TrackingFlags::Flag') + && $field->flag_type eq 'tracking' + && $field->name =~ /_status_/) + { + return $field->is_active; + } - return 0; + return 0; } 1; diff --git a/extensions/BMO/lib/WebService.pm b/extensions/BMO/lib/WebService.pm index 327c8563f..4c9187254 100644 --- a/extensions/BMO/lib/WebService.pm +++ b/extensions/BMO/lib/WebService.pm @@ -32,26 +32,26 @@ use Bugzilla::WebService::Util qw(validate); use Bugzilla::Field; use constant PUBLIC_METHODS => qw( - getBugsConfirmer - getBugsVerifier + getBugsConfirmer + getBugsVerifier ); sub getBugsConfirmer { - my ($self, $params) = validate(@_, 'names'); - my $dbh = Bugzilla->dbh; + my ($self, $params) = validate(@_, 'names'); + my $dbh = Bugzilla->dbh; - defined($params->{names}) - || ThrowCodeError('params_required', - { function => 'BMO.getBugsConfirmer', params => ['names'] }); + defined($params->{names}) + || ThrowCodeError('params_required', + {function => 'BMO.getBugsConfirmer', params => ['names']}); - my @user_objects = map { Bugzilla::User->check($_) } @{ $params->{names} }; + my @user_objects = map { Bugzilla::User->check($_) } @{$params->{names}}; - # start filtering to remove duplicate user ids - @user_objects = values %{{ map { $_->id => $_ } @user_objects }}; + # start filtering to remove duplicate user ids + @user_objects = values %{{map { $_->id => $_ } @user_objects}}; - my $fieldid = get_field_id('bug_status'); + my $fieldid = get_field_id('bug_status'); - my $query = "SELECT DISTINCT bugs_activity.bug_id + my $query = "SELECT DISTINCT bugs_activity.bug_id FROM bugs_activity LEFT JOIN bug_group_map ON bugs_activity.bug_id = bug_group_map.bug_id @@ -62,31 +62,31 @@ sub getBugsConfirmer { AND bug_group_map.bug_id IS NULL ORDER BY bugs_activity.bug_id"; - my %users; - foreach my $user (@user_objects) { - my $bugs = $dbh->selectcol_arrayref($query, undef, $fieldid, $user->id); - $users{$user->login} = $bugs; - } + my %users; + foreach my $user (@user_objects) { + my $bugs = $dbh->selectcol_arrayref($query, undef, $fieldid, $user->id); + $users{$user->login} = $bugs; + } - return \%users; + return \%users; } sub getBugsVerifier { - my ($self, $params) = validate(@_, 'names'); - my $dbh = Bugzilla->dbh; + my ($self, $params) = validate(@_, 'names'); + my $dbh = Bugzilla->dbh; - defined($params->{names}) - || ThrowCodeError('params_required', - { function => 'BMO.getBugsVerifier', params => ['names'] }); + defined($params->{names}) + || ThrowCodeError('params_required', + {function => 'BMO.getBugsVerifier', params => ['names']}); - my @user_objects = map { Bugzilla::User->check($_) } @{ $params->{names} }; + my @user_objects = map { Bugzilla::User->check($_) } @{$params->{names}}; - # start filtering to remove duplicate user ids - @user_objects = values %{{ map { $_->id => $_ } @user_objects }}; + # start filtering to remove duplicate user ids + @user_objects = values %{{map { $_->id => $_ } @user_objects}}; - my $fieldid = get_field_id('bug_status'); + my $fieldid = get_field_id('bug_status'); - my $query = "SELECT DISTINCT bugs_activity.bug_id + my $query = "SELECT DISTINCT bugs_activity.bug_id FROM bugs_activity LEFT JOIN bug_group_map ON bugs_activity.bug_id = bug_group_map.bug_id @@ -97,13 +97,13 @@ sub getBugsVerifier { AND bug_group_map.bug_id IS NULL ORDER BY bugs_activity.bug_id"; - my %users; - foreach my $user (@user_objects) { - my $bugs = $dbh->selectcol_arrayref($query, undef, $fieldid, $user->id); - $users{$user->login} = $bugs; - } + my %users; + foreach my $user (@user_objects) { + my $bugs = $dbh->selectcol_arrayref($query, undef, $fieldid, $user->id); + $users{$user->login} = $bugs; + } - return \%users; + return \%users; } 1; diff --git a/extensions/BMO/t/bounty_attachment.t b/extensions/BMO/t/bounty_attachment.t index 6e596eeba..61552c074 100644 --- a/extensions/BMO/t/bounty_attachment.t +++ b/extensions/BMO/t/bounty_attachment.t @@ -13,46 +13,69 @@ use Test::More; use Bugzilla; BEGIN { Bugzilla->extensions } -my $class = 'Bugzilla::Extension::BMO'; +my $class = 'Bugzilla::Extension::BMO'; my $parse = $class->can('parse_bounty_attachment_description'); my $format = $class->can('format_bounty_attachment_description'); ok($parse, "got the function"); my $bughunter = $parse->('bughunter@hacker.org, , 2014-06-25, , ,false'); -is_deeply({ reporter_email => 'bughunter@hacker.org', - amount_paid => '', - reported_date => '2014-06-25', - fixed_date => '', - awarded_date => '', - publish => 0, - credit => []}, $bughunter); - -my $hfli = $parse->('hfli@fortinet.com, 1000, 2010-07-16, 2010-08-04, 2011-06-15, true, Fortiguard Labs'); -is_deeply({ reporter_email => 'hfli@fortinet.com', - amount_paid => '1000', - reported_date => '2010-07-16', - fixed_date => '2010-08-04', - awarded_date => '2011-06-15', - publish => 1, - credit => ['Fortiguard Labs']}, $hfli); - -is('batman@justiceleague.america,1000,2015-01-01,2015-02-02,2015-03-03,true,JLA,Wayne Industries,Test', - $format->({ reporter_email => 'batman@justiceleague.america', - amount_paid => 1000, - reported_date => '2015-01-01', - fixed_date => '2015-02-02', - awarded_date => '2015-03-03', - publish => 1, - credit => ['JLA', 'Wayne Industries', 'Test'] })); - -my $dylan = $parse->('dylan@hardison.net,2,2014-09-23,2014-09-24,2014-09-25,true,Foo bar,Bork,'); -is_deeply({ reporter_email => 'dylan@hardison.net', - amount_paid => 2, - reported_date => '2014-09-23', - fixed_date => '2014-09-24', - awarded_date => '2014-09-25', - publish => 1, - credit => ['Foo bar', 'Bork']}, $dylan); +is_deeply( + { + reporter_email => 'bughunter@hacker.org', + amount_paid => '', + reported_date => '2014-06-25', + fixed_date => '', + awarded_date => '', + publish => 0, + credit => [] + }, + $bughunter +); + +my $hfli + = $parse->( + 'hfli@fortinet.com, 1000, 2010-07-16, 2010-08-04, 2011-06-15, true, Fortiguard Labs' + ); +is_deeply( + { + reporter_email => 'hfli@fortinet.com', + amount_paid => '1000', + reported_date => '2010-07-16', + fixed_date => '2010-08-04', + awarded_date => '2011-06-15', + publish => 1, + credit => ['Fortiguard Labs'] + }, + $hfli +); + +is( + 'batman@justiceleague.america,1000,2015-01-01,2015-02-02,2015-03-03,true,JLA,Wayne Industries,Test', + $format->({ + reporter_email => 'batman@justiceleague.america', + amount_paid => 1000, + reported_date => '2015-01-01', + fixed_date => '2015-02-02', + awarded_date => '2015-03-03', + publish => 1, + credit => ['JLA', 'Wayne Industries', 'Test'] + }) +); + +my $dylan = $parse->( + 'dylan@hardison.net,2,2014-09-23,2014-09-24,2014-09-25,true,Foo bar,Bork,'); +is_deeply( + { + reporter_email => 'dylan@hardison.net', + amount_paid => 2, + reported_date => '2014-09-23', + fixed_date => '2014-09-24', + awarded_date => '2014-09-25', + publish => 1, + credit => ['Foo bar', 'Bork'] + }, + $dylan +); done_testing; |