&ModShelf
&ShelfPossibleAction
&DelFromShelf &DelShelf
- &GetBibliosShelves &AddShare
+ &GetBibliosShelves
+ &AddShare &AcceptShare &RemoveShare &IsSharedList
);
@EXPORT_OK = qw(
&GetAllShelves &ShelvesMax
C<$loggedinuser,$shelfnumber,$action>
$action can be "view", "add", "delete", "manage", "new_public", "new_private".
+New additional actions are: invite, acceptshare.
Note that add/delete here refers to adding/deleting entries from the list. Deleting the list itself falls under manage.
new_public and new_private refers to creating a new public or private list.
The distinction between deleting your own entries from the list or entries from
Returns 1 if the user can do the $action in the $shelfnumber shelf.
Returns 0 otherwise.
+For the actions invite and acceptshare a second errorcode is returned if the
+result is false. See opac-shareshelf.pl
=cut
#DelFromShelf checks the situation per biblio
return 1 if $user>0 && ($shelf->{allow_delete_own}==1 || $shelf->{allow_delete_other}==1);
}
+ elsif($action eq 'invite') {
+ #for sharing you must be the owner and the list must be private
+ if( $shelf->{category}==1 ) {
+ return 1 if $shelf->{owner}==$user;
+ return (0, 4); # code 4: should be owner
+ }
+ else {
+ return (0, 5); # code 5: should be private list
+ }
+ }
+ elsif($action eq 'acceptshare') {
+ #the key for accepting is checked later in AcceptShare
+ #you must not be the owner, list must be private
+ if( $shelf->{category}==1 ) {
+ return (0, 8) if $shelf->{owner}==$user;
+ #code 8: should not be owner
+ return 1;
+ }
+ else {
+ return (0, 5); # code 5: should be private list
+ }
+ }
elsif($action eq 'manage') {
return 1 if $user && $shelf->{owner}==$user;
}
$dbh->do($sql);
$sql="INSERT INTO virtualshelfshares (shelfnumber, invitekey, sharedate) VALUES (?, ?, ADDDATE(NOW(),?))";
$dbh->do($sql, undef, ($shelfnumber, $key, SHARE_INVITATION_EXPIRY_DAYS));
+ return !$dbh->err;
+}
+
+=head2 AcceptShare
+
+ my $result= AcceptShare($shelfnumber, $key, $borrowernumber);
+
+Checks acceptation of a share request.
+Key must be found for this shelf. Invitation must not have expired.
+Returns true when accepted, false otherwise.
+
+=cut
+
+sub AcceptShare {
+ my ($shelfnumber, $key, $borrowernumber)= @_;
+ return if !$shelfnumber || !$key || !$borrowernumber;
+
+ my $sql;
+ my $dbh = C4::Context->dbh;
+ $sql="
+UPDATE virtualshelfshares
+SET invitekey=NULL, sharedate=NULL, borrowernumber=?
+WHERE shelfnumber=? AND invitekey=? AND sharedate>NOW()
+ ";
+ my $i= $dbh->do($sql, undef, ($borrowernumber, $shelfnumber, $key));
+ return if !defined($i) || !$i || $i eq '0E0'; #not found
+ return 1;
+}
+
+=head2 IsSharedList
+
+ my $bool= IsSharedList( $shelfnumber );
+
+IsSharedList checks if a (private) list has shares.
+Note that such a check would not be useful for public lists. A public list has
+no shares, but is visible for anyone by nature..
+Used to determine the list type in the display of Your lists (all private).
+Returns boolean value.
+
+=cut
+
+sub IsSharedList {
+ my ($shelfnumber) = @_;
+ my $dbh = C4::Context->dbh;
+ my $sql="SELECT id FROM virtualshelfshares WHERE shelfnumber=? AND borrowernumber IS NOT NULL";
+ my $sth = $dbh->prepare($sql);
+ $sth->execute($shelfnumber);
+ my ($rv)= $sth->fetchrow_array;
+ return defined($rv);
+}
+
+=head2 RemoveShare
+
+ RemoveShare( $user, $shelfnumber );
+
+RemoveShare removes a share for specific shelf and borrower.
+Returns true if a record could be deleted.
+
+=cut
+
+sub RemoveShare {
+ my ($user, $shelfnumber)= @_;
+ my $dbh = C4::Context->dbh;
+ my $sql="
+DELETE FROM virtualshelfshares
+WHERE borrowernumber=? AND shelfnumber=?
+ ";
+ my $n= $dbh->do($sql,undef,($user, $shelfnumber));
+ return if !defined($n) || !$n || $n eq '0E0'; #nothing removed
+ return 1;
}
# internal subs
#Deleting a shelf (asking for confirmation if it has entries)
foreach ( $query->param() ) {
- /DEL-(\d+)/ or next;
+ /(DEL|REMSHR)-(\d+)/ or next;
$delflag = 1;
- my $number = $1;
+ my $number = $2;
unless ( defined $shelflist->{$number} || defined $privshelflist->{$number} ) {
push( @paramsloop, { unrecognized => $number } );
last;
}
+ #remove a share
+ if(/REMSHR/) {
+ RemoveShare($loggedinuser, $number);
+ delete $shelflist->{$number} if exists $shelflist->{$number};
+ delete $privshelflist->{$number} if exists $privshelflist->{$number};
+ $stay=0;
+ next;
+ }
+ #
unless ( ShelfPossibleAction( $loggedinuser, $number, 'manage' ) ) {
push( @paramsloop, { nopermission => $shelfnumber } );
last;
$shelflist->{$element}->{ownername} = defined($member) ? $member->{firstname} . " " . $member->{surname} : '';
$numberCanManage++ if $canmanage; # possibly outmoded
if ( $shelflist->{$element}->{'category'} eq '1' ) {
+ $shelflist->{$element}->{shares} = IsSharedList($element);
push( @shelveslooppriv, $shelflist->{$element} );
} else {
push( @shelvesloop, $shelflist->{$element} );
choices:
no: "Don't allow"
yes: Allow
- - opac users to share private lists with other patrons. This feature is not active yet but will be released soon
+ - opac users to share private lists with other patrons.
Privacy:
-
<td><a href="shelves.pl?[% IF ( shelveslooppri.showprivateshelves ) %]display=privateshelves&[% END %]viewshelf=[% shelveslooppri.shelf %]&shelfoff=[% shelfoff %]">[% shelveslooppri.shelfname |html %]</a></td>
<td>[% shelveslooppri.count %] item(s)</td>
<td>[% IF ( shelveslooppri.sortfield == "author" ) %]Author[% ELSIF ( shelveslooppri.sortfield == "copyrightdate" ) %]Year[% ELSIF (shelveslooppri.sortfield == "itemcallnumber") %]Call number[% ELSE %]Title[% END %]</td>
- <td>[% IF ( shelveslooppri.viewcategory1 ) %]Private[% END %]
+ <td>[% IF ( shelveslooppri.viewcategory1 ) %][% IF !shelveslooppri.shares %]Private[% ELSE %]Shared[% END %][% END %]
[% IF ( shelveslooppri.viewcategory2 ) %]Public[% END %]
</td>
<td>
[% IF errcode==4 %]<div class="dialog alert">You can only share a list if you are the owner.</div>[% END %]
[% IF errcode==5 %]<div class="dialog alert">You cannot share a public list.</div>[% END %]
[% IF errcode==6 %]<div class="dialog alert">Sorry, but you did not enter any valid email address.</div>[% END %]
+ [% IF errcode==7 %]<div class="dialog alert">Sorry, but we could not accept this key. The invitation may have expired. Contact the patron who sent you the invitation.</div>[% END %]
+ [% IF errcode==8 %]<div class="dialog alert">As owner of a list you cannot accept an invitation for sharing it.</div>[% END %]
[% ELSIF op=='invite' %]
<form method="post" onsubmit="return $('#invite_address').val().trim()!='';">
</form>
[% ELSIF op=='conf_invite' %]
+ [% IF approvedaddress %]
<p>An invitation to share list <i>[% shelfname %]</i> has been sent to [% approvedaddress %].</p>
+ [% END %]
[% IF failaddress %]
- <p>The following addresses appear to be invalid. Please correct them and try again. These are: [% failaddress %]</p>
+ <p>Something went wrong while processing the following addresses. Please check them. These are: [% failaddress %]</p>
[% END %]
+ [% IF approvedaddress %]
<p>You will receive an email notification if someone accepts your share within two weeks.</p>
+ [% END %]
<p><a href="/cgi-bin/koha/opac-shelves.pl?display=privateshelves">Return to your lists</a></p>
[% ELSIF op=='accept' %]
- [%# TODO: Replace the following two lines %]
- <p>Thank you for testing this feature.</p>
- <p>Your signoff will certainly help in finishing the remaining part!</p>
-
+ [%# Nothing to do: we already display an error or we redirect. %]
[% END %]
[%# End of essential part %]
var MSG_REMOVE_FROM_LIST = _("Are you sure you want to remove these items from the list?");
var MSG_REMOVE_ONE_FROM_LIST = _("Are you sure you want to remove this item from the list?");
var MSG_CONFIRM_DELETE_LIST = _("Are you sure you want to delete this list?");
+var MSG_CONFIRM_REMOVE_SHARE = _("Are you sure you want to remove this share?");
[% IF ( opacuserlogin ) %][% IF ( RequestOnOpac ) %]
function holdSelections() {
</li>
[% END %]
+[%# When using the next block, add the parameter for shelfnumber and add a tag to end the form %]
+[% BLOCK remove_share %]
+ <form action="opac-shelves.pl" method="post">
+ <input type="hidden" name="shelves" value="1" />
+ <input type="hidden" name="display" value="privateshelves" />
+ <input type="hidden" name="shelfoff" value="[% shelfoff %]" />
+ <input type="submit" class="removeshare" onclick="return confirmDelete(MSG_CONFIRM_REMOVE_SHARE);" value="Remove share" />
+[% END %]
+
[% IF ( OpacNav ) %]<div id="doc3" class="yui-t1">[% ELSIF ( loggedinusername ) %]<div id="doc3" class="yui-t1">[% ELSE %]<div id="doc3" class="yui-t7">[% END %]
<div id="bd">
[% INCLUDE 'masthead.inc' %]
<input type="submit" class="Share" value="Share" />
</form>
[% END %]
+ [% ELSIF showprivateshelves %]
+ [% INCLUDE remove_share %]
+ <input type="hidden" name="REMSHR-[% shelfnumber %]" value="1" />
+ </form>
[% END %]
<ul class="link-tabs">
[% IF ( opacuserlogin ) %]
[% IF ( showprivateshelves ) %]
- <li id="privateshelves_tab" class="on"><a href="/cgi-bin/koha/opac-shelves.pl?display=privateshelves">Your private lists</a></li>
+ <li id="privateshelves_tab" class="on"><a href="/cgi-bin/koha/opac-shelves.pl?display=privateshelves">Your lists</a></li>
[% ELSE %]
- <li id="privateshelves_tab" class="off"><a href="/cgi-bin/koha/opac-shelves.pl?display=privateshelves">Your private lists</a></li>
+ <li id="privateshelves_tab" class="off"><a href="/cgi-bin/koha/opac-shelves.pl?display=privateshelves">Your lists</a></li>
[% END %]
[% END %]
[% IF ( showpublicshelves ) %]
<th>List name</th>
<th>Contents</th>
<th>Type</th>
- <th> </th>
+ <th>Options</th>
</tr>
[% FOREACH shelveslooppri IN shelveslooppriv %]
[% UNLESS ( loop.odd ) %]
<td><a href="/cgi-bin/koha/opac-shelves.pl?display=privateshelves&viewshelf=[% shelveslooppri.shelf %]&sortfield=[% shelveslooppri.sortfield %]">[% shelveslooppri.shelfname |html %]</a></td>
<td>[% IF ( shelveslooppri.count ) %][% shelveslooppri.count %] [% IF ( shelveslooppri.single ) %]item[% ELSE %]items[% END %][% ELSE %]Empty[% END %]</td>
<td>
- [% IF ( shelveslooppri.viewcategory1 ) %]Private[% END %]
+ [% IF ( shelveslooppri.viewcategory1 ) %][% IF !shelveslooppri.shares %]Private[% ELSE %]Shared[% END %][% END %]
[% IF ( shelveslooppri.viewcategory2 ) %]Public[% END %]
</td>
<td>
<input type="submit" class="Share" value="Share" />
</form>
[% END %]
+ [% ELSIF shelveslooppri.shares %]
+ [% INCLUDE remove_share %]
+ <input type="hidden" name="REMSHR-[% shelveslooppri.shelf %]" value="1" />
+ </form>
[% END %]
</td>
</tr>
<tr>
<th>List name</th>
<th>Contents</th>
- <th>Type</th><th> </th>
+ <th>Type</th>
+ <th>Options</th>
</tr>
[% FOREACH shelvesloo IN shelvesloop %]
[% UNLESS ( loop.odd ) %]
use constant KEYLENGTH => 10;
use constant TEMPLATE_NAME => 'opac-shareshelf.tmpl';
+use constant SHELVES_URL => '/cgi-bin/koha/opac-shelves.pl?display=privateshelves&viewshelf=';
use CGI;
use Email::Valid;
use C4::Auth;
use C4::Context;
use C4::Letters;
+use C4::Members ();
use C4::Output;
use C4::VirtualShelves;
$param->{addrlist} = $query->param('invite_address')||'';
$param->{key} = $query->param('key')||'';
$param->{appr_addr} = [];
-
+ $param->{fail_addr} = [];
$param->{errcode} = check_common_errors($param);
+
+ #get some list details
+ my @temp;
+ @temp= GetShelf( $param->{shelfnumber} ) if !$param->{errcode};
+ $param->{shelfname} = @temp? $temp[1]: '';
+ $param->{owner} = @temp? $temp[2]: -1;
+ $param->{category} = @temp? $temp[3]: -1;
+
load_template($param);
return $param;
}
sub show_accept {
my ($param) = @_;
- #TODO Add some code here to accept an invitation (followup report)
+
+ my @rv= ShelfPossibleAction($param->{loggedinuser},
+ $param->{shelfnumber}, 'acceptshare');
+ $param->{errcode} = $rv[1] if !$rv[0];
+ return if $param->{errcode};
+ #errorcode 5: should be private list
+ #errorcode 8: should not be owner
+
+ my $dbkey= keytostring( stringtokey($param->{key}, 0), 1);
+ if( AcceptShare($param->{shelfnumber}, $dbkey, $param->{loggedinuser} ) ) {
+ notify_owner($param);
+ #redirect to view of this shared list
+ print $param->{query}->redirect(SHELVES_URL.$param->{shelfnumber});
+ exit;
+ }
+ else {
+ $param->{errcode} = 7; #not accepted (key not found or expired)
+ }
+}
+
+sub notify_owner {
+ my ($param) = @_;
+
+ my $toaddr= C4::Members::GetNoticeEmailAddress( $param->{owner} );
+ return if !$toaddr;
+
+ #prepare letter
+ my $letter= C4::Letters::GetPreparedLetter(
+ module => 'members',
+ letter_code => 'SHARE_ACCEPT',
+ branchcode => C4::Context->userenv->{"branch"},
+ 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 ($param) = @_;
my @temp= split /[,:;]/, $param->{addrlist};
my @appr_addr;
- my $fail_addr='';
+ my @fail_addr;
foreach my $a (@temp) {
$a=~s/^\s+//;
$a=~s/\s+$//;
push @appr_addr, $a;
}
else {
- $fail_addr.= ($fail_addr? '; ': '').$a;
+ push @fail_addr, $a;
}
}
$param->{appr_addr}= \@appr_addr;
- $param->{fail_addr}= $fail_addr;
+ $param->{fail_addr}= \@fail_addr;
}
sub send_invitekey {
$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
+ #add a preliminary share record
+ if( ! AddShare( $param->{shelfnumber}, keytostring(\@newkey,1) ) ) {
+ push @{$param->{fail_addr}}, $a;
+ next;
+ }
+ push @ok, $a;
+
#prepare letter
my $letter= C4::Letters::GetPreparedLetter(
module => 'members',
from_address => $fromaddr,
to_address => $a,
});
- #add a preliminary share record
- AddShare( $param->{shelfnumber}, keytostring(\@newkey,1));
}
+ $param->{appr_addr}= \@ok;
}
sub check_owner_category {
my ($param)= @_;
- #TODO candidate for a module?
- #need to get back the two different error codes and the shelfname
-
- ( undef, $param->{shelfname}, $param->{owner}, my $category ) =
- GetShelf( $param->{shelfnumber} );
+ #sharing user should be the owner
+ #list should be private
$param->{errcode}=4 if $param->{owner}!= $param->{loggedinuser};
- $param->{errcode}=5 if !$param->{errcode} && $category!=1;
- #should be private
+ $param->{errcode}=5 if !$param->{errcode} && $param->{category}!=1;
return !defined $param->{errcode};
}
sub load_template_vars {
my ($param) = @_;
my $template = $param->{template};
- my $str= join '; ', @{$param->{appr_addr}};
+ my $appr= join '; ', @{$param->{appr_addr}};
+ my $fail= join '; ', @{$param->{fail_addr}};
$template->param(
errcode => $param->{errcode},
op => $param->{op},
shelfnumber => $param->{shelfnumber},
shelfname => $param->{shelfname},
- approvedaddress => $str,
- failaddress => $param->{fail_addr},
+ approvedaddress => $appr,
+ failaddress => $fail,
);
}
my @temp=split '', $str||'';
if($flgBase64) {
my $alphabet= [ 'A'..'Z', 'a'..'z', 0..9, '+', '/' ];
- return map { alphabet_ordinal($_, $alphabet); } @temp;
+ 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];
}
- return @retval;
+ return \@retval;
}
sub alphabet_ordinal {