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