Marcel de Rooy
2bf171acaf
Should be XSLT::Base now. Removes old XSLT_Handler stub too (from bug 23290). Result of a git grep | sed statement. Test plan: Run qa tools (so modules compile). Run t/db_dependent/Breeding.t Run t/db_dependent/Koha/XSLT/Base.t (This test fails when only this patch has been applied; see subsequent patch.) Enable XSLT use on results and details display. Check search results and detail view on OPAC and staff. Signed-off-by: Marcel de Rooy <m.de.rooy@rijksmuseum.nl> Signed-off-by: Owen Leonard <oleonard@myacpl.org> Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com> Signed-off-by: Martin Renvoize <martin.renvoize@ptfs-europe.com>
400 lines
15 KiB
Perl
400 lines
15 KiB
Perl
package C4::XSLT;
|
|
|
|
# Copyright (C) 2006 LibLime
|
|
# <jmf at liblime dot com>
|
|
# Parts Copyright Katrin Fischer 2011
|
|
# Parts Copyright ByWater Solutions 2011
|
|
# Parts Copyright Biblibre 2012
|
|
#
|
|
# 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>.
|
|
|
|
use Modern::Perl;
|
|
|
|
use C4::Context;
|
|
use C4::Items;
|
|
use C4::Koha;
|
|
use C4::Biblio;
|
|
use C4::Circulation;
|
|
use C4::Reserves;
|
|
use Koha::AuthorisedValues;
|
|
use Koha::ItemTypes;
|
|
use Koha::XSLT::Base;
|
|
use Koha::Libraries;
|
|
|
|
use Encode;
|
|
|
|
use vars qw(@ISA @EXPORT);
|
|
|
|
my $engine; #XSLT Handler object
|
|
my %authval_per_framework;
|
|
# Cache for tagfield-tagsubfield to decode per framework.
|
|
# Should be preferably be placed in Koha-core...
|
|
|
|
BEGIN {
|
|
require Exporter;
|
|
@ISA = qw(Exporter);
|
|
@EXPORT = qw(
|
|
&XSLTParse4Display
|
|
);
|
|
$engine=Koha::XSLT::Base->new( { do_not_return_source => 1 } );
|
|
}
|
|
|
|
=head1 NAME
|
|
|
|
C4::XSLT - Functions for displaying XSLT-generated content
|
|
|
|
=head1 FUNCTIONS
|
|
|
|
=head2 transformMARCXML4XSLT
|
|
|
|
Replaces codes with authorized values in a MARC::Record object
|
|
Is only used in this module currently.
|
|
|
|
=cut
|
|
|
|
sub transformMARCXML4XSLT {
|
|
my ($biblionumber, $record) = @_;
|
|
my $frameworkcode = GetFrameworkCode($biblionumber) || '';
|
|
my $tagslib = &GetMarcStructure(1, $frameworkcode, { unsafe => 1 });
|
|
my @fields;
|
|
# FIXME: wish there was a better way to handle exceptions
|
|
eval {
|
|
@fields = $record->fields();
|
|
};
|
|
if ($@) { warn "PROBLEM WITH RECORD"; next; }
|
|
my $marcflavour = C4::Context->preference('marcflavour');
|
|
my $av = getAuthorisedValues4MARCSubfields($frameworkcode);
|
|
foreach my $tag ( keys %$av ) {
|
|
foreach my $field ( $record->field( $tag ) ) {
|
|
if ( $av->{ $tag } ) {
|
|
my @new_subfields = ();
|
|
for my $subfield ( $field->subfields() ) {
|
|
my ( $letter, $value ) = @$subfield;
|
|
# Replace the field value with the authorised value *except* for MARC21/NORMARC field 942$n (suppression in opac)
|
|
if ( !( $tag eq '942' && $subfield eq 'n' ) || $marcflavour eq 'UNIMARC' ) {
|
|
$value = GetAuthorisedValueDesc( $tag, $letter, $value, '', $tagslib )
|
|
if $av->{ $tag }->{ $letter };
|
|
}
|
|
push( @new_subfields, $letter, $value );
|
|
}
|
|
$field ->replace_with( MARC::Field->new(
|
|
$tag,
|
|
$field->indicator(1),
|
|
$field->indicator(2),
|
|
@new_subfields
|
|
) );
|
|
}
|
|
}
|
|
}
|
|
return $record;
|
|
}
|
|
|
|
=head2 getAuthorisedValues4MARCSubfields
|
|
|
|
Returns a ref of hash of ref of hash for tag -> letter controlled by authorised values
|
|
Is only used in this module currently.
|
|
|
|
=cut
|
|
|
|
sub getAuthorisedValues4MARCSubfields {
|
|
my ($frameworkcode) = @_;
|
|
unless ( $authval_per_framework{ $frameworkcode } ) {
|
|
my $dbh = C4::Context->dbh;
|
|
my $sth = $dbh->prepare("SELECT DISTINCT tagfield, tagsubfield
|
|
FROM marc_subfield_structure
|
|
WHERE authorised_value IS NOT NULL
|
|
AND authorised_value!=''
|
|
AND frameworkcode=?");
|
|
$sth->execute( $frameworkcode );
|
|
my $av = { };
|
|
while ( my ( $tag, $letter ) = $sth->fetchrow() ) {
|
|
$av->{ $tag }->{ $letter } = 1;
|
|
}
|
|
$authval_per_framework{ $frameworkcode } = $av;
|
|
}
|
|
return $authval_per_framework{ $frameworkcode };
|
|
}
|
|
|
|
=head2 XSLTParse4Display
|
|
|
|
Returns xml for biblionumber and requested XSLT transformation.
|
|
Returns undef if the transform fails.
|
|
|
|
Used in OPAC results and detail, intranet results and detail, list display.
|
|
(Depending on the settings of your XSLT preferences.)
|
|
|
|
The helper function _get_best_default_xslt_filename is used in a unit test.
|
|
|
|
=cut
|
|
|
|
sub _get_best_default_xslt_filename {
|
|
my ($htdocs, $theme, $lang, $base_xslfile) = @_;
|
|
|
|
my @candidates = (
|
|
"$htdocs/$theme/$lang/xslt/${base_xslfile}", # exact match
|
|
"$htdocs/$theme/en/xslt/${base_xslfile}", # if not, preferred theme in English
|
|
"$htdocs/prog/$lang/xslt/${base_xslfile}", # if not, 'prog' theme in preferred language
|
|
"$htdocs/prog/en/xslt/${base_xslfile}", # otherwise, prog theme in English; should always
|
|
# exist
|
|
);
|
|
my $xslfilename;
|
|
foreach my $filename (@candidates) {
|
|
$xslfilename = $filename;
|
|
if (-f $filename) {
|
|
last; # we have a winner!
|
|
}
|
|
}
|
|
return $xslfilename;
|
|
}
|
|
|
|
sub get_xslt_sysprefs {
|
|
my $sysxml = "<sysprefs>\n";
|
|
foreach my $syspref ( qw/ hidelostitems OPACURLOpenInNewWindow
|
|
DisplayOPACiconsXSLT URLLinkText viewISBD
|
|
OPACBaseURL TraceCompleteSubfields UseICU
|
|
UseAuthoritiesForTracings TraceSubjectSubdivisions
|
|
Display856uAsImage OPACDisplay856uAsImage
|
|
UseControlNumber IntranetBiblioDefaultView BiblioDefaultView
|
|
OPACItemLocation DisplayIconsXSLT
|
|
AlternateHoldingsField AlternateHoldingsSeparator
|
|
TrackClicks opacthemes IdRef OpacSuppression
|
|
OPACResultsLibrary OPACShowOpenURL
|
|
OpenURLResolverURL OpenURLImageLocation
|
|
OpenURLText OPACShowMusicalInscripts OPACPlayMusicalInscripts / )
|
|
{
|
|
my $sp = C4::Context->preference( $syspref );
|
|
next unless defined($sp);
|
|
$sysxml .= "<syspref name=\"$syspref\">$sp</syspref>\n";
|
|
}
|
|
|
|
# singleBranchMode was a system preference, but no longer is
|
|
# we can retain it here for compatibility
|
|
my $singleBranchMode = Koha::Libraries->search->count == 1 ? 1 : 0;
|
|
$sysxml .= "<syspref name=\"singleBranchMode\">$singleBranchMode</syspref>\n";
|
|
|
|
$sysxml .= "</sysprefs>\n";
|
|
return $sysxml;
|
|
}
|
|
|
|
sub XSLTParse4Display {
|
|
my ( $biblionumber, $orig_record, $xslsyspref, $fixamps, $hidden_items, $sysxml, $xslfilename, $lang, $variables ) = @_;
|
|
|
|
$sysxml ||= C4::Context->preference($xslsyspref);
|
|
$xslfilename ||= C4::Context->preference($xslsyspref);
|
|
$lang ||= C4::Languages::getlanguage();
|
|
|
|
if ( $xslfilename =~ /^\s*"?default"?\s*$/i ) {
|
|
my $htdocs;
|
|
my $theme;
|
|
my $xslfile;
|
|
if ($xslsyspref eq "XSLTDetailsDisplay") {
|
|
$htdocs = C4::Context->config('intrahtdocs');
|
|
$theme = C4::Context->preference("template");
|
|
$xslfile = C4::Context->preference('marcflavour') .
|
|
"slim2intranetDetail.xsl";
|
|
} elsif ($xslsyspref eq "XSLTResultsDisplay") {
|
|
$htdocs = C4::Context->config('intrahtdocs');
|
|
$theme = C4::Context->preference("template");
|
|
$xslfile = C4::Context->preference('marcflavour') .
|
|
"slim2intranetResults.xsl";
|
|
} elsif ($xslsyspref eq "OPACXSLTDetailsDisplay") {
|
|
$htdocs = C4::Context->config('opachtdocs');
|
|
$theme = C4::Context->preference("opacthemes");
|
|
$xslfile = C4::Context->preference('marcflavour') .
|
|
"slim2OPACDetail.xsl";
|
|
} elsif ($xslsyspref eq "OPACXSLTResultsDisplay") {
|
|
$htdocs = C4::Context->config('opachtdocs');
|
|
$theme = C4::Context->preference("opacthemes");
|
|
$xslfile = C4::Context->preference('marcflavour') .
|
|
"slim2OPACResults.xsl";
|
|
} elsif ($xslsyspref eq 'XSLTListsDisplay') {
|
|
# Lists default to *Results.xslt
|
|
$htdocs = C4::Context->config('intrahtdocs');
|
|
$theme = C4::Context->preference("template");
|
|
$xslfile = C4::Context->preference('marcflavour') .
|
|
"slim2intranetResults.xsl";
|
|
} elsif ($xslsyspref eq 'OPACXSLTListsDisplay') {
|
|
# Lists default to *Results.xslt
|
|
$htdocs = C4::Context->config('opachtdocs');
|
|
$theme = C4::Context->preference("opacthemes");
|
|
$xslfile = C4::Context->preference('marcflavour') .
|
|
"slim2OPACResults.xsl";
|
|
}
|
|
$xslfilename = _get_best_default_xslt_filename($htdocs, $theme, $lang, $xslfile);
|
|
}
|
|
|
|
if ( $xslfilename =~ m/\{langcode\}/ ) {
|
|
$xslfilename =~ s/\{langcode\}/$lang/;
|
|
}
|
|
|
|
# grab the XML, run it through our stylesheet, push it out to the browser
|
|
my $record = transformMARCXML4XSLT($biblionumber, $orig_record);
|
|
my $itemsxml;
|
|
if ( $xslsyspref eq "OPACXSLTDetailsDisplay" || $xslsyspref eq "XSLTDetailsDisplay" || $xslsyspref eq "XSLTResultsDisplay" ) {
|
|
$itemsxml = ""; #We don't use XSLT for items display on these pages
|
|
} else {
|
|
$itemsxml = buildKohaItemsNamespace($biblionumber, $hidden_items);
|
|
}
|
|
my $xmlrecord = $record->as_xml(C4::Context->preference('marcflavour'));
|
|
|
|
$variables ||= {};
|
|
if (C4::Context->preference('OPACShowOpenURL')) {
|
|
my @biblio_itemtypes;
|
|
my $biblio = Koha::Biblios->find($biblionumber);
|
|
if (C4::Context->preference('item-level_itypes')) {
|
|
@biblio_itemtypes = $biblio->items->get_column("itype");
|
|
} else {
|
|
push @biblio_itemtypes, $biblio->itemtype;
|
|
}
|
|
my @itypes = split( /\s/, C4::Context->preference('OPACOpenURLItemTypes') );
|
|
my %original = ();
|
|
map { $original{$_} = 1 } @biblio_itemtypes;
|
|
if ( grep { $original{$_} } @itypes ) {
|
|
$variables->{OpenURLResolverURL} = $biblio->get_openurl;
|
|
}
|
|
}
|
|
my $varxml = "<variables>\n";
|
|
while (my ($key, $value) = each %$variables) {
|
|
$varxml .= "<variable name=\"$key\">$value</variable>\n";
|
|
}
|
|
$varxml .= "</variables>\n";
|
|
|
|
$xmlrecord =~ s/\<\/record\>/$itemsxml$sysxml$varxml\<\/record\>/;
|
|
if ($fixamps) { # We need to correct the ampersand entities that Zebra outputs
|
|
$xmlrecord =~ s/\&amp;/\&/g;
|
|
$xmlrecord =~ s/\&\;lt\;/\<\;/g;
|
|
$xmlrecord =~ s/\&\;gt\;/\>\;/g;
|
|
}
|
|
$xmlrecord =~ s/\& /\&\; /;
|
|
$xmlrecord =~ s/\&\;amp\; /\&\; /;
|
|
|
|
#If the xslt should fail, we will return undef (old behavior was
|
|
#raw MARC)
|
|
#Note that we did set do_not_return_source at object construction
|
|
return $engine->transform($xmlrecord, $xslfilename ); #file or URL
|
|
}
|
|
|
|
=head2 buildKohaItemsNamespace
|
|
|
|
Returns XML for items.
|
|
Is only used in this module currently.
|
|
|
|
=cut
|
|
|
|
sub buildKohaItemsNamespace {
|
|
my ($biblionumber, $hidden_items) = @_;
|
|
|
|
$hidden_items ||= [];
|
|
my @items = Koha::Items->search(
|
|
{
|
|
'me.biblionumber' => $biblionumber,
|
|
'me.itemnumber' => { not_in => $hidden_items }
|
|
},
|
|
{ prefetch => [ 'branchtransfers', 'reserves' ] }
|
|
);
|
|
|
|
my $shelflocations =
|
|
{ map { $_->{authorised_value} => $_->{opac_description} } Koha::AuthorisedValues->get_descriptions_by_koha_field( { frameworkcode => "", kohafield => 'items.location' } ) };
|
|
my $ccodes =
|
|
{ map { $_->{authorised_value} => $_->{opac_description} } Koha::AuthorisedValues->get_descriptions_by_koha_field( { frameworkcode => "", kohafield => 'items.ccode' } ) };
|
|
|
|
my %branches = map { $_->branchcode => $_->branchname } Koha::Libraries->search({}, { order_by => 'branchname' });
|
|
|
|
my $itemtypes = { map { $_->{itemtype} => $_ } @{ Koha::ItemTypes->search->unblessed } };
|
|
my $xml = '';
|
|
my %descs = map { $_->{authorised_value} => $_ } Koha::AuthorisedValues->get_descriptions_by_koha_field( { kohafield => 'items.notforloan' } );
|
|
|
|
for my $item (@items) {
|
|
my $status;
|
|
my $substatus = '';
|
|
|
|
if ($item->has_pending_hold) {
|
|
$status = 'Pending hold';
|
|
}
|
|
elsif ( $item->holds->waiting->count ) {
|
|
$status = 'Waiting';
|
|
}
|
|
elsif ($item->get_transfer) {
|
|
$status = 'In transit';
|
|
}
|
|
elsif ($item->damaged) {
|
|
$status = "Damaged";
|
|
}
|
|
elsif ($item->itemlost) {
|
|
$status = "Lost";
|
|
}
|
|
elsif ( $item->withdrawn) {
|
|
$status = "Withdrawn";
|
|
}
|
|
elsif ($item->onloan) {
|
|
$status = "Checked out";
|
|
}
|
|
elsif ( $item->notforloan ) {
|
|
$status = $item->notforloan < 0 ? "reallynotforloan" : "reference";
|
|
$substatus = exists $descs{$item->notforloan} ? $descs{$item->notforloan}->{opac_description} : "Not for loan_".$item->notforloan;
|
|
}
|
|
elsif ( exists $itemtypes->{ $item->effective_itemtype }
|
|
&& $itemtypes->{ $item->effective_itemtype }->{notforloan} == 1 )
|
|
{
|
|
$status = "reference";
|
|
$substatus = "Not for loan";
|
|
}
|
|
else {
|
|
$status = "available";
|
|
}
|
|
my $homebranch = xml_escape($branches{$item->homebranch});
|
|
my $holdingbranch = xml_escape($branches{$item->holdingbranch});
|
|
my $location = xml_escape($item->location && exists $shelflocations->{$item->location} ? $shelflocations->{$item->location} : $item->location);
|
|
my $ccode = xml_escape($item->ccode && exists $ccodes->{$item->ccode} ? $ccodes->{$item->ccode} : $item->ccode);
|
|
my $itemcallnumber = xml_escape($item->itemcallnumber);
|
|
my $stocknumber = xml_escape($item->stocknumber);
|
|
$xml .=
|
|
"<item>"
|
|
. "<homebranch>$homebranch</homebranch>"
|
|
. "<holdingbranch>$holdingbranch</holdingbranch>"
|
|
. "<location>$location</location>"
|
|
. "<ccode>$ccode</ccode>"
|
|
. "<status>".( $status // q{} )."</status>"
|
|
. "<substatus>$substatus</substatus>"
|
|
. "<itemcallnumber>$itemcallnumber</itemcallnumber>"
|
|
. "<stocknumber>$stocknumber</stocknumber>"
|
|
. "</item>";
|
|
}
|
|
$xml = "<items xmlns=\"http://www.koha-community.org/items\">".$xml."</items>";
|
|
return $xml;
|
|
}
|
|
|
|
=head2 engine
|
|
|
|
Returns reference to XSLT handler object.
|
|
|
|
=cut
|
|
|
|
sub engine {
|
|
return $engine;
|
|
}
|
|
|
|
1;
|
|
|
|
__END__
|
|
|
|
=head1 AUTHOR
|
|
|
|
Joshua Ferraro <jmf@liblime.com>
|
|
|
|
Koha Development Team <http://koha-community.org/>
|
|
|
|
=cut
|