In the first tutorial in this series on customizing the WordPress login experience, we created a plugin that lets you replace your WordPress login screen with a custom page. Today, we will go a step further and replace the new user registration flow in the same way.
The reasons for customizing the login page we outlined in Part 1 (matching the registration page with your site's theme to hide WordPress and make the user experience seamless) apply also to new user registration, but there are also some more specific reasons for why you might want to create your own registration page:
- First, at registration, you might want to change the fields you ask from your new members, adding extra fields or maybe dropping some. As an example, in this tutorial, we will remove the user name field and instead use the new user's email address as login.
- Second, there is registration spam. When I first opened registrations to everyone on my site, it didn't take more than a few hours for the first spam registration to appear, the next one following right after it. Adding a reCAPTCHA field on a custom registration page is a good way to handle this while keeping the user experience consistent.
- Finally, you might want to do some custom actions at registration, for example adding the new user to your mailing list. After having built your own registration flow, this will be easier than ever.
So, equipped with this set of reasons, let's get started.
In this tutorial, you will learn how to replace the WordPress registration screen with a custom page and to implement the registration with your own code, without violating WordPress's design principles.
The functionality will be built on top of the plugin we built in Part 1 of the tutorial series, so if you haven't read it yet, it's a good idea to start by checking out that tutorial. You can write the code yourself while following the tutorial, or download the example code from the tutorial's Github project.
Add a Custom New User Registration Page
The default WordPress new user registration page at wp-login.php?action=register
looks like this:
Not bad, but unless you're running WordPress.org, it's most likely not consistent with your blog or web site design.
Note: If you can't access the registration page on your WordPress site, it's because by default WordPress doesn't allow new users to register themselves. To change this, go to the General Settings page in your admin Dashboard and check the checkbox before "Anyone can register". Then save the settings and return to the registration page.
In the first part in the series, we created a custom page for displaying the login form and a shortcode that was used to place the login form on that page. Now, we'll do the same for registration: first, we'll create a shortcode for displaying the registration form, and then a page on which this shortcode will be placed.
It's also possible to place the shortcode on a different page or have the login and registration forms on a single page.
Step 1: Create the Shortcode
Building on top of the plugin created in Part 1 of the series, let's start by adding a shortcode for the registration page.
At the end of the plugin class's (Personalize_Login_Plugin
) constructor, add the following shortcode definition:
add_shortcode( 'custom-register-form', array( $this, 'render_register_form' ) );
Then, create the function responsible for rendering the new user registration form:
/** * A shortcode for rendering the new user registration form. * * @param array $attributes Shortcode attributes. * @param string $content The text content for shortcode. Not used. * * @return string The shortcode output */ public function render_register_form( $attributes, $content = null ) { // Parse shortcode attributes $default_attributes = array( 'show_title' => false ); $attributes = shortcode_atts( $default_attributes, $attributes ); if ( is_user_logged_in() ) { return __( 'You are already signed in.', 'personalize-login' ); } elseif ( ! get_option( 'users_can_register' ) ) { return __( 'Registering new users is currently not allowed.', 'personalize-login' ); } else { return $this->get_template_html( 'register_form', $attributes ); } }
If you have already read Part 1 in the series, you'll notice a lot of similarities between this function and the login form rendering function from that tutorial.
First, on lines 10-12, you'll notice that the shortcode takes an attribute show_title
, used for defining whether a title should be rendered by the shortcode or not.
Second, the registration form is not shown to users who are already logged in (lines 14-15). The message returned instead of the form is quite simple, so depending on your needs, you may want to replace this piece of code with something more elaborate — for example a link back to the dashboard.
A new element is the check for the WordPress option users_can_register
on line 16. This option is controlled by the WordPress General Settings field Anyone can register mentioned above. To make sure we respect the settings the user defines in WordPress, we should not display the registration form if the setting is set to false
. Instead, as you see on line 17, the function will return a notification about registration being closed.
The actual rendering of the registration form is done on line 19 using a PHP template, register_form.php
, located in the templates
directory we created in the previous tutorial. See Part 1 for a longer explanation on how this works as well as the code for the function used, get_template_html
.
Now, let's add the registration form template.
Step 2: Create the Registration Form
In the templates
directory, add a new PHP file and name it register_form.php
. Then, continue by adding a registration form with the fields you want your new user to fill.
Here's the version I created for this tutorial; a rather standard registration form with fields for the user's email, first name, and last name. The form doesn't contain a separate field for a user name as the email address will double as one.
Just like the default WordPress new user registration, our version will generate the password and email it to the new user. This serves as a simple email check (the user won't be able to log in without entering a valid email address) and enforces some level of password security.
Of course, this approach has its own security risk in the form of emailing passwords, so it's a good idea to ask (or even require) the user to change the password once logged in.
<div id="register-form" class="widecolumn"> <?php if ( $attributes['show_title'] ) : ?> <h3><?php _e( 'Register', 'personalize-login' ); ?></h3> <?php endif; ?> <form id="signupform" action="<?php echo wp_registration_url(); ?>" method="post"> <p class="form-row"> <label for="email"><?php _e( 'Email', 'personalize-login' ); ?> <strong>*</strong></label> <input type="text" name="email" id="email"> </p> <p class="form-row"> <label for="first_name"><?php _e( 'First name', 'personalize-login' ); ?></label> <input type="text" name="first_name" id="first-name"> </p> <p class="form-row"> <label for="last_name"><?php _e( 'Last name', 'personalize-login' ); ?></label> <input type="text" name="last_name" id="last-name"> </p> <p class="form-row"> <?php _e( 'Note: Your password will be generated automatically and sent to your email address.', 'personalize-login' ); ?> </p> <p class="signup-submit"> <input type="submit" name="submit" class="register-button" value="<?php _e( 'Register', 'personalize-login' ); ?>"/> </p> </form> </div>
On lines 2-4, the template renders a title for the form if the show_title
attribute is set.
Then, on line 6, take a look at the action
parameter: The form is submitted to the default WordPress registration URL which we can retrieve using the function wp_registration_url
. We'll talk more about to this soon, but I'll mention already now that this doesn't mean that we'll let WordPress handle the registration...
The rest of the template is a rather standard HTML form with fields for email, first name, and last name.
Before we get to see the shortcode in action, we still need to create the registration page and place the shortcode on it.
Step 3: Create the New User Registration Page
To show the registration form, you'll need to add the following code on a WordPress page:
[custom-register-form]
This could be any page, but for now, let's create a new page for the registration form.
In the previous part, we created a function, plugin_activated
, for creating the plugin's pages at plugin activation. Inside the function, we added an array for storing all the pages the plugin should create.
Now, let's add the information about the new page to the array. After the addition, the array definition should look like this, with the new page (with the slug member-register
) defined last:
// Information needed for creating the plugin's pages $page_definitions = array( 'member-login' => array( 'title' => __( 'Sign In', 'personalize-login' ), 'content' => '[custom-login-form]' ), 'member-account' => array( 'title' => __( 'Your Account', 'personalize-login' ), 'content' => '[account-info]' ), 'member-register' => array( 'title' => __( 'Register', 'personalize-login' ), 'content' => '[custom-register-form]' ), );
The activation hook is only run when a plugin is activated, so go ahead and deactivate and then activate the plugin on the Plugins page. Now, when you navigate to the URL http://<YOUR SITE>/member-register
, you should see something like this (using the current WordPress default theme, Twenty Fifteen):
Step 4: Redirect the User to Our New Registration Page
Before we move to executing the registration action, let's make sure the user always gets redirected to this new registration page instead of the default new user page at wp-login.php?action=register
.
To do this, we'll use the action hook login_form_{action}
which, as you might remember from Part 1, is fired before every action in wp-login.php
. As you see from the link above, the action in this case is register
, and so, we'll hook our function to login_form_register
.
In the plugin's constructor, add the following line:
add_action( 'login_form_register', array( $this, 'redirect_to_custom_register' ) );
Then, create the callback function:
/** * Redirects the user to the custom registration page instead * of wp-login.php?action=register. */ public function redirect_to_custom_register() { if ( 'GET' == $_SERVER['REQUEST_METHOD'] ) { if ( is_user_logged_in() ) { $this->redirect_logged_in_user(); } else { wp_redirect( home_url( 'member-register' ) ); } exit; } }
As we hooked the function to the login_form_register
action, we know that the user is either trying to access the new user registration form or to post it.
That's why the first thing we do in this function (on line 6) is to check the request method that was used to access the page: the redirect is only done on GET
requests as the POST
request will be reserved for executing the registration action. More about this soon.
Then, the function continues by checking if the user is already logged in (line 7).
Logged in users are redirected to the account page (or the admin dashboard if they are admins) using the function redirect_logged_in_user
which we created in Part 1. Visitors are redirected to our new page, member-register
.
Now, with the page and the form in place, let's move into what happens when the user submits the form.
Register a New User
When the user submits the new user registration form, its contents are posted to wp-login.php?action=register
, the same URL we used when redirecting the user to the registration page above.
To do the customizations we mentioned earlier (most importantly, using the email address as the user name) we will need to replace this functionality with our own code. To do this, we will first create a function for registering a new user programmatically and then call this function in an action handler.
Step 1: Create the User
Let's start by creating a function that can be used to register a new user using the data collected from the form above: email address, first name, and last name. The email address will be the only unique identifier, and the rest is just something nice to have.
In the plugin class, add the following private function:
/** * Validates and then completes the new user signup process if all went well. * * @param string $email The new user's email address * @param string $first_name The new user's first name * @param string $last_name The new user's last name * * @return int|WP_Error The id of the user that was created, or error if failed. */ private function register_user( $email, $first_name, $last_name ) { $errors = new WP_Error(); // Email address is used as both username and email. It is also the only // parameter we need to validate if ( ! is_email( $email ) ) { $errors->add( 'email', $this->get_error_message( 'email' ) ); return $errors; } if ( username_exists( $email ) || email_exists( $email ) ) { $errors->add( 'email_exists', $this->get_error_message( 'email_exists') ); return $errors; } // Generate the password so that the subscriber will have to check email... $password = wp_generate_password( 12, false ); $user_data = array( 'user_login' => $email, 'user_email' => $email, 'user_pass' => $password, 'first_name' => $first_name, 'last_name' => $last_name, 'nickname' => $first_name, ); $user_id = wp_insert_user( $user_data ); wp_new_user_notification( $user_id, $password ); return $user_id; }
As I mentioned above, the email address is the only unique identifier, and also the only required parameter. That's why we begin the function by validating its value. First on line 15, we validate the email address and then, on line 20 we verify the email address isn't already in use. If either of the validations fail, a Wp_Error
object is returned. We'll return to displaying these errors soon.
If all goes well and no errors are found, the function continues by generating a password on line 26.
On lines 28-37, you'll find the core of this function, the creation of the new user. The user is added using the WordPress function wp_insert_user
. (line 37). As its only parameter, the function takes an associative array with information about the user being created. As you'll see on lines 28-35, we use $email
for both the user name (user_login
) and email (email
) fields. For a full list of the fields that can be included in the attribute array, take a look at the WordPress codex.
After creating the user, on line 38, the function calls wp_new_user_notification
to send the generated password to the new user and to notify the site admin of the new user.
Step 2: Call the Registration Code When a User Submits the Form
Now that we have written the code for registering the user, we can call it when the registration form is submitted.
Earlier, when we added the redirect to our custom registration page using the action login_form_register
, I mentioned we'd use the same action also for handling the POST
requests.
As is often the case in programming, this isn't the only way we could handle the new user registration, but it does have a rather important benefit: this way, we'll make sure no one can accidentally access the default registration code in WordPress.
For clarity, let's add a separate function and tie it to the same action (technically, there is no reason why you couldn't just as well add this code to the redirect function we created above).
In the plugin class's constructor, add a new action definition:
add_action( 'login_form_register', array( $this, 'do_register_user' ) );
Then, create the function:
/** * Handles the registration of a new user. * * Used through the action hook "login_form_register" activated on wp-login.php * when accessed through the registration action. */ public function do_register_user() { if ( 'POST' == $_SERVER['REQUEST_METHOD'] ) { $redirect_url = home_url( 'member-register' ); if ( ! get_option( 'users_can_register' ) ) { // Registration closed, display error $redirect_url = add_query_arg( 'register-errors', 'closed', $redirect_url ); } else { $email = $_POST['email']; $first_name = sanitize_text_field( $_POST['first_name'] ); $last_name = sanitize_text_field( $_POST['last_name'] ); $result = $this->register_user( $email, $first_name, $last_name ); if ( is_wp_error( $result ) ) { // Parse errors into a string and append as parameter to redirect $errors = join( ',', $result->get_error_codes() ); $redirect_url = add_query_arg( 'register-errors', $errors, $redirect_url ); } else { // Success, redirect to login page. $redirect_url = home_url( 'member-login' ); $redirect_url = add_query_arg( 'registered', $email, $redirect_url ); } } wp_redirect( $redirect_url ); exit; } }
The function begins with a request method check on line 8: the function is linked to the WordPress action login_form_register
, the same that we used for redirecting the user, and the request method is what differentiates the two uses from each other.
On line 11, we verify that registering new users is allowed. If not, the user is redirected back to the registration page with an error code (closed
) as query parameter (register-errors
).
On the other hand, if registration is open, the function collects the required parameters (lines 15-17) from the request data and uses them to call the function we created above (line 19) to create the new user.
After the register_user
call, the function redirects the user to the correct place depending on whether the new user registration was successful or not:
- After a successful redirect, the user is redirected to the login page, with the parameter
$registered
indicating that a new user was just created. - In case of an error, the redirect points back to the registration form with the error codes from the new user registration function are combined into a comma separated list and included in the request (lines 23-24).
Step 3: Display Error and Success Messages
As we saw above, the registration function redirects the user to the login page if registration was successful and back to the registration page if there were errors, with the status passed as request parameter.
Now, let's add some code to display those messages to the user, starting with the error messages on the new user registration page.
In render_register_form
, add the following piece of code right before rendering the template:
// Retrieve possible errors from request parameters $attributes['errors'] = array(); if ( isset( $_REQUEST['register-errors'] ) ) { $error_codes = explode( ',', $_REQUEST['register-errors'] ); foreach ( $error_codes as $error_code ) { $attributes['errors'] []= $this->get_error_message( $error_code ); } }
This snippet first checks if errors were passed in the request parameter register-errors
(line 2). If yes, it goes through all of them, looking up corresponding error messages using the get_error_message
function we created in Part 1 of the tutorial series.
The error messages are collected into an array in the $attributes
array for printing in the template.
To be able to show the correct error messages, we'll also need to add the new error messages to the function get_error_message
. In the switch structure, add these (or your own) error messages:
// Registration errors case 'email': return __( 'The email address you entered is not valid.', 'personalize-login' ); case 'email_exists': return __( 'An account exists with this email address.', 'personalize-login' ); case 'closed': return __( 'Registering new users is currently not allowed.', 'personalize-login' );
To display the errors on the registration page, add the following code in the register_form.php
template in between the title and the form:
<?php if ( count( $attributes['errors'] ) > 0 ) : ?> <?php foreach ( $attributes['errors'] as $error ) : ?> <p> <?php echo $error; ?> </p> <?php endforeach; ?> <?php endif; ?>
Next, let's add the success message.
When a new user has been successfully registered, the user is redirected to the Sign In page, with a parameter, registered={email address}
appended to the URL.
In the function render_login_form
, add the following two lines to check if the parameter is present:
// Check if the user just registered $attributes['registered'] = isset( $_REQUEST['registered'] );
Then, in the template login_form.php
, add a message that will be shown if the registered
flag is set:
<?php if ( $attributes['registered'] ) : ?> <p class="login-info"> <?php printf( __( 'You have successfully registered to <strong>%s</strong>. We have emailed your password to the email address you entered.', 'personalize-login' ), get_bloginfo( 'name' ) ); ?> </p> <?php endif; ?>
That's it. You have now built a complete new user registration flow with parameter validation and error reporting.
Go ahead an create a new user account to test the flow.
If you are testing the plugin on a local server, unless you have configured the emailer settings, you might not receive the email containing the password — that's normal.
Fight Registration Spam with Google's ReCaptcha
While the registration form is now complete, we'll keep customizing it a bit more by adding a reCAPTCHA check (it's the "I'm not a robot" checkbox you'll find on many of the bigger sites online) to prevent registration spammers from creating accounts on your web site.
Step 1: Get Your Captcha Key
First, visit the reCAPTCHA site. Click on the "Get reCAPTCHA" button on the top right corner to access the reCAPTCHA admin page.
If you are not signed in to your Google Account, you'll be asked to sign in. If you don't have one yet, you'll need to create one to use this tool.
On the reCAPTCHA account page, you'll find the following form. Use it to insert information about your web site.
Once you have submitted the form by clicking on Register, you will see a page with instructions on enabling reCAPTCHA on your site, as well as two keys used for using the API: Site Key and Secret Key.
To store the keys in our WordPress plugin, we'll need to create two settings fields. To keep things simple, we'll add them to the General Settings and not create a custom settings page yet.
To add the settings fields, first hook a new action. In the plugin's constructor, add the following new line:
add_filter( 'admin_init' , array( $this, 'register_settings_fields' ) );
Then create the function for defining the settings fields and the two callback functions for rendering the settings fields:
/** * Registers the settings fields needed by the plugin. */ public function register_settings_fields() { // Create settings fields for the two keys used by reCAPTCHA register_setting( 'general', 'personalize-login-recaptcha-site-key' ); register_setting( 'general', 'personalize-login-recaptcha-secret-key' ); add_settings_field( 'personalize-login-recaptcha-site-key', '<label for="personalize-login-recaptcha-site-key">' . __( 'reCAPTCHA site key' , 'personalize-login' ) . '</label>', array( $this, 'render_recaptcha_site_key_field' ), 'general' ); add_settings_field( 'personalize-login-recaptcha-secret-key', '<label for="personalize-login-recaptcha-secret-key">' . __( 'reCAPTCHA secret key' , 'personalize-login' ) . '</label>', array( $this, 'render_recaptcha_secret_key_field' ), 'general' ); } public function render_recaptcha_site_key_field() { $value = get_option( 'personalize-login-recaptcha-site-key', '' ); echo '<input type="text" id="personalize-login-recaptcha-site-key" name="personalize-login-recaptcha-site-key" value="' . esc_attr( $value ) . '" />'; } public function render_recaptcha_secret_key_field() { $value = get_option( 'personalize-login-recaptcha-secret-key', '' ); echo '<input type="text" id="personalize-login-recaptcha-secret-key" name="personalize-login-recaptcha-secret-key" value="' . esc_attr( $value ) . '" />'; }
As this tutorial is not about WordPress Settings API, we won't go through the settings field declaration. If you are not familiar with how adding settings in WordPress works, check out this complete tutorial series by Tom McFarlin.
Now, go to the General Settings page and copy the reCAPTCHA keys to the two fields you just created and save the settings.
Step 2: Display the CAPTCHA
With the preparations done, we can now add the reCAPTCHA field and use it to verify that our users are human.
In render_registration_form
, first retrieve the reCAPTCHA site key from WordPress settings and store it in the $attributes
array to make it available in the template:
// Retrieve recaptcha key $attributes['recaptcha_site_key'] = get_option( 'personalize-login-recaptcha-site-key', null );
Then, using the parameter, add a placeholder for the reCAPTCHA field in the registration form, right above the submit button:
<?php if ( $attributes['recaptcha_site_key'] ) : ?> <div class="recaptcha-container"> <div class="g-recaptcha" data-sitekey="<?php echo $attributes['recaptcha_site_key']; ?>"></div> </div> <?php endif; ?>
The actual reCAPTCHA field will be placed inside the div
above using JavaScript. So, in order to show the field, we'll still need to include the JavaScript in the page footer.
WordPress has a specific action just for this use. Add the following line in the plugin's constructor:
add_action( 'wp_print_footer_scripts', array( $this, 'add_captcha_js_to_footer' ) );
Then, create the function, add_captcha_js_to_footer
that prints out the JavaScript include tag that loads the reCAPTCHA API from Google:
/** * An action function used to include the reCAPTCHA JavaScript file * at the end of the page. */ public function add_captcha_js_to_footer() { echo "<script src='https://www.google.com/recaptcha/api.js'></script>"; }
Now, when you go to the registration page, you should see the reCAPTCHA field in place:
Step 3: Check the CAPTCHA
If the JavaScript based "not a robot" check is successful, the reCAPTCHA widget adds a new parameter to the form, g-recaptcha-response
. Then, in the server side validations in the do_register_user
function, we can use the parameter to check that the user has passed the test.
First, let's create a function for checking the reCAPTCHA parameter:
/** * Checks that the reCAPTCHA parameter sent with the registration * request is valid. * * @return bool True if the CAPTCHA is OK, otherwise false. */ private function verify_recaptcha() { // This field is set by the recaptcha widget if check is successful if ( isset ( $_POST['g-recaptcha-response'] ) ) { $captcha_response = $_POST['g-recaptcha-response']; } else { return false; } // Verify the captcha response from Google $response = wp_remote_post( 'https://www.google.com/recaptcha/api/siteverify', array( 'body' => array( 'secret' => get_option( 'personalize-login-recaptcha-secret-key' ), 'response' => $captcha_response ) ) ); $success = false; if ( $response && is_array( $response ) ) { $decoded_response = json_decode( $response['body'] ); $success = $decoded_response->success; } return $success; }
On lines 9-13, the function retrieves the reCAPTCHA response. If it isn't found, the user hasn't even tried to complete the test before submitting the form.
Then, on lines 16-24, the function uses wp_remote_post
to send a POST
request to the reCAPTCHA server to verify the received response. The request takes two parameters, embedded in an array with the id body
: the reCAPTCHA secret key and the response we are validating, read from the request parameter g-recaptcha-response
.
The server returns its response as a JSON encoded object. Once the response is decoded, it can be used to check the response as we do on line 29.
Next, let's use the function in handle_register_request
to make the reCAPTCHA check a part of the parameter validation. For clarity, here's the entire if...else
construct with the new code appended in the middle (lines 4-6):
if ( ! get_option( 'users_can_register' ) ) { // Registration closed, display error $redirect_url = add_query_arg( 'register-errors', 'closed', $redirect_url ); } elseif ( ! $this->verify_recaptcha() ) { // Recaptcha check failed, display error $redirect_url = add_query_arg( 'register-errors', 'captcha', $redirect_url ); } else { $email = $_POST['email']; $first_name = sanitize_text_field( $_POST['first_name'] ); $last_name = sanitize_text_field( $_POST['last_name'] ); $result = $this->register_user( $email, $first_name, $last_name ); if ( is_wp_error( $result ) ) { // Parse errors into a string and append as parameter to redirect $errors = join( ',', $result->get_error_codes() ); $redirect_url = add_query_arg( 'register-errors', $errors, $redirect_url ); } else { // Success, redirect to login page. $redirect_url = home_url( 'member-login' ); $redirect_url = add_query_arg( 'registered', $email, $redirect_url ); } }
If the reCAPTCHA check fails, the user is sent back to the registration page with the error code captcha
included in the URL.
To display the error to the user, add a matching error message in the get_error_message
function. Something like this, maybe:
case 'captcha': return __( 'The Google reCAPTCHA check failed. Are you a robot?', 'personalize-login' );
And that's it. You have added a reCAPTCHA check to your new user registration flow.
What's Next?
You have now successfully customized your WordPress based site's new user registration flow. If you have more ideas on things you'd like to differently at this step, this is a good time to extend the changes we did in this tutorial. Add new fields, do more validation, add the user to a mailing list. You name it.
In the next, and final, tutorial in this series, we will complete the customization by replacing the "Forgot your password" functionality with our own custom pages.
Until then, happy coding.
Comments