Bug 6435: (follow-up) make -daemon really imply -a and -b
[koha.git] / misc / migration_tools / rebuild_zebra.pl
1 #!/usr/bin/perl
2
3 use strict;
4 #use warnings; FIXME - Bug 2505
5
6 use C4::Context;
7 use Getopt::Long;
8 use File::Temp qw/ tempdir /;
9 use File::Path;
10 use C4::Biblio;
11 use C4::AuthoritiesMarc;
12 use C4::Items;
13 use Koha::RecordProcessor;
14 use XML::LibXML;
15
16 # script that checks zebradir structure & create directories & mandatory files if needed
17 #
18 #
19
20 $|=1; # flushes output
21 # If the cron job starts us in an unreadable dir, we will break without
22 # this.
23 chdir $ENV{HOME} if (!(-r '.'));
24 my $daemon_mode;
25 my $daemon_sleep = 5;
26 my $directory;
27 my $nosanitize;
28 my $skip_export;
29 my $keep_export;
30 my $skip_index;
31 my $reset;
32 my $biblios;
33 my $authorities;
34 my $noxml;
35 my $noshadow;
36 my $do_munge;
37 my $want_help;
38 my $as_xml;
39 my $process_zebraqueue;
40 my $do_not_clear_zebraqueue;
41 my $length;
42 my $where;
43 my $offset;
44 my $run_as_root;
45 my $run_user = (getpwuid($<))[0];
46
47 my $verbose_logging = 0;
48 my $zebraidx_log_opt = " -v none,fatal,warn ";
49 my $result = GetOptions(
50     'daemon'        => \$daemon_mode,
51     'sleep:i'       => \$daemon_sleep,
52     'd:s'           => \$directory,
53     'r|reset'       => \$reset,
54     's'             => \$skip_export,
55     'k'             => \$keep_export,
56     'I|skip-index'  => \$skip_index,
57     'nosanitize'    => \$nosanitize,
58     'b'             => \$biblios,
59     'noxml'         => \$noxml,
60     'w'             => \$noshadow,
61     'munge-config'  => \$do_munge,
62     'a'             => \$authorities,
63     'h|help'        => \$want_help,
64     'x'             => \$as_xml,
65     'y'             => \$do_not_clear_zebraqueue,
66     'z'             => \$process_zebraqueue,
67     'where:s'        => \$where,
68     'length:i'        => \$length,
69     'offset:i'      => \$offset,
70     'v+'             => \$verbose_logging,
71     'run-as-root'    => \$run_as_root,
72 );
73
74 if (not $result or $want_help) {
75     print_usage();
76     exit 0;
77 }
78
79 if( not defined $run_as_root and $run_user eq 'root') {
80     my $msg = "Warning: You are running this script as the user 'root'.\n";
81     $msg   .= "If this is intentional you must explicitly specify this using the -run-as-root switch\n";
82     $msg   .= "Please do '$0 --help' to see usage.\n";
83     die $msg;
84 }
85
86 if ( !$as_xml and $nosanitize ) {
87     my $msg = "Cannot specify both -no_xml and -nosanitize\n";
88     $msg   .= "Please do '$0 --help' to see usage.\n";
89     die $msg;
90 }
91
92 if ($process_zebraqueue and ($skip_export or $reset)) {
93     my $msg = "Cannot specify -r or -s if -z is specified\n";
94     $msg   .= "Please do '$0 --help' to see usage.\n";
95     die $msg;
96 }
97
98 if ($process_zebraqueue and $do_not_clear_zebraqueue) {
99     my $msg = "Cannot specify both -y and -z\n";
100     $msg   .= "Please do '$0 --help' to see usage.\n";
101     die $msg;
102 }
103
104 if ($reset) {
105     $noshadow = 1;
106 }
107
108 if ($noshadow) {
109     $noshadow = ' -n ';
110 }
111
112 if ($daemon_mode) {
113     # incompatible flags handled above: help, reset, and do_not_clear_zebraqueue
114     if ($skip_export or $keep_export or $skip_index or
115           $where or $length or $offset) {
116         my $msg = "Cannot specify -s, -k, -I, -where, -length, or -offset with -daemon.\n";
117         $msg   .= "Please do '$0 --help' to see usage.\n";
118         die $msg;
119     }
120     $authorities = 1;
121     $biblios = 1;
122     $process_zebraqueue = 1;
123 }
124
125 if (not $biblios and not $authorities) {
126     my $msg = "Must specify -b or -a to reindex bibs or authorities\n";
127     $msg   .= "Please do '$0 --help' to see usage.\n";
128     die $msg;
129 }
130
131
132 #  -v is for verbose, which seems backwards here because of how logging is set
133 #    on the CLI of zebraidx.  It works this way.  The default is to not log much
134 if ($verbose_logging >= 2) {
135     $zebraidx_log_opt = '-v none,fatal,warn,all';
136 }
137
138 my $use_tempdir = 0;
139 unless ($directory) {
140     $use_tempdir = 1;
141     $directory = tempdir(CLEANUP => ($keep_export ? 0 : 1));
142 }
143
144
145 my $biblioserverdir = C4::Context->zebraconfig('biblioserver')->{directory};
146 my $authorityserverdir = C4::Context->zebraconfig('authorityserver')->{directory};
147
148 my $kohadir = C4::Context->config('intranetdir');
149 my $bib_index_mode = C4::Context->config('zebra_bib_index_mode') || 'grs1';
150 my $auth_index_mode = C4::Context->config('zebra_auth_index_mode') || 'dom';
151
152 my $dbh = C4::Context->dbh;
153 my ($biblionumbertagfield,$biblionumbertagsubfield) = &GetMarcFromKohaField("biblio.biblionumber","");
154 my ($biblioitemnumbertagfield,$biblioitemnumbertagsubfield) = &GetMarcFromKohaField("biblioitems.biblioitemnumber","");
155
156 if ( $verbose_logging ) {
157     print "Zebra configuration information\n";
158     print "================================\n";
159     print "Zebra biblio directory      = $biblioserverdir\n";
160     print "Zebra authorities directory = $authorityserverdir\n";
161     print "Koha directory              = $kohadir\n";
162     print "BIBLIONUMBER in :     $biblionumbertagfield\$$biblionumbertagsubfield\n";
163     print "BIBLIOITEMNUMBER in : $biblioitemnumbertagfield\$$biblioitemnumbertagsubfield\n";
164     print "================================\n";
165 }
166
167 if ($do_munge) {
168     munge_config();
169 }
170
171 my $tester = XML::LibXML->new();
172
173 if ($daemon_mode) {
174     while (1) {
175         do_one_pass() if ( zebraqueue_not_empty() );
176         sleep $daemon_sleep;
177     }
178 } else {
179     do_one_pass();
180 }
181
182
183 if ( $verbose_logging ) {
184     print "====================\n";
185     print "CLEANING\n";
186     print "====================\n";
187 }
188 if ($keep_export) {
189     print "NOTHING cleaned : the export $directory has been kept.\n";
190     print "You can re-run this script with the -s ";
191     if ($use_tempdir) {
192         print " and -d $directory parameters";
193     } else {
194         print "parameter";
195     }
196     print "\n";
197     print "if you just want to rebuild zebra after changing the record.abs\n";
198     print "or another zebra config file\n";
199 } else {
200     unless ($use_tempdir) {
201         # if we're using a temporary directory
202         # created by File::Temp, it will be removed
203         # automatically.
204         rmtree($directory, 0, 1);
205         print "directory $directory deleted\n";
206     }
207 }
208
209 sub do_one_pass {
210     if ($authorities) {
211         index_records('authority', $directory, $skip_export, $skip_index, $process_zebraqueue, $as_xml, $noxml, $nosanitize, $do_not_clear_zebraqueue, $verbose_logging, $zebraidx_log_opt, $authorityserverdir);
212     } else {
213         print "skipping authorities\n" if ( $verbose_logging );
214     }
215
216     if ($biblios) {
217         index_records('biblio', $directory, $skip_export, $skip_index, $process_zebraqueue, $as_xml, $noxml, $nosanitize, $do_not_clear_zebraqueue, $verbose_logging, $zebraidx_log_opt, $biblioserverdir);
218     } else {
219         print "skipping biblios\n" if ( $verbose_logging );
220     }
221 }
222
223 # Check the zebra update queue and return true if there are records to process
224 # This routine will handle each of -ab, -a, or -b, but in practice we force
225 # -ab when in daemon mode.
226 sub zebraqueue_not_empty {
227     my $where_str;
228
229     if ($authorities && $biblios) {
230         $where_str = 'done = 0;';
231     } elsif ($biblios) {
232         $where_str = 'server = "biblioserver" AND done = 0;';
233     } else {
234         $where_str = 'server = "authorityserver" AND done = 0;';
235     }
236     my $query =
237       $dbh->prepare( 'SELECT COUNT(*) FROM zebraqueue WHERE ' . $where_str );
238
239     $query->execute;
240     my $count = $query->fetchrow_arrayref->[0];
241     print "queued records: $count\n" if $verbose_logging > 0;
242     return $count > 0;
243 }
244
245 # This checks to see if the zebra directories exist under the provided path.
246 # If they don't, then zebra is likely to spit the dummy. This returns true
247 # if the directories had to be created, false otherwise.
248 sub check_zebra_dirs {
249     my ($base) = shift() . '/';
250     my $needed_repairing = 0;
251     my @dirs = ( '', 'key', 'register', 'shadow', 'tmp' );
252     foreach my $dir (@dirs) {
253         my $bdir = $base . $dir;
254         if (! -d $bdir) {
255             $needed_repairing = 1;
256             mkdir $bdir || die "Unable to create '$bdir': $!\n";
257             print "$0: needed to create '$bdir'\n";
258         }
259     }
260     return $needed_repairing;
261 }   # ----------  end of subroutine check_zebra_dirs  ----------
262
263 sub index_records {
264     my ($record_type, $directory, $skip_export, $skip_index, $process_zebraqueue, $as_xml, $noxml, $nosanitize, $do_not_clear_zebraqueue, $verbose_logging, $zebraidx_log_opt, $server_dir) = @_;
265
266     my $num_records_exported = 0;
267     my $records_deleted;
268     my $need_reset = check_zebra_dirs($server_dir);
269     if ($need_reset) {
270         print "$0: found broken zebra server directories: forcing a rebuild\n";
271         $reset = 1;
272     }
273     if ($skip_export && $verbose_logging) {
274         print "====================\n";
275         print "SKIPPING $record_type export\n";
276         print "====================\n";
277     } else {
278         if ( $verbose_logging ) {
279             print "====================\n";
280             print "exporting $record_type\n";
281             print "====================\n";
282         }
283         mkdir "$directory" unless (-d $directory);
284         mkdir "$directory/$record_type" unless (-d "$directory/$record_type");
285         if ($process_zebraqueue) {
286             my $entries = select_zebraqueue_records($record_type, 'deleted');
287             mkdir "$directory/del_$record_type" unless (-d "$directory/del_$record_type");
288             $records_deleted = generate_deleted_marc_records($record_type, $entries, "$directory/del_$record_type", $as_xml);
289             mark_zebraqueue_batch_done($entries);
290             $entries = select_zebraqueue_records($record_type, 'updated');
291             mkdir "$directory/upd_$record_type" unless (-d "$directory/upd_$record_type");
292             $num_records_exported = export_marc_records_from_list($record_type,
293                                                                   $entries, "$directory/upd_$record_type", $as_xml, $noxml, $records_deleted);
294             mark_zebraqueue_batch_done($entries);
295         } else {
296             my $sth = select_all_records($record_type);
297             $num_records_exported = export_marc_records_from_sth($record_type, $sth, "$directory/$record_type", $as_xml, $noxml, $nosanitize);
298             unless ($do_not_clear_zebraqueue) {
299                 mark_all_zebraqueue_done($record_type);
300             }
301         }
302     }
303
304     #
305     # and reindexing everything
306     #
307     if ($skip_index) {
308         if ($verbose_logging) {
309             print "====================\n";
310             print "SKIPPING $record_type indexing\n";
311             print "====================\n";
312         }
313     } else {
314         if ( $verbose_logging ) {
315             print "====================\n";
316             print "REINDEXING zebra\n";
317             print "====================\n";
318         }
319         my $record_fmt = ($as_xml) ? 'marcxml' : 'iso2709' ;
320         if ($process_zebraqueue) {
321             do_indexing($record_type, 'adelete', "$directory/del_$record_type", $reset, $noshadow, $record_fmt, $zebraidx_log_opt)
322                 if %$records_deleted;
323             do_indexing($record_type, 'update', "$directory/upd_$record_type", $reset, $noshadow, $record_fmt, $zebraidx_log_opt)
324                 if $num_records_exported;
325         } else {
326             do_indexing($record_type, 'update', "$directory/$record_type", $reset, $noshadow, $record_fmt, $zebraidx_log_opt)
327                 if ($num_records_exported or $skip_export);
328         }
329     }
330 }
331
332
333 sub select_zebraqueue_records {
334     my ($record_type, $update_type) = @_;
335
336     my $server = ($record_type eq 'biblio') ? 'biblioserver' : 'authorityserver';
337     my $op = ($update_type eq 'deleted') ? 'recordDelete' : 'specialUpdate';
338
339     my $sth = $dbh->prepare("SELECT id, biblio_auth_number
340                              FROM zebraqueue
341                              WHERE server = ?
342                              AND   operation = ?
343                              AND   done = 0
344                              ORDER BY id DESC");
345     $sth->execute($server, $op);
346     my $entries = $sth->fetchall_arrayref({});
347 }
348
349 sub mark_all_zebraqueue_done {
350     my ($record_type) = @_;
351
352     my $server = ($record_type eq 'biblio') ? 'biblioserver' : 'authorityserver';
353
354     my $sth = $dbh->prepare("UPDATE zebraqueue SET done = 1
355                              WHERE server = ?
356                              AND done = 0");
357     $sth->execute($server);
358 }
359
360 sub mark_zebraqueue_batch_done {
361     my ($entries) = @_;
362
363     $dbh->{AutoCommit} = 0;
364     my $sth = $dbh->prepare("UPDATE zebraqueue SET done = 1 WHERE id = ?");
365     $dbh->commit();
366     foreach my $id (map { $_->{id} } @$entries) {
367         $sth->execute($id);
368     }
369     $dbh->{AutoCommit} = 1;
370 }
371
372 sub select_all_records {
373     my $record_type = shift;
374     return ($record_type eq 'biblio') ? select_all_biblios() : select_all_authorities();
375 }
376
377 sub select_all_authorities {
378     my $strsth=qq{SELECT authid FROM auth_header};
379     $strsth.=qq{ WHERE $where } if ($where);
380     $strsth.=qq{ LIMIT $length } if ($length && !$offset);
381     $strsth.=qq{ LIMIT $offset,$length } if ($length && $offset);
382     my $sth = $dbh->prepare($strsth);
383     $sth->execute();
384     return $sth;
385 }
386
387 sub select_all_biblios {
388     my $strsth = qq{ SELECT biblionumber FROM biblioitems };
389     $strsth.=qq{ WHERE $where } if ($where);
390     $strsth.=qq{ LIMIT $length } if ($length && !$offset);
391     $strsth.=qq{ LIMIT $offset,$length } if ($offset);
392     my $sth = $dbh->prepare($strsth);
393     $sth->execute();
394     return $sth;
395 }
396
397 sub include_xml_wrapper {
398     my $as_xml = shift;
399     my $record_type = shift;
400
401     return 0 unless $as_xml;
402     return 1 if $record_type eq 'biblio' and $bib_index_mode eq 'dom';
403     return 1 if $record_type eq 'authority' and $auth_index_mode eq 'dom';
404     return 0;
405
406 }
407
408 sub export_marc_records_from_sth {
409     my ($record_type, $sth, $directory, $as_xml, $noxml, $nosanitize) = @_;
410
411     my $num_exported = 0;
412     open my $fh, '>:encoding(UTF-8) ', "$directory/exported_records" or die $!;
413     if (include_xml_wrapper($as_xml, $record_type)) {
414         # include XML declaration and root element
415         print {$fh} '<?xml version="1.0" encoding="UTF-8"?><collection>';
416     }
417     my $i = 0;
418     my ( $itemtag, $itemsubfield ) = GetMarcFromKohaField("items.itemnumber",'');
419     while (my ($record_number) = $sth->fetchrow_array) {
420         print "." if ( $verbose_logging );
421         print "\r$i" unless ($i++ %100 or !$verbose_logging);
422         if ( $nosanitize ) {
423             my $marcxml = $record_type eq 'biblio'
424                           ? GetXmlBiblio( $record_number )
425                           : GetAuthorityXML( $record_number );
426             if ($record_type eq 'biblio'){
427                 my @items = GetItemsInfo($record_number);
428                 if (@items){
429                     my $record = MARC::Record->new;
430                     $record->encoding('UTF-8');
431                     my @itemsrecord;
432                     foreach my $item (@items){
433                         my $record = Item2Marc($item, $record_number);
434                         push @itemsrecord, $record->field($itemtag);
435                     }
436                     $record->insert_fields_ordered(@itemsrecord);
437                     my $itemsxml = $record->as_xml_record();
438                     $marcxml =
439                         substr($marcxml, 0, length($marcxml)-10) .
440                         substr($itemsxml, index($itemsxml, "</leader>\n", 0) + 10);
441                 }
442             }
443             # extra test to ensure that result is valid XML; otherwise
444             # Zebra won't parse it in DOM mode
445             eval {
446                 my $doc = $tester->parse_string($marcxml);
447             };
448             if ($@) {
449                 warn "Error exporting record $record_number ($record_type): $@\n";
450                 next;
451             }
452             if ( $marcxml ) {
453                 $marcxml =~ s!<\?xml version="1.0" encoding="UTF-8"\?>\n!!;
454                 print {$fh} $marcxml;
455                 $num_exported++;
456             }
457             next;
458         }
459         my ($marc) = get_corrected_marc_record($record_type, $record_number, $noxml);
460         if (defined $marc) {
461             eval {
462                 my $rec;
463                 if ($as_xml) {
464                     $rec = $marc->as_xml_record(C4::Context->preference('marcflavour'));
465                     eval {
466                         my $doc = $tester->parse_string($rec);
467                     };
468                     if ($@) {
469                         die "invalid XML: $@";
470                     }
471                     $rec =~ s!<\?xml version="1.0" encoding="UTF-8"\?>\n!!;
472                 } else {
473                     $rec = $marc->as_usmarc();
474                 }
475                 print {$fh} $rec;
476                 $num_exported++;
477             };
478             if ($@) {
479                 warn "Error exporting record $record_number ($record_type) ".($noxml ? "not XML" : "XML");
480                 warn "... specific error is $@" if $verbose_logging;
481             }
482         }
483     }
484     print "\nRecords exported: $num_exported\n" if ( $verbose_logging );
485     print {$fh} '</collection>' if (include_xml_wrapper($as_xml, $record_type));
486     close $fh;
487     return $num_exported;
488 }
489
490 sub export_marc_records_from_list {
491     my ($record_type, $entries, $directory, $as_xml, $noxml, $records_deleted) = @_;
492
493     my $num_exported = 0;
494     open my $fh, '>:encoding(UTF-8)', "$directory/exported_records" or die $!;
495     if (include_xml_wrapper($as_xml, $record_type)) {
496         # include XML declaration and root element
497         print {$fh} '<?xml version="1.0" encoding="UTF-8"?><collection>';
498     }
499     my $i = 0;
500
501     # Skip any deleted records. We check for this anyway, but this reduces error spam
502     my %found = %$records_deleted;
503     foreach my $record_number ( map { $_->{biblio_auth_number} }
504                                 grep { !$found{ $_->{biblio_auth_number} }++ }
505                                 @$entries ) {
506         print "." if ( $verbose_logging );
507         print "\r$i" unless ($i++ %100 or !$verbose_logging);
508         my ($marc) = get_corrected_marc_record($record_type, $record_number, $noxml);
509         if (defined $marc) {
510             eval {
511                 my $rec;
512                 if ($as_xml) {
513                     $rec = $marc->as_xml_record(C4::Context->preference('marcflavour'));
514                     $rec =~ s!<\?xml version="1.0" encoding="UTF-8"\?>\n!!;
515                 } else {
516                     $rec = $marc->as_usmarc();
517                 }
518                 print {$fh} $rec;
519                 $num_exported++;
520             };
521             if ($@) {
522               warn "Error exporting record $record_number ($record_type) ".($noxml ? "not XML" : "XML");
523             }
524         }
525     }
526     print "\nRecords exported: $num_exported\n" if ( $verbose_logging );
527     print {$fh} '</collection>' if (include_xml_wrapper($as_xml, $record_type));
528     close $fh;
529     return $num_exported;
530 }
531
532 sub generate_deleted_marc_records {
533     my ($record_type, $entries, $directory, $as_xml) = @_;
534
535     my $records_deleted = {};
536     open my $fh, '>:encoding(UTF-8)', "$directory/exported_records" or die $!;
537     if (include_xml_wrapper($as_xml, $record_type)) {
538         # include XML declaration and root element
539         print {$fh} '<?xml version="1.0" encoding="UTF-8"?><collection>';
540     }
541     my $i = 0;
542     foreach my $record_number (map { $_->{biblio_auth_number} } @$entries ) {
543         print "\r$i" unless ($i++ %100 or !$verbose_logging);
544         print "." if ( $verbose_logging );
545
546         my $marc = MARC::Record->new();
547         if ($record_type eq 'biblio') {
548             fix_biblio_ids($marc, $record_number, $record_number);
549         } else {
550             fix_authority_id($marc, $record_number);
551         }
552         if (C4::Context->preference("marcflavour") eq "UNIMARC") {
553             fix_unimarc_100($marc);
554         }
555
556         my $rec;
557         if ($as_xml) {
558             $rec = $marc->as_xml_record(C4::Context->preference('marcflavour'));
559             $rec =~ s!<\?xml version="1.0" encoding="UTF-8"\?>\n!!;
560         } else {
561             $rec = $marc->as_usmarc();
562         }
563         print {$fh} $rec;
564
565         $records_deleted->{$record_number} = 1;
566     }
567     print "\nRecords exported: $i\n" if ( $verbose_logging );
568     print {$fh} '</collection>' if (include_xml_wrapper($as_xml, $record_type));
569     close $fh;
570     return $records_deleted;
571
572
573 }
574
575 sub get_corrected_marc_record {
576     my ($record_type, $record_number, $noxml) = @_;
577
578     my $marc = get_raw_marc_record($record_type, $record_number, $noxml);
579
580     if (defined $marc) {
581         fix_leader($marc);
582         if ($record_type eq 'authority') {
583             fix_authority_id($marc, $record_number);
584         } elsif ($record_type eq 'biblio' && C4::Context->preference('IncludeSeeFromInSearches')) {
585             my $normalizer = Koha::RecordProcessor->new( { filters => 'EmbedSeeFromHeadings' } );
586             $marc = $normalizer->process($marc);
587         }
588         if (C4::Context->preference("marcflavour") eq "UNIMARC") {
589             fix_unimarc_100($marc);
590         }
591     }
592
593     return $marc;
594 }
595
596 sub get_raw_marc_record {
597     my ($record_type, $record_number, $noxml) = @_;
598
599     my $marc;
600     if ($record_type eq 'biblio') {
601         if ($noxml) {
602             my $fetch_sth = $dbh->prepare_cached("SELECT marc FROM biblioitems WHERE biblionumber = ?");
603             $fetch_sth->execute($record_number);
604             if (my ($blob) = $fetch_sth->fetchrow_array) {
605                 $marc = MARC::Record->new_from_usmarc($blob);
606                 unless ($marc) {
607                     warn "error creating MARC::Record from $blob";
608                 }
609             }
610             # failure to find a bib is not a problem -
611             # a delete could have been done before
612             # trying to process a record update
613
614             $fetch_sth->finish();
615             return unless $marc;
616         } else {
617             eval { $marc = GetMarcBiblio($record_number, 1); };
618             if ($@ || !$marc) {
619                 # here we do warn since catching an exception
620                 # means that the bib was found but failed
621                 # to be parsed
622                 warn "error retrieving biblio $record_number";
623                 return;
624             }
625         }
626     } else {
627         eval { $marc = GetAuthority($record_number); };
628         if ($@) {
629             warn "error retrieving authority $record_number";
630             return;
631         }
632     }
633     return $marc;
634 }
635
636 sub fix_leader {
637     # FIXME - this routine is suspect
638     # It blanks the Leader/00-05 and Leader/12-16 to
639     # force them to be recalculated correct when
640     # the $marc->as_usmarc() or $marc->as_xml() is called.
641     # But why is this necessary?  It would be a serious bug
642     # in MARC::Record (definitely) and MARC::File::XML (arguably)
643     # if they are emitting incorrect leader values.
644     my $marc = shift;
645
646     my $leader = $marc->leader;
647     substr($leader,  0, 5) = '     ';
648     substr($leader, 10, 7) = '22     ';
649     $marc->leader(substr($leader, 0, 24));
650 }
651
652 sub fix_biblio_ids {
653     # FIXME - it is essential to ensure that the biblionumber is present,
654     #         otherwise, Zebra will choke on the record.  However, this
655     #         logic belongs in the relevant C4::Biblio APIs.
656     my $marc = shift;
657     my $biblionumber = shift;
658     my $biblioitemnumber;
659     if (@_) {
660         $biblioitemnumber = shift;
661     } else {
662         my $sth = $dbh->prepare(
663             "SELECT biblioitemnumber FROM biblioitems WHERE biblionumber=?");
664         $sth->execute($biblionumber);
665         ($biblioitemnumber) = $sth->fetchrow_array;
666         $sth->finish;
667         unless ($biblioitemnumber) {
668             warn "failed to get biblioitemnumber for biblio $biblionumber";
669             return 0;
670         }
671     }
672
673     # FIXME - this is cheating on two levels
674     # 1. C4::Biblio::_koha_marc_update_bib_ids is meant to be an internal function
675     # 2. Making sure that the biblionumber and biblioitemnumber are correct and
676     #    present in the MARC::Record object ought to be part of GetMarcBiblio.
677     #
678     # On the other hand, this better for now than what rebuild_zebra.pl used to
679     # do, which was duplicate the code for inserting the biblionumber
680     # and biblioitemnumber
681     C4::Biblio::_koha_marc_update_bib_ids($marc, '', $biblionumber, $biblioitemnumber);
682
683     return 1;
684 }
685
686 sub fix_authority_id {
687     # FIXME - as with fix_biblio_ids, the authid must be present
688     #         for Zebra's sake.  However, this really belongs
689     #         in C4::AuthoritiesMarc.
690     my ($marc, $authid) = @_;
691     unless ($marc->field('001') and $marc->field('001')->data() eq $authid){
692         $marc->delete_field($marc->field('001'));
693         $marc->insert_fields_ordered(MARC::Field->new('001',$authid));
694     }
695 }
696
697 sub fix_unimarc_100 {
698     # FIXME - again, if this is necessary, it belongs in C4::AuthoritiesMarc.
699     my $marc = shift;
700
701     my $string;
702     if ( length($marc->subfield( 100, "a" )) == 36 ) {
703         $string = $marc->subfield( 100, "a" );
704         my $f100 = $marc->field(100);
705         $marc->delete_field($f100);
706     }
707     else {
708         $string = POSIX::strftime( "%Y%m%d", localtime );
709         $string =~ s/\-//g;
710         $string = sprintf( "%-*s", 35, $string );
711     }
712     substr( $string, 22, 6, "frey50" );
713     unless ( length($marc->subfield( 100, "a" )) == 36 ) {
714         $marc->delete_field($marc->field(100));
715         $marc->insert_grouped_field(MARC::Field->new( 100, "", "", "a" => $string ));
716     }
717 }
718
719 sub do_indexing {
720     my ($record_type, $op, $record_dir, $reset_index, $noshadow, $record_format, $zebraidx_log_opt) = @_;
721
722     my $zebra_server  = ($record_type eq 'biblio') ? 'biblioserver' : 'authorityserver';
723     my $zebra_db_name = ($record_type eq 'biblio') ? 'biblios' : 'authorities';
724     my $zebra_config  = C4::Context->zebraconfig($zebra_server)->{'config'};
725     my $zebra_db_dir  = C4::Context->zebraconfig($zebra_server)->{'directory'};
726
727     system("zebraidx -c $zebra_config $zebraidx_log_opt -g $record_format -d $zebra_db_name init") if $reset_index;
728     system("zebraidx -c $zebra_config $zebraidx_log_opt $noshadow -g $record_format -d $zebra_db_name $op $record_dir");
729     system("zebraidx -c $zebra_config $zebraidx_log_opt -g $record_format -d $zebra_db_name commit") unless $noshadow;
730
731 }
732
733 sub print_usage {
734     print <<_USAGE_;
735 $0: reindex MARC bibs and/or authorities in Zebra.
736
737 Use this batch job to reindex all biblio or authority
738 records in your Koha database.
739
740 Parameters:
741
742     -b                      index bibliographic records
743
744     -a                      index authority records
745
746     -daemon                 Run in daemon mode.  The program will loop checking
747                             for entries on the zebraqueue table, processing
748                             them incrementally if present, and then sleep
749                             for a few seconds before repeating the process
750                             Checking the zebraqueue table is done with a cheap
751                             SQL query.  This allows for near realtime update of
752                             the zebra search index with low system overhead.
753                             Use -sleep to control the checking interval.
754
755                             Daemon mode implies -z, -a, -b.  The program will
756                             refuse to start if options are present that do not
757                             make sense while running as an incremental update
758                             daemon (e.g. -r or -offset).
759
760     -sleep 10               Seconds to sleep between checks of the zebraqueue
761                             table in daemon mode.  The default is 5 seconds.
762
763     -z                      select only updated and deleted
764                             records marked in the zebraqueue
765                             table.  Cannot be used with -r
766                             or -s.
767
768     -r                      clear Zebra index before
769                             adding records to index. Implies -w.
770
771     -d                      Temporary directory for indexing.
772                             If not specified, one is automatically
773                             created.  The export directory
774                             is automatically deleted unless
775                             you supply the -k switch.
776
777     -k                      Do not delete export directory.
778
779     -s                      Skip export.  Used if you have
780                             already exported the records
781                             in a previous run.
782
783     -noxml                  index from ISO MARC blob
784                             instead of MARC XML.  This
785                             option is recommended only
786                             for advanced user.
787
788     -x                      export and index as xml instead of is02709 (biblios only).
789                             use this if you might have records > 99,999 chars,
790
791     -nosanitize             export biblio/authority records directly from DB marcxml
792                             field without sanitizing records. It speed up
793                             dump process but could fail if DB contains badly
794                             encoded records. Works only with -x,
795
796     -w                      skip shadow indexing for this batch
797
798     -y                      do NOT clear zebraqueue after indexing; normally,
799                             after doing batch indexing, zebraqueue should be
800                             marked done for the affected record type(s) so that
801                             a running zebraqueue_daemon doesn't try to reindex
802                             the same records - specify -y to override this.
803                             Cannot be used with -z.
804
805     -v                      increase the amount of logging.  Normally only
806                             warnings and errors from the indexing are shown.
807                             Use log level 2 (-v -v) to include all Zebra logs.
808
809     --length   1234         how many biblio you want to export
810     --offset 1243           offset you want to start to
811                                 example: --offset 500 --length=500 will result in a LIMIT 500,1000 (exporting 1000 records, starting by the 500th one)
812                                 note that the numbers are NOT related to biblionumber, that's the intended behaviour.
813     --where                 let you specify a WHERE query, like itemtype='BOOK'
814                             or something like that
815
816     --munge-config          Deprecated option to try
817                             to fix Zebra config files.
818
819     --run-as-root           explicitily allow script to run as 'root' user
820
821     --help or -h            show this message.
822 _USAGE_
823 }
824
825 # FIXME: the following routines are deprecated and
826 # will be removed once it is determined whether
827 # a script to fix Zebra configuration files is
828 # actually needed.
829 sub munge_config {
830 #
831 # creating zebra-biblios.cfg depending on system
832 #
833
834 # getting zebraidx directory
835 my $zebraidxdir;
836 foreach (qw(/usr/local/bin/zebraidx
837         /opt/bin/zebraidx
838         /usr/bin/zebraidx
839         )) {
840     if ( -f $_ ) {
841         $zebraidxdir=$_;
842     }
843 }
844
845 unless ($zebraidxdir) {
846     print qq|
847     ERROR: could not find zebraidx directory
848     ERROR: Either zebra is not installed,
849     ERROR: or it's in a directory I don't checked.
850     ERROR: do a which zebraidx and edit this file to add the result you get
851 |;
852     exit;
853 }
854 $zebraidxdir =~ s/\/bin\/.*//;
855 print "Info : zebra is in $zebraidxdir \n";
856
857 # getting modules directory
858 my $modulesdir;
859 foreach (qw(/usr/local/lib/idzebra-2.0/modules/mod-grs-xml.so
860             /usr/local/lib/idzebra/modules/mod-grs-xml.so
861             /usr/lib/idzebra/modules/mod-grs-xml.so
862             /usr/lib/idzebra-2.0/modules/mod-grs-xml.so
863         )) {
864     if ( -f $_ ) {
865         $modulesdir=$_;
866     }
867 }
868
869 unless ($modulesdir) {
870     print qq|
871     ERROR: could not find mod-grs-xml.so directory
872     ERROR: Either zebra is not properly compiled (libxml2 is not setup and you don t have mod-grs-xml.so,
873     ERROR: or it's in a directory I don't checked.
874     ERROR: find where mod-grs-xml.so is and edit this file to add the result you get
875 |;
876     exit;
877 }
878 $modulesdir =~ s/\/modules\/.*//;
879 print "Info: zebra modules dir : $modulesdir\n";
880
881 # getting tab directory
882 my $tabdir;
883 foreach (qw(/usr/local/share/idzebra/tab/explain.att
884             /usr/local/share/idzebra-2.0/tab/explain.att
885             /usr/share/idzebra/tab/explain.att
886             /usr/share/idzebra-2.0/tab/explain.att
887         )) {
888     if ( -f $_ ) {
889         $tabdir=$_;
890     }
891 }
892
893 unless ($tabdir) {
894     print qq|
895     ERROR: could not find explain.att directory
896     ERROR: Either zebra is not properly compiled,
897     ERROR: or it's in a directory I don't checked.
898     ERROR: find where explain.att is and edit this file to add the result you get
899 |;
900     exit;
901 }
902 $tabdir =~ s/\/tab\/.*//;
903 print "Info: tab dir : $tabdir\n";
904
905 #
906 # AUTHORITIES creating directory structure
907 #
908 my $created_dir_or_file = 0;
909 if ($authorities) {
910     if ( $verbose_logging ) {
911         print "====================\n";
912         print "checking directories & files for authorities\n";
913         print "====================\n";
914     }
915     unless (-d "$authorityserverdir") {
916         system("mkdir -p $authorityserverdir");
917         print "Info: created $authorityserverdir\n";
918         $created_dir_or_file++;
919     }
920     unless (-d "$authorityserverdir/lock") {
921         mkdir "$authorityserverdir/lock";
922         print "Info: created $authorityserverdir/lock\n";
923         $created_dir_or_file++;
924     }
925     unless (-d "$authorityserverdir/register") {
926         mkdir "$authorityserverdir/register";
927         print "Info: created $authorityserverdir/register\n";
928         $created_dir_or_file++;
929     }
930     unless (-d "$authorityserverdir/shadow") {
931         mkdir "$authorityserverdir/shadow";
932         print "Info: created $authorityserverdir/shadow\n";
933         $created_dir_or_file++;
934     }
935     unless (-d "$authorityserverdir/tab") {
936         mkdir "$authorityserverdir/tab";
937         print "Info: created $authorityserverdir/tab\n";
938         $created_dir_or_file++;
939     }
940     unless (-d "$authorityserverdir/key") {
941         mkdir "$authorityserverdir/key";
942         print "Info: created $authorityserverdir/key\n";
943         $created_dir_or_file++;
944     }
945
946     unless (-d "$authorityserverdir/etc") {
947         mkdir "$authorityserverdir/etc";
948         print "Info: created $authorityserverdir/etc\n";
949         $created_dir_or_file++;
950     }
951
952     #
953     # AUTHORITIES : copying mandatory files
954     #
955     # the record model, depending on marc flavour
956     unless (-f "$authorityserverdir/tab/record.abs") {
957         if (C4::Context->preference("marcflavour") eq "UNIMARC") {
958             system("cp -f $kohadir/etc/zebradb/marc_defs/unimarc/authorities/record.abs $authorityserverdir/tab/record.abs");
959             print "Info: copied record.abs for UNIMARC\n";
960         } else {
961             system("cp -f $kohadir/etc/zebradb/marc_defs/marc21/authorities/record.abs $authorityserverdir/tab/record.abs");
962             print "Info: copied record.abs for USMARC\n";
963         }
964         $created_dir_or_file++;
965     }
966     unless (-f "$authorityserverdir/tab/sort-string-utf.chr") {
967         system("cp -f $kohadir/etc/zebradb/lang_defs/fr/sort-string-utf.chr $authorityserverdir/tab/sort-string-utf.chr");
968         print "Info: copied sort-string-utf.chr\n";
969         $created_dir_or_file++;
970     }
971     unless (-f "$authorityserverdir/tab/word-phrase-utf.chr") {
972         system("cp -f $kohadir/etc/zebradb/lang_defs/fr/sort-string-utf.chr $authorityserverdir/tab/word-phrase-utf.chr");
973         print "Info: copied word-phase-utf.chr\n";
974         $created_dir_or_file++;
975     }
976     unless (-f "$authorityserverdir/tab/auth1.att") {
977         system("cp -f $kohadir/etc/zebradb/authorities/etc/bib1.att $authorityserverdir/tab/auth1.att");
978         print "Info: copied auth1.att\n";
979         $created_dir_or_file++;
980     }
981     unless (-f "$authorityserverdir/tab/default.idx") {
982         system("cp -f $kohadir/etc/zebradb/etc/default.idx $authorityserverdir/tab/default.idx");
983         print "Info: copied default.idx\n";
984         $created_dir_or_file++;
985     }
986
987     unless (-f "$authorityserverdir/etc/ccl.properties") {
988 #         system("cp -f $kohadir/etc/zebradb/ccl.properties ".C4::Context->zebraconfig('authorityserver')->{ccl2rpn});
989         system("cp -f $kohadir/etc/zebradb/ccl.properties $authorityserverdir/etc/ccl.properties");
990         print "Info: copied ccl.properties\n";
991         $created_dir_or_file++;
992     }
993     unless (-f "$authorityserverdir/etc/pqf.properties") {
994 #         system("cp -f $kohadir/etc/zebradb/pqf.properties ".C4::Context->zebraconfig('authorityserver')->{ccl2rpn});
995         system("cp -f $kohadir/etc/zebradb/pqf.properties $authorityserverdir/etc/pqf.properties");
996         print "Info: copied pqf.properties\n";
997         $created_dir_or_file++;
998     }
999
1000     #
1001     # AUTHORITIES : copying mandatory files
1002     #
1003     unless (-f C4::Context->zebraconfig('authorityserver')->{config}) {
1004     open my $zd, '>:encoding(UTF-8)' ,C4::Context->zebraconfig('authorityserver')->{config};
1005     print {$zd} "
1006 # generated by KOHA/misc/migration_tools/rebuild_zebra.pl
1007 profilePath:\${srcdir:-.}:$authorityserverdir/tab/:$tabdir/tab/:\${srcdir:-.}/tab/
1008
1009 encoding: UTF-8
1010 # Files that describe the attribute sets supported.
1011 attset: auth1.att
1012 attset: explain.att
1013 attset: gils.att
1014
1015 modulePath:$modulesdir/modules/
1016 # Specify record type
1017 iso2709.recordType:grs.marcxml.record
1018 recordType:grs.xml
1019 recordId: (auth1,Local-Number)
1020 storeKeys:1
1021 storeData:1
1022
1023
1024 # Lock File Area
1025 lockDir: $authorityserverdir/lock
1026 perm.anonymous:r
1027 perm.kohaadmin:rw
1028 register: $authorityserverdir/register:4G
1029 shadow: $authorityserverdir/shadow:4G
1030
1031 # Temp File area for result sets
1032 setTmpDir: $authorityserverdir/tmp
1033
1034 # Temp File area for index program
1035 keyTmpDir: $authorityserverdir/key
1036
1037 # Approx. Memory usage during indexing
1038 memMax: 40M
1039 rank:rank-1
1040     ";
1041         print "Info: creating zebra-authorities.cfg\n";
1042         $created_dir_or_file++;
1043     }
1044
1045     if ($created_dir_or_file) {
1046         print "Info: created : $created_dir_or_file directories & files\n";
1047     } else {
1048         print "Info: file & directories OK\n";
1049     }
1050
1051 }
1052 if ($biblios) {
1053     if ( $verbose_logging ) {
1054         print "====================\n";
1055         print "checking directories & files for biblios\n";
1056         print "====================\n";
1057     }
1058
1059     #
1060     # BIBLIOS : creating directory structure
1061     #
1062     unless (-d "$biblioserverdir") {
1063         system("mkdir -p $biblioserverdir");
1064         print "Info: created $biblioserverdir\n";
1065         $created_dir_or_file++;
1066     }
1067     unless (-d "$biblioserverdir/lock") {
1068         mkdir "$biblioserverdir/lock";
1069         print "Info: created $biblioserverdir/lock\n";
1070         $created_dir_or_file++;
1071     }
1072     unless (-d "$biblioserverdir/register") {
1073         mkdir "$biblioserverdir/register";
1074         print "Info: created $biblioserverdir/register\n";
1075         $created_dir_or_file++;
1076     }
1077     unless (-d "$biblioserverdir/shadow") {
1078         mkdir "$biblioserverdir/shadow";
1079         print "Info: created $biblioserverdir/shadow\n";
1080         $created_dir_or_file++;
1081     }
1082     unless (-d "$biblioserverdir/tab") {
1083         mkdir "$biblioserverdir/tab";
1084         print "Info: created $biblioserverdir/tab\n";
1085         $created_dir_or_file++;
1086     }
1087     unless (-d "$biblioserverdir/key") {
1088         mkdir "$biblioserverdir/key";
1089         print "Info: created $biblioserverdir/key\n";
1090         $created_dir_or_file++;
1091     }
1092     unless (-d "$biblioserverdir/etc") {
1093         mkdir "$biblioserverdir/etc";
1094         print "Info: created $biblioserverdir/etc\n";
1095         $created_dir_or_file++;
1096     }
1097
1098     #
1099     # BIBLIOS : copying mandatory files
1100     #
1101     # the record model, depending on marc flavour
1102     unless (-f "$biblioserverdir/tab/record.abs") {
1103         if (C4::Context->preference("marcflavour") eq "UNIMARC") {
1104             system("cp -f $kohadir/etc/zebradb/marc_defs/unimarc/biblios/record.abs $biblioserverdir/tab/record.abs");
1105             print "Info: copied record.abs for UNIMARC\n";
1106         } else {
1107             system("cp -f $kohadir/etc/zebradb/marc_defs/marc21/biblios/record.abs $biblioserverdir/tab/record.abs");
1108             print "Info: copied record.abs for USMARC\n";
1109         }
1110         $created_dir_or_file++;
1111     }
1112     unless (-f "$biblioserverdir/tab/sort-string-utf.chr") {
1113         system("cp -f $kohadir/etc/zebradb/lang_defs/fr/sort-string-utf.chr $biblioserverdir/tab/sort-string-utf.chr");
1114         print "Info: copied sort-string-utf.chr\n";
1115         $created_dir_or_file++;
1116     }
1117     unless (-f "$biblioserverdir/tab/word-phrase-utf.chr") {
1118         system("cp -f $kohadir/etc/zebradb/lang_defs/fr/sort-string-utf.chr $biblioserverdir/tab/word-phrase-utf.chr");
1119         print "Info: copied word-phase-utf.chr\n";
1120         $created_dir_or_file++;
1121     }
1122     unless (-f "$biblioserverdir/tab/bib1.att") {
1123         system("cp -f $kohadir/etc/zebradb/biblios/etc/bib1.att $biblioserverdir/tab/bib1.att");
1124         print "Info: copied bib1.att\n";
1125         $created_dir_or_file++;
1126     }
1127     unless (-f "$biblioserverdir/tab/default.idx") {
1128         system("cp -f $kohadir/etc/zebradb/etc/default.idx $biblioserverdir/tab/default.idx");
1129         print "Info: copied default.idx\n";
1130         $created_dir_or_file++;
1131     }
1132     unless (-f "$biblioserverdir/etc/ccl.properties") {
1133 #         system("cp -f $kohadir/etc/zebradb/ccl.properties ".C4::Context->zebraconfig('biblioserver')->{ccl2rpn});
1134         system("cp -f $kohadir/etc/zebradb/ccl.properties $biblioserverdir/etc/ccl.properties");
1135         print "Info: copied ccl.properties\n";
1136         $created_dir_or_file++;
1137     }
1138     unless (-f "$biblioserverdir/etc/pqf.properties") {
1139 #         system("cp -f $kohadir/etc/zebradb/pqf.properties ".C4::Context->zebraconfig('biblioserver')->{ccl2rpn});
1140         system("cp -f $kohadir/etc/zebradb/pqf.properties $biblioserverdir/etc/pqf.properties");
1141         print "Info: copied pqf.properties\n";
1142         $created_dir_or_file++;
1143     }
1144
1145     #
1146     # BIBLIOS : copying mandatory files
1147     #
1148     unless (-f C4::Context->zebraconfig('biblioserver')->{config}) {
1149     open my $zd, '>:encoding(UTF-8)', C4::Context->zebraconfig('biblioserver')->{config};
1150     print {$zd} "
1151 # generated by KOHA/misc/migrtion_tools/rebuild_zebra.pl
1152 profilePath:\${srcdir:-.}:$biblioserverdir/tab/:$tabdir/tab/:\${srcdir:-.}/tab/
1153
1154 encoding: UTF-8
1155 # Files that describe the attribute sets supported.
1156 attset:bib1.att
1157 attset:explain.att
1158 attset:gils.att
1159
1160 modulePath:$modulesdir/modules/
1161 # Specify record type
1162 iso2709.recordType:grs.marcxml.record
1163 recordType:grs.xml
1164 recordId: (bib1,Local-Number)
1165 storeKeys:1
1166 storeData:1
1167
1168
1169 # Lock File Area
1170 lockDir: $biblioserverdir/lock
1171 perm.anonymous:r
1172 perm.kohaadmin:rw
1173 register: $biblioserverdir/register:4G
1174 shadow: $biblioserverdir/shadow:4G
1175
1176 # Temp File area for result sets
1177 setTmpDir: $biblioserverdir/tmp
1178
1179 # Temp File area for index program
1180 keyTmpDir: $biblioserverdir/key
1181
1182 # Approx. Memory usage during indexing
1183 memMax: 40M
1184 rank:rank-1
1185     ";
1186         print "Info: creating zebra-biblios.cfg\n";
1187         $created_dir_or_file++;
1188     }
1189
1190     if ($created_dir_or_file) {
1191         print "Info: created : $created_dir_or_file directories & files\n";
1192     } else {
1193         print "Info: file & directories OK\n";
1194     }
1195
1196 }
1197 }