Bug 14544: Get rid of ShelfPossibleAction
[koha.git] / opac / opac-shareshelf.pl
1 #!/usr/bin/perl
2
3 # Copyright 2013 Rijksmuseum
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 Modern::Perl;
21
22 use constant KEYLENGTH     => 10;
23 use constant TEMPLATE_NAME => 'opac-shareshelf.tt';
24 use constant SHELVES_URL =>
25   '/cgi-bin/koha/opac-shelves.pl?display=privateshelves&viewshelf=';
26
27 use CGI qw ( -utf8 );
28 use Email::Valid;
29
30 use C4::Auth;
31 use C4::Context;
32 use C4::Letters;
33 use C4::Members ();
34 use C4::Output;
35 use C4::VirtualShelves;
36
37 use Koha::Virtualshelves;
38 use Koha::Virtualshelfshares;
39
40 #-------------------------------------------------------------------------------
41
42 my $pvar = _init( {} );
43 if ( !$pvar->{errcode} ) {
44     show_invite($pvar)    if $pvar->{op} eq 'invite';
45     confirm_invite($pvar) if $pvar->{op} eq 'conf_invite';
46     show_accept($pvar)    if $pvar->{op} eq 'accept';
47 }
48 load_template_vars($pvar);
49 output_html_with_http_headers $pvar->{query}, $pvar->{cookie},
50   $pvar->{template}->output;
51
52 #-------------------------------------------------------------------------------
53
54 sub _init {
55     my ($param) = @_;
56     my $query = new CGI;
57     $param->{query}       = $query;
58     $param->{shelfnumber} = $query->param('shelfnumber') || 0;
59     $param->{op}          = $query->param('op') || '';
60     $param->{addrlist}    = $query->param('invite_address') || '';
61     $param->{key}         = $query->param('key') || '';
62     $param->{appr_addr}   = [];
63     $param->{fail_addr}   = [];
64     $param->{errcode}     = check_common_errors($param);
65
66     # trim email address
67     if ( $param->{addrlist} ) {
68         $param->{addrlist} =~ s|^\s+||;
69         $param->{addrlist} =~ s|\s+$||;
70     }
71
72     #get some list details
73     my $shelf;
74     my $shelfnumber = $param->{shelfnumber};
75     $shelf = Koha::Virtualshelves->find( $shelfnumber ) unless $param->{errcode};
76     $param->{shelfname} = $shelf ? $shelf->shelfname : q||;
77     $param->{owner}     = $shelf ? $shelf->owner : -1;
78     $param->{category}  = $shelf ? $shelf->category : -1;
79
80     load_template($param);
81     return $param;
82 }
83
84 sub check_common_errors {
85     my ($param) = @_;
86     if ( $param->{op} !~ /^(invite|conf_invite|accept)$/ ) {
87         return 1;    #no operation specified
88     }
89     if ( $param->{shelfnumber} !~ /^\d+$/ ) {
90         return 2;    #invalid shelf number
91     }
92     if ( !C4::Context->preference('OpacAllowSharingPrivateLists') ) {
93         return 3;    #not or no longer allowed?
94     }
95     return;
96 }
97
98 sub show_invite {
99     my ($param) = @_;
100     return unless check_owner_category($param);
101 }
102
103 sub confirm_invite {
104     my ($param) = @_;
105     return unless check_owner_category($param);
106     process_addrlist($param);
107     if ( @{ $param->{appr_addr} } ) {
108         send_invitekey($param);
109     }
110     else {
111         $param->{errcode} = 6;    #not one valid address
112     }
113 }
114
115 sub show_accept {
116     my ($param) = @_;
117
118     my $shelfnumber = $param->{shelfnumber};
119     my $shelf = Koha::Virtualshelves->find( $shelfnumber );
120
121     # The key for accepting is checked later in Koha::Virtualshelf->share
122     # You must not be the owner and the list must be private
123     if ( $shelf->category == 2 or $shelf->owner == $param->{loggedinuser} ) {
124         return;
125     }
126
127     # We could have used ->find with the share id, but we don't want to change
128     # the url sent to the patron
129     my $shared_shelf = Koha::Virtualshelfshares->search(
130         {
131             shelfnumber => $param->{shelfnumber},
132         },
133         {
134             order_by => 'sharedate desc',
135             limit => 1,
136         }
137     );
138
139     if ( $shared_shelf ) {
140         $shared_shelf = $shared_shelf->next;
141         my $key = keytostring( stringtokey( $param->{key}, 0 ), 1 );
142         my $is_accepted = eval { $shared_shelf->accept( $key, $param->{loggedinuser} ) };
143         if ( $is_accepted ) {
144             notify_owner($param);
145
146             #redirect to view of this shared list
147             print $param->{query}->redirect(
148                 -uri    => SHELVES_URL . $param->{shelfnumber},
149                 -cookie => $param->{cookie}
150             );
151             exit;
152         }
153         $param->{errcode} = 7;    #not accepted (key not found or expired)
154     } else {
155         # This shelf is not shared
156     }
157 }
158
159 sub notify_owner {
160     my ($param) = @_;
161
162     my $toaddr = C4::Members::GetNoticeEmailAddress( $param->{owner} );
163     return if !$toaddr;
164
165     #prepare letter
166     my $letter = C4::Letters::GetPreparedLetter(
167         module      => 'members',
168         letter_code => 'SHARE_ACCEPT',
169         branchcode  => C4::Context->userenv->{"branch"},
170         tables      => { borrowers => $param->{loggedinuser}, },
171         substitute  => { listname => $param->{shelfname}, },
172     );
173
174     #send letter to queue
175     C4::Letters::EnqueueLetter(
176         {
177             letter                 => $letter,
178             message_transport_type => 'email',
179             from_address => C4::Context->preference('KohaAdminEmailAddress'),
180             to_address   => $toaddr,
181         }
182     );
183 }
184
185 sub process_addrlist {
186     my ($param) = @_;
187     my @temp = split /[,:;]/, $param->{addrlist};
188     my @appr_addr;
189     my @fail_addr;
190     foreach my $a (@temp) {
191         $a =~ s/^\s+//;
192         $a =~ s/\s+$//;
193         if ( IsEmailAddress($a) ) {
194             push @appr_addr, $a;
195         }
196         else {
197             push @fail_addr, $a;
198         }
199     }
200     $param->{appr_addr} = \@appr_addr;
201     $param->{fail_addr} = \@fail_addr;
202 }
203
204 sub send_invitekey {
205     my ($param) = @_;
206     my $fromaddr = C4::Context->preference('KohaAdminEmailAddress');
207     my $url =
208         C4::Context->preference('OPACBaseURL')
209       . "/cgi-bin/koha/opac-shareshelf.pl?shelfnumber="
210       . $param->{shelfnumber}
211       . "&op=accept&key=";
212
213     #TODO Waiting for the right http or https solution (BZ 8952 a.o.)
214
215     my @ok;    #the addresses that were processed well
216     foreach my $a ( @{ $param->{appr_addr} } ) {
217         my @newkey = randomlist( KEYLENGTH, 64 );    #generate a new key
218
219         #add a preliminary share record
220         my $shelf = Koha::Virtualshelves->find( $param->{shelfnumber} );
221         my $key = keytostring( \@newkey, 1 );
222         my $is_shared = eval { $shelf->share( $key ); };
223         # TODO Better error handling, catch the exceptions
224         if ( $@ or not $is_shared ) {
225             push @{ $param->{fail_addr} }, $a;
226             next;
227         }
228         push @ok, $a;
229
230         #prepare letter
231         my $letter = C4::Letters::GetPreparedLetter(
232             module      => 'members',
233             letter_code => 'SHARE_INVITE',
234             branchcode  => C4::Context->userenv->{"branch"},
235             tables      => { borrowers => $param->{loggedinuser}, },
236             substitute  => {
237                 listname => $param->{shelfname},
238                 shareurl => $url . keytostring( \@newkey, 0 ),
239             },
240         );
241
242         #send letter to queue
243         C4::Letters::EnqueueLetter(
244             {
245                 letter                 => $letter,
246                 message_transport_type => 'email',
247                 from_address           => $fromaddr,
248                 to_address             => $a,
249             }
250         );
251     }
252     $param->{appr_addr} = \@ok;
253 }
254
255 sub check_owner_category {
256     my ($param) = @_;
257
258     #sharing user should be the owner
259     #list should be private
260     $param->{errcode} = 4 if $param->{owner} != $param->{loggedinuser};
261     $param->{errcode} = 5 if !$param->{errcode} && $param->{category} != 1;
262     return !defined $param->{errcode};
263 }
264
265 sub load_template {
266     my ($param) = @_;
267     ( $param->{template}, $param->{loggedinuser}, $param->{cookie} ) =
268       get_template_and_user(
269         {
270             template_name   => TEMPLATE_NAME,
271             query           => $param->{query},
272             type            => "opac",
273             authnotrequired => 0,                 #should be a user
274         }
275       );
276 }
277
278 sub load_template_vars {
279     my ($param) = @_;
280     my $template = $param->{template};
281     my $appr = join '; ', @{ $param->{appr_addr} };
282     my $fail = join '; ', @{ $param->{fail_addr} };
283     $template->param(
284         errcode         => $param->{errcode},
285         op              => $param->{op},
286         shelfnumber     => $param->{shelfnumber},
287         shelfname       => $param->{shelfname},
288         approvedaddress => $appr,
289         failaddress     => $fail,
290     );
291 }
292
293 sub IsEmailAddress {
294
295     #TODO candidate for a module?
296     return Email::Valid->address( $_[0] ) ? 1 : 0;
297 }
298
299 sub randomlist {
300
301     #uses rand, safe enough for this application but not for more sensitive data
302     my ( $length, $base ) = @_;
303     return map { int( rand($base) ); } 1 .. $length;
304 }
305
306 sub keytostring {
307     my ( $keyref, $flgBase64 ) = @_;
308     if ($flgBase64) {
309         my $alphabet = [ 'A' .. 'Z', 'a' .. 'z', 0 .. 9, '+', '/' ];
310         return join '', map { alphabet_char( $_, $alphabet ); } @$keyref;
311     }
312     return join '', map { sprintf( "%02d", $_ ); } @$keyref;
313 }
314
315 sub stringtokey {
316     my ( $str, $flgBase64 ) = @_;
317     my @temp = split '', $str || '';
318     if ($flgBase64) {
319         my $alphabet = [ 'A' .. 'Z', 'a' .. 'z', 0 .. 9, '+', '/' ];
320         return [ map { alphabet_ordinal( $_, $alphabet ); } @temp ];
321     }
322     return [] if $str !~ /^\d+$/;
323     my @retval;
324     for ( my $i = 0 ; $i < @temp - 1 ; $i += 2 ) {
325         push @retval, $temp[$i] * 10 + $temp[ $i + 1 ];
326     }
327     return \@retval;
328 }
329
330 sub alphabet_ordinal {
331     my ( $char, $alphabet ) = @_;
332     for my $ord ( 0 .. $#$alphabet ) {
333         return $ord if $char eq $alphabet->[$ord];
334     }
335     return '';    #ignore missing chars
336 }
337
338 sub alphabet_char {
339
340     #reverse operation for ordinal; ignore invalid numbers
341     my ( $num, $alphabet ) = @_;
342     return $num =~ /^\d+$/ && $num <= $#$alphabet ? $alphabet->[$num] : '';
343 }