X-Git-Url: http://git.rot13.org/?a=blobdiff_plain;f=C4%2FXSLT.pm;h=f434fa4fdc1f3212be5bb3b3e2851b0a6550e5a9;hb=dbcd0511e7580cce996965d54982bfcce7c97b14;hp=cbdf35b49df15cf5a7234013809d51506a92d559;hpb=f91bd36399b1ab46649482f80e0ff1b438a8d9f6;p=koha.git diff --git a/C4/XSLT.pm b/C4/XSLT.pm index cbdf35b49d..f434fa4fdc 100644 --- a/C4/XSLT.pm +++ b/C4/XSLT.pm @@ -1,4 +1,5 @@ package C4::XSLT; + # Copyright (C) 2006 LibLime # # Parts Copyright Katrin Fischer 2011 @@ -7,44 +8,48 @@ package C4::XSLT; # # 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 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. +# 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. +# You should have received a copy of the GNU General Public License +# along with Koha; if not, see . -use strict; -use warnings; +use Modern::Perl; use C4::Context; -use C4::Branch; use C4::Items; use C4::Koha; use C4::Biblio; use C4::Circulation; use C4::Reserves; +use Koha::AuthorisedValues; +use Koha::ItemTypes; +use Koha::XSLT_Handler; +use Koha::Libraries; + use Encode; -use XML::LibXML; -use XML::LibXSLT; -use LWP::Simple; -use vars qw($VERSION @ISA @EXPORT); +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; - $VERSION = 0.03; @ISA = qw(Exporter); @EXPORT = qw( &XSLTParse4Display - &GetURI ); + $engine=Koha::XSLT_Handler->new( { do_not_return_source => 1 } ); } =head1 NAME @@ -53,35 +58,24 @@ C4::XSLT - Functions for displaying XSLT-generated content =head1 FUNCTIONS -=head2 GetURI - -GetURI file and returns the xslt as a string - -=cut - -sub GetURI { - my ($uri) = @_; - my $string; - $string = get $uri ; - return $string; -} - =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); + 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 ) ) { @@ -89,8 +83,11 @@ sub transformMARCXML4XSLT { my @new_subfields = (); for my $subfield ( $field->subfields() ) { my ( $letter, $value ) = @$subfield; - $value = GetAuthorisedValueDesc( $tag, $letter, $value, '', $tagslib ) - if $av->{ $tag }->{ $letter }; + # 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( @@ -107,14 +104,11 @@ sub transformMARCXML4XSLT { =head2 getAuthorisedValues4MARCSubfields -Returns a ref of hash of ref of hash for tag -> letter controled by authorised values +Returns a ref of hash of ref of hash for tag -> letter controlled by authorised values +Is only used in this module currently. =cut -# Cache for tagfield-tagsubfield to decode per framework. -# Should be preferably be placed in Koha-core... -my %authval_per_framework; - sub getAuthorisedValues4MARCSubfields { my ($frameworkcode) = @_; unless ( $authval_per_framework{ $frameworkcode } ) { @@ -134,95 +128,143 @@ sub getAuthorisedValues4MARCSubfields { return $authval_per_framework{ $frameworkcode }; } -my $stylesheet; +=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 = "\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 / ) + { + my $sp = C4::Context->preference( $syspref ); + next unless defined($sp); + $sysxml .= "$sp\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 .= "$singleBranchMode\n"; + + $sysxml .= "\n"; + return $sysxml; +} sub XSLTParse4Display { - my ( $biblionumber, $orig_record, $xslsyspref, $fixamps, $hidden_items ) = @_; - my $xslfilename = C4::Context->preference($xslsyspref); + my ( $biblionumber, $orig_record, $xslsyspref, $fixamps, $hidden_items, $sysxml, $xslfilename, $lang ) = @_; + + $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") { - $xslfilename = C4::Context->config('intrahtdocs') . - '/' . C4::Context->preference("template") . - '/' . C4::Templates::_current_language() . - '/xslt/' . - C4::Context->preference('marcflavour') . - "slim2intranetDetail.xsl"; + $htdocs = C4::Context->config('intrahtdocs'); + $theme = C4::Context->preference("template"); + $xslfile = C4::Context->preference('marcflavour') . + "slim2intranetDetail.xsl"; } elsif ($xslsyspref eq "XSLTResultsDisplay") { - $xslfilename = C4::Context->config('intrahtdocs') . - '/' . C4::Context->preference("template") . - '/' . C4::Templates::_current_language() . - '/xslt/' . - C4::Context->preference('marcflavour') . + $htdocs = C4::Context->config('intrahtdocs'); + $theme = C4::Context->preference("template"); + $xslfile = C4::Context->preference('marcflavour') . "slim2intranetResults.xsl"; } elsif ($xslsyspref eq "OPACXSLTDetailsDisplay") { - $xslfilename = C4::Context->config('opachtdocs') . - '/' . C4::Context->preference("opacthemes") . - '/' . C4::Templates::_current_language() . - '/xslt/' . - C4::Context->preference('marcflavour') . - "slim2OPACDetail.xsl"; + $htdocs = C4::Context->config('opachtdocs'); + $theme = C4::Context->preference("opacthemes"); + $xslfile = C4::Context->preference('marcflavour') . + "slim2OPACDetail.xsl"; } elsif ($xslsyspref eq "OPACXSLTResultsDisplay") { - $xslfilename = C4::Context->config('opachtdocs') . - '/' . C4::Context->preference("opacthemes") . - '/' . C4::Templates::_current_language() . - '/xslt/' . - C4::Context->preference('marcflavour') . - "slim2OPACResults.xsl"; + $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}/ ) { - my $lang = C4::Templates::_current_language; - $xslfilename =~ s/{langcode}/$lang/; + 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); - #return $record->as_formatted(); my $itemsxml = buildKohaItemsNamespace($biblionumber, $hidden_items); my $xmlrecord = $record->as_xml(C4::Context->preference('marcflavour')); - my $sysxml = "\n"; - foreach my $syspref ( qw/ hidelostitems OPACURLOpenInNewWindow - DisplayOPACiconsXSLT URLLinkText viewISBD - OPACBaseURL TraceCompleteSubfields UseICU - UseAuthoritiesForTracings TraceSubjectSubdivisions - Display856uAsImage OPACDisplay856uAsImage - UseControlNumber - AlternateHoldingsField AlternateHoldingsSeparator / ) - { - my $sp = C4::Context->preference( $syspref ); - next unless defined($sp); - $sysxml .= "$sp\n"; - } - $sysxml .= "\n"; + $xmlrecord =~ s/\<\/record\>/$itemsxml$sysxml\<\/record\>/; - if ($fixamps) { # We need to correct the ampersand entities that Zebra outputs + if ($fixamps) { # We need to correct the HTML entities that Zebra outputs $xmlrecord =~ s/\&amp;/\&/g; + $xmlrecord =~ s/\&\;lt\;/\<\;/g; + $xmlrecord =~ s/\&\;gt\;/\>\;/g; } $xmlrecord =~ s/\& /\&\; /; $xmlrecord =~ s/\&\;amp\; /\&\; /; - my $parser = XML::LibXML->new(); - # don't die when you find &, >, etc - $parser->recover_silently(0); - my $source = $parser->parse_string($xmlrecord); - unless ( $stylesheet->{$xslfilename} ) { - my $xslt = XML::LibXSLT->new(); - my $style_doc; - if ( $xslfilename =~ /^https?:\/\// ) { - my $xsltstring = GetURI($xslfilename); - $style_doc = $parser->parse_string($xsltstring); - } else { - use Cwd; - $style_doc = $parser->parse_file($xslfilename); - } - $stylesheet->{$xslfilename} = $xslt->parse_stylesheet($style_doc); - } - my $results = $stylesheet->{$xslfilename}->transform($source); - my $newxmlrecord = $stylesheet->{$xslfilename}->output_string($results); - return $newxmlrecord; + #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) = @_; @@ -231,35 +273,44 @@ sub buildKohaItemsNamespace { my %hi = map {$_ => 1} @$hidden_items; @items = grep { !$hi{$_->{itemnumber}} } @items; } - my $branches = GetBranches(); - my $itemtypes = GetItemTypes(); + + my $shelflocations = + { map { $_->{authorised_value} => $_->{opac_description} } Koha::AuthorisedValues->get_descriptions_by_koha_field( { frameworkcode => GetFrameworkCode($biblionumber), kohafield => 'items.location' } ) }; + my $ccodes = + { map { $_->{authorised_value} => $_->{opac_description} } Koha::AuthorisedValues->get_descriptions_by_koha_field( { frameworkcode => GetFrameworkCode($biblionumber), kohafield => 'items.ccode' } ) }; + + my %branches = map { $_->branchcode => $_->branchname } Koha::Libraries->search({}, { order_by => 'branchname' }); + + my $itemtypes = { map { $_->{itemtype} => $_ } @{ Koha::ItemTypes->search->unblessed } }; + my $location = ""; + my $ccode = ""; my $xml = ''; for my $item (@items) { my $status; my ( $transfertwhen, $transfertfrom, $transfertto ) = C4::Circulation::GetTransfers($item->{itemnumber}); - my ( $reservestatus, $reserveitem, undef ) = C4::Reserves::CheckReserves($item->{itemnumber}); + my $reservestatus = C4::Reserves::GetReserveStatus( $item->{itemnumber} ); - if ( $itemtypes->{ $item->{itype} }->{notforloan} || $item->{notforloan} || $item->{onloan} || $item->{wthdrawn} || $item->{itemlost} || $item->{damaged} || + if ( ( $item->{itype} && $itemtypes->{ $item->{itype} }->{notforloan} ) || $item->{notforloan} || $item->{onloan} || $item->{withdrawn} || $item->{itemlost} || $item->{damaged} || (defined $transfertwhen && $transfertwhen ne '') || $item->{itemnotforloan} || (defined $reservestatus && $reservestatus eq "Waiting") ){ if ( $item->{notforloan} < 0) { $status = "On order"; } - if ( $item->{itemnotforloan} > 0 || $item->{notforloan} > 0 || $itemtypes->{ $item->{itype} }->{notforloan} == 1 ) { + if ( $item->{itemnotforloan} && $item->{itemnotforloan} > 0 || $item->{notforloan} && $item->{notforloan} > 0 || $item->{itype} && $itemtypes->{ $item->{itype} }->{notforloan} && $itemtypes->{ $item->{itype} }->{notforloan} == 1 ) { $status = "reference"; } if ($item->{onloan}) { $status = "Checked out"; } - if ( $item->{wthdrawn}) { + if ( $item->{withdrawn}) { $status = "Withdrawn"; } if ($item->{itemlost}) { $status = "Lost"; } if ($item->{damaged}) { - $status = "Damaged"; + $status = "Damaged"; } if (defined $transfertwhen && $transfertwhen ne '') { $status = 'In transit'; @@ -270,29 +321,45 @@ sub buildKohaItemsNamespace { } else { $status = "available"; } - my $homebranch = $item->{homebranch}? xml_escape($branches->{$item->{homebranch}}->{'branchname'}):''; - my $itemcallnumber = xml_escape($item->{itemcallnumber}); - $xml.= "$homebranch". - "$status". - "".$itemcallnumber."" - . ""; - + my $homebranch = $item->{homebranch}? xml_escape($branches{$item->{homebranch}}):''; + my $holdingbranch = $item->{holdingbranch}? xml_escape($branches{$item->{holdingbranch}}):''; + $location = $item->{location}? xml_escape($shelflocations->{$item->{location}}||$item->{location}):''; + $ccode = $item->{ccode}? xml_escape($ccodes->{$item->{ccode}}||$item->{ccode}):''; + my $itemcallnumber = xml_escape($item->{itemcallnumber}); + my $stocknumber = $item->{stocknumber}? xml_escape($item->{stocknumber}):''; + $xml .= + "" + . "$homebranch" + . "$holdingbranch" + . "$location" + . "$ccode" + . "".( $status // q{} )."" + . "$itemcallnumber" + . "$stocknumber" + . ""; } $xml = "".$xml.""; return $xml; } +=head2 engine +Returns reference to XSLT handler object. -1; -__END__ +=cut -=head1 NOTES +sub engine { + return $engine; +} -=cut +1; + +__END__ =head1 AUTHOR Joshua Ferraro +Koha Development Team + =cut