Bug 15429 - sub _parseletter should not change referenced values
[koha.git] / C4 / Letters.pm
1 package C4::Letters;
2
3 # Copyright 2000-2002 Katipo Communications
4 #
5 # This file is part of Koha.
6 #
7 # Koha is free software; you can redistribute it and/or modify it
8 # under the terms of the GNU General Public License as published by
9 # the Free Software Foundation; either version 3 of the License, or
10 # (at your option) any later version.
11 #
12 # Koha is distributed in the hope that it will be useful, but
13 # WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU General Public License for more details.
16 #
17 # You should have received a copy of the GNU General Public License
18 # along with Koha; if not, see <http://www.gnu.org/licenses>.
19
20 use strict;
21 use warnings;
22
23 use MIME::Lite;
24 use Mail::Sendmail;
25
26 use C4::Koha qw(GetAuthorisedValueByCode);
27 use C4::Members;
28 use C4::Members::Attributes qw(GetBorrowerAttributes);
29 use C4::Branch;
30 use C4::Log;
31 use C4::SMS;
32 use C4::Debug;
33 use Koha::DateUtils;
34 use Date::Calc qw( Add_Delta_Days );
35 use Encode;
36 use Carp;
37 use Koha::Email;
38 use Koha::DateUtils qw( format_sqldatetime );
39
40 use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS);
41
42 BEGIN {
43     require Exporter;
44     # set the version for version checking
45     $VERSION = 3.07.00.049;
46     @ISA = qw(Exporter);
47     @EXPORT = qw(
48         &GetLetters &GetLettersAvailableForALibrary &GetLetterTemplates &DelLetter &GetPreparedLetter &GetWrappedLetter &addalert &getalert &delalert &findrelatedto &SendAlerts &GetPrintMessages &GetMessageTransportTypes
49     );
50 }
51
52 =head1 NAME
53
54 C4::Letters - Give functions for Letters management
55
56 =head1 SYNOPSIS
57
58   use C4::Letters;
59
60 =head1 DESCRIPTION
61
62   "Letters" is the tool used in Koha to manage informations sent to the patrons and/or the library. This include some cron jobs like
63   late issues, as well as other tasks like sending a mail to users that have subscribed to a "serial issue alert" (= being warned every time a new issue has arrived at the library)
64
65   Letters are managed through "alerts" sent by Koha on some events. All "alert" related functions are in this module too.
66
67 =head2 GetLetters([$module])
68
69   $letters = &GetLetters($module);
70   returns informations about letters.
71   if needed, $module filters for letters given module
72
73 =cut
74
75 sub GetLetters {
76     my ($filters) = @_;
77     my $module    = $filters->{module};
78     my $code      = $filters->{code};
79     my $branchcode = $filters->{branchcode};
80     my $dbh       = C4::Context->dbh;
81     my $letters   = $dbh->selectall_arrayref(
82         q|
83             SELECT module, code, branchcode, name
84             FROM letter
85             WHERE 1
86         |
87           . ( $module ? q| AND module = ?| : q|| )
88           . ( $code   ? q| AND code = ?|   : q|| )
89           . ( defined $branchcode   ? q| AND branchcode = ?|   : q|| )
90           . q| GROUP BY code ORDER BY name|, { Slice => {} }
91         , ( $module ? $module : () )
92         , ( $code ? $code : () )
93         , ( defined $branchcode ? $branchcode : () )
94     );
95
96     return $letters;
97 }
98
99 =head2 GetLetterTemplates
100
101     my $letter_templates = GetLetterTemplates(
102         {
103             module => 'circulation',
104             code => 'my code',
105             branchcode => 'CPL', # '' for default,
106         }
107     );
108
109     Return a hashref of letter templates.
110     The key will be the message transport type.
111
112 =cut
113
114 sub GetLetterTemplates {
115     my ( $params ) = @_;
116
117     my $module    = $params->{module};
118     my $code      = $params->{code};
119     my $branchcode = $params->{branchcode} // '';
120     my $dbh       = C4::Context->dbh;
121     my $letters   = $dbh->selectall_hashref(
122         q|
123             SELECT module, code, branchcode, name, is_html, title, content, message_transport_type
124             FROM letter
125             WHERE module = ?
126             AND code = ?
127             and branchcode = ?
128         |
129         , 'message_transport_type'
130         , undef
131         , $module, $code, $branchcode
132     );
133
134     return $letters;
135 }
136
137 =head2 GetLettersAvailableForALibrary
138
139     my $letters = GetLettersAvailableForALibrary(
140         {
141             branchcode => 'CPL', # '' for default
142             module => 'circulation',
143         }
144     );
145
146     Return an arrayref of letters, sorted by name.
147     If a specific letter exist for the given branchcode, it will be retrieve.
148     Otherwise the default letter will be.
149
150 =cut
151
152 sub GetLettersAvailableForALibrary {
153     my ($filters)  = @_;
154     my $branchcode = $filters->{branchcode};
155     my $module     = $filters->{module};
156
157     croak "module should be provided" unless $module;
158
159     my $dbh             = C4::Context->dbh;
160     my $default_letters = $dbh->selectall_arrayref(
161         q|
162             SELECT module, code, branchcode, name
163             FROM letter
164             WHERE 1
165         |
166           . q| AND branchcode = ''|
167           . ( $module ? q| AND module = ?| : q|| )
168           . q| ORDER BY name|, { Slice => {} }
169         , ( $module ? $module : () )
170     );
171
172     my $specific_letters;
173     if ($branchcode) {
174         $specific_letters = $dbh->selectall_arrayref(
175             q|
176                 SELECT module, code, branchcode, name
177                 FROM letter
178                 WHERE 1
179             |
180               . q| AND branchcode = ?|
181               . ( $module ? q| AND module = ?| : q|| )
182               . q| ORDER BY name|, { Slice => {} }
183             , $branchcode
184             , ( $module ? $module : () )
185         );
186     }
187
188     my %letters;
189     for my $l (@$default_letters) {
190         $letters{ $l->{code} } = $l;
191     }
192     for my $l (@$specific_letters) {
193         # Overwrite the default letter with the specific one.
194         $letters{ $l->{code} } = $l;
195     }
196
197     return [ map { $letters{$_} }
198           sort { $letters{$a}->{name} cmp $letters{$b}->{name} }
199           keys %letters ];
200
201 }
202
203 # FIXME: using our here means that a Plack server will need to be
204 #        restarted fairly regularly when working with this routine.
205 #        A better option would be to use Koha::Cache and use a cache
206 #        that actually works in a persistent environment, but as a
207 #        short-term fix, our will work.
208 our %letter;
209 sub getletter {
210     my ( $module, $code, $branchcode, $message_transport_type ) = @_;
211     $message_transport_type //= '%';
212
213     if ( C4::Context->preference('IndependentBranches')
214             and $branchcode
215             and C4::Context->userenv ) {
216
217         $branchcode = C4::Context->userenv->{'branch'};
218     }
219     $branchcode //= '';
220
221     if ( my $l = $letter{$module}{$code}{$branchcode}{$message_transport_type} ) {
222         return { %$l }; # deep copy
223     }
224
225     my $dbh = C4::Context->dbh;
226     my $sth = $dbh->prepare(q{
227         SELECT *
228         FROM letter
229         WHERE module=? AND code=? AND (branchcode = ? OR branchcode = '')
230         AND message_transport_type LIKE ?
231         ORDER BY branchcode DESC LIMIT 1
232     });
233     $sth->execute( $module, $code, $branchcode, $message_transport_type );
234     my $line = $sth->fetchrow_hashref
235       or return;
236     $line->{'content-type'} = 'text/html; charset="UTF-8"' if $line->{is_html};
237     $letter{$module}{$code}{$branchcode}{$message_transport_type} = $line;
238     return { %$line };
239 }
240
241
242 =head2 DelLetter
243
244     DelLetter(
245         {
246             branchcode => 'CPL',
247             module => 'circulation',
248             code => 'my code',
249             [ mtt => 'email', ]
250         }
251     );
252
253     Delete the letter. The mtt parameter is facultative.
254     If not given, all templates mathing the other parameters will be removed.
255
256 =cut
257
258 sub DelLetter {
259     my ($params)   = @_;
260     my $branchcode = $params->{branchcode};
261     my $module     = $params->{module};
262     my $code       = $params->{code};
263     my $mtt        = $params->{mtt};
264     my $dbh        = C4::Context->dbh;
265     $dbh->do(q|
266         DELETE FROM letter
267         WHERE branchcode = ?
268           AND module = ?
269           AND code = ?
270     | . ( $mtt ? q| AND message_transport_type = ?| : q|| )
271     , undef, $branchcode, $module, $code, ( $mtt ? $mtt : () ) );
272 }
273
274 =head2 addalert ($borrowernumber, $type, $externalid)
275
276     parameters : 
277     - $borrowernumber : the number of the borrower subscribing to the alert
278     - $type : the type of alert.
279     - $externalid : the primary key of the object to put alert on. For issues, the alert is made on subscriptionid.
280     
281     create an alert and return the alertid (primary key)
282
283 =cut
284
285 sub addalert {
286     my ( $borrowernumber, $type, $externalid ) = @_;
287     my $dbh = C4::Context->dbh;
288     my $sth =
289       $dbh->prepare(
290         "insert into alert (borrowernumber, type, externalid) values (?,?,?)");
291     $sth->execute( $borrowernumber, $type, $externalid );
292
293     # get the alert number newly created and return it
294     my $alertid = $dbh->{'mysql_insertid'};
295     return $alertid;
296 }
297
298 =head2 delalert ($alertid)
299
300     parameters :
301     - alertid : the alert id
302     deletes the alert
303
304 =cut
305
306 sub delalert {
307     my $alertid = shift or die "delalert() called without valid argument (alertid)";    # it's gonna die anyway.
308     $debug and warn "delalert: deleting alertid $alertid";
309     my $sth = C4::Context->dbh->prepare("delete from alert where alertid=?");
310     $sth->execute($alertid);
311 }
312
313 =head2 getalert ([$borrowernumber], [$type], [$externalid])
314
315     parameters :
316     - $borrowernumber : the number of the borrower subscribing to the alert
317     - $type : the type of alert.
318     - $externalid : the primary key of the object to put alert on. For issues, the alert is made on subscriptionid.
319     all parameters NON mandatory. If a parameter is omitted, the query is done without the corresponding parameter. For example, without $externalid, returns all alerts for a borrower on a topic.
320
321 =cut
322
323 sub getalert {
324     my ( $borrowernumber, $type, $externalid ) = @_;
325     my $dbh   = C4::Context->dbh;
326     my $query = "SELECT a.*, b.branchcode FROM alert a JOIN borrowers b USING(borrowernumber) WHERE";
327     my @bind;
328     if ($borrowernumber and $borrowernumber =~ /^\d+$/) {
329         $query .= " borrowernumber=? AND ";
330         push @bind, $borrowernumber;
331     }
332     if ($type) {
333         $query .= " type=? AND ";
334         push @bind, $type;
335     }
336     if ($externalid) {
337         $query .= " externalid=? AND ";
338         push @bind, $externalid;
339     }
340     $query =~ s/ AND $//;
341     my $sth = $dbh->prepare($query);
342     $sth->execute(@bind);
343     return $sth->fetchall_arrayref({});
344 }
345
346 =head2 findrelatedto($type, $externalid)
347
348     parameters :
349     - $type : the type of alert
350     - $externalid : the id of the "object" to query
351
352     In the table alert, a "id" is stored in the externalid field. This "id" is related to another table, depending on the type of the alert.
353     When type=issue, the id is related to a subscriptionid and this sub returns the name of the biblio.
354
355 =cut
356     
357 # outmoded POD:
358 # When type=virtual, the id is related to a virtual shelf and this sub returns the name of the sub
359
360 sub findrelatedto {
361     my $type       = shift or return;
362     my $externalid = shift or return;
363     my $q = ($type eq 'issue'   ) ?
364 "select title as result from subscription left join biblio on subscription.biblionumber=biblio.biblionumber where subscriptionid=?" :
365             ($type eq 'borrower') ?
366 "select concat(firstname,' ',surname) from borrowers where borrowernumber=?" : undef;
367     unless ($q) {
368         warn "findrelatedto(): Illegal type '$type'";
369         return;
370     }
371     my $sth = C4::Context->dbh->prepare($q);
372     $sth->execute($externalid);
373     my ($result) = $sth->fetchrow;
374     return $result;
375 }
376
377 =head2 SendAlerts
378
379     parameters :
380     - $type : the type of alert
381     - $externalid : the id of the "object" to query
382     - $letter_code : the letter to send.
383
384     send an alert to all borrowers having put an alert on a given subject.
385
386 =cut
387
388 sub SendAlerts {
389     my ( $type, $externalid, $letter_code ) = @_;
390     my $dbh = C4::Context->dbh;
391     if ( $type eq 'issue' ) {
392
393         # prepare the letter...
394         # search the subscriptionid
395         my $sth =
396           $dbh->prepare(
397             "SELECT subscriptionid FROM serial WHERE serialid=?");
398         $sth->execute($externalid);
399         my ($subscriptionid) = $sth->fetchrow
400           or warn( "No subscription for '$externalid'" ),
401              return;
402
403         # search the biblionumber
404         $sth =
405           $dbh->prepare(
406             "SELECT biblionumber FROM subscription WHERE subscriptionid=?");
407         $sth->execute($subscriptionid);
408         my ($biblionumber) = $sth->fetchrow
409           or warn( "No biblionumber for '$subscriptionid'" ),
410              return;
411
412         my %letter;
413         # find the list of borrowers to alert
414         my $alerts = getalert( '', 'issue', $subscriptionid );
415         foreach (@$alerts) {
416             my $borinfo = C4::Members::GetMember('borrowernumber' => $_->{'borrowernumber'});
417             my $email = $borinfo->{email} or next;
418
419 #                    warn "sending issues...";
420             my $userenv = C4::Context->userenv;
421             my $branchdetails = GetBranchDetail($_->{'branchcode'});
422             my $letter = GetPreparedLetter (
423                 module => 'serial',
424                 letter_code => $letter_code,
425                 branchcode => $userenv->{branch},
426                 tables => {
427                     'branches'    => $_->{branchcode},
428                     'biblio'      => $biblionumber,
429                     'biblioitems' => $biblionumber,
430                     'borrowers'   => $borinfo,
431                     'subscription' => $subscriptionid,
432                     'serial' => $externalid,
433                 },
434                 want_librarian => 1,
435             ) or return;
436
437             # ... then send mail
438             my $message = Koha::Email->new();
439             my %mail = $message->create_message_headers(
440                 {
441                     to      => $email,
442                     from    => $branchdetails->{'branchemail'},
443                     replyto => $branchdetails->{'branchreplyto'},
444                     sender  => $branchdetails->{'branchreturnpath'},
445                     subject => Encode::encode( "UTF-8", "" . $letter->{title} ),
446                     message => $letter->{'is_html'}
447                                 ? _wrap_html( Encode::encode( "UTF-8", $letter->{'content'} ),
448                                               Encode::encode( "UTF-8", "" . $letter->{'title'} ))
449                                 : Encode::encode( "UTF-8", "" . $letter->{'content'} ),
450                     contenttype => $letter->{'is_html'}
451                                     ? 'text/html; charset="utf-8"'
452                                     : 'text/plain; charset="utf-8"',
453                 }
454             );
455             sendmail(%mail) or carp $Mail::Sendmail::error;
456         }
457     }
458     elsif ( $type eq 'claimacquisition' or $type eq 'claimissues' ) {
459
460         # prepare the letter...
461         # search the biblionumber
462         my $strsth =  $type eq 'claimacquisition'
463             ? qq{
464             SELECT aqorders.*,aqbasket.*,biblio.*,biblioitems.*
465             FROM aqorders
466             LEFT JOIN aqbasket ON aqbasket.basketno=aqorders.basketno
467             LEFT JOIN biblio ON aqorders.biblionumber=biblio.biblionumber
468             LEFT JOIN biblioitems ON aqorders.biblionumber=biblioitems.biblionumber
469             WHERE aqorders.ordernumber IN (
470             }
471             : qq{
472             SELECT serial.*,subscription.*, biblio.*, aqbooksellers.*,
473             aqbooksellers.id AS booksellerid
474             FROM serial
475             LEFT JOIN subscription ON serial.subscriptionid=subscription.subscriptionid
476             LEFT JOIN biblio ON serial.biblionumber=biblio.biblionumber
477             LEFT JOIN aqbooksellers ON subscription.aqbooksellerid=aqbooksellers.id
478             WHERE serial.serialid IN (
479             };
480
481         if (!@$externalid){
482             carp "No Order seleted";
483             return { error => "no_order_seleted" };
484         }
485
486         $strsth .= join( ",", @$externalid ) . ")";
487         my $sthorders = $dbh->prepare($strsth);
488         $sthorders->execute;
489         my $dataorders = $sthorders->fetchall_arrayref( {} );
490
491         my $sthbookseller =
492           $dbh->prepare("select * from aqbooksellers where id=?");
493         $sthbookseller->execute( $dataorders->[0]->{booksellerid} );
494         my $databookseller = $sthbookseller->fetchrow_hashref;
495         my $addressee =  $type eq 'claimacquisition' ? 'acqprimary' : 'serialsprimary';
496         my $sthcontact =
497           $dbh->prepare("SELECT * FROM aqcontacts WHERE booksellerid=? AND $type=1 ORDER BY $addressee DESC");
498         $sthcontact->execute( $dataorders->[0]->{booksellerid} );
499         my $datacontact = $sthcontact->fetchrow_hashref;
500
501         my @email;
502         my @cc;
503         push @email, $databookseller->{bookselleremail} if $databookseller->{bookselleremail};
504         push @email, $datacontact->{email}           if ( $datacontact && $datacontact->{email} );
505         unless (@email) {
506             warn "Bookseller $dataorders->[0]->{booksellerid} without emails";
507             return { error => "no_email" };
508         }
509         my $addlcontact;
510         while ($addlcontact = $sthcontact->fetchrow_hashref) {
511             push @cc, $addlcontact->{email} if ( $addlcontact && $addlcontact->{email} );
512         }
513
514         my $userenv = C4::Context->userenv;
515         my $letter = GetPreparedLetter (
516             module => $type,
517             letter_code => $letter_code,
518             branchcode => $userenv->{branch},
519             tables => {
520                 'branches'    => $userenv->{branch},
521                 'aqbooksellers' => $databookseller,
522                 'aqcontacts'    => $datacontact,
523             },
524             repeat => $dataorders,
525             want_librarian => 1,
526         ) or return;
527
528         # Remove the order tag
529         $letter->{content} =~ s/<order>(.*?)<\/order>/$1/gxms;
530
531         # ... then send mail
532         my %mail = (
533             To => join( ',', @email),
534             Cc             => join( ',', @cc),
535             From           => $userenv->{emailaddress},
536             Subject        => Encode::encode( "UTF-8", "" . $letter->{title} ),
537             Message => $letter->{'is_html'}
538                             ? _wrap_html( Encode::encode( "UTF-8", $letter->{'content'} ),
539                                           Encode::encode( "UTF-8", "" . $letter->{'title'} ))
540                             : Encode::encode( "UTF-8", "" . $letter->{'content'} ),
541             'Content-Type' => $letter->{'is_html'}
542                                 ? 'text/html; charset="utf-8"'
543                                 : 'text/plain; charset="utf-8"',
544         );
545
546         $mail{'Reply-to'} = C4::Context->preference('ReplytoDefault')
547           if C4::Context->preference('ReplytoDefault');
548         $mail{'Sender'} = C4::Context->preference('ReturnpathDefault')
549           if C4::Context->preference('ReturnpathDefault');
550
551         unless ( sendmail(%mail) ) {
552             carp $Mail::Sendmail::error;
553             return { error => $Mail::Sendmail::error };
554         }
555
556         logaction(
557             "ACQUISITION",
558             $type eq 'claimissues' ? "CLAIM ISSUE" : "ACQUISITION CLAIM",
559             undef,
560             "To="
561                 . join( ',', @email )
562                 . " Title="
563                 . $letter->{title}
564                 . " Content="
565                 . $letter->{content}
566         ) if C4::Context->preference("LetterLog");
567     }
568    # send an "account details" notice to a newly created user
569     elsif ( $type eq 'members' ) {
570         my $branchdetails = GetBranchDetail($externalid->{'branchcode'});
571         my $letter = GetPreparedLetter (
572             module => 'members',
573             letter_code => $letter_code,
574             branchcode => $externalid->{'branchcode'},
575             tables => {
576                 'branches'    => $branchdetails,
577                 'borrowers' => $externalid->{'borrowernumber'},
578             },
579             substitute => { 'borrowers.password' => $externalid->{'password'} },
580             want_librarian => 1,
581         ) or return;
582         return { error => "no_email" } unless $externalid->{'emailaddr'};
583         my $email = Koha::Email->new();
584         my %mail  = $email->create_message_headers(
585             {
586                 to      => $externalid->{'emailaddr'},
587                 from    => $branchdetails->{'branchemail'},
588                 replyto => $branchdetails->{'branchreplyto'},
589                 sender  => $branchdetails->{'branchreturnpath'},
590                 subject => Encode::encode( "UTF-8", "" . $letter->{'title'} ),
591                 message => $letter->{'is_html'}
592                             ? _wrap_html( Encode::encode( "UTF-8", $letter->{'content'} ),
593                                           Encode::encode( "UTF-8", "" . $letter->{'title'}  ) )
594                             : Encode::encode( "UTF-8", "" . $letter->{'content'} ),
595                 contenttype => $letter->{'is_html'}
596                                 ? 'text/html; charset="utf-8"'
597                                 : 'text/plain; charset="utf-8"',
598             }
599         );
600         sendmail(%mail) or carp $Mail::Sendmail::error;
601     }
602 }
603
604 =head2 GetPreparedLetter( %params )
605
606     %params hash:
607       module => letter module, mandatory
608       letter_code => letter code, mandatory
609       branchcode => for letter selection, if missing default system letter taken
610       tables => a hashref with table names as keys. Values are either:
611         - a scalar - primary key value
612         - an arrayref - primary key values
613         - a hashref - full record
614       substitute => custom substitution key/value pairs
615       repeat => records to be substituted on consecutive lines:
616         - an arrayref - tries to guess what needs substituting by
617           taking remaining << >> tokensr; not recommended
618         - a hashref token => @tables - replaces <token> << >> << >> </token>
619           subtemplate for each @tables row; table is a hashref as above
620       want_librarian => boolean,  if set to true triggers librarian details
621         substitution from the userenv
622     Return value:
623       letter fields hashref (title & content useful)
624
625 =cut
626
627 sub GetPreparedLetter {
628     my %params = @_;
629
630     my $module      = $params{module} or croak "No module";
631     my $letter_code = $params{letter_code} or croak "No letter_code";
632     my $branchcode  = $params{branchcode} || '';
633     my $mtt         = $params{message_transport_type} || 'email';
634
635     my $letter = getletter( $module, $letter_code, $branchcode, $mtt )
636         or warn( "No $module $letter_code letter transported by " . $mtt ),
637             return;
638
639     my $tables = $params{tables};
640     my $substitute = $params{substitute};
641     my $repeat = $params{repeat};
642     $tables || $substitute || $repeat
643       or carp( "ERROR: nothing to substitute - both 'tables' and 'substitute' are empty" ),
644          return;
645     my $want_librarian = $params{want_librarian};
646
647     if ($substitute) {
648         while ( my ($token, $val) = each %$substitute ) {
649             if ( $token eq 'items.content' ) {
650                 $val =~ s|\n|<br/>|g if $letter->{is_html};
651             }
652
653             $letter->{title} =~ s/<<$token>>/$val/g;
654             $letter->{content} =~ s/<<$token>>/$val/g;
655        }
656     }
657
658     my $OPACBaseURL = C4::Context->preference('OPACBaseURL');
659     $letter->{content} =~ s/<<OPACBaseURL>>/$OPACBaseURL/go;
660
661     if ($want_librarian) {
662         # parsing librarian name
663         my $userenv = C4::Context->userenv;
664         $letter->{content} =~ s/<<LibrarianFirstname>>/$userenv->{firstname}/go;
665         $letter->{content} =~ s/<<LibrarianSurname>>/$userenv->{surname}/go;
666         $letter->{content} =~ s/<<LibrarianEmailaddress>>/$userenv->{emailaddress}/go;
667     }
668
669     my ($repeat_no_enclosing_tags, $repeat_enclosing_tags);
670
671     if ($repeat) {
672         if (ref ($repeat) eq 'ARRAY' ) {
673             $repeat_no_enclosing_tags = $repeat;
674         } else {
675             $repeat_enclosing_tags = $repeat;
676         }
677     }
678
679     if ($repeat_enclosing_tags) {
680         while ( my ($tag, $tag_tables) = each %$repeat_enclosing_tags ) {
681             if ( $letter->{content} =~ m!<$tag>(.*)</$tag>!s ) {
682                 my $subcontent = $1;
683                 my @lines = map {
684                     my %subletter = ( title => '', content => $subcontent );
685                     _substitute_tables( \%subletter, $_ );
686                     $subletter{content};
687                 } @$tag_tables;
688                 $letter->{content} =~ s!<$tag>.*</$tag>!join( "\n", @lines )!se;
689             }
690         }
691     }
692
693     if ($tables) {
694         _substitute_tables( $letter, $tables );
695     }
696
697     if ($repeat_no_enclosing_tags) {
698         if ( $letter->{content} =~ m/[^\n]*<<.*>>[^\n]*/so ) {
699             my $line = $&;
700             my $i = 1;
701             my @lines = map {
702                 my $c = $line;
703                 $c =~ s/<<count>>/$i/go;
704                 foreach my $field ( keys %{$_} ) {
705                     $c =~ s/(<<[^\.]+.$field>>)/$_->{$field}/;
706                 }
707                 $i++;
708                 $c;
709             } @$repeat_no_enclosing_tags;
710
711             my $replaceby = join( "\n", @lines );
712             $letter->{content} =~ s/\Q$line\E/$replaceby/s;
713         }
714     }
715
716     $letter->{content} =~ s/<<\S*>>//go; #remove any stragglers
717 #   $letter->{content} =~ s/<<[^>]*>>//go;
718
719     return $letter;
720 }
721
722 sub _substitute_tables {
723     my ( $letter, $tables ) = @_;
724     while ( my ($table, $param) = each %$tables ) {
725         next unless $param;
726
727         my $ref = ref $param;
728
729         my $values;
730         if ($ref && $ref eq 'HASH') {
731             $values = $param;
732         }
733         else {
734             my $sth = _parseletter_sth($table);
735             unless ($sth) {
736                 warn "_parseletter_sth('$table') failed to return a valid sth.  No substitution will be done for that table.";
737                 return;
738             }
739             $sth->execute( $ref ? @$param : $param );
740
741             $values = $sth->fetchrow_hashref;
742             $sth->finish();
743         }
744
745         _parseletter ( $letter, $table, $values );
746     }
747 }
748
749 sub _parseletter_sth {
750     my $table = shift;
751     my $sth;
752     unless ($table) {
753         carp "ERROR: _parseletter_sth() called without argument (table)";
754         return;
755     }
756     # NOTE: we used to check whether we had a statement handle cached in
757     #       a %handles module-level variable. This was a dumb move and
758     #       broke things for the rest of us. prepare_cached is a better
759     #       way to cache statement handles anyway.
760     my $query = 
761     ($table eq 'biblio'       ) ? "SELECT * FROM $table WHERE   biblionumber = ?"                                  :
762     ($table eq 'biblioitems'  ) ? "SELECT * FROM $table WHERE   biblionumber = ?"                                  :
763     ($table eq 'items'        ) ? "SELECT * FROM $table WHERE     itemnumber = ?"                                  :
764     ($table eq 'issues'       ) ? "SELECT * FROM $table WHERE     itemnumber = ?"                                  :
765     ($table eq 'old_issues'   ) ? "SELECT * FROM $table WHERE     itemnumber = ? ORDER BY timestamp DESC LIMIT 1"  :
766     ($table eq 'reserves'     ) ? "SELECT * FROM $table WHERE borrowernumber = ? and biblionumber = ?"             :
767     ($table eq 'borrowers'    ) ? "SELECT * FROM $table WHERE borrowernumber = ?"                                  :
768     ($table eq 'branches'     ) ? "SELECT * FROM $table WHERE     branchcode = ?"                                  :
769     ($table eq 'suggestions'  ) ? "SELECT * FROM $table WHERE   suggestionid = ?"                                  :
770     ($table eq 'aqbooksellers') ? "SELECT * FROM $table WHERE             id = ?"                                  :
771     ($table eq 'aqorders'     ) ? "SELECT * FROM $table WHERE    ordernumber = ?"                                  :
772     ($table eq 'opac_news'    ) ? "SELECT * FROM $table WHERE          idnew = ?"                                  :
773     ($table eq 'borrower_modifications') ? "SELECT * FROM $table WHERE verification_token = ?" :
774     ($table eq 'subscription') ? "SELECT * FROM $table WHERE subscriptionid = ?" :
775     ($table eq 'serial') ? "SELECT * FROM $table WHERE serialid = ?" :
776     undef ;
777     unless ($query) {
778         warn "ERROR: No _parseletter_sth query for table '$table'";
779         return;     # nothing to get
780     }
781     unless ($sth = C4::Context->dbh->prepare_cached($query)) {
782         warn "ERROR: Failed to prepare query: '$query'";
783         return;
784     }
785     return $sth;    # now cache is populated for that $table
786 }
787
788 =head2 _parseletter($letter, $table, $values)
789
790     parameters :
791     - $letter : a hash to letter fields (title & content useful)
792     - $table : the Koha table to parse.
793     - $values_in : table record hashref
794     parse all fields from a table, and replace values in title & content with the appropriate value
795     (not exported sub, used only internally)
796
797 =cut
798
799 sub _parseletter {
800     my ( $letter, $table, $values_in ) = @_;
801
802     # Work on a local copy of $values_in (passed by reference) to avoid side effects
803     # in callers ( by changing / formatting values )
804     my $values = { %$values_in };
805
806     if ( $table eq 'borrowers' && $values->{'dateexpiry'} ){
807         $values->{'dateexpiry'} = format_sqldatetime( $values->{'dateexpiry'} );
808     }
809
810     if ( $table eq 'reserves' && $values->{'waitingdate'} ) {
811         my @waitingdate = split /-/, $values->{'waitingdate'};
812
813         $values->{'expirationdate'} = '';
814         if ( C4::Context->preference('ReservesMaxPickUpDelay') ) {
815             my $dt = dt_from_string();
816             $dt->add( days => C4::Context->preference('ReservesMaxPickUpDelay') );
817             $values->{'expirationdate'} = output_pref( { dt => $dt, dateonly => 1 } );
818         }
819
820         $values->{'waitingdate'} = output_pref({ dt => dt_from_string( $values->{'waitingdate'} ), dateonly => 1 });
821
822     }
823
824     if ($letter->{content} && $letter->{content} =~ /<<today>>/) {
825         my $todaysdate = output_pref( DateTime->now() );
826         $letter->{content} =~ s/<<today>>/$todaysdate/go;
827     }
828
829     while ( my ($field, $val) = each %$values ) {
830         $val =~ s/\p{P}$// if $val && $table=~/biblio/;
831             #BZ 9886: Assuming that we want to eliminate ISBD punctuation here
832             #Therefore adding the test on biblio. This includes biblioitems,
833             #but excludes items. Removed unneeded global and lookahead.
834
835         $val = GetAuthorisedValueByCode ('ROADTYPE', $val, 0) if $table=~/^borrowers$/ && $field=~/^streettype$/;
836
837         # Dates replacement
838         my $replacedby   = defined ($val) ? $val : '';
839         if (    $replacedby
840             and not $replacedby =~ m|0000-00-00|
841             and not $replacedby =~ m|9999-12-31|
842             and $replacedby =~ m|^\d{4}-\d{2}-\d{2}( \d{2}:\d{2}:\d{2})?$| )
843         {
844             # If the value is XXXX-YY-ZZ[ AA:BB:CC] we assume it is a date
845             my $dateonly = defined $1 ? 0 : 1; #$1 refers to the capture group wrapped in parentheses. In this case, that's the hours, minutes, seconds.
846             my $re_dateonly_filter = qr{ $field( \s* \| \s* dateonly\s*)?>> }xms;
847
848             for my $letter_field ( qw( title content ) ) {
849                 my $filter_string_used = q{};
850                 if ( $letter->{ $letter_field } =~ $re_dateonly_filter ) {
851                     # We overwrite $dateonly if the filter exists and we have a time in the datetime
852                     $filter_string_used = $1 || q{};
853                     $dateonly = $1 unless $dateonly;
854                 }
855                 eval {
856                     $replacedby = output_pref({ dt => dt_from_string( $replacedby ), dateonly => $dateonly });
857                 };
858
859                 if ( $letter->{ $letter_field } ) {
860                     $letter->{ $letter_field } =~ s/\Q<<$table.$field$filter_string_used>>\E/$replacedby/g;
861                     $letter->{ $letter_field } =~ s/\Q<<$field$filter_string_used>>\E/$replacedby/g;
862                 }
863             }
864         }
865         # Other fields replacement
866         else {
867             for my $letter_field ( qw( title content ) ) {
868                 if ( $letter->{ $letter_field } ) {
869                     $letter->{ $letter_field }   =~ s/<<$table.$field>>/$replacedby/g;
870                     $letter->{ $letter_field }   =~ s/<<$field>>/$replacedby/g;
871                 }
872             }
873         }
874     }
875
876     if ($table eq 'borrowers' && $letter->{content}) {
877         if ( my $attributes = GetBorrowerAttributes($values->{borrowernumber}) ) {
878             my %attr;
879             foreach (@$attributes) {
880                 my $code = $_->{code};
881                 my $val  = $_->{value_description} || $_->{value};
882                 $val =~ s/\p{P}(?=$)//g if $val;
883                 next unless $val gt '';
884                 $attr{$code} ||= [];
885                 push @{ $attr{$code} }, $val;
886             }
887             while ( my ($code, $val_ar) = each %attr ) {
888                 my $replacefield = "<<borrower-attribute:$code>>";
889                 my $replacedby   = join ',', @$val_ar;
890                 $letter->{content} =~ s/$replacefield/$replacedby/g;
891             }
892         }
893     }
894     return $letter;
895 }
896
897 =head2 EnqueueLetter
898
899   my $success = EnqueueLetter( { letter => $letter, 
900         borrowernumber => '12', message_transport_type => 'email' } )
901
902 places a letter in the message_queue database table, which will
903 eventually get processed (sent) by the process_message_queue.pl
904 cronjob when it calls SendQueuedMessages.
905
906 return message_id on success
907
908 =cut
909
910 sub EnqueueLetter {
911     my $params = shift or return;
912
913     return unless exists $params->{'letter'};
914 #   return unless exists $params->{'borrowernumber'};
915     return unless exists $params->{'message_transport_type'};
916
917     my $content = $params->{letter}->{content};
918     $content =~ s/\s+//g if(defined $content);
919     if ( not defined $content or $content eq '' ) {
920         warn "Trying to add an empty message to the message queue" if $debug;
921         return;
922     }
923
924     # If we have any attachments we should encode then into the body.
925     if ( $params->{'attachments'} ) {
926         $params->{'letter'} = _add_attachments(
927             {   letter      => $params->{'letter'},
928                 attachments => $params->{'attachments'},
929                 message     => MIME::Lite->new( Type => 'multipart/mixed' ),
930             }
931         );
932     }
933
934     my $dbh       = C4::Context->dbh();
935     my $statement = << 'ENDSQL';
936 INSERT INTO message_queue
937 ( borrowernumber, subject, content, metadata, letter_code, message_transport_type, status, time_queued, to_address, from_address, content_type )
938 VALUES
939 ( ?,              ?,       ?,       ?,        ?,           ?,                      ?,      NOW(),       ?,          ?,            ? )
940 ENDSQL
941
942     my $sth    = $dbh->prepare($statement);
943     my $result = $sth->execute(
944         $params->{'borrowernumber'},              # borrowernumber
945         $params->{'letter'}->{'title'},           # subject
946         $params->{'letter'}->{'content'},         # content
947         $params->{'letter'}->{'metadata'} || '',  # metadata
948         $params->{'letter'}->{'code'}     || '',  # letter_code
949         $params->{'message_transport_type'},      # message_transport_type
950         'pending',                                # status
951         $params->{'to_address'},                  # to_address
952         $params->{'from_address'},                # from_address
953         $params->{'letter'}->{'content-type'},    # content_type
954     );
955     return $dbh->last_insert_id(undef,undef,'message_queue', undef);
956 }
957
958 =head2 SendQueuedMessages ([$hashref]) 
959
960   my $sent = SendQueuedMessages( { verbose => 1 } );
961
962 sends all of the 'pending' items in the message queue.
963
964 returns number of messages sent.
965
966 =cut
967
968 sub SendQueuedMessages {
969     my $params = shift;
970
971     my $unsent_messages = _get_unsent_messages();
972     MESSAGE: foreach my $message ( @$unsent_messages ) {
973         # warn Data::Dumper->Dump( [ $message ], [ 'message' ] );
974         warn sprintf( 'sending %s message to patron: %s',
975                       $message->{'message_transport_type'},
976                       $message->{'borrowernumber'} || 'Admin' )
977           if $params->{'verbose'} or $debug;
978         # This is just begging for subclassing
979         next MESSAGE if ( lc($message->{'message_transport_type'}) eq 'rss' );
980         if ( lc( $message->{'message_transport_type'} ) eq 'email' ) {
981             _send_message_by_email( $message, $params->{'username'}, $params->{'password'}, $params->{'method'} );
982         }
983         elsif ( lc( $message->{'message_transport_type'} ) eq 'sms' ) {
984             _send_message_by_sms( $message );
985         }
986     }
987     return scalar( @$unsent_messages );
988 }
989
990 =head2 GetRSSMessages
991
992   my $message_list = GetRSSMessages( { limit => 10, borrowernumber => '14' } )
993
994 returns a listref of all queued RSS messages for a particular person.
995
996 =cut
997
998 sub GetRSSMessages {
999     my $params = shift;
1000
1001     return unless $params;
1002     return unless ref $params;
1003     return unless $params->{'borrowernumber'};
1004     
1005     return _get_unsent_messages( { message_transport_type => 'rss',
1006                                    limit                  => $params->{'limit'},
1007                                    borrowernumber         => $params->{'borrowernumber'}, } );
1008 }
1009
1010 =head2 GetPrintMessages
1011
1012   my $message_list = GetPrintMessages( { borrowernumber => $borrowernumber } )
1013
1014 Returns a arrayref of all queued print messages (optionally, for a particular
1015 person).
1016
1017 =cut
1018
1019 sub GetPrintMessages {
1020     my $params = shift || {};
1021     
1022     return _get_unsent_messages( { message_transport_type => 'print',
1023                                    borrowernumber         => $params->{'borrowernumber'},
1024                                  } );
1025 }
1026
1027 =head2 GetQueuedMessages ([$hashref])
1028
1029   my $messages = GetQueuedMessage( { borrowernumber => '123', limit => 20 } );
1030
1031 fetches messages out of the message queue.
1032
1033 returns:
1034 list of hashes, each has represents a message in the message queue.
1035
1036 =cut
1037
1038 sub GetQueuedMessages {
1039     my $params = shift;
1040
1041     my $dbh = C4::Context->dbh();
1042     my $statement = << 'ENDSQL';
1043 SELECT message_id, borrowernumber, subject, content, message_transport_type, status, time_queued
1044 FROM message_queue
1045 ENDSQL
1046
1047     my @query_params;
1048     my @whereclauses;
1049     if ( exists $params->{'borrowernumber'} ) {
1050         push @whereclauses, ' borrowernumber = ? ';
1051         push @query_params, $params->{'borrowernumber'};
1052     }
1053
1054     if ( @whereclauses ) {
1055         $statement .= ' WHERE ' . join( 'AND', @whereclauses );
1056     }
1057
1058     if ( defined $params->{'limit'} ) {
1059         $statement .= ' LIMIT ? ';
1060         push @query_params, $params->{'limit'};
1061     }
1062
1063     my $sth = $dbh->prepare( $statement );
1064     my $result = $sth->execute( @query_params );
1065     return $sth->fetchall_arrayref({});
1066 }
1067
1068 =head2 GetMessageTransportTypes
1069
1070   my @mtt = GetMessageTransportTypes();
1071
1072   returns an arrayref of transport types
1073
1074 =cut
1075
1076 sub GetMessageTransportTypes {
1077     my $dbh = C4::Context->dbh();
1078     my $mtts = $dbh->selectcol_arrayref("
1079         SELECT message_transport_type
1080         FROM message_transport_types
1081         ORDER BY message_transport_type
1082     ");
1083     return $mtts;
1084 }
1085
1086 =head2 _add_attachements
1087
1088 named parameters:
1089 letter - the standard letter hashref
1090 attachments - listref of attachments. each attachment is a hashref of:
1091   type - the mime type, like 'text/plain'
1092   content - the actual attachment
1093   filename - the name of the attachment.
1094 message - a MIME::Lite object to attach these to.
1095
1096 returns your letter object, with the content updated.
1097
1098 =cut
1099
1100 sub _add_attachments {
1101     my $params = shift;
1102
1103     my $letter = $params->{'letter'};
1104     my $attachments = $params->{'attachments'};
1105     return $letter unless @$attachments;
1106     my $message = $params->{'message'};
1107
1108     # First, we have to put the body in as the first attachment
1109     $message->attach(
1110         Type => $letter->{'content-type'} || 'TEXT',
1111         Data => $letter->{'is_html'}
1112             ? _wrap_html($letter->{'content'}, $letter->{'title'})
1113             : $letter->{'content'},
1114     );
1115
1116     foreach my $attachment ( @$attachments ) {
1117         $message->attach(
1118             Type     => $attachment->{'type'},
1119             Data     => $attachment->{'content'},
1120             Filename => $attachment->{'filename'},
1121         );
1122     }
1123     # we're forcing list context here to get the header, not the count back from grep.
1124     ( $letter->{'content-type'} ) = grep( /^Content-Type:/, split( /\n/, $params->{'message'}->header_as_string ) );
1125     $letter->{'content-type'} =~ s/^Content-Type:\s+//;
1126     $letter->{'content'} = $message->body_as_string;
1127
1128     return $letter;
1129
1130 }
1131
1132 sub _get_unsent_messages {
1133     my $params = shift;
1134
1135     my $dbh = C4::Context->dbh();
1136     my $statement = << 'ENDSQL';
1137 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, mq.letter_code
1138   FROM message_queue mq
1139   LEFT JOIN borrowers b ON b.borrowernumber = mq.borrowernumber
1140  WHERE status = ?
1141 ENDSQL
1142
1143     my @query_params = ('pending');
1144     if ( ref $params ) {
1145         if ( $params->{'message_transport_type'} ) {
1146             $statement .= ' AND message_transport_type = ? ';
1147             push @query_params, $params->{'message_transport_type'};
1148         }
1149         if ( $params->{'borrowernumber'} ) {
1150             $statement .= ' AND borrowernumber = ? ';
1151             push @query_params, $params->{'borrowernumber'};
1152         }
1153         if ( $params->{'limit'} ) {
1154             $statement .= ' limit ? ';
1155             push @query_params, $params->{'limit'};
1156         }
1157     }
1158
1159     $debug and warn "_get_unsent_messages SQL: $statement";
1160     $debug and warn "_get_unsent_messages params: " . join(',',@query_params);
1161     my $sth = $dbh->prepare( $statement );
1162     my $result = $sth->execute( @query_params );
1163     return $sth->fetchall_arrayref({});
1164 }
1165
1166 sub _send_message_by_email {
1167     my $message = shift or return;
1168     my ($username, $password, $method) = @_;
1169
1170     my $member = C4::Members::GetMember( 'borrowernumber' => $message->{'borrowernumber'} );
1171     my $to_address = $message->{'to_address'};
1172     unless ($to_address) {
1173         unless ($member) {
1174             warn "FAIL: No 'to_address' and INVALID borrowernumber ($message->{borrowernumber})";
1175             _set_message_status( { message_id => $message->{'message_id'},
1176                                    status     => 'failed' } );
1177             return;
1178         }
1179         $to_address = C4::Members::GetNoticeEmailAddress( $message->{'borrowernumber'} );
1180         unless ($to_address) {  
1181             # warn "FAIL: No 'to_address' and no email for " . ($member->{surname} ||'') . ", borrowernumber ($message->{borrowernumber})";
1182             # warning too verbose for this more common case?
1183             _set_message_status( { message_id => $message->{'message_id'},
1184                                    status     => 'failed' } );
1185             return;
1186         }
1187     }
1188
1189     my $utf8   = decode('MIME-Header', $message->{'subject'} );
1190     $message->{subject}= encode('MIME-Header', $utf8);
1191     my $subject = encode('UTF-8', $message->{'subject'});
1192     my $content = encode('UTF-8', $message->{'content'});
1193     my $content_type = $message->{'content_type'} || 'text/plain; charset="UTF-8"';
1194     my $is_html = $content_type =~ m/html/io;
1195     my $branch_email = undef;
1196     my $branch_replyto = undef;
1197     my $branch_returnpath = undef;
1198     if ($member){
1199         my $branchdetail = GetBranchDetail( $member->{'branchcode'} );
1200         $branch_email = $branchdetail->{'branchemail'};
1201         $branch_replyto = $branchdetail->{'branchreplyto'};
1202         $branch_returnpath = $branchdetail->{'branchreturnpath'};
1203     }
1204     my $email = Koha::Email->new();
1205     my %sendmail_params = $email->create_message_headers(
1206         {
1207             to      => $to_address,
1208             from    => $message->{'from_address'} || $branch_email,
1209             replyto => $branch_replyto,
1210             sender  => $branch_returnpath,
1211             subject => $subject,
1212             message => $is_html ? _wrap_html( $content, $subject ) : $content,
1213             contenttype => $content_type
1214         }
1215     );
1216
1217     $sendmail_params{'Auth'} = {user => $username, pass => $password, method => $method} if $username;
1218     if ( my $bcc = C4::Context->preference('OverdueNoticeBcc') ) {
1219        $sendmail_params{ Bcc } = $bcc;
1220     }
1221
1222     _update_message_to_address($message->{'message_id'},$to_address) unless $message->{to_address}; #if initial message address was empty, coming here means that a to address was found and queue should be updated
1223     if ( sendmail( %sendmail_params ) ) {
1224         _set_message_status( { message_id => $message->{'message_id'},
1225                 status     => 'sent' } );
1226         return 1;
1227     } else {
1228         _set_message_status( { message_id => $message->{'message_id'},
1229                 status     => 'failed' } );
1230         carp $Mail::Sendmail::error;
1231         return;
1232     }
1233 }
1234
1235 sub _wrap_html {
1236     my ($content, $title) = @_;
1237
1238     my $css = C4::Context->preference("NoticeCSS") || '';
1239     $css = qq{<link rel="stylesheet" type="text/css" href="$css">} if $css;
1240     return <<EOS;
1241 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
1242     "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
1243 <html lang="en" xml:lang="en" xmlns="http://www.w3.org/1999/xhtml">
1244 <head>
1245 <title>$title</title>
1246 <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
1247 $css
1248 </head>
1249 <body>
1250 $content
1251 </body>
1252 </html>
1253 EOS
1254 }
1255
1256 sub _is_duplicate {
1257     my ( $message ) = @_;
1258     my $dbh = C4::Context->dbh;
1259     my $count = $dbh->selectrow_array(q|
1260         SELECT COUNT(*)
1261         FROM message_queue
1262         WHERE message_transport_type = ?
1263         AND borrowernumber = ?
1264         AND letter_code = ?
1265         AND CAST(time_queued AS date) = CAST(NOW() AS date)
1266         AND status="sent"
1267         AND content = ?
1268     |, {}, $message->{message_transport_type}, $message->{borrowernumber}, $message->{letter_code}, $message->{content} );
1269     return $count;
1270 }
1271
1272 sub _send_message_by_sms {
1273     my $message = shift or return;
1274     my $member = C4::Members::GetMember( 'borrowernumber' => $message->{'borrowernumber'} );
1275
1276     unless ( $member->{smsalertnumber} ) {
1277         _set_message_status( { message_id => $message->{'message_id'},
1278                                status     => 'failed' } );
1279         return;
1280     }
1281
1282     if ( _is_duplicate( $message ) ) {
1283         _set_message_status( { message_id => $message->{'message_id'},
1284                                status     => 'failed' } );
1285         return;
1286     }
1287
1288     my $success = C4::SMS->send_sms( { destination => $member->{'smsalertnumber'},
1289                                        message     => $message->{'content'},
1290                                      } );
1291     _set_message_status( { message_id => $message->{'message_id'},
1292                            status     => ($success ? 'sent' : 'failed') } );
1293     return $success;
1294 }
1295
1296 sub _update_message_to_address {
1297     my ($id, $to)= @_;
1298     my $dbh = C4::Context->dbh();
1299     $dbh->do('UPDATE message_queue SET to_address=? WHERE message_id=?',undef,($to,$id));
1300 }
1301
1302 sub _set_message_status {
1303     my $params = shift or return;
1304
1305     foreach my $required_parameter ( qw( message_id status ) ) {
1306         return unless exists $params->{ $required_parameter };
1307     }
1308
1309     my $dbh = C4::Context->dbh();
1310     my $statement = 'UPDATE message_queue SET status= ? WHERE message_id = ?';
1311     my $sth = $dbh->prepare( $statement );
1312     my $result = $sth->execute( $params->{'status'},
1313                                 $params->{'message_id'} );
1314     return $result;
1315 }
1316
1317
1318 1;
1319 __END__