use Modern::Perl;
-use MIME::Lite;
use Carp qw( carp croak );
use Template;
use Module::Load::Conditional qw( can_load );
-use Try::Tiny qw( catch try );
+use Try::Tiny;
use C4::Members;
use C4::Log qw( logaction );
use C4::SMS;
use C4::Templates;
-use Koha::DateUtils qw( dt_from_string output_pref );
use Koha::SMS::Providers;
use Koha::Email;
use Koha::Notice::Messages;
use Koha::Notice::Templates;
use Koha::DateUtils qw( dt_from_string output_pref );
+use Koha::Auth::TwoFactorAuth;
use Koha::Patrons;
use Koha::SMTP::Servers;
use Koha::Subscriptions;
+use constant SERIALIZED_EMAIL_CONTENT_TYPE => 'message/rfc822';
+
our (@ISA, @EXPORT_OK);
BEGIN {
require Exporter;
}
if ( $type eq 'orderacquisition') {
- my $basketno = $externalid;
+ $basketno = $externalid;
$strsth = qq{
SELECT aqorders.*,aqbasket.*,biblio.*,biblioitems.*
FROM aqorders
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="
. $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;
$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};
}
$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,
}
);
($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 = ?" :
($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'";
# 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 });
}
$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;
$params->{'letter'} = _add_attachments(
{ letter => $params->{'letter'},
attachments => $params->{'attachments'},
- message => MIME::Lite->new( Type => 'multipart/mixed' ),
}
);
}
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, failure_code )
-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
'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 ) {
=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;
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
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 = ?';
);
return;
};
- my $email = try {
- Koha::Email->create(
- {
- to => $to_address,
- (
- C4::Context->preference('NoticeBcc')
- ? ( bcc => C4::Context->preference('NoticeBcc') )
- : ()
- ),
- from => $from_address,
- reply_to => $message->{'reply_address'} || $branch_replyto,
- sender => $branch_returnpath,
- subject => "" . $message->{subject}
+ my $email;
+
+ try {
+
+ my $params = {
+ to => $to_address,
+ (
+ C4::Context->preference('NoticeBcc')
+ ? ( bcc => C4::Context->preference('NoticeBcc') )
+ : ()
+ ),
+ from => $from_address,
+ reply_to => $message->{'reply_address'} || $branch_replyto,
+ sender => $branch_returnpath,
+ subject => "" . $message->{subject}
+ };
+
+ 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' ) {
};
return unless $email;
- if ( $is_html ) {
- $email->html_body(
- _wrap_html( $content, $subject )
- );
- }
- else {
- $email->text_body( $content );
- }
-
my $smtp_server;
if ( $library ) {
$smtp_server = $library->smtp_server;
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'},
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);
}
) 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;
}
module => 'Koha::Old::Checkouts',
singular => 'old_checkout',
plural => 'old_checkouts',
- fk => 'itemnumber',
+ pk => 'issue_id',
},
overdues => {
module => 'Koha::Checkouts',