Bug 28018: Replace obsolete title-string sorting: OPAC templates
[koha.git] / C4 / AuthoritiesMarc.pm
1 package C4::AuthoritiesMarc;
2
3 # Copyright 2000-2002 Katipo Communications
4 # Copyright 2018 The National Library of Finland, University of Helsinki
5 #
6 # This file is part of Koha.
7 #
8 # Koha is free software; you can redistribute it and/or modify it
9 # under the terms of the GNU General Public License as published by
10 # the Free Software Foundation; either version 3 of the License, or
11 # (at your option) any later version.
12 #
13 # Koha is distributed in the hope that it will be useful, but
14 # WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 # GNU General Public License for more details.
17 #
18 # You should have received a copy of the GNU General Public License
19 # along with Koha; if not, see <http://www.gnu.org/licenses>.
20
21 use strict;
22 use warnings;
23 use C4::Context;
24 use MARC::Record;
25 use C4::Biblio;
26 use C4::Search;
27 use C4::AuthoritiesMarc::MARC21;
28 use C4::AuthoritiesMarc::UNIMARC;
29 use C4::Charset;
30 use C4::Log;
31 use Koha::MetadataRecord::Authority;
32 use Koha::Authorities;
33 use Koha::Authority::MergeRequests;
34 use Koha::Authority::Types;
35 use Koha::Authority;
36 use Koha::Libraries;
37 use Koha::SearchEngine;
38 use Koha::SearchEngine::Indexer;
39 use Koha::SearchEngine::Search;
40
41 use vars qw(@ISA @EXPORT);
42
43 BEGIN {
44
45         require Exporter;
46         @ISA = qw(Exporter);
47         @EXPORT = qw(
48             &GetTagsLabels
49         &GetAuthMARCFromKohaField 
50
51         &AddAuthority
52         &ModAuthority
53         &DelAuthority
54         &GetAuthority
55         &GetAuthorityXML
56
57         &SearchAuthorities
58     
59         &BuildSummary
60         &BuildAuthHierarchies
61         &BuildAuthHierarchy
62         &GenerateHierarchy
63     
64         &merge
65         &FindDuplicateAuthority
66
67         &GuessAuthTypeCode
68         &GuessAuthId
69         );
70 }
71
72
73 =head1 NAME
74
75 C4::AuthoritiesMarc
76
77 =head2 GetAuthMARCFromKohaField 
78
79   ( $tag, $subfield ) = &GetAuthMARCFromKohaField ($kohafield,$authtypecode);
80
81 returns tag and subfield linked to kohafield
82
83 Comment :
84 Suppose Kohafield is only linked to ONE subfield
85
86 =cut
87
88 sub GetAuthMARCFromKohaField {
89 #AUTHfind_marc_from_kohafield
90   my ( $kohafield,$authtypecode ) = @_;
91   my $dbh=C4::Context->dbh;
92   return 0, 0 unless $kohafield;
93   $authtypecode="" unless $authtypecode;
94   my $sth = $dbh->prepare("select tagfield,tagsubfield from auth_subfield_structure where kohafield= ? and authtypecode=? ");
95   $sth->execute($kohafield,$authtypecode);
96   my ($tagfield,$tagsubfield) = $sth->fetchrow;
97     
98   return  ($tagfield,$tagsubfield);
99 }
100
101 =head2 SearchAuthorities 
102
103   (\@finalresult, $nbresults)= &SearchAuthorities($tags, $and_or, 
104      $excluding, $operator, $value, $offset,$length,$authtypecode,
105      $sortby[, $skipmetadata])
106
107 returns ref to array result and count of results returned
108
109 =cut
110
111 sub SearchAuthorities {
112     my ($tags, $and_or, $excluding, $operator, $value, $offset,$length,$authtypecode,$sortby,$skipmetadata) = @_;
113     # warn Dumper($tags, $and_or, $excluding, $operator, $value, $offset,$length,$authtypecode,$sortby);
114     my $dbh=C4::Context->dbh;
115     $sortby="" unless $sortby;
116     my $query;
117     my $qpquery = '';
118     my $attr = '';
119         # the marclist may contain "mainentry". In this case, search the tag_to_report, that depends on
120         # the authtypecode. Then, search on $a of this tag_to_report
121         # also store main entry MARC tag, to extract it at end of search
122     ##first set the authtype search and may be multiple authorities
123     if ($authtypecode) {
124         my $n=0;
125         my @authtypecode;
126         my @auths=split / /,$authtypecode ;
127         foreach my  $auth (@auths){
128             $query .=" \@attr 1=authtype \@attr 5=100 ".$auth; ##No truncation on authtype
129                 push @authtypecode ,$auth;
130             $n++;
131         }
132         if ($n>1){
133             while ($n>1){$query= "\@or ".$query;$n--;}
134         }
135     }
136
137     my $dosearch;
138     my $and=" \@and " ;
139     my $q2;
140     my $attr_cnt = 0;
141     for ( my $i = 0 ; $i <= $#{$value} ; $i++ ) {
142         if ( @$value[$i] ) {
143             if ( @$tags[$i] ) {
144                 if ( @$tags[$i] eq "mainmainentry" ) {
145                     $attr = " \@attr 1=Heading-Main ";
146                 }
147                 elsif ( @$tags[$i] eq "mainentry" ) {
148                     $attr = " \@attr 1=Heading ";
149                 }
150                 elsif ( @$tags[$i] eq "match" ) {
151                     $attr = " \@attr 1=Match ";
152                 }
153                 elsif ( @$tags[$i] eq "match-heading" ) {
154                     $attr = " \@attr 1=Match-heading ";
155                 }
156                 elsif ( @$tags[$i] eq "see-from" ) {
157                     $attr = " \@attr 1=Match-heading-see-from ";
158                 }
159                 elsif ( @$tags[$i] eq "thesaurus" ) {
160                     $attr = " \@attr 1=Subject-heading-thesaurus ";
161                 }
162                 elsif ( @$tags[$i] eq "all" ) {
163                     $attr = " \@attr 1=Any ";
164                 }
165                 else {    # Use the index passed in params
166                     $attr = " \@attr 1=" . @$tags[$i] . " ";
167                 }
168             }         #if @$tags[$i]
169             else {    # Assume any if no index was specified
170                 $attr = " \@attr 1=Any ";
171             }
172
173             my $operator = @$operator[$i];
174             if ( $operator and $operator eq 'is' ) {
175                 $attr .= " \@attr 4=1  \@attr 5=100 "
176                   ;    ##Phrase, No truncation,all of subfield field must match
177             }
178             elsif ( $operator and $operator eq "=" ) {
179                 $attr .= " \@attr 4=107 ";    #Number Exact match
180             }
181             elsif ( $operator and $operator eq "start" ) {
182                 $attr .= " \@attr 3=2 \@attr 4=1 \@attr 5=1 "
183                   ;    #Firstinfield Phrase, Right truncated
184             }
185             elsif ( $operator and $operator eq "exact" ) {
186                 $attr .= " \@attr 4=1  \@attr 5=100 \@attr 6=3 "
187                   ;    ##Phrase, No truncation,all of subfield field must match
188             }
189             else {
190                 $attr .= " \@attr 5=1 \@attr 4=6 "
191                   ;    ## Word list, right truncated, anywhere
192                 if ( $sortby eq 'Relevance' ) {
193                     $attr .= "\@attr 2=102 ";
194                 }
195             }
196             @$value[$i] =~
197               s/"/\\"/g;    # Escape the double-quotes in the search value
198             $attr = $attr . "\"" . @$value[$i] . "\"";
199             $q2 .= $attr;
200             $dosearch = 1;
201             ++$attr_cnt;
202         }    #if value
203     }
204     ##Add how many queries generated
205     if (defined $query && $query=~/\S+/){
206       $query= $and x $attr_cnt . $query . (defined $q2 ? $q2 : '');
207     } else {
208       $query= $q2;
209     }
210     ## Adding order
211     #$query=' @or  @attr 7=2 @attr 1=Heading 0 @or  @attr 7=1 @attr 1=Heading 1'.$query if ($sortby eq "HeadingDsc");
212     my $orderstring;
213     if ($sortby eq 'HeadingAsc') {
214         $orderstring = '@attr 7=1 @attr 1=Heading 0';
215     } elsif ($sortby eq 'HeadingDsc') {
216         $orderstring = '@attr 7=2 @attr 1=Heading 0';
217     } elsif ($sortby eq 'AuthidAsc') {
218         $orderstring = '@attr 7=1 @attr 4=109 @attr 1=Local-Number 0';
219     } elsif ($sortby eq 'AuthidDsc') {
220         $orderstring = '@attr 7=2 @attr 4=109 @attr 1=Local-Number 0';
221     }
222     $query=($query?$query:"\@attr 1=_ALLRECORDS \@attr 2=103 ''");
223     $query="\@or $orderstring $query" if $orderstring;
224
225     $offset = 0 if not defined $offset or $offset < 0;
226     my $counter = $offset;
227     $length=10 unless $length;
228     my @oAuth;
229     my $i;
230     $oAuth[0]=C4::Context->Zconn("authorityserver" , 1);
231     my $Anewq= ZOOM::Query::PQF->new($query,$oAuth[0]);
232     my $oAResult;
233     $oAResult= $oAuth[0]->search($Anewq) ;
234     while (($i = ZOOM::event(\@oAuth)) != 0) {
235         my $ev = $oAuth[$i-1]->last_event();
236         last if $ev == ZOOM::Event::ZEND;
237     }
238     my($error, $errmsg, $addinfo, $diagset) = $oAuth[0]->error_x();
239     if ($error) {
240         warn  "oAuth error: $errmsg ($error) $addinfo $diagset\n";
241         goto NOLUCK;
242     }
243
244     my $nbresults;
245     $nbresults=$oAResult->size();
246     my $nremains=$nbresults;
247     my @result = ();
248     my @finalresult = ();
249
250     if ($nbresults>0){
251
252     ##Find authid and linkid fields
253     ##we may be searching multiple authoritytypes.
254     ## FIXME this assumes that all authid and linkid fields are the same for all authority types
255     # my ($authidfield,$authidsubfield)=GetAuthMARCFromKohaField($dbh,"auth_header.authid",$authtypecode[0]);
256     # my ($linkidfield,$linkidsubfield)=GetAuthMARCFromKohaField($dbh,"auth_header.linkid",$authtypecode[0]);
257         while (($counter < $nbresults) && ($counter < ($offset + $length))) {
258         
259         ##Here we have to extract MARC record and $authid from ZEBRA AUTHORITIES
260         my $rec=$oAResult->record($counter);
261         my $separator=C4::Context->preference('AuthoritySeparator');
262         my $authrecord = C4::Search::new_record_from_zebra(
263             'authorityserver',
264             $rec->raw()
265         );
266
267         if ( !defined $authrecord or !defined $authrecord->field('001') ) {
268             $counter++;
269             next;
270         }
271
272         SetUTF8Flag( $authrecord );
273
274         my $authid=$authrecord->field('001')->data();
275         my %newline;
276         $newline{authid} = $authid;
277         if ( !$skipmetadata ) {
278             my $auth_tag_to_report;
279             $auth_tag_to_report = Koha::Authority::Types->find($authtypecode)->auth_tag_to_report
280                 if $authtypecode;
281             my $reported_tag;
282             my $mainentry = $authrecord->field($auth_tag_to_report);
283             if ($mainentry) {
284                 foreach ( $mainentry->subfields() ) {
285                     $reported_tag .= '$' . $_->[0] . $_->[1];
286                 }
287             }
288
289             my ( $thisauthtype, $thisauthtypecode );
290             if ( my $authority = Koha::Authorities->find($authid) ) {
291                 $thisauthtypecode = $authority->authtypecode;
292                 $thisauthtype = Koha::Authority::Types->find($thisauthtypecode);
293             }
294             unless (defined $thisauthtype) {
295                 $thisauthtypecode = $authtypecode;
296                 $thisauthtype = Koha::Authority::Types->find($thisauthtypecode);
297             }
298             my $summary = BuildSummary( $authrecord, $authid, $thisauthtypecode );
299
300             $newline{authtype}     = defined($thisauthtype) ?
301                                         $thisauthtype->authtypetext : '';
302             $newline{summary}      = $summary;
303             $newline{even}         = $counter % 2;
304             $newline{reported_tag} = $reported_tag;
305         }
306         $counter++;
307         push @finalresult, \%newline;
308         }## while counter
309         ###
310         if (! $skipmetadata) {
311             for (my $z=0; $z<@finalresult; $z++){
312                 my $count = Koha::Authorities->get_usage_count({ authid => $finalresult[$z]{authid} });
313                 $finalresult[$z]{used}=$count;
314             }# all $z's
315         }
316
317     }## if nbresult
318     NOLUCK:
319     $oAResult->destroy();
320     # $oAuth[0]->destroy();
321
322     return (\@finalresult, $nbresults);
323 }
324
325 =head2 GuessAuthTypeCode
326
327   my $authtypecode = GuessAuthTypeCode($record);
328
329 Get the record and tries to guess the adequate authtypecode from its content.
330
331 =cut
332
333 sub GuessAuthTypeCode {
334     my ($record, $heading_fields) = @_;
335     return unless defined $record;
336     $heading_fields //= {
337     "MARC21"=>{
338         '100'=>{authtypecode=>'PERSO_NAME'},
339         '110'=>{authtypecode=>'CORPO_NAME'},
340         '111'=>{authtypecode=>'MEETI_NAME'},
341         '130'=>{authtypecode=>'UNIF_TITLE'},
342         '147'=>{authtypecode=>'NAME_EVENT'},
343         '148'=>{authtypecode=>'CHRON_TERM'},
344         '150'=>{authtypecode=>'TOPIC_TERM'},
345         '151'=>{authtypecode=>'GEOGR_NAME'},
346         '155'=>{authtypecode=>'GENRE/FORM'},
347         '162'=>{authtypecode=>'MED_PERFRM'},
348         '180'=>{authtypecode=>'GEN_SUBDIV'},
349         '181'=>{authtypecode=>'GEO_SUBDIV'},
350         '182'=>{authtypecode=>'CHRON_SUBD'},
351         '185'=>{authtypecode=>'FORM_SUBD'},
352     },
353 #200 Personal name      700, 701, 702 4-- with embedded 700, 701, 702 600
354 #                    604 with embedded 700, 701, 702
355 #210 Corporate or meeting name  710, 711, 712 4-- with embedded 710, 711, 712 601 604 with embedded 710, 711, 712
356 #215 Territorial or geographic name     710, 711, 712 4-- with embedded 710, 711, 712 601, 607 604 with embedded 710, 711, 712
357 #216 Trademark  716 [Reserved for future use]
358 #220 Family name        720, 721, 722 4-- with embedded 720, 721, 722 602 604 with embedded 720, 721, 722
359 #230 Title      500 4-- with embedded 500 605
360 #240 Name and title (embedded 200, 210, 215, or 220 and 230)    4-- with embedded 7-- and 500 7--  604 with embedded 7-- and 500 500
361 #245 Name and collective title (embedded 200, 210, 215, or 220 and 235)         4-- with embedded 7-- and 501 604 with embedded 7-- and 501 7-- 501
362 #250 Topical subject    606
363 #260 Place access       620
364 #280 Form, genre or physical characteristics    608
365 #
366 #
367 # Could also be represented with :
368 #leader position 9
369 #a = personal name entry
370 #b = corporate name entry
371 #c = territorial or geographical name
372 #d = trademark
373 #e = family name
374 #f = uniform title
375 #g = collective uniform title
376 #h = name/title
377 #i = name/collective uniform title
378 #j = topical subject
379 #k = place access
380 #l = form, genre or physical characteristics
381     "UNIMARC"=>{
382         '200'=>{authtypecode=>'NP'},
383         '210'=>{authtypecode=>'CO'},
384         '215'=>{authtypecode=>'SNG'},
385         '216'=>{authtypecode=>'TM'},
386         '220'=>{authtypecode=>'FAM'},
387         '230'=>{authtypecode=>'TU'},
388         '235'=>{authtypecode=>'CO_UNI_TI'},
389         '240'=>{authtypecode=>'SAUTTIT'},
390         '245'=>{authtypecode=>'NAME_COL'},
391         '250'=>{authtypecode=>'SNC'},
392         '260'=>{authtypecode=>'PA'},
393         '280'=>{authtypecode=>'GENRE/FORM'},
394     }
395 };
396     foreach my $field (keys %{$heading_fields->{uc(C4::Context->preference('marcflavour'))} }) {
397        return $heading_fields->{uc(C4::Context->preference('marcflavour'))}->{$field}->{'authtypecode'} if (defined $record->field($field));
398     }
399     return;
400 }
401
402 =head2 GuessAuthId
403
404   my $authtid = GuessAuthId($record);
405
406 Get the record and tries to guess the adequate authtypecode from its content.
407
408 =cut
409
410 sub GuessAuthId {
411     my ($record) = @_;
412     return unless ($record && $record->field('001'));
413 #    my $authtypecode=GuessAuthTypeCode($record);
414 #    my ($tag,$subfield)=GetAuthMARCFromKohaField("auth_header.authid",$authtypecode);
415 #    if ($tag > 010) {return $record->subfield($tag,$subfield)}
416 #    else {return $record->field($tag)->data}
417     return $record->field('001')->data;
418 }
419
420 =head2 GetTagsLabels
421
422   $tagslabel= &GetTagsLabels($forlibrarian,$authtypecode)
423
424 returns a ref to hashref of authorities tag and subfield structure.
425
426 tagslabel usage : 
427
428   $tagslabel->{$tag}->{$subfield}->{'attribute'}
429
430 where attribute takes values in :
431
432   lib
433   tab
434   mandatory
435   repeatable
436   authorised_value
437   authtypecode
438   value_builder
439   kohafield
440   seealso
441   hidden
442   isurl
443   link
444
445 =cut
446
447 sub GetTagsLabels {
448   my ($forlibrarian,$authtypecode)= @_;
449   my $dbh=C4::Context->dbh;
450   $authtypecode="" unless $authtypecode;
451   my $sth;
452   my $libfield = ($forlibrarian == 1)? 'liblibrarian' : 'libopac';
453
454
455   # check that authority exists
456   $sth=$dbh->prepare("SELECT count(*) FROM auth_tag_structure WHERE authtypecode=?");
457   $sth->execute($authtypecode);
458   my ($total) = $sth->fetchrow;
459   $authtypecode="" unless ($total >0);
460   $sth= $dbh->prepare(
461 "SELECT auth_tag_structure.tagfield,auth_tag_structure.liblibrarian,auth_tag_structure.libopac,auth_tag_structure.mandatory,auth_tag_structure.repeatable 
462  FROM auth_tag_structure 
463  WHERE authtypecode=? 
464  ORDER BY tagfield"
465     );
466
467   $sth->execute($authtypecode);
468   my ( $liblibrarian, $libopac, $tag, $res, $tab, $mandatory, $repeatable );
469
470   while ( ( $tag, $liblibrarian, $libopac, $mandatory, $repeatable ) = $sth->fetchrow ) {
471         $res->{$tag}->{lib}        = ($forlibrarian or !$libopac)?$liblibrarian:$libopac;
472         $res->{$tag}->{tab}        = " ";            # XXX
473         $res->{$tag}->{mandatory}  = $mandatory;
474         $res->{$tag}->{repeatable} = $repeatable;
475   }
476   $sth=      $dbh->prepare(
477 "SELECT tagfield,tagsubfield,liblibrarian,libopac,tab, mandatory, repeatable,authorised_value,frameworkcode as authtypecode,value_builder,kohafield,seealso,hidden,isurl,defaultvalue, display_order
478 FROM auth_subfield_structure 
479 WHERE authtypecode=? 
480 ORDER BY tagfield, display_order, tagsubfield"
481     );
482     $sth->execute($authtypecode);
483
484     my $subfield;
485     my $authorised_value;
486     my $value_builder;
487     my $kohafield;
488     my $seealso;
489     my $hidden;
490     my $isurl;
491     my $defaultvalue;
492     my $display_order;
493
494     while (
495         ( $tag,         $subfield,   $liblibrarian,   , $libopac,      $tab,
496         $mandatory,     $repeatable, $authorised_value, $authtypecode,
497         $value_builder, $kohafield,  $seealso,          $hidden,
498         $isurl,         $defaultvalue, $display_order )
499         = $sth->fetchrow
500       )
501     {
502         $res->{$tag}->{$subfield}->{subfield}         = $subfield;
503         $res->{$tag}->{$subfield}->{lib}              = ($forlibrarian or !$libopac)?$liblibrarian:$libopac;
504         $res->{$tag}->{$subfield}->{tab}              = $tab;
505         $res->{$tag}->{$subfield}->{mandatory}        = $mandatory;
506         $res->{$tag}->{$subfield}->{repeatable}       = $repeatable;
507         $res->{$tag}->{$subfield}->{authorised_value} = $authorised_value;
508         $res->{$tag}->{$subfield}->{authtypecode}     = $authtypecode;
509         $res->{$tag}->{$subfield}->{value_builder}    = $value_builder;
510         $res->{$tag}->{$subfield}->{kohafield}        = $kohafield;
511         $res->{$tag}->{$subfield}->{seealso}          = $seealso;
512         $res->{$tag}->{$subfield}->{hidden}           = $hidden;
513         $res->{$tag}->{$subfield}->{isurl}            = $isurl;
514         $res->{$tag}->{$subfield}->{defaultvalue}     = $defaultvalue;
515         $res->{$tag}->{$subfield}->{display_order}    = $display_order;
516     }
517
518     return $res;
519 }
520
521 =head2 AddAuthority
522
523   $authid= &AddAuthority($record, $authid,$authtypecode)
524
525 Either Create Or Modify existing authority.
526 returns authid of the newly created authority
527
528 =cut
529
530 sub AddAuthority {
531 # pass the MARC::Record to this function, and it will create the records in the authority table
532   my ($record,$authid,$authtypecode) = @_;
533   my $dbh=C4::Context->dbh;
534         my $leader='     nz  a22     o  4500';#Leader for incomplete MARC21 record
535
536 # if authid empty => true add, find a new authid number
537     my $format;
538     if (uc(C4::Context->preference('marcflavour')) eq 'UNIMARC') {
539         $format= 'UNIMARCAUTH';
540     }
541     else {
542         $format= 'MARC21';
543     }
544
545     #update date/time to 005 for marc and unimarc
546     my $time=POSIX::strftime("%Y%m%d%H%M%S",localtime);
547     my $f5=$record->field('005');
548     if (!$f5) {
549       $record->insert_fields_ordered( MARC::Field->new('005',$time.".0") );
550     }
551     else {
552       $f5->update($time.".0");
553     }
554
555     SetUTF8Flag($record);
556         if ($format eq "MARC21") {
557         my $userenv = C4::Context->userenv;
558         my $library;
559         my $marcorgcode = C4::Context->preference('MARCOrgCode');
560         if ( $userenv && $userenv->{'branch'} ) {
561             $library = Koha::Libraries->find( $userenv->{'branch'} );
562             # userenv's library could not exist because of a trick in misc/commit_file.pl (see FIXME and set_userenv statement)
563             $marcorgcode = $library ? $library->get_effective_marcorgcode : $marcorgcode;
564         }
565                 if (!$record->leader) {
566                         $record->leader($leader);
567                 }
568                 if (!$record->field('003')) {
569                         $record->insert_fields_ordered(
570                 MARC::Field->new('003', $marcorgcode),
571                         );
572                 }
573                 my $date=POSIX::strftime("%y%m%d",localtime);
574                 if (!$record->field('008')) {
575             # Get a valid default value for field 008
576             my $default_008 = C4::Context->preference('MARCAuthorityControlField008');
577             if(!$default_008 or length($default_008)<34) {
578                 $default_008 = '|| aca||aabn           | a|a     d';
579             }
580             else {
581                 $default_008 = substr($default_008,0,34);
582             }
583
584             $record->insert_fields_ordered( MARC::Field->new('008',$date.$default_008) );
585                 }
586                 if (!$record->field('040')) {
587                  $record->insert_fields_ordered(
588         MARC::Field->new('040','','',
589             'a' => $marcorgcode,
590             'c' => $marcorgcode,
591                                 ) 
592                         );
593     }
594         }
595
596   if ($format eq "UNIMARCAUTH") {
597         $record->leader("     nx  j22             ") unless ($record->leader());
598         my $date=POSIX::strftime("%Y%m%d",localtime);
599         my $defaultfield100 = C4::Context->preference('UNIMARCAuthorityField100');
600     if (my $string=$record->subfield('100',"a")){
601         $string=~s/fre50/frey50/;
602         $record->field('100')->update('a'=>$string);
603     }
604     elsif ($record->field('100')){
605           $record->field('100')->update('a'=>$date.$defaultfield100);
606     } else {      
607         $record->append_fields(
608         MARC::Field->new('100',' ',' '
609             ,'a'=>$date.$defaultfield100)
610         );
611     }      
612   }
613   my ($auth_type_tag, $auth_type_subfield) = get_auth_type_location($authtypecode);
614   if (!$authid and $format eq "MARC21") {
615     # only need to do this fix when modifying an existing authority
616     C4::AuthoritiesMarc::MARC21::fix_marc21_auth_type_location($record, $auth_type_tag, $auth_type_subfield);
617   } 
618   if (my $field=$record->field($auth_type_tag)){
619     $field->update($auth_type_subfield=>$authtypecode);
620   }
621   else {
622     $record->add_fields($auth_type_tag,'','', $auth_type_subfield=>$authtypecode); 
623   }
624
625     # Save record into auth_header, update 001
626     if (!$authid ) {
627         # Save a blank record, get authid
628         $dbh->do( "INSERT INTO auth_header (datecreated,marcxml) values (NOW(),?)", undef, '' );
629         $authid = $dbh->last_insert_id( undef, undef, 'auth_header', 'authid' );
630         logaction( "AUTHORITIES", "ADD", $authid, "authority" ) if C4::Context->preference("AuthoritiesLog");
631     }
632     # Insert/update the recordID in MARC record
633     $record->delete_field( $record->field('001') );
634     $record->insert_fields_ordered( MARC::Field->new( '001', $authid ) );
635     # Update
636     $dbh->do( "UPDATE auth_header SET authtypecode=?, marc=?, marcxml=? WHERE authid=?", undef, $authtypecode, $record->as_usmarc, $record->as_xml_record($format), $authid ) or die $DBI::errstr;
637     my $indexer = Koha::SearchEngine::Indexer->new({ index => $Koha::SearchEngine::AUTHORITIES_INDEX });
638     $indexer->index_records( $authid, "specialUpdate", "authorityserver", $record );
639
640     return ( $authid );
641 }
642
643 =head2 DelAuthority
644
645     DelAuthority({ authid => $authid, [ skip_merge => 1 ] });
646
647 Deletes $authid and calls merge to cleanup linked biblio records.
648 Parameter skip_merge is used in authorities/merge.pl. You should normally not
649 use it.
650
651 =cut
652
653 sub DelAuthority {
654     my ( $params ) = @_;
655     my $authid = $params->{authid} || return;
656     my $skip_merge = $params->{skip_merge};
657     my $dbh = C4::Context->dbh;
658
659     # Remove older pending merge requests for $authid to itself. (See bug 22437)
660     my $condition = { authid => $authid, authid_new => [undef, 0, $authid], done => 0 };
661     Koha::Authority::MergeRequests->search($condition)->delete;
662
663     merge({ mergefrom => $authid }) if !$skip_merge;
664     $dbh->do( "DELETE FROM auth_header WHERE authid=?", undef, $authid );
665     logaction( "AUTHORITIES", "DELETE", $authid, "authority" ) if C4::Context->preference("AuthoritiesLog");
666     my $indexer = Koha::SearchEngine::Indexer->new({ index => $Koha::SearchEngine::AUTHORITIES_INDEX });
667     $indexer->index_records( $authid, "recordDelete", "authorityserver", undef );
668 }
669
670 =head2 ModAuthority
671
672   $authid= &ModAuthority($authid,$record,$authtypecode, [ { skip_merge => 1 ] )
673
674 Modifies authority record, optionally updates attached biblios.
675 The parameter skip_merge is optional and should be used with care.
676
677 =cut
678
679 sub ModAuthority {
680     my ( $authid, $record, $authtypecode, $params ) = @_;
681     my $oldrecord = GetAuthority($authid);
682     #Now rewrite the $record to table with an add
683     $authid = AddAuthority($record, $authid, $authtypecode);
684     merge({ mergefrom => $authid, MARCfrom => $oldrecord, mergeto => $authid, MARCto => $record }) if !$params->{skip_merge};
685     logaction( "AUTHORITIES", "MODIFY", $authid, "authority BEFORE=>" . $oldrecord->as_formatted ) if C4::Context->preference("AuthoritiesLog");
686     return $authid;
687 }
688
689 =head2 GetAuthorityXML 
690
691   $marcxml= &GetAuthorityXML( $authid)
692
693 returns xml form of record $authid
694
695 =cut
696
697 sub GetAuthorityXML {
698   # Returns MARC::XML of the authority passed in parameter.
699   my ( $authid ) = @_;
700   if (uc(C4::Context->preference('marcflavour')) eq 'UNIMARC') {
701       my $dbh=C4::Context->dbh;
702       my $sth = $dbh->prepare("select marcxml from auth_header where authid=? "  );
703       $sth->execute($authid);
704       my ($marcxml)=$sth->fetchrow;
705       return $marcxml;
706   }
707   else { 
708       # for MARC21, call GetAuthority instead of
709       # getting the XML directly since we may
710       # need to fix up the location of the authority
711       # code -- note that this is reasonably safe
712       # because GetAuthorityXML is used only by the 
713       # indexing processes like zebraqueue_start.pl
714       my $record = GetAuthority($authid);
715       return $record->as_xml_record('MARC21');
716   }
717 }
718
719 =head2 GetAuthority 
720
721   $record= &GetAuthority( $authid)
722
723 Returns MARC::Record of the authority passed in parameter.
724
725 =cut
726
727 sub GetAuthority {
728     my ($authid)=@_;
729     my $authority = Koha::MetadataRecord::Authority->get_from_authid($authid);
730     return unless $authority;
731     return ($authority->record);
732 }
733
734 =head2 FindDuplicateAuthority
735
736   $record= &FindDuplicateAuthority( $record, $authtypecode)
737
738 return $authid,Summary if duplicate is found.
739
740 Comments : an improvement would be to return All the records that match.
741
742 =cut
743
744 sub FindDuplicateAuthority {
745
746     my ($record,$authtypecode)=@_;
747 #    warn "IN for ".$record->as_formatted;
748     my $dbh = C4::Context->dbh;
749 #    warn "".$record->as_formatted;
750     my $auth_tag_to_report = Koha::Authority::Types->find($authtypecode)->auth_tag_to_report;
751 #     warn "record :".$record->as_formatted."  auth_tag_to_report :$auth_tag_to_report";
752     # build a request for SearchAuthorities
753     my $op = 'AND';
754     $authtypecode =~ s#/#\\/#; # GENRE/FORM contains forward slash which is a reserved character
755     my $query='at:'.$authtypecode.' ';
756     my $filtervalues=qr([\001-\040\Q!'"`#$%&*+,-./:;<=>?@(){[}_|~\E\]]);
757     if ($record->field($auth_tag_to_report)) {
758         foreach ($record->field($auth_tag_to_report)->subfields()) {
759             $_->[1]=~s/$filtervalues/ /g; $query.= " $op he:\"".$_->[1]."\"" if ($_->[0]=~/[A-z]/);
760         }
761     }
762     my $searcher = Koha::SearchEngine::Search->new({index => $Koha::SearchEngine::AUTHORITIES_INDEX});
763     my ($error, $results, $total_hits) = $searcher->simple_search_compat( $query, 0, 1, [ 'authorityserver' ] );
764     # there is at least 1 result => return the 1st one
765     if (!defined $error && @{$results} ) {
766         my $marcrecord = C4::Search::new_record_from_zebra(
767             'authorityserver',
768             $results->[0]
769         );
770         return $marcrecord->field('001')->data,BuildSummary($marcrecord,$marcrecord->field('001')->data,$authtypecode);
771     }
772     # no result, returns nothing
773     return;
774 }
775
776 =head2 BuildSummary
777
778   $summary= &BuildSummary( $record, $authid, $authtypecode)
779
780 Returns a hashref with a summary of the specified record.
781
782 Comment : authtypecode can be inferred from both record and authid.
783 Moreover, authid can also be inferred from $record.
784 Would it be interesting to delete those things.
785
786 =cut
787
788 sub BuildSummary {
789     ## give this a Marc record to return summary
790     my ($record,$authid,$authtypecode)=@_;
791     my $dbh=C4::Context->dbh;
792     my %summary;
793     my $summary_template;
794     # handle $authtypecode is NULL or eq ""
795     if ($authtypecode) {
796         my $authref = Koha::Authority::Types->find($authtypecode);
797         if ( $authref ) {
798             $summary{authtypecode} = $authref->authtypecode;
799             $summary{type} = $authref->authtypetext;
800             $summary_template = $authref->summary;
801             # for MARC21, the authority type summary displays a label meant for
802             # display
803             if (C4::Context->preference('marcflavour') ne 'UNIMARC') {
804                 $summary{label} = $authref->summary;
805             } else {
806                 $summary{summary} = $authref->summary;
807             }
808         }
809     }
810     my $marc21subfields = 'abcdfghjklmnopqrstuvxyz68';
811     my %marc21controlrefs = ( 'a' => 'earlier',
812         'b' => 'later',
813         'd' => 'acronym',
814         'f' => 'musical',
815         'g' => 'broader',
816         'h' => 'narrower',
817         'n' => 'notapplicable',
818         'i' => 'subfi',
819         't' => 'parent'
820     );
821     my %unimarc_relation_from_code = (
822         g => 'broader',
823         h => 'narrower',
824         a => 'seealso',
825     );
826     my %thesaurus;
827     $thesaurus{'1'}="Peuples";
828     $thesaurus{'2'}="Anthroponymes";
829     $thesaurus{'3'}="Oeuvres";
830     $thesaurus{'4'}="Chronologie";
831     $thesaurus{'5'}="Lieux";
832     $thesaurus{'6'}="Sujets";
833     #thesaurus a remplir
834     my $reported_tag;
835 # if the library has a summary defined, use it. Otherwise, build a standard one
836 # FIXME - it appears that the summary field in the authority frameworks
837 #         can work as a display template.  However, this doesn't
838 #         suit the MARC21 version, so for now the "templating"
839 #         feature will be enabled only for UNIMARC for backwards
840 #         compatibility.
841     if ($summary{summary} and C4::Context->preference('marcflavour') eq 'UNIMARC') {
842         my @matches = ($summary{summary} =~ m/\[(.*?)(\d{3})([\*a-z0-9])(.*?)\]/g);
843         my (@textbefore, @tag, @subtag, @textafter);
844         for(my $i = 0; $i < scalar @matches; $i++){
845             push @textbefore, $matches[$i] if($i%4 == 0);
846             push @tag,        $matches[$i] if($i%4 == 1);
847             push @subtag,     $matches[$i] if($i%4 == 2);
848             push @textafter,  $matches[$i] if($i%4 == 3);
849         }
850         for(my $i = scalar @tag; $i >= 0; $i--){
851             my $textbefore = $textbefore[$i] || '';
852             my $tag = $tag[$i] || '';
853             my $subtag = $subtag[$i] || '';
854             my $textafter = $textafter[$i] || '';
855             my $value = '';
856             my $field = $record->field($tag);
857             if ( $field ) {
858                 if($subtag eq '*') {
859                     if($tag < 10) {
860                         $value = $textbefore . $field->data() . $textafter;
861                     }
862                 } else {
863                     my @subfields = $field->subfield($subtag);
864                     if(@subfields > 0) {
865                         $value = $textbefore . join (" - ", @subfields) . $textafter;
866                     }
867                 }
868             }
869             $summary{summary} =~ s/\[\Q$textbefore$tag$subtag$textafter\E\]/$value/;
870         }
871         $summary{summary} =~ s/\\n/<br \/>/g;
872     }
873     my @authorized;
874     my @notes;
875     my @seefrom;
876     my @seealso;
877     my @otherscript;
878     if (C4::Context->preference('marcflavour') eq 'UNIMARC') {
879 # construct UNIMARC summary, that is quite different from MARC21 one
880 # accepted form
881         foreach my $field ($record->field('2..')) {
882             push @authorized, {
883                 heading => $field->as_string('abcdefghijlmnopqrstuvwxyz'),
884                 hemain  => ( $field->subfield('a') // undef ),
885                 field   => $field->tag(),
886             };
887         }
888 # rejected form(s)
889         foreach my $field ($record->field('3..')) {
890             push @notes, { note => $field->subfield('a'), field => $field->tag() };
891         }
892         foreach my $field ($record->field('4..')) {
893             my $thesaurus = $field->subfield('2') ? "thes. : ".$thesaurus{"$field->subfield('2')"}." : " : '';
894             push @seefrom, {
895                 heading => $thesaurus . $field->as_string('abcdefghijlmnopqrstuvwxyz'),
896                 hemain  => ( $field->subfield('a') // undef ),
897                 type    => 'seefrom',
898                 field   => $field->tag(),
899             };
900         }
901
902         # see :
903         @seealso = map {
904             my $type = $unimarc_relation_from_code{$_->subfield('5') || 'a'};
905             my $heading = $_->as_string('abcdefgjxyz');
906             {
907                 field   => $_->tag,
908                 type    => $type,
909                 heading => $heading,
910                 hemain  => ( $_->subfield('a') // undef ),
911                 search  => $heading,
912                 authid  => ( $_->subfield('9') // undef ),
913             }
914         } $record->field('5..');
915
916         # Other forms
917         @otherscript = map { {
918             lang      => length ($_->subfield('8')) == 6 ? substr ($_->subfield('8'), 3, 3) : $_->subfield('8') || '',
919             term      => $_->subfield('a') . ($_->subfield('b') ? ', ' . $_->subfield('b') : ''),
920             direction => 'ltr',
921             field     => $_->tag,
922         } } $record->field('7..');
923
924     } else {
925 # construct MARC21 summary
926 # FIXME - looping over 1XX is questionable
927 # since MARC21 authority should have only one 1XX
928         use C4::Heading::MARC21;
929         my $handler = C4::Heading::MARC21->new();
930         my $subfields_to_report;
931         foreach my $field ($record->field('1..')) {
932             my $tag = $field->tag();
933             next if "152" eq $tag;
934 # FIXME - 152 is not a good tag to use
935 # in MARC21 -- purely local tags really ought to be
936 # 9XX
937
938             $subfields_to_report = $handler->get_auth_heading_subfields_to_report($tag);
939
940             if ($subfields_to_report) {
941                 push @authorized, {
942                     heading => $field->as_string($subfields_to_report),
943                     hemain  => ( $field->subfield( substr($subfields_to_report, 0, 1) ) // undef ),
944                     field   => $tag,
945                 };
946             } else {
947                 push @authorized, {
948                     heading => $field->as_string(),
949                     hemain  => ( $field->subfield( 'a' ) // undef ),
950                     field   => $tag,
951                 };
952             }
953         }
954         foreach my $field ($record->field('4..')) { #See From
955             my $type = 'seefrom';
956             $type = ($marc21controlrefs{substr $field->subfield('w'), 0, 1} || '') if ($field->subfield('w'));
957             if ($type eq 'notapplicable') {
958                 $type = substr $field->subfield('w'), 2, 1;
959                 $type = 'earlier' if $type && $type ne 'n';
960             }
961             if ($type eq 'subfi') {
962                 push @seefrom, {
963                     heading => $field->as_string($marc21subfields),
964                     hemain  => scalar $field->subfield( substr($marc21subfields, 0, 1) ),
965                     type    => ($field->subfield('i') || ''),
966                     field   => $field->tag(),
967                 };
968             } else {
969                 push @seefrom, {
970                     heading => $field->as_string($marc21subfields),
971                     hemain  => scalar $field->subfield( substr($marc21subfields, 0, 1) ),
972                     type    => $type,
973                     field   => $field->tag(),
974                 };
975             }
976         }
977         foreach my $field ($record->field('5..')) { #See Also
978             my $type = 'seealso';
979             $type = ($marc21controlrefs{substr $field->subfield('w'), 0, 1} || '') if ($field->subfield('w'));
980             if ($type eq 'notapplicable') {
981                 $type = substr $field->subfield('w'), 2, 1;
982                 $type = 'earlier' if $type && $type ne 'n';
983             }
984             if ($type eq 'subfi') {
985                 push @seealso, {
986                     heading => $field->as_string($marc21subfields),
987                     hemain  => scalar $field->subfield( substr($marc21subfields, 0, 1) ),
988                     type    => scalar $field->subfield('i'),
989                     field   => $field->tag(),
990                     search  => $field->as_string($marc21subfields) || '',
991                     authid  => $field->subfield('9') || ''
992                 };
993             } else {
994                 push @seealso, {
995                     heading => $field->as_string($marc21subfields),
996                     hemain  => scalar $field->subfield( substr($marc21subfields, 0, 1) ),
997                     type    => $type,
998                     field   => $field->tag(),
999                     search  => $field->as_string($marc21subfields) || '',
1000                     authid  => $field->subfield('9') || ''
1001                 };
1002             }
1003         }
1004         foreach my $field ($record->field('6..')) {
1005             push @notes, { note => $field->as_string(), field => $field->tag() };
1006         }
1007         foreach my $field ($record->field('880')) {
1008             my $linkage = $field->subfield('6');
1009             my $category = substr $linkage, 0, 1;
1010             if ($category eq '1') {
1011                 $category = 'preferred';
1012             } elsif ($category eq '4') {
1013                 $category = 'seefrom';
1014             } elsif ($category eq '5') {
1015                 $category = 'seealso';
1016             }
1017             my $type;
1018             if ($field->subfield('w')) {
1019                 $type = $marc21controlrefs{substr $field->subfield('w'), '0'};
1020             } else {
1021                 $type = $category;
1022             }
1023             my $direction = $linkage =~ m#/r$# ? 'rtl' : 'ltr';
1024             push @otherscript, { term => $field->as_string($subfields_to_report), category => $category, type => $type, direction => $direction, linkage => $linkage };
1025         }
1026     }
1027     $summary{mainentry} = $authorized[0]->{heading};
1028     $summary{mainmainentry} = $authorized[0]->{hemain};
1029     $summary{authorized} = \@authorized;
1030     $summary{notes} = \@notes;
1031     $summary{seefrom} = \@seefrom;
1032     $summary{seealso} = \@seealso;
1033     $summary{otherscript} = \@otherscript;
1034     return \%summary;
1035 }
1036
1037 =head2 GetAuthorizedHeading
1038
1039   $heading = &GetAuthorizedHeading({ record => $record, authid => $authid })
1040
1041 Takes a MARC::Record object describing an authority record or an authid, and
1042 returns a string representation of the first authorized heading. This routine
1043 should be considered a temporary shim to ease the future migration of authority
1044 data from C4::AuthoritiesMarc to the object-oriented Koha::*::Authority.
1045
1046 =cut
1047
1048 sub GetAuthorizedHeading {
1049     my $args = shift;
1050     my $record;
1051     unless ($record = $args->{record}) {
1052         return unless $args->{authid};
1053         $record = GetAuthority($args->{authid});
1054     }
1055     return unless (ref $record eq 'MARC::Record');
1056     if (C4::Context->preference('marcflavour') eq 'UNIMARC') {
1057 # construct UNIMARC summary, that is quite different from MARC21 one
1058 # accepted form
1059         foreach my $field ($record->field('2..')) {
1060             return $field->as_string('abcdefghijlmnopqrstuvwxyz');
1061         }
1062     } else {
1063         use C4::Heading::MARC21;
1064         my $handler = C4::Heading::MARC21->new();
1065
1066         foreach my $field ($record->field('1..')) {
1067             my $subfields = $handler->get_valid_bib_heading_subfields($field->tag());
1068             return $field->as_string($subfields) if ($subfields);
1069         }
1070     }
1071     return;
1072 }
1073
1074 =head2 CompareFieldWithAuthority
1075
1076   $match = &CompareFieldWithAuthority({ field => $field, authid => $authid })
1077
1078 Takes a MARC::Field from a bibliographic record and an authid, and returns true if they match.
1079
1080 =cut
1081
1082 sub CompareFieldWithAuthority {
1083     my $args = shift;
1084
1085     my $record = GetAuthority($args->{authid});
1086     return unless (ref $record eq 'MARC::Record');
1087     if (C4::Context->preference('marcflavour') eq 'UNIMARC') {
1088         # UNIMARC has same subfields for bibs and authorities
1089         foreach my $field ($record->field('2..')) {
1090             return compare_fields($field, $args->{field}, 'abcdefghijlmnopqrstuvwxyz');
1091         }
1092     } else {
1093         use C4::Heading::MARC21;
1094         my $handler = C4::Heading::MARC21->new();
1095
1096         foreach my $field ($record->field('1..')) {
1097             my $subfields = $handler->get_valid_bib_heading_subfields($field->tag());
1098             return compare_fields($field, $args->{field}, $subfields) if ($subfields);
1099         }
1100     }
1101     return 0;
1102 }
1103
1104 =head2 BuildAuthHierarchies
1105
1106   $text= &BuildAuthHierarchies( $authid, $force)
1107
1108 return text containing trees for hierarchies
1109 for them to be stored in auth_header
1110
1111 Example of text:
1112 122,1314,2452;1324,2342,3,2452
1113
1114 =cut
1115
1116 sub BuildAuthHierarchies{
1117     my $authid = shift @_;
1118 #   warn "authid : $authid";
1119     my $force = shift @_ || (C4::Context->preference('marcflavour') eq 'UNIMARC' ? 0 : 1);
1120     my @globalresult;
1121     my $dbh=C4::Context->dbh;
1122     my $hierarchies;
1123     my $data = GetHeaderAuthority($authid);
1124     if ($data->{'authtrees'} and not $force){
1125         return $data->{'authtrees'};
1126 #  } elsif ($data->{'authtrees'}){
1127 #    $hierarchies=$data->{'authtrees'};
1128     } else {
1129         my $record = GetAuthority($authid);
1130         my $found;
1131         return unless $record;
1132         foreach my $field ($record->field('5..')){
1133             my $broader = 0;
1134             $broader = 1 if (
1135                     (C4::Context->preference('marcflavour') eq 'UNIMARC' && $field->subfield('5') && $field->subfield('5') eq 'g') ||
1136                     (C4::Context->preference('marcflavour') ne 'UNIMARC' && $field->subfield('w') && substr($field->subfield('w'), 0, 1) eq 'g'));
1137             if ($broader) {
1138                 my $subfauthid=_get_authid_subfield($field) || '';
1139                 next if ($subfauthid eq $authid);
1140                 my $parentrecord = GetAuthority($subfauthid);
1141                 next unless $parentrecord;
1142                 my $localresult=$hierarchies;
1143                 my $trees;
1144                 $trees = BuildAuthHierarchies($subfauthid);
1145                 my @trees;
1146                 if ($trees=~/;/){
1147                     @trees = split(/;/,$trees);
1148                 } else {
1149                     push @trees, $trees;
1150                 }
1151                 foreach (@trees){
1152                     $_.= ",$authid";
1153                 }
1154                 @globalresult = (@globalresult,@trees);
1155                 $found=1;
1156             }
1157             $hierarchies=join(";",@globalresult);
1158         }
1159 #Unless there is no ancestor, I am alone.
1160         $hierarchies="$authid" unless ($hierarchies);
1161     }
1162     AddAuthorityTrees($authid,$hierarchies);
1163     return $hierarchies;
1164 }
1165
1166 =head2 BuildAuthHierarchy
1167
1168   $ref= &BuildAuthHierarchy( $record, $class,$authid)
1169
1170 return a hashref in order to display hierarchy for record and final Authid $authid
1171
1172 "loopparents"
1173 "loopchildren"
1174 "class"
1175 "loopauthid"
1176 "current_value"
1177 "value"
1178
1179 =cut
1180
1181 sub BuildAuthHierarchy{
1182     my $record = shift @_;
1183     my $class = shift @_;
1184     my $authid_constructed = shift @_;
1185     return unless ($record && $record->field('001'));
1186     my $authid=$record->field('001')->data();
1187     my %cell;
1188     my $parents=""; my $children="";
1189     my (@loopparents,@loopchildren);
1190     my $marcflavour = C4::Context->preference('marcflavour');
1191     my $relationshipsf = $marcflavour eq 'UNIMARC' ? '5' : 'w';
1192     foreach my $field ($record->field('5..')){
1193         my $subfauthid=_get_authid_subfield($field);
1194         if ($subfauthid && $field->subfield($relationshipsf) && $field->subfield('a')){
1195             my $relationship = substr($field->subfield($relationshipsf), 0, 1);
1196             if ($relationship eq 'h'){
1197                 push @loopchildren, { "authid"=>$subfauthid,"value"=>$field->subfield('a')};
1198             }
1199             elsif ($relationship eq 'g'){
1200                 push @loopparents, { "authid"=>$subfauthid,"value"=>$field->subfield('a')};
1201             }
1202 # brothers could get in there with an else
1203         }
1204     }
1205     $cell{"parents"}=\@loopparents;
1206     $cell{"children"}=\@loopchildren;
1207     $cell{"class"}=$class;
1208     $cell{"authid"}=$authid;
1209     $cell{"current_value"} =1 if ($authid eq $authid_constructed);
1210     $cell{"value"}=C4::Context->preference('marcflavour') eq 'UNIMARC' ? $record->subfield('2..',"a") : $record->subfield('1..', 'a');
1211     return \%cell;
1212 }
1213
1214 =head2 BuildAuthHierarchyBranch
1215
1216   $branch = &BuildAuthHierarchyBranch( $tree, $authid[, $cnt])
1217
1218 Return a data structure representing an authority hierarchy
1219 given a list of authorities representing a single branch in
1220 an authority hierarchy tree. $authid is the current node in
1221 the tree (which may or may not be somewhere in the middle).
1222 $cnt represents the level of the upper-most item, and is only
1223 used when BuildAuthHierarchyBranch is called recursively (i.e.,
1224 don't ever pass in anything but zero to it).
1225
1226 =cut
1227
1228 sub BuildAuthHierarchyBranch {
1229     my ($tree, $authid, $cnt) = @_;
1230     $cnt |= 0;
1231     my $elementdata = GetAuthority(shift @$tree);
1232     my $branch = BuildAuthHierarchy($elementdata,"child".$cnt, $authid);
1233     if (scalar @$tree > 0) {
1234         my $nextBranch = BuildAuthHierarchyBranch($tree, $authid, ++$cnt);
1235         my $nextAuthid = $nextBranch->{authid};
1236         my $found;
1237         # If we already have the next branch listed as a child, let's
1238         # replace the old listing with the new one. If not, we will add
1239         # the branch at the end.
1240         foreach my $cell (@{$branch->{children}}) {
1241             if ($cell->{authid} eq $nextAuthid) {
1242                 $cell = $nextBranch;
1243                 $found = 1;
1244                 last;
1245             }
1246         }
1247         push @{$branch->{children}}, $nextBranch unless $found;
1248     }
1249     return $branch;
1250 }
1251
1252 =head2 GenerateHierarchy
1253
1254   $hierarchy = &GenerateHierarchy($authid);
1255
1256 Return an arrayref holding one or more "trees" representing
1257 authority hierarchies.
1258
1259 =cut
1260
1261 sub GenerateHierarchy {
1262     my ($authid) = @_;
1263     my $trees    = BuildAuthHierarchies($authid);
1264     my @trees    = split /;/,$trees ;
1265     push @trees,$trees unless (@trees);
1266     my @loophierarchies;
1267     foreach my $tree (@trees){
1268         my @tree=split /,/,$tree;
1269         push @tree, $tree unless (@tree);
1270         my $branch = BuildAuthHierarchyBranch(\@tree, $authid);
1271         push @loophierarchies, [ $branch ];
1272     }
1273     return \@loophierarchies;
1274 }
1275
1276 sub _get_authid_subfield{
1277     my ($field)=@_;
1278     return $field->subfield('9')||$field->subfield('3');
1279 }
1280
1281 =head2 GetHeaderAuthority
1282
1283   $ref= &GetHeaderAuthority( $authid)
1284
1285 return a hashref in order auth_header table data
1286
1287 =cut
1288
1289 sub GetHeaderAuthority{
1290   my $authid = shift @_;
1291   my $sql= "SELECT * from auth_header WHERE authid = ?";
1292   my $dbh=C4::Context->dbh;
1293   my $rq= $dbh->prepare($sql);
1294   $rq->execute($authid);
1295   my $data= $rq->fetchrow_hashref;
1296   return $data;
1297 }
1298
1299 =head2 AddAuthorityTrees
1300
1301   $ref= &AddAuthorityTrees( $authid, $trees)
1302
1303 return success or failure
1304
1305 =cut
1306
1307 sub AddAuthorityTrees{
1308   my $authid = shift @_;
1309   my $trees = shift @_;
1310   my $sql= "UPDATE IGNORE auth_header set authtrees=? WHERE authid = ?";
1311   my $dbh=C4::Context->dbh;
1312   my $rq= $dbh->prepare($sql);
1313   return $rq->execute($trees,$authid);
1314 }
1315
1316 =head2 merge
1317
1318     $count = merge({
1319         mergefrom => $mergefrom,
1320         [ MARCfrom => $MARCfrom, ]
1321         [ mergeto => $mergeto, ]
1322         [ MARCto => $MARCto, ]
1323         [ biblionumbers => [ $a, $b, $c ], ]
1324         [ override_limit => 1, ]
1325     });
1326
1327 Merge biblios linked to authority $mergefrom (mandatory parameter).
1328 If $mergeto equals mergefrom, the linked biblio field is updated.
1329 If $mergeto is different, the biblio field will be linked to $mergeto.
1330 If $mergeto is missing, the biblio field is deleted.
1331
1332 MARCfrom is used to determine if a cleared subfield in the authority record
1333 should be removed from a biblio. MARCto is used to populate the biblio
1334 record with the updated values; if you do not pass it, the biblio field
1335 will be deleted (same as missing mergeto).
1336
1337 Normally all biblio records linked to $mergefrom, will be considered. But
1338 you can pass specific numbers via the biblionumbers parameter.
1339
1340 The parameter override_limit is used by the cron job to force larger
1341 postponed merges.
1342
1343 Note: Although $mergefrom and $mergeto will normally be of the same
1344 authority type, merge also supports moving to another authority type.
1345
1346 =cut
1347
1348 sub merge {
1349     my ( $params ) = @_;
1350     my $mergefrom = $params->{mergefrom} || return;
1351     my $MARCfrom = $params->{MARCfrom};
1352     my $mergeto = $params->{mergeto};
1353     my $MARCto = $params->{MARCto};
1354     my $override_limit = $params->{override_limit};
1355
1356     # If we do not have biblionumbers, we get all linked biblios if the
1357     # number of linked records does not exceed the limit UNLESS we override.
1358     my @biblionumbers;
1359     if( $params->{biblionumbers} ) {
1360         @biblionumbers = @{ $params->{biblionumbers} };
1361     } elsif( $override_limit ) {
1362         @biblionumbers = Koha::Authorities->linked_biblionumbers({ authid => $mergefrom });
1363     } else { # now first check number of linked records
1364         my $max = C4::Context->preference('AuthorityMergeLimit') // 0;
1365         my $hits = Koha::Authorities->get_usage_count({ authid => $mergefrom });
1366         if( $hits > 0 && $hits <= $max ) {
1367             @biblionumbers = Koha::Authorities->linked_biblionumbers({ authid => $mergefrom });
1368         } elsif( $hits > $max ) { #postpone this merge to the cron job
1369             Koha::Authority::MergeRequest->new({
1370                 authid => $mergefrom,
1371                 oldrecord => $MARCfrom,
1372                 authid_new => $mergeto,
1373             })->store;
1374         }
1375     }
1376     return 0 if !@biblionumbers;
1377
1378     # Search authtypes and reporting tags
1379     my $authfrom = Koha::Authorities->find($mergefrom);
1380     my $authto = Koha::Authorities->find($mergeto);
1381     my $authtypefrom = $authfrom ? Koha::Authority::Types->find($authfrom->authtypecode) : undef;
1382     my $authtypeto   = $authto ? Koha::Authority::Types->find($authto->authtypecode) : undef;
1383     my $auth_tag_to_report_from = $authtypefrom ? $authtypefrom->auth_tag_to_report : '';
1384     my $auth_tag_to_report_to   = $authtypeto ? $authtypeto->auth_tag_to_report : '';
1385
1386     my @record_to;
1387     @record_to = $MARCto->field($auth_tag_to_report_to)->subfields() if $auth_tag_to_report_to && $MARCto && $MARCto->field($auth_tag_to_report_to);
1388     # Exceptional: If MARCto and authtypeto exist but $auth_tag_to_report_to
1389     # is empty, make sure that $9 and $a remain (instead of clearing the
1390     # reference) in order to allow for data recovery.
1391     # Note: We need $a too, since a single $9 does not pass ModBiblio.
1392     if( $MARCto && $authtypeto && !@record_to  ) {
1393         push @record_to, [ 'a', ' ' ]; # do not remove the space
1394     }
1395
1396     my @record_from;
1397     if( !$authfrom && $MARCfrom && $MARCfrom->field('1..','2..') ) {
1398     # postponed merge, authfrom was deleted and MARCfrom only contains the old reporting tag (and possibly a 100 for UNIMARC)
1399     # 2XX is for UNIMARC; we use -1 in order to skip 100 in UNIMARC; this will not impact MARC21, since there is only one tag
1400         @record_from = ( $MARCfrom->field('1..','2..') )[-1]->subfields;
1401     } elsif( $auth_tag_to_report_from && $MARCfrom && $MARCfrom->field($auth_tag_to_report_from) ) {
1402         @record_from = $MARCfrom->field($auth_tag_to_report_from)->subfields;
1403     }
1404
1405     # Get all candidate tags for the change
1406     # (This will reduce the search scope in marc records).
1407     # For a deleted authority record, we scan all auth controlled fields
1408     my $dbh = C4::Context->dbh;
1409     my $sql = "SELECT DISTINCT tagfield FROM marc_subfield_structure WHERE authtypecode=?";
1410     my $tags_using_authtype = $authtypefrom && $authtypefrom->authtypecode ? $dbh->selectcol_arrayref( $sql, undef, ( $authtypefrom->authtypecode )) : $dbh->selectcol_arrayref( "SELECT DISTINCT tagfield FROM marc_subfield_structure WHERE authtypecode IS NOT NULL AND authtypecode<>''" );
1411     my $tags_new;
1412     if( $authtypeto && ( !$authtypefrom || $authtypeto->authtypecode ne $authtypefrom->authtypecode )) {
1413         $tags_new = $dbh->selectcol_arrayref( $sql, undef, ( $authtypeto->authtypecode ));
1414     }  
1415
1416     my $overwrite = C4::Context->preference( 'AuthorityMergeMode' ) eq 'strict';
1417     my $skip_subfields = $overwrite
1418         # This hash contains all subfields from the authority report fields
1419         # Including $MARCfrom as well as $MARCto
1420         # We only need it in loose merge mode; replaces the former $exclude
1421         ? {}
1422         : { map { ( $_->[0], 1 ); } ( @record_from, @record_to ) };
1423
1424     my $counteditedbiblio = 0;
1425     foreach my $biblionumber ( @biblionumbers ) {
1426         my $marcrecord = GetMarcBiblio({ biblionumber => $biblionumber });
1427         next if !$marcrecord;
1428         my $update = 0;
1429         foreach my $tagfield (@$tags_using_authtype) {
1430             my $countfrom = 0;    # used in strict mode to remove duplicates
1431             foreach my $field ( $marcrecord->field($tagfield) ) {
1432                 my $auth_number = $field->subfield("9");    # link to authority
1433                 my $tag         = $field->tag();
1434                 next if !defined($auth_number) || $auth_number ne $mergefrom;
1435                 $countfrom++;
1436                 if ( !$mergeto || !@record_to ||
1437                   ( $overwrite && $countfrom > 1 ) ) {
1438                     # !mergeto or !record_to indicates a delete
1439                     # Other condition: remove this duplicate in strict mode
1440                     $marcrecord->delete_field($field);
1441                     $update = 1;
1442                     next;
1443                 }
1444                 my $newtag = $tags_new && @$tags_new
1445                   ? _merge_newtag( $tag, $tags_new )
1446                   : $tag;
1447                 my $controlled_ind = $authto->controlled_indicators({ record => $MARCto, biblio_tag => $newtag });
1448                 my $field_to = MARC::Field->new(
1449                     $newtag,
1450                     $controlled_ind->{ind1} // $field->indicator(1),
1451                     $controlled_ind->{ind2} // $field->indicator(2),
1452                     9 => $mergeto, # Needed to create field, will be moved
1453                 );
1454                 my ( @prefix, @postfix );
1455                 if ( !$overwrite ) {
1456                     # add subfields back in loose mode, check skip_subfields
1457                     # The first extra subfields will be in front of the
1458                     # controlled block, the rest at the end.
1459                     my $prefix_flag = 1;
1460                     foreach my $subfield ( $field->subfields ) {
1461                         next if $subfield->[0] eq '9'; # skip but leave flag
1462                         if ( $skip_subfields->{ $subfield->[0] } ) {
1463                             # This marks the beginning of the controlled block
1464                             $prefix_flag = 0;
1465                             next;
1466                         }
1467                         if ($prefix_flag) {
1468                             push @prefix, [ $subfield->[0], $subfield->[1] ];
1469                         } else {
1470                             push @postfix, [ $subfield->[0], $subfield->[1] ];
1471                         }
1472                     }
1473                 }
1474                 foreach my $subfield ( @prefix, @record_to, @postfix ) {
1475                     $field_to->add_subfields($subfield->[0] => $subfield->[1]);
1476                 }
1477                 if( exists $controlled_ind->{sub2} ) { # thesaurus info
1478                     if( defined $controlled_ind->{sub2} ) {
1479                         # Add or replace
1480                         $field_to->update( 2 => $controlled_ind->{sub2} );
1481                     } else {
1482                         # Key alerts us here to remove $2
1483                         $field_to->delete_subfield( code => '2' );
1484                     }
1485                 }
1486                 # Move $9 to the end
1487                 $field_to->delete_subfield( code => '9' );
1488                 $field_to->add_subfields( 9 => $mergeto );
1489
1490                 if ($tags_new && @$tags_new) {
1491                     $marcrecord->delete_field($field);
1492                     append_fields_ordered( $marcrecord, $field_to );
1493                 } else {
1494                     $field->replace_with($field_to);
1495                 }
1496                 $update = 1;
1497             }
1498         }
1499         next if !$update;
1500         ModBiblio($marcrecord, $biblionumber, GetFrameworkCode($biblionumber));
1501         $counteditedbiblio++;
1502     }
1503     return $counteditedbiblio;
1504 }
1505
1506 sub _merge_newtag {
1507 # Routine is only called for an (exceptional) authtypecode change
1508 # Fixes old behavior of returning the first tag found
1509     my ( $oldtag, $new_tags ) = @_;
1510
1511     # If we e.g. have 650 and 151,651,751 try 651 and check presence
1512     my $prefix = substr( $oldtag, 0, 1 );
1513     my $guess = $prefix . substr( $new_tags->[0], -2 );
1514     if( grep { $_ eq $guess } @$new_tags ) {
1515         return $guess;
1516     }
1517     # Otherwise return one from the same block e.g. 6XX for 650
1518     # If not there too, fall back to first new tag (old behavior!)
1519     my @same_block = grep { /^$prefix/ } @$new_tags;
1520     return @same_block ? $same_block[0] : $new_tags->[0];
1521 }
1522
1523 sub append_fields_ordered {
1524 # while we lack this function in MARC::Record
1525 # we do not want insert_fields_ordered since it inserts before
1526     my ( $record, $field ) = @_;
1527     if( my @flds = $record->field( $field->tag ) ) {
1528         $record->insert_fields_after( pop @flds, $field );
1529     } else { # now fallback to insert_fields_ordered
1530         $record->insert_fields_ordered( $field );
1531     }
1532 }
1533
1534 =head2 get_auth_type_location
1535
1536   my ($tag, $subfield) = get_auth_type_location($auth_type_code);
1537
1538 Get the tag and subfield used to store the heading type
1539 for indexing purposes.  The C<$auth_type> parameter is
1540 optional; if it is not supplied, assume ''.
1541
1542 This routine searches the MARC authority framework
1543 for the tag and subfield whose kohafield is 
1544 C<auth_header.authtypecode>; if no such field is
1545 defined in the framework, default to the hardcoded value
1546 specific to the MARC format.
1547
1548 =cut
1549
1550 sub get_auth_type_location {
1551     my $auth_type_code = @_ ? shift : '';
1552
1553     my ($tag, $subfield) = GetAuthMARCFromKohaField('auth_header.authtypecode', $auth_type_code);
1554     if (defined $tag and defined $subfield and $tag != 0 and $subfield ne '' and $subfield ne ' ') {
1555         return ($tag, $subfield);
1556     } else {
1557         if (C4::Context->preference('marcflavour') eq "MARC21")  {
1558             return C4::AuthoritiesMarc::MARC21::default_auth_type_location();
1559         } else {
1560             return C4::AuthoritiesMarc::UNIMARC::default_auth_type_location();
1561         }
1562     }
1563 }
1564
1565 =head2 compare_fields
1566
1567   my match = compare_fields($field1, $field2, 'abcde');
1568
1569 Compares the listed subfields of both fields and return true if they all match
1570
1571 =cut
1572
1573 sub compare_fields {
1574     my ($field1, $field2, $subfields) = @_;
1575
1576     foreach my $subfield (split(//, $subfields)) {
1577         my $subfield1 = $field1->subfield($subfield) // '';
1578         my $subfield2 = $field2->subfield($subfield) // '';
1579         return 0 unless $subfield1 eq $subfield2;
1580     }
1581     return 1;
1582 }
1583
1584
1585 END { }       # module clean-up code here (global destructor)
1586
1587 1;
1588 __END__
1589
1590 =head1 AUTHOR
1591
1592 Koha Development Team <http://koha-community.org/>
1593
1594 Paul POULAIN paul.poulain@free.fr
1595 Ere Maijala ere.maijala@helsinki.fi
1596
1597 =cut
1598