Bug 17600: Standardize our EXPORT_OK
[srvgit] / Koha / OAI / Server / ListBase.pm
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 HTTP::OAI;
32 use Koha::OAI::Server::ResumptionToken;
33 use Koha::OAI::Server::Record;
34 use Koha::OAI::Server::DeletedRecord;
35 use C4::OAI::Sets qw( GetOAISetBySpec GetOAISetsBiblio );
36 use MARC::File::XML;
37
38 sub GetRecords {
39     my ($class, $self, $repository, $metadata, %args) = @_;
40
41     my $token = Koha::OAI::Server::ResumptionToken->new( %args );
42     my $dbh = C4::Context->dbh;
43     my $set;
44     if ( defined $token->{'set'} ) {
45         $set = GetOAISetBySpec($token->{'set'});
46     }
47     my $deleted = defined $token->{deleted} ? $token->{deleted} : 0;
48     my $max = $repository->{koha_max_count};
49     my $count = 0;
50     my $format = $args{metadataPrefix} || $token->{metadata_prefix};
51     my $include_items = $repository->items_included( $format );
52     my $from = $token->{from_arg};
53     my $until = $token->{until_arg};
54     my $next_id = $token->{next_id};
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 ? 'deletedbiblio_metadata' : 'biblio_metadata';
62
63         my @part_bind_params = ($next_id);
64
65         my $where = "biblionumber >= ?";
66         if ( $from ) {
67             $where .= " AND timestamp >= ?";
68             push @part_bind_params, $from;
69         }
70         if ( $until ) {
71             $where .= " AND timestamp <= ?";
72             push @part_bind_params, $until;
73         }
74         if ( defined $set ) {
75             $where .= " AND biblionumber in (SELECT osb.biblionumber FROM oai_sets_biblios osb WHERE osb.set_id = ?)";
76             push @part_bind_params, $set->{'id'};
77         }
78
79         my @bind_params = @part_bind_params;
80
81         my $criteria = "WHERE $where ORDER BY biblionumber LIMIT " . ($max + 1);
82
83         my $sql = "
84             SELECT biblionumber
85             FROM $table
86             $criteria
87         ";
88
89         # If items are included, fetch a set of potential biblionumbers from items tables as well.
90         # Then merge them, sort them and take the required number of them from the resulting list.
91         # This may seem counter-intuitive as in worst case we fetch 3 times the biblionumbers needed,
92         # but avoiding joins or subqueries makes this so much faster that it does not matter.
93         if ( $include_items )  {
94             $sql = "($sql) UNION (SELECT DISTINCT(biblionumber) FROM deleteditems $criteria)";
95             push @bind_params, @part_bind_params;
96             if (!$deleted) {
97                 $sql .= " UNION (SELECT DISTINCT(biblionumber) FROM items $criteria)";
98                 push @bind_params, @part_bind_params;
99             }
100             $sql = "SELECT biblionumber FROM ($sql) bibnos ORDER BY biblionumber LIMIT " . ($max + 1);
101         }
102
103         my $sth = $dbh->prepare( $sql ) || die( 'Could not prepare statement: ' . $dbh->errstr );
104         $sth->execute( @bind_params ) || die( 'Could not execute statement: ' . $sth->errstr );
105
106         my $ts_sql;
107         if ( $include_items ) {
108             if ( $deleted ) {
109                 $ts_sql = "
110                     SELECT MAX(timestamp)
111                     FROM (
112                         SELECT timestamp FROM deletedbiblio_metadata WHERE biblionumber = ?
113                         UNION
114                         SELECT timestamp FROM deleteditems WHERE biblionumber = ?
115                     ) bis
116                 ";
117             } else {
118                 $ts_sql = "
119                     SELECT MAX(timestamp)
120                     FROM (
121                         SELECT timestamp FROM biblio_metadata WHERE biblionumber = ?
122                         UNION
123                         SELECT timestamp FROM deleteditems WHERE biblionumber = ?
124                         UNION
125                         SELECT timestamp FROM items WHERE biblionumber = ?
126                     ) bi
127                 ";
128             }
129         } else {
130             $ts_sql = "SELECT timestamp FROM $table WHERE biblionumber = ?";
131         }
132         my $ts_sth = $dbh->prepare( $ts_sql ) || die( 'Could not prepare statement: ' . $dbh->errstr );
133
134         foreach my $row (@{ $sth->fetchall_arrayref() }) {
135             my $biblionumber = $row->[0];
136             $count++;
137             if ( $count > $max ) {
138                 $self->resumptionToken(
139                     Koha::OAI::Server::ResumptionToken->new(
140                         metadataPrefix  => $token->{metadata_prefix},
141                         from            => $token->{from},
142                         until           => $token->{until},
143                         cursor          => $token->{cursor} + $max,
144                         set             => $token->{set},
145                         deleted         => $deleted,
146                         next_id         => $biblionumber
147                     )
148                 );
149                 last STAGELOOP;
150             }
151             my @params = ($biblionumber);
152             if ( $include_items ) {
153                 push @params, $deleted ? ( $biblionumber ) : ( $biblionumber, $biblionumber );
154             }
155             $ts_sth->execute( @params ) || die( 'Could not execute statement: ' . $ts_sth->errstr );
156
157             my ($timestamp) = $ts_sth->fetchrow;
158
159             my $oai_sets = GetOAISetsBiblio($biblionumber);
160             my @setSpecs;
161             foreach ( @$oai_sets ) {
162                 push @setSpecs, $_->{spec};
163             }
164             if ( $metadata ) {
165                 my $marcxml = !$deleted ? $repository->get_biblio_marcxml($biblionumber, $format) : undef;
166                 if ( $marcxml ) {
167                   $self->record( Koha::OAI::Server::Record->new(
168                       $repository, $marcxml, $timestamp, \@setSpecs,
169                       identifier      => $repository->{ koha_identifier } . ':' . $biblionumber,
170                       metadataPrefix  => $token->{metadata_prefix}
171                   ) );
172                 } else {
173                   $self->record( Koha::OAI::Server::DeletedRecord->new(
174                       $timestamp, \@setSpecs, identifier => $repository->{ koha_identifier } . ':' . $biblionumber
175                   ) );
176                 }
177             } else {
178                 $timestamp =~ s/ /T/;
179                 $timestamp .= 'Z';
180                 $self->identifier( HTTP::OAI::Header->new(
181                     identifier => $repository->{ koha_identifier} . ':' . $biblionumber,
182                     datestamp  => $timestamp,
183                     status     => $deleted ? 'deleted' : undef
184                 ) );
185             }
186         }
187         # Reset $next_id for the next stage
188         $next_id = 0;
189     }
190     return $count;
191 }
192
193 1;