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