sub http_header {
my ( $header, $value ) = @_;
- if (exists $ENV{MOD_PERL}) {
- if ( defined $HTML::Mason::Commands::r ) { #Mason
- ## is this the correct pacakge for $r ??? for 1.0x and 1.1x ?
- if ( $header =~ /^Content-Type$/ ) {
- $HTML::Mason::Commands::r->content_type($value);
- } else {
- $HTML::Mason::Commands::r->header_out( $header => $value );
- }
+ if ( defined $HTML::Mason::Commands::r ) { #Mason + apache
+ if ( $header =~ /^Content-Type$/ ) {
+ $HTML::Mason::Commands::r->content_type($value);
} else {
- die "http_header called in unknown environment";
+ $HTML::Mason::Commands::r->header_out( $header => $value );
}
+ } elsif ( defined $HTML::Mason::Commands::m ) {
+ $HTML::Mason::Commands::m->notes(lc("header-$header"), $value);
} else {
- die "http_header called not running under mod_perl";
+ warn "http_header($header, $value) called with no way to set headers\n";
}
-
}
=item menubar ITEM, URL, ...
--- /dev/null
+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;
use FS::olt_site;
use FS::access_user_page_pref;
use FS::part_svc_msgcat;
+ use FS::saved_search;
# Sammath Naur
if ( $FS::Mason::addl_handler_use ) {
],
},
+ 'saved_search' => {
+ 'columns' => [
+ 'searchnum', 'serial', '', '', '', '',
+ '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', '', '', '',
+ 'format', 'varchar', 'NULL', 32, '', '',
+ ],
+ 'primary_key' => 'searchnum',
+ 'unique' => [],
+ 'index' => [],
+ 'foreign_keys' => [
+ { columns => [ 'usernum' ],
+ table => 'access_user',
+ },
+ ],
+ },
+
# name type nullability length default local
#'new_table' => {
return $error;
}
+#3.x
+
+sub saved_search {
+ my $self = shift;
+ qsearch('saved_search', { 'usernum' => $self->usernum });
+}
+
=back
=head1 BUGS
--- /dev/null
+package FS::cdr::callplus;
+use base qw( FS::cdr );
+
+use strict;
+use vars qw( %info );
+use FS::Record qw( qsearchs );
+use Time::Local 'timelocal';
+
+# Date format in the Date/Time col: "13/07/2016 2:40:32 p.m."
+# d/m/y H:M:S, leading zeroes stripped, 12-hour with "a.m." or "p.m.".
+# There are also separate d/m/y and 24-hour time columns, but parsing
+# those separately is hard (DST issues).
+
+%info = (
+ 'name' => 'CallPlus',
+ 'weight' => 610,
+ 'header' => 1,
+ 'type' => 'csv',
+ 'import_fields' => [
+ 'uniqueid', # ID
+ '', # Billing Group (charged_party?)
+ 'src', # Origin Number
+ 'dst', # Destination Number
+ '', # Description (seems to be dest caller id?)
+ '', # Status
+ '', # Terminated
+ '', # Date
+ '', # Time
+ sub { # Date/Time
+ # this format overlaps one of the existing parser cases, so give it
+ # its own special parser
+ my ($cdr, $value) = @_;
+ $value =~ m[^(\d{1,2})/(\d{1,2})/(\d{4}) (\d{1,2}):(\d{2}):(\d{2}) (a\.m\.|p\.m\.)$]
+ or die "unparseable date: $value";
+ my ($day, $mon, $year, $hour, $min, $sec) = ( $1, $2, $3, $4, $5, $6 );
+ $hour = $hour % 12;
+ if ($7 eq 'p.m.') {
+ $hour = 12;
+ }
+ $cdr->set('startdate',
+ timelocal($sec, $min, $hour, $day, $mon-1, $year)
+ );
+ },
+ sub { # Call Length (seconds)
+ my ($cdr, $value) = @_;
+ $cdr->set('duration', $value);
+ $cdr->set('billsec', $value);
+ },
+ sub { # Call Cost (NZD)
+ my ($cdr,$value) = @_;
+ $value =~ s/^\$//;
+ $cdr->upstream_price($value);
+ },
+ skip(4), # Smartcode, Smartcode Description, Type, SubType
+ ],
+);
+
+sub skip { map {''} (1..$_[0]) }
+
+1;
FS::cust_main::Billing_Realtime::realtime_verify_bop
FS::part_pkg
FS::Misc::Geo::standardize_uscensus
+ FS::saved_search::send
+ FS::saved_search::render
Cron::bill
Cron::upload
spool_upload
--- /dev/null
+package FS::saved_search;
+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;
+
+=head1 NAME
+
+FS::saved_search - Object methods for saved_search records
+
+=head1 SYNOPSIS
+
+ use FS::saved_search;
+
+ $record = new FS::saved_search \%hash;
+ $record = new FS::saved_search { 'column' => 'value' };
+
+ $error = $record->insert;
+
+ $error = $new_record->replace($old_record);
+
+ $error = $record->delete;
+
+ $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::saved_search object represents a search (a page in the backoffice
+UI, typically under search/ or browse/) which a user has saved for future
+use or periodic email delivery.
+
+FS::saved_search inherits from FS::Record. The following fields are
+currently supported:
+
+=over 4
+
+=item searchnum
+
+primary key
+
+=item usernum
+
+usernum of the L<FS::access_user> that created the search. Currently, email
+reports will only be sent to this user.
+
+=item searchname
+
+A descriptive name.
+
+=item path
+
+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.
+
+=item freq
+
+A frequency for email delivery of this report: daily, weekly, or
+monthly, or null to disable it.
+
+=item last_sent
+
+The timestamp of the last time this report was sent.
+
+=item format
+
+'html', 'xls', or 'csv'. Not all reports support all of these.
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Creates a new saved search. To add it 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
+
+sub table { 'saved_search'; }
+
+=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.
+
+=cut
+
+# the replace method can be inherited from FS::Record
+
+=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;
+
+ my $error =
+ $self->ut_numbern('searchnum')
+ || $self->ut_number('usernum')
+ #|| $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')
+ || $self->ut_enum('format', [ '', 'html', 'csv', 'xls' ])
+ ;
+ return $error if $error;
+
+ $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
+periodic email sending, returns undef. If it is set up but has never been
+sent before, returns zero.
+
+=cut
+
+sub next_send_date {
+ my $self = shift;
+ my $freq = $self->freq or return undef;
+ return 0 unless $self->last_sent;
+ my $dt = DateTime->from_epoch(epoch => $self->last_sent);
+ $dt->truncate(to => 'day');
+ if ($freq eq 'daily') {
+ $dt->add(days => 1);
+ } elsif ($freq eq 'weekly') {
+ $dt->add(weeks => 1);
+ } elsif ($freq eq 'monthly') {
+ $dt->add(months => 1);
+ }
+ $dt->epoch;
+}
+
+=item query_string
+
+Returns the CGI query string for the parameters to this report.
+
+=cut
+
+sub query_string {
+ my $self = shift;
+
+ my $type = $self->format;
+ $type = 'html-print' if $type eq '' || $type eq 'html';
+ $type = '.xls' if $type eq 'xls';
+ my $query = "_type=$type";
+ $query .= ';' . $self->params if $self->params;
+ $query;
+}
+
+=item render
+
+Returns the report content as an HTML or Excel file.
+
+=cut
+
+sub render {
+ my $self = shift;
+ my $log = FS::Log->new('FS::saved_search::render');
+ my $outbuf;
+
+ # delayed loading
+ load_class('FS::Mason');
+ RT::LoadConfig();
+ RT::Init();
+
+ # do this before setting QUERY_STRING/FSURL
+ my ($fs_interp) = FS::Mason::mason_interps('standalone',
+ outbuf => \$outbuf
+ );
+ $fs_interp->error_mode('fatal');
+ $fs_interp->error_format('text');
+
+ local $FS::CurrentUser::CurrentUser = $self->access_user;
+ local $FS::Mason::Request::QUERY_STRING = $self->query_string;
+ local $FS::Mason::Request::FSURL = $self->access_user->option('rooturl');
+
+ my $mason_request = $fs_interp->make_request(comp => '/' . $self->path);
+ $mason_request->notes('inline_stylesheet', 1);
+
+ local $@;
+ eval { $mason_request->exec(); };
+ if ($@) {
+ my $error = $@;
+ if ( ref($error) eq 'HTML::Mason::Exception' ) {
+ $error = $error->message;
+ }
+
+ $log->error("Error rendering " . $self->path .
+ " for " . $self->access_user->username .
+ ":\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>
+ <p>' . $self->path . '?' . $self->query_string . '</p>
+ <p>' . $_ . '</p>';
+ }
+
+ my %mime = (
+ Data => $outbuf,
+ Type => $mason_request->notes('header-content-type')
+ || 'text/html',
+ Disposition => 'inline',
+ );
+ if (my $disp = $mason_request->notes('header-content-disposition') ) {
+ $disp =~ /^(attachment|inline)\s*;\s*filename=(.*)$/;
+ $mime{Disposition} = $1;
+ my $filename = $2;
+ $filename =~ s/^"(.*)"$/$1/;
+ $mime{Filename} = $filename;
+ }
+ if ($mime{Type} =~ /^text/) {
+ $mime{Encoding} = 'quoted-printable';
+ } else {
+ $mime{Encoding} = 'base64';
+ }
+ return MIME::Entity->build(%mime);
+}
+
+=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 $part = $self->render;
+
+ 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;
+}
+
+#3.x
+sub access_user {
+ my $self = shift;
+ qsearchs('access_user', { 'usernum' => $self->usernum });
+}
+
+=back
+
+=head1 SEE ALSO
+
+L<FS::Record>
+
+=cut
+
+1;
+
t/webservice_log.t
FS/access_user_page_pref.pm
t/access_user_page_pref.t
+FS/saved_search.pm
+t/saved_search.t
use FS::Cron::agent_email qw(agent_email);
agent_email(%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();
--- /dev/null
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::saved_search;
+$loaded=1;
+print "ok 1\n";
--- /dev/null
+<& 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>
#return an error string or empty for no error
'precheck_callback' => sub { my( $cgi ) = @_; },
+ #after the new object is created
+ 'post_new_object_callback' => sub { my( $cgi, $object ) = @_; },
+
#after everything's inserted
'noerror_callback' => sub { my( $cgi, $object ) = @_; },
}
}
+ if ( $opt{'post_new_object_callback'} ) {
+ &{ $opt{'post_new_object_callback'} }( $cgi, $new );
+ }
+
if ( $opt{'agent_virt'} ) {
if ( ! $new->agentnum
--- /dev/null
+<& 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>
--- /dev/null
+<& 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 => '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>
<% $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>
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';
|| $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');
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>
--- /dev/null
+% 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>
<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> |
% $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> |
% 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> |
% }
% $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" );
% }
#http://support.microsoft.com/kb/812935
#http://support.microsoft.com/kb/323308
-$HTML::Mason::Commands::r->headers_out->{'Cache-control'} = 'max-age=0';
+http_header('Cache-control' => 'max-age=0');
my $data = '';
my $XLS = new IO::Scalar \$data;
&>
</%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 ) %>