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