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>.
27 use Koha
::DateUtils
qw(dt_from_string);
28 use Koha
::AuthorisedValues
;
30 use Koha
::MarcSubfieldStructures
;
31 use DateTime
::Format
::MySQL
;
34 use autouse
'Data::cselectall_arrayref' => qw(Dumper);
35 use DBI
qw(:sql_types);
36 use vars
qw(@ISA @EXPORT @EXPORT_OK $DEBUG);
42 &GetPrinters &GetPrinter
43 &GetItemTypesCategorized
49 &getitemtypeimagelocation
54 &GetNormalizedOCLCNumber
67 @EXPORT_OK = qw( GetDailyQuote );
72 C4::Koha - Perl Module containing convenience functions for Koha scripts
80 Koha.pm provides many functions for Koha scripts.
86 =head2 GetItemTypesCategorized
88 $categories = GetItemTypesCategorized();
90 Returns a hashref containing search categories.
91 A search category will be put in the hash if at least one of its itemtypes is visible in OPAC.
92 The categories must be part of Authorized Values (ITEMTYPECAT)
96 sub GetItemTypesCategorized
{
97 my $dbh = C4
::Context
->dbh;
98 # Order is important, so that partially hidden (some items are not visible in OPAC) search
99 # categories will be visible. hideinopac=0 must be last.
101 SELECT itemtype
, description
, imageurl
, hideinopac
, 0 as
'iscat' FROM itemtypes WHERE ISNULL
(searchcategory
) or length(searchcategory
) = 0
103 SELECT DISTINCT searchcategory AS
`itemtype`,
104 authorised_values
.lib_opac AS description
,
105 authorised_values
.imageurl AS imageurl
,
106 hideinopac
, 1 as
'iscat'
108 LEFT JOIN authorised_values ON searchcategory
= authorised_value
109 WHERE searchcategory
> '' and hideinopac
=1
111 SELECT DISTINCT searchcategory AS
`itemtype`,
112 authorised_values
.lib_opac AS description
,
113 authorised_values
.imageurl AS imageurl
,
114 hideinopac
, 1 as
'iscat'
116 LEFT JOIN authorised_values ON searchcategory
= authorised_value
117 WHERE searchcategory
> '' and hideinopac
=0
119 return ($dbh->selectall_hashref($query,'itemtype'));
122 =head2 getitemtypeimagedir
124 my $directory = getitemtypeimagedir( 'opac' );
126 pass in 'opac' or 'intranet'. Defaults to 'opac'.
128 returns the full path to the appropriate directory containing images.
132 sub getitemtypeimagedir
{
133 my $src = shift || 'opac';
134 if ($src eq 'intranet') {
135 return C4
::Context
->config('intrahtdocs') . '/' .C4
::Context
->preference('template') . '/img/itemtypeimg';
137 return C4
::Context
->config('opachtdocs') . '/' . C4
::Context
->preference('opacthemes') . '/itemtypeimg';
141 sub getitemtypeimagesrc
{
142 my $src = shift || 'opac';
143 if ($src eq 'intranet') {
144 return '/intranet-tmpl' . '/' . C4
::Context
->preference('template') . '/img/itemtypeimg';
146 return '/opac-tmpl' . '/' . C4
::Context
->preference('opacthemes') . '/itemtypeimg';
150 sub getitemtypeimagelocation
{
151 my ( $src, $image ) = @_;
153 return '' if ( !$image );
156 my $scheme = ( URI
::Split
::uri_split
( $image ) )[0];
158 return $image if ( $scheme );
160 return getitemtypeimagesrc
( $src ) . '/' . $image;
163 =head3 _getImagesFromDirectory
165 Find all of the image files in a directory in the filesystem
167 parameters: a directory name
169 returns: a list of images in that directory.
171 Notes: this does not traverse into subdirectories. See
172 _getSubdirectoryNames for help with that.
173 Images are assumed to be files with .gif or .png file extensions.
174 The image names returned do not have the directory name on them.
178 sub _getImagesFromDirectory
{
179 my $directoryname = shift;
180 return unless defined $directoryname;
181 return unless -d
$directoryname;
183 if ( opendir ( my $dh, $directoryname ) ) {
184 my @images = grep { /\.(gif|png)$/i } readdir( $dh );
186 @images = sort(@images);
189 warn "unable to opendir $directoryname: $!";
194 =head3 _getSubdirectoryNames
196 Find all of the directories in a directory in the filesystem
198 parameters: a directory name
200 returns: a list of subdirectories in that directory.
202 Notes: this does not traverse into subdirectories. Only the first
203 level of subdirectories are returned.
204 The directory names returned don't have the parent directory name on them.
208 sub _getSubdirectoryNames
{
209 my $directoryname = shift;
210 return unless defined $directoryname;
211 return unless -d
$directoryname;
213 if ( opendir ( my $dh, $directoryname ) ) {
214 my @directories = grep { -d File
::Spec
->catfile( $directoryname, $_ ) && ! ( /^\./ ) } readdir( $dh );
218 warn "unable to opendir $directoryname: $!";
225 returns: a listref of hashrefs. Each hash represents another collection of images.
227 { imagesetname => 'npl', # the name of the image set (npl is the original one)
228 images => listref of image hashrefs
231 each image is represented by a hashref like this:
233 { KohaImage => 'npl/image.gif',
234 StaffImageUrl => '/intranet-tmpl/prog/img/itemtypeimg/npl/image.gif',
235 OpacImageURL => '/opac-tmpl/prog/itemtypeimg/npl/image.gif'
236 checked => 0 or 1: was this the image passed to this method?
237 Note: I'd like to remove this somehow.
244 my $checked = $params{'checked'} || '';
246 my $paths = { staff
=> { filesystem
=> getitemtypeimagedir
('intranet'),
247 url
=> getitemtypeimagesrc
('intranet'),
249 opac
=> { filesystem
=> getitemtypeimagedir
('opac'),
250 url
=> getitemtypeimagesrc
('opac'),
254 my @imagesets = (); # list of hasrefs of image set data to pass to template
255 my @subdirectories = _getSubdirectoryNames
( $paths->{'staff'}{'filesystem'} );
256 foreach my $imagesubdir ( @subdirectories ) {
257 warn $imagesubdir if $DEBUG;
258 my @imagelist = (); # hashrefs of image info
259 my @imagenames = _getImagesFromDirectory
( File
::Spec
->catfile( $paths->{'staff'}{'filesystem'}, $imagesubdir ) );
260 my $imagesetactive = 0;
261 foreach my $thisimage ( @imagenames ) {
263 { KohaImage
=> "$imagesubdir/$thisimage",
264 StaffImageUrl
=> join( '/', $paths->{'staff'}{'url'}, $imagesubdir, $thisimage ),
265 OpacImageUrl
=> join( '/', $paths->{'opac'}{'url'}, $imagesubdir, $thisimage ),
266 checked
=> "$imagesubdir/$thisimage" eq $checked ?
1 : 0,
269 $imagesetactive = 1 if "$imagesubdir/$thisimage" eq $checked;
271 push @imagesets, { imagesetname
=> $imagesubdir,
272 imagesetactive
=> $imagesetactive,
273 images
=> \
@imagelist };
281 $printers = &GetPrinters();
282 @queues = keys %$printers;
284 Returns information about existing printer queues.
286 C<$printers> is a reference-to-hash whose keys are the print queues
287 defined in the printers table of the Koha database. The values are
288 references-to-hash, whose keys are the fields in the printers table.
294 my $dbh = C4
::Context
->dbh;
295 my $sth = $dbh->prepare("select * from printers");
297 while ( my $printer = $sth->fetchrow_hashref ) {
298 $printers{ $printer->{'printqueue'} } = $printer;
300 return ( \
%printers );
305 $printer = GetPrinter( $query, $printers );
310 my ( $query, $printers ) = @_; # get printer for this query from printers
311 my $printer = $query->param('printer');
312 my %cookie = $query->cookie('userenv');
313 ($printer) || ( $printer = $cookie{'printer'} ) || ( $printer = '' );
314 ( $printers->{$printer} ) || ( $printer = ( keys %$printers )[0] );
320 Returns the number of pages to display in a pagination bar, given the number
321 of items and the number of items per page.
326 my ( $nb_items, $nb_items_per_page ) = @_;
328 return int( ( $nb_items - 1 ) / $nb_items_per_page ) + 1;
333 (@themes) = &getallthemes('opac');
334 (@themes) = &getallthemes('intranet');
336 Returns an array of all available themes.
344 if ( $type eq 'intranet' ) {
345 $htdocs = C4
::Context
->config('intrahtdocs');
348 $htdocs = C4
::Context
->config('opachtdocs');
350 opendir D
, "$htdocs";
351 my @dirlist = readdir D
;
352 foreach my $directory (@dirlist) {
353 next if $directory eq 'lib';
354 -d
"$htdocs/$directory/en" and push @themes, $directory;
361 if ( C4
::Context
->preference("marcflavour") eq "UNIMARC" ) {
366 tags
=> [ qw
/ 600ab 601ab 602a 604at 605a 606ax 610a / ],
372 tags
=> [ qw
/ 607a / ],
378 tags
=> [ qw
/ 700ab 701ab 702ab / ],
379 sep
=> C4
::Context
->preference("UNIMARCAuthorsFacetsSeparator"),
384 tags
=> [ qw
/ 225a / ],
390 tags
=> [ qw
/ 995e / ],
394 label
=> 'CollectionCodes',
395 tags
=> [ qw
/ 099t 955h / ],
399 unless ( Koha
::Libraries
->search->count == 1 )
401 my $DisplayLibraryFacets = C4
::Context
->preference('DisplayLibraryFacets');
402 if ( $DisplayLibraryFacets eq 'both'
403 || $DisplayLibraryFacets eq 'holding' )
408 idx
=> 'holdingbranch',
409 label
=> 'HoldingLibrary',
410 tags
=> [qw
/ 995c /],
415 if ( $DisplayLibraryFacets eq 'both'
416 || $DisplayLibraryFacets eq 'home' )
422 label
=> 'HomeLibrary',
423 tags
=> [qw
/ 995b /],
434 tags
=> [ qw
/ 650a / ],
439 # label => 'People and Organizations',
440 # tags => [ qw/ 600a 610a 611a / ],
446 tags
=> [ qw
/ 651a / ],
452 tags
=> [ qw
/ 630a / ],
458 tags
=> [ qw
/ 100a 110a 700a / ],
464 tags
=> [ qw
/ 440a 490a / ],
469 label
=> 'ItemTypes',
470 tags
=> [ qw
/ 952y 942c / ],
476 tags
=> [ qw
/ 952c / ],
480 label
=> 'CollectionCodes',
481 tags
=> [ qw
/ 9528 / ],
485 unless ( Koha
::Libraries
->search->count == 1 )
487 my $DisplayLibraryFacets = C4
::Context
->preference('DisplayLibraryFacets');
488 if ( $DisplayLibraryFacets eq 'both'
489 || $DisplayLibraryFacets eq 'holding' )
494 idx
=> 'holdingbranch',
495 label
=> 'HoldingLibrary',
496 tags
=> [qw
/ 952b /],
501 if ( $DisplayLibraryFacets eq 'both'
502 || $DisplayLibraryFacets eq 'home' )
508 label
=> 'HomeLibrary',
509 tags
=> [qw
/ 952a /],
518 =head2 GetAuthorisedValues
520 $authvalues = GetAuthorisedValues([$category]);
522 This function returns all authorised values from the'authorised_value' table in a reference to array of hashrefs.
524 C<$category> returns authorised values for just one category (optional).
526 C<$opac> If set to a true value, displays OPAC descriptions rather than normal ones when they exist.
530 sub GetAuthorisedValues
{
531 my ( $category, $opac ) = @_;
533 # Is this cached already?
534 $opac = $opac ?
1 : 0; # normalise to be safe
536 C4
::Context
->userenv ? C4
::Context
->userenv->{"branch"} : "";
538 "AuthorisedValues-$category-$opac-$branch_limit";
539 my $cache = Koha
::Caches
->get_instance();
540 my $result = $cache->get_from_cache($cache_key);
541 return $result if $result;
544 my $dbh = C4
::Context
->dbh;
547 FROM authorised_values av
550 LEFT JOIN authorised_values_branches ON
( id
= av_id
)
555 push @where_strings, "category = ?";
556 push @where_args, $category;
559 push @where_strings, "( branchcode = ? OR branchcode IS NULL )";
560 push @where_args, $branch_limit;
562 if(@where_strings > 0) {
563 $query .= " WHERE " . join(" AND ", @where_strings);
565 $query .= ' ORDER BY category, ' . (
566 $opac ?
'COALESCE(lib_opac, lib)'
570 my $sth = $dbh->prepare($query);
572 $sth->execute( @where_args );
573 while (my $data=$sth->fetchrow_hashref) {
574 if ($opac && $data->{lib_opac
}) {
575 $data->{lib
} = $data->{lib_opac
};
577 push @results, $data;
581 $cache->set_in_cache( $cache_key, \
@results, { expiry
=> 5 } );
587 my $escaped_string = C4::Koha::xml_escape($string);
589 Convert &, <, >, ', and " in a string to XML entities
595 return '' unless defined $str;
599 $str =~ s/'/'/g;
600 $str =~ s/"/"/g;
604 =head2 display_marc_indicators
606 my $display_form = C4::Koha::display_marc_indicators($field);
608 C<$field> is a MARC::Field object
610 Generate a display form of the indicators of a variable
611 MARC field, replacing any blanks with '#'.
615 sub display_marc_indicators
{
618 if ($field && $field->tag() >= 10) {
619 $indicators = $field->indicator(1) . $field->indicator(2);
620 $indicators =~ s/ /#/g;
625 sub GetNormalizedUPC
{
626 my ($marcrecord,$marcflavour) = @_;
628 return unless $marcrecord;
629 if ($marcflavour eq 'UNIMARC') {
630 my @fields = $marcrecord->field('072');
631 foreach my $field (@fields) {
632 my $upc = _normalize_match_point
($field->subfield('a'));
639 else { # assume marc21 if not unimarc
640 my @fields = $marcrecord->field('024');
641 foreach my $field (@fields) {
642 my $indicator = $field->indicator(1);
643 my $upc = _normalize_match_point
($field->subfield('a'));
644 if ($upc && $indicator == 1 ) {
651 # Normalizes and returns the first valid ISBN found in the record
652 # ISBN13 are converted into ISBN10. This is required to get some book cover images.
653 sub GetNormalizedISBN
{
654 my ($isbn,$marcrecord,$marcflavour) = @_;
656 # Koha attempts to store multiple ISBNs in biblioitems.isbn, separated by " | "
657 # anything after " | " should be removed, along with the delimiter
658 ($isbn) = split(/\|/, $isbn );
659 return _isbn_cleanup
($isbn);
662 return unless $marcrecord;
664 if ($marcflavour eq 'UNIMARC') {
665 my @fields = $marcrecord->field('010');
666 foreach my $field (@fields) {
667 my $isbn = $field->subfield('a');
669 return _isbn_cleanup
($isbn);
673 else { # assume marc21 if not unimarc
674 my @fields = $marcrecord->field('020');
675 foreach my $field (@fields) {
676 $isbn = $field->subfield('a');
678 return _isbn_cleanup
($isbn);
684 sub GetNormalizedEAN
{
685 my ($marcrecord,$marcflavour) = @_;
687 return unless $marcrecord;
689 if ($marcflavour eq 'UNIMARC') {
690 my @fields = $marcrecord->field('073');
691 foreach my $field (@fields) {
692 my $ean = _normalize_match_point
($field->subfield('a'));
698 else { # assume marc21 if not unimarc
699 my @fields = $marcrecord->field('024');
700 foreach my $field (@fields) {
701 my $indicator = $field->indicator(1);
702 my $ean = _normalize_match_point
($field->subfield('a'));
703 if ( $ean && $indicator == 3 ) {
710 sub GetNormalizedOCLCNumber
{
711 my ($marcrecord,$marcflavour) = @_;
712 return unless $marcrecord;
714 if ($marcflavour ne 'UNIMARC' ) {
715 my @fields = $marcrecord->field('035');
716 foreach my $field (@fields) {
717 my $oclc = $field->subfield('a');
718 if ($oclc =~ /OCoLC/) {
719 $oclc =~ s/\(OCoLC\)//;
729 =head2 GetDailyQuote($opts)
731 Takes a hashref of options
733 Currently supported options are:
735 'id' An exact quote id
736 'random' Select a random quote
737 noop When no option is passed in, this sub will return the quote timestamped for the current day
739 The function returns an anonymous hash following this format:
742 'source' => 'source-of-quote',
743 'timestamp' => 'timestamp-value',
744 'text' => 'text-of-quote',
750 # This is definitely a candidate for some sort of caching once we finally settle caching/persistence issues...
751 # at least for default option
755 my $dbh = C4
::Context
->dbh;
760 $query = 'SELECT * FROM quotes WHERE id = ?';
761 $sth = $dbh->prepare($query);
762 $sth->execute($opts{'id'});
763 $quote = $sth->fetchrow_hashref();
765 elsif ($opts{'random'}) {
766 # Fall through... we also return a random quote as a catch-all if all else fails
769 $query = 'SELECT * FROM quotes WHERE timestamp LIKE CONCAT(CURRENT_DATE,\'%\') ORDER BY timestamp DESC LIMIT 0,1';
770 $sth = $dbh->prepare($query);
772 $quote = $sth->fetchrow_hashref();
774 unless ($quote) { # if there are not matches, choose a random quote
775 # get a list of all available quote ids
776 $sth = C4
::Context
->dbh->prepare('SELECT count(*) FROM quotes;');
778 my $range = ($sth->fetchrow_array)[0];
779 # chose a random id within that range if there is more than one quote
780 my $offset = int(rand($range));
782 $query = 'SELECT * FROM quotes ORDER BY id LIMIT 1 OFFSET ?';
783 $sth = C4
::Context
->dbh->prepare($query);
784 # see http://www.perlmonks.org/?node_id=837422 for why
785 # we're being verbose and using bind_param
786 $sth->bind_param(1, $offset, SQL_INTEGER
);
788 $quote = $sth->fetchrow_hashref();
789 # update the timestamp for that quote
790 $query = 'UPDATE quotes SET timestamp = ? WHERE id = ?';
791 $sth = C4
::Context
->dbh->prepare($query);
793 DateTime
::Format
::MySQL
->format_datetime( dt_from_string
() ),
800 sub _normalize_match_point
{
801 my $match_point = shift;
802 (my $normalized_match_point) = $match_point =~ /([\d-]*[X]*)/;
803 $normalized_match_point =~ s/-//g;
805 return $normalized_match_point;
810 return NormalizeISBN
(
821 my $isbns = NormalizeISBN({
823 strip_hyphens => [0,1],
824 format => ['ISBN-10', 'ISBN-13']
827 Returns an isbn validated by Business::ISBN.
828 Optionally strips hyphens and/or forces the isbn
829 to be of the specified format.
831 If the string cannot be validated as an isbn,
832 it returns nothing unless return_invalid param is passed.
834 #FIXME This routine (and others?) should be moved to Koha::Util::Normalize
841 my $string = $params->{isbn
};
842 my $strip_hyphens = $params->{strip_hyphens
};
843 my $format = $params->{format
} || q{};
844 my $return_invalid = $params->{return_invalid
};
846 return unless $string;
848 my $isbn = Business
::ISBN
->new($string);
850 if ( $isbn && $isbn->is_valid() ) {
852 if ( $format eq 'ISBN-10' ) {
853 $isbn = $isbn->as_isbn10();
855 elsif ( $format eq 'ISBN-13' ) {
856 $isbn = $isbn->as_isbn13();
860 if ($strip_hyphens) {
861 $string = $isbn->as_string( [] );
863 $string = $isbn->as_string();
867 } elsif ( $return_invalid ) {
873 =head2 GetVariationsOfISBN
875 my @isbns = GetVariationsOfISBN( $isbn );
877 Returns a list of variations of the given isbn in
878 both ISBN-10 and ISBN-13 formats, with and without
881 In a scalar context, the isbns are returned as a
882 string delimited by ' | '.
886 sub GetVariationsOfISBN
{
893 push( @isbns, NormalizeISBN
({ isbn
=> $isbn, return_invalid
=> 1 }) );
894 push( @isbns, NormalizeISBN
({ isbn
=> $isbn, format
=> 'ISBN-10' }) );
895 push( @isbns, NormalizeISBN
({ isbn
=> $isbn, format
=> 'ISBN-13' }) );
896 push( @isbns, NormalizeISBN
({ isbn
=> $isbn, format
=> 'ISBN-10', strip_hyphens
=> 1 }) );
897 push( @isbns, NormalizeISBN
({ isbn
=> $isbn, format
=> 'ISBN-13', strip_hyphens
=> 1 }) );
899 # Strip out any "empty" strings from the array
900 @isbns = grep { defined($_) && $_ =~ /\S/ } @isbns;
902 return wantarray ?
@isbns : join( " | ", @isbns );
905 =head2 GetVariationsOfISBNs
907 my @isbns = GetVariationsOfISBNs( @isbns );
909 Returns a list of variations of the given isbns in
910 both ISBN-10 and ISBN-13 formats, with and without
913 In a scalar context, the isbns are returned as a
914 string delimited by ' | '.
918 sub GetVariationsOfISBNs
{
921 @isbns = map { GetVariationsOfISBN
( $_ ) } @isbns;
923 return wantarray ?
@isbns : join( " | ", @isbns );
926 =head2 NormalizedISSN
928 my $issns = NormalizedISSN({
930 strip_hyphen => [0,1]
933 Returns an issn validated by Business::ISSN.
934 Optionally strips hyphen.
936 If the string cannot be validated as an issn,
944 my $string = $params->{issn
};
945 my $strip_hyphen = $params->{strip_hyphen
};
947 my $issn = Business
::ISSN
->new($string);
949 if ( $issn && $issn->is_valid ){
952 $string = $issn->_issn;
955 $string = $issn->as_string;
962 =head2 GetVariationsOfISSN
964 my @issns = GetVariationsOfISSN( $issn );
966 Returns a list of variations of the given issn in
967 with and without a hyphen.
969 In a scalar context, the issns are returned as a
970 string delimited by ' | '.
974 sub GetVariationsOfISSN
{
980 my $str = NormalizeISSN
({ issn
=> $issn });
983 push @issns, NormalizeISSN
({ issn
=> $issn, strip_hyphen
=> 1 });
988 # Strip out any "empty" strings from the array
989 @issns = grep { defined($_) && $_ =~ /\S/ } @issns;
991 return wantarray ?
@issns : join( " | ", @issns );
994 =head2 GetVariationsOfISSNs
996 my @issns = GetVariationsOfISSNs( @issns );
998 Returns a list of variations of the given issns in
999 with and without a hyphen.
1001 In a scalar context, the issns are returned as a
1002 string delimited by ' | '.
1006 sub GetVariationsOfISSNs
{
1009 @issns = map { GetVariationsOfISSN
( $_ ) } @issns;
1011 return wantarray ?
@issns : join( " | ", @issns );