489 lines
17 KiB
Perl
489 lines
17 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 <https://www.gnu.org/licenses>.
|
|
|
|
use Modern::Perl;
|
|
use base 'Exporter';
|
|
use Koha::XSLT::Base;
|
|
my $engine; #XSLT Handler object
|
|
|
|
BEGIN {
|
|
our @EXPORT_OK = qw(
|
|
buildKohaItemsNamespace
|
|
XSLTParse4Display
|
|
);
|
|
$engine = Koha::XSLT::Base->new( { do_not_return_source => 1 } );
|
|
}
|
|
|
|
use C4::Context;
|
|
use C4::Koha qw( xml_escape );
|
|
use C4::Biblio qw( GetAuthorisedValueDesc GetFrameworkCode GetMarcStructure );
|
|
use Koha::AuthorisedValues;
|
|
use Koha::ItemTypes;
|
|
use Koha::Plugins;
|
|
use Koha::RecordProcessor;
|
|
use Koha::Libraries;
|
|
use Koha::Recalls;
|
|
use Koha::TemplateUtils qw( process_tt );
|
|
use Koha::AdditionalContents;
|
|
use C4::Scrubber;
|
|
use C4::Languages;
|
|
|
|
=head1 NAME
|
|
|
|
C4::XSLT - Functions for displaying XSLT-generated content
|
|
|
|
=head1 FUNCTIONS
|
|
|
|
=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
|
|
|
|
=head2 _get_best_default_xslt_filename
|
|
|
|
Find the default file, trying a few standard variations, until the file is located
|
|
|
|
=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;
|
|
}
|
|
|
|
=head2 get_xslt_sysprefs
|
|
|
|
Missing POD for get_xslt_sysprefs.
|
|
|
|
=cut
|
|
|
|
sub get_xslt_sysprefs {
|
|
my $sysxml = "<sysprefs>\n";
|
|
foreach my $syspref (
|
|
qw/ hidelostitems OPACURLOpenInNewWindow
|
|
DisplayOPACiconsXSLT URLLinkText viewISBD
|
|
OPACBaseURL TraceCompleteSubfields UseICUStyleQuotes
|
|
UseAuthoritiesForTracings TraceSubjectSubdivisions
|
|
Display856uAsImage OPACDisplay856uAsImage
|
|
UseControlNumber IntranetBiblioDefaultView BiblioDefaultView
|
|
OPACItemLocation DisplayIconsXSLT
|
|
AlternateHoldingsField AlternateHoldingsSeparator
|
|
TrackClicks opacthemes IdRef OpacSuppression
|
|
OPACResultsLibrary OPACShowOpenURL
|
|
OpenURLResolverURL OpenURLImageLocation
|
|
OPACResultsMaxItems OPACResultsMaxItemsUnavailable OPACResultsUnavailableGroupingBy
|
|
OpenURLText OPACShowMusicalInscripts OPACPlayMusicalInscripts ContentWarningField
|
|
AuthorLinkSortBy AuthorLinkSortOrder /
|
|
)
|
|
{
|
|
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;
|
|
}
|
|
|
|
=head2 get_xsl_filename
|
|
|
|
Missing POD for get_xsl_filename.
|
|
|
|
=cut
|
|
|
|
sub get_xsl_filename {
|
|
my ($xslsyspref) = @_;
|
|
|
|
my $lang = C4::Languages::getlanguage();
|
|
|
|
my $xslfilename = C4::Context->preference($xslsyspref) || "default";
|
|
|
|
if ( $xslfilename =~ /^\s*"?default"?\s*$/i ) {
|
|
|
|
my ( $htdocs, $theme, $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/;
|
|
}
|
|
|
|
return $xslfilename;
|
|
}
|
|
|
|
sub XSLTParse4Display {
|
|
my ($params) = @_;
|
|
|
|
my $biblionumber = $params->{biblionumber};
|
|
my $record = $params->{record};
|
|
my $xslsyspref = $params->{xsl_syspref};
|
|
my $fixamps = $params->{fix_amps};
|
|
my $hidden_items = $params->{hidden_items} || [];
|
|
my $variables = $params->{xslt_variables};
|
|
my $items_rs = $params->{items_rs};
|
|
my $interface = C4::Context->interface;
|
|
|
|
die "Mandatory \$params->{xsl_syspref} was not provided, called with biblionumber $params->{biblionumber}"
|
|
if not defined $params->{xsl_syspref};
|
|
|
|
my $xslfilename = get_xsl_filename($xslsyspref);
|
|
|
|
my $frameworkcode = GetFrameworkCode($biblionumber) || '';
|
|
my $filters = ['ExpandCodedFields'];
|
|
|
|
Koha::Plugins->call(
|
|
'xslt_record_processor_filters',
|
|
{
|
|
filters => $filters,
|
|
interface => $interface,
|
|
frameworkcode => $frameworkcode,
|
|
xsl_syspref => $xslsyspref,
|
|
biblionumber => $biblionumber,
|
|
}
|
|
);
|
|
|
|
my $record_processor = Koha::RecordProcessor->new(
|
|
{
|
|
filters => $filters,
|
|
options => {
|
|
interface => $interface,
|
|
frameworkcode => $frameworkcode
|
|
}
|
|
}
|
|
);
|
|
$record_processor->process($record);
|
|
|
|
# grab the XML, run it through our stylesheet, push it out to the browser
|
|
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, $items_rs );
|
|
}
|
|
my $xmlrecord = $record->as_xml( C4::Context->preference('marcflavour') );
|
|
|
|
$variables ||= {};
|
|
my $biblio;
|
|
if ( $interface eq 'opac' && C4::Context->preference('OPACShowOpenURL') ) {
|
|
my @biblio_itemtypes;
|
|
$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 $extracontent = '';
|
|
my $location;
|
|
my $branch = C4::Context->userenv && C4::Context->userenv->{branch};
|
|
$branch //= '';
|
|
my $lang = C4::Languages::getlanguage;
|
|
|
|
if ( $xslsyspref eq "XSLTDetailsDisplay" ) {
|
|
$location = 'StaffDetailPage';
|
|
} elsif ( $xslsyspref eq "XSLTResultsDisplay" ) {
|
|
$location = 'StaffResultsPage';
|
|
} elsif ( $xslsyspref eq "OPACXSLTDetailsDisplay" ) {
|
|
$location = 'OPACDetailPage';
|
|
} elsif ( $xslsyspref eq "OPACXSLTResultsDisplay" ) {
|
|
$location = 'OPACResultsPage';
|
|
} elsif ( $xslsyspref eq "XSLTListsDisplay" ) {
|
|
$location = 'StaffListPage';
|
|
} elsif ( $xslsyspref eq "OPACXSLTListsDisplay" ) {
|
|
$location = 'OPACListPage';
|
|
}
|
|
|
|
my @record_display_customizations = Koha::AdditionalContents->search_for_display(
|
|
{
|
|
category => 'record_display',
|
|
location => $location,
|
|
lang => $lang,
|
|
library_id => $branch,
|
|
}
|
|
)->as_list;
|
|
|
|
if (@record_display_customizations) {
|
|
foreach my $customization (@record_display_customizations) {
|
|
$extracontent .= $customization->content;
|
|
}
|
|
}
|
|
|
|
if ($extracontent) {
|
|
my $scrubber = C4::Scrubber->new('record_display');
|
|
my $extracontentproccessed = process_tt( $extracontent, { record => $record } );
|
|
$extracontent = $scrubber->scrub($extracontentproccessed);
|
|
}
|
|
|
|
# embed variables
|
|
my $varxml = "<variables>\n";
|
|
while ( my ( $key, $value ) = each %$variables ) {
|
|
$value //= q{};
|
|
$varxml .= "<variable name=\"$key\">$value</variable>\n";
|
|
}
|
|
$varxml .= "</variables>\n";
|
|
|
|
my $sysxml = get_xslt_sysprefs();
|
|
$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
|
|
my $transformed_xml = $engine->transform( $xmlrecord, $xslfilename );
|
|
|
|
my $concatenated_content = $transformed_xml . $extracontent;
|
|
return $concatenated_content;
|
|
}
|
|
|
|
=head2 buildKohaItemsNamespace
|
|
|
|
my $items_xml = buildKohaItemsNamespace( $biblionumber, [ $hidden_items, $items ] );
|
|
|
|
Returns XML for items. It accepts two optional parameters:
|
|
- I<$hidden_items>: An arrayref of itemnumber values, for items that should be hidden
|
|
- I<$items>: A Koha::Items resultset, for the items to be returned
|
|
|
|
If both parameters are passed, I<$items> is used as the basis resultset, and I<$hidden_items>
|
|
are filtered out of it.
|
|
|
|
Is only used in this module currently.
|
|
|
|
=cut
|
|
|
|
sub buildKohaItemsNamespace {
|
|
my ( $biblionumber, $hidden_items, $items_rs ) = @_;
|
|
|
|
$hidden_items ||= [];
|
|
|
|
my $query = {};
|
|
$query = { 'me.itemnumber' => { not_in => $hidden_items } }
|
|
if $hidden_items;
|
|
|
|
unless ( $items_rs && ref($items_rs) eq 'Koha::Items' ) {
|
|
$query->{'me.biblionumber'} = $biblionumber;
|
|
$items_rs = Koha::Items->new;
|
|
}
|
|
|
|
my $items =
|
|
$items_rs->search( $query, { prefetch => [ 'current_branchtransfers', 'reserves', 'tmp_holdsqueue' ] } );
|
|
|
|
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' } )->as_list;
|
|
|
|
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' } );
|
|
my $ref_status = C4::Context->preference('Reference_NFL_Statuses') // q{}; # fallback to 1|2 removed
|
|
|
|
while ( my $item = $items->next ) {
|
|
my $status;
|
|
my $substatus = '';
|
|
my $recalls_count;
|
|
|
|
if ( C4::Context->preference('UseRecalls') ) {
|
|
$recalls_count = Koha::Recalls->search( { item_id => $item->itemnumber, status => 'waiting' } )->count;
|
|
}
|
|
|
|
if ($recalls_count) {
|
|
|
|
# recalls take priority over holds
|
|
$status = 'other';
|
|
$substatus = 'Recall waiting';
|
|
} elsif ( $item->has_pending_hold ) {
|
|
$status = 'other';
|
|
$substatus = 'Pending hold';
|
|
} elsif ( $item->holds->count && $item->holds->waiting->count ) {
|
|
$status = 'other';
|
|
$substatus = 'Hold waiting';
|
|
} elsif ( $item->_result->current_branchtransfers->count ) {
|
|
$status = 'other';
|
|
$substatus = 'In transit';
|
|
} elsif ( $item->damaged && !C4::Context->preference('AllowHoldsOnDamagedItems') ) {
|
|
$status = 'other';
|
|
$substatus = 'Damaged';
|
|
} elsif ( $item->itemlost ) {
|
|
$status = 'other';
|
|
$substatus = "Lost";
|
|
} elsif ( $item->withdrawn ) {
|
|
$status = 'other';
|
|
$substatus = "Withdrawn";
|
|
} elsif ( $item->onloan ) {
|
|
$status = 'other';
|
|
$substatus = "Checked out";
|
|
} elsif ( $item->notforloan ) {
|
|
$status =
|
|
$item->notforloan =~ /^($ref_status)$/
|
|
? "reference"
|
|
: "reallynotforloan";
|
|
$substatus =
|
|
exists $descs{ $item->notforloan } ? $descs{ $item->notforloan }->{opac_description} : "Not for loan";
|
|
} elsif ( exists $itemtypes->{ $item->effective_itemtype }
|
|
&& $itemtypes->{ $item->effective_itemtype }->{notforloan}
|
|
&& $itemtypes->{ $item->effective_itemtype }->{notforloan} == 1 )
|
|
{
|
|
$status =
|
|
"1" =~ /^($ref_status)$/
|
|
? "reference"
|
|
: "reallynotforloan";
|
|
$substatus = "Not for loan";
|
|
} else {
|
|
$status = "available";
|
|
}
|
|
my $homebranch = C4::Koha::xml_escape( $branches{ $item->homebranch } );
|
|
my $holdingbranch = C4::Koha::xml_escape( $branches{ $item->holdingbranch } );
|
|
my $resultbranch = C4::Context->preference('OPACResultsLibrary') eq 'homebranch' ? $homebranch : $holdingbranch;
|
|
my $location = C4::Koha::xml_escape(
|
|
$item->location && exists $shelflocations->{ $item->location }
|
|
? $shelflocations->{ $item->location }
|
|
: $item->location
|
|
);
|
|
my $ccode = C4::Koha::xml_escape( $item->ccode
|
|
&& exists $ccodes->{ $item->ccode } ? $ccodes->{ $item->ccode } : $item->ccode );
|
|
my $itemcallnumber = C4::Koha::xml_escape( $item->itemcallnumber );
|
|
my $stocknumber = C4::Koha::xml_escape( $item->stocknumber );
|
|
$xml .=
|
|
"<item>"
|
|
. "<homebranch>$homebranch</homebranch>"
|
|
. "<holdingbranch>$holdingbranch</holdingbranch>"
|
|
. "<resultbranch>$resultbranch</resultbranch>"
|
|
. "<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 <https://koha-community.org/>
|
|
|
|
=cut
|