WordPress Web Application Development
上QQ阅读APP看书,第一时间看更新

Implementing the frontend registration

Fortunately, we can make use of the existing functionalities to implement registration from the frontend. We can use regular HTTP requests or AJAX-based techniques for implementing this feature. In this book, I am going to focus on a normal process instead of using AJAX. Our first task is creating the registration form on the frontend.

There are various ways to implement such forms on the frontend. Let's look at some of the possibilities as described in the following sections:

  • Shortcode implementation
  • Page template implementation
  • Custom template implementation

Now let's look at the implementation of each of these techniques.

Shortcode implementation

Shortcodes are the quickest way of adding dynamic content to your pages. In this situation, we need to create a page for registration. Therefore, we need to create a shortcode that generates the registration form as shown in the following code:

add_shortcode( "register_form", "display_register_form" );
function display_register_form(){
  $html = "HTML for registration form";
  return $html;
}

Then, you can add the shortcode inside the created page using the following code snippet to display the registration form:

[register_form]
Pros and cons of using shortcodes
  • Easy to implement in any part of your application
  • Hard to manage the template code assigned using the PHP variables
  • Possibility of getting the shortcode deleted from the page by mistake

Page template implementation

Page templates are a widely used technique in modern WordPress themes. We can create a page template to embed the registration form. Consider the following code for a sample page template:

/*
* Template Name :  Registration
*/
HTML code for registration form

Next, we have to copy the template inside the theme folder. Finally, we can create a page and assign the page template to display the registration form. Now let's look at the pros and cons of this technique.

Pros and cons of page templates
  • A page template is more stable than a shortcode.
  • Generally, page templates are associated to the look of the website rather than providing dynamic forms. Full width page, 2 column page, and left sidebar page are some of the common implementations of page templates.
  • A page template is managed separately from logic without using PHP variables.
  • Whether to use this technique depends on the theme and the need to update on theme switching.

Custom template implementation

Experienced web application developers will always look to separate business logic from the view templates. This will be the perfect technique for such people. In this technique, we create our own independent templates by intercepting the WordPress default routing process. Implementation of this technique starts from the next section on routing.

Building a simple router for user modules

Routing is one of the most important aspects in advanced application development. We need to figure out ways of building custom routes for specific functionalities. In this scenario, we are going to create a custom router to handle all the user-related functionalities of our application.

Let's list the requirements for building a router:

  • All the user-related functionalities should go through a custom URL such as http://www.example.com/user
  • Registration should be implemented at http://www.example.com/user/register
  • Login should be implemented at http://www.example.com/user/login
  • Activation should be implemented at http://www.example.com/user/activate
Note

Make sure to set up your permalinks structure to post name for the examples in this book. If you prefer a different permalinks structure, you will have to update the URLs and routing rules accordingly.

As you can see, the user section is common for all the functionalities. The second URL segment changes dynamically based on the functionality. In MVC terms, the user acts as the controller and the next URL segment (register, login, and activate) acts as the action. Now let's see how we can implement a custom router for the given requirements.

Creating the routing rules

There are various ways and action hooks to create custom rewrite rules. We are going to choose the init action to define our custom routes for the user section as shown in the following code:

public function manage_user_routes() {
  add_rewrite_rule( '^user/([^/]+)/?', 'index.php?control_action=$matches[1]', 'top' );
}

Based on the discussed requirements, all the URLs for the user section will follow the /user/custom action pattern. Therefore, we define the regular expression to match all the routes in the user section. Redirection is made to the index.php file with a query variable named control_action. This variable will contain the URI segment after the /user segment. The third parameter of the add_rewrite_rule function will decide whether to check this rewrite rule before the existing rules or after the existing rules. The value of the top parameter will give higher precedence, while the value of the bottom parameter will give lower precedence.

We need to complete two other tasks to get these rewriting rules to take effect, as follows:

  • Add query variables to WordPress: query_vars
  • Flush the rewriting rules
Adding query variables

WordPress doesn't allow you to use any type of variable in the query string. It will check for query variables within the existing list and all other variables will be ignored. Whenever you want to use a new query variable, make sure you add it to the existing list. To do this, we first need to update our constructor with the following filter to customize query variables:

add_filter( 'query_vars', array( $this, 'manage_user_routes_query_vars' ) );

This filter on query_vars will allow us to customize the existing variables list by adding or removing entries from an array. Now consider the implementation for adding a new query variable:

public function manage_user_routes_query_vars( $query_vars ) {
  $query_vars[] = 'control_action';
  return $query_vars;
}

As this is a filter, existing query_vars will be passed as an array. We modify the array by adding a new query variable named control_action and return the list. Now we have the ability to access this variable from the URL.

Flushing the rewriting rules

Once rewrite rules are modified, it's a must to flush the rules in order to prevent the 404 page generation. Flushing existing rules is a time consuming task which impacts the performance of the application, and hence should be avoided in repetitive actions such as init. It's recommended to perform such tasks in plugin activation as we did earlier with user roles and capabilities. So, let's implement the function for flushing rewrite rules on plugin activation:

public function flush_application_rewrite_rules() {
  flush_rewrite_rules();
}

As usual, we need to update the constructor to include the following action to call the flush_application_rewrite_rules function:

register_activation_hook( __FILE__, array( $this, 'flush_application_rewrite_rules' ) );

Now go to the admin panel, deactivate the plugin, and activate the plugin again. Then go to the http://www.example.com/user/login URL and check if it works. Unfortunately, you will still get the 404 error for the request.

You might be wondering what went wrong. Let's go back and think about the process in order to understand the issue. We flushed the rules on plugin activation. So, the new rules should be persisted successfully. However, we define the rules on the init action, which is only executed after the plugin is activated. Therefore, new rules will not be available at the time of flushing.

Consider the updated version of the flush_application_rewrite_rules function for a quick fix to our problem:

public function flush_application_rewrite_rules() {
 $this->manage_user_routes();
  flush_rewrite_rules();
}

We call the manage_user_routes function on plugin activation followed by the call to flush_rewrite_rules. So, the new rules are generated before the flushing is executed. Follow the previous process once again. Now you won't get a 404 page since all the rules have taken effect.

Note

You can get the 404 error due to the modification in rewriting rules and not flushing them properly. In such situations, go to the permalinks section on the settings page and click on the Save Changes button to flush the rewrite rules manually.

Now we are ready with our routing rules for user functionalities. It's important to know the existing routing rules of your application. Even though we can have a look at the routing rules from the database, it's difficult to decode the serialized array as we encountered in previous section.

So, I recommend you use the free plugin named Rewrite Rules Inspector. You can grab a copy at http://wordpress.org/plugins/rewrite-rules-inspector/. Once installed, this plugin allows you to view all the existing routing rules as well as offers a button to flush the rules as shown in the following screenshot:

Controlling access to your functions

We have a custom router that handles the URLs of the user section of our application. Next, we need a controller to handle the requests and generate the template for the user. This works similar to the controllers in the MVC pattern. Even though we have changed the default routing, WordPress will look for an existing template to be sent back to the user. Therefore, we need to intercept this process and create our own templates. WordPress offers an action hook named template_redirect for intercepting requests. So, let's implement our frontend controller based on template_redirect. First, we need to update the constructor with the template_redirect action as shown in the following code:

add_action( 'template_redirect', array( $this, 'front_controller' ) );

Now let's take a look at the implementation of the front_controller function using the following code:

public function front_controller() {
  global $wp_query;
  $control_action = isset ( $wp_query->query_vars['control_action'] ) ? $wp_query->query_vars['control_action'] : ''; ;
  switch ( $control_action ) {
    case 'register':
      do_action( 'wpwa_register_user' );
      break;
  }
}

We will be handling custom routes based on the value of the control_action query variable assigned in the previous section. The value of this variable can be grabbed through the global query_vars array of the $wp_query object. Then, we can use a simple switch statement to handle the controlling based on the action.

The first action to be considered will be register as we are in the registration process. Once the control_action query is matched with registration, we call a handler function using do_action. You might be confused why we use do_action in this scenario. So, let's consider the same implementation in a normal PHP application where we don't have the do_action hook:

switch ( $control_action ) {
  case 'register':
    $this->register_user();
    break;
}

This is a typical scenario where we call a function within the class or in an external class to implement the registration. In the previous code, we are calling a function within the class but with the do_action hook instead of the usual function call.

What are the advantages of using do_action?

WordPress action hooks define specific points in the execution process where we can develop custom functions to modify the existing behavior. In this scenario, we are calling the wpwa_register_user function within the class using do_action.

Unlike websites or blogs, web applications need to be extendable with future requirements. Think of a situation where we only allow Gmail addresses for user registration. This Gmail validation is not implemented in the original code. Therefore, we need to change the existing code to implement the necessary validations. Changing a working component is considered bad practice in application development. Let's see why it's considered as bad practice by looking at the definition of the open/closed principle by Wikipedia.

Note

The open/closed principle states "software entities (classes, modules, functions, and so on) should be open for extension, but closed for modification"; that is, such an entity can allow its behavior to be modified without altering its source code.

This is especially valuable in a production environment where changes to the source code may necessitate code reviews, unit tests, and other such procedures to qualify it for use in a product. The code obeying the principle doesn't change when it is extended and therefore needs no such effort.

WordPress action hooks come to our rescue in this scenario. We can define an action for registration using the add_action function as shown in the following code:

add_action( 'wpwa_register_user', array( $this, 'register_user' ) );

Now you can implement this action multiple times using different functions. In this scenario, register_user will be our primary registration handler. For Gmail validation, we can define another function using the following code:

add_action( 'wpwa_register_user', array( $this, 'validate_gmail_registration') );

Inside this function, we can make the necessary validations as shown in the following code:

public function validate_user(){
  // Code to validate user
  // remove registration function if validation fails
  remove_action( 'wpwa_register_user', array( $this, 'register_user' ) );
}

Now the validate_user function is executed before the primary function. So, we can remove the primary registration function if something goes wrong in validation. With this technique, we have the capability of adding new functionalities as well as changing existing functionalities without affecting the already written code.

We have now implemented a simple controller, which can be quite effective in developing web application functionalities. In the upcoming sections, we are going to continue the process of implementing registration on the frontend with custom templates.

Creating custom templates

Themes provide a default set of templates to cater to the existing behavior of WordPress. Here, we are trying to implement a custom template system to suit web applications. So, our first option is to include the template files directly inside the theme. Personally, I don't like this option due to two reasons:

  • Whenever we switch a theme, we have to move the custom template files to the new theme. So, our templates become theme dependent.
  • In general, all the existing templates are related to CMS functionality. Mixing custom templates with the existing ones becomes hard to manage.

As a solution to the afore mentioned concerns, we are going to implement the custom templates inside the plugin. First, create a folder inside the current plugin folder and name it templates to get things started.

Designing the registration form

We need to design a custom form for frontend registration containing the default header and footer. The entire content area will be used for registration and the default sidebar will be omitted for this screen. Create a PHP file named register.php inside the templates folder with the following code:

<?php get_header(); ?>
<div id="custom_panel">
<?php
if( count( $errors ) > 0) {
  foreach( $errors as $error ){
    echo "<p class='frm_error'>$error</p>";
  }
}
?>
HTML Code for Form
</div>
<?php get_footer(); ?>

We can include the default header and footer using the get_header and get_footer functions, respectively. After the header, we include a display area for the error messages generated in registration. Then, we have the HTML form as shown in the following code:

<form id='registration-form' method='post' action='<?php echo get_site_url() . '/user/register'; ?>'>
  <ul>
    <li>
      <label class='frm_label' for='Username'>Username</label>
      <input class='frm_field' type='text' id='username' name='user' value='' />
    </li>
    <li>
      <label class='frm_label' for='Email'>E-mail</label>
      <input class='frm_field' type='text' id='email' name='email' value='' />
    </li>
    <li>
      <label class='frm_label' for='User Type'>User Type</label>
      <select class='frm_field' name='user_type'>
        <option value='follower'>Follower</option>
        <option value='developer'>Developer</option>
        <option value='member'>Member</option>
      </select>
    </li>
    <li>
      <label class='frm_label' for=''>&nbsp;</label>
      <input type='submit' value='Register' />
    </li>
  </ul>
</form>

As you can see, the form action is set to a custom route named user/register to be handled through the front controller. Also, we have added an extra field named user_type for choosing the preferred user type on registration. We will get an output similar to the following screenshot once we access the /user/register route in the browser:

Once the form is submitted, we have to create the user based on the application requirements.

Planning the registration process

In this application, we have opted to build a complex registration process in order to understand the typical requirements of web applications. So, it's better to plan it upfront before moving on to the implementation. Let's build a list of requirements for registration:

  • The user should be able to register as any of the given user roles
  • An activation code needs to be generated and sent to the user
  • The default notification on successful registration needs to be customized to include the activation link
  • The user should activate his/her account by clicking on the link

So, let's begin the task of registering users by displaying the registration form as given in the following code:

public function register_user() {
  if ( !is_user_logged_in() ) {
    include dirname(__FILE__) . '/templates/register.php';
    exit;
  }
}

Once the user requests /user/register, our controller will call the register_user function using the do_action call. In the initial request, we need to check whether the user is already logged in using the is_user_logged_in function. If not, we can directly include the registration template located inside the templates folder to display the registration form.

WordPress templates can be included using the get_template_part function. However, it doesn't work like a typical template library as we cannot pass data to the template. In this technique, we are including the template directly inside the function. Therefore, we have access to the data inside this function.

Handling registration form submission

Once the user fills the data and clicks on the Register button, we have to execute quite a few tasks in order to register a user in the WordPress database. Let's figure out the main tasks for registering a user:

  • Validating form data
  • Registering the user details
  • Creating and saving the activation code
  • Sending an e-mail notification with an activation link

In the registration form, we specified the action as /user/register, and hence the same register_user function will be used to handle form submission. Validating the user data is one of the main tasks in form submission handling. So, let's take a look at the register_user function with the updated code:

public function register_user() {
  if ( $_POST ) {
    $errors = array();
    $user_login = ( isset ( $_POST['user'] ) ? $_POST['user'] : '' );
    $user_email = ( isset ( $_POST['email'] ) ? $_POST['email'] : '' );
    $user_type  = ( isset ( $_POST['user_type'] ) ? $_POST['user_type'] : '' );

    // Validating user data
    if ( empty( $user_login ) )
      array_push( $errors, 'Please enter a username.' );

        if ( empty( $user_email ) )
          array_push( $errors, 'Please enter e-mail.' );

        if ( empty( $user_type ) )
          array_push( $errors, 'Please enter user type.' );
  }
  // Including the template
}

First, we check whether the request is made as POST. Then, we get the form data from the POST array. Finally, we check the passed values for empty conditions and push the error messages to the $errors variable created at the beginning of this function.

Now we can move into more advanced validations inside the register_user function, as shown in the following code:

$sanitized_user_login = sanitize_user( $user_login );

if ( !empty($user_email) && !is_email( $user_email ) )
  array_push( $errors, 'Please enter valid email.');
elseif ( email_exists( $user_email ) )
  array_push( $errors, 'User with this email already registered.' );

if ( empty( $sanitized_user_login ) || !validate_username( $user_login ) )
  array_push( $errors, 'Invalid username.' );
elseif ( username_exists( $sanitized_user_login ) )
  array_push( $errors, 'Username already exists.' );

First, we use the existing sanitize_user function to remove unsafe characters from the username. Then, we make validations on e-mail to check whether it's valid and whether it exists in the system. Both the email_exists and username_exists functions check for the existence of e-mail and username from the database. Once all the validations are completed, the errors array will be either empty or filled with error messages.

Tip

In this scenario, we choose to go with the most essential validations for a registration form. You can add more advanced validation in your implementations in order to minimize the potential security threats.

In case we get validation errors in the form, we can directly print the contents of the error array on top of the form as it's visible in the registration template. Here is a preview of our registration screen with generated error messages:

Also, it's important to repopulate the form values once errors are generated. We are using the same function for loading the registration form and handling the form submission. Therefore, we can directly access the POST variables inside the template to echo the values as shown in the updated registration form:

<form id='registration-form' method='post' action='<?php echo get_site_url() . '/user/register'; ?>'>
  <ul>
    <li>
      <label class='frm_label' for='Username'>Username</label>
      <input class='frm_field' type='text' id='username' name='user' value='<?php echo isset( $user_login ) ? $user_login : ''; ?>' />
    </li>
    <li>
      <label class='frm_label' for='Email'>E-mail</label>
      <input class='frm_field' type='text' id='email' name='email' value='<?php echo isset( $user_email ) ? $user_email : ''; ?>' />
    </li>
    <li>
      <label class='frm_label' for='User Type'>User Type</label>
      <select class='frm_field' name='user_type'>
        <option <?php echo (isset( $user_type ) && $user_type == 'follower') ? 'selected' : ''; ?> value='follower'>Follower</option>
        <option <?php echo (isset( $user_type ) && $user_type == 'developer') ? 'selected' : ''; ?> value='developer'>Developer</option>
        <option <?php echo (isset( $user_type ) && $user_type == 'member') ? 'selected' : ''; ?> value='member'>Member</option>

      </select>
    </li>
    <li>
      <label class='frm_label' for=''>&nbsp;</label>
      <input type='submit' value='Register' />
    </li>
  </ul>
</form>

Now let's look at the success path, where we don't have any errors, by looking at the remaining sections of the register_user function:

if ( empty( $errors ) ) {
  $user_pass  = wp_generate_password();
  $user_id    = wp_insert_user( array('user_login' => $sanitized_user_login,
  'user_email' => $user_email,
  'role' => $user_type,
  'user_pass' => $user_pass)
  );
  if ( !$user_id ) {
    array_push( $errors, 'Registration failed.' );
  } else {
    $activation_code = $this->random_string();

    update_user_meta( $user_id, 'activation_code', $activation_code );
    update_user_meta( $user_id, 'activation_status', 'inactive' );
    wp_new_user_notification( $user_id, $user_pass, $activation_code );

    $success_message = "Registration completed successfully. Please check your email for activation link.";
  }

  if ( !is_user_logged_in() ) {
    include dirname(__FILE__) . '/templates/login.php';
    exit;
  }
}

We can generate the default password using the wp_generate_password function. Then we can use the wp_insert_user function with respective parameters generated from the form to save the user details into the database.

Tip

The wp_insert_user function will be used to update the current user details or add new users' details to the application. Make sure you are not logged in while executing this function. Otherwise, your admin will suddenly change into another user type after using this function.

If the system fails to save the user details, we create a registration fail message and assign it to the $errors variable as we did earlier. Once the registration is successful, we generate a random string as the activation code. You can use any function here to generate a random string.

Then, we update the user with the activation code and set activation status as inactive for the moment. Finally, we use the wp_new_user_notification function to send an e-mail containing the registration details. By default, this function takes the user ID and password and sends login details. In this scenario, we have a problem as we need to send an activation link with the e-mail.

This is a pluggable function, and hence we can create our own implementation of this function to override the default behavior. Since this is a built-in WordPress function, we cannot declare it inside our plugin class. So, we are going to implement it as a standalone function inside our main plugin file. The complete source code for this function will not be included here as it is quite extensive. I'll explain the modified code from the original function, and you can have a look at the source code for the complete code:

$activate_link = site_url(). "/user/activate/?activation_code=$activate_code";
$message = __('Hi there,') . "\r\n\r\n";
$message .= sprintf(__("Welcome to %s! Please activate your account using the link:"), get_option('blogname')) . "\r\n\r\n";
$message .= sprintf(__('<a href="%s">%s</a>'), $activate_link, $activate_link) . "\r\n";

We create a custom activation link using the third parameter passed to this function. Then, we modify the existing message to include the activation link. That's about all we need to change from the original function. Finally, we set the success message to be passed to the login screen.

Now let's move back to the register_user function. Once the notification is sent, the registration process is completed and the user will be redirected to the login screen. Once the user has the e-mail in the inbox, he/she can use the activation link to activate the account.

Activating system users

Once a user clicks on the activation link, he/she will be redirected to the /user/activate URL of the application. So, we need to modify our controller with a new case for activation as shown in the following code:

case 'activate':
  do_action( 'wpwa_activate_user' );

As usual, the definition of add_action goes in the constructor as shown in the following code:

add_action( 'wpwa_activate_user', array( $this, 'activate_user' ) );

Next, we can have a look at the actual implementation of the activate_user function:

public function activate_user() {

  $activation_code = isset( $_GET['activation_code'] ) ? $_GET['activation_code'] : '';
  $message = '';

  // Get activation record for the user
  $user_query = new WP_User_Query(
    array(
      'meta_key' => 'activation_code',
      'meta_value' => $activation_code
    )
  );

  $users = $user_query->get_results();

  // Check and update activation status
  if ( !empty($users) ) {
    $user_id = $users[0]->ID;
    update_user_meta( $user_id, 'activation_status', 'active' );
    $message = 'Account activated successfully. ';
  } else {
    $message = 'Invalid Activation Code';
  }

  include dirname(__FILE__) . '/templates/info.php';
  exit;
}

We get the activation code from the link and query the database to find a matching entry. If no records are found, we set the message as activation failed. Otherwise, we update the activation status of the matching user to activate the account. Upon activation, the user will be informed with a message using the info.php template, which consists of a very basic template like the following code:

<?php  get_header(); ?>
<div id='info_message'>
<?php echo $message; ?>
</div>
<?php get_footer(); ?>

Once a user visits the activation page on the /user/activation URL, information will be given to the user as shown in the following screenshot:

We have successfully created and activated a new user. The final task of this process is to authenticate and log the user into the system. Let's see how we can create the login functionality.