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