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