Skip to content

Instantly share code, notes, and snippets.

@Qurus
Created May 10, 2023 23:39
Show Gist options
  • Save Qurus/701b493cae467fa0c2f677e40707832d to your computer and use it in GitHub Desktop.
Save Qurus/701b493cae467fa0c2f677e40707832d to your computer and use it in GitHub Desktop.
loginrgister module
<?php namespace ProcessWire;
/**
* ProcessWire Login and Register Process
*
* ProcessWire 3.x, Copyright 2017 by Ryan Cramer
* https://processwire.com
*
* CONFIGURATION PROPERTIES
* ------------------------
* @property bool $renderStyles Render <link> tags for stylesheets in output?
* @property bool $renderScripts Render <script> tags for JS files in output?
* @property array $registerRoles Name of roles to add to users created from register form.
* @property array $registerFields Field names to show in registration form.
* @property array $profileFields Field names to show in profile form.
* @property array $features
*
* HOOKABLE METHODS
* ----------------
* @method string execute() All rendered output passes through this method.
*
* @method InputfieldForm buildLoginForm() Build the login form Inputfields.
* @method string renderLoginForm(InputfieldForm $form) Render the markup for the login form.
* @method array buildLoginFormLinks() Returns an array of <a> tags for links that appear below the login form.
* @method bool processLoginForm(InputfieldForm $form) Process the login form and login (return true) or fail (return false).
*
* @method InputfieldForm buildRegisterForm() Build the user registration form Inputfields.
* @method string renderRegisterForm(InputfieldForm $form) Render the markup for the user registration form.
* @method bool processRegisterForm(InputfieldForm $form) Process the registration form.
*
* @method InputfieldForm buildConfirmationForm() Build the confirmation form.
* @method string renderConfirmationForm(InputfieldForm $form) Render the output for the confirmation form.
* @method bool processConfirmation() Process confirmation, and create user + log-in on success. WireException on fail.
*
* @method InputfieldForm buildProfileForm() Build the profile edit form Inputfields.
* @method string renderProfileForm(InputfieldForm $form) Render the markup for the profile edit form.
* @method bool processProfileForm(InputfieldForm $form) Process the profile edit form.
*
* @method array buildLoggedInLinks() Return an array of <a> tags for links that appear to a user when logged in.
*
* @method void loginSuccess(User $user) Hook called on login success.
* @method void loginFail($username) Hook called on login failure.
*
* @method void createUserReady(User $user) Hook called before a new user is created.
* @method void createdUser(User $user) Hook called right after a new user is created.
*
* @method string renderList(array $items, $class = 'LoginRegisterLinks') Render a list, used primarily for links.
* @method string renderError($error) Render an error message.
* @method string renderMessage($message) Render an informational message.
*
*/
class LoginRegister extends WireData implements Module, ConfigurableModule {
public static function getModuleInfo() {
return array(
'title' => 'Login/Register',
'summary' => 'Login or register for an account in ProcessWire',
'version' => 2,
'icon' => 'user-plus',
);
}
const defaultRoleName = 'login-register';
/**
* Construct and establish default settings
*
*/
public function __construct() {
$this->set('renderStyles', true);
$this->set('renderScripts', true);
$this->set('registerRoles', array(self::defaultRoleName));
$this->set('registerFields', array('name', 'email', 'pass'));
$this->set('profileFields', array('name', 'pass'));
$this->set('features', array('login', 'register', 'profile'));
parent::__construct();
}
/**
* Initialize default markup and classes for forms
*
*/
public function init() {
if($this->wire('page')->template == 'admin') return;
$markup = array(
'list' => "<div {attrs}>{out}</div>",
'item' => "<div {attrs}>{out}</div>",
'item_label' => "<label class='InputfieldHeader' for='{for}'>{out}</label>",
'item_label_hidden' => "<label class='InputfieldHeader'><span>{out}</span></label>",
'item_content' => "<div class='InputfieldContent {class}'>{description}{out}{error}{notes}</div>",
'item_error' => "<div class='LoginRegisterError'><small>{out}</small></div>",
'item_description' => "<p class='description'>{out}</p>",
'item_notes' => "<p class='notes'><small>{out}</small></p>",
'success' => "<p class='LoginRegisterMessage'>{out}</p>",
'error' => "<p class='LoginRegisterError'>{out}</p>",
'item_icon' => "",
'item_toggle' => "",
'InputfieldFieldset' => array(
'item' => "<fieldset {attrs}>{out}</fieldset>",
'item_label' => "<legend>{out}</legend>",
'item_label_hidden' => "<legend style='display:none'>{out}</legend>",
'item_content' => "<div class='InputfieldContent'>{out}</div>",
'item_description' => "<p class='description'>{out}</p>",
'item_notes' => "<p class='notes'><small>{out}</small></p>",
)
);
$classes = array(
'form' => '', // 'InputfieldFormNoHeights',
'list' => 'Inputfields',
'list_clearfix' => 'pw-clearfix',
'item' => 'Inputfield Inputfield_{name} {class}',
'item_required' => 'InputfieldStateRequired',
'item_error' => 'InputfieldStateError',
'item_collapsed' => 'InputfieldStateCollapsed',
'item_column_width' => 'InputfieldColumnWidth',
'item_column_width_first' => 'InputfieldColumnWidthFirst',
'InputfieldFieldset' => array(
'item' => 'Inputfield_{name} {class}',
)
);
InputfieldWrapper::setMarkup($markup);
InputfieldWrapper::setClasses($classes);
}
/**
* Allow the requested feature? (login, register, profile, login-email, forgot)
*
* @param string $name
* @return bool
*
*/
public function allowFeature($name) {
$allow = in_array($name, $this->features);
if($allow && $name == 'forgot') $allow = $this->wire('modules')->isInstalled('ProcessForgotPassword');
return $allow;
}
/**
* Allow the given email address to be used for registration or change-of-email?
*
* @param string $email
* @return bool
*
*/
protected function allowEmail($email) {
$email = $this->wire('sanitizer')->email($email);
if(empty($email)) return false;
$email = $this->wire('sanitizer')->selectorValue($email);
if(empty($email)) return false;
$u = $this->wire('users')->get("email=$email, include=all");
if($u && $u->id) return false;
return true;
}
/**
* Allow the given user name?
*
* @param string $name
* @param string $reason When not allowed, the reason is populated to this string.
* @return bool
*
*/
protected function allowName($name, &$reason) {
$disallowedNames = array(
'admin',
'administrator',
'superuser',
'root',
'user',
'guest',
);
$nameOriginal = $name;
$name = $this->wire('sanitizer')->pageName($name);
$allow = false;
if(strlen($name) < 3) {
$reason = $this->_('User name must be 3+ characters in length');
} else if($name !== $nameOriginal) {
$reason = $this->_('User name does not validate, please try another.');
} else {
$u = $this->wire('users')->get("include=all, name=$name");
if($u && $u->id) {
$reason = $this->_('User name is already in use, please try another.');
} else {
$allow = true;
}
}
if($allow) {
foreach($disallowedNames as $disallowedName) {
if(strpos($disallowedName, $name) !== false) {
$reason = $this->_('Username is not allowed, please try another.');
$allow = false;
break;
}
}
}
return $allow;
}
/**
* Get role specified in name, optionally in "roleName:id" format
*
* @param string $name
* @return Role|NullPage
*
*/
protected function getRole($name) {
list($name, $id) = strpos($name, ':') ? explode(':', $name) : array($name, 0);
$role = $this->wire('roles')->get($name);
if((!$role || !$role->id) && $id) $role = $this->wire('roles')->get((int) $id);
return $role;
}
/**
* Convert an email address to a user name
*
* @param string $email
* @return string
*
*/
protected function emailToName($email) {
$name = $this->wire('sanitizer')->pageName($email, Sanitizer::translate);
$length = strlen($name);
$maxLength = 100;
if($length > $maxLength) {
$n = $length - 100;
$name = substr($name, 0, $maxLength) . "-$n";
}
return $name;
}
/**
* Main login flow control, handles all input/output for this module
*
* @return string
*
*/
public function ___execute() {
/** @var WireInput $input */
$input = $this->wire('input');
/** @var User $user */
$user = $this->wire('user');
/** @var Session $session */
$session = $this->wire('session');
$out = '';
$page = $this->wire('page');
$showLoginForm = false;
if($user->isLoggedin()) {
// user is already logged in
if($input->get('logout')) {
$this->message($this->_('You have logged out'));
$session->logout();
$session->redirect($this->wire('page')->url() . '?logout=1');
} else if($input->get('profile') && $this->allowFeature('profile')) {
$profileForm = $this->buildProfileForm();
if($input->post('profile_submit')) {
$this->processProfileForm($profileForm);
}
$out .= $this->renderProfileForm($profileForm);
} else {
if($input->get('register_created')) {
$this->message($this->_('Thank you, your account is confirmed and you are now logged in'));
}
$this->message(sprintf($this->_('Welcome %s'), $this->allowFeature('login-email') ? $user->email : $user->name));
}
$out .= $this->renderList($this->buildLoggedInLinks());
} else if($input->get('forgot') && $this->allowFeature('forgot')) {
// Forgot password
$process = $this->modules->get("ProcessForgotPassword");
/** @var ProcessForgotPassword $process */
$out = $process->execute();
} else if($input->get('register') && $this->allowFeature('register')) {
// Register
$registerForm = $this->buildRegisterForm();
if($input->post('register_submit')) {
if($this->processRegisterForm($registerForm)) {
$confirmationForm = $this->buildConfirmationForm();
$out = $this->renderConfirmationForm($confirmationForm);
} else {
$out = $this->renderRegisterForm($registerForm);
}
} else {
$out = $this->renderRegisterForm($registerForm);
}
} else if($input->get('register_confirm') && $this->allowFeature('register')) {
// Register confirm
try {
if($this->processConfirmation()) {
// redirect to main page as logged in user
$session->redirect($page->url . '?register_created=1');
} else {
// i.e. honeypot
$showLoginForm = true;
}
} catch(\Exception $e) {
$this->error($this->_('Unable to complete confirmation, please re-register and try again'));
$showLoginForm = true;
}
} else {
// Login or process login
$showLoginForm = true;
}
if($showLoginForm && $this->allowFeature('login')) {
$loginForm = $this->buildLoginForm();
if($input->post('login_submit')) {
if($this->processLoginForm($loginForm)) {
$session->redirect($page->url() . '?login=1');
}
}
$out = $this->renderLoginForm($loginForm);
}
return $this->wrapOutput($out);
}
/**
* Build the list of links to show a logged in user
*
* @return array
*
*/
protected function ___buildLoggedInLinks() {
$page = $this->wire('page');
$links = array();
if(!$this->wire('input')->get('profile') && $this->allowFeature('profile')) {
$links['profile'] = "<a href='$page->url?profile=1'>" . $this->_('Edit profile') . "</a>";
}
$links['logout'] = "<a href='$page->url?logout=1'>" . $this->_('Logout') . "</a>";
return $links;
}
/**
* Process the login form
*
* @param InputfieldForm $form
* @return bool
* @throws WireException on CSRF validation fail
*
*/
protected function ___processLoginForm(InputfieldForm $form) {
/** @var Session $session */
$session = $this->wire('session');
/** @var Sanitizer $sanitizer */
$sanitizer = $this->wire('sanitizer');
$session->CSRF->validate();
$form->processInput($this->wire('input')->post);
$nameIsEmail = $this->allowFeature('login-email');
$passField = $form->getChildByName('login_pass');
$nameField = $form->getChildByName('login_name');
$name = $nameField->attr('value');
$pass = $passField->attr('value');
if(!$name || !$pass) return false;
$name = $nameIsEmail ? $this->emailToName($name) : $sanitizer->pageName($name);
$pass = substr($pass, 0, 128);
$user = $session->login($name, $pass);
if((!$user || $user->name !== $name) && $nameIsEmail) {
// login failed, but login by email is supported
// attempt login a second time by finding user with email address and using their name
$user = null;
$email = $sanitizer->selectorValue($nameField->attr('value'));
$matches = $this->wire('users')->find("include=all, email=$email");
$qty = $matches->count();
if($qty === 1) {
$user = $session->login($matches->first()->name, $pass);
$name = $user->name;
} else if($qty > 1) {
$this->error($this->_('Cannot login because more than one account uses this email address.'));
}
}
if($user && $user->name === $name) {
// successful login
$this->loginSuccess($user);
$this->message(sprintf($this->_('Successful login for %s'), ($nameIsEmail ? $user->email : $user->name)));
return true;
} else {
// login failed
$this->loginFail($name);
$this->error($this->_("Login failed"));
$passField->attr('value', '');
}
return false;
}
/**
* Build the login form
*
* @return InputfieldForm
*
*/
protected function ___buildLoginForm() {
/** @var InputfieldForm $form */
$form = $this->modules->get('InputfieldForm');
$form->attr('id', 'LoginRegisterLoginForm');
$form->description = $this->_('Login');
/** @var InputfieldText $nameField */
if($this->allowFeature('login-email')) {
$nameField = $this->modules->get('InputfieldEmail');
$nameField->set('label', $this->_('Email')); // Login form: email field label
} else {
$nameField = $this->modules->get('InputfieldText');
$nameField->set('label', $this->_('Username')); // Login form: username field label
}
$nameField->attr('id+name', 'login_name');
$nameField->attr('class', $this->className() . 'Name');
$nameField->collapsed = Inputfield::collapsedNever;
$nameField->required = true;
$form->add($nameField);
/** @var InputfieldText $passField */
$passField = $this->modules->get('InputfieldText');
$passField->set('label', $this->_('Password')); // Login form: password field label
$passField->attr('id+name', 'login_pass');
$passField->attr('type', 'password');
$passField->attr('class', $this->className() . 'Pass');
$passField->collapsed = Inputfield::collapsedNever;
$passField->required = true;
$form->add($passField);
/** @var InputfieldSubmit $submitField */
$submitField = $this->modules->get('InputfieldSubmit');
$submitField->attr('name', 'login_submit');
$submitField->attr('value', $this->_('Login')); // Login form: submit login button
$submitField->appendMarkup = $this->wire('session')->CSRF->renderInput();
$form->add($submitField);
return $form;
}
/**
* Render the login form
*
* @param InputfieldForm $form
* @return string
*
*/
protected function ___renderLoginForm(InputfieldForm $form) {
return $form->render() . $this->renderList($this->buildLoginFormLinks());
}
/**
* Get the login alternative links that appear below login form
*
* @return array
*
*/
protected function ___buildLoginFormLinks() {
$links = array();
$page = $this->wire('page');
if($this->allowFeature('forgot')) {
$links['forgot'] = "<a href='$page->url?forgot=1'>" . $this->_("Forgot your password?") . "</a>";
}
if($this->allowFeature('register')) {
$links['register'] = "<a href='./?register=1'>" . $this->_("Register for an account") . "</a>";
}
if($this->wire('modules')->isInstalled('LoginFacebook')) {
$p = $this->wire('pages')->get('template=login-facebook');
if($p->id) {
$links['facebook'] = "<a href='$p->url'>" . $this->_('Login with Facebook') . "</a>";
}
}
return $links;
}
/**
* Build the registration form
*
* @return InputfieldForm
*
*/
protected function ___buildRegisterForm() {
$userTemplate = $this->wire('templates')->get('user');
$registerFields = $this->registerFields;
/** @var InputfieldForm $form */
$form = $this->modules->get('InputfieldForm');
$form->attr('id', 'LoginRegisterForm');
$form->attr('method', 'post');
$form->attr('action', $this->wire('page')->url() . '?register=1');
$form->description = $this->_('Register for an account');
if(!$this->allowFeature('login-email')) {
/** @var InputfieldText $f */
$f = $this->modules->get('InputfieldText');
$f->attr('id+name', 'register_name');
$f->label = $this->_('Login name');
$f->description = $this->_('Use letters, numbers, hyphens or underscores. No spaces.');
$f->required = true;
$f->attr('required', 'required');
$form->add($f);
}
if(!in_array('email', $registerFields)) $registerFields[] = 'email';
if(!in_array('pass', $registerFields)) $registerFields[] = 'pass';
$nullPage = new NullPage();
$this->wire($nullPage);
foreach($registerFields as $fieldName) {
if($fieldName == 'roles') continue;
$field = $userTemplate->fieldgroup->getFieldContext($fieldName);
if(!$field) continue;
$inputfield = $field->getInputfield($nullPage);
$inputfield->attr('id+name', 'register_' . $inputfield->attr('name'));
if($fieldName == 'email' || $fieldName == 'pass') $inputfield->required = true;
if($inputfield->required && $inputfield instanceof InputfieldText) {
$inputfield->attr('required', 'required');
}
$form->add($inputfield);
}
// honeypot
/** @var InputfieldTextarea $f */
$f = $this->modules->get('InputfieldTextarea');
$f->attr('id+name', 'register_comment');
$f->label = $this->_('Comment');
$f->wrapAttr('style', 'display:none');
$form->add($f);
/** @var InputfieldSubmit $f */
$f = $this->modules->get('InputfieldSubmit');
$f->attr('id+name', 'register_submit');
$f->attr('value', $this->_('Register'));
$f->appendMarkup = $this->wire('session')->CSRF->renderInput();
$form->add($f);
return $form;
}
/**
* Render/output the registration form
*
* @param InputfieldForm $form
* @return string
*
*/
protected function ___renderRegisterForm(InputfieldForm $form) {
return $form->render();
}
/**
* Process the registration form
*
* @param InputfieldForm $form
* @return bool
* @throws WireException on CSRF validation fail
*
*/
protected function ___processRegisterForm(InputfieldForm $form) {
/** @var Session $session */
$session = $this->wire('session');
/** @var Sanitizer $sanitizer */
$sanitizer = $this->wire('sanitizer');
$session->CSRF->validate();
$form->processInput($this->wire('input')->post);
$emailInUseError = $this->_('Email address is already in use, please use another.');
// check if honeypot is populated
$honeypotField = $form->getChildByName('register_comment');
$honeypot = $honeypotField->attr('value');
if(strlen($honeypot)) return false;
// get requested password
$passField = $form->getChildByName('register_pass');
$pass = $passField->attr('value');
// validate email
$emailField = $form->getChildByName('register_email');
$email = $sanitizer->email($emailField->attr('value'));
if(strlen($email) && !$this->allowEmail($email)) {
$emailField->error($emailInUseError);
$emailField->attr('value', '');
}
// validate requested user name
$error = '';
if($this->allowFeature('login-email')) {
$name = $this->emailToName($emailField->attr('value'));
if(!$this->allowName($name, $error)) $emailField->error($emailInUseError);
} else {
$nameField = $form->getChildByName('register_name');
$name = $nameField->attr('value');
if(!$this->allowName($name, $error)) $nameField->error($error);
}
$errors = $form->getErrors();
if(!count($errors) && $name && $pass && $email) {
// if there were no errors
$session->setFor($this, 'register_name', $name);
foreach($form->getAll() as $f) {
$name = $f->attr('name');
if($name == 'register_submit') continue;
if(strpos($name, 'register_') !== 0) continue;
$session->setFor($this, $name, $f->attr('value'));
}
$code = $this->createConfirmationCode();
$this->sendConfirmationEmail($email, $code);
return true;
} else {
// there were errors and the form will be displayed again
foreach($errors as $error) $this->error($error);
}
return false;
}
/**
* Create a code for confirmation of account creation
*
* Also adds it to the session for later retrieval via 'confirm_code'
*
* @return string
*
*/
protected function createConfirmationCode() {
$pw = new Password();
$code = $pw->randomBase64String(40);
$this->wire('session')->setFor($this, 'confirm_code', $code);
return $code;
}
/**
* Send an email with a confirmation code they have to click on (or paste in)
*
* @param string $email
* @param string $confirmCode
* @return int 1 on success 0 on fail
*
*/
protected function sendConfirmationEmail($email, $confirmCode) {
$config = $this->wire('config');
$confirmURL = $this->wire('page')->httpUrl() . "?register_confirm=" . urlencode($confirmCode);
$mail = new WireMail();
$mail->subject(sprintf($this->_('Confirm account at %s'), $config->httpHost));
$mail->to($email);
$body = "<p>" . sprintf(
$this->_('Please click the link below to confirm the account you requested at %s'), $config->httpHost
) . "</p>\n\n<p>" . $this->_('Confirmation code:') . " $confirmCode</p>";
$mail->body(strip_tags($body) . "\n\n$confirmURL");
$mail->bodyHTML($body . "<p><a href='$confirmURL'>" . $this->_('Click to confirm') . "</a></p>");
return $mail->send();
}
/**
* Build the form where user can paste and submit a confirmation code
*
* This form is displayed after a successful registration form submission, but
* is only submitteed if user opts not to click the link in their email.
*
* @return InputfieldForm
*
*/
protected function ___buildConfirmationForm() {
/** @var InputfieldForm $form */
$form = $this->modules->get('InputfieldForm');
$form->attr('id', 'LoginRegisterConfirmationForm');
$form->attr('method', 'get');
$form->attr('action', $this->wire('page')->url());
$form->description =
$this->_('Thank you, a confirmation code has been emailed to you.') . ' ' .
$this->_('When you receive the email, click the link it contains, or paste the confirmation code below.');
/** @var InputfieldText $f */
$f = $this->modules->get('InputfieldText');
$f->attr('id+name', 'register_confirm');
$f->label = $this->_('Confirmation code');
$f->attr('required', 'required');
$form->add($f);
/** @var InputfieldSubmit $f */
$f = $this->modules->get('InputfieldSubmit');
$f->attr('name', 'confirm_submit');
$form->add($f);
return $form;
}
/**
* Render the confirmation form
*
* @param InputfieldForm $form
* @return string
*
*/
protected function ___renderConfirmationForm(InputfieldForm $form) {
return $form->render();
}
/**
* Process the confirmation, create new user and log them in
*
* This can be triggered by a URL clicked in an email, or by submission of the confirmation form.
*
* @return bool False if no code is present in URL
* @throws WireException Throws this exception for most error conditions
*
*/
protected function ___processConfirmation() {
$input = $this->wire('input');
$session = $this->wire('session');
$users = $this->wire('users');
$code = $input->get('register_confirm');
if(empty($code)) return false;
// validate that the code in the session is the same one present in the URL
if($session->getFor($this, 'confirm_code') !== $code) throw new WireException('Invalid confirmation code');
// get all values previously stored in session
$values = $session->getFor($this, '');
// check if email is now in use
if(!$this->allowEmail($values['register_email'])) {
throw new WireException('Email address already taken');
}
$error = '';
if(!$this->allowName($values['register_name'], $error)) {
throw new WireException('Unable to complete registration - ' . $error);
}
$user = $users->newUser();
// populate values to new user
foreach($values as $key => $value) {
if(strpos($key, 'register_') !== 0) continue;
$key = str_replace('register_', '', $key);
if($key == 'roles') continue; // don't allow this, just in case
if($key != 'name' && !$user->template->fieldgroup->hasField($key)) continue;
$user->set($key, $value);
}
// add role to user if defined
foreach($this->registerRoles as $roleName) {
$role = $this->getRole($roleName);
if($role->id && !$role->hasPermission('page-edit')) {
$user->addRole($role);
}
}
// create the user
try {
$this->createUserReady($user);
$user->save();
} catch(\Exception $e) {
throw new WireException('Unable to create user');
}
// login the newly created user
$u = $session->login($user->name, $values['register_pass']);
if($u && $u->id) {
// trigger created user hook
$this->createdUser($user);
// trigger login success hook
$this->loginSuccess($user);
// remove session data
$session->removeFor($this, true);
return true;
} else {
throw new WireException('Unable to login created user: ' . $u->name);
}
}
/**
* Build the profile edit form
*
* @return InputfieldForm
*
*/
protected function ___buildProfileForm() {
$user = $this->wire('user');
$of = $user->of();
$user->of(false);
$userTemplate = $this->wire('templates')->get('user');
/** @var InputfieldForm $form */
$form = $this->modules->get('InputfieldForm');
$form->attr('id', 'LoginRegisterProfileForm');
$form->attr('method', 'post');
$form->attr('action', $this->wire('page')->url() . '?profile=1');
$form->description = $this->_('Edit profile');
$profileFields = $this->profileFields;
if(!in_array('email', $profileFields)) $profileFields[] = 'email';
if(!in_array('pass', $profileFields)) $profileFields[] = 'pass';
foreach($profileFields as $fieldName) {
if($fieldName == 'roles') continue;
$field = $userTemplate->fieldgroup->getFieldContext($fieldName);
if(!$field) continue;
$inputfield = $user->getInputfield($field);
if(!$inputfield) continue;
if($fieldName == 'email') $inputfield->attr('required', 'required');
$inputfield->attr('name', 'profile_' . $inputfield->attr('name'));
$form->add($inputfield);
}
/** @var InputfieldSubmit $f */
$f = $this->modules->get('InputfieldSubmit');
$f->attr('id+name', 'profile_submit');
$f->appendMarkup = $this->wire('session')->CSRF->renderInput();
$form->add($f);
if($of) $user->of(true);
return $form;
}
/**
* Render the profile edit form
*
* @param InputfieldForm $form
* @return string
*
*/
protected function ___renderProfileForm(InputfieldForm $form) {
return $form->render();
}
/**
* Process the profile edit form and save user
*
* @param InputfieldForm $form
* @return bool
*
*/
protected function ___processProfileForm(InputfieldForm $form) {
/** @var Session $session */
$session = $this->wire('session');
/** @var User $user */
$user = $this->wire('user');
/** @var Sanitizer $sanitizer */
$sanitizer = $this->wire('sanitizer');
$session->CSRF->validate();
$form->processInput($this->wire('input')->post);
$of = $user->of();
$user->of(false);
$user->resetTrackChanges();
$message = $this->_('Updated: %s');
// password
$passField = $form->getChildByName('profile_pass');
$pass = $passField->val();
if(strlen($pass)) {
$user->set('pass', $pass);
$this->message(sprintf($message, $passField->label));
}
// email
$emailField = $form->getChildByName('profile_email');
if(strtolower($emailField->val()) != strtolower($user->email)) {
$email = $sanitizer->email($emailField->val());
if(strlen($email)) {
if($this->allowEmail($email)) {
// email changed allowed
if($this->allowFeature('login-email')) {
// update name to be consistent with email
$name = $this->emailToName($email);
$reason = '';
if(!$this->allowName($name, $reason)) {
$emailField->error($this->_('Unable to change to new email address') . " ($reason)");
$emailField->val($user->email);
$email = '';
}
}
if(strlen($email)) {
$user->set('email', $email);
$this->message(sprintf($message, $emailField->label));
}
} else {
// email is in use
$emailField->error($this->_('Email address was already in use, so changed back to previous.'));
$emailField->val($user->email);
}
}
}
// all other form fields
foreach($form->getAll() as $f) {
$name = str_replace('profile_', '', $f->attr('name'));
if($name == 'pass' || $name == 'submit' || $name == 'roles' || $name == 'email') continue;
$value = $f->val();
if($user->hasField($name) && $user->get($name) != $value) {
$user->set($name, $value);
$this->message(sprintf($message, $f->label));
}
}
$errors = $form->getErrors();
if(!count($errors)) {
if($user->isChanged()) {
$user->save();
$this->message($this->_('Saved profile'));
}
}
if($of) $user->of(true);
foreach($errors as $error) $this->error($error);
return true;
}
/**
* Wrap all LoginRegister output and add errors, messages, scripts, styles, etc.
*
* @param string $out
* @return string
*
*/
protected function wrapOutput($out) {
$o = '';
$config = $this->wire('config');
if($this->renderStyles) {
$styles = $config->styles;
$styles->add($config->urls($this->className()) . $this->className() . '.css');
foreach($this->wire('config')->styles as $file) {
$o .= "<link rel='stylesheet' type='text/css' href='$file' />";
}
}
foreach($this->errors('array') as $error) {
$o .= $this->renderError($error);
}
foreach($this->messages('array') as $message) {
$o .= $this->renderMessage($message);
}
if($this->renderScripts) {
foreach($config->scripts as $file) {
if(strpos($file, 'JqueryCore.js') !== false) {
$out .= "<script>if(typeof jQuery == 'undefined') " .
"document.write('<scr' + 'ipt src=\"$file\"></scr' + 'ipt>');</script>";
} else {
$out .= "<script src='$file'></script>";
}
}
}
return "<div id='LoginRegister'>$o$out</div>";
}
/**
* Render a list (used primary for lists of links)
*
* @param array $items Associative array where key is name and value is HTML content of list item
* @param string $class Class to use for the list, and as prefix for items
* @return string
*
*/
protected function ___renderList(array $items, $class = 'LoginRegisterLinks') {
$out = '';
foreach($items as $name => $item) {
$c = $class . ucfirst($name);
$out .= "<li class='$c'>$item</li>";
}
if($out) $out = "<ul class='$class'>$out</ul>";
return $out;
}
/**
* Render an error message in markup
*
* @param string $error Error message (not entity encoded)
* @return string HTML
*
*/
protected function ___renderError($error) {
return "<p class='LoginRegisterError'>" . $this->wire('sanitizer')->entities($error) . "</p>";
}
/**
* Render an informational message in markup
*
* @param string $message Message (not entity encoded)
* @return string HTML
*
*/
protected function ___renderMessage($message) {
return "<p class='LoginRegisterMessage'>" . $this->wire('sanitizer')->entities($message) . "</p>";
}
/**
* Hook called after successful login
*
* @param User $user
*
*/
protected function ___loginSuccess(User $user) { }
/**
* Hook called after login failure
*
* @param string $name
*
*/
protected function ___loginFail($name) { }
/**
* Hook called when User is about to be created
*
* @param User $user
*
*/
protected function ___createUserReady(User $user) { }
/**
* Hook called after user is created
*
* @param User $user
*
*/
protected function ___createdUser(User $user) { }
/**
* Install the module and add a login-register role
*
*/
public function ___install() {
/** @var Roles $roles */
$roles = $this->wire('roles');
$role = $roles->get(self::defaultRoleName);
if(!$role->id) {
$role = $roles->add(self::defaultRoleName);
if($role->id) $this->message("Added role: $role->name");
}
}
/**
* Module configuration
*
* @param InputfieldWrapper $inputfields
*
*/
public function getModuleConfigInputfields(InputfieldWrapper $inputfields) {
$hasForgot = $this->modules->isInstalled('ProcessForgotPassword');
/** @var InputfieldCheckboxes $f */
$f = $this->modules->get('InputfieldCheckboxes');
$f->attr('name', 'features');
$f->label = $this->_('Features to use');
$f->icon = 'sliders';
$f->addOption('login', $this->_('Login form'));
$f->addOption('register', $this->_('New user registration form'));
$f->addOption('profile', $this->_('Edit profile form (for logged-in users)'));
$f->addOption('login-email', $this->_('Use email address for login rather than user name'));
$f->addOption('forgot', $this->_('Forgot password reset') . ($hasForgot ? '' : '*'));
$f->attr('value', $this->features);
if(!$hasForgot) $f->notes =
$this->_('*Requires core “ProcessForgotPassword” module') . ' - [' .
$this->_('Click to install') . '](./installConfirm?name=ProcessForgotPassword)';
$inputfields->add($f);
/** @var InputfieldAsmSelect $f */
$f = $this->modules->get('InputfieldAsmSelect');
$f->attr('name', 'registerFields');
$f->label = $this->_('Registration form fields');
$f->description =
$this->_('If you do not see all the fields you need, add them to the “user” template first and then come back here.') . ' ' .
$this->_('Only standard fields may be used on the front-end (no files, repeaters, richtext, multi-value, combined fields, etc.).');
$f->showIf = 'features=register';
$f->icon = 'address-book-o';
foreach($this->wire('templates')->get('user')->fieldgroup as $field) {
if($field->name == 'roles') continue;
$label = $field->getLabel();
if($field->name == 'email' || $field->name == 'pass') $label .= ' ' . $this->_('(required)');
$f->addOption($field->name, $label);
}
$f2 = clone $f;
$value = $this->registerFields;
if(!in_array('email', $value)) $value[] = 'email';
if(!in_array('pass', $value)) $value[] = 'pass';
$f->attr('value', $value);
$inputfields->add($f);
/** @var InputfieldAsmSelect $f2 */
$f2->attr('name', 'profileFields');
$f2->label = $this->_('Profile form fields');
$f2->showIf = 'features=profile';
$f2->icon = 'address-card-o';
$value = $this->profileFields;
if(!in_array('email', $value)) $value[] = 'email';
if(!in_array('pass', $value)) $value[] = 'pass';
$f2->attr('value', $value);
$inputfields->add($f2);
/** @var InputfieldCheckboxes $f */
$f = $this->modules->get('InputfieldCheckboxes');
$f->attr('name', 'registerRoles');
$f->label = $this->_('Roles to add to newly registered users');
$f->notes = $this->_('The “guest” role is assumed. Roles with page-edit permission are not shown.');
$f->showIf = 'features=register';
$f->icon = 'gears';
$value = array();
foreach($this->wire('roles') as $role) {
if($role->name == 'guest' || $role->name == 'superuser') continue;
if($role->hasPermission('page-edit')) continue;
$option = "$role->name:$role->id";
$f->addOption($option, $role->name);
if(in_array($option, $this->registerRoles) || in_array($role->name, $this->registerRoles)) $value[] = $option;
}
$f->attr('value', $value);
$inputfields->add($f);
/** @var InputfieldMarkup $f */
$f = $this->modules->get('InputfieldMarkup');
$f->attr('name', '_docs');
$f->label = $this->_('How to use + documentation');
$f->icon = 'bookmark-o';
$f->collapsed = Inputfield::collapsedYes;
if($this->wire('adminTheme') instanceof AdminThemeFramework) {
$f->value = $this->wire('sanitizer')->entitiesMarkdown(file_get_contents(__DIR__ . '/README.md'), true);
} else {
$f->description =
$this->_('Place the following where you want the login/register/profile output to go in your template file.') . ' ' .
$this->_('It should ideally be placed in a main/bodycopy area.');
$f->value = '<pre><code>&lt;?php echo $modules->get("LoginRegister")->execute(); ?&gt;</code></pre>';
$f->notes = sprintf(
$this->_('See the [full documentation](%s) for more details.'),
'https://github.com/ryancramerdesign/LoginRegister/blob/master/README.md'
);
}
$inputfields->add($f);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment