butlerblog
1/14/2015 - 3:39 PM

ajax-reg.js

<?php
/**
* Create a HTML form for front-end user registration.
* WordPress Version 4.1
* @package CW_View_Form
* @author David Egan <david@carawebs.com>
*
*
*/

class CW_View_Form {

  public $proto_session_id;

  /**
   * Create a user registration form.
   * @param string $user_role      User role for this instance: 'student' or 'supervisor'
   * @param int    $coordinator_ID User ID of originating coordinator
   *
   *
   */

  /**
   * Set up properties
   * @param string $user_role   The user role for the new user
   * @param int $coordinator_ID The user ID of the originating coordinator
   */
  function __construct ( $user_role, $coordinator_ID ) {

    $this->user_role      = $user_role;
    $this->coordinator_ID = $coordinator_ID;
    //$this->sessionnonce = $sessionnonce;
    $this->proto_session_id = md5( 'whoohoo'. microtime() ); // do we need an original nonce??
    // TM: Ideally, I also use whatever nonce WordPress generates. Easiest for code maintainability.

  }

  /**
   * Build a front-end form for user creation
   * @return string Form html
   *
   */
  public function render() {

    $user_role = ucfirst($this->user_role);
    $_SESSION['cw_session_id'] = $this->proto_session_id;

    // TM: Consider moving this to a 'partial' file that is basically an HTML file with nested PHP and then run an include_once
    $form = '
      <div class="cw-registration-form">
        <form id="user-reg-form" class="registration-form" role="form" action="' . $_SERVER['REQUEST_URI'] . '" method="post">
          <input id="cw_user_role" type="hidden" name="cw_user_role" value="' . $this->user_role . '" />
          <input type="hidden" size="45" name="sessionnonce" value="' . $this->proto_session_id . '" />
          <div class="form-group">
            <label for="cw_firstname" class="sr-only">' . $user_role . '\'s First Name</label>
            <input autocomplete="off" type="text" name="cw_firstname" id="cw_firstname" value="" placeholder="' . $user_role . '\'s First Name" class="form-control" />
          </div>
          <div class="form-group">
            <label for="cw_lastname" class="sr-only">' . $user_role . '\'s Last Name</label>
            <input autocomplete="off" type="text" name="cw_lastname" id="cw_lastname" value="" placeholder="' . $user_role . '\'s Last Name" class="form-control" />
          </div>
          <div class="form-group">
            <label for="cw_email" class="sr-only">' . $user_role . '\'s Email</label>
            <input autocomplete="off" type="email" name="cw_email" id="cw_email" value="" placeholder="' . $user_role . '\'s Email" class="form-control" />
          </div>' .
          wp_nonce_field('cw_new_user','cw_new_user_nonce', true, true ) .
          '<input type="submit" name="cw_register" class="btn btn-primary" id="btn-new-user" value="Register" />
        </form>
        <div class="indicator">Please wait...</div>
        <div class="result-message well topspace"></div>
      </div>';

    return $form;

  }

}
<?php
/**
 * Class to validate & sanitise form input.
 * 
 * WordPress Version 4.1
 * @package CW_Reg_Validator
 * @author David Egan <david@carawebs.com>
 *
 *
 */
class CW_Reg_Validator {

  public $form_errors = array();
  public $user_values = array();

  /**
   * Construct method
   * @param string $firstname Un-sanitized form input
   * @param string $lastname  Un-sanitized form input
   * @param string $email     Un-sanitized form input
   *
   */
  function __construct ( $firstname, $lastname, $email ) {

    $this->firstname  = $firstname;
    $this->lastname   = $lastname;
    $this->email      = $email;

  }

  /**
   * Check validity of class proprties
   * @return boolean Returns true if there are no errors, otherwise pushes errors into $form_errors array.
   *
   */
  public function is_valid() {

    // allow letters (both cases), hyphen, space and (\').
    $preg_str_check="#[^][(\\\\') A-Za-z-]#";

    if ( empty( $this->email ) ) {
      array_push( $this->form_errors, 'You must enter an email address.' );
    }

    if ( empty( $this->firstname ) ) {
      array_push( $this->form_errors, 'You must enter a first name.' );
    }

    if( preg_match( $preg_str_check, $this->firstname ) ) {
      array_push( $this->form_errors, 'The first name you entered doesn\'t look right - please try again with alphabet characters only.' );
    }

    if ( empty( $this->lastname ) ) {
      array_push( $this->form_errors, 'You must enter a last name.' );
    }

    if( preg_match( $preg_str_check, $this->lastname ) ) {
      array_push( $this->form_errors, 'The last name you entered doesn\'t look right - please try again with alphabet characters only.' );
    }

    if ( ( $this->email) && ! is_email( $this->email ) ) {
      array_push( $this->form_errors, 'The email address you submitted doesn\'t look right - please try again, with the format name@domain.com');
    }

    if ( email_exists( $this->email ) ) {
      array_push( $this->form_errors, 'This email is already in use.');
    }

    if ( empty( $this->form_errors ) ) { // There are no errors, so return the string 'true'
      // TM: Inconsistently returning true and 'true' in your code. Pick one variable type and be consistent with it throughout.
      return 'true';

    }

  }

  /**
   * Return form submission/validation errors
   * @return array Form errors.
   *
   */
  public function get_errors() {

    return $this->form_errors;

  }

  /**
   * Sanitize input data.
   * @param  string $data Form input data: firstname, lastname, email.
   * @return string       Cleaned up data - sanitised.
   */
  public function sanitise_inputs( $data ) {

    $data = trim( $data );
    $data = stripslashes( $data );
    $data = strip_tags( $data );
    $data = htmlspecialchars( $data );
    /* TM: May simplify this to the following:
     * return trim( stripslashes ( strip_tags( htmlspecialchars( $data ) ) ) );
     */

    return $data;

  }

  /**
   * Build an array of data relating to the new user & return it.
   * @return array contains user firstname, lastname, email address.
   *
   */
  public function get_values() {

    $this->firstname  = $this->sanitise_inputs($this->firstname);
    $this->lastname   = $this->sanitise_inputs($this->lastname);
    $this->email      = sanitize_email($this->email);

    $this->user_values = array(
      'first_name'  => $this->firstname,
      'last_name'   => $this->lastname,
      'email'       => $this->email
    );

    return $this->user_values;

  }

}
<?php
/**
 * CW_Create_User - Create a new user from form input.
 *
 * All input values MUST have been sanitized previously. Form render, validation & sanitization
 * is handled by separate classes, to separate the logic.
 *
 * This class is called by a facade type function. For Student Studio, the following classes are related:
 *
 * - CW_Reg_Validator, which validates & sanitizes input, /classes/class-cw-reg-validator.php
 * - CW_View_Form, which builds the form, /classes/class-cw-view-form.php
 *
 * The facade function used for supervisor & student registration is carawebs_userform_process_facade(),
 * located in /lib/forms.php
 *
 * WordPress Version 4.1
 * @package CW_Create_User
 * @author David Egan <david@carawebs.com>
 *
 * @TODO return error messages if:
 * - email doesn't get sent
 * - wp_insert_user fails
 *
 */

class CW_Create_User {

  public $new_user_info = array();
  public $insert_user_error;

  public $firstname;
  public $lastname;
  public $email;
  public $user_role;
  public $coordinator_ID;
  public $username;
  public $success_message;

  /**
   * Construct method to set properties.
   * @param string $user_role This will be either 'student', 'supervisor'.
   * @param int $coordinator_ID The originating coordinator's user ID
   * @param array $user_values
   */
  function __construct ( $user_role, $coordinator_ID, $user_values ) {

    $this->user_role  = $user_role;
    $this->coordinator_ID = $coordinator_ID;
    $this->firstname = $user_values['first_name'];
    $this->lastname = $user_values['last_name'];
    $this->email = $user_values['email'];
    $this->username = $user_values['email'];

  }

  /**
   * Uses wp_insert_user to programmatically create a new user. New user will have an auto-generated password.
   * The user login is set to the email address. Uses add_user_meta to set the user role.
   * @param  string $user_role User role: 'student' or 'supervisor'
   * @return [type]            [description]
   */
  public function register( $user_role ) {

    $password = wp_generate_password();
    $username = $this->username;

    $userdata = array(
      'user_login'      => $this->email,
      'user_pass'       => $password,
      'user_email'      => $this->email,
      'first_name'      => $this->firstname,
      'last_name'       => $this->lastname,
      'user_nicename'   => $this->firstname,
    );

    $user_id = wp_insert_user( $userdata ) ;

      if ( is_wp_error( $user_id ) ) {

        // If there is an error inserting a new user...
        $insert_user_error = $user_id->get_error_message();

      } else {

        // Set the user role
        // -----------------
        $user = new WP_User( $user_id );
        $user->set_role( $this->user_role );

        // Set the Student's Coordinator
        // ------------------------------
        add_user_meta( $user_id, 'coordinator_id', $this->coordinator_ID ); // $coordinator_id comes from the originating page

        $coordinator_info = get_userdata( $this->coordinator_ID );
        $coordinator_name = $coordinator_info->user_firstname . ' ' . $coordinator_info->user_lastname;

        // Build an array that can be returned, for a success message
        // ----------------------------------------------------------
        $this->new_user_info = array(
          'first_name'    => $user->user_firstname,
          'last_name'     => $user->user_lastname,
          'email'         => $user->user_email,
          'login'         => $user->user_login,
          'display_name'  => $user->user_nicename,
        );

        return true;

        // TM: This shouldn't ever fire since a return statement is directly before it.
        $this->email_new_user( $password );

      }

  }

  /**
   * Send email to the new user with instructions & password.
   * Email content is generated from the content of a specified 'email' CPT -
   * this will allow site admins to control the message.
   *
   * @TODO make message specific to 'student' and 'supervisor' user roles.
   * @TODO set up success and error messages.
   * @param  string $password Auto generated password.
   * @return [type]           [description]
   *
   */
  public function email_new_user( $password ){

    // Email the user
    // --------------

    // Allow html in email
    add_filter( 'wp_mail_content_type', function($content_type){

      return 'text/html';

    });

    /**
    * TODO allow Admin to select the attachment in back end of site
    */
    //$attachments = array( WP_CONTENT_DIR . '/uploads/2015/01/RDBMStoMongoDBMigration.pdf' );
    $headers = 'From: info@studentstudio.co.uk' . "\r\n";
    $welcome = 'Welcome, ' . $this->firstname . '!';

    /**
    * TODO set this up so that the right 'email' CPT is referenced.
    * It might be better to set up to reference by post_title, and instruct client to
    * give appropriate name to the relevant email CPT.
    */
    $post_id = 62; // Refers to the Student Intro email custom post - rough & ready solution.
    $post_object = get_post( $post_id );
    $mailmessage = $post_object->post_content; // Build a mail message from the content of a post.
    $mailmessage .= '<hr><p>Your password is: ' . $password . '</p>';

    wp_mail( $this->email, $welcome, $mailmessage, $headers/*, $attachments*/ );

  }

}
<?php
/**
* Facade function to control form validation, data processing and error/success reporting.
*
* @param int $coordinator_ID  User Id of originating coordinator
* @param string $user_role    The user role for the newly created user
* 
* The function has empty values for $coordinator_ID and $user_role by default, because:
*   - If the form is submitted by PHP, the values will be passed in from the originating call and will be available
*   for form processing.
*   - If the form is submitted by Ajax, the function will be called by add_action('wp_ajax_..., function_name)
*   and the relevant values will be submitted by the jQuery .post() function in ajax-reg.js.
*
*
*/

/**
 * TM: This function is a bit long such that it should probably be refactored so that each conditional
 *     is calls it's own method. So rather than having so many if and if/else branches in the code it would
 *     still have the actual conditionals but it would call "private functions") like this:
 *
 *     if ( conditional ) {
 *       _acme_function_do_work( $params );
 *     }
 *
 *     This will make the code easier to trace and even read a bit more line English. A lot of this is like
 *     the work that you're doing with classes where to ultimate goal is to get the units of work down to the
 *     most atomic level possible.
 */
 }
function carawebs_userform_process_facade ( $coordinator_ID = '', $user_role = '' ) {

  if ( ! empty( $_POST ) ) {

    if ( isset ($_POST['cw_new_user_nonce'] ) ) { 
      $nonce = $_POST['cw_new_user_nonce']; 
    }

    // check to see if the submitted nonce matches the generated nonce
    // TM: Always use braces, even in one line conditionals/loops/etc. It's too dangerous not to :).
    if ( ! wp_verify_nonce( $nonce, 'cw_new_user' ) ) {
        die ( 'Sorry, but the security check has failed and we can\'t process this form');
    }


    // Define variables and set to empty values, then add $_POST data if set.
    // -------------------------------------------------------------------------
    $firstname = $email = $lastname = $user_role = $coordinator_ID = '';

      $firstname      = ( isset( $_POST['cw_firstname'] ) ) ? $_POST['cw_firstname'] : '';
      $lastname       = ( isset( $_POST['cw_lastname'] ) ) ? $_POST['cw_lastname'] : '';
      $email          = ( isset( $_POST['cw_email'] ) ) ? $_POST['cw_email'] : '';
      $user_role      = ( isset( $_POST['cw_user_role'] ) ) ? $_POST['cw_user_role'] : '';
      $coordinator_ID = ( isset( $_POST['cw_coord_id'] ) ) ? $_POST['cw_coord_id'] : ''; // if Ajax submission, set by ajax-reg.js
      $cw_ajax        = isset( $_POST['cw_ajax'] ) ? $_POST['cw_ajax'] : 'false'; // Set cw_ajax in the jQuery function - this allows a differential response for ajax submissions
      // TM: the above 'true/false' values would probably be better as boolean literals than string literals

      // Validate form data
      // -------------------
      $form = new CW_Reg_Validator( $firstname, $lastname, $email );
      $form->is_valid();
      $errors = $form->get_errors(); // Get the validation errors, if they exist.

      // Form data is valid
      // -----------------------------------------------------------------------
      if ( 'true' == $form->is_valid() ) { // returns 'true' if no validation errors found

        // Create new user
        // ---------------
        $new_user = new CW_Create_User( $user_role, $coordinator_ID, $form->get_values() );

        // register a user, using values that have been checked for errors & sanitised
        // If the user is created, $user_created will be set to true.
        $user_created = $new_user->register( $user_role );

        // User Creation Success
        // ---------------------
        if ( true == $user_created ){

          /**
           * Build a success message - common for PHP & Ajax
           *
           * 	$new_user->new_user_info = array(
           *   	'first_name'    => $user->user_firstname,
           *    'last_name'     => $user->user_lastname,
           *    'email'         => $user->user_email,
           *    'login'         => $user->user_login,
           *    'display_name'  => $user->user_nicename,
           *    );
           *
           */
          // Build a success message
          // -----------------------
          $new_user_details = $new_user->new_user_info;

          /* TM: If you wrap the string in double-quotes ("), you won't have to escape (\') single quotes
           *     and you can also drop the variables directly in the string and they'll be processed.
           *     For example:
           *
           *     $user_created = "You've just created a new $user_role.";
           */
          $user_created = 'You\'ve just created a new ' . $user_role . '. Their details are:
          <ul class="user-list">
            <li>Name: ' . $new_user_details['first_name'] . ' ' . $new_user_details['last_name'] . '</li>
            <li>Email: ' . $new_user_details['email'] . '</li>
            <li>Login Username: ' . $new_user_details['login'] . '</li>
            <li>Display name: ' . $new_user_details['display_name'] . '</li>
          </ul>';

          // Success Return for Ajax
          // ---------------

          if ( 'true' == $cw_ajax ) {

            $response = array(); // this will be a JSON array
            $response['status']   = 'success';
            $response['message']  = $user_created;

            wp_send_json( $response ); // sends $response as a JSON object

          } else {
          // Success Return for PHP
          // ---------------

            echo $new_user->success_message;

          }

        } else {
        // User Creation Failure
        // ---------------------

          $user_creation_failure = 'User creation failed.';
          // TODO: Proper error reporting here.

          // Failure return for Ajax
          // ---------------
          if ('true' == $cw_ajax) {

            $response = array(); // this will be a JSON array
            $response['status']       = 'error';
            //$response['message']      = json_encode($errors, JSON_FORCE_OBJECT);
            $response['message']      = $user_creation_failure;//json_encode($user_creation_failure);

          } else {
          // Failure return for PHP
          // ---------------

            echo $user_creation_failure;

          }

        }

      }

      // Form Data is invalid: Response
      // -----------------------------------------------------------------------

      if ( ! empty( $errors ) ) {

        // Build the error message
        // ------------------------
        $error_message =
          '<p>Sorry - we can\'t process your form because:</p>
          
          <ul class="error-list">';
          foreach ( $errors as $error ) {
            $error_message .= '<li>' . $error . '</li>';
          }
          $error_message .= '</ul>';

        if ( 'true' == $cw_ajax ) {
          // Error message for Ajax
          // -----------------------
          $response = array(); // this will be a JSON array
          $response['status']       = 'error';
          $response['message']      = $error_message;
          wp_send_json( $response ); // sends $response as a JSON object

        } else {
          // Error message for PHP
          // ---------------------

          echo $error_message;

        }

      }

  }

  $view = new CW_View_Form( $user_role, $coordinator_ID );

  echo $view->render();

}
/**
* This file is enqueued by means of wp_enqueue_script() - variables are passed
* in from PHP by means of wp_localize_script()
*
*/

/* TM: We use an anonymous function to invoke the JavaScript. Also refactored for proper
 * WordPress coding standards.
 */
(function( $ ) {
  'use strict';
  
  $(function() {
    
$('#btn-new-user').click( function(event) {

    // Prevent default action
    // -----------------------
    event.preventDefault();

    // Show 'Please wait' loader to user
    // ---------------------------------
    $( '.indicator' ).fadeIn( 400 );
    $( '.result-message' ).hide();

    // Collect data from inputs, and slot into variables
    // -------------------------------------------------
    var reg_nonce = $( '#cw_new_user_nonce' ).val();
    var reg_user_role = $( '#cw_user_role' ).val();
    var reg_email  = $( '#cw_email' ).val();
    var reg_firstname  = $( '#cw_firstname' ).val();
    var reg_lastname  = $( '#cw_lastname' ).val();
    var reg_cw_ajax = 'true'; // TM: I think this would work better as a boolean literal rather than as a string.

    // AJAX URL: where to send data (set in the localize_script function)
    // --------------------------------------------------
    var ajax_url = carawebs_reg_vars.carawebs_ajax_url;
    var coordinatorID = carawebs_reg_vars.carawebs_coordinator_id;

    // Data to send
    // -----------------
    data = {
      action: 'register_new_user',
      cw_coord_id: coordinatorID,
      cw_user_role: reg_user_role,
      cw_new_user_nonce: reg_nonce,
      cw_email: reg_email,
      cw_firstname: reg_firstname,
      cw_lastname: reg_lastname,
      cw_ajax: reg_cw_ajax // let the processing function know it's Ajax - callback function can be tailored.
    };

    // Do AJAX request
    $.post( ajax_url, data, function( respons e) {

      if ( response ) {

        alert( 'Response from server is: ' + response.status );

        // Hide 'Please wait' indicator
        $( '.indicator' ).hide();

        var status = response.status;
          
        // TM: Rewritten to use Yoda Conditions as per the WPCS
        if( 'error' === response.status ) {

              $( '.result-message' ).empty(); // Clear old user list/error messages

              var output = response.message // decided to build message in PHP and send as a string

              $( '.result-message' ).append( output );
              $( '.result-message' ).show( 800 ); // Show results div

            } else {

              // Clear the form, so that another user can be easily registered
              $( '#user-reg-form' )[0].reset();
              $( '.result-message' ).empty(); // Clear old user list/error messages

                // Build a success message
                var studentReturn =
                  "<p>" + response.message + "</p>";

                $( '.result-message' ).html( studentReturn );
                $( '.result-message' ).show( 800 ); // Show results div
            }

      }

    });

  });

  //var coordinatorID = carawebs_reg_vars.carawebs_coordinator_id;
  //$('#coord-id').html('<h3>' + coordinatorID + '</h3>');

  // Handle another form on the same page

  $( '#btn-different-id' ).click( function(event) {

    // Process the form data

  });
    
  });
  
})( jQuery );