Bug 17499: (follow-up) Rename to Koha::Patron::MessagePreference
[koha.git] / Koha / Patron / MessagePreference.pm
1 package Koha::Patron::MessagePreference;
2
3 # Copyright Koha-Suomi Oy 2016
4 #
5 # This file is part of Koha.
6 #
7 # Koha is free software; you can redistribute it and/or modify it
8 # under the terms of the GNU General Public License as published by
9 # the Free Software Foundation; either version 3 of the License, or
10 # (at your option) any later version.
11 #
12 # Koha is distributed in the hope that it will be useful, but
13 # WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU General Public License for more details.
16 #
17 # You should have received a copy of the GNU General Public License
18 # along with Koha; if not, see <http://www.gnu.org/licenses>.
19
20 use Modern::Perl;
21
22 use Koha::Database;
23 use Koha::Exceptions;
24 use Koha::Exceptions::Patron::MessagePreference;
25 use Koha::Exceptions::Patron::MessagePreference::Transport;
26 use Koha::Exceptions::Patron::Category;
27 use Koha::Patron::Categories;
28 use Koha::Patron::MessagePreference::Attributes;
29 use Koha::Patron::MessagePreferences;
30 use Koha::Patron::MessagePreference::Transport::Preferences;
31 use Koha::Patron::MessagePreference::Transport::Types;
32 use Koha::Patron::MessagePreference::Transports;
33 use Koha::Patrons;
34
35 use base qw(Koha::Object);
36
37 =head1 NAME
38
39 Koha::Patron::MessagePreference - Koha Patron Message Preference object class
40
41 =head1 API
42
43 =head2 Class Methods
44
45 =cut
46
47 =head3 new
48
49 my $preference = Koha::Patron::MessagePreference->new({
50    borrowernumber => 123,
51    #categorycode => 'ABC',
52    message_attribute_id => 4,
53    message_transport_types => ['email', 'sms'], # see documentation below
54    wants_digest => 1,
55    days_in_advance => 7,
56 });
57
58 Takes either borrowernumber or categorycode, but not both.
59
60 days_in_advance may not be available. See message_attributes table for takes_days
61 configuration.
62
63 wants_digest may not be available. See message_transports table for is_digest
64 configuration.
65
66 You can instantiate a new object without custom validation errors, but when
67 storing, validation may throw exceptions. See C<validate()> for more
68 documentation.
69
70 C<message_transport_types> is a parameter that is not actually a column in this
71 Koha-object. Given this parameter, the message transport types will be added as
72 related transport types for this object. For get and set, you can access them via
73 subroutine C<message_transport_types()> in this class.
74
75 =cut
76
77 sub new {
78     my ($class, $params) = @_;
79
80     my $types = $params->{'message_transport_types'};
81     delete $params->{'message_transport_types'};
82
83     my $self = $class->SUPER::new($params);
84
85     $self->_set_message_transport_types($types);
86
87     return $self;
88 }
89
90 =head3 new_from_default
91
92 my $preference = Koha::Patron::MessagePreference->new_from_default({
93     borrowernumber => 123,
94     categorycode   => 'ABC',   # if not given, patron's categorycode will be used
95     message_attribute_id => 1,
96 });
97
98 NOTE: This subroutine initializes and STORES the object (in order to set
99 message transport types for the preference), so no need to call ->store when
100 preferences are initialized via this method.
101
102 Stores default messaging preference for C<categorycode> to patron for given
103 C<message_attribute_id>.
104
105 Throws Koha::Exceptions::MissingParameter if any of following is missing:
106 - borrowernumber
107 - message_attribute_id
108
109 Throws Koha::Exceptions::ObjectNotFound if default preferences are not found.
110
111 =cut
112
113 sub new_from_default {
114     my ($class, $params) = @_;
115
116     my @required = qw(borrowernumber message_attribute_id);
117     foreach my $p (@required) {
118         Koha::Exceptions::MissingParameter->throw(
119             error => "Missing required parameter '$p'.",
120         ) unless exists $params->{$p};
121     }
122     unless ($params->{'categorycode'}) {
123         my $patron = Koha::Patrons->find($params->{borrowernumber});
124         $params->{'categorycode'} = $patron->categorycode;
125     }
126
127     my $default = Koha::Patron::MessagePreferences->find({
128         categorycode => $params->{'categorycode'},
129         message_attribute_id => $params->{'message_attribute_id'},
130     });
131     Koha::Exceptions::ObjectNotFound->throw(
132         error => 'Default messaging preference for given categorycode and'
133         .' message_attribute_id cannot be found.',
134     ) unless $default;
135     $default = $default->unblessed;
136
137     # Add a new messaging preference for patron
138     my $self = $class->SUPER::new({
139         borrowernumber => $params->{'borrowernumber'},
140         message_attribute_id => $default->{'message_attribute_id'},
141         days_in_advance => $default->{'days_in_advance'},
142         wants_digest => $default->{'wants_digest'},
143     })->store;
144
145     # Set default messaging transport types
146     my $default_transport_types =
147     Koha::Patron::MessagePreference::Transport::Preferences->search({
148         borrower_message_preference_id =>
149                     $default->{'borrower_message_preference_id'}
150     });
151     while (my $transport = $default_transport_types->next) {
152         Koha::Patron::MessagePreference::Transport::Preference->new({
153             borrower_message_preference_id => $self->borrower_message_preference_id,
154             message_transport_type => $transport->message_transport_type,
155         })->store;
156     }
157
158     return $self;
159 }
160
161 =head3 message_name
162
163 $preference->message_name
164
165 Gets message_name for this messaging preference.
166
167 Setter not implemented.
168
169 =cut
170
171 sub message_name {
172     my ($self) = @_;
173
174     if ($self->{'_message_name'}) {
175         return $self->{'_message_name'};
176     }
177     $self->{'_message_name'} = Koha::Patron::MessagePreference::Attributes->find({
178         message_attribute_id => $self->message_attribute_id,
179     })->message_name;
180     return $self->{'_message_name'};
181 }
182
183 =head3 message_transport_types
184
185 $preference->message_transport_types
186 Returns a HASHREF of message transport types for this messaging preference, e.g.
187 if ($preference->message_transport_types->{'email'}) {
188     # email is one of the transport preferences
189 }
190
191 $preference->message_transport_types('email', 'sms');
192 Sets the given message transport types for this messaging preference
193
194 =cut
195
196 sub message_transport_types {
197     my $self = shift;
198
199     unless (@_) {
200         if ($self->{'_message_transport_types'}) {
201             return $self->{'_message_transport_types'};
202         }
203         map {
204             my $transport = Koha::Patron::MessagePreference::Transports->find({
205                 message_attribute_id => $self->message_attribute_id,
206                 message_transport_type => $_->message_transport_type,
207                 is_digest => $self->wants_digest
208             });
209             unless ($transport) {
210                 my $logger = Koha::Logger->get;
211                 $logger->warn(
212                     $self->message_name . ' has no transport with '.
213                     $_->message_transport_type . ' (digest: '.
214                     ($self->wants_digest ? 'yes':'no').').'
215                 );
216             }
217             $self->{'_message_transport_types'}->{$_->message_transport_type}
218                 = $transport ? $transport->letter_code : ' ';
219         }
220         Koha::Patron::MessagePreference::Transport::Preferences->search({
221             borrower_message_preference_id => $self->borrower_message_preference_id,
222         })->as_list;
223         return $self->{'_message_transport_types'} || {};
224     }
225     else {
226         $self->_set_message_transport_types(@_);
227         return $self;
228     }
229 }
230
231 =head3 mtt_deliverable
232
233 $preference->mtt_deliverable('sms'[, $borrowernumer]);
234
235 Returns true if given message transport type can be used to deliver message to
236 patron.
237
238 By default, uses the borrowernumber bound to C<$preference>, but this may be
239 overridden by providing optional C<$borrowernumber> parameter.
240
241 =cut
242
243 sub mtt_deliverable {
244     my ( $self, $mtt, $borrowernumber ) = @_;
245
246     $borrowernumber //= $self->borrowernumber;
247
248     return 0 unless ($borrowernumber);
249
250     my $patron = Koha::Patrons->find($self->borrowernumber);
251
252     return (( $mtt eq 'email' and $patron->notice_email_address ) # No email address
253          or ( $mtt eq 'sms'   and $patron->smsalertnumber ) # No SMS number
254          or ( $mtt eq 'itiva' and C4::Context->preference('TalkingTechItivaPhoneNotification') ) # Notice is handled by TalkingTech_itiva_outbound.pl
255          or ( $mtt eq 'phone' and $patron->phone )) # No phone number to call
256     ? 1 : 0;
257 }
258
259 =head3 set
260
261 $preference->set({
262     message_transport_types => ['sms', 'phone'],
263     wants_digest => 0,
264 })->store;
265
266 Sets preference object values and additionally message_transport_types if given.
267
268 =cut
269
270 sub set {
271     my ($self, $params) = @_;
272
273     my $mtt = $params->{'message_transport_types'};
274     delete $params->{'message_transport_types'};
275
276     $self->SUPER::set($params) if $params;
277     if ($mtt) {
278         $self->message_transport_types($mtt);
279     }
280
281     return $self;
282 }
283
284 =head3 store
285
286 Makes a validation before actual Koha::Object->store so that proper exceptions
287 can be thrown. See C<validate()> for documentation about exceptions.
288
289 =cut
290
291 sub store {
292     my $self = shift;
293
294     $self->validate->SUPER::store(@_);
295
296     # store message transport types
297     if (exists $self->{'_message_transport_types'}) {
298         Koha::Patron::MessagePreference::Transport::Preferences->search({
299             borrower_message_preference_id =>
300                 $self->borrower_message_preference_id,
301         })->delete;
302         foreach my $type (keys %{$self->{'_message_transport_types'}}) {
303             Koha::Patron::MessagePreference::Transport::Preference->new({
304                 borrower_message_preference_id =>
305                     $self->borrower_message_preference_id,
306                 message_transport_type => $type,
307             })->store;
308         }
309     }
310
311     return $self;
312 }
313
314 =head3 validate
315
316 Makes a basic validation for object.
317
318 Returns Koha::Patron::MessagePreference object, or throws and exception.
319
320 =cut
321
322 sub validate {
323     my ($self) = @_;
324
325     if ($self->borrowernumber && $self->categorycode) {
326         Koha::Exceptions::TooManyParameters->throw(
327             error => 'Both borrowernumber and category given, only one accepted',
328         );
329     }
330     if (!$self->borrowernumber && !$self->categorycode) {
331         Koha::Exceptions::MissingParameter->throw(
332             error => 'borrowernumber or category required, none given',
333         );
334     }
335     if ($self->borrowernumber) {
336         Koha::Exceptions::Patron::NotFound->throw(
337             error => 'Patron not found.',
338         ) unless Koha::Patrons->find($self->borrowernumber);
339     }
340     if ($self->categorycode) {
341         Koha::Exceptions::Patron::Category::NotFound->throw(
342             error => 'Category not found.',
343         ) unless Koha::Patron::Categories->find($self->categorycode);
344     }
345
346     if (!$self->in_storage) {
347         my $previous = Koha::Patron::MessagePreferences->search({
348             borrowernumber => $self->borrowernumber,
349             categorycode   => $self->categorycode,
350             message_attribute_id => $self->message_attribute_id,
351         });
352         if ($previous->count) {
353             Koha::Exceptions::DuplicateObject->throw(
354                 error => 'A preference for this borrower/category and'
355                 .' message_attribute_id already exists',
356             );
357         }
358     }
359
360     my $attr = Koha::Patron::MessagePreference::Attributes->find(
361         $self->message_attribute_id
362     );
363     unless ($attr) {
364         Koha::Exceptions::Patron::MessagePreference::AttributeNotFound->throw(
365             error => 'Message attribute with id '.$self->message_attribute_id
366             .' not found',
367             message_attribute_id => $self->message_attribute_id,
368         );
369     }
370     if (defined $self->days_in_advance) {
371         if ($attr && $attr->takes_days == 0) {
372             Koha::Exceptions::Patron::MessagePreference::DaysInAdvanceNotAvailable->throw(
373                 error => 'days_in_advance cannot be defined for '.
374                 $attr->message_name . '.',
375                 message_name => $attr->message_name,
376             );
377         }
378         elsif ($self->days_in_advance < 0 || $self->days_in_advance > 30) {
379             Koha::Exceptions::Patron::MessagePreference::DaysInAdvanceOutOfRange->throw(
380                 error => 'days_in_advance has to be a value between 0-30 for '.
381                 $attr->message_name . '.',
382                 message_name => $attr->message_name,
383                 min => 0,
384                 max => 30,
385             );
386         }
387     }
388     if (defined $self->wants_digest) {
389         my $transports = Koha::Patron::MessagePreference::Transports->search({
390             message_attribute_id => $self->message_attribute_id,
391             is_digest            => $self->wants_digest ? 1 : 0,
392         });
393         unless ($transports->count) {
394             if (!$self->wants_digest) {
395                 Koha::Exceptions::Patron::MessagePreference::DigestRequired->throw(
396                     error => 'Digest must be selected for '.$attr->message_name.'.',
397                     message_name => $attr->message_name
398                 );
399             } else {
400                 Koha::Exceptions::Patron::MessagePreference::DigestNotAvailable->throw(
401                     error => 'Digest cannot be selected for '.$attr->message_name.'.',
402                     message_name => $attr->message_name
403                 );
404             }
405         }
406     }
407
408     return $self;
409 }
410
411 sub _set_message_transport_types {
412     my $self = shift;
413
414     return unless $_[0];
415
416     $self->{'_message_transport_types'} = undef;
417     my $types = ref $_[0] eq "ARRAY" ? $_[0] : [@_];
418     return unless $types;
419     $self->_validate_message_transport_types({ message_transport_types => $types });
420     foreach my $type (@$types) {
421         unless (exists $self->{'_message_transport_types'}->{$type}) {
422             my $transport = Koha::Patron::MessagePreference::Transports->search({
423                 message_attribute_id => $self->message_attribute_id,
424                 message_transport_type => $type
425             })->next;
426             unless ($transport) {
427                 Koha::Exceptions::Patron::MessagePreference::NoTransportType->throw(
428                     error => 'No transport configured for '.$self->message_name.
429                         " transport type $type.",
430                     message_name => $self->message_name,
431                     transport_type => $type,
432                 );
433             }
434             if (defined $self->borrowernumber) {
435                 if ( ! $self->mtt_deliverable($type) ) {
436                     Koha::Exceptions::Patron::MessagePreference::EmailAddressRequired->throw(
437                         error => 'Patron has not set email address, '.
438                                   'cannot use email as message transport',
439                         message_name => $self->message_name,
440                         borrowernumber => $self->borrowernumber,
441                     ) if $type eq 'email';
442                     Koha::Exceptions::Patron::MessagePreference::TalkingTechItivaPhoneNotificationRequired->throw(
443                         error => 'System preference TalkingTechItivaPhoneNotification disabled'.
444                                  'cannot use itiva as message transport',
445                         message_name => $self->message_name,
446                         borrowernumber => $self->borrowernumber,
447                     ) if $type eq 'itiva';
448                     Koha::Exceptions::Patron::MessagePreference::PhoneNumberRequired->throw(
449                         error => 'Patron has not set phone number'.
450                                  'cannot use phone as message transport',
451                         message_name => $self->message_name,
452                         borrowernumber => $self->borrowernumber,
453                     ) if $type eq 'phone';
454                     Koha::Exceptions::Patron::MessagePreference::SMSNumberRequired->throw(
455                         error => 'Patron has not set SMS number'.
456                                  'cannot use sms as message transport',
457                         message_name => $self->message_name,
458                         borrowernumber => $self->borrowernumber,
459                     ) if $type eq 'sms';
460                 }
461             }
462             $self->{'_message_transport_types'}->{$type}
463                 = $transport->letter_code;
464         }
465     }
466     return $self;
467 }
468
469 sub _validate_message_transport_types {
470     my ($self, $params) = @_;
471
472     if (ref($params) eq 'HASH' && $params->{'message_transport_types'}) {
473         if (ref($params->{'message_transport_types'}) ne 'ARRAY') {
474             $params->{'message_transport_types'} = [$params->{'message_transport_types'}];
475         }
476         my $types = $params->{'message_transport_types'};
477
478         foreach my $type (@{$types}) {
479             unless (Koha::Patron::MessagePreference::Transport::Types->find({
480                 message_transport_type => $type
481             })) {
482                 Koha::Exceptions::Patron::MessagePreference::Transport::TypeNotFound->throw(
483                     error => "Message transport type '$type' does not exist",
484                     transport_type => $type,
485                 );
486             }
487         }
488         return $types;
489     }
490 }
491
492 =head3 type
493
494 =cut
495
496 sub _type {
497     return 'BorrowerMessagePreference';
498 }
499
500 =head1 AUTHOR
501
502 Lari Taskula <lari.taskula@hypernova.fi>
503
504 =cut
505
506 1;