Bug 32027: Fix 'librarian interface' to use 'staff interface' in Pages/HTML customiza...
[koha-ffzg.git] / C4 / Letters.pm
index 991a7ea..3bddc69 100644 (file)
@@ -19,37 +19,49 @@ package C4::Letters;
 
 use Modern::Perl;
 
-use MIME::Lite;
-use Date::Calc qw( Add_Delta_Days );
-use Encode;
-use Carp;
+use Carp qw( carp croak );
 use Template;
-use Module::Load::Conditional qw(can_load);
+use Module::Load::Conditional qw( can_load );
 
 use Try::Tiny;
 
 use C4::Members;
-use C4::Log;
+use C4::Log qw( logaction );
 use C4::SMS;
 use C4::Templates;
-use Koha::DateUtils;
 use Koha::SMS::Providers;
 
 use Koha::Email;
 use Koha::Notice::Messages;
 use Koha::Notice::Templates;
-use Koha::DateUtils qw( format_sqldatetime dt_from_string );
+use Koha::DateUtils qw( dt_from_string output_pref );
+use Koha::Auth::TwoFactorAuth;
 use Koha::Patrons;
 use Koha::SMTP::Servers;
 use Koha::Subscriptions;
 
-use vars qw(@ISA @EXPORT @EXPORT_OK %EXPORT_TAGS);
+use constant SERIALIZED_EMAIL_CONTENT_TYPE => 'message/rfc822';
 
+our (@ISA, @EXPORT_OK);
 BEGIN {
     require Exporter;
     @ISA = qw(Exporter);
-    @EXPORT = qw(
-        &EnqueueLetter &GetLetters &GetLettersAvailableForALibrary &GetLetterTemplates &DelLetter &GetPreparedLetter &GetWrappedLetter &SendAlerts &GetPrintMessages &GetMessageTransportTypes
+    @EXPORT_OK = qw(
+      GetLetters
+      GetLettersAvailableForALibrary
+      GetLetterTemplates
+      DelLetter
+      GetPreparedLetter
+      GetWrappedLetter
+      SendAlerts
+      GetPrintMessages
+      GetQueuedMessages
+      GetMessage
+      GetMessageTransportTypes
+
+      EnqueueLetter
+      SendQueuedMessages
+      ResendMessage
     );
 }
 
@@ -399,7 +411,7 @@ sub SendAlerts {
         }
 
         if ( $type eq 'orderacquisition') {
-            my $basketno = $externalid;
+            $basketno = $externalid;
             $strsth = qq{
             SELECT aqorders.*,aqbasket.*,biblio.*,biblioitems.*
             FROM aqorders
@@ -504,11 +516,12 @@ sub SendAlerts {
         return { error => $error }
             unless $success;
 
+        my $log_object = $action eq 'ACQUISITION ORDER' ? $externalid : undef;
         my $module = $action eq 'ACQUISITION ORDER' ? 'ACQUISITIONS' : 'CLAIMS';
         logaction(
             $module,
             $action,
-            undef,
+            $log_object,
             "To="
                 . join( ',', @email )
                 . " Title="
@@ -517,56 +530,6 @@ sub SendAlerts {
                 . $letter->{content}
         ) if C4::Context->preference("ClaimsLog");
     }
-   # send an "account details" notice to a newly created user
-    elsif ( $type eq 'members' ) {
-        my $library = Koha::Libraries->find( $externalid->{branchcode} );
-        my $letter = GetPreparedLetter (
-            module => 'members',
-            letter_code => $letter_code,
-            branchcode => $externalid->{'branchcode'},
-            lang       => $externalid->{lang} || 'default',
-            tables => {
-                'branches'    => $library->unblessed,
-                'borrowers' => $externalid->{'borrowernumber'},
-            },
-            substitute => { 'borrowers.password' => $externalid->{'password'} },
-            want_librarian => 1,
-        ) or return;
-        return { error => "no_email" } unless $externalid->{'emailaddr'};
-
-        my $success = try {
-
-            # FIXME: This 'default' behaviour should be moved to Koha::Email
-            my $mail = Koha::Email->create(
-                {
-                    to       => $externalid->{'emailaddr'},
-                    from     => $library->branchemail,
-                    reply_to => $library->branchreplyto,
-                    sender   => $library->branchreturnpath,
-                    subject  => "" . $letter->{'title'},
-                }
-            );
-
-            if ( $letter->{is_html} ) {
-                $mail->html_body( _wrap_html( $letter->{content}, "" . $letter->{title} ) );
-            }
-            else {
-                $mail->text_body( $letter->{content} );
-            }
-
-            $mail->send_or_die({ transport => $library->smtp_server->transport });
-        }
-        catch {
-            # We expect ref($_) eq 'Email::Sender::Failure'
-            $error = $_->message;
-
-            carp "$_";
-            return;
-        };
-
-        return { error => $error }
-            unless $success;
-    }
 
     # If we come here, return an OK status
     return 1;
@@ -626,17 +589,19 @@ sub GetPreparedLetter {
         $letter->{'content-type'} = 'text/html; charset="UTF-8"' if $letter->{is_html};
     }
 
+    my $objects = $params{objects} || {};
     my $tables = $params{tables} || {};
     my $substitute = $params{substitute} || {};
     my $loops  = $params{loops} || {}; # loops is not supported for historical notices syntax
     my $repeat = $params{repeat};
-    %$tables || %$substitute || $repeat || %$loops
-      or carp( "ERROR: nothing to substitute - both 'tables', 'loops' and 'substitute' are empty" ),
+    %$tables || %$substitute || $repeat || %$loops || %$objects
+      or carp( "ERROR: nothing to substitute - all of 'objects', 'tables', 'loops' and 'substitute' are empty" ),
          return;
     my $want_librarian = $params{want_librarian};
 
     if (%$substitute) {
         while ( my ($token, $val) = each %$substitute ) {
+            $val //= q{};
             if ( $token eq 'items.content' ) {
                 $val =~ s|\n|<br/>|g if $letter->{is_html};
             }
@@ -706,20 +671,23 @@ sub GetPreparedLetter {
 
     $letter->{content} = _process_tt(
         {
-            content => $letter->{content},
-            tables  => $tables,
-            loops  => $loops,
+            content    => $letter->{content},
+            lang       => $lang,
+            loops      => $loops,
+            objects    => $objects,
             substitute => $substitute,
-            lang => $lang
+            tables     => $tables,
         }
     );
 
     $letter->{title} = _process_tt(
         {
-            content => $letter->{title},
-            tables  => $tables,
-            loops  => $loops,
+            content    => $letter->{title},
+            lang       => $lang,
+            loops      => $loops,
+            objects    => $objects,
             substitute => $substitute,
+            tables     => $tables,
         }
     );
 
@@ -774,7 +742,7 @@ sub _parseletter_sth {
     ($table eq 'debits'       )    ? "SELECT * FROM accountlines WHERE   accountlines_id = ?"                         :
     ($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 'old_issues'   )    ? "SELECT * FROM $table WHERE     issue_id = ?"  :
     ($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 = ?"                                  :
@@ -783,12 +751,13 @@ sub _parseletter_sth {
     ($table eq 'aqorders'     )    ? "SELECT * FROM $table WHERE    ordernumber = ?"                                  :
     ($table eq 'aqbasket'     )    ? "SELECT * FROM $table WHERE       basketno = ?"                                  :
     ($table eq 'illrequests'  )    ? "SELECT * FROM $table WHERE  illrequest_id = ?"                                  :
-    ($table eq 'opac_news'    )    ? "SELECT * FROM $table WHERE          idnew = ?"                                  :
     ($table eq 'article_requests') ? "SELECT * FROM $table WHERE             id = ?"                                  :
     ($table eq 'borrower_modifications') ? "SELECT * FROM $table WHERE verification_token = ?" :
     ($table eq 'subscription') ? "SELECT * FROM $table WHERE subscriptionid = ?" :
     ($table eq 'serial') ? "SELECT * FROM $table WHERE serialid = ?" :
     ($table eq 'problem_reports') ? "SELECT * FROM $table WHERE reportid = ?" :
+    ($table eq 'additional_contents' || $table eq 'opac_news') ? "SELECT * FROM additional_contents WHERE idnew = ?" :
+    ($table eq 'recalls') ? "SELECT * FROM $table WHERE recall_id = ?" :
     undef ;
     unless ($query) {
         warn "ERROR: No _parseletter_sth query for table '$table'";
@@ -819,6 +788,7 @@ sub _parseletter {
     # in callers ( by changing / formatting values )
     my $values = $values_in ? { %$values_in } : {};
 
+    # FIXME Dates formatting must be done in notice's templates
     if ( $table eq 'borrowers' && $values->{'dateexpiry'} ){
         $values->{'dateexpiry'} = output_pref({ dt => dt_from_string( $values->{'dateexpiry'} ), dateonly => 1 });
     }
@@ -861,8 +831,9 @@ sub _parseletter {
                     $dateonly = $1 unless $dateonly;
                 }
                 my $replacedby_date = eval {
-                    output_pref({ dt => dt_from_string( $replacedby ), dateonly => $dateonly });
+                    output_pref({ dt => scalar dt_from_string( $replacedby ), dateonly => $dateonly });
                 };
+                $replacedby_date //= q{};
 
                 if ( $letter->{ $letter_field } ) {
                     $letter->{ $letter_field } =~ s/\Q<<$table.$field$filter_string_used>>\E/$replacedby_date/g;
@@ -909,11 +880,19 @@ sub _parseletter {
   my $success = EnqueueLetter( { letter => $letter, 
         borrowernumber => '12', message_transport_type => 'email' } )
 
-places a letter in the message_queue database table, which will
+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 message_id on success
+Return message_id on success
+
+Parameters
+* letter - required; A letter hashref as returned from GetPreparedLetter
+* message_transport_type - required; One of the available mtts
+* borrowernumber - optional if 'to_address' is passed; The borrowernumber of the patron we enqueuing the notice for
+* to_address - optional if 'borrowernumber' is passed; The destination email address for the notice (defaults to patron->notice_email_address)
+* from_address - optional; The from address for the notice, defaults to patron->library->from_email_address
+* reply_address - optional; The reply address for the notice, defaults to patron->library->reply_to
 
 =cut
 
@@ -936,7 +915,6 @@ sub EnqueueLetter {
         $params->{'letter'} = _add_attachments(
             {   letter      => $params->{'letter'},
                 attachments => $params->{'attachments'},
-                message     => MIME::Lite->new( Type => 'multipart/mixed' ),
             }
         );
     }
@@ -944,13 +922,13 @@ sub EnqueueLetter {
     my $dbh       = C4::Context->dbh();
     my $statement = << 'ENDSQL';
 INSERT INTO message_queue
-( borrowernumber, subject, content, metadata, letter_code, message_transport_type, status, time_queued, to_address, from_address, reply_address, content_type, delivery_note )
-VALUES
-( ?,              ?,       ?,       ?,        ?,           ?,                      ?,      CAST(NOW() AS DATETIME),       ?,          ?,            ?,           ?,              ? )
+( letter_id, borrowernumber, subject, content, metadata, letter_code, message_transport_type, status, time_queued, to_address, from_address, reply_address, content_type, failure_code )
+VALUES ( ?, ?, ?, ?, ?, ?, ?, ?, CAST(NOW() AS DATETIME), ?, ?, ?, ?, ? )
 ENDSQL
 
     my $sth    = $dbh->prepare($statement);
     my $result = $sth->execute(
+        $params->{letter}->{id} || undef,         # letter.id
         $params->{'borrowernumber'},              # borrowernumber
         $params->{'letter'}->{'title'},           # subject
         $params->{'letter'}->{'content'},         # content
@@ -962,7 +940,7 @@ ENDSQL
         $params->{'from_address'},                # from_address
         $params->{'reply_address'},               # reply_address
         $params->{'letter'}->{'content-type'},    # content_type
-        $params->{'delivery_note'}        || '',  # delivery_note
+        $params->{'failure_code'}        || '',   # failure_code
     );
     return $dbh->last_insert_id(undef,undef,'message_queue', undef);
 }
@@ -999,7 +977,7 @@ sub SendQueuedMessages {
         'limit'          => $params->{'limit'} // 0,
         'borrowernumber' => $params->{'borrowernumber'} // q{},
         'letter_code'    => $params->{'letter_code'} // q{},
-        'type'           => $params->{'type'} // q{},
+        'message_transport_type'           => $params->{'type'} // q{},
     };
     my $unsent_messages = _get_unsent_messages( $which_unsent_messages );
     MESSAGE: foreach my $message ( @$unsent_messages ) {
@@ -1105,7 +1083,7 @@ sub GetQueuedMessages {
 
     my $dbh = C4::Context->dbh();
     my $statement = << 'ENDSQL';
-SELECT message_id, borrowernumber, subject, content, message_transport_type, status, time_queued, updated_on, delivery_note
+SELECT message_id, borrowernumber, subject, content, message_transport_type, status, time_queued, updated_on, failure_code
 FROM message_queue
 ENDSQL
 
@@ -1159,7 +1137,7 @@ sub GetMessage {
     return unless $message_id;
     my $dbh = C4::Context->dbh;
     return $dbh->selectrow_hashref(q|
-        SELECT message_id, borrowernumber, subject, content, metadata, letter_code, message_transport_type, status, time_queued, updated_on, to_address, from_address, reply_address, content_type, delivery_note
+        SELECT message_id, borrowernumber, subject, content, metadata, letter_code, message_transport_type, status, time_queued, updated_on, to_address, from_address, reply_address, content_type, failure_code
         FROM message_queue
         WHERE message_id = ?
     |, {}, $message_id );
@@ -1200,45 +1178,50 @@ sub ResendMessage {
 
 =head2 _add_attachements
 
+  _add_attachments({ letter => $letter, attachments => $attachments });
+
   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.
+  This routine picks the I<content> of I<letter> and generates a MIME
+  email, attaching the passed I<attachments> using Koha::Email. The
+  content is replaced by the string representation of the MIME object,
+  and the content-type is updated for later handling.
 
 =cut
 
 sub _add_attachments {
     my $params = shift;
 
-    my $letter = $params->{'letter'};
-    my $attachments = $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
-    $message->attach(
-        Type => $letter->{'content-type'} || 'TEXT',
-        Data => $letter->{'is_html'}
-            ? _wrap_html($letter->{'content'}, $letter->{'title'})
-            : $letter->{'content'},
-    );
+
+    my $message = Koha::Email->new;
+
+    if ( $letter->{is_html} ) {
+        $message->html_body( _wrap_html( $letter->{content}, $letter->{title} ) );
+    }
+    else {
+        $message->text_body( $letter->{content} );
+    }
 
     foreach my $attachment ( @$attachments ) {
         $message->attach(
-            Type     => $attachment->{'type'},
-            Data     => $attachment->{'content'},
-            Filename => $attachment->{'filename'},
+            Encode::encode( "UTF-8", $attachment->{content} ),
+            content_type => $attachment->{type} || 'application/octet-stream',
+            name         => $attachment->{filename},
+            disposition  => 'attachment',
         );
     }
-    # we're forcing list context here to get the header, not the count back from grep.
-    ( $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;
+
+    $letter->{'content-type'} = SERIALIZED_EMAIL_CONTENT_TYPE;
+    $letter->{content} = $message->as_string;
 
     return $letter;
 
@@ -1249,8 +1232,10 @@ sub _add_attachments {
   This function's parameter hash reference takes the following
   optional named parameters:
    message_transport_type: method of message sending (e.g. email, sms, etc.)
+                           Can be a single string, or an arrayref of strings
    borrowernumber        : who the message is to be sent
    letter_code           : type of message being sent (e.g. PASSWORD_RESET)
+                           Can be a single string, or an arrayref of strings
    message_id            : the message_id of the message. In that case the sub will return only 1 result
    limit                 : maximum number of messages to send
 
@@ -1264,7 +1249,7 @@ sub _get_unsent_messages {
 
     my $dbh = C4::Context->dbh();
     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.reply_address, mq.to_address, mq.content_type, b.branchcode, mq.letter_code, mq.delivery_note
+        SELECT mq.message_id, mq.borrowernumber, mq.subject, mq.content, mq.message_transport_type, mq.status, mq.time_queued, mq.from_address, mq.reply_address, mq.to_address, mq.content_type, b.branchcode, mq.letter_code, mq.failure_code
         FROM message_queue mq
         LEFT JOIN borrowers b ON b.borrowernumber = mq.borrowernumber
         WHERE status = ?
@@ -1272,21 +1257,25 @@ sub _get_unsent_messages {
 
     my @query_params = ('pending');
     if ( ref $params ) {
-        if ( $params->{'message_transport_type'} ) {
-            $statement .= ' AND mq.message_transport_type = ? ';
-            push @query_params, $params->{'message_transport_type'};
-        }
         if ( $params->{'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'};
+            my @letter_codes = ref $params->{'letter_code'} eq "ARRAY" ? @{$params->{'letter_code'}} : $params->{'letter_code'};
+            if ( @letter_codes ) {
+                my $q = join( ",", ("?") x @letter_codes );
+                $statement .= " AND mq.letter_code IN ( $q ) ";
+                push @query_params, @letter_codes;
+            }
         }
-        if ( $params->{'type'} ) {
-            $statement .= ' AND message_transport_type = ? ';
-            push @query_params, $params->{'type'};
+        if ( $params->{'message_transport_type'} ) {
+            my @types = ref $params->{'message_transport_type'} eq "ARRAY" ? @{$params->{'message_transport_type'}} : $params->{'message_transport_type'};
+            if ( @types ) {
+                my $q = join( ",", ("?") x @types );
+                $statement .= " AND message_transport_type IN ( $q ) ";
+                push @query_params, @types;
+            }
         }
         if ( $params->{message_id} ) {
             $statement .= ' AND message_id = ?';
@@ -1312,20 +1301,26 @@ sub _send_message_by_email {
     unless ($to_address) {
         unless ($patron) {
             warn "FAIL: No 'to_address' and INVALID borrowernumber ($message->{borrowernumber})";
-            _set_message_status( { message_id => $message->{'message_id'},
-                                   status     => 'failed',
-                                   delivery_note => 'Invalid borrowernumber '.$message->{borrowernumber},
-                                   error_code => 'INVALID_BORNUMBER' } );
+            _set_message_status(
+                {
+                    message_id   => $message->{'message_id'},
+                    status       => 'failed',
+                    failure_code => 'INVALID_BORNUMBER'
+                }
+            );
             return;
         }
         $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?
-            _set_message_status( { message_id => $message->{'message_id'},
-                                   status     => 'failed',
-                                   delivery_note => 'Unable to find an email address for this borrower',
-                                   error_code => 'NO_EMAIL' } );
+            _set_message_status(
+                {
+                    message_id   => $message->{'message_id'},
+                    status       => 'failed',
+                    failure_code => 'NO_EMAIL'
+                }
+            );
             return;
         }
     }
@@ -1343,34 +1338,80 @@ sub _send_message_by_email {
 
     if ($patron) {
         $library           = $patron->library;
-        $branch_email      = $library->branchemail;
+        $branch_email      = $library->from_email_address;
         $branch_replyto    = $library->branchreplyto;
         $branch_returnpath = $library->branchreturnpath;
     }
 
-    my $email = Koha::Email->create(
-        {
+    # NOTE: Patron may not be defined above so branch_email may be undefined still
+    # so we need to fallback to KohaAdminEmailAddress as a last resort.
+    my $from_address =
+         $message->{'from_address'}
+      || $branch_email
+      || C4::Context->preference('KohaAdminEmailAddress');
+    if( !$from_address ) {
+        _set_message_status(
+            {
+                message_id   => $message->{'message_id'},
+                status       => 'failed',
+                failure_code => 'NO_FROM',
+            }
+        );
+        return;
+    };
+    my $email;
+
+    try {
+
+        my $params = {
             to => $to_address,
             (
                 C4::Context->preference('NoticeBcc')
                 ? ( bcc => C4::Context->preference('NoticeBcc') )
                 : ()
             ),
-            from     => $message->{'from_address'}  || $branch_email,
+            from     => $from_address,
             reply_to => $message->{'reply_address'} || $branch_replyto,
             sender   => $branch_returnpath,
             subject  => "" . $message->{subject}
-        }
-    );
+        };
 
-    if ( $is_html ) {
-        $email->html_body(
-            _wrap_html( $content, $subject )
-        );
-    }
-    else {
-        $email->text_body( $content );
+        if ( $message->{'content_type'} && $message->{'content_type'} eq SERIALIZED_EMAIL_CONTENT_TYPE ) {
+
+            # The message has been previously composed as a valid MIME object
+            # and serialized as a string on the DB
+            $email = Koha::Email->new_from_string($content);
+            $email->create($params);
+        } else {
+            $email = Koha::Email->create($params);
+            if ($is_html) {
+                $email->html_body( _wrap_html( $content, $subject ) );
+            } else {
+                $email->text_body($content);
+            }
+        }
     }
+    catch {
+        if ( ref($_) eq 'Koha::Exceptions::BadParameter' ) {
+            _set_message_status(
+                {
+                    message_id   => $message->{'message_id'},
+                    status       => 'failed',
+                    failure_code => "INVALID_EMAIL:".$_->parameter
+                }
+            );
+        } else {
+            _set_message_status(
+                {
+                    message_id   => $message->{'message_id'},
+                    status       => 'failed',
+                    failure_code => 'UNKNOWN_ERROR'
+                }
+            );
+        }
+        return 0;
+    };
+    return unless $email;
 
     my $smtp_server;
     if ( $library ) {
@@ -1402,7 +1443,7 @@ sub _send_message_by_email {
             {
                 message_id => $message->{'message_id'},
                 status     => 'sent',
-                delivery_note => ''
+                failure_code => ''
             }
         );
         return 1;
@@ -1412,10 +1453,11 @@ sub _send_message_by_email {
             {
                 message_id => $message->{'message_id'},
                 status     => 'failed',
-                delivery_note => $Mail::Sendmail::error
+                failure_code => 'SENDMAIL'
             }
         );
         carp "$_";
+        carp "$Mail::Sendmail::error";
         return;
     };
 }
@@ -1460,30 +1502,51 @@ sub _is_duplicate {
 sub _send_message_by_sms {
     my $message = shift or return;
     my $patron = Koha::Patrons->find( $message->{borrowernumber} );
+    _update_message_to_address($message->{message_id}, $patron->smsalertnumber) if $patron;
 
     unless ( $patron and $patron->smsalertnumber ) {
         _set_message_status( { message_id => $message->{'message_id'},
                                status     => 'failed',
-                               delivery_note => 'Missing SMS number',
-                               error_code => 'MISSING_SMS' } );
+                               failure_code => 'MISSING_SMS' } );
         return;
     }
 
     if ( _is_duplicate( $message ) ) {
-        _set_message_status( { message_id => $message->{'message_id'},
-                               status     => 'failed',
-                               delivery_note => 'Message is duplicate',
-                               error_code => 'DUPLICATE_MESSAGE' } );
+        _set_message_status(
+            {
+                message_id   => $message->{'message_id'},
+                status       => 'failed',
+                failure_code => 'DUPLICATE_MESSAGE'
+            }
+        );
         return;
     }
 
-    my $success = C4::SMS->send_sms( { destination => $patron->smsalertnumber,
-                                       message     => $message->{'content'},
-                                     } );
-    _set_message_status( { message_id => $message->{'message_id'},
-                           status     => ($success ? 'sent' : 'failed'),
-                           delivery_note => ($success ? '' : 'No notes from SMS driver'),
-                           error_code => 'NO_NOTES' } );
+    my $success = C4::SMS->send_sms(
+        {
+            destination => $patron->smsalertnumber,
+            message     => $message->{'content'},
+        }
+    );
+
+    if ($success) {
+        _set_message_status(
+            {
+                message_id   => $message->{'message_id'},
+                status       => 'sent',
+                failure_code => ''
+            }
+        );
+    }
+    else {
+        _set_message_status(
+            {
+                message_id   => $message->{'message_id'},
+                status       => 'failed',
+                failure_code => 'NO_NOTES'
+            }
+        );
+    }
 
     return $success;
 }
@@ -1508,10 +1571,10 @@ sub _set_message_status {
     }
 
     my $dbh = C4::Context->dbh();
-    my $statement = 'UPDATE message_queue SET status= ?, delivery_note= ? WHERE message_id = ?';
+    my $statement = 'UPDATE message_queue SET status= ?, failure_code= ? WHERE message_id = ?';
     my $sth = $dbh->prepare( $statement );
     my $result = $sth->execute( $params->{'status'},
-                                $params->{'delivery_note'} || '',
+                                $params->{'failure_code'} || '',
                                 $params->{'message_id'} );
     return $result;
 }
@@ -1519,9 +1582,10 @@ sub _set_message_status {
 sub _process_tt {
     my ( $params ) = @_;
 
-    my $content = $params->{content};
-    my $tables = $params->{tables};
-    my $loops = $params->{loops};
+    my $content    = $params->{content};
+    my $tables     = $params->{tables};
+    my $loops      = $params->{loops};
+    my $objects    = $params->{objects} || {};
     my $substitute = $params->{substitute} || {};
     my $lang = defined($params->{lang}) && $params->{lang} ne 'default' ? $params->{lang} : 'en';
     my ($theme, $availablethemes);
@@ -1548,13 +1612,21 @@ sub _process_tt {
         }
     ) or die Template->error();
 
-    my $tt_params = { %{ _get_tt_params( $tables ) }, %{ _get_tt_params( $loops, 'is_a_loop' ) }, %$substitute };
+    my $tt_params = { %{ _get_tt_params( $tables ) }, %{ _get_tt_params( $loops, 'is_a_loop' ) }, %$substitute, %$objects };
 
     $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();
+    my $schema = Koha::Database->new->schema;
+    $schema->txn_begin;
+    my $processed = try {
+        $template->process( \$content, $tt_params, \$output );
+    }
+    finally {
+        $schema->txn_rollback;
+    };
+    croak "ERROR PROCESSING TEMPLATE: " . $template->error() unless $processed;
 
     return $output;
 }
@@ -1620,8 +1692,14 @@ sub _get_tt_params {
             plural   => 'items',
             pk       => 'itemnumber',
         },
+        additional_contents => {
+            module   => 'Koha::AdditionalContents',
+            singular => 'additional_content',
+            plural   => 'additional_contents',
+            pk       => 'idnew',
+        },
         opac_news => {
-            module   => 'Koha::News',
+            module   => 'Koha::AdditionalContents',
             singular => 'news',
             plural   => 'news',
             pk       => 'idnew',
@@ -1666,7 +1744,7 @@ sub _get_tt_params {
             module   => 'Koha::Old::Checkouts',
             singular => 'old_checkout',
             plural   => 'old_checkouts',
-            fk       => 'itemnumber',
+            pk       => 'issue_id',
         },
         overdues => {
             module   => 'Koha::Checkouts',