Jump to content

FrontendUser: login, logout and register users / members


pwFoo

Recommended Posts

Great! The basic process now works without errors.

Since this is the first Processwire module I am working with, I am still very confused how to finish this. I need to figure out the following:

1. What happens after the user has registered, with the email validation code added to the form? There is no second 'Welcome' email with instructions for the user. Or instead of a confirmation/welcome email you could put a message on the screen after registering: 'Thank you for registering. You can now log in with the username and the password you just picked.' Or log them in automatically after registering. But most users will probably expect a Welcome/confirmation email.

2. How can I assign a role to users upon registration? I have added a custom role "member".

3. How can I add fields to the form, starting with Firstname Lastname? Can Processwire's users db table(s) be extended and used to store entire user accounts - address, phone number, rss feed, favorite color, hobbies, spirit animal, ...?

4. How can I style the form, including the html, not just using the CSS classes/ids? I want to get rid of the listing tags li etc. Would there be a way to just hardcode a form in the template instead of generating it with fu->register()?

5. How can I change the text/style of the validation email? Probably add a template file somewhere?

The answers to this are probably to be found in PW documentation and elsewhere on the forum. Any pointers in the right direction would help.

 

Link to comment
Share on other sites

Take a look at the frontenduser repo wiki (nickname, default role) and the PW hook documentation for examples how to add / modify features. 

FrontendUser just use PW features (API, InputfieldForm and hooks)! Add own features / plugins by hook into PW / FrontendUser methods (add notification or welcome email after a new user registered successful).

 

Link to comment
Share on other sites

Thanks pwFoo. I guess I could use this and this as guides then? They are from 2011...

I don't see anything about nickname (?) or Firstname Lastname in the frontenduser repo wiki.

What is the hook for new user registered successful? I can't find it here searching on 'register' or 'registration' or 'user'.

Has anyone implemented a welcome/confirmation email with FrontendUser?

Link to comment
Share on other sites

You can use examples from the forum or just modify to fit your needs.
There is a mail example inside FrontendUserRegisterEmailValidation.

 

PW hooks: 
https://processwire.com/api/hooks/

FrontendUser Wiki:
https://bitbucket.org/pwFoo/frontenduser/wiki/Register extensions and plugins
https://bitbucket.org/pwFoo/frontenduser/wiki/Login extensions and plugins
https://bitbucket.org/pwFoo/frontenduser/wiki/Code snippets / Examples
https://bitbucket.org/pwFoo/frontenduser/wiki/Documentation#markdown-header-styles-scripts-templates

You can hook the FrontendUser methods auth (=login) and save (=register / save user).
Just hook after FrontendUser save() and check the event return value if user was saved sucessful...

Captain Hook -> search login -> /wire/core/session __login($name, $pass)

 

I love the PW API and hooks ^-^

 

  • Like 2
Link to comment
Share on other sites

@modifiedcontent

 

Imagine we have 3 pagesHome, Register, Member Area and 3 templates : home, register, member-area.

 

  • In the template home, if the user is not logged in, the login form appear on the homepage - there is also a link to our register page, another for the member area page and the logout link.

Code of home.php :

Spoiler

<?php
// load the module
$fu = $modules->get('FrontendUser');
// if the user is logged in and the get variable "logout" is set, logout the user
if($input->get->logout && $user->isLoggedin()) $fu->logout($pages->get('/')->url);
?>
<html>
<head>
</head>
<body>
<h1>Page: <?php echo $page->title; ?></h1>
<ul>
    <li><a href="<?php echo $pages->get('/register/')->url; ?>">Register</a> <?php if($user->isLoggedin()): ?> | <a href="?logout=logout">Logout</a> <?php endif; ?> </li>
</ul>

<?php
$out = '';
// check if user is logged
if($user->isLoggedin()) {

    $out = "Welcome {$user->name} - You are already logged in...";
    // if the user have the role "member"
    if ($user->hasRole("member")) {
        //redirect the user to the member area
        $session->redirect($pages->get('/member-area/')->url);
        // or do something
    } else {
        // do other stuff for other roles
    }
} else {
    // user is not logged in, show the login form
    $fu->login(['username', 'password']);
    $fu->process($pages->get('/member-area/')->url);
    $out .= $fu->render();
}

echo $out;
?>
</body>
</html>

 

 

 

  • In the register template, we show the registration form. First step, once the form is filled, a validation email is sent to the user. Then if the user go to his mailbox and click to the validation link, he return to our register page and submit the form with the validation token. If everything went smooth, we use a hook before the form is saved to add a role "member" to the user. Then we use a hook after the form is saved to send a confirmation email with the details (username, email, passowrd, welcome message, etc.).

 Code of register.php :

Spoiler

<html>
    <head>
    </head>
    <body>
        <h1>Page: <?php echo $page->title; ?></h1>
        <ul>
            <li><a href="<?php echo $pages->get('/')->url; ?>">Homepage / login</a></li>
        </ul>

        <?php
        $out = '';
        // check if user is logged
        if($user->isLoggedin()) {
            // the user is already logged in, no registration possible, redirect the user to the hmepage
            $session->redirect($pages->get('/')->url);

        } else {
            // user is not logged in then show the registration form
            $fu = $modules->get('FrontendUser'); // load the module
            $fu->register(['username', 'email', 'password', 'emailValidation']); // prepare the form
            // hook to add role
            $fu->addHookBefore('FrontendUser::save', function() use($fu) {
                $fu->userObj->addRole('member'); // add role "member"
            });
            // hook to send an email back to the user with connection/login details
            $fu->addHookAfter('FrontendUser::save', function($event) use($fu, $input) {
                  if($event->return === true) {
                    if(!empty($input->post->password) && !count($fu->form->getErrors())) {
                      $subject = "Your new account at mydomain.com";
                      $emailContentPlain = "Your connection details:\nUsername: {$fu->userObj->name}\nEmail: {$fu->userObj->email}\nPassword: {$input->post->password}";
                      $mail = wireMail();
                      $mail->to($fu->userObj->email);
                      $mail->subject($subject);
                      // plain body
                      $mail->body($emailContentPlain);

                      $mail->send();
                    }
                  }
            });
            $fu->process($pages->get('/')->url);
            $out .= $fu->render();
        }

        echo $out;
        ?>
    </body>
</html>

 

 

 

  • And then in the member-area template, just show welcome message and a logout link. Nothing special here.

Code of member-area.php :

Spoiler

<?php
// load the module
$fu = $modules->get('FrontendUser');
// if the user is logged in and the get variable "logout" is set, logout the user and redirect to the homepage
if($input->get->logout && $user->isLoggedin()) $fu->logout($pages->get('/')->url);
// if the user has not the role "member", redirect him to the homepage
if(!$user->hasRole("member") || !$user->isLoggedin()) $session->redirect($pages->get('/')->url);
?>
<html>
    <head>
    </head>
    <body>
        <h1>Page: <?php echo $page->title; ?></h1>
        <ul>
            <li><?php if($user->isLoggedin()): ?> <a href="?logout=logout">Logout</a> <?php endif; ?> </li>
        </ul>
        <p>Hello <?php echo $user->name; ?> - Welcome to the member Area / profile page / whatever.</p>
    </body>
</html>

 

 

To test it, just create and copy paste the code of each template.

 

FAQ:

Quote

1. [...] But most users will probably expect a Welcome/confirmation email.

Check the hook in the register.php template above.

 

Quote

2. How can I assign a role to users upon registration? I have added a custom role "member".

Check the hook in the register.php template above.

 

Quote

3. How can I add fields to the form, starting with Firstname Lastname? Can Processwire's users db table(s) be extended and used to store entire user accounts - address, phone number, rss feed, favorite color, hobbies, spirit animal, ...?

Read this post and ask for example/help if needed.

 

Quote

4. How can I style the form, including the html, not just using the CSS classes/ids? I want to get rid of the listing tags li etc. Would there be a way to just hardcode a form in the template instead of generating it with fu->register()?

You could do something like that :

$markup = $fu->render();
$markup = str_replace('<ul', '<div', $markup);
$markup = str_replace('<li', '<div', $markup);
$markup = str_replace('</li>', '</div>', $markup);
$markup = str_replace('</ul>', '</div>', $markup);
$out .= $markup;

(I am sure there is a better way to do it)

 

Quote

5. How can I change the text/style of the validation email? Probably add a template file somewhere?

You can modify the file emailValidation.php or use your own in /site/modules/FrontendUser/templates.

 

 

Hope it help :)

Edited by flydev
FAQ
  • Like 5
Link to comment
Share on other sites

Yes, that helps a lot, especially the addHookAfter bit for the confirmation email. Thanks flydev & pwFoo!

Below is my one page working version, as a minumum starting point:

Spoiler

$fu = $modules->get('FrontendUser');

$redirectDestination = htmlentities($_SERVER['REQUEST_URI']);

$fu->addHookBefore('save', function($event) {
    $user = wire('fu')->userObj;
    $user->addRole('member');
});

$fu->addHookAfter('FrontendUser::save', function($event) use($fu, $input) {
	if($event->return === true) {
		if(!empty($input->post->password) && !count($fu->form->getErrors())) {
			$subject = "Welcome - your new account at mydomain.com";
			$emailContentPlain = "Your connection details:\nUsername: {$fu->userObj->name}\nEmail: {$fu->userObj->email}\nPassword: {$input->post->password}";
			$mail = wireMail();
			$mail->to($fu->userObj->email);
			$mail->subject($subject);
			$mail->body($emailContentPlain);
			$mail->send();
		}
	}
});

// Login with the email address instead of the username
$fu->addHookBefore('FrontendUser::auth', function($event) {
        $email = wire('fu')->form->fhValue('username', 'email');
        $loginUser = wire('users')->get("email={$email}");

        if ($loginUser instanceof User && !$loginUser->isGuest()) {
            $userObj = $event->arguments[0];
            $userObj->name = $loginUser->name;
        }
});

if ($user->isGuest()) {
$fu->register(array('username', 'email', 'emailValidation', 'password'));
$fu->process($redirectDestination);
echo $fu->render();

echo 'or log in here if you already have an account';
echo $fu->login(null, $redirectDestination);
}

if($user->isLoggedin()) {
	if (isset($_GET['logout'])) $fu->logout($redirectDestination);
	echo "<a href='?logout'>log out</a>";
}

Any suggestions for improvements? Is that log out link the cleanest way to do it?

Link to comment
Share on other sites

Hello pwFoo,

Thank you for this suite of modules - I've just been trying them out for the first time and they look very useful. I particularly like the email validation portion of this.

One small observation though, when registering a new user and using a name or email that already exists, all the entered values are lost on unsuccessful form submission. I think it would be helpful to keep the value of any field that is not in error. So, if a user typed an in-use name, but the email was OK, then we should keep the email field and only clear the name field.

Thank you!

Link to comment
Share on other sites

I'll take a look... don't know when, but I think I have to change the clear session values part...

            // Clear session values
            wire('session')->remove('registerToken');
            wire('session')->remove('registerUsername');
            wire('session')->remove('registerEmail');

https://bitbucket.org/pwFoo/frontenduser/src/0070dc3106945198ac438bab3ab742b1784080e9/FrontendUser/FrontendUserRegisterEmailValidation.module?at=master&fileviewer=file-view-default#FrontendUserRegisterEmailValidation.module-165

https://bitbucket.org/pwFoo/frontenduser/src/0070dc3106945198ac438bab3ab742b1784080e9/FrontendUser/FrontendUserRegisterEmailValidation.module?at=master&fileviewer=file-view-default#FrontendUserRegisterEmailValidation.module-143

It should check and take care about field errors first.

Link to comment
Share on other sites

Trying to add firstname, lastname fields now. Here is what I have so far, following an earlier post suggested by flydev:

Spoiler

<?php
$fu = $modules->get('FrontendUser');

$redirectDestination = htmlentities($_SERVER['REQUEST_URI']);

$firstname = $modules->get('InputfieldText');
$firstname->label = 'First name';
$firstname->attr('id+name','firstname');
$firstname->required = 1;

$firstname->addHookAfter('processInput', function($event) {
   $field = $event->object;
   $mySanitizedCustomInput = wire('fu')->form->fhValue($field->name);
   wire('fu')->userObj->firstname = $mySanitizedCustomInput;
});

$fu->addHookBefore('save', function($event) {
    $user = wire('fu')->userObj;
    $user->addRole('member');
});

$fu->addHookAfter('FrontendUser::save', function($event) use($fu, $input) {
	if($event->return === true) {
		if(!empty($input->post->password) && !count($fu->form->getErrors())) {
			$subject = "Welcome - your new account at mydomain.com";
			$emailContentPlain = "Your connection details:\nUsername: {$fu->userObj->name}\nEmail: {$fu->userObj->email}\nPassword: {$input->post->password}";
			$mail = wireMail();
			$mail->to($fu->userObj->email);
			$mail->subject($subject);
			$mail->body($emailContentPlain);
			$mail->send();
		}
	}
});

// Login with the email address instead of the username
$fu->addHookBefore('FrontendUser::auth', function($event) {
        $email = wire('fu')->form->fhValue('username', 'email');
        $loginUser = wire('users')->get("email={$email}");

        if ($loginUser instanceof User && !$loginUser->isGuest()) {
            $userObj = $event->arguments[0];
            $userObj->name = $loginUser->name;
        }
});

if ($user->isGuest()) {
$fu->register(array($firstname,'username', 'email', 'emailValidation', 'password'));
$fu->process($redirectDestination);
echo $fu->render();

echo 'or log in here if you already have an account';
echo $fu->login(null, $redirectDestination);
}

if($user->isLoggedin()) {
	if (isset($_GET['logout'])) $fu->logout($redirectDestination);
	echo "<a href='?logout'>log out</a>";
	echo '<h3>Hello '. $user->firstname .'</h3>';
}
?>

 

This sort of works, but the firstname is not filled in when you click on the email validation link; the user has to manually add it again. It should be stored along with the username, right?

Is 'addHookAfter ...' the wrong hook? Is it essentially the same problem netcarver posted about here?

Will I have to add the same lines like the $firstname stuff for each custom field or is there a way to consolidate that, process all custom fields with one function?

 

Link to comment
Share on other sites

At the moment I think about a rewrite and code cleanup, but have no time to do it.

The return value of process() method could cause a strange behavior because it returns "true" also if the form wasn't submitted because of the "return $this" for chaining 

    public function process($redirect) {
        if ($this->form->fhProcessForm()) {
            switch ($this->action) {
                case 'Login':
                    $result = $this->auth($this->userObj);
                  break;
                case 'Register':
                    $result = $this->save($this->userObj);
                  break;
            }
            if ($result === true) {
                $this->session->redirect($redirect, false);     // User sucessfully registered
            }
            else {
                $this->form->fhSubmitBtn->error($result);       // Save user failed?
            }        
        }
        return $this;
    }

 

  • Like 1
Link to comment
Share on other sites

  • 1 month later...

I want to generate the username from a real/full name; firstname + lastname. How can I get onkeyup into the fullname input field?

This didn't work:

$fullname = $modules->get('InputfieldText');
$fullname->label = 'Full name';
$fullname->attr('id+name','fullname');
$fullname->required = 1;
$fullname->onkeyup='generateusername()';

Question now asked here with the username autogenerating javascript included.

Link to comment
Share on other sites

I can't figure out the forms API and hookable etc. I only have very rudimentary PHP knowledge.

I had copied and pasted this together to get a custom realname/fullname field:

Spoiler

$fullname = $modules->get('InputfieldText');
$fullname->label = 'Full name';
$fullname->attr('id+name','fullname');
$fullname->required = 1;

$fullname->addHookAfter('processInput', function($event) {
   $field = $event->object;
   $mySanitizedCustomInput = wire('fu')->form->fhValue($field->name);
   wire('fu')->userObj->fullname = $mySanitizedCustomInput;
});

$fu->addHookBefore('save', function($event) {
    $user = wire('fu')->userObj;
    $user->addRole('member');
});

>

But the fullname field is not stored. How do you store custom fields with userObj? I can't find any clear code examples anywhere that don't go off into a million different directions.

Should I add something under $fu->addHookBefore, like $user->fullname or something like that?

What is fhValue($field->name)? What "name" is that? Is that the username? I tried it changed to $field->fullname, but that didn't do anything.

Adding a new user apparently looks something like this:

Spoiler

$u = new User();
$u->name = $form->get('username')->value;
$u->pass = $form->get('password')->value;
$u->email = $form->get('email')->value;
$u->addRole('guest');

 

So I guess I have to define fullname "somewhere" using $form->get('fullname')->value?

I have no idea what the module does and what I am supposed to code together myself and how it is all supposed to hang together. Completely lost. Getting this ready for "production" is starting to take way too long.

Link to comment
Share on other sites

Yes, I get that. I have a custom fullname field, but can't find anywhere how to store that value and how that works in combination with your module. PW features and hookable etc. may be completely obvious to you, but this is the first PW module I am working with.

I had added the fullname field to the template. Should I also add a username field to the template? Or does the module take care of that?

There is this error log in admin:

Error: Exception: Method FrontendUser::fullnameRegister does not exist or is not callable in this context (in /home/mysitepath/wire/core/Wire.php line 410)

Link to comment
Share on other sites

12 hours ago, modifiedcontent said:

Yes, I get that. I have a custom fullname field, but can't find anywhere how to store that value and how that works in combination with your module.

Hi,

you first need a backend template for your users that has the fields you want to work with.

think of the fields in a template as the columns of a row of data for the 'table' (here: "user", most times "page") (actually this is not how it works under the hood, but it helps to think of it this way in the beginning); the standard user template just has the minimum (data)fields required, aka name, password, and email and a bit of meta things. so to work with your new field 'fullname', you need to create a field 'fullname' in the admin, then apply this field to a new template (for example 'user_extended'; this template should have all the fields of the regular user template, plus your newly created one(s)) or. You can extend the user-template with your new field(s), by editing the user template (and setting the list to include system fields).

once you have the template applied to the 'user'Once your field is in the user template, your $userObject->fullname = blablainputfield->fullname will work (assuming that the field for the full name in the template is named 'fullname').

hope this helps to get you started,

Tom

Link to comment
Share on other sites

Hi, I would like to do a register form with email pre-registration validation, but without the username field.

Instead the username should be the email sanitzed as a pageName.

I have the following code right now:

<?php namespace ProcessWire;

// prepare register form
// Additional email pre-register validation plugin (built-in)
$fu->register(array('email', 'emailValidation', 'password'));

$fu->form->setMarkup($form_markup);
$fu->form->setClasses = ($form_classes);

$fu->addHookBefore('FrontendUser::save', function ($event) {
    $user = wire('fu')->userObj;
    $form = wire('fu')->form;
    if (!count($form->getErrors())) {
        // set the username to sanitized email value
//        $user->name = $form->fhValue('email', 'pageName');
        $user->addRole('editor');
    }
});

// process register / form submit
$fu->process($profile_url);
$register_form = $fu->render();
$view->set('registerForm', $register_form); // this is for Twig

But when I submit that form I get an error:

Call to a member function getErrors() on null in line 94 of FrontendUserRegisterEmailValidation.module

I think it is because in the function "hookRegisterFormAfterProcess" there is line 84:

$user = $form->get('username');

As I send no username, this can not work. So how can I make it work without touching the FrontendUserRegisterEmailValidation.module?

Thanks in advance.

  • Like 1
Link to comment
Share on other sites

@jmartsch, actually go without a username entirely would probably require a rewrite of this module and maybe PW core itself.

I am trying to get rid of the username field by autogenerating a username based on the full name input and then make the username field hidden; there is still a username, but only for internal system purposes. I use this javascript:

Spoiler

<script>
$("#fullname").bind("keyup change", function(e) {
	var input=$(this).val();
	
	input = input.replace(/^\s+|\s+$/g, ''); // trim
	input = input.toLowerCase();
  
  // replace diacritics, weird accent letters, swap ñ for n, è for e, etc
	var diacritics = [
    {char: 'A', base: /[\300-\306]/g},
    {char: 'a', base: /[\340-\346]/g},
    {char: 'E', base: /[\310-\313]/g},
    {char: 'e', base: /[\350-\353]/g},
    {char: 'I', base: /[\314-\317]/g},
    {char: 'i', base: /[\354-\357]/g},
    {char: 'O', base: /[\322-\330]/g},
    {char: 'o', base: /[\362-\370]/g},
    {char: 'U', base: /[\331-\334]/g},
    {char: 'u', base: /[\371-\374]/g},
    {char: 'N', base: /[\321]/g},
    {char: 'n', base: /[\361]/g},
    {char: 'C', base: /[\307]/g},
    {char: 'c', base: /[\347]/g}
	]

	diacritics.forEach(function(letter){
    input = input.replace(letter.base, letter.char);
	});
	
	input = input.replace(/[^a-z0-9 -]/g, '') // remove invalid chars
	.replace(/\s+/g, '') // collapse whitespace and replace by -
	.replace(/-+/g, ''); // collapse dashes

	$("#username").val(input);
});
</script>

 

 

Link to comment
Share on other sites

@modifiedcontent Thank you for your suggestion. But relying on JavaScript is not good. I also think that a rewrite is not needed. I don´t want no username. I just don´t want the username in the form because it could be manipulated. The username should be the same as the email address, but sanitized as pageName.

 

  • Like 1
Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
×
×
  • Create New...