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

(-)a/Koha/OAI/Server/GetRecord.pm (-22 / +66 lines)
Lines 32-51 sub new { Link Here
32
32
33
    my $self = HTTP::OAI::GetRecord->new(%args);
33
    my $self = HTTP::OAI::GetRecord->new(%args);
34
34
35
    my $prefix = $repository->{koha_identifier} . ':';
36
    my ($biblionumber) = $args{identifier} =~ /^$prefix(.*)/;
37
    my $items_included = $repository->items_included( $args{metadataPrefix} );
35
    my $dbh = C4::Context->dbh;
38
    my $dbh = C4::Context->dbh;
36
    my $sth = $dbh->prepare("
39
    my $sql = "
37
        SELECT timestamp
40
        SELECT timestamp
38
        FROM   biblioitems
41
        FROM   biblioitems
39
        WHERE  biblionumber=? " );
42
        WHERE  biblionumber=?
40
    my $prefix = $repository->{koha_identifier} . ':';
43
    ";
41
    my ($biblionumber) = $args{identifier} =~ /^$prefix(.*)/;
44
    my @bind_params = ($biblionumber);
42
    $sth->execute( $biblionumber );
45
    if ( $items_included ) {
46
        # Take latest timestamp of biblio and any items
47
        $sql .= "
48
            UNION
49
            SELECT timestamp from deleteditems
50
            WHERE biblionumber=?
51
            UNION
52
            SELECT timestamp from items
53
            WHERE biblionumber=?
54
        ";
55
        push @bind_params, $biblionumber;
56
        push @bind_params, $biblionumber;
57
        $sql = "
58
            SELECT max(timestamp)
59
            FROM ($sql) bib
60
        ";
61
    }
62
63
    my $sth = $dbh->prepare( $sql ) || die( 'Could not prepare statement: ' . $dbh->errstr );
64
    $sth->execute( @bind_params ) || die( 'Could not execute statement: ' . $sth->errstr );
43
    my ($timestamp, $deleted);
65
    my ($timestamp, $deleted);
44
    unless ( ($timestamp) = $sth->fetchrow ) {
66
    unless ( ($timestamp = $sth->fetchrow) ) {
45
        unless ( ($timestamp) = $dbh->selectrow_array(q/
67
        $sql = "
46
            SELECT timestamp
68
            SELECT timestamp
47
            FROM deletedbiblio
69
            FROM deletedbiblio
48
            WHERE biblionumber=? /, undef, $biblionumber ))
70
            WHERE biblionumber=?
71
        ";
72
        @bind_params = ($biblionumber);
73
74
        if ( $items_included ) {
75
            # Take latest timestamp among biblio and items
76
            $sql .= "
77
                UNION
78
                SELECT timestamp from deleteditems
79
                WHERE biblionumber=?
80
            ";
81
            push @bind_params, $biblionumber;
82
            $sql = "
83
                SELECT max(timestamp)
84
                FROM ($sql) bib
85
            ";
86
        }
87
88
        $sth = $dbh->prepare($sql) || die('Could not prepare statement: ' . $dbh->errstr);
89
        $sth->execute( @bind_params ) || die('Could not execute statement: ' . $sth->errstr);
90
        unless ( ($timestamp = $sth->fetchrow) )
49
        {
91
        {
50
            return HTTP::OAI::Response->new(
92
            return HTTP::OAI::Response->new(
51
             requestURL  => $repository->self_url(),
93
             requestURL  => $repository->self_url(),
Lines 55-82 sub new { Link Here
55
                ) ],
97
                ) ],
56
            );
98
            );
57
        }
99
        }
58
        else {
100
        $deleted = 1;
59
            $deleted = 1;
60
        }
61
    }
101
    }
62
102
63
    # We fetch it using this method, rather than the database directly,
64
    # so it'll include the item data
65
    my $marcxml;
66
    $marcxml = $repository->get_biblio_marcxml($biblionumber, $args{metadataPrefix})
67
        unless $deleted;
68
    my $oai_sets = GetOAISetsBiblio($biblionumber);
103
    my $oai_sets = GetOAISetsBiblio($biblionumber);
69
    my @setSpecs;
104
    my @setSpecs;
70
    foreach (@$oai_sets) {
105
    foreach (@$oai_sets) {
71
        push @setSpecs, $_->{spec};
106
        push @setSpecs, $_->{spec};
72
    }
107
    }
73
108
74
    #$self->header( HTTP::OAI::Header->new( identifier  => $args{identifier} ) );
109
    if ($deleted) {
75
    $self->record(
110
        $self->record(
76
        $deleted
111
            Koha::OAI::Server::DeletedRecord->new($timestamp, \@setSpecs, %args)
77
        ? Koha::OAI::Server::DeletedRecord->new($timestamp, \@setSpecs, %args)
112
        );
78
        : Koha::OAI::Server::Record->new($repository, $marcxml, $timestamp, \@setSpecs, %args)
113
    } else {
79
    );
114
        # We fetch it using this method, rather than the database directly,
115
        # so it'll include the item data
116
        my $marcxml;
117
        $marcxml = $repository->get_biblio_marcxml($biblionumber, $args{metadataPrefix})
118
            unless $deleted;
119
120
        $self->record(
121
            Koha::OAI::Server::Record->new($repository, $marcxml, $timestamp, \@setSpecs, %args)
122
        );
123
    }
80
    return $self;
124
    return $self;
81
}
125
}
82
126
(-)a/Koha/OAI/Server/Identify.pm (-3 / +4 lines)
Lines 1-5 Link Here
1
# Copyright Tamil s.a.r.l. 2008-2015
1
# Copyright Tamil s.a.r.l. 2008-2015
2
# Copyright Biblibre 2008-2015
2
# Copyright Biblibre 2008-2015
3
# Copyright The National Library of Finland, University of Helsinki 2016-2017
3
#
4
#
4
# This file is part of Koha.
5
# This file is part of Koha.
5
#
6
#
Lines 33-40 sub new { Link Here
33
        repositoryName      => C4::Context->preference("LibraryName"),
34
        repositoryName      => C4::Context->preference("LibraryName"),
34
        adminEmail          => C4::Context->preference("KohaAdminEmailAddress"),
35
        adminEmail          => C4::Context->preference("KohaAdminEmailAddress"),
35
        MaxCount            => C4::Context->preference("OAI-PMH:MaxCount"),
36
        MaxCount            => C4::Context->preference("OAI-PMH:MaxCount"),
36
        granularity         => 'YYYY-MM-DD',
37
        granularity         => 'YYYY-MM-DDThh:mm:ssZ',
37
        earliestDatestamp   => _get_earliest_datestamp() || '0001-01-01',
38
        earliestDatestamp   => _get_earliest_datestamp() || '0001-01-01T00:00:00Z',
38
        deletedRecord       => C4::Context->preference("OAI-PMH:DeletedRecord") || 'no',
39
        deletedRecord       => C4::Context->preference("OAI-PMH:DeletedRecord") || 'no',
39
    );
40
    );
40
41
Lines 53-59 sub new { Link Here
53
# will be returned and we will report the fallback 0001-01-01.
54
# will be returned and we will report the fallback 0001-01-01.
54
sub _get_earliest_datestamp {
55
sub _get_earliest_datestamp {
55
    my $dbh = C4::Context->dbh;
56
    my $dbh = C4::Context->dbh;
56
    my ( $earliest ) = $dbh->selectrow_array("SELECT DATE(MIN(timestamp)) AS earliest FROM biblio" );
57
    my ( $earliest ) = $dbh->selectrow_array("SELECT MIN(timestamp) AS earliest FROM biblio" );
57
    return $earliest
58
    return $earliest
58
}
59
}
59
60
(-)a/Koha/OAI/Server/ListBase.pm (+182 lines)
Line 0 Link Here
1
package Koha::OAI::Server::ListBase;
2
3
# Copyright The National Library of Finland, University of Helsinki 2016-2017
4
#
5
# This file is part of Koha.
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::OAI::Server::ListBase - OAI ListIdentifiers/ListRecords shared functionality
23
24
=head1 DESCRIPTION
25
26
Koha::OAI::Server::ListBase contains OAI-PMH functions shared by ListIdentifiers and ListRecords.
27
28
=cut
29
30
use Modern::Perl;
31
use C4::Biblio;
32
use HTTP::OAI;
33
use Koha::OAI::Server::ResumptionToken;
34
use Koha::OAI::Server::Record;
35
use Koha::OAI::Server::DeletedRecord;
36
use C4::OAI::Sets;
37
use MARC::File::XML;
38
39
sub GetRecords {
40
    my ($class, $self, $repository, $metadata, %args) = @_;
41
42
    my $token = new Koha::OAI::Server::ResumptionToken( %args );
43
    my $dbh = C4::Context->dbh;
44
    my $set;
45
    if ( defined $token->{'set'} ) {
46
        $set = GetOAISetBySpec($token->{'set'});
47
    }
48
    my $offset = $token->{offset};
49
    my $deleted = defined $token->{deleted} ? $token->{deleted} : 0;
50
    my $deleted_count = defined $token->{deleted_count} ? $token->{deleted_count} : 0;
51
    my $max = $repository->{koha_max_count};
52
    my $count = 0;
53
    my $format = $args{metadataPrefix} || $token->{metadata_prefix};
54
    my $include_items = $repository->items_included( $format );
55
56
    # Since creating a union of normal and deleted record tables would be a heavy
57
    # operation in a large database, build results in two stages:
58
    # first deleted records ($deleted == 1), then normal records ($deleted == 0)
59
    STAGELOOP:
60
    for ( ; $deleted >= 0; $deleted-- ) {
61
        my $table = $deleted ? 'deletedbiblioitems' : 'biblioitems';
62
        my $sql = "
63
            SELECT biblionumber
64
            FROM $table
65
            WHERE (timestamp >= ? AND timestamp <= ?)
66
        ";
67
        my @bind_params = ($token->{'from_arg'}, $token->{'until_arg'});
68
69
        if ($include_items) {
70
            $sql .= "
71
                OR biblionumber IN (SELECT biblionumber from deleteditems WHERE timestamp >= ? AND timestamp <= ?)
72
            ";
73
            push @bind_params, ($token->{'from_arg'}, $token->{'until_arg'});
74
            if (!$deleted) {
75
                $sql .= "
76
                    OR biblionumber IN (SELECT biblionumber from items WHERE timestamp >= ? AND timestamp <= ?)
77
                ";
78
                push @bind_params, ($token->{'from_arg'}, $token->{'until_arg'});
79
            }
80
        }
81
82
        $sql .= "
83
            ORDER BY biblionumber
84
        ";
85
86
        # Use a subquery for sets since it allows us to use an index in
87
        # biblioitems table and is quite a bit faster than a join.
88
        if (defined $set) {
89
            $sql = "
90
                SELECT bi.* FROM ($sql) bi
91
                  WHERE bi.biblionumber in (SELECT osb.biblionumber FROM oai_sets_biblios osb WHERE osb.set_id = ?)
92
            ";
93
            push @bind_params, $set->{'id'};
94
        }
95
96
        $sql .= "
97
            LIMIT " . ($max + 1) . "
98
            OFFSET " . ($offset - $deleted_count);
99
100
        my $sth = $dbh->prepare( $sql ) || die( 'Could not prepare statement: ' . $dbh->errstr );
101
102
        if ( $deleted ) {
103
            $sql = "
104
                SELECT MAX(timestamp)
105
                FROM (
106
                    SELECT timestamp FROM deletedbiblioitems WHERE biblionumber = ?
107
                    UNION
108
                    SELECT timestamp FROM deleteditems WHERE biblionumber = ?
109
                ) bis
110
            ";
111
        } else {
112
            $sql = "
113
                SELECT MAX(timestamp)
114
                FROM (
115
                    SELECT timestamp FROM biblioitems WHERE biblionumber = ?
116
                    UNION
117
                    SELECT timestamp FROM deleteditems WHERE biblionumber = ?
118
                    UNION
119
                    SELECT timestamp FROM items WHERE biblionumber = ?
120
                ) bi
121
            ";
122
        }
123
        my $record_sth = $dbh->prepare( $sql ) || die( 'Could not prepare statement: ' . $dbh->errstr );
124
125
        $sth->execute( @bind_params ) || die( 'Could not execute statement: ' . $sth->errstr );
126
        while ( my ($biblionumber) = $sth->fetchrow ) {
127
            $count++;
128
            if ( $count > $max ) {
129
                $self->resumptionToken(
130
                    new Koha::OAI::Server::ResumptionToken(
131
                        metadataPrefix  => $token->{metadata_prefix},
132
                        from            => $token->{from},
133
                        until           => $token->{until},
134
                        offset          => $token->{offset} + $max,
135
                        set             => $token->{set},
136
                        deleted         => $deleted,
137
                        deleted_count   => $deleted_count
138
                    )
139
                );
140
                last STAGELOOP;
141
            }
142
            my @params = $deleted ? ( $biblionumber, $biblionumber ) : ( $biblionumber, $biblionumber, $biblionumber );
143
            $record_sth->execute( @params ) || die( 'Could not execute statement: ' . $sth->errstr );
144
145
            my ($timestamp) = $record_sth->fetchrow;
146
147
            my $oai_sets = GetOAISetsBiblio($biblionumber);
148
            my @setSpecs;
149
            foreach ( @$oai_sets ) {
150
                push @setSpecs, $_->{spec};
151
            }
152
            if ( $metadata ) {
153
                my $marcxml = !$deleted ? $repository->get_biblio_marcxml($biblionumber, $format) : undef;
154
                if ( $marcxml ) {
155
                  $self->record( Koha::OAI::Server::Record->new(
156
                      $repository, $marcxml, $timestamp, \@setSpecs,
157
                      identifier      => $repository->{ koha_identifier } . ':' . $biblionumber,
158
                      metadataPrefix  => $token->{metadata_prefix}
159
                  ) );
160
                } else {
161
                  $self->record( Koha::OAI::Server::DeletedRecord->new(
162
                      $timestamp, \@setSpecs, identifier => $repository->{ koha_identifier } . ':' . $biblionumber
163
                  ) );
164
                }
165
            } else {
166
                $timestamp =~ s/ /T/;
167
                $timestamp .= 'Z';
168
                $self->identifier( new HTTP::OAI::Header(
169
                    identifier => $repository->{ koha_identifier} . ':' . $biblionumber,
170
                    datestamp  => $timestamp,
171
                    status     => $deleted ? 'deleted' : undef
172
                ) );
173
            }
174
        }
175
        # Store offset and deleted record count
176
        $offset += $count;
177
        $deleted_count = $offset if ($deleted);
178
    }
179
    return $count;
180
}
181
182
1;
(-)a/Koha/OAI/Server/ListIdentifiers.pm (-55 / +3 lines)
Lines 1-5 Link Here
1
# Copyright Tamil s.a.r.l. 2008-2015
1
# Copyright Tamil s.a.r.l. 2008-2015
2
# Copyright Biblibre 2008-2015
2
# Copyright Biblibre 2008-2015
3
# Copyright The National Library of Finland, University of Helsinki 2016
3
#
4
#
4
# This file is part of Koha.
5
# This file is part of Koha.
5
#
6
#
Lines 20-87 package Koha::OAI::Server::ListIdentifiers; Link Here
20
21
21
use Modern::Perl;
22
use Modern::Perl;
22
use HTTP::OAI;
23
use HTTP::OAI;
23
use C4::OAI::Sets;
24
25
use base ("HTTP::OAI::ListIdentifiers");
26
24
25
use base qw(HTTP::OAI::ListIdentifiers Koha::OAI::Server::ListBase);
27
26
28
sub new {
27
sub new {
29
    my ($class, $repository, %args) = @_;
28
    my ($class, $repository, %args) = @_;
30
29
31
    my $self = HTTP::OAI::ListIdentifiers->new(%args);
30
    my $self = HTTP::OAI::ListIdentifiers->new(%args);
32
31
33
    my $token = new Koha::OAI::Server::ResumptionToken( %args );
32
    my $count = $class->GetRecords($self, $repository, 0, %args);
34
    my $dbh = C4::Context->dbh;
35
    my $set;
36
    if(defined $token->{'set'}) {
37
        $set = GetOAISetBySpec($token->{'set'});
38
    }
39
    my $max = $repository->{koha_max_count};
40
    my $sql = "
41
        (SELECT biblioitems.biblionumber, biblioitems.timestamp
42
        FROM biblioitems
43
    ";
44
    $sql .= " JOIN oai_sets_biblios ON biblioitems.biblionumber = oai_sets_biblios.biblionumber " if defined $set;
45
    $sql .= " WHERE timestamp >= ? AND timestamp <= ? ";
46
    $sql .= " AND oai_sets_biblios.set_id = ? " if defined $set;
47
    $sql .= ") UNION
48
        (SELECT deletedbiblio.biblionumber, timestamp FROM deletedbiblio";
49
    $sql .= " JOIN oai_sets_biblios ON deletedbiblio.biblionumber = oai_sets_biblios.biblionumber " if defined $set;
50
    $sql .= " WHERE DATE(timestamp) >= ? AND DATE(timestamp) <= ? ";
51
    $sql .= " AND oai_sets_biblios.set_id = ? " if defined $set;
52
53
    $sql .= ") ORDER BY biblionumber
54
        LIMIT " . ($max+1) . "
55
        OFFSET $token->{offset}
56
    ";
57
    my $sth = $dbh->prepare( $sql );
58
    my @bind_params = ($token->{'from_arg'}, $token->{'until_arg'});
59
    push @bind_params, $set->{'id'} if defined $set;
60
    push @bind_params, ($token->{'from'}, $token->{'until'});
61
    push @bind_params, $set->{'id'} if defined $set;
62
    $sth->execute( @bind_params );
63
64
    my $count = 0;
65
    while ( my ($biblionumber, $timestamp) = $sth->fetchrow ) {
66
        $count++;
67
        if ( $count > $max ) {
68
            $self->resumptionToken(
69
                new Koha::OAI::Server::ResumptionToken(
70
                    metadataPrefix  => $token->{metadata_prefix},
71
                    from            => $token->{from},
72
                    until           => $token->{until},
73
                    offset          => $token->{offset} + $max,
74
                    set             => $token->{set}
75
                )
76
            );
77
            last;
78
        }
79
        $timestamp =~ s/ /T/, $timestamp .= 'Z';
80
        $self->identifier( new HTTP::OAI::Header(
81
            identifier => $repository->{ koha_identifier} . ':' . $biblionumber,
82
            datestamp  => $timestamp,
83
        ) );
84
    }
85
33
86
    # Return error if no results
34
    # Return error if no results
87
    unless ($count) {
35
    unless ($count) {
(-)a/Koha/OAI/Server/ListMetadataFormats.pm (-1 / +6 lines)
Lines 1-5 Link Here
1
# Copyright Tamil s.a.r.l. 2008-2015
1
# Copyright Tamil s.a.r.l. 2008-2015
2
# Copyright Biblibre 2008-2015
2
# Copyright Biblibre 2008-2015
3
# Copyright The National Library of Finland, University of Helsinki 2016
3
#
4
#
4
# This file is part of Koha.
5
# This file is part of Koha.
5
#
6
#
Lines 23-29 use HTTP::OAI; Link Here
23
24
24
use base ("HTTP::OAI::ListMetadataFormats");
25
use base ("HTTP::OAI::ListMetadataFormats");
25
26
26
27
sub new {
27
sub new {
28
    my ($class, $repository) = @_;
28
    my ($class, $repository) = @_;
29
29
Lines 45-50 sub new { Link Here
45
            metadataNamespace => 'http://www.openarchives.org/OAI/2.0/oai_dc/'
45
            metadataNamespace => 'http://www.openarchives.org/OAI/2.0/oai_dc/'
46
        ) );
46
        ) );
47
        $self->metadataFormat( HTTP::OAI::MetadataFormat->new(
47
        $self->metadataFormat( HTTP::OAI::MetadataFormat->new(
48
            metadataPrefix    => 'marc21',
49
            schema            => 'http://www.loc.gov/standards/marcxml/schema/MARC21slim.xsd',
50
            metadataNamespace => 'http://www.loc.gov/MARC21/slim http://www.loc.gov/standards/marcxml/schema/MARC21slim'
51
        ) );
52
        $self->metadataFormat( HTTP::OAI::MetadataFormat->new(
48
            metadataPrefix    => 'marcxml',
53
            metadataPrefix    => 'marcxml',
49
            schema            => 'http://www.loc.gov/standards/marcxml/schema/MARC21slim.xsd',
54
            schema            => 'http://www.loc.gov/standards/marcxml/schema/MARC21slim.xsd',
50
            metadataNamespace => 'http://www.loc.gov/MARC21/slim http://www.loc.gov/standards/marcxml/schema/MARC21slim'
55
            metadataNamespace => 'http://www.loc.gov/MARC21/slim http://www.loc.gov/standards/marcxml/schema/MARC21slim'
(-)a/Koha/OAI/Server/ListRecords.pm (-72 / +3 lines)
Lines 1-5 Link Here
1
# Copyright Tamil s.a.r.l. 2008-2015
1
# Copyright Tamil s.a.r.l. 2008-2015
2
# Copyright Biblibre 2008-2015
2
# Copyright Biblibre 2008-2015
3
# Copyright The National Library of Finland, University of Helsinki 2016
3
#
4
#
4
# This file is part of Koha.
5
# This file is part of Koha.
5
#
6
#
Lines 19-104 Link Here
19
package Koha::OAI::Server::ListRecords;
20
package Koha::OAI::Server::ListRecords;
20
21
21
use Modern::Perl;
22
use Modern::Perl;
22
use C4::Biblio;
23
use HTTP::OAI;
23
use HTTP::OAI;
24
use Koha::OAI::Server::ResumptionToken;
25
use Koha::OAI::Server::Record;
26
use Koha::OAI::Server::DeletedRecord;
27
use C4::OAI::Sets;
28
use MARC::File::XML;
29
30
use base ("HTTP::OAI::ListRecords");
31
24
25
use base qw(HTTP::OAI::ListRecords Koha::OAI::Server::ListBase);
32
26
33
sub new {
27
sub new {
34
    my ($class, $repository, %args) = @_;
28
    my ($class, $repository, %args) = @_;
35
29
36
    my $self = HTTP::OAI::ListRecords->new(%args);
30
    my $self = HTTP::OAI::ListRecords->new(%args);
37
31
38
    my $token = new Koha::OAI::Server::ResumptionToken( %args );
32
    my $count = $class->GetRecords($self, $repository, 1, %args);
39
    my $dbh = C4::Context->dbh;
40
    my $set;
41
    if(defined $token->{'set'}) {
42
        $set = GetOAISetBySpec($token->{'set'});
43
    }
44
    my $max = $repository->{koha_max_count};
45
    my $sql = "
46
        (SELECT biblioitems.biblionumber, biblioitems.timestamp
47
        FROM biblioitems
48
    ";
49
    $sql .= " JOIN oai_sets_biblios ON biblioitems.biblionumber = oai_sets_biblios.biblionumber " if defined $set;
50
    $sql .= " WHERE timestamp >= ? AND timestamp <= ? ";
51
    $sql .= " AND oai_sets_biblios.set_id = ? " if defined $set;
52
    $sql .= ") UNION
53
        (SELECT deletedbiblio.biblionumber, timestamp FROM deletedbiblio";
54
    $sql .= " JOIN oai_sets_biblios ON deletedbiblio.biblionumber = oai_sets_biblios.biblionumber " if defined $set;
55
    $sql .= " WHERE DATE(timestamp) >= ? AND DATE(timestamp) <= ? ";
56
    $sql .= " AND oai_sets_biblios.set_id = ? " if defined $set;
57
58
    $sql .= ") ORDER BY biblionumber
59
        LIMIT " . ($max + 1) . "
60
        OFFSET $token->{offset}
61
    ";
62
    my $sth = $dbh->prepare( $sql );
63
    my @bind_params = ($token->{'from_arg'}, $token->{'until_arg'});
64
    push @bind_params, $set->{'id'} if defined $set;
65
    push @bind_params, ($token->{'from'}, $token->{'until'});
66
    push @bind_params, $set->{'id'} if defined $set;
67
    $sth->execute( @bind_params );
68
69
    my $count = 0;
70
    my $format = $args{metadataPrefix} || $token->{metadata_prefix};
71
    while ( my ($biblionumber, $timestamp) = $sth->fetchrow ) {
72
        $count++;
73
        if ( $count > $max ) {
74
            $self->resumptionToken(
75
                new Koha::OAI::Server::ResumptionToken(
76
                    metadataPrefix  => $token->{metadata_prefix},
77
                    from            => $token->{from},
78
                    until           => $token->{until},
79
                    offset          => $token->{offset} + $max,
80
                    set             => $token->{set}
81
                )
82
            );
83
            last;
84
        }
85
        my $marcxml = $repository->get_biblio_marcxml($biblionumber, $format);
86
        my $oai_sets = GetOAISetsBiblio($biblionumber);
87
        my @setSpecs;
88
        foreach (@$oai_sets) {
89
            push @setSpecs, $_->{spec};
90
        }
91
        if ($marcxml) {
92
          $self->record( Koha::OAI::Server::Record->new(
93
              $repository, $marcxml, $timestamp, \@setSpecs,
94
              identifier      => $repository->{ koha_identifier } . ':' . $biblionumber,
95
              metadataPrefix  => $token->{metadata_prefix}
96
          ) );
97
        } else {
98
          $self->record( Koha::OAI::Server::DeletedRecord->new(
99
          $timestamp, \@setSpecs, identifier => $repository->{ koha_identifier } . ':' . $biblionumber ) );
100
        }
101
    }
102
33
103
    # Return error if no results
34
    # Return error if no results
104
    unless ($count) {
35
    unless ($count) {
(-)a/Koha/OAI/Server/Record.pm (-1 / +1 lines)
Lines 43-49 sub new { Link Here
43
    my $parser = XML::LibXML->new();
43
    my $parser = XML::LibXML->new();
44
    my $record_dom = $parser->parse_string( $marcxml );
44
    my $record_dom = $parser->parse_string( $marcxml );
45
    my $format =  $args{metadataPrefix};
45
    my $format =  $args{metadataPrefix};
46
    if ( $format ne 'marcxml' ) {
46
    if ( $format ne 'marc21' && $format ne 'marcxml' ) {
47
        my %args = (
47
        my %args = (
48
            OPACBaseURL => "'" . C4::Context->preference('OPACBaseURL') . "'"
48
            OPACBaseURL => "'" . C4::Context->preference('OPACBaseURL') . "'"
49
        );
49
        );
(-)a/Koha/OAI/Server/Repository.pm (-3 / +24 lines)
Lines 1-5 Link Here
1
# Copyright Tamil s.a.r.l. 2008-2015
1
# Copyright Tamil s.a.r.l. 2008-2015
2
# Copyright Biblibre 2008-2015
2
# Copyright Biblibre 2008-2015
3
# Copyright The National Library of Finland, University of Helsinki 2016
3
#
4
#
4
# This file is part of Koha.
5
# This file is part of Koha.
5
#
6
#
Lines 77-82 mode. A configuration file koha-oai.conf can look like that: Link Here
77
      metadataNamespace: http://veryspecial.tamil.fr/vs/format-pivot/1.1/vs
78
      metadataNamespace: http://veryspecial.tamil.fr/vs/format-pivot/1.1/vs
78
      schema: http://veryspecial.tamil.fr/vs/format-pivot/1.1/vs.xsd
79
      schema: http://veryspecial.tamil.fr/vs/format-pivot/1.1/vs.xsd
79
      xsl_file: /usr/local/koha/xslt/vs.xsl
80
      xsl_file: /usr/local/koha/xslt/vs.xsl
81
    marc21:
82
      metadataPrefix: marc21
83
      metadataNamespace: http://www.loc.gov/MARC21/slim http://www.loc.gov/standards/marcxml/schema/MARC21slim
84
      schema: http://www.loc.gov/MARC21/slim http://www.loc.gov/standards/marcxml/schema/MARC21slim.xsd
85
      include_items: 1
80
    marcxml:
86
    marcxml:
81
      metadataPrefix: marxml
87
      metadataPrefix: marxml
82
      metadataNamespace: http://www.loc.gov/MARC21/slim http://www.loc.gov/standards/marcxml/schema/MARC21slim
88
      metadataNamespace: http://www.loc.gov/MARC21/slim http://www.loc.gov/standards/marcxml/schema/MARC21slim
Lines 88-94 mode. A configuration file koha-oai.conf can look like that: Link Here
88
      schema: http://www.openarchives.org/OAI/2.0/oai_dc.xsd
94
      schema: http://www.openarchives.org/OAI/2.0/oai_dc.xsd
89
      xsl_file: /usr/local/koha/koha-tmpl/intranet-tmpl/xslt/UNIMARCslim2OAIDC.xsl
95
      xsl_file: /usr/local/koha/koha-tmpl/intranet-tmpl/xslt/UNIMARCslim2OAIDC.xsl
90
96
91
Note de 'include_items' parameter which is the only mean to return item-level info.
97
Note the 'include_items' parameter which is the only mean to return item-level info.
92
98
93
=cut
99
=cut
94
100
Lines 99-105 sub new { Link Here
99
105
100
    $self->{ koha_identifier      } = C4::Context->preference("OAI-PMH:archiveID");
106
    $self->{ koha_identifier      } = C4::Context->preference("OAI-PMH:archiveID");
101
    $self->{ koha_max_count       } = C4::Context->preference("OAI-PMH:MaxCount");
107
    $self->{ koha_max_count       } = C4::Context->preference("OAI-PMH:MaxCount");
102
    $self->{ koha_metadata_format } = ['oai_dc', 'marcxml'];
108
    $self->{ koha_metadata_format } = ['oai_dc', 'marc21', 'marcxml'];
103
    $self->{ koha_stylesheet      } = { }; # Build when needed
109
    $self->{ koha_stylesheet      } = { }; # Build when needed
104
110
105
    # Load configuration file if defined in OAI-PMH:ConfFile syspref
111
    # Load configuration file if defined in OAI-PMH:ConfFile syspref
Lines 109-118 sub new { Link Here
109
        $self->{ koha_metadata_format } =  \@formats;
115
        $self->{ koha_metadata_format } =  \@formats;
110
    }
116
    }
111
117
118
    # OAI-PMH handles dates in UTC, so do that on the database level to avoid need for
119
    # any conversions
120
    C4::Context->dbh->prepare("SET time_zone='+00:00'")->execute();
121
112
    # Check for grammatical errors in the request
122
    # Check for grammatical errors in the request
113
    my @errs = validate_request( CGI::Vars() );
123
    my @errs = validate_request( CGI::Vars() );
114
124
115
    # Is metadataPrefix supported by the respository?
125
    # Is metadataPrefix supported by the repository?
116
    my $mdp = param('metadataPrefix') || '';
126
    my $mdp = param('metadataPrefix') || '';
117
    if ( $mdp && !grep { $_ eq $mdp } @{$self->{ koha_metadata_format }} ) {
127
    if ( $mdp && !grep { $_ eq $mdp } @{$self->{ koha_metadata_format }} ) {
118
        push @errs, new HTTP::OAI::Error(
128
        push @errs, new HTTP::OAI::Error(
Lines 166-171 sub stylesheet { Link Here
166
                         '/prog/en/xslt/' .
176
                         '/prog/en/xslt/' .
167
                         C4::Context->preference('marcflavour') .
177
                         C4::Context->preference('marcflavour') .
168
                         'slim2OAIDC.xsl' );
178
                         'slim2OAIDC.xsl' );
179
        $xsl_file || die( "No stylesheet found for $format" );
169
        my $parser = XML::LibXML->new();
180
        my $parser = XML::LibXML->new();
170
        my $xslt = XML::LibXSLT->new();
181
        my $xslt = XML::LibXSLT->new();
171
        my $style_doc = $parser->parse_file( $xsl_file );
182
        my $style_doc = $parser->parse_file( $xsl_file );
Lines 176-179 sub stylesheet { Link Here
176
    return $stylesheet;
187
    return $stylesheet;
177
}
188
}
178
189
190
191
sub items_included {
192
    my ( $self, $format ) = @_;
193
194
    if ( my $conf = $self->{ conf } ) {
195
        return $conf->{ format }->{ $format }->{ include_items };
196
    }
197
    return 0;
198
}
199
179
1;
200
1;
(-)a/Koha/OAI/Server/ResumptionToken.pm (-5 / +10 lines)
Lines 1-5 Link Here
1
# Copyright Tamil s.a.r.l. 2008-2015
1
# Copyright Tamil s.a.r.l. 2008-2015
2
# Copyright Biblibre 2008-2015
2
# Copyright Biblibre 2008-2015
3
# Copyright The National Library of Finland, University of Helsinki 2016-2017
3
#
4
#
4
# This file is part of Koha.
5
# This file is part of Koha.
5
#
6
#
Lines 31-46 use base ("HTTP::OAI::ResumptionToken"); Link Here
31
# - from
32
# - from
32
# - until
33
# - until
33
# - offset
34
# - offset
34
35
# - deleted
35
36
36
sub new {
37
sub new {
37
    my ($class, %args) = @_;
38
    my ($class, %args) = @_;
38
39
39
    my $self = $class->SUPER::new(%args);
40
    my $self = $class->SUPER::new(%args);
40
41
41
    my ($metadata_prefix, $offset, $from, $until, $set);
42
    my ($metadata_prefix, $offset, $from, $until, $set, $deleted, $deleted_count);
42
    if ( $args{ resumptionToken } ) {
43
    if ( $args{ resumptionToken } ) {
43
        ($metadata_prefix, $offset, $from, $until, $set)
44
        ($metadata_prefix, $offset, $from, $until, $set, $deleted, $deleted_count)
44
            = split( '/', $args{resumptionToken} );
45
            = split( '/', $args{resumptionToken} );
45
    }
46
    }
46
    else {
47
    else {
Lines 55-61 sub new { Link Here
55
        $from .= 'T00:00:00Z' if length($from) == 10;
56
        $from .= 'T00:00:00Z' if length($from) == 10;
56
        $until .= 'T23:59:59Z' if length($until) == 10;
57
        $until .= 'T23:59:59Z' if length($until) == 10;
57
        $offset = $args{ offset } || 0;
58
        $offset = $args{ offset } || 0;
58
        $set = $args{set} || '';
59
        $set = $args{ set } || '';
60
        $deleted = defined $args{ deleted } ? $args{ deleted } : 1;
61
        $deleted_count = defined $args{ deleted_count } ? $args{ deleted_count } : 0;
59
    }
62
    }
60
63
61
    $self->{ metadata_prefix } = $metadata_prefix;
64
    $self->{ metadata_prefix } = $metadata_prefix;
Lines 65-73 sub new { Link Here
65
    $self->{ set             } = $set;
68
    $self->{ set             } = $set;
66
    $self->{ from_arg        } = _strip_UTC_designators($from);
69
    $self->{ from_arg        } = _strip_UTC_designators($from);
67
    $self->{ until_arg       } = _strip_UTC_designators($until);
70
    $self->{ until_arg       } = _strip_UTC_designators($until);
71
    $self->{ deleted         } = $deleted;
72
    $self->{ deleted_count   } = $deleted_count;
68
73
69
    $self->resumptionToken(
74
    $self->resumptionToken(
70
        join( '/', $metadata_prefix, $offset, $from, $until, $set ) );
75
        join( '/', $metadata_prefix, $offset, $from, $until, $set, $deleted, $deleted_count ) );
71
    $self->cursor( $offset );
76
    $self->cursor( $offset );
72
77
73
    return $self;
78
    return $self;
(-)a/installer/data/mysql/atomicupdate/Bug15108-OAI-PMH_provider_improvements.perl (+11 lines)
Line 0 Link Here
1
$DBversion = 'XXX';  # will be replaced by the RM
2
if( CheckVersion( $DBversion ) ) {
3
    $dbh->do("ALTER TABLE biblioitems ADD KEY `timestamp` (`timestamp`);");
4
    $dbh->do("ALTER TABLE deletedbiblioitems ADD KEY `timestamp` (`timestamp`);");
5
    $dbh->do("ALTER TABLE items ADD KEY `timestamp` (`timestamp`);");
6
    $dbh->do("ALTER TABLE deleteditems ADD KEY `timestamp` (`timestamp`);");
7
8
    # Always end with this (adjust the bug info)
9
    SetVersion( $DBversion );
10
    print "Upgrade to $DBversion done (Bug 15108 - OAI-PMH provider improvements)\n";
11
}
(-)a/installer/data/mysql/kohastructure.sql (-10 / +14 lines)
Lines 200-205 CREATE TABLE `biblioitems` ( -- information related to bibliographic records in Link Here
200
  KEY `isbn` (`isbn`(255)),
200
  KEY `isbn` (`isbn`(255)),
201
  KEY `issn` (`issn`(255)),
201
  KEY `issn` (`issn`(255)),
202
  KEY `publishercode` (`publishercode`),
202
  KEY `publishercode` (`publishercode`),
203
  KEY `timestamp` (`timestamp`),
203
  CONSTRAINT `biblioitems_ibfk_1` FOREIGN KEY (`biblionumber`) REFERENCES `biblio` (`biblionumber`) ON DELETE CASCADE ON UPDATE CASCADE
204
  CONSTRAINT `biblioitems_ibfk_1` FOREIGN KEY (`biblionumber`) REFERENCES `biblio` (`biblionumber`) ON DELETE CASCADE ON UPDATE CASCADE
204
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
205
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
205
206
Lines 551-557 CREATE TABLE `deletedbiblioitems` ( -- information about bibliographic records t Link Here
551
  KEY `bibnoidx` (`biblionumber`),
552
  KEY `bibnoidx` (`biblionumber`),
552
  KEY `itemtype_idx` (`itemtype`),
553
  KEY `itemtype_idx` (`itemtype`),
553
  KEY `isbn` (`isbn`(255)),
554
  KEY `isbn` (`isbn`(255)),
554
  KEY `publishercode` (`publishercode`)
555
  KEY `publishercode` (`publishercode`),
556
  KEY `timestamp` (`timestamp`)
555
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
557
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
556
558
557
--
559
--
Lines 693-699 CREATE TABLE `deleteditems` ( Link Here
693
  KEY `delitembibnoidx` (`biblionumber`),
695
  KEY `delitembibnoidx` (`biblionumber`),
694
  KEY `delhomebranch` (`homebranch`),
696
  KEY `delhomebranch` (`homebranch`),
695
  KEY `delholdingbranch` (`holdingbranch`),
697
  KEY `delholdingbranch` (`holdingbranch`),
696
  KEY `itype_idx` (`itype`)
698
  KEY `itype_idx` (`itype`),
699
  KEY `timestamp` (`timestamp`)
697
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
700
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
698
701
699
--
702
--
Lines 898-904 CREATE TABLE `refund_lost_item_fee_rules` ( -- refund lost item fee rules tbale Link Here
898
--
901
--
899
902
900
DROP TABLE IF EXISTS `items`;
903
DROP TABLE IF EXISTS `items`;
901
CREATE TABLE `items` ( -- holdings/item information 
904
CREATE TABLE `items` ( -- holdings/item information
902
  `itemnumber` int(11) NOT NULL auto_increment, -- primary key and unique identifier added by Koha
905
  `itemnumber` int(11) NOT NULL auto_increment, -- primary key and unique identifier added by Koha
903
  `biblionumber` int(11) NOT NULL default 0, -- foreign key from biblio table used to link this item to the right bib record
906
  `biblionumber` int(11) NOT NULL default 0, -- foreign key from biblio table used to link this item to the right bib record
904
  `biblioitemnumber` int(11) NOT NULL default 0, -- foreign key from the biblioitems table to link to item to additional information
907
  `biblioitemnumber` int(11) NOT NULL default 0, -- foreign key from the biblioitems table to link to item to additional information
Lines 954-959 CREATE TABLE `items` ( -- holdings/item information Link Here
954
  KEY `items_location` (`location`),
957
  KEY `items_location` (`location`),
955
  KEY `items_ccode` (`ccode`),
958
  KEY `items_ccode` (`ccode`),
956
  KEY `itype_idx` (`itype`),
959
  KEY `itype_idx` (`itype`),
960
  KEY `timestamp` (`timestamp`),
957
  CONSTRAINT `items_ibfk_1` FOREIGN KEY (`biblioitemnumber`) REFERENCES `biblioitems` (`biblioitemnumber`) ON DELETE CASCADE ON UPDATE CASCADE,
961
  CONSTRAINT `items_ibfk_1` FOREIGN KEY (`biblioitemnumber`) REFERENCES `biblioitems` (`biblioitemnumber`) ON DELETE CASCADE ON UPDATE CASCADE,
958
  CONSTRAINT `items_ibfk_2` FOREIGN KEY (`homebranch`) REFERENCES `branches` (`branchcode`) ON UPDATE CASCADE,
962
  CONSTRAINT `items_ibfk_2` FOREIGN KEY (`homebranch`) REFERENCES `branches` (`branchcode`) ON UPDATE CASCADE,
959
  CONSTRAINT `items_ibfk_3` FOREIGN KEY (`holdingbranch`) REFERENCES `branches` (`branchcode`) ON UPDATE CASCADE,
963
  CONSTRAINT `items_ibfk_3` FOREIGN KEY (`holdingbranch`) REFERENCES `branches` (`branchcode`) ON UPDATE CASCADE,
Lines 2239-2245 CREATE TABLE `userflags` ( Link Here
2239
--
2243
--
2240
2244
2241
DROP TABLE IF EXISTS `virtualshelves`;
2245
DROP TABLE IF EXISTS `virtualshelves`;
2242
CREATE TABLE `virtualshelves` ( -- information about lists (or virtual shelves) 
2246
CREATE TABLE `virtualshelves` ( -- information about lists (or virtual shelves)
2243
  `shelfnumber` int(11) NOT NULL auto_increment, -- unique identifier assigned by Koha
2247
  `shelfnumber` int(11) NOT NULL auto_increment, -- unique identifier assigned by Koha
2244
  `shelfname` varchar(255) default NULL, -- name of the list
2248
  `shelfname` varchar(255) default NULL, -- name of the list
2245
  `owner` int default NULL, -- foreign key linking to the borrowers table (using borrowernumber) for the creator of this list (changed from varchar(80) to int)
2249
  `owner` int default NULL, -- foreign key linking to the borrowers table (using borrowernumber) for the creator of this list (changed from varchar(80) to int)
Lines 2434-2445 CREATE TABLE `permissions` ( Link Here
2434
2438
2435
DROP TABLE IF EXISTS `serialitems`;
2439
DROP TABLE IF EXISTS `serialitems`;
2436
CREATE TABLE `serialitems` (
2440
CREATE TABLE `serialitems` (
2437
	`itemnumber` int(11) NOT NULL,
2441
  `itemnumber` int(11) NOT NULL,
2438
	`serialid` int(11) NOT NULL,
2442
  `serialid` int(11) NOT NULL,
2439
	UNIQUE KEY `serialitemsidx` (`itemnumber`),
2443
  UNIQUE KEY `serialitemsidx` (`itemnumber`),
2440
	KEY `serialitems_sfk_1` (`serialid`),
2444
  KEY `serialitems_sfk_1` (`serialid`),
2441
	CONSTRAINT `serialitems_sfk_1` FOREIGN KEY (`serialid`) REFERENCES `serial` (`serialid`) ON DELETE CASCADE ON UPDATE CASCADE,
2445
  CONSTRAINT `serialitems_sfk_1` FOREIGN KEY (`serialid`) REFERENCES `serial` (`serialid`) ON DELETE CASCADE ON UPDATE CASCADE,
2442
	CONSTRAINT `serialitems_sfk_2` FOREIGN KEY (`itemnumber`) REFERENCES `items` (`itemnumber`) ON DELETE CASCADE ON UPDATE CASCADE
2446
  CONSTRAINT `serialitems_sfk_2` FOREIGN KEY (`itemnumber`) REFERENCES `items` (`itemnumber`) ON DELETE CASCADE ON UPDATE CASCADE
2443
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
2447
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
2444
2448
2445
--
2449
--
(-)a/koha-tmpl/opac-tmpl/xslt/OAI.xslt (-2 / +2 lines)
Lines 531-539 Link Here
531
                        <xsl:value-of select="$total" />
531
                        <xsl:value-of select="$total" />
532
                    </xsl:when>
532
                    </xsl:when>
533
                    <xsl:otherwise>
533
                    <xsl:otherwise>
534
                        <xsl:value-of select="$cursor * $count" />
534
                        <xsl:value-of select="$cursor + 1 - $count" />
535
                        -
535
                        -
536
                        <xsl:value-of select="($cursor+1) * $count" />
536
                        <xsl:value-of select="$cursor" />
537
                    </xsl:otherwise>
537
                    </xsl:otherwise>
538
                </xsl:choose>
538
                </xsl:choose>
539
            </xsl:when>
539
            </xsl:when>
(-)a/t/db_dependent/OAI/Server.t (-56 / +272 lines)
Lines 1-6 Link Here
1
#!/usr/bin/perl
1
#!/usr/bin/perl
2
2
3
# Copyright Tamil s.a.r.l. 2015
3
# Copyright Tamil s.a.r.l. 2016
4
#
4
#
5
# This file is part of Koha.
5
# This file is part of Koha.
6
#
6
#
Lines 17-39 Link Here
17
# You should have received a copy of the GNU General Public License
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>.
18
# along with Koha; if not, see <http://www.gnu.org/licenses>.
19
19
20
21
use Modern::Perl;
20
use Modern::Perl;
22
use C4::Context;
21
23
use C4::Biblio;
22
use Test::More tests => 28;
24
use Test::More tests => 13;
23
use DateTime;
25
use Test::MockModule;
24
use Test::MockModule;
26
use Test::Warn;
25
use Test::Warn;
27
use DateTime;
28
use XML::Simple;
26
use XML::Simple;
27
use YAML;
28
29
use t::lib::Mocks;
29
use t::lib::Mocks;
30
30
31
use C4::Biblio;
32
use C4::Context;
33
use Koha::Database;
31
34
32
BEGIN {
35
BEGIN {
33
    use_ok('Koha::OAI::Server::DeletedRecord');
36
    use_ok('Koha::OAI::Server::DeletedRecord');
34
    use_ok('Koha::OAI::Server::Description');
37
    use_ok('Koha::OAI::Server::Description');
35
    use_ok('Koha::OAI::Server::GetRecord');
38
    use_ok('Koha::OAI::Server::GetRecord');
36
    use_ok('Koha::OAI::Server::Identify');
39
    use_ok('Koha::OAI::Server::Identify');
40
    use_ok('Koha::OAI::Server::ListBase');
37
    use_ok('Koha::OAI::Server::ListIdentifiers');
41
    use_ok('Koha::OAI::Server::ListIdentifiers');
38
    use_ok('Koha::OAI::Server::ListMetadataFormats');
42
    use_ok('Koha::OAI::Server::ListMetadataFormats');
39
    use_ok('Koha::OAI::Server::ListRecords');
43
    use_ok('Koha::OAI::Server::ListRecords');
Lines 43-94 BEGIN { Link Here
43
    use_ok('Koha::OAI::Server::ResumptionToken');
47
    use_ok('Koha::OAI::Server::ResumptionToken');
44
}
48
}
45
49
50
use constant NUMBER_OF_MARC_RECORDS => 10;
46
51
47
# Mocked CGI module in order to be able to send CGI parameters to OAI Server
52
# Mocked CGI module in order to be able to send CGI parameters to OAI Server
48
my %param;
53
my %param;
49
my $module = Test::MockModule->new('CGI');
54
my $module = Test::MockModule->new('CGI');
50
$module->mock('Vars', sub { %param; });
55
$module->mock('Vars', sub { %param; });
51
56
57
my $schema = Koha::Database->schema;
58
$schema->storage->txn_begin;
52
my $dbh = C4::Context->dbh;
59
my $dbh = C4::Context->dbh;
53
$dbh->{AutoCommit} = 0;
60
54
$dbh->{RaiseError} = 1;
61
$dbh->do("SET time_zone='+00:00'");
55
$dbh->do('DELETE FROM issues');
56
$dbh->do('DELETE FROM biblio');
62
$dbh->do('DELETE FROM biblio');
57
$dbh->do('DELETE FROM biblioitems');
63
$dbh->do('DELETE FROM deletedbiblio');
58
$dbh->do('DELETE FROM items');
64
$dbh->do('DELETE FROM deletedbiblioitems');
65
$dbh->do('DELETE FROM deleteditems');
59
66
60
# Add 10 biblio records
67
my $date_added = DateTime->now() . 'Z';
61
my @bibs = map {
68
my $date_to = substr($date_added, 0, 10) . 'T23:59:59Z';
69
my (@header, @marcxml, @oaidc);
70
my $sth = $dbh->prepare('SELECT timestamp FROM biblioitems WHERE biblionumber=?');
71
72
# Add biblio records
73
foreach my $index ( 0 .. NUMBER_OF_MARC_RECORDS - 1 ) {
62
    my $record = MARC::Record->new();
74
    my $record = MARC::Record->new();
63
    $record->append_fields( MARC::Field->new('245', '', '', 'a' => "Title $_" ) );
75
    $record->append_fields( MARC::Field->new('245', '', '', 'a' => "Title $index" ) );
64
    my ($biblionumber) = AddBiblio($record, '');
76
    my ($biblionumber) = AddBiblio($record, '');
65
    $biblionumber;
77
    $sth->execute($biblionumber);
66
} (1..10);
78
    my $timestamp = $sth->fetchrow_array . 'Z';
67
79
    $timestamp =~ s/ /T/;
68
t::lib::Mocks::mock_preference('LibraryName', 'My Library');
80
    $timestamp = manipulate_timestamp( $index, $biblionumber, $timestamp );
69
t::lib::Mocks::mock_preference('OAI::PMH', 1);
81
    $record = GetMarcBiblio($biblionumber);
70
t::lib::Mocks::mock_preference('OAI-PMH:archiveID', 'TEST');
82
    $record = XMLin($record->as_xml_record);
71
t::lib::Mocks::mock_preference('OAI-PMH:ConfFile', '' );
83
    push @header, { datestamp => $timestamp, identifier => "TEST:$biblionumber" };
72
t::lib::Mocks::mock_preference('OAI-PMH:MaxCount', 3);
84
    push @oaidc, {
73
t::lib::Mocks::mock_preference('OAI-PMH:DeletedRecord', 'persistent');
85
        header => $header[$index],
74
86
        metadata => {
75
%param = ( verb => 'ListMetadataFormats' );
87
            'oai_dc:dc' => {
76
my $response;
88
                'dc:title' => "Title $index",
77
my $get_response = sub {
89
                'dc:language' => {},
78
    my $stdout;
90
                'dc:type' => {},
79
    local *STDOUT;
91
                'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance',
80
    open STDOUT, '>', \$stdout;
92
                'xmlns:oai_dc' => 'http://www.openarchives.org/OAI/2.0/oai_dc/',
81
    Koha::OAI::Server::Repository->new();
93
                'xmlns:dc' => 'http://purl.org/dc/elements/1.1/',
82
    $response = XMLin($stdout);
94
                'xsi:schemaLocation' => 'http://www.openarchives.org/OAI/2.0/oai_dc/ http://www.openarchives.org/OAI/2.0/oai_dc.xsd',
95
            },
96
        },
97
    };
98
    push @marcxml, {
99
        header => $header[$index],
100
        metadata => {
101
            record => $record,
102
        },
103
    };
104
}
105
106
my $syspref = {
107
    'LibraryName'           => 'My Library',
108
    'OAI::PMH'              => 1,
109
    'OAI-PMH:archiveID'     => 'TEST',
110
    'OAI-PMH:ConfFile'      => '',
111
    'OAI-PMH:MaxCount'      => 3,
112
    'OAI-PMH:DeletedRecord' => 'persistent',
83
};
113
};
84
$get_response->();
114
while ( my ($name, $value) = each %$syspref ) {
85
my $now = DateTime->now . 'Z';
115
    t::lib::Mocks::mock_preference( $name => $value );
86
my $expected = {
116
}
87
    request => 'http://localhost',
117
88
    responseDate => $now,
118
sub test_query {
89
    xmlns => 'http://www.openarchives.org/OAI/2.0/',
119
    my ($test, $param, $expected) = @_;
90
    'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance',
120
91
    'xsi:schemaLocation' => 'http://www.openarchives.org/OAI/2.0/ http://www.openarchives.org/OAI/2.0/OAI-PMH.xsd',
121
    %param = %$param;
122
    my %full_expected = (
123
        %$expected,
124
        (
125
            request      => 'http://localhost',
126
            responseDate => DateTime->now . 'Z',
127
            xmlns        => 'http://www.openarchives.org/OAI/2.0/',
128
            'xmlns:xsi'  => 'http://www.w3.org/2001/XMLSchema-instance',
129
            'xsi:schemaLocation' => 'http://www.openarchives.org/OAI/2.0/ http://www.openarchives.org/OAI/2.0/OAI-PMH.xsd',
130
        )
131
    );
132
133
    my $response;
134
    {
135
        my $stdout;
136
        local *STDOUT;
137
        open STDOUT, '>', \$stdout;
138
        Koha::OAI::Server::Repository->new();
139
        $response = XMLin($stdout);
140
    }
141
142
    unless (is_deeply($response, \%full_expected, $test)) {
143
        diag
144
            "PARAM:" . Dump($param) .
145
            "EXPECTED:" . Dump(\%full_expected) .
146
            "RESPONSE:" . Dump($response);
147
    }
148
}
149
150
test_query('ListMetadataFormats', {verb => 'ListMetadataFormats'}, {
92
    ListMetadataFormats => {
151
    ListMetadataFormats => {
93
        metadataFormat => [
152
        metadataFormat => [
94
            {
153
            {
Lines 98-125 my $expected = { Link Here
98
            },
157
            },
99
            {
158
            {
100
                metadataNamespace => 'http://www.loc.gov/MARC21/slim http://www.loc.gov/standards/marcxml/schema/MARC21slim',
159
                metadataNamespace => 'http://www.loc.gov/MARC21/slim http://www.loc.gov/standards/marcxml/schema/MARC21slim',
160
                metadataPrefix => 'marc21',
161
                schema => 'http://www.loc.gov/standards/marcxml/schema/MARC21slim.xsd',
162
            },
163
            {
164
                metadataNamespace => 'http://www.loc.gov/MARC21/slim http://www.loc.gov/standards/marcxml/schema/MARC21slim',
101
                metadataPrefix => 'marcxml',
165
                metadataPrefix => 'marcxml',
102
                schema => 'http://www.loc.gov/standards/marcxml/schema/MARC21slim.xsd',
166
                schema => 'http://www.loc.gov/standards/marcxml/schema/MARC21slim.xsd',
103
            },
167
            },
104
        ],
168
        ],
105
    },
169
    },
106
};
170
});
107
is_deeply($response, $expected, "ListMetadataFormats");
171
108
172
test_query('ListIdentifiers without metadataPrefix', {verb => 'ListIdentifiers'}, {
109
%param = ( verb => 'ListIdentifiers' );
110
$get_response->();
111
$now = DateTime->now . 'Z';
112
$expected = {
113
    request => 'http://localhost',
114
    responseDate => $now,
115
    xmlns => 'http://www.openarchives.org/OAI/2.0/',
116
    'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance',
117
    'xsi:schemaLocation' => 'http://www.openarchives.org/OAI/2.0/ http://www.openarchives.org/OAI/2.0/OAI-PMH.xsd',
118
    error => {
173
    error => {
119
        code => 'badArgument',
174
        code => 'badArgument',
120
        content => "Required argument 'metadataPrefix' was undefined",
175
        content => "Required argument 'metadataPrefix' was undefined",
121
    },
176
    },
122
};
177
});
123
is_deeply($response, $expected, "ListIdentifiers without metadaPrefix argument");
178
179
test_query('ListIdentifiers', {verb => 'ListIdentifiers', metadataPrefix => 'marcxml'}, {
180
    ListIdentifiers => {
181
        header => [ @header[0..2] ],
182
        resumptionToken => {
183
            content => "marcxml/3/1970-01-01T00:00:00Z/$date_to//0/0",
184
            cursor  => 3,
185
        },
186
    },
187
});
188
189
test_query('ListIdentifiers', {verb => 'ListIdentifiers', metadataPrefix => 'marcxml'}, {
190
    ListIdentifiers => {
191
        header => [ @header[0..2] ],
192
        resumptionToken => {
193
            content => "marcxml/3/1970-01-01T00:00:00Z/$date_to//0/0",
194
            cursor  => 3,
195
        },
196
    },
197
});
124
198
125
$dbh->rollback;
199
test_query(
200
    'ListIdentifiers with resumptionToken 1',
201
    { verb => 'ListIdentifiers', resumptionToken => "marcxml/3/1970-01-01T00:00:00Z/$date_to//0/0" },
202
    {
203
        ListIdentifiers => {
204
            header => [ @header[3..5] ],
205
            resumptionToken => {
206
              content => "marcxml/6/1970-01-01T00:00:00Z/$date_to//0/0",
207
              cursor  => 6,
208
            },
209
          },
210
    },
211
);
212
213
test_query(
214
    'ListIdentifiers with resumptionToken 2',
215
    { verb => 'ListIdentifiers', resumptionToken => "marcxml/6/1970-01-01T00:00:00Z/$date_to//0/0" },
216
    {
217
        ListIdentifiers => {
218
            header => [ @header[6..8] ],
219
            resumptionToken => {
220
              content => "marcxml/9/1970-01-01T00:00:00Z/$date_to//0/0",
221
              cursor  => 9,
222
            },
223
          },
224
    },
225
);
226
227
test_query(
228
    'ListIdentifiers with resumptionToken 3, response without resumption',
229
    { verb => 'ListIdentifiers', resumptionToken => "marcxml/9/1970-01-01T00:00:00Z/$date_to//0/0" },
230
    {
231
        ListIdentifiers => {
232
            header => $header[9],
233
          },
234
    },
235
);
236
237
test_query('ListRecords marcxml without metadataPrefix', {verb => 'ListRecords'}, {
238
    error => {
239
        code => 'badArgument',
240
        content => "Required argument 'metadataPrefix' was undefined",
241
    },
242
});
243
244
test_query('ListRecords marcxml', {verb => 'ListRecords', metadataPrefix => 'marcxml'}, {
245
    ListRecords => {
246
        record => [ @marcxml[0..2] ],
247
        resumptionToken => {
248
          content => "marcxml/3/1970-01-01T00:00:00Z/$date_to//0/0",
249
          cursor  => 3,
250
        },
251
    },
252
});
253
254
test_query(
255
    'ListRecords marcxml with resumptionToken 1',
256
    { verb => 'ListRecords', resumptionToken => "marcxml/3/1970-01-01T00:00:00Z/$date_to//0/0" },
257
    { ListRecords => {
258
        record => [ @marcxml[3..5] ],
259
        resumptionToken => {
260
          content => "marcxml/6/1970-01-01T00:00:00Z/$date_to//0/0",
261
          cursor  => 6,
262
        },
263
    },
264
});
265
266
test_query(
267
    'ListRecords marcxml with resumptionToken 2',
268
    { verb => 'ListRecords', resumptionToken => "marcxml/6/1970-01-01T00:00:00Z/$date_to//0/0" },
269
    { ListRecords => {
270
        record => [ @marcxml[6..8] ],
271
        resumptionToken => {
272
          content => "marcxml/9/1970-01-01T00:00:00Z/$date_to//0/0",
273
          cursor  => 9,
274
        },
275
    },
276
});
277
278
# Last record, so no resumption token
279
test_query(
280
    'ListRecords marcxml with resumptionToken 3, response without resumption',
281
    { verb => 'ListRecords', resumptionToken => "marcxml/9/1970-01-01T00:00:00Z/$date_to//0/0" },
282
    { ListRecords => {
283
        record => $marcxml[9],
284
    },
285
});
286
287
test_query('ListRecords oai_dc', {verb => 'ListRecords', metadataPrefix => 'oai_dc'}, {
288
    ListRecords => {
289
        record => [ @oaidc[0..2] ],
290
        resumptionToken => {
291
          content => "oai_dc/3/1970-01-01T00:00:00Z/$date_to//0/0",
292
          cursor  => 3,
293
        },
294
    },
295
});
296
297
test_query(
298
    'ListRecords oai_dc with resumptionToken 1',
299
    { verb => 'ListRecords', resumptionToken => "oai_dc/3/1970-01-01T00:00:00Z/$date_to//0/0" },
300
    { ListRecords => {
301
        record => [ @oaidc[3..5] ],
302
        resumptionToken => {
303
          content => "oai_dc/6/1970-01-01T00:00:00Z/$date_to//0/0",
304
          cursor  => 6,
305
        },
306
    },
307
});
308
309
test_query(
310
    'ListRecords oai_dc with resumptionToken 2',
311
    { verb => 'ListRecords', resumptionToken => "oai_dc/6/1970-01-01T00:00:00Z/$date_to//0/0" },
312
    { ListRecords => {
313
        record => [ @oaidc[6..8] ],
314
        resumptionToken => {
315
          content => "oai_dc/9/1970-01-01T00:00:00Z/$date_to//0/0",
316
          cursor  => 9,
317
        },
318
    },
319
});
320
321
# Last record, so no resumption token
322
test_query(
323
    'ListRecords oai_dc with resumptionToken 3, response without resumption',
324
    { verb => 'ListRecords', resumptionToken => "oai_dc/9/1970-01-01T00:00:00Z/$date_to//0/0" },
325
    { ListRecords => {
326
        record => $oaidc[9],
327
    },
328
});
329
330
$schema->storage->txn_rollback;
331
332
sub manipulate_timestamp {
333
# This eliminates waiting a few seconds in order to get a higher timestamp
334
# Works only for 60 records..
335
    my ( $index, $bibno, $timestamp ) = @_;
336
    return $timestamp if $timestamp !~ /\d{2}Z/;
337
    my $secs = sprintf( "%02d", $index );
338
    $timestamp =~ s/\d{2}Z/${secs}Z/;
339
    $dbh->do("UPDATE biblioitems SET timestamp=? WHERE biblionumber=?", undef,
340
        ( $timestamp, $bibno ));
341
    return $timestamp;
342
}
126
- 

Return to bug 15108