diff options
Diffstat (limited to 'extensions/BMO/lib')
-rw-r--r-- | extensions/BMO/lib/Constants.pm | 33 | ||||
-rw-r--r-- | extensions/BMO/lib/Data.pm | 266 | ||||
-rw-r--r-- | extensions/BMO/lib/FakeBug.pm | 42 | ||||
-rw-r--r-- | extensions/BMO/lib/Reports.pm | 522 | ||||
-rw-r--r-- | extensions/BMO/lib/WebService.pm | 207 |
5 files changed, 1070 insertions, 0 deletions
diff --git a/extensions/BMO/lib/Constants.pm b/extensions/BMO/lib/Constants.pm new file mode 100644 index 000000000..23eaae9cb --- /dev/null +++ b/extensions/BMO/lib/Constants.pm @@ -0,0 +1,33 @@ +# -*- Mode: perl; indent-tabs-mode: nil -*- +# +# The contents of this file are subject to the Mozilla Public +# License Version 1.1 (the "License"); you may not use this file +# except in compliance with the License. You may obtain a copy of +# the License at http://www.mozilla.org/MPL/ +# +# Software distributed under the License is distributed on an "AS +# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or +# implied. See the License for the specific language governing +# rights and limitations under the License. +# +# The Original Code is the BMO Bugzilla Extension. +# +# The Initial Developer of the Original Code is the Mozilla Foundation. +# Portions created by the Initial Developer are Copyright (C) 2007 +# the Initial Developer. All Rights Reserved. +# +# Contributor(s): +# David Lawrence <dkl@mozilla.com> + +package Bugzilla::Extension::BMO::Constants; +use strict; +use base qw(Exporter); +our @EXPORT = qw( + REQUEST_MAX_ATTACH_LINES +); + +# Maximum attachment size in lines that will be sent with a +# requested attachment flag notification. +use constant REQUEST_MAX_ATTACH_LINES => 1000; + +1; diff --git a/extensions/BMO/lib/Data.pm b/extensions/BMO/lib/Data.pm new file mode 100644 index 000000000..ccc729a6d --- /dev/null +++ b/extensions/BMO/lib/Data.pm @@ -0,0 +1,266 @@ +# -*- Mode: perl; indent-tabs-mode: nil -*- +# +# The contents of this file are subject to the Mozilla Public +# License Version 1.1 (the "License"); you may not use this file +# except in compliance with the License. You may obtain a copy of +# the License at http://www.mozilla.org/MPL/ +# +# Software distributed under the License is distributed on an "AS +# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or +# implied. See the License for the specific language governing +# rights and limitations under the License. +# +# The Original Code is the BMO Bugzilla Extension. +# +# The Initial Developer of the Original Code is the Mozilla Foundation. +# Portions created by the Initial Developer are Copyright (C) 2010 the +# Initial Developer. All Rights Reserved. +# +# Contributor(s): +# Gervase Markham <gerv@gerv.net> +# Reed Loden <reed@reedloden.com> + +package Bugzilla::Extension::BMO::Data; +use strict; + +use base qw(Exporter); +use Tie::IxHash; + +our @EXPORT_OK = qw($cf_visible_in_products + $cf_flags $cf_disabled_flags + %group_to_cc_map + $blocking_trusted_setters + $blocking_trusted_requesters + $status_trusted_wanters + $status_trusted_setters + $other_setters + %always_fileable_group + %product_sec_groups); + +# Which custom fields are visible in which products and components. +# +# By default, custom fields are visible in all products. However, if the name +# of the field matches any of these regexps, it is only visible if the +# product (and component if necessary) is a member of the attached hash. [] +# for component means "all". +# +# IxHash keeps them in insertion order, and so we get regexp priorities right. +our $cf_visible_in_products; +tie(%$cf_visible_in_products, "Tie::IxHash", + qr/^cf_blocking_fennec/ => { + "addons.mozilla.org" => [], + "AUS" => [], + "Core" => [], + "Fennec" => [], + "mozilla.org" => ["Release Engineering"], + "Mozilla Services" => [], + "NSPR" => [], + "support.mozilla.com" => [], + "Toolkit" => [], + "Tech Evangelism" => [], + "Mozilla Localizations" => [], + }, + qr/^cf_tracking_thunderbird|cf_blocking_thunderbird|cf_status_thunderbird/ => { + "support.mozillamessaging.com" => [], + "Thunderbird" => [], + "MailNews Core" => [], + "Mozilla Messaging" => [], + "Websites" => ["www.mozillamessaging.com"], + }, + qr/^(cf_(blocking|tracking)_seamonkey|cf_status_seamonkey)/ => { + "Composer" => [], + "MailNews Core" => [], + "Mozilla Localizations" => [], + "Other Applications" => [], + "SeaMonkey" => [], + }, + qr/^cf_blocking_|cf_tracking_|cf_status/ => { + "Add-on SDK" => [], + "addons.mozilla.org" => [], + "AUS" => [], + "Core Graveyard" => [], + "Core" => [], + "Directory" => [], + "Fennec" => [], + "Firefox" => [], + "MailNews Core" => [], + "mozilla.org" => ["Release Engineering"], + "Mozilla Localizations" => [], + "Mozilla Services" => [], + "NSPR" => [], + "NSS" => [], + "Other Applications" => [], + "SeaMonkey" => [], + "support.mozilla.com" => [], + "Tech Evangelism" => [], + "Testing" => [], + "Toolkit" => [], + "Websites" => ["getpersonas.com"], + "Webtools" => [], + "Plugins" => [], + }, + qr/^cf_colo_site$/ => { + "mozilla.org" => [ + "Server Operations", + "Server Operations: Projects", + "Server Operations: RelEng", + "Server Operations: Security", + ], + }, + qw/^cf_office$/ => { + "mozilla.org" => ["Server Operations: Desktop Issues"], + }, + qr/^cf_crash_signature$/ => { + "addons.mozilla.org" => [], + "Add-on SDK" => [], + "Calendar" => [], + "Camino" => [], + "Composer" => [], + "Fennec" => [], + "Firefox" => [], + "Mozilla Localizations" => [], + "Mozilla Services" => [], + "Other Applications" => [], + "Penelope" => [], + "SeaMonkey" => [], + "Thunderbird" => [], + "Core" => [], + "Directory" => [], + "JSS" => [], + "MailNews Core" => [], + "NSPR" => [], + "NSS" => [], + "Plugins" => [], + "Rhino" => [], + "Tamarin" => [], + "Testing" => [], + "Toolkit" => [], + "Mozilla Labs" => [], + "mozilla.org" => [], + "Tech Evangelism" => [], + }, + qw/^cf_due_date$/ => { + "Mozilla Reps" => [], + }, +); + +# Which custom fields are acting as flags (ie. custom flags) +our $cf_flags = [ + qr/^cf_(?:blocking|tracking|status)_/, +]; + +# List of disabled fields. +# Temp kludge until custom fields can be disabled correctly upstream. +# Disabled fields are hidden unless they have a value set +our $cf_disabled_flags = [ + 'cf_blocking_20', + 'cf_status_20', + 'cf_tracking_firefox5', + 'cf_status_firefox5', + 'cf_blocking_thunderbird32', + 'cf_status_thunderbird32', + 'cf_blocking_thunderbird30', + 'cf_status_thunderbird30', + 'cf_blocking_seamonkey21', + 'cf_status_seamonkey21', + 'cf_tracking_seamonkey22', + 'cf_status_seamonkey22', + 'cf_tracking_firefox6', + 'cf_status_firefox6', + 'cf_tracking_thunderbird6', + 'cf_status_thunderbird6', +]; + +# Who to CC on particular bugmails when certain groups are added or removed. +our %group_to_cc_map = ( + 'bugzilla-security' => 'security@bugzilla.org', + 'client-services-security' => 'amo-admins@mozilla.org', + 'core-security' => 'security@mozilla.org', + 'tamarin-security' => 'tamarinsecurity@adobe.com', + 'websites-security' => 'website-drivers@mozilla.org', + 'webtools-security' => 'webtools-security@mozilla.org', +); + +# Only users in certain groups can change certain custom fields in +# certain ways. +# +# Who can set cf_blocking_* or cf_tracking_* to +/- +our $blocking_trusted_setters = { + 'cf_blocking_fennec' => 'fennec-drivers', + 'cf_blocking_20' => 'mozilla-next-drivers', + qr/^cf_tracking_firefox/ => 'mozilla-next-drivers', + qr/^cf_blocking_thunderbird/ => 'thunderbird-drivers', + qr/^cf_tracking_thunderbird/ => 'thunderbird-drivers', + qr/^cf_tracking_seamonkey/ => 'seamonkey-council', + qr/^cf_blocking_seamonkey/ => 'seamonkey-council', + '_default' => 'mozilla-stable-branch-drivers', +}; + +# Who can request cf_blocking_* or cf_tracking_* +our $blocking_trusted_requesters = { + qr/^cf_blocking_thunderbird/ => 'thunderbird-trusted-requesters', + '_default' => 'everyone', +}; + +# Who can set cf_status_* to "wanted"? +our $status_trusted_wanters = { + 'cf_status_20' => 'mozilla-next-drivers', + qr/^cf_status_thunderbird/ => 'thunderbird-drivers', + qr/^cf_status_seamonkey/ => 'seamonkey-council', + '_default' => 'mozilla-stable-branch-drivers', +}; + +# Who can set cf_status_* to values other than "wanted"? +our $status_trusted_setters = { + qr/^cf_status_thunderbird/ => 'editbugs', + '_default' => 'canconfirm', +}; + +# Who can set other custom flags (use full field names only, not regex's) +our $other_setters = { + 'cf_colo_site' => ['infra', 'build'], +}; + +# Groups in which you can always file a bug, whoever you are. +our %always_fileable_group = ( + 'bugzilla-security' => 1, + 'client-services-security' => 1, + 'consulting' => 1, + 'core-security' => 1, + 'infra' => 1, + 'marketing-private' => 1, + 'mozilla-confidential' => 1, + 'mozilla-corporation-confidential' => 1, + 'mozilla-messaging-confidential' => 1, + 'tamarin-security' => 1, + 'websites-security' => 1, + 'webtools-security' => 1, +); + +# Mapping of products to their security bits +our %product_sec_groups = ( + "mozilla.org" => 'mozilla-confidential', + "Webtools" => 'webtools-security', + "Marketing" => 'marketing-private', + "addons.mozilla.org" => 'client-services-security', + "AUS" => 'client-services-security', + "Mozilla Services" => 'client-services-security', + "Mozilla Corporation" => 'mozilla-corporation-confidential', + "Mozilla Metrics" => 'metrics-private', + "Legal" => 'legal', + "Mozilla Messaging" => 'mozilla-messaging-confidential', + "Websites" => 'websites-security', + "Mozilla Developer Network" => 'websites-security', + "support.mozilla.com" => 'websites-security', + "quality.mozilla.org" => 'websites-security', + "Skywriter" => 'websites-security', + "support.mozillamessaging.com" => 'websites-security', + "Bugzilla" => 'bugzilla-security', + "bugzilla.mozilla.org" => 'bugzilla-security', + "Testopia" => 'bugzilla-security', + "Tamarin" => 'tamarin-security', + "Mozilla PR" => 'pr-private', + "_default" => 'core-security' +); + +1; diff --git a/extensions/BMO/lib/FakeBug.pm b/extensions/BMO/lib/FakeBug.pm new file mode 100644 index 000000000..6127cb560 --- /dev/null +++ b/extensions/BMO/lib/FakeBug.pm @@ -0,0 +1,42 @@ +package Bugzilla::Extension::BMO::FakeBug; + +# hack to allow the bug entry templates to use check_can_change_field to see if +# various field values should be available to the current user + +use strict; + +use Bugzilla::Bug; + +our $AUTOLOAD; + +sub new { + my $class = shift; + my $self = shift; + bless $self, $class; + return $self; +} + +sub AUTOLOAD { + my $self = shift; + my $name = $AUTOLOAD; + $name =~ s/.*://; + return exists $self->{$name} ? $self->{$name} : undef; +} + +sub check_can_change_field { + my $self = shift; + return Bugzilla::Bug::check_can_change_field($self, @_) +} + +sub _changes_everconfirmed { + my $self = shift; + return Bugzilla::Bug::_changes_everconfirmed($self, @_) +} + +sub everconfirmed { + my $self = shift; + return ($self->{'status'} == 'UNCONFIRMED') ? 0 : 1; +} + +1; + diff --git a/extensions/BMO/lib/Reports.pm b/extensions/BMO/lib/Reports.pm new file mode 100644 index 000000000..f291e72e7 --- /dev/null +++ b/extensions/BMO/lib/Reports.pm @@ -0,0 +1,522 @@ +# -*- Mode: perl; indent-tabs-mode: nil -*- +# +# The contents of this file are subject to the Mozilla Public License Version +# 1.1 (the "License"); you may not use this file except in compliance with the +# License. You may obtain a copy of the License at http://www.mozilla.org/MPL/ +# +# Software distributed under the License is distributed on an "AS IS" basis, +# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for +# the specific language governing rights and limitations under the License. +# +# The Original Code is the BMO Bugzilla Extension. +# +# The Initial Developer of the Original Code is Byron Jones. Portions created +# by the Initial Developer are Copyright (C) 2011 the Mozilla Foundation. All +# Rights Reserved. +# +# Contributor(s): +# Byron Jones <glob@mozilla.com> + +package Bugzilla::Extension::BMO::Reports; +use strict; + +use Bugzilla::User; +use Bugzilla::Util qw(trim detaint_natural); +use Bugzilla::Error; +use Bugzilla::Constants; + +use Date::Parse; +use DateTime; + +use base qw(Exporter); + +our @EXPORT_OK = qw(user_activity_report + triage_reports + group_admins); + +sub user_activity_report { + my ($vars) = @_; + my $dbh = Bugzilla->dbh; + my $input = Bugzilla->input_params; + + my @who = (); + my $from = trim($input->{'from'}); + my $to = trim($input->{'to'}); + + if ($input->{'action'} eq 'run') { + if ($input->{'who'} eq '') { + ThrowUserError('user_activity_missing_username'); + } + Bugzilla::User::match_field({ 'who' => {'type' => 'multi'} }); + + ThrowUserError('user_activity_missing_from_date') unless $from; + my $from_time = str2time($from) + or ThrowUserError('user_activity_invalid_date', { date => $from }); + my $from_dt = DateTime->from_epoch(epoch => $from_time) + ->set_time_zone('local') + ->truncate(to => 'day'); + $from = $from_dt->ymd(); + + ThrowUserError('user_activity_missing_to_date') unless $to; + my $to_time = str2time($to) + or ThrowUserError('user_activity_invalid_date', { date => $to }); + my $to_dt = DateTime->from_epoch(epoch => $to_time) + ->set_time_zone('local') + ->truncate(to => 'day'); + $to = $to_dt->ymd(); + # add one day to include all activity that happened on the 'to' date + $to_dt->add(days => 1); + + my ($activity_joins, $activity_where) = ('', ''); + my ($attachments_joins, $attachments_where) = ('', ''); + if (Bugzilla->params->{"insidergroup"} + && !Bugzilla->user->in_group(Bugzilla->params->{'insidergroup'})) + { + $activity_joins = "LEFT JOIN attachments + ON attachments.attach_id = bugs_activity.attach_id"; + $activity_where = "AND COALESCE(attachments.isprivate, 0) = 0"; + $attachments_where = $activity_where; + } + + my @who_bits; + foreach my $who ( + ref $input->{'who'} + ? @{$input->{'who'}} + : $input->{'who'} + ) { + push @who, $who; + push @who_bits, '?'; + } + my $who_bits = join(',', @who_bits); + + if (!@who) { + my $template = Bugzilla->template; + my $cgi = Bugzilla->cgi; + my $vars = {}; + $vars->{'script'} = $cgi->url(-relative => 1); + $vars->{'fields'} = {}; + $vars->{'matches'} = []; + $vars->{'matchsuccess'} = 0; + $vars->{'matchmultiple'} = 1; + print $cgi->header(); + $template->process("global/confirm-user-match.html.tmpl", $vars) + || ThrowTemplateError($template->error()); + exit; + } + + $from_dt = $from_dt->ymd() . ' 00:00:00'; + $to_dt = $to_dt->ymd() . ' 23:59:59'; + my @params; + for (1..4) { + push @params, @who; + push @params, ($from_dt, $to_dt); + } + + my $comment_filter = ''; + if (!Bugzilla->user->is_insider) { + $comment_filter = 'AND longdescs.isprivate = 0'; + } + + my $query = " + SELECT + fielddefs.name, + bugs_activity.bug_id, + bugs_activity.attach_id, + ".$dbh->sql_date_format('bugs_activity.bug_when', '%Y.%m.%d %H:%i:%s')." AS ts, + bugs_activity.removed, + bugs_activity.added, + profiles.login_name, + bugs_activity.comment_id, + bugs_activity.bug_when + FROM bugs_activity + $activity_joins + LEFT JOIN fielddefs + ON bugs_activity.fieldid = fielddefs.id + INNER JOIN profiles + ON profiles.userid = bugs_activity.who + WHERE profiles.login_name IN ($who_bits) + AND bugs_activity.bug_when >= ? AND bugs_activity.bug_when <= ? + $activity_where + + UNION ALL + + SELECT + 'bug_id' AS name, + bugs.bug_id, + NULL AS attach_id, + ".$dbh->sql_date_format('bugs.creation_ts', '%Y.%m.%d %H:%i:%s')." AS ts, + '(new bug)' AS removed, + bugs.short_desc AS added, + profiles.login_name, + NULL AS comment_id, + bugs.creation_ts AS bug_when + FROM bugs + INNER JOIN profiles + ON profiles.userid = bugs.reporter + WHERE profiles.login_name IN ($who_bits) + AND bugs.creation_ts >= ? AND bugs.creation_ts <= ? + + UNION ALL + + SELECT + 'longdesc' AS name, + longdescs.bug_id, + NULL AS attach_id, + DATE_FORMAT(longdescs.bug_when, '%Y.%m.%d %H:%i:%s') AS ts, + '' AS removed, + '' AS added, + profiles.login_name, + longdescs.comment_id AS comment_id, + longdescs.bug_when + FROM longdescs + INNER JOIN profiles + ON profiles.userid = longdescs.who + WHERE profiles.login_name IN ($who_bits) + AND longdescs.bug_when >= ? AND longdescs.bug_when <= ? + $comment_filter + + UNION ALL + + SELECT + 'attachments.filename' AS name, + attachments.bug_id, + attachments.attach_id, + ".$dbh->sql_date_format('attachments.creation_ts', '%Y.%m.%d %H:%i:%s')." AS ts, + '' AS removed, + attachments.description AS added, + profiles.login_name, + NULL AS comment_id, + attachments.creation_ts AS bug_when + FROM attachments + INNER JOIN profiles + ON profiles.userid = attachments.submitter_id + WHERE profiles.login_name IN ($who_bits) + AND attachments.creation_ts >= ? AND attachments.creation_ts <= ? + $attachments_where + + ORDER BY bug_when "; + + my $list = $dbh->selectall_arrayref($query, undef, @params); + + my @operations; + my $operation = {}; + my $changes = []; + my $incomplete_data = 0; + + foreach my $entry (@$list) { + my ($fieldname, $bugid, $attachid, $when, $removed, $added, $who, + $comment_id) = @$entry; + my %change; + my $activity_visible = 1; + + next unless Bugzilla->user->can_see_bug($bugid); + + # check if the user should see this field's activity + if ($fieldname eq 'remaining_time' + || $fieldname eq 'estimated_time' + || $fieldname eq 'work_time' + || $fieldname eq 'deadline') + { + $activity_visible = Bugzilla->user->is_timetracker; + } + elsif ($fieldname eq 'longdescs.isprivate' + && !Bugzilla->user->is_insider + && $added) + { + $activity_visible = 0; + } + else { + $activity_visible = 1; + } + + if ($activity_visible) { + # Check for the results of an old Bugzilla data corruption bug + if (($added eq '?' && $removed eq '?') + || ($added =~ /^\? / || $removed =~ /^\? /)) { + $incomplete_data = 1; + } + + # An operation, done by 'who' at time 'when', has a number of + # 'changes' associated with it. + # If this is the start of a new operation, store the data from the + # previous one, and set up the new one. + if ($operation->{'who'} + && ($who ne $operation->{'who'} + || $when ne $operation->{'when'})) + { + $operation->{'changes'} = $changes; + push (@operations, $operation); + $operation = {}; + $changes = []; + } + + $operation->{'bug'} = $bugid; + $operation->{'who'} = $who; + $operation->{'when'} = $when; + + $change{'fieldname'} = $fieldname; + $change{'attachid'} = $attachid; + $change{'removed'} = $removed; + $change{'added'} = $added; + + if ($comment_id) { + $change{'comment'} = Bugzilla::Comment->new($comment_id); + next if $change{'comment'}->count == 0; + } + + push (@$changes, \%change); + } + } + + if ($operation->{'who'}) { + $operation->{'changes'} = $changes; + push (@operations, $operation); + } + + $vars->{'incomplete_data'} = $incomplete_data; + $vars->{'operations'} = \@operations; + + } else { + + if ($from eq '') { + my ($yy, $mm) = (localtime)[5, 4]; + $from = sprintf("%4d-%02d-01", $yy + 1900, $mm + 1); + } + if ($to eq '') { + my ($yy, $mm, $dd) = (localtime)[5, 4, 3]; + $to = sprintf("%4d-%02d-%02d", $yy + 1900, $mm + 1, $dd); + } + } + + $vars->{'action'} = $input->{'action'}; + $vars->{'who'} = join(',', @who); + $vars->{'from'} = $from; + $vars->{'to'} = $to; +} + +sub triage_reports { + my ($vars, $filter) = @_; + my $dbh = Bugzilla->dbh; + my $input = Bugzilla->input_params; + my $user = Bugzilla->user; + + if ($input->{'action'} eq 'run' && $input->{'product'}) { + + # load product and components from input + + my $product = Bugzilla::Product->new({ name => $input->{'product'} }) + || ThrowUserError('invalid_object', { object => 'Product', value => $input->{'product'} }); + + my @component_ids; + if ($input->{'component'} ne '') { + my $ra_components = ref($input->{'component'}) + ? $input->{'component'} : [ $input->{'component'} ]; + foreach my $component_name (@$ra_components) { + my $component = Bugzilla::Component->new({ name => $component_name, product => $product }) + || ThrowUserError('invalid_object', { object => 'Component', value => $component_name }); + push @component_ids, $component->id; + } + } + + # determine which comment filters to run + + my $filter_commenter = $input->{'filter_commenter'}; + my $filter_commenter_on = $input->{'commenter'}; + my $filter_last = $input->{'filter_last'}; + my $filter_last_period = $input->{'last'}; + + if (!$filter_commenter || $filter_last) { + $filter_commenter = '1'; + $filter_commenter_on = 'reporter'; + } + + my $filter_commenter_id; + if ($filter_commenter && $filter_commenter_on eq 'is') { + Bugzilla::User::match_field({ 'commenter_is' => {'type' => 'single'} }); + my $user = Bugzilla::User->new({ name => $input->{'commenter_is'} }) + || ThrowUserError('invalid_object', { object => 'User', value => $input->{'commenter_is'} }); + $filter_commenter_id = $user ? $user->id : 0; + } + + my $filter_last_time; + if ($filter_last) { + if ($filter_last_period eq 'is') { + $filter_last_period = -1; + $filter_last_time = str2time($input->{'last_is'} . " 00:00:00") || 0; + } else { + detaint_natural($filter_last_period); + $filter_last_period = 14 if $filter_last_period < 14; + } + } + + # form sql queries + + my $now = (time); + my $bugs_sql = " + SELECT bug_id, short_desc, reporter, creation_ts + FROM bugs + WHERE product_id = ? + AND bug_status = 'UNCONFIRMED'"; + if (@component_ids) { + $bugs_sql .= " AND component_id IN (" . join(',', @component_ids) . ")"; + } + $bugs_sql .= " + ORDER BY creation_ts + "; + + my $comment_count_sql = " + SELECT COUNT(*) + FROM longdescs + WHERE bug_id = ? + "; + + my $comment_sql = " + SELECT who, bug_when, type, thetext, extra_data + FROM longdescs + WHERE bug_id = ? + "; + if (!Bugzilla->user->is_insider) { + $comment_sql .= " AND isprivate = 0 "; + } + $comment_sql .= " + ORDER BY bug_when DESC + LIMIT 1 + "; + + my $attach_sql = " + SELECT description, isprivate + FROM attachments + WHERE attach_id = ? + "; + + # work on an initial list of bugs + + my $list = $dbh->selectall_arrayref($bugs_sql, undef, $product->id); + my @bugs; + + foreach my $entry (@$list) { + my ($bug_id, $summary, $reporter_id, $creation_ts) = @$entry; + + next unless $user->can_see_bug($bug_id); + + # get last comment information + + my ($comment_count) = $dbh->selectrow_array($comment_count_sql, undef, $bug_id); + my ($commenter_id, $comment_ts, $type, $comment, $extra) + = $dbh->selectrow_array($comment_sql, undef, $bug_id); + my $commenter = 0; + + # apply selected filters + + if ($filter_commenter) { + next if $comment_count <= 1; + + if ($filter_commenter_on eq 'reporter') { + next if $commenter_id != $reporter_id; + + } elsif ($filter_commenter_on eq 'noconfirm') { + $commenter = Bugzilla::User->new($commenter_id); + next if $commenter_id != $reporter_id + || $commenter->in_group('canconfirm'); + + } elsif ($filter_commenter_on eq 'is') { + next if $commenter_id != $filter_commenter_id; + } + } else { + $input->{'commenter'} = ''; + $input->{'commenter_is'} = ''; + } + + if ($filter_last) { + my $comment_time = str2time($comment_ts) + or next; + if ($filter_last_period == -1) { + next if $comment_time >= $filter_last_time; + } else { + next if $now - $comment_time <= 60 * 60 * 24 * $filter_last_period; + } + } else { + $input->{'last'} = ''; + $input->{'last_is'} = ''; + } + + # get data for attachment comments + + if ($comment eq '' && $type == CMT_ATTACHMENT_CREATED) { + my ($description, $is_private) = $dbh->selectrow_array($attach_sql, undef, $extra); + next if $is_private && !Bugzilla->user->is_insider; + $comment = "(Attachment) " . $description; + } + + # truncate long comments + + if (length($comment) > 80) { + $comment = substr($comment, 0, 80) . '...'; + } + + # build bug hash for template + + my $bug = {}; + $bug->{id} = $bug_id; + $bug->{summary} = $summary; + $bug->{reporter} = Bugzilla::User->new($reporter_id); + $bug->{creation_ts} = $creation_ts; + $bug->{commenter} = $commenter || Bugzilla::User->new($commenter_id); + $bug->{comment_ts} = $comment_ts; + $bug->{comment} = $comment; + $bug->{comment_count} = $comment_count; + push @bugs, $bug; + } + + @bugs = sort { $b->{comment_ts} cmp $a->{comment_ts} } @bugs; + + $vars->{bugs} = \@bugs; + } else { + $input->{action} = ''; + } + + if (!$input->{filter_commenter} && !$input->{filter_last}) { + $input->{filter_commenter} = 1; + } + + $vars->{'input'} = $input; +} + +sub group_admins { + my ($vars, $filter) = @_; + my $dbh = Bugzilla->dbh; + my $user = Bugzilla->user; + + $user->in_group('editusers') + || ThrowUserError('auth_failure', { group => 'editusers', + action => 'run', + object => 'group_admins' }); + + my $query = " + SELECT groups.name, " . + $dbh->sql_group_concat('profiles.login_name', "','", 1) . " + FROM groups + LEFT JOIN user_group_map + ON user_group_map.group_id = groups.id + AND user_group_map.isbless = 1 + AND user_group_map.grant_type = 0 + LEFT JOIN profiles + ON user_group_map.user_id = profiles.userid + WHERE groups.isbuggroup = 1 + GROUP BY groups.name"; + + my @groups; + foreach my $group (@{ $dbh->selectall_arrayref($query) }) { + my @admins; + if ($group->[1]) { + foreach my $admin (split(/,/, $group->[1])) { + push(@admins, Bugzilla::User->new({ name => $admin })); + } + } + push(@groups, { name => $group->[0], admins => \@admins }); + } + + $vars->{'groups'} = \@groups; +} + +1; diff --git a/extensions/BMO/lib/WebService.pm b/extensions/BMO/lib/WebService.pm new file mode 100644 index 000000000..95bdcbdf3 --- /dev/null +++ b/extensions/BMO/lib/WebService.pm @@ -0,0 +1,207 @@ +# -*- Mode: perl; indent-tabs-mode: nil -*- +# +# The contents of this file are subject to the Mozilla Public License Version +# 1.1 (the "License"); you may not use this file except in compliance with the +# License. You may obtain a copy of the License at http://www.mozilla.org/MPL/ +# +# Software distributed under the License is distributed on an "AS IS" basis, +# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for +# the specific language governing rights and limitations under the License. +# +# The Original Code is the BMO Bugzilla Extension. +# +# The Initial Developer of the Original Code is Mozilla Foundation. Portions created +# by the Initial Developer are Copyright (C) 2011 the Mozilla Foundation. All +# Rights Reserved. +# +# Contributor(s): +# Dave Lawrence <dkl@mozilla.com> + +package Bugzilla::Extension::BMO::WebService; + +use strict; +use warnings; + +use base qw(Bugzilla::WebService); + +use Bugzilla::Constants; +use Bugzilla::Error; +use Bugzilla::WebService::Util qw(validate); +use Bugzilla::Field; + +sub getBugsConfirmer { + my ($self, $params) = validate(@_, 'names'); + my $dbh = Bugzilla->dbh; + + defined($params->{names}) + || ThrowCodeError('params_required', + { function => 'BMO.getBugsConfirmer', params => ['names'] }); + + my @user_objects = map { Bugzilla::User->check($_) } @{ $params->{names} }; + + # start filtering to remove duplicate user ids + @user_objects = values %{{ map { $_->id => $_ } @user_objects }}; + + my $fieldid = get_field_id('bug_status'); + + my $query = "SELECT DISTINCT bugs_activity.bug_id + FROM bugs_activity + LEFT JOIN bug_group_map + ON bugs_activity.bug_id = bug_group_map.bug_id + WHERE bugs_activity.fieldid = ? + AND bugs_activity.added = 'NEW' + AND bugs_activity.removed = 'UNCONFIRMED' + AND bugs_activity.who = ? + AND bug_group_map.bug_id IS NULL + ORDER BY bugs_activity.bug_id"; + + my %users; + foreach my $user (@user_objects) { + my $bugs = $dbh->selectcol_arrayref($query, undef, $fieldid, $user->id); + $users{$user->login} = $bugs; + } + + return \%users; +} + +sub getBugsVerifier { + my ($self, $params) = validate(@_, 'names'); + my $dbh = Bugzilla->dbh; + + defined($params->{names}) + || ThrowCodeError('params_required', + { function => 'BMO.getBugsVerifier', params => ['names'] }); + + my @user_objects = map { Bugzilla::User->check($_) } @{ $params->{names} }; + + # start filtering to remove duplicate user ids + @user_objects = values %{{ map { $_->id => $_ } @user_objects }}; + + my $fieldid = get_field_id('bug_status'); + + my $query = "SELECT DISTINCT bugs_activity.bug_id + FROM bugs_activity + LEFT JOIN bug_group_map + ON bugs_activity.bug_id = bug_group_map.bug_id + WHERE bugs_activity.fieldid = ? + AND bugs_activity.removed = 'RESOLVED' + AND bugs_activity.added = 'VERIFIED' + AND bugs_activity.who = ? + AND bug_group_map.bug_id IS NULL + ORDER BY bugs_activity.bug_id"; + + my %users; + foreach my $user (@user_objects) { + my $bugs = $dbh->selectcol_arrayref($query, undef, $fieldid, $user->id); + $users{$user->login} = $bugs; + } + + return \%users; +} + +1; + +__END__ + +=head1 NAME + +Bugzilla::Extension::BMO::Webservice - The BMO WebServices API + +=head1 DESCRIPTION + +This module contains API methods that are useful to user's of bugzilla.mozilla.org. + +=head1 METHODS + +See L<Bugzilla::WebService> for a description of how parameters are passed, +and what B<STABLE>, B<UNSTABLE>, and B<EXPERIMENTAL> mean. + +=head2 getBugsConfirmer + +B<UNSTABLE> + +=over + +=item B<Description> + +This method returns public bug ids that a given user has confirmed (changed from +C<UNCONFIRMED> to C<NEW>). + +=item B<Params> + +You pass a field called C<names> that is a list of Bugzilla login names to find bugs for. + +=over + +=item C<names> (array) - An array of strings representing Bugzilla login names. + +=back + +=item B<Returns> + +=over + +A hash of Bugzilla login names. Each name points to an array of bug ids that the user has confirmed. + +=back + +=item B<Errors> + +=over + +=back + +=item B<History> + +=over + +=item Added in BMO Bugzilla B<4.0>. + +=back + +=back + +=head2 getBugsVerifier + +B<UNSTABLE> + +=over + +=item B<Description> + +This method returns public bug ids that a given user has verified (changed from +C<RESOLVED> to C<VERIFIED>). + +=item B<Params> + +You pass a field called C<names> that is a list of Bugzilla login names to find bugs for. + +=over + +=item C<names> (array) - An array of strings representing Bugzilla login names. + +=back + +=item B<Returns> + +=over + +A hash of Bugzilla login names. Each name points to an array of bug ids that the user has verified. + +=back + +=item B<Errors> + +=over + +=back + +=item B<History> + +=over + +=item Added in BMO Bugzilla B<4.0>. + +=back + +=back |