Bug 14385: Squash of a lot of patches rebased
[koha.git] / opac / opac-shareshelf.pl
index 6c95f0d..8e79792 100755 (executable)
 #
 # This file is part of Koha.
 #
-# Koha is free software; you can redistribute it and/or modify it under the
-# terms of the GNU General Public License as published by the Free Software
-# Foundation; either version 2 of the License, or (at your option) any later
-# version.
+# Koha is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
 #
-# Koha is distributed in the hope that it will be useful, but WITHOUT ANY
-# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
-# A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+# Koha is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
 #
-# You should have received a copy of the GNU General Public License along
-# with Koha; if not, write to the Free Software Foundation, Inc.,
-# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+# You should have received a copy of the GNU General Public License
+# along with Koha; if not, see <http://www.gnu.org/licenses>.
 
-use strict;
-use warnings;
+use Modern::Perl;
 
-use constant KEYLENGTH => 10;
+use constant KEYLENGTH     => 10;
+use constant TEMPLATE_NAME => 'opac-shareshelf.tt';
+use constant SHELVES_URL =>
+  '/cgi-bin/koha/opac-shelves.pl?display=privateshelves&viewshelf=';
 
-use CGI;
+use CGI qw ( -utf8 );
 use Email::Valid;
 
 use C4::Auth;
 use C4::Context;
 use C4::Letters;
+use C4::Members ();
 use C4::Output;
-use C4::VirtualShelves;
 
-#-------------------------------------------------------------------------------
+use Koha::Patrons;
+use Koha::Virtualshelves;
+use Koha::Virtualshelfshares;
 
-my $query= new CGI;
-my ($shelfname, $owner);
-my ($template, $loggedinuser, $cookie);
-my $errcode=0;
-my (@addr, $fail_addr, @newkey);
-my @base64alphabet= ('A'..'Z', 'a'..'z', 0..9, '+', '/');
 
-my $shelfnumber= $query->param('shelfnumber')||0;
-my $op= $query->param('op')||'';
-my $addrlist= $query->param('invite_address')||'';
-my $key= $query->param('key')||'';
+# if virtualshelves is disabled, leave immediately
+if ( ! C4::Context->preference('virtualshelves') ) {
+    my $query = new CGI;
+    print $query->redirect("/cgi-bin/koha/errors/404.pl");
+    exit;
+}
 
 #-------------------------------------------------------------------------------
 
-check_common_errors();
-load_template("opac-shareshelf.tmpl");
-if($errcode) {
-    #nothing to do
-}
-elsif($op eq 'invite') {
-    show_invite();
+my $pvar = _init( {} );
+if ( !$pvar->{errcode} ) {
+    show_invite($pvar)    if $pvar->{op} eq 'invite';
+    confirm_invite($pvar) if $pvar->{op} eq 'conf_invite';
+    show_accept($pvar)    if $pvar->{op} eq 'accept';
 }
-elsif($op eq 'conf_invite') {
-    confirm_invite();
-}
-elsif($op eq 'accept') {
-    show_accept();
-}
-load_template_vars();
-output_html_with_http_headers $query, $cookie, $template->output;
+load_template_vars($pvar);
+output_html_with_http_headers $pvar->{query}, $pvar->{cookie},
+  $pvar->{template}->output;
 
 #-------------------------------------------------------------------------------
 
+sub _init {
+    my ($param) = @_;
+    my $query = new CGI;
+    $param->{query}       = $query;
+    $param->{shelfnumber} = $query->param('shelfnumber') || 0;
+    $param->{op}          = $query->param('op') || '';
+    $param->{addrlist}    = $query->param('invite_address') || '';
+    $param->{key}         = $query->param('key') || '';
+    $param->{appr_addr}   = [];
+    $param->{fail_addr}   = [];
+    $param->{errcode}     = check_common_errors($param);
+
+    # trim email address
+    if ( $param->{addrlist} ) {
+        $param->{addrlist} =~ s|^\s+||;
+        $param->{addrlist} =~ s|\s+$||;
+    }
+
+    #get some list details
+    my $shelf;
+    my $shelfnumber = $param->{shelfnumber};
+    $shelf = Koha::Virtualshelves->find( $shelfnumber ) unless $param->{errcode};
+    $param->{shelfname} = $shelf ? $shelf->shelfname : q||;
+    $param->{owner}     = $shelf ? $shelf->owner : -1;
+    $param->{category}  = $shelf ? $shelf->category : -1;
+
+    load_template($param);
+    return $param;
+}
+
 sub check_common_errors {
-    if($op!~/^(invite|conf_invite|accept)$/) {
-        $errcode=1; #no operation specified
-        return;
+    my ($param) = @_;
+    if ( $param->{op} !~ /^(invite|conf_invite|accept)$/ ) {
+        return 1;    #no operation specified
     }
-    if($shelfnumber!~/^\d+$/) {
-        $errcode=2; #invalid shelf number
-        return;
+    if ( $param->{shelfnumber} !~ /^\d+$/ ) {
+        return 2;    #invalid shelf number
     }
-    if(!C4::Context->preference('OpacAllowSharingPrivateLists')) {
-        $errcode=3; #not or no longer allowed?
-        return;
+    if ( !C4::Context->preference('OpacAllowSharingPrivateLists') ) {
+        return 3;    #not or no longer allowed?
     }
+    return;
 }
 
 sub show_invite {
-    return unless check_owner_category();
+    my ($param) = @_;
+    return unless check_owner_category($param);
 }
 
 sub confirm_invite {
-    return unless check_owner_category();
-    process_addrlist();
-    if(@addr) {
-        send_invitekey();
+    my ($param) = @_;
+    return unless check_owner_category($param);
+    process_addrlist($param);
+    if ( @{ $param->{appr_addr} } ) {
+        send_invitekey($param);
     }
     else {
-        $errcode=6; #not one valid address
+        $param->{errcode} = 6;    #not one valid address
     }
 }
 
 sub show_accept {
-    #TODO Add some code here to accept an invitation (followup report)
+    my ($param) = @_;
+
+    my $shelfnumber = $param->{shelfnumber};
+    my $shelf = Koha::Virtualshelves->find( $shelfnumber );
+
+    # The key for accepting is checked later in Koha::Virtualshelfshare
+    # You must not be the owner and the list must be private
+    if( !$shelf ) {
+        $param->{errcode} = 2;
+    } elsif( $shelf->category == 2 ) {
+        $param->{errcode} = 5;
+    } elsif( $shelf->owner == $param->{loggedinuser} ) {
+        $param->{errcode} = 8;
+    }
+    return if $param->{errcode};
+
+    # Look for shelfnumber and invitekey in shares, expiration check later
+    my $key = keytostring( stringtokey( $param->{key}, 0 ), 1 );
+    my $shared_shelves = Koha::Virtualshelfshares->search({
+        shelfnumber => $param->{shelfnumber},
+        invitekey => $key,
+    });
+    my $shared_shelf = $shared_shelves ? $shared_shelves->next : undef; # we pick the first, but there should only be one
+
+    if ( $shared_shelf ) {
+        my $is_accepted = eval { $shared_shelf->accept( $key, $param->{loggedinuser} ) };
+        if( $is_accepted ) {
+            notify_owner($param);
+            #redirect to view of this shared list
+            print $param->{query}->redirect(
+                -uri    => SHELVES_URL . $param->{shelfnumber},
+                -cookie => $param->{cookie}
+            );
+            exit;
+        }
+    }
+    $param->{errcode} = 7; # not accepted: key invalid or expired
+}
+
+sub notify_owner {
+    my ($param) = @_;
+
+    my $patron = Koha::Patrons->find( $param->{owner} );
+    return unless $patron;
+
+    my $toaddr = $patron->notice_email_address or return;
+
+    #prepare letter
+    my $letter = C4::Letters::GetPreparedLetter(
+        module      => 'members',
+        letter_code => 'SHARE_ACCEPT',
+        branchcode  => C4::Context->userenv->{"branch"},
+        lang        => $patron->lang,
+        tables      => { borrowers => $param->{loggedinuser}, },
+        substitute  => { listname => $param->{shelfname}, },
+    );
+
+    #send letter to queue
+    C4::Letters::EnqueueLetter(
+        {
+            letter                 => $letter,
+            message_transport_type => 'email',
+            from_address => C4::Context->preference('KohaAdminEmailAddress'),
+            to_address   => $toaddr,
+        }
+    );
 }
 
 sub process_addrlist {
-    my @temp= split /[,:;]/, $addrlist;
-    $fail_addr='';
+    my ($param) = @_;
+    my @temp = split /[,:;]/, $param->{addrlist};
+    my @appr_addr;
+    my @fail_addr;
     foreach my $a (@temp) {
-        $a=~s/^\s+//;
-        $a=~s/\s+$//;
-        if(IsEmailAddress($a)) {
-            push @addr, $a;
+        $a =~ s/^\s+//;
+        $a =~ s/\s+$//;
+        if ( IsEmailAddress($a) ) {
+            push @appr_addr, $a;
         }
         else {
-            $fail_addr.= ($fail_addr? '; ': '').$a;
+            push @fail_addr, $a;
         }
     }
+    $param->{appr_addr} = \@appr_addr;
+    $param->{fail_addr} = \@fail_addr;
 }
 
 sub send_invitekey {
-    my $fromaddr= C4::Context->preference('KohaAdminEmailAddress');
-    my $url= 'http://'.C4::Context->preference('OPACBaseURL');
-    $url.= "/cgi-bin/koha/opac-shareshelf.pl?shelfnumber=$shelfnumber";
-    $url.= "&op=accept&key=";
-        #FIXME Waiting for the right http or https solution (BZ 8952 a.o.)
+    my ($param) = @_;
+    my $fromaddr = C4::Context->preference('KohaAdminEmailAddress');
+    my $url =
+        C4::Context->preference('OPACBaseURL')
+      . "/cgi-bin/koha/opac-shareshelf.pl?shelfnumber="
+      . $param->{shelfnumber}
+      . "&op=accept&key=";
+
+    #TODO Waiting for the right http or https solution (BZ 8952 a.o.)
+
+    my @ok;    #the addresses that were processed well
+    foreach my $a ( @{ $param->{appr_addr} } ) {
+        my @newkey = randomlist( KEYLENGTH, 64 );    #generate a new key
 
-    foreach my $a (@addr) {
-        @newkey=randomlist(KEYLENGTH, 64); #generate a new key
+        #add a preliminary share record
+        my $shelf = Koha::Virtualshelves->find( $param->{shelfnumber} );
+        my $key = keytostring( \@newkey, 1 );
+        my $is_shared = eval { $shelf->share( $key ); };
+        # TODO Better error handling, catch the exceptions
+        if ( $@ or not $is_shared ) {
+            push @{ $param->{fail_addr} }, $a;
+            next;
+        }
+        push @ok, $a;
 
         #prepare letter
-        my $letter= C4::Letters::GetPreparedLetter(
-            module => 'members',
+        my $letter = C4::Letters::GetPreparedLetter(
+            module      => 'members',
             letter_code => 'SHARE_INVITE',
-            branchcode => C4::Context->userenv->{"branch"},
-            tables => { borrowers => $loggedinuser, },
-            substitute => {
-                listname => $shelfname,
-                shareurl => $url.keytostring(\@newkey,0),
+            branchcode  => C4::Context->userenv->{"branch"},
+            lang        => 'default', # Not sure how we could use something more useful else here
+            tables      => { borrowers => $param->{loggedinuser}, },
+            substitute  => {
+                listname => $param->{shelfname},
+                shareurl => $url . keytostring( \@newkey, 0 ),
             },
         );
 
         #send letter to queue
-        C4::Letters::EnqueueLetter( {
-            letter                 => $letter,
-            message_transport_type => 'email',
-            from_address           => $fromaddr,
-            to_address             => $a,
-        });
-        #add a preliminary share record
-        AddShare($shelfnumber,keytostring(\@newkey,1));
+        C4::Letters::EnqueueLetter(
+            {
+                letter                 => $letter,
+                message_transport_type => 'email',
+                from_address           => $fromaddr,
+                to_address             => $a,
+            }
+        );
     }
+    $param->{appr_addr} = \@ok;
 }
 
 sub check_owner_category {
-    #FIXME candidate for a module? what held me back is: getting back the two different error codes and the shelfname
-    (undef,$shelfname,$owner,my $category)= GetShelf($shelfnumber);
-    $errcode=4 if $owner!= $loggedinuser; #should be owner
-    $errcode=5 if !$errcode && $category!=1; #should be private
-    return $errcode==0;
+    my ($param) = @_;
+
+    #sharing user should be the owner
+    #list should be private
+    $param->{errcode} = 4 if $param->{owner} != $param->{loggedinuser};
+    $param->{errcode} = 5 if !$param->{errcode} && $param->{category} != 1;
+    return !defined $param->{errcode};
 }
 
 sub load_template {
-    my ($file)= @_;
-    ($template, $loggedinuser, $cookie)= get_template_and_user({
-        template_name   => $file,
-        query           => $query,
-        type            => "opac",
-        authnotrequired => 0, #should be a user
-    });
+    my ($param) = @_;
+    ( $param->{template}, $param->{loggedinuser}, $param->{cookie} ) =
+      get_template_and_user(
+        {
+            template_name   => TEMPLATE_NAME,
+            query           => $param->{query},
+            type            => "opac",
+            authnotrequired => 0,                 #should be a user
+        }
+      );
 }
 
 sub load_template_vars {
+    my ($param) = @_;
+    my $template = $param->{template};
+    my $appr = join '; ', @{ $param->{appr_addr} };
+    my $fail = join '; ', @{ $param->{fail_addr} };
     $template->param(
-        errcode         => $errcode,
-        op              => $op,
-        shelfnumber     => $shelfnumber,
-        shelfname       => $shelfname,
-        approvedaddress => (join '; ', @addr),
-        failaddress     => $fail_addr,
+        errcode         => $param->{errcode},
+        op              => $param->{op},
+        shelfnumber     => $param->{shelfnumber},
+        shelfname       => $param->{shelfname},
+        approvedaddress => $appr,
+        failaddress     => $fail,
     );
 }
 
 sub IsEmailAddress {
-    #FIXME candidate for a module?
-    return Email::Valid->address($_[0])? 1: 0;
+
+    #TODO candidate for a module?
+    return Email::Valid->address( $_[0] ) ? 1 : 0;
 }
 
 sub randomlist {
-#uses rand, safe enough for this application but not for more sensitive data
-    my ($length, $base)= @_;
-    return map { int(rand($base)); } 1..$length;
+
+    #uses rand, safe enough for this application but not for more sensitive data
+    my ( $length, $base ) = @_;
+    return map { int( rand($base) ); } 1 .. $length;
 }
 
 sub keytostring {
-    my ($keyref, $flgBase64)= @_;
-    if($flgBase64) {
-        return join '', map { base64chr($_); } @$keyref;
+    my ( $keyref, $flgBase64 ) = @_;
+    if ($flgBase64) {
+        my $alphabet = [ 'A' .. 'Z', 'a' .. 'z', 0 .. 9, '+', '/' ];
+        return join '', map { alphabet_char( $_, $alphabet ); } @$keyref;
     }
-    return join '', map { sprintf("%02d",$_); } @$keyref;
+    return join '', map { sprintf( "%02d", $_ ); } @$keyref;
 }
 
 sub stringtokey {
-    my ($str, $flgBase64)= @_;
-    my @temp=split '', $str||'';
-    if($flgBase64) {
-        return map { base64ord($_); } @temp;
+    my ( $str, $flgBase64 ) = @_;
+    my @temp = split '', $str || '';
+    if ($flgBase64) {
+        my $alphabet = [ 'A' .. 'Z', 'a' .. 'z', 0 .. 9, '+', '/' ];
+        return [ map { alphabet_ordinal( $_, $alphabet ); } @temp ];
     }
-    return () if $str!~/^\d+$/;
+    return [] if $str !~ /^\d+$/;
     my @retval;
-    for(my $i=0; $i<@temp-1; $i+=2) {
-        push @retval, $temp[$i]*10+$temp[$i+1];
+    for ( my $i = 0 ; $i < @temp - 1 ; $i += 2 ) {
+        push @retval, $temp[$i] * 10 + $temp[ $i + 1 ];
     }
-    return @retval;
+    return \@retval;
 }
 
-sub base64ord { #base64 ordinal
-    my ($char)=@_;
-    return 0 -ord('A')+ord($char) if $char=~/[A-Z]/;
-    return 26-ord('a')+ord($char) if $char=~/[a-z]/;
-    return 52-ord('0')+ord($char) if $char=~/[0-9]/;
-    return 62 if $char eq '+';
-    return 63 if $char eq '/';
-    return;
+sub alphabet_ordinal {
+    my ( $char, $alphabet ) = @_;
+    for my $ord ( 0 .. $#$alphabet ) {
+        return $ord if $char eq $alphabet->[$ord];
+    }
+    return '';    #ignore missing chars
 }
 
-sub base64chr { #reverse operation for ord
-    return $_[0]=~/^\d+$/ && $_[0]<64? $base64alphabet[$_[0]]: undef;
+sub alphabet_char {
+
+    #reverse operation for ordinal; ignore invalid numbers
+    my ( $num, $alphabet ) = @_;
+    return $num =~ /^\d+$/ && $num <= $#$alphabet ? $alphabet->[$num] : '';
 }