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 &GetItemTypesCategorized
48 &getitemtypeimagelocation
53 &GetNormalizedOCLCNumber
66 @EXPORT_OK = qw( GetDailyQuote );
71 C4::Koha - Perl Module containing convenience functions for Koha scripts
79 Koha.pm provides many functions for Koha scripts.
85 =head2 GetItemTypesCategorized
87 $categories = GetItemTypesCategorized();
89 Returns a hashref containing search categories.
90 A search category will be put in the hash if at least one of its itemtypes is visible in OPAC.
91 The categories must be part of Authorized Values (ITEMTYPECAT)
95 sub GetItemTypesCategorized
{
96 my $dbh = C4
::Context
->dbh;
97 # Order is important, so that partially hidden (some items are not visible in OPAC) search
98 # categories will be visible. hideinopac=0 must be last.
100 SELECT itemtype
, description
, imageurl
, hideinopac
, 0 as
'iscat' FROM itemtypes WHERE ISNULL
(searchcategory
) or length(searchcategory
) = 0
102 SELECT DISTINCT searchcategory AS
`itemtype`,
103 COALESCE
(authorised_values
.lib_opac
,authorised_values
.lib
) AS description
,
104 authorised_values
.imageurl AS imageurl
,
105 hideinopac
, 1 as
'iscat'
107 LEFT JOIN authorised_values ON searchcategory
= authorised_value
108 WHERE searchcategory
> '' and hideinopac
=1
110 SELECT DISTINCT searchcategory AS
`itemtype`,
111 COALESCE
(authorised_values
.lib_opac
,authorised_values
.lib
) AS description
,
112 authorised_values
.imageurl AS imageurl
,
113 hideinopac
, 1 as
'iscat'
115 LEFT JOIN authorised_values ON searchcategory
= authorised_value
116 WHERE searchcategory
> '' and hideinopac
=0
118 return ($dbh->selectall_hashref($query,'itemtype'));
121 =head2 getitemtypeimagedir
123 my $directory = getitemtypeimagedir( 'opac' );
125 pass in 'opac' or 'intranet'. Defaults to 'opac'.
127 returns the full path to the appropriate directory containing images.
131 sub getitemtypeimagedir
{
132 my $src = shift || 'opac';
133 if ($src eq 'intranet') {
134 return C4
::Context
->config('intrahtdocs') . '/' .C4
::Context
->preference('template') . '/img/itemtypeimg';
136 return C4
::Context
->config('opachtdocs') . '/' . C4
::Context
->preference('opacthemes') . '/itemtypeimg';
140 sub getitemtypeimagesrc
{
141 my $src = shift || 'opac';
142 if ($src eq 'intranet') {
143 return '/intranet-tmpl' . '/' . C4
::Context
->preference('template') . '/img/itemtypeimg';
145 return '/opac-tmpl' . '/' . C4
::Context
->preference('opacthemes') . '/itemtypeimg';
149 sub getitemtypeimagelocation
{
150 my ( $src, $image ) = @_;
152 return '' if ( !$image );
155 my $scheme = ( URI
::Split
::uri_split
( $image ) )[0];
157 return $image if ( $scheme );
159 return getitemtypeimagesrc
( $src ) . '/' . $image;
162 =head3 _getImagesFromDirectory
164 Find all of the image files in a directory in the filesystem
166 parameters: a directory name
168 returns: a list of images in that directory.
170 Notes: this does not traverse into subdirectories. See
171 _getSubdirectoryNames for help with that.
172 Images are assumed to be files with .gif or .png file extensions.
173 The image names returned do not have the directory name on them.
177 sub _getImagesFromDirectory
{
178 my $directoryname = shift;
179 return unless defined $directoryname;
180 return unless -d
$directoryname;
182 if ( opendir ( my $dh, $directoryname ) ) {
183 my @images = grep { /\.(gif|png)$/i } readdir( $dh );
185 @images = sort(@images);
188 warn "unable to opendir $directoryname: $!";
193 =head3 _getSubdirectoryNames
195 Find all of the directories in a directory in the filesystem
197 parameters: a directory name
199 returns: a list of subdirectories in that directory.
201 Notes: this does not traverse into subdirectories. Only the first
202 level of subdirectories are returned.
203 The directory names returned don't have the parent directory name on them.
207 sub _getSubdirectoryNames
{
208 my $directoryname = shift;
209 return unless defined $directoryname;
210 return unless -d
$directoryname;
212 if ( opendir ( my $dh, $directoryname ) ) {
213 my @directories = grep { -d File
::Spec
->catfile( $directoryname, $_ ) && ! ( /^\./ ) } readdir( $dh );
217 warn "unable to opendir $directoryname: $!";
224 returns: a listref of hashrefs. Each hash represents another collection of images.
226 { imagesetname => 'npl', # the name of the image set (npl is the original one)
227 images => listref of image hashrefs
230 each image is represented by a hashref like this:
232 { KohaImage => 'npl/image.gif',
233 StaffImageUrl => '/intranet-tmpl/prog/img/itemtypeimg/npl/image.gif',
234 OpacImageURL => '/opac-tmpl/prog/itemtypeimg/npl/image.gif'
235 checked => 0 or 1: was this the image passed to this method?
236 Note: I'd like to remove this somehow.
243 my $checked = $params{'checked'} || '';
245 my $paths = { staff
=> { filesystem
=> getitemtypeimagedir
('intranet'),
246 url
=> getitemtypeimagesrc
('intranet'),
248 opac
=> { filesystem
=> getitemtypeimagedir
('opac'),
249 url
=> getitemtypeimagesrc
('opac'),
253 my @imagesets = (); # list of hasrefs of image set data to pass to template
254 my @subdirectories = _getSubdirectoryNames
( $paths->{'staff'}{'filesystem'} );
255 foreach my $imagesubdir ( @subdirectories ) {
256 warn $imagesubdir if $DEBUG;
257 my @imagelist = (); # hashrefs of image info
258 my @imagenames = _getImagesFromDirectory
( File
::Spec
->catfile( $paths->{'staff'}{'filesystem'}, $imagesubdir ) );
259 my $imagesetactive = 0;
260 foreach my $thisimage ( @imagenames ) {
262 { KohaImage
=> "$imagesubdir/$thisimage",
263 StaffImageUrl
=> join( '/', $paths->{'staff'}{'url'}, $imagesubdir, $thisimage ),
264 OpacImageUrl
=> join( '/', $paths->{'opac'}{'url'}, $imagesubdir, $thisimage ),
265 checked
=> "$imagesubdir/$thisimage" eq $checked ?
1 : 0,
268 $imagesetactive = 1 if "$imagesubdir/$thisimage" eq $checked;
270 push @imagesets, { imagesetname
=> $imagesubdir,
271 imagesetactive
=> $imagesetactive,
272 images
=> \
@imagelist };
280 Returns the number of pages to display in a pagination bar, given the number
281 of items and the number of items per page.
286 my ( $nb_items, $nb_items_per_page ) = @_;
288 return int( ( $nb_items - 1 ) / $nb_items_per_page ) + 1;
293 (@themes) = &getallthemes('opac');
294 (@themes) = &getallthemes('intranet');
296 Returns an array of all available themes.
304 if ( $type eq 'intranet' ) {
305 $htdocs = C4
::Context
->config('intrahtdocs');
308 $htdocs = C4
::Context
->config('opachtdocs');
310 opendir D
, "$htdocs";
311 my @dirlist = readdir D
;
312 foreach my $directory (@dirlist) {
313 next if $directory eq 'lib';
314 -d
"$htdocs/$directory/en" and push @themes, $directory;
321 if ( C4
::Context
->preference("marcflavour") eq "UNIMARC" ) {
326 tags
=> [ qw
/ 600ab 601ab 602a 604at 605a 606ax 610a / ],
332 tags
=> [ qw
/ 607a / ],
338 tags
=> [ qw
/ 700ab 701ab 702ab / ],
339 sep
=> C4
::Context
->preference("UNIMARCAuthorsFacetsSeparator"),
344 tags
=> [ qw
/ 225a / ],
350 tags
=> [ qw
/ 995e / ],
354 label
=> 'CollectionCodes',
355 tags
=> [ qw
/ 099t 955h / ],
359 unless ( Koha
::Libraries
->search->count == 1 )
361 my $DisplayLibraryFacets = C4
::Context
->preference('DisplayLibraryFacets');
362 if ( $DisplayLibraryFacets eq 'both'
363 || $DisplayLibraryFacets eq 'holding' )
368 idx
=> 'holdingbranch',
369 label
=> 'HoldingLibrary',
370 tags
=> [qw
/ 995c /],
375 if ( $DisplayLibraryFacets eq 'both'
376 || $DisplayLibraryFacets eq 'home' )
382 label
=> 'HomeLibrary',
383 tags
=> [qw
/ 995b /],
394 tags
=> [ qw
/ 650a / ],
399 # label => 'People and Organizations',
400 # tags => [ qw/ 600a 610a 611a / ],
406 tags
=> [ qw
/ 651a / ],
412 tags
=> [ qw
/ 630a / ],
418 tags
=> [ qw
/ 100a 110a 700a / ],
424 tags
=> [ qw
/ 440a 490a / ],
429 label
=> 'ItemTypes',
430 tags
=> [ qw
/ 952y 942c / ],
436 tags
=> [ qw
/ 952c / ],
440 label
=> 'CollectionCodes',
441 tags
=> [ qw
/ 9528 / ],
445 unless ( Koha
::Libraries
->search->count == 1 )
447 my $DisplayLibraryFacets = C4
::Context
->preference('DisplayLibraryFacets');
448 if ( $DisplayLibraryFacets eq 'both'
449 || $DisplayLibraryFacets eq 'holding' )
454 idx
=> 'holdingbranch',
455 label
=> 'HoldingLibrary',
456 tags
=> [qw
/ 952b /],
461 if ( $DisplayLibraryFacets eq 'both'
462 || $DisplayLibraryFacets eq 'home' )
468 label
=> 'HomeLibrary',
469 tags
=> [qw
/ 952a /],
478 =head2 GetAuthorisedValues
480 $authvalues = GetAuthorisedValues([$category]);
482 This function returns all authorised values from the'authorised_value' table in a reference to array of hashrefs.
484 C<$category> returns authorised values for just one category (optional).
486 C<$opac> If set to a true value, displays OPAC descriptions rather than normal ones when they exist.
490 sub GetAuthorisedValues
{
491 my ( $category, $opac ) = @_;
493 # Is this cached already?
494 $opac = $opac ?
1 : 0; # normalise to be safe
496 C4
::Context
->userenv ? C4
::Context
->userenv->{"branch"} : "";
498 "AuthorisedValues-$category-$opac-$branch_limit";
499 my $cache = Koha
::Caches
->get_instance();
500 my $result = $cache->get_from_cache($cache_key);
501 return $result if $result;
504 my $dbh = C4
::Context
->dbh;
507 FROM authorised_values av
510 LEFT JOIN authorised_values_branches ON
( id
= av_id
)
515 push @where_strings, "category = ?";
516 push @where_args, $category;
519 push @where_strings, "( branchcode = ? OR branchcode IS NULL )";
520 push @where_args, $branch_limit;
522 if(@where_strings > 0) {
523 $query .= " WHERE " . join(" AND ", @where_strings);
525 $query .= ' ORDER BY category, ' . (
526 $opac ?
'COALESCE(lib_opac, lib)'
530 my $sth = $dbh->prepare($query);
532 $sth->execute( @where_args );
533 while (my $data=$sth->fetchrow_hashref) {
534 if ($opac && $data->{lib_opac
}) {
535 $data->{lib
} = $data->{lib_opac
};
537 push @results, $data;
541 $cache->set_in_cache( $cache_key, \
@results, { expiry
=> 5 } );
547 my $escaped_string = C4::Koha::xml_escape($string);
549 Convert &, <, >, ', and " in a string to XML entities
555 return '' unless defined $str;
559 $str =~ s/'/'/g;
560 $str =~ s/"/"/g;
564 =head2 display_marc_indicators
566 my $display_form = C4::Koha::display_marc_indicators($field);
568 C<$field> is a MARC::Field object
570 Generate a display form of the indicators of a variable
571 MARC field, replacing any blanks with '#'.
575 sub display_marc_indicators
{
578 if ($field && $field->tag() >= 10) {
579 $indicators = $field->indicator(1) . $field->indicator(2);
580 $indicators =~ s/ /#/g;
585 sub GetNormalizedUPC
{
586 my ($marcrecord,$marcflavour) = @_;
588 return unless $marcrecord;
589 if ($marcflavour eq 'UNIMARC') {
590 my @fields = $marcrecord->field('072');
591 foreach my $field (@fields) {
592 my $upc = _normalize_match_point
($field->subfield('a'));
599 else { # assume marc21 if not unimarc
600 my @fields = $marcrecord->field('024');
601 foreach my $field (@fields) {
602 my $indicator = $field->indicator(1);
603 my $upc = _normalize_match_point
($field->subfield('a'));
604 if ($upc && $indicator == 1 ) {
611 # Normalizes and returns the first valid ISBN found in the record
612 # ISBN13 are converted into ISBN10. This is required to get some book cover images.
613 sub GetNormalizedISBN
{
614 my ($isbn,$marcrecord,$marcflavour) = @_;
616 # Koha attempts to store multiple ISBNs in biblioitems.isbn, separated by " | "
617 # anything after " | " should be removed, along with the delimiter
618 ($isbn) = split(/\|/, $isbn );
619 return _isbn_cleanup
($isbn);
622 return unless $marcrecord;
624 if ($marcflavour eq 'UNIMARC') {
625 my @fields = $marcrecord->field('010');
626 foreach my $field (@fields) {
627 my $isbn = $field->subfield('a');
629 return _isbn_cleanup
($isbn);
633 else { # assume marc21 if not unimarc
634 my @fields = $marcrecord->field('020');
635 foreach my $field (@fields) {
636 $isbn = $field->subfield('a');
638 return _isbn_cleanup
($isbn);
644 sub GetNormalizedEAN
{
645 my ($marcrecord,$marcflavour) = @_;
647 return unless $marcrecord;
649 if ($marcflavour eq 'UNIMARC') {
650 my @fields = $marcrecord->field('073');
651 foreach my $field (@fields) {
652 my $ean = _normalize_match_point
($field->subfield('a'));
658 else { # assume marc21 if not unimarc
659 my @fields = $marcrecord->field('024');
660 foreach my $field (@fields) {
661 my $indicator = $field->indicator(1);
662 my $ean = _normalize_match_point
($field->subfield('a'));
663 if ( $ean && $indicator == 3 ) {
670 sub GetNormalizedOCLCNumber
{
671 my ($marcrecord,$marcflavour) = @_;
672 return unless $marcrecord;
674 if ($marcflavour ne 'UNIMARC' ) {
675 my @fields = $marcrecord->field('035');
676 foreach my $field (@fields) {
677 my $oclc = $field->subfield('a');
678 if ($oclc =~ /OCoLC/) {
679 $oclc =~ s/\(OCoLC\)//;
689 =head2 GetDailyQuote($opts)
691 Takes a hashref of options
693 Currently supported options are:
695 'id' An exact quote id
696 'random' Select a random quote
697 noop When no option is passed in, this sub will return the quote timestamped for the current day
699 The function returns an anonymous hash following this format:
702 'source' => 'source-of-quote',
703 'timestamp' => 'timestamp-value',
704 'text' => 'text-of-quote',
710 # This is definitely a candidate for some sort of caching once we finally settle caching/persistence issues...
711 # at least for default option
715 my $dbh = C4
::Context
->dbh;
720 $query = 'SELECT * FROM quotes WHERE id = ?';
721 $sth = $dbh->prepare($query);
722 $sth->execute($opts{'id'});
723 $quote = $sth->fetchrow_hashref();
725 elsif ($opts{'random'}) {
726 # Fall through... we also return a random quote as a catch-all if all else fails
729 $query = 'SELECT * FROM quotes WHERE timestamp LIKE CONCAT(CURRENT_DATE,\'%\') ORDER BY timestamp DESC LIMIT 0,1';
730 $sth = $dbh->prepare($query);
732 $quote = $sth->fetchrow_hashref();
734 unless ($quote) { # if there are not matches, choose a random quote
735 # get a list of all available quote ids
736 $sth = C4
::Context
->dbh->prepare('SELECT count(*) FROM quotes;');
738 my $range = ($sth->fetchrow_array)[0];
739 # chose a random id within that range if there is more than one quote
740 my $offset = int(rand($range));
742 $query = 'SELECT * FROM quotes ORDER BY id LIMIT 1 OFFSET ?';
743 $sth = C4
::Context
->dbh->prepare($query);
744 # see http://www.perlmonks.org/?node_id=837422 for why
745 # we're being verbose and using bind_param
746 $sth->bind_param(1, $offset, SQL_INTEGER
);
748 $quote = $sth->fetchrow_hashref();
749 # update the timestamp for that quote
750 $query = 'UPDATE quotes SET timestamp = ? WHERE id = ?';
751 $sth = C4
::Context
->dbh->prepare($query);
753 DateTime
::Format
::MySQL
->format_datetime( dt_from_string
() ),
760 sub _normalize_match_point
{
761 my $match_point = shift;
762 (my $normalized_match_point) = $match_point =~ /([\d-]*[X]*)/;
763 $normalized_match_point =~ s/-//g;
765 return $normalized_match_point;
770 return NormalizeISBN
(
781 my $isbns = NormalizeISBN({
783 strip_hyphens => [0,1],
784 format => ['ISBN-10', 'ISBN-13']
787 Returns an isbn validated by Business::ISBN.
788 Optionally strips hyphens and/or forces the isbn
789 to be of the specified format.
791 If the string cannot be validated as an isbn,
792 it returns nothing unless return_invalid param is passed.
794 #FIXME This routine (and others?) should be moved to Koha::Util::Normalize
801 my $string = $params->{isbn
};
802 my $strip_hyphens = $params->{strip_hyphens
};
803 my $format = $params->{format
} || q{};
804 my $return_invalid = $params->{return_invalid
};
806 return unless $string;
808 my $isbn = Business
::ISBN
->new($string);
810 if ( $isbn && $isbn->is_valid() ) {
812 if ( $format eq 'ISBN-10' ) {
813 $isbn = $isbn->as_isbn10();
815 elsif ( $format eq 'ISBN-13' ) {
816 $isbn = $isbn->as_isbn13();
820 if ($strip_hyphens) {
821 $string = $isbn->as_string( [] );
823 $string = $isbn->as_string();
827 } elsif ( $return_invalid ) {
833 =head2 GetVariationsOfISBN
835 my @isbns = GetVariationsOfISBN( $isbn );
837 Returns a list of variations of the given isbn in
838 both ISBN-10 and ISBN-13 formats, with and without
841 In a scalar context, the isbns are returned as a
842 string delimited by ' | '.
846 sub GetVariationsOfISBN
{
853 push( @isbns, NormalizeISBN
({ isbn
=> $isbn, return_invalid
=> 1 }) );
854 push( @isbns, NormalizeISBN
({ isbn
=> $isbn, format
=> 'ISBN-10' }) );
855 push( @isbns, NormalizeISBN
({ isbn
=> $isbn, format
=> 'ISBN-13' }) );
856 push( @isbns, NormalizeISBN
({ isbn
=> $isbn, format
=> 'ISBN-10', strip_hyphens
=> 1 }) );
857 push( @isbns, NormalizeISBN
({ isbn
=> $isbn, format
=> 'ISBN-13', strip_hyphens
=> 1 }) );
859 # Strip out any "empty" strings from the array
860 @isbns = grep { defined($_) && $_ =~ /\S/ } @isbns;
862 return wantarray ?
@isbns : join( " | ", @isbns );
865 =head2 GetVariationsOfISBNs
867 my @isbns = GetVariationsOfISBNs( @isbns );
869 Returns a list of variations of the given isbns in
870 both ISBN-10 and ISBN-13 formats, with and without
873 In a scalar context, the isbns are returned as a
874 string delimited by ' | '.
878 sub GetVariationsOfISBNs
{
881 @isbns = map { GetVariationsOfISBN
( $_ ) } @isbns;
883 return wantarray ?
@isbns : join( " | ", @isbns );
886 =head2 NormalizedISSN
888 my $issns = NormalizedISSN({
890 strip_hyphen => [0,1]
893 Returns an issn validated by Business::ISSN.
894 Optionally strips hyphen.
896 If the string cannot be validated as an issn,
904 my $string = $params->{issn
};
905 my $strip_hyphen = $params->{strip_hyphen
};
907 my $issn = Business
::ISSN
->new($string);
909 if ( $issn && $issn->is_valid ){
912 $string = $issn->_issn;
915 $string = $issn->as_string;
922 =head2 GetVariationsOfISSN
924 my @issns = GetVariationsOfISSN( $issn );
926 Returns a list of variations of the given issn in
927 with and without a hyphen.
929 In a scalar context, the issns are returned as a
930 string delimited by ' | '.
934 sub GetVariationsOfISSN
{
940 my $str = NormalizeISSN
({ issn
=> $issn });
943 push @issns, NormalizeISSN
({ issn
=> $issn, strip_hyphen
=> 1 });
948 # Strip out any "empty" strings from the array
949 @issns = grep { defined($_) && $_ =~ /\S/ } @issns;
951 return wantarray ?
@issns : join( " | ", @issns );
954 =head2 GetVariationsOfISSNs
956 my @issns = GetVariationsOfISSNs( @issns );
958 Returns a list of variations of the given issns in
959 with and without a hyphen.
961 In a scalar context, the issns are returned as a
962 string delimited by ' | '.
966 sub GetVariationsOfISSNs
{
969 @issns = map { GetVariationsOfISSN
( $_ ) } @issns;
971 return wantarray ?
@issns : join( " | ", @issns );