Bug 11810: Input fields in OPAC suggestion form are a bit short (Bootstrap)
[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     return if $sth->err;
338     my $shelfnumber = $dbh->{'mysql_insertid'};
339     return $shelfnumber;
340 }
341
342 =head2 AddToShelf
343
344   &AddToShelf($biblionumber, $shelfnumber, $borrower);
345
346 Adds bib number C<$biblionumber> to virtual virtualshelves number
347 C<$shelfnumber>, unless that bib is already on that shelf.
348
349 =cut
350
351 sub AddToShelf {
352     my ($biblionumber, $shelfnumber, $borrowernumber) = @_;
353     return unless $biblionumber;
354     my $dbh = C4::Context->dbh;
355     my $query = qq(
356         SELECT *
357         FROM   virtualshelfcontents
358         WHERE  shelfnumber=? AND biblionumber=?
359     );
360     my $sth = $dbh->prepare($query);
361
362     $sth->execute( $shelfnumber, $biblionumber );
363     ($sth->rows) and return; # already on shelf
364     $query = qq(
365         INSERT INTO virtualshelfcontents
366             (shelfnumber, biblionumber, flags, borrowernumber)
367         VALUES (?, ?, 0, ?));
368     $sth = $dbh->prepare($query);
369     $sth->execute( $shelfnumber, $biblionumber, $borrowernumber);
370     $query = qq(UPDATE virtualshelves
371                 SET lastmodified = CURRENT_TIMESTAMP
372                 WHERE shelfnumber = ?);
373     $sth = $dbh->prepare($query);
374     $sth->execute( $shelfnumber );
375 }
376
377 =head2 ModShelf
378
379 my $result= ModShelf($shelfnumber, $hashref)
380
381 Where $hashref->{column} = param
382
383 Modify the value into virtualshelves table with values given 
384 from hashref, which each key of the hashref should be
385 the name of a column of virtualshelves.
386 Fields like shelfnumber or owner cannot be changed.
387
388 Returns 1 if the action seemed to be successful.
389
390 =cut
391
392 sub ModShelf {
393     my ($shelfnumber,$hashref) = @_;
394     my $dbh = C4::Context->dbh;
395
396     my $query= "SELECT * FROM virtualshelves WHERE shelfnumber=?";
397     my $sth = $dbh->prepare($query);
398     $sth->execute($shelfnumber);
399     my $oldrecord= $sth->fetchrow_hashref;
400     return 0 unless $oldrecord; #not found?
401
402     #initialize missing hash values to silence warnings
403     foreach('shelfname','category', 'sortfield', 'allow_add', 'allow_delete_own', 'allow_delete_other' ) {
404         $hashref->{$_}= undef unless exists $hashref->{$_};
405     }
406
407     #if name or category changes, the name should be tested
408     if($hashref->{shelfname} || $hashref->{category}) {
409         unless(_CheckShelfName(
410             $hashref->{shelfname}//$oldrecord->{shelfname},
411             $hashref->{category}//$oldrecord->{category},
412             $oldrecord->{owner},
413             $shelfnumber )) {
414                 return 0; #name check failed
415         }
416     }
417
418     #only the following fields from the hash may be changed
419     $query= "UPDATE virtualshelves SET shelfname=?, category=?, sortfield=?, allow_add=?, allow_delete_own=?, allow_delete_other=? WHERE shelfnumber=?";
420     $sth = $dbh->prepare($query);
421     $sth->execute(
422         $hashref->{shelfname}//$oldrecord->{shelfname},
423         $hashref->{category}//$oldrecord->{category},
424         $hashref->{sortfield}//$oldrecord->{sortfield},
425         $hashref->{allow_add}//$oldrecord->{allow_add},
426         $hashref->{allow_delete_own}//$oldrecord->{allow_delete_own},
427         $hashref->{allow_delete_other}//$oldrecord->{allow_delete_other},
428         $shelfnumber );
429     return $@? 0: 1;
430 }
431
432 =head2 ShelfPossibleAction
433
434 ShelfPossibleAction($loggedinuser, $shelfnumber, $action);
435
436 C<$loggedinuser,$shelfnumber,$action>
437
438 $action can be "view", "add", "delete", "manage", "new_public", "new_private".
439 New additional actions are: invite, acceptshare.
440 Note that add/delete here refers to adding/deleting entries from the list. Deleting the list itself falls under manage.
441 new_public and new_private refers to creating a new public or private list.
442 The distinction between deleting your own entries from the list or entries from
443 others is made in DelFromShelf.
444
445 Returns 1 if the user can do the $action in the $shelfnumber shelf.
446 Returns 0 otherwise.
447 For the actions invite and acceptshare a second errorcode is returned if the
448 result is false. See opac-shareshelf.pl
449
450 =cut
451
452 sub ShelfPossibleAction {
453     my ( $user, $shelfnumber, $action ) = @_;
454     $action= 'view' unless $action;
455     $user=0 unless $user;
456
457     if($action =~ /^new/) { #no shelfnumber needed
458         if($action eq 'new_private') {
459             return $user>0;
460         }
461         elsif($action eq 'new_public') {
462             return $user>0 && C4::Context->preference('OpacAllowPublicListCreation');
463         }
464         return 0;
465     }
466
467     return 0 unless defined($shelfnumber);
468
469     my $dbh = C4::Context->dbh;
470     my $query = qq/
471         SELECT COALESCE(owner,0) AS owner, category, allow_add, allow_delete_own, allow_delete_other, COALESCE(sh.borrowernumber,0) AS borrowernumber
472         FROM virtualshelves vs
473         LEFT JOIN virtualshelfshares sh ON sh.shelfnumber=vs.shelfnumber
474         AND sh.borrowernumber=?
475         WHERE vs.shelfnumber=?
476     /;
477     my $sth = $dbh->prepare($query);
478     $sth->execute($user, $shelfnumber);
479     my $shelf= $sth->fetchrow_hashref;
480
481     return 0 unless $shelf && ($shelf->{category}==2 || $shelf->{owner}==$user || ($user && $shelf->{borrowernumber}==$user));
482     if($action eq 'view') {
483         #already handled in the above condition
484         return 1;
485     }
486     elsif($action eq 'add') {
487         return 0 if $user<=0; #should be logged in
488         return 1 if $shelf->{allow_add}==1 || $shelf->{owner}==$user;
489         #owner may always add
490     }
491     elsif($action eq 'delete') {
492         #this answer is just diplomatic: it says that you may be able to delete
493         #some items from that shelf
494         #it does not answer the question about a specific biblio
495         #DelFromShelf checks the situation per biblio
496         return 1 if $user>0 && ($shelf->{allow_delete_own}==1 || $shelf->{allow_delete_other}==1);
497     }
498     elsif($action eq 'invite') {
499         #for sharing you must be the owner and the list must be private
500         if( $shelf->{category}==1 ) {
501             return 1 if $shelf->{owner}==$user;
502             return (0, 4); # code 4: should be owner
503         }
504         else {
505             return (0, 5); # code 5: should be private list
506         }
507     }
508     elsif($action eq 'acceptshare') {
509         #the key for accepting is checked later in AcceptShare
510         #you must not be the owner, list must be private
511         if( $shelf->{category}==1 ) {
512             return (0, 8) if $shelf->{owner}==$user;
513                 #code 8: should not be owner
514             return 1;
515         }
516         else {
517             return (0, 5); # code 5: should be private list
518         }
519     }
520     elsif($action eq 'manage') {
521         return 1 if $user && $shelf->{owner}==$user;
522     }
523     return 0;
524 }
525
526 =head2 DelFromShelf
527
528     $result= &DelFromShelf( $bibref, $shelfnumber, $user);
529
530 Removes biblionumbers in passed arrayref from shelf C<$shelfnumber>.
531 If the bib wasn't on that virtualshelves to begin with, nothing happens.
532
533 Returns 0 if no items have been deleted.
534
535 =cut
536
537 sub DelFromShelf {
538     my ($bibref, $shelfnumber, $user) = @_;
539     my $dbh = C4::Context->dbh;
540     my $query = qq(SELECT allow_delete_own, allow_delete_other FROM virtualshelves WHERE shelfnumber=?);
541     my $sth= $dbh->prepare($query);
542     $sth->execute($shelfnumber);
543     my ($del_own, $del_oth)= $sth->fetchrow;
544     my $r; my $t=0;
545
546     if($del_own) {
547         $query = qq(DELETE FROM virtualshelfcontents
548             WHERE shelfnumber=? AND biblionumber=? AND borrowernumber=?);
549         $sth= $dbh->prepare($query);
550         foreach my $biblionumber (@$bibref) {
551             $sth->execute($shelfnumber, $biblionumber, $user);
552             $r= $sth->rows; #Expect -1, 0 or 1 (-1 means Don't know; count as 1)
553             $t+= ($r==-1)? 1: $r;
554         }
555     }
556     if($del_oth) {
557         #includes a check if borrowernumber is null (deleted patron)
558         $query = qq/DELETE FROM virtualshelfcontents
559             WHERE shelfnumber=? AND biblionumber=? AND
560             (borrowernumber IS NULL OR borrowernumber<>?)/;
561         $sth= $dbh->prepare($query);
562         foreach my $biblionumber (@$bibref) {
563             $sth->execute($shelfnumber, $biblionumber, $user);
564             $r= $sth->rows;
565             $t+= ($r==-1)? 1: $r;
566         }
567     }
568     return $t;
569 }
570
571 =head2 DelShelf
572
573   $Number = DelShelf($shelfnumber);
574
575 This function deletes the shelf number, and all of it's content.
576 Authorization to do so MUST have been checked before calling, while using
577 ShelfPossibleAction with manage parameter.
578
579 =cut
580
581 sub DelShelf {
582     my ($shelfnumber)= @_;
583     return unless $shelfnumber && $shelfnumber =~ /^\d+$/;
584     my $dbh = C4::Context->dbh;
585     my $sth = $dbh->prepare("DELETE FROM virtualshelves WHERE shelfnumber=?");
586     return $sth->execute($shelfnumber);
587 }
588
589 =head2 GetBibliosShelves
590
591 This finds all the public lists that this bib record is in.
592
593 =cut
594
595 sub GetBibliosShelves {
596     my ( $biblionumber )  = @_;
597     my $dbh = C4::Context->dbh;
598     my $sth = $dbh->prepare('
599         SELECT vs.shelfname, vs.shelfnumber 
600         FROM virtualshelves vs 
601         JOIN virtualshelfcontents vc ON (vs.shelfnumber= vc.shelfnumber) 
602         WHERE vs.category=2
603         AND vc.biblionumber= ?
604     ');
605     $sth->execute( $biblionumber );
606     return $sth->fetchall_arrayref({});
607 }
608
609 =head2 ShelvesMax
610
611     $howmany= ShelvesMax($context);
612
613 Tells how much shelves are shown in which context.
614 POPUP refers to addbybiblionumber popup, MGRPAGE is managing page (in opac or
615 staff), COMBO refers to the Add to-combo of search results. MASTHEAD is the
616 main Koha toolbar with Lists button.
617
618 =cut
619
620 sub ShelvesMax {
621     my $which= shift;
622     return SHELVES_POPUP_MAX if $which eq 'POPUP';
623     return SHELVES_MGRPAGE_MAX if $which eq 'MGRPAGE';
624     return SHELVES_COMBO_MAX if $which eq 'COMBO';
625     return SHELVES_MASTHEAD_MAX if $which eq 'MASTHEAD';
626     return SHELVES_MASTHEAD_MAX;
627 }
628
629 =head2 HandleDelBorrower
630
631      HandleDelBorrower($borrower);
632
633 When a member is deleted (DelMember in Members.pm), you should call me first.
634 This routine deletes/moves lists and entries for the deleted member/borrower.
635 Lists owned by the borrower are deleted, but entries from the borrower to
636 other lists are kept.
637
638 =cut
639
640 sub HandleDelBorrower {
641     my ($borrower)= @_;
642     my $query;
643     my $dbh = C4::Context->dbh;
644
645     #Delete all lists and all shares of this borrower
646     #Consistent with the approach Koha uses on deleting individual lists
647     #Note that entries in virtualshelfcontents added by this borrower to
648     #lists of others will be handled by a table constraint: the borrower
649     #is set to NULL in those entries.
650     $query="DELETE FROM virtualshelves WHERE owner=?";
651     $dbh->do($query,undef,($borrower));
652
653     #NOTE:
654     #We could handle the above deletes via a constraint too.
655     #But a new BZ report 11889 has been opened to discuss another approach.
656     #Instead of deleting we could also disown lists (based on a pref).
657     #In that way we could save shared and public lists.
658     #The current table constraints support that idea now.
659     #This pref should then govern the results of other routines such as
660     #DelShelf too.
661 }
662
663 =head2 AddShare
664
665      AddShare($shelfnumber, $key);
666
667 Adds a share request to the virtualshelves table.
668 Authorization must have been checked, and a key must be supplied. See script
669 opac-shareshelf.pl for an example.
670 This request is not yet confirmed. So it has no borrowernumber, it does have an
671 expiry date.
672
673 =cut
674
675 sub AddShare {
676     my ($shelfnumber, $key)= @_;
677     return if !$shelfnumber || !$key;
678
679     my $dbh = C4::Context->dbh;
680     my $sql = "INSERT INTO virtualshelfshares (shelfnumber, invitekey, sharedate) VALUES (?, ?, NOW())";
681     $dbh->do($sql, undef, ($shelfnumber, $key));
682     return !$dbh->err;
683 }
684
685 =head2 AcceptShare
686
687      my $result= AcceptShare($shelfnumber, $key, $borrowernumber);
688
689 Checks acceptation of a share request.
690 Key must be found for this shelf. Invitation must not have expired.
691 Returns true when accepted, false otherwise.
692
693 =cut
694
695 sub AcceptShare {
696     my ($shelfnumber, $key, $borrowernumber)= @_;
697     return if !$shelfnumber || !$key || !$borrowernumber;
698
699     my $sql;
700     my $dbh = C4::Context->dbh;
701     $sql="
702 UPDATE virtualshelfshares
703 SET invitekey=NULL, sharedate=NOW(), borrowernumber=?
704 WHERE shelfnumber=? AND invitekey=? AND (sharedate + INTERVAL ? DAY) >NOW()
705     ";
706     my $i= $dbh->do($sql, undef, ($borrowernumber, $shelfnumber, $key,  SHARE_INVITATION_EXPIRY_DAYS));
707     return if !defined($i) || !$i || $i eq '0E0'; #not found
708     return 1;
709 }
710
711 =head2 IsSharedList
712
713      my $bool= IsSharedList( $shelfnumber );
714
715 IsSharedList checks if a (private) list has shares.
716 Note that such a check would not be useful for public lists. A public list has
717 no shares, but is visible for anyone by nature..
718 Used to determine the list type in the display of Your lists (all private).
719 Returns boolean value.
720
721 =cut
722
723 sub IsSharedList {
724     my ($shelfnumber) = @_;
725     my $dbh = C4::Context->dbh;
726     my $sql="SELECT id FROM virtualshelfshares WHERE shelfnumber=? AND borrowernumber IS NOT NULL";
727     my $sth = $dbh->prepare($sql);
728     $sth->execute($shelfnumber);
729     my ($rv)= $sth->fetchrow_array;
730     return defined($rv);
731 }
732
733 =head2 RemoveShare
734
735      RemoveShare( $user, $shelfnumber );
736
737 RemoveShare removes a share for specific shelf and borrower.
738 Returns true if a record could be deleted.
739
740 =cut
741
742 sub RemoveShare {
743     my ($user, $shelfnumber)= @_;
744     my $dbh = C4::Context->dbh;
745     my $sql="
746 DELETE FROM virtualshelfshares
747 WHERE borrowernumber=? AND shelfnumber=?
748     ";
749     my $n= $dbh->do($sql,undef,($user, $shelfnumber));
750     return if !defined($n) || !$n || $n eq '0E0'; #nothing removed
751     return 1;
752 }
753
754 # internal subs
755
756 sub _shelf_count {
757     my ($owner, $category) = @_;
758     my @params;
759     # Find out how many shelves total meet the submitted criteria...
760
761     my $dbh = C4::Context->dbh;
762     my $query = "SELECT count(*) FROM virtualshelves vs ";
763     if($category==1) {
764         $query.= qq{
765             LEFT JOIN virtualshelfshares sh ON sh.shelfnumber=vs.shelfnumber
766             AND sh.borrowernumber=?
767         WHERE category=1 AND (vs.owner=? OR sh.borrowernumber=?) };
768         @params= ($owner, $owner, $owner);
769     }
770     else {
771         $query.='WHERE category=2';
772         @params= ();
773     }
774     my $sth = $dbh->prepare($query);
775     $sth->execute(@params);
776     my ($total)= $sth->fetchrow;
777     return $total;
778 }
779
780 sub _CheckShelfName {
781     my ($name, $cat, $owner, $number)= @_;
782
783     my $dbh = C4::Context->dbh;
784     my @pars;
785     my $query = qq(
786         SELECT DISTINCT shelfnumber
787         FROM   virtualshelves
788         LEFT JOIN virtualshelfshares sh USING (shelfnumber)
789         WHERE  shelfname=? AND shelfnumber<>?);
790     if($cat==1 && defined($owner)) {
791         $query.= ' AND (sh.borrowernumber=? OR owner=?) AND category=1';
792         @pars=($name, $number, $owner, $owner);
793     }
794     elsif($cat==1 && !defined($owner)) { #owner is null (exceptional)
795         $query.= ' AND owner IS NULL AND category=1';
796         @pars=($name, $number);
797     }
798     else { #public list
799         $query.= ' AND category=2';
800         @pars=($name, $number);
801     }
802     my $sth = $dbh->prepare($query);
803     $sth->execute(@pars);
804     return $sth->rows>0? 0: 1;
805 }
806
807 1;
808
809 __END__
810
811 =head1 AUTHOR
812
813 Koha Development Team <http://koha-community.org/>
814
815 =head1 SEE ALSO
816
817 C4::Circulation::Circ2(3)
818
819 =cut