Bug 14591: Update other calls to AddReturn
[koha.git] / C4 / Letters.pm
index 419f272..cbe6559 100644 (file)
@@ -36,7 +36,10 @@ use Koha::DateUtils;
 use Koha::SMS::Providers;
 
 use Koha::Email;
+use Koha::Notice::Messages;
 use Koha::DateUtils qw( format_sqldatetime dt_from_string );
+use Koha::Patrons;
+use Koha::Subscriptions;
 
 use vars qw(@ISA @EXPORT @EXPORT_OK %EXPORT_TAGS);
 
@@ -44,7 +47,7 @@ BEGIN {
     require Exporter;
     @ISA = qw(Exporter);
     @EXPORT = qw(
-        &GetLetters &GetLettersAvailableForALibrary &GetLetterTemplates &DelLetter &GetPreparedLetter &GetWrappedLetter &addalert &getalert &delalert &findrelatedto &SendAlerts &GetPrintMessages &GetMessageTransportTypes
+        &GetLetters &GetLettersAvailableForALibrary &GetLetterTemplates &DelLetter &GetPreparedLetter &GetWrappedLetter &SendAlerts &GetPrintMessages &GetMessageTransportTypes
     );
 }
 
@@ -69,6 +72,9 @@ C4::Letters - Give functions for Letters management
   returns informations about letters.
   if needed, $module filters for letters given module
 
+  DEPRECATED - You must use Koha::Notice::Templates instead
+  The group by clause is confusing and can lead to issues
+
 =cut
 
 sub GetLetters {
@@ -79,14 +85,14 @@ sub GetLetters {
     my $dbh       = C4::Context->dbh;
     my $letters   = $dbh->selectall_arrayref(
         q|
-            SELECT module, code, branchcode, name
+            SELECT code, module, name
             FROM letter
             WHERE 1
         |
           . ( $module ? q| AND module = ?| : q|| )
           . ( $code   ? q| AND code = ?|   : q|| )
           . ( defined $branchcode   ? q| AND branchcode = ?|   : q|| )
-          . q| GROUP BY code ORDER BY name|, { Slice => {} }
+          . q| GROUP BY code, module, name ORDER BY name|, { Slice => {} }
         , ( $module ? $module : () )
         , ( $code ? $code : () )
         , ( defined $branchcode ? $branchcode : () )
@@ -106,7 +112,6 @@ sub GetLetters {
     );
 
     Return a hashref of letter templates.
-    The key will be the message transport type.
 
 =cut
 
@@ -117,16 +122,15 @@ sub GetLetterTemplates {
     my $code      = $params->{code};
     my $branchcode = $params->{branchcode} // '';
     my $dbh       = C4::Context->dbh;
-    my $letters   = $dbh->selectall_hashref(
+    my $letters   = $dbh->selectall_arrayref(
         q|
-            SELECT module, code, branchcode, name, is_html, title, content, message_transport_type
+            SELECT module, code, branchcode, name, is_html, title, content, message_transport_type, lang
             FROM letter
             WHERE module = ?
             AND code = ?
             and branchcode = ?
         |
-        , 'message_transport_type'
-        , undef
+        , { Slice => {} }
         , $module, $code, $branchcode
     );
 
@@ -200,14 +204,14 @@ sub GetLettersAvailableForALibrary {
 }
 
 sub getletter {
-    my ( $module, $code, $branchcode, $message_transport_type ) = @_;
+    my ( $module, $code, $branchcode, $message_transport_type, $lang) = @_;
     $message_transport_type //= '%';
+    $lang = 'default' unless( $lang && C4::Context->preference('TranslateNotices') );
 
-    if ( C4::Context->preference('IndependentBranches')
-            and $branchcode
-            and C4::Context->userenv ) {
 
-        $branchcode = C4::Context->userenv->{'branch'};
+    my $only_my_library = C4::Context->only_my_library;
+    if ( $only_my_library and $branchcode ) {
+        $branchcode = C4::Context::mybranch();
     }
     $branchcode //= '';
 
@@ -217,9 +221,10 @@ sub getletter {
         FROM letter
         WHERE module=? AND code=? AND (branchcode = ? OR branchcode = '')
         AND message_transport_type LIKE ?
+        AND lang =?
         ORDER BY branchcode DESC LIMIT 1
     });
-    $sth->execute( $module, $code, $branchcode, $message_transport_type );
+    $sth->execute( $module, $code, $branchcode, $message_transport_type, $lang );
     my $line = $sth->fetchrow_hashref
       or return;
     $line->{'content-type'} = 'text/html; charset="UTF-8"' if $line->{is_html};
@@ -249,116 +254,17 @@ sub DelLetter {
     my $module     = $params->{module};
     my $code       = $params->{code};
     my $mtt        = $params->{mtt};
+    my $lang       = $params->{lang};
     my $dbh        = C4::Context->dbh;
     $dbh->do(q|
         DELETE FROM letter
         WHERE branchcode = ?
           AND module = ?
           AND code = ?
-    | . ( $mtt ? q| AND message_transport_type = ?| : q|| )
-    , undef, $branchcode, $module, $code, ( $mtt ? $mtt : () ) );
-}
-
-=head2 addalert ($borrowernumber, $type, $externalid)
-
-    parameters : 
-    - $borrowernumber : the number of the borrower subscribing to the alert
-    - $type : the type of alert.
-    - $externalid : the primary key of the object to put alert on. For issues, the alert is made on subscriptionid.
-    
-    create an alert and return the alertid (primary key)
-
-=cut
-
-sub addalert {
-    my ( $borrowernumber, $type, $externalid ) = @_;
-    my $dbh = C4::Context->dbh;
-    my $sth =
-      $dbh->prepare(
-        "insert into alert (borrowernumber, type, externalid) values (?,?,?)");
-    $sth->execute( $borrowernumber, $type, $externalid );
-
-    # get the alert number newly created and return it
-    my $alertid = $dbh->{'mysql_insertid'};
-    return $alertid;
-}
-
-=head2 delalert ($alertid)
-
-    parameters :
-    - alertid : the alert id
-    deletes the alert
-
-=cut
-
-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=?");
-    $sth->execute($alertid);
-}
-
-=head2 getalert ([$borrowernumber], [$type], [$externalid])
-
-    parameters :
-    - $borrowernumber : the number of the borrower subscribing to the alert
-    - $type : the type of alert.
-    - $externalid : the primary key of the object to put alert on. For issues, the alert is made on subscriptionid.
-    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.
-
-=cut
-
-sub getalert {
-    my ( $borrowernumber, $type, $externalid ) = @_;
-    my $dbh   = C4::Context->dbh;
-    my $query = "SELECT a.*, b.branchcode FROM alert a JOIN borrowers b USING(borrowernumber) WHERE 1";
-    my @bind;
-    if ($borrowernumber and $borrowernumber =~ /^\d+$/) {
-        $query .= " AND borrowernumber=?";
-        push @bind, $borrowernumber;
-    }
-    if ($type) {
-        $query .= " AND type=?";
-        push @bind, $type;
-    }
-    if ($externalid) {
-        $query .= " AND externalid=?";
-        push @bind, $externalid;
-    }
-    my $sth = $dbh->prepare($query);
-    $sth->execute(@bind);
-    return $sth->fetchall_arrayref({});
-}
-
-=head2 findrelatedto($type, $externalid)
-
-    parameters :
-    - $type : the type of alert
-    - $externalid : the id of the "object" to query
-
-    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.
-    When type=issue, the id is related to a subscriptionid and this sub returns the name of the biblio.
-
-=cut
-    
-# 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;
-    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;
-    }
-    my $sth = C4::Context->dbh->prepare($q);
-    $sth->execute($externalid);
-    my ($result) = $sth->fetchrow;
-    return $result;
+    |
+    . ( $mtt ? q| AND message_transport_type = ?| : q|| )
+    . ( $lang? q| AND lang = ?| : q|| )
+    , undef, $branchcode, $module, $code, ( $mtt ? $mtt : () ), ( $lang ? $lang : () ) );
 }
 
 =head2 SendAlerts
@@ -409,24 +315,24 @@ sub SendAlerts {
              return;
 
         my %letter;
-        # find the list of borrowers to alert
-        my $alerts = getalert( '', 'issue', $subscriptionid );
-        foreach (@$alerts) {
-            my $borinfo = C4::Members::GetMember('borrowernumber' => $_->{'borrowernumber'});
-            my $email = $borinfo->{email} or next;
+        # find the list of subscribers to notify
+        my $subscription = Koha::Subscriptions->find( $subscriptionid );
+        my $subscribers = $subscription->subscribers;
+        while ( my $patron = $subscribers->next ) {
+            my $email = $patron->email or next;
 
 #                    warn "sending issues...";
             my $userenv = C4::Context->userenv;
-            my $library = Koha::Libraries->find( $_->{branchcode} );
+            my $library = $patron->library;
             my $letter = GetPreparedLetter (
                 module => 'serial',
                 letter_code => $letter_code,
                 branchcode => $userenv->{branch},
                 tables => {
-                    'branches'    => $_->{branchcode},
+                    'branches'    => $library->branchcode,
                     'biblio'      => $biblionumber,
                     'biblioitems' => $biblionumber,
-                    'borrowers'   => $borinfo,
+                    'borrowers'   => $patron->unblessed,
                     'subscription' => $subscriptionid,
                     'serial' => $externalid,
                 },
@@ -451,7 +357,7 @@ sub SendAlerts {
                                     : 'text/plain; charset="utf-8"',
                 }
             );
-            unless( sendmail(%mail) ) {
+            unless( Mail::Sendmail::sendmail(%mail) ) {
                 carp $Mail::Sendmail::error;
                 return { error => $Mail::Sendmail::error };
             }
@@ -487,22 +393,23 @@ sub SendAlerts {
 
         if ($type eq 'claimissues') {
             $strsth = qq{
-            SELECT serial.*,subscription.*, biblio.*, aqbooksellers.*,
+            SELECT serial.*,subscription.*, biblio.*, biblioitems.*, 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 biblioitems ON serial.biblionumber = biblioitems.biblionumber
             LEFT JOIN aqbooksellers ON subscription.aqbooksellerid=aqbooksellers.id
             WHERE serial.serialid IN (
             };
 
             if (!@$externalid){
-                carp "No Order selected";
-                return { error => "no_order_selected" };
+                carp "No issues selected";
+                return { error => "no_issues_selected" };
             }
 
             $strsth .= join( ",", ('?') x @$externalid ) . ")";
-            $action = "CLAIM ISSUE";
+            $action = "SERIAL CLAIM";
             $sthorders = $dbh->prepare($strsth);
             $sthorders->execute( @$externalid );
             $dataorders = $sthorders->fetchall_arrayref( {} );
@@ -596,7 +503,7 @@ sub SendAlerts {
               if C4::Context->preference("ClaimsBccCopy");
         }
 
-        unless ( sendmail(%mail) ) {
+        unless ( Mail::Sendmail::sendmail(%mail) ) {
             carp $Mail::Sendmail::error;
             return { error => $Mail::Sendmail::error };
         }
@@ -620,6 +527,7 @@ sub SendAlerts {
             module => 'members',
             letter_code => $letter_code,
             branchcode => $externalid->{'branchcode'},
+            lang       => $externalid->{lang} || 'default',
             tables => {
                 'branches'    => $library,
                 'borrowers' => $externalid->{'borrowernumber'},
@@ -645,7 +553,7 @@ sub SendAlerts {
                                 : 'text/plain; charset="utf-8"',
             }
         );
-        unless( sendmail(%mail) ) {
+        unless( Mail::Sendmail::sendmail(%mail) ) {
             carp $Mail::Sendmail::error;
             return { error => $Mail::Sendmail::error };
         }
@@ -681,14 +589,23 @@ sub SendAlerts {
 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 $mtt         = $params{message_transport_type} || 'email';
+    my $letter = $params{letter};
 
-    my $letter = getletter( $module, $letter_code, $branchcode, $mtt )
-        or warn( "No $module $letter_code letter transported by " . $mtt ),
-            return;
+    unless ( $letter ) {
+        my $module      = $params{module} or croak "No module";
+        my $letter_code = $params{letter_code} or croak "No letter_code";
+        my $branchcode  = $params{branchcode} || '';
+        my $mtt         = $params{message_transport_type} || 'email';
+        my $lang        = $params{lang} || 'default';
+
+        $letter = getletter( $module, $letter_code, $branchcode, $mtt, $lang );
+
+        unless ( $letter ) {
+            $letter = getletter( $module, $letter_code, $branchcode, $mtt, 'default' )
+                or warn( "No $module $letter_code letter transported by " . $mtt ),
+                    return;
+        }
+    }
 
     my $tables = $params{tables} || {};
     my $substitute = $params{substitute} || {};
@@ -773,6 +690,7 @@ sub GetPreparedLetter {
             content => $letter->{content},
             tables  => $tables,
             loops  => $loops,
+            substitute => $substitute,
         }
     );
 
@@ -867,7 +785,7 @@ sub _parseletter {
     my $values = $values_in ? { %$values_in } : {};
 
     if ( $table eq 'borrowers' && $values->{'dateexpiry'} ){
-        $values->{'dateexpiry'} = format_sqldatetime( $values->{'dateexpiry'} );
+        $values->{'dateexpiry'} = output_pref({ dt => dt_from_string( $values->{'dateexpiry'} ), dateonly => 1 });
     }
 
     if ( $table eq 'reserves' && $values->{'waitingdate'} ) {
@@ -1013,19 +931,44 @@ ENDSQL
 
 =head2 SendQueuedMessages ([$hashref]) 
 
-  my $sent = SendQueuedMessages( { verbose => 1 } );
+    my $sent = SendQueuedMessages({
+        letter_code => $letter_code,
+        borrowernumber => $who_letter_is_for,
+        limit => 50,
+        verbose => 1,
+        type => 'sms',
+    });
+
+Sends all of the 'pending' items in the message queue, unless
+parameters are passed.
+
+The letter_code, borrowernumber and limit parameters are used
+to build a parameter set for _get_unsent_messages, thus limiting
+which pending messages will be processed. They are all optional.
 
-sends all of the 'pending' items in the message queue.
+The verbose parameter can be used to generate debugging output.
+It is also optional.
 
-returns number of messages sent.
+Returns number of messages sent.
 
 =cut
 
 sub SendQueuedMessages {
     my $params = shift;
 
-    my $unsent_messages = _get_unsent_messages();
+    my $which_unsent_messages  = {
+        'limit'          => $params->{'limit'} // 0,
+        'borrowernumber' => $params->{'borrowernumber'} // q{},
+        'letter_code'    => $params->{'letter_code'} // q{},
+        'type'           => $params->{'type'} // q{},
+    };
+    my $unsent_messages = _get_unsent_messages( $which_unsent_messages );
     MESSAGE: foreach my $message ( @$unsent_messages ) {
+        my $message_object = Koha::Notice::Messages->find( $message->{message_id} );
+        # If this fails the database is unwritable and we won't manage to send a message that continues to be marked 'pending'
+        $message_object->make_column_dirty('status');
+        return unless $message_object->store;
+
         # warn Data::Dumper->Dump( [ $message ], [ 'message' ] );
         warn sprintf( 'sending %s message to patron: %s',
                       $message->{'message_transport_type'},
@@ -1038,13 +981,29 @@ sub SendQueuedMessages {
         }
         elsif ( lc( $message->{'message_transport_type'} ) eq 'sms' ) {
             if ( C4::Context->preference('SMSSendDriver') eq 'Email' ) {
-                my $member = C4::Members::GetMember( 'borrowernumber' => $message->{'borrowernumber'} );
-                my $sms_provider = Koha::SMS::Providers->find( $member->{'sms_provider_id'} );
+                my $patron = Koha::Patrons->find( $message->{borrowernumber} );
+                my $sms_provider = Koha::SMS::Providers->find( $patron->sms_provider_id );
                 unless ( $sms_provider ) {
                     warn sprintf( "Patron %s has no sms provider id set!", $message->{'borrowernumber'} ) if $params->{'verbose'} or $debug;
+                    _set_message_status( { message_id => $message->{'message_id'}, status => 'failed' } );
+                    next MESSAGE;
+                }
+                unless ( $patron->smsalertnumber ) {
+                    _set_message_status( { message_id => $message->{'message_id'}, status => 'failed' } );
+                    warn sprintf( "No smsalertnumber found for patron %s!", $message->{'borrowernumber'} ) if $params->{'verbose'} or $debug;
                     next MESSAGE;
                 }
+                $message->{to_address}  = $patron->smsalertnumber; #Sometime this is set to email - sms should always use smsalertnumber
                 $message->{to_address} .= '@' . $sms_provider->domain();
+
+                # Check for possible from_address override
+                my $from_address = C4::Context->preference('EmailSMSSendDriverFromAddress');
+                if ($from_address && $message->{from_address} ne $from_address) {
+                    $message->{from_address} = $from_address;
+                    _update_message_from_address($message->{'message_id'}, $message->{from_address});
+                }
+
+                _update_message_to_address($message->{'message_id'}, $message->{to_address});
                 _send_message_by_email( $message, $params->{'username'}, $params->{'password'}, $params->{'method'} );
             } else {
                 _send_message_by_sms( $message );
@@ -1202,15 +1161,15 @@ sub ResendMessage {
 
 =head2 _add_attachements
 
-named parameters:
-letter - the standard letter hashref
-attachments - listref of attachments. each attachment is a hashref of:
-  type - the mime type, like 'text/plain'
-  content - the actual attachment
-  filename - the name of the attachment.
-message - a MIME::Lite object to attach these to.
+  named parameters:
+  letter - the standard letter hashref
+  attachments - listref of attachments. each attachment is a hashref of:
+    type - the mime type, like 'text/plain'
+    content - the actual attachment
+    filename - the name of the attachment.
+  message - a MIME::Lite object to attach these to.
 
-returns your letter object, with the content updated.
+  returns your letter object, with the content updated.
 
 =cut
 
@@ -1246,27 +1205,49 @@ sub _add_attachments {
 
 }
 
+=head2 _get_unsent_messages
+
+  This function's parameter hash reference takes the following
+  optional named parameters:
+   message_transport_type: method of message sending (e.g. email, sms, etc.)
+   borrowernumber        : who the message is to be sent
+   letter_code           : type of message being sent (e.g. PASSWORD_RESET)
+   limit                 : maximum number of messages to send
+
+  This function returns an array of matching hash referenced rows from
+  message_queue with some borrower information added.
+
+=cut
+
 sub _get_unsent_messages {
     my $params = shift;
 
     my $dbh = C4::Context->dbh();
-    my $statement = << 'ENDSQL';
-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
-  FROM message_queue mq
-  LEFT JOIN borrowers b ON b.borrowernumber = mq.borrowernumber
- WHERE status = ?
-ENDSQL
+    my $statement = qq{
+        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
+        FROM message_queue mq
+        LEFT JOIN borrowers b ON b.borrowernumber = mq.borrowernumber
       WHERE status = ?
+    };
 
     my @query_params = ('pending');
     if ( ref $params ) {
         if ( $params->{'message_transport_type'} ) {
-            $statement .= ' AND message_transport_type = ? ';
+            $statement .= ' AND mq.message_transport_type = ? ';
             push @query_params, $params->{'message_transport_type'};
         }
         if ( $params->{'borrowernumber'} ) {
-            $statement .= ' AND borrowernumber = ? ';
+            $statement .= ' AND mq.borrowernumber = ? ';
             push @query_params, $params->{'borrowernumber'};
         }
+        if ( $params->{'letter_code'} ) {
+            $statement .= ' AND mq.letter_code = ? ';
+            push @query_params, $params->{'letter_code'};
+        }
+        if ( $params->{'type'} ) {
+            $statement .= ' AND message_transport_type = ? ';
+            push @query_params, $params->{'type'};
+        }
         if ( $params->{'limit'} ) {
             $statement .= ' limit ? ';
             push @query_params, $params->{'limit'};
@@ -1284,16 +1265,16 @@ sub _send_message_by_email {
     my $message = shift or return;
     my ($username, $password, $method) = @_;
 
-    my $member = C4::Members::GetMember( 'borrowernumber' => $message->{'borrowernumber'} );
+    my $patron = Koha::Patrons->find( $message->{borrowernumber} );
     my $to_address = $message->{'to_address'};
     unless ($to_address) {
-        unless ($member) {
+        unless ($patron) {
             warn "FAIL: No 'to_address' and INVALID borrowernumber ($message->{borrowernumber})";
             _set_message_status( { message_id => $message->{'message_id'},
                                    status     => 'failed' } );
             return;
         }
-        $to_address = C4::Members::GetNoticeEmailAddress( $message->{'borrowernumber'} );
+        $to_address = $patron->notice_email_address;
         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?
@@ -1303,17 +1284,18 @@ sub _send_message_by_email {
         }
     }
 
-    my $utf8   = decode('MIME-Header', $message->{'subject'} );
-    $message->{subject}= encode('MIME-Header', $utf8);
-    my $subject = encode('UTF-8', $message->{'subject'});
+    # Encode subject line separately
+    $message->{subject} = encode('MIME-Header', $message->{'subject'} );
+    my $subject = $message->{'subject'};
+
     my $content = encode('UTF-8', $message->{'content'});
     my $content_type = $message->{'content_type'} || 'text/plain; charset="UTF-8"';
     my $is_html = $content_type =~ m/html/io;
     my $branch_email = undef;
     my $branch_replyto = undef;
     my $branch_returnpath = undef;
-    if ($member) {
-        my $library = Koha::Libraries->find( $member->{branchcode} );
+    if ($patron) {
+        my $library = $patron->library;
         $branch_email      = $library->branchemail;
         $branch_replyto    = $library->branchreplyto;
         $branch_returnpath = $library->branchreturnpath;
@@ -1338,7 +1320,7 @@ sub _send_message_by_email {
 
     _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
 
-    if ( sendmail( %sendmail_params ) ) {
+    if ( Mail::Sendmail::sendmail( %sendmail_params ) ) {
         _set_message_status( { message_id => $message->{'message_id'},
                 status     => 'sent' } );
         return 1;
@@ -1389,9 +1371,9 @@ sub _is_duplicate {
 
 sub _send_message_by_sms {
     my $message = shift or return;
-    my $member = C4::Members::GetMember( 'borrowernumber' => $message->{'borrowernumber'} );
+    my $patron = Koha::Patrons->find( $message->{borrowernumber} );
 
-    unless ( $member->{smsalertnumber} ) {
+    unless ( $patron and $patron->smsalertnumber ) {
         _set_message_status( { message_id => $message->{'message_id'},
                                status     => 'failed' } );
         return;
@@ -1403,7 +1385,7 @@ sub _send_message_by_sms {
         return;
     }
 
-    my $success = C4::SMS->send_sms( { destination => $member->{'smsalertnumber'},
+    my $success = C4::SMS->send_sms( { destination => $patron->smsalertnumber,
                                        message     => $message->{'content'},
                                      } );
     _set_message_status( { message_id => $message->{'message_id'},
@@ -1417,6 +1399,12 @@ sub _update_message_to_address {
     $dbh->do('UPDATE message_queue SET to_address=? WHERE message_id=?',undef,($to,$id));
 }
 
+sub _update_message_from_address {
+    my ($message_id, $from_address) = @_;
+    my $dbh = C4::Context->dbh();
+    $dbh->do('UPDATE message_queue SET from_address = ? WHERE message_id = ?', undef, ($from_address, $message_id));
+}
+
 sub _set_message_status {
     my $params = shift or return;
 
@@ -1438,6 +1426,7 @@ sub _process_tt {
     my $content = $params->{content};
     my $tables = $params->{tables};
     my $loops = $params->{loops};
+    my $substitute = $params->{substitute} || {};
 
     my $use_template_cache = C4::Context->config('template_cache_dir') && defined $ENV{GATEWAY_INTERFACE};
     my $template           = Template->new(
@@ -1452,9 +1441,10 @@ sub _process_tt {
         }
     ) or die Template->error();
 
-    my $tt_params = { %{ _get_tt_params( $tables ) }, %{ _get_tt_params( $loops, 'is_a_loop' ) } };
+    my $tt_params = { %{ _get_tt_params( $tables ) }, %{ _get_tt_params( $loops, 'is_a_loop' ) }, %$substitute };
 
-    $content = qq|[% USE KohaDates %]$content|;
+    $content = add_tt_filters( $content );
+    $content = qq|[% USE KohaDates %][% USE Remove_MARC_punctuation %]$content|;
 
     my $output;
     $template->process( \$content, $tt_params, \$output ) || croak "ERROR PROCESSING TEMPLATE: " . $template->error();
@@ -1481,6 +1471,12 @@ sub _get_tt_params {
             plural   => 'biblios',
             pk       => 'biblionumber',
         },
+        biblioitems => {
+            module   => 'Koha::Biblioitems',
+            singular => 'biblioitem',
+            plural   => 'biblioitems',
+            pk       => 'biblioitemnumber',
+        },
         borrowers => {
             module   => 'Koha::Patrons',
             singular => 'borrower',
@@ -1506,7 +1502,7 @@ sub _get_tt_params {
             pk       => 'idnew',
         },
         aqorders => {
-            module   => 'Koha::Tmp::Orders', # Should Koha::Acquisition::Orders when will be based on Koha::Objects
+            module   => 'Koha::Acquisition::Orders',
             singular => 'order',
             plural   => 'orders',
             pk       => 'ordernumber',
@@ -1547,6 +1543,12 @@ sub _get_tt_params {
             plural   => 'old_checkouts',
             fk       => 'itemnumber',
         },
+        overdues => {
+            module   => 'Koha::Checkouts',
+            singular => 'overdue',
+            plural   => 'overdues',
+            fk       => 'itemnumber',
+        },
         borrower_modifications => {
             module   => 'Koha::Patron::Modifications',
             singular => 'patron_modification',
@@ -1571,7 +1573,18 @@ sub _get_tt_params {
                     croak "ERROR processing table $table. Wrong API call.";
                 }
                 my $key = $pk ? $pk : $fk;
-                my $objects = $module->search( { $key => { -in => $values } } );
+                # $key does not come from user input
+                my $objects = $module->search(
+                    { $key => $values },
+                    {
+                            # We want to retrieve the data in the same order
+                            # FIXME MySQLism
+                            # field is a MySQLism, but they are no other way to do it
+                            # To be generic we could do it in perl, but we will need to fetch
+                            # all the data then order them
+                        @$values ? ( order_by => \[ "field($key, " . join( ', ', @$values ) . ")" ] ) : ()
+                    }
+                );
                 $params->{ $config->{$table}->{plural} } = $objects;
             }
             elsif ( $ref eq q{} || $ref eq 'HASH' ) {
@@ -1608,11 +1621,61 @@ sub _get_tt_params {
         }
     }
 
-    $params->{today} = dt_from_string();
+    $params->{today} = output_pref({ dt => dt_from_string, dateformat => 'iso' });
 
     return $params;
 }
 
+=head3 add_tt_filters
+
+$content = add_tt_filters( $content );
+
+Add TT filters to some specific fields if needed.
+
+For now we only add the Remove_MARC_punctuation TT filter to biblio and biblioitem fields
+
+=cut
+
+sub add_tt_filters {
+    my ( $content ) = @_;
+    $content =~ s|\[%\s*biblio\.(.*?)\s*%\]|[% biblio.$1 \| \$Remove_MARC_punctuation %]|gxms;
+    $content =~ s|\[%\s*biblioitem\.(.*?)\s*%\]|[% biblioitem.$1 \| \$Remove_MARC_punctuation %]|gxms;
+    return $content;
+}
+
+=head2 get_item_content
+
+    my $item = Koha::Items->find(...)->unblessed;
+    my @item_content_fields = qw( date_due title barcode author itemnumber );
+    my $item_content = C4::Letters::get_item_content({
+                             item => $item,
+                             item_content_fields => \@item_content_fields
+                       });
+
+This function generates a tab-separated list of values for the passed item. Dates
+are formatted following the current setup.
+
+=cut
+
+sub get_item_content {
+    my ( $params ) = @_;
+    my $item = $params->{item};
+    my $dateonly = $params->{dateonly} || 0;
+    my $item_content_fields = $params->{item_content_fields} || [];
+
+    return unless $item;
+
+    my @item_info = map {
+        $_ =~ /^date|date$/
+          ? eval {
+            output_pref(
+                { dt => dt_from_string( $item->{$_} ), dateonly => $dateonly } );
+          }
+          : $item->{$_}
+          || ''
+    } @$item_content_fields;
+    return join( "\t", @item_info ) . "\n";
+}
 
 1;
 __END__