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