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