Revamped the organisation approvals process & refactored some files

This commit is contained in:
2022-02-15 15:26:56 +00:00
parent e029728d69
commit cd8a16f4a8
10 changed files with 643 additions and 668 deletions

View File

@@ -50,22 +50,26 @@ class OrganisationController extends SimpleController
*/ */
public function create(Request $request, Response $response, $args) public function create(Request $request, Response $response, $args)
{ {
// Get POST parameters: name, slug, icon, description
$params = $request->getParsedBody();
/** @var \UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager $authorizer */ /** @var \UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager $authorizer */
$authorizer = $this->ci->authorizer; $authorizer = $this->ci->authorizer;
/** @var \UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */
$classMapper = $this->ci->classMapper;
/** @var \UserFrosting\Sprinkle\Account\Database\Models\Interfaces\UserInterface $currentUser */ /** @var \UserFrosting\Sprinkle\Account\Database\Models\Interfaces\UserInterface $currentUser */
$currentUser = $this->ci->currentUser; $currentUser = $this->ci->currentUser;
/** @var \UserFrosting\Sprinkle\Core\Alert\AlertStream $ms */
$ms = $this->ci->alerts;
// Access-controlled page // Access-controlled page
if (!$authorizer->checkAccess($currentUser, 'create_organisation')) { if (!$authorizer->checkAccess($currentUser, 'create_organisation')) {
throw new ForbiddenException(); throw new ForbiddenException();
} }
/** @var \UserFrosting\Sprinkle\Core\Alert\AlertStream $ms */ // Get POST parameters: name, slug, icon, description
$ms = $this->ci->alerts; $params = $request->getParsedBody();
// Load the request schema // Load the request schema
$schema = new RequestSchema('schema://requests/organisation/create.yaml'); $schema = new RequestSchema('schema://requests/organisation/create.yaml');
@@ -83,9 +87,6 @@ class OrganisationController extends SimpleController
$error = true; $error = true;
} }
/** @var \UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */
$classMapper = $this->ci->classMapper;
// Check if name or slug already exists // Check if name or slug already exists
if ($classMapper->getClassMapping('organisation')::findUnique($data['name'], 'name')) { if ($classMapper->getClassMapping('organisation')::findUnique($data['name'], 'name')) {
$ms->addMessageTranslated('danger', 'ORGANISATION.NAME.IN_USE', $data); $ms->addMessageTranslated('danger', 'ORGANISATION.NAME.IN_USE', $data);
@@ -102,6 +103,7 @@ class OrganisationController extends SimpleController
} }
$data['flag_approved'] = 1; $data['flag_approved'] = 1;
$data['registrant_id'] = $currentUser->id;
// All checks passed! log events/activities and create organisation // All checks passed! log events/activities and create organisation
// Begin transaction - DB will be rolled back if an exception occurs // Begin transaction - DB will be rolled back if an exception occurs
@@ -139,6 +141,13 @@ class OrganisationController extends SimpleController
*/ */
public function getInfo(Request $request, Response $response, array $args) public function getInfo(Request $request, Response $response, array $args)
{ {
/** @var \UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager $authorizer */
$authorizer = $this->ci->authorizer;
/** @var \UserFrosting\Sprinkle\Account\Database\Models\Interfaces\UserInterface $currentUser */
$currentUser = $this->ci->currentUser;
$organisation = $this->getOrganisationFromParams($args); $organisation = $this->getOrganisationFromParams($args);
// If the organisation doesn't exist, return 404 // If the organisation doesn't exist, return 404
@@ -146,21 +155,6 @@ class OrganisationController extends SimpleController
throw new NotFoundException(); throw new NotFoundException();
} }
/** @var \UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */
$classMapper = $this->ci->classMapper;
// Join organisation's most recent activity
$organisation = $classMapper->createInstance('organisation')
->where('slug', $organisation->slug)
->joinMemberCounts()
->first();
/** @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 // Access-controlled page
if (!$authorizer->checkAccess($currentUser, 'uri_organisation', [ if (!$authorizer->checkAccess($currentUser, 'uri_organisation', [
'organisation' => $organisation, 'organisation' => $organisation,
@@ -168,10 +162,11 @@ class OrganisationController extends SimpleController
throw new ForbiddenException(); throw new ForbiddenException();
} }
// Join organisation's member counts
$organisation = $organisation->joinMemberCounts()->first();
$result = $organisation->toArray(); $result = $organisation->toArray();
// Be careful how you consume this data - it has not been escaped and contains untrusted user-supplied content.
// For example, if you plan to insert it into an HTML DOM, you must escape it on the client side (or use client-side templating).
return $response->withJson($result, 200, JSON_PRETTY_PRINT); return $response->withJson($result, 200, JSON_PRETTY_PRINT);
} }
@@ -197,6 +192,19 @@ class OrganisationController extends SimpleController
*/ */
public function update(Request $request, Response $response, $args) public function update(Request $request, Response $response, $args)
{ {
/** @var \UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager $authorizer */
$authorizer = $this->ci->authorizer;
/** @var \UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */
$classMapper = $this->ci->classMapper;
/** @var \UserFrosting\Sprinkle\Account\Database\Models\Interfaces\UserInterface $currentUser */
$currentUser = $this->ci->currentUser;
/** @var \UserFrosting\Sprinkle\Core\Alert\AlertStream $ms */
$ms = $this->ci->alerts;
// Get the organisation based on slug in URL // Get the organisation based on slug in URL
$organisation = $this->getOrganisationFromParams($args); $organisation = $this->getOrganisationFromParams($args);
@@ -207,9 +215,6 @@ class OrganisationController extends SimpleController
// Get PUT parameters: (name, slug, icon, description) // Get PUT parameters: (name, slug, icon, description)
$params = $request->getParsedBody(); $params = $request->getParsedBody();
/** @var \UserFrosting\Sprinkle\Core\Alert\AlertStream $ms */
$ms = $this->ci->alerts;
// Load the request schema // Load the request schema
$schema = new RequestSchema('schema://requests/organisation/edit-info.yaml'); $schema = new RequestSchema('schema://requests/organisation/edit-info.yaml');
@@ -232,12 +237,6 @@ class OrganisationController extends SimpleController
$fieldNames[] = $name; $fieldNames[] = $name;
} }
/** @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 resource - check that currentUser has permission to edit submitted fields for this organisation // Access-controlled resource - check that currentUser has permission to edit submitted fields for this organisation
if (!$authorizer->checkAccess($currentUser, 'update_organisation_field', [ if (!$authorizer->checkAccess($currentUser, 'update_organisation_field', [
'organisation' => $organisation, 'organisation' => $organisation,
@@ -246,14 +245,11 @@ class OrganisationController extends SimpleController
throw new ForbiddenException(); throw new ForbiddenException();
} }
/** @var \UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */
$classMapper = $this->ci->classMapper;
// Check if name or slug already exists // Check if name or slug already exists
if ( if (
isset($data['name']) && isset($data['name']) &&
$data['name'] != $organisation->name && $data['name'] != $organisation->name &&
$classMapper->getClassMapping('organisation')::where('name', $data['name'])->first() $classMapper->getClassMapping('organisation')::findUnique($data['name'], 'name')
) { ) {
$ms->addMessageTranslated('danger', 'ORGANISATION.NAME.IN_USE', $data); $ms->addMessageTranslated('danger', 'ORGANISATION.NAME.IN_USE', $data);
$error = true; $error = true;
@@ -262,7 +258,7 @@ class OrganisationController extends SimpleController
if ( if (
isset($data['slug']) && isset($data['slug']) &&
$data['slug'] != $organisation->slug && $data['slug'] != $organisation->slug &&
$classMapper->getClassMapping('organisation')::where('slug', $data['slug'])->first() $classMapper->getClassMapping('organisation')::findUnique($data['slug'], 'slug')
) { ) {
$ms->addMessageTranslated('danger', 'ORGANISATION.SLUG.IN_USE', $data); $ms->addMessageTranslated('danger', 'ORGANISATION.SLUG.IN_USE', $data);
$error = true; $error = true;
@@ -319,6 +315,24 @@ class OrganisationController extends SimpleController
*/ */
public function merge(Request $request, Response $response, $args) public function merge(Request $request, Response $response, $args)
{ {
/** @var \UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager $authorizer */
$authorizer = $this->ci->authorizer;
/** @var \UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */
$classMapper = $this->ci->classMapper;
/** @var \UserFrosting\Sprinkle\Account\Database\Models\Interfaces\UserInterface $currentUser */
$currentUser = $this->ci->currentUser;
/** @var \UserFrosting\Sprinkle\Core\Alert\AlertStream $ms */
$ms = $this->ci->alerts;
// Access-controlled page
if (!$authorizer->checkAccess($currentUser, 'merge_organisations')) {
throw new ForbiddenException();
}
// Get POST parameters // Get POST parameters
$params = $request->getParsedBody(); $params = $request->getParsedBody();
@@ -329,53 +343,26 @@ class OrganisationController extends SimpleController
$transformer = new RequestDataTransformer($schema); $transformer = new RequestDataTransformer($schema);
$data = $transformer->transform($params); $data = $transformer->transform($params);
// Validate, and throw exception on validation errors. // Validate, and return bad request on validation errors.
$validator = new ServerSideValidator($schema, $this->ci->translator); $validator = new ServerSideValidator($schema, $this->ci->translator);
if (!$validator->validate($data)) { if (!$validator->validate($data)) {
$e = new BadRequestException(); $ms->addValidationErrors($validator);
return $response->withJson([], 400);
foreach ($validator->errors() as $idx => $field) {
foreach ($field as $eidx => $error) {
$e->addUserMessage($error);
}
}
throw $e;
} }
/** @var \UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */
$classMapper = $this->ci->classMapper;
// Get the organisations // Get the organisations
$source = $classMapper->getClassMapping('organisation')::where('slug', $data['source_slug'])->first(); $source = $classMapper->getClassMapping('organisation')::findUnique($data['source_slug'], 'slug', false);
$target = $classMapper->getClassMapping('organisation')::where('slug', $data['target_slug'])->first(); $target = $classMapper->getClassMapping('organisation')::findUnique($data['target_slug'], 'slug', false);
// If a organisation doesn't exist, return 404 // If a organisation doesn't exist, return 404
if (!$source || !$target) { if (!$source || !$target) {
throw new BadRequestException(); 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, 'merge_organisations')) {
throw new ForbiddenException();
}
/** @var \UserFrosting\Support\Repository\Repository $config */
$config = $this->ci->config;
/** @var \UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */
$classMapper = $this->ci->classMapper;
$sourceName = $source->name;
// Begin transaction - DB will be rolled back if an exception occurs // Begin transaction - DB will be rolled back if an exception occurs
Capsule::transaction(function () use ($source, $sourceName, $target, $currentUser) { Capsule::transaction(function () use ($source, $target, $currentUser) {
$sourceName = $source->name;
$this->ci->get('organisation.beforeMerge')($source, $target); $this->ci->get('organisation.beforeMerge')($source, $target);
$source->beforeMerge($target, ['currentUser' => $currentUser]); $source->beforeMerge($target, ['currentUser' => $currentUser]);
@@ -390,9 +377,6 @@ class OrganisationController extends SimpleController
]); ]);
}); });
/** @var \UserFrosting\Sprinkle\Core\Alert\AlertStream $ms */
$ms = $this->ci->alerts;
$ms->addMessageTranslated('success', 'ORGANISATION.MERGE_SUCCESSFUL', [ $ms->addMessageTranslated('success', 'ORGANISATION.MERGE_SUCCESSFUL', [
'source' => $sourceName, 'source' => $sourceName,
'target' => $target->name, 'target' => $target->name,
@@ -423,6 +407,16 @@ class OrganisationController extends SimpleController
*/ */
public function delete(Request $request, Response $response, $args) public function delete(Request $request, Response $response, $args)
{ {
/** @var \UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager $authorizer */
$authorizer = $this->ci->authorizer;
/** @var \UserFrosting\Sprinkle\Account\Database\Models\Interfaces\UserInterface $currentUser */
$currentUser = $this->ci->currentUser;
/** @var \UserFrosting\Sprinkle\Core\Alert\AlertStream $ms */
$ms = $this->ci->alerts;
$organisation = $this->getOrganisationFromParams($args); $organisation = $this->getOrganisationFromParams($args);
// If the organisation doesn't exist, return 404 // If the organisation doesn't exist, return 404
@@ -430,12 +424,6 @@ class OrganisationController extends SimpleController
throw new NotFoundException(); 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 // Access-controlled page
if (!$authorizer->checkAccess($currentUser, 'delete_organisation', [ if (!$authorizer->checkAccess($currentUser, 'delete_organisation', [
'organisation' => $organisation, 'organisation' => $organisation,
@@ -443,16 +431,11 @@ class OrganisationController extends SimpleController
throw new ForbiddenException(); throw new ForbiddenException();
} }
/** @var \UserFrosting\Support\Repository\Repository $config */
$config = $this->ci->config;
/** @var \UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */
$classMapper = $this->ci->classMapper;
$organisationName = $organisation->name;
// Begin transaction - DB will be rolled back if an exception occurs // Begin transaction - DB will be rolled back if an exception occurs
Capsule::transaction(function () use ($organisation, $organisationName, $currentUser) { Capsule::transaction(function () use ($organisation, $currentUser) {
$organisationName = $organisation->name;
// Delete the organisation (soft)
$organisation->delete(); $organisation->delete();
unset($organisation); unset($organisation);
@@ -463,9 +446,6 @@ class OrganisationController extends SimpleController
]); ]);
}); });
/** @var \UserFrosting\Sprinkle\Core\Alert\AlertStream $ms */
$ms = $this->ci->alerts;
$ms->addMessageTranslated('success', 'ORGANISATION.DELETION_SUCCESSFUL', [ $ms->addMessageTranslated('success', 'ORGANISATION.DELETION_SUCCESSFUL', [
'name' => $organisationName, 'name' => $organisationName,
]); ]);
@@ -495,6 +475,16 @@ class OrganisationController extends SimpleController
*/ */
public function deletePermenent(Request $request, Response $response, $args) public function deletePermenent(Request $request, Response $response, $args)
{ {
/** @var \UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager $authorizer */
$authorizer = $this->ci->authorizer;
/** @var \UserFrosting\Sprinkle\Account\Database\Models\Interfaces\UserInterface $currentUser */
$currentUser = $this->ci->currentUser;
/** @var \UserFrosting\Sprinkle\Core\Alert\AlertStream $ms */
$ms = $this->ci->alerts;
$organisation = $this->getOrganisationFromParams($args, true); $organisation = $this->getOrganisationFromParams($args, true);
// If the organisation doesn't exist, return 404 // If the organisation doesn't exist, return 404
@@ -502,12 +492,6 @@ class OrganisationController extends SimpleController
throw new NotFoundException(); 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 // Access-controlled page
if (!$authorizer->checkAccess($currentUser, 'permenent_delete_organisation', [ if (!$authorizer->checkAccess($currentUser, 'permenent_delete_organisation', [
'organisation' => $organisation, 'organisation' => $organisation,
@@ -515,16 +499,11 @@ class OrganisationController extends SimpleController
throw new ForbiddenException(); throw new ForbiddenException();
} }
/** @var \UserFrosting\Support\Repository\Repository $config */
$config = $this->ci->config;
/** @var \UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */
$classMapper = $this->ci->classMapper;
$organisationName = $organisation->name; $organisationName = $organisation->name;
// Begin transaction - DB will be rolled back if an exception occurs // Begin transaction - DB will be rolled back if an exception occurs
Capsule::transaction(function () use ($organisation, $organisationName, $currentUser) { Capsule::transaction(function () use ($organisation, $organisationName, $currentUser) {
// Delete the organisation (HARD)
$organisation->delete(true); $organisation->delete(true);
unset($organisation); unset($organisation);
@@ -535,9 +514,6 @@ class OrganisationController extends SimpleController
]); ]);
}); });
/** @var \UserFrosting\Sprinkle\Core\Alert\AlertStream $ms */
$ms = $this->ci->alerts;
$ms->addMessageTranslated('success', 'ORGANISATION.PERMENENT_DELETION_SUCCESSFUL', [ $ms->addMessageTranslated('success', 'ORGANISATION.PERMENENT_DELETION_SUCCESSFUL', [
'name' => $organisationName, 'name' => $organisationName,
]); ]);
@@ -565,6 +541,16 @@ class OrganisationController extends SimpleController
*/ */
public function restore(Request $request, Response $response, $args) public function restore(Request $request, Response $response, $args)
{ {
/** @var \UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager $authorizer */
$authorizer = $this->ci->authorizer;
/** @var \UserFrosting\Sprinkle\Account\Database\Models\Interfaces\UserInterface $currentUser */
$currentUser = $this->ci->currentUser;
/** @var \UserFrosting\Sprinkle\Core\Alert\AlertStream $ms */
$ms = $this->ci->alerts;
$organisation = $this->getOrganisationFromParams($args, true); $organisation = $this->getOrganisationFromParams($args, true);
// If the organisation doesn't exist, return 404 // If the organisation doesn't exist, return 404
@@ -572,12 +558,6 @@ class OrganisationController extends SimpleController
throw new NotFoundException(); 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 // Access-controlled page
if (!$authorizer->checkAccess($currentUser, 'restore_organisation', [ if (!$authorizer->checkAccess($currentUser, 'restore_organisation', [
'organisation' => $organisation, 'organisation' => $organisation,
@@ -585,16 +565,10 @@ class OrganisationController extends SimpleController
throw new ForbiddenException(); 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 // Begin transaction - DB will be rolled back if an exception occurs
Capsule::transaction(function () use ($organisation, $currentUser) { Capsule::transaction(function () use ($organisation, $currentUser) {
if (!$organisation->flag_approved) { if (!$organisation->flag_approved) {
$verification = $this->ci->repoOrganisationApproval->revert($organisation); $this->ci->repoOrganisationApproval->revert($organisation);
} }
$organisation->restore(); $organisation->restore();
@@ -606,9 +580,6 @@ class OrganisationController extends SimpleController
]); ]);
}); });
/** @var \UserFrosting\Sprinkle\Core\Alert\AlertStream $ms */
$ms = $this->ci->alerts;
$ms->addMessageTranslated('success', 'ORGANISATION.RESTORE_SUCCESSFUL', [ $ms->addMessageTranslated('success', 'ORGANISATION.RESTORE_SUCCESSFUL', [
'name' => $organisation->name, 'name' => $organisation->name,
]); ]);
@@ -633,22 +604,23 @@ class OrganisationController extends SimpleController
*/ */
public function getList(Request $request, Response $response, $args) public function getList(Request $request, Response $response, $args)
{ {
// GET parameters
$params = $request->getQueryParams();
/** @var \UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager $authorizer */ /** @var \UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager $authorizer */
$authorizer = $this->ci->authorizer; $authorizer = $this->ci->authorizer;
/** @var \UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */
$classMapper = $this->ci->classMapper;
/** @var \UserFrosting\Sprinkle\Account\Database\Models\Interfaces\UserInterface $currentUser */ /** @var \UserFrosting\Sprinkle\Account\Database\Models\Interfaces\UserInterface $currentUser */
$currentUser = $this->ci->currentUser; $currentUser = $this->ci->currentUser;
// Access-controlled page // Access-controlled page
if (!$authorizer->checkAccess($currentUser, 'uri_organisations')) { if (!$authorizer->checkAccess($currentUser, 'uri_organisations')) {
throw new ForbiddenException(); throw new ForbiddenException();
} }
/** @var \UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */ // GET parameters
$classMapper = $this->ci->classMapper; $params = $request->getQueryParams();
$params['ci'] = $this->ci; $params['ci'] = $this->ci;
@@ -682,22 +654,23 @@ class OrganisationController extends SimpleController
*/ */
public function getListDeleted(Request $request, Response $response, $args) public function getListDeleted(Request $request, Response $response, $args)
{ {
// GET parameters
$params = $request->getQueryParams();
/** @var \UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager $authorizer */ /** @var \UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager $authorizer */
$authorizer = $this->ci->authorizer; $authorizer = $this->ci->authorizer;
/** @var \UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */
$classMapper = $this->ci->classMapper;
/** @var \UserFrosting\Sprinkle\Account\Database\Models\Interfaces\UserInterface $currentUser */ /** @var \UserFrosting\Sprinkle\Account\Database\Models\Interfaces\UserInterface $currentUser */
$currentUser = $this->ci->currentUser; $currentUser = $this->ci->currentUser;
// Access-controlled page // Access-controlled page
if (!$authorizer->checkAccess($currentUser, 'uri_deleted_organisations')) { if (!$authorizer->checkAccess($currentUser, 'uri_deleted_organisations')) {
throw new ForbiddenException(); throw new ForbiddenException();
} }
/** @var \UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */ // GET parameters
$classMapper = $this->ci->classMapper; $params = $request->getQueryParams();
$sprunje = $classMapper->createInstance('organisation_sprunje', $classMapper, $params); $sprunje = $classMapper->createInstance('organisation_sprunje', $classMapper, $params);
$sprunje->extendQuery(function ($query) use ($user) { $sprunje->extendQuery(function ($query) use ($user) {
@@ -722,6 +695,16 @@ class OrganisationController extends SimpleController
*/ */
public function getModalConfirmDelete(Request $request, Response $response, $args) public function getModalConfirmDelete(Request $request, Response $response, $args)
{ {
/** @var \UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager $authorizer */
$authorizer = $this->ci->authorizer;
/** @var \UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */
$classMapper = $this->ci->classMapper;
/** @var \UserFrosting\Sprinkle\Account\Database\Models\Interfaces\UserInterface $currentUser */
$currentUser = $this->ci->currentUser;
// GET parameters // GET parameters
$params = $request->getQueryParams(); $params = $request->getQueryParams();
@@ -732,12 +715,6 @@ class OrganisationController extends SimpleController
throw new NotFoundException(); 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 // Access-controlled page
if (!$authorizer->checkAccess($currentUser, 'delete_organisation', [ if (!$authorizer->checkAccess($currentUser, 'delete_organisation', [
'organisation' => $organisation, 'organisation' => $organisation,
@@ -745,9 +722,6 @@ class OrganisationController extends SimpleController
throw new ForbiddenException(); throw new ForbiddenException();
} }
/** @var \UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */
$classMapper = $this->ci->classMapper;
return $this->ci->view->render($response, 'modals/confirm-delete-organisation.html.twig', [ return $this->ci->view->render($response, 'modals/confirm-delete-organisation.html.twig', [
'organisation' => $organisation, 'organisation' => $organisation,
'form' => [ 'form' => [
@@ -769,6 +743,16 @@ class OrganisationController extends SimpleController
*/ */
public function getModalConfirmPermenentDelete(Request $request, Response $response, $args) public function getModalConfirmPermenentDelete(Request $request, Response $response, $args)
{ {
/** @var \UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager $authorizer */
$authorizer = $this->ci->authorizer;
/** @var \UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */
$classMapper = $this->ci->classMapper;
/** @var \UserFrosting\Sprinkle\Account\Database\Models\Interfaces\UserInterface $currentUser */
$currentUser = $this->ci->currentUser;
// GET parameters // GET parameters
$params = $request->getQueryParams(); $params = $request->getQueryParams();
@@ -779,12 +763,6 @@ class OrganisationController extends SimpleController
throw new NotFoundException(); 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 // Access-controlled page
if (!$authorizer->checkAccess($currentUser, 'permenent_delete_organisation', [ if (!$authorizer->checkAccess($currentUser, 'permenent_delete_organisation', [
'organisation' => $organisation, 'organisation' => $organisation,
@@ -792,9 +770,6 @@ class OrganisationController extends SimpleController
throw new ForbiddenException(); throw new ForbiddenException();
} }
/** @var \UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */
$classMapper = $this->ci->classMapper;
return $this->ci->view->render($response, 'modals/confirm-permenent-delete-organisation.html.twig', [ return $this->ci->view->render($response, 'modals/confirm-permenent-delete-organisation.html.twig', [
'organisation' => $organisation, 'organisation' => $organisation,
'form' => [ 'form' => [
@@ -822,20 +797,21 @@ class OrganisationController extends SimpleController
/** @var \UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager $authorizer */ /** @var \UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager $authorizer */
$authorizer = $this->ci->authorizer; $authorizer = $this->ci->authorizer;
/** @var \UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */
$classMapper = $this->ci->classMapper;
/** @var \UserFrosting\Sprinkle\Account\Database\Models\Interfaces\UserInterface $currentUser */ /** @var \UserFrosting\Sprinkle\Account\Database\Models\Interfaces\UserInterface $currentUser */
$currentUser = $this->ci->currentUser; $currentUser = $this->ci->currentUser;
/** @var \UserFrosting\I18n\Translator $translator */ /** @var \UserFrosting\I18n\Translator $translator */
$translator = $this->ci->translator; $translator = $this->ci->translator;
// Access-controlled page // Access-controlled page
if (!$authorizer->checkAccess($currentUser, 'create_organisation')) { if (!$authorizer->checkAccess($currentUser, 'create_organisation')) {
throw new ForbiddenException(); throw new ForbiddenException();
} }
/** @var \UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */
$classMapper = $this->ci->classMapper;
// Create a dummy organisation to prepopulate fields // Create a dummy organisation to prepopulate fields
$organisation = $classMapper->createInstance('organisation', []); $organisation = $classMapper->createInstance('organisation', []);
@@ -880,6 +856,16 @@ class OrganisationController extends SimpleController
*/ */
public function getModalEdit(Request $request, Response $response, $args) public function getModalEdit(Request $request, Response $response, $args)
{ {
/** @var \UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager $authorizer */
$authorizer = $this->ci->authorizer;
/** @var \UserFrosting\Sprinkle\Account\Database\Models\Interfaces\UserInterface $currentUser */
$currentUser = $this->ci->currentUser;
/** @var \UserFrosting\I18n\Translator $translator */
$translator = $this->ci->translator;
// GET parameters // GET parameters
$params = $request->getQueryParams(); $params = $request->getQueryParams();
@@ -890,18 +876,6 @@ class OrganisationController extends SimpleController
throw new NotFoundException(); throw new NotFoundException();
} }
/** @var \UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */
$classMapper = $this->ci->classMapper;
/** @var \UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager $authorizer */
$authorizer = $this->ci->authorizer;
/** @var \UserFrosting\Sprinkle\Account\Database\Models\Interfaces\UserInterface $currentUser */
$currentUser = $this->ci->currentUser;
/** @var \UserFrosting\I18n\Translator $translator */
$translator = $this->ci->translator;
// Access-controlled resource - check that currentUser has permission to edit basic fields "name", "slug", "description" for this organisation // Access-controlled resource - check that currentUser has permission to edit basic fields "name", "slug", "description" for this organisation
$fieldNames = ['name', 'slug', 'description']; $fieldNames = ['name', 'slug', 'description'];
if (!$authorizer->checkAccess($currentUser, 'update_organisation_field', [ if (!$authorizer->checkAccess($currentUser, 'update_organisation_field', [
@@ -955,6 +929,19 @@ class OrganisationController extends SimpleController
*/ */
public function getModalMerge(Request $request, Response $response, $args) public function getModalMerge(Request $request, Response $response, $args)
{ {
/** @var \UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager $authorizer */
$authorizer = $this->ci->authorizer;
/** @var \UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */
$classMapper = $this->ci->classMapper;
/** @var \UserFrosting\Sprinkle\Account\Database\Models\Interfaces\UserInterface $currentUser */
$currentUser = $this->ci->currentUser;
/** @var \UserFrosting\I18n\Translator $translator */
$translator = $this->ci->translator;
// GET parameters // GET parameters
$params = $request->getQueryParams(); $params = $request->getQueryParams();
@@ -965,18 +952,6 @@ class OrganisationController extends SimpleController
throw new NotFoundException(); throw new NotFoundException();
} }
/** @var \UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */
$classMapper = $this->ci->classMapper;
/** @var \UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager $authorizer */
$authorizer = $this->ci->authorizer;
/** @var \UserFrosting\Sprinkle\Account\Database\Models\Interfaces\UserInterface $currentUser */
$currentUser = $this->ci->currentUser;
/** @var \UserFrosting\I18n\Translator $translator */
$translator = $this->ci->translator;
// Access-controlled resource - check that currentUser has permission to merge organisations. // Access-controlled resource - check that currentUser has permission to merge organisations.
if (!$authorizer->checkAccess($currentUser, 'merge_organisations')) { if (!$authorizer->checkAccess($currentUser, 'merge_organisations')) {
throw new ForbiddenException(); throw new ForbiddenException();
@@ -1020,7 +995,13 @@ class OrganisationController extends SimpleController
* @throws ForbiddenException If user is not authorized to access page * @throws ForbiddenException If user is not authorized to access page
*/ */
public function pageInfo(Request $request, Response $response, $args) public function pageInfo(Request $request, Response $response, $args)
{ { /** @var \UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager $authorizer */
$authorizer = $this->ci->authorizer;
/** @var \UserFrosting\Sprinkle\Account\Database\Models\Interfaces\UserInterface $currentUser */
$currentUser = $this->ci->currentUser;
$organisation = $this->getOrganisationFromParams($args); $organisation = $this->getOrganisationFromParams($args);
// If the organisation no longer exists, forward to main organisation listing page // If the organisation no longer exists, forward to main organisation listing page
@@ -1028,12 +1009,6 @@ class OrganisationController extends SimpleController
throw new NotFoundException(); 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 // Access-controlled page
if (!$authorizer->checkAccess($currentUser, 'uri_organisation', [ if (!$authorizer->checkAccess($currentUser, 'uri_organisation', [
'organisation' => $organisation, 'organisation' => $organisation,

View File

@@ -54,25 +54,29 @@ class OrganisationRegistrationController extends SimpleController
*/ */
public function register(Request $request, Response $response, $args) public function register(Request $request, Response $response, $args)
{ {
// Get POST parameters: name, slug, icon, description
$params = $request->getParsedBody();
/** @var \UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager $authorizer */ /** @var \UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager $authorizer */
$authorizer = $this->ci->authorizer; $authorizer = $this->ci->authorizer;
/** @var \UserFrosting\Sprinkle\Account\Database\Models\Interfaces\UserInterface $currentUser */ /** @var \UserFrosting\Sprinkle\Account\Database\Models\Interfaces\UserInterface $currentUser */
$currentUser = $this->ci->currentUser; $currentUser = $this->ci->currentUser;
/** @var \UserFrosting\Sprinkle\Core\Alert\AlertStream $ms */
$ms = $this->ci->alerts;
/** @var \UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */
$classMapper = $this->ci->classMapper;
/** @var \UserFrosting\Support\Repository\Repository $config */
$config = $this->ci->config;
// Access-controlled page // Access-controlled page
if (!$authorizer->checkAccess($currentUser, 'register_organisation')) { if (!$authorizer->checkAccess($currentUser, 'register_organisation')) {
throw new ForbiddenException(); throw new ForbiddenException();
} }
/** @var \UserFrosting\Sprinkle\Core\Alert\AlertStream $ms */ // Get POST parameters: name, slug, icon, description
$ms = $this->ci->alerts; $params = $request->getParsedBody();
/** @var \UserFrosting\Support\Repository\Repository $config */
$config = $this->ci->config;
// Load the request schema // Load the request schema
$schema = new RequestSchema('schema://requests/organisation/create.yaml'); $schema = new RequestSchema('schema://requests/organisation/create.yaml');
@@ -90,9 +94,6 @@ class OrganisationRegistrationController extends SimpleController
$error = true; $error = true;
} }
/** @var \UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */
$classMapper = $this->ci->classMapper;
// Check if name or slug already exists // Check if name or slug already exists
if ($classMapper->getClassMapping('organisation')::findUnique($data['name'], 'name')) { if ($classMapper->getClassMapping('organisation')::findUnique($data['name'], 'name')) {
$ms->addMessageTranslated('danger', 'ORGANISATION.NAME.IN_USE_REGISTER', $data); $ms->addMessageTranslated('danger', 'ORGANISATION.NAME.IN_USE_REGISTER', $data);
@@ -103,6 +104,7 @@ class OrganisationRegistrationController extends SimpleController
return $response->withJson([], 400); return $response->withJson([], 400);
} }
// Generate a unique slug
$slugIncrement = 0; $slugIncrement = 0;
$slugIncrementString = ''; $slugIncrementString = '';
while ($classMapper->getClassMapping('organisation')::findUnique($data['slug'] . $slugIncrementString, 'slug')) { while ($classMapper->getClassMapping('organisation')::findUnique($data['slug'] . $slugIncrementString, 'slug')) {
@@ -111,41 +113,45 @@ class OrganisationRegistrationController extends SimpleController
} }
$data['slug'] .= $slugIncrementString; $data['slug'] .= $slugIncrementString;
// Prefill some default values
$data['flag_approved'] = !$config['organisation.registration.require_approval']; $data['flag_approved'] = !$config['organisation.registration.require_approval'];
$data['registrant_id'] = $currentUser->id;
// All checks passed! log events/activities and create organisation // All checks passed! log events/activities and create organisation
// Begin transaction - DB will be rolled back if an exception occurs // Begin transaction - DB will be rolled back if an exception occurs
Capsule::transaction(function () use ($classMapper, $data, $ms, $currentUser, $config) { Capsule::transaction(function () use ($classMapper, $data, $currentUser, $config) {
// Create the organisation // Create the organisation
$organisation = $classMapper->createInstance('organisation', $data); $organisation = $classMapper->createInstance('organisation', $data);
// Store new organisation to database // Store new organisation to database
$organisation->save(); $organisation->save();
// Attach the members // Attach the creating member
$organisation->members()->attach($currentUser, ['flag_admin' => true]); $organisation->members()->attach($currentUser->id, [
'flag_admin' => 1,
'flag_approved' => 1,
]);
// Save members // If approval is required, generate a token & send an email
$organisation->save(); if ($config['organisation.registration.require_approval']) {
$timeout = $config['organisation.registration.timeout'];
// Try to generate a new approval request
$approval = $this->ci->repoOrganisationApproval->create($organisation, $timeout);
$this->sendApprovalEmail($currentUser, $organisation, $approval->getToken(), $timeout);
}
// Create activity record // Create activity record
$this->ci->userActivityLogger->info("User {$currentUser->user_name} registered organisation {$organisation->name}.", [ $this->ci->userActivityLogger->info("User {$currentUser->user_name} registered organisation {$organisation->name}.", [
'type' => 'organisation_register', 'type' => 'organisation_register',
'user_id' => $currentUser->id, 'user_id' => $currentUser->id,
]); ]);
if ($config['organisation.registration.require_approval']) {
$timeout = $this->ci->config['organisation.registration.timeout'];
// Try to generate a new approval request
$approval = $this->ci->repoOrganisationApproval->create($organisation, $currentUser, $timeout);
$this->sendApprovalEmail($currentUser, $organisation, $approval->getToken());
}
$ms->addMessageTranslated('success', 'ORGANISATION.REGISTRATION.SUCCESSFUL', $data);
}); });
// Generate a success message
$ms->addMessageTranslated('success', 'ORGANISATION.REGISTRATION.SUCCESSFUL', $data);
return $response->withJson([], 200); return $response->withJson([], 200);
} }
@@ -156,7 +162,7 @@ class OrganisationRegistrationController extends SimpleController
* Before doing so, checks that: * Before doing so, checks that:
* 1. The user has permission to cancel this organisation registration request; * 1. The user has permission to cancel this organisation registration request;
* 2. The submitted data is valid. * 2. The submitted data is valid.
* This route requires authentication (and should generally be limited to admins or the root user). * This route requires authentication. (NOTE: This route is only available to the organisation registrant!)
* *
* Request type: DELETE * Request type: DELETE
* *
@@ -170,6 +176,17 @@ class OrganisationRegistrationController extends SimpleController
*/ */
public function cancel(Request $request, Response $response, $args) public function cancel(Request $request, Response $response, $args)
{ {
/** @var \UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager $authorizer */
$authorizer = $this->ci->authorizer;
/** @var \UserFrosting\Sprinkle\Account\Database\Models\Interfaces\UserInterface $currentUser */
$currentUser = $this->ci->currentUser;
/** @var \UserFrosting\Sprinkle\Core\Alert\AlertStream $ms */
$ms = $this->ci->alerts;
// Get the organisation from the uri
$organisation = $this->getOrganisationFromParams($args); $organisation = $this->getOrganisationFromParams($args);
// If the organisation doesn't exist, return 404 // If the organisation doesn't exist, return 404
@@ -177,12 +194,6 @@ class OrganisationRegistrationController extends SimpleController
throw new NotFoundException(); 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 // Access-controlled page
if (!$authorizer->checkAccess($currentUser, 'register_organisation', [ if (!$authorizer->checkAccess($currentUser, 'register_organisation', [
'organisation' => $organisation, 'organisation' => $organisation,
@@ -190,20 +201,16 @@ class OrganisationRegistrationController extends SimpleController
throw new ForbiddenException(); throw new ForbiddenException();
} }
if (!$authorizer->runCallback($currentUser, 'is_organisation_admin', $currentUser->id, $organisation->id)) { // Check the user is registrant of this organisation
if ($organisation->registrant_id == $currentUser->id) {
throw new ForbiddenException(); throw new ForbiddenException();
} }
/** @var \UserFrosting\Support\Repository\Repository $config */
$config = $this->ci->config;
/** @var \UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */
$classMapper = $this->ci->classMapper;
$organisationName = $organisation->name;
// Begin transaction - DB will be rolled back if an exception occurs // Begin transaction - DB will be rolled back if an exception occurs
Capsule::transaction(function () use ($organisation, $organisationName, $currentUser) { Capsule::transaction(function () use ($organisation, $currentUser) {
$organisationName = $organisation->name;
// Delete the organisation
$organisation->delete(true); $organisation->delete(true);
unset($organisation); unset($organisation);
@@ -214,9 +221,7 @@ class OrganisationRegistrationController extends SimpleController
]); ]);
}); });
/** @var \UserFrosting\Sprinkle\Core\Alert\AlertStream $ms */ // Generate a response
$ms = $this->ci->alerts;
$ms->addMessageTranslated('success', 'ORGANISATION.REGISTRATION.CANCEL_SUCCESSFUL', [ $ms->addMessageTranslated('success', 'ORGANISATION.REGISTRATION.CANCEL_SUCCESSFUL', [
'name' => $organisationName, 'name' => $organisationName,
]); ]);
@@ -243,21 +248,22 @@ class OrganisationRegistrationController extends SimpleController
*/ */
public function approve(Request $request, Response $response, $args) public function approve(Request $request, Response $response, $args)
{ {
/** @var \UserFrosting\Sprinkle\Core\Alert\AlertStream $ms */
$ms = $this->ci->alerts;
/** @var \UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */
$classMapper = $this->ci->classMapper;
/** @var \UserFrosting\Support\Repository\Repository $config */
$config = $this->ci->config;
/** @var \UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager $authorizer */ /** @var \UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager $authorizer */
$authorizer = $this->ci->authorizer; $authorizer = $this->ci->authorizer;
/** @var \UserFrosting\Sprinkle\Account\Database\Models\Interfaces\UserInterface $currentUser */ /** @var \UserFrosting\Sprinkle\Account\Database\Models\Interfaces\UserInterface $currentUser */
$currentUser = $this->ci->currentUser; $currentUser = $this->ci->currentUser;
/** @var \UserFrosting\Sprinkle\Core\Alert\AlertStream $ms */
$ms = $this->ci->alerts;
// Access-controlled page
if (!$authorizer->checkAccess($currentUser, 'approve_organisation')) {
throw new ForbiddenException();
}
// Get the organisation from the uri
$organisation = $this->getOrganisationFromParams($args); $organisation = $this->getOrganisationFromParams($args);
// If the organisation doesn't exist, return 404 // If the organisation doesn't exist, return 404
@@ -265,22 +271,30 @@ class OrganisationRegistrationController extends SimpleController
throw new NotFoundException(); throw new NotFoundException();
} }
// Access-controlled page $approval = $this->ci->repoOrganisationApproval->exists($organisation);
if (!$authorizer->checkAccess($currentUser, 'approve_organisation')) { if (!$approval) {
throw new ForbiddenException(); $ms->addMessageTranslated('danger', 'ORGANISATION.REGISTRATION.TOKEN_NOT_FOUND');
return $response->withJson([], 400);
} }
$verification = $this->ci->repoOrganisationApproval->completeWithoutToken($organisation, $currentUser, ['approved' => true]); // Begin transaction - DB will be rolled back if an exception occurs
Capsule::transaction(function () use ($organisation, $currentUser) {
$this->ci->repoOrganisationApproval->completeForOwner($organisation, [
'approver_id' => $currentUser->id,
'approved' => true
]);
$requester = $classMapper->getClassMapping('user')::find($verification->requester_id); // Send a notification email to the registrant
$this->sendApprovedEmail($organisation);
$this->sendApprovedEmail($organisation, $requester); // Record the event in the activity log
$this->ci->userActivityLogger->info("User {$currentUser->user_name} approved the registration request for organisation {$organisation->name}.", [
$this->ci->userActivityLogger->info("User {$currentUser->user_name} approved the registration request for organisation {$organisation->name}.", [ 'type' => 'organisation_approved',
'type' => 'organisation_approved', 'user_id' => $currentUser->id,
'user_id' => $currentUser->id, ]);
]); });
// Generate a response
$ms->addMessageTranslated('success', 'ORGANISATION.REGISTRATION.APPROVED', [ $ms->addMessageTranslated('success', 'ORGANISATION.REGISTRATION.APPROVED', [
'name' => $organisation->name 'name' => $organisation->name
]); ]);
@@ -307,21 +321,19 @@ class OrganisationRegistrationController extends SimpleController
*/ */
public function approveToken(Request $request, Response $response, $args) public function approveToken(Request $request, Response $response, $args)
{ {
/** @var \UserFrosting\Sprinkle\Core\Alert\AlertStream $ms */ /** @var \UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager $authorizer */
$ms = $this->ci->alerts; $authorizer = $this->ci->authorizer;
/** @var \UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */ /** @var \UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */
$classMapper = $this->ci->classMapper; $classMapper = $this->ci->classMapper;
/** @var \UserFrosting\Support\Repository\Repository $config */
$config = $this->ci->config;
/** @var \UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager $authorizer */
$authorizer = $this->ci->authorizer;
/** @var \UserFrosting\Sprinkle\Account\Database\Models\Interfaces\UserInterface $currentUser */ /** @var \UserFrosting\Sprinkle\Account\Database\Models\Interfaces\UserInterface $currentUser */
$currentUser = $this->ci->currentUser; $currentUser = $this->ci->currentUser;
/** @var \UserFrosting\Sprinkle\Core\Alert\AlertStream $ms */
$ms = $this->ci->alerts;
// Access-controlled page // Access-controlled page
if (!$authorizer->checkAccess($currentUser, 'approve_organisation')) { if (!$authorizer->checkAccess($currentUser, 'approve_organisation')) {
throw new ForbiddenException(); throw new ForbiddenException();
@@ -342,39 +354,50 @@ class OrganisationRegistrationController extends SimpleController
if (!$validator->validate($data)) { if (!$validator->validate($data)) {
$ms->addValidationErrors($validator); $ms->addValidationErrors($validator);
return $response->withRedirect($this->ci->router->pathFor('dashboard')); return $response->withRedirect($this->ci->router->pathFor('uri_organisations'));
} }
$verification = $this->ci->repoOrganisationApproval->complete($data['token'], $currentUser, ['approved' => true]); $token = $data['token'];
if (!$verification) { $organisation = $classMapper->getClassMapping('organisation')::query()
->where('id', $this->ci->repoOrganisationApproval->findOwner($token))
->first();
if (!$organisation) {
$ms->addMessageTranslated('danger', 'ORGANISATION.REGISTRATION.TOKEN_NOT_FOUND'); $ms->addMessageTranslated('danger', 'ORGANISATION.REGISTRATION.TOKEN_NOT_FOUND');
return $response->withRedirect($this->ci->router->pathFor('dashboard')); return $response->withRedirect($this->ci->router->pathFor('uri_organisations'));
} }
$organisation = $classMapper->getClassMapping('organisation')::find($verification->organisation_id); // Begin transaction - DB will be rolled back if an exception occurs
$requester = $classMapper->getClassMapping('user')::find($verification->requester_id); Capsule::transaction(function () use ($currentUser, $token, $organisation) {
$this->ci->repoOrganisationApproval->complete($token, [
'approver_id' => $currentUser->id,
'approved' => true
]);
$this->sendApprovedEmail($organisation, $requester); // Send a notification email to the registrant
$this->sendApprovedEmail($organisation);
$this->ci->userActivityLogger->info("User {$currentUser->user_name} approved the registration request for organisation {$organisation->name}.", [ // Record the event in the activity log
'type' => 'organisation_approved', $this->ci->userActivityLogger->info("User {$currentUser->user_name} approved the registration request for organisation {$organisation->name}.", [
'user_id' => $currentUser->id, 'type' => 'organisation_approved',
]); 'user_id' => $currentUser->id,
]);
});
// Generate the response
$ms->addMessageTranslated('success', 'ORGANISATION.REGISTRATION.APPROVED', [ $ms->addMessageTranslated('success', 'ORGANISATION.REGISTRATION.APPROVED', [
'name' => $organisation->name 'name' => $organisation->name
]); ]);
// Forward to login page // Forward to organisations page
return $response->withRedirect($this->ci->router->pathFor('dashboard')); return $response->withRedirect($this->ci->router->pathFor('uri_organisations'));
} }
/** /**
* Denies an organisation registration request. * Denies an organisation registration request.
* *
* Processes the request from the email verification link that was emailed to the organisation administrators, checking that: * Processes the request from the organisation page, checking that:
* 1. The token provided matches an organisation in the database; * 1. The organisation exists;
* 2. The organisation is not already approved; * 2. The organisation is not already approved;
* This route requires authorization. * This route requires authorization.
* *
@@ -389,21 +412,22 @@ class OrganisationRegistrationController extends SimpleController
*/ */
public function deny(Request $request, Response $response, $args) public function deny(Request $request, Response $response, $args)
{ {
/** @var \UserFrosting\Sprinkle\Core\Alert\AlertStream $ms */
$ms = $this->ci->alerts;
/** @var \UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */
$classMapper = $this->ci->classMapper;
/** @var \UserFrosting\Support\Repository\Repository $config */
$config = $this->ci->config;
/** @var \UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager $authorizer */ /** @var \UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager $authorizer */
$authorizer = $this->ci->authorizer; $authorizer = $this->ci->authorizer;
/** @var \UserFrosting\Sprinkle\Account\Database\Models\Interfaces\UserInterface $currentUser */ /** @var \UserFrosting\Sprinkle\Account\Database\Models\Interfaces\UserInterface $currentUser */
$currentUser = $this->ci->currentUser; $currentUser = $this->ci->currentUser;
/** @var \UserFrosting\Sprinkle\Core\Alert\AlertStream $ms */
$ms = $this->ci->alerts;
// Access-controlled page
if (!$authorizer->checkAccess($currentUser, 'approve_organisation')) {
throw new ForbiddenException();
}
// Get the organisation from the uri
$organisation = $this->getOrganisationFromParams($args); $organisation = $this->getOrganisationFromParams($args);
// If the organisation doesn't exist, return 404 // If the organisation doesn't exist, return 404
@@ -411,24 +435,30 @@ class OrganisationRegistrationController extends SimpleController
throw new NotFoundException(); throw new NotFoundException();
} }
// Access-controlled page $approval = $this->ci->repoOrganisationApproval->exists($organisation);
if (!$authorizer->checkAccess($currentUser, 'approve_organisation')) { if (!$approval) {
throw new ForbiddenException(); $ms->addMessageTranslated('danger', 'ORGANISATION.REGISTRATION.TOKEN_NOT_FOUND');
return $response->withJson([], 400);
} }
$verification = $this->ci->repoOrganisationApproval->completeWithoutToken($organisation, $currentUser, ['approved' => false]); // Begin transaction - DB will be rolled back if an exception occurs
Capsule::transaction(function () use ($organisation, $currentUser) {
$this->ci->repoOrganisationApproval->completeForOwner($organisation, [
'approver_id' => $currentUser->id,
'approved' => false
]);
$requester = $classMapper->getClassMapping('user')::find($verification->requester_id); // Send a notification email to the registrant
$this->sendDeniedEmail($organisation);
$this->sendDeniedEmail($organisation, $requester); // Record the event in the activity log
$this->ci->userActivityLogger->info("User {$currentUser->user_name} denied the registration request for organisation {$organisation->name}.", [
$organisation->delete(); 'type' => 'organisation_approved',
'user_id' => $currentUser->id,
$this->ci->userActivityLogger->info("User {$currentUser->user_name} denied the registration request for organisation {$organisation->name}.", [ ]);
'type' => 'organisation_approval', });
'user_id' => $currentUser->id,
]);
// Generate a response
$ms->addMessageTranslated('success', 'ORGANISATION.REGISTRATION.DENIED', [ $ms->addMessageTranslated('success', 'ORGANISATION.REGISTRATION.DENIED', [
'name' => $organisation->name 'name' => $organisation->name
]); ]);
@@ -455,23 +485,18 @@ class OrganisationRegistrationController extends SimpleController
*/ */
public function denyToken(Request $request, Response $response, $args) public function denyToken(Request $request, Response $response, $args)
{ {
/** @var \UserFrosting\Sprinkle\Core\Alert\AlertStream $ms */ /** @var \UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager $authorizer */
$ms = $this->ci->alerts; $authorizer = $this->ci->authorizer;
/** @var \UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */ /** @var \UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */
$classMapper = $this->ci->classMapper; $classMapper = $this->ci->classMapper;
/** @var \UserFrosting\Support\Repository\Repository $config */
$config = $this->ci->config;
/** @var \UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager $authorizer */
$authorizer = $this->ci->authorizer;
/** @var \UserFrosting\Sprinkle\Account\Database\Models\Interfaces\UserInterface $currentUser */ /** @var \UserFrosting\Sprinkle\Account\Database\Models\Interfaces\UserInterface $currentUser */
$currentUser = $this->ci->currentUser; $currentUser = $this->ci->currentUser;
/** @var \UserFrosting\Sprinkle\Core\Alert\AlertStream $ms */
$ms = $this->ci->alerts;
$dashboardPage = $this->ci->router->pathFor('dashboard');
// Access-controlled page // Access-controlled page
if (!$authorizer->checkAccess($currentUser, 'approve_organisation')) { if (!$authorizer->checkAccess($currentUser, 'approve_organisation')) {
@@ -493,33 +518,43 @@ class OrganisationRegistrationController extends SimpleController
if (!$validator->validate($data)) { if (!$validator->validate($data)) {
$ms->addValidationErrors($validator); $ms->addValidationErrors($validator);
return $response->withRedirect($dashboardPage); return $response->withRedirect($this->ci->router->pathFor('uri_organisations'));
} }
$verification = $this->ci->repoOrganisationApproval->complete($data['token'], $currentUser, ['approved' => false]); $token = $data['token'];
if ($verification === false) { $organisation = $classMapper->getClassMapping('organisation')::query()
->where('id', $this->ci->repoOrganisationApproval->findOwner($token))
->first();
if (!$organisation) {
$ms->addMessageTranslated('danger', 'ORGANISATION.REGISTRATION.TOKEN_NOT_FOUND'); $ms->addMessageTranslated('danger', 'ORGANISATION.REGISTRATION.TOKEN_NOT_FOUND');
return $response->withRedirect($this->ci->router->pathFor('uri_organisations'));
return $response->withRedirect($dashboardPage);
} }
$organisation = $classMapper->getClassMapping('organisation')::find($verification->organisation_id); // Begin transaction - DB will be rolled back if an exception occurs
$requester = $classMapper->getClassMapping('user')::find($verification->requester_id); Capsule::transaction(function () use ($currentUser, $token, $organisation) {
$this->ci->repoOrganisationApproval->complete($token, [
'approver_id' => $currentUser->id,
'approved' => false
]);
$this->sendDeniedEmail($organisation, $requester); // Send a notification email to the registrant
$this->sendDeniedEmail($organisation);
$this->ci->userActivityLogger->info("User {$currentUser->user_name} denied the registration request for organisation {$organisation->name}.", [ // Record the event in the activity log
'type' => 'organisation_approval', $this->ci->userActivityLogger->info("User {$currentUser->user_name} denied the registration request for organisation {$organisation->name}.", [
'user_id' => $currentUser->id, 'type' => 'organisation_approved',
]); 'user_id' => $currentUser->id,
]);
});
// Generate the response
$ms->addMessageTranslated('success', 'ORGANISATION.REGISTRATION.DENIED', [ $ms->addMessageTranslated('success', 'ORGANISATION.REGISTRATION.DENIED', [
'name' => $organisation->name 'name' => $organisation->name
]); ]);
// Forward to login page // Forward to organisations page
return $response->withRedirect($dashboardPage); return $response->withRedirect($this->ci->router->pathFor('uri_organisations'));
} }
@@ -542,20 +577,21 @@ class OrganisationRegistrationController extends SimpleController
/** @var \UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager $authorizer */ /** @var \UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager $authorizer */
$authorizer = $this->ci->authorizer; $authorizer = $this->ci->authorizer;
/** @var \UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */
$classMapper = $this->ci->classMapper;
/** @var \UserFrosting\Sprinkle\Account\Database\Models\Interfaces\UserInterface $currentUser */ /** @var \UserFrosting\Sprinkle\Account\Database\Models\Interfaces\UserInterface $currentUser */
$currentUser = $this->ci->currentUser; $currentUser = $this->ci->currentUser;
/** @var \UserFrosting\I18n\Translator $translator */ /** @var \UserFrosting\I18n\Translator $translator */
$translator = $this->ci->translator; $translator = $this->ci->translator;
// Access-controlled page // Access-controlled page
if (!$authorizer->checkAccess($currentUser, 'register_organisation')) { if (!$authorizer->checkAccess($currentUser, 'register_organisation')) {
throw new ForbiddenException(); throw new ForbiddenException();
} }
/** @var \UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */
$classMapper = $this->ci->classMapper;
// Create a dummy organisation to prepopulate fields // Create a dummy organisation to prepopulate fields
$organisation = $classMapper->createInstance('organisation', []); $organisation = $classMapper->createInstance('organisation', []);
@@ -569,6 +605,7 @@ class OrganisationRegistrationController extends SimpleController
$schema = new RequestSchema('schema://requests/organisation/create.yaml'); $schema = new RequestSchema('schema://requests/organisation/create.yaml');
$validator = new JqueryValidationAdapter($schema, $this->ci->translator); $validator = new JqueryValidationAdapter($schema, $this->ci->translator);
// Generate the response
return $this->ci->view->render($response, 'modals/organisation.html.twig', [ return $this->ci->view->render($response, 'modals/organisation.html.twig', [
'organisation' => $organisation, 'organisation' => $organisation,
'form' => [ 'form' => [
@@ -596,6 +633,12 @@ class OrganisationRegistrationController extends SimpleController
*/ */
public function getModalConfirmCancel(Request $request, Response $response, $args) public function getModalConfirmCancel(Request $request, Response $response, $args)
{ {
/** @var \UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager $authorizer */
$authorizer = $this->ci->authorizer;
/** @var \UserFrosting\Sprinkle\Account\Database\Models\Interfaces\UserInterface $currentUser */
$currentUser = $this->ci->currentUser;
// GET parameters // GET parameters
$params = $request->getQueryParams(); $params = $request->getQueryParams();
@@ -606,12 +649,6 @@ class OrganisationRegistrationController extends SimpleController
throw new NotFoundException(); 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 // Access-controlled page
if (!$authorizer->checkAccess($currentUser, 'register_organisation', [ if (!$authorizer->checkAccess($currentUser, 'register_organisation', [
'organisation' => $organisation, 'organisation' => $organisation,
@@ -619,14 +656,12 @@ class OrganisationRegistrationController extends SimpleController
throw new ForbiddenException(); throw new ForbiddenException();
} }
// Access-controlled page // Check the user is registrant of this organisation
if (!$authorizer->runCallback($currentUser, 'is_organisation_admin', $currentUser->id, $organisation->id)) { if ($organisation->registrant_id == $currentUser->id) {
throw new ForbiddenException(); throw new ForbiddenException();
} }
/** @var \UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */ // Generate the response
$classMapper = $this->ci->classMapper;
return $this->ci->view->render($response, 'modals/confirm-cancel-organisation-registration.html.twig', [ return $this->ci->view->render($response, 'modals/confirm-cancel-organisation-registration.html.twig', [
'organisation' => $organisation, 'organisation' => $organisation,
'form' => [ 'form' => [
@@ -642,10 +677,8 @@ class OrganisationRegistrationController extends SimpleController
* @param UserInterface $requester The user to send the confirmation of registration to * @param UserInterface $requester The user to send the confirmation of registration to
* @param UserInterface $organisation The organisation to send the email for * @param UserInterface $organisation The organisation to send the email for
*/ */
protected function sendApprovalEmail(UserInterface $requester, OrganisationInterface $organisation, $token) protected function sendApprovalEmail(UserInterface $requester, OrganisationInterface $organisation, $token, $timeout)
{ {
$timeout = $this->ci->config['organisation.registration.timeout'];
// Create and send approval email // Create and send approval email
$message = new TwigMailMessage($this->ci->view, 'mail/organisation-approval-request.html.twig'); $message = new TwigMailMessage($this->ci->view, 'mail/organisation-approval-request.html.twig');
@@ -657,18 +690,19 @@ class OrganisationRegistrationController extends SimpleController
'approval_expiration' => ($timeout > 0 ? floor($timeout / 86400) . ' days' : false), 'approval_expiration' => ($timeout > 0 ? floor($timeout / 86400) . ' days' : false),
]); ]);
// Find the organisations admins or, if there are none, the site admin.
$role = $this->ci->classMapper->getClassMapping('role')::where('slug', 'organisations-admin')->with('users')->first(); $role = $this->ci->classMapper->getClassMapping('role')::where('slug', 'organisations-admin')->with('users')->first();
if ($role->users()->count() == 0) { if ($role->users()->count() == 0) {
$role = $this->ci->classMapper->getClassMapping('role')::where('slug', 'site-admin')->with('users')->first(); $role = $this->ci->classMapper->getClassMapping('role')::where('slug', 'site-admin')->with('users')->first();
} }
// Generate a list of recipients and send the email addressed to each.
$recipients = $role->users()->get(); $recipients = $role->users()->get();
foreach($recipients as $recipient) { foreach($recipients as $recipient) {
$message->addEmailRecipient(new EmailRecipient($recipient->email, $recipient->full_name)); $message->addEmailRecipient(new EmailRecipient($recipient->email, $recipient->full_name));
$message->addParams([ 'recipient' => $recipient ]); $message->addParams([ 'recipient' => $recipient ]);
$this->ci->mailer->send($message); $this->ci->mailer->send($message);
$message->addParams([ 'recipient' => null ]);
$message->clearRecipients(); $message->clearRecipients();
} }
} }
@@ -679,8 +713,10 @@ class OrganisationRegistrationController extends SimpleController
* @param UserInterface $requester The user to send the approved notice to * @param UserInterface $requester The user to send the approved notice to
* @param UserInterface $organisation The organisation to send the email for * @param UserInterface $organisation The organisation to send the email for
*/ */
protected function sendApprovedEmail(OrganisationInterface $organisation, UserInterface $requester) protected function sendApprovedEmail(OrganisationInterface $organisation)
{ {
$requester = $organisation->registrant()->first();
$message = new TwigMailMessage($this->ci->view, 'mail/organisation-approval-approved.html.twig'); $message = new TwigMailMessage($this->ci->view, 'mail/organisation-approval-approved.html.twig');
$message->from($this->ci->config['address_book.admin']) $message->from($this->ci->config['address_book.admin'])
@@ -699,8 +735,13 @@ class OrganisationRegistrationController extends SimpleController
* @param UserInterface $requester The user to send the denial notice to * @param UserInterface $requester The user to send the denial notice to
* @param UserInterface $organisation The organisation to send the email for * @param UserInterface $organisation The organisation to send the email for
*/ */
protected function sendDeniedEmail(OrganisationInterface $organisation, UserInterface $requester) protected function sendDeniedEmail(OrganisationInterface $organisation)
{ {
$requester = $organisation->registrant()->first();
$this->ci->debugLogger->debug('organisation', [$organisation]);
$this->ci->debugLogger->debug('requester', [$requester]);
$message = new TwigMailMessage($this->ci->view, 'mail/organisation-approval-denied.html.twig'); $message = new TwigMailMessage($this->ci->view, 'mail/organisation-approval-denied.html.twig');
$message->from($this->ci->config['address_book.admin']) $message->from($this->ci->config['address_book.admin'])

View File

@@ -0,0 +1,68 @@
<?php
/*
* AVSDev UF Organisations (https://avsdev.uk)
*
* @link https://git.avsdev.uk/avsdev/sprinkle-organisations
* @license https://git.avsdev.uk/avsdev/sprinkle-organisations/blob/master/LICENSE.md (LGPL-3.0 License)
*/
namespace UserFrosting\Sprinkle\Organisations\Database\Migrations\v003;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Capsule\Manager as DB;
use UserFrosting\Sprinkle\Core\Database\Migration;
/**
* Organisations approvals table migration
* Drops the requester_id (moved to organisations as registrant_id) and organisation_id in favor of an owner_id.
* Version 1.0.0.
*
* @author Craig Williams (https://avsdev.uk)
*/
class UpdateOrganisationApprovalsTable extends Migration
{
/**
* {@inheritdoc}
*/
public static $dependencies = [
'\UserFrosting\Sprinkle\Organisations\Database\Migrations\v002\UpdateOrganisationApprovalsTable',
];
/**
* {@inheritdoc}
*/
public function up()
{
if ($this->schema->hasTable('organisation_approvals')) {
DB::table('organisation_approvals')->delete();
$this->schema->table('organisation_approvals', function (Blueprint $table) {
$table->dropForeign(['requester_id']);
$table->dropForeign(['organisation_id']);
$table->dropIndex(['requester_id']);
$table->dropColumn(['requester_id']);
$table->dropColumn(['organisation_id']);
$table->integer('owner_id')->unsigned();
$table->index('owner_id');
});
}
}
/**
* {@inheritdoc}
*/
public function down()
{
DB::table('organisation_approvals')->delete();
$this->schema->table('organisation_approvals', function (Blueprint $table) {
$table->dropIndex(['owner_id']);
$table->dropColumn('owner_id')->unsigned();
$table->integer('requester_id')->unsigned();
$table->integer('organisation_id')->unsigned();
$table->index('requester_id');
$table->foreign('requester_id')->references('id')->on('users');
$table->foreign('organisation_id')->references('id')->on('organisations');
});
}
}

View File

@@ -0,0 +1,55 @@
<?php
/*
* AVSDev UF Organisations (https://avsdev.uk)
*
* @link https://git.avsdev.uk/avsdev/sprinkle-organisations
* @license https://git.avsdev.uk/avsdev/sprinkle-organisations/blob/master/LICENSE.md (LGPL-3.0 License)
*/
namespace UserFrosting\Sprinkle\Organisations\Database\Migrations\v003;
use Illuminate\Database\Schema\Blueprint;
use UserFrosting\Sprinkle\Organisations\Database\Models\Organisation;
use UserFrosting\Sprinkle\Core\Database\Migration;
use UserFrosting\Sprinkle\Core\Facades\Seeder;
/**
* Organisations table migration
* Adds a `registrant_id` column to the `organisations` table
* Version 1.0.0.
*
* @author Craig Williams (https://avsdev.uk)
*/
class UpdateOrganisationsTable extends Migration
{
/**
* {@inheritdoc}
*/
public static $dependencies = [
'\UserFrosting\Sprinkle\Account\Database\Migrations\v400\UsersTable',
'\UserFrosting\Sprinkle\Organisations\Database\Migrations\v002\UpdateOrganisationsTable',
];
/**
* {@inheritdoc}
*/
public function up()
{
if ($this->schema->hasTable('organisations')) {
$this->schema->table('organisations', function (Blueprint $table) {
$table->integer('registrant_id')->unsigned()->nullable();
});
}
}
/**
* {@inheritdoc}
*/
public function down()
{
$this->schema->table('organisations', function (Blueprint $table) {
$table->dropColumn('registrant_id');
});
}
}

View File

@@ -1,111 +0,0 @@
<?php
/*
* AVSDev UF Organisations (https://avsdev.uk)
*
* @link https://git.avsdev.uk/avsdev/sprinkle-organisations
* @license https://git.avsdev.uk/avsdev/sprinkle-organisations/blob/master/LICENSE.md (LGPL-3.0 License)
*/
namespace UserFrosting\Sprinkle\Organisations\Database\Models;
use Illuminate\Database\Capsule\Manager as DB;
use UserFrosting\Sprinkle\Core\Database\Models\Model;
/**
* Base organisation approval class.
*
* Represents a pending approval request which is tied to a user/organisation pair (membership, registration etc)
*
* @author Craig Williams (https://avsdev.uk)
*
* @property int $requester_id
* @property int $organisation_id
* @property hash $token
* @property bool $completed
* @property datetime $expires_at
* @property datetime $completed_at
* @property int $approver_id
*/
class BaseOrganisationApproval extends Model
{
protected $fillable = [
'requester_id',
'organisation_id',
'hash',
'completed',
'expires_at',
'completed_at',
'approver_id,'
];
/**
* @var bool Enable timestamps for Verifications.
*/
public $timestamps = true;
/**
* @var string Stores the raw (unhashed) token when created, so that it can be emailed out to the user. NOT persisted.
*/
protected $token;
/**
* @return string
*/
public function getToken()
{
return $this->token;
}
/**
* @param string $value
*
* @return self
*/
public function setToken($value)
{
$this->token = $value;
return $this;
}
/**
* Get the user associated with this request of this approval.
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function requester()
{
/** @var \UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */
$classMapper = static::$ci->classMapper;
return $this->belongsTo($classMapper->getClassMapping('user'), 'requester_id');
}
/**
* Get the organisation associated with this approval request.
*
* @return \Illuminate\Database\Eloquent\Relations\belongsTo
*/
public function organisation()
{
/** @var \UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */
$classMapper = static::$ci->classMapper;
return $this->belongsTo($classMapper->getClassMapping('organisation'), 'organisation_id');
}
/**
* Get the user associated with this approval or denial of this request.
*
* @return \Illuminate\Database\Eloquent\Relations\belongsTo
*/
public function approver()
{
/** @var \UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */
$classMapper = static::$ci->classMapper;
return $this->belongsTo($classMapper->getClassMapping('user'), 'approver_id');
}
}

View File

@@ -13,6 +13,7 @@ use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Database\Capsule\Manager as DB; use Illuminate\Database\Capsule\Manager as DB;
use UserFrosting\Sprinkle\Core\Database\Models\Model; use UserFrosting\Sprinkle\Core\Database\Models\Model;
use UserFrosting\Sprinkle\Organisations\Database\Models\Interfaces\OrganisationInterface; use UserFrosting\Sprinkle\Organisations\Database\Models\Interfaces\OrganisationInterface;
use UserFrosting\Sprinkle\Organisations\Repository\Interfaces\TokenOwnerInterface;
/** /**
* Organisation Class. * Organisation Class.
@@ -28,7 +29,7 @@ use UserFrosting\Sprinkle\Organisations\Database\Models\Interfaces\OrganisationI
* @property timestamp $updated_at * @property timestamp $updated_at
* @property timestamp $deleted_at * @property timestamp $deleted_at
*/ */
class Organisation extends Model implements OrganisationInterface class Organisation extends Model implements OrganisationInterface, TokenOwnerInterface
{ {
use SoftDeletes; use SoftDeletes;
@@ -46,6 +47,7 @@ class Organisation extends Model implements OrganisationInterface
'slug', 'slug',
'name', 'name',
'description', 'description',
'registrant_id',
'flag_approved', 'flag_approved',
'deleted_at', 'deleted_at',
]; ];
@@ -82,6 +84,25 @@ class Organisation extends Model implements OrganisationInterface
*/ */
public $timestamps = true; public $timestamps = true;
/**
* Get the mapping id, as per TokenOwnerInterface
*/
public function getId()
{
return $this->id;
}
/**
* Get the user who registered this organisation
*/
public function registrant()
{
/** @var \UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */
$classMapper = static::$ci->classMapper;
return $this->hasOne($classMapper->getClassMapping('user'), 'id', 'registrant_id');
}
/** /**
* Get a list of members within this organisation. * Get a list of members within this organisation.
@@ -93,8 +114,7 @@ class Organisation extends Model implements OrganisationInterface
return $this return $this
->belongsToMany($classMapper->getClassMapping('user'), 'organisation_members', 'organisation_id', 'user_id') ->belongsToMany($classMapper->getClassMapping('user'), 'organisation_members', 'organisation_id', 'user_id')
->where('flag_admin', false) ->where('flag_admin', false);
->withTimestamps();
} }
/** /**
@@ -107,8 +127,7 @@ class Organisation extends Model implements OrganisationInterface
return $this return $this
->belongsToMany($classMapper->getClassMapping('user'), 'organisation_members', 'organisation_id', 'user_id') ->belongsToMany($classMapper->getClassMapping('user'), 'organisation_members', 'organisation_id', 'user_id')
->where('flag_admin', true) ->where('flag_admin', true);
->withTimestamps();
} }
/** /**
@@ -127,7 +146,7 @@ class Organisation extends Model implements OrganisationInterface
static::$ci->get('organisation.beforeDelete')($this); static::$ci->get('organisation.beforeDelete')($this);
// Remove all organisation tokens // Remove all organisation tokens
$classMapper->getClassMapping('organisation_approval')::where('organisation_id', $this->id)->delete(); $classMapper->getClassMapping('organisation_approval')::where('owner_id', $this->id)->delete();
// Remove all member associations // Remove all member associations
$this->members()->detach(); $this->members()->detach();

View File

@@ -9,8 +9,7 @@
namespace UserFrosting\Sprinkle\Organisations\Database\Models; namespace UserFrosting\Sprinkle\Organisations\Database\Models;
use Illuminate\Database\Capsule\Manager as DB; use UserFrosting\Sprinkle\Core\Database\Models\Model;
use UserFrosting\Sprinkle\Organisations\Database\Models\BaseOrganisationApproval;
/** /**
* Organisation Approval Class. * Organisation Approval Class.
@@ -18,12 +17,111 @@ use UserFrosting\Sprinkle\Organisations\Database\Models\BaseOrganisationApproval
* Represents a pending organisation approval request. * Represents a pending organisation approval request.
* *
* @author Craig Williams (https://avsdev.uk) * @author Craig Williams (https://avsdev.uk)
*
* @property int $owner_id
* @property hash $token
* @property bool $completed
* @property datetime $expires_at
* @property datetime $completed_at
* @property int $approver_id
*/ */
class OrganisationApproval extends Model
class OrganisationApproval extends BaseOrganisationApproval
{ {
/** /**
* @var string The name of the table for the current model. * @var string The name of the table for the current model.
*/ */
protected $table = 'organisation_approvals'; protected $table = 'organisation_approvals';
/**
* Fields that should be mass-assignable when creating a new Organisation.
*
* @var string[]
*/
protected $fillable = [
'owner_id',
'hash',
'completed',
'expires_at',
'completed_at',
'approver_id',
];
/**
* @var bool Enable timestamps for Verifications.
*/
public $timestamps = true;
/**
* @var string Stores the raw (unhashed) token when created, so that it can be emailed out to the user. NOT persisted.
*/
protected $token;
/**
* @return string
*/
public function getToken()
{
return $this->token;
}
/**
* @param string $value
*
* @return self
*/
public function setToken($value)
{
$this->token = $value;
return $this;
}
/**
* Get the owner.
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function owner()
{
return $this->organisation();
}
/**
* Get the organisation of this approval.
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function organisation()
{
/** @var \UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */
$classMapper = static::$ci->classMapper;
return $this->belongsTo($classMapper->getClassMapping('organisation'), 'owner_id');
}
/**
* Get the requester of this approval.
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function requester()
{
return $this->organisation->registrant();
}
/**
* Get the user associated with this approval or denial of this request.
*
* @return \Illuminate\Database\Eloquent\Relations\belongsTo
*/
public function approver()
{
/** @var \UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */
$classMapper = static::$ci->classMapper;
return $this->belongsTo($classMapper->getClassMapping('user'), 'approver_id');
}
} }

View File

@@ -37,7 +37,11 @@ class User extends UFUser
/** @var \UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */ /** @var \UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */
$classMapper = static::$ci->classMapper; $classMapper = static::$ci->classMapper;
return $this->belongsToMany($classMapper->getClassMapping('organisation'), 'organisation_members', 'user_id', 'organisation_id')->orderBy('organisations.name', 'asc')->withPivot('flag_admin'); return $this->belongsToMany(
$classMapper->getClassMapping('organisation'), 'organisation_members', 'user_id', 'organisation_id'
)
->orderBy('organisations.name', 'asc')
->withPivot('flag_admin');
} }
/** /**
@@ -53,7 +57,16 @@ class User extends UFUser
$classMapper = static::$ci->classMapper; $classMapper = static::$ci->classMapper;
if ($hardDelete) { if ($hardDelete) {
// TODO: remove user from organisations? $classMapper->getClassMapping('organisation')::query()
->where('registrant_id', $this->id)
->update(['registrant_id' => null]);
$classMapper->getClassMapping('organisation_approval')::query()
->where('approver_id', $this->id)
->update(['approver_id' => null]);
$this->organisations()->detach();
$this->refresh();
} }
return parent::delete(); return parent::delete();

View File

@@ -9,12 +9,6 @@
namespace UserFrosting\Sprinkle\Organisations\Repository; namespace UserFrosting\Sprinkle\Organisations\Repository;
use Carbon\Carbon;
use Illuminate\Database\Capsule\Manager as Capsule;
use UserFrosting\Sprinkle\Organisations\Database\Models\Interfaces\OrganisationInterface;
use UserFrosting\Sprinkle\Account\Database\Models\Interfaces\UserInterface;
use UserFrosting\Sprinkle\Account\Repository\TokenRepository;
use UserFrosting\Sprinkle\Core\Database\Models\Model;
use UserFrosting\Sprinkle\Core\Util\ClassMapper; use UserFrosting\Sprinkle\Core\Util\ClassMapper;
/** /**
@@ -22,215 +16,38 @@ use UserFrosting\Sprinkle\Core\Util\ClassMapper;
* *
* @author Craig Williams (https://avsdev.uk) * @author Craig Williams (https://avsdev.uk)
*/ */
class OrganisationApprovalRepository extends TokenRepository class OrganisationApprovalRepository extends BasicTokenRepository
{ {
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */
protected $modelIdentifier = 'organisation_approval'; protected $modelIdentifier = 'organisation_approval';
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */
public function complete($token, UserInterface $approver, $params = []) protected function updateTokenOwner($owner_id, $model, $args)
{ {
// Hash the token for the stored version $organisation = $this->classMapper->getClassMapping('organisation')::findUnique($owner_id, 'id', false);
$hash = hash($this->algorithm, $token);
// Find an unexpired, incomplete token for the specified hash if (!$organisation) {
$model = $this->classMapper->getClassMapping($this->modelIdentifier)::query()
->where('hash', $hash)
->where('completed', false)
->where(function($query) {
return $query->where('expires_at', '>', Carbon::now())->orWhereNull('expires_at');
})
->first();
if ($model === null) {
return false; return false;
} }
// Fetch user for this token // If specified, recored the approver. This assumes the model has an approver_id field which it may not...
$organisation = $this->classMapper->getClassMapping('organisation')::find($model->organisation_id); if ($args['approver_id']) {
$requester = $this->classMapper->getClassMapping('user')::find($model->requester_id); $model->approver_id = $args['approver_id'];
if (!$organisation || !$requester) {
return false;
} }
// Begin transaction - DB will be rolled back if an exception occurs
Capsule::transaction(function () use ($model, $organisation, $requester, $approver, $params) {
$this->updateOrganisation($organisation, $requester, $approver, $params);
$model->fill([
'completed' => true,
'completed_at' => Carbon::now(),
]);
$model->approver_id = $approver->id;
$model->save();
});
return $model;
}
/**
* Completes a token request without requiring the token (admin overrride)
*/
public function completeWithoutToken(OrganisationInterface $organisation, UserInterface $approver, $params = [])
{
$model = $this->classMapper->getClassMapping($this->modelIdentifier)::query()
->where('organisation_id', $organisation->id)
->where('completed', false)
->first();
if ($model === null) {
return false;
}
// Fetch user for this token
$requester = $this->classMapper->getClassMapping('user')::find($model->requester_id);
if (!$requester) {
return false;
}
// Begin transaction - DB will be rolled back if an exception occurs
Capsule::transaction(function () use ($model, $organisation, $requester, $approver, $params) {
$this->updateOrganisation($organisation, $requester, $approver, $params);
$model->fill([
'completed' => true,
'completed_at' => Carbon::now(),
]);
$model->approver_id = $approver->id;
$model->save();
});
return $model;
}
/**
* Reverts a token request without requiring the token (admin overrride)
*/
public function revert(OrganisationInterface $organisation)
{
$model = $this->classMapper->getClassMapping($this->modelIdentifier)::query()
->where('organisation_id', $organisation->id)
->where('completed', true)
->first();
if ($model === null) {
return false;
}
// Begin transaction - DB will be rolled back if an exception occurs
Capsule::transaction(function () use ($model) {
$model->fill([
'completed' => false,
'completed_at' => null,
'approver_id' => null,
]);
$model->save();
});
return $model;
}
/**
* {@inheritdoc}
*/
public function create(OrganisationInterface $organisation, UserInterface $requester, $timeout)
{
// Remove any previous tokens for this organisation
$this->removeExisting($organisation);
// Compute expiration time
$expiresAt = Carbon::now()->addSeconds($timeout);
$model = $this->classMapper->createInstance($this->modelIdentifier);
// Generate a random token
$model->setToken($this->generateRandomToken());
// Hash the password reset token for the stored version
$hash = hash($this->algorithm, $model->getToken());
$model->fill([
'organisation_id' => $organisation->id,
'requester_id' => $requester->id,
'hash' => $hash,
'completed' => false,
'expires_at' => ($timeout >= 0 ? $expiresAt : null),
]);
$model->save();
return $model;
}
/**
* {@inheritdoc}
*/
public function exists(OrganisationInterface $organisation, UserInterface $requester = null, $token = null)
{
$model = $this->classMapper->getClassMapping($this->modelIdentifier)::query()
->where('organisation_id', $organisation->id)
->where('completed', false)
->where(function($query) {
return $query->where('expires_at', '>', Carbon::now())->orWhereNull('expires_at');
});
if ($token) {
// get token hash
$hash = hash($this->algorithm, $token);
$model->where('hash', $hash);
}
if ($requester) {
$model->where('requester_id', $requester->id);
}
return $model->first() ?: false;
}
/**
* {@inheritdoc}
*/
protected function removeExisting(OrganisationInterface $organisation, UserInterface $requester = null)
{
$model = $this->classMapper->getClassMapping($this->modelIdentifier)::query()
->where('organisation_id', $organisation->id);
if ($requester) {
$model->where('requester_id', $requester->id);
}
return $model->delete();
}
/**
* {@inheritdoc}
*/
protected function updateOrganisation(OrganisationInterface $organisation, UserInterface $requester, UserInterface $approver, $args)
{
if ($args['approved']) { if ($args['approved']) {
// Mark the organisation as approved
$organisation->flag_approved = 1; $organisation->flag_approved = 1;
$organisation->save(); $organisation->save();
} else {
// Soft delete the organisation
$organisation->delete(false);
} }
}
/** return true;
* Overridden
*/
protected function updateUser(UserInterface $user, $args)
{
return false;
} }
} }

View File

@@ -156,7 +156,7 @@ class ServicesProvider
*/ */
$container['organisation.beforeDelete'] = function ($c) { $container['organisation.beforeDelete'] = function ($c) {
/* /*
* This method is invoked when an organisation is about to be merged * This method is invoked when an organisation is about to be deleted
* *
* Returns a callback that handles re-owning any organisation objects. * Returns a callback that handles re-owning any organisation objects.
* Throwing exceptions is allowed but not recommended. This method is triggered within a Capsule context. * Throwing exceptions is allowed but not recommended. This method is triggered within a Capsule context.
@@ -168,7 +168,7 @@ class ServicesProvider
}; };
/* /*
* Repository for approval requests. * Repository for organisation registration approval requests.
* *
* @return \UserFrosting\Sprinkle\Organisations\Repository\OrganisationApprovalRepository * @return \UserFrosting\Sprinkle\Organisations\Repository\OrganisationApprovalRepository
*/ */
@@ -176,7 +176,7 @@ class ServicesProvider
$classMapper = $c->classMapper; $classMapper = $c->classMapper;
$config = $c->config; $config = $c->config;
$repo = new OrganisationApprovalRepository($classMapper, $config['verification.algorithm']); $repo = new OrganisationApprovalRepository($classMapper, $config['verification.algorithm'], $c['tokenLogger'], $config['debug.tokens']);
return $repo; return $repo;
}; };