LP1207396 Patron self-registration web form
authorBill Erickson <berick@esilibrary.com>
Fri, 2 Aug 2013 21:20:07 +0000 (17:20 -0400)
committerBill Erickson <berick@esilibrary.com>
Fri, 2 Aug 2013 21:20:07 +0000 (17:20 -0400)
Signed-off-by: Bill Erickson <berick@esilibrary.com>
Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader/Register.pm
Open-ILS/src/templates/opac/register.tt2

index b2265b1..9d8d48d 100644 (file)
@@ -4,6 +4,7 @@ use Apache2::Const -compile => qw(OK FORBIDDEN HTTP_INTERNAL_SERVER_ERROR);
 use OpenSRF::Utils::Logger qw/$logger/;
 use OpenILS::Utils::Fieldmapper;
 use OpenILS::Application::AppUtils;
+use OpenILS::Utils::CStoreEditor qw/:funcs/;
 use OpenILS::Event;
 use Data::Dumper;
 $Data::Dumper::Indent = 0;
@@ -14,6 +15,7 @@ sub load_patron_reg {
     my $ctx = $self->ctx;
     my $cgi = $self->cgi;
     $ctx->{register} = {};
+    $self->collect_register_validation_settings;
 
     # just loading the form
     return Apache2::Const::OK
@@ -25,7 +27,7 @@ sub load_patron_reg {
     # user
     foreach (grep /^stgu\./, $cgi->param) {
         my $val = $cgi->param($_);
-        # TODO validate
+        $self->inspect_register_value($_, $val);
         s/^stgu\.//g;
         $user->$_($val);
     }
@@ -33,11 +35,14 @@ sub load_patron_reg {
     # address
     foreach (grep /^stgma\./, $cgi->param) {
         my $val = $cgi->param($_);
-        # TODO validate
+        $self->inspect_register_value($_, $val);
         s/^stgma\.//g;
         $addr->$_($val);
     }
 
+    # At least one value was invalid. Exit early and re-render.
+    return Apache2::Const::OK if $ctx->{register}{invalid};
+
     # user.stage.create will generate a temporary usrname and 
     # link the user and address objects via this username in the DB.
     my $resp = $U->simplereq(
@@ -59,3 +64,102 @@ sub load_patron_reg {
 
     return Apache2::Const::OK;
 }
+
+sub collect_register_validation_settings {
+    my $self = shift;
+    my $ctx = $self->ctx;
+    my $e = new_editor();
+    my $ctx_org = $ctx->{physical_loc} || $self->_get_search_lib;
+    my $shash = $self->{register}{settings} = {};
+
+    # retrieve the org unit setting types and values
+    # that are relevant to our validation tasks.
+
+    my $settings = $e->json_query({
+        select => {coust => ['name']},
+        from => 'coust',
+        where => {name => {like => 'ui.patron.edit.%.%.%'}}
+    });
+
+    # load org setting values for all of the regex, 
+    # example, show, and require settings
+    for my $set (@$settings) {
+        $set = $set->{name};
+        next unless $set =~ /regex$|show$|require$|example$/;
+
+        my $val = $ctx->{get_org_setting}->($ctx_org, $set);
+        next unless $val; # no configured org setting
+
+        # extract the field class, name, and 
+        # setting type from the setting name
+        my (undef, undef, undef, $cls, $field, $type) = split(/\./, $set);
+
+        # translate classes into stage classes
+        my $scls = ($cls eq 'au') ? 'stgu' : 'stgma';
+
+        $shash->{$scls}{$field}{$type} = $val;
+    }
+
+    # use the generic phone settings where none are provided for day_phone.
+
+    $shash->{stgu}{day_phone}{example} =
+        $ctx->{get_org_setting}->($ctx_org, 'ui.patron.edit.phone.example')
+        unless $shash->{stgu}{day_phone}{example};
+
+    $shash->{stgu}{day_phone}{regex} =
+        $ctx->{get_org_setting}->($ctx_org, 'ui.patron.edit.phone.regex')
+        unless $shash->{stgu}{day_phone}{regex};
+
+    # some fields are assumed to be visible / required even without the            
+    # presence of org unit settings.  E.g. we obviously want the user to 
+    # enter a name, since a name is required for ultimately creating a user 
+    # account.  We can mimic that by forcing some org unit setting values
+    
+    $shash->{stgu}{first_given_name}{require} = 1
+        unless defined $shash->{stgu}{first_given_name}{require};
+    $shash->{stgu}{second_given_name}{show} = 1
+        unless defined $shash->{stgu}{second_given_name}{show};
+    $shash->{stgu}{family_name}{require} = 1
+        unless defined $shash->{stgu}{family_name}{require};
+    $shash->{stgma}{street1}{require} = 1
+        unless defined $shash->{stgma}{street1}{require};
+    $shash->{stgma}{city}{require} = 1
+        unless defined $shash->{stgma}{city}{require};
+    $shash->{stgma}{post_code}{require} = 1
+        unless defined $shash->{stgma}{post_code}{require};
+
+    $ctx->{register}{settings} = $shash;
+}
+
+# inspects each value and determines, based on org unit settings, 
+# if the value is invalid.  Invalid is defined as not providing 
+# a value when one is required or not matching the configured regex.
+sub inspect_register_value {
+    my ($self, $field_path, $value) = @_;
+    my $ctx = $self->ctx;
+    my ($scls, $field) = split(/\./, $field_path);
+
+    if (!$value) {
+
+        if ($self->{register}{settings}{$scls}{$field}{require}) {
+            $ctx->{register}{invalid}{$scls}{$field}{require} = 1;
+
+            $logger->info("patron register field $field ".
+                "requires a value, but none was entered");
+        }
+        return;
+    }
+
+    my $regex = $self->{register}{settings}{$scls}{$field}{regex};
+    return if !$regex or $value =~ /$regex/; # field is valid
+
+    $logger->info("invalid value was provided for patron ".
+        "register field=$field; pattern=$regex; value=$value");
+
+    $ctx->{register}{invalid}{$scls}{$field}{regex} = 1;
+
+    return;
+}
+
+
+
index 6ebfe47..4ecc655 100644 (file)
     PROCESS "opac/parts/org_selector.tt2";
     WRAPPER "opac/parts/base.tt2";
     INCLUDE "opac/parts/topnav.tt2";
-    ctx.page_title = l("Request Library Card") %]
-    <div id="content-wrapper">
-        <div id="main-content-register">
-            <div class="common-full-pad"></div>
-            <h1>[% l('Request a Library Card')%]</h1>
-            <hr/>
+    ctx.page_title = l("Request Library Card");
 
-            [% IF ctx.register.success %]
-                <h3>[% |l %]
-                Registration successful!  Please see library staff to 
-                complete your registration.
-                [% END %]</h3>
-            [% ELSIF ctx.register.error %]
-                <h3>[% |l %]
-                An registration error has occurred. Please see library staff.
-                [% END %]</h3>
+# some useful variables and MACROs for display, 
+# field validation, and added info display
 
-            [% ELSE;
-                ctx_org = ctx.physical_loc || ctx.search_ou || ctx.aou_tree.id ;
+ctx_org = ctx.physical_loc || ctx.search_ou || ctx.aou_tree.id;
 
-                addr_required = ctx.get_org_setting(ctx_org, 
-                    'ui.patron.registration.require_address');
+# list of the registration fields to (potentially) 
+# display in the order they should be shown
 
-                # true if the selected field is required
-                MACRO require_field(cls, field_name) BLOCK;
-                    sname = "ui.patron.edit.${cls}.${field_name}.require";
-                    IF ctx.get_org_setting(ctx_org, sname); 1; ELSE; 0; END;
-                END;
+register_fields = [
+    {class => 'stgu',  name = 'first_given_name', label => l('First Name')},
+    {class => 'stgu',  name = 'second_given_name', label => l('Middle Name')},
+    {class => 'stgu',  name = 'family_name', label => l('Last Name')},
+    {class => 'stgu',  name = 'family_name', label => l('Last Name')},
+    {class => 'stgma', name = 'street1', label => l('Street Address')},
+    {class => 'stgma', name = 'street2', label => l('Street Address (2)')},
+    {class => 'stgma', name = 'city', label => l('City')},
+    {class => 'stgma', name = 'county', label => l('County')},
+    {class => 'stgma', name = 'state', label => l('State')},
+    {class => 'stgma', name = 'post_code', label => l('Zip Code')},
+    {class => 'stgu',  name = 'dob', label => l('Date of Birth')},
+    {class => 'stgu',  name = 'day_phone', label => l('Phone Number')},
+    {class => 'stgu',  name = 'email', label => l('Email Address')}
+];
+%]
 
-                # true if the selected field should be shown
-                MACRO show_field(cls, field_name) BLOCK;
-                    sname = "ui.patron.edit.${cls}.${field_name}.show";
-                    IF require_field(cls, field_name) || 
-                        ctx.get_org_setting(ctx_org, sname); 1; ELSE; 0; END;
-                END;
+<div id="content-wrapper">
+    <div id="main-content-register">
+        <div class="common-full-pad"></div>
+        <h1>[% l('Request a Library Card')%]</h1>
+        <hr/>
 
-                # indicate whether a field is optional; display example text
-                MACRO show_field_extras(cls, field_name, test_req, test_ex) BLOCK;
-                    required = 0;
-                    example = '';
+        [% IF ctx.register.success %]
+            <h3>[% |l %]
+            Registration successful!  Please see library staff to 
+            complete your registration.
+            [% END %]</h3>
 
-                    sname = "ui.patron.edit.${cls}.${field_name}.example";
-                    IF test_ex;
-                        example = ctx.get_org_setting(ctx_org, sname);
+        [% ELSIF ctx.register.error %]
+            <h3>[% |l %]
+            An registration error has occurred. Please see library staff.
+            [% END %]</h3>
 
-                        # day_phone is a special case, since there is a both
-                        # a day_phone example and generic phone example
-                        IF field_name == 'day_phone' AND !example;
-                            sname = 'ui.patron.edit.phone.example';
-                            example = ctx.get_org_setting(ctx_org, sname);
-                        END;
-                    END;
-                    
-                    SET required = require_field(cls, field_name) IF test_req;
-                    IF example;
-                        IF required;
-                            l('(Example: [_1])', example);
-                        ELSE;
-                            l('(Optional, Example: [_1])', example);
-                        END;
-                    ELSIF !required;
-                        l('(Optional)');
-                    END;
-                END;
-            %]
+        [% ELSE %]
 
-            <form method='POST'>
-                <table>
-                    <!-- TODO <label>s -->
-                    <tr>
-                        <td>[% l('Home Library') %]</td>
-                        <td>[% INCLUDE build_org_selector 
-                                # TODO: limit to orgs where pending-allowed
-                                name='stgu.home_ou' 
-                                value=ctx_org
-                                can_have_users_only=1
-                            %]
-                        </td>
-                    </tr>
-                    <tr>
-                        <td>[% l('First Name') %]</td>
-                        <td><input type='text' name='stgu.first_given_name'/></td>
-                    </tr>
-                    <tr>
-                        <td>[% l('Middle Name') %]</td>
-                        <td><input type='text' name='stgu.second_given_name'/></td>
-                    </tr>
-                    <tr>
-                        <td>[% l('Last Name') %]</td>
-                        <td><input type='text' name='stgu.family_name'/></td>
-                    </tr>
+        <form method='POST'>
+            <table>
+                <!-- TODO <label>s -->
+                <tr>
+                    <td>
+                        <label for='stgu.home_ou'>[% l('Home Library') %]</label>
+                    </td>
+                    <td>[% INCLUDE build_org_selector 
+                            # TODO: limit to orgs where pending-allowed
+                            name='stgu.home_ou' 
+                            value=ctx_org
+                            can_have_users_only=1
+                        %]
+                    </td>
+                </tr>
+[%
+# <=== shifting code left for readability
 
-                    [% IF addr_required # --------------------------------- %]
-                    <tr>
-                        <td>[% l('Street Address') %]</td>
-                        <td><input type='text' name='stgma.street1'/></td>
-                    </tr>
-                    <tr>
-                        <td>[% l('Street Address (2)') %]</td>
-                        <td><input type='text' name='stgma.street2'/></td>
-                        <td>[% l('(Optional)') %]</td>
-                    </tr>
-                    <tr>
-                        <td>[% l('City') %]</td>
-                        <td><input type='text' name='stgma.city'/></td>
-                    </tr>
-                    [% IF show_field('aua', 'state') %]
-                    <tr>
-                        <td>[% l('State') %]</td>
-                        <td><input type='text' name='stgma.state'/></td>
-                        <td>[% show_field_extras('aua', 'state', 1) %]</td>
-                    </tr>
-                    [% END %]
-                    <tr>
-                        <td>[% l('Post/Zip Code') %]</td>
-                        <td><input type='text' name='stgma.post_code'/></td>
-                        <td>[% show_field_extras('aua', 'post_code', 0, 1) %]</td>
-                    </tr>
-                    [% IF show_field('aua', 'county') %]
-                    <tr>
-                        <td>[% l('County') %]</td>
-                        <td><input type='text' name='stgma.county'/></td>
-                        <td>[% show_field_extras('aua', 'county', 1) %]</td>
-                    </tr>
-                    [% END %]
-                    [% END # addr_required -------------------------------- %]
+# render the table row for each of the register fields
+FOR field_def IN register_fields;
+    fclass = field_def.class;
+    fname = field_def.name;
+    field_path = fclass _ "." _ fname;
 
-                    [% IF show_field('au', 'dob') %]
-                    <tr>
-                        <td>[% l('Date of Birth') %]</td>
-                        <td><input type='text' name='stgu.dob'/></td>
-                        <td>[% show_field_extras('au', 'dob', 1) %]</td>
-                    </tr>
-                    [% END %]
-                    [% IF show_field('au', 'day_phone') %]
-                    <tr>
-                        <td>[% l('Phone Number') %]</td>
-                        <td><input type='text' name='stgu.day_phone'/></td>
-                        <td>[% show_field_extras('au', 'day_phone', 1, 1) %]</td>
-                    </tr>
-                    [% END %]
-                    [% IF show_field('au', 'email') %]
-                    <tr>
-                        <td>[% l('Email Address') %]</td>
-                        <td><input type='text' name='stgu.email'/></td>
-                        <td>[% show_field_extras('au', 'email', 1, 1) %]</td>
-                    </tr>
-                    [% END %]
+    show = ctx.register.settings.$fclass.$fname.show;
+    require = ctx.register.settings.$fclass.$fname.require;
+    example = ctx.register.settings.$fclass.$fname.example;
+
+    invalid_require = ctx.register.invalid.$fclass.$fname.require;
+    invalid_regex = ctx.register.invalid.$fclass.$fname.regex;
+
+    NEXT UNLESS require OR show;
+%]
+<tr>
+    <td>
+        <label for='[% field_path %]'>[% field_def.label | html %]</label>
+    </td>
+    <td>
+        <input 
+            type='text' 
+            name='[% field_path %]'
+            value='[% CGI.param(field_path) | html %]'/>
+    </td>
+    <td>
+[%
+    # display wether the field is optional and any example text
+
+    IF example;
+        IF require;
+            l('(Example: [_1])', example);
+        ELSE;
+            l('(Optional, Example: [_1])', example);
+        END;
+    ELSIF !require;
+        l('(Optional)');
+    END;
+%]
+    </td>
+    <td>
+    [% IF invalid_require %]
+        <span class='patron-reg-invalid'>
+            [% l('This field is required') %]
+        </span>
+    [% ELSIF invalid_regex %]
+        <span class='patron-reg-invalid'>
+            [% l('The provided value does not have the correct format') %]
+        </span>
+    [% END %]
+    </td>
+</tr>
+[% END;
+# ====> shifting the code back to the right for context %]
                     <tr>
                         <td>
                             <a href="[% ctx.opac_root %]/home" 
                 </table>
             </form>
             [% END %]
-
             <div class="common-full-pad"></div>        
         </div>
     </div>