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/BugModal/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/BugModal/lib')
-rw-r--r-- | extensions/BugModal/lib/ActivityStream.pm | 567 | ||||
-rw-r--r-- | extensions/BugModal/lib/MonkeyPatches.pm | 21 | ||||
-rw-r--r-- | extensions/BugModal/lib/Util.pm | 28 | ||||
-rw-r--r-- | extensions/BugModal/lib/WebService.pm | 618 |
4 files changed, 629 insertions, 605 deletions
diff --git a/extensions/BugModal/lib/ActivityStream.pm b/extensions/BugModal/lib/ActivityStream.pm index 098c5df33..a7983e85c 100644 --- a/extensions/BugModal/lib/ActivityStream.pm +++ b/extensions/BugModal/lib/ActivityStream.pm @@ -49,310 +49,324 @@ use Bugzilla::Constants; # ] sub activity_stream { - my ($self) = @_; - if (!$self->{activity_stream}) { - my $stream = []; - _add_comments_to_stream($self, $stream); - _add_activities_to_stream($self, $stream); - _add_duplicates_to_stream($self, $stream); - - my $base_time = date_str_to_time($self->creation_ts); - foreach my $change_set (@$stream) { - $change_set->{id} = $change_set->{comment} - ? 'c' . $change_set->{comment}->count - : 'a' . ($change_set->{time} - $base_time) . '_' . $change_set->{user_id}; - foreach my $activity (@{ $change_set->{activity} }) { - $activity->{changes} = [ - sort { $a->{fieldname} cmp $b->{fieldname} } - @{ $activity->{changes} } - ]; - } - } - my $order = Bugzilla->user->setting('comment_sort_order'); - if ($order eq 'oldest_to_newest') { - $self->{activity_stream} = [ sort { $a->{time} <=> $b->{time} } @$stream ]; - } - elsif ($order eq 'newest_to_oldest') { - $self->{activity_stream} = [ sort { $b->{time} <=> $a->{time} } @$stream ]; - } - elsif ($order eq 'newest_to_oldest_desc_first') { - my $desc = shift @$stream; - $self->{activity_stream} = [ $desc, sort { $b->{time} <=> $a->{time} } @$stream ]; - } + my ($self) = @_; + if (!$self->{activity_stream}) { + my $stream = []; + _add_comments_to_stream($self, $stream); + _add_activities_to_stream($self, $stream); + _add_duplicates_to_stream($self, $stream); + + my $base_time = date_str_to_time($self->creation_ts); + foreach my $change_set (@$stream) { + $change_set->{id} + = $change_set->{comment} + ? 'c' . $change_set->{comment}->count + : 'a' . ($change_set->{time} - $base_time) . '_' . $change_set->{user_id}; + foreach my $activity (@{$change_set->{activity}}) { + $activity->{changes} + = [sort { $a->{fieldname} cmp $b->{fieldname} } @{$activity->{changes}}]; + } + } + my $order = Bugzilla->user->setting('comment_sort_order'); + if ($order eq 'oldest_to_newest') { + $self->{activity_stream} = [sort { $a->{time} <=> $b->{time} } @$stream]; } - return $self->{activity_stream}; + elsif ($order eq 'newest_to_oldest') { + $self->{activity_stream} = [sort { $b->{time} <=> $a->{time} } @$stream]; + } + elsif ($order eq 'newest_to_oldest_desc_first') { + my $desc = shift @$stream; + $self->{activity_stream} = [$desc, sort { $b->{time} <=> $a->{time} } @$stream]; + } + } + return $self->{activity_stream}; } sub find_activity_id_for_attachment { - my ($self, $attachment) = @_; - my $attach_id = $attachment->id; - my $stream = $self->activity_stream; - foreach my $change_set (@$stream) { - next unless exists $change_set->{attach_id}; - return $change_set->{id} if $change_set->{attach_id} == $attach_id; - } - return undef; + my ($self, $attachment) = @_; + my $attach_id = $attachment->id; + my $stream = $self->activity_stream; + foreach my $change_set (@$stream) { + next unless exists $change_set->{attach_id}; + return $change_set->{id} if $change_set->{attach_id} == $attach_id; + } + return undef; } sub find_activity_id_for_flag { - my ($self, $flag) = @_; - my $flagtype_name = $flag->type->name; - my $date = $flag->modification_date; - my $setter_id = $flag->setter->id; - my $stream = $self->activity_stream; - - # unfortunately bugs_activity treats all flag changes as the same field, so - # we don't have an object_id to match on - - if (!exists $self->{activity_cache}->{flag}->{$flag->id}) { - foreach my $change_set (reverse @$stream) { - foreach my $activity (@{ $change_set->{activity} }) { - # match by user, timestamp, and flag-type name - next unless - $activity->{who}->id == $setter_id - && $activity->{when} eq $date; - foreach my $change (@{ $activity->{changes} }) { - next unless - $change->{fieldname} eq 'flagtypes.name' - && $change->{flagtype_name} eq $flagtype_name; - $self->{activity_cache}->{flag}->{$flag->id} = $change_set->{id}; - return $change_set->{id}; - } - } + my ($self, $flag) = @_; + my $flagtype_name = $flag->type->name; + my $date = $flag->modification_date; + my $setter_id = $flag->setter->id; + my $stream = $self->activity_stream; + + # unfortunately bugs_activity treats all flag changes as the same field, so + # we don't have an object_id to match on + + if (!exists $self->{activity_cache}->{flag}->{$flag->id}) { + foreach my $change_set (reverse @$stream) { + foreach my $activity (@{$change_set->{activity}}) { + + # match by user, timestamp, and flag-type name + next unless $activity->{who}->id == $setter_id && $activity->{when} eq $date; + foreach my $change (@{$activity->{changes}}) { + next + unless $change->{fieldname} eq 'flagtypes.name' + && $change->{flagtype_name} eq $flagtype_name; + $self->{activity_cache}->{flag}->{$flag->id} = $change_set->{id}; + return $change_set->{id}; } - # if we couldn't find the flag in bugs_activity it means it was set - # during bug creation - $self->{activity_cache}->{flag}->{$flag->id} = 'c0'; + } } - return $self->{activity_cache}->{flag}->{$flag->id}; + + # if we couldn't find the flag in bugs_activity it means it was set + # during bug creation + $self->{activity_cache}->{flag}->{$flag->id} = 'c0'; + } + return $self->{activity_cache}->{flag}->{$flag->id}; } # comments are processed first, so there's no need to merge into existing entries sub _add_comment_to_stream { - my ($stream, $time, $user_id, $comment) = @_; - my $rh = { - time => $time, - user_id => $user_id, - comment => $comment, - activity => [], - }; - if ($comment->type == CMT_ATTACHMENT_CREATED || $comment->type == CMT_ATTACHMENT_UPDATED) { - $rh->{attach_id} = $comment->extra_data; - } - push @$stream, $rh; + my ($stream, $time, $user_id, $comment) = @_; + my $rh + = {time => $time, user_id => $user_id, comment => $comment, activity => [],}; + if ( $comment->type == CMT_ATTACHMENT_CREATED + || $comment->type == CMT_ATTACHMENT_UPDATED) + { + $rh->{attach_id} = $comment->extra_data; + } + push @$stream, $rh; } sub _add_activity_to_stream { - my ($stream, $time, $user_id, $data) = @_; - foreach my $entry (@$stream) { - next unless $entry->{time} == $time && $entry->{user_id} == $user_id; - $entry->{cc_only} = $entry->{cc_only} && $data->{cc_only}; - push @{ $entry->{activity} }, $data; - return; - } - push @$stream, { - time => $time, - user_id => $user_id, - comment => undef, - cc_only => $data->{cc_only}, - activity => [ $data ], + my ($stream, $time, $user_id, $data) = @_; + foreach my $entry (@$stream) { + next unless $entry->{time} == $time && $entry->{user_id} == $user_id; + $entry->{cc_only} = $entry->{cc_only} && $data->{cc_only}; + push @{$entry->{activity}}, $data; + return; + } + push @$stream, + { + time => $time, + user_id => $user_id, + comment => undef, + cc_only => $data->{cc_only}, + activity => [$data], }; } sub _add_comments_to_stream { - my ($bug, $stream) = @_; - my $user = Bugzilla->user; - my $treeherder_id = Bugzilla->treeherder_user->id; - - my $raw_comments = $bug->comments(); - foreach my $comment (@$raw_comments) { - next if $comment->type == CMT_HAS_DUPE; - my $author_id = $comment->author->id; - next if $comment->is_private && !($user->is_insider || $user->id == $author_id); - next if $comment->body eq '' && ($comment->work_time - 0) != 0 && $user->is_timetracker; - - # treeherder is so spammy we hide its comments by default - if ($author_id == $treeherder_id) { - $comment->{collapsed} = 1; - $comment->{collapsed_reason} = $comment->author->name; - } - if ($comment->type != CMT_ATTACHMENT_CREATED && $comment->count == 0 && length($comment->body) == 0) { - $comment->{collapsed} = 1; - $comment->{collapsed_reason} = 'empty'; - } - # If comment type is resolved as duplicate, do not add '...marked as duplicate...' string to comment body - if ($comment->type == CMT_DUPE_OF) { - $comment->set_type(0); - # Skip if user did not supply comment also - next if $comment->body eq ''; - } - - _add_comment_to_stream($stream, date_str_to_time($comment->creation_ts), $comment->author->id, $comment); + my ($bug, $stream) = @_; + my $user = Bugzilla->user; + my $treeherder_id = Bugzilla->treeherder_user->id; + + my $raw_comments = $bug->comments(); + foreach my $comment (@$raw_comments) { + next if $comment->type == CMT_HAS_DUPE; + my $author_id = $comment->author->id; + next if $comment->is_private && !($user->is_insider || $user->id == $author_id); + next + if $comment->body eq '' + && ($comment->work_time - 0) != 0 + && $user->is_timetracker; + + # treeherder is so spammy we hide its comments by default + if ($author_id == $treeherder_id) { + $comment->{collapsed} = 1; + $comment->{collapsed_reason} = $comment->author->name; } -} - -sub _add_activities_to_stream { - my ($bug, $stream) = @_; - my $dbh = Bugzilla->dbh; - my $user = Bugzilla->user; - - # build bug activity - my ($raw_activity) = $bug->can('get_activity') - ? $bug->get_activity() - : Bugzilla::Bug::GetBugActivity($bug->id); - - # allow other extensions to alter history - Bugzilla::Hook::process('inline_history_activitiy', { activity => $raw_activity }); - - my %attachment_cache; - foreach my $attachment (@{$bug->attachments}) { - $attachment_cache{$attachment->id} = $attachment; + if ( $comment->type != CMT_ATTACHMENT_CREATED + && $comment->count == 0 + && length($comment->body) == 0) + { + $comment->{collapsed} = 1; + $comment->{collapsed_reason} = 'empty'; } - # build a list of bugs we need to check visibility of, so we can check with a single query - my %visible_bug_ids; +# If comment type is resolved as duplicate, do not add '...marked as duplicate...' string to comment body + if ($comment->type == CMT_DUPE_OF) { + $comment->set_type(0); - # envelope, augment and tweak - foreach my $operation (@$raw_activity) { - - # make operation.who an object - $operation->{who} = Bugzilla::User->new({ name => $operation->{who}, cache => 1 }); - - # we need to track operations which are just cc changes - $operation->{cc_only} = 1; - - for (my $i = 0; $i < scalar(@{$operation->{changes}}); $i++) { - my $change = $operation->{changes}->[$i]; - - # make an attachment object - if ($change->{attachid}) { - $change->{attach} = $attachment_cache{$change->{attachid}}; - } - - # empty resolutions are displayed as --- by default - # make it explicit here to enable correct display of the change - if ($change->{fieldname} eq 'resolution') { - $change->{removed} = '---' if $change->{removed} eq ''; - $change->{added} = '---' if $change->{added} eq ''; - } - - # make boolean fields true/false instead of 1/0 - my ($table, $field) = ('bugs', $change->{fieldname}); - if ($field =~ /^([^\.]+)\.(.+)$/) { - ($table, $field) = ($1, $2); - } - my $column = $dbh->bz_column_info($table, $field); - if ($column && $column->{TYPE} eq 'BOOLEAN') { - $change->{removed} = ''; - $change->{added} = $change->{added} ? 'true' : 'false'; - } - - # load field object (only required for custom fields), and set the - # field type for custom fields - my $field_obj; - if ($change->{fieldname} =~ /^cf_/) { - $field_obj = Bugzilla::Field->new({ name => $change->{fieldname}, cache => 1 }); - $change->{fieldtype} = $field_obj->type; - } + # Skip if user did not supply comment also + next if $comment->body eq ''; + } - # identify buglist changes - if ($change->{fieldname} eq 'blocked' || - $change->{fieldname} eq 'dependson' || - $change->{fieldname} eq 'dupe' || - ($field_obj && $field_obj->type == FIELD_TYPE_BUG_ID) - ) { - $change->{buglist} = 1; - foreach my $what (qw(removed added)) { - my @buglist = split(/[\s,]+/, $change->{$what}); - foreach my $id (@buglist) { - if ($id && $id =~ /^\d+$/) { - $visible_bug_ids{$id} = 1; - } - } - } - } + _add_comment_to_stream($stream, date_str_to_time($comment->creation_ts), + $comment->author->id, $comment); + } +} - # split see-also - if ($change->{fieldname} eq 'see_also') { - my $url_base = Bugzilla->localconfig->{urlbase}; - foreach my $f (qw( added removed )) { - my @values; - foreach my $value (split(/, /, $change->{$f})) { - my ($bug_id) = substr($value, 0, length($url_base)) eq $url_base - ? $value =~ /id=(\d+)$/ - : undef; - push @values, { - url => $value, - bug_id => $bug_id, - }; - } - $change->{$f} = \@values; - } +sub _add_activities_to_stream { + my ($bug, $stream) = @_; + my $dbh = Bugzilla->dbh; + my $user = Bugzilla->user; + + # build bug activity + my ($raw_activity) + = $bug->can('get_activity') + ? $bug->get_activity() + : Bugzilla::Bug::GetBugActivity($bug->id); + + # allow other extensions to alter history + Bugzilla::Hook::process('inline_history_activitiy', + {activity => $raw_activity}); + + my %attachment_cache; + foreach my $attachment (@{$bug->attachments}) { + $attachment_cache{$attachment->id} = $attachment; + } + +# build a list of bugs we need to check visibility of, so we can check with a single query + my %visible_bug_ids; + + # envelope, augment and tweak + foreach my $operation (@$raw_activity) { + + # make operation.who an object + $operation->{who} + = Bugzilla::User->new({name => $operation->{who}, cache => 1}); + + # we need to track operations which are just cc changes + $operation->{cc_only} = 1; + + for (my $i = 0; $i < scalar(@{$operation->{changes}}); $i++) { + my $change = $operation->{changes}->[$i]; + + # make an attachment object + if ($change->{attachid}) { + $change->{attach} = $attachment_cache{$change->{attachid}}; + } + + # empty resolutions are displayed as --- by default + # make it explicit here to enable correct display of the change + if ($change->{fieldname} eq 'resolution') { + $change->{removed} = '---' if $change->{removed} eq ''; + $change->{added} = '---' if $change->{added} eq ''; + } + + # make boolean fields true/false instead of 1/0 + my ($table, $field) = ('bugs', $change->{fieldname}); + if ($field =~ /^([^\.]+)\.(.+)$/) { + ($table, $field) = ($1, $2); + } + my $column = $dbh->bz_column_info($table, $field); + if ($column && $column->{TYPE} eq 'BOOLEAN') { + $change->{removed} = ''; + $change->{added} = $change->{added} ? 'true' : 'false'; + } + + # load field object (only required for custom fields), and set the + # field type for custom fields + my $field_obj; + if ($change->{fieldname} =~ /^cf_/) { + $field_obj = Bugzilla::Field->new({name => $change->{fieldname}, cache => 1}); + $change->{fieldtype} = $field_obj->type; + } + + # identify buglist changes + if ( $change->{fieldname} eq 'blocked' + || $change->{fieldname} eq 'dependson' + || $change->{fieldname} eq 'dupe' + || ($field_obj && $field_obj->type == FIELD_TYPE_BUG_ID)) + { + $change->{buglist} = 1; + foreach my $what (qw(removed added)) { + my @buglist = split(/[\s,]+/, $change->{$what}); + foreach my $id (@buglist) { + if ($id && $id =~ /^\d+$/) { + $visible_bug_ids{$id} = 1; } + } + } + } + + # split see-also + if ($change->{fieldname} eq 'see_also') { + my $url_base = Bugzilla->localconfig->{urlbase}; + foreach my $f (qw( added removed )) { + my @values; + foreach my $value (split(/, /, $change->{$f})) { + my ($bug_id) + = substr($value, 0, length($url_base)) eq $url_base + ? $value =~ /id=(\d+)$/ + : undef; + push @values, {url => $value, bug_id => $bug_id,}; + } + $change->{$f} = \@values; + } + } + + # track cc-only + if ($change->{fieldname} ne 'cc') { + $operation->{cc_only} = 0; + } + + # split multiple flag changes (must be processed last) + # set $change->{flagtype_name} to make searching the activity + # stream for flag changes easier and quicker + if ($change->{fieldname} eq 'flagtypes.name') { + my @added = split(/, /, $change->{added}); + my @removed = split(/, /, $change->{removed}); + if (scalar(@added) <= 1 && scalar(@removed) <= 1) { + $change->{flagtype_name} = _extract_flagtype($added[0] || $removed[0]); + next; + } - # track cc-only - if ($change->{fieldname} ne 'cc') { - $operation->{cc_only} = 0; - } + # remove current change + splice(@{$operation->{changes}}, $i, 1); - # split multiple flag changes (must be processed last) - # set $change->{flagtype_name} to make searching the activity - # stream for flag changes easier and quicker - if ($change->{fieldname} eq 'flagtypes.name') { - my @added = split(/, /, $change->{added}); - my @removed = split(/, /, $change->{removed}); - if (scalar(@added) <= 1 && scalar(@removed) <= 1) { - $change->{flagtype_name} = _extract_flagtype($added[0] || $removed[0]); - next; - } - # remove current change - splice(@{$operation->{changes}}, $i, 1); - # restructure into added/removed for each flag - my %flags; - foreach my $flag (@added) { - $flags{$flag}{added} = $flag; - $flags{$flag}{removed} = ''; - } - foreach my $flag (@removed) { - $flags{$flag}{added} = ''; - $flags{$flag}{removed} = $flag; - } - # clone current change, modify and insert - foreach my $flag (sort keys %flags) { - my $flag_change = {}; - foreach my $key (keys %$change) { - $flag_change->{$key} = $change->{$key}; - } - $flag_change->{removed} = $flags{$flag}{removed}; - $flag_change->{added} = $flags{$flag}{added}; - $flag_change->{flagtype_name} = _extract_flagtype($flag); - splice(@{$operation->{changes}}, $i, 0, $flag_change); - } - $i--; - } + # restructure into added/removed for each flag + my %flags; + foreach my $flag (@added) { + $flags{$flag}{added} = $flag; + $flags{$flag}{removed} = ''; + } + foreach my $flag (@removed) { + $flags{$flag}{added} = ''; + $flags{$flag}{removed} = $flag; } - _add_activity_to_stream($stream, date_str_to_time($operation->{when}), $operation->{who}->id, $operation); + # clone current change, modify and insert + foreach my $flag (sort keys %flags) { + my $flag_change = {}; + foreach my $key (keys %$change) { + $flag_change->{$key} = $change->{$key}; + } + $flag_change->{removed} = $flags{$flag}{removed}; + $flag_change->{added} = $flags{$flag}{added}; + $flag_change->{flagtype_name} = _extract_flagtype($flag); + splice(@{$operation->{changes}}, $i, 0, $flag_change); + } + $i--; + } } - # prime the visible-bugs cache - $user->visible_bugs([keys %visible_bug_ids]); + _add_activity_to_stream( + $stream, + date_str_to_time($operation->{when}), + $operation->{who}->id, $operation + ); + } + + # prime the visible-bugs cache + $user->visible_bugs([keys %visible_bug_ids]); } sub _extract_flagtype { - my ($value) = @_; - return $value =~ /^(.+)[\?\-\+]/ ? $1 : undef; + my ($value) = @_; + return $value =~ /^(.+)[\?\-\+]/ ? $1 : undef; } # display 'duplicate of this bug' as an activity entry, not a comment sub _add_duplicates_to_stream { - my ($bug, $stream) = @_; - my $dbh = Bugzilla->dbh; + my ($bug, $stream) = @_; + my $dbh = Bugzilla->dbh; - my $sth = $dbh->prepare(" + my $sth = $dbh->prepare(" SELECT longdescs.who, - UNIX_TIMESTAMP(bug_when), " . - $dbh->sql_date_format('bug_when') . ", + UNIX_TIMESTAMP(bug_when), " . $dbh->sql_date_format('bug_when') . ", type, extra_data FROM longdescs @@ -360,19 +374,22 @@ sub _add_duplicates_to_stream { WHERE bug_id = ? AND (type = ? OR type = ?) ORDER BY bug_when "); - $sth->execute($bug->id, CMT_HAS_DUPE, CMT_DUPE_OF); - - while (my($who, $time, $when, $type, $dupe_id) = $sth->fetchrow_array) { - _add_activity_to_stream($stream, $time, $who, { - who => Bugzilla::User->new({ id => $who, cache => 1 }), - when => $when, - changes => [{ - fieldname => ($type == CMT_HAS_DUPE ? 'has_dupe' : 'dupe_of'), - added => $dupe_id, - buglist => 1, - }], - }); - } + $sth->execute($bug->id, CMT_HAS_DUPE, CMT_DUPE_OF); + + while (my ($who, $time, $when, $type, $dupe_id) = $sth->fetchrow_array) { + _add_activity_to_stream( + $stream, $time, $who, + { + who => Bugzilla::User->new({id => $who, cache => 1}), + when => $when, + changes => [{ + fieldname => ($type == CMT_HAS_DUPE ? 'has_dupe' : 'dupe_of'), + added => $dupe_id, + buglist => 1, + }], + } + ); + } } 1; diff --git a/extensions/BugModal/lib/MonkeyPatches.pm b/extensions/BugModal/lib/MonkeyPatches.pm index 54bd6e560..042dabc38 100644 --- a/extensions/BugModal/lib/MonkeyPatches.pm +++ b/extensions/BugModal/lib/MonkeyPatches.pm @@ -17,10 +17,10 @@ use warnings; use Bugzilla::User; sub treeherder_user { - return Bugzilla->process_cache->{treeherder_user} //= - Bugzilla::User->new({ name => 'tbplbot@gmail.com', cache => 1 }) - || Bugzilla::User->new({ name => 'orangefactor@bots.tld', cache => 1 }) - || Bugzilla::User->new(); + return Bugzilla->process_cache->{treeherder_user} + //= Bugzilla::User->new({name => 'tbplbot@gmail.com', cache => 1}) + || Bugzilla::User->new({name => 'orangefactor@bots.tld', cache => 1}) + || Bugzilla::User->new(); } package Bugzilla::Bug; @@ -32,10 +32,11 @@ use warnings; use Bugzilla::Attachment; sub active_attachments { - my ($self) = @_; - return [] if $self->{error}; - return $self->{active_attachments} //= Bugzilla::Attachment->get_attachments_by_bug( - $self, { exclude_obsolete => 1, preload => 1 }); + my ($self) = @_; + return [] if $self->{error}; + return $self->{active_attachments} + //= Bugzilla::Attachment->get_attachments_by_bug($self, + {exclude_obsolete => 1, preload => 1}); } 1; @@ -47,8 +48,8 @@ use strict; use warnings; sub is_image { - my ($self) = @_; - return substr($self->contenttype, 0, 6) eq 'image/'; + my ($self) = @_; + return substr($self->contenttype, 0, 6) eq 'image/'; } 1; diff --git a/extensions/BugModal/lib/Util.pm b/extensions/BugModal/lib/Util.pm index 6a453159e..b1d7068d8 100644 --- a/extensions/BugModal/lib/Util.pm +++ b/extensions/BugModal/lib/Util.pm @@ -21,19 +21,21 @@ use DateTime::TimeZone; use Time::Local qw(timelocal); sub date_str_to_time { - my ($date) = @_; - # avoid creating a DateTime object - if ($date =~ /^(\d{4})[\.\-](\d{2})[\.\-](\d{2}) (\d{2}):(\d{2}):(\d{2})$/) { - return timelocal($6, $5, $4, $3, $2 - 1, $1 - 1900); - } - state $tz //= DateTime::TimeZone->new( name => 'local' ); - my $dt = datetime_from($date, $tz); - if (!$dt) { - # this should never happen - warn("invalid datetime '$date'"); - return undef; - } - return $dt->epoch; + my ($date) = @_; + + # avoid creating a DateTime object + if ($date =~ /^(\d{4})[\.\-](\d{2})[\.\-](\d{2}) (\d{2}):(\d{2}):(\d{2})$/) { + return timelocal($6, $5, $4, $3, $2 - 1, $1 - 1900); + } + state $tz //= DateTime::TimeZone->new(name => 'local'); + my $dt = datetime_from($date, $tz); + if (!$dt) { + + # this should never happen + warn("invalid datetime '$date'"); + return undef; + } + return $dt->epoch; } 1; diff --git a/extensions/BugModal/lib/WebService.pm b/extensions/BugModal/lib/WebService.pm index b69d609dd..5f3308327 100644 --- a/extensions/BugModal/lib/WebService.pm +++ b/extensions/BugModal/lib/WebService.pm @@ -27,353 +27,357 @@ use Taint::Util qw(untaint); # these methods are much lighter than our public API calls sub rest_resources { - return [ - # return all the products accessible by the user. - # required by new-bug - qr{^/bug_modal/initial_field_values}, { - GET => { - method => 'initial_field_values' - }, + return [ + # return all the products accessible by the user. + # required by new-bug + qr{^/bug_modal/initial_field_values}, + {GET => {method => 'initial_field_values'},}, + + # return all the components pertaining to the product. + # required by new-bug + qr{^/bug_modal/product_info}, + { + GET => { + method => 'product_info', + params => sub { + return {product_name => Bugzilla->input_params->{product}}; }, + }, + }, - # return all the components pertaining to the product. - # required by new-bug - qr{^/bug_modal/product_info}, { - GET => { - method => 'product_info', - params => sub { - return { product_name => Bugzilla->input_params->{product} } - }, - }, + # return all the lazy-loaded data; kept in sync with the UI's + # requirements. + qr{^/bug_modal/edit/(\d+)$}, + { + GET => { + method => 'edit', + params => sub { + return {id => $_[0]}; }, + }, + }, - # return all the lazy-loaded data; kept in sync with the UI's - # requirements. - qr{^/bug_modal/edit/(\d+)$}, { - GET => { - method => 'edit', - params => sub { - return { id => $_[0] } - }, - }, + # returns pre-formatted html, enabling reuse of the user template + qr{^/bug_modal/cc/(\d+)$}, + { + GET => { + method => 'cc', + params => sub { + return {id => $_[0]}; }, + }, + }, - # returns pre-formatted html, enabling reuse of the user template - qr{^/bug_modal/cc/(\d+)$}, { - GET => { - method => 'cc', - params => sub { - return { id => $_[0] } - }, - }, - }, + # returns fields that require touching when the product is changed + qw{^/bug_modal/new_product/(\d+)$}, + { + GET => { + method => 'new_product', + params => sub { - # returns fields that require touching when the product is changed - qw{^/bug_modal/new_product/(\d+)$}, { - GET => { - method => 'new_product', - params => sub { - # products with slashes in their name means we have to grab - # the product from the query-string instead of the path - return { id => $_[0], product_name => Bugzilla->input_params->{product} } - }, - }, + # products with slashes in their name means we have to grab + # the product from the query-string instead of the path + return {id => $_[0], product_name => Bugzilla->input_params->{product}}; }, - ] + }, + }, + ]; } sub initial_field_values { - my $user = Bugzilla->user; - return { - products => _name($user->get_enterable_products), - keywords => _name([Bugzilla::Keyword->get_all()]), - }; + my $user = Bugzilla->user; + return { + products => _name($user->get_enterable_products), + keywords => _name([Bugzilla::Keyword->get_all()]), + }; } sub product_info { - my ( $self, $params ) = @_; - if ( !ref $params->{product_name} ) { - untaint( $params->{product_name} ); - } - else { - ThrowCodeError( 'params_required', { function => 'BugModal.components', params => ['product'] } ); - } - my $product = Bugzilla::Product->check( { name => $params->{product_name}, cache => 1 } ); - $product = Bugzilla->user->can_enter_product( $product, 1 ); - my @components = map { - { - name => $_->name, - description => $_->description, - } - } @{ $product->components }; - return { - components => \@components, - versions => _name($product->versions), - }; + my ($self, $params) = @_; + if (!ref $params->{product_name}) { + untaint($params->{product_name}); + } + else { + ThrowCodeError('params_required', + {function => 'BugModal.components', params => ['product']}); + } + my $product + = Bugzilla::Product->check({name => $params->{product_name}, cache => 1}); + $product = Bugzilla->user->can_enter_product($product, 1); + my @components = map { {name => $_->name, description => $_->description,} } + @{$product->components}; + return {components => \@components, versions => _name($product->versions),}; } # everything we need for edit mode in a single call, returning just the fields # that the ui requires. sub edit { - my ($self, $params) = @_; - my $user = Bugzilla->user; - my $bug = Bugzilla::Bug->check({ id => $params->{id} }); - - # the keys of the options hash must match the field id in the ui - my %options; - - my @products = @{ $user->get_enterable_products }; - unless (grep { $_->id == $bug->product_id } @products) { - unshift @products, $bug->product_obj; - } - $options{product} = [ map { { name => $_->name } } @products ]; - - $options{component} = _name($bug->product_obj->components, $bug->component); - $options{version} = _name($bug->product_obj->versions, $bug->version); - $options{target_milestone} = _name($bug->product_obj->milestones, $bug->target_milestone); - $options{priority} = _name('priority', $bug->priority); - $options{bug_severity} = _name('bug_severity', $bug->bug_severity); - $options{rep_platform} = _name('rep_platform', $bug->rep_platform); - $options{op_sys} = _name('op_sys', $bug->op_sys); - - # custom select fields - my @custom_fields = - grep { $_->type == FIELD_TYPE_SINGLE_SELECT || $_->type == FIELD_TYPE_MULTI_SELECT } - Bugzilla->active_custom_fields({ product => $bug->product_obj, component => $bug->component_obj }); - foreach my $field (@custom_fields) { - my $field_name = $field->name; - my @values = map { { name => $_->name } } - grep { $bug->$field_name eq $_->name - || ($_->is_active - && $bug->check_can_change_field($field_name, $bug->$field_name, $_->name)) } - @{ $field->legal_values }; - $options{$field_name} = \@values; - } - - # keywords - my @keywords = grep { $_->is_active } Bugzilla::Keyword->get_all(); - - # results - return { - options => \%options, - keywords => [ map { $_->name } @keywords ], - }; + my ($self, $params) = @_; + my $user = Bugzilla->user; + my $bug = Bugzilla::Bug->check({id => $params->{id}}); + + # the keys of the options hash must match the field id in the ui + my %options; + + my @products = @{$user->get_enterable_products}; + unless (grep { $_->id == $bug->product_id } @products) { + unshift @products, $bug->product_obj; + } + $options{product} = [map { {name => $_->name} } @products]; + + $options{component} = _name($bug->product_obj->components, $bug->component); + $options{version} = _name($bug->product_obj->versions, $bug->version); + $options{target_milestone} + = _name($bug->product_obj->milestones, $bug->target_milestone); + $options{priority} = _name('priority', $bug->priority); + $options{bug_severity} = _name('bug_severity', $bug->bug_severity); + $options{rep_platform} = _name('rep_platform', $bug->rep_platform); + $options{op_sys} = _name('op_sys', $bug->op_sys); + + # custom select fields + my @custom_fields = grep { + $_->type == FIELD_TYPE_SINGLE_SELECT + || $_->type == FIELD_TYPE_MULTI_SELECT + } Bugzilla->active_custom_fields( + {product => $bug->product_obj, component => $bug->component_obj}); + foreach my $field (@custom_fields) { + my $field_name = $field->name; + my @values = map { {name => $_->name} } grep { + $bug->$field_name eq $_->name + || ($_->is_active + && $bug->check_can_change_field($field_name, $bug->$field_name, $_->name)) + } @{$field->legal_values}; + $options{$field_name} = \@values; + } + + # keywords + my @keywords = grep { $_->is_active } Bugzilla::Keyword->get_all(); + + # results + return {options => \%options, keywords => [map { $_->name } @keywords],}; } sub _name { - my ($values, $current) = @_; - # values can either be an array-ref of values, or a field name, which - # result in that field's legal-values being used. - if (!ref($values)) { - $values = Bugzilla::Field->new({ name => $values, cache => 1 })->legal_values; - } - return [ - map { { name => $_->name } } - grep { (defined $current && $_->name eq $current) || $_->is_active } - @$values - ]; + my ($values, $current) = @_; + + # values can either be an array-ref of values, or a field name, which + # result in that field's legal-values being used. + if (!ref($values)) { + $values = Bugzilla::Field->new({name => $values, cache => 1})->legal_values; + } + return [map { {name => $_->name} } + grep { (defined $current && $_->name eq $current) || $_->is_active } + @$values]; } sub cc { - my ($self, $params) = @_; - my $template = Bugzilla->template; - my $bug = Bugzilla::Bug->check({ id => $params->{id} }); - my $vars = { - bug => $bug, - cc_list => [ - sort { lc($a->identity) cmp lc($b->identity) } - @{ $bug->cc_users } - ] - }; - - my $html = ''; - $template->process('bug_modal/cc_list.html.tmpl', $vars, \$html) - || ThrowTemplateError($template->error); - return { html => $html }; + my ($self, $params) = @_; + my $template = Bugzilla->template; + my $bug = Bugzilla::Bug->check({id => $params->{id}}); + my $vars = { + bug => $bug, + cc_list => [sort { lc($a->identity) cmp lc($b->identity) } @{$bug->cc_users}] + }; + + my $html = ''; + $template->process('bug_modal/cc_list.html.tmpl', $vars, \$html) + || ThrowTemplateError($template->error); + return {html => $html}; } sub new_product { - my ($self, $params) = @_; - my $dbh = Bugzilla->dbh; - my $user = Bugzilla->user; - my $bug = Bugzilla::Bug->check({ id => $params->{id} }); - my $product = Bugzilla::Product->check({ name => $params->{product_name}, cache => 1 }); - my $true = $self->type('boolean', 1); - my %result; - - # components - - my $components = _name($product->components); - my $current_component = $bug->component; - if (my $component = first_value { $_->{name} eq $current_component} @$components) { - # identical component in both products - $component->{selected} = $true; - } - else { - # default to a blank value - unshift @$components, { - name => '', - selected => $true, - }; - } - $result{component} = $components; - - # milestones - - my $milestones = _name($product->milestones); - my $current_milestone = $bug->target_milestone; - if ($bug->check_can_change_field('target_milestone', 0, 1) - && (my $milestone = first_value { $_->{name} eq $current_milestone} @$milestones)) - { - # identical milestone in both products - $milestone->{selected} = $true; - } - else { - # use default milestone - my $default_milestone = $product->default_milestone; - my $milestone = first_value { $_->{name} eq $default_milestone } @$milestones; - $milestone->{selected} = $true; + my ($self, $params) = @_; + my $dbh = Bugzilla->dbh; + my $user = Bugzilla->user; + my $bug = Bugzilla::Bug->check({id => $params->{id}}); + my $product + = Bugzilla::Product->check({name => $params->{product_name}, cache => 1}); + my $true = $self->type('boolean', 1); + my %result; + + # components + + my $components = _name($product->components); + my $current_component = $bug->component; + if (my $component + = first_value { $_->{name} eq $current_component } @$components) + { + # identical component in both products + $component->{selected} = $true; + } + else { + # default to a blank value + unshift @$components, {name => '', selected => $true,}; + } + $result{component} = $components; + + # milestones + + my $milestones = _name($product->milestones); + my $current_milestone = $bug->target_milestone; + if ($bug->check_can_change_field('target_milestone', 0, 1) + && (my $milestone + = first_value { $_->{name} eq $current_milestone } @$milestones)) + { + # identical milestone in both products + $milestone->{selected} = $true; + } + else { + # use default milestone + my $default_milestone = $product->default_milestone; + my $milestone = first_value { $_->{name} eq $default_milestone } @$milestones; + $milestone->{selected} = $true; + } + $result{target_milestone} = $milestones; + + # versions + + my $versions = _name($product->versions); + my $current_version = $bug->version; + my $selected_version; + if (my $version = first_value { $_->{name} eq $current_version } @$versions) { + + # identical version in both products + $version->{selected} = $true; + $selected_version = $version; + } + elsif ($current_version =~ /^(\d+) Branch$/ + || $current_version =~ /^Firefox (\d+)$/ + || $current_version =~ /^(\d+)$/) + { + # firefox, with its three version naming schemes + my $branch = $1; + foreach my $test_version ("$branch Branch", "Firefox $branch", $branch) { + if (my $version = first_value { $_->{name} eq $test_version } @$versions) { + $version->{selected} = $true; + $selected_version = $version; + last; + } } - $result{target_milestone} = $milestones; - - # versions + } + if (!$selected_version) { - my $versions = _name($product->versions); - my $current_version = $bug->version; - my $selected_version; - if (my $version = first_value { $_->{name} eq $current_version } @$versions) { - # identical version in both products + # "unspecified", "other" + foreach my $test_version ("unspecified", "other") { + if (my $version = first_value { lc($_->{name}) eq $test_version } @$versions) { $version->{selected} = $true; $selected_version = $version; + last; + } } - elsif ( - $current_version =~ /^(\d+) Branch$/ - || $current_version =~ /^Firefox (\d+)$/ - || $current_version =~ /^(\d+)$/) + } + if (!$selected_version) { + + # default to a blank value + unshift @$versions, {name => '', selected => $true,}; + } + $result{version} = $versions; + + # groups + + my @groups; + + # find invalid groups + push @groups, + map { {type => 'invalid', group => $_, checked => 0,} } + @{Bugzilla::Bug->get_invalid_groups( + {bug_ids => [$bug->id], product => $product})}; + + # logic lifted from bug/process/verify-new-product.html.tmpl + my $current_groups = $bug->groups_in; + my $group_controls = $product->group_controls; + foreach my $group_id (keys %$group_controls) { + my $group_control = $group_controls->{$group_id}; + if ( + $group_control->{membercontrol} == CONTROLMAPMANDATORY + || ($group_control->{othercontrol} == CONTROLMAPMANDATORY + && !$user->in_group($group_control->{name})) + ) { - # firefox, with its three version naming schemes - my $branch = $1; - foreach my $test_version ("$branch Branch", "Firefox $branch", $branch) { - if (my $version = first_value { $_->{name} eq $test_version } @$versions) { - $version->{selected} = $true; - $selected_version = $version; - last; - } - } - } - if (!$selected_version) { - # "unspecified", "other" - foreach my $test_version ("unspecified", "other") { - if (my $version = first_value { lc($_->{name}) eq $test_version } @$versions) { - $version->{selected} = $true; - $selected_version = $version; - last; - } - } - } - if (!$selected_version) { - # default to a blank value - unshift @$versions, { - name => '', - selected => $true, - }; - } - $result{version} = $versions; - - # groups - - my @groups; - - # find invalid groups - push @groups, - map {{ - type => 'invalid', - group => $_, - checked => 0, - }} - @{ Bugzilla::Bug->get_invalid_groups({ bug_ids => [ $bug->id ], product => $product }) }; - - # logic lifted from bug/process/verify-new-product.html.tmpl - my $current_groups = $bug->groups_in; - my $group_controls = $product->group_controls; - foreach my $group_id (keys %$group_controls) { - my $group_control = $group_controls->{$group_id}; - if ($group_control->{membercontrol} == CONTROLMAPMANDATORY - || ($group_control->{othercontrol} == CONTROLMAPMANDATORY && !$user->in_group($group_control->{name}))) - { - # mandatory, always checked - push @groups, { - type => 'mandatory', - group => $group_control->{group}, - checked => 1, - }; - } - elsif ( - ($group_control->{membercontrol} != CONTROLMAPNA && $user->in_group($group_control->{name})) - || $group_control->{othercontrol} != CONTROLMAPNA) - { - # optional, checked if.. - my $group = $group_control->{group}; - my $checked = - # same group as current product - (any { $_->id == $group->id } @$current_groups) - # member default - || $group_control->{membercontrol} == CONTROLMAPDEFAULT && $user->in_group($group_control->{name}) - # or other default - || $group_control->{othercontrol} == CONTROLMAPDEFAULT && !$user->in_group($group_control->{name}) - ; - push @groups, { - type => 'optional', - group => $group_control->{group}, - checked => $checked || 0, - }; - } + # mandatory, always checked + push @groups, + {type => 'mandatory', group => $group_control->{group}, checked => 1,}; } + elsif ( + ( + $group_control->{membercontrol} != CONTROLMAPNA + && $user->in_group($group_control->{name}) + ) + || $group_control->{othercontrol} != CONTROLMAPNA + ) + { + # optional, checked if.. + my $group = $group_control->{group}; + my $checked = - my $default_group_name = $product->default_security_group; - if (my $default_group = first_value { $_->{group}->name eq $default_group_name } @groups) { - # because we always allow the default product group to be selected, it's never invalid - $default_group->{type} = 'optional' if $default_group->{type} eq 'invalid'; - } - else { - # add the product's default group if it's missing - unshift @groups, { - type => 'optional', - group => $product->default_security_group_obj, - checked => 0, - }; - } + # same group as current product + (any { $_->id == $group->id } @$current_groups) - # if the bug is currently in a group, ensure a group is checked by default - # by checking the product's default group if no other groups apply - if (@$current_groups && !any { $_->{checked} } @groups) { - foreach my $g (@groups) { - next unless $g->{group}->name eq $default_group_name; - $g->{checked} = 1; - last; - } - } + # member default + || $group_control->{membercontrol} == CONTROLMAPDEFAULT + && $user->in_group($group_control->{name}) - # group by type and flatten - my $vars = { - product => $product, - groups => { invalid => [], mandatory => [], optional => [] }, - }; - foreach my $g (@groups) { - push @{ $vars->{groups}->{$g->{type}} }, { - id => $g->{group}->id, - name => $g->{group}->name, - description => $g->{group}->description, - checked => $g->{checked}, + # or other default + || $group_control->{othercontrol} == CONTROLMAPDEFAULT + && !$user->in_group($group_control->{name}); + push @groups, + { + type => 'optional', + group => $group_control->{group}, + checked => $checked || 0, }; } - - # build group selection html - my $template = Bugzilla->template; - $template->process('bug_modal/new_product_groups.html.tmpl', $vars, \$result{groups}) - || ThrowTemplateError($template->error); - - return \%result; + } + + my $default_group_name = $product->default_security_group; + if (my $default_group + = first_value { $_->{group}->name eq $default_group_name } @groups) + { +# because we always allow the default product group to be selected, it's never invalid + $default_group->{type} = 'optional' if $default_group->{type} eq 'invalid'; + } + else { + # add the product's default group if it's missing + unshift @groups, + { + type => 'optional', + group => $product->default_security_group_obj, + checked => 0, + }; + } + + # if the bug is currently in a group, ensure a group is checked by default + # by checking the product's default group if no other groups apply + if (@$current_groups && !any { $_->{checked} } @groups) { + foreach my $g (@groups) { + next unless $g->{group}->name eq $default_group_name; + $g->{checked} = 1; + last; + } + } + + # group by type and flatten + my $vars = { + product => $product, + groups => {invalid => [], mandatory => [], optional => []}, + }; + foreach my $g (@groups) { + push @{$vars->{groups}->{$g->{type}}}, + { + id => $g->{group}->id, + name => $g->{group}->name, + description => $g->{group}->description, + checked => $g->{checked}, + }; + } + + # build group selection html + my $template = Bugzilla->template; + $template->process('bug_modal/new_product_groups.html.tmpl', + $vars, \$result{groups}) + || ThrowTemplateError($template->error); + + return \%result; } 1; |