Skip to content
This repository has been archived by the owner on Dec 9, 2024. It is now read-only.

Commit

Permalink
1.2.0: Users can now deactivate their own accounts.
Browse files Browse the repository at this point in the history
  • Loading branch information
daftspunk committed Dec 12, 2015
1 parent d9853e6 commit 2897367
Show file tree
Hide file tree
Showing 15 changed files with 285 additions and 56 deletions.
11 changes: 6 additions & 5 deletions Plugin.php
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ public function registerPermissions()
return [
'rainlab.users.access_users' => ['tab' => 'rainlab.user::lang.plugin.tab', 'label' => 'rainlab.user::lang.plugin.access_users'],
'rainlab.users.access_groups' => ['tab' => 'rainlab.user::lang.plugin.tab', 'label' => 'rainlab.user::lang.plugin.access_groups'],
'rainlab.users.access_settings' => ['tab' => 'rainlab.user::lang.plugin.tab', 'label' => 'rainlab.user::lang.plugin.access_settings']
'rainlab.users.access_settings' => ['tab' => 'rainlab.user::lang.plugin.tab', 'label' => 'rainlab.user::lang.plugin.access_settings']
];
}

Expand Down Expand Up @@ -91,10 +91,11 @@ public function registerSettings()
public function registerMailTemplates()
{
return [
'rainlab.user::mail.activate' => 'Activation email sent to new users.',
'rainlab.user::mail.welcome' => 'Welcome email sent when a user is activated.',
'rainlab.user::mail.restore' => 'Password reset instructions for front-end users.',
'rainlab.user::mail.new_user' => 'Sent to administrators when a new user joins.'
'rainlab.user::mail.activate' => 'Activation email sent to new users.',
'rainlab.user::mail.welcome' => 'Welcome email sent when a user is activated.',
'rainlab.user::mail.restore' => 'Password reset instructions for front-end users.',
'rainlab.user::mail.new_user' => 'Sent to administrators when a new user joins.',
'rainlab.user::mail.reactivate' => 'Notification for users who reactivate their account.',
];
}
}
56 changes: 55 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,44 @@
# Front-end user plugin

This plugin adds front-end user management features to [OctoberCMS](http://octobercms.com).
Front-end user management for October CMS.

## Managing users

Users are managed on the Users tab found in the back-end. Each user provides minimal data fields - **Name**, **Surname**, **Email** and **Password**. The Name can represent either the person's first name or their full name, making the Surname field optional, depending on the complexity of your site.

## Plugin settings

This plugin creates a Settings menu item, found by navigating to **Settings > Users > User settings**. This page allows the setting of common features, described in more detail below.

#### Registration

Registration to the site is allowed by default. If you are running a closed site, or need to temporarily disable registration, you may disable this feature by switching **Allow user registration** to the OFF setting.

#### Activation

Activation is a process of vetting a user who joins the site. By default, users are activated automatically when they register and an activated account is required to sign in.

The **Activation mode** specifies the activation workflow:

- **Automatic**: This mode will automatically activate a user when they first register. This is the same as disabling activation entirely and is the default setting.
- **User**: The user can activate their account by responding to a confirmation message sent to their nominated email address.
- **Administrator**: The user can only be activated by an administrator via the back-end area.

You can allow users to sign in without activating by switching **Sign in requires activation** to the OFF setting. This is useful for minimising friction when registering, however with this approach it is often a good idea to disable any "identity sensitive" features until the user has been activated, such as posting content. Alternatively, you could implement a grace period that deletes users (with sufficient warning!) who have not activated within a given period of time.

#### Sign in

By default a User will sign in to the site using their email address as a unique identifier. You may use a unique login name instead by changing the **Login attribute** value to Username. This will introduce a new field called **Username** for each user, allowing them to specify their own short name or alias for identification. Both the Email address and Username must be unique to the user.

If a user experiences too many failed sign in attempts, their account will be temporarily suspended for a period of time. This feature is enabled by default and will suspend an account for 15 minutes after 5 failed sign in attempts, for a given IP address. You may disable this feature by switching **Throttle attempts** to the OFF setting.

#### Notifications

When a user is first activated -- either by registration, email confirmation or administrator approval -- they are sent a welcome email. To disable the welcome email, select "Do not send a notification" from the **Welcome mail template** dropdown. The default message template used is `rainlab.user::mail.welcome` and you can customize this by selecting **Mail > Mail Templates** from the settings menu.

## Extended features

For extra functionality, consider also installing the [User Plus+ plugin](http://octobercms.com/plugin/rainlab-userplus) (`RainLab.UserPlus`).

## Session component

Expand Down Expand Up @@ -89,13 +127,15 @@ We can add any other additional fields here too, such as `phone`, `company`, etc
## Error handling

### Flash messages

This plugin makes use of October's [`Flash API`](http://octobercms.com/docs/markup/tag-flash). In order to display the error messages, you need to place the following snippet in your layout or page.

{% flash %}
<div class="alert alert-{{ type == 'error' ? 'danger' : type }}">{{ message }}</div>
{% endflash %}

### AJAX errors

The User plugin displays AJAX error messages in a simple ``alert()``-box by default. However, this might scare non-technical users. You can change the default behavior of an AJAX error from displaying an ``alert()`` message, like this:

<script>
Expand Down Expand Up @@ -125,3 +165,17 @@ Here is how you would override the `onSignin()` handler to log any error message
}

Here the local handler method will take priority over the **account** component's event handler. Then we simply inherit the logic by calling the parent handler manually, via the component object (`$this->account`).

## Events

This plugin will fire some global events that can be useful for interacting with other plugins.

- **rainlab.user.deactivate**: The user has opted-out of the site by deactivating their account. This should be used to disable any content the user may want removed.
- **rainlab.user.reactivate**: The user has reactivated their own account by signing back in. This should revive the users content on the site.

Here is an example of hooking an event:

Event::listen('rainlab.user.deactivate', function($user) {
// Hide all posts by the user
});

7 changes: 6 additions & 1 deletion classes/AuthManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ class AuthManager extends RainAuthManager

protected $userModel = 'RainLab\User\Models\User';

// protected $groupModel = 'RainLab\User\Models\Group';
protected $groupModel = 'RainLab\User\Models\UserGroup';

protected $throttleModel = 'RainLab\User\Models\Throttle';

Expand All @@ -21,4 +21,9 @@ public function init()
$this->requireActivation = UserSettings::get('require_activation', $this->requireActivation);
parent::init();
}

public function extendUserQuery($query)
{
$query->withTrashed();
}
}
55 changes: 45 additions & 10 deletions components/Account.php
Original file line number Diff line number Diff line change
Expand Up @@ -279,13 +279,36 @@ public function onUpdate()
Flash::success(post('flash', Lang::get('rainlab.user::lang.account.success_saved')));

/*
* Redirect to the intended page after successful update
* Redirect
*/
$redirectUrl = $this->pageUrl($this->property('redirect'))
?: $this->property('redirect');
if ($redirect = $this->makeRedirection()) {
return $redirect;
}
}

if ($redirectUrl = post('redirect', $redirectUrl)) {
return Redirect::to($redirectUrl);
/**
* Deactivate user
*/
public function onDeactivate()
{
if (!$user = $this->user()) {
return;
}

if (!$user->checkHashValue('password', post('password'))) {
throw new ValidationException(['password' => Lang::get('rainlab.user::lang.account.invalid_deactivation_pass')]);
}

$user->delete();
Auth::logout();

Flash::success(post('flash', Lang::get('rainlab.user::lang.account.success_deactivation')));

/*
* Redirect
*/
if ($redirect = $this->makeRedirection()) {
return $redirect;
}
}

Expand Down Expand Up @@ -316,11 +339,8 @@ public function onSendActivationEmail()
/*
* Redirect
*/
$redirectUrl = $this->pageUrl($this->property('redirect'))
?: $this->property('redirect');

if ($redirectUrl = post('redirect', $redirectUrl)) {
return Redirect::to($redirectUrl);
if ($redirect = $this->makeRedirection()) {
return $redirect;
}
}

Expand Down Expand Up @@ -348,4 +368,19 @@ protected function sendActivationEmail($user)
});
}

/**
* Redirect to the intended page after successful update, sign in or registration.
* The URL can come from the "redirect" property or the "redirect" postback value.
* @return mixed
*/
protected function makeRedirection()
{
$redirectUrl = $this->pageUrl($this->property('redirect'))
?: $this->property('redirect');

if ($redirectUrl = post('redirect', $redirectUrl)) {
return Redirect::to($redirectUrl);
}
}

}
28 changes: 28 additions & 0 deletions components/account/deactivate_link.htm
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<a
href="javascript:;"
onclick="$('#accountDeactivateForm').toggle()">
Deactivate account
</a>

<div id="accountDeactivateForm" style="display: none">
{{ form_ajax('onDeactivate') }}
<hr />
<h3>Deactivate your account?</h3>
<p>
Your account will be disabled and your details remove from the site.
You can reactivate your account any time by signing back in.
</p>
<div class="form-group">
<label for="accountDeletePassword">To continue, please enter your password:</label>
<input name="password" type="password" class="form-control" id="accountDeletePassword" />
</div>
<button type="submit" class="btn btn-danger">
Confirm Deactivate Account
</button>
<a
href="javascript:;"
onclick="$('#accountDeactivateForm').toggle()">
I changed my mind
</a>
{{ form_close() }}
</div>
2 changes: 2 additions & 0 deletions components/account/default.htm
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,6 @@ <h3>Register</h3>

{% partial __SELF__ ~ '::update' %}

{% partial __SELF__ ~ '::deactivate_link' %}

{% endif %}
40 changes: 32 additions & 8 deletions controllers/Users.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,19 +34,27 @@ public function __construct()
}

/**
* Manually activate a user
* {@inheritDoc}
*/
public function update_onActivate($recordId = null)
public function listInjectRowClass($record, $definition = null)
{
$model = $this->formFindModelObject($recordId);
if ($record->trashed()) {
return 'strike';
}

$model->attemptActivation($model->activation_code);
if (!$record->is_activated) {
return 'disabled';
}
}

Flash::success(Lang::get('rainlab.user::lang.users.activated_success'));
public function listExtendQuery($query)
{
$query->withTrashed();
}

if ($redirect = $this->makeRedirect('update', $model)) {
return $redirect;
}
public function formExtendQuery($query)
{
$query->withTrashed();
}

/**
Expand All @@ -65,6 +73,22 @@ public function formExtendFields($form)
}
}

/**
* Manually activate a user
*/
public function update_onActivate($recordId = null)
{
$model = $this->formFindModelObject($recordId);

$model->attemptActivation($model->activation_code);

Flash::success(Lang::get('rainlab.user::lang.users.activated_success'));

if ($redirect = $this->makeRedirect('update', $model)) {
return $redirect;
}
}

/**
* Deleted checked users
*/
Expand Down
21 changes: 21 additions & 0 deletions controllers/users/_hint_activate.htm
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<div class="layout-row min-size">
<div class="callout callout-warning">
<div class="header">
<i class="icon-warning"></i>
<h3><?= e(trans('rainlab.user::lang.users.activate_warning_title')) ?></h3>
<p><?= e(trans('rainlab.user::lang.users.activate_warning_desc')) ?></p>
</div>
<div class="content">
<div class="loading-indicator-container">
<button
type="button"
class="btn btn-warning"
data-request="onActivate"
data-load-indicator="<?= e(trans('rainlab.user::lang.users.activating')) ?>"
data-request-confirm="<?= e(trans('rainlab.user::lang.users.activate_confirm')) ?>">
<?= e(trans('rainlab.user::lang.users.active_manually')) ?>
</button>
</div>
</div>
</div>
</div>
9 changes: 9 additions & 0 deletions controllers/users/_hint_trashed.htm
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<div class="layout-row min-size">
<div class="callout callout-danger">
<div class="header">
<i class="icon-minus-circle"></i>
<h3><?= e(trans('rainlab.user::lang.users.trashed_hint_title')) ?></h3>
<p><?= e(trans('rainlab.user::lang.users.trashed_hint_desc')) ?></p>
</div>
</div>
</div>
26 changes: 4 additions & 22 deletions controllers/users/update.htm
Original file line number Diff line number Diff line change
Expand Up @@ -9,28 +9,10 @@

<?php Block::put('form-contents') ?>

<?php if (!$formModel->is_activated): ?>
<div class="layout-row min-size">
<div class="callout callout-warning">
<div class="header">
<i class="icon-warning"></i>
<h3><?= e(trans('rainlab.user::lang.users.activate_warning_title')) ?></h3>
<p><?= e(trans('rainlab.user::lang.users.activate_warning_desc')) ?></p>
</div>
<div class="content">
<div class="loading-indicator-container">
<button
type="button"
class="btn btn-warning"
data-request="onActivate"
data-load-indicator="<?= e(trans('rainlab.user::lang.users.activating')) ?>"
data-request-confirm="<?= e(trans('rainlab.user::lang.users.activate_confirm')) ?>">
<?= e(trans('rainlab.user::lang.users.active_manually')) ?>
</button>
</div>
</div>
</div>
</div>
<?php if ($formModel->trashed()): ?>
<?= $this->makePartial('hint_trashed') ?>
<?php elseif (!$formModel->is_activated): ?>
<?= $this->makePartial('hint_activate') ?>
<?php endif ?>

<div class="layout-row min-size">
Expand Down
6 changes: 5 additions & 1 deletion lang/en/lang.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
'new_user' => 'New User',
'list_title' => 'Manage Users',
'activating' => 'Activating...',
'trashed_hint_title' => 'User has deactivated their account',
'trashed_hint_desc' => 'This user has deactivated their account and no longer wants to appear on the site. They can reactivate their account at any time by signing back in.',
'activate_warning_title' => 'User not activated!',
'activate_warning_desc' => 'This user has not been activated and will be unable to sign in.',
'activate_confirm' => 'Do you really want to activate this user?',
Expand Down Expand Up @@ -109,9 +111,11 @@
'redirect_to_desc' => 'Page name to redirect to after update, sign in or registration.',
'code_param' => 'Activation Code Param',
'code_param_desc' => 'The page URL parameter used for the registration activation code',
'invalid_activation_code' => 'Invalid activation code supplied',
'invalid_user' => 'A user was not found with the given credentials.',
'invalid_activation_code' => 'Invalid activation code supplied.',
'invalid_deactivation_pass' => 'The password you entered was invalid.',
'success_activation' => 'Successfully activated your account.',
'success_deactivation' => 'Successfully deactivated your account. Sorry to see you go!',
'success_saved' => 'Settings successfully saved!',
'login_first' => 'You must be logged in first!',
'already_active' => 'Your account is already activated!',
Expand Down
Loading

0 comments on commit 2897367

Please sign in to comment.