aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'Bugzilla/WebService/Bug.pm')
-rw-r--r--Bugzilla/WebService/Bug.pm2295
1 files changed, 1155 insertions, 1140 deletions
diff --git a/Bugzilla/WebService/Bug.pm b/Bugzilla/WebService/Bug.pm
index b07d3cb01..04c4eae2e 100644
--- a/Bugzilla/WebService/Bug.pm
+++ b/Bugzilla/WebService/Bug.pm
@@ -19,7 +19,8 @@ use Bugzilla::Constants;
use Bugzilla::Error;
use Bugzilla::Field;
use Bugzilla::WebService::Constants;
-use Bugzilla::WebService::Util qw(extract_flags filter filter_wants validate translate);
+use Bugzilla::WebService::Util
+ qw(extract_flags filter filter_wants validate translate);
use Bugzilla::Bug;
use Bugzilla::BugMail;
use Bugzilla::Util qw(trick_taint trim diff_arrays detaint_natural);
@@ -43,58 +44,54 @@ use Storable qw(dclone);
use constant PRODUCT_SPECIFIC_FIELDS => qw(version target_milestone component);
use constant DATE_FIELDS => {
- comments => ['new_since'],
- history => ['new_since'],
- search => ['last_change_time', 'creation_time'],
+ comments => ['new_since'],
+ history => ['new_since'],
+ search => ['last_change_time', 'creation_time'],
};
-use constant BASE64_FIELDS => {
- add_attachment => ['data'],
-};
+use constant BASE64_FIELDS => {add_attachment => ['data'],};
use constant READ_ONLY => qw(
- attachments
- comments
- fields
- get
- history
- legal_values
- search
+ attachments
+ comments
+ fields
+ get
+ history
+ legal_values
+ search
);
use constant PUBLIC_METHODS => qw(
- add_attachment
- add_comment
- attachments
- comments
- create
- fields
- get
- history
- legal_values
- possible_duplicates
- render_comment
- search
- search_comment_tags
- update
- update_attachment
- update_comment_tags
- update_see_also
- update_tags
+ add_attachment
+ add_comment
+ attachments
+ comments
+ create
+ fields
+ get
+ history
+ legal_values
+ possible_duplicates
+ render_comment
+ search
+ search_comment_tags
+ update
+ update_attachment
+ update_comment_tags
+ update_see_also
+ update_tags
);
-use constant ATTACHMENT_MAPPED_SETTERS => {
- file_name => 'filename',
- summary => 'description',
-};
+use constant ATTACHMENT_MAPPED_SETTERS =>
+ {file_name => 'filename', summary => 'description',};
use constant ATTACHMENT_MAPPED_RETURNS => {
- description => 'summary',
- ispatch => 'is_patch',
- isprivate => 'is_private',
- isobsolete => 'is_obsolete',
- filename => 'file_name',
- mimetype => 'content_type',
+ description => 'summary',
+ ispatch => 'is_patch',
+ isprivate => 'is_private',
+ isobsolete => 'is_obsolete',
+ filename => 'file_name',
+ mimetype => 'content_type',
};
###########
@@ -102,1089 +99,1101 @@ use constant ATTACHMENT_MAPPED_RETURNS => {
###########
sub fields {
- my ($self, $params) = validate(@_, 'ids', 'names');
+ my ($self, $params) = validate(@_, 'ids', 'names');
- Bugzilla->switch_to_shadow_db();
+ Bugzilla->switch_to_shadow_db();
- my @fields;
- if (defined $params->{ids}) {
- my $ids = $params->{ids};
- foreach my $id (@$ids) {
- my $loop_field = Bugzilla::Field->check({ id => $id });
- push(@fields, $loop_field);
- }
+ my @fields;
+ if (defined $params->{ids}) {
+ my $ids = $params->{ids};
+ foreach my $id (@$ids) {
+ my $loop_field = Bugzilla::Field->check({id => $id});
+ push(@fields, $loop_field);
}
-
- if (defined $params->{names}) {
- my $names = $params->{names};
- foreach my $field_name (@$names) {
- my $loop_field = Bugzilla::Field->check($field_name);
- # Don't push in duplicate fields if we also asked for this field
- # in "ids".
- if (!grep($_->id == $loop_field->id, @fields)) {
- push(@fields, $loop_field);
- }
- }
+ }
+
+ if (defined $params->{names}) {
+ my $names = $params->{names};
+ foreach my $field_name (@$names) {
+ my $loop_field = Bugzilla::Field->check($field_name);
+
+ # Don't push in duplicate fields if we also asked for this field
+ # in "ids".
+ if (!grep($_->id == $loop_field->id, @fields)) {
+ push(@fields, $loop_field);
+ }
}
-
- if (!defined $params->{ids} and !defined $params->{names}) {
- @fields = @{ Bugzilla->fields({ obsolete => 0 }) };
+ }
+
+ if (!defined $params->{ids} and !defined $params->{names}) {
+ @fields = @{Bugzilla->fields({obsolete => 0})};
+ }
+
+ my @fields_out;
+ foreach my $field (@fields) {
+ my $visibility_field
+ = $field->visibility_field ? $field->visibility_field->name : undef;
+ my $vis_values = $field->visibility_values;
+ my $value_field = $field->value_field ? $field->value_field->name : undef;
+
+ my (@values, $has_values);
+ if ( ($field->is_select and $field->name ne 'product')
+ or grep($_ eq $field->name, PRODUCT_SPECIFIC_FIELDS)
+ or $field->name eq 'keywords')
+ {
+ $has_values = 1;
+ @values = @{$self->_legal_field_values({field => $field})};
}
- my @fields_out;
- foreach my $field (@fields) {
- my $visibility_field = $field->visibility_field
- ? $field->visibility_field->name : undef;
- my $vis_values = $field->visibility_values;
- my $value_field = $field->value_field
- ? $field->value_field->name : undef;
-
- my (@values, $has_values);
- if ( ($field->is_select and $field->name ne 'product')
- or grep($_ eq $field->name, PRODUCT_SPECIFIC_FIELDS)
- or $field->name eq 'keywords')
- {
- $has_values = 1;
- @values = @{ $self->_legal_field_values({ field => $field }) };
- }
-
- if (grep($_ eq $field->name, PRODUCT_SPECIFIC_FIELDS)) {
- $value_field = 'product';
- }
+ if (grep($_ eq $field->name, PRODUCT_SPECIFIC_FIELDS)) {
+ $value_field = 'product';
+ }
- my %field_data = (
- id => $self->type('int', $field->id),
- type => $self->type('int', $field->type),
- is_custom => $self->type('boolean', $field->custom),
- name => $self->type('string', $field->name),
- display_name => $self->type('string', $field->description),
- is_mandatory => $self->type('boolean', $field->is_mandatory),
- is_on_bug_entry => $self->type('boolean', $field->enter_bug),
- visibility_field => $self->type('string', $visibility_field),
- visibility_values =>
- [ map { $self->type('string', $_->name) } @$vis_values ],
- );
- if ($has_values) {
- $field_data{value_field} = $self->type('string', $value_field);
- $field_data{values} = \@values;
- };
- push(@fields_out, filter $params, \%field_data);
+ my %field_data = (
+ id => $self->type('int', $field->id),
+ type => $self->type('int', $field->type),
+ is_custom => $self->type('boolean', $field->custom),
+ name => $self->type('string', $field->name),
+ display_name => $self->type('string', $field->description),
+ is_mandatory => $self->type('boolean', $field->is_mandatory),
+ is_on_bug_entry => $self->type('boolean', $field->enter_bug),
+ visibility_field => $self->type('string', $visibility_field),
+ visibility_values => [map { $self->type('string', $_->name) } @$vis_values],
+ );
+ if ($has_values) {
+ $field_data{value_field} = $self->type('string', $value_field);
+ $field_data{values} = \@values;
}
+ push(@fields_out, filter $params, \%field_data);
+ }
- return { fields => \@fields_out };
+ return {fields => \@fields_out};
}
sub _legal_field_values {
- my ($self, $params) = @_;
- my $field = $params->{field};
- my $field_name = $field->name;
- my $user = Bugzilla->user;
-
- my @result;
- if (grep($_ eq $field_name, PRODUCT_SPECIFIC_FIELDS)) {
- my @list;
- if ($field_name eq 'version') {
- @list = Bugzilla::Version->get_all;
- }
- elsif ($field_name eq 'component') {
- @list = Bugzilla::Component->get_all;
- }
- else {
- @list = Bugzilla::Milestone->get_all;
- }
+ my ($self, $params) = @_;
+ my $field = $params->{field};
+ my $field_name = $field->name;
+ my $user = Bugzilla->user;
+
+ my @result;
+ if (grep($_ eq $field_name, PRODUCT_SPECIFIC_FIELDS)) {
+ my @list;
+ if ($field_name eq 'version') {
+ @list = Bugzilla::Version->get_all;
+ }
+ elsif ($field_name eq 'component') {
+ @list = Bugzilla::Component->get_all;
+ }
+ else {
+ @list = Bugzilla::Milestone->get_all;
+ }
- foreach my $value (@list) {
- my $sortkey = $field_name eq 'target_milestone'
- ? $value->sortkey : 0;
- # XXX This is very slow for large numbers of values.
- my $product_name = $value->product->name;
- if ($user->can_see_product($product_name)) {
- push(@result, {
- name => $self->type('string', $value->name),
- sort_key => $self->type('int', $sortkey),
- sortkey => $self->type('int', $sortkey), # deprecated
- visibility_values => [$self->type('string', $product_name)],
- is_active => $self->type('boolean', $value->is_active),
- });
- }
- }
+ foreach my $value (@list) {
+ my $sortkey = $field_name eq 'target_milestone' ? $value->sortkey : 0;
+
+ # XXX This is very slow for large numbers of values.
+ my $product_name = $value->product->name;
+ if ($user->can_see_product($product_name)) {
+ push(
+ @result,
+ {
+ name => $self->type('string', $value->name),
+ sort_key => $self->type('int', $sortkey),
+ sortkey => $self->type('int', $sortkey), # deprecated
+ visibility_values => [$self->type('string', $product_name)],
+ is_active => $self->type('boolean', $value->is_active),
+ }
+ );
+ }
}
+ }
+
+ elsif ($field_name eq 'bug_status') {
+ my @status_all = Bugzilla::Status->get_all;
+ my $initial_status = bless(
+ {
+ id => 0,
+ name => '',
+ is_open => 1,
+ sortkey => 0,
+ can_change_to => Bugzilla::Status->can_change_to
+ },
+ 'Bugzilla::Status'
+ );
+ unshift(@status_all, $initial_status);
+
+ foreach my $status (@status_all) {
+ my @can_change_to;
+ foreach my $change_to (@{$status->can_change_to}) {
+
+ # There's no need to note that a status can transition
+ # to itself.
+ next if $change_to->id == $status->id;
+ my %change_to_hash = (
+ name => $self->type('string', $change_to->name),
+ comment_required =>
+ $self->type('boolean', $change_to->comment_required_on_change_from($status)),
+ );
+ push(@can_change_to, \%change_to_hash);
+ }
- elsif ($field_name eq 'bug_status') {
- my @status_all = Bugzilla::Status->get_all;
- my $initial_status = bless({ id => 0, name => '', is_open => 1, sortkey => 0,
- can_change_to => Bugzilla::Status->can_change_to },
- 'Bugzilla::Status');
- unshift(@status_all, $initial_status);
-
- foreach my $status (@status_all) {
- my @can_change_to;
- foreach my $change_to (@{ $status->can_change_to }) {
- # There's no need to note that a status can transition
- # to itself.
- next if $change_to->id == $status->id;
- my %change_to_hash = (
- name => $self->type('string', $change_to->name),
- comment_required => $self->type('boolean',
- $change_to->comment_required_on_change_from($status)),
- );
- push(@can_change_to, \%change_to_hash);
- }
-
- push (@result, {
- name => $self->type('string', $status->name),
- is_open => $self->type('boolean', $status->is_open),
- sort_key => $self->type('int', $status->sortkey),
- sortkey => $self->type('int', $status->sortkey), # deprecated
- can_change_to => \@can_change_to,
- visibility_values => [],
- });
+ push(
+ @result,
+ {
+ name => $self->type('string', $status->name),
+ is_open => $self->type('boolean', $status->is_open),
+ sort_key => $self->type('int', $status->sortkey),
+ sortkey => $self->type('int', $status->sortkey), # deprecated
+ can_change_to => \@can_change_to,
+ visibility_values => [],
}
+ );
}
+ }
- elsif ($field_name eq 'keywords') {
- my @legal_keywords = Bugzilla::Keyword->get_all;
- foreach my $value (@legal_keywords) {
- push (@result, {
- name => $self->type('string', $value->name),
- description => $self->type('string', $value->description),
- });
+ elsif ($field_name eq 'keywords') {
+ my @legal_keywords = Bugzilla::Keyword->get_all;
+ foreach my $value (@legal_keywords) {
+ push(
+ @result,
+ {
+ name => $self->type('string', $value->name),
+ description => $self->type('string', $value->description),
}
+ );
}
- else {
- my @values = Bugzilla::Field::Choice->type($field)->get_all();
- foreach my $value (@values) {
- my $vis_val = $value->visibility_value;
- push(@result, {
- name => $self->type('string', $value->name),
- sort_key => $self->type('int' , $value->sortkey),
- sortkey => $self->type('int' , $value->sortkey), # deprecated
- visibility_values => [
- defined $vis_val ? $self->type('string', $vis_val->name)
- : ()
- ],
- });
+ }
+ else {
+ my @values = Bugzilla::Field::Choice->type($field)->get_all();
+ foreach my $value (@values) {
+ my $vis_val = $value->visibility_value;
+ push(
+ @result,
+ {
+ name => $self->type('string', $value->name),
+ sort_key => $self->type('int', $value->sortkey),
+ sortkey => $self->type('int', $value->sortkey), # deprecated
+ visibility_values =>
+ [defined $vis_val ? $self->type('string', $vis_val->name) : ()],
}
+ );
}
+ }
- return \@result;
+ return \@result;
}
sub comments {
- my ($self, $params) = validate(@_, 'ids', 'comment_ids');
+ my ($self, $params) = validate(@_, 'ids', 'comment_ids');
- if (!(defined $params->{ids} || defined $params->{comment_ids})) {
- ThrowCodeError('params_required',
- { function => 'Bug.comments',
- params => ['ids', 'comment_ids'] });
- }
+ if (!(defined $params->{ids} || defined $params->{comment_ids})) {
+ ThrowCodeError('params_required',
+ {function => 'Bug.comments', params => ['ids', 'comment_ids']});
+ }
- my $bug_ids = $params->{ids} || [];
- my $comment_ids = $params->{comment_ids} || [];
-
- my $dbh = Bugzilla->switch_to_shadow_db();
- my $user = Bugzilla->user;
-
- my %bugs;
- foreach my $bug_id (@$bug_ids) {
- my $bug = Bugzilla::Bug->check($bug_id);
- # We want the API to always return comments in the same order.
-
- my $comments = $bug->comments({ order => 'oldest_to_newest',
- after => $params->{new_since} });
- my @result;
- foreach my $comment (@$comments) {
- next if $comment->is_private && !$user->is_insider;
- push(@result, $self->_translate_comment($comment, $params));
- }
- $bugs{$bug->id}{'comments'} = \@result;
+ my $bug_ids = $params->{ids} || [];
+ my $comment_ids = $params->{comment_ids} || [];
+
+ my $dbh = Bugzilla->switch_to_shadow_db();
+ my $user = Bugzilla->user;
+
+ my %bugs;
+ foreach my $bug_id (@$bug_ids) {
+ my $bug = Bugzilla::Bug->check($bug_id);
+
+ # We want the API to always return comments in the same order.
+
+ my $comments = $bug->comments(
+ {order => 'oldest_to_newest', after => $params->{new_since}});
+ my @result;
+ foreach my $comment (@$comments) {
+ next if $comment->is_private && !$user->is_insider;
+ push(@result, $self->_translate_comment($comment, $params));
+ }
+ $bugs{$bug->id}{'comments'} = \@result;
+ }
+
+ my %comments;
+ if (scalar @$comment_ids) {
+ my @ids = map { trim($_) } @$comment_ids;
+ my $comment_data = Bugzilla::Comment->new_from_list(\@ids);
+
+ # See if we were passed any invalid comment ids.
+ my %got_ids = map { $_->id => 1 } @$comment_data;
+ foreach my $comment_id (@ids) {
+ if (!$got_ids{$comment_id}) {
+ ThrowUserError('comment_id_invalid', {id => $comment_id});
+ }
}
- my %comments;
- if (scalar @$comment_ids) {
- my @ids = map { trim($_) } @$comment_ids;
- my $comment_data = Bugzilla::Comment->new_from_list(\@ids);
-
- # See if we were passed any invalid comment ids.
- my %got_ids = map { $_->id => 1 } @$comment_data;
- foreach my $comment_id (@ids) {
- if (!$got_ids{$comment_id}) {
- ThrowUserError('comment_id_invalid', { id => $comment_id });
- }
- }
-
- # Now make sure that we can see all the associated bugs.
- my %got_bug_ids = map { $_->bug_id => 1 } @$comment_data;
- Bugzilla::Bug->check($_) foreach (keys %got_bug_ids);
-
- foreach my $comment (@$comment_data) {
- if ($comment->is_private && !$user->is_insider) {
- ThrowUserError('comment_is_private', { id => $comment->id });
- }
- $comments{$comment->id} =
- $self->_translate_comment($comment, $params);
- }
+ # Now make sure that we can see all the associated bugs.
+ my %got_bug_ids = map { $_->bug_id => 1 } @$comment_data;
+ Bugzilla::Bug->check($_) foreach (keys %got_bug_ids);
+
+ foreach my $comment (@$comment_data) {
+ if ($comment->is_private && !$user->is_insider) {
+ ThrowUserError('comment_is_private', {id => $comment->id});
+ }
+ $comments{$comment->id} = $self->_translate_comment($comment, $params);
}
+ }
- return { bugs => \%bugs, comments => \%comments };
+ return {bugs => \%bugs, comments => \%comments};
}
sub render_comment {
- my ($self, $params) = @_;
+ my ($self, $params) = @_;
- unless (defined $params->{text}) {
- ThrowCodeError('params_required',
- { function => 'Bug.render_comment',
- params => ['text'] });
- }
+ unless (defined $params->{text}) {
+ ThrowCodeError('params_required',
+ {function => 'Bug.render_comment', params => ['text']});
+ }
- Bugzilla->switch_to_shadow_db();
- my $bug = $params->{id} ? Bugzilla::Bug->check($params->{id}) : undef;
+ Bugzilla->switch_to_shadow_db();
+ my $bug = $params->{id} ? Bugzilla::Bug->check($params->{id}) : undef;
- my $tmpl = '[% text FILTER quoteUrls(bug) %]';
- my $html;
- my $template = Bugzilla->template;
- $template->process(
- \$tmpl,
- { bug => $bug, text => $params->{text}},
- \$html
- );
+ my $tmpl = '[% text FILTER quoteUrls(bug) %]';
+ my $html;
+ my $template = Bugzilla->template;
+ $template->process(\$tmpl, {bug => $bug, text => $params->{text}}, \$html);
- return { html => $html };
+ return {html => $html};
}
# Helper for Bug.comments
sub _translate_comment {
- my ($self, $comment, $filters, $types, $prefix) = @_;
- my $attach_id = $comment->is_about_attachment ? $comment->extra_data
- : undef;
-
- my $comment_hash = {
- id => $self->type('int', $comment->id),
- bug_id => $self->type('int', $comment->bug_id),
- creator => $self->type('email', $comment->author->login),
- time => $self->type('dateTime', $comment->creation_ts),
- creation_time => $self->type('dateTime', $comment->creation_ts),
- is_private => $self->type('boolean', $comment->is_private),
- text => $self->type('string', $comment->body_full),
- attachment_id => $self->type('int', $attach_id),
- count => $self->type('int', $comment->count),
- };
-
- # Don't load comment tags unless enabled
- if (Bugzilla->params->{'comment_taggers_group'}) {
- $comment_hash->{tags} = [
- map { $self->type('string', $_) }
- @{ $comment->tags }
- ];
- }
-
- return filter($filters, $comment_hash, $types, $prefix);
+ my ($self, $comment, $filters, $types, $prefix) = @_;
+ my $attach_id = $comment->is_about_attachment ? $comment->extra_data : undef;
+
+ my $comment_hash = {
+ id => $self->type('int', $comment->id),
+ bug_id => $self->type('int', $comment->bug_id),
+ creator => $self->type('email', $comment->author->login),
+ time => $self->type('dateTime', $comment->creation_ts),
+ creation_time => $self->type('dateTime', $comment->creation_ts),
+ is_private => $self->type('boolean', $comment->is_private),
+ text => $self->type('string', $comment->body_full),
+ attachment_id => $self->type('int', $attach_id),
+ count => $self->type('int', $comment->count),
+ };
+
+ # Don't load comment tags unless enabled
+ if (Bugzilla->params->{'comment_taggers_group'}) {
+ $comment_hash->{tags} = [map { $self->type('string', $_) } @{$comment->tags}];
+ }
+
+ return filter($filters, $comment_hash, $types, $prefix);
}
sub get {
- my ($self, $params) = validate(@_, 'ids');
-
- Bugzilla->switch_to_shadow_db() unless Bugzilla->user->id;
-
- my $ids = $params->{ids};
- defined $ids || ThrowCodeError('param_required', { param => 'ids' });
-
- my (@bugs, @faults, @hashes);
-
- # Cache permissions for bugs. This highly reduces the number of calls to the DB.
- # visible_bugs() is only able to handle bug IDs, so we have to skip aliases.
- my @int = grep { $_ =~ /^\d+$/ } @$ids;
- Bugzilla->user->visible_bugs(\@int);
-
- foreach my $bug_id (@$ids) {
- my $bug;
- if ($params->{permissive}) {
- eval { $bug = Bugzilla::Bug->check($bug_id); };
- if ($@) {
- push(@faults, {id => $bug_id,
- faultString => $@->faultstring,
- faultCode => $@->faultcode,
- }
- );
- undef $@;
- next;
- }
- }
- else {
- $bug = Bugzilla::Bug->check($bug_id);
- }
- push(@bugs, $bug);
- push(@hashes, $self->_bug_to_hash($bug, $params));
+ my ($self, $params) = validate(@_, 'ids');
+
+ Bugzilla->switch_to_shadow_db() unless Bugzilla->user->id;
+
+ my $ids = $params->{ids};
+ defined $ids || ThrowCodeError('param_required', {param => 'ids'});
+
+ my (@bugs, @faults, @hashes);
+
+ # Cache permissions for bugs. This highly reduces the number of calls to the DB.
+ # visible_bugs() is only able to handle bug IDs, so we have to skip aliases.
+ my @int = grep { $_ =~ /^\d+$/ } @$ids;
+ Bugzilla->user->visible_bugs(\@int);
+
+ foreach my $bug_id (@$ids) {
+ my $bug;
+ if ($params->{permissive}) {
+ eval { $bug = Bugzilla::Bug->check($bug_id); };
+ if ($@) {
+ push(@faults,
+ {id => $bug_id, faultString => $@->faultstring, faultCode => $@->faultcode,});
+ undef $@;
+ next;
+ }
}
+ else {
+ $bug = Bugzilla::Bug->check($bug_id);
+ }
+ push(@bugs, $bug);
+ push(@hashes, $self->_bug_to_hash($bug, $params));
+ }
- # Set the ETag before inserting the update tokens
- # since the tokens will always be unique even if
- # the data has not changed.
- $self->bz_etag(\@hashes);
+ # Set the ETag before inserting the update tokens
+ # since the tokens will always be unique even if
+ # the data has not changed.
+ $self->bz_etag(\@hashes);
- $self->_add_update_tokens($params, \@bugs, \@hashes);
+ $self->_add_update_tokens($params, \@bugs, \@hashes);
- return { bugs => \@hashes, faults => \@faults };
+ return {bugs => \@hashes, faults => \@faults};
}
-# this is a function that gets bug activity for list of bug ids
+# this is a function that gets bug activity for list of bug ids
# it can be called as the following:
# $call = $rpc->call( 'Bug.history', { ids => [1,2] });
sub history {
- my ($self, $params) = validate(@_, 'ids');
-
- Bugzilla->switch_to_shadow_db();
-
- my $ids = $params->{ids};
- defined $ids || ThrowCodeError('param_required', { param => 'ids' });
-
- my %api_name = reverse %{ Bugzilla::Bug::FIELD_MAP() };
- $api_name{'bug_group'} = 'groups';
-
- my @return;
- foreach my $bug_id (@$ids) {
- my %item;
- my $bug = Bugzilla::Bug->check($bug_id);
- $bug_id = $bug->id;
- $item{id} = $self->type('int', $bug_id);
-
- my ($activity) = $bug->get_activity(undef, $params->{new_since});
-
- my @history;
- foreach my $changeset (@$activity) {
- my %bug_history;
- $bug_history{when} = $self->type('dateTime', $changeset->{when});
- $bug_history{who} = $self->type('string', $changeset->{who});
- $bug_history{changes} = [];
- foreach my $change (@{ $changeset->{changes} }) {
- my $api_field = $api_name{$change->{fieldname}} || $change->{fieldname};
- my $attach_id = delete $change->{attachid};
- if ($attach_id) {
- $change->{attachment_id} = $self->type('int', $attach_id);
- }
- $change->{removed} = $self->type('string', $change->{removed});
- $change->{added} = $self->type('string', $change->{added});
- $change->{field_name} = $self->type('string', $api_field);
- delete $change->{fieldname};
- push (@{$bug_history{changes}}, $change);
- }
-
- push (@history, \%bug_history);
+ my ($self, $params) = validate(@_, 'ids');
+
+ Bugzilla->switch_to_shadow_db();
+
+ my $ids = $params->{ids};
+ defined $ids || ThrowCodeError('param_required', {param => 'ids'});
+
+ my %api_name = reverse %{Bugzilla::Bug::FIELD_MAP()};
+ $api_name{'bug_group'} = 'groups';
+
+ my @return;
+ foreach my $bug_id (@$ids) {
+ my %item;
+ my $bug = Bugzilla::Bug->check($bug_id);
+ $bug_id = $bug->id;
+ $item{id} = $self->type('int', $bug_id);
+
+ my ($activity) = $bug->get_activity(undef, $params->{new_since});
+
+ my @history;
+ foreach my $changeset (@$activity) {
+ my %bug_history;
+ $bug_history{when} = $self->type('dateTime', $changeset->{when});
+ $bug_history{who} = $self->type('string', $changeset->{who});
+ $bug_history{changes} = [];
+ foreach my $change (@{$changeset->{changes}}) {
+ my $api_field = $api_name{$change->{fieldname}} || $change->{fieldname};
+ my $attach_id = delete $change->{attachid};
+ if ($attach_id) {
+ $change->{attachment_id} = $self->type('int', $attach_id);
}
+ $change->{removed} = $self->type('string', $change->{removed});
+ $change->{added} = $self->type('string', $change->{added});
+ $change->{field_name} = $self->type('string', $api_field);
+ delete $change->{fieldname};
+ push(@{$bug_history{changes}}, $change);
+ }
+
+ push(@history, \%bug_history);
+ }
- $item{history} = \@history;
+ $item{history} = \@history;
- # alias is returned in case users passes a mixture of ids and aliases
- # then they get to know which bug activity relates to which value
- # they passed
- $item{alias} = [ map { $self->type('string', $_) } @{ $bug->alias } ];
+ # alias is returned in case users passes a mixture of ids and aliases
+ # then they get to know which bug activity relates to which value
+ # they passed
+ $item{alias} = [map { $self->type('string', $_) } @{$bug->alias}];
- push(@return, \%item);
- }
+ push(@return, \%item);
+ }
- return { bugs => \@return };
+ return {bugs => \@return};
}
sub search {
- my ($self, $params) = @_;
- my $user = Bugzilla->user;
- my $dbh = Bugzilla->dbh;
-
- Bugzilla->switch_to_shadow_db();
-
- my $match_params = dclone($params);
- delete $match_params->{include_fields};
- delete $match_params->{exclude_fields};
-
- # Determine whether this is a quicksearch query
- if (exists $match_params->{quicksearch}) {
- my $quicksearch = quicksearch($match_params->{'quicksearch'});
- my $cgi = Bugzilla::CGI->new($quicksearch);
- $match_params = $cgi->Vars;
- }
-
- if ( defined($match_params->{offset}) and !defined($match_params->{limit}) ) {
- ThrowCodeError('param_required',
- { param => 'limit', function => 'Bug.search()' });
- }
-
- my $max_results = Bugzilla->params->{max_search_results};
- unless (defined $match_params->{limit} && $match_params->{limit} == 0) {
- if (!defined $match_params->{limit} || $match_params->{limit} > $max_results) {
- $match_params->{limit} = $max_results;
- }
- }
- else {
- delete $match_params->{limit};
- delete $match_params->{offset};
- }
+ my ($self, $params) = @_;
+ my $user = Bugzilla->user;
+ my $dbh = Bugzilla->dbh;
- $match_params = Bugzilla::Bug::map_fields($match_params);
+ Bugzilla->switch_to_shadow_db();
- my %options = ( fields => ['bug_id'] );
+ my $match_params = dclone($params);
+ delete $match_params->{include_fields};
+ delete $match_params->{exclude_fields};
- # Find the highest custom field id
- my @field_ids = grep(/^f(\d+)$/, keys %$match_params);
- my $last_field_id = @field_ids ? max @field_ids + 1 : 1;
+ # Determine whether this is a quicksearch query
+ if (exists $match_params->{quicksearch}) {
+ my $quicksearch = quicksearch($match_params->{'quicksearch'});
+ my $cgi = Bugzilla::CGI->new($quicksearch);
+ $match_params = $cgi->Vars;
+ }
- # Do special search types for certain fields.
- if (my $change_when = delete $match_params->{'delta_ts'}) {
- $match_params->{"f${last_field_id}"} = 'delta_ts';
- $match_params->{"o${last_field_id}"} = 'greaterthaneq';
- $match_params->{"v${last_field_id}"} = $change_when;
- $last_field_id++;
- }
- if (my $creation_when = delete $match_params->{'creation_ts'}) {
- $match_params->{"f${last_field_id}"} = 'creation_ts';
- $match_params->{"o${last_field_id}"} = 'greaterthaneq';
- $match_params->{"v${last_field_id}"} = $creation_when;
- $last_field_id++;
- }
-
- # Some fields require a search type such as short desc, keywords, etc.
- foreach my $param (qw(short_desc longdesc status_whiteboard bug_file_loc)) {
- if (defined $match_params->{$param} && !defined $match_params->{$param . '_type'}) {
- $match_params->{$param . '_type'} = 'allwordssubstr';
- }
- }
- if (defined $match_params->{'keywords'} && !defined $match_params->{'keywords_type'}) {
- $match_params->{'keywords_type'} = 'allwords';
- }
+ if (defined($match_params->{offset}) and !defined($match_params->{limit})) {
+ ThrowCodeError('param_required',
+ {param => 'limit', function => 'Bug.search()'});
+ }
- # Backwards compatibility with old method regarding role search
- $match_params->{'reporter'} = delete $match_params->{'creator'} if $match_params->{'creator'};
- foreach my $role (qw(assigned_to reporter qa_contact longdesc cc)) {
- next if !exists $match_params->{$role};
- my $value = delete $match_params->{$role};
- $match_params->{"f${last_field_id}"} = $role;
- $match_params->{"o${last_field_id}"} = "anywordssubstr";
- $match_params->{"v${last_field_id}"} = ref $value ? join(" ", @{$value}) : $value;
- $last_field_id++;
+ my $max_results = Bugzilla->params->{max_search_results};
+ unless (defined $match_params->{limit} && $match_params->{limit} == 0) {
+ if (!defined $match_params->{limit} || $match_params->{limit} > $max_results) {
+ $match_params->{limit} = $max_results;
}
-
- # If no other parameters have been passed other than limit and offset
- # then we throw error if system is configured to do so.
- if (!grep(!/^(limit|offset)$/, keys %$match_params)
- && !Bugzilla->params->{search_allow_no_criteria})
+ }
+ else {
+ delete $match_params->{limit};
+ delete $match_params->{offset};
+ }
+
+ $match_params = Bugzilla::Bug::map_fields($match_params);
+
+ my %options = (fields => ['bug_id']);
+
+ # Find the highest custom field id
+ my @field_ids = grep(/^f(\d+)$/, keys %$match_params);
+ my $last_field_id = @field_ids ? max @field_ids + 1 : 1;
+
+ # Do special search types for certain fields.
+ if (my $change_when = delete $match_params->{'delta_ts'}) {
+ $match_params->{"f${last_field_id}"} = 'delta_ts';
+ $match_params->{"o${last_field_id}"} = 'greaterthaneq';
+ $match_params->{"v${last_field_id}"} = $change_when;
+ $last_field_id++;
+ }
+ if (my $creation_when = delete $match_params->{'creation_ts'}) {
+ $match_params->{"f${last_field_id}"} = 'creation_ts';
+ $match_params->{"o${last_field_id}"} = 'greaterthaneq';
+ $match_params->{"v${last_field_id}"} = $creation_when;
+ $last_field_id++;
+ }
+
+ # Some fields require a search type such as short desc, keywords, etc.
+ foreach my $param (qw(short_desc longdesc status_whiteboard bug_file_loc)) {
+ if (defined $match_params->{$param}
+ && !defined $match_params->{$param . '_type'})
{
- ThrowUserError('buglist_parameters_required');
+ $match_params->{$param . '_type'} = 'allwordssubstr';
}
-
- $options{order} = [ split(/\s*,\s*/, delete $match_params->{order}) ] if $match_params->{order};
- $options{params} = $match_params;
-
- my $search = new Bugzilla::Search(%options);
- my ($data) = $search->data;
-
- if (!scalar @$data) {
- return { bugs => [] };
- }
-
- # Search.pm won't return bugs that the user shouldn't see so no filtering is needed.
- my @bug_ids = map { $_->[0] } @$data;
- my %bug_objects = map { $_->id => $_ } @{ Bugzilla::Bug->new_from_list(\@bug_ids) };
- my @bugs = map { $bug_objects{$_} } @bug_ids;
- @bugs = map { $self->_bug_to_hash($_, $params) } @bugs;
-
- return { bugs => \@bugs };
+ }
+ if (defined $match_params->{'keywords'}
+ && !defined $match_params->{'keywords_type'})
+ {
+ $match_params->{'keywords_type'} = 'allwords';
+ }
+
+ # Backwards compatibility with old method regarding role search
+ $match_params->{'reporter'} = delete $match_params->{'creator'}
+ if $match_params->{'creator'};
+ foreach my $role (qw(assigned_to reporter qa_contact longdesc cc)) {
+ next if !exists $match_params->{$role};
+ my $value = delete $match_params->{$role};
+ $match_params->{"f${last_field_id}"} = $role;
+ $match_params->{"o${last_field_id}"} = "anywordssubstr";
+ $match_params->{"v${last_field_id}"}
+ = ref $value ? join(" ", @{$value}) : $value;
+ $last_field_id++;
+ }
+
+ # If no other parameters have been passed other than limit and offset
+ # then we throw error if system is configured to do so.
+ if ( !grep(!/^(limit|offset)$/, keys %$match_params)
+ && !Bugzilla->params->{search_allow_no_criteria})
+ {
+ ThrowUserError('buglist_parameters_required');
+ }
+
+ $options{order} = [split(/\s*,\s*/, delete $match_params->{order})]
+ if $match_params->{order};
+ $options{params} = $match_params;
+
+ my $search = new Bugzilla::Search(%options);
+ my ($data) = $search->data;
+
+ if (!scalar @$data) {
+ return {bugs => []};
+ }
+
+# Search.pm won't return bugs that the user shouldn't see so no filtering is needed.
+ my @bug_ids = map { $_->[0] } @$data;
+ my %bug_objects
+ = map { $_->id => $_ } @{Bugzilla::Bug->new_from_list(\@bug_ids)};
+ my @bugs = map { $bug_objects{$_} } @bug_ids;
+ @bugs = map { $self->_bug_to_hash($_, $params) } @bugs;
+
+ return {bugs => \@bugs};
}
sub possible_duplicates {
- my ($self, $params) = validate(@_, 'products');
- my $user = Bugzilla->user;
-
- Bugzilla->switch_to_shadow_db();
-
- # Undo the array-ification that validate() does, for "summary".
- $params->{summary} || ThrowCodeError('param_required',
- { function => 'Bug.possible_duplicates', param => 'summary' });
-
- my @products;
- foreach my $name (@{ $params->{'products'} || [] }) {
- my $object = $user->can_enter_product($name, THROW_ERROR);
- push(@products, $object);
- }
-
- my $possible_dupes = Bugzilla::Bug->possible_duplicates(
- { summary => $params->{summary}, products => \@products,
- limit => $params->{limit} });
- my @hashes = map { $self->_bug_to_hash($_, $params) } @$possible_dupes;
- $self->_add_update_tokens($params, $possible_dupes, \@hashes);
- return { bugs => \@hashes };
+ my ($self, $params) = validate(@_, 'products');
+ my $user = Bugzilla->user;
+
+ Bugzilla->switch_to_shadow_db();
+
+ # Undo the array-ification that validate() does, for "summary".
+ $params->{summary}
+ || ThrowCodeError('param_required',
+ {function => 'Bug.possible_duplicates', param => 'summary'});
+
+ my @products;
+ foreach my $name (@{$params->{'products'} || []}) {
+ my $object = $user->can_enter_product($name, THROW_ERROR);
+ push(@products, $object);
+ }
+
+ my $possible_dupes = Bugzilla::Bug->possible_duplicates({
+ summary => $params->{summary},
+ products => \@products,
+ limit => $params->{limit}
+ });
+ my @hashes = map { $self->_bug_to_hash($_, $params) } @$possible_dupes;
+ $self->_add_update_tokens($params, $possible_dupes, \@hashes);
+ return {bugs => \@hashes};
}
sub update {
- my ($self, $params) = validate(@_, 'ids');
+ my ($self, $params) = validate(@_, 'ids');
- my $user = Bugzilla->login(LOGIN_REQUIRED);
- my $dbh = Bugzilla->dbh;
+ my $user = Bugzilla->login(LOGIN_REQUIRED);
+ my $dbh = Bugzilla->dbh;
- # We skip certain fields because their set_ methods actually use
- # the external names instead of the internal names.
- $params = Bugzilla::Bug::map_fields($params,
- { summary => 1, platform => 1, severity => 1, url => 1 });
+ # We skip certain fields because their set_ methods actually use
+ # the external names instead of the internal names.
+ $params = Bugzilla::Bug::map_fields($params,
+ {summary => 1, platform => 1, severity => 1, url => 1});
- my $ids = delete $params->{ids};
- defined $ids || ThrowCodeError('param_required', { param => 'ids' });
+ my $ids = delete $params->{ids};
+ defined $ids || ThrowCodeError('param_required', {param => 'ids'});
- my @bugs = map { Bugzilla::Bug->check_for_edit($_) } @$ids;
+ my @bugs = map { Bugzilla::Bug->check_for_edit($_) } @$ids;
- my %values = %$params;
- $values{other_bugs} = \@bugs;
+ my %values = %$params;
+ $values{other_bugs} = \@bugs;
- if (exists $values{comment} and exists $values{comment}{comment}) {
- $values{comment}{body} = delete $values{comment}{comment};
- }
+ if (exists $values{comment} and exists $values{comment}{comment}) {
+ $values{comment}{body} = delete $values{comment}{comment};
+ }
- # Prevent bugs that could be triggered by specifying fields that
- # have valid "set_" functions in Bugzilla::Bug, but shouldn't be
- # called using those field names.
- delete $values{dependencies};
+ # Prevent bugs that could be triggered by specifying fields that
+ # have valid "set_" functions in Bugzilla::Bug, but shouldn't be
+ # called using those field names.
+ delete $values{dependencies};
- # For backwards compatibility, treat alias string or array as a set action
- if (exists $values{alias}) {
- if (not ref $values{alias}) {
- $values{alias} = { set => [ $values{alias} ] };
- }
- elsif (ref $values{alias} eq 'ARRAY') {
- $values{alias} = { set => $values{alias} };
- }
+ # For backwards compatibility, treat alias string or array as a set action
+ if (exists $values{alias}) {
+ if (not ref $values{alias}) {
+ $values{alias} = {set => [$values{alias}]};
}
-
- my $flags = delete $values{flags};
-
- foreach my $bug (@bugs) {
- $bug->set_all(\%values);
- if ($flags) {
- my ($old_flags, $new_flags) = extract_flags($flags, $bug);
- $bug->set_flags($old_flags, $new_flags);
- }
+ elsif (ref $values{alias} eq 'ARRAY') {
+ $values{alias} = {set => $values{alias}};
}
+ }
- my %all_changes;
- $dbh->bz_start_transaction();
- foreach my $bug (@bugs) {
- $all_changes{$bug->id} = $bug->update();
- }
- $dbh->bz_commit_transaction();
+ my $flags = delete $values{flags};
- foreach my $bug (@bugs) {
- $bug->send_changes($all_changes{$bug->id});
+ foreach my $bug (@bugs) {
+ $bug->set_all(\%values);
+ if ($flags) {
+ my ($old_flags, $new_flags) = extract_flags($flags, $bug);
+ $bug->set_flags($old_flags, $new_flags);
}
+ }
+
+ my %all_changes;
+ $dbh->bz_start_transaction();
+ foreach my $bug (@bugs) {
+ $all_changes{$bug->id} = $bug->update();
+ }
+ $dbh->bz_commit_transaction();
+
+ foreach my $bug (@bugs) {
+ $bug->send_changes($all_changes{$bug->id});
+ }
+
+ my %api_name = reverse %{Bugzilla::Bug::FIELD_MAP()};
+
+ # This doesn't normally belong in FIELD_MAP, but we do want to translate
+ # "bug_group" back into "groups".
+ $api_name{'bug_group'} = 'groups';
+
+ my @result;
+ foreach my $bug (@bugs) {
+ my %hash = (
+ id => $self->type('int', $bug->id),
+ last_change_time => $self->type('dateTime', $bug->delta_ts),
+ changes => {},
+ );
- my %api_name = reverse %{ Bugzilla::Bug::FIELD_MAP() };
- # This doesn't normally belong in FIELD_MAP, but we do want to translate
- # "bug_group" back into "groups".
- $api_name{'bug_group'} = 'groups';
-
- my @result;
- foreach my $bug (@bugs) {
- my %hash = (
- id => $self->type('int', $bug->id),
- last_change_time => $self->type('dateTime', $bug->delta_ts),
- changes => {},
- );
-
- # alias is returned in case users pass a mixture of ids and aliases,
- # so that they can know which set of changes relates to which value
- # they passed.
- $hash{alias} = [ map { $self->type('string', $_) } @{ $bug->alias } ];
-
- my %changes = %{ $all_changes{$bug->id} };
- foreach my $field (keys %changes) {
- my $change = $changes{$field};
- my $api_field = $api_name{$field} || $field;
- # We normalize undef to an empty string, so that the API
- # stays consistent for things like Deadline that can become
- # empty.
- $change->[0] = '' if !defined $change->[0];
- $change->[1] = '' if !defined $change->[1];
- $hash{changes}->{$api_field} = {
- removed => $self->type('string', $change->[0]),
- added => $self->type('string', $change->[1])
- };
- }
-
- push(@result, \%hash);
+ # alias is returned in case users pass a mixture of ids and aliases,
+ # so that they can know which set of changes relates to which value
+ # they passed.
+ $hash{alias} = [map { $self->type('string', $_) } @{$bug->alias}];
+
+ my %changes = %{$all_changes{$bug->id}};
+ foreach my $field (keys %changes) {
+ my $change = $changes{$field};
+ my $api_field = $api_name{$field} || $field;
+
+ # We normalize undef to an empty string, so that the API
+ # stays consistent for things like Deadline that can become
+ # empty.
+ $change->[0] = '' if !defined $change->[0];
+ $change->[1] = '' if !defined $change->[1];
+ $hash{changes}->{$api_field} = {
+ removed => $self->type('string', $change->[0]),
+ added => $self->type('string', $change->[1])
+ };
}
- return { bugs => \@result };
+ push(@result, \%hash);
+ }
+
+ return {bugs => \@result};
}
sub create {
- my ($self, $params) = @_;
- my $dbh = Bugzilla->dbh;
+ my ($self, $params) = @_;
+ my $dbh = Bugzilla->dbh;
- Bugzilla->login(LOGIN_REQUIRED);
+ Bugzilla->login(LOGIN_REQUIRED);
- $params = Bugzilla::Bug::map_fields($params);
+ $params = Bugzilla::Bug::map_fields($params);
- my $flags = delete $params->{flags};
+ my $flags = delete $params->{flags};
- # We start a nested transaction in case flag setting fails
- # we want the bug creation to roll back as well.
- $dbh->bz_start_transaction();
+ # We start a nested transaction in case flag setting fails
+ # we want the bug creation to roll back as well.
+ $dbh->bz_start_transaction();
- my $bug = Bugzilla::Bug->create($params);
+ my $bug = Bugzilla::Bug->create($params);
- # Set bug flags
- if ($flags) {
- my ($flags, $new_flags) = extract_flags($flags, $bug);
- $bug->set_flags($flags, $new_flags);
- $bug->update($bug->creation_ts);
- }
+ # Set bug flags
+ if ($flags) {
+ my ($flags, $new_flags) = extract_flags($flags, $bug);
+ $bug->set_flags($flags, $new_flags);
+ $bug->update($bug->creation_ts);
+ }
- $dbh->bz_commit_transaction();
+ $dbh->bz_commit_transaction();
- $bug->send_changes();
+ $bug->send_changes();
- return { id => $self->type('int', $bug->bug_id) };
+ return {id => $self->type('int', $bug->bug_id)};
}
sub legal_values {
- my ($self, $params) = @_;
+ my ($self, $params) = @_;
- Bugzilla->switch_to_shadow_db();
+ Bugzilla->switch_to_shadow_db();
- defined $params->{field}
- or ThrowCodeError('param_required', { param => 'field' });
+ defined $params->{field}
+ or ThrowCodeError('param_required', {param => 'field'});
- my $field = Bugzilla::Bug::FIELD_MAP->{$params->{field}}
- || $params->{field};
+ my $field = Bugzilla::Bug::FIELD_MAP->{$params->{field}} || $params->{field};
- my @global_selects =
- @{ Bugzilla->fields({ is_select => 1, is_abnormal => 0 }) };
+ my @global_selects = @{Bugzilla->fields({is_select => 1, is_abnormal => 0})};
- my $values;
- if (grep($_->name eq $field, @global_selects)) {
- # The field is a valid one.
- trick_taint($field);
- $values = get_legal_field_values($field);
- }
- elsif (grep($_ eq $field, PRODUCT_SPECIFIC_FIELDS)) {
- my $id = $params->{product_id};
- defined $id || ThrowCodeError('param_required',
- { function => 'Bug.legal_values', param => 'product_id' });
- grep($_->id eq $id, @{Bugzilla->user->get_accessible_products})
- || ThrowUserError('product_access_denied', { id => $id });
-
- my $product = new Bugzilla::Product($id);
- my @objects;
- if ($field eq 'version') {
- @objects = @{$product->versions};
- }
- elsif ($field eq 'target_milestone') {
- @objects = @{$product->milestones};
- }
- elsif ($field eq 'component') {
- @objects = @{$product->components};
- }
+ my $values;
+ if (grep($_->name eq $field, @global_selects)) {
- $values = [map { $_->name } @objects];
+ # The field is a valid one.
+ trick_taint($field);
+ $values = get_legal_field_values($field);
+ }
+ elsif (grep($_ eq $field, PRODUCT_SPECIFIC_FIELDS)) {
+ my $id = $params->{product_id};
+ defined $id
+ || ThrowCodeError('param_required',
+ {function => 'Bug.legal_values', param => 'product_id'});
+ grep($_->id eq $id, @{Bugzilla->user->get_accessible_products})
+ || ThrowUserError('product_access_denied', {id => $id});
+
+ my $product = new Bugzilla::Product($id);
+ my @objects;
+ if ($field eq 'version') {
+ @objects = @{$product->versions};
}
- else {
- ThrowCodeError('invalid_field_name', { field => $params->{field} });
+ elsif ($field eq 'target_milestone') {
+ @objects = @{$product->milestones};
}
-
- my @result;
- foreach my $val (@$values) {
- push(@result, $self->type('string', $val));
+ elsif ($field eq 'component') {
+ @objects = @{$product->components};
}
- return { values => \@result };
+ $values = [map { $_->name } @objects];
+ }
+ else {
+ ThrowCodeError('invalid_field_name', {field => $params->{field}});
+ }
+
+ my @result;
+ foreach my $val (@$values) {
+ push(@result, $self->type('string', $val));
+ }
+
+ return {values => \@result};
}
sub add_attachment {
- my ($self, $params) = validate(@_, 'ids');
- my $dbh = Bugzilla->dbh;
-
- Bugzilla->login(LOGIN_REQUIRED);
- defined $params->{ids}
- || ThrowCodeError('param_required', { param => 'ids' });
- defined $params->{data}
- || ThrowCodeError('param_required', { param => 'data' });
-
- my @bugs = map { Bugzilla::Bug->check_for_edit($_) } @{ $params->{ids} };
-
- my @created;
- $dbh->bz_start_transaction();
- my $timestamp = $dbh->selectrow_array('SELECT LOCALTIMESTAMP(0)');
-
- my $flags = delete $params->{flags};
-
- foreach my $bug (@bugs) {
- my $attachment = Bugzilla::Attachment->create({
- bug => $bug,
- creation_ts => $timestamp,
- data => $params->{data},
- description => $params->{summary},
- filename => $params->{file_name},
- mimetype => $params->{content_type},
- ispatch => $params->{is_patch},
- isprivate => $params->{is_private},
- });
-
- if ($flags) {
- my ($old_flags, $new_flags) = extract_flags($flags, $bug, $attachment);
- $attachment->set_flags($old_flags, $new_flags);
- }
+ my ($self, $params) = validate(@_, 'ids');
+ my $dbh = Bugzilla->dbh;
+
+ Bugzilla->login(LOGIN_REQUIRED);
+ defined $params->{ids} || ThrowCodeError('param_required', {param => 'ids'});
+ defined $params->{data} || ThrowCodeError('param_required', {param => 'data'});
+
+ my @bugs = map { Bugzilla::Bug->check_for_edit($_) } @{$params->{ids}};
+
+ my @created;
+ $dbh->bz_start_transaction();
+ my $timestamp = $dbh->selectrow_array('SELECT LOCALTIMESTAMP(0)');
+
+ my $flags = delete $params->{flags};
+
+ foreach my $bug (@bugs) {
+ my $attachment = Bugzilla::Attachment->create({
+ bug => $bug,
+ creation_ts => $timestamp,
+ data => $params->{data},
+ description => $params->{summary},
+ filename => $params->{file_name},
+ mimetype => $params->{content_type},
+ ispatch => $params->{is_patch},
+ isprivate => $params->{is_private},
+ });
- $attachment->update($timestamp);
- my $comment = $params->{comment} || '';
- $attachment->bug->add_comment($comment,
- { isprivate => $attachment->isprivate,
- type => CMT_ATTACHMENT_CREATED,
- extra_data => $attachment->id });
- push(@created, $attachment);
+ if ($flags) {
+ my ($old_flags, $new_flags) = extract_flags($flags, $bug, $attachment);
+ $attachment->set_flags($old_flags, $new_flags);
}
- $_->bug->update($timestamp) foreach @created;
- $dbh->bz_commit_transaction();
- $_->send_changes() foreach @bugs;
+ $attachment->update($timestamp);
+ my $comment = $params->{comment} || '';
+ $attachment->bug->add_comment(
+ $comment,
+ {
+ isprivate => $attachment->isprivate,
+ type => CMT_ATTACHMENT_CREATED,
+ extra_data => $attachment->id
+ }
+ );
+ push(@created, $attachment);
+ }
+ $_->bug->update($timestamp) foreach @created;
+ $dbh->bz_commit_transaction();
+
+ $_->send_changes() foreach @bugs;
- my @created_ids = map { $_->id } @created;
+ my @created_ids = map { $_->id } @created;
- return { ids => \@created_ids };
+ return {ids => \@created_ids};
}
sub update_attachment {
- my ($self, $params) = validate(@_, 'ids');
+ my ($self, $params) = validate(@_, 'ids');
+
+ my $user = Bugzilla->login(LOGIN_REQUIRED);
+ my $dbh = Bugzilla->dbh;
+
+ my $ids = delete $params->{ids};
+ defined $ids || ThrowCodeError('param_required', {param => 'ids'});
+
+ # Some fields cannot be sent to set_all
+ foreach my $key (qw(login password token)) {
+ delete $params->{$key};
+ }
+
+ $params = translate($params, ATTACHMENT_MAPPED_SETTERS);
+
+ # Get all the attachments, after verifying that they exist and are editable
+ my @attachments = ();
+ my %bugs = ();
+ foreach my $id (@$ids) {
+ my $attachment = Bugzilla::Attachment->new($id)
+ || ThrowUserError("invalid_attach_id", {attach_id => $id});
+ my $bug = $attachment->bug;
+ $attachment->_check_bug;
+
+ push @attachments, $attachment;
+ $bugs{$bug->id} = $bug;
+ }
+
+ my $flags = delete $params->{flags};
+ my $comment = delete $params->{comment};
+
+ # Update the values
+ foreach my $attachment (@attachments) {
+ my ($update_flags, $new_flags)
+ = $flags ? extract_flags($flags, $attachment->bug, $attachment) : ([], []);
+ if ($attachment->validate_can_edit) {
+ $attachment->set_all($params);
+ $attachment->set_flags($update_flags, $new_flags) if $flags;
+ }
+ elsif (scalar @$update_flags && !scalar(@$new_flags) && !scalar keys %$params) {
+
+ # Requestees can set flags targetted to them, even if they cannot
+ # edit the attachment. Flag setters can edit their own flags too.
+ my %flag_list = map { $_->{id} => $_ } @$update_flags;
+ my $flag_objs = Bugzilla::Flag->new_from_list([keys %flag_list]);
+ my @editable_flags;
+ foreach my $flag_obj (@$flag_objs) {
+ if ($flag_obj->setter_id == $user->id
+ || ($flag_obj->requestee_id && $flag_obj->requestee_id == $user->id))
+ {
+ push(@editable_flags, $flag_list{$flag_obj->id});
+ }
+ }
+ if (!scalar @editable_flags) {
+ ThrowUserError("illegal_attachment_edit", {attach_id => $attachment->id});
+ }
+ $attachment->set_flags(\@editable_flags, []);
+ }
+ else {
+ ThrowUserError("illegal_attachment_edit", {attach_id => $attachment->id});
+ }
+ }
- my $user = Bugzilla->login(LOGIN_REQUIRED);
- my $dbh = Bugzilla->dbh;
+ $dbh->bz_start_transaction();
- my $ids = delete $params->{ids};
- defined $ids || ThrowCodeError('param_required', { param => 'ids' });
+ # Do the actual update and get information to return to user
+ my @result;
+ foreach my $attachment (@attachments) {
+ my $changes = $attachment->update();
- # Some fields cannot be sent to set_all
- foreach my $key (qw(login password token)) {
- delete $params->{$key};
+ if ($comment = trim($comment)) {
+ $attachment->bug->add_comment(
+ $comment,
+ {
+ isprivate => $attachment->isprivate,
+ type => CMT_ATTACHMENT_UPDATED,
+ extra_data => $attachment->id
+ }
+ );
}
- $params = translate($params, ATTACHMENT_MAPPED_SETTERS);
+ $changes = translate($changes, ATTACHMENT_MAPPED_RETURNS);
- # Get all the attachments, after verifying that they exist and are editable
- my @attachments = ();
- my %bugs = ();
- foreach my $id (@$ids) {
- my $attachment = Bugzilla::Attachment->new($id)
- || ThrowUserError("invalid_attach_id", { attach_id => $id });
- my $bug = $attachment->bug;
- $attachment->_check_bug;
+ my %hash = (
+ id => $self->type('int', $attachment->id),
+ last_change_time => $self->type('dateTime', $attachment->modification_time),
+ changes => {},
+ );
- push @attachments, $attachment;
- $bugs{$bug->id} = $bug;
- }
+ foreach my $field (keys %$changes) {
+ my $change = $changes->{$field};
- my $flags = delete $params->{flags};
- my $comment = delete $params->{comment};
-
- # Update the values
- foreach my $attachment (@attachments) {
- my ($update_flags, $new_flags) = $flags
- ? extract_flags($flags, $attachment->bug, $attachment)
- : ([], []);
- if ($attachment->validate_can_edit) {
- $attachment->set_all($params);
- $attachment->set_flags($update_flags, $new_flags) if $flags;
- }
- elsif (scalar @$update_flags && !scalar(@$new_flags) && !scalar keys %$params) {
- # Requestees can set flags targetted to them, even if they cannot
- # edit the attachment. Flag setters can edit their own flags too.
- my %flag_list = map { $_->{id} => $_ } @$update_flags;
- my $flag_objs = Bugzilla::Flag->new_from_list([ keys %flag_list ]);
- my @editable_flags;
- foreach my $flag_obj (@$flag_objs) {
- if ($flag_obj->setter_id == $user->id
- || ($flag_obj->requestee_id && $flag_obj->requestee_id == $user->id))
- {
- push(@editable_flags, $flag_list{$flag_obj->id});
- }
- }
- if (!scalar @editable_flags) {
- ThrowUserError("illegal_attachment_edit", { attach_id => $attachment->id });
- }
- $attachment->set_flags(\@editable_flags, []);
- }
- else {
- ThrowUserError("illegal_attachment_edit", { attach_id => $attachment->id });
- }
+ # We normalize undef to an empty string, so that the API
+ # stays consistent for things like Deadline that can become
+ # empty.
+ $hash{changes}->{$field} = {
+ removed => $self->type('string', $change->[0] // ''),
+ added => $self->type('string', $change->[1] // '')
+ };
}
- $dbh->bz_start_transaction();
-
- # Do the actual update and get information to return to user
- my @result;
- foreach my $attachment (@attachments) {
- my $changes = $attachment->update();
-
- if ($comment = trim($comment)) {
- $attachment->bug->add_comment($comment,
- { isprivate => $attachment->isprivate,
- type => CMT_ATTACHMENT_UPDATED,
- extra_data => $attachment->id });
- }
+ push(@result, \%hash);
+ }
- $changes = translate($changes, ATTACHMENT_MAPPED_RETURNS);
+ $dbh->bz_commit_transaction();
- my %hash = (
- id => $self->type('int', $attachment->id),
- last_change_time => $self->type('dateTime', $attachment->modification_time),
- changes => {},
- );
+ # Email users about the change
+ foreach my $bug (values %bugs) {
+ $bug->update();
+ $bug->send_changes();
+ }
- foreach my $field (keys %$changes) {
- my $change = $changes->{$field};
+ # Return the information to the user
+ return {attachments => \@result};
+}
- # We normalize undef to an empty string, so that the API
- # stays consistent for things like Deadline that can become
- # empty.
- $hash{changes}->{$field} = {
- removed => $self->type('string', $change->[0] // ''),
- added => $self->type('string', $change->[1] // '')
- };
- }
+sub add_comment {
+ my ($self, $params) = @_;
- push(@result, \%hash);
- }
+ # The user must login in order add a comment
+ my $user = Bugzilla->login(LOGIN_REQUIRED);
- $dbh->bz_commit_transaction();
+ # Check parameters
+ defined $params->{id} || ThrowCodeError('param_required', {param => 'id'});
+ my $comment = $params->{comment};
+ (defined $comment && trim($comment) ne '')
+ || ThrowCodeError('param_required', {param => 'comment'});
- # Email users about the change
- foreach my $bug (values %bugs) {
- $bug->update();
- $bug->send_changes();
- }
+ my $bug = Bugzilla::Bug->check_for_edit($params->{id});
- # Return the information to the user
- return { attachments => \@result };
-}
+ # Backwards-compatibility for versions before 3.6
+ if (defined $params->{private}) {
+ $params->{is_private} = delete $params->{private};
+ }
-sub add_comment {
- my ($self, $params) = @_;
-
- # The user must login in order add a comment
- my $user = Bugzilla->login(LOGIN_REQUIRED);
-
- # Check parameters
- defined $params->{id}
- || ThrowCodeError('param_required', { param => 'id' });
- my $comment = $params->{comment};
- (defined $comment && trim($comment) ne '')
- || ThrowCodeError('param_required', { param => 'comment' });
-
- my $bug = Bugzilla::Bug->check_for_edit($params->{id});
-
- # Backwards-compatibility for versions before 3.6
- if (defined $params->{private}) {
- $params->{is_private} = delete $params->{private};
- }
- # Append comment
- $bug->add_comment($comment, { isprivate => $params->{is_private},
- work_time => $params->{work_time} });
- $bug->update();
+ # Append comment
+ $bug->add_comment($comment,
+ {isprivate => $params->{is_private}, work_time => $params->{work_time}});
+ $bug->update();
- my $new_comment_id = $bug->{added_comments}[0]->id;
+ my $new_comment_id = $bug->{added_comments}[0]->id;
- # Send mail.
- Bugzilla::BugMail::Send($bug->bug_id, { changer => $user });
+ # Send mail.
+ Bugzilla::BugMail::Send($bug->bug_id, {changer => $user});
- return { id => $self->type('int', $new_comment_id) };
+ return {id => $self->type('int', $new_comment_id)};
}
sub update_see_also {
- my ($self, $params) = @_;
-
- my $user = Bugzilla->login(LOGIN_REQUIRED);
-
- # Check parameters
- $params->{ids}
- || ThrowCodeError('param_required', { param => 'id' });
- my ($add, $remove) = @$params{qw(add remove)};
- ($add || $remove)
- or ThrowCodeError('params_required', { params => ['add', 'remove'] });
-
- my @bugs;
- foreach my $id (@{ $params->{ids} }) {
- my $bug = Bugzilla::Bug->check_for_edit($id);
- push(@bugs, $bug);
- if ($remove) {
- $bug->remove_see_also($_) foreach @$remove;
- }
- if ($add) {
- $bug->add_see_also($_) foreach @$add;
- }
+ my ($self, $params) = @_;
+
+ my $user = Bugzilla->login(LOGIN_REQUIRED);
+
+ # Check parameters
+ $params->{ids} || ThrowCodeError('param_required', {param => 'id'});
+ my ($add, $remove) = @$params{qw(add remove)};
+ ($add || $remove)
+ or ThrowCodeError('params_required', {params => ['add', 'remove']});
+
+ my @bugs;
+ foreach my $id (@{$params->{ids}}) {
+ my $bug = Bugzilla::Bug->check_for_edit($id);
+ push(@bugs, $bug);
+ if ($remove) {
+ $bug->remove_see_also($_) foreach @$remove;
}
-
- my %changes;
- foreach my $bug (@bugs) {
- my $change = $bug->update();
- if (my $see_also = $change->{see_also}) {
- $changes{$bug->id}->{see_also} = {
- removed => [split(', ', $see_also->[0])],
- added => [split(', ', $see_also->[1])],
- };
- }
- else {
- # We still want a changes entry, for API consistency.
- $changes{$bug->id}->{see_also} = { added => [], removed => [] };
- }
-
- Bugzilla::BugMail::Send($bug->id, { changer => $user });
+ if ($add) {
+ $bug->add_see_also($_) foreach @$add;
+ }
+ }
+
+ my %changes;
+ foreach my $bug (@bugs) {
+ my $change = $bug->update();
+ if (my $see_also = $change->{see_also}) {
+ $changes{$bug->id}->{see_also} = {
+ removed => [split(', ', $see_also->[0])],
+ added => [split(', ', $see_also->[1])],
+ };
}
+ else {
+ # We still want a changes entry, for API consistency.
+ $changes{$bug->id}->{see_also} = {added => [], removed => []};
+ }
+
+ Bugzilla::BugMail::Send($bug->id, {changer => $user});
+ }
- return { changes => \%changes };
+ return {changes => \%changes};
}
sub attachments {
- my ($self, $params) = validate(@_, 'ids', 'attachment_ids');
+ my ($self, $params) = validate(@_, 'ids', 'attachment_ids');
- Bugzilla->switch_to_shadow_db() unless Bugzilla->user->id;
+ Bugzilla->switch_to_shadow_db() unless Bugzilla->user->id;
- if (!(defined $params->{ids}
- or defined $params->{attachment_ids}))
- {
- ThrowCodeError('param_required',
- { function => 'Bug.attachments',
- params => ['ids', 'attachment_ids'] });
- }
-
- my $ids = $params->{ids} || [];
- my $attach_ids = $params->{attachment_ids} || [];
-
- my %bugs;
- foreach my $bug_id (@$ids) {
- my $bug = Bugzilla::Bug->check($bug_id);
- $bugs{$bug->id} = [];
- foreach my $attach (@{$bug->attachments}) {
- push @{$bugs{$bug->id}},
- $self->_attachment_to_hash($attach, $params);
- }
+ if (!(defined $params->{ids} or defined $params->{attachment_ids})) {
+ ThrowCodeError('param_required',
+ {function => 'Bug.attachments', params => ['ids', 'attachment_ids']});
+ }
+
+ my $ids = $params->{ids} || [];
+ my $attach_ids = $params->{attachment_ids} || [];
+
+ my %bugs;
+ foreach my $bug_id (@$ids) {
+ my $bug = Bugzilla::Bug->check($bug_id);
+ $bugs{$bug->id} = [];
+ foreach my $attach (@{$bug->attachments}) {
+ push @{$bugs{$bug->id}}, $self->_attachment_to_hash($attach, $params);
}
-
- my %attachments;
- foreach my $attach (@{Bugzilla::Attachment->new_from_list($attach_ids)}) {
- Bugzilla::Bug->check($attach->bug_id);
- if ($attach->isprivate && !Bugzilla->user->is_insider) {
- ThrowUserError('auth_failure', {action => 'access',
- object => 'attachment',
- attach_id => $attach->id});
- }
- $attachments{$attach->id} =
- $self->_attachment_to_hash($attach, $params);
+ }
+
+ my %attachments;
+ foreach my $attach (@{Bugzilla::Attachment->new_from_list($attach_ids)}) {
+ Bugzilla::Bug->check($attach->bug_id);
+ if ($attach->isprivate && !Bugzilla->user->is_insider) {
+ ThrowUserError('auth_failure',
+ {action => 'access', object => 'attachment', attach_id => $attach->id});
}
+ $attachments{$attach->id} = $self->_attachment_to_hash($attach, $params);
+ }
- return { bugs => \%bugs, attachments => \%attachments };
+ return {bugs => \%bugs, attachments => \%attachments};
}
sub update_tags {
- my ($self, $params) = @_;
+ my ($self, $params) = @_;
- Bugzilla->login(LOGIN_REQUIRED);
+ Bugzilla->login(LOGIN_REQUIRED);
- my $ids = $params->{ids};
- my $tags = $params->{tags};
+ my $ids = $params->{ids};
+ my $tags = $params->{tags};
- ThrowCodeError('param_required',
- { function => 'Bug.update_tags',
- param => 'ids' }) if !defined $ids;
+ ThrowCodeError('param_required',
+ {function => 'Bug.update_tags', param => 'ids'})
+ if !defined $ids;
- ThrowCodeError('param_required',
- { function => 'Bug.update_tags',
- param => 'tags' }) if !defined $tags;
+ ThrowCodeError('param_required',
+ {function => 'Bug.update_tags', param => 'tags'})
+ if !defined $tags;
- my %changes;
- foreach my $bug_id (@$ids) {
- my $bug = Bugzilla::Bug->check($bug_id);
- my @old_tags = @{ $bug->tags };
+ my %changes;
+ foreach my $bug_id (@$ids) {
+ my $bug = Bugzilla::Bug->check($bug_id);
+ my @old_tags = @{$bug->tags};
- $bug->remove_tag($_) foreach @{ $tags->{remove} || [] };
- $bug->add_tag($_) foreach @{ $tags->{add} || [] };
+ $bug->remove_tag($_) foreach @{$tags->{remove} || []};
+ $bug->add_tag($_) foreach @{$tags->{add} || []};
- my ($removed, $added) = diff_arrays(\@old_tags, $bug->tags);
+ my ($removed, $added) = diff_arrays(\@old_tags, $bug->tags);
- my @removed = map { $self->type('string', $_) } @$removed;
- my @added = map { $self->type('string', $_) } @$added;
+ my @removed = map { $self->type('string', $_) } @$removed;
+ my @added = map { $self->type('string', $_) } @$added;
- $changes{$bug->id}->{tags} = {
- removed => \@removed,
- added => \@added
- };
- }
+ $changes{$bug->id}->{tags} = {removed => \@removed, added => \@added};
+ }
- return { changes => \%changes };
+ return {changes => \%changes};
}
sub update_comment_tags {
- my ($self, $params) = @_;
-
- my $user = Bugzilla->login(LOGIN_REQUIRED);
- Bugzilla->params->{'comment_taggers_group'}
- || ThrowUserError("comment_tag_disabled");
- $user->can_tag_comments
- || ThrowUserError("auth_failure",
- { group => Bugzilla->params->{'comment_taggers_group'},
- action => "update",
- object => "comment_tags" });
-
- my $comment_id = $params->{comment_id}
- // ThrowCodeError('param_required',
- { function => 'Bug.update_comment_tags',
- param => 'comment_id' });
-
- ThrowCodeError('param_integer_required', { function => 'Bug.update_comment_tags',
- param => 'comment_id' })
- unless $comment_id =~ /^[0-9]+$/;
-
- my $comment = Bugzilla::Comment->new($comment_id)
- || return [];
- $comment->bug->check_is_visible();
- if ($comment->is_private && !$user->is_insider) {
- ThrowUserError('comment_is_private', { id => $comment_id });
- }
+ my ($self, $params) = @_;
- my $dbh = Bugzilla->dbh;
- $dbh->bz_start_transaction();
- foreach my $tag (@{ $params->{add} || [] }) {
- $comment->add_tag($tag) if defined $tag;
- }
- foreach my $tag (@{ $params->{remove} || [] }) {
- $comment->remove_tag($tag) if defined $tag;
+ my $user = Bugzilla->login(LOGIN_REQUIRED);
+ Bugzilla->params->{'comment_taggers_group'}
+ || ThrowUserError("comment_tag_disabled");
+ $user->can_tag_comments || ThrowUserError(
+ "auth_failure",
+ {
+ group => Bugzilla->params->{'comment_taggers_group'},
+ action => "update",
+ object => "comment_tags"
}
- $comment->update();
- $dbh->bz_commit_transaction();
-
- return $comment->tags;
+ );
+
+ my $comment_id = $params->{comment_id} // ThrowCodeError('param_required',
+ {function => 'Bug.update_comment_tags', param => 'comment_id'});
+
+ ThrowCodeError('param_integer_required',
+ {function => 'Bug.update_comment_tags', param => 'comment_id'})
+ unless $comment_id =~ /^[0-9]+$/;
+
+ my $comment = Bugzilla::Comment->new($comment_id) || return [];
+ $comment->bug->check_is_visible();
+ if ($comment->is_private && !$user->is_insider) {
+ ThrowUserError('comment_is_private', {id => $comment_id});
+ }
+
+ my $dbh = Bugzilla->dbh;
+ $dbh->bz_start_transaction();
+ foreach my $tag (@{$params->{add} || []}) {
+ $comment->add_tag($tag) if defined $tag;
+ }
+ foreach my $tag (@{$params->{remove} || []}) {
+ $comment->remove_tag($tag) if defined $tag;
+ }
+ $comment->update();
+ $dbh->bz_commit_transaction();
+
+ return $comment->tags;
}
sub search_comment_tags {
- my ($self, $params) = @_;
-
- Bugzilla->login(LOGIN_REQUIRED);
- Bugzilla->params->{'comment_taggers_group'}
- || ThrowUserError("comment_tag_disabled");
- Bugzilla->user->can_tag_comments
- || ThrowUserError("auth_failure", { group => Bugzilla->params->{'comment_taggers_group'},
- action => "search",
- object => "comment_tags"});
-
- my $query = $params->{query};
- $query
- // ThrowCodeError('param_required', { param => 'query' });
- my $limit = $params->{limit} || 7;
- detaint_natural($limit)
- || ThrowCodeError('param_must_be_numeric', { param => 'limit',
- function => 'Bug.search_comment_tags' });
-
-
- my $tags = Bugzilla::Comment::TagWeights->match({
- WHERE => {
- 'tag LIKE ?' => "\%$query\%",
- },
- LIMIT => $limit,
- });
- return [ map { $_->tag } @$tags ];
+ my ($self, $params) = @_;
+
+ Bugzilla->login(LOGIN_REQUIRED);
+ Bugzilla->params->{'comment_taggers_group'}
+ || ThrowUserError("comment_tag_disabled");
+ Bugzilla->user->can_tag_comments || ThrowUserError(
+ "auth_failure",
+ {
+ group => Bugzilla->params->{'comment_taggers_group'},
+ action => "search",
+ object => "comment_tags"
+ }
+ );
+
+ my $query = $params->{query};
+ $query // ThrowCodeError('param_required', {param => 'query'});
+ my $limit = $params->{limit} || 7;
+ detaint_natural($limit)
+ || ThrowCodeError('param_must_be_numeric',
+ {param => 'limit', function => 'Bug.search_comment_tags'});
+
+
+ my $tags = Bugzilla::Comment::TagWeights->match(
+ {WHERE => {'tag LIKE ?' => "\%$query\%",}, LIMIT => $limit,});
+ return [map { $_->tag } @$tags];
}
##############################
@@ -1197,232 +1206,238 @@ sub search_comment_tags {
# return them directly.
sub _bug_to_hash {
- my ($self, $bug, $params) = @_;
-
- # All the basic bug attributes are here, in alphabetical order.
- # A bug attribute is "basic" if it doesn't require an additional
- # database call to get the info.
- my %item = %{ filter $params, {
- # No need to format $bug->deadline specially, because Bugzilla::Bug
- # already does it for us.
- deadline => $self->type('string', $bug->deadline),
- id => $self->type('int', $bug->bug_id),
- is_confirmed => $self->type('boolean', $bug->everconfirmed),
- op_sys => $self->type('string', $bug->op_sys),
- platform => $self->type('string', $bug->rep_platform),
- priority => $self->type('string', $bug->priority),
- resolution => $self->type('string', $bug->resolution),
- severity => $self->type('string', $bug->bug_severity),
- status => $self->type('string', $bug->bug_status),
- summary => $self->type('string', $bug->short_desc),
- target_milestone => $self->type('string', $bug->target_milestone),
- url => $self->type('string', $bug->bug_file_loc),
- version => $self->type('string', $bug->version),
- whiteboard => $self->type('string', $bug->status_whiteboard),
- } };
-
- # First we handle any fields that require extra work (such as date parsing
- # or SQL calls).
- if (filter_wants $params, 'alias') {
- $item{alias} = [ map { $self->type('string', $_) } @{ $bug->alias } ];
- }
- if (filter_wants $params, 'assigned_to') {
- $item{'assigned_to'} = $self->type('email', $bug->assigned_to->login);
- $item{'assigned_to_detail'} = $self->_user_to_hash($bug->assigned_to, $params, undef, 'assigned_to');
- }
- if (filter_wants $params, 'blocks') {
- my @blocks = map { $self->type('int', $_) } @{ $bug->blocked };
- $item{'blocks'} = \@blocks;
- }
- if (filter_wants $params, 'classification') {
- $item{classification} = $self->type('string', $bug->classification);
- }
- if (filter_wants $params, 'component') {
- $item{component} = $self->type('string', $bug->component);
- }
- if (filter_wants $params, 'cc') {
- my @cc = map { $self->type('email', $_) } @{ $bug->cc };
- $item{'cc'} = \@cc;
- $item{'cc_detail'} = [ map { $self->_user_to_hash($_, $params, undef, 'cc') } @{ $bug->cc_users } ];
- }
- if (filter_wants $params, 'creation_time') {
- $item{'creation_time'} = $self->type('dateTime', $bug->creation_ts);
- }
- if (filter_wants $params, 'creator') {
- $item{'creator'} = $self->type('email', $bug->reporter->login);
- $item{'creator_detail'} = $self->_user_to_hash($bug->reporter, $params, undef, 'creator');
- }
- if (filter_wants $params, 'depends_on') {
- my @depends_on = map { $self->type('int', $_) } @{ $bug->dependson };
- $item{'depends_on'} = \@depends_on;
- }
- if (filter_wants $params, 'dupe_of') {
- $item{'dupe_of'} = $self->type('int', $bug->dup_id);
- }
- if (filter_wants $params, 'groups') {
- my @groups = map { $self->type('string', $_->name) }
- @{ $bug->groups_in };
- $item{'groups'} = \@groups;
- }
- if (filter_wants $params, 'is_open') {
- $item{'is_open'} = $self->type('boolean', $bug->status->is_open);
- }
- if (filter_wants $params, 'keywords') {
- my @keywords = map { $self->type('string', $_->name) }
- @{ $bug->keyword_objects };
- $item{'keywords'} = \@keywords;
- }
- if (filter_wants $params, 'last_change_time') {
- $item{'last_change_time'} = $self->type('dateTime', $bug->delta_ts);
- }
- if (filter_wants $params, 'product') {
- $item{product} = $self->type('string', $bug->product);
+ my ($self, $bug, $params) = @_;
+
+ # All the basic bug attributes are here, in alphabetical order.
+ # A bug attribute is "basic" if it doesn't require an additional
+ # database call to get the info.
+ my %item = %{filter $params,
+ {
+ # No need to format $bug->deadline specially, because Bugzilla::Bug
+ # already does it for us.
+ deadline => $self->type('string', $bug->deadline),
+ id => $self->type('int', $bug->bug_id),
+ is_confirmed => $self->type('boolean', $bug->everconfirmed),
+ op_sys => $self->type('string', $bug->op_sys),
+ platform => $self->type('string', $bug->rep_platform),
+ priority => $self->type('string', $bug->priority),
+ resolution => $self->type('string', $bug->resolution),
+ severity => $self->type('string', $bug->bug_severity),
+ status => $self->type('string', $bug->bug_status),
+ summary => $self->type('string', $bug->short_desc),
+ target_milestone => $self->type('string', $bug->target_milestone),
+ url => $self->type('string', $bug->bug_file_loc),
+ version => $self->type('string', $bug->version),
+ whiteboard => $self->type('string', $bug->status_whiteboard),
}
- if (filter_wants $params, 'qa_contact') {
- my $qa_login = $bug->qa_contact ? $bug->qa_contact->login : '';
- $item{'qa_contact'} = $self->type('email', $qa_login);
- if ($bug->qa_contact) {
- $item{'qa_contact_detail'} = $self->_user_to_hash($bug->qa_contact, $params, undef, 'qa_contact');
- }
+ };
+
+ # First we handle any fields that require extra work (such as date parsing
+ # or SQL calls).
+ if (filter_wants $params, 'alias') {
+ $item{alias} = [map { $self->type('string', $_) } @{$bug->alias}];
+ }
+ if (filter_wants $params, 'assigned_to') {
+ $item{'assigned_to'} = $self->type('email', $bug->assigned_to->login);
+ $item{'assigned_to_detail'}
+ = $self->_user_to_hash($bug->assigned_to, $params, undef, 'assigned_to');
+ }
+ if (filter_wants $params, 'blocks') {
+ my @blocks = map { $self->type('int', $_) } @{$bug->blocked};
+ $item{'blocks'} = \@blocks;
+ }
+ if (filter_wants $params, 'classification') {
+ $item{classification} = $self->type('string', $bug->classification);
+ }
+ if (filter_wants $params, 'component') {
+ $item{component} = $self->type('string', $bug->component);
+ }
+ if (filter_wants $params, 'cc') {
+ my @cc = map { $self->type('email', $_) } @{$bug->cc};
+ $item{'cc'} = \@cc;
+ $item{'cc_detail'}
+ = [map { $self->_user_to_hash($_, $params, undef, 'cc') } @{$bug->cc_users}];
+ }
+ if (filter_wants $params, 'creation_time') {
+ $item{'creation_time'} = $self->type('dateTime', $bug->creation_ts);
+ }
+ if (filter_wants $params, 'creator') {
+ $item{'creator'} = $self->type('email', $bug->reporter->login);
+ $item{'creator_detail'}
+ = $self->_user_to_hash($bug->reporter, $params, undef, 'creator');
+ }
+ if (filter_wants $params, 'depends_on') {
+ my @depends_on = map { $self->type('int', $_) } @{$bug->dependson};
+ $item{'depends_on'} = \@depends_on;
+ }
+ if (filter_wants $params, 'dupe_of') {
+ $item{'dupe_of'} = $self->type('int', $bug->dup_id);
+ }
+ if (filter_wants $params, 'groups') {
+ my @groups = map { $self->type('string', $_->name) } @{$bug->groups_in};
+ $item{'groups'} = \@groups;
+ }
+ if (filter_wants $params, 'is_open') {
+ $item{'is_open'} = $self->type('boolean', $bug->status->is_open);
+ }
+ if (filter_wants $params, 'keywords') {
+ my @keywords = map { $self->type('string', $_->name) } @{$bug->keyword_objects};
+ $item{'keywords'} = \@keywords;
+ }
+ if (filter_wants $params, 'last_change_time') {
+ $item{'last_change_time'} = $self->type('dateTime', $bug->delta_ts);
+ }
+ if (filter_wants $params, 'product') {
+ $item{product} = $self->type('string', $bug->product);
+ }
+ if (filter_wants $params, 'qa_contact') {
+ my $qa_login = $bug->qa_contact ? $bug->qa_contact->login : '';
+ $item{'qa_contact'} = $self->type('email', $qa_login);
+ if ($bug->qa_contact) {
+ $item{'qa_contact_detail'}
+ = $self->_user_to_hash($bug->qa_contact, $params, undef, 'qa_contact');
}
- if (filter_wants $params, 'see_also') {
- my @see_also = map { $self->type('string', $_->name) }
- @{ $bug->see_also };
- $item{'see_also'} = \@see_also;
+ }
+ if (filter_wants $params, 'see_also') {
+ my @see_also = map { $self->type('string', $_->name) } @{$bug->see_also};
+ $item{'see_also'} = \@see_also;
+ }
+ if (filter_wants $params, 'flags') {
+ $item{'flags'} = [map { $self->_flag_to_hash($_) } @{$bug->flags}];
+ }
+ if (filter_wants $params, 'tags', 'extra') {
+ $item{'tags'} = $bug->tags;
+ }
+
+ # And now custom fields
+ my @custom_fields = Bugzilla->active_custom_fields;
+ foreach my $field (@custom_fields) {
+ my $name = $field->name;
+ next if !filter_wants($params, $name, ['default', 'custom']);
+ if ($field->type == FIELD_TYPE_BUG_ID) {
+ $item{$name} = $self->type('int', $bug->$name);
}
- if (filter_wants $params, 'flags') {
- $item{'flags'} = [ map { $self->_flag_to_hash($_) } @{$bug->flags} ];
+ elsif ($field->type == FIELD_TYPE_DATETIME || $field->type == FIELD_TYPE_DATE) {
+ $item{$name} = $self->type('dateTime', $bug->$name);
}
- if (filter_wants $params, 'tags', 'extra') {
- $item{'tags'} = $bug->tags;
+ elsif ($field->type == FIELD_TYPE_MULTI_SELECT) {
+ my @values = map { $self->type('string', $_) } @{$bug->$name};
+ $item{$name} = \@values;
}
-
- # And now custom fields
- my @custom_fields = Bugzilla->active_custom_fields;
- foreach my $field (@custom_fields) {
- my $name = $field->name;
- next if !filter_wants($params, $name, ['default', 'custom']);
- if ($field->type == FIELD_TYPE_BUG_ID) {
- $item{$name} = $self->type('int', $bug->$name);
- }
- elsif ($field->type == FIELD_TYPE_DATETIME
- || $field->type == FIELD_TYPE_DATE)
- {
- $item{$name} = $self->type('dateTime', $bug->$name);
- }
- elsif ($field->type == FIELD_TYPE_MULTI_SELECT) {
- my @values = map { $self->type('string', $_) } @{ $bug->$name };
- $item{$name} = \@values;
- }
- else {
- $item{$name} = $self->type('string', $bug->$name);
- }
+ else {
+ $item{$name} = $self->type('string', $bug->$name);
}
+ }
- # Timetracking fields are only sent if the user can see them.
- if (Bugzilla->user->is_timetracker) {
- if (filter_wants $params, 'estimated_time') {
- $item{'estimated_time'} = $self->type('double', $bug->estimated_time);
- }
- if (filter_wants $params, 'remaining_time') {
- $item{'remaining_time'} = $self->type('double', $bug->remaining_time);
- }
- if (filter_wants $params, 'actual_time') {
- $item{'actual_time'} = $self->type('double', $bug->actual_time);
- }
+ # Timetracking fields are only sent if the user can see them.
+ if (Bugzilla->user->is_timetracker) {
+ if (filter_wants $params, 'estimated_time') {
+ $item{'estimated_time'} = $self->type('double', $bug->estimated_time);
}
-
- # The "accessible" bits go here because they have long names and it
- # makes the code look nicer to separate them out.
- if (filter_wants $params, 'is_cc_accessible') {
- $item{'is_cc_accessible'} = $self->type('boolean', $bug->cclist_accessible);
+ if (filter_wants $params, 'remaining_time') {
+ $item{'remaining_time'} = $self->type('double', $bug->remaining_time);
}
- if (filter_wants $params, 'is_creator_accessible') {
- $item{'is_creator_accessible'} = $self->type('boolean', $bug->reporter_accessible);
+ if (filter_wants $params, 'actual_time') {
+ $item{'actual_time'} = $self->type('double', $bug->actual_time);
}
-
- return \%item;
+ }
+
+ # The "accessible" bits go here because they have long names and it
+ # makes the code look nicer to separate them out.
+ if (filter_wants $params, 'is_cc_accessible') {
+ $item{'is_cc_accessible'} = $self->type('boolean', $bug->cclist_accessible);
+ }
+ if (filter_wants $params, 'is_creator_accessible') {
+ $item{'is_creator_accessible'}
+ = $self->type('boolean', $bug->reporter_accessible);
+ }
+
+ return \%item;
}
sub _user_to_hash {
- my ($self, $user, $filters, $types, $prefix) = @_;
- my $item = filter $filters, {
- id => $self->type('int', $user->id),
- real_name => $self->type('string', $user->name),
- name => $self->type('email', $user->login),
- email => $self->type('email', $user->email),
- }, $types, $prefix;
- return $item;
+ my ($self, $user, $filters, $types, $prefix) = @_;
+ my $item = filter $filters,
+ {
+ id => $self->type('int', $user->id),
+ real_name => $self->type('string', $user->name),
+ name => $self->type('email', $user->login),
+ email => $self->type('email', $user->email),
+ },
+ $types, $prefix;
+ return $item;
}
sub _attachment_to_hash {
- my ($self, $attach, $filters, $types, $prefix) = @_;
-
- my $item = filter $filters, {
- creation_time => $self->type('dateTime', $attach->attached),
- last_change_time => $self->type('dateTime', $attach->modification_time),
- id => $self->type('int', $attach->id),
- bug_id => $self->type('int', $attach->bug_id),
- file_name => $self->type('string', $attach->filename),
- summary => $self->type('string', $attach->description),
- content_type => $self->type('string', $attach->contenttype),
- is_private => $self->type('int', $attach->isprivate),
- is_obsolete => $self->type('int', $attach->isobsolete),
- is_patch => $self->type('int', $attach->ispatch),
- }, $types, $prefix;
-
- # creator requires an extra lookup, so we only send them if
- # the filter wants them.
- if (filter_wants $filters, 'creator', $types, $prefix) {
- $item->{'creator'} = $self->type('email', $attach->attacher->login);
- }
-
- if (filter_wants $filters, 'data', $types, $prefix) {
- $item->{'data'} = $self->type('base64', $attach->data);
- }
-
- if (filter_wants $filters, 'size', $types, $prefix) {
- $item->{'size'} = $self->type('int', $attach->datasize);
- }
-
- if (filter_wants $filters, 'flags', $types, $prefix) {
- $item->{'flags'} = [ map { $self->_flag_to_hash($_) } @{$attach->flags} ];
- }
+ my ($self, $attach, $filters, $types, $prefix) = @_;
- return $item;
+ my $item = filter $filters,
+ {
+ creation_time => $self->type('dateTime', $attach->attached),
+ last_change_time => $self->type('dateTime', $attach->modification_time),
+ id => $self->type('int', $attach->id),
+ bug_id => $self->type('int', $attach->bug_id),
+ file_name => $self->type('string', $attach->filename),
+ summary => $self->type('string', $attach->description),
+ content_type => $self->type('string', $attach->contenttype),
+ is_private => $self->type('int', $attach->isprivate),
+ is_obsolete => $self->type('int', $attach->isobsolete),
+ is_patch => $self->type('int', $attach->ispatch),
+ },
+ $types, $prefix;
+
+ # creator requires an extra lookup, so we only send them if
+ # the filter wants them.
+ if (filter_wants $filters, 'creator', $types, $prefix) {
+ $item->{'creator'} = $self->type('email', $attach->attacher->login);
+ }
+
+ if (filter_wants $filters, 'data', $types, $prefix) {
+ $item->{'data'} = $self->type('base64', $attach->data);
+ }
+
+ if (filter_wants $filters, 'size', $types, $prefix) {
+ $item->{'size'} = $self->type('int', $attach->datasize);
+ }
+
+ if (filter_wants $filters, 'flags', $types, $prefix) {
+ $item->{'flags'} = [map { $self->_flag_to_hash($_) } @{$attach->flags}];
+ }
+
+ return $item;
}
sub _flag_to_hash {
- my ($self, $flag) = @_;
-
- my $item = {
- id => $self->type('int', $flag->id),
- name => $self->type('string', $flag->name),
- type_id => $self->type('int', $flag->type_id),
- creation_date => $self->type('dateTime', $flag->creation_date),
- modification_date => $self->type('dateTime', $flag->modification_date),
- status => $self->type('string', $flag->status)
- };
-
- foreach my $field (qw(setter requestee)) {
- my $field_id = $field . "_id";
- $item->{$field} = $self->type('email', $flag->$field->login)
- if $flag->$field_id;
- }
-
- return $item;
+ my ($self, $flag) = @_;
+
+ my $item = {
+ id => $self->type('int', $flag->id),
+ name => $self->type('string', $flag->name),
+ type_id => $self->type('int', $flag->type_id),
+ creation_date => $self->type('dateTime', $flag->creation_date),
+ modification_date => $self->type('dateTime', $flag->modification_date),
+ status => $self->type('string', $flag->status)
+ };
+
+ foreach my $field (qw(setter requestee)) {
+ my $field_id = $field . "_id";
+ $item->{$field} = $self->type('email', $flag->$field->login)
+ if $flag->$field_id;
+ }
+
+ return $item;
}
sub _add_update_tokens {
- my ($self, $params, $bugs, $hashes) = @_;
+ my ($self, $params, $bugs, $hashes) = @_;
- return if !Bugzilla->user->id;
- return if !filter_wants($params, 'update_token');
+ return if !Bugzilla->user->id;
+ return if !filter_wants($params, 'update_token');
- for(my $i = 0; $i < @$bugs; $i++) {
- my $token = issue_hash_token([$bugs->[$i]->id, $bugs->[$i]->delta_ts]);
- $hashes->[$i]->{'update_token'} = $self->type('string', $token);
- }
+ for (my $i = 0; $i < @$bugs; $i++) {
+ my $token = issue_hash_token([$bugs->[$i]->id, $bugs->[$i]->delta_ts]);
+ $hashes->[$i]->{'update_token'} = $self->type('string', $token);
+ }
}
1;