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/Extension.pm | |
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/Extension.pm')
-rw-r--r-- | extensions/BMO/Extension.pm | 4648 |
1 files changed, 2382 insertions, 2266 deletions
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; |