Bug 12252: OAI-PMH result includes item data
[koha.git] / opac / oai.pl
1 #!/usr/bin/perl
2
3 # Copyright Biblibre 2008
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
21 use strict;
22 use warnings;
23
24 use CGI qw( :standard -oldstyle_urls -utf8 );
25 use vars qw( $GZIP );
26 use C4::Context;
27
28
29 BEGIN {
30     eval { require PerlIO::gzip };
31     $GZIP = ($@) ? 0 : 1;
32 }
33
34 unless ( C4::Context->preference('OAI-PMH') ) {
35     print
36         header(
37             -type       => 'text/plain; charset=utf-8',
38             -charset    => 'utf-8',
39             -status     => '404 OAI-PMH service is disabled',
40         ),
41         "OAI-PMH service is disabled";
42     exit;
43 }
44
45 my @encodings = http('HTTP_ACCEPT_ENCODING');
46 if ( $GZIP && grep { defined($_) && $_ eq 'gzip' } @encodings ) {
47     print header(
48         -type               => 'text/xml; charset=utf-8',
49         -charset            => 'utf-8',
50         -Content-Encoding   => 'gzip',
51     );
52     binmode( STDOUT, ":gzip" );
53 }
54 else {
55     print header(
56         -type       => 'text/xml; charset=utf-8',
57         -charset    => 'utf-8',
58     );
59 }
60
61 binmode STDOUT, ':encoding(UTF-8)';
62 my $repository = C4::OAI::Repository->new();
63
64 # __END__ Main Prog
65
66
67 #
68 # Extends HTTP::OAI::ResumptionToken
69 # A token is identified by:
70 # - metadataPrefix
71 # - from
72 # - until
73 # - offset
74 #
75 package C4::OAI::ResumptionToken;
76
77 use strict;
78 use warnings;
79 use HTTP::OAI;
80
81 use base ("HTTP::OAI::ResumptionToken");
82
83
84 sub new {
85     my ($class, %args) = @_;
86
87     my $self = $class->SUPER::new(%args);
88
89     my ($metadata_prefix, $offset, $from, $until, $set);
90     if ( $args{ resumptionToken } ) {
91         ($metadata_prefix, $offset, $from, $until, $set)
92             = split( '/', $args{resumptionToken} );
93     }
94     else {
95         $metadata_prefix = $args{ metadataPrefix };
96         $from = $args{ from } || '1970-01-01';
97         $until = $args{ until };
98         unless ( $until) {
99             my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday) = gmtime( time );
100             $until = sprintf( "%.4d-%.2d-%.2d", $year+1900, $mon+1,$mday );
101         }
102         #Add times to the arguments, when necessary, so they correctly match against the DB timestamps
103         $from .= 'T00:00:00Z' if length($from) == 10;
104         $until .= 'T23:59:59Z' if length($until) == 10;
105         $offset = $args{ offset } || 0;
106         $set = $args{set};
107     }
108
109     $self->{ metadata_prefix } = $metadata_prefix;
110     $self->{ offset          } = $offset;
111     $self->{ from            } = $from;
112     $self->{ until           } = $until;
113     $self->{ set             } = $set;
114     $self->{ from_arg        } = _strip_UTC_designators($from);
115     $self->{ until_arg       } = _strip_UTC_designators($until);
116
117     $self->resumptionToken(
118         join( '/', $metadata_prefix, $offset, $from, $until, $set ) );
119     $self->cursor( $offset );
120
121     return $self;
122 }
123
124 sub _strip_UTC_designators {
125     my ( $timestamp ) = @_;
126     $timestamp =~ s/T/ /g;
127     $timestamp =~ s/Z//g;
128     return $timestamp;
129 }
130
131 # __END__ C4::OAI::ResumptionToken
132
133
134
135 package C4::OAI::Identify;
136
137 use strict;
138 use warnings;
139 use HTTP::OAI;
140 use C4::Context;
141
142 use base ("HTTP::OAI::Identify");
143
144 sub new {
145     my ($class, $repository) = @_;
146
147     my ($baseURL) = $repository->self_url() =~ /(.*)\?.*/;
148     my $self = $class->SUPER::new(
149         baseURL             => $baseURL,
150         repositoryName      => C4::Context->preference("LibraryName"),
151         adminEmail          => C4::Context->preference("KohaAdminEmailAddress"),
152         MaxCount            => C4::Context->preference("OAI-PMH:MaxCount"),
153         granularity         => 'YYYY-MM-DD',
154         earliestDatestamp   => '0001-01-01',
155         deletedRecord       => C4::Context->preference("OAI-PMH:DeletedRecord") || 'no',
156     );
157
158     # FIXME - alas, the description element is not so simple; to validate
159     # against the OAI-PMH schema, it cannot contain just a string,
160     # but one or more elements that validate against another XML schema.
161     # For now, simply omitting it.
162     # $self->description( "Koha OAI Repository" );
163
164     $self->compression( 'gzip' );
165
166     return $self;
167 }
168
169 # __END__ C4::OAI::Identify
170
171
172
173 package C4::OAI::ListMetadataFormats;
174
175 use strict;
176 use warnings;
177 use HTTP::OAI;
178
179 use base ("HTTP::OAI::ListMetadataFormats");
180
181 sub new {
182     my ($class, $repository) = @_;
183
184     my $self = $class->SUPER::new();
185
186     if ( $repository->{ conf } ) {
187         foreach my $name ( @{ $repository->{ koha_metadata_format } } ) {
188             my $format = $repository->{ conf }->{ format }->{ $name };
189             $self->metadataFormat( HTTP::OAI::MetadataFormat->new(
190                 metadataPrefix    => $format->{metadataPrefix},
191                 schema            => $format->{schema},
192                 metadataNamespace => $format->{metadataNamespace}, ) );
193         }
194     }
195     else {
196         $self->metadataFormat( HTTP::OAI::MetadataFormat->new(
197             metadataPrefix    => 'oai_dc',
198             schema            => 'http://www.openarchives.org/OAI/2.0/oai_dc.xsd',
199             metadataNamespace => 'http://www.openarchives.org/OAI/2.0/oai_dc/'
200         ) );
201         $self->metadataFormat( HTTP::OAI::MetadataFormat->new(
202             metadataPrefix    => 'marcxml',
203             schema            => 'http://www.loc.gov/MARC21/slim http://www.loc.gov/ standards/marcxml/schema/MARC21slim.xsd',
204             metadataNamespace => 'http://www.loc.gov/MARC21/slim http://www.loc.gov/ standards/marcxml/schema/MARC21slim'
205         ) );
206     }
207
208     return $self;
209 }
210
211 # __END__ C4::OAI::ListMetadataFormats
212
213
214
215 package C4::OAI::Record;
216
217 use strict;
218 use warnings;
219 use HTTP::OAI;
220 use HTTP::OAI::Metadata::OAI_DC;
221
222 use base ("HTTP::OAI::Record");
223
224 sub new {
225     my ($class, $repository, $marcxml, $timestamp, $setSpecs, %args) = @_;
226
227     my $self = $class->SUPER::new(%args);
228
229     $timestamp =~ s/ /T/, $timestamp .= 'Z';
230     $self->header( new HTTP::OAI::Header(
231         identifier  => $args{identifier},
232         datestamp   => $timestamp,
233     ) );
234
235     foreach my $setSpec (@$setSpecs) {
236         $self->header->setSpec($setSpec);
237     }
238
239     my $parser = XML::LibXML->new();
240     my $record_dom = $parser->parse_string( $marcxml );
241     my $format =  $args{metadataPrefix};
242     if ( $format ne 'marcxml' ) {
243         my %args = (
244             OPACBaseURL => "'" . C4::Context->preference('OPACBaseURL') . "'"
245         );
246         $record_dom = $repository->stylesheet($format)->transform($record_dom, %args);
247     }
248     $self->metadata( HTTP::OAI::Metadata->new( dom => $record_dom ) );
249
250     return $self;
251 }
252
253 # __END__ C4::OAI::Record
254
255 package C4::OAI::DeletedRecord;
256
257 use Modern::Perl;
258 use HTTP::OAI;
259 use HTTP::OAI::Metadata::OAI_DC;
260
261 use base ("HTTP::OAI::Record");
262
263 sub new {
264     my ($class, $timestamp, $setSpecs, %args) = @_;
265
266     my $self = $class->SUPER::new(%args);
267
268     $timestamp =~ s/ /T/, $timestamp .= 'Z';
269     $self->header( new HTTP::OAI::Header(
270         status      => 'deleted',
271         identifier  => $args{identifier},
272         datestamp   => $timestamp,
273     ) );
274
275     foreach my $setSpec (@$setSpecs) {
276         $self->header->setSpec($setSpec);
277     }
278
279     return $self;
280 }
281
282 # __END__ C4::OAI::DeletedRecord
283
284
285
286 package C4::OAI::GetRecord;
287
288 use strict;
289 use warnings;
290 use HTTP::OAI;
291 use C4::Biblio;
292 use C4::OAI::Sets;
293 use MARC::File::XML;
294
295 use base ("HTTP::OAI::GetRecord");
296
297
298 sub new {
299     my ($class, $repository, %args) = @_;
300
301     my $self = HTTP::OAI::GetRecord->new(%args);
302
303     my $dbh = C4::Context->dbh;
304     my $sth = $dbh->prepare("
305         SELECT timestamp
306         FROM   biblioitems
307         WHERE  biblionumber=? " );
308     my $prefix = $repository->{koha_identifier} . ':';
309     my ($biblionumber) = $args{identifier} =~ /^$prefix(.*)/;
310     $sth->execute( $biblionumber );
311     my ($timestamp);
312     unless ( ($timestamp) = $sth->fetchrow ) {
313         unless ( ($timestamp) = $dbh->selectrow_array(q/
314             SELECT timestamp
315             FROM deletedbiblio
316             WHERE biblionumber=? /, undef, $biblionumber ))
317         {
318             return HTTP::OAI::Response->new(
319              requestURL  => $repository->self_url(),
320              errors      => [ new HTTP::OAI::Error(
321                 code    => 'idDoesNotExist',
322                 message => "There is no biblio record with this identifier",
323                 ) ],
324             );
325         }
326         else {
327             $deleted = 1;
328         }
329     }
330
331     # We fetch it using this method, rather than the database directly,
332     # so it'll include the item data
333     my $marcxml;
334     unless ($deleted) {
335         my $record = GetMarcBiblio($biblionumber, 1);
336         $marcxml = $record->as_xml();
337     }
338     my $oai_sets = GetOAISetsBiblio($biblionumber);
339     my @setSpecs;
340     foreach (@$oai_sets) {
341         push @setSpecs, $_->{spec};
342     }
343
344     #$self->header( HTTP::OAI::Header->new( identifier  => $args{identifier} ) );
345     $self->record(
346         $deleted
347         ? C4::OAI::DeletedRecord->new($timestamp, \@setSpecs, %args)
348         : C4::OAI::Record->new($repository, $marcxml, $timestamp, \@setSpecs, %args);
349     );
350     return $self;
351 }
352
353 # __END__ C4::OAI::GetRecord
354
355
356
357 package C4::OAI::ListIdentifiers;
358
359 use strict;
360 use warnings;
361 use HTTP::OAI;
362 use C4::OAI::Sets;
363
364 use base ("HTTP::OAI::ListIdentifiers");
365
366
367 sub new {
368     my ($class, $repository, %args) = @_;
369
370     my $self = HTTP::OAI::ListIdentifiers->new(%args);
371
372     my $token = new C4::OAI::ResumptionToken( %args );
373     my $dbh = C4::Context->dbh;
374     my $set;
375     if(defined $token->{'set'}) {
376         $set = GetOAISetBySpec($token->{'set'});
377     }
378     my $max = $repository->{koha_max_count};
379     my $sql = "
380         (SELECT biblioitems.biblionumber, biblioitems.timestamp
381         FROM biblioitems
382     ";
383     $sql .= " JOIN oai_sets_biblios ON biblioitems.biblionumber = oai_sets_biblios.biblionumber " if defined $set;
384     $sql .= " WHERE timestamp >= ? AND timestamp <= ? ";
385     $sql .= " AND oai_sets_biblios.set_id = ? " if defined $set;
386     $sql .= ") UNION
387         (SELECT deletedbiblio.biblionumber, timestamp FROM deletedbiblio";
388     $sql .= " JOIN oai_sets_biblios ON deletedbiblio.biblionumber = oai_sets_biblios.biblionumber " if defined $set;
389     $sql .= " WHERE DATE(timestamp) >= ? AND DATE(timestamp) <= ? ";
390     $sql .= " AND oai_sets_biblios.set_id = ? " if defined $set;
391
392     $sql .= ") ORDER BY biblionumber
393         LIMIT " . ($max+1) . "
394         OFFSET $token->{offset}
395     ";
396     my $sth = $dbh->prepare( $sql );
397     my @bind_params = ($token->{'from_arg'}, $token->{'until_arg'});
398     push @bind_params, $set->{'id'} if defined $set;
399     push @bind_params, ($token->{'from'}, $token->{'until'});
400     push @bind_params, $set->{'id'} if defined $set;
401     $sth->execute( @bind_params );
402
403     my $count = 0;
404     while ( my ($biblionumber, $timestamp) = $sth->fetchrow ) {
405         $count++;
406         if ( $count > $max ) {
407             $self->resumptionToken(
408                 new C4::OAI::ResumptionToken(
409                     metadataPrefix  => $token->{metadata_prefix},
410                     from            => $token->{from},
411                     until           => $token->{until},
412                     offset          => $token->{offset} + $max,
413                     set             => $token->{set}
414                 )
415             );
416             last;
417         }
418         $timestamp =~ s/ /T/, $timestamp .= 'Z';
419         $self->identifier( new HTTP::OAI::Header(
420             identifier => $repository->{ koha_identifier} . ':' . $biblionumber,
421             datestamp  => $timestamp,
422         ) );
423     }
424
425     # Return error if no results
426     unless ($count) {
427         return HTTP::OAI::Response->new(
428             requestURL => $repository->self_url(),
429             errors     => [ new HTTP::OAI::Error( code => 'noRecordsMatch' ) ],
430         );
431     }
432
433     return $self;
434 }
435
436 # __END__ C4::OAI::ListIdentifiers
437
438 package C4::OAI::Description;
439
440 use strict;
441 use warnings;
442 use HTTP::OAI;
443 use HTTP::OAI::SAXHandler qw/ :SAX /;
444
445 sub new {
446     my ( $class, %args ) = @_;
447
448     my $self = {};
449
450     if(my $setDescription = $args{setDescription}) {
451         $self->{setDescription} = $setDescription;
452     }
453     if(my $handler = $args{handler}) {
454         $self->{handler} = $handler;
455     }
456
457     bless $self, $class;
458     return $self;
459 }
460
461 sub set_handler {
462     my ( $self, $handler ) = @_;
463
464     $self->{handler} = $handler if $handler;
465
466     return $self;
467 }
468
469 sub generate {
470     my ( $self ) = @_;
471
472     g_data_element($self->{handler}, 'http://www.openarchives.org/OAI/2.0/', 'setDescription', {}, $self->{setDescription});
473
474     return $self;
475 }
476
477 # __END__ C4::OAI::Description
478
479 package C4::OAI::ListSets;
480
481 use strict;
482 use warnings;
483 use HTTP::OAI;
484 use C4::OAI::Sets;
485
486 use base ("HTTP::OAI::ListSets");
487
488 sub new {
489     my ( $class, $repository, %args ) = @_;
490
491     my $self = HTTP::OAI::ListSets->new(%args);
492
493     my $token = C4::OAI::ResumptionToken->new(%args);
494     my $sets = GetOAISets;
495     my $pos = 0;
496     foreach my $set (@$sets) {
497         if ($pos < $token->{offset}) {
498             $pos++;
499             next;
500         }
501         my @descriptions;
502         foreach my $desc (@{$set->{'descriptions'}}) {
503             push @descriptions, C4::OAI::Description->new(
504                 setDescription => $desc,
505             );
506         }
507         $self->set(
508             HTTP::OAI::Set->new(
509                 setSpec => $set->{'spec'},
510                 setName => $set->{'name'},
511                 setDescription => \@descriptions,
512             )
513         );
514         $pos++;
515         last if ($pos + 1 - $token->{offset}) > $repository->{koha_max_count};
516     }
517
518     $self->resumptionToken(
519         new C4::OAI::ResumptionToken(
520             metadataPrefix => $token->{metadata_prefix},
521             offset         => $pos
522         )
523     ) if ( $pos > $token->{offset} );
524
525     return $self;
526 }
527
528 # __END__ C4::OAI::ListSets;
529
530 package C4::OAI::ListRecords;
531
532 use strict;
533 use warnings;
534 use HTTP::OAI;
535 use C4::OAI::Sets;
536
537 use base ("HTTP::OAI::ListRecords");
538
539
540 sub new {
541     my ($class, $repository, %args) = @_;
542
543     my $self = HTTP::OAI::ListRecords->new(%args);
544
545     my $token = new C4::OAI::ResumptionToken( %args );
546     my $dbh = C4::Context->dbh;
547     my $set;
548     if(defined $token->{'set'}) {
549         $set = GetOAISetBySpec($token->{'set'});
550     }
551     my $max = $repository->{koha_max_count};
552     my $sql = "
553         (SELECT biblioitems.biblionumber, biblioitems.marcxml, biblioitems.timestamp
554         FROM biblioitems
555     ";
556     $sql .= " JOIN oai_sets_biblios ON biblioitems.biblionumber = oai_sets_biblios.biblionumber " if defined $set;
557     $sql .= " WHERE timestamp >= ? AND timestamp <= ? ";
558     $sql .= " AND oai_sets_biblios.set_id = ? " if defined $set;
559     $sql .= ") UNION
560         (SELECT deletedbiblio.biblionumber, null as marcxml, timestamp FROM deletedbiblio";
561     $sql .= " JOIN oai_sets_biblios ON deletedbiblio.biblionumber = oai_sets_biblios.biblionumber " if defined $set;
562     $sql .= " WHERE DATE(timestamp) >= ? AND DATE(timestamp) <= ? ";
563     $sql .= " AND oai_sets_biblios.set_id = ? " if defined $set;
564
565     $sql .= ") ORDER BY biblionumber
566         LIMIT " . ($max + 1) . "
567         OFFSET $token->{offset}
568     ";
569     my $sth = $dbh->prepare( $sql );
570     my @bind_params = ($token->{'from_arg'}, $token->{'until_arg'});
571     push @bind_params, $set->{'id'} if defined $set;
572     push @bind_params, ($token->{'from'}, $token->{'until'});
573     push @bind_params, $set->{'id'} if defined $set;
574     $sth->execute( @bind_params );
575
576     my $count = 0;
577     while ( my ($biblionumber, $marcxml, $timestamp) = $sth->fetchrow ) {
578         $count++;
579         if ( $count > $max ) {
580             $self->resumptionToken(
581                 new C4::OAI::ResumptionToken(
582                     metadataPrefix  => $token->{metadata_prefix},
583                     from            => $token->{from},
584                     until           => $token->{until},
585                     offset          => $token->{offset} + $max,
586                     set             => $token->{set}
587                 )
588             );
589             last;
590         }
591         my $oai_sets = GetOAISetsBiblio($biblionumber);
592         my @setSpecs;
593         foreach (@$oai_sets) {
594             push @setSpecs, $_->{spec};
595         }
596         if ($marcxml) {
597           $self->record( C4::OAI::Record->new(
598               $repository, $marcxml, $timestamp, \@setSpecs,
599               identifier      => $repository->{ koha_identifier } . ':' . $biblionumber,
600               metadataPrefix  => $token->{metadata_prefix}
601           ) );
602         } else {
603           $self->record( C4::OAI::DeletedRecord->new(
604           $timestamp, \@setSpecs, identifier => $repository->{ koha_identifier } . ':' . $biblionumber ) );
605         }
606     }
607
608     # Return error if no results
609     unless ($count) {
610         return HTTP::OAI::Response->new(
611             requestURL => $repository->self_url(),
612             errors     => [ new HTTP::OAI::Error( code => 'noRecordsMatch' ) ],
613         );
614     }
615
616     return $self;
617 }
618
619 # __END__ C4::OAI::ListRecords
620
621
622
623 package C4::OAI::Repository;
624
625 use base ("HTTP::OAI::Repository");
626
627 use strict;
628 use warnings;
629
630 use HTTP::OAI;
631 use HTTP::OAI::Repository qw/:validate/;
632
633 use XML::SAX::Writer;
634 use XML::LibXML;
635 use XML::LibXSLT;
636 use YAML::Syck qw( LoadFile );
637 use CGI qw/:standard -oldstyle_urls/;
638
639 use C4::Context;
640 use C4::Biblio;
641
642
643 sub new {
644     my ($class, %args) = @_;
645     my $self = $class->SUPER::new(%args);
646
647     $self->{ koha_identifier      } = C4::Context->preference("OAI-PMH:archiveID");
648     $self->{ koha_max_count       } = C4::Context->preference("OAI-PMH:MaxCount");
649     $self->{ koha_metadata_format } = ['oai_dc', 'marcxml'];
650     $self->{ koha_stylesheet      } = { }; # Build when needed
651
652     # Load configuration file if defined in OAI-PMH:ConfFile syspref
653     if ( my $file = C4::Context->preference("OAI-PMH:ConfFile") ) {
654         $self->{ conf } = LoadFile( $file );
655         my @formats = keys %{ $self->{conf}->{format} };
656         $self->{ koha_metadata_format } =  \@formats;
657     }
658
659     # Check for grammatical errors in the request
660     my @errs = validate_request( CGI::Vars() );
661
662     # Is metadataPrefix supported by the respository?
663     my $mdp = param('metadataPrefix') || '';
664     if ( $mdp && !grep { $_ eq $mdp } @{$self->{ koha_metadata_format }} ) {
665         push @errs, new HTTP::OAI::Error(
666             code    => 'cannotDisseminateFormat',
667             message => "Dissemination as '$mdp' is not supported",
668         );
669     }
670
671     my $response;
672     if ( @errs ) {
673         $response = HTTP::OAI::Response->new(
674             requestURL  => self_url(),
675             errors      => \@errs,
676         );
677     }
678     else {
679         my %attr = CGI::Vars();
680         my $verb = delete( $attr{verb} );
681         if ( $verb eq 'ListSets' ) {
682             $response = C4::OAI::ListSets->new($self, %attr);
683         }
684         elsif ( $verb eq 'Identify' ) {
685             $response = C4::OAI::Identify->new( $self );
686         }
687         elsif ( $verb eq 'ListMetadataFormats' ) {
688             $response = C4::OAI::ListMetadataFormats->new( $self );
689         }
690         elsif ( $verb eq 'GetRecord' ) {
691             $response = C4::OAI::GetRecord->new( $self, %attr );
692         }
693         elsif ( $verb eq 'ListRecords' ) {
694             $response = C4::OAI::ListRecords->new( $self, %attr );
695         }
696         elsif ( $verb eq 'ListIdentifiers' ) {
697             $response = C4::OAI::ListIdentifiers->new( $self, %attr );
698         }
699     }
700
701     $response->set_handler( XML::SAX::Writer->new( Output => *STDOUT ) );
702     $response->generate;
703
704     bless $self, $class;
705     return $self;
706 }
707
708
709 sub stylesheet {
710     my ( $self, $format ) = @_;
711
712     my $stylesheet = $self->{ koha_stylesheet }->{ $format };
713     unless ( $stylesheet ) {
714         my $xsl_file = $self->{ conf }
715                        ? $self->{ conf }->{ format }->{ $format }->{ xsl_file }
716                        : ( C4::Context->config('intrahtdocs') .
717                          '/prog/en/xslt/' .
718                          C4::Context->preference('marcflavour') .
719                          'slim2OAIDC.xsl' );
720         my $parser = XML::LibXML->new();
721         my $xslt = XML::LibXSLT->new();
722         my $style_doc = $parser->parse_file( $xsl_file );
723         $stylesheet = $xslt->parse_stylesheet( $style_doc );
724         $self->{ koha_stylesheet }->{ $format } = $stylesheet;
725     }
726
727     return $stylesheet;
728 }
729
730
731
732 =head1 NAME
733
734 C4::OAI::Repository - Handles OAI-PMH requests for a Koha database.
735
736 =head1 SYNOPSIS
737
738   use C4::OAI::Repository;
739
740   my $repository = C4::OAI::Repository->new();
741
742 =head1 DESCRIPTION
743
744 This object extend HTTP::OAI::Repository object.
745 It accepts OAI-PMH HTTP requests and returns result.
746
747 This OAI-PMH server can operate in a simple mode and extended one.
748
749 In simple mode, repository configuration comes entirely from Koha system
750 preferences (OAI-PMH:archiveID and OAI-PMH:MaxCount) and the server returns
751 records in marcxml or dublin core format. Dublin core records are created from
752 koha marcxml records tranformed with XSLT. Used XSL file is located in
753 koha-tmpl/intranet-tmpl/prog/en/xslt directory and choosed based on marcflavour,
754 respecively MARC21slim2OAIDC.xsl for MARC21 and  MARC21slim2OAIDC.xsl for
755 UNIMARC.
756
757 In extende mode, it's possible to parameter other format than marcxml or Dublin
758 Core. A new syspref OAI-PMH:ConfFile specify a YAML configuration file which
759 list available metadata formats and XSL file used to create them from marcxml
760 records. If this syspref isn't set, Koha OAI server works in simple mode. A
761 configuration file koha-oai.conf can look like that:
762
763   ---
764   format:
765     vs:
766       metadataPrefix: vs
767       metadataNamespace: http://veryspecial.tamil.fr/vs/format-pivot/1.1/vs
768       schema: http://veryspecial.tamil.fr/vs/format-pivot/1.1/vs.xsd
769       xsl_file: /usr/local/koha/xslt/vs.xsl
770     marcxml:
771       metadataPrefix: marxml
772       metadataNamespace: http://www.loc.gov/MARC21/slim http://www.loc.gov/standards/marcxml/schema/MARC21slim
773       schema: http://www.loc.gov/MARC21/slim http://www.loc.gov/standards/marcxml/schema/MARC21slim.xsd
774     oai_dc:
775       metadataPrefix: oai_dc
776       metadataNamespace: http://www.openarchives.org/OAI/2.0/oai_dc/
777       schema: http://www.openarchives.org/OAI/2.0/oai_dc.xsd
778       xsl_file: /usr/local/koha/koha-tmpl/intranet-tmpl/xslt/UNIMARCslim2OAIDC.xsl
779
780 =cut
781
782
783