From 711968df08b53cfe5f782caabbd5a1535d306e1a Mon Sep 17 00:00:00 2001
From: Craig Williams
Date: Wed, 9 Feb 2022 12:34:30 +0000
Subject: [PATCH] Allow members to leave organisations
---
assets/avsdev/js/pages/organisation.js | 2 +-
assets/avsdev/js/widgets/organisations.js | 25 ++++
locale/en_US/messages.php | 13 +-
routes/organisation-members.php | 7 ++
src/Controller/OrganisationController.php | 1 +
.../OrganisationMembersController.php | 113 ++++++++++++++++++
.../Seeds/OrganisationPermissions.php | 7 ++
.../confirm-leave-organisation.html.twig | 17 +++
templates/pages/organisation.html.twig | 9 +-
9 files changed, 189 insertions(+), 5 deletions(-)
create mode 100644 templates/modals/confirm-leave-organisation.html.twig
diff --git a/assets/avsdev/js/pages/organisation.js b/assets/avsdev/js/pages/organisation.js
index 02b3693..f365d09 100644
--- a/assets/avsdev/js/pages/organisation.js
+++ b/assets/avsdev/js/pages/organisation.js
@@ -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({
diff --git a/assets/avsdev/js/widgets/organisations.js b/assets/avsdev/js/widgets/organisations.js
index 77847d0..c189251 100644
--- a/assets/avsdev/js/widgets/organisations.js
+++ b/assets/avsdev/js/widgets/organisations.js
@@ -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();
diff --git a/locale/en_US/messages.php b/locale/en_US/messages.php
index b9b1300..a2198ac 100644
--- a/locale/en_US/messages.php
+++ b/locale/en_US/messages.php
@@ -27,6 +27,11 @@ return [
'EDIT' => 'Edit organistion',
'UPDATE' => 'Details updated for organistion {{name}}',
+ 'LEAVE' => 'Leave organisation',
+ 'LEAVE_CONFIRM' => 'Are you sure you want to leave the organisation {{name}}?',
+ 'LEAVE_YES' => 'Yes, leave organisation.',
+ 'LEAVE_SUCCESSFUL' => 'Successfully left organisation {{name}}',
+
'MERGE' => 'Merge organisation',
'MERGE_INFORM' => 'Select an organisation from the list below to merge organisation {{name}} into. All users of organisation {{name}} will be moved into the chosen organisation.',
'MERGE_SUCCESSFUL' => 'Successfully merged organisation {{source}} into organisation {{target}}.',
@@ -68,7 +73,9 @@ return [
'MERGE_INTO' => 'Merge into',
'MERGE_CANNOT_UNDONE' => 'This action cannot be undone.',
- 'SOURCE_SLUG' => 'Source Slug',
- 'TARGET_SLUG' => 'Target Slug',
- 'SLUG_NOT_IN_USE' => 'A {{slug}} slug does not exist',
+ 'SOURCE_SLUG' => 'Source Slug',
+ 'TARGET_SLUG' => 'Target Slug',
+ 'SLUG_NOT_IN_USE' => 'A {{slug}} slug does not exist',
+
+ 'LEAVE_CANNOT_UNDONE' => 'This action cannot be undone.',
];
diff --git a/routes/organisation-members.php b/routes/organisation-members.php
index b8b4a24..6d742e9 100644
--- a/routes/organisation-members.php
+++ b/routes/organisation-members.php
@@ -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());
\ No newline at end of file
diff --git a/src/Controller/OrganisationController.php b/src/Controller/OrganisationController.php
index 525c9de..b11058c 100644
--- a/src/Controller/OrganisationController.php
+++ b/src/Controller/OrganisationController.php
@@ -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'),
]);
}
diff --git a/src/Controller/OrganisationMembersController.php b/src/Controller/OrganisationMembersController.php
index b91e492..b84e7da 100644
--- a/src/Controller/OrganisationMembersController.php
+++ b/src/Controller/OrganisationMembersController.php
@@ -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.
diff --git a/src/Database/Seeds/OrganisationPermissions.php b/src/Database/Seeds/OrganisationPermissions.php
index f5ad470..66c8e0f 100644
--- a/src/Database/Seeds/OrganisationPermissions.php
+++ b/src/Database/Seeds/OrganisationPermissions.php
@@ -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,
]);
}
}
diff --git a/templates/modals/confirm-leave-organisation.html.twig b/templates/modals/confirm-leave-organisation.html.twig
new file mode 100644
index 0000000..a7091b5
--- /dev/null
+++ b/templates/modals/confirm-leave-organisation.html.twig
@@ -0,0 +1,17 @@
+{% extends "modals/modal.html.twig" %}
+
+{% block modal_title %}{{translate("ORGANISATION.LEAVE")}}{% endblock %}
+
+{% block modal_body %}
+
+{% endblock %}
diff --git a/templates/pages/organisation.html.twig b/templates/pages/organisation.html.twig
index 19121cc..3eb5b28 100644
--- a/templates/pages/organisation.html.twig
+++ b/templates/pages/organisation.html.twig
@@ -64,6 +64,12 @@
{% endif %}
{% block organisation_profile %}{% endblock %}
+ {% if checkAccess('leave_organisation') %}
+
+
+
+
+ {% endif %}
@@ -95,7 +101,8 @@
true, // deep extend
{
organisation_slug: "{{organisation.slug}}",
- delete_redirect: "{{delete_redirect}}"
+ delete_redirect: "{{delete_redirect}}",
+ leave_redirect: "{{leave_redirect}}"
},
page
);