Bug 23377: use ENV syspref override instead of complete disable
[koha-ffzg.git] / misc / migration_tools / bulkmarcimport.pl
1 #!/usr/bin/perl
2 # Import an iso2709 file into Koha 3
3
4 use Modern::Perl;
5 #use diagnostics;
6 BEGIN {
7     # find Koha's Perl modules
8     # test carefully before changing this
9     use FindBin;
10     eval { require "$FindBin::Bin/../kohalib.pl" };
11 }
12
13 # Koha modules used
14 use MARC::File::USMARC;
15 use MARC::File::XML;
16 use MARC::Record;
17 use MARC::Batch;
18 use MARC::Charset;
19
20 use Koha::Script;
21 use C4::Context;
22 use C4::Biblio;
23 use C4::Koha;
24 use C4::Debug;
25 use C4::Charset;
26 use C4::Items;
27 use C4::MarcModificationTemplates;
28
29 use YAML;
30 use Unicode::Normalize;
31 use Time::HiRes qw(gettimeofday);
32 use Getopt::Long;
33 use IO::File;
34 use Pod::Usage;
35
36 use Koha::Biblios;
37 use Koha::SearchEngine;
38 use Koha::SearchEngine::Search;
39
40 use open qw( :std :encoding(UTF-8) );
41 binmode( STDOUT, ":encoding(UTF-8)" );
42 my ( $input_marc_file, $number, $offset) = ('',0,0);
43 my ($version, $delete, $test_parameter, $skip_marc8_conversion, $char_encoding, $verbose, $commit, $fk_off,$format,$biblios,$authorities,$keepids,$match, $isbn_check, $logfile);
44 my ( $insert, $filters, $update, $all, $yamlfile, $authtypes, $append );
45 my $cleanisbn = 1;
46 my ($sourcetag,$sourcesubfield,$idmapfl, $dedup_barcode);
47 my $framework = '';
48 my $localcust;
49 my $marc_mod_template = '';
50 my $marc_mod_template_id = -1;
51
52 $|=1;
53
54 GetOptions(
55     'commit:f'    => \$commit,
56     'file:s'    => \$input_marc_file,
57     'n:f' => \$number,
58     'o|offset:f' => \$offset,
59     'h' => \$version,
60     'd' => \$delete,
61     't|test' => \$test_parameter,
62     's' => \$skip_marc8_conversion,
63     'c:s' => \$char_encoding,
64     'v:+' => \$verbose,
65     'fk' => \$fk_off,
66     'm:s' => \$format,
67     'l:s' => \$logfile,
68     'append' => \$append,
69     'k|keepids:s' => \$keepids,
70     'b|biblios' => \$biblios,
71     'a|authorities' => \$authorities,
72     'authtypes:s' => \$authtypes,
73     'filter=s@'     => \$filters,
74     'insert'        => \$insert,
75     'update'        => \$update,
76     'all'           => \$all,
77     'match=s@'    => \$match,
78     'i|isbn' => \$isbn_check,
79     'x:s' => \$sourcetag,
80     'y:s' => \$sourcesubfield,
81     'idmap:s' => \$idmapfl,
82     'cleanisbn!'     => \$cleanisbn,
83     'yaml:s'        => \$yamlfile,
84     'dedupbarcode' => \$dedup_barcode,
85     'framework=s' => \$framework,
86     'custom:s'    => \$localcust,
87     'marcmodtemplate:s' => \$marc_mod_template,
88 );
89 $biblios ||= !$authorities;
90 $insert  ||= !$update;
91 my $writemode = ($append) ? "a" : "w";
92
93 pod2usage( -msg => "\nYou must specify either --biblios or --authorities, not both.\n", -exitval ) if $biblios && $authorities;
94
95 if ($all) {
96     $insert = 1;
97     $update = 1;
98 }
99
100 if ($version || ($input_marc_file eq '')) {
101     pod2usage( -verbose => 2 );
102     exit;
103 }
104 if( $update && !( $match || $isbn_check ) ) {
105     warn "Using -update without -match or -isbn seems to be useless.\n";
106 }
107
108 if(defined $localcust) { #local customize module
109     if(!-e $localcust) {
110         $localcust= $localcust||'LocalChanges'; #default name
111         $localcust=~ s/^.*\/([^\/]+)$/$1/; #extract file name only
112         $localcust=~ s/\.pm$//;           #remove extension
113         my $fqcust= $FindBin::Bin."/$localcust.pm"; #try migration_tools dir
114         if(-e $fqcust) {
115             $localcust= $fqcust;
116         }
117         else {
118             print "WARNING: customize module $localcust.pm not found!\n";
119             exit 1;
120         }
121     }
122     require $localcust if $localcust;
123     $localcust=\&customize if $localcust;
124 }
125
126 if($marc_mod_template ne '') {
127     my @templates = GetModificationTemplates();
128     foreach my $this_template (@templates) {
129         if($this_template->{'name'} eq $marc_mod_template) {
130             if($marc_mod_template_id < 0) {
131                 $marc_mod_template_id = $this_template->{'template_id'};
132             } else {
133                 print "WARNING: MARC modification template name " .
134                 "'$marc_mod_template' matches multiple templates. " .
135                 "Please rename these templates\n";
136                 exit 1;
137             }
138         }
139     }
140     if($marc_mod_template_id < 0) {
141         die "Can't located MARC modification template '$marc_mod_template'\n";
142     } else {
143         print "Records will be modified using MARC modofication template: $marc_mod_template\n" if $verbose;
144     }
145 }
146
147 my $dbh = C4::Context->dbh;
148 my $heading_fields=get_heading_fields();
149
150 if (defined $idmapfl) {
151   open(IDMAP,">$idmapfl") or die "cannot open $idmapfl \n";
152 }
153
154 if ((not defined $sourcesubfield) && (not defined $sourcetag)){
155   $sourcetag="910";
156   $sourcesubfield="a";
157 }
158
159
160 # Disable logging for the biblios and authorities import operation. It would unnecessarily
161 # slow the import
162 $ENV{SYSPREF_OVERRIDE_CataloguingLog} = 0;
163 $ENV{SYSPREF_OVERRIDE_AuthoritiesLog} = 0;
164
165 if ($fk_off) {
166         $dbh->do("SET FOREIGN_KEY_CHECKS = 0");
167 }
168
169
170 if ($delete) {
171         if ($biblios){
172         print "deleting biblios\n";
173         $dbh->do("DELETE FROM biblio");
174         $dbh->do("ALTER TABLE biblio AUTO_INCREMENT = 1");
175         $dbh->do("DELETE FROM biblioitems");
176         $dbh->do("ALTER TABLE biblioitems AUTO_INCREMENT = 1");
177         $dbh->do("DELETE FROM items");
178         $dbh->do("ALTER TABLE items AUTO_INCREMENT = 1");
179         }
180         else {
181         print "deleting authorities\n";
182         $dbh->do("truncate auth_header");
183         }
184     $dbh->do("truncate zebraqueue");
185 }
186
187
188
189 if ($test_parameter) {
190     print "TESTING MODE ONLY\n    DOING NOTHING\n===============\n";
191 }
192
193 my $marcFlavour = C4::Context->preference('marcflavour') || 'MARC21';
194
195 # The definition of $searcher must be before MARC::Batch->new
196 my $searcher = Koha::SearchEngine::Search->new(
197     {
198         index => (
199               $authorities
200             ? $Koha::SearchEngine::AUTHORITIES_INDEX
201             : $Koha::SearchEngine::BIBLIOS_INDEX
202         )
203     }
204 );
205
206 print "Characteristic MARC flavour: $marcFlavour\n" if $verbose;
207 my $starttime = gettimeofday;
208 my $batch;
209 my $fh = IO::File->new($input_marc_file); # don't let MARC::Batch open the file, as it applies the ':utf8' IO layer
210 if (defined $format && $format =~ /XML/i) {
211     # ugly hack follows -- MARC::File::XML, when used by MARC::Batch,
212     # appears to try to convert incoming XML records from MARC-8
213     # to UTF-8.  Setting the BinaryEncoding key turns that off
214     # TODO: see what happens to ISO-8859-1 XML files.
215     # TODO: determine if MARC::Batch can be fixed to handle
216     #       XML records properly -- it probably should be
217     #       be using a proper push or pull XML parser to
218     #       extract the records, not using regexes to look
219     #       for <record>.*</record>.
220     $MARC::File::XML::_load_args{BinaryEncoding} = 'utf-8';
221     my $recordformat= ($marcFlavour eq "MARC21"?"USMARC":uc($marcFlavour));
222 #UNIMARC Authorities have a different way to manage encoding than UNIMARC biblios.
223     $recordformat=$recordformat."AUTH" if ($authorities and $marcFlavour ne "MARC21");
224     $MARC::File::XML::_load_args{RecordFormat} = $recordformat;
225     $batch = MARC::Batch->new( 'XML', $fh );
226 } else {
227     $batch = MARC::Batch->new( 'USMARC', $fh );
228 }
229 $batch->warnings_off();
230 $batch->strict_off();
231 my $i=0;
232 my $commitnum = $commit ? $commit : 50;
233 my $yamlhash;
234
235 # Skip file offset
236 if ( $offset ) {
237     print "Skipping file offset: $offset records\n";
238     $batch->next() while ($offset--);
239 }
240
241 my ($tagid,$subfieldid);
242 if ($authorities){
243           $tagid='001';
244 }
245 else {
246    ( $tagid, $subfieldid ) =
247             GetMarcFromKohaField( "biblio.biblionumber" );
248         $tagid||="001";
249 }
250
251 # the SQL query to search on isbn
252 my $sth_isbn = $dbh->prepare("SELECT biblionumber,biblioitemnumber FROM biblioitems WHERE isbn=?");
253
254 $dbh->{AutoCommit} = 0;
255 my $loghandle;
256 if ($logfile){
257    $loghandle= IO::File->new($logfile, $writemode) ;
258    print $loghandle "id;operation;status\n";
259 }
260
261 RECORD: while (  ) {
262     my $record;
263     # get records
264     eval { $record = $batch->next() };
265     if ( $@ ) {
266         print "Bad MARC record $i: $@ skipped\n";
267         # FIXME - because MARC::Batch->next() combines grabbing the next
268         # blob and parsing it into one operation, a correctable condition
269         # such as a MARC-8 record claiming that it's UTF-8 can't be recovered
270         # from because we don't have access to the original blob.  Note
271         # that the staging import can deal with this condition (via
272         # C4::Charset::MarcToUTF8Record) because it doesn't use MARC::Batch.
273         next;
274     }
275     # skip if we get an empty record (that is MARC valid, but will result in AddBiblio failure
276     last unless ( $record );
277     $i++;
278     if( ($verbose//1)==1 ) { #no dot for verbose==2
279         print "." . ( $i % 100==0 ? "\n$i" : '' );
280     }
281
282     # transcode the record to UTF8 if needed & applicable.
283     if ($record->encoding() eq 'MARC-8' and not $skip_marc8_conversion) {
284         # FIXME update condition
285         my ($guessed_charset, $charset_errors);
286          ($record, $guessed_charset, $charset_errors) = MarcToUTF8Record($record, $marcFlavour.(($authorities and $marcFlavour ne "MARC21")?'AUTH':''));
287         if ($guessed_charset eq 'failed') {
288             warn "ERROR: failed to perform character conversion for record $i\n";
289             next RECORD;            
290         }
291     }
292     SetUTF8Flag($record);
293     if($marc_mod_template_id > 0) {
294     print "Modifying MARC\n" if $verbose;
295     ModifyRecordWithTemplate( $marc_mod_template_id, $record );
296     }
297     &$localcust($record) if $localcust;
298     my $isbn;
299     # remove trailing - in isbn (only for biblios, of course)
300     if( $biblios ) {
301         my $tag = $marcFlavour eq 'UNIMARC' ? '010' : '020';
302         my $field = $record->field($tag);
303         $isbn = $field && $field->subfield('a');
304         if ( $isbn && $cleanisbn ) {
305             $isbn =~ s/-//g;
306             $field->update('a' => $isbn);
307         }
308     }
309     my $id;
310     # search for duplicates (based on Local-number)
311     my $originalid;
312     $originalid = GetRecordId( $record, $tagid, $subfieldid );
313     if ($match) {
314         require C4::Search;
315         my $query = build_query( $match, $record );
316         my $server = ( $authorities ? 'authorityserver' : 'biblioserver' );
317         $debug && warn $query;
318         my ( $error, $results, $totalhits ) = $searcher->simple_search_compat( $query, 0, 3, [$server] );
319         # changed to warn so able to continue with one broken record
320         if ( defined $error ) {
321             warn "unable to search the database for duplicates : $error";
322             printlog( { id => $id || $originalid || $match, op => "match", status => "ERROR" } ) if ($logfile);
323             next RECORD;
324         }
325         $debug && warn "$query $server : $totalhits";
326         if ( $results && scalar(@$results) == 1 ) {
327             my $marcrecord = C4::Search::new_record_from_zebra( $server, $results->[0] );
328             SetUTF8Flag($marcrecord);
329             $id = GetRecordId( $marcrecord, $tagid, $subfieldid );
330             if ( $authorities && $marcFlavour ) {
331                 #Skip if authority in database is the same as the on in database
332                 if ( $marcrecord->field('005') && $record->field('005') &&
333                      $marcrecord->field('005')->data && $record->field('005')->data &&
334                      $marcrecord->field('005')->data >= $record->field('005')->data ) {
335                     if ($yamlfile) {
336                         $yamlhash->{$originalid}->{'authid'} = $id;
337
338                         # we recover all subfields of the heading authorities
339                         my @subfields;
340                         foreach my $field ( $marcrecord->field("2..") ) {
341                             push @subfields, map { ( $_->[0] =~ /[a-z]/ ? $_->[1] : () ) } $field->subfields();
342                         }
343                         $yamlhash->{$originalid}->{'subfields'} = \@subfields;
344                     }
345                     next;
346                 }
347             }
348         } elsif ( $results && scalar(@$results) > 1 ) {
349             $debug && warn "more than one match for $query";
350         } else {
351             $debug && warn "nomatch for $query";
352         }
353     }
354     if ($keepids && $originalid) {
355             my $storeidfield;
356             if ( length($keepids) == 3 ) {
357                 $storeidfield = MARC::Field->new( $keepids, $originalid );
358             } else {
359                 $storeidfield = MARC::Field->new( substr( $keepids, 0, 3 ), "", "", substr( $keepids, 3, 1 ), $originalid );
360             }
361             $record->insert_fields_ordered($storeidfield);
362             $record->delete_field( $record->field($tagid) );
363     }
364     foreach my $stringfilter (@$filters) {
365         if ( length($stringfilter) == 3 ) {
366             foreach my $field ( $record->field($stringfilter) ) {
367                 $record->delete_field($field);
368                 $debug && warn "removed : ", $field->as_string;
369             }
370         } elsif ($stringfilter =~ /([0-9]{3})([a-z0-9])(.*)/) {
371             my $removetag = $1;
372             my $removesubfield = $2;
373             my $removematch = $3;
374             if ( ( $removetag > "010" ) && $removesubfield ) {
375                 foreach my $field ( $record->field($removetag) ) {
376                     $field->delete_subfield( code => "$removesubfield", match => $removematch );
377                     $debug && warn "Potentially removed : ", $field->subfield($removesubfield);
378                 }
379             }
380         }
381     }
382     unless ($test_parameter) {
383         if ($authorities){
384             use C4::AuthoritiesMarc;
385             my $authtypecode=GuessAuthTypeCode($record, $heading_fields);
386             my $authid= ($id?$id:GuessAuthId($record));
387             if ($authid && GetAuthority($authid) && $update ){
388             ## Authority has an id and is in database : Replace
389                 eval { ( $authid ) = ModAuthority($authid,$record, $authtypecode) };
390                 if ($@){
391                     warn "Problem with authority $authid Cannot Modify";
392                                         printlog({id=>$originalid||$id||$authid, op=>"edit",status=>"ERROR"}) if ($logfile);
393                 }
394                                 else{
395                                         printlog({id=>$originalid||$id||$authid, op=>"edit",status=>"ok"}) if ($logfile);
396                                 }
397             }  
398             elsif (defined $authid) {
399             ## An authid is defined but no authority in database : add
400                 eval { ( $authid ) = AddAuthority($record,$authid, $authtypecode) };
401                 if ($@){
402                     warn "Problem with authority $authid Cannot Add ".$@;
403                                         printlog({id=>$originalid||$id||$authid, op=>"insert",status=>"ERROR"}) if ($logfile);
404                 }
405                                 else{
406                                         printlog({id=>$originalid||$id||$authid, op=>"insert",status=>"ok"}) if ($logfile);
407                                 }
408             }
409                 else {
410             ## True insert in database
411                 eval { ( $authid ) = AddAuthority($record,"", $authtypecode) };
412                 if ($@){
413                     warn "Problem with authority $authid Cannot Add".$@;
414                                         printlog({id=>$originalid||$id||$authid, op=>"insert",status=>"ERROR"}) if ($logfile);
415                 }
416                                 else{
417                                         printlog({id=>$originalid||$id||$authid, op=>"insert",status=>"ok"}) if ($logfile);
418                                 }
419                 }
420             if ($yamlfile) {
421             $yamlhash->{$originalid}->{'authid'} = $authid;
422             my @subfields;
423             foreach my $field ( $record->field("2..") ) {
424                 push @subfields, map { ( $_->[0] =~ /[a-z]/ ? $_->[1] : () ) } $field->subfields();
425             }
426             $yamlhash->{$originalid}->{'subfields'} = \@subfields;
427             }
428         }
429         else {
430             my ( $biblionumber, $biblioitemnumber, $itemnumbers_ref, $errors_ref );
431             $biblionumber = $id;
432             # check for duplicate, based on ISBN (skip it if we already have found a duplicate with match parameter
433             if (!$biblionumber && $isbn_check && $isbn) {
434     #         warn "search ISBN : $isbn";
435                 $sth_isbn->execute($isbn);
436                 ($biblionumber,$biblioitemnumber) = $sth_isbn->fetchrow;
437             }
438                 if (defined $idmapfl) {
439                                 if ($sourcetag < "010"){
440                                         if ($record->field($sourcetag)){
441                                           my $source = $record->field($sourcetag)->data();
442                                           printf(IDMAP "%s|%s\n",$source,$biblionumber);
443                                         }
444                             } else {
445                                         my $source=$record->subfield($sourcetag,$sourcesubfield);
446                                         printf(IDMAP "%s|%s\n",$source,$biblionumber);
447                           }
448                         }
449                                         # create biblio, unless we already have it ( either match or isbn )
450             if ($biblionumber) {
451                 eval{
452                     $biblioitemnumber = Koha::Biblios->find( $biblionumber )->biblioitem->biblioitemnumber;
453                 };
454                 if ($update) {
455                     eval { ModBiblio( $record, $biblionumber, GetFrameworkCode($biblionumber) ) };
456                     if ($@) {
457                         warn "ERROR: Edit biblio $biblionumber failed: $@\n";
458                         printlog( { id => $id || $originalid || $biblionumber, op => "update", status => "ERROR" } ) if ($logfile);
459                         next RECORD;
460                     } else {
461                         printlog( { id => $id || $originalid || $biblionumber, op => "update", status => "ok" } ) if ($logfile);
462                     }
463                 } else {
464                     printlog( { id => $id || $originalid || $biblionumber, op => "insert", status => "warning : already in database" } ) if ($logfile);
465                 }
466             } else {
467                 if ($insert) {
468                     eval { ( $biblionumber, $biblioitemnumber ) = AddBiblio( $record, '', { defer_marc_save => 1 } ) };
469                     if ($@) {
470                         warn "ERROR: Adding biblio $biblionumber failed: $@\n";
471                         printlog( { id => $id || $originalid || $biblionumber, op => "insert", status => "ERROR" } ) if ($logfile);
472                         next RECORD;
473                     } else {
474                         printlog( { id => $id || $originalid || $biblionumber, op => "insert", status => "ok" } ) if ($logfile);
475                     }
476                 } else {
477                     warn "WARNING: Updating record ".($id||$originalid)." failed";
478                     printlog( { id => $id || $originalid || $biblionumber, op => "update", status => "warning : not in database" } ) if ($logfile);
479                     next RECORD;
480                 }
481             }
482             eval { ( $itemnumbers_ref, $errors_ref ) = AddItemBatchFromMarc( $record, $biblionumber, $biblioitemnumber, '' ); };
483             my $error_adding = $@;
484             # Work on a clone so that if there are real errors, we can maybe
485             # fix them up later.
486                         my $clone_record = $record->clone();
487             C4::Biblio::_strip_item_fields($clone_record, '');
488             # This sets the marc fields if there was an error, and also calls
489             # defer_marc_save.
490             ModBiblioMarc( $clone_record, $biblionumber, $framework );
491             if ( $error_adding ) {
492                 warn "ERROR: Adding items to bib $biblionumber failed: $error_adding";
493                                 printlog({id=>$id||$originalid||$biblionumber, op=>"insertitem",status=>"ERROR"}) if ($logfile);
494                 # if we failed because of an exception, assume that 
495                 # the MARC columns in biblioitems were not set.
496                 next RECORD;
497             }
498                         else{
499                                 printlog({id=>$id||$originalid||$biblionumber, op=>"insertitem",status=>"ok"}) if ($logfile);
500                         }
501             if ($dedup_barcode && grep { exists $_->{error_code} && $_->{error_code} eq 'duplicate_barcode' } @$errors_ref) {
502                 # Find the record called 'barcode'
503                 my ($tag, $sub) = C4::Biblio::GetMarcFromKohaField( 'items.barcode' );
504                 # Now remove any items that didn't have a duplicate_barcode error,
505                 # erase the barcodes on items that did, and re-add those items.
506                 my %dupes;
507                 foreach my $i (0 .. $#{$errors_ref}) {
508                     my $ref = $errors_ref->[$i];
509                     if ($ref && ($ref->{error_code} eq 'duplicate_barcode')) {
510                         $dupes{$ref->{item_sequence}} = 1;
511                         # Delete the error message because we're going to
512                         # retry this one.
513                         delete $errors_ref->[$i];
514                     }
515                 }
516                 my $seq = 0;
517                 foreach my $field ($record->field($tag)) {
518                     $seq++;
519                     if ($dupes{$seq}) {
520                         # Here we remove the barcode
521                         $field->delete_subfield(code => $sub);
522                     } else {
523                         # otherwise we delete the field because we don't want
524                         # two of them
525                         $record->delete_fields($field);
526                     }
527                 }
528                 # Now re-add the record as before, adding errors to the prev list
529                 my $more_errors;
530                 eval { ( $itemnumbers_ref, $more_errors ) = AddItemBatchFromMarc( $record, $biblionumber, $biblioitemnumber, '' ); };
531                 if ( $@ ) {
532                     warn "ERROR: Adding items to bib $biblionumber failed: $@\n";
533                     printlog({id=>$id||$originalid||$biblionumber, op=>"insertitem",status=>"ERROR"}) if ($logfile);
534                     # if we failed because of an exception, assume that
535                     # the MARC columns in biblioitems were not set.
536                     ModBiblioMarc( $record, $biblionumber, $framework );
537                     next RECORD;
538                 } else {
539                     printlog({id=>$id||$originalid||$biblionumber, op=>"insertitem",status=>"ok"}) if ($logfile);
540                 }
541                 push @$errors_ref, @{ $more_errors };
542             }
543             if ($#{ $errors_ref } > -1) {
544                 report_item_errors($biblionumber, $errors_ref);
545             }
546             $yamlhash->{$originalid} = $biblionumber if ($yamlfile);
547         }
548         $dbh->commit() if (0 == $i % $commitnum);
549     }
550     print $record->as_formatted()."\n" if ($verbose//0)==2;
551     last if $i == $number;
552 }
553 $dbh->commit();
554 $dbh->{AutoCommit} = 1;
555
556
557 if ($fk_off) {
558         $dbh->do("SET FOREIGN_KEY_CHECKS = 1");
559 }
560
561 # Restore CataloguingLog and AuthoritiesLog
562 delete $ENV{SYSPREF_OVERRIDE_CataloguingLog};
563 delete $ENV{SYSPREF_OVERRIDE_AuthoritiesLog};
564
565 my $timeneeded = gettimeofday - $starttime;
566 print "\n$i MARC records done in $timeneeded seconds\n";
567 if ($logfile){
568   print $loghandle "file : $input_marc_file\n";
569   print $loghandle "$i MARC records done in $timeneeded seconds\n";
570   $loghandle->close;
571 }
572 if ($yamlfile) {
573     open my $yamlfileout, q{>}, "$yamlfile" or die "cannot open $yamlfile \n";
574     print $yamlfileout Dump($yamlhash);
575 }
576 exit 0;
577
578 sub GetRecordId{
579         my $marcrecord=shift;
580         my $tag=shift;
581         my $subfield=shift;
582         my $id;
583         if ($tag lt "010"){
584                 return $marcrecord->field($tag)->data() if $marcrecord->field($tag);
585         } 
586         elsif ($subfield){
587                 if ($marcrecord->field($tag)){
588                         return $marcrecord->subfield($tag,$subfield);
589                 }
590         }
591         return $id;
592 }
593 sub build_query {
594         my $match = shift;
595         my $record=shift;
596         my @searchstrings;
597         foreach my $matchingpoint (@$match){
598           my $string = build_simplequery($matchingpoint,$record);
599           push @searchstrings,$string if (length($string)>0);
600         }
601     my $QParser;
602     $QParser = C4::Context->queryparser if (C4::Context->preference('UseQueryParser'));
603     my $op;
604     if ($QParser) {
605         $op = '&&';
606     } else {
607         $op = 'and';
608     }
609     return join(" $op ",@searchstrings);
610 }
611 sub build_simplequery {
612         my $element=shift;
613         my $record=shift;
614     my @searchstrings;
615     my ($index,$recorddata)=split /,/,$element;
616     if ($recorddata=~/(\d{3})(.*)/) {
617         my ($tag,$subfields) =($1,$2);
618         foreach my $field ($record->field($tag)){
619                   if (length($field->as_string("$subfields"))>0){
620               push @searchstrings,"$index:\"".$field->as_string("$subfields")."\"";
621                   }
622         }
623     }
624     my $QParser;
625     $QParser = C4::Context->queryparser if (C4::Context->preference('UseQueryParser'));
626     my $op;
627     if ($QParser) {
628         $op = '&&';
629     } else {
630         $op = 'and';
631     }
632     return join(" $op ",@searchstrings);
633 }
634 sub report_item_errors {
635     my $biblionumber = shift;
636     my $errors_ref = shift;
637
638     foreach my $error (@{ $errors_ref }) {
639         next if !$error;
640         my $msg = "Item not added (bib $biblionumber, item tag #$error->{'item_sequence'}, barcode $error->{'item_barcode'}): ";
641         my $error_code = $error->{'error_code'};
642         $error_code =~ s/_/ /g;
643         $msg .= "$error_code $error->{'error_information'}";
644         print $msg, "\n";
645     }
646 }
647 sub printlog{
648         my $logelements=shift;
649     print $loghandle join( ";", map { defined $_ ? $_ : "" } @$logelements{qw<id op status>} ), "\n";
650 }
651 sub get_heading_fields{
652     my $headingfields;
653     if ($authtypes){
654         $headingfields=YAML::LoadFile($authtypes);
655         $headingfields={C4::Context->preference('marcflavour')=>$headingfields};
656         $debug && warn YAML::Dump($headingfields);
657     }
658     unless ($headingfields){
659         $headingfields=$dbh->selectall_hashref("SELECT auth_tag_to_report, authtypecode from auth_types",'auth_tag_to_report',{Slice=>{}});
660         $headingfields={C4::Context->preference('marcflavour')=>$headingfields};
661     }
662     return $headingfields;
663 }
664
665 =head1 NAME
666
667 bulkmarcimport.pl - Import bibliographic/authority records into Koha
668
669 =head1 USAGE
670
671  $ export KOHA_CONF=/etc/koha.conf
672  $ perl misc/migration_tools/bulkmarcimport.pl -d -commit 1000 \\
673     -file /home/jmf/koha.mrc -n 3000
674
675 =head1 WARNING
676
677 Don't use this script before you've entered and checked your MARC parameters
678 tables twice (or more!). Otherwise, the import won't work correctly and you
679 will get invalid data.
680
681 =head1 DESCRIPTION
682
683 =over
684
685 =item  B<-h>
686
687 This version/help screen
688
689 =item B<-b, -biblios>
690
691 Type of import: bibliographic records
692
693 =item B<-a, -authorities>
694
695 Type of import: authority records
696
697 =item B<-file>=I<FILE>
698
699 The I<FILE> to import
700
701 =item  B<-v>
702
703 Verbose mode. 1 means "some infos", 2 means "MARC dumping"
704
705 =item B<-fk>
706
707 Turn off foreign key checks during import.
708
709 =item B<-n>=I<NUMBER>
710
711 The I<NUMBER> of records to import. If missing, all the file is imported
712
713 =item B<-o, -offset>=I<NUMBER>
714
715 File offset before importing, ie I<NUMBER> of records to skip.
716
717 =item B<-commit>=I<NUMBER>
718
719 The I<NUMBER> of records to wait before performing a 'commit' operation
720
721 =item B<-l>
722
723 File logs actions done for each record and their status into file
724
725 =item B<-append>
726
727 If specified, data will be appended to the logfile. If not, the logfile will be erased for each execution.
728
729 =item B<-t, -test>
730
731 Test mode: parses the file, saying what it would do, but doing nothing.
732
733 =item B<-s>
734
735 Skip automatic conversion of MARC-8 to UTF-8.  This option is provided for
736 debugging.
737
738 =item B<-c>=I<CHARACTERISTIC>
739
740 The I<CHARACTERISTIC> MARC flavour. At the moment, only I<MARC21> and
741 I<UNIMARC> are supported. MARC21 by default.
742
743 =item B<-d>
744
745 Delete EVERYTHING related to biblio in koha-DB before import. Tables: biblio,
746 biblioitems, items
747
748 =item B<-m>=I<FORMAT>
749
750 Input file I<FORMAT>: I<MARCXML> or I<ISO2709> (defaults to ISO2709)
751
752 =item B<-authtypes>
753
754 file yamlfile with authoritiesTypes and distinguishable record field in order
755 to store the correct authtype
756
757 =item B<-yaml>
758
759 yaml file  format a yaml file with ids
760
761 =item B<-filter>
762
763 list of fields that will not be imported. Can be any from 000 to 999 or field,
764 subfield and subfield's matching value such as 200avalue
765
766 =item B<-insert>
767
768 if set, only insert when possible
769
770 =item B<-update>
771
772 if set, only updates (any biblio should have a matching record)
773
774 =item B<-all>
775
776 if set, do whatever is required
777
778 =item B<-k, -keepids>=<FIELD>
779
780 Field store ids in I<FIELD> (useful for authorities, where 001 contains the
781 authid for Koha, that can contain a very valuable info for authorities coming
782 from LOC or BNF. useless for biblios probably)
783
784 =item B<-match>=<FIELD>
785
786 I<FIELD> matchindex,fieldtomatch matchpoint to use to deduplicate fieldtomatch
787 can be either 001 to 999 or field and list of subfields as such 100abcde
788
789 =item B<-i,-isbn>
790
791 If set, a search will be done on isbn, and, if the same isbn is found, the
792 biblio is not added. It's another method to deduplicate.  B<-match> & B<-isbn>
793 can be both set.
794
795 =item B<-cleanisbn>
796
797 Clean ISBN fields from entering biblio records, ie removes hyphens. By default,
798 ISBN are cleaned. --nocleanisbn will keep ISBN unchanged.
799
800 =item B<-x>=I<TAG>
801
802 Source bib I<TAG> for reporting the source bib number
803
804 =item B<-y>=I<SUBFIELD>
805
806 Source I<SUBFIELD> for reporting the source bib number
807
808 =item B<-idmap>=I<FILE>
809
810 I<FILE> for the koha bib and source id
811
812 =item B<-keepids>
813
814 Store ids in 009 (useful for authorities, where 001 contains the authid for
815 Koha, that can contain a very valuable info for authorities coming from LOC or
816 BNF. useless for biblios probably)
817
818 =item B<-dedupbarcode>
819
820 If set, whenever a duplicate barcode is detected, it is removed and the attempt
821 to add the record is retried, thereby giving the record a blank barcode. This
822 is useful when something has set barcodes to be a biblio ID, or similar
823 (usually other software.)
824
825 =item B<-framework>
826
827 This is the code for the framework that the requested records will have attached
828 to them when they are created. If not specified, then the default framework
829 will be used.
830
831 =item B<-custom>=I<MODULE>
832
833 This parameter allows you to use a local module with a customize subroutine
834 that is called for each MARC record.
835 If no filename is passed, LocalChanges.pm is assumed to be in the
836 migration_tools subdirectory. You may pass an absolute file name or a file name
837 from the migration_tools directory.
838
839 =item B<-marcmodtemplate>=I<TEMPLATE>
840
841 This parameter allows you to specify the name of an existing MARC
842 modification template to apply as the MARC records are imported (these
843 templates are created in the "MARC modification templates" tool in Koha).
844 If not specified, no MARC modification templates are used (default).
845
846 =back
847
848 =cut
849