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