Bug 15721: (QA followup) pick the most probable first
[koha.git] / C4 / Letters.pm
index 055c8c1..0d092ac 100644 (file)
@@ -4,18 +4,18 @@ package C4::Letters;
 #
 # This file is part of Koha.
 #
 #
 # This file is part of Koha.
 #
-# Koha is free software; you can redistribute it and/or modify it under the
-# terms of the GNU General Public License as published by the Free Software
-# Foundation; either version 2 of the License, or (at your option) any later
-# version.
+# Koha is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
 #
 #
-# Koha is distributed in the hope that it will be useful, but WITHOUT ANY
-# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
-# A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+# Koha is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
 #
 #
-# You should have received a copy of the GNU General Public License along
-# with Koha; if not, write to the Free Software Foundation, Inc.,
-# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+# You should have received a copy of the GNU General Public License
+# along with Koha; if not, see <http://www.gnu.org/licenses>.
 
 use strict;
 use warnings;
 
 use strict;
 use warnings;
@@ -31,9 +31,13 @@ use C4::Log;
 use C4::SMS;
 use C4::Debug;
 use Koha::DateUtils;
 use C4::SMS;
 use C4::Debug;
 use Koha::DateUtils;
+use Koha::SMS::Providers;
+
 use Date::Calc qw( Add_Delta_Days );
 use Encode;
 use Carp;
 use Date::Calc qw( Add_Delta_Days );
 use Encode;
 use Carp;
+use Koha::Email;
+use Koha::DateUtils qw( format_sqldatetime );
 
 use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS);
 
 
 use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS);
 
@@ -43,7 +47,7 @@ BEGIN {
     $VERSION = 3.07.00.049;
     @ISA = qw(Exporter);
     @EXPORT = qw(
     $VERSION = 3.07.00.049;
     @ISA = qw(Exporter);
     @EXPORT = qw(
-        &GetLetters &GetPreparedLetter &GetWrappedLetter &addalert &getalert &delalert &findrelatedto &SendAlerts &GetPrintMessages &GetMessageTransportTypes
+        &GetLetters &GetLettersAvailableForALibrary &GetLetterTemplates &DelLetter &GetPreparedLetter &GetWrappedLetter &addalert &getalert &delalert &findrelatedto &SendAlerts &GetPrintMessages &GetMessageTransportTypes
     );
 }
 
     );
 }
 
@@ -62,58 +66,140 @@ C4::Letters - Give functions for Letters management
 
   Letters are managed through "alerts" sent by Koha on some events. All "alert" related functions are in this module too.
 
 
   Letters are managed through "alerts" sent by Koha on some events. All "alert" related functions are in this module too.
 
-=head2 GetLetters([$category])
+=head2 GetLetters([$module])
 
 
-  $letters = &GetLetters($category);
+  $letters = &GetLetters($module);
   returns informations about letters.
   returns informations about letters.
-  if needed, $category filters for letters given category
-  Create a letter selector with the following code
-
-=head3 in PERL SCRIPT
-
-my $letters = GetLetters($cat);
-my @letterloop;
-foreach my $thisletter (keys %$letters) {
-    my $selected = 1 if $thisletter eq $letter;
-    my %row =(
-        value => $thisletter,
-        selected => $selected,
-        lettername => $letters->{$thisletter},
+  if needed, $module filters for letters given module
+
+=cut
+
+sub GetLetters {
+    my ($filters) = @_;
+    my $module    = $filters->{module};
+    my $code      = $filters->{code};
+    my $branchcode = $filters->{branchcode};
+    my $dbh       = C4::Context->dbh;
+    my $letters   = $dbh->selectall_arrayref(
+        q|
+            SELECT module, code, branchcode, 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 => {} }
+        , ( $module ? $module : () )
+        , ( $code ? $code : () )
+        , ( defined $branchcode ? $branchcode : () )
     );
     );
-    push @letterloop, \%row;
+
+    return $letters;
 }
 }
-$template->param(LETTERLOOP => \@letterloop);
 
 
-=head3 in TEMPLATE
+=head2 GetLetterTemplates
 
 
-    <select name="letter">
-        <option value="">Default</option>
-    <!-- TMPL_LOOP name="LETTERLOOP" -->
-        <option value="<!-- TMPL_VAR name="value" -->" <!-- TMPL_IF name="selected" -->selected<!-- /TMPL_IF -->><!-- TMPL_VAR name="lettername" --></option>
-    <!-- /TMPL_LOOP -->
-    </select>
+    my $letter_templates = GetLetterTemplates(
+        {
+            module => 'circulation',
+            code => 'my code',
+            branchcode => 'CPL', # '' for default,
+        }
+    );
+
+    Return a hashref of letter templates.
+    The key will be the message transport type.
 
 =cut
 
 
 =cut
 
-sub GetLetters {
+sub GetLetterTemplates {
+    my ( $params ) = @_;
+
+    my $module    = $params->{module};
+    my $code      = $params->{code};
+    my $branchcode = $params->{branchcode} // '';
+    my $dbh       = C4::Context->dbh;
+    my $letters   = $dbh->selectall_hashref(
+        q|
+            SELECT module, code, branchcode, name, is_html, title, content, message_transport_type
+            FROM letter
+            WHERE module = ?
+            AND code = ?
+            and branchcode = ?
+        |
+        , 'message_transport_type'
+        , undef
+        , $module, $code, $branchcode
+    );
+
+    return $letters;
+}
+
+=head2 GetLettersAvailableForALibrary
+
+    my $letters = GetLettersAvailableForALibrary(
+        {
+            branchcode => 'CPL', # '' for default
+            module => 'circulation',
+        }
+    );
+
+    Return an arrayref of letters, sorted by name.
+    If a specific letter exist for the given branchcode, it will be retrieve.
+    Otherwise the default letter will be.
+
+=cut
+
+sub GetLettersAvailableForALibrary {
+    my ($filters)  = @_;
+    my $branchcode = $filters->{branchcode};
+    my $module     = $filters->{module};
+
+    croak "module should be provided" unless $module;
+
+    my $dbh             = C4::Context->dbh;
+    my $default_letters = $dbh->selectall_arrayref(
+        q|
+            SELECT module, code, branchcode, name
+            FROM letter
+            WHERE 1
+        |
+          . q| AND branchcode = ''|
+          . ( $module ? q| AND module = ?| : q|| )
+          . q| ORDER BY name|, { Slice => {} }
+        , ( $module ? $module : () )
+    );
+
+    my $specific_letters;
+    if ($branchcode) {
+        $specific_letters = $dbh->selectall_arrayref(
+            q|
+                SELECT module, code, branchcode, name
+                FROM letter
+                WHERE 1
+            |
+              . q| AND branchcode = ?|
+              . ( $module ? q| AND module = ?| : q|| )
+              . q| ORDER BY name|, { Slice => {} }
+            , $branchcode
+            , ( $module ? $module : () )
+        );
+    }
 
 
-    # returns a reference to a hash of references to ALL letters...
-    my ( $cat ) = @_;
     my %letters;
     my %letters;
-    my $dbh = C4::Context->dbh;
-    my $sth;
-    my $query = q{
-        SELECT * FROM letter WHERE 1
-    };
-    $query .= q{ AND module = ? } if defined $cat;
-    $query .= q{ GROUP BY code ORDER BY name};
-    $sth = $dbh->prepare($query);
-    $sth->execute((defined $cat ? $cat : ()));
-
-    while ( my $letter = $sth->fetchrow_hashref ) {
-        $letters{ $letter->{'code'} } = $letter->{'name'};
+    for my $l (@$default_letters) {
+        $letters{ $l->{code} } = $l;
+    }
+    for my $l (@$specific_letters) {
+        # Overwrite the default letter with the specific one.
+        $letters{ $l->{code} } = $l;
     }
     }
-    return \%letters;
+
+    return [ map { $letters{$_} }
+          sort { $letters{$a}->{name} cmp $letters{$b}->{name} }
+          keys %letters ];
+
 }
 
 # FIXME: using our here means that a Plack server will need to be
 }
 
 # FIXME: using our here means that a Plack server will need to be
@@ -124,8 +210,7 @@ sub GetLetters {
 our %letter;
 sub getletter {
     my ( $module, $code, $branchcode, $message_transport_type ) = @_;
 our %letter;
 sub getletter {
     my ( $module, $code, $branchcode, $message_transport_type ) = @_;
-    $message_transport_type ||= 'email';
-
+    $message_transport_type //= '%';
 
     if ( C4::Context->preference('IndependentBranches')
             and $branchcode
 
     if ( C4::Context->preference('IndependentBranches')
             and $branchcode
@@ -143,7 +228,8 @@ sub getletter {
     my $sth = $dbh->prepare(q{
         SELECT *
         FROM letter
     my $sth = $dbh->prepare(q{
         SELECT *
         FROM letter
-        WHERE module=? AND code=? AND (branchcode = ? OR branchcode = '') AND message_transport_type = ?
+        WHERE module=? AND code=? AND (branchcode = ? OR branchcode = '')
+        AND message_transport_type LIKE ?
         ORDER BY branchcode DESC LIMIT 1
     });
     $sth->execute( $module, $code, $branchcode, $message_transport_type );
         ORDER BY branchcode DESC LIMIT 1
     });
     $sth->execute( $module, $code, $branchcode, $message_transport_type );
@@ -154,6 +240,39 @@ sub getletter {
     return { %$line };
 }
 
     return { %$line };
 }
 
+
+=head2 DelLetter
+
+    DelLetter(
+        {
+            branchcode => 'CPL',
+            module => 'circulation',
+            code => 'my code',
+            [ mtt => 'email', ]
+        }
+    );
+
+    Delete the letter. The mtt parameter is facultative.
+    If not given, all templates mathing the other parameters will be removed.
+
+=cut
+
+sub DelLetter {
+    my ($params)   = @_;
+    my $branchcode = $params->{branchcode};
+    my $module     = $params->{module};
+    my $code       = $params->{code};
+    my $mtt        = $params->{mtt};
+    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 : 
 =head2 addalert ($borrowernumber, $type, $externalid)
 
     parameters : 
@@ -228,12 +347,12 @@ sub getalert {
 
 =head2 findrelatedto($type, $externalid)
 
 
 =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.
+    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
     
 
 =cut
     
@@ -274,26 +393,34 @@ sub SendAlerts {
     if ( $type eq 'issue' ) {
 
         # prepare the letter...
     if ( $type eq 'issue' ) {
 
         # prepare the letter...
-        # search the biblionumber
+        # search the subscriptionid
         my $sth =
           $dbh->prepare(
         my $sth =
           $dbh->prepare(
-            "SELECT biblionumber FROM subscription WHERE subscriptionid=?");
+            "SELECT subscriptionid FROM serial WHERE serialid=?");
         $sth->execute($externalid);
         $sth->execute($externalid);
-        my ($biblionumber) = $sth->fetchrow
+        my ($subscriptionid) = $sth->fetchrow
           or warn( "No subscription for '$externalid'" ),
              return;
 
           or warn( "No subscription for '$externalid'" ),
              return;
 
+        # search the biblionumber
+        $sth =
+          $dbh->prepare(
+            "SELECT biblionumber FROM subscription WHERE subscriptionid=?");
+        $sth->execute($subscriptionid);
+        my ($biblionumber) = $sth->fetchrow
+          or warn( "No biblionumber for '$subscriptionid'" ),
+             return;
+
         my %letter;
         # find the list of borrowers to alert
         my %letter;
         # find the list of borrowers to alert
-        my $alerts = getalert( '', 'issue', $externalid );
+        my $alerts = getalert( '', 'issue', $subscriptionid );
         foreach (@$alerts) {
         foreach (@$alerts) {
-
             my $borinfo = C4::Members::GetMember('borrowernumber' => $_->{'borrowernumber'});
             my $email = $borinfo->{email} or next;
 
             my $borinfo = C4::Members::GetMember('borrowernumber' => $_->{'borrowernumber'});
             my $email = $borinfo->{email} or next;
 
-            #          warn "sending issues...";
+#                    warn "sending issues...";
             my $userenv = C4::Context->userenv;
             my $userenv = C4::Context->userenv;
-            my $branchdetails = GetBranchDetail($_->{'branchcode'});
+            my $library = Koha::Libraries->find( $_->{branchcode} );
             my $letter = GetPreparedLetter (
                 module => 'serial',
                 letter_code => $letter_code,
             my $letter = GetPreparedLetter (
                 module => 'serial',
                 letter_code => $letter_code,
@@ -303,18 +430,30 @@ sub SendAlerts {
                     'biblio'      => $biblionumber,
                     'biblioitems' => $biblionumber,
                     'borrowers'   => $borinfo,
                     'biblio'      => $biblionumber,
                     'biblioitems' => $biblionumber,
                     'borrowers'   => $borinfo,
+                    'subscription' => $subscriptionid,
+                    'serial' => $externalid,
                 },
                 want_librarian => 1,
             ) or return;
 
             # ... then send mail
                 },
                 want_librarian => 1,
             ) or return;
 
             # ... then send mail
-            my %mail = (
-                To      => $email,
-                From    => $branchdetails->{'branchemail'} || C4::Context->preference("KohaAdminEmailAddress"),
-                Subject => Encode::encode( "utf8", "" . $letter->{title} ),
-                Message => Encode::encode( "utf8", "" . $letter->{content} ),
-                'Content-Type' => 'text/plain; charset="utf8"',
-                );
+            my $message = Koha::Email->new();
+            my %mail = $message->create_message_headers(
+                {
+                    to      => $email,
+                    from    => $library->branchemail,
+                    replyto => $library->branchreplyto,
+                    sender  => $library->branchreturnpath,
+                    subject => Encode::encode( "UTF-8", "" . $letter->{title} ),
+                    message => $letter->{'is_html'}
+                                ? _wrap_html( Encode::encode( "UTF-8", $letter->{'content'} ),
+                                              Encode::encode( "UTF-8", "" . $letter->{'title'} ))
+                                : Encode::encode( "UTF-8", "" . $letter->{'content'} ),
+                    contenttype => $letter->{'is_html'}
+                                    ? 'text/html; charset="utf-8"'
+                                    : 'text/plain; charset="utf-8"',
+                }
+            );
             sendmail(%mail) or carp $Mail::Sendmail::error;
         }
     }
             sendmail(%mail) or carp $Mail::Sendmail::error;
         }
     }
@@ -324,13 +463,11 @@ sub SendAlerts {
         # search the biblionumber
         my $strsth =  $type eq 'claimacquisition'
             ? qq{
         # search the biblionumber
         my $strsth =  $type eq 'claimacquisition'
             ? qq{
-            SELECT aqorders.*,aqbasket.*,biblio.*,biblioitems.*,aqbooksellers.*,
-            aqbooksellers.id AS booksellerid
+            SELECT aqorders.*,aqbasket.*,biblio.*,biblioitems.*
             FROM aqorders
             LEFT JOIN aqbasket ON aqbasket.basketno=aqorders.basketno
             LEFT JOIN biblio ON aqorders.biblionumber=biblio.biblionumber
             LEFT JOIN biblioitems ON aqorders.biblionumber=biblioitems.biblionumber
             FROM aqorders
             LEFT JOIN aqbasket ON aqbasket.basketno=aqorders.basketno
             LEFT JOIN biblio ON aqorders.biblionumber=biblio.biblionumber
             LEFT JOIN biblioitems ON aqorders.biblionumber=biblioitems.biblionumber
-            LEFT JOIN aqbooksellers ON aqbasket.booksellerid=aqbooksellers.id
             WHERE aqorders.ordernumber IN (
             }
             : qq{
             WHERE aqorders.ordernumber IN (
             }
             : qq{
@@ -342,6 +479,12 @@ sub SendAlerts {
             LEFT JOIN aqbooksellers ON subscription.aqbooksellerid=aqbooksellers.id
             WHERE serial.serialid IN (
             };
             LEFT JOIN aqbooksellers ON subscription.aqbooksellerid=aqbooksellers.id
             WHERE serial.serialid IN (
             };
+
+        if (!@$externalid){
+            carp "No Order seleted";
+            return { error => "no_order_seleted" };
+        }
+
         $strsth .= join( ",", @$externalid ) . ")";
         my $sthorders = $dbh->prepare($strsth);
         $sthorders->execute;
         $strsth .= join( ",", @$externalid ) . ")";
         my $sthorders = $dbh->prepare($strsth);
         $sthorders->execute;
@@ -351,14 +494,24 @@ sub SendAlerts {
           $dbh->prepare("select * from aqbooksellers where id=?");
         $sthbookseller->execute( $dataorders->[0]->{booksellerid} );
         my $databookseller = $sthbookseller->fetchrow_hashref;
           $dbh->prepare("select * from aqbooksellers where id=?");
         $sthbookseller->execute( $dataorders->[0]->{booksellerid} );
         my $databookseller = $sthbookseller->fetchrow_hashref;
+        my $addressee =  $type eq 'claimacquisition' ? 'acqprimary' : 'serialsprimary';
+        my $sthcontact =
+          $dbh->prepare("SELECT * FROM aqcontacts WHERE booksellerid=? AND $type=1 ORDER BY $addressee DESC");
+        $sthcontact->execute( $dataorders->[0]->{booksellerid} );
+        my $datacontact = $sthcontact->fetchrow_hashref;
 
         my @email;
 
         my @email;
+        my @cc;
         push @email, $databookseller->{bookselleremail} if $databookseller->{bookselleremail};
         push @email, $databookseller->{bookselleremail} if $databookseller->{bookselleremail};
-        push @email, $databookseller->{contemail}       if $databookseller->{contemail};
+        push @email, $datacontact->{email}           if ( $datacontact && $datacontact->{email} );
         unless (@email) {
             warn "Bookseller $dataorders->[0]->{booksellerid} without emails";
             return { error => "no_email" };
         }
         unless (@email) {
             warn "Bookseller $dataorders->[0]->{booksellerid} without emails";
             return { error => "no_email" };
         }
+        my $addlcontact;
+        while ($addlcontact = $sthcontact->fetchrow_hashref) {
+            push @cc, $addlcontact->{email} if ( $addlcontact && $addlcontact->{email} );
+        }
 
         my $userenv = C4::Context->userenv;
         my $letter = GetPreparedLetter (
 
         my $userenv = C4::Context->userenv;
         my $letter = GetPreparedLetter (
@@ -368,27 +521,48 @@ sub SendAlerts {
             tables => {
                 'branches'    => $userenv->{branch},
                 'aqbooksellers' => $databookseller,
             tables => {
                 'branches'    => $userenv->{branch},
                 'aqbooksellers' => $databookseller,
+                'aqcontacts'    => $datacontact,
             },
             repeat => $dataorders,
             want_librarian => 1,
         ) or return;
 
             },
             repeat => $dataorders,
             want_librarian => 1,
         ) or return;
 
+        # Remove the order tag
+        $letter->{content} =~ s/<order>(.*?)<\/order>/$1/gxms;
+
         # ... then send mail
         my %mail = (
             To => join( ',', @email),
         # ... then send mail
         my %mail = (
             To => join( ',', @email),
+            Cc             => join( ',', @cc),
             From           => $userenv->{emailaddress},
             From           => $userenv->{emailaddress},
-            Subject        => Encode::encode( "utf8", "" . $letter->{title} ),
-            Message        => Encode::encode( "utf8", "" . $letter->{content} ),
-            'Content-Type' => 'text/plain; charset="utf8"',
+            Subject        => Encode::encode( "UTF-8", "" . $letter->{title} ),
+            Message => $letter->{'is_html'}
+                            ? _wrap_html( Encode::encode( "UTF-8", $letter->{'content'} ),
+                                          Encode::encode( "UTF-8", "" . $letter->{'title'} ))
+                            : Encode::encode( "UTF-8", "" . $letter->{'content'} ),
+            'Content-Type' => $letter->{'is_html'}
+                                ? 'text/html; charset="utf-8"'
+                                : 'text/plain; charset="utf-8"',
         );
         );
-        sendmail(%mail) or carp $Mail::Sendmail::error;
+
+        $mail{'Reply-to'} = C4::Context->preference('ReplytoDefault')
+          if C4::Context->preference('ReplytoDefault');
+        $mail{'Sender'} = C4::Context->preference('ReturnpathDefault')
+          if C4::Context->preference('ReturnpathDefault');
+        $mail{'Bcc'} = $userenv->{emailaddress}
+          if C4::Context->preference("ClaimsBccCopy");
+
+        unless ( sendmail(%mail) ) {
+            carp $Mail::Sendmail::error;
+            return { error => $Mail::Sendmail::error };
+        }
 
         logaction(
             "ACQUISITION",
             $type eq 'claimissues' ? "CLAIM ISSUE" : "ACQUISITION CLAIM",
             undef,
             "To="
 
         logaction(
             "ACQUISITION",
             $type eq 'claimissues' ? "CLAIM ISSUE" : "ACQUISITION CLAIM",
             undef,
             "To="
-                . $databookseller->{contemail}
+                . join( ',', @email )
                 . " Title="
                 . $letter->{title}
                 . " Content="
                 . " Title="
                 . $letter->{title}
                 . " Content="
@@ -397,26 +571,35 @@ sub SendAlerts {
     }
    # send an "account details" notice to a newly created user
     elsif ( $type eq 'members' ) {
     }
    # send an "account details" notice to a newly created user
     elsif ( $type eq 'members' ) {
-        my $branchdetails = GetBranchDetail($externalid->{'branchcode'});
+        my $library = Koha::Libraries->find( $externalid->{branchcode} )->unblessed;
         my $letter = GetPreparedLetter (
             module => 'members',
             letter_code => $letter_code,
             branchcode => $externalid->{'branchcode'},
             tables => {
         my $letter = GetPreparedLetter (
             module => 'members',
             letter_code => $letter_code,
             branchcode => $externalid->{'branchcode'},
             tables => {
-                'branches'    => $branchdetails,
+                'branches'    => $library,
                 'borrowers' => $externalid->{'borrowernumber'},
             },
             substitute => { 'borrowers.password' => $externalid->{'password'} },
             want_librarian => 1,
         ) or return;
                 'borrowers' => $externalid->{'borrowernumber'},
             },
             substitute => { 'borrowers.password' => $externalid->{'password'} },
             want_librarian => 1,
         ) or return;
-
         return { error => "no_email" } unless $externalid->{'emailaddr'};
         return { error => "no_email" } unless $externalid->{'emailaddr'};
-        my %mail = (
-                To      =>     $externalid->{'emailaddr'},
-                From    =>  $branchdetails->{'branchemail'} || C4::Context->preference("KohaAdminEmailAddress"),
-                Subject => Encode::encode( "utf8", $letter->{'title'} ),
-                Message => Encode::encode( "utf8", $letter->{'content'} ),
-                'Content-Type' => 'text/plain; charset="utf8"',
+        my $email = Koha::Email->new();
+        my %mail  = $email->create_message_headers(
+            {
+                to      => $externalid->{'emailaddr'},
+                from    => $library->{branchemail},
+                replyto => $library->{branchreplyto},
+                sender  => $library->{branchreturnpath},
+                subject => Encode::encode( "UTF-8", "" . $letter->{'title'} ),
+                message => $letter->{'is_html'}
+                            ? _wrap_html( Encode::encode( "UTF-8", $letter->{'content'} ),
+                                          Encode::encode( "UTF-8", "" . $letter->{'title'}  ) )
+                            : Encode::encode( "UTF-8", "" . $letter->{'content'} ),
+                contenttype => $letter->{'is_html'}
+                                ? 'text/html; charset="utf-8"'
+                                : 'text/plain; charset="utf-8"',
+            }
         );
         sendmail(%mail) or carp $Mail::Sendmail::error;
     }
         );
         sendmail(%mail) or carp $Mail::Sendmail::error;
     }
@@ -467,6 +650,10 @@ sub GetPreparedLetter {
 
     if ($substitute) {
         while ( my ($token, $val) = each %$substitute ) {
 
     if ($substitute) {
         while ( my ($token, $val) = each %$substitute ) {
+            if ( $token eq 'items.content' ) {
+                $val =~ s|\n|<br/>|g if $letter->{is_html};
+            }
+
             $letter->{title} =~ s/<<$token>>/$val/g;
             $letter->{content} =~ s/<<$token>>/$val/g;
        }
             $letter->{title} =~ s/<<$token>>/$val/g;
             $letter->{content} =~ s/<<$token>>/$val/g;
        }
@@ -548,7 +735,6 @@ sub _substitute_tables {
             $values = $param;
         }
         else {
             $values = $param;
         }
         else {
-            my @pk;
             my $sth = _parseletter_sth($table);
             unless ($sth) {
                 warn "_parseletter_sth('$table') failed to return a valid sth.  No substitution will be done for that table.";
             my $sth = _parseletter_sth($table);
             unless ($sth) {
                 warn "_parseletter_sth('$table') failed to return a valid sth.  No substitution will be done for that table.";
@@ -588,7 +774,9 @@ sub _parseletter_sth {
     ($table eq 'aqbooksellers') ? "SELECT * FROM $table WHERE             id = ?"                                  :
     ($table eq 'aqorders'     ) ? "SELECT * FROM $table WHERE    ordernumber = ?"                                  :
     ($table eq 'opac_news'    ) ? "SELECT * FROM $table WHERE          idnew = ?"                                  :
     ($table eq 'aqbooksellers') ? "SELECT * FROM $table WHERE             id = ?"                                  :
     ($table eq 'aqorders'     ) ? "SELECT * FROM $table WHERE    ordernumber = ?"                                  :
     ($table eq 'opac_news'    ) ? "SELECT * FROM $table WHERE          idnew = ?"                                  :
-    ($table eq 'borrower_modifications') ? "SELECT * FROM $table WHERE borrowernumber = ? OR verification_token =?":
+    ($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 = ?" :
     undef ;
     unless ($query) {
         warn "ERROR: No _parseletter_sth query for table '$table'";
     undef ;
     unless ($query) {
         warn "ERROR: No _parseletter_sth query for table '$table'";
@@ -606,24 +794,31 @@ sub _parseletter_sth {
     parameters :
     - $letter : a hash to letter fields (title & content useful)
     - $table : the Koha table to parse.
     parameters :
     - $letter : a hash to letter fields (title & content useful)
     - $table : the Koha table to parse.
-    - $values : table record hashref
+    - $values_in : table record hashref
     parse all fields from a table, and replace values in title & content with the appropriate value
     (not exported sub, used only internally)
 
 =cut
 
 sub _parseletter {
     parse all fields from a table, and replace values in title & content with the appropriate value
     (not exported sub, used only internally)
 
 =cut
 
 sub _parseletter {
-    my ( $letter, $table, $values ) = @_;
+    my ( $letter, $table, $values_in ) = @_;
+
+    # Work on a local copy of $values_in (passed by reference) to avoid side effects
+    # in callers ( by changing / formatting values )
+    my $values = $values_in ? { %$values_in } : {};
+
+    if ( $table eq 'borrowers' && $values->{'dateexpiry'} ){
+        $values->{'dateexpiry'} = format_sqldatetime( $values->{'dateexpiry'} );
+    }
 
     if ( $table eq 'reserves' && $values->{'waitingdate'} ) {
         my @waitingdate = split /-/, $values->{'waitingdate'};
 
         $values->{'expirationdate'} = '';
 
     if ( $table eq 'reserves' && $values->{'waitingdate'} ) {
         my @waitingdate = split /-/, $values->{'waitingdate'};
 
         $values->{'expirationdate'} = '';
-        if( C4::Context->preference('ExpireReservesMaxPickUpDelay') &&
-        C4::Context->preference('ReservesMaxPickUpDelay') ) {
+        if ( C4::Context->preference('ReservesMaxPickUpDelay') ) {
             my $dt = dt_from_string();
             $dt->add( days => C4::Context->preference('ReservesMaxPickUpDelay') );
             my $dt = dt_from_string();
             $dt->add( days => C4::Context->preference('ReservesMaxPickUpDelay') );
-            $values->{'expirationdate'} = output_pref({ dt => $dt, dateonly => 1 });
+            $values->{'expirationdate'} = output_pref( { dt => $dt, dateonly => 1 } );
         }
 
         $values->{'waitingdate'} = output_pref({ dt => dt_from_string( $values->{'waitingdate'} ), dateonly => 1 });
         }
 
         $values->{'waitingdate'} = output_pref({ dt => dt_from_string( $values->{'waitingdate'} ), dateonly => 1 });
@@ -636,23 +831,50 @@ sub _parseletter {
     }
 
     while ( my ($field, $val) = each %$values ) {
     }
 
     while ( my ($field, $val) = each %$values ) {
-        my $replacetablefield = "<<$table.$field>>";
-        my $replacefield = "<<$field>>";
         $val =~ s/\p{P}$// if $val && $table=~/biblio/;
             #BZ 9886: Assuming that we want to eliminate ISBD punctuation here
             #Therefore adding the test on biblio. This includes biblioitems,
             #but excludes items. Removed unneeded global and lookahead.
 
         $val = GetAuthorisedValueByCode ('ROADTYPE', $val, 0) if $table=~/^borrowers$/ && $field=~/^streettype$/;
         $val =~ s/\p{P}$// if $val && $table=~/biblio/;
             #BZ 9886: Assuming that we want to eliminate ISBD punctuation here
             #Therefore adding the test on biblio. This includes biblioitems,
             #but excludes items. Removed unneeded global and lookahead.
 
         $val = GetAuthorisedValueByCode ('ROADTYPE', $val, 0) if $table=~/^borrowers$/ && $field=~/^streettype$/;
+
+        # Dates replacement
         my $replacedby   = defined ($val) ? $val : '';
         my $replacedby   = defined ($val) ? $val : '';
-        ($letter->{title}  ) and do {
-            $letter->{title}   =~ s/$replacetablefield/$replacedby/g;
-            $letter->{title}   =~ s/$replacefield/$replacedby/g;
-        };
-        ($letter->{content}) and do {
-            $letter->{content} =~ s/$replacetablefield/$replacedby/g;
-            $letter->{content} =~ s/$replacefield/$replacedby/g;
-        };
+        if (    $replacedby
+            and not $replacedby =~ m|0000-00-00|
+            and not $replacedby =~ m|9999-12-31|
+            and $replacedby =~ m|^\d{4}-\d{2}-\d{2}( \d{2}:\d{2}:\d{2})?$| )
+        {
+            # If the value is XXXX-YY-ZZ[ AA:BB:CC] we assume it is a date
+            my $dateonly = defined $1 ? 0 : 1; #$1 refers to the capture group wrapped in parentheses. In this case, that's the hours, minutes, seconds.
+            my $re_dateonly_filter = qr{ $field( \s* \| \s* dateonly\s*)?>> }xms;
+
+            for my $letter_field ( qw( title content ) ) {
+                my $filter_string_used = q{};
+                if ( $letter->{ $letter_field } =~ $re_dateonly_filter ) {
+                    # We overwrite $dateonly if the filter exists and we have a time in the datetime
+                    $filter_string_used = $1 || q{};
+                    $dateonly = $1 unless $dateonly;
+                }
+                eval {
+                    $replacedby = output_pref({ dt => dt_from_string( $replacedby ), dateonly => $dateonly });
+                };
+
+                if ( $letter->{ $letter_field } ) {
+                    $letter->{ $letter_field } =~ s/\Q<<$table.$field$filter_string_used>>\E/$replacedby/g;
+                    $letter->{ $letter_field } =~ s/\Q<<$field$filter_string_used>>\E/$replacedby/g;
+                }
+            }
+        }
+        # Other fields replacement
+        else {
+            for my $letter_field ( qw( title content ) ) {
+                if ( $letter->{ $letter_field } ) {
+                    $letter->{ $letter_field }   =~ s/<<$table.$field>>/$replacedby/g;
+                    $letter->{ $letter_field }   =~ s/<<$field>>/$replacedby/g;
+                }
+            }
+        }
     }
 
     if ($table eq 'borrowers' && $letter->{content}) {
     }
 
     if ($table eq 'borrowers' && $letter->{content}) {
@@ -763,7 +985,14 @@ sub SendQueuedMessages {
             _send_message_by_email( $message, $params->{'username'}, $params->{'password'}, $params->{'method'} );
         }
         elsif ( lc( $message->{'message_transport_type'} ) eq 'sms' ) {
             _send_message_by_email( $message, $params->{'username'}, $params->{'password'}, $params->{'method'} );
         }
         elsif ( lc( $message->{'message_transport_type'} ) eq 'sms' ) {
-            _send_message_by_sms( $message );
+            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'} );
+                $message->{to_address} .= '@' . $sms_provider->domain();
+                _send_message_by_email( $message, $params->{'username'}, $params->{'password'}, $params->{'method'} );
+            } else {
+                _send_message_by_sms( $message );
+            }
         }
     }
     return scalar( @$unsent_messages );
         }
     }
     return scalar( @$unsent_messages );
@@ -970,27 +1199,39 @@ sub _send_message_by_email {
 
     my $utf8   = decode('MIME-Header', $message->{'subject'} );
     $message->{subject}= encode('MIME-Header', $utf8);
 
     my $utf8   = decode('MIME-Header', $message->{'subject'} );
     $message->{subject}= encode('MIME-Header', $utf8);
-    my $subject = encode('utf8', $message->{'subject'});
-    my $content = encode('utf8', $message->{'content'});
+    my $subject = encode('UTF-8', $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 $content_type = $message->{'content_type'} || 'text/plain; charset="UTF-8"';
     my $is_html = $content_type =~ m/html/io;
-
-    my $branch_email = ( $member ) ? GetBranchDetail( $member->{'branchcode'} )->{'branchemail'} : undef;
-
-    my %sendmail_params = (
-        To   => $to_address,
-        From => $message->{'from_address'} || $branch_email || C4::Context->preference('KohaAdminEmailAddress'),
-        Subject => $subject,
-        charset => 'utf8',
-        Message => $is_html ? _wrap_html($content, $subject) : $content,
-        'content-type' => $content_type,
+    my $branch_email = undef;
+    my $branch_replyto = undef;
+    my $branch_returnpath = undef;
+    if ($member) {
+        my $library = Koha::Libraries->find( $member->{branchcode} );
+        $branch_email      = $library->branchemail;
+        $branch_replyto    = $library->branchreplyto;
+        $branch_returnpath = $library->branchreturnpath;
+    }
+    my $email = Koha::Email->new();
+    my %sendmail_params = $email->create_message_headers(
+        {
+            to      => $to_address,
+            from    => $message->{'from_address'} || $branch_email,
+            replyto => $branch_replyto,
+            sender  => $branch_returnpath,
+            subject => $subject,
+            message => $is_html ? _wrap_html( $content, $subject ) : $content,
+            contenttype => $content_type
+        }
     );
     );
+
     $sendmail_params{'Auth'} = {user => $username, pass => $password, method => $method} if $username;
     if ( my $bcc = C4::Context->preference('OverdueNoticeBcc') ) {
        $sendmail_params{ Bcc } = $bcc;
     }
 
     _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
     $sendmail_params{'Auth'} = {user => $username, pass => $password, method => $method} if $username;
     if ( my $bcc = C4::Context->preference('OverdueNoticeBcc') ) {
        $sendmail_params{ Bcc } = $bcc;
     }
 
     _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 ) ) {
         _set_message_status( { message_id => $message->{'message_id'},
                 status     => 'sent' } );
     if ( sendmail( %sendmail_params ) ) {
         _set_message_status( { message_id => $message->{'message_id'},
                 status     => 'sent' } );