View | Details | Raw Unified | Return to bug 11300
Collapse All | Expand All

(-)a/C4/Biblio.pm (-2 / +12 lines)
Lines 499-505 sub BiblioAutoLink { Link Here
499
499
500
=head2 LinkBibHeadingsToAuthorities
500
=head2 LinkBibHeadingsToAuthorities
501
501
502
  my $num_headings_changed, %results = LinkBibHeadingsToAuthorities($linker, $marc, $frameworkcode, [$allowrelink, $verbose]);
502
  my $num_headings_changed, %results = LinkBibHeadingsToAuthorities($linker, $marc, $frameworkcode, [$allowrelink, $verbose, $dont_auto_create]);
503
503
504
Links bib headings to authority records by checking
504
Links bib headings to authority records by checking
505
each authority-controlled field in the C<MARC::Record>
505
each authority-controlled field in the C<MARC::Record>
Lines 522-527 sub LinkBibHeadingsToAuthorities { Link Here
522
    my $frameworkcode = shift;
522
    my $frameworkcode = shift;
523
    my $allowrelink = shift;
523
    my $allowrelink = shift;
524
    my $verbose = shift;
524
    my $verbose = shift;
525
    my $dont_auto_create = shift;
525
    my %results;
526
    my %results;
526
    require C4::Heading;
527
    require C4::Heading;
527
    require C4::AuthoritiesMarc;
528
    require C4::AuthoritiesMarc;
Lines 543-548 sub LinkBibHeadingsToAuthorities { Link Here
543
        }
544
        }
544
545
545
        my ( $authid, $fuzzy, $status ) = $linker->get_link($heading);
546
        my ( $authid, $fuzzy, $status ) = $linker->get_link($heading);
547
548
        if(defined $status and ($status eq 'NO_CONNECTION' or $status eq 'SERVER_NOT_FOUND')) {
549
            return 0, { error => $status };
550
        }
551
546
        if ($authid) {
552
        if ($authid) {
547
            $results{ $fuzzy ? 'fuzzy' : 'linked' }
553
            $results{ $fuzzy ? 'fuzzy' : 'linked' }
548
              ->{ $heading->display_form() }++;
554
              ->{ $heading->display_form() }++;
Lines 563-571 sub LinkBibHeadingsToAuthorities { Link Here
563
                $results{'fuzzy'}->{ $heading->display_form() }++;
569
                $results{'fuzzy'}->{ $heading->display_form() }++;
564
                push(@{$results{'details'}}, { tag => $field->tag(), authid => $current_link, status => 'UNCHANGED'}) if $verbose;
570
                push(@{$results{'details'}}, { tag => $field->tag(), authid => $current_link, status => 'UNCHANGED'}) if $verbose;
565
            }
571
            }
566
            elsif ( C4::Context->preference('AutoCreateAuthorities') ) {
572
            elsif ( !$dont_auto_create && C4::Context->preference('AutoCreateAuthorities') ) {
567
                if ( _check_valid_auth_link( $current_link, $field ) ) {
573
                if ( _check_valid_auth_link( $current_link, $field ) ) {
568
                    $results{'linked'}->{ $heading->display_form() }++;
574
                    $results{'linked'}->{ $heading->display_form() }++;
575
                    push(@{$results{'details'}}, { tag => $field->tag(), authid => $authid, status => 'UNCHANGED'}) if $verbose;
569
                }
576
                }
570
                else {
577
                else {
571
                    $authid = AddAuthorityFromHeading($heading, $field, $bib);
578
                    $authid = AddAuthorityFromHeading($heading, $field, $bib);
Lines 636-641 sub AddAuthorityFromHeading { Link Here
636
    my $heading = shift;
643
    my $heading = shift;
637
    my $field = shift;
644
    my $field = shift;
638
    my $bib = shift;
645
    my $bib = shift;
646
    my $current_link = $field->subfield('9');
639
647
640
    my $authtypedata =
648
    my $authtypedata =
641
      C4::AuthoritiesMarc::GetAuthType( $heading->auth_type() );
649
      C4::AuthoritiesMarc::GetAuthType( $heading->auth_type() );
Lines 644-649 sub AddAuthorityFromHeading { Link Here
644
        $marcrecordauth->leader('     nz  a22     o  4500');
652
        $marcrecordauth->leader('     nz  a22     o  4500');
645
        SetMarcUnicodeFlag( $marcrecordauth, 'MARC21' );
653
        SetMarcUnicodeFlag( $marcrecordauth, 'MARC21' );
646
    }
654
    }
655
    $field->delete_subfield( code => '9' )
656
      if defined $current_link;
647
    my $authfield =
657
    my $authfield =
648
      MARC::Field->new( $authtypedata->{auth_tag_to_report},
658
      MARC::Field->new( $authtypedata->{auth_tag_to_report},
649
        '', '', "a" => "" . $field->subfield('a') );
659
        '', '', "a" => "" . $field->subfield('a') );
(-)a/C4/Linker/Z3950Server.pm (+317 lines)
Line 0 Link Here
1
package C4::Linker::Z3950Server;
2
3
# Copyright 2012 Frédérick Capovilla - Libéo
4
#
5
# This file is part of Koha.
6
#
7
# Koha is free software; you can redistribute it and/or modify it under the
8
# terms of the GNU General Public License as published by the Free Software
9
# Foundation; either version 2 of the License, or (at your option) any later
10
# version.
11
#
12
# Koha is distributed in the hope that it will be useful, but WITHOUT ANY
13
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
14
# A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
15
#
16
# You should have received a copy of the GNU General Public License along
17
# with Koha; if not, write to the Free Software Foundation, Inc.,
18
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19
20
use strict;
21
use warnings;
22
use Carp;
23
use MARC::Field;
24
use MARC::Record;
25
use C4::Context;
26
use C4::Heading;
27
use ZOOM; # For Z39.50 Searches
28
use C4::AuthoritiesMarc; # For Authority addition
29
use C4::Log; # logaction
30
31
use base qw(C4::Linker);
32
33
34
sub get_link {
35
    my $self        = shift;
36
    my $heading     = shift;
37
    my $behavior    = shift || 'default';
38
    my $search_form = $heading->search_form();
39
    my $authid;
40
    my $fuzzy = 0;
41
    my $status = '';
42
43
    if ( $self->{'cache'}->{$search_form}->{'cached'} ) {
44
        $authid = $self->{'cache'}->{$search_form}->{'authid'};
45
        $fuzzy  = $self->{'cache'}->{$search_form}->{'fuzzy'};
46
    }
47
    else {
48
        # Look for matching authorities on the Z39.50 server
49
        unless($self->{'local_first'}) {
50
            ($authid, undef, $status) = $self->getZ3950Authority($heading);
51
52
            if($status eq 'Z3950_SEARCH_ERROR' or
53
               $status eq 'Z3950_QUERY_ERROR' or
54
               $status eq 'NO_CONNECTION' or
55
               $status eq 'SERVER_NOT_FOUND') {
56
                return $authid, $fuzzy, $status;
57
            }
58
        }
59
60
        if(!$authid) {
61
            # look for matching authorities
62
            my $authorities = $heading->authorities(1);    # $skipmetadata = true
63
64
            if ( $behavior eq 'default' && $#{$authorities} == 0 ) {
65
                $authid = $authorities->[0]->{'authid'};
66
            }
67
            elsif ( $behavior eq 'first' && $#{$authorities} >= 0 ) {
68
                $authid = $authorities->[0]->{'authid'};
69
                $fuzzy  = $#{$authorities} > 0;
70
            }
71
            elsif ( $behavior eq 'last' && $#{$authorities} >= 0 ) {
72
                $authid = $authorities->[ $#{$authorities} ]->{'authid'};
73
                $fuzzy  = $#{$authorities} > 0;
74
            }
75
76
            if ( !defined $authid && $self->{'broader_headings'} ) {
77
                my $field     = $heading->field();
78
                my @subfields = $field->subfields();
79
                if ( scalar @subfields > 1 ) {
80
                    pop @subfields;
81
                    $field->replace_with(
82
                        MARC::Field->new(
83
                            $field->tag,
84
                            $field->indicator(1),
85
                            $field->indicator(2),
86
                            map { $_[0] => $_[1] } @subfields
87
                        )
88
                    );
89
                    ( $authid, $fuzzy ) =
90
                      $self->get_link( C4::Heading->new_from_bib_field($field),
91
                        $behavior );
92
                }
93
            }
94
        }
95
96
        if($self->{'local_first'} && !$authid) {
97
            ($authid, undef, $status) = $self->getZ3950Authority($heading);
98
        }
99
100
        $self->{'cache'}->{$search_form}->{'cached'} = 1;
101
        $self->{'cache'}->{$search_form}->{'authid'} = $authid;
102
        $self->{'cache'}->{$search_form}->{'fuzzy'}  = $fuzzy;
103
    }
104
    return $self->SUPER::_handle_auth_limit($authid), $fuzzy, $status;
105
}
106
107
sub flip_heading {
108
    my $self    = shift;
109
    my $heading = shift;
110
111
    # TODO: implement
112
}
113
114
115
=head2 getZ3950Authority
116
117
  ($authid, $record, $status) = $self->getZ3950Authority($heading);
118
119
  Do a Z39.50 search for the heading using the $conn ZOOM::Connection object and the $heading Heading.
120
  The column kx_rcimport_control_number is used to check for duplicates.
121
  FIXME: Use thesaurus in search? As of Koha 3.8, the community stopped using them.
122
123
  RETURNS :
124
  $authid = the ID of the local copy of the authority record that was found. undef if nothing was found.
125
  $record = the MARC record of the found authority.
126
  $status = A string with additional informations on the search status (Z3950_CREATED, Z3950_UPDATED, Z3950_QUERY_ERROR, Z3950_SEARCH_ERROR)
127
128
=cut
129
130
sub getZ3950Authority {
131
    my $self = shift;
132
    my $heading = shift;
133
134
    # Try to find a match on the Z39.50 server if LinkerZ3950Server is set
135
    if(C4::Context->preference('LinkerZ3950Server')) {
136
        unless($self->{'conn'}) {
137
            my $sth = C4::Context->dbh->prepare("select * from z3950servers where name=?");
138
            $sth->execute(C4::Context->preference('LinkerZ3950Server'));
139
            my $server = $sth->fetchrow_hashref or undef;
140
            $sth->finish;
141
142
            if($server) {
143
                my $options = new ZOOM::Options();
144
                $options->option('cclfile' => C4::Context->ModZebrations('authorityserver')->{"ccl2rpn"});
145
                $options->option('elementSetName', 'F');
146
                $options->option('async', 0);
147
                $options->option('databaseName', $server->{db});
148
                $options->option('user',         $server->{userid}  ) if $server->{userid};
149
                $options->option('password',     $server->{password}) if $server->{password};
150
                $options->option('preferredRecordSyntax', $server->{syntax});
151
                $self->{'conn'} = create ZOOM::Connection($options);
152
                eval{ $self->{'conn'}->connect( $server->{host}, $server->{port} ) };
153
                if($@) {
154
                    return undef, undef, 'NO_CONNECTION';
155
                }
156
            }
157
            else {
158
                return undef, undef, 'SERVER_NOT_FOUND';
159
            }
160
        }
161
    }
162
    else {
163
        return;
164
    }
165
166
    my $query = qq(Match-heading,do-not-truncate,ext="$heading->{'search_form'}");
167
168
    my $zquery = eval{ new ZOOM::Query::CCL2RPN($query, $self->{'conn'}) };
169
    if($@) {
170
        warn $query . "\n" . $@;
171
        return undef, undef, 'Z3950_QUERY_ERROR';
172
    }
173
174
    # Try to send the search query to the server.
175
    my $rs = eval{ $self->{'conn'}->search($zquery) };
176
    if($@){
177
        warn $query . "\n" . $@;
178
        return undef, undef, 'Z3950_SEARCH_ERROR';
179
    }
180
181
    # If authorities are found, select the first valid one for addition in the local authority table.
182
    my $record;
183
    if($rs->size != 0) {
184
        $record = MARC::Record::new_from_usmarc($rs->record(0)->raw());
185
    }
186
    $rs->destroy();
187
    $zquery->destroy();
188
189
    # If a valid authority was found, add it in the local authority database.
190
    if($record) {
191
        my $dbh=C4::Context->dbh;
192
193
        my $authtypecode = C4::AuthoritiesMarc::GuessAuthTypeCode($record);
194
        my $authId;
195
196
        # Use the control number to prevent the creation of duplicate authorities.
197
        my $controlNumber = $record->field('970')->subfield('0');
198
        my $sthExist=$dbh->prepare("SELECT authid FROM auth_header WHERE kx_rcimport_control_number=?");
199
        $sthExist->execute($controlNumber) or die $sthExist->errstr;
200
        ($authId) = $sthExist->fetchrow;
201
        $sthExist->finish;
202
203
        #------------------------------------------------------------------------------------------
204
        # Corrections and verifications before insertion
205
        my $format;
206
        my $leader='     nz  a22     o  4500';# Leader for incomplete MARC21 record
207
208
        if (uc(C4::Context->preference('marcflavour')) eq 'UNIMARC') {
209
            $format= 'UNIMARCAUTH';
210
        }
211
        else {
212
            $format= 'MARC21';
213
        }
214
215
        if ($format eq "MARC21") {
216
            if (!$record->leader) {
217
                $record->leader($leader);
218
            }
219
            if (!$record->field('003')) {
220
                $record->insert_fields_ordered(
221
                        MARC::Field->new('003',C4::Context->preference('MARCOrgCode'))
222
                        );
223
            }
224
            my $time=POSIX::strftime("%Y%m%d%H%M%S",localtime);
225
            if (!$record->field('005')) {
226
                $record->insert_fields_ordered(
227
                        MARC::Field->new('005',$time.".0")
228
                        );
229
            }
230
            my $date=POSIX::strftime("%y%m%d",localtime);
231
            if (!$record->field('008')) {
232
                # Get a valid default value for field 008
233
                my $default_008 = C4::Context->preference('MARCAuthorityControlField008');
234
                if(!$default_008 or length($default_008)<34) {
235
                    $default_008 = '|| aca||aabn           | a|a     d';
236
                }
237
                else {
238
                    $default_008 = substr($default_008,0,34);
239
                }
240
241
                $record->insert_fields_ordered( MARC::Field->new('008',$date.$default_008) );
242
            }
243
            if (!$record->field('040')) {
244
                $record->insert_fields_ordered(
245
                    MARC::Field->new('040','','',
246
                        'a' => C4::Context->preference('MARCOrgCode'),
247
                        'c' => C4::Context->preference('MARCOrgCode')
248
                    )
249
                );
250
            }
251
        }
252
        if ($format eq "UNIMARCAUTH") {
253
            $record->leader("     nx  j22             ") unless ($record->leader());
254
            my $date=POSIX::strftime("%Y%m%d",localtime);
255
            if (my $string=$record->subfield('100',"a")){
256
                $string=~s/fre50/frey50/;
257
                $record->field('100')->update('a'=>$string);
258
            }
259
            elsif ($record->field('100')){
260
                $record->field('100')->update('a'=>$date."afrey50      ba0");
261
            } else {
262
                $record->append_fields(
263
                    MARC::Field->new('100',' ',' '
264
                        ,'a'=>$date."afrey50      ba0")
265
                );
266
            }
267
        }
268
        my ($auth_type_tag, $auth_type_subfield) = C4::AuthoritiesMarc::get_auth_type_location($authtypecode);
269
        if (!$authId and $format eq "MARC21") {
270
        # only need to do this fix when modifying an existing authority
271
            C4::AuthoritiesMarc::MARC21::fix_marc21_auth_type_location($record, $auth_type_tag, $auth_type_subfield);
272
        }
273
        if (my $field=$record->field($auth_type_tag)){
274
            $field->update($auth_type_subfield=>$authtypecode);
275
        }
276
        else {
277
            $record->add_fields($auth_type_tag,'','', $auth_type_subfield=>$authtypecode); 
278
        }
279
        #------------------------------------------------------------------------------------------
280
        if ($authId) {
281
            my $oldRecord=C4::AuthoritiesMarc::GetAuthority($authId);
282
283
            my $sth=$dbh->prepare("UPDATE auth_header SET authtypecode=?,marc=?,marcxml=? WHERE kx_rcimport_control_number=?");
284
            eval { $sth->execute($authtypecode,$record->as_usmarc,$record->as_xml_record($format),$controlNumber) or die $sth->errstr; };
285
            $sth->finish;
286
            warn "Problem with authority $controlNumber : Cannot update" if $@;
287
            $dbh->commit unless $dbh->{AutoCommit};
288
            return if $@;
289
290
            C4::Biblio::ModZebra($authId,'linkerUpdate',"authorityserver",$oldRecord,$record);
291
            return ($authId, $record, 'Z3950_UPDATED');
292
        } 
293
        else {
294
            my $sth=$dbh->prepare("INSERT INTO auth_header (datecreated,authtypecode,marc,marcxml,kx_rcimport_control_number) VALUES (NOW(),?,?,?,?)");
295
            eval { $sth->execute($authtypecode,$record->as_usmarc,$record->as_xml_record($format),$controlNumber) or die $sth->errstr; };
296
            $sth->finish;
297
            warn "Problem with authority $controlNumber : Cannot insert" if $@;
298
            my $id = $dbh->{'mysql_insertid'};
299
            $dbh->commit unless $dbh->{AutoCommit};
300
            return if $@;
301
302
            logaction( "AUTHORITIES", "ADD", $id, "authority" ) if C4::Context->preference("AuthoritiesLog");
303
            C4::Biblio::ModZebra($id,'linkerUpdate',"authorityserver",undef,$record);
304
            return ($id, $record, 'Z3950_CREATED');
305
        }
306
    }
307
    return;
308
}
309
310
1;
311
__END__
312
313
=head1 NAME
314
315
C4::Linker::Default - match only if there is a single matching auth
316
317
=cut
(-)a/cataloguing/automatic_linker.pl (-4 / +10 lines)
Lines 43-49 if ($auth_status ne "ok") { Link Here
43
#
43
#
44
# tag = the tag number of the field
44
# tag = the tag number of the field
45
# authid = the value of the $9 subfield for this tag
45
# authid = the value of the $9 subfield for this tag
46
# status = The status of the link (LOCAL_FOUND, NONE_FOUND, MULTIPLE_MATCH, UNCHANGED, CREATED)
46
# status = The status of the link (LOCAL_FOUND, NONE_FOUND, MULTIPLE_MATCH, UNCHANGED, CREATED, Z3950_CREATED, Z3950_UPDATED)
47
47
48
my $json;
48
my $json;
49
49
Lines 62-70 if ($@) { Link Here
62
}
62
}
63
my $linker = $linker_module->new( { 'options' => C4::Context->preference("LinkerOptions") } );
63
my $linker = $linker_module->new( { 'options' => C4::Context->preference("LinkerOptions") } );
64
64
65
my ( $headings_changed, $results ) = LinkBibHeadingsToAuthorities( $linker, $record, $params['frameworkcode'], C4::Context->preference("CatalogModuleRelink") || '', 1 );
65
my ( $headings_changed, $results ) = LinkBibHeadingsToAuthorities( $linker, $record, $params['frameworkcode'],
66
    C4::Context->preference("CatalogModuleRelink") || '', 1, 1);
66
67
67
$json->{status} = 'OK';
68
if(defined $results->{error}) {
68
$json->{links} = $results->{details} || '';
69
    $json->{status} = $results->{error};
70
}
71
else {
72
    $json->{status} = 'OK';
73
    $json->{links} = $results->{details} || '';
74
}
69
75
70
print to_json($json);
76
print to_json($json);
(-)a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/authorities.pref (+5 lines)
Lines 58-63 Authorities: Link Here
58
                  Default: Default
58
                  Default: Default
59
                  FirstMatch: "First Match"
59
                  FirstMatch: "First Match"
60
                  LastMatch: "Last Match"
60
                  LastMatch: "Last Match"
61
                  Z3950Server: "Z39.50 Server"
61
            - linker module for matching headings to authority records.
62
            - linker module for matching headings to authority records.
62
        -
63
        -
63
            - Set the following options for the authority linker
64
            - Set the following options for the authority linker
Lines 85-87 Authorities: Link Here
85
                  yes: Do
86
                  yes: Do
86
                  no: "Do not"
87
                  no: "Do not"
87
            - automatically relink headings that have previously been linked when saving records in the cataloging module.
88
            - automatically relink headings that have previously been linked when saving records in the cataloging module.
89
        -
90
            - "Import authorities from this Z39.50 server when searching for authority links with the 'Z39.50 Server' linker module :"
91
            - pref: LinkerZ3950Server
92
              class: multi
(-)a/koha-tmpl/intranet-tmpl/prog/en/modules/cataloguing/addbiblio.tt (-1 / +16 lines)
Lines 200-205 function updateHeadingLinks(links) { Link Here
200
        var message = '';
200
        var message = '';
201
        var field_color = '#FFAAAA';
201
        var field_color = '#FFAAAA';
202
        switch(heading.status) {
202
        switch(heading.status) {
203
            case 'Z3950_CREATED':
204
                image = 'approve.gif';
205
                message = _("A matching authority record was found on the Z39.50 server and was imported locally.");
206
                field_color = '#99FF99';
207
                break;
208
            case 'Z3950_UPDATED':
209
                image = 'approve.gif';
210
                message = _("A matching authority record was found on the Z39.50 server and a local authority was updated.");
211
                field_color = '#99FF99';
212
                break;
203
            case 'LOCAL_FOUND':
213
            case 'LOCAL_FOUND':
204
                image = 'approve.gif';
214
                image = 'approve.gif';
205
                message = _("A matching authority was found in the local database.");
215
                message = _("A matching authority was found in the local database.");
Lines 281-286 function AutomaticLinker() { Link Here
281
                case 'UNAUTHORIZED':
291
                case 'UNAUTHORIZED':
282
                    alert(_("Error : You do not have the permissions necessary to use this functionality."));
292
                    alert(_("Error : You do not have the permissions necessary to use this functionality."));
283
                    break;
293
                    break;
294
                case 'SERVER_NOT_FOUND':
295
                    alert(_("Error : The Z39.50 server configured in the 'LinkerZ3950Server' preference was not found in the Z39.50 servers list."));
296
                    break;
297
                case 'NO_CONNECTION':
298
                    alert(_("Error : Could not connect to the Z39.50 server."));
299
                    break;
284
                case 'OK':
300
                case 'OK':
285
                    updateHeadingLinks(json.links);
301
                    updateHeadingLinks(json.links);
286
                    break;
302
                    break;
287
- 

Return to bug 11300