3b0d4e04e0
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
589 lines
14 KiB
Perl
589 lines
14 KiB
Perl
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;
|