Koha-clms/Koha/OAI/Server/Repository.pm

236 lines
8.0 KiB
Perl

# Copyright Tamil s.a.r.l. 2008-2015
# Copyright Biblibre 2008-2015
# Copyright The National Library of Finland, University of Helsinki 2016
#
# 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 3 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, see <http://www.gnu.org/licenses>.
package Koha::OAI::Server::Repository;
use Modern::Perl;
use HTTP::OAI;
use HTTP::OAI::Repository qw( validate_request );
use base ("HTTP::OAI::Repository");
use Koha::RecordProcessor;
use Koha::OAI::Server::Identify;
use Koha::OAI::Server::ListSets;
use Koha::OAI::Server::ListMetadataFormats;
use Koha::OAI::Server::GetRecord;
use Koha::OAI::Server::ListRecords;
use Koha::OAI::Server::ListIdentifiers;
use XML::SAX::Writer;
use YAML::XS;
use CGI qw/:standard -oldstyle_urls/;
use C4::Context;
use C4::Biblio qw( GetFrameworkCode );
use C4::Charset qw( StripNonXmlChars );
use Koha::XSLT::Base;
use Koha::Biblios;
use MARC::Record;
=head1 NAME
Koha::OAI::Server::Repository - Handles OAI-PMH requests for a Koha database.
=head1 SYNOPSIS
use Koha::OAI::Server::Repository;
my $repository = Koha::OAI::Server::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 transformed with XSLT. Used XSL file is located in koha-
tmpl/intranet-tmpl/prog/en/xslt directory and chosen based on marcflavour,
respecively MARC21slim2OAIDC.xsl for MARC21 and MARC21slim2OAIDC.xsl for
UNIMARC.
In extended 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
marc21:
metadataPrefix: marc21
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
include_items: 1
marcxml:
metadataPrefix: marcxml
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
include_items: 1
expanded_avs: 1
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
Note the 'include_items' parameter which is the only mean to return item-level info.
=cut
sub new {
my ($class, %args) = @_;
my $self = $class->SUPER::new(%args);
$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', 'marc21', 'marcxml'];
$self->{ xslt_engine } = Koha::XSLT::Base->new;
# Load configuration file if defined in OAI-PMH:ConfFile syspref
if ( my $file = C4::Context->preference("OAI-PMH:ConfFile") ) {
$self->{ conf } = YAML::XS::LoadFile( $file );
my @formats = keys %{ $self->{conf}->{format} };
$self->{ koha_metadata_format } = \@formats;
}
# OAI-PMH handles dates in UTC, so do that on the database level to avoid need for
# any conversions
my $sth = C4::Context->dbh->prepare('SELECT @@session.time_zone');
$sth->execute();
my ( $orig_tz ) = $sth->fetchrow();
$self->{ mysql_orig_tz } = $orig_tz;
C4::Context->dbh->prepare("SET time_zone='+00:00'")->execute();
# Check for grammatical errors in the request
my @errs = validate_request( CGI::Vars() );
# Is metadataPrefix supported by the repository?
my $mdp = param('metadataPrefix') || '';
if ( $mdp && !grep { $_ eq $mdp } @{$self->{ koha_metadata_format }} ) {
push @errs, HTTP::OAI::Error->new(
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};
my $class = "Koha::OAI::Server::$verb";
$response = $class->new($self, %attr);
}
$response->set_handler( XML::SAX::Writer->new( Output => *STDOUT ) );
$response->xslt( "/opac-tmpl/xslt/OAI.xslt" );
$response->generate;
bless $self, $class;
return $self;
}
sub DESTROY {
my ( $self ) = @_;
# Reset time zone to the original value
C4::Context->dbh->prepare("SET time_zone='" . $self->{ mysql_orig_tz } . "'")->execute()
if $self->{ mysql_orig_tz };
}
sub get_biblio_marcxml {
my ( $self, $biblionumber, $format ) = @_;
my $with_items = 0;
my $expanded_avs = 0;
if ( my $conf = $self->{conf} ) {
$with_items = $conf->{format}->{$format}->{include_items };
$expanded_avs = $conf->{format}->{$format}->{expanded_avs};
}
my $biblio = Koha::Biblios->find($biblionumber);
my $record;
# Koha::Biblio::Metadata->record throws an exception on bad encoding,
# we don't want OAI harvests to die, so we catch and warn, and try to clean the record
eval { $record = $biblio->metadata->record( { embed_items => $with_items, opac => 1 } ); };
my $decoding_error;
if ($@) {
my $exception = $@;
$exception->rethrow unless ( $exception->isa('Koha::Exceptions::Metadata::Invalid') );
$decoding_error = "INVALID_METADATA";
$record = $biblio->metadata->record_strip_nonxml( { embed_items => $with_items, opac => 1 } );
}
if ( $record && $expanded_avs ) {
my $frameworkcode = GetFrameworkCode($biblionumber) || '';
my $record_processor = Koha::RecordProcessor->new(
{
filters => [ 'ExpandCodedFields' ],
options => {
interface => 'opac',
frameworkcode => $frameworkcode
}
}
);
$record_processor->process($record);
}
return ( $record ? $record->as_xml_record( C4::Context->preference('marcflavour') ) : undef, $decoding_error );
}
sub stylesheet {
my ( $self, $format ) = @_;
my $xsl_file = $self->{ conf }
? $self->{ conf }->{ format }->{ $format }->{ xsl_file }
: ( C4::Context->config('intrahtdocs') .
'/prog/en/xslt/' .
C4::Context->preference('marcflavour') .
'slim2OAIDC.xsl'
);
return $xsl_file;
}
sub items_included {
my ( $self, $format ) = @_;
if ( my $conf = $self->{ conf } ) {
return $conf->{ format }->{ $format }->{ include_items };
}
return 0;
}
1;