Bug 6440: Implement OAI-PMH Sets

New sql tables:
  - oai_sets: contains the list of sets, described by a spec and a name
  - oai_sets_descriptions: contains a list of descriptions for each set
  - oai_sets_mappings: conditions on marc fields to match for biblio to be
    in a set
  - oai_sets_biblios: list of biblionumbers for each set

New admin page: allow to configure sets:
  - Creation, deletion, modification of spec, name and descriptions
  - Define mappings which will be used for building oai sets

Implements OAI Sets in opac/oai.pl:
  - ListSets, ListIdentifiers, ListRecords, GetRecord

New script misc/migration_tools/build_oai_sets.pl:
  - Retrieve marcxml from all biblios and test if they belong to defined
    sets. The oai_sets_biblios table is then updated accordingly

New system preference OAI-PMH:AutoUpdateSets. If on, update sets
automatically when a biblio is created or updated.

Use OPACBaseURL in oai_dc xslt
This commit is contained in:
Julian Maurice 2012-01-23 16:25:51 +01:00 committed by Paul Poulain
parent aac5379b22
commit 3b0d4e04e0
19 changed files with 1581 additions and 57 deletions

View File

@ -35,6 +35,7 @@ use C4::Log; # logaction
use C4::ClassSource;
use C4::Charset;
use C4::Linker;
use C4::OAI::Sets;
use vars qw($VERSION @ISA @EXPORT);
@ -268,6 +269,11 @@ sub AddBiblio {
# now add the record
ModBiblioMarc( $record, $biblionumber, $frameworkcode ) unless $defer_marc_save;
# update OAI-PMH sets
if(C4::Context->preference("OAI-PMH:AutoUpdateSets")) {
C4::OAI::Sets::UpdateOAISetsBiblio($biblionumber, $record);
}
logaction( "CATALOGUING", "ADD", $biblionumber, "biblio" ) if C4::Context->preference("CataloguingLog");
return ( $biblionumber, $biblioitemnumber );
}
@ -339,6 +345,12 @@ sub ModBiblio {
# modify the other koha tables
_koha_modify_biblio( $dbh, $oldbiblio, $frameworkcode );
_koha_modify_biblioitem_nonmarc( $dbh, $oldbiblio );
# update OAI-PMH sets
if(C4::Context->preference("OAI-PMH:AutoUpdateSets")) {
C4::OAI::Sets::UpdateOAISetsBiblio($biblionumber, $record);
}
return 1;
}

589
C4/OAI/Sets.pm Normal file
View File

@ -0,0 +1,589 @@
package C4::OAI::Sets;
# Copyright 2011 BibLibre
#
# 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.
=head1 NAME
C4::OAI::Sets - OAI Sets management functions
=head1 DESCRIPTION
C4::OAI::Sets contains functions for managing storage and editing of OAI Sets.
OAI Set description can be found L<here|http://www.openarchives.org/OAI/openarchivesprotocol.html#Set>
=cut
use Modern::Perl;
use C4::Context;
use vars qw(@ISA @EXPORT);
BEGIN {
require Exporter;
@ISA = qw(Exporter);
@EXPORT = qw(
&GetOAISets &GetOAISet &GetOAISetBySpec &ModOAISet &DelOAISet &AddOAISet
&GetOAISetsMappings &GetOAISetMappings &ModOAISetMappings
&GetOAISetsBiblio &ModOAISetsBiblios &AddOAISetsBiblios
&CalcOAISetsBiblio &UpdateOAISetsBiblio
);
}
=head1 FUNCTIONS
=head2 GetOAISets
$oai_sets = GetOAISets;
GetOAISets return a array reference of hash references describing the sets.
The hash references looks like this:
{
'name' => 'set name',
'spec' => 'set spec',
'descriptions' => [
'description 1',
'description 2',
...
]
}
=cut
sub GetOAISets {
my $dbh = C4::Context->dbh;
my $query = qq{
SELECT * FROM oai_sets
};
my $sth = $dbh->prepare($query);
$sth->execute;
my $results = $sth->fetchall_arrayref({});
$query = qq{
SELECT description
FROM oai_sets_descriptions
WHERE set_id = ?
};
$sth = $dbh->prepare($query);
foreach my $set (@$results) {
$sth->execute($set->{'id'});
my $desc = $sth->fetchall_arrayref({});
foreach (@$desc) {
push @{$set->{'descriptions'}}, $_->{'description'};
}
}
return $results;
}
=head2 GetOAISet
$set = GetOAISet($set_id);
GetOAISet returns a hash reference describing the set with the given set_id.
See GetOAISets to see what the hash looks like.
=cut
sub GetOAISet {
my ($set_id) = @_;
return unless $set_id;
my $dbh = C4::Context->dbh;
my $query = qq{
SELECT *
FROM oai_sets
WHERE id = ?
};
my $sth = $dbh->prepare($query);
$sth->execute($set_id);
my $set = $sth->fetchrow_hashref;
$query = qq{
SELECT description
FROM oai_sets_descriptions
WHERE set_id = ?
};
$sth = $dbh->prepare($query);
$sth->execute($set->{'id'});
my $desc = $sth->fetchall_arrayref({});
foreach (@$desc) {
push @{$set->{'descriptions'}}, $_->{'description'};
}
return $set;
}
=head2 GetOAISetBySpec
my $set = GetOAISetBySpec($setSpec);
Returns a hash describing the set whose spec is $setSpec
=cut
sub GetOAISetBySpec {
my $setSpec = shift;
return unless defined $setSpec;
my $dbh = C4::Context->dbh;
my $query = qq{
SELECT *
FROM oai_sets
WHERE spec = ?
LIMIT 1
};
my $sth = $dbh->prepare($query);
$sth->execute($setSpec);
return $sth->fetchrow_hashref;
}
=head2 ModOAISet
my $set = {
'id' => $set_id, # mandatory
'spec' => $spec, # mandatory
'name' => $name, # mandatory
'descriptions => \@descriptions, # optional, [] to remove descriptions
};
ModOAISet($set);
ModOAISet modify a set in the database.
=cut
sub ModOAISet {
my ($set) = @_;
return unless($set && $set->{'spec'} && $set->{'name'});
if(!defined $set->{'id'}) {
warn "Set ID not defined, can't modify the set";
return;
}
my $dbh = C4::Context->dbh;
my $query = qq{
UPDATE oai_sets
SET spec = ?,
name = ?
WHERE id = ?
};
my $sth = $dbh->prepare($query);
$sth->execute($set->{'spec'}, $set->{'name'}, $set->{'id'});
if($set->{'descriptions'}) {
$query = qq{
DELETE FROM oai_sets_descriptions
WHERE set_id = ?
};
$sth = $dbh->prepare($query);
$sth->execute($set->{'id'});
if(scalar @{$set->{'descriptions'}} > 0) {
$query = qq{
INSERT INTO oai_sets_descriptions (set_id, description)
VALUES (?,?)
};
$sth = $dbh->prepare($query);
foreach (@{ $set->{'descriptions'} }) {
$sth->execute($set->{'id'}, $_) if $_;
}
}
}
}
=head2 DelOAISet
DelOAISet($set_id);
DelOAISet remove the set with the given set_id
=cut
sub DelOAISet {
my ($set_id) = @_;
return unless $set_id;
my $dbh = C4::Context->dbh;
my $query = qq{
DELETE oai_sets, oai_sets_descriptions, oai_sets_mappings
FROM oai_sets
LEFT JOIN oai_sets_descriptions ON oai_sets_descriptions.set_id = oai_sets.id
LEFT JOIN oai_sets_mappings ON oai_sets_mappings.set_id = oai_sets.id
WHERE oai_sets.id = ?
};
my $sth = $dbh->prepare($query);
$sth->execute($set_id);
}
=head2 AddOAISet
my $set = {
'id' => $set_id, # mandatory
'spec' => $spec, # mandatory
'name' => $name, # mandatory
'descriptions => \@descriptions, # optional
};
my $set_id = AddOAISet($set);
AddOAISet adds a new set and returns its id, or undef if something went wrong.
=cut
sub AddOAISet {
my ($set) = @_;
return unless($set && $set->{'spec'} && $set->{'name'});
my $set_id;
my $dbh = C4::Context->dbh;
my $query = qq{
INSERT INTO oai_sets (spec, name)
VALUES (?,?)
};
my $sth = $dbh->prepare($query);
if( $sth->execute($set->{'spec'}, $set->{'name'}) ) {
$set_id = $dbh->last_insert_id(undef, undef, 'oai_sets', undef);
if($set->{'descriptions'}) {
$query = qq{
INSERT INTO oai_sets_descriptions (set_id, description)
VALUES (?,?)
};
$sth = $dbh->prepare($query);
foreach( @{ $set->{'descriptions'} } ) {
$sth->execute($set_id, $_) if $_;
}
}
} else {
warn "AddOAISet failed";
}
return $set_id;
}
=head2 GetOAISetsMappings
my $mappings = GetOAISetsMappings;
GetOAISetsMappings returns mappings for all OAI Sets.
Mappings define how biblios are categorized in sets.
A mapping is defined by three properties:
{
marcfield => 'XXX', # the MARC field to check
marcsubfield => 'Y', # the MARC subfield to check
marcvalue => 'zzzz', # the value to check
}
If defined in a set mapping, a biblio which have at least one 'Y' subfield of
one 'XXX' field equal to 'zzzz' will belong to this set.
If multiple mappings are defined in a set, the biblio will belong to this set
if at least one condition is matched.
GetOAISetsMappings returns a hashref of arrayrefs of hashrefs.
The first hashref keys are the sets IDs, so it looks like this:
$mappings = {
'1' => [
{
marcfield => 'XXX',
marcsubfield => 'Y',
marcvalue => 'zzzz'
},
{
...
},
...
],
'2' => [...],
...
};
=cut
sub GetOAISetsMappings {
my $dbh = C4::Context->dbh;
my $query = qq{
SELECT * FROM oai_sets_mappings
};
my $sth = $dbh->prepare($query);
$sth->execute;
my $mappings = {};
while(my $result = $sth->fetchrow_hashref) {
push @{ $mappings->{$result->{'set_id'}} }, {
marcfield => $result->{'marcfield'},
marcsubfield => $result->{'marcsubfield'},
marcvalue => $result->{'marcvalue'}
};
}
return $mappings;
}
=head2 GetOAISetMappings
my $set_mappings = GetOAISetMappings($set_id);
Return mappings for the set with given set_id. It's an arrayref of hashrefs
=cut
sub GetOAISetMappings {
my ($set_id) = @_;
return unless $set_id;
my $dbh = C4::Context->dbh;
my $query = qq{
SELECT *
FROM oai_sets_mappings
WHERE set_id = ?
};
my $sth = $dbh->prepare($query);
$sth->execute($set_id);
my @mappings;
while(my $result = $sth->fetchrow_hashref) {
push @mappings, {
marcfield => $result->{'marcfield'},
marcsubfield => $result->{'marcsubfield'},
marcvalue => $result->{'marcvalue'}
};
}
return \@mappings;
}
=head2 ModOAISetMappings {
my $mappings = [
{
marcfield => 'XXX',
marcsubfield => 'Y',
marcvalue => 'zzzz'
},
...
];
ModOAISetMappings($set_id, $mappings);
ModOAISetMappings modifies mappings of a given set.
=cut
sub ModOAISetMappings {
my ($set_id, $mappings) = @_;
return unless $set_id;
my $dbh = C4::Context->dbh;
my $query = qq{
DELETE FROM oai_sets_mappings
WHERE set_id = ?
};
my $sth = $dbh->prepare($query);
$sth->execute($set_id);
if(scalar @$mappings > 0) {
$query = qq{
INSERT INTO oai_sets_mappings (set_id, marcfield, marcsubfield, marcvalue)
VALUES (?,?,?,?)
};
$sth = $dbh->prepare($query);
foreach (@$mappings) {
$sth->execute($set_id, $_->{'marcfield'}, $_->{'marcsubfield'}, $_->{'marcvalue'});
}
}
}
=head2 GetOAISetsBiblio
$oai_sets = GetOAISetsBiblio($biblionumber);
Return the OAI sets where biblio appears.
Return value is an arrayref of hashref where each element of the array is a set.
Keys of hash are id, spec and name
=cut
sub GetOAISetsBiblio {
my ($biblionumber) = @_;
my $dbh = C4::Context->dbh;
my $query = qq{
SELECT oai_sets.*
FROM oai_sets
LEFT JOIN oai_sets_biblios ON oai_sets_biblios.set_id = oai_sets.id
WHERE biblionumber = ?
};
my $sth = $dbh->prepare($query);
$sth->execute($biblionumber);
return $sth->fetchall_arrayref({});
}
=head2 DelOAISetsBiblio
DelOAISetsBiblio($biblionumber);
Remove a biblio from all sets
=cut
sub DelOAISetsBiblio {
my ($biblionumber) = @_;
return unless $biblionumber;
my $dbh = C4::Context->dbh;
my $query = qq{
DELETE FROM oai_sets_biblios
WHERE biblionumber = ?
};
my $sth = $dbh->prepare($query);
return $sth->execute($biblionumber);
}
=head2 CalcOAISetsBiblio
my @sets = CalcOAISetsBiblio($record, $oai_sets_mappings);
Return a list of set ids the record belongs to. $record must be a MARC::Record
and $oai_sets_mappings (optional) must be a hashref returned by
GetOAISetsMappings
=cut
sub CalcOAISetsBiblio {
my ($record, $oai_sets_mappings) = @_;
return unless $record;
$oai_sets_mappings ||= GetOAISetsMappings;
my @biblio_sets;
foreach my $set_id (keys %$oai_sets_mappings) {
foreach my $mapping (@{ $oai_sets_mappings->{$set_id} }) {
next if not $mapping;
my $field = $mapping->{'marcfield'};
my $subfield = $mapping->{'marcsubfield'};
my $value = $mapping->{'marcvalue'};
my @subfield_values = $record->subfield($field, $subfield);
if(0 < grep /^$value$/, @subfield_values) {
push @biblio_sets, $set_id;
last;
}
}
}
return @biblio_sets;
}
=head2 ModOAISetsBiblios
my $oai_sets_biblios = {
'1' => [1, 3, 4], # key is the set_id, and value is an array ref of biblionumbers
'2' => [],
...
};
ModOAISetsBiblios($oai_sets_biblios);
ModOAISetsBiblios truncate oai_sets_biblios table and call AddOAISetsBiblios.
This table is then used in opac/oai.pl.
=cut
sub ModOAISetsBiblios {
my $oai_sets_biblios = shift;
return unless ref($oai_sets_biblios) eq "HASH";
my $dbh = C4::Context->dbh;
my $query = qq{
TRUNCATE TABLE oai_sets_biblios
};
my $sth = $dbh->prepare($query);
$sth->execute;
AddOAISetsBiblios($oai_sets_biblios);
}
=head2 UpdateOAISetsBiblio
UpdateOAISetsBiblio($biblionumber, $record);
Update OAI sets for one biblio. The two parameters are mandatory.
$record is a MARC::Record.
=cut
sub UpdateOAISetsBiblio {
my ($biblionumber, $record) = @_;
return unless($biblionumber and $record);
my $sets_biblios;
my @sets = CalcOAISetsBiblio($record);
foreach (@sets) {
push @{ $sets_biblios->{$_} }, $biblionumber;
}
DelOAISetsBiblio($biblionumber);
AddOAISetsBiblios($sets_biblios);
}
=head2 AddOAISetsBiblios
my $oai_sets_biblios = {
'1' => [1, 3, 4], # key is the set_id, and value is an array ref of biblionumbers
'2' => [],
...
};
ModOAISetsBiblios($oai_sets_biblios);
AddOAISetsBiblios insert given infos in oai_sets_biblios table.
This table is then used in opac/oai.pl.
=cut
sub AddOAISetsBiblios {
my $oai_sets_biblios = shift;
return unless ref($oai_sets_biblios) eq "HASH";
my $dbh = C4::Context->dbh;
my $query = qq{
INSERT INTO oai_sets_biblios (set_id, biblionumber)
VALUES (?,?)
};
my $sth = $dbh->prepare($query);
foreach my $set_id (keys %$oai_sets_biblios) {
foreach my $biblionumber (@{$oai_sets_biblios->{$set_id}}) {
$sth->execute($set_id, $biblionumber);
}
}
}
1;

86
admin/oai_set_mappings.pl Executable file
View File

@ -0,0 +1,86 @@
#!/usr/bin/perl
# Copyright 2011 BibLibre SARL
# 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.
=head1 NAME
oai_set_mappings.pl
=head1 DESCRIPTION
Define mappings for a given set.
Mappings are conditions that define which biblio is included in which set.
A condition is in the form 200$a = 'abc'.
Multiple conditions can be defined for a given set. In this case,
the OR operator will be applied.
=cut
use Modern::Perl;
use CGI;
use C4::Auth;
use C4::Output;
use C4::OAI::Sets;
use Data::Dumper;
my $input = new CGI;
my ($template, $loggedinuser, $cookie, $flags) = get_template_and_user( {
template_name => 'admin/oai_set_mappings.tt',
query => $input,
type => 'intranet',
authnotrequired => 0,
flagsrequired => { 'parameters' => '*' },
debug => 1,
} );
my $id = $input->param('id');
my $op = $input->param('op');
if($op && $op eq "save") {
my @marcfields = $input->param('marcfield');
my @marcsubfields = $input->param('marcsubfield');
my @marcvalues = $input->param('marcvalue');
my @mappings;
my $i = 0;
while($i < @marcfields and $i < @marcsubfields and $i < @marcvalues) {
if($marcfields[$i] and $marcsubfields[$i] and $marcvalues[$i]) {
push @mappings, {
marcfield => $marcfields[$i],
marcsubfield => $marcsubfields[$i],
marcvalue => $marcvalues[$i]
};
}
$i++;
}
ModOAISetMappings($id, \@mappings);
$template->param(mappings_saved => 1);
}
my $set = GetOAISet($id);
my $mappings = GetOAISetMappings($id);
$template->param(
id => $id,
setName => $set->{'name'},
setSpec => $set->{'spec'},
mappings => $mappings,
);
output_html_with_http_headers $input, $cookie, $template->output;

102
admin/oai_sets.pl Executable file
View File

@ -0,0 +1,102 @@
#!/usr/bin/perl
# Copyright 2011 BibLibre SARL
# 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.
=head1 NAME
oai_sets.pl
=head1 DESCRIPTION
Admin page to describe OAI SETs
=cut
use Modern::Perl;
use CGI;
use C4::Auth;
use C4::Output;
use C4::OAI::Sets;
use Data::Dumper;
my $input = new CGI;
my ($template, $loggedinuser, $cookie, $flags) = get_template_and_user( {
template_name => 'admin/oai_sets.tt',
query => $input,
type => 'intranet',
authnotrequired => 0,
flagsrequired => { 'parameters' => '*' },
debug => 1,
} );
my $op = $input->param('op');
if($op && $op eq "new") {
$template->param( op_new => 1 );
} elsif($op && $op eq "savenew") {
my $spec = $input->param('spec');
my $name = $input->param('name');
my @descriptions = $input->param('description');
AddOAISet({
spec => $spec,
name => $name,
descriptions => \@descriptions
});
} elsif($op && $op eq "mod") {
my $id = $input->param('id');
my $set = GetOAISet($id);
$template->param(
op_mod => 1,
id => $set->{'id'},
spec => $set->{'spec'},
name => $set->{'name'},
descriptions => [ map { {description => $_} } @{ $set->{'descriptions'} } ],
);
} elsif($op && $op eq "savemod") {
my $id = $input->param('id');
my $spec = $input->param('spec');
my $name = $input->param('name');
my @descriptions = $input->param('description');
ModOAISet({
id => $id,
spec => $spec,
name => $name,
descriptions => \@descriptions
});
} elsif($op && $op eq "del") {
my $id = $input->param('id');
DelOAISet($id);
}
my $OAISets = GetOAISets;
my @sets_loop;
foreach(@$OAISets) {
push @sets_loop, {
id => $_->{'id'},
spec => $_->{'spec'},
name => $_->{'name'},
descriptions => [ map { {description => $_} } @{ $_->{'descriptions'} } ]
};
}
$template->param(
sets_loop => \@sets_loop,
);
output_html_with_http_headers $input, $cookie, $template->output;

View File

@ -0,0 +1,35 @@
DROP TABLE IF EXISTS `oai_sets_descriptions`;
DROP TABLE IF EXISTS `oai_sets_mappings`;
DROP TABLE IF EXISTS `oai_sets_biblios`;
DROP TABLE IF EXISTS `oai_sets`;
CREATE TABLE `oai_sets` (
`id` int(11) NOT NULL auto_increment,
`spec` varchar(80) NOT NULL UNIQUE,
`name` varchar(80) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE `oai_sets_descriptions` (
`set_id` int(11) NOT NULL,
`description` varchar(255) NOT NULL,
CONSTRAINT `oai_sets_descriptions_ibfk_1` FOREIGN KEY (`set_id`) REFERENCES `oai_sets` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE `oai_sets_mappings` (
`set_id` int(11) NOT NULL,
`marcfield` char(3) NOT NULL,
`marcsubfield` char(1) NOT NULL,
`marcvalue` varchar(80) NOT NULL,
CONSTRAINT `oai_sets_mappings_ibfk_1` FOREIGN KEY (`set_id`) REFERENCES `oai_sets` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE `oai_sets_biblios` (
`biblionumber` int(11) NOT NULL,
`set_id` int(11) NOT NULL,
PRIMARY KEY (`biblionumber`, `set_id`),
CONSTRAINT `oai_sets_biblios_ibfk_1` FOREIGN KEY (`biblionumber`) REFERENCES `biblio` (`biblionumber`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `oai_sets_biblios_ibfk_2` FOREIGN KEY (`set_id`) REFERENCES `oai_sets` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO systempreferences (variable,value,explanation,options,type) VALUES('OAI-PMH:AutoUpdateSets','0','Automatically update OAI sets when a bibliographic record is created or updated','','YesNo');

View File

@ -1355,6 +1355,55 @@ CREATE TABLE `nozebra` (
KEY `value` (`server`,`value`))
ENGINE=InnoDB DEFAULT CHARSET=utf8;
--
-- Table structure for table `oai_sets`
--
DROP TABLE IF EXISTS `oai_sets`;
CREATE TABLE `oai_sets` (
`id` int(11) NOT NULL auto_increment,
`spec` varchar(80) NOT NULL UNIQUE,
`name` varchar(80) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
--
-- Table structure for table `oai_sets_descriptions`
--
DROP TABLE IF EXISTS `oai_sets_descriptions`;
CREATE TABLE `oai_sets_descriptions` (
`set_id` int(11) NOT NULL,
`description` varchar(255) NOT NULL,
CONSTRAINT `oai_sets_descriptions_ibfk_1` FOREIGN KEY (`set_id`) REFERENCES `oai_sets` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
--
-- Table structure for table `oai_sets_mappings`
--
DROP TABLE IF EXISTS `oai_sets_mappings`;
CREATE TABLE `oai_sets_mappings` (
`set_id` int(11) NOT NULL,
`marcfield` char(3) NOT NULL,
`marcsubfield` char(1) NOT NULL,
`marcvalue` varchar(80) NOT NULL,
CONSTRAINT `oai_sets_mappings_ibfk_1` FOREIGN KEY (`set_id`) REFERENCES `oai_sets` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
--
-- Table structure for table `oai_sets_biblios`
--
DROP TABLE IF EXISTS `oai_sets_biblios`;
CREATE TABLE `oai_sets_biblios` (
`biblionumber` int(11) NOT NULL,
`set_id` int(11) NOT NULL,
PRIMARY KEY (`biblionumber`, `set_id`),
CONSTRAINT `oai_sets_biblios_ibfk_1` FOREIGN KEY (`biblionumber`) REFERENCES `biblio` (`biblionumber`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `oai_sets_biblios_ibfk_2` FOREIGN KEY (`set_id`) REFERENCES `oai_sets` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
--
-- Table structure for table `old_issues`
--

View File

@ -352,3 +352,4 @@ INSERT INTO systempreferences (variable,value,explanation,options,type) VALUES('
INSERT INTO systempreferences` (variable,value,options,explanation,type) VALUES ('ExpireReservesMaxPickUpDelayCharge', '0', NULL , 'If ExpireReservesMaxPickUpDelay is enabled, and this field has a non-zero value, than a borrower whose waiting hold has expired will be charged this amount.', 'free')
INSERT INTO systempreferences (variable,value,explanation,options,type) VALUES('RoutingListNote','To change this note edit <a href="/cgi-bin/koha/admin/preferences.pl?op=search&searchfield=RoutingListNote#jumped">RoutlingListNote</a> system preference.','Define a note to be shown on all routing lists','70|10','Textarea');
INSERT INTO systempreferences (variable,value,explanation,options,type) VALUES ('AllowPKIAuth','None','Use the field from a client-side SSL certificate to look a user in the Koha database','None|Common Name|emailAddress','Choice');
INSERT INTO systempreferences (variable,value,explanation,options,type) VALUES('OAI-PMH:AutoUpdateSets','0','Automatically update OAI sets when a bibliographic record is created or updated','','YesNo');

View File

@ -4932,6 +4932,18 @@ if ( C4::Context->preference("Version") < TransformToNum($DBversion) ) {
SetVersion($DBversion);
}
$DBversion = "3.07.00.029";
if ( C4::Context->preference("Version") < TransformToNum($DBversion) ) {
my $installer = C4::Installer->new();
my $full_path = C4::Context->config('intranetdir') . "/installer/data/$installer->{dbms}/atomicupdate/oai_sets.sql";
my $error = $installer->load_sql($full_path);
warn $error if $error;
print "Upgrade to $DBversion done (Atomic update for OAI-PMH sets management)\n";
SetVersion($DBversion);
}
=head1 FUNCTIONS
=head2 DropAllForeignKeys($table)

View File

@ -43,6 +43,7 @@
<li><a href="/cgi-bin/koha/admin/authtypes.pl">Authority types</a></li>
<li><a href="/cgi-bin/koha/admin/classsources.pl">Classification sources</a></li>
<li><a href="/cgi-bin/koha/admin/matching-rules.pl">Record matching rules</a></li>
<li><a href="/cgi-bin/koha/admin/oai_sets.pl">OAI Sets configuration</a></li>
</ul>
<h5>Acquisition parameters</h5>

View File

@ -74,6 +74,8 @@
<dd>Define classification sources (i.e., call number schemes) used by your collection. Also define filing rules used for sorting call numbers.</dd>
<dt><a href="/cgi-bin/koha/admin/matching-rules.pl">Record matching rules</a></dt>
<dd>Manage rules for automatically matching MARC records during record imports.</dd>
<dt><a href="/cgi-bin/koha/admin/oai_sets.pl">OAI Sets Configuration</a></dt>
<dd>Manage OAI Sets</dd>
</dl>
<h3>Acquisition parameters</h3>

View File

@ -0,0 +1,103 @@
[% INCLUDE 'doc-head-open.inc' %]
<title>Koha &rsaquo; Admin &rsaquo; OAI Set Mappings</title>
[% INCLUDE 'doc-head-close.inc' %]
<script type="text/javascript">
//<![CDATA[
$(document).ready(function() {
// Some JS
});
function newCondition() {
var tr = $('#ORbutton').parents('tr');
var clone = $(tr).clone();
$("#ORbutton").parent('td').replaceWith('<td style="text-align:center">OR</td>');
$(tr).parent('tbody').append(clone);
}
function hideDialogBox() {
$('div.dialog').remove();
}
function returnToSetsPage() {
window.location.href = "/cgi-bin/koha/admin/oai_sets.pl";
}
//]]>
</script>
</head>
<body>
[% INCLUDE 'header.inc' %]
[% INCLUDE 'cat-search.inc' %]
<div id="breadcrumbs"><a href="/cgi-bin/koha/mainpage.pl">Home</a> &rsaquo; <a href="/cgi-bin/koha/admin/admin-home.pl">Admin</a> &rsaquo; <a href="/cgi-bin/koha/admin/oai_set_mappings.pl?id=[% id %]">OAI Set Mappings</a></div>
<div id="doc3" class="yui-t2">
<div id="bd">
<div id="yui-main">
<div class="yui-b">
[% IF ( mappings_saved ) %]
<div class="dialog">
<p>Mappings have been saved</p>
<p><a href="/cgi-bin/koha/admin/oai_sets.pl">Return to sets management</a></p>
</div>
[% END %]
<h1>Mappings for set '[% setName %]' ([% setSpec %])</h1>
[% UNLESS ( mappings ) %]
<p class="warning">Warning: no mappings defined for this set</p>
[% END %]
<form action="/cgi-bin/koha/admin/oai_set_mappings.pl" method="post" onsubmit="hideDialogBox();">
<table id="mappings">
<thead>
<tr>
<th>Field</th>
<th>Subfield</th>
<th>&nbsp;</th>
<th>Value</th>
<th>&nbsp;</th>
</tr>
</thead>
<tbody>
[% IF ( mappings ) %]
[% FOREACH mapping IN mappings %]
<tr>
<td><input type="text" name="marcfield" size="3" value="[% mapping.marcfield %]" /></td>
<td style="text-align:center"><input type="text" name="marcsubfield" size="1" value="[% mapping.marcsubfield %]" /></td>
<td>is equal to</td>
<td><input type="text" name="marcvalue" value="[% mapping.marcvalue %]" /></td>
<td style="text-align:center">
[% IF ( loop.last ) %]
<input type="button" id="ORbutton" value="OR" onclick="newCondition()"/>
[% ELSE %]
OR
[% END %]
</td>
</tr>
[% END %]
[% ELSE %]
<tr>
<td><input type="text" name="marcfield" size="3" /></td>
<td style="text-align:center"><input type="text" name="marcsubfield" size="1" /></td>
<td>is equal to</td>
<td><input type="text" name="marcvalue" /></td>
<td><input type="button" id="ORbutton" value="OR" onclick="newCondition()"/></td>
</tr>
[% END %]
</tbody>
</table>
<p class="hint">Hint: to delete a line, empty at least one of the text fields in this line</p>
<input type="hidden" name="id" value="[% id %]" />
<input type="hidden" name="op" value="save" />
<fieldset class="action">
<input type="submit" value="Save" />
<input type="button" value="Cancel" onclick="returnToSetsPage();" />
</fieldset>
</form>
</div>
</div>
<div class="yui-b">
[% INCLUDE 'admin-menu.inc' %]
</div>
</div>
[% INCLUDE 'intranet-bottom.inc' %]

View File

@ -0,0 +1,140 @@
[% INCLUDE 'doc-head-open.inc' %]
<title>Koha &rsaquo; Admin &rsaquo; OAI Sets</title>
[% INCLUDE 'doc-head-close.inc' %]
<script type="text/javascript">
//<![CDATA[
function newDescField() {
$("#descriptionlist").append(
'<li>' +
'<textarea style="vertical-align:middle" name="description"></textarea>' +
'<a style="cursor:pointer" onclick="delDescField(this)">&nbsp;&times;</a>' +
'</li>'
);
}
function delDescField(minusButton) {
var li = $(minusButton).parent('li');
$(li).remove();
}
$(document).ready(function() {
// Some JS
});
//]]>
</script>
</head>
<body>
[% INCLUDE 'header.inc' %]
[% INCLUDE 'cat-search.inc' %]
<div id="breadcrumbs"><a href="/cgi-bin/koha/mainpage.pl">Home</a> &rsaquo; <a href="/cgi-bin/koha/admin/admin-home.pl">Admin</a> &rsaquo; OAI Sets</div>
<div id="doc3" class="yui-t2">
<div id="bd">
<div id="yui-main">
<div class="yui-b">
<h1>OAI Sets Configuration</h1>
[% IF op_new %]
<h2>Add a new set</h2>
<form method="post" action="/cgi-bin/koha/admin/oai_sets.pl">
<input type="hidden" name="op" value="savenew" />
<fieldset>
<label for="spec">setSpec</label>
<input type="text" id="spec" name="spec" />
<br />
<label for="name">setName</label>
<input type="text" id="name" name="name" />
<br />
<label>setDescriptions</label>
<ul id="descriptionlist">
</ul>
<a style="cursor:pointer" onclick='newDescField()'>Add description</a>
</fieldset>
<input type="submit" value="Save" />
<input type="button" value="Cancel" onclick="window.location.href = '/cgi-bin/koha/admin/oai_sets.pl'" />
</form>
[% ELSE %][% IF op_mod %]
<h2>Modify set '[% spec %]'</h2>
<form method="post" action="/cgi-bin/koha/admin/oai_sets.pl">
<input type="hidden" name="op" value="savemod" />
<input type="hidden" name="id" value="[% id %]" />
<fieldset>
<label for="spec">setSpec</label>
<input type="text" id="spec" name="spec" value="[% spec %]" />
<br />
<label for="name">setName</label>
<input type="text" id="name" name="name" value="[% name %]" />
<br />
<label>setDescriptions</label>
<ul id="descriptionlist">
[% FOREACH desc IN descriptions %]
<li>
<textarea style="vertical-align:middle" name="description">[% desc.description %]</textarea>
<a style="cursor:pointer" onclick="delDescField(this)">&nbsp;&times;</a>
</li>
[% END %]
</ul>
<a style="cursor:pointer" onclick='newDescField()'>Add description</a>
</fieldset>
<input type="submit" value="Save" />
<input type="button" value="Cancel" onclick="window.location.href = '/cgi-bin/koha/admin/oai_sets.pl'" />
</form>
[% END %]
[% END %]
<h2>List of sets</h2>
[% UNLESS ( op_new ) %]
<a href="/cgi-bin/koha/admin/oai_sets.pl?op=new">Add a new set</a>
[% END %]
[% IF sets_loop %]
<table>
<thead>
<tr>
<th>setSpec</th>
<th>setName</th>
<th>setDescriptions</th>
<th>Action</th>
</tr>
</thead>
<tbody>
[% FOREACH set IN sets_loop %]
<tr>
<td>[% set.spec %]</td>
<td>[% set.name %]</td>
<td>
[% IF set.descriptions %]
<ul>
[% FOREACH desc IN set.descriptions %]
<li>[% desc.description %]</li>
[% END %]
</ul>
[% ELSE %]
<em>No descriptions</em>
[% END %]
</td>
<td>
<a href="/cgi-bin/koha/admin/oai_sets.pl?op=mod&id=[% set.id %]">Modify</a>
|
<a href="/cgi-bin/koha/admin/oai_sets.pl?op=del&id=[% set.id %]">Delete</a>
|
<a href="/cgi-bin/koha/admin/oai_set_mappings.pl?id=[% set.id %]">Define mappings</a>
</td>
</tr>
[% END %]
</tbody>
</table>
[% ELSE %]
<p>There is no set defined.</p>
[% END %]
</div>
</div>
<div class="yui-b">
[% INCLUDE 'admin-menu.inc' %]
</div>
</div>
[% INCLUDE 'intranet-bottom.inc' %]

View File

@ -21,6 +21,12 @@ Web Services:
- pref: "OAI-PMH:ConfFile"
class: file
- . If empty, Koha OAI Server operates in normal mode, otherwise it operates in extended mode. In extended mode, it's possible to parameter other formats than marcxml or Dublin Core. OAI-PMH:ConfFile specify a YAML configuration file which list available metadata formats and XSL file used to create them from marcxml records.
-
- pref: "OAI-PMH:AutoUpdateSets"
choices:
yes: Enable
no: Disable
- automatic update of OAI-PMH sets when a bibliographic record is created or updated
ILS-DI:
-
- pref: ILS-DI

View File

@ -0,0 +1,40 @@
[% INCLUDE 'help-top.inc' %]
<h1>OAI-PMH Sets Mappings Configuration</h1>
<p>
Here you can define how a set will be build (what records will belong to
this set) by defining mappings. Mappings are a list of conditions on record
content. A record only need to match one condition to belong to the set.
<p>
<h2>Defining a mapping</h2>
<ol>
<li>
Fill the fields 'Field', 'Subfield' and 'Value'. For example if you
want to include in this set all records that have a 999$9 equal to
'XXX'. Fill 'Field' with 999, 'Subfield' with 9 and 'Value' with XXX.
</li>
<li>
If you want to add another condition, click on 'OR' button and repeat
step 1.
</li>
<li>Click on 'Save'</li>
</ol>
<p>
To delete a condition, just leave at least one of 'Field', 'Subfield' or
'Value' empty and click on 'Save'.
</p>
<p>
Note: Actually, a condition is true if value in the corresponding subfield
is strictly equal to what is defined if 'Value'. A record having
999$9 = 'XXX YYY' will not belong to a set where condition is
999$9 = 'XXX'.
<br />
And it is case sensitive : a record having 999$9 = 'xxx' will not belong
to a set where condition is 999$9 = 'XXX'.
</p>
[% INCLUDE 'help-bottom.inc' %]

View File

@ -0,0 +1,49 @@
[% INCLUDE 'help-top.inc' %]
<h1>OAI-PMH Sets Configuration</h1>
<p>On this page you can create, modify and delete OAI-PMH sets<p>
<h2>Create a set</h2>
<ol>
<li>Click on the link 'Add a new set'</li>
<li>Fill the mandatory fields 'setSpec' and 'setName'</li>
<li>
Then you can add descriptions for this set. To do this click on
'Add description' and fill the newly created text box. You can add as
many descriptions as you want.
</li>
<li>Click on 'Save' button'</li>
</ol>
<h2>Modify a set</h2>
<p>
To modify a set, just click on the link 'Modify' on the same line of the
set you want to modify. A form similar to set creation form will appear and
allow you to modify the setSpec, setName and descriptions.
<p>
<h2>Delete a set</h2>
<p>
To delete a set, just click on the link 'Delete' on the same line of the
set you want to delete.
</p>
<h2>Define mappings</h2>
<p>
The 'Define mappings' link allow you to tell how the set will be build
(what records will belong to this set)
</p>
<h2>Build sets</h2>
<p>
Once you have configured all your sets, you have to build the sets. This is
done by calling the script misc/migration_tools/build_oai_sets.pl.
</p>
[% INCLUDE 'help-bottom.inc' %]

View File

@ -163,9 +163,10 @@
</dc:identifier>
</xsl:for-each>
<xsl:for-each select="marc:datafield[@tag=090]">
<dc:identifier>
<xsl:text>http://opac.mylibrary.org/bib/</xsl:text>
<xsl:value-of select="marc:subfield[@code='a']"/>
<dc:identifier>
<xsl:value-of select="$OPACBaseURL" />
<xsl:text>/bib/</xsl:text>
<xsl:value-of select="marc:subfield[@code='a']"/>
</dc:identifier>
</xsl:for-each>
<xsl:for-each select="marc:datafield[@tag=995]">
@ -175,10 +176,10 @@
<xsl:when test="marc:subfield[@code='c']='MAIN'">Main Branch</xsl:when>
<xsl:when test="marc:subfield[@code='c']='BIB2'">Library 2</xsl:when>
</xsl:choose>
<xsl:foreach select="marc:subfield[@code='k']">
<xsl:for-each select="marc:subfield[@code='k']">
<xsl:text>:</xsl:text>
<xsl:value-of select="."/>
</xsl:foreach>
</xsl:for-each>
</dc:identifier>
</xsl:for-each>
</xsl:template>

View File

@ -16,7 +16,7 @@ the kohaversion is divided in 4 parts :
use strict;
sub kohaversion {
our $VERSION = '3.07.00.028';
our $VERSION = '3.07.00.029';
# version needs to be set this way
# so that it can be picked up by Makefile.PL
# during install

View File

@ -0,0 +1,165 @@
#!/usr/bin/perl
# Copyright 2011 BibLibre
#
# 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.
=head1 DESCRIPTION
This script build OAI-PMH sets (to be used by opac/oai.pl) according to sets
and mappings defined in Koha. It reads informations from oai_sets and
oai_sets_mappings, and then fill table oai_sets_biblios with builded infos.
=head1 USAGE
build_oai_sets.pl [-h] [-v] [-r] [-i] [-l LENGTH [-o OFFSET]]
-h Print help message;
-v Be verbose
-r Truncate table oai_sets_biblios before inserting new rows
-i Embed items informations, mandatory if you defined mappings
on item fields
-l LENGTH Process LENGTH biblios
-o OFFSET If LENGTH is defined, start processing from OFFSET
=cut
use Modern::Perl;
use MARC::Record;
use MARC::File::XML;
use List::MoreUtils qw/uniq/;
use Getopt::Std;
use C4::Context;
use C4::Charset qw/StripNonXmlChars/;
use C4::Biblio;
use C4::OAI::Sets;
my %opts;
$Getopt::Std::STANDARD_HELP_VERSION = 1;
my $go = getopts('vo:l:ihr', \%opts);
if(!$go or $opts{h}){
&print_usage;
exit;
}
my $verbose = $opts{v};
my $offset = $opts{o};
my $length = $opts{l};
my $embed_items = $opts{i};
my $reset = $opts{r};
my $dbh = C4::Context->dbh;
# Get OAI sets mappings
my $mappings = GetOAISetsMappings;
# Get all biblionumbers and marcxml
print "Retrieving biblios... " if $verbose;
my $query = qq{
SELECT biblionumber, marcxml
FROM biblioitems
};
if($length) {
$query .= "LIMIT $length";
if($offset) {
$query .= " OFFSET $offset";
}
}
my $sth = $dbh->prepare($query);
$sth->execute;
my $results = $sth->fetchall_arrayref({});
print "done.\n" if $verbose;
# Build lists of parents sets
my $sets = GetOAISets;
my $parentsets;
foreach my $set (@$sets) {
my $setSpec = $set->{'spec'};
while($setSpec =~ /^(.+):(.+)$/) {
my $parent = $1;
my $parent_set = GetOAISetBySpec($parent);
if($parent_set) {
push @{ $parentsets->{$set->{'id'}} }, $parent_set->{'id'};
$setSpec = $parent;
} else {
last;
}
}
}
my $num_biblios = scalar @$results;
my $i = 1;
my $sets_biblios = {};
foreach my $res (@$results) {
my $biblionumber = $res->{'biblionumber'};
my $marcxml = $res->{'marcxml'};
if($verbose and $i % 1000 == 0) {
my $percent = ($i * 100) / $num_biblios;
$percent = sprintf("%.2f", $percent);
say "Progression: $i/$num_biblios ($percent %)";
}
# The following lines are copied from GetMarcBiblio
# We don't call GetMarcBiblio to avoid a sql query to be executed each time
$marcxml = StripNonXmlChars($marcxml);
MARC::File::XML->default_record_format(C4::Context->preference('marcflavour'));
my $record;
eval {
$record = MARC::Record::new_from_xml($marcxml, "utf8", C4::Context->preference('marcflavour'));
};
if($@) {
warn "(biblio $biblionumber) Error while creating record from marcxml: $@";
next;
}
if($embed_items) {
C4::Biblio::EmbedItemsInMarcBiblio($record, $biblionumber);
}
my @biblio_sets = CalcOAISetsBiblio($record, $mappings);
foreach my $set_id (@biblio_sets) {
push @{ $sets_biblios->{$set_id} }, $biblionumber;
foreach my $parent_set_id ( @{ $parentsets->{$set_id} } ) {
push @{ $sets_biblios->{$parent_set_id} }, $biblionumber;
}
}
$i++;
}
say "Progression: done." if $verbose;
say "Summary:";
foreach my $set_id (keys %$sets_biblios) {
$sets_biblios->{$set_id} = [ uniq @{ $sets_biblios->{$set_id} } ];
my $set = GetOAISet($set_id);
my $setSpec = $set->{'spec'};
say "Set '$setSpec': ". scalar(@{$sets_biblios->{$set_id}}) ." biblios";
}
print "Updating database... ";
if($reset) {
ModOAISetsBiblios( {} );
}
AddOAISetsBiblios($sets_biblios);
print "done.\n";
sub print_usage {
print "build_oai_sets.pl: Build OAI-PMH sets, according to mappings defined in Koha\n";
print "Usage: build_oai_sets.pl [-h] [-v] [-i] [-l LENGTH [-o OFFSET]]\n\n";
print "\t-h\t\tPrint this help and exit\n";
print "\t-v\t\tBe verbose\n";
print "\t-i\t\tEmbed items informations, mandatory if you defined mappings on item fields\n";
print "\t-l LENGTH\tProcess LENGTH biblios\n";
print "\t-o OFFSET\tIf LENGTH is defined, start processing from OFFSET\n\n";
}

View File

@ -2,7 +2,6 @@
use strict;
use warnings;
use diagnostics;
use CGI qw/:standard -oldstyle_urls/;
use vars qw( $GZIP );
@ -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;
@ -145,7 +144,6 @@ package C4::OAI::ListMetadataFormats;
use strict;
use warnings;
use diagnostics;
use HTTP::OAI;
use base ("HTTP::OAI::ListMetadataFormats");
@ -188,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);
@ -205,11 +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 );
my $format = $args{metadataPrefix};
if ( $format ne 'marcxml' ) {
$record_dom = $repository->stylesheet($format)->transform( $record_dom );
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 ) );
@ -224,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");
@ -254,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;
}
@ -269,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");
@ -282,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 ) ) if ($pos > $token->{offset});
}
$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");
@ -329,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 ) ) if ($pos > $token->{offset});
}
$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;
}
@ -365,7 +503,6 @@ use base ("HTTP::OAI::Repository");
use strict;
use warnings;
use diagnostics;
use HTTP::OAI;
use HTTP::OAI::Repository qw/:validate/;
@ -418,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 );