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

(-)a/Koha/PID/Controller.pm (+401 lines)
Line 0 Link Here
1
package Koha::PID::Controller;
2
3
# This file is part of Koha.
4
#
5
# Copyright (C) 2020 Rijksmuseum
6
#
7
# Koha is free software; you can redistribute it and/or modify it
8
# under the terms of the GNU General Public License as published by
9
# the Free Software Foundation; either version 3 of the License, or
10
# (at your option) any later version.
11
#
12
# Koha is distributed in the hope that it will be useful, but
13
# WITHOUT ANY WARRANTY; without even the implied warranty of
14
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
# GNU General Public License for more details.
16
#
17
# You should have received a copy of the GNU General Public License
18
# along with Koha; if not, see <http://www.gnu.org/licenses>.
19
20
=head1 NAME
21
22
Koha::PID::Controller -- Facilitate handling of persistent identifiers
23
24
=cut
25
26
use Modern::Perl;
27
#use Data::Dumper qw/Dumper/;
28
29
use C4::AuthoritiesMarc qw//;
30
use C4::Biblio qw//;
31
use C4::Context;
32
use Koha::Authorities;
33
use Koha::Biblios;
34
use Koha::Exceptions;
35
use Koha::Items;
36
use Koha::Plugins;
37
38
=head1 METHODS
39
40
=head2 new
41
42
    my $ctrl = Koha::PID::Controller->new({ domain => _optional_domain_ });
43
44
    Simple constructor; blesses any passed hash reference.
45
    The object parses and caches the pref PID_Field during its lifetime.
46
    It also keeps PID_Domain or the overriding parameter to extract PIDs in the get method.
47
48
=cut
49
50
sub new {
51
    my ( $class, $params ) = @_;
52
    my $self = ref($params) eq 'HASH' ? $params : {};
53
54
    $self->{_field} = _parse_field();
55
    $self->{_domain} = $params->{domain} || C4::Context->preference('PID_Domain') || q/http/;
56
57
    return bless $self, $class;
58
}
59
60
sub _parse_field { # Return hash for field/subfield per auth, biblio or item
61
    my $pref = C4::Context->preference('PID_Field');
62
        # form is: auth:999$x,biblio:999$x,item (subfield $x optional)
63
64
    my $hash = {};
65
    foreach my $cat ( split /,/, $pref ) {
66
        if( $cat eq 'item' ) {
67
            $hash->{item} = 1; # items are handled differently
68
            next;
69
        }
70
        next if $cat !~ /^(auth|biblio)[:=]/;
71
        my @temp = split /[:=]/, $cat, 2;
72
        next if @temp<2 || !$temp[1];
73
        if( $temp[1] =~ /(\d{3})\$(.)$/ ) {
74
            $hash->{$temp[0]}->{tag} = $1;
75
            $hash->{$temp[0]}->{subfield} = $2;
76
        } elsif( $temp[1] =~ /(\d{3})/ ) {
77
            $hash->{$temp[0]}->{tag} = $1;
78
            $hash->{$temp[0]}->{subfield} = 'a' if $1 ge '010';
79
        }
80
    }
81
    return $hash;
82
}
83
84
=head2 get
85
86
    my $pid = $ctrl->get({
87
        category => $category,
88
        kohaIdentifier => $kohaIdentifier,
89
        [ object => $object ],
90
    });
91
92
    Get persistent URI from record for category/kohaIdentifier.
93
    Depends on PID_Field and PID_Domain (see new method).
94
    Optionally pass corresponding Koha object/MARC record.
95
96
    Can be controlled with plugin method pid_get_hook.
97
    Note: a plugin that returns false, makes get return undef.
98
99
    Returns URI or undef.
100
101
=cut
102
103
sub get {
104
    my ( $self, $params ) = @_;
105
    my $category = $params->{category};
106
    my $kohaIdentifier = $params->{kohaIdentifier};
107
    my $object = $params->{object};
108
109
    return if !exists $self->{_field}->{$category};
110
111
    my $record = $self->_get_record($category, $kohaIdentifier, $object);
112
    return if !$self->_get_hook($category, $record); # optional plugin control
113
114
    my @fields;
115
    if( $category eq 'item') {
116
        # check domain and return the first == last inserted one
117
        @fields = grep { /\Q$self->{_domain}\E/ } split ' | ', $record->uri//'';
118
        return _first_uri(@fields);
119
    }
120
121
    my $tag = $self->{_field}->{$category}->{tag};
122
    my $sf = $self->{_field}->{$category}->{subfield};
123
    if( !$sf ) { # control field, might be UNIMARC; no check on domain
124
        return _first_uri( $record->field($tag)->data );
125
    }
126
127
    # Regular MARC21 walk-through for auth or biblio
128
    @fields = map { $_->subfield($sf) && $_->subfield($sf) =~ /\Q$self->{_domain}\E/ ? $_->subfield($sf) : () } $record->field($tag);
129
    return _first_uri(@fields);
130
}
131
132
sub _get_record {
133
    my ( $self, $category, $kohaIdentifier, $object ) = @_;
134
    my $record;
135
    if( $category eq 'biblio' ) {
136
        $object = Koha::Biblios->find($kohaIdentifier) unless $object;
137
        $record = $object->metadata->record;
138
    } elsif( $category eq 'item' ) {
139
        $record = $object // Koha::Items->find($kohaIdentifier);
140
    } else { # assume authority
141
        $object = Koha::Authorities->find( $kohaIdentifier ) unless $object;
142
        $record = MARC::Record->new_from_xml($object->marcxml, 'UTF-8', C4::Context->preference("marcflavour") eq "UNIMARC" ? "UNIMARCAUTH" : "MARC21" )
143
            if $object;
144
    }
145
    Koha::Exceptions::ObjectNotFound->throw( error => "No record found for $category:$kohaIdentifier\n" ) if !$record;
146
    return $record;
147
}
148
149
sub _get_hook { # these plugins may modify $record to control results, or return false
150
    my ($self, $category, $record) = @_;
151
    my $rv;
152
    foreach my $plugin ( Koha::Plugins->new->GetPlugins({ method => 'pid_get_hook' }) ) {
153
        $rv = $plugin->pid_get_hook({ record => $record, category => $category });
154
        return if !$rv;
155
    }
156
    return 1;
157
}
158
159
sub _first_uri {
160
    my ($first, @rest) = @_;
161
    return if !defined $first;
162
    $first =~ s/^.*http/http/; # could be spaces or perhaps orgcode in 035, not expecting two http occurrences
163
    return $first;
164
}
165
166
=head2 create
167
168
    my $uri = $ctrl->create({ category => $category, kohaIdentifier => $kohaIdentifier });
169
170
    Creates a new persistent identifier URI.
171
    This is plugin controlled. It needs a pid_create_hook method. The first returned true value is used.
172
    Preferably, this plugin should be the interface to an external service.
173
    Category, kohaIdentifier and domain (from object creation) are passed to the plugin.
174
    Note that authtypecode (like PERSO_NAME etc.) is passed as category for authority records.
175
    Does not (directly) depend on PID_Field and PID_Domain.
176
177
    Returns { success =>0|1, [ uri => ... ], [ info => 'additional' ] }
178
179
=cut
180
181
sub create {
182
    my ( $self, $params ) = @_;
183
    my $category = $params->{category};
184
    my $kohaIdentifier = $params->{kohaIdentifier};
185
186
    my $authtypecode;
187
    if( $category eq 'auth' ) {
188
        my $rec = Koha::Authorities->find( $kohaIdentifier );
189
        $authtypecode = $rec->authtypecode if $rec;
190
    }
191
192
    # Call plugin
193
    my @plugins = Koha::Plugins->new->GetPlugins({ method => 'pid_create_hook' });
194
    return { success => 0, info => 'NO_PLUGIN' } if !@plugins;
195
    my $domain = $self->{_domain} eq 'http' ? q{} : $self->{_domain};
196
    my $rv;
197
    foreach my $plugin ( @plugins ) {
198
        $rv = eval { $plugins[0]->pid_create_hook({ category => $authtypecode // $category, identifier => $kohaIdentifier, domain => $domain }) };
199
        last if $rv; # first true value
200
    }
201
    if( !$rv ) {
202
        return { success => 0, info => $@ // q{} };
203
    } else {
204
        return { success => 1, uri => $rv };
205
    }
206
}
207
208
=head2 insert
209
210
    $ctrl->insert({
211
        pid => $pid,
212
        category => $category,
213
        kohaIdentifier => $kohaIdentifier,
214
        [ object => $object ],
215
        [ do_not_save => 1 ],
216
    });
217
218
    Insert $pid into the record referenced by $category and $kohaIdentifier.
219
    Optionally pass corresponding Koha object/MARC record.
220
    Depends on PID_Field.
221
222
    Behavior can be controlled with plugin hook pid_insert_hook.
223
    The plugin is called with $pid, record, $category and inserted flag.
224
    The inserted flag tells if a new field was inserted (no exact match found).
225
226
    The do_not_save flag may be used for optimization. Use it with care.
227
    Returns an errcode if the $pid already exists.
228
229
    Returns { success => 1|0, [record => $marc | $item ], [ errcode => X ]  }
230
231
=cut
232
233
sub insert {
234
    my ( $self, $params ) = @_;
235
    my $pid = $params->{pid};
236
    my $category = $params->{category};
237
    my $kohaIdentifier = $params->{kohaIdentifier};
238
    my $object = $params->{object};
239
240
    return { success => 0,  errcode => 'INV_CAT' } if !exists $self->{_field}->{$category};
241
    my $record = $self->_get_record($category, $kohaIdentifier, $object);
242
243
    # Add new field
244
    my $insert;
245
    if( $category eq 'item') {
246
        $insert = $self->_update_item_field( $record, $pid );
247
    } else {
248
        $insert = $self->_update_marc_record( $record, $category, $pid );
249
    }
250
251
    # Call plugin
252
    my (@plugins, $rv);
253
    @plugins = Koha::Plugins->new->GetPlugins({ method => 'pid_insert_hook' });
254
    foreach my $plugin ( @plugins ) {
255
        $rv = $plugin->pid_insert_hook({ pid => $pid, record => $record, category => $category, inserted => $insert });
256
        return { success => 0, errcode => 'HOOK' } if !$rv;
257
    }
258
    return { success => 0, errcode => 'EXIST' } if !@plugins && !$insert;
259
    $self->_default_insert_hook({ pid => $pid, record => $record, category => $category }) if !@plugins;
260
261
    # Save
262
    $category = Koha::Authorities->find( $kohaIdentifier )->authtypecode if $category eq 'auth'; # should exist
263
    $self->_save_record( $category, $kohaIdentifier, $record ) unless $params->{do_not_save};
264
    return { success => 1, record => $record };
265
}
266
267
sub _update_item_field {
268
    my ($self, $record, $pid ) = @_;
269
270
    my $value = $record->uri;
271
    if( !$value ) {
272
        $value = $pid;
273
    } elsif( $value =~ /(^|\| )$pid($| \|)/ ) { # exists
274
        return;
275
    } else { # insert in front
276
        $value= "$pid | $value";
277
    }
278
    $record->uri( $value );
279
    return 1;
280
}
281
282
sub _update_marc_record {
283
    my ( $self, $record, $category, $pid ) = @_;
284
    my $tag = $self->{_field}->{$category}->{tag};
285
    my $sf = $self->{_field}->{$category}->{subfield};
286
287
    if(!$sf) { # control field
288
        if( $record->field($tag) ) {
289
            return if $record->field($tag)->data eq $pid;
290
            $record->field($tag)->update( $pid ); # overwrite
291
        } else {
292
            $record->insert_fields_ordered( MARC::Field->new($tag, $pid) );
293
        }
294
    } else {
295
        # Adds a new field unless we find an exact match
296
        return if grep { $_->subfield($sf) && $_->subfield($sf) eq $pid } $record->field($tag);
297
        my $field = MARC::Field->new( $tag, '', '', $sf => $pid );
298
        $record->insert_fields_ordered($field);
299
    }
300
    return 1;
301
}
302
303
sub _default_insert_hook {
304
# If there are no hooks, this sub is used to update 024 when you specified it in PID_Field
305
# Note: If you want to disable this behavior, add a hook returning true
306
    my ($self, $params) = @_; # pid, record, category
307
    my $category = $params->{category};
308
    my $record = $params->{record};
309
    if( $category eq 'item' ) { # skip
310
    } elsif( $self->{_field}->{$category}->{tag} eq '024' ) {
311
        $record->field('024')->update( ind1 => '7', 2 => 'uri' ); # affects only first inserted 024
312
    }
313
}
314
315
sub _save_record {
316
    my ( $self, $category, $kohaIdentifier, $record ) = @_;
317
318
    if( $category eq 'item' ) {
319
        $record->store; # previously calling ModItem
320
    } elsif( $category eq 'biblio' ) {
321
        C4::Biblio::ModBiblio($record, $kohaIdentifier, C4::Biblio::GetFrameworkCode($kohaIdentifier));
322
    } else {
323
        C4::AuthoritiesMarc::ModAuthority( $kohaIdentifier, $record, $category );
324
    }
325
}
326
327
1;
328
__END__
329
330
=head1 SYNOPSIS
331
332
use Koha::PID::Controller;
333
my $ctrl = Koha::PID::Controller->new;
334
335
my $pid = $ctrl->get({ category => $category, kohaIdentifier => $kohaIdentifier });
336
337
my $newpid = $ctrl->create({ category => $category, kohaIdentifier => $kohaIdentifier });
338
if( !$ctrl->insert({ pid => $newpid, category => $category, kohaIdentifier => $kohaIdentifier })->{success} ) {
339
    # tell me
340
}
341
342
=head1 DESCRIPTION
343
344
This controller allows you to maintain persistent identifiers in MARC records
345
of bibliographic and authority records for MARC21 and UNIMARC, and in items.
346
347
Operation is controlled by preferences PID_Field and PID_Domain. PID_Field allows
348
you to specify which categories (auth, biblio, item) have PIDs, and where to store
349
them. PID_Domain is used as a base URL for finding PIDs.
350
351
The module has three main methods: get, create and insert. Get extracts a PID from
352
a record. Create generates a new PID. And insert saves a PID into a record. All three
353
methods have a plugin hook to control the operation: pid_get_hook, pid_create_hook and
354
pid_insert_hook. The get and insert method do not depend on a plugin hook, but the create
355
method does not provide results without pid_create_hook.
356
357
Preference PID_Field allows you to store the PIDs for biblio and authority records
358
anywhere in the MARC record. But MARC21 field 024 or UNIMARC field 003 would
359
probably be your best choice. (If you choose 024, a default insert hook
360
updates indicator1 and adds $2 for you. Only in absence of other insert hooks.)
361
362
=head1 ADDITIONAL COMMENTS
363
364
=head2 pid_get_hook
365
366
This hook receives a parameter hash with record and category. It should return true
367
or false.
368
369
Before the get method looks at the record, it runs the get hooks. If one returns false,
370
get returns undef. Furthermore, the hook receives the MARC record or item object.
371
So it allows you to also control results by changing field values (which are not saved
372
in the method, but be careful when you also use the object parameter).
373
374
Get performs a contains match on PID_Domain. If this is too simple to fit your needs,
375
your get hook could reorder or remove fields so that get returns what you want. Note
376
that this may involve choosing an intelligent value for PID_Domain.
377
378
=head2 pid_create_hook
379
380
This hook receives a parameter hash with category, identifier and domain. It should
381
return a URI or false. The first true value is used.
382
383
The create hook is meant to be the interface to your PID generator. Which should normally
384
be independent of the source system (Koha). You could also use it as your PID generator,
385
although purists won't like that.
386
387
=head2 pid_insert_hook
388
389
This hook receives a parameter hash with pid, record, category and inserted. It should
390
return true or false. (Inserted flag: see below.) A false value makes insert return an
391
error condition while not saving the change.
392
393
The hook is called after the insert method adds a new field. But it did not add a field
394
when an exact match was found. The inserted flag tells the plugin about that. The hook
395
allows you to customize field changes.
396
397
=head1 AUTHOR
398
399
Marcel de Rooy, Rijksmuseum
400
401
=cut
(-)a/t/db_dependent/Koha/PID/Controller.t (-1 / +270 lines)
Line 0 Link Here
0
- 
1
#!/usr/bin/perl
2
3
# This file is part of Koha.
4
#
5
# Copyright (C) 2020 Rijksmuseum
6
#
7
# Koha is free software; you can redistribute it and/or modify it
8
# under the terms of the GNU General Public License as published by
9
# the Free Software Foundation; either version 3 of the License, or
10
# (at your option) any later version.
11
#
12
# Koha is distributed in the hope that it will be useful, but
13
# WITHOUT ANY WARRANTY; without even the implied warranty of
14
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
# GNU General Public License for more details.
16
#
17
# You should have received a copy of the GNU General Public License
18
# along with Koha; if not, see <http://www.gnu.org/licenses>.
19
20
use Modern::Perl;
21
#use Data::Dumper qw/Dumper/;
22
use MARC::Record;
23
use Test::More tests => 4;
24
use Test::MockModule;
25
use Test::MockObject;
26
use Test::Warn;
27
28
use t::lib::Mocks;
29
use t::lib::TestBuilder;
30
31
use C4::AuthoritiesMarc qw//;
32
use C4::Biblio qw//;
33
use C4::Context;
34
use C4::Items;
35
use Koha::Database;
36
use Koha::PID::Controller;
37
38
our $schema = Koha::Database->new->schema;
39
our $builder = t::lib::TestBuilder->new;
40
41
our @plugins;
42
my $plugin_module = Test::MockModule->new( 'Koha::Plugins' );
43
$plugin_module->mock( 'GetPlugins', sub { return @plugins; });
44
45
$schema->storage->txn_begin;
46
47
subtest 'Test get method (biblio)' => sub {
48
    plan tests => 6;
49
50
    my $plugin = Test::MockObject->new;
51
52
    my $record = MARC::Record->new;
53
    $record->append_fields(
54
        MARC::Field->new( '003',              'http://unimarc.fr/U-003' ),
55
        MARC::Field->new( '024', '', '', 1 => 'http://donotgohere.com/M24a', q => 'qual_a' ),
56
        MARC::Field->new( '024', '', '', 1 => 'http://bestpersistentid.nl/M24b' ),
57
        MARC::Field->new( '024', '', '', 1 => 'http://bestpersistentid.nl/M24c', q => 'qual_c' ),
58
        MARC::Field->new( '035', '', '', a => '(RMA)  http://mysite01.org/M35a' ), # deliberate spaces
59
        MARC::Field->new( '035', '', '', a => '(RMAxx)http://mysite02.org/M35b' ),
60
        MARC::Field->new( '035', '', '', a => '(RMAxx)http://bestpersistentid.nl/M35c' ),
61
        MARC::Field->new( '035', '', '', a => '(RMAx) http://mysite03.org/M35d' ),
62
    );
63
    my ($biblionumber) = C4::Biblio::AddBiblio( $record, q{} );
64
65
    t::lib::Mocks::mock_preference('PID_Field', 'biblio:003');
66
    t::lib::Mocks::mock_preference('PID_Domain', 'http://bestpersistentid.nl');
67
    my $ctrl = Koha::PID::Controller->new;
68
    # UNIMARC 003, no domain control
69
    like( $ctrl->get({ category => 'biblio', kohaIdentifier => $biblionumber }), qr/^http.*U-003$/, 'Test get for UNIMARC biblio' );
70
71
    t::lib::Mocks::mock_preference('PID_Field', 'biblio:024$1');
72
    $ctrl = Koha::PID::Controller->new;
73
    # MARC21 024 $1, domain controlled
74
    like( $ctrl->get({ category => 'biblio', kohaIdentifier => $biblionumber }), qr/^http.*M24b$/, 'Test get b for MARC21 biblio (024)' );
75
76
    $plugin->mock( 'pid_get_hook', \&get_hook_24 );
77
    push @plugins, $plugin;
78
    t::lib::Mocks::mock_preference('PID_Domain', q{});
79
    $ctrl = Koha::PID::Controller->new;
80
    # MARC21 024 $1, no domain check, plugin hook (qualifier)
81
    like( $ctrl->get({ category => 'biblio', kohaIdentifier => $biblionumber }), qr/^http.*M24c$/, 'Test get c for MARC21 biblio (024)' );
82
    pop @plugins;
83
84
    t::lib::Mocks::mock_preference('PID_Field', 'biblio:035'); # NOTE: we omit subfield
85
    t::lib::Mocks::mock_preference('PID_Domain', 'http://bestpersistentid.nl');
86
    $ctrl = Koha::PID::Controller->new;
87
    # MARC21 035 $a, domain controlled
88
    like( $ctrl->get({ category => 'biblio', kohaIdentifier => $biblionumber }), qr/^http.*M35c$/, 'Test get c for MARC21 biblio (035)' );
89
90
    $ctrl = Koha::PID::Controller->new({ domain => 'site' });
91
    # MARC21 035 $a, incomplete domain check
92
    like( $ctrl->get({ category => 'biblio', kohaIdentifier => $biblionumber }), qr/^http.*M35a$/, 'Test get a for MARC21 biblio (035)' );
93
94
    $plugin->mock( 'pid_get_hook', \&get_hook_35 );
95
    push @plugins, $plugin;
96
    t::lib::Mocks::mock_preference('MARCOrgCode', 'RMAx');
97
    t::lib::Mocks::mock_preference('PID_Domain', q{});
98
    $ctrl = Koha::PID::Controller->new;
99
    # MARC21 035 $a, no domain check, plugin hook (orgcode)
100
    like( $ctrl->get({ category => 'biblio', kohaIdentifier => $biblionumber }), qr/^http.*M35d$/, 'Test get d for MARC21 biblio (035)' );
101
    pop @plugins;
102
};
103
104
sub get_hook_24 { # Mimics plugin that makes get return 024 with condition on $q
105
    my ($self, $params) = @_;
106
    my $record = $params->{record};
107
    my $qual = 'qual_c';
108
    foreach my $fld ( $record->field('024') ) {
109
        if( !$fld->subfield('q') || $fld->subfield('q') !~ /^\Q$qual\E/ ) {
110
            $record->delete_fields( $fld );
111
        }
112
    }
113
    return 1;
114
}
115
116
sub get_hook_35 { # Mimics plugin that makes get return 035 with orgcode
117
    my ($self, $params) = @_;
118
    my $record = $params->{record};
119
    my $orgcode = C4::Context->preference('MARCOrgCode');
120
    foreach my $fld ( $record->field('035') ) {
121
        if( !$fld->subfield('a') || $fld->subfield('a') !~ /^\(\Q$orgcode\E\)/ ) {
122
            $record->delete_fields( $fld );
123
        }
124
    }
125
    return 1;
126
}
127
128
subtest 'Test get method (item)' => sub {
129
    plan tests => 4;
130
131
    # Test item with object
132
    t::lib::Mocks::mock_preference('PID_Field', 'item');
133
    t::lib::Mocks::mock_preference('PID_Domain', 'my'); # do not use this value normally
134
    my $ctrl = Koha::PID::Controller->new;
135
    my $item = $builder->build_object({ class => 'Koha::Items', value => { uri => 'uri01 | my02 | uri03 | http://site04.org' } });
136
    is( $ctrl->get({ category => 'item', object => $item }), 'my02', 'Test get my02 for item' );
137
    # Test fallback to http
138
    t::lib::Mocks::mock_preference('PID_Domain', q{});
139
    $ctrl = Koha::PID::Controller->new;
140
    like( $ctrl->get({ category => 'item', kohaIdentifier => $item->itemnumber }), qr/04\.org$/, 'Test get http item' );
141
    # Item hook
142
    my $plugin = Test::MockObject->new;
143
    $plugin->mock( 'pid_get_hook', \&get_hook_item );
144
    push @plugins, $plugin;
145
    $ctrl->get({ category => 'item', object => $item });
146
    is( $item->itemnotes, 'Reached', 'Item hook executed' );
147
    pop @plugins;
148
    # Test clearing PID_Field
149
    t::lib::Mocks::mock_preference('PID_Field', q{});
150
    $ctrl = Koha::PID::Controller->new;
151
    is( $ctrl->get({ category => 'item', kohaIdentifier => $item->itemnumber }), undef, 'Test no item category' );
152
};
153
154
sub get_hook_item { # Simple hack to test execution
155
    my ($self, $params) = @_;
156
    my $record = $params->{record}; # Koha::Item
157
    $record->itemnotes('Reached');
158
    return 1;
159
}
160
161
subtest 'Test create' => sub {
162
    plan tests => 4;
163
164
    my $plugin = Test::MockObject->new;
165
    $plugin->mock( 'pid_create_hook', \&create_hook );
166
    push @plugins, $plugin;
167
    my $ctrl = Koha::PID::Controller->new({ domain => 'https://id.rijksmuseum.nl' });
168
    is( $ctrl->create({ category => 'biblio', kohaIdentifier => 100000 })->{uri}, 'https://id.rijksmuseum.nl/biblio/100000', "Create biblio pid" );
169
    # Category is not checked
170
    is( $ctrl->create({ category => 'unchecked', kohaIdentifier => 999 })->{uri}, 'https://id.rijksmuseum.nl/unchecked/999', "Create unchecked pid" );
171
    # Two plugins, 'whatever' comes first
172
    unshift @plugins, Test::MockObject->new->mock( 'pid_create_hook', sub { 'whatever' } );
173
    my $rv = $ctrl->create({ category => 'auth', kohaIdentifier => 1234 });
174
    is( $rv->{uri}, 'whatever', "Result of first plugin returning true value" );
175
    # No plugins
176
    @plugins = ();
177
    is( $ctrl->create({ category => 'item', kohaIdentifier => 654321 })->{success}, 0, "Test without plugin" );
178
};
179
180
sub create_hook { # Super trivial return
181
    my ($self, $params) = @_;
182
    return $params->{domain}. '/'. $params->{category}. '/'. $params->{identifier};
183
}
184
185
subtest 'Test insert' => sub {
186
    plan tests => 19;
187
188
    t::lib::Mocks::mock_preference('PID_Field', 'biblio:024$1,item');
189
    my $ctrl = Koha::PID::Controller->new;
190
191
    # category undefined
192
    is( $ctrl->insert({ pid => '1', category => 'auth', kohaIdentifier => 1 })->{errcode}, 'INV_CAT', 'Undefined category' );
193
194
    # hook
195
    my $plugin = Test::MockObject->new;
196
    $plugin->mock( 'pid_insert_hook', \&insert_hook );
197
    push @plugins, $plugin;
198
    my $item = $builder->build_object({ class => 'Koha::Items', value => { uri => 'A | B' } });
199
    $ctrl->insert({ pid => '1', category => 'item', kohaIdentifier => $item->itemnumber }); # 1 | A | B
200
    is( @plugins, 0, 'Hook got executed' );
201
    # item
202
    $item->discard_changes;
203
    is( $item->uri, '1 | A | B', 'Check result of item insert' );
204
    # reinsert the same value
205
    is( $ctrl->insert({ pid => '1', category => 'item', kohaIdentifier => $item->itemnumber })->{errcode}, 'EXIST', 'Check reinsert' );
206
    # Test do_not_save
207
    my $result = $ctrl->insert({ pid => '2', category => 'item', kohaIdentifier => $item->itemnumber, do_not_save => 1 });
208
    like( $result->{record}->uri, qr/^2/, 'New uri in response' );
209
    $item->discard_changes;
210
    is( $item->uri, '1 | A | B', 'But not saved as expected' );
211
212
    # marc record changes (biblio or auth)
213
    my $biblio = $builder->build_sample_biblio;
214
    my $record;
215
    $result = $ctrl->insert({ pid => '123', category => 'biblio', kohaIdentifier => $biblio->biblionumber });
216
    $record = C4::Biblio::GetMarcBiblio({ biblionumber => $biblio->biblionumber });
217
    is( $record->subfield('024', '1'), '123', 'Check biblio pid 123' );
218
    is( $record->field('024')->indicator(1), '7', 'Check indicator1 from default hook' );
219
    is( $record->subfield('024', '2'), 'uri', 'Check $2 from default hook' );
220
    # reinsert (without hook)
221
    $result = $ctrl->insert({ pid => '123', category => 'biblio', kohaIdentifier => $biblio->biblionumber });
222
    is( $result->{errcode}, 'EXIST', 'Check reinsert 123' );
223
    # empty hook
224
    $plugin->mock('after_biblio_action', sub {} ); # prevent warn
225
    $plugin->mock('pid_insert_hook', sub { 1; } );
226
    push @plugins, $plugin;
227
    $result = $ctrl->insert({ pid => '456', category => 'biblio', kohaIdentifier => $biblio->biblionumber });
228
    $record = C4::Biblio::GetMarcBiblio({ biblionumber => $biblio->biblionumber });
229
    is( $record->subfield('024', '1'), '456', 'Check biblio pid 456' );
230
    is( $record->field('024')->indicator(1), q{ }, 'Check if indicator1 blank' );
231
    is( $record->subfield('024', '2'), undef, 'Check if $2 empty' );
232
    # reinsert (with hook)
233
    $result = $ctrl->insert({ pid => '123', category => 'biblio', kohaIdentifier => $biblio->biblionumber });
234
    is( $result->{success}, 1, 'Reinsert with hook returns success' ); # since the hook 'fixes everything'
235
    my @fields = $result->{record}->field('024');
236
    is( @fields, 2, 'Still expecting to find two 024s' );
237
    # hook returns false
238
    my $plugin2 = Test::MockObject->new;
239
    $plugin2->mock('pid_insert_hook', sub {} );
240
    push @plugins, $plugin2;
241
    $result = $ctrl->insert({ pid => '567', category => 'biblio', kohaIdentifier => $biblio->biblionumber });
242
    is( $result->{errcode}, 'HOOK', 'Hook returned false' );
243
    @plugins = ();
244
245
    # PERSO_NAME authority
246
    t::lib::Mocks::mock_preference('PID_Field', 'auth:024');
247
    $ctrl = Koha::PID::Controller->new;
248
    $record = MARC::Record->new;
249
    my $authid = C4::AuthoritiesMarc::AddAuthority( $record, undef, 'PERSO_NAME' );
250
    $result = $ctrl->insert({ pid => '234', category => 'auth', kohaIdentifier => $authid });
251
    $record = $result->{record};
252
    is( $record->subfield('024', 'a'), '234', 'Check auth pid 234' );
253
    # control field
254
    t::lib::Mocks::mock_preference('PID_Field', 'auth:003');
255
    $ctrl = Koha::PID::Controller->new;
256
    $result = $ctrl->insert({ pid => '345', category => 'auth', kohaIdentifier => $authid });
257
    $record = $result->{record};
258
    is( $record->field('003')->data, '345', 'Check auth pid 345' );
259
    # reinsert
260
    $result = $ctrl->insert({ pid => '345', category => 'auth', kohaIdentifier => $authid });
261
    is( $result->{errcode}, 'EXIST', 'Check reinsert 345' );
262
};
263
264
sub insert_hook {
265
    my ($self, $params) = @_;
266
    pop @plugins; # self destructive ;)
267
    return 1;
268
}
269
270
$schema->storage->txn_rollback;

Return to bug 24544