Front End Login WordPress Plugin – Model View Controller (MVC) – Tutorial – Part Two
Oops .. I forgot my Password
The scenario we are addressing is a user who forgot their password. Remember that as part of the Sign In page, we have a link to handle such.
This link directs to the “lost-password” page, so we want to make sure we create one. On this page we will place a shortcode to generate a simple form like this:
When the username entered corresponds to a real user on the site, we send a message, to the email address on record for that user, with a link which will allow her to reset her password.
For security reasons, this link can only be used once.
View
The View class produces the form.
private function add_shortcodes() { add_shortcode('ferl_login_form', array( $this,'login_form' ) ); add_shortcode('ferl_password_form', array( $this,'password_form' ) ); add_shortcode('ferl_request_password_reset_form', array( $this,'request_password_reset_form' ) ); }
The shortcode “ferl_request_password_reset_form” is added in line 4.
public function request_password_reset_form() { $this->load_css = true; $output = ''; if(!is_user_logged_in()) { $output = $this->request_password_reset_form_view(); } else { // could show some logged in user info here $current_user = wp_get_current_user(); $output = $this->logout_form_view($current_user); } return $output; }
If a logged-in user accidentally arrives here, show the logout form, else show the form defined below.
private function request_password_reset_form_view () { ob_start(); // show any error messages after form submission $this->show_error_messages(); $this->show_info_messages(); ?> <form id="ferl_request_password_reset_form" class="ferl_form" action="" method="POST"> <fieldset> <p> <label for="ferl_username"><?php _e('Username'); ?></label> <input name="ferl_username" id="ferl_username" class="required" type="text" value="<?= $this->model->get_username() ?>" /> </p> <p> <input type="hidden" name="ferl_action" value="request_reset" /> <input type="hidden" name="ferl_request_password_reset_nonce" value="<?php echo wp_create_nonce('dbs-request-password-reset-nonce'); ?>"/> <input type="submit" value="<?php _e('Request password reset'); ?>" name="submit" /> </p> </fieldset> </form> <?php return ob_get_clean(); }
This is a standard HTML form, a variation on what was done before.
Controller
The Controller looks for the “ferl_action”, and calls the appropriate method in the Model.
case "request_reset": add_action( 'init', array( $this->model, 'lost_password' ) ); break;
Model
The Model serves two functions initially. To validate the username, and send the email.
function lost_password() { global $wpdb; if (isset( $this->post_array["submit"] ) && wp_verify_nonce($this->post_array['dbs_request_password_reset_nonce'], 'dbs-request-password-reset-nonce')) { $username = $this->post_array["ferl_username"]; if(trim($username) == '') { // empty username $this->wp_error->add('username_empty', __('Please enter your Username')); } else { $user_login = strtolower($username); } if( ! username_exists($user_login) ) { // Username not registered at all $this->wp_error->add('username_not_registered', __('No such user is registered')); } else { // get the user object $the_user = get_user_by( 'login', $user_login ); } $errors = $this->wp_error->get_error_messages(); // only send the message if no errors if(empty($errors)) { // set the user activation key $key = wp_generate_password(20, false); $wpdb->update($wpdb->users, array('user_activation_key' => $key), array('user_login' => $user_login)); /* " .. sending email .. to " . $the_user->user_email */ $to = $the_user->user_email; $body = ".."; $body .= "\nHello ".$the_user->display_name." !"; $body .= "\nSomeone requested that the password be reset for your \"".get_bloginfo('name')."\" website account."; $body .= "\nIf this was a mistake, just ignore this message and nothing will happen."; $body .= "\nTo reset your password, visit the following address:"; $body .= "\n ".site_url()."/reset-password?action=reset-password&username=".$user_login."&key=".$key." "; $body .= "\n"; wp_mail($to, "[".get_bloginfo( 'name' )."] Password reset request for ".$username,$body); wp_redirect(site_url().'/email-message-sent'); exit; } } }
Validating the username is straightforward, see lines 8-20.
Note that we use the global wpdb (database) object. One of the fields in the WordPress users table is the user_activation_key field. We will use that field to store a key which is generated using the wp_generate_password function (see lines 27-28).
The rest of the function then uses the wp_mail function to send an email, and to redirect to an “Email Has Been Sent” page. The clickable link is set in line 36. It contains “action”, “username” and the “key” as GET parameters.
An example email is shown here (click on the image):
The Password Reset Link
When this link is clicked from the email, or copy / pasted into the browser address bar, a GET request is generated. The request is for the “reset-password” page which we will create. On that page, there is a shortcode “ferl_reset_password_form”.
The reaction to the GET request, is in two parts. First the controller will intercept it, and validate the request. If something is wrong, we stop all processing and present an error message. If it passes, WordPress will continue loading the reset-password page and the View class will handle the shortcode.
private function init() { /* pass request parameters to the model */ $this->model->set_request_params($this->post_array, $this->get_array); /* validate the reset-password request */ add_action( 'template_redirect', array($this->model, 'validate_reset_request') ); (...) }
Lines 5-6 are added to the Controller’s init() method. We hook this to the “template_redirect” action hook which is triggered just before a page is rendered.
public function validate_reset_request() { // only keep going if it is the "reset-password" page if ( !is_page( 'reset-password' ) ) return; // need some WP globals global $current_user; global $wpdb; $dead_message = "<h1>Invalid request. You can use a password reset link only once.</h1>"; $dead_message .= '<h2><a href="'.home_url().'">'.get_bloginfo( 'name' ).'</a></h2>'; // logged in ? I don't think so !! if (is_user_logged_in()) { // kill the key .. this page should not have been requested $user_id = $current_user->ID; $wpdb->update( $wpdb->users, array('user_activation_key' => ""), array('ID' => $user_id) ); wp_redirect(home_url()); exit; } // first see if we have a "GET" or .. it could be a "POST" from the form on this page if (! isset($this->get_array['action']) && ! isset($this->post_array['dbs_action'])) { wp_redirect(home_url()); exit; } // handle the "GET" request if ( isset($this->get_array['action']) && ! isset($this->post_array['ferl_action']) ) { // make sure all fields are there if (sizeof($this->get_array) != 3) die ($dead_message); if (! isset($this->get_array['username']) || ! isset($this->get_array['key'])) die ($dead_message); $user_login = $this->get_array['username']; $reset_key = $this->get_array['key']; if(! $reset_key) die ($dead_message); if ($this->get_array['action'] != 'reset-password') die ($dead_message); // get user information $user_data = $wpdb->get_row($wpdb->prepare("SELECT ID, user_login, user_activation_key FROM $wpdb->users WHERE user_login = %s", $user_login)); if(! $user_data) die ($dead_message); if ($reset_key != $user_data->user_activation_key) die ($dead_message); // ok .. reset the key .. only one time usage $user_id = $user_data->ID; $wpdb->update( $wpdb->users, array('user_activation_key' => ""), array('ID' => $user_id) ); } // we are good to go return; }
Lines 10-11 define the “death message”. It is used in lines 27, 28, 31, 37. The “key” is killed / reset in lines 16-17 and 40-41. Only when everything checks out, does the method return, and the “reset-password” page continues to load.
Here is an example of the “death message” :
private function add_shortcodes() { add_shortcode('ferl_login_form', array( $this,'login_form' ) ); add_shortcode('ferl_password_form', array( $this,'password_form' ) ); add_shortcode('ferl_request_password_reset_form', array( $this,'request_password_reset_form' ) ); add_shortcode('ferl_reset_password_form', array( $this,'reset_password_form' ) ); } (...) public function reset_password_form() { $this->load_css = true; $output = ''; if( !is_user_logged_in() ) { // we do have a valid username from the get array $output = $this->password_form_view($this->model->get_username()); } else { // could show some logged in user info here $current_user = wp_get_current_user(); $output = $this->logout_form_view($current_user); } return $output; }
Note how the ferl_reset_password_form shortcode is added in line 5. The reset_password_form method is similar to the password_form method introduced on page 1 of this article. Whereas the latter could only render a form when a user is logged in, this one will only render when nobody is logged in.
Also note that both methods call the same form to be rendered, i.e. password_form_view (see line 13).
And that is it. User registration is next.