Merge commit 'kc/master'
[koha.git] / misc / cronjobs / overdue_notices.pl
index b358797..4de6cdd 100755 (executable)
@@ -1,4 +1,4 @@
-#!/usr/bin/perl -w
+#!/usr/bin/perl
 
 # Copyright 2008 Liblime
 #
 # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
 # A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
 #
-# You should have received a copy of the GNU General Public License along with
-# Koha; if not, write to the Free Software Foundation, Inc., 59 Temple Place,
-# Suite 330, Boston, MA  02111-1307 USA
+# You should have received a copy of the GNU General Public License along
+# with Koha; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 
 use strict;
 use warnings;
+use utf8;
 
 BEGIN {
 
@@ -28,14 +29,17 @@ BEGIN {
     eval { require "$FindBin::Bin/../kohalib.pl" };
 }
 
+use Getopt::Long;
+use Pod::Usage;
+use Text::CSV_XS;
+use Locale::Currency::Format 1.28;
+use Encode;
+
 use C4::Context;
 use C4::Dates qw/format_date/;
 use C4::Debug;
 use C4::Letters;
-
-use Getopt::Long;
-use Pod::Usage;
-use Text::CSV_XS;
+use C4::Overdues qw(GetFine);
 
 =head1 NAME
 
@@ -52,6 +56,7 @@ overdue_notices.pl [ -n ] [ -library <branchcode> ] [ -library <branchcode>...]
    -max          <days>           maximum days overdue to deal with
    -library      <branchname>     only deal with overdues from this library (repeatable : several libraries can be given)
    -csv          <filename>       populate CSV file
+   -html         <filename>       Output html to file
    -itemscontent <list of fields> item information in templates
    -borcat       <categorycode>   category code that must be included
    -borcatout    <categorycode>   category code that must be excluded
@@ -242,9 +247,10 @@ my $nomail  = 0;
 my $MAX     = 90;
 my @branchcodes; # Branch(es) passed as parameter
 my $csvfilename;
+my $htmlfilename;
 my $triggered = 0;
 my $listall = 0;
-my $itemscontent = join( ',', qw( issuedate title barcode author ) );
+my $itemscontent = join( ',', qw( issuedate title barcode author biblionumber ) );
 my @myborcat;
 my @myborcatout;
 
@@ -256,6 +262,7 @@ GetOptions(
     'max=s'          => \$MAX,
     'library=s'      => \@branchcodes,
     'csv:s'          => \$csvfilename,    # this optional argument gets '' if not supplied.
+    'html:s'          => \$htmlfilename,    # this optional argument gets '' if not supplied.
     'itemscontent=s' => \$itemscontent,
     'list-all'      => \$listall,
     't|triggered'             => \$triggered,
@@ -276,6 +283,8 @@ my $branchcount = scalar(@overduebranches);
 my $overduebranch_word = scalar @overduebranches > 1 ? 'branches' : 'branch';
 my $branchcodes_word = scalar @branchcodes > 1 ? 'branches' : 'branch';
 
+my $PrintNoticesMaxLines = C4::Context->preference('PrintNoticesMaxLines');
+
 if ($branchcount) {
     $verbose and warn "Found $branchcount $overduebranch_word with first message enabled: " . join( ', ', map { "'$_'" } @overduebranches ), "\n";
 } else {
@@ -310,6 +319,7 @@ my @item_content_fields = split( /,/, $itemscontent );
 
 binmode( STDOUT, ":utf8" );
 
+
 our $csv;       # the Text::CSV_XS object
 our $csv_fh;    # the filehandle to the CSV file.
 if ( defined $csvfilename ) {
@@ -328,6 +338,28 @@ if ( defined $csvfilename ) {
 }
 
 @branches = @overduebranches unless @branches;
+our $html_fh;
+if ( defined $htmlfilename ) {
+  if ( $htmlfilename eq '' ) {
+    $html_fh = *STDOUT;
+  } else {
+    my $today = C4::Dates->new();
+    open $html_fh, ">",File::Spec->catdir ($htmlfilename,"notices-".$today->output('iso').".html");
+  }
+  
+  print $html_fh "<html>\n";
+  print $html_fh "<head>\n";
+  print $html_fh "<style type='text/css'>\n";
+  print $html_fh "pre {page-break-after: always;}\n";
+  print $html_fh "pre {white-space: pre-wrap;}\n";
+  print $html_fh "pre {white-space: -moz-pre-wrap;}\n";
+  print $html_fh "pre {white-space: -o-pre-wrap;}\n";
+  print $html_fh "pre {word-wrap: break-work;}\n";
+  print $html_fh "</style>\n";
+  print $html_fh "</head>\n";
+  print $html_fh "<body>\n";
+}
+
 foreach my $branchcode (@branches) {
 
     my $branch_details = C4::Branch::GetBranchDetail($branchcode);
@@ -422,6 +454,7 @@ END_SQL
                 $verbose and warn "borrower $firstname, $lastname ($borrowernumber) has $itemcount items triggering level $i.";
     
                 my $letter = C4::Letters::getletter( 'circulation', $overdue_rules->{"letter$i"} );
+
                 unless ($letter) {
                     $verbose and warn "Message '$overdue_rules->{letter$i}' content not found";
     
@@ -436,27 +469,42 @@ END_SQL
                     C4::Members::DebarMember($borrowernumber);
                     $verbose and warn "debarring $borrowernumber $firstname $lastname\n";
                 }
-                $sth2->execute( ($listall) ? ( $borrowernumber , 1 , $MAX ) : ( $borrowernumber, $mindays, $maxdays ) );
+                my @params = ($listall ? ( $borrowernumber , 1 , $MAX ) : ( $borrowernumber, $mindays, $maxdays ));
+                $sth2->execute(@params);
                 my $itemcount = 0;
                 my $titles = "";
+                my @items = ();
+                
+                my $i = 0;
+                my $exceededPrintNoticesMaxLines = 0;
                 while ( my $item_info = $sth2->fetchrow_hashref() ) {
+                    if ( ( !$email || $nomail ) && $PrintNoticesMaxLines && $i >= $PrintNoticesMaxLines ) {
+                      $exceededPrintNoticesMaxLines = 1;
+                      last;
+                    }
+                    $i++;
                     my @item_info = map { $_ =~ /^date|date$/ ? format_date( $item_info->{$_} ) : $item_info->{$_} || '' } @item_content_fields;
                     $titles .= join("\t", @item_info) . "\n";
                     $itemcount++;
+                    push (@items, $item_info->{'biblionumber'});
                 }
                 $sth2->finish;
-    
                 $letter = parse_letter(
-                    {   letter         => $letter,
-                        borrowernumber => $borrowernumber,
-                        branchcode     => $branchcode,
-                        substitute     => {
-                            bib             => $branch_details->{'branchname'},
-                            'items.content' => $titles
-                        }
+                    {   letter          => $letter,
+                        borrowernumber  => $borrowernumber,
+                        branchcode      => $branchcode,
+                        biblionumber    => \@items,
+                        substitute      => {    # this appears to be a hack to overcome incomplete features in this code.
+                                            bib             => $branch_details->{'branchname'}, # maybe 'bib' is a typo for 'lib<rary>'?
+                                            'items.content' => $titles
+                                           }
                     }
                 );
-    
+                
+                if ( $exceededPrintNoticesMaxLines ) {
+                  $letter->{'content'} .= "List too long for form; please check your account online for a complete list of your overdue items.";
+                }
+
                 my @misses = grep { /./ } map { /^([^>]*)[>]+/; ( $1 || '' ); } split /\</, $letter->{'content'};
                 if (@misses) {
                     $verbose and warn "The following terms were not matched and replaced: \n\t" . join "\n\t", @misses;
@@ -479,7 +527,7 @@ END_SQL
                             email          => $email,
                             itemcount      => $itemcount,
                             titles         => $titles,
-                            outputformat   => defined $csvfilename ? 'csv' : '',
+                            outputformat   => defined $csvfilename ? 'csv' : defined $htmlfilename ? 'html' : '',
                         }
                       );
                 } else {
@@ -507,7 +555,7 @@ END_SQL
                                 email          => $email,
                                 itemcount      => $itemcount,
                                 titles         => $titles,
-                                outputformat   => defined $csvfilename ? 'csv' : '',
+                                outputformat   => defined $csvfilename ? 'csv' : defined $htmlfilename ? 'html' : '',
                             }
                           );
                     }
@@ -518,43 +566,49 @@ END_SQL
     }
 
     if (@output_chunks) {
-        if ($nomail) {
-            if ( defined $csvfilename ) {
-                print $csv_fh @output_chunks;
-            } else {
+        if ( defined $csvfilename ) {
+            print $csv_fh @output_chunks;        
+        }
+        elsif ( defined $htmlfilename ) {
+            print $html_fh @output_chunks;        
+        }
+        elsif ($nomail){
                 local $, = "\f";    # pagebreak
                 print @output_chunks;
-            }
-        } else {
-            my $attachment = {
-                filename => defined $csvfilename ? 'attachment.csv' : 'attachment.txt',
-                type => 'text/plain',
-                content => join( "\n", @output_chunks )
-            };
-
-            my $letter = {
-                title   => 'Overdue Notices',
-                content => 'These messages were not sent directly to the patrons.',
-            };
-            C4::Letters::EnqueueLetter(
-                {   letter                 => $letter,
-                    borrowernumber         => undef,
-                    message_transport_type => 'email',
-                    attachments            => [$attachment],
-                    to_address             => $admin_email_address,
-                }
-            );
         }
+        my $attachment = {
+            filename => defined $csvfilename ? 'attachment.csv' : 'attachment.txt',
+            type => 'text/plain',
+            content => join( "\n", @output_chunks )
+        };
+
+        my $letter = {
+            title   => 'Overdue Notices',
+            content => 'These messages were not sent directly to the patrons.',
+        };
+        C4::Letters::EnqueueLetter(
+            {   letter                 => $letter,
+                borrowernumber         => undef,
+                message_transport_type => 'email',
+                attachments            => [$attachment],
+                to_address             => $admin_email_address,
+            }
+        );
     }
 
 }
 if ($csvfilename) {
-
     # note that we're not testing on $csv_fh to prevent closing
     # STDOUT.
     close $csv_fh;
 }
 
+if ( defined $htmlfilename ) {
+  print $html_fh "</body>\n";
+  print $html_fh "</html>\n";
+  close $html_fh;
+}
+
 =head1 INTERNAL METHODS
 
 These methods are internal to the operation of overdue_notices.pl.
@@ -576,32 +630,52 @@ substituted keys and values.
 
 =cut
 
-sub parse_letter {
+sub parse_letter { # FIXME: this code should probably be moved to C4::Letters:parseletter
     my $params = shift;
     foreach my $required (qw( letter borrowernumber )) {
         return unless exists $params->{$required};
     }
 
+
     if ( $params->{'substitute'} ) {
         while ( my ( $key, $replacedby ) = each %{ $params->{'substitute'} } ) {
             my $replacefield = "<<$key>>";
-
             $params->{'letter'}->{title}   =~ s/$replacefield/$replacedby/g;
             $params->{'letter'}->{content} =~ s/$replacefield/$replacedby/g;
         }
     }
 
-    C4::Letters::parseletter( $params->{'letter'}, 'borrowers', $params->{'borrowernumber'} );
+    $params->{'letter'} = C4::Letters::parseletter( $params->{'letter'}, 'borrowers', $params->{'borrowernumber'} );
 
     if ( $params->{'branchcode'} ) {
-        C4::Letters::parseletter( $params->{'letter'}, 'branches', $params->{'branchcode'} );
+        $params->{'letter'} = C4::Letters::parseletter( $params->{'letter'}, 'branches', $params->{'branchcode'} );
     }
 
     if ( $params->{'biblionumber'} ) {
-        C4::Letters::parseletter( $params->{'letter'}, 'biblio',      $params->{'biblionumber'} );
-        C4::Letters::parseletter( $params->{'letter'}, 'biblioitems', $params->{'biblionumber'} );
-    }
+        my $item_format = '';
+        PROCESS_ITEMS:
+        while (scalar(@{$params->{'biblionumber'}}) > 0) {
+            my $item = shift @{$params->{'biblionumber'}};
+            my $fine = GetFine($item, $params->{'borrowernumber'});
+            if (!$item_format) {
+                $params->{'letter'}->{'content'} =~ m/(<item>.*<\/item>)/;
+                $item_format = $1;
+            }
+            if ($params->{'letter'}->{'content'} =~ m/<fine>(.*)<\/fine>/) { # process any fine tags...
+                no strict; # currency_format behaves badly if we quote the bareword for some reason...
+                my $formatted_fine = currency_format("$1", "$fine", FMT_SYMBOL);
+                use strict;
+                $formatted_fine = Encode::encode("utf8", $formatted_fine);
+                $params->{'letter'}->{'content'} =~ s/<fine>.*<\/fine>/$formatted_fine/;
+            }
+            $params->{'letter'} = C4::Letters::parseletter( $params->{'letter'}, 'biblio',      $item );
+            $params->{'letter'} = C4::Letters::parseletter( $params->{'letter'}, 'biblioitems', $item );
+            $params->{'letter'} = C4::Letters::parseletter( $params->{'letter'}, 'items', $item );
+            $params->{'letter'}->{'content'} =~ s/(<item>.*<\/item>)/$1\n$item_format/ if scalar(@{$params->{'biblionumber'}} > 0);
 
+        }
+    }
+    $params->{'letter'}->{'content'} =~ s/<\/{0,1}?item>//g; # strip all remaining item tags...
     return $params->{'letter'};
 }
 
@@ -641,6 +715,10 @@ sub prepare_letter_for_printing {
         } else {
             $verbose and warn 'combine failed on argument: ' . $csv->error_input;
         }
+    } elsif ( exists $params->{'outputformat'} && $params->{'outputformat'} eq 'html' ) {
+      $return = "<pre>\n";
+      $return .= "$params->{'letter'}->{'content'}\n";
+      $return .= "\n</pre>\n";
     } else {
         $return .= "$params->{'letter'}->{'content'}\n";