X-Git-Url: http://git.rot13.org/?a=blobdiff_plain;f=C4%2FLetters.pm;h=cbe6559e8202e10ce48e74c68b9837159b9a29c5;hb=9f5a1bc7ebbe3954607da88b87bab6e9c2689dad;hp=4c253305031b34bb70d70f4a1ae777600637f5c8;hpb=30048f4aabeec27fdf0a4f195f82c822eddab501;p=koha.git diff --git a/C4/Letters.pm b/C4/Letters.pm index 4c25330503..cbe6559e82 100644 --- a/C4/Letters.pm +++ b/C4/Letters.pm @@ -36,8 +36,10 @@ use Koha::DateUtils; use Koha::SMS::Providers; use Koha::Email; +use Koha::Notice::Messages; use Koha::DateUtils qw( format_sqldatetime dt_from_string ); use Koha::Patrons; +use Koha::Subscriptions; use vars qw(@ISA @EXPORT @EXPORT_OK %EXPORT_TAGS); @@ -45,7 +47,7 @@ BEGIN { require Exporter; @ISA = qw(Exporter); @EXPORT = qw( - &GetLetters &GetLettersAvailableForALibrary &GetLetterTemplates &DelLetter &GetPreparedLetter &GetWrappedLetter &addalert &getalert &delalert &findrelatedto &SendAlerts &GetPrintMessages &GetMessageTransportTypes + &GetLetters &GetLettersAvailableForALibrary &GetLetterTemplates &DelLetter &GetPreparedLetter &GetWrappedLetter &SendAlerts &GetPrintMessages &GetMessageTransportTypes ); } @@ -70,6 +72,9 @@ C4::Letters - Give functions for Letters management returns informations about letters. if needed, $module filters for letters given module + DEPRECATED - You must use Koha::Notice::Templates instead + The group by clause is confusing and can lead to issues + =cut sub GetLetters { @@ -80,14 +85,14 @@ sub GetLetters { my $dbh = C4::Context->dbh; my $letters = $dbh->selectall_arrayref( q| - SELECT module, code, branchcode, name + SELECT code, module, name FROM letter WHERE 1 | . ( $module ? q| AND module = ?| : q|| ) . ( $code ? q| AND code = ?| : q|| ) . ( defined $branchcode ? q| AND branchcode = ?| : q|| ) - . q| GROUP BY code ORDER BY name|, { Slice => {} } + . q| GROUP BY code, module, name ORDER BY name|, { Slice => {} } , ( $module ? $module : () ) , ( $code ? $code : () ) , ( defined $branchcode ? $branchcode : () ) @@ -262,108 +267,6 @@ sub DelLetter { , undef, $branchcode, $module, $code, ( $mtt ? $mtt : () ), ( $lang ? $lang : () ) ); } -=head2 addalert ($borrowernumber, $type, $externalid) - - parameters : - - $borrowernumber : the number of the borrower subscribing to the alert - - $type : the type of alert. - - $externalid : the primary key of the object to put alert on. For issues, the alert is made on subscriptionid. - - create an alert and return the alertid (primary key) - -=cut - -sub addalert { - my ( $borrowernumber, $type, $externalid ) = @_; - my $dbh = C4::Context->dbh; - my $sth = - $dbh->prepare( - "insert into alert (borrowernumber, type, externalid) values (?,?,?)"); - $sth->execute( $borrowernumber, $type, $externalid ); - - # get the alert number newly created and return it - my $alertid = $dbh->{'mysql_insertid'}; - return $alertid; -} - -=head2 delalert ($alertid) - - parameters : - - alertid : the alert id - deletes the alert - -=cut - -sub delalert { - my $alertid = shift or die "delalert() called without valid argument (alertid)"; # it's gonna die anyway. - $debug and warn "delalert: deleting alertid $alertid"; - my $sth = C4::Context->dbh->prepare("delete from alert where alertid=?"); - $sth->execute($alertid); -} - -=head2 getalert ([$borrowernumber], [$type], [$externalid]) - - parameters : - - $borrowernumber : the number of the borrower subscribing to the alert - - $type : the type of alert. - - $externalid : the primary key of the object to put alert on. For issues, the alert is made on subscriptionid. - all parameters NON mandatory. If a parameter is omitted, the query is done without the corresponding parameter. For example, without $externalid, returns all alerts for a borrower on a topic. - -=cut - -sub getalert { - my ( $borrowernumber, $type, $externalid ) = @_; - my $dbh = C4::Context->dbh; - my $query = "SELECT a.*, b.branchcode FROM alert a JOIN borrowers b USING(borrowernumber) WHERE 1"; - my @bind; - if ($borrowernumber and $borrowernumber =~ /^\d+$/) { - $query .= " AND borrowernumber=?"; - push @bind, $borrowernumber; - } - if ($type) { - $query .= " AND type=?"; - push @bind, $type; - } - if ($externalid) { - $query .= " AND externalid=?"; - push @bind, $externalid; - } - my $sth = $dbh->prepare($query); - $sth->execute(@bind); - return $sth->fetchall_arrayref({}); -} - -=head2 findrelatedto($type, $externalid) - - parameters : - - $type : the type of alert - - $externalid : the id of the "object" to query - - In the table alert, a "id" is stored in the externalid field. This "id" is related to another table, depending on the type of the alert. - When type=issue, the id is related to a subscriptionid and this sub returns the name of the biblio. - -=cut - -# outmoded POD: -# When type=virtual, the id is related to a virtual shelf and this sub returns the name of the sub - -sub findrelatedto { - my $type = shift or return; - my $externalid = shift or return; - my $q = ($type eq 'issue' ) ? -"select title as result from subscription left join biblio on subscription.biblionumber=biblio.biblionumber where subscriptionid=?" : - ($type eq 'borrower') ? -"select concat(firstname,' ',surname) from borrowers where borrowernumber=?" : undef; - unless ($q) { - warn "findrelatedto(): Illegal type '$type'"; - return; - } - my $sth = C4::Context->dbh->prepare($q); - $sth->execute($externalid); - my ($result) = $sth->fetchrow; - return $result; -} - =head2 SendAlerts my $err = &SendAlerts($type, $externalid, $letter_code); @@ -412,22 +315,21 @@ sub SendAlerts { return; my %letter; - # find the list of borrowers to alert - my $alerts = getalert( '', 'issue', $subscriptionid ); - foreach (@$alerts) { - my $patron = Koha::Patrons->find( $_->{borrowernumber} ); - next unless $patron; # Just in case + # find the list of subscribers to notify + my $subscription = Koha::Subscriptions->find( $subscriptionid ); + my $subscribers = $subscription->subscribers; + while ( my $patron = $subscribers->next ) { my $email = $patron->email or next; # warn "sending issues..."; my $userenv = C4::Context->userenv; - my $library = Koha::Libraries->find( $_->{branchcode} ); + my $library = $patron->library; my $letter = GetPreparedLetter ( module => 'serial', letter_code => $letter_code, branchcode => $userenv->{branch}, tables => { - 'branches' => $_->{branchcode}, + 'branches' => $library->branchcode, 'biblio' => $biblionumber, 'biblioitems' => $biblionumber, 'borrowers' => $patron->unblessed, @@ -455,7 +357,7 @@ sub SendAlerts { : 'text/plain; charset="utf-8"', } ); - unless( sendmail(%mail) ) { + unless( Mail::Sendmail::sendmail(%mail) ) { carp $Mail::Sendmail::error; return { error => $Mail::Sendmail::error }; } @@ -491,22 +393,23 @@ sub SendAlerts { if ($type eq 'claimissues') { $strsth = qq{ - SELECT serial.*,subscription.*, biblio.*, aqbooksellers.*, + SELECT serial.*,subscription.*, biblio.*, biblioitems.*, aqbooksellers.*, aqbooksellers.id AS booksellerid FROM serial LEFT JOIN subscription ON serial.subscriptionid=subscription.subscriptionid LEFT JOIN biblio ON serial.biblionumber=biblio.biblionumber + LEFT JOIN biblioitems ON serial.biblionumber = biblioitems.biblionumber LEFT JOIN aqbooksellers ON subscription.aqbooksellerid=aqbooksellers.id WHERE serial.serialid IN ( }; if (!@$externalid){ - carp "No Order selected"; - return { error => "no_order_selected" }; + carp "No issues selected"; + return { error => "no_issues_selected" }; } $strsth .= join( ",", ('?') x @$externalid ) . ")"; - $action = "CLAIM ISSUE"; + $action = "SERIAL CLAIM"; $sthorders = $dbh->prepare($strsth); $sthorders->execute( @$externalid ); $dataorders = $sthorders->fetchall_arrayref( {} ); @@ -600,7 +503,7 @@ sub SendAlerts { if C4::Context->preference("ClaimsBccCopy"); } - unless ( sendmail(%mail) ) { + unless ( Mail::Sendmail::sendmail(%mail) ) { carp $Mail::Sendmail::error; return { error => $Mail::Sendmail::error }; } @@ -624,6 +527,7 @@ sub SendAlerts { module => 'members', letter_code => $letter_code, branchcode => $externalid->{'branchcode'}, + lang => $externalid->{lang} || 'default', tables => { 'branches' => $library, 'borrowers' => $externalid->{'borrowernumber'}, @@ -649,7 +553,7 @@ sub SendAlerts { : 'text/plain; charset="utf-8"', } ); - unless( sendmail(%mail) ) { + unless( Mail::Sendmail::sendmail(%mail) ) { carp $Mail::Sendmail::error; return { error => $Mail::Sendmail::error }; } @@ -685,18 +589,22 @@ sub SendAlerts { sub GetPreparedLetter { my %params = @_; - my $module = $params{module} or croak "No module"; - my $letter_code = $params{letter_code} or croak "No letter_code"; - my $branchcode = $params{branchcode} || ''; - my $mtt = $params{message_transport_type} || 'email'; - my $lang = $params{lang} || 'default'; - - my $letter = getletter( $module, $letter_code, $branchcode, $mtt, $lang ); + my $letter = $params{letter}; unless ( $letter ) { - $letter = getletter( $module, $letter_code, $branchcode, $mtt, 'default' ) - or warn( "No $module $letter_code letter transported by " . $mtt ), - return; + my $module = $params{module} or croak "No module"; + my $letter_code = $params{letter_code} or croak "No letter_code"; + my $branchcode = $params{branchcode} || ''; + my $mtt = $params{message_transport_type} || 'email'; + my $lang = $params{lang} || 'default'; + + $letter = getletter( $module, $letter_code, $branchcode, $mtt, $lang ); + + unless ( $letter ) { + $letter = getletter( $module, $letter_code, $branchcode, $mtt, 'default' ) + or warn( "No $module $letter_code letter transported by " . $mtt ), + return; + } } my $tables = $params{tables} || {}; @@ -877,7 +785,7 @@ sub _parseletter { my $values = $values_in ? { %$values_in } : {}; if ( $table eq 'borrowers' && $values->{'dateexpiry'} ){ - $values->{'dateexpiry'} = format_sqldatetime( $values->{'dateexpiry'} ); + $values->{'dateexpiry'} = output_pref({ dt => dt_from_string( $values->{'dateexpiry'} ), dateonly => 1 }); } if ( $table eq 'reserves' && $values->{'waitingdate'} ) { @@ -1023,19 +931,44 @@ ENDSQL =head2 SendQueuedMessages ([$hashref]) - my $sent = SendQueuedMessages( { verbose => 1 } ); + my $sent = SendQueuedMessages({ + letter_code => $letter_code, + borrowernumber => $who_letter_is_for, + limit => 50, + verbose => 1, + type => 'sms', + }); + +Sends all of the 'pending' items in the message queue, unless +parameters are passed. + +The letter_code, borrowernumber and limit parameters are used +to build a parameter set for _get_unsent_messages, thus limiting +which pending messages will be processed. They are all optional. -sends all of the 'pending' items in the message queue. +The verbose parameter can be used to generate debugging output. +It is also optional. -returns number of messages sent. +Returns number of messages sent. =cut sub SendQueuedMessages { my $params = shift; - my $unsent_messages = _get_unsent_messages(); + my $which_unsent_messages = { + 'limit' => $params->{'limit'} // 0, + 'borrowernumber' => $params->{'borrowernumber'} // q{}, + 'letter_code' => $params->{'letter_code'} // q{}, + 'type' => $params->{'type'} // q{}, + }; + my $unsent_messages = _get_unsent_messages( $which_unsent_messages ); MESSAGE: foreach my $message ( @$unsent_messages ) { + my $message_object = Koha::Notice::Messages->find( $message->{message_id} ); + # If this fails the database is unwritable and we won't manage to send a message that continues to be marked 'pending' + $message_object->make_column_dirty('status'); + return unless $message_object->store; + # warn Data::Dumper->Dump( [ $message ], [ 'message' ] ); warn sprintf( 'sending %s message to patron: %s', $message->{'message_transport_type'}, @@ -1055,14 +988,22 @@ sub SendQueuedMessages { _set_message_status( { message_id => $message->{'message_id'}, status => 'failed' } ); next MESSAGE; } - $message->{to_address} ||= $patron->smsalertnumber; - unless ( $message->{to_address} && $patron->smsalertnumber ) { + unless ( $patron->smsalertnumber ) { _set_message_status( { message_id => $message->{'message_id'}, status => 'failed' } ); warn sprintf( "No smsalertnumber found for patron %s!", $message->{'borrowernumber'} ) if $params->{'verbose'} or $debug; next MESSAGE; } + $message->{to_address} = $patron->smsalertnumber; #Sometime this is set to email - sms should always use smsalertnumber $message->{to_address} .= '@' . $sms_provider->domain(); - _update_message_to_address($message->{'message_id'},$message->{to_address}); + + # Check for possible from_address override + my $from_address = C4::Context->preference('EmailSMSSendDriverFromAddress'); + if ($from_address && $message->{from_address} ne $from_address) { + $message->{from_address} = $from_address; + _update_message_from_address($message->{'message_id'}, $message->{from_address}); + } + + _update_message_to_address($message->{'message_id'}, $message->{to_address}); _send_message_by_email( $message, $params->{'username'}, $params->{'password'}, $params->{'method'} ); } else { _send_message_by_sms( $message ); @@ -1220,15 +1161,15 @@ sub ResendMessage { =head2 _add_attachements -named parameters: -letter - the standard letter hashref -attachments - listref of attachments. each attachment is a hashref of: - type - the mime type, like 'text/plain' - content - the actual attachment - filename - the name of the attachment. -message - a MIME::Lite object to attach these to. + named parameters: + letter - the standard letter hashref + attachments - listref of attachments. each attachment is a hashref of: + type - the mime type, like 'text/plain' + content - the actual attachment + filename - the name of the attachment. + message - a MIME::Lite object to attach these to. -returns your letter object, with the content updated. + returns your letter object, with the content updated. =cut @@ -1264,27 +1205,49 @@ sub _add_attachments { } +=head2 _get_unsent_messages + + This function's parameter hash reference takes the following + optional named parameters: + message_transport_type: method of message sending (e.g. email, sms, etc.) + borrowernumber : who the message is to be sent + letter_code : type of message being sent (e.g. PASSWORD_RESET) + limit : maximum number of messages to send + + This function returns an array of matching hash referenced rows from + message_queue with some borrower information added. + +=cut + sub _get_unsent_messages { my $params = shift; my $dbh = C4::Context->dbh(); - my $statement = << 'ENDSQL'; -SELECT mq.message_id, mq.borrowernumber, mq.subject, mq.content, mq.message_transport_type, mq.status, mq.time_queued, mq.from_address, mq.to_address, mq.content_type, b.branchcode, mq.letter_code - FROM message_queue mq - LEFT JOIN borrowers b ON b.borrowernumber = mq.borrowernumber - WHERE status = ? -ENDSQL + my $statement = qq{ + SELECT mq.message_id, mq.borrowernumber, mq.subject, mq.content, mq.message_transport_type, mq.status, mq.time_queued, mq.from_address, mq.to_address, mq.content_type, b.branchcode, mq.letter_code + FROM message_queue mq + LEFT JOIN borrowers b ON b.borrowernumber = mq.borrowernumber + WHERE status = ? + }; my @query_params = ('pending'); if ( ref $params ) { if ( $params->{'message_transport_type'} ) { - $statement .= ' AND message_transport_type = ? '; + $statement .= ' AND mq.message_transport_type = ? '; push @query_params, $params->{'message_transport_type'}; } if ( $params->{'borrowernumber'} ) { - $statement .= ' AND borrowernumber = ? '; + $statement .= ' AND mq.borrowernumber = ? '; push @query_params, $params->{'borrowernumber'}; } + if ( $params->{'letter_code'} ) { + $statement .= ' AND mq.letter_code = ? '; + push @query_params, $params->{'letter_code'}; + } + if ( $params->{'type'} ) { + $statement .= ' AND message_transport_type = ? '; + push @query_params, $params->{'type'}; + } if ( $params->{'limit'} ) { $statement .= ' limit ? '; push @query_params, $params->{'limit'}; @@ -1311,7 +1274,7 @@ sub _send_message_by_email { status => 'failed' } ); return; } - $to_address = C4::Members::GetNoticeEmailAddress( $message->{'borrowernumber'} ); + $to_address = $patron->notice_email_address; unless ($to_address) { # warn "FAIL: No 'to_address' and no email for " . ($member->{surname} ||'') . ", borrowernumber ($message->{borrowernumber})"; # warning too verbose for this more common case? @@ -1321,9 +1284,10 @@ sub _send_message_by_email { } } - my $utf8 = decode('MIME-Header', $message->{'subject'} ); - $message->{subject}= encode('MIME-Header', $utf8); - my $subject = encode('UTF-8', $message->{'subject'}); + # Encode subject line separately + $message->{subject} = encode('MIME-Header', $message->{'subject'} ); + my $subject = $message->{'subject'}; + my $content = encode('UTF-8', $message->{'content'}); my $content_type = $message->{'content_type'} || 'text/plain; charset="UTF-8"'; my $is_html = $content_type =~ m/html/io; @@ -1356,7 +1320,7 @@ sub _send_message_by_email { _update_message_to_address($message->{'message_id'},$to_address) unless $message->{to_address}; #if initial message address was empty, coming here means that a to address was found and queue should be updated - if ( sendmail( %sendmail_params ) ) { + if ( Mail::Sendmail::sendmail( %sendmail_params ) ) { _set_message_status( { message_id => $message->{'message_id'}, status => 'sent' } ); return 1; @@ -1435,6 +1399,12 @@ sub _update_message_to_address { $dbh->do('UPDATE message_queue SET to_address=? WHERE message_id=?',undef,($to,$id)); } +sub _update_message_from_address { + my ($message_id, $from_address) = @_; + my $dbh = C4::Context->dbh(); + $dbh->do('UPDATE message_queue SET from_address = ? WHERE message_id = ?', undef, ($from_address, $message_id)); +} + sub _set_message_status { my $params = shift or return; @@ -1473,7 +1443,8 @@ sub _process_tt { my $tt_params = { %{ _get_tt_params( $tables ) }, %{ _get_tt_params( $loops, 'is_a_loop' ) }, %$substitute }; - $content = qq|[% USE KohaDates %]$content|; + $content = add_tt_filters( $content ); + $content = qq|[% USE KohaDates %][% USE Remove_MARC_punctuation %]$content|; my $output; $template->process( \$content, $tt_params, \$output ) || croak "ERROR PROCESSING TEMPLATE: " . $template->error(); @@ -1500,6 +1471,12 @@ sub _get_tt_params { plural => 'biblios', pk => 'biblionumber', }, + biblioitems => { + module => 'Koha::Biblioitems', + singular => 'biblioitem', + plural => 'biblioitems', + pk => 'biblioitemnumber', + }, borrowers => { module => 'Koha::Patrons', singular => 'borrower', @@ -1525,7 +1502,7 @@ sub _get_tt_params { pk => 'idnew', }, aqorders => { - module => 'Koha::Tmp::Orders', # Should Koha::Acquisition::Orders when will be based on Koha::Objects + module => 'Koha::Acquisition::Orders', singular => 'order', plural => 'orders', pk => 'ordernumber', @@ -1566,6 +1543,12 @@ sub _get_tt_params { plural => 'old_checkouts', fk => 'itemnumber', }, + overdues => { + module => 'Koha::Checkouts', + singular => 'overdue', + plural => 'overdues', + fk => 'itemnumber', + }, borrower_modifications => { module => 'Koha::Patron::Modifications', singular => 'patron_modification', @@ -1590,7 +1573,18 @@ sub _get_tt_params { croak "ERROR processing table $table. Wrong API call."; } my $key = $pk ? $pk : $fk; - my $objects = $module->search( { $key => { -in => $values } } ); + # $key does not come from user input + my $objects = $module->search( + { $key => $values }, + { + # We want to retrieve the data in the same order + # FIXME MySQLism + # field is a MySQLism, but they are no other way to do it + # To be generic we could do it in perl, but we will need to fetch + # all the data then order them + @$values ? ( order_by => \[ "field($key, " . join( ', ', @$values ) . ")" ] ) : () + } + ); $params->{ $config->{$table}->{plural} } = $objects; } elsif ( $ref eq q{} || $ref eq 'HASH' ) { @@ -1632,6 +1626,23 @@ sub _get_tt_params { return $params; } +=head3 add_tt_filters + +$content = add_tt_filters( $content ); + +Add TT filters to some specific fields if needed. + +For now we only add the Remove_MARC_punctuation TT filter to biblio and biblioitem fields + +=cut + +sub add_tt_filters { + my ( $content ) = @_; + $content =~ s|\[%\s*biblio\.(.*?)\s*%\]|[% biblio.$1 \| \$Remove_MARC_punctuation %]|gxms; + $content =~ s|\[%\s*biblioitem\.(.*?)\s*%\]|[% biblioitem.$1 \| \$Remove_MARC_punctuation %]|gxms; + return $content; +} + =head2 get_item_content my $item = Koha::Items->find(...)->unblessed;