Bug 14629: [QA Follow-up] Small tidy up
[koha.git] / C4 / Koha.pm
blob48c55328b244d2e368cf0e51659dab0382988acf
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::AuthorisedValues;
30 use Koha::Libraries;
31 use Koha::MarcSubfieldStructures;
32 use DateTime::Format::MySQL;
33 use Business::ISBN;
34 use Business::ISSN;
35 use autouse 'Data::cselectall_arrayref' => qw(Dumper);
36 use DBI qw(:sql_types);
37 use vars qw(@ISA @EXPORT @EXPORT_OK $DEBUG);
39 BEGIN {
40 require Exporter;
41 @ISA = qw(Exporter);
42 @EXPORT = qw(
43 &GetPrinters &GetPrinter
44 &GetItemTypes &getitemtypeinfo
45 &GetItemTypesCategorized &GetItemTypesByCategory
46 &getframeworks &getframeworkinfo
47 &GetFrameworksLoop
48 &getallthemes
49 &getFacets
50 &getnbpages
51 &get_infos_of
52 &get_notforloan_label_of
53 &getitemtypeimagedir
54 &getitemtypeimagesrc
55 &getitemtypeimagelocation
56 &GetAuthorisedValues
57 &GetAuthorisedValueCategories
58 &GetNormalizedUPC
59 &GetNormalizedISBN
60 &GetNormalizedEAN
61 &GetNormalizedOCLCNumber
62 &xml_escape
64 &GetVariationsOfISBN
65 &GetVariationsOfISBNs
66 &NormalizeISBN
67 &GetVariationsOfISSN
68 &GetVariationsOfISSNs
69 &NormalizeISSN
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 GetAuthorisedValues
886 $authvalues = GetAuthorisedValues([$category]);
888 This function returns all authorised values from the'authorised_value' table in a reference to array of hashrefs.
890 C<$category> returns authorised values for just one category (optional).
892 C<$opac> If set to a true value, displays OPAC descriptions rather than normal ones when they exist.
894 =cut
896 sub GetAuthorisedValues {
897 my ( $category, $opac ) = @_;
899 # Is this cached already?
900 $opac = $opac ? 1 : 0; # normalise to be safe
901 my $branch_limit =
902 C4::Context->userenv ? C4::Context->userenv->{"branch"} : "";
903 my $cache_key =
904 "AuthorisedValues-$category-$opac-$branch_limit";
905 my $cache = Koha::Caches->get_instance();
906 my $result = $cache->get_from_cache($cache_key);
907 return $result if $result;
909 my @results;
910 my $dbh = C4::Context->dbh;
911 my $query = qq{
912 SELECT DISTINCT av.*
913 FROM authorised_values av
915 $query .= qq{
916 LEFT JOIN authorised_values_branches ON ( id = av_id )
917 } if $branch_limit;
918 my @where_strings;
919 my @where_args;
920 if($category) {
921 push @where_strings, "category = ?";
922 push @where_args, $category;
924 if($branch_limit) {
925 push @where_strings, "( branchcode = ? OR branchcode IS NULL )";
926 push @where_args, $branch_limit;
928 if(@where_strings > 0) {
929 $query .= " WHERE " . join(" AND ", @where_strings);
931 $query .= ' ORDER BY category, ' . (
932 $opac ? 'COALESCE(lib_opac, lib)'
933 : 'lib, lib_opac'
936 my $sth = $dbh->prepare($query);
938 $sth->execute( @where_args );
939 while (my $data=$sth->fetchrow_hashref) {
940 if ($opac && $data->{lib_opac}) {
941 $data->{lib} = $data->{lib_opac};
943 push @results, $data;
945 $sth->finish;
947 $cache->set_in_cache( $cache_key, \@results, { expiry => 5 } );
948 return \@results;
951 =head2 GetAuthorisedValueCategories
953 $auth_categories = GetAuthorisedValueCategories();
955 Return an arrayref of all of the available authorised
956 value categories.
958 =cut
960 sub GetAuthorisedValueCategories {
961 my $dbh = C4::Context->dbh;
962 my $sth = $dbh->prepare("SELECT DISTINCT category FROM authorised_values ORDER BY category");
963 $sth->execute;
964 my @results;
965 while (defined (my $category = $sth->fetchrow_array) ) {
966 push @results, $category;
968 return \@results;
971 =head2 xml_escape
973 my $escaped_string = C4::Koha::xml_escape($string);
975 Convert &, <, >, ', and " in a string to XML entities
977 =cut
979 sub xml_escape {
980 my $str = shift;
981 return '' unless defined $str;
982 $str =~ s/&/&amp;/g;
983 $str =~ s/</&lt;/g;
984 $str =~ s/>/&gt;/g;
985 $str =~ s/'/&apos;/g;
986 $str =~ s/"/&quot;/g;
987 return $str;
990 =head2 display_marc_indicators
992 my $display_form = C4::Koha::display_marc_indicators($field);
994 C<$field> is a MARC::Field object
996 Generate a display form of the indicators of a variable
997 MARC field, replacing any blanks with '#'.
999 =cut
1001 sub display_marc_indicators {
1002 my $field = shift;
1003 my $indicators = '';
1004 if ($field && $field->tag() >= 10) {
1005 $indicators = $field->indicator(1) . $field->indicator(2);
1006 $indicators =~ s/ /#/g;
1008 return $indicators;
1011 sub GetNormalizedUPC {
1012 my ($marcrecord,$marcflavour) = @_;
1014 return unless $marcrecord;
1015 if ($marcflavour eq 'UNIMARC') {
1016 my @fields = $marcrecord->field('072');
1017 foreach my $field (@fields) {
1018 my $upc = _normalize_match_point($field->subfield('a'));
1019 if ($upc) {
1020 return $upc;
1025 else { # assume marc21 if not unimarc
1026 my @fields = $marcrecord->field('024');
1027 foreach my $field (@fields) {
1028 my $indicator = $field->indicator(1);
1029 my $upc = _normalize_match_point($field->subfield('a'));
1030 if ($upc && $indicator == 1 ) {
1031 return $upc;
1037 # Normalizes and returns the first valid ISBN found in the record
1038 # ISBN13 are converted into ISBN10. This is required to get some book cover images.
1039 sub GetNormalizedISBN {
1040 my ($isbn,$marcrecord,$marcflavour) = @_;
1041 if ($isbn) {
1042 # Koha attempts to store multiple ISBNs in biblioitems.isbn, separated by " | "
1043 # anything after " | " should be removed, along with the delimiter
1044 ($isbn) = split(/\|/, $isbn );
1045 return _isbn_cleanup($isbn);
1048 return unless $marcrecord;
1050 if ($marcflavour eq 'UNIMARC') {
1051 my @fields = $marcrecord->field('010');
1052 foreach my $field (@fields) {
1053 my $isbn = $field->subfield('a');
1054 if ($isbn) {
1055 return _isbn_cleanup($isbn);
1059 else { # assume marc21 if not unimarc
1060 my @fields = $marcrecord->field('020');
1061 foreach my $field (@fields) {
1062 $isbn = $field->subfield('a');
1063 if ($isbn) {
1064 return _isbn_cleanup($isbn);
1070 sub GetNormalizedEAN {
1071 my ($marcrecord,$marcflavour) = @_;
1073 return unless $marcrecord;
1075 if ($marcflavour eq 'UNIMARC') {
1076 my @fields = $marcrecord->field('073');
1077 foreach my $field (@fields) {
1078 my $ean = _normalize_match_point($field->subfield('a'));
1079 if ( $ean ) {
1080 return $ean;
1084 else { # assume marc21 if not unimarc
1085 my @fields = $marcrecord->field('024');
1086 foreach my $field (@fields) {
1087 my $indicator = $field->indicator(1);
1088 my $ean = _normalize_match_point($field->subfield('a'));
1089 if ( $ean && $indicator == 3 ) {
1090 return $ean;
1096 sub GetNormalizedOCLCNumber {
1097 my ($marcrecord,$marcflavour) = @_;
1098 return unless $marcrecord;
1100 if ($marcflavour ne 'UNIMARC' ) {
1101 my @fields = $marcrecord->field('035');
1102 foreach my $field (@fields) {
1103 my $oclc = $field->subfield('a');
1104 if ($oclc =~ /OCoLC/) {
1105 $oclc =~ s/\(OCoLC\)//;
1106 return $oclc;
1109 } else {
1110 # TODO for UNIMARC
1112 return
1115 sub GetAuthvalueDropbox {
1116 my ( $authcat, $default ) = @_;
1117 my $branch_limit = C4::Context->userenv ? C4::Context->userenv->{"branch"} : "";
1118 my $dbh = C4::Context->dbh;
1120 my $query = qq{
1121 SELECT *
1122 FROM authorised_values
1124 $query .= qq{
1125 LEFT JOIN authorised_values_branches ON ( id = av_id )
1126 } if $branch_limit;
1127 $query .= qq{
1128 WHERE category = ?
1130 $query .= " AND ( branchcode = ? OR branchcode IS NULL )" if $branch_limit;
1131 $query .= " GROUP BY lib ORDER BY category, lib, lib_opac";
1132 my $sth = $dbh->prepare($query);
1133 $sth->execute( $authcat, $branch_limit ? $branch_limit : () );
1136 my $option_list = [];
1137 my @authorised_values = ( q{} );
1138 while (my $av = $sth->fetchrow_hashref) {
1139 push @{$option_list}, {
1140 value => $av->{authorised_value},
1141 label => $av->{lib},
1142 default => ($default eq $av->{authorised_value}),
1146 if ( @{$option_list} ) {
1147 return $option_list;
1149 return;
1153 =head2 GetDailyQuote($opts)
1155 Takes a hashref of options
1157 Currently supported options are:
1159 'id' An exact quote id
1160 'random' Select a random quote
1161 noop When no option is passed in, this sub will return the quote timestamped for the current day
1163 The function returns an anonymous hash following this format:
1166 'source' => 'source-of-quote',
1167 'timestamp' => 'timestamp-value',
1168 'text' => 'text-of-quote',
1169 'id' => 'quote-id'
1172 =cut
1174 # This is definitely a candidate for some sort of caching once we finally settle caching/persistence issues...
1175 # at least for default option
1177 sub GetDailyQuote {
1178 my %opts = @_;
1179 my $dbh = C4::Context->dbh;
1180 my $query = '';
1181 my $sth = undef;
1182 my $quote = undef;
1183 if ($opts{'id'}) {
1184 $query = 'SELECT * FROM quotes WHERE id = ?';
1185 $sth = $dbh->prepare($query);
1186 $sth->execute($opts{'id'});
1187 $quote = $sth->fetchrow_hashref();
1189 elsif ($opts{'random'}) {
1190 # Fall through... we also return a random quote as a catch-all if all else fails
1192 else {
1193 $query = 'SELECT * FROM quotes WHERE timestamp LIKE CONCAT(CURRENT_DATE,\'%\') ORDER BY timestamp DESC LIMIT 0,1';
1194 $sth = $dbh->prepare($query);
1195 $sth->execute();
1196 $quote = $sth->fetchrow_hashref();
1198 unless ($quote) { # if there are not matches, choose a random quote
1199 # get a list of all available quote ids
1200 $sth = C4::Context->dbh->prepare('SELECT count(*) FROM quotes;');
1201 $sth->execute;
1202 my $range = ($sth->fetchrow_array)[0];
1203 # chose a random id within that range if there is more than one quote
1204 my $offset = int(rand($range));
1205 # grab it
1206 $query = 'SELECT * FROM quotes ORDER BY id LIMIT 1 OFFSET ?';
1207 $sth = C4::Context->dbh->prepare($query);
1208 # see http://www.perlmonks.org/?node_id=837422 for why
1209 # we're being verbose and using bind_param
1210 $sth->bind_param(1, $offset, SQL_INTEGER);
1211 $sth->execute();
1212 $quote = $sth->fetchrow_hashref();
1213 # update the timestamp for that quote
1214 $query = 'UPDATE quotes SET timestamp = ? WHERE id = ?';
1215 $sth = C4::Context->dbh->prepare($query);
1216 $sth->execute(
1217 DateTime::Format::MySQL->format_datetime( dt_from_string() ),
1218 $quote->{'id'}
1221 return $quote;
1224 sub _normalize_match_point {
1225 my $match_point = shift;
1226 (my $normalized_match_point) = $match_point =~ /([\d-]*[X]*)/;
1227 $normalized_match_point =~ s/-//g;
1229 return $normalized_match_point;
1232 sub _isbn_cleanup {
1233 my ($isbn) = @_;
1234 return NormalizeISBN(
1236 isbn => $isbn,
1237 format => 'ISBN-10',
1238 strip_hyphens => 1,
1240 ) if $isbn;
1243 =head2 NormalizedISBN
1245 my $isbns = NormalizedISBN({
1246 isbn => $isbn,
1247 strip_hyphens => [0,1],
1248 format => ['ISBN-10', 'ISBN-13']
1251 Returns an isbn validated by Business::ISBN.
1252 Optionally strips hyphens and/or forces the isbn
1253 to be of the specified format.
1255 If the string cannot be validated as an isbn,
1256 it returns nothing.
1258 =cut
1260 sub NormalizeISBN {
1261 my ($params) = @_;
1263 my $string = $params->{isbn};
1264 my $strip_hyphens = $params->{strip_hyphens};
1265 my $format = $params->{format};
1267 return unless $string;
1269 my $isbn = Business::ISBN->new($string);
1271 if ( $isbn && $isbn->is_valid() ) {
1273 if ( $format eq 'ISBN-10' ) {
1274 $isbn = $isbn->as_isbn10();
1276 elsif ( $format eq 'ISBN-13' ) {
1277 $isbn = $isbn->as_isbn13();
1279 return unless $isbn;
1281 if ($strip_hyphens) {
1282 $string = $isbn->as_string( [] );
1283 } else {
1284 $string = $isbn->as_string();
1287 return $string;
1291 =head2 GetVariationsOfISBN
1293 my @isbns = GetVariationsOfISBN( $isbn );
1295 Returns a list of variations of the given isbn in
1296 both ISBN-10 and ISBN-13 formats, with and without
1297 hyphens.
1299 In a scalar context, the isbns are returned as a
1300 string delimited by ' | '.
1302 =cut
1304 sub GetVariationsOfISBN {
1305 my ($isbn) = @_;
1307 return unless $isbn;
1309 my @isbns;
1311 push( @isbns, NormalizeISBN({ isbn => $isbn }) );
1312 push( @isbns, NormalizeISBN({ isbn => $isbn, format => 'ISBN-10' }) );
1313 push( @isbns, NormalizeISBN({ isbn => $isbn, format => 'ISBN-13' }) );
1314 push( @isbns, NormalizeISBN({ isbn => $isbn, format => 'ISBN-10', strip_hyphens => 1 }) );
1315 push( @isbns, NormalizeISBN({ isbn => $isbn, format => 'ISBN-13', strip_hyphens => 1 }) );
1317 # Strip out any "empty" strings from the array
1318 @isbns = grep { defined($_) && $_ =~ /\S/ } @isbns;
1320 return wantarray ? @isbns : join( " | ", @isbns );
1323 =head2 GetVariationsOfISBNs
1325 my @isbns = GetVariationsOfISBNs( @isbns );
1327 Returns a list of variations of the given isbns in
1328 both ISBN-10 and ISBN-13 formats, with and without
1329 hyphens.
1331 In a scalar context, the isbns are returned as a
1332 string delimited by ' | '.
1334 =cut
1336 sub GetVariationsOfISBNs {
1337 my (@isbns) = @_;
1339 @isbns = map { GetVariationsOfISBN( $_ ) } @isbns;
1341 return wantarray ? @isbns : join( " | ", @isbns );
1344 =head2 NormalizedISSN
1346 my $issns = NormalizedISSN({
1347 issn => $issn,
1348 strip_hyphen => [0,1]
1351 Returns an issn validated by Business::ISSN.
1352 Optionally strips hyphen.
1354 If the string cannot be validated as an issn,
1355 it returns nothing.
1357 =cut
1359 sub NormalizeISSN {
1360 my ($params) = @_;
1362 my $string = $params->{issn};
1363 my $strip_hyphen = $params->{strip_hyphen};
1365 my $issn = Business::ISSN->new($string);
1367 if ( $issn && $issn->is_valid ){
1369 if ($strip_hyphen) {
1370 $string = $issn->_issn;
1372 else {
1373 $string = $issn->as_string;
1375 return $string;
1380 =head2 GetVariationsOfISSN
1382 my @issns = GetVariationsOfISSN( $issn );
1384 Returns a list of variations of the given issn in
1385 with and without a hyphen.
1387 In a scalar context, the issns are returned as a
1388 string delimited by ' | '.
1390 =cut
1392 sub GetVariationsOfISSN {
1393 my ( $issn ) = @_;
1395 return unless $issn;
1397 my @issns;
1398 my $str = NormalizeISSN({ issn => $issn });
1399 if( $str ) {
1400 push @issns, $str;
1401 push @issns, NormalizeISSN({ issn => $issn, strip_hyphen => 1 });
1402 } else {
1403 push @issns, $issn;
1406 # Strip out any "empty" strings from the array
1407 @issns = grep { defined($_) && $_ =~ /\S/ } @issns;
1409 return wantarray ? @issns : join( " | ", @issns );
1412 =head2 GetVariationsOfISSNs
1414 my @issns = GetVariationsOfISSNs( @issns );
1416 Returns a list of variations of the given issns in
1417 with and without a hyphen.
1419 In a scalar context, the issns are returned as a
1420 string delimited by ' | '.
1422 =cut
1424 sub GetVariationsOfISSNs {
1425 my (@issns) = @_;
1427 @issns = map { GetVariationsOfISSN( $_ ) } @issns;
1429 return wantarray ? @issns : join( " | ", @issns );
1433 =head2 IsKohaFieldLinked
1435 my $is_linked = IsKohaFieldLinked({
1436 kohafield => $kohafield,
1437 frameworkcode => $frameworkcode,
1440 Return 1 if the field is linked
1442 =cut
1444 sub IsKohaFieldLinked {
1445 my ( $params ) = @_;
1446 my $kohafield = $params->{kohafield};
1447 my $frameworkcode = $params->{frameworkcode} || '';
1448 my $dbh = C4::Context->dbh;
1449 my $is_linked = $dbh->selectcol_arrayref( q|
1450 SELECT COUNT(*)
1451 FROM marc_subfield_structure
1452 WHERE frameworkcode = ?
1453 AND kohafield = ?
1454 |,{}, $frameworkcode, $kohafield );
1455 return $is_linked->[0];
1460 __END__
1462 =head1 AUTHOR
1464 Koha Team
1466 =cut