From 4471bd9ef9243d08e1c21c31dc52ab61f0ef461b Mon Sep 17 00:00:00 2001
From: Kyle M Hall <kyle@bywatersolutions.com>
Date: Tue, 31 Jul 2012 10:07:04 -0400
Subject: [PATCH] Bug 7067 - OPAC Borrower Self Registration
Content-Type: text/plain; charset="utf-8"

---
 C4/Auth.pm                                         |    1 +
 C4/Letters.pm                                      |   30 +-
 C4/Members.pm                                      |   44 ++-
 Koha/Borrower/Modifications.pm                     |  206 ++++++
 .../data/mysql/en/mandatory/sample_notices.sql     |   12 +
 installer/data/mysql/kohastructure.sql             |   79 +++
 installer/data/mysql/sysprefs.sql                  |    5 +
 installer/data/mysql/updatedatabase.pl             |  102 +++
 .../prog/en/modules/admin/preferences/opac.pref    |   24 +
 .../intranet-tmpl/prog/en/modules/intranet-main.tt |   11 +-
 .../prog/en/modules/members/members-update.tt      |  137 ++++
 .../opac-tmpl/prog/en/includes/opac-bottom.inc     |    4 +-
 koha-tmpl/opac-tmpl/prog/en/includes/usermenu.inc  |    2 +-
 koha-tmpl/opac-tmpl/prog/en/modules/opac-main.tt   |    2 +
 .../modules/opac-memberentry-update-submitted.tt   |   29 +
 .../opac-tmpl/prog/en/modules/opac-memberentry.tt  |  703 ++++++++++++++++++++
 .../en/modules/opac-registration-confirmation.tt   |   70 ++
 .../en/modules/opac-registration-email-sent.tt     |   31 +
 mainpage.pl                                        |   23 +-
 members/members-update-do.pl                       |   64 ++
 members/members-update.pl                          |   59 ++
 misc/cronjobs/delete_expired_opac_registrations.pl |   55 ++
 .../delete_unverified_opac_registrations.pl        |   57 ++
 opac/opac-memberentry.pl                           |  234 +++++++
 opac/opac-registration-verify.pl                   |   69 ++
 25 files changed, 2021 insertions(+), 32 deletions(-)
 create mode 100644 Koha/Borrower/Modifications.pm
 create mode 100644 koha-tmpl/intranet-tmpl/prog/en/modules/members/members-update.tt
 create mode 100644 koha-tmpl/opac-tmpl/prog/en/modules/opac-memberentry-update-submitted.tt
 create mode 100644 koha-tmpl/opac-tmpl/prog/en/modules/opac-memberentry.tt
 create mode 100644 koha-tmpl/opac-tmpl/prog/en/modules/opac-registration-confirmation.tt
 create mode 100644 koha-tmpl/opac-tmpl/prog/en/modules/opac-registration-email-sent.tt
 create mode 100755 members/members-update-do.pl
 create mode 100755 members/members-update.pl
 create mode 100755 misc/cronjobs/delete_expired_opac_registrations.pl
 create mode 100755 misc/cronjobs/delete_unverified_opac_registrations.pl
 create mode 100755 opac/opac-memberentry.pl
 create mode 100755 opac/opac-registration-verify.pl

diff --git a/C4/Auth.pm b/C4/Auth.pm
index 59c9955..efb9101 100644
--- a/C4/Auth.pm
+++ b/C4/Auth.pm
@@ -457,6 +457,7 @@ sub get_template_and_user {
             SyndeticsSeries              => C4::Context->preference("SyndeticsSeries"),
             SyndeticsCoverImageSize      => C4::Context->preference("SyndeticsCoverImageSize"),
             OPACLocalCoverImages         => C4::Context->preference("OPACLocalCoverImages"),
+            PatronSelfRegistration       => C4::Context->preference("PatronSelfRegistration"),
         );
 
         $template->param(OpacPublic => '1') if ($user || C4::Context->preference("OpacPublic"));
diff --git a/C4/Letters.pm b/C4/Letters.pm
index b17cb08..763eab5 100644
--- a/C4/Letters.pm
+++ b/C4/Letters.pm
@@ -456,6 +456,9 @@ sub GetPreparedLetter {
        }
     }
 
+    my $OPACBaseURL = C4::Context->preference('OPACBaseURL');
+    $letter->{content} =~ s/<<OPACBaseURL>>/$OPACBaseURL/go;
+
     if ($want_librarian) {
         # parsing librarian name
         my $userenv = C4::Context->userenv;
@@ -554,18 +557,19 @@ sub _parseletter_sth {
     # check cache first
     (defined $handles{$table}) and return $handles{$table};
     my $query = 
-    ($table eq 'biblio'       ) ? "SELECT * FROM $table WHERE   biblionumber = ?"                                 :
-    ($table eq 'biblioitems'  ) ? "SELECT * FROM $table WHERE   biblionumber = ?"                                 :
-    ($table eq 'items'        ) ? "SELECT * FROM $table WHERE     itemnumber = ?"                                 :
-    ($table eq 'issues'       ) ? "SELECT * FROM $table WHERE     itemnumber = ?"                                 :
-    ($table eq 'old_issues'   ) ? "SELECT * FROM $table WHERE     itemnumber = ? ORDER BY timestamp DESC LIMIT 1" :
-    ($table eq 'reserves'     ) ? "SELECT * FROM $table WHERE borrowernumber = ? and biblionumber = ?"            :
-    ($table eq 'borrowers'    ) ? "SELECT * FROM $table WHERE borrowernumber = ?"                                 :
-    ($table eq 'branches'     ) ? "SELECT * FROM $table WHERE     branchcode = ?"                                 :
-    ($table eq 'suggestions'  ) ? "SELECT * FROM $table WHERE   suggestionid = ?"                                 :
-    ($table eq 'aqbooksellers') ? "SELECT * FROM $table WHERE             id = ?"                                 :
-    ($table eq 'aqorders'     ) ? "SELECT * FROM $table WHERE    ordernumber = ?"                                 :
-    ($table eq 'opac_news'    ) ? "SELECT * FROM $table WHERE          idnew = ?"                                 :
+    ($table eq 'biblio'       ) ? "SELECT * FROM $table WHERE   biblionumber = ?"                                  :
+    ($table eq 'biblioitems'  ) ? "SELECT * FROM $table WHERE   biblionumber = ?"                                  :
+    ($table eq 'items'        ) ? "SELECT * FROM $table WHERE     itemnumber = ?"                                  :
+    ($table eq 'issues'       ) ? "SELECT * FROM $table WHERE     itemnumber = ?"                                  :
+    ($table eq 'old_issues'   ) ? "SELECT * FROM $table WHERE     itemnumber = ? ORDER BY timestamp DESC LIMIT 1"  :
+    ($table eq 'reserves'     ) ? "SELECT * FROM $table WHERE borrowernumber = ? and biblionumber = ?"             :
+    ($table eq 'borrowers'    ) ? "SELECT * FROM $table WHERE borrowernumber = ?"                                  :
+    ($table eq 'branches'     ) ? "SELECT * FROM $table WHERE     branchcode = ?"                                  :
+    ($table eq 'suggestions'  ) ? "SELECT * FROM $table WHERE   suggestionid = ?"                                  :
+    ($table eq 'aqbooksellers') ? "SELECT * FROM $table WHERE             id = ?"                                  :
+    ($table eq 'aqorders'     ) ? "SELECT * FROM $table WHERE    ordernumber = ?"                                  :
+    ($table eq 'opac_news'    ) ? "SELECT * FROM $table WHERE          idnew = ?"                                  :
+    ($table eq 'borrower_modifications') ? "SELECT * FROM $table WHERE borrowernumber = ? OR verification_token =?":
     undef ;
     unless ($query) {
         warn "ERROR: No _parseletter_sth query for table '$table'";
@@ -672,7 +676,7 @@ sub EnqueueLetter {
     my $params = shift or return;
 
     return unless exists $params->{'letter'};
-    return unless exists $params->{'borrowernumber'};
+#   return unless exists $params->{'borrowernumber'};
     return unless exists $params->{'message_transport_type'};
 
     my $content = $params->{letter}->{content};
diff --git a/C4/Members.pm b/C4/Members.pm
index 2e315aa..26e6b48 100644
--- a/C4/Members.pm
+++ b/C4/Members.pm
@@ -25,6 +25,7 @@ use strict;
 use C4::Context;
 use C4::Dates qw(format_date_in_iso format_date);
 use Digest::MD5 qw(md5_base64);
+use String::Random qw( random_string );
 use Date::Calc qw/Today Add_Delta_YM check_date Date_to_Days/;
 use C4::Log; # logaction
 use C4::Overdues;
@@ -118,6 +119,7 @@ BEGIN {
     #Insert data
     push @EXPORT, qw(
         &AddMember
+        &AddMember_Opac
         &add_member_orgs
         &MoveMemberToDeleted
         &ExtendMemberSubscriptionTo
@@ -751,11 +753,27 @@ Returns as undef upon any db error without further processing
 sub AddMember {
     my (%data) = @_;
     my $dbh = C4::Context->dbh;
-	# generate a proper login if none provided
-	$data{'userid'} = Generate_Userid($data{'borrowernumber'}, $data{'firstname'}, $data{'surname'}) if $data{'userid'} eq '';
-	# create a disabled account if no password provided
-	$data{'password'} = ($data{'password'})? md5_base64($data{'password'}) : '!';
-	$data{'borrowernumber'}=InsertInTable("borrowers",\%data);	
+    
+    # generate a proper login if none provided
+    $data{'userid'} = Generate_Userid($data{'borrowernumber'}, $data{'firstname'}, $data{'surname'}) if $data{'userid'} eq '';
+
+    # create a disabled account if no password provided
+    $data{'password'} = ($data{'password'})? md5_base64($data{'password'}) : '!';
+    $data{'borrowernumber'}=InsertInTable("borrowers",\%data);	
+
+    # add expiration date if it isn't already there
+    unless ( $data{'dateexpiry'} ) {
+        my $date = (C4::Context->preference('BorrowerRenewalPeriodBase') eq 'dateexpiry') ?
+            C4::Dates->new(data->{'dateexpiry'}, 'iso')->output("iso") :
+            C4::Dates->new()->output("iso");
+        data{'dateexpiry'} = GetExpiryDate( data->{'categorycode'}, $date );
+    }
+     
+    # add enrollment date if it isn't already there
+    unless ( $data{'dateenrolled'} ) {
+        $data{'dateenrolled'} = C4::Dates->new()->output("iso");
+    }                                                                                           
+
     # mysql_insertid is probably bad.  not necessarily accurate and mysql-specific at best.
     logaction("MEMBERS", "CREATE", $data{'borrowernumber'}, "") if C4::Context->preference("BorrowersLog");
     
@@ -2365,6 +2383,22 @@ sub GetBorrowersWithEmail {
     return @result;
 }
 
+sub AddMember_Opac {
+    my ( %borrower ) = @_;
+
+    $borrower{'categorycode'} = C4::Context->preference('PatronSelfRegistrationUseTemporaryStatus');
+    
+    my $sr = new String::Random;
+    $sr->{'A'} = [ 'A'..'Z', 'a'..'z' ];
+    my $password = $sr->randpattern("AAAAAAAAAA");
+    $borrower{'password'} = $password;
+
+    $borrower{'cardnumber'} = fixup_cardnumber();
+
+    my $borrowernumber = AddMember(%borrower);
+
+    return ( $borrowernumber, $password );
+}
 
 END { }    # module clean-up code here (global destructor)
 
diff --git a/Koha/Borrower/Modifications.pm b/Koha/Borrower/Modifications.pm
new file mode 100644
index 0000000..d6aa915
--- /dev/null
+++ b/Koha/Borrower/Modifications.pm
@@ -0,0 +1,206 @@
+package Koha::Borrower::Modifications;
+
+# Copyright 2012 ByWater Solutions
+# 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 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.
+
+=head1 NAME
+
+C4::Borrowers::Modifications
+
+=cut
+
+use Modern::Perl;
+
+use C4::Context;
+use C4::Debug;
+use C4::SQLHelper qw(InsertInTable UpdateInTable DeleteInTable);
+
+use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS);
+
+BEGIN {
+
+    # set the version for version checking
+    $VERSION = 0.01;
+    require Exporter;
+    @ISA    = qw(Exporter);
+    @EXPORT = qw(
+
+    );
+
+    my $debug = C4::Context->preference("DebugLevel");
+}
+
+sub new {
+    my ( $class, %args ) = @_;
+    my $self = bless( {}, $class );
+
+    $self->{'verification_token'} = $args{'verification_token'};
+    $self->{'borrowernumber'}     = $args{'borrowernumber'};
+
+    return $self;
+}
+
+sub AddModifications {
+    my ( $self, %data ) = @_;
+
+    if ( $self->{'borrowernumber'} ) {
+        $data{'borrowernumber'} = $self->{'borrowernumber'};
+        my $dbh   = C4::Context->dbh;
+        my $query = "
+            SELECT COUNT(*) AS count
+            FROM borrower_modifications
+            WHERE borrowernumber = ?
+        ";
+        my $sth = $dbh->prepare($query);
+        $sth->execute( $self->{'borrowernumber'} );
+        my $result = $sth->fetchrow_hashref();
+
+        if ( $result->{'count'} ) {
+            $data{'verification_token'} = q{};
+            return UpdateInTable( "borrower_modifications", \%data );
+        }
+        else {
+            return InsertInTable( "borrower_modifications", \%data );
+        }
+
+    }
+    elsif ( $self->{'verification_token'} ) {
+        delete $data{'borrowernumber'};
+        $data{'verification_token'} = $self->{'verification_token'};
+
+        return InsertInTable( "borrower_modifications", \%data );
+    }
+    else {
+        return;
+    }
+}
+
+sub Verify {
+    my ( $self, $verification_token ) = @_;
+
+    if ( ref($self) ) {
+        $verification_token =
+          ($verification_token)
+          ? $verification_token
+          : $self->{'verification_token'};
+    }
+
+    my $dbh   = C4::Context->dbh;
+    my $query = "
+        SELECT COUNT(*) AS count
+        FROM borrower_modifications
+        WHERE verification_token = ?
+    ";
+    my $sth = $dbh->prepare($query);
+    $sth->execute($verification_token);
+    my $result = $sth->fetchrow_hashref();
+
+    return $result->{'count'};
+}
+
+sub GetPendingModificationsCount {
+    my ($self) = @_;
+
+    my $dbh   = C4::Context->dbh;
+    my $query = "
+        SELECT COUNT(*) AS count
+        FROM borrower_modifications
+        WHERE borrowernumber > 0
+    ";
+
+    my $sth = $dbh->prepare($query);
+    $sth->execute();
+    my $result = $sth->fetchrow_hashref();
+
+    return $result->{'count'};
+}
+
+sub GetPendingModifications {
+    my ($self) = @_;
+
+    my $dbh   = C4::Context->dbh;
+    my $query = "
+        SELECT *
+        FROM borrower_modifications
+        WHERE borrowernumber > 0
+    ";
+
+    my $sth = $dbh->prepare($query);
+    $sth->execute();
+
+    return $sth->fetchall_arrayref( {} );
+}
+
+sub ApproveModifications {
+    my ( $self, $borrowernumber ) = @_;
+    warn "ApproveModifications( $self, $borrowernumber )";
+    if ( ref($self) ) {
+        $borrowernumber =
+          ($borrowernumber) ? $borrowernumber : $self->{'borrowernumber'};
+    }
+
+    return unless $borrowernumber;
+
+    my $data = $self->_GetModifications($borrowernumber);
+
+    if ( UpdateInTable( "borrowers", $data ) ) {
+        return DeleteInTable( "borrower_modifications", $data );
+    }
+}
+
+sub DenyModifications {
+    my ( $self, $borrowernumber ) = @_;
+    warn "DenyModifications( $self, $borrowernumber )";
+    if ( ref($self) ) {
+        $borrowernumber =
+          ($borrowernumber) ? $borrowernumber : $self->{'borrowernumber'};
+    }
+
+    return unless $borrowernumber;
+
+    my $data = $self->_GetModifications($borrowernumber);
+
+    return DeleteInTable( "borrower_modifications", $data );
+}
+
+sub _GetModifications {
+    my ( $self, $borrowernumber ) = @_;
+    warn "_GetModifications( $self, $borrowernumber )";
+    if ( ref($self) ) {
+        $borrowernumber =
+          $borrowernumber ? $borrowernumber : $self->{'borrowernumber'};
+    }
+
+    return unless $borrowernumber;
+
+    my $query = "
+        SELECT *
+        FROM borrower_modifications
+        WHERE borrowernumber = ?
+    ";
+
+    my $sth = C4::Context->dbh->prepare($query);
+    $sth->execute($borrowernumber);
+    my $data = $sth->fetchrow_hashref();
+
+    foreach my $key ( keys %$data ) {
+        delete $data->{$key} unless ( defined( $data->{$key} ) );
+    }
+
+    return $data;
+}
+
+1;
diff --git a/installer/data/mysql/en/mandatory/sample_notices.sql b/installer/data/mysql/en/mandatory/sample_notices.sql
index 6fa0a54..e6e9df0 100644
--- a/installer/data/mysql/en/mandatory/sample_notices.sql
+++ b/installer/data/mysql/en/mandatory/sample_notices.sql
@@ -105,3 +105,15 @@ Date due: <<issues.date_due>><br />
    <li><<items.barcode>></li>
    <li><<items.itemcallnumber>></li>
 </ul>', 1);
+
+
+INSERT INTO `letter` (`module`,`code`,`branchcode`,`name`,`is_html`,`title`,`content`)
+VALUES (
+'members',  'OPAC_REG_VERIFY',  '',  'Opac Self-Registration Verification Email',  '1',  'Verify Your Account',  'Hello!
+
+Your library account has been created. Please verify your email address by clicking this link to complete the signup process:
+
+http://<<OPACBaseURL>>/cgi-bin/koha/opac-registration-verify.pl?token=<<borrower_modifications.verification_token>>
+
+If you did not initiate this request, you may safely ignore this one-time message. The request will expire shortly.'
+);
\ No newline at end of file
diff --git a/installer/data/mysql/kohastructure.sql b/installer/data/mysql/kohastructure.sql
index 69304e1..0a01da4 100644
--- a/installer/data/mysql/kohastructure.sql
+++ b/installer/data/mysql/kohastructure.sql
@@ -2867,6 +2867,85 @@ CREATE TABLE `quotes` (
   PRIMARY KEY (`id`)
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
 
+--
+-- Table structure for table `borrower_modifications`
+--
+
+CREATE TABLE IF NOT EXISTS `borrower_modifications` (
+  `timestamp` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+  `verification_token` varchar(255) NOT NULL DEFAULT '',
+  `borrowernumber` int(11) NOT NULL DEFAULT '0',
+  `cardnumber` varchar(16) DEFAULT NULL,
+  `surname` mediumtext,
+  `firstname` text,
+  `title` mediumtext,
+  `othernames` mediumtext,
+  `initials` text,
+  `streetnumber` varchar(10) DEFAULT NULL,
+  `streettype` varchar(50) DEFAULT NULL,
+  `address` mediumtext,
+  `address2` text,
+  `city` mediumtext,
+  `state` text,
+  `zipcode` varchar(25) DEFAULT NULL,
+  `country` text,
+  `email` mediumtext,
+  `phone` text,
+  `mobile` varchar(50) DEFAULT NULL,
+  `fax` mediumtext,
+  `emailpro` text,
+  `phonepro` text,
+  `B_streetnumber` varchar(10) DEFAULT NULL,
+  `B_streettype` varchar(50) DEFAULT NULL,
+  `B_address` varchar(100) DEFAULT NULL,
+  `B_address2` text,
+  `B_city` mediumtext,
+  `B_state` text,
+  `B_zipcode` varchar(25) DEFAULT NULL,
+  `B_country` text,
+  `B_email` text,
+  `B_phone` mediumtext,
+  `dateofbirth` date DEFAULT NULL,
+  `branchcode` varchar(10) DEFAULT NULL,
+  `categorycode` varchar(10) DEFAULT NULL,
+  `dateenrolled` date DEFAULT NULL,
+  `dateexpiry` date DEFAULT NULL,
+  `gonenoaddress` tinyint(1) DEFAULT NULL,
+  `lost` tinyint(1) DEFAULT NULL,
+  `debarred` date DEFAULT NULL,
+  `debarredcomment` varchar(255) DEFAULT NULL,
+  `contactname` mediumtext,
+  `contactfirstname` text,
+  `contacttitle` text,
+  `guarantorid` int(11) DEFAULT NULL,
+  `borrowernotes` mediumtext,
+  `relationship` varchar(100) DEFAULT NULL,
+  `ethnicity` varchar(50) DEFAULT NULL,
+  `ethnotes` varchar(255) DEFAULT NULL,
+  `sex` varchar(1) DEFAULT NULL,
+  `password` varchar(30) DEFAULT NULL,
+  `flags` int(11) DEFAULT NULL,
+  `userid` varchar(75) DEFAULT NULL,
+  `opacnote` mediumtext,
+  `contactnote` varchar(255) DEFAULT NULL,
+  `sort1` varchar(80) DEFAULT NULL,
+  `sort2` varchar(80) DEFAULT NULL,
+  `altcontactfirstname` varchar(255) DEFAULT NULL,
+  `altcontactsurname` varchar(255) DEFAULT NULL,
+  `altcontactaddress1` varchar(255) DEFAULT NULL,
+  `altcontactaddress2` varchar(255) DEFAULT NULL,
+  `altcontactaddress3` varchar(255) DEFAULT NULL,
+  `altcontactstate` text,
+  `altcontactzipcode` varchar(50) DEFAULT NULL,
+  `altcontactcountry` text,
+  `altcontactphone` varchar(50) DEFAULT NULL,
+  `smsalertnumber` varchar(50) DEFAULT NULL,
+  `privacy` int(11) DEFAULT NULL,
+  PRIMARY KEY (`verification_token`,`borrowernumber`),
+  KEY `verification_token` (`verification_token`),
+  KEY `borrowernumber` (`borrowernumber`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
 /*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;
 /*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
 /*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
diff --git a/installer/data/mysql/sysprefs.sql b/installer/data/mysql/sysprefs.sql
index 430585a..62dde81 100644
--- a/installer/data/mysql/sysprefs.sql
+++ b/installer/data/mysql/sysprefs.sql
@@ -369,3 +369,8 @@ INSERT INTO systempreferences (variable,value,explanation,options,type) VALUES('
 INSERT INTO systempreferences (variable,value,explanation,options,type) VALUES('OpacSuppressionByIPRange','','Restrict the suppression to IP adresses outside of the IP range','','free');
 INSERT INTO systempreferences (variable,value,explanation,options,type) VALUES('PrefillItem','0','When a new item is added, should it be prefilled with last created item values?','','YesNo');
 INSERT INTO systempreferences (variable,value,explanation,options,type) VALUES ('SubfieldsToUseWhenPrefill','','Define a list of subfields to use when prefilling items (separated by space)','','Free');
+INSERT INTO systempreferences (variable,value,options,explanation,type) VALUES
+('PatronSelfRegistration', '0', NULL, 'If enabled, patrons will be able to register themselves via the OPAC.', 'YesNo'),
+('PatronSelfRegistrationVerifyByEmail', '0', NULL, 'If enabled, any patron attempting to register themselves via the OPAC will be required to verify themselves via email to activate his or her account.', 'YesNo'),
+('PatronSelfRegistrationUseTemporaryStatus', '', '', 'A patron registered via the OPAC will receive a borrower category code set in this system preference.', 'free'),
+('PatronSelfRegistrationExpireTemporaryAccountsDelay', '0', NULL, 'If PatronSelfRegistrationUseTemporaryStatus is enabled, this system preference controls how long a patron can have a temporary status before the account is deleted automatically. It is an integer value representing a number of days to wait before deleting a temporary patron account. Setting it to 0 disables the deleting of temporary accounts.', 'Integer');
diff --git a/installer/data/mysql/updatedatabase.pl b/installer/data/mysql/updatedatabase.pl
index c6b6063..aff4e75 100755
--- a/installer/data/mysql/updatedatabase.pl
+++ b/installer/data/mysql/updatedatabase.pl
@@ -5684,6 +5684,108 @@ if ( C4::Context->preference("Version") < TransformToNum($DBversion) ) {
     SetVersion($DBversion);
 }
 
+$DBversion = '3.09.00.XXX';
+if (C4::Context->preference("Version") < TransformToNum($DBversion)) {
+    $dbh->do("
+        CREATE TABLE IF NOT EXISTS `borrower_modifications` (
+          `timestamp` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+          `verification_token` varchar(255) NOT NULL DEFAULT '',
+          `borrowernumber` int(11) NOT NULL DEFAULT '0',
+          `cardnumber` varchar(16) DEFAULT NULL,
+          `surname` mediumtext,
+          `firstname` text,
+          `title` mediumtext,
+          `othernames` mediumtext,
+          `initials` text,
+          `streetnumber` varchar(10) DEFAULT NULL,
+          `streettype` varchar(50) DEFAULT NULL,
+          `address` mediumtext,
+          `address2` text,
+          `city` mediumtext,
+          `state` text,
+          `zipcode` varchar(25) DEFAULT NULL,
+          `country` text,
+          `email` mediumtext,
+          `phone` text,
+          `mobile` varchar(50) DEFAULT NULL,
+          `fax` mediumtext,
+          `emailpro` text,
+          `phonepro` text,
+          `B_streetnumber` varchar(10) DEFAULT NULL,
+          `B_streettype` varchar(50) DEFAULT NULL,
+          `B_address` varchar(100) DEFAULT NULL,
+          `B_address2` text,
+          `B_city` mediumtext,
+          `B_state` text,
+          `B_zipcode` varchar(25) DEFAULT NULL,
+          `B_country` text,
+          `B_email` text,
+          `B_phone` mediumtext,
+          `dateofbirth` date DEFAULT NULL,
+          `branchcode` varchar(10) DEFAULT NULL,
+          `categorycode` varchar(10) DEFAULT NULL,
+          `dateenrolled` date DEFAULT NULL,
+          `dateexpiry` date DEFAULT NULL,
+          `gonenoaddress` tinyint(1) DEFAULT NULL,
+          `lost` tinyint(1) DEFAULT NULL,
+          `debarred` date DEFAULT NULL,
+          `debarredcomment` varchar(255) DEFAULT NULL,
+          `contactname` mediumtext,
+          `contactfirstname` text,
+          `contacttitle` text,
+          `guarantorid` int(11) DEFAULT NULL,
+          `borrowernotes` mediumtext,
+          `relationship` varchar(100) DEFAULT NULL,
+          `ethnicity` varchar(50) DEFAULT NULL,
+          `ethnotes` varchar(255) DEFAULT NULL,
+          `sex` varchar(1) DEFAULT NULL,
+          `password` varchar(30) DEFAULT NULL,
+          `flags` int(11) DEFAULT NULL,
+          `userid` varchar(75) DEFAULT NULL,
+          `opacnote` mediumtext,
+          `contactnote` varchar(255) DEFAULT NULL,
+          `sort1` varchar(80) DEFAULT NULL,
+          `sort2` varchar(80) DEFAULT NULL,
+          `altcontactfirstname` varchar(255) DEFAULT NULL,
+          `altcontactsurname` varchar(255) DEFAULT NULL,
+          `altcontactaddress1` varchar(255) DEFAULT NULL,
+          `altcontactaddress2` varchar(255) DEFAULT NULL,
+          `altcontactaddress3` varchar(255) DEFAULT NULL,
+          `altcontactstate` text,
+          `altcontactzipcode` varchar(50) DEFAULT NULL,
+          `altcontactcountry` text,
+          `altcontactphone` varchar(50) DEFAULT NULL,
+          `smsalertnumber` varchar(50) DEFAULT NULL,
+          `privacy` int(11) DEFAULT NULL,
+          PRIMARY KEY (`verification_token`,`borrowernumber`),
+          KEY `verification_token` (`verification_token`),
+          KEY `borrowernumber` (`borrowernumber`)
+        ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+");
+
+    $dbh->do("
+        INSERT INTO systempreferences (`variable`, `value`, `options`, `explanation`, `type`) VALUES
+        ('PatronSelfRegistration', '0', NULL, 'If enabled, patrons will be able to register themselves via the OPAC.', 'YesNo'),
+        ('PatronSelfRegistrationVerifyByEmail', '0', NULL, 'If enabled, any patron attempting to register themselves via the OPAC will be required to verify themselves via email to activate his or her account.', 'YesNo'),
+        ('PatronSelfRegistrationUseTemporaryStatus', '', '', 'A patron registered via the OPAC will receive a borrower category code set in this system preference.', 'free'),
+        ('PatronSelfRegistrationExpireTemporaryAccountsDelay', '0', NULL, 'If PatronSelfRegistrationUseTemporaryStatus is enabled, this system preference controls how long a patron can have a temporary status before the account is deleted automatically. It is an integer value representing a number of days to wait before deleting a temporary patron account. Setting it to 0 disables the deleting of temporary accounts.', 'Integer');
+    ");
+
+    $dbh->do("
+    INSERT INTO  letter ( `module`, `code`, `branchcode`, `name`, `is_html`, `title`, `content` )
+    VALUES ( 'members', 'OPAC_REG_VERIFY', '', 'Opac Self-Registration Verification Email', '1', 'Verify Your Account', 'Hello!
+
+    Your library account has been created. Please verify your email address by clicking this link to complete the signup process:
+
+    http://<<OPACBaseURL>>/cgi-bin/koha/opac-registration-verify.pl?token=<<borrower_modifications.verification_token>>
+
+    If you did not initiate this request, you may safely ignore this one-time message. The request will expire shortly.'
+    )");
+
+    print "Upgrade to $DBversion done (Add Patron Self Registration)\n";
+    SetVersion ($DBversion);
+}
+
 =head1 FUNCTIONS
 
 =head2 TableExists($table)
diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/opac.pref b/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/opac.pref
index 50abfeb..c0a656d 100644
--- a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/opac.pref
+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/opac.pref
@@ -499,3 +499,27 @@ OPAC:
                   yes: Use
                   no: "Don't use"
             - "the item collection code when finding items for the shelf browser."      
+
+    Self Registration:
+        -
+            - pref: PatronSelfRegistration
+              choices:
+                  yes: Allow
+                  no: "Don't allow"
+            - "library patrons to register an account via the OPAC."
+        -
+            - pref: PatronSelfRegistrationVerifyByEmail
+              choices:
+                  yes: Require
+                  no: "Don't require"
+            - "that a self-registering patron verify his or herself via email."
+        -
+            - "Use the patron category code"
+            - pref: PatronSelfRegistrationUseTemporaryStatus
+              class: short
+            - "as the default patron category for patrons registered via the OPAC."
+        -
+            - "Delete patrons registered via the OPAC, but not yet verified after"
+            - pref: PatronSelfRegistrationExpireTemporaryAccountsDelay
+              class: integer
+            - "days."
diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/intranet-main.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/intranet-main.tt
index 38658dd..8242d23 100644
--- a/koha-tmpl/intranet-tmpl/prog/en/modules/intranet-main.tt
+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/intranet-main.tt
@@ -123,9 +123,18 @@
                         <span class="pending-number-link">[% pendingtags %]</span>
                     </div>
                     [% END %]
+
+
+                    [% IF ( CAN_user_borrowers && pending_borrower_modifications )%]
+                    <div class="pending-info">
+                        <a href="/cgi-bin/koha/members/members-update.pl">Patrons requesting modifications</a>:
+                        <span class="pending-number-link">[% pending_borrower_modifications %]</span>
+                    </div>
+                    [% END %]
+
                 </div>
-            [% END %]
 
+            [% END %]
 
 </div>
             [% IF ( IntranetmainUserblock ) %]
diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/members/members-update.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/members/members-update.tt
new file mode 100644
index 0000000..8878b7f
--- /dev/null
+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/members/members-update.tt
@@ -0,0 +1,137 @@
+[% INCLUDE 'doc-head-open.inc' %]
+<title>Koha &rsaquo; Patrons [% IF ( searching ) %]&rsaquo; Search results[% END %]</title>
+[% INCLUDE 'doc-head-close.inc' %]
+</head>
+<body id="pat_member" class="pat">
+[% INCLUDE 'header.inc' %]
+[% INCLUDE 'patron-search.inc' %]
+
+[%
+    SET field_display_names = {
+        surname         => "Surname"
+        firstname       => "First name"
+        title           => "Title"
+        othernames      => "Other names"
+        initials        => "Initials"
+        streetnumber    => "Street number"
+        streettype      => "Street type"
+        address         => "Address"
+        address2        => "Address 2"
+        city            => "City"
+        state           => "State"
+        zipcode         => "Zip code"
+        country         => "Country"
+        email           => "Email"
+        phone           => "Primary Phone"
+        mobile          => "Primary Mobile Phone"
+        fax             => "Fax"
+        emailpro        => "Secondary email"
+        phonepro        => "Secondary phone"
+        B_streetnumber  => "Alternate address - street number"
+        B_streettype    => "Alternate address - street type"
+        B_address       => "Alternate address"
+        B_address2      => "Alternate address 2"
+        B_city          => "Alternate address - city"
+        B_state         => "Alternate address - state"
+        B_zipcode       => "Alternate address - zip code"
+        B_email         => "Alternate address - email"
+        B_phone         => "Alertnate address - phone"
+        dateofbirth     => "Date of birth"
+        contactname     => "Contact - last name"
+        contactfirstname=> "Contact - first name"
+        contacttitle    => "Contact - title"
+        relationship    => "Contact - relationship"
+        ethnicity       => "Ethnicity"
+        ethnotes        => "Ethnicity notes"
+        sex             => "Sex"
+        altcontactfirstname => "Alternate contact - first name"
+        altcontactsurname   => "Alternate contact - surname"
+        altcontactaddress1  => "Alternate contact - address"
+        altcontactaddress2  => "Alternate contact - address 2"
+        altcontactaddress3  => "Alternate contact - city"
+        altcontactstate     => "Alternate contact - state"
+        altcontactzipcode   => "Alternate contact - zip code"
+        altcontactcounty    => "Alternate contact - county"
+        altcontactphone     => "Alternate contact - phone"
+        smsalertnumber      => "SMS alert number"
+    }
+%]
+<div id="breadcrumbs"><a href="/cgi-bin/koha/mainpage.pl">Home</a> &rsaquo; Patrons &rsaquo; Modifications</div>
+
+<div id="doc2" class="yui-t7">
+
+   <div id="bd">
+        <div id="yui-main">
+            <div class="yui-b">
+                <div class="yui-g">
+
+                    <form method="post" action="members-update-do.pl">
+
+                        <table>
+                            <thead>
+                                <tr>
+                                    <th colspan="3">Action</th>
+                                    <th rowspan="2">Patron</th>
+                                    <th rowspan="2">Changes</th>
+                                </tr>
+
+                                <tr>
+                                    <th>Approve</th>
+                                    <th>Deny</th>
+                                    <th>Ignore</th>
+                                </tr>
+                            </thead>
+
+                            <tbody>
+                                [% FOREACH pm IN PendingModifications %]
+                                    [% SET borrowernumber = pm.borrowernumber %]
+                                    <tr>
+                                        <td>
+                                            <input type="radio" name="modify_[% pm.borrowernumber %]" value="approve" />
+                                        </td>
+                                        <td>
+                                            <input type="radio" name="modify_[% pm.borrowernumber %]" value="deny" />
+                                        </td>
+                                        <td>
+                                            <input type="radio" name="modify_[% pm.borrowernumber %]" value="ignore" checked="checked"/>
+                                        </td>
+
+                                        <td>
+                                            [% borrowers.$borrowernumber.firstname %] [% borrowers.$borrowernumber.surname %]
+                                        </td>
+
+                                        <td>
+                                            <table>
+                                                <tr>
+                                                    <th>Field</th>
+                                                    <th>From</th>
+                                                    <th>To</th>
+                                                </tr>
+
+
+                                                [% FOREACH key IN pm.keys %]
+                                                    [% IF field_display_names.$key %]
+                                                        [% IF ( ( pm.$key OR borrowers.$borrowernumber.$key ) && ( pm.$key != borrowers.$borrowernumber.$key ) ) %]
+                                                            <tr>
+                                                                <td>[% field_display_names.$key %]</td>
+                                                                <td>[% borrowers.$borrowernumber.$key %]</td>
+                                                                <td>[% pm.$key %]</td>
+                                                            </td>
+                                                        [% END %]
+                                                    [% END %]
+                                                [% END %]
+                                            </table>
+                                        </td>
+                                    </tr>
+                                [% END %]
+                            </tbody>
+                        </table>
+
+                        <p><input type="submit" /></p>
+
+                    </form>
+                </div>
+            </div>
+        </div>
+    </div>
+[% INCLUDE 'intranet-bottom.inc' %]
diff --git a/koha-tmpl/opac-tmpl/prog/en/includes/opac-bottom.inc b/koha-tmpl/opac-tmpl/prog/en/includes/opac-bottom.inc
index 295eb5b..3df0221 100644
--- a/koha-tmpl/opac-tmpl/prog/en/includes/opac-bottom.inc
+++ b/koha-tmpl/opac-tmpl/prog/en/includes/opac-bottom.inc
@@ -1,7 +1,7 @@
 [% IF ( opaccredits ) %]
 	<div class="ft">
-        [% opaccredits %]
-    </div>
+            [% opaccredits %]
+        </div>
 [% END %]
 </div>
 
diff --git a/koha-tmpl/opac-tmpl/prog/en/includes/usermenu.inc b/koha-tmpl/opac-tmpl/prog/en/includes/usermenu.inc
index 352d6b3..5a07d95 100644
--- a/koha-tmpl/opac-tmpl/prog/en/includes/usermenu.inc
+++ b/koha-tmpl/opac-tmpl/prog/en/includes/usermenu.inc
@@ -5,7 +5,7 @@
   [% IF ( OPACFinesTab ) %]
   [% IF ( accountview ) %]<li class="active">[% ELSE %]<li>[% END %]<a href="/cgi-bin/koha/opac-account.pl">my fines</a></li>
   [% END %]
-  [% IF ( userupdateview ) %]<li class="active">[% ELSE %]<li>[% END %]<a href="/cgi-bin/koha/opac-userupdate.pl">my personal details</a></li>
+  [% IF ( userupdateview ) %]<li class="active">[% ELSE %]<li>[% END %]<a href="/cgi-bin/koha/opac-memberentry.pl">my personal details</a></li>
   [% IF ( TagsEnabled ) %]
     [% IF ( tagsview ) %]<li class="active">[% ELSE %]<li>[% END %]<a href="/cgi-bin/koha/opac-tags.pl?mine=1">my tags</a></li>
   [% END %]
diff --git a/koha-tmpl/opac-tmpl/prog/en/modules/opac-main.tt b/koha-tmpl/opac-tmpl/prog/en/modules/opac-main.tt
index d291b2a..db5c021 100644
--- a/koha-tmpl/opac-tmpl/prog/en/modules/opac-main.tt
+++ b/koha-tmpl/opac-tmpl/prog/en/modules/opac-main.tt
@@ -55,6 +55,8 @@
 		<li><label for="password">Password:</label><input type="password" id="password" size="10" name="password" /></li>
 		</ol>	 <fieldset class="action">
 	 <input type="submit" value="Log In" class="submit" />
+        [% IF PatronSelfRegistration %]<div>Don't have an account? <a href="/cgi-bin/koha/opac-memberentry.pl">Register here.</a></div>[% END %]
+
 	 </fieldset></fieldset>
 	</form>
 	</div>
diff --git a/koha-tmpl/opac-tmpl/prog/en/modules/opac-memberentry-update-submitted.tt b/koha-tmpl/opac-tmpl/prog/en/modules/opac-memberentry-update-submitted.tt
new file mode 100644
index 0000000..9183824
--- /dev/null
+++ b/koha-tmpl/opac-tmpl/prog/en/modules/opac-memberentry-update-submitted.tt
@@ -0,0 +1,29 @@
+[% INCLUDE 'doc-head-open.inc' %]
+[% IF ( LibraryNameTitle ) %][% LibraryNameTitle %][% ELSE %]Koha online[% END %] catalog
+[% INCLUDE 'doc-head-close.inc' %]
+</head>
+<body id="opac-main">
+[% IF ( OpacNav ) %]<div id="doc3" class="yui-t1">[% ELSE %]<div id="doc3" class="yui-t7">[% END %]
+   <div id="bd">
+[% INCLUDE 'masthead.inc' %]
+
+<div id="yui-main">
+    <div class="yui-b">
+        <div id="loggedin" class="yui-ge">
+            <div class="yui-u first">
+
+                <p>Your updates have been submitted. A librarian will now review you updates before applying them.</p>
+
+            </div>
+        </div>
+    </div>
+</div>
+
+[% IF ( OpacNav ) %]<div class="yui-b">
+    <div id="opacnav" class="container">
+        [% INCLUDE 'navigation.inc' %]
+    </div>
+[% END %]
+
+</div>
+[% INCLUDE 'opac-bottom.inc' %]
diff --git a/koha-tmpl/opac-tmpl/prog/en/modules/opac-memberentry.tt b/koha-tmpl/opac-tmpl/prog/en/modules/opac-memberentry.tt
new file mode 100644
index 0000000..afb8d6b
--- /dev/null
+++ b/koha-tmpl/opac-tmpl/prog/en/modules/opac-memberentry.tt
@@ -0,0 +1,703 @@
+[% USE KohaDates %]
+
+    [% INCLUDE 'doc-head-open.inc' %]
+        [% IF ( LibraryNameTitle ) %][% LibraryNameTitle %][% ELSE %]Koha online[% END %] catalog &rsaquo; Your fines and charges
+    [% INCLUDE 'doc-head-close.inc' %]
+</head>
+
+<body id="opac-account">
+    <div id="doc3" class="yui-t1">
+        <div id="bd">
+            [% INCLUDE 'masthead.inc' %]
+
+            <div id="yui-main">
+                <div class="yui-b">
+                    <div class="yui-g">
+                        <div id="useraccount" class="container">
+
+                            [% IF empty_mandatory_fields %]
+                                <div class="error">You have not filled out all required fields. Please fill in all missing fields and resubmit.</div>
+                            [% END %]
+
+                            [% IF failed_captcha %]
+                                <div class="error">You typed in the wrong characters in the box before submitting. Please try again.</div>
+                            [% END %]
+
+                            <form method="post">
+
+                                [% UNLESS
+                                    hidden.defined('branchcode')
+                                %]
+
+                                <fieldset class="rows" id="memberentry_library">
+
+                                    <legend id="library_legend">Library</legend>
+                                        <ol>
+                                            [% UNLESS hidden.defined('branchcode') %]
+                                                <li>
+                                                    [% IF mandatory.defined('branchcode') %]
+                                                        <label for="borrower_branchcode" class="required">
+                                                    [% ELSE %]
+                                                        <label for="borrower_branchcode">
+                                                    [% END %]
+                                                    Home Library:</label>
+
+                                                    <select id="borrower_branchcode" name="borrower_branchcode">
+                                                        [% FOREACH b IN branches %]
+                                                            [% IF b.value == borrower.branchcode %]
+                                                                <option value="[% b.value %]" selected="selected">[% b.branchname %]</option>
+                                                            [% ELSE %]
+                                                                <option value="[% b.value %]">[% b.branchname %]</option>
+                                                            [% END %]
+                                                        [% END %]
+                                                    </select>
+                                                </li>
+                                            [% END %]
+                                        </ol>
+                                    </fieldset>
+                                [% END %]
+
+                                [% UNLESS
+                                    hidden.defined('title') && hidden.defined('surname') && hidden.defined('firstname') &&
+                                    hidden.defined('dateofbirth') && hidden.defined('initials') && hidden.defined('othernames') &&
+                                    hidden.defined('sex')
+                                %]
+                                    <fieldset class="rows" id="memberentry_identity">
+                                        <legend id="identity_legend">Identity</legend>
+
+                                        <ol>
+                                            [% UNLESS hidden.defined('title') %]
+                                                <li>
+                                                    [% IF mandatory.defined('title') %]
+                                                        <label for="borrower_title" class="required">
+                                                    [% ELSE %]
+                                                        <label for="borrower_title">
+                                                    [% END %]
+                                                    Salutation:</label>
+
+                                                    <select id="borrower_title" name="borrower_title">
+                                                        <option value="">&nbsp;</option>
+                                                        [% FOREACH mt IN member_titles %]
+                                                            [% IF mt == borrower.title %]
+                                                                <option value="[% mt %]" selected="selected">[% mt %]</option>
+                                                            [% ELSE %]
+                                                                <option value="[% mt %]">[% mt %]</option>
+                                                            [% END %]
+                                                        [% END %]
+                                                    </select>
+                                                </li>
+                                            [% END %]
+
+                                            [% UNLESS hidden.defined('surname') %]
+                                                <li>
+                                                    [% IF mandatory.defined('surname') %]
+                                                        <label for="borrower_surname" class="required">
+                                                    [% ELSE %]
+                                                        <label for="borrower_surname">
+                                                    [% END %]
+                                                    Surname:</label>
+
+                                                    <input type="text" id="borrower_surname" name="borrower_surname" value="[% borrower.surname %]" />
+                                                    [% IF mandatory.defined('surname') %]<span class="required">Required</span>[% END %]
+                                                </li>
+                                            [% END %]
+
+                                            [% UNLESS hidden.defined('firstname') %]
+                                                <li>
+                                                    [% IF mandatory.defined('firstname') %]
+                                                        <label for="borrower_firstname" class="required">
+                                                    [% ELSE %]
+                                                        <label for="borrower_firstname">
+                                                    [% END %]
+                                                    First name:</label>
+
+                                                    <input type="text" id="borrower_firstname" name="borrower_firstname" value="[% borrower.firstname %]" />
+                                                    [% IF mandatory.defined('firstname') %]<span class="required">Required</span>[% END %]
+                                                </li>
+                                            [% END %]
+
+                                            [% UNLESS hidden.defined('dateofbirth') %]
+                                                <li>
+                                                    [% IF mandatory.defined('dateofbirth') %]
+                                                        <label for="borrower_dateofbirth" class="required">
+                                                    [% ELSE %]
+                                                        <label for="borrower_dateofbirth">
+                                                    [% END %]
+                                                    Date of birth:</label>
+
+                                                    <input type="text" id="borrower_dateofbirth" name="borrower_dateofbirth" value="[% borrower.dateofbirth %]" />
+                                                    [% IF mandatory.defined('dateofbirth') %]<span class="required">Required</span>[% END %]
+                                                </li>
+                                            [% END %]
+
+                                            [% UNLESS hidden.defined('initials') %]
+                                                <li>
+                                                    [% IF mandatory.defined('initials') %]
+                                                        <label for="borrower_initials" class="required">
+                                                    [% ELSE %]
+                                                        <label for="borrower_initials">
+                                                    [% END %]
+                                                    Initials:</label>
+
+                                                    <input type="text" id="borrower_initials" name="borrower_initials" value="[% borrower.initials %]" />
+                                                    [% IF mandatory.defined('initials') %]<span class="required">Required</span>[% END %]
+                                                </li>
+                                            [% END %]
+
+                                            [% UNLESS hidden.defined('othernames') %]
+                                                <li>
+                                                    [% IF mandatory.defined('othernames') %]
+                                                        <label for="borrower_othernames" class="required">
+                                                    [% ELSE %]
+                                                        <label for="borrower_othernames">
+                                                    [% END %]
+                                                    Other names:</label>
+
+                                                    <input type="text" id="borrower_othernames" name="borrower_othernames" value="[% borrower.othernames %]" />
+                                                    [% IF mandatory.defined('othernames') %]<span class="required">Required</span>[% END %]
+                                                </li>
+                                            [% END %]
+
+                                            [% UNLESS hidden.defined('sex') %]
+                                                <li class="radio">
+                                                    <label for="sex-female">Female:</label>
+                                                    [% IF borrower.sex == 'F' %]
+                                                        <input type="radio" name="borrower_sex" id="sex-female" value="F" checked="checked" />
+                                                    [% ELSE %]
+                                                        <input type="radio" name="borrower_sex" id="sex-female" value="F" />
+                                                    [% END %]
+
+                                                    <label for="sex-male">Male:</label>
+                                                    [% IF borrower.sex == 'M' %]
+                                                        <input type="radio" name="borrower_sex" id="sex-male" value="M" checked="checked" />
+                                                    [% ELSE %]
+                                                        <input type="radio" name="borrower_sex" id="sex-male" value="M" />
+                                                    [% END %]
+
+                                                    <label for="sex-none">N/A:</label>
+                                                    [% IF borrower.sex == '' %]
+                                                        <input type="radio" name="borrower_sex" id="sex-none" value="" checked="checked" />
+                                                    [% ELSE %]
+                                                        <input type="radio" name="borrower_sex" id="sex-none" value="" />
+                                                    [% END %]
+
+                                                    [% IF mandatory.defined('sex') %]<span class="required">Required</span>[% END %]
+                                                </li>
+                                            [% END %]
+                                        </ol>
+                                    </fieldset>
+                                [% END %]
+
+                                [% UNLESS
+                                    hidden.defined('streetnumber') && hidden.defined('address') && hidden.defined('address2') &&
+                                    hidden.defined('city') && hidden.defined('state') && hidden.defined('zipcode') &&
+                                    hidden.defined('country')
+                                %]
+                                    <fieldset class="rows" id="memberentry_mainaddress">
+                                        <legend id="mainaddress_legend">Main address</legend>
+
+                                        <ol>
+                                            [% UNLESS hidden.defined('streetnumber') %]
+                                                <li>
+                                                    [% IF mandatory.defined('streetnumber') %]
+                                                        <label for="borrower_streetnumber" class="required">
+                                                    [% ELSE %]
+                                                        <label for="borrower_streetnumber">
+                                                    [% END %]
+                                                    Street number:</label>
+
+                                                    <input type="text" id="borrower_streetnumber" name="borrower_streetnumber" value="[% borrower.streetnumber %]" />
+                                                    [% IF mandatory.defined('streetnumber') %]<span class="required">Required</span>[% END %]
+                                                </li>
+                                            [% END %]
+
+                                            [% UNLESS hidden.defined('address') %]
+                                                <li>
+                                                    [% IF mandatory.defined('address') %]
+                                                        <label for="borrower_address" class="required">
+                                                    [% ELSE %]
+                                                        <label for="borrower_address">
+                                                    [% END %]
+                                                    Address:</label>
+
+                                                    <input type="text" id="borrower_address" name="borrower_address" value="[% borrower.address %]" />
+                                                    [% IF mandatory.defined('address') %]<span class="required">Required</span>[% END %]
+                                                </li>
+                                            [% END %]
+
+                                            [% UNLESS hidden.defined('address2') %]
+                                                <li>
+                                                    [% IF mandatory.defined('address2') %]
+                                                        <label for="borrower_address2" class="required">
+                                                    [% ELSE %]
+                                                        <label for="borrower_address2">
+                                                    [% END %]
+                                                    Address 2:</label>
+
+                                                    <input type="text" id="borrower_address2" name="borrower_address2" value="[% borrower.address2 %]" />
+                                                    [% IF mandatory.defined('address2') %]<span class="required">Required</span>[% END %]
+                                                </li>
+                                            [% END %]
+
+                                            [% UNLESS hidden.defined('city') %]
+                                                <li>
+                                                    [% IF mandatory.defined('city') %]
+                                                        <label for="borrower_city" class="required">
+                                                    [% ELSE %]
+                                                        <label for="borrower_city">
+                                                    [% END %]
+                                                    City:</label>
+
+                                                    <input type="text" id="borrower_city" name="borrower_city" value="[% borrower.city %]" />
+                                                    [% IF mandatory.defined('city') %]<span class="required">Required</span>[% END %]
+                                                </li>
+                                            [% END %]
+
+                                            [% UNLESS hidden.defined('state') %]
+                                                <li>
+                                                    [% IF mandatory.defined('state') %]
+                                                        <label for="borrower_state" class="required">
+                                                    [% ELSE %]
+                                                        <label for="borrower_state">
+                                                    [% END %]
+                                                    State:</label>
+
+                                                    <input type="text" id="borrower_state" name="borrower_state" value="[% borrower.state %]" />
+                                                    [% IF mandatory.defined('state') %]<span class="required">Required</span>[% END %]
+                                                </li>
+                                            [% END %]
+
+                                            [% UNLESS hidden.defined('zipcode') %]
+                                                <li>
+                                                    [% IF mandatory.defined('zipcode') %]
+                                                        <label for="borrower_zipcode" class="required">
+                                                    [% ELSE %]
+                                                        <label for="borrower_zipcode">
+                                                    [% END %]
+                                                    Zip/Postal code:</label>
+
+                                                    <input type="text" id="borrower_zipcode" name="borrower_zipcode" value="[% borrower.zipcode %]" />
+                                                    [% IF mandatory.defined('zipcode') %]<span class="required">Required</span>[% END %]
+                                                </li>
+                                            [% END %]
+
+                                            [% UNLESS hidden.defined('country') %]
+                                                <li>
+                                                    [% IF mandatory.defined('country') %]
+                                                        <label for="borrower_country" class="required">
+                                                    [% ELSE %]
+                                                        <label for="borrower_country">
+                                                    [% END %]
+                                                    Country:</label>
+
+                                                    <input type="text" id="borrower_country" name="borrower_country" value="[% borrower.country %]" />
+                                                    [% IF mandatory.defined('country') %]<span class="required">Required</span>[% END %]
+                                                </li>
+                                            [% END %]
+
+                                        </ol>
+                                    </fieldset>
+                                [% END %]
+
+                                [% UNLESS
+                                    hidden.defined('phone') && hidden.defined('phonepro') && hidden.defined('mobile') &&
+                                    hidden.defined('email') && hidden.defined('emailpro') && hidden.defined('fax')
+                                %]
+                                    <fieldset class="rows" id="memberentry_contact">
+                                        <legend id="contact_legend">Contact information</legend>
+
+                                        <ol>
+                                            [% UNLESS hidden.defined('phone') %]
+                                                <li>
+                                                    [% IF mandatory.defined('phone') %]
+                                                        <label for="borrower_phone" class="required">
+                                                    [% ELSE %]
+                                                        <label for="borrower_phone">
+                                                    [% END %]
+                                                    Primary phone:</label>
+
+                                                    <input type="text" id="borrower_phone" name="borrower_phone" value="[% borrower.phone %]" />
+                                                    [% IF mandatory.defined('phone') %]<span class="required">Required</span>[% END %]
+                                                </li>
+                                            [% END %]
+
+                                            [% UNLESS hidden.defined('phonepro') %]
+                                                <li>
+                                                    [% IF mandatory.defined('phonepro') %]
+                                                        <label for="borrower_phonepro" class="required">
+                                                    [% ELSE %]
+                                                        <label for="borrower_phonepro">
+                                                    [% END %]
+                                                    Secondary phone:</label>
+
+                                                    <input type="text" id="borrower_phonepro" name="borrower_phonepro" value="[% borrower.phonepro %]" />
+                                                    [% IF mandatory.defined('phonepro') %]<span class="required">Required</span>[% END %]
+                                                </li>
+                                            [% END %]
+
+                                            [% UNLESS hidden.defined('mobile') %]
+                                                <li>
+                                                    [% IF mandatory.defined('mobile') %]
+                                                        <label for="borrower_mobile" class="required">
+                                                    [% ELSE %]
+                                                        <label for="borrower_mobile">
+                                                    [% END %]
+                                                    Mobile phone:</label>
+
+                                                    <input type="text" id="borrower_mobile" name="borrower_mobile" value="[% borrower.mobile %]" />
+                                                    [% IF mandatory.defined('mobile') %]<span class="required">Required</span>[% END %]
+                                                </li>
+                                            [% END %]
+
+                                            [% UNLESS hidden.defined('email') %]
+                                                <li>
+                                                    [% IF mandatory.defined('email') %]
+                                                        <label for="borrower_email" class="required">
+                                                    [% ELSE %]
+                                                        <label for="borrower_email">
+                                                    [% END %]
+                                                    Primary email:</label>
+
+                                                    <input type="text" id="borrower_email" name="borrower_email" value="[% borrower.email %]" />
+                                                    [% IF mandatory.defined('email') %]<span class="required">Required</span>[% END %]
+                                                </li>
+                                            [% END %]
+
+                                            [% UNLESS hidden.defined('emailpro') %]
+                                                <li>
+                                                    [% IF mandatory.defined('emailpro') %]
+                                                        <label for="borrower_emailpro" class="required">
+                                                    [% ELSE %]
+                                                        <label for="borrower_emailpro">
+                                                    [% END %]
+                                                    Secondary email:</label>
+
+                                                    <input type="text" id="borrower_emailpro" name="borrower_emailpro" value="[% borrower.emailpro %]" />
+                                                    [% IF mandatory.defined('emailpro') %]<span class="required">Required</span>[% END %]
+                                                </li>
+                                            [% END %]
+
+                                            [% UNLESS hidden.defined('fax') %]
+                                                <li>
+                                                    [% IF mandatory.defined('fax') %]
+                                                        <label for="borrower_fax" class="required">
+                                                    [% ELSE %]
+                                                        <label for="borrower_fax">
+                                                    [% END %]
+                                                    Fax:</label>
+
+                                                    <input type="text" id="borrower_fax" name="borrower_fax" value="[% borrower.fax %]" />
+                                                    [% IF mandatory.defined('fax') %]<span class="required">Required</span>[% END %]
+                                                </li>
+                                            [% END %]
+                                        </ol>
+                                    </fieldset>
+                                [% END %]
+
+                                [% UNLESS
+                                    hidden.defined('B_address') && hidden.defined('B_address2') && hidden.defined('B_city') &&
+                                    hidden.defined('B_state') && hidden.defined('B_zipcode') && hidden.defined('B_county') &&
+                                    hidden.defined('B_phone') && hidden.defined('B_email') && hidden.defined('contactnote')
+                                %]
+                                    <fieldset class="rows" id="memberentry_alternateaddress">
+                                        <legend id="alternateaddress_legend">Alternate address</legend>
+
+                                        <ol>
+                                            [% UNLESS hidden.defined('B_address') %]
+                                                <li>
+                                                    [% IF mandatory.defined('B_address') %]
+                                                        <label for="borrower_B_address" class="required">
+                                                    [% ELSE %]
+                                                        <label for="borrower_B_address">
+                                                    [% END %]
+                                                    Address:</label>
+
+                                                    <input type="text" id="borrower_B_address" name="borrower_B_address" value="[% borrower.B_address %]" />
+                                                    [% IF mandatory.defined('B_address') %]<span class="required">Required</span>[% END %]
+                                                </li>
+                                            [% END %]
+
+                                            [% UNLESS hidden.defined('B_address2') %]
+                                                <li>
+                                                    [% IF mandatory.defined('B_address2') %]
+                                                        <label for="borrower_B_address2" class="required">
+                                                    [% ELSE %]
+                                                        <label for="borrower_B_address2">
+                                                    [% END %]
+                                                    Address 2:</label>
+
+                                                    <input type="text" id="borrower_B_address2" name="borrower_B_address2" value="[% borrower.B_address2 %]" />
+                                                    [% IF mandatory.defined('B_address2') %]<span class="required">Required</span>[% END %]
+                                                </li>
+                                            [% END %]
+
+                                            [% UNLESS hidden.defined('B_city') %]
+                                                <li>
+                                                    [% IF mandatory.defined('B_city') %]
+                                                        <label for="borrower_B_city" class="required">
+                                                    [% ELSE %]
+                                                        <label for="borrower_B_city">
+                                                    [% END %]
+                                                    City:</label>
+
+                                                    <input type="text" id="borrower_B_city" name="borrower_B_city" value="[% borrower.B_city %]" />
+                                                    [% IF mandatory.defined('B_city') %]<span class="required">Required</span>[% END %]
+                                                </li>
+                                            [% END %]
+
+                                            [% UNLESS hidden.defined('B_state') %]
+                                                <li>
+                                                    [% IF mandatory.defined('B_state') %]
+                                                        <label for="borrower_B_state" class="required">
+                                                    [% ELSE %]
+                                                        <label for="borrower_B_state">
+                                                    [% END %]
+                                                    State:</label>
+
+                                                    <input type="text" id="borrower_B_state" name="borrower_B_state" value="[% borrower.B_state %]" />
+                                                    [% IF mandatory.defined('B_state') %]<span class="required">Required</span>[% END %]
+                                                </li>
+                                            [% END %]
+
+                                            [% UNLESS hidden.defined('B_zipcode') %]
+                                                <li>
+                                                    [% IF mandatory.defined('B_zipcode') %]
+                                                        <label for="borrower_B_zipcode" class="required">
+                                                    [% ELSE %]
+                                                        <label for="borrower_B_zipcode">
+                                                    [% END %]
+                                                    Zip/Postal code:</label>
+
+                                                    <input type="text" id="borrower_B_zipcode" name="borrower_B_zipcode" value="[% borrower.B_zipcode %]" />
+                                                    [% IF mandatory.defined('B_zipcode') %]<span class="required">Required</span>[% END %]
+                                                </li>
+                                            [% END %]
+
+                                            [% UNLESS hidden.defined('B_country') %]
+                                                <li>
+                                                    [% IF mandatory.defined('B_country') %]
+                                                        <label for="borrower_B_country" class="required">
+                                                    [% ELSE %]
+                                                        <label for="borrower_B_country">
+                                                    [% END %]
+                                                    Country:</label>
+
+                                                    <input type="text" id="borrower_B_country" name="borrower_B_country" value="[% borrower.B_country %]" />
+                                                    [% IF mandatory.defined('B_country') %]<span class="required">Required</span>[% END %]
+                                                </li>
+                                            [% END %]
+
+                                            [% UNLESS hidden.defined('B_phone') %]
+                                                <li>
+                                                    [% IF mandatory.defined('B_phone') %]
+                                                        <label for="borrower_B_phone" class="required">
+                                                    [% ELSE %]
+                                                        <label for="borrower_B_phone">
+                                                    [% END %]
+                                                    Phone:</label>
+
+                                                    <input type="text" id="borrower_B_phone" name="borrower_B_phone" value="[% borrower.B_phone %]" />
+                                                    [% IF mandatory.defined('B_phone') %]<span class="required">Required</span>[% END %]
+                                                </li>
+                                            [% END %]
+
+                                            [% UNLESS hidden.defined('B_email') %]
+                                                <li>
+                                                    [% IF mandatory.defined('B_email') %]
+                                                        <label for="borrower_B_email" class="required">
+                                                    [% ELSE %]
+                                                        <label for="borrower_B_email">
+                                                    [% END %]
+                                                    Email:</label>
+
+                                                    <input type="text" id="borrower_B_email" name="borrower_B_email" value="[% borrower.B_email %]" />
+                                                    [% IF mandatory.defined('B_email') %]<span class="required">Required</span>[% END %]
+                                                </li>
+                                            [% END %]
+
+                                            [% UNLESS hidden.defined('contactnote') %]
+                                                <li>
+                                                    [% IF mandatory.defined('contactnote') %]
+                                                        <label for="borrower_contactnote" class="required">
+                                                    [% ELSE %]
+                                                        <label for="borrower_contactnote">
+                                                    [% END %]
+                                                    Contact Note:</label>
+
+                                                    <textarea id="borrower_contactnote" name="borrower_contactnote" cols="40" rows="2">[% borrower.contactnote %]</textarea>
+                                                    [% IF mandatory.defined('contactnote') %]<span class="required">Required</span>[% END %]
+                                                </li>
+                                            [% END %]
+
+                                        </ol>
+                                    </fieldset>
+                                [% END %]
+
+                                [% UNLESS
+                                    hidden.defined('altcontactsurname') && hidden.defined('altcontactfirstname') && hidden.defined('altcontactaddress1') &&
+                                    hidden.defined('altcontactaddress2') && hidden.defined('altcontactaddress3') && hidden.defined('altcontactstate') &&
+                                    hidden.defined('altcontactzipcode') && hidden.defined('altcontactcountry') && hidden.defined('altcontactphone')
+                                %]
+                                    <fieldset class="rows" id="memberentry_alternatecontact">
+                                        <legend id="alternatecontact_legend">Alternate contact</legend>
+
+                                        <ol>
+                                            [% UNLESS hidden.defined('altcontactsurname') %]
+                                                <li>
+                                                    [% IF mandatory.defined('altcontactsurname') %]
+                                                        <label for="borrower_altcontactsurname" class="required">
+                                                    [% ELSE %]
+                                                        <label for="borrower_altcontactsurname">
+                                                    [% END %]
+                                                    Surname:</label>
+
+                                                    <input type="text" id="borrower_altcontactsurname" name="borrower_altcontactsurname" value="[% borrower.altcontactsurname %]" />
+                                                    [% IF mandatory.defined('altcontactsurname') %]<span class="required">Required</span>[% END %]
+                                                </li>
+                                            [% END %]
+
+                                            [% UNLESS hidden.defined('altcontactfirstname') %]
+                                                <li>
+                                                    [% IF mandatory.defined('altcontactfirstname') %]
+                                                        <label for="borrower_altcontactfirstname" class="required">
+                                                    [% ELSE %]
+                                                        <label for="borrower_altcontactfirstname">
+                                                    [% END %]
+                                                    First name:</label>
+
+                                                    <input type="text" id="borrower_altcontactfirstname" name="borrower_altcontactfirstname" value="[% borrower.altcontactfirstname %]" />
+                                                    [% IF mandatory.defined('altcontactfirstname') %]<span class="required">Required</span>[% END %]
+                                                </li>
+                                            [% END %]
+
+                                            [% UNLESS hidden.defined('altcontactaddress1') %]
+                                                <li>
+                                                    [% IF mandatory.defined('altcontactaddress1') %]
+                                                        <label for="borrower_altcontactaddress1" class="required">
+                                                    [% ELSE %]
+                                                        <label for="borrower_altcontactaddress1">
+                                                    [% END %]
+                                                    Address:</label>
+
+                                                    <input type="text" id="borrower_altcontactaddress1" name="borrower_altcontactaddress1" value="[% borrower.altcontactaddress1 %]" />
+                                                    [% IF mandatory.defined('altcontactaddress1') %]<span class="required">Required</span>[% END %]
+                                                </li>
+                                            [% END %]
+
+                                            [% UNLESS hidden.defined('altcontactaddress2') %]
+                                                <li>
+                                                    [% IF mandatory.defined('altcontactaddress2') %]
+                                                        <label for="borrower_altcontactaddress2" class="required">
+                                                    [% ELSE %]
+                                                        <label for="borrower_altcontactaddress2">
+                                                    [% END %]
+                                                    Address 2:</label>
+
+                                                    <input type="text" id="borrower_altcontactaddress2" name="borrower_altcontactaddress2" value="[% borrower.altcontactaddress2 %]" />
+                                                    [% IF mandatory.defined('altcontactaddress2') %]<span class="required">Required</span>[% END %]
+                                                </li>
+                                            [% END %]
+
+                                            [% UNLESS hidden.defined('altcontactaddress3') %]
+                                                <li>
+                                                    [% IF mandatory.defined('altcontactaddress3') %]
+                                                        <label for="borrower_altcontactaddress3" class="required">
+                                                    [% ELSE %]
+                                                        <label for="borrower_altcontactaddress3">
+                                                    [% END %]
+                                                    City:</label>
+
+                                                    <input type="text" id="borrower_altcontactaddress3" name="borrower_altcontactaddress3" value="[% borrower.altcontactaddress3 %]" />
+                                                    [% IF mandatory.defined('altcontactaddress3') %]<span class="required">Required</span>[% END %]
+                                                </li>
+                                            [% END %]
+
+                                            [% UNLESS hidden.defined('altcontactstate') %]
+                                                <li>
+                                                    [% IF mandatory.defined('altcontactstate') %]
+                                                        <label for="borrower_altcontactstate" class="required">
+                                                    [% ELSE %]
+                                                        <label for="borrower_altcontactstate">
+                                                    [% END %]
+                                                    State:</label>
+
+                                                    <input type="text" id="borrower_altcontactstate" name="borrower_altcontactstate" value="[% borrower.altcontactstate %]" />
+                                                    [% IF mandatory.defined('altcontactstate') %]<span class="required">Required</span>[% END %]
+                                                </li>
+                                            [% END %]
+
+                                            [% UNLESS hidden.defined('altcontactzipcode') %]
+                                                <li>
+                                                    [% IF mandatory.defined('altcontactzipcode') %]
+                                                        <label for="borrower_altcontactzipcode" class="required">
+                                                    [% ELSE %]
+                                                        <label for="borrower_altcontactzipcode">
+                                                    [% END %]
+                                                    Zip/Postal code:</label>
+
+                                                    <input type="text" id="borrower_altcontactzipcode" name="borrower_altcontactzipcode" value="[% borrower.altcontactzipcode %]" />
+                                                    [% IF mandatory.defined('altcontactzipcode') %]<span class="required">Required</span>[% END %]
+                                                </li>
+                                            [% END %]
+
+                                            [% UNLESS hidden.defined('altcontactcountry') %]
+                                                <li>
+                                                    [% IF mandatory.defined('altcontactcountry') %]
+                                                        <label for="borrower_altcontactcountry" class="required">
+                                                    [% ELSE %]
+                                                        <label for="borrower_altcontactcountry">
+                                                    [% END %]
+                                                    Country:</label>
+
+                                                    <input type="text" id="borrower_altcontactcountry" name="borrower_altcontactcountry" value="[% borrower.altcontactcountry %]" />
+                                                    [% IF mandatory.defined('altcontactcountry') %]<span class="required">Required</span>[% END %]
+                                                </li>
+                                            [% END %]
+
+                                            [% UNLESS hidden.defined('altcontactphone') %]
+                                                <li>
+                                                    [% IF mandatory.defined('altcontactphone') %]
+                                                        <label for="borrower_altcontactphone" class="required">
+                                                    [% ELSE %]
+                                                        <label for="borrower_altcontactphone">
+                                                    [% END %]
+                                                    Phone:</label>
+
+                                                    <input type="text" id="borrower_altcontactphone" name="borrower_altcontactphone" value="[% borrower.altcontactphone %]" />
+                                                    [% IF mandatory.defined('altcontactphone') %]<span class="required">Required</span>[% END %]
+                                                </li>
+                                            [% END %]
+                                        </ol>
+                                    </fieldset>
+                                [% END %]
+
+                                [% UNLESS action == 'edit' %]
+                                    <p>Please type this following characters into the box below : [% captcha %]</p>
+                                    <p>
+                                        <input type="text" name="captcha" id="captcha" />
+                                        <input type="hidden" name="captcha_digest" value="[% captcha_digest %]" />
+                                    </p>
+                                [% END %]
+
+                                [% IF action == 'edit' %]
+                                    <input type="hidden" name="action" value="update" />
+                                    <input type="submit" value="Submit Update Request" />
+                                [% ELSE %]
+                                    <input type="hidden" name="action" value="create" />
+                                    <input type="submit" value="Submit" />
+                                [% END %]
+
+                            </form>
+
+                        </div><!--/div id="useraccount" -->
+                    </div>
+                </div>
+            </div>
+
+            <div class="yui-b">
+                <div id="leftmenus" class="container">
+                    [% INCLUDE 'navigation.inc' IsPatronPage=1 %]
+                </div>
+            </div>
+        </div>
+[% INCLUDE 'opac-bottom.inc' %]
diff --git a/koha-tmpl/opac-tmpl/prog/en/modules/opac-registration-confirmation.tt b/koha-tmpl/opac-tmpl/prog/en/modules/opac-registration-confirmation.tt
new file mode 100644
index 0000000..968faa0
--- /dev/null
+++ b/koha-tmpl/opac-tmpl/prog/en/modules/opac-registration-confirmation.tt
@@ -0,0 +1,70 @@
+[% INCLUDE 'doc-head-open.inc' %]
+[% IF ( LibraryNameTitle ) %][% LibraryNameTitle %][% ELSE %]Koha online[% END %] catalog
+[% INCLUDE 'doc-head-close.inc' %]
+</head>
+<body id="opac-main">
+[% IF ( OpacNav ) %]<div id="doc3" class="yui-t1">[% ELSE %]<div id="doc3" class="yui-t7">[% END %]
+   <div id="bd">
+[% INCLUDE 'masthead.inc' %]
+
+<div id="yui-main">
+    <div class="yui-b">
+        <div id="loggedin" class="yui-ge">
+            <div class="yui-u first">
+                <h1>Registration Complete!</h1>
+
+                <p>You have successfully registered your new account. To log in, use the following credentials:</p>
+
+                <p>
+                    Username: [% borrower.userid %]
+                    <br/>
+                    Password: [% password_cleartext %]
+                </p>
+
+                <p>For your convenience, the login box on this page has been pre-filled with this data. Please log in[% IF OpacPasswordChange %] and change your password[% END %].</p>
+
+                <div id="PatronSelfRegistrationAdditionalInstructions">[% PatronSelfRegistrationAdditionalInstructions %]</div>
+            </div>
+
+            [% IF ( opacuserlogin || OpacNavRight ) %]
+                <div class="yui-u">
+                    [% IF ( opacuserlogin ) %]
+                        [% UNLESS ( loggedinusername ) %]
+                            [% UNLESS ( casAuthentication ) %]
+                                <div id="login" class="container clearfix">
+                                    <form action="/cgi-bin/koha/opac-user.pl" method="post" name="auth" id="auth">
+                                        <input type="hidden" name="koha_login_context" value="opac" />
+
+                                        <fieldset class="brief">
+                                            <legend>Log in to your account:</legend>
+
+                                            <ol>
+                                                <li><label for="userid">Login:</label><input type="text" id="userid" size="10" name="userid" value="[% borrower.userid %]" /></li>
+                                                <li><label for="password">Password:</label><input type="password" id="password" size="10" name="password" value="[% password_cleartext %]" /></li>
+                                            </ol>
+
+                                            <fieldset class="action">
+                                                <input type="submit" value="Log In" class="submit" />
+                                            </fieldset>
+                                        </fieldset>
+                                    </form>
+                                </div>
+                            [% END %]
+                        [% END %]
+                    [% END %]
+
+                    [% IF ( OpacNavRight ) %]<div id="opacrightsidebar" class="container">[% OpacNavRight %]</div>[% END %]
+                </div>
+            [% END %]
+        </div>
+    </div>
+</div>
+
+[% IF ( OpacNav ) %]<div class="yui-b">
+    <div id="opacnav" class="container">
+        [% INCLUDE 'navigation.inc' %]
+    </div>
+[% END %]
+
+</div>
+[% INCLUDE 'opac-bottom.inc' %]
diff --git a/koha-tmpl/opac-tmpl/prog/en/modules/opac-registration-email-sent.tt b/koha-tmpl/opac-tmpl/prog/en/modules/opac-registration-email-sent.tt
new file mode 100644
index 0000000..bdad739
--- /dev/null
+++ b/koha-tmpl/opac-tmpl/prog/en/modules/opac-registration-email-sent.tt
@@ -0,0 +1,31 @@
+[% INCLUDE 'doc-head-open.inc' %]
+[% IF ( LibraryNameTitle ) %][% LibraryNameTitle %][% ELSE %]Koha online[% END %] catalog
+[% INCLUDE 'doc-head-close.inc' %]
+</head>
+<body id="opac-main">
+[% IF ( OpacNav ) %]<div id="doc3" class="yui-t1">[% ELSE %]<div id="doc3" class="yui-t7">[% END %]
+   <div id="bd">
+[% INCLUDE 'masthead.inc' %]
+
+<div id="yui-main">
+    <div class="yui-b">
+        <div id="loggedin" class="yui-ge">
+            <div class="yui-u first">
+                <h1>Please Confirm Registration</h1>
+
+                <p>A confirmation email has been sent to the email address <strong>[% email %]</strong>.</p>
+
+                <p>Your account will not be activated until you follow the link provided in the confirmation email.</p>
+            </div>
+        </div>
+    </div>
+</div>
+
+[% IF ( OpacNav ) %]<div class="yui-b">
+    <div id="opacnav" class="container">
+        [% INCLUDE 'navigation.inc' %]
+    </div>
+[% END %]
+
+</div>
+[% INCLUDE 'opac-bottom.inc' %]
diff --git a/mainpage.pl b/mainpage.pl
index f834985..78dbe66 100755
--- a/mainpage.pl
+++ b/mainpage.pl
@@ -28,7 +28,9 @@ use C4::NewsChannels;
 use C4::Review qw/numberofreviews/;
 use C4::Suggestions qw/CountSuggestion/;
 use C4::Tags qw/get_count_by_tag_status/;
-my $query     = new CGI;
+use Koha::Borrower::Modifications;
+
+my $query = new CGI;
 
 my ( $template, $loggedinuser, $cookie ) = get_template_and_user(
     {
@@ -36,9 +38,7 @@ my ( $template, $loggedinuser, $cookie ) = get_template_and_user(
         query           => $query,
         type            => "intranet",
         authnotrequired => 0,
-        flagsrequired   => {
-            catalogue => 1,
-        },
+        flagsrequired   => { catalogue => 1, },
     }
 );
 
@@ -50,14 +50,17 @@ $template->param(
     koha_news_count => $koha_news_count
 );
 
-my $pendingcomments = numberofreviews(0);
-my $pendingtags = get_count_by_tag_status(0);
-my $pendingsuggestions       = CountSuggestion("ASKED");
+my $pendingcomments    = numberofreviews(0);
+my $pendingtags        = get_count_by_tag_status(0);
+my $pendingsuggestions = CountSuggestion("ASKED");
+my $pending_borrower_modifications =
+  Koha::Borrower::Modifications->GetPendingModificationsCount();
 
 $template->param(
-    pendingcomments    => $pendingcomments,
-    pendingtags        => $pendingtags,
-    pendingsuggestions => $pendingsuggestions
+    pendingcomments                => $pendingcomments,
+    pendingtags                    => $pendingtags,
+    pendingsuggestions             => $pendingsuggestions,
+    pending_borrower_modifications => $pending_borrower_modifications,
 );
 
 output_html_with_http_headers $query, $cookie, $template->output;
diff --git a/members/members-update-do.pl b/members/members-update-do.pl
new file mode 100755
index 0000000..147975a
--- /dev/null
+++ b/members/members-update-do.pl
@@ -0,0 +1,64 @@
+#!/usr/bin/perl
+
+# Parts Copyright Biblibre 2010
+# 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 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.
+
+use strict;
+use warnings;
+
+use CGI;
+use C4::Auth;
+use C4::Output;
+use C4::Context;
+use C4::Members;
+use C4::Branch;
+use C4::Category;
+use Koha::Borrower::Modifications;
+
+my $query = new CGI;
+
+my ( $template, $loggedinuser, $cookie ) = get_template_and_user(
+    {
+        template_name   => "about.tmpl",
+        query           => $query,
+        type            => "intranet",
+        authnotrequired => 0,
+        flagsrequired   => { borrowers => 1 },
+        debug           => 1,
+    }
+);
+
+my @params = $query->param;
+
+foreach my $param (@params) {
+    if ( $param =~ "^modify_" ) {
+        my (undef, $borrowernumber) = split( /_/, $param );
+
+        my $action = $query->param($param);
+
+        if ( $action eq 'approve' ) {
+            Koha::Borrower::Modifications->ApproveModifications( $borrowernumber );
+        }
+        elsif ( $action eq 'deny' ) {
+            Koha::Borrower::Modifications->DenyModifications( $borrowernumber );
+        }
+        elsif ( $action eq 'ignore' ) {
+
+        }
+    }
+}
+
+print $query->redirect("/cgi-bin/koha/mainpage.pl");
diff --git a/members/members-update.pl b/members/members-update.pl
new file mode 100755
index 0000000..9ffcf42
--- /dev/null
+++ b/members/members-update.pl
@@ -0,0 +1,59 @@
+#!/usr/bin/perl
+
+# Parts Copyright Biblibre 2010
+# 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 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.
+
+use strict;
+use warnings;
+
+use CGI;
+use C4::Auth;
+use C4::Output;
+use C4::Context;
+use C4::Members;
+use C4::Branch;
+use C4::Category;
+use Koha::Borrower::Modifications;
+
+my $query = new CGI;
+
+my ( $template, $loggedinuser, $cookie ) = get_template_and_user(
+    {
+        template_name   => "members/members-update.tmpl",
+        query           => $query,
+        type            => "intranet",
+        authnotrequired => 0,
+        flagsrequired   => { borrowers => 1 },
+        debug           => 1,
+    }
+);
+
+my $pending_modifications =
+  Koha::Borrower::Modifications->GetPendingModifications();
+
+my $borrowers;
+foreach my $pm (@$pending_modifications) {
+    $borrowers->{ $pm->{'borrowernumber'} } =
+      GetMember( borrowernumber => $pm->{'borrowernumber'} );
+
+}
+
+$template->param(
+    PendingModifications => $pending_modifications,
+    borrowers            => $borrowers,
+);
+
+output_html_with_http_headers $query, $cookie, $template->output;
diff --git a/misc/cronjobs/delete_expired_opac_registrations.pl b/misc/cronjobs/delete_expired_opac_registrations.pl
new file mode 100755
index 0000000..af7881a
--- /dev/null
+++ b/misc/cronjobs/delete_expired_opac_registrations.pl
@@ -0,0 +1,55 @@
+#!/usr/bin/perl
+
+# Copyright 2009-2010 Kyle Hall
+#
+# 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 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.
+
+#use strict;
+#use warnings; FIXME - Bug 2505
+
+BEGIN {
+
+    # find Koha's Perl modules
+    # test carefully before changing this
+    use FindBin;
+    eval { require "$FindBin::Bin/../kohalib.pl" };
+}
+
+use C4::Context;
+use C4::Members qw/ DelMember /;
+
+## Delete accounts that haven't been upgraded from the 'temporary' category code'
+my $delay =
+  C4::Context->preference('PatronSelfRegistrationExpireTemporaryAccountsDelay');
+my $category_code =
+  C4::Context->preference('PatronSelfRegistrationUseTemporaryStatus');
+
+my $query = "
+    SELECT borrowernumber
+    FROM borrowers
+    WHERE
+        categorycode = ?
+      AND
+        DATEDIFF( DATE( NOW() ), DATE(dateenrolled) ) = ? )
+";
+
+my $dbh = C4::Context->dbh;
+my $sth = $dbh->prepare($query);
+$sth->execute( $category_code, $delay );
+
+while ( my ($borrowernumber) = $sth->fetchrow_array() ) {
+    DelMember($borrowernumber);
+}
diff --git a/misc/cronjobs/delete_unverified_opac_registrations.pl b/misc/cronjobs/delete_unverified_opac_registrations.pl
new file mode 100755
index 0000000..9c801a4
--- /dev/null
+++ b/misc/cronjobs/delete_unverified_opac_registrations.pl
@@ -0,0 +1,57 @@
+#!/usr/bin/perl
+
+# Copyright 2009-2010 Kyle Hall
+#
+# 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 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.
+
+#use strict;
+#use warnings; FIXME - Bug 2505
+
+BEGIN {
+
+    # find Koha's Perl modules
+    # test carefully before changing this
+    use FindBin;
+    eval { require "$FindBin::Bin/../kohalib.pl" };
+}
+
+use C4::Context;
+use C4::Members qw/ DelMember /;
+
+## Delete accounts that haven't been upgraded from the 'temporary' category code'
+my $delay =
+  C4::Context->preference('PatronSelfRegistrationExpireTemporaryAccountsDelay');
+
+if ($delay) {
+    my $query = "
+    SELECT borrowernumber
+    FROM borrowers
+    WHERE
+        categorycode = ?
+      AND
+        DATEDIFF( DATE( NOW() ), DATE(dateenrolled) ) = ? )
+    ";
+
+    my $dbh = C4::Context->dbh;
+
+    $dbh->do("
+         DELETE FROM borrower_modifications
+         WHERE
+             borrowernumber = 0
+           AND
+             TIME_TO_SEC( TIMEDIFF( NOW(), timestamp )) / 3600 > 24
+    ");
+}
diff --git a/opac/opac-memberentry.pl b/opac/opac-memberentry.pl
new file mode 100755
index 0000000..81c8ae5
--- /dev/null
+++ b/opac/opac-memberentry.pl
@@ -0,0 +1,234 @@
+#!/usr/bin/perl
+
+# 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 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.
+
+use Modern::Perl;
+
+use CGI;
+use Digest::MD5 qw( md5_base64 md5_hex );
+use String::Random qw( random_string );
+
+use C4::Auth;
+use C4::Output;
+use C4::Members;
+use Koha::Borrower::Modifications;
+use C4::Branch qw(GetBranchesLoop);
+
+my $cgi = new CGI;
+my $dbh = C4::Context->dbh;
+
+unless ( C4::Context->preference('PatronSelfRegistration') ) {
+    print $cgi->redirect("/cgi-bin/koha/opac-main.pl");
+    exit;
+}
+
+my ( $template, $borrowernumber, $cookie ) = get_template_and_user(
+    {
+        template_name   => "opac-memberentry.tmpl",
+        type            => "opac",
+        query           => $cgi,
+        authnotrequired => 1,
+    }
+);
+
+$template->param(
+    hidden        => GetHiddenFields(),
+    mandatory     => GetMandatoryFields(),
+    member_titles => GetTitles(),
+    branches      => GetBranchesLoop()
+);
+
+my $action = $cgi->param('action') || q{};
+if ( $action eq 'create' ) {
+
+    my %borrower = ParseCgiForBorrower($cgi);
+
+    my @empty_mandatory_fields = CheckMandatoryFields( \%borrower );
+
+    if (@empty_mandatory_fields) {
+        $template->param(
+            empty_mandatory_fields => \@empty_mandatory_fields,
+            borrower               => \%borrower
+        );
+    }
+    elsif (
+        md5_base64( $cgi->param('captcha') ) ne $cgi->param('captcha_digest') )
+    {
+        $template->param(
+            failed_captcha => 1,
+            borrower       => \%borrower
+        );
+    }
+    else {
+        if (
+            C4::Context->boolean_preference(
+                'PatronSelfRegistrationVerifyByEmail')
+          )
+        {
+            ( $template, $borrowernumber, $cookie ) = get_template_and_user(
+                {
+                    template_name   => "opac-registration-email-sent.tmpl",
+                    type            => "opac",
+                    query           => $cgi,
+                    authnotrequired => 1,
+                }
+            );
+            $template->param( 'email' => $borrower{'email'} );
+
+            my $verification_token = md5_hex( \%borrower );
+            $borrower{'password'} = random_string("..........");
+
+            Koha::Borrower::Modifications->new( verification_token => $verification_token )->AddModifications(%borrower);
+
+            #Send verification email
+            my $letter = C4::Letters::GetPreparedLetter(
+                module      => 'members',
+                letter_code => 'OPAC_REG_VERIFY',
+                tables      => {
+                    borrower_modifications =>
+                      [ $verification_token, $verification_token ],
+                },
+            );
+
+            C4::Letters::EnqueueLetter(
+                {
+                    letter                 => $letter,
+                    message_transport_type => 'email',
+                    to_address             => $borrower{'email'},
+                    from_address =>
+                      C4::Context->preference('KohaAdminEmailAddress'),
+                }
+            );
+        }
+        else {
+            ( $template, $borrowernumber, $cookie ) = get_template_and_user(
+                {
+                    template_name   => "opac-registration-confirmation.tmpl",
+                    type            => "opac",
+                    query           => $cgi,
+                    authnotrequired => 1,
+                }
+            );
+
+            $template->param( OpacPasswordChange => C4::Context->preference('OpacPasswordChange') );
+
+            my ( $borrowernumber, $password ) = AddMember_Opac(%borrower);
+
+            $template->param( password_cleartext => $password );
+            $template->param(
+                borrower => GetMember( borrowernumber => $borrowernumber ) );
+            $template->param(
+                PatronSelfRegistrationAdditionalInstructions =>
+                  C4::Context->preference(
+                    'PatronSelfRegistrationAdditionalInstructions')
+            );
+        }
+    }
+}
+elsif ( $action eq 'update' ) {
+    ( $template, $borrowernumber, $cookie ) = get_template_and_user(
+        {
+            template_name   => "opac-memberentry-update-submitted.tmpl",
+            type            => "opac",
+            query           => $cgi,
+            authnotrequired => 1,
+        }
+    );
+
+    my %borrower = ParseCgiForBorrower($cgi);
+
+    Koha::Borrower::Modifications->new( borrowernumber => $borrowernumber )->AddModifications(%borrower);
+}
+elsif ($borrowernumber) {    #Display logged in borrower's data
+    $action = 'edit';
+
+    $template->param(
+        borrower => GetMember( borrowernumber => $borrowernumber ) );
+}
+
+my $captcha = random_string("CCCCC");
+
+$template->param(
+    captcha        => $captcha,
+    captcha_digest => md5_base64($captcha)
+);
+
+$template->param( action => $action );
+
+output_html_with_http_headers $cgi, $cookie, $template->output;
+
+sub GetHiddenFields {
+    my %hidden_fields;
+
+    my $BorrowerUnwantedField =
+      C4::Context->preference("BorrowerUnwantedField");
+
+    my @fields = split( /\|/, $BorrowerUnwantedField );
+    foreach (@fields) {
+        next unless m/\w/o;
+        $hidden_fields{$_} = 1;
+    }
+
+    return \%hidden_fields;
+}
+
+sub GetMandatoryFields {
+    my %mandatory_fields;
+
+    my $BorrowerMandatoryField =
+      C4::Context->preference("BorrowerMandatoryField");
+
+    my @fields = split( /\|/, $BorrowerMandatoryField );
+
+    foreach (@fields) {
+        $mandatory_fields{$_} = 1;
+    }
+
+    $mandatory_fields{'email'} = 1
+      if C4::Context->boolean_preference('PatronSelfRegistrationVerifyByEmail');
+
+    return \%mandatory_fields;
+}
+
+sub CheckMandatoryFields {
+    my ($borrower) = @_;
+
+    my @empty_mandatory_fields;
+
+    my $mandatory_fields = GetMandatoryFields();
+    delete $mandatory_fields->{'cardnumber'};
+
+    foreach my $key ( keys %$mandatory_fields ) {
+        push( @empty_mandatory_fields, $key )
+          unless ( defined( $borrower->{$key} ) && $borrower->{$key} );
+    }
+
+    return @empty_mandatory_fields;
+}
+
+sub ParseCgiForBorrower {
+    my ($cgi) = @_;
+
+    my %borrower;
+
+    foreach ( $cgi->param ) {
+        my ($key) = substr( $_, 9 );
+        $borrower{$key} = $cgi->param($_)
+          if ( $_ =~ '^borrower_' && $cgi->param($_) );
+    }
+
+    return %borrower;
+}
diff --git a/opac/opac-registration-verify.pl b/opac/opac-registration-verify.pl
new file mode 100755
index 0000000..7d4c2ba
--- /dev/null
+++ b/opac/opac-registration-verify.pl
@@ -0,0 +1,69 @@
+#!/usr/bin/perl
+
+# 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 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.
+
+use Modern::Perl;
+
+use CGI;
+
+use C4::Auth;
+use C4::Output;
+use C4::Members;
+use Koha::Borrower::Modifications;
+
+my $cgi = new CGI;
+my $dbh = C4::Context->dbh;
+
+unless ( C4::Context->preference('PatronSelfRegistration') ) {
+    print $cgi->redirect("/cgi-bin/koha/opac-main.pl");
+    exit;
+}
+
+( $template, $borrowernumber, $cookie ) = get_template_and_user(
+    {
+        template_name   => "opac-registration-confirmation.tmpl",
+        type            => "opac",
+        query           => $cgi,
+        authnotrequired => 1,
+    }
+);
+
+$template->param( OpacPasswordChange => C4::Context->preference('OpacPasswordChange') );
+
+my $token = $cgi->param('token');
+my $m = Koha::Borrower::Modifications->new( verification_token => token );
+
+if ( $m->Verify() ) {
+
+    my $password;
+    ( $borrowernumber, $password ) = AddMember_Opac(%borrower);
+
+    $m->Delete();
+
+    $template->param( password_cleartext => $password );
+    $template->param(
+        borrower => GetMember( borrowernumber => $borrowernumber ) );
+    $template->param(
+        PatronSelfRegistrationAdditionalInstructions => C4::Context->preference(
+            'PatronSelfRegistrationAdditionalInstructions')
+    );
+
+}
+else {
+
+}
+
+output_html_with_http_headers $cgi, $cookie, $template->output;
-- 
1.7.2.5