Membership
Statamic’s membership functionality has been expanded to allow users to sign up, sign in, update their profiles, and more — all from the front-side of the website.
note We differentiate users from members. A member is a logged-in user of the site. A user is anyone that uses your site. Their relationship is a bit like squares and rectangles: all members are users, but not all users are members.
Configuring
Membership is packaged as a bundle, and comes with two config files:
_config/bundles/member/member.yaml
holds a couple of settings for making membership work_config/bundles/member/fields.yaml
is a list of allowed fields that each member can have in their profile
Fields
The fields.yaml
file is structured the same as fieldsets (if you’ve ever set up the Statamic Control Panel, you’ll be right at home).
We’ve provided a default set of fields to get you started, but you can add or remove whichever ones you’d like with a couple of exceptions:
- This fieldset allows you to define validation rules with the
validate
option; it uses the same validation library and settings that Raven Forms does - This fieldset is used in the Control Panel to power the edit form for members, this is where the
type
is used for each field; displaying these fields on the front-end will be completely up to you and the HTML that you bring username
andpassword
are the fields used to log into the system, you should update the validation requirements and messages as you see fit, but you’ll need to have these fields for members to be able to log inroles
aren’t specifically required for use on the front-end, but users will need to have them to use the Control Panel (which requires theadmin
role)
For front-end use, this becomes the list of allowed fields in a form submission. When you save a member’s information, the member
forms will loop through this list of fields as ones that should be saved. Fields not found in this list will be ignored, as will fields in this list that weren’t submitted with the form. This feature lets you control the fields that each member’s content file contains.
Checking Values Without Saving Them Added in v1.7.2
Fields can now set the save_value
option (which is true
by default).
When this value is set to false
, the value of this field will still be validated when submitted through a member form, but its value will not be stored in the member’s data.
As an example, it makes sense to set password_confirmation
’s save_value
to false
, as we want to check its value, match it against password
, but we don’t want to save it in the member’s profile.
The show_password
Field
The list of default fields included with the member
tag includes a show_password
field.
Part of the addition of front-end member
functionality was updating how a member gets saved in the Control Panel.
The show_password
field is a helper for the Control Panel.
In the Control Panel, admins gets a full form of all fields that can be stored for a member, including the ability to reset a member’s password.
The show_password
field allows admins to save member information without having to reset the member’s password.
In the Control Panel, this field is essentially a button that will show the password
and password_confirmation
field when pressed, which will then force the admin to enter a new password for the member.
For backwards-compatibility purposes, it’s a good idea to keep this field in the member-fields list.
Feel free to set its save_value
of false
, but it’s needed if you’re going to be managing members through the control panel.
Member Login
Use the new {{ member:login_form }}
tag-pair to create a form that will log a user in (assuming they type in valid credentials). A simple sample use of this tag looks like this:
{{ member:login_form }}
<p>
<label for="username">Username:</label>
<input type="text" name="username" value="" id="username">
</p>
<p>
<label for="password">Password:</label>
<input type="password" name="password" value="" id="password">
</p>
<p>
<input type="submit" name="submit" value="Log In">
</p>
{{ /member:login_form }}
As you can see, we let you dictate the HTML here. The {{ member:login_form }}
and its closing tag will be transformed into <form>
and </form>
tags respectively. (Note that you can pass an attribute string into the attr
parameter to set a class and an ID on the form tag itself.) Inside, it’s all up to you.
This tag (and thus, form) is expecting you to submit two fields: username
and password
. They must be named this, and these will be the two variables used to determine if a member can sign in or not.
There are more tags and parameters that you can use to customize the form and display even further:
{{ member:login_form return="/member-home" allow_request_return="true" }}
{{ if error }}
<p class="error">{{ error }}</p>
{{ endif }}
<p>
<label for="username">Username:</label>
<input type="text" name="username" value="{{ old_values:username }}" id="username">
</p>
<p>
<label for="password">Password:</label>
<input type="password" name="password" value="" id="password">
</p>
<p>
<input type="submit" name="submit" value="Log In">
</p>
{{ /member:login_form }}
In this example we’re doing a couple of new things:
- We’ve added the
return
parameter, this gives us a last-minute chance to customize where members may be sent once they successfully log in - We’ve also added the
allow_request_return
parameter and set it totrue
, this gives us another way to tell the tag where to redirect a successful log in, more on that below - The
{{ error }}
tag will be populated with a notice that something’s gone wrong if it has - The
{{ old_values }}
tag is a list containing all of the previously submitted information which can then be used in your form, here we’re using{{ old_values:username }}
to get the submitted value ofusername
in case something goes wrong, note that we’re not doing this for password as to not display the information in the source of the page in plain-text, however, use these how you want
On a Successful Login
There are four values that {{ member:login_form }}
will use to determine where to send a successfully logged in member:
- If
allow_request_return
is set to true, it will look for thereturn
GET variable in the URL string and send the user there; this will be automatically appended by Statamic’s protect functionality if a user attempts to visit a page that requires them to be logged in - If that isn’t available, it will try to use the value you’ve set in the
return
parameter on the tag - If that isn’t available, it will try to use the value set to
member_home
in yourmember
config file - If that isn’t available, it will use your site’s root
Note that allow_request_return
is false
by default.
Member Registration
Use the new {{ member:register_form }}
tag-pair to create a form that will create a user in the system. An example of this tag looks like this:
{{ member:register_form }}
{{ if error }}
<p class="error">{{ error }}</p>
{{ endif }}
<p>
<label for="username">Username:</label>
<input type="text" name="username" value="{{ old_values:username }}" id="username">
{{ if field_errors:username }}
<br>
<small class="error">{{ field_errors:username }}</small>
{{ endif }}
</p>
<p>
<label for="email">Email:</label>
<input type="text" name="email" value="{{ old_values:email }}" id="email">
{{ if field_errors:email }}
<br>
<small class="error">{{ field_errors:email }}</small>
{{ endif }}
</p>
<p>
<label for="password">Password:</label>
<input type="password" name="password" value="" id="password">
{{ if field_errors:password }}
<br>
<small class="error">{{ field_errors:password }}</small>
{{ endif }}
</p>
<p>
<label for="password_confirmation">Password Again:</label>
<input type="password" name="password_confirmation" value="" id="password_confirmation">
{{ if field_errors:password_confirmation }}
<br>
<small class="error">{{ field_errors:password_confirmation }}</small>
{{ endif }}
</p>
<p>
<input type="submit" name="Submit" value="Sign Me Up">
</p>
{{ /member:register_form }}
Once again, we let you create your own HTML. The {{ member:register_form }}
tag and its closing counterpart will create the <form>
and </form>
tag respectively, and once again you can use the attr
parameter to set an attribute string to set the form’s class and ID.
Each of the field’s names within the form must match to a field in your fields.yaml
file. These are the only fields that will be accepted and saved to the member’s profile.
Setting New Member Roles
Most of the time, new members will need some roles assigned to them so that they can do different things on your site. You get to choose these roles with the new_member_roles
field in your member.yaml
config file. When a user successfully registers as a member, their account will automatically be assigned the roles in this list.
Remember, the admin
role can use the Control Panel. Assign that role carefully.
It’s best to remember that these are starting roles for the user. You can later either manually add roles to users in their files, update their account through the Control Panel, or have add-ons automatically add or remove roles as needed when members perform certain tasks.
If Errors Occur
If the user either misses a required field, or enters all of the fields but fills in some incorrectly (or any combination of these), they will be redirected back to this form and a couple of new tags will be available for use.
error
will state any general form errorsfield_errors
is a list of field-specific errors and corresponding messages, which can be set in thefields.yaml
fileold_values
is a list of the values submitted by the user so that you can re-populate the form
Users can keep submitting this form until they get it all right.
On Successful Submission
Once the user fills in everything correctly, there are a couple of parameters that can be set to alter where they’re taken from here:
auto_login
(which istrue
by default) will automatically log in the user as the member account they’ve just created- The same return system that
{{ member:login_form }}
uses
Once Logged In
Once a member is logged in, their attempts to view pages protected with _protect
will resolve differently. In addition to using the built in protect scheme, you can look for a logged-in member in your templates with a couple of automatically-made variables:
logged_in
will betrue
if the current user is logged incurrent_member
will be a list that contains the current member’s profile, includingis_[role]
checks
Update Member Profile
Providing a way to let members update their profile works similarly to how the {{ member:register_form }}
works. An example of the {{ member:profile_form }}
:
{{ member:profile_form }}
{{ if error }}
<p class="error">{{ error }}</p>
{{ elseif success }}
<p class="success">{{ success }}</p>
{{ endif }}
<p>
Username: {{ old_values:username }}
</p>
<p>
<label for="first_name">First Name:</label>
<input type="text" name="first_name" value="" id="first_name">
{{ if field_errors:first_name }}
<br>
<small class="error">{{ field_errors:first_name }}</small>
{{ endif }}
</p>
<p>
<label for="last_name">Last Name:</label>
<input type="text" name="last_name" value="" id="last_name">
{{ if field_errors:last_name }}
<br>
<small class="error">{{ field_errors:last_name }}</small>
{{ endif }}
</p>
<p>
<label for="email">Email:</label>
<input type="text" name="email" value="{{ old_values:email }}" id="email">
{{ if field_errors:email }}
<br>
<small class="error">{{ field_errors:email }}</small>
{{ endif }}
</p>
<p>
<input type="submit" name="Submit" value="Update Profile">
</p>
{{ /member:profile_form }}
As you should be getting used to be now, we let you write your own HTML. The {{ member:profile_form }}
tag and its closing counterpart will create the <form>
and </form>
tag respectively, and once again you can use the attr
parameter to set an attribute string to set the form’s class and ID.
Each of the field’s names within the form must match to a field in your fields.yaml
file. These are the only fields that will be accepted and saved to the member’s profile.
Using Old Values
This tag differs from the {{ member:register_form }}
tag in that the {{ old_values }}
tag will be available to you before the user submits anything. This will be filled with existing member data. As you can see in the example above, this lets us do two things:
- Pre-populate the form with the right information; if a user changes a field’s value to something and then attempts to save their profile, this tag will redirect them back to the form, and their invalid submission will now be in
{{ old_values }}
- We can display information about the user without necessarily doing that with an
input
tag, as we’re doing withusername
Field Errors
Once again, field errors will be available to you in the {{ field_errors }}
tag. You can check for their existence the same way you did on the registration form.
On Success
Like the other tags, {{ member:profile_form }}
has a return
attribute, but it isn’t as flexible as it is with other tags. If you include it as a parameter, the user will be redirected to the URL you specify upon success. If you don’t include it, the user will be redirected back to the current page. There are no options to use the return
GET variable, as updating a profile shouldn’t stand in the way of someone accessing a dynamic page.
You can use {{ success }}
like you do with {{ error }}
to display a success message above your form.
Displaying a Profile
If you’re just looking to display profile information and not edit it, you can continue to use the {{ member:profile }}
tag. You specify the username
parameter to say which member’s profile you want to look up, and between the tags you can use all of the fields in their stored profile. As of v1.8.1, you can alternatively specify the uid
parameter with a member’s unique ID.
For example:
{{ member:profile username="{segment_2}" }}
{{ if no_results }}
<p class="error">Could not find a profile for member <em>{{ segment_2|sanitize }}</em>.</p>
{{ else }}
<dl class="profile">
<dt>Username</dt>
<dd>{{ segment_2|sanitize }}</dd>
<dt>Full Name</dt>
<dd>{{ first_name }} {{ last_name }}</dd>
<dt>Email</dt>
<dd><a href="mailto:{{ email }}">{{ email }}</a></dd>
</dl>
{{ endif }}
{{ /member:profile }}
Members, Unique IDs, and Add-ons
Members are now each automatically assigned a unique ID that is stored within their profile (a field called _uid
). In your add-on files, you can access the unique ID from any Member
object, like this:
<?php
class Plugin_sample extends Plugin
{
public function index()
{
// check that someone is logged in
if (!Auth::isLoggedIn()) {
return false;
}
$member = Auth::getCurrentMember();
$uid = $member->getUID(); // this is the UID
}
}
?>
UID vs. Username
Add-ons that want to store information about members should always use the unique ID and never use the username.
A member’s username is the name of the YAML file that’s keeping that member’s information stored in the filesystem. Although this provides an easy way of making sure that all usernames will be unique (because you can’t have two files named the same thing), the username is too easy for admins (or members, if you let them) to change.
The problem with add-ons attaching information to a username is that once someone does change a member’s username, all of the connections linked to that username are broken. It would be impossible to automatically search through (third-party add-on) data to update the member’s username everywhere, and although we probably could fire a hook for such an event, this makes for a more complex machine than necessary. The more moving parts, the more chance for things to break. This is where the idea for the unique ID comes from.
A member’s _uid
field is checked for on every login. If it’s not found in the member’s profile (which usually happens because either the user account was created either by hand or it was created in a version before v1.7), it will be added. When you create a member through the {{ member:register_form }}
tag, the _uid
field will be created at the same time.
Every profile save, every login, and every member registration will ensure that this field is there. This means that as of v1.7, you can rely on every logged-in member having a _uid
field to which you can link things.
Resetting Passwords
As of 1.9, you can provide a way for your members to reset their own passwords.
Resetting a password is a 3 step process:
- Visit a forgot password page, where a member enters their username into a
{{ member:forgot_password_form }}
. - They will receive an email containing a link to a reset password page.
- On this page, they can enter a new password into a
{{ member:reset_password_form }}
.
The member has a set amount of time (defaulting to 20 minutes) to click the link in their email and reset the password. Once this time has elapsed, the link will become void. To change this length, adjust the reset_password_age_limit
variable in member.yaml
.
Workflow setup
Create two URLs: one for the {{ member:forgot_password_form }}
and one for the {{ member:reset_password_form }}
.
In your site navigation, direct users to the ‘forgot password’ template. You should never take your users directly to the ‘reset password’ page – they will access it through the link in the email.
You should specify the page containing the {{ member:reset_password_form }}
in your member.yaml
file using reset_password_url
– it defaults to /member/reset-password
.
In order for emails to be sent, you’ll need to set some email variables in your general site settings and your member.yaml
file.
General settings:
_email_sender
_email_handler
_email_handler_key
Member settings:
reset_password_subject
– The subject line of the emailreset_password_text_email
– The template containing your text version.reset_password_html_email
– The template containing your HTML version, if you want one
In your email templates, the {{ reset_url }}
variable is available to you.
note If your
forgot_password_form
renders nothing, you most likely haven’t set your template path.