Bug 28290: Don't send subfields to 'as_string' if none to send
[koha.git] / C4 / Message.pm
1 package C4::Message;
2
3 # Copyright Liblime 2009
4 # Copyright Catalyst IT 2012
5 #
6 # This file is part of Koha.
7 #
8 # Koha is free software; you can redistribute it and/or modify it
9 # under the terms of the GNU General Public License as published by
10 # the Free Software Foundation; either version 3 of the License, or
11 # (at your option) any later version.
12 #
13 # Koha is distributed in the hope that it will be useful, but
14 # WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 # GNU General Public License for more details.
17 #
18 # You should have received a copy of the GNU General Public License
19 # along with Koha; if not, see <http://www.gnu.org/licenses>.
20
21
22 use strict;
23 use warnings;
24 use C4::Context;
25 use C4::Letters qw( GetPreparedLetter EnqueueLetter );
26 use YAML::XS qw( Dump );
27 use Encode;
28 use Carp qw( carp );
29
30 =head1 NAME
31
32 C4::Message - object for messages in the message_queue table
33
34 =head1 SYNOPSIS
35
36 How to add a new message to the queue:
37
38   use C4::Message;
39   use C4::Items;
40   my $borrower = { borrowernumber => 1 };
41   my $item     = Koha::Items->find($itemnumber)->unblessed;
42   my $letter =  C4::Letters::GetPreparedLetter (
43       module => 'circulation',
44       letter_code => 'CHECKOUT',
45       branchcode => $branch,
46       tables => {
47           'biblio', $item->{biblionumber},
48           'biblioitems', $item->{biblionumber},
49       },
50   );
51   C4::Message->enqueue($letter, $borrower->{borrowernumber}, 'email');
52
53 How to update a borrower's last checkout message:
54
55   use C4::Message;
56   my $borrower = { borrowernumber => 1 };
57   my $message  = C4::Message->find_last_message($borrower, 'CHECKOUT', 'email');
58   $message->append("you also checked out some other book....");
59   $message->update;
60
61 =head1 DESCRIPTION
62
63 This module presents an OO interface to the message_queue.  Previously, 
64 you could only add messages to the message_queue via 
65 C<C4::Letters::EnqueueMessage()>.  With this module, you can also get 
66 previously inserted messages, manipulate them, and save them back to the 
67 database.
68
69 =cut
70
71
72 our $AUTOLOAD;
73
74
75 =head2 Class Methods
76
77 =head3 C4::Message->new(\%attributes)
78
79 This method creates an in-memory version of a message object.
80
81 =cut
82
83 # C4::Message->new(\%attributes) -- constructor
84 sub new {
85     my ($class, $opts) = @_;
86     $opts ||= {};
87     bless {%$opts} => $class;
88 }
89
90
91 =head3 C4::Message->find($id)
92
93 This method searches the message_queue table for a row with the given
94 C<message_id> and it'll return a C4::Message object if it finds one.
95
96 =cut
97
98 # C4::Message->find($id) -- find a message by its message_id
99 sub find {
100     my ($class, $id) = @_;
101     my $dbh = C4::Context->dbh;
102     my $msgs = $dbh->selectall_arrayref(
103         qq{SELECT * FROM message_queue WHERE message_id = ?},
104         { Slice => {} },
105         $id,
106     );
107     if (@$msgs) {
108         return $class->new($msgs->[0]);
109     } else {
110         return;
111     }
112 }
113
114 =head3 C4::Message->find_last_message($borrower, $letter_code, $transport)
115
116 This method is used to get the borrower's most recent, pending, check-in or
117 checkout message.  (This makes it possible to add more information to the
118 message before it gets sent out.)
119
120 =cut
121
122 # C4::Message->find_last_message($borrower, $letter_code, $transport)
123 # -- get the borrower's most recent pending checkin or checkout notification
124 sub find_last_message {
125     my ($class, $borrower, $letter_code, $transport) = @_;
126     # $type is the message_transport_type
127     $transport ||= 'email';
128     my $dbh = C4::Context->dbh;
129     my $msgs = $dbh->selectall_arrayref(
130         qq{
131             SELECT *
132             FROM   message_queue
133             WHERE  status                 = 'pending'
134             AND    borrowernumber         = ?
135             AND    letter_code            = ?
136             AND    message_transport_type = ?
137         },
138         { Slice => {} },
139         $borrower->{borrowernumber},
140         $letter_code,
141         $transport,
142     );
143     if (@$msgs) {
144         return $class->new($msgs->[0]);
145     } else {
146         return;
147     }
148 }
149
150
151 =head3 C4::Message->enqueue($letter, $borrower, $transport)
152
153 This is a front-end for C<C4::Letters::EnqueueLetter()> that adds metadata to
154 the message.
155
156 =cut
157
158 # C4::Message->enqueue($letter, $borrower, $transport)
159 sub enqueue {
160     my ($class, $letter, $borrower, $transport) = @_;
161     my $metadata   = _metadata($letter);
162     my $to_address = _to_address($borrower, $transport);
163
164     # Same as render_metadata
165     my $format ||= sub { $_[0] || "" };
166     my $body = join('', map { $format->($_) } @{$metadata->{body}});
167     $letter->{content} = $metadata->{header} . $body . $metadata->{footer};
168
169     $letter->{metadata} = Encode::decode_utf8(Dump($metadata));
170     C4::Letters::EnqueueLetter({
171         letter                 => $letter,
172         borrowernumber         => $borrower->{borrowernumber},
173         message_transport_type => $transport,
174         to_address             => $to_address,
175     });
176 }
177
178 # based on message $transport, pick an appropriate address to send to
179 sub _to_address {
180     my ($borrower, $transport) = @_;
181     my $address;
182     if ($transport eq 'email') {
183         $address = $borrower->{email}
184             || $borrower->{emailpro}
185             || $borrower->{B_email};
186     } elsif ($transport eq 'sms') {
187         $address = $borrower->{smsalertnumber};
188     } else {
189         warn "'$transport' is an unknown message transport.";
190     }
191     if (not defined $address) {
192         warn "An appropriate $transport address "
193             . "for borrower $borrower->{userid} "
194             . "could not be found.";
195     }
196     return $address;
197 }
198
199 # _metadata($letter) -- return the letter split into head/body/footer
200 sub _metadata {
201     my ($letter) = @_;
202     if ($letter->{content} =~ /----/) {
203         my ($header, $body, $footer) = split(/----\r?\n?/, $letter->{content});
204         return {
205             header => $header,
206             body   => [$body],
207             footer => $footer,
208         };
209     } else {
210         return {
211             header => '',
212             body   => [$letter->{content}],
213             footer => '',
214         };
215     }
216 }
217
218 =head2 Instance Methods
219
220 =head3 $message->update()
221
222 This saves the $message object back to the database.  It needs to have
223 already been created via C<enqueue> for this to work.
224
225 =cut
226
227 # $object->update -- save object to database
228 sub update {
229     my ($self) = @_;
230     my $dbh = C4::Context->dbh;
231     $dbh->do(
232         qq{
233             UPDATE message_queue
234             SET
235                 borrowernumber         = ?,
236                 subject                = ?,
237                 content                = ?,
238                 metadata               = ?,
239                 letter_code            = ?,
240                 message_transport_type = ?,
241                 status                 = ?,
242                 time_queued            = ?,
243                 to_address             = ?,
244                 from_address           = ?,
245                 content_type           = ?
246             WHERE message_id = ?
247         },
248         {},
249         $self->borrowernumber,
250         $self->subject,
251         $self->content,
252         $self->{metadata}, # we want the raw YAML here
253         $self->letter_code,
254         $self->message_transport_type,
255         $self->status,
256         $self->time_queued,
257         $self->to_address,
258         $self->from_address,
259         $self->content_type,
260         $self->message_id
261     );
262 }
263
264 =head3 $message->metadata(\%new_metadata)
265
266 This method automatically serializes and deserializes the metadata
267 attribute.  (It is stored in YAML format.)
268
269 =cut
270
271 # $object->metadata -- this is a YAML serialized column that contains a
272 # structured representation of $object->content
273 sub metadata {
274     my ($self, $data) = @_;
275     if ($data) {
276         $data->{header} ||= '';
277         $data->{body}   ||= [];
278         $data->{footer} ||= '';
279         $self->{metadata} = Encode::decode_utf8(Dump($data));
280         $self->content($self->render_metadata);
281         return $data;
282     } else {
283         return YAML::XS::Load(Encode::encode_utf8($self->{metadata}));
284     }
285 }
286
287 # turn $object->metadata into a string suitable for $object->content
288 sub render_metadata {
289     my ($self, $format) = @_;
290     $format ||= sub { $_[0] || "" };
291     my $metadata = $self->metadata;
292     my $body     = $metadata->{body};
293     my $text     = join('', map { $format->($_) } @$body);
294     return $metadata->{header} . $text . $metadata->{footer};
295 }
296
297 =head3 $message->append(\%letter)
298
299 If passed a hashref, this method will assume that the hashref is in the form
300 that C<C4::Letters::getletter()> returns.  It will append the body of the
301 letter to the message.
302
303 =head3 $message->append($string)
304
305 If passed a string, it'll append the string to the message.
306
307 =cut
308
309 # $object->append($letter_or_item) -- add a new item to a message's content
310 sub append {
311     my ($self, $letter_or_item, $format) = @_;
312     my ( $item, $header, $footer );
313     if (ref($letter_or_item)) {
314         my $letter   = $letter_or_item;
315         my $metadata = _metadata($letter);
316         $header = $metadata->{header};
317         $footer = $metadata->{footer};
318         $item = $metadata->{body}->[0];
319     } else {
320         $item = $letter_or_item;
321     }
322     if (not $self->metadata) {
323         carp "Can't append to messages that don't have metadata.";
324         return;
325     }
326     my $metadata = $self->metadata;
327     push @{$metadata->{body}}, $item;
328     $metadata->{header} = $header;
329     $metadata->{footer} = $footer;
330     $self->metadata($metadata);
331     my $new_content = $self->render_metadata($format);
332     return $self->content($new_content);
333 }
334
335 =head2 Attributes Accessors
336
337 =head3 $message->message_id
338
339 =cut
340
341 =head3 $message->borrowernumber
342
343 =cut
344
345 =head3 $message->subject
346
347 =cut
348
349 =head3 $message->content
350
351 =cut
352
353 =head3 $message->metadata
354
355 =cut
356
357 =head3 $message->letter_code
358
359 =cut
360
361 =head3 $message->message_transport_type
362
363 =cut
364
365 =head3 $message->status
366
367 =cut
368
369 =head3 $message->time_queued
370
371 =cut
372
373 =head3 $message->to_address
374
375 =cut
376
377 =head3 $message->from_address
378
379 =cut
380
381 =head3 $message->content_type
382
383 =cut
384
385 # $object->$method -- treat keys as methods
386 sub AUTOLOAD {
387     my ($self, @args) = @_;
388     my $attr = $AUTOLOAD;
389     $attr =~ s/.*://;
390     if (ref($self->{$attr}) eq 'CODE') {
391         $self->{$attr}->($self, @args);
392     } else {
393         if (@args) {
394             $self->{$attr} = $args[0];
395         } else {
396             $self->{$attr};
397         }
398     }
399 }
400
401 sub DESTROY { }
402
403 1;
404
405 =head1 SEE ALSO
406
407 L<C4::Circulation>, L<C4::Letters>, L<C4::Members::Messaging>
408
409 =head1 AUTHOR
410
411 John Beppu <john.beppu@liblime.com>
412
413 =cut