Bug 5526: Follow up patch removing a warn statement
[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
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         $shelflist{$shelfnumber}->{'sortfield'} = $sortfield;
129         $shelflist{$shelfnumber}->{'category'}  = $category;
130         $shelflist{$shelfnumber}->{'owner'}     = $owner;
131         $shelflist{$shelfnumber}->{'surname'}   = $surname;
132         $shelflist{$shelfnumber}->{'firstname'} = $firstname;
133     }
134     return ( \%shelflist, $total );
135 }
136
137 =head2 GetShelvesSummary
138
139         ($shelves, $total) = GetShelvesSummary($mincategory, $row_count, $offset, $owner)
140
141 Returns the number of shelves specified by C<$row_count> and C<$offset> as well as the total
142 number of shelves that meet the C<$owner> and/or C<$mincategory> criteria. C<$mincategory>,
143 C<$row_count>, and C<$offset> are required. C<$owner> must be supplied when C<$mincategory> == 1.
144 When C<$mincategory> is 2 or 3, supply undef as argument for C<$owner>.
145
146 =cut
147
148 sub GetShelvesSummary ($$$$) {
149     my ($mincategory, $row_count, $offset, $owner) = @_;
150         my @params = ($owner, $mincategory, ($offset ? $offset : 0), $row_count);
151         my @params1 = ($owner, $mincategory);
152         if ($mincategory > 1) {
153                 shift @params;
154                 shift @params1;
155         }
156         my $total = _shelf_count($owner, $mincategory);
157     # grab only the shelves meeting the row_count/offset spec...
158         my $query = qq(
159                 SELECT
160                         virtualshelves.shelfnumber,
161                         virtualshelves.shelfname,
162                         owner,
163                         CONCAT(firstname, ' ', surname) AS name,
164                         virtualshelves.category,
165                         count(virtualshelfcontents.biblionumber) AS count
166                 FROM   virtualshelves
167                         LEFT JOIN  virtualshelfcontents ON virtualshelves.shelfnumber = virtualshelfcontents.shelfnumber
168                         LEFT JOIN             borrowers ON virtualshelves.owner = borrowers.borrowernumber );
169     $query .= ($mincategory == 1) ? "WHERE  owner=? AND category=?" : "WHERE category>=?";
170         $query .= qq(
171                 GROUP BY virtualshelves.shelfnumber
172                 ORDER BY virtualshelves.category
173                 DESC 
174                 LIMIT ?, ?);
175         my $sth2 = $dbh->prepare($query);
176         $sth2->execute(@params);
177     my $shelves = $sth2->fetchall_arrayref({});
178     return ($shelves, $total);
179
180         # Probably NOT the final implementation since it is still bulky (repeated hash keys).
181         # might like an array of rows of delimited values:
182         # 1|2||0|blacklist|112
183         # 2|6|Josh Ferraro|51|en_fuego|106
184 }
185
186 =head2 GetRecentShelves
187
188         ($shelflist) = GetRecentShelves(1, $limit, $owner)
189
190 This function returns a references to an array of hashrefs containing specified shelves sorted
191 by the date the shelf was last modified in descending order limited to the number of records
192 specified by C<$row_count>. If calling with C<$mincategory> other than 1, use undef as C<$owner>.
193
194 This function is intended to return a dataset reflecting the most recently active shelves for
195 the submitted parameters.
196
197 =cut
198
199 sub GetRecentShelves ($$$) {
200         my ($mincategory, $row_count, $owner) = @_;
201     my (@shelflist);
202         my $total = _shelf_count($owner, $mincategory);
203         my @params = ($owner, $mincategory, 0, $row_count);      #FIXME: offset is hardcoded here, but could be passed in for enhancements
204         shift @params if (not defined $owner);
205         my $query = "SELECT * FROM virtualshelves";
206         $query .= ((defined $owner) ? " WHERE owner = ? AND category = ?" : " WHERE category >= ? ");
207         $query .= " ORDER BY lastmodified DESC LIMIT ?, ?";
208         my $sth = $dbh->prepare($query);
209         $sth->execute(@params);
210         @shelflist = $sth->fetchall_arrayref({});
211         return ( \@shelflist, $total );
212 }
213
214 =head2 GetShelf
215
216   (shelfnumber,shelfname,owner,category,sortfield) = &GetShelf($shelfnumber);
217
218 Looks up information about the contents of virtual virtualshelves number
219 C<$shelfnumber>
220
221 Returns the database's information on 'virtualshelves' table.
222
223 =cut
224
225 sub GetShelf ($) {
226     my ($shelfnumber) = @_;
227     my $query = qq(
228         SELECT shelfnumber, shelfname, owner, category, sortfield
229         FROM   virtualshelves
230         WHERE  shelfnumber=?
231     );
232     my $sth = $dbh->prepare($query);
233     $sth->execute($shelfnumber);
234     return $sth->fetchrow;
235 }
236
237 =head2 GetShelfContents
238
239   $biblist = &GetShelfContents($shelfnumber);
240
241 Looks up information about the contents of virtual virtualshelves number
242 C<$shelfnumber>.  Sorted by a field in the biblio table.  copyrightdate 
243 gives a desc sort.
244
245 Returns a reference-to-array, whose elements are references-to-hash,
246 as returned by C<C4::Biblio::GetBiblioFromItemNumber>.
247
248 Note: the notforloan status comes from the itemtype, and where it equals 0
249 it does not ensure that related items.notforloan status is likewise 0. The
250 caller has to check any items on their own, possibly with CanBookBeIssued
251 from C4::Circulation.
252
253 =cut
254
255 sub GetShelfContents ($;$$$) {
256     my ($shelfnumber, $row_count, $offset, $sortfield) = @_;
257     my $dbh=C4::Context->dbh();
258         my $sth1 = $dbh->prepare("SELECT count(*) FROM virtualshelfcontents WHERE shelfnumber = ?");
259         $sth1->execute($shelfnumber);
260         my $total = $sth1->fetchrow;
261         if(!$sortfield) {
262                 my $sth2 = $dbh->prepare('SELECT sortfield FROM virtualshelves WHERE shelfnumber=?');
263                 $sth2->execute($shelfnumber);
264                 ($sortfield) = $sth2->fetchrow_array;
265         }
266     my $query =
267        " SELECT vc.biblionumber, vc.shelfnumber, vc.dateadded, itemtypes.*,
268                                 biblio.*, biblioitems.itemtype, biblioitems.publicationyear
269          FROM   virtualshelfcontents vc
270                  LEFT JOIN biblio      ON      vc.biblionumber =      biblio.biblionumber
271                  LEFT JOIN biblioitems ON  biblio.biblionumber = biblioitems.biblionumber
272                  LEFT JOIN itemtypes   ON biblioitems.itemtype = itemtypes.itemtype
273          WHERE  vc.shelfnumber=? ";
274         my @params = ($shelfnumber);
275         if($sortfield) {
276                 $query .= " ORDER BY " . $sortfield;
277                 $query .= " DESC " if ($sortfield eq 'copyrightdate');
278         }
279     if($row_count){
280            $query .= " LIMIT ?, ? ";
281            push (@params, ($offset ? $offset : 0));
282            push (@params, $row_count);
283     }
284     my $sth3 = $dbh->prepare($query);
285         $sth3->execute(@params);
286         return ($sth3->fetchall_arrayref({}), $total);
287         # Like the perldoc says,
288         # returns reference-to-array, where each element is reference-to-hash of the row:
289         #   like [ $sth->fetchrow_hashref(), $sth->fetchrow_hashref() ... ] 
290         # Suitable for use in TMPL_LOOP.
291         # See http://search.cpan.org/~timb/DBI-1.601/DBI.pm#fetchall_arrayref
292         # or newer, for your version of DBI.
293 }
294
295 =head2 AddShelf
296
297   $shelfnumber = &AddShelf( $shelfname, $owner, $category);
298
299 Creates a new virtual virtualshelves with name C<$shelfname>, owner C<$owner> and category
300 C<$category>.
301
302 Returns a code to know what's happen.
303     * -1 : if this virtualshelves already exist.
304     * $shelfnumber : if success.
305
306 =cut
307
308 sub AddShelf {
309     my ( $shelfname, $owner, $category, $sortfield ) = @_;
310     my $query = qq(
311         SELECT *
312         FROM   virtualshelves
313         WHERE  shelfname=? AND owner=?
314     );
315     my $sth = $dbh->prepare($query);
316     $sth->execute($shelfname,$owner);
317     ( $sth->rows ) and return (-1);
318     $query = qq(
319         INSERT INTO virtualshelves
320             (shelfname,owner,category,sortfield)
321         VALUES (?,?,?,?)
322     );
323     $sth = $dbh->prepare($query);
324     $sth->execute( $shelfname, $owner, $category, $sortfield );
325     my $shelfnumber = $dbh->{'mysql_insertid'};
326     return ($shelfnumber);
327 }
328
329 =head2 AddToShelf
330
331   &AddToShelf($biblionumber, $shelfnumber);
332
333 Adds bib number C<$biblionumber> to virtual virtualshelves number
334 C<$shelfnumber>, unless that bib is already on that shelf.
335
336 =cut
337
338 #'
339 sub AddToShelf {
340     my ( $biblionumber, $shelfnumber ) = @_;
341     return unless $biblionumber;
342     my $query = qq(
343         SELECT *
344         FROM   virtualshelfcontents
345         WHERE  shelfnumber=? AND biblionumber=?
346     );
347     my $sth = $dbh->prepare($query);
348
349     $sth->execute( $shelfnumber, $biblionumber );
350     ($sth->rows) and return undef;      # already on shelf
351         $query = qq(
352                 INSERT INTO virtualshelfcontents
353                         (shelfnumber, biblionumber, flags)
354                 VALUES
355                         (?, ?, 0)
356         );
357         $sth = $dbh->prepare($query);
358         $sth->execute( $shelfnumber, $biblionumber );
359         $query = qq(UPDATE virtualshelves
360                                 SET lastmodified = CURRENT_TIMESTAMP
361                                 WHERE shelfnumber = ?);
362         $sth = $dbh->prepare($query);
363         $sth->execute( $shelfnumber );
364 }
365
366 =head2 ModShelf
367
368 ModShelf($shelfnumber, $hashref)
369
370 Where $hashref->{column} = param
371
372 Modify the value into virtualshelves table with values given 
373 from hashref, which each key of the hashref should be
374 the name of a column of virtualshelves.
375
376 =cut
377
378 sub ModShelf {
379     my $shelfnumber = shift;
380     my $shelf = shift;
381
382     if (exists $shelf->{shelfnumber}) {
383         carp "Should not use ModShelf to change shelfnumber";
384         return;
385     }
386     unless (defined $shelfnumber and $shelfnumber =~ /^\d+$/) {
387         carp "Invalid shelfnumber passed to ModShelf: $shelfnumber";
388         return;
389     }
390
391         my $query = "UPDATE virtualshelves SET ";
392     my @bind_params = ();
393     my @set_clauses = ();
394
395         foreach my $column (keys %$shelf) {
396         push @set_clauses, "$column = ?";
397         push @bind_params, $shelf->{$column};
398     }
399
400     if ($#set_clauses == -1) {
401         carp "No columns to update passed to ModShelf";
402         return;
403     }
404     $query .= join(", ", @set_clauses);
405
406     $query .= " WHERE shelfnumber = ? ";
407     push @bind_params, $shelfnumber;
408
409     $debug and warn "ModShelf query:\n $query\n",
410                         "ModShelf query args: ", join(',', @bind_params), "\n";
411         my $sth = $dbh->prepare($query);
412         $sth->execute( @bind_params );
413 }
414
415 =head2 ShelfPossibleAction
416
417 ShelfPossibleAction($loggedinuser, $shelfnumber, $action);
418
419 C<$loggedinuser,$shelfnumber,$action>
420
421 $action can be "view" or "manage".
422
423 Returns 1 if the user can do the $action in the $shelfnumber shelf.
424 Returns 0 otherwise.
425
426 =cut
427
428 sub ShelfPossibleAction {
429     my ( $user, $shelfnumber, $action ) = @_;
430     my $query = qq(
431         SELECT owner,category
432         FROM   virtualshelves
433         WHERE  shelfnumber=?
434     );
435     my $sth = $dbh->prepare($query);
436     $sth->execute($shelfnumber);
437     my ( $owner, $category ) = $sth->fetchrow;
438         my $borrower = GetMemberDetails($user);
439         return 0 if not defined($user);
440         return 1 if ( $category >= 3);                                                  # open list
441     return 1 if (($category >= 2) and
442                                 defined($action) and $action eq 'view');        # public list, anybody can view
443     return 1 if (($category >= 2) and defined($user) and ($borrower->{authflags}->{superlibrarian} || $user == 0));     # public list, superlibrarian can edit/delete
444     return 1 if (defined($user)  and $owner  eq $user );        # user owns this list.  Check last.
445     return 0;
446 }
447
448 =head2 DelFromShelf
449
450   &DelFromShelf( $biblionumber, $shelfnumber);
451
452 Removes bib number C<$biblionumber> from virtual virtualshelves number
453 C<$shelfnumber>. If the bib wasn't on that virtualshelves to begin with,
454 nothing happens.
455
456 =cut
457
458 #'
459 sub DelFromShelf {
460     my ( $biblionumber, $shelfnumber ) = @_;
461     my $query = qq(
462         DELETE FROM virtualshelfcontents
463         WHERE  shelfnumber=? AND biblionumber=?
464     );
465     my $sth = $dbh->prepare($query);
466     $sth->execute( $shelfnumber, $biblionumber );
467 }
468
469 =head2 DelShelf (old version)
470
471   ($status, $msg) = &DelShelf($shelfnumber);
472
473 Deletes virtual virtualshelves number C<$shelfnumber>. The virtualshelves must
474 be empty.
475
476 Returns a two-element array, where C<$status> is 0 if the operation
477 was successful, or non-zero otherwise. C<$msg> is "Done" in case of
478 success, or an error message giving the reason for failure.
479
480 =head2 DelShelf (current version)
481
482   $Number = DelShelf($shelfnumber);
483
484 This function deletes the shelf number, and all of it's content.
485
486 =cut
487
488 sub DelShelf {
489         unless (@_) {
490                 carp "DelShelf called without valid argument (shelfnumber)";
491                 return undef;
492         }
493         my $sth = $dbh->prepare("DELETE FROM virtualshelves WHERE shelfnumber=?");
494         return $sth->execute(shift);
495 }
496
497 =head2 GetBibShelves
498
499 This finds all the public lists that this bib record is in.
500
501 =cut
502
503 sub GetBibliosShelves {
504     my ( $biblionumber )  = @_;
505     my $dbh = C4::Context->dbh;
506     my $sth = $dbh->prepare('
507         SELECT vs.shelfname, vs.shelfnumber 
508         FROM virtualshelves vs 
509         JOIN virtualshelfcontents vc ON (vs.shelfnumber= vc.shelfnumber) 
510         WHERE vs.category != 1 
511         AND vc.biblionumber= ?
512     ');
513     $sth->execute( $biblionumber );
514     return $sth->fetchall_arrayref({});
515 }
516
517 =head2 RefreshShelvesSummary
518
519         ($total, $pubshelves, $barshelves) = RefreshShelvesSummary($sessionID, $loggedinuser, $row_count);
520
521 Updates the current session and userenv with the most recent shelves
522
523 Returns the total number of shelves stored in the session/userenv along with two references each to an
524 array of hashes, one containing the C<$loggedinuser>'s private shelves and one containing all public/open shelves.
525
526 This function is used in conjunction with the 'Lists' button in masthead.inc.
527
528 =cut
529
530 sub RefreshShelvesSummary ($$$) {
531         
532         my ($sessionID, $loggedinuser, $row_count) = @_;
533         my $session = C4::Auth::get_session($sessionID);
534         my ($total, $totshelves, $barshelves, $pubshelves);
535
536         ($barshelves, $totshelves) = GetRecentShelves(1, $row_count, $loggedinuser);
537         $total->{'bartotal'} = $totshelves;
538         ($pubshelves, $totshelves) = GetRecentShelves(2, $row_count, undef);
539         $total->{'pubtotal'} = $totshelves;
540
541         # Update the current session with the latest shelves...
542         $session->param('barshelves', $barshelves->[0]);
543         $session->param('pubshelves', $pubshelves->[0]);
544         $session->param('totshelves', $total);
545
546         # likewise the userenv...
547         C4::Context->set_shelves_userenv('bar',$barshelves->[0]);
548         C4::Context->set_shelves_userenv('pub',$pubshelves->[0]);
549         C4::Context::set_shelves_userenv('tot',$total);
550
551         return ($total, $pubshelves, $barshelves);
552 }
553
554 # internal subs
555
556 sub _shelf_count ($$) {
557         my (@params) = @_;
558         # Find out how many shelves total meet the submitted criteria...
559         my $query = "SELECT count(*) FROM virtualshelves";
560         $query .= ($params[1] > 1) ? " WHERE category >= ?" : " WHERE  owner=? AND category=?";
561         shift @params if $params[1] > 1;
562         my $sth = $dbh->prepare($query);
563         $sth->execute(@params);
564         my $total = $sth->fetchrow;
565         return $total;
566 }
567
568 sub _biblionumber_sth {
569     my ($shelf) = @_;
570     my $query = 'select biblionumber from virtualshelfcontents where shelfnumber = ?';
571     my $dbh = C4::Context->dbh;
572     my $sth = $dbh->prepare($query)
573         or die $dbh->errstr;
574     $sth->execute( $shelf )
575         or die $sth->errstr;
576     $sth;
577 }
578
579 sub each_biblionumbers (&$) {
580     my ($code,$shelf) = @_;
581     my $ref =  _biblionumber_sth($shelf)->fetchall_arrayref;
582     map {
583         $_=$$_[0];
584         $code->();
585     } @$ref;
586 }
587
588 1;
589
590 __END__
591
592 =head1 AUTHOR
593
594 Koha Development Team <http://koha-community.org/>
595
596 =head1 SEE ALSO
597
598 C4::Circulation::Circ2(3)
599
600 =cut