X-Git-Url: http://git.rot13.org/?a=blobdiff_plain;f=opac%2Foai.pl;h=7aa5a7d537c028357da30c12b312832dc2452602;hb=bcde875b47fa6be38b05ffaeb204b2d76b262287;hp=532c3b883baf3d9966aac5c48245d838eb194d04;hpb=d4edef97c9a742aee5c4b5ccc5eb0f5b72a3e1ae;p=koha.git diff --git a/opac/oai.pl b/opac/oai.pl index 532c3b883b..7aa5a7d537 100755 --- a/opac/oai.pl +++ b/opac/oai.pl @@ -1,399 +1,678 @@ #!/usr/bin/perl +# Copyright Biblibre 2008 +# +# This file is part of Koha. +# +# Koha is free software; you can redistribute it and/or modify it under the +# terms of the GNU General Public License as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) any later +# version. +# +# Koha is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR +# A PARTICULAR PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with Koha; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + + use strict; +use warnings; +use CGI qw/:standard -oldstyle_urls/; +use vars qw( $GZIP ); use C4::Context; -use C4::Biblio; -=head1 OAI-PMH for koha -This file is an implementation of the OAI-PMH protocol for koha. Its purpose -is to share metadata in Dublin core format with harvester like PKP-Harverster. -Presently, all the bibliographic records managed by the runing koha instance -are publicly shared (as the opac is). +BEGIN { + eval { require PerlIO::gzip }; + $GZIP = ($@) ? 0 : 1; +} -=head1 Package MARC::Record::KOHADC +unless ( C4::Context->preference('OAI-PMH') ) { + print + header( + -type => 'text/plain; charset=utf-8', + -charset => 'utf-8', + -status => '404 OAI-PMH service is disabled', + ), + "OAI-PMH service is disabled"; + exit; +} -This package is a sub-class of the MARC::File::USMARC. It add methods and functions -to map the content of a marc record (of any flavor) to Dublin core. -As soon as it is possible, mapping between marc fields and there semantic -are got from ::GetMarcFromKohaField fonction from C4::Biblio (see also the "Koha -to MARC mapping" preferences). +my @encodings = http('HTTP_ACCEPT_ENCODING'); +if ( $GZIP && grep { defined($_) && $_ eq 'gzip' } @encodings ) { + print header( + -type => 'text/xml; charset=utf-8', + -charset => 'utf-8', + -Content-Encoding => 'gzip', + ); + binmode( STDOUT, ":gzip" ); +} +else { + print header( + -type => 'text/xml; charset=utf-8', + -charset => 'utf-8', + ); +} -=cut +binmode STDOUT, ':encoding(UTF-8)'; +my $repository = C4::OAI::Repository->new(); -package MARC::Record::KOHADC; -use vars ('@ISA'); -@ISA = qw(MARC::Record); +# __END__ Main Prog -use MARC::File::USMARC; -sub new { # Get a MAR::Record as parameter and bless it as MARC::Record::KOHADC - shift; - my $marc = shift; - bless $marc if( ref( $marc ) ); -} +# +# Extends HTTP::OAI::ResumptionToken +# A token is identified by: +# - metadataPrefix +# - from +# - until +# - offset +# +package C4::OAI::ResumptionToken; -sub subfield { - my $self = shift; - my ($t,$sf) = @_; +use strict; +use warnings; +use HTTP::OAI; - return $self->SUPER::subfield( @_ ) unless wantarray; +use base ("HTTP::OAI::ResumptionToken"); - my @field = $self->field($t); - my @list = (); - my $f; - foreach $f ( @field ) { - push( @list, $f->subfield( $sf ) ); - } - return @list; -} +sub new { + my ($class, %args) = @_; -sub getfields { -my $marc = shift; -my @result = (); + my $self = $class->SUPER::new(%args); - foreach my $kohafield ( @_ ) { - my ( $field, $subfield ) = ::GetMarcFromKohaField( $kohafield, '' ); - next unless defined $field; # $kohafield not defined in framework - push( @result, $field < 10 ? $marc->field( $field )->as_string() : $marc->subfield( $field, $subfield ) ); + my ($metadata_prefix, $offset, $from, $until, $set); + if ( $args{ resumptionToken } ) { + ($metadata_prefix, $offset, $from, $until, $set) + = split( ':', $args{resumptionToken} ); + } + else { + $metadata_prefix = $args{ metadataPrefix }; + $from = $args{ from } || '1970-01-01'; + $until = $args{ until }; + unless ( $until) { + my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday) = gmtime( time ); + $until = sprintf( "%.4d-%.2d-%.2d", $year+1900, $mon+1,$mday ); } -# @result>1 ? \@result : $result[0]; - \@result; -} - -sub XMLescape { -my ($t) = shift; - - foreach (@$t ) { - s/\&/\&/g; s/getfields('biblio.title') ); -} + $self->{ metadata_prefix } = $metadata_prefix; + $self->{ offset } = $offset; + $self->{ from } = $from; + $self->{ until } = $until; + $self->{ set } = $set; -sub Creator { - my $self = shift; - &XMLescape( $self->getfields('biblio.author') ); -} + $self->resumptionToken( + join( ':', $metadata_prefix, $offset, $from, $until, $set ) ); + $self->cursor( $offset ); -sub Subject { - my $self = shift; - &XMLescape( $self->getfields('bibliosubject.subject') ); + return $self; } -sub DateStamp { - my $self = shift; - my ($d,$h) = split( ' ', $self->{'biblio.timestamp'} ); - $d . "T" . $h . "Z"; -} +# __END__ C4::OAI::ResumptionToken -sub Date { - my $self = shift; - my ($str) = @{$self->getfields('biblioitems.publicationyear')}; - my ($y,$m,$d) = (substr($str,0,4), substr($str,4,2), substr($str,6,2)); - $y=1970 unless($y>0); $m=1 unless($m>0); $d=1 unless($d>0); - sprintf( "%.4d-%.2d-%.2d", $y,$m,$d); -} +package C4::OAI::Identify; -sub Description { - my $self = shift; - undef; -} +use strict; +use warnings; +use HTTP::OAI; +use C4::Context; -sub Identifier { - my $self = shift; - my $id = $self->getfields('biblio.biblionumber')->[0]; - -# get url of this script and assume that OAI server is in the same place as opac-detail script -# and build a direct link to the record. - my $uri = $ENV{'SCRIPT_URI'}; - $uri= "http://" . $ENV{'HTTP_HOST'} . $ENV{'REQUEST_URI'} unless( $uri ); # SCRIPT_URI doesn't exist on all httpd server - $uri =~ s#[^/]+$##; - [ - C4::Context->preference("OAI-PMH:archiveID") .":" .$id, - "${uri}opac-detail.pl?bib=$id", - @{$self->getfields('biblioitems.isbn', 'biblioitems.issn')} - ]; -} +use base ("HTTP::OAI::Identify"); -sub Language { - my $self = shift; - undef; -} +sub new { + my ($class, $repository) = @_; -sub Type { - my $self = shift; - &XMLescape( $self->getfields('biblioitems.itemtype') ); -} + my ($baseURL) = $repository->self_url() =~ /(.*)\?.*/; + my $self = $class->SUPER::new( + baseURL => $baseURL, + repositoryName => C4::Context->preference("LibraryName"), + adminEmail => C4::Context->preference("KohaAdminEmailAddress"), + MaxCount => C4::Context->preference("OAI-PMH:MaxCount"), + granularity => 'YYYY-MM-DD', + earliestDatestamp => '0001-01-01', + deletedRecord => 'no', + ); -sub Publisher { - my $self = shift; - &XMLescape( $self->getfields('biblioitems.publishercode') ); -} + # FIXME - alas, the description element is not so simple; to validate + # against the OAI-PMH schema, it cannot contain just a string, + # but one or more elements that validate against another XML schema. + # For now, simply omitting it. + # $self->description( "Koha OAI Repository" ); -sub Set { -my $set = &OAI::KOHA::Set(); - [ map( $_=$_->[0], @$set) ]; + $self->compression( 'gzip' ); + + return $self; } -=head1 The OAI::KOHA package +# __END__ C4::OAI::Identify -This package is a subclass of the OAI::DC data provider. It overides needed methods -and provide the links between the OAI-PMH request and the koha application. -The data used in answers are from the koha table I. -=cut -package OAI::KOHA; +package C4::OAI::ListMetadataFormats; -use C4::OAI::DC; -use vars ('@ISA'); -@ISA = ("C4::OAI::DC"); +use strict; +use warnings; +use HTTP::OAI; -=head2 Set +use base ("HTTP::OAI::ListMetadataFormats"); -return the Set list to the I query. Data are from the 'OAI-PMH:Set' preference. +sub new { + my ($class, $repository) = @_; -=cut + my $self = $class->SUPER::new(); -sub Set { -# [ -# ['BRISE','Experimental unimarc set for BRISE network'], -# ['BRISE:EMSE','EMSE set in BRISE network'] -# ]; -# -# A blinder correctement - [ map( $_ = [ split(",", $_)], split( "\n",C4::Context->preference("OAI-PMH:Set") ) ) ]; + if ( $repository->{ conf } ) { + foreach my $name ( @{ $repository->{ koha_metadata_format } } ) { + my $format = $repository->{ conf }->{ format }->{ $name }; + $self->metadataFormat( HTTP::OAI::MetadataFormat->new( + metadataPrefix => $format->{metadataPrefix}, + schema => $format->{schema}, + metadataNamespace => $format->{metadataNamespace}, ) ); + } + } + else { + $self->metadataFormat( HTTP::OAI::MetadataFormat->new( + metadataPrefix => 'oai_dc', + schema => 'http://www.openarchives.org/OAI/2.0/oai_dc.xsd', + metadataNamespace => 'http://www.openarchives.org/OAI/2.0/oai_dc/' + ) ); + $self->metadataFormat( HTTP::OAI::MetadataFormat->new( + metadataPrefix => 'marcxml', + schema => 'http://www.loc.gov/MARC21/slim http://www.loc.gov/ standards/marcxml/schema/MARC21slim.xsd', + metadataNamespace => 'http://www.loc.gov/MARC21/slim http://www.loc.gov/ standards/marcxml/schema/MARC21slim' + ) ); + } + + return $self; } -=head2 new +# __END__ C4::OAI::ListMetadataFormats -The new method is the constructor for this class. It doesn't have any parameters and -get required data from koha preferences. Koha I is used to identify the -OAI-PMH repository, I is used to set the maximun number of records -returned at the same time in answers to I or I -queries. -The method return a blessed reference. -=cut +package C4::OAI::Record; -# constructor -sub new -{ - my $classname = shift; - my $self = $classname->SUPER::new (); +use strict; +use warnings; +use HTTP::OAI; +use HTTP::OAI::Metadata::OAI_DC; - # set configuration - $self->{'repositoryName'} = C4::Context->preference("LibraryName"); - $self->{'MaxCount'} = C4::Context->preference("OAI-PMH:MaxCount"); - $self->{'adminEmail'} = C4::Context->preference("KohaAdminEmailAddress"); +use base ("HTTP::OAI::Record"); - bless $self, $classname; - return $self; -} +sub new { + my ($class, $repository, $marcxml, $timestamp, $setSpecs, %args) = @_; -=head2 dispose + my $self = $class->SUPER::new(%args); -The dispose method is used as a destructor. It call just the SUPER::dispose method. + $timestamp =~ s/ /T/, $timestamp .= 'Z'; + $self->header( new HTTP::OAI::Header( + identifier => $args{identifier}, + datestamp => $timestamp, + ) ); -=cut + foreach my $setSpec (@$setSpecs) { + $self->header->setSpec($setSpec); + } + + my $parser = XML::LibXML->new(); + my $record_dom = $parser->parse_string( $marcxml ); + my $format = $args{metadataPrefix}; + if ( $format ne 'marcxml' ) { + my %args = ( + OPACBaseURL => "'" . C4::Context->preference('OPACBaseURL') . "'" + ); + $record_dom = $repository->stylesheet($format)->transform($record_dom, %args); + } + $self->metadata( HTTP::OAI::Metadata->new( dom => $record_dom ) ); -# destructor -sub dispose -{ - my ($self) = @_; - $self->SUPER::dispose (); + return $self; } -# now date -sub now { -my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday) = gmtime( time ); +# __END__ C4::OAI::Record + + + +package C4::OAI::GetRecord; + +use strict; +use warnings; +use HTTP::OAI; +use C4::OAI::Sets; + +use base ("HTTP::OAI::GetRecord"); + + +sub new { + my ($class, $repository, %args) = @_; + + my $self = HTTP::OAI::GetRecord->new(%args); + + my $dbh = C4::Context->dbh; + my $sth = $dbh->prepare(" + SELECT marcxml, timestamp + FROM biblioitems + WHERE biblionumber=? " ); + my $prefix = $repository->{koha_identifier} . ':'; + my ($biblionumber) = $args{identifier} =~ /^$prefix(.*)/; + $sth->execute( $biblionumber ); + my ($marcxml, $timestamp); + unless ( ($marcxml, $timestamp) = $sth->fetchrow ) { + return HTTP::OAI::Response->new( + requestURL => $repository->self_url(), + errors => [ new HTTP::OAI::Error( + code => 'idDoesNotExist', + message => "There is no biblio record with this identifier", + ) ] , + ); + } + + my $oai_sets = GetOAISetsBiblio($biblionumber); + my @setSpecs; + foreach (@$oai_sets) { + push @setSpecs, $_->{spec}; + } - sprintf( "%.4d-%.2d-%.2d", $year+1900, $mon+1,$mday ); + #$self->header( HTTP::OAI::Header->new( identifier => $args{identifier} ) ); + $self->record( C4::OAI::Record->new( + $repository, $marcxml, $timestamp, \@setSpecs, %args ) ); + + return $self; } -# build the resumptionTocken fom ($metadataPrefix,$offset,$from,$until) +# __END__ C4::OAI::GetRecord -=head2 buildResumptionToken and parseResumptionToken -Theses two functions are used to manage resumption tokens. The choosed syntax is simple as -possible, a token is only the metadata prefix, the offset in the full answer, the from and -the until date (in the yyyy-mm-dd format) joined by ':' caracter. -I get the four elements as parameters and return the ':' separated -string. +package C4::OAI::ListIdentifiers; -I is used to set the default values to the from and until date, the -metadata prefix using the resumption tocken if necessary. This function have four parameters -(from,until,metadata prefix and resumption tocken) which can be undefined and return every -time this list of values correctly set. The missing values are set with defaults: offset=0, -from= 1970-01-01 and until is set to current date. +use strict; +use warnings; +use HTTP::OAI; +use C4::OAI::Sets; -=cut +use base ("HTTP::OAI::ListIdentifiers"); + + +sub new { + my ($class, $repository, %args) = @_; + + my $self = HTTP::OAI::ListIdentifiers->new(%args); -sub buildResumptionToken { - join( ':', @_ ); + my $token = new C4::OAI::ResumptionToken( %args ); + my $dbh = C4::Context->dbh; + my $set; + if(defined $token->{'set'}) { + $set = GetOAISetBySpec($token->{'set'}); + } + my $sql = " + SELECT biblioitems.biblionumber, biblioitems.timestamp + FROM biblioitems + "; + $sql .= " JOIN oai_sets_biblios ON biblioitems.biblionumber = oai_sets_biblios.biblionumber " if defined $set; + $sql .= " WHERE DATE(timestamp) >= ? AND DATE(timestamp) <= ? "; + $sql .= " AND oai_sets_biblios.set_id = ? " if defined $set; + $sql .= " + LIMIT $repository->{'koha_max_count'} + OFFSET $token->{'offset'} + "; + my $sth = $dbh->prepare( $sql ); + my @bind_params = ($token->{'from'}, $token->{'until'}); + push @bind_params, $set->{'id'} if defined $set; + $sth->execute( @bind_params ); + + my $pos = $token->{offset}; + while ( my ($biblionumber, $timestamp) = $sth->fetchrow ) { + $timestamp =~ s/ /T/, $timestamp .= 'Z'; + $self->identifier( new HTTP::OAI::Header( + identifier => $repository->{ koha_identifier} . ':' . $biblionumber, + datestamp => $timestamp, + ) ); + $pos++; + } + $self->resumptionToken( + new C4::OAI::ResumptionToken( + metadataPrefix => $token->{metadata_prefix}, + from => $token->{from}, + until => $token->{until}, + offset => $pos, + set => $token->{set} + ) + ) if ($pos > $token->{offset}); + + return $self; } -# parse the resumptionTocken -sub parseResumptionToken { -my ($from, $until, $metadataPrefix, $resumptionToken) = @_; -my $offset = 0; +# __END__ C4::OAI::ListIdentifiers - if( $resumptionToken ) { - ($metadataPrefix,$offset,$from,$until) = split( ':', $resumptionToken ); - } +package C4::OAI::Description; + +use strict; +use warnings; +use HTTP::OAI; +use HTTP::OAI::SAXHandler qw/ :SAX /; + +sub new { + my ( $class, %args ) = @_; + + my $self = {}; + + if(my $setDescription = $args{setDescription}) { + $self->{setDescription} = $setDescription; + } + if(my $handler = $args{handler}) { + $self->{handler} = $handler; + } - $from = "1970-01-01" unless( $from ); - $until = &now unless( $until ); - ($metadataPrefix, $offset, $from, $until ); + bless $self, $class; + return $self; } -=head2 Archive_ListSets +sub set_handler { + my ( $self, $handler ) = @_; -return the full list Set to the I query. Data are from the 'OAI-PMH:Set' preference. + $self->{handler} = $handler if $handler; -=cut + return $self; +} + +sub generate { + my ( $self ) = @_; -# get full list of sets from the archive -sub Archive_ListSets -{ - &Set(); + g_data_element($self->{handler}, 'http://www.openarchives.org/OAI/2.0/', 'setDescription', {}, $self->{setDescription}); + + return $self; } - -=head2 Archive_GetRecord -This method select the record specified as its first parameter from the koha I -table and return a reference to a MARC::Record::KOHADC object. +# __END__ C4::OAI::Description -=cut +package C4::OAI::ListSets; + +use strict; +use warnings; +use HTTP::OAI; +use C4::OAI::Sets; -# get a single record from the archive -sub Archive_GetRecord -{ - my ($self, $identifier, $metadataFormat) = @_; - my $dbh = C4::Context->dbh; - my $sth = $dbh->prepare("SELECT biblionumber,timestamp FROM biblio WHERE biblionumber=?"); - my $prefixID = C4::Context->preference("OAI-PMH:archiveID"); $prefixID=qr{$prefixID:}; - - $identifier =~ s/^$prefixID//; - - $sth->execute( $identifier ); - - if( my $r = $sth->fetchrow_hashref() ) { - my $marc = new MARC::Record::KOHADC( ::GetMarcBiblio( $identifier ) ); - if( $marc ) { - $marc->{'biblio.timestamp'} = $r->{'timestamp'}; - return $marc ; - } - else { - warn("Archive_GetRecord : no MARC record for " . C4::Context->preference("OAI-PMH:archiveID") . ":" . $identifier); - } - } - - $self->AddError ('idDoesNotExist', 'The value of the identifier argument is unknown or illegal in this repository'); - undef; +use base ("HTTP::OAI::ListSets"); + +sub new { + my ( $class, $repository, %args ) = @_; + + my $self = HTTP::OAI::ListSets->new(%args); + + my $token = C4::OAI::ResumptionToken->new(%args); + my $sets = GetOAISets; + my $pos = 0; + foreach my $set (@$sets) { + if ($pos < $token->{offset}) { + $pos++; + next; + } + my @descriptions; + foreach my $desc (@{$set->{'descriptions'}}) { + push @descriptions, C4::OAI::Description->new( + setDescription => $desc, + ); + } + $self->set( + HTTP::OAI::Set->new( + setSpec => $set->{'spec'}, + setName => $set->{'name'}, + setDescription => \@descriptions, + ) + ); + $pos++; + last if ($pos + 1 - $token->{offset}) > $repository->{koha_max_count}; + } + + $self->resumptionToken( + new C4::OAI::ResumptionToken( + metadataPrefix => $token->{metadata_prefix}, + offset => $pos + ) + ) if ( $pos > $token->{offset} ); + + return $self; } -=head2 Archive_ListRecords +# __END__ C4::OAI::ListSets; -This method return a list of 'MaxCount' references to MARC::Record::KOHADC object build from the -koha I table according to its parameters : set, from and until date, metadata prefix -and resumption token. +package C4::OAI::ListRecords; -=cut +use strict; +use warnings; +use HTTP::OAI; +use C4::OAI::Sets; + +use base ("HTTP::OAI::ListRecords"); + + +sub new { + my ($class, $repository, %args) = @_; + + my $self = HTTP::OAI::ListRecords->new(%args); -# list metadata records from the archive -sub Archive_ListRecords -{ - my ($self, $set, $from, $until, $metadataPrefix, $resumptionToken) = @_; - - my @allrows = (); - my $marc; - my $offset; - my $tokenInfo; - my $dbh = C4::Context->dbh; - my $sth = $dbh->prepare("SELECT biblionumber,timestamp FROM biblio WHERE DATE(timestamp) >= ? and DATE(timestamp) <= ? LIMIT ? OFFSET ?"); - my $count; - - ($metadataPrefix, $offset, $from, $until ) = &parseResumptionToken($from, $until, $metadataPrefix, $resumptionToken); - -#warn( "Archive_ListRecords : $set, $from, $until, $metadataPrefix, $resumptionToken\n"); - $sth->execute( $from,$until,$self->{'MaxCount'}?$self->{'MaxCount'}:100000, $offset ); - - while( my $r = $sth->fetchrow_hashref() ) { - my $marc = new MARC::Record::KOHADC( ::GetMarcBiblio( $r->{'biblionumber'} ) ); - unless( $marc ) { # somme time there is problems within koha, and we can't get valid marc record - warn("Archive_ListRecords : no MARC record for " . C4::Context->preference("OAI-PMH:archiveID") .":" . $r->{'biblionumber'} ); - next; - } - $marc->{'biblio.timestamp'} = $r->{'timestamp'}; - push( @allrows, $marc ); - } - - $sth = $dbh->prepare("SELECT count(*) FROM biblioitems WHERE DATE(timestamp) >= ? and DATE(timestamp) <= ?"); - $sth->execute($from, $until); - ( $count ) = $sth->fetchrow_array(); - - unless( @allrows ) { - $self->AddError ('noRecordsMatch', 'The combination of the values of arguments results in an empty set'); - } - - if( $offset + $self->{'MaxCount'} < $count ) { # Not at the end - $offset = $offset + $self->{'MaxCount'}; - $resumptionToken = &buildResumptionToken($metadataPrefix,$offset,$from,$until); - $tokenInfo = { 'completeListSize' => $count, 'cursor' => $offset }; - } - else { - $resumptionToken = ''; - $tokenInfo = {}; - } - ( \@allrows, $resumptionToken, $metadataPrefix, $tokenInfo ); + my $token = new C4::OAI::ResumptionToken( %args ); + my $dbh = C4::Context->dbh; + my $set; + if(defined $token->{'set'}) { + $set = GetOAISetBySpec($token->{'set'}); + } + my $sql = " + SELECT biblioitems.biblionumber, biblioitems.marcxml, biblioitems.timestamp + FROM biblioitems + "; + $sql .= " JOIN oai_sets_biblios ON biblioitems.biblionumber = oai_sets_biblios.biblionumber " if defined $set; + $sql .= " WHERE DATE(timestamp) >= ? AND DATE(timestamp) <= ? "; + $sql .= " AND oai_sets_biblios.set_id = ? " if defined $set; + $sql .= " + LIMIT $repository->{'koha_max_count'} + OFFSET $token->{'offset'} + "; + + my $sth = $dbh->prepare( $sql ); + my @bind_params = ($token->{'from'}, $token->{'until'}); + push @bind_params, $set->{'id'} if defined $set; + $sth->execute( @bind_params ); + + my $pos = $token->{offset}; + while ( my ($biblionumber, $marcxml, $timestamp) = $sth->fetchrow ) { + my $oai_sets = GetOAISetsBiblio($biblionumber); + my @setSpecs; + foreach (@$oai_sets) { + push @setSpecs, $_->{spec}; + } + $self->record( C4::OAI::Record->new( + $repository, $marcxml, $timestamp, \@setSpecs, + identifier => $repository->{ koha_identifier } . ':' . $biblionumber, + metadataPrefix => $token->{metadata_prefix} + ) ); + $pos++; + } + $self->resumptionToken( + new C4::OAI::ResumptionToken( + metadataPrefix => $token->{metadata_prefix}, + from => $token->{from}, + until => $token->{until}, + offset => $pos, + set => $token->{set} + ) + ) if ($pos > $token->{offset}); + + return $self; } -package main; +# __END__ C4::OAI::ListRecords -=head1 Main package -The I
function is the starting point of the service. The first step is -to verify if the service is enable using the 'OAI-PMH' preference value -(See Koha systeme preferences). -If the service is enable, it create a new instance of the OAI::KOHA data -provider (see before) and run the service. +package C4::OAI::Repository; + +use base ("HTTP::OAI::Repository"); + +use strict; +use warnings; + +use HTTP::OAI; +use HTTP::OAI::Repository qw/:validate/; + +use XML::SAX::Writer; +use XML::LibXML; +use XML::LibXSLT; +use YAML::Syck qw( LoadFile ); +use CGI qw/:standard -oldstyle_urls/; + +use C4::Context; +use C4::Biblio; -=cut -sub disable { - print "Status:404 OAI-PMH service is disabled\n"; - print "Content-type: text/plain\n\n"; +sub new { + my ($class, %args) = @_; + my $self = $class->SUPER::new(%args); - print "OAI-PMH service is disable.\n"; + $self->{ koha_identifier } = C4::Context->preference("OAI-PMH:archiveID"); + $self->{ koha_max_count } = C4::Context->preference("OAI-PMH:MaxCount"); + $self->{ koha_metadata_format } = ['oai_dc', 'marcxml']; + $self->{ koha_stylesheet } = { }; # Build when needed + + # Load configuration file if defined in OAI-PMH:ConfFile syspref + if ( my $file = C4::Context->preference("OAI-PMH:ConfFile") ) { + $self->{ conf } = LoadFile( $file ); + my @formats = keys %{ $self->{conf}->{format} }; + $self->{ koha_metadata_format } = \@formats; + } + + # Check for grammatical errors in the request + my @errs = validate_request( CGI::Vars() ); + + # Is metadataPrefix supported by the respository? + my $mdp = param('metadataPrefix') || ''; + if ( $mdp && !grep { $_ eq $mdp } @{$self->{ koha_metadata_format }} ) { + push @errs, new HTTP::OAI::Error( + code => 'cannotDisseminateFormat', + message => "Dissemination as '$mdp' is not supported", + ); + } + + my $response; + if ( @errs ) { + $response = HTTP::OAI::Response->new( + requestURL => self_url(), + errors => \@errs, + ); + } + else { + my %attr = CGI::Vars(); + my $verb = delete( $attr{verb} ); + if ( $verb eq 'ListSets' ) { + $response = C4::OAI::ListSets->new($self, %attr); + } + elsif ( $verb eq 'Identify' ) { + $response = C4::OAI::Identify->new( $self ); + } + elsif ( $verb eq 'ListMetadataFormats' ) { + $response = C4::OAI::ListMetadataFormats->new( $self ); + } + elsif ( $verb eq 'GetRecord' ) { + $response = C4::OAI::GetRecord->new( $self, %attr ); + } + elsif ( $verb eq 'ListRecords' ) { + $response = C4::OAI::ListRecords->new( $self, %attr ); + } + elsif ( $verb eq 'ListIdentifiers' ) { + $response = C4::OAI::ListIdentifiers->new( $self, %attr ); + } + } + + $response->set_handler( XML::SAX::Writer->new( Output => *STDOUT ) ); + $response->generate; + + bless $self, $class; + return $self; } -sub main -{ - return &disable() unless( C4::Context->preference('OAI-PMH') ); - my $OAI = new OAI::KOHA(); - $OAI->Run; - $OAI->dispose; +sub stylesheet { + my ( $self, $format ) = @_; + + my $stylesheet = $self->{ koha_stylesheet }->{ $format }; + unless ( $stylesheet ) { + my $xsl_file = $self->{ conf } + ? $self->{ conf }->{ format }->{ $format }->{ xsl_file } + : ( C4::Context->config('intrahtdocs') . + '/prog/en/xslt/' . + C4::Context->preference('marcflavour') . + 'slim2OAIDC.xsl' ); + my $parser = XML::LibXML->new(); + my $xslt = XML::LibXSLT->new(); + my $style_doc = $parser->parse_file( $xsl_file ); + $stylesheet = $xslt->parse_stylesheet( $style_doc ); + $self->{ koha_stylesheet }->{ $format } = $stylesheet; + } + + return $stylesheet; } -main; -1; + +=head1 NAME + +C4::OAI::Repository - Handles OAI-PMH requests for a Koha database. + +=head1 SYNOPSIS + + use C4::OAI::Repository; + + my $repository = C4::OAI::Repository->new(); + +=head1 DESCRIPTION + +This object extend HTTP::OAI::Repository object. +It accepts OAI-PMH HTTP requests and returns result. + +This OAI-PMH server can operate in a simple mode and extended one. + +In simple mode, repository configuration comes entirely from Koha system +preferences (OAI-PMH:archiveID and OAI-PMH:MaxCount) and the server returns +records in marcxml or dublin core format. Dublin core records are created from +koha marcxml records tranformed with XSLT. Used XSL file is located in +koha-tmpl/intranet-tmpl/prog/en/xslt directory and choosed based on marcflavour, +respecively MARC21slim2OAIDC.xsl for MARC21 and MARC21slim2OAIDC.xsl for +UNIMARC. + +In extende mode, it's possible to parameter other format than marcxml or Dublin +Core. A new syspref OAI-PMH:ConfFile specify a YAML configuration file which +list available metadata formats and XSL file used to create them from marcxml +records. If this syspref isn't set, Koha OAI server works in simple mode. A +configuration file koha-oai.conf can look like that: + + --- + format: + vs: + metadataPrefix: vs + metadataNamespace: http://veryspecial.tamil.fr/vs/format-pivot/1.1/vs + schema: http://veryspecial.tamil.fr/vs/format-pivot/1.1/vs.xsd + xsl_file: /usr/local/koha/xslt/vs.xsl + marcxml: + metadataPrefix: marxml + metadataNamespace: http://www.loc.gov/MARC21/slim http://www.loc.gov/standards/marcxml/schema/MARC21slim + schema: http://www.loc.gov/MARC21/slim http://www.loc.gov/standards/marcxml/schema/MARC21slim.xsd + oai_dc: + metadataPrefix: oai_dc + metadataNamespace: http://www.openarchives.org/OAI/2.0/oai_dc/ + schema: http://www.openarchives.org/OAI/2.0/oai_dc.xsd + xsl_file: /usr/local/koha/koha-tmpl/intranet-tmpl/xslt/UNIMARCslim2OAIDC.xsl + +=cut + + +