Allow members to leave organisations

This commit is contained in:
2022-02-09 12:34:30 +00:00
parent 00128aff5d
commit 711968df08
9 changed files with 189 additions and 5 deletions

View File

@@ -9,7 +9,7 @@
$(document).ready(function() {
// Control buttons
bindOrganisationButtons($("#view-organisation"), { delete_redirect: page.delete_redirect });
bindOrganisationButtons($("#view-organisation"), { delete_redirect: page.delete_redirect, leave_redirect: page.leave_redirect });
// Table of users in this organisation
$("#widget-organisation-members").ufTable({

View File

@@ -133,6 +133,31 @@ function bindOrganisationButtons(el, options) {
});
});
// Leave organisation button
el.find('.js-organisation-leave').click(function(e) {
e.preventDefault();
$("body").ufModal({
sourceUrl: site.uri.public + "/modals/organisations/o/" + page.organisation_slug + "/members/confirm-leave",
ajaxParams: {
slug: $(this).data('slug')
},
msgTarget: $("#alerts-page")
});
$("body").on('renderSuccess.ufModal', function() {
var modal = $(this).ufModal('getModal');
var form = modal.find('.js-form');
form.ufForm()
.on("submitSuccess.ufForm", function() {
// Navigate or reload page on success
if (options.leave_redirect) window.location.href = options.leave_redirect;
else window.location.reload();
});
});
});
// Delete organisation button
el.find('.js-organisation-delete').click(function(e) {
e.preventDefault();

View File

@@ -27,6 +27,11 @@ return [
'EDIT' => 'Edit organistion',
'UPDATE' => 'Details updated for organistion <strong>{{name}}</strong>',
'LEAVE' => 'Leave organisation',
'LEAVE_CONFIRM' => 'Are you sure you want to leave the organisation <strong>{{name}}</strong>?',
'LEAVE_YES' => 'Yes, leave organisation.',
'LEAVE_SUCCESSFUL' => 'Successfully left organisation <strong>{{name}}</strong>',
'MERGE' => 'Merge organisation',
'MERGE_INFORM' => 'Select an organisation from the list below to merge organisation <strong>{{name}}</strong> into. All users of organisation <strong>{{name}}</strong> will be moved into the chosen organisation.',
'MERGE_SUCCESSFUL' => 'Successfully merged organisation <strong>{{source}}</strong> into organisation <strong>{{target}}</strong>.',
@@ -71,4 +76,6 @@ return [
'SOURCE_SLUG' => 'Source Slug',
'TARGET_SLUG' => 'Target Slug',
'SLUG_NOT_IN_USE' => 'A <strong>{{slug}}</strong> slug does not exist',
'LEAVE_CANNOT_UNDONE' => 'This action cannot be undone.',
];

View File

@@ -14,4 +14,11 @@ use UserFrosting\Sprinkle\Core\Util\NoCache;
*/
$app->group('/api/organisations/o/{slug}/members', function () {
$this->get('', 'UserFrosting\Sprinkle\Organisations\Controller\OrganisationMembersController:getList');
$this->delete('', 'UserFrosting\Sprinkle\Organisations\Controller\OrganisationMembersController:leave');
})->add('authGuard')->add(new NoCache());
$app->group('/modals/organisations/o/{slug}/members', function () {
$this->get('/confirm-leave', 'UserFrosting\Sprinkle\Organisations\Controller\OrganisationMembersController:getModalConfirmLeave');
})->add('authGuard')->add(new NoCache());

View File

@@ -836,6 +836,7 @@ class OrganisationController extends SimpleController
'fields' => $fields,
'tools' => $editButtons,
'delete_redirect' => $this->ci->router->pathFor('uri_organisations'),
'leave_redirect' => $this->ci->router->pathFor('dashboard'),
]);
}

View File

@@ -29,6 +29,75 @@ use UserFrosting\Support\Exception\NotFoundException;
*/
class OrganisationMembersController extends SimpleController
{
/**
* Processes the request to leave an organisation.
*
* Removes a member from the specified organisation.
* Before doing so, checks that:
* 1. The user has permission to leave this organisation;
* 2. The submitted data is valid.
* This route requires authentication.
*
* Request type: DELETE
*
* @param Request $request
* @param Response $response
* @param array $args
*
* @throws NotFoundException If organisation is not found
* @throws ForbiddenException If user is not authorized to access page
* @throws BadRequestException
*/
public function leave(Request $request, Response $response, $args)
{
$organisation = $this->getOrganisationFromParams($args);
// If the organisation doesn't exist, return 404
if (!$organisation) {
throw new NotFoundException();
}
/** @var \UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager $authorizer */
$authorizer = $this->ci->authorizer;
/** @var \UserFrosting\Sprinkle\Account\Database\Models\Interfaces\UserInterface $currentUser */
$currentUser = $this->ci->currentUser;
// Access-controlled page
if (!$authorizer->checkAccess($currentUser, 'leave_organisation', [
'organisation' => $organisation,
])) {
throw new ForbiddenException();
}
/** @var \UserFrosting\Support\Repository\Repository $config */
$config = $this->ci->config;
/** @var \UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */
$classMapper = $this->ci->classMapper;
// Begin transaction - DB will be rolled back if an exception occurs
Capsule::transaction(function () use ($organisation, $currentUser) {
$currentUser->organisations()->detach($organisation->id);
// Create activity record
$this->ci->userActivityLogger->info("User {$currentUser->user_name} left organisation {$organisation->name}.", [
'type' => 'leave_organisation',
'user_id' => $currentUser->id,
]);
});
/** @var \UserFrosting\Sprinkle\Core\Alert\AlertStream $ms */
$ms = $this->ci->alerts;
$ms->addMessageTranslated('success', 'ORGANISATION.LEAVE_SUCCESSFUL', [
'name' => $organisationName,
]);
return $response->withJson([], 200);
}
/**
* Returns a list of organisation members.
*
@@ -82,6 +151,50 @@ class OrganisationMembersController extends SimpleController
return $sprunje->toResponse($response);
}
/**
* Get leave confirmation modal.
*
* @param Request $request
* @param Response $response
* @param array $args
*
* @throws NotFoundException If organisation is not found
* @throws ForbiddenException If user is not authorized to access page
* @throws BadRequestException
*/
public function getModalConfirmLeave(Request $request, Response $response, $args)
{
$organisation = $this->getOrganisationFromParams($args);
// If the organisation no longer exists, forward to main organisation listing page
if (!$organisation) {
throw new NotFoundException();
}
/** @var \UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager $authorizer */
$authorizer = $this->ci->authorizer;
/** @var \UserFrosting\Sprinkle\Account\Database\Models\Interfaces\UserInterface $currentUser */
$currentUser = $this->ci->currentUser;
// Access-controlled page
if (!$authorizer->checkAccess($currentUser, 'leave_organisation', [
'organisation' => $organisation,
])) {
throw new ForbiddenException();
}
/** @var \UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */
$classMapper = $this->ci->classMapper;
return $this->ci->view->render($response, 'modals/confirm-leave-organisation.html.twig', [
'organisation' => $organisation,
'form' => [
'action' => "api/organisations/o/{$organisation->slug}/members",
],
]);
}
/**
* Get organisation from params.

View File

@@ -75,6 +75,12 @@ class OrganisationPermissions extends BaseSeed
'conditions' => 'always()',
'description' => 'Merge two organisations together, including all the members.',
]),
'leave_organisation' => new Permission([
'slug' => 'leave_organisation',
'name' => 'Leave organisation',
'conditions' => 'always()',
'description' => 'Allows members to leave organisations.',
]),
'delete_organisation' => new Permission([
'slug' => 'delete_organisation',
'name' => 'Delete organisation',
@@ -163,6 +169,7 @@ class OrganisationPermissions extends BaseSeed
$roleUser->permissions()->syncWithoutDetaching([
$permissions['uri_organisation_own']->id,
$permissions['view_organisation_field_own']->id,
$permissions['leave_organisation']->id,
]);
}
}

View File

@@ -0,0 +1,17 @@
{% extends "modals/modal.html.twig" %}
{% block modal_title %}{{translate("ORGANISATION.LEAVE")}}{% endblock %}
{% block modal_body %}
<form class="js-form" method="delete" action="{{site.uri.public}}/{{form.action}}">
{% include "forms/csrf.html.twig" %}
<div class="js-form-alerts">
</div>
<h4>{{translate("ORGANISATION.LEAVE_CONFIRM", {name: organisation.name})}}<br><small>{{translate("LEAVE_CANNOT_UNDONE")}}</small></h4>
<br>
<div class="btn-group-action">
<button type="submit" class="btn btn-danger btn-lg btn-block">{{translate("ORGANISATION.LEAVE_YES")}}</button>
<button type="button" class="btn btn-default btn-lg btn-block" data-dismiss="modal">{{translate("CANCEL")}}</button>
</div>
</form>
{% endblock %}

View File

@@ -64,6 +64,12 @@
</p>
{% endif %}
{% block organisation_profile %}{% endblock %}
{% if checkAccess('leave_organisation') %}
<hr>
<div class="text-center">
<button type="button" class="btn btn-danger js-organisation-leave">{{translate('ORGANISATION.LEAVE')}}</button>
</div>
{% endif %}
</div>
</div>
</div>
@@ -95,7 +101,8 @@
true, // deep extend
{
organisation_slug: "{{organisation.slug}}",
delete_redirect: "{{delete_redirect}}"
delete_redirect: "{{delete_redirect}}",
leave_redirect: "{{leave_redirect}}"
},
page
);