Bug 17310: Broken URLs in 'Item renewed' / 'Cannot renew' messages
[koha.git] / C4 / Koha.pm
blob0ff4851ec2f7c051fbafc49272649550c71285b3
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 &GetKohaAuthorisedValueLib
59 &GetAuthorisedValueByCode
60 &GetAuthValCode
61 &GetNormalizedUPC
62 &GetNormalizedISBN
63 &GetNormalizedEAN
64 &GetNormalizedOCLCNumber
65 &xml_escape
67 &GetVariationsOfISBN
68 &GetVariationsOfISBNs
69 &NormalizeISBN
71 $DEBUG
73 $DEBUG = 0;
74 @EXPORT_OK = qw( GetDailyQuote );
77 =head1 NAME
79 C4::Koha - Perl Module containing convenience functions for Koha scripts
81 =head1 SYNOPSIS
83 use C4::Koha;
85 =head1 DESCRIPTION
87 Koha.pm provides many functions for Koha scripts.
89 =head1 FUNCTIONS
91 =cut
93 =head2 GetItemTypes
95 $itemtypes = &GetItemTypes( style => $style );
97 Returns information about existing itemtypes.
99 Params:
100 style: either 'array' or 'hash', defaults to 'hash'.
101 'array' returns an arrayref,
102 'hash' return a hashref with the itemtype value as the key
104 build a HTML select with the following code :
106 =head3 in PERL SCRIPT
108 my $itemtypes = GetItemTypes;
109 my @itemtypesloop;
110 foreach my $thisitemtype (sort keys %$itemtypes) {
111 my $selected = 1 if $thisitemtype eq $itemtype;
112 my %row =(value => $thisitemtype,
113 selected => $selected,
114 description => $itemtypes->{$thisitemtype}->{'description'},
116 push @itemtypesloop, \%row;
118 $template->param(itemtypeloop => \@itemtypesloop);
120 =head3 in TEMPLATE
122 <form action='<!-- TMPL_VAR name="script_name" -->' method=post>
123 <select name="itemtype">
124 <option value="">Default</option>
125 <!-- TMPL_LOOP name="itemtypeloop" -->
126 <option value="<!-- TMPL_VAR name="value" -->" <!-- TMPL_IF name="selected" -->selected<!-- /TMPL_IF -->><!-- TMPL_VAR name="description" --></option>
127 <!-- /TMPL_LOOP -->
128 </select>
129 <input type=text name=searchfield value="<!-- TMPL_VAR name="searchfield" -->">
130 <input type="submit" value="OK" class="button">
131 </form>
133 =cut
135 sub GetItemTypes {
136 my ( %params ) = @_;
137 my $style = defined( $params{'style'} ) ? $params{'style'} : 'hash';
139 require C4::Languages;
140 my $language = C4::Languages::getlanguage();
141 # returns a reference to a hash of references to itemtypes...
142 my $dbh = C4::Context->dbh;
143 my $query = q|
144 SELECT
145 itemtypes.itemtype,
146 itemtypes.description,
147 itemtypes.rentalcharge,
148 itemtypes.notforloan,
149 itemtypes.imageurl,
150 itemtypes.summary,
151 itemtypes.checkinmsg,
152 itemtypes.checkinmsgtype,
153 itemtypes.sip_media_type,
154 itemtypes.hideinopac,
155 itemtypes.searchcategory,
156 COALESCE( localization.translation, itemtypes.description ) AS translated_description
157 FROM itemtypes
158 LEFT JOIN localization ON itemtypes.itemtype = localization.code
159 AND localization.entity = 'itemtypes'
160 AND localization.lang = ?
161 ORDER BY itemtype
163 my $sth = $dbh->prepare($query);
164 $sth->execute( $language );
166 if ( $style eq 'hash' ) {
167 my %itemtypes;
168 while ( my $IT = $sth->fetchrow_hashref ) {
169 $itemtypes{ $IT->{'itemtype'} } = $IT;
171 return ( \%itemtypes );
172 } else {
173 return [ sort { lc $a->{translated_description} cmp lc $b->{translated_description} } @{ $sth->fetchall_arrayref( {} ) } ];
177 =head2 GetItemTypesCategorized
179 $categories = GetItemTypesCategorized();
181 Returns a hashref containing search categories.
182 A search category will be put in the hash if at least one of its itemtypes is visible in OPAC.
183 The categories must be part of Authorized Values (ITEMTYPECAT)
185 =cut
187 sub GetItemTypesCategorized {
188 my $dbh = C4::Context->dbh;
189 # Order is important, so that partially hidden (some items are not visible in OPAC) search
190 # categories will be visible. hideinopac=0 must be last.
191 my $query = q|
192 SELECT itemtype, description, imageurl, hideinopac, 0 as 'iscat' FROM itemtypes WHERE ISNULL(searchcategory) or length(searchcategory) = 0
193 UNION
194 SELECT DISTINCT searchcategory AS `itemtype`,
195 authorised_values.lib_opac AS description,
196 authorised_values.imageurl AS imageurl,
197 hideinopac, 1 as 'iscat'
198 FROM itemtypes
199 LEFT JOIN authorised_values ON searchcategory = authorised_value
200 WHERE searchcategory > '' and hideinopac=1
201 UNION
202 SELECT DISTINCT searchcategory AS `itemtype`,
203 authorised_values.lib_opac AS description,
204 authorised_values.imageurl AS imageurl,
205 hideinopac, 1 as 'iscat'
206 FROM itemtypes
207 LEFT JOIN authorised_values ON searchcategory = authorised_value
208 WHERE searchcategory > '' and hideinopac=0
210 return ($dbh->selectall_hashref($query,'itemtype'));
213 =head2 GetItemTypesByCategory
215 @results = GetItemTypesByCategory( $searchcategory );
217 Returns the itemtype code of all itemtypes included in a searchcategory.
219 =cut
221 sub GetItemTypesByCategory {
222 my ($category) = @_;
223 my $count = 0;
224 my @results;
225 my $dbh = C4::Context->dbh;
226 my $query = qq|SELECT itemtype FROM itemtypes WHERE searchcategory=?|;
227 my $tmp=$dbh->selectcol_arrayref($query,undef,$category);
228 return @$tmp;
231 =head2 getframework
233 $frameworks = &getframework();
235 Returns information about existing frameworks
237 build a HTML select with the following code :
239 =head3 in PERL SCRIPT
241 my $frameworks = getframeworks();
242 my @frameworkloop;
243 foreach my $thisframework (keys %$frameworks) {
244 my $selected = 1 if $thisframework eq $frameworkcode;
245 my %row =(
246 value => $thisframework,
247 selected => $selected,
248 description => $frameworks->{$thisframework}->{'frameworktext'},
250 push @frameworksloop, \%row;
252 $template->param(frameworkloop => \@frameworksloop);
254 =head3 in TEMPLATE
256 <form action="[% script_name %] method=post>
257 <select name="frameworkcode">
258 <option value="">Default</option>
259 [% FOREACH framework IN frameworkloop %]
260 [% IF ( framework.selected ) %]
261 <option value="[% framework.value %]" selected="selected">[% framework.description %]</option>
262 [% ELSE %]
263 <option value="[% framework.value %]">[% framework.description %]</option>
264 [% END %]
265 [% END %]
266 </select>
267 <input type=text name=searchfield value="[% searchfield %]">
268 <input type="submit" value="OK" class="button">
269 </form>
271 =cut
273 sub getframeworks {
275 # returns a reference to a hash of references to branches...
276 my %itemtypes;
277 my $dbh = C4::Context->dbh;
278 my $sth = $dbh->prepare("select * from biblio_framework");
279 $sth->execute;
280 while ( my $IT = $sth->fetchrow_hashref ) {
281 $itemtypes{ $IT->{'frameworkcode'} } = $IT;
283 return ( \%itemtypes );
286 =head2 GetFrameworksLoop
288 $frameworks = GetFrameworksLoop( $frameworkcode );
290 Returns the loop suggested on getframework(), but ordered by framework description.
292 build a HTML select with the following code :
294 =head3 in PERL SCRIPT
296 $template->param( frameworkloop => GetFrameworksLoop( $frameworkcode ) );
298 =head3 in TEMPLATE
300 Same as getframework()
302 <form action="[% script_name %] method=post>
303 <select name="frameworkcode">
304 <option value="">Default</option>
305 [% FOREACH framework IN frameworkloop %]
306 [% IF ( framework.selected ) %]
307 <option value="[% framework.value %]" selected="selected">[% framework.description %]</option>
308 [% ELSE %]
309 <option value="[% framework.value %]">[% framework.description %]</option>
310 [% END %]
311 [% END %]
312 </select>
313 <input type=text name=searchfield value="[% searchfield %]">
314 <input type="submit" value="OK" class="button">
315 </form>
317 =cut
319 sub GetFrameworksLoop {
320 my $frameworkcode = shift;
321 my $frameworks = getframeworks();
322 my @frameworkloop;
323 foreach my $thisframework (sort { uc($frameworks->{$a}->{'frameworktext'}) cmp uc($frameworks->{$b}->{'frameworktext'}) } keys %$frameworks) {
324 my $selected = ( $thisframework eq $frameworkcode ) ? 1 : undef;
325 my %row = (
326 value => $thisframework,
327 selected => $selected,
328 description => $frameworks->{$thisframework}->{'frameworktext'},
330 push @frameworkloop, \%row;
332 return \@frameworkloop;
335 =head2 getframeworkinfo
337 $frameworkinfo = &getframeworkinfo($frameworkcode);
339 Returns information about an frameworkcode.
341 =cut
343 sub getframeworkinfo {
344 my ($frameworkcode) = @_;
345 my $dbh = C4::Context->dbh;
346 my $sth =
347 $dbh->prepare("select * from biblio_framework where frameworkcode=?");
348 $sth->execute($frameworkcode);
349 my $res = $sth->fetchrow_hashref;
350 return $res;
353 =head2 getitemtypeinfo
355 $itemtype = &getitemtypeinfo($itemtype, [$interface]);
357 Returns information about an itemtype. The optional $interface argument
358 sets which interface ('opac' or 'intranet') to return the imageurl for.
359 Defaults to intranet.
361 =cut
363 sub getitemtypeinfo {
364 my ($itemtype, $interface) = @_;
365 my $dbh = C4::Context->dbh;
366 require C4::Languages;
367 my $language = C4::Languages::getlanguage();
368 my $it = $dbh->selectrow_hashref(q|
369 SELECT
370 itemtypes.itemtype,
371 itemtypes.description,
372 itemtypes.rentalcharge,
373 itemtypes.notforloan,
374 itemtypes.imageurl,
375 itemtypes.summary,
376 itemtypes.checkinmsg,
377 itemtypes.checkinmsgtype,
378 itemtypes.sip_media_type,
379 COALESCE( localization.translation, itemtypes.description ) AS translated_description
380 FROM itemtypes
381 LEFT JOIN localization ON itemtypes.itemtype = localization.code
382 AND localization.entity = 'itemtypes'
383 AND localization.lang = ?
384 WHERE itemtypes.itemtype = ?
385 |, undef, $language, $itemtype );
387 $it->{imageurl} = getitemtypeimagelocation( ( ( defined $interface && $interface eq 'opac' ) ? 'opac' : 'intranet' ), $it->{imageurl} );
389 return $it;
392 =head2 getitemtypeimagedir
394 my $directory = getitemtypeimagedir( 'opac' );
396 pass in 'opac' or 'intranet'. Defaults to 'opac'.
398 returns the full path to the appropriate directory containing images.
400 =cut
402 sub getitemtypeimagedir {
403 my $src = shift || 'opac';
404 if ($src eq 'intranet') {
405 return C4::Context->config('intrahtdocs') . '/' .C4::Context->preference('template') . '/img/itemtypeimg';
406 } else {
407 return C4::Context->config('opachtdocs') . '/' . C4::Context->preference('opacthemes') . '/itemtypeimg';
411 sub getitemtypeimagesrc {
412 my $src = shift || 'opac';
413 if ($src eq 'intranet') {
414 return '/intranet-tmpl' . '/' . C4::Context->preference('template') . '/img/itemtypeimg';
415 } else {
416 return '/opac-tmpl' . '/' . C4::Context->preference('opacthemes') . '/itemtypeimg';
420 sub getitemtypeimagelocation {
421 my ( $src, $image ) = @_;
423 return '' if ( !$image );
424 require URI::Split;
426 my $scheme = ( URI::Split::uri_split( $image ) )[0];
428 return $image if ( $scheme );
430 return getitemtypeimagesrc( $src ) . '/' . $image;
433 =head3 _getImagesFromDirectory
435 Find all of the image files in a directory in the filesystem
437 parameters: a directory name
439 returns: a list of images in that directory.
441 Notes: this does not traverse into subdirectories. See
442 _getSubdirectoryNames for help with that.
443 Images are assumed to be files with .gif or .png file extensions.
444 The image names returned do not have the directory name on them.
446 =cut
448 sub _getImagesFromDirectory {
449 my $directoryname = shift;
450 return unless defined $directoryname;
451 return unless -d $directoryname;
453 if ( opendir ( my $dh, $directoryname ) ) {
454 my @images = grep { /\.(gif|png)$/i } readdir( $dh );
455 closedir $dh;
456 @images = sort(@images);
457 return @images;
458 } else {
459 warn "unable to opendir $directoryname: $!";
460 return;
464 =head3 _getSubdirectoryNames
466 Find all of the directories in a directory in the filesystem
468 parameters: a directory name
470 returns: a list of subdirectories in that directory.
472 Notes: this does not traverse into subdirectories. Only the first
473 level of subdirectories are returned.
474 The directory names returned don't have the parent directory name on them.
476 =cut
478 sub _getSubdirectoryNames {
479 my $directoryname = shift;
480 return unless defined $directoryname;
481 return unless -d $directoryname;
483 if ( opendir ( my $dh, $directoryname ) ) {
484 my @directories = grep { -d File::Spec->catfile( $directoryname, $_ ) && ! ( /^\./ ) } readdir( $dh );
485 closedir $dh;
486 return @directories;
487 } else {
488 warn "unable to opendir $directoryname: $!";
489 return;
493 =head3 getImageSets
495 returns: a listref of hashrefs. Each hash represents another collection of images.
497 { imagesetname => 'npl', # the name of the image set (npl is the original one)
498 images => listref of image hashrefs
501 each image is represented by a hashref like this:
503 { KohaImage => 'npl/image.gif',
504 StaffImageUrl => '/intranet-tmpl/prog/img/itemtypeimg/npl/image.gif',
505 OpacImageURL => '/opac-tmpl/prog/itemtypeimg/npl/image.gif'
506 checked => 0 or 1: was this the image passed to this method?
507 Note: I'd like to remove this somehow.
510 =cut
512 sub getImageSets {
513 my %params = @_;
514 my $checked = $params{'checked'} || '';
516 my $paths = { staff => { filesystem => getitemtypeimagedir('intranet'),
517 url => getitemtypeimagesrc('intranet'),
519 opac => { filesystem => getitemtypeimagedir('opac'),
520 url => getitemtypeimagesrc('opac'),
524 my @imagesets = (); # list of hasrefs of image set data to pass to template
525 my @subdirectories = _getSubdirectoryNames( $paths->{'staff'}{'filesystem'} );
526 foreach my $imagesubdir ( @subdirectories ) {
527 warn $imagesubdir if $DEBUG;
528 my @imagelist = (); # hashrefs of image info
529 my @imagenames = _getImagesFromDirectory( File::Spec->catfile( $paths->{'staff'}{'filesystem'}, $imagesubdir ) );
530 my $imagesetactive = 0;
531 foreach my $thisimage ( @imagenames ) {
532 push( @imagelist,
533 { KohaImage => "$imagesubdir/$thisimage",
534 StaffImageUrl => join( '/', $paths->{'staff'}{'url'}, $imagesubdir, $thisimage ),
535 OpacImageUrl => join( '/', $paths->{'opac'}{'url'}, $imagesubdir, $thisimage ),
536 checked => "$imagesubdir/$thisimage" eq $checked ? 1 : 0,
539 $imagesetactive = 1 if "$imagesubdir/$thisimage" eq $checked;
541 push @imagesets, { imagesetname => $imagesubdir,
542 imagesetactive => $imagesetactive,
543 images => \@imagelist };
546 return \@imagesets;
549 =head2 GetPrinters
551 $printers = &GetPrinters();
552 @queues = keys %$printers;
554 Returns information about existing printer queues.
556 C<$printers> is a reference-to-hash whose keys are the print queues
557 defined in the printers table of the Koha database. The values are
558 references-to-hash, whose keys are the fields in the printers table.
560 =cut
562 sub GetPrinters {
563 my %printers;
564 my $dbh = C4::Context->dbh;
565 my $sth = $dbh->prepare("select * from printers");
566 $sth->execute;
567 while ( my $printer = $sth->fetchrow_hashref ) {
568 $printers{ $printer->{'printqueue'} } = $printer;
570 return ( \%printers );
573 =head2 GetPrinter
575 $printer = GetPrinter( $query, $printers );
577 =cut
579 sub GetPrinter {
580 my ( $query, $printers ) = @_; # get printer for this query from printers
581 my $printer = $query->param('printer');
582 my %cookie = $query->cookie('userenv');
583 ($printer) || ( $printer = $cookie{'printer'} ) || ( $printer = '' );
584 ( $printers->{$printer} ) || ( $printer = ( keys %$printers )[0] );
585 return $printer;
588 =head2 getnbpages
590 Returns the number of pages to display in a pagination bar, given the number
591 of items and the number of items per page.
593 =cut
595 sub getnbpages {
596 my ( $nb_items, $nb_items_per_page ) = @_;
598 return int( ( $nb_items - 1 ) / $nb_items_per_page ) + 1;
601 =head2 getallthemes
603 (@themes) = &getallthemes('opac');
604 (@themes) = &getallthemes('intranet');
606 Returns an array of all available themes.
608 =cut
610 sub getallthemes {
611 my $type = shift;
612 my $htdocs;
613 my @themes;
614 if ( $type eq 'intranet' ) {
615 $htdocs = C4::Context->config('intrahtdocs');
617 else {
618 $htdocs = C4::Context->config('opachtdocs');
620 opendir D, "$htdocs";
621 my @dirlist = readdir D;
622 foreach my $directory (@dirlist) {
623 next if $directory eq 'lib';
624 -d "$htdocs/$directory/en" and push @themes, $directory;
626 return @themes;
629 sub getFacets {
630 my $facets;
631 if ( C4::Context->preference("marcflavour") eq "UNIMARC" ) {
632 $facets = [
634 idx => 'su-to',
635 label => 'Topics',
636 tags => [ qw/ 600ab 601ab 602a 604at 605a 606ax 610a / ],
637 sep => ' - ',
640 idx => 'su-geo',
641 label => 'Places',
642 tags => [ qw/ 607a / ],
643 sep => ' - ',
646 idx => 'su-ut',
647 label => 'Titles',
648 tags => [ qw/ 500a 501a 503a / ],
649 sep => ', ',
652 idx => 'au',
653 label => 'Authors',
654 tags => [ qw/ 700ab 701ab 702ab / ],
655 sep => C4::Context->preference("UNIMARCAuthorsFacetsSeparator"),
658 idx => 'se',
659 label => 'Series',
660 tags => [ qw/ 225a / ],
661 sep => ', ',
664 idx => 'location',
665 label => 'Location',
666 tags => [ qw/ 995e / ],
670 unless ( Koha::Libraries->search->count == 1 )
672 my $DisplayLibraryFacets = C4::Context->preference('DisplayLibraryFacets');
673 if ( $DisplayLibraryFacets eq 'both'
674 || $DisplayLibraryFacets eq 'holding' )
676 push(
677 @$facets,
679 idx => 'holdingbranch',
680 label => 'HoldingLibrary',
681 tags => [qw / 995c /],
686 if ( $DisplayLibraryFacets eq 'both'
687 || $DisplayLibraryFacets eq 'home' )
689 push(
690 @$facets,
692 idx => 'homebranch',
693 label => 'HomeLibrary',
694 tags => [qw / 995b /],
700 else {
701 $facets = [
703 idx => 'su-to',
704 label => 'Topics',
705 tags => [ qw/ 650a / ],
706 sep => '--',
709 # idx => 'su-na',
710 # label => 'People and Organizations',
711 # tags => [ qw/ 600a 610a 611a / ],
712 # sep => 'a',
713 # },
715 idx => 'su-geo',
716 label => 'Places',
717 tags => [ qw/ 651a / ],
718 sep => '--',
721 idx => 'su-ut',
722 label => 'Titles',
723 tags => [ qw/ 630a / ],
724 sep => '--',
727 idx => 'au',
728 label => 'Authors',
729 tags => [ qw/ 100a 110a 700a / ],
730 sep => ', ',
733 idx => 'se',
734 label => 'Series',
735 tags => [ qw/ 440a 490a / ],
736 sep => ', ',
739 idx => 'itype',
740 label => 'ItemTypes',
741 tags => [ qw/ 952y 942c / ],
742 sep => ', ',
745 idx => 'location',
746 label => 'Location',
747 tags => [ qw / 952c / ],
751 unless ( Koha::Libraries->search->count == 1 )
753 my $DisplayLibraryFacets = C4::Context->preference('DisplayLibraryFacets');
754 if ( $DisplayLibraryFacets eq 'both'
755 || $DisplayLibraryFacets eq 'holding' )
757 push(
758 @$facets,
760 idx => 'holdingbranch',
761 label => 'HoldingLibrary',
762 tags => [qw / 952b /],
767 if ( $DisplayLibraryFacets eq 'both'
768 || $DisplayLibraryFacets eq 'home' )
770 push(
771 @$facets,
773 idx => 'homebranch',
774 label => 'HomeLibrary',
775 tags => [qw / 952a /],
781 return $facets;
784 =head2 get_infos_of
786 Return a href where a key is associated to a href. You give a query,
787 the name of the key among the fields returned by the query. If you
788 also give as third argument the name of the value, the function
789 returns a href of scalar. The optional 4th argument is an arrayref of
790 items passed to the C<execute()> call. It is designed to bind
791 parameters to any placeholders in your SQL.
793 my $query = '
794 SELECT itemnumber,
795 notforloan,
796 barcode
797 FROM items
800 # generic href of any information on the item, href of href.
801 my $iteminfos_of = get_infos_of($query, 'itemnumber');
802 print $iteminfos_of->{$itemnumber}{barcode};
804 # specific information, href of scalar
805 my $barcode_of_item = get_infos_of($query, 'itemnumber', 'barcode');
806 print $barcode_of_item->{$itemnumber};
808 =cut
810 sub get_infos_of {
811 my ( $query, $key_name, $value_name, $bind_params ) = @_;
813 my $dbh = C4::Context->dbh;
815 my $sth = $dbh->prepare($query);
816 $sth->execute( @$bind_params );
818 my %infos_of;
819 while ( my $row = $sth->fetchrow_hashref ) {
820 if ( defined $value_name ) {
821 $infos_of{ $row->{$key_name} } = $row->{$value_name};
823 else {
824 $infos_of{ $row->{$key_name} } = $row;
827 $sth->finish;
829 return \%infos_of;
832 =head2 get_notforloan_label_of
834 my $notforloan_label_of = get_notforloan_label_of();
836 Each authorised value of notforloan (information available in items and
837 itemtypes) is link to a single label.
839 Returns a href where keys are authorised values and values are corresponding
840 labels.
842 foreach my $authorised_value (keys %{$notforloan_label_of}) {
843 printf(
844 "authorised_value: %s => %s\n",
845 $authorised_value,
846 $notforloan_label_of->{$authorised_value}
850 =cut
852 # FIXME - why not use GetAuthorisedValues ??
854 sub get_notforloan_label_of {
855 my $dbh = C4::Context->dbh;
857 my $query = '
858 SELECT authorised_value
859 FROM marc_subfield_structure
860 WHERE kohafield = \'items.notforloan\'
861 LIMIT 0, 1
863 my $sth = $dbh->prepare($query);
864 $sth->execute();
865 my ($statuscode) = $sth->fetchrow_array();
867 $query = '
868 SELECT lib,
869 authorised_value
870 FROM authorised_values
871 WHERE category = ?
873 $sth = $dbh->prepare($query);
874 $sth->execute($statuscode);
875 my %notforloan_label_of;
876 while ( my $row = $sth->fetchrow_hashref ) {
877 $notforloan_label_of{ $row->{authorised_value} } = $row->{lib};
879 $sth->finish;
881 return \%notforloan_label_of;
884 =head2 GetAuthValCode
886 $authvalcode = GetAuthValCode($kohafield,$frameworkcode);
888 =cut
890 sub GetAuthValCode {
891 my ($kohafield,$fwcode) = @_;
892 my $dbh = C4::Context->dbh;
893 $fwcode='' unless $fwcode;
894 my $sth = $dbh->prepare('select authorised_value from marc_subfield_structure where kohafield=? and frameworkcode=?');
895 $sth->execute($kohafield,$fwcode);
896 my ($authvalcode) = $sth->fetchrow_array;
897 return $authvalcode;
900 =head2 GetAuthValCodeFromField
902 $authvalcode = GetAuthValCodeFromField($field,$subfield,$frameworkcode);
904 C<$subfield> can be undefined
906 =cut
908 sub GetAuthValCodeFromField {
909 my ($field,$subfield,$fwcode) = @_;
910 my $dbh = C4::Context->dbh;
911 $fwcode='' unless $fwcode;
912 my $sth;
913 if (defined $subfield) {
914 $sth = $dbh->prepare('select authorised_value from marc_subfield_structure where tagfield=? and tagsubfield=? and frameworkcode=?');
915 $sth->execute($field,$subfield,$fwcode);
916 } else {
917 $sth = $dbh->prepare('select authorised_value from marc_tag_structure where tagfield=? and frameworkcode=?');
918 $sth->execute($field,$fwcode);
920 my ($authvalcode) = $sth->fetchrow_array;
921 return $authvalcode;
924 =head2 GetAuthorisedValues
926 $authvalues = GetAuthorisedValues([$category]);
928 This function returns all authorised values from the'authorised_value' table in a reference to array of hashrefs.
930 C<$category> returns authorised values for just one category (optional).
932 C<$opac> If set to a true value, displays OPAC descriptions rather than normal ones when they exist.
934 =cut
936 sub GetAuthorisedValues {
937 my ( $category, $opac ) = @_;
939 # Is this cached already?
940 $opac = $opac ? 1 : 0; # normalise to be safe
941 my $branch_limit =
942 C4::Context->userenv ? C4::Context->userenv->{"branch"} : "";
943 my $cache_key =
944 "AuthorisedValues-$category-$opac-$branch_limit";
945 my $cache = Koha::Caches->get_instance();
946 my $result = $cache->get_from_cache($cache_key);
947 return $result if $result;
949 my @results;
950 my $dbh = C4::Context->dbh;
951 my $query = qq{
952 SELECT DISTINCT av.*
953 FROM authorised_values av
955 $query .= qq{
956 LEFT JOIN authorised_values_branches ON ( id = av_id )
957 } if $branch_limit;
958 my @where_strings;
959 my @where_args;
960 if($category) {
961 push @where_strings, "category = ?";
962 push @where_args, $category;
964 if($branch_limit) {
965 push @where_strings, "( branchcode = ? OR branchcode IS NULL )";
966 push @where_args, $branch_limit;
968 if(@where_strings > 0) {
969 $query .= " WHERE " . join(" AND ", @where_strings);
971 $query .= ' ORDER BY category, ' . (
972 $opac ? 'COALESCE(lib_opac, lib)'
973 : 'lib, lib_opac'
976 my $sth = $dbh->prepare($query);
978 $sth->execute( @where_args );
979 while (my $data=$sth->fetchrow_hashref) {
980 if ($opac && $data->{lib_opac}) {
981 $data->{lib} = $data->{lib_opac};
983 push @results, $data;
985 $sth->finish;
987 $cache->set_in_cache( $cache_key, \@results, { deepcopy => 1, expiry => 5 } );
988 return \@results;
991 =head2 GetAuthorisedValueCategories
993 $auth_categories = GetAuthorisedValueCategories();
995 Return an arrayref of all of the available authorised
996 value categories.
998 =cut
1000 sub GetAuthorisedValueCategories {
1001 my $dbh = C4::Context->dbh;
1002 my $sth = $dbh->prepare("SELECT DISTINCT category FROM authorised_values ORDER BY category");
1003 $sth->execute;
1004 my @results;
1005 while (defined (my $category = $sth->fetchrow_array) ) {
1006 push @results, $category;
1008 return \@results;
1011 =head2 GetAuthorisedValueByCode
1013 $authorised_value = GetAuthorisedValueByCode( $category, $authvalcode, $opac );
1015 Return the lib attribute from authorised_values from the row identified
1016 by the passed category and code
1018 =cut
1020 sub GetAuthorisedValueByCode {
1021 my ( $category, $authvalcode, $opac ) = @_;
1023 my $field = $opac ? 'lib_opac' : 'lib';
1024 my $dbh = C4::Context->dbh;
1025 my $sth = $dbh->prepare("SELECT $field FROM authorised_values WHERE category=? AND authorised_value =?");
1026 $sth->execute( $category, $authvalcode );
1027 while ( my $data = $sth->fetchrow_hashref ) {
1028 return $data->{ $field };
1032 =head2 GetKohaAuthorisedValues
1034 Takes $kohafield, $fwcode as parameters.
1036 If $opac parameter is set to a true value, displays OPAC descriptions rather than normal ones when they exist.
1038 Returns hashref of Code => description
1040 Returns undef if no authorised value category is defined for the kohafield.
1042 =cut
1044 sub GetKohaAuthorisedValues {
1045 my ($kohafield,$fwcode,$opac) = @_;
1046 $fwcode='' unless $fwcode;
1047 my %values;
1048 my $dbh = C4::Context->dbh;
1049 my $avcode = GetAuthValCode($kohafield,$fwcode);
1050 if ($avcode) {
1051 my $sth = $dbh->prepare("select authorised_value, lib, lib_opac from authorised_values where category=? ");
1052 $sth->execute($avcode);
1053 while ( my ($val, $lib, $lib_opac) = $sth->fetchrow_array ) {
1054 $values{$val} = ($opac && $lib_opac) ? $lib_opac : $lib;
1056 return \%values;
1057 } else {
1058 return;
1062 =head2 GetKohaAuthorisedValuesFromField
1064 Takes $field, $subfield, $fwcode as parameters.
1066 If $opac parameter is set to a true value, displays OPAC descriptions rather than normal ones when they exist.
1067 $subfield can be undefined
1069 Returns hashref of Code => description
1071 Returns undef if no authorised value category is defined for the given field and subfield
1073 =cut
1075 sub GetKohaAuthorisedValuesFromField {
1076 my ($field, $subfield, $fwcode,$opac) = @_;
1077 $fwcode='' unless $fwcode;
1078 my %values;
1079 my $dbh = C4::Context->dbh;
1080 my $avcode = GetAuthValCodeFromField($field, $subfield, $fwcode);
1081 if ($avcode) {
1082 my $sth = $dbh->prepare("select authorised_value, lib, lib_opac from authorised_values where category=? ");
1083 $sth->execute($avcode);
1084 while ( my ($val, $lib, $lib_opac) = $sth->fetchrow_array ) {
1085 $values{$val} = ($opac && $lib_opac) ? $lib_opac : $lib;
1087 return \%values;
1088 } else {
1089 return;
1093 =head2 GetKohaAuthorisedValuesMapping
1095 Takes a hash as a parameter. The interface key indicates the
1096 description to use in the mapping.
1098 Returns hashref of:
1099 "{kohafield},{frameworkcode},{authorised_value}" => "{description}"
1100 for all the kohafields, frameworkcodes, and authorised values.
1102 Returns undef if nothing is found.
1104 =cut
1106 sub GetKohaAuthorisedValuesMapping {
1107 my ($parameter) = @_;
1108 my $interface = $parameter->{'interface'} // '';
1110 my $query_mapping = q{
1111 SELECT TA.kohafield,TA.authorised_value AS category,
1112 TA.frameworkcode,TB.authorised_value,
1113 IF(TB.lib_opac>'',TB.lib_opac,TB.lib) AS OPAC,
1114 TB.lib AS Intranet,TB.lib_opac
1115 FROM marc_subfield_structure AS TA JOIN
1116 authorised_values as TB ON
1117 TA.authorised_value=TB.category
1118 WHERE TA.kohafield>'' AND TA.authorised_value>'';
1120 my $dbh = C4::Context->dbh;
1121 my $sth = $dbh->prepare($query_mapping);
1122 $sth->execute();
1123 my $avmapping;
1124 if ($interface eq 'opac') {
1125 while (my $row = $sth->fetchrow_hashref) {
1126 $avmapping->{$row->{kohafield}.",".$row->{frameworkcode}.",".$row->{authorised_value}} = $row->{OPAC};
1129 else {
1130 while (my $row = $sth->fetchrow_hashref) {
1131 $avmapping->{$row->{kohafield}.",".$row->{frameworkcode}.",".$row->{authorised_value}} = $row->{Intranet};
1134 return $avmapping;
1137 =head2 xml_escape
1139 my $escaped_string = C4::Koha::xml_escape($string);
1141 Convert &, <, >, ', and " in a string to XML entities
1143 =cut
1145 sub xml_escape {
1146 my $str = shift;
1147 return '' unless defined $str;
1148 $str =~ s/&/&amp;/g;
1149 $str =~ s/</&lt;/g;
1150 $str =~ s/>/&gt;/g;
1151 $str =~ s/'/&apos;/g;
1152 $str =~ s/"/&quot;/g;
1153 return $str;
1156 =head2 GetKohaAuthorisedValueLib
1158 Takes $category, $authorised_value as parameters.
1160 If $opac parameter is set to a true value, displays OPAC descriptions rather than normal ones when they exist.
1162 Returns authorised value description
1164 =cut
1166 sub GetKohaAuthorisedValueLib {
1167 my ($category,$authorised_value,$opac) = @_;
1168 my $value;
1169 my $dbh = C4::Context->dbh;
1170 my $sth = $dbh->prepare("select lib, lib_opac from authorised_values where category=? and authorised_value=?");
1171 $sth->execute($category,$authorised_value);
1172 my $data = $sth->fetchrow_hashref;
1173 $value = ($opac && $$data{'lib_opac'}) ? $$data{'lib_opac'} : $$data{'lib'};
1174 return $value;
1177 =head2 display_marc_indicators
1179 my $display_form = C4::Koha::display_marc_indicators($field);
1181 C<$field> is a MARC::Field object
1183 Generate a display form of the indicators of a variable
1184 MARC field, replacing any blanks with '#'.
1186 =cut
1188 sub display_marc_indicators {
1189 my $field = shift;
1190 my $indicators = '';
1191 if ($field && $field->tag() >= 10) {
1192 $indicators = $field->indicator(1) . $field->indicator(2);
1193 $indicators =~ s/ /#/g;
1195 return $indicators;
1198 sub GetNormalizedUPC {
1199 my ($marcrecord,$marcflavour) = @_;
1201 return unless $marcrecord;
1202 if ($marcflavour eq 'UNIMARC') {
1203 my @fields = $marcrecord->field('072');
1204 foreach my $field (@fields) {
1205 my $upc = _normalize_match_point($field->subfield('a'));
1206 if ($upc) {
1207 return $upc;
1212 else { # assume marc21 if not unimarc
1213 my @fields = $marcrecord->field('024');
1214 foreach my $field (@fields) {
1215 my $indicator = $field->indicator(1);
1216 my $upc = _normalize_match_point($field->subfield('a'));
1217 if ($upc && $indicator == 1 ) {
1218 return $upc;
1224 # Normalizes and returns the first valid ISBN found in the record
1225 # ISBN13 are converted into ISBN10. This is required to get some book cover images.
1226 sub GetNormalizedISBN {
1227 my ($isbn,$marcrecord,$marcflavour) = @_;
1228 if ($isbn) {
1229 # Koha attempts to store multiple ISBNs in biblioitems.isbn, separated by " | "
1230 # anything after " | " should be removed, along with the delimiter
1231 ($isbn) = split(/\|/, $isbn );
1232 return _isbn_cleanup($isbn);
1235 return unless $marcrecord;
1237 if ($marcflavour eq 'UNIMARC') {
1238 my @fields = $marcrecord->field('010');
1239 foreach my $field (@fields) {
1240 my $isbn = $field->subfield('a');
1241 if ($isbn) {
1242 return _isbn_cleanup($isbn);
1246 else { # assume marc21 if not unimarc
1247 my @fields = $marcrecord->field('020');
1248 foreach my $field (@fields) {
1249 $isbn = $field->subfield('a');
1250 if ($isbn) {
1251 return _isbn_cleanup($isbn);
1257 sub GetNormalizedEAN {
1258 my ($marcrecord,$marcflavour) = @_;
1260 return unless $marcrecord;
1262 if ($marcflavour eq 'UNIMARC') {
1263 my @fields = $marcrecord->field('073');
1264 foreach my $field (@fields) {
1265 my $ean = _normalize_match_point($field->subfield('a'));
1266 if ( $ean ) {
1267 return $ean;
1271 else { # assume marc21 if not unimarc
1272 my @fields = $marcrecord->field('024');
1273 foreach my $field (@fields) {
1274 my $indicator = $field->indicator(1);
1275 my $ean = _normalize_match_point($field->subfield('a'));
1276 if ( $ean && $indicator == 3 ) {
1277 return $ean;
1283 sub GetNormalizedOCLCNumber {
1284 my ($marcrecord,$marcflavour) = @_;
1285 return unless $marcrecord;
1287 if ($marcflavour ne 'UNIMARC' ) {
1288 my @fields = $marcrecord->field('035');
1289 foreach my $field (@fields) {
1290 my $oclc = $field->subfield('a');
1291 if ($oclc =~ /OCoLC/) {
1292 $oclc =~ s/\(OCoLC\)//;
1293 return $oclc;
1296 } else {
1297 # TODO for UNIMARC
1299 return
1302 sub GetAuthvalueDropbox {
1303 my ( $authcat, $default ) = @_;
1304 my $branch_limit = C4::Context->userenv ? C4::Context->userenv->{"branch"} : "";
1305 my $dbh = C4::Context->dbh;
1307 my $query = qq{
1308 SELECT *
1309 FROM authorised_values
1311 $query .= qq{
1312 LEFT JOIN authorised_values_branches ON ( id = av_id )
1313 } if $branch_limit;
1314 $query .= qq{
1315 WHERE category = ?
1317 $query .= " AND ( branchcode = ? OR branchcode IS NULL )" if $branch_limit;
1318 $query .= " GROUP BY lib ORDER BY category, lib, lib_opac";
1319 my $sth = $dbh->prepare($query);
1320 $sth->execute( $authcat, $branch_limit ? $branch_limit : () );
1323 my $option_list = [];
1324 my @authorised_values = ( q{} );
1325 while (my $av = $sth->fetchrow_hashref) {
1326 push @{$option_list}, {
1327 value => $av->{authorised_value},
1328 label => $av->{lib},
1329 default => ($default eq $av->{authorised_value}),
1333 if ( @{$option_list} ) {
1334 return $option_list;
1336 return;
1340 =head2 GetDailyQuote($opts)
1342 Takes a hashref of options
1344 Currently supported options are:
1346 'id' An exact quote id
1347 'random' Select a random quote
1348 noop When no option is passed in, this sub will return the quote timestamped for the current day
1350 The function returns an anonymous hash following this format:
1353 'source' => 'source-of-quote',
1354 'timestamp' => 'timestamp-value',
1355 'text' => 'text-of-quote',
1356 'id' => 'quote-id'
1359 =cut
1361 # This is definitely a candidate for some sort of caching once we finally settle caching/persistence issues...
1362 # at least for default option
1364 sub GetDailyQuote {
1365 my %opts = @_;
1366 my $dbh = C4::Context->dbh;
1367 my $query = '';
1368 my $sth = undef;
1369 my $quote = undef;
1370 if ($opts{'id'}) {
1371 $query = 'SELECT * FROM quotes WHERE id = ?';
1372 $sth = $dbh->prepare($query);
1373 $sth->execute($opts{'id'});
1374 $quote = $sth->fetchrow_hashref();
1376 elsif ($opts{'random'}) {
1377 # Fall through... we also return a random quote as a catch-all if all else fails
1379 else {
1380 $query = 'SELECT * FROM quotes WHERE timestamp LIKE CONCAT(CURRENT_DATE,\'%\') ORDER BY timestamp DESC LIMIT 0,1';
1381 $sth = $dbh->prepare($query);
1382 $sth->execute();
1383 $quote = $sth->fetchrow_hashref();
1385 unless ($quote) { # if there are not matches, choose a random quote
1386 # get a list of all available quote ids
1387 $sth = C4::Context->dbh->prepare('SELECT count(*) FROM quotes;');
1388 $sth->execute;
1389 my $range = ($sth->fetchrow_array)[0];
1390 # chose a random id within that range if there is more than one quote
1391 my $offset = int(rand($range));
1392 # grab it
1393 $query = 'SELECT * FROM quotes ORDER BY id LIMIT 1 OFFSET ?';
1394 $sth = C4::Context->dbh->prepare($query);
1395 # see http://www.perlmonks.org/?node_id=837422 for why
1396 # we're being verbose and using bind_param
1397 $sth->bind_param(1, $offset, SQL_INTEGER);
1398 $sth->execute();
1399 $quote = $sth->fetchrow_hashref();
1400 # update the timestamp for that quote
1401 $query = 'UPDATE quotes SET timestamp = ? WHERE id = ?';
1402 $sth = C4::Context->dbh->prepare($query);
1403 $sth->execute(
1404 DateTime::Format::MySQL->format_datetime( dt_from_string() ),
1405 $quote->{'id'}
1408 return $quote;
1411 sub _normalize_match_point {
1412 my $match_point = shift;
1413 (my $normalized_match_point) = $match_point =~ /([\d-]*[X]*)/;
1414 $normalized_match_point =~ s/-//g;
1416 return $normalized_match_point;
1419 sub _isbn_cleanup {
1420 my ($isbn) = @_;
1421 return NormalizeISBN(
1423 isbn => $isbn,
1424 format => 'ISBN-10',
1425 strip_hyphens => 1,
1427 ) if $isbn;
1430 =head2 NormalizedISBN
1432 my $isbns = NormalizedISBN({
1433 isbn => $isbn,
1434 strip_hyphens => [0,1],
1435 format => ['ISBN-10', 'ISBN-13']
1438 Returns an isbn validated by Business::ISBN.
1439 Optionally strips hyphens and/or forces the isbn
1440 to be of the specified format.
1442 If the string cannot be validated as an isbn,
1443 it returns nothing.
1445 =cut
1447 sub NormalizeISBN {
1448 my ($params) = @_;
1450 my $string = $params->{isbn};
1451 my $strip_hyphens = $params->{strip_hyphens};
1452 my $format = $params->{format};
1454 return unless $string;
1456 my $isbn = Business::ISBN->new($string);
1458 if ( $isbn && $isbn->is_valid() ) {
1460 if ( $format eq 'ISBN-10' ) {
1461 $isbn = $isbn->as_isbn10();
1463 elsif ( $format eq 'ISBN-13' ) {
1464 $isbn = $isbn->as_isbn13();
1466 return unless $isbn;
1468 if ($strip_hyphens) {
1469 $string = $isbn->as_string( [] );
1470 } else {
1471 $string = $isbn->as_string();
1474 return $string;
1478 =head2 GetVariationsOfISBN
1480 my @isbns = GetVariationsOfISBN( $isbn );
1482 Returns a list of variations of the given isbn in
1483 both ISBN-10 and ISBN-13 formats, with and without
1484 hyphens.
1486 In a scalar context, the isbns are returned as a
1487 string delimited by ' | '.
1489 =cut
1491 sub GetVariationsOfISBN {
1492 my ($isbn) = @_;
1494 return unless $isbn;
1496 my @isbns;
1498 push( @isbns, NormalizeISBN({ isbn => $isbn }) );
1499 push( @isbns, NormalizeISBN({ isbn => $isbn, format => 'ISBN-10' }) );
1500 push( @isbns, NormalizeISBN({ isbn => $isbn, format => 'ISBN-13' }) );
1501 push( @isbns, NormalizeISBN({ isbn => $isbn, format => 'ISBN-10', strip_hyphens => 1 }) );
1502 push( @isbns, NormalizeISBN({ isbn => $isbn, format => 'ISBN-13', strip_hyphens => 1 }) );
1504 # Strip out any "empty" strings from the array
1505 @isbns = grep { defined($_) && $_ =~ /\S/ } @isbns;
1507 return wantarray ? @isbns : join( " | ", @isbns );
1510 =head2 GetVariationsOfISBNs
1512 my @isbns = GetVariationsOfISBNs( @isbns );
1514 Returns a list of variations of the given isbns in
1515 both ISBN-10 and ISBN-13 formats, with and without
1516 hyphens.
1518 In a scalar context, the isbns are returned as a
1519 string delimited by ' | '.
1521 =cut
1523 sub GetVariationsOfISBNs {
1524 my (@isbns) = @_;
1526 @isbns = map { GetVariationsOfISBN( $_ ) } @isbns;
1528 return wantarray ? @isbns : join( " | ", @isbns );
1531 =head2 IsKohaFieldLinked
1533 my $is_linked = IsKohaFieldLinked({
1534 kohafield => $kohafield,
1535 frameworkcode => $frameworkcode,
1538 Return 1 if the field is linked
1540 =cut
1542 sub IsKohaFieldLinked {
1543 my ( $params ) = @_;
1544 my $kohafield = $params->{kohafield};
1545 my $frameworkcode = $params->{frameworkcode} || '';
1546 my $dbh = C4::Context->dbh;
1547 my $is_linked = $dbh->selectcol_arrayref( q|
1548 SELECT COUNT(*)
1549 FROM marc_subfield_structure
1550 WHERE frameworkcode = ?
1551 AND kohafield = ?
1552 |,{}, $frameworkcode, $kohafield );
1553 return $is_linked->[0];
1558 __END__
1560 =head1 AUTHOR
1562 Koha Team
1564 =cut