Bug 4289: 'OpacPublic' feature
[koha.git] / C4 / VirtualShelves.pm
blob77ae394b1137ca8956626fd78d9eda490910e045
1 # -*- tab-width: 8 -*-
2 # Please use 8-character tabs for this file (indents are every 4 characters)
4 package C4::VirtualShelves;
7 # Copyright 2000-2002 Katipo Communications
9 # This file is part of Koha.
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.
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.
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.
24 use strict;
25 use warnings;
27 use Carp;
28 use C4::Context;
29 use C4::Circulation;
30 use C4::Debug;
31 use C4::Members;
32 require C4::Auth;
34 use vars qw($VERSION @ISA @EXPORT @EXPORT_OK);
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
49 @EXPORT_OK = qw(
50 &GetShelvesSummary &GetRecentShelves
51 &RefreshShelvesSummary &SetShelvesLimit
56 my $dbh = C4::Context->dbh;
58 =head1 NAME
60 C4::VirtualShelves - Functions for manipulating Koha virtual virtualshelves
62 =head1 SYNOPSIS
64 use C4::VirtualShelves;
66 =head1 DESCRIPTION
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.
72 =head1 FUNCTIONS
74 =head2 GetShelves
76 ($shelflist, $totshelves) = &GetShelves($mincategory, $row_count, $offset, $owner);
77 ($shelfnumber, $shelfhash) = each %{$shelflist};
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:
86 =over
88 =item C<$shelfhash-E<gt>{shelfname}>
90 A string. The name of the shelf.
92 =item C<$shelfhash-E<gt>{count}>
94 The number of virtuals on that virtualshelves.
96 =back
98 =cut
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;
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;
135 return ( \%shelflist, $total );
138 =head2 GetShelvesSummary
140 ($shelves, $total) = GetShelvesSummary($mincategory, $row_count, $offset, $owner)
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>.
147 =cut
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;
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);
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
187 =head2 GetRecentShelves
189 ($shelflist) = GetRecentShelves(1, $limit, $owner)
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>.
195 This function is intended to return a dataset reflecting the most recently active shelves for
196 the submitted parameters.
198 =cut
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 );
215 =head2 GetShelf
217 (shelfnumber,shelfname,owner,category,sortfield) = &GetShelf($shelfnumber);
219 Looks up information about the contents of virtual virtualshelves number
220 C<$shelfnumber>
222 Returns the database's information on 'virtualshelves' table.
224 =cut
226 sub GetShelf ($) {
227 my ($shelfnumber) = @_;
228 my $query = qq(
229 SELECT shelfnumber, shelfname, owner, category, sortfield
230 FROM virtualshelves
231 WHERE shelfnumber=?
233 my $sth = $dbh->prepare($query);
234 $sth->execute($shelfnumber);
235 return $sth->fetchrow;
238 =head2 GetShelfContents
240 $biblist = &GetShelfContents($shelfnumber);
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.
246 Returns a reference-to-array, whose elements are references-to-hash,
247 as returned by C<C4::Biblio::GetBiblioFromItemNumber>.
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.
254 =cut
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;
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');
280 if($row_count){
281 $query .= " LIMIT ?, ? ";
282 push (@params, ($offset ? $offset : 0));
283 push (@params, $row_count);
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.
296 =head2 AddShelf
298 $shelfnumber = &AddShelf( $shelfname, $owner, $category);
300 Creates a new virtual virtualshelves with name C<$shelfname>, owner C<$owner> and category
301 C<$category>.
303 Returns a code to know what's happen.
304 * -1 : if this virtualshelves already exist.
305 * $shelfnumber : if success.
307 =cut
309 sub AddShelf {
310 my ( $shelfname, $owner, $category, $sortfield ) = @_;
311 my $query = qq(
312 SELECT *
313 FROM virtualshelves
314 WHERE shelfname=? AND owner=?
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 (?,?,?,?)
324 $sth = $dbh->prepare($query);
325 $sth->execute( $shelfname, $owner, $category, $sortfield );
326 my $shelfnumber = $dbh->{'mysql_insertid'};
327 return ($shelfnumber);
330 =head2 AddToShelf
332 &AddToShelf($biblionumber, $shelfnumber);
334 Adds bib number C<$biblionumber> to virtual virtualshelves number
335 C<$shelfnumber>, unless that bib is already on that shelf.
337 =cut
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=?
348 my $sth = $dbh->prepare($query);
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)
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 );
367 =head2 ModShelf
369 ModShelf($shelfnumber, $hashref)
371 Where $hashref->{column} = param
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.
377 =cut
379 sub ModShelf {
380 my $shelfnumber = shift;
381 my $shelf = shift;
383 if (exists $shelf->{shelfnumber}) {
384 carp "Should not use ModShelf to change shelfnumber";
385 return;
387 unless (defined $shelfnumber and $shelfnumber =~ /^\d+$/) {
388 carp "Invalid shelfnumber passed to ModShelf: $shelfnumber";
389 return;
392 my $query = "UPDATE virtualshelves SET ";
393 my @bind_params = ();
394 my @set_clauses = ();
396 foreach my $column (keys %$shelf) {
397 push @set_clauses, "$column = ?";
398 push @bind_params, $shelf->{$column};
401 if ($#set_clauses == -1) {
402 carp "No columns to update passed to ModShelf";
403 return;
405 $query .= join(", ", @set_clauses);
407 $query .= " WHERE shelfnumber = ? ";
408 push @bind_params, $shelfnumber;
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 );
416 =head2 ShelfPossibleAction
418 ShelfPossibleAction($loggedinuser, $shelfnumber, $action);
420 C<$loggedinuser,$shelfnumber,$action>
422 $action can be "view" or "manage".
424 Returns 1 if the user can do the $action in the $shelfnumber shelf.
425 Returns 0 otherwise.
427 =cut
429 sub ShelfPossibleAction {
430 my ( $user, $shelfnumber, $action ) = @_;
431 my $query = qq(
432 SELECT owner,category
433 FROM virtualshelves
434 WHERE shelfnumber=?
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;
449 =head2 DelFromShelf
451 &DelFromShelf( $biblionumber, $shelfnumber);
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.
457 =cut
460 sub DelFromShelf {
461 my ( $biblionumber, $shelfnumber ) = @_;
462 my $query = qq(
463 DELETE FROM virtualshelfcontents
464 WHERE shelfnumber=? AND biblionumber=?
466 my $sth = $dbh->prepare($query);
467 $sth->execute( $shelfnumber, $biblionumber );
470 =head2 DelShelf (old version)
472 ($status, $msg) = &DelShelf($shelfnumber);
474 Deletes virtual virtualshelves number C<$shelfnumber>. The virtualshelves must
475 be empty.
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.
481 =head2 DelShelf (current version)
483 $Number = DelShelf($shelfnumber);
485 This function deletes the shelf number, and all of it's content.
487 =cut
489 sub DelShelf {
490 unless (@_) {
491 carp "DelShelf called without valid argument (shelfnumber)";
492 return undef;
494 my $sth = $dbh->prepare("DELETE FROM virtualshelves WHERE shelfnumber=?");
495 return $sth->execute(shift);
498 =head2 GetBibShelves
500 This finds all the public lists that this bib record is in.
502 =cut
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= ?
514 $sth->execute( $biblionumber );
515 return $sth->fetchall_arrayref({});
518 =head2 RefreshShelvesSummary
520 ($total, $pubshelves, $barshelves) = RefreshShelvesSummary($sessionID, $loggedinuser, $row_count);
522 Updates the current session and userenv with the most recent shelves
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.
527 This function is used in conjunction with the 'Lists' button in masthead.inc.
529 =cut
531 sub RefreshShelvesSummary ($$$) {
533 my ($sessionID, $loggedinuser, $row_count) = @_;
534 my $session = C4::Auth::get_session($sessionID);
535 my ($total, $totshelves, $barshelves, $pubshelves);
537 ($barshelves, $totshelves) = GetRecentShelves(1, $row_count, $loggedinuser);
538 $total->{'bartotal'} = $totshelves;
539 ($pubshelves, $totshelves) = GetRecentShelves(2, $row_count, undef);
540 $total->{'pubtotal'} = $totshelves;
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);
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);
552 return ($total, $pubshelves, $barshelves);
555 # internal subs
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;
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;
580 sub each_biblionumbers (&$) {
581 my ($code,$shelf) = @_;
582 my $ref = _biblionumber_sth($shelf)->fetchall_arrayref;
583 map {
584 $_=$$_[0];
585 $code->();
586 } @$ref;
591 __END__
593 =head1 AUTHOR
595 Koha Development Team <http://koha-community.org/>
597 =head1 SEE ALSO
599 C4::Circulation::Circ2(3)
601 =cut