Bug 7331 - Circulation.pl generates errors in the log
[koha.git] / C4 / VirtualShelves.pm
1 # -*- tab-width: 8 -*-
2 # Please use 8-character tabs for this file (indents are every 4 characters)
3
4 package C4::VirtualShelves;
5
6
7 # Copyright 2000-2002 Katipo Communications
8 #
9 # This file is part of Koha.
10 #
11 # Koha is free software; you can redistribute it and/or modify it under the
12 # terms of the GNU General Public License as published by the Free Software
13 # Foundation; either version 2 of the License, or (at your option) any later
14 # version.
15 #
16 # Koha is distributed in the hope that it will be useful, but WITHOUT ANY
17 # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
18 # A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
19 #
20 # You should have received a copy of the GNU General Public License along
21 # with Koha; if not, write to the Free Software Foundation, Inc.,
22 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
23
24 use strict;
25 use warnings;
26
27 use Carp;
28 use C4::Context;
29 use C4::Circulation;
30 use C4::Debug;
31 use C4::Members;
32 require C4::Auth;
33
34 use vars qw($VERSION @ISA @EXPORT @EXPORT_OK);
35
36 BEGIN {
37         # set the version for version checking
38         $VERSION = 3.02;
39         require Exporter;
40         @ISA    = qw(Exporter);
41         @EXPORT = qw(
42             &GetShelves &GetShelfContents &GetShelf
43             &AddToShelf &AddShelf
44             &ModShelf
45             &ShelfPossibleAction
46             &DelFromShelf &DelShelf
47             &GetBibliosShelves
48         );
49         @EXPORT_OK = qw(
50             &GetShelvesSummary &GetRecentShelves &GetAllShelves
51             &RefreshShelvesSummary &SetShelvesLimit
52         );
53 }
54
55
56 my $dbh = C4::Context->dbh;
57
58 =head1 NAME
59
60 C4::VirtualShelves - Functions for manipulating Koha virtual virtualshelves
61
62 =head1 SYNOPSIS
63
64   use C4::VirtualShelves;
65
66 =head1 DESCRIPTION
67
68 This module provides functions for manipulating virtual virtualshelves,
69 including creating and deleting virtualshelves, and adding and removing
70 bibs to and from virtualshelves.
71
72 =head1 FUNCTIONS
73
74 =head2 GetShelves
75
76   ($shelflist, $totshelves) = &GetShelves($mincategory, $row_count, $offset, $owner);
77   ($shelfnumber, $shelfhash) = each %{$shelflist};
78
79 Returns the number of shelves specified by C<$row_count> and C<$offset> as well as the total
80 number of shelves that meet the C<$owner> and C<$mincategory> criteria.  C<$mincategory>,
81 C<$row_count>, and C<$offset> are required. C<$owner> must be supplied when C<$mincategory> == 1.
82 When C<$mincategory> is 2 or 3, supply undef as argument for C<$owner>.
83 C<$shelflist>is a reference-to-hash. The keys are the virtualshelves numbers (C<$shelfnumber>, above),
84 and the values (C<$shelfhash>, above) are themselves references-to-hash, with the following keys:
85
86 =over
87
88 =item C<$shelfhash-E<gt>{shelfname}>
89
90 A string. The name of the shelf.
91
92 =item C<$shelfhash-E<gt>{count}>
93
94 The number of virtuals on that virtualshelves.
95
96 =back
97
98 =cut
99
100 sub GetShelves ($$$$) {
101     my ($mincategory, $row_count, $offset, $owner) = @_;
102         my @params = ($owner, $mincategory, ($offset ? $offset : 0), $row_count);
103         my @params1 = ($owner, $mincategory);
104         if ($mincategory > 1) {
105                 shift @params;
106                 shift @params1;
107         }
108         my $total = _shelf_count($owner, $mincategory);
109     # grab only the shelves meeting the row_count/offset spec...
110     my $query = qq(
111         SELECT virtualshelves.shelfnumber, virtualshelves.shelfname,owner,surname,firstname,virtualshelves.category,virtualshelves.sortfield,
112                count(virtualshelfcontents.biblionumber) as count
113         FROM   virtualshelves
114             LEFT JOIN   virtualshelfcontents ON virtualshelves.shelfnumber = virtualshelfcontents.shelfnumber
115             LEFT JOIN   borrowers ON virtualshelves.owner = borrowers.borrowernumber );
116     $query .= ($mincategory == 1) ? "WHERE  owner=? AND category=?" : "WHERE category>=?";
117         $query .= qq(
118         GROUP BY virtualshelves.shelfnumber
119         ORDER BY virtualshelves.shelfname
120                 LIMIT ?, ?);
121     my $sth2 = $dbh->prepare($query);
122     $sth2->execute(@params);
123     my %shelflist;
124     while ( my ( $shelfnumber, $shelfname, $owner, $surname,
125                 $firstname,   $category,  $sortfield, $count ) = $sth2->fetchrow ) {
126         $shelflist{$shelfnumber}->{'shelfname'} = $shelfname;
127         $shelflist{$shelfnumber}->{'count'}     = $count;
128         if($count eq 1){ $shelflist{$shelfnumber}->{'single'} = 1; }
129         $shelflist{$shelfnumber}->{'sortfield'} = $sortfield;
130         $shelflist{$shelfnumber}->{'category'}  = $category;
131         $shelflist{$shelfnumber}->{'owner'}     = $owner;
132         $shelflist{$shelfnumber}->{'surname'}   = $surname;
133         $shelflist{$shelfnumber}->{'firstname'} = $firstname;
134     }
135     return ( \%shelflist, $total );
136 }
137
138 =head2 GetShelvesSummary
139
140         ($shelves, $total) = GetShelvesSummary($mincategory, $row_count, $offset, $owner)
141
142 Returns the number of shelves specified by C<$row_count> and C<$offset> as well as the total
143 number of shelves that meet the C<$owner> and/or C<$mincategory> criteria. C<$mincategory>,
144 C<$row_count>, and C<$offset> are required. C<$owner> must be supplied when C<$mincategory> == 1.
145 When C<$mincategory> is 2 or 3, supply undef as argument for C<$owner>.
146
147 =cut
148
149 sub GetShelvesSummary ($$$$) {
150     my ($mincategory, $row_count, $offset, $owner) = @_;
151         my @params = ($owner, $mincategory, ($offset ? $offset : 0), $row_count);
152         my @params1 = ($owner, $mincategory);
153         if ($mincategory > 1) {
154                 shift @params;
155                 shift @params1;
156         }
157         my $total = _shelf_count($owner, $mincategory);
158     # grab only the shelves meeting the row_count/offset spec...
159         my $query = qq(
160                 SELECT
161                         virtualshelves.shelfnumber,
162                         virtualshelves.shelfname,
163                         owner,
164                         CONCAT(firstname, ' ', surname) AS name,
165                         virtualshelves.category,
166                         count(virtualshelfcontents.biblionumber) AS count
167                 FROM   virtualshelves
168                         LEFT JOIN  virtualshelfcontents ON virtualshelves.shelfnumber = virtualshelfcontents.shelfnumber
169                         LEFT JOIN             borrowers ON virtualshelves.owner = borrowers.borrowernumber );
170     $query .= ($mincategory == 1) ? "WHERE  owner=? AND category=?" : "WHERE category>=?";
171         $query .= qq(
172                 GROUP BY virtualshelves.shelfnumber
173                 ORDER BY virtualshelves.category
174                 DESC 
175                 LIMIT ?, ?);
176         my $sth2 = $dbh->prepare($query);
177         $sth2->execute(@params);
178     my $shelves = $sth2->fetchall_arrayref({});
179     return ($shelves, $total);
180
181         # Probably NOT the final implementation since it is still bulky (repeated hash keys).
182         # might like an array of rows of delimited values:
183         # 1|2||0|blacklist|112
184         # 2|6|Josh Ferraro|51|en_fuego|106
185 }
186
187 =head2 GetRecentShelves
188
189         ($shelflist, $total) = GetRecentShelves(1, $limit, $owner)
190
191 This function returns a reference to an array of hashrefs containing specified shelves sorted
192 by the date the shelf was last modified in descending order limited to the number of records
193 specified by C<$row_count>. If calling with C<$mincategory> other than 1, use undef as C<$owner>.
194
195 This function is intended to return a dataset reflecting the most recently active shelves for
196 the submitted parameters.
197
198 =cut
199
200 sub GetRecentShelves {
201     my ($mincategory, $row_count, $owner) = @_;
202     my $total = _shelf_count($owner, $mincategory);
203     my @params;
204     my $selection;
205     if (defined $owner) {
206         @params = ($owner, $mincategory);
207         $selection = ' WHERE owner = ? AND category = ?';
208     } else {
209         @params = ( $mincategory);
210         $selection = ' WHERE category >= ? ';
211     }
212     my $query = 'SELECT * FROM virtualshelves';
213     $query .= $selection;
214     $query .= ' ORDER BY lastmodified DESC';
215     if ($row_count){
216         $query .= ' LIMIT ?';
217         push @params,$row_count;
218     }
219     my $sth = $dbh->prepare($query);
220     $sth->execute(@params);
221     my $shelflist = $sth->fetchall_arrayref({});
222     return ( $shelflist, $total );
223 }
224
225 =head2 GetAllShelves
226
227     $shelflist = GetAllShelves($owner)
228
229 This function returns a reference to an array of hashrefs containing all shelves sorted
230 by the shelf name.
231
232 This function is intended to return a dataset reflecting all the shelves for
233 the submitted parameters.
234
235 =cut
236
237 sub GetAllShelves {
238     my ($category,$owner) = @_;
239     my $query = 'SELECT * FROM virtualshelves WHERE category = ? AND owner = ? ORDER BY shelfname ASC';
240     my $sth = $dbh->prepare( $query );
241     $sth->execute( $category, $owner );
242     return $sth->fetchall_arrayref({});
243 }
244
245 =head2 GetShelf
246
247   (shelfnumber,shelfname,owner,category,sortfield) = &GetShelf($shelfnumber);
248
249 Looks up information about the contents of virtual virtualshelves number
250 C<$shelfnumber>
251
252 Returns the database's information on 'virtualshelves' table.
253
254 =cut
255
256 sub GetShelf ($) {
257     my ($shelfnumber) = @_;
258     my $query = qq(
259         SELECT shelfnumber, shelfname, owner, category, sortfield
260         FROM   virtualshelves
261         WHERE  shelfnumber=?
262     );
263     my $sth = $dbh->prepare($query);
264     $sth->execute($shelfnumber);
265     return $sth->fetchrow;
266 }
267
268 =head2 GetShelfContents
269
270   $biblist = &GetShelfContents($shelfnumber);
271
272 Looks up information about the contents of virtual virtualshelves number
273 C<$shelfnumber>.  Sorted by a field in the biblio table.  copyrightdate 
274 gives a desc sort.
275
276 Returns a reference-to-array, whose elements are references-to-hash,
277 as returned by C<C4::Biblio::GetBiblioFromItemNumber>.
278
279 Note: the notforloan status comes from the itemtype, and where it equals 0
280 it does not ensure that related items.notforloan status is likewise 0. The
281 caller has to check any items on their own, possibly with CanBookBeIssued
282 from C4::Circulation.
283
284 =cut
285
286 sub GetShelfContents ($;$$$) {
287     my ($shelfnumber, $row_count, $offset, $sortfield) = @_;
288     my $dbh=C4::Context->dbh();
289         my $sth1 = $dbh->prepare("SELECT count(*) FROM virtualshelfcontents WHERE shelfnumber = ?");
290         $sth1->execute($shelfnumber);
291         my $total = $sth1->fetchrow;
292         if(!$sortfield) {
293                 my $sth2 = $dbh->prepare('SELECT sortfield FROM virtualshelves WHERE shelfnumber=?');
294                 $sth2->execute($shelfnumber);
295                 ($sortfield) = $sth2->fetchrow_array;
296         }
297     my $query =
298        " SELECT vc.biblionumber, vc.shelfnumber, vc.dateadded, itemtypes.*,
299             biblio.*, biblioitems.itemtype, biblioitems.publicationyear as year, biblioitems.publishercode, biblioitems.place, biblioitems.size, biblioitems.pages
300          FROM   virtualshelfcontents vc
301                  LEFT JOIN biblio      ON      vc.biblionumber =      biblio.biblionumber
302                  LEFT JOIN biblioitems ON  biblio.biblionumber = biblioitems.biblionumber
303                  LEFT JOIN itemtypes   ON biblioitems.itemtype = itemtypes.itemtype
304          WHERE  vc.shelfnumber=? ";
305         my @params = ($shelfnumber);
306         if($sortfield) {
307                 $query .= " ORDER BY " . $sortfield;
308                 $query .= " DESC " if ($sortfield eq 'copyrightdate');
309         }
310     if($row_count){
311            $query .= " LIMIT ?, ? ";
312            push (@params, ($offset ? $offset : 0));
313            push (@params, $row_count);
314     }
315     my $sth3 = $dbh->prepare($query);
316         $sth3->execute(@params);
317         return ($sth3->fetchall_arrayref({}), $total);
318         # Like the perldoc says,
319         # returns reference-to-array, where each element is reference-to-hash of the row:
320         #   like [ $sth->fetchrow_hashref(), $sth->fetchrow_hashref() ... ] 
321         # Suitable for use in TMPL_LOOP.
322         # See http://search.cpan.org/~timb/DBI-1.601/DBI.pm#fetchall_arrayref
323         # or newer, for your version of DBI.
324 }
325
326 =head2 AddShelf
327
328   $shelfnumber = &AddShelf( $shelfname, $owner, $category);
329
330 Creates a new virtual virtualshelves with name C<$shelfname>, owner C<$owner> and category
331 C<$category>.
332
333 Returns a code to know what's happen.
334     * -1 : if this virtualshelves already exist.
335     * $shelfnumber : if success.
336
337 =cut
338
339 sub AddShelf {
340     my ( $shelfname, $owner, $category, $sortfield ) = @_;
341     my $query = qq(
342         SELECT *
343         FROM   virtualshelves
344         WHERE  shelfname=? AND owner=?
345     );
346     my $sth = $dbh->prepare($query);
347     $sth->execute($shelfname,$owner);
348     ( $sth->rows ) and return (-1);
349     $query = qq(
350         INSERT INTO virtualshelves
351             (shelfname,owner,category,sortfield)
352         VALUES (?,?,?,?)
353     );
354     $sth = $dbh->prepare($query);
355     $sth->execute( $shelfname, $owner, $category, $sortfield );
356     my $shelfnumber = $dbh->{'mysql_insertid'};
357     return ($shelfnumber);
358 }
359
360 =head2 AddToShelf
361
362   &AddToShelf($biblionumber, $shelfnumber);
363
364 Adds bib number C<$biblionumber> to virtual virtualshelves number
365 C<$shelfnumber>, unless that bib is already on that shelf.
366
367 =cut
368
369 #'
370 sub AddToShelf {
371     my ( $biblionumber, $shelfnumber ) = @_;
372     return unless $biblionumber;
373     my $query = qq(
374         SELECT *
375         FROM   virtualshelfcontents
376         WHERE  shelfnumber=? AND biblionumber=?
377     );
378     my $sth = $dbh->prepare($query);
379
380     $sth->execute( $shelfnumber, $biblionumber );
381     ($sth->rows) and return undef;      # already on shelf
382         $query = qq(
383                 INSERT INTO virtualshelfcontents
384                         (shelfnumber, biblionumber, flags)
385                 VALUES
386                         (?, ?, 0)
387         );
388         $sth = $dbh->prepare($query);
389         $sth->execute( $shelfnumber, $biblionumber );
390         $query = qq(UPDATE virtualshelves
391                                 SET lastmodified = CURRENT_TIMESTAMP
392                                 WHERE shelfnumber = ?);
393         $sth = $dbh->prepare($query);
394         $sth->execute( $shelfnumber );
395 }
396
397 =head2 ModShelf
398
399 ModShelf($shelfnumber, $hashref)
400
401 Where $hashref->{column} = param
402
403 Modify the value into virtualshelves table with values given 
404 from hashref, which each key of the hashref should be
405 the name of a column of virtualshelves.
406
407 =cut
408
409 sub ModShelf {
410     my $shelfnumber = shift;
411     my $shelf = shift;
412
413     if (exists $shelf->{shelfnumber}) {
414         carp "Should not use ModShelf to change shelfnumber";
415         return;
416     }
417     unless (defined $shelfnumber and $shelfnumber =~ /^\d+$/) {
418         carp "Invalid shelfnumber passed to ModShelf: $shelfnumber";
419         return;
420     }
421
422         my $query = "UPDATE virtualshelves SET ";
423     my @bind_params = ();
424     my @set_clauses = ();
425
426         foreach my $column (keys %$shelf) {
427         push @set_clauses, "$column = ?";
428         push @bind_params, $shelf->{$column};
429     }
430
431     if ($#set_clauses == -1) {
432         carp "No columns to update passed to ModShelf";
433         return;
434     }
435     $query .= join(", ", @set_clauses);
436
437     $query .= " WHERE shelfnumber = ? ";
438     push @bind_params, $shelfnumber;
439
440     $debug and warn "ModShelf query:\n $query\n",
441                         "ModShelf query args: ", join(',', @bind_params), "\n";
442         my $sth = $dbh->prepare($query);
443         $sth->execute( @bind_params );
444 }
445
446 =head2 ShelfPossibleAction
447
448 ShelfPossibleAction($loggedinuser, $shelfnumber, $action);
449
450 C<$loggedinuser,$shelfnumber,$action>
451
452 $action can be "view" or "manage".
453
454 Returns 1 if the user can do the $action in the $shelfnumber shelf.
455 Returns 0 otherwise.
456
457 =cut
458
459 sub ShelfPossibleAction {
460     my ( $user, $shelfnumber, $action ) = @_;
461     my $query = qq(
462         SELECT owner,category
463         FROM   virtualshelves
464         WHERE  shelfnumber=?
465     );
466     my $sth = $dbh->prepare($query);
467     $sth->execute($shelfnumber);
468     my ( $owner, $category ) = $sth->fetchrow;
469         my $borrower = GetMemberDetails($user);
470         return 0 if not defined($user);
471         return 1 if ( $category >= 3);                                                  # open list
472     return 1 if (($category >= 2) and
473                                 defined($action) and $action eq 'view');        # public list, anybody can view
474     return 1 if (($category >= 2) and defined($user) and ($borrower->{authflags}->{superlibrarian} || $user == 0));     # public list, superlibrarian can edit/delete
475     return 1 if (defined($user)  and $owner  eq $user );        # user owns this list.  Check last.
476     return 0;
477 }
478
479 =head2 DelFromShelf
480
481   &DelFromShelf( $biblionumber, $shelfnumber);
482
483 Removes bib number C<$biblionumber> from virtual virtualshelves number
484 C<$shelfnumber>. If the bib wasn't on that virtualshelves to begin with,
485 nothing happens.
486
487 =cut
488
489 #'
490 sub DelFromShelf {
491     my ( $biblionumber, $shelfnumber ) = @_;
492     my $query = qq(
493         DELETE FROM virtualshelfcontents
494         WHERE  shelfnumber=? AND biblionumber=?
495     );
496     my $sth = $dbh->prepare($query);
497     $sth->execute( $shelfnumber, $biblionumber );
498 }
499
500 =head2 DelShelf (old version)
501
502   ($status, $msg) = &DelShelf($shelfnumber);
503
504 Deletes virtual virtualshelves number C<$shelfnumber>. The virtualshelves must
505 be empty.
506
507 Returns a two-element array, where C<$status> is 0 if the operation
508 was successful, or non-zero otherwise. C<$msg> is "Done" in case of
509 success, or an error message giving the reason for failure.
510
511 =head2 DelShelf (current version)
512
513   $Number = DelShelf($shelfnumber);
514
515 This function deletes the shelf number, and all of it's content.
516
517 =cut
518
519 sub DelShelf {
520         unless (@_) {
521                 carp "DelShelf called without valid argument (shelfnumber)";
522                 return undef;
523         }
524         my $sth = $dbh->prepare("DELETE FROM virtualshelves WHERE shelfnumber=?");
525         return $sth->execute(shift);
526 }
527
528 =head2 GetBibShelves
529
530 This finds all the public lists that this bib record is in.
531
532 =cut
533
534 sub GetBibliosShelves {
535     my ( $biblionumber )  = @_;
536     my $dbh = C4::Context->dbh;
537     my $sth = $dbh->prepare('
538         SELECT vs.shelfname, vs.shelfnumber 
539         FROM virtualshelves vs 
540         JOIN virtualshelfcontents vc ON (vs.shelfnumber= vc.shelfnumber) 
541         WHERE vs.category != 1 
542         AND vc.biblionumber= ?
543     ');
544     $sth->execute( $biblionumber );
545     return $sth->fetchall_arrayref({});
546 }
547
548 =head2 RefreshShelvesSummary
549
550         ($total, $pubshelves, $barshelves) = RefreshShelvesSummary($sessionID, $loggedinuser, $row_count);
551
552 Updates the current session and userenv with the most recent shelves
553
554 Returns the total number of shelves stored in the session/userenv along with two references each to an
555 array of hashes, one containing the C<$loggedinuser>'s private shelves and one containing all public/open shelves.
556
557 This function is used in conjunction with the 'Lists' button in masthead.inc.
558
559 =cut
560
561 sub RefreshShelvesSummary ($$$) {
562         
563         my ($sessionID, $loggedinuser, $row_count) = @_;
564         my $session = C4::Auth::get_session($sessionID);
565         my ($total, $totshelves, $barshelves, $pubshelves);
566
567         ($barshelves, $totshelves) = GetRecentShelves(1, $row_count, $loggedinuser);
568         $total->{'bartotal'} = $totshelves;
569         ($pubshelves, $totshelves) = GetRecentShelves(2, $row_count, undef);
570         $total->{'pubtotal'} = $totshelves;
571
572         # Update the current session with the latest shelves...
573         $session->param('barshelves', $barshelves);
574         $session->param('pubshelves', $pubshelves);
575         $session->param('totshelves', $total);
576
577         # likewise the userenv...
578         C4::Context->set_shelves_userenv('bar',$barshelves);
579         C4::Context->set_shelves_userenv('pub',$pubshelves);
580         C4::Context::set_shelves_userenv('tot',$total);
581
582         return ($total, $pubshelves, $barshelves);
583 }
584
585 # internal subs
586
587 sub _shelf_count ($$) {
588         my (@params) = @_;
589         # Find out how many shelves total meet the submitted criteria...
590         my $query = "SELECT count(*) FROM virtualshelves";
591         $query .= ($params[1] > 1) ? " WHERE category >= ?" : " WHERE  owner=? AND category=?";
592         shift @params if $params[1] > 1;
593         my $sth = $dbh->prepare($query);
594         $sth->execute(@params);
595         my $total = $sth->fetchrow;
596         return $total;
597 }
598
599 sub _biblionumber_sth {
600     my ($shelf) = @_;
601     my $query = 'select biblionumber from virtualshelfcontents where shelfnumber = ?';
602     my $dbh = C4::Context->dbh;
603     my $sth = $dbh->prepare($query)
604         or die $dbh->errstr;
605     $sth->execute( $shelf )
606         or die $sth->errstr;
607     $sth;
608 }
609
610 sub each_biblionumbers (&$) {
611     my ($code,$shelf) = @_;
612     my $ref =  _biblionumber_sth($shelf)->fetchall_arrayref;
613     map {
614         $_=$$_[0];
615         $code->();
616     } @$ref;
617 }
618
619 1;
620
621 __END__
622
623 =head1 AUTHOR
624
625 Koha Development Team <http://koha-community.org/>
626
627 =head1 SEE ALSO
628
629 C4::Circulation::Circ2(3)
630
631 =cut