Bug 13969: Replace calls to $sth->fetchrow* with a call to $dbh->selectrow* and Clean...
[koha.git] / C4 / VirtualShelves.pm
1 package C4::VirtualShelves;
2
3 # Copyright 2000-2002 Katipo Communications
4 #
5 # This file is part of Koha.
6 #
7 # Koha is free software; you can redistribute it and/or modify it
8 # under the terms of the GNU General Public License as published by
9 # the Free Software Foundation; either version 3 of the License, or
10 # (at your option) any later version.
11 #
12 # Koha is distributed in the hope that it will be useful, but
13 # WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU General Public License for more details.
16 #
17 # You should have received a copy of the GNU General Public License
18 # along with Koha; if not, see <http://www.gnu.org/licenses>.
19
20 use strict;
21 use warnings;
22
23 use Carp;
24 use C4::Context;
25 use C4::Debug;
26 use C4::Members;
27
28 use constant SHELVES_MASTHEAD_MAX => 10; #number under Lists button in masthead
29 use constant SHELVES_COMBO_MAX => 10; #add to combo in search
30 use constant SHELVES_MGRPAGE_MAX => 20; #managing page
31 use constant SHELVES_POPUP_MAX => 40; #addbybiblio popup
32
33 use constant SHARE_INVITATION_EXPIRY_DAYS => 14; #two weeks to accept
34
35 use vars qw($VERSION @ISA @EXPORT @EXPORT_OK);
36
37 BEGIN {
38     # set the version for version checking
39     $VERSION = 3.07.00.049;
40     require Exporter;
41     @ISA    = qw(Exporter);
42     @EXPORT = qw(
43             &GetShelves &GetShelfContents &GetShelf
44             &AddToShelf &AddShelf
45             &ModShelf
46             &ShelfPossibleAction
47             &DelFromShelf &DelShelf
48             &GetBibliosShelves
49             &AddShare &AcceptShare &RemoveShare &IsSharedList
50     );
51         @EXPORT_OK = qw(
52             &GetAllShelves &ShelvesMax
53         );
54 }
55
56
57 =head1 NAME
58
59 C4::VirtualShelves - Functions for manipulating Koha virtual shelves
60
61 =head1 SYNOPSIS
62
63   use C4::VirtualShelves;
64
65 =head1 DESCRIPTION
66
67 This module provides functions for manipulating virtual shelves,
68 including creating and deleting virtual shelves, and adding and removing
69 bibs to and from virtual shelves.
70
71 =head1 FUNCTIONS
72
73 =head2 GetShelves
74
75   $shelflist = &GetShelves($category, $row_count, $offset, $owner);
76   ($shelfnumber, $shelfhash) = each %{$shelflist};
77
78 Returns the number of shelves specified by C<$row_count> and C<$offset> as well as the total
79 number of shelves that meet the C<$owner> and C<$category> criteria.  C<$category>,
80 C<$row_count>, and C<$offset> are required. C<$owner> must be supplied when C<$category> == 1.
81 When C<$category> is 2, supply undef as argument for C<$owner>.
82
83 This function is used by shelfpage in VirtualShelves/Page.pm when listing all shelves for lists management in opac or staff client. Order is by shelfname.
84
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
89
90 =item C<$shelfhash-E<gt>{shelfname}>
91
92 A string. The name of the shelf.
93
94 =back
95
96 =cut
97
98 sub GetShelves {
99     my ($category, $row_count, $offset, $owner) = @_;
100     $offset ||= 0;
101     my @params = ( $offset, $row_count );
102     my $dbh = C4::Context->dbh;
103     my $query = qq{
104         SELECT vs.shelfnumber, vs.shelfname,vs.owner,
105         bo.surname,bo.firstname,vs.category,vs.sortfield,
106         count(vc.biblionumber) as count
107         FROM virtualshelves vs
108         LEFT JOIN borrowers bo ON vs.owner=bo.borrowernumber
109         LEFT JOIN virtualshelfcontents vc USING (shelfnumber) };
110     if($category==1) {
111         $query.= qq{
112             LEFT JOIN virtualshelfshares sh ON sh.shelfnumber=vs.shelfnumber
113             AND sh.borrowernumber=?
114         WHERE category=1 AND (vs.owner=? OR sh.borrowernumber=?) };
115         unshift @params, ($owner) x 3;
116     }
117     else {
118         $query.= 'WHERE category=2 ';
119     }
120     $query.= qq{
121         GROUP BY vs.shelfnumber
122         ORDER BY vs.shelfname
123         LIMIT ?, ?};
124
125     my $sth2 = $dbh->prepare($query);
126     $sth2->execute(@params);
127     my %shelflist;
128     while( my ($shelfnumber, $shelfname, $owner, $surname, $firstname, $category, $sortfield, $count)= $sth2->fetchrow) {
129         $shelflist{$shelfnumber}->{'shelfname'} = $shelfname;
130         $shelflist{$shelfnumber}->{'count'}     = $count;
131         $shelflist{$shelfnumber}->{'single'}    = $count==1;
132         $shelflist{$shelfnumber}->{'sortfield'} = $sortfield;
133         $shelflist{$shelfnumber}->{'category'}  = $category;
134         $shelflist{$shelfnumber}->{'owner'}     = $owner;
135         $shelflist{$shelfnumber}->{'surname'}   = $surname;
136         $shelflist{$shelfnumber}->{'firstname'} = $firstname;
137     }
138     return \%shelflist;
139 }
140
141 =head2 GetAllShelves
142
143     $shelflist = GetAllShelves($category, $owner)
144
145 This function returns a reference to an array of hashrefs containing all shelves
146 sorted by the shelf name.
147
148 This function is intended to return a dataset reflecting all the shelves for
149 the submitted parameters.
150
151 =cut
152
153 sub GetAllShelves {
154     my ($category,$owner,$adding_allowed) = @_;
155     my @params;
156     my $dbh = C4::Context->dbh;
157     my $query = 'SELECT vs.* FROM virtualshelves vs ';
158     if($category==1) {
159         $query.= qq{
160             LEFT JOIN virtualshelfshares sh ON sh.shelfnumber=vs.shelfnumber
161             AND sh.borrowernumber=?
162         WHERE category=1 AND (vs.owner=? OR sh.borrowernumber=?) };
163         @params = ($owner, $owner, $owner);
164     }
165     else {
166     $query.='WHERE category=2 ';
167         @params = ();
168     }
169     $query.='AND (allow_add=1 OR owner=?) ' if $adding_allowed;
170     push @params, $owner if $adding_allowed;
171     $query.= 'ORDER BY shelfname ASC';
172     my $sth = $dbh->prepare( $query );
173     $sth->execute(@params);
174     return $sth->fetchall_arrayref({});
175 }
176
177 =head2 GetSomeShelfNames
178
179 Returns shelf names and numbers for Add to combo of search results and Lists button of OPAC header.
180
181 =cut
182
183 sub GetSomeShelfNames {
184     my ($owner, $purpose, $adding_allowed)= @_;
185     my ($bar, $pub, @params);
186     my $dbh = C4::Context->dbh;
187
188     my $bquery = 'SELECT vs.shelfnumber, vs.shelfname FROM virtualshelves vs ';
189     my $limit= ShelvesMax($purpose);
190
191     my $qry1= $bquery."WHERE vs.category=2 ";
192     $qry1.= "AND (allow_add=1 OR owner=?) " if $adding_allowed;
193     push @params, $owner||0 if $adding_allowed;
194     $qry1.= "ORDER BY vs.lastmodified DESC LIMIT $limit";
195
196     unless($adding_allowed && (!defined($owner) || $owner<=0)) {
197         #if adding items, user should be known
198         $pub= $dbh->selectall_arrayref($qry1,{Slice=>{}},@params);
199     }
200
201     if($owner) {
202         my $qry2= $bquery. qq{
203             LEFT JOIN virtualshelfshares sh ON sh.shelfnumber=vs.shelfnumber AND sh.borrowernumber=?
204             WHERE vs.category=1 AND (vs.owner=? OR sh.borrowernumber=?) };
205         @params=($owner,$owner,$owner);
206         $qry2.= "AND (allow_add=1 OR owner=?) " if $adding_allowed;
207         push @params, $owner if $adding_allowed;
208         $qry2.= "ORDER BY vs.lastmodified DESC ";
209         $qry2.= "LIMIT $limit";
210         $bar= $dbh->selectall_arrayref($qry2,{Slice=>{}},@params);
211     }
212
213     return ( { bartotal => $bar? scalar @$bar: 0, pubtotal => $pub? scalar @$pub: 0}, $pub, $bar);
214 }
215
216 =head2 GetShelf
217
218   (shelfnumber,shelfname,owner,category,sortfield,allow_add,allow_delete_own,allow_delete_other) = &GetShelf($shelfnumber);
219
220 Returns the above-mentioned fields for passed virtual shelf number.
221
222 =cut
223
224 sub GetShelf {
225     my ($shelfnumber) = @_;
226     my $dbh = C4::Context->dbh;
227     my $query = qq(
228         SELECT shelfnumber, shelfname, owner, category, sortfield,
229             allow_add, allow_delete_own, allow_delete_other
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, $sort_direction ) = @_;
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 DISTINCT vc.biblionumber, vc.shelfnumber, vc.dateadded, itemtypes.*,
269             biblio.*, biblioitems.itemtype, biblioitems.publicationyear as year, biblioitems.publishercode, biblioitems.place, biblioitems.size, biblioitems.pages
270          FROM   virtualshelfcontents vc
271          JOIN biblio      ON      vc.biblionumber =      biblio.biblionumber
272          LEFT JOIN biblioitems ON  biblio.biblionumber = biblioitems.biblionumber
273          LEFT JOIN items ON items.biblionumber=vc.biblionumber
274          LEFT JOIN itemtypes   ON biblioitems.itemtype = itemtypes.itemtype
275          WHERE  vc.shelfnumber=? ";
276     my @params = ($shelfnumber);
277     if($sortfield) {
278         $query .= " ORDER BY " . $dbh->quote_identifier( $sortfield );
279         $query .= " DESC " if ( $sort_direction eq 'desc' );
280     }
281     if($row_count){
282        $query .= " LIMIT ?, ? ";
283        push (@params, ($offset ? $offset : 0));
284        push (@params, $row_count);
285     }
286     my $sth3 = $dbh->prepare($query);
287     $sth3->execute(@params);
288     return ($sth3->fetchall_arrayref({}), $total);
289     # Like the perldoc says,
290     # returns reference-to-array, where each element is reference-to-hash of the row:
291     #   like [ $sth->fetchrow_hashref(), $sth->fetchrow_hashref() ... ]
292     # Suitable for use in TMPL_LOOP.
293     # See http://search.cpan.org/~timb/DBI-1.601/DBI.pm#fetchall_arrayref
294     # or newer, for your version of DBI.
295 }
296
297 =head2 AddShelf
298
299   $shelfnumber = &AddShelf($hashref, $owner);
300
301 Creates a new virtual shelf. Params passed in a hash like ModShelf.
302
303 Returns a code to know what's happen.
304     * -1 : if this virtualshelves already exists.
305     * $shelfnumber : if success.
306
307 =cut
308
309 sub AddShelf {
310     my ($hashref, $owner)= @_;
311     my $dbh = C4::Context->dbh;
312
313     #initialize missing hash values to silence warnings
314     foreach('shelfname','category', 'sortfield', 'allow_add', 'allow_delete_own', 'allow_delete_other' ) {
315         $hashref->{$_}= undef unless exists $hashref->{$_};
316     }
317
318     return -1 unless _CheckShelfName($hashref->{shelfname}, $hashref->{category}, $owner, 0);
319
320     my $query = q|INSERT INTO virtualshelves
321         (shelfname,owner,category,sortfield,allow_add,allow_delete_own,allow_delete_other, created_on)
322         VALUES (?,?,?,?,?,?,?, NOW())
323     |;
324
325     my $sth = $dbh->prepare($query);
326     $sth->execute(
327         $hashref->{shelfname},
328         $owner,
329         $hashref->{category},
330         $hashref->{sortfield},
331         $hashref->{allow_add}//0,
332         $hashref->{allow_delete_own}//1,
333         $hashref->{allow_delete_other}//0 );
334     return if $sth->err;
335     my $shelfnumber = $dbh->{'mysql_insertid'};
336     return $shelfnumber;
337 }
338
339 =head2 AddToShelf
340
341   &AddToShelf($biblionumber, $shelfnumber, $borrower);
342
343 Adds bib number C<$biblionumber> to virtual virtualshelves number
344 C<$shelfnumber>, unless that bib is already on that shelf.
345
346 =cut
347
348 sub AddToShelf {
349     my ($biblionumber, $shelfnumber, $borrowernumber) = @_;
350     return unless $biblionumber;
351     my $dbh = C4::Context->dbh;
352     my $query = qq(
353         SELECT *
354         FROM   virtualshelfcontents
355         WHERE  shelfnumber=? AND biblionumber=?
356     );
357     my $sth = $dbh->prepare($query);
358
359     $sth->execute( $shelfnumber, $biblionumber );
360     ($sth->rows) and return; # already on shelf
361     $query = qq(
362         INSERT INTO virtualshelfcontents
363             (shelfnumber, biblionumber, flags, borrowernumber)
364         VALUES (?, ?, 0, ?));
365     $sth = $dbh->prepare($query);
366     $sth->execute( $shelfnumber, $biblionumber, $borrowernumber);
367     $query = qq(UPDATE virtualshelves
368                 SET lastmodified = CURRENT_TIMESTAMP
369                 WHERE shelfnumber = ?);
370     $sth = $dbh->prepare($query);
371     $sth->execute( $shelfnumber );
372 }
373
374 =head2 ModShelf
375
376 my $result= ModShelf($shelfnumber, $hashref)
377
378 Where $hashref->{column} = param
379
380 Modify the value into virtualshelves table with values given 
381 from hashref, which each key of the hashref should be
382 the name of a column of virtualshelves.
383 Fields like shelfnumber or owner cannot be changed.
384
385 Returns 1 if the action seemed to be successful.
386
387 =cut
388
389 sub ModShelf {
390     my ($shelfnumber,$hashref) = @_;
391     my $dbh = C4::Context->dbh;
392
393     my $query= "SELECT * FROM virtualshelves WHERE shelfnumber=?";
394     my $sth = $dbh->prepare($query);
395     $sth->execute($shelfnumber);
396     my $oldrecord= $sth->fetchrow_hashref;
397     return 0 unless $oldrecord; #not found?
398
399     #initialize missing hash values to silence warnings
400     foreach('shelfname','category', 'sortfield', 'allow_add', 'allow_delete_own', 'allow_delete_other' ) {
401         $hashref->{$_}= undef unless exists $hashref->{$_};
402     }
403
404     #if name or category changes, the name should be tested
405     if($hashref->{shelfname} || $hashref->{category}) {
406         unless(_CheckShelfName(
407             $hashref->{shelfname}//$oldrecord->{shelfname},
408             $hashref->{category}//$oldrecord->{category},
409             $oldrecord->{owner},
410             $shelfnumber )) {
411                 return 0; #name check failed
412         }
413     }
414
415     #only the following fields from the hash may be changed
416     $query= "UPDATE virtualshelves SET shelfname=?, category=?, sortfield=?, allow_add=?, allow_delete_own=?, allow_delete_other=? WHERE shelfnumber=?";
417     $sth = $dbh->prepare($query);
418     $sth->execute(
419         $hashref->{shelfname}//$oldrecord->{shelfname},
420         $hashref->{category}//$oldrecord->{category},
421         $hashref->{sortfield}//$oldrecord->{sortfield},
422         $hashref->{allow_add}//$oldrecord->{allow_add},
423         $hashref->{allow_delete_own}//$oldrecord->{allow_delete_own},
424         $hashref->{allow_delete_other}//$oldrecord->{allow_delete_other},
425         $shelfnumber );
426     return $@? 0: 1;
427 }
428
429 =head2 ShelfPossibleAction
430
431 ShelfPossibleAction($loggedinuser, $shelfnumber, $action);
432
433 C<$loggedinuser,$shelfnumber,$action>
434
435 $action can be "view", "add", "delete", "manage", "new_public", "new_private".
436 New additional actions are: invite, acceptshare.
437 Note that add/delete here refers to adding/deleting entries from the list. Deleting the list itself falls under manage.
438 new_public and new_private refers to creating a new public or private list.
439 The distinction between deleting your own entries from the list or entries from
440 others is made in DelFromShelf.
441
442 Returns 1 if the user can do the $action in the $shelfnumber shelf.
443 Returns 0 otherwise.
444 For the actions invite and acceptshare a second errorcode is returned if the
445 result is false. See opac-shareshelf.pl
446
447 =cut
448
449 sub ShelfPossibleAction {
450     my ( $user, $shelfnumber, $action ) = @_;
451     $action= 'view' unless $action;
452     $user=0 unless $user;
453
454     if($action =~ /^new/) { #no shelfnumber needed
455         if($action eq 'new_private') {
456             return $user>0;
457         }
458         elsif($action eq 'new_public') {
459             return $user>0 && C4::Context->preference('OpacAllowPublicListCreation');
460         }
461         return 0;
462     }
463
464     return 0 unless defined($shelfnumber);
465
466     if ( $user > 0 and $action eq 'delete_shelf' ) {
467         my $borrower = C4::Members::GetMember( borrowernumber => $user );
468         require C4::Auth;
469         return 1
470             if C4::Auth::haspermission( $borrower->{userid}, { shelves => 'delete_public_lists' } );
471     }
472
473     my $dbh = C4::Context->dbh;
474     my $query = q{
475         SELECT COALESCE(owner,0) AS owner, category, allow_add, allow_delete_own, allow_delete_other, COALESCE(sh.borrowernumber,0) AS borrowernumber
476         FROM virtualshelves vs
477         LEFT JOIN virtualshelfshares sh ON sh.shelfnumber=vs.shelfnumber
478         AND sh.borrowernumber=?
479         WHERE vs.shelfnumber=?
480         };
481     my $sth = $dbh->prepare($query);
482     $sth->execute($user, $shelfnumber);
483     my $shelf= $sth->fetchrow_hashref;
484
485     return 0 unless $shelf && ($shelf->{category}==2 || $shelf->{owner}==$user || ($user && $shelf->{borrowernumber}==$user));
486     if($action eq 'view') {
487         #already handled in the above condition
488         return 1;
489     }
490     elsif($action eq 'add') {
491         return 0 if $user<=0; #should be logged in
492         return 1 if $shelf->{allow_add}==1 || $shelf->{owner}==$user;
493         #owner may always add
494     }
495     elsif($action eq 'delete') {
496         #this answer is just diplomatic: it says that you may be able to delete
497         #some items from that shelf
498         #it does not answer the question about a specific biblio
499         #DelFromShelf checks the situation per biblio
500         return 1 if $user>0 && ($shelf->{allow_delete_own}==1 || $shelf->{allow_delete_other}==1);
501     }
502     elsif($action eq 'invite') {
503         #for sharing you must be the owner and the list must be private
504         if( $shelf->{category}==1 ) {
505             return 1 if $shelf->{owner}==$user;
506             return (0, 4); # code 4: should be owner
507         }
508         else {
509             return (0, 5); # code 5: should be private list
510         }
511     }
512     elsif($action eq 'acceptshare') {
513         #the key for accepting is checked later in AcceptShare
514         #you must not be the owner, list must be private
515         if( $shelf->{category}==1 ) {
516             return (0, 8) if $shelf->{owner}==$user;
517                 #code 8: should not be owner
518             return 1;
519         }
520         else {
521             return (0, 5); # code 5: should be private list
522         }
523     }
524     elsif($action eq 'manage' or $action eq 'delete_shelf') {
525         return 1 if $user && $shelf->{owner}==$user;
526     }
527     return 0;
528 }
529
530 =head2 DelFromShelf
531
532     $result= &DelFromShelf( $bibref, $shelfnumber, $user);
533
534 Removes biblionumbers in passed arrayref from shelf C<$shelfnumber>.
535 If the bib wasn't on that virtualshelves to begin with, nothing happens.
536
537 Returns 0 if no items have been deleted.
538
539 =cut
540
541 sub DelFromShelf {
542     my ($bibref, $shelfnumber, $user) = @_;
543     my $dbh = C4::Context->dbh;
544     my $query = qq(SELECT allow_delete_own, allow_delete_other FROM virtualshelves WHERE shelfnumber=?);
545     my $sth= $dbh->prepare($query);
546     $sth->execute($shelfnumber);
547     my ($del_own, $del_oth)= $sth->fetchrow;
548     my $r; my $t=0;
549
550     if($del_own) {
551         $query = qq(DELETE FROM virtualshelfcontents
552             WHERE shelfnumber=? AND biblionumber=? AND borrowernumber=?);
553         $sth= $dbh->prepare($query);
554         foreach my $biblionumber (@$bibref) {
555             $sth->execute($shelfnumber, $biblionumber, $user);
556             $r= $sth->rows; #Expect -1, 0 or 1 (-1 means Don't know; count as 1)
557             $t+= ($r==-1)? 1: $r;
558         }
559     }
560     if($del_oth) {
561         #includes a check if borrowernumber is null (deleted patron)
562         $query = qq/DELETE FROM virtualshelfcontents
563             WHERE shelfnumber=? AND biblionumber=? AND
564             (borrowernumber IS NULL OR borrowernumber<>?)/;
565         $sth= $dbh->prepare($query);
566         foreach my $biblionumber (@$bibref) {
567             $sth->execute($shelfnumber, $biblionumber, $user);
568             $r= $sth->rows;
569             $t+= ($r==-1)? 1: $r;
570         }
571     }
572     return $t;
573 }
574
575 =head2 DelShelf
576
577   $Number = DelShelf($shelfnumber);
578
579 This function deletes the shelf number, and all of it's content.
580 Authorization to do so MUST have been checked before calling, while using
581 ShelfPossibleAction with manage parameter.
582
583 =cut
584
585 sub DelShelf {
586     my ($shelfnumber)= @_;
587     return unless $shelfnumber && $shelfnumber =~ /^\d+$/;
588     my $dbh = C4::Context->dbh;
589     my $sth = $dbh->prepare("DELETE FROM virtualshelves WHERE shelfnumber=?");
590     return $sth->execute($shelfnumber);
591 }
592
593 =head2 GetBibliosShelves
594
595 This finds all the public lists that this bib record is in.
596
597 =cut
598
599 sub GetBibliosShelves {
600     my ( $biblionumber )  = @_;
601     my $dbh = C4::Context->dbh;
602     my $sth = $dbh->prepare('
603         SELECT vs.shelfname, vs.shelfnumber 
604         FROM virtualshelves vs 
605         JOIN virtualshelfcontents vc ON (vs.shelfnumber= vc.shelfnumber) 
606         WHERE vs.category=2
607         AND vc.biblionumber= ?
608     ');
609     $sth->execute( $biblionumber );
610     return $sth->fetchall_arrayref({});
611 }
612
613 =head2 ShelvesMax
614
615     $howmany= ShelvesMax($context);
616
617 Tells how much shelves are shown in which context.
618 POPUP refers to addbybiblionumber popup, MGRPAGE is managing page (in opac or
619 staff), COMBO refers to the Add to-combo of search results. MASTHEAD is the
620 main Koha toolbar with Lists button.
621
622 =cut
623
624 sub ShelvesMax {
625     my $which= shift;
626     return SHELVES_POPUP_MAX if $which eq 'POPUP';
627     return SHELVES_MGRPAGE_MAX if $which eq 'MGRPAGE';
628     return SHELVES_COMBO_MAX if $which eq 'COMBO';
629     return SHELVES_MASTHEAD_MAX if $which eq 'MASTHEAD';
630     return SHELVES_MASTHEAD_MAX;
631 }
632
633 =head2 HandleDelBorrower
634
635      HandleDelBorrower($borrower);
636
637 When a member is deleted (DelMember in Members.pm), you should call me first.
638 This routine deletes/moves lists and entries for the deleted member/borrower.
639 Lists owned by the borrower are deleted, but entries from the borrower to
640 other lists are kept.
641
642 =cut
643
644 sub HandleDelBorrower {
645     my ($borrower)= @_;
646     my $query;
647     my $dbh = C4::Context->dbh;
648
649     #Delete all lists and all shares of this borrower
650     #Consistent with the approach Koha uses on deleting individual lists
651     #Note that entries in virtualshelfcontents added by this borrower to
652     #lists of others will be handled by a table constraint: the borrower
653     #is set to NULL in those entries.
654     $query="DELETE FROM virtualshelves WHERE owner=?";
655     $dbh->do($query,undef,($borrower));
656
657     #NOTE:
658     #We could handle the above deletes via a constraint too.
659     #But a new BZ report 11889 has been opened to discuss another approach.
660     #Instead of deleting we could also disown lists (based on a pref).
661     #In that way we could save shared and public lists.
662     #The current table constraints support that idea now.
663     #This pref should then govern the results of other routines such as
664     #DelShelf too.
665 }
666
667 =head2 AddShare
668
669      AddShare($shelfnumber, $key);
670
671 Adds a share request to the virtualshelves table.
672 Authorization must have been checked, and a key must be supplied. See script
673 opac-shareshelf.pl for an example.
674 This request is not yet confirmed. So it has no borrowernumber, it does have an
675 expiry date.
676
677 =cut
678
679 sub AddShare {
680     my ($shelfnumber, $key)= @_;
681     return if !$shelfnumber || !$key;
682
683     my $dbh = C4::Context->dbh;
684     my $sql = "INSERT INTO virtualshelfshares (shelfnumber, invitekey, sharedate) VALUES (?, ?, NOW())";
685     $dbh->do($sql, undef, ($shelfnumber, $key));
686     return !$dbh->err;
687 }
688
689 =head2 AcceptShare
690
691      my $result= AcceptShare($shelfnumber, $key, $borrowernumber);
692
693 Checks acceptation of a share request.
694 Key must be found for this shelf. Invitation must not have expired.
695 Returns true when accepted, false otherwise.
696
697 =cut
698
699 sub AcceptShare {
700     my ($shelfnumber, $key, $borrowernumber)= @_;
701     return if !$shelfnumber || !$key || !$borrowernumber;
702
703     my $sql;
704     my $dbh = C4::Context->dbh;
705     $sql="
706 UPDATE virtualshelfshares
707 SET invitekey=NULL, sharedate=NOW(), borrowernumber=?
708 WHERE shelfnumber=? AND invitekey=? AND (sharedate + INTERVAL ? DAY) >NOW()
709     ";
710     my $i= $dbh->do($sql, undef, ($borrowernumber, $shelfnumber, $key,  SHARE_INVITATION_EXPIRY_DAYS));
711     return if !defined($i) || !$i || $i eq '0E0'; #not found
712     return 1;
713 }
714
715 =head2 IsSharedList
716
717      my $bool= IsSharedList( $shelfnumber );
718
719 IsSharedList checks if a (private) list has shares.
720 Note that such a check would not be useful for public lists. A public list has
721 no shares, but is visible for anyone by nature..
722 Used to determine the list type in the display of Your lists (all private).
723 Returns boolean value.
724
725 =cut
726
727 sub IsSharedList {
728     my ($shelfnumber) = @_;
729     my $dbh = C4::Context->dbh;
730     my $sql="SELECT id FROM virtualshelfshares WHERE shelfnumber=? AND borrowernumber IS NOT NULL";
731     my $sth = $dbh->prepare($sql);
732     $sth->execute($shelfnumber);
733     my ($rv)= $sth->fetchrow_array;
734     return defined($rv);
735 }
736
737 =head2 RemoveShare
738
739      RemoveShare( $user, $shelfnumber );
740
741 RemoveShare removes a share for specific shelf and borrower.
742 Returns true if a record could be deleted.
743
744 =cut
745
746 sub RemoveShare {
747     my ($user, $shelfnumber)= @_;
748     my $dbh = C4::Context->dbh;
749     my $sql="
750 DELETE FROM virtualshelfshares
751 WHERE borrowernumber=? AND shelfnumber=?
752     ";
753     my $n= $dbh->do($sql,undef,($user, $shelfnumber));
754     return if !defined($n) || !$n || $n eq '0E0'; #nothing removed
755     return 1;
756 }
757
758
759 sub GetShelfCount {
760     my ($owner, $category) = @_;
761     my @params;
762     # Find out how many shelves total meet the submitted criteria...
763
764     my $dbh = C4::Context->dbh;
765     my $query = "SELECT count(*) FROM virtualshelves vs ";
766     if($category==1) {
767         $query.= qq{
768             LEFT JOIN virtualshelfshares sh ON sh.shelfnumber=vs.shelfnumber
769             AND sh.borrowernumber=?
770         WHERE category=1 AND (vs.owner=? OR sh.borrowernumber=?) };
771         @params= ($owner, $owner, $owner);
772     }
773     else {
774         $query.='WHERE category=2';
775         @params= ();
776     }
777     my $sth = $dbh->prepare($query);
778     $sth->execute(@params);
779     my ($total)= $sth->fetchrow;
780     return $total;
781 }
782
783 # internal subs
784 sub _CheckShelfName {
785     my ($name, $cat, $owner, $number)= @_;
786
787     my $dbh = C4::Context->dbh;
788     my @pars;
789     my $query = qq(
790         SELECT DISTINCT shelfnumber
791         FROM   virtualshelves
792         LEFT JOIN virtualshelfshares sh USING (shelfnumber)
793         WHERE  shelfname=? AND shelfnumber<>?);
794     if($cat==1 && defined($owner)) {
795         $query.= ' AND (sh.borrowernumber=? OR owner=?) AND category=1';
796         @pars=($name, $number, $owner, $owner);
797     }
798     elsif($cat==1 && !defined($owner)) { #owner is null (exceptional)
799         $query.= ' AND owner IS NULL AND category=1';
800         @pars=($name, $number);
801     }
802     else { #public list
803         $query.= ' AND category=2';
804         @pars=($name, $number);
805     }
806     my $sth = $dbh->prepare($query);
807     $sth->execute(@pars);
808     return $sth->rows>0? 0: 1;
809 }
810
811 1;
812
813 __END__
814
815 =head1 AUTHOR
816
817 Koha Development Team <http://koha-community.org/>
818
819 =head1 SEE ALSO
820
821 C4::Circulation::Circ2(3)
822
823 =cut