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