Convert to UTF-8.
[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 &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 " . $sortfield;
280                 $query .= " DESC " if ($sortfield eq 'copyrightdate');
281         }
282     if($row_count){
283            $query .= " LIMIT ?, ? ";
284            push (@params, ($offset ? $offset : 0));
285            push (@params, $row_count);
286     }
287     my $sth3 = $dbh->prepare($query);
288         $sth3->execute(@params);
289         return ($sth3->fetchall_arrayref({}), $total);
290         # Like the perldoc says,
291         # returns reference-to-array, where each element is reference-to-hash of the row:
292         #   like [ $sth->fetchrow_hashref(), $sth->fetchrow_hashref() ... ] 
293         # Suitable for use in TMPL_LOOP.
294         # See http://search.cpan.org/~timb/DBI-1.601/DBI.pm#fetchall_arrayref
295         # or newer, for your version of DBI.
296 }
297
298 =item AddShelf
299
300   $shelfnumber = &AddShelf( $shelfname, $owner, $category);
301
302 Creates a new virtual virtualshelves with name C<$shelfname>, owner C<$owner> and category
303 C<$category>.
304
305 Returns a code to know what's happen.
306     * -1 : if this virtualshelves already exist.
307     * $shelfnumber : if success.
308
309 =cut
310
311 sub AddShelf {
312     my ( $shelfname, $owner, $category, $sortfield ) = @_;
313     my $query = qq(
314         SELECT *
315         FROM   virtualshelves
316         WHERE  shelfname=? AND owner=?
317     );
318     my $sth = $dbh->prepare($query);
319     $sth->execute($shelfname,$owner);
320     ( $sth->rows ) and return (-1);
321     $query = qq(
322         INSERT INTO virtualshelves
323             (shelfname,owner,category,sortfield)
324         VALUES (?,?,?,?)
325     );
326     $sth = $dbh->prepare($query);
327     $sth->execute( $shelfname, $owner, $category, $sortfield );
328     my $shelfnumber = $dbh->{'mysql_insertid'};
329     return ($shelfnumber);
330 }
331
332 =item AddToShelf
333
334   &AddToShelf($biblionumber, $shelfnumber);
335
336 Adds item number C<$biblionumber> to virtual virtualshelves number
337 C<$shelfnumber>, unless that item is already on that shelf.
338
339 =cut
340
341 #'
342 sub AddToShelf {
343     my ( $biblionumber, $shelfnumber ) = @_;
344     return unless $biblionumber;
345     my $query = qq(
346         SELECT *
347         FROM   virtualshelfcontents
348         WHERE  shelfnumber=? AND biblionumber=?
349     );
350     my $sth = $dbh->prepare($query);
351
352     $sth->execute( $shelfnumber, $biblionumber );
353     ($sth->rows) and return undef;      # already on shelf
354         $query = qq(
355                 INSERT INTO virtualshelfcontents
356                         (shelfnumber, biblionumber, flags)
357                 VALUES
358                         (?, ?, 0)
359         );
360         $sth = $dbh->prepare($query);
361         $sth->execute( $shelfnumber, $biblionumber );
362         $query = qq(UPDATE virtualshelves
363                                 SET lastmodified = CURRENT_TIMESTAMP
364                                 WHERE shelfnumber = ?);
365         $sth = $dbh->prepare($query);
366         $sth->execute( $shelfnumber );
367 }
368
369 =item AddToShelfFromBiblio
370  
371     &AddToShelfFromBiblio($biblionumber, $shelfnumber)
372
373     this function allow to add a virtual into the shelf number $shelfnumber
374     from biblionumber.
375
376 =cut
377
378 sub AddToShelfFromBiblio {
379     my ( $biblionumber, $shelfnumber ) = @_;
380     return unless $biblionumber;
381     my $query = qq(
382         SELECT *
383         FROM   virtualshelfcontents
384         WHERE  shelfnumber=? AND biblionumber=?
385     );
386     my $sth = $dbh->prepare($query);
387     $sth->execute( $shelfnumber, $biblionumber );
388     unless ( $sth->rows ) {
389         my $query =qq(
390             INSERT INTO virtualshelfcontents
391                 (shelfnumber, biblionumber, flags)
392             VALUES
393                 (?, ?, 0)
394         );
395         $sth = $dbh->prepare($query);
396         $sth->execute( $shelfnumber, $biblionumber );
397                 $query = qq(UPDATE virtualshelves
398                                         SET lastmodified = CURRENT_TIMESTAMP
399                                         WHERE shelfnumber = ?);
400                 $sth = $dbh->prepare($query);
401                 $sth->execute( $shelfnumber );
402     }
403 }
404
405 =item ModShelf
406
407 ModShelf($shelfnumber, $hashref)
408
409 Where $hashref->{column} = param
410
411 Modify the value into virtualshelves table with values given 
412 from hashref, which each key of the hashref should be
413 the name of a column of virtualshelves.
414
415 =cut
416
417 sub ModShelf {
418     my $shelfnumber = shift;
419     my $shelf = shift;
420
421     if (exists $shelf->{shelfnumber}) {
422         carp "Should not use ModShelf to change shelfnumber";
423         return;
424     }
425     unless (defined $shelfnumber and $shelfnumber =~ /^\d+$/) {
426         carp "Invalid shelfnumber passed to ModShelf: $shelfnumber";
427         return;
428     }
429
430         my $query = "UPDATE virtualshelves SET ";
431     my @bind_params = ();
432     my @set_clauses = ();
433
434         foreach my $column (keys %$shelf) {
435         push @set_clauses, "$column = ?";
436         push @bind_params, $shelf->{$column};
437     }
438
439     if ($#set_clauses == -1) {
440         carp "No columns to update passed to ModShelf";
441         return;
442     }
443     $query .= join(", ", @set_clauses);
444
445     $query .= " WHERE shelfnumber = ? ";
446     push @bind_params, $shelfnumber;
447
448     $debug and warn "ModShelf query:\n $query\n",
449                         "ModShelf query args: ", join(',', @bind_params), "\n";
450         my $sth = $dbh->prepare($query);
451         $sth->execute( @bind_params );
452 }
453
454 =item ShelfPossibleAction
455
456 ShelfPossibleAction($loggedinuser, $shelfnumber, $action);
457
458 C<$loggedinuser,$shelfnumber,$action>
459
460 $action can be "view" or "manage".
461
462 Returns 1 if the user can do the $action in the $shelfnumber shelf.
463 Returns 0 otherwise.
464
465 =cut
466
467 sub ShelfPossibleAction {
468     my ( $user, $shelfnumber, $action ) = @_;
469     my $query = qq(
470         SELECT owner,category
471         FROM   virtualshelves
472         WHERE  shelfnumber=?
473     );
474     my $sth = $dbh->prepare($query);
475     $sth->execute($shelfnumber);
476     my ( $owner, $category ) = $sth->fetchrow;
477         my $borrower = GetMemberDetails($user);
478         return 0 if not defined($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