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