Bug 14544: Get rid of GetBibliosShelves
[koha.git] / C4 / VirtualShelves.pm
1 package C4::VirtualShelves;
2
3 # Copyright 2000-2002 Katipo Communications
4 #
5 # This file is part of Koha.
6 #
7 # Koha is free software; you can redistribute it and/or modify it
8 # under the terms of the GNU General Public License as published by
9 # the Free Software Foundation; either version 3 of the License, or
10 # (at your option) any later version.
11 #
12 # Koha is distributed in the hope that it will be useful, but
13 # WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU General Public License for more details.
16 #
17 # You should have received a copy of the GNU General Public License
18 # along with Koha; if not, see <http://www.gnu.org/licenses>.
19
20 use strict;
21 use warnings;
22
23 use Carp;
24 use C4::Context;
25 use C4::Debug;
26 use C4::Members;
27
28 use constant SHELVES_MASTHEAD_MAX => 10; #number under Lists button in masthead
29 use constant SHELVES_COMBO_MAX => 10; #add to combo in search
30 use constant SHELVES_MGRPAGE_MAX => 20; #managing page
31 use constant SHELVES_POPUP_MAX => 40; #addbybiblio popup
32
33 use constant SHARE_INVITATION_EXPIRY_DAYS => 14; #two weeks to accept
34
35 use vars qw($VERSION @ISA @EXPORT @EXPORT_OK);
36
37 BEGIN {
38     # set the version for version checking
39     $VERSION = 3.07.00.049;
40     require Exporter;
41     @ISA    = qw(Exporter);
42     @EXPORT = qw(
43             &GetShelves &GetShelfContents
44             &ShelfPossibleAction
45     );
46         @EXPORT_OK = qw(
47             &ShelvesMax
48         );
49 }
50
51
52 =head1 NAME
53
54 C4::VirtualShelves - Functions for manipulating Koha virtual shelves
55
56 =head1 SYNOPSIS
57
58   use C4::VirtualShelves;
59
60 =head1 DESCRIPTION
61
62 This module provides functions for manipulating virtual shelves,
63 including creating and deleting virtual shelves, and adding and removing
64 bibs to and from virtual shelves.
65
66 =head1 FUNCTIONS
67
68 =head2 GetShelves
69
70   $shelflist = &GetShelves($category, $row_count, $offset, $owner);
71   ($shelfnumber, $shelfhash) = each %{$shelflist};
72
73 Returns the number of shelves specified by C<$row_count> and C<$offset> as well as the total
74 number of shelves that meet the C<$owner> and C<$category> criteria.  C<$category>,
75 C<$row_count>, and C<$offset> are required. C<$owner> must be supplied when C<$category> == 1.
76 When C<$category> is 2, supply undef as argument for C<$owner>.
77
78 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.
79
80 C<$shelflist>is a reference-to-hash. The keys are the virtualshelves numbers (C<$shelfnumber>, above),
81 and the values (C<$shelfhash>, above) are themselves references-to-hash, with the following keys:
82
83 =over
84
85 =item C<$shelfhash-E<gt>{shelfname}>
86
87 A string. The name of the shelf.
88
89 =back
90
91 =cut
92
93 sub GetShelves {
94     my ($category, $row_count, $offset, $owner) = @_;
95     $offset ||= 0;
96     my @params = ( $offset, $row_count );
97     my $dbh = C4::Context->dbh;
98     my $query = qq{
99         SELECT vs.shelfnumber, vs.shelfname,vs.owner,
100         bo.surname,bo.firstname,vs.category,vs.sortfield,
101         count(vc.biblionumber) as count
102         FROM virtualshelves vs
103         LEFT JOIN borrowers bo ON vs.owner=bo.borrowernumber
104         LEFT JOIN virtualshelfcontents vc USING (shelfnumber) };
105     if($category==1) {
106         $query.= qq{
107             LEFT JOIN virtualshelfshares sh ON sh.shelfnumber=vs.shelfnumber
108             AND sh.borrowernumber=?
109         WHERE category=1 AND (vs.owner=? OR sh.borrowernumber=?) };
110         unshift @params, ($owner) x 3;
111     }
112     else {
113         $query.= 'WHERE category=2 ';
114     }
115     $query.= qq{
116         GROUP BY vs.shelfnumber
117         ORDER BY vs.shelfname
118         LIMIT ?, ?};
119
120     my $sth2 = $dbh->prepare($query);
121     $sth2->execute(@params);
122     my %shelflist;
123     while( my ($shelfnumber, $shelfname, $owner, $surname, $firstname, $category, $sortfield, $count)= $sth2->fetchrow) {
124         $shelflist{$shelfnumber}->{'shelfname'} = $shelfname;
125         $shelflist{$shelfnumber}->{'count'}     = $count;
126         $shelflist{$shelfnumber}->{'single'}    = $count==1;
127         $shelflist{$shelfnumber}->{'sortfield'} = $sortfield;
128         $shelflist{$shelfnumber}->{'category'}  = $category;
129         $shelflist{$shelfnumber}->{'owner'}     = $owner;
130         $shelflist{$shelfnumber}->{'surname'}   = $surname;
131         $shelflist{$shelfnumber}->{'firstname'} = $firstname;
132     }
133     return \%shelflist;
134 }
135
136 =head2 GetSomeShelfNames
137
138 Returns shelf names and numbers for Add to combo of search results and Lists button of OPAC header.
139
140 =cut
141
142 sub GetSomeShelfNames {
143     my ($owner, $purpose, $adding_allowed)= @_;
144     my ($bar, $pub, @params);
145     my $dbh = C4::Context->dbh;
146
147     my $bquery = 'SELECT vs.shelfnumber, vs.shelfname FROM virtualshelves vs ';
148     my $limit= ShelvesMax($purpose);
149
150     my $qry1= $bquery."WHERE vs.category=2 ";
151     $qry1.= "AND (allow_add=1 OR owner=?) " if $adding_allowed;
152     push @params, $owner||0 if $adding_allowed;
153     $qry1.= "ORDER BY vs.lastmodified DESC LIMIT $limit";
154
155     unless($adding_allowed && (!defined($owner) || $owner<=0)) {
156         #if adding items, user should be known
157         $pub= $dbh->selectall_arrayref($qry1,{Slice=>{}},@params);
158     }
159
160     if($owner) {
161         my $qry2= $bquery. qq{
162             LEFT JOIN virtualshelfshares sh ON sh.shelfnumber=vs.shelfnumber AND sh.borrowernumber=?
163             WHERE vs.category=1 AND (vs.owner=? OR sh.borrowernumber=?) };
164         @params=($owner,$owner,$owner);
165         $qry2.= "AND (allow_add=1 OR owner=?) " if $adding_allowed;
166         push @params, $owner if $adding_allowed;
167         $qry2.= "ORDER BY vs.lastmodified DESC ";
168         $qry2.= "LIMIT $limit";
169         $bar= $dbh->selectall_arrayref($qry2,{Slice=>{}},@params);
170     }
171
172     return ( { bartotal => $bar? scalar @$bar: 0, pubtotal => $pub? scalar @$pub: 0}, $pub, $bar);
173 }
174
175 =head2 GetShelfContents
176
177   $biblist = &GetShelfContents($shelfnumber);
178
179 Looks up information about the contents of virtual virtualshelves number
180 C<$shelfnumber>.  Sorted by a field in the biblio table.  copyrightdate 
181 gives a desc sort.
182
183 Returns a reference-to-array, whose elements are references-to-hash,
184 as returned by C<C4::Biblio::GetBiblioFromItemNumber>.
185
186 Note: the notforloan status comes from the itemtype, and where it equals 0
187 it does not ensure that related items.notforloan status is likewise 0. The
188 caller has to check any items on their own, possibly with CanBookBeIssued
189 from C4::Circulation.
190
191 =cut
192
193 sub GetShelfContents {
194     my ($shelfnumber, $row_count, $offset, $sortfield, $sort_direction ) = @_;
195     my $dbh=C4::Context->dbh();
196     my $sth1 = $dbh->prepare("SELECT count(*) FROM virtualshelfcontents WHERE shelfnumber = ?");
197     $sth1->execute($shelfnumber);
198     my $total = $sth1->fetchrow;
199     if(!$sortfield) {
200         my $sth2 = $dbh->prepare('SELECT sortfield FROM virtualshelves WHERE shelfnumber=?');
201         $sth2->execute($shelfnumber);
202         ($sortfield) = $sth2->fetchrow_array;
203     }
204     my $query =
205        " SELECT DISTINCT vc.biblionumber, vc.shelfnumber, vc.dateadded, itemtypes.*,
206             biblio.*, biblioitems.itemtype, biblioitems.publicationyear as year, biblioitems.publishercode, biblioitems.place, biblioitems.size, biblioitems.pages
207          FROM   virtualshelfcontents vc
208          JOIN biblio      ON      vc.biblionumber =      biblio.biblionumber
209          LEFT JOIN biblioitems ON  biblio.biblionumber = biblioitems.biblionumber
210          LEFT JOIN items ON items.biblionumber=vc.biblionumber
211          LEFT JOIN itemtypes   ON biblioitems.itemtype = itemtypes.itemtype
212          WHERE  vc.shelfnumber=? ";
213     my @params = ($shelfnumber);
214     if($sortfield) {
215         $query .= " ORDER BY " . $dbh->quote_identifier( $sortfield );
216         $query .= " DESC " if ( $sort_direction eq 'desc' );
217     }
218     if($row_count){
219        $query .= " LIMIT ?, ? ";
220        push (@params, ($offset ? $offset : 0));
221        push (@params, $row_count);
222     }
223     my $sth3 = $dbh->prepare($query);
224     $sth3->execute(@params);
225     return ($sth3->fetchall_arrayref({}), $total);
226     # Like the perldoc says,
227     # returns reference-to-array, where each element is reference-to-hash of the row:
228     #   like [ $sth->fetchrow_hashref(), $sth->fetchrow_hashref() ... ]
229     # Suitable for use in TMPL_LOOP.
230     # See http://search.cpan.org/~timb/DBI-1.601/DBI.pm#fetchall_arrayref
231     # or newer, for your version of DBI.
232 }
233
234 =head2 ShelfPossibleAction
235
236 ShelfPossibleAction($loggedinuser, $shelfnumber, $action);
237
238 C<$loggedinuser,$shelfnumber,$action>
239
240 $action can be "view", "add", "delete", "manage", "new_public", "new_private".
241 New additional actions are: invite, acceptshare.
242 Note that add/delete here refers to adding/deleting entries from the list. Deleting the list itself falls under manage.
243 new_public and new_private refers to creating a new public or private list.
244 The distinction between deleting your own entries from the list or entries from
245 others is made when deleting a content from the shelf.
246
247 Returns 1 if the user can do the $action in the $shelfnumber shelf.
248 Returns 0 otherwise.
249 For the actions invite and acceptshare a second errorcode is returned if the
250 result is false. See opac-shareshelf.pl
251
252 =cut
253
254 sub ShelfPossibleAction {
255     my ( $user, $shelfnumber, $action ) = @_;
256     $action= 'view' unless $action;
257     $user=0 unless $user;
258
259     if($action =~ /^new/) { #no shelfnumber needed
260         if($action eq 'new_private') {
261             return $user>0;
262         }
263         elsif($action eq 'new_public') {
264             return $user>0 && C4::Context->preference('OpacAllowPublicListCreation');
265         }
266         return 0;
267     }
268
269     return 0 unless defined($shelfnumber);
270
271     if ( $user > 0 and $action eq 'delete_shelf' ) {
272         my $borrower = C4::Members::GetMember( borrowernumber => $user );
273         require C4::Auth;
274         return 1
275             if C4::Auth::haspermission( $borrower->{userid}, { lists => 'delete_public_lists' } );
276     }
277
278     my $dbh = C4::Context->dbh;
279     my $query = q{
280         SELECT COALESCE(owner,0) AS owner, category, allow_add, allow_delete_own, allow_delete_other, COALESCE(sh.borrowernumber,0) AS borrowernumber
281         FROM virtualshelves vs
282         LEFT JOIN virtualshelfshares sh ON sh.shelfnumber=vs.shelfnumber
283         AND sh.borrowernumber=?
284         WHERE vs.shelfnumber=?
285         };
286     my $sth = $dbh->prepare($query);
287     $sth->execute($user, $shelfnumber);
288     my $shelf= $sth->fetchrow_hashref;
289
290     return 0 unless $shelf && ($shelf->{category}==2 || $shelf->{owner}==$user || ($user && $shelf->{borrowernumber}==$user));
291     if($action eq 'view') {
292         #already handled in the above condition
293         return 1;
294     }
295     elsif($action eq 'add') {
296         return 0 if $user<=0; #should be logged in
297         return 1 if $shelf->{allow_add}==1 || $shelf->{owner}==$user;
298         #owner may always add
299     }
300     elsif($action eq 'delete') {
301         #this answer is just diplomatic: it says that you may be able to delete
302         #some items from that shelf
303         #it does not answer the question about a specific biblio
304         #Koha::Virtualshelf->remove_biblios checks the situation per biblio
305         return 1 if $user>0 && ($shelf->{allow_delete_own}==1 || $shelf->{allow_delete_other}==1);
306     }
307     elsif($action eq 'invite') {
308         #for sharing you must be the owner and the list must be private
309         if( $shelf->{category}==1 ) {
310             return 1 if $shelf->{owner}==$user;
311             return (0, 4); # code 4: should be owner
312         }
313         else {
314             return (0, 5); # code 5: should be private list
315         }
316     }
317     elsif($action eq 'acceptshare') {
318         #the key for accepting is checked later in Koha::Virtualshelf->share
319         #you must not be the owner, list must be private
320         if( $shelf->{category}==1 ) {
321             return (0, 8) if $shelf->{owner}==$user;
322                 #code 8: should not be owner
323             return 1;
324         }
325         else {
326             return (0, 5); # code 5: should be private list
327         }
328     }
329     elsif($action eq 'manage' or $action eq 'delete_shelf') {
330         return 1 if $user && $shelf->{owner}==$user;
331     }
332     return 0;
333 }
334
335 =head2 ShelvesMax
336
337     $howmany= ShelvesMax($context);
338
339 Tells how much shelves are shown in which context.
340 POPUP refers to addbybiblionumber popup, MGRPAGE is managing page (in opac or
341 staff), COMBO refers to the Add to-combo of search results. MASTHEAD is the
342 main Koha toolbar with Lists button.
343
344 =cut
345
346 sub ShelvesMax {
347     my $which= shift;
348     return SHELVES_POPUP_MAX if $which eq 'POPUP';
349     return SHELVES_MGRPAGE_MAX if $which eq 'MGRPAGE';
350     return SHELVES_COMBO_MAX if $which eq 'COMBO';
351     return SHELVES_MASTHEAD_MAX if $which eq 'MASTHEAD';
352     return SHELVES_MASTHEAD_MAX;
353 }
354
355 =head2 HandleDelBorrower
356
357      HandleDelBorrower($borrower);
358
359 When a member is deleted (DelMember in Members.pm), you should call me first.
360 This routine deletes/moves lists and entries for the deleted member/borrower.
361 Lists owned by the borrower are deleted, but entries from the borrower to
362 other lists are kept.
363
364 =cut
365
366 sub HandleDelBorrower {
367     my ($borrower)= @_;
368     my $query;
369     my $dbh = C4::Context->dbh;
370
371     #Delete all lists and all shares of this borrower
372     #Consistent with the approach Koha uses on deleting individual lists
373     #Note that entries in virtualshelfcontents added by this borrower to
374     #lists of others will be handled by a table constraint: the borrower
375     #is set to NULL in those entries.
376     $query="DELETE FROM virtualshelves WHERE owner=?";
377     $dbh->do($query,undef,($borrower));
378
379     #NOTE:
380     #We could handle the above deletes via a constraint too.
381     #But a new BZ report 11889 has been opened to discuss another approach.
382     #Instead of deleting we could also disown lists (based on a pref).
383     #In that way we could save shared and public lists.
384     #The current table constraints support that idea now.
385     #This pref should then govern the results of other routines/methods such as
386     #Koha::Virtualshelf->new->delete too.
387 }
388
389 sub GetShelfCount {
390     my ($owner, $category) = @_;
391     my @params;
392     # Find out how many shelves total meet the submitted criteria...
393
394     my $dbh = C4::Context->dbh;
395     my $query = "SELECT count(*) FROM virtualshelves vs ";
396     if($category==1) {
397         $query.= qq{
398             LEFT JOIN virtualshelfshares sh ON sh.shelfnumber=vs.shelfnumber
399             AND sh.borrowernumber=?
400         WHERE category=1 AND (vs.owner=? OR sh.borrowernumber=?) };
401         @params= ($owner, $owner, $owner);
402     }
403     else {
404         $query.='WHERE category=2';
405         @params= ();
406     }
407     my $sth = $dbh->prepare($query);
408     $sth->execute(@params);
409     my ($total)= $sth->fetchrow;
410     return $total;
411 }
412
413 1;
414
415 __END__
416
417 =head1 AUTHOR
418
419 Koha Development Team <http://koha-community.org/>
420
421 =head1 SEE ALSO
422
423 C4::Circulation::Circ2(3)
424
425 =cut