Bug 9032: enforce consistent behavior when deleting lists
[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 Lists owned by the borrower are deleted, but entries from the borrower to
635 other lists are kept.
636
637 =cut
638
639 sub HandleDelBorrower {
640     my ($borrower)= @_;
641     my $query;
642     my $dbh = C4::Context->dbh;
643
644     #Delete all lists and all shares of this borrower
645     #Consistent with the approach Koha uses on deleting individual lists
646     #Note that entries in virtualshelfcontents added by this borrower to
647     #lists of others will be handled by a table constraint: the borrower
648     #is set to NULL in those entries.
649     $query="DELETE FROM virtualshelves WHERE owner=?";
650     $dbh->do($query,undef,($borrower));
651
652     #NOTE:
653     #We could handle the above deletes via a constraint too.
654     #But a new BZ report 11889 has been opened to discuss another approach.
655     #Instead of deleting we could also disown lists (based on a pref).
656     #In that way we could save shared and public lists.
657     #The current table constraints support that idea now.
658     #This pref should then govern the results of other routines such as
659     #DelShelf too.
660 }
661
662 =head2 AddShare
663
664      AddShare($shelfnumber, $key);
665
666 Adds a share request to the virtualshelves table.
667 Authorization must have been checked, and a key must be supplied. See script
668 opac-shareshelf.pl for an example.
669 This request is not yet confirmed. So it has no borrowernumber, it does have an
670 expiry date.
671
672 =cut
673
674 sub AddShare {
675     my ($shelfnumber, $key)= @_;
676     return if !$shelfnumber || !$key;
677
678     my $sql;
679     my $dbh = C4::Context->dbh;
680     $sql="DELETE FROM virtualshelfshares WHERE sharedate<NOW() LIMIT 10";
681         #housekeeping: add one, remove max 10 expired ones
682     $dbh->do($sql);
683     $sql="INSERT INTO virtualshelfshares (shelfnumber, invitekey, sharedate) VALUES (?, ?, ADDDATE(NOW(),?))";
684     $dbh->do($sql, undef, ($shelfnumber, $key, SHARE_INVITATION_EXPIRY_DAYS));
685     return !$dbh->err;
686 }
687
688 =head2 AcceptShare
689
690      my $result= AcceptShare($shelfnumber, $key, $borrowernumber);
691
692 Checks acceptation of a share request.
693 Key must be found for this shelf. Invitation must not have expired.
694 Returns true when accepted, false otherwise.
695
696 =cut
697
698 sub AcceptShare {
699     my ($shelfnumber, $key, $borrowernumber)= @_;
700     return if !$shelfnumber || !$key || !$borrowernumber;
701
702     my $sql;
703     my $dbh = C4::Context->dbh;
704     $sql="
705 UPDATE virtualshelfshares
706 SET invitekey=NULL, sharedate=NULL, borrowernumber=?
707 WHERE shelfnumber=? AND invitekey=? AND sharedate>NOW()
708     ";
709     my $i= $dbh->do($sql, undef, ($borrowernumber, $shelfnumber, $key));
710     return if !defined($i) || !$i || $i eq '0E0'; #not found
711     return 1;
712 }
713
714 =head2 IsSharedList
715
716      my $bool= IsSharedList( $shelfnumber );
717
718 IsSharedList checks if a (private) list has shares.
719 Note that such a check would not be useful for public lists. A public list has
720 no shares, but is visible for anyone by nature..
721 Used to determine the list type in the display of Your lists (all private).
722 Returns boolean value.
723
724 =cut
725
726 sub IsSharedList {
727     my ($shelfnumber) = @_;
728     my $dbh = C4::Context->dbh;
729     my $sql="SELECT id FROM virtualshelfshares WHERE shelfnumber=? AND borrowernumber IS NOT NULL";
730     my $sth = $dbh->prepare($sql);
731     $sth->execute($shelfnumber);
732     my ($rv)= $sth->fetchrow_array;
733     return defined($rv);
734 }
735
736 =head2 RemoveShare
737
738      RemoveShare( $user, $shelfnumber );
739
740 RemoveShare removes a share for specific shelf and borrower.
741 Returns true if a record could be deleted.
742
743 =cut
744
745 sub RemoveShare {
746     my ($user, $shelfnumber)= @_;
747     my $dbh = C4::Context->dbh;
748     my $sql="
749 DELETE FROM virtualshelfshares
750 WHERE borrowernumber=? AND shelfnumber=?
751     ";
752     my $n= $dbh->do($sql,undef,($user, $shelfnumber));
753     return if !defined($n) || !$n || $n eq '0E0'; #nothing removed
754     return 1;
755 }
756
757 # internal subs
758
759 sub _shelf_count {
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 sub _CheckShelfName {
784     my ($name, $cat, $owner, $number)= @_;
785
786     my $dbh = C4::Context->dbh;
787     my @pars;
788     my $query = qq(
789         SELECT DISTINCT shelfnumber
790         FROM   virtualshelves
791         LEFT JOIN virtualshelfshares sh USING (shelfnumber)
792         WHERE  shelfname=? AND shelfnumber<>?);
793     if($cat==1 && defined($owner)) {
794         $query.= ' AND (sh.borrowernumber=? OR owner=?) AND category=1';
795         @pars=($name, $number, $owner, $owner);
796     }
797     elsif($cat==1 && !defined($owner)) { #owner is null (exceptional)
798         $query.= ' AND owner IS NULL AND category=1';
799         @pars=($name, $number);
800     }
801     else { #public list
802         $query.= ' AND category=2';
803         @pars=($name, $number);
804     }
805     my $sth = $dbh->prepare($query);
806     $sth->execute(@pars);
807     return $sth->rows>0? 0: 1;
808 }
809
810 1;
811
812 __END__
813
814 =head1 AUTHOR
815
816 Koha Development Team <http://koha-community.org/>
817
818 =head1 SEE ALSO
819
820 C4::Circulation::Circ2(3)
821
822 =cut