fixing SQL builded if there is no where_strings
[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 1 if ( $category >= 3);                                                  # open list
480     return 1 if (($category >= 2) and
481                                 defined($action) and $action eq 'view');        # public list, anybody can view
482     return 1 if (($category >= 2) and defined($user) and ($borrower->{authflags}->{superlibrarian} || $user == 0));     # public list, superlibrarian can edit/delete
483     return 1 if (defined($user)  and $owner  eq $user );        # user owns this list.  Check last.
484     return 0;
485 }
486
487 =item DelFromShelf
488
489   &DelFromShelf( $biblionumber, $shelfnumber);
490
491 Removes item number C<$biblionumber> from virtual virtualshelves number
492 C<$shelfnumber>. If the item wasn't on that virtualshelves to begin with,
493 nothing happens.
494
495 =cut
496
497 #'
498 sub DelFromShelf {
499     my ( $biblionumber, $shelfnumber ) = @_;
500     my $query = qq(
501         DELETE FROM virtualshelfcontents
502         WHERE  shelfnumber=? AND biblionumber=?
503     );
504     my $sth = $dbh->prepare($query);
505     $sth->execute( $shelfnumber, $biblionumber );
506 }
507
508 =item DelShelf (old version)
509
510   ($status, $msg) = &DelShelf($shelfnumber);
511
512 Deletes virtual virtualshelves number C<$shelfnumber>. The virtualshelves must
513 be empty.
514
515 Returns a two-element array, where C<$status> is 0 if the operation
516 was successful, or non-zero otherwise. C<$msg> is "Done" in case of
517 success, or an error message giving the reason for failure.
518
519 =item DelShelf (current version)
520
521   $Number = DelShelf($shelfnumber);
522
523 This function deletes the shelf number, and all of it's content.
524
525 =cut
526
527 sub DelShelf {
528         unless (@_) {
529                 carp "DelShelf called without valid argument (shelfnumber)";
530                 return undef;
531         }
532         my $sth = $dbh->prepare("DELETE FROM virtualshelves WHERE shelfnumber=?");
533         return $sth->execute(shift);
534 }
535
536 =item GetBibShelves
537
538 This finds all the public lists that this bib record is in.
539
540 =cut
541
542 sub GetBibliosShelves {
543     my ( $biblionumber )  = @_;
544     my $dbh = C4::Context->dbh;
545     my $sth = $dbh->prepare('
546         SELECT vs.shelfname, vs.shelfnumber 
547         FROM virtualshelves vs 
548         JOIN virtualshelfcontents vc ON (vs.shelfnumber= vc.shelfnumber) 
549         WHERE vs.category != 1 
550         AND vc.biblionumber= ?
551     ');
552     $sth->execute( $biblionumber );
553     return $sth->fetchall_arrayref({});
554 }
555
556 =item RefreshShelvesSummary
557
558         ($total, $pubshelves, $barshelves) = RefreshShelvesSummary($sessionID, $loggedinuser, $row_count);
559
560 Updates the current session and userenv with the most recent shelves
561
562 Returns the total number of shelves stored in the session/userenv along with two references each to an
563 array of hashes, one containing the C<$loggedinuser>'s private shelves and one containing all public/open shelves.
564
565 This function is used in conjunction with the 'Lists' button in masthead.inc.
566
567 =cut
568
569 sub RefreshShelvesSummary ($$$) {
570         
571         my ($sessionID, $loggedinuser, $row_count) = @_;
572         my $session = C4::Auth::get_session($sessionID);
573         my ($total, $totshelves, $barshelves, $pubshelves);
574
575         ($barshelves, $totshelves) = GetRecentShelves(1, $row_count, $loggedinuser);
576         $total->{'bartotal'} = $totshelves;
577         ($pubshelves, $totshelves) = GetRecentShelves(2, $row_count, undef);
578         $total->{'pubtotal'} = $totshelves;
579
580         # Update the current session with the latest shelves...
581         $session->param('barshelves', $barshelves->[0]);
582         $session->param('pubshelves', $pubshelves->[0]);
583         $session->param('totshelves', $total);
584
585         # likewise the userenv...
586         C4::Context->set_shelves_userenv('bar',$barshelves->[0]);
587         C4::Context->set_shelves_userenv('pub',$pubshelves->[0]);
588         C4::Context::set_shelves_userenv('tot',$total);
589
590         return ($total, $pubshelves, $barshelves);
591 }
592
593 # internal subs
594
595 sub _shelf_count ($$) {
596         my (@params) = @_;
597         # Find out how many shelves total meet the submitted criteria...
598         my $query = "SELECT count(*) FROM virtualshelves";
599         $query .= ($params[1] > 1) ? " WHERE category >= ?" : " WHERE  owner=? AND category=?";
600         shift @params if $params[1] > 1;
601         my $sth = $dbh->prepare($query);
602         $sth->execute(@params);
603         my $total = $sth->fetchrow;
604         return $total;
605 }
606
607 sub _biblionumber_sth {
608     my ($shelf) = @_;
609     my $query = 'select biblionumber from virtualshelfcontents where shelfnumber = ?';
610     my $dbh = C4::Context->dbh;
611     my $sth = $dbh->prepare($query)
612         or die $dbh->errstr;
613     $sth->execute( $shelf )
614         or die $sth->errstr;
615     $sth;
616 }
617
618 sub each_biblionumbers (&$) {
619     my ($code,$shelf) = @_;
620     my $ref =  _biblionumber_sth($shelf)->fetchall_arrayref;
621     map {
622         $_=$$_[0];
623         $code->();
624     } @$ref;
625 }
626
627 1;
628
629 __END__
630
631 =back
632
633 =head1 AUTHOR
634
635 Koha Developement team <info@koha.org>
636
637 =head1 SEE ALSO
638
639 C4::Circulation::Circ2(3)
640
641 =cut