bug 2613: allow web self-check to accept patron ID
[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 Carp;
26 use C4::Context;
27 use C4::Circulation;
28 use C4::Debug;
29 use C4::Members;
30 require C4::Auth;
31
32 use vars qw($VERSION @ISA @EXPORT @EXPORT_OK);
33
34 BEGIN {
35         # set the version for version checking
36         $VERSION = 3.02;
37         require Exporter;
38         @ISA    = qw(Exporter);
39         @EXPORT = qw(
40             &GetShelves &GetShelfContents &GetShelf
41             &AddToShelf &AddToShelfFromBiblio &AddShelf
42             &ModShelf
43             &ShelfPossibleAction
44             &DelFromShelf &DelShelf
45         );
46         @EXPORT_OK = qw(
47             &GetShelvesSummary &GetRecentShelves
48             &RefreshShelvesSummary &SetShelvesLimit
49         );
50 }
51
52
53 my $dbh = C4::Context->dbh;
54
55 =head1 NAME
56
57 C4::VirtualShelves - Functions for manipulating Koha virtual virtualshelves
58
59 =head1 SYNOPSIS
60
61   use C4::VirtualShelves;
62
63 =head1 DESCRIPTION
64
65 This module provides functions for manipulating virtual virtualshelves,
66 including creating and deleting virtualshelves, and adding and removing
67 items to and from virtualshelves.
68
69 =head1 FUNCTIONS
70
71 =over 2
72
73 =item GetShelves
74
75   ($shelflist, $totshelves) = &GetShelves($mincategory, $row_count, $offset, $owner);
76   ($shelfnumber, $shelfhash) = each %{$shelflist};
77
78 Returns the number of shelves specified by C<$row_count> and C<$offset> as well as the total
79 number of shelves that meet the C<$owner> and C<$mincategory> criteria.  C<$mincategory>,
80 C<$row_count>, and C<$offset> are required. C<$owner> must be supplied when C<$mincategory> == 1.
81 When C<$mincategory> is 2 or 3, supply undef as argument for C<$owner>.
82 C<$shelflist>is a reference-to-hash. The keys are the virtualshelves numbers (C<$shelfnumber>, above),
83 and the values (C<$shelfhash>, above) are themselves references-to-hash, with the following keys:
84
85 =over 4
86
87 =item C<$shelfhash-E<gt>{shelfname}>
88
89 A string. The name of the shelf.
90
91 =item C<$shelfhash-E<gt>{count}>
92
93 The number of virtuals on that virtualshelves.
94
95 =back
96
97 =cut
98
99 sub GetShelves ($$$$) {
100     my ($mincategory, $row_count, $offset, $owner) = @_;
101         my @params = ($owner, $mincategory, ($offset ? $offset : 0), $row_count);
102         my @params1 = ($owner, $mincategory);
103         if ($mincategory > 1) {
104                 shift @params;
105                 shift @params1;
106         }
107         my $total = _shelf_count($owner, $mincategory);
108     # grab only the shelves meeting the row_count/offset spec...
109     my $query = qq(
110         SELECT virtualshelves.shelfnumber, virtualshelves.shelfname,owner,surname,firstname,virtualshelves.category,virtualshelves.sortfield,
111                count(virtualshelfcontents.biblionumber) as count
112         FROM   virtualshelves
113             LEFT JOIN   virtualshelfcontents ON virtualshelves.shelfnumber = virtualshelfcontents.shelfnumber
114             LEFT JOIN   borrowers ON virtualshelves.owner = borrowers.borrowernumber );
115     $query .= ($mincategory == 1) ? "WHERE  owner=? AND category=?" : "WHERE category>=?";
116         $query .= qq(
117         GROUP BY virtualshelves.shelfnumber
118         ORDER BY virtualshelves.category
119                 DESC 
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 =item 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 =item 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 !$owner;
205         my $query = "SELECT * FROM virtualshelves";
206         $query .= ($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 =item 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 =item GetShelfContents
238
239   $itemlist = &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,
268                                 biblio.*, biblioitems.itemtype, itemtypes.*
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 ? ";
277                 $query .= " DESC " if ($sortfield eq 'copyrightdate');
278                 push (@params, $sortfield);
279         }
280         $query .= " LIMIT ?, ? ";
281         push (@params, ($offset ? $offset : 0));
282         push (@params, $row_count);
283     my $sth3 = $dbh->prepare($query);
284         $sth3->execute(@params);
285         return ($sth3->fetchall_arrayref({}), $total);
286         # Like the perldoc says,
287         # returns reference-to-array, where each element is reference-to-hash of the row:
288         #   like [ $sth->fetchrow_hashref(), $sth->fetchrow_hashref() ... ] 
289         # Suitable for use in TMPL_LOOP.
290         # See http://search.cpan.org/~timb/DBI-1.601/DBI.pm#fetchall_arrayref
291         # or newer, for your version of DBI.
292 }
293
294 =item AddShelf
295
296   $shelfnumber = &AddShelf( $shelfname, $owner, $category);
297
298 Creates a new virtual virtualshelves with name C<$shelfname>, owner C<$owner> and category
299 C<$category>.
300
301 Returns a code to know what's happen.
302     * -1 : if this virtualshelves already exist.
303     * $shelfnumber : if success.
304
305 =cut
306
307 sub AddShelf {
308     my ( $shelfname, $owner, $category, $sortfield ) = @_;
309     my $query = qq(
310         SELECT *
311         FROM   virtualshelves
312         WHERE  shelfname=? AND owner=?
313     );
314     my $sth = $dbh->prepare($query);
315     $sth->execute($shelfname,$owner);
316     ( $sth->rows ) and return (-1);
317     $query = qq(
318         INSERT INTO virtualshelves
319             (shelfname,owner,category,sortfield)
320         VALUES (?,?,?,?)
321     );
322     $sth = $dbh->prepare($query);
323     $sth->execute( $shelfname, $owner, $category, $sortfield );
324     my $shelfnumber = $dbh->{'mysql_insertid'};
325     return ($shelfnumber);
326 }
327
328 =item AddToShelf
329
330   &AddToShelf($biblionumber, $shelfnumber);
331
332 Adds item number C<$biblionumber> to virtual virtualshelves number
333 C<$shelfnumber>, unless that item is already on that shelf.
334
335 =cut
336
337 #'
338 sub AddToShelf {
339     my ( $biblionumber, $shelfnumber ) = @_;
340     return unless $biblionumber;
341     my $query = qq(
342         SELECT *
343         FROM   virtualshelfcontents
344         WHERE  shelfnumber=? AND biblionumber=?
345     );
346     my $sth = $dbh->prepare($query);
347
348     $sth->execute( $shelfnumber, $biblionumber );
349     ($sth->rows) and return undef;      # already on shelf
350         $query = qq(
351                 INSERT INTO virtualshelfcontents
352                         (shelfnumber, biblionumber, flags)
353                 VALUES
354                         (?, ?, 0)
355         );
356         $sth = $dbh->prepare($query);
357         $sth->execute( $shelfnumber, $biblionumber );
358         $query = qq(UPDATE virtualshelves
359                                 SET lastmodified = CURRENT_TIMESTAMP
360                                 WHERE shelfnumber = ?);
361         $sth = $dbh->prepare($query);
362         $sth->execute( $shelfnumber );
363 }
364
365 =item AddToShelfFromBiblio
366  
367     &AddToShelfFromBiblio($biblionumber, $shelfnumber)
368
369     this function allow to add a virtual into the shelf number $shelfnumber
370     from biblionumber.
371
372 =cut
373
374 sub AddToShelfFromBiblio {
375     my ( $biblionumber, $shelfnumber ) = @_;
376     return unless $biblionumber;
377     my $query = qq(
378         SELECT *
379         FROM   virtualshelfcontents
380         WHERE  shelfnumber=? AND biblionumber=?
381     );
382     my $sth = $dbh->prepare($query);
383     $sth->execute( $shelfnumber, $biblionumber );
384     unless ( $sth->rows ) {
385         my $query =qq(
386             INSERT INTO virtualshelfcontents
387                 (shelfnumber, biblionumber, flags)
388             VALUES
389                 (?, ?, 0)
390         );
391         $sth = $dbh->prepare($query);
392         $sth->execute( $shelfnumber, $biblionumber );
393                 $query = qq(UPDATE virtualshelves
394                                         SET lastmodified = CURRENT_TIMESTAMP
395                                         WHERE shelfnumber = ?);
396                 $sth = $dbh->prepare($query);
397                 $sth->execute( $shelfnumber );
398     }
399 }
400
401 =item ModShelf
402
403 ModShelf($shelfnumber, $hashref)
404
405 Where $hashref->{column} = param
406
407 Modify the value into virtualshelves table with values given 
408 from hashref, which each key of the hashref should be
409 the name of a column of virtualshelves.
410
411 =cut
412
413 sub ModShelf {
414     my $shelfnumber = shift;
415     my $shelf = shift;
416
417     if (exists $shelf->{shelfnumber}) {
418         carp "Should not use ModShelf to change shelfnumber";
419         return;
420     }
421     unless (defined $shelfnumber and $shelfnumber =~ /^\d+$/) {
422         carp "Invalid shelfnumber passed to ModShelf: $shelfnumber";
423         return;
424     }
425
426         my $query = "UPDATE virtualshelves SET ";
427     my @bind_params = ();
428     my @set_clauses = ();
429
430         foreach my $column (keys %$shelf) {
431         push @set_clauses, "$column = ?";
432         push @bind_params, $shelf->{$column};
433     }
434
435     if ($#set_clauses == -1) {
436         carp "No columns to update passed to ModShelf";
437         return;
438     }
439     $query .= join(", ", @set_clauses);
440
441     $query .= " WHERE shelfnumber = ? ";
442     push @bind_params, $shelfnumber;
443
444     $debug and warn "ModShelf query:\n $query\n",
445                         "ModShelf query args: ", join(',', @bind_params), "\n";
446         my $sth = $dbh->prepare($query);
447         $sth->execute( @bind_params );
448 }
449
450 =item ShelfPossibleAction
451
452 ShelfPossibleAction($loggedinuser, $shelfnumber, $action);
453
454 C<$loggedinuser,$shelfnumber,$action>
455
456 $action can be "view" or "manage".
457
458 Returns 1 if the user can do the $action in the $shelfnumber shelf.
459 Returns 0 otherwise.
460
461 =cut
462
463 sub ShelfPossibleAction {
464     my ( $user, $shelfnumber, $action ) = @_;
465     my $query = qq(
466         SELECT owner,category
467         FROM   virtualshelves
468         WHERE  shelfnumber=?
469     );
470     my $sth = $dbh->prepare($query);
471     $sth->execute($shelfnumber);
472     my ( $owner, $category ) = $sth->fetchrow;
473         my $borrower = GetMemberDetails($user);
474         return 1 if ( $category >= 3);                                                  # open list
475     return 1 if (($category >= 2) and
476                                 defined($action) and $action eq 'view');        # public list, anybody can view
477     return 1 if (($category >= 2) and defined($user) and ($borrower->{authflags}->{superlibrarian} || $user == 0));     # public list, superlibrarian can edit/delete
478     return 1 if (defined($user)  and $owner  eq $user );        # user owns this list.  Check last.
479     return 0;
480 }
481
482 =item DelFromShelf
483
484   &DelFromShelf( $biblionumber, $shelfnumber);
485
486 Removes item number C<$biblionumber> from virtual virtualshelves number
487 C<$shelfnumber>. If the item wasn't on that virtualshelves to begin with,
488 nothing happens.
489
490 =cut
491
492 #'
493 sub DelFromShelf {
494     my ( $biblionumber, $shelfnumber ) = @_;
495     my $query = qq(
496         DELETE FROM virtualshelfcontents
497         WHERE  shelfnumber=? AND biblionumber=?
498     );
499     my $sth = $dbh->prepare($query);
500     $sth->execute( $shelfnumber, $biblionumber );
501 }
502
503 =item DelShelf (old version)
504
505   ($status, $msg) = &DelShelf($shelfnumber);
506
507 Deletes virtual virtualshelves number C<$shelfnumber>. The virtualshelves must
508 be empty.
509
510 Returns a two-element array, where C<$status> is 0 if the operation
511 was successful, or non-zero otherwise. C<$msg> is "Done" in case of
512 success, or an error message giving the reason for failure.
513
514 =item DelShelf (current version)
515
516   $Number = DelShelf($shelfnumber);
517
518 This function deletes the shelf number, and all of it's content.
519
520 =cut
521
522 sub DelShelf {
523         unless (@_) {
524                 carp "DelShelf called without valid argument (shelfnumber)";
525                 return undef;
526         }
527         my $sth = $dbh->prepare("DELETE FROM virtualshelves WHERE shelfnumber=?");
528         return $sth->execute(shift);
529 }
530
531 =item RefreshShelvesSummary
532
533         ($total, $pubshelves, $barshelves) = RefreshShelvesSummary($sessionID, $loggedinuser, $row_count);
534
535 Updates the current session and userenv with the most recent shelves
536
537 Returns the total number of shelves stored in the session/userenv along with two references each to an
538 array of hashes, one containing the C<$loggedinuser>'s private shelves and one containing all public/open shelves.
539
540 This function is used in conjunction with the 'Lists' button in masthead.inc.
541
542 =cut
543
544 sub RefreshShelvesSummary ($$$) {
545         
546         my ($sessionID, $loggedinuser, $row_count) = @_;
547         my $session = C4::Auth::get_session($sessionID);
548         my ($total, $totshelves, $barshelves, $pubshelves);
549
550         ($barshelves, $totshelves) = GetRecentShelves(1, $row_count, $loggedinuser);
551         $total->{'bartotal'} = $totshelves;
552         ($pubshelves, $totshelves) = GetRecentShelves(2, $row_count, undef);
553         $total->{'pubtotal'} = $totshelves;
554
555         # Update the current session with the latest shelves...
556         $session->param('barshelves', $barshelves->[0]);
557         $session->param('pubshelves', $pubshelves->[0]);
558         $session->param('totshelves', $total);
559
560         # likewise the userenv...
561         C4::Context->set_shelves_userenv('bar',$barshelves->[0]);
562         C4::Context->set_shelves_userenv('pub',$pubshelves->[0]);
563         C4::Context::set_shelves_userenv('tot',$total);
564
565         return ($total, $pubshelves, $barshelves);
566 }
567
568 # internal subs
569
570 sub _shelf_count ($$) {
571         my (@params) = @_;
572         # Find out how many shelves total meet the submitted criteria...
573         my $query = "SELECT count(*) FROM virtualshelves";
574         $query .= ($params[1] > 1) ? " WHERE category >= ?" : " WHERE  owner=? AND category=?";
575         shift @params if $params[1] > 1;
576         my $sth = $dbh->prepare($query);
577         $sth->execute(@params);
578         my $total = $sth->fetchrow;
579         return $total;
580 }
581
582 1;
583
584 __END__
585
586 =back
587
588 =head1 AUTHOR
589
590 Koha Developement team <info@koha.org>
591
592 =head1 SEE ALSO
593
594 C4::Circulation::Circ2(3)
595
596 =cut