Skip to content

Instantly share code, notes, and snippets.

@GDmac
Last active December 24, 2015 16:39
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save GDmac/6829953 to your computer and use it in GitHub Desktop.
Save GDmac/6829953 to your computer and use it in GitHub Desktop.
ExpressionEngine Core_member add-on

Core_member

an ExpressioneEngine add-on

Core Member is a Mash-up of the old Freemember plugin and cp-login code. This add-on was developed for a hobby-group website that uses EE-Core and to allow member-only templates and members-only content.

Members can be added and edited in the control panel, by the admin. This plugin provides only some very basic member functionality like: login, logout, forgot password, reset password and change password.

Tested on EE version 2.7.1

installation: make a directory system/expressionengine/third_party/core_member/ and place the files in there.

Basic parameters

form_id=""

Required - this needs to be set to something unique

form_name=""

Sets the name attribute on the generated form

form_class=""

Sets the class attribute on the generated form

return_url=""

The template group path to redirect the user to after success. e.g. "accounts/login-success"

error_handling="inline"

Turns on inline error handling
Note: The add-on uses EE Form validation, when displaying errors inline, errors generally must be shown with the {error:error} tag. Currently one exception for the forgot_password tag, which uses the {error:email} tag.

error_delimiters=""

Specify the code you want to wrap the error messages in, if you are using inline error handling. For example:

error_delimiters="<span class="error">|</span>"

Examples

Login form

Since EE 2.6 supports username or email in "username" input field.

{exp:core_member:login 
  form_id="login_form"
  return_url="account/success"
  error_handling="inline"
  error_delimiters="<div style='color:red;'>|</div>"
  }
    <h3>Login</h3>
    {error:error}
    Username: <input type="text" name="username" value="{username}"><br>
    Password: <input type="password" name="password"><br>
    <input type="submit" value="login">
    <p><a href="{path=account/forgot-password}">forgot password?</a></p>
{/exp:core_member:login}

Logout form

{exp:core_member:logout return_url="home/index"}

Forgot password form

note: errors are displayed in the {error:email} tag note: the reset code is appended to the reset_url and send to the user. The reset_url should point to the template with the reset_password tag.
e.g. "account/reset-password" becomes: example.com/account/reset-password/Fbd534X

{exp:core_member:forgot_password 
  form_id="forgot_password_form"
  return_url="account/mail-is-send"
  error_handling="inline"
  reset_url="account/reset-password"
  error_delimiters="<div style='color:red;'>|</div>"
  }
    <h3>Forgot Password</h3>
    {error:email}
    email: <input type="text" name="email" value="{email}"><br>
    <input type="submit" value="reset password">
  {/exp:core_member:forgot_password}

Reset password form

code="{segment_3}" supply the reset code manually from the segment.
By default, the last_segment is used.

{exp:core_member:reset_password 
  form_id="reset_password_form"
  return_url="account/password-changed"
  error_handling="inline"
  code="{segment_3}"
  error_delimiters="<div style='color:red;'>|</div>"
  }
    <h3>Reset Password</h3>
    {if no_results}
      <p>Sorry, the link you clicked does not appear to be valid, or has expired.</p>
    {/if}

    {error:error}
    New password: <input type="password" name="password"><br>
    Confirm password: <input type="password" name="password_confirm"><br>
    <input type="submit" value="update password">
{/exp:core_member:reset_password}

Change password form

{exp:core_member:change_password 
  form_id="change_password_form"
  return_url="account/password-changed"
  error_handling="inline"
  error_delimiters="<div style='color:red;'>|</div>"
  }
    <h3>Change Password</h3>
    {error:error}
    New password: <input type="password" name="password"><br>
    Confirm password: <input type="password" name="password_confirm"><br>
    Current password: <input type="password" name="current_password"><br>
    <input type="submit" value="update password">
{/exp:core_member:change_password}

Changelog and info

Version 1.0 Beta (pre-refactoring) 2013-10-04

Todo:

  • automatically login user after successful reset_password.
  • add update password form for when security settings change. (on the old hobby-site there was one generic password for hidden content. Core_member is the first step towards more secure content. First have members use a username/password combo, next is to have members change their passwords :-\ )
<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
/*
* Core Member is a Mash-up of the old Freemember plugin and cp-login code,
* to allow basic member functionality without the member module.
* Supports login, logout, forgot password, reset password and change password.
*
*
* FreeMember add-on for ExpressionEngine
* Copyright (c) 2011 Crescendo Multimedia Ltd
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
$plugin_info = array(
'pi_name' => 'core member plugin',
'pi_version' => '1.0',
'pi_author' => 'dev',
'pi_author_url' => '',
'pi_description' => 'Basic member login/logout and password functionality for EE-Core',
);
class Core_member
{
public $return_data;
public function __construct()
{
// no default stuff
}
// ==================================================================
public function login()
{
$form_id = ee()->TMPL->fetch_param('form_id');
if (empty($form_id))
{
return 'Missing form_id parameter.';
}
$tag_vars[0] = array(
'username' => FALSE,
'password' => FALSE,
'auto_login' => FALSE,
'error' => FALSE,
);
foreach ($tag_vars[0] as $field_name => $value)
{
$tag_vars[0]['error:'.$field_name] = FALSE;
}
// handle form POST submission
if (ee()->input->post('login_form') == $form_id)
{
$tag_vars[0]['username'] = ee()->input->post('username');
$tag_vars[0]['auto_login'] = (bool) ee()->input->post('remember_me');
$tag_vars[0]['password'] = FALSE; // don't pre-load password field
$result = $this->_member_login();
if ($result === true)
{
$return_url = ee()->functions->create_url( ee()->input->post('return_url') );
ee()->functions->redirect($return_url);
}
else
{
ee()->lang->loadfile('login');
$errors = array('error'=>'');
foreach ($result as $error) {
$errors['error'] .= lang($error) . ' ';
}
$tag_vars = $this->_display_errors($tag_vars, $errors);
}
}
// auto_login_checked helper tag
$tag_vars[0]['auto_login_checked'] = $tag_vars[0]['auto_login'] ? ' checked="checked" ' : FALSE;
// start our form output
$out = $this->_form_open(array(
'hidden_fields' => array('login_form' => $form_id),
));
// parse tagdata variables
$out .= ee()->TMPL->parse_variables( ee()->TMPL->tagdata, $tag_vars);
// end form output and return
return $out.'</form>';
}
// ==================================================================
protected function _member_login()
{
ee()->load->library('auth');
$errors = array();
/* ---------------------------------
/* 'member_member_login_start' hook.
/* - Take control of member login routine
*/
$edata = ee()->extensions->call('member_member_login_start');
if (ee()->extensions->end_script === TRUE) return;
/*
/* ------------------------------- */
// Run through basic verifications: authenticate, username and
// password both exist, not banned, IP checking is okay, run hook
if ( ! ($verify_result = ee()->auth->verify()))
{
// In the event it's a string, send it to return to login
return ee()->auth->errors;
}
list($username, $password, $incoming) = $verify_result;
$member_id = $incoming->member('member_id');
// Set cookies and start session
// Kill existing flash cookie
ee()->functions->set_cookie('flash');
if (isset($_POST['remember_me']))
{
$incoming->remember_me();
}
if (is_numeric(ee()->input->post('site_id')))
{
ee()->functions->set_cookie('cp_last_site_id', ee()->input->post('site_id'), 0);
}
$incoming->start_session(FALSE); // not a CP session
return true;
}
// ==================================================================
public function logout()
{
if (ee()->session->userdata('member_id') !== 0)
{
ee()->db->where('ip_address', ee()->input->ip_address());
ee()->db->where('member_id', ee()->session->userdata('member_id'));
ee()->db->delete('online_users');
ee()->session->destroy();
ee()->functions->set_cookie('read_topics');
/* -------------------------------------------
/* 'member_member_logout' hook.
/* - Perform additional actions after logout
/* - Added EE 1.6.1
*/
$edata = ee()->extensions->call('member_member_logout');
if (ee()->extensions->end_script === TRUE) return;
/*
/* -------------------------------------------*/
}
if (($return = ee()->TMPL->fetch_param('return_url')) !== FALSE)
{
ee()->functions->redirect(ee()->functions->create_url($return));
}
else
{
// return to most recent page
ee()->functions->redirect(ee()->functions->form_backtrack(1));
}
}
// ==================================================================
public function change_password()
{
$form_id = ee()->TMPL->fetch_param('form_id');
if (empty($form_id))
{
return 'Missing form_id parameter.';
}
$member_id = ee()->session->userdata('member_id');
if ($member_id == 0)
{
show_error('change_password - You are not logged in');
}
$tag_vars[0] = array(
'password' => FALSE,
'password_confirm' => FALSE,
'current_password' => FALSE,
'resetcode' => FALSE,
'error' => FALSE,
);
foreach ($tag_vars[0] as $field_name => $value)
{
$tag_vars[0]['error:'.$field_name] = FALSE;
}
// process form data
if (ee()->input->post('login_form') == $form_id)
{
ee()->load->library('form_validation');
ee()->lang->loadfile('myaccount');
// Put username into $_POST for valid_password validation
$_POST['username'] = ee()->session->userdata('username');
ee()->form_validation->set_rules('current_password', 'lang:existing_password', 'required');
ee()->form_validation->set_rules('password', 'lang:new_password', 'valid_password|required');
ee()->form_validation->set_rules('password_confirm', 'lang:new_password_confirm', 'matches[password]|required');
if(ee()->form_validation->run() !== FALSE)
{
// Validate current password
ee()->load->library('auth');
$auth = ee()->auth->authenticate_id(
$member_id,
ee()->input->post('current_password')
);
if ($auth !== FALSE)
{
// Update the member row with the new password.
ee()->auth->update_password(
$member_id,
ee()->input->post('password')
);
$return_url = ee()->functions->create_url( ee()->input->post('return_url') );
ee()->functions->redirect($return_url);
}
else
{
$errors = array('error'=> lang('invalid_password'));
$tag_vars = $this->_display_errors($tag_vars, $errors);
}
}
else
{
$errors = array('error'=> ee()->form_validation->error_string());
$tag_vars = $this->_display_errors($tag_vars, $errors);
}
}
// start our form output
$out = $this->_form_open(array(
'hidden_fields' => array('login_form' => $form_id),
));
// parse tagdata variables
$out .= ee()->TMPL->parse_variables( ee()->TMPL->tagdata, $tag_vars);
// end form output and return
return $out.'</form>';
}
// ==================================================================
public function forgot_password()
{
$tag_vars = array(array(
'email' => FALSE,
'error:email' => FALSE,
));
if ( ! $reset_url = ee()->TMPL->fetch_param('reset_url'))
{
show_error('Missing reset_url parameter');
}
// process form
if (ee()->input->post('forgot_password'))
{
$tag_vars[0]['email'] = ee()->input->post('email', TRUE);
// generate reset code and URL
$reset_code = ee()->functions->random('alnum', 10);
$reset_url = ee()->functions->create_url($reset_url.'/'.$reset_code);
// valide email address and send reset instructions
$errors = $this->_member_forgot_password($reset_code, $reset_url);
if (empty($errors))
{
$return_url = ee()->functions->create_url(ee()->input->post('return_url'));
ee()->functions->redirect($return_url);
}
else
{
$tag_vars = $this->_display_errors($tag_vars, $errors);
}
}
// start our form output
$out = $this->_form_open(array(
'hidden_fields' => array('forgot_password' => 1),
));
// parse tagdata variables
$out .= ee()->TMPL->parse_variables(ee()->TMPL->tagdata, $tag_vars);
// end form output and return
return $out.'</form>';
}
// ==================================================================
private function _member_forgot_password($reset_code, $reset_url)
{
ee()->lang->loadfile('myaccount');
// Error trapping
if ( ! $address = ee()->input->post('email'))
{
return array('email' => lang('invalid_email_address'));
}
ee()->load->helper('email');
if ( ! valid_email($address))
{
return array('email' => lang('invalid_email_address'));
}
$address = strip_tags($address);
// Fetch user data
ee()->db->select('member_id, username, screen_name');
ee()->db->where('email', $address);
$query = ee()->db->get('members');
// Member not found, but show a generic action success message so spammers don't know if an email exists or not
if ($query->num_rows() == 0)
{
return array('email' => lang('forgotten_email_sent'));
}
$member_id = $query->row('member_id');
$name = ($query->row('screen_name') == '') ? $query->row('username') : $query->row('screen_name');
// Clean out any old reset codes.
$a_day_ago = time() - (60*60*24);
ee()->db->where('date <', $a_day_ago);
ee()->db->or_where('member_id', $member_id);
ee()->db->delete('reset_password');
// Create a new DB record with the temporary reset code
$data = array('member_id' => $member_id, 'resetcode' => $reset_code, 'date' => time());
ee()->db->query(ee()->db->insert_string('exp_reset_password', $data));
// Buid the email message
$swap = array(
'name' => $name,
'reset_url' => $reset_url,
'site_name' => stripslashes(ee()->config->item('site_name')),
'site_url' => ee()->config->item('site_url')
);
$template = ee()->functions->fetch_email_template('forgot_password_instructions');
$message_title = ee()->functions->var_swap($template['title'], $swap);
$message = ee()->functions->var_swap($template['data'], $swap);
// Instantiate the email class
ee()->load->library('email');
ee()->email->wordwrap = true;
ee()->email->from(ee()->config->item('webmaster_email'), ee()->config->item('webmaster_name'));
ee()->email->to($address);
ee()->email->subject($message_title);
ee()->email->message($message);
$vars['message_success'] = '';
$vars['message_error'] = '';
if ( ! ee()->email->send())
{
return array('email' => lang('error_sending_email'));
}
return FALSE;
}
// ==================================================================
public function reset_password()
{
if (($reset_code = ee()->TMPL->fetch_param('code')) === FALSE)
{
$reset_code = ee()->uri->segment(ee()->uri->total_segments());
}
if (ee()->session->userdata('member_id') !== 0)
{
show_error('Youre already logged in');
}
if (ee()->session->userdata('is_banned') === TRUE)
{
return show_error(lang('unauthorized_request'));
}
// Validate their reset code. Make sure it matches a valid member.
$a_day_ago = time() - (60*60*24);
$member_id_query = ee()->db->select('member_id')
->where('resetcode', $reset_code)
->where('date >', $a_day_ago)
->get('reset_password');
// If we don't find a valid token, then they shouldn't be here.
if ($member_id_query->num_rows() === 0)
{
return ee()->TMPL->no_results();
}
$member_id = $member_id_query->row('member_id');
$tag_vars = array(array(
'password' => FALSE,
'password_confirm' => FALSE,
'error' => FALSE,
));
foreach ($tag_vars[0] as $field_name => $value)
{
$tag_vars[0]['error:'.$field_name] = FALSE;
}
// process form
if (ee()->input->post('reset_password'))
{
ee()->load->library('form_validation');
ee()->lang->loadfile('myaccount');
// Put username into $_POST for valid_password validation
$_POST['username'] = ee()->db->select('username')
->where('member_id', $member_id)
->get('members')
->row('username');
ee()->form_validation->set_rules('password', 'lang:new_password', 'valid_password|required');
ee()->form_validation->set_rules('password_confirm', 'lang:new_password_confirm', 'matches[password]|required');
if(ee()->form_validation->run() !== FALSE)
{
// Update the member row with the new password.
ee()->load->library('auth');
ee()->auth->update_password(
$member_id,
ee()->input->post('password')
);
// Invalidate the old token. While we're at it, may as well wipe out expired
// tokens too, just to keep them from building up.
ee()->db->where('date <', $a_day_ago)
->or_where('member_id', $member_id)
->delete('reset_password');
// Success, return to success template
$return_url = ee()->functions->create_url(ee()->input->post('return_url'));
ee()->functions->redirect($return_url);
}
else
{
$errors = array('error'=> ee()->form_validation->error_string());
$tag_vars = $this->_display_errors($tag_vars, $errors);
}
}
// start our form output
$out = $this->_form_open(array(
'hidden_fields' => array('reset_password' => 1),
));
// parse tagdata variables
$out .= ee()->TMPL->parse_variables(ee()->TMPL->tagdata, $tag_vars);
// end form output and return
return $out.'</form>';
}
// ==================================================================
/**
* All our forms have common attributes
*/
protected function _form_open($data)
{
$data['action'] = ee()->functions->create_url(ee()->uri->uri_string);
$data['id'] = ee()->TMPL->fetch_param('form_id');
$data['name'] = ee()->TMPL->fetch_param('form_name');
$data['class'] = ee()->TMPL->fetch_param('form_class');
if (empty($data['hidden_fields']))
{
$data['hidden_fields'] = array();
}
$data['hidden_fields']['return_url'] = ee()->TMPL->fetch_param('return_url');
return ee()->functions->form_declaration($data);
}
// ==================================================================
private function _display_errors($tag_vars, $errors)
{
if ( ! is_array($errors))
{
// fatal error, display error message
return ee()->output->show_user_error(FALSE, array($errors));
}
if (ee()->TMPL->fetch_param('error_handling') != 'inline')
{
// display standard error form
ee()->output->show_user_error(FALSE, $errors);
}
// inline errors
$delim = explode('|', ee()->TMPL->fetch_param('error_delimiters'));
if (count($delim) != 2)
{
$delim = array('', '');
}
foreach ($errors as $field_name => $message)
{
$tag_vars[0]['error:'.$field_name] = $delim[0].$message.$delim[1];
}
return $tag_vars;
}
}
/* End of file */
@shadydragon
Copy link

Great plugin!!! However I needed to add a pi. to the beginning of the filename to get it to work. Not sure if that was the right thing to do or not... but it worked.

thanks for the headsup, i changed the filename

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment