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>.
24 #use warnings; FIXME - Bug 2505
28 use Koha
::DateUtils
qw(dt_from_string);
29 use Koha
::AuthorisedValues
;
31 use Koha
::MarcSubfieldStructures
;
32 use DateTime
::Format
::MySQL
;
35 use autouse
'Data::cselectall_arrayref' => qw(Dumper);
36 use DBI
qw(:sql_types);
37 use vars
qw(@ISA @EXPORT @EXPORT_OK $DEBUG);
43 &GetPrinters &GetPrinter
44 &GetItemTypesCategorized
50 &getitemtypeimagelocation
55 &GetNormalizedOCLCNumber
68 @EXPORT_OK = qw( GetDailyQuote );
73 C4::Koha - Perl Module containing convenience functions for Koha scripts
81 Koha.pm provides many functions for Koha scripts.
87 =head2 GetItemTypesCategorized
89 $categories = GetItemTypesCategorized();
91 Returns a hashref containing search categories.
92 A search category will be put in the hash if at least one of its itemtypes is visible in OPAC.
93 The categories must be part of Authorized Values (ITEMTYPECAT)
97 sub GetItemTypesCategorized
{
98 my $dbh = C4
::Context
->dbh;
99 # Order is important, so that partially hidden (some items are not visible in OPAC) search
100 # categories will be visible. hideinopac=0 must be last.
102 SELECT itemtype
, description
, imageurl
, hideinopac
, 0 as
'iscat' FROM itemtypes WHERE ISNULL
(searchcategory
) or length(searchcategory
) = 0
104 SELECT DISTINCT searchcategory AS
`itemtype`,
105 authorised_values
.lib_opac AS description
,
106 authorised_values
.imageurl AS imageurl
,
107 hideinopac
, 1 as
'iscat'
109 LEFT JOIN authorised_values ON searchcategory
= authorised_value
110 WHERE searchcategory
> '' and hideinopac
=1
112 SELECT DISTINCT searchcategory AS
`itemtype`,
113 authorised_values
.lib_opac AS description
,
114 authorised_values
.imageurl AS imageurl
,
115 hideinopac
, 1 as
'iscat'
117 LEFT JOIN authorised_values ON searchcategory
= authorised_value
118 WHERE searchcategory
> '' and hideinopac
=0
120 return ($dbh->selectall_hashref($query,'itemtype'));
123 =head2 getitemtypeimagedir
125 my $directory = getitemtypeimagedir( 'opac' );
127 pass in 'opac' or 'intranet'. Defaults to 'opac'.
129 returns the full path to the appropriate directory containing images.
133 sub getitemtypeimagedir
{
134 my $src = shift || 'opac';
135 if ($src eq 'intranet') {
136 return C4
::Context
->config('intrahtdocs') . '/' .C4
::Context
->preference('template') . '/img/itemtypeimg';
138 return C4
::Context
->config('opachtdocs') . '/' . C4
::Context
->preference('opacthemes') . '/itemtypeimg';
142 sub getitemtypeimagesrc
{
143 my $src = shift || 'opac';
144 if ($src eq 'intranet') {
145 return '/intranet-tmpl' . '/' . C4
::Context
->preference('template') . '/img/itemtypeimg';
147 return '/opac-tmpl' . '/' . C4
::Context
->preference('opacthemes') . '/itemtypeimg';
151 sub getitemtypeimagelocation
{
152 my ( $src, $image ) = @_;
154 return '' if ( !$image );
157 my $scheme = ( URI
::Split
::uri_split
( $image ) )[0];
159 return $image if ( $scheme );
161 return getitemtypeimagesrc
( $src ) . '/' . $image;
164 =head3 _getImagesFromDirectory
166 Find all of the image files in a directory in the filesystem
168 parameters: a directory name
170 returns: a list of images in that directory.
172 Notes: this does not traverse into subdirectories. See
173 _getSubdirectoryNames for help with that.
174 Images are assumed to be files with .gif or .png file extensions.
175 The image names returned do not have the directory name on them.
179 sub _getImagesFromDirectory
{
180 my $directoryname = shift;
181 return unless defined $directoryname;
182 return unless -d
$directoryname;
184 if ( opendir ( my $dh, $directoryname ) ) {
185 my @images = grep { /\.(gif|png)$/i } readdir( $dh );
187 @images = sort(@images);
190 warn "unable to opendir $directoryname: $!";
195 =head3 _getSubdirectoryNames
197 Find all of the directories in a directory in the filesystem
199 parameters: a directory name
201 returns: a list of subdirectories in that directory.
203 Notes: this does not traverse into subdirectories. Only the first
204 level of subdirectories are returned.
205 The directory names returned don't have the parent directory name on them.
209 sub _getSubdirectoryNames
{
210 my $directoryname = shift;
211 return unless defined $directoryname;
212 return unless -d
$directoryname;
214 if ( opendir ( my $dh, $directoryname ) ) {
215 my @directories = grep { -d File
::Spec
->catfile( $directoryname, $_ ) && ! ( /^\./ ) } readdir( $dh );
219 warn "unable to opendir $directoryname: $!";
226 returns: a listref of hashrefs. Each hash represents another collection of images.
228 { imagesetname => 'npl', # the name of the image set (npl is the original one)
229 images => listref of image hashrefs
232 each image is represented by a hashref like this:
234 { KohaImage => 'npl/image.gif',
235 StaffImageUrl => '/intranet-tmpl/prog/img/itemtypeimg/npl/image.gif',
236 OpacImageURL => '/opac-tmpl/prog/itemtypeimg/npl/image.gif'
237 checked => 0 or 1: was this the image passed to this method?
238 Note: I'd like to remove this somehow.
245 my $checked = $params{'checked'} || '';
247 my $paths = { staff
=> { filesystem
=> getitemtypeimagedir
('intranet'),
248 url
=> getitemtypeimagesrc
('intranet'),
250 opac
=> { filesystem
=> getitemtypeimagedir
('opac'),
251 url
=> getitemtypeimagesrc
('opac'),
255 my @imagesets = (); # list of hasrefs of image set data to pass to template
256 my @subdirectories = _getSubdirectoryNames
( $paths->{'staff'}{'filesystem'} );
257 foreach my $imagesubdir ( @subdirectories ) {
258 warn $imagesubdir if $DEBUG;
259 my @imagelist = (); # hashrefs of image info
260 my @imagenames = _getImagesFromDirectory
( File
::Spec
->catfile( $paths->{'staff'}{'filesystem'}, $imagesubdir ) );
261 my $imagesetactive = 0;
262 foreach my $thisimage ( @imagenames ) {
264 { KohaImage
=> "$imagesubdir/$thisimage",
265 StaffImageUrl
=> join( '/', $paths->{'staff'}{'url'}, $imagesubdir, $thisimage ),
266 OpacImageUrl
=> join( '/', $paths->{'opac'}{'url'}, $imagesubdir, $thisimage ),
267 checked
=> "$imagesubdir/$thisimage" eq $checked ?
1 : 0,
270 $imagesetactive = 1 if "$imagesubdir/$thisimage" eq $checked;
272 push @imagesets, { imagesetname
=> $imagesubdir,
273 imagesetactive
=> $imagesetactive,
274 images
=> \
@imagelist };
282 $printers = &GetPrinters();
283 @queues = keys %$printers;
285 Returns information about existing printer queues.
287 C<$printers> is a reference-to-hash whose keys are the print queues
288 defined in the printers table of the Koha database. The values are
289 references-to-hash, whose keys are the fields in the printers table.
295 my $dbh = C4
::Context
->dbh;
296 my $sth = $dbh->prepare("select * from printers");
298 while ( my $printer = $sth->fetchrow_hashref ) {
299 $printers{ $printer->{'printqueue'} } = $printer;
301 return ( \
%printers );
306 $printer = GetPrinter( $query, $printers );
311 my ( $query, $printers ) = @_; # get printer for this query from printers
312 my $printer = $query->param('printer');
313 my %cookie = $query->cookie('userenv');
314 ($printer) || ( $printer = $cookie{'printer'} ) || ( $printer = '' );
315 ( $printers->{$printer} ) || ( $printer = ( keys %$printers )[0] );
321 Returns the number of pages to display in a pagination bar, given the number
322 of items and the number of items per page.
327 my ( $nb_items, $nb_items_per_page ) = @_;
329 return int( ( $nb_items - 1 ) / $nb_items_per_page ) + 1;
334 (@themes) = &getallthemes('opac');
335 (@themes) = &getallthemes('intranet');
337 Returns an array of all available themes.
345 if ( $type eq 'intranet' ) {
346 $htdocs = C4
::Context
->config('intrahtdocs');
349 $htdocs = C4
::Context
->config('opachtdocs');
351 opendir D
, "$htdocs";
352 my @dirlist = readdir D
;
353 foreach my $directory (@dirlist) {
354 next if $directory eq 'lib';
355 -d
"$htdocs/$directory/en" and push @themes, $directory;
362 if ( C4
::Context
->preference("marcflavour") eq "UNIMARC" ) {
367 tags
=> [ qw
/ 600ab 601ab 602a 604at 605a 606ax 610a / ],
373 tags
=> [ qw
/ 607a / ],
379 tags
=> [ qw
/ 500a 501a 503a / ],
385 tags
=> [ qw
/ 700ab 701ab 702ab / ],
386 sep
=> C4
::Context
->preference("UNIMARCAuthorsFacetsSeparator"),
391 tags
=> [ qw
/ 225a / ],
397 tags
=> [ qw
/ 995e / ],
401 unless ( Koha
::Libraries
->search->count == 1 )
403 my $DisplayLibraryFacets = C4
::Context
->preference('DisplayLibraryFacets');
404 if ( $DisplayLibraryFacets eq 'both'
405 || $DisplayLibraryFacets eq 'holding' )
410 idx
=> 'holdingbranch',
411 label
=> 'HoldingLibrary',
412 tags
=> [qw
/ 995c /],
417 if ( $DisplayLibraryFacets eq 'both'
418 || $DisplayLibraryFacets eq 'home' )
424 label
=> 'HomeLibrary',
425 tags
=> [qw
/ 995b /],
436 tags
=> [ qw
/ 650a / ],
441 # label => 'People and Organizations',
442 # tags => [ qw/ 600a 610a 611a / ],
448 tags
=> [ qw
/ 651a / ],
454 tags
=> [ qw
/ 630a / ],
460 tags
=> [ qw
/ 100a 110a 700a / ],
466 tags
=> [ qw
/ 440a 490a / ],
471 label
=> 'ItemTypes',
472 tags
=> [ qw
/ 952y 942c / ],
478 tags
=> [ qw
/ 952c / ],
482 unless ( Koha
::Libraries
->search->count == 1 )
484 my $DisplayLibraryFacets = C4
::Context
->preference('DisplayLibraryFacets');
485 if ( $DisplayLibraryFacets eq 'both'
486 || $DisplayLibraryFacets eq 'holding' )
491 idx
=> 'holdingbranch',
492 label
=> 'HoldingLibrary',
493 tags
=> [qw
/ 952b /],
498 if ( $DisplayLibraryFacets eq 'both'
499 || $DisplayLibraryFacets eq 'home' )
505 label
=> 'HomeLibrary',
506 tags
=> [qw
/ 952a /],
515 =head2 GetAuthorisedValues
517 $authvalues = GetAuthorisedValues([$category]);
519 This function returns all authorised values from the'authorised_value' table in a reference to array of hashrefs.
521 C<$category> returns authorised values for just one category (optional).
523 C<$opac> If set to a true value, displays OPAC descriptions rather than normal ones when they exist.
527 sub GetAuthorisedValues
{
528 my ( $category, $opac ) = @_;
530 # Is this cached already?
531 $opac = $opac ?
1 : 0; # normalise to be safe
533 C4
::Context
->userenv ? C4
::Context
->userenv->{"branch"} : "";
535 "AuthorisedValues-$category-$opac-$branch_limit";
536 my $cache = Koha
::Caches
->get_instance();
537 my $result = $cache->get_from_cache($cache_key);
538 return $result if $result;
541 my $dbh = C4
::Context
->dbh;
544 FROM authorised_values av
547 LEFT JOIN authorised_values_branches ON
( id
= av_id
)
552 push @where_strings, "category = ?";
553 push @where_args, $category;
556 push @where_strings, "( branchcode = ? OR branchcode IS NULL )";
557 push @where_args, $branch_limit;
559 if(@where_strings > 0) {
560 $query .= " WHERE " . join(" AND ", @where_strings);
562 $query .= ' ORDER BY category, ' . (
563 $opac ?
'COALESCE(lib_opac, lib)'
567 my $sth = $dbh->prepare($query);
569 $sth->execute( @where_args );
570 while (my $data=$sth->fetchrow_hashref) {
571 if ($opac && $data->{lib_opac
}) {
572 $data->{lib
} = $data->{lib_opac
};
574 push @results, $data;
578 $cache->set_in_cache( $cache_key, \
@results, { expiry
=> 5 } );
584 my $escaped_string = C4::Koha::xml_escape($string);
586 Convert &, <, >, ', and " in a string to XML entities
592 return '' unless defined $str;
596 $str =~ s/'/'/g;
597 $str =~ s/"/"/g;
601 =head2 display_marc_indicators
603 my $display_form = C4::Koha::display_marc_indicators($field);
605 C<$field> is a MARC::Field object
607 Generate a display form of the indicators of a variable
608 MARC field, replacing any blanks with '#'.
612 sub display_marc_indicators
{
615 if ($field && $field->tag() >= 10) {
616 $indicators = $field->indicator(1) . $field->indicator(2);
617 $indicators =~ s/ /#/g;
622 sub GetNormalizedUPC
{
623 my ($marcrecord,$marcflavour) = @_;
625 return unless $marcrecord;
626 if ($marcflavour eq 'UNIMARC') {
627 my @fields = $marcrecord->field('072');
628 foreach my $field (@fields) {
629 my $upc = _normalize_match_point
($field->subfield('a'));
636 else { # assume marc21 if not unimarc
637 my @fields = $marcrecord->field('024');
638 foreach my $field (@fields) {
639 my $indicator = $field->indicator(1);
640 my $upc = _normalize_match_point
($field->subfield('a'));
641 if ($upc && $indicator == 1 ) {
648 # Normalizes and returns the first valid ISBN found in the record
649 # ISBN13 are converted into ISBN10. This is required to get some book cover images.
650 sub GetNormalizedISBN
{
651 my ($isbn,$marcrecord,$marcflavour) = @_;
653 # Koha attempts to store multiple ISBNs in biblioitems.isbn, separated by " | "
654 # anything after " | " should be removed, along with the delimiter
655 ($isbn) = split(/\|/, $isbn );
656 return _isbn_cleanup
($isbn);
659 return unless $marcrecord;
661 if ($marcflavour eq 'UNIMARC') {
662 my @fields = $marcrecord->field('010');
663 foreach my $field (@fields) {
664 my $isbn = $field->subfield('a');
666 return _isbn_cleanup
($isbn);
670 else { # assume marc21 if not unimarc
671 my @fields = $marcrecord->field('020');
672 foreach my $field (@fields) {
673 $isbn = $field->subfield('a');
675 return _isbn_cleanup
($isbn);
681 sub GetNormalizedEAN
{
682 my ($marcrecord,$marcflavour) = @_;
684 return unless $marcrecord;
686 if ($marcflavour eq 'UNIMARC') {
687 my @fields = $marcrecord->field('073');
688 foreach my $field (@fields) {
689 my $ean = _normalize_match_point
($field->subfield('a'));
695 else { # assume marc21 if not unimarc
696 my @fields = $marcrecord->field('024');
697 foreach my $field (@fields) {
698 my $indicator = $field->indicator(1);
699 my $ean = _normalize_match_point
($field->subfield('a'));
700 if ( $ean && $indicator == 3 ) {
707 sub GetNormalizedOCLCNumber
{
708 my ($marcrecord,$marcflavour) = @_;
709 return unless $marcrecord;
711 if ($marcflavour ne 'UNIMARC' ) {
712 my @fields = $marcrecord->field('035');
713 foreach my $field (@fields) {
714 my $oclc = $field->subfield('a');
715 if ($oclc =~ /OCoLC/) {
716 $oclc =~ s/\(OCoLC\)//;
726 =head2 GetDailyQuote($opts)
728 Takes a hashref of options
730 Currently supported options are:
732 'id' An exact quote id
733 'random' Select a random quote
734 noop When no option is passed in, this sub will return the quote timestamped for the current day
736 The function returns an anonymous hash following this format:
739 'source' => 'source-of-quote',
740 'timestamp' => 'timestamp-value',
741 'text' => 'text-of-quote',
747 # This is definitely a candidate for some sort of caching once we finally settle caching/persistence issues...
748 # at least for default option
752 my $dbh = C4
::Context
->dbh;
757 $query = 'SELECT * FROM quotes WHERE id = ?';
758 $sth = $dbh->prepare($query);
759 $sth->execute($opts{'id'});
760 $quote = $sth->fetchrow_hashref();
762 elsif ($opts{'random'}) {
763 # Fall through... we also return a random quote as a catch-all if all else fails
766 $query = 'SELECT * FROM quotes WHERE timestamp LIKE CONCAT(CURRENT_DATE,\'%\') ORDER BY timestamp DESC LIMIT 0,1';
767 $sth = $dbh->prepare($query);
769 $quote = $sth->fetchrow_hashref();
771 unless ($quote) { # if there are not matches, choose a random quote
772 # get a list of all available quote ids
773 $sth = C4
::Context
->dbh->prepare('SELECT count(*) FROM quotes;');
775 my $range = ($sth->fetchrow_array)[0];
776 # chose a random id within that range if there is more than one quote
777 my $offset = int(rand($range));
779 $query = 'SELECT * FROM quotes ORDER BY id LIMIT 1 OFFSET ?';
780 $sth = C4
::Context
->dbh->prepare($query);
781 # see http://www.perlmonks.org/?node_id=837422 for why
782 # we're being verbose and using bind_param
783 $sth->bind_param(1, $offset, SQL_INTEGER
);
785 $quote = $sth->fetchrow_hashref();
786 # update the timestamp for that quote
787 $query = 'UPDATE quotes SET timestamp = ? WHERE id = ?';
788 $sth = C4
::Context
->dbh->prepare($query);
790 DateTime
::Format
::MySQL
->format_datetime( dt_from_string
() ),
797 sub _normalize_match_point
{
798 my $match_point = shift;
799 (my $normalized_match_point) = $match_point =~ /([\d-]*[X]*)/;
800 $normalized_match_point =~ s/-//g;
802 return $normalized_match_point;
807 return NormalizeISBN
(
816 =head2 NormalizedISBN
818 my $isbns = NormalizedISBN({
820 strip_hyphens => [0,1],
821 format => ['ISBN-10', 'ISBN-13']
824 Returns an isbn validated by Business::ISBN.
825 Optionally strips hyphens and/or forces the isbn
826 to be of the specified format.
828 If the string cannot be validated as an isbn,
836 my $string = $params->{isbn
};
837 my $strip_hyphens = $params->{strip_hyphens
};
838 my $format = $params->{format
};
840 return unless $string;
842 my $isbn = Business
::ISBN
->new($string);
844 if ( $isbn && $isbn->is_valid() ) {
846 if ( $format eq 'ISBN-10' ) {
847 $isbn = $isbn->as_isbn10();
849 elsif ( $format eq 'ISBN-13' ) {
850 $isbn = $isbn->as_isbn13();
854 if ($strip_hyphens) {
855 $string = $isbn->as_string( [] );
857 $string = $isbn->as_string();
864 =head2 GetVariationsOfISBN
866 my @isbns = GetVariationsOfISBN( $isbn );
868 Returns a list of variations of the given isbn in
869 both ISBN-10 and ISBN-13 formats, with and without
872 In a scalar context, the isbns are returned as a
873 string delimited by ' | '.
877 sub GetVariationsOfISBN
{
884 push( @isbns, NormalizeISBN
({ isbn
=> $isbn }) );
885 push( @isbns, NormalizeISBN
({ isbn
=> $isbn, format
=> 'ISBN-10' }) );
886 push( @isbns, NormalizeISBN
({ isbn
=> $isbn, format
=> 'ISBN-13' }) );
887 push( @isbns, NormalizeISBN
({ isbn
=> $isbn, format
=> 'ISBN-10', strip_hyphens
=> 1 }) );
888 push( @isbns, NormalizeISBN
({ isbn
=> $isbn, format
=> 'ISBN-13', strip_hyphens
=> 1 }) );
890 # Strip out any "empty" strings from the array
891 @isbns = grep { defined($_) && $_ =~ /\S/ } @isbns;
893 return wantarray ?
@isbns : join( " | ", @isbns );
896 =head2 GetVariationsOfISBNs
898 my @isbns = GetVariationsOfISBNs( @isbns );
900 Returns a list of variations of the given isbns in
901 both ISBN-10 and ISBN-13 formats, with and without
904 In a scalar context, the isbns are returned as a
905 string delimited by ' | '.
909 sub GetVariationsOfISBNs
{
912 @isbns = map { GetVariationsOfISBN
( $_ ) } @isbns;
914 return wantarray ?
@isbns : join( " | ", @isbns );
917 =head2 NormalizedISSN
919 my $issns = NormalizedISSN({
921 strip_hyphen => [0,1]
924 Returns an issn validated by Business::ISSN.
925 Optionally strips hyphen.
927 If the string cannot be validated as an issn,
935 my $string = $params->{issn
};
936 my $strip_hyphen = $params->{strip_hyphen
};
938 my $issn = Business
::ISSN
->new($string);
940 if ( $issn && $issn->is_valid ){
943 $string = $issn->_issn;
946 $string = $issn->as_string;
953 =head2 GetVariationsOfISSN
955 my @issns = GetVariationsOfISSN( $issn );
957 Returns a list of variations of the given issn in
958 with and without a hyphen.
960 In a scalar context, the issns are returned as a
961 string delimited by ' | '.
965 sub GetVariationsOfISSN
{
971 my $str = NormalizeISSN
({ issn
=> $issn });
974 push @issns, NormalizeISSN
({ issn
=> $issn, strip_hyphen
=> 1 });
979 # Strip out any "empty" strings from the array
980 @issns = grep { defined($_) && $_ =~ /\S/ } @issns;
982 return wantarray ?
@issns : join( " | ", @issns );
985 =head2 GetVariationsOfISSNs
987 my @issns = GetVariationsOfISSNs( @issns );
989 Returns a list of variations of the given issns in
990 with and without a hyphen.
992 In a scalar context, the issns are returned as a
993 string delimited by ' | '.
997 sub GetVariationsOfISSNs
{
1000 @issns = map { GetVariationsOfISSN
( $_ ) } @issns;
1002 return wantarray ?
@issns : join( " | ", @issns );
1006 =head2 IsKohaFieldLinked
1008 my $is_linked = IsKohaFieldLinked({
1009 kohafield => $kohafield,
1010 frameworkcode => $frameworkcode,
1013 Return 1 if the field is linked
1017 sub IsKohaFieldLinked
{
1018 my ( $params ) = @_;
1019 my $kohafield = $params->{kohafield
};
1020 my $frameworkcode = $params->{frameworkcode
} || '';
1021 my $dbh = C4
::Context
->dbh;
1022 my $is_linked = $dbh->selectcol_arrayref( q
|
1024 FROM marc_subfield_structure
1025 WHERE frameworkcode
= ?
1027 |,{}, $frameworkcode, $kohafield );
1028 return $is_linked->[0];