3 # Copyright (C) 2006 LibLime
4 # <jmf at liblime dot com>
5 # Parts Copyright Katrin Fischer 2011
6 # Parts Copyright ByWater Solutions 2011
7 # Parts Copyright Biblibre 2012
9 # This file is part of Koha.
11 # Koha is free software; you can redistribute it and/or modify it
12 # under the terms of the GNU General Public License as published by
13 # the Free Software Foundation; either version 3 of the License, or
14 # (at your option) any later version.
16 # Koha is distributed in the hope that it will be useful, but
17 # WITHOUT ANY WARRANTY; without even the implied warranty of
18 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 # GNU General Public License for more details.
21 # You should have received a copy of the GNU General Public License
22 # along with Koha; if not, see <http://www.gnu.org/licenses>.
32 use Koha::AuthorisedValues;
39 use vars qw(@ISA @EXPORT);
41 my $engine; #XSLT Handler object
42 my %authval_per_framework;
43 # Cache for tagfield-tagsubfield to decode per framework.
44 # Should be preferably be placed in Koha-core...
51 &transformMARCXML4XSLT
53 $engine=Koha::XSLT::Base->new( { do_not_return_source => 1 } );
58 C4::XSLT - Functions for displaying XSLT-generated content
62 =head2 transformMARCXML4XSLT
64 Replaces codes with authorized values in a MARC::Record object
65 Is only used in this module currently.
69 sub transformMARCXML4XSLT {
70 my ($biblionumber, $record) = @_;
71 my $frameworkcode = GetFrameworkCode($biblionumber) || '';
72 my $tagslib = &GetMarcStructure(1, $frameworkcode, { unsafe => 1 });
74 # FIXME: wish there was a better way to handle exceptions
76 @fields = $record->fields();
78 if ($@) { warn "PROBLEM WITH RECORD"; next; }
79 my $marcflavour = C4::Context->preference('marcflavour');
80 my $av = getAuthorisedValues4MARCSubfields($frameworkcode);
81 foreach my $tag ( keys %$av ) {
82 foreach my $field ( $record->field( $tag ) ) {
83 if ( $av->{ $tag } ) {
84 my @new_subfields = ();
85 for my $subfield ( $field->subfields() ) {
86 my ( $letter, $value ) = @$subfield;
87 # Replace the field value with the authorised value *except* for MARC21/NORMARC field 942$n (suppression in opac)
88 if ( !( $tag eq '942' && $subfield->[0] eq 'n' ) || $marcflavour eq 'UNIMARC' ) {
89 $value = GetAuthorisedValueDesc( $tag, $letter, $value, '', $tagslib )
90 if $av->{ $tag }->{ $letter };
92 push( @new_subfields, $letter, $value );
94 $field ->replace_with( MARC::Field->new(
106 =head2 getAuthorisedValues4MARCSubfields
108 Returns a ref of hash of ref of hash for tag -> letter controlled by authorised values
109 Is only used in this module currently.
113 sub getAuthorisedValues4MARCSubfields {
114 my ($frameworkcode) = @_;
115 unless ( $authval_per_framework{ $frameworkcode } ) {
116 my $dbh = C4::Context->dbh;
117 my $sth = $dbh->prepare("SELECT DISTINCT tagfield, tagsubfield
118 FROM marc_subfield_structure
119 WHERE authorised_value IS NOT NULL
120 AND authorised_value!=''
121 AND frameworkcode=?");
122 $sth->execute( $frameworkcode );
124 while ( my ( $tag, $letter ) = $sth->fetchrow() ) {
125 $av->{ $tag }->{ $letter } = 1;
127 $authval_per_framework{ $frameworkcode } = $av;
129 return $authval_per_framework{ $frameworkcode };
132 =head2 XSLTParse4Display
134 Returns xml for biblionumber and requested XSLT transformation.
135 Returns undef if the transform fails.
137 Used in OPAC results and detail, intranet results and detail, list display.
138 (Depending on the settings of your XSLT preferences.)
140 The helper function _get_best_default_xslt_filename is used in a unit test.
144 sub _get_best_default_xslt_filename {
145 my ($htdocs, $theme, $lang, $base_xslfile) = @_;
148 "$htdocs/$theme/$lang/xslt/${base_xslfile}", # exact match
149 "$htdocs/$theme/en/xslt/${base_xslfile}", # if not, preferred theme in English
150 "$htdocs/prog/$lang/xslt/${base_xslfile}", # if not, 'prog' theme in preferred language
151 "$htdocs/prog/en/xslt/${base_xslfile}", # otherwise, prog theme in English; should always
155 foreach my $filename (@candidates) {
156 $xslfilename = $filename;
158 last; # we have a winner!
164 sub get_xslt_sysprefs {
165 my $sysxml = "<sysprefs>\n";
166 foreach my $syspref ( qw/ hidelostitems OPACURLOpenInNewWindow
167 DisplayOPACiconsXSLT URLLinkText viewISBD
168 OPACBaseURL TraceCompleteSubfields UseICUStyleQuotes
169 UseAuthoritiesForTracings TraceSubjectSubdivisions
170 Display856uAsImage OPACDisplay856uAsImage
171 UseControlNumber IntranetBiblioDefaultView BiblioDefaultView
172 OPACItemLocation DisplayIconsXSLT
173 AlternateHoldingsField AlternateHoldingsSeparator
174 TrackClicks opacthemes IdRef OpacSuppression
175 OPACResultsLibrary OPACShowOpenURL
176 OpenURLResolverURL OpenURLImageLocation
177 OpenURLText OPACShowMusicalInscripts OPACPlayMusicalInscripts / )
179 my $sp = C4::Context->preference( $syspref );
180 next unless defined($sp);
181 $sysxml .= "<syspref name=\"$syspref\">$sp</syspref>\n";
184 # singleBranchMode was a system preference, but no longer is
185 # we can retain it here for compatibility
186 my $singleBranchMode = Koha::Libraries->search->count == 1 ? 1 : 0;
187 $sysxml .= "<syspref name=\"singleBranchMode\">$singleBranchMode</syspref>\n";
189 $sysxml .= "</sysprefs>\n";
193 sub XSLTParse4Display {
194 my ( $biblionumber, $orig_record, $xslsyspref, $fixamps, $hidden_items, $sysxml, $xslfilename, $lang, $variables, $items_rs ) = @_;
196 $sysxml ||= C4::Context->preference($xslsyspref);
197 $xslfilename ||= C4::Context->preference($xslsyspref);
198 $lang ||= C4::Languages::getlanguage();
200 if ( $xslfilename =~ /^\s*"?default"?\s*$/i ) {
204 if ($xslsyspref eq "XSLTDetailsDisplay") {
205 $htdocs = C4::Context->config('intrahtdocs');
206 $theme = C4::Context->preference("template");
207 $xslfile = C4::Context->preference('marcflavour') .
208 "slim2intranetDetail.xsl";
209 } elsif ($xslsyspref eq "XSLTResultsDisplay") {
210 $htdocs = C4::Context->config('intrahtdocs');
211 $theme = C4::Context->preference("template");
212 $xslfile = C4::Context->preference('marcflavour') .
213 "slim2intranetResults.xsl";
214 } elsif ($xslsyspref eq "OPACXSLTDetailsDisplay") {
215 $htdocs = C4::Context->config('opachtdocs');
216 $theme = C4::Context->preference("opacthemes");
217 $xslfile = C4::Context->preference('marcflavour') .
218 "slim2OPACDetail.xsl";
219 } elsif ($xslsyspref eq "OPACXSLTResultsDisplay") {
220 $htdocs = C4::Context->config('opachtdocs');
221 $theme = C4::Context->preference("opacthemes");
222 $xslfile = C4::Context->preference('marcflavour') .
223 "slim2OPACResults.xsl";
224 } elsif ($xslsyspref eq 'XSLTListsDisplay') {
225 # Lists default to *Results.xslt
226 $htdocs = C4::Context->config('intrahtdocs');
227 $theme = C4::Context->preference("template");
228 $xslfile = C4::Context->preference('marcflavour') .
229 "slim2intranetResults.xsl";
230 } elsif ($xslsyspref eq 'OPACXSLTListsDisplay') {
231 # Lists default to *Results.xslt
232 $htdocs = C4::Context->config('opachtdocs');
233 $theme = C4::Context->preference("opacthemes");
234 $xslfile = C4::Context->preference('marcflavour') .
235 "slim2OPACResults.xsl";
237 $xslfilename = _get_best_default_xslt_filename($htdocs, $theme, $lang, $xslfile);
240 if ( $xslfilename =~ m/\{langcode\}/ ) {
241 $xslfilename =~ s/\{langcode\}/$lang/;
244 # grab the XML, run it through our stylesheet, push it out to the browser
245 my $record = transformMARCXML4XSLT($biblionumber, $orig_record);
247 if ( $xslsyspref eq "OPACXSLTDetailsDisplay" || $xslsyspref eq "XSLTDetailsDisplay" || $xslsyspref eq "XSLTResultsDisplay" ) {
248 $itemsxml = ""; #We don't use XSLT for items display on these pages
250 $itemsxml = buildKohaItemsNamespace($biblionumber, $hidden_items, $items_rs);
252 my $xmlrecord = $record->as_xml(C4::Context->preference('marcflavour'));
255 if (C4::Context->preference('OPACShowOpenURL')) {
256 my @biblio_itemtypes;
257 my $biblio = Koha::Biblios->find($biblionumber);
258 if (C4::Context->preference('item-level_itypes')) {
259 @biblio_itemtypes = $biblio->items->get_column("itype");
261 push @biblio_itemtypes, $biblio->itemtype;
263 my @itypes = split( /\s/, C4::Context->preference('OPACOpenURLItemTypes') );
265 map { $original{$_} = 1 } @biblio_itemtypes;
266 if ( grep { $original{$_} } @itypes ) {
267 $variables->{OpenURLResolverURL} = $biblio->get_openurl;
270 my $varxml = "<variables>\n";
271 while (my ($key, $value) = each %$variables) {
272 $varxml .= "<variable name=\"$key\">$value</variable>\n";
274 $varxml .= "</variables>\n";
276 $xmlrecord =~ s/\<\/record\>/$itemsxml$sysxml$varxml\<\/record\>/;
277 if ($fixamps) { # We need to correct the ampersand entities that Zebra outputs
278 $xmlrecord =~ s/\&amp;/\&/g;
279 $xmlrecord =~ s/\&\;lt\;/\<\;/g;
280 $xmlrecord =~ s/\&\;gt\;/\>\;/g;
282 $xmlrecord =~ s/\& /\&\; /;
283 $xmlrecord =~ s/\&\;amp\; /\&\; /;
285 #If the xslt should fail, we will return undef (old behavior was
287 #Note that we did set do_not_return_source at object construction
288 return $engine->transform($xmlrecord, $xslfilename ); #file or URL
291 =head2 buildKohaItemsNamespace
293 my $items_xml = buildKohaItemsNamespace( $biblionumber, [ $hidden_items, $items ] );
295 Returns XML for items. It accepts two optional parameters:
296 - I<$hidden_items>: An arrayref of itemnumber values, for items that should be hidden
297 - I<$items>: A Koha::Items resultset, for the items to be returned
299 If both parameters are passed, I<$items> is used as the basis resultset, and I<$hidden_items>
300 are filtered out of it.
302 Is only used in this module currently.
306 sub buildKohaItemsNamespace {
307 my ($biblionumber, $hidden_items, $items_rs) = @_;
309 $hidden_items ||= [];
312 $query = { 'me.itemnumber' => { not_in => $hidden_items } }
315 unless ( $items_rs && ref($items_rs) eq 'Koha::Items' ) {
316 $query->{'me.biblionumber'} = $biblionumber;
317 $items_rs = Koha::Items->new;
320 my $items = $items_rs->search( $query, { prefetch => [ 'branchtransfers', 'reserves' ] } );
323 { map { $_->{authorised_value} => $_->{opac_description} } Koha::AuthorisedValues->get_descriptions_by_koha_field( { frameworkcode => "", kohafield => 'items.location' } ) };
325 { map { $_->{authorised_value} => $_->{opac_description} } Koha::AuthorisedValues->get_descriptions_by_koha_field( { frameworkcode => "", kohafield => 'items.ccode' } ) };
327 my %branches = map { $_->branchcode => $_->branchname } Koha::Libraries->search({}, { order_by => 'branchname' });
329 my $itemtypes = { map { $_->{itemtype} => $_ } @{ Koha::ItemTypes->search->unblessed } };
331 my %descs = map { $_->{authorised_value} => $_ } Koha::AuthorisedValues->get_descriptions_by_koha_field( { kohafield => 'items.notforloan' } );
332 my $ref_status = C4::Context->preference('Reference_NFL_Statuses') || '1|2';
334 while ( my $item = $items->next ) {
338 if ($item->has_pending_hold) {
339 $status = 'Pending hold';
341 elsif ( $item->holds->waiting->count ) {
344 elsif ($item->get_transfer) {
345 $status = 'In transit';
347 elsif ($item->damaged) {
350 elsif ($item->itemlost) {
353 elsif ( $item->withdrawn) {
354 $status = "Withdrawn";
356 elsif ($item->onloan) {
357 $status = "Checked out";
359 elsif ( $item->notforloan ) {
360 $status = $item->notforloan =~ /^($ref_status)$/
362 : "reallynotforloan";
363 $substatus = exists $descs{$item->notforloan} ? $descs{$item->notforloan}->{opac_description} : "Not for loan";
365 elsif ( exists $itemtypes->{ $item->effective_itemtype }
366 && $itemtypes->{ $item->effective_itemtype }->{notforloan}
367 && $itemtypes->{ $item->effective_itemtype }->{notforloan} == 1 )
369 $status = "1" =~ /^($ref_status)$/
371 : "reallynotforloan";
372 $substatus = "Not for loan";
375 $status = "available";
377 my $homebranch = xml_escape($branches{$item->homebranch});
378 my $holdingbranch = xml_escape($branches{$item->holdingbranch});
379 my $location = xml_escape($item->location && exists $shelflocations->{$item->location} ? $shelflocations->{$item->location} : $item->location);
380 my $ccode = xml_escape($item->ccode && exists $ccodes->{$item->ccode} ? $ccodes->{$item->ccode} : $item->ccode);
381 my $itemcallnumber = xml_escape($item->itemcallnumber);
382 my $stocknumber = xml_escape($item->stocknumber);
385 . "<homebranch>$homebranch</homebranch>"
386 . "<holdingbranch>$holdingbranch</holdingbranch>"
387 . "<location>$location</location>"
388 . "<ccode>$ccode</ccode>"
389 . "<status>".( $status // q{} )."</status>"
390 . "<substatus>$substatus</substatus>"
391 . "<itemcallnumber>$itemcallnumber</itemcallnumber>"
392 . "<stocknumber>$stocknumber</stocknumber>"
395 $xml = "<items xmlns=\"http://www.koha-community.org/items\">".$xml."</items>";
401 Returns reference to XSLT handler object.
415 Joshua Ferraro <jmf@liblime.com>
417 Koha Development Team <http://koha-community.org/>