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