Bug 16219: (follow-up) Nicer message if params missing and note that they are not...
[koha.git] / misc / cronjobs / runreport.pl
1 #!/usr/bin/perl
2 #
3 # Copyright 2008 Liblime
4 # Copyright 2014 Foundations Bible College, Inc.
5 #
6 # This file is part of Koha.
7 #
8 # Koha is free software; you can redistribute it and/or modify it
9 # under the terms of the GNU General Public License as published by
10 # the Free Software Foundation; either version 3 of the License, or
11 # (at your option) any later version.
12 #
13 # Koha is distributed in the hope that it will be useful, but
14 # WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 # GNU General Public License for more details.
17 #
18 # You should have received a copy of the GNU General Public License
19 # along with Koha; if not, see <http://www.gnu.org/licenses>.
20
21 use Modern::Perl;
22
23 use Koha::Script -cron;
24 use C4::Reports::Guided; # 0.12
25 use Koha::Reports;
26 use C4::Context;
27 use C4::Log;
28 use Koha::Email;
29 use Koha::DateUtils;
30
31 use Getopt::Long qw(:config auto_help auto_version);
32 use Pod::Usage;
33 use MIME::Lite;
34 use Text::CSV::Encoded;
35 use CGI qw ( -utf8 );
36 use Carp;
37 use Encode;
38 use JSON qw( to_json );
39
40 BEGIN {
41     # find Koha's Perl modules
42     # test carefully before changing this
43     use FindBin;
44     eval { require "$FindBin::Bin/../kohalib.pl" };
45 }
46
47 =head1 NAME
48
49 runreport.pl - Run pre-existing saved reports
50
51 =head1 SYNOPSIS
52
53 runreport.pl [ -h | -m ] [ -v ] reportID [ reportID ... ]
54
55  Options:
56    -h --help       brief help message
57    -m --man        full documentation, same as --help --verbose
58    -v --verbose    verbose output
59
60    --format=s      selects format. Choice of text, html, csv or tsv
61
62    -e --email      whether to use e-mail (implied by --to or --from)
63    -a --attachment additionally attach the report as a file. cannot be used with html format
64    --username      username to pass to the SMTP server for authentication
65    --password      password to pass to the SMTP server for authentication
66    --method        method is the type of authentication. Ie. LOGIN, DIGEST-MD5, etc.
67    --to=s          e-mail address to send report to
68    --from=s        e-mail address to send report from
69    --subject=s     subject for the e-mail
70    --param=s      parameters for the report
71    --store-results store the result of the report
72    --csv-header    add column names as first line of csv output
73
74
75  Arguments:
76    reportID        report ID Number from saved_sql.id, multiple ID's may be specified
77
78 =head1 OPTIONS
79
80 =over
81
82 =item B<--help>
83
84 Print a brief help message and exits.
85
86 =item B<--man>
87
88 Prints the manual page and exits.
89
90 =item B<-v>
91
92 Verbose. Without this flag set, only fatal errors are reported.
93
94 =item B<--format>
95
96 Current options are text, html, csv, and tsv. At the moment, text and tsv both produce tab-separated tab-separated output.
97
98 =item B<--email>
99
100 Whether to use e-mail (implied by --to or --from).
101
102 =item B<--username>
103
104 Username to pass to the SMTP server for authentication
105
106 =item B<--password>
107
108 Password to pass to the SMTP server for authentication
109
110 =item B<--method>
111
112 Method is the type of authentication. Ie. LOGIN, DIGEST-MD5, etc.
113
114 =item B<--to>
115
116 E-mail address to send report to. Defaults to KohaAdminEmailAddress.
117
118 =item B<--from>
119
120 E-mail address to send report from. Defaults to KohaAdminEmailAddress.
121
122 =item B<--subject>
123
124 Subject for the e-mail message. Defaults to "Koha Saved Report"
125
126 =item B<--param>
127
128 Repeatable, should provide one param per param requested for the report.
129 Report params are not combined as on the staff side, so you may need to repeat
130 params.
131
132 =item B<--store-results>
133
134 Store the result of the report into the saved_reports DB table.
135
136 To access the results, go on Reports > Guided reports > Saved report.
137
138 =back
139
140 =head1 DESCRIPTION
141
142 This script is designed to run existing Saved Reports.
143
144 =head1 USAGE EXAMPLES
145
146 B<runreport.pl 16>
147
148 In the most basic form, runs the report specified by ID number from 
149 saved_sql.id, in this case #16, outputting the results to STDOUT.  
150
151 B<runreport.pl 16 17>
152
153 Same as above, but also runs report #17. 
154
155 =head1 TO DO
156
157 =over
158
159
160 =item *
161
162 Allow Saved Results option.
163
164
165 =back
166
167 =head1 SEE ALSO
168
169 Reports - Guided Reports
170
171 =cut
172
173 # These variables can be set by command line options,
174 # initially set to default values.
175
176 my $help    = 0;
177 my $man     = 0;
178 my $verbose = 0;
179 my $email   = 0;
180 my $attachment = 0;
181 my $format  = "text";
182 my $to      = "";
183 my $from    = "";
184 my $subject = "";
185 my @params = ();
186 my $separator = ',';
187 my $quote = '"';
188 my $store_results = 0;
189 my $csv_header = 0;
190
191 my $username = undef;
192 my $password = undef;
193 my $method = 'LOGIN';
194
195 GetOptions(
196     'help|?'            => \$help,
197     'man'               => \$man,
198     'verbose'           => \$verbose,
199     'format=s'          => \$format,
200     'to=s'              => \$to,
201     'from=s'            => \$from,
202     'subject=s'         => \$subject,
203     'param=s'           => \@params,
204     'email'             => \$email,
205     'a|attachment'      => \$attachment,
206     'username:s'        => \$username,
207     'password:s'        => \$password,
208     'method:s'          => \$method,
209     'store-results'     => \$store_results,
210     'csv-header'        => \$csv_header,
211
212 ) or pod2usage(2);
213 pod2usage( -verbose => 2 ) if ($man);
214 pod2usage( -verbose => 2 ) if ($help and $verbose);
215 pod2usage(1) if $help;
216
217 cronlogaction();
218
219 unless ($format) {
220     $verbose and print STDERR "No format specified, assuming 'text'\n";
221     $format = 'text';
222 }
223
224 if ($format eq 'tsv' || $format eq 'text') {
225     $format = 'csv';
226     $separator = "\t";
227 }
228
229 if ($to or $from or $email) {
230     $email = 1;
231     $from or $from = C4::Context->preference('KohaAdminEmailAddress');
232     $to   or $to   = C4::Context->preference('KohaAdminEmailAddress');
233 }
234
235 unless (scalar(@ARGV)) {
236     print STDERR "ERROR: No reportID(s) specified\n";
237     pod2usage(1);
238 }
239 ($verbose) and print scalar(@ARGV), " argument(s) after options: " . join(" ", @ARGV) . "\n";
240
241 my $today = dt_from_string();
242 my $date = $today->ymd();
243
244 foreach my $report_id (@ARGV) {
245     my $report = Koha::Reports->find( $report_id );
246     unless ($report) {
247         warn "ERROR: No saved report $report_id found";
248         next;
249     }
250     my $sql         = $report->savedsql;
251     my $report_name = $report->report_name;
252     my $type        = $report->type;
253
254     $verbose and print "SQL: $sql\n\n";
255     if ( $subject eq "" )
256     {
257         if ( defined($report_name) and $report_name ne "")
258         {
259             $subject = $report_name ;
260         }
261         else
262         {
263             $subject = 'Koha Saved Report';
264         }
265     }
266
267     # convert SQL parameters to placeholders
268     my $params_needed = ( $sql =~ s/(<<.*?>>)/\?/g );
269     die("You supplied ". scalar @params . " parameter(s) and $params_needed are required by the report") if scalar @params != $params_needed;
270
271     my ($sth) = execute_query( $sql, undef, undef, \@params, $report_id );
272     my $count = scalar($sth->rows);
273     unless ($count) {
274         print "NO OUTPUT: 0 results from execute_query\n";
275         next;
276     }
277     $verbose and print "$count results from execute_query\n";
278
279     my $message;
280     my @rows_to_store;
281     if ($format eq 'html') {
282         my $cgi = CGI->new();
283         my @rows;
284         while (my $line = $sth->fetchrow_arrayref) {
285             foreach (@$line) { defined($_) or $_ = ''; }    # catch undef values, replace w/ ''
286             push @rows, $cgi->TR( join('', $cgi->td($line)) ) . "\n";
287             push @rows_to_store, [@$line] if $store_results;
288         }
289         $message = $cgi->table(join "", @rows);
290     } elsif ($format eq 'csv') {
291         my $csv = Text::CSV::Encoded->new({
292             encoding_out => 'utf8',
293             binary      => 1,
294             quote_char  => $quote,
295             sep_char    => $separator,
296             });
297
298         if ( $csv_header ) {
299             my @fields = map { decode( 'utf8', $_ ) } @{ $sth->{NAME} };
300             $csv->combine( @fields );
301             $message .= $csv->string() . "\n";
302             push @rows_to_store, [@fields] if $store_results;
303         }
304
305         while (my $line = $sth->fetchrow_arrayref) {
306             $csv->combine(@$line);
307             $message .= $csv->string() . "\n";
308             push @rows_to_store, [@$line] if $store_results;
309         }
310     }
311     if ( $store_results ) {
312         my $json = to_json( \@rows_to_store );
313         C4::Reports::Guided::store_results( $report_id, $json );
314     }
315     if ($email) {
316         my $args = { to => $to, from => $from, subject => $subject };
317         if ( $format eq 'html' ) {
318             $message = "<html><head><style>tr:nth-child(2n+1) { background-color: #ccc;}</style></head><body>$message</body></html>";
319             $args->{contenttype} = 'text/html';
320         }
321         my $email = Koha::Email->new();
322         my %mail  = $email->create_message_headers($args);
323         $mail{Data} = $message;
324         $mail{Auth} = { user => $username, pass => $password, method => $method } if $username;
325
326         my $msg = MIME::Lite->new(%mail);
327
328         $msg->attach(
329             Type        => "text/$format",
330             Data        => encode( 'utf8', $message ),
331             Filename    => "report$report_id-$date.$format",
332             Disposition => 'attachment',
333         ) if $attachment;
334
335         $msg->send();
336         carp "Mail not sent" unless $msg->last_send_successful();
337     }
338     else {
339         print $message;
340     }
341 }