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