diff options
Diffstat (limited to 'report.cgi')
-rwxr-xr-x | report.cgi | 512 |
1 files changed, 263 insertions, 249 deletions
diff --git a/report.cgi b/report.cgi index 2a8317d7a..ecb99dbcf 100755 --- a/report.cgi +++ b/report.cgi @@ -23,76 +23,80 @@ use Bugzilla::Token; use List::MoreUtils qw(uniq); -my $cgi = Bugzilla->cgi; +my $cgi = Bugzilla->cgi; my $template = Bugzilla->template; -my $vars = {}; +my $vars = {}; # Go straight back to query.cgi if we are adding a boolean chart. if (grep(/^cmd-/, $cgi->param())) { - my $params = $cgi->canonicalise_query("format", "ctype"); - my $location = "query.cgi?format=" . $cgi->param('query_format') . - ($params ? "&$params" : ""); - - print $cgi->redirect($location); - exit; + my $params = $cgi->canonicalise_query("format", "ctype"); + my $location + = "query.cgi?format=" + . $cgi->param('query_format') + . ($params ? "&$params" : ""); + + print $cgi->redirect($location); + exit; } Bugzilla->login(); my $action = $cgi->param('action') || 'menu'; -my $token = $cgi->param('token'); +my $token = $cgi->param('token'); if ($action eq "menu") { - # No need to do any searching in this case, so bail out early. - print $cgi->header(); - $template->process("reports/menu.html.tmpl", $vars) - || ThrowTemplateError($template->error()); - exit; + + # No need to do any searching in this case, so bail out early. + print $cgi->header(); + $template->process("reports/menu.html.tmpl", $vars) + || ThrowTemplateError($template->error()); + exit; } elsif ($action eq 'add') { - my $user = Bugzilla->login(LOGIN_REQUIRED); - check_hash_token($token, ['save_report']); - - my $name = clean_text($cgi->param('name')); - my $query = $cgi->param('query'); - - if (my ($report) = grep{ lc($_->name) eq lc($name) } @{$user->reports}) { - $report->set_query($query); - $report->update; - $vars->{'message'} = "report_updated"; - } else { - my $report = Bugzilla::Report->create({name => $name, query => $query}); - $vars->{'message'} = "report_created"; - } + my $user = Bugzilla->login(LOGIN_REQUIRED); + check_hash_token($token, ['save_report']); + + my $name = clean_text($cgi->param('name')); + my $query = $cgi->param('query'); + + if (my ($report) = grep { lc($_->name) eq lc($name) } @{$user->reports}) { + $report->set_query($query); + $report->update; + $vars->{'message'} = "report_updated"; + } + else { + my $report = Bugzilla::Report->create({name => $name, query => $query}); + $vars->{'message'} = "report_created"; + } - $user->flush_reports_cache; + $user->flush_reports_cache; - print $cgi->header(); + print $cgi->header(); - $vars->{'reportname'} = $name; + $vars->{'reportname'} = $name; - $template->process("global/message.html.tmpl", $vars) - || ThrowTemplateError($template->error()); - exit; + $template->process("global/message.html.tmpl", $vars) + || ThrowTemplateError($template->error()); + exit; } elsif ($action eq 'del') { - my $user = Bugzilla->login(LOGIN_REQUIRED); - my $report_id = $cgi->param('saved_report_id'); - check_hash_token($token, ['delete_report', $report_id]); + my $user = Bugzilla->login(LOGIN_REQUIRED); + my $report_id = $cgi->param('saved_report_id'); + check_hash_token($token, ['delete_report', $report_id]); - my $report = Bugzilla::Report->check({id => $report_id}); - $report->remove_from_db(); + my $report = Bugzilla::Report->check({id => $report_id}); + $report->remove_from_db(); - $user->flush_reports_cache; + $user->flush_reports_cache; - print $cgi->header(); + print $cgi->header(); - $vars->{'message'} = 'report_deleted'; - $vars->{'reportname'} = $report->name; + $vars->{'message'} = 'report_deleted'; + $vars->{'reportname'} = $report->name; - $template->process("global/message.html.tmpl", $vars) - || ThrowTemplateError($template->error()); - exit; + $template->process("global/message.html.tmpl", $vars) + || ThrowTemplateError($template->error()); + exit; } # Sanitize the URL, to make URLs shorter. @@ -103,16 +107,15 @@ my $row_field = $cgi->param('y_axis_field') || ''; my $tbl_field = $cgi->param('z_axis_field') || ''; if (!($col_field || $row_field || $tbl_field)) { - ThrowUserError("no_axes_defined"); + ThrowUserError("no_axes_defined"); } # There is no UI for these parameters anymore, # but they are still here just in case. -my $width = $cgi->param('width') || 1024; +my $width = $cgi->param('width') || 1024; my $height = $cgi->param('height') || 600; -(detaint_natural($width) && $width > 0) - || ThrowUserError("invalid_dimensions"); +(detaint_natural($width) && $width > 0) || ThrowUserError("invalid_dimensions"); $width <= 2000 || ThrowUserError("chart_too_large"); (detaint_natural($height) && $height > 0) @@ -121,50 +124,52 @@ $height <= 2000 || ThrowUserError("chart_too_large"); my $formatparam = $cgi->param('format') || ''; -# These shenanigans are necessary to make sure that both vertical and +# These shenanigans are necessary to make sure that both vertical and # horizontal 1D tables convert to the correct dimension when you ask to # display them as some sort of chart. if ($formatparam eq "table") { - if ($col_field && !$row_field) { - # 1D *tables* should be displayed vertically (with a row_field only) - $row_field = $col_field; - $col_field = ''; - } + if ($col_field && !$row_field) { + + # 1D *tables* should be displayed vertically (with a row_field only) + $row_field = $col_field; + $col_field = ''; + } } else { - if (!Bugzilla->feature('graphical_reports')) { - ThrowUserError('feature_disabled', { feature => 'graphical_reports' }); - } + if (!Bugzilla->feature('graphical_reports')) { + ThrowUserError('feature_disabled', {feature => 'graphical_reports'}); + } - if ($row_field && !$col_field) { - # 1D *charts* should be displayed horizontally (with an col_field only) - $col_field = $row_field; - $row_field = ''; - } + if ($row_field && !$col_field) { + + # 1D *charts* should be displayed horizontally (with an col_field only) + $col_field = $row_field; + $row_field = ''; + } } # Valid bug fields that can be reported on. my $valid_columns = Bugzilla::Search::REPORT_COLUMNS; # Validate the values in the axis fields or throw an error. -!$row_field +!$row_field || ($valid_columns->{$row_field} && trick_taint($row_field)) || ThrowUserError("report_axis_invalid", {fld => "x", val => $row_field}); -!$col_field +!$col_field || ($valid_columns->{$col_field} && trick_taint($col_field)) || ThrowUserError("report_axis_invalid", {fld => "y", val => $col_field}); -!$tbl_field +!$tbl_field || ($valid_columns->{$tbl_field} && trick_taint($tbl_field)) || ThrowUserError("report_axis_invalid", {fld => "z", val => $tbl_field}); -my @axis_fields = grep { $_ } ($row_field, $col_field, $tbl_field); +my @axis_fields = grep {$_} ($row_field, $col_field, $tbl_field); # Clone the params, so that Bugzilla::Search can modify them my $params = new Bugzilla::CGI($cgi); my $search = new Bugzilla::Search( - fields => \@axis_fields, - params => scalar $params->Vars, - allow_unlimited => 1, + fields => \@axis_fields, + params => scalar $params->Vars, + allow_unlimited => 1, ); $::SIG{TERM} = 'DEFAULT'; @@ -173,7 +178,7 @@ $::SIG{PIPE} = 'DEFAULT'; Bugzilla->switch_to_shadow_db(); my ($results, $extra_data) = $search->data; -# We have a hash of hashes for the data itself, and a hash to hold the +# We have a hash of hashes for the data itself, and a hash to hold the # row/col/table names. my %data; my %names; @@ -189,62 +194,61 @@ my $tbl_isnumeric = 1; # define which fields are multiselect my @multi_selects = map { $_->name } @{Bugzilla->fields( - { - obsolete => 0, - type => [FIELD_TYPE_MULTI_SELECT, FIELD_TYPE_KEYWORDS] - } -)}; -my $col_ismultiselect = scalar grep {$col_field eq $_} @multi_selects; -my $row_ismultiselect = scalar grep {$row_field eq $_} @multi_selects; -my $tbl_ismultiselect = scalar grep {$tbl_field eq $_} @multi_selects; + {obsolete => 0, type => [FIELD_TYPE_MULTI_SELECT, FIELD_TYPE_KEYWORDS]} + ) +}; +my $col_ismultiselect = scalar grep { $col_field eq $_ } @multi_selects; +my $row_ismultiselect = scalar grep { $row_field eq $_ } @multi_selects; +my $tbl_ismultiselect = scalar grep { $tbl_field eq $_ } @multi_selects; foreach my $result (@$results) { - # handle empty dimension member names - - my @rows = check_value($row_field, $result, $row_ismultiselect); - my @cols = check_value($col_field, $result, $col_ismultiselect); - my @tbls = check_value($tbl_field, $result, $tbl_ismultiselect); - - my %in_total_row; - my %in_total_col; - for my $tbl (@tbls) { - my %in_row_total; - for my $col (@cols) { - for my $row (@rows) { - $data{$tbl}{$col}{$row}++; - $names{"row"}{$row}++; - $row_isnumeric &&= ($row =~ /^-?\d+(\.\d+)?$/o); - if ($formatparam eq "table") { - if (!$in_row_total{$row}) { - $data{$tbl}{'-total-'}{$row}++; - $in_row_total{$row} = 1; - } - if (!$in_total_row{$row}) { - $data{'-total-'}{'-total-'}{$row}++; - $in_total_row{$row} = 1; - } - } - } - if ($formatparam eq "table") { - $data{$tbl}{$col}{'-total-'}++; - if (!$in_total_col{$col}) { - $data{'-total-'}{$col}{'-total-'}++; - $in_total_col{$col} = 1; - } - } - $names{"col"}{$col}++; - $col_isnumeric &&= ($col =~ /^-?\d+(\.\d+)?$/o); - } - $names{"tbl"}{$tbl}++; - $tbl_isnumeric &&= ($tbl =~ /^-?\d+(\.\d+)?$/o); + + # handle empty dimension member names + + my @rows = check_value($row_field, $result, $row_ismultiselect); + my @cols = check_value($col_field, $result, $col_ismultiselect); + my @tbls = check_value($tbl_field, $result, $tbl_ismultiselect); + + my %in_total_row; + my %in_total_col; + for my $tbl (@tbls) { + my %in_row_total; + for my $col (@cols) { + for my $row (@rows) { + $data{$tbl}{$col}{$row}++; + $names{"row"}{$row}++; + $row_isnumeric &&= ($row =~ /^-?\d+(\.\d+)?$/o); if ($formatparam eq "table") { - $data{$tbl}{'-total-'}{'-total-'}++; + if (!$in_row_total{$row}) { + $data{$tbl}{'-total-'}{$row}++; + $in_row_total{$row} = 1; + } + if (!$in_total_row{$row}) { + $data{'-total-'}{'-total-'}{$row}++; + $in_total_row{$row} = 1; + } + } + } + if ($formatparam eq "table") { + $data{$tbl}{$col}{'-total-'}++; + if (!$in_total_col{$col}) { + $data{'-total-'}{$col}{'-total-'}++; + $in_total_col{$col} = 1; } + } + $names{"col"}{$col}++; + $col_isnumeric &&= ($col =~ /^-?\d+(\.\d+)?$/o); } + $names{"tbl"}{$tbl}++; + $tbl_isnumeric &&= ($tbl =~ /^-?\d+(\.\d+)?$/o); if ($formatparam eq "table") { - $data{'-total-'}{'-total-'}{'-total-'}++; + $data{$tbl}{'-total-'}{'-total-'}++; } + } + if ($formatparam eq "table") { + $data{'-total-'}{'-total-'}{'-total-'}++; + } } my @col_names = get_names($names{"col"}, $col_isnumeric, $col_field); @@ -255,106 +259,112 @@ my @tbl_names = get_names($names{"tbl"}, $tbl_isnumeric, $tbl_field); # gathered everything into the hashes and made sure we know the size of the # data, we reformat it into an array of arrays of arrays of data. push(@tbl_names, "-total-") if (scalar(@tbl_names) > 1); - + my @image_data; foreach my $tbl (@tbl_names) { - my @tbl_data; - push(@tbl_data, \@col_names); - foreach my $row (@row_names) { - my @col_data; - foreach my $col (@col_names) { - $data{$tbl}{$col}{$row} = $data{$tbl}{$col}{$row} || 0; - push(@col_data, $data{$tbl}{$col}{$row}); - if ($tbl ne "-total-") { - # This is a bit sneaky. We spend every loop except the last - # building up the -total- data, and then last time round, - # we process it as another tbl, and push() the total values - # into the image_data array. - $data{"-total-"}{$col}{$row} += $data{$tbl}{$col}{$row}; - } - } - - push(@tbl_data, \@col_data); + my @tbl_data; + push(@tbl_data, \@col_names); + foreach my $row (@row_names) { + my @col_data; + foreach my $col (@col_names) { + $data{$tbl}{$col}{$row} = $data{$tbl}{$col}{$row} || 0; + push(@col_data, $data{$tbl}{$col}{$row}); + if ($tbl ne "-total-") { + + # This is a bit sneaky. We spend every loop except the last + # building up the -total- data, and then last time round, + # we process it as another tbl, and push() the total values + # into the image_data array. + $data{"-total-"}{$col}{$row} += $data{$tbl}{$col}{$row}; + } } - - unshift(@image_data, \@tbl_data); + + push(@tbl_data, \@col_data); + } + + unshift(@image_data, \@tbl_data); } $vars->{'col_field'} = $col_field; $vars->{'row_field'} = $row_field; $vars->{'tbl_field'} = $tbl_field; -$vars->{'time'} = localtime(time()); +$vars->{'time'} = localtime(time()); -$vars->{'col_names'} = \@col_names; -$vars->{'row_names'} = \@row_names; -$vars->{'tbl_names'} = \@tbl_names; +$vars->{'col_names'} = \@col_names; +$vars->{'row_names'} = \@row_names; +$vars->{'tbl_names'} = \@tbl_names; $vars->{'note_multi_select'} = $row_ismultiselect || $col_ismultiselect; # Below a certain width, we don't see any bars, so there needs to be a minimum. if ($formatparam eq "bar") { - my $min_width = (scalar(@col_names) || 1) * 20; + my $min_width = (scalar(@col_names) || 1) * 20; - if (!$cgi->param('cumulate')) { - $min_width *= (scalar(@row_names) || 1); - } + if (!$cgi->param('cumulate')) { + $min_width *= (scalar(@row_names) || 1); + } - $vars->{'min_width'} = $min_width; + $vars->{'min_width'} = $min_width; } -$vars->{'width'} = $width; -$vars->{'height'} = $height; -$vars->{'queries'} = $extra_data; +$vars->{'width'} = $width; +$vars->{'height'} = $height; +$vars->{'queries'} = $extra_data; $vars->{'saved_report_id'} = $cgi->param('saved_report_id'); -if ($cgi->param('debug') - && Bugzilla->params->{debug_group} - && Bugzilla->user->in_group(Bugzilla->params->{debug_group}) -) { - $vars->{'debug'} = 1; +if ( $cgi->param('debug') + && Bugzilla->params->{debug_group} + && Bugzilla->user->in_group(Bugzilla->params->{debug_group})) +{ + $vars->{'debug'} = 1; } if ($action eq "wrap") { - # So which template are we using? If action is "wrap", we will be using - # no format (it gets passed through to be the format of the actual data), - # and either report.csv.tmpl (CSV), or report.html.tmpl (everything else). - # report.html.tmpl produces an HTML framework for either tables of HTML - # data, or images generated by calling report.cgi again with action as - # "plot". - $formatparam =~ s/[^a-zA-Z\-]//g; - $vars->{'format'} = $formatparam; - $formatparam = ''; - - # We need to keep track of the defined restrictions on each of the - # axes, because buglistbase, below, throws them away. Without this, we - # get buglistlinks wrong if there is a restriction on an axis field. - $vars->{'col_vals'} = get_field_restrictions($col_field); - $vars->{'row_vals'} = get_field_restrictions($row_field); - $vars->{'tbl_vals'} = get_field_restrictions($tbl_field); - - # We need a number of different variants of the base URL for different - # URLs in the HTML. - $vars->{'buglistbase'} = $cgi->canonicalise_query( - "x_axis_field", "y_axis_field", "z_axis_field", - "ctype", "format", "query_format", @axis_fields); - $vars->{'imagebase'} = $cgi->canonicalise_query( - $tbl_field, "action", "ctype", "format", "width", "height"); - $vars->{'switchbase'} = $cgi->canonicalise_query( - "query_format", "action", "ctype", "format", "width", "height"); - $vars->{'data'} = \%data; + + # So which template are we using? If action is "wrap", we will be using + # no format (it gets passed through to be the format of the actual data), + # and either report.csv.tmpl (CSV), or report.html.tmpl (everything else). + # report.html.tmpl produces an HTML framework for either tables of HTML + # data, or images generated by calling report.cgi again with action as + # "plot". + $formatparam =~ s/[^a-zA-Z\-]//g; + $vars->{'format'} = $formatparam; + $formatparam = ''; + + # We need to keep track of the defined restrictions on each of the + # axes, because buglistbase, below, throws them away. Without this, we + # get buglistlinks wrong if there is a restriction on an axis field. + $vars->{'col_vals'} = get_field_restrictions($col_field); + $vars->{'row_vals'} = get_field_restrictions($row_field); + $vars->{'tbl_vals'} = get_field_restrictions($tbl_field); + + # We need a number of different variants of the base URL for different + # URLs in the HTML. + $vars->{'buglistbase'} = $cgi->canonicalise_query( + "x_axis_field", "y_axis_field", "z_axis_field", "ctype", + "format", "query_format", @axis_fields + ); + $vars->{'imagebase'} + = $cgi->canonicalise_query($tbl_field, "action", "ctype", "format", "width", + "height"); + $vars->{'switchbase'} + = $cgi->canonicalise_query("query_format", "action", "ctype", "format", + "width", "height"); + $vars->{'data'} = \%data; } elsif ($action eq "plot") { - # If action is "plot", we will be using a format as normal (pie, bar etc.) - # and a ctype as normal (currently only png.) - $vars->{'cumulate'} = $cgi->param('cumulate') ? 1 : 0; - $vars->{'x_labels_vertical'} = $cgi->param('x_labels_vertical') ? 1 : 0; - $vars->{'data'} = \@image_data; + + # If action is "plot", we will be using a format as normal (pie, bar etc.) + # and a ctype as normal (currently only png.) + $vars->{'cumulate'} = $cgi->param('cumulate') ? 1 : 0; + $vars->{'x_labels_vertical'} = $cgi->param('x_labels_vertical') ? 1 : 0; + $vars->{'data'} = \@image_data; } else { - ThrowUserError('unknown_action', {action => $action}); + ThrowUserError('unknown_action', {action => $action}); } my $format = $template->get_format("reports/report", $formatparam, - scalar($cgi->param('ctype'))); + scalar($cgi->param('ctype'))); # If we get a template or CGI error, it comes out as HTML, which isn't valid # PNG data, and the browser just displays a "corrupt PNG" message. So, you can @@ -367,11 +377,11 @@ print $cgi->header($format->{'ctype'}); # Problems with this CGI are often due to malformed data. Setting debug=1 # prints out both data structures. if ($cgi->param('debug')) { - require Data::Dumper; - say "<pre>data hash:"; - say html_quote(Data::Dumper::Dumper(%data)); - say "\ndata array:"; - say html_quote(Data::Dumper::Dumper(@image_data)) . "\n\n</pre>"; + require Data::Dumper; + say "<pre>data hash:"; + say html_quote(Data::Dumper::Dumper(%data)); + say "\ndata array:"; + say html_quote(Data::Dumper::Dumper(@image_data)) . "\n\n</pre>"; } # All formats point to the same section of the documentation. @@ -384,65 +394,69 @@ $template->process("$format->{'template'}", $vars) sub get_names { - my ($names, $isnumeric, $field_name) = @_; - my ($field, @sorted); - # XXX - This is a hack to handle the actual_time/work_time field, - # because it's named 'actual_time' in Search.pm but 'work_time' in Field.pm. - $_[2] = $field_name = 'work_time' if $field_name eq 'actual_time'; - - # _realname fields aren't real Bugzilla::Field objects, but they are a - # valid axis, so we don't vailidate them as Bugzilla::Field objects. - $field = Bugzilla::Field->check($field_name) - if ($field_name && $field_name !~ /_realname$/); - - if ($field && $field->is_select) { - foreach my $value (@{$field->legal_values}) { - push(@sorted, $value->name) if $names->{$value->name}; - } - unshift(@sorted, '---') if ($field_name eq 'resolution' - || $field->type == FIELD_TYPE_MULTI_SELECT); - @sorted = uniq @sorted; - } - elsif ($isnumeric) { - # It's not a field we are preserving the order of, so sort it - # numerically... - @sorted = sort { $a <=> $b } keys %$names; - } - else { - # ...or alphabetically, as appropriate. - @sorted = sort keys %$names; + my ($names, $isnumeric, $field_name) = @_; + my ($field, @sorted); + + # XXX - This is a hack to handle the actual_time/work_time field, + # because it's named 'actual_time' in Search.pm but 'work_time' in Field.pm. + $_[2] = $field_name = 'work_time' if $field_name eq 'actual_time'; + + # _realname fields aren't real Bugzilla::Field objects, but they are a + # valid axis, so we don't vailidate them as Bugzilla::Field objects. + $field = Bugzilla::Field->check($field_name) + if ($field_name && $field_name !~ /_realname$/); + + if ($field && $field->is_select) { + foreach my $value (@{$field->legal_values}) { + push(@sorted, $value->name) if $names->{$value->name}; } - - return @sorted; + unshift(@sorted, '---') + if ($field_name eq 'resolution' || $field->type == FIELD_TYPE_MULTI_SELECT); + @sorted = uniq @sorted; + } + elsif ($isnumeric) { + + # It's not a field we are preserving the order of, so sort it + # numerically... + @sorted = sort { $a <=> $b } keys %$names; + } + else { + # ...or alphabetically, as appropriate. + @sorted = sort keys %$names; + } + + return @sorted; } sub check_value { - my ($field, $result, $ismultiselect) = @_; - - my $value; - if (!defined $field) { - $value = ''; - } - elsif ($field eq '') { - $value = ' '; - } - else { - $value = shift @$result; - $value = ' ' if (!defined $value || $value eq ''); - $value = '---' if (($field eq 'resolution' || $ismultiselect ) && - $value eq ' '); - } - if ($ismultiselect) { - # Some DB servers have a space after the comma, some others don't. - return split(/, ?/, $value); - } else { - return ($value); - } + my ($field, $result, $ismultiselect) = @_; + + my $value; + if (!defined $field) { + $value = ''; + } + elsif ($field eq '') { + $value = ' '; + } + else { + $value = shift @$result; + $value = ' ' if (!defined $value || $value eq ''); + $value = '---' if (($field eq 'resolution' || $ismultiselect) && $value eq ' '); + } + if ($ismultiselect) { + + # Some DB servers have a space after the comma, some others don't. + return split(/, ?/, $value); + } + else { + return ($value); + } } sub get_field_restrictions { - my $field = shift; - my $cgi = Bugzilla->cgi; + my $field = shift; + my $cgi = Bugzilla->cgi; - return join('&', map {url_quote($field) . '=' . url_quote($_)} $cgi->param($field)); + return join('&', + map { url_quote($field) . '=' . url_quote($_) } $cgi->param($field)); } |