@@ -, +, @@
---
C4/Auth.pm | 4 +-
C4/Members.pm | 82 ++++++-
.../prog/en/modules/admin/preferences/opac.pref | 9 +-
koha-tmpl/opac-tmpl/prog/en/modules/opac-auth.tt | 3 +
koha-tmpl/opac-tmpl/prog/en/modules/opac-main.tt | 8 +-
.../prog/en/modules/opac-password-recovery.tt | 129 +++++++++++
.../prog/en/modules/opac-send-password-recovery.tt | 13 ++
opac/opac-main.pl | 1 +
opac/opac-password-recovery.pl | 224 ++++++++++++++++++++
9 files changed, 468 insertions(+), 5 deletions(-)
create mode 100644 koha-tmpl/opac-tmpl/prog/en/modules/opac-password-recovery.tt
create mode 100644 koha-tmpl/opac-tmpl/prog/en/modules/opac-send-password-recovery.tt
create mode 100755 opac/opac-password-recovery.pl
--- a/C4/Auth.pm
+++ a/C4/Auth.pm
@@ -1020,8 +1020,10 @@ sub checkauth {
PatronSelfRegistrationDefaultCategory => C4::Context->preference("PatronSelfRegistrationDefaultCategory"),
persona => C4::Context->preference("Persona"),
opac_css_override => $ENV{'OPAC_CSS_OVERRIDE'},
+ OpacResetPassword => C4::Context->preference("OpacResetPassword"),
+ OpacPasswordChange => C4::Context->preference("OpacPasswordChange"),
);
-
+
$template->param( OpacPublic => C4::Context->preference("OpacPublic"));
$template->param( loginprompt => 1 ) unless $info{'nopermission'};
--- a/C4/Members.pm
+++ a/C4/Members.pm
@@ -41,6 +41,12 @@ use DateTime::Format::DateParse;
use Koha::DateUtils;
use Text::Unaccent qw( unac_string );
+## Password recovery
+use CGI;
+use Encode qw(encode decode);
+use MIME::QuotedPrint qw(encode_qp);
+use Mail::Sendmail;
+
our ($VERSION,@ISA,@EXPORT,@EXPORT_OK,$debug);
BEGIN {
@@ -109,7 +115,12 @@ BEGIN {
push @EXPORT, qw(
&ModMember
&changepassword
- &ModPrivacy
+ &ModPrivacy
+ );
+
+ # Password recovery
+ push @EXPORT, qw(
+ &SendPasswordRecoveryEmail
);
#Delete data
@@ -2287,6 +2298,75 @@ sub ModPrivacy {
privacy => $privacy );
}
+
+sub SendPasswordRecoveryEmail {
+ my $borrowernumber = shift;
+ my $email = shift;
+ my $dbh = C4::Context->dbh;
+ #use constant EMAIL_ENCODE = 'iso-8859-1';
+ my $emailEncode = 'iso-8859-1';
+ my $username = GetMemberDetails($borrowernumber)->{userid};
+
+ #generate UUID
+ my @chars = ("A".."Z", "a".."z", "0".."9");
+ my $uuid_str;
+ $uuid_str .= $chars[rand @chars] for 1..32;
+
+ #insert into database
+ my $sth = $dbh->prepare( 'INSERT INTO borrower_password_recovery VALUES (? ,? , ADDDATE(NOW(), INTERVAL 2 DAY) )' );
+ $sth->execute($borrowernumber, $uuid_str);
+
+ my $userEmail = ( $email ) ? $email : GetFirstValidEmailAddress($borrowernumber);
+ #define to/from emails
+ my $kohaEmail = C4::Context->preference( 'KohaAdminEmailAddress' );
+
+ #create link
+ my $uuidLink = "http://" . C4::Context->preference( 'OPACBaseURL' ) . "/cgi-bin/koha/opac-password-recovery.pl?uniqueKey=$uuid_str";
+ #warn $uuidLink;
+
+ #build email content
+ my $query = new CGI;
+ my ( $template2, $borrower_number, $cookie ) = C4::Auth::get_template_and_user(
+ {
+ template_name => "opac-send-password-recovery.tmpl",
+ query => $query,
+ type => "opac",
+ authnotrequired => 1,
+ }
+ );
+ $template2->param(
+ uuidLink => $uuidLink,
+ username => $username,
+ );
+
+ # Getting template result and
+ my $template_res = $template2->output();
+
+ #Mail attributes
+ my %mail = (
+ To => $userEmail,
+ From => $kohaEmail,
+ 'Content-Type' => 'text/html; charset="' . $emailEncode . '"',
+ );
+
+ #getting mail properties
+ if ( $template_res =~ /(.*)/s ) { $mail{'subject'} = decode( 'utf-8', $1 ); }
+ if ( $template_res =~ /\n(.*)\n/s ) { $mail{'body'} = decode( 'utf-8', $1 ); }
+ #send mail
+ if ( sendmail %mail )
+ {
+ # if it works....
+ return 1;
+ }
+ else
+ {
+ # if it doesnt work....
+ warn "Error sending mail: $Mail::Sendmail::error \n";
+ return 0;
+ }
+}
+
+
=head2 AddMessage
AddMessage( $borrowernumber, $message_type, $message, $branchcode );
--- a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/opac.pref
+++ a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/opac.pref
@@ -551,7 +551,14 @@ OPAC:
track: "Track"
no: "Don't track"
- links that patrons click on
-
+ -
+ - pref: OpacResetPassword
+ default: 1
+ choices:
+ yes: "On"
+ no: "Off"
+ - ". If On, the user can reset his password on OPAC."
+
Shelf Browser:
-
- pref: OPACShelfBrowser
--- a/koha-tmpl/opac-tmpl/prog/en/modules/opac-auth.tt
+++ a/koha-tmpl/opac-tmpl/prog/en/modules/opac-auth.tt
@@ -85,6 +85,9 @@ please choose against which one you would like to authenticate:
+[% IF OpacPasswordChange && OpacResetPassword %]
+ Forgot your password?
+[% END %]
Don't have a password yet? If you don't have a password yet, stop by the circulation desk the next time you're in the library. We'll happily set one up for you.
Don't have a library card? If you don't have a library card, stop by your local library to sign up[% IF PatronSelfRegistration && PatronSelfRegistrationDefaultCategory %] or register here [% END %].
--- a/koha-tmpl/opac-tmpl/prog/en/modules/opac-main.tt
+++ a/koha-tmpl/opac-tmpl/prog/en/modules/opac-main.tt
@@ -59,8 +59,12 @@
[% IF PatronSelfRegistration && PatronSelfRegistrationDefaultCategory %]
[% END %]
-
-
+
+
+ [% IF OpacPasswordChange && opacResetPassword %]
+
Forgot your password?
+ [% END %]
+
[% END %]
--- a/koha-tmpl/opac-tmpl/prog/en/modules/opac-password-recovery.tt
+++ a/koha-tmpl/opac-tmpl/prog/en/modules/opac-password-recovery.tt
@@ -0,0 +1,129 @@
+[% INCLUDE 'doc-head-open.inc' %]
+[% IF (LibraryNameTitle) %][% LibraryNameTitle %][% ELSE %]Koha Online[% END %] Catalog ›
+[% INCLUDE 'doc-head-close.inc' %]
+
+
+
+
+
+
+
+[% INCLUDE 'masthead.inc' %]
+
+
+
+
+[% IF (!OpacResetPassword) %]
+
You can't reset your password.
+[% ELSIF (password_recovery) %]
+ [% IF (hasError) %]
+
+ [% IF (sendmailError) %]
+ An error has occured while sending you the password recovery link.
+ Please try again later.
+ [% ELSIF (errNoEmailFound) %]
+ No account was found with the email address "[% email %] "
+ Check if you typed it correctly.
+ [% ELSIF (errTooManyEmailFound) %]
+ More than one account has been found for the email address: "[% email %] "
+ Try yo use your alternative email if you have another.
+ [% ELSIF (errAlreadyStartRecovery) %]
+ The process of password recovery has already started for this account ("[% email %] ")
+ Check your emails; you should recieve the link to reset your password.
+ If you didn't recieve it, click here to get a new password recovery link
+ [% END %]
+ Please contact the staff if you need further assistance.
+
+ [% END %]
+
+
+[% ELSIF (new_password) %]
+ [% IF (errLinkNotValid) %]
+
+ We could not authentify you as the account owner.
+ Be sure to use the link you recieved in your email.
+
+ [% ELSE %]
+ [% IF (hasError) %]
+
+ [% IF (errPassNotMatch) %]
+ The passwords entered does not match.
+ Please try again.
+ [% ELSIF (errPassTooShort) %]
+ The password is too short.
+ The password must contain at least [% minPassLength %] characters.
+ [% END %]
+
+ [% END %]
+
+ [% END %]
+[% ELSIF (mail_sent) %]
+
A mail has been sent to "[% email %]".
+ It contains a link to create a new password.
+ This link will be valid for 2 days from now.
+
Click here to return to the main page.
+[% ELSIF (password_reset_done) %]
+
The password has been changed for the user "[% username %]".
+ You can now login using this form .
+[% END %]
+
+
+
+
+
+
+ [% INCLUDE 'usermenu.inc' %]
+
+
+
+[% INCLUDE 'opac-bottom.inc' %]
+
--- a/koha-tmpl/opac-tmpl/prog/en/modules/opac-send-password-recovery.tt
+++ a/koha-tmpl/opac-tmpl/prog/en/modules/opac-send-password-recovery.tt
@@ -0,0 +1,13 @@
+[% IF ( LibraryNameTitle ) %][% LibraryNameTitle %][% ELSE %]Koha Online[% END %] - Password recovery
+
+
+
+This email has been sent in response to your password recovery request for the account [% username %] .
+
+You can now create your new password using the following link:
+[% uuidLink %]
+
+This link will be valid for 2 days from this email's reception, then you must reapply if you do not change your password.
+Thank you.
+
+
--- a/opac/opac-main.pl
+++ a/opac/opac-main.pl
@@ -58,6 +58,7 @@ $template->param(
koha_news_count => $koha_news_count,
display_daily_quote => C4::Context->preference('QuoteOfTheDay'),
daily_quote => $quote,
+ opacResetPassword => C4::Context->preference('OpacResetPassword'),
);
# If GoogleIndicTransliteration system preference is On Set paramter to load Google's javascript in OPAC search screens
--- a/opac/opac-password-recovery.pl
+++ a/opac/opac-password-recovery.pl
@@ -0,0 +1,224 @@
+#!/usr/bin/perl
+
+use strict;
+
+use CGI;
+use Mail::Sendmail;
+use Digest::MD5 qw(md5_base64);
+use HTML::Entities;
+
+use C4::Auth;
+use C4::Koha;
+use C4::Members qw(changepassword GetMember GetMemberDetails SendPasswordRecoveryEmail);
+use C4::Output;
+use C4::Context;
+
+my $query = new CGI;
+
+my ( $template, $borrowernumber, $cookie ) = get_template_and_user(
+ {
+ template_name => "opac-password-recovery.tmpl",
+ query => $query,
+ type => "opac",
+ authnotrequired => 1,
+ debug => 1,
+ }
+);
+
+my $email = $query->param('email');
+my $password = $query->param('password');
+my $repeatPassword = $query->param('repeatPassword');
+my $minPassLength = C4::Context->preference( 'minPasswordLength' );
+my $id = $query->param('id');
+my $uniqueKey = $query->param('uniqueKey');
+my $username = $query->param('username');
+my $borrower_number;
+
+#errors
+my $hasError;
+
+#email form error
+my $errNoEmailFound;
+my $errAlreadyStartRecovery;
+
+#new password form error
+my $errLinkNotValid;
+my $errPassNotMatch;
+my $errPassTooShort;
+
+my $dbh = C4::Context->dbh;
+
+$template->param( OpacResetPassword => C4::Context->preference("OpacResetPassword") );
+
+if ( $query->param('sendEmail') || $query->param('resendEmail') )
+{
+#send mail + confirmation
+
+ #try with the main email
+ my $borrower_number;
+ my $accountActivated;
+ my %borrower_infos = GetMember(email => $email);
+ if ( %borrower_infos )
+ {
+ $borrower_number = GetMember(email => $email)->{'borrowernumber'};
+ $username = GetMemberDetails( $borrower_number )->{'userid'};
+ $accountActivated = ( GetMemberDetails( $borrower_number )->{'categorycode'} ne 'UNAUTHORIZ' ) ? 1 : 0;
+ }
+ else
+ {
+ #try with the secondary email
+ %borrower_infos = GetMember(emailpro => $email);
+ if ( %borrower_infos )
+ {
+ $borrower_number = GetMember(emailpro => $email)->{'borrowernumber'};
+ $username = GetMemberDetails( $borrower_number )->{'userid'};
+ $accountActivated = ( GetMemberDetails( $borrower_number )->{'categorycode'} ne 'UNAUTHORIZ' ) ? 1 : 0;
+ }
+ else
+ {
+ #try when the other contact email
+ %borrower_infos = GetMember(B_email => $email);
+ if ( %borrower_infos )
+ {
+ $borrower_number = GetMember(B_email => $email)->{'borrowernumber'};
+ $username = GetMemberDetails( $borrower_number )->{'userid'};
+ $accountActivated = ( GetMemberDetails( $borrower_number )->{'categorycode'} ne 'UNAUTHORIZ' ) ? 1 : 0;
+ }
+ }
+ }
+
+ if ( !$email || !$username || !$borrower_number || !$accountActivated )
+ {
+ $hasError = 1;
+ $errNoEmailFound = 1;
+ }
+ elsif ( !$query->param('resendEmail') )
+ {
+ my $sth = $dbh->prepare( "SELECT borrowernumber FROM borrower_password_recovery WHERE NOW() < valid_until AND borrowernumber = ?" );
+ $sth->execute($borrower_number);
+ if ( my $already = $sth->fetchrow )
+ {
+ $hasError = 1;
+ $errAlreadyStartRecovery = 1;
+ }
+ }
+
+ if ( $hasError )
+ {
+ $template->param( hasError => 1,
+ errNoEmailFound => $errNoEmailFound,
+ errAlreadyStartRecovery => $errAlreadyStartRecovery,
+ password_recovery => 1,
+ email => HTML::Entities::encode($email),
+ );
+ }
+ else
+ {
+ #generate uuid and send recovery email
+ if ( SendPasswordRecoveryEmail($borrower_number, $email) )
+ {
+ # if it works....
+ $template->param( mail_sent => 1,
+ email => $email
+ );
+ }
+ else
+ {
+ # if it doesnt work....
+ $template->param( password_recovery => 1,
+ sendmailError => 1
+ );
+ }
+ }
+}
+elsif ( $query->param('passwordReset') )
+{
+#new password form
+ #check if the link is still valid
+ my $sth = $dbh->prepare( "SELECT borrower_password_recovery.borrowernumber, userid
+ FROM borrower_password_recovery, borrowers
+ WHERE borrowers.borrowernumber = borrower_password_recovery.borrowernumber
+ AND NOW() < valid_until
+ AND uuid = ?" );
+ $sth->execute( $uniqueKey );
+ ( $borrower_number , $username) = $sth->fetchrow;
+
+ #validate password length & match
+ if ( ( $borrower_number ) && ( $password eq $repeatPassword ) && ( length( $password ) >= $minPassLength ) )
+ {
+ #apply changes
+ changepassword( $username, $borrower_number, md5_base64($password) );
+ #this line needed to fix a log bug in "Log.pm:75"
+ #
+ # if ( !$usernumber ) { $usernumber=0; }
+ #
+
+ #remove entry
+ my $sth = $dbh->prepare( "DELETE FROM borrower_password_recovery
+ WHERE uuid = ?
+ ORDER BY valid_until DESC LIMIT 1" );
+ $sth->execute( $uniqueKey);
+
+ $template->param( password_reset_done => 1,
+ username => $username
+ );
+ }
+ else
+ {
+ #errors
+ if ( !$borrower_number )
+ {
+ #parameters not valid
+ $errLinkNotValid = 1;
+ }
+ elsif ( $password ne $repeatPassword )
+ {
+ #passwords does not match
+ $errPassNotMatch = 1;
+ }
+ elsif ( length( $password ) < $minPassLength )
+ {
+ #password too short
+ $errPassTooShort = 1;
+ }
+ $template->param( new_password => 1,
+ minPassLength => $minPassLength,
+ email => $email,
+ uniqueKey => $uniqueKey,
+ errLinkNotValid => $errLinkNotValid,
+ errPassNotMatch => $errPassNotMatch,
+ errPassTooShort => $errPassTooShort,
+ hasError => 1 );
+ }
+}
+elsif ( $uniqueKey )
+{
+#reset password form
+ #check if the link is valid
+ my $sth = $dbh->prepare( "SELECT borrower_password_recovery.borrowernumber, userid
+ FROM borrower_password_recovery, borrowers
+ WHERE borrowers.borrowernumber = borrower_password_recovery.borrowernumber
+ AND NOW() < valid_until
+ AND uuid = ?" );
+ $sth->execute( $uniqueKey );
+ ( $borrower_number, $username ) = $sth->fetchrow;
+ if( !$borrower_number )
+ {
+ $errLinkNotValid = 1;
+ }
+ $template->param( new_password => 1,
+ minPassLength => $minPassLength,
+ email => $email,
+ uniqueKey => $uniqueKey,
+ username => $username,
+ errLinkNotValid => $errLinkNotValid
+ );
+}
+else
+{
+ #password recovery form (to send email)
+ $template->param( password_recovery => 1 );
+}
+
+output_html_with_http_headers $query, $cookie, $template->output;
+
--