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