bb6fecb0aad56f377ae76632f65398c4169114dc
[koha-equinox.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 C4::Biblio;
535 use HTTP::OAI;
536 use C4::OAI::Sets;
537 use MARC::File::XML;
538
539 use base ("HTTP::OAI::ListRecords");
540
541
542 sub new {
543     my ($class, $repository, %args) = @_;
544
545     my $self = HTTP::OAI::ListRecords->new(%args);
546
547     my $token = new C4::OAI::ResumptionToken( %args );
548     my $dbh = C4::Context->dbh;
549     my $set;
550     if(defined $token->{'set'}) {
551         $set = GetOAISetBySpec($token->{'set'});
552     }
553     my $max = $repository->{koha_max_count};
554     my $sql = "
555         SELECT biblioitems.biblionumber, biblioitems.timestamp
556         FROM biblioitems
557     ";
558     $sql .= " JOIN oai_sets_biblios ON biblioitems.biblionumber = oai_sets_biblios.biblionumber " if defined $set;
559     $sql .= " WHERE timestamp >= ? AND timestamp <= ? ";
560     $sql .= " AND oai_sets_biblios.set_id = ? " if defined $set;
561     $sql .= ") UNION
562         (SELECT deletedbiblio.biblionumber, null as marcxml, timestamp FROM deletedbiblio";
563     $sql .= " JOIN oai_sets_biblios ON deletedbiblio.biblionumber = oai_sets_biblios.biblionumber " if defined $set;
564     $sql .= " WHERE DATE(timestamp) >= ? AND DATE(timestamp) <= ? ";
565     $sql .= " AND oai_sets_biblios.set_id = ? " if defined $set;
566
567     $sql .= ") ORDER BY biblionumber
568         LIMIT " . ($max + 1) . "
569         OFFSET $token->{offset}
570     ";
571     my $sth = $dbh->prepare( $sql );
572     my @bind_params = ($token->{'from_arg'}, $token->{'until_arg'});
573     push @bind_params, $set->{'id'} if defined $set;
574     push @bind_params, ($token->{'from'}, $token->{'until'});
575     push @bind_params, $set->{'id'} if defined $set;
576     $sth->execute( @bind_params );
577
578     my $count = 0;
579     while ( my ($biblionumber, $timestamp) = $sth->fetchrow ) {
580         $count++;
581         if ( $count > $max ) {
582             $self->resumptionToken(
583                 new C4::OAI::ResumptionToken(
584                     metadataPrefix  => $token->{metadata_prefix},
585                     from            => $token->{from},
586                     until           => $token->{until},
587                     offset          => $token->{offset} + $max,
588                     set             => $token->{set}
589                 )
590             );
591             last;
592         }
593         my $record = GetMarcBiblio($biblionumber, 1, 1);
594         my $marcxml = $record->as_xml();
595         my $oai_sets = GetOAISetsBiblio($biblionumber);
596         my @setSpecs;
597         foreach (@$oai_sets) {
598             push @setSpecs, $_->{spec};
599         }
600         if ($marcxml) {
601           $self->record( C4::OAI::Record->new(
602               $repository, $marcxml, $timestamp, \@setSpecs,
603               identifier      => $repository->{ koha_identifier } . ':' . $biblionumber,
604               metadataPrefix  => $token->{metadata_prefix}
605           ) );
606         } else {
607           $self->record( C4::OAI::DeletedRecord->new(
608           $timestamp, \@setSpecs, identifier => $repository->{ koha_identifier } . ':' . $biblionumber ) );
609         }
610     }
611
612     # Return error if no results
613     unless ($count) {
614         return HTTP::OAI::Response->new(
615             requestURL => $repository->self_url(),
616             errors     => [ new HTTP::OAI::Error( code => 'noRecordsMatch' ) ],
617         );
618     }
619
620     return $self;
621 }
622
623 # __END__ C4::OAI::ListRecords
624
625
626
627 package C4::OAI::Repository;
628
629 use base ("HTTP::OAI::Repository");
630
631 use strict;
632 use warnings;
633
634 use HTTP::OAI;
635 use HTTP::OAI::Repository qw/:validate/;
636
637 use XML::SAX::Writer;
638 use XML::LibXML;
639 use XML::LibXSLT;
640 use YAML::Syck qw( LoadFile );
641 use CGI qw/:standard -oldstyle_urls/;
642
643 use C4::Context;
644 use C4::Biblio;
645
646
647 sub new {
648     my ($class, %args) = @_;
649     my $self = $class->SUPER::new(%args);
650
651     $self->{ koha_identifier      } = C4::Context->preference("OAI-PMH:archiveID");
652     $self->{ koha_max_count       } = C4::Context->preference("OAI-PMH:MaxCount");
653     $self->{ koha_metadata_format } = ['oai_dc', 'marcxml'];
654     $self->{ koha_stylesheet      } = { }; # Build when needed
655
656     # Load configuration file if defined in OAI-PMH:ConfFile syspref
657     if ( my $file = C4::Context->preference("OAI-PMH:ConfFile") ) {
658         $self->{ conf } = LoadFile( $file );
659         my @formats = keys %{ $self->{conf}->{format} };
660         $self->{ koha_metadata_format } =  \@formats;
661     }
662
663     # Check for grammatical errors in the request
664     my @errs = validate_request( CGI::Vars() );
665
666     # Is metadataPrefix supported by the respository?
667     my $mdp = param('metadataPrefix') || '';
668     if ( $mdp && !grep { $_ eq $mdp } @{$self->{ koha_metadata_format }} ) {
669         push @errs, new HTTP::OAI::Error(
670             code    => 'cannotDisseminateFormat',
671             message => "Dissemination as '$mdp' is not supported",
672         );
673     }
674
675     my $response;
676     if ( @errs ) {
677         $response = HTTP::OAI::Response->new(
678             requestURL  => self_url(),
679             errors      => \@errs,
680         );
681     }
682     else {
683         my %attr = CGI::Vars();
684         my $verb = delete( $attr{verb} );
685         if ( $verb eq 'ListSets' ) {
686             $response = C4::OAI::ListSets->new($self, %attr);
687         }
688         elsif ( $verb eq 'Identify' ) {
689             $response = C4::OAI::Identify->new( $self );
690         }
691         elsif ( $verb eq 'ListMetadataFormats' ) {
692             $response = C4::OAI::ListMetadataFormats->new( $self );
693         }
694         elsif ( $verb eq 'GetRecord' ) {
695             $response = C4::OAI::GetRecord->new( $self, %attr );
696         }
697         elsif ( $verb eq 'ListRecords' ) {
698             $response = C4::OAI::ListRecords->new( $self, %attr );
699         }
700         elsif ( $verb eq 'ListIdentifiers' ) {
701             $response = C4::OAI::ListIdentifiers->new( $self, %attr );
702         }
703     }
704
705     $response->set_handler( XML::SAX::Writer->new( Output => *STDOUT ) );
706     $response->generate;
707
708     bless $self, $class;
709     return $self;
710 }
711
712
713 sub stylesheet {
714     my ( $self, $format ) = @_;
715
716     my $stylesheet = $self->{ koha_stylesheet }->{ $format };
717     unless ( $stylesheet ) {
718         my $xsl_file = $self->{ conf }
719                        ? $self->{ conf }->{ format }->{ $format }->{ xsl_file }
720                        : ( C4::Context->config('intrahtdocs') .
721                          '/prog/en/xslt/' .
722                          C4::Context->preference('marcflavour') .
723                          'slim2OAIDC.xsl' );
724         my $parser = XML::LibXML->new();
725         my $xslt = XML::LibXSLT->new();
726         my $style_doc = $parser->parse_file( $xsl_file );
727         $stylesheet = $xslt->parse_stylesheet( $style_doc );
728         $self->{ koha_stylesheet }->{ $format } = $stylesheet;
729     }
730
731     return $stylesheet;
732 }
733
734
735
736 =head1 NAME
737
738 C4::OAI::Repository - Handles OAI-PMH requests for a Koha database.
739
740 =head1 SYNOPSIS
741
742   use C4::OAI::Repository;
743
744   my $repository = C4::OAI::Repository->new();
745
746 =head1 DESCRIPTION
747
748 This object extend HTTP::OAI::Repository object.
749 It accepts OAI-PMH HTTP requests and returns result.
750
751 This OAI-PMH server can operate in a simple mode and extended one.
752
753 In simple mode, repository configuration comes entirely from Koha system
754 preferences (OAI-PMH:archiveID and OAI-PMH:MaxCount) and the server returns
755 records in marcxml or dublin core format. Dublin core records are created from
756 koha marcxml records tranformed with XSLT. Used XSL file is located in
757 koha-tmpl/intranet-tmpl/prog/en/xslt directory and choosed based on marcflavour,
758 respecively MARC21slim2OAIDC.xsl for MARC21 and  MARC21slim2OAIDC.xsl for
759 UNIMARC.
760
761 In extende mode, it's possible to parameter other format than marcxml or Dublin
762 Core. A new syspref OAI-PMH:ConfFile specify a YAML configuration file which
763 list available metadata formats and XSL file used to create them from marcxml
764 records. If this syspref isn't set, Koha OAI server works in simple mode. A
765 configuration file koha-oai.conf can look like that:
766
767   ---
768   format:
769     vs:
770       metadataPrefix: vs
771       metadataNamespace: http://veryspecial.tamil.fr/vs/format-pivot/1.1/vs
772       schema: http://veryspecial.tamil.fr/vs/format-pivot/1.1/vs.xsd
773       xsl_file: /usr/local/koha/xslt/vs.xsl
774     marcxml:
775       metadataPrefix: marxml
776       metadataNamespace: http://www.loc.gov/MARC21/slim http://www.loc.gov/standards/marcxml/schema/MARC21slim
777       schema: http://www.loc.gov/MARC21/slim http://www.loc.gov/standards/marcxml/schema/MARC21slim.xsd
778     oai_dc:
779       metadataPrefix: oai_dc
780       metadataNamespace: http://www.openarchives.org/OAI/2.0/oai_dc/
781       schema: http://www.openarchives.org/OAI/2.0/oai_dc.xsd
782       xsl_file: /usr/local/koha/koha-tmpl/intranet-tmpl/xslt/UNIMARCslim2OAIDC.xsl
783
784 =cut
785
786
787