123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328 |
- #!/usr/bin/env perl -wT
- # -*- 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 Bugzilla Bug Tracking System.
- #
- # The Initial Developer of the Original Code is Netscape Communications
- # Corporation. Portions created by Netscape are
- # Copyright (C) 1998 Netscape Communications Corporation. All
- # Rights Reserved.
- #
- # Contributor(s): Gervase Markham <gerv@gerv.net>
- # Lance Larsh <lance.larsh@oracle.com>
- # Glossary:
- # series: An individual, defined set of data plotted over time.
- # data set: What a series is called in the UI.
- # line: A set of one or more series, to be summed and drawn as a single
- # line when the series is plotted.
- # chart: A set of lines
- #
- # So when you select rows in the UI, you are selecting one or more lines, not
- # series.
- # Generic Charting TODO:
- #
- # JS-less chart creation - hard.
- # Broken image on error or no data - need to do much better.
- # Centralise permission checking, so Bugzilla->user->in_group('editbugs')
- # not scattered everywhere.
- # User documentation :-)
- #
- # Bonus:
- # Offer subscription when you get a "series already exists" error?
- use strict;
- use lib qw(. lib);
- use Bugzilla;
- use Bugzilla::Constants;
- use Bugzilla::Error;
- use Bugzilla::Util;
- use Bugzilla::Chart;
- use Bugzilla::Series;
- use Bugzilla::User;
- # For most scripts we don't make $cgi and $template global variables. But
- # when preparing Bugzilla for mod_perl, this script used these
- # variables in so many subroutines that it was easier to just
- # make them globals.
- local our $cgi = Bugzilla->cgi;
- local our $template = Bugzilla->template;
- local our $vars = {};
- # Go back to query.cgi if we are adding a boolean chart parameter.
- if (grep(/^cmd-/, $cgi->param())) {
- my $params = $cgi->canonicalise_query("format", "ctype", "action");
- print "Location: query.cgi?format=" . $cgi->param('query_format') .
- ($params ? "&$params" : "") . "\n\n";
- exit;
- }
- my $action = $cgi->param('action');
- my $series_id = $cgi->param('series_id');
- $vars->{'doc_section'} = 'reporting.html#charts';
- # Because some actions are chosen by buttons, we can't encode them as the value
- # of the action param, because that value is localization-dependent. So, we
- # encode it in the name, as "action-<action>". Some params even contain the
- # series_id they apply to (e.g. subscribe, unsubscribe).
- my @actions = grep(/^action-/, $cgi->param());
- if ($actions[0] && $actions[0] =~ /^action-([^\d]+)(\d*)$/) {
- $action = $1;
- $series_id = $2 if $2;
- }
- $action ||= "assemble";
- # Go to buglist.cgi if we are doing a search.
- if ($action eq "search") {
- my $params = $cgi->canonicalise_query("format", "ctype", "action");
- print "Location: buglist.cgi" . ($params ? "?$params" : "") . "\n\n";
- exit;
- }
- my $user = Bugzilla->login(LOGIN_REQUIRED);
- Bugzilla->user->in_group(Bugzilla->params->{"chartgroup"})
- || ThrowUserError("auth_failure", {group => Bugzilla->params->{"chartgroup"},
- action => "use",
- object => "charts"});
- # Only admins may create public queries
- Bugzilla->user->in_group('admin') || $cgi->delete('public');
- # All these actions relate to chart construction.
- if ($action =~ /^(assemble|add|remove|sum|subscribe|unsubscribe)$/) {
- # These two need to be done before the creation of the Chart object, so
- # that the changes they make will be reflected in it.
- if ($action =~ /^subscribe|unsubscribe$/) {
- detaint_natural($series_id) || ThrowCodeError("invalid_series_id");
- my $series = new Bugzilla::Series($series_id);
- $series->$action($user->id);
- }
- my $chart = new Bugzilla::Chart($cgi);
- if ($action =~ /^remove|sum$/) {
- $chart->$action(getSelectedLines());
- }
- elsif ($action eq "add") {
- my @series_ids = getAndValidateSeriesIDs();
- $chart->add(@series_ids);
- }
- view($chart);
- }
- elsif ($action eq "plot") {
- plot();
- }
- elsif ($action eq "wrap") {
- # For CSV "wrap", we go straight to "plot".
- if ($cgi->param('ctype') && $cgi->param('ctype') eq "csv") {
- plot();
- }
- else {
- wrap();
- }
- }
- elsif ($action eq "create") {
- assertCanCreate($cgi);
-
- my $series = new Bugzilla::Series($cgi);
- if (!$series->existsInDatabase()) {
- $series->writeToDatabase();
- $vars->{'message'} = "series_created";
- }
- else {
- ThrowUserError("series_already_exists", {'series' => $series});
- }
- $vars->{'series'} = $series;
- print $cgi->header();
- $template->process("global/message.html.tmpl", $vars)
- || ThrowTemplateError($template->error());
- }
- elsif ($action eq "edit") {
- detaint_natural($series_id) || ThrowCodeError("invalid_series_id");
- assertCanEdit($series_id);
- my $series = new Bugzilla::Series($series_id);
-
- edit($series);
- }
- elsif ($action eq "alter") {
- # This is the "commit" action for editing a series
- detaint_natural($series_id) || ThrowCodeError("invalid_series_id");
- assertCanEdit($series_id);
- my $series = new Bugzilla::Series($cgi);
- # We need to check if there is _another_ series in the database with
- # our (potentially new) name. So we call existsInDatabase() to see if
- # the return value is us or some other series we need to avoid stomping
- # on.
- my $id_of_series_in_db = $series->existsInDatabase();
- if (defined($id_of_series_in_db) &&
- $id_of_series_in_db != $series->{'series_id'})
- {
- ThrowUserError("series_already_exists", {'series' => $series});
- }
-
- $series->writeToDatabase();
- $vars->{'changes_saved'} = 1;
-
- edit($series);
- }
- else {
- ThrowCodeError("unknown_action");
- }
- exit;
- # Find any selected series and return either the first or all of them.
- sub getAndValidateSeriesIDs {
- my @series_ids = grep(/^\d+$/, $cgi->param("name"));
- return wantarray ? @series_ids : $series_ids[0];
- }
- # Return a list of IDs of all the lines selected in the UI.
- sub getSelectedLines {
- my @ids = map { /^select(\d+)$/ ? $1 : () } $cgi->param();
- return @ids;
- }
- # Check if the user is the owner of series_id or is an admin.
- sub assertCanEdit {
- my ($series_id) = @_;
- my $user = Bugzilla->user;
- return if $user->in_group('admin');
- my $dbh = Bugzilla->dbh;
- my $iscreator = $dbh->selectrow_array("SELECT CASE WHEN creator = ? " .
- "THEN 1 ELSE 0 END FROM series " .
- "WHERE series_id = ?", undef,
- $user->id, $series_id);
- $iscreator || ThrowUserError("illegal_series_edit");
- }
- # Check if the user is permitted to create this series with these parameters.
- sub assertCanCreate {
- my ($cgi) = shift;
-
- Bugzilla->user->in_group("editbugs") || ThrowUserError("illegal_series_creation");
- # Check permission for frequency
- my $min_freq = 7;
- if ($cgi->param('frequency') < $min_freq && !Bugzilla->user->in_group("admin")) {
- ThrowUserError("illegal_frequency", { 'minimum' => $min_freq });
- }
- }
- sub validateWidthAndHeight {
- $vars->{'width'} = $cgi->param('width');
- $vars->{'height'} = $cgi->param('height');
- if (defined($vars->{'width'})) {
- (detaint_natural($vars->{'width'}) && $vars->{'width'} > 0)
- || ThrowCodeError("invalid_dimensions");
- }
- if (defined($vars->{'height'})) {
- (detaint_natural($vars->{'height'}) && $vars->{'height'} > 0)
- || ThrowCodeError("invalid_dimensions");
- }
- # The equivalent of 2000 square seems like a very reasonable maximum size.
- # This is merely meant to prevent accidental or deliberate DOS, and should
- # have no effect in practice.
- if ($vars->{'width'} && $vars->{'height'}) {
- (($vars->{'width'} * $vars->{'height'}) <= 4000000)
- || ThrowUserError("chart_too_large");
- }
- }
- sub edit {
- my $series = shift;
- $vars->{'category'} = Bugzilla::Chart::getVisibleSeries();
- $vars->{'creator'} = new Bugzilla::User($series->{'creator'});
- $vars->{'default'} = $series;
- print $cgi->header();
- $template->process("reports/edit-series.html.tmpl", $vars)
- || ThrowTemplateError($template->error());
- }
- sub plot {
- validateWidthAndHeight();
- $vars->{'chart'} = new Bugzilla::Chart($cgi);
- my $format = $template->get_format("reports/chart", "", scalar($cgi->param('ctype')));
- # Debugging PNGs is a pain; we need to be able to see the error messages
- if ($cgi->param('debug')) {
- print $cgi->header();
- $vars->{'chart'}->dump();
- }
- print $cgi->header($format->{'ctype'});
- disable_utf8() if ($format->{'ctype'} =~ /^image\//);
- $template->process($format->{'template'}, $vars)
- || ThrowTemplateError($template->error());
- }
- sub wrap {
- validateWidthAndHeight();
-
- # We create a Chart object so we can validate the parameters
- my $chart = new Bugzilla::Chart($cgi);
-
- $vars->{'time'} = time();
- $vars->{'imagebase'} = $cgi->canonicalise_query(
- "action", "action-wrap", "ctype", "format", "width", "height");
- print $cgi->header();
- $template->process("reports/chart.html.tmpl", $vars)
- || ThrowTemplateError($template->error());
- }
- sub view {
- my $chart = shift;
- # Set defaults
- foreach my $field ('category', 'subcategory', 'name', 'ctype') {
- $vars->{'default'}{$field} = $cgi->param($field) || 0;
- }
- # Pass the state object to the display UI.
- $vars->{'chart'} = $chart;
- $vars->{'category'} = Bugzilla::Chart::getVisibleSeries();
- print $cgi->header();
- # If we have having problems with bad data, we can set debug=1 to dump
- # the data structure.
- $chart->dump() if $cgi->param('debug');
- $template->process("reports/create-chart.html.tmpl", $vars)
- || ThrowTemplateError($template->error());
- }
|