Bug 17367 - Showing all items must keep show holdings tab in OPAC details
[koha.git] / C4 / Koha.pm
blob5c93cc2a972416449de9ed5b6a980429e72e5da9
1 package C4::Koha;
3 # Copyright 2000-2002 Katipo Communications
4 # Parts Copyright 2010 Nelsonville Public Library
5 # Parts copyright 2010 BibLibre
7 # This file is part of Koha.
9 # Koha is free software; you can redistribute it and/or modify it
10 # under the terms of the GNU General Public License as published by
11 # the Free Software Foundation; either version 3 of the License, or
12 # (at your option) any later version.
14 # Koha is distributed in the hope that it will be useful, but
15 # WITHOUT ANY WARRANTY; without even the implied warranty of
16 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 # GNU General Public License for more details.
19 # You should have received a copy of the GNU General Public License
20 # along with Koha; if not, see <http://www.gnu.org/licenses>.
23 use strict;
24 #use warnings; FIXME - Bug 2505
26 use C4::Context;
27 use Koha::Caches;
28 use Koha::DateUtils qw(dt_from_string);
29 use Koha::Libraries;
30 use DateTime::Format::MySQL;
31 use Business::ISBN;
32 use autouse 'Data::cselectall_arrayref' => qw(Dumper);
33 use DBI qw(:sql_types);
34 use vars qw(@ISA @EXPORT @EXPORT_OK $DEBUG);
36 BEGIN {
37 require Exporter;
38 @ISA = qw(Exporter);
39 @EXPORT = qw(
40 &GetPrinters &GetPrinter
41 &GetItemTypes &getitemtypeinfo
42 &GetItemTypesCategorized &GetItemTypesByCategory
43 &getframeworks &getframeworkinfo
44 &GetFrameworksLoop
45 &getallthemes
46 &getFacets
47 &getnbpages
48 &get_infos_of
49 &get_notforloan_label_of
50 &getitemtypeimagedir
51 &getitemtypeimagesrc
52 &getitemtypeimagelocation
53 &GetAuthorisedValues
54 &GetAuthorisedValueCategories
55 &GetKohaAuthorisedValues
56 &GetKohaAuthorisedValuesFromField
57 &GetKohaAuthorisedValuesMapping
58 &GetAuthorisedValueByCode
59 &GetAuthValCode
60 &GetNormalizedUPC
61 &GetNormalizedISBN
62 &GetNormalizedEAN
63 &GetNormalizedOCLCNumber
64 &xml_escape
66 &GetVariationsOfISBN
67 &GetVariationsOfISBNs
68 &NormalizeISBN
70 $DEBUG
72 $DEBUG = 0;
73 @EXPORT_OK = qw( GetDailyQuote );
76 =head1 NAME
78 C4::Koha - Perl Module containing convenience functions for Koha scripts
80 =head1 SYNOPSIS
82 use C4::Koha;
84 =head1 DESCRIPTION
86 Koha.pm provides many functions for Koha scripts.
88 =head1 FUNCTIONS
90 =cut
92 =head2 GetItemTypes
94 $itemtypes = &GetItemTypes( style => $style );
96 Returns information about existing itemtypes.
98 Params:
99 style: either 'array' or 'hash', defaults to 'hash'.
100 'array' returns an arrayref,
101 'hash' return a hashref with the itemtype value as the key
103 build a HTML select with the following code :
105 =head3 in PERL SCRIPT
107 my $itemtypes = GetItemTypes;
108 my @itemtypesloop;
109 foreach my $thisitemtype (sort keys %$itemtypes) {
110 my $selected = 1 if $thisitemtype eq $itemtype;
111 my %row =(value => $thisitemtype,
112 selected => $selected,
113 description => $itemtypes->{$thisitemtype}->{'description'},
115 push @itemtypesloop, \%row;
117 $template->param(itemtypeloop => \@itemtypesloop);
119 =head3 in TEMPLATE
121 <form action='<!-- TMPL_VAR name="script_name" -->' method=post>
122 <select name="itemtype">
123 <option value="">Default</option>
124 <!-- TMPL_LOOP name="itemtypeloop" -->
125 <option value="<!-- TMPL_VAR name="value" -->" <!-- TMPL_IF name="selected" -->selected<!-- /TMPL_IF -->><!-- TMPL_VAR name="description" --></option>
126 <!-- /TMPL_LOOP -->
127 </select>
128 <input type=text name=searchfield value="<!-- TMPL_VAR name="searchfield" -->">
129 <input type="submit" value="OK" class="button">
130 </form>
132 =cut
134 sub GetItemTypes {
135 my ( %params ) = @_;
136 my $style = defined( $params{'style'} ) ? $params{'style'} : 'hash';
138 require C4::Languages;
139 my $language = C4::Languages::getlanguage();
140 # returns a reference to a hash of references to itemtypes...
141 my $dbh = C4::Context->dbh;
142 my $query = q|
143 SELECT
144 itemtypes.itemtype,
145 itemtypes.description,
146 itemtypes.rentalcharge,
147 itemtypes.notforloan,
148 itemtypes.imageurl,
149 itemtypes.summary,
150 itemtypes.checkinmsg,
151 itemtypes.checkinmsgtype,
152 itemtypes.sip_media_type,
153 itemtypes.hideinopac,
154 itemtypes.searchcategory,
155 COALESCE( localization.translation, itemtypes.description ) AS translated_description
156 FROM itemtypes
157 LEFT JOIN localization ON itemtypes.itemtype = localization.code
158 AND localization.entity = 'itemtypes'
159 AND localization.lang = ?
160 ORDER BY itemtype
162 my $sth = $dbh->prepare($query);
163 $sth->execute( $language );
165 if ( $style eq 'hash' ) {
166 my %itemtypes;
167 while ( my $IT = $sth->fetchrow_hashref ) {
168 $itemtypes{ $IT->{'itemtype'} } = $IT;
170 return ( \%itemtypes );
171 } else {
172 return [ sort { lc $a->{translated_description} cmp lc $b->{translated_description} } @{ $sth->fetchall_arrayref( {} ) } ];
176 =head2 GetItemTypesCategorized
178 $categories = GetItemTypesCategorized();
180 Returns a hashref containing search categories.
181 A search category will be put in the hash if at least one of its itemtypes is visible in OPAC.
182 The categories must be part of Authorized Values (ITEMTYPECAT)
184 =cut
186 sub GetItemTypesCategorized {
187 my $dbh = C4::Context->dbh;
188 # Order is important, so that partially hidden (some items are not visible in OPAC) search
189 # categories will be visible. hideinopac=0 must be last.
190 my $query = q|
191 SELECT itemtype, description, imageurl, hideinopac, 0 as 'iscat' FROM itemtypes WHERE ISNULL(searchcategory) or length(searchcategory) = 0
192 UNION
193 SELECT DISTINCT searchcategory AS `itemtype`,
194 authorised_values.lib_opac AS description,
195 authorised_values.imageurl AS imageurl,
196 hideinopac, 1 as 'iscat'
197 FROM itemtypes
198 LEFT JOIN authorised_values ON searchcategory = authorised_value
199 WHERE searchcategory > '' and hideinopac=1
200 UNION
201 SELECT DISTINCT searchcategory AS `itemtype`,
202 authorised_values.lib_opac AS description,
203 authorised_values.imageurl AS imageurl,
204 hideinopac, 1 as 'iscat'
205 FROM itemtypes
206 LEFT JOIN authorised_values ON searchcategory = authorised_value
207 WHERE searchcategory > '' and hideinopac=0
209 return ($dbh->selectall_hashref($query,'itemtype'));
212 =head2 GetItemTypesByCategory
214 @results = GetItemTypesByCategory( $searchcategory );
216 Returns the itemtype code of all itemtypes included in a searchcategory.
218 =cut
220 sub GetItemTypesByCategory {
221 my ($category) = @_;
222 my $count = 0;
223 my @results;
224 my $dbh = C4::Context->dbh;
225 my $query = qq|SELECT itemtype FROM itemtypes WHERE searchcategory=?|;
226 my $tmp=$dbh->selectcol_arrayref($query,undef,$category);
227 return @$tmp;
230 =head2 getframework
232 $frameworks = &getframework();
234 Returns information about existing frameworks
236 build a HTML select with the following code :
238 =head3 in PERL SCRIPT
240 my $frameworks = getframeworks();
241 my @frameworkloop;
242 foreach my $thisframework (keys %$frameworks) {
243 my $selected = 1 if $thisframework eq $frameworkcode;
244 my %row =(
245 value => $thisframework,
246 selected => $selected,
247 description => $frameworks->{$thisframework}->{'frameworktext'},
249 push @frameworksloop, \%row;
251 $template->param(frameworkloop => \@frameworksloop);
253 =head3 in TEMPLATE
255 <form action="[% script_name %] method=post>
256 <select name="frameworkcode">
257 <option value="">Default</option>
258 [% FOREACH framework IN frameworkloop %]
259 [% IF ( framework.selected ) %]
260 <option value="[% framework.value %]" selected="selected">[% framework.description %]</option>
261 [% ELSE %]
262 <option value="[% framework.value %]">[% framework.description %]</option>
263 [% END %]
264 [% END %]
265 </select>
266 <input type=text name=searchfield value="[% searchfield %]">
267 <input type="submit" value="OK" class="button">
268 </form>
270 =cut
272 sub getframeworks {
274 # returns a reference to a hash of references to branches...
275 my %itemtypes;
276 my $dbh = C4::Context->dbh;
277 my $sth = $dbh->prepare("select * from biblio_framework");
278 $sth->execute;
279 while ( my $IT = $sth->fetchrow_hashref ) {
280 $itemtypes{ $IT->{'frameworkcode'} } = $IT;
282 return ( \%itemtypes );
285 =head2 GetFrameworksLoop
287 $frameworks = GetFrameworksLoop( $frameworkcode );
289 Returns the loop suggested on getframework(), but ordered by framework description.
291 build a HTML select with the following code :
293 =head3 in PERL SCRIPT
295 $template->param( frameworkloop => GetFrameworksLoop( $frameworkcode ) );
297 =head3 in TEMPLATE
299 Same as getframework()
301 <form action="[% script_name %] method=post>
302 <select name="frameworkcode">
303 <option value="">Default</option>
304 [% FOREACH framework IN frameworkloop %]
305 [% IF ( framework.selected ) %]
306 <option value="[% framework.value %]" selected="selected">[% framework.description %]</option>
307 [% ELSE %]
308 <option value="[% framework.value %]">[% framework.description %]</option>
309 [% END %]
310 [% END %]
311 </select>
312 <input type=text name=searchfield value="[% searchfield %]">
313 <input type="submit" value="OK" class="button">
314 </form>
316 =cut
318 sub GetFrameworksLoop {
319 my $frameworkcode = shift;
320 my $frameworks = getframeworks();
321 my @frameworkloop;
322 foreach my $thisframework (sort { uc($frameworks->{$a}->{'frameworktext'}) cmp uc($frameworks->{$b}->{'frameworktext'}) } keys %$frameworks) {
323 my $selected = ( $thisframework eq $frameworkcode ) ? 1 : undef;
324 my %row = (
325 value => $thisframework,
326 selected => $selected,
327 description => $frameworks->{$thisframework}->{'frameworktext'},
329 push @frameworkloop, \%row;
331 return \@frameworkloop;
334 =head2 getframeworkinfo
336 $frameworkinfo = &getframeworkinfo($frameworkcode);
338 Returns information about an frameworkcode.
340 =cut
342 sub getframeworkinfo {
343 my ($frameworkcode) = @_;
344 my $dbh = C4::Context->dbh;
345 my $sth =
346 $dbh->prepare("select * from biblio_framework where frameworkcode=?");
347 $sth->execute($frameworkcode);
348 my $res = $sth->fetchrow_hashref;
349 return $res;
352 =head2 getitemtypeinfo
354 $itemtype = &getitemtypeinfo($itemtype, [$interface]);
356 Returns information about an itemtype. The optional $interface argument
357 sets which interface ('opac' or 'intranet') to return the imageurl for.
358 Defaults to intranet.
360 =cut
362 sub getitemtypeinfo {
363 my ($itemtype, $interface) = @_;
364 my $dbh = C4::Context->dbh;
365 require C4::Languages;
366 my $language = C4::Languages::getlanguage();
367 my $it = $dbh->selectrow_hashref(q|
368 SELECT
369 itemtypes.itemtype,
370 itemtypes.description,
371 itemtypes.rentalcharge,
372 itemtypes.notforloan,
373 itemtypes.imageurl,
374 itemtypes.summary,
375 itemtypes.checkinmsg,
376 itemtypes.checkinmsgtype,
377 itemtypes.sip_media_type,
378 COALESCE( localization.translation, itemtypes.description ) AS translated_description
379 FROM itemtypes
380 LEFT JOIN localization ON itemtypes.itemtype = localization.code
381 AND localization.entity = 'itemtypes'
382 AND localization.lang = ?
383 WHERE itemtypes.itemtype = ?
384 |, undef, $language, $itemtype );
386 $it->{imageurl} = getitemtypeimagelocation( ( ( defined $interface && $interface eq 'opac' ) ? 'opac' : 'intranet' ), $it->{imageurl} );
388 return $it;
391 =head2 getitemtypeimagedir
393 my $directory = getitemtypeimagedir( 'opac' );
395 pass in 'opac' or 'intranet'. Defaults to 'opac'.
397 returns the full path to the appropriate directory containing images.
399 =cut
401 sub getitemtypeimagedir {
402 my $src = shift || 'opac';
403 if ($src eq 'intranet') {
404 return C4::Context->config('intrahtdocs') . '/' .C4::Context->preference('template') . '/img/itemtypeimg';
405 } else {
406 return C4::Context->config('opachtdocs') . '/' . C4::Context->preference('opacthemes') . '/itemtypeimg';
410 sub getitemtypeimagesrc {
411 my $src = shift || 'opac';
412 if ($src eq 'intranet') {
413 return '/intranet-tmpl' . '/' . C4::Context->preference('template') . '/img/itemtypeimg';
414 } else {
415 return '/opac-tmpl' . '/' . C4::Context->preference('opacthemes') . '/itemtypeimg';
419 sub getitemtypeimagelocation {
420 my ( $src, $image ) = @_;
422 return '' if ( !$image );
423 require URI::Split;
425 my $scheme = ( URI::Split::uri_split( $image ) )[0];
427 return $image if ( $scheme );
429 return getitemtypeimagesrc( $src ) . '/' . $image;
432 =head3 _getImagesFromDirectory
434 Find all of the image files in a directory in the filesystem
436 parameters: a directory name
438 returns: a list of images in that directory.
440 Notes: this does not traverse into subdirectories. See
441 _getSubdirectoryNames for help with that.
442 Images are assumed to be files with .gif or .png file extensions.
443 The image names returned do not have the directory name on them.
445 =cut
447 sub _getImagesFromDirectory {
448 my $directoryname = shift;
449 return unless defined $directoryname;
450 return unless -d $directoryname;
452 if ( opendir ( my $dh, $directoryname ) ) {
453 my @images = grep { /\.(gif|png)$/i } readdir( $dh );
454 closedir $dh;
455 @images = sort(@images);
456 return @images;
457 } else {
458 warn "unable to opendir $directoryname: $!";
459 return;
463 =head3 _getSubdirectoryNames
465 Find all of the directories in a directory in the filesystem
467 parameters: a directory name
469 returns: a list of subdirectories in that directory.
471 Notes: this does not traverse into subdirectories. Only the first
472 level of subdirectories are returned.
473 The directory names returned don't have the parent directory name on them.
475 =cut
477 sub _getSubdirectoryNames {
478 my $directoryname = shift;
479 return unless defined $directoryname;
480 return unless -d $directoryname;
482 if ( opendir ( my $dh, $directoryname ) ) {
483 my @directories = grep { -d File::Spec->catfile( $directoryname, $_ ) && ! ( /^\./ ) } readdir( $dh );
484 closedir $dh;
485 return @directories;
486 } else {
487 warn "unable to opendir $directoryname: $!";
488 return;
492 =head3 getImageSets
494 returns: a listref of hashrefs. Each hash represents another collection of images.
496 { imagesetname => 'npl', # the name of the image set (npl is the original one)
497 images => listref of image hashrefs
500 each image is represented by a hashref like this:
502 { KohaImage => 'npl/image.gif',
503 StaffImageUrl => '/intranet-tmpl/prog/img/itemtypeimg/npl/image.gif',
504 OpacImageURL => '/opac-tmpl/prog/itemtypeimg/npl/image.gif'
505 checked => 0 or 1: was this the image passed to this method?
506 Note: I'd like to remove this somehow.
509 =cut
511 sub getImageSets {
512 my %params = @_;
513 my $checked = $params{'checked'} || '';
515 my $paths = { staff => { filesystem => getitemtypeimagedir('intranet'),
516 url => getitemtypeimagesrc('intranet'),
518 opac => { filesystem => getitemtypeimagedir('opac'),
519 url => getitemtypeimagesrc('opac'),
523 my @imagesets = (); # list of hasrefs of image set data to pass to template
524 my @subdirectories = _getSubdirectoryNames( $paths->{'staff'}{'filesystem'} );
525 foreach my $imagesubdir ( @subdirectories ) {
526 warn $imagesubdir if $DEBUG;
527 my @imagelist = (); # hashrefs of image info
528 my @imagenames = _getImagesFromDirectory( File::Spec->catfile( $paths->{'staff'}{'filesystem'}, $imagesubdir ) );
529 my $imagesetactive = 0;
530 foreach my $thisimage ( @imagenames ) {
531 push( @imagelist,
532 { KohaImage => "$imagesubdir/$thisimage",
533 StaffImageUrl => join( '/', $paths->{'staff'}{'url'}, $imagesubdir, $thisimage ),
534 OpacImageUrl => join( '/', $paths->{'opac'}{'url'}, $imagesubdir, $thisimage ),
535 checked => "$imagesubdir/$thisimage" eq $checked ? 1 : 0,
538 $imagesetactive = 1 if "$imagesubdir/$thisimage" eq $checked;
540 push @imagesets, { imagesetname => $imagesubdir,
541 imagesetactive => $imagesetactive,
542 images => \@imagelist };
545 return \@imagesets;
548 =head2 GetPrinters
550 $printers = &GetPrinters();
551 @queues = keys %$printers;
553 Returns information about existing printer queues.
555 C<$printers> is a reference-to-hash whose keys are the print queues
556 defined in the printers table of the Koha database. The values are
557 references-to-hash, whose keys are the fields in the printers table.
559 =cut
561 sub GetPrinters {
562 my %printers;
563 my $dbh = C4::Context->dbh;
564 my $sth = $dbh->prepare("select * from printers");
565 $sth->execute;
566 while ( my $printer = $sth->fetchrow_hashref ) {
567 $printers{ $printer->{'printqueue'} } = $printer;
569 return ( \%printers );
572 =head2 GetPrinter
574 $printer = GetPrinter( $query, $printers );
576 =cut
578 sub GetPrinter {
579 my ( $query, $printers ) = @_; # get printer for this query from printers
580 my $printer = $query->param('printer');
581 my %cookie = $query->cookie('userenv');
582 ($printer) || ( $printer = $cookie{'printer'} ) || ( $printer = '' );
583 ( $printers->{$printer} ) || ( $printer = ( keys %$printers )[0] );
584 return $printer;
587 =head2 getnbpages
589 Returns the number of pages to display in a pagination bar, given the number
590 of items and the number of items per page.
592 =cut
594 sub getnbpages {
595 my ( $nb_items, $nb_items_per_page ) = @_;
597 return int( ( $nb_items - 1 ) / $nb_items_per_page ) + 1;
600 =head2 getallthemes
602 (@themes) = &getallthemes('opac');
603 (@themes) = &getallthemes('intranet');
605 Returns an array of all available themes.
607 =cut
609 sub getallthemes {
610 my $type = shift;
611 my $htdocs;
612 my @themes;
613 if ( $type eq 'intranet' ) {
614 $htdocs = C4::Context->config('intrahtdocs');
616 else {
617 $htdocs = C4::Context->config('opachtdocs');
619 opendir D, "$htdocs";
620 my @dirlist = readdir D;
621 foreach my $directory (@dirlist) {
622 next if $directory eq 'lib';
623 -d "$htdocs/$directory/en" and push @themes, $directory;
625 return @themes;
628 sub getFacets {
629 my $facets;
630 if ( C4::Context->preference("marcflavour") eq "UNIMARC" ) {
631 $facets = [
633 idx => 'su-to',
634 label => 'Topics',
635 tags => [ qw/ 600ab 601ab 602a 604at 605a 606ax 610a / ],
636 sep => ' - ',
639 idx => 'su-geo',
640 label => 'Places',
641 tags => [ qw/ 607a / ],
642 sep => ' - ',
645 idx => 'su-ut',
646 label => 'Titles',
647 tags => [ qw/ 500a 501a 503a / ],
648 sep => ', ',
651 idx => 'au',
652 label => 'Authors',
653 tags => [ qw/ 700ab 701ab 702ab / ],
654 sep => C4::Context->preference("UNIMARCAuthorsFacetsSeparator"),
657 idx => 'se',
658 label => 'Series',
659 tags => [ qw/ 225a / ],
660 sep => ', ',
663 idx => 'location',
664 label => 'Location',
665 tags => [ qw/ 995e / ],
669 unless ( Koha::Libraries->search->count == 1 )
671 my $DisplayLibraryFacets = C4::Context->preference('DisplayLibraryFacets');
672 if ( $DisplayLibraryFacets eq 'both'
673 || $DisplayLibraryFacets eq 'holding' )
675 push(
676 @$facets,
678 idx => 'holdingbranch',
679 label => 'HoldingLibrary',
680 tags => [qw / 995c /],
685 if ( $DisplayLibraryFacets eq 'both'
686 || $DisplayLibraryFacets eq 'home' )
688 push(
689 @$facets,
691 idx => 'homebranch',
692 label => 'HomeLibrary',
693 tags => [qw / 995b /],
699 else {
700 $facets = [
702 idx => 'su-to',
703 label => 'Topics',
704 tags => [ qw/ 650a / ],
705 sep => '--',
708 # idx => 'su-na',
709 # label => 'People and Organizations',
710 # tags => [ qw/ 600a 610a 611a / ],
711 # sep => 'a',
712 # },
714 idx => 'su-geo',
715 label => 'Places',
716 tags => [ qw/ 651a / ],
717 sep => '--',
720 idx => 'su-ut',
721 label => 'Titles',
722 tags => [ qw/ 630a / ],
723 sep => '--',
726 idx => 'au',
727 label => 'Authors',
728 tags => [ qw/ 100a 110a 700a / ],
729 sep => ', ',
732 idx => 'se',
733 label => 'Series',
734 tags => [ qw/ 440a 490a / ],
735 sep => ', ',
738 idx => 'itype',
739 label => 'ItemTypes',
740 tags => [ qw/ 952y 942c / ],
741 sep => ', ',
744 idx => 'location',
745 label => 'Location',
746 tags => [ qw / 952c / ],
750 unless ( Koha::Libraries->search->count == 1 )
752 my $DisplayLibraryFacets = C4::Context->preference('DisplayLibraryFacets');
753 if ( $DisplayLibraryFacets eq 'both'
754 || $DisplayLibraryFacets eq 'holding' )
756 push(
757 @$facets,
759 idx => 'holdingbranch',
760 label => 'HoldingLibrary',
761 tags => [qw / 952b /],
766 if ( $DisplayLibraryFacets eq 'both'
767 || $DisplayLibraryFacets eq 'home' )
769 push(
770 @$facets,
772 idx => 'homebranch',
773 label => 'HomeLibrary',
774 tags => [qw / 952a /],
780 return $facets;
783 =head2 get_infos_of
785 Return a href where a key is associated to a href. You give a query,
786 the name of the key among the fields returned by the query. If you
787 also give as third argument the name of the value, the function
788 returns a href of scalar. The optional 4th argument is an arrayref of
789 items passed to the C<execute()> call. It is designed to bind
790 parameters to any placeholders in your SQL.
792 my $query = '
793 SELECT itemnumber,
794 notforloan,
795 barcode
796 FROM items
799 # generic href of any information on the item, href of href.
800 my $iteminfos_of = get_infos_of($query, 'itemnumber');
801 print $iteminfos_of->{$itemnumber}{barcode};
803 # specific information, href of scalar
804 my $barcode_of_item = get_infos_of($query, 'itemnumber', 'barcode');
805 print $barcode_of_item->{$itemnumber};
807 =cut
809 sub get_infos_of {
810 my ( $query, $key_name, $value_name, $bind_params ) = @_;
812 my $dbh = C4::Context->dbh;
814 my $sth = $dbh->prepare($query);
815 $sth->execute( @$bind_params );
817 my %infos_of;
818 while ( my $row = $sth->fetchrow_hashref ) {
819 if ( defined $value_name ) {
820 $infos_of{ $row->{$key_name} } = $row->{$value_name};
822 else {
823 $infos_of{ $row->{$key_name} } = $row;
826 $sth->finish;
828 return \%infos_of;
831 =head2 get_notforloan_label_of
833 my $notforloan_label_of = get_notforloan_label_of();
835 Each authorised value of notforloan (information available in items and
836 itemtypes) is link to a single label.
838 Returns a href where keys are authorised values and values are corresponding
839 labels.
841 foreach my $authorised_value (keys %{$notforloan_label_of}) {
842 printf(
843 "authorised_value: %s => %s\n",
844 $authorised_value,
845 $notforloan_label_of->{$authorised_value}
849 =cut
851 # FIXME - why not use GetAuthorisedValues ??
853 sub get_notforloan_label_of {
854 my $dbh = C4::Context->dbh;
856 my $query = '
857 SELECT authorised_value
858 FROM marc_subfield_structure
859 WHERE kohafield = \'items.notforloan\'
860 LIMIT 0, 1
862 my $sth = $dbh->prepare($query);
863 $sth->execute();
864 my ($statuscode) = $sth->fetchrow_array();
866 $query = '
867 SELECT lib,
868 authorised_value
869 FROM authorised_values
870 WHERE category = ?
872 $sth = $dbh->prepare($query);
873 $sth->execute($statuscode);
874 my %notforloan_label_of;
875 while ( my $row = $sth->fetchrow_hashref ) {
876 $notforloan_label_of{ $row->{authorised_value} } = $row->{lib};
878 $sth->finish;
880 return \%notforloan_label_of;
883 =head2 GetAuthValCode
885 $authvalcode = GetAuthValCode($kohafield,$frameworkcode);
887 =cut
889 sub GetAuthValCode {
890 my ($kohafield,$fwcode) = @_;
891 my $dbh = C4::Context->dbh;
892 $fwcode='' unless $fwcode;
893 my $sth = $dbh->prepare('select authorised_value from marc_subfield_structure where kohafield=? and frameworkcode=?');
894 $sth->execute($kohafield,$fwcode);
895 my ($authvalcode) = $sth->fetchrow_array;
896 return $authvalcode;
899 =head2 GetAuthValCodeFromField
901 $authvalcode = GetAuthValCodeFromField($field,$subfield,$frameworkcode);
903 C<$subfield> can be undefined
905 =cut
907 sub GetAuthValCodeFromField {
908 my ($field,$subfield,$fwcode) = @_;
909 my $dbh = C4::Context->dbh;
910 $fwcode='' unless $fwcode;
911 my $sth;
912 if (defined $subfield) {
913 $sth = $dbh->prepare('select authorised_value from marc_subfield_structure where tagfield=? and tagsubfield=? and frameworkcode=?');
914 $sth->execute($field,$subfield,$fwcode);
915 } else {
916 $sth = $dbh->prepare('select authorised_value from marc_tag_structure where tagfield=? and frameworkcode=?');
917 $sth->execute($field,$fwcode);
919 my ($authvalcode) = $sth->fetchrow_array;
920 return $authvalcode;
923 =head2 GetAuthorisedValues
925 $authvalues = GetAuthorisedValues([$category]);
927 This function returns all authorised values from the'authorised_value' table in a reference to array of hashrefs.
929 C<$category> returns authorised values for just one category (optional).
931 C<$opac> If set to a true value, displays OPAC descriptions rather than normal ones when they exist.
933 =cut
935 sub GetAuthorisedValues {
936 my ( $category, $opac ) = @_;
938 # Is this cached already?
939 $opac = $opac ? 1 : 0; # normalise to be safe
940 my $branch_limit =
941 C4::Context->userenv ? C4::Context->userenv->{"branch"} : "";
942 my $cache_key =
943 "AuthorisedValues-$category-$opac-$branch_limit";
944 my $cache = Koha::Caches->get_instance();
945 my $result = $cache->get_from_cache($cache_key);
946 return $result if $result;
948 my @results;
949 my $dbh = C4::Context->dbh;
950 my $query = qq{
951 SELECT DISTINCT av.*
952 FROM authorised_values av
954 $query .= qq{
955 LEFT JOIN authorised_values_branches ON ( id = av_id )
956 } if $branch_limit;
957 my @where_strings;
958 my @where_args;
959 if($category) {
960 push @where_strings, "category = ?";
961 push @where_args, $category;
963 if($branch_limit) {
964 push @where_strings, "( branchcode = ? OR branchcode IS NULL )";
965 push @where_args, $branch_limit;
967 if(@where_strings > 0) {
968 $query .= " WHERE " . join(" AND ", @where_strings);
970 $query .= ' ORDER BY category, ' . (
971 $opac ? 'COALESCE(lib_opac, lib)'
972 : 'lib, lib_opac'
975 my $sth = $dbh->prepare($query);
977 $sth->execute( @where_args );
978 while (my $data=$sth->fetchrow_hashref) {
979 if ($opac && $data->{lib_opac}) {
980 $data->{lib} = $data->{lib_opac};
982 push @results, $data;
984 $sth->finish;
986 $cache->set_in_cache( $cache_key, \@results, { expiry => 5 } );
987 return \@results;
990 =head2 GetAuthorisedValueCategories
992 $auth_categories = GetAuthorisedValueCategories();
994 Return an arrayref of all of the available authorised
995 value categories.
997 =cut
999 sub GetAuthorisedValueCategories {
1000 my $dbh = C4::Context->dbh;
1001 my $sth = $dbh->prepare("SELECT DISTINCT category FROM authorised_values ORDER BY category");
1002 $sth->execute;
1003 my @results;
1004 while (defined (my $category = $sth->fetchrow_array) ) {
1005 push @results, $category;
1007 return \@results;
1010 =head2 GetAuthorisedValueByCode
1012 $authorised_value = GetAuthorisedValueByCode( $category, $authvalcode, $opac );
1014 Return the lib attribute from authorised_values from the row identified
1015 by the passed category and code
1017 =cut
1019 sub GetAuthorisedValueByCode {
1020 my ( $category, $authvalcode, $opac ) = @_;
1022 my $field = $opac ? 'lib_opac' : 'lib';
1023 my $dbh = C4::Context->dbh;
1024 my $sth = $dbh->prepare("SELECT $field FROM authorised_values WHERE category=? AND authorised_value =?");
1025 $sth->execute( $category, $authvalcode );
1026 while ( my $data = $sth->fetchrow_hashref ) {
1027 return $data->{ $field };
1031 =head2 GetKohaAuthorisedValues
1033 Takes $kohafield, $fwcode as parameters.
1035 If $opac parameter is set to a true value, displays OPAC descriptions rather than normal ones when they exist.
1037 Returns hashref of Code => description
1039 Returns undef if no authorised value category is defined for the kohafield.
1041 =cut
1043 sub GetKohaAuthorisedValues {
1044 my ($kohafield,$fwcode,$opac) = @_;
1045 $fwcode='' unless $fwcode;
1046 my %values;
1047 my $dbh = C4::Context->dbh;
1048 my $avcode = GetAuthValCode($kohafield,$fwcode);
1049 if ($avcode) {
1050 my $sth = $dbh->prepare("select authorised_value, lib, lib_opac from authorised_values where category=? ");
1051 $sth->execute($avcode);
1052 while ( my ($val, $lib, $lib_opac) = $sth->fetchrow_array ) {
1053 $values{$val} = ($opac && $lib_opac) ? $lib_opac : $lib;
1055 return \%values;
1056 } else {
1057 return;
1061 =head2 GetKohaAuthorisedValuesFromField
1063 Takes $field, $subfield, $fwcode as parameters.
1065 If $opac parameter is set to a true value, displays OPAC descriptions rather than normal ones when they exist.
1066 $subfield can be undefined
1068 Returns hashref of Code => description
1070 Returns undef if no authorised value category is defined for the given field and subfield
1072 =cut
1074 sub GetKohaAuthorisedValuesFromField {
1075 my ($field, $subfield, $fwcode,$opac) = @_;
1076 $fwcode='' unless $fwcode;
1077 my %values;
1078 my $dbh = C4::Context->dbh;
1079 my $avcode = GetAuthValCodeFromField($field, $subfield, $fwcode);
1080 if ($avcode) {
1081 my $sth = $dbh->prepare("select authorised_value, lib, lib_opac from authorised_values where category=? ");
1082 $sth->execute($avcode);
1083 while ( my ($val, $lib, $lib_opac) = $sth->fetchrow_array ) {
1084 $values{$val} = ($opac && $lib_opac) ? $lib_opac : $lib;
1086 return \%values;
1087 } else {
1088 return;
1092 =head2 GetKohaAuthorisedValuesMapping
1094 Takes a hash as a parameter. The interface key indicates the
1095 description to use in the mapping.
1097 Returns hashref of:
1098 "{kohafield},{frameworkcode},{authorised_value}" => "{description}"
1099 for all the kohafields, frameworkcodes, and authorised values.
1101 Returns undef if nothing is found.
1103 =cut
1105 sub GetKohaAuthorisedValuesMapping {
1106 my ($parameter) = @_;
1107 my $interface = $parameter->{'interface'} // '';
1109 my $query_mapping = q{
1110 SELECT TA.kohafield,TA.authorised_value AS category,
1111 TA.frameworkcode,TB.authorised_value,
1112 IF(TB.lib_opac>'',TB.lib_opac,TB.lib) AS OPAC,
1113 TB.lib AS Intranet,TB.lib_opac
1114 FROM marc_subfield_structure AS TA JOIN
1115 authorised_values as TB ON
1116 TA.authorised_value=TB.category
1117 WHERE TA.kohafield>'' AND TA.authorised_value>'';
1119 my $dbh = C4::Context->dbh;
1120 my $sth = $dbh->prepare($query_mapping);
1121 $sth->execute();
1122 my $avmapping;
1123 if ($interface eq 'opac') {
1124 while (my $row = $sth->fetchrow_hashref) {
1125 $avmapping->{$row->{kohafield}.",".$row->{frameworkcode}.",".$row->{authorised_value}} = $row->{OPAC};
1128 else {
1129 while (my $row = $sth->fetchrow_hashref) {
1130 $avmapping->{$row->{kohafield}.",".$row->{frameworkcode}.",".$row->{authorised_value}} = $row->{Intranet};
1133 return $avmapping;
1136 =head2 xml_escape
1138 my $escaped_string = C4::Koha::xml_escape($string);
1140 Convert &, <, >, ', and " in a string to XML entities
1142 =cut
1144 sub xml_escape {
1145 my $str = shift;
1146 return '' unless defined $str;
1147 $str =~ s/&/&amp;/g;
1148 $str =~ s/</&lt;/g;
1149 $str =~ s/>/&gt;/g;
1150 $str =~ s/'/&apos;/g;
1151 $str =~ s/"/&quot;/g;
1152 return $str;
1155 =head2 display_marc_indicators
1157 my $display_form = C4::Koha::display_marc_indicators($field);
1159 C<$field> is a MARC::Field object
1161 Generate a display form of the indicators of a variable
1162 MARC field, replacing any blanks with '#'.
1164 =cut
1166 sub display_marc_indicators {
1167 my $field = shift;
1168 my $indicators = '';
1169 if ($field && $field->tag() >= 10) {
1170 $indicators = $field->indicator(1) . $field->indicator(2);
1171 $indicators =~ s/ /#/g;
1173 return $indicators;
1176 sub GetNormalizedUPC {
1177 my ($marcrecord,$marcflavour) = @_;
1179 return unless $marcrecord;
1180 if ($marcflavour eq 'UNIMARC') {
1181 my @fields = $marcrecord->field('072');
1182 foreach my $field (@fields) {
1183 my $upc = _normalize_match_point($field->subfield('a'));
1184 if ($upc) {
1185 return $upc;
1190 else { # assume marc21 if not unimarc
1191 my @fields = $marcrecord->field('024');
1192 foreach my $field (@fields) {
1193 my $indicator = $field->indicator(1);
1194 my $upc = _normalize_match_point($field->subfield('a'));
1195 if ($upc && $indicator == 1 ) {
1196 return $upc;
1202 # Normalizes and returns the first valid ISBN found in the record
1203 # ISBN13 are converted into ISBN10. This is required to get some book cover images.
1204 sub GetNormalizedISBN {
1205 my ($isbn,$marcrecord,$marcflavour) = @_;
1206 if ($isbn) {
1207 # Koha attempts to store multiple ISBNs in biblioitems.isbn, separated by " | "
1208 # anything after " | " should be removed, along with the delimiter
1209 ($isbn) = split(/\|/, $isbn );
1210 return _isbn_cleanup($isbn);
1213 return unless $marcrecord;
1215 if ($marcflavour eq 'UNIMARC') {
1216 my @fields = $marcrecord->field('010');
1217 foreach my $field (@fields) {
1218 my $isbn = $field->subfield('a');
1219 if ($isbn) {
1220 return _isbn_cleanup($isbn);
1224 else { # assume marc21 if not unimarc
1225 my @fields = $marcrecord->field('020');
1226 foreach my $field (@fields) {
1227 $isbn = $field->subfield('a');
1228 if ($isbn) {
1229 return _isbn_cleanup($isbn);
1235 sub GetNormalizedEAN {
1236 my ($marcrecord,$marcflavour) = @_;
1238 return unless $marcrecord;
1240 if ($marcflavour eq 'UNIMARC') {
1241 my @fields = $marcrecord->field('073');
1242 foreach my $field (@fields) {
1243 my $ean = _normalize_match_point($field->subfield('a'));
1244 if ( $ean ) {
1245 return $ean;
1249 else { # assume marc21 if not unimarc
1250 my @fields = $marcrecord->field('024');
1251 foreach my $field (@fields) {
1252 my $indicator = $field->indicator(1);
1253 my $ean = _normalize_match_point($field->subfield('a'));
1254 if ( $ean && $indicator == 3 ) {
1255 return $ean;
1261 sub GetNormalizedOCLCNumber {
1262 my ($marcrecord,$marcflavour) = @_;
1263 return unless $marcrecord;
1265 if ($marcflavour ne 'UNIMARC' ) {
1266 my @fields = $marcrecord->field('035');
1267 foreach my $field (@fields) {
1268 my $oclc = $field->subfield('a');
1269 if ($oclc =~ /OCoLC/) {
1270 $oclc =~ s/\(OCoLC\)//;
1271 return $oclc;
1274 } else {
1275 # TODO for UNIMARC
1277 return
1280 sub GetAuthvalueDropbox {
1281 my ( $authcat, $default ) = @_;
1282 my $branch_limit = C4::Context->userenv ? C4::Context->userenv->{"branch"} : "";
1283 my $dbh = C4::Context->dbh;
1285 my $query = qq{
1286 SELECT *
1287 FROM authorised_values
1289 $query .= qq{
1290 LEFT JOIN authorised_values_branches ON ( id = av_id )
1291 } if $branch_limit;
1292 $query .= qq{
1293 WHERE category = ?
1295 $query .= " AND ( branchcode = ? OR branchcode IS NULL )" if $branch_limit;
1296 $query .= " GROUP BY lib ORDER BY category, lib, lib_opac";
1297 my $sth = $dbh->prepare($query);
1298 $sth->execute( $authcat, $branch_limit ? $branch_limit : () );
1301 my $option_list = [];
1302 my @authorised_values = ( q{} );
1303 while (my $av = $sth->fetchrow_hashref) {
1304 push @{$option_list}, {
1305 value => $av->{authorised_value},
1306 label => $av->{lib},
1307 default => ($default eq $av->{authorised_value}),
1311 if ( @{$option_list} ) {
1312 return $option_list;
1314 return;
1318 =head2 GetDailyQuote($opts)
1320 Takes a hashref of options
1322 Currently supported options are:
1324 'id' An exact quote id
1325 'random' Select a random quote
1326 noop When no option is passed in, this sub will return the quote timestamped for the current day
1328 The function returns an anonymous hash following this format:
1331 'source' => 'source-of-quote',
1332 'timestamp' => 'timestamp-value',
1333 'text' => 'text-of-quote',
1334 'id' => 'quote-id'
1337 =cut
1339 # This is definitely a candidate for some sort of caching once we finally settle caching/persistence issues...
1340 # at least for default option
1342 sub GetDailyQuote {
1343 my %opts = @_;
1344 my $dbh = C4::Context->dbh;
1345 my $query = '';
1346 my $sth = undef;
1347 my $quote = undef;
1348 if ($opts{'id'}) {
1349 $query = 'SELECT * FROM quotes WHERE id = ?';
1350 $sth = $dbh->prepare($query);
1351 $sth->execute($opts{'id'});
1352 $quote = $sth->fetchrow_hashref();
1354 elsif ($opts{'random'}) {
1355 # Fall through... we also return a random quote as a catch-all if all else fails
1357 else {
1358 $query = 'SELECT * FROM quotes WHERE timestamp LIKE CONCAT(CURRENT_DATE,\'%\') ORDER BY timestamp DESC LIMIT 0,1';
1359 $sth = $dbh->prepare($query);
1360 $sth->execute();
1361 $quote = $sth->fetchrow_hashref();
1363 unless ($quote) { # if there are not matches, choose a random quote
1364 # get a list of all available quote ids
1365 $sth = C4::Context->dbh->prepare('SELECT count(*) FROM quotes;');
1366 $sth->execute;
1367 my $range = ($sth->fetchrow_array)[0];
1368 # chose a random id within that range if there is more than one quote
1369 my $offset = int(rand($range));
1370 # grab it
1371 $query = 'SELECT * FROM quotes ORDER BY id LIMIT 1 OFFSET ?';
1372 $sth = C4::Context->dbh->prepare($query);
1373 # see http://www.perlmonks.org/?node_id=837422 for why
1374 # we're being verbose and using bind_param
1375 $sth->bind_param(1, $offset, SQL_INTEGER);
1376 $sth->execute();
1377 $quote = $sth->fetchrow_hashref();
1378 # update the timestamp for that quote
1379 $query = 'UPDATE quotes SET timestamp = ? WHERE id = ?';
1380 $sth = C4::Context->dbh->prepare($query);
1381 $sth->execute(
1382 DateTime::Format::MySQL->format_datetime( dt_from_string() ),
1383 $quote->{'id'}
1386 return $quote;
1389 sub _normalize_match_point {
1390 my $match_point = shift;
1391 (my $normalized_match_point) = $match_point =~ /([\d-]*[X]*)/;
1392 $normalized_match_point =~ s/-//g;
1394 return $normalized_match_point;
1397 sub _isbn_cleanup {
1398 my ($isbn) = @_;
1399 return NormalizeISBN(
1401 isbn => $isbn,
1402 format => 'ISBN-10',
1403 strip_hyphens => 1,
1405 ) if $isbn;
1408 =head2 NormalizedISBN
1410 my $isbns = NormalizedISBN({
1411 isbn => $isbn,
1412 strip_hyphens => [0,1],
1413 format => ['ISBN-10', 'ISBN-13']
1416 Returns an isbn validated by Business::ISBN.
1417 Optionally strips hyphens and/or forces the isbn
1418 to be of the specified format.
1420 If the string cannot be validated as an isbn,
1421 it returns nothing.
1423 =cut
1425 sub NormalizeISBN {
1426 my ($params) = @_;
1428 my $string = $params->{isbn};
1429 my $strip_hyphens = $params->{strip_hyphens};
1430 my $format = $params->{format};
1432 return unless $string;
1434 my $isbn = Business::ISBN->new($string);
1436 if ( $isbn && $isbn->is_valid() ) {
1438 if ( $format eq 'ISBN-10' ) {
1439 $isbn = $isbn->as_isbn10();
1441 elsif ( $format eq 'ISBN-13' ) {
1442 $isbn = $isbn->as_isbn13();
1444 return unless $isbn;
1446 if ($strip_hyphens) {
1447 $string = $isbn->as_string( [] );
1448 } else {
1449 $string = $isbn->as_string();
1452 return $string;
1456 =head2 GetVariationsOfISBN
1458 my @isbns = GetVariationsOfISBN( $isbn );
1460 Returns a list of variations of the given isbn in
1461 both ISBN-10 and ISBN-13 formats, with and without
1462 hyphens.
1464 In a scalar context, the isbns are returned as a
1465 string delimited by ' | '.
1467 =cut
1469 sub GetVariationsOfISBN {
1470 my ($isbn) = @_;
1472 return unless $isbn;
1474 my @isbns;
1476 push( @isbns, NormalizeISBN({ isbn => $isbn }) );
1477 push( @isbns, NormalizeISBN({ isbn => $isbn, format => 'ISBN-10' }) );
1478 push( @isbns, NormalizeISBN({ isbn => $isbn, format => 'ISBN-13' }) );
1479 push( @isbns, NormalizeISBN({ isbn => $isbn, format => 'ISBN-10', strip_hyphens => 1 }) );
1480 push( @isbns, NormalizeISBN({ isbn => $isbn, format => 'ISBN-13', strip_hyphens => 1 }) );
1482 # Strip out any "empty" strings from the array
1483 @isbns = grep { defined($_) && $_ =~ /\S/ } @isbns;
1485 return wantarray ? @isbns : join( " | ", @isbns );
1488 =head2 GetVariationsOfISBNs
1490 my @isbns = GetVariationsOfISBNs( @isbns );
1492 Returns a list of variations of the given isbns in
1493 both ISBN-10 and ISBN-13 formats, with and without
1494 hyphens.
1496 In a scalar context, the isbns are returned as a
1497 string delimited by ' | '.
1499 =cut
1501 sub GetVariationsOfISBNs {
1502 my (@isbns) = @_;
1504 @isbns = map { GetVariationsOfISBN( $_ ) } @isbns;
1506 return wantarray ? @isbns : join( " | ", @isbns );
1509 =head2 IsKohaFieldLinked
1511 my $is_linked = IsKohaFieldLinked({
1512 kohafield => $kohafield,
1513 frameworkcode => $frameworkcode,
1516 Return 1 if the field is linked
1518 =cut
1520 sub IsKohaFieldLinked {
1521 my ( $params ) = @_;
1522 my $kohafield = $params->{kohafield};
1523 my $frameworkcode = $params->{frameworkcode} || '';
1524 my $dbh = C4::Context->dbh;
1525 my $is_linked = $dbh->selectcol_arrayref( q|
1526 SELECT COUNT(*)
1527 FROM marc_subfield_structure
1528 WHERE frameworkcode = ?
1529 AND kohafield = ?
1530 |,{}, $frameworkcode, $kohafield );
1531 return $is_linked->[0];
1536 __END__
1538 =head1 AUTHOR
1540 Koha Team
1542 =cut