X-Git-Url: http://git.rot13.org/?a=blobdiff_plain;f=C4%2FLetters.pm;h=93bc34d445ea81677e6ca6b2758f88ca49200ef8;hb=6751c5dc7c963defd0a31adfd46d0665abf41cd6;hp=28c6984c61629fcba19f2fdd1f0a48bdaa78fde5;hpb=e93126834cddfb5e0f33f0f8df77e67ee2a68b48;p=koha.git diff --git a/C4/Letters.pm b/C4/Letters.pm index 28c6984c61..93bc34d445 100644 --- a/C4/Letters.pm +++ b/C4/Letters.pm @@ -24,10 +24,12 @@ use MIME::Lite; use Mail::Sendmail; use C4::Members; +use C4::Members::Attributes qw(GetBorrowerAttributes); use C4::Branch; use C4::Log; use C4::SMS; use C4::Debug; +use Koha::DateUtils; use Date::Calc qw( Add_Delta_Days ); use Encode; use Carp; @@ -37,10 +39,10 @@ use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS); BEGIN { require Exporter; # set the version for version checking - $VERSION = 3.01; + $VERSION = 3.07.00.049; @ISA = qw(Exporter); @EXPORT = qw( - &GetLetters &getletter &addalert &getalert &delalert &findrelatedto &SendAlerts GetPrintMessages + &GetLetters &GetPreparedLetter &GetWrappedLetter &addalert &getalert &delalert &findrelatedto &SendAlerts &GetPrintMessages ); } @@ -92,7 +94,7 @@ $template->param(LETTERLOOP => \@letterloop); =cut -sub GetLetters (;$) { +sub GetLetters { # returns a reference to a hash of references to ALL letters... my $cat = shift; @@ -115,13 +117,36 @@ sub GetLetters (;$) { return \%letters; } -sub getletter ($$) { - my ( $module, $code ) = @_; +# FIXME: using our here means that a Plack server will need to be +# restarted fairly regularly when working with this routine. +# A better option would be to use Koha::Cache and use a cache +# that actually works in a persistent environment, but as a +# short-term fix, our will work. +our %letter; +sub getletter { + my ( $module, $code, $branchcode ) = @_; + + $branchcode ||= ''; + + if ( C4::Context->preference('IndependentBranches') + and $branchcode + and C4::Context->userenv ) { + + $branchcode = C4::Context->userenv->{'branch'}; + } + + if ( my $l = $letter{$module}{$code}{$branchcode} ) { + return { %$l }; # deep copy + } + my $dbh = C4::Context->dbh; - my $sth = $dbh->prepare("select * from letter where module=? and code=?"); - $sth->execute( $module, $code ); - my $line = $sth->fetchrow_hashref; - return $line; + my $sth = $dbh->prepare("select * from letter where module=? and code=? and (branchcode = ? or branchcode = '') order by branchcode desc limit 1"); + $sth->execute( $module, $code, $branchcode ); + my $line = $sth->fetchrow_hashref + or return; + $line->{'content-type'} = 'text/html; charset="UTF-8"' if $line->{is_html}; + $letter{$module}{$code}{$branchcode} = $line; + return { %$line }; } =head2 addalert ($borrowernumber, $type, $externalid) @@ -135,7 +160,7 @@ sub getletter ($$) { =cut -sub addalert ($$$) { +sub addalert { my ( $borrowernumber, $type, $externalid ) = @_; my $dbh = C4::Context->dbh; my $sth = @@ -156,7 +181,7 @@ sub addalert ($$$) { =cut -sub delalert ($) { +sub delalert { my $alertid = shift or die "delalert() called without valid argument (alertid)"; # it's gonna die anyway. $debug and warn "delalert: deleting alertid $alertid"; my $sth = C4::Context->dbh->prepare("delete from alert where alertid=?"); @@ -173,10 +198,10 @@ sub delalert ($) { =cut -sub getalert (;$$$) { +sub getalert { my ( $borrowernumber, $type, $externalid ) = @_; my $dbh = C4::Context->dbh; - my $query = "SELECT * FROM alert WHERE"; + my $query = "SELECT a.*, b.branchcode FROM alert a JOIN borrowers b USING(borrowernumber) WHERE"; my @bind; if ($borrowernumber and $borrowernumber =~ /^\d+$/) { $query .= " borrowernumber=? AND "; @@ -210,16 +235,16 @@ sub getalert (;$$$) { # outmoded POD: # When type=virtual, the id is related to a virtual shelf and this sub returns the name of the sub -sub findrelatedto ($$) { - my $type = shift or return undef; - my $externalid = shift or return undef; +sub findrelatedto { + my $type = shift or return; + my $externalid = shift or return; my $q = ($type eq 'issue' ) ? "select title as result from subscription left join biblio on subscription.biblionumber=biblio.biblionumber where subscriptionid=?" : ($type eq 'borrower') ? "select concat(firstname,' ',surname) from borrowers where borrowernumber=?" : undef; unless ($q) { warn "findrelatedto(): Illegal type '$type'"; - return undef; + return; } my $sth = C4::Context->dbh->prepare($q); $sth->execute($externalid); @@ -232,284 +257,410 @@ sub findrelatedto ($$) { parameters : - $type : the type of alert - $externalid : the id of the "object" to query - - $letter : the letter to send. + - $letter_code : the letter to send. send an alert to all borrowers having put an alert on a given subject. =cut sub SendAlerts { - my ( $type, $externalid, $letter ) = @_; + my ( $type, $externalid, $letter_code ) = @_; my $dbh = C4::Context->dbh; - my $strsth; if ( $type eq 'issue' ) { - # warn "sending issues..."; - my $letter = getletter( 'serial', $letter ); - # prepare the letter... # search the biblionumber my $sth = $dbh->prepare( "SELECT biblionumber FROM subscription WHERE subscriptionid=?"); $sth->execute($externalid); - my ($biblionumber) = $sth->fetchrow; - - # parsing branch info - my $userenv = C4::Context->userenv; - parseletter( $letter, 'branches', $userenv->{branch} ); - - # parsing librarian name - $letter->{content} =~ s/<>/$userenv->{firstname}/g; - $letter->{content} =~ s/<>/$userenv->{surname}/g; - $letter->{content} =~ - s/<>/$userenv->{emailaddress}/g; - - # parsing biblio information - parseletter( $letter, 'biblio', $biblionumber ); - parseletter( $letter, 'biblioitems', $biblionumber ); + my ($biblionumber) = $sth->fetchrow + or warn( "No subscription for '$externalid'" ), + return; + my %letter; # find the list of borrowers to alert my $alerts = getalert( '', 'issue', $externalid ); foreach (@$alerts) { - # and parse borrower ... - my $innerletter = $letter; my $borinfo = C4::Members::GetMember('borrowernumber' => $_->{'borrowernumber'}); - parseletter( $innerletter, 'borrowers', $_->{'borrowernumber'} ); + my $email = $borinfo->{email} or next; + + # warn "sending issues..."; + my $userenv = C4::Context->userenv; + my $branchdetails = GetBranchDetail($_->{'branchcode'}); + my $letter = GetPreparedLetter ( + module => 'serial', + letter_code => $letter_code, + branchcode => $userenv->{branch}, + tables => { + 'branches' => $_->{branchcode}, + 'biblio' => $biblionumber, + 'biblioitems' => $biblionumber, + 'borrowers' => $borinfo, + }, + want_librarian => 1, + ) or return; # ... then send mail - if ( $borinfo->{email} ) { - my %mail = ( - To => $borinfo->{email}, - From => $borinfo->{email}, - Subject => "" . $innerletter->{title}, - Message => "" . $innerletter->{content}, - 'Content-Type' => 'text/plain; charset="utf8"', - ); - sendmail(%mail) or carp $Mail::Sendmail::error; - - } + my %mail = ( + To => $email, + From => $branchdetails->{'branchemail'} || C4::Context->preference("KohaAdminEmailAddress"), + Subject => Encode::encode( "utf8", "" . $letter->{title} ), + Message => Encode::encode( "utf8", "" . $letter->{content} ), + 'Content-Type' => 'text/plain; charset="utf8"', + ); + sendmail(%mail) or carp $Mail::Sendmail::error; } } - elsif ( $type eq 'claimacquisition' ) { - - $letter = getletter( 'claimacquisition', $letter ); + elsif ( $type eq 'claimacquisition' or $type eq 'claimissues' ) { # prepare the letter... # search the biblionumber - $strsth = qq{ - SELECT aqorders.*,aqbasket.*,biblio.*,biblioitems.*,aqbooksellers.* + my $strsth = $type eq 'claimacquisition' + ? qq{ + SELECT aqorders.*,aqbasket.*,biblio.*,biblioitems.*,aqbooksellers.*, + aqbooksellers.id AS booksellerid FROM aqorders LEFT JOIN aqbasket ON aqbasket.basketno=aqorders.basketno LEFT JOIN biblio ON aqorders.biblionumber=biblio.biblionumber - LEFT JOIN biblioitems ON aqorders.biblioitemnumber=biblioitems.biblioitemnumber + LEFT JOIN biblioitems ON aqorders.biblionumber=biblioitems.biblionumber LEFT JOIN aqbooksellers ON aqbasket.booksellerid=aqbooksellers.id WHERE aqorders.ordernumber IN ( - } - . join( ",", @$externalid ) . ")"; - } - elsif ( $type eq 'claimissues' ) { - - $letter = getletter( 'claimissues', $letter ); - - # prepare the letter... - # search the biblionumber - $strsth = qq{ - SELECT serial.*,subscription.*, biblio.*, aqbooksellers.* + } + : qq{ + SELECT serial.*,subscription.*, biblio.*, aqbooksellers.*, + aqbooksellers.id AS booksellerid FROM serial LEFT JOIN subscription ON serial.subscriptionid=subscription.subscriptionid LEFT JOIN biblio ON serial.biblionumber=biblio.biblionumber LEFT JOIN aqbooksellers ON subscription.aqbooksellerid=aqbooksellers.id WHERE serial.serialid IN ( - } - . join( ",", @$externalid ) . ")"; - } - - if ( $type eq 'claimacquisition' or $type eq 'claimissues' ) { + }; + $strsth .= join( ",", @$externalid ) . ")"; my $sthorders = $dbh->prepare($strsth); $sthorders->execute; - my @fields = map { - $sthorders->{mysql_table}[$_] . "." . $sthorders->{NAME}[$_] } - (0 .. $#{$sthorders->{NAME}} ) ; - - my @orders_infos; - while ( my $row = $sthorders->fetchrow_arrayref() ) { - my %rec = (); - @rec{@fields} = @$row; - push @orders_infos, \%rec; + my $dataorders = $sthorders->fetchall_arrayref( {} ); + + my $sthbookseller = + $dbh->prepare("select * from aqbooksellers where id=?"); + $sthbookseller->execute( $dataorders->[0]->{booksellerid} ); + my $databookseller = $sthbookseller->fetchrow_hashref; + + my @email; + push @email, $databookseller->{bookselleremail} if $databookseller->{bookselleremail}; + push @email, $databookseller->{contemail} if $databookseller->{contemail}; + unless (@email) { + warn "Bookseller $dataorders->[0]->{booksellerid} without emails"; + return { error => "no_email" }; } - # parsing branch info my $userenv = C4::Context->userenv; - parseletter( $letter, 'branches', $userenv->{branch} ); - - # parsing librarian name - $letter->{content} =~ s/<>/$userenv->{firstname}/g; - $letter->{content} =~ s/<>/$userenv->{surname}/g; - $letter->{content} =~ s/<>/$userenv->{emailaddress}/g; - - # Get Fields remplacement - my $order_format = $1 if ( $letter->{content} =~ m/(.*<\/order>)/xms ); - - # Foreach field to remplace - while ( $letter->{content} =~ m/<<([^>]*)>>/g ) { - my $field = $1; - my $value = $orders_infos[0]->{$field} || ""; - $value = sprintf("%.2f", $value) if $field =~ /price/; - $letter->{content} =~ s/<<$field>>/$value/g; - } - - if ( $order_format ) { - # For each order - foreach my $infos ( @orders_infos ) { - my $order_content = $order_format; - # We replace by value - while ( $order_content =~ m/<<([^>]*)>>/g ) { - my $field = $1; - my $value = $infos->{$field} || ""; - $value = sprintf("%.2f", $value) if $field =~ /price/; - $order_content =~ s/(<<$field>>)/$value/g; - } - $order_content =~ s/<\/{0,1}?order>//g; - $letter->{content} =~ s/.*<\/order>/$order_content\n$order_format/xms; - } - $letter->{content} =~ s/.*<\/order>//xms; - } - - my $innerletter = $letter; + my $letter = GetPreparedLetter ( + module => $type, + letter_code => $letter_code, + branchcode => $userenv->{branch}, + tables => { + 'branches' => $userenv->{branch}, + 'aqbooksellers' => $databookseller, + }, + repeat => $dataorders, + want_librarian => 1, + ) or return; # ... then send mail - if ( $orders_infos[0]->{'aqbooksellers.bookselleremail'} - || $orders_infos[0]->{'aqbooksellers.contemail'} ) { - my $to = $orders_infos[0]->{'aqbooksellers.bookselleremail'}; - $to .= ", " if $to; - $to .= $orders_infos[0]->{'aqbooksellers.contemail'} || ""; - my %mail = ( - To => $to, - From => $userenv->{emailaddress}, - Subject => Encode::encode( "utf8", "" . $innerletter->{title} ), - Message => Encode::encode( "utf8", "" . $innerletter->{content} ), - 'Content-Type' => 'text/plain; charset="utf8"', - ); - sendmail(%mail) or carp $Mail::Sendmail::error; - warn "sending to $mail{To} From $mail{From} subj $mail{Subject} Mess $mail{Message}" if $debug; - if ( C4::Context->preference("LetterLog") ) { - logaction( "ACQUISITION", "Send Acquisition claim letter", "", "order list : " . join( ",", @$externalid ) . "\n$innerletter->{title}\n$innerletter->{content}" ) if $type eq 'claimacquisition'; - logaction( "ACQUISITION", "CLAIM ISSUE", undef, "To=" . $mail{To} . " Title=" . $innerletter->{title} . " Content=" . $innerletter->{content} ) if $type eq 'claimissues'; - } - } else { - return {error => "no_email" }; - } + my %mail = ( + To => join( ',', @email), + From => $userenv->{emailaddress}, + Subject => Encode::encode( "utf8", "" . $letter->{title} ), + Message => Encode::encode( "utf8", "" . $letter->{content} ), + 'Content-Type' => 'text/plain; charset="utf8"', + ); + sendmail(%mail) or carp $Mail::Sendmail::error; - warn "sending to From $userenv->{emailaddress} subj $innerletter->{title} Mess $innerletter->{content}" if $debug; + logaction( + "ACQUISITION", + $type eq 'claimissues' ? "CLAIM ISSUE" : "ACQUISITION CLAIM", + undef, + "To=" + . $databookseller->{contemail} + . " Title=" + . $letter->{title} + . " Content=" + . $letter->{content} + ) if C4::Context->preference("LetterLog"); } - - # send an "account details" notice to a newly created user + # send an "account details" notice to a newly created user elsif ( $type eq 'members' ) { - # must parse the password special, before it's hashed. - $letter->{content} =~ s/<>/$externalid->{'password'}/g; - - parseletter( $letter, 'borrowers', $externalid->{'borrowernumber'}); - parseletter( $letter, 'branches', $externalid->{'branchcode'} ); - my $branchdetails = GetBranchDetail($externalid->{'branchcode'}); + my $letter = GetPreparedLetter ( + module => 'members', + letter_code => $letter_code, + branchcode => $externalid->{'branchcode'}, + tables => { + 'branches' => $branchdetails, + 'borrowers' => $externalid->{'borrowernumber'}, + }, + substitute => { 'borrowers.password' => $externalid->{'password'} }, + want_librarian => 1, + ) or return; + + return { error => "no_email" } unless $externalid->{'emailaddr'}; my %mail = ( To => $externalid->{'emailaddr'}, From => $branchdetails->{'branchemail'} || C4::Context->preference("KohaAdminEmailAddress"), - Subject => $letter->{'title'}, - Message => $letter->{'content'}, + Subject => Encode::encode( "utf8", $letter->{'title'} ), + Message => Encode::encode( "utf8", $letter->{'content'} ), 'Content-Type' => 'text/plain; charset="utf8"', ); sendmail(%mail) or carp $Mail::Sendmail::error; } } -=head2 parseletter($letter, $table, $pk) - - parameters : - - $letter : a hash to letter fields (title & content useful) - - $table : the Koha table to parse. - - $pk : the primary key to query on the $table table - parse all fields from a table, and replace values in title & content with the appropriate value - (not exported sub, used only internally) +=head2 GetPreparedLetter( %params ) + + %params hash: + module => letter module, mandatory + letter_code => letter code, mandatory + branchcode => for letter selection, if missing default system letter taken + tables => a hashref with table names as keys. Values are either: + - a scalar - primary key value + - an arrayref - primary key values + - a hashref - full record + substitute => custom substitution key/value pairs + repeat => records to be substituted on consecutive lines: + - an arrayref - tries to guess what needs substituting by + taking remaining << >> tokensr; not recommended + - a hashref token => @tables - replaces << >> << >> + subtemplate for each @tables row; table is a hashref as above + want_librarian => boolean, if set to true triggers librarian details + substitution from the userenv + Return value: + letter fields hashref (title & content useful) =cut -our %handles = (); -our %columns = (); +sub GetPreparedLetter { + my %params = @_; + + my $module = $params{module} or croak "No module"; + my $letter_code = $params{letter_code} or croak "No letter_code"; + my $branchcode = $params{branchcode} || ''; + + my $letter = getletter( $module, $letter_code, $branchcode ) + or warn( "No $module $letter_code letter"), + return; + + my $tables = $params{tables}; + my $substitute = $params{substitute}; + my $repeat = $params{repeat}; + $tables || $substitute || $repeat + or carp( "ERROR: nothing to substitute - both 'tables' and 'substitute' are empty" ), + return; + my $want_librarian = $params{want_librarian}; + + if ($substitute) { + while ( my ($token, $val) = each %$substitute ) { + $letter->{title} =~ s/<<$token>>/$val/g; + $letter->{content} =~ s/<<$token>>/$val/g; + } + } + + my $OPACBaseURL = C4::Context->preference('OPACBaseURL'); + $letter->{content} =~ s/<>/$OPACBaseURL/go; + + if ($want_librarian) { + # parsing librarian name + my $userenv = C4::Context->userenv; + $letter->{content} =~ s/<>/$userenv->{firstname}/go; + $letter->{content} =~ s/<>/$userenv->{surname}/go; + $letter->{content} =~ s/<>/$userenv->{emailaddress}/go; + } + + my ($repeat_no_enclosing_tags, $repeat_enclosing_tags); -sub parseletter_sth { + if ($repeat) { + if (ref ($repeat) eq 'ARRAY' ) { + $repeat_no_enclosing_tags = $repeat; + } else { + $repeat_enclosing_tags = $repeat; + } + } + + if ($repeat_enclosing_tags) { + while ( my ($tag, $tag_tables) = each %$repeat_enclosing_tags ) { + if ( $letter->{content} =~ m!<$tag>(.*)!s ) { + my $subcontent = $1; + my @lines = map { + my %subletter = ( title => '', content => $subcontent ); + _substitute_tables( \%subletter, $_ ); + $subletter{content}; + } @$tag_tables; + $letter->{content} =~ s!<$tag>.*!join( "\n", @lines )!se; + } + } + } + + if ($tables) { + _substitute_tables( $letter, $tables ); + } + + if ($repeat_no_enclosing_tags) { + if ( $letter->{content} =~ m/[^\n]*<<.*>>[^\n]*/so ) { + my $line = $&; + my $i = 1; + my @lines = map { + my $c = $line; + $c =~ s/<>/$i/go; + foreach my $field ( keys %{$_} ) { + $c =~ s/(<<[^\.]+.$field>>)/$_->{$field}/; + } + $i++; + $c; + } @$repeat_no_enclosing_tags; + + my $replaceby = join( "\n", @lines ); + $letter->{content} =~ s/\Q$line\E/$replaceby/s; + } + } + + $letter->{content} =~ s/<<\S*>>//go; #remove any stragglers +# $letter->{content} =~ s/<<[^>]*>>//go; + + return $letter; +} + +sub _substitute_tables { + my ( $letter, $tables ) = @_; + while ( my ($table, $param) = each %$tables ) { + next unless $param; + + my $ref = ref $param; + + my $values; + if ($ref && $ref eq 'HASH') { + $values = $param; + } + else { + my @pk; + my $sth = _parseletter_sth($table); + unless ($sth) { + warn "_parseletter_sth('$table') failed to return a valid sth. No substitution will be done for that table."; + return; + } + $sth->execute( $ref ? @$param : $param ); + + $values = $sth->fetchrow_hashref; + $sth->finish(); + } + + _parseletter ( $letter, $table, $values ); + } +} + +sub _parseletter_sth { my $table = shift; + my $sth; unless ($table) { - carp "ERROR: parseletter_sth() called without argument (table)"; + carp "ERROR: _parseletter_sth() called without argument (table)"; return; } - # check cache first - (defined $handles{$table}) and return $handles{$table}; + # NOTE: we used to check whether we had a statement handle cached in + # a %handles module-level variable. This was a dumb move and + # broke things for the rest of us. prepare_cached is a better + # way to cache statement handles anyway. my $query = - ($table eq 'biblio' ) ? "SELECT * FROM $table WHERE biblionumber = ?" : - ($table eq 'biblioitems' ) ? "SELECT * FROM $table WHERE biblionumber = ?" : - ($table eq 'items' ) ? "SELECT * FROM $table WHERE itemnumber = ?" : - ($table eq 'issues' ) ? "SELECT * FROM $table WHERE itemnumber = ?" : - ($table eq 'reserves' ) ? "SELECT * FROM $table WHERE borrowernumber = ? and biblionumber = ?" : - ($table eq 'borrowers' ) ? "SELECT * FROM $table WHERE borrowernumber = ?" : - ($table eq 'branches' ) ? "SELECT * FROM $table WHERE branchcode = ?" : - ($table eq 'suggestions' ) ? "SELECT * FROM $table WHERE suggestionid = ?" : - ($table eq 'aqbooksellers') ? "SELECT * FROM $table WHERE id = ?" : undef ; + ($table eq 'biblio' ) ? "SELECT * FROM $table WHERE biblionumber = ?" : + ($table eq 'biblioitems' ) ? "SELECT * FROM $table WHERE biblionumber = ?" : + ($table eq 'items' ) ? "SELECT * FROM $table WHERE itemnumber = ?" : + ($table eq 'issues' ) ? "SELECT * FROM $table WHERE itemnumber = ?" : + ($table eq 'old_issues' ) ? "SELECT * FROM $table WHERE itemnumber = ? ORDER BY timestamp DESC LIMIT 1" : + ($table eq 'reserves' ) ? "SELECT * FROM $table WHERE borrowernumber = ? and biblionumber = ?" : + ($table eq 'borrowers' ) ? "SELECT * FROM $table WHERE borrowernumber = ?" : + ($table eq 'branches' ) ? "SELECT * FROM $table WHERE branchcode = ?" : + ($table eq 'suggestions' ) ? "SELECT * FROM $table WHERE suggestionid = ?" : + ($table eq 'aqbooksellers') ? "SELECT * FROM $table WHERE id = ?" : + ($table eq 'aqorders' ) ? "SELECT * FROM $table WHERE ordernumber = ?" : + ($table eq 'opac_news' ) ? "SELECT * FROM $table WHERE idnew = ?" : + ($table eq 'borrower_modifications') ? "SELECT * FROM $table WHERE borrowernumber = ? OR verification_token =?": + undef ; unless ($query) { - warn "ERROR: No parseletter_sth query for table '$table'"; + warn "ERROR: No _parseletter_sth query for table '$table'"; return; # nothing to get } - unless ($handles{$table} = C4::Context->dbh->prepare($query)) { + unless ($sth = C4::Context->dbh->prepare_cached($query)) { warn "ERROR: Failed to prepare query: '$query'"; return; } - return $handles{$table}; # now cache is populated for that $table + return $sth; # now cache is populated for that $table } -sub parseletter { - my ( $letter, $table, $pk, $pk2 ) = @_; - unless ($letter) { - carp "ERROR: parseletter() 1st argument 'letter' empty"; - return; - } - my $sth = parseletter_sth($table); - unless ($sth) { - warn "parseletter_sth('$table') failed to return a valid sth. No substitution will be done for that table."; - return; - } - if ( $pk2 ) { - $sth->execute($pk, $pk2); - } else { - $sth->execute($pk); - } +=head2 _parseletter($letter, $table, $values) + + parameters : + - $letter : a hash to letter fields (title & content useful) + - $table : the Koha table to parse. + - $values : table record hashref + parse all fields from a table, and replace values in title & content with the appropriate value + (not exported sub, used only internally) + +=cut + +sub _parseletter { + my ( $letter, $table, $values ) = @_; - my $values = $sth->fetchrow_hashref; - - # TEMPORARY hack until the expirationdate column is added to reserves if ( $table eq 'reserves' && $values->{'waitingdate'} ) { my @waitingdate = split /-/, $values->{'waitingdate'}; - $values->{'expirationdate'} = C4::Dates->new( - sprintf( - '%04d-%02d-%02d', - Add_Delta_Days( @waitingdate, C4::Context->preference( 'ReservesMaxPickUpDelay' ) ) - ), - 'iso' - )->output(); + my $dt = dt_from_string(); + $dt->add( days => C4::Context->preference('ReservesMaxPickUpDelay') ); + $values->{'expirationdate'} = output_pref( $dt, undef, 1 ); + + $values->{'waitingdate'} = output_pref( dt_from_string( $values->{'waitingdate'} ), undef, 1 ); + } + if ($letter->{content} && $letter->{content} =~ /<>/) { + my $todaysdate = output_pref( DateTime->now() ); + $letter->{content} =~ s/<>/$todaysdate/go; + } - # and get all fields from the table - my $columns = C4::Context->dbh->prepare("SHOW COLUMNS FROM $table"); - $columns->execute; - while ( ( my $field ) = $columns->fetchrow_array ) { - my $replacefield = "<<$table.$field>>"; - $values->{$field} =~ s/\p{P}(?=$)//g if $values->{$field}; - my $replacedby = $values->{$field} || ''; - ($letter->{title} ) and $letter->{title} =~ s/$replacefield/$replacedby/g; - ($letter->{content}) and $letter->{content} =~ s/$replacefield/$replacedby/g; + while ( my ($field, $val) = each %$values ) { + my $replacetablefield = "<<$table.$field>>"; + my $replacefield = "<<$field>>"; + $val =~ s/\p{P}$// if $val && $table=~/biblio/; + #BZ 9886: Assuming that we want to eliminate ISBD punctuation here + #Therefore adding the test on biblio. This includes biblioitems, + #but excludes items. Removed unneeded global and lookahead. + + my $replacedby = defined ($val) ? $val : ''; + ($letter->{title} ) and do { + $letter->{title} =~ s/$replacetablefield/$replacedby/g; + $letter->{title} =~ s/$replacefield/$replacedby/g; + }; + ($letter->{content}) and do { + $letter->{content} =~ s/$replacetablefield/$replacedby/g; + $letter->{content} =~ s/$replacefield/$replacedby/g; + }; + } + + if ($table eq 'borrowers' && $letter->{content}) { + if ( my $attributes = GetBorrowerAttributes($values->{borrowernumber}) ) { + my %attr; + foreach (@$attributes) { + my $code = $_->{code}; + my $val = $_->{value_description} || $_->{value}; + $val =~ s/\p{P}(?=$)//g if $val; + next unless $val gt ''; + $attr{$code} ||= []; + push @{ $attr{$code} }, $val; + } + while ( my ($code, $val_ar) = each %attr ) { + my $replacefield = "<>"; + my $replacedby = join ',', @$val_ar; + $letter->{content} =~ s/$replacefield/$replacedby/g; + } + } } return $letter; } @@ -523,17 +674,24 @@ places a letter in the message_queue database table, which will eventually get processed (sent) by the process_message_queue.pl cronjob when it calls SendQueuedMessages. -return true on success +return message_id on success =cut -sub EnqueueLetter ($) { - my $params = shift or return undef; +sub EnqueueLetter { + my $params = shift or return; return unless exists $params->{'letter'}; - return unless exists $params->{'borrowernumber'}; +# return unless exists $params->{'borrowernumber'}; return unless exists $params->{'message_transport_type'}; + my $content = $params->{letter}->{content}; + $content =~ s/\s+//g if(defined $content); + if ( not defined $content or $content eq '' ) { + warn "Trying to add an empty message to the message queue" if $debug; + return; + } + # If we have any attachments we should encode then into the body. if ( $params->{'attachments'} ) { $params->{'letter'} = _add_attachments( @@ -565,7 +723,7 @@ ENDSQL $params->{'from_address'}, # from_address $params->{'letter'}->{'content-type'}, # content_type ); - return $result; + return $dbh->last_insert_id(undef,undef,'message_queue', undef); } =head2 SendQueuedMessages ([$hashref]) @@ -578,7 +736,7 @@ returns number of messages sent. =cut -sub SendQueuedMessages (;$) { +sub SendQueuedMessages { my $params = shift; my $unsent_messages = _get_unsent_messages(); @@ -633,7 +791,8 @@ sub GetPrintMessages { my $params = shift || {}; return _get_unsent_messages( { message_transport_type => 'print', - borrowernumber => $params->{'borrowernumber'}, } ); + borrowernumber => $params->{'borrowernumber'}, + } ); } =head2 GetQueuedMessages ([$hashref]) @@ -694,41 +853,43 @@ returns your letter object, with the content updated. sub _add_attachments { my $params = shift; - return unless 'HASH' eq ref $params; - foreach my $required_parameter (qw( letter attachments message )) { - return unless exists $params->{$required_parameter}; - } - return $params->{'letter'} unless @{ $params->{'attachments'} }; + my $letter = $params->{'letter'}; + my $attachments = $params->{'attachments'}; + return $letter unless @$attachments; + my $message = $params->{'message'}; # First, we have to put the body in as the first attachment - $params->{'message'}->attach( - Type => 'TEXT', - Data => $params->{'letter'}->{'content'}, + $message->attach( + Type => $letter->{'content-type'} || 'TEXT', + Data => $letter->{'is_html'} + ? _wrap_html($letter->{'content'}, $letter->{'title'}) + : $letter->{'content'}, ); - foreach my $attachment ( @{ $params->{'attachments'} } ) { - $params->{'message'}->attach( + foreach my $attachment ( @$attachments ) { + $message->attach( Type => $attachment->{'type'}, Data => $attachment->{'content'}, Filename => $attachment->{'filename'}, ); } # we're forcing list context here to get the header, not the count back from grep. - ( $params->{'letter'}->{'content-type'} ) = grep( /^Content-Type:/, split( /\n/, $params->{'message'}->header_as_string ) ); - $params->{'letter'}->{'content-type'} =~ s/^Content-Type:\s+//; - $params->{'letter'}->{'content'} = $params->{'message'}->body_as_string; + ( $letter->{'content-type'} ) = grep( /^Content-Type:/, split( /\n/, $params->{'message'}->header_as_string ) ); + $letter->{'content-type'} =~ s/^Content-Type:\s+//; + $letter->{'content'} = $message->body_as_string; - return $params->{'letter'}; + return $letter; } -sub _get_unsent_messages (;$) { +sub _get_unsent_messages { my $params = shift; my $dbh = C4::Context->dbh(); my $statement = << 'ENDSQL'; -SELECT message_id, borrowernumber, subject, content, message_transport_type, status, time_queued, from_address, to_address, content_type - FROM message_queue +SELECT mq.message_id, mq.borrowernumber, mq.subject, mq.content, mq.message_transport_type, mq.status, mq.time_queued, mq.from_address, mq.to_address, mq.content_type, b.branchcode + FROM message_queue mq + LEFT JOIN borrowers b ON b.borrowernumber = mq.borrowernumber WHERE status = ? ENDSQL @@ -747,6 +908,7 @@ ENDSQL push @query_params, $params->{'limit'}; } } + $debug and warn "_get_unsent_messages SQL: $statement"; $debug and warn "_get_unsent_messages params: " . join(',',@query_params); my $sth = $dbh->prepare( $statement ); @@ -754,7 +916,7 @@ ENDSQL return $sth->fetchall_arrayref({}); } -sub _send_message_by_email ($;$$$) { +sub _send_message_by_email { my $message = shift or return; my ($username, $password, $method) = @_; @@ -767,13 +929,7 @@ sub _send_message_by_email ($;$$$) { status => 'failed' } ); return; } - my $which_address = C4::Context->preference('AutoEmailPrimaryAddress'); - # If the system preference is set to 'first valid' (value == OFF), look up email address - if ($which_address eq 'OFF') { - $to_address = GetFirstValidEmailAddress( $message->{'borrowernumber'} ); - } else { - $to_address = $member->{$which_address}; - } + $to_address = C4::Members::GetNoticeEmailAddress( $message->{'borrowernumber'} ); unless ($to_address) { # warn "FAIL: No 'to_address' and no email for " . ($member->{surname} ||'') . ", borrowernumber ($message->{borrowernumber})"; # warning too verbose for this more common case? @@ -785,14 +941,17 @@ sub _send_message_by_email ($;$$$) { my $utf8 = decode('MIME-Header', $message->{'subject'} ); $message->{subject}= encode('MIME-Header', $utf8); + my $subject = encode('utf8', $message->{'subject'}); my $content = encode('utf8', $message->{'content'}); + my $content_type = $message->{'content_type'} || 'text/plain; charset="UTF-8"'; + my $is_html = $content_type =~ m/html/io; my %sendmail_params = ( To => $to_address, From => $message->{'from_address'} || C4::Context->preference('KohaAdminEmailAddress'), - Subject => encode('utf8', $message->{'subject'}), + Subject => $subject, charset => 'utf8', - Message => $content, - 'content-type' => $message->{'content_type'} || 'text/plain; charset="UTF-8"', + Message => $is_html ? _wrap_html($content, $subject) : $content, + 'content-type' => $content_type, ); $sendmail_params{'Auth'} = {user => $username, pass => $password, method => $method} if $username; if ( my $bcc = C4::Context->preference('OverdueNoticeBcc') ) { @@ -812,8 +971,29 @@ sub _send_message_by_email ($;$$$) { } } -sub _send_message_by_sms ($) { - my $message = shift or return undef; +sub _wrap_html { + my ($content, $title) = @_; + + my $css = C4::Context->preference("NoticeCSS") || ''; + $css = qq{} if $css; + return < + + +$title + +$css + + +$content + + +EOS +} + +sub _send_message_by_sms { + my $message = shift or return; my $member = C4::Members::GetMember( 'borrowernumber' => $message->{'borrowernumber'} ); return unless $member->{'smsalertnumber'}; @@ -831,11 +1011,11 @@ sub _update_message_to_address { $dbh->do('UPDATE message_queue SET to_address=? WHERE message_id=?',undef,($to,$id)); } -sub _set_message_status ($) { - my $params = shift or return undef; +sub _set_message_status { + my $params = shift or return; foreach my $required_parameter ( qw( message_id status ) ) { - return undef unless exists $params->{ $required_parameter }; + return unless exists $params->{ $required_parameter }; } my $dbh = C4::Context->dbh();