Bug 13622: Display a datetime without time in a notice
[koha.git] / C4 / Letters.pm
index 37883c6..863da75 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;
@@ -23,6 +23,7 @@ use warnings;
 use MIME::Lite;
 use Mail::Sendmail;
 
 use MIME::Lite;
 use Mail::Sendmail;
 
+use C4::Koha qw(GetAuthorisedValueByCode);
 use C4::Members;
 use C4::Members::Attributes qw(GetBorrowerAttributes);
 use C4::Branch;
 use C4::Members;
 use C4::Members::Attributes qw(GetBorrowerAttributes);
 use C4::Branch;
@@ -33,17 +34,18 @@ use Koha::DateUtils;
 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 vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS);
 
 BEGIN {
 
 use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS);
 
 BEGIN {
-       require Exporter;
-       # set the version for version checking
+    require Exporter;
+    # set the version for version checking
     $VERSION = 3.07.00.049;
     $VERSION = 3.07.00.049;
-       @ISA = qw(Exporter);
-       @EXPORT = qw(
-       &GetLetters &GetPreparedLetter &GetWrappedLetter &addalert &getalert &delalert &findrelatedto &SendAlerts &GetPrintMessages
-       );
+    @ISA = qw(Exporter);
+    @EXPORT = qw(
+        &GetLetters &GetLettersAvailableForALibrary &GetLetterTemplates &DelLetter &GetPreparedLetter &GetWrappedLetter &addalert &getalert &delalert &findrelatedto &SendAlerts &GetPrintMessages &GetMessageTransportTypes
+    );
 }
 
 =head1 NAME
 }
 
 =head1 NAME
@@ -61,60 +63,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
+
+    my $letter_templates = GetLetterTemplates(
+        {
+            module => 'circulation',
+            code => 'my code',
+            branchcode => 'CPL', # '' for default,
+        }
+    );
 
 
-    <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>
+    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
+    );
 
 
-    # returns a reference to a hash of references to ALL letters...
-    my $cat = shift;
-    my %letters;
-    my $dbh = C4::Context->dbh;
-    my $sth;
-    if (defined $cat) {
-        my $query = "SELECT * FROM letter WHERE module = ? ORDER BY name";
-        $sth = $dbh->prepare($query);
-        $sth->execute($cat);
+    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 : () )
+        );
     }
     }
-    else {
-        my $query = "SELECT * FROM letter ORDER BY name";
-        $sth = $dbh->prepare($query);
-        $sth->execute;
+
+    my %letters;
+    for my $l (@$default_letters) {
+        $letters{ $l->{code} } = $l;
     }
     }
-    while ( my $letter = $sth->fetchrow_hashref ) {
-        $letters{ $letter->{'code'} } = $letter->{'name'};
+    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,9 +206,8 @@ sub GetLetters {
 #        short-term fix, our will work.
 our %letter;
 sub getletter {
 #        short-term fix, our will work.
 our %letter;
 sub getletter {
-    my ( $module, $code, $branchcode ) = @_;
-
-    $branchcode ||= '';
+    my ( $module, $code, $branchcode, $message_transport_type ) = @_;
+    $message_transport_type //= '%';
 
     if ( C4::Context->preference('IndependentBranches')
             and $branchcode
 
     if ( C4::Context->preference('IndependentBranches')
             and $branchcode
@@ -134,21 +215,61 @@ sub getletter {
 
         $branchcode = C4::Context->userenv->{'branch'};
     }
 
         $branchcode = C4::Context->userenv->{'branch'};
     }
+    $branchcode //= '';
 
 
-    if ( my $l = $letter{$module}{$code}{$branchcode} ) {
+    if ( my $l = $letter{$module}{$code}{$branchcode}{$message_transport_type} ) {
         return { %$l }; # deep copy
     }
 
     my $dbh = C4::Context->dbh;
         return { %$l }; # deep copy
     }
 
     my $dbh = C4::Context->dbh;
-    my $sth = $dbh->prepare("select * from letter where module=? and code=? and (branchcode = ? or branchcode = '') order by branchcode desc limit 1");
-    $sth->execute( $module, $code, $branchcode );
+    my $sth = $dbh->prepare(q{
+        SELECT *
+        FROM letter
+        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 );
     my $line = $sth->fetchrow_hashref
       or return;
     $line->{'content-type'} = 'text/html; charset="UTF-8"' if $line->{is_html};
     my $line = $sth->fetchrow_hashref
       or return;
     $line->{'content-type'} = 'text/html; charset="UTF-8"' if $line->{is_html};
-    $letter{$module}{$code}{$branchcode} = $line;
+    $letter{$module}{$code}{$branchcode}{$message_transport_type} = $line;
     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 : 
@@ -223,12 +344,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
     
@@ -269,25 +390,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 $letter = GetPreparedLetter (
                 module => 'serial',
                 letter_code => $letter_code,
             my $letter = GetPreparedLetter (
                 module => 'serial',
                 letter_code => $letter_code,
@@ -297,18 +427,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    => $email,
-                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    => $branchdetails->{'branchemail'},
+                    replyto => $branchdetails->{'branchreplyto'},
+                    sender  => $branchdetails->{'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;
         }
     }
@@ -318,13 +460,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{
@@ -336,6 +476,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;
@@ -345,14 +491,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 (
@@ -362,27 +518,46 @@ 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');
+
+        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="
@@ -403,14 +578,23 @@ sub SendAlerts {
             substitute => { 'borrowers.password' => $externalid->{'password'} },
             want_librarian => 1,
         ) or return;
             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    => $branchdetails->{'branchemail'},
+                replyto => $branchdetails->{'branchreplyto'},
+                sender  => $branchdetails->{'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;
     }
@@ -445,9 +629,10 @@ sub GetPreparedLetter {
     my $module      = $params{module} or croak "No module";
     my $letter_code = $params{letter_code} or croak "No letter_code";
     my $branchcode  = $params{branchcode} || '';
     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 = getletter( $module, $letter_code, $branchcode )
-        or warn( "No $module $letter_code letter"),
+    my $letter = getletter( $module, $letter_code, $branchcode, $mtt )
+        or warn( "No $module $letter_code letter transported by " . $mtt ),
             return;
 
     my $tables = $params{tables};
             return;
 
     my $tables = $params{tables};
@@ -460,6 +645,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;
        }
@@ -541,7 +730,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.";
@@ -581,7 +769,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'";
@@ -611,11 +801,15 @@ sub _parseletter {
     if ( $table eq 'reserves' && $values->{'waitingdate'} ) {
         my @waitingdate = split /-/, $values->{'waitingdate'};
 
     if ( $table eq 'reserves' && $values->{'waitingdate'} ) {
         my @waitingdate = split /-/, $values->{'waitingdate'};
 
-        my $dt = dt_from_string();
-        $dt->add( days => C4::Context->preference('ReservesMaxPickUpDelay') );
-        $values->{'expirationdate'} = output_pref( $dt, undef, 1 );
+        $values->{'expirationdate'} = '';
+        if( C4::Context->preference('ExpireReservesMaxPickUpDelay') &&
+        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->{'waitingdate'} = output_pref( dt_from_string( $values->{'waitingdate'} ), undef, 1 );
+        $values->{'waitingdate'} = output_pref({ dt => dt_from_string( $values->{'waitingdate'} ), dateonly => 1 });
 
     }
 
 
     }
 
@@ -625,22 +819,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 =~ 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}) {
@@ -835,6 +1057,24 @@ ENDSQL
     return $sth->fetchall_arrayref({});
 }
 
     return $sth->fetchall_arrayref({});
 }
 
+=head2 GetMessageTransportTypes
+
+  my @mtt = GetMessageTransportTypes();
+
+  returns an arrayref of transport types
+
+=cut
+
+sub GetMessageTransportTypes {
+    my $dbh = C4::Context->dbh();
+    my $mtts = $dbh->selectcol_arrayref("
+        SELECT message_transport_type
+        FROM message_transport_types
+        ORDER BY message_transport_type
+    ");
+    return $mtts;
+}
+
 =head2 _add_attachements
 
 named parameters:
 =head2 _add_attachements
 
 named parameters:
@@ -886,7 +1126,7 @@ sub _get_unsent_messages {
 
     my $dbh = C4::Context->dbh();
     my $statement = << 'ENDSQL';
 
     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
+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 = ?
   FROM message_queue mq
   LEFT JOIN borrowers b ON b.borrowernumber = mq.borrowernumber
  WHERE status = ?
@@ -919,9 +1159,9 @@ sub _send_message_by_email {
     my $message = shift or return;
     my ($username, $password, $method) = @_;
 
     my $message = shift or return;
     my ($username, $password, $method) = @_;
 
-    my $to_address = $message->{to_address};
+    my $member = C4::Members::GetMember( 'borrowernumber' => $message->{'borrowernumber'} );
+    my $to_address = $message->{'to_address'};
     unless ($to_address) {
     unless ($to_address) {
-        my $member = C4::Members::GetMember( 'borrowernumber' => $message->{'borrowernumber'} );
         unless ($member) {
             warn "FAIL: No 'to_address' and INVALID borrowernumber ($message->{borrowernumber})";
             _set_message_status( { message_id => $message->{'message_id'},
         unless ($member) {
             warn "FAIL: No 'to_address' and INVALID borrowernumber ($message->{borrowernumber})";
             _set_message_status( { message_id => $message->{'message_id'},
@@ -940,18 +1180,32 @@ 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 %sendmail_params = (
-        To   => $to_address,
-        From => $message->{'from_address'} || 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 $branchdetail = GetBranchDetail( $member->{'branchcode'} );
+        $branch_email = $branchdetail->{'branchemail'};
+        $branch_replyto = $branchdetail->{'branchreplyto'};
+        $branch_returnpath = $branchdetail->{'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;
     $sendmail_params{'Auth'} = {user => $username, pass => $password, method => $method} if $username;
     if ( my $bcc = C4::Context->preference('OverdueNoticeBcc') ) {
        $sendmail_params{ Bcc } = $bcc;
@@ -991,10 +1245,37 @@ $content
 EOS
 }
 
 EOS
 }
 
+sub _is_duplicate {
+    my ( $message ) = @_;
+    my $dbh = C4::Context->dbh;
+    my $count = $dbh->selectrow_array(q|
+        SELECT COUNT(*)
+        FROM message_queue
+        WHERE message_transport_type = ?
+        AND borrowernumber = ?
+        AND letter_code = ?
+        AND CAST(time_queued AS date) = CAST(NOW() AS date)
+        AND status="sent"
+        AND content = ?
+    |, {}, $message->{message_transport_type}, $message->{borrowernumber}, $message->{letter_code}, $message->{content} );
+    return $count;
+}
+
 sub _send_message_by_sms {
     my $message = shift or return;
     my $member = C4::Members::GetMember( 'borrowernumber' => $message->{'borrowernumber'} );
 sub _send_message_by_sms {
     my $message = shift or return;
     my $member = C4::Members::GetMember( 'borrowernumber' => $message->{'borrowernumber'} );
-    return unless $member->{'smsalertnumber'};
+
+    unless ( $member->{smsalertnumber} ) {
+        _set_message_status( { message_id => $message->{'message_id'},
+                               status     => 'failed' } );
+        return;
+    }
+
+    if ( _is_duplicate( $message ) ) {
+        _set_message_status( { message_id => $message->{'message_id'},
+                               status     => 'failed' } );
+        return;
+    }
 
     my $success = C4::SMS->send_sms( { destination => $member->{'smsalertnumber'},
                                        message     => $message->{'content'},
 
     my $success = C4::SMS->send_sms( { destination => $member->{'smsalertnumber'},
                                        message     => $message->{'content'},