X-Git-Url: http://koha-dev.rot13.org:8081/gitweb/?a=blobdiff_plain;f=C4%2FXSLT.pm;h=8c2b4c1bcdc8d0d6bb5bc984409c2683b141c862;hb=f3dd750a972ecd95c566d413a7d468ecc01df9fe;hp=69516742cda0f157b83e85b8a61c66d06d39b071;hpb=308237541beee0672855b43dcab34b1fed40b301;p=koha-ffzg.git diff --git a/C4/XSLT.pm b/C4/XSLT.pm index 69516742cd..8c2b4c1bcd 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,42 @@ 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 Encode; -use XML::LibXML; -use XML::LibXSLT; -use LWP::Simple; - -use vars qw($VERSION @ISA @EXPORT); - +use C4::Koha qw( xml_escape ); +use C4::Biblio qw( GetAuthorisedValueDesc GetFrameworkCode GetMarcStructure ); +use Koha::AuthorisedValues; +use Koha::ItemTypes; +use Koha::RecordProcessor; +use Koha::XSLT::Base; +use Koha::Libraries; +use Koha::Recalls; + +my $engine; #XSLT Handler object + +our (@ISA, @EXPORT_OK); BEGIN { require Exporter; - $VERSION = 3.07.00.049; @ISA = qw(Exporter); - @EXPORT = qw( - &XSLTParse4Display - &GetURI + @EXPORT_OK = qw( + buildKohaItemsNamespace + XSLTParse4Display ); + $engine=Koha::XSLT::Base->new( { do_not_return_source => 1 } ); } =head1 NAME @@ -53,97 +52,79 @@ C4::XSLT - Functions for displaying XSLT-generated content =head1 FUNCTIONS -=head2 GetURI - -GetURI file and returns the xslt as a string - -=cut +=head2 XSLTParse4Display -sub GetURI { - my ($uri) = @_; - my $string; - $string = get $uri ; - return $string; -} +Returns xml for biblionumber and requested XSLT transformation. +Returns undef if the transform fails. -=head2 transformMARCXML4XSLT +Used in OPAC results and detail, intranet results and detail, list display. +(Depending on the settings of your XSLT preferences.) -Replaces codes with authorized values in a MARC::Record object +The helper function _get_best_default_xslt_filename is used in a unit test. =cut -sub transformMARCXML4XSLT { - my ($biblionumber, $record) = @_; - my $frameworkcode = GetFrameworkCode($biblionumber) || ''; - my $tagslib = &GetMarcStructure(1,$frameworkcode); - my @fields; - # FIXME: wish there was a better way to handle exceptions - eval { - @fields = $record->fields(); - }; - if ($@) { warn "PROBLEM WITH RECORD"; next; } - 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; - $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 - ) ); - } +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 $record; + return $xslfilename; } -=head2 getAuthorisedValues4MARCSubfields - -Returns a ref of hash of ref of hash for tag -> letter controled by authorised values +sub get_xslt_sysprefs { + my $sysxml = "\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 / ) + { + my $sp = C4::Context->preference( $syspref ); + next unless defined($sp); + $sysxml .= "$sp\n"; + } -=cut + # 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"; -# 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 } ) { - 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 }; + $sysxml .= "\n"; + return $sysxml; } -my $stylesheet; +sub get_xsl_filename { + my ( $xslsyspref ) = @_; + + my $lang = C4::Languages::getlanguage(); + + my $xslfilename = C4::Context->preference($xslsyspref) || "default"; -sub XSLTParse4Display { - my ( $biblionumber, $orig_record, $xslsyspref, $fixamps, $hidden_items ) = @_; - my $xslfilename = C4::Context->preference($xslsyspref); if ( $xslfilename =~ /^\s*"?default"?\s*$/i ) { - my $htdocs; - my $theme; - my $lang = C4::Templates::_current_language(); - my $xslfile; + + my ( $htdocs, $theme, $xslfile ); + if ($xslsyspref eq "XSLTDetailsDisplay") { $htdocs = C4::Context->config('intrahtdocs'); $theme = C4::Context->preference("template"); @@ -164,149 +145,253 @@ sub XSLTParse4Display { $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 = "$htdocs/$theme/$lang/xslt/$xslfile"; - $xslfilename = "$htdocs/$theme/en/xslt/$xslfile" unless ( $lang ne 'en' && -f $xslfilename ); - $xslfilename = "$htdocs/prog/$lang/xslt/$xslfile" unless ( -f $xslfilename ); - $xslfilename = "$htdocs/prog/en/xslt/$xslfile" unless ( $lang ne 'en' && -f $xslfilename ); + $xslfilename = _get_best_default_xslt_filename($htdocs, $theme, $lang, $xslfile); } if ( $xslfilename =~ m/\{langcode\}/ ) { - my $lang = C4::Templates::_current_language(); $xslfilename =~ s/\{langcode\}/$lang/; } + return $xslfilename; +} + +sub XSLTParse4Display { + my ( $params ) = @_; + + my $biblionumber = $params->{biblionumber}; + my $record = $params->{record}; + my $xslsyspref = $params->{xsl_syspref}; + my $interface = ( $xslsyspref =~ /OPAC/ ) ? 'opac' : 'intranet' ; + my $fixamps = $params->{fix_amps}; + my $hidden_items = $params->{hidden_items} || []; + my $variables = $params->{xslt_variables}; + my $items_rs = $params->{items_rs}; + + 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 $record_processor = Koha::RecordProcessor->new( + { + filters => [ 'ExpandCodedFields' ], + options => { + interface => $interface, + frameworkcode => $frameworkcode + } + } + ); + $record_processor->process($record); + # 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 $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')); - my $sysxml = "\n"; - foreach my $syspref ( qw/ hidelostitems OPACURLOpenInNewWindow - DisplayOPACiconsXSLT URLLinkText viewISBD - OPACBaseURL TraceCompleteSubfields UseICU - UseAuthoritiesForTracings TraceSubjectSubdivisions - Display856uAsImage OPACDisplay856uAsImage - UseControlNumber IntranetBiblioDefaultView BiblioDefaultView - singleBranchMode OPACItemLocation DisplayIconsXSLT - AlternateHoldingsField AlternateHoldingsSeparator - TrackClicks / ) - { - my $sp = C4::Context->preference( $syspref ); - next unless defined($sp); - $sysxml .= "$sp\n"; + + $variables ||= {}; + my $biblio; + if (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; + } } - $sysxml .= "\n"; - $xmlrecord =~ s/\<\/record\>/$itemsxml$sysxml\<\/record\>/; + + my $varxml = "\n"; + while (my ($key, $value) = each %$variables) { + $value //= q{}; + $varxml .= "$value\n"; + } + $varxml .= "\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\; /\&\; /; - 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 + + 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) = @_; + my ($biblionumber, $hidden_items, $items_rs) = @_; + + $hidden_items ||= []; - my @items = C4::Items::GetItemsInfo($biblionumber); - if ($hidden_items && @$hidden_items) { - my %hi = map {$_ => 1} @$hidden_items; - @items = grep { !$hi{$_->{itemnumber}} } @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 $shelflocations = GetKohaAuthorisedValues('items.location',GetFrameworkCode($biblionumber), 'opac'); - my $ccodes = GetKohaAuthorisedValues('items.ccode',GetFrameworkCode($biblionumber), 'opac'); + my $items = $items_rs->search( $query, { 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 = GetBranches(); - my $itemtypes = GetItemTypes(); - my $location = ""; - my $ccode = ""; + my %branches = map { $_->branchcode => $_->branchname } Koha::Libraries->search({}, { order_by => 'branchname' })->as_list; + + my $itemtypes = { map { $_->{itemtype} => $_ } @{ Koha::ItemTypes->search->unblessed } }; my $xml = ''; - for my $item (@items) { - my $status; + my %descs = map { $_->{authorised_value} => $_ } Koha::AuthorisedValues->get_descriptions_by_koha_field( { kohafield => 'items.notforloan' } ); + my $ref_status = C4::Context->preference('Reference_NFL_Statuses') || '1|2'; - my ( $transfertwhen, $transfertfrom, $transfertto ) = C4::Circulation::GetTransfers($item->{itemnumber}); + while ( my $item = $items->next ) { + my $status; + my $substatus = ''; + my $recalls_count; - my $reservestatus = C4::Reserves::GetReserveStatus( $item->{itemnumber} ); + if ( C4::Context->preference('UseRecalls') ) { + $recalls_count = Koha::Recalls->search({ item_id => $item->itemnumber, status => 'waiting' })->count; + } - if ( $itemtypes->{ $item->{itype} }->{notforloan} || $item->{notforloan} || $item->{onloan} || $item->{wthdrawn} || $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 ) { - $status = "reference"; - } - if ($item->{onloan}) { - $status = "Checked out"; - } - if ( $item->{wthdrawn}) { - $status = "Withdrawn"; - } - if ($item->{itemlost}) { - $status = "Lost"; - } - if ($item->{damaged}) { - $status = "Damaged"; - } - if (defined $transfertwhen && $transfertwhen ne '') { - $status = 'In transit'; - } - if (defined $reservestatus && $reservestatus eq "Waiting") { - $status = 'Waiting'; - } - } else { + 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->waiting->count ) { + $status = 'other'; + $substatus = 'Hold waiting'; + } + elsif ($item->get_transfer) { + $status = 'other'; + $substatus = 'In transit'; + } + elsif ($item->damaged) { + $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 = $item->{homebranch}? xml_escape($branches->{$item->{homebranch}}->{'branchname'}):''; - my $holdingbranch = $item->{holdingbranch}? xml_escape($branches->{$item->{holdingbranch}}->{'branchname'}):''; - $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}); - $xml.= "$homebranch". - "$holdingbranch". - "$location". - "$ccode". - "$status". - "".$itemcallnumber."" - . ""; - + 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 .= + "" + . "$homebranch" + . "$holdingbranch" + . "$resultbranch" + . "$location" + . "$ccode" + . "".( $status // q{} )."" + . "$substatus" + . "$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