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/BzAPI/lib | |
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/BzAPI/lib')
-rw-r--r-- | extensions/BzAPI/lib/Constants.pm | 230 | ||||
-rw-r--r-- | extensions/BzAPI/lib/Resources/Bug.pm | 1385 | ||||
-rw-r--r-- | extensions/BzAPI/lib/Resources/Bugzilla.pm | 208 | ||||
-rw-r--r-- | extensions/BzAPI/lib/Resources/User.pm | 75 | ||||
-rw-r--r-- | extensions/BzAPI/lib/Util.pm | 652 |
5 files changed, 1270 insertions, 1280 deletions
diff --git a/extensions/BzAPI/lib/Constants.pm b/extensions/BzAPI/lib/Constants.pm index fb611aae6..f31dac7a8 100644 --- a/extensions/BzAPI/lib/Constants.pm +++ b/extensions/BzAPI/lib/Constants.pm @@ -13,142 +13,144 @@ use warnings; use base qw(Exporter); our @EXPORT = qw( - USER_FIELDS - BUG_FIELD_MAP - BOOLEAN_TYPE_MAP - ATTACHMENT_FIELD_MAP - DEFAULT_BUG_FIELDS - DEFAULT_ATTACHMENT_FIELDS + USER_FIELDS + BUG_FIELD_MAP + BOOLEAN_TYPE_MAP + ATTACHMENT_FIELD_MAP + DEFAULT_BUG_FIELDS + DEFAULT_ATTACHMENT_FIELDS - BZAPI_DOC + BZAPI_DOC ); # These are fields that are normally exported as a single value such # as the user's email. BzAPI needs to convert them to user objects # where possible. -use constant USER_FIELDS => (qw( +use constant USER_FIELDS => ( + qw( assigned_to cc creator qa_contact reporter -)); + ) +); # Convert old field names from old to new use constant BUG_FIELD_MAP => { - 'opendate' => 'creation_time', # query - 'creation_ts' => 'creation_time', - 'changeddate' => 'last_change_time', # query - 'delta_ts' => 'last_change_time', - 'bug_id' => 'id', - 'rep_platform' => 'platform', - 'bug_severity' => 'severity', - 'bug_status' => 'status', - 'short_desc' => 'summary', - 'bug_file_loc' => 'url', - 'status_whiteboard' => 'whiteboard', - 'reporter' => 'creator', - 'reporter_realname' => 'creator_realname', - 'cclist_accessible' => 'is_cc_accessible', - 'reporter_accessible' => 'is_creator_accessible', - 'everconfirmed' => 'is_confirmed', - 'dependson' => 'depends_on', - 'blocked' => 'blocks', - 'attachment' => 'attachments', - 'flag' => 'flags', - 'flagtypes.name' => 'flag', - 'bug_group' => 'group', - 'group' => 'groups', - 'longdesc' => 'comment', - 'bug_file_loc_type' => 'url_type', - 'bugidtype' => 'id_mode', - 'longdesc_type' => 'comment_type', - 'short_desc_type' => 'summary_type', - 'status_whiteboard_type' => 'whiteboard_type', - 'emailassigned_to1' => 'email1_assigned_to', - 'emailassigned_to2' => 'email2_assigned_to', - 'emailcc1' => 'email1_cc', - 'emailcc2' => 'email2_cc', - 'emailqa_contact1' => 'email1_qa_contact', - 'emailqa_contact2' => 'email2_qa_contact', - 'emailreporter1' => 'email1_creator', - 'emailreporter2' => 'email2_creator', - 'emaillongdesc1' => 'email1_comment_creator', - 'emaillongdesc2' => 'email2_comment_creator', - 'emailtype1' => 'email1_type', - 'emailtype2' => 'email2_type', - 'chfieldfrom' => 'changed_after', - 'chfieldto' => 'changed_before', - 'chfield' => 'changed_field', - 'chfieldvalue' => 'changed_field_to', - 'deadlinefrom' => 'deadline_after', - 'deadlineto' => 'deadline_before', - 'attach_data.thedata' => 'attachment.data', - 'longdescs.isprivate' => 'comment.is_private', - 'commenter' => 'comment.creator', - 'requestees.login_name' => 'flag.requestee', - 'setters.login_name' => 'flag.setter', - 'days_elapsed' => 'idle', - 'owner_idle_time' => 'assignee_idle', - 'dup_id' => 'dupe_of', - 'isopened' => 'is_open', - 'flag_type' => 'flag_types', - 'attachments.submitter' => 'attachment.attacher', - 'attachments.filename' => 'attachment.file_name', - 'attachments.description' => 'attachment.description', - 'attachments.delta_ts' => 'attachment.last_change_time', - 'attachments.isobsolete' => 'attachment.is_obsolete', - 'attachments.ispatch' => 'attachment.is_patch', - 'attachments.isprivate' => 'attachment.is_private', - 'attachments.mimetype' => 'attachment.content_type', - 'attachments.date' => 'attachment.creation_time', - 'attachments.attachid' => 'attachment.id', - 'attachments.flag' => 'attachment.flags', - 'attachments.token' => 'attachment.update_token' + 'opendate' => 'creation_time', # query + 'creation_ts' => 'creation_time', + 'changeddate' => 'last_change_time', # query + 'delta_ts' => 'last_change_time', + 'bug_id' => 'id', + 'rep_platform' => 'platform', + 'bug_severity' => 'severity', + 'bug_status' => 'status', + 'short_desc' => 'summary', + 'bug_file_loc' => 'url', + 'status_whiteboard' => 'whiteboard', + 'reporter' => 'creator', + 'reporter_realname' => 'creator_realname', + 'cclist_accessible' => 'is_cc_accessible', + 'reporter_accessible' => 'is_creator_accessible', + 'everconfirmed' => 'is_confirmed', + 'dependson' => 'depends_on', + 'blocked' => 'blocks', + 'attachment' => 'attachments', + 'flag' => 'flags', + 'flagtypes.name' => 'flag', + 'bug_group' => 'group', + 'group' => 'groups', + 'longdesc' => 'comment', + 'bug_file_loc_type' => 'url_type', + 'bugidtype' => 'id_mode', + 'longdesc_type' => 'comment_type', + 'short_desc_type' => 'summary_type', + 'status_whiteboard_type' => 'whiteboard_type', + 'emailassigned_to1' => 'email1_assigned_to', + 'emailassigned_to2' => 'email2_assigned_to', + 'emailcc1' => 'email1_cc', + 'emailcc2' => 'email2_cc', + 'emailqa_contact1' => 'email1_qa_contact', + 'emailqa_contact2' => 'email2_qa_contact', + 'emailreporter1' => 'email1_creator', + 'emailreporter2' => 'email2_creator', + 'emaillongdesc1' => 'email1_comment_creator', + 'emaillongdesc2' => 'email2_comment_creator', + 'emailtype1' => 'email1_type', + 'emailtype2' => 'email2_type', + 'chfieldfrom' => 'changed_after', + 'chfieldto' => 'changed_before', + 'chfield' => 'changed_field', + 'chfieldvalue' => 'changed_field_to', + 'deadlinefrom' => 'deadline_after', + 'deadlineto' => 'deadline_before', + 'attach_data.thedata' => 'attachment.data', + 'longdescs.isprivate' => 'comment.is_private', + 'commenter' => 'comment.creator', + 'requestees.login_name' => 'flag.requestee', + 'setters.login_name' => 'flag.setter', + 'days_elapsed' => 'idle', + 'owner_idle_time' => 'assignee_idle', + 'dup_id' => 'dupe_of', + 'isopened' => 'is_open', + 'flag_type' => 'flag_types', + 'attachments.submitter' => 'attachment.attacher', + 'attachments.filename' => 'attachment.file_name', + 'attachments.description' => 'attachment.description', + 'attachments.delta_ts' => 'attachment.last_change_time', + 'attachments.isobsolete' => 'attachment.is_obsolete', + 'attachments.ispatch' => 'attachment.is_patch', + 'attachments.isprivate' => 'attachment.is_private', + 'attachments.mimetype' => 'attachment.content_type', + 'attachments.date' => 'attachment.creation_time', + 'attachments.attachid' => 'attachment.id', + 'attachments.flag' => 'attachment.flags', + 'attachments.token' => 'attachment.update_token' }; # Convert from old boolean chart type names to new names use constant BOOLEAN_TYPE_MAP => { - 'equals' => 'equals', - 'not_equals' => 'notequals', - 'equals_any' => 'anyexact', - 'contains' => 'substring', - 'not_contains' => 'notsubstring', - 'case_contains' => 'casesubstring', - 'contains_any' => 'anywordssubstr', - 'not_contains_any' => 'nowordssubstr', - 'contains_all' => 'allwordssubstr', - 'contains_any_words' => 'anywords', - 'not_contains_any_words' => 'nowords', - 'contains_all_words' => 'allwords', - 'regex' => 'regexp', - 'not_regex' => 'notregexp', - 'less_than' => 'lessthan', - 'greater_than' => 'greaterthan', - 'changed_before' => 'changedbefore', - 'changed_after' => 'changedafter', - 'changed_from' => 'changedfrom', - 'changed_to' => 'changedto', - 'changed_by' => 'changedby', - 'matches' => 'matches' + 'equals' => 'equals', + 'not_equals' => 'notequals', + 'equals_any' => 'anyexact', + 'contains' => 'substring', + 'not_contains' => 'notsubstring', + 'case_contains' => 'casesubstring', + 'contains_any' => 'anywordssubstr', + 'not_contains_any' => 'nowordssubstr', + 'contains_all' => 'allwordssubstr', + 'contains_any_words' => 'anywords', + 'not_contains_any_words' => 'nowords', + 'contains_all_words' => 'allwords', + 'regex' => 'regexp', + 'not_regex' => 'notregexp', + 'less_than' => 'lessthan', + 'greater_than' => 'greaterthan', + 'changed_before' => 'changedbefore', + 'changed_after' => 'changedafter', + 'changed_from' => 'changedfrom', + 'changed_to' => 'changedto', + 'changed_by' => 'changedby', + 'matches' => 'matches' }; # Convert old attachment field names from old to new use constant ATTACHMENT_FIELD_MAP => { - 'submitter' => 'attacher', - 'description' => 'description', - 'filename' => 'file_name', - 'delta_ts' => 'last_change_time', - 'isobsolete' => 'is_obsolete', - 'ispatch' => 'is_patch', - 'isprivate' => 'is_private', - 'mimetype' => 'content_type', - 'contenttypeentry' => 'content_type', - 'date' => 'creation_time', - 'attachid' => 'id', - 'desc' => 'description', - 'flag' => 'flags', - 'type' => 'content_type', + 'submitter' => 'attacher', + 'description' => 'description', + 'filename' => 'file_name', + 'delta_ts' => 'last_change_time', + 'isobsolete' => 'is_obsolete', + 'ispatch' => 'is_patch', + 'isprivate' => 'is_private', + 'mimetype' => 'content_type', + 'contenttypeentry' => 'content_type', + 'date' => 'creation_time', + 'attachid' => 'id', + 'desc' => 'description', + 'flag' => 'flags', + 'type' => 'content_type', }; # A base link to the current BzAPI Documentation. diff --git a/extensions/BzAPI/lib/Resources/Bug.pm b/extensions/BzAPI/lib/Resources/Bug.pm index 4fd59824c..ee76ddbcd 100644 --- a/extensions/BzAPI/lib/Resources/Bug.pm +++ b/extensions/BzAPI/lib/Resources/Bug.pm @@ -28,111 +28,86 @@ use List::Util qw(max); ################# BEGIN { - require Bugzilla::WebService::Bug; - *Bugzilla::WebService::Bug::get_bug_count = \&get_bug_count_resource; + require Bugzilla::WebService::Bug; + *Bugzilla::WebService::Bug::get_bug_count = \&get_bug_count_resource; } sub rest_handlers { - my $rest_handlers = [ - qr{^/bug$}, { - GET => { - request => \&search_bugs_request, - response => \&search_bugs_response - }, - POST => { - request => \&create_bug_request, - response => \&create_bug_response - } - }, - qr{^/bug/([^/]+)$}, { - GET => { - response => \&get_bug_response - }, - PUT => { - request => \&update_bug_request, - response => \&update_bug_response - } - }, - qr{^/bug/([^/]+)/comment$}, { - GET => { - response => \&get_comments_response - }, - POST => { - request => \&add_comment_request, - response => \&add_comment_response - } - }, - qr{^/bug/([^/]+)/history$}, { - GET => { - response => \&get_history_response - } - }, - qr{^/bug/([^/]+)/attachment$}, { - GET => { - response => \&get_attachments_response - }, - POST => { - request => \&add_attachment_request, - response => \&add_attachment_response - } - }, - qr{^/bug/attachment/([^/]+)$}, { - GET => { - response => \&get_attachment_response - }, - PUT => { - request => \&update_attachment_request, - response => \&update_attachment_response - } - }, - qr{^/attachment/([^/]+)$}, { - GET => { - response => \&get_attachment_response - }, - PUT => { - request => \&update_attachment_request, - response => \&update_attachment_response - } - }, - qr{^/bug/([^/]+)/flag$}, { - GET => { - resource => { - method => 'get', - params => sub { - return { ids => [ $_[0] ], - include_fields => ['flags'] }; - } - }, - response => \&get_bug_flags_response, - } - }, - qr{^/count$}, { - GET => { - resource => { - method => 'get_bug_count' - } - } + my $rest_handlers = [ + qr{^/bug$}, + { + GET => {request => \&search_bugs_request, response => \&search_bugs_response}, + POST => {request => \&create_bug_request, response => \&create_bug_response} + }, + qr{^/bug/([^/]+)$}, + { + GET => {response => \&get_bug_response}, + PUT => {request => \&update_bug_request, response => \&update_bug_response} + }, + qr{^/bug/([^/]+)/comment$}, + { + GET => {response => \&get_comments_response}, + POST => {request => \&add_comment_request, response => \&add_comment_response} + }, + qr{^/bug/([^/]+)/history$}, + {GET => {response => \&get_history_response}}, + qr{^/bug/([^/]+)/attachment$}, + { + GET => {response => \&get_attachments_response}, + POST => + {request => \&add_attachment_request, response => \&add_attachment_response} + }, + qr{^/bug/attachment/([^/]+)$}, + { + GET => {response => \&get_attachment_response}, + PUT => { + request => \&update_attachment_request, + response => \&update_attachment_response + } + }, + qr{^/attachment/([^/]+)$}, + { + GET => {response => \&get_attachment_response}, + PUT => { + request => \&update_attachment_request, + response => \&update_attachment_response + } + }, + qr{^/bug/([^/]+)/flag$}, + { + GET => { + resource => { + method => 'get', + params => sub { + return {ids => [$_[0]], include_fields => ['flags']}; + } }, - qr{^/attachment/([^/]+)$}, { - GET => { - resource => { - method => 'attachments', - params => sub { - return { attachment_ids => [ $_[0] ] }; - } - } - }, - PUT => { - resource => { - method => 'update_attachment', - params => sub { - return { ids => [ $_[0] ] }; - } - } - } + response => \&get_bug_flags_response, + } + }, + qr{^/count$}, + {GET => {resource => {method => 'get_bug_count'}}}, + qr{^/attachment/([^/]+)$}, + { + GET => { + resource => { + method => 'attachments', + params => sub { + return {attachment_ids => [$_[0]]}; + } } - ]; - return $rest_handlers; + }, + PUT => { + resource => { + method => 'update_attachment', + params => sub { + return {ids => [$_[0]]}; + } + } + } + } + ]; + return $rest_handlers; } ######################### @@ -144,205 +119,203 @@ sub rest_handlers { # this should be broken into it's own module so that report.cgi # and here can share the same code. sub get_bug_count_resource { - my ($self, $params) = @_; - - Bugzilla->switch_to_shadow_db(); - - my $col_field = $params->{x_axis_field} || ''; - my $row_field = $params->{y_axis_field} || ''; - my $tbl_field = $params->{z_axis_field} || ''; - - my $dimensions = $col_field ? - $row_field ? - $tbl_field ? 3 : 2 : 1 : 0; - - if ($dimensions == 0) { - $col_field = "bug_status"; - $params->{x_axis_field} = "bug_status"; - } - - # Valid bug fields that can be reported on. - my $valid_columns = Bugzilla::Search::REPORT_COLUMNS; - - # Convert external names to internal if necessary - $params = Bugzilla::Bug::map_fields($params); - $row_field = Bugzilla::Bug::FIELD_MAP->{$row_field} || $row_field; - $col_field = Bugzilla::Bug::FIELD_MAP->{$col_field} || $col_field; - $tbl_field = Bugzilla::Bug::FIELD_MAP->{$tbl_field} || $tbl_field; - - # Validate the values in the axis fields or throw an error. - !$row_field - || ($valid_columns->{$row_field} && trick_taint($row_field)) - || ThrowCodeError("report_axis_invalid", { fld => "x", val => $row_field }); - !$col_field - || ($valid_columns->{$col_field} && trick_taint($col_field)) - || ThrowCodeError("report_axis_invalid", { fld => "y", val => $col_field }); - !$tbl_field - || ($valid_columns->{$tbl_field} && trick_taint($tbl_field)) - || ThrowCodeError("report_axis_invalid", { fld => "z", val => $tbl_field }); - - my @axis_fields = grep { $_ } ($row_field, $col_field, $tbl_field); - - my $search = new Bugzilla::Search( - fields => \@axis_fields, - params => $params, - allow_unlimited => 1, - ); - - my ($results, $extra_data) = $search->data; - - # We have a hash of hashes for the data itself, and a hash to hold the - # row/col/table names. - my %data; - my %names; - - # Read the bug data and count the bugs for each possible value of row, column - # and table. - # - # We detect a numerical field, and sort appropriately, if all the values are - # numeric. - my $col_isnumeric = 1; - my $row_isnumeric = 1; - my $tbl_isnumeric = 1; - - foreach my $result (@$results) { - # handle empty dimension member names - my $row = check_value($row_field, $result); - my $col = check_value($col_field, $result); - my $tbl = check_value($tbl_field, $result); - - $data{$tbl}{$col}{$row}++; - $names{"col"}{$col}++; - $names{"row"}{$row}++; - $names{"tbl"}{$tbl}++; - - $col_isnumeric &&= ($col =~ /^-?\d+(\.\d+)?$/o); - $row_isnumeric &&= ($row =~ /^-?\d+(\.\d+)?$/o); - $tbl_isnumeric &&= ($tbl =~ /^-?\d+(\.\d+)?$/o); - } - - my @col_names = get_names($names{"col"}, $col_isnumeric, $col_field); - my @row_names = get_names($names{"row"}, $row_isnumeric, $row_field); - my @tbl_names = get_names($names{"tbl"}, $tbl_isnumeric, $tbl_field); - - push(@tbl_names, "-total-") if (scalar(@tbl_names) > 1); - - my @data; - foreach my $tbl (@tbl_names) { - my @tbl_data; - foreach my $row (@row_names) { - my @col_data; - foreach my $col (@col_names) { - $data{$tbl}{$col}{$row} = $data{$tbl}{$col}{$row} || 0; - push(@col_data, $data{$tbl}{$col}{$row}); - if ($tbl ne "-total-") { - # This is a bit sneaky. We spend every loop except the last - # building up the -total- data, and then last time round, - # we process it as another tbl, and push() the total values - # into the image_data array. - $data{"-total-"}{$col}{$row} += $data{$tbl}{$col}{$row}; - } - } - push(@tbl_data, \@col_data); - } - push(@data, \@tbl_data); - } - - my $result = {}; - if ($dimensions == 0) { - my $sum = 0; - - # If the search returns no results, we just get an 0-byte file back - # and so there is no data at all. - if (@data) { - foreach my $value (@{ $data[0][0] }) { - $sum += $value; - } - } - - $result = { - 'data' => $sum - }; - } - elsif ($dimensions == 1) { - $result = { - 'x_labels' => \@col_names, - 'data' => $data[0][0] || [] - }; - } - elsif ($dimensions == 2) { - $result = { - 'x_labels' => \@col_names, - 'y_labels' => \@row_names, - 'data' => $data[0] || [[]] - }; - } - elsif ($dimensions == 3) { - if (@data > 1 && $tbl_names[-1] eq "-total-") { - # Last table is a total, which we discard - pop(@data); - pop(@tbl_names); + my ($self, $params) = @_; + + Bugzilla->switch_to_shadow_db(); + + my $col_field = $params->{x_axis_field} || ''; + my $row_field = $params->{y_axis_field} || ''; + my $tbl_field = $params->{z_axis_field} || ''; + + my $dimensions = $col_field ? $row_field ? $tbl_field ? 3 : 2 : 1 : 0; + + if ($dimensions == 0) { + $col_field = "bug_status"; + $params->{x_axis_field} = "bug_status"; + } + + # Valid bug fields that can be reported on. + my $valid_columns = Bugzilla::Search::REPORT_COLUMNS; + + # Convert external names to internal if necessary + $params = Bugzilla::Bug::map_fields($params); + $row_field = Bugzilla::Bug::FIELD_MAP->{$row_field} || $row_field; + $col_field = Bugzilla::Bug::FIELD_MAP->{$col_field} || $col_field; + $tbl_field = Bugzilla::Bug::FIELD_MAP->{$tbl_field} || $tbl_field; + + # Validate the values in the axis fields or throw an error. + !$row_field + || ($valid_columns->{$row_field} && trick_taint($row_field)) + || ThrowCodeError("report_axis_invalid", {fld => "x", val => $row_field}); + !$col_field + || ($valid_columns->{$col_field} && trick_taint($col_field)) + || ThrowCodeError("report_axis_invalid", {fld => "y", val => $col_field}); + !$tbl_field + || ($valid_columns->{$tbl_field} && trick_taint($tbl_field)) + || ThrowCodeError("report_axis_invalid", {fld => "z", val => $tbl_field}); + + my @axis_fields = grep {$_} ($row_field, $col_field, $tbl_field); + + my $search = new Bugzilla::Search( + fields => \@axis_fields, + params => $params, + allow_unlimited => 1, + ); + + my ($results, $extra_data) = $search->data; + + # We have a hash of hashes for the data itself, and a hash to hold the + # row/col/table names. + my %data; + my %names; + + # Read the bug data and count the bugs for each possible value of row, column + # and table. + # + # We detect a numerical field, and sort appropriately, if all the values are + # numeric. + my $col_isnumeric = 1; + my $row_isnumeric = 1; + my $tbl_isnumeric = 1; + + foreach my $result (@$results) { + + # handle empty dimension member names + my $row = check_value($row_field, $result); + my $col = check_value($col_field, $result); + my $tbl = check_value($tbl_field, $result); + + $data{$tbl}{$col}{$row}++; + $names{"col"}{$col}++; + $names{"row"}{$row}++; + $names{"tbl"}{$tbl}++; + + $col_isnumeric &&= ($col =~ /^-?\d+(\.\d+)?$/o); + $row_isnumeric &&= ($row =~ /^-?\d+(\.\d+)?$/o); + $tbl_isnumeric &&= ($tbl =~ /^-?\d+(\.\d+)?$/o); + } + + my @col_names = get_names($names{"col"}, $col_isnumeric, $col_field); + my @row_names = get_names($names{"row"}, $row_isnumeric, $row_field); + my @tbl_names = get_names($names{"tbl"}, $tbl_isnumeric, $tbl_field); + + push(@tbl_names, "-total-") if (scalar(@tbl_names) > 1); + + my @data; + foreach my $tbl (@tbl_names) { + my @tbl_data; + foreach my $row (@row_names) { + my @col_data; + foreach my $col (@col_names) { + $data{$tbl}{$col}{$row} = $data{$tbl}{$col}{$row} || 0; + push(@col_data, $data{$tbl}{$col}{$row}); + if ($tbl ne "-total-") { + + # This is a bit sneaky. We spend every loop except the last + # building up the -total- data, and then last time round, + # we process it as another tbl, and push() the total values + # into the image_data array. + $data{"-total-"}{$col}{$row} += $data{$tbl}{$col}{$row}; } - - $result = { - 'x_labels' => \@col_names, - 'y_labels' => \@row_names, - 'z_labels' => \@tbl_names, - 'data' => @data ? \@data : [[[]]] - }; - } - - return $result; + } + push(@tbl_data, \@col_data); + } + push(@data, \@tbl_data); + } + + my $result = {}; + if ($dimensions == 0) { + my $sum = 0; + + # If the search returns no results, we just get an 0-byte file back + # and so there is no data at all. + if (@data) { + foreach my $value (@{$data[0][0]}) { + $sum += $value; + } + } + + $result = {'data' => $sum}; + } + elsif ($dimensions == 1) { + $result = {'x_labels' => \@col_names, 'data' => $data[0][0] || []}; + } + elsif ($dimensions == 2) { + $result = { + 'x_labels' => \@col_names, + 'y_labels' => \@row_names, + 'data' => $data[0] || [[]] + }; + } + elsif ($dimensions == 3) { + if (@data > 1 && $tbl_names[-1] eq "-total-") { + + # Last table is a total, which we discard + pop(@data); + pop(@tbl_names); + } + + $result = { + 'x_labels' => \@col_names, + 'y_labels' => \@row_names, + 'z_labels' => \@tbl_names, + 'data' => @data ? \@data : [[[]]] + }; + } + + return $result; } sub get_names { - my ($names, $isnumeric, $field_name) = @_; - my ($field, @sorted); - # XXX - This is a hack to handle the actual_time/work_time field, - # because it's named 'actual_time' in Search.pm but 'work_time' in Field.pm. - $_[2] = $field_name = 'work_time' if $field_name eq 'actual_time'; - - # _realname fields aren't real Bugzilla::Field objects, but they are a - # valid axis, so we don't vailidate them as Bugzilla::Field objects. - $field = Bugzilla::Field->check($field_name) - if ($field_name && $field_name !~ /_realname$/); - - if ($field && $field->is_select) { - foreach my $value (@{$field->legal_values}) { - push(@sorted, $value->name) if $names->{$value->name}; - } - unshift(@sorted, '---') if $field_name eq 'resolution'; - @sorted = uniq @sorted; - } - elsif ($isnumeric) { - # It's not a field we are preserving the order of, so sort it - # numerically... - @sorted = sort { $a <=> $b } keys %$names; - } - else { - # ...or alphabetically, as appropriate. - @sorted = sort keys %$names; - } - - return @sorted; + my ($names, $isnumeric, $field_name) = @_; + my ($field, @sorted); + + # XXX - This is a hack to handle the actual_time/work_time field, + # because it's named 'actual_time' in Search.pm but 'work_time' in Field.pm. + $_[2] = $field_name = 'work_time' if $field_name eq 'actual_time'; + + # _realname fields aren't real Bugzilla::Field objects, but they are a + # valid axis, so we don't vailidate them as Bugzilla::Field objects. + $field = Bugzilla::Field->check($field_name) + if ($field_name && $field_name !~ /_realname$/); + + if ($field && $field->is_select) { + foreach my $value (@{$field->legal_values}) { + push(@sorted, $value->name) if $names->{$value->name}; + } + unshift(@sorted, '---') if $field_name eq 'resolution'; + @sorted = uniq @sorted; + } + elsif ($isnumeric) { + + # It's not a field we are preserving the order of, so sort it + # numerically... + @sorted = sort { $a <=> $b } keys %$names; + } + else { + # ...or alphabetically, as appropriate. + @sorted = sort keys %$names; + } + + return @sorted; } sub check_value { - my ($field, $result) = @_; - - my $value; - if (!defined $field) { - $value = ''; - } - elsif ($field eq '') { - $value = ' '; - } - else { - $value = shift @$result; - $value = ' ' if (!defined $value || $value eq ''); - $value = '---' if ($field eq 'resolution' && $value eq ' '); - } - return $value; + my ($field, $result) = @_; + + my $value; + if (!defined $field) { + $value = ''; + } + elsif ($field eq '') { + $value = ' '; + } + else { + $value = shift @$result; + $value = ' ' if (!defined $value || $value eq ''); + $value = '---' if ($field eq 'resolution' && $value eq ' '); + } + return $value; } ######################## @@ -350,282 +323,290 @@ sub check_value { ######################## sub search_bugs_request { - my ($params) = @_; - - if (defined $params->{changed_field} - && $params->{changed_field} eq "creation_time") - { - $params->{changed_field} = "[Bug creation]"; - } + my ($params) = @_; - my $FIELD_NEW_TO_OLD = { reverse %{ BUG_FIELD_MAP() } }; + if (defined $params->{changed_field} + && $params->{changed_field} eq "creation_time") + { + $params->{changed_field} = "[Bug creation]"; + } - # Update values of various forms. - foreach my $key (keys %$params) { - # First, search types. These are found in the value of any field ending - # _type, and the value of any field matching type\d-\d-\d. - if ($key =~ /^type(\d+)-(\d+)-(\d+)$|_type$/) { - $params->{$key} - = BOOLEAN_TYPE_MAP->{$params->{$key}} || $params->{$key}; - } + my $FIELD_NEW_TO_OLD = {reverse %{BUG_FIELD_MAP()}}; - # Field names hiding in values instead of keys: changed_field, boolean - # charts and axis names. - if ($key =~ /^(field\d+-\d+-\d+| - changed_field| - (x|y|z)_axis_field)$ - /x) { - $params->{$key} - = $FIELD_NEW_TO_OLD->{$params->{$key}} || $params->{$key}; - } - } + # Update values of various forms. + foreach my $key (keys %$params) { - # Update field names - foreach my $field (keys %$FIELD_NEW_TO_OLD) { - if (defined $params->{$field}) { - $params->{$FIELD_NEW_TO_OLD->{$field}} = delete $params->{$field}; - } - } - - if (exists $params->{bug_id_type}) { - $params->{bug_id_type} - = BOOLEAN_TYPE_MAP->{$params->{bug_id_type}} || $params->{bug_id_type}; + # First, search types. These are found in the value of any field ending + # _type, and the value of any field matching type\d-\d-\d. + if ($key =~ /^type(\d+)-(\d+)-(\d+)$|_type$/) { + $params->{$key} = BOOLEAN_TYPE_MAP->{$params->{$key}} || $params->{$key}; } - # Time field names are screwy, and got reused. We can't put this mapping - # in NEW2OLD as everything will go haywire. actual_time has to be queried - # as work_time even though work_time is the submit-only field for _adding_ - # to actual_time, which can't be arbitrarily manipulated. - if (defined $params->{work_time}) { - $params->{actual_time} = delete $params->{work_time}; - } - - # Other convenience search ariables used by BzAPI - my @field_ids = grep(/^f(\d+)$/, keys %$params); - my $last_field_id = @field_ids ? max @field_ids + 1 : 1; - foreach my $field (qw(setters.login_name requestees.login_name)) { - if (my $value = delete $params->{$field}) { - $params->{"f${last_field_id}"} = $FIELD_NEW_TO_OLD->{$field} || $field; - $params->{"o${last_field_id}"} = 'equals'; - $params->{"v${last_field_id}"} = $value; - $last_field_id++; - } - } + # Field names hiding in values instead of keys: changed_field, boolean + # charts and axis names. + if ( + $key =~ /^(field\d+-\d+-\d+| + changed_field| + (x|y|z)_axis_field)$ + /x + ) + { + $params->{$key} = $FIELD_NEW_TO_OLD->{$params->{$key}} || $params->{$key}; + } + } + + # Update field names + foreach my $field (keys %$FIELD_NEW_TO_OLD) { + if (defined $params->{$field}) { + $params->{$FIELD_NEW_TO_OLD->{$field}} = delete $params->{$field}; + } + } + + if (exists $params->{bug_id_type}) { + $params->{bug_id_type} + = BOOLEAN_TYPE_MAP->{$params->{bug_id_type}} || $params->{bug_id_type}; + } + + # Time field names are screwy, and got reused. We can't put this mapping + # in NEW2OLD as everything will go haywire. actual_time has to be queried + # as work_time even though work_time is the submit-only field for _adding_ + # to actual_time, which can't be arbitrarily manipulated. + if (defined $params->{work_time}) { + $params->{actual_time} = delete $params->{work_time}; + } + + # Other convenience search ariables used by BzAPI + my @field_ids = grep(/^f(\d+)$/, keys %$params); + my $last_field_id = @field_ids ? max @field_ids + 1 : 1; + foreach my $field (qw(setters.login_name requestees.login_name)) { + if (my $value = delete $params->{$field}) { + $params->{"f${last_field_id}"} = $FIELD_NEW_TO_OLD->{$field} || $field; + $params->{"o${last_field_id}"} = 'equals'; + $params->{"v${last_field_id}"} = $value; + $last_field_id++; + } + } } sub create_bug_request { - my ($params) = @_; - - # User roles such as assigned_to and qa_contact should be just the - # email (login) of the user you want to set to. - foreach my $field (qw(assigned_to qa_contact)) { - if (exists $params->{$field}) { - $params->{$field} = $params->{$field}->{name}; - } - } - - # CC should just be a list of bugzilla logins - if (exists $params->{cc}) { - $params->{cc} = [ map { $_->{name} } @{ $params->{cc} } ]; - } - - # Comment - if (exists $params->{comments}) { - $params->{comment_is_private} = $params->{comments}->[0]->{is_private}; - $params->{description} = $params->{comments}->[0]->{text}; - delete $params->{comments}; - } - - # Some fields are not supported by Bugzilla::Bug->create but are supported - # by Bugzilla::Bug->update :( - my $cache = Bugzilla->request_cache->{bzapi_bug_create_extra} ||= {}; - foreach my $field (qw(remaining_time)) { - next if !exists $params->{$field}; - $cache->{$field} = delete $params->{$field}; - } - - # remove username/password - delete $params->{username}; - delete $params->{password}; + my ($params) = @_; + + # User roles such as assigned_to and qa_contact should be just the + # email (login) of the user you want to set to. + foreach my $field (qw(assigned_to qa_contact)) { + if (exists $params->{$field}) { + $params->{$field} = $params->{$field}->{name}; + } + } + + # CC should just be a list of bugzilla logins + if (exists $params->{cc}) { + $params->{cc} = [map { $_->{name} } @{$params->{cc}}]; + } + + # Comment + if (exists $params->{comments}) { + $params->{comment_is_private} = $params->{comments}->[0]->{is_private}; + $params->{description} = $params->{comments}->[0]->{text}; + delete $params->{comments}; + } + + # Some fields are not supported by Bugzilla::Bug->create but are supported + # by Bugzilla::Bug->update :( + my $cache = Bugzilla->request_cache->{bzapi_bug_create_extra} ||= {}; + foreach my $field (qw(remaining_time)) { + next if !exists $params->{$field}; + $cache->{$field} = delete $params->{$field}; + } + + # remove username/password + delete $params->{username}; + delete $params->{password}; } sub update_bug_request { - my ($params) = @_; - - my $bug_id = ref $params->{ids} ? $params->{ids}->[0] : $params->{ids}; - my $bug = Bugzilla::Bug->check($bug_id); - - # Convert groups to proper add/remove lists - if (exists $params->{groups}) { - my @new_groups = map { $_->{name} } @{ $params->{groups} }; - my @old_groups = map { $_->name } @{ $bug->groups_in }; - my ($removed, $added) = diff_arrays(\@old_groups, \@new_groups); - if (@$added || @$removed) { - my $groups_data = {}; - $groups_data->{add} = $added if @$added; - $groups_data->{remove} = $removed if @$removed; - $params->{groups} = $groups_data; - } - else { - delete $params->{groups}; - } + my ($params) = @_; + + my $bug_id = ref $params->{ids} ? $params->{ids}->[0] : $params->{ids}; + my $bug = Bugzilla::Bug->check($bug_id); + + # Convert groups to proper add/remove lists + if (exists $params->{groups}) { + my @new_groups = map { $_->{name} } @{$params->{groups}}; + my @old_groups = map { $_->name } @{$bug->groups_in}; + my ($removed, $added) = diff_arrays(\@old_groups, \@new_groups); + if (@$added || @$removed) { + my $groups_data = {}; + $groups_data->{add} = $added if @$added; + $groups_data->{remove} = $removed if @$removed; + $params->{groups} = $groups_data; } - - # Other fields such as keywords, blocks depends_on - # support 'set' which will make the list exactly what - # the user passes in. - foreach my $field (qw(blocks depends_on dependson keywords)) { - if (exists $params->{$field}) { - $params->{$field} = { set => $params->{$field} }; - } + else { + delete $params->{groups}; + } + } + + # Other fields such as keywords, blocks depends_on + # support 'set' which will make the list exactly what + # the user passes in. + foreach my $field (qw(blocks depends_on dependson keywords)) { + if (exists $params->{$field}) { + $params->{$field} = {set => $params->{$field}}; + } + } + + # User roles such as assigned_to and qa_contact should be just the + # email (login) of the user you want to change to. Also if defined + # but set to NULL then we reset them to default + foreach my $field (qw(assigned_to qa_contact)) { + if (exists $params->{$field}) { + if (!$params->{$field}) { + $params->{"reset_$field"} = 1; + delete $params->{$field}; + } + else { + $params->{$field} = $params->{$field}->{name}; + } + } + } + + # CC is treated like groups in that we need 'add' and 'remove' keys + if (exists $params->{cc}) { + my $new_cc = [map { $_->{name} } @{$params->{cc}}]; + my ($removed, $added) = diff_arrays($bug->cc, $new_cc); + if (@$added || @$removed) { + my $cc_data = {}; + $cc_data->{add} = $added if @$added; + $cc_data->{remove} = $removed if @$removed; + $params->{cc} = $cc_data; } - - # User roles such as assigned_to and qa_contact should be just the - # email (login) of the user you want to change to. Also if defined - # but set to NULL then we reset them to default - foreach my $field (qw(assigned_to qa_contact)) { - if (exists $params->{$field}) { - if (!$params->{$field}) { - $params->{"reset_$field"} = 1; - delete $params->{$field}; - } - else { - $params->{$field} = $params->{$field}->{name}; - } - } + else { + delete $params->{cc}; } + } - # CC is treated like groups in that we need 'add' and 'remove' keys - if (exists $params->{cc}) { - my $new_cc = [ map { $_->{name} } @{ $params->{cc} } ]; - my ($removed, $added) = diff_arrays($bug->cc, $new_cc); - if (@$added || @$removed) { - my $cc_data = {}; - $cc_data->{add} = $added if @$added; - $cc_data->{remove} = $removed if @$removed; - $params->{cc} = $cc_data; - } - else { - delete $params->{cc}; - } + # see_also is treated like groups in that we need 'add' and 'remove' keys + if (exists $params->{see_also}) { + my $old_see_also = [map { $_->name } @{$bug->see_also}]; + my ($removed, $added) = diff_arrays($old_see_also, $params->{see_also}); + if (@$added || @$removed) { + my $data = {}; + $data->{add} = $added if @$added; + $data->{remove} = $removed if @$removed; + $params->{see_also} = $data; } - - # see_also is treated like groups in that we need 'add' and 'remove' keys - if (exists $params->{see_also}) { - my $old_see_also = [ map { $_->name } @{ $bug->see_also } ]; - my ($removed, $added) = diff_arrays($old_see_also, $params->{see_also}); - if (@$added || @$removed) { - my $data = {}; - $data->{add} = $added if @$added; - $data->{remove} = $removed if @$removed; - $params->{see_also} = $data; - } - else { - delete $params->{see_also}; - } + else { + delete $params->{see_also}; } + } - # BzAPI allows for adding comments by appending to the list of current - # comments and passing the whole list back. - # 1. If a comment id is specified, the user can update the comment privacy - # 2. If no id is specified it is considered a new comment but only the last - # one will be accepted. - my %comment_is_private; - foreach my $comment (@{ $params->{'comments'} }) { - if (my $id = $comment->{'id'}) { - # Existing comment; tweak privacy flags if necessary - $comment_is_private{$id} - = ($comment->{'is_private'} && $comment->{'is_private'} eq "true") ? 1 : 0; - } - else { - # New comment to be added - # If multiple new comments are specified, only the last one will be - # added. - $params->{comment} = { - body => $comment->{text}, - is_private => ($comment->{'is_private'} && - $comment->{'is_private'} eq "true") ? 1 : 0 - }; - } - } - $params->{comment_is_private} = \%comment_is_private if %comment_is_private; - - # Remove setter and convert requestee to just name - if (exists $params->{flags}) { - foreach my $flag (@{ $params->{flags} }) { - delete $flag->{setter}; # Always use logged in user - if (exists $flag->{requestee} && ref $flag->{requestee}) { - $flag->{requestee} = $flag->{requestee}->{name}; - } - # If no flag id provided, assume it is new - if (!exists $flag->{id}) { - $flag->{new} = 1; - } - } + # BzAPI allows for adding comments by appending to the list of current + # comments and passing the whole list back. + # 1. If a comment id is specified, the user can update the comment privacy + # 2. If no id is specified it is considered a new comment but only the last + # one will be accepted. + my %comment_is_private; + foreach my $comment (@{$params->{'comments'}}) { + if (my $id = $comment->{'id'}) { + + # Existing comment; tweak privacy flags if necessary + $comment_is_private{$id} + = ($comment->{'is_private'} && $comment->{'is_private'} eq "true") ? 1 : 0; } + else { + # New comment to be added + # If multiple new comments are specified, only the last one will be + # added. + $params->{comment} = { + body => $comment->{text}, + is_private => ($comment->{'is_private'} && $comment->{'is_private'} eq "true") + ? 1 + : 0 + }; + } + } + $params->{comment_is_private} = \%comment_is_private if %comment_is_private; + + # Remove setter and convert requestee to just name + if (exists $params->{flags}) { + foreach my $flag (@{$params->{flags}}) { + delete $flag->{setter}; # Always use logged in user + if (exists $flag->{requestee} && ref $flag->{requestee}) { + $flag->{requestee} = $flag->{requestee}->{name}; + } + + # If no flag id provided, assume it is new + if (!exists $flag->{id}) { + $flag->{new} = 1; + } + } + } } sub add_comment_request { - my ($params) = @_; - $params->{comment} = delete $params->{text} if $params->{text}; + my ($params) = @_; + $params->{comment} = delete $params->{text} if $params->{text}; } sub add_attachment_request { - my ($params) = @_; - - # Bug.add_attachment uses 'summary' for description. - if ($params->{description}) { - $params->{summary} = $params->{description}; - delete $params->{description}; - } - - # Remove setter and convert requestee to just name - if (exists $params->{flags}) { - foreach my $flag (@{ $params->{flags} }) { - delete $flag->{setter}; # Always use logged in user - if (exists $flag->{requestee} && ref $flag->{requestee}) { - $flag->{requestee} = $flag->{requestee}->{name}; - } - } - } - - # Add comment if one is provided - if (exists $params->{comments} && scalar @{ $params->{comments} }) { - $params->{comment} = $params->{comments}->[0]->{text}; - delete $params->{comments}; - } + my ($params) = @_; + + # Bug.add_attachment uses 'summary' for description. + if ($params->{description}) { + $params->{summary} = $params->{description}; + delete $params->{description}; + } + + # Remove setter and convert requestee to just name + if (exists $params->{flags}) { + foreach my $flag (@{$params->{flags}}) { + delete $flag->{setter}; # Always use logged in user + if (exists $flag->{requestee} && ref $flag->{requestee}) { + $flag->{requestee} = $flag->{requestee}->{name}; + } + } + } + + # Add comment if one is provided + if (exists $params->{comments} && scalar @{$params->{comments}}) { + $params->{comment} = $params->{comments}->[0]->{text}; + delete $params->{comments}; + } } sub update_attachment_request { - my ($params) = @_; - - # Stash away for midair checking later - if ($params->{last_change_time}) { - my $stash = Bugzilla->request_cache->{bzapi_stash} ||= {}; - $stash->{last_change_time} = delete $params->{last_change_time}; - } - - # Immutable values - foreach my $key (qw(attacher bug_id bug_ref creation_time - encoding id ref size update_token)) { - delete $params->{$key}; - } - - # Convert setter and requestee to standard values - if (exists $params->{flags}) { - foreach my $flag (@{ $params->{flags} }) { - delete $flag->{setter}; # Always use logged in user - if (exists $flag->{requestee} && ref $flag->{requestee}) { - $flag->{requestee} = $flag->{requestee}->{name}; - } - } - } - - # Add comment if one is provided - if (exists $params->{comments} && scalar @{ $params->{comments} }) { - $params->{comment} = $params->{comments}->[0]->{text}; - delete $params->{comments}; - } + my ($params) = @_; + + # Stash away for midair checking later + if ($params->{last_change_time}) { + my $stash = Bugzilla->request_cache->{bzapi_stash} ||= {}; + $stash->{last_change_time} = delete $params->{last_change_time}; + } + + # Immutable values + foreach my $key ( + qw(attacher bug_id bug_ref creation_time + encoding id ref size update_token) + ) + { + delete $params->{$key}; + } + + # Convert setter and requestee to standard values + if (exists $params->{flags}) { + foreach my $flag (@{$params->{flags}}) { + delete $flag->{setter}; # Always use logged in user + if (exists $flag->{requestee} && ref $flag->{requestee}) { + $flag->{requestee} = $flag->{requestee}->{name}; + } + } + } + + # Add comment if one is provided + if (exists $params->{comments} && scalar @{$params->{comments}}) { + $params->{comment} = $params->{comments}->[0]->{text}; + delete $params->{comments}; + } } ######################### @@ -633,237 +614,247 @@ sub update_attachment_request { ######################### sub search_bugs_response { - my ($result, $response) = @_; - my $cache = Bugzilla->request_cache; - my $params = Bugzilla->input_params; + my ($result, $response) = @_; + my $cache = Bugzilla->request_cache; + my $params = Bugzilla->input_params; - return if !exists $$result->{bugs}; + return if !exists $$result->{bugs}; - my $bug_objs = $cache->{bzapi_search_bugs}; + my $bug_objs = $cache->{bzapi_search_bugs}; - my @fixed_bugs; - my $stash = {}; - foreach my $bug_data (@{$$result->{bugs}}) { - my $bug_obj = shift @$bug_objs; - my $fixed = fix_bug($bug_data, $bug_obj, $stash); + my @fixed_bugs; + my $stash = {}; + foreach my $bug_data (@{$$result->{bugs}}) { + my $bug_obj = shift @$bug_objs; + my $fixed = fix_bug($bug_data, $bug_obj, $stash); - # CC count and Dupe count - if (filter_wants_nocache($params, 'cc_count')) { - $fixed->{cc_count} = scalar @{ $bug_obj->cc } - if $bug_obj->cc; - } - if (filter_wants_nocache($params, 'dupe_count')) { - $fixed->{dupe_count} = scalar @{ $bug_obj->duplicate_ids } - if $bug_obj->duplicate_ids; - } - - push(@fixed_bugs, $fixed); + # CC count and Dupe count + if (filter_wants_nocache($params, 'cc_count')) { + $fixed->{cc_count} = scalar @{$bug_obj->cc} if $bug_obj->cc; + } + if (filter_wants_nocache($params, 'dupe_count')) { + $fixed->{dupe_count} = scalar @{$bug_obj->duplicate_ids} + if $bug_obj->duplicate_ids; } - $$result->{bugs} = \@fixed_bugs; + push(@fixed_bugs, $fixed); + } + + $$result->{bugs} = \@fixed_bugs; } sub create_bug_response { - my ($result, $response) = @_; - my $rpc = Bugzilla->request_cache->{bzapi_rpc}; + my ($result, $response) = @_; + my $rpc = Bugzilla->request_cache->{bzapi_rpc}; - return if !exists $$result->{id}; - my $bug_id = $$result->{id}; + return if !exists $$result->{id}; + my $bug_id = $$result->{id}; - $$result->{ref} = $rpc->type('string', ref_urlbase() . "/bug/$bug_id"); - $response->code(STATUS_CREATED); + $$result->{ref} = $rpc->type('string', ref_urlbase() . "/bug/$bug_id"); + $response->code(STATUS_CREATED); } sub get_bug_response { - my ($result) = @_; - my $rpc = Bugzilla->request_cache->{bzapi_rpc}; + my ($result) = @_; + my $rpc = Bugzilla->request_cache->{bzapi_rpc}; - return if !exists $$result->{bugs}; - my $bug_data = $$result->{bugs}->[0]; + return if !exists $$result->{bugs}; + my $bug_data = $$result->{bugs}->[0]; - my $bug_id = $rpc->bz_rest_params->{ids}->[0]; - my $bug_obj = Bugzilla::Bug->check($bug_id); - my $fixed = fix_bug($bug_data, $bug_obj); + my $bug_id = $rpc->bz_rest_params->{ids}->[0]; + my $bug_obj = Bugzilla::Bug->check($bug_id); + my $fixed = fix_bug($bug_data, $bug_obj); - $$result = $fixed; + $$result = $fixed; } sub update_bug_response { - my ($result) = @_; - return if !exists $$result->{bugs} - || !scalar @{$$result->{bugs}}; - $$result = { ok => 1 }; + my ($result) = @_; + return if !exists $$result->{bugs} || !scalar @{$$result->{bugs}}; + $$result = {ok => 1}; } # Get all comments for a bug sub get_comments_response { - my ($result) = @_; - my $rpc = Bugzilla->request_cache->{bzapi_rpc}; - my $params = Bugzilla->input_params; + my ($result) = @_; + my $rpc = Bugzilla->request_cache->{bzapi_rpc}; + my $params = Bugzilla->input_params; - return if !exists $$result->{bugs}; + return if !exists $$result->{bugs}; - my $bug_id = $rpc->bz_rest_params->{ids}->[0]; - my $bug = Bugzilla::Bug->check($bug_id); + my $bug_id = $rpc->bz_rest_params->{ids}->[0]; + my $bug = Bugzilla::Bug->check($bug_id); - my $comment_objs = $bug->comments({ order => 'oldest_to_newest', - after => $params->{new_since} }); - my @filtered_comment_objs; - foreach my $comment (@$comment_objs) { - next if $comment->is_private && !Bugzilla->user->is_insider; - push(@filtered_comment_objs, $comment); - } + my $comment_objs + = $bug->comments({order => 'oldest_to_newest', after => $params->{new_since} + }); + my @filtered_comment_objs; + foreach my $comment (@$comment_objs) { + next if $comment->is_private && !Bugzilla->user->is_insider; + push(@filtered_comment_objs, $comment); + } - my $comments_data = $$result->{bugs}->{$bug_id}->{comments}; + my $comments_data = $$result->{bugs}->{$bug_id}->{comments}; - my @fixed_comments; - foreach my $comment_data (@$comments_data) { - my $comment_obj = shift @filtered_comment_objs; - my $fixed = fix_comment($comment_data, $comment_obj); + my @fixed_comments; + foreach my $comment_data (@$comments_data) { + my $comment_obj = shift @filtered_comment_objs; + my $fixed = fix_comment($comment_data, $comment_obj); - if (exists $fixed->{creator}) { - # /bug/<ID>/comment returns full login for creator but not for /bug/<ID>?include_fields=comments :( - $fixed->{creator}->{name} = $rpc->type('string', $comment_obj->author->login); - # /bug/<ID>/comment does not return real_name for creator but returns ref - $fixed->{creator}->{'ref'} = $rpc->type('string', ref_urlbase() . "/user/" . $comment_obj->author->login); - delete $fixed->{creator}->{real_name}; - } + if (exists $fixed->{creator}) { + +# /bug/<ID>/comment returns full login for creator but not for /bug/<ID>?include_fields=comments :( + $fixed->{creator}->{name} = $rpc->type('string', $comment_obj->author->login); - push(@fixed_comments, filter($params, $fixed)); + # /bug/<ID>/comment does not return real_name for creator but returns ref + $fixed->{creator}->{'ref'} = $rpc->type('string', + ref_urlbase() . "/user/" . $comment_obj->author->login); + delete $fixed->{creator}->{real_name}; } - $$result = { comments => \@fixed_comments }; + push(@fixed_comments, filter($params, $fixed)); + } + + $$result = {comments => \@fixed_comments}; } # Format the return response on successful comment creation sub add_comment_response { - my ($result, $response) = @_; - my $rpc = Bugzilla->request_cache->{bzapi_rpc}; + my ($result, $response) = @_; + my $rpc = Bugzilla->request_cache->{bzapi_rpc}; - return if !exists $$result->{id}; - my $bug_id = $rpc->bz_rest_params->{id}; + return if !exists $$result->{id}; + my $bug_id = $rpc->bz_rest_params->{id}; - $$result = { ref => $rpc->type('string', ref_urlbase() . "/bug/$bug_id/comment") }; - $response->code(STATUS_CREATED); + $$result + = {ref => $rpc->type('string', ref_urlbase() . "/bug/$bug_id/comment")}; + $response->code(STATUS_CREATED); } # Get the history for a bug sub get_history_response { - my ($result) = @_; - my $params = Bugzilla->input_params; + my ($result) = @_; + my $params = Bugzilla->input_params; - return if !exists $$result->{bugs}; - my $history = $$result->{bugs}->[0]->{history}; + return if !exists $$result->{bugs}; + my $history = $$result->{bugs}->[0]->{history}; - my @new_history; - foreach my $changeset (@$history) { - $changeset = fix_changeset($changeset); - push(@new_history, filter($params, $changeset)); - } + my @new_history; + foreach my $changeset (@$history) { + $changeset = fix_changeset($changeset); + push(@new_history, filter($params, $changeset)); + } - $$result = { history => \@new_history }; + $$result = {history => \@new_history}; } # Get all attachments for a bug sub get_attachments_response { - my ($result) = @_; - my $rpc = Bugzilla->request_cache->{bzapi_rpc}; - my $params = Bugzilla->input_params; - - return if !exists $$result->{bugs}; - my $bug_id = $rpc->bz_rest_params->{ids}->[0]; - my $bug = Bugzilla::Bug->check($bug_id); - my $attachment_objs = $bug->attachments; - - my $attachments_data = $$result->{bugs}->{$bug_id}; - - my @fixed_attachments; - foreach my $attachment (@$attachments_data) { - my $attachment_obj = shift @$attachment_objs; - my $fixed = fix_attachment($attachment, $attachment_obj); - - if ((filter_wants_nocache($params, 'data', 'extra') - || filter_wants_nocache($params, 'encoding', 'extra') - || $params->{attachmentdata})) - { - if (!$fixed->{data}) { - $fixed->{data} = $rpc->type('base64', $attachment_obj->data); - $fixed->{encoding} = $rpc->type('string', 'base64'); - } - } - else { - delete $fixed->{data}; - delete $fixed->{encoding}; - } - - push(@fixed_attachments, filter($params, $fixed)); + my ($result) = @_; + my $rpc = Bugzilla->request_cache->{bzapi_rpc}; + my $params = Bugzilla->input_params; + + return if !exists $$result->{bugs}; + my $bug_id = $rpc->bz_rest_params->{ids}->[0]; + my $bug = Bugzilla::Bug->check($bug_id); + my $attachment_objs = $bug->attachments; + + my $attachments_data = $$result->{bugs}->{$bug_id}; + + my @fixed_attachments; + foreach my $attachment (@$attachments_data) { + my $attachment_obj = shift @$attachment_objs; + my $fixed = fix_attachment($attachment, $attachment_obj); + + if (( + filter_wants_nocache($params, 'data', 'extra') + || filter_wants_nocache($params, 'encoding', 'extra') + || $params->{attachmentdata} + )) + { + if (!$fixed->{data}) { + $fixed->{data} = $rpc->type('base64', $attachment_obj->data); + $fixed->{encoding} = $rpc->type('string', 'base64'); + } + } + else { + delete $fixed->{data}; + delete $fixed->{encoding}; } - $$result = { attachments => \@fixed_attachments }; + push(@fixed_attachments, filter($params, $fixed)); + } + + $$result = {attachments => \@fixed_attachments}; } # Format the return response on successful attachment creation sub add_attachment_response { - my ($result, $response) = @_; - my $rpc = Bugzilla->request_cache->{bzapi_rpc}; + my ($result, $response) = @_; + my $rpc = Bugzilla->request_cache->{bzapi_rpc}; - my ($attach_id) = keys %{ $$result->{attachments} }; + my ($attach_id) = keys %{$$result->{attachments}}; - $$result = { ref => $rpc->type('string', ref_urlbase() . "/attachment/$attach_id"), id => $attach_id }; - $response->code(STATUS_CREATED); + $$result = { + ref => $rpc->type('string', ref_urlbase() . "/attachment/$attach_id"), + id => $attach_id + }; + $response->code(STATUS_CREATED); } # Update an attachment's metadata sub update_attachment_response { - my ($result) = @_; - $$result = { ok => 1 }; + my ($result) = @_; + $$result = {ok => 1}; } # Get a single attachment by attachment_id sub get_attachment_response { - my ($result) = @_; - my $rpc = Bugzilla->request_cache->{bzapi_rpc}; - my $params = Bugzilla->input_params; - - return if !exists $$result->{attachments}; - my $attach_id = $rpc->bz_rest_params->{attachment_ids}->[0]; - my $attachment_data = $$result->{attachments}->{$attach_id}; - my $attachment_obj = Bugzilla::Attachment->new($attach_id); - my $fixed = fix_attachment($attachment_data, $attachment_obj); - - if ((filter_wants_nocache($params, 'data', 'extra') - || filter_wants_nocache($params, 'encoding', 'extra') - || $params->{attachmentdata})) - { - if (!$fixed->{data}) { - $fixed->{data} = $rpc->type('base64', $attachment_obj->data); - $fixed->{encoding} = $rpc->type('string', 'base64'); - } - } - else { - delete $fixed->{data}; - delete $fixed->{encoding}; - } - - $fixed = filter($params, $fixed); - - $$result = $fixed; + my ($result) = @_; + my $rpc = Bugzilla->request_cache->{bzapi_rpc}; + my $params = Bugzilla->input_params; + + return if !exists $$result->{attachments}; + my $attach_id = $rpc->bz_rest_params->{attachment_ids}->[0]; + my $attachment_data = $$result->{attachments}->{$attach_id}; + my $attachment_obj = Bugzilla::Attachment->new($attach_id); + my $fixed = fix_attachment($attachment_data, $attachment_obj); + + if (( + filter_wants_nocache($params, 'data', 'extra') + || filter_wants_nocache($params, 'encoding', 'extra') + || $params->{attachmentdata} + )) + { + if (!$fixed->{data}) { + $fixed->{data} = $rpc->type('base64', $attachment_obj->data); + $fixed->{encoding} = $rpc->type('string', 'base64'); + } + } + else { + delete $fixed->{data}; + delete $fixed->{encoding}; + } + + $fixed = filter($params, $fixed); + + $$result = $fixed; } # Get a list of flags for a bug sub get_bug_flags_response { - my ($result) = @_; - my $params = Bugzilla->input_params; + my ($result) = @_; + my $params = Bugzilla->input_params; - return if !exists $$result->{bugs}; - my $flags = $$result->{bugs}->[0]->{flags}; + return if !exists $$result->{bugs}; + my $flags = $$result->{bugs}->[0]->{flags}; - my @new_flags; - foreach my $flag (@$flags) { - push(@new_flags, fix_flag($flag)); - } + my @new_flags; + foreach my $flag (@$flags) { + push(@new_flags, fix_flag($flag)); + } - $$result = { flags => \@new_flags }; + $$result = {flags => \@new_flags}; } 1; diff --git a/extensions/BzAPI/lib/Resources/Bugzilla.pm b/extensions/BzAPI/lib/Resources/Bugzilla.pm index 6e350d839..23d423d5a 100644 --- a/extensions/BzAPI/lib/Resources/Bugzilla.pm +++ b/extensions/BzAPI/lib/Resources/Bugzilla.pm @@ -29,124 +29,118 @@ use Digest::MD5 qw(md5_base64); ######################### BEGIN { - require Bugzilla::WebService::Bugzilla; - *Bugzilla::WebService::Bugzilla::get_configuration = \&get_configuration; - *Bugzilla::WebService::Bugzilla::get_empty = \&get_empty; + require Bugzilla::WebService::Bugzilla; + *Bugzilla::WebService::Bugzilla::get_configuration = \&get_configuration; + *Bugzilla::WebService::Bugzilla::get_empty = \&get_empty; } sub rest_handlers { - my $rest_handlers = [ - qr{^/$}, { - GET => { - resource => { - method => 'get_empty' - } - } - }, - qr{^/configuration$}, { - GET => { - resource => { - method => 'get_configuration' - } - } - } - ]; - return $rest_handlers; + my $rest_handlers = [ + qr{^/$}, {GET => {resource => {method => 'get_empty'}}}, + qr{^/configuration$}, {GET => {resource => {method => 'get_configuration'}}} + ]; + return $rest_handlers; } sub get_configuration { - my ($self) = @_; - my $user = Bugzilla->user; - my $params = Bugzilla->input_params; - - my $can_cache = !exists $params->{product} && !exists $params->{flags}; - my $cache_key = 'bzapi_get_configuration'; - - if ($can_cache) { - my $result = Bugzilla->memcached->get_config({key => $cache_key}); - return $result if defined $result; - } - - # Get data from the shadow DB as they don't change very often. - Bugzilla->switch_to_shadow_db; - - # Pass a bunch of Bugzilla configuration to the templates. - my $vars = {}; - $vars->{'priority'} = get_legal_field_values('priority'); - $vars->{'severity'} = get_legal_field_values('bug_severity'); - $vars->{'platform'} = get_legal_field_values('rep_platform'); - $vars->{'op_sys'} = get_legal_field_values('op_sys'); - $vars->{'keyword'} = [ map($_->name, Bugzilla::Keyword->get_all) ]; - $vars->{'resolution'} = get_legal_field_values('resolution'); - $vars->{'status'} = get_legal_field_values('bug_status'); - $vars->{'custom_fields'} = - [ grep {$_->is_select} Bugzilla->active_custom_fields ]; - - # Include a list of product objects. - if ($params->{'product'}) { - my @products = $params->{'product'}; - foreach my $product_name (@products) { - my $product = new Bugzilla::Product({ name => $product_name }); - if ($product && $user->can_see_product($product->name)) { - push (@{$vars->{'products'}}, $product); - } - } - } else { - $vars->{'products'} = $user->get_selectable_products; - } - - # We set the 2nd argument to 1 to also preload flag types. - Bugzilla::Product::preload($vars->{'products'}, 1, { is_active => 1 }); - - # Allow consumers to specify whether or not they want flag data. - if (defined $params->{'flags'}) { - $vars->{'show_flags'} = $params->{'flags'}; - } - else { - # We default to sending flag data. - $vars->{'show_flags'} = 1; - } - - # Create separate lists of open versus resolved statuses. This should really - # be made part of the configuration. - my @open_status; - my @closed_status; - foreach my $status (@{$vars->{'status'}}) { - is_open_state($status) ? push(@open_status, $status) - : push(@closed_status, $status); + my ($self) = @_; + my $user = Bugzilla->user; + my $params = Bugzilla->input_params; + + my $can_cache = !exists $params->{product} && !exists $params->{flags}; + my $cache_key = 'bzapi_get_configuration'; + + if ($can_cache) { + my $result = Bugzilla->memcached->get_config({key => $cache_key}); + return $result if defined $result; + } + + # Get data from the shadow DB as they don't change very often. + Bugzilla->switch_to_shadow_db; + + # Pass a bunch of Bugzilla configuration to the templates. + my $vars = {}; + $vars->{'priority'} = get_legal_field_values('priority'); + $vars->{'severity'} = get_legal_field_values('bug_severity'); + $vars->{'platform'} = get_legal_field_values('rep_platform'); + $vars->{'op_sys'} = get_legal_field_values('op_sys'); + $vars->{'keyword'} = [map($_->name, Bugzilla::Keyword->get_all)]; + $vars->{'resolution'} = get_legal_field_values('resolution'); + $vars->{'status'} = get_legal_field_values('bug_status'); + $vars->{'custom_fields'} + = [grep { $_->is_select } Bugzilla->active_custom_fields]; + + # Include a list of product objects. + if ($params->{'product'}) { + my @products = $params->{'product'}; + foreach my $product_name (@products) { + my $product = new Bugzilla::Product({name => $product_name}); + if ($product && $user->can_see_product($product->name)) { + push(@{$vars->{'products'}}, $product); + } } - $vars->{'open_status'} = \@open_status; - $vars->{'closed_status'} = \@closed_status; - - # Generate a list of fields that can be queried. - my @fields = @{Bugzilla::Field->match({obsolete => 0})}; - # Exclude fields the user cannot query. - if (!Bugzilla->user->is_timetracker) { - @fields = grep { $_->name !~ /^(estimated_time|remaining_time|work_time|percentage_complete|deadline)$/ } @fields; - } - $vars->{'field'} = \@fields; - - my $json; - Bugzilla->template->process('config.json.tmpl', $vars, \$json); - if ($json) { - my $result = $self->json->decode($json); - if ($can_cache) { - Bugzilla->memcached->set_config({key => $cache_key, data => $result}); - } - return $result; - } - else { - return {}; + } + else { + $vars->{'products'} = $user->get_selectable_products; + } + + # We set the 2nd argument to 1 to also preload flag types. + Bugzilla::Product::preload($vars->{'products'}, 1, {is_active => 1}); + + # Allow consumers to specify whether or not they want flag data. + if (defined $params->{'flags'}) { + $vars->{'show_flags'} = $params->{'flags'}; + } + else { + # We default to sending flag data. + $vars->{'show_flags'} = 1; + } + + # Create separate lists of open versus resolved statuses. This should really + # be made part of the configuration. + my @open_status; + my @closed_status; + foreach my $status (@{$vars->{'status'}}) { + is_open_state($status) + ? push(@open_status, $status) + : push(@closed_status, $status); + } + $vars->{'open_status'} = \@open_status; + $vars->{'closed_status'} = \@closed_status; + + # Generate a list of fields that can be queried. + my @fields = @{Bugzilla::Field->match({obsolete => 0})}; + + # Exclude fields the user cannot query. + if (!Bugzilla->user->is_timetracker) { + @fields = grep { + $_->name + !~ /^(estimated_time|remaining_time|work_time|percentage_complete|deadline)$/ + } @fields; + } + $vars->{'field'} = \@fields; + + my $json; + Bugzilla->template->process('config.json.tmpl', $vars, \$json); + if ($json) { + my $result = $self->json->decode($json); + if ($can_cache) { + Bugzilla->memcached->set_config({key => $cache_key, data => $result}); } + return $result; + } + else { + return {}; + } } sub get_empty { - my ($self) = @_; - return { - ref => $self->type('string', Bugzilla->localconfig->{urlbase} . "bzapi/"), - documentation => $self->type('string', BZAPI_DOC), - version => $self->type('string', BUGZILLA_VERSION) - }; + my ($self) = @_; + return { + ref => $self->type('string', Bugzilla->localconfig->{urlbase} . "bzapi/"), + documentation => $self->type('string', BZAPI_DOC), + version => $self->type('string', BUGZILLA_VERSION) + }; } 1; diff --git a/extensions/BzAPI/lib/Resources/User.pm b/extensions/BzAPI/lib/Resources/User.pm index 550a61d28..7a7a183a9 100644 --- a/extensions/BzAPI/lib/Resources/User.pm +++ b/extensions/BzAPI/lib/Resources/User.pm @@ -14,67 +14,58 @@ use warnings; use Bugzilla::Extension::BzAPI::Util; sub rest_handlers { - my $rest_handlers = [ - qr{/user$}, { - GET => { - response => \&get_users, - }, - }, - qr{/user/([^/]+)$}, { - GET => { - response => \&get_user, - }, - } - ]; - return $rest_handlers; + my $rest_handlers = [ + qr{/user$}, {GET => {response => \&get_users,},}, + qr{/user/([^/]+)$}, {GET => {response => \&get_user,},} + ]; + return $rest_handlers; } sub get_users { - my ($result) = @_; - my $rpc = Bugzilla->request_cache->{bzapi_rpc}; - my $params = Bugzilla->input_params; + my ($result) = @_; + my $rpc = Bugzilla->request_cache->{bzapi_rpc}; + my $params = Bugzilla->input_params; - return if !exists $$result->{users}; + return if !exists $$result->{users}; - my @users; - foreach my $user (@{$$result->{users}}) { - my $object = Bugzilla::User->new( - { id => $user->{id}, cache => 1 }); + my @users; + foreach my $user (@{$$result->{users}}) { + my $object = Bugzilla::User->new({id => $user->{id}, cache => 1}); - $user = fix_user($user, $object); + $user = fix_user($user, $object); - # Use userid instead of email for 'ref' for /user calls - $user->{'ref'} = $rpc->type('string', ref_urlbase . "/user/" . $object->id); + # Use userid instead of email for 'ref' for /user calls + $user->{'ref'} = $rpc->type('string', ref_urlbase . "/user/" . $object->id); - # Emails are not filtered even if user is not logged in - $user->{name} = $rpc->type('string', $object->login); + # Emails are not filtered even if user is not logged in + $user->{name} = $rpc->type('string', $object->login); - push(@users, filter($params, $user)); - } + push(@users, filter($params, $user)); + } - $$result->{users} = \@users; + $$result->{users} = \@users; } sub get_user { - my ($result) = @_; - my $rpc = Bugzilla->request_cache->{bzapi_rpc}; - my $params = Bugzilla->input_params; + my ($result) = @_; + my $rpc = Bugzilla->request_cache->{bzapi_rpc}; + my $params = Bugzilla->input_params; - return if !exists $$result->{users}; - my $user = $$result->{users}->[0] || return; - my $object = Bugzilla::User->new({ id => $user->{id}, cache => 1 }); + return if !exists $$result->{users}; + my $user = $$result->{users}->[0] || return; + my $object = Bugzilla::User->new({id => $user->{id}, cache => 1}); - $user = fix_user($user, $object); + $user = fix_user($user, $object); - # Use userid instead of email for 'ref' for /user calls - $user->{'ref'} = $rpc->type('string', ref_urlbase . "/user/" . $object->id); + # Use userid instead of email for 'ref' for /user calls + $user->{'ref'} = $rpc->type('string', ref_urlbase . "/user/" . $object->id); - # Emails are not filtered even if user is not logged in - $user->{name} = $rpc->type('string', $object->login); + # Emails are not filtered even if user is not logged in + $user->{name} = $rpc->type('string', $object->login); - $user = filter($params, $user); + $user = filter($params, $user); - $$result = $user; + $$result = $user; } 1; diff --git a/extensions/BzAPI/lib/Util.pm b/extensions/BzAPI/lib/Util.pm index d50679a6c..6f7e2c025 100644 --- a/extensions/BzAPI/lib/Util.pm +++ b/extensions/BzAPI/lib/Util.pm @@ -28,425 +28,437 @@ use MIME::Base64; use base qw(Exporter); our @EXPORT = qw( - ref_urlbase - fix_bug - fix_user - fix_flag - fix_comment - fix_changeset - fix_attachment - filter_wants_nocache - filter - fix_credentials - filter_email + ref_urlbase + fix_bug + fix_user + fix_flag + fix_comment + fix_changeset + fix_attachment + filter_wants_nocache + filter + fix_credentials + filter_email ); # Return an URL base appropriate for constructing a ref link # normally required by REST API calls. sub ref_urlbase { - return Bugzilla->localconfig->{urlbase} . "bzapi"; + return Bugzilla->localconfig->{urlbase} . "bzapi"; } # convert certain fields within a bug object # from a simple scalar value to their respective objects sub fix_bug { - my ($data, $bug, $stash) = @_; - my $dbh = $stash->{dbh} //= Bugzilla->dbh; - my $params = $stash->{params} //= Bugzilla->input_params; - my $rpc = $stash->{rpc} //= Bugzilla->request_cache->{bzapi_rpc}; - my $method = $stash->{method} //= Bugzilla->request_cache->{bzapi_rpc_method}; - - $bug = ref $bug ? $bug : Bugzilla::Bug->check($bug || $data->{id}); - - # Add REST API reference to the individual bug - if ($stash->{wants_ref} //= filter_wants_nocache($params, 'ref')) { - $data->{'ref'} = ref_urlbase() . "/bug/" . $bug->id; + my ($data, $bug, $stash) = @_; + my $dbh = $stash->{dbh} //= Bugzilla->dbh; + my $params = $stash->{params} //= Bugzilla->input_params; + my $rpc = $stash->{rpc} //= Bugzilla->request_cache->{bzapi_rpc}; + my $method = $stash->{method} //= Bugzilla->request_cache->{bzapi_rpc_method}; + + $bug = ref $bug ? $bug : Bugzilla::Bug->check($bug || $data->{id}); + + # Add REST API reference to the individual bug + if ($stash->{wants_ref} //= filter_wants_nocache($params, 'ref')) { + $data->{'ref'} = ref_urlbase() . "/bug/" . $bug->id; + } + + # User fields + foreach my $field (USER_FIELDS) { + next if !exists $data->{$field}; + if ($field eq 'cc') { + my @new_cc; + foreach my $cc (@{$bug->cc_users}) { + my $cc_data = {name => filter_email($cc->email)}; + push(@new_cc, fix_user($cc_data, $cc)); + } + $data->{$field} = \@new_cc; } - - # User fields - foreach my $field (USER_FIELDS) { - next if !exists $data->{$field}; - if ($field eq 'cc') { - my @new_cc; - foreach my $cc (@{ $bug->cc_users }) { - my $cc_data = { name => filter_email($cc->email) }; - push(@new_cc, fix_user($cc_data, $cc)); - } - $data->{$field} = \@new_cc; - } - else { - my $field_name = $field; - if ($field eq 'creator') { - $field_name = 'reporter'; - } - $data->{$field} - = fix_user($data->{"${field}_detail"}, $bug->$field_name); - delete $data->{$field}->{id}; - delete $data->{$field}->{email}; - $data->{$field} = filter($params, $data->{$field}, undef, $field); - } - - # Get rid of extra detail hash if exists since redundant - delete $data->{"${field}_detail"} if exists $data->{"${field}_detail"}; + else { + my $field_name = $field; + if ($field eq 'creator') { + $field_name = 'reporter'; + } + $data->{$field} = fix_user($data->{"${field}_detail"}, $bug->$field_name); + delete $data->{$field}->{id}; + delete $data->{$field}->{email}; + $data->{$field} = filter($params, $data->{$field}, undef, $field); } - # Groups - if ($stash->{wants_groups} //= filter_wants_nocache($params, 'groups')) { - my @new_groups; - foreach my $group (@{ $data->{groups} }) { - if (my $object = Bugzilla::Group->new({ name => $group, cache => 1 })) { - $group = { - id => $rpc->type('int', $object->id), - name => $rpc->type('string', $object->name), - }; - } - push(@new_groups, $group); - } - $data->{groups} = \@new_groups; + # Get rid of extra detail hash if exists since redundant + delete $data->{"${field}_detail"} if exists $data->{"${field}_detail"}; + } + + # Groups + if ($stash->{wants_groups} //= filter_wants_nocache($params, 'groups')) { + my @new_groups; + foreach my $group (@{$data->{groups}}) { + if (my $object = Bugzilla::Group->new({name => $group, cache => 1})) { + $group = { + id => $rpc->type('int', $object->id), + name => $rpc->type('string', $object->name), + }; + } + push(@new_groups, $group); } - - # Flags - if (exists $data->{flags}) { - my @new_flags; - foreach my $flag (@{ $data->{flags} }) { - push(@new_flags, fix_flag($flag)); - } - $data->{flags} = \@new_flags; + $data->{groups} = \@new_groups; + } + + # Flags + if (exists $data->{flags}) { + my @new_flags; + foreach my $flag (@{$data->{flags}}) { + push(@new_flags, fix_flag($flag)); } - - # Attachment metadata is included by default but not data - if ($stash->{wants_attachments} //= filter_wants_nocache($params, 'attachments')) { - my $attachment_params = { ids => $bug->id }; - if (!filter_wants_nocache($params, 'data', 'extra', 'attachments') - && !$params->{attachmentdata}) - { - $attachment_params->{exclude_fields} = ['data']; - } - - my $attachments = $rpc->attachments($attachment_params); - - my @fixed_attachments; - foreach my $attachment (@{ $attachments->{bugs}->{$bug->id} }) { - my $fixed = fix_attachment($attachment); - push(@fixed_attachments, filter($params, $fixed, undef, 'attachments')); - } - - $data->{attachments} = \@fixed_attachments; + $data->{flags} = \@new_flags; + } + + # Attachment metadata is included by default but not data + if ($stash->{wants_attachments} + //= filter_wants_nocache($params, 'attachments')) + { + my $attachment_params = {ids => $bug->id}; + if ( !filter_wants_nocache($params, 'data', 'extra', 'attachments') + && !$params->{attachmentdata}) + { + $attachment_params->{exclude_fields} = ['data']; } - # Comments and history are not part of _default and have to be requested + my $attachments = $rpc->attachments($attachment_params); - # Comments - if ($stash->{wants_comments} //= filter_wants_nocache($params, 'comments', 'extra', 'comments')) { - my $comments = $rpc->comments({ ids => $bug->id }); - $comments = $comments->{bugs}->{$bug->id}->{comments}; - my @new_comments; - foreach my $comment (@$comments) { - $comment = fix_comment($comment); - push(@new_comments, filter($params, $comment, 'extra', 'comments')); - } - $data->{comments} = \@new_comments; + my @fixed_attachments; + foreach my $attachment (@{$attachments->{bugs}->{$bug->id}}) { + my $fixed = fix_attachment($attachment); + push(@fixed_attachments, filter($params, $fixed, undef, 'attachments')); } - # History - if ($stash->{wants_history} //= filter_wants_nocache($params, 'history', 'extra', 'history')) { - my $history = $rpc->history({ ids => [ $bug->id ] }); - my @new_history; - foreach my $changeset (@{ $history->{bugs}->[0]->{history} }) { - push(@new_history, fix_changeset($changeset, $bug)); - } - $data->{history} = \@new_history; + $data->{attachments} = \@fixed_attachments; + } + + # Comments and history are not part of _default and have to be requested + + # Comments + if ($stash->{wants_comments} + //= filter_wants_nocache($params, 'comments', 'extra', 'comments')) + { + my $comments = $rpc->comments({ids => $bug->id}); + $comments = $comments->{bugs}->{$bug->id}->{comments}; + my @new_comments; + foreach my $comment (@$comments) { + $comment = fix_comment($comment); + push(@new_comments, filter($params, $comment, 'extra', 'comments')); + } + $data->{comments} = \@new_comments; + } + + # History + if ($stash->{wants_history} + //= filter_wants_nocache($params, 'history', 'extra', 'history')) + { + my $history = $rpc->history({ids => [$bug->id]}); + my @new_history; + foreach my $changeset (@{$history->{bugs}->[0]->{history}}) { + push(@new_history, fix_changeset($changeset, $bug)); + } + $data->{history} = \@new_history; + } + + # Add in all custom fields even if not set or visible on this bug + my $custom_fields = $stash->{custom_fields} + //= Bugzilla->fields({custom => 1, obsolete => 0, by_name => 1}); + foreach my $field (values %$custom_fields) { + my $name = $field->name; + my $type = $field->type; + if (!filter_wants_nocache($params, $name, ['default', 'custom'])) { + delete $custom_fields->{$name}; + next; + } + if ($type == FIELD_TYPE_BUG_ID) { + $data->{$name} = $rpc->type('int', $bug->$name); + } + elsif ($type == FIELD_TYPE_DATETIME || $type == FIELD_TYPE_DATE) { + $data->{$name} = $rpc->type('dateTime', $bug->$name); } + elsif ($type == FIELD_TYPE_MULTI_SELECT) { + +# Bug.search, when include_fields=_all, returns array, otherwise return as comma delimited string :( + if ($method eq 'Bug.search' + && !grep($_ eq '_all', @{$params->{include_fields}})) + { + $data->{$name} = $rpc->type('string', join(', ', @{$bug->$name})); + } + else { + my @values = map { $rpc->type('string', $_) } @{$bug->$name}; + $data->{$name} = \@values; + } + } + else { + $data->{$name} = $rpc->type('string', $bug->$name); + } + } - # Add in all custom fields even if not set or visible on this bug - my $custom_fields = $stash->{custom_fields} //= - Bugzilla->fields({ custom => 1, obsolete => 0, by_name => 1 }); - foreach my $field (values %$custom_fields) { - my $name = $field->name; - my $type = $field->type; - if (!filter_wants_nocache($params, $name, ['default','custom'])) { - delete $custom_fields->{$name}; - next; - } - if ($type == FIELD_TYPE_BUG_ID) { - $data->{$name} = $rpc->type('int', $bug->$name); - } - elsif ($type == FIELD_TYPE_DATETIME - || $type == FIELD_TYPE_DATE) - { - $data->{$name} = $rpc->type('dateTime', $bug->$name); - } - elsif ($type == FIELD_TYPE_MULTI_SELECT) { - # Bug.search, when include_fields=_all, returns array, otherwise return as comma delimited string :( - if ($method eq 'Bug.search' && !grep($_ eq '_all', @{ $params->{include_fields} })) { - $data->{$name} = $rpc->type('string', join(', ', @{ $bug->$name })); - } - else { - my @values = map { $rpc->type('string', $_) } @{ $bug->$name }; - $data->{$name} = \@values; - } - } - else { - $data->{$name} = $rpc->type('string', $bug->$name); - } + # Remove empty values in some cases + foreach my $key (keys %$data) { + + # QA Contact is null if single bug or "" if doing search + if ($key eq 'qa_contact' && !$data->{$key}->{name}) { + if ($method eq 'Bug.search') { + $data->{$key}->{name} = $rpc->type('string', ''); + } + next; } - # Remove empty values in some cases - foreach my $key (keys %$data) { - # QA Contact is null if single bug or "" if doing search - if ($key eq 'qa_contact' && !$data->{$key}->{name}) { - if ($method eq 'Bug.search') { - $data->{$key}->{name} = $rpc->type('string', ''); - } - next; - } + next if $method eq 'Bug.search' && $key eq 'url'; # Return url even if empty + next if $method eq 'Bug.search' && $key eq 'keywords'; # Return keywords even if empty + next if $method eq 'Bug.search' && $key eq 'whiteboard'; # Return whiteboard even if empty + next if $method eq 'Bug.get' && grep($_ eq $key, TIMETRACKING_FIELDS); - next if $method eq 'Bug.search' && $key eq 'url'; # Return url even if empty - next if $method eq 'Bug.search' && $key eq 'keywords'; # Return keywords even if empty - next if $method eq 'Bug.search' && $key eq 'whiteboard'; # Return whiteboard even if empty - next if $method eq 'Bug.get' && grep($_ eq $key, TIMETRACKING_FIELDS); + next + if ($method eq 'Bug.search' + && $key =~ /^(resolution|cc_count|dupe_count)$/ + && !grep($_ eq '_all', @{$params->{include_fields}})); - next if ($method eq 'Bug.search' - && $key =~ /^(resolution|cc_count|dupe_count)$/ - && !grep($_ eq '_all', @{ $params->{include_fields} })); + if (!ref $data->{$key}) { + delete $data->{$key} if !$data->{$key}; + } + else { + if (ref $data->{$key} eq 'ARRAY' && !@{$data->{$key}}) { - if (!ref $data->{$key}) { - delete $data->{$key} if !$data->{$key}; + # Return empty string if blocks or depends_on is empty + if ($method eq 'Bug.search' && ($key eq 'depends_on' || $key eq 'blocks')) { + $data->{$key} = ''; } else { - if (ref $data->{$key} eq 'ARRAY' && !@{$data->{$key}}) { - # Return empty string if blocks or depends_on is empty - if ($method eq 'Bug.search' && ($key eq 'depends_on' || $key eq 'blocks')) { - $data->{$key} = ''; - } - else { - delete $data->{$key}; - } - } - elsif (ref $data->{$key} eq 'HASH' && !%{$data->{$key}}) { - delete $data->{$key}; - } + delete $data->{$key}; } + } + elsif (ref $data->{$key} eq 'HASH' && !%{$data->{$key}}) { + delete $data->{$key}; + } } + } - return $data; + return $data; } # convert a user related field from being just login # names to user objects sub fix_user { - my ($data, $object) = @_; - my $user = Bugzilla->user; - my $rpc = Bugzilla->request_cache->{bzapi_rpc}; + my ($data, $object) = @_; + my $user = Bugzilla->user; + my $rpc = Bugzilla->request_cache->{bzapi_rpc}; - return { name => undef } if !$data; + return {name => undef} if !$data; - if (!ref $data) { - $data = { - name => filter_email($object->login) - }; - $data->{real_name} = $rpc->type('string', $object->name); - } - else { - $data->{name} = filter_email($data->{name}); - } + if (!ref $data) { + $data = {name => filter_email($object->login)}; + $data->{real_name} = $rpc->type('string', $object->name); + } + else { + $data->{name} = filter_email($data->{name}); + } - if ($user->id) { - $data->{ref} = $rpc->type('string', ref_urlbase . "/user/" . $object->login); - } + if ($user->id) { + $data->{ref} = $rpc->type('string', ref_urlbase . "/user/" . $object->login); + } - return $data; + return $data; } # convert certain attributes of a comment to objects # and also remove other unwanted key/values. sub fix_comment { - my ($data, $object) = @_; - my $rpc = Bugzilla->request_cache->{bzapi_rpc}; - my $method = Bugzilla->request_cache->{bzapi_rpc_method}; + my ($data, $object) = @_; + my $rpc = Bugzilla->request_cache->{bzapi_rpc}; + my $method = Bugzilla->request_cache->{bzapi_rpc_method}; - $object ||= Bugzilla::Comment->new({ id => $data->{id}, cache => 1 }); + $object ||= Bugzilla::Comment->new({id => $data->{id}, cache => 1}); - if (exists $data->{creator}) { - $data->{creator} = fix_user($data->{creator}, $object->author); - } + if (exists $data->{creator}) { + $data->{creator} = fix_user($data->{creator}, $object->author); + } - if ($data->{attachment_id} && $method ne 'Bug.search') { - $data->{attachment_ref} = $rpc->type('string', ref_urlbase() . - "/attachment/" . $object->extra_data); - } - else { - delete $data->{attachment_id}; - } + if ($data->{attachment_id} && $method ne 'Bug.search') { + $data->{attachment_ref} + = $rpc->type('string', ref_urlbase() . "/attachment/" . $object->extra_data); + } + else { + delete $data->{attachment_id}; + } - delete $data->{author}; - delete $data->{time}; - delete $data->{raw_text}; + delete $data->{author}; + delete $data->{time}; + delete $data->{raw_text}; - return $data; + return $data; } # convert certain attributes of a changeset object from # scalar values to related objects. Also remove other unwanted # key/values. sub fix_changeset { - my ($data, $object) = @_; - my $user = Bugzilla->user; - my $rpc = Bugzilla->request_cache->{bzapi_rpc}; - - if ($data->{who}) { - $data->{changer} = { - name => $rpc->type('string', $data->{who}), - ref => $rpc->type('string', ref_urlbase() . "/user/" . $data->{who}) - }; - delete $data->{who}; - } - - if ($data->{when}) { - $data->{change_time} = $rpc->type('dateTime', $data->{when}); - delete $data->{when}; - } - - foreach my $change (@{ $data->{changes} }) { - $change->{field_name} = 'flag' if $change->{field_name} eq 'flagtypes.name'; - } - - return $data; + my ($data, $object) = @_; + my $user = Bugzilla->user; + my $rpc = Bugzilla->request_cache->{bzapi_rpc}; + + if ($data->{who}) { + $data->{changer} = { + name => $rpc->type('string', $data->{who}), + ref => $rpc->type('string', ref_urlbase() . "/user/" . $data->{who}) + }; + delete $data->{who}; + } + + if ($data->{when}) { + $data->{change_time} = $rpc->type('dateTime', $data->{when}); + delete $data->{when}; + } + + foreach my $change (@{$data->{changes}}) { + $change->{field_name} = 'flag' if $change->{field_name} eq 'flagtypes.name'; + } + + return $data; } # convert certain attributes of an attachment object from # scalar values to related objects. Also add in additional # key/values. sub fix_attachment { - my ($data, $object) = @_; - my $rpc = Bugzilla->request_cache->{bzapi_rpc}; - my $method = Bugzilla->request_cache->{bzapi_rpc_method}; - my $params = Bugzilla->input_params; - my $user = Bugzilla->user; - - $object ||= Bugzilla::Attachment->new({ id => $data->{id}, cache => 1 }); - - if (exists $data->{attacher}) { - $data->{attacher} = fix_user($data->{attacher}, $object->attacher); - if ($method eq 'Bug.search') { - delete $data->{attacher}->{real_name}; - } - else { - $data->{attacher}->{real_name} = $rpc->type('string', $object->attacher->name); - } + my ($data, $object) = @_; + my $rpc = Bugzilla->request_cache->{bzapi_rpc}; + my $method = Bugzilla->request_cache->{bzapi_rpc_method}; + my $params = Bugzilla->input_params; + my $user = Bugzilla->user; + + $object ||= Bugzilla::Attachment->new({id => $data->{id}, cache => 1}); + + if (exists $data->{attacher}) { + $data->{attacher} = fix_user($data->{attacher}, $object->attacher); + if ($method eq 'Bug.search') { + delete $data->{attacher}->{real_name}; } - - if (exists $data->{data}) { - $data->{encoding} = $rpc->type('string', 'base64'); - if ($params->{attachmentdata} - || filter_wants_nocache($params, 'attachments.data')) - { - $data->{encoding} = $rpc->type('string', 'base64'); - } - else { - delete $data->{data}; - } - } - - if (exists $data->{bug_id}) { - $data->{bug_ref} = $rpc->type('string', ref_urlbase() . "/bug/" . $object->bug_id); - } - - # Upstream API returns these as integers where bzapi returns as booleans - if (exists $data->{is_patch}) { - $data->{is_patch} = $rpc->type('boolean', $data->{is_patch}); - } - if (exists $data->{is_obsolete}) { - $data->{is_obsolete} = $rpc->type('boolean', $data->{is_obsolete}); - } - if (exists $data->{is_private}) { - $data->{is_private} = $rpc->type('boolean', $data->{is_private}); + else { + $data->{attacher}->{real_name} = $rpc->type('string', $object->attacher->name); } - - if (exists $data->{flags} && @{ $data->{flags} }) { - my @new_flags; - foreach my $flag (@{ $data->{flags} }) { - push(@new_flags, fix_flag($flag)); - } - $data->{flags} = \@new_flags; + } + + if (exists $data->{data}) { + $data->{encoding} = $rpc->type('string', 'base64'); + if ($params->{attachmentdata} + || filter_wants_nocache($params, 'attachments.data')) + { + $data->{encoding} = $rpc->type('string', 'base64'); } else { - delete $data->{flags}; + delete $data->{data}; } + } + + if (exists $data->{bug_id}) { + $data->{bug_ref} + = $rpc->type('string', ref_urlbase() . "/bug/" . $object->bug_id); + } + + # Upstream API returns these as integers where bzapi returns as booleans + if (exists $data->{is_patch}) { + $data->{is_patch} = $rpc->type('boolean', $data->{is_patch}); + } + if (exists $data->{is_obsolete}) { + $data->{is_obsolete} = $rpc->type('boolean', $data->{is_obsolete}); + } + if (exists $data->{is_private}) { + $data->{is_private} = $rpc->type('boolean', $data->{is_private}); + } + + if (exists $data->{flags} && @{$data->{flags}}) { + my @new_flags; + foreach my $flag (@{$data->{flags}}) { + push(@new_flags, fix_flag($flag)); + } + $data->{flags} = \@new_flags; + } + else { + delete $data->{flags}; + } - $data->{ref} = $rpc->type('string', ref_urlbase() . "/attachment/" . $object->id); + $data->{ref} + = $rpc->type('string', ref_urlbase() . "/attachment/" . $object->id); - # Add update token if we are getting an attachment outside of Bug.get and user is logged in - if ($user->id && ($method eq 'Bug.attachments'|| $method eq 'Bug.search')) { - $data->{update_token} = issue_hash_token([ $object->id, $object->modification_time ]); - } +# Add update token if we are getting an attachment outside of Bug.get and user is logged in + if ($user->id && ($method eq 'Bug.attachments' || $method eq 'Bug.search')) { + $data->{update_token} + = issue_hash_token([$object->id, $object->modification_time]); + } - delete $data->{creator}; - delete $data->{summary}; + delete $data->{creator}; + delete $data->{summary}; - return $data; + return $data; } # convert certain attributes of a flag object from # scalar values to related objects. Also remove other unwanted # key/values. sub fix_flag { - my ($data, $object) = @_; - my $rpc = Bugzilla->request_cache->{bzapi_rpc}; + my ($data, $object) = @_; + my $rpc = Bugzilla->request_cache->{bzapi_rpc}; - $object ||= Bugzilla::Flag->new({ id => $data->{id}, cache => 1 }); + $object ||= Bugzilla::Flag->new({id => $data->{id}, cache => 1}); - if (exists $data->{setter}) { - $data->{setter} = fix_user($data->{setter}, $object->setter); - delete $data->{setter}->{real_name}; - } + if (exists $data->{setter}) { + $data->{setter} = fix_user($data->{setter}, $object->setter); + delete $data->{setter}->{real_name}; + } - if (exists $data->{requestee}) { - $data->{requestee} = fix_user($data->{requestee}, $object->requestee); - delete $data->{requestee}->{real_name}; - } + if (exists $data->{requestee}) { + $data->{requestee} = fix_user($data->{requestee}, $object->requestee); + delete $data->{requestee}->{real_name}; + } - return $data; + return $data; } # Calls Bugzilla::WebService::Util::filter_wants but disables caching # as we make several webservice calls in a single REST call and the # caching can cause unexpected results. sub filter_wants_nocache { - my ($params, $field, $types, $prefix) = @_; - delete Bugzilla->request_cache->{filter_wants}; - return filter_wants($params, $field, $types, $prefix); + my ($params, $field, $types, $prefix) = @_; + delete Bugzilla->request_cache->{filter_wants}; + return filter_wants($params, $field, $types, $prefix); } sub filter { - my ($params, $hash, $types, $prefix) = @_; - my %newhash = %$hash; - foreach my $key (keys %$hash) { - delete $newhash{$key} if !filter_wants_nocache($params, $key, $types, $prefix); - } - return \%newhash; + my ($params, $hash, $types, $prefix) = @_; + my %newhash = %$hash; + foreach my $key (keys %$hash) { + delete $newhash{$key} if !filter_wants_nocache($params, $key, $types, $prefix); + } + return \%newhash; } sub fix_credentials { - my ($params) = @_; - # Allow user to pass in username=foo&password=bar to be compatible - $params->{'Bugzilla_login'} = $params->{'login'} = delete $params->{'username'} - if exists $params->{'username'}; - $params->{'Bugzilla_password'} = $params->{'password'} if exists $params->{'password'}; - - # Allow user to pass userid=1&cookie=3iYGuKZdyz for compatibility with BzAPI - if (exists $params->{'userid'} && exists $params->{'cookie'}) { - my $userid = delete $params->{'userid'}; - my $cookie = delete $params->{'cookie'}; - $params->{'Bugzilla_token'} = "${userid}-${cookie}"; - } + my ($params) = @_; + + # Allow user to pass in username=foo&password=bar to be compatible + $params->{'Bugzilla_login'} = $params->{'login'} = delete $params->{'username'} + if exists $params->{'username'}; + $params->{'Bugzilla_password'} = $params->{'password'} + if exists $params->{'password'}; + + # Allow user to pass userid=1&cookie=3iYGuKZdyz for compatibility with BzAPI + if (exists $params->{'userid'} && exists $params->{'cookie'}) { + my $userid = delete $params->{'userid'}; + my $cookie = delete $params->{'cookie'}; + $params->{'Bugzilla_token'} = "${userid}-${cookie}"; + } } # Filter email addresses by default ignoring the system # webservice_email_filter setting sub filter_email { - my $rpc = Bugzilla->request_cache->{bzapi_rpc}; - return $rpc->type('string', email_filter($_[0])); + my $rpc = Bugzilla->request_cache->{bzapi_rpc}; + return $rpc->type('string', email_filter($_[0])); } 1; |