X-Git-Url: http://koha-dev.rot13.org:8081/gitweb/?a=blobdiff_plain;f=opac%2Foai.pl;h=bad250a2f9fe5059ea2bd9f1df0072b65371aecf;hb=3b0d4e04e09f37e8dcbaba71abd47980bfef7059;hp=37b9f6f66e8efbf5640119d0dbfad07318409c83;hpb=1e6b61cdb1dcfbf05abf1a3cd0d5c36439e0a75c;p=koha_gimpoz diff --git a/opac/oai.pl b/opac/oai.pl index 37b9f6f66e..bad250a2f9 100755 --- a/opac/oai.pl +++ b/opac/oai.pl @@ -2,7 +2,6 @@ use strict; use warnings; -use diagnostics; use CGI qw/:standard -oldstyle_urls/; use vars qw( $GZIP ); @@ -11,7 +10,7 @@ use C4::Context; BEGIN { eval { require PerlIO::gzip }; - $GZIP = $@ ? 0 : 1; + $GZIP = ($@) ? 0 : 1; } unless ( C4::Context->preference('OAI-PMH') ) { @@ -41,7 +40,7 @@ else { ); } -binmode( STDOUT, ":utf8" ); +binmode STDOUT, ':encoding(UTF-8)'; my $repository = C4::OAI::Repository->new(); # __END__ Main Prog @@ -59,7 +58,6 @@ package C4::OAI::ResumptionToken; use strict; use warnings; -use diagnostics; use HTTP::OAI; use base ("HTTP::OAI::ResumptionToken"); @@ -70,9 +68,9 @@ sub new { my $self = $class->SUPER::new(%args); - my ($metadata_prefix, $offset, $from, $until); + my ($metadata_prefix, $offset, $from, $until, $set); if ( $args{ resumptionToken } ) { - ($metadata_prefix, $offset, $from, $until) + ($metadata_prefix, $offset, $from, $until, $set) = split( ':', $args{resumptionToken} ); } else { @@ -84,15 +82,17 @@ sub new { $until = sprintf( "%.4d-%.2d-%.2d", $year+1900, $mon+1,$mday ); } $offset = $args{ offset } || 0; + $set = $args{set}; } $self->{ metadata_prefix } = $metadata_prefix; $self->{ offset } = $offset; $self->{ from } = $from; $self->{ until } = $until; + $self->{ set } = $set; $self->resumptionToken( - join( ':', $metadata_prefix, $offset, $from, $until ) ); + join( ':', $metadata_prefix, $offset, $from, $until, $set ) ); $self->cursor( $offset ); return $self; @@ -106,7 +106,6 @@ package C4::OAI::Identify; use strict; use warnings; -use diagnostics; use HTTP::OAI; use C4::Context; @@ -123,8 +122,15 @@ sub new { MaxCount => C4::Context->preference("OAI-PMH:MaxCount"), granularity => 'YYYY-MM-DD', earliestDatestamp => '0001-01-01', + deletedRecord => 'no', ); - $self->description( "Koha OAI Repository" ); + + # 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" ); + $self->compression( 'gzip' ); return $self; @@ -138,7 +144,6 @@ package C4::OAI::ListMetadataFormats; use strict; use warnings; -use diagnostics; use HTTP::OAI; use base ("HTTP::OAI::ListMetadataFormats"); @@ -148,16 +153,27 @@ sub new { my $self = $class->SUPER::new(); - $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' - ) ); + 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; } @@ -170,14 +186,13 @@ package C4::OAI::Record; use strict; use warnings; -use diagnostics; use HTTP::OAI; use HTTP::OAI::Metadata::OAI_DC; use base ("HTTP::OAI::Record"); sub new { - my ($class, $repository, $marcxml, $timestamp, %args) = @_; + my ($class, $repository, $marcxml, $timestamp, $setSpecs, %args) = @_; my $self = $class->SUPER::new(%args); @@ -187,10 +202,18 @@ sub new { datestamp => $timestamp, ) ); + foreach my $setSpec (@$setSpecs) { + $self->header->setSpec($setSpec); + } + my $parser = XML::LibXML->new(); my $record_dom = $parser->parse_string( $marcxml ); - if ( $args{metadataPrefix} ne 'marcxml' ) { - $record_dom = $repository->oai_dc_stylesheet()->transform( $record_dom ); + 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 ) ); @@ -205,8 +228,8 @@ package C4::OAI::GetRecord; use strict; use warnings; -use diagnostics; use HTTP::OAI; +use C4::OAI::Sets; use base ("HTTP::OAI::GetRecord"); @@ -235,9 +258,15 @@ sub new { ); } + my $oai_sets = GetOAISetsBiblio($biblionumber); + my @setSpecs; + foreach (@$oai_sets) { + push @setSpecs, $_->{spec}; + } + #$self->header( HTTP::OAI::Header->new( identifier => $args{identifier} ) ); $self->record( C4::OAI::Record->new( - $repository, $marcxml, $timestamp, %args ) ); + $repository, $marcxml, $timestamp, \@setSpecs, %args ) ); return $self; } @@ -250,8 +279,8 @@ package C4::OAI::ListIdentifiers; use strict; use warnings; -use diagnostics; use HTTP::OAI; +use C4::OAI::Sets; use base ("HTTP::OAI::ListIdentifiers"); @@ -263,42 +292,148 @@ sub new { my $token = new C4::OAI::ResumptionToken( %args ); my $dbh = C4::Context->dbh; - my $sql = "SELECT biblionumber, timestamp - FROM biblioitems - WHERE timestamp >= ? AND timestamp <= ? - LIMIT " . $repository->{koha_max_count} . " - OFFSET " . $token->{offset}; + 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 ); - $sth->execute( $token->{from}, $token->{until} ); + 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'; + 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 ) ); + } + $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; } # __END__ C4::OAI::ListIdentifiers +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; + } + + bless $self, $class; + return $self; +} + +sub set_handler { + my ( $self, $handler ) = @_; + + $self->{handler} = $handler if $handler; + + return $self; +} + +sub generate { + my ( $self ) = @_; + + g_data_element($self->{handler}, 'http://www.openarchives.org/OAI/2.0/', 'setDescription', {}, $self->{setDescription}); + + return $self; +} + +# __END__ C4::OAI::Description + +package C4::OAI::ListSets; + +use strict; +use warnings; +use HTTP::OAI; +use C4::OAI::Sets; + +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; +} +# __END__ C4::OAI::ListSets; package C4::OAI::ListRecords; use strict; use warnings; -use diagnostics; use HTTP::OAI; +use C4::OAI::Sets; use base ("HTTP::OAI::ListRecords"); @@ -310,28 +445,50 @@ sub new { my $token = new C4::OAI::ResumptionToken( %args ); my $dbh = C4::Context->dbh; - my $sql = "SELECT biblionumber, marcxml, timestamp - FROM biblioitems - WHERE timestamp >= ? AND timestamp <= ? - LIMIT " . $repository->{koha_max_count} . " - OFFSET " . $token->{offset}; + 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 ); - $sth->execute( $token->{from}, $token->{until} ); + 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 ) { + 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, + $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 ) ); + } + $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; } @@ -346,7 +503,6 @@ use base ("HTTP::OAI::Repository"); use strict; use warnings; -use diagnostics; use HTTP::OAI; use HTTP::OAI::Repository qw/:validate/; @@ -354,30 +510,13 @@ 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; -=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. - -=cut - - - sub new { my ($class, %args) = @_; my $self = $class->SUPER::new(%args); @@ -385,6 +524,14 @@ sub new { $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() ); @@ -408,14 +555,8 @@ sub new { else { my %attr = CGI::Vars(); my $verb = delete( $attr{verb} ); - if ( grep { $_ eq $verb } qw( ListSets ) ) { - $response = HTTP::OAI::Response->new( - requestURL => $self->self_url(), - errors => [ new HTTP::OAI::Error( - code => 'noSetHierarchy', - message => "Koha repository doesn't have sets", - ) ] , - ); + if ( $verb eq 'ListSets' ) { + $response = C4::OAI::ListSets->new($self, %attr); } elsif ( $verb eq 'Identify' ) { $response = C4::OAI::Identify->new( $self ); @@ -442,30 +583,78 @@ sub new { } -# -# XSLT stylesheet used to transform MARCXML record into OAI Dublin Core. -# The object is constructed the fist time this method is called. -# -# Styleeet file is located in /koha-tmpl/intranet-tmpl/prog/en/xslt/ directory. -# Its name is constructed with 'marcflavour' syspref: -# - MARC21slim2OAIDC.xsl -# - UNIMARCslim2OADIC.xsl -# -sub oai_dc_stylesheet { - my $self = shift; - - unless ( $self->{ oai_dc_stylesheet } ) { - my $xslt_file = C4::Context->config('intranetdir') . - "/koha-tmpl/intranet-tmpl/prog/en/xslt/" . - C4::Context->preference('marcflavour') . - "slim2OAIDC.xsl"; +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( $xslt_file ); - my $stylesheet = $xslt->parse_stylesheet( $style_doc ); - $self->{ oai_dc_stylesheet } = $stylesheet; + my $style_doc = $parser->parse_file( $xsl_file ); + $stylesheet = $xslt->parse_stylesheet( $style_doc ); + $self->{ koha_stylesheet }->{ $format } = $stylesheet; } - return $self->{ oai_dc_stylesheet }; + return $stylesheet; } + + +=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 + + +