summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--FS/FS/Cron/send_subscribed.pm32
-rw-r--r--FS/FS/Mason.pm1
-rw-r--r--FS/FS/Schema.pm18
-rw-r--r--FS/FS/log_context.pm2
-rw-r--r--FS/FS/saved_search.pm97
-rw-r--r--FS/FS/saved_search_option.pm124
-rw-r--r--FS/MANIFEST2
-rwxr-xr-xFS/bin/freeside-daily4
-rw-r--r--FS/t/saved_search_option.t5
-rw-r--r--httemplate/browse/saved_search.html81
-rw-r--r--httemplate/edit/process/saved_search.html15
-rw-r--r--httemplate/edit/saved_search.html113
-rw-r--r--httemplate/elements/header-popup.html6
-rw-r--r--httemplate/elements/menu.html17
-rw-r--r--httemplate/elements/tr-fixed-date.html2
-rw-r--r--httemplate/misc/delete-saved_search.html25
-rw-r--r--httemplate/search/elements/search-html.html24
-rw-r--r--httemplate/search/elements/search.html1
18 files changed, 398 insertions, 171 deletions
diff --git a/FS/FS/Cron/send_subscribed.pm b/FS/FS/Cron/send_subscribed.pm
new file mode 100644
index 000000000..2b1f662e6
--- /dev/null
+++ b/FS/FS/Cron/send_subscribed.pm
@@ -0,0 +1,32 @@
+package FS::Cron::send_subscribed;
+
+use strict;
+use base 'Exporter';
+use FS::saved_search;
+use FS::Record qw(qsearch);
+use FS::queue;
+
+our @EXPORT_OK = qw( send_subscribed );
+our $DEBUG = 1;
+
+sub send_subscribed {
+
+ my @subs = qsearch('saved_search', {
+ 'disabled' => '',
+ 'freq' => { op => '!=', value => '' },
+ });
+ foreach my $saved_search (@subs) {
+ my $date = $saved_search->next_send_date;
+ warn "checking '".$saved_search->searchname."' with date $date\n"
+ if $DEBUG;
+
+ if ( $^T > $saved_search->next_send_date ) {
+ warn "queueing delivery\n";
+ my $job = FS::queue->new({ job => 'FS::saved_search::queueable_send' });
+ $job->insert( $saved_search->searchnum );
+ }
+ }
+
+}
+
+1;
diff --git a/FS/FS/Mason.pm b/FS/FS/Mason.pm
index e8a1af6c6..ee87b2de4 100644
--- a/FS/FS/Mason.pm
+++ b/FS/FS/Mason.pm
@@ -416,7 +416,6 @@ if ( -e $addl_handler_use_file ) {
use FS::commission_schedule;
use FS::commission_rate;
use FS::saved_search;
- use FS::saved_search_option;
# Sammath Naur
if ( $FS::Mason::addl_handler_use ) {
diff --git a/FS/FS/Schema.pm b/FS/FS/Schema.pm
index 4ff9db211..df987ffc7 100644
--- a/FS/FS/Schema.pm
+++ b/FS/FS/Schema.pm
@@ -7492,6 +7492,7 @@ sub tables_hashref {
'usernum', 'int', 'NULL', '', '', '',
'searchname', 'varchar', '', $char_d, '', '',
'path', 'varchar', '', $char_d, '', '',
+ 'params', 'text', 'NULL', '', '', '',
'disabled', 'char', 'NULL', 1, '', '',
'freq', 'varchar', 'NULL', 16, '', '',
'last_sent', 'int', 'NULL', '', '', '',
@@ -7507,23 +7508,6 @@ sub tables_hashref {
],
},
- 'saved_search_option' => {
- 'columns' => [
- 'optionnum', 'serial', '', '', '', '',
- 'searchnum', 'int', '', '', '', '',
- 'optionname', 'varchar', '', $char_d, '', '',
- 'optionvalue', 'text', 'NULL', '', '', '',
- ],
- 'primary_key' => 'optionnum',
- 'unique' => [ [ 'searchnum', 'optionname' ] ],
- 'index' => [],
- 'foreign_keys' => [
- { columns => [ 'searchnum' ],
- table => 'saved_search',
- },
- ],
- },
-
# name type nullability length default local
#'new_table' => {
diff --git a/FS/FS/log_context.pm b/FS/FS/log_context.pm
index 37befb515..ee3e413ee 100644
--- a/FS/FS/log_context.pm
+++ b/FS/FS/log_context.pm
@@ -13,6 +13,8 @@ my @contexts = ( qw(
FS::pay_batch::import_from_gateway
FS::part_pkg
FS::Misc::Geo::standardize_uscensus
+ FS::saved_search::send
+ FS::saved_search::render
Cron::bill
Cron::backup
Cron::upload
diff --git a/FS/FS/saved_search.pm b/FS/FS/saved_search.pm
index 075d759f6..caaf7fea8 100644
--- a/FS/FS/saved_search.pm
+++ b/FS/FS/saved_search.pm
@@ -1,13 +1,15 @@
package FS::saved_search;
-use base qw( FS::option_Common FS::Record );
+use base qw( FS::Record );
use strict;
use FS::Record qw( qsearch qsearchs );
use FS::Conf;
+use FS::Log;
+use FS::Misc qw(send_email);
+use MIME::Entity;
use Class::Load 'load_class';
use URI::Escape;
use DateTime;
-use Try::Tiny;
=head1 NAME
@@ -56,6 +58,10 @@ A descriptive name.
The path to the page within the Mason document space.
+=item params
+
+The query string for the search.
+
=item disabled
'Y' to hide the search from the user's Reports / Saved menu.
@@ -128,6 +134,7 @@ sub check {
#|| $self->ut_foreign_keyn('usernum', 'access_user', 'usernum')
|| $self->ut_text('searchname')
|| $self->ut_text('path')
+ || $self->ut_textn('params') # URL-escaped, so ut_textn
|| $self->ut_flag('disabled')
|| $self->ut_enum('freq', [ '', 'daily', 'weekly', 'monthly' ])
|| $self->ut_numbern('last_sent')
@@ -138,6 +145,14 @@ sub check {
$self->SUPER::check;
}
+sub replace_check {
+ my ($new, $old) = @_;
+ if ($new->usernum != $old->usernum) {
+ return "can't change owner of a saved search";
+ }
+ '';
+}
+
=item next_send_date
Returns the next date this report should be sent next. If it's not set for
@@ -168,8 +183,6 @@ Returns the CGI query string for the parameters to this report.
=cut
-# multivalued options are newline-separated in the database
-
sub query_string {
my $self = shift;
@@ -177,12 +190,7 @@ sub query_string {
$type = 'html-print' if $type eq '' || $type eq 'html';
$type = '.xls' if $type eq 'xls';
my $query = "_type=$type";
- my %options = $self->options;
- foreach my $k (keys %options) {
- foreach my $v (split("\n", $options{$k})) {
- $query .= ';' . uri_escape($k) . '=' . uri_escape($v);
- }
- }
+ $query .= ';' . $self->params if $self->params;
$query;
}
@@ -194,6 +202,7 @@ Returns the report content as an HTML or Excel file.
sub render {
my $self = shift;
+ my $log = FS::Log->new('FS::saved_search::render');
my $outbuf;
# delayed loading
@@ -210,11 +219,10 @@ sub render {
local $FS::CurrentUser::CurrentUser = $self->access_user;
local $FS::Mason::Request::QUERY_STRING = $self->query_string;
- local $FS::Mason::Request::FSURL = ''; #?
-# local $ENV{SERVER_NAME} = 'localhost'; #?
-# local $ENV{SCRIPT_NAME} = '/freeside'. $self->path;
+ local $FS::Mason::Request::FSURL = $self->access_user->option('rooturl');
- my $mason_request = $fs_interp->make_request(comp => $self->path);
+ my $mason_request = $fs_interp->make_request(comp => '/' . $self->path);
+ $mason_request->notes('inline_stylesheet', 1);
local $@;
eval { $mason_request->exec(); };
@@ -224,9 +232,9 @@ sub render {
$error = $error->message;
}
- warn "Error rendering " . $self->path .
+ $log->error("Error rendering " . $self->path .
" for " . $self->access_user->username .
- ":\n$error\n";
+ ":\n$error\n");
# send it to the user anyway, so there's a way to diagnose the error
$outbuf = '<h3>Error</h3>
<p>There was an error generating the report "'.$self->searchname.'".</p>
@@ -237,6 +245,63 @@ sub render {
return $outbuf;
}
+=item send
+
+Sends the search by email. If anything fails, logs and returns an error.
+
+=cut
+
+sub send {
+ my $self = shift;
+ my $log = FS::Log->new('FS::saved_search::send');
+ my $conf = FS::Conf->new;
+ my $user = $self->access_user;
+ my $username = $user->username;
+ my $user_email = $user->option('email_address');
+ my $error;
+ if (!$user_email) {
+ $error = "User '$username' has no email address.";
+ $log->error($error);
+ return $error;
+ }
+ $log->debug('Rendering saved search');
+ my $content = $self->render;
+ # XXX come back to this for content-type options
+ my $part = MIME::Entity->build(
+ 'Type' => 'text/html',
+ 'Encoding' => 'quoted-printable', # change this for spreadsheet
+ 'Disposition' => 'inline',
+ 'Data' => $content,
+ );
+
+ my %email_param = (
+ 'from' => $conf->config('invoice_from'),
+ 'to' => $user_email,
+ 'subject' => $self->searchname,
+ 'nobody' => 1,
+ 'mimeparts' => [ $part ],
+ );
+
+ $log->debug('Sending to '.$user_email);
+ $error = send_email(%email_param);
+
+ # update the timestamp
+ $self->set('last_sent', time);
+ $error ||= $self->replace;
+ if ($error) {
+ $log->error($error);
+ return $error;
+ }
+
+}
+
+sub queueable_send {
+ my $searchnum = shift;
+ my $self = FS::saved_search->by_key($searchnum)
+ or die "searchnum $searchnum not found\n";
+ $self->send;
+}
+
=back
=head1 SEE ALSO
diff --git a/FS/FS/saved_search_option.pm b/FS/FS/saved_search_option.pm
deleted file mode 100644
index f349af393..000000000
--- a/FS/FS/saved_search_option.pm
+++ /dev/null
@@ -1,124 +0,0 @@
-package FS::saved_search_option;
-use base qw( FS::Record );
-
-use strict;
-use FS::Record qw( qsearch qsearchs );
-
-=head1 NAME
-
-FS::saved_search_option - Object methods for saved_search_option records
-
-=head1 SYNOPSIS
-
- use FS::saved_search_option;
-
- $record = new FS::saved_search_option \%hash;
- $record = new FS::saved_search_option { 'column' => 'value' };
-
- $error = $record->insert;
-
- $error = $new_record->replace($old_record);
-
- $error = $record->delete;
-
- $error = $record->check;
-
-=head1 DESCRIPTION
-
-An FS::saved_search_option object represents a CGI parameter for a report
-saved in L<FS::saved_search>. FS::saved_search_option inherits from
-FS::Record. The following fields are currently supported:
-
-=over 4
-
-=item optionnum
-
-primary key
-
-=item searchnum
-
-searchnum
-
-=item optionname
-
-optionname
-
-=item optionvalue
-
-optionvalue
-
-
-=back
-
-=head1 METHODS
-
-=over 4
-
-=item new HASHREF
-
-Creates a new parameter. To add the record to the database, see
-L<"insert">.
-
-Note that this stores the hash reference, not a distinct copy of the hash it
-points to. You can ask the object for a copy with the I<hash> method.
-
-=cut
-
-# the new method can be inherited from FS::Record, if a table method is defined
-
-sub table { 'saved_search_option'; }
-
-=item insert
-
-Adds this record to the database. If there is an error, returns the error,
-otherwise returns false.
-
-=item delete
-
-Delete this record from the database.
-
-=item replace OLD_RECORD
-
-Replaces the OLD_RECORD with this one in the database. If there is an error,
-returns the error, otherwise returns false.
-
-=item check
-
-Checks all fields to make sure this is a valid example. If there is
-an error, returns the error, otherwise returns false. Called by the insert
-and replace methods.
-
-=cut
-
-# the check method should currently be supplied - FS::Record contains some
-# data checking routines
-
-sub check {
- my $self = shift;
-
-# unpack these from the format used by CGI
- my $optionvalue = $self->optionvalue;
- $optionvalue =~ s/\0/\n/g;
-
- my $error =
- $self->ut_numbern('optionnum')
- || $self->ut_number('searchnum')
-# || $self->ut_foreign_key('searchnum', 'saved_search', 'searchnum')
- || $self->ut_text('optionname')
- || $self->ut_textn('optionvalue')
- ;
- return $error if $error;
-
- $self->SUPER::check;
-}
-
-=back
-
-=head1 SEE ALSO
-
-L<FS::Record>
-
-=cut
-
-1;
-
diff --git a/FS/MANIFEST b/FS/MANIFEST
index d06f2637f..73a740f63 100644
--- a/FS/MANIFEST
+++ b/FS/MANIFEST
@@ -876,5 +876,3 @@ FS/commission_rate.pm
t/commission_rate.t
FS/saved_search.pm
t/saved_search.t
-FS/saved_search_option.pm
-t/saved_search_option.t
diff --git a/FS/bin/freeside-daily b/FS/bin/freeside-daily
index 6a2daf934..03d235061 100755
--- a/FS/bin/freeside-daily
+++ b/FS/bin/freeside-daily
@@ -79,6 +79,10 @@ pay_batch_receive(%opt);
use FS::Cron::export_batch qw(export_batch_submit);
export_batch_submit(%opt);
+#does nothing unless there are users with subscribed searches
+use FS::Cron::send_subscribed qw(send_subscribed);
+send_subscribed(%opt);
+
#clears out cacti imports & deletes select database cache files
use FS::Cron::cleanup qw( cleanup cleanup_before_backup );
cleanup_before_backup();
diff --git a/FS/t/saved_search_option.t b/FS/t/saved_search_option.t
deleted file mode 100644
index f30bfb806..000000000
--- a/FS/t/saved_search_option.t
+++ /dev/null
@@ -1,5 +0,0 @@
-BEGIN { $| = 1; print "1..1\n" }
-END {print "not ok 1\n" unless $loaded;}
-use FS::saved_search_option;
-$loaded=1;
-print "ok 1\n";
diff --git a/httemplate/browse/saved_search.html b/httemplate/browse/saved_search.html
new file mode 100644
index 000000000..d2efa6ed9
--- /dev/null
+++ b/httemplate/browse/saved_search.html
@@ -0,0 +1,81 @@
+<& elements/browse.html,
+ 'title' => 'My saved searches',
+ 'name' => 'saved searches',
+ 'query' => { 'table' => 'saved_search',
+ 'hashref' => { usernum => $curuser->usernum },
+ },
+ 'count_query' => $count_query,
+ 'header' => [ '#',
+ 'Name',
+ 'Subscription',
+ 'Last sent',
+ 'Format',
+ 'Path',
+ 'Parameters',
+ ],
+ 'sort_fields' => [ 'searchnum',
+ 'searchname',
+ 'freq',
+ 'last_sent',
+ 'format',
+ "path || '?' || 'params'",
+ '',
+ ],
+ 'fields' => [ 'searchnum',
+ 'searchname',
+ 'freq',
+ sub { my $date = shift->get('last_sent');
+ $date ? time2str('%b %o, %Y', $date) : '';
+ },
+ sub { $format_label{ shift->get('format') }
+ },
+ 'path',
+ sub { join('<BR>',
+ sort
+ map { encode_entities(uri_unescape($_)) }
+ split(/[;&]/, shift->get('params') )
+ )
+ },
+ ],
+ 'size' => [ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '-1',
+ ],
+ 'links' => [ '', '' ],
+ 'link_onclicks' => [ '', $edit_popup ],
+# 'disableable' => 1, # currrently unused
+# 'disabled_statuspos' => 2,
+ 'really_disable_download' => 1
+&>
+<%init>
+
+my $curuser = $FS::CurrentUser::CurrentUser;
+
+my $query = {
+ 'table' => 'saved_search',
+ 'hashref' => { 'usernum' => $curuser->usernum },
+};
+my $count_query = "SELECT COUNT(*) FROM saved_search WHERE usernum = ".
+ $curuser->usernum;
+
+my %format_label = (
+ 'html' => 'webpage',
+ 'csv' => 'CSV',
+ 'xls' => 'spreadsheet',
+);
+
+my $edit_popup = sub {
+ my $searchnum = shift->searchnum;
+ include('/elements/popup_link_onclick.html',
+ 'action' => $fsurl.'/edit/saved_search.html?'.$searchnum,
+ 'actionlabel' => 'Save this search',
+ 'width' => 650,
+ 'height' => 500,
+ );
+};
+
+</%init>
diff --git a/httemplate/edit/process/saved_search.html b/httemplate/edit/process/saved_search.html
new file mode 100644
index 000000000..7ae7e0d78
--- /dev/null
+++ b/httemplate/edit/process/saved_search.html
@@ -0,0 +1,15 @@
+<& elements/process.html,
+ 'table' => 'saved_search',
+ 'popup_reload' => 'Saving',
+ 'post_new_object_callback' => $callback,
+&>
+<%init>
+
+my $callback = sub {
+ my ($cgi, $obj) = @_;
+ $obj->usernum( $FS::CurrentUser::CurrentUser->usernum );
+ # if this would change it from its existing owner, replace_check
+ # will refuse
+};
+
+</%init>
diff --git a/httemplate/edit/saved_search.html b/httemplate/edit/saved_search.html
new file mode 100644
index 000000000..cb6aa45d1
--- /dev/null
+++ b/httemplate/edit/saved_search.html
@@ -0,0 +1,113 @@
+<& elements/edit.html,
+ 'name' => 'saved search',
+ 'table' => 'saved_search',
+ 'popup' => 1,
+ 'fields' => [
+ { field => 'searchname',
+ type => 'text',
+ size => 40,
+ },
+ { field => 'freq',
+ type => 'select',
+ options => [ '', 'daily', 'weekly', 'monthly' ],
+ labels => { '' => 'no' },
+ },
+ { field => 'emailaddress',
+ type => 'fixed',
+ curr_value_callback => sub {
+ $curuser->option('email_address')
+ || 'no email address configured'
+ },
+ },
+ { field => 'last_sent',
+ type => 'fixed-date',
+ },
+ { field => 'format',
+ type => 'hidden', # revisit this later
+# type => 'select',
+# options => [ 'html', 'xls', 'csv' ],
+# labels => {
+# 'html' => 'webpage',
+# 'xls' => 'spreadsheet',
+# 'csv' => 'CSV',
+# },
+ },
+ { field => 'disabled', # currently unused
+ type => 'hidden',
+ },
+ { type => 'tablebreak-tr-title' },
+ { field => 'path',
+ type => 'fixed',
+ cell_style => 'font-size: small',
+ },
+ { field => 'params',
+ type => 'fixed',
+ cell_style => 'font-size: small',
+ },
+ ],
+ 'labels' => {
+ 'searchnum' => 'Saved search',
+ 'searchname' => 'Name this search',
+ 'path' => 'Search page',
+ 'params' => 'Parameters',
+ 'freq' => 'Subscribe by email',
+ 'last_sent' => 'Last sent on',
+ 'emailaddress' => 'Will be sent to',
+ 'format' => 'Report format',
+ },
+ 'new_object_callback' => $new_object,
+ 'delete_url' => $fsurl.'misc/delete-saved_search.html',
+&>
+<%init>
+
+my $curuser = $FS::CurrentUser::CurrentUser;
+# remember the user's rooturl() when accessing the UI. this will be the
+# base URL for sending email reports to that user so that links work.
+my $rooturl_pref = qsearchs('access_user_pref', {
+ usernum => $curuser->usernum,
+ prefname => 'rooturl',
+});
+my $error;
+if ($rooturl_pref) {
+ if ($rooturl_pref->prefvalue ne rooturl()) {
+ $rooturl_pref->set('prefvalue', rooturl());
+ $error = $rooturl_pref->replace;
+ } # else don't update it
+} else {
+ $rooturl_pref = FS::access_user_pref->new({
+ usernum => $curuser->usernum,
+ prefname => 'rooturl',
+ prefvalue => rooturl(),
+ });
+ $error = $rooturl_pref->insert;
+}
+
+warn "error updating rooturl pref: $error" if $error;
+
+# prefix to the freeside document root (usually '/freeside/')
+my $root = URI->new($fsurl)->path;
+
+# alternatively, could do all this on the client using window.top.location
+my $new_object = sub {
+ my $cgi = shift;
+ my $hashref = shift;
+ my $fields = shift;
+ for (grep { $_->{field} eq 'last_sent' } @$fields) {
+ $_->{type} = 'hidden';
+ }
+ my $url = $r->header_in('Referer')
+ or die "no referring page found";
+ $url = URI->new($url);
+ my $path = $url->path;
+ $path =~ s/^$root//; # path should not have a leading slash
+ my $title = $cgi->param('title');
+ return FS::saved_search->new({
+ 'usernum' => $curuser->usernum,
+ 'path' => $path,
+ 'params' => $url->query,
+ 'format' => 'html',
+ 'searchname' => $title,
+ });
+};
+
+</%init>
diff --git a/httemplate/elements/header-popup.html b/httemplate/elements/header-popup.html
index 839a63676..327673bc6 100644
--- a/httemplate/elements/header-popup.html
+++ b/httemplate/elements/header-popup.html
@@ -38,7 +38,13 @@ Example:
<% $head |n %>
</HEAD>
<BODY <% $etc |n %>>
+% if ($m->notes('inline_stylesheet')) { # for email delivery
+ <style type="text/css">
+ <& /elements/freeside.css &>
+ </style>
+% } else {
<link href="<%$fsurl%>elements/freeside.css" type="text/css" rel="stylesheet">
+% }
% if ( $title || $title_noescape ) {
<FONT SIZE=6>
<CENTER><% encode_entities($title) || $title_noescape |n %></CENTER>
diff --git a/httemplate/elements/menu.html b/httemplate/elements/menu.html
index fcfc9fb74..d6ea06891 100644
--- a/httemplate/elements/menu.html
+++ b/httemplate/elements/menu.html
@@ -87,6 +87,21 @@ my $mobile = $opt{'mobile'} || 0;
my $curuser = $FS::CurrentUser::CurrentUser;
+# saved searches
+tie my %report_saved_searches, 'Tie::IxHash';
+if ( my @searches = grep { $_->disabled eq '' } $curuser->saved_search ) {
+ foreach my $search (@searches) {
+ $report_saved_searches{ $search->searchname } = [
+ # don't use query_string here; we don't want to override the format
+ $fsurl . $search->path . '?' . $search->params , ''
+ ];
+ }
+ $report_saved_searches{'separator'} = '';
+ $report_saved_searches{'My saved searches'} =
+ [ $fsurl. 'browse/saved_search.html',
+ 'Manage saved searches and subscriptions' ];
+}
+
#XXX Active tickets not assigned to a customer
tie my %report_prospects, 'Tie::IxHash';
@@ -419,6 +434,8 @@ $report_logs{'Outgoing messages'} = [ $fsurl.'search/cust_msg.html', 'View outgo
|| $curuser->access_right('Configuration');
tie my %report_menu, 'Tie::IxHash';
+$report_menu{'Saved searches'} = [ \%report_saved_searches, 'My saved searches' ]
+ if keys(%report_saved_searches);
$report_menu{'Prospects'} = [ \%report_prospects, 'Prospect reports' ]
if $curuser->access_right('List prospects')
|| $curuser->access_right('List contacts');
diff --git a/httemplate/elements/tr-fixed-date.html b/httemplate/elements/tr-fixed-date.html
index ef599796d..731a3caa7 100644
--- a/httemplate/elements/tr-fixed-date.html
+++ b/httemplate/elements/tr-fixed-date.html
@@ -14,6 +14,6 @@ my $value = $opt{'curr_value'} || $opt{'value'};
my $conf = new FS::Conf;
my $date_format = $opt{'format'} || $conf->config('date_format') || '%m/%d/%Y';
-$opt{'formatted_value'} = time2str($date_format, $value);
+$opt{'formatted_value'} = $value > 0 ? time2str($date_format, $value) : '';
</%init>
diff --git a/httemplate/misc/delete-saved_search.html b/httemplate/misc/delete-saved_search.html
new file mode 100644
index 000000000..34567ec1c
--- /dev/null
+++ b/httemplate/misc/delete-saved_search.html
@@ -0,0 +1,25 @@
+% if ( $error ) {
+<& /elements/errorpage-popup.html, $error &>
+% } else {
+<& /elements/header-popup.html, 'Saved search deleted' &>
+ <script type="text/javascript">
+ topreload();
+ </script>
+</body>
+</html>
+% }
+<%init>
+
+my $curuser = $FS::CurrentUser::CurrentUser;
+
+my($query) = $cgi->keywords;
+$query =~ /^(\d+)$/ || die "Illegal searchnum";
+my $searchnum = $1;
+
+my $search = qsearchs('saved_search', {
+ 'searchnum' => $searchnum,
+ 'usernum' => $curuser->usernum,
+});
+my $error = $search->delete;
+
+</%init>
diff --git a/httemplate/search/elements/search-html.html b/httemplate/search/elements/search-html.html
index 12f6c1e04..3ea38aee8 100644
--- a/httemplate/search/elements/search-html.html
+++ b/httemplate/search/elements/search-html.html
@@ -136,22 +136,36 @@
<TD ALIGN="right" CLASS="noprint">
- <% $opt{'download_label'} || 'Download full results' %><BR>
+ <% $opt{'download_label'} || 'Download results:' %>
% $cgi->param('_type', "$xlsname.xls" );
- as <A HREF="<% "$self_url?". $cgi->query_string %>">Excel spreadsheet</A><BR>
+ <A HREF="<% "$self_url?". $cgi->query_string %>">Spreadsheet</A>&nbsp;|&nbsp;
% $cgi->param('_type', 'csv');
- as <A HREF="<% "$self_url?". $cgi->query_string %>">CSV file</A><BR>
+ <A HREF="<% "$self_url?". $cgi->query_string %>">CSV</A>&nbsp;|&nbsp;
% if ( defined($opt{xml_elements}) ) {
% $cgi->param('_type', 'xml');
- as <A HREF="<% "$self_url?". $cgi->query_string %>">XML file</A><BR>
+ <A HREF="<% "$self_url?". $cgi->query_string %>">XML</A>&nbsp;|&nbsp;
% }
% $cgi->param('_type', 'html-print');
- as <A HREF="<% "$self_url?". $cgi->query_string %>">printable copy</A>
+ <A HREF="<% "$self_url?". $cgi->query_string %>">webpage</A>
+%# "save search" -- for now, obey disable_download and the 'Download
+%# report data' ACL, because saving a search allows the user to receive
+%# copies of the data.
+ <BR>
+%# XXX should do a check here on whether the user already has this
+%# search saved...
+ <& /elements/popup_link.html,
+ 'action' => $fsurl.'/edit/saved_search.html?title='.
+ uri_escape($opt{title}),
+ 'label' => 'Save this search',
+ 'actionlabel' => 'Save this search',
+ 'width' => 650,
+ 'height' => 500,
+ &>
</TD>
% $cgi->param('_type', "html" );
% }
diff --git a/httemplate/search/elements/search.html b/httemplate/search/elements/search.html
index b6ee7b373..0f71218c2 100644
--- a/httemplate/search/elements/search.html
+++ b/httemplate/search/elements/search.html
@@ -179,6 +179,7 @@ Example:
&>
</%doc>
+% # if changing this, also update saved search behavior to match!
% if ( $type eq 'csv' ) {
%
<% include('search-csv.html', header=>$header, rows=>$rows, opt=>\%opt ) %>