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