diff options
-rw-r--r-- | Bugzilla/DB/Schema.pm | 17 | ||||
-rw-r--r-- | Bugzilla/Report.pm | 134 | ||||
-rw-r--r-- | Bugzilla/User.pm | 30 | ||||
-rwxr-xr-x | report.cgi | 50 | ||||
-rw-r--r-- | template/en/default/global/messages.html.tmpl | 9 | ||||
-rw-r--r-- | template/en/default/global/useful-links.html.tmpl | 13 | ||||
-rw-r--r-- | template/en/default/global/user-error.html.tmpl | 8 | ||||
-rw-r--r-- | template/en/default/reports/report.html.tmpl | 41 |
8 files changed, 289 insertions, 13 deletions
diff --git a/Bugzilla/DB/Schema.pm b/Bugzilla/DB/Schema.pm index 156dca378..728176684 100644 --- a/Bugzilla/DB/Schema.pm +++ b/Bugzilla/DB/Schema.pm @@ -1026,6 +1026,23 @@ use constant ABSTRACT_SCHEMA => { ], }, + reports => { + FIELDS => [ + id => {TYPE => 'MEDIUMSERIAL', NOTNULL => 1, + PRIMARYKEY => 1}, + user_id => {TYPE => 'INT3', NOTNULL => 1, + REFERENCES => {TABLE => 'profiles', + COLUMN => 'userid', + DELETE => 'CASCADE'}}, + name => {TYPE => 'varchar(64)', NOTNULL => 1}, + query => {TYPE => 'LONGTEXT', NOTNULL => 1}, + ], + INDEXES => [ + reports_user_id_idx => {FIELDS => [qw(user_id name)], + TYPE => 'UNIQUE'}, + ], + }, + component_cc => { FIELDS => [ diff --git a/Bugzilla/Report.pm b/Bugzilla/Report.pm new file mode 100644 index 000000000..4c9f33226 --- /dev/null +++ b/Bugzilla/Report.pm @@ -0,0 +1,134 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# This Source Code Form is "Incompatible With Secondary Licenses", as +# defined by the Mozilla Public License, v. 2.0. + +use strict; + +package Bugzilla::Report; + +use base qw(Bugzilla::Object); + +use Bugzilla::CGI; +use Bugzilla::Constants; +use Bugzilla::Error; +use Bugzilla::Util; + +use constant DB_TABLE => 'reports'; + +# Do not track reports saved by users. +use constant AUDIT_CREATES => 0; +use constant AUDIT_UPDATES => 0; +use constant AUDIT_REMOVES => 0; + +use constant DB_COLUMNS => qw( + id + user_id + name + query +); + +use constant UPDATE_COLUMNS => qw( + name + query +); + +use constant VALIDATORS => { + name => \&_check_name, + query => \&_check_query, +}; + +############## +# Validators # +############## + +sub _check_name { + my ($invocant, $name) = @_; + $name = clean_text($name); + $name || ThrowUserError("report_name_missing"); + $name !~ /[<>&]/ || ThrowUserError("illegal_query_name"); + if (length($name) > MAX_LEN_QUERY_NAME) { + ThrowUserError("query_name_too_long"); + } + return $name; +} + +sub _check_query { + my ($invocant, $query) = @_; + $query || ThrowUserError("buglist_parameters_required"); + my $cgi = new Bugzilla::CGI($query); + $cgi->clean_search_url; + return $cgi->query_string; +} + +############# +# Accessors # +############# + +sub query { return $_[0]->{'query'}; } + +sub set_name { $_[0]->set('name', $_[1]); } +sub set_query { $_[0]->set('query', $_[1]); } + +########### +# Methods # +########### + +sub create { + my $class = shift; + my $param = shift; + + Bugzilla->login(LOGIN_REQUIRED); + $param->{'user_id'} = Bugzilla->user->id; + + unshift @_, $param; + my $self = $class->SUPER::create(@_); +} + +sub check { + my $class = shift; + my $report = $class->SUPER::check(@_); + my $user = Bugzilla->user; + if ( grep($_->id eq $report->id, @{$user->reports})) { + return $report; + } else { + ThrowUserError('report_access_denied'); + } +} + +1; + +__END__ + +=head1 NAME + +Bugzilla::Report - Bugzilla report class. + +=head1 SYNOPSIS + + use Bugzilla::Report; + + my $report = new Bugzilla::Report(1); + + my $report = Bugzilla::Report->check({id => $id}); + + my $name = $report->name; + my $query = $report->query; + + my $report = Bugzilla::Report->create({ name => $name, query => $query }); + + $report->set_name($new_name); + $report->set_query($new_query); + $report->update(); + + $report->remove_from_db; + +=head1 DESCRIPTION + +Report.pm represents a Report object. It is an implementation +of L<Bugzilla::Object>, and thus provides all methods that +L<Bugzilla::Object> provides. + +=cut diff --git a/Bugzilla/User.pm b/Bugzilla/User.pm index 9c869dc63..708d12c64 100644 --- a/Bugzilla/User.pm +++ b/Bugzilla/User.pm @@ -578,6 +578,25 @@ sub save_last_search { return $search; } +sub reports { + my $self = shift; + return $self->{reports} if defined $self->{reports}; + return [] unless $self->id; + + my $dbh = Bugzilla->dbh; + my $report_ids = $dbh->selectcol_arrayref( + 'SELECT id FROM reports WHERE user_id = ?', undef, $self->id); + require Bugzilla::Report; + $self->{reports} = Bugzilla::Report->new_from_list($report_ids); + return $self->{reports}; +} + +sub flush_reports_cache { + my $self = shift; + + delete $self->{reports}; +} + sub settings { my ($self) = @_; @@ -2275,6 +2294,17 @@ Should only be called by C<Bugzilla::Auth::login>, for the most part. Returns the disable text of the user, if any. +=item C<reports> + +Returns an arrayref of the user's own saved reports. The array contains +L<Bugzilla::Reports> objects. + +=item C<flush_reports_cache> + +Some code modifies the set of stored reports. Because C<Bugzilla::User> does +not handle these modifications, but does cache the result of calling C<reports> +internally, such code must call this method to flush the cached result. + =item C<settings> Returns a hash of hashes which holds the user's settings. The first key is diff --git a/report.cgi b/report.cgi index 83561fde5..5778841b3 100755 --- a/report.cgi +++ b/report.cgi @@ -15,6 +15,8 @@ use Bugzilla::Util; use Bugzilla::Error; use Bugzilla::Field; use Bugzilla::Search; +use Bugzilla::Report; +use Bugzilla::Token; use List::MoreUtils qw(uniq); @@ -34,6 +36,7 @@ if (grep(/^cmd-/, $cgi->param())) { Bugzilla->login(); my $action = $cgi->param('action') || 'menu'; +my $token = $cgi->param('token'); if ($action eq "menu") { # No need to do any searching in this case, so bail out early. @@ -41,6 +44,52 @@ if ($action eq "menu") { $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"; + } + + $user->flush_reports_cache; + + print $cgi->header(); + + $vars->{'reportname'} = $name; + + $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 $report = Bugzilla::Report->check({id => $report_id}); + $report->remove_from_db(); + + $user->flush_reports_cache; + + print $cgi->header(); + + $vars->{'message'} = 'report_deleted'; + $vars->{'reportname'} = $report->name; + + $template->process("global/message.html.tmpl", $vars) + || ThrowTemplateError($template->error()); + exit; } # Sanitize the URL, to make URLs shorter. @@ -209,6 +258,7 @@ $vars->{'width'} = $width if $width; $vars->{'height'} = $height if $height; $vars->{'query'} = $query; +$vars->{'saved_report_id'} = $cgi->param('saved_report_id'); $vars->{'debug'} = $cgi->param('debug'); my $formatparam = $cgi->param('format'); diff --git a/template/en/default/global/messages.html.tmpl b/template/en/default/global/messages.html.tmpl index d39890c13..d93ed537e 100644 --- a/template/en/default/global/messages.html.tmpl +++ b/template/en/default/global/messages.html.tmpl @@ -795,6 +795,15 @@ or you don't have access to it. The following is a list of the products you can choose from. + [% ELSIF message_tag == "report_created" %] + OK, you have a new saved report named <em>[% reportname FILTER html %]</em>. + + [% ELSIF message_tag == "report_deleted" %] + OK, the <em>[% reportname FILTER html %]</em> report is gone. + + [% ELSIF message_tag == "report_updated" %] + The saved report <em>[% reportname FILTER html %]</em> has been updated. + [% ELSIF message_tag == "remaining_time_zeroed" %] The [% field_descs.remaining_time FILTER html %] field has been set to zero automatically as part of closing this [% terms.bug %] diff --git a/template/en/default/global/useful-links.html.tmpl b/template/en/default/global/useful-links.html.tmpl index 1b5ba9a30..5959ab656 100644 --- a/template/en/default/global/useful-links.html.tmpl +++ b/template/en/default/global/useful-links.html.tmpl @@ -56,7 +56,18 @@ </li> [% END %] - [%# Individual bugs addition %] + [% IF user.reports.size %] + <li id="reports-saved"> + <ul class="links"> + [% FOREACH r = user.reports %] + <li>[% '<span class="separator">| </span>' IF print_pipe %] + <a href="report.cgi?[% r.query FILTER html %]&saved_report_id= + [%~ r.id FILTER uri %]">[% r.name FILTER html %]</a></li> + [% print_pipe = 1 %] + [% END %] + </ul> + </li> + [% END %] [%# Sections of links to more things users can do on this installation. %] [% Hook.process("end") %] diff --git a/template/en/default/global/user-error.html.tmpl b/template/en/default/global/user-error.html.tmpl index 0d51eaa1b..fbb9ca169 100644 --- a/template/en/default/global/user-error.html.tmpl +++ b/template/en/default/global/user-error.html.tmpl @@ -1480,6 +1480,14 @@ To reassign [% terms.abug %], you must provide an address for the new assignee. + [% ELSIF error == "report_name_missing" %] + [% title = "No Report Name Specified" %] + You must enter a name for your report. + + [% ELSIF error == "report_access_denied" %] + [% title = "Report Access Denied" %] + You cannot access this report. + [% ELSIF error == "require_component" %] [% title = "Component Needed" %] To file this [% terms.bug %], you must first choose a component. diff --git a/template/en/default/reports/report.html.tmpl b/template/en/default/reports/report.html.tmpl index 84cefbded..76049d04e 100644 --- a/template/en/default/reports/report.html.tmpl +++ b/template/en/default/reports/report.html.tmpl @@ -150,18 +150,35 @@ </tr> </table> - <p> - [% IF format == "table" %] - <a href="query.cgi?[% switchbase %]&format=report-table">Edit - this report</a> - [% ELSE %] - <a href="query.cgi?[% switchbase %]&chart_format= - [% format %]&format=report-graph&cumulate=[% cumulate %]"> - Edit this report - </a> - [% END %] - </p> - + <table> + <tr> + <td> + [% IF format == "table" %] + <a href="query.cgi?[% switchbase %]&format=report-table">Edit this report</a> + [% ELSE %] + <a href="query.cgi?[% switchbase %]&chart_format= + [%~ format %]&format=report-graph&cumulate=[% cumulate %]"> + Edit this report</a> + [% END %] + </td> + <td> </td> + <td> + [% IF saved_report_id %] + <a href="report.cgi?action=del&saved_report_id=[% saved_report_id FILTER uri %]&token= + [%~ issue_hash_token(['delete_report', saved_report_id]) FILTER uri %]">Forget this report</a> + [% ELSE %] + <form method="get" action="report.cgi"> + <input type="submit" id="remember" value="Remember report"> as + <input type="hidden" name="query" value="[% switchbase %]&format=[% format FILTER html %]&action=wrap"> + <input type="hidden" name="action" value="add"> + <input type="hidden" name="token" value="[% issue_hash_token(['save_report']) FILTER html %]"> + <input type="text" id="name" name="name" size="20" value="" maxlength="64"> + </form> + [% END %] + </td> + </tr> + </table> + </div> [% PROCESS global/footer.html.tmpl %] |