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