Bug 26265: (QA follow-up) Remove g option from regex, add few dirs
[koha-equinox.git] / Koha / Patrons.pm
1 package Koha::Patrons;
2
3 # Copyright 2014 ByWater Solutions
4 # Copyright 2016 Koha Development Team
5 #
6 # This file is part of Koha.
7 #
8 # Koha is free software; you can redistribute it and/or modify it under the
9 # terms of the GNU General Public License as published by the Free Software
10 # Foundation; either version 3 of the License, or (at your option) any later
11 # version.
12 #
13 # Koha is distributed in the hope that it will be useful, but WITHOUT ANY
14 # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
15 # A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
16 #
17 # You should have received a copy of the GNU General Public License along
18 # with Koha; if not, write to the Free Software Foundation, Inc.,
19 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20
21 use Modern::Perl;
22
23 use Carp;
24
25 use Koha::Database;
26 use Koha::DateUtils;
27
28 use Koha::ArticleRequests;
29 use Koha::ArticleRequest::Status;
30 use Koha::Patron;
31 use Koha::Exceptions::Patron;
32
33 use base qw(Koha::Objects);
34
35 =head1 NAME
36
37 Koha::Patron - Koha Patron Object class
38
39 =head1 API
40
41 =head2 Class Methods
42
43 =cut
44
45 =head3 search_limited
46
47 my $patrons = Koha::Patrons->search_limit( $params, $attributes );
48
49 Returns all the patrons the logged in user is allowed to see
50
51 =cut
52
53 sub search_limited {
54     my ( $self, $params, $attributes ) = @_;
55
56     my $userenv = C4::Context->userenv;
57     my @restricted_branchcodes;
58     if ( $userenv and $userenv->{number} ) {
59         my $logged_in_user = Koha::Patrons->find( $userenv->{number} );
60         @restricted_branchcodes = $logged_in_user->libraries_where_can_see_patrons;
61     }
62     $params->{'me.branchcode'} = { -in => \@restricted_branchcodes } if @restricted_branchcodes;
63     return $self->search( $params, $attributes );
64 }
65
66 =head3 search_housebound_choosers
67
68 Returns all Patrons which are Housebound choosers.
69
70 =cut
71
72 sub search_housebound_choosers {
73     my ( $self ) = @_;
74     my $cho = $self->_resultset
75         ->search_related('housebound_role', {
76             housebound_chooser => 1,
77         })->search_related('borrowernumber');
78     return Koha::Patrons->_new_from_dbic($cho);
79 }
80
81 =head3 search_housebound_deliverers
82
83 Returns all Patrons which are Housebound deliverers.
84
85 =cut
86
87 sub search_housebound_deliverers {
88     my ( $self ) = @_;
89     my $del = $self->_resultset
90         ->search_related('housebound_role', {
91             housebound_deliverer => 1,
92         })->search_related('borrowernumber');
93     return Koha::Patrons->_new_from_dbic($del);
94 }
95
96 =head3 search_upcoming_membership_expires
97
98 my $patrons = Koha::Patrons->search_upcoming_membership_expires();
99
100 The 'before' and 'after' represent the number of days before/after the date
101 that is set by the preference MembershipExpiryDaysNotice.
102 If the pref is 14, before 2 and after 3 then you will get all expires
103 from 12 to 17 days.
104
105 =cut
106
107 sub search_upcoming_membership_expires {
108     my ( $self, $params ) = @_;
109     my $before = $params->{before} || 0;
110     my $after  = $params->{after} || 0;
111     delete $params->{before};
112     delete $params->{after};
113
114     my $days = C4::Context->preference("MembershipExpiryDaysNotice") || 0;
115     my $date_before = dt_from_string->add( days => $days - $before );
116     my $date_after = dt_from_string->add( days => $days + $after );
117     my $dtf = Koha::Database->new->schema->storage->datetime_parser;
118
119     $params->{dateexpiry} = {
120         ">=" => $dtf->format_date( $date_before ),
121         "<=" => $dtf->format_date( $date_after ),
122     };
123     return $self->SUPER::search(
124         $params, { join => ['branchcode', 'categorycode'] }
125     );
126 }
127
128 =head3 guarantor
129
130 Returns a Koha::Patron object for this borrower's guarantor
131
132 =cut
133
134 sub guarantor {
135     my ( $self ) = @_;
136
137     return Koha::Patrons->find( $self->guarantorid() );
138 }
139
140 =head3 search_patrons_to_anonymise
141
142     my $patrons = Koha::Patrons->search_patrons_to_anonymise( { before => $older_than_date, [ library => $library ] } );
143
144 This method returns all patrons who has an issue history older than a given date.
145
146 =cut
147
148 sub search_patrons_to_anonymise {
149     my ( $class, $params ) = @_;
150     my $older_than_date = $params->{before};
151     my $library         = $params->{library};
152     $older_than_date = $older_than_date ? dt_from_string($older_than_date) : dt_from_string;
153     $library ||=
154       ( C4::Context->preference('IndependentBranches') && C4::Context->userenv && !C4::Context->IsSuperLibrarian() && C4::Context->userenv->{branch} )
155       ? C4::Context->userenv->{branch}
156       : undef;
157
158     my $dtf = Koha::Database->new->schema->storage->datetime_parser;
159     my $rs = $class->_resultset->search(
160         {   returndate                  => { '<'   =>  $dtf->format_datetime($older_than_date), },
161             'old_issues.borrowernumber' => { 'not' => undef },
162             privacy                     => { '<>'  => 0 },                  # Keep forever
163             ( $library ? ( 'old_issues.branchcode' => $library ) : () ),
164         },
165         {   join     => ["old_issues"],
166             distinct => 1,
167         }
168     );
169     return Koha::Patrons->_new_from_dbic($rs);
170 }
171
172 =head3 anonymise_issue_history
173
174     Koha::Patrons->search->anonymise_issue_history( { [ before => $older_than_date ] } );
175
176 Anonymise issue history (old_issues) for all patrons older than the given date (optional).
177 To make sure all the conditions are met, the caller has the responsibility to
178 call search_patrons_to_anonymise to filter the Koha::Patrons set
179
180 =cut
181
182 sub anonymise_issue_history {
183     my ( $self, $params ) = @_;
184
185     my $older_than_date = $params->{before};
186
187     $older_than_date = dt_from_string $older_than_date if $older_than_date;
188
189     # The default of 0 does not work due to foreign key constraints
190     # The anonymisation should not fail quietly if AnonymousPatron is not a valid entry
191     # Set it to undef (NULL)
192     my $dtf = Koha::Database->new->schema->storage->datetime_parser;
193     my $nb_rows = 0;
194     while ( my $patron = $self->next ) {
195         my $old_issues_to_anonymise = $patron->old_checkouts->search(
196         {
197             (
198                 $older_than_date
199                 ? ( returndate =>
200                       { '<' => $dtf->format_datetime($older_than_date) } )
201                 : ()
202             )
203         }
204         );
205         my $anonymous_patron = C4::Context->preference('AnonymousPatron') || undef;
206         $nb_rows += $old_issues_to_anonymise->update( { 'old_issues.borrowernumber' => $anonymous_patron } );
207     }
208     return $nb_rows;
209 }
210
211 =head3 delete
212
213     Koha::Patrons->search({ some filters here })->delete({ move => 1, verbose => 1 });
214
215     Delete passed set of patron objects.
216     Wrapper for Koha::Patron->delete. (We do not want to bypass Koha::Patron
217     and let DBIx do the job without further housekeeping.)
218     Includes a move to deletedborrowers if move flag set.
219
220     Just like DBIx, the delete will only succeed when all entries could be
221     deleted. Returns true or throws an exception.
222
223 =cut
224
225 sub delete {
226     my ( $self, $params ) = @_;
227     my $patrons_deleted;
228     $self->_resultset->result_source->schema->txn_do( sub {
229         my ( $set, $params ) = @_;
230         my $count = $set->count;
231         while( my $patron = $set->next ) {
232             $patron->move_to_deleted if $params->{move};
233             $patron->delete == 1 || Koha::Exceptions::Patron::FailedDelete->throw;
234             $patrons_deleted++;
235         }
236         warn "Deleted $count patrons\n" if $params->{verbose};
237     }, $self, $params );
238     return $patrons_deleted;
239 }
240
241 =head3 search_unsubscribed
242
243     Koha::Patrons->search_unsubscribed;
244
245     Returns a set of Koha patron objects for patrons that recently
246     unsubscribed and are not locked (candidates for locking).
247     Depends on UnsubscribeReflectionDelay.
248
249 =cut
250
251 sub search_unsubscribed {
252     my ( $class ) = @_;
253
254     my $delay = C4::Context->preference('UnsubscribeReflectionDelay');
255     if( !defined($delay) || $delay eq q{} ) {
256         # return empty set
257         return $class->search({ borrowernumber => undef });
258     }
259     my $parser = Koha::Database->new->schema->storage->datetime_parser;
260     my $dt = dt_from_string()->subtract( days => $delay );
261     my $str = $parser->format_datetime($dt);
262     my $fails = C4::Context->preference('FailedLoginAttempts') || 0;
263     my $cond = [ undef, 0, 1..$fails-1 ]; # NULL, 0, 1..fails-1 (if fails>0)
264     return $class->search(
265         {
266             'patron_consents.refused_on' => { '<=' => $str },
267             'login_attempts' => $cond,
268         },
269         { join => 'patron_consents' },
270     );
271 }
272
273 =head3 search_anonymize_candidates
274
275     Koha::Patrons->search_anonymize_candidates({ locked => 1 });
276
277     Returns a set of Koha patron objects for patrons whose account is expired
278     and locked (if parameter set). These are candidates for anonymizing.
279     Depends on PatronAnonymizeDelay.
280
281 =cut
282
283 sub search_anonymize_candidates {
284     my ( $class, $params ) = @_;
285
286     my $delay = C4::Context->preference('PatronAnonymizeDelay');
287     if( !defined($delay) || $delay eq q{} ) {
288         # return empty set
289         return $class->search({ borrowernumber => undef });
290     }
291     my $cond = {};
292     my $parser = Koha::Database->new->schema->storage->datetime_parser;
293     my $dt = dt_from_string()->subtract( days => $delay );
294     my $str = $parser->format_datetime($dt);
295     $cond->{dateexpiry} = { '<=' => $str };
296     $cond->{anonymized} = 0; # not yet done
297     if( $params->{locked} ) {
298         my $fails = C4::Context->preference('FailedLoginAttempts');
299         $cond->{login_attempts} = [ -and => { '!=' => undef }, { -not_in => [0, 1..$fails-1 ] } ]; # -not_in does not like undef
300     }
301     return $class->search( $cond );
302 }
303
304 =head3 search_anonymized
305
306     Koha::Patrons->search_anonymized;
307
308     Returns a set of Koha patron objects for patron accounts that have been
309     anonymized before and could be removed.
310     Depends on PatronRemovalDelay.
311
312 =cut
313
314 sub search_anonymized {
315     my ( $class ) = @_;
316
317     my $delay = C4::Context->preference('PatronRemovalDelay');
318     if( !defined($delay) || $delay eq q{} ) {
319         # return empty set
320         return $class->search({ borrowernumber => undef });
321     }
322     my $cond = {};
323     my $parser = Koha::Database->new->schema->storage->datetime_parser;
324     my $dt = dt_from_string()->subtract( days => $delay );
325     my $str = $parser->format_datetime($dt);
326     $cond->{dateexpiry} = { '<=' => $str };
327     $cond->{anonymized} = 1;
328     return $class->search( $cond );
329 }
330
331 =head3 lock
332
333     Koha::Patrons->search({ some filters })->lock({ expire => 1, remove => 1, verbose => 1 })
334
335     Lock the passed set of patron objects. Optionally expire and remove holds.
336     Optional verbose flag is used in cron job.
337     Wrapper around Koha::Patron->lock.
338
339 =cut
340
341 sub lock {
342     my ( $self, $params ) = @_;
343     my $count = $self->count;
344     while( my $patron = $self->next ) {
345         $patron->lock($params);
346     }
347     if( $params->{verbose} ) {
348         warn "Locked $count patrons\n";
349     }
350 }
351
352 =head3 anonymize
353
354     Koha::Patrons->search({ some filters })->anonymize({ verbose => 1 });
355
356     Anonymize passed set of patron objects.
357     Optional verbose flag is used in cron job.
358     Wrapper around Koha::Patron->anonymize.
359
360 =cut
361
362 sub anonymize {
363     my ( $self, $params ) = @_;
364     my $count = $self->count;
365     while( my $patron = $self->next ) {
366         $patron->anonymize;
367     }
368     if( $params->{verbose} ) {
369         warn "Anonymized $count patrons\n";
370     }
371 }
372
373 =head3 _type
374
375 =cut
376
377 sub _type {
378     return 'Borrower';
379 }
380
381 sub object_class {
382     return 'Koha::Patron';
383 }
384
385 =head1 AUTHOR
386
387 Kyle M Hall <kyle@bywatersolutions.com>
388
389 =cut
390
391 1;