Bug 21549: (follow-up) Switch to filter_by
[koha.git] / misc / cronjobs / cleanup_database.pl
1 #!/usr/bin/perl
2
3 # Copyright 2009 PTFS, Inc.
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 constant DEFAULT_ZEBRAQ_PURGEDAYS             => 30;
23 use constant DEFAULT_MAIL_PURGEDAYS               => 30;
24 use constant DEFAULT_IMPORT_PURGEDAYS             => 60;
25 use constant DEFAULT_LOGS_PURGEDAYS               => 180;
26 use constant DEFAULT_MESSAGES_PURGEDAYS           => 365;
27 use constant DEFAULT_SEARCHHISTORY_PURGEDAYS      => 30;
28 use constant DEFAULT_SHARE_INVITATION_EXPIRY_DAYS => 14;
29 use constant DEFAULT_DEBARMENTS_PURGEDAYS         => 30;
30
31 BEGIN {
32     # find Koha's Perl modules
33     # test carefully before changing this
34     use FindBin;
35     eval { require "$FindBin::Bin/../kohalib.pl" };
36 }
37
38 use Koha::Script -cron;
39 use C4::Context;
40 use C4::Search;
41 use C4::Search::History;
42 use Getopt::Long;
43 use C4::Log;
44 use C4::Accounts;
45 use Koha::UploadedFiles;
46 use Koha::Old::Biblios;
47 use Koha::Old::Items;
48 use Koha::Old::Biblioitems;
49 use Koha::Old::Checkouts;
50 use Koha::Old::Holds;
51 use Koha::Old::Patrons;
52 use Koha::Item::Transfers;
53 use Koha::PseudonymizedTransactions;
54 use Koha::Patron::Messages;
55
56 sub usage {
57     print STDERR <<USAGE;
58 Usage: $0 [-h|--help] [--confirm] [--sessions] [--sessdays DAYS] [-v|--verbose] [--zebraqueue DAYS] [-m|--mail] [--merged] [--import DAYS] [--logs DAYS] [--searchhistory DAYS] [--restrictions DAYS] [--all-restrictions] [--fees DAYS] [--temp-uploads] [--temp-uploads-days DAYS] [--uploads-missing 0|1 ] [--statistics DAYS] [--deleted-catalog DAYS] [--deleted-patrons DAYS] [--old-issues DAYS] [--old-reserves DAYS] [--transfers DAYS]
59
60    -h --help          prints this help message, and exits, ignoring all
61                       other options
62    --confirm          Confirmation flag, the script will be running in dry-run mode is not set.
63    --sessions         purge the sessions table.  If you use this while users 
64                       are logged into Koha, they will have to reconnect.
65    --sessdays DAYS    purge only sessions older than DAYS days.
66    -v --verbose       will cause the script to give you a bit more information
67                       about the run.
68    --zebraqueue DAYS  purge completed zebraqueue entries older than DAYS days.
69                       Defaults to 30 days if no days specified.
70    -m --mail DAYS     purge items from the mail queue that are older than DAYS days.
71                       Defaults to 30 days if no days specified.
72    --merged           purged completed entries from need_merge_authorities.
73    --messages DAYS    purge entries from messages table older than DAYS days.
74                       Defaults to 365 days if no days specified.
75    --import DAYS      purge records from import tables older than DAYS days.
76                       Defaults to 60 days if no days specified.
77    --z3950            purge records from import tables that are the result
78                       of Z39.50 searches
79    --fees DAYS        purge entries accountlines older than DAYS days, where
80                       amountoutstanding is 0 or NULL.
81                       In the case of --fees, DAYS must be greater than
82                       or equal to 1.
83    --logs DAYS        purge entries from action_logs older than DAYS days.
84                       Defaults to 180 days if no days specified.
85    --searchhistory DAYS  purge entries from search_history older than DAYS days.
86                          Defaults to 30 days if no days specified
87    --list-invites  DAYS  purge (unaccepted) list share invites older than DAYS
88                          days.  Defaults to 14 days if no days specified.
89    --restrictions DAYS   purge patrons restrictions expired since more than DAYS days.
90                          Defaults to 30 days if no days specified.
91     --all-restrictions   purge all expired patrons restrictions.
92    --del-exp-selfreg  Delete expired self registration accounts
93    --del-unv-selfreg  DAYS  Delete unverified self registrations older than DAYS
94    --unique-holidays DAYS  Delete all unique holidays older than DAYS
95    --temp-uploads     Delete temporary uploads.
96    --temp-uploads-days DAYS Override the corresponding preference value.
97    --uploads-missing FLAG Delete upload records for missing files when FLAG is true, count them otherwise
98    --oauth-tokens     Delete expired OAuth2 tokens
99    --statistics DAYS       Purge statistics entries more than DAYS days old.
100                            This table is used to build reports, make sure you are aware of the consequences of this before using it!
101    --deleted-catalog  DAYS Purge catalog records deleted more then DAYS days ago
102                            (from tables deleteditems, deletedbiblioitems, deletedbiblio_metadata and deletedbiblio).
103    --deleted-patrons DAYS  Purge patrons deleted more than DAYS days ago.
104    --old-issues DAYS       Purge checkouts (old_issues) returned more than DAYS days ago.
105    --old-reserves DAYS     Purge reserves (old_reserves) more than DAYS old.
106    --transfers DAYS        Purge transfers completed more than DAYS day ago.
107    --pseudo-transactions DAYS   Purge the pseudonymized transactions that have been originally created more than DAYS days ago
108                                 DAYS is optional and can be replaced by:
109                                     --pseudo-transactions-from YYYY-MM-DD and/or --pseudo-transactions-to YYYY-MM-DD
110 USAGE
111     exit $_[0];
112 }
113
114 my $help;
115 my $confirm;
116 my $sessions;
117 my $sess_days;
118 my $verbose;
119 my $zebraqueue_days;
120 my $mail;
121 my $purge_merged;
122 my $pImport;
123 my $pLogs;
124 my $pSearchhistory;
125 my $pZ3950;
126 my $pListShareInvites;
127 my $pDebarments;
128 my $allDebarments;
129 my $pExpSelfReg;
130 my $pUnvSelfReg;
131 my $fees_days;
132 my $special_holidays_days;
133 my $temp_uploads;
134 my $temp_uploads_days;
135 my $uploads_missing;
136 my $oauth_tokens;
137 my $pStatistics;
138 my $pDeletedCatalog;
139 my $pDeletedPatrons;
140 my $pOldIssues;
141 my $pOldReserves;
142 my $pTransfers;
143 my ( $pPseudoTransactions, $pPseudoTransactionsFrom, $pPseudoTransactionsTo );
144 my $pMessages;
145
146 GetOptions(
147     'h|help'            => \$help,
148     'confirm'           => \$confirm,
149     'sessions'          => \$sessions,
150     'sessdays:i'        => \$sess_days,
151     'v|verbose'         => \$verbose,
152     'm|mail:i'          => \$mail,
153     'zebraqueue:i'      => \$zebraqueue_days,
154     'merged'            => \$purge_merged,
155     'import:i'          => \$pImport,
156     'z3950'             => \$pZ3950,
157     'logs:i'            => \$pLogs,
158     'messages:i'        => \$pMessages,
159     'fees:i'            => \$fees_days,
160     'searchhistory:i'   => \$pSearchhistory,
161     'list-invites:i'    => \$pListShareInvites,
162     'restrictions:i'    => \$pDebarments,
163     'all-restrictions'  => \$allDebarments,
164     'del-exp-selfreg'   => \$pExpSelfReg,
165     'del-unv-selfreg'   => \$pUnvSelfReg,
166     'unique-holidays:i' => \$special_holidays_days,
167     'temp-uploads'      => \$temp_uploads,
168     'temp-uploads-days:i' => \$temp_uploads_days,
169     'uploads-missing:i' => \$uploads_missing,
170     'oauth-tokens'      => \$oauth_tokens,
171     'statistics:i'      => \$pStatistics,
172     'deleted-catalog:i' => \$pDeletedCatalog,
173     'deleted-patrons:i' => \$pDeletedPatrons,
174     'old-issues:i'      => \$pOldIssues,
175     'old-reserves:i'    => \$pOldReserves,
176     'transfers:i'       => \$pTransfers,
177     'pseudo-transactions:i'      => \$pPseudoTransactions,
178     'pseudo-transactions-from:s' => \$pPseudoTransactionsFrom,
179     'pseudo-transactions-to:s'   => \$pPseudoTransactionsTo,
180 ) || usage(1);
181
182 # Use default values
183 $sessions          = 1                                    if $sess_days                  && $sess_days > 0;
184 $pImport           = DEFAULT_IMPORT_PURGEDAYS             if defined($pImport)           && $pImport == 0;
185 $pLogs             = DEFAULT_LOGS_PURGEDAYS               if defined($pLogs)             && $pLogs == 0;
186 $zebraqueue_days   = DEFAULT_ZEBRAQ_PURGEDAYS             if defined($zebraqueue_days)   && $zebraqueue_days == 0;
187 $mail              = DEFAULT_MAIL_PURGEDAYS               if defined($mail)              && $mail == 0;
188 $pSearchhistory    = DEFAULT_SEARCHHISTORY_PURGEDAYS      if defined($pSearchhistory)    && $pSearchhistory == 0;
189 $pListShareInvites = DEFAULT_SHARE_INVITATION_EXPIRY_DAYS if defined($pListShareInvites) && $pListShareInvites == 0;
190 $pDebarments       = DEFAULT_DEBARMENTS_PURGEDAYS         if defined($pDebarments)       && $pDebarments == 0;
191 $pMessages         = DEFAULT_MESSAGES_PURGEDAYS           if defined($pMessages)         && $pMessages == 0;
192
193 if ($help) {
194     usage(0);
195 }
196
197 unless ( $sessions
198     || $zebraqueue_days
199     || $mail
200     || $purge_merged
201     || $pImport
202     || $pLogs
203     || $fees_days
204     || $pSearchhistory
205     || $pZ3950
206     || $pListShareInvites
207     || $pDebarments
208     || $allDebarments
209     || $pExpSelfReg
210     || $pUnvSelfReg
211     || $special_holidays_days
212     || $temp_uploads
213     || defined $uploads_missing
214     || $oauth_tokens
215     || $pStatistics
216     || $pDeletedCatalog
217     || $pDeletedPatrons
218     || $pOldIssues
219     || $pOldReserves
220     || $pTransfers
221     || defined $pPseudoTransactions
222     || $pPseudoTransactionsFrom
223     || $pPseudoTransactionsTo
224     || $pMessages
225 ) {
226     print "You did not specify any cleanup work for the script to do.\n\n";
227     usage(1);
228 }
229
230 if ($pDebarments && $allDebarments) {
231     print "You can not specify both --restrictions and --all-restrictions.\n\n";
232     usage(1);
233 }
234
235 say "Confirm flag not passed, running in dry-run mode..." unless $confirm;
236
237 cronlogaction() unless $confirm;
238
239 my $dbh = C4::Context->dbh();
240 my $sth;
241 my $sth2;
242
243 if ( $sessions && !$sess_days ) {
244     if ($verbose) {
245         say "Session purge triggered.";
246         $sth = $dbh->prepare(q{ SELECT COUNT(*) FROM sessions });
247         $sth->execute() or die $dbh->errstr;
248         my @count_arr = $sth->fetchrow_array;
249         say $confirm ? "$count_arr[0] entries will be deleted." : "$count_arr[0] entries would be deleted.";
250     }
251     if ( $confirm ) {
252         $sth = $dbh->prepare(q{ TRUNCATE sessions });
253         $sth->execute() or die $dbh->errstr;
254     }
255     if ($verbose) {
256         print "Done with session purge.\n";
257     }
258 }
259 elsif ( $sessions && $sess_days > 0 ) {
260     print "Session purge triggered with days>$sess_days.\n" if $verbose;
261     RemoveOldSessions() if $confirm;
262     print "Done with session purge with days>$sess_days.\n" if $verbose;
263 }
264
265 if ($zebraqueue_days) {
266     my $count = 0;
267     print "Zebraqueue purge triggered for $zebraqueue_days days.\n" if $verbose;
268     $sth = $dbh->prepare(
269         q{
270             SELECT id,biblio_auth_number,server,time
271             FROM zebraqueue
272             WHERE done=1 AND time < date_sub(curdate(), INTERVAL ? DAY)
273         }
274     );
275     if ( $confirm ) {
276         $sth->execute($zebraqueue_days) or die $dbh->errstr;
277     }
278     $sth2 = $dbh->prepare(q{ DELETE FROM zebraqueue WHERE id=? });
279     while ( my $record = $sth->fetchrow_hashref ) {
280         if ( $confirm ) {
281             $sth2->execute( $record->{id} ) or die $dbh->errstr;
282         }
283         $count++;
284     }
285     if ( $verbose ) {
286         say $confirm ? "$count records were deleted." : "$count records would have been deleted.";
287         say "Done with zebraqueue purge.";
288     }
289 }
290
291 if ($mail) {
292     my $count = 0;
293     print "Mail queue purge triggered for $mail days.\n" if $verbose;
294     $sth = $dbh->prepare(
295         q{
296             DELETE FROM message_queue
297             WHERE time_queued < date_sub(curdate(), INTERVAL ? DAY)
298         }
299     );
300     if ( $confirm ) {
301         $sth->execute($mail) or die $dbh->errstr;
302         $count = $sth->rows;
303     }
304     if ( $verbose ) {
305         say $confirm ? "$count messages were deleted from the mail queue." : "Message from message_queue would have been deleted";
306         say "Done with message_queue purge.";
307     }
308 }
309
310 if ($purge_merged) {
311     print "Purging completed entries from need_merge_authorities.\n" if $verbose;
312     if ( $confirm ) {
313         $sth = $dbh->prepare(q{ DELETE FROM need_merge_authorities WHERE done=1 });
314         $sth->execute() or die $dbh->errstr;
315     }
316     print "Done with purging need_merge_authorities.\n" if $verbose;
317 }
318
319 if ($pImport) {
320     print "Purging records from import tables.\n" if $verbose;
321     PurgeImportTables() if $confirm;
322     print "Done with purging import tables.\n" if $verbose;
323 }
324
325 if ($pZ3950) {
326     print "Purging Z39.50 records from import tables.\n" if $verbose;
327     PurgeZ3950() if $confirm;
328     print "Done with purging Z39.50 records from import tables.\n" if $verbose;
329 }
330
331 if ($pLogs) {
332     print "Purging records from action_logs.\n" if $verbose;
333     $sth = $dbh->prepare(
334         q{
335             DELETE FROM action_logs
336             WHERE timestamp < date_sub(curdate(), INTERVAL ? DAY)
337         }
338     );
339     if ( $confirm ) {
340         $sth->execute($pLogs) or die $dbh->errstr;
341     }
342     print "Done with purging action_logs.\n" if $verbose;
343 }
344
345 if ($pMessages) {
346     print "Purging messages older than $pMessages days.\n" if $verbose;
347     my $messages = Koha::Patron::Messages->filter_by_last_update(
348         { timestamp_column_name => 'message_date', days => $pMessages } );
349     my $count = $messages->count;
350     $messages->delete if $confirm;
351     if ( $verbose ) {
352         say $confirm
353           ? sprintf( "Done with purging %d messages", $count )
354           : sprintf( "%d messages would have been removed", $count );
355     }
356 }
357
358 if ($fees_days) {
359     print "Purging records from accountlines.\n" if $verbose;
360     purge_zero_balance_fees( $fees_days ) if $confirm;
361     print "Done purging records from accountlines.\n" if $verbose;
362 }
363
364 if ($pSearchhistory) {
365     print "Purging records older than $pSearchhistory from search_history.\n" if $verbose;
366     C4::Search::History::delete({ interval => $pSearchhistory }) if $confirm;
367     print "Done with purging search_history.\n" if $verbose;
368 }
369
370 if ($pListShareInvites) {
371     print "Purging unaccepted list share invites older than $pListShareInvites days.\n" if $verbose;
372     $sth = $dbh->prepare(
373         q{
374             DELETE FROM virtualshelfshares
375             WHERE invitekey IS NOT NULL
376             AND (sharedate + INTERVAL ? DAY) < NOW()
377         }
378     );
379     if ( $confirm ) {
380         $sth->execute($pListShareInvites);
381     }
382     print "Done with purging unaccepted list share invites.\n" if $verbose;
383 }
384
385 if ($pDebarments) {
386     print "Expired patrons restrictions purge triggered for $pDebarments days.\n" if $verbose;
387     my $count = PurgeDebarments($pDebarments, $confirm);
388     if ( $verbose ) {
389         say $confirm ? "$count restrictions were deleted." : "$count restrictions would have been deleted";
390         say "Done with restrictions purge.";
391     }
392 }
393
394 if($allDebarments) {
395     print "All expired patrons restrictions purge triggered.\n" if $verbose;
396     my $count = PurgeDebarments(0, $confirm);
397     if ( $verbose ) {
398         say $confirm ? "$count restrictions were deleted." : "$count restrictions would have been deleted";
399         say "Done with all restrictions purge.";
400     }
401 }
402
403 # Lock expired patrons?
404 my $days = C4::Context->preference('LockExpiredDelay');
405 if( defined $days && $days ne q{} ) {
406     say "Start locking expired patrons" if $verbose;
407     my $expired_patrons = Koha::Patrons->filter_by_dateexpiry({ days => $days })->search({ login_attempts => { '!=' => -1 } });
408     my $count = $expired_patrons->count;
409     $expired_patrons->lock({ remove => 1 }) if $confirm;
410     if( $verbose ) {
411         say $confirm ? sprintf("Locked %d patrons", $count) : sprintf("Found %d patrons", $count);
412     }
413 }
414
415 # Handle unsubscribe requests from GDPR consent form, depends on UnsubscribeReflectionDelay preference
416 say "Start lock unsubscribed, anonymize and delete" if $verbose;
417 my $unsubscribed_patrons = Koha::Patrons->search_unsubscribed;
418 my $count = $unsubscribed_patrons->count;
419 $unsubscribed_patrons->lock( { expire => 1, remove => 1 } ) if $confirm;
420 say $confirm ? sprintf("Locked %d patrons", $count) : sprintf("%d patrons would have been locked", $count) if $verbose;
421
422 # Anonymize patron data, depending on PatronAnonymizeDelay
423 my $anonymize_candidates = Koha::Patrons->search_anonymize_candidates( { locked => 1 } );
424 $count = $anonymize_candidates->count;
425 $anonymize_candidates->anonymize if $confirm;
426 say $confirm ? sprintf("Anonymized %d patrons", $count) : sprintf("%d patrons would have been anonymized", $count) if $verbose;
427
428 # Remove patron data, depending on PatronRemovalDelay (will raise an exception if problem encountered
429 my $anonymized_patrons = Koha::Patrons->search_anonymized;
430 $count = $anonymized_patrons->count;
431 if ( $confirm ) {
432     $anonymized_patrons->delete( { move => 1 } );
433     if ($@) {
434         warn $@;
435     }
436 }
437 if ($verbose) {
438     say $confirm ? sprintf("Deleted %d patrons", $count) : sprintf("%d patrons would have been deleted", $count);
439 }
440
441 # FIXME The output for dry-run mode needs to be improved
442 # But non trivial changes to C4::Members need to be done before.
443 if( $pExpSelfReg ) {
444     if ( $confirm ) {
445         DeleteExpiredSelfRegs();
446     } elsif ( $verbose ) {
447         say "self-registered borrowers may be deleted";
448     }
449 }
450 if( $pUnvSelfReg ) {
451     if ( $confirm ) {
452         DeleteUnverifiedSelfRegs( $pUnvSelfReg );
453     } elsif ( $verbose ) {
454         say "unverified self-registrations may be deleted";
455     }
456 }
457
458 if ($special_holidays_days) {
459     if ( $confirm ) {
460         DeleteSpecialHolidays( abs($special_holidays_days) );
461     } elsif ( $verbose ) {
462         say "self-registered borrowers may be deleted";
463     }
464 }
465
466 if( $temp_uploads ) {
467     # Delete temporary uploads, governed by a pref (unless you override)
468     print "Purging temporary uploads.\n" if $verbose;
469     if ( $confirm ) {
470         Koha::UploadedFiles->delete_temporary({
471             defined($temp_uploads_days)
472                 ? ( override_pref => $temp_uploads_days )
473                 : ()
474         });
475     }
476     print "Done purging temporary uploads.\n" if $verbose;
477 }
478
479 if( defined $uploads_missing ) {
480     print "Looking for missing uploads\n" if $verbose;
481     if ( $confirm ) {
482         my $keep = $uploads_missing == 1 ? 0 : 1;
483         my $count = Koha::UploadedFiles->delete_missing({ keep_record => $keep });
484         if( $keep ) {
485             print "Counted $count missing uploaded files\n";
486         } else {
487             print "Removed $count records for missing uploads\n";
488         }
489     } else {
490         # FIXME need to create a filter_by_missing method (then call ->delete) instead of delete_missing
491         say "Dry-run mode cannot guess how many uploads would have been deleted";
492     }
493 }
494
495 if ($oauth_tokens) {
496     require Koha::OAuthAccessTokens;
497
498     my $tokens = Koha::OAuthAccessTokens->search({ expires => { '<=', time } });
499     my $count = $tokens->count;
500     $tokens->delete if $confirm;
501     if ( $verbose ) {
502         say $confirm
503           ? sprintf( "Removed %d expired OAuth2 tokens", $count )
504           : sprintf( "%d expired OAuth tokens would have been removed", $count );
505     }
506 }
507
508 if ($pStatistics) {
509     print "Purging statistics older than $pStatistics days.\n" if $verbose;
510     my $statistics = Koha::Statistics->filter_by_last_update(
511         { timestamp_column_name => 'datetime', days => $pStatistics } );
512     my $count = $statistics->count;
513     $statistics->delete if $confirm;
514     if ( $verbose ) {
515         say $confirm
516           ? sprintf( "Done with purging %d statistics", $count )
517           : sprintf( "%d statistics would have been removed", $count );
518     }
519 }
520
521 if ($pDeletedCatalog) {
522     print "Purging deleted catalog older than $pDeletedCatalog days.\n"
523       if $verbose;
524     my $old_items = Koha::Old::Items->filter_by_last_update( { days => $pDeletedCatalog } );
525     my $old_biblioitems = Koha::Old::Biblioitems->filter_by_last_update( { days => $pDeletedCatalog } );
526     my $old_biblios = Koha::Old::Biblios->filter_by_last_update( { days => $pDeletedCatalog } );
527     my ( $c_i, $c_bi, $c_b ) =
528       ( $old_items->count, $old_biblioitems->count, $old_biblios->count );
529     if ($confirm) {
530         $old_items->delete;
531         $old_biblioitems->delete;
532         $old_biblios->delete;
533     }
534     if ($verbose) {
535         say sprintf(
536             $confirm
537             ? "Done with purging deleted catalog (%d items, %d biblioitems, %d biblios)."
538             : "Deleted catalog would have been removed (%d items, %d biblioitems, %d biblios).",
539         $c_i, $c_bi, $c_b);
540     }
541 }
542
543 if ($pDeletedPatrons) {
544     print "Purging deleted patrons older than $pDeletedPatrons days.\n" if $verbose;
545     my $old_patrons = Koha::Old::Patrons->filter_by_last_update(
546         { timestamp_column_name => 'updated_on', days => $pDeletedPatrons } );
547     my $count = $old_patrons->count;
548     $old_patrons->delete if $confirm;
549     if ($verbose) {
550         say $confirm
551           ? sprintf "Done with purging %d deleted patrons.", $count
552           : sprintf "%d deleted patrons would have been purged.", $count;
553     }
554 }
555
556 if ($pOldIssues) {
557     print "Purging old checkouts older than $pOldIssues days.\n" if $verbose;
558     my $old_checkouts = Koha::Old::Checkouts->filter_by_last_update( { days => $pOldIssues } );
559     my $count = $old_checkouts->count;
560     $old_checkouts->delete if $confirm;
561     if ($verbose) {
562         say $confirm
563           ? sprintf "Done with purging %d old checkouts.", $count
564           : sprintf "%d old checkouts would have been purged.", $count;
565     }
566 }
567
568 if ($pOldReserves) {
569     print "Purging old reserves older than $pOldReserves days.\n" if $verbose;
570     my $old_reserves = Koha::Old::Holds->filter_by_last_update( { days => $pOldReserves } );
571     my $count = $old_reserves->count;
572     $old_reserves->delete if $verbose;
573     if ($verbose) {
574         say $confirm
575           ? sprintf "Done with purging %d old reserves.", $count
576           : sprintf "%d old reserves would have been purged.", $count;
577     }
578 }
579
580 if ($pTransfers) {
581     print "Purging arrived item transfers older than $pTransfers days.\n" if $verbose;
582     my $transfers = Koha::Item::Transfers->filter_by_last_update(
583         {
584             timestamp_column_name => 'datearrived',
585             days => $pTransfers,
586         }
587     );
588     my $count = $transfers->count;
589     $transfers->delete if $verbose;
590     if ($verbose) {
591         say $confirm
592           ? sprintf "Done with purging %d transfers.", $count
593           : sprintf "%d transfers would have been purged.", $count;
594     }
595 }
596
597 if (defined $pPseudoTransactions or $pPseudoTransactionsFrom or $pPseudoTransactionsTo ) {
598     print "Purging pseudonymized transactions\n" if $verbose;
599     my $anonymized_transactions = Koha::PseudonymizedTransactions->filter_by_last_update(
600         {
601             timestamp_column_name => 'datetime',
602             ( defined $pPseudoTransactions  ? ( days => $pPseudoTransactions     ) : () ),
603             ( $pPseudoTransactionsFrom      ? ( from => $pPseudoTransactionsFrom ) : () ),
604             ( $pPseudoTransactionsTo        ? ( to   => $pPseudoTransactionsTo   ) : () ),
605         }
606     );
607     my $count = $anonymized_transactions->count;
608     $anonymized_transactions->delete if $confirm;
609     if ($verbose) {
610         say $confirm
611           ? sprintf "Done with purging %d pseudonymized transactions.", $count
612           : sprintf "%d pseudonymized transactions would have been purged.", $count;
613     }
614 }
615
616 exit(0);
617
618 sub RemoveOldSessions {
619     my ( $id, $a_session, $limit, $lasttime );
620     $limit = time() - 24 * 3600 * $sess_days;
621
622     $sth = $dbh->prepare(q{ SELECT id, a_session FROM sessions });
623     $sth->execute or die $dbh->errstr;
624     $sth->bind_columns( \$id, \$a_session );
625     $sth2  = $dbh->prepare(q{ DELETE FROM sessions WHERE id=? });
626     my $count = 0;
627
628     while ( $sth->fetch ) {
629         $lasttime = 0;
630         if ( $a_session =~ /lasttime:\s+'?(\d+)/ ) {
631             $lasttime = $1;
632         }
633         elsif ( $a_session =~ /(ATIME|CTIME):\s+'?(\d+)/ ) {
634             $lasttime = $2;
635         }
636         if ( $lasttime && $lasttime < $limit ) {
637             $sth2->execute($id) or die $dbh->errstr;
638             $count++;
639         }
640     }
641     if ($verbose) {
642         print "$count sessions were deleted.\n";
643     }
644 }
645
646 sub PurgeImportTables {
647
648     #First purge import_records
649     #Delete cascades to import_biblios, import_items and import_record_matches
650     $sth = $dbh->prepare(
651         q{
652             DELETE FROM import_records
653             WHERE upload_timestamp < date_sub(curdate(), INTERVAL ? DAY)
654         }
655     );
656     $sth->execute($pImport) or die $dbh->errstr;
657
658     # Now purge import_batches
659     # Timestamp cannot be used here without care, because records are added
660     # continuously to batches without updating timestamp (Z39.50 search).
661     # So we only delete older empty batches.
662     # This delete will therefore not have a cascading effect.
663     $sth = $dbh->prepare(
664         q{
665             DELETE ba
666             FROM import_batches ba
667             LEFT JOIN import_records re ON re.import_batch_id=ba.import_batch_id
668             WHERE re.import_record_id IS NULL AND
669             ba.upload_timestamp < date_sub(curdate(), INTERVAL ? DAY)
670         }
671     );
672     $sth->execute($pImport) or die $dbh->errstr;
673 }
674
675 sub PurgeZ3950 {
676     $sth = $dbh->prepare(
677         q{
678             DELETE FROM import_batches
679             WHERE batch_type = 'z3950'
680         }
681     );
682     $sth->execute() or die $dbh->errstr;
683 }
684
685 sub PurgeDebarments {
686     require Koha::Patron::Debarments;
687     my ( $days, $doit ) = @_;
688     my $count = 0;
689     $sth   = $dbh->prepare(
690         q{
691             SELECT borrower_debarment_id
692             FROM borrower_debarments
693             WHERE expiration < date_sub(curdate(), INTERVAL ? DAY)
694         }
695     );
696     $sth->execute($days) or die $dbh->errstr;
697     while ( my ($borrower_debarment_id) = $sth->fetchrow_array ) {
698         Koha::Patron::Debarments::DelDebarment($borrower_debarment_id) if $doit;
699         $count++;
700     }
701     return $count;
702 }
703
704 sub DeleteExpiredSelfRegs {
705     my $cnt= C4::Members::DeleteExpiredOpacRegistrations();
706     print "Removed $cnt expired self-registered borrowers\n" if $verbose;
707 }
708
709 sub DeleteUnverifiedSelfRegs {
710     my $cnt= C4::Members::DeleteUnverifiedOpacRegistrations( $_[0] );
711     print "Removed $cnt unverified self-registrations\n" if $verbose;
712 }
713
714 sub DeleteSpecialHolidays {
715     my ( $days ) = @_;
716
717     my $sth = $dbh->prepare(q{
718         DELETE FROM special_holidays
719         WHERE DATE( CONCAT( year, '-', month, '-', day ) ) < DATE_SUB( CAST(NOW() AS DATE), INTERVAL ? DAY );
720     });
721     my $count = $sth->execute( $days ) + 0;
722     print "Removed $count unique holidays\n" if $verbose;
723 }