Merge branch 'bug_6554' into 3.14-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         }
465     }
466     print "\nRecords exported: $num_exported\n" if ( $verbose_logging );
467     print {$fh} '</collection>' if (include_xml_wrapper($as_xml, $record_type));
468     close $fh;
469     return $num_exported;
470 }
471
472 sub generate_deleted_marc_records {
473     my ($record_type, $entries, $directory, $as_xml) = @_;
474
475     my $records_deleted = {};
476     open my $fh, '>:encoding(UTF-8)', "$directory/exported_records" or die $!;
477     if (include_xml_wrapper($as_xml, $record_type)) {
478         # include XML declaration and root element
479         print {$fh} '<?xml version="1.0" encoding="UTF-8"?><collection>';
480     }
481     my $i = 0;
482     foreach my $record_number (map { $_->{biblio_auth_number} } @$entries ) {
483         print "\r$i" unless ($i++ %100 or !$verbose_logging);
484         print "." if ( $verbose_logging );
485
486         my $marc = MARC::Record->new();
487         if ($record_type eq 'biblio') {
488             fix_biblio_ids($marc, $record_number, $record_number);
489         } else {
490             fix_authority_id($marc, $record_number);
491         }
492         if (C4::Context->preference("marcflavour") eq "UNIMARC") {
493             fix_unimarc_100($marc);
494         }
495
496         my $rec;
497         if ($as_xml) {
498             $rec = $marc->as_xml_record(C4::Context->preference('marcflavour'));
499             $rec =~ s!<\?xml version="1.0" encoding="UTF-8"\?>\n!!;
500         } else {
501             $rec = $marc->as_usmarc();
502         }
503         print {$fh} $rec;
504
505         $records_deleted->{$record_number} = 1;
506     }
507     print "\nRecords exported: $i\n" if ( $verbose_logging );
508     print {$fh} '</collection>' if (include_xml_wrapper($as_xml, $record_type));
509     close $fh;
510     return $records_deleted;
511     
512
513 }
514
515 sub get_corrected_marc_record {
516     my ($record_type, $record_number, $noxml) = @_;
517
518     my $marc = get_raw_marc_record($record_type, $record_number, $noxml); 
519
520     if (defined $marc) {
521         fix_leader($marc);
522         if ($record_type eq 'authority') {
523             fix_authority_id($marc, $record_number);
524         } elsif ($record_type eq 'biblio' && C4::Context->preference('IncludeSeeFromInSearches')) {
525             my $normalizer = Koha::RecordProcessor->new( { filters => 'EmbedSeeFromHeadings' } );
526             $marc = $normalizer->process($marc);
527         }
528         if (C4::Context->preference("marcflavour") eq "UNIMARC") {
529             fix_unimarc_100($marc);
530         }
531     }
532
533     return $marc;
534 }
535
536 sub get_raw_marc_record {
537     my ($record_type, $record_number, $noxml) = @_;
538   
539     my $marc; 
540     if ($record_type eq 'biblio') {
541         if ($noxml) {
542             my $fetch_sth = $dbh->prepare_cached("SELECT marc FROM biblioitems WHERE biblionumber = ?");
543             $fetch_sth->execute($record_number);
544             if (my ($blob) = $fetch_sth->fetchrow_array) {
545                 $marc = MARC::Record->new_from_usmarc($blob);
546                 unless ($marc) {
547                     warn "error creating MARC::Record from $blob";
548                 }
549             }
550             # failure to find a bib is not a problem -
551             # a delete could have been done before
552             # trying to process a record update
553
554             $fetch_sth->finish();
555             return unless $marc;
556         } else {
557             eval { $marc = GetMarcBiblio($record_number, 1); };
558             if ($@ || !$marc) {
559                 # here we do warn since catching an exception
560                 # means that the bib was found but failed
561                 # to be parsed
562                 warn "error retrieving biblio $record_number";
563                 return;
564             }
565         }
566     } else {
567         eval { $marc = GetAuthority($record_number); };
568         if ($@) {
569             warn "error retrieving authority $record_number";
570             return;
571         }
572     }
573     return $marc;
574 }
575
576 sub fix_leader {
577     # FIXME - this routine is suspect
578     # It blanks the Leader/00-05 and Leader/12-16 to
579     # force them to be recalculated correct when
580     # the $marc->as_usmarc() or $marc->as_xml() is called.
581     # But why is this necessary?  It would be a serious bug
582     # in MARC::Record (definitely) and MARC::File::XML (arguably) 
583     # if they are emitting incorrect leader values.
584     my $marc = shift;
585
586     my $leader = $marc->leader;
587     substr($leader,  0, 5) = '     ';
588     substr($leader, 10, 7) = '22     ';
589     $marc->leader(substr($leader, 0, 24));
590 }
591
592 sub fix_biblio_ids {
593     # FIXME - it is essential to ensure that the biblionumber is present,
594     #         otherwise, Zebra will choke on the record.  However, this
595     #         logic belongs in the relevant C4::Biblio APIs.
596     my $marc = shift;
597     my $biblionumber = shift;
598     my $biblioitemnumber;
599     if (@_) {
600         $biblioitemnumber = shift;
601     } else {    
602         my $sth = $dbh->prepare(
603             "SELECT biblioitemnumber FROM biblioitems WHERE biblionumber=?");
604         $sth->execute($biblionumber);
605         ($biblioitemnumber) = $sth->fetchrow_array;
606         $sth->finish;
607         unless ($biblioitemnumber) {
608             warn "failed to get biblioitemnumber for biblio $biblionumber";
609             return 0;
610         }
611     }
612
613     # FIXME - this is cheating on two levels
614     # 1. C4::Biblio::_koha_marc_update_bib_ids is meant to be an internal function
615     # 2. Making sure that the biblionumber and biblioitemnumber are correct and
616     #    present in the MARC::Record object ought to be part of GetMarcBiblio.
617     #
618     # On the other hand, this better for now than what rebuild_zebra.pl used to
619     # do, which was duplicate the code for inserting the biblionumber 
620     # and biblioitemnumber
621     C4::Biblio::_koha_marc_update_bib_ids($marc, '', $biblionumber, $biblioitemnumber);
622
623     return 1;
624 }
625
626 sub fix_authority_id {
627     # FIXME - as with fix_biblio_ids, the authid must be present
628     #         for Zebra's sake.  However, this really belongs
629     #         in C4::AuthoritiesMarc.
630     my ($marc, $authid) = @_;
631     unless ($marc->field('001') and $marc->field('001')->data() eq $authid){
632         $marc->delete_field($marc->field('001'));
633         $marc->insert_fields_ordered(MARC::Field->new('001',$authid));
634     }
635 }
636
637 sub fix_unimarc_100 {
638     # FIXME - again, if this is necessary, it belongs in C4::AuthoritiesMarc.
639     my $marc = shift;
640
641     my $string;
642     if ( length($marc->subfield( 100, "a" )) == 36 ) {
643         $string = $marc->subfield( 100, "a" );
644         my $f100 = $marc->field(100);
645         $marc->delete_field($f100);
646     }
647     else {
648         $string = POSIX::strftime( "%Y%m%d", localtime );
649         $string =~ s/\-//g;
650         $string = sprintf( "%-*s", 35, $string );
651     }
652     substr( $string, 22, 6, "frey50" );
653     unless ( length($marc->subfield( 100, "a" )) == 36 ) {
654         $marc->delete_field($marc->field(100));
655         $marc->insert_grouped_field(MARC::Field->new( 100, "", "", "a" => $string ));
656     }
657 }
658
659 sub do_indexing {
660     my ($record_type, $op, $record_dir, $reset_index, $noshadow, $record_format, $zebraidx_log_opt) = @_;
661
662     my $zebra_server  = ($record_type eq 'biblio') ? 'biblioserver' : 'authorityserver';
663     my $zebra_db_name = ($record_type eq 'biblio') ? 'biblios' : 'authorities';
664     my $zebra_config  = C4::Context->zebraconfig($zebra_server)->{'config'};
665     my $zebra_db_dir  = C4::Context->zebraconfig($zebra_server)->{'directory'};
666
667     system("zebraidx -c $zebra_config $zebraidx_log_opt -g $record_format -d $zebra_db_name init") if $reset_index;
668     system("zebraidx -c $zebra_config $zebraidx_log_opt $noshadow -g $record_format -d $zebra_db_name $op $record_dir");
669     system("zebraidx -c $zebra_config $zebraidx_log_opt -g $record_format -d $zebra_db_name commit") unless $noshadow;
670
671 }
672
673 sub print_usage {
674     print <<_USAGE_;
675 $0: reindex MARC bibs and/or authorities in Zebra.
676
677 Use this batch job to reindex all biblio or authority
678 records in your Koha database.
679
680 Parameters:
681     -b                      index bibliographic records
682
683     -a                      index authority records
684
685     -z                      select only updated and deleted
686                             records marked in the zebraqueue
687                             table.  Cannot be used with -r
688                             or -s.
689
690     -r                      clear Zebra index before
691                             adding records to index. Implies -w.
692
693     -d                      Temporary directory for indexing.
694                             If not specified, one is automatically
695                             created.  The export directory
696                             is automatically deleted unless
697                             you supply the -k switch.
698
699     -k                      Do not delete export directory.
700
701     -s                      Skip export.  Used if you have
702                             already exported the records 
703                             in a previous run.
704
705     -noxml                  index from ISO MARC blob
706                             instead of MARC XML.  This
707                             option is recommended only
708                             for advanced user.
709
710     -x                      export and index as xml instead of is02709 (biblios only).
711                             use this if you might have records > 99,999 chars,
712                                                         
713     -nosanitize             export biblio/authority records directly from DB marcxml
714                             field without sanitizing records. It speed up
715                             dump process but could fail if DB contains badly
716                             encoded records. Works only with -x,
717
718     -w                      skip shadow indexing for this batch
719
720     -y                      do NOT clear zebraqueue after indexing; normally,
721                             after doing batch indexing, zebraqueue should be
722                             marked done for the affected record type(s) so that
723                             a running zebraqueue_daemon doesn't try to reindex
724                             the same records - specify -y to override this.  
725                             Cannot be used with -z.
726
727     -v                      increase the amount of logging.  Normally only 
728                             warnings and errors from the indexing are shown.
729                             Use log level 2 (-v -v) to include all Zebra logs.
730
731     --length   1234         how many biblio you want to export
732     --offset 1243           offset you want to start to
733                                 example: --offset 500 --length=500 will result in a LIMIT 500,1000 (exporting 1000 records, starting by the 500th one)
734                                 note that the numbers are NOT related to biblionumber, that's the intended behaviour.
735     --where                 let you specify a WHERE query, like itemtype='BOOK'
736                             or something like that
737
738     --munge-config          Deprecated option to try
739                             to fix Zebra config files.
740     --help or -h            show this message.
741 _USAGE_
742 }
743
744 # FIXME: the following routines are deprecated and 
745 # will be removed once it is determined whether
746 # a script to fix Zebra configuration files is 
747 # actually needed.
748 sub munge_config {
749 #
750 # creating zebra-biblios.cfg depending on system
751 #
752
753 # getting zebraidx directory
754 my $zebraidxdir;
755 foreach (qw(/usr/local/bin/zebraidx
756         /opt/bin/zebraidx
757         /usr/bin/zebraidx
758         )) {
759     if ( -f $_ ) {
760         $zebraidxdir=$_;
761     }
762 }
763
764 unless ($zebraidxdir) {
765     print qq|
766     ERROR: could not find zebraidx directory
767     ERROR: Either zebra is not installed,
768     ERROR: or it's in a directory I don't checked.
769     ERROR: do a which zebraidx and edit this file to add the result you get
770 |;
771     exit;
772 }
773 $zebraidxdir =~ s/\/bin\/.*//;
774 print "Info : zebra is in $zebraidxdir \n";
775
776 # getting modules directory
777 my $modulesdir;
778 foreach (qw(/usr/local/lib/idzebra-2.0/modules/mod-grs-xml.so
779             /usr/local/lib/idzebra/modules/mod-grs-xml.so
780             /usr/lib/idzebra/modules/mod-grs-xml.so
781             /usr/lib/idzebra-2.0/modules/mod-grs-xml.so
782         )) {
783     if ( -f $_ ) {
784         $modulesdir=$_;
785     }
786 }
787
788 unless ($modulesdir) {
789     print qq|
790     ERROR: could not find mod-grs-xml.so directory
791     ERROR: Either zebra is not properly compiled (libxml2 is not setup and you don t have mod-grs-xml.so,
792     ERROR: or it's in a directory I don't checked.
793     ERROR: find where mod-grs-xml.so is and edit this file to add the result you get
794 |;
795     exit;
796 }
797 $modulesdir =~ s/\/modules\/.*//;
798 print "Info: zebra modules dir : $modulesdir\n";
799
800 # getting tab directory
801 my $tabdir;
802 foreach (qw(/usr/local/share/idzebra/tab/explain.att
803             /usr/local/share/idzebra-2.0/tab/explain.att
804             /usr/share/idzebra/tab/explain.att
805             /usr/share/idzebra-2.0/tab/explain.att
806         )) {
807     if ( -f $_ ) {
808         $tabdir=$_;
809     }
810 }
811
812 unless ($tabdir) {
813     print qq|
814     ERROR: could not find explain.att directory
815     ERROR: Either zebra is not properly compiled,
816     ERROR: or it's in a directory I don't checked.
817     ERROR: find where explain.att is and edit this file to add the result you get
818 |;
819     exit;
820 }
821 $tabdir =~ s/\/tab\/.*//;
822 print "Info: tab dir : $tabdir\n";
823
824 #
825 # AUTHORITIES creating directory structure
826 #
827 my $created_dir_or_file = 0;
828 if ($authorities) {
829     if ( $verbose_logging ) {
830         print "====================\n";
831         print "checking directories & files for authorities\n";
832         print "====================\n";
833     }
834     unless (-d "$authorityserverdir") {
835         system("mkdir -p $authorityserverdir");
836         print "Info: created $authorityserverdir\n";
837         $created_dir_or_file++;
838     }
839     unless (-d "$authorityserverdir/lock") {
840         mkdir "$authorityserverdir/lock";
841         print "Info: created $authorityserverdir/lock\n";
842         $created_dir_or_file++;
843     }
844     unless (-d "$authorityserverdir/register") {
845         mkdir "$authorityserverdir/register";
846         print "Info: created $authorityserverdir/register\n";
847         $created_dir_or_file++;
848     }
849     unless (-d "$authorityserverdir/shadow") {
850         mkdir "$authorityserverdir/shadow";
851         print "Info: created $authorityserverdir/shadow\n";
852         $created_dir_or_file++;
853     }
854     unless (-d "$authorityserverdir/tab") {
855         mkdir "$authorityserverdir/tab";
856         print "Info: created $authorityserverdir/tab\n";
857         $created_dir_or_file++;
858     }
859     unless (-d "$authorityserverdir/key") {
860         mkdir "$authorityserverdir/key";
861         print "Info: created $authorityserverdir/key\n";
862         $created_dir_or_file++;
863     }
864     
865     unless (-d "$authorityserverdir/etc") {
866         mkdir "$authorityserverdir/etc";
867         print "Info: created $authorityserverdir/etc\n";
868         $created_dir_or_file++;
869     }
870     
871     #
872     # AUTHORITIES : copying mandatory files
873     #
874     # the record model, depending on marc flavour
875     unless (-f "$authorityserverdir/tab/record.abs") {
876         if (C4::Context->preference("marcflavour") eq "UNIMARC") {
877             system("cp -f $kohadir/etc/zebradb/marc_defs/unimarc/authorities/record.abs $authorityserverdir/tab/record.abs");
878             print "Info: copied record.abs for UNIMARC\n";
879         } else {
880             system("cp -f $kohadir/etc/zebradb/marc_defs/marc21/authorities/record.abs $authorityserverdir/tab/record.abs");
881             print "Info: copied record.abs for USMARC\n";
882         }
883         $created_dir_or_file++;
884     }
885     unless (-f "$authorityserverdir/tab/sort-string-utf.chr") {
886         system("cp -f $kohadir/etc/zebradb/lang_defs/fr/sort-string-utf.chr $authorityserverdir/tab/sort-string-utf.chr");
887         print "Info: copied sort-string-utf.chr\n";
888         $created_dir_or_file++;
889     }
890     unless (-f "$authorityserverdir/tab/word-phrase-utf.chr") {
891         system("cp -f $kohadir/etc/zebradb/lang_defs/fr/sort-string-utf.chr $authorityserverdir/tab/word-phrase-utf.chr");
892         print "Info: copied word-phase-utf.chr\n";
893         $created_dir_or_file++;
894     }
895     unless (-f "$authorityserverdir/tab/auth1.att") {
896         system("cp -f $kohadir/etc/zebradb/authorities/etc/bib1.att $authorityserverdir/tab/auth1.att");
897         print "Info: copied auth1.att\n";
898         $created_dir_or_file++;
899     }
900     unless (-f "$authorityserverdir/tab/default.idx") {
901         system("cp -f $kohadir/etc/zebradb/etc/default.idx $authorityserverdir/tab/default.idx");
902         print "Info: copied default.idx\n";
903         $created_dir_or_file++;
904     }
905     
906     unless (-f "$authorityserverdir/etc/ccl.properties") {
907 #         system("cp -f $kohadir/etc/zebradb/ccl.properties ".C4::Context->zebraconfig('authorityserver')->{ccl2rpn});
908         system("cp -f $kohadir/etc/zebradb/ccl.properties $authorityserverdir/etc/ccl.properties");
909         print "Info: copied ccl.properties\n";
910         $created_dir_or_file++;
911     }
912     unless (-f "$authorityserverdir/etc/pqf.properties") {
913 #         system("cp -f $kohadir/etc/zebradb/pqf.properties ".C4::Context->zebraconfig('authorityserver')->{ccl2rpn});
914         system("cp -f $kohadir/etc/zebradb/pqf.properties $authorityserverdir/etc/pqf.properties");
915         print "Info: copied pqf.properties\n";
916         $created_dir_or_file++;
917     }
918     
919     #
920     # AUTHORITIES : copying mandatory files
921     #
922     unless (-f C4::Context->zebraconfig('authorityserver')->{config}) {
923     open my $zd, '>:encoding(UTF-8)' ,C4::Context->zebraconfig('authorityserver')->{config};
924     print {$zd} "
925 # generated by KOHA/misc/migration_tools/rebuild_zebra.pl 
926 profilePath:\${srcdir:-.}:$authorityserverdir/tab/:$tabdir/tab/:\${srcdir:-.}/tab/
927
928 encoding: UTF-8
929 # Files that describe the attribute sets supported.
930 attset: auth1.att
931 attset: explain.att
932 attset: gils.att
933
934 modulePath:$modulesdir/modules/
935 # Specify record type
936 iso2709.recordType:grs.marcxml.record
937 recordType:grs.xml
938 recordId: (auth1,Local-Number)
939 storeKeys:1
940 storeData:1
941
942
943 # Lock File Area
944 lockDir: $authorityserverdir/lock
945 perm.anonymous:r
946 perm.kohaadmin:rw
947 register: $authorityserverdir/register:4G
948 shadow: $authorityserverdir/shadow:4G
949
950 # Temp File area for result sets
951 setTmpDir: $authorityserverdir/tmp
952
953 # Temp File area for index program
954 keyTmpDir: $authorityserverdir/key
955
956 # Approx. Memory usage during indexing
957 memMax: 40M
958 rank:rank-1
959     ";
960         print "Info: creating zebra-authorities.cfg\n";
961         $created_dir_or_file++;
962     }
963     
964     if ($created_dir_or_file) {
965         print "Info: created : $created_dir_or_file directories & files\n";
966     } else {
967         print "Info: file & directories OK\n";
968     }
969     
970 }
971 if ($biblios) {
972     if ( $verbose_logging ) {
973         print "====================\n";
974         print "checking directories & files for biblios\n";
975         print "====================\n";
976     }
977
978     #
979     # BIBLIOS : creating directory structure
980     #
981     unless (-d "$biblioserverdir") {
982         system("mkdir -p $biblioserverdir");
983         print "Info: created $biblioserverdir\n";
984         $created_dir_or_file++;
985     }
986     unless (-d "$biblioserverdir/lock") {
987         mkdir "$biblioserverdir/lock";
988         print "Info: created $biblioserverdir/lock\n";
989         $created_dir_or_file++;
990     }
991     unless (-d "$biblioserverdir/register") {
992         mkdir "$biblioserverdir/register";
993         print "Info: created $biblioserverdir/register\n";
994         $created_dir_or_file++;
995     }
996     unless (-d "$biblioserverdir/shadow") {
997         mkdir "$biblioserverdir/shadow";
998         print "Info: created $biblioserverdir/shadow\n";
999         $created_dir_or_file++;
1000     }
1001     unless (-d "$biblioserverdir/tab") {
1002         mkdir "$biblioserverdir/tab";
1003         print "Info: created $biblioserverdir/tab\n";
1004         $created_dir_or_file++;
1005     }
1006     unless (-d "$biblioserverdir/key") {
1007         mkdir "$biblioserverdir/key";
1008         print "Info: created $biblioserverdir/key\n";
1009         $created_dir_or_file++;
1010     }
1011     unless (-d "$biblioserverdir/etc") {
1012         mkdir "$biblioserverdir/etc";
1013         print "Info: created $biblioserverdir/etc\n";
1014         $created_dir_or_file++;
1015     }
1016     
1017     #
1018     # BIBLIOS : copying mandatory files
1019     #
1020     # the record model, depending on marc flavour
1021     unless (-f "$biblioserverdir/tab/record.abs") {
1022         if (C4::Context->preference("marcflavour") eq "UNIMARC") {
1023             system("cp -f $kohadir/etc/zebradb/marc_defs/unimarc/biblios/record.abs $biblioserverdir/tab/record.abs");
1024             print "Info: copied record.abs for UNIMARC\n";
1025         } else {
1026             system("cp -f $kohadir/etc/zebradb/marc_defs/marc21/biblios/record.abs $biblioserverdir/tab/record.abs");
1027             print "Info: copied record.abs for USMARC\n";
1028         }
1029         $created_dir_or_file++;
1030     }
1031     unless (-f "$biblioserverdir/tab/sort-string-utf.chr") {
1032         system("cp -f $kohadir/etc/zebradb/lang_defs/fr/sort-string-utf.chr $biblioserverdir/tab/sort-string-utf.chr");
1033         print "Info: copied sort-string-utf.chr\n";
1034         $created_dir_or_file++;
1035     }
1036     unless (-f "$biblioserverdir/tab/word-phrase-utf.chr") {
1037         system("cp -f $kohadir/etc/zebradb/lang_defs/fr/sort-string-utf.chr $biblioserverdir/tab/word-phrase-utf.chr");
1038         print "Info: copied word-phase-utf.chr\n";
1039         $created_dir_or_file++;
1040     }
1041     unless (-f "$biblioserverdir/tab/bib1.att") {
1042         system("cp -f $kohadir/etc/zebradb/biblios/etc/bib1.att $biblioserverdir/tab/bib1.att");
1043         print "Info: copied bib1.att\n";
1044         $created_dir_or_file++;
1045     }
1046     unless (-f "$biblioserverdir/tab/default.idx") {
1047         system("cp -f $kohadir/etc/zebradb/etc/default.idx $biblioserverdir/tab/default.idx");
1048         print "Info: copied default.idx\n";
1049         $created_dir_or_file++;
1050     }
1051     unless (-f "$biblioserverdir/etc/ccl.properties") {
1052 #         system("cp -f $kohadir/etc/zebradb/ccl.properties ".C4::Context->zebraconfig('biblioserver')->{ccl2rpn});
1053         system("cp -f $kohadir/etc/zebradb/ccl.properties $biblioserverdir/etc/ccl.properties");
1054         print "Info: copied ccl.properties\n";
1055         $created_dir_or_file++;
1056     }
1057     unless (-f "$biblioserverdir/etc/pqf.properties") {
1058 #         system("cp -f $kohadir/etc/zebradb/pqf.properties ".C4::Context->zebraconfig('biblioserver')->{ccl2rpn});
1059         system("cp -f $kohadir/etc/zebradb/pqf.properties $biblioserverdir/etc/pqf.properties");
1060         print "Info: copied pqf.properties\n";
1061         $created_dir_or_file++;
1062     }
1063     
1064     #
1065     # BIBLIOS : copying mandatory files
1066     #
1067     unless (-f C4::Context->zebraconfig('biblioserver')->{config}) {
1068     open my $zd, '>:encoding(UTF-8)', C4::Context->zebraconfig('biblioserver')->{config};
1069     print {$zd} "
1070 # generated by KOHA/misc/migrtion_tools/rebuild_zebra.pl 
1071 profilePath:\${srcdir:-.}:$biblioserverdir/tab/:$tabdir/tab/:\${srcdir:-.}/tab/
1072
1073 encoding: UTF-8
1074 # Files that describe the attribute sets supported.
1075 attset:bib1.att
1076 attset:explain.att
1077 attset:gils.att
1078
1079 modulePath:$modulesdir/modules/
1080 # Specify record type
1081 iso2709.recordType:grs.marcxml.record
1082 recordType:grs.xml
1083 recordId: (bib1,Local-Number)
1084 storeKeys:1
1085 storeData:1
1086
1087
1088 # Lock File Area
1089 lockDir: $biblioserverdir/lock
1090 perm.anonymous:r
1091 perm.kohaadmin:rw
1092 register: $biblioserverdir/register:4G
1093 shadow: $biblioserverdir/shadow:4G
1094
1095 # Temp File area for result sets
1096 setTmpDir: $biblioserverdir/tmp
1097
1098 # Temp File area for index program
1099 keyTmpDir: $biblioserverdir/key
1100
1101 # Approx. Memory usage during indexing
1102 memMax: 40M
1103 rank:rank-1
1104     ";
1105         print "Info: creating zebra-biblios.cfg\n";
1106         $created_dir_or_file++;
1107     }
1108     
1109     if ($created_dir_or_file) {
1110         print "Info: created : $created_dir_or_file directories & files\n";
1111     } else {
1112         print "Info: file & directories OK\n";
1113     }
1114     
1115 }
1116 }