Bug 22831: (follow-up) Sort results, group by db
[koha.git] / opac / opac-shareshelf.pl
blob34a17788ed6e825c18d27773a77dd7405e8117d1
1 #!/usr/bin/perl
3 # Copyright 2013 Rijksmuseum
5 # This file is part of Koha.
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.
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.
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>.
20 use Modern::Perl;
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=';
27 use CGI qw ( -utf8 );
28 use Email::Valid;
30 use C4::Auth;
31 use C4::Context;
32 use C4::Letters;
33 use C4::Members ();
34 use C4::Output;
36 use Koha::Patrons;
37 use Koha::Virtualshelves;
38 use Koha::Virtualshelfshares;
41 # if virtualshelves is disabled, leave immediately
42 if ( ! C4::Context->preference('virtualshelves') ) {
43 my $query = new CGI;
44 print $query->redirect("/cgi-bin/koha/errors/404.pl");
45 exit;
48 #-------------------------------------------------------------------------------
50 my $pvar = _init( {} );
51 if ( !$pvar->{errcode} ) {
52 show_invite($pvar) if $pvar->{op} eq 'invite';
53 confirm_invite($pvar) if $pvar->{op} eq 'conf_invite';
54 show_accept($pvar) if $pvar->{op} eq 'accept';
56 load_template_vars($pvar);
57 output_html_with_http_headers $pvar->{query}, $pvar->{cookie}, $pvar->{template}->output, undef, { force_no_caching => 1 };
59 #-------------------------------------------------------------------------------
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);
73 # trim email address
74 if ( $param->{addrlist} ) {
75 $param->{addrlist} =~ s|^\s+||;
76 $param->{addrlist} =~ s|\s+$||;
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;
87 load_template($param);
88 return $param;
91 sub check_common_errors {
92 my ($param) = @_;
93 if ( $param->{op} !~ /^(invite|conf_invite|accept)$/ ) {
94 return 1; #no operation specified
96 if ( $param->{shelfnumber} !~ /^\d+$/ ) {
97 return 2; #invalid shelf number
99 if ( !C4::Context->preference('OpacAllowSharingPrivateLists') ) {
100 return 3; #not or no longer allowed?
102 return;
105 sub show_invite {
106 my ($param) = @_;
107 return unless check_owner_category($param);
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);
117 else {
118 $param->{errcode} = 6; #not one valid address
122 sub show_accept {
123 my ($param) = @_;
125 my $shelfnumber = $param->{shelfnumber};
126 my $shelf = Koha::Virtualshelves->find( $shelfnumber );
128 # The key for accepting is checked later in Koha::Virtualshelfshare
129 # You must not be the owner and the list must be private
130 if( !$shelf ) {
131 $param->{errcode} = 2;
132 } elsif( $shelf->category == 2 ) {
133 $param->{errcode} = 5;
134 } elsif( $shelf->owner == $param->{loggedinuser} ) {
135 $param->{errcode} = 8;
137 return if $param->{errcode};
139 # Look for shelfnumber and invitekey in shares, expiration check later
140 my $key = keytostring( stringtokey( $param->{key}, 0 ), 1 );
141 my $shared_shelves = Koha::Virtualshelfshares->search({
142 shelfnumber => $param->{shelfnumber},
143 invitekey => $key,
145 my $shared_shelf = $shared_shelves ? $shared_shelves->next : undef; # we pick the first, but there should only be one
147 if ( $shared_shelf ) {
148 my $is_accepted = eval { $shared_shelf->accept( $key, $param->{loggedinuser} ) };
149 if( $is_accepted ) {
150 notify_owner($param);
151 #redirect to view of this shared list
152 print $param->{query}->redirect(
153 -uri => SHELVES_URL . $param->{shelfnumber},
154 -cookie => $param->{cookie}
156 exit;
159 $param->{errcode} = 7; # not accepted: key invalid or expired
162 sub notify_owner {
163 my ($param) = @_;
165 my $patron = Koha::Patrons->find( $param->{owner} );
166 return unless $patron;
168 my $toaddr = $patron->notice_email_address or return;
170 #prepare letter
171 my $letter = C4::Letters::GetPreparedLetter(
172 module => 'members',
173 letter_code => 'SHARE_ACCEPT',
174 branchcode => C4::Context->userenv->{"branch"},
175 lang => $patron->lang,
176 tables => { borrowers => $param->{loggedinuser}, },
177 substitute => { listname => $param->{shelfname}, },
180 #send letter to queue
181 C4::Letters::EnqueueLetter(
183 letter => $letter,
184 message_transport_type => 'email',
185 from_address => C4::Context->preference('KohaAdminEmailAddress'),
186 to_address => $toaddr,
191 sub process_addrlist {
192 my ($param) = @_;
193 my @temp = split /[,:;]/, $param->{addrlist};
194 my @appr_addr;
195 my @fail_addr;
196 foreach my $a (@temp) {
197 $a =~ s/^\s+//;
198 $a =~ s/\s+$//;
199 if ( IsEmailAddress($a) ) {
200 push @appr_addr, $a;
202 else {
203 push @fail_addr, $a;
206 $param->{appr_addr} = \@appr_addr;
207 $param->{fail_addr} = \@fail_addr;
210 sub send_invitekey {
211 my ($param) = @_;
212 my $fromaddr = C4::Context->preference('KohaAdminEmailAddress');
213 my $url =
214 C4::Context->preference('OPACBaseURL')
215 . "/cgi-bin/koha/opac-shareshelf.pl?shelfnumber="
216 . $param->{shelfnumber}
217 . "&op=accept&key=";
219 #TODO Waiting for the right http or https solution (BZ 8952 a.o.)
221 my @ok; #the addresses that were processed well
222 foreach my $a ( @{ $param->{appr_addr} } ) {
223 my @newkey = randomlist( KEYLENGTH, 64 ); #generate a new key
225 #add a preliminary share record
226 my $shelf = Koha::Virtualshelves->find( $param->{shelfnumber} );
227 my $key = keytostring( \@newkey, 1 );
228 my $is_shared = eval { $shelf->share( $key ); };
229 # TODO Better error handling, catch the exceptions
230 if ( $@ or not $is_shared ) {
231 push @{ $param->{fail_addr} }, $a;
232 next;
234 push @ok, $a;
236 #prepare letter
237 my $letter = C4::Letters::GetPreparedLetter(
238 module => 'members',
239 letter_code => 'SHARE_INVITE',
240 branchcode => C4::Context->userenv->{"branch"},
241 lang => 'default', # Not sure how we could use something more useful else here
242 tables => { borrowers => $param->{loggedinuser}, },
243 substitute => {
244 listname => $param->{shelfname},
245 shareurl => $url . keytostring( \@newkey, 0 ),
249 #send letter to queue
250 C4::Letters::EnqueueLetter(
252 letter => $letter,
253 message_transport_type => 'email',
254 from_address => $fromaddr,
255 to_address => $a,
259 $param->{appr_addr} = \@ok;
262 sub check_owner_category {
263 my ($param) = @_;
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};
272 sub load_template {
273 my ($param) = @_;
274 ( $param->{template}, $param->{loggedinuser}, $param->{cookie} ) =
275 get_template_and_user(
277 template_name => TEMPLATE_NAME,
278 query => $param->{query},
279 type => "opac",
280 authnotrequired => 0, #should be a user
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,
300 sub IsEmailAddress {
302 #TODO candidate for a module?
303 return Email::Valid->address( $_[0] ) ? 1 : 0;
306 sub randomlist {
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;
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;
319 return join '', map { sprintf( "%02d", $_ ); } @$keyref;
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 ];
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 ];
334 return \@retval;
337 sub alphabet_ordinal {
338 my ( $char, $alphabet ) = @_;
339 for my $ord ( 0 .. $#$alphabet ) {
340 return $ord if $char eq $alphabet->[$ord];
342 return ''; #ignore missing chars
345 sub alphabet_char {
347 #reverse operation for ordinal; ignore invalid numbers
348 my ( $num, $alphabet ) = @_;
349 return $num =~ /^\d+$/ && $num <= $#$alphabet ? $alphabet->[$num] : '';