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