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